@pourkit/cli 0.0.0-next-20260611235559 → 0.0.0-next-20260613012953

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
@@ -1041,7 +1041,7 @@ async function cleanupRepository(options) {
1041
1041
  // commands/artifact-validation.ts
1042
1042
  import { createHash } from "crypto";
1043
1043
  import { execSync } from "child_process";
1044
- import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
1044
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
1045
1045
  import { isAbsolute, join as join6, resolve } from "path";
1046
1046
 
1047
1047
  // pr/review-verdict.ts
@@ -2938,6 +2938,194 @@ function parseConflictResolutionArtifact(output) {
2938
2938
  };
2939
2939
  }
2940
2940
 
2941
+ // prd-run/final-review-validation.ts
2942
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
2943
+ function parseFinalReviewArtifact(artifactPath) {
2944
+ if (!existsSync5(artifactPath)) {
2945
+ return {
2946
+ ok: false,
2947
+ reason: "Final Review artifact not found.",
2948
+ diagnostics: [`Artifact path: ${artifactPath}`]
2949
+ };
2950
+ }
2951
+ let content;
2952
+ try {
2953
+ content = readFileSync5(artifactPath, "utf-8");
2954
+ } catch (error) {
2955
+ return {
2956
+ ok: false,
2957
+ reason: "Final Review artifact could not be read.",
2958
+ diagnostics: [
2959
+ error instanceof Error ? error.message : String(error),
2960
+ `Artifact path: ${artifactPath}`
2961
+ ]
2962
+ };
2963
+ }
2964
+ let parsed;
2965
+ try {
2966
+ parsed = JSON.parse(content);
2967
+ } catch {
2968
+ return {
2969
+ ok: false,
2970
+ reason: "Final Review artifact is not valid JSON.",
2971
+ diagnostics: [`Artifact path: ${artifactPath}`]
2972
+ };
2973
+ }
2974
+ const verdict = parsed.verdict;
2975
+ if (!verdict || typeof verdict !== "string") {
2976
+ return {
2977
+ ok: false,
2978
+ reason: "Final Review artifact is missing a verdict field.",
2979
+ diagnostics: [`Artifact content preview: ${content.slice(0, 300)}`]
2980
+ };
2981
+ }
2982
+ const allowedVerdicts = [
2983
+ "pass_no_changes",
2984
+ "pass_with_retouch",
2985
+ "needs_human_review",
2986
+ "blocked"
2987
+ ];
2988
+ if (!allowedVerdicts.includes(verdict)) {
2989
+ return {
2990
+ ok: false,
2991
+ reason: `Final Review artifact has unsupported verdict "${verdict}".`,
2992
+ diagnostics: [`Allowed verdicts: ${allowedVerdicts.join(", ")}`]
2993
+ };
2994
+ }
2995
+ const summary = typeof parsed.summary === "string" ? parsed.summary : typeof parsed.reason === "string" ? parsed.reason : String(verdict);
2996
+ const diagnostics = Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [];
2997
+ const changedPaths = Array.isArray(parsed.changedPaths) ? parsed.changedPaths : void 0;
2998
+ const isAutoSummary = !parsed.summary && !parsed.reason;
2999
+ if (verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && isAutoSummary) {
3000
+ return {
3001
+ ok: false,
3002
+ reason: 'Final Review artifact with "pass_with_retouch" verdict requires changed paths.',
3003
+ diagnostics: [
3004
+ "pass_with_retouch verdict must include a changedPaths array in the artifact or provide a summary/reason with enough context for git diff fallback.",
3005
+ "Provide changedPaths or a descriptive summary in the artifact."
3006
+ ]
3007
+ };
3008
+ }
3009
+ const prdRef = typeof parsed.prdRef === "string" && parsed.prdRef.trim() ? parsed.prdRef.trim() : void 0;
3010
+ const stage = typeof parsed.stage === "string" && parsed.stage.trim() ? parsed.stage.trim() : void 0;
3011
+ const checkoutBase = typeof parsed.checkoutBase === "string" && parsed.checkoutBase.trim() ? parsed.checkoutBase.trim() : void 0;
3012
+ const reviewBase = typeof parsed.reviewBase === "string" && parsed.reviewBase.trim() ? parsed.reviewBase.trim() : void 0;
3013
+ return {
3014
+ ok: true,
3015
+ verdict,
3016
+ summary,
3017
+ diagnostics,
3018
+ changedPaths,
3019
+ prdRef,
3020
+ stage,
3021
+ checkoutBase,
3022
+ reviewBase
3023
+ };
3024
+ }
3025
+ function validateFinalReviewArtifactSemanticIds(artifact, context) {
3026
+ const errors = [];
3027
+ if (!artifact.prdRef) {
3028
+ errors.push("Final Review artifact is missing prdRef.");
3029
+ } else if (artifact.prdRef !== context.prdRef) {
3030
+ errors.push(
3031
+ `Final Review artifact prdRef "${artifact.prdRef}" does not match active PRD Run "${context.prdRef}".`
3032
+ );
3033
+ }
3034
+ if (!artifact.stage) {
3035
+ errors.push("Final Review artifact is missing stage field.");
3036
+ } else if (artifact.stage !== "prdFinalReview") {
3037
+ errors.push(
3038
+ `Final Review artifact stage "${artifact.stage}" is not "prdFinalReview".`
3039
+ );
3040
+ }
3041
+ if (!artifact.checkoutBase) {
3042
+ errors.push("Final Review artifact is missing checkoutBase.");
3043
+ } else if (artifact.checkoutBase !== context.prdBranch) {
3044
+ errors.push(
3045
+ `Final Review artifact checkoutBase "${artifact.checkoutBase}" does not match active PRD Branch "${context.prdBranch}".`
3046
+ );
3047
+ }
3048
+ if (!artifact.reviewBase) {
3049
+ errors.push("Final Review artifact is missing reviewBase.");
3050
+ } else if (artifact.reviewBase !== context.mergeBase) {
3051
+ errors.push(
3052
+ `Final Review artifact reviewBase "${artifact.reviewBase}" does not match active merge base "${context.mergeBase}".`
3053
+ );
3054
+ }
3055
+ if (errors.length > 0) {
3056
+ return { ok: false, errors, blockedGate: "final-review" };
3057
+ }
3058
+ return { ok: true };
3059
+ }
3060
+ function validateFinalReviewRetouchScope(changedPaths) {
3061
+ if (!changedPaths || changedPaths.length === 0) {
3062
+ return {
3063
+ ok: false,
3064
+ reason: "Final Review retouch scope validation failed. No changed paths available. Provide changed paths or run Final Review with a summary that includes changed paths.",
3065
+ diagnostics: ["Changed paths list is empty or undefined."],
3066
+ offendingPaths: []
3067
+ };
3068
+ }
3069
+ const offendingPaths = changedPaths.filter((p) => !isRetouchScopePath(p));
3070
+ const allowedPaths = changedPaths.filter((p) => isRetouchScopePath(p));
3071
+ if (allowedPaths.length === 0) {
3072
+ return {
3073
+ ok: false,
3074
+ reason: "Final Review retouch scope validation failed. No implementation, test, or Changeset paths found for retouch.",
3075
+ diagnostics: [
3076
+ "All changed paths are prohibited for retouch.",
3077
+ ...changedPaths.map((p) => ` - ${p}`)
3078
+ ],
3079
+ offendingPaths
3080
+ };
3081
+ }
3082
+ if (offendingPaths.length > 0) {
3083
+ return {
3084
+ ok: false,
3085
+ reason: "Final Review retouch scope validation failed. Prohibited paths cannot be included in retouch PR.",
3086
+ diagnostics: [
3087
+ "Prohibited paths found:",
3088
+ ...offendingPaths.map((p) => ` - ${p}`)
3089
+ ],
3090
+ offendingPaths
3091
+ };
3092
+ }
3093
+ return { ok: true, changedPaths };
3094
+ }
3095
+ function validateFinalReviewAgentOutputs(options) {
3096
+ const artifact = parseFinalReviewArtifact(options.artifactPath);
3097
+ if (!artifact.ok) {
3098
+ return { ...artifact, offendingPaths: [] };
3099
+ }
3100
+ const semanticResult = validateFinalReviewArtifactSemanticIds(
3101
+ artifact,
3102
+ options.context
3103
+ );
3104
+ if (!semanticResult.ok) {
3105
+ return {
3106
+ ok: false,
3107
+ reason: "Final Review artifact has mismatched semantic IDs.",
3108
+ diagnostics: semanticResult.errors,
3109
+ offendingPaths: []
3110
+ };
3111
+ }
3112
+ if (artifact.verdict !== "pass_with_retouch") {
3113
+ return { ok: true, artifact };
3114
+ }
3115
+ const changedPaths = options.changedPaths && options.changedPaths.length > 0 ? options.changedPaths : artifact.changedPaths;
3116
+ const retouch = validateFinalReviewRetouchScope(changedPaths ?? []);
3117
+ if (!retouch.ok) {
3118
+ return retouch;
3119
+ }
3120
+ return { ok: true, artifact, retouch };
3121
+ }
3122
+ function isRetouchScopePath(path9) {
3123
+ if (path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/adr/") || /^\.pourkit\/prd-runs\/[^/]+\.json$/.test(path9)) {
3124
+ return false;
3125
+ }
3126
+ return true;
3127
+ }
3128
+
2941
3129
  // commands/artifact-validation.ts
2942
3130
  var CONFLICT_MARKER_PATTERN = /<<<<<<<|=======|>>>>>>>/m;
2943
3131
  function invalid(options, reason, diagnostics = []) {
@@ -2958,14 +3146,14 @@ function valid(options, diagnostics = []) {
2958
3146
  };
2959
3147
  }
2960
3148
  function readArtifact(options) {
2961
- if (!existsSync5(options.artifactPath)) {
3149
+ if (!existsSync6(options.artifactPath)) {
2962
3150
  return {
2963
3151
  ok: false,
2964
3152
  result: invalid(options, `Artifact missing at ${options.artifactPath}`)
2965
3153
  };
2966
3154
  }
2967
3155
  try {
2968
- const content = readFileSync5(options.artifactPath, "utf-8");
3156
+ const content = readFileSync6(options.artifactPath, "utf-8");
2969
3157
  if (!content.trim()) {
2970
3158
  return { ok: false, result: invalid(options, "Artifact is empty") };
2971
3159
  }
@@ -2998,7 +3186,7 @@ function validateAgentArtifact(options) {
2998
3186
  case "refactor": {
2999
3187
  let findingIds = options.findingIds ?? [];
3000
3188
  if (findingIds.length === 0 && options.latestReviewArtifactPath) {
3001
- const latestReview = readFileSync5(
3189
+ const latestReview = readFileSync6(
3002
3190
  options.latestReviewArtifactPath,
3003
3191
  "utf-8"
3004
3192
  );
@@ -3024,7 +3212,7 @@ function validateAgentArtifact(options) {
3024
3212
  const filePath = resolve(base, file);
3025
3213
  try {
3026
3214
  return CONFLICT_MARKER_PATTERN.test(
3027
- readFileSync5(filePath, "utf-8")
3215
+ readFileSync6(filePath, "utf-8")
3028
3216
  );
3029
3217
  } catch {
3030
3218
  return false;
@@ -3040,6 +3228,26 @@ function validateAgentArtifact(options) {
3040
3228
  }
3041
3229
  return valid(options, [`status: ${parsed.status}`]);
3042
3230
  }
3231
+ case "final-review": {
3232
+ if (!options.prdRef || !options.checkoutBase || !options.reviewBase) {
3233
+ return invalid(
3234
+ options,
3235
+ "Final Review artifact validation requires --prd-ref, --checkout-base, and --review-base."
3236
+ );
3237
+ }
3238
+ const result = validateFinalReviewAgentOutputs({
3239
+ artifactPath: options.artifactPath,
3240
+ context: {
3241
+ prdRef: options.prdRef,
3242
+ prdBranch: options.checkoutBase,
3243
+ mergeBase: options.reviewBase
3244
+ }
3245
+ });
3246
+ if (!result.ok) {
3247
+ return invalid(options, result.reason, result.diagnostics);
3248
+ }
3249
+ return valid(options, [`verdict: ${result.artifact.verdict}`]);
3250
+ }
3043
3251
  case "failure-resolution": {
3044
3252
  const parsed = parseRecoveryArtifact(
3045
3253
  artifact.content,
@@ -3145,7 +3353,7 @@ function runLocalValidateArtifactCommand(options) {
3145
3353
  }
3146
3354
  function readLocalArtifact(path9, failureCode) {
3147
3355
  try {
3148
- const raw = readFileSync5(path9, "utf-8");
3356
+ const raw = readFileSync6(path9, "utf-8");
3149
3357
  const parsed = JSON.parse(raw);
3150
3358
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
3151
3359
  return {
@@ -3844,7 +4052,7 @@ function validateLocalTriage(prdPath, issuePaths) {
3844
4052
  }
3845
4053
 
3846
4054
  // commands/issue-run.ts
3847
- import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
4055
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
3848
4056
  import { join as join13 } from "path";
3849
4057
 
3850
4058
  // pr/templates.ts
@@ -3980,7 +4188,7 @@ function runEffectAndMapExit(program) {
3980
4188
  }
3981
4189
 
3982
4190
  // shared/attempt-log.ts
3983
- import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6 } from "fs";
4191
+ import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7 } from "fs";
3984
4192
  import { dirname as dirname3, join as join7 } from "path";
3985
4193
  var ATTEMPT_LOG_PATH = ".pourkit/attempt-log.jsonl";
3986
4194
  function writeAttemptLog(worktreePath, entry) {
@@ -3990,10 +4198,10 @@ function writeAttemptLog(worktreePath, entry) {
3990
4198
  }
3991
4199
  function readAttemptLog(worktreePath) {
3992
4200
  const logPath = join7(worktreePath, ATTEMPT_LOG_PATH);
3993
- if (!existsSync6(logPath)) {
4201
+ if (!existsSync7(logPath)) {
3994
4202
  return [];
3995
4203
  }
3996
- const raw = readFileSync6(logPath, "utf-8");
4204
+ const raw = readFileSync7(logPath, "utf-8");
3997
4205
  const lines = raw.split("\n").filter((l) => l.length > 0);
3998
4206
  const entries = [];
3999
4207
  for (const line of lines) {
@@ -4112,7 +4320,7 @@ async function runBaseRefreshAttempt(options) {
4112
4320
  }
4113
4321
 
4114
4322
  // commands/conflict-resolution.ts
4115
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
4323
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
4116
4324
  import { join as join8 } from "path";
4117
4325
  init_common();
4118
4326
  var CONFLICT_MARKER_PATTERN2 = /<<<<<<<|=======|>>>>>>>/m;
@@ -4120,7 +4328,7 @@ async function hasUnresolvedConflictMarkers(worktreePath, files) {
4120
4328
  for (const file of files) {
4121
4329
  const filePath = join8(worktreePath, file);
4122
4330
  try {
4123
- const content = readFileSync7(filePath, "utf-8");
4331
+ const content = readFileSync8(filePath, "utf-8");
4124
4332
  if (CONFLICT_MARKER_PATTERN2.test(content)) {
4125
4333
  return true;
4126
4334
  }
@@ -4451,7 +4659,7 @@ async function prepareSerenaForTarget(options) {
4451
4659
  }
4452
4660
 
4453
4661
  // failure-resolution/failure-resolution-agent.ts
4454
- import { readFileSync as readFileSync8 } from "fs";
4662
+ import { readFileSync as readFileSync9 } from "fs";
4455
4663
  import { join as join9 } from "path";
4456
4664
 
4457
4665
  // failure-resolution/recovery-policy.ts
@@ -4605,7 +4813,7 @@ async function runFailureResolutionAgent(options) {
4605
4813
  }
4606
4814
  let artifact;
4607
4815
  try {
4608
- const md = readFileSync8(fullArtifactPath, "utf-8");
4816
+ const md = readFileSync9(fullArtifactPath, "utf-8");
4609
4817
  const validation2 = validateAgentArtifact({
4610
4818
  kind: "failure-resolution",
4611
4819
  artifactPath: fullArtifactPath,
@@ -4699,7 +4907,7 @@ async function writeRecoveryAttempt(worktreePath, outcome, fingerprint, summary,
4699
4907
 
4700
4908
  // commands/pr-description-agent.ts
4701
4909
  import { join as join11 } from "path";
4702
- import { readFileSync as readFileSync9 } from "fs";
4910
+ import { readFileSync as readFileSync10 } from "fs";
4703
4911
 
4704
4912
  // pr/pr-description-context.ts
4705
4913
  init_common();
@@ -4893,7 +5101,7 @@ function runFinalizerAgent(options) {
4893
5101
  ],
4894
5102
  logger: options.logger
4895
5103
  });
4896
- const output = readFileSync9(artifactPath, "utf-8");
5104
+ const output = readFileSync10(artifactPath, "utf-8");
4897
5105
  yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
4898
5106
  return result;
4899
5107
  });
@@ -5021,7 +5229,7 @@ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
5021
5229
 
5022
5230
  // prd-run/local-merge-coordinator.ts
5023
5231
  import { execFileSync as execFileSync2 } from "child_process";
5024
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
5232
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
5025
5233
  import { join as join12 } from "path";
5026
5234
 
5027
5235
  // prd-run/local-branches.ts
@@ -5122,9 +5330,9 @@ function getIssueArtifactPath(repoRoot2, prdId, issueId) {
5122
5330
  }
5123
5331
  function readIssueBranchName(repoRoot2, prdId, issueId) {
5124
5332
  const issuePath = getIssueArtifactPath(repoRoot2, prdId, issueId);
5125
- if (!existsSync8(issuePath)) return null;
5333
+ if (!existsSync9(issuePath)) return null;
5126
5334
  try {
5127
- const content = readFileSync10(issuePath, "utf-8");
5335
+ const content = readFileSync11(issuePath, "utf-8");
5128
5336
  const parsed = JSON.parse(content);
5129
5337
  return typeof parsed.branchName === "string" && parsed.branchName ? parsed.branchName : null;
5130
5338
  } catch {
@@ -5134,9 +5342,9 @@ function readIssueBranchName(repoRoot2, prdId, issueId) {
5134
5342
  async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
5135
5343
  const root = repoRoot2 ?? process.cwd();
5136
5344
  const receiptPath = getMergeReceiptPath(root, prdId, issueId);
5137
- if (!existsSync8(receiptPath)) return null;
5345
+ if (!existsSync9(receiptPath)) return null;
5138
5346
  try {
5139
- const content = readFileSync10(receiptPath, "utf-8");
5347
+ const content = readFileSync11(receiptPath, "utf-8");
5140
5348
  const parsed = JSON.parse(content);
5141
5349
  if (typeof parsed.prdId === "string" && typeof parsed.issueId === "string" && typeof parsed.stage === "string" && typeof parsed.sourceBranch === "string" && typeof parsed.localPrdBranch === "string" && typeof parsed.mergeCommit === "string" && typeof parsed.completedAt === "string") {
5142
5350
  return parsed;
@@ -5149,10 +5357,10 @@ async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
5149
5357
  async function squashMergeLocalIssue(prdId, issueId, input, repoRoot2) {
5150
5358
  const root = repoRoot2 ?? process.cwd();
5151
5359
  const receiptPath = getMergeReceiptPath(root, prdId, issueId);
5152
- if (existsSync8(receiptPath)) {
5360
+ if (existsSync9(receiptPath)) {
5153
5361
  try {
5154
5362
  const existing = JSON.parse(
5155
- readFileSync10(receiptPath, "utf-8")
5363
+ readFileSync11(receiptPath, "utf-8")
5156
5364
  );
5157
5365
  if (existing.prdId === prdId && existing.issueId === issueId && existing.mergeCommit) {
5158
5366
  return {
@@ -6047,12 +6255,12 @@ async function completeIssueRun(options) {
6047
6255
  prTitle = finalizerFromState.title;
6048
6256
  prBody = finalizerFromState.body;
6049
6257
  } else if (finalizerFromState.artifactPath) {
6050
- if (!existsSync9(finalizerFromState.artifactPath)) {
6258
+ if (!existsSync10(finalizerFromState.artifactPath)) {
6051
6259
  throw new FinalizerFailure({
6052
6260
  message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
6053
6261
  });
6054
6262
  }
6055
- const artifactContent = readFileSync11(
6263
+ const artifactContent = readFileSync12(
6056
6264
  finalizerFromState.artifactPath,
6057
6265
  "utf-8"
6058
6266
  );
@@ -6749,7 +6957,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
6749
6957
  }
6750
6958
  function loadBuilderPrompt(repoRoot2, promptTemplate) {
6751
6959
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
6752
- const promptBody = existsSync9(promptPath) ? readFileSync11(promptPath, "utf-8") : promptTemplate;
6960
+ const promptBody = existsSync10(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
6753
6961
  return appendProtectedWorkGuidance(`${promptBody}
6754
6962
 
6755
6963
  ## Shared Run Context
@@ -7207,9 +7415,9 @@ import { Match, pipe } from "effect";
7207
7415
 
7208
7416
  // prd-run/state.ts
7209
7417
  import {
7210
- existsSync as existsSync10,
7418
+ existsSync as existsSync11,
7211
7419
  mkdirSync as mkdirSync8,
7212
- readFileSync as readFileSync12,
7420
+ readFileSync as readFileSync13,
7213
7421
  readdirSync as readdirSync3,
7214
7422
  writeFileSync as writeFileSync5
7215
7423
  } from "fs";
@@ -7245,6 +7453,7 @@ var PrdRunRecordSchema = z2.object({
7245
7453
  status: z2.enum(["started", "succeeded", "adopted", "reused"]),
7246
7454
  targetName: z2.string().min(1),
7247
7455
  prdBranch: z2.string().min(1),
7456
+ startBaseBranch: z2.string().min(1),
7248
7457
  startBaseCommit: z2.string().min(1).optional(),
7249
7458
  branchAction: z2.enum(["created", "reused", "adopted"]).optional(),
7250
7459
  startedAt: z2.string().min(1).optional(),
@@ -7303,15 +7512,26 @@ function normalizePrdRunRef(ref) {
7303
7512
  function readPrdRun(repoRoot2, prdRef) {
7304
7513
  const normalized = normalizePrdRunRef(prdRef);
7305
7514
  const recordPath = getRecordPath(repoRoot2, normalized);
7306
- if (!existsSync10(recordPath)) {
7515
+ if (!existsSync11(recordPath)) {
7307
7516
  return { record: null, diagnostics: [] };
7308
7517
  }
7309
7518
  try {
7310
- const parsed = PrdRunRecordSchema.parse(
7311
- JSON.parse(readFileSync12(recordPath, "utf-8"))
7312
- );
7519
+ const raw = JSON.parse(readFileSync13(recordPath, "utf-8"));
7520
+ const parsed = PrdRunRecordSchema.parse(raw);
7313
7521
  return { record: parsed, diagnostics: [] };
7314
7522
  } catch (error) {
7523
+ try {
7524
+ const raw = JSON.parse(readFileSync13(recordPath, "utf-8"));
7525
+ if (raw && typeof raw === "object" && raw.start && typeof raw.start === "object" && raw.start.startBaseBranch === void 0) {
7526
+ return {
7527
+ record: raw,
7528
+ diagnostics: [
7529
+ `Legacy PRD Run record at ${recordPath} is missing startBaseBranch.`
7530
+ ]
7531
+ };
7532
+ }
7533
+ } catch {
7534
+ }
7315
7535
  return {
7316
7536
  record: null,
7317
7537
  diagnostics: [
@@ -7323,7 +7543,7 @@ function readPrdRun(repoRoot2, prdRef) {
7323
7543
  }
7324
7544
  function listPrdRuns(repoRoot2) {
7325
7545
  const stateDir = join14(repoRoot2, PRD_RUN_STATE_DIR);
7326
- if (!existsSync10(stateDir)) {
7546
+ if (!existsSync11(stateDir)) {
7327
7547
  return { records: [], diagnostics: [] };
7328
7548
  }
7329
7549
  const records = [];
@@ -7335,7 +7555,7 @@ function listPrdRuns(repoRoot2) {
7335
7555
  const recordPath = join14(stateDir, entry.name);
7336
7556
  try {
7337
7557
  const record = PrdRunRecordSchema.parse(
7338
- JSON.parse(readFileSync12(recordPath, "utf-8"))
7558
+ JSON.parse(readFileSync13(recordPath, "utf-8"))
7339
7559
  );
7340
7560
  records.push(record);
7341
7561
  } catch (error) {
@@ -7403,7 +7623,7 @@ function getLocalStorePath2(repoRoot2, prdId) {
7403
7623
  async function readLocalPrdRun(repoRoot2, prdId) {
7404
7624
  const normalized = normalizePrdRunRef(prdId);
7405
7625
  const recordPath = getLocalStorePath2(repoRoot2, normalized);
7406
- if (!existsSync10(recordPath)) {
7626
+ if (!existsSync11(recordPath)) {
7407
7627
  return null;
7408
7628
  }
7409
7629
  try {
@@ -7512,191 +7732,30 @@ function buildEvidencePacket(input) {
7512
7732
  prdBranch: input.prdBranch.trim(),
7513
7733
  mergeBase,
7514
7734
  planningManifestPath: input.planningManifestPath.trim(),
7515
- planningManifestFacts: {
7516
- parentPrdIssueUrl: input.planningManifestFacts.parentPrdIssueUrl.trim(),
7517
- childIssueCount: input.planningManifestFacts.childIssueCount
7518
- },
7519
- stage: stageResult.data,
7520
- stageReceipts: input.stageReceipts
7521
- };
7522
- return packet;
7523
- }
7524
- var TOKEN_LIKE_PATTERNS = [
7525
- /gh[ps]_[A-Za-z0-9]{20,}/g,
7526
- /token[=:_\s][A-Za-z0-9_-]{20,}/gi,
7527
- /secret[=:_\s][A-Za-z0-9_-]{20,}/gi,
7528
- /credential[=:_\s][A-Za-z0-9_-]{20,}/gi,
7529
- /key[=:_\s][A-Za-z0-9_-]{20,}/gi,
7530
- /-----BEGIN (RSA |EC )?PRIVATE KEY-----/g,
7531
- /xox[baprs]-[A-Za-z0-9_-]{10,}/g
7532
- ];
7533
- function redactSensitiveValues(input) {
7534
- let redacted = input;
7535
- for (const pattern of TOKEN_LIKE_PATTERNS) {
7536
- redacted = redacted.replace(pattern, "[REDACTED]");
7537
- }
7538
- return redacted;
7539
- }
7540
-
7541
- // prd-run/final-review-validation.ts
7542
- import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
7543
- function parseFinalReviewArtifact(artifactPath) {
7544
- if (!existsSync11(artifactPath)) {
7545
- return {
7546
- ok: false,
7547
- reason: "Final Review artifact not found.",
7548
- diagnostics: [`Artifact path: ${artifactPath}`]
7549
- };
7550
- }
7551
- let content;
7552
- try {
7553
- content = readFileSync13(artifactPath, "utf-8");
7554
- } catch (error) {
7555
- return {
7556
- ok: false,
7557
- reason: "Final Review artifact could not be read.",
7558
- diagnostics: [
7559
- error instanceof Error ? error.message : String(error),
7560
- `Artifact path: ${artifactPath}`
7561
- ]
7562
- };
7563
- }
7564
- let parsed;
7565
- try {
7566
- parsed = JSON.parse(content);
7567
- } catch {
7568
- return {
7569
- ok: false,
7570
- reason: "Final Review artifact is not valid JSON.",
7571
- diagnostics: [`Artifact path: ${artifactPath}`]
7572
- };
7573
- }
7574
- const verdict = parsed.verdict;
7575
- if (!verdict || typeof verdict !== "string") {
7576
- return {
7577
- ok: false,
7578
- reason: "Final Review artifact is missing a verdict field.",
7579
- diagnostics: [`Artifact content preview: ${content.slice(0, 300)}`]
7580
- };
7581
- }
7582
- const allowedVerdicts = [
7583
- "pass_no_changes",
7584
- "pass_with_retouch",
7585
- "needs_human_review",
7586
- "blocked"
7587
- ];
7588
- if (!allowedVerdicts.includes(verdict)) {
7589
- return {
7590
- ok: false,
7591
- reason: `Final Review artifact has unsupported verdict "${verdict}".`,
7592
- diagnostics: [`Allowed verdicts: ${allowedVerdicts.join(", ")}`]
7593
- };
7594
- }
7595
- const summary = typeof parsed.summary === "string" ? parsed.summary : typeof parsed.reason === "string" ? parsed.reason : String(verdict);
7596
- const diagnostics = Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [];
7597
- const changedPaths = Array.isArray(parsed.changedPaths) ? parsed.changedPaths : void 0;
7598
- const isAutoSummary = !parsed.summary && !parsed.reason;
7599
- if (verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && isAutoSummary) {
7600
- return {
7601
- ok: false,
7602
- reason: 'Final Review artifact with "pass_with_retouch" verdict requires changed paths.',
7603
- diagnostics: [
7604
- "pass_with_retouch verdict must include a changedPaths array in the artifact or provide a summary/reason with enough context for git diff fallback.",
7605
- "Provide changedPaths or a descriptive summary in the artifact."
7606
- ]
7607
- };
7608
- }
7609
- const prdRef = typeof parsed.prdRef === "string" && parsed.prdRef.trim() ? parsed.prdRef.trim() : void 0;
7610
- const stage = typeof parsed.stage === "string" && parsed.stage.trim() ? parsed.stage.trim() : void 0;
7611
- const checkoutBase = typeof parsed.checkoutBase === "string" && parsed.checkoutBase.trim() ? parsed.checkoutBase.trim() : void 0;
7612
- const reviewBase = typeof parsed.reviewBase === "string" && parsed.reviewBase.trim() ? parsed.reviewBase.trim() : void 0;
7613
- return {
7614
- ok: true,
7615
- verdict,
7616
- summary,
7617
- diagnostics,
7618
- changedPaths,
7619
- prdRef,
7620
- stage,
7621
- checkoutBase,
7622
- reviewBase
7623
- };
7624
- }
7625
- function validateFinalReviewArtifactSemanticIds(artifact, context) {
7626
- const errors = [];
7627
- if (!artifact.prdRef) {
7628
- errors.push("Final Review artifact is missing prdRef.");
7629
- } else if (artifact.prdRef !== context.prdRef) {
7630
- errors.push(
7631
- `Final Review artifact prdRef "${artifact.prdRef}" does not match active PRD Run "${context.prdRef}".`
7632
- );
7633
- }
7634
- if (!artifact.stage) {
7635
- errors.push("Final Review artifact is missing stage field.");
7636
- } else if (artifact.stage !== "prdFinalReview") {
7637
- errors.push(
7638
- `Final Review artifact stage "${artifact.stage}" is not "prdFinalReview".`
7639
- );
7640
- }
7641
- if (!artifact.checkoutBase) {
7642
- errors.push("Final Review artifact is missing checkoutBase.");
7643
- } else if (artifact.checkoutBase !== context.prdBranch) {
7644
- errors.push(
7645
- `Final Review artifact checkoutBase "${artifact.checkoutBase}" does not match active PRD Branch "${context.prdBranch}".`
7646
- );
7647
- }
7648
- if (!artifact.reviewBase) {
7649
- errors.push("Final Review artifact is missing reviewBase.");
7650
- } else if (artifact.reviewBase !== context.mergeBase) {
7651
- errors.push(
7652
- `Final Review artifact reviewBase "${artifact.reviewBase}" does not match active merge base "${context.mergeBase}".`
7653
- );
7654
- }
7655
- if (errors.length > 0) {
7656
- return { ok: false, errors, blockedGate: "final-review" };
7657
- }
7658
- return { ok: true };
7659
- }
7660
- function validateFinalReviewRetouchScope(changedPaths) {
7661
- if (!changedPaths || changedPaths.length === 0) {
7662
- return {
7663
- ok: false,
7664
- reason: "Final Review retouch scope validation failed. No changed paths available. Provide changed paths or run Final Review with a summary that includes changed paths.",
7665
- diagnostics: ["Changed paths list is empty or undefined."],
7666
- offendingPaths: []
7667
- };
7668
- }
7669
- const offendingPaths = changedPaths.filter((p) => !isRetouchScopePath(p));
7670
- const allowedPaths = changedPaths.filter((p) => isRetouchScopePath(p));
7671
- if (allowedPaths.length === 0) {
7672
- return {
7673
- ok: false,
7674
- reason: "Final Review retouch scope validation failed. No implementation, test, or Changeset paths found for retouch.",
7675
- diagnostics: [
7676
- "All changed paths are prohibited for retouch.",
7677
- ...changedPaths.map((p) => ` - ${p}`)
7678
- ],
7679
- offendingPaths
7680
- };
7681
- }
7682
- if (offendingPaths.length > 0) {
7683
- return {
7684
- ok: false,
7685
- reason: "Final Review retouch scope validation failed. Prohibited paths cannot be included in retouch PR.",
7686
- diagnostics: [
7687
- "Prohibited paths found:",
7688
- ...offendingPaths.map((p) => ` - ${p}`)
7689
- ],
7690
- offendingPaths
7691
- };
7692
- }
7693
- return { ok: true, changedPaths };
7735
+ planningManifestFacts: {
7736
+ parentPrdIssueUrl: input.planningManifestFacts.parentPrdIssueUrl.trim(),
7737
+ childIssueCount: input.planningManifestFacts.childIssueCount
7738
+ },
7739
+ stage: stageResult.data,
7740
+ stageReceipts: input.stageReceipts
7741
+ };
7742
+ return packet;
7694
7743
  }
7695
- function isRetouchScopePath(path9) {
7696
- if (path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/adr/") || /^\.pourkit\/prd-runs\/[^/]+\.json$/.test(path9)) {
7697
- return false;
7744
+ var TOKEN_LIKE_PATTERNS = [
7745
+ /gh[ps]_[A-Za-z0-9]{20,}/g,
7746
+ /token[=:_\s][A-Za-z0-9_-]{20,}/gi,
7747
+ /secret[=:_\s][A-Za-z0-9_-]{20,}/gi,
7748
+ /credential[=:_\s][A-Za-z0-9_-]{20,}/gi,
7749
+ /key[=:_\s][A-Za-z0-9_-]{20,}/gi,
7750
+ /-----BEGIN (RSA |EC )?PRIVATE KEY-----/g,
7751
+ /xox[baprs]-[A-Za-z0-9_-]{10,}/g
7752
+ ];
7753
+ function redactSensitiveValues(input) {
7754
+ let redacted = input;
7755
+ for (const pattern of TOKEN_LIKE_PATTERNS) {
7756
+ redacted = redacted.replace(pattern, "[REDACTED]");
7698
7757
  }
7699
- return true;
7758
+ return redacted;
7700
7759
  }
7701
7760
 
7702
7761
  // commands/prd-run.ts
@@ -8886,6 +8945,7 @@ function buildStartReceipt(options) {
8886
8945
  status: options.status,
8887
8946
  targetName: options.targetName,
8888
8947
  prdBranch: options.prdBranch,
8948
+ startBaseBranch: options.startBaseBranch,
8889
8949
  startBaseCommit: options.startBaseCommit,
8890
8950
  branchAction: options.branchAction,
8891
8951
  startedAt: options.startedAt,
@@ -8914,7 +8974,7 @@ function buildFinalReviewPrompt(options) {
8914
8974
  `Worktree checkout base: ${options.evidencePacket.prdBranch}`,
8915
8975
  `Review only this range: ${options.evidencePacket.mergeBase}..HEAD`,
8916
8976
  "Do not compare against current target branch HEAD; the merge base is the review baseline.",
8917
- `Before handoff, run: pourkit prd-run validate-final-review ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
8977
+ `Before handoff, run: pourkit validate-artifact final-review .pourkit/final-review-artifact.json --prd-ref ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
8918
8978
  "Fix any validation failures before handing off.",
8919
8979
  "",
8920
8980
  "## Verification",
@@ -8941,40 +9001,6 @@ function finalReviewWorktreePath(repoRoot2, branchName) {
8941
9001
  branchName.replace(/\//g, "-")
8942
9002
  );
8943
9003
  }
8944
- function computeFinalReviewMergeBase(repoRoot2, prdRef) {
8945
- const result = spawnSync3(
8946
- "git",
8947
- ["merge-base", "origin/dev", `origin/${prdRef}`],
8948
- {
8949
- cwd: repoRoot2,
8950
- encoding: "utf8"
8951
- }
8952
- );
8953
- if (result.status !== 0) {
8954
- return {
8955
- ok: false,
8956
- gate: "final-review",
8957
- reason: `Unable to fetch refs and compute Final Review merge-base for ${prdRef}.`,
8958
- diagnostics: collectSpawnDiagnostics(result, "git merge-base failed")
8959
- };
8960
- }
8961
- return { ok: true, mergeBase: result.stdout.trim(), diagnostics: [] };
8962
- }
8963
- function computeLocalFinalReviewMergeBase(repoRoot2, localPrdBranch) {
8964
- const result = spawnSync3("git", ["merge-base", "HEAD", localPrdBranch], {
8965
- cwd: repoRoot2,
8966
- encoding: "utf8"
8967
- });
8968
- if (result.status !== 0) {
8969
- return {
8970
- ok: false,
8971
- gate: "final-review",
8972
- reason: `Unable to compute Final Review merge-base for ${localPrdBranch}.`,
8973
- diagnostics: collectSpawnDiagnostics(result, "git merge-base failed")
8974
- };
8975
- }
8976
- return { ok: true, mergeBase: result.stdout.trim(), diagnostics: [] };
8977
- }
8978
9004
  function collectSpawnDiagnostics(result, fallback) {
8979
9005
  const diagnostics = [];
8980
9006
  const stderr = result.stderr?.toString?.().trim();
@@ -9411,7 +9437,7 @@ async function runPrdRunFinalReviewCommand(options) {
9411
9437
  options.repoRoot,
9412
9438
  prdRef
9413
9439
  );
9414
- if (readDiagnostics.length > 0) {
9440
+ if (readDiagnostics.length > 0 && !record) {
9415
9441
  const reason = `PRD Run ${prdRef} has a malformed record and cannot proceed to Final Review.`;
9416
9442
  writePrdRunRecord(options.repoRoot, {
9417
9443
  prdRef,
@@ -9617,7 +9643,33 @@ async function runPrdRunFinalReviewCommand(options) {
9617
9643
  );
9618
9644
  } else {
9619
9645
  prdBranch = record.prdBranch ?? prdRef;
9620
- mergeBaseResult = computeFinalReviewMergeBase(options.repoRoot, prdRef);
9646
+ const startBaseBranch = record.start?.startBaseBranch;
9647
+ if (!startBaseBranch) {
9648
+ const reason = `PRD Run ${prdRef} has a start receipt without startBaseBranch. This record predates target-owned PRD Run base branches and cannot proceed to Final Review.`;
9649
+ writePrdRunRecord(options.repoRoot, {
9650
+ ...record,
9651
+ status: "blocked",
9652
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9653
+ blockedGate: "final-review",
9654
+ blockedReason: reason,
9655
+ diagnostics: [reason],
9656
+ targetName,
9657
+ offendingPaths: []
9658
+ });
9659
+ return {
9660
+ prdRef,
9661
+ status: "blocked",
9662
+ blockedGate: "final-review",
9663
+ blockedReason: reason,
9664
+ diagnostics: [reason],
9665
+ offendingPaths: []
9666
+ };
9667
+ }
9668
+ mergeBaseResult = computeFinalReviewMergeBase(
9669
+ options.repoRoot,
9670
+ prdRef,
9671
+ startBaseBranch
9672
+ );
9621
9673
  }
9622
9674
  if (!mergeBaseResult.ok) {
9623
9675
  writePrdRunRecord(options.repoRoot, {
@@ -9666,7 +9718,10 @@ async function runPrdRunFinalReviewCommand(options) {
9666
9718
  childIssueCount: relatedIssues.length
9667
9719
  },
9668
9720
  stage: "prdFinalReview",
9669
- stageReceipts: { mergeBase: mergeBaseResult.mergeBase }
9721
+ stageReceipts: {
9722
+ mergeBase: mergeBaseResult.mergeBase,
9723
+ startBaseBranch: record.start?.startBaseBranch
9724
+ }
9670
9725
  });
9671
9726
  const startReceipt = {
9672
9727
  status: "started",
@@ -10502,6 +10557,99 @@ async function runPrdRunFinalReviewCommand(options) {
10502
10557
  offendingPaths: []
10503
10558
  };
10504
10559
  }
10560
+ function computeFinalReviewMergeBase(repoRoot2, prdRef, baseBranch) {
10561
+ const normalized = normalizePrdRunRef(prdRef);
10562
+ const fetchResult = spawnSync3(
10563
+ "git",
10564
+ ["fetch", "origin", baseBranch, normalized],
10565
+ {
10566
+ cwd: repoRoot2,
10567
+ encoding: "utf8"
10568
+ }
10569
+ );
10570
+ const fetchDiagnostics = [];
10571
+ if (fetchResult.status !== 0) {
10572
+ const stderr = fetchResult.stderr?.toString?.() ?? "";
10573
+ const stdout = fetchResult.stdout?.toString?.() ?? "";
10574
+ if (stderr.trim()) fetchDiagnostics.push(stderr.trim());
10575
+ if (stdout.trim()) fetchDiagnostics.push(stdout.trim());
10576
+ return {
10577
+ ok: false,
10578
+ gate: "final-review",
10579
+ reason: `Final Review failed to fetch origin/${baseBranch} and origin/${normalized}.`,
10580
+ diagnostics: fetchDiagnostics.length > 0 ? fetchDiagnostics : ["git fetch failed"]
10581
+ };
10582
+ }
10583
+ if (fetchResult.stderr?.toString?.()?.trim()) {
10584
+ fetchDiagnostics.push(fetchResult.stderr.toString().trim());
10585
+ }
10586
+ const mergeBaseResult = spawnSync3(
10587
+ "git",
10588
+ ["merge-base", `origin/${baseBranch}`, `origin/${normalized}`],
10589
+ {
10590
+ cwd: repoRoot2,
10591
+ encoding: "utf8"
10592
+ }
10593
+ );
10594
+ const mergeBaseDiagnostics = [];
10595
+ if (mergeBaseResult.status !== 0) {
10596
+ if (mergeBaseResult.stderr?.toString?.()?.trim()) {
10597
+ mergeBaseDiagnostics.push(mergeBaseResult.stderr.toString().trim());
10598
+ }
10599
+ if (mergeBaseResult.stdout?.toString?.()?.trim()) {
10600
+ mergeBaseDiagnostics.push(mergeBaseResult.stdout.toString().trim());
10601
+ }
10602
+ return {
10603
+ ok: false,
10604
+ gate: "final-review",
10605
+ reason: `Final Review merge-base computation failed for origin/${baseBranch} and origin/${normalized}.`,
10606
+ diagnostics: mergeBaseDiagnostics.length > 0 ? mergeBaseDiagnostics : ["git merge-base returned non-zero exit status"]
10607
+ };
10608
+ }
10609
+ const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
10610
+ if (!mergeBase) {
10611
+ return {
10612
+ ok: false,
10613
+ gate: "final-review",
10614
+ reason: `Final Review merge-base returned empty result for origin/${baseBranch} and origin/${normalized}.`,
10615
+ diagnostics: [
10616
+ `merge-base stdout was empty for origin/${baseBranch}..origin/${normalized}`
10617
+ ]
10618
+ };
10619
+ }
10620
+ return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
10621
+ }
10622
+ function computeLocalFinalReviewMergeBase(repoRoot2, prdBranch) {
10623
+ const mergeBaseResult = spawnSync3("git", ["merge-base", "dev", prdBranch], {
10624
+ cwd: repoRoot2,
10625
+ encoding: "utf8"
10626
+ });
10627
+ if (mergeBaseResult.status !== 0) {
10628
+ const diagnostics = [];
10629
+ if (mergeBaseResult.stderr?.toString?.()?.trim()) {
10630
+ diagnostics.push(mergeBaseResult.stderr.toString().trim());
10631
+ }
10632
+ if (mergeBaseResult.stdout?.toString?.()?.trim()) {
10633
+ diagnostics.push(mergeBaseResult.stdout.toString().trim());
10634
+ }
10635
+ return {
10636
+ ok: false,
10637
+ gate: "final-review",
10638
+ reason: `Final Review merge-base computation failed for dev and ${prdBranch}.`,
10639
+ diagnostics: diagnostics.length > 0 ? diagnostics : ["git merge-base returned non-zero exit status"]
10640
+ };
10641
+ }
10642
+ const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
10643
+ if (!mergeBase) {
10644
+ return {
10645
+ ok: false,
10646
+ gate: "final-review",
10647
+ reason: `Final Review merge-base returned empty result for dev and ${prdBranch}.`,
10648
+ diagnostics: [`merge-base stdout was empty for dev..${prdBranch}`]
10649
+ };
10650
+ }
10651
+ return { ok: true, mergeBase, diagnostics: [] };
10652
+ }
10505
10653
  async function runPrdRunLaunchCommand(options) {
10506
10654
  const prdRef = normalizePrdRunRef(options.prdRef);
10507
10655
  const attempted = [];
@@ -10576,7 +10724,8 @@ async function runPrdRunLaunchCommand(options) {
10576
10724
  prProvider: options.prProvider,
10577
10725
  executionProvider: options.executionProvider,
10578
10726
  logger: options.logger,
10579
- adoptExistingBranch: options.adoptExistingBranch
10727
+ adoptExistingBranch: options.adoptExistingBranch,
10728
+ baseBranchOverride: options.baseBranchOverride
10580
10729
  });
10581
10730
  const skippedAfterStart = [
10582
10731
  "queue",
@@ -10738,7 +10887,7 @@ async function runPrdRunLaunchCommand(options) {
10738
10887
  skipped,
10739
10888
  resumed,
10740
10889
  diagnostics: [
10741
- "Integration Gate is not implemented. PRD Run is waiting for manual integration."
10890
+ `Integration Gate is not implemented. PRD Run is waiting for manual integration back to ${currentRecord?.start?.startBaseBranch ?? "(missing startBaseBranch)"}.`
10742
10891
  ],
10743
10892
  start: startResult,
10744
10893
  finalReview: finalReviewResult
@@ -10777,8 +10926,52 @@ async function runPrdRunStartCommand(options) {
10777
10926
  });
10778
10927
  return buildBlockedStartResult(prdRef, failure);
10779
10928
  }
10929
+ const resolvedTarget = options.config ? resolveTarget(options.config, targetName) : null;
10930
+ const targetBaseBranch = resolvedTarget?.baseBranch ?? "main";
10931
+ const baseResolution = resolvePrdRunBaseBranch({
10932
+ targetBaseBranch,
10933
+ baseBranchOverride: options.baseBranchOverride
10934
+ });
10935
+ if (!baseResolution.ok) {
10936
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, baseResolution, {
10937
+ targetName
10938
+ });
10939
+ return buildBlockedStartResult(prdRef, baseResolution);
10940
+ }
10780
10941
  if (existingRecord.record?.start) {
10781
- const fetchResult2 = fetchOriginDev(options.repoRoot);
10942
+ const recordedBaseBranch = existingRecord.record.start.startBaseBranch;
10943
+ if (!recordedBaseBranch) {
10944
+ const failure = {
10945
+ ok: false,
10946
+ gate: "branch-state",
10947
+ reason: `PRD Run ${prdRef} has a start receipt without startBaseBranch. Run prd-run start to create a fresh start receipt.`,
10948
+ diagnostics: [
10949
+ `Recorded start: ${JSON.stringify(existingRecord.record.start)}`
10950
+ ],
10951
+ offendingPaths: []
10952
+ };
10953
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
10954
+ targetName
10955
+ });
10956
+ return buildBlockedStartResult(prdRef, failure);
10957
+ }
10958
+ if (recordedBaseBranch !== baseResolution.baseBranch) {
10959
+ const failure = {
10960
+ ok: false,
10961
+ gate: "branch-state",
10962
+ reason: `PRD Run ${prdRef} start receipt records startBaseBranch "${recordedBaseBranch}" but current base resolution resolves to "${baseResolution.baseBranch}". Use --base with the matching value or create a fresh start receipt.`,
10963
+ diagnostics: [
10964
+ `Recorded startBaseBranch: ${recordedBaseBranch}`,
10965
+ `Current base resolution: ${baseResolution.baseBranch} (source: ${baseResolution.source})`
10966
+ ],
10967
+ offendingPaths: []
10968
+ };
10969
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
10970
+ targetName
10971
+ });
10972
+ return buildBlockedStartResult(prdRef, failure);
10973
+ }
10974
+ const fetchResult2 = fetchOriginBranch(options.repoRoot, recordedBaseBranch);
10782
10975
  if (!fetchResult2.ok) {
10783
10976
  persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, fetchResult2, {
10784
10977
  targetName
@@ -10831,7 +11024,7 @@ async function runPrdRunStartCommand(options) {
10831
11024
  `Existing remote branch: ${prdRef}`,
10832
11025
  `Recorded start branch: ${existingRecord.record.start.prdBranch}`,
10833
11026
  `Recorded start base commit: ${existingRecord.record.start.startBaseCommit}`,
10834
- `Current origin/dev head: ${fetchResult2.startBaseCommit}`
11027
+ `Current origin/${recordedBaseBranch} head: ${fetchResult2.startBaseCommit}`
10835
11028
  ],
10836
11029
  offendingPaths: []
10837
11030
  };
@@ -10852,15 +11045,41 @@ async function runPrdRunStartCommand(options) {
10852
11045
  );
10853
11046
  return buildBlockedStartResult(prdRef, fetchPrdResult);
10854
11047
  }
10855
- const adoptedStart = {
11048
+ const ancestryResult = spawnSync3(
11049
+ "git",
11050
+ [
11051
+ "merge-base",
11052
+ "--is-ancestor",
11053
+ `origin/${recordedBaseBranch}`,
11054
+ `origin/${prdRef}`
11055
+ ],
11056
+ { cwd: options.repoRoot, encoding: "utf8" }
11057
+ );
11058
+ if (ancestryResult.status !== 0) {
11059
+ const failure = {
11060
+ ok: false,
11061
+ gate: "branch-state",
11062
+ reason: `PRD Branch ${prdRef} base ancestry check failed: origin/${recordedBaseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
11063
+ diagnostics: [
11064
+ `Base branch: origin/${recordedBaseBranch}`,
11065
+ `PRD branch: origin/${prdRef}`
11066
+ ],
11067
+ offendingPaths: []
11068
+ };
11069
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
11070
+ targetName
11071
+ });
11072
+ return buildBlockedStartResult(prdRef, failure);
11073
+ }
11074
+ const adoptedStart = buildStartReceipt({
10856
11075
  status: "adopted",
10857
11076
  targetName,
10858
11077
  prdBranch: prdRef,
11078
+ startBaseBranch: recordedBaseBranch,
10859
11079
  startBaseCommit: fetchResult2.startBaseCommit,
10860
11080
  branchAction: "adopted",
10861
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
10862
- refreshReceipts: []
10863
- };
11081
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
11082
+ });
10864
11083
  persistStartingPrdRunRecord(
10865
11084
  options.repoRoot,
10866
11085
  prdRef,
@@ -10884,7 +11103,10 @@ async function runPrdRunStartCommand(options) {
10884
11103
  );
10885
11104
  }
10886
11105
  }
10887
- const fetchResult = fetchOriginDev(options.repoRoot);
11106
+ const fetchResult = fetchOriginBranch(
11107
+ options.repoRoot,
11108
+ baseResolution.baseBranch
11109
+ );
10888
11110
  if (!fetchResult.ok) {
10889
11111
  persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, fetchResult, {
10890
11112
  targetName
@@ -10928,6 +11150,7 @@ async function runPrdRunStartCommand(options) {
10928
11150
  status: "started",
10929
11151
  targetName,
10930
11152
  prdBranch: localBranchName,
11153
+ startBaseBranch: baseResolution.baseBranch,
10931
11154
  startBaseCommit: fetchResult.startBaseCommit,
10932
11155
  branchAction: localBranchResult.created ? "created" : "reused",
10933
11156
  startedAt: updatedAt2
@@ -10969,7 +11192,7 @@ async function runPrdRunStartCommand(options) {
10969
11192
  `Existing remote branch: ${prdRef}`,
10970
11193
  `Recorded start branch: ${existingRecord.record?.start?.prdBranch ?? "(missing)"}`,
10971
11194
  `Recorded start base commit: ${existingRecord.record?.start?.startBaseCommit ?? "(missing)"}`,
10972
- `Current origin/dev head: ${fetchResult.startBaseCommit}`
11195
+ `Current origin/${baseResolution.baseBranch} head: ${fetchResult.startBaseCommit}`
10973
11196
  ],
10974
11197
  offendingPaths: []
10975
11198
  };
@@ -10990,15 +11213,41 @@ async function runPrdRunStartCommand(options) {
10990
11213
  );
10991
11214
  return buildBlockedStartResult(prdRef, fetchPrdResult);
10992
11215
  }
10993
- const adoptedStart = {
11216
+ const ancestryResult = spawnSync3(
11217
+ "git",
11218
+ [
11219
+ "merge-base",
11220
+ "--is-ancestor",
11221
+ `origin/${baseResolution.baseBranch}`,
11222
+ `origin/${prdRef}`
11223
+ ],
11224
+ { cwd: options.repoRoot, encoding: "utf8" }
11225
+ );
11226
+ if (ancestryResult.status !== 0) {
11227
+ const failure = {
11228
+ ok: false,
11229
+ gate: "branch-state",
11230
+ reason: `PRD Branch ${prdRef} base ancestry check failed: origin/${baseResolution.baseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
11231
+ diagnostics: [
11232
+ `Base branch: origin/${baseResolution.baseBranch}`,
11233
+ `PRD branch: origin/${prdRef}`
11234
+ ],
11235
+ offendingPaths: []
11236
+ };
11237
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
11238
+ targetName
11239
+ });
11240
+ return buildBlockedStartResult(prdRef, failure);
11241
+ }
11242
+ const adoptedStart = buildStartReceipt({
10994
11243
  status: "adopted",
10995
11244
  targetName,
10996
11245
  prdBranch: prdRef,
11246
+ startBaseBranch: baseResolution.baseBranch,
10997
11247
  startBaseCommit: fetchResult.startBaseCommit,
10998
11248
  branchAction: "adopted",
10999
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
11000
- refreshReceipts: []
11001
- };
11249
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
11250
+ });
11002
11251
  persistStartingPrdRunRecord(
11003
11252
  options.repoRoot,
11004
11253
  prdRef,
@@ -11047,6 +11296,7 @@ async function runPrdRunStartCommand(options) {
11047
11296
  status: "started",
11048
11297
  targetName,
11049
11298
  prdBranch: prdRef,
11299
+ startBaseBranch: baseResolution.baseBranch,
11050
11300
  startBaseCommit: fetchResult.startBaseCommit,
11051
11301
  branchAction: "created",
11052
11302
  startedAt: updatedAt
@@ -11367,8 +11617,98 @@ function inspectRemotePrdBranch(repoRoot2, prdRef) {
11367
11617
  }
11368
11618
  return { ok: true, exists: true, headSha };
11369
11619
  }
11370
- function fetchOriginDev(repoRoot2) {
11371
- const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
11620
+ function resolvePrdRunBaseBranch(options) {
11621
+ const raw = options.baseBranchOverride ?? options.targetBaseBranch;
11622
+ const trimmed = raw.trim();
11623
+ if (!trimmed) {
11624
+ return {
11625
+ ok: false,
11626
+ gate: "branch-state",
11627
+ reason: `Invalid PRD Run base branch: value is empty after trimming.`,
11628
+ diagnostics: [`Provided branch value: "${raw}"`],
11629
+ offendingPaths: []
11630
+ };
11631
+ }
11632
+ if (trimmed.startsWith("origin/")) {
11633
+ return {
11634
+ ok: false,
11635
+ gate: "branch-state",
11636
+ reason: `Invalid PRD Run base branch: "${trimmed}" must not include "origin/" prefix. Use just the branch lane name (e.g. "dev" instead of "origin/dev").`,
11637
+ diagnostics: [`Provided branch value: "${trimmed}"`],
11638
+ offendingPaths: []
11639
+ };
11640
+ }
11641
+ if (/^(refs\/|heads\/|remotes\/|tags\/)/.test(trimmed)) {
11642
+ return {
11643
+ ok: false,
11644
+ gate: "branch-state",
11645
+ reason: `Invalid PRD Run base branch: "${trimmed}" must be a simple branch lane name, not a ref specification.`,
11646
+ diagnostics: [`Provided branch value: "${trimmed}"`],
11647
+ offendingPaths: []
11648
+ };
11649
+ }
11650
+ if (/^[0-9a-f]{7,40}$/i.test(trimmed)) {
11651
+ return {
11652
+ ok: false,
11653
+ gate: "branch-state",
11654
+ reason: `Invalid PRD Run base branch: value looks like a commit SHA rather than a branch lane name.`,
11655
+ diagnostics: [`Provided branch value: "${trimmed}"`],
11656
+ offendingPaths: []
11657
+ };
11658
+ }
11659
+ const segments = trimmed.split("/");
11660
+ for (const segment of segments) {
11661
+ if (segment === "." || segment === ".." || segment === "") {
11662
+ return {
11663
+ ok: false,
11664
+ gate: "branch-state",
11665
+ reason: `Invalid PRD Run base branch: "${trimmed}" must not contain ".", "..", or empty segments.`,
11666
+ diagnostics: [`Provided branch value: "${trimmed}"`],
11667
+ offendingPaths: []
11668
+ };
11669
+ }
11670
+ }
11671
+ if (/[~^:?*\[\\]/.test(trimmed)) {
11672
+ return {
11673
+ ok: false,
11674
+ gate: "branch-state",
11675
+ reason: `Invalid PRD Run base branch: "${trimmed}" contains invalid characters (~ ^ : ? * [ \\).`,
11676
+ diagnostics: [`Provided branch value: "${trimmed}"`],
11677
+ offendingPaths: []
11678
+ };
11679
+ }
11680
+ if (/[\x00-\x1f\x7f]/.test(trimmed)) {
11681
+ return {
11682
+ ok: false,
11683
+ gate: "branch-state",
11684
+ reason: `Invalid PRD Run base branch: value contains control characters.`,
11685
+ diagnostics: [`Provided branch value: "${trimmed}"`],
11686
+ offendingPaths: []
11687
+ };
11688
+ }
11689
+ if (trimmed.endsWith("/")) {
11690
+ return {
11691
+ ok: false,
11692
+ gate: "branch-state",
11693
+ reason: `Invalid PRD Run base branch: "${trimmed}" must not end with a trailing slash.`,
11694
+ diagnostics: [`Provided branch value: "${trimmed}"`],
11695
+ offendingPaths: []
11696
+ };
11697
+ }
11698
+ if (trimmed.endsWith(".lock")) {
11699
+ return {
11700
+ ok: false,
11701
+ gate: "branch-state",
11702
+ reason: `Invalid PRD Run base branch: "${trimmed}" must not end with ".lock" suffix.`,
11703
+ diagnostics: [`Provided branch value: "${trimmed}"`],
11704
+ offendingPaths: []
11705
+ };
11706
+ }
11707
+ const source = options.baseBranchOverride !== void 0 ? "override" : "target";
11708
+ return { ok: true, baseBranch: trimmed, source };
11709
+ }
11710
+ function fetchOriginBranch(repoRoot2, baseBranch) {
11711
+ const fetchResult = spawnSync3("git", ["fetch", "origin", baseBranch], {
11372
11712
  cwd: repoRoot2,
11373
11713
  encoding: "utf8"
11374
11714
  });
@@ -11376,7 +11716,7 @@ function fetchOriginDev(repoRoot2) {
11376
11716
  return {
11377
11717
  ok: false,
11378
11718
  gate: "git",
11379
- reason: "PRD Run start failed while fetching origin/dev.",
11719
+ reason: `PRD Run start failed while fetching origin/${baseBranch}.`,
11380
11720
  diagnostics: [
11381
11721
  fetchResult.error instanceof Error ? fetchResult.error.message : void 0,
11382
11722
  fetchResult.stderr?.toString?.() ?? String(fetchResult.stderr ?? ""),
@@ -11385,15 +11725,19 @@ function fetchOriginDev(repoRoot2) {
11385
11725
  offendingPaths: []
11386
11726
  };
11387
11727
  }
11388
- const revParseResult = spawnSync3("git", ["rev-parse", "origin/dev"], {
11389
- cwd: repoRoot2,
11390
- encoding: "utf8"
11391
- });
11728
+ const revParseResult = spawnSync3(
11729
+ "git",
11730
+ ["rev-parse", `origin/${baseBranch}`],
11731
+ {
11732
+ cwd: repoRoot2,
11733
+ encoding: "utf8"
11734
+ }
11735
+ );
11392
11736
  if (revParseResult.status !== 0) {
11393
11737
  return {
11394
11738
  ok: false,
11395
11739
  gate: "git",
11396
- reason: "PRD Run start failed while resolving origin/dev.",
11740
+ reason: `PRD Run start failed while resolving origin/${baseBranch}.`,
11397
11741
  diagnostics: [
11398
11742
  revParseResult.error instanceof Error ? revParseResult.error.message : void 0,
11399
11743
  revParseResult.stderr?.toString?.() ?? String(revParseResult.stderr ?? ""),
@@ -14979,7 +15323,7 @@ function createCliProgram(version) {
14979
15323
  });
14980
15324
  program.command("validate-artifact").description("Validate an agent handoff artifact").argument(
14981
15325
  "<kind>",
14982
- "artifact kind: reviewer, refactor, finalizer, conflict-resolution, failure-resolution, local-prd, local-issue, or local-triage"
15326
+ "artifact kind: reviewer, refactor, finalizer, conflict-resolution, final-review, failure-resolution, local-prd, local-issue, or local-triage"
14983
15327
  ).argument("<artifactPath>", "artifact path to validate").argument("[extraPaths...]", "additional artifact paths (for local-triage)").option("--iteration <number>", "review/refactor iteration", (value) => {
14984
15328
  const parsed = Number.parseInt(value, 10);
14985
15329
  if (!Number.isInteger(parsed) || parsed < 1) {
@@ -15012,6 +15356,12 @@ function createCliProgram(version) {
15012
15356
  return previous;
15013
15357
  },
15014
15358
  []
15359
+ ).option("--prd-ref <ref>", "PRD ref for final-review artifacts").option(
15360
+ "--checkout-base <ref>",
15361
+ "checkout base / PRD branch for final-review artifacts"
15362
+ ).option(
15363
+ "--review-base <ref>",
15364
+ "review merge base for final-review artifacts"
15015
15365
  ).option("--no-check-conflict-markers", "skip conflict marker scan").option("--cwd <path>", "target repository directory").action(
15016
15366
  (kind, artifactPath, extraPaths, options) => {
15017
15367
  const allowedKinds = /* @__PURE__ */ new Set([
@@ -15019,6 +15369,7 @@ function createCliProgram(version) {
15019
15369
  "refactor",
15020
15370
  "finalizer",
15021
15371
  "conflict-resolution",
15372
+ "final-review",
15022
15373
  "failure-resolution",
15023
15374
  "local-prd",
15024
15375
  "local-issue",
@@ -15051,6 +15402,9 @@ function createCliProgram(version) {
15051
15402
  findingIds: options.findingId,
15052
15403
  latestReviewArtifactPath: options.latestReviewArtifact,
15053
15404
  allowedDecisions: options.allowedDecision,
15405
+ prdRef: options.prdRef,
15406
+ checkoutBase: options.checkoutBase,
15407
+ reviewBase: options.reviewBase,
15054
15408
  checkConflictMarkers: options.checkConflictMarkers
15055
15409
  });
15056
15410
  console.log(JSON.stringify(result, null, 2));
@@ -15263,7 +15617,7 @@ function createCliProgram(version) {
15263
15617
  }
15264
15618
  );
15265
15619
  const prdRun = program.command("prd-run").description("PRD Run commands");
15266
- prdRun.command("start").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--adopt-existing-branch", "adopt an existing PRD branch").option("--cwd <path>", "target repository directory").action(
15620
+ prdRun.command("start").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--base <branch>", "override target baseBranch for this PRD Run").option("--adopt-existing-branch", "adopt an existing PRD branch").option("--cwd <path>", "target repository directory").action(
15267
15621
  async (prdRef, options) => {
15268
15622
  const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
15269
15623
  const normalizedPrdRef = normalizePrdRef(prdRef);
@@ -15288,6 +15642,7 @@ function createCliProgram(version) {
15288
15642
  repoRoot: targetRepoRoot,
15289
15643
  prdRef: normalizedPrdRef,
15290
15644
  targetName: options.target,
15645
+ baseBranchOverride: options.base,
15291
15646
  adoptExistingBranch: options.adoptExistingBranch ?? false,
15292
15647
  config,
15293
15648
  issueProvider,
@@ -15308,7 +15663,7 @@ function createCliProgram(version) {
15308
15663
  }
15309
15664
  }
15310
15665
  );
15311
- prdRun.command("launch").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").option("--no-auto-merge", "skip auto-merge for manual review").option("--adopt-existing-branch", "adopt an existing PRD branch").action(
15666
+ prdRun.command("launch").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--base <branch>", "override target baseBranch for this PRD Run").option("--cwd <path>", "target repository directory").option("--no-auto-merge", "skip auto-merge for manual review").option("--adopt-existing-branch", "adopt an existing PRD branch").action(
15312
15667
  async (prdRef, options) => {
15313
15668
  const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
15314
15669
  const normalizedPrdRef = normalizePrdRef(prdRef);
@@ -15335,6 +15690,7 @@ function createCliProgram(version) {
15335
15690
  repoRoot: targetRepoRoot,
15336
15691
  prdRef: normalizedPrdRef,
15337
15692
  targetName: options.target,
15693
+ baseBranchOverride: options.base,
15338
15694
  autoMerge: options.autoMerge ?? true,
15339
15695
  adoptExistingBranch: options.adoptExistingBranch ?? false,
15340
15696
  config,
@@ -15530,11 +15886,11 @@ function createCliProgram(version) {
15530
15886
  return program;
15531
15887
  }
15532
15888
  async function resolveCliVersion() {
15533
- if (isPackageVersion("0.0.0-next-20260611235559")) {
15534
- return "0.0.0-next-20260611235559";
15889
+ if (isPackageVersion("0.0.0-next-20260613012953")) {
15890
+ return "0.0.0-next-20260613012953";
15535
15891
  }
15536
- if (isReleaseVersion("0.0.0-next-20260611235559")) {
15537
- return "0.0.0-next-20260611235559";
15892
+ if (isReleaseVersion("0.0.0-next-20260613012953")) {
15893
+ return "0.0.0-next-20260613012953";
15538
15894
  }
15539
15895
  try {
15540
15896
  const root = repoRoot();