@phi-code-admin/phi-code 0.93.2 → 0.94.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.94.0] - 2026-07-12
4
+
5
+ ### Fixed — three defects the run telemetry caught live
6
+
7
+ The .phi/runs.jsonl telemetry (0.93.0) paid for itself on its first outing: it
8
+ showed /fix shots being killed mid-work by the flat 10-min phase cap (601s run,
9
+ empty phases, lost REPRO-CMD handoff), oracles going blind after those aborts,
10
+ and escalations burning ~10 min re-confirming a reproduction the driver itself
11
+ had just executed.
12
+
13
+ - **Per-phase timeout** (OrchestratorPhase.timeoutMs): the /fix single shot now
14
+ gets 25 min (measured: real shots run 8-18 min). The FIRST phase of a generic
15
+ run also gains the standard retry-on-fallback before being skipped (it was
16
+ skip-direct).
17
+ - **Oracle fallback reproduction**: when the shot's REPRO-CMD handoff was lost
18
+ but the conventional repro_issue.py exists, the oracle runs it instead of
19
+ finishing UNVERIFIED blind.
20
+ - **Escalation skips REPRODUCE**: the oracle just executed the red reproduction;
21
+ /fix now goes straight to LOCALIZE -> FIX -> VERIFY with the red run seeded,
22
+ saving ~10 min under tight budgets.
23
+
24
+ Full suite green (1472).
25
+
3
26
  ## [0.93.2] - 2026-07-12
4
27
 
5
28
  ### Fixed
@@ -307,6 +307,11 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
307
307
  // 0..4 for the five base phases (explore..review); undefined for synthetic
308
308
  // phases (the review fix + re-review). Used to checkpoint resume position.
309
309
  baseIndex?: number;
310
+ // Per-phase timeout override (generic driver only). Defaults to
311
+ // MAX_PHASE_DURATION_MS. Measured need: a /fix single shot legitimately
312
+ // runs 8–18 min — the flat 10 min cap killed it mid-work and lost its
313
+ // REPRO-CMD handoff (telemetry: 601s run, phases empty, UNVERIFIED).
314
+ timeoutMs?: number;
310
315
  }
311
316
 
312
317
  let phaseQueue: OrchestratorPhase[] = [];
@@ -1360,8 +1365,18 @@ Tag the note with relevant keywords for vector search.
1360
1365
  fixContext.oracleRan = true;
1361
1366
  const cwd = ctx.cwd || process.cwd();
1362
1367
  const sandbox = getSessionSandbox(cwd);
1363
- const reproCmd =
1368
+ let reproCmd =
1364
1369
  fixContext.state.failingTest?.trim() || fixContext.state.reproCommand?.trim() || fixContext.reproFromShot;
1370
+ // Conventional fallback: the shot is instructed to write `repro_issue.py`.
1371
+ // If its handoff was lost (phase timeout/abort) but the file exists, the
1372
+ // oracle can still run it instead of going blind (telemetry-measured gap).
1373
+ if (!reproCmd && existsSync(join(cwd, "repro_issue.py"))) {
1374
+ reproCmd = "python repro_issue.py";
1375
+ ctx.ui.notify(
1376
+ `\n🧷 Oracle fallback: found \`repro_issue.py\` — using \`${reproCmd}\` as the reproduction.`,
1377
+ "info",
1378
+ );
1379
+ }
1365
1380
  const suiteCmd = sandbox.recipe.test?.trim();
1366
1381
 
1367
1382
  // A shot that changed NOTHING has nothing to verify — "UNVERIFIED" would be
@@ -1409,13 +1424,16 @@ Tag the note with relevant keywords for vector search.
1409
1424
  );
1410
1425
  return true;
1411
1426
  }
1412
- // escalate: the pipeline inherits the RED run as its concrete failing state.
1427
+ // escalate: the pipeline inherits the RED run as its concrete failing
1428
+ // state. REPRODUCE is SKIPPED — the oracle just executed the reproduction
1429
+ // and it is red; re-confirming it would burn ~10 min re-running what the
1430
+ // driver already ran (measured: escalations were dying on outer budgets).
1413
1431
  ctx.ui.notify(
1414
- `\n🔺 **/fix escalating to the full /debug pipeline** — ${decision.diagnostic}. The single shot was not enough; the pipeline inherits the red run as its reproduction.`,
1432
+ `\n🔺 **/fix escalating** — ${decision.diagnostic}. The reproduction is already established (the oracle just ran it); going straight to LOCALIZE FIX VERIFY.`,
1415
1433
  "warning",
1416
1434
  );
1417
- phaseQueue.push(...buildDebugPhases(decision.failing));
1418
- return false; // fall through: sendNextGenericPhase will dispatch REPRODUCE
1435
+ phaseQueue.push(...buildDebugPhases(decision.failing).slice(1));
1436
+ return false; // fall through: sendNextGenericPhase dispatches LOCALIZE
1419
1437
  }
1420
1438
 
1421
1439
  // Multi-candidate arbitration — the oracle disposes. Apply each captured
@@ -1522,6 +1540,7 @@ Tag the note with relevant keywords for vector search.
1522
1540
  if (warning) ctx.ui.notify(`\n⚠️ ${warning}`, "warning");
1523
1541
  setTimeout(() => pi.sendUserMessage(phase.instruction, { deliverAs: "followUp" }), 500);
1524
1542
  if (phaseTimeoutId) clearTimeout(phaseTimeoutId);
1543
+ const phaseBudget = phase.timeoutMs ?? MAX_PHASE_DURATION_MS;
1525
1544
  phaseTimeoutId = setTimeout(() => {
1526
1545
  if (orchestrationActive && phasePending) {
1527
1546
  phasePending = false;
@@ -1536,7 +1555,7 @@ Tag the note with relevant keywords for vector search.
1536
1555
  phase.useFallback = true;
1537
1556
  phaseQueue.unshift(phase);
1538
1557
  ctx.ui.notify(
1539
- `\n⏰ **Phase timed out** (${MAX_PHASE_DURATION_MS / 60000} min) — retrying once on the fallback model.`,
1558
+ `\n⏰ **Phase timed out** (${Math.round(phaseBudget / 60000)} min) — retrying once on the fallback model.`,
1540
1559
  "warning",
1541
1560
  );
1542
1561
  } else {
@@ -1545,7 +1564,7 @@ Tag the note with relevant keywords for vector search.
1545
1564
  }
1546
1565
  sendNextGenericPhase(ctx);
1547
1566
  }
1548
- }, MAX_PHASE_DURATION_MS);
1567
+ }, phaseBudget);
1549
1568
  });
1550
1569
  }
1551
1570
 
@@ -1743,6 +1762,7 @@ Tag the note with relevant keywords for vector search.
1743
1762
  ctx.ui.notify(`\n${first.label} → \`${modelId}\``, "info");
1744
1763
  if (warning) ctx.ui.notify(`\n⚠️ ${warning}`, "warning");
1745
1764
  if (phaseTimeoutId) clearTimeout(phaseTimeoutId);
1765
+ const firstBudget = first.timeoutMs ?? MAX_PHASE_DURATION_MS;
1746
1766
  phaseTimeoutId = setTimeout(() => {
1747
1767
  if (orchestrationActive && phasePending) {
1748
1768
  phasePending = false;
@@ -1752,11 +1772,24 @@ Tag the note with relevant keywords for vector search.
1752
1772
  } catch {
1753
1773
  /* best effort */
1754
1774
  }
1755
- skippedPhases++;
1756
- ctx.ui.notify(`\n⏰ **First phase timed out** skipping.`, "warning");
1775
+ // Same policy as every other phase: one retry on the fallback
1776
+ // model before skipping (the old skip-direct lost the whole run
1777
+ // when the very first phase was slow).
1778
+ if (!first.retried) {
1779
+ first.retried = true;
1780
+ first.useFallback = true;
1781
+ phaseQueue.unshift(first);
1782
+ ctx.ui.notify(
1783
+ `\n⏰ **First phase timed out** (${Math.round(firstBudget / 60000)} min) — retrying once on the fallback model.`,
1784
+ "warning",
1785
+ );
1786
+ } else {
1787
+ skippedPhases++;
1788
+ ctx.ui.notify(`\n⏰ **First phase timed out again** — skipping.`, "warning");
1789
+ }
1757
1790
  sendNextGenericPhase(ctx);
1758
1791
  }
1759
- }, MAX_PHASE_DURATION_MS);
1792
+ }, firstBudget);
1760
1793
  setTimeout(() => pi.sendUserMessage(first.instruction, { deliverAs: "followUp" }), 200);
1761
1794
  });
1762
1795
  }
@@ -2319,6 +2352,9 @@ With no runnable check at all, the result is honestly labelled UNVERIFIED.`,
2319
2352
  await ensurePlansDir();
2320
2353
  fixContext = { state, oracleRan: false };
2321
2354
  const shot = genericPhase("shot", "🎯 Phase 1 — SINGLE SHOT", "code", "code", singleShotInstruction(state));
2355
+ // A real single shot legitimately runs 8–18 min (measured on the
2356
+ // baselines); the flat 10 min phase cap was killing it mid-work.
2357
+ shot.timeoutMs = 25 * 60 * 1000;
2322
2358
  startGenericOrchestration(
2323
2359
  "fix",
2324
2360
  [shot],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.93.2",
3
+ "version": "0.94.0",
4
4
  "description": "Coding agent CLI with persistent memory, sub-agents, intelligent routing, and orchestration",
5
5
  "type": "module",
6
6
  "piConfig": {