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

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
@@ -776,13 +776,17 @@ function getVerificationCommands(target) {
776
776
  }
777
777
  function resolvePrdRunMode(target, opts) {
778
778
  if (opts?.localOverride === true) {
779
- return { mode: "local", source: "cli-override" };
779
+ return { mode: "local", source: "cli-override", targetName: target.name };
780
780
  }
781
781
  const configMode = target.strategy.prdRun?.mode;
782
782
  if (configMode) {
783
- return { mode: configMode, source: "target-config" };
783
+ return {
784
+ mode: configMode,
785
+ source: "target-config",
786
+ targetName: target.name
787
+ };
784
788
  }
785
- return { mode: "github", source: "default" };
789
+ return { mode: "github", source: "default", targetName: target.name };
786
790
  }
787
791
  async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
788
792
  const { existsSync: existsSync20 } = await import("fs");
@@ -6158,10 +6162,57 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as rea
6158
6162
  import { join as join13 } from "path";
6159
6163
 
6160
6164
  // prd-run/local-branches.ts
6161
- import { execFileSync } from "child_process";
6165
+ import { execFileSync, spawnSync as spawnSync2 } from "child_process";
6162
6166
  function getLocalPrdBranchName(prdId) {
6163
6167
  return `local/${prdId}`;
6164
6168
  }
6169
+ function materializeLocalPrdBranch(prdId, startBaseCommit, repoRoot2) {
6170
+ const branch = getLocalPrdBranchName(prdId);
6171
+ const root = repoRoot2 ?? process.cwd();
6172
+ const branchExists = (() => {
6173
+ try {
6174
+ return spawnSync2("git", ["rev-parse", "--verify", "--quiet", branch], {
6175
+ cwd: root,
6176
+ encoding: "utf8",
6177
+ stdio: "pipe"
6178
+ }).status === 0;
6179
+ } catch {
6180
+ return false;
6181
+ }
6182
+ })();
6183
+ if (branchExists) {
6184
+ const currentCommit = spawnSync2("git", ["rev-parse", branch], {
6185
+ cwd: root,
6186
+ encoding: "utf8",
6187
+ stdio: "pipe"
6188
+ }).stdout?.toString?.().trim() ?? "";
6189
+ if (currentCommit !== startBaseCommit) {
6190
+ return {
6191
+ ok: false,
6192
+ failureCode: "branch_commit_mismatch",
6193
+ message: `Local PRD branch ${branch} exists at commit ${currentCommit} but expected commit ${startBaseCommit}. The branch has diverged from the start base. To resolve, delete the local branch and rerun start, or manually reset it to the expected commit.`
6194
+ };
6195
+ }
6196
+ return {
6197
+ ok: true,
6198
+ created: false
6199
+ };
6200
+ }
6201
+ const branchResult = spawnSync2("git", ["branch", branch, startBaseCommit], {
6202
+ cwd: root,
6203
+ encoding: "utf8",
6204
+ stdio: "pipe"
6205
+ });
6206
+ if (branchResult.status !== 0) {
6207
+ const stderr = branchResult.stderr?.toString?.() ?? "unknown error";
6208
+ return {
6209
+ ok: false,
6210
+ failureCode: "branch_creation_failed",
6211
+ message: `Failed to create local PRD branch ${branch} at ${startBaseCommit}: ${stderr}`
6212
+ };
6213
+ }
6214
+ return { ok: true, created: true };
6215
+ }
6165
6216
  var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
6166
6217
  var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
6167
6218
  function validateLocalBranchName(name) {
@@ -8307,13 +8358,13 @@ import {
8307
8358
  lstatSync,
8308
8359
  mkdirSync as mkdirSync11,
8309
8360
  mkdtempSync,
8310
- readdirSync as readdirSync6,
8311
- readFileSync as readFileSync16,
8361
+ readdirSync as readdirSync5,
8362
+ readFileSync as readFileSync17,
8312
8363
  realpathSync,
8313
8364
  rmSync as rmSync3
8314
8365
  } from "fs";
8315
- import { spawnSync as spawnSync2 } from "child_process";
8316
- import { dirname as dirname5, join as join20, relative as relative2 } from "path";
8366
+ import { spawnSync as spawnSync3 } from "child_process";
8367
+ import { dirname as dirname5, join as join21, relative as relative2 } from "path";
8317
8368
  import { tmpdir } from "os";
8318
8369
 
8319
8370
  // prd-run/state.ts
@@ -8343,9 +8394,11 @@ var PrdRunRecordSchema = z3.object({
8343
8394
  "waiting_for_integration",
8344
8395
  "finalizing",
8345
8396
  "final_reviewed",
8346
- "complete"
8397
+ "complete",
8398
+ "completed_local_branch"
8347
8399
  ]),
8348
8400
  updatedAt: z3.string().min(1),
8401
+ mode: z3.enum(["github", "local"]).optional(),
8349
8402
  blockedGate: z3.enum([
8350
8403
  "manifest-location",
8351
8404
  "manifest-readiness",
@@ -9152,6 +9205,28 @@ function getLocalStorePath3(repoRoot2, prdId) {
9152
9205
  function getFinalReviewReceiptPath(repoRoot2, prdId) {
9153
9206
  return join17(getLocalStorePath3(repoRoot2, prdId), "final-review-receipt.json");
9154
9207
  }
9208
+ function computeLocalMergeBase(repoRoot2, prdBranch) {
9209
+ const candidates = ["dev", "main", "master", "HEAD"];
9210
+ for (const base of candidates) {
9211
+ try {
9212
+ const result = execFileSync3("git", ["merge-base", base, prdBranch], {
9213
+ cwd: repoRoot2,
9214
+ encoding: "utf8",
9215
+ stdio: "pipe"
9216
+ });
9217
+ const mergeBase = result.trim();
9218
+ if (mergeBase && mergeBase.length >= 6) {
9219
+ return { ok: true, mergeBase };
9220
+ }
9221
+ } catch {
9222
+ continue;
9223
+ }
9224
+ }
9225
+ return {
9226
+ ok: false,
9227
+ reason: "Could not compute merge base against any known base branch (dev/main/master/HEAD)."
9228
+ };
9229
+ }
9155
9230
  async function runLocalFinalReview(prdId, options) {
9156
9231
  const root = options?.repoRoot ?? process.cwd();
9157
9232
  const commands = options?.verificationCommands ?? [];
@@ -9164,6 +9239,34 @@ async function runLocalFinalReview(prdId, options) {
9164
9239
  };
9165
9240
  }
9166
9241
  const branch = getLocalPrdBranchName(prdId);
9242
+ const mergeBaseResult = computeLocalMergeBase(root, branch);
9243
+ if (!mergeBaseResult.ok) {
9244
+ return {
9245
+ ok: false,
9246
+ failureCode: "merge_base_failed",
9247
+ message: mergeBaseResult.reason
9248
+ };
9249
+ }
9250
+ const semanticResult = validateFinalReviewArtifactSemanticIds(
9251
+ {
9252
+ prdRef: prdId,
9253
+ stage: "prdFinalReview",
9254
+ checkoutBase: branch,
9255
+ reviewBase: mergeBaseResult.mergeBase
9256
+ },
9257
+ {
9258
+ prdRef: prdId,
9259
+ prdBranch: branch,
9260
+ mergeBase: mergeBaseResult.mergeBase
9261
+ }
9262
+ );
9263
+ if (!semanticResult.ok) {
9264
+ return {
9265
+ ok: false,
9266
+ failureCode: "invalid_artifact_semantics",
9267
+ message: semanticResult.errors.join("; ")
9268
+ };
9269
+ }
9167
9270
  const failures = [];
9168
9271
  for (const cmd of commands) {
9169
9272
  try {
@@ -9184,7 +9287,7 @@ async function runLocalFinalReview(prdId, options) {
9184
9287
  });
9185
9288
  verdict = "pass_with_retouch";
9186
9289
  } catch {
9187
- verdict = "pass";
9290
+ verdict = "pass_no_changes";
9188
9291
  }
9189
9292
  } else if (failures.length === commands.length) {
9190
9293
  verdict = "blocked";
@@ -9202,9 +9305,10 @@ async function runLocalFinalReview(prdId, options) {
9202
9305
  }
9203
9306
  }
9204
9307
  const receipt = {
9205
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
9308
+ reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
9206
9309
  verdict,
9207
- branch
9310
+ prdBranch: branch,
9311
+ mergeBase: mergeBaseResult.mergeBase
9208
9312
  };
9209
9313
  const receiptPath = getFinalReviewReceiptPath(root, prdId);
9210
9314
  mkdirSync9(getLocalStorePath3(root, prdId), { recursive: true });
@@ -9276,6 +9380,26 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
9276
9380
  stdio: "pipe"
9277
9381
  }
9278
9382
  );
9383
+ const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
9384
+ cwd: root,
9385
+ encoding: "utf8",
9386
+ stdio: "pipe"
9387
+ }).trim();
9388
+ const changedPathsResult = execFileSync3(
9389
+ "git",
9390
+ ["diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"],
9391
+ {
9392
+ cwd: root,
9393
+ encoding: "utf8",
9394
+ stdio: "pipe"
9395
+ }
9396
+ );
9397
+ const changedPaths = changedPathsResult.split(/\r?\n/).filter(Boolean);
9398
+ return {
9399
+ mergeCommit,
9400
+ changedPaths,
9401
+ reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString()
9402
+ };
9279
9403
  } catch (error) {
9280
9404
  const message = error instanceof Error ? error.message : String(error);
9281
9405
  if (message.toLowerCase().includes("conflict")) {
@@ -9287,13 +9411,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
9287
9411
 
9288
9412
  // prd-run/local-reconciliation.ts
9289
9413
  import { execFileSync as execFileSync4 } from "child_process";
9290
- import {
9291
- existsSync as existsSync15,
9292
- mkdirSync as mkdirSync10,
9293
- readFileSync as readFileSync15,
9294
- readdirSync as readdirSync5,
9295
- writeFileSync as writeFileSync7
9296
- } from "fs";
9414
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
9297
9415
  import { join as join18 } from "path";
9298
9416
  function getLocalStorePath4(repoRoot2, prdId) {
9299
9417
  return join18(repoRoot2, ".pourkit", "local-prd-runs", prdId);
@@ -9304,26 +9422,6 @@ function getFinalReviewReceiptPath2(repoRoot2, prdId) {
9304
9422
  function getHandoffArtifactPath(repoRoot2, prdId) {
9305
9423
  return join18(repoRoot2, ".pourkit", ".tmp", "reconciliation", `${prdId}.json`);
9306
9424
  }
9307
- function getCompletionsDir(repoRoot2, prdId) {
9308
- const initiativesRoot = join18(
9309
- repoRoot2,
9310
- ".pourkit",
9311
- "architecture",
9312
- "initiatives"
9313
- );
9314
- if (existsSync15(initiativesRoot)) {
9315
- const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join18(initiativesRoot, e.name));
9316
- for (const dir of dirs) {
9317
- const prdsDir = join18(dir, "prds");
9318
- if (!existsSync15(prdsDir)) continue;
9319
- const prdDirs = readdirSync5(prdsDir, { withFileTypes: true }).filter(
9320
- (e) => e.isDirectory() && e.name.startsWith(prdId.toLowerCase())
9321
- );
9322
- if (prdDirs.length > 0) return join18(dir, "completions");
9323
- }
9324
- }
9325
- return join18(getLocalStorePath4(repoRoot2, prdId), "completions");
9326
- }
9327
9425
  function getReconciliationReceiptPath(repoRoot2, prdId) {
9328
9426
  return join18(
9329
9427
  getLocalStorePath4(repoRoot2, prdId),
@@ -9365,6 +9463,20 @@ async function runLocalReconciliation(prdId, options) {
9365
9463
  message: "Handoff artifact validation failed. Check Architect reconciliation output."
9366
9464
  };
9367
9465
  }
9466
+ const validResults = [
9467
+ "no_changes_needed",
9468
+ "changes_produced",
9469
+ "blocked"
9470
+ ];
9471
+ if (!validResults.includes(
9472
+ handoff.result
9473
+ )) {
9474
+ return {
9475
+ ok: false,
9476
+ failureCode: "invalid_handoff",
9477
+ message: "Handoff artifact validation failed. Check Architect reconciliation output."
9478
+ };
9479
+ }
9368
9480
  if (handoff.result === "blocked") {
9369
9481
  return {
9370
9482
  ok: false,
@@ -9379,48 +9491,6 @@ async function runLocalReconciliation(prdId, options) {
9379
9491
  message: "Handoff artifact validation failed. Check Architect reconciliation output."
9380
9492
  };
9381
9493
  }
9382
- if (handoff.result === "changes_produced") {
9383
- try {
9384
- const completionsDir = getCompletionsDir(root, prdId);
9385
- mkdirSync10(completionsDir, { recursive: true });
9386
- const existing = existsSync15(completionsDir) ? readdirSync5(completionsDir).filter((f) => f.endsWith(".md")) : [];
9387
- const nextNum = String(existing.length + 1).padStart(3, "0");
9388
- const filename = `${nextNum}-prd-${prdId.replace("PRD-", "").toLowerCase()}-reconciliation-completion.md`;
9389
- const filepath = join18(completionsDir, filename);
9390
- const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
9391
- const summary = typeof handoff.summary === "string" ? handoff.summary : "";
9392
- const lines = [
9393
- `# ${prdId} Reconciliation Completion`,
9394
- "",
9395
- `Result: ${handoff.result}`
9396
- ];
9397
- if (summary) lines.push(`Summary: ${summary}`);
9398
- lines.push("", "## Changed Planning Paths", "");
9399
- if (changedPaths.length > 0) {
9400
- lines.push(...changedPaths.map((p) => `- ${p}`));
9401
- } else {
9402
- lines.push("- (none)");
9403
- }
9404
- lines.push("");
9405
- writeFileSync7(filepath, lines.join("\n"), "utf-8");
9406
- execFileSync4("git", ["add", filepath], {
9407
- cwd: root,
9408
- encoding: "utf8",
9409
- stdio: "pipe"
9410
- });
9411
- execFileSync4(
9412
- "git",
9413
- ["commit", "-m", `docs: ${prdId} reconciliation completion`],
9414
- { cwd: root, encoding: "utf8", stdio: "pipe" }
9415
- );
9416
- } catch {
9417
- return {
9418
- ok: false,
9419
- failureCode: "completion_commit_failed",
9420
- message: "Failed to commit completion files."
9421
- };
9422
- }
9423
- }
9424
9494
  if (handoff.result === "changes_produced") {
9425
9495
  try {
9426
9496
  const targetBranch = getLocalPrdBranchName(prdId);
@@ -9434,7 +9504,7 @@ async function runLocalReconciliation(prdId, options) {
9434
9504
  return {
9435
9505
  ok: false,
9436
9506
  failureCode: "squash_merge_failed",
9437
- message: "Squash merge into local PRD Branch failed."
9507
+ message: `Local PRD Branch "${targetBranch}" does not exist. Run prd-run start first to create it.`
9438
9508
  };
9439
9509
  }
9440
9510
  execFileSync4("git", ["checkout", targetBranch], {
@@ -9457,7 +9527,7 @@ async function runLocalReconciliation(prdId, options) {
9457
9527
  return {
9458
9528
  ok: false,
9459
9529
  failureCode: "squash_merge_failed",
9460
- message: "Squash merge into local PRD Branch failed."
9530
+ message: `Reconciliation source branch "${sourceBranch}" does not exist. Re-run reconciliation.`
9461
9531
  };
9462
9532
  }
9463
9533
  execFileSync4("git", ["merge", "--squash", sourceBranch], {
@@ -9478,6 +9548,25 @@ async function runLocalReconciliation(prdId, options) {
9478
9548
  { cwd: root, encoding: "utf8", stdio: "pipe" }
9479
9549
  );
9480
9550
  }
9551
+ } catch (error) {
9552
+ const message = error instanceof Error ? error.message : "unknown error";
9553
+ return {
9554
+ ok: false,
9555
+ failureCode: "squash_merge_failed",
9556
+ message: `Squash merge into local PRD Branch failed: ${message}`
9557
+ };
9558
+ }
9559
+ }
9560
+ const prdBranch = getLocalPrdBranchName(prdId);
9561
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9562
+ let mergeCommit;
9563
+ if (handoff.result === "changes_produced") {
9564
+ try {
9565
+ mergeCommit = execFileSync4("git", ["rev-parse", "HEAD"], {
9566
+ cwd: root,
9567
+ encoding: "utf8",
9568
+ stdio: "pipe"
9569
+ }).trim();
9481
9570
  } catch {
9482
9571
  return {
9483
9572
  ok: false,
@@ -9486,18 +9575,21 @@ async function runLocalReconciliation(prdId, options) {
9486
9575
  };
9487
9576
  }
9488
9577
  }
9489
- const branch = getLocalPrdBranchName(prdId);
9490
- const receipt = {
9491
- reconciledAt: (/* @__PURE__ */ new Date()).toISOString(),
9492
- branch
9578
+ const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
9579
+ const entry = {
9580
+ result: handoff.result ?? "blocked",
9581
+ prdBranch,
9582
+ ...changedPaths.length > 0 ? { changedPlanningPaths: changedPaths } : {},
9583
+ ...mergeCommit ? { mergeCommit } : {},
9584
+ reconciledTimestamp: now
9493
9585
  };
9494
9586
  mkdirSync10(getLocalStorePath4(root, prdId), { recursive: true });
9495
9587
  writeFileSync7(
9496
9588
  getReconciliationReceiptPath(root, prdId),
9497
- JSON.stringify(receipt, null, 2),
9589
+ JSON.stringify(entry, null, 2),
9498
9590
  "utf-8"
9499
9591
  );
9500
- return { ok: true, receipt };
9592
+ return { ok: true, receipt: entry };
9501
9593
  }
9502
9594
 
9503
9595
  // prd-run/local-prepare.ts
@@ -9642,78 +9734,369 @@ async function validateLocalPrepareGates(prdId, repoRoot2) {
9642
9734
  return { ok: true, gates };
9643
9735
  }
9644
9736
 
9645
- // commands/queue.ts
9646
- init_common();
9647
- import { Effect as Effect8 } from "effect";
9737
+ // prd-run/local-queue-loop.ts
9738
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
9739
+ import { join as join20 } from "path";
9648
9740
 
9649
- // issues/select-issue.ts
9741
+ // prd-run/local-issue-run.ts
9742
+ import { execFileSync as execFileSync5 } from "child_process";
9650
9743
  init_common();
9651
- var TYPE_PRIORITY = {
9652
- "type:bugfix": 1,
9653
- "type:infra": 2,
9654
- "type:feature": 3,
9655
- "type:polish": 4,
9656
- "type:refactor": 5
9657
- };
9658
- function selectIssue(candidates, options = {}) {
9659
- const blockedLabel = options.blockedLabel ?? "blocked";
9660
- const agentInProgressLabel = options.agentInProgressLabel ?? "agent-in-progress";
9661
- if (candidates.length === 0) {
9662
- return { ok: false, reason: "No candidate issues provided." };
9744
+ async function createLocalIssueBranch(prdId, issueId, repoRoot2) {
9745
+ const root = repoRoot2 ?? process.cwd();
9746
+ const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
9747
+ if (!issuesResult.ok) {
9748
+ throw new Error(
9749
+ `Cannot create local issue branch: ${issuesResult.message}`
9750
+ );
9663
9751
  }
9664
- const unblocked = candidates.filter(
9665
- (issue) => !issue.labels.includes(blockedLabel)
9666
- );
9667
- if (unblocked.length === 0) {
9668
- return { ok: false, reason: "All candidate issues are blocked." };
9752
+ const issue = issuesResult.data.find((i) => i.id === issueId);
9753
+ if (!issue) {
9754
+ throw new Error(`Issue "${issueId}" not found under PRD "${prdId}"`);
9669
9755
  }
9670
- const valid2 = [];
9671
- const rejected = [];
9672
- for (const issue of unblocked) {
9673
- if (issue.labels.includes(agentInProgressLabel)) {
9674
- rejected.push(
9675
- `Issue #${issue.number} is labeled ${agentInProgressLabel}.`
9676
- );
9677
- continue;
9678
- }
9679
- const typeLabels = issue.labels.filter(
9680
- (l) => TYPE_LABELS.includes(l)
9756
+ const slug = slugify(issue.title);
9757
+ const branchName = `local/${prdId}/${issueId}-${slug}`;
9758
+ const validation = validateLocalBranchName(branchName);
9759
+ if (!validation.ok) {
9760
+ throw new Error(
9761
+ `Invalid local issue branch name "${branchName}": ${validation.message}`
9681
9762
  );
9682
- if (typeLabels.length !== 1) {
9683
- rejected.push(
9684
- `Issue #${issue.number} has ${typeLabels.length} type label(s); expected exactly one.`
9685
- );
9686
- continue;
9687
- }
9688
- valid2.push({ issue, priority: TYPE_PRIORITY[typeLabels[0]] });
9689
9763
  }
9690
- if (valid2.length === 0) {
9764
+ const localPrdBranch = `local/${prdId}`;
9765
+ try {
9766
+ execFileSync5(
9767
+ "git",
9768
+ ["show-ref", "--verify", "--quiet", `refs/heads/${localPrdBranch}`],
9769
+ {
9770
+ cwd: root,
9771
+ encoding: "utf8",
9772
+ stdio: "pipe"
9773
+ }
9774
+ );
9775
+ } catch {
9776
+ throw new Error(
9777
+ `Local PRD branch "${localPrdBranch}" does not exist. Create it first.`
9778
+ );
9779
+ }
9780
+ let branchExists = false;
9781
+ try {
9782
+ execFileSync5(
9783
+ "git",
9784
+ ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
9785
+ {
9786
+ cwd: root,
9787
+ encoding: "utf8",
9788
+ stdio: "pipe"
9789
+ }
9790
+ );
9791
+ branchExists = true;
9792
+ } catch {
9793
+ }
9794
+ if (!branchExists) {
9795
+ execFileSync5("git", ["checkout", "-b", branchName, localPrdBranch], {
9796
+ cwd: root,
9797
+ encoding: "utf8",
9798
+ stdio: "pipe"
9799
+ });
9800
+ }
9801
+ return { branchName, issue };
9802
+ }
9803
+ async function runLocalIssue(prdId, issueId, builder, repoRoot2) {
9804
+ try {
9805
+ const root = repoRoot2 ?? process.cwd();
9806
+ const { branchName: branch, issue } = await createLocalIssueBranch(
9807
+ prdId,
9808
+ issueId,
9809
+ root
9810
+ );
9811
+ if (builder) {
9812
+ const result = await builder.execute({
9813
+ prdId,
9814
+ issueId,
9815
+ branch,
9816
+ bodyMarkdown: issue.bodyMarkdown
9817
+ });
9818
+ if (!result.ok) {
9819
+ return {
9820
+ ok: false,
9821
+ issueId,
9822
+ branch,
9823
+ failureCode: result.error ?? "Builder execution failed"
9824
+ };
9825
+ }
9826
+ }
9827
+ return { ok: true, issueId, branch };
9828
+ } catch (error) {
9691
9829
  return {
9692
9830
  ok: false,
9693
- reason: "No runnable issues: " + rejected.join(" ")
9831
+ issueId,
9832
+ branch: "",
9833
+ failureCode: error instanceof Error ? error.message : String(error)
9694
9834
  };
9695
9835
  }
9696
- valid2.sort((a, b) => {
9697
- if (a.priority !== b.priority) return a.priority - b.priority;
9698
- const ageCmp = a.issue.createdAt.localeCompare(b.issue.createdAt);
9699
- if (ageCmp !== 0) return ageCmp;
9700
- return a.issue.number - b.issue.number;
9701
- });
9702
- return { ok: true, issue: valid2[0].issue };
9703
9836
  }
9704
9837
 
9705
- // issues/blocked-issue.ts
9706
- function parseBlockedBy(body) {
9707
- if (!body) return [];
9708
- const bm = body.match(/## Blocked by\s*\n([\s\S]*?)(?=\n## |$)/i);
9709
- if (!bm) return [];
9710
- const refs = [];
9711
- const re = /#(\d+)/g;
9712
- let m;
9713
- while ((m = re.exec(bm[1])) !== null) {
9714
- refs.push(Number(m[1]));
9715
- }
9716
- return refs;
9838
+ // prd-run/local-queue-loop.ts
9839
+ var CHILD_CLEANUP_LABELS = ["agent-in-progress", "pr-open-awaiting-merge"];
9840
+ function getIssueArtifactPath2(repoRoot2, prdId, issueId) {
9841
+ return join20(
9842
+ repoRoot2,
9843
+ ".pourkit",
9844
+ "local-prd-runs",
9845
+ prdId,
9846
+ "issues",
9847
+ `${issueId}.json`
9848
+ );
9849
+ }
9850
+ function readIssueArtifact(repoRoot2, prdId, issueId) {
9851
+ try {
9852
+ const content = readFileSync16(
9853
+ getIssueArtifactPath2(repoRoot2, prdId, issueId),
9854
+ "utf-8"
9855
+ );
9856
+ return JSON.parse(content);
9857
+ } catch {
9858
+ return null;
9859
+ }
9860
+ }
9861
+ function writeIssueArtifact(repoRoot2, prdId, issue) {
9862
+ writeFileSync8(
9863
+ getIssueArtifactPath2(repoRoot2, prdId, issue.id),
9864
+ JSON.stringify(issue, null, 2),
9865
+ "utf-8"
9866
+ );
9867
+ }
9868
+ async function reconcileLocalBlockedIssues(prdId, repoRoot2) {
9869
+ const root = repoRoot2 ?? process.cwd();
9870
+ const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
9871
+ if (!issuesResult.ok) return;
9872
+ const issues = issuesResult.data;
9873
+ for (const issue of issues) {
9874
+ const isBlocked = issue.status === "blocked";
9875
+ if (!isBlocked) continue;
9876
+ if (issue.dependencyIssueIds.length === 0) continue;
9877
+ const depResults = await Promise.all(
9878
+ issue.dependencyIssueIds.map(
9879
+ (depId) => hasLocalIssueMergeReceipt(prdId, depId, root)
9880
+ )
9881
+ );
9882
+ if (!depResults.every(Boolean)) continue;
9883
+ const updated = readIssueArtifact(root, prdId, issue.id);
9884
+ if (!updated) continue;
9885
+ updated.blockedReason = null;
9886
+ updated.labels = updated.labels.filter((l) => l !== "blocked");
9887
+ if (!updated.labels.includes("ready-for-agent")) {
9888
+ updated.labels.push("ready-for-agent");
9889
+ }
9890
+ if (hasValidLabels(updated.labels)) {
9891
+ updated.status = "ready-for-agent";
9892
+ } else {
9893
+ updated.status = "needs-triage";
9894
+ updated.labels = updated.labels.filter((l) => l !== "ready-for-agent");
9895
+ if (!updated.labels.includes("needs-triage")) {
9896
+ updated.labels.push("needs-triage");
9897
+ }
9898
+ }
9899
+ updated.receipts.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9900
+ writeIssueArtifact(root, prdId, updated);
9901
+ }
9902
+ }
9903
+ async function cleanupChildGitHubIssue(issue, issueProvider) {
9904
+ const issueNumber = issue.githubProjection.issueNumber;
9905
+ if (!issueNumber) return { ok: true };
9906
+ if (!issueProvider) {
9907
+ return {
9908
+ ok: false,
9909
+ failureCode: "missing_issue_provider",
9910
+ repairGuidance: "Issue provider is not available. Cannot close child GitHub Issue or remove labels. Configure a GitHub Issue provider to enable cleanup parity."
9911
+ };
9912
+ }
9913
+ for (const label of CHILD_CLEANUP_LABELS) {
9914
+ try {
9915
+ await issueProvider.removeLabel(issueNumber, label);
9916
+ } catch {
9917
+ }
9918
+ }
9919
+ try {
9920
+ await issueProvider.closeIssue(issueNumber);
9921
+ } catch {
9922
+ }
9923
+ return { ok: true };
9924
+ }
9925
+ async function runLocalQueueLoop(prdId, repoRoot2, issueProvider) {
9926
+ const root = repoRoot2 ?? process.cwd();
9927
+ const completedIssues = [];
9928
+ const blockedIssues = [];
9929
+ await reconcileLocalBlockedIssues(prdId, root);
9930
+ for (; ; ) {
9931
+ const runnable = await getRunnableLocalIssues(prdId, root);
9932
+ if (runnable.length === 0) {
9933
+ const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
9934
+ if (issuesResult.ok) {
9935
+ for (const issue2 of issuesResult.data) {
9936
+ if (issue2.status === "blocked") {
9937
+ blockedIssues.push(issue2.id);
9938
+ }
9939
+ }
9940
+ }
9941
+ break;
9942
+ }
9943
+ const issue = runnable[0];
9944
+ const runResult = await runLocalIssue(prdId, issue.id, void 0, root);
9945
+ if (!runResult.ok) {
9946
+ return {
9947
+ ok: false,
9948
+ completedIssues,
9949
+ blockedIssues,
9950
+ failureCode: runResult.failureCode ?? `Issue ${issue.id} failed`,
9951
+ repairGuidance: `Local issue run failed for ${issue.id}. Check the issue artifact and branch state.`,
9952
+ blockedGate: "queue"
9953
+ };
9954
+ }
9955
+ const mergeResult = await squashMergeLocalIssue(
9956
+ prdId,
9957
+ issue.id,
9958
+ void 0,
9959
+ root
9960
+ );
9961
+ if (!mergeResult.ok) {
9962
+ if (mergeResult.failureCode === "already_merged") {
9963
+ const alreadyMergedArtifact = readIssueArtifact(root, prdId, issue.id);
9964
+ if (alreadyMergedArtifact) {
9965
+ alreadyMergedArtifact.status = "complete";
9966
+ alreadyMergedArtifact.receipts.completedAt = mergeResult.receipt?.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
9967
+ alreadyMergedArtifact.receipts.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9968
+ writeIssueArtifact(root, prdId, alreadyMergedArtifact);
9969
+ const cleanupResult = await cleanupChildGitHubIssue(
9970
+ alreadyMergedArtifact,
9971
+ issueProvider
9972
+ );
9973
+ if (!cleanupResult.ok) {
9974
+ return {
9975
+ ok: false,
9976
+ completedIssues,
9977
+ blockedIssues,
9978
+ failureCode: cleanupResult.failureCode ?? "child_cleanup_failed",
9979
+ repairGuidance: cleanupResult.repairGuidance ?? `Child GitHub Issue cleanup failed for ${issue.id}.`,
9980
+ blockedGate: "queue"
9981
+ };
9982
+ }
9983
+ }
9984
+ completedIssues.push(issue.id);
9985
+ await reconcileLocalBlockedIssues(prdId, root);
9986
+ continue;
9987
+ }
9988
+ return {
9989
+ ok: false,
9990
+ completedIssues,
9991
+ blockedIssues,
9992
+ failureCode: mergeResult.failureCode ?? `Merge of ${issue.id} failed`,
9993
+ repairGuidance: mergeResult.repairGuidance,
9994
+ blockedGate: "queue"
9995
+ };
9996
+ }
9997
+ const completedArtifact = readIssueArtifact(root, prdId, issue.id);
9998
+ if (completedArtifact) {
9999
+ completedArtifact.status = "complete";
10000
+ completedArtifact.receipts.completedAt = mergeResult.receipt?.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
10001
+ completedArtifact.receipts.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
10002
+ writeIssueArtifact(root, prdId, completedArtifact);
10003
+ const cleanupResult = await cleanupChildGitHubIssue(
10004
+ completedArtifact,
10005
+ issueProvider
10006
+ );
10007
+ if (!cleanupResult.ok) {
10008
+ return {
10009
+ ok: false,
10010
+ completedIssues,
10011
+ blockedIssues,
10012
+ failureCode: cleanupResult.failureCode ?? "child_cleanup_failed",
10013
+ repairGuidance: cleanupResult.repairGuidance ?? `Child GitHub Issue cleanup failed for ${issue.id}.`,
10014
+ blockedGate: "queue"
10015
+ };
10016
+ }
10017
+ }
10018
+ completedIssues.push(issue.id);
10019
+ await reconcileLocalBlockedIssues(prdId, root);
10020
+ }
10021
+ return {
10022
+ ok: true,
10023
+ completedIssues,
10024
+ blockedIssues
10025
+ };
10026
+ }
10027
+
10028
+ // commands/queue.ts
10029
+ init_common();
10030
+ import { Effect as Effect8 } from "effect";
10031
+
10032
+ // issues/select-issue.ts
10033
+ init_common();
10034
+ var TYPE_PRIORITY = {
10035
+ "type:bugfix": 1,
10036
+ "type:infra": 2,
10037
+ "type:feature": 3,
10038
+ "type:polish": 4,
10039
+ "type:refactor": 5
10040
+ };
10041
+ function selectIssue(candidates, options = {}) {
10042
+ const blockedLabel = options.blockedLabel ?? "blocked";
10043
+ const agentInProgressLabel = options.agentInProgressLabel ?? "agent-in-progress";
10044
+ if (candidates.length === 0) {
10045
+ return { ok: false, reason: "No candidate issues provided." };
10046
+ }
10047
+ const unblocked = candidates.filter(
10048
+ (issue) => !issue.labels.includes(blockedLabel)
10049
+ );
10050
+ if (unblocked.length === 0) {
10051
+ return { ok: false, reason: "All candidate issues are blocked." };
10052
+ }
10053
+ const valid2 = [];
10054
+ const rejected = [];
10055
+ for (const issue of unblocked) {
10056
+ if (issue.labels.includes(agentInProgressLabel)) {
10057
+ rejected.push(
10058
+ `Issue #${issue.number} is labeled ${agentInProgressLabel}.`
10059
+ );
10060
+ continue;
10061
+ }
10062
+ const typeLabels = issue.labels.filter(
10063
+ (l) => TYPE_LABELS.includes(l)
10064
+ );
10065
+ if (typeLabels.length !== 1) {
10066
+ rejected.push(
10067
+ `Issue #${issue.number} has ${typeLabels.length} type label(s); expected exactly one.`
10068
+ );
10069
+ continue;
10070
+ }
10071
+ valid2.push({ issue, priority: TYPE_PRIORITY[typeLabels[0]] });
10072
+ }
10073
+ if (valid2.length === 0) {
10074
+ return {
10075
+ ok: false,
10076
+ reason: "No runnable issues: " + rejected.join(" ")
10077
+ };
10078
+ }
10079
+ valid2.sort((a, b) => {
10080
+ if (a.priority !== b.priority) return a.priority - b.priority;
10081
+ const ageCmp = a.issue.createdAt.localeCompare(b.issue.createdAt);
10082
+ if (ageCmp !== 0) return ageCmp;
10083
+ return a.issue.number - b.issue.number;
10084
+ });
10085
+ return { ok: true, issue: valid2[0].issue };
10086
+ }
10087
+
10088
+ // issues/blocked-issue.ts
10089
+ function parseBlockedBy(body) {
10090
+ if (!body) return [];
10091
+ const bm = body.match(/## Blocked by\s*\n([\s\S]*?)(?=\n## |$)/i);
10092
+ if (!bm) return [];
10093
+ const refs = [];
10094
+ const re = /#(\d+)/g;
10095
+ let m;
10096
+ while ((m = re.exec(bm[1])) !== null) {
10097
+ refs.push(Number(m[1]));
10098
+ }
10099
+ return refs;
9717
10100
  }
9718
10101
  async function reconcileBlockedIssue(issue, deps) {
9719
10102
  const blockers = parseBlockedBy(issue.body);
@@ -10164,7 +10547,7 @@ function validateChangedPlanningPaths(paths, options) {
10164
10547
  continue;
10165
10548
  }
10166
10549
  if (options?.repoRoot) {
10167
- const fullPath = join20(options.repoRoot, p);
10550
+ const fullPath = join21(options.repoRoot, p);
10168
10551
  try {
10169
10552
  if (lstatSync(fullPath).isSymbolicLink()) {
10170
10553
  rejected.push(p);
@@ -10275,6 +10658,12 @@ async function validateFinalReviewChildCompleteness(repoRoot2, prdRef, record, i
10275
10658
  );
10276
10659
  continue;
10277
10660
  }
10661
+ if (!issueData || typeof issueData.state !== "string") {
10662
+ incompleteChildren.push(
10663
+ `${child.id} (#${child.number}): failed to fetch issue state`
10664
+ );
10665
+ continue;
10666
+ }
10278
10667
  if (issueData.state === "closed") {
10279
10668
  continue;
10280
10669
  }
@@ -10331,7 +10720,7 @@ function canRetryFinalReviewBlock(record) {
10331
10720
  }
10332
10721
  function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
10333
10722
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
10334
- return existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : promptTemplate;
10723
+ return existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : promptTemplate;
10335
10724
  }
10336
10725
  function formatVerificationCommands(commands) {
10337
10726
  if (commands.length === 0) return "No verification commands configured.";
@@ -10385,7 +10774,7 @@ function buildReconciliationBranchName(prdRef) {
10385
10774
  return `${normalizePrdRunRef2(prdRef)}-reconciliation`;
10386
10775
  }
10387
10776
  function reconciliationWorktreePath(repoRoot2, branchName) {
10388
- return join20(repoRoot2, ".sandcastle", "worktrees", branchName);
10777
+ return join21(repoRoot2, ".sandcastle", "worktrees", branchName);
10389
10778
  }
10390
10779
  function reconciliationReceiptWorktreePath(branchName) {
10391
10780
  return `.sandcastle/worktrees/${branchName}`;
@@ -10397,7 +10786,7 @@ function buildFinalReviewBranchName(prdRef) {
10397
10786
  return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
10398
10787
  }
10399
10788
  function finalReviewWorktreePath(repoRoot2, branchName) {
10400
- return join20(
10789
+ return join21(
10401
10790
  repoRoot2,
10402
10791
  ".sandcastle",
10403
10792
  "worktrees",
@@ -10420,7 +10809,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
10420
10809
  }
10421
10810
  paths.add(path9);
10422
10811
  };
10423
- const diffResult = spawnSync2(
10812
+ const diffResult = spawnSync3(
10424
10813
  "git",
10425
10814
  ["diff", "--name-only", mergeBase, "--", "."],
10426
10815
  {
@@ -10431,7 +10820,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
10431
10820
  for (const path9 of diffResult.stdout.split(/\r?\n/).filter(Boolean)) {
10432
10821
  addPath(path9);
10433
10822
  }
10434
- const statusResult = spawnSync2(
10823
+ const statusResult = spawnSync3(
10435
10824
  "git",
10436
10825
  ["status", "--porcelain", "--untracked-files=all"],
10437
10826
  {
@@ -10452,7 +10841,7 @@ function buildFinalReviewFinalizerPrompt(options) {
10452
10841
  options.repoRoot,
10453
10842
  options.promptTemplate
10454
10843
  );
10455
- const promptBody = existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : options.promptTemplate;
10844
+ const promptBody = existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : options.promptTemplate;
10456
10845
  return [
10457
10846
  "# Final Review Retouch PR Finalizer",
10458
10847
  "",
@@ -10486,7 +10875,7 @@ function buildFinalReviewFinalizerPrompt(options) {
10486
10875
  }
10487
10876
  async function runFinalReviewPrFinalizer(options) {
10488
10877
  const finalizer = options.target.strategy.finalize.prDescriptionAgent;
10489
- const artifactPathInWorktree = join20(
10878
+ const artifactPathInWorktree = join21(
10490
10879
  ".pourkit",
10491
10880
  ".tmp",
10492
10881
  "finalizer",
@@ -10532,7 +10921,7 @@ function ensureFinalReviewWorktree(options) {
10532
10921
  options.repoRoot,
10533
10922
  options.branchName
10534
10923
  );
10535
- const listResult = spawnSync2("git", ["worktree", "list", "--porcelain"], {
10924
+ const listResult = spawnSync3("git", ["worktree", "list", "--porcelain"], {
10536
10925
  cwd: options.repoRoot,
10537
10926
  encoding: "utf8"
10538
10927
  });
@@ -10568,7 +10957,7 @@ function ensureFinalReviewWorktree(options) {
10568
10957
  };
10569
10958
  }
10570
10959
  mkdirSync11(dirname5(worktreePath), { recursive: true });
10571
- const branchResult = spawnSync2(
10960
+ const branchResult = spawnSync3(
10572
10961
  "git",
10573
10962
  ["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
10574
10963
  { cwd: options.repoRoot, encoding: "utf8" }
@@ -10580,7 +10969,7 @@ function ensureFinalReviewWorktree(options) {
10580
10969
  diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
10581
10970
  };
10582
10971
  }
10583
- const addResult = spawnSync2(
10972
+ const addResult = spawnSync3(
10584
10973
  "git",
10585
10974
  ["worktree", "add", worktreePath, options.branchName],
10586
10975
  { cwd: options.repoRoot, encoding: "utf8" }
@@ -10622,7 +11011,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
10622
11011
  if (existingPr && existingPr.state === "OPEN") {
10623
11012
  return existingPr;
10624
11013
  }
10625
- const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-retouch-"));
11014
+ const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-retouch-"));
10626
11015
  try {
10627
11016
  runGitOrThrow(
10628
11017
  options.repoRoot,
@@ -10640,8 +11029,8 @@ async function createOrReuseFinalReviewRetouchPr(options) {
10640
11029
  "create retouch branch"
10641
11030
  );
10642
11031
  for (const changedFile of options.changedPaths) {
10643
- const sourcePath = join20(options.sourceWorktreePath, changedFile);
10644
- const targetPath = join20(worktreePath, changedFile);
11032
+ const sourcePath = join21(options.sourceWorktreePath, changedFile);
11033
+ const targetPath = join21(worktreePath, changedFile);
10645
11034
  mkdirSync11(dirname5(targetPath), { recursive: true });
10646
11035
  cpSync(sourcePath, targetPath, { recursive: true });
10647
11036
  }
@@ -10656,13 +11045,13 @@ async function createOrReuseFinalReviewRetouchPr(options) {
10656
11045
  "commit retouch diff"
10657
11046
  );
10658
11047
  } finally {
10659
- spawnSync2("git", ["worktree", "remove", "--force", worktreePath], {
11048
+ spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
10660
11049
  cwd: options.repoRoot,
10661
11050
  encoding: "utf8"
10662
11051
  });
10663
11052
  rmSync3(worktreePath, { recursive: true, force: true });
10664
11053
  }
10665
- const pushResult = spawnSync2(
11054
+ const pushResult = spawnSync3(
10666
11055
  "git",
10667
11056
  ["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
10668
11057
  {
@@ -11072,7 +11461,7 @@ async function runPrdRunFinalReviewCommand(options) {
11072
11461
  };
11073
11462
  }
11074
11463
  const resolvedWorktreePath = executionResult.worktreePath;
11075
- const artifactPath = join20(
11464
+ const artifactPath = join21(
11076
11465
  resolvedWorktreePath,
11077
11466
  ".pourkit/final-review-artifact.json"
11078
11467
  );
@@ -11656,7 +12045,7 @@ async function runPrdRunFinalReviewCommand(options) {
11656
12045
  function runPrdRunValidateFinalReviewCommand(options) {
11657
12046
  const prdRef = normalizePrdRunRef2(options.prdRef);
11658
12047
  const checkoutBase = options.checkoutBase ?? prdRef;
11659
- const artifactPath = options.artifactPath ? options.artifactPath : join20(options.repoRoot, ".pourkit", "final-review-artifact.json");
12048
+ const artifactPath = options.artifactPath ? options.artifactPath : join21(options.repoRoot, ".pourkit", "final-review-artifact.json");
11660
12049
  const artifact = parseFinalReviewArtifact(artifactPath);
11661
12050
  if (!artifact.ok) {
11662
12051
  return {
@@ -11670,7 +12059,7 @@ function runPrdRunValidateFinalReviewCommand(options) {
11670
12059
  }
11671
12060
  let changedPaths = options.changedPaths;
11672
12061
  if (artifact.verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && (!artifact.changedPaths || artifact.changedPaths.length === 0)) {
11673
- const diffResult = spawnSync2(
12062
+ const diffResult = spawnSync3(
11674
12063
  "git",
11675
12064
  ["diff", "--name-only", options.reviewBase, "HEAD", "--", "."],
11676
12065
  { cwd: options.repoRoot, encoding: "utf8" }
@@ -11822,12 +12211,12 @@ async function runReconcilePreflightAndSetup(options) {
11822
12211
  );
11823
12212
  const worktreeCwd = worktreePath;
11824
12213
  const existingReconciliationReceipt = record.reconciliation;
11825
- const existingLocalBranch = spawnSync2(
12214
+ const existingLocalBranch = spawnSync3(
11826
12215
  "git",
11827
12216
  ["rev-parse", "--verify", "--quiet", reconciliationBranchName],
11828
12217
  { cwd: options.repoRoot, encoding: "utf8" }
11829
12218
  ).status === 0;
11830
- const existingWorktree = spawnSync2(
12219
+ const existingWorktree = spawnSync3(
11831
12220
  "git",
11832
12221
  ["worktree", "list", "--porcelain"],
11833
12222
  { cwd: options.repoRoot, encoding: "utf8" }
@@ -11880,7 +12269,7 @@ async function runReconcilePreflightAndSetup(options) {
11880
12269
  branchAction: "blocked"
11881
12270
  };
11882
12271
  }
11883
- const resetResult = spawnSync2(
12272
+ const resetResult = spawnSync3(
11884
12273
  "git",
11885
12274
  ["reset", "--hard", `origin/${prdBranch}`],
11886
12275
  { cwd: worktreeCwd, encoding: "utf8" }
@@ -11923,7 +12312,7 @@ async function runReconcilePreflightAndSetup(options) {
11923
12312
  );
11924
12313
  return { ok: false, diagnostics, branchAction: "blocked" };
11925
12314
  }
11926
- const adoptionCheck = spawnSync2(
12315
+ const adoptionCheck = spawnSync3(
11927
12316
  "git",
11928
12317
  [
11929
12318
  "merge-base",
@@ -11971,7 +12360,7 @@ async function runReconcilePreflightAndSetup(options) {
11971
12360
  );
11972
12361
  return { ok: false, diagnostics, branchAction: "blocked" };
11973
12362
  }
11974
- const branchResult = spawnSync2(
12363
+ const branchResult = spawnSync3(
11975
12364
  "git",
11976
12365
  ["branch", "-f", reconciliationBranchName, `origin/${prdBranch}`],
11977
12366
  { cwd: options.repoRoot, encoding: "utf8" }
@@ -11986,13 +12375,13 @@ async function runReconcilePreflightAndSetup(options) {
11986
12375
  return { ok: false, diagnostics, branchAction: "blocked" };
11987
12376
  }
11988
12377
  mkdirSync11(dirname5(worktreePath), { recursive: true });
11989
- const addResult = spawnSync2(
12378
+ const addResult = spawnSync3(
11990
12379
  "git",
11991
12380
  ["worktree", "add", worktreePath, reconciliationBranchName],
11992
12381
  { cwd: options.repoRoot, encoding: "utf8" }
11993
12382
  );
11994
12383
  if (addResult.status !== 0) {
11995
- spawnSync2("git", ["branch", "-D", reconciliationBranchName], {
12384
+ spawnSync3("git", ["branch", "-D", reconciliationBranchName], {
11996
12385
  cwd: options.repoRoot,
11997
12386
  encoding: "utf8"
11998
12387
  });
@@ -12040,7 +12429,7 @@ function buildReconciliationPrBody(options) {
12040
12429
  function commitAndPushReconciliationChanges(options) {
12041
12430
  const diagnostics = [];
12042
12431
  const existingPaths = options.changedPlanningPaths.filter((p) => {
12043
- return existsSync17(join20(options.worktreeCwd, p));
12432
+ return existsSync17(join21(options.worktreeCwd, p));
12044
12433
  });
12045
12434
  if (existingPaths.length === 0) {
12046
12435
  diagnostics.push(
@@ -12048,7 +12437,7 @@ function commitAndPushReconciliationChanges(options) {
12048
12437
  );
12049
12438
  return { ok: true, diagnostics: [...diagnostics] };
12050
12439
  }
12051
- const addResult = spawnSync2("git", ["add", "--", ...existingPaths], {
12440
+ const addResult = spawnSync3("git", ["add", "--", ...existingPaths], {
12052
12441
  cwd: options.worktreeCwd,
12053
12442
  encoding: "utf8"
12054
12443
  });
@@ -12065,7 +12454,7 @@ function commitAndPushReconciliationChanges(options) {
12065
12454
  };
12066
12455
  }
12067
12456
  const commitTitle = `docs: reconcile ${normalizePrdRunRef2(options.prdRef)} architecture`;
12068
- const commitResult = spawnSync2("git", ["commit", "-m", commitTitle], {
12457
+ const commitResult = spawnSync3("git", ["commit", "-m", commitTitle], {
12069
12458
  cwd: options.worktreeCwd,
12070
12459
  encoding: "utf8"
12071
12460
  });
@@ -12089,7 +12478,7 @@ function commitAndPushReconciliationChanges(options) {
12089
12478
  };
12090
12479
  }
12091
12480
  }
12092
- const pushResult = spawnSync2(
12481
+ const pushResult = spawnSync3(
12093
12482
  "git",
12094
12483
  [
12095
12484
  "push",
@@ -12183,7 +12572,7 @@ async function runPrdRunReconcileCommand(options) {
12183
12572
  const record = preflightResult.record;
12184
12573
  const prdBranch = record?.prdBranch ?? prdRef;
12185
12574
  const mergeBase = preflightResult.mergeBase;
12186
- const worktreeCwd = preflightResult.worktreePath ? join20(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
12575
+ const worktreeCwd = preflightResult.worktreePath ? join21(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
12187
12576
  const staleSucceededRecovery = assessStaleSucceededReconciliationReceipt({
12188
12577
  receipt: record?.reconciliation,
12189
12578
  worktreeCwd,
@@ -12485,7 +12874,7 @@ async function runPrdRunReconcileCommand(options) {
12485
12874
  baseRef: prdBranch,
12486
12875
  checkoutBase,
12487
12876
  reviewBase: mergeBase,
12488
- worktreePath: join20(options.repoRoot, preflightResult.worktreePath),
12877
+ worktreePath: join21(options.repoRoot, preflightResult.worktreePath),
12489
12878
  sandbox: options.config.sandbox,
12490
12879
  artifactPath,
12491
12880
  logger: options.logger
@@ -12586,7 +12975,7 @@ async function runPrdRunReconcileCommand(options) {
12586
12975
  offendingPaths: []
12587
12976
  };
12588
12977
  }
12589
- const fullArtifactPath = join20(executionResult.worktreePath, artifactPath);
12978
+ const fullArtifactPath = join21(executionResult.worktreePath, artifactPath);
12590
12979
  let artifactContent;
12591
12980
  try {
12592
12981
  artifactContent = JSON.parse(retryResult.artifact.value);
@@ -12693,11 +13082,11 @@ async function runPrdRunReconcileCommand(options) {
12693
13082
  const artifactResult = artifactContent.result ?? "changes_produced";
12694
13083
  const artifactChangedPaths = artifactContent.changedPlanningPaths ?? [];
12695
13084
  const artifactNoChangeRationale = artifactContent.noChangeRationale;
12696
- const gitStatusResult = spawnSync2("git", ["status", "--short"], {
13085
+ const gitStatusResult = spawnSync3("git", ["status", "--short"], {
12697
13086
  cwd: worktreeCwd,
12698
13087
  encoding: "utf8"
12699
13088
  });
12700
- const gitDiffResult = spawnSync2("git", ["diff", "--name-status"], {
13089
+ const gitDiffResult = spawnSync3("git", ["diff", "--name-status"], {
12701
13090
  cwd: worktreeCwd,
12702
13091
  encoding: "utf8"
12703
13092
  });
@@ -13450,7 +13839,7 @@ async function runPrdRunReconcileCommand(options) {
13450
13839
  }
13451
13840
  function computeFinalReviewMergeBase(repoRoot2, prdRef) {
13452
13841
  const normalized = normalizePrdRunRef2(prdRef);
13453
- const fetchResult = spawnSync2("git", ["fetch", "origin", "dev", normalized], {
13842
+ const fetchResult = spawnSync3("git", ["fetch", "origin", "dev", normalized], {
13454
13843
  cwd: repoRoot2,
13455
13844
  encoding: "utf8"
13456
13845
  });
@@ -13470,7 +13859,7 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
13470
13859
  if (fetchResult.stderr?.toString?.()?.trim()) {
13471
13860
  fetchDiagnostics.push(fetchResult.stderr.toString().trim());
13472
13861
  }
13473
- const mergeBaseResult = spawnSync2(
13862
+ const mergeBaseResult = spawnSync3(
13474
13863
  "git",
13475
13864
  ["merge-base", "origin/dev", `origin/${normalized}`],
13476
13865
  {
@@ -13613,17 +14002,17 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
13613
14002
  const blockerBugs = [];
13614
14003
  const blockersFixed = [];
13615
14004
  const nonBlockers = [];
13616
- const prdMirrorPath = join20(repoRoot2, PRD_047_PRD_MIRROR_PATH);
13617
- const completionsDir = join20(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
14005
+ const prdMirrorPath = join21(repoRoot2, PRD_047_PRD_MIRROR_PATH);
14006
+ const completionsDir = join21(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
13618
14007
  let escapeHatchPresent = false;
13619
14008
  if (existsSync17(completionsDir)) {
13620
- const files = readdirSync6(completionsDir);
14009
+ const files = readdirSync5(completionsDir);
13621
14010
  escapeHatchPresent = files.some(
13622
14011
  (f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
13623
14012
  );
13624
14013
  }
13625
14014
  if (!escapeHatchPresent) {
13626
- escapeHatchPresent = existsSync17(join20(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
14015
+ escapeHatchPresent = existsSync17(join21(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
13627
14016
  }
13628
14017
  const prdMirrorExists = existsSync17(prdMirrorPath);
13629
14018
  if (!prdMirrorExists) {
@@ -13632,7 +14021,7 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
13632
14021
  );
13633
14022
  }
13634
14023
  if (prdMirrorExists) {
13635
- const mirrorContent = readFileSync16(prdMirrorPath, "utf-8");
14024
+ const mirrorContent = readFileSync17(prdMirrorPath, "utf-8");
13636
14025
  const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
13637
14026
  const hasEscapeHatch = mirrorContent.includes("escape hatch");
13638
14027
  if (!hasReconcileCommand) {
@@ -13646,16 +14035,16 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
13646
14035
  );
13647
14036
  }
13648
14037
  }
13649
- const childIssueDir = join20(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
14038
+ const childIssueDir = join21(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
13650
14039
  const childIssuesExist = existsSync17(childIssueDir);
13651
14040
  if (!childIssuesExist) {
13652
14041
  nonBlockers.push(
13653
14042
  `PRD-0047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
13654
14043
  );
13655
14044
  }
13656
- const manifestPath = join20(repoRoot2, PRD_047_MANIFEST_PATH);
14045
+ const manifestPath = join21(repoRoot2, PRD_047_MANIFEST_PATH);
13657
14046
  if (existsSync17(manifestPath)) {
13658
- const manifestContent = readFileSync16(manifestPath, "utf-8");
14047
+ const manifestContent = readFileSync17(manifestPath, "utf-8");
13659
14048
  if (!manifestContent.includes("ready_for_prepare")) {
13660
14049
  nonBlockers.push(
13661
14050
  "Planning Artifact Manifest is not marked as ready_for_prepare."
@@ -13744,6 +14133,17 @@ function runPrdRunAuditCommand(options) {
13744
14133
  const prdRef = normalizePrdRunRef2(options.prdRef);
13745
14134
  return auditPrd047Implementation(options.repoRoot, prdRef);
13746
14135
  }
14136
+ async function closeParentGitHubIssue(prdRef, repoRoot2, issueProvider) {
14137
+ const prdResult = await resolveLocalPrdArtifact(prdRef, repoRoot2);
14138
+ const parentIssueNumber = prdResult.ok ? prdResult.data.githubProjection.issueNumber : null;
14139
+ if (!parentIssueNumber) return { closed: false, issueNumber: null };
14140
+ try {
14141
+ await issueProvider.closeIssue(parentIssueNumber);
14142
+ return { closed: true, issueNumber: parentIssueNumber };
14143
+ } catch {
14144
+ return { closed: false, issueNumber: parentIssueNumber };
14145
+ }
14146
+ }
13747
14147
  async function runPrdRunLaunchCommand(options) {
13748
14148
  const prdRef = normalizePrdRunRef2(options.prdRef);
13749
14149
  const attempted = [];
@@ -13755,10 +14155,11 @@ async function runPrdRunLaunchCommand(options) {
13755
14155
  const terminal = /* @__PURE__ */ new Set([
13756
14156
  "waiting_for_integration",
13757
14157
  "finalizing",
13758
- "complete"
14158
+ "complete",
14159
+ "completed_local_branch"
13759
14160
  ]);
13760
14161
  if (terminal.has(existingRecord.record.status)) {
13761
- return {
14162
+ const result = {
13762
14163
  prdRef,
13763
14164
  status: existingRecord.record.status,
13764
14165
  attempted: [],
@@ -13768,10 +14169,38 @@ async function runPrdRunLaunchCommand(options) {
13768
14169
  `PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`
13769
14170
  ]
13770
14171
  };
14172
+ if (existingRecord.record.status === "completed_local_branch" && existingRecord.record.prdBranch) {
14173
+ result.prdBranch = existingRecord.record.prdBranch;
14174
+ }
14175
+ return result;
13771
14176
  }
13772
14177
  }
13773
14178
  if (existingRecord.record) {
13774
14179
  const status = existingRecord.record.status;
14180
+ const resolvedLaunchMode = options.config ? (() => {
14181
+ try {
14182
+ const target = resolveTarget(options.config, options.targetName);
14183
+ return resolvePrdRunMode(target).mode;
14184
+ } catch {
14185
+ return void 0;
14186
+ }
14187
+ })() : void 0;
14188
+ if (resolvedLaunchMode && existingRecord.record.mode && existingRecord.record.mode !== resolvedLaunchMode) {
14189
+ return {
14190
+ prdRef,
14191
+ status: "blocked",
14192
+ attempted: [],
14193
+ skipped: ["prepare", "start", "queue", "final-review", "reconcile"],
14194
+ resumed: [],
14195
+ diagnostics: [
14196
+ `Recorded mode: ${existingRecord.record.mode}`,
14197
+ `Resolved mode: ${resolvedLaunchMode}`
14198
+ ],
14199
+ blockedGate: "branch-state",
14200
+ blockedReason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedLaunchMode}". Resolve by using the correct target or --local-override.`,
14201
+ offendingPaths: []
14202
+ };
14203
+ }
13775
14204
  if (status === "drained") {
13776
14205
  skipped.push("prepare", "start", "queue");
13777
14206
  resumed.push("final-review");
@@ -13899,17 +14328,59 @@ async function runPrdRunLaunchCommand(options) {
13899
14328
  }
13900
14329
  diagnostics.push(...startResult.diagnostics);
13901
14330
  attempted.push("queue");
14331
+ if (startResult.status === "drained" && options.issueProvider && existsSync17(join21(options.repoRoot, ".pourkit", "local-prd-runs", prdRef))) {
14332
+ await closeParentGitHubIssue(
14333
+ prdRef,
14334
+ options.repoRoot,
14335
+ options.issueProvider
14336
+ );
14337
+ }
13902
14338
  }
13903
14339
  let finalReviewResult;
13904
14340
  if (!skipped.includes("final-review")) {
13905
14341
  attempted.push("final-review");
13906
- const localStorePath = join20(
14342
+ const localStorePath2 = join21(
13907
14343
  options.repoRoot,
13908
14344
  ".pourkit",
13909
14345
  "local-prd-runs",
13910
14346
  prdRef
13911
14347
  );
13912
- if (existsSync17(localStorePath)) {
14348
+ if (existsSync17(localStorePath2)) {
14349
+ const prdRecord = readPrdRun(options.repoRoot, prdRef);
14350
+ if (prdRecord.record && prdRecord.record.status !== "drained" && !canRetryFinalReviewBlock(prdRecord.record)) {
14351
+ const reason = `Final Review blocked: Queue drain not completed. Status is "${prdRecord.record.status}". Finish all child Issues before Final Review.`;
14352
+ writePrdRunRecord(options.repoRoot, {
14353
+ prdRef,
14354
+ status: "blocked",
14355
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14356
+ blockedGate: "final-review",
14357
+ blockedReason: reason,
14358
+ diagnostics: [
14359
+ `Current status: ${prdRecord.record.status}. Queue drain receipt is missing. Run child Issues through queue-run before Final Review.`
14360
+ ],
14361
+ targetName: options.targetName,
14362
+ offendingPaths: []
14363
+ });
14364
+ return {
14365
+ prdRef,
14366
+ status: "blocked",
14367
+ attempted,
14368
+ skipped: [
14369
+ ...skipped.includes("prepare") ? ["prepare"] : [],
14370
+ ...skipped.includes("start") ? ["queue"] : [],
14371
+ "reconcile"
14372
+ ],
14373
+ resumed,
14374
+ diagnostics: [
14375
+ `Current status: ${prdRecord.record.status}. Queue drain receipt is missing. Run child Issues through queue-run before Final Review.`
14376
+ ],
14377
+ blockedGate: "final-review",
14378
+ blockedReason: reason,
14379
+ offendingPaths: [],
14380
+ prepare: prepareResult,
14381
+ start: startResult
14382
+ };
14383
+ }
13913
14384
  const targetConfig = options.config?.targets?.find(
13914
14385
  (t) => t.name === options.targetName
13915
14386
  );
@@ -13948,9 +14419,48 @@ async function runPrdRunLaunchCommand(options) {
13948
14419
  start: startResult
13949
14420
  };
13950
14421
  }
14422
+ if (localResult.verdict === "blocked" || localResult.verdict === "needs_human_review") {
14423
+ const reason = localResult.message ?? `Local Final Review verdict: ${localResult.verdict}`;
14424
+ writePrdRunRecord(options.repoRoot, {
14425
+ prdRef,
14426
+ status: "blocked",
14427
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14428
+ blockedGate: "final-review",
14429
+ blockedReason: reason,
14430
+ diagnostics: [reason],
14431
+ targetName: options.targetName,
14432
+ offendingPaths: []
14433
+ });
14434
+ return {
14435
+ prdRef,
14436
+ status: "blocked",
14437
+ attempted,
14438
+ skipped: [
14439
+ ...skipped.includes("prepare") ? ["prepare"] : [],
14440
+ ...skipped.includes("start") ? ["queue"] : [],
14441
+ "reconcile"
14442
+ ],
14443
+ resumed,
14444
+ diagnostics: [reason],
14445
+ blockedGate: "final-review",
14446
+ blockedReason: reason,
14447
+ offendingPaths: [],
14448
+ prepare: prepareResult,
14449
+ start: startResult
14450
+ };
14451
+ }
14452
+ let retouchMergeCommit;
14453
+ let changedPaths;
13951
14454
  if (localResult.verdict === "pass_with_retouch") {
13952
14455
  try {
13953
- await squashFinalReviewRetouch(prdRef, options.repoRoot);
14456
+ const evidence = await squashFinalReviewRetouch(
14457
+ prdRef,
14458
+ options.repoRoot
14459
+ );
14460
+ if (evidence) {
14461
+ retouchMergeCommit = evidence.mergeCommit;
14462
+ changedPaths = evidence.changedPaths;
14463
+ }
13954
14464
  } catch (error) {
13955
14465
  const reason = error instanceof Error ? error.message : String(error);
13956
14466
  writePrdRunRecord(options.repoRoot, {
@@ -13982,14 +14492,16 @@ async function runPrdRunLaunchCommand(options) {
13982
14492
  };
13983
14493
  }
13984
14494
  }
13985
- const mappedVerdict = localResult.verdict === "pass" ? "pass_no_changes" : localResult.verdict;
13986
14495
  const receipt = {
13987
14496
  status: "final_reviewed",
13988
14497
  targetName: options.targetName,
13989
14498
  prdBranch: `local/${prdRef}`,
14499
+ mergeBase: localResult.receipt?.mergeBase,
13990
14500
  diagnostics: [],
13991
14501
  reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
13992
- verdict: mappedVerdict
14502
+ verdict: localResult.verdict ?? "pass_no_changes",
14503
+ retouchMergeCommit,
14504
+ changedPaths
13993
14505
  };
13994
14506
  const existingRecord2 = readPrdRun(options.repoRoot, prdRef);
13995
14507
  writePrdRunRecord(options.repoRoot, {
@@ -14064,17 +14576,100 @@ async function runPrdRunLaunchCommand(options) {
14064
14576
  let reconcileResult;
14065
14577
  if (!skipped.includes("reconcile")) {
14066
14578
  attempted.push("reconcile");
14067
- reconcileResult = await runPrdRunReconcileCommand({
14068
- repoRoot: options.repoRoot,
14069
- prdRef,
14070
- targetName: options.targetName,
14071
- config: options.config,
14072
- logger: options.logger,
14073
- executionProvider: options.executionProvider,
14074
- prProvider: options.prProvider,
14075
- autoMerge: options.autoMerge
14076
- });
14077
- if (reconcileResult.status === "blocked") {
14579
+ const localStorePath2 = join21(
14580
+ options.repoRoot,
14581
+ ".pourkit",
14582
+ "local-prd-runs",
14583
+ prdRef
14584
+ );
14585
+ if (existsSync17(localStorePath2)) {
14586
+ const prdRecord = readPrdRun(options.repoRoot, prdRef);
14587
+ const finalReviewReceipt = prdRecord.record?.finalReview;
14588
+ const hasCompletedFinalReview = finalReviewReceipt?.status === "final_reviewed" || finalReviewReceipt?.status === "succeeded";
14589
+ if (!hasCompletedFinalReview) {
14590
+ const reason = `Reconciliation blocked: Final Review not completed. Run Final Review before reconciliation.`;
14591
+ return {
14592
+ prdRef,
14593
+ status: "blocked",
14594
+ attempted,
14595
+ skipped: [...skipped],
14596
+ resumed,
14597
+ diagnostics: [reason],
14598
+ blockedGate: "reconciliation",
14599
+ blockedReason: reason,
14600
+ offendingPaths: [],
14601
+ prepare: prepareResult,
14602
+ start: startResult,
14603
+ finalReview: finalReviewResult
14604
+ };
14605
+ }
14606
+ const localResult = await runLocalReconciliation(prdRef, {
14607
+ repoRoot: options.repoRoot
14608
+ });
14609
+ if (!localResult.ok) {
14610
+ return {
14611
+ prdRef,
14612
+ status: "blocked",
14613
+ attempted,
14614
+ skipped: [...skipped],
14615
+ resumed,
14616
+ diagnostics: localResult.message ? [localResult.message] : [],
14617
+ blockedGate: "reconciliation",
14618
+ blockedReason: localResult.message ?? "Local reconciliation blocked.",
14619
+ offendingPaths: [],
14620
+ prepare: prepareResult,
14621
+ start: startResult,
14622
+ finalReview: finalReviewResult
14623
+ };
14624
+ }
14625
+ const receipt = {
14626
+ status: "succeeded",
14627
+ result: localResult.receipt?.result,
14628
+ changedPlanningPaths: localResult.receipt?.changedPlanningPaths,
14629
+ targetName: options.targetName,
14630
+ prdBranch: localResult.receipt?.prdBranch ?? `local/${prdRef}`,
14631
+ mergeCommit: localResult.receipt?.mergeCommit,
14632
+ reconciledAt: localResult.receipt?.reconciledTimestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
14633
+ autoMerge: false,
14634
+ agentIdentity: "pourkit-architect",
14635
+ noChange: localResult.receipt?.result === "no_changes_needed",
14636
+ source: "dev",
14637
+ target: localResult.receipt?.prdBranch ?? `local/${prdRef}`
14638
+ };
14639
+ const existingRecord2 = readPrdRun(options.repoRoot, prdRef);
14640
+ writePrdRunRecord(options.repoRoot, {
14641
+ prdRef,
14642
+ status: "completed_local_branch",
14643
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14644
+ targetName: options.targetName,
14645
+ prdBranch: `local/${prdRef}`,
14646
+ manifestPath: existingRecord2.record?.manifestPath,
14647
+ planning: existingRecord2.record?.planning,
14648
+ start: existingRecord2.record?.start,
14649
+ finalReview: existingRecord2.record?.finalReview,
14650
+ reconciliation: receipt,
14651
+ scopeChanges: existingRecord2.record?.scopeChanges
14652
+ });
14653
+ reconcileResult = {
14654
+ prdRef,
14655
+ status: "succeeded",
14656
+ reconciliation: receipt,
14657
+ diagnostics: []
14658
+ };
14659
+ diagnostics.push("Local reconciliation completed.");
14660
+ } else {
14661
+ reconcileResult = await runPrdRunReconcileCommand({
14662
+ repoRoot: options.repoRoot,
14663
+ prdRef,
14664
+ targetName: options.targetName,
14665
+ config: options.config,
14666
+ logger: options.logger,
14667
+ executionProvider: options.executionProvider,
14668
+ prProvider: options.prProvider,
14669
+ autoMerge: options.autoMerge
14670
+ });
14671
+ }
14672
+ if (reconcileResult && reconcileResult.status === "blocked") {
14078
14673
  return {
14079
14674
  prdRef,
14080
14675
  status: "blocked",
@@ -14091,7 +14686,9 @@ async function runPrdRunLaunchCommand(options) {
14091
14686
  reconcile: reconcileResult
14092
14687
  };
14093
14688
  }
14094
- diagnostics.push(...reconcileResult.diagnostics);
14689
+ if (reconcileResult) {
14690
+ diagnostics.push(...reconcileResult.diagnostics);
14691
+ }
14095
14692
  }
14096
14693
  const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
14097
14694
  const reconciliationReceipt = reconcileResult && reconcileResult.status === "succeeded" ? reconcileResult.reconciliation : currentRecord?.reconciliation;
@@ -14174,6 +14771,29 @@ async function runPrdRunLaunchCommand(options) {
14174
14771
  reconcile: reconcileResult
14175
14772
  };
14176
14773
  }
14774
+ const localStorePath = join21(
14775
+ options.repoRoot,
14776
+ ".pourkit",
14777
+ "local-prd-runs",
14778
+ prdRef
14779
+ );
14780
+ if (existsSync17(localStorePath)) {
14781
+ return {
14782
+ prdRef,
14783
+ status: "completed_local_branch",
14784
+ prdBranch: `local/${prdRef}`,
14785
+ attempted,
14786
+ skipped,
14787
+ resumed,
14788
+ diagnostics: [
14789
+ `Local PRD Run ${prdRef} completed. Branch: local/${prdRef}.`
14790
+ ],
14791
+ prepare: prepareResult,
14792
+ start: startResult,
14793
+ finalReview: finalReviewResult,
14794
+ reconcile: reconcileResult
14795
+ };
14796
+ }
14177
14797
  writePrdRunRecord(options.repoRoot, {
14178
14798
  prdRef,
14179
14799
  status: "waiting_for_integration",
@@ -14503,7 +15123,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
14503
15123
  const branch = `local/${normalizedRef}`;
14504
15124
  const branchExists = (() => {
14505
15125
  try {
14506
- return spawnSync2("git", ["rev-parse", "--verify", "--quiet", branch], {
15126
+ return spawnSync3("git", ["rev-parse", "--verify", "--quiet", branch], {
14507
15127
  cwd: root,
14508
15128
  encoding: "utf8",
14509
15129
  stdio: "pipe"
@@ -14523,7 +15143,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
14523
15143
  message: `Branch ${branch} already exists without a matching start receipt. Remove it manually or use a different PRD ID.`
14524
15144
  };
14525
15145
  }
14526
- const branchResult = spawnSync2("git", ["branch", branch], {
15146
+ const branchResult = spawnSync3("git", ["branch", branch], {
14527
15147
  cwd: root,
14528
15148
  encoding: "utf8",
14529
15149
  stdio: "pipe"
@@ -14538,7 +15158,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
14538
15158
  }
14539
15159
  const baseCommit = (() => {
14540
15160
  try {
14541
- return spawnSync2("git", ["rev-parse", "HEAD"], {
15161
+ return spawnSync3("git", ["rev-parse", "HEAD"], {
14542
15162
  cwd: root,
14543
15163
  encoding: "utf8",
14544
15164
  stdio: "pipe"
@@ -14660,6 +15280,17 @@ async function runLocalLaunchCommand(prdId, repoRoot2) {
14660
15280
  };
14661
15281
  return { ok: false, failedStage: "finalReview", stages };
14662
15282
  }
15283
+ if (result.verdict === "blocked" || result.verdict === "needs_human_review") {
15284
+ stages.finalReview = {
15285
+ ok: false,
15286
+ stage: "finalReview",
15287
+ failureCode: result.verdict === "blocked" ? "final_review_blocked" : "needs_human_review",
15288
+ repairabilityClass: "manual",
15289
+ reason: result.message ?? `Final Review verdict: ${result.verdict}`,
15290
+ repairGuidance: `Fix final review failures and rerun \`pourkit prd-run launch --local ${normalizedRef}\`.`
15291
+ };
15292
+ return { ok: false, failedStage: "finalReview", stages };
15293
+ }
14663
15294
  if (result.verdict === "pass_with_retouch") {
14664
15295
  try {
14665
15296
  await squashFinalReviewRetouch(normalizedRef, root);
@@ -14779,6 +15410,32 @@ async function runPrdRunStartCommand(options) {
14779
15410
  );
14780
15411
  }
14781
15412
  const existingRecord = readPrdRun(options.repoRoot, prdRef);
15413
+ const resolvedMode = options.config ? (() => {
15414
+ try {
15415
+ const target = resolveTarget(options.config, targetName);
15416
+ return resolvePrdRunMode(target).mode;
15417
+ } catch {
15418
+ return void 0;
15419
+ }
15420
+ })() : void 0;
15421
+ if (resolvedMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedMode) {
15422
+ const failure = {
15423
+ ok: false,
15424
+ gate: "branch-state",
15425
+ reason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedMode}". Resolve by using the correct target or --local-override.`,
15426
+ diagnostics: [
15427
+ `Recorded mode: ${existingRecord.record.mode}`,
15428
+ `Resolved mode: ${resolvedMode}`
15429
+ ],
15430
+ offendingPaths: []
15431
+ };
15432
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
15433
+ manifestPath: existingRecord.record?.manifestPath,
15434
+ planning: existingRecord.record?.planning,
15435
+ targetName
15436
+ });
15437
+ return buildBlockedStartResult(prdRef, failure);
15438
+ }
14782
15439
  if (existingRecord.record?.start) {
14783
15440
  const fetchResult2 = fetchOriginDev(options.repoRoot);
14784
15441
  if (!fetchResult2.ok) {
@@ -14814,7 +15471,8 @@ async function runPrdRunStartCommand(options) {
14814
15471
  ...reusedStart
14815
15472
  },
14816
15473
  {
14817
- targetName
15474
+ targetName,
15475
+ mode: resolvedMode
14818
15476
  }
14819
15477
  );
14820
15478
  return await processStartResult(
@@ -14917,6 +15575,7 @@ async function runPrdRunStartCommand(options) {
14917
15575
  },
14918
15576
  {
14919
15577
  targetName,
15578
+ mode: resolvedMode,
14920
15579
  manifestPath: existingRecord.record?.manifestPath,
14921
15580
  planning: existingRecord.record?.planning
14922
15581
  }
@@ -15076,6 +15735,7 @@ async function runPrdRunStartCommand(options) {
15076
15735
  },
15077
15736
  {
15078
15737
  targetName,
15738
+ mode: resolvedMode,
15079
15739
  manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
15080
15740
  planning: existingRecord.record?.planning
15081
15741
  }
@@ -15158,6 +15818,68 @@ async function runPrdRunStartCommand(options) {
15158
15818
  });
15159
15819
  return buildBlockedStartResult(prdRef, failure);
15160
15820
  }
15821
+ if (resolvedMode === "local") {
15822
+ const localBranchResult = materializeLocalPrdBranch(
15823
+ prdRef,
15824
+ fetchResult.startBaseCommit,
15825
+ options.repoRoot
15826
+ );
15827
+ if (!localBranchResult.ok) {
15828
+ const failure = {
15829
+ ok: false,
15830
+ gate: "branch-state",
15831
+ reason: localBranchResult.message,
15832
+ diagnostics: [localBranchResult.message],
15833
+ offendingPaths: []
15834
+ };
15835
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
15836
+ manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
15837
+ planning: existingRecord.record?.planning,
15838
+ targetName
15839
+ });
15840
+ return buildBlockedStartResult(prdRef, failure);
15841
+ }
15842
+ const localStoreDir = join21(
15843
+ options.repoRoot,
15844
+ ".pourkit",
15845
+ "local-prd-runs",
15846
+ prdRef
15847
+ );
15848
+ let localStoreReady = false;
15849
+ if (existsSync17(localStoreDir)) {
15850
+ const localStorePath = join21(localStoreDir, "prd.json");
15851
+ try {
15852
+ const content = JSON.parse(readFileSync17(localStorePath, "utf8"));
15853
+ localStoreReady = content?.id === prdRef && content?.kind === "prd";
15854
+ } catch {
15855
+ localStoreReady = false;
15856
+ }
15857
+ }
15858
+ const prereqs = {
15859
+ localBranchName: getLocalPrdBranchName(prdRef),
15860
+ localBranchExists: !localBranchResult.created,
15861
+ localBranchCommit: fetchResult.startBaseCommit,
15862
+ localStoreReady
15863
+ };
15864
+ if (existsSync17(localStoreDir) && !prereqs.localStoreReady) {
15865
+ const failure = {
15866
+ ok: false,
15867
+ gate: "branch-state",
15868
+ 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. Ensure Local PRD Run Store is initialized before starting.`,
15869
+ diagnostics: [
15870
+ `Expected store path: .pourkit/local-prd-runs/${prdRef}/prd.json`,
15871
+ `Expected PRD ID: ${prdRef}`
15872
+ ],
15873
+ offendingPaths: []
15874
+ };
15875
+ persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
15876
+ manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
15877
+ planning: existingRecord.record?.planning,
15878
+ targetName
15879
+ });
15880
+ return buildBlockedStartResult(prdRef, failure);
15881
+ }
15882
+ }
15161
15883
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
15162
15884
  const start = {
15163
15885
  status: "started",
@@ -15185,6 +15907,7 @@ async function runPrdRunStartCommand(options) {
15185
15907
  updatedAt,
15186
15908
  targetName,
15187
15909
  start,
15910
+ mode: resolvedMode,
15188
15911
  manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath ?? void 0,
15189
15912
  planning: existingRecord.record?.planning
15190
15913
  });
@@ -15215,26 +15938,93 @@ async function processStartResult(startResult, options) {
15215
15938
  } catch {
15216
15939
  resolvedMode = void 0;
15217
15940
  }
15941
+ const modeForRecord = resolvedMode?.mode;
15218
15942
  writePrdRunRecord(repoRoot2, {
15219
15943
  prdRef,
15220
15944
  status: "running",
15221
15945
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15222
15946
  targetName: start.targetName,
15223
15947
  start,
15948
+ mode: modeForRecord,
15224
15949
  manifestPath: existingRecord.record?.manifestPath,
15225
15950
  planning: existingRecord.record?.planning
15226
15951
  });
15227
- const localStorePath = join20(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
15952
+ const localStorePath = join21(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
15228
15953
  if (existsSync17(localStorePath)) {
15229
- const localIssues = await getRunnableLocalIssues(prdRef, repoRoot2);
15954
+ const queueResult = await runLocalQueueLoop(
15955
+ prdRef,
15956
+ repoRoot2,
15957
+ options.issueProvider
15958
+ );
15959
+ if (!queueResult.ok) {
15960
+ const failureCode = queueResult.failureCode ?? "queue_error";
15961
+ const reason = queueResult.repairGuidance ?? `Local queue loop blocked: ${failureCode}`;
15962
+ const diagnostics = [
15963
+ `Local queue loop failed with failureCode "${failureCode}".`,
15964
+ ...queueResult.repairGuidance ? [`Repair guidance: ${queueResult.repairGuidance}`] : []
15965
+ ];
15966
+ writePrdRunRecord(repoRoot2, {
15967
+ prdRef,
15968
+ status: "blocked",
15969
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15970
+ targetName: start.targetName,
15971
+ start,
15972
+ mode: modeForRecord,
15973
+ manifestPath: existingRecord.record?.manifestPath,
15974
+ planning: existingRecord.record?.planning,
15975
+ blockedGate: "queue",
15976
+ blockedReason: reason,
15977
+ diagnostics,
15978
+ offendingPaths: []
15979
+ });
15980
+ return {
15981
+ prdRef,
15982
+ status: "blocked",
15983
+ blockedGate: "queue",
15984
+ blockedReason: reason,
15985
+ diagnostics,
15986
+ offendingPaths: []
15987
+ };
15988
+ }
15989
+ if (queueResult.blockedIssues.length > 0) {
15990
+ const reason = `Queue processed complete but ${queueResult.blockedIssues.length} blocked child issue(s) remain.`;
15991
+ const diagnostics = [
15992
+ reason,
15993
+ `Blocked issues: ${queueResult.blockedIssues.join(", ")}`,
15994
+ "Resolve blocked children before parent close."
15995
+ ];
15996
+ writePrdRunRecord(repoRoot2, {
15997
+ prdRef,
15998
+ status: "blocked",
15999
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
16000
+ targetName: start.targetName,
16001
+ start,
16002
+ mode: modeForRecord,
16003
+ manifestPath: existingRecord.record?.manifestPath,
16004
+ planning: existingRecord.record?.planning,
16005
+ blockedGate: "queue",
16006
+ blockedReason: reason,
16007
+ diagnostics,
16008
+ offendingPaths: []
16009
+ });
16010
+ return {
16011
+ prdRef,
16012
+ status: "blocked",
16013
+ blockedGate: "queue",
16014
+ blockedReason: reason,
16015
+ diagnostics,
16016
+ offendingPaths: []
16017
+ };
16018
+ }
15230
16019
  start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
15231
- start.queueProcessedCount = localIssues.length;
16020
+ start.queueProcessedCount = queueResult.completedIssues.length + queueResult.blockedIssues.length;
15232
16021
  writePrdRunRecord(repoRoot2, {
15233
16022
  prdRef,
15234
16023
  status: "drained",
15235
16024
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15236
16025
  targetName: start.targetName,
15237
16026
  start,
16027
+ mode: modeForRecord,
15238
16028
  manifestPath: existingRecord.record?.manifestPath,
15239
16029
  planning: existingRecord.record?.planning
15240
16030
  });
@@ -15277,6 +16067,7 @@ async function processStartResult(startResult, options) {
15277
16067
  diagnostics,
15278
16068
  targetName: start.targetName,
15279
16069
  start,
16070
+ mode: existingRecord.record?.mode,
15280
16071
  manifestPath: existingRecord.record?.manifestPath,
15281
16072
  planning: existingRecord.record?.planning,
15282
16073
  offendingPaths: []
@@ -15298,6 +16089,7 @@ async function processStartResult(startResult, options) {
15298
16089
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15299
16090
  targetName: start.targetName,
15300
16091
  start,
16092
+ mode: existingRecord.record?.mode,
15301
16093
  manifestPath: existingRecord.record?.manifestPath,
15302
16094
  planning: existingRecord.record?.planning
15303
16095
  });
@@ -15319,6 +16111,7 @@ async function processStartResult(startResult, options) {
15319
16111
  diagnostics,
15320
16112
  targetName: start.targetName,
15321
16113
  start,
16114
+ mode: existingRecord.record?.mode,
15322
16115
  manifestPath: existingRecord.record?.manifestPath,
15323
16116
  planning: existingRecord.record?.planning,
15324
16117
  offendingPaths: []
@@ -15343,6 +16136,7 @@ async function processStartResult(startResult, options) {
15343
16136
  diagnostics: outcomeDiagnostics,
15344
16137
  targetName: start.targetName,
15345
16138
  start,
16139
+ mode: existingRecord.record?.mode,
15346
16140
  manifestPath: existingRecord.record?.manifestPath,
15347
16141
  planning: existingRecord.record?.planning,
15348
16142
  offendingPaths: []
@@ -15367,6 +16161,7 @@ async function processStartResult(startResult, options) {
15367
16161
  diagnostics,
15368
16162
  targetName: start.targetName,
15369
16163
  start,
16164
+ mode: existingRecord.record?.mode,
15370
16165
  manifestPath: existingRecord.record?.manifestPath,
15371
16166
  planning: existingRecord.record?.planning,
15372
16167
  offendingPaths: []
@@ -15398,12 +16193,13 @@ function persistStartingPrdRunRecord(repoRoot2, prdRef, existingRecord, start, c
15398
16193
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15399
16194
  targetName: context.targetName,
15400
16195
  start,
16196
+ mode: context.mode,
15401
16197
  manifestPath: context.manifestPath ?? existingRecord.record?.manifestPath,
15402
16198
  planning: context.planning ?? existingRecord.record?.planning
15403
16199
  });
15404
16200
  }
15405
16201
  function inspectRemotePrdBranch(repoRoot2, prdRef) {
15406
- const result = spawnSync2("git", ["ls-remote", "--heads", "origin", prdRef], {
16202
+ const result = spawnSync3("git", ["ls-remote", "--heads", "origin", prdRef], {
15407
16203
  cwd: repoRoot2,
15408
16204
  encoding: "utf8"
15409
16205
  });
@@ -15428,7 +16224,7 @@ function inspectRemotePrdBranch(repoRoot2, prdRef) {
15428
16224
  }
15429
16225
  function ensureAdoptableExistingPrdBranch(repoRoot2, prdRef, prepareMergeCommit) {
15430
16226
  const branchRef = `refs/remotes/origin/${prdRef}`;
15431
- const result = spawnSync2(
16227
+ const result = spawnSync3(
15432
16228
  "git",
15433
16229
  ["merge-base", "--is-ancestor", prepareMergeCommit, branchRef],
15434
16230
  {
@@ -15558,7 +16354,7 @@ async function createOrReusePlanningPr(options) {
15558
16354
  });
15559
16355
  }
15560
16356
  function fetchOriginDev(repoRoot2) {
15561
- const fetchResult = spawnSync2("git", ["fetch", "origin", "dev"], {
16357
+ const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
15562
16358
  cwd: repoRoot2,
15563
16359
  encoding: "utf8"
15564
16360
  });
@@ -15575,7 +16371,7 @@ function fetchOriginDev(repoRoot2) {
15575
16371
  offendingPaths: []
15576
16372
  };
15577
16373
  }
15578
- const revParseResult = spawnSync2("git", ["rev-parse", "origin/dev"], {
16374
+ const revParseResult = spawnSync3("git", ["rev-parse", "origin/dev"], {
15579
16375
  cwd: repoRoot2,
15580
16376
  encoding: "utf8"
15581
16377
  });
@@ -15595,7 +16391,7 @@ function fetchOriginDev(repoRoot2) {
15595
16391
  return { ok: true, startBaseCommit: revParseResult.stdout.trim() };
15596
16392
  }
15597
16393
  function fetchPrdBranch(repoRoot2, prdRef) {
15598
- const result = spawnSync2("git", ["fetch", "origin", prdRef], {
16394
+ const result = spawnSync3("git", ["fetch", "origin", prdRef], {
15599
16395
  cwd: repoRoot2,
15600
16396
  encoding: "utf8"
15601
16397
  });
@@ -15615,7 +16411,7 @@ function fetchPrdBranch(repoRoot2, prdRef) {
15615
16411
  return { ok: true };
15616
16412
  }
15617
16413
  function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startBaseCommit) {
15618
- const result = spawnSync2(
16414
+ const result = spawnSync3(
15619
16415
  "git",
15620
16416
  ["merge-base", "--is-ancestor", prepareMergeCommit, startBaseRef],
15621
16417
  {
@@ -15642,7 +16438,7 @@ function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startB
15642
16438
  return { ok: true };
15643
16439
  }
15644
16440
  async function ensurePrdBranchPublished(repoRoot2, prdRef, startBaseCommit) {
15645
- const pushResult = spawnSync2(
16441
+ const pushResult = spawnSync3(
15646
16442
  "git",
15647
16443
  ["push", "origin", `${startBaseCommit}:refs/heads/${prdRef}`],
15648
16444
  {
@@ -15695,7 +16491,7 @@ function buildPlanningPrBody(options) {
15695
16491
  ].join("\n");
15696
16492
  }
15697
16493
  function runGitOrThrow(cwd, args, label) {
15698
- const result = spawnSync2("git", args, { cwd, encoding: "utf8" });
16494
+ const result = spawnSync3("git", args, { cwd, encoding: "utf8" });
15699
16495
  if (result.status !== 0) {
15700
16496
  throw new Error(
15701
16497
  `Failed to ${label}: ${[
@@ -15707,7 +16503,7 @@ function runGitOrThrow(cwd, args, label) {
15707
16503
  }
15708
16504
  }
15709
16505
  function validatePrepareAutoMergeDevCheckout(repoRoot2) {
15710
- const branchResult = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
16506
+ const branchResult = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
15711
16507
  cwd: repoRoot2,
15712
16508
  encoding: "utf8"
15713
16509
  });
@@ -15738,7 +16534,7 @@ function validatePrepareAutoMergeDevCheckout(repoRoot2) {
15738
16534
  function syncLocalDevToOriginDev(repoRoot2) {
15739
16535
  const branchResult = validatePrepareAutoMergeDevCheckout(repoRoot2);
15740
16536
  if (!branchResult.ok) return branchResult;
15741
- const fetchResult = spawnSync2("git", ["fetch", "origin", "dev"], {
16537
+ const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
15742
16538
  cwd: repoRoot2,
15743
16539
  encoding: "utf8"
15744
16540
  });
@@ -15754,7 +16550,7 @@ function syncLocalDevToOriginDev(repoRoot2) {
15754
16550
  offendingPaths: []
15755
16551
  };
15756
16552
  }
15757
- const resetResult = spawnSync2("git", ["reset", "--hard", "origin/dev"], {
16553
+ const resetResult = spawnSync3("git", ["reset", "--hard", "origin/dev"], {
15758
16554
  cwd: repoRoot2,
15759
16555
  encoding: "utf8"
15760
16556
  });
@@ -15798,14 +16594,14 @@ function isArchitecturePlanningDiffPath(path9) {
15798
16594
  return path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/");
15799
16595
  }
15800
16596
  async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles) {
15801
- const remoteResult = spawnSync2("git", ["remote", "get-url", "origin"], {
16597
+ const remoteResult = spawnSync3("git", ["remote", "get-url", "origin"], {
15802
16598
  cwd: repoRoot2,
15803
16599
  encoding: "utf8"
15804
16600
  });
15805
16601
  if (remoteResult.status !== 0) {
15806
16602
  return;
15807
16603
  }
15808
- const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-planning-"));
16604
+ const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-planning-"));
15809
16605
  try {
15810
16606
  runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
15811
16607
  runGitOrThrow(
@@ -15819,8 +16615,8 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
15819
16615
  "create planning branch"
15820
16616
  );
15821
16617
  for (const changedFile of changedFiles) {
15822
- const sourcePath = join20(repoRoot2, changedFile);
15823
- const targetPath = join20(worktreePath, changedFile);
16618
+ const sourcePath = join21(repoRoot2, changedFile);
16619
+ const targetPath = join21(worktreePath, changedFile);
15824
16620
  mkdirSync11(dirname5(targetPath), { recursive: true });
15825
16621
  cpSync(sourcePath, targetPath, { recursive: true });
15826
16622
  }
@@ -15835,13 +16631,13 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
15835
16631
  "commit planning diff"
15836
16632
  );
15837
16633
  } finally {
15838
- spawnSync2("git", ["worktree", "remove", "--force", worktreePath], {
16634
+ spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
15839
16635
  cwd: repoRoot2,
15840
16636
  encoding: "utf8"
15841
16637
  });
15842
16638
  rmSync3(worktreePath, { recursive: true, force: true });
15843
16639
  }
15844
- const pushResult = spawnSync2(
16640
+ const pushResult = spawnSync3(
15845
16641
  "git",
15846
16642
  ["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
15847
16643
  {
@@ -15977,7 +16773,7 @@ function validateStartPrepareReceipt(prdRef, record, options = {}) {
15977
16773
  function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
15978
16774
  for (const artifactPath of listManifestArtifactPaths(repoRoot2, manifest)) {
15979
16775
  const repoRelativePath = toRepoRelativePath2(repoRoot2, artifactPath);
15980
- const result = spawnSync2(
16776
+ const result = spawnSync3(
15981
16777
  "git",
15982
16778
  ["show", `origin/dev:${repoRelativePath}`],
15983
16779
  {
@@ -16003,7 +16799,7 @@ function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
16003
16799
  return { ok: true };
16004
16800
  }
16005
16801
  function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
16006
- const result = spawnSync2("git", ["status", "--porcelain=v1", "-uall"], {
16802
+ const result = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
16007
16803
  cwd: repoRoot2,
16008
16804
  encoding: "utf8"
16009
16805
  });
@@ -16058,11 +16854,11 @@ function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
16058
16854
  };
16059
16855
  }
16060
16856
  function collectObservedReconciliationDirtyPaths(options) {
16061
- const statusResult = spawnSync2("git", ["status", "--porcelain=v1", "-uall"], {
16857
+ const statusResult = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
16062
16858
  cwd: options.worktreeCwd,
16063
16859
  encoding: "utf8"
16064
16860
  });
16065
- const diffResult = spawnSync2("git", ["diff", "--name-status"], {
16861
+ const diffResult = spawnSync3("git", ["diff", "--name-status"], {
16066
16862
  cwd: options.worktreeCwd,
16067
16863
  encoding: "utf8"
16068
16864
  });
@@ -16096,13 +16892,13 @@ function collectObservedReconciliationDirtyPaths(options) {
16096
16892
  };
16097
16893
  }
16098
16894
  function gitRefExists(repoRoot2, ref) {
16099
- return spawnSync2("git", ["rev-parse", "--verify", "--quiet", ref], {
16895
+ return spawnSync3("git", ["rev-parse", "--verify", "--quiet", ref], {
16100
16896
  cwd: repoRoot2,
16101
16897
  encoding: "utf8"
16102
16898
  }).status === 0;
16103
16899
  }
16104
16900
  function collectGitDiffNames(repoRoot2, baseRef, headRef) {
16105
- const result = spawnSync2("git", ["diff", "--name-only", baseRef, headRef], {
16901
+ const result = spawnSync3("git", ["diff", "--name-only", baseRef, headRef], {
16106
16902
  cwd: repoRoot2,
16107
16903
  encoding: "utf8"
16108
16904
  });
@@ -19163,8 +19959,8 @@ function formatChecks2(checks) {
19163
19959
  init_common();
19164
19960
 
19165
19961
  // execution/sandcastle-execution.ts
19166
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync8 } from "fs";
19167
- import { join as join22 } from "path";
19962
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync9 } from "fs";
19963
+ import { join as join23 } from "path";
19168
19964
  import { createWorktree, opencode } from "@ai-hero/sandcastle";
19169
19965
  import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
19170
19966
 
@@ -19173,10 +19969,10 @@ init_common();
19173
19969
  import { mkdtempSync as mkdtempSync2 } from "fs";
19174
19970
  import { writeFile as writeFile3 } from "fs/promises";
19175
19971
  import { tmpdir as tmpdir2 } from "os";
19176
- import { dirname as dirname6, join as join21 } from "path";
19972
+ import { dirname as dirname6, join as join22 } from "path";
19177
19973
  async function writeExecutionArtifacts(worktreePath, artifacts) {
19178
19974
  for (const artifact of artifacts) {
19179
- const filePath = join21(worktreePath, artifact.path);
19975
+ const filePath = join22(worktreePath, artifact.path);
19180
19976
  await ensureDir(dirname6(filePath));
19181
19977
  await writeFile3(filePath, artifact.content, "utf-8");
19182
19978
  }
@@ -19188,7 +19984,7 @@ import path7 from "path";
19188
19984
 
19189
19985
  // execution/sandbox-image.ts
19190
19986
  import { createHash as createHash4 } from "crypto";
19191
- import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
19987
+ import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
19192
19988
  import path6 from "path";
19193
19989
  function sandboxImageName(repoRoot2) {
19194
19990
  const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
@@ -19198,7 +19994,7 @@ function sandboxImageName(repoRoot2) {
19198
19994
  if (!existsSync19(dockerfilePath)) {
19199
19995
  return `sandcastle:${baseName}`;
19200
19996
  }
19201
- const fingerprint = createHash4("sha256").update(readFileSync17(dockerfilePath)).digest("hex").slice(0, 8);
19997
+ const fingerprint = createHash4("sha256").update(readFileSync18(dockerfilePath)).digest("hex").slice(0, 8);
19202
19998
  return `sandcastle:${baseName}-${fingerprint}`;
19203
19999
  }
19204
20000
 
@@ -19458,17 +20254,32 @@ function parseToolCallArgs(formattedArgs) {
19458
20254
  return { ok: false };
19459
20255
  }
19460
20256
  }
20257
+ var SUMMARIZED_TOOL_FIELDS = {
20258
+ write: { content: "contentCount" },
20259
+ edit: { oldString: "oldCount", newString: "newCount" }
20260
+ };
20261
+ var SUMMARIZED_TOOL_ARRAY_FIELDS = {
20262
+ todowrite: { todos: "todoCount" }
20263
+ };
19461
20264
  function summarizeToolCallArgs(name, args) {
19462
20265
  if (!isPlainObject(args)) {
19463
20266
  return args;
19464
20267
  }
19465
- if (name.toLowerCase() !== "write") {
20268
+ const fields = SUMMARIZED_TOOL_FIELDS[name.toLowerCase()];
20269
+ const arrayFields = SUMMARIZED_TOOL_ARRAY_FIELDS[name.toLowerCase()];
20270
+ if (!fields && !arrayFields) {
19466
20271
  return args;
19467
20272
  }
19468
20273
  const summarizedArgs = {};
19469
20274
  for (const [key, value] of Object.entries(args)) {
19470
- if (key === "content" && typeof value === "string") {
19471
- summarizedArgs.contentCount = value.length;
20275
+ const mappedKey = fields?.[key];
20276
+ if (mappedKey && typeof value === "string") {
20277
+ summarizedArgs[mappedKey] = value.length;
20278
+ continue;
20279
+ }
20280
+ const mappedArrayKey = arrayFields?.[key];
20281
+ if (mappedArrayKey && Array.isArray(value)) {
20282
+ summarizedArgs[mappedArrayKey] = value.length;
19472
20283
  continue;
19473
20284
  }
19474
20285
  summarizedArgs[key] = value;
@@ -19479,13 +20290,13 @@ function isPlainObject(value) {
19479
20290
  return typeof value === "object" && value !== null && !Array.isArray(value);
19480
20291
  }
19481
20292
  function savePromptToFile(repoRoot2, stage, iteration, prompt) {
19482
- const promptsDir = join22(repoRoot2, ".pourkit", ".tmp", "prompts");
20293
+ const promptsDir = join23(repoRoot2, ".pourkit", ".tmp", "prompts");
19483
20294
  mkdirSync12(promptsDir, { recursive: true });
19484
20295
  const timestamp2 = Date.now();
19485
20296
  const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
19486
20297
  const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
19487
- const filePath = join22(promptsDir, filename);
19488
- writeFileSync8(filePath, prompt, "utf-8");
20298
+ const filePath = join23(promptsDir, filename);
20299
+ writeFileSync9(filePath, prompt, "utf-8");
19489
20300
  }
19490
20301
 
19491
20302
  // cli.ts
@@ -20358,11 +21169,11 @@ function createCliProgram(version) {
20358
21169
  return program;
20359
21170
  }
20360
21171
  async function resolveCliVersion() {
20361
- if (isPackageVersion("0.0.0-next-20260608072322")) {
20362
- return "0.0.0-next-20260608072322";
21172
+ if (isPackageVersion("0.0.0-next-20260608221925")) {
21173
+ return "0.0.0-next-20260608221925";
20363
21174
  }
20364
- if (isReleaseVersion("0.0.0-next-20260608072322")) {
20365
- return "0.0.0-next-20260608072322";
21175
+ if (isReleaseVersion("0.0.0-next-20260608221925")) {
21176
+ return "0.0.0-next-20260608221925";
20366
21177
  }
20367
21178
  try {
20368
21179
  const root = repoRoot();