baro-ai 0.70.11 → 0.70.13

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.
@@ -22283,38 +22283,29 @@ If the goal is NON-TRIVIAL: decompose normally per the rules below.
22283
22283
  When in doubt, prefer FEWER stories over more. A single 2-file story is better
22284
22284
  than two artificially-split 1-file stories.
22285
22285
 
22286
- PARALLELISM IS THE WHOLE POINT \u2014 DO NOT BUILD LINEAR CHAINS:
22287
- baro spawns one agent per DAG level concurrently. A plan where every story
22288
- depends on the previous one (S1 \u2192 S2 \u2192 S3 \u2192 S4 \u2192 S5) collapses that into a
22289
- sequential run and wastes the orchestrator. This is a BUG in the plan, not a
22290
- feature. Treat \`dependsOn\` as expensive: every edge you add removes a parallel
22291
- slot. Only add a dependency when story B literally cannot start until A is
22292
- merged because B imports a symbol A defines, modifies a file A creates, or
22293
- relies on a schema A introduces.
22294
-
22295
- Heuristics for parallel-friendly DAGs:
22296
- - Stories touching disjoint files/modules \u2192 NO dependency, same level.
22297
- - Multiple provider/integration/feature variants of the same shape (e.g.
22298
- "Add provider X", "Add provider Y", "Add provider Z" after a shared
22299
- abstraction exists) \u2192 all parallel siblings.
22300
- - Tests, docs, and config changes that don't read newly-introduced symbols
22301
- \u2192 usually parallel to the implementation, not downstream of it.
22302
- - "Wiring" stories that connect already-existing pieces \u2192 depend only on
22303
- the pieces they actually wire, not on every prior story.
22304
-
22305
- Anti-patterns (DO NOT DO):
22306
- - Decorative chains: S2 dependsOn S1, S3 dependsOn S2, S4 dependsOn S3 with
22307
- no real symbol/import/schema reason. If you cannot name the specific
22308
- symbol or file that forces the order, REMOVE the edge.
22309
- - "S1 = setup, then everything dependsOn S1" when S1 only adds an
22310
- interface/abstraction the other stories don't actually consume.
22311
- - One-story-per-level "staircase" plans for goals that obviously have
22312
- independent pieces (e.g. five new providers, three new endpoints, four
22313
- new components).
22314
-
22315
- Target shape: most non-trivial plans should have AT LEAST one DAG level with
22316
- 2+ siblings. If your output is a single linear chain, re-examine \u2014 you almost
22317
- certainly over-specified \`dependsOn\`.
22286
+ RUN SHAPE:
22287
+ baro can run focused, sequential, or parallel work. Parallelism is valuable only
22288
+ when stories have independent write surfaces. Do NOT create parallel stories
22289
+ that edit the same file, component, state machine, schema, or API contract.
22290
+
22291
+ Mode semantics:
22292
+ - focused: exactly one story. Use this for one bug, one UI/component issue,
22293
+ one failing build/runtime error, or anything likely centered on a shared
22294
+ file/surface. The story should carry enough context to finish the PR.
22295
+ - sequential: several small stories with real dependencies. Use this when the
22296
+ work is one feature that must be implemented in ordered steps.
22297
+ - parallel: a DAG with sibling stories only where you can prove independence
22298
+ (different files/modules/contracts). Parallelism requires a reason; do not
22299
+ fan out just to use agents.
22300
+
22301
+ Dependency rules:
22302
+ - Stories touching disjoint files/modules may run in parallel.
22303
+ - Stories touching the same file/component/state/API must be sequential or one
22304
+ focused story.
22305
+ - Only add dependsOn when story B literally cannot start until A is merged
22306
+ because B imports a symbol A defines, modifies a file A creates, or relies
22307
+ on a schema/API A introduces.
22308
+ - Decorative chains are bad, but unsafe parallel edits are worse.
22318
22309
 
22319
22310
  Output ONLY valid JSON matching this exact schema (no markdown, no explanation, just JSON):
22320
22311
  {
@@ -22375,6 +22366,139 @@ Rules:
22375
22366
  - IDs: S1, S2, S3...
22376
22367
  - Build on existing code, don't recreate what exists
22377
22368
  - Output ONLY the JSON, nothing else`;
22369
+ function heuristicModeContract(args) {
22370
+ if (args.quick) {
22371
+ return {
22372
+ mode: "focused",
22373
+ confidence: 1,
22374
+ reason: "Quick mode was explicitly requested.",
22375
+ maxStories: 1,
22376
+ parallelism: 1
22377
+ };
22378
+ }
22379
+ const goal = args.goal.toLowerCase();
22380
+ const bugLike = /\b(fix|bug|error|crash|broken|console|build|doesn'?t|isn'?t|still|again|issue|problem|wrong|shifted|display|render)\b/.test(goal);
22381
+ const uiLike = /\b(ui|frontend|react|component|button|card|tab|modal|page|screen|css|style|layout|episode|season)\b/.test(goal);
22382
+ const bigLike = /\b(refactor|rewrite|migrate|redesign|implement|add support|multiple|several|backend|frontend|database|api|tests|docs)\b/.test(goal);
22383
+ if (bugLike || uiLike && !bigLike) {
22384
+ return {
22385
+ mode: "focused",
22386
+ confidence: 0.7,
22387
+ reason: "The goal looks like a localized bugfix/follow-up likely centered on shared UI or one code surface.",
22388
+ maxStories: 1,
22389
+ parallelism: 1
22390
+ };
22391
+ }
22392
+ if (bigLike) {
22393
+ return {
22394
+ mode: "sequential",
22395
+ confidence: 0.55,
22396
+ reason: "The goal may need multiple steps, but no LLM intake proved independent write surfaces.",
22397
+ maxStories: 5,
22398
+ parallelism: 1
22399
+ };
22400
+ }
22401
+ return {
22402
+ mode: "focused",
22403
+ confidence: 0.5,
22404
+ reason: "Uncertain goals default to focused mode to avoid unsafe parallel decomposition.",
22405
+ maxStories: 1,
22406
+ parallelism: 1
22407
+ };
22408
+ }
22409
+ function renderModeContract(decision) {
22410
+ const lines = [
22411
+ `mode: ${decision.mode}`,
22412
+ `confidence: ${decision.confidence}`,
22413
+ `reason: ${decision.reason}`
22414
+ ];
22415
+ if (decision.maxStories) lines.push(`maxStories: ${decision.maxStories}`);
22416
+ if (decision.parallelism) lines.push(`parallelism: ${decision.parallelism}`);
22417
+ if (decision.mode === "focused") {
22418
+ lines.push(
22419
+ 'Planner rules: output EXACTLY ONE story. Do not split. Set model to "opus" so this focused run uses the strong route.',
22420
+ "The story must include enough implementation context and acceptance criteria for one agent to finish the PR."
22421
+ );
22422
+ } else if (decision.mode === "sequential") {
22423
+ lines.push(
22424
+ "Planner rules: output a small ordered chain. Use dependsOn for real shared-surface dependencies.",
22425
+ "Do not create parallel siblings that edit the same file/component/state/API. Keep each story cheap-model-capable."
22426
+ );
22427
+ } else {
22428
+ lines.push(
22429
+ "Planner rules: output parallel siblings only where write surfaces are independent.",
22430
+ "For each sibling story, name its expected write surface in the description. Shared files/components must be sequential."
22431
+ );
22432
+ }
22433
+ return lines.join("\n");
22434
+ }
22435
+ function buildIntakePrompt(args) {
22436
+ if (args.quick) {
22437
+ return JSON.stringify({
22438
+ mode: "focused",
22439
+ confidence: 1,
22440
+ reason: "Quick mode was explicitly requested.",
22441
+ maxStories: 1,
22442
+ parallelism: 1
22443
+ });
22444
+ }
22445
+ return [
22446
+ "You are Baro Intake. Choose the execution shape BEFORE planning.",
22447
+ "",
22448
+ "Return ONLY valid JSON with this schema:",
22449
+ '{"mode":"focused|sequential|parallel","confidence":0.0,"reason":"short","maxStories":1,"parallelism":1}',
22450
+ "",
22451
+ "Definitions:",
22452
+ "- focused: one strong agent/story. Use for small bugfixes, UI tweaks, build/runtime errors, one component/file/surface, or unclear shared-write work.",
22453
+ "- sequential: multiple small ordered stories. Use when one feature naturally requires steps that touch shared code.",
22454
+ "- parallel: only when there are independent write surfaces (different modules/files/contracts) that can safely run at the same time.",
22455
+ "",
22456
+ "Bias rules:",
22457
+ "- If several agents would edit the same file/component/state/schema, choose focused or sequential, not parallel.",
22458
+ "- If the prompt is a follow-up bug report from screenshots/console output, choose focused unless it clearly spans independent surfaces.",
22459
+ "- If uncertain, choose focused. Unsafe parallelism is worse than leaving speed on the table.",
22460
+ "- maxStories is a cap for the planner, not a target.",
22461
+ "",
22462
+ args.projectContext?.trim() ? `Project context summary:
22463
+ ${args.projectContext.trim().slice(0, 3e3)}
22464
+ ` : "",
22465
+ args.decisionDocument?.trim() ? `Architect decision exists; prefer a compact plan that implements it.
22466
+ ${args.decisionDocument.trim().slice(0, 3e3)}
22467
+ ` : "",
22468
+ `User goal:
22469
+ ${args.goal.trim()}`
22470
+ ].filter(Boolean).join("\n");
22471
+ }
22472
+ function parseModeContract(text) {
22473
+ const json = JSON.parse(extractJsonObject(text));
22474
+ const mode = json.mode === "parallel" || json.mode === "sequential" || json.mode === "focused" ? json.mode : "focused";
22475
+ const confidence = Number.isFinite(Number(json.confidence)) ? Math.max(0, Math.min(1, Number(json.confidence))) : 0.5;
22476
+ return {
22477
+ mode,
22478
+ confidence,
22479
+ reason: typeof json.reason === "string" && json.reason.trim() ? json.reason.trim() : "No reason supplied by intake.",
22480
+ maxStories: Number.isFinite(Number(json.maxStories)) ? Math.max(1, Math.floor(Number(json.maxStories))) : void 0,
22481
+ parallelism: Number.isFinite(Number(json.parallelism)) ? Math.max(1, Math.floor(Number(json.parallelism))) : void 0
22482
+ };
22483
+ }
22484
+ function extractJsonObject(text) {
22485
+ const trimmed = text.trim();
22486
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) return trimmed;
22487
+ const fence = trimmed.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
22488
+ if (fence) return fence[1];
22489
+ const start = trimmed.indexOf("{");
22490
+ if (start < 0) throw new Error(`no JSON object in response: ${trimmed.slice(0, 200)}`);
22491
+ let depth = 0;
22492
+ for (let i2 = start; i2 < trimmed.length; i2++) {
22493
+ const ch = trimmed[i2];
22494
+ if (ch === "{") depth++;
22495
+ else if (ch === "}") {
22496
+ depth--;
22497
+ if (depth === 0) return trimmed.slice(start, i2 + 1);
22498
+ }
22499
+ }
22500
+ throw new Error(`unbalanced JSON in response: ${trimmed.slice(0, 200)}`);
22501
+ }
22378
22502
  function buildPlannerUserMessage(args) {
22379
22503
  const sections = [];
22380
22504
  if (args.projectContext && args.projectContext.trim().length > 0) {
@@ -22395,6 +22519,15 @@ function buildPlannerUserMessage(args) {
22395
22519
  sections.push("---");
22396
22520
  sections.push("");
22397
22521
  }
22522
+ const modeContract = typeof args.modeContract === "string" ? args.modeContract : args.modeContract ? renderModeContract(args.modeContract) : void 0;
22523
+ if (modeContract && modeContract.trim().length > 0) {
22524
+ sections.push("EXECUTION MODE CONTRACT (chosen by Baro Intake \u2014 obey it):");
22525
+ sections.push("");
22526
+ sections.push(modeContract.trim());
22527
+ sections.push("");
22528
+ sections.push("---");
22529
+ sections.push("");
22530
+ }
22398
22531
  if (args.quick) {
22399
22532
  sections.push(
22400
22533
  "QUICK MODE OVERRIDE \u2014 the user invoked `baro --quick`. They have told us this goal is trivial. You MUST output EXACTLY ONE story. Do not split. Do not decompose. Do not add a `verify` story. If you genuinely cannot do this in one story, emit the one story anyway with a description that explains what's missing; the user will rerun without --quick. One story, tight acceptance, minimum useful test command."
@@ -22424,11 +22557,19 @@ function effortTimeoutMs(effort) {
22424
22557
  }
22425
22558
  }
22426
22559
  async function runPlannerClaude(opts) {
22560
+ const modeContract = await runClaudeIntake(opts).catch((e2) => {
22561
+ process.stderr.write(`[planner-claude] intake failed (${e2?.message ?? String(e2)}) \u2014 using heuristic mode contract
22562
+ `);
22563
+ return heuristicModeContract(opts);
22564
+ });
22565
+ process.stderr.write(`[planner-claude] intake mode=${modeContract.mode} confidence=${modeContract.confidence}
22566
+ `);
22427
22567
  const userMessage = buildPlannerUserMessage({
22428
22568
  goal: opts.goal,
22429
22569
  decisionDocument: opts.decisionDocument,
22430
22570
  quick: opts.quick,
22431
- projectContext: opts.projectContext
22571
+ projectContext: opts.projectContext,
22572
+ modeContract
22432
22573
  });
22433
22574
  const { stdout } = await execFileAsync(
22434
22575
  opts.claudeBin ?? "claude",
@@ -22456,9 +22597,37 @@ async function runPlannerClaude(opts) {
22456
22597
  if (!planText) {
22457
22598
  throw new Error("PlannerClaude: claude returned empty result");
22458
22599
  }
22459
- return extractJsonObject(planText);
22600
+ return extractJsonObject2(planText);
22460
22601
  }
22461
- function extractJsonObject(text) {
22602
+ async function runClaudeIntake(opts) {
22603
+ if (opts.quick) return heuristicModeContract(opts);
22604
+ const { stdout } = await execFileAsync(
22605
+ opts.claudeBin ?? "claude",
22606
+ [
22607
+ "--print",
22608
+ "--output-format",
22609
+ "json",
22610
+ ...opts.model ? ["--model", opts.model] : [],
22611
+ ...opts.effort ? ["--effort", opts.effort] : [],
22612
+ "--permission-mode",
22613
+ "bypassPermissions",
22614
+ "--system-prompt",
22615
+ "You classify software tasks for an autonomous PR workflow. Output JSON only.",
22616
+ "-p",
22617
+ buildIntakePrompt(opts)
22618
+ ],
22619
+ {
22620
+ cwd: opts.cwd,
22621
+ timeout: Math.min(opts.timeoutMs ?? effortTimeoutMs(opts.effort), 18e4),
22622
+ maxBuffer: 2 * 1024 * 1024
22623
+ }
22624
+ );
22625
+ const wrapper = JSON.parse(stdout);
22626
+ const text = typeof wrapper.result === "string" ? wrapper.result.trim() : "";
22627
+ if (!text) throw new Error("empty intake result");
22628
+ return parseModeContract(text);
22629
+ }
22630
+ function extractJsonObject2(text) {
22462
22631
  const trimmed = text.trim();
22463
22632
  if (trimmed.startsWith("{") && trimmed.endsWith("}")) return trimmed;
22464
22633
  const fence = trimmed.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
@@ -22603,11 +22772,19 @@ ${item.text}` : item.text;
22603
22772
 
22604
22773
  // ../baro-orchestrator/src/planning/planner-codex.ts
22605
22774
  async function runPlannerCodex(opts) {
22775
+ const modeContract = await runCodexIntake(opts).catch((e2) => {
22776
+ process.stderr.write(`[planner-codex] intake failed (${e2?.message ?? String(e2)}) \u2014 using heuristic mode contract
22777
+ `);
22778
+ return heuristicModeContract(opts);
22779
+ });
22780
+ process.stderr.write(`[planner-codex] intake mode=${modeContract.mode} confidence=${modeContract.confidence}
22781
+ `);
22606
22782
  const userMessage = buildPlannerUserMessage({
22607
22783
  goal: opts.goal,
22608
22784
  decisionDocument: opts.decisionDocument,
22609
22785
  quick: opts.quick,
22610
- projectContext: opts.projectContext
22786
+ projectContext: opts.projectContext,
22787
+ modeContract
22611
22788
  });
22612
22789
  const prompt = `${PLANNER_SYSTEM_PROMPT}
22613
22790
 
@@ -22624,9 +22801,23 @@ ${userMessage}`;
22624
22801
  if (!planText) {
22625
22802
  throw new Error("PlannerCodex: codex returned empty result");
22626
22803
  }
22627
- return extractJsonObject2(planText);
22804
+ return extractJsonObject3(planText);
22628
22805
  }
22629
- function extractJsonObject2(text) {
22806
+ async function runCodexIntake(opts) {
22807
+ if (opts.quick) return heuristicModeContract(opts);
22808
+ const text = await runCodexOneShot({
22809
+ prompt: `You classify software tasks for an autonomous PR workflow. Output JSON only.
22810
+
22811
+ ${buildIntakePrompt(opts)}`,
22812
+ cwd: opts.cwd,
22813
+ model: opts.model,
22814
+ codexBin: opts.codexBin,
22815
+ timeoutMs: Math.min(opts.timeoutMs ?? 9e5, 18e4),
22816
+ label: "codex-intake"
22817
+ });
22818
+ return parseModeContract(text.trim());
22819
+ }
22820
+ function extractJsonObject3(text) {
22630
22821
  const trimmed = text.trim();
22631
22822
  if (trimmed.startsWith("{") && trimmed.endsWith("}")) return trimmed;
22632
22823
  const fence = trimmed.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
@@ -40110,6 +40301,19 @@ function pickModel(name) {
40110
40301
  }
40111
40302
  async function runPlannerOpenAI(opts) {
40112
40303
  const model = pickModel(opts.model ?? "gpt-5.5");
40304
+ const intake = await decideExecutionMode(opts, model).catch((e2) => {
40305
+ process.stderr.write(`[planner-openai] intake failed (${e2?.message ?? String(e2)}) \u2014 defaulting to focused
40306
+ `);
40307
+ return {
40308
+ mode: "focused",
40309
+ confidence: 0,
40310
+ reason: "Intake failed, so Baro uses the conservative focused mode instead of unsafe parallel decomposition.",
40311
+ maxStories: 1,
40312
+ parallelism: 1
40313
+ };
40314
+ });
40315
+ process.stderr.write(`[planner-openai] intake mode=${intake.mode} confidence=${intake.confidence} reason=${oneLine(intake.reason).slice(0, 180)}
40316
+ `);
40113
40317
  const tools = createCodebaseTools(opts.cwd);
40114
40318
  setModelTools(model, tools);
40115
40319
  let context = ModelContext.create("planner").addContextItem(SystemMessageItem.create(PLANNER_SYSTEM_PROMPT)).addContextItem(
@@ -40118,7 +40322,8 @@ async function runPlannerOpenAI(opts) {
40118
40322
  goal: opts.goal,
40119
40323
  decisionDocument: opts.decisionDocument,
40120
40324
  quick: opts.quick,
40121
- projectContext: opts.projectContext
40325
+ projectContext: opts.projectContext,
40326
+ modeContract: renderModeContract(intake)
40122
40327
  })
40123
40328
  )
40124
40329
  );
@@ -40187,7 +40392,7 @@ async function runPlannerOpenAI(opts) {
40187
40392
  process.stderr.write(`[planner-openai] ${usage.summary()}
40188
40393
  `);
40189
40394
  try {
40190
- return extractJsonObject3(raw);
40395
+ return extractJsonObject(raw);
40191
40396
  } catch (e2) {
40192
40397
  process.stderr.write(`[planner-openai] invalid final JSON (${e2?.message ?? String(e2)}) \u2014 using fallback PRD
40193
40398
  `);
@@ -40199,6 +40404,24 @@ async function runPlannerOpenAI(opts) {
40199
40404
  `);
40200
40405
  return fallbackPrdJson(opts.goal, `Planner exceeded maxRounds=${maxRounds} without producing a final plan.`);
40201
40406
  }
40407
+ async function decideExecutionMode(opts, plannerModel) {
40408
+ if (opts.quick) {
40409
+ return {
40410
+ mode: "focused",
40411
+ confidence: 1,
40412
+ reason: "The user explicitly invoked quick mode.",
40413
+ maxStories: 1,
40414
+ parallelism: 1
40415
+ };
40416
+ }
40417
+ const intakeModel = pickModel(process.env.BARO_INTAKE_MODEL || plannerModel.specification.name);
40418
+ setModelTools(intakeModel, []);
40419
+ const prompt = buildIntakePrompt(opts);
40420
+ const context = ModelContext.create("intake").addContextItem(SystemMessageItem.create("You classify software tasks for an autonomous PR workflow. Output JSON only.")).addContextItem(UserMessageItem.create(prompt));
40421
+ const result = await runInferenceRound(context, intakeModel);
40422
+ const raw = result.items.filter((i2) => i2.type === "message").map((i2) => i2.toJSON().content?.[0]?.text ?? "").join("\n").trim();
40423
+ return parseModeContract(raw);
40424
+ }
40202
40425
  function numberEnv(name, fallback) {
40203
40426
  const n = Number(process.env[name]);
40204
40427
  return Number.isFinite(n) && n > 0 ? n : fallback;
@@ -40264,26 +40487,6 @@ function setModelTools(model, tools) {
40264
40487
  }
40265
40488
  m2.setTools(tools);
40266
40489
  }
40267
- function extractJsonObject3(text) {
40268
- const trimmed = text.trim();
40269
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) return trimmed;
40270
- const fence = trimmed.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
40271
- if (fence) return fence[1];
40272
- const start = trimmed.indexOf("{");
40273
- if (start < 0) {
40274
- throw new Error(`PlannerOpenAI: no JSON object in response: ${trimmed.slice(0, 200)}`);
40275
- }
40276
- let depth = 0;
40277
- for (let i2 = start; i2 < trimmed.length; i2++) {
40278
- const ch = trimmed[i2];
40279
- if (ch === "{") depth++;
40280
- else if (ch === "}") {
40281
- depth--;
40282
- if (depth === 0) return trimmed.slice(start, i2 + 1);
40283
- }
40284
- }
40285
- throw new Error(`PlannerOpenAI: unbalanced JSON in response: ${trimmed.slice(0, 200)}`);
40286
- }
40287
40490
 
40288
40491
  // ../baro-orchestrator/src/opencode-one-shot.ts
40289
40492
  import { spawn as spawn2 } from "child_process";
@@ -40407,11 +40610,19 @@ ${part.text}` : part.text;
40407
40610
 
40408
40611
  // ../baro-orchestrator/src/planning/planner-opencode.ts
40409
40612
  async function runPlannerOpenCode(opts) {
40613
+ const modeContract = await runOpenCodeIntake(opts).catch((e2) => {
40614
+ process.stderr.write(`[planner-opencode] intake failed (${e2?.message ?? String(e2)}) \u2014 using heuristic mode contract
40615
+ `);
40616
+ return heuristicModeContract(opts);
40617
+ });
40618
+ process.stderr.write(`[planner-opencode] intake mode=${modeContract.mode} confidence=${modeContract.confidence}
40619
+ `);
40410
40620
  const userMessage = buildPlannerUserMessage({
40411
40621
  goal: opts.goal,
40412
40622
  decisionDocument: opts.decisionDocument,
40413
40623
  quick: opts.quick,
40414
- projectContext: opts.projectContext
40624
+ projectContext: opts.projectContext,
40625
+ modeContract
40415
40626
  });
40416
40627
  const prompt = `${PLANNER_SYSTEM_PROMPT}
40417
40628
 
@@ -40430,6 +40641,20 @@ ${userMessage}`;
40430
40641
  }
40431
40642
  return extractJsonObject4(planText);
40432
40643
  }
40644
+ async function runOpenCodeIntake(opts) {
40645
+ if (opts.quick) return heuristicModeContract(opts);
40646
+ const text = await runOpenCodeOneShot({
40647
+ prompt: `You classify software tasks for an autonomous PR workflow. Output JSON only.
40648
+
40649
+ ${buildIntakePrompt(opts)}`,
40650
+ cwd: opts.cwd,
40651
+ model: opts.model,
40652
+ opencodeBin: opts.opencodeBin,
40653
+ timeoutMs: Math.min(opts.timeoutMs ?? 9e5, 18e4),
40654
+ label: "opencode-intake"
40655
+ });
40656
+ return parseModeContract(text.trim());
40657
+ }
40433
40658
  function extractJsonObject4(text) {
40434
40659
  const trimmed = text.trim();
40435
40660
  if (trimmed.startsWith("{") && trimmed.endsWith("}")) return trimmed;
@@ -40623,11 +40848,19 @@ ${block.text}` : block.text;
40623
40848
 
40624
40849
  // ../baro-orchestrator/src/planning/planner-pi.ts
40625
40850
  async function runPlannerPi(opts) {
40851
+ const modeContract = await runPiIntake(opts).catch((e2) => {
40852
+ process.stderr.write(`[planner-pi] intake failed (${e2?.message ?? String(e2)}) \u2014 using heuristic mode contract
40853
+ `);
40854
+ return heuristicModeContract(opts);
40855
+ });
40856
+ process.stderr.write(`[planner-pi] intake mode=${modeContract.mode} confidence=${modeContract.confidence}
40857
+ `);
40626
40858
  const userMessage = buildPlannerUserMessage({
40627
40859
  goal: opts.goal,
40628
40860
  decisionDocument: opts.decisionDocument,
40629
40861
  quick: opts.quick,
40630
- projectContext: opts.projectContext
40862
+ projectContext: opts.projectContext,
40863
+ modeContract
40631
40864
  });
40632
40865
  const prompt = `${PLANNER_SYSTEM_PROMPT}
40633
40866
 
@@ -40647,6 +40880,21 @@ ${userMessage}`;
40647
40880
  }
40648
40881
  return extractJsonObject5(planText);
40649
40882
  }
40883
+ async function runPiIntake(opts) {
40884
+ if (opts.quick) return heuristicModeContract(opts);
40885
+ const text = await runPiOneShot({
40886
+ prompt: `You classify software tasks for an autonomous PR workflow. Output JSON only.
40887
+
40888
+ ${buildIntakePrompt(opts)}`,
40889
+ cwd: opts.cwd,
40890
+ provider: opts.provider,
40891
+ model: opts.model,
40892
+ piBin: opts.piBin,
40893
+ timeoutMs: Math.min(opts.timeoutMs ?? 9e5, 18e4),
40894
+ label: "pi-intake"
40895
+ });
40896
+ return parseModeContract(text.trim());
40897
+ }
40650
40898
  function extractJsonObject5(text) {
40651
40899
  const trimmed = text.trim();
40652
40900
  if (trimmed.startsWith("{") && trimmed.endsWith("}")) return trimmed;