@pourkit/cli 0.0.0-next-20260608072322 → 0.0.0-next-20260609130908

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/cli.js CHANGED
@@ -776,13 +776,17 @@ function getVerificationCommands(target) {
776
776
  }
777
777
  function resolvePrdRunMode(target, opts) {
778
778
  if (opts?.localOverride === true) {
779
- return { mode: "local", source: "cli-override" };
779
+ return { mode: "local", source: "cli-override", targetName: target.name };
780
780
  }
781
781
  const configMode = target.strategy.prdRun?.mode;
782
782
  if (configMode) {
783
- return { mode: configMode, source: "target-config" };
783
+ return {
784
+ mode: configMode,
785
+ source: "target-config",
786
+ targetName: target.name
787
+ };
784
788
  }
785
- return { mode: "github", source: "default" };
789
+ return { mode: "github", source: "default", targetName: target.name };
786
790
  }
787
791
  async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
788
792
  const { existsSync: existsSync20 } = await import("fs");
@@ -1350,13 +1354,7 @@ function buildRunContextMarkdown(options) {
1350
1354
  );
1351
1355
  }
1352
1356
  if (sections.includes("verification-commands")) {
1353
- parts.push(
1354
- ...renderCommandList(
1355
- target,
1356
- getVerificationCommands(target),
1357
- "Verification Commands"
1358
- )
1359
- );
1357
+ parts.push(...renderCommandList(target, "Verification Commands"));
1360
1358
  }
1361
1359
  if (sections.includes("review-criteria")) {
1362
1360
  parts.push(...renderCriteria(reviewerCriteria));
@@ -1374,29 +1372,11 @@ function buildRunContextMarkdown(options) {
1374
1372
  }
1375
1373
  return parts.join("\n");
1376
1374
  }
1377
- function renderCommandList(target, commands, heading) {
1378
- if (commands.length === 0) {
1379
- return [
1380
- `## ${heading}`,
1381
- "",
1382
- `Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
1383
- "",
1384
- "Underlying project commands:",
1385
- "",
1386
- "(none configured)",
1387
- ""
1388
- ];
1389
- }
1375
+ function renderCommandList(target, heading) {
1390
1376
  return [
1391
1377
  `## ${heading}`,
1392
1378
  "",
1393
1379
  `Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
1394
- "",
1395
- "Underlying project commands:",
1396
- "",
1397
- "Run these commands from the repository root exactly as written. Do not substitute equivalent scripts from nested package.json files.",
1398
- "",
1399
- ...commands.map((command) => `- ${command.label}: \`${command.command}\``),
1400
1380
  ""
1401
1381
  ];
1402
1382
  }
@@ -6158,10 +6138,57 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as rea
6158
6138
  import { join as join13 } from "path";
6159
6139
 
6160
6140
  // prd-run/local-branches.ts
6161
- import { execFileSync } from "child_process";
6141
+ import { execFileSync, spawnSync as spawnSync2 } from "child_process";
6162
6142
  function getLocalPrdBranchName(prdId) {
6163
6143
  return `local/${prdId}`;
6164
6144
  }
6145
+ function materializeLocalPrdBranch(prdId, startBaseCommit, repoRoot2) {
6146
+ const branch = getLocalPrdBranchName(prdId);
6147
+ const root = repoRoot2 ?? process.cwd();
6148
+ const branchExists = (() => {
6149
+ try {
6150
+ return spawnSync2("git", ["rev-parse", "--verify", "--quiet", branch], {
6151
+ cwd: root,
6152
+ encoding: "utf8",
6153
+ stdio: "pipe"
6154
+ }).status === 0;
6155
+ } catch {
6156
+ return false;
6157
+ }
6158
+ })();
6159
+ if (branchExists) {
6160
+ const currentCommit = spawnSync2("git", ["rev-parse", branch], {
6161
+ cwd: root,
6162
+ encoding: "utf8",
6163
+ stdio: "pipe"
6164
+ }).stdout?.toString?.().trim() ?? "";
6165
+ if (currentCommit !== startBaseCommit) {
6166
+ return {
6167
+ ok: false,
6168
+ failureCode: "branch_commit_mismatch",
6169
+ message: `Local PRD branch ${branch} exists at commit ${currentCommit} but expected commit ${startBaseCommit}. The branch has diverged from the start base. To resolve, delete the local branch and rerun start, or manually reset it to the expected commit.`
6170
+ };
6171
+ }
6172
+ return {
6173
+ ok: true,
6174
+ created: false
6175
+ };
6176
+ }
6177
+ const branchResult = spawnSync2("git", ["branch", branch, startBaseCommit], {
6178
+ cwd: root,
6179
+ encoding: "utf8",
6180
+ stdio: "pipe"
6181
+ });
6182
+ if (branchResult.status !== 0) {
6183
+ const stderr = branchResult.stderr?.toString?.() ?? "unknown error";
6184
+ return {
6185
+ ok: false,
6186
+ failureCode: "branch_creation_failed",
6187
+ message: `Failed to create local PRD branch ${branch} at ${startBaseCommit}: ${stderr}`
6188
+ };
6189
+ }
6190
+ return { ok: true, created: true };
6191
+ }
6165
6192
  var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
6166
6193
  var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
6167
6194
  function validateLocalBranchName(name) {
@@ -8307,13 +8334,13 @@ import {
8307
8334
  lstatSync,
8308
8335
  mkdirSync as mkdirSync11,
8309
8336
  mkdtempSync,
8310
- readdirSync as readdirSync6,
8311
- readFileSync as readFileSync16,
8337
+ readdirSync as readdirSync5,
8338
+ readFileSync as readFileSync17,
8312
8339
  realpathSync,
8313
8340
  rmSync as rmSync3
8314
8341
  } from "fs";
8315
- import { spawnSync as spawnSync2 } from "child_process";
8316
- import { dirname as dirname5, join as join20, relative as relative2 } from "path";
8342
+ import { spawnSync as spawnSync3 } from "child_process";
8343
+ import { dirname as dirname5, join as join21, relative as relative2 } from "path";
8317
8344
  import { tmpdir } from "os";
8318
8345
 
8319
8346
  // prd-run/state.ts
@@ -8343,9 +8370,11 @@ var PrdRunRecordSchema = z3.object({
8343
8370
  "waiting_for_integration",
8344
8371
  "finalizing",
8345
8372
  "final_reviewed",
8346
- "complete"
8373
+ "complete",
8374
+ "completed_local_branch"
8347
8375
  ]),
8348
8376
  updatedAt: z3.string().min(1),
8377
+ mode: z3.enum(["github", "local"]).optional(),
8349
8378
  blockedGate: z3.enum([
8350
8379
  "manifest-location",
8351
8380
  "manifest-readiness",
@@ -8544,7 +8573,20 @@ var LocalPrdRunRecordSchema = z3.object({
8544
8573
  baseCommit: z3.string().min(1)
8545
8574
  }).optional(),
8546
8575
  queue: z3.object({ completedAt: z3.string().min(1) }).optional(),
8547
- finalReview: z3.object({ completedAt: z3.string().min(1) }).optional(),
8576
+ finalReview: z3.object({
8577
+ completedAt: z3.string().min(1),
8578
+ targetName: z3.string().optional(),
8579
+ prdBranch: z3.string().optional(),
8580
+ mergeBase: z3.string().optional(),
8581
+ verdict: z3.enum([
8582
+ "pass_no_changes",
8583
+ "pass_with_retouch",
8584
+ "needs_human_review",
8585
+ "blocked"
8586
+ ]).optional(),
8587
+ diagnostics: z3.array(z3.string()).optional(),
8588
+ artifactPath: z3.string().optional()
8589
+ }).optional(),
8548
8590
  reconciliation: z3.object({ completedAt: z3.string().min(1) }).optional(),
8549
8591
  complete: z3.object({
8550
8592
  completedAt: z3.string().min(1),
@@ -9152,6 +9194,28 @@ function getLocalStorePath3(repoRoot2, prdId) {
9152
9194
  function getFinalReviewReceiptPath(repoRoot2, prdId) {
9153
9195
  return join17(getLocalStorePath3(repoRoot2, prdId), "final-review-receipt.json");
9154
9196
  }
9197
+ function computeLocalMergeBase(repoRoot2, prdBranch) {
9198
+ const candidates = ["dev", "main", "master", "HEAD"];
9199
+ for (const base of candidates) {
9200
+ try {
9201
+ const result = execFileSync3("git", ["merge-base", base, prdBranch], {
9202
+ cwd: repoRoot2,
9203
+ encoding: "utf8",
9204
+ stdio: "pipe"
9205
+ });
9206
+ const mergeBase = result.trim();
9207
+ if (mergeBase && mergeBase.length >= 6) {
9208
+ return { ok: true, mergeBase };
9209
+ }
9210
+ } catch {
9211
+ continue;
9212
+ }
9213
+ }
9214
+ return {
9215
+ ok: false,
9216
+ reason: "Could not compute merge base against any known base branch (dev/main/master/HEAD)."
9217
+ };
9218
+ }
9155
9219
  async function runLocalFinalReview(prdId, options) {
9156
9220
  const root = options?.repoRoot ?? process.cwd();
9157
9221
  const commands = options?.verificationCommands ?? [];
@@ -9164,6 +9228,34 @@ async function runLocalFinalReview(prdId, options) {
9164
9228
  };
9165
9229
  }
9166
9230
  const branch = getLocalPrdBranchName(prdId);
9231
+ const mergeBaseResult = computeLocalMergeBase(root, branch);
9232
+ if (!mergeBaseResult.ok) {
9233
+ return {
9234
+ ok: false,
9235
+ failureCode: "merge_base_failed",
9236
+ message: mergeBaseResult.reason
9237
+ };
9238
+ }
9239
+ const semanticResult = validateFinalReviewArtifactSemanticIds(
9240
+ {
9241
+ prdRef: prdId,
9242
+ stage: "prdFinalReview",
9243
+ checkoutBase: branch,
9244
+ reviewBase: mergeBaseResult.mergeBase
9245
+ },
9246
+ {
9247
+ prdRef: prdId,
9248
+ prdBranch: branch,
9249
+ mergeBase: mergeBaseResult.mergeBase
9250
+ }
9251
+ );
9252
+ if (!semanticResult.ok) {
9253
+ return {
9254
+ ok: false,
9255
+ failureCode: "invalid_artifact_semantics",
9256
+ message: semanticResult.errors.join("; ")
9257
+ };
9258
+ }
9167
9259
  const failures = [];
9168
9260
  for (const cmd of commands) {
9169
9261
  try {
@@ -9175,7 +9267,7 @@ async function runLocalFinalReview(prdId, options) {
9175
9267
  }
9176
9268
  let verdict;
9177
9269
  if (commands.length === 0 || failures.length === 0) {
9178
- const retouchBranch = `local/${prdId}/retouch`;
9270
+ const retouchBranch = `local-${prdId}-final-review-retouch`;
9179
9271
  try {
9180
9272
  execSync2(`git show-ref --verify --quiet refs/heads/${retouchBranch}`, {
9181
9273
  cwd: root,
@@ -9184,12 +9276,12 @@ async function runLocalFinalReview(prdId, options) {
9184
9276
  });
9185
9277
  verdict = "pass_with_retouch";
9186
9278
  } catch {
9187
- verdict = "pass";
9279
+ verdict = "pass_no_changes";
9188
9280
  }
9189
9281
  } else if (failures.length === commands.length) {
9190
9282
  verdict = "blocked";
9191
9283
  } else {
9192
- const retouchBranch = `local/${prdId}/retouch`;
9284
+ const retouchBranch = `local-${prdId}-final-review-retouch`;
9193
9285
  try {
9194
9286
  execSync2(`git show-ref --verify --quiet refs/heads/${retouchBranch}`, {
9195
9287
  cwd: root,
@@ -9202,9 +9294,10 @@ async function runLocalFinalReview(prdId, options) {
9202
9294
  }
9203
9295
  }
9204
9296
  const receipt = {
9205
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
9297
+ reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
9206
9298
  verdict,
9207
- branch
9299
+ prdBranch: branch,
9300
+ mergeBase: mergeBaseResult.mergeBase
9208
9301
  };
9209
9302
  const receiptPath = getFinalReviewReceiptPath(root, prdId);
9210
9303
  mkdirSync9(getLocalStorePath3(root, prdId), { recursive: true });
@@ -9217,10 +9310,10 @@ async function runLocalFinalReview(prdId, options) {
9217
9310
  }
9218
9311
  return result;
9219
9312
  }
9220
- async function squashFinalReviewRetouch(prdId, repoRoot2) {
9313
+ async function squashFinalReviewRetouch(prdId, repoRoot2, title, body) {
9221
9314
  const root = repoRoot2 ?? process.cwd();
9222
9315
  const targetBranch = `local/${prdId}`;
9223
- const retouchBranch = `local/${prdId}/retouch`;
9316
+ const retouchBranch = `local-${prdId}-final-review-retouch`;
9224
9317
  try {
9225
9318
  execFileSync3(
9226
9319
  "git",
@@ -9267,15 +9360,46 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
9267
9360
  return;
9268
9361
  } catch {
9269
9362
  }
9270
- execFileSync3(
9363
+ if (title) {
9364
+ const commitBody = body ? `${title}
9365
+
9366
+ ${body}` : title;
9367
+ execFileSync3("git", ["commit", "-m", commitBody], {
9368
+ cwd: root,
9369
+ encoding: "utf8",
9370
+ stdio: "pipe"
9371
+ });
9372
+ } else {
9373
+ execFileSync3(
9374
+ "git",
9375
+ ["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
9376
+ {
9377
+ cwd: root,
9378
+ encoding: "utf8",
9379
+ stdio: "pipe"
9380
+ }
9381
+ );
9382
+ }
9383
+ const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
9384
+ cwd: root,
9385
+ encoding: "utf8",
9386
+ stdio: "pipe"
9387
+ }).trim();
9388
+ const changedPathsResult = execFileSync3(
9271
9389
  "git",
9272
- ["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
9390
+ ["diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"],
9273
9391
  {
9274
9392
  cwd: root,
9275
9393
  encoding: "utf8",
9276
9394
  stdio: "pipe"
9277
9395
  }
9278
9396
  );
9397
+ const changedPaths = changedPathsResult.split(/\r?\n/).filter(Boolean);
9398
+ return {
9399
+ mergeCommit,
9400
+ changedPaths,
9401
+ reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString()
9402
+ };
9279
9403
  } catch (error) {
9280
9404
  const message = error instanceof Error ? error.message : String(error);
9281
9405
  if (message.toLowerCase().includes("conflict")) {
@@ -9287,13 +9411,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
9287
9411
 
9288
9412
  // prd-run/local-reconciliation.ts
9289
9413
  import { execFileSync as execFileSync4 } from "child_process";
9290
- import {
9291
- existsSync as existsSync15,
9292
- mkdirSync as mkdirSync10,
9293
- readFileSync as readFileSync15,
9294
- readdirSync as readdirSync5,
9295
- writeFileSync as writeFileSync7
9296
- } from "fs";
9414
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
9297
9415
  import { join as join18 } from "path";
9298
9416
  function getLocalStorePath4(repoRoot2, prdId) {
9299
9417
  return join18(repoRoot2, ".pourkit", "local-prd-runs", prdId);
@@ -9304,26 +9422,6 @@ function getFinalReviewReceiptPath2(repoRoot2, prdId) {
9304
9422
  function getHandoffArtifactPath(repoRoot2, prdId) {
9305
9423
  return join18(repoRoot2, ".pourkit", ".tmp", "reconciliation", `${prdId}.json`);
9306
9424
  }
9307
- function getCompletionsDir(repoRoot2, prdId) {
9308
- const initiativesRoot = join18(
9309
- repoRoot2,
9310
- ".pourkit",
9311
- "architecture",
9312
- "initiatives"
9313
- );
9314
- if (existsSync15(initiativesRoot)) {
9315
- const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join18(initiativesRoot, e.name));
9316
- for (const dir of dirs) {
9317
- const prdsDir = join18(dir, "prds");
9318
- if (!existsSync15(prdsDir)) continue;
9319
- const prdDirs = readdirSync5(prdsDir, { withFileTypes: true }).filter(
9320
- (e) => e.isDirectory() && e.name.startsWith(prdId.toLowerCase())
9321
- );
9322
- if (prdDirs.length > 0) return join18(dir, "completions");
9323
- }
9324
- }
9325
- return join18(getLocalStorePath4(repoRoot2, prdId), "completions");
9326
- }
9327
9425
  function getReconciliationReceiptPath(repoRoot2, prdId) {
9328
9426
  return join18(
9329
9427
  getLocalStorePath4(repoRoot2, prdId),
@@ -9365,6 +9463,20 @@ async function runLocalReconciliation(prdId, options) {
9365
9463
  message: "Handoff artifact validation failed. Check Architect reconciliation output."
9366
9464
  };
9367
9465
  }
9466
+ const validResults = [
9467
+ "no_changes_needed",
9468
+ "changes_produced",
9469
+ "blocked"
9470
+ ];
9471
+ if (!validResults.includes(
9472
+ handoff.result
9473
+ )) {
9474
+ return {
9475
+ ok: false,
9476
+ failureCode: "invalid_handoff",
9477
+ message: "Handoff artifact validation failed. Check Architect reconciliation output."
9478
+ };
9479
+ }
9368
9480
  if (handoff.result === "blocked") {
9369
9481
  return {
9370
9482
  ok: false,
@@ -9379,48 +9491,6 @@ async function runLocalReconciliation(prdId, options) {
9379
9491
  message: "Handoff artifact validation failed. Check Architect reconciliation output."
9380
9492
  };
9381
9493
  }
9382
- if (handoff.result === "changes_produced") {
9383
- try {
9384
- const completionsDir = getCompletionsDir(root, prdId);
9385
- mkdirSync10(completionsDir, { recursive: true });
9386
- const existing = existsSync15(completionsDir) ? readdirSync5(completionsDir).filter((f) => f.endsWith(".md")) : [];
9387
- const nextNum = String(existing.length + 1).padStart(3, "0");
9388
- const filename = `${nextNum}-prd-${prdId.replace("PRD-", "").toLowerCase()}-reconciliation-completion.md`;
9389
- const filepath = join18(completionsDir, filename);
9390
- const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
9391
- const summary = typeof handoff.summary === "string" ? handoff.summary : "";
9392
- const lines = [
9393
- `# ${prdId} Reconciliation Completion`,
9394
- "",
9395
- `Result: ${handoff.result}`
9396
- ];
9397
- if (summary) lines.push(`Summary: ${summary}`);
9398
- lines.push("", "## Changed Planning Paths", "");
9399
- if (changedPaths.length > 0) {
9400
- lines.push(...changedPaths.map((p) => `- ${p}`));
9401
- } else {
9402
- lines.push("- (none)");
9403
- }
9404
- lines.push("");
9405
- writeFileSync7(filepath, lines.join("\n"), "utf-8");
9406
- execFileSync4("git", ["add", filepath], {
9407
- cwd: root,
9408
- encoding: "utf8",
9409
- stdio: "pipe"
9410
- });
9411
- execFileSync4(
9412
- "git",
9413
- ["commit", "-m", `docs: ${prdId} reconciliation completion`],
9414
- { cwd: root, encoding: "utf8", stdio: "pipe" }
9415
- );
9416
- } catch {
9417
- return {
9418
- ok: false,
9419
- failureCode: "completion_commit_failed",
9420
- message: "Failed to commit completion files."
9421
- };
9422
- }
9423
- }
9424
9494
  if (handoff.result === "changes_produced") {
9425
9495
  try {
9426
9496
  const targetBranch = getLocalPrdBranchName(prdId);
@@ -9434,7 +9504,7 @@ async function runLocalReconciliation(prdId, options) {
9434
9504
  return {
9435
9505
  ok: false,
9436
9506
  failureCode: "squash_merge_failed",
9437
- message: "Squash merge into local PRD Branch failed."
9507
+ message: `Local PRD Branch "${targetBranch}" does not exist. Run prd-run start first to create it.`
9438
9508
  };
9439
9509
  }
9440
9510
  execFileSync4("git", ["checkout", targetBranch], {
@@ -9457,7 +9527,7 @@ async function runLocalReconciliation(prdId, options) {
9457
9527
  return {
9458
9528
  ok: false,
9459
9529
  failureCode: "squash_merge_failed",
9460
- message: "Squash merge into local PRD Branch failed."
9530
+ message: `Reconciliation source branch "${sourceBranch}" does not exist. Re-run reconciliation.`
9461
9531
  };
9462
9532
  }
9463
9533
  execFileSync4("git", ["merge", "--squash", sourceBranch], {
@@ -9478,6 +9548,25 @@ async function runLocalReconciliation(prdId, options) {
9478
9548
  { cwd: root, encoding: "utf8", stdio: "pipe" }
9479
9549
  );
9480
9550
  }
9551
+ } catch (error) {
9552
+ const message = error instanceof Error ? error.message : "unknown error";
9553
+ return {
9554
+ ok: false,
9555
+ failureCode: "squash_merge_failed",
9556
+ message: `Squash merge into local PRD Branch failed: ${message}`
9557
+ };
9558
+ }
9559
+ }
9560
+ const prdBranch = getLocalPrdBranchName(prdId);
9561
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9562
+ let mergeCommit;
9563
+ if (handoff.result === "changes_produced") {
9564
+ try {
9565
+ mergeCommit = execFileSync4("git", ["rev-parse", "HEAD"], {
9566
+ cwd: root,
9567
+ encoding: "utf8",
9568
+ stdio: "pipe"
9569
+ }).trim();
9481
9570
  } catch {
9482
9571
  return {
9483
9572
  ok: false,
@@ -9486,18 +9575,21 @@ async function runLocalReconciliation(prdId, options) {
9486
9575
  };
9487
9576
  }
9488
9577
  }
9489
- const branch = getLocalPrdBranchName(prdId);
9490
- const receipt = {
9491
- reconciledAt: (/* @__PURE__ */ new Date()).toISOString(),
9492
- branch
9578
+ const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
9579
+ const entry = {
9580
+ result: handoff.result ?? "blocked",
9581
+ prdBranch,
9582
+ ...changedPaths.length > 0 ? { changedPlanningPaths: changedPaths } : {},
9583
+ ...mergeCommit ? { mergeCommit } : {},
9584
+ reconciledTimestamp: now
9493
9585
  };
9494
9586
  mkdirSync10(getLocalStorePath4(root, prdId), { recursive: true });
9495
9587
  writeFileSync7(
9496
9588
  getReconciliationReceiptPath(root, prdId),
9497
- JSON.stringify(receipt, null, 2),
9589
+ JSON.stringify(entry, null, 2),
9498
9590
  "utf-8"
9499
9591
  );
9500
- return { ok: true, receipt };
9592
+ return { ok: true, receipt: entry };
9501
9593
  }
9502
9594
 
9503
9595
  // prd-run/local-prepare.ts
@@ -9642,55 +9734,346 @@ async function validateLocalPrepareGates(prdId, repoRoot2) {
9642
9734
  return { ok: true, gates };
9643
9735
  }
9644
9736
 
9645
- // commands/queue.ts
9646
- init_common();
9647
- import { Effect as Effect8 } from "effect";
9737
+ // prd-run/local-queue-loop.ts
9738
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
9739
+ import { join as join20 } from "path";
9648
9740
 
9649
- // issues/select-issue.ts
9741
+ // prd-run/local-issue-run.ts
9742
+ import { execFileSync as execFileSync5 } from "child_process";
9650
9743
  init_common();
9651
- var TYPE_PRIORITY = {
9652
- "type:bugfix": 1,
9653
- "type:infra": 2,
9654
- "type:feature": 3,
9655
- "type:polish": 4,
9656
- "type:refactor": 5
9657
- };
9658
- function selectIssue(candidates, options = {}) {
9659
- const blockedLabel = options.blockedLabel ?? "blocked";
9660
- const agentInProgressLabel = options.agentInProgressLabel ?? "agent-in-progress";
9661
- if (candidates.length === 0) {
9662
- return { ok: false, reason: "No candidate issues provided." };
9744
+ async function createLocalIssueBranch(prdId, issueId, repoRoot2) {
9745
+ const root = repoRoot2 ?? process.cwd();
9746
+ const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
9747
+ if (!issuesResult.ok) {
9748
+ throw new Error(
9749
+ `Cannot create local issue branch: ${issuesResult.message}`
9750
+ );
9663
9751
  }
9664
- const unblocked = candidates.filter(
9665
- (issue) => !issue.labels.includes(blockedLabel)
9666
- );
9667
- if (unblocked.length === 0) {
9668
- return { ok: false, reason: "All candidate issues are blocked." };
9752
+ const issue = issuesResult.data.find((i) => i.id === issueId);
9753
+ if (!issue) {
9754
+ throw new Error(`Issue "${issueId}" not found under PRD "${prdId}"`);
9669
9755
  }
9670
- const valid2 = [];
9671
- const rejected = [];
9672
- for (const issue of unblocked) {
9673
- if (issue.labels.includes(agentInProgressLabel)) {
9674
- rejected.push(
9675
- `Issue #${issue.number} is labeled ${agentInProgressLabel}.`
9676
- );
9677
- continue;
9678
- }
9679
- const typeLabels = issue.labels.filter(
9680
- (l) => TYPE_LABELS.includes(l)
9756
+ const slug = slugify(issue.title);
9757
+ const branchName = `local/${prdId}/${issueId}-${slug}`;
9758
+ const validation = validateLocalBranchName(branchName);
9759
+ if (!validation.ok) {
9760
+ throw new Error(
9761
+ `Invalid local issue branch name "${branchName}": ${validation.message}`
9681
9762
  );
9682
- if (typeLabels.length !== 1) {
9683
- rejected.push(
9684
- `Issue #${issue.number} has ${typeLabels.length} type label(s); expected exactly one.`
9685
- );
9686
- continue;
9687
- }
9688
- valid2.push({ issue, priority: TYPE_PRIORITY[typeLabels[0]] });
9689
9763
  }
9690
- if (valid2.length === 0) {
9764
+ const localPrdBranch = `local/${prdId}`;
9765
+ try {
9766
+ execFileSync5(
9767
+ "git",
9768
+ ["show-ref", "--verify", "--quiet", `refs/heads/${localPrdBranch}`],
9769
+ {
9770
+ cwd: root,
9771
+ encoding: "utf8",
9772
+ stdio: "pipe"
9773
+ }
9774
+ );
9775
+ } catch {
9776
+ throw new Error(
9777
+ `Local PRD branch "${localPrdBranch}" does not exist. Create it first.`
9778
+ );
9779
+ }
9780
+ let branchExists = false;
9781
+ try {
9782
+ execFileSync5(
9783
+ "git",
9784
+ ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
9785
+ {
9786
+ cwd: root,
9787
+ encoding: "utf8",
9788
+ stdio: "pipe"
9789
+ }
9790
+ );
9791
+ branchExists = true;
9792
+ } catch {
9793
+ }
9794
+ if (!branchExists) {
9795
+ execFileSync5("git", ["checkout", "-b", branchName, localPrdBranch], {
9796
+ cwd: root,
9797
+ encoding: "utf8",
9798
+ stdio: "pipe"
9799
+ });
9800
+ }
9801
+ return { branchName, issue };
9802
+ }
9803
+ async function runLocalIssue(prdId, issueId, builder, repoRoot2) {
9804
+ try {
9805
+ const root = repoRoot2 ?? process.cwd();
9806
+ const { branchName: branch, issue } = await createLocalIssueBranch(
9807
+ prdId,
9808
+ issueId,
9809
+ root
9810
+ );
9811
+ if (builder) {
9812
+ const result = await builder.execute({
9813
+ prdId,
9814
+ issueId,
9815
+ branch,
9816
+ bodyMarkdown: issue.bodyMarkdown
9817
+ });
9818
+ if (!result.ok) {
9819
+ return {
9820
+ ok: false,
9821
+ issueId,
9822
+ branch,
9823
+ failureCode: result.error ?? "Builder execution failed"
9824
+ };
9825
+ }
9826
+ }
9827
+ return { ok: true, issueId, branch };
9828
+ } catch (error) {
9691
9829
  return {
9692
9830
  ok: false,
9693
- reason: "No runnable issues: " + rejected.join(" ")
9831
+ issueId,
9832
+ branch: "",
9833
+ failureCode: error instanceof Error ? error.message : String(error)
9834
+ };
9835
+ }
9836
+ }
9837
+
9838
+ // prd-run/local-queue-loop.ts
9839
+ var CHILD_CLEANUP_LABELS = ["agent-in-progress", "pr-open-awaiting-merge"];
9840
+ function getIssueArtifactPath2(repoRoot2, prdId, issueId) {
9841
+ return join20(
9842
+ repoRoot2,
9843
+ ".pourkit",
9844
+ "local-prd-runs",
9845
+ prdId,
9846
+ "issues",
9847
+ `${issueId}.json`
9848
+ );
9849
+ }
9850
+ function readIssueArtifact(repoRoot2, prdId, issueId) {
9851
+ try {
9852
+ const content = readFileSync16(
9853
+ getIssueArtifactPath2(repoRoot2, prdId, issueId),
9854
+ "utf-8"
9855
+ );
9856
+ return JSON.parse(content);
9857
+ } catch {
9858
+ return null;
9859
+ }
9860
+ }
9861
+ function writeIssueArtifact(repoRoot2, prdId, issue) {
9862
+ writeFileSync8(
9863
+ getIssueArtifactPath2(repoRoot2, prdId, issue.id),
9864
+ JSON.stringify(issue, null, 2),
9865
+ "utf-8"
9866
+ );
9867
+ }
9868
+ async function reconcileLocalBlockedIssues(prdId, repoRoot2) {
9869
+ const root = repoRoot2 ?? process.cwd();
9870
+ const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
9871
+ if (!issuesResult.ok) return;
9872
+ const issues = issuesResult.data;
9873
+ for (const issue of issues) {
9874
+ const isBlocked = issue.status === "blocked";
9875
+ if (!isBlocked) continue;
9876
+ if (issue.dependencyIssueIds.length === 0) continue;
9877
+ const depResults = await Promise.all(
9878
+ issue.dependencyIssueIds.map(
9879
+ (depId) => hasLocalIssueMergeReceipt(prdId, depId, root)
9880
+ )
9881
+ );
9882
+ if (!depResults.every(Boolean)) continue;
9883
+ const updated = readIssueArtifact(root, prdId, issue.id);
9884
+ if (!updated) continue;
9885
+ updated.blockedReason = null;
9886
+ updated.labels = updated.labels.filter((l) => l !== "blocked");
9887
+ if (!updated.labels.includes("ready-for-agent")) {
9888
+ updated.labels.push("ready-for-agent");
9889
+ }
9890
+ if (hasValidLabels(updated.labels)) {
9891
+ updated.status = "ready-for-agent";
9892
+ } else {
9893
+ updated.status = "needs-triage";
9894
+ updated.labels = updated.labels.filter((l) => l !== "ready-for-agent");
9895
+ if (!updated.labels.includes("needs-triage")) {
9896
+ updated.labels.push("needs-triage");
9897
+ }
9898
+ }
9899
+ updated.receipts.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9900
+ writeIssueArtifact(root, prdId, updated);
9901
+ }
9902
+ }
9903
+ async function cleanupChildGitHubIssue(issue, issueProvider) {
9904
+ const issueNumber = issue.githubProjection.issueNumber;
9905
+ if (!issueNumber) return { ok: true };
9906
+ if (!issueProvider) {
9907
+ return {
9908
+ ok: false,
9909
+ failureCode: "missing_issue_provider",
9910
+ repairGuidance: "Issue provider is not available. Cannot close child GitHub Issue or remove labels. Configure a GitHub Issue provider to enable cleanup parity."
9911
+ };
9912
+ }
9913
+ for (const label of CHILD_CLEANUP_LABELS) {
9914
+ try {
9915
+ await issueProvider.removeLabel(issueNumber, label);
9916
+ } catch {
9917
+ }
9918
+ }
9919
+ try {
9920
+ await issueProvider.closeIssue(issueNumber);
9921
+ } catch {
9922
+ }
9923
+ return { ok: true };
9924
+ }
9925
+ async function runLocalQueueLoop(prdId, repoRoot2, issueProvider) {
9926
+ const root = repoRoot2 ?? process.cwd();
9927
+ const completedIssues = [];
9928
+ const blockedIssues = [];
9929
+ await reconcileLocalBlockedIssues(prdId, root);
9930
+ for (; ; ) {
9931
+ const runnable = await getRunnableLocalIssues(prdId, root);
9932
+ if (runnable.length === 0) {
9933
+ const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
9934
+ if (issuesResult.ok) {
9935
+ for (const issue2 of issuesResult.data) {
9936
+ if (issue2.status === "blocked") {
9937
+ blockedIssues.push(issue2.id);
9938
+ }
9939
+ }
9940
+ }
9941
+ break;
9942
+ }
9943
+ const issue = runnable[0];
9944
+ const runResult = await runLocalIssue(prdId, issue.id, void 0, root);
9945
+ if (!runResult.ok) {
9946
+ return {
9947
+ ok: false,
9948
+ completedIssues,
9949
+ blockedIssues,
9950
+ failureCode: runResult.failureCode ?? `Issue ${issue.id} failed`,
9951
+ repairGuidance: `Local issue run failed for ${issue.id}. Check the issue artifact and branch state.`,
9952
+ blockedGate: "queue"
9953
+ };
9954
+ }
9955
+ const mergeResult = await squashMergeLocalIssue(
9956
+ prdId,
9957
+ issue.id,
9958
+ void 0,
9959
+ root
9960
+ );
9961
+ if (!mergeResult.ok) {
9962
+ if (mergeResult.failureCode === "already_merged") {
9963
+ const alreadyMergedArtifact = readIssueArtifact(root, prdId, issue.id);
9964
+ if (alreadyMergedArtifact) {
9965
+ alreadyMergedArtifact.status = "complete";
9966
+ alreadyMergedArtifact.receipts.completedAt = mergeResult.receipt?.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
9967
+ alreadyMergedArtifact.receipts.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9968
+ writeIssueArtifact(root, prdId, alreadyMergedArtifact);
9969
+ const cleanupResult = await cleanupChildGitHubIssue(
9970
+ alreadyMergedArtifact,
9971
+ issueProvider
9972
+ );
9973
+ if (!cleanupResult.ok) {
9974
+ return {
9975
+ ok: false,
9976
+ completedIssues,
9977
+ blockedIssues,
9978
+ failureCode: cleanupResult.failureCode ?? "child_cleanup_failed",
9979
+ repairGuidance: cleanupResult.repairGuidance ?? `Child GitHub Issue cleanup failed for ${issue.id}.`,
9980
+ blockedGate: "queue"
9981
+ };
9982
+ }
9983
+ }
9984
+ completedIssues.push(issue.id);
9985
+ await reconcileLocalBlockedIssues(prdId, root);
9986
+ continue;
9987
+ }
9988
+ return {
9989
+ ok: false,
9990
+ completedIssues,
9991
+ blockedIssues,
9992
+ failureCode: mergeResult.failureCode ?? `Merge of ${issue.id} failed`,
9993
+ repairGuidance: mergeResult.repairGuidance,
9994
+ blockedGate: "queue"
9995
+ };
9996
+ }
9997
+ const completedArtifact = readIssueArtifact(root, prdId, issue.id);
9998
+ if (completedArtifact) {
9999
+ completedArtifact.status = "complete";
10000
+ completedArtifact.receipts.completedAt = mergeResult.receipt?.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
10001
+ completedArtifact.receipts.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
10002
+ writeIssueArtifact(root, prdId, completedArtifact);
10003
+ const cleanupResult = await cleanupChildGitHubIssue(
10004
+ completedArtifact,
10005
+ issueProvider
10006
+ );
10007
+ if (!cleanupResult.ok) {
10008
+ return {
10009
+ ok: false,
10010
+ completedIssues,
10011
+ blockedIssues,
10012
+ failureCode: cleanupResult.failureCode ?? "child_cleanup_failed",
10013
+ repairGuidance: cleanupResult.repairGuidance ?? `Child GitHub Issue cleanup failed for ${issue.id}.`,
10014
+ blockedGate: "queue"
10015
+ };
10016
+ }
10017
+ }
10018
+ completedIssues.push(issue.id);
10019
+ await reconcileLocalBlockedIssues(prdId, root);
10020
+ }
10021
+ return {
10022
+ ok: true,
10023
+ completedIssues,
10024
+ blockedIssues
10025
+ };
10026
+ }
10027
+
10028
+ // commands/queue.ts
10029
+ init_common();
10030
+ import { Effect as Effect8 } from "effect";
10031
+
10032
+ // issues/select-issue.ts
10033
+ init_common();
10034
+ var TYPE_PRIORITY = {
10035
+ "type:bugfix": 1,
10036
+ "type:infra": 2,
10037
+ "type:feature": 3,
10038
+ "type:polish": 4,
10039
+ "type:refactor": 5
10040
+ };
10041
+ function selectIssue(candidates, options = {}) {
10042
+ const blockedLabel = options.blockedLabel ?? "blocked";
10043
+ const agentInProgressLabel = options.agentInProgressLabel ?? "agent-in-progress";
10044
+ if (candidates.length === 0) {
10045
+ return { ok: false, reason: "No candidate issues provided." };
10046
+ }
10047
+ const unblocked = candidates.filter(
10048
+ (issue) => !issue.labels.includes(blockedLabel)
10049
+ );
10050
+ if (unblocked.length === 0) {
10051
+ return { ok: false, reason: "All candidate issues are blocked." };
10052
+ }
10053
+ const valid2 = [];
10054
+ const rejected = [];
10055
+ for (const issue of unblocked) {
10056
+ if (issue.labels.includes(agentInProgressLabel)) {
10057
+ rejected.push(
10058
+ `Issue #${issue.number} is labeled ${agentInProgressLabel}.`
10059
+ );
10060
+ continue;
10061
+ }
10062
+ const typeLabels = issue.labels.filter(
10063
+ (l) => TYPE_LABELS.includes(l)
10064
+ );
10065
+ if (typeLabels.length !== 1) {
10066
+ rejected.push(
10067
+ `Issue #${issue.number} has ${typeLabels.length} type label(s); expected exactly one.`
10068
+ );
10069
+ continue;
10070
+ }
10071
+ valid2.push({ issue, priority: TYPE_PRIORITY[typeLabels[0]] });
10072
+ }
10073
+ if (valid2.length === 0) {
10074
+ return {
10075
+ ok: false,
10076
+ reason: "No runnable issues: " + rejected.join(" ")
9694
10077
  };
9695
10078
  }
9696
10079
  valid2.sort((a, b) => {
@@ -10164,7 +10547,7 @@ function validateChangedPlanningPaths(paths, options) {
10164
10547
  continue;
10165
10548
  }
10166
10549
  if (options?.repoRoot) {
10167
- const fullPath = join20(options.repoRoot, p);
10550
+ const fullPath = join21(options.repoRoot, p);
10168
10551
  try {
10169
10552
  if (lstatSync(fullPath).isSymbolicLink()) {
10170
10553
  rejected.push(p);
@@ -10275,6 +10658,12 @@ async function validateFinalReviewChildCompleteness(repoRoot2, prdRef, record, i
10275
10658
  );
10276
10659
  continue;
10277
10660
  }
10661
+ if (!issueData || typeof issueData.state !== "string") {
10662
+ incompleteChildren.push(
10663
+ `${child.id} (#${child.number}): failed to fetch issue state`
10664
+ );
10665
+ continue;
10666
+ }
10278
10667
  if (issueData.state === "closed") {
10279
10668
  continue;
10280
10669
  }
@@ -10331,11 +10720,7 @@ function canRetryFinalReviewBlock(record) {
10331
10720
  }
10332
10721
  function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
10333
10722
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
10334
- return existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : promptTemplate;
10335
- }
10336
- function formatVerificationCommands(commands) {
10337
- if (commands.length === 0) return "No verification commands configured.";
10338
- return commands.map((cmd) => `- ${cmd.label}: ${cmd.command}`).join("\n");
10723
+ return existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : promptTemplate;
10339
10724
  }
10340
10725
  function buildFinalReviewPrompt(options) {
10341
10726
  return [
@@ -10351,9 +10736,6 @@ function buildFinalReviewPrompt(options) {
10351
10736
  "",
10352
10737
  `Run this command before handoff when retouch changes are made: pourkit run-verification --target ${options.targetName}`,
10353
10738
  "",
10354
- "Underlying project commands:",
10355
- formatVerificationCommands(options.verificationCommands),
10356
- "",
10357
10739
  "Evidence Packet (do not infer PRD context from local state files):",
10358
10740
  JSON.stringify(options.evidencePacket, null, 2),
10359
10741
  "",
@@ -10385,7 +10767,7 @@ function buildReconciliationBranchName(prdRef) {
10385
10767
  return `${normalizePrdRunRef2(prdRef)}-reconciliation`;
10386
10768
  }
10387
10769
  function reconciliationWorktreePath(repoRoot2, branchName) {
10388
- return join20(repoRoot2, ".sandcastle", "worktrees", branchName);
10770
+ return join21(repoRoot2, ".sandcastle", "worktrees", branchName);
10389
10771
  }
10390
10772
  function reconciliationReceiptWorktreePath(branchName) {
10391
10773
  return `.sandcastle/worktrees/${branchName}`;
@@ -10397,7 +10779,7 @@ function buildFinalReviewBranchName(prdRef) {
10397
10779
  return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
10398
10780
  }
10399
10781
  function finalReviewWorktreePath(repoRoot2, branchName) {
10400
- return join20(
10782
+ return join21(
10401
10783
  repoRoot2,
10402
10784
  ".sandcastle",
10403
10785
  "worktrees",
@@ -10420,7 +10802,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
10420
10802
  }
10421
10803
  paths.add(path9);
10422
10804
  };
10423
- const diffResult = spawnSync2(
10805
+ const diffResult = spawnSync3(
10424
10806
  "git",
10425
10807
  ["diff", "--name-only", mergeBase, "--", "."],
10426
10808
  {
@@ -10431,7 +10813,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
10431
10813
  for (const path9 of diffResult.stdout.split(/\r?\n/).filter(Boolean)) {
10432
10814
  addPath(path9);
10433
10815
  }
10434
- const statusResult = spawnSync2(
10816
+ const statusResult = spawnSync3(
10435
10817
  "git",
10436
10818
  ["status", "--porcelain", "--untracked-files=all"],
10437
10819
  {
@@ -10452,7 +10834,7 @@ function buildFinalReviewFinalizerPrompt(options) {
10452
10834
  options.repoRoot,
10453
10835
  options.promptTemplate
10454
10836
  );
10455
- const promptBody = existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : options.promptTemplate;
10837
+ const promptBody = existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : options.promptTemplate;
10456
10838
  return [
10457
10839
  "# Final Review Retouch PR Finalizer",
10458
10840
  "",
@@ -10478,15 +10860,12 @@ function buildFinalReviewFinalizerPrompt(options) {
10478
10860
  "Changed paths:",
10479
10861
  ...options.changedPaths.map((p) => `- ${p}`),
10480
10862
  "",
10481
- "Verification commands given to Final Review:",
10482
- formatVerificationCommands(options.verificationCommands),
10483
- "",
10484
10863
  promptBody
10485
10864
  ].join("\n");
10486
10865
  }
10487
10866
  async function runFinalReviewPrFinalizer(options) {
10488
10867
  const finalizer = options.target.strategy.finalize.prDescriptionAgent;
10489
- const artifactPathInWorktree = join20(
10868
+ const artifactPathInWorktree = join21(
10490
10869
  ".pourkit",
10491
10870
  ".tmp",
10492
10871
  "finalizer",
@@ -10507,8 +10886,7 @@ async function runFinalReviewPrFinalizer(options) {
10507
10886
  prdBranch: options.prdBranch,
10508
10887
  mergeBase: options.mergeBase,
10509
10888
  summary: options.summary,
10510
- changedPaths: options.changedPaths,
10511
- verificationCommands: options.verificationCommands
10889
+ changedPaths: options.changedPaths
10512
10890
  }),
10513
10891
  branchName: options.branchName,
10514
10892
  repoRoot: options.repoRoot,
@@ -10532,7 +10910,7 @@ function ensureFinalReviewWorktree(options) {
10532
10910
  options.repoRoot,
10533
10911
  options.branchName
10534
10912
  );
10535
- const listResult = spawnSync2("git", ["worktree", "list", "--porcelain"], {
10913
+ const listResult = spawnSync3("git", ["worktree", "list", "--porcelain"], {
10536
10914
  cwd: options.repoRoot,
10537
10915
  encoding: "utf8"
10538
10916
  });
@@ -10568,19 +10946,34 @@ function ensureFinalReviewWorktree(options) {
10568
10946
  };
10569
10947
  }
10570
10948
  mkdirSync11(dirname5(worktreePath), { recursive: true });
10571
- const branchResult = spawnSync2(
10572
- "git",
10573
- ["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
10574
- { cwd: options.repoRoot, encoding: "utf8" }
10575
- );
10576
- if (branchResult.status !== 0) {
10577
- return {
10578
- ok: false,
10579
- reason: `Final Review failed to prepare branch ${options.branchName}.`,
10580
- diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
10581
- };
10949
+ if (options.isLocal) {
10950
+ const branchResult = spawnSync3(
10951
+ "git",
10952
+ ["branch", "-f", options.branchName, options.checkoutBase],
10953
+ { cwd: options.repoRoot, encoding: "utf8" }
10954
+ );
10955
+ if (branchResult.status !== 0) {
10956
+ return {
10957
+ ok: false,
10958
+ reason: `Final Review failed to prepare branch ${options.branchName}.`,
10959
+ diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
10960
+ };
10961
+ }
10962
+ } else {
10963
+ const branchResult = spawnSync3(
10964
+ "git",
10965
+ ["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
10966
+ { cwd: options.repoRoot, encoding: "utf8" }
10967
+ );
10968
+ if (branchResult.status !== 0) {
10969
+ return {
10970
+ ok: false,
10971
+ reason: `Final Review failed to prepare branch ${options.branchName}.`,
10972
+ diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
10973
+ };
10974
+ }
10582
10975
  }
10583
- const addResult = spawnSync2(
10976
+ const addResult = spawnSync3(
10584
10977
  "git",
10585
10978
  ["worktree", "add", worktreePath, options.branchName],
10586
10979
  { cwd: options.repoRoot, encoding: "utf8" }
@@ -10622,7 +11015,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
10622
11015
  if (existingPr && existingPr.state === "OPEN") {
10623
11016
  return existingPr;
10624
11017
  }
10625
- const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-retouch-"));
11018
+ const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-retouch-"));
10626
11019
  try {
10627
11020
  runGitOrThrow(
10628
11021
  options.repoRoot,
@@ -10640,8 +11033,8 @@ async function createOrReuseFinalReviewRetouchPr(options) {
10640
11033
  "create retouch branch"
10641
11034
  );
10642
11035
  for (const changedFile of options.changedPaths) {
10643
- const sourcePath = join20(options.sourceWorktreePath, changedFile);
10644
- const targetPath = join20(worktreePath, changedFile);
11036
+ const sourcePath = join21(options.sourceWorktreePath, changedFile);
11037
+ const targetPath = join21(worktreePath, changedFile);
10645
11038
  mkdirSync11(dirname5(targetPath), { recursive: true });
10646
11039
  cpSync(sourcePath, targetPath, { recursive: true });
10647
11040
  }
@@ -10656,13 +11049,13 @@ async function createOrReuseFinalReviewRetouchPr(options) {
10656
11049
  "commit retouch diff"
10657
11050
  );
10658
11051
  } finally {
10659
- spawnSync2("git", ["worktree", "remove", "--force", worktreePath], {
11052
+ spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
10660
11053
  cwd: options.repoRoot,
10661
11054
  encoding: "utf8"
10662
11055
  });
10663
11056
  rmSync3(worktreePath, { recursive: true, force: true });
10664
11057
  }
10665
- const pushResult = spawnSync2(
11058
+ const pushResult = spawnSync3(
10666
11059
  "git",
10667
11060
  ["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
10668
11061
  {
@@ -10690,6 +11083,156 @@ async function createOrReuseFinalReviewRetouchPr(options) {
10690
11083
  })
10691
11084
  });
10692
11085
  }
11086
+ async function writeAndVerifyLocalDualReceipt(repoRoot2, prdRef, record, targetName, prdBranch, mergeBase, verdict, reviewedAt, isLocalMode) {
11087
+ if (!isLocalMode) return null;
11088
+ let currentRecord = await readLocalPrdRun(repoRoot2, prdRef);
11089
+ if (!currentRecord) {
11090
+ currentRecord = {
11091
+ prdId: normalizePrdRunRef2(prdRef),
11092
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
11093
+ receipts: {},
11094
+ metadata: {}
11095
+ };
11096
+ }
11097
+ const localStoreReceipt = {
11098
+ completedAt: reviewedAt,
11099
+ targetName,
11100
+ prdBranch,
11101
+ mergeBase,
11102
+ verdict,
11103
+ diagnostics: [],
11104
+ artifactPath: ".pourkit/final-review-artifact.json"
11105
+ };
11106
+ await writeLocalPrdRunRecord(repoRoot2, prdRef, {
11107
+ ...currentRecord,
11108
+ receipts: {
11109
+ ...currentRecord.receipts,
11110
+ finalReview: localStoreReceipt
11111
+ }
11112
+ });
11113
+ const prdRunStateResult = readPrdRun(repoRoot2, prdRef);
11114
+ const localStoreResult = await readLocalPrdRun(repoRoot2, prdRef);
11115
+ if (!prdRunStateResult.record?.finalReview) {
11116
+ const reason = `PRD Run State missing finalReview receipt after write for ${prdRef}.`;
11117
+ writePrdRunRecord(repoRoot2, {
11118
+ ...record,
11119
+ prdRef,
11120
+ status: "blocked",
11121
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11122
+ blockedGate: "final-review",
11123
+ blockedReason: reason,
11124
+ diagnostics: [reason],
11125
+ targetName,
11126
+ offendingPaths: []
11127
+ });
11128
+ return {
11129
+ prdRef,
11130
+ status: "blocked",
11131
+ blockedGate: "final-review",
11132
+ blockedReason: reason,
11133
+ diagnostics: [reason],
11134
+ offendingPaths: []
11135
+ };
11136
+ }
11137
+ if (!localStoreResult?.receipts.finalReview) {
11138
+ const reason = `Local PRD Run store missing finalReview receipt after write for ${prdRef}.`;
11139
+ writePrdRunRecord(repoRoot2, {
11140
+ ...record,
11141
+ prdRef,
11142
+ status: "blocked",
11143
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11144
+ blockedGate: "final-review",
11145
+ blockedReason: reason,
11146
+ diagnostics: [reason],
11147
+ targetName,
11148
+ offendingPaths: []
11149
+ });
11150
+ return {
11151
+ prdRef,
11152
+ status: "blocked",
11153
+ blockedGate: "final-review",
11154
+ blockedReason: reason,
11155
+ diagnostics: [reason],
11156
+ offendingPaths: []
11157
+ };
11158
+ }
11159
+ const stateFr = prdRunStateResult.record.finalReview;
11160
+ const localFr = localStoreResult.receipts.finalReview;
11161
+ const mismatchFields = [];
11162
+ if (stateFr.targetName !== localFr.targetName)
11163
+ mismatchFields.push("targetName");
11164
+ if (stateFr.prdBranch !== localFr.prdBranch) mismatchFields.push("prdBranch");
11165
+ if (stateFr.mergeBase !== localFr.mergeBase) mismatchFields.push("mergeBase");
11166
+ if (stateFr.verdict !== localFr.verdict) mismatchFields.push("verdict");
11167
+ if (!stateFr.artifactPath !== !localFr.artifactPath)
11168
+ mismatchFields.push("artifactPath");
11169
+ if (!stateFr.diagnostics !== !localFr.diagnostics)
11170
+ mismatchFields.push("diagnostics");
11171
+ if (!stateFr.reviewedAt !== !localFr.completedAt)
11172
+ mismatchFields.push("reviewedTimestamp");
11173
+ if (mismatchFields.length > 0) {
11174
+ const reason = `Dual receipt mismatch on fields: ${mismatchFields.join(", ")}`;
11175
+ writePrdRunRecord(repoRoot2, {
11176
+ ...record,
11177
+ prdRef,
11178
+ status: "blocked",
11179
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11180
+ blockedGate: "final-review",
11181
+ blockedReason: reason,
11182
+ diagnostics: [reason],
11183
+ targetName,
11184
+ finalReview: stateFr,
11185
+ offendingPaths: []
11186
+ });
11187
+ return {
11188
+ prdRef,
11189
+ status: "blocked",
11190
+ blockedGate: "final-review",
11191
+ blockedReason: reason,
11192
+ diagnostics: [reason],
11193
+ offendingPaths: []
11194
+ };
11195
+ }
11196
+ return null;
11197
+ }
11198
+ async function verifyLocalDualFinalReviewReceipts(repoRoot2, prdRef) {
11199
+ const prdRunStateResult = readPrdRun(repoRoot2, prdRef);
11200
+ const stateFr = prdRunStateResult.record?.finalReview;
11201
+ const localStoreResult = await readLocalPrdRun(repoRoot2, prdRef);
11202
+ const localFr = localStoreResult?.receipts.finalReview;
11203
+ const diagnostics = [];
11204
+ const validFinalReviewStatuses = /* @__PURE__ */ new Set(["final_reviewed", "succeeded"]);
11205
+ if (!stateFr || !validFinalReviewStatuses.has(stateFr.status)) {
11206
+ diagnostics.push(
11207
+ stateFr ? `PRD Run State finalReview status is "${stateFr.status}". Expected "final_reviewed" or "succeeded".` : "PRD Run State has no finalReview receipt."
11208
+ );
11209
+ return {
11210
+ ok: false,
11211
+ reason: diagnostics[0],
11212
+ diagnostics
11213
+ };
11214
+ }
11215
+ if (!localFr) {
11216
+ diagnostics.push("Local PRD Run Store has no finalReview receipt.");
11217
+ return {
11218
+ ok: false,
11219
+ reason: diagnostics[0],
11220
+ diagnostics
11221
+ };
11222
+ }
11223
+ const mismatchFields = [];
11224
+ if (stateFr.targetName !== localFr.targetName)
11225
+ mismatchFields.push("targetName");
11226
+ if (stateFr.prdBranch !== localFr.prdBranch) mismatchFields.push("prdBranch");
11227
+ if (stateFr.mergeBase !== localFr.mergeBase) mismatchFields.push("mergeBase");
11228
+ if (stateFr.verdict !== localFr.verdict) mismatchFields.push("verdict");
11229
+ if (mismatchFields.length > 0) {
11230
+ const reason = `Dual receipt mismatch on fields: ${mismatchFields.join(", ")}`;
11231
+ diagnostics.push(reason);
11232
+ return { ok: false, reason, diagnostics };
11233
+ }
11234
+ return { ok: true };
11235
+ }
10693
11236
  async function runPrdRunFinalReviewCommand(options) {
10694
11237
  const prdRef = normalizePrdRunRef2(options.prdRef);
10695
11238
  const targetName = options.targetName.trim();
@@ -10754,6 +11297,39 @@ async function runPrdRunFinalReviewCommand(options) {
10754
11297
  offendingPaths: []
10755
11298
  };
10756
11299
  }
11300
+ if (record.mode === "local") {
11301
+ const runnableIssues = await getRunnableLocalIssues(
11302
+ prdRef,
11303
+ options.repoRoot
11304
+ );
11305
+ if (runnableIssues.length > 0) {
11306
+ const reason = `PRD Run ${prdRef} is marked "drained" but ${runnableIssues.length} runnable local Issues remain. Queue drain receipt may be premature.`;
11307
+ writePrdRunRecord(options.repoRoot, {
11308
+ ...record,
11309
+ status: "blocked",
11310
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11311
+ blockedGate: "final-review",
11312
+ blockedReason: reason,
11313
+ diagnostics: [
11314
+ `Current status: ${record.status}`,
11315
+ `Runnable issues remaining: ${runnableIssues.length}`
11316
+ ],
11317
+ targetName,
11318
+ offendingPaths: []
11319
+ });
11320
+ return {
11321
+ prdRef,
11322
+ status: "blocked",
11323
+ blockedGate: "final-review",
11324
+ blockedReason: reason,
11325
+ diagnostics: [
11326
+ `Current status: ${record.status}`,
11327
+ `Runnable issues remaining: ${runnableIssues.length}`
11328
+ ],
11329
+ offendingPaths: []
11330
+ };
11331
+ }
11332
+ }
10757
11333
  if (!options.issueProvider) {
10758
11334
  const reason = "Missing IssueProvider. Cannot validate child completeness.";
10759
11335
  writePrdRunRecord(options.repoRoot, {
@@ -10862,8 +11438,19 @@ async function runPrdRunFinalReviewCommand(options) {
10862
11438
  offendingPaths: []
10863
11439
  };
10864
11440
  }
10865
- const verificationCommands = targetConfig.strategy.verify?.commands ?? [];
10866
- const mergeBaseResult = computeFinalReviewMergeBase(options.repoRoot, prdRef);
11441
+ const isLocalMode = record.mode === "local";
11442
+ let mergeBaseResult;
11443
+ let prdBranch;
11444
+ if (isLocalMode) {
11445
+ prdBranch = getLocalPrdBranchName(prdRef);
11446
+ mergeBaseResult = computeLocalFinalReviewMergeBase(
11447
+ options.repoRoot,
11448
+ prdBranch
11449
+ );
11450
+ } else {
11451
+ prdBranch = record.prdBranch ?? prdRef;
11452
+ mergeBaseResult = computeFinalReviewMergeBase(options.repoRoot, prdRef);
11453
+ }
10867
11454
  if (!mergeBaseResult.ok) {
10868
11455
  writePrdRunRecord(options.repoRoot, {
10869
11456
  ...record,
@@ -10876,7 +11463,7 @@ async function runPrdRunFinalReviewCommand(options) {
10876
11463
  finalReview: {
10877
11464
  status: "blocked",
10878
11465
  targetName,
10879
- prdBranch: record.prdBranch ?? prdRef,
11466
+ prdBranch,
10880
11467
  mergeBase: mergeBaseResult.mergeBase,
10881
11468
  diagnostics: mergeBaseResult.diagnostics,
10882
11469
  reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -10892,7 +11479,6 @@ async function runPrdRunFinalReviewCommand(options) {
10892
11479
  offendingPaths: []
10893
11480
  };
10894
11481
  }
10895
- const prdBranch = record.prdBranch ?? prdRef;
10896
11482
  const manifestResult = readPlanningArtifactManifest(options.repoRoot, prdRef);
10897
11483
  if (!manifestResult.ok) {
10898
11484
  const reason = `Evidence Packet construction failed: ${manifestResult.reason}`;
@@ -10952,11 +11538,12 @@ async function runPrdRunFinalReviewCommand(options) {
10952
11538
  planning: record.planning,
10953
11539
  finalReview: startReceipt
10954
11540
  });
10955
- const finalReviewBranchName = buildFinalReviewBranchName(prdBranch);
11541
+ const finalReviewBranchName = isLocalMode ? `${prdBranch.replace(/\//g, "-")}-final-review-retouch` : buildFinalReviewBranchName(prdBranch);
10956
11542
  const worktreeResult = ensureFinalReviewWorktree({
10957
11543
  repoRoot: options.repoRoot,
10958
11544
  branchName: finalReviewBranchName,
10959
- checkoutBase: prdBranch
11545
+ checkoutBase: prdBranch,
11546
+ isLocal: isLocalMode
10960
11547
  });
10961
11548
  if (!worktreeResult.ok) {
10962
11549
  writePrdRunRecord(options.repoRoot, {
@@ -10997,8 +11584,7 @@ async function runPrdRunFinalReviewCommand(options) {
10997
11584
  repoRoot: options.repoRoot,
10998
11585
  promptTemplate: finalReviewConfig.promptTemplate,
10999
11586
  targetName,
11000
- evidencePacket,
11001
- verificationCommands
11587
+ evidencePacket
11002
11588
  }),
11003
11589
  target: targetConfig,
11004
11590
  repoRoot: options.repoRoot,
@@ -11072,7 +11658,7 @@ async function runPrdRunFinalReviewCommand(options) {
11072
11658
  };
11073
11659
  }
11074
11660
  const resolvedWorktreePath = executionResult.worktreePath;
11075
- const artifactPath = join20(
11661
+ const artifactPath = join21(
11076
11662
  resolvedWorktreePath,
11077
11663
  ".pourkit/final-review-artifact.json"
11078
11664
  );
@@ -11141,6 +11727,7 @@ async function runPrdRunFinalReviewCommand(options) {
11141
11727
  }
11142
11728
  const { verdict, summary, diagnostics: artifactDiagnostics } = artifactResult;
11143
11729
  if (verdict === "pass_no_changes") {
11730
+ const reviewedAt = (/* @__PURE__ */ new Date()).toISOString();
11144
11731
  const receipt = {
11145
11732
  status: "succeeded",
11146
11733
  targetName,
@@ -11149,17 +11736,29 @@ async function runPrdRunFinalReviewCommand(options) {
11149
11736
  verdict: "pass_no_changes",
11150
11737
  artifactPath: ".pourkit/final-review-artifact.json",
11151
11738
  diagnostics: [],
11152
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
11739
+ reviewedAt
11153
11740
  };
11154
11741
  writePrdRunRecord(options.repoRoot, {
11155
11742
  ...record,
11156
11743
  status: "final_reviewed",
11157
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11744
+ updatedAt: reviewedAt,
11158
11745
  targetName,
11159
11746
  start: record.start,
11160
11747
  planning: record.planning,
11161
11748
  finalReview: receipt
11162
11749
  });
11750
+ const blocked = await writeAndVerifyLocalDualReceipt(
11751
+ options.repoRoot,
11752
+ prdRef,
11753
+ record,
11754
+ targetName,
11755
+ prdBranch,
11756
+ mergeBaseResult.mergeBase,
11757
+ "pass_no_changes",
11758
+ reviewedAt,
11759
+ isLocalMode
11760
+ );
11761
+ if (blocked) return blocked;
11163
11762
  return {
11164
11763
  prdRef,
11165
11764
  status: "final_reviewed",
@@ -11168,7 +11767,7 @@ async function runPrdRunFinalReviewCommand(options) {
11168
11767
  };
11169
11768
  }
11170
11769
  if (verdict === "pass_with_retouch") {
11171
- if (!options.prProvider) {
11770
+ if (!options.prProvider && !isLocalMode) {
11172
11771
  const reason = "Missing PRProvider. Cannot create retouch PR without a PR provider.";
11173
11772
  writePrdRunRecord(options.repoRoot, {
11174
11773
  ...record,
@@ -11199,7 +11798,7 @@ async function runPrdRunFinalReviewCommand(options) {
11199
11798
  };
11200
11799
  }
11201
11800
  const autoMerge = options.autoMerge ?? true;
11202
- const existingRetouchPrNumber = record.finalReview?.retouchPrNumber;
11801
+ const existingRetouchPrNumber = !isLocalMode ? record.finalReview?.retouchPrNumber : void 0;
11203
11802
  if (existingRetouchPrNumber && autoMerge) {
11204
11803
  const existingPr = await getPrByNumberIfAvailable(
11205
11804
  options.prProvider,
@@ -11285,7 +11884,6 @@ async function runPrdRunFinalReviewCommand(options) {
11285
11884
  mergeBase: mergeBaseResult.mergeBase,
11286
11885
  summary,
11287
11886
  changedPaths: scopeResult.changedPaths,
11288
- verificationCommands,
11289
11887
  logger: options.logger
11290
11888
  });
11291
11889
  if (!finalizerResult.ok) {
@@ -11317,6 +11915,116 @@ async function runPrdRunFinalReviewCommand(options) {
11317
11915
  offendingPaths: []
11318
11916
  };
11319
11917
  }
11918
+ if (isLocalMode) {
11919
+ let localResult;
11920
+ try {
11921
+ const result = await squashFinalReviewRetouch(
11922
+ prdRef,
11923
+ options.repoRoot,
11924
+ finalizerResult.title,
11925
+ finalizerResult.body
11926
+ );
11927
+ if (!result) {
11928
+ const reason = "Retouch branch missing or empty. Cannot squash-merge.";
11929
+ writePrdRunRecord(options.repoRoot, {
11930
+ ...record,
11931
+ status: "blocked",
11932
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11933
+ blockedGate: "final-review",
11934
+ blockedReason: reason,
11935
+ diagnostics: [reason],
11936
+ targetName,
11937
+ finalReview: {
11938
+ status: "blocked",
11939
+ targetName,
11940
+ prdBranch,
11941
+ mergeBase: mergeBaseResult.mergeBase,
11942
+ verdict: "pass_with_retouch",
11943
+ diagnostics: [reason],
11944
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
11945
+ },
11946
+ offendingPaths: []
11947
+ });
11948
+ return {
11949
+ prdRef,
11950
+ status: "blocked",
11951
+ blockedGate: "final-review",
11952
+ blockedReason: reason,
11953
+ diagnostics: [reason],
11954
+ offendingPaths: []
11955
+ };
11956
+ }
11957
+ localResult = result;
11958
+ } catch (error) {
11959
+ const msg = error instanceof Error ? error.message : String(error);
11960
+ writePrdRunRecord(options.repoRoot, {
11961
+ ...record,
11962
+ status: "blocked",
11963
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11964
+ blockedGate: "final-review",
11965
+ blockedReason: `Local retouch squash-merge failed: ${msg}`,
11966
+ diagnostics: [msg],
11967
+ targetName,
11968
+ finalReview: {
11969
+ status: "blocked",
11970
+ targetName,
11971
+ prdBranch,
11972
+ mergeBase: mergeBaseResult.mergeBase,
11973
+ verdict: "pass_with_retouch",
11974
+ diagnostics: [msg],
11975
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
11976
+ },
11977
+ offendingPaths: []
11978
+ });
11979
+ return {
11980
+ prdRef,
11981
+ status: "blocked",
11982
+ blockedGate: "final-review",
11983
+ blockedReason: `Local retouch squash-merge failed: ${msg}`,
11984
+ diagnostics: [msg],
11985
+ offendingPaths: []
11986
+ };
11987
+ }
11988
+ const receipt2 = {
11989
+ status: "final_reviewed",
11990
+ targetName,
11991
+ prdBranch,
11992
+ mergeBase: mergeBaseResult.mergeBase,
11993
+ verdict: "pass_with_retouch",
11994
+ artifactPath: ".pourkit/final-review-artifact.json",
11995
+ diagnostics: [],
11996
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
11997
+ retouchMergeCommit: localResult.mergeCommit,
11998
+ changedPaths: localResult.changedPaths
11999
+ };
12000
+ writePrdRunRecord(options.repoRoot, {
12001
+ ...record,
12002
+ status: "final_reviewed",
12003
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12004
+ targetName,
12005
+ start: record.start,
12006
+ planning: record.planning,
12007
+ finalReview: receipt2
12008
+ });
12009
+ const blocked = await writeAndVerifyLocalDualReceipt(
12010
+ options.repoRoot,
12011
+ prdRef,
12012
+ record,
12013
+ targetName,
12014
+ prdBranch,
12015
+ mergeBaseResult.mergeBase,
12016
+ "pass_with_retouch",
12017
+ (/* @__PURE__ */ new Date()).toISOString(),
12018
+ isLocalMode
12019
+ );
12020
+ if (blocked) return blocked;
12021
+ return {
12022
+ prdRef,
12023
+ status: "final_reviewed",
12024
+ finalReview: receipt2,
12025
+ diagnostics: []
12026
+ };
12027
+ }
11320
12028
  let retouchPr;
11321
12029
  try {
11322
12030
  retouchPr = await createOrReuseFinalReviewRetouchPr({
@@ -11656,7 +12364,7 @@ async function runPrdRunFinalReviewCommand(options) {
11656
12364
  function runPrdRunValidateFinalReviewCommand(options) {
11657
12365
  const prdRef = normalizePrdRunRef2(options.prdRef);
11658
12366
  const checkoutBase = options.checkoutBase ?? prdRef;
11659
- const artifactPath = options.artifactPath ? options.artifactPath : join20(options.repoRoot, ".pourkit", "final-review-artifact.json");
12367
+ const artifactPath = options.artifactPath ? options.artifactPath : join21(options.repoRoot, ".pourkit", "final-review-artifact.json");
11660
12368
  const artifact = parseFinalReviewArtifact(artifactPath);
11661
12369
  if (!artifact.ok) {
11662
12370
  return {
@@ -11670,7 +12378,7 @@ function runPrdRunValidateFinalReviewCommand(options) {
11670
12378
  }
11671
12379
  let changedPaths = options.changedPaths;
11672
12380
  if (artifact.verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && (!artifact.changedPaths || artifact.changedPaths.length === 0)) {
11673
- const diffResult = spawnSync2(
12381
+ const diffResult = spawnSync3(
11674
12382
  "git",
11675
12383
  ["diff", "--name-only", options.reviewBase, "HEAD", "--", "."],
11676
12384
  { cwd: options.repoRoot, encoding: "utf8" }
@@ -11822,12 +12530,12 @@ async function runReconcilePreflightAndSetup(options) {
11822
12530
  );
11823
12531
  const worktreeCwd = worktreePath;
11824
12532
  const existingReconciliationReceipt = record.reconciliation;
11825
- const existingLocalBranch = spawnSync2(
12533
+ const existingLocalBranch = spawnSync3(
11826
12534
  "git",
11827
12535
  ["rev-parse", "--verify", "--quiet", reconciliationBranchName],
11828
12536
  { cwd: options.repoRoot, encoding: "utf8" }
11829
12537
  ).status === 0;
11830
- const existingWorktree = spawnSync2(
12538
+ const existingWorktree = spawnSync3(
11831
12539
  "git",
11832
12540
  ["worktree", "list", "--porcelain"],
11833
12541
  { cwd: options.repoRoot, encoding: "utf8" }
@@ -11880,7 +12588,7 @@ async function runReconcilePreflightAndSetup(options) {
11880
12588
  branchAction: "blocked"
11881
12589
  };
11882
12590
  }
11883
- const resetResult = spawnSync2(
12591
+ const resetResult = spawnSync3(
11884
12592
  "git",
11885
12593
  ["reset", "--hard", `origin/${prdBranch}`],
11886
12594
  { cwd: worktreeCwd, encoding: "utf8" }
@@ -11923,7 +12631,7 @@ async function runReconcilePreflightAndSetup(options) {
11923
12631
  );
11924
12632
  return { ok: false, diagnostics, branchAction: "blocked" };
11925
12633
  }
11926
- const adoptionCheck = spawnSync2(
12634
+ const adoptionCheck = spawnSync3(
11927
12635
  "git",
11928
12636
  [
11929
12637
  "merge-base",
@@ -11971,7 +12679,7 @@ async function runReconcilePreflightAndSetup(options) {
11971
12679
  );
11972
12680
  return { ok: false, diagnostics, branchAction: "blocked" };
11973
12681
  }
11974
- const branchResult = spawnSync2(
12682
+ const branchResult = spawnSync3(
11975
12683
  "git",
11976
12684
  ["branch", "-f", reconciliationBranchName, `origin/${prdBranch}`],
11977
12685
  { cwd: options.repoRoot, encoding: "utf8" }
@@ -11986,13 +12694,13 @@ async function runReconcilePreflightAndSetup(options) {
11986
12694
  return { ok: false, diagnostics, branchAction: "blocked" };
11987
12695
  }
11988
12696
  mkdirSync11(dirname5(worktreePath), { recursive: true });
11989
- const addResult = spawnSync2(
12697
+ const addResult = spawnSync3(
11990
12698
  "git",
11991
12699
  ["worktree", "add", worktreePath, reconciliationBranchName],
11992
12700
  { cwd: options.repoRoot, encoding: "utf8" }
11993
12701
  );
11994
12702
  if (addResult.status !== 0) {
11995
- spawnSync2("git", ["branch", "-D", reconciliationBranchName], {
12703
+ spawnSync3("git", ["branch", "-D", reconciliationBranchName], {
11996
12704
  cwd: options.repoRoot,
11997
12705
  encoding: "utf8"
11998
12706
  });
@@ -12040,7 +12748,7 @@ function buildReconciliationPrBody(options) {
12040
12748
  function commitAndPushReconciliationChanges(options) {
12041
12749
  const diagnostics = [];
12042
12750
  const existingPaths = options.changedPlanningPaths.filter((p) => {
12043
- return existsSync17(join20(options.worktreeCwd, p));
12751
+ return existsSync17(join21(options.worktreeCwd, p));
12044
12752
  });
12045
12753
  if (existingPaths.length === 0) {
12046
12754
  diagnostics.push(
@@ -12048,7 +12756,7 @@ function commitAndPushReconciliationChanges(options) {
12048
12756
  );
12049
12757
  return { ok: true, diagnostics: [...diagnostics] };
12050
12758
  }
12051
- const addResult = spawnSync2("git", ["add", "--", ...existingPaths], {
12759
+ const addResult = spawnSync3("git", ["add", "--", ...existingPaths], {
12052
12760
  cwd: options.worktreeCwd,
12053
12761
  encoding: "utf8"
12054
12762
  });
@@ -12065,7 +12773,7 @@ function commitAndPushReconciliationChanges(options) {
12065
12773
  };
12066
12774
  }
12067
12775
  const commitTitle = `docs: reconcile ${normalizePrdRunRef2(options.prdRef)} architecture`;
12068
- const commitResult = spawnSync2("git", ["commit", "-m", commitTitle], {
12776
+ const commitResult = spawnSync3("git", ["commit", "-m", commitTitle], {
12069
12777
  cwd: options.worktreeCwd,
12070
12778
  encoding: "utf8"
12071
12779
  });
@@ -12089,7 +12797,7 @@ function commitAndPushReconciliationChanges(options) {
12089
12797
  };
12090
12798
  }
12091
12799
  }
12092
- const pushResult = spawnSync2(
12800
+ const pushResult = spawnSync3(
12093
12801
  "git",
12094
12802
  [
12095
12803
  "push",
@@ -12183,7 +12891,7 @@ async function runPrdRunReconcileCommand(options) {
12183
12891
  const record = preflightResult.record;
12184
12892
  const prdBranch = record?.prdBranch ?? prdRef;
12185
12893
  const mergeBase = preflightResult.mergeBase;
12186
- const worktreeCwd = preflightResult.worktreePath ? join20(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
12894
+ const worktreeCwd = preflightResult.worktreePath ? join21(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
12187
12895
  const staleSucceededRecovery = assessStaleSucceededReconciliationReceipt({
12188
12896
  receipt: record?.reconciliation,
12189
12897
  worktreeCwd,
@@ -12485,7 +13193,7 @@ async function runPrdRunReconcileCommand(options) {
12485
13193
  baseRef: prdBranch,
12486
13194
  checkoutBase,
12487
13195
  reviewBase: mergeBase,
12488
- worktreePath: join20(options.repoRoot, preflightResult.worktreePath),
13196
+ worktreePath: join21(options.repoRoot, preflightResult.worktreePath),
12489
13197
  sandbox: options.config.sandbox,
12490
13198
  artifactPath,
12491
13199
  logger: options.logger
@@ -12586,7 +13294,7 @@ async function runPrdRunReconcileCommand(options) {
12586
13294
  offendingPaths: []
12587
13295
  };
12588
13296
  }
12589
- const fullArtifactPath = join20(executionResult.worktreePath, artifactPath);
13297
+ const fullArtifactPath = join21(executionResult.worktreePath, artifactPath);
12590
13298
  let artifactContent;
12591
13299
  try {
12592
13300
  artifactContent = JSON.parse(retryResult.artifact.value);
@@ -12693,11 +13401,11 @@ async function runPrdRunReconcileCommand(options) {
12693
13401
  const artifactResult = artifactContent.result ?? "changes_produced";
12694
13402
  const artifactChangedPaths = artifactContent.changedPlanningPaths ?? [];
12695
13403
  const artifactNoChangeRationale = artifactContent.noChangeRationale;
12696
- const gitStatusResult = spawnSync2("git", ["status", "--short"], {
13404
+ const gitStatusResult = spawnSync3("git", ["status", "--short"], {
12697
13405
  cwd: worktreeCwd,
12698
13406
  encoding: "utf8"
12699
13407
  });
12700
- const gitDiffResult = spawnSync2("git", ["diff", "--name-status"], {
13408
+ const gitDiffResult = spawnSync3("git", ["diff", "--name-status"], {
12701
13409
  cwd: worktreeCwd,
12702
13410
  encoding: "utf8"
12703
13411
  });
@@ -13450,7 +14158,7 @@ async function runPrdRunReconcileCommand(options) {
13450
14158
  }
13451
14159
  function computeFinalReviewMergeBase(repoRoot2, prdRef) {
13452
14160
  const normalized = normalizePrdRunRef2(prdRef);
13453
- const fetchResult = spawnSync2("git", ["fetch", "origin", "dev", normalized], {
14161
+ const fetchResult = spawnSync3("git", ["fetch", "origin", "dev", normalized], {
13454
14162
  cwd: repoRoot2,
13455
14163
  encoding: "utf8"
13456
14164
  });
@@ -13470,7 +14178,7 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
13470
14178
  if (fetchResult.stderr?.toString?.()?.trim()) {
13471
14179
  fetchDiagnostics.push(fetchResult.stderr.toString().trim());
13472
14180
  }
13473
- const mergeBaseResult = spawnSync2(
14181
+ const mergeBaseResult = spawnSync3(
13474
14182
  "git",
13475
14183
  ["merge-base", "origin/dev", `origin/${normalized}`],
13476
14184
  {
@@ -13506,6 +14214,37 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
13506
14214
  }
13507
14215
  return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
13508
14216
  }
14217
+ function computeLocalFinalReviewMergeBase(repoRoot2, prdBranch) {
14218
+ const mergeBaseResult = spawnSync3("git", ["merge-base", "dev", prdBranch], {
14219
+ cwd: repoRoot2,
14220
+ encoding: "utf8"
14221
+ });
14222
+ if (mergeBaseResult.status !== 0) {
14223
+ const diagnostics = [];
14224
+ if (mergeBaseResult.stderr?.toString?.()?.trim()) {
14225
+ diagnostics.push(mergeBaseResult.stderr.toString().trim());
14226
+ }
14227
+ if (mergeBaseResult.stdout?.toString?.()?.trim()) {
14228
+ diagnostics.push(mergeBaseResult.stdout.toString().trim());
14229
+ }
14230
+ return {
14231
+ ok: false,
14232
+ gate: "final-review",
14233
+ reason: `Final Review merge-base computation failed for dev and ${prdBranch}.`,
14234
+ diagnostics: diagnostics.length > 0 ? diagnostics : ["git merge-base returned non-zero exit status"]
14235
+ };
14236
+ }
14237
+ const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
14238
+ if (!mergeBase) {
14239
+ return {
14240
+ ok: false,
14241
+ gate: "final-review",
14242
+ reason: `Final Review merge-base returned empty result for dev and ${prdBranch}.`,
14243
+ diagnostics: [`merge-base stdout was empty for dev..${prdBranch}`]
14244
+ };
14245
+ }
14246
+ return { ok: true, mergeBase, diagnostics: [] };
14247
+ }
13509
14248
  function validateReconciliationArtifact2(artifact, context) {
13510
14249
  const errors = [];
13511
14250
  if (!artifact.prdRef) {
@@ -13613,17 +14352,17 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
13613
14352
  const blockerBugs = [];
13614
14353
  const blockersFixed = [];
13615
14354
  const nonBlockers = [];
13616
- const prdMirrorPath = join20(repoRoot2, PRD_047_PRD_MIRROR_PATH);
13617
- const completionsDir = join20(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
14355
+ const prdMirrorPath = join21(repoRoot2, PRD_047_PRD_MIRROR_PATH);
14356
+ const completionsDir = join21(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
13618
14357
  let escapeHatchPresent = false;
13619
14358
  if (existsSync17(completionsDir)) {
13620
- const files = readdirSync6(completionsDir);
14359
+ const files = readdirSync5(completionsDir);
13621
14360
  escapeHatchPresent = files.some(
13622
14361
  (f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
13623
14362
  );
13624
14363
  }
13625
14364
  if (!escapeHatchPresent) {
13626
- escapeHatchPresent = existsSync17(join20(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
14365
+ escapeHatchPresent = existsSync17(join21(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
13627
14366
  }
13628
14367
  const prdMirrorExists = existsSync17(prdMirrorPath);
13629
14368
  if (!prdMirrorExists) {
@@ -13632,7 +14371,7 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
13632
14371
  );
13633
14372
  }
13634
14373
  if (prdMirrorExists) {
13635
- const mirrorContent = readFileSync16(prdMirrorPath, "utf-8");
14374
+ const mirrorContent = readFileSync17(prdMirrorPath, "utf-8");
13636
14375
  const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
13637
14376
  const hasEscapeHatch = mirrorContent.includes("escape hatch");
13638
14377
  if (!hasReconcileCommand) {
@@ -13646,16 +14385,16 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
13646
14385
  );
13647
14386
  }
13648
14387
  }
13649
- const childIssueDir = join20(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
14388
+ const childIssueDir = join21(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
13650
14389
  const childIssuesExist = existsSync17(childIssueDir);
13651
14390
  if (!childIssuesExist) {
13652
14391
  nonBlockers.push(
13653
14392
  `PRD-0047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
13654
14393
  );
13655
14394
  }
13656
- const manifestPath = join20(repoRoot2, PRD_047_MANIFEST_PATH);
14395
+ const manifestPath = join21(repoRoot2, PRD_047_MANIFEST_PATH);
13657
14396
  if (existsSync17(manifestPath)) {
13658
- const manifestContent = readFileSync16(manifestPath, "utf-8");
14397
+ const manifestContent = readFileSync17(manifestPath, "utf-8");
13659
14398
  if (!manifestContent.includes("ready_for_prepare")) {
13660
14399
  nonBlockers.push(
13661
14400
  "Planning Artifact Manifest is not marked as ready_for_prepare."
@@ -13744,6 +14483,17 @@ function runPrdRunAuditCommand(options) {
13744
14483
  const prdRef = normalizePrdRunRef2(options.prdRef);
13745
14484
  return auditPrd047Implementation(options.repoRoot, prdRef);
13746
14485
  }
14486
+ async function closeParentGitHubIssue(prdRef, repoRoot2, issueProvider) {
14487
+ const prdResult = await resolveLocalPrdArtifact(prdRef, repoRoot2);
14488
+ const parentIssueNumber = prdResult.ok ? prdResult.data.githubProjection.issueNumber : null;
14489
+ if (!parentIssueNumber) return { closed: false, issueNumber: null };
14490
+ try {
14491
+ await issueProvider.closeIssue(parentIssueNumber);
14492
+ return { closed: true, issueNumber: parentIssueNumber };
14493
+ } catch {
14494
+ return { closed: false, issueNumber: parentIssueNumber };
14495
+ }
14496
+ }
13747
14497
  async function runPrdRunLaunchCommand(options) {
13748
14498
  const prdRef = normalizePrdRunRef2(options.prdRef);
13749
14499
  const attempted = [];
@@ -13755,10 +14505,11 @@ async function runPrdRunLaunchCommand(options) {
13755
14505
  const terminal = /* @__PURE__ */ new Set([
13756
14506
  "waiting_for_integration",
13757
14507
  "finalizing",
13758
- "complete"
14508
+ "complete",
14509
+ "completed_local_branch"
13759
14510
  ]);
13760
14511
  if (terminal.has(existingRecord.record.status)) {
13761
- return {
14512
+ const result = {
13762
14513
  prdRef,
13763
14514
  status: existingRecord.record.status,
13764
14515
  attempted: [],
@@ -13768,10 +14519,38 @@ async function runPrdRunLaunchCommand(options) {
13768
14519
  `PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`
13769
14520
  ]
13770
14521
  };
14522
+ if (existingRecord.record.status === "completed_local_branch" && existingRecord.record.prdBranch) {
14523
+ result.prdBranch = existingRecord.record.prdBranch;
14524
+ }
14525
+ return result;
13771
14526
  }
13772
14527
  }
13773
14528
  if (existingRecord.record) {
13774
14529
  const status = existingRecord.record.status;
14530
+ const resolvedLaunchMode = options.config ? (() => {
14531
+ try {
14532
+ const target = resolveTarget(options.config, options.targetName);
14533
+ return resolvePrdRunMode(target).mode;
14534
+ } catch {
14535
+ return void 0;
14536
+ }
14537
+ })() : void 0;
14538
+ if (resolvedLaunchMode && existingRecord.record.mode && existingRecord.record.mode !== resolvedLaunchMode) {
14539
+ return {
14540
+ prdRef,
14541
+ status: "blocked",
14542
+ attempted: [],
14543
+ skipped: ["prepare", "start", "queue", "final-review", "reconcile"],
14544
+ resumed: [],
14545
+ diagnostics: [
14546
+ `Recorded mode: ${existingRecord.record.mode}`,
14547
+ `Resolved mode: ${resolvedLaunchMode}`
14548
+ ],
14549
+ blockedGate: "branch-state",
14550
+ blockedReason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedLaunchMode}". Resolve by using the correct target or --local-override.`,
14551
+ offendingPaths: []
14552
+ };
14553
+ }
13775
14554
  if (status === "drained") {
13776
14555
  skipped.push("prepare", "start", "queue");
13777
14556
  resumed.push("final-review");
@@ -13899,182 +14678,216 @@ async function runPrdRunLaunchCommand(options) {
13899
14678
  }
13900
14679
  diagnostics.push(...startResult.diagnostics);
13901
14680
  attempted.push("queue");
14681
+ if (startResult.status === "drained" && options.issueProvider && existsSync17(join21(options.repoRoot, ".pourkit", "local-prd-runs", prdRef))) {
14682
+ await closeParentGitHubIssue(
14683
+ prdRef,
14684
+ options.repoRoot,
14685
+ options.issueProvider
14686
+ );
14687
+ }
14688
+ }
14689
+ let finalReviewResult;
14690
+ if (!skipped.includes("final-review")) {
14691
+ attempted.push("final-review");
14692
+ if (existingRecord.record?.mode === "local") {
14693
+ const runnableIssues = await getRunnableLocalIssues(
14694
+ prdRef,
14695
+ options.repoRoot
14696
+ );
14697
+ if (runnableIssues.length > 0) {
14698
+ const reason = `Config-local Final Review blocked: Queue not drained (${runnableIssues.length} runnable Issues remain).`;
14699
+ return {
14700
+ prdRef,
14701
+ status: "blocked",
14702
+ attempted,
14703
+ skipped: ["start", "queue", "reconcile"],
14704
+ resumed,
14705
+ diagnostics: [reason],
14706
+ blockedGate: "final-review",
14707
+ blockedReason: reason,
14708
+ offendingPaths: [],
14709
+ prepare: prepareResult,
14710
+ start: startResult,
14711
+ finalReview: {
14712
+ prdRef,
14713
+ status: "blocked",
14714
+ blockedGate: "final-review",
14715
+ blockedReason: reason,
14716
+ diagnostics: [reason],
14717
+ offendingPaths: []
14718
+ }
14719
+ };
14720
+ }
14721
+ }
14722
+ finalReviewResult = await runPrdRunFinalReviewCommand({
14723
+ repoRoot: options.repoRoot,
14724
+ prdRef,
14725
+ targetName: options.targetName,
14726
+ autoMerge: options.autoMerge,
14727
+ issueProvider: options.issueProvider,
14728
+ prProvider: options.prProvider,
14729
+ executionProvider: options.executionProvider,
14730
+ config: options.config,
14731
+ logger: options.logger
14732
+ });
14733
+ const skippedAfterFr = [
14734
+ ...skipped.includes("prepare") ? ["prepare"] : [],
14735
+ ...skipped.includes("start") ? ["queue"] : [],
14736
+ "reconcile"
14737
+ ];
14738
+ if (finalReviewResult.status === "blocked") {
14739
+ return {
14740
+ prdRef,
14741
+ status: "blocked",
14742
+ attempted,
14743
+ skipped: skippedAfterFr,
14744
+ resumed,
14745
+ diagnostics: finalReviewResult.diagnostics,
14746
+ blockedGate: "final-review",
14747
+ blockedReason: finalReviewResult.blockedReason,
14748
+ offendingPaths: finalReviewResult.offendingPaths,
14749
+ prepare: prepareResult,
14750
+ start: startResult,
14751
+ finalReview: finalReviewResult
14752
+ };
14753
+ }
14754
+ if (finalReviewResult.status === "needs_human_review") {
14755
+ return {
14756
+ prdRef,
14757
+ status: "blocked",
14758
+ attempted,
14759
+ skipped: skippedAfterFr,
14760
+ resumed,
14761
+ diagnostics: finalReviewResult.diagnostics,
14762
+ blockedGate: "final-review",
14763
+ blockedReason: finalReviewResult.finalReview.verdict ?? "needs_human_review",
14764
+ offendingPaths: [],
14765
+ prepare: prepareResult,
14766
+ start: startResult,
14767
+ finalReview: finalReviewResult
14768
+ };
14769
+ }
14770
+ diagnostics.push(...finalReviewResult.diagnostics);
13902
14771
  }
13903
- let finalReviewResult;
13904
- if (!skipped.includes("final-review")) {
13905
- attempted.push("final-review");
13906
- const localStorePath = join20(
14772
+ let reconcileResult;
14773
+ if (!skipped.includes("reconcile")) {
14774
+ attempted.push("reconcile");
14775
+ const localStorePath2 = join21(
13907
14776
  options.repoRoot,
13908
14777
  ".pourkit",
13909
14778
  "local-prd-runs",
13910
14779
  prdRef
13911
14780
  );
13912
- if (existsSync17(localStorePath)) {
13913
- const targetConfig = options.config?.targets?.find(
13914
- (t) => t.name === options.targetName
13915
- );
13916
- const verificationCommands = targetConfig?.strategy?.verify?.commands ?? [];
13917
- const localResult = await runLocalFinalReview(prdRef, {
13918
- repoRoot: options.repoRoot,
13919
- verificationCommands
13920
- });
13921
- if (!localResult.ok) {
13922
- const reason = localResult.message ?? "Local Final Review blocked.";
13923
- writePrdRunRecord(options.repoRoot, {
13924
- prdRef,
13925
- status: "blocked",
13926
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
13927
- blockedGate: "final-review",
13928
- blockedReason: reason,
13929
- diagnostics: [reason],
13930
- targetName: options.targetName,
13931
- offendingPaths: []
13932
- });
14781
+ if (existsSync17(localStorePath2)) {
14782
+ const prdRecord = readPrdRun(options.repoRoot, prdRef);
14783
+ const finalReviewReceipt = prdRecord.record?.finalReview;
14784
+ const hasCompletedFinalReview = finalReviewReceipt?.status === "final_reviewed" || finalReviewReceipt?.status === "succeeded";
14785
+ if (!hasCompletedFinalReview) {
14786
+ const reason = `Reconciliation blocked: Final Review not completed. Run Final Review before reconciliation.`;
13933
14787
  return {
13934
14788
  prdRef,
13935
14789
  status: "blocked",
13936
14790
  attempted,
13937
- skipped: [
13938
- ...skipped.includes("prepare") ? ["prepare"] : [],
13939
- ...skipped.includes("start") ? ["queue"] : [],
13940
- "reconcile"
13941
- ],
14791
+ skipped: [...skipped],
13942
14792
  resumed,
13943
14793
  diagnostics: [reason],
13944
- blockedGate: "final-review",
14794
+ blockedGate: "reconciliation",
13945
14795
  blockedReason: reason,
13946
14796
  offendingPaths: [],
13947
14797
  prepare: prepareResult,
13948
- start: startResult
14798
+ start: startResult,
14799
+ finalReview: finalReviewResult
13949
14800
  };
13950
14801
  }
13951
- if (localResult.verdict === "pass_with_retouch") {
13952
- try {
13953
- await squashFinalReviewRetouch(prdRef, options.repoRoot);
13954
- } catch (error) {
13955
- const reason = error instanceof Error ? error.message : String(error);
13956
- writePrdRunRecord(options.repoRoot, {
13957
- prdRef,
13958
- status: "blocked",
13959
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
13960
- blockedGate: "final-review",
13961
- blockedReason: reason,
13962
- diagnostics: [reason],
13963
- targetName: options.targetName,
13964
- offendingPaths: []
13965
- });
14802
+ if (prdRecord.record?.mode === "local") {
14803
+ const dualCheck = await verifyLocalDualFinalReviewReceipts(
14804
+ options.repoRoot,
14805
+ prdRef
14806
+ );
14807
+ if (!dualCheck.ok) {
13966
14808
  return {
13967
14809
  prdRef,
13968
14810
  status: "blocked",
13969
14811
  attempted,
13970
- skipped: [
13971
- ...skipped.includes("prepare") ? ["prepare"] : [],
13972
- ...skipped.includes("start") ? ["queue"] : [],
13973
- "reconcile"
13974
- ],
14812
+ skipped: [...skipped],
13975
14813
  resumed,
13976
- diagnostics: [reason],
13977
- blockedGate: "final-review",
13978
- blockedReason: reason,
14814
+ diagnostics: dualCheck.diagnostics,
14815
+ blockedGate: "reconciliation",
14816
+ blockedReason: dualCheck.reason,
13979
14817
  offendingPaths: [],
13980
14818
  prepare: prepareResult,
13981
- start: startResult
14819
+ start: startResult,
14820
+ finalReview: finalReviewResult
13982
14821
  };
13983
14822
  }
13984
14823
  }
13985
- const mappedVerdict = localResult.verdict === "pass" ? "pass_no_changes" : localResult.verdict;
14824
+ const localResult = await runLocalReconciliation(prdRef, {
14825
+ repoRoot: options.repoRoot
14826
+ });
14827
+ if (!localResult.ok) {
14828
+ return {
14829
+ prdRef,
14830
+ status: "blocked",
14831
+ attempted,
14832
+ skipped: [...skipped],
14833
+ resumed,
14834
+ diagnostics: localResult.message ? [localResult.message] : [],
14835
+ blockedGate: "reconciliation",
14836
+ blockedReason: localResult.message ?? "Local reconciliation blocked.",
14837
+ offendingPaths: [],
14838
+ prepare: prepareResult,
14839
+ start: startResult,
14840
+ finalReview: finalReviewResult
14841
+ };
14842
+ }
13986
14843
  const receipt = {
13987
- status: "final_reviewed",
14844
+ status: "succeeded",
14845
+ result: localResult.receipt?.result,
14846
+ changedPlanningPaths: localResult.receipt?.changedPlanningPaths,
13988
14847
  targetName: options.targetName,
13989
- prdBranch: `local/${prdRef}`,
13990
- diagnostics: [],
13991
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
13992
- verdict: mappedVerdict
14848
+ prdBranch: localResult.receipt?.prdBranch ?? `local/${prdRef}`,
14849
+ mergeCommit: localResult.receipt?.mergeCommit,
14850
+ reconciledAt: localResult.receipt?.reconciledTimestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
14851
+ autoMerge: false,
14852
+ agentIdentity: "pourkit-architect",
14853
+ noChange: localResult.receipt?.result === "no_changes_needed",
14854
+ source: "dev",
14855
+ target: localResult.receipt?.prdBranch ?? `local/${prdRef}`
13993
14856
  };
13994
14857
  const existingRecord2 = readPrdRun(options.repoRoot, prdRef);
13995
14858
  writePrdRunRecord(options.repoRoot, {
13996
14859
  prdRef,
13997
- status: "final_reviewed",
14860
+ status: "completed_local_branch",
13998
14861
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
13999
14862
  targetName: options.targetName,
14863
+ prdBranch: `local/${prdRef}`,
14000
14864
  manifestPath: existingRecord2.record?.manifestPath,
14001
14865
  planning: existingRecord2.record?.planning,
14002
14866
  start: existingRecord2.record?.start,
14003
- finalReview: receipt
14867
+ finalReview: existingRecord2.record?.finalReview,
14868
+ reconciliation: receipt,
14869
+ scopeChanges: existingRecord2.record?.scopeChanges
14004
14870
  });
14005
- finalReviewResult = {
14871
+ reconcileResult = {
14006
14872
  prdRef,
14007
- status: "final_reviewed",
14008
- finalReview: receipt,
14009
- diagnostics: localResult.message ? [localResult.message] : []
14873
+ status: "succeeded",
14874
+ reconciliation: receipt,
14875
+ diagnostics: []
14010
14876
  };
14011
- diagnostics.push(...localResult.message ? [localResult.message] : []);
14877
+ diagnostics.push("Local reconciliation completed.");
14012
14878
  } else {
14013
- finalReviewResult = await runPrdRunFinalReviewCommand({
14879
+ reconcileResult = await runPrdRunReconcileCommand({
14014
14880
  repoRoot: options.repoRoot,
14015
14881
  prdRef,
14016
14882
  targetName: options.targetName,
14017
- autoMerge: options.autoMerge,
14018
- issueProvider: options.issueProvider,
14019
- prProvider: options.prProvider,
14020
- executionProvider: options.executionProvider,
14021
14883
  config: options.config,
14022
- logger: options.logger
14884
+ logger: options.logger,
14885
+ executionProvider: options.executionProvider,
14886
+ prProvider: options.prProvider,
14887
+ autoMerge: options.autoMerge
14023
14888
  });
14024
14889
  }
14025
- const skippedAfterFr = [
14026
- ...skipped.includes("prepare") ? ["prepare"] : [],
14027
- ...skipped.includes("start") ? ["queue"] : [],
14028
- "reconcile"
14029
- ];
14030
- if (finalReviewResult.status === "blocked") {
14031
- return {
14032
- prdRef,
14033
- status: "blocked",
14034
- attempted,
14035
- skipped: skippedAfterFr,
14036
- resumed,
14037
- diagnostics: finalReviewResult.diagnostics,
14038
- blockedGate: "final-review",
14039
- blockedReason: finalReviewResult.blockedReason,
14040
- offendingPaths: finalReviewResult.offendingPaths,
14041
- prepare: prepareResult,
14042
- start: startResult,
14043
- finalReview: finalReviewResult
14044
- };
14045
- }
14046
- if (finalReviewResult.status === "needs_human_review") {
14047
- return {
14048
- prdRef,
14049
- status: "blocked",
14050
- attempted,
14051
- skipped: skippedAfterFr,
14052
- resumed,
14053
- diagnostics: finalReviewResult.diagnostics,
14054
- blockedGate: "final-review",
14055
- blockedReason: finalReviewResult.finalReview.verdict ?? "needs_human_review",
14056
- offendingPaths: [],
14057
- prepare: prepareResult,
14058
- start: startResult,
14059
- finalReview: finalReviewResult
14060
- };
14061
- }
14062
- diagnostics.push(...finalReviewResult.diagnostics);
14063
- }
14064
- let reconcileResult;
14065
- if (!skipped.includes("reconcile")) {
14066
- attempted.push("reconcile");
14067
- reconcileResult = await runPrdRunReconcileCommand({
14068
- repoRoot: options.repoRoot,
14069
- prdRef,
14070
- targetName: options.targetName,
14071
- config: options.config,
14072
- logger: options.logger,
14073
- executionProvider: options.executionProvider,
14074
- prProvider: options.prProvider,
14075
- autoMerge: options.autoMerge
14076
- });
14077
- if (reconcileResult.status === "blocked") {
14890
+ if (reconcileResult && reconcileResult.status === "blocked") {
14078
14891
  return {
14079
14892
  prdRef,
14080
14893
  status: "blocked",
@@ -14091,7 +14904,9 @@ async function runPrdRunLaunchCommand(options) {
14091
14904
  reconcile: reconcileResult
14092
14905
  };
14093
14906
  }
14094
- diagnostics.push(...reconcileResult.diagnostics);
14907
+ if (reconcileResult) {
14908
+ diagnostics.push(...reconcileResult.diagnostics);
14909
+ }
14095
14910
  }
14096
14911
  const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
14097
14912
  const reconciliationReceipt = reconcileResult && reconcileResult.status === "succeeded" ? reconcileResult.reconciliation : currentRecord?.reconciliation;
@@ -14174,6 +14989,29 @@ async function runPrdRunLaunchCommand(options) {
14174
14989
  reconcile: reconcileResult
14175
14990
  };
14176
14991
  }
14992
+ const localStorePath = join21(
14993
+ options.repoRoot,
14994
+ ".pourkit",
14995
+ "local-prd-runs",
14996
+ prdRef
14997
+ );
14998
+ if (existsSync17(localStorePath)) {
14999
+ return {
15000
+ prdRef,
15001
+ status: "completed_local_branch",
15002
+ prdBranch: `local/${prdRef}`,
15003
+ attempted,
15004
+ skipped,
15005
+ resumed,
15006
+ diagnostics: [
15007
+ `Local PRD Run ${prdRef} completed. Branch: local/${prdRef}.`
15008
+ ],
15009
+ prepare: prepareResult,
15010
+ start: startResult,
15011
+ finalReview: finalReviewResult,
15012
+ reconcile: reconcileResult
15013
+ };
15014
+ }
14177
15015
  writePrdRunRecord(options.repoRoot, {
14178
15016
  prdRef,
14179
15017
  status: "waiting_for_integration",
@@ -14503,7 +15341,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
14503
15341
  const branch = `local/${normalizedRef}`;
14504
15342
  const branchExists = (() => {
14505
15343
  try {
14506
- return spawnSync2("git", ["rev-parse", "--verify", "--quiet", branch], {
15344
+ return spawnSync3("git", ["rev-parse", "--verify", "--quiet", branch], {
14507
15345
  cwd: root,
14508
15346
  encoding: "utf8",
14509
15347
  stdio: "pipe"
@@ -14523,7 +15361,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
14523
15361
  message: `Branch ${branch} already exists without a matching start receipt. Remove it manually or use a different PRD ID.`
14524
15362
  };
14525
15363
  }
14526
- const branchResult = spawnSync2("git", ["branch", branch], {
15364
+ const branchResult = spawnSync3("git", ["branch", branch], {
14527
15365
  cwd: root,
14528
15366
  encoding: "utf8",
14529
15367
  stdio: "pipe"
@@ -14538,7 +15376,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
14538
15376
  }
14539
15377
  const baseCommit = (() => {
14540
15378
  try {
14541
- return spawnSync2("git", ["rev-parse", "HEAD"], {
15379
+ return spawnSync3("git", ["rev-parse", "HEAD"], {
14542
15380
  cwd: root,
14543
15381
  encoding: "utf8",
14544
15382
  stdio: "pipe"
@@ -14660,6 +15498,17 @@ async function runLocalLaunchCommand(prdId, repoRoot2) {
14660
15498
  };
14661
15499
  return { ok: false, failedStage: "finalReview", stages };
14662
15500
  }
15501
+ if (result.verdict === "blocked" || result.verdict === "needs_human_review") {
15502
+ stages.finalReview = {
15503
+ ok: false,
15504
+ stage: "finalReview",
15505
+ failureCode: result.verdict === "blocked" ? "final_review_blocked" : "needs_human_review",
15506
+ repairabilityClass: "manual",
15507
+ reason: result.message ?? `Final Review verdict: ${result.verdict}`,
15508
+ repairGuidance: `Fix final review failures and rerun \`pourkit prd-run launch --local ${normalizedRef}\`.`
15509
+ };
15510
+ return { ok: false, failedStage: "finalReview", stages };
15511
+ }
14663
15512
  if (result.verdict === "pass_with_retouch") {
14664
15513
  try {
14665
15514
  await squashFinalReviewRetouch(normalizedRef, root);
@@ -14779,6 +15628,32 @@ async function runPrdRunStartCommand(options) {
14779
15628
  );
14780
15629
  }
14781
15630
  const existingRecord = readPrdRun(options.repoRoot, prdRef);
15631
+ const resolvedMode = options.config ? (() => {
15632
+ try {
15633
+ const target = resolveTarget(options.config, targetName);
15634
+ return resolvePrdRunMode(target).mode;
15635
+ } catch {
15636
+ return void 0;
15637
+ }
15638
+ })() : void 0;
15639
+ if (resolvedMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedMode) {
15640
+ const failure = {
15641
+ ok: false,
15642
+ gate: "branch-state",
15643
+ reason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedMode}". Resolve by using the correct target or --local-override.`,
15644
+ diagnostics: [
15645
+ `Recorded mode: ${existingRecord.record.mode}`,
15646
+ `Resolved mode: ${resolvedMode}`
15647
+ ],
15648
+ offendingPaths: []
15649
+ };
15650
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
15651
+ manifestPath: existingRecord.record?.manifestPath,
15652
+ planning: existingRecord.record?.planning,
15653
+ targetName
15654
+ });
15655
+ return buildBlockedStartResult(prdRef, failure);
15656
+ }
14782
15657
  if (existingRecord.record?.start) {
14783
15658
  const fetchResult2 = fetchOriginDev(options.repoRoot);
14784
15659
  if (!fetchResult2.ok) {
@@ -14814,7 +15689,8 @@ async function runPrdRunStartCommand(options) {
14814
15689
  ...reusedStart
14815
15690
  },
14816
15691
  {
14817
- targetName
15692
+ targetName,
15693
+ mode: resolvedMode
14818
15694
  }
14819
15695
  );
14820
15696
  return await processStartResult(
@@ -14917,6 +15793,7 @@ async function runPrdRunStartCommand(options) {
14917
15793
  },
14918
15794
  {
14919
15795
  targetName,
15796
+ mode: resolvedMode,
14920
15797
  manifestPath: existingRecord.record?.manifestPath,
14921
15798
  planning: existingRecord.record?.planning
14922
15799
  }
@@ -15076,6 +15953,7 @@ async function runPrdRunStartCommand(options) {
15076
15953
  },
15077
15954
  {
15078
15955
  targetName,
15956
+ mode: resolvedMode,
15079
15957
  manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
15080
15958
  planning: existingRecord.record?.planning
15081
15959
  }
@@ -15158,6 +16036,68 @@ async function runPrdRunStartCommand(options) {
15158
16036
  });
15159
16037
  return buildBlockedStartResult(prdRef, failure);
15160
16038
  }
16039
+ if (resolvedMode === "local") {
16040
+ const localBranchResult = materializeLocalPrdBranch(
16041
+ prdRef,
16042
+ fetchResult.startBaseCommit,
16043
+ options.repoRoot
16044
+ );
16045
+ if (!localBranchResult.ok) {
16046
+ const failure = {
16047
+ ok: false,
16048
+ gate: "branch-state",
16049
+ reason: localBranchResult.message,
16050
+ diagnostics: [localBranchResult.message],
16051
+ offendingPaths: []
16052
+ };
16053
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
16054
+ manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
16055
+ planning: existingRecord.record?.planning,
16056
+ targetName
16057
+ });
16058
+ return buildBlockedStartResult(prdRef, failure);
16059
+ }
16060
+ const localStoreDir = join21(
16061
+ options.repoRoot,
16062
+ ".pourkit",
16063
+ "local-prd-runs",
16064
+ prdRef
16065
+ );
16066
+ let localStoreReady = false;
16067
+ if (existsSync17(localStoreDir)) {
16068
+ const localStorePath = join21(localStoreDir, "prd.json");
16069
+ try {
16070
+ const content = JSON.parse(readFileSync17(localStorePath, "utf8"));
16071
+ localStoreReady = content?.id === prdRef && content?.kind === "prd";
16072
+ } catch {
16073
+ localStoreReady = false;
16074
+ }
16075
+ }
16076
+ const prereqs = {
16077
+ localBranchName: getLocalPrdBranchName(prdRef),
16078
+ localBranchExists: !localBranchResult.created,
16079
+ localBranchCommit: fetchResult.startBaseCommit,
16080
+ localStoreReady
16081
+ };
16082
+ if (existsSync17(localStoreDir) && !prereqs.localStoreReady) {
16083
+ const failure = {
16084
+ ok: false,
16085
+ gate: "branch-state",
16086
+ reason: `Local PRD Run Store not ready for ${prdRef}. Expected valid prd.json at .pourkit/local-prd-runs/${prdRef}/prd.json with matching PRD ID. Ensure Local PRD Run Store is initialized before starting.`,
16087
+ diagnostics: [
16088
+ `Expected store path: .pourkit/local-prd-runs/${prdRef}/prd.json`,
16089
+ `Expected PRD ID: ${prdRef}`
16090
+ ],
16091
+ offendingPaths: []
16092
+ };
16093
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
16094
+ manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
16095
+ planning: existingRecord.record?.planning,
16096
+ targetName
16097
+ });
16098
+ return buildBlockedStartResult(prdRef, failure);
16099
+ }
16100
+ }
15161
16101
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
15162
16102
  const start = {
15163
16103
  status: "started",
@@ -15185,6 +16125,7 @@ async function runPrdRunStartCommand(options) {
15185
16125
  updatedAt,
15186
16126
  targetName,
15187
16127
  start,
16128
+ mode: resolvedMode,
15188
16129
  manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath ?? void 0,
15189
16130
  planning: existingRecord.record?.planning
15190
16131
  });
@@ -15215,26 +16156,93 @@ async function processStartResult(startResult, options) {
15215
16156
  } catch {
15216
16157
  resolvedMode = void 0;
15217
16158
  }
16159
+ const modeForRecord = resolvedMode?.mode;
15218
16160
  writePrdRunRecord(repoRoot2, {
15219
16161
  prdRef,
15220
16162
  status: "running",
15221
16163
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15222
16164
  targetName: start.targetName,
15223
16165
  start,
16166
+ mode: modeForRecord,
15224
16167
  manifestPath: existingRecord.record?.manifestPath,
15225
16168
  planning: existingRecord.record?.planning
15226
16169
  });
15227
- const localStorePath = join20(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
16170
+ const localStorePath = join21(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
15228
16171
  if (existsSync17(localStorePath)) {
15229
- const localIssues = await getRunnableLocalIssues(prdRef, repoRoot2);
16172
+ const queueResult = await runLocalQueueLoop(
16173
+ prdRef,
16174
+ repoRoot2,
16175
+ options.issueProvider
16176
+ );
16177
+ if (!queueResult.ok) {
16178
+ const failureCode = queueResult.failureCode ?? "queue_error";
16179
+ const reason = queueResult.repairGuidance ?? `Local queue loop blocked: ${failureCode}`;
16180
+ const diagnostics = [
16181
+ `Local queue loop failed with failureCode "${failureCode}".`,
16182
+ ...queueResult.repairGuidance ? [`Repair guidance: ${queueResult.repairGuidance}`] : []
16183
+ ];
16184
+ writePrdRunRecord(repoRoot2, {
16185
+ prdRef,
16186
+ status: "blocked",
16187
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
16188
+ targetName: start.targetName,
16189
+ start,
16190
+ mode: modeForRecord,
16191
+ manifestPath: existingRecord.record?.manifestPath,
16192
+ planning: existingRecord.record?.planning,
16193
+ blockedGate: "queue",
16194
+ blockedReason: reason,
16195
+ diagnostics,
16196
+ offendingPaths: []
16197
+ });
16198
+ return {
16199
+ prdRef,
16200
+ status: "blocked",
16201
+ blockedGate: "queue",
16202
+ blockedReason: reason,
16203
+ diagnostics,
16204
+ offendingPaths: []
16205
+ };
16206
+ }
16207
+ if (queueResult.blockedIssues.length > 0) {
16208
+ const reason = `Queue processed complete but ${queueResult.blockedIssues.length} blocked child issue(s) remain.`;
16209
+ const diagnostics = [
16210
+ reason,
16211
+ `Blocked issues: ${queueResult.blockedIssues.join(", ")}`,
16212
+ "Resolve blocked children before parent close."
16213
+ ];
16214
+ writePrdRunRecord(repoRoot2, {
16215
+ prdRef,
16216
+ status: "blocked",
16217
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
16218
+ targetName: start.targetName,
16219
+ start,
16220
+ mode: modeForRecord,
16221
+ manifestPath: existingRecord.record?.manifestPath,
16222
+ planning: existingRecord.record?.planning,
16223
+ blockedGate: "queue",
16224
+ blockedReason: reason,
16225
+ diagnostics,
16226
+ offendingPaths: []
16227
+ });
16228
+ return {
16229
+ prdRef,
16230
+ status: "blocked",
16231
+ blockedGate: "queue",
16232
+ blockedReason: reason,
16233
+ diagnostics,
16234
+ offendingPaths: []
16235
+ };
16236
+ }
15230
16237
  start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
15231
- start.queueProcessedCount = localIssues.length;
16238
+ start.queueProcessedCount = queueResult.completedIssues.length + queueResult.blockedIssues.length;
15232
16239
  writePrdRunRecord(repoRoot2, {
15233
16240
  prdRef,
15234
16241
  status: "drained",
15235
16242
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15236
16243
  targetName: start.targetName,
15237
16244
  start,
16245
+ mode: modeForRecord,
15238
16246
  manifestPath: existingRecord.record?.manifestPath,
15239
16247
  planning: existingRecord.record?.planning
15240
16248
  });
@@ -15277,6 +16285,7 @@ async function processStartResult(startResult, options) {
15277
16285
  diagnostics,
15278
16286
  targetName: start.targetName,
15279
16287
  start,
16288
+ mode: existingRecord.record?.mode,
15280
16289
  manifestPath: existingRecord.record?.manifestPath,
15281
16290
  planning: existingRecord.record?.planning,
15282
16291
  offendingPaths: []
@@ -15298,6 +16307,7 @@ async function processStartResult(startResult, options) {
15298
16307
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15299
16308
  targetName: start.targetName,
15300
16309
  start,
16310
+ mode: existingRecord.record?.mode,
15301
16311
  manifestPath: existingRecord.record?.manifestPath,
15302
16312
  planning: existingRecord.record?.planning
15303
16313
  });
@@ -15319,6 +16329,7 @@ async function processStartResult(startResult, options) {
15319
16329
  diagnostics,
15320
16330
  targetName: start.targetName,
15321
16331
  start,
16332
+ mode: existingRecord.record?.mode,
15322
16333
  manifestPath: existingRecord.record?.manifestPath,
15323
16334
  planning: existingRecord.record?.planning,
15324
16335
  offendingPaths: []
@@ -15343,6 +16354,7 @@ async function processStartResult(startResult, options) {
15343
16354
  diagnostics: outcomeDiagnostics,
15344
16355
  targetName: start.targetName,
15345
16356
  start,
16357
+ mode: existingRecord.record?.mode,
15346
16358
  manifestPath: existingRecord.record?.manifestPath,
15347
16359
  planning: existingRecord.record?.planning,
15348
16360
  offendingPaths: []
@@ -15367,6 +16379,7 @@ async function processStartResult(startResult, options) {
15367
16379
  diagnostics,
15368
16380
  targetName: start.targetName,
15369
16381
  start,
16382
+ mode: existingRecord.record?.mode,
15370
16383
  manifestPath: existingRecord.record?.manifestPath,
15371
16384
  planning: existingRecord.record?.planning,
15372
16385
  offendingPaths: []
@@ -15398,12 +16411,13 @@ function persistStartingPrdRunRecord(repoRoot2, prdRef, existingRecord, start, c
15398
16411
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15399
16412
  targetName: context.targetName,
15400
16413
  start,
16414
+ mode: context.mode,
15401
16415
  manifestPath: context.manifestPath ?? existingRecord.record?.manifestPath,
15402
16416
  planning: context.planning ?? existingRecord.record?.planning
15403
16417
  });
15404
16418
  }
15405
16419
  function inspectRemotePrdBranch(repoRoot2, prdRef) {
15406
- const result = spawnSync2("git", ["ls-remote", "--heads", "origin", prdRef], {
16420
+ const result = spawnSync3("git", ["ls-remote", "--heads", "origin", prdRef], {
15407
16421
  cwd: repoRoot2,
15408
16422
  encoding: "utf8"
15409
16423
  });
@@ -15428,7 +16442,7 @@ function inspectRemotePrdBranch(repoRoot2, prdRef) {
15428
16442
  }
15429
16443
  function ensureAdoptableExistingPrdBranch(repoRoot2, prdRef, prepareMergeCommit) {
15430
16444
  const branchRef = `refs/remotes/origin/${prdRef}`;
15431
- const result = spawnSync2(
16445
+ const result = spawnSync3(
15432
16446
  "git",
15433
16447
  ["merge-base", "--is-ancestor", prepareMergeCommit, branchRef],
15434
16448
  {
@@ -15558,7 +16572,7 @@ async function createOrReusePlanningPr(options) {
15558
16572
  });
15559
16573
  }
15560
16574
  function fetchOriginDev(repoRoot2) {
15561
- const fetchResult = spawnSync2("git", ["fetch", "origin", "dev"], {
16575
+ const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
15562
16576
  cwd: repoRoot2,
15563
16577
  encoding: "utf8"
15564
16578
  });
@@ -15575,7 +16589,7 @@ function fetchOriginDev(repoRoot2) {
15575
16589
  offendingPaths: []
15576
16590
  };
15577
16591
  }
15578
- const revParseResult = spawnSync2("git", ["rev-parse", "origin/dev"], {
16592
+ const revParseResult = spawnSync3("git", ["rev-parse", "origin/dev"], {
15579
16593
  cwd: repoRoot2,
15580
16594
  encoding: "utf8"
15581
16595
  });
@@ -15595,7 +16609,7 @@ function fetchOriginDev(repoRoot2) {
15595
16609
  return { ok: true, startBaseCommit: revParseResult.stdout.trim() };
15596
16610
  }
15597
16611
  function fetchPrdBranch(repoRoot2, prdRef) {
15598
- const result = spawnSync2("git", ["fetch", "origin", prdRef], {
16612
+ const result = spawnSync3("git", ["fetch", "origin", prdRef], {
15599
16613
  cwd: repoRoot2,
15600
16614
  encoding: "utf8"
15601
16615
  });
@@ -15615,7 +16629,7 @@ function fetchPrdBranch(repoRoot2, prdRef) {
15615
16629
  return { ok: true };
15616
16630
  }
15617
16631
  function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startBaseCommit) {
15618
- const result = spawnSync2(
16632
+ const result = spawnSync3(
15619
16633
  "git",
15620
16634
  ["merge-base", "--is-ancestor", prepareMergeCommit, startBaseRef],
15621
16635
  {
@@ -15642,7 +16656,7 @@ function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startB
15642
16656
  return { ok: true };
15643
16657
  }
15644
16658
  async function ensurePrdBranchPublished(repoRoot2, prdRef, startBaseCommit) {
15645
- const pushResult = spawnSync2(
16659
+ const pushResult = spawnSync3(
15646
16660
  "git",
15647
16661
  ["push", "origin", `${startBaseCommit}:refs/heads/${prdRef}`],
15648
16662
  {
@@ -15695,7 +16709,7 @@ function buildPlanningPrBody(options) {
15695
16709
  ].join("\n");
15696
16710
  }
15697
16711
  function runGitOrThrow(cwd, args, label) {
15698
- const result = spawnSync2("git", args, { cwd, encoding: "utf8" });
16712
+ const result = spawnSync3("git", args, { cwd, encoding: "utf8" });
15699
16713
  if (result.status !== 0) {
15700
16714
  throw new Error(
15701
16715
  `Failed to ${label}: ${[
@@ -15707,7 +16721,7 @@ function runGitOrThrow(cwd, args, label) {
15707
16721
  }
15708
16722
  }
15709
16723
  function validatePrepareAutoMergeDevCheckout(repoRoot2) {
15710
- const branchResult = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
16724
+ const branchResult = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
15711
16725
  cwd: repoRoot2,
15712
16726
  encoding: "utf8"
15713
16727
  });
@@ -15738,7 +16752,7 @@ function validatePrepareAutoMergeDevCheckout(repoRoot2) {
15738
16752
  function syncLocalDevToOriginDev(repoRoot2) {
15739
16753
  const branchResult = validatePrepareAutoMergeDevCheckout(repoRoot2);
15740
16754
  if (!branchResult.ok) return branchResult;
15741
- const fetchResult = spawnSync2("git", ["fetch", "origin", "dev"], {
16755
+ const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
15742
16756
  cwd: repoRoot2,
15743
16757
  encoding: "utf8"
15744
16758
  });
@@ -15754,7 +16768,7 @@ function syncLocalDevToOriginDev(repoRoot2) {
15754
16768
  offendingPaths: []
15755
16769
  };
15756
16770
  }
15757
- const resetResult = spawnSync2("git", ["reset", "--hard", "origin/dev"], {
16771
+ const resetResult = spawnSync3("git", ["reset", "--hard", "origin/dev"], {
15758
16772
  cwd: repoRoot2,
15759
16773
  encoding: "utf8"
15760
16774
  });
@@ -15798,14 +16812,14 @@ function isArchitecturePlanningDiffPath(path9) {
15798
16812
  return path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/");
15799
16813
  }
15800
16814
  async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles) {
15801
- const remoteResult = spawnSync2("git", ["remote", "get-url", "origin"], {
16815
+ const remoteResult = spawnSync3("git", ["remote", "get-url", "origin"], {
15802
16816
  cwd: repoRoot2,
15803
16817
  encoding: "utf8"
15804
16818
  });
15805
16819
  if (remoteResult.status !== 0) {
15806
16820
  return;
15807
16821
  }
15808
- const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-planning-"));
16822
+ const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-planning-"));
15809
16823
  try {
15810
16824
  runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
15811
16825
  runGitOrThrow(
@@ -15819,8 +16833,8 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
15819
16833
  "create planning branch"
15820
16834
  );
15821
16835
  for (const changedFile of changedFiles) {
15822
- const sourcePath = join20(repoRoot2, changedFile);
15823
- const targetPath = join20(worktreePath, changedFile);
16836
+ const sourcePath = join21(repoRoot2, changedFile);
16837
+ const targetPath = join21(worktreePath, changedFile);
15824
16838
  mkdirSync11(dirname5(targetPath), { recursive: true });
15825
16839
  cpSync(sourcePath, targetPath, { recursive: true });
15826
16840
  }
@@ -15835,13 +16849,13 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
15835
16849
  "commit planning diff"
15836
16850
  );
15837
16851
  } finally {
15838
- spawnSync2("git", ["worktree", "remove", "--force", worktreePath], {
16852
+ spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
15839
16853
  cwd: repoRoot2,
15840
16854
  encoding: "utf8"
15841
16855
  });
15842
16856
  rmSync3(worktreePath, { recursive: true, force: true });
15843
16857
  }
15844
- const pushResult = spawnSync2(
16858
+ const pushResult = spawnSync3(
15845
16859
  "git",
15846
16860
  ["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
15847
16861
  {
@@ -15977,7 +16991,7 @@ function validateStartPrepareReceipt(prdRef, record, options = {}) {
15977
16991
  function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
15978
16992
  for (const artifactPath of listManifestArtifactPaths(repoRoot2, manifest)) {
15979
16993
  const repoRelativePath = toRepoRelativePath2(repoRoot2, artifactPath);
15980
- const result = spawnSync2(
16994
+ const result = spawnSync3(
15981
16995
  "git",
15982
16996
  ["show", `origin/dev:${repoRelativePath}`],
15983
16997
  {
@@ -16003,7 +17017,7 @@ function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
16003
17017
  return { ok: true };
16004
17018
  }
16005
17019
  function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
16006
- const result = spawnSync2("git", ["status", "--porcelain=v1", "-uall"], {
17020
+ const result = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
16007
17021
  cwd: repoRoot2,
16008
17022
  encoding: "utf8"
16009
17023
  });
@@ -16058,11 +17072,11 @@ function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
16058
17072
  };
16059
17073
  }
16060
17074
  function collectObservedReconciliationDirtyPaths(options) {
16061
- const statusResult = spawnSync2("git", ["status", "--porcelain=v1", "-uall"], {
17075
+ const statusResult = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
16062
17076
  cwd: options.worktreeCwd,
16063
17077
  encoding: "utf8"
16064
17078
  });
16065
- const diffResult = spawnSync2("git", ["diff", "--name-status"], {
17079
+ const diffResult = spawnSync3("git", ["diff", "--name-status"], {
16066
17080
  cwd: options.worktreeCwd,
16067
17081
  encoding: "utf8"
16068
17082
  });
@@ -16096,13 +17110,13 @@ function collectObservedReconciliationDirtyPaths(options) {
16096
17110
  };
16097
17111
  }
16098
17112
  function gitRefExists(repoRoot2, ref) {
16099
- return spawnSync2("git", ["rev-parse", "--verify", "--quiet", ref], {
17113
+ return spawnSync3("git", ["rev-parse", "--verify", "--quiet", ref], {
16100
17114
  cwd: repoRoot2,
16101
17115
  encoding: "utf8"
16102
17116
  }).status === 0;
16103
17117
  }
16104
17118
  function collectGitDiffNames(repoRoot2, baseRef, headRef) {
16105
- const result = spawnSync2("git", ["diff", "--name-only", baseRef, headRef], {
17119
+ const result = spawnSync3("git", ["diff", "--name-only", baseRef, headRef], {
16106
17120
  cwd: repoRoot2,
16107
17121
  encoding: "utf8"
16108
17122
  });
@@ -19163,8 +20177,8 @@ function formatChecks2(checks) {
19163
20177
  init_common();
19164
20178
 
19165
20179
  // execution/sandcastle-execution.ts
19166
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync8 } from "fs";
19167
- import { join as join22 } from "path";
20180
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync9 } from "fs";
20181
+ import { join as join23 } from "path";
19168
20182
  import { createWorktree, opencode } from "@ai-hero/sandcastle";
19169
20183
  import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
19170
20184
 
@@ -19173,10 +20187,10 @@ init_common();
19173
20187
  import { mkdtempSync as mkdtempSync2 } from "fs";
19174
20188
  import { writeFile as writeFile3 } from "fs/promises";
19175
20189
  import { tmpdir as tmpdir2 } from "os";
19176
- import { dirname as dirname6, join as join21 } from "path";
20190
+ import { dirname as dirname6, join as join22 } from "path";
19177
20191
  async function writeExecutionArtifacts(worktreePath, artifacts) {
19178
20192
  for (const artifact of artifacts) {
19179
- const filePath = join21(worktreePath, artifact.path);
20193
+ const filePath = join22(worktreePath, artifact.path);
19180
20194
  await ensureDir(dirname6(filePath));
19181
20195
  await writeFile3(filePath, artifact.content, "utf-8");
19182
20196
  }
@@ -19188,7 +20202,7 @@ import path7 from "path";
19188
20202
 
19189
20203
  // execution/sandbox-image.ts
19190
20204
  import { createHash as createHash4 } from "crypto";
19191
- import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
20205
+ import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
19192
20206
  import path6 from "path";
19193
20207
  function sandboxImageName(repoRoot2) {
19194
20208
  const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
@@ -19198,7 +20212,7 @@ function sandboxImageName(repoRoot2) {
19198
20212
  if (!existsSync19(dockerfilePath)) {
19199
20213
  return `sandcastle:${baseName}`;
19200
20214
  }
19201
- const fingerprint = createHash4("sha256").update(readFileSync17(dockerfilePath)).digest("hex").slice(0, 8);
20215
+ const fingerprint = createHash4("sha256").update(readFileSync18(dockerfilePath)).digest("hex").slice(0, 8);
19202
20216
  return `sandcastle:${baseName}-${fingerprint}`;
19203
20217
  }
19204
20218
 
@@ -19458,17 +20472,32 @@ function parseToolCallArgs(formattedArgs) {
19458
20472
  return { ok: false };
19459
20473
  }
19460
20474
  }
20475
+ var SUMMARIZED_TOOL_FIELDS = {
20476
+ write: { content: "contentCount" },
20477
+ edit: { oldString: "oldCount", newString: "newCount" }
20478
+ };
20479
+ var SUMMARIZED_TOOL_ARRAY_FIELDS = {
20480
+ todowrite: { todos: "todoCount" }
20481
+ };
19461
20482
  function summarizeToolCallArgs(name, args) {
19462
20483
  if (!isPlainObject(args)) {
19463
20484
  return args;
19464
20485
  }
19465
- if (name.toLowerCase() !== "write") {
20486
+ const fields = SUMMARIZED_TOOL_FIELDS[name.toLowerCase()];
20487
+ const arrayFields = SUMMARIZED_TOOL_ARRAY_FIELDS[name.toLowerCase()];
20488
+ if (!fields && !arrayFields) {
19466
20489
  return args;
19467
20490
  }
19468
20491
  const summarizedArgs = {};
19469
20492
  for (const [key, value] of Object.entries(args)) {
19470
- if (key === "content" && typeof value === "string") {
19471
- summarizedArgs.contentCount = value.length;
20493
+ const mappedKey = fields?.[key];
20494
+ if (mappedKey && typeof value === "string") {
20495
+ summarizedArgs[mappedKey] = value.length;
20496
+ continue;
20497
+ }
20498
+ const mappedArrayKey = arrayFields?.[key];
20499
+ if (mappedArrayKey && Array.isArray(value)) {
20500
+ summarizedArgs[mappedArrayKey] = value.length;
19472
20501
  continue;
19473
20502
  }
19474
20503
  summarizedArgs[key] = value;
@@ -19479,13 +20508,13 @@ function isPlainObject(value) {
19479
20508
  return typeof value === "object" && value !== null && !Array.isArray(value);
19480
20509
  }
19481
20510
  function savePromptToFile(repoRoot2, stage, iteration, prompt) {
19482
- const promptsDir = join22(repoRoot2, ".pourkit", ".tmp", "prompts");
20511
+ const promptsDir = join23(repoRoot2, ".pourkit", ".tmp", "prompts");
19483
20512
  mkdirSync12(promptsDir, { recursive: true });
19484
20513
  const timestamp2 = Date.now();
19485
20514
  const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
19486
20515
  const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
19487
- const filePath = join22(promptsDir, filename);
19488
- writeFileSync8(filePath, prompt, "utf-8");
20516
+ const filePath = join23(promptsDir, filename);
20517
+ writeFileSync9(filePath, prompt, "utf-8");
19489
20518
  }
19490
20519
 
19491
20520
  // cli.ts
@@ -20358,11 +21387,11 @@ function createCliProgram(version) {
20358
21387
  return program;
20359
21388
  }
20360
21389
  async function resolveCliVersion() {
20361
- if (isPackageVersion("0.0.0-next-20260608072322")) {
20362
- return "0.0.0-next-20260608072322";
21390
+ if (isPackageVersion("0.0.0-next-20260609130908")) {
21391
+ return "0.0.0-next-20260609130908";
20363
21392
  }
20364
- if (isReleaseVersion("0.0.0-next-20260608072322")) {
20365
- return "0.0.0-next-20260608072322";
21393
+ if (isReleaseVersion("0.0.0-next-20260609130908")) {
21394
+ return "0.0.0-next-20260609130908";
20366
21395
  }
20367
21396
  try {
20368
21397
  const root = repoRoot();