@pourkit/cli 0.0.0-next-20260614201555 → 0.0.0-next-20260615001408

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
@@ -3186,6 +3186,11 @@ function parseConflictResolutionArtifact(output) {
3186
3186
 
3187
3187
  // commands/artifact-validation.ts
3188
3188
  var CONFLICT_MARKER_PATTERN = /<<<<<<<|=======|>>>>>>>/m;
3189
+ var ISSUE_FINAL_REVIEW_SCOPE_VERDICTS = [
3190
+ "in-scope",
3191
+ "incidental",
3192
+ "out-of-scope"
3193
+ ];
3189
3194
  function invalid(options, reason, diagnostics = []) {
3190
3195
  return {
3191
3196
  ok: false,
@@ -3203,6 +3208,20 @@ function valid(options, diagnostics = []) {
3203
3208
  diagnostics
3204
3209
  };
3205
3210
  }
3211
+ function validateRepoRelativePathField(pathValue, fieldName) {
3212
+ if (typeof pathValue !== "string") {
3213
+ return { ok: false, reason: `${fieldName} must be a string` };
3214
+ }
3215
+ const normalized = pathValue.replace(/\\/g, "/");
3216
+ const segments = normalized.split("/");
3217
+ if (normalized.trim() === "" || normalized === "." || normalized === ".." || isAbsolute3(pathValue) || normalized.startsWith("/") || segments.some((segment) => segment === "..")) {
3218
+ return {
3219
+ ok: false,
3220
+ reason: `${fieldName} must not contain absolute paths or path traversal: ${pathValue}`
3221
+ };
3222
+ }
3223
+ return { ok: true };
3224
+ }
3206
3225
  function readArtifact(options) {
3207
3226
  if (!existsSync7(options.artifactPath)) {
3208
3227
  return {
@@ -3271,27 +3290,142 @@ function validateIssueFinalReviewArtifact(parsed, options) {
3271
3290
  };
3272
3291
  }
3273
3292
  diagnostics.push("summary: present");
3274
- if (!Array.isArray(parsed.changedPaths) || !parsed.changedPaths.every((p) => typeof p === "string")) {
3293
+ if (!Array.isArray(parsed.changedFiles)) {
3294
+ return {
3295
+ ok: false,
3296
+ reason: "changedFiles must be an array",
3297
+ diagnostics
3298
+ };
3299
+ }
3300
+ const outOfScopeChangedFiles = [];
3301
+ for (const [index, changedFile] of parsed.changedFiles.entries()) {
3302
+ if (!changedFile || typeof changedFile !== "object") {
3303
+ return {
3304
+ ok: false,
3305
+ reason: `changedFiles[${index}] must be an object`,
3306
+ diagnostics
3307
+ };
3308
+ }
3309
+ const file = changedFile;
3310
+ const pathValidation = validateRepoRelativePathField(
3311
+ file.path,
3312
+ `changedFiles[${index}].path`
3313
+ );
3314
+ if (!pathValidation.ok) {
3315
+ return { ok: false, reason: pathValidation.reason, diagnostics };
3316
+ }
3317
+ if (typeof file.change !== "string" || file.change.trim() === "") {
3318
+ return {
3319
+ ok: false,
3320
+ reason: `changedFiles[${index}].change must be a non-empty string`,
3321
+ diagnostics
3322
+ };
3323
+ }
3324
+ if (!ISSUE_FINAL_REVIEW_SCOPE_VERDICTS.includes(file.scopeVerdict)) {
3325
+ return {
3326
+ ok: false,
3327
+ reason: `changedFiles[${index}].scopeVerdict must be "in-scope", "incidental", or "out-of-scope"`,
3328
+ diagnostics
3329
+ };
3330
+ }
3331
+ if (typeof file.revertFlag !== "boolean") {
3332
+ return {
3333
+ ok: false,
3334
+ reason: `changedFiles[${index}].revertFlag must be a boolean`,
3335
+ diagnostics
3336
+ };
3337
+ }
3338
+ if (file.scopeVerdict === "out-of-scope") {
3339
+ outOfScopeChangedFiles.push(file.path);
3340
+ }
3341
+ }
3342
+ diagnostics.push(
3343
+ `changedFiles: ${parsed.changedFiles.length} files`
3344
+ );
3345
+ if (!Array.isArray(parsed.outOfScopeFiles) || !parsed.outOfScopeFiles.every((p) => typeof p === "string")) {
3275
3346
  return {
3276
3347
  ok: false,
3277
- reason: "changedPaths must be an array of strings",
3348
+ reason: "outOfScopeFiles must be an array of strings",
3278
3349
  diagnostics
3279
3350
  };
3280
3351
  }
3281
- for (const p of parsed.changedPaths) {
3282
- const normalized = p.replace(/\\/g, "/");
3283
- const segments = normalized.split("/");
3284
- if (normalized.trim() === "" || normalized === "." || normalized === ".." || isAbsolute3(p) || normalized.startsWith("/") || segments.some((segment) => segment === "..")) {
3352
+ for (const p of parsed.outOfScopeFiles) {
3353
+ const pathValidation = validateRepoRelativePathField(p, "outOfScopeFiles");
3354
+ if (!pathValidation.ok) {
3355
+ return { ok: false, reason: pathValidation.reason, diagnostics };
3356
+ }
3357
+ }
3358
+ for (const p of outOfScopeChangedFiles) {
3359
+ if (!parsed.outOfScopeFiles.includes(p)) {
3285
3360
  return {
3286
3361
  ok: false,
3287
- reason: `changedPaths must not contain absolute paths or path traversal: ${p}`,
3362
+ reason: `outOfScopeFiles must include changedFiles path marked out-of-scope: ${p}`,
3288
3363
  diagnostics
3289
3364
  };
3290
3365
  }
3291
3366
  }
3292
3367
  diagnostics.push(
3293
- `changedPaths: ${parsed.changedPaths.length} paths`
3368
+ `outOfScopeFiles: ${parsed.outOfScopeFiles.length} paths`
3294
3369
  );
3370
+ if (!Array.isArray(parsed.badReverts)) {
3371
+ return {
3372
+ ok: false,
3373
+ reason: "badReverts must be an array",
3374
+ diagnostics
3375
+ };
3376
+ }
3377
+ for (const [index, badRevert] of parsed.badReverts.entries()) {
3378
+ if (typeof badRevert === "string") {
3379
+ if (badRevert.trim() === "") {
3380
+ return {
3381
+ ok: false,
3382
+ reason: `badReverts[${index}] must not be empty`,
3383
+ diagnostics
3384
+ };
3385
+ }
3386
+ continue;
3387
+ }
3388
+ if (!badRevert || typeof badRevert !== "object") {
3389
+ return {
3390
+ ok: false,
3391
+ reason: `badReverts[${index}] must be a string or object`,
3392
+ diagnostics
3393
+ };
3394
+ }
3395
+ const entry = badRevert;
3396
+ const pathValidation = validateRepoRelativePathField(
3397
+ entry.path,
3398
+ `badReverts[${index}].path`
3399
+ );
3400
+ if (!pathValidation.ok) {
3401
+ return { ok: false, reason: pathValidation.reason, diagnostics };
3402
+ }
3403
+ const description = entry.hunk ?? entry.description;
3404
+ if (typeof description !== "string" || description.trim() === "") {
3405
+ return {
3406
+ ok: false,
3407
+ reason: `badReverts[${index}] must include a non-empty hunk or description`,
3408
+ diagnostics
3409
+ };
3410
+ }
3411
+ }
3412
+ diagnostics.push(`badReverts: ${parsed.badReverts.length}`);
3413
+ if (parsed.verdict === "pass") {
3414
+ if (parsed.outOfScopeFiles.length > 0) {
3415
+ return {
3416
+ ok: false,
3417
+ reason: "pass verdict requires outOfScopeFiles to be empty",
3418
+ diagnostics
3419
+ };
3420
+ }
3421
+ if (parsed.badReverts.length > 0) {
3422
+ return {
3423
+ ok: false,
3424
+ reason: "pass verdict requires badReverts to be empty",
3425
+ diagnostics
3426
+ };
3427
+ }
3428
+ }
3295
3429
  if (typeof parsed.selfRetouched !== "boolean") {
3296
3430
  return {
3297
3431
  ok: false,
@@ -6054,7 +6188,7 @@ async function runIssueFinalReviewAgent(options) {
6054
6188
  verdict: "pass",
6055
6189
  artifactPath,
6056
6190
  selfRetouched: parsed.selfRetouched === true,
6057
- changedPaths: parsed.changedPaths,
6191
+ changedPaths: extractChangedPaths(parsed),
6058
6192
  verificationPassed: parsed.verification?.passed === true
6059
6193
  };
6060
6194
  }
@@ -6064,13 +6198,18 @@ async function runIssueFinalReviewAgent(options) {
6064
6198
  artifactPath,
6065
6199
  needsHumanReason: parsed.needsHumanReason,
6066
6200
  selfRetouched: parsed.selfRetouched === true,
6067
- changedPaths: parsed.changedPaths
6201
+ changedPaths: extractChangedPaths(parsed)
6068
6202
  };
6069
6203
  }
6070
6204
  throw new Error(
6071
6205
  `Unknown Issue Final Review verdict: ${JSON.stringify(verdict)}`
6072
6206
  );
6073
6207
  }
6208
+ function extractChangedPaths(parsed) {
6209
+ return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
6210
+ (file) => file && typeof file === "object" ? file.path : void 0
6211
+ ).filter((path9) => typeof path9 === "string") : [];
6212
+ }
6074
6213
  function loadIssueFinalReviewPrompt(repoRoot2, promptTemplate) {
6075
6214
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
6076
6215
  const promptBody = existsSync11(promptPath) ? readFileSync13(promptPath, "utf-8") : promptTemplate;
@@ -6971,6 +7110,25 @@ async function completeIssueRun(options) {
6971
7110
  });
6972
7111
  }
6973
7112
  }
7113
+ if (executionResult.worktreePath && !worktreeState?.pr?.issueFinalReviewSummaryCommented) {
7114
+ const summary = readIssueFinalReviewSummary(
7115
+ ifrState,
7116
+ executionResult.worktreePath
7117
+ );
7118
+ if (summary) {
7119
+ await prProvider.commentPr?.(
7120
+ pr.number,
7121
+ buildIssueFinalReviewPrComment(summary)
7122
+ );
7123
+ if (prProvider.commentPr) {
7124
+ updateWorktreeRunState(executionResult.worktreePath, {
7125
+ pr: {
7126
+ issueFinalReviewSummaryCommented: true
7127
+ }
7128
+ });
7129
+ }
7130
+ }
7131
+ }
6974
7132
  if (target.autoMerge === false) {
6975
7133
  await prProvider.waitForPrChecks(pr.number, checkWaitOptions);
6976
7134
  const transitions = makeIssueTransitions(issueProvider, config);
@@ -7124,6 +7282,19 @@ function extractHumanHandoffSummary(artifactContent) {
7124
7282
  }
7125
7283
  return summaryLines.join("\n").trim() || "(No Human Handoff Summary provided)";
7126
7284
  }
7285
+ function readIssueFinalReviewSummary(worktreeState, worktreePath) {
7286
+ const configuredPath = worktreeState?.issueFinalReview?.artifactPath;
7287
+ if (!configuredPath) {
7288
+ return null;
7289
+ }
7290
+ const artifactPath = isAbsolute4(configuredPath) ? configuredPath : join15(worktreePath, configuredPath);
7291
+ if (!existsSync12(artifactPath)) return null;
7292
+ const parsed = JSON.parse(readFileSync14(artifactPath, "utf-8"));
7293
+ return typeof parsed.summary === "string" && parsed.summary.trim() !== "" ? parsed.summary.trim() : null;
7294
+ }
7295
+ function buildIssueFinalReviewPrComment(summary) {
7296
+ return ["## Issue Final Review", "", summary].join("\n");
7297
+ }
7127
7298
  function getRefactorArtifactDir(artifactPath) {
7128
7299
  return artifactPath.replace(/\/reviewers\//, "/refactors/").replace(/\/[^/]+$/, "");
7129
7300
  }
@@ -7900,10 +8071,8 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
7900
8071
  }
7901
8072
 
7902
8073
  // commands/prd-run.ts
7903
- import { existsSync as existsSync16, lstatSync, readFileSync as readFileSync18, realpathSync } from "fs";
7904
- import { spawnSync as spawnSync3 } from "child_process";
7905
- import { join as join19, relative as relative2 } from "path";
7906
- import { Match, pipe } from "effect";
8074
+ import { lstatSync, realpathSync } from "fs";
8075
+ import { join as join20 } from "path";
7907
8076
 
7908
8077
  // prd-run/state.ts
7909
8078
  import {
@@ -8123,6 +8292,12 @@ function getRecordPath(repoRoot2, prdRef) {
8123
8292
  );
8124
8293
  }
8125
8294
 
8295
+ // prd-run/coordinator.ts
8296
+ import { spawnSync as spawnSync3 } from "child_process";
8297
+ import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
8298
+ import { join as join19 } from "path";
8299
+ import { Match, pipe } from "effect";
8300
+
8126
8301
  // prd-run/local-queue-loop.ts
8127
8302
  import { readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
8128
8303
  import { join as join18 } from "path";
@@ -8981,7 +9156,7 @@ function makeReconcileDeps(options) {
8981
9156
  }
8982
9157
  function reconcileBlockedEffect(options) {
8983
9158
  return Effect8.gen(function* () {
8984
- const blocked = yield* Effect8.tryPromise({
9159
+ const blocked2 = yield* Effect8.tryPromise({
8985
9160
  try: () => options.issueProvider.listBlockedIssues(),
8986
9161
  catch: (e) => {
8987
9162
  if (e instanceof Error) {
@@ -8990,13 +9165,13 @@ function reconcileBlockedEffect(options) {
8990
9165
  throw e;
8991
9166
  }
8992
9167
  });
8993
- if (blocked.length > 0) {
9168
+ if (blocked2.length > 0) {
8994
9169
  options.logger.step(
8995
9170
  "info",
8996
- `Reconciling ${blocked.length} blocked issue(s)`
9171
+ `Reconciling ${blocked2.length} blocked issue(s)`
8997
9172
  );
8998
9173
  yield* Effect8.tryPromise({
8999
- try: () => reconcileBlockedIssues(blocked, makeReconcileDeps(options)),
9174
+ try: () => reconcileBlockedIssues(blocked2, makeReconcileDeps(options)),
9000
9175
  catch: (e) => {
9001
9176
  if (e instanceof Error) {
9002
9177
  return new QueueProviderError(e.message);
@@ -9136,11 +9311,19 @@ async function runQueueCommand(options) {
9136
9311
  return runEffectAndMapExit(runQueueLoop(queueOptions));
9137
9312
  }
9138
9313
 
9139
- // prd-run/final-review-validation.ts
9140
- import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
9141
-
9142
- // commands/prd-run.ts
9143
- function planLaunchResume(record) {
9314
+ // prd-run/coordinator.ts
9315
+ function canRetryFinalReviewBlock(record) {
9316
+ if (record.status !== "blocked" || record.blockedGate !== "final-review") {
9317
+ return false;
9318
+ }
9319
+ if (record.start?.queueDrainedAt || record.finalReview) {
9320
+ return true;
9321
+ }
9322
+ return Boolean(
9323
+ record.start && !record.blockedReason?.includes("not drained")
9324
+ );
9325
+ }
9326
+ function planPrdRunLaunchResume(record) {
9144
9327
  if (!record) return { attempted: [], skipped: [], resumed: [] };
9145
9328
  return pipe(
9146
9329
  record,
@@ -9167,7 +9350,7 @@ function planLaunchResume(record) {
9167
9350
  })),
9168
9351
  Match.when({ status: "final_reviewed" }, () => ({
9169
9352
  attempted: [],
9170
- skipped: ["start", "queue"],
9353
+ skipped: [],
9171
9354
  resumed: [],
9172
9355
  blocked: "final_reviewed-incompatible"
9173
9356
  })),
@@ -9180,7 +9363,7 @@ function planLaunchResume(record) {
9180
9363
  })
9181
9364
  ),
9182
9365
  Match.when(
9183
- (value) => value.status === "starting" || value.status === "running" || value.status === "blocked" && (value.blockedGate === "queue" || value.blockedGate === "branch-state"),
9366
+ (value) => value.status === "starting" || value.status === "running",
9184
9367
  (value) => value.start ? {
9185
9368
  attempted: [],
9186
9369
  skipped: [],
@@ -9192,6 +9375,27 @@ function planLaunchResume(record) {
9192
9375
  blocked: "missing-start-receipt"
9193
9376
  }
9194
9377
  ),
9378
+ Match.when(
9379
+ (value) => value.status === "blocked" && value.blockedGate === "branch-state",
9380
+ () => ({
9381
+ attempted: [],
9382
+ skipped: [],
9383
+ resumed: ["start"]
9384
+ })
9385
+ ),
9386
+ Match.when(
9387
+ (value) => value.status === "blocked" && value.blockedGate === "queue",
9388
+ (value) => value.start ? {
9389
+ attempted: [],
9390
+ skipped: ["start"],
9391
+ resumed: ["queue"]
9392
+ } : {
9393
+ attempted: [],
9394
+ skipped: ["start", "queue"],
9395
+ resumed: [],
9396
+ blocked: "missing-start-receipt"
9397
+ }
9398
+ ),
9195
9399
  Match.orElse(() => ({
9196
9400
  attempted: [],
9197
9401
  skipped: [],
@@ -9199,19 +9403,109 @@ function planLaunchResume(record) {
9199
9403
  }))
9200
9404
  );
9201
9405
  }
9406
+ function resolvePrdRunBaseBranch(options) {
9407
+ const raw = options.baseBranchOverride ?? options.targetBaseBranch;
9408
+ const trimmed = raw.trim();
9409
+ if (!trimmed) {
9410
+ return {
9411
+ ok: false,
9412
+ gate: "branch-state",
9413
+ reason: `Invalid PRD Run base branch: value is empty after trimming.`,
9414
+ diagnostics: [`Provided branch value: "${raw}"`],
9415
+ offendingPaths: []
9416
+ };
9417
+ }
9418
+ if (trimmed.startsWith("origin/")) {
9419
+ return {
9420
+ ok: false,
9421
+ gate: "branch-state",
9422
+ reason: `Invalid PRD Run base branch: "${trimmed}" must not include "origin/" prefix. Use just the branch lane name (e.g. "dev" instead of "origin/dev").`,
9423
+ diagnostics: [`Provided branch value: "${trimmed}"`],
9424
+ offendingPaths: []
9425
+ };
9426
+ }
9427
+ if (/^(refs\/|heads\/|remotes\/|tags\/)/.test(trimmed)) {
9428
+ return {
9429
+ ok: false,
9430
+ gate: "branch-state",
9431
+ reason: `Invalid PRD Run base branch: "${trimmed}" must be a simple branch lane name, not a ref specification.`,
9432
+ diagnostics: [`Provided branch value: "${trimmed}"`],
9433
+ offendingPaths: []
9434
+ };
9435
+ }
9436
+ if (/^[0-9a-f]{7,40}$/i.test(trimmed)) {
9437
+ return {
9438
+ ok: false,
9439
+ gate: "branch-state",
9440
+ reason: `Invalid PRD Run base branch: value looks like a commit SHA rather than a branch lane name.`,
9441
+ diagnostics: [`Provided branch value: "${trimmed}"`],
9442
+ offendingPaths: []
9443
+ };
9444
+ }
9445
+ const segments = trimmed.split("/");
9446
+ for (const segment of segments) {
9447
+ if (segment === "." || segment === ".." || segment === "") {
9448
+ return {
9449
+ ok: false,
9450
+ gate: "branch-state",
9451
+ reason: `Invalid PRD Run base branch: "${trimmed}" must not contain ".", "..", or empty segments.`,
9452
+ diagnostics: [`Provided branch value: "${trimmed}"`],
9453
+ offendingPaths: []
9454
+ };
9455
+ }
9456
+ }
9457
+ if (/[~^:?*\[\\]/.test(trimmed)) {
9458
+ return {
9459
+ ok: false,
9460
+ gate: "branch-state",
9461
+ reason: `Invalid PRD Run base branch: "${trimmed}" contains invalid characters (~ ^ : ? * [ \\).`,
9462
+ diagnostics: [`Provided branch value: "${trimmed}"`],
9463
+ offendingPaths: []
9464
+ };
9465
+ }
9466
+ if (/[\x00-\x1f\x7f]/.test(trimmed)) {
9467
+ return {
9468
+ ok: false,
9469
+ gate: "branch-state",
9470
+ reason: `Invalid PRD Run base branch: value contains control characters.`,
9471
+ diagnostics: [`Provided branch value: "${trimmed}"`],
9472
+ offendingPaths: []
9473
+ };
9474
+ }
9475
+ if (trimmed.endsWith("/")) {
9476
+ return {
9477
+ ok: false,
9478
+ gate: "branch-state",
9479
+ reason: `Invalid PRD Run base branch: "${trimmed}" must not end with a trailing slash.`,
9480
+ diagnostics: [`Provided branch value: "${trimmed}"`],
9481
+ offendingPaths: []
9482
+ };
9483
+ }
9484
+ if (trimmed.endsWith(".lock")) {
9485
+ return {
9486
+ ok: false,
9487
+ gate: "branch-state",
9488
+ reason: `Invalid PRD Run base branch: "${trimmed}" must not end with ".lock" suffix.`,
9489
+ diagnostics: [`Provided branch value: "${trimmed}"`],
9490
+ offendingPaths: []
9491
+ };
9492
+ }
9493
+ const source = options.baseBranchOverride !== void 0 ? "override" : "target";
9494
+ return { ok: true, baseBranch: trimmed, source };
9495
+ }
9202
9496
  function validateLocalStartStore(repoRoot2, prdRef) {
9203
9497
  const localStoreDir = join19(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
9498
+ const localStorePath = join19(localStoreDir, "prd.json");
9204
9499
  let localStoreReady = false;
9205
- if (existsSync16(localStoreDir)) {
9206
- const localStorePath = join19(localStoreDir, "prd.json");
9500
+ if (existsSync15(localStorePath)) {
9207
9501
  try {
9208
- const content = JSON.parse(readFileSync18(localStorePath, "utf8"));
9502
+ const content = JSON.parse(readFileSync17(localStorePath, "utf8"));
9209
9503
  localStoreReady = content?.id === prdRef && content?.kind === "prd";
9210
9504
  } catch {
9211
9505
  localStoreReady = false;
9212
9506
  }
9213
9507
  }
9214
- if (existsSync16(localStoreDir) && !localStoreReady) {
9508
+ if (!localStoreReady) {
9215
9509
  return {
9216
9510
  ok: false,
9217
9511
  gate: "branch-state",
@@ -9225,213 +9519,178 @@ function validateLocalStartStore(repoRoot2, prdRef) {
9225
9519
  }
9226
9520
  return { ok: true };
9227
9521
  }
9228
- function buildStartReceipt(options) {
9229
- return {
9230
- status: options.status,
9231
- targetName: options.targetName,
9232
- prdBranch: options.prdBranch,
9233
- startBaseBranch: options.startBaseBranch,
9234
- startBaseCommit: options.startBaseCommit,
9235
- branchAction: options.branchAction,
9236
- startedAt: options.startedAt,
9237
- refreshReceipts: []
9238
- };
9239
- }
9240
- function canRetryFinalReviewBlock(record) {
9241
- if (record.status !== "blocked" || record.blockedGate !== "final-review") {
9242
- return false;
9243
- }
9244
- if (record.start?.queueDrainedAt || record.finalReview) {
9245
- return true;
9522
+ function fetchOriginBranch(repoRoot2, baseBranch) {
9523
+ const fetchResult = spawnSync3("git", ["fetch", "origin", baseBranch], {
9524
+ cwd: repoRoot2,
9525
+ encoding: "utf8"
9526
+ });
9527
+ if (fetchResult.status !== 0) {
9528
+ return {
9529
+ ok: false,
9530
+ gate: "git",
9531
+ reason: `PRD Run start failed while fetching origin/${baseBranch}.`,
9532
+ diagnostics: [
9533
+ fetchResult.error instanceof Error ? fetchResult.error.message : void 0,
9534
+ fetchResult.stderr?.toString?.() ?? String(fetchResult.stderr ?? ""),
9535
+ fetchResult.stdout?.toString?.() ?? String(fetchResult.stdout ?? "")
9536
+ ].filter((value) => Boolean(value && value.trim())),
9537
+ offendingPaths: []
9538
+ };
9246
9539
  }
9247
- return Boolean(
9248
- record.start && !record.blockedReason?.includes("not drained")
9249
- );
9250
- }
9251
- async function runPrdRunLaunchCommand(options) {
9252
- const prdRef = normalizePrdRunRef(options.prdRef);
9253
- const attempted = [];
9254
- const skipped = [];
9255
- const resumed = [];
9256
- const diagnostics = [];
9257
- const existingRecord = readPrdRun(options.repoRoot, prdRef);
9258
- const resolvedLaunchMode = options.config ? (() => {
9259
- try {
9260
- const target = resolveTarget(options.config, options.targetName);
9261
- return resolvePrdRunMode(target).mode;
9262
- } catch {
9263
- return void 0;
9540
+ const revParseResult = spawnSync3(
9541
+ "git",
9542
+ ["rev-parse", `origin/${baseBranch}`],
9543
+ {
9544
+ cwd: repoRoot2,
9545
+ encoding: "utf8"
9264
9546
  }
9265
- })() : void 0;
9266
- if (resolvedLaunchMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedLaunchMode) {
9547
+ );
9548
+ if (revParseResult.status !== 0) {
9267
9549
  return {
9268
- prdRef,
9269
- status: "blocked",
9270
- attempted: [],
9271
- skipped: ["start", "queue"],
9272
- resumed: [],
9550
+ ok: false,
9551
+ gate: "git",
9552
+ reason: `PRD Run start failed while resolving origin/${baseBranch}.`,
9273
9553
  diagnostics: [
9274
- `Recorded mode: ${existingRecord.record.mode}`,
9275
- `Resolved mode: ${resolvedLaunchMode}`
9276
- ],
9277
- blockedGate: "branch-state",
9278
- blockedReason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedLaunchMode}". Resolve by using the correct target.`,
9554
+ revParseResult.error instanceof Error ? revParseResult.error.message : void 0,
9555
+ revParseResult.stderr?.toString?.() ?? String(revParseResult.stderr ?? ""),
9556
+ revParseResult.stdout?.toString?.() ?? String(revParseResult.stdout ?? "")
9557
+ ].filter((value) => Boolean(value && value.trim())),
9279
9558
  offendingPaths: []
9280
9559
  };
9281
9560
  }
9282
- const plan = planLaunchResume(existingRecord.record);
9283
- skipped.push(...plan.skipped);
9284
- resumed.push(...plan.resumed);
9285
- if (plan.blocked === "missing-start-receipt") {
9561
+ return { ok: true, startBaseCommit: revParseResult.stdout.trim() };
9562
+ }
9563
+ function inspectRemotePrdBranch(repoRoot2, prdRef) {
9564
+ const result = spawnSync3("git", ["ls-remote", "--heads", "origin", prdRef], {
9565
+ cwd: repoRoot2,
9566
+ encoding: "utf8"
9567
+ });
9568
+ if (result.status !== 0) {
9286
9569
  return {
9287
- prdRef,
9288
- status: "blocked",
9289
- attempted: [],
9290
- skipped,
9291
- resumed,
9570
+ ok: false,
9571
+ gate: "git",
9572
+ reason: `PRD Run start failed while inspecting remote branch ${prdRef}.`,
9292
9573
  diagnostics: [
9293
- `PRD Run ${prdRef} is in status "${existingRecord.record?.status}" but has no start receipt.`
9294
- ],
9295
- blockedGate: "branch-state",
9296
- blockedReason: `PRD Run ${prdRef} is missing branch/start state. Run prd-run start or rerun prd-run launch.`,
9574
+ result.error instanceof Error ? result.error.message : void 0,
9575
+ result.stderr?.toString?.() ?? String(result.stderr ?? ""),
9576
+ result.stdout?.toString?.() ?? String(result.stdout ?? "")
9577
+ ].filter((value) => Boolean(value && value.trim())),
9297
9578
  offendingPaths: []
9298
9579
  };
9299
9580
  }
9300
- if (plan.blocked === "final_reviewed-incompatible") {
9301
- return {
9302
- prdRef,
9303
- status: "blocked",
9304
- attempted: [],
9305
- skipped: [],
9306
- resumed: [],
9307
- diagnostics: [
9308
- `PRD Run ${prdRef} has obsolete status "final_reviewed". PRD-wide Final Review has been removed from the launch lifecycle.`
9309
- ],
9310
- blockedGate: "final-review",
9311
- blockedReason: `PRD Run ${prdRef} has status "final_reviewed", which is no longer a valid launch status. PRD-wide Final Review has been removed from the launch lifecycle. Update the PRD Run state to "drained" and rerun launch, or start a new PRD Run.`,
9312
- offendingPaths: []
9313
- };
9581
+ const headSha = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0]?.split(/\s+/)[0];
9582
+ if (!headSha) {
9583
+ return { ok: true, exists: false };
9314
9584
  }
9315
- if (existingRecord.record?.status === "completed_prd_branch" || existingRecord.record?.status === "complete" || existingRecord.record?.status === "completed_local_branch") {
9585
+ return { ok: true, exists: true, headSha };
9586
+ }
9587
+ function fetchPrdBranch(repoRoot2, prdRef) {
9588
+ const result = spawnSync3("git", ["fetch", "origin", prdRef], {
9589
+ cwd: repoRoot2,
9590
+ encoding: "utf8"
9591
+ });
9592
+ if (result.status !== 0) {
9316
9593
  return {
9317
- prdRef,
9318
- status: existingRecord.record.status,
9319
- attempted,
9320
- skipped,
9321
- resumed,
9594
+ ok: false,
9595
+ gate: "git",
9596
+ reason: `PRD Run start failed while fetching origin/${prdRef}.`,
9322
9597
  diagnostics: [
9323
- `PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`,
9324
- ...existingRecord.record.status === "completed_prd_branch" ? [
9325
- buildCompletedPrdBranchHint(
9326
- existingRecord.record.prdBranch ?? prdRef
9327
- )
9328
- ] : []
9329
- ],
9330
- prdBranch: existingRecord.record.prdBranch
9598
+ result.error instanceof Error ? result.error.message : void 0,
9599
+ result.stderr?.toString?.() ?? String(result.stderr ?? ""),
9600
+ result.stdout?.toString?.() ?? String(result.stdout ?? "")
9601
+ ].filter((value) => Boolean(value && value.trim())),
9602
+ offendingPaths: []
9331
9603
  };
9332
9604
  }
9333
- let startResult;
9334
- if (!skipped.includes("start")) {
9335
- attempted.push("start");
9336
- startResult = await runPrdRunStartCommand({
9337
- repoRoot: options.repoRoot,
9338
- prdRef,
9339
- targetName: options.targetName,
9340
- config: options.config,
9341
- issueProvider: options.issueProvider,
9342
- prProvider: options.prProvider,
9343
- executionProvider: options.executionProvider,
9344
- logger: options.logger,
9345
- adoptExistingBranch: options.adoptExistingBranch,
9346
- baseBranchOverride: options.baseBranchOverride
9347
- });
9348
- const skippedAfterStart = ["queue"];
9349
- if (startResult.status === "blocked") {
9350
- return {
9351
- prdRef,
9352
- status: "blocked",
9353
- attempted,
9354
- skipped: skippedAfterStart,
9355
- resumed,
9356
- diagnostics: startResult.diagnostics,
9357
- blockedGate: startResult.blockedGate,
9358
- blockedReason: startResult.blockedReason,
9359
- offendingPaths: startResult.offendingPaths,
9360
- start: startResult
9361
- };
9362
- }
9363
- if (startResult.status === "starting") {
9364
- return {
9365
- prdRef,
9366
- status: "starting",
9367
- attempted,
9368
- skipped: skippedAfterStart,
9369
- resumed,
9370
- diagnostics: startResult.diagnostics,
9371
- start: startResult
9372
- };
9605
+ return { ok: true };
9606
+ }
9607
+ async function ensurePrdBranchPublished(repoRoot2, prdRef, startBaseCommit) {
9608
+ const pushResult = spawnSync3(
9609
+ "git",
9610
+ ["push", "origin", `${startBaseCommit}:refs/heads/${prdRef}`],
9611
+ {
9612
+ cwd: repoRoot2,
9613
+ encoding: "utf8"
9373
9614
  }
9374
- diagnostics.push(...startResult.diagnostics);
9375
- attempted.push("queue");
9376
- }
9377
- const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
9378
- const localStorePath = join19(
9379
- options.repoRoot,
9380
- ".pourkit",
9381
- "local-prd-runs",
9382
- prdRef
9383
9615
  );
9384
- if (existsSync16(localStorePath)) {
9385
- writePrdRunRecord(options.repoRoot, {
9386
- prdRef,
9387
- status: "completed_local_branch",
9388
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9389
- mode: "local",
9390
- targetName: currentRecord?.targetName ?? options.targetName,
9391
- prdBranch: currentRecord?.prdBranch ?? `local/${prdRef}`,
9392
- start: currentRecord?.start,
9393
- scopeChanges: currentRecord?.scopeChanges
9394
- });
9395
- return {
9396
- prdRef,
9397
- status: "completed_local_branch",
9398
- prdBranch: currentRecord?.prdBranch ?? `local/${prdRef}`,
9399
- attempted,
9400
- skipped,
9401
- resumed,
9402
- diagnostics: [
9403
- `Local PRD Run ${prdRef} completed. Branch: local/${prdRef}.`
9404
- ],
9405
- start: startResult
9406
- };
9616
+ if (pushResult.status !== 0) {
9617
+ throw new Error(
9618
+ `Failed to create PRD Branch ${prdRef} from ${startBaseCommit}: ${[
9619
+ pushResult.error instanceof Error ? pushResult.error.message : void 0,
9620
+ pushResult.stderr?.toString?.() ?? String(pushResult.stderr ?? ""),
9621
+ pushResult.stdout?.toString?.() ?? String(pushResult.stdout ?? "")
9622
+ ].filter((value) => Boolean(value && value.trim())).join(" ")}`
9623
+ );
9407
9624
  }
9408
- writePrdRunRecord(options.repoRoot, {
9625
+ }
9626
+ function buildStartReceipt(options) {
9627
+ return {
9628
+ status: options.status,
9629
+ targetName: options.targetName,
9630
+ prdBranch: options.prdBranch,
9631
+ startBaseBranch: options.startBaseBranch,
9632
+ startBaseCommit: options.startBaseCommit,
9633
+ branchAction: options.branchAction,
9634
+ startedAt: options.startedAt,
9635
+ refreshReceipts: []
9636
+ };
9637
+ }
9638
+ function buildBlockedOutcome(repoRoot2, prdRef, guidance, targetName) {
9639
+ writePrdRunRecord(repoRoot2, {
9409
9640
  prdRef,
9410
- status: "completed_prd_branch",
9641
+ status: "blocked",
9411
9642
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9412
- targetName: currentRecord?.targetName ?? options.targetName,
9413
- prdBranch: currentRecord?.prdBranch,
9414
- start: currentRecord?.start,
9415
- scopeChanges: currentRecord?.scopeChanges
9643
+ blockedGate: guidance.blockedGate,
9644
+ blockedReason: guidance.reason,
9645
+ diagnostics: [...guidance.diagnostics ?? []],
9646
+ offendingPaths: [...guidance.offendingPaths ?? []],
9647
+ targetName
9416
9648
  });
9417
- return {
9649
+ return { kind: "blocked", prdRef, guidance };
9650
+ }
9651
+ function persistStartingPrdRunRecord(repoRoot2, prdRef, existingRecord, start, context) {
9652
+ const {
9653
+ blockedGate: _blockedGate,
9654
+ blockedReason: _blockedReason,
9655
+ diagnostics: _diagnostics,
9656
+ offendingPaths: _offendingPaths,
9657
+ prdBranch: _prdBranch,
9658
+ start: _start,
9659
+ ...cleanRecord
9660
+ } = existingRecord.record ?? { prdRef };
9661
+ writePrdRunRecord(repoRoot2, {
9662
+ ...cleanRecord,
9418
9663
  prdRef,
9419
- status: "completed_prd_branch",
9420
- attempted,
9421
- skipped,
9422
- resumed,
9423
- diagnostics: [
9424
- buildCompletedPrdBranchHint(currentRecord?.prdBranch ?? prdRef)
9425
- ],
9426
- start: startResult,
9427
- prdBranch: currentRecord?.prdBranch ?? prdRef
9428
- };
9664
+ status: "starting",
9665
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9666
+ targetName: context.targetName,
9667
+ start,
9668
+ mode: context.mode
9669
+ });
9429
9670
  }
9430
- async function runPrdRunStartCommand(options) {
9671
+ function blocked(repoRoot2, prdRef, gate, failureCode, reason, diagnostics, targetName) {
9672
+ return buildBlockedOutcome(
9673
+ repoRoot2,
9674
+ prdRef,
9675
+ {
9676
+ blockedGate: gate,
9677
+ failureCode,
9678
+ reason,
9679
+ diagnostics: diagnostics ? [...diagnostics] : [],
9680
+ repairability: "operator-action"
9681
+ },
9682
+ targetName
9683
+ );
9684
+ }
9685
+ async function startPrdRun(options) {
9431
9686
  const prdRef = normalizePrdRunRef(options.prdRef);
9432
9687
  const targetName = options.targetName.trim();
9433
9688
  if (!targetName) {
9434
- throw new Error(
9689
+ return blocked(
9690
+ options.repoRoot,
9691
+ prdRef,
9692
+ "branch-state",
9693
+ "invalid_target_name",
9435
9694
  `Invalid target name "${options.targetName}". Expected a non-empty target name.`
9436
9695
  );
9437
9696
  }
@@ -9445,20 +9704,18 @@ async function runPrdRunStartCommand(options) {
9445
9704
  }
9446
9705
  })() : void 0;
9447
9706
  if (resolvedMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedMode) {
9448
- const failure = {
9449
- ok: false,
9450
- gate: "branch-state",
9451
- reason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedMode}". Resolve by using the correct target or --local-override.`,
9452
- diagnostics: [
9707
+ return blocked(
9708
+ options.repoRoot,
9709
+ prdRef,
9710
+ "branch-state",
9711
+ "mode_mismatch",
9712
+ `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedMode}". Resolve by using the correct target.`,
9713
+ [
9453
9714
  `Recorded mode: ${existingRecord.record.mode}`,
9454
9715
  `Resolved mode: ${resolvedMode}`
9455
9716
  ],
9456
- offendingPaths: []
9457
- };
9458
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
9459
9717
  targetName
9460
- });
9461
- return buildBlockedStartResult(prdRef, failure);
9718
+ );
9462
9719
  }
9463
9720
  const resolvedTarget = options.config ? resolveTarget(options.config, targetName) : null;
9464
9721
  const targetBaseBranch = resolvedTarget?.baseBranch ?? "main";
@@ -9467,57 +9724,75 @@ async function runPrdRunStartCommand(options) {
9467
9724
  baseBranchOverride: options.baseBranchOverride
9468
9725
  });
9469
9726
  if (!baseResolution.ok) {
9470
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, baseResolution, {
9727
+ return blocked(
9728
+ options.repoRoot,
9729
+ prdRef,
9730
+ baseResolution.gate,
9731
+ "base_resolution_failed",
9732
+ baseResolution.reason,
9733
+ baseResolution.diagnostics,
9471
9734
  targetName
9472
- });
9473
- return buildBlockedStartResult(prdRef, baseResolution);
9735
+ );
9474
9736
  }
9475
9737
  if (existingRecord.record?.start) {
9476
9738
  const recordedBaseBranch = existingRecord.record.start.startBaseBranch;
9477
9739
  if (!recordedBaseBranch) {
9478
- const failure = {
9479
- ok: false,
9480
- gate: "branch-state",
9481
- reason: `PRD Run ${prdRef} has a start receipt without startBaseBranch. Run prd-run start to create a fresh start receipt.`,
9482
- diagnostics: [
9483
- `Recorded start: ${JSON.stringify(existingRecord.record.start)}`
9484
- ],
9485
- offendingPaths: []
9486
- };
9487
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
9740
+ return blocked(
9741
+ options.repoRoot,
9742
+ prdRef,
9743
+ "branch-state",
9744
+ "missing_start_base_branch",
9745
+ `PRD Run ${prdRef} has a start receipt without startBaseBranch. Run prd-run start to create a fresh start receipt.`,
9746
+ [`Recorded start: ${JSON.stringify(existingRecord.record.start)}`],
9488
9747
  targetName
9489
- });
9490
- return buildBlockedStartResult(prdRef, failure);
9748
+ );
9491
9749
  }
9492
9750
  if (recordedBaseBranch !== baseResolution.baseBranch) {
9493
- const failure = {
9494
- ok: false,
9495
- gate: "branch-state",
9496
- reason: `PRD Run ${prdRef} start receipt records startBaseBranch "${recordedBaseBranch}" but current base resolution resolves to "${baseResolution.baseBranch}". Use --base with the matching value or create a fresh start receipt.`,
9497
- diagnostics: [
9751
+ return blocked(
9752
+ options.repoRoot,
9753
+ prdRef,
9754
+ "branch-state",
9755
+ "base_mismatch",
9756
+ `PRD Run ${prdRef} start receipt records startBaseBranch "${recordedBaseBranch}" but current base resolution resolves to "${baseResolution.baseBranch}". Use --base with the matching value or create a fresh start receipt.`,
9757
+ [
9498
9758
  `Recorded startBaseBranch: ${recordedBaseBranch}`,
9499
9759
  `Current base resolution: ${baseResolution.baseBranch} (source: ${baseResolution.source})`
9500
9760
  ],
9501
- offendingPaths: []
9502
- };
9503
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
9504
9761
  targetName
9505
- });
9506
- return buildBlockedStartResult(prdRef, failure);
9762
+ );
9763
+ }
9764
+ const currentStatus = existingRecord.record.status;
9765
+ if (currentStatus === "running" || currentStatus === "drained" || currentStatus === "completed_prd_branch" || currentStatus === "completed_local_branch" || currentStatus === "complete") {
9766
+ return {
9767
+ kind: "started",
9768
+ prdRef,
9769
+ mode: resolvedMode,
9770
+ start: existingRecord.record.start,
9771
+ diagnostics: [],
9772
+ preservedStatus: currentStatus
9773
+ };
9507
9774
  }
9508
9775
  const fetchResult2 = fetchOriginBranch(options.repoRoot, recordedBaseBranch);
9509
9776
  if (!fetchResult2.ok) {
9510
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, fetchResult2, {
9511
- targetName
9512
- });
9513
- return buildBlockedStartResult(prdRef, fetchResult2);
9777
+ return blocked(
9778
+ options.repoRoot,
9779
+ prdRef,
9780
+ fetchResult2.gate,
9781
+ "fetch_failed",
9782
+ fetchResult2.reason,
9783
+ fetchResult2.diagnostics
9784
+ );
9514
9785
  }
9515
9786
  const branchResult2 = inspectRemotePrdBranch(options.repoRoot, prdRef);
9516
9787
  if (!branchResult2.ok) {
9517
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, branchResult2, {
9518
- targetName
9519
- });
9520
- return buildBlockedStartResult(prdRef, branchResult2);
9788
+ return blocked(
9789
+ options.repoRoot,
9790
+ prdRef,
9791
+ branchResult2.gate,
9792
+ "branch_inspection_failed",
9793
+ branchResult2.reason,
9794
+ branchResult2.diagnostics
9795
+ );
9521
9796
  }
9522
9797
  if (branchResult2.exists) {
9523
9798
  const matchingStart = existingRecord.record.start;
@@ -9531,53 +9806,42 @@ async function runPrdRunStartCommand(options) {
9531
9806
  options.repoRoot,
9532
9807
  prdRef,
9533
9808
  existingRecord,
9534
- {
9535
- ...reusedStart
9536
- },
9537
- {
9538
- targetName,
9539
- mode: resolvedMode
9540
- }
9541
- );
9542
- return await processStartResult(
9543
- {
9544
- prdRef,
9545
- status: "starting",
9546
- start: reusedStart,
9547
- diagnostics: []
9548
- },
9549
- options
9809
+ reusedStart,
9810
+ { targetName, mode: resolvedMode }
9550
9811
  );
9812
+ return {
9813
+ kind: "started",
9814
+ prdRef,
9815
+ mode: resolvedMode,
9816
+ start: reusedStart,
9817
+ diagnostics: []
9818
+ };
9551
9819
  }
9552
9820
  if (!options.adoptExistingBranch) {
9553
- const failure = {
9554
- ok: false,
9555
- gate: "branch-state",
9556
- reason: `PRD Branch ${prdRef} already exists remotely but PRD Run state does not record the same branch and start base commit. Use --adopt-existing-branch to adopt it.`,
9557
- diagnostics: [
9558
- `Existing remote branch: ${prdRef}`,
9559
- `Recorded start branch: ${existingRecord.record.start.prdBranch}`,
9560
- `Recorded start base commit: ${existingRecord.record.start.startBaseCommit}`,
9821
+ return blocked(
9822
+ options.repoRoot,
9823
+ prdRef,
9824
+ "branch-state",
9825
+ "unrecorded_branch_exists",
9826
+ `PRD Branch ${prdRef} already exists remotely but PRD Run state does not record the same branch and start base commit. Use --adopt-existing-branch to adopt it.`,
9827
+ [
9828
+ `Existing remote branch: ${prdRef}`,
9829
+ `Recorded start branch: ${matchingStart.prdBranch}`,
9830
+ `Recorded start base commit: ${matchingStart.startBaseCommit}`,
9561
9831
  `Current origin/${recordedBaseBranch} head: ${fetchResult2.startBaseCommit}`
9562
- ],
9563
- offendingPaths: []
9564
- };
9565
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
9566
- targetName
9567
- });
9568
- return buildBlockedStartResult(prdRef, failure);
9832
+ ]
9833
+ );
9569
9834
  }
9570
9835
  const fetchPrdResult = fetchPrdBranch(options.repoRoot, prdRef);
9571
9836
  if (!fetchPrdResult.ok) {
9572
- persistBlockedPrdRunStartRecord(
9837
+ return blocked(
9573
9838
  options.repoRoot,
9574
9839
  prdRef,
9575
- fetchPrdResult,
9576
- {
9577
- targetName
9578
- }
9840
+ fetchPrdResult.gate,
9841
+ "fetch_prd_branch_failed",
9842
+ fetchPrdResult.reason,
9843
+ fetchPrdResult.diagnostics
9579
9844
  );
9580
- return buildBlockedStartResult(prdRef, fetchPrdResult);
9581
9845
  }
9582
9846
  const ancestryResult = spawnSync3(
9583
9847
  "git",
@@ -9590,20 +9854,17 @@ async function runPrdRunStartCommand(options) {
9590
9854
  { cwd: options.repoRoot, encoding: "utf8" }
9591
9855
  );
9592
9856
  if (ancestryResult.status !== 0) {
9593
- const failure = {
9594
- ok: false,
9595
- gate: "branch-state",
9596
- reason: `PRD Branch ${prdRef} base ancestry check failed: origin/${recordedBaseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
9597
- diagnostics: [
9857
+ return blocked(
9858
+ options.repoRoot,
9859
+ prdRef,
9860
+ "branch-state",
9861
+ "ancestry_check_failed",
9862
+ `PRD Branch ${prdRef} base ancestry check failed: origin/${recordedBaseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
9863
+ [
9598
9864
  `Base branch: origin/${recordedBaseBranch}`,
9599
9865
  `PRD branch: origin/${prdRef}`
9600
- ],
9601
- offendingPaths: []
9602
- };
9603
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
9604
- targetName
9605
- });
9606
- return buildBlockedStartResult(prdRef, failure);
9866
+ ]
9867
+ );
9607
9868
  }
9608
9869
  const adoptedStart = buildStartReceipt({
9609
9870
  status: "adopted",
@@ -9618,23 +9879,16 @@ async function runPrdRunStartCommand(options) {
9618
9879
  options.repoRoot,
9619
9880
  prdRef,
9620
9881
  existingRecord,
9621
- {
9622
- ...adoptedStart
9623
- },
9624
- {
9625
- targetName,
9626
- mode: resolvedMode
9627
- }
9628
- );
9629
- return await processStartResult(
9630
- {
9631
- prdRef,
9632
- status: "starting",
9633
- start: adoptedStart,
9634
- diagnostics: []
9635
- },
9636
- options
9882
+ adoptedStart,
9883
+ { targetName, mode: resolvedMode }
9637
9884
  );
9885
+ return {
9886
+ kind: "started",
9887
+ prdRef,
9888
+ mode: resolvedMode,
9889
+ start: adoptedStart,
9890
+ diagnostics: []
9891
+ };
9638
9892
  }
9639
9893
  }
9640
9894
  const fetchResult = fetchOriginBranch(
@@ -9642,12 +9896,27 @@ async function runPrdRunStartCommand(options) {
9642
9896
  baseResolution.baseBranch
9643
9897
  );
9644
9898
  if (!fetchResult.ok) {
9645
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, fetchResult, {
9646
- targetName
9647
- });
9648
- return buildBlockedStartResult(prdRef, fetchResult);
9899
+ return blocked(
9900
+ options.repoRoot,
9901
+ prdRef,
9902
+ fetchResult.gate,
9903
+ "fetch_failed",
9904
+ fetchResult.reason,
9905
+ fetchResult.diagnostics
9906
+ );
9649
9907
  }
9650
9908
  if (resolvedMode === "local") {
9909
+ const localStoreResult = validateLocalStartStore(options.repoRoot, prdRef);
9910
+ if (!localStoreResult.ok) {
9911
+ return blocked(
9912
+ options.repoRoot,
9913
+ prdRef,
9914
+ localStoreResult.gate,
9915
+ "local_store_invalid",
9916
+ localStoreResult.reason,
9917
+ localStoreResult.diagnostics
9918
+ );
9919
+ }
9651
9920
  const localBranchName = getLocalPrdBranchName(prdRef);
9652
9921
  const localBranchResult = materializeLocalPrdBranch(
9653
9922
  prdRef,
@@ -9655,29 +9924,14 @@ async function runPrdRunStartCommand(options) {
9655
9924
  options.repoRoot
9656
9925
  );
9657
9926
  if (!localBranchResult.ok) {
9658
- const failure = {
9659
- ok: false,
9660
- gate: "branch-state",
9661
- reason: localBranchResult.message,
9662
- diagnostics: [localBranchResult.message],
9663
- offendingPaths: []
9664
- };
9665
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
9666
- targetName
9667
- });
9668
- return buildBlockedStartResult(prdRef, failure);
9669
- }
9670
- const localStoreResult = validateLocalStartStore(options.repoRoot, prdRef);
9671
- if (!localStoreResult.ok) {
9672
- persistBlockedPrdRunStartRecord(
9927
+ return blocked(
9673
9928
  options.repoRoot,
9674
9929
  prdRef,
9675
- localStoreResult,
9676
- {
9677
- targetName
9678
- }
9930
+ "branch-state",
9931
+ "local_branch_failed",
9932
+ localBranchResult.message,
9933
+ [localBranchResult.message]
9679
9934
  );
9680
- return buildBlockedStartResult(prdRef, localStoreResult);
9681
9935
  }
9682
9936
  const updatedAt2 = (/* @__PURE__ */ new Date()).toISOString();
9683
9937
  const start2 = buildStartReceipt({
@@ -9694,58 +9948,53 @@ async function runPrdRunStartCommand(options) {
9694
9948
  prdRef,
9695
9949
  existingRecord,
9696
9950
  start2,
9697
- {
9698
- targetName,
9699
- mode: resolvedMode
9700
- }
9701
- );
9702
- return await processStartResult(
9703
- {
9704
- prdRef,
9705
- status: "starting",
9706
- start: start2,
9707
- diagnostics: []
9708
- },
9709
- options
9951
+ { targetName, mode: resolvedMode }
9710
9952
  );
9953
+ return {
9954
+ kind: "started",
9955
+ prdRef,
9956
+ mode: resolvedMode,
9957
+ start: start2,
9958
+ diagnostics: []
9959
+ };
9711
9960
  }
9712
9961
  const branchResult = inspectRemotePrdBranch(options.repoRoot, prdRef);
9713
9962
  if (!branchResult.ok) {
9714
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, branchResult, {
9715
- targetName
9716
- });
9717
- return buildBlockedStartResult(prdRef, branchResult);
9963
+ return blocked(
9964
+ options.repoRoot,
9965
+ prdRef,
9966
+ branchResult.gate,
9967
+ "branch_inspection_failed",
9968
+ branchResult.reason,
9969
+ branchResult.diagnostics
9970
+ );
9718
9971
  }
9719
9972
  if (branchResult.exists) {
9720
9973
  if (!options.adoptExistingBranch) {
9721
- const failure = {
9722
- ok: false,
9723
- gate: "branch-state",
9724
- reason: `PRD Branch ${prdRef} already exists remotely but PRD Run state does not record the same branch and start base commit. Use --adopt-existing-branch to adopt it.`,
9725
- diagnostics: [
9974
+ return blocked(
9975
+ options.repoRoot,
9976
+ prdRef,
9977
+ "branch-state",
9978
+ "unrecorded_branch_exists",
9979
+ `PRD Branch ${prdRef} already exists remotely but PRD Run state does not record the same branch and start base commit. Use --adopt-existing-branch to adopt it.`,
9980
+ [
9726
9981
  `Existing remote branch: ${prdRef}`,
9727
9982
  `Recorded start branch: ${existingRecord.record?.start?.prdBranch ?? "(missing)"}`,
9728
9983
  `Recorded start base commit: ${existingRecord.record?.start?.startBaseCommit ?? "(missing)"}`,
9729
9984
  `Current origin/${baseResolution.baseBranch} head: ${fetchResult.startBaseCommit}`
9730
- ],
9731
- offendingPaths: []
9732
- };
9733
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
9734
- targetName
9735
- });
9736
- return buildBlockedStartResult(prdRef, failure);
9985
+ ]
9986
+ );
9737
9987
  }
9738
9988
  const fetchPrdResult = fetchPrdBranch(options.repoRoot, prdRef);
9739
9989
  if (!fetchPrdResult.ok) {
9740
- persistBlockedPrdRunStartRecord(
9990
+ return blocked(
9741
9991
  options.repoRoot,
9742
9992
  prdRef,
9743
- fetchPrdResult,
9744
- {
9745
- targetName
9746
- }
9993
+ fetchPrdResult.gate,
9994
+ "fetch_prd_branch_failed",
9995
+ fetchPrdResult.reason,
9996
+ fetchPrdResult.diagnostics
9747
9997
  );
9748
- return buildBlockedStartResult(prdRef, fetchPrdResult);
9749
9998
  }
9750
9999
  const ancestryResult = spawnSync3(
9751
10000
  "git",
@@ -9758,227 +10007,811 @@ async function runPrdRunStartCommand(options) {
9758
10007
  { cwd: options.repoRoot, encoding: "utf8" }
9759
10008
  );
9760
10009
  if (ancestryResult.status !== 0) {
9761
- const failure = {
9762
- ok: false,
9763
- gate: "branch-state",
9764
- reason: `PRD Branch ${prdRef} base ancestry check failed: origin/${baseResolution.baseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
9765
- diagnostics: [
10010
+ return blocked(
10011
+ options.repoRoot,
10012
+ prdRef,
10013
+ "branch-state",
10014
+ "ancestry_check_failed",
10015
+ `PRD Branch ${prdRef} base ancestry check failed: origin/${baseResolution.baseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
10016
+ [
9766
10017
  `Base branch: origin/${baseResolution.baseBranch}`,
9767
10018
  `PRD branch: origin/${prdRef}`
10019
+ ]
10020
+ );
10021
+ }
10022
+ const adoptedStart = buildStartReceipt({
10023
+ status: "adopted",
10024
+ targetName,
10025
+ prdBranch: prdRef,
10026
+ startBaseBranch: baseResolution.baseBranch,
10027
+ startBaseCommit: fetchResult.startBaseCommit,
10028
+ branchAction: "adopted",
10029
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
10030
+ });
10031
+ persistStartingPrdRunRecord(
10032
+ options.repoRoot,
10033
+ prdRef,
10034
+ existingRecord,
10035
+ adoptedStart,
10036
+ { targetName, mode: resolvedMode }
10037
+ );
10038
+ return {
10039
+ kind: "started",
10040
+ prdRef,
10041
+ mode: resolvedMode,
10042
+ start: adoptedStart,
10043
+ diagnostics: []
10044
+ };
10045
+ }
10046
+ try {
10047
+ await ensurePrdBranchPublished(
10048
+ options.repoRoot,
10049
+ prdRef,
10050
+ fetchResult.startBaseCommit
10051
+ );
10052
+ } catch (error) {
10053
+ return blocked(
10054
+ options.repoRoot,
10055
+ prdRef,
10056
+ "git",
10057
+ "branch_creation_failed",
10058
+ `Failed to create PRD Branch ${prdRef} from ${fetchResult.startBaseCommit}.`,
10059
+ [error instanceof Error ? error.message : String(error)].filter(
10060
+ (v) => Boolean(v && v.trim())
10061
+ )
10062
+ );
10063
+ }
10064
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
10065
+ const start = buildStartReceipt({
10066
+ status: "started",
10067
+ targetName,
10068
+ prdBranch: prdRef,
10069
+ startBaseBranch: baseResolution.baseBranch,
10070
+ startBaseCommit: fetchResult.startBaseCommit,
10071
+ branchAction: "created",
10072
+ startedAt: updatedAt
10073
+ });
10074
+ persistStartingPrdRunRecord(options.repoRoot, prdRef, existingRecord, start, {
10075
+ targetName,
10076
+ mode: resolvedMode
10077
+ });
10078
+ return {
10079
+ kind: "started",
10080
+ prdRef,
10081
+ mode: resolvedMode,
10082
+ start,
10083
+ diagnostics: []
10084
+ };
10085
+ }
10086
+ function buildLaunchBlockedOutcome(prdRef, guidance, resume, start) {
10087
+ return {
10088
+ kind: "blocked",
10089
+ prdRef,
10090
+ guidance,
10091
+ start,
10092
+ resume
10093
+ };
10094
+ }
10095
+ function writeDrainedRecord(repoRoot2, prdRef, startReceipt, targetName, mode) {
10096
+ writePrdRunRecord(repoRoot2, {
10097
+ prdRef,
10098
+ status: "drained",
10099
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10100
+ targetName,
10101
+ start: startReceipt,
10102
+ mode
10103
+ });
10104
+ }
10105
+ function writeTerminalRecord(repoRoot2, prdRef, status, targetName, startReceipt, prdBranch, mode) {
10106
+ writePrdRunRecord(repoRoot2, {
10107
+ prdRef,
10108
+ status,
10109
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10110
+ targetName,
10111
+ prdBranch,
10112
+ start: startReceipt,
10113
+ mode
10114
+ });
10115
+ }
10116
+ function validatePrdRunStartEvidence(record, context) {
10117
+ if (!record.start) {
10118
+ return {
10119
+ ok: false,
10120
+ guidance: {
10121
+ blockedGate: "branch-state",
10122
+ failureCode: "missing_start_evidence",
10123
+ reason: `PRD Run ${context.prdRef} has no start receipt.`,
10124
+ repairability: "automatic-retry-safe",
10125
+ diagnostics: [],
10126
+ offendingPaths: []
10127
+ }
10128
+ };
10129
+ }
10130
+ if (!record.start.startBaseBranch) {
10131
+ return {
10132
+ ok: false,
10133
+ guidance: {
10134
+ blockedGate: "branch-state",
10135
+ failureCode: "missing_start_base_branch",
10136
+ reason: `PRD Run ${context.prdRef} start receipt missing startBaseBranch.`,
10137
+ repairability: "operator-action",
10138
+ diagnostics: [],
10139
+ offendingPaths: []
10140
+ }
10141
+ };
10142
+ }
10143
+ if (record.start.startBaseBranch !== context.baseBranch) {
10144
+ return {
10145
+ ok: false,
10146
+ guidance: {
10147
+ blockedGate: "branch-state",
10148
+ failureCode: "base_mismatch",
10149
+ reason: `PRD Run ${context.prdRef} startBaseBranch "${record.start.startBaseBranch}" does not match expected "${context.baseBranch}".`,
10150
+ repairability: "operator-action",
10151
+ diagnostics: [
10152
+ `Recorded startBaseBranch: ${record.start.startBaseBranch}`,
10153
+ `Expected baseBranch: ${context.baseBranch}`
10154
+ ],
10155
+ offendingPaths: []
10156
+ }
10157
+ };
10158
+ }
10159
+ if (!record.start.prdBranch) {
10160
+ return {
10161
+ ok: false,
10162
+ guidance: {
10163
+ blockedGate: "branch-state",
10164
+ failureCode: "missing_prd_branch",
10165
+ reason: `PRD Run ${context.prdRef} start receipt missing prdBranch.`,
10166
+ repairability: "operator-action",
10167
+ diagnostics: [],
10168
+ offendingPaths: []
10169
+ }
10170
+ };
10171
+ }
10172
+ if (record.mode && record.mode !== context.mode) {
10173
+ return {
10174
+ ok: false,
10175
+ guidance: {
10176
+ blockedGate: "branch-state",
10177
+ failureCode: "mode_mismatch",
10178
+ reason: `PRD Run ${context.prdRef} mode mismatch: recorded "${record.mode}" but expected "${context.mode}".`,
10179
+ repairability: "operator-action",
10180
+ diagnostics: [
10181
+ `Recorded mode: ${record.mode}`,
10182
+ `Expected mode: ${context.mode}`
10183
+ ],
10184
+ offendingPaths: []
10185
+ }
10186
+ };
10187
+ }
10188
+ if (record.prdRef !== context.prdRef) {
10189
+ return {
10190
+ ok: false,
10191
+ guidance: {
10192
+ blockedGate: "branch-state",
10193
+ failureCode: "prd_ref_mismatch",
10194
+ reason: `PRD Run prdRef mismatch: "${record.prdRef}" does not match expected "${context.prdRef}".`,
10195
+ repairability: "operator-action",
10196
+ diagnostics: [
10197
+ `Recorded prdRef: ${record.prdRef}`,
10198
+ `Expected prdRef: ${context.prdRef}`
10199
+ ],
10200
+ offendingPaths: []
10201
+ }
10202
+ };
10203
+ }
10204
+ return { ok: true };
10205
+ }
10206
+ function validatePrdRunDrainEvidence(record, context) {
10207
+ const start = record.start;
10208
+ if (!start) {
10209
+ return {
10210
+ ok: false,
10211
+ guidance: {
10212
+ blockedGate: "queue",
10213
+ failureCode: "missing_drain_evidence",
10214
+ reason: `PRD Run ${context.prdRef} has no start receipt for drain validation.`,
10215
+ repairability: "automatic-retry-safe",
10216
+ diagnostics: [],
10217
+ offendingPaths: []
10218
+ }
10219
+ };
10220
+ }
10221
+ if (!start.queueStartedAt) {
10222
+ return {
10223
+ ok: false,
10224
+ guidance: {
10225
+ blockedGate: "queue",
10226
+ failureCode: "missing_queue_started_at",
10227
+ reason: `PRD Run ${context.prdRef} drain evidence missing queueStartedAt.`,
10228
+ repairability: "automatic-retry-safe",
10229
+ diagnostics: [],
10230
+ offendingPaths: []
10231
+ }
10232
+ };
10233
+ }
10234
+ if (start.queueCommand !== "queue-run") {
10235
+ return {
10236
+ ok: false,
10237
+ guidance: {
10238
+ blockedGate: "queue",
10239
+ failureCode: "missing_queue_command",
10240
+ reason: `PRD Run ${context.prdRef} drain evidence missing queue-run command.`,
10241
+ repairability: "automatic-retry-safe",
10242
+ diagnostics: [],
10243
+ offendingPaths: []
10244
+ }
10245
+ };
10246
+ }
10247
+ if (!start.queueDrainedAt) {
10248
+ return {
10249
+ ok: false,
10250
+ guidance: {
10251
+ blockedGate: "queue",
10252
+ failureCode: "missing_queue_drained_at",
10253
+ reason: `PRD Run ${context.prdRef} drain evidence missing queueDrainedAt.`,
10254
+ repairability: "automatic-retry-safe",
10255
+ diagnostics: [],
10256
+ offendingPaths: []
10257
+ }
10258
+ };
10259
+ }
10260
+ if (typeof start.queueProcessedCount !== "number") {
10261
+ return {
10262
+ ok: false,
10263
+ guidance: {
10264
+ blockedGate: "queue",
10265
+ failureCode: "missing_queue_processed_count",
10266
+ reason: `PRD Run ${context.prdRef} drain evidence missing queueProcessedCount.`,
10267
+ repairability: "automatic-retry-safe",
10268
+ diagnostics: [],
10269
+ offendingPaths: []
10270
+ }
10271
+ };
10272
+ }
10273
+ if (start.queueProcessedCount <= 0) {
10274
+ return {
10275
+ ok: false,
10276
+ guidance: {
10277
+ blockedGate: "queue",
10278
+ failureCode: "zero_processed",
10279
+ reason: `PRD Run ${context.prdRef} queue drain processed zero issues.`,
10280
+ repairability: "automatic-retry-safe",
10281
+ diagnostics: [`queueProcessedCount: ${start.queueProcessedCount}`],
10282
+ offendingPaths: []
10283
+ }
10284
+ };
10285
+ }
10286
+ if (start.prdBranch !== context.prdBranch) {
10287
+ return {
10288
+ ok: false,
10289
+ guidance: {
10290
+ blockedGate: "queue",
10291
+ failureCode: "prd_branch_mismatch",
10292
+ reason: `PRD Run ${context.prdRef} prdBranch "${start.prdBranch}" does not match expected "${context.prdBranch}".`,
10293
+ repairability: "operator-action",
10294
+ diagnostics: [
10295
+ `Recorded prdBranch: ${start.prdBranch}`,
10296
+ `Expected prdBranch: ${context.prdBranch}`
10297
+ ],
10298
+ offendingPaths: []
10299
+ }
10300
+ };
10301
+ }
10302
+ return { ok: true };
10303
+ }
10304
+ async function launchPrdRun(options) {
10305
+ const prdRef = normalizePrdRunRef(options.prdRef);
10306
+ const existingRecord = readPrdRun(options.repoRoot, prdRef);
10307
+ const resolvedLaunchMode = options.config ? (() => {
10308
+ try {
10309
+ const target = resolveTarget(options.config, options.targetName);
10310
+ return resolvePrdRunMode(target).mode;
10311
+ } catch {
10312
+ return void 0;
10313
+ }
10314
+ })() : void 0;
10315
+ if (resolvedLaunchMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedLaunchMode) {
10316
+ return buildLaunchBlockedOutcome(
10317
+ prdRef,
10318
+ {
10319
+ blockedGate: "branch-state",
10320
+ failureCode: "mode_mismatch",
10321
+ reason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedLaunchMode}". Resolve by using the correct target.`,
10322
+ repairability: "operator-action",
10323
+ diagnostics: [
10324
+ `Recorded mode: ${existingRecord.record.mode}`,
10325
+ `Resolved mode: ${resolvedLaunchMode}`
10326
+ ],
10327
+ offendingPaths: []
10328
+ },
10329
+ { attempted: [], skipped: ["start", "queue"], resumed: [] }
10330
+ );
10331
+ }
10332
+ const plan = planPrdRunLaunchResume(existingRecord.record);
10333
+ if (plan.blocked === "final_reviewed-incompatible") {
10334
+ return buildLaunchBlockedOutcome(
10335
+ prdRef,
10336
+ {
10337
+ blockedGate: "final-review",
10338
+ failureCode: "final_reviewed_incompatible",
10339
+ reason: `PRD Run ${prdRef} has status "final_reviewed", which is no longer a valid launch status. PRD-wide Final Review has been removed from the launch lifecycle. Update the PRD Run state to "drained" and rerun launch, or start a new PRD Run.`,
10340
+ repairability: "unsupported-state",
10341
+ diagnostics: [
10342
+ `PRD Run ${prdRef} has obsolete status "final_reviewed". PRD-wide Final Review has been removed from the launch lifecycle.`
10343
+ ],
10344
+ offendingPaths: []
10345
+ },
10346
+ plan
10347
+ );
10348
+ }
10349
+ if (existingRecord.record?.status === "completed_prd_branch" || existingRecord.record?.status === "complete" || existingRecord.record?.status === "completed_local_branch") {
10350
+ return {
10351
+ kind: "already-terminal",
10352
+ prdRef,
10353
+ status: existingRecord.record.status === "complete" ? "complete" : existingRecord.record.status,
10354
+ prdBranch: existingRecord.record.prdBranch,
10355
+ diagnostics: [
10356
+ `PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`
10357
+ ],
10358
+ resume: plan
10359
+ };
10360
+ }
10361
+ if (existingRecord.record?.status === "drained") {
10362
+ const record = existingRecord.record;
10363
+ if (!record.start) {
10364
+ return buildLaunchBlockedOutcome(
10365
+ prdRef,
10366
+ {
10367
+ blockedGate: "queue",
10368
+ failureCode: "missing_drain_evidence",
10369
+ reason: `PRD Run ${prdRef} is drained but missing start receipt.`,
10370
+ repairability: "automatic-retry-safe",
10371
+ diagnostics: [],
10372
+ offendingPaths: []
10373
+ },
10374
+ plan
10375
+ );
10376
+ }
10377
+ const drainMode = resolvedLaunchMode ?? record.mode ?? "github";
10378
+ const resolvedTarget = options.config ? resolveTarget(options.config, options.targetName) : null;
10379
+ const targetBaseBranch = resolvedTarget?.baseBranch ?? "main";
10380
+ const baseResolution = resolvePrdRunBaseBranch({
10381
+ targetBaseBranch,
10382
+ baseBranchOverride: options.baseBranchOverride
10383
+ });
10384
+ if (!baseResolution.ok) {
10385
+ return buildLaunchBlockedOutcome(
10386
+ prdRef,
10387
+ {
10388
+ blockedGate: baseResolution.gate,
10389
+ failureCode: "base_resolution_failed",
10390
+ reason: baseResolution.reason,
10391
+ repairability: "operator-action",
10392
+ diagnostics: [...baseResolution.diagnostics],
10393
+ offendingPaths: [...baseResolution.offendingPaths]
10394
+ },
10395
+ plan
10396
+ );
10397
+ }
10398
+ const drainContext = {
10399
+ prdRef,
10400
+ mode: drainMode,
10401
+ baseBranch: baseResolution.baseBranch,
10402
+ prdBranch: record.start.prdBranch
10403
+ };
10404
+ const startEvidence = validatePrdRunStartEvidence(record, drainContext);
10405
+ if (!startEvidence.ok) {
10406
+ return buildLaunchBlockedOutcome(prdRef, startEvidence.guidance, plan);
10407
+ }
10408
+ const drainEvidence = validatePrdRunDrainEvidence(record, drainContext);
10409
+ if (!drainEvidence.ok) {
10410
+ return buildLaunchBlockedOutcome(prdRef, drainEvidence.guidance, plan);
10411
+ }
10412
+ const terminalStatus = drainMode === "local" ? "completed_local_branch" : "completed_prd_branch";
10413
+ writeTerminalRecord(
10414
+ options.repoRoot,
10415
+ prdRef,
10416
+ terminalStatus,
10417
+ record.targetName ?? options.targetName,
10418
+ record.start,
10419
+ record.start.prdBranch,
10420
+ drainMode
10421
+ );
10422
+ return {
10423
+ kind: "completed",
10424
+ prdRef,
10425
+ status: terminalStatus,
10426
+ prdBranch: record.start.prdBranch,
10427
+ diagnostics: [
10428
+ `PRD Run ${prdRef} completed from drained state. Branch: ${record.start.prdBranch}.`
10429
+ ],
10430
+ resume: plan
10431
+ };
10432
+ }
10433
+ if (plan.blocked === "missing-start-receipt") {
10434
+ return buildLaunchBlockedOutcome(
10435
+ prdRef,
10436
+ {
10437
+ blockedGate: "branch-state",
10438
+ failureCode: "missing_start_receipt",
10439
+ reason: `PRD Run ${prdRef} is missing branch/start state. Run prd-run start or rerun prd-run launch.`,
10440
+ repairability: "automatic-retry-safe",
10441
+ diagnostics: [
10442
+ `PRD Run ${prdRef} is in status "${existingRecord.record?.status}" but has no start receipt.`
9768
10443
  ],
9769
10444
  offendingPaths: []
10445
+ },
10446
+ plan
10447
+ );
10448
+ }
10449
+ let startOutcome;
10450
+ if (!plan.skipped.includes("start")) {
10451
+ startOutcome = await startPrdRun({
10452
+ repoRoot: options.repoRoot,
10453
+ prdRef,
10454
+ targetName: options.targetName,
10455
+ config: options.config,
10456
+ issueProvider: options.issueProvider,
10457
+ prProvider: options.prProvider,
10458
+ executionProvider: options.executionProvider,
10459
+ logger: options.logger,
10460
+ adoptExistingBranch: options.adoptExistingBranch,
10461
+ baseBranchOverride: options.baseBranchOverride
10462
+ });
10463
+ if (startOutcome.kind === "blocked") {
10464
+ return {
10465
+ kind: "blocked",
10466
+ prdRef,
10467
+ guidance: startOutcome.guidance,
10468
+ start: startOutcome,
10469
+ resume: plan
9770
10470
  };
9771
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
9772
- targetName
9773
- });
9774
- return buildBlockedStartResult(prdRef, failure);
9775
10471
  }
9776
- const adoptedStart = buildStartReceipt({
9777
- status: "adopted",
10472
+ }
10473
+ const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
10474
+ const startReceipt = currentRecord?.start;
10475
+ const targetName = currentRecord?.targetName ?? options.targetName;
10476
+ const explicitMode = resolvedLaunchMode ?? currentRecord?.mode;
10477
+ if (startReceipt) {
10478
+ startReceipt.queueStartedAt = (/* @__PURE__ */ new Date()).toISOString();
10479
+ startReceipt.queueCommand = "queue-run";
10480
+ }
10481
+ writePrdRunRecord(options.repoRoot, {
10482
+ prdRef,
10483
+ status: "running",
10484
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10485
+ targetName,
10486
+ start: startReceipt,
10487
+ mode: explicitMode
10488
+ });
10489
+ if (explicitMode === "local") {
10490
+ return await launchLocalQueueDrain(
10491
+ options,
10492
+ prdRef,
10493
+ startReceipt,
10494
+ targetName,
10495
+ explicitMode,
10496
+ plan,
10497
+ startOutcome
10498
+ );
10499
+ }
10500
+ return await launchGithubQueueDrain(
10501
+ options,
10502
+ prdRef,
10503
+ startReceipt,
10504
+ targetName,
10505
+ explicitMode,
10506
+ plan,
10507
+ startOutcome
10508
+ );
10509
+ }
10510
+ async function launchLocalQueueDrain(options, prdRef, startReceipt, targetName, mode, plan, startOutcome) {
10511
+ if (!startReceipt) {
10512
+ writePrdRunRecord(options.repoRoot, {
10513
+ prdRef,
10514
+ status: "blocked",
10515
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10516
+ targetName,
10517
+ blockedGate: "branch-state",
10518
+ blockedReason: `PRD Run ${prdRef} is missing start receipt before local queue drain.`,
10519
+ diagnostics: [],
10520
+ offendingPaths: []
10521
+ });
10522
+ return buildLaunchBlockedOutcome(
10523
+ prdRef,
10524
+ {
10525
+ blockedGate: "branch-state",
10526
+ failureCode: "missing_start_receipt",
10527
+ reason: `PRD Run ${prdRef} is missing start receipt before local queue drain.`,
10528
+ repairability: "automatic-retry-safe",
10529
+ diagnostics: [],
10530
+ offendingPaths: []
10531
+ },
10532
+ plan,
10533
+ startOutcome
10534
+ );
10535
+ }
10536
+ const localStoreDir = join19(
10537
+ options.repoRoot,
10538
+ ".pourkit",
10539
+ "local-prd-runs",
10540
+ prdRef
10541
+ );
10542
+ let localStoreReady = false;
10543
+ if (existsSync15(localStoreDir)) {
10544
+ const localStorePath = join19(localStoreDir, "prd.json");
10545
+ try {
10546
+ const content = JSON.parse(readFileSync17(localStorePath, "utf8"));
10547
+ localStoreReady = content?.id === prdRef && content?.kind === "prd";
10548
+ } catch {
10549
+ localStoreReady = false;
10550
+ }
10551
+ }
10552
+ if (!localStoreReady) {
10553
+ const reason = `Local PRD Run Store not ready for ${prdRef}. Expected valid prd.json at .pourkit/local-prd-runs/${prdRef}/prd.json with matching PRD ID.`;
10554
+ writePrdRunRecord(options.repoRoot, {
10555
+ prdRef,
10556
+ status: "blocked",
10557
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10558
+ targetName,
10559
+ start: startReceipt,
10560
+ mode,
10561
+ blockedGate: "branch-state",
10562
+ blockedReason: reason,
10563
+ diagnostics: [
10564
+ `Expected store path: .pourkit/local-prd-runs/${prdRef}/prd.json`,
10565
+ `Expected PRD ID: ${prdRef}`
10566
+ ],
10567
+ offendingPaths: []
10568
+ });
10569
+ return buildLaunchBlockedOutcome(
10570
+ prdRef,
10571
+ {
10572
+ blockedGate: "branch-state",
10573
+ failureCode: "local_store_invalid",
10574
+ reason,
10575
+ repairability: "operator-action",
10576
+ diagnostics: [
10577
+ `Expected store path: .pourkit/local-prd-runs/${prdRef}/prd.json`,
10578
+ `Expected PRD ID: ${prdRef}`
10579
+ ],
10580
+ offendingPaths: []
10581
+ },
10582
+ plan,
10583
+ startOutcome
10584
+ );
10585
+ }
10586
+ const queueResult = await runLocalQueueLoop(
10587
+ prdRef,
10588
+ options.repoRoot,
10589
+ options.issueProvider
10590
+ );
10591
+ if (!queueResult.ok) {
10592
+ const failureCode = queueResult.failureCode ?? "queue_error";
10593
+ const reason = queueResult.repairGuidance ?? `Local queue loop blocked: ${failureCode}`;
10594
+ const diagnostics = [
10595
+ `Local queue loop failed with failureCode "${failureCode}".`,
10596
+ ...queueResult.repairGuidance ? [`Repair guidance: ${queueResult.repairGuidance}`] : []
10597
+ ];
10598
+ writePrdRunRecord(options.repoRoot, {
10599
+ prdRef,
10600
+ status: "blocked",
10601
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9778
10602
  targetName,
9779
- prdBranch: prdRef,
9780
- startBaseBranch: baseResolution.baseBranch,
9781
- startBaseCommit: fetchResult.startBaseCommit,
9782
- branchAction: "adopted",
9783
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
10603
+ start: startReceipt,
10604
+ mode,
10605
+ blockedGate: "queue",
10606
+ blockedReason: reason,
10607
+ diagnostics,
10608
+ offendingPaths: []
9784
10609
  });
9785
- persistStartingPrdRunRecord(
9786
- options.repoRoot,
10610
+ return buildLaunchBlockedOutcome(
9787
10611
  prdRef,
9788
- existingRecord,
9789
10612
  {
9790
- ...adoptedStart
10613
+ blockedGate: "queue",
10614
+ failureCode,
10615
+ reason,
10616
+ repairability: "automatic-retry-safe",
10617
+ diagnostics,
10618
+ offendingPaths: []
9791
10619
  },
9792
- {
9793
- targetName,
9794
- mode: resolvedMode
9795
- }
10620
+ plan,
10621
+ startOutcome
9796
10622
  );
9797
- return await processStartResult(
10623
+ }
10624
+ if (queueResult.blockedIssues.length > 0) {
10625
+ const reason = `Queue processed complete but ${queueResult.blockedIssues.length} blocked child issue(s) remain.`;
10626
+ const diagnostics = [
10627
+ reason,
10628
+ `Blocked issues: ${queueResult.blockedIssues.join(", ")}`,
10629
+ "Resolve blocked children before parent close."
10630
+ ];
10631
+ writePrdRunRecord(options.repoRoot, {
10632
+ prdRef,
10633
+ status: "blocked",
10634
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10635
+ targetName,
10636
+ start: startReceipt,
10637
+ mode,
10638
+ blockedGate: "queue",
10639
+ blockedReason: reason,
10640
+ diagnostics,
10641
+ offendingPaths: []
10642
+ });
10643
+ return buildLaunchBlockedOutcome(
10644
+ prdRef,
9798
10645
  {
9799
- prdRef,
9800
- status: "starting",
9801
- start: adoptedStart,
9802
- diagnostics: []
10646
+ blockedGate: "queue",
10647
+ failureCode: "blocked_child_issues",
10648
+ reason,
10649
+ repairability: "operator-action",
10650
+ diagnostics,
10651
+ offendingPaths: []
9803
10652
  },
9804
- options
10653
+ plan,
10654
+ startOutcome
9805
10655
  );
9806
10656
  }
9807
- try {
9808
- await ensurePrdBranchPublished(
9809
- options.repoRoot,
10657
+ startReceipt.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
10658
+ startReceipt.queueProcessedCount = queueResult.completedIssues.length + queueResult.blockedIssues.length;
10659
+ const prdBranch = startReceipt.prdBranch ?? `local/${prdRef}`;
10660
+ writeDrainedRecord(options.repoRoot, prdRef, startReceipt, targetName, mode);
10661
+ const drainRecord = readPrdRun(options.repoRoot, prdRef).record;
10662
+ if (drainRecord) {
10663
+ const drainContext = {
9810
10664
  prdRef,
9811
- fetchResult.startBaseCommit
9812
- );
9813
- } catch (error) {
9814
- const failure = {
9815
- ok: false,
9816
- gate: "git",
9817
- reason: `Failed to create PRD Branch ${prdRef} from ${fetchResult.startBaseCommit}.`,
9818
- diagnostics: [
9819
- error instanceof Error ? error.message : String(error)
9820
- ].filter((value) => Boolean(value && value.trim())),
9821
- offendingPaths: []
10665
+ mode,
10666
+ baseBranch: startReceipt.startBaseBranch,
10667
+ prdBranch
9822
10668
  };
9823
- persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
9824
- targetName
9825
- });
9826
- return buildBlockedStartResult(prdRef, failure);
10669
+ const startEvidence = validatePrdRunStartEvidence(
10670
+ drainRecord,
10671
+ drainContext
10672
+ );
10673
+ if (!startEvidence.ok) {
10674
+ return buildLaunchBlockedOutcome(
10675
+ prdRef,
10676
+ startEvidence.guidance,
10677
+ plan,
10678
+ startOutcome
10679
+ );
10680
+ }
10681
+ const drainEvidence = validatePrdRunDrainEvidence(
10682
+ drainRecord,
10683
+ drainContext
10684
+ );
10685
+ if (!drainEvidence.ok) {
10686
+ return buildLaunchBlockedOutcome(
10687
+ prdRef,
10688
+ drainEvidence.guidance,
10689
+ plan,
10690
+ startOutcome
10691
+ );
10692
+ }
9827
10693
  }
9828
- const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9829
- const start = buildStartReceipt({
9830
- status: "started",
9831
- targetName,
9832
- prdBranch: prdRef,
9833
- startBaseBranch: baseResolution.baseBranch,
9834
- startBaseCommit: fetchResult.startBaseCommit,
9835
- branchAction: "created",
9836
- startedAt: updatedAt
9837
- });
9838
- const {
9839
- blockedGate: _blockedGate,
9840
- blockedReason: _blockedReason,
9841
- diagnostics: _diagnostics,
9842
- offendingPaths: _offendingPaths,
9843
- prdBranch: _prdBranch,
9844
- start: _start,
9845
- ...cleanRecord
9846
- } = existingRecord.record ?? { prdRef };
9847
- writePrdRunRecord(options.repoRoot, {
9848
- ...cleanRecord,
10694
+ writeTerminalRecord(
10695
+ options.repoRoot,
9849
10696
  prdRef,
9850
- status: "starting",
9851
- updatedAt,
10697
+ "completed_local_branch",
9852
10698
  targetName,
9853
- start,
9854
- mode: resolvedMode
9855
- });
9856
- return await processStartResult(
9857
- {
9858
- prdRef,
9859
- status: "starting",
9860
- start,
9861
- diagnostics: []
9862
- },
9863
- options
10699
+ startReceipt,
10700
+ prdBranch,
10701
+ mode
9864
10702
  );
9865
- }
9866
- async function processStartResult(startResult, options) {
9867
- if (!options.config) {
9868
- return startResult;
9869
- }
9870
- const prdRef = startResult.prdRef;
9871
- const start = startResult.start;
9872
- const repoRoot2 = options.repoRoot;
9873
- start.queueStartedAt = (/* @__PURE__ */ new Date()).toISOString();
9874
- start.queueCommand = "queue-run";
9875
- const existingRecord = readPrdRun(repoRoot2, prdRef);
9876
- let resolvedMode;
9877
- try {
9878
- const target = options.config ? resolveTarget(options.config, start.targetName) : null;
9879
- resolvedMode = target?.strategy ? resolvePrdRunMode(target) : void 0;
9880
- } catch {
9881
- resolvedMode = void 0;
9882
- }
9883
- const modeForRecord = resolvedMode?.mode;
9884
- if (modeForRecord !== "local" && (!options.issueProvider || !options.prProvider || !options.executionProvider || !options.logger)) {
9885
- return startResult;
9886
- }
9887
- writePrdRunRecord(repoRoot2, {
10703
+ return {
10704
+ kind: "completed",
9888
10705
  prdRef,
9889
- status: "running",
9890
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9891
- targetName: start.targetName,
9892
- start,
9893
- mode: modeForRecord
9894
- });
9895
- const localStorePath = join19(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
9896
- if (existsSync16(localStorePath)) {
9897
- const queueResult = await runLocalQueueLoop(
10706
+ status: "completed_local_branch",
10707
+ prdBranch,
10708
+ diagnostics: [`Local PRD Run ${prdRef} completed. Branch: ${prdBranch}.`],
10709
+ start: startOutcome,
10710
+ resume: plan
10711
+ };
10712
+ }
10713
+ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName, mode, plan, startOutcome) {
10714
+ if (!options.issueProvider || !options.prProvider || !options.executionProvider || !options.logger) {
10715
+ const reason = `PRD Run ${prdRef} requires issueProvider, prProvider, executionProvider, and logger for GitHub-backed queue drain.`;
10716
+ writePrdRunRecord(options.repoRoot, {
9898
10717
  prdRef,
9899
- repoRoot2,
9900
- options.issueProvider
9901
- );
9902
- if (!queueResult.ok) {
9903
- const failureCode = queueResult.failureCode ?? "queue_error";
9904
- const reason = queueResult.repairGuidance ?? `Local queue loop blocked: ${failureCode}`;
9905
- const diagnostics = [
9906
- `Local queue loop failed with failureCode "${failureCode}".`,
9907
- ...queueResult.repairGuidance ? [`Repair guidance: ${queueResult.repairGuidance}`] : []
9908
- ];
9909
- writePrdRunRecord(repoRoot2, {
9910
- prdRef,
9911
- status: "blocked",
9912
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9913
- targetName: start.targetName,
9914
- start,
9915
- mode: modeForRecord,
9916
- blockedGate: "queue",
9917
- blockedReason: reason,
9918
- diagnostics,
9919
- offendingPaths: []
9920
- });
9921
- return {
9922
- prdRef,
9923
- status: "blocked",
10718
+ status: "blocked",
10719
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10720
+ targetName,
10721
+ start: startReceipt,
10722
+ mode,
10723
+ blockedGate: "queue",
10724
+ blockedReason: reason,
10725
+ diagnostics: [
10726
+ "Missing one or more required providers for GitHub-backed mode.",
10727
+ "Provide --issue-provider, --pr-provider, --execution-provider, and --logger options."
10728
+ ],
10729
+ offendingPaths: []
10730
+ });
10731
+ return buildLaunchBlockedOutcome(
10732
+ prdRef,
10733
+ {
9924
10734
  blockedGate: "queue",
9925
- blockedReason: reason,
9926
- diagnostics,
9927
- offendingPaths: []
9928
- };
9929
- }
9930
- if (queueResult.blockedIssues.length > 0) {
9931
- const reason = `Queue processed complete but ${queueResult.blockedIssues.length} blocked child issue(s) remain.`;
9932
- const diagnostics = [
10735
+ failureCode: "missing_github_providers",
9933
10736
  reason,
9934
- `Blocked issues: ${queueResult.blockedIssues.join(", ")}`,
9935
- "Resolve blocked children before parent close."
9936
- ];
9937
- writePrdRunRecord(repoRoot2, {
9938
- prdRef,
9939
- status: "blocked",
9940
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9941
- targetName: start.targetName,
9942
- start,
9943
- mode: modeForRecord,
9944
- blockedGate: "queue",
9945
- blockedReason: reason,
9946
- diagnostics,
10737
+ repairability: "operator-action",
10738
+ diagnostics: [
10739
+ "Missing one or more required providers for GitHub-backed mode."
10740
+ ],
9947
10741
  offendingPaths: []
9948
- });
9949
- return {
9950
- prdRef,
9951
- status: "blocked",
9952
- blockedGate: "queue",
9953
- blockedReason: reason,
9954
- diagnostics,
10742
+ },
10743
+ plan,
10744
+ startOutcome
10745
+ );
10746
+ }
10747
+ if (!startReceipt) {
10748
+ writePrdRunRecord(options.repoRoot, {
10749
+ prdRef,
10750
+ status: "blocked",
10751
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10752
+ targetName,
10753
+ mode,
10754
+ blockedGate: "branch-state",
10755
+ blockedReason: `PRD Run ${prdRef} is missing start receipt before GitHub queue drain.`,
10756
+ diagnostics: [],
10757
+ offendingPaths: []
10758
+ });
10759
+ return buildLaunchBlockedOutcome(
10760
+ prdRef,
10761
+ {
10762
+ blockedGate: "branch-state",
10763
+ failureCode: "missing_start_receipt",
10764
+ reason: `PRD Run ${prdRef} is missing start receipt before GitHub queue drain.`,
10765
+ repairability: "automatic-retry-safe",
10766
+ diagnostics: [],
9955
10767
  offendingPaths: []
9956
- };
9957
- }
9958
- start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
9959
- start.queueProcessedCount = queueResult.completedIssues.length + queueResult.blockedIssues.length;
9960
- writePrdRunRecord(repoRoot2, {
10768
+ },
10769
+ plan,
10770
+ startOutcome
10771
+ );
10772
+ }
10773
+ if (!options.config) {
10774
+ const reason = `PRD Run ${prdRef} requires config for GitHub-backed queue drain.`;
10775
+ writePrdRunRecord(options.repoRoot, {
9961
10776
  prdRef,
9962
- status: "drained",
10777
+ status: "blocked",
9963
10778
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
9964
- targetName: start.targetName,
9965
- start,
9966
- mode: modeForRecord
10779
+ targetName,
10780
+ start: startReceipt,
10781
+ mode,
10782
+ blockedGate: "queue",
10783
+ blockedReason: reason,
10784
+ diagnostics: ["Missing config for GitHub-backed mode."],
10785
+ offendingPaths: []
9967
10786
  });
9968
- return {
10787
+ return buildLaunchBlockedOutcome(
9969
10788
  prdRef,
9970
- status: "drained",
9971
- start,
9972
- diagnostics: []
9973
- };
10789
+ {
10790
+ blockedGate: "queue",
10791
+ failureCode: "missing_config",
10792
+ reason,
10793
+ repairability: "operator-action",
10794
+ diagnostics: ["Missing config for GitHub-backed mode."],
10795
+ offendingPaths: []
10796
+ },
10797
+ plan,
10798
+ startOutcome
10799
+ );
9974
10800
  }
9975
10801
  const issueProvider = options.issueProvider;
9976
10802
  const prProvider = options.prProvider;
9977
10803
  const executionProvider = options.executionProvider;
9978
10804
  const logger = options.logger;
10805
+ let resolvedModeForQueue;
10806
+ try {
10807
+ const target = resolveTarget(options.config, options.targetName);
10808
+ resolvedModeForQueue = resolvePrdRunMode(target);
10809
+ } catch {
10810
+ resolvedModeForQueue = void 0;
10811
+ }
9979
10812
  try {
9980
10813
  const outcome = await runQueueCommand({
9981
- targetName: start.targetName,
10814
+ targetName,
9982
10815
  config: options.config,
9983
10816
  issueProvider,
9984
10817
  prProvider,
@@ -9986,12 +10819,12 @@ async function processStartResult(startResult, options) {
9986
10819
  force: false,
9987
10820
  loop: true,
9988
10821
  logger,
9989
- repoRoot: repoRoot2,
10822
+ repoRoot: options.repoRoot,
9990
10823
  prdRef,
9991
10824
  queueRunContext: {
9992
10825
  prdRef,
9993
- prdBranch: start.prdBranch,
9994
- prdRunMode: resolvedMode
10826
+ prdBranch: startReceipt.prdBranch,
10827
+ prdRunMode: resolvedModeForQueue
9995
10828
  }
9996
10829
  });
9997
10830
  if (outcome.selected === null && outcome.code === "drained") {
@@ -9999,327 +10832,259 @@ async function processStartResult(startResult, options) {
9999
10832
  const diagnostics = [
10000
10833
  "Queue drained without processing issues. No PRD-scoped candidates or runnable Issues."
10001
10834
  ];
10002
- writePrdRunRecord(repoRoot2, {
10835
+ writePrdRunRecord(options.repoRoot, {
10003
10836
  prdRef,
10004
10837
  status: "blocked",
10005
10838
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10839
+ targetName,
10840
+ start: startReceipt,
10841
+ mode,
10006
10842
  blockedGate: "queue",
10007
10843
  blockedReason: diagnostics[0],
10008
10844
  diagnostics,
10009
- targetName: start.targetName,
10010
- start,
10011
- mode: existingRecord.record?.mode,
10012
10845
  offendingPaths: []
10013
10846
  });
10014
- return {
10847
+ return buildLaunchBlockedOutcome(
10015
10848
  prdRef,
10016
- status: "blocked",
10017
- blockedGate: "queue",
10018
- blockedReason: diagnostics[0],
10019
- diagnostics,
10020
- offendingPaths: []
10849
+ {
10850
+ blockedGate: "queue",
10851
+ failureCode: "zero_processed",
10852
+ reason: diagnostics[0],
10853
+ repairability: "automatic-retry-safe",
10854
+ diagnostics,
10855
+ offendingPaths: []
10856
+ },
10857
+ plan,
10858
+ startOutcome
10859
+ );
10860
+ }
10861
+ startReceipt.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
10862
+ startReceipt.queueProcessedCount = outcome.processedCount;
10863
+ const prdBranch = startReceipt.prdBranch ?? prdRef;
10864
+ writeDrainedRecord(
10865
+ options.repoRoot,
10866
+ prdRef,
10867
+ startReceipt,
10868
+ targetName,
10869
+ mode
10870
+ );
10871
+ const drainRecord = readPrdRun(options.repoRoot, prdRef).record;
10872
+ if (drainRecord) {
10873
+ const drainMode = mode ?? "github";
10874
+ const drainContext = {
10875
+ prdRef,
10876
+ mode: drainMode,
10877
+ baseBranch: startReceipt.startBaseBranch,
10878
+ prdBranch
10021
10879
  };
10880
+ const startEvidence = validatePrdRunStartEvidence(
10881
+ drainRecord,
10882
+ drainContext
10883
+ );
10884
+ if (!startEvidence.ok) {
10885
+ return buildLaunchBlockedOutcome(
10886
+ prdRef,
10887
+ startEvidence.guidance,
10888
+ plan,
10889
+ startOutcome
10890
+ );
10891
+ }
10892
+ const drainEvidence = validatePrdRunDrainEvidence(
10893
+ drainRecord,
10894
+ drainContext
10895
+ );
10896
+ if (!drainEvidence.ok) {
10897
+ return buildLaunchBlockedOutcome(
10898
+ prdRef,
10899
+ drainEvidence.guidance,
10900
+ plan,
10901
+ startOutcome
10902
+ );
10903
+ }
10022
10904
  }
10023
- start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
10024
- start.queueProcessedCount = outcome.processedCount;
10025
- writePrdRunRecord(repoRoot2, {
10905
+ writeTerminalRecord(
10906
+ options.repoRoot,
10026
10907
  prdRef,
10027
- status: "drained",
10028
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10029
- targetName: start.targetName,
10030
- start,
10031
- mode: existingRecord.record?.mode
10032
- });
10908
+ "completed_prd_branch",
10909
+ targetName,
10910
+ startReceipt,
10911
+ prdBranch,
10912
+ mode
10913
+ );
10033
10914
  return {
10915
+ kind: "completed",
10034
10916
  prdRef,
10035
- status: "drained",
10036
- start,
10037
- diagnostics: []
10917
+ status: "completed_prd_branch",
10918
+ prdBranch,
10919
+ diagnostics: [
10920
+ `PRD Run completed on branch ${prdBranch}. Next: checkout ${prdBranch} for human review, or use release promotion workflow when ready.`
10921
+ ],
10922
+ start: startOutcome,
10923
+ resume: plan
10038
10924
  };
10039
10925
  }
10040
10926
  if (outcome.selected === null) {
10041
10927
  const diagnostics = [outcome.reason];
10042
- writePrdRunRecord(repoRoot2, {
10928
+ writePrdRunRecord(options.repoRoot, {
10043
10929
  prdRef,
10044
10930
  status: "blocked",
10045
10931
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10932
+ targetName,
10933
+ start: startReceipt,
10934
+ mode,
10046
10935
  blockedGate: "queue",
10047
10936
  blockedReason: outcome.reason,
10048
10937
  diagnostics,
10049
- targetName: start.targetName,
10050
- start,
10051
- mode: existingRecord.record?.mode,
10052
10938
  offendingPaths: []
10053
10939
  });
10054
- return {
10940
+ return buildLaunchBlockedOutcome(
10055
10941
  prdRef,
10056
- status: "blocked",
10057
- blockedGate: "queue",
10058
- blockedReason: outcome.reason,
10059
- diagnostics,
10060
- offendingPaths: []
10061
- };
10942
+ {
10943
+ blockedGate: "queue",
10944
+ failureCode: "queue_blocked",
10945
+ reason: outcome.reason,
10946
+ repairability: "automatic-retry-safe",
10947
+ diagnostics,
10948
+ offendingPaths: []
10949
+ },
10950
+ plan,
10951
+ startOutcome
10952
+ );
10062
10953
  }
10063
10954
  const outcomeReason = `Unexpected queue outcome: selected issue ${outcome.selected.number}`;
10064
10955
  const outcomeDiagnostics = [outcomeReason];
10065
- writePrdRunRecord(repoRoot2, {
10956
+ writePrdRunRecord(options.repoRoot, {
10066
10957
  prdRef,
10067
10958
  status: "blocked",
10068
10959
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10960
+ targetName,
10961
+ start: startReceipt,
10962
+ mode,
10069
10963
  blockedGate: "queue",
10070
10964
  blockedReason: outcomeReason,
10071
10965
  diagnostics: outcomeDiagnostics,
10072
- targetName: start.targetName,
10073
- start,
10074
- mode: existingRecord.record?.mode,
10075
10966
  offendingPaths: []
10076
10967
  });
10077
- return {
10968
+ return buildLaunchBlockedOutcome(
10078
10969
  prdRef,
10079
- status: "blocked",
10080
- blockedGate: "queue",
10081
- blockedReason: outcomeReason,
10082
- diagnostics: outcomeDiagnostics,
10083
- offendingPaths: []
10084
- };
10970
+ {
10971
+ blockedGate: "queue",
10972
+ failureCode: "unexpected_queue_outcome",
10973
+ reason: outcomeReason,
10974
+ repairability: "operator-action",
10975
+ diagnostics: outcomeDiagnostics,
10976
+ offendingPaths: []
10977
+ },
10978
+ plan,
10979
+ startOutcome
10980
+ );
10085
10981
  } catch (error) {
10086
10982
  const msg = error instanceof Error ? error.message : String(error);
10087
10983
  const diagnostics = [msg];
10088
- writePrdRunRecord(repoRoot2, {
10089
- prdRef,
10090
- status: "blocked",
10091
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10092
- blockedGate: "queue",
10093
- blockedReason: msg,
10094
- diagnostics,
10095
- targetName: start.targetName,
10096
- start,
10097
- mode: existingRecord.record?.mode,
10098
- offendingPaths: []
10099
- });
10100
- return {
10984
+ writePrdRunRecord(options.repoRoot, {
10101
10985
  prdRef,
10102
10986
  status: "blocked",
10987
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10988
+ targetName,
10989
+ start: startReceipt,
10990
+ mode,
10103
10991
  blockedGate: "queue",
10104
10992
  blockedReason: msg,
10105
10993
  diagnostics,
10106
10994
  offendingPaths: []
10107
- };
10995
+ });
10996
+ return buildLaunchBlockedOutcome(
10997
+ prdRef,
10998
+ {
10999
+ blockedGate: "queue",
11000
+ failureCode: "queue_exception",
11001
+ reason: msg,
11002
+ repairability: "automatic-retry-safe",
11003
+ diagnostics,
11004
+ offendingPaths: []
11005
+ },
11006
+ plan,
11007
+ startOutcome
11008
+ );
10108
11009
  }
10109
11010
  }
10110
- function persistStartingPrdRunRecord(repoRoot2, prdRef, existingRecord, start, context) {
10111
- const {
10112
- blockedGate: _blockedGate,
10113
- blockedReason: _blockedReason,
10114
- diagnostics: _diagnostics,
10115
- offendingPaths: _offendingPaths,
10116
- prdBranch: _prdBranch,
10117
- start: _start,
10118
- ...cleanRecord
10119
- } = existingRecord.record ?? { prdRef };
10120
- writePrdRunRecord(repoRoot2, {
10121
- ...cleanRecord,
10122
- prdRef,
10123
- status: "starting",
10124
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10125
- targetName: context.targetName,
10126
- start,
10127
- mode: context.mode
10128
- });
10129
- }
10130
- function inspectRemotePrdBranch(repoRoot2, prdRef) {
10131
- const result = spawnSync3("git", ["ls-remote", "--heads", "origin", prdRef], {
10132
- cwd: repoRoot2,
10133
- encoding: "utf8"
10134
- });
10135
- if (result.status !== 0) {
10136
- return {
10137
- ok: false,
10138
- gate: "git",
10139
- reason: `PRD Run start failed while inspecting remote branch ${prdRef}.`,
10140
- diagnostics: [
10141
- result.error instanceof Error ? result.error.message : void 0,
10142
- result.stderr?.toString?.() ?? String(result.stderr ?? ""),
10143
- result.stdout?.toString?.() ?? String(result.stdout ?? "")
10144
- ].filter((value) => Boolean(value && value.trim())),
10145
- offendingPaths: []
10146
- };
10147
- }
10148
- const headSha = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0]?.split(/\s+/)[0];
10149
- if (!headSha) {
10150
- return { ok: true, exists: false };
10151
- }
10152
- return { ok: true, exists: true, headSha };
11011
+
11012
+ // prd-run/final-review-validation.ts
11013
+ import { existsSync as existsSync16, readFileSync as readFileSync18 } from "fs";
11014
+
11015
+ // commands/prd-run.ts
11016
+ async function runPrdRunLaunchCommand(options) {
11017
+ const outcome = await launchPrdRun(options);
11018
+ return mapLaunchPrdRunOutcomeToCommandResult(outcome, options);
10153
11019
  }
10154
- function resolvePrdRunBaseBranch(options) {
10155
- const raw = options.baseBranchOverride ?? options.targetBaseBranch;
10156
- const trimmed = raw.trim();
10157
- if (!trimmed) {
10158
- return {
10159
- ok: false,
10160
- gate: "branch-state",
10161
- reason: `Invalid PRD Run base branch: value is empty after trimming.`,
10162
- diagnostics: [`Provided branch value: "${raw}"`],
10163
- offendingPaths: []
10164
- };
10165
- }
10166
- if (trimmed.startsWith("origin/")) {
10167
- return {
10168
- ok: false,
10169
- gate: "branch-state",
10170
- reason: `Invalid PRD Run base branch: "${trimmed}" must not include "origin/" prefix. Use just the branch lane name (e.g. "dev" instead of "origin/dev").`,
10171
- diagnostics: [`Provided branch value: "${trimmed}"`],
10172
- offendingPaths: []
10173
- };
10174
- }
10175
- if (/^(refs\/|heads\/|remotes\/|tags\/)/.test(trimmed)) {
10176
- return {
10177
- ok: false,
10178
- gate: "branch-state",
10179
- reason: `Invalid PRD Run base branch: "${trimmed}" must be a simple branch lane name, not a ref specification.`,
10180
- diagnostics: [`Provided branch value: "${trimmed}"`],
10181
- offendingPaths: []
10182
- };
10183
- }
10184
- if (/^[0-9a-f]{7,40}$/i.test(trimmed)) {
10185
- return {
10186
- ok: false,
10187
- gate: "branch-state",
10188
- reason: `Invalid PRD Run base branch: value looks like a commit SHA rather than a branch lane name.`,
10189
- diagnostics: [`Provided branch value: "${trimmed}"`],
10190
- offendingPaths: []
10191
- };
10192
- }
10193
- const segments = trimmed.split("/");
10194
- for (const segment of segments) {
10195
- if (segment === "." || segment === ".." || segment === "") {
10196
- return {
10197
- ok: false,
10198
- gate: "branch-state",
10199
- reason: `Invalid PRD Run base branch: "${trimmed}" must not contain ".", "..", or empty segments.`,
10200
- diagnostics: [`Provided branch value: "${trimmed}"`],
10201
- offendingPaths: []
10202
- };
10203
- }
10204
- }
10205
- if (/[~^:?*\[\\]/.test(trimmed)) {
10206
- return {
10207
- ok: false,
10208
- gate: "branch-state",
10209
- reason: `Invalid PRD Run base branch: "${trimmed}" contains invalid characters (~ ^ : ? * [ \\).`,
10210
- diagnostics: [`Provided branch value: "${trimmed}"`],
10211
- offendingPaths: []
10212
- };
10213
- }
10214
- if (/[\x00-\x1f\x7f]/.test(trimmed)) {
10215
- return {
10216
- ok: false,
10217
- gate: "branch-state",
10218
- reason: `Invalid PRD Run base branch: value contains control characters.`,
10219
- diagnostics: [`Provided branch value: "${trimmed}"`],
10220
- offendingPaths: []
10221
- };
10222
- }
10223
- if (trimmed.endsWith("/")) {
10224
- return {
10225
- ok: false,
10226
- gate: "branch-state",
10227
- reason: `Invalid PRD Run base branch: "${trimmed}" must not end with a trailing slash.`,
10228
- diagnostics: [`Provided branch value: "${trimmed}"`],
10229
- offendingPaths: []
10230
- };
10231
- }
10232
- if (trimmed.endsWith(".lock")) {
10233
- return {
10234
- ok: false,
10235
- gate: "branch-state",
10236
- reason: `Invalid PRD Run base branch: "${trimmed}" must not end with ".lock" suffix.`,
10237
- diagnostics: [`Provided branch value: "${trimmed}"`],
10238
- offendingPaths: []
10239
- };
10240
- }
10241
- const source = options.baseBranchOverride !== void 0 ? "override" : "target";
10242
- return { ok: true, baseBranch: trimmed, source };
11020
+ async function runPrdRunStartCommand(options) {
11021
+ const outcome = await startPrdRun(options);
11022
+ return mapStartPrdRunOutcomeToCommandResult(outcome);
10243
11023
  }
10244
- function fetchOriginBranch(repoRoot2, baseBranch) {
10245
- const fetchResult = spawnSync3("git", ["fetch", "origin", baseBranch], {
10246
- cwd: repoRoot2,
10247
- encoding: "utf8"
10248
- });
10249
- if (fetchResult.status !== 0) {
11024
+ function mapLaunchPrdRunOutcomeToCommandResult(outcome, options) {
11025
+ const prdRef = normalizePrdRunRef(options.prdRef);
11026
+ if (outcome.kind === "already-terminal") {
11027
+ const diagnostics = [
11028
+ `PRD Run ${prdRef} is already in status "${outcome.status}".`,
11029
+ ...outcome.status === "completed_prd_branch" ? [buildCompletedPrdBranchHint(outcome.prdBranch ?? prdRef)] : [],
11030
+ ...outcome.status === "completed_local_branch" ? [
11031
+ `Local PRD Run ${prdRef} completed. Branch: ${outcome.prdBranch ?? `local/${prdRef}`}.`
11032
+ ] : []
11033
+ ];
10250
11034
  return {
10251
- ok: false,
10252
- gate: "git",
10253
- reason: `PRD Run start failed while fetching origin/${baseBranch}.`,
10254
- diagnostics: [
10255
- fetchResult.error instanceof Error ? fetchResult.error.message : void 0,
10256
- fetchResult.stderr?.toString?.() ?? String(fetchResult.stderr ?? ""),
10257
- fetchResult.stdout?.toString?.() ?? String(fetchResult.stdout ?? "")
10258
- ].filter((value) => Boolean(value && value.trim())),
10259
- offendingPaths: []
11035
+ prdRef,
11036
+ status: outcome.status,
11037
+ attempted: [...outcome.resume.attempted],
11038
+ skipped: [...outcome.resume.skipped],
11039
+ resumed: [...outcome.resume.resumed],
11040
+ diagnostics,
11041
+ prdBranch: outcome.prdBranch
10260
11042
  };
10261
11043
  }
10262
- const revParseResult = spawnSync3(
10263
- "git",
10264
- ["rev-parse", `origin/${baseBranch}`],
10265
- {
10266
- cwd: repoRoot2,
10267
- encoding: "utf8"
10268
- }
10269
- );
10270
- if (revParseResult.status !== 0) {
11044
+ if (outcome.kind === "blocked") {
11045
+ const diagnostics = outcome.guidance.diagnostics ? [...outcome.guidance.diagnostics] : [outcome.guidance.reason];
10271
11046
  return {
10272
- ok: false,
10273
- gate: "git",
10274
- reason: `PRD Run start failed while resolving origin/${baseBranch}.`,
10275
- diagnostics: [
10276
- revParseResult.error instanceof Error ? revParseResult.error.message : void 0,
10277
- revParseResult.stderr?.toString?.() ?? String(revParseResult.stderr ?? ""),
10278
- revParseResult.stdout?.toString?.() ?? String(revParseResult.stdout ?? "")
10279
- ].filter((value) => Boolean(value && value.trim())),
10280
- offendingPaths: []
11047
+ prdRef,
11048
+ status: "blocked",
11049
+ attempted: [...outcome.resume.attempted],
11050
+ skipped: [...outcome.resume.skipped],
11051
+ resumed: [...outcome.resume.resumed],
11052
+ diagnostics,
11053
+ blockedGate: outcome.guidance.blockedGate,
11054
+ blockedReason: outcome.guidance.reason,
11055
+ offendingPaths: outcome.guidance.offendingPaths ? [...outcome.guidance.offendingPaths] : [],
11056
+ start: outcome.start ? mapStartPrdRunOutcomeToCommandResult(outcome.start) : void 0
10281
11057
  };
10282
11058
  }
10283
- return { ok: true, startBaseCommit: revParseResult.stdout.trim() };
11059
+ return {
11060
+ prdRef,
11061
+ status: outcome.status,
11062
+ attempted: [...outcome.resume.attempted],
11063
+ skipped: [...outcome.resume.skipped],
11064
+ resumed: [...outcome.resume.resumed],
11065
+ diagnostics: [...outcome.diagnostics],
11066
+ prdBranch: outcome.prdBranch,
11067
+ start: outcome.start ? mapStartPrdRunOutcomeToCommandResult(outcome.start) : void 0
11068
+ };
10284
11069
  }
10285
- function fetchPrdBranch(repoRoot2, prdRef) {
10286
- const result = spawnSync3("git", ["fetch", "origin", prdRef], {
10287
- cwd: repoRoot2,
10288
- encoding: "utf8"
10289
- });
10290
- if (result.status !== 0) {
11070
+ function mapStartPrdRunOutcomeToCommandResult(outcome) {
11071
+ if (outcome.kind === "started") {
10291
11072
  return {
10292
- ok: false,
10293
- gate: "git",
10294
- reason: `PRD Run start failed while fetching origin/${prdRef}.`,
10295
- diagnostics: [
10296
- result.error instanceof Error ? result.error.message : void 0,
10297
- result.stderr?.toString?.() ?? String(result.stderr ?? ""),
10298
- result.stdout?.toString?.() ?? String(result.stdout ?? "")
10299
- ].filter((value) => Boolean(value && value.trim())),
10300
- offendingPaths: []
11073
+ prdRef: outcome.prdRef,
11074
+ status: "starting",
11075
+ start: outcome.start,
11076
+ diagnostics: [...outcome.diagnostics]
10301
11077
  };
10302
11078
  }
10303
- return { ok: true };
10304
- }
10305
- async function ensurePrdBranchPublished(repoRoot2, prdRef, startBaseCommit) {
10306
- const pushResult = spawnSync3(
10307
- "git",
10308
- ["push", "origin", `${startBaseCommit}:refs/heads/${prdRef}`],
10309
- {
10310
- cwd: repoRoot2,
10311
- encoding: "utf8"
10312
- }
10313
- );
10314
- if (pushResult.status !== 0) {
10315
- throw new Error(
10316
- `Failed to create PRD Branch ${prdRef} from ${startBaseCommit}: ${[
10317
- pushResult.error instanceof Error ? pushResult.error.message : void 0,
10318
- pushResult.stderr?.toString?.() ?? String(pushResult.stderr ?? ""),
10319
- pushResult.stdout?.toString?.() ?? String(pushResult.stdout ?? "")
10320
- ].filter((value) => Boolean(value && value.trim())).join(" ")}`
10321
- );
10322
- }
11079
+ const diagnostics = outcome.guidance.diagnostics ? [...outcome.guidance.diagnostics] : [outcome.guidance.reason];
11080
+ return {
11081
+ prdRef: outcome.prdRef,
11082
+ status: "blocked",
11083
+ blockedGate: outcome.guidance.blockedGate,
11084
+ blockedReason: outcome.guidance.reason,
11085
+ diagnostics,
11086
+ offendingPaths: outcome.guidance.offendingPaths ? [...outcome.guidance.offendingPaths] : []
11087
+ };
10323
11088
  }
10324
11089
  function runPrdRunStatusCommand(options) {
10325
11090
  const prdRef = normalizePrdRunRef(options.prdRef);
@@ -10336,49 +11101,9 @@ function runPrdRunStatusCommand(options) {
10336
11101
  function runPrdRunListCommand(options) {
10337
11102
  return listPrdRuns(options.repoRoot);
10338
11103
  }
10339
- function persistBlockedPrdRunStartRecord(repoRoot2, prdRef, failure, context = {}) {
10340
- writePrdRunRecord(repoRoot2, {
10341
- prdRef,
10342
- status: "blocked",
10343
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10344
- blockedGate: coercePrdRunRecordBlockedGate(failure.gate),
10345
- blockedReason: failure.reason,
10346
- diagnostics: buildBlockedDiagnostics(failure),
10347
- offendingPaths: failure.offendingPaths,
10348
- targetName: context.targetName
10349
- });
10350
- }
10351
- function coercePrdRunRecordBlockedGate(gate) {
10352
- switch (gate) {
10353
- case "receipt-check":
10354
- case "branch-state":
10355
- case "git":
10356
- case "queue":
10357
- case "final-review":
10358
- return gate;
10359
- default:
10360
- return "branch-state";
10361
- }
10362
- }
10363
11104
  function buildCompletedPrdBranchHint(prdBranch) {
10364
11105
  return `PRD Run completed on branch ${prdBranch}. Next: checkout ${prdBranch} for human review, or use release promotion workflow when ready.`;
10365
11106
  }
10366
- function buildBlockedDiagnostics(failure) {
10367
- return failure.diagnostics ?? [
10368
- failure.reason,
10369
- ...failure.offendingPaths.map((path9) => `Offending path: ${path9}`)
10370
- ];
10371
- }
10372
- function buildBlockedStartResult(prdRef, failure, manifestPath) {
10373
- return {
10374
- prdRef,
10375
- status: "blocked",
10376
- blockedGate: failure.gate,
10377
- blockedReason: failure.reason,
10378
- diagnostics: buildBlockedDiagnostics(failure),
10379
- offendingPaths: failure.offendingPaths.length > 0 ? failure.offendingPaths : manifestPath ? [manifestPath] : []
10380
- };
10381
- }
10382
11107
 
10383
11108
  // commands/pr-create.ts
10384
11109
  init_common();
@@ -13326,6 +14051,14 @@ var GitHubPRProvider = class {
13326
14051
  this.logger.kv("PR_URL", pr.url);
13327
14052
  return pr;
13328
14053
  }
14054
+ async commentPr(prNumber, body) {
14055
+ await this.client.octokit.rest.issues.createComment({
14056
+ owner: this.client.owner,
14057
+ repo: this.client.repo,
14058
+ issue_number: prNumber,
14059
+ body
14060
+ });
14061
+ }
13329
14062
  async getPr(branchName) {
13330
14063
  try {
13331
14064
  const { data } = await this.client.octokit.rest.pulls.list({
@@ -13631,7 +14364,7 @@ init_common();
13631
14364
 
13632
14365
  // execution/sandcastle-execution.ts
13633
14366
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
13634
- import { join as join21 } from "path";
14367
+ import { join as join22 } from "path";
13635
14368
  import { createWorktree, opencode } from "@ai-hero/sandcastle";
13636
14369
  import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
13637
14370
 
@@ -13640,10 +14373,10 @@ init_common();
13640
14373
  import { mkdtempSync } from "fs";
13641
14374
  import { writeFile as writeFile4 } from "fs/promises";
13642
14375
  import { tmpdir } from "os";
13643
- import { dirname as dirname6, join as join20 } from "path";
14376
+ import { dirname as dirname6, join as join21 } from "path";
13644
14377
  async function writeExecutionArtifacts(worktreePath, artifacts) {
13645
14378
  for (const artifact of artifacts) {
13646
- const filePath = join20(worktreePath, artifact.path);
14379
+ const filePath = join21(worktreePath, artifact.path);
13647
14380
  await ensureDir(dirname6(filePath));
13648
14381
  await writeFile4(filePath, artifact.content, "utf-8");
13649
14382
  }
@@ -13961,12 +14694,12 @@ function isPlainObject(value) {
13961
14694
  return typeof value === "object" && value !== null && !Array.isArray(value);
13962
14695
  }
13963
14696
  function savePromptToFile(repoRoot2, stage, iteration, prompt) {
13964
- const promptsDir = join21(repoRoot2, ".pourkit", ".tmp", "prompts");
14697
+ const promptsDir = join22(repoRoot2, ".pourkit", ".tmp", "prompts");
13965
14698
  mkdirSync9(promptsDir, { recursive: true });
13966
14699
  const timestamp2 = Date.now();
13967
14700
  const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
13968
14701
  const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
13969
- const filePath = join21(promptsDir, filename);
14702
+ const filePath = join22(promptsDir, filename);
13970
14703
  writeFileSync7(filePath, prompt, "utf-8");
13971
14704
  }
13972
14705
 
@@ -14660,11 +15393,11 @@ function createCliProgram(version) {
14660
15393
  return program;
14661
15394
  }
14662
15395
  async function resolveCliVersion() {
14663
- if (isPackageVersion("0.0.0-next-20260614201555")) {
14664
- return "0.0.0-next-20260614201555";
15396
+ if (isPackageVersion("0.0.0-next-20260615001408")) {
15397
+ return "0.0.0-next-20260615001408";
14665
15398
  }
14666
- if (isReleaseVersion("0.0.0-next-20260614201555")) {
14667
- return "0.0.0-next-20260614201555";
15399
+ if (isReleaseVersion("0.0.0-next-20260615001408")) {
15400
+ return "0.0.0-next-20260615001408";
14668
15401
  }
14669
15402
  try {
14670
15403
  const root = repoRoot();