@pourkit/cli 0.0.0-next-20260604153710 → 0.0.0-next-20260604225844

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
@@ -430,7 +430,9 @@ var init_github_client = __esm({
430
430
  });
431
431
 
432
432
  // execution/execution-provider.ts
433
+ import { mkdtempSync } from "fs";
433
434
  import { writeFile } from "fs/promises";
435
+ import { tmpdir } from "os";
434
436
  import { dirname as dirname3, join as join7 } from "path";
435
437
  async function writeExecutionArtifacts(worktreePath, artifacts) {
436
438
  for (const artifact of artifacts) {
@@ -898,13 +900,19 @@ var SerenaConfigSchema = z.object({
898
900
  dataDir: z.string().default(".pourkit/serena/"),
899
901
  autoStart: z.boolean().default(false)
900
902
  }).strict();
903
+ var SecurityConfigSchema = z.object({
904
+ secretLikeContentDetection: z.object({
905
+ enabled: z.boolean().default(true)
906
+ }).strict().default({})
907
+ }).strict();
901
908
  var PourkitConfigSchema = z.object({
902
909
  targets: z.array(TargetSchema).min(1),
903
910
  labels: LabelsSchema,
904
911
  sandbox: SandboxSchema,
905
912
  checks: ChecksSchema,
906
913
  cleanup: CleanupConfigSchema.optional(),
907
- serena: SerenaConfigSchema.default({})
914
+ serena: SerenaConfigSchema.default({}),
915
+ security: SecurityConfigSchema.default({})
908
916
  }).strict();
909
917
  var removedFieldReplacements = {
910
918
  "config.implementor": "targets[].strategy.implement.builder",
@@ -1151,6 +1159,7 @@ function parseConfig(raw) {
1151
1159
  issueListLimit: data.checks.issueListLimit ?? 50
1152
1160
  },
1153
1161
  serena,
1162
+ security: data.security,
1154
1163
  cleanup: {
1155
1164
  enabled: data.cleanup?.enabled ?? true,
1156
1165
  worktreeRetentionDays: data.cleanup?.worktreeRetentionDays ?? 14,
@@ -1169,13 +1178,13 @@ function getVerificationCommands(target) {
1169
1178
  return target.strategy.verify?.commands ?? [];
1170
1179
  }
1171
1180
  async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
1172
- const { existsSync: existsSync13 } = await import("fs");
1181
+ const { existsSync: existsSync14 } = await import("fs");
1173
1182
  const { mkdir: mkdir5, writeFile: writeFile3, rm } = await import("fs/promises");
1174
1183
  const { join: pjoin, basename } = await import("path");
1175
1184
  const { pathToFileURL: pathToFileURL2 } = await import("url");
1176
1185
  const { build } = await import("esbuild");
1177
1186
  const configPath = pjoin(repoRoot2, configFileName);
1178
- if (!existsSync13(configPath)) {
1187
+ if (!existsSync14(configPath)) {
1179
1188
  throw new Error(
1180
1189
  `No config file found at ${configPath}. Create a ${configFileName} that exports a default PourkitConfig.`
1181
1190
  );
@@ -5013,6 +5022,7 @@ async function completeIssueRun(options) {
5013
5022
  baseRef: `origin/${effectiveBaseBranch}`,
5014
5023
  title: prTitle,
5015
5024
  body: finalBody,
5025
+ secretLikeContentDetectionEnabled: isSecretLikeContentDetectionEnabled(config),
5016
5026
  logger
5017
5027
  });
5018
5028
  if (executionResult.worktreePath) {
@@ -5053,6 +5063,7 @@ async function completeIssueRun(options) {
5053
5063
  baseRef: `origin/${effectiveBaseBranch}`,
5054
5064
  title: prTitle,
5055
5065
  body: finalBody,
5066
+ secretLikeContentDetectionEnabled: isSecretLikeContentDetectionEnabled(config),
5056
5067
  logger
5057
5068
  });
5058
5069
  await execCapture("git", ["push", "-u", "origin", branchName], {
@@ -5233,7 +5244,14 @@ function getRefactorArtifactDir(artifactPath) {
5233
5244
  return artifactPath.replace(/\/reviewers\//, "/refactors/").replace(/\/[^/]+$/, "");
5234
5245
  }
5235
5246
  async function finalizeWorktreeCommit(options) {
5236
- const { worktreePath, baseRef, title, body, logger } = options;
5247
+ const {
5248
+ worktreePath,
5249
+ baseRef,
5250
+ title,
5251
+ body,
5252
+ secretLikeContentDetectionEnabled,
5253
+ logger
5254
+ } = options;
5237
5255
  await syncRemoteBaseRef(worktreePath, baseRef, logger);
5238
5256
  try {
5239
5257
  await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
@@ -5260,6 +5278,7 @@ async function finalizeWorktreeCommit(options) {
5260
5278
  worktreePath,
5261
5279
  title,
5262
5280
  body,
5281
+ enabled: secretLikeContentDetectionEnabled,
5263
5282
  logger
5264
5283
  });
5265
5284
  await execCapture("git", ["commit", "--no-verify", "-m", title, "-m", body], {
@@ -5269,7 +5288,10 @@ async function finalizeWorktreeCommit(options) {
5269
5288
  });
5270
5289
  }
5271
5290
  async function guardFinalCommitContent(options) {
5272
- const { worktreePath, title, body, logger } = options;
5291
+ const { worktreePath, title, body, enabled, logger } = options;
5292
+ if (!enabled) {
5293
+ return;
5294
+ }
5273
5295
  const stagedDiff = await execCapture("git", ["diff", "--cached"], {
5274
5296
  cwd: worktreePath,
5275
5297
  logger,
@@ -5285,7 +5307,17 @@ async function guardFinalCommitContent(options) {
5285
5307
  ]);
5286
5308
  }
5287
5309
  async function guardFinalPublishContent(options) {
5288
- const { worktreePath, baseRef, title, body, logger } = options;
5310
+ const {
5311
+ worktreePath,
5312
+ baseRef,
5313
+ title,
5314
+ body,
5315
+ secretLikeContentDetectionEnabled,
5316
+ logger
5317
+ } = options;
5318
+ if (!secretLikeContentDetectionEnabled) {
5319
+ return;
5320
+ }
5289
5321
  const finalDiff = await execCapture("git", ["diff", `${baseRef}...HEAD`], {
5290
5322
  cwd: worktreePath,
5291
5323
  logger,
@@ -5315,6 +5347,9 @@ function assertNoSecretLikeContent(inputs) {
5315
5347
  `Secret-like content detected before finalization (${summary}). Redact it before committing, pushing, or creating a PR.`
5316
5348
  );
5317
5349
  }
5350
+ function isSecretLikeContentDetectionEnabled(config) {
5351
+ return config.security?.secretLikeContentDetection.enabled !== false;
5352
+ }
5318
5353
  function findSecretLikeContent(source, content) {
5319
5354
  const findings = [];
5320
5355
  const patterns = [
@@ -6044,15 +6079,16 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
6044
6079
  // commands/prd-run.ts
6045
6080
  import {
6046
6081
  cpSync,
6047
- existsSync as existsSync11,
6082
+ existsSync as existsSync12,
6048
6083
  mkdirSync as mkdirSync8,
6049
- mkdtempSync,
6050
- readFileSync as readFileSync11,
6084
+ mkdtempSync as mkdtempSync2,
6085
+ readdirSync as readdirSync4,
6086
+ readFileSync as readFileSync12,
6051
6087
  rmSync as rmSync3
6052
6088
  } from "fs";
6053
6089
  import { spawnSync as spawnSync2 } from "child_process";
6054
6090
  import { dirname as dirname7, join as join15, relative as relative2 } from "path";
6055
- import { tmpdir } from "os";
6091
+ import { tmpdir as tmpdir2 } from "os";
6056
6092
 
6057
6093
  // prd-run/planning-manifest.ts
6058
6094
  import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
@@ -6678,7 +6714,8 @@ var PrdRunRecordSchema = z3.object({
6678
6714
  "branch-state",
6679
6715
  "git",
6680
6716
  "queue",
6681
- "final-review"
6717
+ "final-review",
6718
+ "reconciliation"
6682
6719
  ]).optional(),
6683
6720
  targetName: z3.string().min(1).optional(),
6684
6721
  prdBranch: z3.string().min(1).optional(),
@@ -6735,6 +6772,14 @@ var PrdRunRecordSchema = z3.object({
6735
6772
  autoMerge: z3.boolean().optional(),
6736
6773
  changedPaths: z3.array(z3.string()).optional()
6737
6774
  }).strict().optional(),
6775
+ reconciliation: z3.object({
6776
+ status: z3.enum(["succeeded", "blocked"]),
6777
+ targetName: z3.string().min(1),
6778
+ prdBranch: z3.string().min(1),
6779
+ mergeBase: z3.string().min(1).optional(),
6780
+ diagnostics: z3.array(z3.string()).optional(),
6781
+ reconciledAt: z3.string().min(1).optional()
6782
+ }).strict().optional(),
6738
6783
  scopeChanges: z3.array(
6739
6784
  z3.object({
6740
6785
  issueNumber: z3.number().int().positive(),
@@ -6820,6 +6865,300 @@ function getRecordPath(repoRoot2, prdRef) {
6820
6865
  );
6821
6866
  }
6822
6867
 
6868
+ // prd-run/evidence-packet.ts
6869
+ import { z as z4 } from "zod";
6870
+ var PRD_REF_REGEX2 = /^PRD-\d{3,4}$/;
6871
+ var StageSchema = z4.enum(["prdFinalReview", "prdReconciliation"]);
6872
+ var EvidencePacketSchema = z4.object({
6873
+ prdRef: z4.string().regex(PRD_REF_REGEX2),
6874
+ prdBranch: z4.string().min(1),
6875
+ mergeBase: z4.string().min(6),
6876
+ planningManifestPath: z4.string().min(1),
6877
+ planningManifestFacts: z4.object({
6878
+ parentPrdIssueUrl: z4.string().min(1),
6879
+ childIssueCount: z4.number().int().nonnegative()
6880
+ }),
6881
+ stage: StageSchema,
6882
+ stageReceipts: z4.record(z4.string(), z4.unknown())
6883
+ }).strict();
6884
+ function buildEvidencePacket(input) {
6885
+ const requiredFields = [
6886
+ "prdRef",
6887
+ "prdBranch",
6888
+ "mergeBase",
6889
+ "planningManifestPath",
6890
+ "stage"
6891
+ ];
6892
+ for (const field of requiredFields) {
6893
+ const value = input[field];
6894
+ if (typeof value !== "string" || value.trim().length === 0) {
6895
+ throw new Error(
6896
+ `Evidence Packet construction failed: "${field}" is required and must be a non-empty string.`
6897
+ );
6898
+ }
6899
+ }
6900
+ if (!input.planningManifestFacts || typeof input.planningManifestFacts.parentPrdIssueUrl !== "string" || input.planningManifestFacts.parentPrdIssueUrl.trim().length === 0) {
6901
+ throw new Error(
6902
+ 'Evidence Packet construction failed: "planningManifestFacts.parentPrdIssueUrl" is required and must be a non-empty string.'
6903
+ );
6904
+ }
6905
+ if (typeof input.planningManifestFacts.childIssueCount !== "number" || !Number.isInteger(input.planningManifestFacts.childIssueCount) || input.planningManifestFacts.childIssueCount < 0) {
6906
+ throw new Error(
6907
+ 'Evidence Packet construction failed: "planningManifestFacts.childIssueCount" is required and must be a non-negative integer.'
6908
+ );
6909
+ }
6910
+ if (!PRD_REF_REGEX2.test(input.prdRef.trim())) {
6911
+ throw new Error(
6912
+ `Evidence Packet construction failed: "prdRef" must match PRD-\\d{3,4} format, got "${input.prdRef}".`
6913
+ );
6914
+ }
6915
+ const stageResult = StageSchema.safeParse(input.stage);
6916
+ if (!stageResult.success) {
6917
+ throw new Error(
6918
+ `Evidence Packet construction failed: "stage" must be one of "prdFinalReview" or "prdReconciliation", got "${input.stage}".`
6919
+ );
6920
+ }
6921
+ if (stageResult.data === "prdFinalReview") {
6922
+ const hasMergeBaseReceipt = "mergeBase" in input.stageReceipts;
6923
+ const hasFinalReviewRef = "finalReviewRef" in input.stageReceipts;
6924
+ if (!hasMergeBaseReceipt && !hasFinalReviewRef) {
6925
+ throw new Error(
6926
+ "Evidence Packet construction failed: Final Review stage receipts must include at minimum a mergeBase or finalReviewRef receipt."
6927
+ );
6928
+ }
6929
+ }
6930
+ if (stageResult.data === "prdReconciliation") {
6931
+ if (!("finalReviewReceipt" in input.stageReceipts)) {
6932
+ throw new Error(
6933
+ "Evidence Packet construction failed: Reconciliation stage receipts must include at minimum a finalReviewReceipt."
6934
+ );
6935
+ }
6936
+ }
6937
+ const mergeBase = input.mergeBase.trim();
6938
+ if (mergeBase.length < 6) {
6939
+ throw new Error(
6940
+ `Evidence Packet construction failed: "mergeBase" must be a non-empty commit SHA with at least 6 characters, got "${mergeBase}".`
6941
+ );
6942
+ }
6943
+ const packet = {
6944
+ prdRef: input.prdRef.trim(),
6945
+ prdBranch: input.prdBranch.trim(),
6946
+ mergeBase,
6947
+ planningManifestPath: input.planningManifestPath.trim(),
6948
+ planningManifestFacts: {
6949
+ parentPrdIssueUrl: input.planningManifestFacts.parentPrdIssueUrl.trim(),
6950
+ childIssueCount: input.planningManifestFacts.childIssueCount
6951
+ },
6952
+ stage: stageResult.data,
6953
+ stageReceipts: input.stageReceipts
6954
+ };
6955
+ return packet;
6956
+ }
6957
+ var TOKEN_LIKE_PATTERNS = [
6958
+ /gh[ps]_[A-Za-z0-9]{36}/g,
6959
+ /token[=:_\s][A-Za-z0-9_-]{20,}/gi,
6960
+ /secret[=:_\s][A-Za-z0-9_-]{20,}/gi,
6961
+ /credential[=:_\s][A-Za-z0-9_-]{20,}/gi,
6962
+ /key[=:_\s][A-Za-z0-9_-]{20,}/gi,
6963
+ /-----BEGIN (RSA |EC )?PRIVATE KEY-----/g,
6964
+ /xox[baprs]-[A-Za-z0-9_-]{10,}/g
6965
+ ];
6966
+ function redactSensitiveValues(input) {
6967
+ let redacted = input;
6968
+ for (const pattern of TOKEN_LIKE_PATTERNS) {
6969
+ redacted = redacted.replace(pattern, "[REDACTED]");
6970
+ }
6971
+ return redacted;
6972
+ }
6973
+
6974
+ // prd-run/final-review-validation.ts
6975
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
6976
+ function parseFinalReviewArtifact(artifactPath) {
6977
+ if (!existsSync11(artifactPath)) {
6978
+ return {
6979
+ ok: false,
6980
+ reason: "Final Review artifact not found.",
6981
+ diagnostics: [`Artifact path: ${artifactPath}`]
6982
+ };
6983
+ }
6984
+ let content;
6985
+ try {
6986
+ content = readFileSync11(artifactPath, "utf-8");
6987
+ } catch (error) {
6988
+ return {
6989
+ ok: false,
6990
+ reason: "Final Review artifact could not be read.",
6991
+ diagnostics: [
6992
+ error instanceof Error ? error.message : String(error),
6993
+ `Artifact path: ${artifactPath}`
6994
+ ]
6995
+ };
6996
+ }
6997
+ let parsed;
6998
+ try {
6999
+ parsed = JSON.parse(content);
7000
+ } catch {
7001
+ return {
7002
+ ok: false,
7003
+ reason: "Final Review artifact is not valid JSON.",
7004
+ diagnostics: [`Artifact path: ${artifactPath}`]
7005
+ };
7006
+ }
7007
+ const verdict = parsed.verdict;
7008
+ if (!verdict || typeof verdict !== "string") {
7009
+ return {
7010
+ ok: false,
7011
+ reason: "Final Review artifact is missing a verdict field.",
7012
+ diagnostics: [`Artifact content preview: ${content.slice(0, 300)}`]
7013
+ };
7014
+ }
7015
+ const allowedVerdicts = [
7016
+ "pass_no_changes",
7017
+ "pass_with_retouch",
7018
+ "needs_human_review",
7019
+ "blocked"
7020
+ ];
7021
+ if (!allowedVerdicts.includes(verdict)) {
7022
+ return {
7023
+ ok: false,
7024
+ reason: `Final Review artifact has unsupported verdict "${verdict}".`,
7025
+ diagnostics: [`Allowed verdicts: ${allowedVerdicts.join(", ")}`]
7026
+ };
7027
+ }
7028
+ const summary = typeof parsed.summary === "string" ? parsed.summary : typeof parsed.reason === "string" ? parsed.reason : String(verdict);
7029
+ const diagnostics = Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [];
7030
+ const changedPaths = Array.isArray(parsed.changedPaths) ? parsed.changedPaths : void 0;
7031
+ const isAutoSummary = !parsed.summary && !parsed.reason;
7032
+ if (verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && isAutoSummary) {
7033
+ return {
7034
+ ok: false,
7035
+ reason: 'Final Review artifact with "pass_with_retouch" verdict requires changed paths.',
7036
+ diagnostics: [
7037
+ "pass_with_retouch verdict must include a changedPaths array in the artifact or provide a summary/reason with enough context for git diff fallback.",
7038
+ "Provide changedPaths or a descriptive summary in the artifact."
7039
+ ]
7040
+ };
7041
+ }
7042
+ const prdRef = typeof parsed.prdRef === "string" && parsed.prdRef.trim() ? parsed.prdRef.trim() : void 0;
7043
+ const stage = typeof parsed.stage === "string" && parsed.stage.trim() ? parsed.stage.trim() : void 0;
7044
+ const checkoutBase = typeof parsed.checkoutBase === "string" && parsed.checkoutBase.trim() ? parsed.checkoutBase.trim() : void 0;
7045
+ const reviewBase = typeof parsed.reviewBase === "string" && parsed.reviewBase.trim() ? parsed.reviewBase.trim() : void 0;
7046
+ return {
7047
+ ok: true,
7048
+ verdict,
7049
+ summary,
7050
+ diagnostics,
7051
+ changedPaths,
7052
+ prdRef,
7053
+ stage,
7054
+ checkoutBase,
7055
+ reviewBase
7056
+ };
7057
+ }
7058
+ function validateFinalReviewArtifactSemanticIds(artifact, context) {
7059
+ const errors = [];
7060
+ if (!artifact.prdRef) {
7061
+ errors.push("Final Review artifact is missing prdRef.");
7062
+ } else if (artifact.prdRef !== context.prdRef) {
7063
+ errors.push(
7064
+ `Final Review artifact prdRef "${artifact.prdRef}" does not match active PRD Run "${context.prdRef}".`
7065
+ );
7066
+ }
7067
+ if (!artifact.stage) {
7068
+ errors.push("Final Review artifact is missing stage field.");
7069
+ } else if (artifact.stage !== "prdFinalReview") {
7070
+ errors.push(
7071
+ `Final Review artifact stage "${artifact.stage}" is not "prdFinalReview".`
7072
+ );
7073
+ }
7074
+ if (!artifact.checkoutBase) {
7075
+ errors.push("Final Review artifact is missing checkoutBase.");
7076
+ } else if (artifact.checkoutBase !== context.prdBranch) {
7077
+ errors.push(
7078
+ `Final Review artifact checkoutBase "${artifact.checkoutBase}" does not match active PRD Branch "${context.prdBranch}".`
7079
+ );
7080
+ }
7081
+ if (!artifact.reviewBase) {
7082
+ errors.push("Final Review artifact is missing reviewBase.");
7083
+ } else if (artifact.reviewBase !== context.mergeBase) {
7084
+ errors.push(
7085
+ `Final Review artifact reviewBase "${artifact.reviewBase}" does not match active merge base "${context.mergeBase}".`
7086
+ );
7087
+ }
7088
+ if (errors.length > 0) {
7089
+ return { ok: false, errors, blockedGate: "final-review" };
7090
+ }
7091
+ return { ok: true };
7092
+ }
7093
+ function validateFinalReviewRetouchScope(changedPaths) {
7094
+ if (!changedPaths || changedPaths.length === 0) {
7095
+ return {
7096
+ ok: false,
7097
+ 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.",
7098
+ diagnostics: ["Changed paths list is empty or undefined."],
7099
+ offendingPaths: []
7100
+ };
7101
+ }
7102
+ const offendingPaths = changedPaths.filter((p) => !isRetouchScopePath(p));
7103
+ const allowedPaths = changedPaths.filter((p) => isRetouchScopePath(p));
7104
+ if (allowedPaths.length === 0) {
7105
+ return {
7106
+ ok: false,
7107
+ reason: "Final Review retouch scope validation failed. No implementation, test, or Changeset paths found for retouch.",
7108
+ diagnostics: [
7109
+ "All changed paths are prohibited for retouch.",
7110
+ ...changedPaths.map((p) => ` - ${p}`)
7111
+ ],
7112
+ offendingPaths
7113
+ };
7114
+ }
7115
+ if (offendingPaths.length > 0) {
7116
+ return {
7117
+ ok: false,
7118
+ reason: "Final Review retouch scope validation failed. Prohibited paths cannot be included in retouch PR.",
7119
+ diagnostics: [
7120
+ "Prohibited paths found:",
7121
+ ...offendingPaths.map((p) => ` - ${p}`)
7122
+ ],
7123
+ offendingPaths
7124
+ };
7125
+ }
7126
+ return { ok: true, changedPaths };
7127
+ }
7128
+ function validateFinalReviewAgentOutputs(options) {
7129
+ const artifact = parseFinalReviewArtifact(options.artifactPath);
7130
+ if (!artifact.ok) {
7131
+ return { ...artifact, offendingPaths: [] };
7132
+ }
7133
+ const semanticResult = validateFinalReviewArtifactSemanticIds(
7134
+ artifact,
7135
+ options.context
7136
+ );
7137
+ if (!semanticResult.ok) {
7138
+ return {
7139
+ ok: false,
7140
+ reason: "Final Review artifact has mismatched semantic IDs.",
7141
+ diagnostics: semanticResult.errors,
7142
+ offendingPaths: []
7143
+ };
7144
+ }
7145
+ if (artifact.verdict !== "pass_with_retouch") {
7146
+ return { ok: true, artifact };
7147
+ }
7148
+ const changedPaths = options.changedPaths && options.changedPaths.length > 0 ? options.changedPaths : artifact.changedPaths;
7149
+ const retouch = validateFinalReviewRetouchScope(changedPaths ?? []);
7150
+ if (!retouch.ok) {
7151
+ return retouch;
7152
+ }
7153
+ return { ok: true, artifact, retouch };
7154
+ }
7155
+ function isRetouchScopePath(path9) {
7156
+ if (path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/adr/") || /^\.pourkit\/prd-runs\/[^/]+\.json$/.test(path9)) {
7157
+ return false;
7158
+ }
7159
+ return true;
7160
+ }
7161
+
6823
7162
  // commands/prd-run.ts
6824
7163
  init_common();
6825
7164
 
@@ -7338,19 +7677,20 @@ function canRetryFinalReviewBlock(record) {
7338
7677
  }
7339
7678
  function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
7340
7679
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
7341
- return existsSync11(promptPath) ? readFileSync11(promptPath, "utf-8") : promptTemplate;
7680
+ return existsSync12(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
7342
7681
  }
7343
7682
  function buildFinalReviewPrompt(options) {
7344
7683
  return [
7345
7684
  "# Active PRD Final Review Context",
7346
7685
  "",
7347
- `- PRD Ref: ${options.prdRef}`,
7348
- `- PRD Branch: ${options.prdBranch}`,
7349
- `- Merge Base: ${options.mergeBase}`,
7350
- `- PRD Run State: .pourkit/prd-runs/${options.prdRef}.json`,
7351
- `- Planning Manifest: ${options.manifestPath ?? "(not recorded)"}`,
7686
+ `Worktree checkout base: ${options.evidencePacket.prdBranch}`,
7687
+ `Review only this range: ${options.evidencePacket.mergeBase}..HEAD`,
7688
+ "Do not compare against current target branch HEAD; the merge base is the review baseline.",
7689
+ `Before handoff, run: npm run pourkit:validate-final-review -- ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
7690
+ "Fix any validation failures before handing off.",
7352
7691
  "",
7353
- "Review only the active PRD above. Do not infer the active PRD from nearby files, Changesets, glob order, or other PRD Run state files.",
7692
+ "Evidence Packet (do not infer PRD context from local state files):",
7693
+ JSON.stringify(options.evidencePacket, null, 2),
7354
7694
  "",
7355
7695
  loadFinalReviewPrompt(options.repoRoot, options.promptTemplate)
7356
7696
  ].join("\n");
@@ -7358,6 +7698,95 @@ function buildFinalReviewPrompt(options) {
7358
7698
  function buildRetouchBranchName(prdRef) {
7359
7699
  return `prd-run/${prdRef}-final-review-retouch`;
7360
7700
  }
7701
+ function buildFinalReviewBranchName(prdRef) {
7702
+ return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
7703
+ }
7704
+ function finalReviewWorktreePath(repoRoot2, branchName) {
7705
+ return join15(
7706
+ repoRoot2,
7707
+ ".sandcastle",
7708
+ "worktrees",
7709
+ branchName.replace(/\//g, "-")
7710
+ );
7711
+ }
7712
+ function collectSpawnDiagnostics(result, fallback) {
7713
+ const diagnostics = [];
7714
+ const stderr = result.stderr?.toString?.().trim();
7715
+ const stdout = result.stdout?.toString?.().trim();
7716
+ if (stderr) diagnostics.push(stderr);
7717
+ if (stdout) diagnostics.push(stdout);
7718
+ return diagnostics.length > 0 ? diagnostics : [fallback];
7719
+ }
7720
+ function ensureFinalReviewWorktree(options) {
7721
+ const worktreePath = finalReviewWorktreePath(
7722
+ options.repoRoot,
7723
+ options.branchName
7724
+ );
7725
+ const listResult = spawnSync2("git", ["worktree", "list", "--porcelain"], {
7726
+ cwd: options.repoRoot,
7727
+ encoding: "utf8"
7728
+ });
7729
+ if (listResult.status !== 0) {
7730
+ return {
7731
+ ok: false,
7732
+ reason: "Final Review failed to list git worktrees.",
7733
+ diagnostics: collectSpawnDiagnostics(
7734
+ listResult,
7735
+ "git worktree list failed"
7736
+ )
7737
+ };
7738
+ }
7739
+ const registeredPath = parseWorktreeListPorcelain(
7740
+ listResult.stdout?.toString?.() ?? "",
7741
+ options.branchName
7742
+ );
7743
+ if (registeredPath) {
7744
+ if (registeredPath !== worktreePath) {
7745
+ return {
7746
+ ok: false,
7747
+ reason: `Final Review branch ${options.branchName} already has a registered worktree outside .sandcastle/worktrees.`,
7748
+ diagnostics: [`registered worktree: ${registeredPath}`]
7749
+ };
7750
+ }
7751
+ return { ok: true, worktreePath: registeredPath };
7752
+ }
7753
+ if (existsSync12(worktreePath)) {
7754
+ return {
7755
+ ok: false,
7756
+ reason: "Final Review worktree path exists but is not registered with git.",
7757
+ diagnostics: [`stale worktree path: ${worktreePath}`]
7758
+ };
7759
+ }
7760
+ mkdirSync8(dirname7(worktreePath), { recursive: true });
7761
+ const branchResult = spawnSync2(
7762
+ "git",
7763
+ ["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
7764
+ { cwd: options.repoRoot, encoding: "utf8" }
7765
+ );
7766
+ if (branchResult.status !== 0) {
7767
+ return {
7768
+ ok: false,
7769
+ reason: `Final Review failed to prepare branch ${options.branchName}.`,
7770
+ diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
7771
+ };
7772
+ }
7773
+ const addResult = spawnSync2(
7774
+ "git",
7775
+ ["worktree", "add", worktreePath, options.branchName],
7776
+ { cwd: options.repoRoot, encoding: "utf8" }
7777
+ );
7778
+ if (addResult.status !== 0) {
7779
+ return {
7780
+ ok: false,
7781
+ reason: `Final Review failed to create worktree for ${options.branchName}.`,
7782
+ diagnostics: collectSpawnDiagnostics(
7783
+ addResult,
7784
+ "git worktree add failed"
7785
+ )
7786
+ };
7787
+ }
7788
+ return { ok: true, worktreePath };
7789
+ }
7361
7790
  function buildRetouchPrTitle(prdRef) {
7362
7791
  return `chore: ${prdRef} Final Review retouch`;
7363
7792
  }
@@ -7383,7 +7812,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
7383
7812
  if (existingPr && existingPr.state === "OPEN") {
7384
7813
  return existingPr;
7385
7814
  }
7386
- const worktreePath = mkdtempSync(join15(tmpdir(), "pourkit-retouch-"));
7815
+ const worktreePath = mkdtempSync2(join15(tmpdir2(), "pourkit-retouch-"));
7387
7816
  try {
7388
7817
  runGitOrThrow(
7389
7818
  options.repoRoot,
@@ -7653,53 +8082,245 @@ async function runPrdRunFinalReviewCommand(options) {
7653
8082
  };
7654
8083
  }
7655
8084
  const prdBranch = record.prdBranch ?? prdRef;
7656
- const worktreePath = mkdtempSync(join15(tmpdir(), "pourkit-final-review-"));
7657
- try {
7658
- runGitOrThrow(
7659
- options.repoRoot,
7660
- ["worktree", "add", "--detach", worktreePath, `origin/${prdBranch}`],
7661
- "create final review worktree from PRD branch"
7662
- );
7663
- const startReceipt = {
7664
- status: "started",
7665
- targetName,
7666
- prdBranch,
7667
- mergeBase: mergeBaseResult.mergeBase,
7668
- diagnostics: [],
7669
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
7670
- };
8085
+ const manifestResult = readPlanningArtifactManifest(options.repoRoot, prdRef);
8086
+ if (!manifestResult.ok) {
8087
+ const reason = `Evidence Packet construction failed: ${manifestResult.reason}`;
7671
8088
  writePrdRunRecord(options.repoRoot, {
7672
8089
  ...record,
7673
- status: "running",
8090
+ status: "blocked",
7674
8091
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8092
+ blockedGate: "final-review",
8093
+ blockedReason: reason,
8094
+ diagnostics: manifestResult.diagnostics,
7675
8095
  targetName,
7676
- start: record.start,
7677
- planning: record.planning,
7678
- finalReview: startReceipt
7679
- });
7680
- const executionResult = await options.executionProvider.execute({
7681
- stage: "prdFinalReview",
7682
- agent: finalReviewConfig.agent,
7683
- model: finalReviewConfig.model,
7684
- prompt: buildFinalReviewPrompt({
7685
- repoRoot: options.repoRoot,
7686
- promptTemplate: finalReviewConfig.promptTemplate,
7687
- prdRef,
8096
+ finalReview: {
8097
+ status: "blocked",
8098
+ targetName,
7688
8099
  prdBranch,
7689
8100
  mergeBase: mergeBaseResult.mergeBase,
7690
- manifestPath: record.manifestPath
7691
- }),
7692
- target: targetConfig,
8101
+ diagnostics: manifestResult.diagnostics,
8102
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8103
+ },
8104
+ offendingPaths: manifestResult.offendingPaths
8105
+ });
8106
+ return {
8107
+ prdRef,
8108
+ status: "blocked",
8109
+ blockedGate: "final-review",
8110
+ blockedReason: reason,
8111
+ diagnostics: manifestResult.diagnostics,
8112
+ offendingPaths: manifestResult.offendingPaths
8113
+ };
8114
+ }
8115
+ const evidencePacket = buildEvidencePacket({
8116
+ prdRef,
8117
+ prdBranch,
8118
+ mergeBase: mergeBaseResult.mergeBase,
8119
+ planningManifestPath: manifestResult.manifest.manifestPath,
8120
+ planningManifestFacts: {
8121
+ parentPrdIssueUrl: manifestResult.manifest.parentIssue.url,
8122
+ childIssueCount: manifestResult.manifest.childIssues.length
8123
+ },
8124
+ stage: "prdFinalReview",
8125
+ stageReceipts: { mergeBase: mergeBaseResult.mergeBase }
8126
+ });
8127
+ const startReceipt = {
8128
+ status: "started",
8129
+ targetName,
8130
+ prdBranch,
8131
+ mergeBase: mergeBaseResult.mergeBase,
8132
+ diagnostics: [],
8133
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8134
+ };
8135
+ writePrdRunRecord(options.repoRoot, {
8136
+ ...record,
8137
+ status: "running",
8138
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8139
+ targetName,
8140
+ start: record.start,
8141
+ planning: record.planning,
8142
+ finalReview: startReceipt
8143
+ });
8144
+ const finalReviewBranchName = buildFinalReviewBranchName(prdBranch);
8145
+ const worktreeResult = ensureFinalReviewWorktree({
8146
+ repoRoot: options.repoRoot,
8147
+ branchName: finalReviewBranchName,
8148
+ checkoutBase: prdBranch
8149
+ });
8150
+ if (!worktreeResult.ok) {
8151
+ writePrdRunRecord(options.repoRoot, {
8152
+ ...record,
8153
+ status: "blocked",
8154
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8155
+ blockedGate: "final-review",
8156
+ blockedReason: worktreeResult.reason,
8157
+ diagnostics: worktreeResult.diagnostics,
8158
+ targetName,
8159
+ start: record.start,
8160
+ planning: record.planning,
8161
+ finalReview: {
8162
+ ...startReceipt,
8163
+ status: "blocked",
8164
+ diagnostics: worktreeResult.diagnostics
8165
+ }
8166
+ });
8167
+ return {
8168
+ prdRef,
8169
+ status: "blocked",
8170
+ blockedGate: "final-review",
8171
+ blockedReason: worktreeResult.reason,
8172
+ diagnostics: worktreeResult.diagnostics,
8173
+ offendingPaths: []
8174
+ };
8175
+ }
8176
+ const executionResult = await options.executionProvider.execute({
8177
+ stage: "prdFinalReview",
8178
+ agent: finalReviewConfig.agent,
8179
+ model: finalReviewConfig.model,
8180
+ prompt: buildFinalReviewPrompt({
7693
8181
  repoRoot: options.repoRoot,
7694
- branchName: prdBranch,
7695
- baseRef: mergeBaseResult.mergeBase,
7696
- sandbox: options.config.sandbox,
7697
- worktreePath,
8182
+ promptTemplate: finalReviewConfig.promptTemplate,
8183
+ evidencePacket
8184
+ }),
8185
+ target: targetConfig,
8186
+ repoRoot: options.repoRoot,
8187
+ branchName: finalReviewBranchName,
8188
+ baseRef: prdBranch,
8189
+ checkoutBase: prdBranch,
8190
+ reviewBase: mergeBaseResult.mergeBase,
8191
+ worktreePath: worktreeResult.worktreePath,
8192
+ sandbox: options.config.sandbox,
8193
+ artifactPath: ".pourkit/final-review-artifact.json",
8194
+ logger: options.logger
8195
+ });
8196
+ if (!executionResult.success) {
8197
+ const reason = executionResult.error ?? "Final Review execution failed.";
8198
+ writePrdRunRecord(options.repoRoot, {
8199
+ ...record,
8200
+ status: "blocked",
8201
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8202
+ blockedGate: "final-review",
8203
+ blockedReason: reason,
8204
+ diagnostics: [reason],
8205
+ targetName,
8206
+ finalReview: {
8207
+ status: "blocked",
8208
+ targetName,
8209
+ prdBranch,
8210
+ mergeBase: mergeBaseResult.mergeBase,
8211
+ diagnostics: [reason],
8212
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8213
+ },
8214
+ offendingPaths: []
8215
+ });
8216
+ return {
8217
+ prdRef,
8218
+ status: "blocked",
8219
+ blockedGate: "final-review",
8220
+ blockedReason: reason,
8221
+ diagnostics: [reason],
8222
+ offendingPaths: []
8223
+ };
8224
+ }
8225
+ const resolvedWorktreePath = executionResult.worktreePath;
8226
+ const artifactPath = join15(
8227
+ resolvedWorktreePath,
8228
+ ".pourkit/final-review-artifact.json"
8229
+ );
8230
+ const artifactResult = parseFinalReviewArtifact(artifactPath);
8231
+ if (!artifactResult.ok) {
8232
+ writePrdRunRecord(options.repoRoot, {
8233
+ ...record,
8234
+ status: "blocked",
8235
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8236
+ blockedGate: "final-review",
8237
+ blockedReason: artifactResult.reason,
8238
+ diagnostics: artifactResult.diagnostics,
8239
+ targetName,
8240
+ finalReview: {
8241
+ status: "blocked",
8242
+ targetName,
8243
+ prdBranch,
8244
+ mergeBase: mergeBaseResult.mergeBase,
8245
+ diagnostics: artifactResult.diagnostics,
8246
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8247
+ },
8248
+ offendingPaths: []
8249
+ });
8250
+ return {
8251
+ prdRef,
8252
+ status: "blocked",
8253
+ blockedGate: "final-review",
8254
+ blockedReason: artifactResult.reason,
8255
+ diagnostics: artifactResult.diagnostics,
8256
+ offendingPaths: []
8257
+ };
8258
+ }
8259
+ const semanticResult = validateFinalReviewArtifactSemanticIds(
8260
+ artifactResult,
8261
+ { prdRef, prdBranch, mergeBase: mergeBaseResult.mergeBase }
8262
+ );
8263
+ if (!semanticResult.ok) {
8264
+ const diagnostics = semanticResult.errors.map(redactSensitiveValues);
8265
+ const reason = "Final Review artifact has mismatched semantic IDs.";
8266
+ writePrdRunRecord(options.repoRoot, {
8267
+ ...record,
8268
+ status: "blocked",
8269
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8270
+ blockedGate: "final-review",
8271
+ blockedReason: reason,
8272
+ diagnostics,
8273
+ targetName,
8274
+ finalReview: {
8275
+ status: "blocked",
8276
+ targetName,
8277
+ prdBranch,
8278
+ mergeBase: mergeBaseResult.mergeBase,
8279
+ diagnostics,
8280
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8281
+ },
8282
+ offendingPaths: []
8283
+ });
8284
+ return {
8285
+ prdRef,
8286
+ status: "blocked",
8287
+ blockedGate: "final-review",
8288
+ blockedReason: reason,
8289
+ diagnostics,
8290
+ offendingPaths: []
8291
+ };
8292
+ }
8293
+ const { verdict, summary, diagnostics: artifactDiagnostics } = artifactResult;
8294
+ if (verdict === "pass_no_changes") {
8295
+ const receipt = {
8296
+ status: "succeeded",
8297
+ targetName,
8298
+ prdBranch,
8299
+ mergeBase: mergeBaseResult.mergeBase,
8300
+ verdict: "pass_no_changes",
7698
8301
  artifactPath: ".pourkit/final-review-artifact.json",
7699
- logger: options.logger
8302
+ diagnostics: [],
8303
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8304
+ };
8305
+ writePrdRunRecord(options.repoRoot, {
8306
+ ...record,
8307
+ status: "final_reviewed",
8308
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8309
+ targetName,
8310
+ start: record.start,
8311
+ planning: record.planning,
8312
+ finalReview: receipt
7700
8313
  });
7701
- if (!executionResult.success) {
7702
- const reason = executionResult.error ?? "Final Review execution failed.";
8314
+ return {
8315
+ prdRef,
8316
+ status: "final_reviewed",
8317
+ finalReview: receipt,
8318
+ diagnostics: []
8319
+ };
8320
+ }
8321
+ if (verdict === "pass_with_retouch") {
8322
+ if (!options.prProvider) {
8323
+ const reason = "Missing PRProvider. Cannot create retouch PR without a PR provider.";
7703
8324
  writePrdRunRecord(options.repoRoot, {
7704
8325
  ...record,
7705
8326
  status: "blocked",
@@ -7713,6 +8334,7 @@ async function runPrdRunFinalReviewCommand(options) {
7713
8334
  targetName,
7714
8335
  prdBranch,
7715
8336
  mergeBase: mergeBaseResult.mergeBase,
8337
+ verdict: "pass_with_retouch",
7716
8338
  diagnostics: [reason],
7717
8339
  reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
7718
8340
  },
@@ -7727,26 +8349,118 @@ async function runPrdRunFinalReviewCommand(options) {
7727
8349
  offendingPaths: []
7728
8350
  };
7729
8351
  }
7730
- const artifactPath = join15(
7731
- worktreePath,
7732
- ".pourkit/final-review-artifact.json"
7733
- );
7734
- const artifactResult = parseFinalReviewArtifact(artifactPath);
7735
- if (!artifactResult.ok) {
8352
+ const autoMerge = options.autoMerge ?? true;
8353
+ const existingRetouchPrNumber = record.finalReview?.retouchPrNumber;
8354
+ if (existingRetouchPrNumber && autoMerge) {
8355
+ const existingPr = await getPrByNumberIfAvailable(
8356
+ options.prProvider,
8357
+ existingRetouchPrNumber
8358
+ );
8359
+ if (existingPr?.state === "MERGED" && existingPr.mergeCommitSha) {
8360
+ const receipt2 = {
8361
+ status: "final_reviewed",
8362
+ targetName,
8363
+ prdBranch,
8364
+ mergeBase: mergeBaseResult.mergeBase,
8365
+ verdict: "pass_with_retouch",
8366
+ artifactPath: ".pourkit/final-review-artifact.json",
8367
+ diagnostics: [],
8368
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
8369
+ retouchPrNumber: existingRetouchPrNumber,
8370
+ retouchPrUrl: existingPr.url,
8371
+ retouchMergeCommit: existingPr.mergeCommitSha,
8372
+ autoMerge,
8373
+ changedPaths: record.finalReview?.changedPaths ?? []
8374
+ };
8375
+ writePrdRunRecord(options.repoRoot, {
8376
+ ...record,
8377
+ status: "final_reviewed",
8378
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8379
+ targetName,
8380
+ start: record.start,
8381
+ planning: record.planning,
8382
+ finalReview: receipt2
8383
+ });
8384
+ return {
8385
+ prdRef,
8386
+ status: "final_reviewed",
8387
+ finalReview: receipt2,
8388
+ diagnostics: []
8389
+ };
8390
+ }
8391
+ }
8392
+ const changedPathsFromArtifact = artifactResult.changedPaths;
8393
+ let resolvedChangedPaths;
8394
+ if (changedPathsFromArtifact && changedPathsFromArtifact.length > 0) {
8395
+ resolvedChangedPaths = changedPathsFromArtifact;
8396
+ } else {
8397
+ const diffResult = spawnSync2(
8398
+ "git",
8399
+ ["diff", "--name-only", mergeBaseResult.mergeBase, "HEAD", "--", "."],
8400
+ {
8401
+ cwd: resolvedWorktreePath,
8402
+ encoding: "utf8"
8403
+ }
8404
+ );
8405
+ resolvedChangedPaths = diffResult.stdout.split(/\r?\n/).filter(Boolean);
8406
+ }
8407
+ const scopeResult = validateFinalReviewRetouchScope(resolvedChangedPaths);
8408
+ if (!scopeResult.ok) {
7736
8409
  writePrdRunRecord(options.repoRoot, {
7737
8410
  ...record,
7738
8411
  status: "blocked",
7739
8412
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7740
8413
  blockedGate: "final-review",
7741
- blockedReason: artifactResult.reason,
7742
- diagnostics: artifactResult.diagnostics,
8414
+ blockedReason: scopeResult.reason,
8415
+ diagnostics: scopeResult.diagnostics,
8416
+ targetName,
8417
+ finalReview: {
8418
+ status: "blocked",
8419
+ targetName,
8420
+ prdBranch,
8421
+ mergeBase: mergeBaseResult.mergeBase,
8422
+ verdict: "pass_with_retouch",
8423
+ diagnostics: scopeResult.diagnostics,
8424
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8425
+ },
8426
+ offendingPaths: scopeResult.offendingPaths
8427
+ });
8428
+ return {
8429
+ prdRef,
8430
+ status: "blocked",
8431
+ blockedGate: "final-review",
8432
+ blockedReason: scopeResult.reason,
8433
+ diagnostics: scopeResult.diagnostics,
8434
+ offendingPaths: scopeResult.offendingPaths
8435
+ };
8436
+ }
8437
+ let retouchPr;
8438
+ try {
8439
+ retouchPr = await createOrReuseFinalReviewRetouchPr({
8440
+ repoRoot: options.repoRoot,
8441
+ sourceWorktreePath: resolvedWorktreePath,
8442
+ prdRef,
8443
+ changedPaths: scopeResult.changedPaths,
8444
+ summary,
8445
+ prProvider: options.prProvider
8446
+ });
8447
+ } catch (error) {
8448
+ const msg = error instanceof Error ? error.message : String(error);
8449
+ writePrdRunRecord(options.repoRoot, {
8450
+ ...record,
8451
+ status: "blocked",
8452
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8453
+ blockedGate: "final-review",
8454
+ blockedReason: `Failed to create retouch PR: ${msg}`,
8455
+ diagnostics: [msg],
7743
8456
  targetName,
7744
8457
  finalReview: {
7745
8458
  status: "blocked",
7746
8459
  targetName,
7747
8460
  prdBranch,
7748
8461
  mergeBase: mergeBaseResult.mergeBase,
7749
- diagnostics: artifactResult.diagnostics,
8462
+ verdict: "pass_with_retouch",
8463
+ diagnostics: [msg],
7750
8464
  reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
7751
8465
  },
7752
8466
  offendingPaths: []
@@ -7755,501 +8469,555 @@ async function runPrdRunFinalReviewCommand(options) {
7755
8469
  prdRef,
7756
8470
  status: "blocked",
7757
8471
  blockedGate: "final-review",
7758
- blockedReason: artifactResult.reason,
7759
- diagnostics: artifactResult.diagnostics,
8472
+ blockedReason: `Failed to create retouch PR: ${msg}`,
8473
+ diagnostics: [msg],
7760
8474
  offendingPaths: []
7761
8475
  };
7762
8476
  }
7763
- const {
7764
- verdict,
7765
- summary,
7766
- diagnostics: artifactDiagnostics
7767
- } = artifactResult;
7768
- if (verdict === "pass_no_changes") {
7769
- const receipt = {
7770
- status: "succeeded",
8477
+ if (!autoMerge) {
8478
+ const receipt2 = {
8479
+ status: "needs_human_review",
7771
8480
  targetName,
7772
8481
  prdBranch,
7773
8482
  mergeBase: mergeBaseResult.mergeBase,
7774
- verdict: "pass_no_changes",
8483
+ verdict: "pass_with_retouch",
7775
8484
  artifactPath: ".pourkit/final-review-artifact.json",
7776
8485
  diagnostics: [],
7777
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8486
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
8487
+ retouchPrNumber: retouchPr.number,
8488
+ retouchPrUrl: retouchPr.url,
8489
+ autoMerge: false,
8490
+ changedPaths: scopeResult.changedPaths
7778
8491
  };
7779
8492
  writePrdRunRecord(options.repoRoot, {
7780
8493
  ...record,
7781
- status: "final_reviewed",
8494
+ status: "blocked",
7782
8495
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8496
+ blockedGate: "final-review",
8497
+ blockedReason: `Retouch PR #${retouchPr.number} created but auto-merge disabled. PRD Final Review will not advance until the PR is merged.`,
8498
+ diagnostics: [
8499
+ `Retouch PR #${retouchPr.number}: ${retouchPr.url}`,
8500
+ "Auto-merge disabled. Merge the PR manually or rerun with auto-merge enabled."
8501
+ ],
7783
8502
  targetName,
7784
- start: record.start,
7785
- planning: record.planning,
7786
- finalReview: receipt
8503
+ finalReview: receipt2,
8504
+ offendingPaths: []
7787
8505
  });
7788
8506
  return {
7789
8507
  prdRef,
7790
- status: "final_reviewed",
7791
- finalReview: receipt,
7792
- diagnostics: []
8508
+ status: "needs_human_review",
8509
+ finalReview: receipt2,
8510
+ diagnostics: [
8511
+ `Retouch PR #${retouchPr.number}: ${retouchPr.url}`,
8512
+ "Auto-merge disabled. Merge the PR manually or rerun with auto-merge enabled."
8513
+ ]
7793
8514
  };
7794
8515
  }
7795
- if (verdict === "pass_with_retouch") {
7796
- if (!options.prProvider) {
7797
- const reason = "Missing PRProvider. Cannot create retouch PR without a PR provider.";
7798
- writePrdRunRecord(options.repoRoot, {
7799
- ...record,
7800
- status: "blocked",
7801
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7802
- blockedGate: "final-review",
7803
- blockedReason: reason,
7804
- diagnostics: [reason],
7805
- targetName,
7806
- finalReview: {
7807
- status: "blocked",
7808
- targetName,
7809
- prdBranch,
7810
- mergeBase: mergeBaseResult.mergeBase,
7811
- verdict: "pass_with_retouch",
7812
- diagnostics: [reason],
7813
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
7814
- },
7815
- offendingPaths: []
7816
- });
7817
- return {
7818
- prdRef,
7819
- status: "blocked",
7820
- blockedGate: "final-review",
7821
- blockedReason: reason,
7822
- diagnostics: [reason],
7823
- offendingPaths: []
7824
- };
7825
- }
7826
- const autoMerge = options.autoMerge ?? true;
7827
- const existingRetouchPrNumber = record.finalReview?.retouchPrNumber;
7828
- if (existingRetouchPrNumber && autoMerge) {
7829
- const existingPr = await getPrByNumberIfAvailable(
7830
- options.prProvider,
7831
- existingRetouchPrNumber
7832
- );
7833
- if (existingPr?.state === "MERGED" && existingPr.mergeCommitSha) {
7834
- const receipt2 = {
7835
- status: "final_reviewed",
7836
- targetName,
7837
- prdBranch,
7838
- mergeBase: mergeBaseResult.mergeBase,
7839
- verdict: "pass_with_retouch",
7840
- artifactPath: ".pourkit/final-review-artifact.json",
7841
- diagnostics: [],
7842
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
7843
- retouchPrNumber: existingRetouchPrNumber,
7844
- retouchPrUrl: existingPr.url,
7845
- retouchMergeCommit: existingPr.mergeCommitSha,
7846
- autoMerge,
7847
- changedPaths: record.finalReview?.changedPaths ?? []
7848
- };
7849
- writePrdRunRecord(options.repoRoot, {
7850
- ...record,
7851
- status: "final_reviewed",
7852
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7853
- targetName,
7854
- start: record.start,
7855
- planning: record.planning,
7856
- finalReview: receipt2
7857
- });
7858
- return {
7859
- prdRef,
7860
- status: "final_reviewed",
7861
- finalReview: receipt2,
7862
- diagnostics: []
7863
- };
7864
- }
7865
- }
7866
- const changedPathsFromArtifact = artifactResult.changedPaths;
7867
- let resolvedChangedPaths;
7868
- if (changedPathsFromArtifact && changedPathsFromArtifact.length > 0) {
7869
- resolvedChangedPaths = changedPathsFromArtifact;
7870
- } else {
7871
- const diffResult = spawnSync2(
7872
- "git",
7873
- ["diff", "--name-only", mergeBaseResult.mergeBase, "HEAD", "--", "."],
7874
- {
7875
- cwd: worktreePath,
7876
- encoding: "utf8"
7877
- }
7878
- );
7879
- resolvedChangedPaths = diffResult.stdout.split(/\r?\n/).filter(Boolean);
7880
- }
7881
- const scopeResult = validateFinalReviewRetouchScope(resolvedChangedPaths);
7882
- if (!scopeResult.ok) {
7883
- writePrdRunRecord(options.repoRoot, {
7884
- ...record,
7885
- status: "blocked",
7886
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7887
- blockedGate: "final-review",
7888
- blockedReason: scopeResult.reason,
7889
- diagnostics: scopeResult.diagnostics,
7890
- targetName,
7891
- finalReview: {
7892
- status: "blocked",
7893
- targetName,
7894
- prdBranch,
7895
- mergeBase: mergeBaseResult.mergeBase,
7896
- verdict: "pass_with_retouch",
7897
- diagnostics: scopeResult.diagnostics,
7898
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
7899
- },
7900
- offendingPaths: scopeResult.offendingPaths
7901
- });
7902
- return {
7903
- prdRef,
7904
- status: "blocked",
7905
- blockedGate: "final-review",
7906
- blockedReason: scopeResult.reason,
7907
- diagnostics: scopeResult.diagnostics,
7908
- offendingPaths: scopeResult.offendingPaths
7909
- };
7910
- }
7911
- let retouchPr;
7912
- try {
7913
- retouchPr = await createOrReuseFinalReviewRetouchPr({
7914
- repoRoot: options.repoRoot,
7915
- sourceWorktreePath: worktreePath,
7916
- prdRef,
7917
- changedPaths: scopeResult.changedPaths,
7918
- summary,
7919
- prProvider: options.prProvider
7920
- });
7921
- } catch (error) {
7922
- const msg = error instanceof Error ? error.message : String(error);
7923
- writePrdRunRecord(options.repoRoot, {
7924
- ...record,
7925
- status: "blocked",
7926
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7927
- blockedGate: "final-review",
7928
- blockedReason: `Failed to create retouch PR: ${msg}`,
7929
- diagnostics: [msg],
7930
- targetName,
7931
- finalReview: {
7932
- status: "blocked",
7933
- targetName,
7934
- prdBranch,
7935
- mergeBase: mergeBaseResult.mergeBase,
7936
- verdict: "pass_with_retouch",
7937
- diagnostics: [msg],
7938
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
7939
- },
7940
- offendingPaths: []
7941
- });
7942
- return {
7943
- prdRef,
7944
- status: "blocked",
7945
- blockedGate: "final-review",
7946
- blockedReason: `Failed to create retouch PR: ${msg}`,
7947
- diagnostics: [msg],
7948
- offendingPaths: []
7949
- };
7950
- }
7951
- if (!autoMerge) {
7952
- const receipt2 = {
7953
- status: "needs_human_review",
7954
- targetName,
7955
- prdBranch,
7956
- mergeBase: mergeBaseResult.mergeBase,
7957
- verdict: "pass_with_retouch",
7958
- artifactPath: ".pourkit/final-review-artifact.json",
7959
- diagnostics: [],
7960
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
7961
- retouchPrNumber: retouchPr.number,
7962
- retouchPrUrl: retouchPr.url,
7963
- autoMerge: false,
7964
- changedPaths: scopeResult.changedPaths
7965
- };
7966
- writePrdRunRecord(options.repoRoot, {
7967
- ...record,
7968
- status: "blocked",
7969
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7970
- blockedGate: "final-review",
7971
- blockedReason: `Retouch PR #${retouchPr.number} created but auto-merge disabled. PRD Final Review will not advance until the PR is merged.`,
7972
- diagnostics: [
7973
- `Retouch PR #${retouchPr.number}: ${retouchPr.url}`,
7974
- "Auto-merge disabled. Merge the PR manually or rerun with auto-merge enabled."
7975
- ],
7976
- targetName,
7977
- finalReview: receipt2,
7978
- offendingPaths: []
7979
- });
7980
- return {
7981
- prdRef,
7982
- status: "needs_human_review",
7983
- finalReview: receipt2,
7984
- diagnostics: [
7985
- `Retouch PR #${retouchPr.number}: ${retouchPr.url}`,
7986
- "Auto-merge disabled. Merge the PR manually or rerun with auto-merge enabled."
7987
- ]
7988
- };
7989
- }
7990
- try {
7991
- await options.prProvider.waitForPrChecks(retouchPr.number);
7992
- } catch (error) {
7993
- const msg = error instanceof Error ? error.message : String(error);
7994
- const receipt2 = {
7995
- status: "started",
7996
- targetName,
7997
- prdBranch,
7998
- mergeBase: mergeBaseResult.mergeBase,
7999
- verdict: "pass_with_retouch",
8000
- artifactPath: ".pourkit/final-review-artifact.json",
8001
- diagnostics: [msg],
8002
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
8003
- retouchPrNumber: retouchPr.number,
8004
- retouchPrUrl: retouchPr.url,
8005
- autoMerge: true,
8006
- changedPaths: scopeResult.changedPaths
8007
- };
8008
- writePrdRunRecord(options.repoRoot, {
8009
- ...record,
8010
- status: "blocked",
8011
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8012
- blockedGate: "final-review",
8013
- blockedReason: `Retouch PR #${retouchPr.number} checks failed: ${msg}`,
8014
- diagnostics: [msg],
8015
- targetName,
8016
- finalReview: receipt2,
8017
- offendingPaths: []
8018
- });
8019
- return {
8020
- prdRef,
8021
- status: "blocked",
8022
- blockedGate: "final-review",
8023
- blockedReason: `Retouch PR #${retouchPr.number} checks failed: ${msg}`,
8024
- diagnostics: [msg],
8025
- offendingPaths: []
8026
- };
8027
- }
8028
- try {
8029
- await options.prProvider.mergePr(retouchPr.number, {
8030
- method: "squash"
8031
- });
8032
- } catch (error) {
8033
- const msg = error instanceof Error ? error.message : String(error);
8034
- const receipt2 = {
8035
- status: "started",
8036
- targetName,
8037
- prdBranch,
8038
- mergeBase: mergeBaseResult.mergeBase,
8039
- verdict: "pass_with_retouch",
8040
- artifactPath: ".pourkit/final-review-artifact.json",
8041
- diagnostics: [msg],
8042
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
8043
- retouchPrNumber: retouchPr.number,
8044
- retouchPrUrl: retouchPr.url,
8045
- autoMerge: true,
8046
- changedPaths: scopeResult.changedPaths
8047
- };
8048
- writePrdRunRecord(options.repoRoot, {
8049
- ...record,
8050
- status: "blocked",
8051
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8052
- blockedGate: "final-review",
8053
- blockedReason: `Retouch PR #${retouchPr.number} merge failed: ${msg}`,
8054
- diagnostics: [msg],
8055
- targetName,
8056
- finalReview: receipt2,
8057
- offendingPaths: []
8058
- });
8059
- return {
8060
- prdRef,
8061
- status: "blocked",
8062
- blockedGate: "final-review",
8063
- blockedReason: `Retouch PR #${retouchPr.number} merge failed: ${msg}`,
8064
- diagnostics: [msg],
8065
- offendingPaths: []
8066
- };
8067
- }
8068
- let mergedPr = null;
8069
- for (let attempt = 0; attempt < 3; attempt += 1) {
8070
- try {
8071
- const candidate = await options.prProvider.getPr(
8072
- retouchPr.headRefName
8073
- );
8074
- if (candidate?.state === "MERGED" && candidate.mergeCommitSha) {
8075
- mergedPr = candidate;
8076
- break;
8077
- }
8078
- } catch {
8079
- }
8080
- if (attempt < 2) {
8081
- await sleep(1e3);
8082
- }
8083
- }
8084
- const mergeCommitSha = mergedPr?.mergeCommitSha;
8085
- if (!mergeCommitSha) {
8086
- const receipt2 = {
8087
- status: "started",
8088
- targetName,
8089
- prdBranch,
8090
- mergeBase: mergeBaseResult.mergeBase,
8091
- verdict: "pass_with_retouch",
8092
- artifactPath: ".pourkit/final-review-artifact.json",
8093
- diagnostics: [
8094
- `Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
8095
- ],
8096
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
8097
- retouchPrNumber: retouchPr.number,
8098
- retouchPrUrl: retouchPr.url,
8099
- autoMerge: true,
8100
- changedPaths: scopeResult.changedPaths
8101
- };
8102
- writePrdRunRecord(options.repoRoot, {
8103
- ...record,
8104
- status: "blocked",
8105
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8106
- blockedGate: "final-review",
8107
- blockedReason: `Retouch PR #${retouchPr.number} merged without exposing a merge commit SHA.`,
8108
- diagnostics: [
8109
- `Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
8110
- ],
8111
- targetName,
8112
- finalReview: receipt2,
8113
- offendingPaths: []
8114
- });
8115
- return {
8116
- prdRef,
8117
- status: "blocked",
8118
- blockedGate: "final-review",
8119
- blockedReason: `Retouch PR #${retouchPr.number} merged without exposing a merge commit SHA.`,
8120
- diagnostics: [
8121
- `Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
8122
- ],
8123
- offendingPaths: []
8124
- };
8125
- }
8126
- const receipt = {
8127
- status: "final_reviewed",
8516
+ try {
8517
+ await options.prProvider.waitForPrChecks(retouchPr.number);
8518
+ } catch (error) {
8519
+ const msg = error instanceof Error ? error.message : String(error);
8520
+ const receipt2 = {
8521
+ status: "started",
8128
8522
  targetName,
8129
8523
  prdBranch,
8130
8524
  mergeBase: mergeBaseResult.mergeBase,
8131
8525
  verdict: "pass_with_retouch",
8132
8526
  artifactPath: ".pourkit/final-review-artifact.json",
8133
- diagnostics: [],
8527
+ diagnostics: [msg],
8134
8528
  reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
8135
8529
  retouchPrNumber: retouchPr.number,
8136
8530
  retouchPrUrl: retouchPr.url,
8137
- retouchMergeCommit: mergeCommitSha,
8138
8531
  autoMerge: true,
8139
8532
  changedPaths: scopeResult.changedPaths
8140
8533
  };
8141
8534
  writePrdRunRecord(options.repoRoot, {
8142
8535
  ...record,
8143
- status: "final_reviewed",
8536
+ status: "blocked",
8144
8537
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8538
+ blockedGate: "final-review",
8539
+ blockedReason: `Retouch PR #${retouchPr.number} checks failed: ${msg}`,
8540
+ diagnostics: [msg],
8145
8541
  targetName,
8146
- start: record.start,
8147
- planning: record.planning,
8148
- finalReview: receipt
8542
+ finalReview: receipt2,
8543
+ offendingPaths: []
8149
8544
  });
8150
8545
  return {
8151
8546
  prdRef,
8152
- status: "final_reviewed",
8153
- finalReview: receipt,
8154
- diagnostics: []
8547
+ status: "blocked",
8548
+ blockedGate: "final-review",
8549
+ blockedReason: `Retouch PR #${retouchPr.number} checks failed: ${msg}`,
8550
+ diagnostics: [msg],
8551
+ offendingPaths: []
8155
8552
  };
8156
8553
  }
8157
- if (verdict === "needs_human_review") {
8158
- const reason = summary || "Final Review requires human review.";
8159
- const receipt = {
8160
- status: "needs_human_review",
8554
+ try {
8555
+ await options.prProvider.mergePr(retouchPr.number, {
8556
+ method: "squash"
8557
+ });
8558
+ } catch (error) {
8559
+ const msg = error instanceof Error ? error.message : String(error);
8560
+ const receipt2 = {
8561
+ status: "started",
8161
8562
  targetName,
8162
8563
  prdBranch,
8163
8564
  mergeBase: mergeBaseResult.mergeBase,
8164
- verdict: "needs_human_review",
8565
+ verdict: "pass_with_retouch",
8165
8566
  artifactPath: ".pourkit/final-review-artifact.json",
8166
- diagnostics: artifactDiagnostics.length > 0 ? artifactDiagnostics : [reason],
8167
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8567
+ diagnostics: [msg],
8568
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
8569
+ retouchPrNumber: retouchPr.number,
8570
+ retouchPrUrl: retouchPr.url,
8571
+ autoMerge: true,
8572
+ changedPaths: scopeResult.changedPaths
8168
8573
  };
8169
8574
  writePrdRunRecord(options.repoRoot, {
8170
8575
  ...record,
8171
8576
  status: "blocked",
8172
8577
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8173
8578
  blockedGate: "final-review",
8174
- blockedReason: reason,
8175
- diagnostics: [reason],
8579
+ blockedReason: `Retouch PR #${retouchPr.number} merge failed: ${msg}`,
8580
+ diagnostics: [msg],
8176
8581
  targetName,
8177
- finalReview: receipt,
8582
+ finalReview: receipt2,
8178
8583
  offendingPaths: []
8179
8584
  });
8180
8585
  return {
8181
8586
  prdRef,
8182
- status: "needs_human_review",
8183
- finalReview: receipt,
8184
- diagnostics: [reason]
8587
+ status: "blocked",
8588
+ blockedGate: "final-review",
8589
+ blockedReason: `Retouch PR #${retouchPr.number} merge failed: ${msg}`,
8590
+ diagnostics: [msg],
8591
+ offendingPaths: []
8185
8592
  };
8186
8593
  }
8187
- if (verdict === "blocked") {
8188
- const reason = summary || "Final Review blocked.";
8189
- const receipt = {
8190
- status: "blocked",
8594
+ let mergedPr = null;
8595
+ for (let attempt = 0; attempt < 3; attempt += 1) {
8596
+ try {
8597
+ const candidate = await options.prProvider.getPr(
8598
+ retouchPr.headRefName
8599
+ );
8600
+ if (candidate?.state === "MERGED" && candidate.mergeCommitSha) {
8601
+ mergedPr = candidate;
8602
+ break;
8603
+ }
8604
+ } catch {
8605
+ }
8606
+ if (attempt < 2) {
8607
+ await sleep(1e3);
8608
+ }
8609
+ }
8610
+ const mergeCommitSha = mergedPr?.mergeCommitSha;
8611
+ if (!mergeCommitSha) {
8612
+ const receipt2 = {
8613
+ status: "started",
8191
8614
  targetName,
8192
8615
  prdBranch,
8193
8616
  mergeBase: mergeBaseResult.mergeBase,
8194
- verdict: "blocked",
8617
+ verdict: "pass_with_retouch",
8195
8618
  artifactPath: ".pourkit/final-review-artifact.json",
8196
- diagnostics: artifactDiagnostics.length > 0 ? artifactDiagnostics : [reason],
8197
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8619
+ diagnostics: [
8620
+ `Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
8621
+ ],
8622
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
8623
+ retouchPrNumber: retouchPr.number,
8624
+ retouchPrUrl: retouchPr.url,
8625
+ autoMerge: true,
8626
+ changedPaths: scopeResult.changedPaths
8198
8627
  };
8199
8628
  writePrdRunRecord(options.repoRoot, {
8200
8629
  ...record,
8201
8630
  status: "blocked",
8202
8631
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8203
8632
  blockedGate: "final-review",
8204
- blockedReason: reason,
8205
- diagnostics: [reason],
8633
+ blockedReason: `Retouch PR #${retouchPr.number} merged without exposing a merge commit SHA.`,
8634
+ diagnostics: [
8635
+ `Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
8636
+ ],
8206
8637
  targetName,
8207
- finalReview: receipt,
8638
+ finalReview: receipt2,
8208
8639
  offendingPaths: []
8209
8640
  });
8210
8641
  return {
8211
8642
  prdRef,
8212
8643
  status: "blocked",
8213
8644
  blockedGate: "final-review",
8214
- blockedReason: reason,
8215
- diagnostics: [reason],
8645
+ blockedReason: `Retouch PR #${retouchPr.number} merged without exposing a merge commit SHA.`,
8646
+ diagnostics: [
8647
+ `Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
8648
+ ],
8216
8649
  offendingPaths: []
8217
8650
  };
8218
8651
  }
8219
- const unsupportedReason = `Final Review returned unsupported verdict "${verdict}".`;
8652
+ const receipt = {
8653
+ status: "final_reviewed",
8654
+ targetName,
8655
+ prdBranch,
8656
+ mergeBase: mergeBaseResult.mergeBase,
8657
+ verdict: "pass_with_retouch",
8658
+ artifactPath: ".pourkit/final-review-artifact.json",
8659
+ diagnostics: [],
8660
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
8661
+ retouchPrNumber: retouchPr.number,
8662
+ retouchPrUrl: retouchPr.url,
8663
+ retouchMergeCommit: mergeCommitSha,
8664
+ autoMerge: true,
8665
+ changedPaths: scopeResult.changedPaths
8666
+ };
8667
+ writePrdRunRecord(options.repoRoot, {
8668
+ ...record,
8669
+ status: "final_reviewed",
8670
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8671
+ targetName,
8672
+ start: record.start,
8673
+ planning: record.planning,
8674
+ finalReview: receipt
8675
+ });
8676
+ return {
8677
+ prdRef,
8678
+ status: "final_reviewed",
8679
+ finalReview: receipt,
8680
+ diagnostics: []
8681
+ };
8682
+ }
8683
+ if (verdict === "needs_human_review") {
8684
+ const reason = summary || "Final Review requires human review.";
8685
+ const receipt = {
8686
+ status: "needs_human_review",
8687
+ targetName,
8688
+ prdBranch,
8689
+ mergeBase: mergeBaseResult.mergeBase,
8690
+ verdict: "needs_human_review",
8691
+ artifactPath: ".pourkit/final-review-artifact.json",
8692
+ diagnostics: artifactDiagnostics.length > 0 ? artifactDiagnostics : [reason],
8693
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8694
+ };
8220
8695
  writePrdRunRecord(options.repoRoot, {
8221
8696
  ...record,
8222
8697
  status: "blocked",
8223
8698
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8224
8699
  blockedGate: "final-review",
8225
- blockedReason: unsupportedReason,
8226
- diagnostics: [unsupportedReason],
8700
+ blockedReason: reason,
8701
+ diagnostics: [reason],
8227
8702
  targetName,
8228
- finalReview: {
8229
- status: "blocked",
8230
- targetName,
8231
- prdBranch,
8232
- mergeBase: mergeBaseResult.mergeBase,
8233
- diagnostics: [unsupportedReason],
8234
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8235
- },
8703
+ finalReview: receipt,
8236
8704
  offendingPaths: []
8237
8705
  });
8238
8706
  return {
8239
8707
  prdRef,
8708
+ status: "needs_human_review",
8709
+ finalReview: receipt,
8710
+ diagnostics: [reason]
8711
+ };
8712
+ }
8713
+ if (verdict === "blocked") {
8714
+ const reason = summary || "Final Review blocked.";
8715
+ const receipt = {
8240
8716
  status: "blocked",
8717
+ targetName,
8718
+ prdBranch,
8719
+ mergeBase: mergeBaseResult.mergeBase,
8720
+ verdict: "blocked",
8721
+ artifactPath: ".pourkit/final-review-artifact.json",
8722
+ diagnostics: artifactDiagnostics.length > 0 ? artifactDiagnostics : [reason],
8723
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8724
+ };
8725
+ writePrdRunRecord(options.repoRoot, {
8726
+ ...record,
8727
+ status: "blocked",
8728
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8241
8729
  blockedGate: "final-review",
8242
- blockedReason: unsupportedReason,
8730
+ blockedReason: reason,
8731
+ diagnostics: [reason],
8732
+ targetName,
8733
+ finalReview: receipt,
8734
+ offendingPaths: []
8735
+ });
8736
+ return {
8737
+ prdRef,
8738
+ status: "blocked",
8739
+ blockedGate: "final-review",
8740
+ blockedReason: reason,
8741
+ diagnostics: [reason],
8742
+ offendingPaths: []
8743
+ };
8744
+ }
8745
+ const unsupportedReason = `Final Review returned unsupported verdict "${verdict}".`;
8746
+ writePrdRunRecord(options.repoRoot, {
8747
+ ...record,
8748
+ status: "blocked",
8749
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8750
+ blockedGate: "final-review",
8751
+ blockedReason: unsupportedReason,
8752
+ diagnostics: [unsupportedReason],
8753
+ targetName,
8754
+ finalReview: {
8755
+ status: "blocked",
8756
+ targetName,
8757
+ prdBranch,
8758
+ mergeBase: mergeBaseResult.mergeBase,
8243
8759
  diagnostics: [unsupportedReason],
8760
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
8761
+ },
8762
+ offendingPaths: []
8763
+ });
8764
+ return {
8765
+ prdRef,
8766
+ status: "blocked",
8767
+ blockedGate: "final-review",
8768
+ blockedReason: unsupportedReason,
8769
+ diagnostics: [unsupportedReason],
8770
+ offendingPaths: []
8771
+ };
8772
+ }
8773
+ function runPrdRunValidateFinalReviewCommand(options) {
8774
+ const prdRef = normalizePrdRunRef2(options.prdRef);
8775
+ const checkoutBase = options.checkoutBase ?? prdRef;
8776
+ const artifactPath = options.artifactPath ? options.artifactPath : join15(options.repoRoot, ".pourkit", "final-review-artifact.json");
8777
+ const artifact = parseFinalReviewArtifact(artifactPath);
8778
+ if (!artifact.ok) {
8779
+ return {
8780
+ prdRef,
8781
+ status: "invalid",
8782
+ artifactPath,
8783
+ reason: artifact.reason,
8784
+ diagnostics: artifact.diagnostics,
8244
8785
  offendingPaths: []
8245
8786
  };
8246
- } finally {
8247
- spawnSync2("git", ["worktree", "remove", "--force", worktreePath], {
8248
- cwd: options.repoRoot,
8249
- encoding: "utf8"
8787
+ }
8788
+ let changedPaths = options.changedPaths;
8789
+ if (artifact.verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && (!artifact.changedPaths || artifact.changedPaths.length === 0)) {
8790
+ const diffResult = spawnSync2(
8791
+ "git",
8792
+ ["diff", "--name-only", options.reviewBase, "HEAD", "--", "."],
8793
+ { cwd: options.repoRoot, encoding: "utf8" }
8794
+ );
8795
+ if (diffResult.status !== 0) {
8796
+ return {
8797
+ prdRef,
8798
+ status: "invalid",
8799
+ artifactPath,
8800
+ reason: "Final Review retouch path discovery failed.",
8801
+ diagnostics: collectSpawnDiagnostics(diffResult, "git diff failed"),
8802
+ offendingPaths: []
8803
+ };
8804
+ }
8805
+ changedPaths = diffResult.stdout.split(/\r?\n/).filter(Boolean);
8806
+ }
8807
+ const result = validateFinalReviewAgentOutputs({
8808
+ artifactPath,
8809
+ context: {
8810
+ prdRef,
8811
+ prdBranch: checkoutBase,
8812
+ mergeBase: options.reviewBase
8813
+ },
8814
+ changedPaths
8815
+ });
8816
+ if (!result.ok) {
8817
+ return {
8818
+ prdRef,
8819
+ status: "invalid",
8820
+ artifactPath,
8821
+ reason: result.reason,
8822
+ diagnostics: result.diagnostics,
8823
+ offendingPaths: result.offendingPaths
8824
+ };
8825
+ }
8826
+ return {
8827
+ prdRef,
8828
+ status: "valid",
8829
+ artifactPath,
8830
+ verdict: result.artifact.verdict,
8831
+ diagnostics: [],
8832
+ changedPaths: result.retouch?.changedPaths
8833
+ };
8834
+ }
8835
+ async function runPrdRunReconcileCommand(options) {
8836
+ const prdRef = normalizePrdRunRef2(options.prdRef);
8837
+ const targetName = options.targetName.trim();
8838
+ if (!targetName) {
8839
+ throw new Error(
8840
+ `Invalid target name "${options.targetName}". Expected a non-empty target name.`
8841
+ );
8842
+ }
8843
+ const { record, diagnostics: readDiagnostics } = readPrdRun(
8844
+ options.repoRoot,
8845
+ prdRef
8846
+ );
8847
+ if (readDiagnostics.length > 0) {
8848
+ const reason = `PRD Run ${prdRef} has a malformed record and cannot reconcile.`;
8849
+ return {
8850
+ prdRef,
8851
+ status: "blocked",
8852
+ blockedGate: "reconciliation",
8853
+ blockedReason: reason,
8854
+ diagnostics: readDiagnostics,
8855
+ offendingPaths: []
8856
+ };
8857
+ }
8858
+ if (!record) {
8859
+ return {
8860
+ prdRef,
8861
+ status: "blocked",
8862
+ blockedGate: "reconciliation",
8863
+ blockedReason: `PRD Run ${prdRef} does not exist. Run prd-run prepare/start/launch first.`,
8864
+ diagnostics: [`No PRD Run record found for ${prdRef}`],
8865
+ offendingPaths: []
8866
+ };
8867
+ }
8868
+ if (record.status !== "final_reviewed" && record.blockedGate !== "reconciliation") {
8869
+ const reason = `PRD Run ${prdRef} has not completed Final Review. Reconciliation requires status "final_reviewed", got "${record.status}".`;
8870
+ return {
8871
+ prdRef,
8872
+ status: "blocked",
8873
+ blockedGate: "reconciliation",
8874
+ blockedReason: reason,
8875
+ diagnostics: [`Current status: ${record.status}`],
8876
+ offendingPaths: []
8877
+ };
8878
+ }
8879
+ if (!record.finalReview) {
8880
+ const reason = `PRD Run ${prdRef} has no Final Review receipt. Cannot reconcile without a completed Final Review.`;
8881
+ return {
8882
+ prdRef,
8883
+ status: "blocked",
8884
+ blockedGate: "reconciliation",
8885
+ blockedReason: reason,
8886
+ diagnostics: ["Missing finalReview receipt in PRD Run record."],
8887
+ offendingPaths: []
8888
+ };
8889
+ }
8890
+ const finalReviewReceipt = record.finalReview;
8891
+ if (finalReviewReceipt.status !== "final_reviewed" && finalReviewReceipt.status !== "succeeded") {
8892
+ const reason = `PRD Run ${prdRef} Final Review receipt has status "${finalReviewReceipt.status}"; expected "final_reviewed" or "succeeded".`;
8893
+ return {
8894
+ prdRef,
8895
+ status: "blocked",
8896
+ blockedGate: "reconciliation",
8897
+ blockedReason: reason,
8898
+ diagnostics: [
8899
+ `Final Review status: ${finalReviewReceipt.status}`,
8900
+ `Verdict: ${finalReviewReceipt.verdict ?? "unknown"}`
8901
+ ],
8902
+ offendingPaths: []
8903
+ };
8904
+ }
8905
+ const prdBranch = record.prdBranch ?? prdRef;
8906
+ const mergeBase = finalReviewReceipt.mergeBase;
8907
+ if (!mergeBase || mergeBase.length < 6) {
8908
+ const reason = `PRD Run ${prdRef} Final Review receipt is missing a valid merge base.`;
8909
+ return {
8910
+ prdRef,
8911
+ status: "blocked",
8912
+ blockedGate: "reconciliation",
8913
+ blockedReason: reason,
8914
+ diagnostics: [
8915
+ mergeBase ? `Merge base "${mergeBase}" is too short.` : "Merge base is missing."
8916
+ ],
8917
+ offendingPaths: []
8918
+ };
8919
+ }
8920
+ const manifestResult = readPlanningArtifactManifest(options.repoRoot, prdRef);
8921
+ if (!manifestResult.ok) {
8922
+ const reason = `Reconciliation Evidence Packet construction failed: ${manifestResult.reason}`;
8923
+ return {
8924
+ prdRef,
8925
+ status: "blocked",
8926
+ blockedGate: "reconciliation",
8927
+ blockedReason: reason,
8928
+ diagnostics: manifestResult.diagnostics,
8929
+ offendingPaths: manifestResult.offendingPaths
8930
+ };
8931
+ }
8932
+ const manifest = manifestResult.manifest;
8933
+ let evidencePacket;
8934
+ try {
8935
+ evidencePacket = buildEvidencePacket({
8936
+ prdRef,
8937
+ prdBranch,
8938
+ mergeBase,
8939
+ planningManifestPath: manifest.manifestPath,
8940
+ planningManifestFacts: {
8941
+ parentPrdIssueUrl: manifest.parentIssue.url,
8942
+ childIssueCount: manifest.childIssues.length
8943
+ },
8944
+ stage: "prdReconciliation",
8945
+ stageReceipts: { finalReviewReceipt }
8250
8946
  });
8251
- rmSync3(worktreePath, { recursive: true, force: true });
8947
+ } catch (error) {
8948
+ const msg = error instanceof Error ? error.message : String(error);
8949
+ const reason = `Reconciliation Evidence Packet construction failed: ${msg}`;
8950
+ return {
8951
+ prdRef,
8952
+ status: "blocked",
8953
+ blockedGate: "reconciliation",
8954
+ blockedReason: reason,
8955
+ diagnostics: [msg],
8956
+ offendingPaths: []
8957
+ };
8958
+ }
8959
+ if (evidencePacket.stage !== "prdReconciliation") {
8960
+ return {
8961
+ prdRef,
8962
+ status: "blocked",
8963
+ blockedGate: "reconciliation",
8964
+ blockedReason: `Evidence Packet stage is "${evidencePacket.stage}", expected "prdReconciliation".`,
8965
+ diagnostics: [`Stage: ${evidencePacket.stage}`],
8966
+ offendingPaths: []
8967
+ };
8252
8968
  }
8969
+ const expectedRef = prdRef;
8970
+ if (evidencePacket.prdRef !== expectedRef) {
8971
+ return {
8972
+ prdRef,
8973
+ status: "blocked",
8974
+ blockedGate: "reconciliation",
8975
+ blockedReason: `Evidence Packet PRD ref "${evidencePacket.prdRef}" does not match active PRD Run "${expectedRef}".`,
8976
+ diagnostics: [
8977
+ `Evidence Packet PRD ref: ${evidencePacket.prdRef}`,
8978
+ `Expected: ${expectedRef}`
8979
+ ],
8980
+ offendingPaths: []
8981
+ };
8982
+ }
8983
+ if (evidencePacket.prdBranch !== prdBranch) {
8984
+ return {
8985
+ prdRef,
8986
+ status: "blocked",
8987
+ blockedGate: "reconciliation",
8988
+ blockedReason: `Evidence Packet PRD branch "${evidencePacket.prdBranch}" does not match active PRD Branch "${prdBranch}".`,
8989
+ diagnostics: [
8990
+ `Evidence Packet branch: ${evidencePacket.prdBranch}`,
8991
+ `Expected: ${prdBranch}`
8992
+ ],
8993
+ offendingPaths: []
8994
+ };
8995
+ }
8996
+ const receipt = {
8997
+ status: "blocked",
8998
+ targetName,
8999
+ prdBranch,
9000
+ mergeBase,
9001
+ diagnostics: [
9002
+ "Reconciliation automation is not fully implemented. Full reconciliation requires Worktree creation, Architect invocation, output validation, and PR creation, none of which are implemented in this version."
9003
+ ]
9004
+ };
9005
+ writePrdRunRecord(options.repoRoot, {
9006
+ ...record,
9007
+ status: "final_reviewed",
9008
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9009
+ targetName,
9010
+ start: record.start,
9011
+ planning: record.planning,
9012
+ finalReview: record.finalReview,
9013
+ reconciliation: receipt
9014
+ });
9015
+ return {
9016
+ prdRef,
9017
+ status: "reconciled",
9018
+ reconciliation: receipt,
9019
+ diagnostics: ["Reconciliation automation is not fully implemented."]
9020
+ };
8253
9021
  }
8254
9022
  function computeFinalReviewMergeBase(repoRoot2, prdRef) {
8255
9023
  const normalized = normalizePrdRunRef2(prdRef);
@@ -8285,97 +9053,169 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
8285
9053
  if (mergeBaseResult.status !== 0) {
8286
9054
  if (mergeBaseResult.stderr?.toString?.()?.trim()) {
8287
9055
  mergeBaseDiagnostics.push(mergeBaseResult.stderr.toString().trim());
8288
- }
8289
- if (mergeBaseResult.stdout?.toString?.()?.trim()) {
8290
- mergeBaseDiagnostics.push(mergeBaseResult.stdout.toString().trim());
8291
- }
8292
- return {
8293
- ok: false,
8294
- gate: "final-review",
8295
- reason: `Final Review merge-base computation failed for origin/dev and origin/${normalized}.`,
8296
- diagnostics: mergeBaseDiagnostics.length > 0 ? mergeBaseDiagnostics : ["git merge-base returned non-zero exit status"]
8297
- };
8298
- }
8299
- const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
8300
- if (!mergeBase) {
8301
- return {
8302
- ok: false,
8303
- gate: "final-review",
8304
- reason: `Final Review merge-base returned empty result for origin/dev and origin/${normalized}.`,
8305
- diagnostics: [
8306
- `merge-base stdout was empty for origin/dev..origin/${normalized}`
8307
- ]
8308
- };
8309
- }
8310
- return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
8311
- }
8312
- function parseFinalReviewArtifact(artifactPath) {
8313
- if (!existsSync11(artifactPath)) {
8314
- return {
8315
- ok: false,
8316
- reason: "Final Review artifact not found.",
8317
- diagnostics: [`Artifact path: ${artifactPath}`]
8318
- };
8319
- }
8320
- let content;
8321
- try {
8322
- content = readFileSync11(artifactPath, "utf-8");
8323
- } catch (error) {
8324
- return {
8325
- ok: false,
8326
- reason: "Final Review artifact could not be read.",
8327
- diagnostics: [
8328
- error instanceof Error ? error.message : String(error),
8329
- `Artifact path: ${artifactPath}`
8330
- ]
8331
- };
8332
- }
8333
- let parsed;
8334
- try {
8335
- parsed = JSON.parse(content);
8336
- } catch {
8337
- return {
8338
- ok: false,
8339
- reason: "Final Review artifact is not valid JSON.",
8340
- diagnostics: [`Artifact path: ${artifactPath}`]
8341
- };
8342
- }
8343
- const verdict = parsed.verdict;
8344
- if (!verdict || typeof verdict !== "string") {
8345
- return {
8346
- ok: false,
8347
- reason: "Final Review artifact is missing a verdict field.",
8348
- diagnostics: [`Artifact content preview: ${content.slice(0, 300)}`]
8349
- };
8350
- }
8351
- const allowedVerdicts = [
8352
- "pass_no_changes",
8353
- "pass_with_retouch",
8354
- "needs_human_review",
8355
- "blocked"
8356
- ];
8357
- if (!allowedVerdicts.includes(verdict)) {
9056
+ }
9057
+ if (mergeBaseResult.stdout?.toString?.()?.trim()) {
9058
+ mergeBaseDiagnostics.push(mergeBaseResult.stdout.toString().trim());
9059
+ }
8358
9060
  return {
8359
9061
  ok: false,
8360
- reason: `Final Review artifact has unsupported verdict "${verdict}".`,
8361
- diagnostics: [`Allowed verdicts: ${allowedVerdicts.join(", ")}`]
9062
+ gate: "final-review",
9063
+ reason: `Final Review merge-base computation failed for origin/dev and origin/${normalized}.`,
9064
+ diagnostics: mergeBaseDiagnostics.length > 0 ? mergeBaseDiagnostics : ["git merge-base returned non-zero exit status"]
8362
9065
  };
8363
9066
  }
8364
- const summary = typeof parsed.summary === "string" ? parsed.summary : typeof parsed.reason === "string" ? parsed.reason : String(verdict);
8365
- const diagnostics = Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [];
8366
- const changedPaths = Array.isArray(parsed.changedPaths) ? parsed.changedPaths : void 0;
8367
- const isAutoSummary = !parsed.summary && !parsed.reason;
8368
- if (verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && isAutoSummary) {
9067
+ const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
9068
+ if (!mergeBase) {
8369
9069
  return {
8370
9070
  ok: false,
8371
- reason: 'Final Review artifact with "pass_with_retouch" verdict requires changed paths.',
9071
+ gate: "final-review",
9072
+ reason: `Final Review merge-base returned empty result for origin/dev and origin/${normalized}.`,
8372
9073
  diagnostics: [
8373
- "pass_with_retouch verdict must include a changedPaths array in the artifact or provide a summary/reason with enough context for git diff fallback.",
8374
- "Provide changedPaths or a descriptive summary in the artifact."
9074
+ `merge-base stdout was empty for origin/dev..origin/${normalized}`
8375
9075
  ]
8376
9076
  };
8377
9077
  }
8378
- return { ok: true, verdict, summary, diagnostics, changedPaths };
9078
+ return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
9079
+ }
9080
+ var PRD_047_PRD_MIRROR_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-047-prd-reconciliation-run/PRD.md";
9081
+ var PRD_047_CHILD_ISSUE_DIR = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-047-prd-reconciliation-run/issues/";
9082
+ var PRD_047_ESCAPE_HATCH_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/completions/006-prd-047-prd-reconciliation-run-status.md";
9083
+ var PRD_047_MANIFEST_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-047-prd-reconciliation-run/planning-manifest.md";
9084
+ function auditPrd047Implementation(repoRoot2, prdRef) {
9085
+ const normalizedRef = normalizePrdRunRef2(prdRef);
9086
+ const blockerBugs = [];
9087
+ const blockersFixed = [];
9088
+ const nonBlockers = [];
9089
+ const prdMirrorPath = join15(repoRoot2, PRD_047_PRD_MIRROR_PATH);
9090
+ const completionsDir = join15(repoRoot2, dirname7(PRD_047_ESCAPE_HATCH_PATH));
9091
+ let escapeHatchPresent = false;
9092
+ if (existsSync12(completionsDir)) {
9093
+ const files = readdirSync4(completionsDir);
9094
+ escapeHatchPresent = files.some(
9095
+ (f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
9096
+ );
9097
+ }
9098
+ if (!escapeHatchPresent) {
9099
+ escapeHatchPresent = existsSync12(join15(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
9100
+ }
9101
+ const prdMirrorExists = existsSync12(prdMirrorPath);
9102
+ if (!prdMirrorExists) {
9103
+ nonBlockers.push(
9104
+ `PRD-047 PRD mirror not found at ${PRD_047_PRD_MIRROR_PATH}. Cannot verify scope alignment from published mirrors.`
9105
+ );
9106
+ }
9107
+ if (prdMirrorExists) {
9108
+ const mirrorContent = readFileSync12(prdMirrorPath, "utf-8");
9109
+ const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
9110
+ const hasEscapeHatch = mirrorContent.includes("escape hatch");
9111
+ if (!hasReconcileCommand) {
9112
+ blockerBugs.push(
9113
+ "PRD-047 PRD mirror does not reference prd-run reconcile command."
9114
+ );
9115
+ }
9116
+ if (!hasEscapeHatch) {
9117
+ nonBlockers.push(
9118
+ "PRD-047 PRD mirror does not reference escape hatch mechanism."
9119
+ );
9120
+ }
9121
+ }
9122
+ const childIssueDir = join15(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
9123
+ const childIssuesExist = existsSync12(childIssueDir);
9124
+ if (!childIssuesExist) {
9125
+ nonBlockers.push(
9126
+ `PRD-047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
9127
+ );
9128
+ }
9129
+ const manifestPath = join15(repoRoot2, PRD_047_MANIFEST_PATH);
9130
+ if (existsSync12(manifestPath)) {
9131
+ const manifestContent = readFileSync12(manifestPath, "utf-8");
9132
+ if (!manifestContent.includes("ready_for_prepare")) {
9133
+ nonBlockers.push(
9134
+ "Planning Artifact Manifest is not marked as ready_for_prepare."
9135
+ );
9136
+ }
9137
+ if (!manifestContent.includes("Publication Receipt")) {
9138
+ nonBlockers.push(
9139
+ "Planning Artifact Manifest is missing parent PRD publication receipt."
9140
+ );
9141
+ }
9142
+ if (!manifestContent.includes("Triage Receipt")) {
9143
+ nonBlockers.push(
9144
+ "Planning Artifact Manifest is missing child Issue triage receipt."
9145
+ );
9146
+ }
9147
+ } else {
9148
+ nonBlockers.push(
9149
+ "Planning Artifact Manifest not found. Cannot verify publication receipts."
9150
+ );
9151
+ }
9152
+ const prdRunRecord = readPrdRun(repoRoot2, normalizedRef);
9153
+ if (prdRunRecord.record && prdRunRecord.record.reconciliation) {
9154
+ if (prdRunRecord.record.reconciliation.status === "succeeded") {
9155
+ blockerBugs.push(
9156
+ "Fabricated reconciliation success: reconciliation receipt has status 'succeeded' but full reconciliation automation is not implemented."
9157
+ );
9158
+ }
9159
+ }
9160
+ if (prdRunRecord.record && prdRunRecord.record.finalReview) {
9161
+ if (prdRunRecord.record.finalReview.status === "succeeded" && (!prdRunRecord.record.finalReview.artifactPath || !prdRunRecord.record.finalReview.reviewedAt)) {
9162
+ blockerBugs.push(
9163
+ "Fabricated Final Review receipt: status is 'succeeded' without evidence of agent execution (missing artifactPath or reviewedAt)."
9164
+ );
9165
+ }
9166
+ }
9167
+ if (prdRunRecord.record && prdRunRecord.record.start) {
9168
+ const start = prdRunRecord.record.start;
9169
+ if (start.prdBranch !== normalizedRef) {
9170
+ blockerBugs.push(
9171
+ `PRD Branch mismatch: start receipt branch "${start.prdBranch}" does not match PRD ref "${normalizedRef}".`
9172
+ );
9173
+ }
9174
+ if (!start.startBaseCommit) {
9175
+ blockerBugs.push(
9176
+ `PRD Run start receipt is missing startBaseCommit for branch "${start.prdBranch}".`
9177
+ );
9178
+ }
9179
+ if (!start.branchAction) {
9180
+ blockerBugs.push(
9181
+ `PRD Run start receipt is missing branchAction for branch "${start.prdBranch}".`
9182
+ );
9183
+ }
9184
+ }
9185
+ if (prdRunRecord.record && prdRunRecord.record.status === "waiting_for_integration" && !prdRunRecord.record.reconciliation) {
9186
+ blockerBugs.push(
9187
+ "PRD Run is in 'waiting_for_integration' status without a reconciliation receipt."
9188
+ );
9189
+ }
9190
+ if (!prdRunRecord.record) {
9191
+ nonBlockers.push(
9192
+ `No PRD Run record found for ${normalizedRef}. Cannot verify implementation state.`
9193
+ );
9194
+ }
9195
+ if (!prdMirrorExists && !childIssuesExist && !prdRunRecord.record) {
9196
+ nonBlockers.push(
9197
+ "Insufficient evidence to verify PRD-047 scope alignment: mirrors and PRD Run record are missing."
9198
+ );
9199
+ }
9200
+ if (escapeHatchPresent) {
9201
+ blockersFixed.push(
9202
+ "PRD-047 escape-hatch lifecycle violation record confirmed present."
9203
+ );
9204
+ } else {
9205
+ nonBlockers.push(
9206
+ "PRD-047 escape-hatch lifecycle violation record is missing. Expected at " + PRD_047_ESCAPE_HATCH_PATH + "."
9207
+ );
9208
+ }
9209
+ return {
9210
+ blockerBugs,
9211
+ blockersFixed,
9212
+ nonBlockers,
9213
+ escapeHatchPresent
9214
+ };
9215
+ }
9216
+ function runPrdRunAuditCommand(options) {
9217
+ const prdRef = normalizePrdRunRef2(options.prdRef);
9218
+ return auditPrd047Implementation(options.repoRoot, prdRef);
8379
9219
  }
8380
9220
  async function runPrdRunLaunchCommand(options) {
8381
9221
  const prdRef = normalizePrdRunRef2(options.prdRef);
@@ -8386,18 +9226,16 @@ async function runPrdRunLaunchCommand(options) {
8386
9226
  const existingRecord = readPrdRun(options.repoRoot, prdRef);
8387
9227
  if (existingRecord.record) {
8388
9228
  const terminal = /* @__PURE__ */ new Set([
8389
- "drained",
8390
9229
  "waiting_for_integration",
8391
9230
  "finalizing",
8392
- "complete",
8393
- "final_reviewed"
9231
+ "complete"
8394
9232
  ]);
8395
9233
  if (terminal.has(existingRecord.record.status)) {
8396
9234
  return {
8397
9235
  prdRef,
8398
9236
  status: existingRecord.record.status,
8399
9237
  attempted: [],
8400
- skipped: ["prepare", "start", "queue"],
9238
+ skipped: ["prepare", "start", "queue", "final-review", "reconcile"],
8401
9239
  resumed: [],
8402
9240
  diagnostics: [
8403
9241
  `PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`
@@ -8407,28 +9245,42 @@ async function runPrdRunLaunchCommand(options) {
8407
9245
  }
8408
9246
  if (existingRecord.record) {
8409
9247
  const status = existingRecord.record.status;
8410
- const resumableStartBlock = status === "blocked" && (existingRecord.record.blockedGate === "queue" || existingRecord.record.blockedGate === "branch-state");
8411
- if (status === "starting" || status === "running" || resumableStartBlock) {
8412
- if (existingRecord.record.start) {
9248
+ if (status === "drained") {
9249
+ skipped.push("prepare", "start", "queue");
9250
+ resumed.push("final-review");
9251
+ } else if (status === "final_reviewed") {
9252
+ skipped.push("prepare", "start", "queue", "final-review");
9253
+ resumed.push("reconcile");
9254
+ } else if (status === "blocked" && existingRecord.record.blockedGate === "reconciliation") {
9255
+ skipped.push("prepare", "start", "queue", "final-review");
9256
+ resumed.push("reconcile");
9257
+ } else if (status === "blocked" && existingRecord.record.blockedGate === "final-review" && canRetryFinalReviewBlock(existingRecord.record)) {
9258
+ skipped.push("prepare", "start", "queue");
9259
+ resumed.push("final-review");
9260
+ } else {
9261
+ const resumableStartBlock = status === "blocked" && (existingRecord.record.blockedGate === "queue" || existingRecord.record.blockedGate === "branch-state");
9262
+ if (status === "starting" || status === "running" || resumableStartBlock) {
9263
+ if (existingRecord.record.start) {
9264
+ skipped.push("prepare");
9265
+ resumed.push("start");
9266
+ } else {
9267
+ return {
9268
+ prdRef,
9269
+ status: "blocked",
9270
+ attempted: [],
9271
+ skipped: ["prepare", "start", "queue", "final-review", "reconcile"],
9272
+ resumed: [],
9273
+ diagnostics: [
9274
+ `PRD Run ${prdRef} is in status "${status}" but has no start receipt.`
9275
+ ],
9276
+ blockedGate: "branch-state",
9277
+ blockedReason: `PRD Run ${prdRef} is in status "${status}" without a start receipt. Cannot resume launch.`,
9278
+ offendingPaths: []
9279
+ };
9280
+ }
9281
+ } else if (status === "ready_to_start") {
8413
9282
  skipped.push("prepare");
8414
- resumed.push("start");
8415
- } else {
8416
- return {
8417
- prdRef,
8418
- status: "blocked",
8419
- attempted: [],
8420
- skipped: ["prepare", "start", "queue"],
8421
- resumed: [],
8422
- diagnostics: [
8423
- `PRD Run ${prdRef} is in status "${status}" but has no start receipt.`
8424
- ],
8425
- blockedGate: "branch-state",
8426
- blockedReason: `PRD Run ${prdRef} is in status "${status}" without a start receipt. Cannot resume launch.`,
8427
- offendingPaths: []
8428
- };
8429
9283
  }
8430
- } else if (status === "ready_to_start") {
8431
- skipped.push("prepare");
8432
9284
  }
8433
9285
  }
8434
9286
  if (existingRecord.record && existingRecord.record.status !== "blocked" && !skipped.includes("prepare")) {
@@ -8470,57 +9322,166 @@ async function runPrdRunLaunchCommand(options) {
8470
9322
  }
8471
9323
  diagnostics.push(...prepareResult.diagnostics);
8472
9324
  }
8473
- attempted.push("start");
8474
- const startResult = await runPrdRunStartCommand({
8475
- repoRoot: options.repoRoot,
8476
- prdRef,
8477
- targetName: options.targetName,
8478
- config: options.config,
8479
- issueProvider: options.issueProvider,
8480
- prProvider: options.prProvider,
8481
- executionProvider: options.executionProvider,
8482
- logger: options.logger,
8483
- skipPrepareCheck: options.skipPrepareCheck,
8484
- adoptExistingBranch: options.adoptExistingBranch
8485
- });
8486
- const skippedAfterStart = skipped.includes("prepare") ? [...skipped, "queue"] : ["queue"];
8487
- if (startResult.status === "blocked") {
8488
- return {
9325
+ let startResult;
9326
+ if (!skipped.includes("start")) {
9327
+ attempted.push("start");
9328
+ startResult = await runPrdRunStartCommand({
9329
+ repoRoot: options.repoRoot,
8489
9330
  prdRef,
8490
- status: "blocked",
8491
- attempted,
8492
- skipped: skippedAfterStart,
8493
- resumed,
8494
- diagnostics: startResult.diagnostics,
8495
- blockedGate: startResult.blockedGate,
8496
- blockedReason: startResult.blockedReason,
8497
- offendingPaths: startResult.offendingPaths,
8498
- prepare: prepareResult,
8499
- start: startResult
8500
- };
8501
- }
8502
- if (startResult.status === "starting") {
8503
- return {
9331
+ targetName: options.targetName,
9332
+ config: options.config,
9333
+ issueProvider: options.issueProvider,
9334
+ prProvider: options.prProvider,
9335
+ executionProvider: options.executionProvider,
9336
+ logger: options.logger,
9337
+ skipPrepareCheck: options.skipPrepareCheck,
9338
+ adoptExistingBranch: options.adoptExistingBranch
9339
+ });
9340
+ const skippedAfterStart = [
9341
+ ...skipped.includes("prepare") ? ["prepare"] : [],
9342
+ "queue",
9343
+ "final-review",
9344
+ "reconcile"
9345
+ ];
9346
+ if (startResult.status === "blocked") {
9347
+ return {
9348
+ prdRef,
9349
+ status: "blocked",
9350
+ attempted,
9351
+ skipped: skippedAfterStart,
9352
+ resumed,
9353
+ diagnostics: startResult.diagnostics,
9354
+ blockedGate: startResult.blockedGate,
9355
+ blockedReason: startResult.blockedReason,
9356
+ offendingPaths: startResult.offendingPaths,
9357
+ prepare: prepareResult,
9358
+ start: startResult
9359
+ };
9360
+ }
9361
+ if (startResult.status === "starting") {
9362
+ return {
9363
+ prdRef,
9364
+ status: "starting",
9365
+ attempted,
9366
+ skipped: skippedAfterStart,
9367
+ resumed,
9368
+ diagnostics: startResult.diagnostics,
9369
+ prepare: prepareResult,
9370
+ start: startResult
9371
+ };
9372
+ }
9373
+ diagnostics.push(...startResult.diagnostics);
9374
+ attempted.push("queue");
9375
+ }
9376
+ let finalReviewResult;
9377
+ if (!skipped.includes("final-review")) {
9378
+ attempted.push("final-review");
9379
+ finalReviewResult = await runPrdRunFinalReviewCommand({
9380
+ repoRoot: options.repoRoot,
8504
9381
  prdRef,
8505
- status: "starting",
8506
- attempted,
8507
- skipped: skippedAfterStart,
8508
- resumed,
8509
- diagnostics: startResult.diagnostics,
8510
- prepare: prepareResult,
8511
- start: startResult
8512
- };
9382
+ targetName: options.targetName,
9383
+ autoMerge: options.autoMerge,
9384
+ issueProvider: options.issueProvider,
9385
+ prProvider: options.prProvider,
9386
+ executionProvider: options.executionProvider,
9387
+ config: options.config,
9388
+ logger: options.logger
9389
+ });
9390
+ const skippedAfterFr = [
9391
+ ...skipped.includes("prepare") ? ["prepare"] : [],
9392
+ ...skipped.includes("start") ? ["queue"] : [],
9393
+ "reconcile"
9394
+ ];
9395
+ if (finalReviewResult.status === "blocked") {
9396
+ return {
9397
+ prdRef,
9398
+ status: "blocked",
9399
+ attempted,
9400
+ skipped: skippedAfterFr,
9401
+ resumed,
9402
+ diagnostics: finalReviewResult.diagnostics,
9403
+ blockedGate: "final-review",
9404
+ blockedReason: finalReviewResult.blockedReason,
9405
+ offendingPaths: finalReviewResult.offendingPaths,
9406
+ prepare: prepareResult,
9407
+ start: startResult,
9408
+ finalReview: finalReviewResult
9409
+ };
9410
+ }
9411
+ if (finalReviewResult.status === "needs_human_review") {
9412
+ return {
9413
+ prdRef,
9414
+ status: "blocked",
9415
+ attempted,
9416
+ skipped: skippedAfterFr,
9417
+ resumed,
9418
+ diagnostics: finalReviewResult.diagnostics,
9419
+ blockedGate: "final-review",
9420
+ blockedReason: finalReviewResult.finalReview.verdict ?? "needs_human_review",
9421
+ offendingPaths: [],
9422
+ prepare: prepareResult,
9423
+ start: startResult,
9424
+ finalReview: finalReviewResult
9425
+ };
9426
+ }
9427
+ diagnostics.push(...finalReviewResult.diagnostics);
9428
+ }
9429
+ let reconcileResult;
9430
+ if (!skipped.includes("reconcile")) {
9431
+ attempted.push("reconcile");
9432
+ reconcileResult = await runPrdRunReconcileCommand({
9433
+ repoRoot: options.repoRoot,
9434
+ prdRef,
9435
+ targetName: options.targetName,
9436
+ config: options.config,
9437
+ logger: options.logger
9438
+ });
9439
+ if (reconcileResult.status === "blocked") {
9440
+ return {
9441
+ prdRef,
9442
+ status: "blocked",
9443
+ attempted,
9444
+ skipped: [...skipped],
9445
+ resumed,
9446
+ diagnostics: reconcileResult.diagnostics,
9447
+ blockedGate: "reconciliation",
9448
+ blockedReason: reconcileResult.blockedReason,
9449
+ offendingPaths: reconcileResult.offendingPaths,
9450
+ prepare: prepareResult,
9451
+ start: startResult,
9452
+ finalReview: finalReviewResult,
9453
+ reconcile: reconcileResult
9454
+ };
9455
+ }
9456
+ diagnostics.push(...reconcileResult.diagnostics);
8513
9457
  }
8514
- attempted.push("queue");
9458
+ const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
9459
+ writePrdRunRecord(options.repoRoot, {
9460
+ prdRef,
9461
+ status: "waiting_for_integration",
9462
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9463
+ targetName: currentRecord?.targetName ?? options.targetName,
9464
+ prdBranch: currentRecord?.prdBranch,
9465
+ manifestPath: currentRecord?.manifestPath,
9466
+ planning: currentRecord?.planning,
9467
+ start: currentRecord?.start,
9468
+ finalReview: currentRecord?.finalReview,
9469
+ reconciliation: currentRecord?.reconciliation,
9470
+ scopeChanges: currentRecord?.scopeChanges
9471
+ });
8515
9472
  return {
8516
9473
  prdRef,
8517
- status: "drained",
9474
+ status: "waiting_for_integration",
8518
9475
  attempted,
8519
9476
  skipped,
8520
9477
  resumed,
8521
- diagnostics: startResult.diagnostics,
9478
+ diagnostics: [
9479
+ "Integration Gate is not implemented. PRD Run is waiting for manual integration."
9480
+ ],
8522
9481
  prepare: prepareResult,
8523
- start: startResult
9482
+ start: startResult,
9483
+ finalReview: finalReviewResult,
9484
+ reconcile: reconcileResult
8524
9485
  };
8525
9486
  }
8526
9487
  async function runPrdRunPrepareCommand(options) {
@@ -9690,47 +10651,6 @@ function validateArchitecturePlanningDiffScope(changedFiles) {
9690
10651
  function isArchitecturePlanningDiffPath(path9) {
9691
10652
  return path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/adr/");
9692
10653
  }
9693
- function isRetouchScopePath(path9) {
9694
- if (path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/adr/") || /^\.pourkit\/prd-runs\/[^/]+\.json$/.test(path9)) {
9695
- return false;
9696
- }
9697
- return true;
9698
- }
9699
- function validateFinalReviewRetouchScope(changedPaths) {
9700
- if (!changedPaths || changedPaths.length === 0) {
9701
- return {
9702
- ok: false,
9703
- 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.",
9704
- diagnostics: ["Changed paths list is empty or undefined."],
9705
- offendingPaths: []
9706
- };
9707
- }
9708
- const offendingPaths = changedPaths.filter((p) => !isRetouchScopePath(p));
9709
- const allowedPaths = changedPaths.filter((p) => isRetouchScopePath(p));
9710
- if (allowedPaths.length === 0) {
9711
- return {
9712
- ok: false,
9713
- reason: "Final Review retouch scope validation failed. No implementation, test, or Changeset paths found for retouch.",
9714
- diagnostics: [
9715
- "All changed paths are prohibited for retouch.",
9716
- ...changedPaths.map((p) => ` - ${p}`)
9717
- ],
9718
- offendingPaths
9719
- };
9720
- }
9721
- if (offendingPaths.length > 0) {
9722
- return {
9723
- ok: false,
9724
- reason: "Final Review retouch scope validation failed. Prohibited paths cannot be included in retouch PR.",
9725
- diagnostics: [
9726
- "Prohibited paths found:",
9727
- ...offendingPaths.map((p) => ` - ${p}`)
9728
- ],
9729
- offendingPaths
9730
- };
9731
- }
9732
- return { ok: true, changedPaths };
9733
- }
9734
10654
  async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles) {
9735
10655
  const remoteResult = spawnSync2("git", ["remote", "get-url", "origin"], {
9736
10656
  cwd: repoRoot2,
@@ -9739,7 +10659,7 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
9739
10659
  if (remoteResult.status !== 0) {
9740
10660
  return;
9741
10661
  }
9742
- const worktreePath = mkdtempSync(join15(tmpdir(), "pourkit-planning-"));
10662
+ const worktreePath = mkdtempSync2(join15(tmpdir2(), "pourkit-planning-"));
9743
10663
  try {
9744
10664
  runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
9745
10665
  runGitOrThrow(
@@ -10013,7 +10933,7 @@ function parseGitStatusLine(line) {
10013
10933
  return { status, path: path9.replace(/^"|"$/g, "") };
10014
10934
  }
10015
10935
  function validateManifestArtifactExistence(repoRoot2, manifest) {
10016
- const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !existsSync11(path9)).map((path9) => toRepoRelativePath2(repoRoot2, path9));
10936
+ const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !existsSync12(path9)).map((path9) => toRepoRelativePath2(repoRoot2, path9));
10017
10937
  if (missingPaths.length > 0) {
10018
10938
  return {
10019
10939
  ok: false,
@@ -10430,7 +11350,7 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
10430
11350
 
10431
11351
  // commands/init.ts
10432
11352
  init_github_client();
10433
- import { existsSync as existsSync12, statSync } from "fs";
11353
+ import { existsSync as existsSync13, statSync } from "fs";
10434
11354
  import {
10435
11355
  copyFile,
10436
11356
  mkdir as mkdir4,
@@ -10872,7 +11792,7 @@ async function computeFileChecksum(filePath) {
10872
11792
  return createHash2("sha256").update(content).digest("hex");
10873
11793
  }
10874
11794
  function lockfileExists(root, name) {
10875
- return existsSync12(path7.join(root, name));
11795
+ return existsSync13(path7.join(root, name));
10876
11796
  }
10877
11797
  function detectPackageManager(root) {
10878
11798
  if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
@@ -10916,7 +11836,7 @@ async function discoverLocalSource(sourcePath) {
10916
11836
  async function discoverReadme(root) {
10917
11837
  for (const name of ["README.md", "readme.md"]) {
10918
11838
  const p = path7.join(root, name);
10919
- if (existsSync12(p)) {
11839
+ if (existsSync13(p)) {
10920
11840
  return p;
10921
11841
  }
10922
11842
  }
@@ -10926,7 +11846,7 @@ async function discoverAgentFiles(root) {
10926
11846
  const files = [];
10927
11847
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
10928
11848
  const p = path7.join(root, name);
10929
- if (existsSync12(p)) {
11849
+ if (existsSync13(p)) {
10930
11850
  files.push(p);
10931
11851
  }
10932
11852
  }
@@ -10934,7 +11854,7 @@ async function discoverAgentFiles(root) {
10934
11854
  }
10935
11855
  async function discoverMerlleState(root) {
10936
11856
  const p = path7.join(root, ".pourkit", "state.json");
10937
- return existsSync12(p) ? p : null;
11857
+ return existsSync13(p) ? p : null;
10938
11858
  }
10939
11859
  async function discoverAgentSkills(root) {
10940
11860
  const dirs = [
@@ -10943,7 +11863,7 @@ async function discoverAgentSkills(root) {
10943
11863
  ];
10944
11864
  const found = [];
10945
11865
  for (const d of dirs) {
10946
- if (existsSync12(d)) {
11866
+ if (existsSync13(d)) {
10947
11867
  found.push(d);
10948
11868
  }
10949
11869
  }
@@ -10953,12 +11873,12 @@ async function discoverRootDomainDocs(root) {
10953
11873
  const docs = [];
10954
11874
  for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
10955
11875
  const p = path7.join(root, name);
10956
- if (existsSync12(p)) {
11876
+ if (existsSync13(p)) {
10957
11877
  docs.push(p);
10958
11878
  }
10959
11879
  }
10960
11880
  const adrDir = path7.join(root, "docs", "adr");
10961
- if (existsSync12(adrDir)) {
11881
+ if (existsSync13(adrDir)) {
10962
11882
  const entries = await readdir(adrDir, { withFileTypes: true });
10963
11883
  for (const entry of entries) {
10964
11884
  if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -11101,7 +12021,7 @@ async function planInit(options) {
11101
12021
  for (const file of skillFiles) {
11102
12022
  const relPath = path7.relative(s, file);
11103
12023
  const destPath = path7.join(targetRoot, ".agents", "skills", relPath);
11104
- if (!existsSync12(destPath)) {
12024
+ if (!existsSync13(destPath)) {
11105
12025
  operations.push({
11106
12026
  kind: "copy",
11107
12027
  sourcePath: file,
@@ -11164,7 +12084,7 @@ async function planInit(options) {
11164
12084
  });
11165
12085
  }
11166
12086
  if (sourceRoot) {
11167
- if (!existsSync12(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
12087
+ if (!existsSync13(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
11168
12088
  warnings.push(
11169
12089
  `--from-local path does not exist or is not a directory: ${sourceRoot}`
11170
12090
  );
@@ -11283,7 +12203,7 @@ async function planInit(options) {
11283
12203
  requiresConfirmation: false,
11284
12204
  destructive: false
11285
12205
  });
11286
- } else if (existsSync12(destPath)) {
12206
+ } else if (existsSync13(destPath)) {
11287
12207
  operations.push({
11288
12208
  kind: "skip",
11289
12209
  path: destPath,
@@ -11341,7 +12261,7 @@ async function planInit(options) {
11341
12261
  }
11342
12262
  }
11343
12263
  const contextPath = path7.join(targetRoot, ".pourkit", "CONTEXT.md");
11344
- if (!existsSync12(contextPath) && !merleDestPaths.has(contextPath)) {
12264
+ if (!existsSync13(contextPath) && !merleDestPaths.has(contextPath)) {
11345
12265
  operations.push({
11346
12266
  kind: "create",
11347
12267
  path: contextPath,
@@ -11359,7 +12279,7 @@ async function planInit(options) {
11359
12279
  "adr",
11360
12280
  ".gitkeep"
11361
12281
  );
11362
- if (!existsSync12(adrGitkeep)) {
12282
+ if (!existsSync13(adrGitkeep)) {
11363
12283
  operations.push({
11364
12284
  kind: "create",
11365
12285
  path: adrGitkeep,
@@ -11371,7 +12291,7 @@ async function planInit(options) {
11371
12291
  }
11372
12292
  const srcDocAgents = path7.join(sourceRoot, ".pourkit", "docs", "agents");
11373
12293
  const tgtDocAgents = path7.join(targetRoot, ".pourkit", "docs", "agents");
11374
- if (existsSync12(srcDocAgents) && !existsSync12(tgtDocAgents)) {
12294
+ if (existsSync13(srcDocAgents) && !existsSync13(tgtDocAgents)) {
11375
12295
  const docFiles = await walkDir(srcDocAgents);
11376
12296
  for (const file of docFiles) {
11377
12297
  const relPath = path7.relative(srcDocAgents, file);
@@ -11404,7 +12324,7 @@ async function planInit(options) {
11404
12324
  }
11405
12325
  const srcPrompts = path7.join(sourceRoot, ".pourkit", "prompts");
11406
12326
  const tgtPrompts = path7.join(targetRoot, ".pourkit", "prompts");
11407
- if (existsSync12(srcPrompts) && !existsSync12(tgtPrompts)) {
12327
+ if (existsSync13(srcPrompts) && !existsSync13(tgtPrompts)) {
11408
12328
  const promptFiles = await walkDir(srcPrompts);
11409
12329
  for (const file of promptFiles) {
11410
12330
  const relPath = path7.relative(srcPrompts, file);
@@ -11431,7 +12351,7 @@ async function planInit(options) {
11431
12351
  ".sandcastle",
11432
12352
  "Dockerfile"
11433
12353
  );
11434
- if (existsSync12(tgtSandboxDockerfile)) {
12354
+ if (existsSync13(tgtSandboxDockerfile)) {
11435
12355
  operations.push({
11436
12356
  kind: "skip",
11437
12357
  path: tgtSandboxDockerfile,
@@ -11440,7 +12360,7 @@ async function planInit(options) {
11440
12360
  requiresConfirmation: false,
11441
12361
  destructive: false
11442
12362
  });
11443
- } else if (existsSync12(srcSandboxDockerfile)) {
12363
+ } else if (existsSync13(srcSandboxDockerfile)) {
11444
12364
  const checksum = await computeFileChecksum(srcSandboxDockerfile);
11445
12365
  operations.push({
11446
12366
  kind: "copy",
@@ -11454,7 +12374,7 @@ async function planInit(options) {
11454
12374
  });
11455
12375
  }
11456
12376
  const configTsPath = path7.join(targetRoot, "pourkit.config.ts");
11457
- if (!existsSync12(configTsPath)) {
12377
+ if (!existsSync13(configTsPath)) {
11458
12378
  const verifyCommands = inferVerificationCommands(
11459
12379
  packageScripts,
11460
12380
  pm || "npm"
@@ -11491,7 +12411,7 @@ async function planInit(options) {
11491
12411
  const hasExistingAgents = operations.some(
11492
12412
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
11493
12413
  );
11494
- if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync12(path7.join(targetRoot, "AGENTS.md"))) {
12414
+ if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync13(path7.join(targetRoot, "AGENTS.md"))) {
11495
12415
  operations.push({
11496
12416
  kind: "create",
11497
12417
  path: path7.join(targetRoot, "AGENTS.md"),
@@ -11507,7 +12427,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
11507
12427
  const hasExistingClaude = operations.some(
11508
12428
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
11509
12429
  );
11510
- if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync12(path7.join(targetRoot, "CLAUDE.md"))) {
12430
+ if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync13(path7.join(targetRoot, "CLAUDE.md"))) {
11511
12431
  operations.push({
11512
12432
  kind: "create",
11513
12433
  path: path7.join(targetRoot, "CLAUDE.md"),
@@ -11522,7 +12442,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
11522
12442
  }
11523
12443
  const gitignoreTarget = path7.join(targetRoot, ".gitignore");
11524
12444
  const gitignoreContent = generateGitignoreBlock();
11525
- if (!existsSync12(gitignoreTarget)) {
12445
+ if (!existsSync13(gitignoreTarget)) {
11526
12446
  operations.push({
11527
12447
  kind: "create",
11528
12448
  path: gitignoreTarget,
@@ -11546,7 +12466,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
11546
12466
  });
11547
12467
  }
11548
12468
  const openCodePath = path7.join(targetRoot, "opencode.json");
11549
- if (!existsSync12(openCodePath)) {
12469
+ if (!existsSync13(openCodePath)) {
11550
12470
  operations.push({
11551
12471
  kind: "create",
11552
12472
  path: openCodePath,
@@ -11603,7 +12523,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
11603
12523
  }
11604
12524
  }
11605
12525
  const manifestPath = path7.join(targetRoot, ".pourkit", "manifest.json");
11606
- if (existsSync12(manifestPath)) {
12526
+ if (existsSync13(manifestPath)) {
11607
12527
  operations.push({
11608
12528
  kind: "skip",
11609
12529
  path: manifestPath,
@@ -11922,7 +12842,7 @@ async function updateManagedBlock(filePath, content) {
11922
12842
  const blockContent = `${MANAGED_BLOCK_BEGIN}
11923
12843
  ${content}${MANAGED_BLOCK_END}
11924
12844
  `;
11925
- if (!existsSync12(filePath)) {
12845
+ if (!existsSync13(filePath)) {
11926
12846
  const dir = path7.dirname(filePath);
11927
12847
  await mkdir4(dir, { recursive: true });
11928
12848
  await writeFileAtomic(filePath, blockContent);
@@ -11951,7 +12871,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
11951
12871
  if (op.requiresConfirmation) continue;
11952
12872
  const relPath = path7.relative(plan.targetRoot, op.path);
11953
12873
  if (relPath === ".pourkit/manifest.json") continue;
11954
- if (existsSync12(op.path)) {
12874
+ if (existsSync13(op.path)) {
11955
12875
  const sha256 = await computeFileChecksum(op.path);
11956
12876
  assets[relPath] = {
11957
12877
  ownership: op.ownership || "managed",
@@ -11996,7 +12916,7 @@ async function applyInitPlan(plan, options) {
11996
12916
  skipped++;
11997
12917
  continue;
11998
12918
  }
11999
- if (existsSync12(op.path) && !op.destructive) {
12919
+ if (existsSync13(op.path) && !op.destructive) {
12000
12920
  skipped++;
12001
12921
  continue;
12002
12922
  }
@@ -12011,7 +12931,7 @@ async function applyInitPlan(plan, options) {
12011
12931
  skipped++;
12012
12932
  continue;
12013
12933
  }
12014
- if (existsSync12(op.path)) {
12934
+ if (existsSync13(op.path)) {
12015
12935
  skipped++;
12016
12936
  continue;
12017
12937
  }
@@ -12040,7 +12960,7 @@ async function applyInitPlan(plan, options) {
12040
12960
  skipped++;
12041
12961
  continue;
12042
12962
  }
12043
- if (existsSync12(op.path)) {
12963
+ if (existsSync13(op.path)) {
12044
12964
  skipped++;
12045
12965
  continue;
12046
12966
  }
@@ -12180,7 +13100,7 @@ async function applyInitFromSource(options) {
12180
13100
  if (!manifestSkipped) {
12181
13101
  const agentFiles = [];
12182
13102
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
12183
- if (existsSync12(path7.join(targetRoot, name))) {
13103
+ if (existsSync13(path7.join(targetRoot, name))) {
12184
13104
  agentFiles.push(path7.join(targetRoot, name));
12185
13105
  }
12186
13106
  }
@@ -13411,11 +14331,46 @@ function createCliProgram(version) {
13411
14331
  });
13412
14332
  console.log(JSON.stringify(result, null, 2));
13413
14333
  });
14334
+ prdRun.command("audit").argument("<prdRef>", "PRD ref").option("--cwd <path>", "target repository directory").action(async (prdRef, options) => {
14335
+ const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
14336
+ const result = runPrdRunAuditCommand({
14337
+ repoRoot: targetRepoRoot,
14338
+ prdRef
14339
+ });
14340
+ console.log(JSON.stringify(result, null, 2));
14341
+ });
13414
14342
  prdRun.command("list").option("--cwd <path>", "target repository directory").action(async (options) => {
13415
14343
  const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
13416
14344
  const result = runPrdRunListCommand({ repoRoot: targetRepoRoot });
13417
14345
  console.log(JSON.stringify(result, null, 2));
13418
14346
  });
14347
+ prdRun.command("validate-final-review").argument("<prdRef>", "PRD ref").requiredOption("--review-base <sha>", "merge-base SHA used for review").option("--checkout-base <ref>", "PRD Branch checkout base").option("--artifact-path <path>", "artifact path to validate").option(
14348
+ "--changed-path <path>",
14349
+ "changed path to validate for retouch scope (repeatable)",
14350
+ (value, previous) => {
14351
+ previous.push(value);
14352
+ return previous;
14353
+ },
14354
+ []
14355
+ ).option("--cwd <path>", "target repository directory").action(
14356
+ (prdRef, options) => {
14357
+ const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
14358
+ const normalizedPrdRef = normalizePrdRef(prdRef);
14359
+ const artifactPath = options.artifactPath ? path8.isAbsolute(options.artifactPath) ? options.artifactPath : path8.resolve(targetRepoRoot, options.artifactPath) : void 0;
14360
+ const result = runPrdRunValidateFinalReviewCommand({
14361
+ repoRoot: targetRepoRoot,
14362
+ prdRef: normalizedPrdRef,
14363
+ checkoutBase: options.checkoutBase,
14364
+ reviewBase: options.reviewBase,
14365
+ artifactPath,
14366
+ changedPaths: options.changedPath
14367
+ });
14368
+ console.log(JSON.stringify(result, null, 2));
14369
+ if (result.status === "invalid") {
14370
+ process.exitCode = 1;
14371
+ }
14372
+ }
14373
+ );
13419
14374
  prdRun.command("final-review").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").option("--no-auto-merge", "disable auto-merge").action(
13420
14375
  async (prdRef, options) => {
13421
14376
  const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
@@ -13463,6 +14418,39 @@ function createCliProgram(version) {
13463
14418
  }
13464
14419
  }
13465
14420
  );
14421
+ prdRun.command("reconcile").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(
14422
+ async (prdRef, options) => {
14423
+ const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
14424
+ const normalizedPrdRef = normalizePrdRef(prdRef);
14425
+ const logPath = path8.join(
14426
+ targetRepoRoot,
14427
+ ".pourkit",
14428
+ "logs",
14429
+ "prd-run-reconcile.log"
14430
+ );
14431
+ const logger = createLogger("pourkit", logPath);
14432
+ try {
14433
+ const config = await loadRepoConfig(targetRepoRoot);
14434
+ const result = await runPrdRunReconcileCommand({
14435
+ repoRoot: targetRepoRoot,
14436
+ prdRef: normalizedPrdRef,
14437
+ targetName: options.target,
14438
+ config,
14439
+ logger
14440
+ });
14441
+ console.log(JSON.stringify(result, null, 2));
14442
+ if (result.status === "blocked") {
14443
+ process.exitCode = 1;
14444
+ }
14445
+ } catch (error) {
14446
+ const msg = error instanceof Error ? error.message : String(error);
14447
+ console.error(`Error: ${msg}`);
14448
+ process.exit(1);
14449
+ } finally {
14450
+ await logger.close();
14451
+ }
14452
+ }
14453
+ );
13466
14454
  program.command("init").description("Initialize .pourkit layout in a target repo").option("--dry-run", "print the init plan without applying changes").option("--json", "output machine-readable JSON plan").option("--from-local <path>", "local source repo to copy artifacts from").option("--cwd <path>", "target repository directory").addOption(
13467
14455
  new Option(
13468
14456
  "--docs-migration <mode>",
@@ -13624,11 +14612,11 @@ function createCliProgram(version) {
13624
14612
  return program;
13625
14613
  }
13626
14614
  async function resolveCliVersion() {
13627
- if (isPackageVersion("0.0.0-next-20260604153710")) {
13628
- return "0.0.0-next-20260604153710";
14615
+ if (isPackageVersion("0.0.0-next-20260604225844")) {
14616
+ return "0.0.0-next-20260604225844";
13629
14617
  }
13630
- if (isReleaseVersion("0.0.0-next-20260604153710")) {
13631
- return "0.0.0-next-20260604153710";
14618
+ if (isReleaseVersion("0.0.0-next-20260604225844")) {
14619
+ return "0.0.0-next-20260604225844";
13632
14620
  }
13633
14621
  try {
13634
14622
  const root = repoRoot();