@pourkit/cli 0.0.0-next-20260608221925 → 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
@@ -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
  }
@@ -8597,7 +8573,20 @@ var LocalPrdRunRecordSchema = z3.object({
8597
8573
  baseCommit: z3.string().min(1)
8598
8574
  }).optional(),
8599
8575
  queue: z3.object({ completedAt: z3.string().min(1) }).optional(),
8600
- 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(),
8601
8590
  reconciliation: z3.object({ completedAt: z3.string().min(1) }).optional(),
8602
8591
  complete: z3.object({
8603
8592
  completedAt: z3.string().min(1),
@@ -9278,7 +9267,7 @@ async function runLocalFinalReview(prdId, options) {
9278
9267
  }
9279
9268
  let verdict;
9280
9269
  if (commands.length === 0 || failures.length === 0) {
9281
- const retouchBranch = `local/${prdId}/retouch`;
9270
+ const retouchBranch = `local-${prdId}-final-review-retouch`;
9282
9271
  try {
9283
9272
  execSync2(`git show-ref --verify --quiet refs/heads/${retouchBranch}`, {
9284
9273
  cwd: root,
@@ -9292,7 +9281,7 @@ async function runLocalFinalReview(prdId, options) {
9292
9281
  } else if (failures.length === commands.length) {
9293
9282
  verdict = "blocked";
9294
9283
  } else {
9295
- const retouchBranch = `local/${prdId}/retouch`;
9284
+ const retouchBranch = `local-${prdId}-final-review-retouch`;
9296
9285
  try {
9297
9286
  execSync2(`git show-ref --verify --quiet refs/heads/${retouchBranch}`, {
9298
9287
  cwd: root,
@@ -9321,10 +9310,10 @@ async function runLocalFinalReview(prdId, options) {
9321
9310
  }
9322
9311
  return result;
9323
9312
  }
9324
- async function squashFinalReviewRetouch(prdId, repoRoot2) {
9313
+ async function squashFinalReviewRetouch(prdId, repoRoot2, title, body) {
9325
9314
  const root = repoRoot2 ?? process.cwd();
9326
9315
  const targetBranch = `local/${prdId}`;
9327
- const retouchBranch = `local/${prdId}/retouch`;
9316
+ const retouchBranch = `local-${prdId}-final-review-retouch`;
9328
9317
  try {
9329
9318
  execFileSync3(
9330
9319
  "git",
@@ -9371,15 +9360,26 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
9371
9360
  return;
9372
9361
  } catch {
9373
9362
  }
9374
- execFileSync3(
9375
- "git",
9376
- ["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
9377
- {
9363
+ if (title) {
9364
+ const commitBody = body ? `${title}
9365
+
9366
+ ${body}` : title;
9367
+ execFileSync3("git", ["commit", "-m", commitBody], {
9378
9368
  cwd: root,
9379
9369
  encoding: "utf8",
9380
9370
  stdio: "pipe"
9381
- }
9382
- );
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
9383
  const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
9384
9384
  cwd: root,
9385
9385
  encoding: "utf8",
@@ -10722,10 +10722,6 @@ function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
10722
10722
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
10723
10723
  return existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : promptTemplate;
10724
10724
  }
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
10725
  function buildFinalReviewPrompt(options) {
10730
10726
  return [
10731
10727
  "# Active PRD Final Review Context",
@@ -10740,9 +10736,6 @@ function buildFinalReviewPrompt(options) {
10740
10736
  "",
10741
10737
  `Run this command before handoff when retouch changes are made: pourkit run-verification --target ${options.targetName}`,
10742
10738
  "",
10743
- "Underlying project commands:",
10744
- formatVerificationCommands(options.verificationCommands),
10745
- "",
10746
10739
  "Evidence Packet (do not infer PRD context from local state files):",
10747
10740
  JSON.stringify(options.evidencePacket, null, 2),
10748
10741
  "",
@@ -10867,9 +10860,6 @@ function buildFinalReviewFinalizerPrompt(options) {
10867
10860
  "Changed paths:",
10868
10861
  ...options.changedPaths.map((p) => `- ${p}`),
10869
10862
  "",
10870
- "Verification commands given to Final Review:",
10871
- formatVerificationCommands(options.verificationCommands),
10872
- "",
10873
10863
  promptBody
10874
10864
  ].join("\n");
10875
10865
  }
@@ -10896,8 +10886,7 @@ async function runFinalReviewPrFinalizer(options) {
10896
10886
  prdBranch: options.prdBranch,
10897
10887
  mergeBase: options.mergeBase,
10898
10888
  summary: options.summary,
10899
- changedPaths: options.changedPaths,
10900
- verificationCommands: options.verificationCommands
10889
+ changedPaths: options.changedPaths
10901
10890
  }),
10902
10891
  branchName: options.branchName,
10903
10892
  repoRoot: options.repoRoot,
@@ -10957,17 +10946,32 @@ function ensureFinalReviewWorktree(options) {
10957
10946
  };
10958
10947
  }
10959
10948
  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
- };
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
+ }
10971
10975
  }
10972
10976
  const addResult = spawnSync3(
10973
10977
  "git",
@@ -11079,6 +11083,156 @@ async function createOrReuseFinalReviewRetouchPr(options) {
11079
11083
  })
11080
11084
  });
11081
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
+ }
11082
11236
  async function runPrdRunFinalReviewCommand(options) {
11083
11237
  const prdRef = normalizePrdRunRef2(options.prdRef);
11084
11238
  const targetName = options.targetName.trim();
@@ -11143,6 +11297,39 @@ async function runPrdRunFinalReviewCommand(options) {
11143
11297
  offendingPaths: []
11144
11298
  };
11145
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
+ }
11146
11333
  if (!options.issueProvider) {
11147
11334
  const reason = "Missing IssueProvider. Cannot validate child completeness.";
11148
11335
  writePrdRunRecord(options.repoRoot, {
@@ -11251,8 +11438,19 @@ async function runPrdRunFinalReviewCommand(options) {
11251
11438
  offendingPaths: []
11252
11439
  };
11253
11440
  }
11254
- const verificationCommands = targetConfig.strategy.verify?.commands ?? [];
11255
- 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
+ }
11256
11454
  if (!mergeBaseResult.ok) {
11257
11455
  writePrdRunRecord(options.repoRoot, {
11258
11456
  ...record,
@@ -11265,7 +11463,7 @@ async function runPrdRunFinalReviewCommand(options) {
11265
11463
  finalReview: {
11266
11464
  status: "blocked",
11267
11465
  targetName,
11268
- prdBranch: record.prdBranch ?? prdRef,
11466
+ prdBranch,
11269
11467
  mergeBase: mergeBaseResult.mergeBase,
11270
11468
  diagnostics: mergeBaseResult.diagnostics,
11271
11469
  reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -11281,7 +11479,6 @@ async function runPrdRunFinalReviewCommand(options) {
11281
11479
  offendingPaths: []
11282
11480
  };
11283
11481
  }
11284
- const prdBranch = record.prdBranch ?? prdRef;
11285
11482
  const manifestResult = readPlanningArtifactManifest(options.repoRoot, prdRef);
11286
11483
  if (!manifestResult.ok) {
11287
11484
  const reason = `Evidence Packet construction failed: ${manifestResult.reason}`;
@@ -11341,11 +11538,12 @@ async function runPrdRunFinalReviewCommand(options) {
11341
11538
  planning: record.planning,
11342
11539
  finalReview: startReceipt
11343
11540
  });
11344
- const finalReviewBranchName = buildFinalReviewBranchName(prdBranch);
11541
+ const finalReviewBranchName = isLocalMode ? `${prdBranch.replace(/\//g, "-")}-final-review-retouch` : buildFinalReviewBranchName(prdBranch);
11345
11542
  const worktreeResult = ensureFinalReviewWorktree({
11346
11543
  repoRoot: options.repoRoot,
11347
11544
  branchName: finalReviewBranchName,
11348
- checkoutBase: prdBranch
11545
+ checkoutBase: prdBranch,
11546
+ isLocal: isLocalMode
11349
11547
  });
11350
11548
  if (!worktreeResult.ok) {
11351
11549
  writePrdRunRecord(options.repoRoot, {
@@ -11386,8 +11584,7 @@ async function runPrdRunFinalReviewCommand(options) {
11386
11584
  repoRoot: options.repoRoot,
11387
11585
  promptTemplate: finalReviewConfig.promptTemplate,
11388
11586
  targetName,
11389
- evidencePacket,
11390
- verificationCommands
11587
+ evidencePacket
11391
11588
  }),
11392
11589
  target: targetConfig,
11393
11590
  repoRoot: options.repoRoot,
@@ -11530,6 +11727,7 @@ async function runPrdRunFinalReviewCommand(options) {
11530
11727
  }
11531
11728
  const { verdict, summary, diagnostics: artifactDiagnostics } = artifactResult;
11532
11729
  if (verdict === "pass_no_changes") {
11730
+ const reviewedAt = (/* @__PURE__ */ new Date()).toISOString();
11533
11731
  const receipt = {
11534
11732
  status: "succeeded",
11535
11733
  targetName,
@@ -11538,17 +11736,29 @@ async function runPrdRunFinalReviewCommand(options) {
11538
11736
  verdict: "pass_no_changes",
11539
11737
  artifactPath: ".pourkit/final-review-artifact.json",
11540
11738
  diagnostics: [],
11541
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
11739
+ reviewedAt
11542
11740
  };
11543
11741
  writePrdRunRecord(options.repoRoot, {
11544
11742
  ...record,
11545
11743
  status: "final_reviewed",
11546
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11744
+ updatedAt: reviewedAt,
11547
11745
  targetName,
11548
11746
  start: record.start,
11549
11747
  planning: record.planning,
11550
11748
  finalReview: receipt
11551
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;
11552
11762
  return {
11553
11763
  prdRef,
11554
11764
  status: "final_reviewed",
@@ -11557,7 +11767,7 @@ async function runPrdRunFinalReviewCommand(options) {
11557
11767
  };
11558
11768
  }
11559
11769
  if (verdict === "pass_with_retouch") {
11560
- if (!options.prProvider) {
11770
+ if (!options.prProvider && !isLocalMode) {
11561
11771
  const reason = "Missing PRProvider. Cannot create retouch PR without a PR provider.";
11562
11772
  writePrdRunRecord(options.repoRoot, {
11563
11773
  ...record,
@@ -11588,7 +11798,7 @@ async function runPrdRunFinalReviewCommand(options) {
11588
11798
  };
11589
11799
  }
11590
11800
  const autoMerge = options.autoMerge ?? true;
11591
- const existingRetouchPrNumber = record.finalReview?.retouchPrNumber;
11801
+ const existingRetouchPrNumber = !isLocalMode ? record.finalReview?.retouchPrNumber : void 0;
11592
11802
  if (existingRetouchPrNumber && autoMerge) {
11593
11803
  const existingPr = await getPrByNumberIfAvailable(
11594
11804
  options.prProvider,
@@ -11674,7 +11884,6 @@ async function runPrdRunFinalReviewCommand(options) {
11674
11884
  mergeBase: mergeBaseResult.mergeBase,
11675
11885
  summary,
11676
11886
  changedPaths: scopeResult.changedPaths,
11677
- verificationCommands,
11678
11887
  logger: options.logger
11679
11888
  });
11680
11889
  if (!finalizerResult.ok) {
@@ -11706,6 +11915,116 @@ async function runPrdRunFinalReviewCommand(options) {
11706
11915
  offendingPaths: []
11707
11916
  };
11708
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
+ }
11709
12028
  let retouchPr;
11710
12029
  try {
11711
12030
  retouchPr = await createOrReuseFinalReviewRetouchPr({
@@ -13895,6 +14214,37 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
13895
14214
  }
13896
14215
  return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
13897
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
+ }
13898
14248
  function validateReconciliationArtifact2(artifact, context) {
13899
14249
  const errors = [];
13900
14250
  if (!artifact.prdRef) {
@@ -14339,201 +14689,47 @@ async function runPrdRunLaunchCommand(options) {
14339
14689
  let finalReviewResult;
14340
14690
  if (!skipped.includes("final-review")) {
14341
14691
  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
14692
+ if (existingRecord.record?.mode === "local") {
14693
+ const runnableIssues = await getRunnableLocalIssues(
14694
+ prdRef,
14695
+ options.repoRoot
14386
14696
  );
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
- });
14697
+ if (runnableIssues.length > 0) {
14698
+ const reason = `Config-local Final Review blocked: Queue not drained (${runnableIssues.length} runnable Issues remain).`;
14434
14699
  return {
14435
14700
  prdRef,
14436
14701
  status: "blocked",
14437
14702
  attempted,
14438
- skipped: [
14439
- ...skipped.includes("prepare") ? ["prepare"] : [],
14440
- ...skipped.includes("start") ? ["queue"] : [],
14441
- "reconcile"
14442
- ],
14703
+ skipped: ["start", "queue", "reconcile"],
14443
14704
  resumed,
14444
14705
  diagnostics: [reason],
14445
14706
  blockedGate: "final-review",
14446
14707
  blockedReason: reason,
14447
14708
  offendingPaths: [],
14448
14709
  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, {
14710
+ start: startResult,
14711
+ finalReview: {
14467
14712
  prdRef,
14468
14713
  status: "blocked",
14469
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14470
14714
  blockedGate: "final-review",
14471
14715
  blockedReason: reason,
14472
14716
  diagnostics: [reason],
14473
- targetName: options.targetName,
14474
14717
  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
- }
14718
+ }
14719
+ };
14494
14720
  }
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
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
+ });
14537
14733
  const skippedAfterFr = [
14538
14734
  ...skipped.includes("prepare") ? ["prepare"] : [],
14539
14735
  ...skipped.includes("start") ? ["queue"] : [],
@@ -14603,6 +14799,28 @@ async function runPrdRunLaunchCommand(options) {
14603
14799
  finalReview: finalReviewResult
14604
14800
  };
14605
14801
  }
14802
+ if (prdRecord.record?.mode === "local") {
14803
+ const dualCheck = await verifyLocalDualFinalReviewReceipts(
14804
+ options.repoRoot,
14805
+ prdRef
14806
+ );
14807
+ if (!dualCheck.ok) {
14808
+ return {
14809
+ prdRef,
14810
+ status: "blocked",
14811
+ attempted,
14812
+ skipped: [...skipped],
14813
+ resumed,
14814
+ diagnostics: dualCheck.diagnostics,
14815
+ blockedGate: "reconciliation",
14816
+ blockedReason: dualCheck.reason,
14817
+ offendingPaths: [],
14818
+ prepare: prepareResult,
14819
+ start: startResult,
14820
+ finalReview: finalReviewResult
14821
+ };
14822
+ }
14823
+ }
14606
14824
  const localResult = await runLocalReconciliation(prdRef, {
14607
14825
  repoRoot: options.repoRoot
14608
14826
  });
@@ -21169,11 +21387,11 @@ function createCliProgram(version) {
21169
21387
  return program;
21170
21388
  }
21171
21389
  async function resolveCliVersion() {
21172
- if (isPackageVersion("0.0.0-next-20260608221925")) {
21173
- return "0.0.0-next-20260608221925";
21390
+ if (isPackageVersion("0.0.0-next-20260609130908")) {
21391
+ return "0.0.0-next-20260609130908";
21174
21392
  }
21175
- if (isReleaseVersion("0.0.0-next-20260608221925")) {
21176
- return "0.0.0-next-20260608221925";
21393
+ if (isReleaseVersion("0.0.0-next-20260609130908")) {
21394
+ return "0.0.0-next-20260609130908";
21177
21395
  }
21178
21396
  try {
21179
21397
  const root = repoRoot();