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

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
@@ -376,6 +376,7 @@ var VerificationCommandSchema = z.object({
376
376
  command: d.command,
377
377
  label: d.label && d.label.trim() !== "" ? d.label : void 0
378
378
  }));
379
+ var PrdRunModeSchema = z.enum(["github", "local"]);
379
380
  var QueueConfigSchema = z.object({
380
381
  loop: z.boolean().optional()
381
382
  }).strict();
@@ -422,6 +423,7 @@ var ReviewRefactorLoopStrategySchema = z.object({
422
423
  maxAttempts: z.number().int().positive()
423
424
  }).strict(),
424
425
  prdRun: z.object({
426
+ mode: PrdRunModeSchema.optional(),
425
427
  // Uses promptTemplate (canonical StageAgentConfig field), not prompt as Issue contract may suggest
426
428
  finalReview: StageAgentConfigSchema,
427
429
  reconciliation: StageAgentConfigSchema.optional()
@@ -715,6 +717,7 @@ function parseConfig(raw) {
715
717
  },
716
718
  ...t.strategy.prdRun ? {
717
719
  prdRun: {
720
+ ...t.strategy.prdRun.mode ? { mode: t.strategy.prdRun.mode } : {},
718
721
  finalReview: t.strategy.prdRun.finalReview,
719
722
  ...t.strategy.prdRun.reconciliation ? { reconciliation: t.strategy.prdRun.reconciliation } : {}
720
723
  }
@@ -771,14 +774,24 @@ function assertKnownKeys(value, path9, knownKeys) {
771
774
  function getVerificationCommands(target) {
772
775
  return target.strategy.verify?.commands ?? [];
773
776
  }
777
+ function resolvePrdRunMode(target, opts) {
778
+ if (opts?.localOverride === true) {
779
+ return { mode: "local", source: "cli-override" };
780
+ }
781
+ const configMode = target.strategy.prdRun?.mode;
782
+ if (configMode) {
783
+ return { mode: configMode, source: "target-config" };
784
+ }
785
+ return { mode: "github", source: "default" };
786
+ }
774
787
  async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
775
- const { existsSync: existsSync19 } = await import("fs");
788
+ const { existsSync: existsSync20 } = await import("fs");
776
789
  const { mkdir: mkdir6, writeFile: writeFile4, rm } = await import("fs/promises");
777
790
  const { join: pjoin, basename } = await import("path");
778
791
  const { pathToFileURL: pathToFileURL2 } = await import("url");
779
792
  const { build } = await import("esbuild");
780
793
  const configPath = pjoin(repoRoot2, configFileName);
781
- if (!existsSync19(configPath)) {
794
+ if (!existsSync20(configPath)) {
782
795
  throw new Error(
783
796
  `No config file found at ${configPath}. Create a ${configFileName} that exports a default PourkitConfig.`
784
797
  );
@@ -1142,6 +1155,77 @@ function prepareArtifactPath(artifactPath) {
1142
1155
  rmSync(artifactPath, { recursive: true, force: true });
1143
1156
  }
1144
1157
 
1158
+ // commands/run-verification.ts
1159
+ init_common();
1160
+ function buildRunVerificationCommand(target) {
1161
+ return `pourkit run-verification --target ${target.name}`;
1162
+ }
1163
+ async function runVerificationCommand(command, cwd, logger) {
1164
+ try {
1165
+ const result = await execCapture("bash", ["-lc", command.command], {
1166
+ cwd,
1167
+ logger,
1168
+ label: command.label
1169
+ });
1170
+ return {
1171
+ label: command.label,
1172
+ command: command.command,
1173
+ ok: true,
1174
+ stdout: result.stdout,
1175
+ stderr: result.stderr
1176
+ };
1177
+ } catch (error) {
1178
+ return {
1179
+ label: command.label,
1180
+ command: command.command,
1181
+ ok: false,
1182
+ error: error instanceof Error ? error.message : String(error)
1183
+ };
1184
+ }
1185
+ }
1186
+ async function runVerificationCommands(options) {
1187
+ const commands = getVerificationCommands(options.target);
1188
+ const diagnostics = [];
1189
+ if (commands.length === 0) {
1190
+ diagnostics.push(
1191
+ `No verification commands configured for target "${options.target.name}".`
1192
+ );
1193
+ return {
1194
+ ok: true,
1195
+ target: options.target.name,
1196
+ commands: [],
1197
+ diagnostics
1198
+ };
1199
+ }
1200
+ const results = [];
1201
+ for (const command of commands) {
1202
+ const result = await runVerificationCommand(
1203
+ command,
1204
+ options.cwd,
1205
+ options.logger
1206
+ );
1207
+ results.push(result);
1208
+ if (!result.ok) {
1209
+ diagnostics.push(`Verification failed for "${command.label}".`);
1210
+ return {
1211
+ ok: false,
1212
+ target: options.target.name,
1213
+ commands: results,
1214
+ diagnostics
1215
+ };
1216
+ }
1217
+ }
1218
+ diagnostics.push(
1219
+ `Verification passed for target "${options.target.name}".`
1220
+ );
1221
+ return {
1222
+ ok: true,
1223
+ target: options.target.name,
1224
+ commands: results,
1225
+ diagnostics
1226
+ };
1227
+ }
1228
+
1145
1229
  // shared/run-context.ts
1146
1230
  var RUN_CONTEXT_PATH_IN_WORKTREE = ".pourkit/.tmp/run-context.md";
1147
1231
  var ALL_RUN_CONTEXT_SECTIONS = [
@@ -1268,6 +1352,7 @@ function buildRunContextMarkdown(options) {
1268
1352
  if (sections.includes("verification-commands")) {
1269
1353
  parts.push(
1270
1354
  ...renderCommandList(
1355
+ target,
1271
1356
  getVerificationCommands(target),
1272
1357
  "Verification Commands"
1273
1358
  )
@@ -1289,13 +1374,26 @@ function buildRunContextMarkdown(options) {
1289
1374
  }
1290
1375
  return parts.join("\n");
1291
1376
  }
1292
- function renderCommandList(commands, heading) {
1377
+ function renderCommandList(target, commands, heading) {
1293
1378
  if (commands.length === 0) {
1294
- return [`## ${heading}`, "", "(none)", ""];
1379
+ return [
1380
+ `## ${heading}`,
1381
+ "",
1382
+ `Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
1383
+ "",
1384
+ "Underlying project commands:",
1385
+ "",
1386
+ "(none configured)",
1387
+ ""
1388
+ ];
1295
1389
  }
1296
1390
  return [
1297
1391
  `## ${heading}`,
1298
1392
  "",
1393
+ `Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
1394
+ "",
1395
+ "Underlying project commands:",
1396
+ "",
1299
1397
  "Run these commands from the repository root exactly as written. Do not substitute equivalent scripts from nested package.json files.",
1300
1398
  "",
1301
1399
  ...commands.map((command) => `- ${command.label}: \`${command.command}\``),
@@ -2079,7 +2177,7 @@ ${criteriaBlock}
2079
2177
 
2080
2178
  Write your review to: ${artifactPathInWorktree}
2081
2179
 
2082
- Before handoff, run: npm run pourkit:validate-reviewer -- ${artifactPathInWorktree} --iteration ${iteration}${priorRefactorArtifacts ? " --prior-refactor-artifacts" : ""}
2180
+ Before handoff, run: pourkit validate-artifact reviewer ${artifactPathInWorktree} --iteration ${iteration}${priorRefactorArtifacts ? " --prior-refactor-artifacts" : ""}
2083
2181
 
2084
2182
  Do not provide a separate chat response. The runner only reads the file above.
2085
2183
 
@@ -2615,7 +2713,7 @@ ${latestReview.trimEnd()}
2615
2713
 
2616
2714
  Write your refactor artifact to: ${artifactPathInWorktree}
2617
2715
 
2618
- Before handoff, run: npm run pourkit:validate-refactor -- ${artifactPathInWorktree} --iteration ${iteration}${findingArgs}
2716
+ Before handoff, run: pourkit validate-artifact refactor ${artifactPathInWorktree} --iteration ${iteration}${findingArgs}
2619
2717
 
2620
2718
  When you are done, finish with <promise>COMPLETE</promise>.`);
2621
2719
  });
@@ -4879,8 +4977,8 @@ function validateLocalTriage(prdPath, issuePaths) {
4879
4977
  }
4880
4978
 
4881
4979
  // commands/issue-run.ts
4882
- import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
4883
- import { join as join13 } from "path";
4980
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
4981
+ import { join as join14 } from "path";
4884
4982
 
4885
4983
  // pr/templates.ts
4886
4984
  init_common();
@@ -5581,7 +5679,7 @@ async function runFailureResolutionAgent(options) {
5581
5679
  "",
5582
5680
  "Allowed decisions: " + packet.allowedDecisions.join(", "),
5583
5681
  "",
5584
- `Before handoff, run: npm run pourkit:validate-failure-resolution -- ${artifactPath} ${packet.allowedDecisions.map((decision) => `--allowed-decision ${decision}`).join(" ")}`
5682
+ `Before handoff, run: pourkit validate-artifact failure-resolution ${artifactPath} ${packet.allowedDecisions.map((decision) => `--allowed-decision ${decision}`).join(" ")}`
5585
5683
  ].join("\n");
5586
5684
  const retryResult = await executeWithMissingOrEmptyArtifactRetry({
5587
5685
  executionProvider,
@@ -5853,7 +5951,7 @@ Rules:
5853
5951
 
5854
5952
  Write your finalizer output to: ${artifactPathInWorktree}
5855
5953
 
5856
- Before handoff, run: npm run pourkit:validate-finalizer -- ${artifactPathInWorktree}`;
5954
+ Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktree}`;
5857
5955
  }
5858
5956
 
5859
5957
  // commands/pr-description-agent.ts
@@ -6054,6 +6152,293 @@ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
6054
6152
  });
6055
6153
  }
6056
6154
 
6155
+ // prd-run/local-merge-coordinator.ts
6156
+ import { execFileSync as execFileSync2 } from "child_process";
6157
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
6158
+ import { join as join13 } from "path";
6159
+
6160
+ // prd-run/local-branches.ts
6161
+ import { execFileSync } from "child_process";
6162
+ function getLocalPrdBranchName(prdId) {
6163
+ return `local/${prdId}`;
6164
+ }
6165
+ var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
6166
+ var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
6167
+ function validateLocalBranchName(name) {
6168
+ if (!name || name.length === 0) {
6169
+ return {
6170
+ ok: false,
6171
+ failureCode: "invalid_format",
6172
+ message: "Branch name is empty."
6173
+ };
6174
+ }
6175
+ if (isProtectedBranch(name)) {
6176
+ return {
6177
+ ok: false,
6178
+ failureCode: "protected_branch",
6179
+ message: `Branch "${name}" is protected.`
6180
+ };
6181
+ }
6182
+ if (!LOCAL_BRANCH_PATTERN.test(name)) {
6183
+ return {
6184
+ ok: false,
6185
+ failureCode: "invalid_format",
6186
+ message: `Branch "${name}" does not match the local branch pattern.`
6187
+ };
6188
+ }
6189
+ return { ok: true };
6190
+ }
6191
+ function isProtectedBranch(name) {
6192
+ return PROTECTED_BRANCHES.has(name);
6193
+ }
6194
+ function hasRemoteBackedCollision(localName, repoRoot2) {
6195
+ const match = localName.match(/^local\/(PRD-\d{4})/);
6196
+ if (!match) {
6197
+ return Promise.resolve({
6198
+ ok: false,
6199
+ failureCode: "invalid_format",
6200
+ message: `Cannot extract PRD ref from "${localName}".`
6201
+ });
6202
+ }
6203
+ const prdRef = match[1];
6204
+ try {
6205
+ execFileSync(
6206
+ "git",
6207
+ ["show-ref", "--verify", "--quiet", `refs/heads/${prdRef}`],
6208
+ {
6209
+ cwd: repoRoot2 ?? process.cwd(),
6210
+ encoding: "utf8",
6211
+ stdio: "pipe"
6212
+ }
6213
+ );
6214
+ return Promise.resolve({
6215
+ ok: false,
6216
+ failureCode: "remote_backed_collision",
6217
+ message: `Branch "${prdRef}" exists locally.`
6218
+ });
6219
+ } catch {
6220
+ }
6221
+ try {
6222
+ execFileSync(
6223
+ "git",
6224
+ ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${prdRef}`],
6225
+ {
6226
+ cwd: repoRoot2 ?? process.cwd(),
6227
+ encoding: "utf8",
6228
+ stdio: "pipe"
6229
+ }
6230
+ );
6231
+ return Promise.resolve({
6232
+ ok: false,
6233
+ failureCode: "remote_backed_collision",
6234
+ message: `Remote branch "origin/${prdRef}" exists.`
6235
+ });
6236
+ } catch {
6237
+ }
6238
+ return Promise.resolve({ ok: true });
6239
+ }
6240
+
6241
+ // prd-run/local-merge-coordinator.ts
6242
+ function getLocalStorePath(repoRoot2, prdId) {
6243
+ return join13(repoRoot2, ".pourkit", "local-prd-runs", prdId);
6244
+ }
6245
+ function getMergeReceiptPath(repoRoot2, prdId, issueId) {
6246
+ return join13(
6247
+ getLocalStorePath(repoRoot2, prdId),
6248
+ "merge-receipts",
6249
+ `${issueId}.json`
6250
+ );
6251
+ }
6252
+ function getIssueArtifactPath(repoRoot2, prdId, issueId) {
6253
+ return join13(getLocalStorePath(repoRoot2, prdId), "issues", `${issueId}.json`);
6254
+ }
6255
+ function readIssueBranchName(repoRoot2, prdId, issueId) {
6256
+ const issuePath = getIssueArtifactPath(repoRoot2, prdId, issueId);
6257
+ if (!existsSync9(issuePath)) return null;
6258
+ try {
6259
+ const content = readFileSync11(issuePath, "utf-8");
6260
+ const parsed = JSON.parse(content);
6261
+ return typeof parsed.branchName === "string" && parsed.branchName ? parsed.branchName : null;
6262
+ } catch {
6263
+ return null;
6264
+ }
6265
+ }
6266
+ async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
6267
+ const root = repoRoot2 ?? process.cwd();
6268
+ const receiptPath = getMergeReceiptPath(root, prdId, issueId);
6269
+ if (!existsSync9(receiptPath)) return null;
6270
+ try {
6271
+ const content = readFileSync11(receiptPath, "utf-8");
6272
+ const parsed = JSON.parse(content);
6273
+ if (typeof parsed.prdId === "string" && typeof parsed.issueId === "string" && typeof parsed.stage === "string" && typeof parsed.sourceBranch === "string" && typeof parsed.localPrdBranch === "string" && typeof parsed.mergeCommit === "string" && typeof parsed.completedAt === "string") {
6274
+ return parsed;
6275
+ }
6276
+ return null;
6277
+ } catch {
6278
+ return null;
6279
+ }
6280
+ }
6281
+ async function squashMergeLocalIssue(prdId, issueId, input, repoRoot2) {
6282
+ const root = repoRoot2 ?? process.cwd();
6283
+ const receiptPath = getMergeReceiptPath(root, prdId, issueId);
6284
+ if (existsSync9(receiptPath)) {
6285
+ try {
6286
+ const existing = JSON.parse(
6287
+ readFileSync11(receiptPath, "utf-8")
6288
+ );
6289
+ if (existing.prdId === prdId && existing.issueId === issueId && existing.mergeCommit) {
6290
+ return {
6291
+ ok: false,
6292
+ failureCode: "already_merged",
6293
+ repairGuidance: `Issue ${issueId} was already merged into ${existing.localPrdBranch}. Merge commit: ${existing.mergeCommit}`
6294
+ };
6295
+ }
6296
+ return {
6297
+ ok: false,
6298
+ failureCode: "invalid_receipt",
6299
+ repairGuidance: `Merge receipt for ${issueId} belongs to different prd/issue. Remove it manually: ${receiptPath}`
6300
+ };
6301
+ } catch {
6302
+ return {
6303
+ ok: false,
6304
+ failureCode: "invalid_receipt",
6305
+ repairGuidance: `Merge receipt for ${issueId} is corrupted. Remove it manually: ${receiptPath}`
6306
+ };
6307
+ }
6308
+ }
6309
+ const sourceBranch = input?.sourceBranch ?? readIssueBranchName(root, prdId, issueId);
6310
+ if (!sourceBranch) {
6311
+ return {
6312
+ ok: false,
6313
+ failureCode: "not_found",
6314
+ repairGuidance: `No source branch found for issue ${issueId} under PRD ${prdId}. Ensure the issue artifact exists with a valid branchName field.`
6315
+ };
6316
+ }
6317
+ try {
6318
+ execFileSync2(
6319
+ "git",
6320
+ ["show-ref", "--verify", "--quiet", `refs/heads/${sourceBranch}`],
6321
+ { cwd: root, encoding: "utf8", stdio: "pipe" }
6322
+ );
6323
+ } catch {
6324
+ return {
6325
+ ok: false,
6326
+ failureCode: "not_found",
6327
+ repairGuidance: `Source branch "${sourceBranch}" does not exist locally. Check that the branch was created and not deleted.`
6328
+ };
6329
+ }
6330
+ const targetBranch = getLocalPrdBranchName(prdId);
6331
+ try {
6332
+ execFileSync2(
6333
+ "git",
6334
+ ["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
6335
+ { cwd: root, encoding: "utf8", stdio: "pipe" }
6336
+ );
6337
+ } catch {
6338
+ return {
6339
+ ok: false,
6340
+ failureCode: "not_found",
6341
+ repairGuidance: `Local PRD branch "${targetBranch}" does not exist. Run \`prd-run start\` first to create it.`
6342
+ };
6343
+ }
6344
+ try {
6345
+ execFileSync2("git", ["checkout", targetBranch], {
6346
+ cwd: root,
6347
+ encoding: "utf8",
6348
+ stdio: "pipe"
6349
+ });
6350
+ let preMergeHead;
6351
+ try {
6352
+ preMergeHead = execFileSync2("git", ["rev-parse", "HEAD"], {
6353
+ cwd: root,
6354
+ encoding: "utf8",
6355
+ stdio: "pipe"
6356
+ }).trim();
6357
+ } catch {
6358
+ return {
6359
+ ok: false,
6360
+ failureCode: "merge_error",
6361
+ repairGuidance: "Failed to read current HEAD before merge."
6362
+ };
6363
+ }
6364
+ execFileSync2("git", ["merge", "--squash", sourceBranch], {
6365
+ cwd: root,
6366
+ encoding: "utf8",
6367
+ stdio: "pipe"
6368
+ });
6369
+ if (input) {
6370
+ execFileSync2(
6371
+ "git",
6372
+ ["commit", "-m", input.finalizerTitle, "-m", input.finalizerBody],
6373
+ { cwd: root, encoding: "utf8", stdio: "pipe" }
6374
+ );
6375
+ } else {
6376
+ execFileSync2(
6377
+ "git",
6378
+ ["commit", "-m", `Squash merge ${sourceBranch} into ${targetBranch}`],
6379
+ { cwd: root, encoding: "utf8", stdio: "pipe" }
6380
+ );
6381
+ }
6382
+ const revParseResult = execFileSync2("git", ["rev-parse", "HEAD"], {
6383
+ cwd: root,
6384
+ encoding: "utf8",
6385
+ stdio: "pipe"
6386
+ });
6387
+ const mergeCommit = revParseResult.trim();
6388
+ const receipt = {
6389
+ prdId,
6390
+ issueId,
6391
+ stage: "child-issue",
6392
+ sourceBranch,
6393
+ localPrdBranch: targetBranch,
6394
+ mergeCommit,
6395
+ finalizerArtifactPath: input?.finalizerArtifactPath ?? "",
6396
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
6397
+ };
6398
+ try {
6399
+ const receiptsDir = join13(
6400
+ root,
6401
+ ".pourkit",
6402
+ "local-prd-runs",
6403
+ prdId,
6404
+ "merge-receipts"
6405
+ );
6406
+ mkdirSync7(receiptsDir, { recursive: true });
6407
+ writeFileSync4(receiptPath, JSON.stringify(receipt, null, 2), "utf-8");
6408
+ } catch {
6409
+ try {
6410
+ execFileSync2("git", ["reset", "--hard", preMergeHead], {
6411
+ cwd: root,
6412
+ encoding: "utf8",
6413
+ stdio: "pipe"
6414
+ });
6415
+ } catch {
6416
+ }
6417
+ return {
6418
+ ok: false,
6419
+ failureCode: "receipt_error",
6420
+ repairGuidance: "Failed to write merge receipt. Check disk space and permissions on .pourkit/local-prd-runs/. The merge commit has been rolled back."
6421
+ };
6422
+ }
6423
+ return { ok: true, receipt };
6424
+ } catch (error) {
6425
+ const message = error instanceof Error ? error.message : String(error);
6426
+ if (message.toLowerCase().includes("conflict")) {
6427
+ return {
6428
+ ok: false,
6429
+ failureCode: "conflict",
6430
+ repairGuidance: "Merge conflict during squash. Resolve conflicts in the working tree, then retry."
6431
+ };
6432
+ }
6433
+ const stderr = error instanceof Error && "stderr" in error ? error.stderr : void 0;
6434
+ return {
6435
+ ok: false,
6436
+ failureCode: "merge_error",
6437
+ repairGuidance: stderr ? `Merge failed: ${stderr}` : `Merge failed: ${message}`
6438
+ };
6439
+ }
6440
+ }
6441
+
6057
6442
  // pr/pr-body.ts
6058
6443
  import { readFile as readFile2 } from "fs/promises";
6059
6444
  var DEFAULT_MANUAL_PR_BODY = `## Summary
@@ -6786,44 +7171,75 @@ async function completeIssueRun(options) {
6786
7171
  let prTitle = issue.title;
6787
7172
  let prBody;
6788
7173
  let finalizerResult;
7174
+ const isLocalModeForFinalizer = options.prdRunMode?.mode === "local";
6789
7175
  const finalizerFromState = worktreeState?.finalizer?.completed ? worktreeState.finalizer : null;
6790
7176
  if (finalizerFromState) {
6791
- if (finalizerFromState.title && finalizerFromState.body) {
6792
- prTitle = finalizerFromState.title;
6793
- prBody = finalizerFromState.body;
6794
- } else if (finalizerFromState.artifactPath) {
6795
- if (!existsSync9(finalizerFromState.artifactPath)) {
7177
+ try {
7178
+ if (finalizerFromState.title && finalizerFromState.body) {
7179
+ prTitle = finalizerFromState.title;
7180
+ prBody = finalizerFromState.body;
7181
+ } else if (finalizerFromState.artifactPath) {
7182
+ if (!existsSync10(finalizerFromState.artifactPath)) {
7183
+ throw new FinalizerFailure({
7184
+ message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
7185
+ });
7186
+ }
7187
+ const artifactContent = readFileSync12(
7188
+ finalizerFromState.artifactPath,
7189
+ "utf-8"
7190
+ );
7191
+ const parsed = parsePrDescription(artifactContent);
7192
+ prTitle = parsed.title;
7193
+ prBody = parsed.body;
7194
+ } else {
6796
7195
  throw new FinalizerFailure({
6797
- message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
7196
+ message: "Finalizer state is incomplete: missing title, body, and artifactPath"
6798
7197
  });
6799
7198
  }
6800
- const artifactContent = readFileSync11(
6801
- finalizerFromState.artifactPath,
6802
- "utf-8"
6803
- );
6804
- const parsed = parsePrDescription(artifactContent);
6805
- prTitle = parsed.title;
6806
- prBody = parsed.body;
6807
- } else {
6808
- throw new FinalizerFailure({
6809
- message: "Finalizer state is incomplete: missing title, body, and artifactPath"
6810
- });
7199
+ } catch (error) {
7200
+ if (isLocalModeForFinalizer) {
7201
+ return {
7202
+ branchName,
7203
+ target,
7204
+ issue,
7205
+ noOp: false,
7206
+ mode: "local",
7207
+ failureCode: "finalizer_failed",
7208
+ repairGuidance: error instanceof Error ? error.message : "Finalizer state is invalid"
7209
+ };
7210
+ }
7211
+ throw error;
6811
7212
  }
6812
7213
  } else {
6813
- finalizerResult = await runEffectAndMapExit(
6814
- runFinalizerAgent({
6815
- executionProvider,
6816
- config,
6817
- target,
6818
- issue,
6819
- builderBranch: branchName,
6820
- targetBaseBranch: effectiveBaseBranch,
6821
- worktreePath: executionResult.worktreePath,
6822
- reviewArtifactPath,
6823
- repoRoot: ROOT,
6824
- logger
6825
- })
6826
- );
7214
+ try {
7215
+ finalizerResult = await runEffectAndMapExit(
7216
+ runFinalizerAgent({
7217
+ executionProvider,
7218
+ config,
7219
+ target,
7220
+ issue,
7221
+ builderBranch: branchName,
7222
+ targetBaseBranch: effectiveBaseBranch,
7223
+ worktreePath: executionResult.worktreePath,
7224
+ reviewArtifactPath,
7225
+ repoRoot: ROOT,
7226
+ logger
7227
+ })
7228
+ );
7229
+ } catch (error) {
7230
+ if (isLocalModeForFinalizer) {
7231
+ return {
7232
+ branchName,
7233
+ target,
7234
+ issue,
7235
+ noOp: false,
7236
+ mode: "local",
7237
+ failureCode: "finalizer_failed",
7238
+ repairGuidance: error instanceof Error ? error.message : "Finalizer execution failed"
7239
+ };
7240
+ }
7241
+ throw error;
7242
+ }
6827
7243
  prTitle = finalizerResult.title;
6828
7244
  prBody = finalizerResult.body;
6829
7245
  }
@@ -6868,6 +7284,113 @@ async function completeIssueRun(options) {
6868
7284
  });
6869
7285
  }
6870
7286
  }
7287
+ const isLocalMode = options.prdRunMode?.mode === "local";
7288
+ if (isLocalMode) {
7289
+ const parsed = parseStackedIssue(issue.title, issue.body);
7290
+ const prdId = parsed.parentRef;
7291
+ if (!prdId) {
7292
+ return {
7293
+ branchName,
7294
+ target,
7295
+ issue,
7296
+ noOp: false,
7297
+ mode: "local",
7298
+ failureCode: "finalizer_failed",
7299
+ repairGuidance: "Local mode requires a PRD reference (## Parent) in the issue body or title. Add the parent PRD reference and retry."
7300
+ };
7301
+ }
7302
+ const finalizerFromWorktreeState = worktreeState?.finalizer;
7303
+ if (finalizerFromWorktreeState && !finalizerFromWorktreeState.completed) {
7304
+ return {
7305
+ branchName,
7306
+ target,
7307
+ issue,
7308
+ noOp: false,
7309
+ mode: "local",
7310
+ failureCode: "finalizer_failed",
7311
+ repairGuidance: "Finalizer did not complete on previous run. Re-run the issue finalizer and try again."
7312
+ };
7313
+ }
7314
+ if (!prTitle || !finalBody) {
7315
+ return {
7316
+ branchName,
7317
+ target,
7318
+ issue,
7319
+ noOp: false,
7320
+ mode: "local",
7321
+ failureCode: "finalizer_failed",
7322
+ repairGuidance: "Finalizer produced empty title or body. Re-run the issue finalizer and try again."
7323
+ };
7324
+ }
7325
+ const existingReceipt = await hasLocalIssueMergeReceipt(
7326
+ prdId,
7327
+ `issue-${issueNumber}`,
7328
+ ROOT
7329
+ );
7330
+ if (existingReceipt) {
7331
+ return {
7332
+ branchName,
7333
+ target,
7334
+ issue,
7335
+ noOp: false,
7336
+ mode: "local",
7337
+ failureCode: "already_merged",
7338
+ localPrdBranch: existingReceipt.localPrdBranch,
7339
+ mergeCommit: existingReceipt.mergeCommit,
7340
+ receiptPath: join14(
7341
+ ROOT,
7342
+ ".pourkit",
7343
+ "local-prd-runs",
7344
+ prdId,
7345
+ "merge-receipts",
7346
+ `issue-${issueNumber}.json`
7347
+ )
7348
+ };
7349
+ }
7350
+ const mergeResult = await squashMergeLocalIssue(
7351
+ prdId,
7352
+ `issue-${issueNumber}`,
7353
+ {
7354
+ finalizerTitle: prTitle,
7355
+ finalizerBody: finalBody,
7356
+ finalizerArtifactPath: worktreeState?.finalizer?.artifactPath ?? "",
7357
+ sourceBranch: branchName
7358
+ },
7359
+ ROOT
7360
+ );
7361
+ if (!mergeResult.ok) {
7362
+ return {
7363
+ branchName,
7364
+ target,
7365
+ issue,
7366
+ noOp: false,
7367
+ mode: "local",
7368
+ failureCode: mergeResult.failureCode,
7369
+ repairGuidance: mergeResult.repairGuidance ?? `Local squash-merge failed: ${mergeResult.failureCode}`
7370
+ };
7371
+ }
7372
+ await issueProvider.removeLabel(
7373
+ issueNumber,
7374
+ config.labels.agentInProgress
7375
+ );
7376
+ return {
7377
+ branchName,
7378
+ target,
7379
+ issue,
7380
+ noOp: false,
7381
+ mode: "local",
7382
+ localPrdBranch: getLocalPrdBranchName(prdId),
7383
+ mergeCommit: mergeResult.receipt.mergeCommit,
7384
+ receiptPath: join14(
7385
+ ROOT,
7386
+ ".pourkit",
7387
+ "local-prd-runs",
7388
+ prdId,
7389
+ "merge-receipts",
7390
+ `issue-${issueNumber}.json`
7391
+ )
7392
+ };
7393
+ }
6871
7394
  let pr;
6872
7395
  if (worktreeState?.pr?.merged) {
6873
7396
  mergeCompleted = true;
@@ -7309,7 +7832,7 @@ async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
7309
7832
  return { mode: "new", branchName, baseRef };
7310
7833
  }
7311
7834
  function issueWorktreePath(root, branchName) {
7312
- return join13(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
7835
+ return join14(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
7313
7836
  }
7314
7837
  function resolveRegisteredIssueWorktreePath(worktreeListPorcelain, root, branchName) {
7315
7838
  const branchWorktreePath = parseWorktreeListPorcelain(
@@ -7337,7 +7860,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
7337
7860
  }
7338
7861
  function loadBuilderPrompt(repoRoot2, promptTemplate) {
7339
7862
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
7340
- const promptBody = existsSync9(promptPath) ? readFileSync11(promptPath, "utf-8") : promptTemplate;
7863
+ const promptBody = existsSync10(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
7341
7864
  return appendProtectedWorkGuidance(`${promptBody}
7342
7865
 
7343
7866
  ## Shared Run Context
@@ -7780,29 +8303,29 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
7780
8303
  // commands/prd-run.ts
7781
8304
  import {
7782
8305
  cpSync,
7783
- existsSync as existsSync16,
8306
+ existsSync as existsSync17,
7784
8307
  lstatSync,
7785
- mkdirSync as mkdirSync10,
8308
+ mkdirSync as mkdirSync11,
7786
8309
  mkdtempSync,
7787
8310
  readdirSync as readdirSync6,
7788
- readFileSync as readFileSync15,
8311
+ readFileSync as readFileSync16,
7789
8312
  realpathSync,
7790
8313
  rmSync as rmSync3
7791
8314
  } from "fs";
7792
8315
  import { spawnSync as spawnSync2 } from "child_process";
7793
- import { dirname as dirname5, join as join19, relative as relative2 } from "path";
8316
+ import { dirname as dirname5, join as join20, relative as relative2 } from "path";
7794
8317
  import { tmpdir } from "os";
7795
8318
 
7796
8319
  // prd-run/state.ts
7797
8320
  import {
7798
- existsSync as existsSync10,
7799
- mkdirSync as mkdirSync7,
7800
- readFileSync as readFileSync12,
8321
+ existsSync as existsSync11,
8322
+ mkdirSync as mkdirSync8,
8323
+ readFileSync as readFileSync13,
7801
8324
  readdirSync as readdirSync4,
7802
- writeFileSync as writeFileSync4
8325
+ writeFileSync as writeFileSync5
7803
8326
  } from "fs";
7804
8327
  import { mkdir as mkdir4, readFile as readFile4, writeFile } from "fs/promises";
7805
- import { join as join14 } from "path";
8328
+ import { join as join15 } from "path";
7806
8329
  import { z as z3 } from "zod";
7807
8330
  var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
7808
8331
  var PrdRunRecordSchema = z3.object({
@@ -7946,12 +8469,12 @@ function normalizePrdRunRef2(ref) {
7946
8469
  function readPrdRun(repoRoot2, prdRef) {
7947
8470
  const normalized = normalizePrdRunRef2(prdRef);
7948
8471
  const recordPath = getRecordPath(repoRoot2, normalized);
7949
- if (!existsSync10(recordPath)) {
8472
+ if (!existsSync11(recordPath)) {
7950
8473
  return { record: null, diagnostics: [] };
7951
8474
  }
7952
8475
  try {
7953
8476
  const parsed = PrdRunRecordSchema.parse(
7954
- JSON.parse(readFileSync12(recordPath, "utf-8"))
8477
+ JSON.parse(readFileSync13(recordPath, "utf-8"))
7955
8478
  );
7956
8479
  return { record: parsed, diagnostics: [] };
7957
8480
  } catch (error) {
@@ -7965,8 +8488,8 @@ function readPrdRun(repoRoot2, prdRef) {
7965
8488
  }
7966
8489
  }
7967
8490
  function listPrdRuns(repoRoot2) {
7968
- const stateDir = join14(repoRoot2, PRD_RUN_STATE_DIR);
7969
- if (!existsSync10(stateDir)) {
8491
+ const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
8492
+ if (!existsSync11(stateDir)) {
7970
8493
  return { records: [], diagnostics: [] };
7971
8494
  }
7972
8495
  const records = [];
@@ -7975,10 +8498,10 @@ function listPrdRuns(repoRoot2) {
7975
8498
  (left, right) => left.name.localeCompare(right.name)
7976
8499
  )) {
7977
8500
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
7978
- const recordPath = join14(stateDir, entry.name);
8501
+ const recordPath = join15(stateDir, entry.name);
7979
8502
  try {
7980
8503
  const record = PrdRunRecordSchema.parse(
7981
- JSON.parse(readFileSync12(recordPath, "utf-8"))
8504
+ JSON.parse(readFileSync13(recordPath, "utf-8"))
7982
8505
  );
7983
8506
  records.push(record);
7984
8507
  } catch (error) {
@@ -7991,10 +8514,10 @@ function listPrdRuns(repoRoot2) {
7991
8514
  }
7992
8515
  function writePrdRunRecord(repoRoot2, record) {
7993
8516
  const normalized = normalizePrdRunRef2(record.prdRef);
7994
- const stateDir = join14(repoRoot2, PRD_RUN_STATE_DIR);
8517
+ const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
7995
8518
  const recordPath = getRecordPath(repoRoot2, normalized);
7996
- mkdirSync7(stateDir, { recursive: true });
7997
- writeFileSync4(
8519
+ mkdirSync8(stateDir, { recursive: true });
8520
+ writeFileSync5(
7998
8521
  recordPath,
7999
8522
  JSON.stringify({ ...record, prdRef: normalized }, null, 2),
8000
8523
  "utf-8"
@@ -8031,8 +8554,8 @@ var LocalPrdRunRecordSchema = z3.object({
8031
8554
  }),
8032
8555
  metadata: z3.record(z3.unknown())
8033
8556
  });
8034
- function getLocalStorePath(repoRoot2, prdId) {
8035
- return join14(
8557
+ function getLocalStorePath2(repoRoot2, prdId) {
8558
+ return join15(
8036
8559
  repoRoot2,
8037
8560
  LOCAL_PRD_RUN_STATE_DIR,
8038
8561
  `${normalizePrdRunRef2(prdId)}.json`
@@ -8040,8 +8563,8 @@ function getLocalStorePath(repoRoot2, prdId) {
8040
8563
  }
8041
8564
  async function readLocalPrdRun(repoRoot2, prdId) {
8042
8565
  const normalized = normalizePrdRunRef2(prdId);
8043
- const recordPath = getLocalStorePath(repoRoot2, normalized);
8044
- if (!existsSync10(recordPath)) {
8566
+ const recordPath = getLocalStorePath2(repoRoot2, normalized);
8567
+ if (!existsSync11(recordPath)) {
8045
8568
  return null;
8046
8569
  }
8047
8570
  try {
@@ -8053,16 +8576,16 @@ async function readLocalPrdRun(repoRoot2, prdId) {
8053
8576
  }
8054
8577
  async function writeLocalPrdRunRecord(repoRoot2, prdId, record) {
8055
8578
  const normalized = normalizePrdRunRef2(prdId);
8056
- const storeDir = join14(repoRoot2, LOCAL_PRD_RUN_STATE_DIR);
8579
+ const storeDir = join15(repoRoot2, LOCAL_PRD_RUN_STATE_DIR);
8057
8580
  await mkdir4(storeDir, { recursive: true });
8058
8581
  await writeFile(
8059
- getLocalStorePath(repoRoot2, normalized),
8582
+ getLocalStorePath2(repoRoot2, normalized),
8060
8583
  JSON.stringify({ ...record, prdId: normalized }, null, 2),
8061
8584
  "utf-8"
8062
8585
  );
8063
8586
  }
8064
8587
  function getRecordPath(repoRoot2, prdRef) {
8065
- return join14(
8588
+ return join15(
8066
8589
  repoRoot2,
8067
8590
  PRD_RUN_STATE_DIR,
8068
8591
  `${normalizePrdRunRef2(prdRef)}.json`
@@ -8195,9 +8718,9 @@ function redactSensitiveValues(input) {
8195
8718
  }
8196
8719
 
8197
8720
  // prd-run/final-review-validation.ts
8198
- import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
8721
+ import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
8199
8722
  function parseFinalReviewArtifact(artifactPath) {
8200
- if (!existsSync11(artifactPath)) {
8723
+ if (!existsSync12(artifactPath)) {
8201
8724
  return {
8202
8725
  ok: false,
8203
8726
  reason: "Final Review artifact not found.",
@@ -8206,7 +8729,7 @@ function parseFinalReviewArtifact(artifactPath) {
8206
8729
  }
8207
8730
  let content;
8208
8731
  try {
8209
- content = readFileSync13(artifactPath, "utf-8");
8732
+ content = readFileSync14(artifactPath, "utf-8");
8210
8733
  } catch (error) {
8211
8734
  return {
8212
8735
  ok: false,
@@ -8386,8 +8909,8 @@ function isRetouchScopePath(path9) {
8386
8909
  init_common();
8387
8910
 
8388
8911
  // prd-run/local-artifacts.ts
8389
- import { existsSync as existsSync12 } from "fs";
8390
- import { join as join15 } from "path";
8912
+ import { existsSync as existsSync13 } from "fs";
8913
+ import { join as join16 } from "path";
8391
8914
  var REQUIRED_PRD_FIELDS = [
8392
8915
  "schemaVersion",
8393
8916
  "kind",
@@ -8420,13 +8943,13 @@ var REQUIRED_ISSUE_FIELDS = [
8420
8943
  "githubProjection"
8421
8944
  ];
8422
8945
  function prdStorePath(repoRoot2, prdId) {
8423
- return join15(repoRoot2, ".pourkit", "local-prd-runs", prdId);
8946
+ return join16(repoRoot2, ".pourkit", "local-prd-runs", prdId);
8424
8947
  }
8425
8948
  function prdArtifactPath(repoRoot2, prdId) {
8426
- return join15(prdStorePath(repoRoot2, prdId), "prd.json");
8949
+ return join16(prdStorePath(repoRoot2, prdId), "prd.json");
8427
8950
  }
8428
8951
  function issueArtifactPath(repoRoot2, prdId, issueId) {
8429
- return join15(prdStorePath(repoRoot2, prdId), "issues", `${issueId}.json`);
8952
+ return join16(prdStorePath(repoRoot2, prdId), "issues", `${issueId}.json`);
8430
8953
  }
8431
8954
  function hasRequiredFields(data, requiredFields) {
8432
8955
  for (const field of requiredFields) {
@@ -8439,7 +8962,7 @@ function hasRequiredFields(data, requiredFields) {
8439
8962
  async function resolveLocalPrdArtifact(prdId, repoRoot2) {
8440
8963
  const root = repoRoot2 ?? process.cwd();
8441
8964
  const prdPath = prdArtifactPath(root, prdId);
8442
- if (!existsSync12(prdPath)) {
8965
+ if (!existsSync13(prdPath)) {
8443
8966
  return {
8444
8967
  ok: false,
8445
8968
  failureCode: "missing_prd_artifact",
@@ -8484,7 +9007,7 @@ async function resolveLocalIssueArtifacts(prdId, repoRoot2) {
8484
9007
  const issues = [];
8485
9008
  for (const childId of childIssueIds) {
8486
9009
  const issuePath = issueArtifactPath(root, prdId, childId);
8487
- if (!existsSync12(issuePath)) {
9010
+ if (!existsSync13(issuePath)) {
8488
9011
  return {
8489
9012
  ok: false,
8490
9013
  failureCode: "missing_child_issue",
@@ -8620,97 +9143,14 @@ async function getRunnableLocalIssues(prdId, repoRoot2) {
8620
9143
  }
8621
9144
 
8622
9145
  // prd-run/local-final-review.ts
8623
- import { execFileSync as execFileSync2, execSync as execSync2 } from "child_process";
8624
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
8625
- import { join as join16 } from "path";
8626
-
8627
- // prd-run/local-branches.ts
8628
- import { execFileSync } from "child_process";
8629
- function getLocalPrdBranchName(prdId) {
8630
- return `local/${prdId}`;
8631
- }
8632
- var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
8633
- var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
8634
- function validateLocalBranchName(name) {
8635
- if (!name || name.length === 0) {
8636
- return {
8637
- ok: false,
8638
- failureCode: "invalid_format",
8639
- message: "Branch name is empty."
8640
- };
8641
- }
8642
- if (isProtectedBranch(name)) {
8643
- return {
8644
- ok: false,
8645
- failureCode: "protected_branch",
8646
- message: `Branch "${name}" is protected.`
8647
- };
8648
- }
8649
- if (!LOCAL_BRANCH_PATTERN.test(name)) {
8650
- return {
8651
- ok: false,
8652
- failureCode: "invalid_format",
8653
- message: `Branch "${name}" does not match the local branch pattern.`
8654
- };
8655
- }
8656
- return { ok: true };
8657
- }
8658
- function isProtectedBranch(name) {
8659
- return PROTECTED_BRANCHES.has(name);
8660
- }
8661
- function hasRemoteBackedCollision(localName, repoRoot2) {
8662
- const match = localName.match(/^local\/(PRD-\d{4})/);
8663
- if (!match) {
8664
- return Promise.resolve({
8665
- ok: false,
8666
- failureCode: "invalid_format",
8667
- message: `Cannot extract PRD ref from "${localName}".`
8668
- });
8669
- }
8670
- const prdRef = match[1];
8671
- try {
8672
- execFileSync(
8673
- "git",
8674
- ["show-ref", "--verify", "--quiet", `refs/heads/${prdRef}`],
8675
- {
8676
- cwd: repoRoot2 ?? process.cwd(),
8677
- encoding: "utf8",
8678
- stdio: "pipe"
8679
- }
8680
- );
8681
- return Promise.resolve({
8682
- ok: false,
8683
- failureCode: "remote_backed_collision",
8684
- message: `Branch "${prdRef}" exists locally.`
8685
- });
8686
- } catch {
8687
- }
8688
- try {
8689
- execFileSync(
8690
- "git",
8691
- ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${prdRef}`],
8692
- {
8693
- cwd: repoRoot2 ?? process.cwd(),
8694
- encoding: "utf8",
8695
- stdio: "pipe"
8696
- }
8697
- );
8698
- return Promise.resolve({
8699
- ok: false,
8700
- failureCode: "remote_backed_collision",
8701
- message: `Remote branch "origin/${prdRef}" exists.`
8702
- });
8703
- } catch {
8704
- }
8705
- return Promise.resolve({ ok: true });
8706
- }
8707
-
8708
- // prd-run/local-final-review.ts
8709
- function getLocalStorePath2(repoRoot2, prdId) {
8710
- return join16(repoRoot2, ".pourkit", "local-prd-runs", prdId);
9146
+ import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
9147
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
9148
+ import { join as join17 } from "path";
9149
+ function getLocalStorePath3(repoRoot2, prdId) {
9150
+ return join17(repoRoot2, ".pourkit", "local-prd-runs", prdId);
8711
9151
  }
8712
9152
  function getFinalReviewReceiptPath(repoRoot2, prdId) {
8713
- return join16(getLocalStorePath2(repoRoot2, prdId), "final-review-receipt.json");
9153
+ return join17(getLocalStorePath3(repoRoot2, prdId), "final-review-receipt.json");
8714
9154
  }
8715
9155
  async function runLocalFinalReview(prdId, options) {
8716
9156
  const root = options?.repoRoot ?? process.cwd();
@@ -8767,8 +9207,8 @@ async function runLocalFinalReview(prdId, options) {
8767
9207
  branch
8768
9208
  };
8769
9209
  const receiptPath = getFinalReviewReceiptPath(root, prdId);
8770
- mkdirSync8(getLocalStorePath2(root, prdId), { recursive: true });
8771
- writeFileSync5(receiptPath, JSON.stringify(receipt, null, 2), "utf-8");
9210
+ mkdirSync9(getLocalStorePath3(root, prdId), { recursive: true });
9211
+ writeFileSync6(receiptPath, JSON.stringify(receipt, null, 2), "utf-8");
8772
9212
  const result = { ok: true, verdict, receipt };
8773
9213
  if (failures.length > 0) {
8774
9214
  result.message = failures.map(
@@ -8782,7 +9222,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
8782
9222
  const targetBranch = `local/${prdId}`;
8783
9223
  const retouchBranch = `local/${prdId}/retouch`;
8784
9224
  try {
8785
- execFileSync2(
9225
+ execFileSync3(
8786
9226
  "git",
8787
9227
  ["show-ref", "--verify", "--quiet", `refs/heads/${retouchBranch}`],
8788
9228
  {
@@ -8795,7 +9235,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
8795
9235
  return;
8796
9236
  }
8797
9237
  try {
8798
- execFileSync2(
9238
+ execFileSync3(
8799
9239
  "git",
8800
9240
  ["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
8801
9241
  {
@@ -8808,18 +9248,18 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
8808
9248
  return;
8809
9249
  }
8810
9250
  try {
8811
- execFileSync2("git", ["checkout", targetBranch], {
9251
+ execFileSync3("git", ["checkout", targetBranch], {
8812
9252
  cwd: root,
8813
9253
  encoding: "utf8",
8814
9254
  stdio: "pipe"
8815
9255
  });
8816
- execFileSync2("git", ["merge", "--squash", retouchBranch], {
9256
+ execFileSync3("git", ["merge", "--squash", retouchBranch], {
8817
9257
  cwd: root,
8818
9258
  encoding: "utf8",
8819
9259
  stdio: "pipe"
8820
9260
  });
8821
9261
  try {
8822
- execFileSync2("git", ["diff", "--cached", "--quiet"], {
9262
+ execFileSync3("git", ["diff", "--cached", "--quiet"], {
8823
9263
  cwd: root,
8824
9264
  encoding: "utf8",
8825
9265
  stdio: "pipe"
@@ -8827,7 +9267,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
8827
9267
  return;
8828
9268
  } catch {
8829
9269
  }
8830
- execFileSync2(
9270
+ execFileSync3(
8831
9271
  "git",
8832
9272
  ["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
8833
9273
  {
@@ -8846,53 +9286,53 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
8846
9286
  }
8847
9287
 
8848
9288
  // prd-run/local-reconciliation.ts
8849
- import { execFileSync as execFileSync3 } from "child_process";
9289
+ import { execFileSync as execFileSync4 } from "child_process";
8850
9290
  import {
8851
- existsSync as existsSync14,
8852
- mkdirSync as mkdirSync9,
8853
- readFileSync as readFileSync14,
9291
+ existsSync as existsSync15,
9292
+ mkdirSync as mkdirSync10,
9293
+ readFileSync as readFileSync15,
8854
9294
  readdirSync as readdirSync5,
8855
- writeFileSync as writeFileSync6
9295
+ writeFileSync as writeFileSync7
8856
9296
  } from "fs";
8857
- import { join as join17 } from "path";
8858
- function getLocalStorePath3(repoRoot2, prdId) {
8859
- return join17(repoRoot2, ".pourkit", "local-prd-runs", prdId);
9297
+ import { join as join18 } from "path";
9298
+ function getLocalStorePath4(repoRoot2, prdId) {
9299
+ return join18(repoRoot2, ".pourkit", "local-prd-runs", prdId);
8860
9300
  }
8861
9301
  function getFinalReviewReceiptPath2(repoRoot2, prdId) {
8862
- return join17(getLocalStorePath3(repoRoot2, prdId), "final-review-receipt.json");
9302
+ return join18(getLocalStorePath4(repoRoot2, prdId), "final-review-receipt.json");
8863
9303
  }
8864
9304
  function getHandoffArtifactPath(repoRoot2, prdId) {
8865
- return join17(repoRoot2, ".pourkit", ".tmp", "reconciliation", `${prdId}.json`);
9305
+ return join18(repoRoot2, ".pourkit", ".tmp", "reconciliation", `${prdId}.json`);
8866
9306
  }
8867
9307
  function getCompletionsDir(repoRoot2, prdId) {
8868
- const initiativesRoot = join17(
9308
+ const initiativesRoot = join18(
8869
9309
  repoRoot2,
8870
9310
  ".pourkit",
8871
9311
  "architecture",
8872
9312
  "initiatives"
8873
9313
  );
8874
- if (existsSync14(initiativesRoot)) {
8875
- const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join17(initiativesRoot, e.name));
9314
+ if (existsSync15(initiativesRoot)) {
9315
+ const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join18(initiativesRoot, e.name));
8876
9316
  for (const dir of dirs) {
8877
- const prdsDir = join17(dir, "prds");
8878
- if (!existsSync14(prdsDir)) continue;
9317
+ const prdsDir = join18(dir, "prds");
9318
+ if (!existsSync15(prdsDir)) continue;
8879
9319
  const prdDirs = readdirSync5(prdsDir, { withFileTypes: true }).filter(
8880
9320
  (e) => e.isDirectory() && e.name.startsWith(prdId.toLowerCase())
8881
9321
  );
8882
- if (prdDirs.length > 0) return join17(dir, "completions");
9322
+ if (prdDirs.length > 0) return join18(dir, "completions");
8883
9323
  }
8884
9324
  }
8885
- return join17(getLocalStorePath3(repoRoot2, prdId), "completions");
9325
+ return join18(getLocalStorePath4(repoRoot2, prdId), "completions");
8886
9326
  }
8887
9327
  function getReconciliationReceiptPath(repoRoot2, prdId) {
8888
- return join17(
8889
- getLocalStorePath3(repoRoot2, prdId),
9328
+ return join18(
9329
+ getLocalStorePath4(repoRoot2, prdId),
8890
9330
  "reconciliation-receipt.json"
8891
9331
  );
8892
9332
  }
8893
9333
  async function runLocalReconciliation(prdId, options) {
8894
9334
  const root = options?.repoRoot ?? process.cwd();
8895
- if (!existsSync14(getFinalReviewReceiptPath2(root, prdId))) {
9335
+ if (!existsSync15(getFinalReviewReceiptPath2(root, prdId))) {
8896
9336
  return {
8897
9337
  ok: false,
8898
9338
  failureCode: "missing_final_review",
@@ -8900,7 +9340,7 @@ async function runLocalReconciliation(prdId, options) {
8900
9340
  };
8901
9341
  }
8902
9342
  try {
8903
- execFileSync3("pourkit-architect", ["reconcile", prdId], {
9343
+ execFileSync4("pourkit-architect", ["reconcile", prdId], {
8904
9344
  cwd: root,
8905
9345
  encoding: "utf8",
8906
9346
  stdio: "pipe"
@@ -8916,7 +9356,7 @@ async function runLocalReconciliation(prdId, options) {
8916
9356
  }
8917
9357
  let handoff;
8918
9358
  try {
8919
- const content = readFileSync14(getHandoffArtifactPath(root, prdId), "utf-8");
9359
+ const content = readFileSync15(getHandoffArtifactPath(root, prdId), "utf-8");
8920
9360
  handoff = JSON.parse(content);
8921
9361
  } catch {
8922
9362
  return {
@@ -8942,11 +9382,11 @@ async function runLocalReconciliation(prdId, options) {
8942
9382
  if (handoff.result === "changes_produced") {
8943
9383
  try {
8944
9384
  const completionsDir = getCompletionsDir(root, prdId);
8945
- mkdirSync9(completionsDir, { recursive: true });
8946
- const existing = existsSync14(completionsDir) ? readdirSync5(completionsDir).filter((f) => f.endsWith(".md")) : [];
9385
+ mkdirSync10(completionsDir, { recursive: true });
9386
+ const existing = existsSync15(completionsDir) ? readdirSync5(completionsDir).filter((f) => f.endsWith(".md")) : [];
8947
9387
  const nextNum = String(existing.length + 1).padStart(3, "0");
8948
9388
  const filename = `${nextNum}-prd-${prdId.replace("PRD-", "").toLowerCase()}-reconciliation-completion.md`;
8949
- const filepath = join17(completionsDir, filename);
9389
+ const filepath = join18(completionsDir, filename);
8950
9390
  const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
8951
9391
  const summary = typeof handoff.summary === "string" ? handoff.summary : "";
8952
9392
  const lines = [
@@ -8962,13 +9402,13 @@ async function runLocalReconciliation(prdId, options) {
8962
9402
  lines.push("- (none)");
8963
9403
  }
8964
9404
  lines.push("");
8965
- writeFileSync6(filepath, lines.join("\n"), "utf-8");
8966
- execFileSync3("git", ["add", filepath], {
9405
+ writeFileSync7(filepath, lines.join("\n"), "utf-8");
9406
+ execFileSync4("git", ["add", filepath], {
8967
9407
  cwd: root,
8968
9408
  encoding: "utf8",
8969
9409
  stdio: "pipe"
8970
9410
  });
8971
- execFileSync3(
9411
+ execFileSync4(
8972
9412
  "git",
8973
9413
  ["commit", "-m", `docs: ${prdId} reconciliation completion`],
8974
9414
  { cwd: root, encoding: "utf8", stdio: "pipe" }
@@ -8985,7 +9425,7 @@ async function runLocalReconciliation(prdId, options) {
8985
9425
  try {
8986
9426
  const targetBranch = getLocalPrdBranchName(prdId);
8987
9427
  try {
8988
- execFileSync3(
9428
+ execFileSync4(
8989
9429
  "git",
8990
9430
  ["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
8991
9431
  { cwd: root, encoding: "utf8", stdio: "pipe" }
@@ -8997,14 +9437,14 @@ async function runLocalReconciliation(prdId, options) {
8997
9437
  message: "Squash merge into local PRD Branch failed."
8998
9438
  };
8999
9439
  }
9000
- execFileSync3("git", ["checkout", targetBranch], {
9440
+ execFileSync4("git", ["checkout", targetBranch], {
9001
9441
  cwd: root,
9002
9442
  encoding: "utf8",
9003
9443
  stdio: "pipe"
9004
9444
  });
9005
9445
  const sourceBranch = `local/${prdId}/reconciliation`;
9006
9446
  try {
9007
- execFileSync3(
9447
+ execFileSync4(
9008
9448
  "git",
9009
9449
  ["show-ref", "--verify", "--quiet", `refs/heads/${sourceBranch}`],
9010
9450
  {
@@ -9020,19 +9460,19 @@ async function runLocalReconciliation(prdId, options) {
9020
9460
  message: "Squash merge into local PRD Branch failed."
9021
9461
  };
9022
9462
  }
9023
- execFileSync3("git", ["merge", "--squash", sourceBranch], {
9463
+ execFileSync4("git", ["merge", "--squash", sourceBranch], {
9024
9464
  cwd: root,
9025
9465
  encoding: "utf8",
9026
9466
  stdio: "pipe"
9027
9467
  });
9028
9468
  try {
9029
- execFileSync3("git", ["diff", "--cached", "--quiet"], {
9469
+ execFileSync4("git", ["diff", "--cached", "--quiet"], {
9030
9470
  cwd: root,
9031
9471
  encoding: "utf8",
9032
9472
  stdio: "pipe"
9033
9473
  });
9034
9474
  } catch {
9035
- execFileSync3(
9475
+ execFileSync4(
9036
9476
  "git",
9037
9477
  ["commit", "-m", `Squash merge ${sourceBranch} into ${targetBranch}`],
9038
9478
  { cwd: root, encoding: "utf8", stdio: "pipe" }
@@ -9051,8 +9491,8 @@ async function runLocalReconciliation(prdId, options) {
9051
9491
  reconciledAt: (/* @__PURE__ */ new Date()).toISOString(),
9052
9492
  branch
9053
9493
  };
9054
- mkdirSync9(getLocalStorePath3(root, prdId), { recursive: true });
9055
- writeFileSync6(
9494
+ mkdirSync10(getLocalStorePath4(root, prdId), { recursive: true });
9495
+ writeFileSync7(
9056
9496
  getReconciliationReceiptPath(root, prdId),
9057
9497
  JSON.stringify(receipt, null, 2),
9058
9498
  "utf-8"
@@ -9061,8 +9501,8 @@ async function runLocalReconciliation(prdId, options) {
9061
9501
  }
9062
9502
 
9063
9503
  // prd-run/local-prepare.ts
9064
- import { existsSync as existsSync15 } from "fs";
9065
- import { join as join18 } from "path";
9504
+ import { existsSync as existsSync16 } from "fs";
9505
+ import { join as join19 } from "path";
9066
9506
  var VALID_TRIAGE_LABELS2 = /* @__PURE__ */ new Set([
9067
9507
  "needs-triage",
9068
9508
  "ready-for-agent",
@@ -9080,8 +9520,8 @@ function fail(gate, failureCode, repairGuidance, gates) {
9080
9520
  async function validateLocalPrepareGates(prdId, repoRoot2) {
9081
9521
  const root = repoRoot2 ?? process.cwd();
9082
9522
  const gates = {};
9083
- const storePath = join18(root, ".pourkit", "local-prd-runs", prdId);
9084
- if (!existsSync15(storePath)) {
9523
+ const storePath = join19(root, ".pourkit", "local-prd-runs", prdId);
9524
+ if (!existsSync16(storePath)) {
9085
9525
  return fail(
9086
9526
  "store_shape",
9087
9527
  "local_prepare_store_shape_failed",
@@ -9507,6 +9947,7 @@ function runOneQueueIssueEffect(options) {
9507
9947
  }
9508
9948
  const { issue: selected } = outcome;
9509
9949
  const baseBranchOverride = options.queueRunContext?.prdBranch;
9950
+ const prdRunMode = options.prdRunMode ?? options.queueRunContext?.prdRunMode;
9510
9951
  const runResult = yield* Effect8.tryPromise({
9511
9952
  try: () => runIssueCommand({
9512
9953
  issueNumber: selected.number,
@@ -9518,7 +9959,8 @@ function runOneQueueIssueEffect(options) {
9518
9959
  force,
9519
9960
  logger,
9520
9961
  repoRoot: repoRoot2,
9521
- ...baseBranchOverride ? { baseBranchOverride } : {}
9962
+ ...baseBranchOverride ? { baseBranchOverride } : {},
9963
+ ...prdRunMode ? { prdRunMode } : {}
9522
9964
  }),
9523
9965
  catch: (e) => {
9524
9966
  if (e instanceof Error) return e;
@@ -9529,6 +9971,10 @@ function runOneQueueIssueEffect(options) {
9529
9971
  logger.raw(` Branch: ${runResult.branchName}`);
9530
9972
  if (runResult.noOp) {
9531
9973
  logger.raw(" Status: no-op (closed without PR)");
9974
+ } else if (runResult.mode === "local") {
9975
+ logger.raw(` Local PRD Branch: ${runResult.localPrdBranch}`);
9976
+ logger.raw(` Merge Commit: ${runResult.mergeCommit}`);
9977
+ logger.raw(` Receipt: ${runResult.receiptPath}`);
9532
9978
  } else {
9533
9979
  logger.raw(` PR Title: ${runResult.prTitle}`);
9534
9980
  logger.raw(` PR Number: ${runResult.prNumber}`);
@@ -9588,7 +10034,8 @@ async function runQueueCommand(options) {
9588
10034
  logger: options.logger,
9589
10035
  repoRoot: options.repoRoot,
9590
10036
  prdRef: options.prdRef,
9591
- queueRunContext: options.queueRunContext
10037
+ queueRunContext: options.queueRunContext,
10038
+ prdRunMode: options.queueRunContext?.prdRunMode
9592
10039
  };
9593
10040
  if (!options.loop) {
9594
10041
  return runEffectAndMapExit(runQueue(queueOptions));
@@ -9717,7 +10164,7 @@ function validateChangedPlanningPaths(paths, options) {
9717
10164
  continue;
9718
10165
  }
9719
10166
  if (options?.repoRoot) {
9720
- const fullPath = join19(options.repoRoot, p);
10167
+ const fullPath = join20(options.repoRoot, p);
9721
10168
  try {
9722
10169
  if (lstatSync(fullPath).isSymbolicLink()) {
9723
10170
  rejected.push(p);
@@ -9742,7 +10189,7 @@ function assessStaleSucceededReconciliationReceipt(options) {
9742
10189
  if (!receipt || receipt.status !== "succeeded" || receipt.prNumber || receipt.mergeCommit) {
9743
10190
  return { mode: "none" };
9744
10191
  }
9745
- const observedDiff = collectObservedReconciliationPlanningDiff({
10192
+ const observedDiff = collectObservedReconciliationDirtyPaths({
9746
10193
  worktreeCwd: options.worktreeCwd,
9747
10194
  ignoredPrdRunRecordRef: options.prdRef
9748
10195
  });
@@ -9753,23 +10200,14 @@ function assessStaleSucceededReconciliationReceipt(options) {
9753
10200
  offendingPaths: []
9754
10201
  };
9755
10202
  }
9756
- if (observedDiff.rejectedPaths.length > 0) {
9757
- return {
9758
- mode: "blocked",
9759
- diagnostics: [
9760
- "Reconciliation worktree contains unpublished changes outside safe planning scope."
9761
- ],
9762
- offendingPaths: observedDiff.rejectedPaths
9763
- };
9764
- }
9765
- if (observedDiff.changedPlanningPaths.length === 0) {
10203
+ if (observedDiff.dirtyPaths.length === 0) {
9766
10204
  return { mode: "none" };
9767
10205
  }
9768
10206
  return {
9769
10207
  mode: "recoverable",
9770
- changedPlanningPaths: observedDiff.changedPlanningPaths,
10208
+ dirtyPaths: observedDiff.dirtyPaths,
9771
10209
  diagnostics: [
9772
- "Recovered stale reconciliation success receipt from unpublished planning changes in worktree."
10210
+ "Recovered stale reconciliation success receipt from dirty reconciliation worktree."
9773
10211
  ]
9774
10212
  };
9775
10213
  }
@@ -9893,7 +10331,7 @@ function canRetryFinalReviewBlock(record) {
9893
10331
  }
9894
10332
  function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
9895
10333
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
9896
- return existsSync16(promptPath) ? readFileSync15(promptPath, "utf-8") : promptTemplate;
10334
+ return existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : promptTemplate;
9897
10335
  }
9898
10336
  function formatVerificationCommands(commands) {
9899
10337
  if (commands.length === 0) return "No verification commands configured.";
@@ -9906,12 +10344,14 @@ function buildFinalReviewPrompt(options) {
9906
10344
  `Worktree checkout base: ${options.evidencePacket.prdBranch}`,
9907
10345
  `Review only this range: ${options.evidencePacket.mergeBase}..HEAD`,
9908
10346
  "Do not compare against current target branch HEAD; the merge base is the review baseline.",
9909
- `Before handoff, run: npm run pourkit:validate-final-review -- ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
10347
+ `Before handoff, run: pourkit prd-run validate-final-review ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
9910
10348
  "Fix any validation failures before handing off.",
9911
10349
  "",
9912
- "## Target Verification Commands",
10350
+ "## Verification",
10351
+ "",
10352
+ `Run this command before handoff when retouch changes are made: pourkit run-verification --target ${options.targetName}`,
9913
10353
  "",
9914
- "Run these commands before handoff when retouch changes are made:",
10354
+ "Underlying project commands:",
9915
10355
  formatVerificationCommands(options.verificationCommands),
9916
10356
  "",
9917
10357
  "Evidence Packet (do not infer PRD context from local state files):",
@@ -9938,14 +10378,14 @@ function buildReconciliationPrompt(options) {
9938
10378
  options.artifactPath,
9939
10379
  "",
9940
10380
  "Self-validation command:",
9941
- `validate-artifact reconciliation ${options.artifactPath}`
10381
+ `pourkit validate-artifact reconciliation ${options.artifactPath}`
9942
10382
  ].join("\n");
9943
10383
  }
9944
10384
  function buildReconciliationBranchName(prdRef) {
9945
10385
  return `${normalizePrdRunRef2(prdRef)}-reconciliation`;
9946
10386
  }
9947
10387
  function reconciliationWorktreePath(repoRoot2, branchName) {
9948
- return join19(repoRoot2, ".sandcastle", "worktrees", branchName);
10388
+ return join20(repoRoot2, ".sandcastle", "worktrees", branchName);
9949
10389
  }
9950
10390
  function reconciliationReceiptWorktreePath(branchName) {
9951
10391
  return `.sandcastle/worktrees/${branchName}`;
@@ -9957,7 +10397,7 @@ function buildFinalReviewBranchName(prdRef) {
9957
10397
  return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
9958
10398
  }
9959
10399
  function finalReviewWorktreePath(repoRoot2, branchName) {
9960
- return join19(
10400
+ return join20(
9961
10401
  repoRoot2,
9962
10402
  ".sandcastle",
9963
10403
  "worktrees",
@@ -10012,7 +10452,7 @@ function buildFinalReviewFinalizerPrompt(options) {
10012
10452
  options.repoRoot,
10013
10453
  options.promptTemplate
10014
10454
  );
10015
- const promptBody = existsSync16(promptPath) ? readFileSync15(promptPath, "utf-8") : options.promptTemplate;
10455
+ const promptBody = existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : options.promptTemplate;
10016
10456
  return [
10017
10457
  "# Final Review Retouch PR Finalizer",
10018
10458
  "",
@@ -10046,7 +10486,7 @@ function buildFinalReviewFinalizerPrompt(options) {
10046
10486
  }
10047
10487
  async function runFinalReviewPrFinalizer(options) {
10048
10488
  const finalizer = options.target.strategy.finalize.prDescriptionAgent;
10049
- const artifactPathInWorktree = join19(
10489
+ const artifactPathInWorktree = join20(
10050
10490
  ".pourkit",
10051
10491
  ".tmp",
10052
10492
  "finalizer",
@@ -10120,14 +10560,14 @@ function ensureFinalReviewWorktree(options) {
10120
10560
  }
10121
10561
  return { ok: true, worktreePath: registeredPath };
10122
10562
  }
10123
- if (existsSync16(worktreePath)) {
10563
+ if (existsSync17(worktreePath)) {
10124
10564
  return {
10125
10565
  ok: false,
10126
10566
  reason: "Final Review worktree path exists but is not registered with git.",
10127
10567
  diagnostics: [`stale worktree path: ${worktreePath}`]
10128
10568
  };
10129
10569
  }
10130
- mkdirSync10(dirname5(worktreePath), { recursive: true });
10570
+ mkdirSync11(dirname5(worktreePath), { recursive: true });
10131
10571
  const branchResult = spawnSync2(
10132
10572
  "git",
10133
10573
  ["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
@@ -10182,7 +10622,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
10182
10622
  if (existingPr && existingPr.state === "OPEN") {
10183
10623
  return existingPr;
10184
10624
  }
10185
- const worktreePath = mkdtempSync(join19(tmpdir(), "pourkit-retouch-"));
10625
+ const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-retouch-"));
10186
10626
  try {
10187
10627
  runGitOrThrow(
10188
10628
  options.repoRoot,
@@ -10200,9 +10640,9 @@ async function createOrReuseFinalReviewRetouchPr(options) {
10200
10640
  "create retouch branch"
10201
10641
  );
10202
10642
  for (const changedFile of options.changedPaths) {
10203
- const sourcePath = join19(options.sourceWorktreePath, changedFile);
10204
- const targetPath = join19(worktreePath, changedFile);
10205
- mkdirSync10(dirname5(targetPath), { recursive: true });
10643
+ const sourcePath = join20(options.sourceWorktreePath, changedFile);
10644
+ const targetPath = join20(worktreePath, changedFile);
10645
+ mkdirSync11(dirname5(targetPath), { recursive: true });
10206
10646
  cpSync(sourcePath, targetPath, { recursive: true });
10207
10647
  }
10208
10648
  runGitOrThrow(
@@ -10556,6 +10996,7 @@ async function runPrdRunFinalReviewCommand(options) {
10556
10996
  prompt: buildFinalReviewPrompt({
10557
10997
  repoRoot: options.repoRoot,
10558
10998
  promptTemplate: finalReviewConfig.promptTemplate,
10999
+ targetName,
10559
11000
  evidencePacket,
10560
11001
  verificationCommands
10561
11002
  }),
@@ -10631,7 +11072,7 @@ async function runPrdRunFinalReviewCommand(options) {
10631
11072
  };
10632
11073
  }
10633
11074
  const resolvedWorktreePath = executionResult.worktreePath;
10634
- const artifactPath = join19(
11075
+ const artifactPath = join20(
10635
11076
  resolvedWorktreePath,
10636
11077
  ".pourkit/final-review-artifact.json"
10637
11078
  );
@@ -11215,7 +11656,7 @@ async function runPrdRunFinalReviewCommand(options) {
11215
11656
  function runPrdRunValidateFinalReviewCommand(options) {
11216
11657
  const prdRef = normalizePrdRunRef2(options.prdRef);
11217
11658
  const checkoutBase = options.checkoutBase ?? prdRef;
11218
- const artifactPath = options.artifactPath ? options.artifactPath : join19(options.repoRoot, ".pourkit", "final-review-artifact.json");
11659
+ const artifactPath = options.artifactPath ? options.artifactPath : join20(options.repoRoot, ".pourkit", "final-review-artifact.json");
11219
11660
  const artifact = parseFinalReviewArtifact(artifactPath);
11220
11661
  if (!artifact.ok) {
11221
11662
  return {
@@ -11523,7 +11964,7 @@ async function runReconcilePreflightAndSetup(options) {
11523
11964
  );
11524
11965
  return { ok: false, diagnostics, branchAction: "blocked" };
11525
11966
  }
11526
- if (existsSync16(worktreePath)) {
11967
+ if (existsSync17(worktreePath)) {
11527
11968
  diagnostics.push(
11528
11969
  "Reconciliation worktree path exists but is not registered with git.",
11529
11970
  `stale worktree path: ${worktreePath}`
@@ -11544,7 +11985,7 @@ async function runReconcilePreflightAndSetup(options) {
11544
11985
  );
11545
11986
  return { ok: false, diagnostics, branchAction: "blocked" };
11546
11987
  }
11547
- mkdirSync10(dirname5(worktreePath), { recursive: true });
11988
+ mkdirSync11(dirname5(worktreePath), { recursive: true });
11548
11989
  const addResult = spawnSync2(
11549
11990
  "git",
11550
11991
  ["worktree", "add", worktreePath, reconciliationBranchName],
@@ -11599,7 +12040,7 @@ function buildReconciliationPrBody(options) {
11599
12040
  function commitAndPushReconciliationChanges(options) {
11600
12041
  const diagnostics = [];
11601
12042
  const existingPaths = options.changedPlanningPaths.filter((p) => {
11602
- return existsSync16(join19(options.worktreeCwd, p));
12043
+ return existsSync17(join20(options.worktreeCwd, p));
11603
12044
  });
11604
12045
  if (existingPaths.length === 0) {
11605
12046
  diagnostics.push(
@@ -11742,7 +12183,7 @@ async function runPrdRunReconcileCommand(options) {
11742
12183
  const record = preflightResult.record;
11743
12184
  const prdBranch = record?.prdBranch ?? prdRef;
11744
12185
  const mergeBase = preflightResult.mergeBase;
11745
- const worktreeCwd = preflightResult.worktreePath ? join19(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
12186
+ const worktreeCwd = preflightResult.worktreePath ? join20(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
11746
12187
  const staleSucceededRecovery = assessStaleSucceededReconciliationReceipt({
11747
12188
  receipt: record?.reconciliation,
11748
12189
  worktreeCwd,
@@ -12044,7 +12485,7 @@ async function runPrdRunReconcileCommand(options) {
12044
12485
  baseRef: prdBranch,
12045
12486
  checkoutBase,
12046
12487
  reviewBase: mergeBase,
12047
- worktreePath: join19(options.repoRoot, preflightResult.worktreePath),
12488
+ worktreePath: join20(options.repoRoot, preflightResult.worktreePath),
12048
12489
  sandbox: options.config.sandbox,
12049
12490
  artifactPath,
12050
12491
  logger: options.logger
@@ -12145,7 +12586,7 @@ async function runPrdRunReconcileCommand(options) {
12145
12586
  offendingPaths: []
12146
12587
  };
12147
12588
  }
12148
- const fullArtifactPath = join19(executionResult.worktreePath, artifactPath);
12589
+ const fullArtifactPath = join20(executionResult.worktreePath, artifactPath);
12149
12590
  let artifactContent;
12150
12591
  try {
12151
12592
  artifactContent = JSON.parse(retryResult.artifact.value);
@@ -12267,11 +12708,27 @@ async function runPrdRunReconcileCommand(options) {
12267
12708
  gitStatusShortLines,
12268
12709
  gitDiffNameStatusLines
12269
12710
  });
12711
+ const observedWorktreeDiff = collectObservedReconciliationDirtyPaths({
12712
+ worktreeCwd,
12713
+ ignoredPrdRunRecordRef: prdRef
12714
+ });
12270
12715
  let outcomeResult;
12271
12716
  let outcomeDiagnostics = [];
12272
12717
  let outcomeChangedPaths = [];
12273
12718
  let outcomeRationale;
12274
- if (artifactResult === "no_changes_needed") {
12719
+ if (!observedWorktreeDiff.ok) {
12720
+ outcomeResult = "blocked";
12721
+ outcomeDiagnostics.push(...observedWorktreeDiff.diagnostics);
12722
+ } else if (observedWorktreeDiff.dirtyPaths.length > 0) {
12723
+ outcomeResult = "changes_produced";
12724
+ outcomeChangedPaths = [...observedWorktreeDiff.dirtyPaths];
12725
+ outcomeRationale = void 0;
12726
+ if (artifactResult === "no_changes_needed") {
12727
+ outcomeDiagnostics.push(
12728
+ "Dirty reconciliation worktree observed after agent run; treating reconciliation result as changes_produced."
12729
+ );
12730
+ }
12731
+ } else if (artifactResult === "no_changes_needed") {
12275
12732
  outcomeResult = "no_changes_needed";
12276
12733
  outcomeChangedPaths = [];
12277
12734
  outcomeRationale = artifactNoChangeRationale ?? "No changes needed.";
@@ -12292,11 +12749,11 @@ async function runPrdRunReconcileCommand(options) {
12292
12749
  }
12293
12750
  if (staleSucceededRecovery.mode === "recoverable" && artifactResult === "no_changes_needed") {
12294
12751
  outcomeResult = "changes_produced";
12295
- outcomeChangedPaths = [...staleSucceededRecovery.changedPlanningPaths];
12752
+ outcomeChangedPaths = [...staleSucceededRecovery.dirtyPaths];
12296
12753
  outcomeRationale = void 0;
12297
12754
  outcomeDiagnostics.push(...staleSucceededRecovery.diagnostics);
12298
12755
  }
12299
- if (!pathValidation.ok && outcomeResult !== "blocked") {
12756
+ if (!pathValidation.ok && outcomeResult !== "blocked" && observedWorktreeDiff.ok && observedWorktreeDiff.dirtyPaths.length === 0) {
12300
12757
  outcomeResult = "blocked";
12301
12758
  outcomeDiagnostics.push(
12302
12759
  `Unsafe changed planning paths: ${(pathValidation.rejected ?? []).join(", ")}`
@@ -13156,26 +13613,26 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
13156
13613
  const blockerBugs = [];
13157
13614
  const blockersFixed = [];
13158
13615
  const nonBlockers = [];
13159
- const prdMirrorPath = join19(repoRoot2, PRD_047_PRD_MIRROR_PATH);
13160
- const completionsDir = join19(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
13616
+ const prdMirrorPath = join20(repoRoot2, PRD_047_PRD_MIRROR_PATH);
13617
+ const completionsDir = join20(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
13161
13618
  let escapeHatchPresent = false;
13162
- if (existsSync16(completionsDir)) {
13619
+ if (existsSync17(completionsDir)) {
13163
13620
  const files = readdirSync6(completionsDir);
13164
13621
  escapeHatchPresent = files.some(
13165
13622
  (f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
13166
13623
  );
13167
13624
  }
13168
13625
  if (!escapeHatchPresent) {
13169
- escapeHatchPresent = existsSync16(join19(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
13626
+ escapeHatchPresent = existsSync17(join20(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
13170
13627
  }
13171
- const prdMirrorExists = existsSync16(prdMirrorPath);
13628
+ const prdMirrorExists = existsSync17(prdMirrorPath);
13172
13629
  if (!prdMirrorExists) {
13173
13630
  nonBlockers.push(
13174
13631
  `PRD-0047 PRD mirror not found at ${PRD_047_PRD_MIRROR_PATH}. Cannot verify scope alignment from published mirrors.`
13175
13632
  );
13176
13633
  }
13177
13634
  if (prdMirrorExists) {
13178
- const mirrorContent = readFileSync15(prdMirrorPath, "utf-8");
13635
+ const mirrorContent = readFileSync16(prdMirrorPath, "utf-8");
13179
13636
  const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
13180
13637
  const hasEscapeHatch = mirrorContent.includes("escape hatch");
13181
13638
  if (!hasReconcileCommand) {
@@ -13189,16 +13646,16 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
13189
13646
  );
13190
13647
  }
13191
13648
  }
13192
- const childIssueDir = join19(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
13193
- const childIssuesExist = existsSync16(childIssueDir);
13649
+ const childIssueDir = join20(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
13650
+ const childIssuesExist = existsSync17(childIssueDir);
13194
13651
  if (!childIssuesExist) {
13195
13652
  nonBlockers.push(
13196
13653
  `PRD-0047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
13197
13654
  );
13198
13655
  }
13199
- const manifestPath = join19(repoRoot2, PRD_047_MANIFEST_PATH);
13200
- if (existsSync16(manifestPath)) {
13201
- const manifestContent = readFileSync15(manifestPath, "utf-8");
13656
+ const manifestPath = join20(repoRoot2, PRD_047_MANIFEST_PATH);
13657
+ if (existsSync17(manifestPath)) {
13658
+ const manifestContent = readFileSync16(manifestPath, "utf-8");
13202
13659
  if (!manifestContent.includes("ready_for_prepare")) {
13203
13660
  nonBlockers.push(
13204
13661
  "Planning Artifact Manifest is not marked as ready_for_prepare."
@@ -13446,13 +13903,13 @@ async function runPrdRunLaunchCommand(options) {
13446
13903
  let finalReviewResult;
13447
13904
  if (!skipped.includes("final-review")) {
13448
13905
  attempted.push("final-review");
13449
- const localStorePath = join19(
13906
+ const localStorePath = join20(
13450
13907
  options.repoRoot,
13451
13908
  ".pourkit",
13452
13909
  "local-prd-runs",
13453
13910
  prdRef
13454
13911
  );
13455
- if (existsSync16(localStorePath)) {
13912
+ if (existsSync17(localStorePath)) {
13456
13913
  const targetConfig = options.config?.targets?.find(
13457
13914
  (t) => t.name === options.targetName
13458
13915
  );
@@ -14751,6 +15208,13 @@ async function processStartResult(startResult, options) {
14751
15208
  start.queueStartedAt = (/* @__PURE__ */ new Date()).toISOString();
14752
15209
  start.queueCommand = "queue-run";
14753
15210
  const existingRecord = readPrdRun(repoRoot2, prdRef);
15211
+ let resolvedMode;
15212
+ try {
15213
+ const target = options.config ? resolveTarget(options.config, start.targetName) : null;
15214
+ resolvedMode = target?.strategy ? resolvePrdRunMode(target) : void 0;
15215
+ } catch {
15216
+ resolvedMode = void 0;
15217
+ }
14754
15218
  writePrdRunRecord(repoRoot2, {
14755
15219
  prdRef,
14756
15220
  status: "running",
@@ -14760,8 +15224,8 @@ async function processStartResult(startResult, options) {
14760
15224
  manifestPath: existingRecord.record?.manifestPath,
14761
15225
  planning: existingRecord.record?.planning
14762
15226
  });
14763
- const localStorePath = join19(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
14764
- if (existsSync16(localStorePath)) {
15227
+ const localStorePath = join20(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
15228
+ if (existsSync17(localStorePath)) {
14765
15229
  const localIssues = await getRunnableLocalIssues(prdRef, repoRoot2);
14766
15230
  start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
14767
15231
  start.queueProcessedCount = localIssues.length;
@@ -14795,7 +15259,8 @@ async function processStartResult(startResult, options) {
14795
15259
  prdRef,
14796
15260
  queueRunContext: {
14797
15261
  prdRef,
14798
- prdBranch: start.prdBranch
15262
+ prdBranch: start.prdBranch,
15263
+ prdRunMode: resolvedMode
14799
15264
  }
14800
15265
  });
14801
15266
  if (outcome.selected === null && outcome.code === "drained") {
@@ -15340,7 +15805,7 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
15340
15805
  if (remoteResult.status !== 0) {
15341
15806
  return;
15342
15807
  }
15343
- const worktreePath = mkdtempSync(join19(tmpdir(), "pourkit-planning-"));
15808
+ const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-planning-"));
15344
15809
  try {
15345
15810
  runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
15346
15811
  runGitOrThrow(
@@ -15354,9 +15819,9 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
15354
15819
  "create planning branch"
15355
15820
  );
15356
15821
  for (const changedFile of changedFiles) {
15357
- const sourcePath = join19(repoRoot2, changedFile);
15358
- const targetPath = join19(worktreePath, changedFile);
15359
- mkdirSync10(dirname5(targetPath), { recursive: true });
15822
+ const sourcePath = join20(repoRoot2, changedFile);
15823
+ const targetPath = join20(worktreePath, changedFile);
15824
+ mkdirSync11(dirname5(targetPath), { recursive: true });
15360
15825
  cpSync(sourcePath, targetPath, { recursive: true });
15361
15826
  }
15362
15827
  runGitOrThrow(
@@ -15592,7 +16057,7 @@ function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
15592
16057
  changedFiles: Array.from(new Set(changedFiles))
15593
16058
  };
15594
16059
  }
15595
- function collectObservedReconciliationPlanningDiff(options) {
16060
+ function collectObservedReconciliationDirtyPaths(options) {
15596
16061
  const statusResult = spawnSync2("git", ["status", "--porcelain=v1", "-uall"], {
15597
16062
  cwd: options.worktreeCwd,
15598
16063
  encoding: "utf8"
@@ -15625,27 +16090,9 @@ function collectObservedReconciliationPlanningDiff(options) {
15625
16090
  )
15626
16091
  )
15627
16092
  );
15628
- const intrinsicallyRejectedPaths = candidatePaths.filter(
15629
- (path9) => !isAllowedPlanningPath(path9) || isBlockedPlanningPath(path9) || hasDirectoryTraversal(path9)
15630
- );
15631
- const allowedCandidatePaths = candidatePaths.filter(
15632
- (path9) => !intrinsicallyRejectedPaths.includes(path9)
15633
- );
15634
- const validation = validateChangedPlanningPaths(allowedCandidatePaths, {
15635
- repoRoot: options.worktreeCwd,
15636
- gitStatusShortLines,
15637
- gitDiffNameStatusLines
15638
- });
15639
- const rejectedPaths = Array.from(
15640
- /* @__PURE__ */ new Set([
15641
- ...intrinsicallyRejectedPaths,
15642
- ...validation.ok ? [] : validation.rejected ?? []
15643
- ])
15644
- ).sort();
15645
16093
  return {
15646
16094
  ok: true,
15647
- changedPlanningPaths: allowedCandidatePaths.filter((path9) => !rejectedPaths.includes(path9)).sort(),
15648
- rejectedPaths
16095
+ dirtyPaths: candidatePaths.sort()
15649
16096
  };
15650
16097
  }
15651
16098
  function gitRefExists(repoRoot2, ref) {
@@ -15670,7 +16117,7 @@ function parseGitStatusLine(line) {
15670
16117
  return { status, path: path9.replace(/^"|"$/g, "") };
15671
16118
  }
15672
16119
  function validateManifestArtifactExistence(repoRoot2, manifest) {
15673
- const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !existsSync16(path9)).map((path9) => toRepoRelativePath2(repoRoot2, path9));
16120
+ const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !existsSync17(path9)).map((path9) => toRepoRelativePath2(repoRoot2, path9));
15674
16121
  if (missingPaths.length > 0) {
15675
16122
  return {
15676
16123
  ok: false,
@@ -16086,7 +16533,7 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
16086
16533
  }
16087
16534
 
16088
16535
  // commands/init.ts
16089
- import { existsSync as existsSync17, statSync } from "fs";
16536
+ import { existsSync as existsSync18, statSync } from "fs";
16090
16537
  import {
16091
16538
  copyFile,
16092
16539
  mkdir as mkdir5,
@@ -16589,7 +17036,7 @@ async function computeFileChecksum(filePath) {
16589
17036
  return createHash3("sha256").update(content).digest("hex");
16590
17037
  }
16591
17038
  function lockfileExists(root, name) {
16592
- return existsSync17(path5.join(root, name));
17039
+ return existsSync18(path5.join(root, name));
16593
17040
  }
16594
17041
  function detectPackageManager(root) {
16595
17042
  if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
@@ -16633,7 +17080,7 @@ async function discoverLocalSource(sourcePath) {
16633
17080
  async function discoverReadme(root) {
16634
17081
  for (const name of ["README.md", "readme.md"]) {
16635
17082
  const p = path5.join(root, name);
16636
- if (existsSync17(p)) {
17083
+ if (existsSync18(p)) {
16637
17084
  return p;
16638
17085
  }
16639
17086
  }
@@ -16643,7 +17090,7 @@ async function discoverAgentFiles(root) {
16643
17090
  const files = [];
16644
17091
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
16645
17092
  const p = path5.join(root, name);
16646
- if (existsSync17(p)) {
17093
+ if (existsSync18(p)) {
16647
17094
  files.push(p);
16648
17095
  }
16649
17096
  }
@@ -16651,7 +17098,7 @@ async function discoverAgentFiles(root) {
16651
17098
  }
16652
17099
  async function discoverMerlleState(root) {
16653
17100
  const p = path5.join(root, ".pourkit", "state.json");
16654
- return existsSync17(p) ? p : null;
17101
+ return existsSync18(p) ? p : null;
16655
17102
  }
16656
17103
  async function discoverAgentSkills(root) {
16657
17104
  const dirs = [
@@ -16660,7 +17107,7 @@ async function discoverAgentSkills(root) {
16660
17107
  ];
16661
17108
  const found = [];
16662
17109
  for (const d of dirs) {
16663
- if (existsSync17(d)) {
17110
+ if (existsSync18(d)) {
16664
17111
  found.push(d);
16665
17112
  }
16666
17113
  }
@@ -16670,12 +17117,12 @@ async function discoverRootDomainDocs(root) {
16670
17117
  const docs = [];
16671
17118
  for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
16672
17119
  const p = path5.join(root, name);
16673
- if (existsSync17(p)) {
17120
+ if (existsSync18(p)) {
16674
17121
  docs.push(p);
16675
17122
  }
16676
17123
  }
16677
17124
  const adrDir = path5.join(root, "docs", "adr");
16678
- if (existsSync17(adrDir)) {
17125
+ if (existsSync18(adrDir)) {
16679
17126
  const entries = await readdir(adrDir, { withFileTypes: true });
16680
17127
  for (const entry of entries) {
16681
17128
  if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -16818,7 +17265,7 @@ async function planInit(options) {
16818
17265
  for (const file of skillFiles) {
16819
17266
  const relPath = path5.relative(s, file);
16820
17267
  const destPath = path5.join(targetRoot, ".agents", "skills", relPath);
16821
- if (!existsSync17(destPath)) {
17268
+ if (!existsSync18(destPath)) {
16822
17269
  operations.push({
16823
17270
  kind: "copy",
16824
17271
  sourcePath: file,
@@ -16881,7 +17328,7 @@ async function planInit(options) {
16881
17328
  });
16882
17329
  }
16883
17330
  if (sourceRoot) {
16884
- if (!existsSync17(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
17331
+ if (!existsSync18(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
16885
17332
  warnings.push(
16886
17333
  `--from-local path does not exist or is not a directory: ${sourceRoot}`
16887
17334
  );
@@ -17000,7 +17447,7 @@ async function planInit(options) {
17000
17447
  requiresConfirmation: false,
17001
17448
  destructive: false
17002
17449
  });
17003
- } else if (existsSync17(destPath)) {
17450
+ } else if (existsSync18(destPath)) {
17004
17451
  operations.push({
17005
17452
  kind: "skip",
17006
17453
  path: destPath,
@@ -17058,7 +17505,7 @@ async function planInit(options) {
17058
17505
  }
17059
17506
  }
17060
17507
  const contextPath = path5.join(targetRoot, ".pourkit", "CONTEXT.md");
17061
- if (!existsSync17(contextPath) && !merleDestPaths.has(contextPath)) {
17508
+ if (!existsSync18(contextPath) && !merleDestPaths.has(contextPath)) {
17062
17509
  operations.push({
17063
17510
  kind: "create",
17064
17511
  path: contextPath,
@@ -17076,7 +17523,7 @@ async function planInit(options) {
17076
17523
  "adr",
17077
17524
  ".gitkeep"
17078
17525
  );
17079
- if (!existsSync17(adrGitkeep)) {
17526
+ if (!existsSync18(adrGitkeep)) {
17080
17527
  operations.push({
17081
17528
  kind: "create",
17082
17529
  path: adrGitkeep,
@@ -17088,7 +17535,7 @@ async function planInit(options) {
17088
17535
  }
17089
17536
  const srcDocAgents = path5.join(sourceRoot, ".pourkit", "docs", "agents");
17090
17537
  const tgtDocAgents = path5.join(targetRoot, ".pourkit", "docs", "agents");
17091
- if (existsSync17(srcDocAgents) && !existsSync17(tgtDocAgents)) {
17538
+ if (existsSync18(srcDocAgents) && !existsSync18(tgtDocAgents)) {
17092
17539
  const docFiles = await walkDir(srcDocAgents);
17093
17540
  for (const file of docFiles) {
17094
17541
  const relPath = path5.relative(srcDocAgents, file);
@@ -17121,7 +17568,7 @@ async function planInit(options) {
17121
17568
  }
17122
17569
  const srcPrompts = path5.join(sourceRoot, ".pourkit", "prompts");
17123
17570
  const tgtPrompts = path5.join(targetRoot, ".pourkit", "prompts");
17124
- if (existsSync17(srcPrompts) && !existsSync17(tgtPrompts)) {
17571
+ if (existsSync18(srcPrompts) && !existsSync18(tgtPrompts)) {
17125
17572
  const promptFiles = await walkDir(srcPrompts);
17126
17573
  for (const file of promptFiles) {
17127
17574
  const relPath = path5.relative(srcPrompts, file);
@@ -17148,7 +17595,7 @@ async function planInit(options) {
17148
17595
  ".sandcastle",
17149
17596
  "Dockerfile"
17150
17597
  );
17151
- if (existsSync17(tgtSandboxDockerfile)) {
17598
+ if (existsSync18(tgtSandboxDockerfile)) {
17152
17599
  operations.push({
17153
17600
  kind: "skip",
17154
17601
  path: tgtSandboxDockerfile,
@@ -17157,7 +17604,7 @@ async function planInit(options) {
17157
17604
  requiresConfirmation: false,
17158
17605
  destructive: false
17159
17606
  });
17160
- } else if (existsSync17(srcSandboxDockerfile)) {
17607
+ } else if (existsSync18(srcSandboxDockerfile)) {
17161
17608
  const checksum = await computeFileChecksum(srcSandboxDockerfile);
17162
17609
  operations.push({
17163
17610
  kind: "copy",
@@ -17171,7 +17618,7 @@ async function planInit(options) {
17171
17618
  });
17172
17619
  }
17173
17620
  const configTsPath = path5.join(targetRoot, "pourkit.config.ts");
17174
- if (!existsSync17(configTsPath)) {
17621
+ if (!existsSync18(configTsPath)) {
17175
17622
  const verifyCommands = inferVerificationCommands(
17176
17623
  packageScripts,
17177
17624
  pm || "npm"
@@ -17208,7 +17655,7 @@ async function planInit(options) {
17208
17655
  const hasExistingAgents = operations.some(
17209
17656
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
17210
17657
  );
17211
- if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync17(path5.join(targetRoot, "AGENTS.md"))) {
17658
+ if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync18(path5.join(targetRoot, "AGENTS.md"))) {
17212
17659
  operations.push({
17213
17660
  kind: "create",
17214
17661
  path: path5.join(targetRoot, "AGENTS.md"),
@@ -17224,7 +17671,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
17224
17671
  const hasExistingClaude = operations.some(
17225
17672
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
17226
17673
  );
17227
- if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync17(path5.join(targetRoot, "CLAUDE.md"))) {
17674
+ if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync18(path5.join(targetRoot, "CLAUDE.md"))) {
17228
17675
  operations.push({
17229
17676
  kind: "create",
17230
17677
  path: path5.join(targetRoot, "CLAUDE.md"),
@@ -17239,7 +17686,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
17239
17686
  }
17240
17687
  const gitignoreTarget = path5.join(targetRoot, ".gitignore");
17241
17688
  const gitignoreContent = generateGitignoreBlock();
17242
- if (!existsSync17(gitignoreTarget)) {
17689
+ if (!existsSync18(gitignoreTarget)) {
17243
17690
  operations.push({
17244
17691
  kind: "create",
17245
17692
  path: gitignoreTarget,
@@ -17263,7 +17710,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
17263
17710
  });
17264
17711
  }
17265
17712
  const openCodePath = path5.join(targetRoot, "opencode.json");
17266
- if (!existsSync17(openCodePath)) {
17713
+ if (!existsSync18(openCodePath)) {
17267
17714
  operations.push({
17268
17715
  kind: "create",
17269
17716
  path: openCodePath,
@@ -17320,7 +17767,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
17320
17767
  }
17321
17768
  }
17322
17769
  const manifestPath = path5.join(targetRoot, ".pourkit", "manifest.json");
17323
- if (existsSync17(manifestPath)) {
17770
+ if (existsSync18(manifestPath)) {
17324
17771
  operations.push({
17325
17772
  kind: "skip",
17326
17773
  path: manifestPath,
@@ -17639,7 +18086,7 @@ async function updateManagedBlock(filePath, content) {
17639
18086
  const blockContent = `${MANAGED_BLOCK_BEGIN}
17640
18087
  ${content}${MANAGED_BLOCK_END}
17641
18088
  `;
17642
- if (!existsSync17(filePath)) {
18089
+ if (!existsSync18(filePath)) {
17643
18090
  const dir = path5.dirname(filePath);
17644
18091
  await mkdir5(dir, { recursive: true });
17645
18092
  await writeFileAtomic(filePath, blockContent);
@@ -17668,7 +18115,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
17668
18115
  if (op.requiresConfirmation) continue;
17669
18116
  const relPath = path5.relative(plan.targetRoot, op.path);
17670
18117
  if (relPath === ".pourkit/manifest.json") continue;
17671
- if (existsSync17(op.path)) {
18118
+ if (existsSync18(op.path)) {
17672
18119
  const sha256 = await computeFileChecksum(op.path);
17673
18120
  assets[relPath] = {
17674
18121
  ownership: op.ownership || "managed",
@@ -17713,7 +18160,7 @@ async function applyInitPlan(plan, options) {
17713
18160
  skipped++;
17714
18161
  continue;
17715
18162
  }
17716
- if (existsSync17(op.path) && !op.destructive) {
18163
+ if (existsSync18(op.path) && !op.destructive) {
17717
18164
  skipped++;
17718
18165
  continue;
17719
18166
  }
@@ -17728,7 +18175,7 @@ async function applyInitPlan(plan, options) {
17728
18175
  skipped++;
17729
18176
  continue;
17730
18177
  }
17731
- if (existsSync17(op.path)) {
18178
+ if (existsSync18(op.path)) {
17732
18179
  skipped++;
17733
18180
  continue;
17734
18181
  }
@@ -17757,7 +18204,7 @@ async function applyInitPlan(plan, options) {
17757
18204
  skipped++;
17758
18205
  continue;
17759
18206
  }
17760
- if (existsSync17(op.path)) {
18207
+ if (existsSync18(op.path)) {
17761
18208
  skipped++;
17762
18209
  continue;
17763
18210
  }
@@ -17897,7 +18344,7 @@ async function applyInitFromSource(options) {
17897
18344
  if (!manifestSkipped) {
17898
18345
  const agentFiles = [];
17899
18346
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
17900
- if (existsSync17(path5.join(targetRoot, name))) {
18347
+ if (existsSync18(path5.join(targetRoot, name))) {
17901
18348
  agentFiles.push(path5.join(targetRoot, name));
17902
18349
  }
17903
18350
  }
@@ -18716,8 +19163,8 @@ function formatChecks2(checks) {
18716
19163
  init_common();
18717
19164
 
18718
19165
  // execution/sandcastle-execution.ts
18719
- import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync7 } from "fs";
18720
- import { join as join21 } from "path";
19166
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync8 } from "fs";
19167
+ import { join as join22 } from "path";
18721
19168
  import { createWorktree, opencode } from "@ai-hero/sandcastle";
18722
19169
  import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
18723
19170
 
@@ -18726,10 +19173,10 @@ init_common();
18726
19173
  import { mkdtempSync as mkdtempSync2 } from "fs";
18727
19174
  import { writeFile as writeFile3 } from "fs/promises";
18728
19175
  import { tmpdir as tmpdir2 } from "os";
18729
- import { dirname as dirname6, join as join20 } from "path";
19176
+ import { dirname as dirname6, join as join21 } from "path";
18730
19177
  async function writeExecutionArtifacts(worktreePath, artifacts) {
18731
19178
  for (const artifact of artifacts) {
18732
- const filePath = join20(worktreePath, artifact.path);
19179
+ const filePath = join21(worktreePath, artifact.path);
18733
19180
  await ensureDir(dirname6(filePath));
18734
19181
  await writeFile3(filePath, artifact.content, "utf-8");
18735
19182
  }
@@ -18741,17 +19188,17 @@ import path7 from "path";
18741
19188
 
18742
19189
  // execution/sandbox-image.ts
18743
19190
  import { createHash as createHash4 } from "crypto";
18744
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
19191
+ import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
18745
19192
  import path6 from "path";
18746
19193
  function sandboxImageName(repoRoot2) {
18747
19194
  const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
18748
19195
  const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
18749
19196
  const baseName = sanitized || "local";
18750
19197
  const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
18751
- if (!existsSync18(dockerfilePath)) {
19198
+ if (!existsSync19(dockerfilePath)) {
18752
19199
  return `sandcastle:${baseName}`;
18753
19200
  }
18754
- const fingerprint = createHash4("sha256").update(readFileSync16(dockerfilePath)).digest("hex").slice(0, 8);
19201
+ const fingerprint = createHash4("sha256").update(readFileSync17(dockerfilePath)).digest("hex").slice(0, 8);
18755
19202
  return `sandcastle:${baseName}-${fingerprint}`;
18756
19203
  }
18757
19204
 
@@ -18901,11 +19348,7 @@ var SandcastleExecutionSession = class {
18901
19348
  type: "file",
18902
19349
  path: logPath,
18903
19350
  onAgentStreamEvent: (event) => {
18904
- if (event.type === "text") {
18905
- logger.raw(event.message);
18906
- } else if (event.type === "toolCall") {
18907
- logger.raw(`${event.name}(${event.formattedArgs})`);
18908
- }
19351
+ logger.raw(formatAgentStreamEvent(event));
18909
19352
  }
18910
19353
  },
18911
19354
  completionSignal: "<promise>COMPLETE</promise>",
@@ -18990,14 +19433,59 @@ function resolveSandboxProvider(provider, dockerFactory) {
18990
19433
  function sanitizeBranch(branchName) {
18991
19434
  return branchName.replace(/[^A-Za-z0-9._-]/g, "-");
18992
19435
  }
19436
+ function formatAgentStreamEvent(event) {
19437
+ if (event.type === "text") {
19438
+ return JSON.stringify({ type: "text", textCount: event.message.length });
19439
+ }
19440
+ const parsedArgs = parseToolCallArgs(event.formattedArgs);
19441
+ if (!parsedArgs.ok) {
19442
+ return JSON.stringify({
19443
+ type: "toolCall",
19444
+ name: event.name,
19445
+ argsTextCount: event.formattedArgs.length
19446
+ });
19447
+ }
19448
+ return JSON.stringify({
19449
+ type: "toolCall",
19450
+ name: event.name,
19451
+ args: summarizeToolCallArgs(event.name, parsedArgs.value)
19452
+ });
19453
+ }
19454
+ function parseToolCallArgs(formattedArgs) {
19455
+ try {
19456
+ return { ok: true, value: JSON.parse(formattedArgs) };
19457
+ } catch {
19458
+ return { ok: false };
19459
+ }
19460
+ }
19461
+ function summarizeToolCallArgs(name, args) {
19462
+ if (!isPlainObject(args)) {
19463
+ return args;
19464
+ }
19465
+ if (name.toLowerCase() !== "write") {
19466
+ return args;
19467
+ }
19468
+ const summarizedArgs = {};
19469
+ for (const [key, value] of Object.entries(args)) {
19470
+ if (key === "content" && typeof value === "string") {
19471
+ summarizedArgs.contentCount = value.length;
19472
+ continue;
19473
+ }
19474
+ summarizedArgs[key] = value;
19475
+ }
19476
+ return summarizedArgs;
19477
+ }
19478
+ function isPlainObject(value) {
19479
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19480
+ }
18993
19481
  function savePromptToFile(repoRoot2, stage, iteration, prompt) {
18994
- const promptsDir = join21(repoRoot2, ".pourkit", ".tmp", "prompts");
18995
- mkdirSync11(promptsDir, { recursive: true });
19482
+ const promptsDir = join22(repoRoot2, ".pourkit", ".tmp", "prompts");
19483
+ mkdirSync12(promptsDir, { recursive: true });
18996
19484
  const timestamp2 = Date.now();
18997
19485
  const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
18998
19486
  const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
18999
- const filePath = join21(promptsDir, filename);
19000
- writeFileSync7(filePath, prompt, "utf-8");
19487
+ const filePath = join22(promptsDir, filename);
19488
+ writeFileSync8(filePath, prompt, "utf-8");
19001
19489
  }
19002
19490
 
19003
19491
  // cli.ts
@@ -19066,6 +19554,35 @@ function createCliProgram(version) {
19066
19554
  const program = new Command();
19067
19555
  program.name("pourkit").version(version).exitOverride().description("AI-driven issue-to-PR workflow for GitHub repositories.");
19068
19556
  const issueCommand = program.command("issue");
19557
+ program.command("run-verification").description("Run configured verification commands for a target").option("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(async (options) => {
19558
+ const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
19559
+ const logPath = path8.join(
19560
+ targetRepoRoot,
19561
+ ".pourkit",
19562
+ "logs",
19563
+ "run-verification.log"
19564
+ );
19565
+ const logger = createLogger("pourkit", logPath);
19566
+ let failed = false;
19567
+ try {
19568
+ const config = await loadRepoConfig(targetRepoRoot);
19569
+ const target = resolveTarget(config, options.target);
19570
+ const result = await runVerificationCommands({
19571
+ target,
19572
+ cwd: targetRepoRoot,
19573
+ logger
19574
+ });
19575
+ console.log(JSON.stringify(result, null, 2));
19576
+ failed = !result.ok;
19577
+ } catch (error) {
19578
+ await handleError(logger, error);
19579
+ } finally {
19580
+ await logger.close();
19581
+ }
19582
+ if (failed) {
19583
+ process.exit(1);
19584
+ }
19585
+ });
19069
19586
  program.command("validate-artifact").description("Validate an agent handoff artifact").argument(
19070
19587
  "<kind>",
19071
19588
  "artifact kind: reviewer, refactor, finalizer, conflict-resolution, failure-resolution, reconciliation, planning-manifest, local-prd, local-issue, local-triage, or local-prepare"
@@ -19841,11 +20358,11 @@ function createCliProgram(version) {
19841
20358
  return program;
19842
20359
  }
19843
20360
  async function resolveCliVersion() {
19844
- if (isPackageVersion("0.0.0-next-20260607223610")) {
19845
- return "0.0.0-next-20260607223610";
20361
+ if (isPackageVersion("0.0.0-next-20260608072322")) {
20362
+ return "0.0.0-next-20260608072322";
19846
20363
  }
19847
- if (isReleaseVersion("0.0.0-next-20260607223610")) {
19848
- return "0.0.0-next-20260607223610";
20364
+ if (isReleaseVersion("0.0.0-next-20260608072322")) {
20365
+ return "0.0.0-next-20260608072322";
19849
20366
  }
19850
20367
  try {
19851
20368
  const root = repoRoot();