@phi-code-admin/phi-code 0.93.1 → 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,38 @@
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
+
26
+ ## [0.93.2] - 2026-07-12
27
+
28
+ ### Fixed
29
+
30
+ - /fix: a single shot that changed NOTHING (text-only reply, zero edits — a
31
+ measured 91s failure mode) no longer ends UNVERIFIED; in a git repo with a
32
+ clean tree it ESCALATES to the full pipeline (nothing to verify = the work
33
+ was not done). The shot instruction now states that acting with tools is
34
+ mandatory and a textual plan forfeits the attempt. +1 integration test.
35
+
3
36
  ## [0.93.1] - 2026-07-12
4
37
 
5
38
  ### 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,10 +1365,34 @@ 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
 
1382
+ // A shot that changed NOTHING has nothing to verify — "UNVERIFIED" would be
1383
+ // a lie of omission (measured: a text-only 91s shot ended UNVERIFIED with
1384
+ // zero edits). In a git repo with a clean diff, escalate to the full
1385
+ // pipeline instead: the work simply was not done.
1386
+ const inGitRepo = passed(runCommand("git rev-parse --is-inside-work-tree", { cwd, timeoutMs: 15_000 }));
1387
+ if (inGitRepo && !gitIn(cwd, "diff").trim() && !gitIn(cwd, "status --porcelain").trim()) {
1388
+ ctx.ui.notify(
1389
+ `\n🔺 **/fix escalating: the single shot made NO changes** — nothing to verify, the full /debug pipeline takes over.`,
1390
+ "warning",
1391
+ );
1392
+ phaseQueue.push(...buildDebugPhases(fixContext.state));
1393
+ return false; // dispatch REPRODUCE
1394
+ }
1395
+
1367
1396
  if (!sandbox.available() && (reproCmd || suiteCmd)) {
1368
1397
  ctx.ui.notify(
1369
1398
  `\n⚠️ **/fix oracle: sandbox unavailable** (${sandbox.reason}) — the single shot cannot be verified. Treating it as UNVERIFIED.`,
@@ -1395,13 +1424,16 @@ Tag the note with relevant keywords for vector search.
1395
1424
  );
1396
1425
  return true;
1397
1426
  }
1398
- // 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).
1399
1431
  ctx.ui.notify(
1400
- `\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.`,
1401
1433
  "warning",
1402
1434
  );
1403
- phaseQueue.push(...buildDebugPhases(decision.failing));
1404
- return false; // fall through: sendNextGenericPhase will dispatch REPRODUCE
1435
+ phaseQueue.push(...buildDebugPhases(decision.failing).slice(1));
1436
+ return false; // fall through: sendNextGenericPhase dispatches LOCALIZE
1405
1437
  }
1406
1438
 
1407
1439
  // Multi-candidate arbitration — the oracle disposes. Apply each captured
@@ -1508,6 +1540,7 @@ Tag the note with relevant keywords for vector search.
1508
1540
  if (warning) ctx.ui.notify(`\n⚠️ ${warning}`, "warning");
1509
1541
  setTimeout(() => pi.sendUserMessage(phase.instruction, { deliverAs: "followUp" }), 500);
1510
1542
  if (phaseTimeoutId) clearTimeout(phaseTimeoutId);
1543
+ const phaseBudget = phase.timeoutMs ?? MAX_PHASE_DURATION_MS;
1511
1544
  phaseTimeoutId = setTimeout(() => {
1512
1545
  if (orchestrationActive && phasePending) {
1513
1546
  phasePending = false;
@@ -1522,7 +1555,7 @@ Tag the note with relevant keywords for vector search.
1522
1555
  phase.useFallback = true;
1523
1556
  phaseQueue.unshift(phase);
1524
1557
  ctx.ui.notify(
1525
- `\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.`,
1526
1559
  "warning",
1527
1560
  );
1528
1561
  } else {
@@ -1531,7 +1564,7 @@ Tag the note with relevant keywords for vector search.
1531
1564
  }
1532
1565
  sendNextGenericPhase(ctx);
1533
1566
  }
1534
- }, MAX_PHASE_DURATION_MS);
1567
+ }, phaseBudget);
1535
1568
  });
1536
1569
  }
1537
1570
 
@@ -1729,6 +1762,7 @@ Tag the note with relevant keywords for vector search.
1729
1762
  ctx.ui.notify(`\n${first.label} → \`${modelId}\``, "info");
1730
1763
  if (warning) ctx.ui.notify(`\n⚠️ ${warning}`, "warning");
1731
1764
  if (phaseTimeoutId) clearTimeout(phaseTimeoutId);
1765
+ const firstBudget = first.timeoutMs ?? MAX_PHASE_DURATION_MS;
1732
1766
  phaseTimeoutId = setTimeout(() => {
1733
1767
  if (orchestrationActive && phasePending) {
1734
1768
  phasePending = false;
@@ -1738,11 +1772,24 @@ Tag the note with relevant keywords for vector search.
1738
1772
  } catch {
1739
1773
  /* best effort */
1740
1774
  }
1741
- skippedPhases++;
1742
- 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
+ }
1743
1790
  sendNextGenericPhase(ctx);
1744
1791
  }
1745
- }, MAX_PHASE_DURATION_MS);
1792
+ }, firstBudget);
1746
1793
  setTimeout(() => pi.sendUserMessage(first.instruction, { deliverAs: "followUp" }), 200);
1747
1794
  });
1748
1795
  }
@@ -2305,6 +2352,9 @@ With no runnable check at all, the result is honestly labelled UNVERIFIED.`,
2305
2352
  await ensurePlansDir();
2306
2353
  fixContext = { state, oracleRan: false };
2307
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;
2308
2358
  startGenericOrchestration(
2309
2359
  "fix",
2310
2360
  [shot],
@@ -126,6 +126,8 @@ export function singleShotInstruction(state: FailingState): string {
126
126
  **The problem:**
127
127
  ${failing}
128
128
 
129
+ **You must ACT in this turn — call the read/edit/write tools and actually change the code. A textual plan or analysis with no edits counts as a FAILED shot (measured failure mode) and forfeits your attempt to the full pipeline.**
130
+
129
131
  **Do exactly this:**
130
132
  1. Read the relevant code and locate the root cause.
131
133
  2. Make the smallest change that addresses it. Do NOT edit tests. Every added guard/branch is a liability.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.93.1",
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": {