@pourkit/cli 0.0.0-next-20260608221925 → 0.0.0-next-20260609213420

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
@@ -1354,13 +1354,7 @@ function buildRunContextMarkdown(options) {
1354
1354
  );
1355
1355
  }
1356
1356
  if (sections.includes("verification-commands")) {
1357
- parts.push(
1358
- ...renderCommandList(
1359
- target,
1360
- getVerificationCommands(target),
1361
- "Verification Commands"
1362
- )
1363
- );
1357
+ parts.push(...renderCommandList(target, "Verification Commands"));
1364
1358
  }
1365
1359
  if (sections.includes("review-criteria")) {
1366
1360
  parts.push(...renderCriteria(reviewerCriteria));
@@ -1378,29 +1372,11 @@ function buildRunContextMarkdown(options) {
1378
1372
  }
1379
1373
  return parts.join("\n");
1380
1374
  }
1381
- function renderCommandList(target, commands, heading) {
1382
- if (commands.length === 0) {
1383
- return [
1384
- `## ${heading}`,
1385
- "",
1386
- `Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
1387
- "",
1388
- "Underlying project commands:",
1389
- "",
1390
- "(none configured)",
1391
- ""
1392
- ];
1393
- }
1375
+ function renderCommandList(target, heading) {
1394
1376
  return [
1395
1377
  `## ${heading}`,
1396
1378
  "",
1397
1379
  `Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
1398
- "",
1399
- "Underlying project commands:",
1400
- "",
1401
- "Run these commands from the repository root exactly as written. Do not substitute equivalent scripts from nested package.json files.",
1402
- "",
1403
- ...commands.map((command) => `- ${command.label}: \`${command.command}\``),
1404
1380
  ""
1405
1381
  ];
1406
1382
  }
@@ -7424,6 +7400,27 @@ async function completeIssueRun(options) {
7424
7400
  issueNumber,
7425
7401
  config.labels.agentInProgress
7426
7402
  );
7403
+ await issueProvider.removeLabel(
7404
+ issueNumber,
7405
+ config.labels.prOpenAwaitingMerge
7406
+ );
7407
+ let childCloseSucceeded2 = false;
7408
+ try {
7409
+ await issueProvider.closeIssue(issueNumber);
7410
+ childCloseSucceeded2 = true;
7411
+ } catch (error) {
7412
+ logger.step(
7413
+ "warn",
7414
+ `Issue #${issueNumber} could not be closed: ${error instanceof Error ? error.message : String(error)}`
7415
+ );
7416
+ }
7417
+ if (childCloseSucceeded2) {
7418
+ await maybeCloseParentAfterChildCompletion(
7419
+ issueProvider,
7420
+ issueNumber,
7421
+ logger
7422
+ );
7423
+ }
7427
7424
  return {
7428
7425
  branchName,
7429
7426
  target,
@@ -8597,7 +8594,20 @@ var LocalPrdRunRecordSchema = z3.object({
8597
8594
  baseCommit: z3.string().min(1)
8598
8595
  }).optional(),
8599
8596
  queue: z3.object({ completedAt: z3.string().min(1) }).optional(),
8600
- finalReview: z3.object({ completedAt: z3.string().min(1) }).optional(),
8597
+ finalReview: z3.object({
8598
+ completedAt: z3.string().min(1),
8599
+ targetName: z3.string().optional(),
8600
+ prdBranch: z3.string().optional(),
8601
+ mergeBase: z3.string().optional(),
8602
+ verdict: z3.enum([
8603
+ "pass_no_changes",
8604
+ "pass_with_retouch",
8605
+ "needs_human_review",
8606
+ "blocked"
8607
+ ]).optional(),
8608
+ diagnostics: z3.array(z3.string()).optional(),
8609
+ artifactPath: z3.string().optional()
8610
+ }).optional(),
8601
8611
  reconciliation: z3.object({ completedAt: z3.string().min(1) }).optional(),
8602
8612
  complete: z3.object({
8603
8613
  completedAt: z3.string().min(1),
@@ -9278,7 +9288,7 @@ async function runLocalFinalReview(prdId, options) {
9278
9288
  }
9279
9289
  let verdict;
9280
9290
  if (commands.length === 0 || failures.length === 0) {
9281
- const retouchBranch = `local/${prdId}/retouch`;
9291
+ const retouchBranch = `local-${prdId}-final-review-retouch`;
9282
9292
  try {
9283
9293
  execSync2(`git show-ref --verify --quiet refs/heads/${retouchBranch}`, {
9284
9294
  cwd: root,
@@ -9292,7 +9302,7 @@ async function runLocalFinalReview(prdId, options) {
9292
9302
  } else if (failures.length === commands.length) {
9293
9303
  verdict = "blocked";
9294
9304
  } else {
9295
- const retouchBranch = `local/${prdId}/retouch`;
9305
+ const retouchBranch = `local-${prdId}-final-review-retouch`;
9296
9306
  try {
9297
9307
  execSync2(`git show-ref --verify --quiet refs/heads/${retouchBranch}`, {
9298
9308
  cwd: root,
@@ -9321,10 +9331,10 @@ async function runLocalFinalReview(prdId, options) {
9321
9331
  }
9322
9332
  return result;
9323
9333
  }
9324
- async function squashFinalReviewRetouch(prdId, repoRoot2) {
9334
+ async function squashFinalReviewRetouch(prdId, repoRoot2, title, body) {
9325
9335
  const root = repoRoot2 ?? process.cwd();
9326
9336
  const targetBranch = `local/${prdId}`;
9327
- const retouchBranch = `local/${prdId}/retouch`;
9337
+ const retouchBranch = `local-${prdId}-final-review-retouch`;
9328
9338
  try {
9329
9339
  execFileSync3(
9330
9340
  "git",
@@ -9371,15 +9381,26 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
9371
9381
  return;
9372
9382
  } catch {
9373
9383
  }
9374
- execFileSync3(
9375
- "git",
9376
- ["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
9377
- {
9384
+ if (title) {
9385
+ const commitBody = body ? `${title}
9386
+
9387
+ ${body}` : title;
9388
+ execFileSync3("git", ["commit", "-m", commitBody], {
9378
9389
  cwd: root,
9379
9390
  encoding: "utf8",
9380
9391
  stdio: "pipe"
9381
- }
9382
- );
9392
+ });
9393
+ } else {
9394
+ execFileSync3(
9395
+ "git",
9396
+ ["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
9397
+ {
9398
+ cwd: root,
9399
+ encoding: "utf8",
9400
+ stdio: "pipe"
9401
+ }
9402
+ );
9403
+ }
9383
9404
  const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
9384
9405
  cwd: root,
9385
9406
  encoding: "utf8",
@@ -10722,10 +10743,6 @@ function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
10722
10743
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
10723
10744
  return existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : promptTemplate;
10724
10745
  }
10725
- function formatVerificationCommands(commands) {
10726
- if (commands.length === 0) return "No verification commands configured.";
10727
- return commands.map((cmd) => `- ${cmd.label}: ${cmd.command}`).join("\n");
10728
- }
10729
10746
  function buildFinalReviewPrompt(options) {
10730
10747
  return [
10731
10748
  "# Active PRD Final Review Context",
@@ -10740,9 +10757,6 @@ function buildFinalReviewPrompt(options) {
10740
10757
  "",
10741
10758
  `Run this command before handoff when retouch changes are made: pourkit run-verification --target ${options.targetName}`,
10742
10759
  "",
10743
- "Underlying project commands:",
10744
- formatVerificationCommands(options.verificationCommands),
10745
- "",
10746
10760
  "Evidence Packet (do not infer PRD context from local state files):",
10747
10761
  JSON.stringify(options.evidencePacket, null, 2),
10748
10762
  "",
@@ -10867,9 +10881,6 @@ function buildFinalReviewFinalizerPrompt(options) {
10867
10881
  "Changed paths:",
10868
10882
  ...options.changedPaths.map((p) => `- ${p}`),
10869
10883
  "",
10870
- "Verification commands given to Final Review:",
10871
- formatVerificationCommands(options.verificationCommands),
10872
- "",
10873
10884
  promptBody
10874
10885
  ].join("\n");
10875
10886
  }
@@ -10896,8 +10907,7 @@ async function runFinalReviewPrFinalizer(options) {
10896
10907
  prdBranch: options.prdBranch,
10897
10908
  mergeBase: options.mergeBase,
10898
10909
  summary: options.summary,
10899
- changedPaths: options.changedPaths,
10900
- verificationCommands: options.verificationCommands
10910
+ changedPaths: options.changedPaths
10901
10911
  }),
10902
10912
  branchName: options.branchName,
10903
10913
  repoRoot: options.repoRoot,
@@ -10957,17 +10967,32 @@ function ensureFinalReviewWorktree(options) {
10957
10967
  };
10958
10968
  }
10959
10969
  mkdirSync11(dirname5(worktreePath), { recursive: true });
10960
- const branchResult = spawnSync3(
10961
- "git",
10962
- ["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
10963
- { cwd: options.repoRoot, encoding: "utf8" }
10964
- );
10965
- if (branchResult.status !== 0) {
10966
- return {
10967
- ok: false,
10968
- reason: `Final Review failed to prepare branch ${options.branchName}.`,
10969
- diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
10970
- };
10970
+ if (options.isLocal) {
10971
+ const branchResult = spawnSync3(
10972
+ "git",
10973
+ ["branch", "-f", options.branchName, options.checkoutBase],
10974
+ { cwd: options.repoRoot, encoding: "utf8" }
10975
+ );
10976
+ if (branchResult.status !== 0) {
10977
+ return {
10978
+ ok: false,
10979
+ reason: `Final Review failed to prepare branch ${options.branchName}.`,
10980
+ diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
10981
+ };
10982
+ }
10983
+ } else {
10984
+ const branchResult = spawnSync3(
10985
+ "git",
10986
+ ["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
10987
+ { cwd: options.repoRoot, encoding: "utf8" }
10988
+ );
10989
+ if (branchResult.status !== 0) {
10990
+ return {
10991
+ ok: false,
10992
+ reason: `Final Review failed to prepare branch ${options.branchName}.`,
10993
+ diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
10994
+ };
10995
+ }
10971
10996
  }
10972
10997
  const addResult = spawnSync3(
10973
10998
  "git",
@@ -11079,6 +11104,156 @@ async function createOrReuseFinalReviewRetouchPr(options) {
11079
11104
  })
11080
11105
  });
11081
11106
  }
11107
+ async function writeAndVerifyLocalDualReceipt(repoRoot2, prdRef, record, targetName, prdBranch, mergeBase, verdict, reviewedAt, isLocalMode) {
11108
+ if (!isLocalMode) return null;
11109
+ let currentRecord = await readLocalPrdRun(repoRoot2, prdRef);
11110
+ if (!currentRecord) {
11111
+ currentRecord = {
11112
+ prdId: normalizePrdRunRef2(prdRef),
11113
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
11114
+ receipts: {},
11115
+ metadata: {}
11116
+ };
11117
+ }
11118
+ const localStoreReceipt = {
11119
+ completedAt: reviewedAt,
11120
+ targetName,
11121
+ prdBranch,
11122
+ mergeBase,
11123
+ verdict,
11124
+ diagnostics: [],
11125
+ artifactPath: ".pourkit/final-review-artifact.json"
11126
+ };
11127
+ await writeLocalPrdRunRecord(repoRoot2, prdRef, {
11128
+ ...currentRecord,
11129
+ receipts: {
11130
+ ...currentRecord.receipts,
11131
+ finalReview: localStoreReceipt
11132
+ }
11133
+ });
11134
+ const prdRunStateResult = readPrdRun(repoRoot2, prdRef);
11135
+ const localStoreResult = await readLocalPrdRun(repoRoot2, prdRef);
11136
+ if (!prdRunStateResult.record?.finalReview) {
11137
+ const reason = `PRD Run State missing finalReview receipt after write for ${prdRef}.`;
11138
+ writePrdRunRecord(repoRoot2, {
11139
+ ...record,
11140
+ prdRef,
11141
+ status: "blocked",
11142
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11143
+ blockedGate: "final-review",
11144
+ blockedReason: reason,
11145
+ diagnostics: [reason],
11146
+ targetName,
11147
+ offendingPaths: []
11148
+ });
11149
+ return {
11150
+ prdRef,
11151
+ status: "blocked",
11152
+ blockedGate: "final-review",
11153
+ blockedReason: reason,
11154
+ diagnostics: [reason],
11155
+ offendingPaths: []
11156
+ };
11157
+ }
11158
+ if (!localStoreResult?.receipts.finalReview) {
11159
+ const reason = `Local PRD Run store missing finalReview receipt after write for ${prdRef}.`;
11160
+ writePrdRunRecord(repoRoot2, {
11161
+ ...record,
11162
+ prdRef,
11163
+ status: "blocked",
11164
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11165
+ blockedGate: "final-review",
11166
+ blockedReason: reason,
11167
+ diagnostics: [reason],
11168
+ targetName,
11169
+ offendingPaths: []
11170
+ });
11171
+ return {
11172
+ prdRef,
11173
+ status: "blocked",
11174
+ blockedGate: "final-review",
11175
+ blockedReason: reason,
11176
+ diagnostics: [reason],
11177
+ offendingPaths: []
11178
+ };
11179
+ }
11180
+ const stateFr = prdRunStateResult.record.finalReview;
11181
+ const localFr = localStoreResult.receipts.finalReview;
11182
+ const mismatchFields = [];
11183
+ if (stateFr.targetName !== localFr.targetName)
11184
+ mismatchFields.push("targetName");
11185
+ if (stateFr.prdBranch !== localFr.prdBranch) mismatchFields.push("prdBranch");
11186
+ if (stateFr.mergeBase !== localFr.mergeBase) mismatchFields.push("mergeBase");
11187
+ if (stateFr.verdict !== localFr.verdict) mismatchFields.push("verdict");
11188
+ if (!stateFr.artifactPath !== !localFr.artifactPath)
11189
+ mismatchFields.push("artifactPath");
11190
+ if (!stateFr.diagnostics !== !localFr.diagnostics)
11191
+ mismatchFields.push("diagnostics");
11192
+ if (!stateFr.reviewedAt !== !localFr.completedAt)
11193
+ mismatchFields.push("reviewedTimestamp");
11194
+ if (mismatchFields.length > 0) {
11195
+ const reason = `Dual receipt mismatch on fields: ${mismatchFields.join(", ")}`;
11196
+ writePrdRunRecord(repoRoot2, {
11197
+ ...record,
11198
+ prdRef,
11199
+ status: "blocked",
11200
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11201
+ blockedGate: "final-review",
11202
+ blockedReason: reason,
11203
+ diagnostics: [reason],
11204
+ targetName,
11205
+ finalReview: stateFr,
11206
+ offendingPaths: []
11207
+ });
11208
+ return {
11209
+ prdRef,
11210
+ status: "blocked",
11211
+ blockedGate: "final-review",
11212
+ blockedReason: reason,
11213
+ diagnostics: [reason],
11214
+ offendingPaths: []
11215
+ };
11216
+ }
11217
+ return null;
11218
+ }
11219
+ async function verifyLocalDualFinalReviewReceipts(repoRoot2, prdRef) {
11220
+ const prdRunStateResult = readPrdRun(repoRoot2, prdRef);
11221
+ const stateFr = prdRunStateResult.record?.finalReview;
11222
+ const localStoreResult = await readLocalPrdRun(repoRoot2, prdRef);
11223
+ const localFr = localStoreResult?.receipts.finalReview;
11224
+ const diagnostics = [];
11225
+ const validFinalReviewStatuses = /* @__PURE__ */ new Set(["final_reviewed", "succeeded"]);
11226
+ if (!stateFr || !validFinalReviewStatuses.has(stateFr.status)) {
11227
+ diagnostics.push(
11228
+ stateFr ? `PRD Run State finalReview status is "${stateFr.status}". Expected "final_reviewed" or "succeeded".` : "PRD Run State has no finalReview receipt."
11229
+ );
11230
+ return {
11231
+ ok: false,
11232
+ reason: diagnostics[0],
11233
+ diagnostics
11234
+ };
11235
+ }
11236
+ if (!localFr) {
11237
+ diagnostics.push("Local PRD Run Store has no finalReview receipt.");
11238
+ return {
11239
+ ok: false,
11240
+ reason: diagnostics[0],
11241
+ diagnostics
11242
+ };
11243
+ }
11244
+ const mismatchFields = [];
11245
+ if (stateFr.targetName !== localFr.targetName)
11246
+ mismatchFields.push("targetName");
11247
+ if (stateFr.prdBranch !== localFr.prdBranch) mismatchFields.push("prdBranch");
11248
+ if (stateFr.mergeBase !== localFr.mergeBase) mismatchFields.push("mergeBase");
11249
+ if (stateFr.verdict !== localFr.verdict) mismatchFields.push("verdict");
11250
+ if (mismatchFields.length > 0) {
11251
+ const reason = `Dual receipt mismatch on fields: ${mismatchFields.join(", ")}`;
11252
+ diagnostics.push(reason);
11253
+ return { ok: false, reason, diagnostics };
11254
+ }
11255
+ return { ok: true };
11256
+ }
11082
11257
  async function runPrdRunFinalReviewCommand(options) {
11083
11258
  const prdRef = normalizePrdRunRef2(options.prdRef);
11084
11259
  const targetName = options.targetName.trim();
@@ -11143,6 +11318,39 @@ async function runPrdRunFinalReviewCommand(options) {
11143
11318
  offendingPaths: []
11144
11319
  };
11145
11320
  }
11321
+ if (record.mode === "local") {
11322
+ const runnableIssues = await getRunnableLocalIssues(
11323
+ prdRef,
11324
+ options.repoRoot
11325
+ );
11326
+ if (runnableIssues.length > 0) {
11327
+ const reason = `PRD Run ${prdRef} is marked "drained" but ${runnableIssues.length} runnable local Issues remain. Queue drain receipt may be premature.`;
11328
+ writePrdRunRecord(options.repoRoot, {
11329
+ ...record,
11330
+ status: "blocked",
11331
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11332
+ blockedGate: "final-review",
11333
+ blockedReason: reason,
11334
+ diagnostics: [
11335
+ `Current status: ${record.status}`,
11336
+ `Runnable issues remaining: ${runnableIssues.length}`
11337
+ ],
11338
+ targetName,
11339
+ offendingPaths: []
11340
+ });
11341
+ return {
11342
+ prdRef,
11343
+ status: "blocked",
11344
+ blockedGate: "final-review",
11345
+ blockedReason: reason,
11346
+ diagnostics: [
11347
+ `Current status: ${record.status}`,
11348
+ `Runnable issues remaining: ${runnableIssues.length}`
11349
+ ],
11350
+ offendingPaths: []
11351
+ };
11352
+ }
11353
+ }
11146
11354
  if (!options.issueProvider) {
11147
11355
  const reason = "Missing IssueProvider. Cannot validate child completeness.";
11148
11356
  writePrdRunRecord(options.repoRoot, {
@@ -11251,8 +11459,19 @@ async function runPrdRunFinalReviewCommand(options) {
11251
11459
  offendingPaths: []
11252
11460
  };
11253
11461
  }
11254
- const verificationCommands = targetConfig.strategy.verify?.commands ?? [];
11255
- const mergeBaseResult = computeFinalReviewMergeBase(options.repoRoot, prdRef);
11462
+ const isLocalMode = record.mode === "local";
11463
+ let mergeBaseResult;
11464
+ let prdBranch;
11465
+ if (isLocalMode) {
11466
+ prdBranch = getLocalPrdBranchName(prdRef);
11467
+ mergeBaseResult = computeLocalFinalReviewMergeBase(
11468
+ options.repoRoot,
11469
+ prdBranch
11470
+ );
11471
+ } else {
11472
+ prdBranch = record.prdBranch ?? prdRef;
11473
+ mergeBaseResult = computeFinalReviewMergeBase(options.repoRoot, prdRef);
11474
+ }
11256
11475
  if (!mergeBaseResult.ok) {
11257
11476
  writePrdRunRecord(options.repoRoot, {
11258
11477
  ...record,
@@ -11265,7 +11484,7 @@ async function runPrdRunFinalReviewCommand(options) {
11265
11484
  finalReview: {
11266
11485
  status: "blocked",
11267
11486
  targetName,
11268
- prdBranch: record.prdBranch ?? prdRef,
11487
+ prdBranch,
11269
11488
  mergeBase: mergeBaseResult.mergeBase,
11270
11489
  diagnostics: mergeBaseResult.diagnostics,
11271
11490
  reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -11281,7 +11500,6 @@ async function runPrdRunFinalReviewCommand(options) {
11281
11500
  offendingPaths: []
11282
11501
  };
11283
11502
  }
11284
- const prdBranch = record.prdBranch ?? prdRef;
11285
11503
  const manifestResult = readPlanningArtifactManifest(options.repoRoot, prdRef);
11286
11504
  if (!manifestResult.ok) {
11287
11505
  const reason = `Evidence Packet construction failed: ${manifestResult.reason}`;
@@ -11341,11 +11559,12 @@ async function runPrdRunFinalReviewCommand(options) {
11341
11559
  planning: record.planning,
11342
11560
  finalReview: startReceipt
11343
11561
  });
11344
- const finalReviewBranchName = buildFinalReviewBranchName(prdBranch);
11562
+ const finalReviewBranchName = isLocalMode ? `${prdBranch.replace(/\//g, "-")}-final-review-retouch` : buildFinalReviewBranchName(prdBranch);
11345
11563
  const worktreeResult = ensureFinalReviewWorktree({
11346
11564
  repoRoot: options.repoRoot,
11347
11565
  branchName: finalReviewBranchName,
11348
- checkoutBase: prdBranch
11566
+ checkoutBase: prdBranch,
11567
+ isLocal: isLocalMode
11349
11568
  });
11350
11569
  if (!worktreeResult.ok) {
11351
11570
  writePrdRunRecord(options.repoRoot, {
@@ -11386,8 +11605,7 @@ async function runPrdRunFinalReviewCommand(options) {
11386
11605
  repoRoot: options.repoRoot,
11387
11606
  promptTemplate: finalReviewConfig.promptTemplate,
11388
11607
  targetName,
11389
- evidencePacket,
11390
- verificationCommands
11608
+ evidencePacket
11391
11609
  }),
11392
11610
  target: targetConfig,
11393
11611
  repoRoot: options.repoRoot,
@@ -11530,6 +11748,7 @@ async function runPrdRunFinalReviewCommand(options) {
11530
11748
  }
11531
11749
  const { verdict, summary, diagnostics: artifactDiagnostics } = artifactResult;
11532
11750
  if (verdict === "pass_no_changes") {
11751
+ const reviewedAt = (/* @__PURE__ */ new Date()).toISOString();
11533
11752
  const receipt = {
11534
11753
  status: "succeeded",
11535
11754
  targetName,
@@ -11538,17 +11757,29 @@ async function runPrdRunFinalReviewCommand(options) {
11538
11757
  verdict: "pass_no_changes",
11539
11758
  artifactPath: ".pourkit/final-review-artifact.json",
11540
11759
  diagnostics: [],
11541
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
11760
+ reviewedAt
11542
11761
  };
11543
11762
  writePrdRunRecord(options.repoRoot, {
11544
11763
  ...record,
11545
11764
  status: "final_reviewed",
11546
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11765
+ updatedAt: reviewedAt,
11547
11766
  targetName,
11548
11767
  start: record.start,
11549
11768
  planning: record.planning,
11550
11769
  finalReview: receipt
11551
11770
  });
11771
+ const blocked = await writeAndVerifyLocalDualReceipt(
11772
+ options.repoRoot,
11773
+ prdRef,
11774
+ record,
11775
+ targetName,
11776
+ prdBranch,
11777
+ mergeBaseResult.mergeBase,
11778
+ "pass_no_changes",
11779
+ reviewedAt,
11780
+ isLocalMode
11781
+ );
11782
+ if (blocked) return blocked;
11552
11783
  return {
11553
11784
  prdRef,
11554
11785
  status: "final_reviewed",
@@ -11557,7 +11788,7 @@ async function runPrdRunFinalReviewCommand(options) {
11557
11788
  };
11558
11789
  }
11559
11790
  if (verdict === "pass_with_retouch") {
11560
- if (!options.prProvider) {
11791
+ if (!options.prProvider && !isLocalMode) {
11561
11792
  const reason = "Missing PRProvider. Cannot create retouch PR without a PR provider.";
11562
11793
  writePrdRunRecord(options.repoRoot, {
11563
11794
  ...record,
@@ -11588,7 +11819,7 @@ async function runPrdRunFinalReviewCommand(options) {
11588
11819
  };
11589
11820
  }
11590
11821
  const autoMerge = options.autoMerge ?? true;
11591
- const existingRetouchPrNumber = record.finalReview?.retouchPrNumber;
11822
+ const existingRetouchPrNumber = !isLocalMode ? record.finalReview?.retouchPrNumber : void 0;
11592
11823
  if (existingRetouchPrNumber && autoMerge) {
11593
11824
  const existingPr = await getPrByNumberIfAvailable(
11594
11825
  options.prProvider,
@@ -11674,7 +11905,6 @@ async function runPrdRunFinalReviewCommand(options) {
11674
11905
  mergeBase: mergeBaseResult.mergeBase,
11675
11906
  summary,
11676
11907
  changedPaths: scopeResult.changedPaths,
11677
- verificationCommands,
11678
11908
  logger: options.logger
11679
11909
  });
11680
11910
  if (!finalizerResult.ok) {
@@ -11706,6 +11936,116 @@ async function runPrdRunFinalReviewCommand(options) {
11706
11936
  offendingPaths: []
11707
11937
  };
11708
11938
  }
11939
+ if (isLocalMode) {
11940
+ let localResult;
11941
+ try {
11942
+ const result = await squashFinalReviewRetouch(
11943
+ prdRef,
11944
+ options.repoRoot,
11945
+ finalizerResult.title,
11946
+ finalizerResult.body
11947
+ );
11948
+ if (!result) {
11949
+ const reason = "Retouch branch missing or empty. Cannot squash-merge.";
11950
+ writePrdRunRecord(options.repoRoot, {
11951
+ ...record,
11952
+ status: "blocked",
11953
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11954
+ blockedGate: "final-review",
11955
+ blockedReason: reason,
11956
+ diagnostics: [reason],
11957
+ targetName,
11958
+ finalReview: {
11959
+ status: "blocked",
11960
+ targetName,
11961
+ prdBranch,
11962
+ mergeBase: mergeBaseResult.mergeBase,
11963
+ verdict: "pass_with_retouch",
11964
+ diagnostics: [reason],
11965
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
11966
+ },
11967
+ offendingPaths: []
11968
+ });
11969
+ return {
11970
+ prdRef,
11971
+ status: "blocked",
11972
+ blockedGate: "final-review",
11973
+ blockedReason: reason,
11974
+ diagnostics: [reason],
11975
+ offendingPaths: []
11976
+ };
11977
+ }
11978
+ localResult = result;
11979
+ } catch (error) {
11980
+ const msg = error instanceof Error ? error.message : String(error);
11981
+ writePrdRunRecord(options.repoRoot, {
11982
+ ...record,
11983
+ status: "blocked",
11984
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11985
+ blockedGate: "final-review",
11986
+ blockedReason: `Local retouch squash-merge failed: ${msg}`,
11987
+ diagnostics: [msg],
11988
+ targetName,
11989
+ finalReview: {
11990
+ status: "blocked",
11991
+ targetName,
11992
+ prdBranch,
11993
+ mergeBase: mergeBaseResult.mergeBase,
11994
+ verdict: "pass_with_retouch",
11995
+ diagnostics: [msg],
11996
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
11997
+ },
11998
+ offendingPaths: []
11999
+ });
12000
+ return {
12001
+ prdRef,
12002
+ status: "blocked",
12003
+ blockedGate: "final-review",
12004
+ blockedReason: `Local retouch squash-merge failed: ${msg}`,
12005
+ diagnostics: [msg],
12006
+ offendingPaths: []
12007
+ };
12008
+ }
12009
+ const receipt2 = {
12010
+ status: "final_reviewed",
12011
+ targetName,
12012
+ prdBranch,
12013
+ mergeBase: mergeBaseResult.mergeBase,
12014
+ verdict: "pass_with_retouch",
12015
+ artifactPath: ".pourkit/final-review-artifact.json",
12016
+ diagnostics: [],
12017
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
12018
+ retouchMergeCommit: localResult.mergeCommit,
12019
+ changedPaths: localResult.changedPaths
12020
+ };
12021
+ writePrdRunRecord(options.repoRoot, {
12022
+ ...record,
12023
+ status: "final_reviewed",
12024
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12025
+ targetName,
12026
+ start: record.start,
12027
+ planning: record.planning,
12028
+ finalReview: receipt2
12029
+ });
12030
+ const blocked = await writeAndVerifyLocalDualReceipt(
12031
+ options.repoRoot,
12032
+ prdRef,
12033
+ record,
12034
+ targetName,
12035
+ prdBranch,
12036
+ mergeBaseResult.mergeBase,
12037
+ "pass_with_retouch",
12038
+ (/* @__PURE__ */ new Date()).toISOString(),
12039
+ isLocalMode
12040
+ );
12041
+ if (blocked) return blocked;
12042
+ return {
12043
+ prdRef,
12044
+ status: "final_reviewed",
12045
+ finalReview: receipt2,
12046
+ diagnostics: []
12047
+ };
12048
+ }
11709
12049
  let retouchPr;
11710
12050
  try {
11711
12051
  retouchPr = await createOrReuseFinalReviewRetouchPr({
@@ -13895,6 +14235,37 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
13895
14235
  }
13896
14236
  return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
13897
14237
  }
14238
+ function computeLocalFinalReviewMergeBase(repoRoot2, prdBranch) {
14239
+ const mergeBaseResult = spawnSync3("git", ["merge-base", "dev", prdBranch], {
14240
+ cwd: repoRoot2,
14241
+ encoding: "utf8"
14242
+ });
14243
+ if (mergeBaseResult.status !== 0) {
14244
+ const diagnostics = [];
14245
+ if (mergeBaseResult.stderr?.toString?.()?.trim()) {
14246
+ diagnostics.push(mergeBaseResult.stderr.toString().trim());
14247
+ }
14248
+ if (mergeBaseResult.stdout?.toString?.()?.trim()) {
14249
+ diagnostics.push(mergeBaseResult.stdout.toString().trim());
14250
+ }
14251
+ return {
14252
+ ok: false,
14253
+ gate: "final-review",
14254
+ reason: `Final Review merge-base computation failed for dev and ${prdBranch}.`,
14255
+ diagnostics: diagnostics.length > 0 ? diagnostics : ["git merge-base returned non-zero exit status"]
14256
+ };
14257
+ }
14258
+ const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
14259
+ if (!mergeBase) {
14260
+ return {
14261
+ ok: false,
14262
+ gate: "final-review",
14263
+ reason: `Final Review merge-base returned empty result for dev and ${prdBranch}.`,
14264
+ diagnostics: [`merge-base stdout was empty for dev..${prdBranch}`]
14265
+ };
14266
+ }
14267
+ return { ok: true, mergeBase, diagnostics: [] };
14268
+ }
13898
14269
  function validateReconciliationArtifact2(artifact, context) {
13899
14270
  const errors = [];
13900
14271
  if (!artifact.prdRef) {
@@ -14339,201 +14710,47 @@ async function runPrdRunLaunchCommand(options) {
14339
14710
  let finalReviewResult;
14340
14711
  if (!skipped.includes("final-review")) {
14341
14712
  attempted.push("final-review");
14342
- const localStorePath2 = join21(
14343
- options.repoRoot,
14344
- ".pourkit",
14345
- "local-prd-runs",
14346
- prdRef
14347
- );
14348
- if (existsSync17(localStorePath2)) {
14349
- const prdRecord = readPrdRun(options.repoRoot, prdRef);
14350
- if (prdRecord.record && prdRecord.record.status !== "drained" && !canRetryFinalReviewBlock(prdRecord.record)) {
14351
- const reason = `Final Review blocked: Queue drain not completed. Status is "${prdRecord.record.status}". Finish all child Issues before Final Review.`;
14352
- writePrdRunRecord(options.repoRoot, {
14353
- prdRef,
14354
- status: "blocked",
14355
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14356
- blockedGate: "final-review",
14357
- blockedReason: reason,
14358
- diagnostics: [
14359
- `Current status: ${prdRecord.record.status}. Queue drain receipt is missing. Run child Issues through queue-run before Final Review.`
14360
- ],
14361
- targetName: options.targetName,
14362
- offendingPaths: []
14363
- });
14364
- return {
14365
- prdRef,
14366
- status: "blocked",
14367
- attempted,
14368
- skipped: [
14369
- ...skipped.includes("prepare") ? ["prepare"] : [],
14370
- ...skipped.includes("start") ? ["queue"] : [],
14371
- "reconcile"
14372
- ],
14373
- resumed,
14374
- diagnostics: [
14375
- `Current status: ${prdRecord.record.status}. Queue drain receipt is missing. Run child Issues through queue-run before Final Review.`
14376
- ],
14377
- blockedGate: "final-review",
14378
- blockedReason: reason,
14379
- offendingPaths: [],
14380
- prepare: prepareResult,
14381
- start: startResult
14382
- };
14383
- }
14384
- const targetConfig = options.config?.targets?.find(
14385
- (t) => t.name === options.targetName
14713
+ if (existingRecord.record?.mode === "local") {
14714
+ const runnableIssues = await getRunnableLocalIssues(
14715
+ prdRef,
14716
+ options.repoRoot
14386
14717
  );
14387
- const verificationCommands = targetConfig?.strategy?.verify?.commands ?? [];
14388
- const localResult = await runLocalFinalReview(prdRef, {
14389
- repoRoot: options.repoRoot,
14390
- verificationCommands
14391
- });
14392
- if (!localResult.ok) {
14393
- const reason = localResult.message ?? "Local Final Review blocked.";
14394
- writePrdRunRecord(options.repoRoot, {
14395
- prdRef,
14396
- status: "blocked",
14397
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14398
- blockedGate: "final-review",
14399
- blockedReason: reason,
14400
- diagnostics: [reason],
14401
- targetName: options.targetName,
14402
- offendingPaths: []
14403
- });
14404
- return {
14405
- prdRef,
14406
- status: "blocked",
14407
- attempted,
14408
- skipped: [
14409
- ...skipped.includes("prepare") ? ["prepare"] : [],
14410
- ...skipped.includes("start") ? ["queue"] : [],
14411
- "reconcile"
14412
- ],
14413
- resumed,
14414
- diagnostics: [reason],
14415
- blockedGate: "final-review",
14416
- blockedReason: reason,
14417
- offendingPaths: [],
14418
- prepare: prepareResult,
14419
- start: startResult
14420
- };
14421
- }
14422
- if (localResult.verdict === "blocked" || localResult.verdict === "needs_human_review") {
14423
- const reason = localResult.message ?? `Local Final Review verdict: ${localResult.verdict}`;
14424
- writePrdRunRecord(options.repoRoot, {
14425
- prdRef,
14426
- status: "blocked",
14427
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14428
- blockedGate: "final-review",
14429
- blockedReason: reason,
14430
- diagnostics: [reason],
14431
- targetName: options.targetName,
14432
- offendingPaths: []
14433
- });
14718
+ if (runnableIssues.length > 0) {
14719
+ const reason = `Config-local Final Review blocked: Queue not drained (${runnableIssues.length} runnable Issues remain).`;
14434
14720
  return {
14435
14721
  prdRef,
14436
14722
  status: "blocked",
14437
14723
  attempted,
14438
- skipped: [
14439
- ...skipped.includes("prepare") ? ["prepare"] : [],
14440
- ...skipped.includes("start") ? ["queue"] : [],
14441
- "reconcile"
14442
- ],
14724
+ skipped: ["start", "queue", "reconcile"],
14443
14725
  resumed,
14444
14726
  diagnostics: [reason],
14445
14727
  blockedGate: "final-review",
14446
14728
  blockedReason: reason,
14447
14729
  offendingPaths: [],
14448
14730
  prepare: prepareResult,
14449
- start: startResult
14450
- };
14451
- }
14452
- let retouchMergeCommit;
14453
- let changedPaths;
14454
- if (localResult.verdict === "pass_with_retouch") {
14455
- try {
14456
- const evidence = await squashFinalReviewRetouch(
14457
- prdRef,
14458
- options.repoRoot
14459
- );
14460
- if (evidence) {
14461
- retouchMergeCommit = evidence.mergeCommit;
14462
- changedPaths = evidence.changedPaths;
14463
- }
14464
- } catch (error) {
14465
- const reason = error instanceof Error ? error.message : String(error);
14466
- writePrdRunRecord(options.repoRoot, {
14731
+ start: startResult,
14732
+ finalReview: {
14467
14733
  prdRef,
14468
14734
  status: "blocked",
14469
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14470
14735
  blockedGate: "final-review",
14471
14736
  blockedReason: reason,
14472
14737
  diagnostics: [reason],
14473
- targetName: options.targetName,
14474
14738
  offendingPaths: []
14475
- });
14476
- return {
14477
- prdRef,
14478
- status: "blocked",
14479
- attempted,
14480
- skipped: [
14481
- ...skipped.includes("prepare") ? ["prepare"] : [],
14482
- ...skipped.includes("start") ? ["queue"] : [],
14483
- "reconcile"
14484
- ],
14485
- resumed,
14486
- diagnostics: [reason],
14487
- blockedGate: "final-review",
14488
- blockedReason: reason,
14489
- offendingPaths: [],
14490
- prepare: prepareResult,
14491
- start: startResult
14492
- };
14493
- }
14739
+ }
14740
+ };
14494
14741
  }
14495
- const receipt = {
14496
- status: "final_reviewed",
14497
- targetName: options.targetName,
14498
- prdBranch: `local/${prdRef}`,
14499
- mergeBase: localResult.receipt?.mergeBase,
14500
- diagnostics: [],
14501
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
14502
- verdict: localResult.verdict ?? "pass_no_changes",
14503
- retouchMergeCommit,
14504
- changedPaths
14505
- };
14506
- const existingRecord2 = readPrdRun(options.repoRoot, prdRef);
14507
- writePrdRunRecord(options.repoRoot, {
14508
- prdRef,
14509
- status: "final_reviewed",
14510
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14511
- targetName: options.targetName,
14512
- manifestPath: existingRecord2.record?.manifestPath,
14513
- planning: existingRecord2.record?.planning,
14514
- start: existingRecord2.record?.start,
14515
- finalReview: receipt
14516
- });
14517
- finalReviewResult = {
14518
- prdRef,
14519
- status: "final_reviewed",
14520
- finalReview: receipt,
14521
- diagnostics: localResult.message ? [localResult.message] : []
14522
- };
14523
- diagnostics.push(...localResult.message ? [localResult.message] : []);
14524
- } else {
14525
- finalReviewResult = await runPrdRunFinalReviewCommand({
14526
- repoRoot: options.repoRoot,
14527
- prdRef,
14528
- targetName: options.targetName,
14529
- autoMerge: options.autoMerge,
14530
- issueProvider: options.issueProvider,
14531
- prProvider: options.prProvider,
14532
- executionProvider: options.executionProvider,
14533
- config: options.config,
14534
- logger: options.logger
14535
- });
14536
14742
  }
14743
+ finalReviewResult = await runPrdRunFinalReviewCommand({
14744
+ repoRoot: options.repoRoot,
14745
+ prdRef,
14746
+ targetName: options.targetName,
14747
+ autoMerge: options.autoMerge,
14748
+ issueProvider: options.issueProvider,
14749
+ prProvider: options.prProvider,
14750
+ executionProvider: options.executionProvider,
14751
+ config: options.config,
14752
+ logger: options.logger
14753
+ });
14537
14754
  const skippedAfterFr = [
14538
14755
  ...skipped.includes("prepare") ? ["prepare"] : [],
14539
14756
  ...skipped.includes("start") ? ["queue"] : [],
@@ -14603,6 +14820,28 @@ async function runPrdRunLaunchCommand(options) {
14603
14820
  finalReview: finalReviewResult
14604
14821
  };
14605
14822
  }
14823
+ if (prdRecord.record?.mode === "local") {
14824
+ const dualCheck = await verifyLocalDualFinalReviewReceipts(
14825
+ options.repoRoot,
14826
+ prdRef
14827
+ );
14828
+ if (!dualCheck.ok) {
14829
+ return {
14830
+ prdRef,
14831
+ status: "blocked",
14832
+ attempted,
14833
+ skipped: [...skipped],
14834
+ resumed,
14835
+ diagnostics: dualCheck.diagnostics,
14836
+ blockedGate: "reconciliation",
14837
+ blockedReason: dualCheck.reason,
14838
+ offendingPaths: [],
14839
+ prepare: prepareResult,
14840
+ start: startResult,
14841
+ finalReview: finalReviewResult
14842
+ };
14843
+ }
14844
+ }
14606
14845
  const localResult = await runLocalReconciliation(prdRef, {
14607
14846
  repoRoot: options.repoRoot
14608
14847
  });
@@ -21169,11 +21408,11 @@ function createCliProgram(version) {
21169
21408
  return program;
21170
21409
  }
21171
21410
  async function resolveCliVersion() {
21172
- if (isPackageVersion("0.0.0-next-20260608221925")) {
21173
- return "0.0.0-next-20260608221925";
21411
+ if (isPackageVersion("0.0.0-next-20260609213420")) {
21412
+ return "0.0.0-next-20260609213420";
21174
21413
  }
21175
- if (isReleaseVersion("0.0.0-next-20260608221925")) {
21176
- return "0.0.0-next-20260608221925";
21414
+ if (isReleaseVersion("0.0.0-next-20260609213420")) {
21415
+ return "0.0.0-next-20260609213420";
21177
21416
  }
21178
21417
  try {
21179
21418
  const root = repoRoot();