@pourkit/cli 0.0.0-next-20260621045826 → 0.0.0-next-20260621233457

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
@@ -442,6 +442,25 @@ function checkRemovedFields(raw) {
442
442
  );
443
443
  }
444
444
  }
445
+ const strategy = target.strategy;
446
+ if (strategy && typeof strategy === "object") {
447
+ const review = strategy.review;
448
+ if (review && typeof review === "object") {
449
+ if ("passWithNotesRefactorAttempts" in review) {
450
+ throw new Error(
451
+ `targets[${i}].strategy.review.passWithNotesRefactorAttempts has been removed; PASS_WITH_NOTES no longer triggers Refactor.`
452
+ );
453
+ }
454
+ const reviewer = review.reviewer;
455
+ if (reviewer && typeof reviewer === "object") {
456
+ if ("passWithNotesRefactorAttempts" in reviewer) {
457
+ throw new Error(
458
+ `targets[${i}].strategy.review.reviewer.passWithNotesRefactorAttempts has been removed; PASS_WITH_NOTES no longer triggers Refactor.`
459
+ );
460
+ }
461
+ }
462
+ }
463
+ }
445
464
  }
446
465
  }
447
466
  }
@@ -773,14 +792,12 @@ function parseConfig(raw) {
773
792
  reviewReviewer.outputRetries
774
793
  ),
775
794
  criteria: reviewReviewer.criteria,
776
- includeReviewHistory: reviewReviewer.includeReviewHistory,
777
- passWithNotesRefactorAttempts: reviewReviewer.passWithNotesRefactorAttempts
795
+ includeReviewHistory: reviewReviewer.includeReviewHistory
778
796
  },
779
797
  refactor: applyStageAgentDefaults(
780
798
  reviewRefactor
781
799
  ),
782
- maxIterations: review.maxIterations,
783
- passWithNotesRefactorAttempts: review.passWithNotesRefactorAttempts ?? 2
800
+ maxIterations: review.maxIterations
784
801
  },
785
802
  ...verifyLabeled ? { verify: { commands: verifyLabeled } } : {},
786
803
  issueFinalReview: {
@@ -880,11 +897,11 @@ var OBSOLETE_CONFIG_PATHS = [
880
897
  ];
881
898
  var CANONICAL_CONFIG_PATH = ".pourkit/config.json";
882
899
  async function loadRepoConfig(repoRoot2, _configFileName) {
883
- const { existsSync: existsSync21 } = await import("fs");
900
+ const { existsSync: existsSync22 } = await import("fs");
884
901
  const { join: pjoin } = await import("path");
885
902
  for (const obPath of OBSOLETE_CONFIG_PATHS) {
886
903
  const fullPath = pjoin(repoRoot2, obPath);
887
- if (existsSync21(fullPath)) {
904
+ if (existsSync22(fullPath)) {
888
905
  const isRootJson = obPath === "pourkit.json";
889
906
  throw new Error(
890
907
  isRootJson ? `Found root ${obPath}, but Pourkit config now lives at ${CANONICAL_CONFIG_PATH}. Move the file and update "$schema" to "./schema/pourkit.schema.json".` : `Found ${obPath}, but executable config is no longer supported. Move configuration to ${CANONICAL_CONFIG_PATH} with "$schema": "./schema/pourkit.schema.json".`
@@ -892,7 +909,7 @@ async function loadRepoConfig(repoRoot2, _configFileName) {
892
909
  }
893
910
  }
894
911
  const configPath = pjoin(repoRoot2, CANONICAL_CONFIG_PATH);
895
- if (!existsSync21(configPath)) {
912
+ if (!existsSync22(configPath)) {
896
913
  throw new Error(
897
914
  `No Pourkit config found at ${CANONICAL_CONFIG_PATH}. Run pourkit init or create ${CANONICAL_CONFIG_PATH} with "$schema": "./schema/pourkit.schema.json".`
898
915
  );
@@ -1168,9 +1185,11 @@ function parseReviewVerdict(output) {
1168
1185
  }
1169
1186
 
1170
1187
  // commands/review.ts
1188
+ import { Effect as Effect3 } from "effect";
1189
+
1190
+ // issues/issue-review-loop.ts
1171
1191
  import {
1172
1192
  existsSync as existsSync6,
1173
- mkdirSync as mkdirSync5,
1174
1193
  readFileSync as readFileSync6,
1175
1194
  readdirSync as readdirSync2,
1176
1195
  writeFileSync as writeFileSync3
@@ -1465,7 +1484,7 @@ function buildRunContextMarkdown(options) {
1465
1484
  );
1466
1485
  }
1467
1486
  if (sections.includes("verification-commands")) {
1468
- parts.push(...renderCommandList(target, "Verification Commands"));
1487
+ parts.push(...renderCommandList(target, "Verification Commands", repoRoot2));
1469
1488
  }
1470
1489
  if (sections.includes("review-criteria")) {
1471
1490
  parts.push(...renderCriteria(reviewerCriteria));
@@ -1483,14 +1502,27 @@ function buildRunContextMarkdown(options) {
1483
1502
  }
1484
1503
  return parts.join("\n");
1485
1504
  }
1486
- function renderCommandList(target, heading) {
1505
+ function renderCommandList(target, heading, repoRoot2) {
1487
1506
  const commands = getVerificationCommands(target);
1507
+ const wrapperCommand = buildRunVerificationCommand(target);
1488
1508
  const parts = [
1489
1509
  `## ${heading}`,
1490
1510
  "",
1491
- `Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
1511
+ `Run this command from the repository root: \`${wrapperCommand}\``,
1512
+ "",
1513
+ "Use the wrapper as the configured verification method for Builder, Reviewer, Refactor, and Issue Final Review handoffs. Run the underlying commands directly only when no wrapper path can run, and record that degraded path in the role's artifact or handoff notes.",
1492
1514
  ""
1493
1515
  ];
1516
+ if (isPourkitSourceRepo(repoRoot2)) {
1517
+ parts.push(
1518
+ "Pourkit self-development fallback:",
1519
+ "",
1520
+ `- If \`${wrapperCommand}\` fails before running configured commands because the installed CLI rejects this worktree's config schemaVersion, build and rerun the same target with the worktree-local CLI: \`npm run build --workspace pourkit\`, then \`node pourkit/dist/cli.js run-verification --target ${target.name}\`.`,
1521
+ '- Treat local CLI wrapper success as valid configured verification and record it as `verification.method: "local-wrapper"` in Issue Final Review artifacts, or as local-wrapper verification in Builder/Reviewer/Refactor handoff notes.',
1522
+ '- Fall back to the underlying configured commands only if the worktree-local CLI cannot be built or invoked; record that degraded path as `verification.method: "commands"` in Issue Final Review artifacts, or as direct-command verification in Builder/Reviewer/Refactor handoff notes, and explain why the wrapper path could not run.',
1523
+ ""
1524
+ );
1525
+ }
1494
1526
  if (commands.length > 0) {
1495
1527
  parts.push(
1496
1528
  "Configured commands executed by the wrapper:",
@@ -1503,6 +1535,17 @@ function renderCommandList(target, heading) {
1503
1535
  }
1504
1536
  return parts;
1505
1537
  }
1538
+ function isPourkitSourceRepo(repoRoot2) {
1539
+ if (!repoRoot2) return false;
1540
+ const packagePath = join5(repoRoot2, "pourkit", "package.json");
1541
+ if (!existsSync4(packagePath)) return false;
1542
+ try {
1543
+ const parsed = JSON.parse(readFileSync4(packagePath, "utf-8"));
1544
+ return parsed.name === "@pourkit/cli";
1545
+ } catch {
1546
+ return false;
1547
+ }
1548
+ }
1506
1549
  function renderParentPrdContext(issue, parentPrdIssue, repoRoot2) {
1507
1550
  const parent = extractIssueSection(issue.body, "Parent");
1508
1551
  const parentRef = parent?.match(/\bPRD-\d+\b/i)?.[0]?.toUpperCase() ?? issue.title.match(/^\s*(PRD-\d+)\s*\/\s*I-\d+\b/i)?.[1]?.toUpperCase();
@@ -1971,7 +2014,7 @@ function validateRecoveryDecision(artifact, allowedDecisions) {
1971
2014
  return { valid: true, decision };
1972
2015
  }
1973
2016
 
1974
- // commands/review.ts
2017
+ // issues/issue-review-loop.ts
1975
2018
  import { Effect as Effect2, Layer as Layer2 } from "effect";
1976
2019
  var ReviewArtifactValidationError = class extends Error {
1977
2020
  constructor(message) {
@@ -2162,6 +2205,172 @@ function validateReviewArtifact(output, verdict, iteration, priorRefactorArtifac
2162
2205
  }
2163
2206
  }
2164
2207
  }
2208
+ function renderReviewHistory(reviewHistory) {
2209
+ if (reviewHistory.length === 0) {
2210
+ return "";
2211
+ }
2212
+ return `## Review History
2213
+
2214
+ ${reviewHistory.map((entry, index) => `### Iteration ${index + 1}
2215
+
2216
+ ${entry.trimEnd()}`).join("\n\n")}
2217
+
2218
+ `;
2219
+ }
2220
+ function renderReviewCriteriaEffect(repoRoot2, criteria, fs) {
2221
+ return Effect2.gen(function* () {
2222
+ const parts = [];
2223
+ for (const criterion of criteria) {
2224
+ const snippetPath = join6(
2225
+ repoRoot2,
2226
+ ".pourkit",
2227
+ "managed",
2228
+ "prompts",
2229
+ `reviewer-${criterion}.snippet.md`
2230
+ );
2231
+ const exists = yield* fs.exists(snippetPath).pipe(Effect2.orDie);
2232
+ if (exists) {
2233
+ const content = yield* fs.readFile(snippetPath).pipe(Effect2.orDie);
2234
+ parts.push(content.trimEnd());
2235
+ } else {
2236
+ parts.push(`- ${criterion}`);
2237
+ }
2238
+ }
2239
+ return parts.join("\n\n");
2240
+ });
2241
+ }
2242
+ function loadReviewerPromptTemplateEffect(repoRoot2, promptTemplate, criteriaBlock, fs) {
2243
+ return Effect2.gen(function* () {
2244
+ const promptTemplatePath = resolvePromptTemplatePath(
2245
+ repoRoot2,
2246
+ promptTemplate
2247
+ );
2248
+ const exists = yield* fs.exists(promptTemplatePath).pipe(Effect2.orDie);
2249
+ const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect2.orDie) : promptTemplate;
2250
+ const hasCriteriaPlaceholder = promptBody.includes("{{REVIEW_CRITERIA}}");
2251
+ return {
2252
+ content: promptBody.replace(/\{\{REVIEW_CRITERIA\}\}/g, criteriaBlock),
2253
+ hasCriteriaPlaceholder
2254
+ };
2255
+ });
2256
+ }
2257
+ function buildReviewerPromptEffect(repoRoot2, promptTemplate, criteria, artifactPathInWorktree, iteration, fs, reviewHistory = [], priorRefactorArtifacts, humanHandoffResolved, priorReviewerArtifacts) {
2258
+ return Effect2.gen(function* () {
2259
+ const criteriaBlock = yield* renderReviewCriteriaEffect(
2260
+ repoRoot2,
2261
+ criteria,
2262
+ fs
2263
+ );
2264
+ const { content: renderedTemplate, hasCriteriaPlaceholder } = yield* loadReviewerPromptTemplateEffect(
2265
+ repoRoot2,
2266
+ promptTemplate,
2267
+ criteriaBlock,
2268
+ fs
2269
+ );
2270
+ const priorRefactorBlock = priorRefactorArtifacts ? `${priorRefactorArtifacts}` : "";
2271
+ const priorRefactorProtocolReminder = priorRefactorArtifacts ? `## Prior Refactor Protocol Reminder
2272
+
2273
+ MANDATORY: because this prompt contains ## Prior Refactor Artifacts, your artifact must contain ## Prior Refactor Response Assessment exactly, before verdict. Section headings must match the names defined in this protocol exactly.
2274
+
2275
+ ` : "";
2276
+ const priorReviewerBlock = priorReviewerArtifacts ? `${priorReviewerArtifacts}` : "";
2277
+ const humanHandoffBoundary = humanHandoffResolved ? `## Human-Resolved Handoff Boundary
2278
+
2279
+ A prior review emitted \`NEEDS_HUMAN\` and stopped the agent loop. The issue has since been moved back to \`ready-for-agent\`.
2280
+
2281
+ Before carrying forward old blockers, inspect newer issue comments and the current worktree. Treat prior Reviewer and Refactor Artifacts as historical context, not active findings unless they still apply.
2282
+
2283
+ ` : "";
2284
+ return appendProtectedWorkGuidance(`${renderedTemplate}
2285
+
2286
+ ## Shared Run Context
2287
+
2288
+ Read the selected issue requirements, planning context (including plan and PRD content when present), branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
2289
+
2290
+ ## Initial Verification Pass
2291
+
2292
+ - First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
2293
+ - Before reviewing code, diffs, artifacts, or prior findings, run the configured verification wrapper yourself from the Worktree.
2294
+ - If ${RUN_CONTEXT_PATH_IN_WORKTREE} provides a worktree-local CLI fallback, use that fallback before considering direct command execution.
2295
+ - Run underlying verification commands directly only when no wrapper path can run, and record why verification used direct commands.
2296
+ - If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
2297
+ - If a command cannot run because the environment is missing required setup, dependencies, or scripts outside agent control, treat it as a human handoff blocker.
2298
+ - If no verification commands are configured, note that and proceed with normal review.
2299
+
2300
+ ## Scope Evidence Rules
2301
+
2302
+ - Use the Run Context's canonical base ref, for example \`origin/<base>\`, for scope diffs and commit ranges.
2303
+ - Do not use bare PRD/base branch names such as \`PRD-0063\`, \`main\`, or \`dev\` for scope decisions; local branches with those names may be stale.
2304
+ - Only call a file or commit out of scope when it is part of the working branch's delta from the canonical base ref.
2305
+ - Do not recommend reverting accepted sibling/base work. If a file is present or changed on the canonical base ref, it is not this Issue's scope overreach.
2306
+ - If scope evidence is ambiguous, use \`NEEDS_HUMAN\` or ask for a runner/base mismatch decision instead of telling Refactor to revert files.
2307
+
2308
+ ${hasCriteriaPlaceholder ? "" : `## Review Criteria
2309
+
2310
+ ${criteriaBlock}
2311
+
2312
+ `}${humanHandoffBoundary}${priorReviewerBlock}${renderReviewHistory(reviewHistory)}${priorRefactorBlock}${priorRefactorProtocolReminder}## Output
2313
+
2314
+ Write your review to: ${artifactPathInWorktree}
2315
+
2316
+ Before handoff, run: pourkit validate-artifact reviewer ${artifactPathInWorktree} --iteration ${iteration}${priorRefactorArtifacts ? " --prior-refactor-artifacts" : ""}
2317
+
2318
+ Do not provide a separate chat response. The runner only reads the file above.
2319
+
2320
+ End the file with exactly one wrapped verdict token: <verdict>PASS</verdict>, <verdict>PASS_WITH_NOTES</verdict>, <verdict>NEEDS_REFACTOR</verdict>, <verdict>FAIL</verdict>, or <verdict>NEEDS_HUMAN</verdict>. The verdict token must appear exactly once in the output.
2321
+
2322
+ Findings must include an ID column with values in the format R${iteration}.F{findingNumber} (e.g., R${iteration}.F1, R${iteration}.F2) and a Supersedes column referencing the finding ID being superseded (or a hyphen for new findings).
2323
+
2324
+ When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token.`);
2325
+ });
2326
+ }
2327
+ function recoverReviewOutputFromString(logContent) {
2328
+ const startIndex = logContent.indexOf("## Findings");
2329
+ if (startIndex === -1) return null;
2330
+ const verdictMatch = logContent.slice(startIndex).match(
2331
+ /<verdict>(PASS|PASS_WITH_NOTES|NEEDS_REFACTOR|FAIL|NEEDS_HUMAN)<\/verdict>/
2332
+ );
2333
+ if (!verdictMatch || verdictMatch.index === void 0) return null;
2334
+ const recoveredOutput = logContent.slice(startIndex, startIndex + verdictMatch.index + verdictMatch[0].length).trim();
2335
+ return recoveredOutput.length > 0 ? recoveredOutput : null;
2336
+ }
2337
+ function recoverReviewOutputFromLog(logPath) {
2338
+ if (!existsSync6(logPath)) {
2339
+ return null;
2340
+ }
2341
+ const logContent = readFileSync6(logPath, "utf-8");
2342
+ return recoverReviewOutputFromString(logContent);
2343
+ }
2344
+ function readReviewArtifact(artifactPath, logPath) {
2345
+ if (existsSync6(artifactPath)) {
2346
+ const output = readFileSync6(artifactPath, "utf-8");
2347
+ if (output.trim()) {
2348
+ return output;
2349
+ }
2350
+ }
2351
+ const recoveredOutput = logPath ? recoverReviewOutputFromLog(logPath) : null;
2352
+ if (recoveredOutput) {
2353
+ writeFileSync3(artifactPath, recoveredOutput, "utf-8");
2354
+ return recoveredOutput;
2355
+ }
2356
+ if (!existsSync6(artifactPath)) {
2357
+ throw new Error(`Reviewer did not produce output at ${artifactPath}`);
2358
+ }
2359
+ throw new Error(`Reviewer produced empty output at ${artifactPath}`);
2360
+ }
2361
+ function bridgeExecutionProvider(ep) {
2362
+ return Layer2.succeed(
2363
+ ExecutionProvider,
2364
+ ExecutionProvider.of({
2365
+ execute: (opts) => Effect2.tryPromise({
2366
+ try: () => ep.execute(opts),
2367
+ catch: (e) => new Error(
2368
+ e instanceof Error ? e.message : `Execution failed: ${String(e)}`
2369
+ )
2370
+ })
2371
+ })
2372
+ );
2373
+ }
2165
2374
  function runReviewCommandEffect(options) {
2166
2375
  const {
2167
2376
  executionProvider,
@@ -2314,100 +2523,43 @@ function runReviewCommandEffect(options) {
2314
2523
  return { verdict: parsedVerdict, output, artifactPath };
2315
2524
  });
2316
2525
  }
2317
- function bridgeExecutionProvider(ep) {
2318
- return Layer2.succeed(
2319
- ExecutionProvider,
2320
- ExecutionProvider.of({
2321
- execute: (opts) => Effect2.tryPromise({
2322
- try: () => ep.execute(opts),
2323
- catch: (e) => new Error(
2324
- e instanceof Error ? e.message : `Execution failed: ${String(e)}`
2325
- )
2326
- })
2327
- })
2328
- );
2329
- }
2330
- function buildReviewerPromptEffect(repoRoot2, promptTemplate, criteria, artifactPathInWorktree, iteration, fs, reviewHistory = [], priorRefactorArtifacts, humanHandoffResolved, priorReviewerArtifacts) {
2526
+ function buildRefactorPromptEffect(repoRoot2, promptTemplate, latestReview, artifactPathInWorktree, iteration, fs) {
2331
2527
  return Effect2.gen(function* () {
2332
- const criteriaBlock = yield* renderReviewCriteriaEffect(
2333
- repoRoot2,
2334
- criteria,
2335
- fs
2336
- );
2337
- const { content: renderedTemplate, hasCriteriaPlaceholder } = yield* loadReviewerPromptTemplateEffect(
2528
+ const promptTemplatePath = resolvePromptTemplatePath(
2338
2529
  repoRoot2,
2339
- promptTemplate,
2340
- criteriaBlock,
2341
- fs
2530
+ promptTemplate
2342
2531
  );
2343
- const priorRefactorBlock = priorRefactorArtifacts ? `${priorRefactorArtifacts}` : "";
2344
- const priorRefactorProtocolReminder = priorRefactorArtifacts ? `## Prior Refactor Protocol Reminder
2345
-
2346
- MANDATORY: because this prompt contains ## Prior Refactor Artifacts, your artifact must contain ## Prior Refactor Response Assessment exactly, before verdict. Section headings must match the names defined in this protocol exactly.
2347
-
2348
- ` : "";
2349
- const priorReviewerBlock = priorReviewerArtifacts ? `${priorReviewerArtifacts}` : "";
2350
- const humanHandoffBoundary = humanHandoffResolved ? `## Human-Resolved Handoff Boundary
2351
-
2352
- A prior review emitted \`NEEDS_HUMAN\` and stopped the agent loop. The issue has since been moved back to \`ready-for-agent\`.
2353
-
2354
- Before carrying forward old blockers, inspect newer issue comments and the current worktree. Treat prior Reviewer and Refactor Artifacts as historical context, not active findings unless they still apply.
2355
-
2356
- ` : "";
2357
- return appendProtectedWorkGuidance(`${renderedTemplate}
2532
+ const exists = yield* fs.exists(promptTemplatePath).pipe(Effect2.orDie);
2533
+ const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect2.orDie) : promptTemplate;
2534
+ const findingIds = extractLatestFindingIds(latestReview, iteration);
2535
+ const findingArgs = findingIds.map((id) => ` --finding-id ${id}`).join("");
2536
+ return appendProtectedWorkGuidance(`${promptBody}
2358
2537
 
2359
2538
  ## Shared Run Context
2360
2539
 
2361
2540
  Read the selected issue requirements, planning context (including plan and PRD content when present), branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
2362
2541
 
2363
- ## Initial Verification Pass
2364
-
2365
- - First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
2366
- - Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
2367
- - Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
2368
- - If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
2369
- - If a command cannot run because the environment is missing required setup, dependencies, or scripts outside agent control, treat it as a human handoff blocker.
2370
- - If no verification commands are configured, note that and proceed with normal review.
2542
+ ## Verification Handoff
2371
2543
 
2372
- ## Scope Evidence Rules
2544
+ - Run the verification wrapper listed in ${RUN_CONTEXT_PATH_IN_WORKTREE} after making changes.
2545
+ - If ${RUN_CONTEXT_PATH_IN_WORKTREE} provides a worktree-local CLI fallback, use that fallback before considering direct command execution.
2546
+ - Run underlying verification commands directly only when no wrapper path can run, and record why verification used direct commands in the Refactor Artifact.
2547
+ - If configured verification fails because of fixable Issue work, fix the work and rerun verification before handoff.
2548
+ - Failed or blocked verification prohibits <promise>COMPLETE</promise>.
2373
2549
 
2374
- - Use the Run Context's canonical base ref, for example \`origin/<base>\`, for scope diffs and commit ranges.
2375
- - Do not use bare PRD/base branch names such as \`PRD-0063\`, \`main\`, or \`dev\` for scope decisions; local branches with those names may be stale.
2376
- - Only call a file or commit out of scope when it is part of the working branch's delta from the canonical base ref.
2377
- - Do not recommend reverting accepted sibling/base work. If a file is present or changed on the canonical base ref, it is not this Issue's scope overreach.
2378
- - If scope evidence is ambiguous, use \`NEEDS_HUMAN\` or ask for a runner/base mismatch decision instead of telling Refactor to revert files.
2379
-
2380
- ${hasCriteriaPlaceholder ? "" : `## Review Criteria
2381
-
2382
- ${criteriaBlock}
2383
-
2384
- `}${humanHandoffBoundary}${priorReviewerBlock}${renderReviewHistory(reviewHistory)}${priorRefactorBlock}${priorRefactorProtocolReminder}## Output
2385
-
2386
- Write your review to: ${artifactPathInWorktree}
2550
+ ## Latest Review
2387
2551
 
2388
- Before handoff, run: pourkit validate-artifact reviewer ${artifactPathInWorktree} --iteration ${iteration}${priorRefactorArtifacts ? " --prior-refactor-artifacts" : ""}
2552
+ ${latestReview.trimEnd()}
2389
2553
 
2390
- Do not provide a separate chat response. The runner only reads the file above.
2554
+ ## Output
2391
2555
 
2392
- End the file with exactly one wrapped verdict token: <verdict>PASS</verdict>, <verdict>PASS_WITH_NOTES</verdict>, <verdict>NEEDS_REFACTOR</verdict>, <verdict>FAIL</verdict>, or <verdict>NEEDS_HUMAN</verdict>. The verdict token must appear exactly once in the output.
2556
+ Write your refactor artifact to: ${artifactPathInWorktree}
2393
2557
 
2394
- Findings must include an ID column with values in the format R${iteration}.F{findingNumber} (e.g., R${iteration}.F1, R${iteration}.F2) and a Supersedes column referencing the finding ID being superseded (or a hyphen for new findings).
2558
+ Before handoff, run: pourkit validate-artifact refactor ${artifactPathInWorktree} --iteration ${iteration}${findingArgs}
2395
2559
 
2396
- When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token.`);
2560
+ When you are done, finish with <promise>COMPLETE</promise>.`);
2397
2561
  });
2398
2562
  }
2399
- function renderReviewHistory(reviewHistory) {
2400
- if (reviewHistory.length === 0) {
2401
- return "";
2402
- }
2403
- return `## Review History
2404
-
2405
- ${reviewHistory.map((entry, index) => `### Iteration ${index + 1}
2406
-
2407
- ${entry.trimEnd()}`).join("\n\n")}
2408
-
2409
- `;
2410
- }
2411
2563
  function renderPriorRefactorArtifactsEffect(worktreePath, currentIteration) {
2412
2564
  return Effect2.gen(function* () {
2413
2565
  const fb = yield* FileSystem;
@@ -2468,78 +2620,54 @@ ${iterationsBlocks}
2468
2620
  `;
2469
2621
  });
2470
2622
  }
2471
- function loadReviewerPromptTemplateEffect(repoRoot2, promptTemplate, criteriaBlock, fs) {
2472
- return Effect2.gen(function* () {
2473
- const promptTemplatePath = resolvePromptTemplatePath(
2474
- repoRoot2,
2475
- promptTemplate
2476
- );
2477
- const exists = yield* fs.exists(promptTemplatePath).pipe(Effect2.orDie);
2478
- const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect2.orDie) : promptTemplate;
2479
- const hasCriteriaPlaceholder = promptBody.includes("{{REVIEW_CRITERIA}}");
2480
- return {
2481
- content: promptBody.replace(/\{\{REVIEW_CRITERIA\}\}/g, criteriaBlock),
2482
- hasCriteriaPlaceholder
2483
- };
2484
- });
2485
- }
2486
- function renderReviewCriteriaEffect(repoRoot2, criteria, fs) {
2487
- return Effect2.gen(function* () {
2488
- const parts = [];
2489
- for (const criterion of criteria) {
2490
- const snippetPath = join6(
2491
- repoRoot2,
2492
- ".pourkit",
2493
- "managed",
2494
- "prompts",
2495
- `reviewer-${criterion}.snippet.md`
2496
- );
2497
- const exists = yield* fs.exists(snippetPath).pipe(Effect2.orDie);
2498
- if (exists) {
2499
- const content = yield* fs.readFile(snippetPath).pipe(Effect2.orDie);
2500
- parts.push(content.trimEnd());
2501
- } else {
2502
- parts.push(`- ${criterion}`);
2503
- }
2504
- }
2505
- return parts.join("\n\n");
2506
- });
2507
- }
2508
- function recoverReviewOutputFromString(logContent) {
2509
- const startIndex = logContent.indexOf("## Findings");
2510
- if (startIndex === -1) return null;
2511
- const verdictMatch = logContent.slice(startIndex).match(
2512
- /<verdict>(PASS|PASS_WITH_NOTES|NEEDS_REFACTOR|FAIL|NEEDS_HUMAN)<\/verdict>/
2513
- );
2514
- if (!verdictMatch || verdictMatch.index === void 0) return null;
2515
- const recoveredOutput = logContent.slice(startIndex, startIndex + verdictMatch.index + verdictMatch[0].length).trim();
2516
- return recoveredOutput.length > 0 ? recoveredOutput : null;
2517
- }
2518
- function recoverReviewOutputFromLog(logPath) {
2519
- if (!existsSync6(logPath)) {
2520
- return null;
2521
- }
2522
- const logContent = readFileSync6(logPath, "utf-8");
2523
- return recoverReviewOutputFromString(logContent);
2623
+ function persistIterationArtifactEffect(worktreePath, output, iteration, fs) {
2624
+ return Effect2.gen(function* () {
2625
+ const dir = join6(worktreePath, ".pourkit", ".tmp", "reviewers");
2626
+ yield* fs.mkdir(dir).pipe(Effect2.catchAll(() => Effect2.void));
2627
+ yield* fs.writeFile(join6(dir, `iteration-${iteration}.md`), output).pipe(Effect2.catchAll(() => Effect2.void));
2628
+ });
2524
2629
  }
2525
- function readReviewArtifact(artifactPath, logPath) {
2526
- if (existsSync6(artifactPath)) {
2527
- const output = readFileSync6(artifactPath, "utf-8");
2528
- if (output.trim()) {
2529
- return output;
2530
- }
2630
+ function mapResult(result) {
2631
+ const base = {
2632
+ output: result.output,
2633
+ iterations: result.iterations,
2634
+ lifetimeIterations: result.lifetimeIterations,
2635
+ refactorCompletedForLastReview: result.refactorCompletedForLastReview,
2636
+ refactorArtifactPaths: result.refactorArtifactPaths
2637
+ };
2638
+ if (result.exhaustedMaxIterations) {
2639
+ return {
2640
+ status: "exhausted_max_iterations",
2641
+ reviewVerdict: result.verdict === "FAIL" ? "FAIL" : "NEEDS_REFACTOR",
2642
+ reviewArtifactPath: result.artifactPath,
2643
+ ...base,
2644
+ reason: "max_iterations"
2645
+ };
2531
2646
  }
2532
- const recoveredOutput = logPath ? recoverReviewOutputFromLog(logPath) : null;
2533
- if (recoveredOutput) {
2534
- writeFileSync3(artifactPath, recoveredOutput, "utf-8");
2535
- return recoveredOutput;
2647
+ if (result.verdict === "NEEDS_HUMAN") {
2648
+ return {
2649
+ status: "needs_human",
2650
+ reviewVerdict: "NEEDS_HUMAN",
2651
+ reviewArtifactPath: result.artifactPath,
2652
+ ...base
2653
+ };
2536
2654
  }
2537
- if (!existsSync6(artifactPath)) {
2538
- throw new Error(`Reviewer did not produce output at ${artifactPath}`);
2655
+ if (result.verdict === "FAIL") {
2656
+ return {
2657
+ status: "needs_human",
2658
+ reviewVerdict: "NEEDS_HUMAN",
2659
+ reviewArtifactPath: result.artifactPath,
2660
+ ...base
2661
+ };
2539
2662
  }
2540
- throw new Error(`Reviewer produced empty output at ${artifactPath}`);
2663
+ return {
2664
+ status: "passed",
2665
+ reviewVerdict: result.verdict,
2666
+ reviewArtifactPath: result.artifactPath,
2667
+ ...base
2668
+ };
2541
2669
  }
2542
- function runReviewWithRefactorLoop(options) {
2670
+ function runIssueReviewLoop(options) {
2543
2671
  const {
2544
2672
  executionProvider,
2545
2673
  config,
@@ -2565,7 +2693,6 @@ function runReviewWithRefactorLoop(options) {
2565
2693
  return Effect2.die(new Error("No refactorer config found"));
2566
2694
  }
2567
2695
  const maxIterations = strategy.review.maxIterations;
2568
- const passWithNotesRefactorAttempts = strategy.review.passWithNotesRefactorAttempts;
2569
2696
  let resolvedStartingIteration = startingLifetimeIteration;
2570
2697
  {
2571
2698
  const reviewersDir = join6(worktreePath, ".pourkit", ".tmp", "reviewers");
@@ -2598,7 +2725,6 @@ function runReviewWithRefactorLoop(options) {
2598
2725
  iteration: 0,
2599
2726
  lastResult: null,
2600
2727
  reviewHistory: [],
2601
- passWithNotesRefactorAttemptsRemaining: passWithNotesRefactorAttempts,
2602
2728
  accumulatedRefactorPaths: [],
2603
2729
  doneResult: null
2604
2730
  };
@@ -2654,11 +2780,7 @@ function runReviewWithRefactorLoop(options) {
2654
2780
  }
2655
2781
  };
2656
2782
  }
2657
- if (reviewResult.verdict === "PASS_WITH_NOTES" && s.passWithNotesRefactorAttemptsRemaining === 0) {
2658
- logger.step(
2659
- "info",
2660
- "PASS_WITH_NOTES refactor attempts exhausted, treating as PASS"
2661
- );
2783
+ if (reviewResult.verdict === "PASS_WITH_NOTES") {
2662
2784
  return {
2663
2785
  ...s,
2664
2786
  done: true,
@@ -2666,7 +2788,7 @@ function runReviewWithRefactorLoop(options) {
2666
2788
  lastResult: reviewResult,
2667
2789
  reviewHistory: updatedHistory,
2668
2790
  doneResult: {
2669
- verdict: "PASS",
2791
+ verdict: reviewResult.verdict,
2670
2792
  output: reviewResult.output,
2671
2793
  artifactPath: reviewResult.artifactPath,
2672
2794
  iterations: iteration,
@@ -2677,14 +2799,6 @@ function runReviewWithRefactorLoop(options) {
2677
2799
  }
2678
2800
  };
2679
2801
  }
2680
- let updatedPassWithNotesRefactorAttemptsRemaining = s.passWithNotesRefactorAttemptsRemaining;
2681
- if (reviewResult.verdict === "PASS_WITH_NOTES") {
2682
- updatedPassWithNotesRefactorAttemptsRemaining--;
2683
- logger.step(
2684
- "info",
2685
- `PASS_WITH_NOTES refactor attempts remaining: ${updatedPassWithNotesRefactorAttemptsRemaining}`
2686
- );
2687
- }
2688
2802
  if (reviewResult.verdict === "NEEDS_HUMAN") {
2689
2803
  logger.step("info", "NEEDS_HUMAN verdict, stopping review loop");
2690
2804
  return {
@@ -2705,7 +2819,7 @@ function runReviewWithRefactorLoop(options) {
2705
2819
  }
2706
2820
  };
2707
2821
  }
2708
- if (reviewResult.verdict === "NEEDS_REFACTOR" || reviewResult.verdict === "PASS_WITH_NOTES" || reviewResult.verdict === "FAIL") {
2822
+ if (reviewResult.verdict === "NEEDS_REFACTOR" || reviewResult.verdict === "FAIL") {
2709
2823
  logger.step("info", "Running refactor agent");
2710
2824
  const refactorArtifactPathInWorktree = join6(
2711
2825
  ".pourkit",
@@ -2861,7 +2975,6 @@ function runReviewWithRefactorLoop(options) {
2861
2975
  iteration,
2862
2976
  lastResult: reviewResult,
2863
2977
  reviewHistory: updatedHistory,
2864
- passWithNotesRefactorAttemptsRemaining: updatedPassWithNotesRefactorAttemptsRemaining,
2865
2978
  accumulatedRefactorPaths: updatedAccumulated,
2866
2979
  doneResult: null
2867
2980
  };
@@ -2871,7 +2984,6 @@ function runReviewWithRefactorLoop(options) {
2871
2984
  iteration,
2872
2985
  lastResult: reviewResult,
2873
2986
  reviewHistory: updatedHistory,
2874
- passWithNotesRefactorAttemptsRemaining: updatedPassWithNotesRefactorAttemptsRemaining,
2875
2987
  accumulatedRefactorPaths: s.accumulatedRefactorPaths,
2876
2988
  doneResult: null
2877
2989
  };
@@ -2882,7 +2994,7 @@ function runReviewWithRefactorLoop(options) {
2882
2994
  }
2883
2995
  logger.step("warn", `Max review iterations (${maxIterations}) exhausted`);
2884
2996
  return {
2885
- verdict: "FAIL",
2997
+ verdict: finalState.lastResult?.verdict ?? "FAIL",
2886
2998
  output: finalState.lastResult?.output ?? "",
2887
2999
  artifactPath: finalState.lastResult?.artifactPath ?? "",
2888
3000
  iterations: finalState.iteration,
@@ -2894,45 +3006,10 @@ function runReviewWithRefactorLoop(options) {
2894
3006
  });
2895
3007
  const executionLayer = bridgeExecutionProvider(executionProvider);
2896
3008
  return program.pipe(
2897
- Effect2.provide(Layer2.merge(executionLayer, FileSystemDefault))
3009
+ Effect2.provide(Layer2.merge(executionLayer, FileSystemDefault)),
3010
+ Effect2.map(mapResult)
2898
3011
  );
2899
3012
  }
2900
- function persistIterationArtifactEffect(worktreePath, output, iteration, fs) {
2901
- return Effect2.gen(function* () {
2902
- const dir = join6(worktreePath, ".pourkit", ".tmp", "reviewers");
2903
- yield* fs.mkdir(dir).pipe(Effect2.catchAll(() => Effect2.void));
2904
- yield* fs.writeFile(join6(dir, `iteration-${iteration}.md`), output).pipe(Effect2.catchAll(() => Effect2.void));
2905
- });
2906
- }
2907
- function buildRefactorPromptEffect(repoRoot2, promptTemplate, latestReview, artifactPathInWorktree, iteration, fs) {
2908
- return Effect2.gen(function* () {
2909
- const promptTemplatePath = resolvePromptTemplatePath(
2910
- repoRoot2,
2911
- promptTemplate
2912
- );
2913
- const exists = yield* fs.exists(promptTemplatePath).pipe(Effect2.orDie);
2914
- const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect2.orDie) : promptTemplate;
2915
- const findingIds = extractLatestFindingIds(latestReview, iteration);
2916
- const findingArgs = findingIds.map((id) => ` --finding-id ${id}`).join("");
2917
- return appendProtectedWorkGuidance(`${promptBody}
2918
-
2919
- ## Shared Run Context
2920
-
2921
- Read the selected issue requirements, planning context (including plan and PRD content when present), branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
2922
-
2923
- ## Latest Review
2924
-
2925
- ${latestReview.trimEnd()}
2926
-
2927
- ## Output
2928
-
2929
- Write your refactor artifact to: ${artifactPathInWorktree}
2930
-
2931
- Before handoff, run: pourkit validate-artifact refactor ${artifactPathInWorktree} --iteration ${iteration}${findingArgs}
2932
-
2933
- When you are done, finish with <promise>COMPLETE</promise>.`);
2934
- });
2935
- }
2936
3013
 
2937
3014
  // pr/pr-description.ts
2938
3015
  var CONVENTIONAL_TITLE_PATTERN = /^(feat|fix|perf|refactor|docs|test|chore|ci|build)(\([^)]+\))?!?:\s+\S/;
@@ -3183,6 +3260,12 @@ var ISSUE_FINAL_REVIEW_SCOPE_VERDICTS = [
3183
3260
  "incidental",
3184
3261
  "out-of-scope"
3185
3262
  ];
3263
+ var ISSUE_FINAL_REVIEW_VERIFICATION_METHODS = [
3264
+ "wrapper",
3265
+ "local-wrapper",
3266
+ "commands",
3267
+ "none"
3268
+ ];
3186
3269
  function invalid(options, reason, diagnostics = []) {
3187
3270
  return {
3188
3271
  ok: false,
@@ -3490,9 +3573,47 @@ function validateIssueFinalReviewArtifact(parsed, options) {
3490
3573
  diagnostics
3491
3574
  };
3492
3575
  }
3576
+ if ("method" in verification && !ISSUE_FINAL_REVIEW_VERIFICATION_METHODS.includes(
3577
+ verification.method
3578
+ )) {
3579
+ return {
3580
+ ok: false,
3581
+ reason: 'verification.method must be "wrapper", "local-wrapper", "commands", or "none"',
3582
+ diagnostics
3583
+ };
3584
+ }
3493
3585
  diagnostics.push("verification: present");
3494
- if (parsed.selfRetouched === true && parsed.verdict === "pass") {
3586
+ const verificationWasRecorded = verification.required === true || Array.isArray(verification.commands) && verification.commands.length > 0;
3587
+ if (parsed.verdict === "pass" && verificationWasRecorded) {
3495
3588
  if (verification.required !== true || verification.passed !== true) {
3589
+ return {
3590
+ ok: false,
3591
+ reason: "Issue Final Review pass with verification evidence requires verification.required === true and verification.passed === true",
3592
+ diagnostics: [
3593
+ ...diagnostics,
3594
+ `verification.required: ${verification.required}`,
3595
+ `verification.passed: ${verification.passed}`
3596
+ ]
3597
+ };
3598
+ }
3599
+ if (typeof verification.method !== "string") {
3600
+ return {
3601
+ ok: false,
3602
+ reason: "Issue Final Review pass with verification evidence requires verification.method",
3603
+ diagnostics
3604
+ };
3605
+ }
3606
+ if (verification.method === "none") {
3607
+ return {
3608
+ ok: false,
3609
+ reason: 'Issue Final Review pass with verification evidence requires verification.method other than "none"',
3610
+ diagnostics
3611
+ };
3612
+ }
3613
+ diagnostics.push("pass verification: valid");
3614
+ }
3615
+ if (parsed.selfRetouched === true && parsed.verdict === "pass") {
3616
+ if (!verificationWasRecorded) {
3496
3617
  return {
3497
3618
  ok: false,
3498
3619
  reason: "Self-retouched pass requires verification.required === true and verification.passed === true",
@@ -3672,7 +3793,7 @@ function shellQuote(value) {
3672
3793
  }
3673
3794
 
3674
3795
  // commands/issue-run.ts
3675
- import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
3796
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
3676
3797
  import { isAbsolute as isAbsolute4, join as join15 } from "path";
3677
3798
 
3678
3799
  // pr/templates.ts
@@ -3693,10 +3814,10 @@ init_common();
3693
3814
  import { Exit as Exit3 } from "effect";
3694
3815
 
3695
3816
  // failure-resolution/effect-runtime.ts
3696
- import { Effect as Effect4 } from "effect";
3817
+ import { Effect as Effect5 } from "effect";
3697
3818
 
3698
3819
  // shared/effect-runtime.ts
3699
- import { Effect as Effect3, Exit } from "effect";
3820
+ import { Effect as Effect4, Exit } from "effect";
3700
3821
  import { UnknownException } from "effect/Cause";
3701
3822
  var initialized = false;
3702
3823
  function initializeEffectRuntime() {
@@ -3705,7 +3826,7 @@ function initializeEffectRuntime() {
3705
3826
  }
3706
3827
  function runEffect(program) {
3707
3828
  ensureEffectRuntime();
3708
- return Effect3.runPromiseExit(program);
3829
+ return Effect4.runPromiseExit(program);
3709
3830
  }
3710
3831
  function ensureEffectRuntime() {
3711
3832
  if (!initialized) {
@@ -3883,23 +4004,23 @@ function recordStageAttempt(worktreePath, record) {
3883
4004
 
3884
4005
  // failure-resolution/effect-runtime.ts
3885
4006
  function baseRefreshEffect(options) {
3886
- return Effect4.promise(() => refreshStaleIssueBranch(options)).pipe(
3887
- Effect4.flatMap(
4007
+ return Effect5.promise(() => refreshStaleIssueBranch(options)).pipe(
4008
+ Effect5.flatMap(
3888
4009
  (result) => {
3889
4010
  switch (result.status) {
3890
4011
  case "refreshed":
3891
- return Effect4.succeed({ status: "refreshed" });
4012
+ return Effect5.succeed({ status: "refreshed" });
3892
4013
  case "skipped-current":
3893
- return Effect4.succeed({ status: "skipped-current" });
4014
+ return Effect5.succeed({ status: "skipped-current" });
3894
4015
  case "conflicted":
3895
- return Effect4.fail(
4016
+ return Effect5.fail(
3896
4017
  new RebaseConflict({
3897
4018
  conflictedPaths: result.conflictedPaths,
3898
4019
  message: result.message
3899
4020
  })
3900
4021
  );
3901
4022
  case "refused-published-history":
3902
- return Effect4.fail(
4023
+ return Effect5.fail(
3903
4024
  new PublishedHistoryRisk({
3904
4025
  prNumber: result.prNumber,
3905
4026
  prState: result.prState
@@ -3914,8 +4035,8 @@ async function runBaseRefreshAttempt(options) {
3914
4035
  const attemptId = createStageAttemptId();
3915
4036
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3916
4037
  const program = baseRefreshEffect(options).pipe(
3917
- Effect4.tapBoth({
3918
- onSuccess: (success) => Effect4.sync(() => {
4038
+ Effect5.tapBoth({
4039
+ onSuccess: (success) => Effect5.sync(() => {
3919
4040
  recordStageAttempt(options.worktreePath, {
3920
4041
  id: attemptId,
3921
4042
  stage: "baseRefresh",
@@ -3924,7 +4045,7 @@ async function runBaseRefreshAttempt(options) {
3924
4045
  outcome: "success"
3925
4046
  });
3926
4047
  }),
3927
- onFailure: (failure) => Effect4.sync(() => {
4048
+ onFailure: (failure) => Effect5.sync(() => {
3928
4049
  recordStageAttempt(options.worktreePath, {
3929
4050
  id: attemptId,
3930
4051
  stage: "baseRefresh",
@@ -4395,6 +4516,10 @@ function writeFailureState(options, message) {
4395
4516
  }
4396
4517
  }
4397
4518
 
4519
+ // issues/issue-builder.ts
4520
+ init_common();
4521
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
4522
+
4398
4523
  // serena/baseline.ts
4399
4524
  init_common();
4400
4525
  import path3 from "path";
@@ -4647,82 +4772,258 @@ var SERENA_DASHBOARD_PORT = 24282;
4647
4772
  var SERENA_IMAGE = "ghcr.io/oraios/serena:latest";
4648
4773
  function sidecarOptions(paths, mcpUrl) {
4649
4774
  return {
4650
- baselineWorktreePath: paths.baselineWorktreePath,
4651
- dataDir: paths.dataDir,
4652
- mcpPort: SERENA_MCP_PORT,
4653
- dashboardPort: SERENA_DASHBOARD_PORT,
4654
- image: SERENA_IMAGE,
4655
- mcpUrl
4775
+ baselineWorktreePath: paths.baselineWorktreePath,
4776
+ dataDir: paths.dataDir,
4777
+ mcpPort: SERENA_MCP_PORT,
4778
+ dashboardPort: SERENA_DASHBOARD_PORT,
4779
+ image: SERENA_IMAGE,
4780
+ mcpUrl
4781
+ };
4782
+ }
4783
+ async function canReachMcp(url) {
4784
+ for (let attempt = 0; attempt < 10; attempt += 1) {
4785
+ try {
4786
+ await fetch(url, { method: "GET", signal: AbortSignal.timeout(500) });
4787
+ return true;
4788
+ } catch {
4789
+ if (attempt < 9) {
4790
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
4791
+ }
4792
+ }
4793
+ }
4794
+ return false;
4795
+ }
4796
+ function formatError(error) {
4797
+ return error instanceof Error ? error.message : String(error);
4798
+ }
4799
+ async function prepareSerenaForTarget(options) {
4800
+ if (!options.enabled) {
4801
+ return { enabled: false };
4802
+ }
4803
+ try {
4804
+ const paths = await ensureBaselineWorktree({
4805
+ repoRoot: options.repoRoot,
4806
+ dataDir: options.dataDir
4807
+ });
4808
+ await prepareSerenaSidecarConfig({
4809
+ baselineWorktreePath: paths.baselineWorktreePath,
4810
+ dataDir: paths.dataDir
4811
+ });
4812
+ const status = options.autoStart ? await startSerenaSidecar(sidecarOptions(paths, options.mcpUrl)) : await getSerenaSidecarStatus(sidecarOptions(paths, options.mcpUrl));
4813
+ const mcpReachable = await canReachMcp(options.mcpUrl);
4814
+ if (!mcpReachable) {
4815
+ return {
4816
+ enabled: true,
4817
+ available: false,
4818
+ error: status.running ? `Serena MCP is not reachable at ${options.mcpUrl}` : `Serena sidecar is not running for target ${options.targetName}`
4819
+ };
4820
+ }
4821
+ await refreshSerenaBaseline({
4822
+ repoRoot: options.repoRoot,
4823
+ dataDir: options.dataDir,
4824
+ baseBranch: options.baseBranch
4825
+ });
4826
+ return {
4827
+ enabled: true,
4828
+ available: true,
4829
+ mcpUrl: options.mcpUrl
4830
+ };
4831
+ } catch (error) {
4832
+ return {
4833
+ enabled: true,
4834
+ available: false,
4835
+ error: formatError(error)
4836
+ };
4837
+ }
4838
+ }
4839
+
4840
+ // issues/issue-builder.ts
4841
+ async function runIssueBuilder(options) {
4842
+ const {
4843
+ repoRoot: repoRoot2,
4844
+ issueNumber,
4845
+ target,
4846
+ config,
4847
+ branchName,
4848
+ worktreePath,
4849
+ baseRef,
4850
+ worktreeState,
4851
+ restartBuilder,
4852
+ executionProvider,
4853
+ runContextArtifact,
4854
+ logger,
4855
+ memory,
4856
+ prepareSerena,
4857
+ onSerenaExecutionContext,
4858
+ timeoutMs
4859
+ } = options;
4860
+ const serenaRuntimeConfig = {
4861
+ enabled: target.serena?.enabled ?? config.serena.enabled,
4862
+ required: target.serena?.required ?? config.serena.required,
4863
+ autoStart: config.serena.autoStart,
4864
+ dataDir: config.serena.dataDir,
4865
+ mcpUrl: config.serena.mcpUrl,
4866
+ sandboxMcpUrl: config.serena.sandboxMcpUrl
4867
+ };
4868
+ let serenaExecutionContext;
4869
+ if (serenaRuntimeConfig.enabled || serenaRuntimeConfig.required) {
4870
+ const serenaPreflight = await (prepareSerena ?? prepareSerenaForTarget)({
4871
+ repoRoot: repoRoot2,
4872
+ targetName: target.name,
4873
+ baseBranch: target.baseBranch,
4874
+ dataDir: serenaRuntimeConfig.dataDir,
4875
+ mcpUrl: serenaRuntimeConfig.mcpUrl,
4876
+ enabled: serenaRuntimeConfig.enabled || serenaRuntimeConfig.required,
4877
+ required: serenaRuntimeConfig.required,
4878
+ autoStart: serenaRuntimeConfig.autoStart,
4879
+ logger
4880
+ });
4881
+ if (serenaPreflight.enabled && serenaPreflight.available) {
4882
+ serenaExecutionContext = {
4883
+ available: true,
4884
+ sandboxMcpUrl: serenaRuntimeConfig.sandboxMcpUrl
4885
+ };
4886
+ onSerenaExecutionContext?.(serenaExecutionContext);
4887
+ }
4888
+ if (serenaPreflight.enabled && !serenaPreflight.available) {
4889
+ const message = `Serena preflight unavailable for target ${target.name}: ${serenaPreflight.error}`;
4890
+ if (serenaRuntimeConfig.required) {
4891
+ throw new Error(message);
4892
+ }
4893
+ logger.step("warn", message);
4894
+ }
4895
+ }
4896
+ const shouldRunBuilder = restartBuilder || worktreeState === null || !worktreeState.completedStages.builder;
4897
+ if (!shouldRunBuilder) {
4898
+ return {
4899
+ status: "skipped",
4900
+ ...worktreePath ? { worktreePath } : {},
4901
+ worktreeState
4902
+ };
4903
+ }
4904
+ const strategy = target.strategy;
4905
+ const prompt = loadBuilderPrompt(
4906
+ repoRoot2,
4907
+ strategy.implement.builder.promptTemplate
4908
+ );
4909
+ const executionResult = await executionProvider.execute({
4910
+ stage: "builder",
4911
+ agent: strategy.implement.builder.agent,
4912
+ model: strategy.implement.builder.model,
4913
+ variant: strategy.implement.builder.variant,
4914
+ env: strategy.implement.builder.env,
4915
+ prompt,
4916
+ target,
4917
+ repoRoot: repoRoot2,
4918
+ branchName,
4919
+ ...baseRef ? { baseRef } : {},
4920
+ sandbox: config.sandbox,
4921
+ ...serenaExecutionContext ? { serena: serenaExecutionContext } : {},
4922
+ ...memory ? { memory } : {},
4923
+ autoApprove: true,
4924
+ timeoutMs,
4925
+ ...worktreePath ? { worktreePath } : {},
4926
+ artifacts: [runContextArtifact],
4927
+ logger
4928
+ });
4929
+ if (!executionResult.success) {
4930
+ throw new Error(`Sandcastle failed: ${executionResult.error}`);
4931
+ }
4932
+ const effectiveWorktreePath = worktreePath ?? executionResult.worktreePath;
4933
+ if (effectiveWorktreePath) {
4934
+ await assertCanonicalBaseAncestor({
4935
+ worktreePath: effectiveWorktreePath,
4936
+ baseRef: `origin/${target.baseBranch}`,
4937
+ stageName: "Builder",
4938
+ logger
4939
+ });
4940
+ if (worktreeState) {
4941
+ updateWorktreeRunState(effectiveWorktreePath, {
4942
+ completedStages: { builder: true }
4943
+ });
4944
+ } else {
4945
+ writeWorktreeRunState(effectiveWorktreePath, {
4946
+ issueNumber,
4947
+ targetName: target.name,
4948
+ branchName,
4949
+ baseBranch: target.baseBranch,
4950
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4951
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4952
+ completedStages: { builder: true },
4953
+ review: { lifetimeIterations: 0 }
4954
+ });
4955
+ }
4956
+ }
4957
+ const refreshedState = effectiveWorktreePath ? readWorktreeRunState(effectiveWorktreePath) : worktreeState;
4958
+ return {
4959
+ status: "ran",
4960
+ ...effectiveWorktreePath ? { worktreePath: effectiveWorktreePath } : {},
4961
+ worktreeState: refreshedState
4656
4962
  };
4657
4963
  }
4658
- async function canReachMcp(url) {
4659
- for (let attempt = 0; attempt < 10; attempt += 1) {
4660
- try {
4661
- await fetch(url, { method: "GET", signal: AbortSignal.timeout(500) });
4662
- return true;
4663
- } catch {
4664
- if (attempt < 9) {
4665
- await new Promise((resolve5) => setTimeout(resolve5, 100));
4666
- }
4667
- }
4668
- }
4669
- return false;
4964
+ function loadBuilderPrompt(repoRoot2, promptTemplate) {
4965
+ const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
4966
+ const promptBody = existsSync10(promptPath) ? readFileSync11(promptPath, "utf-8") : promptTemplate;
4967
+ return appendProtectedWorkGuidance(`${promptBody}
4968
+
4969
+ ## Shared Run Context
4970
+
4971
+ Read the selected issue requirements, planning context (including plan and PRD content when present), comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
4972
+
4973
+ ## Verification Handoff
4974
+
4975
+ - Run the verification wrapper listed in ${RUN_CONTEXT_PATH_IN_WORKTREE}.
4976
+ - If ${RUN_CONTEXT_PATH_IN_WORKTREE} provides a worktree-local CLI fallback, use that fallback before considering direct command execution.
4977
+ - Run underlying verification commands directly only when no wrapper path can run, and report why verification used direct commands.
4978
+ - If configured verification fails because of fixable Issue work, fix the work and rerun verification before handoff.
4979
+ - Failed or blocked verification prohibits <promise>COMPLETE</promise>.`);
4670
4980
  }
4671
- function formatError(error) {
4672
- return error instanceof Error ? error.message : String(error);
4981
+ async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
4982
+ const remoteBase = parseRemoteBaseRef(baseRef);
4983
+ if (!remoteBase) {
4984
+ return;
4985
+ }
4986
+ await execCapture("git", ["fetch", remoteBase.remote, remoteBase.branch], {
4987
+ cwd: worktreePath,
4988
+ logger,
4989
+ label: "git fetch target"
4990
+ });
4673
4991
  }
4674
- async function prepareSerenaForTarget(options) {
4675
- if (!options.enabled) {
4676
- return { enabled: false };
4992
+ function parseRemoteBaseRef(baseRef) {
4993
+ const [remote, ...branchParts] = baseRef.split("/");
4994
+ const branch = branchParts.join("/");
4995
+ if (!remote || !branch) {
4996
+ return null;
4677
4997
  }
4998
+ return { remote, branch };
4999
+ }
5000
+ async function assertCanonicalBaseAncestor(options) {
5001
+ const { worktreePath, baseRef, stageName, logger } = options;
5002
+ await syncRemoteBaseRef(worktreePath, baseRef, logger);
4678
5003
  try {
4679
- const paths = await ensureBaselineWorktree({
4680
- repoRoot: options.repoRoot,
4681
- dataDir: options.dataDir
4682
- });
4683
- await prepareSerenaSidecarConfig({
4684
- baselineWorktreePath: paths.baselineWorktreePath,
4685
- dataDir: paths.dataDir
4686
- });
4687
- const status = options.autoStart ? await startSerenaSidecar(sidecarOptions(paths, options.mcpUrl)) : await getSerenaSidecarStatus(sidecarOptions(paths, options.mcpUrl));
4688
- const mcpReachable = await canReachMcp(options.mcpUrl);
4689
- if (!mcpReachable) {
4690
- return {
4691
- enabled: true,
4692
- available: false,
4693
- error: status.running ? `Serena MCP is not reachable at ${options.mcpUrl}` : `Serena sidecar is not running for target ${options.targetName}`
4694
- };
4695
- }
4696
- await refreshSerenaBaseline({
4697
- repoRoot: options.repoRoot,
4698
- dataDir: options.dataDir,
4699
- baseBranch: options.baseBranch
5004
+ await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
5005
+ cwd: worktreePath,
5006
+ logger,
5007
+ label: "git merge-base --is-ancestor"
4700
5008
  });
4701
- return {
4702
- enabled: true,
4703
- available: true,
4704
- mcpUrl: options.mcpUrl
4705
- };
4706
- } catch (error) {
4707
- return {
4708
- enabled: true,
4709
- available: false,
4710
- error: formatError(error)
4711
- };
5009
+ } catch {
5010
+ throw new Error(
5011
+ `Cannot continue after ${stageName}: ${baseRef} is not an ancestor of HEAD. An agent may have moved the issue branch behind the canonical base; refresh the branch onto the latest target base before continuing.`
5012
+ );
4712
5013
  }
4713
5014
  }
4714
5015
 
4715
5016
  // commands/pr-description-agent.ts
4716
5017
  import { join as join11 } from "path";
4717
- import { readFileSync as readFileSync11 } from "fs";
5018
+ import { readFileSync as readFileSync12 } from "fs";
4718
5019
 
4719
5020
  // pr/pr-description-context.ts
4720
5021
  init_common();
4721
5022
  import { join as join10 } from "path";
4722
5023
  import { readFile } from "fs/promises";
4723
- import { Effect as Effect5 } from "effect";
5024
+ import { Effect as Effect6 } from "effect";
4724
5025
  function collectFinalizerContextEffect(options) {
4725
- return Effect5.gen(function* () {
5026
+ return Effect6.gen(function* () {
4726
5027
  const git = yield* GitOperations;
4727
5028
  const fs = yield* FileSystem;
4728
5029
  const remoteBase = remoteTargetBase(options.targetBase);
@@ -4735,8 +5036,8 @@ function collectFinalizerContextEffect(options) {
4735
5036
  ],
4736
5037
  { cwd: options.worktreePath }
4737
5038
  ).pipe(
4738
- Effect5.catchAll(
4739
- (error) => Effect5.fail(
5039
+ Effect6.catchAll(
5040
+ (error) => Effect6.fail(
4740
5041
  new Error(`Failed to collect commit range: ${error.message}`)
4741
5042
  )
4742
5043
  )
@@ -4751,9 +5052,9 @@ function collectFinalizerContextEffect(options) {
4751
5052
  let reviewArtifact;
4752
5053
  if (options.reviewArtifactPath) {
4753
5054
  const content = yield* fs.readFile(options.reviewArtifactPath).pipe(
4754
- Effect5.catchIf(
5055
+ Effect6.catchIf(
4755
5056
  (error) => error.message.includes("ENOENT") || error.message.includes("no such file"),
4756
- () => Effect5.fail(
5057
+ () => Effect6.fail(
4757
5058
  new Error(
4758
5059
  `Review artifact not found at ${options.reviewArtifactPath}. Ensure the review stage completed before running finalizer generation.`
4759
5060
  )
@@ -4761,7 +5062,7 @@ function collectFinalizerContextEffect(options) {
4761
5062
  )
4762
5063
  );
4763
5064
  if (!content.trim()) {
4764
- return yield* Effect5.fail(
5065
+ return yield* Effect6.fail(
4765
5066
  new Error(
4766
5067
  `Review artifact at ${options.reviewArtifactPath} is empty. Ensure the review stage produced output before running finalizer generation.`
4767
5068
  )
@@ -4837,12 +5138,12 @@ Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktre
4837
5138
  }
4838
5139
 
4839
5140
  // commands/pr-description-agent.ts
4840
- import { Effect as Effect6, Layer as Layer3 } from "effect";
5141
+ import { Effect as Effect7, Layer as Layer3 } from "effect";
4841
5142
  function bridgeExecutionProvider2(ep) {
4842
5143
  return Layer3.succeed(
4843
5144
  ExecutionProvider,
4844
5145
  ExecutionProvider.of({
4845
- execute: (opts) => Effect6.tryPromise({
5146
+ execute: (opts) => Effect7.tryPromise({
4846
5147
  try: () => ep.execute(opts),
4847
5148
  catch: (e) => new Error(
4848
5149
  e instanceof Error ? e.message : `Execution failed: ${String(e)}`
@@ -4860,7 +5161,7 @@ function runFinalizerAgent(options) {
4860
5161
  "agent-output.md"
4861
5162
  );
4862
5163
  const artifactPath = join11(options.worktreePath, artifactPathInWorktree);
4863
- const program = Effect6.gen(function* () {
5164
+ const program = Effect7.gen(function* () {
4864
5165
  const fs = yield* FileSystem;
4865
5166
  const context = yield* collectFinalizerContextEffect({
4866
5167
  targetBase: options.targetBaseBranch,
@@ -4869,8 +5170,8 @@ function runFinalizerAgent(options) {
4869
5170
  reviewArtifactPath: options.reviewArtifactPath,
4870
5171
  logger: options.logger
4871
5172
  }).pipe(
4872
- Effect6.catchAll(
4873
- (error) => Effect6.fail(
5173
+ Effect7.catchAll(
5174
+ (error) => Effect7.fail(
4874
5175
  new FinalizerFailure({
4875
5176
  message: `Failed to collect finalizer context: ${error.message}`
4876
5177
  })
@@ -4909,13 +5210,13 @@ function runFinalizerAgent(options) {
4909
5210
  logger: options.logger,
4910
5211
  ...options.memory ? { memory: options.memory } : {}
4911
5212
  });
4912
- const output = readFileSync11(artifactPath, "utf-8");
5213
+ const output = readFileSync12(artifactPath, "utf-8");
4913
5214
  yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
4914
5215
  return result;
4915
5216
  });
4916
5217
  const executionLayer = bridgeExecutionProvider2(executionProvider);
4917
5218
  return program.pipe(
4918
- Effect6.provide(
5219
+ Effect7.provide(
4919
5220
  Layer3.mergeAll(executionLayer, FileSystemDefault, GitOperationsDefault)
4920
5221
  )
4921
5222
  );
@@ -4925,11 +5226,11 @@ function runPrDescriptionFinalizerCore(options) {
4925
5226
  options.worktreePath,
4926
5227
  options.artifactPathInWorktree
4927
5228
  );
4928
- return Effect6.gen(function* () {
5229
+ return Effect7.gen(function* () {
4929
5230
  let parsed;
4930
5231
  let lastValidationError;
4931
5232
  for (let protocolAttempt = 1; protocolAttempt <= options.maxAttempts; protocolAttempt++) {
4932
- const retryResult = yield* Effect6.tryPromise({
5233
+ const retryResult = yield* Effect7.tryPromise({
4933
5234
  try: () => executeWithMissingOrEmptyArtifactRetry({
4934
5235
  executionProvider: options.executionProvider,
4935
5236
  missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(
@@ -4966,7 +5267,7 @@ function runPrDescriptionFinalizerCore(options) {
4966
5267
  });
4967
5268
  const executionResult = retryResult.executionResult;
4968
5269
  if (!executionResult.success) {
4969
- return yield* Effect6.fail(
5270
+ return yield* Effect7.fail(
4970
5271
  new FinalizerFailure({
4971
5272
  message: `Finalizer agent execution failed: ${executionResult.error}`
4972
5273
  })
@@ -5004,7 +5305,7 @@ function runPrDescriptionFinalizerCore(options) {
5004
5305
  }
5005
5306
  }
5006
5307
  if (!parsed) {
5007
- return yield* Effect6.fail(
5308
+ return yield* Effect7.fail(
5008
5309
  new FinalizerFailure({
5009
5310
  message: lastValidationError?.message ?? "Finalizer validation failed"
5010
5311
  })
@@ -5019,20 +5320,20 @@ function runPrDescriptionFinalizerCore(options) {
5019
5320
  });
5020
5321
  }
5021
5322
  function loadFinalizerPromptEffect(repoRoot2, promptTemplate, fs) {
5022
- return Effect6.gen(function* () {
5323
+ return Effect7.gen(function* () {
5023
5324
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
5024
- const exists = yield* fs.exists(promptPath).pipe(Effect6.orDie);
5325
+ const exists = yield* fs.exists(promptPath).pipe(Effect7.orDie);
5025
5326
  if (exists) {
5026
- return yield* fs.readFile(promptPath).pipe(Effect6.orDie);
5327
+ return yield* fs.readFile(promptPath).pipe(Effect7.orDie);
5027
5328
  }
5028
5329
  return promptTemplate;
5029
5330
  });
5030
5331
  }
5031
5332
  function persistGeneratedArtifactEffect(worktreePath, output, fs) {
5032
- return Effect6.gen(function* () {
5333
+ return Effect7.gen(function* () {
5033
5334
  const dir = join11(worktreePath, ".pourkit", ".tmp", "finalizer");
5034
- yield* fs.mkdir(dir).pipe(Effect6.catchAll(() => Effect6.void));
5035
- yield* fs.writeFile(join11(dir, "generated.md"), output).pipe(Effect6.catchAll(() => Effect6.void));
5335
+ yield* fs.mkdir(dir).pipe(Effect7.catchAll(() => Effect7.void));
5336
+ yield* fs.writeFile(join11(dir, "generated.md"), output).pipe(Effect7.catchAll(() => Effect7.void));
5036
5337
  });
5037
5338
  }
5038
5339
 
@@ -5106,7 +5407,7 @@ function extractClosingRefs(body) {
5106
5407
 
5107
5408
  // issues/github-issue-publication.ts
5108
5409
  init_common();
5109
- import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
5410
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
5110
5411
  import { isAbsolute as isAbsolute3, join as join12 } from "path";
5111
5412
 
5112
5413
  // issues/issue-transitions.ts
@@ -5372,7 +5673,7 @@ function secondsRemaining(deadline, observedAt) {
5372
5673
  }
5373
5674
 
5374
5675
  // issues/merge-coordinator.ts
5375
- import { Effect as Effect7, Either } from "effect";
5676
+ import { Effect as Effect8, Either } from "effect";
5376
5677
  import { UnknownException as UnknownException2 } from "effect/Cause";
5377
5678
  function unwrapError2(error) {
5378
5679
  if (error instanceof UnknownException2 && error.error !== void 0) {
@@ -5391,9 +5692,9 @@ function runMergeCoordinator(options) {
5391
5692
  } = options;
5392
5693
  const method = options.method ?? "squash";
5393
5694
  const waitForTargetGreen = options.waitForTargetGreen ?? true;
5394
- return Effect7.gen(function* () {
5395
- const checksEither = yield* Effect7.either(
5396
- Effect7.tryPromise(
5695
+ return Effect8.gen(function* () {
5696
+ const checksEither = yield* Effect8.either(
5697
+ Effect8.tryPromise(
5397
5698
  () => prProvider.waitForPrChecks(prNumber, checkWaitOptions)
5398
5699
  )
5399
5700
  );
@@ -5406,8 +5707,8 @@ function runMergeCoordinator(options) {
5406
5707
  error: error instanceof Error ? error : new Error(String(error))
5407
5708
  };
5408
5709
  }
5409
- const mergeEither = yield* Effect7.either(
5410
- Effect7.tryPromise(
5710
+ const mergeEither = yield* Effect8.either(
5711
+ Effect8.tryPromise(
5411
5712
  () => prProvider.mergePr(prNumber, { method, matchHeadCommit })
5412
5713
  )
5413
5714
  );
@@ -5421,8 +5722,8 @@ function runMergeCoordinator(options) {
5421
5722
  };
5422
5723
  }
5423
5724
  if (waitForTargetGreen) {
5424
- const targetEither = yield* Effect7.either(
5425
- Effect7.tryPromise(
5725
+ const targetEither = yield* Effect8.either(
5726
+ Effect8.tryPromise(
5426
5727
  () => waitForBranchChecks(prProvider, logger, {
5427
5728
  branchName: targetBranch,
5428
5729
  checksFoundTimeoutMs: checkWaitOptions.checksFoundTimeoutMs,
@@ -5583,10 +5884,10 @@ function readIssueFinalReviewArtifact(worktreeState, worktreePath) {
5583
5884
  return null;
5584
5885
  }
5585
5886
  const artifactPath = isAbsolute3(configuredPath) ? configuredPath : join12(worktreePath, configuredPath);
5586
- if (!existsSync10(artifactPath)) return null;
5887
+ if (!existsSync11(artifactPath)) return null;
5587
5888
  let parsed;
5588
5889
  try {
5589
- parsed = JSON.parse(readFileSync12(artifactPath, "utf-8"));
5890
+ parsed = JSON.parse(readFileSync13(artifactPath, "utf-8"));
5590
5891
  } catch {
5591
5892
  return null;
5592
5893
  }
@@ -5621,6 +5922,9 @@ function buildIssueFinalReviewPrComment(artifact) {
5621
5922
  `- **Required:** ${formatBoolean(artifact.verification.required)}`,
5622
5923
  `- **Passed:** ${formatBoolean(artifact.verification.passed)}`
5623
5924
  );
5925
+ if (typeof artifact.verification.method === "string") {
5926
+ lines.push(`- **Method:** ${formatLabel(artifact.verification.method)}`);
5927
+ }
5624
5928
  const commands = Array.isArray(artifact.verification.commands) ? artifact.verification.commands.filter(
5625
5929
  (command) => typeof command === "string"
5626
5930
  ) : [];
@@ -5818,7 +6122,7 @@ function findSecretLikeContent(source, content) {
5818
6122
  ];
5819
6123
  for (const { kind, regex } of patterns) {
5820
6124
  for (const match of content.matchAll(regex)) {
5821
- if (isAllowedSecretPlaceholder(match[0])) {
6125
+ if (isAllowedSecretPlaceholder(match[0]) || isAllowedDocumentationTokenReference(match[0])) {
5822
6126
  continue;
5823
6127
  }
5824
6128
  findings.push({ source, kind });
@@ -5839,9 +6143,12 @@ function isAllowedSecretPlaceholder(value) {
5839
6143
  "xxxxx"
5840
6144
  ].some((allowed) => normalized.includes(allowed));
5841
6145
  }
6146
+ function isAllowedDocumentationTokenReference(value) {
6147
+ return /^token\s*:\s*["']?<verdict>/i.test(value);
6148
+ }
5842
6149
 
5843
6150
  // commands/issue-final-review-agent.ts
5844
- import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
6151
+ import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
5845
6152
  import { join as join13 } from "path";
5846
6153
  var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join13(
5847
6154
  ".pourkit",
@@ -5868,7 +6175,9 @@ async function runIssueFinalReviewAgent(options) {
5868
6175
  const prompt = loadIssueFinalReviewPrompt({
5869
6176
  repoRoot: repoRoot2,
5870
6177
  promptTemplate: agent.promptTemplate,
5871
- validationCommand: `pourkit validate-artifact issue-final-review ${artifactPathInWorktree} --issue-number ${issue.number} --branch-name ${builderBranch} --base-ref origin/${target.baseBranch}`
6178
+ validationCommand: `pourkit validate-artifact issue-final-review ${artifactPathInWorktree} --issue-number ${issue.number} --branch-name ${builderBranch} --base-ref origin/${target.baseBranch}`,
6179
+ reviewArtifactPath: options.reviewArtifactPath,
6180
+ reviewVerdict: options.reviewVerdict
5872
6181
  });
5873
6182
  const entry = await executeWithMissingOrEmptyArtifactRetry({
5874
6183
  executionProvider,
@@ -5964,10 +6273,16 @@ function extractChangedPaths(parsed) {
5964
6273
  ).filter((path9) => typeof path9 === "string") : [];
5965
6274
  }
5966
6275
  function loadIssueFinalReviewPrompt(options) {
5967
- const { repoRoot: repoRoot2, promptTemplate, validationCommand } = options;
6276
+ const {
6277
+ repoRoot: repoRoot2,
6278
+ promptTemplate,
6279
+ validationCommand,
6280
+ reviewArtifactPath,
6281
+ reviewVerdict
6282
+ } = options;
5968
6283
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
5969
- const promptBody = existsSync11(promptPath) ? readFileSync13(promptPath, "utf-8") : promptTemplate;
5970
- return appendProtectedWorkGuidance(`${promptBody}
6284
+ const promptBody = existsSync12(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
6285
+ let prompt = appendProtectedWorkGuidance(`${promptBody}
5971
6286
 
5972
6287
  ## Shared Run Context
5973
6288
 
@@ -5976,9 +6291,12 @@ Read the selected issue requirements, planning context (including plan and PRD c
5976
6291
  ## Initial Verification Pass
5977
6292
 
5978
6293
  - First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
5979
- - Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
5980
- - Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
5981
- - If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
6294
+ - Before reviewing code, diffs, artifacts, or prior findings, run the verification wrapper from the Worktree.
6295
+ - If the installed Pourkit CLI rejects the worktree config schemaVersion and the Run Context provides a Pourkit self-development fallback, build and run the worktree-local CLI wrapper exactly as instructed.
6296
+ - Do not substitute direct underlying commands unless neither wrapper path can run; if you must use direct commands, record degraded verification with \`verification.method: "commands"\` and explain why in \`verification.summary\`.
6297
+ - Record successful wrapper verification with \`verification.method: "wrapper"\`, or worktree-local CLI wrapper verification with \`verification.method: "local-wrapper"\`.
6298
+ - If a configured verification command fails because of fixable Issue work, self-retouch the Worktree, then rerun all configured verification commands.
6299
+ - Do not return \`pass\` while any configured verification command is failing.
5982
6300
  - If a command cannot run because the environment is missing required setup, dependencies, or scripts outside agent control, treat it as a human handoff blocker.
5983
6301
  - If no verification commands are configured, note that and proceed with normal review.
5984
6302
 
@@ -5998,6 +6316,19 @@ Read the selected issue requirements, planning context (including plan and PRD c
5998
6316
  - This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
5999
6317
  - If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
6000
6318
  - Do not finish until the validation command passes.`);
6319
+ if (reviewVerdict) {
6320
+ prompt += `
6321
+
6322
+ ## Prior Review Context
6323
+
6324
+ - Reviewer Artifact Path: ${reviewArtifactPath}
6325
+ - Prior Review Verdict: ${reviewVerdict}`;
6326
+ if (reviewVerdict === "PASS_WITH_NOTES") {
6327
+ prompt += `
6328
+ - This Issue passed the prior Reviewer with notes. Read the Reviewer Artifact and inspect its notes before making your final pass decision.`;
6329
+ }
6330
+ }
6331
+ return prompt;
6001
6332
  }
6002
6333
 
6003
6334
  // issues/issue-worktree-resolution.ts
@@ -6167,16 +6498,6 @@ function checkIssueGates(issue, config, force) {
6167
6498
  }
6168
6499
  return { allowed: true, gates };
6169
6500
  }
6170
- function resolveSerenaRuntimeConfig(config, target) {
6171
- return {
6172
- enabled: target.serena?.enabled ?? config.serena.enabled,
6173
- required: target.serena?.required ?? config.serena.required,
6174
- autoStart: config.serena.autoStart,
6175
- dataDir: config.serena.dataDir,
6176
- mcpUrl: config.serena.mcpUrl,
6177
- sandboxMcpUrl: config.serena.sandboxMcpUrl
6178
- };
6179
- }
6180
6501
  function assertIssueFinalReviewPassed(worktreeState) {
6181
6502
  const ifr = worktreeState?.issueFinalReview;
6182
6503
  if (!ifr || !ifr.completed || ifr.verdict !== "pass") {
@@ -6197,6 +6518,7 @@ async function advanceIssueFinalReview(options) {
6197
6518
  repoRoot: repoRoot2,
6198
6519
  logger,
6199
6520
  reviewArtifactPath,
6521
+ reviewVerdict,
6200
6522
  worktreeState
6201
6523
  } = options;
6202
6524
  const ifrFromState = worktreeState?.issueFinalReview;
@@ -6237,9 +6559,10 @@ async function advanceIssueFinalReview(options) {
6237
6559
  repoRoot: repoRoot2,
6238
6560
  logger,
6239
6561
  reviewArtifactPath,
6562
+ reviewVerdict,
6240
6563
  ...options.memory ? { memory: options.memory } : {}
6241
6564
  });
6242
- await assertCanonicalBaseAncestor({
6565
+ await assertCanonicalBaseAncestor2({
6243
6566
  worktreePath,
6244
6567
  baseRef: `origin/${target.baseBranch}`,
6245
6568
  stageName: "Issue Final Review",
@@ -6292,35 +6615,7 @@ async function startIssueRun(options) {
6292
6615
  const effectiveTarget = { ...target, baseBranch: effectiveBaseBranch };
6293
6616
  const branchName = renderBranchName(target.branchTemplate, issue);
6294
6617
  const strategy = target.strategy;
6295
- const serenaRuntimeConfig = resolveSerenaRuntimeConfig(config, target);
6296
- const shouldPrepareSerena = serenaRuntimeConfig.enabled || serenaRuntimeConfig.required;
6297
6618
  let serenaExecutionContext;
6298
- if (shouldPrepareSerena) {
6299
- const serenaPreflight = await prepareSerenaForTarget({
6300
- repoRoot: ROOT,
6301
- targetName: target.name,
6302
- baseBranch: target.baseBranch,
6303
- dataDir: serenaRuntimeConfig.dataDir,
6304
- mcpUrl: serenaRuntimeConfig.mcpUrl,
6305
- enabled: shouldPrepareSerena,
6306
- required: serenaRuntimeConfig.required,
6307
- autoStart: serenaRuntimeConfig.autoStart,
6308
- logger
6309
- });
6310
- if (serenaPreflight.enabled && serenaPreflight.available) {
6311
- serenaExecutionContext = {
6312
- available: true,
6313
- sandboxMcpUrl: serenaRuntimeConfig.sandboxMcpUrl
6314
- };
6315
- }
6316
- if (serenaPreflight.enabled && !serenaPreflight.available) {
6317
- const message = `Serena preflight unavailable for target ${target.name}: ${serenaPreflight.error}`;
6318
- if (serenaRuntimeConfig.required) {
6319
- throw new Error(message);
6320
- }
6321
- logger.step("warn", message);
6322
- }
6323
- }
6324
6619
  const memoryExecutionContext = config.memory?.enabled ? { available: true, sandboxDbPath: "/home/agent/.pourkit/icm/memories.db" } : void 0;
6325
6620
  if (options.resetWorktree) {
6326
6621
  const existingPr = await prProvider.getPr(branchName);
@@ -6332,10 +6627,6 @@ async function startIssueRun(options) {
6332
6627
  await resetIssueWorktree(ROOT, branchName, logger);
6333
6628
  await syncTargetBranch(ROOT, effectiveBaseBranch, logger);
6334
6629
  }
6335
- const prompt = loadBuilderPrompt(
6336
- ROOT,
6337
- strategy.implement.builder.promptTemplate
6338
- );
6339
6630
  const resolution = await resolveIssueWorktree(
6340
6631
  ROOT,
6341
6632
  branchName,
@@ -6380,68 +6671,33 @@ async function startIssueRun(options) {
6380
6671
  });
6381
6672
  await issueProvider.addLabels(issueNumber, [config.labels.agentInProgress]);
6382
6673
  await issueProvider.removeLabel(issueNumber, config.labels.readyForAgent);
6383
- let executionResult;
6384
- const shouldRunBuilder = resolution.mode === "new" ? true : worktreeState === null || !worktreeState.completedStages.builder;
6385
- if (shouldRunBuilder) {
6386
- executionResult = await executionProvider.execute({
6387
- stage: "builder",
6388
- agent: strategy.implement.builder.agent,
6389
- model: strategy.implement.builder.model,
6390
- variant: strategy.implement.builder.variant,
6391
- env: strategy.implement.builder.env,
6392
- prompt,
6393
- target: effectiveTarget,
6394
- repoRoot: ROOT,
6395
- branchName,
6396
- ...resolution.mode === "new" ? { baseRef: resolution.baseRef } : {},
6397
- sandbox: config.sandbox,
6398
- ...serenaExecutionContext ? { serena: serenaExecutionContext } : {},
6399
- ...memoryExecutionContext ? { memory: memoryExecutionContext } : {},
6400
- autoApprove: true,
6401
- timeoutMs: EXECUTION_TIMEOUT_MS,
6402
- ...resolution.worktreePath ? { worktreePath: resolution.worktreePath } : {},
6403
- artifacts: [runContextArtifact],
6404
- logger
6405
- });
6406
- if (!executionResult.success) {
6407
- throw new Error(`Sandcastle failed: ${executionResult.error}`);
6408
- }
6409
- } else {
6410
- executionResult = {
6411
- success: true,
6412
- branch: branchName,
6413
- worktreePath: resolution.worktreePath,
6414
- commits: [],
6415
- logPath: null
6416
- };
6417
- }
6418
- if (executionResult.worktreePath) {
6419
- if (shouldRunBuilder) {
6420
- await assertCanonicalBaseAncestor({
6421
- worktreePath: executionResult.worktreePath,
6422
- baseRef: `origin/${effectiveBaseBranch}`,
6423
- stageName: "Builder",
6424
- logger
6425
- });
6426
- }
6427
- if (worktreeState) {
6428
- updateWorktreeRunState(executionResult.worktreePath, {
6429
- completedStages: { builder: true }
6430
- });
6431
- } else {
6432
- writeWorktreeRunState(executionResult.worktreePath, {
6433
- issueNumber,
6434
- targetName: target.name,
6435
- branchName,
6436
- baseBranch: effectiveBaseBranch,
6437
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6438
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6439
- completedStages: { builder: true },
6440
- review: { lifetimeIterations: 0 }
6441
- });
6674
+ const builderResult = await runIssueBuilder({
6675
+ repoRoot: ROOT,
6676
+ issueNumber,
6677
+ target: effectiveTarget,
6678
+ config,
6679
+ branchName,
6680
+ ...resolution.worktreePath ? { worktreePath: resolution.worktreePath } : {},
6681
+ ...resolution.mode === "new" ? { baseRef: resolution.baseRef } : {},
6682
+ worktreeState,
6683
+ restartBuilder: resolution.mode === "new",
6684
+ executionProvider,
6685
+ runContextArtifact,
6686
+ logger,
6687
+ ...memoryExecutionContext ? { memory: memoryExecutionContext } : {},
6688
+ timeoutMs: EXECUTION_TIMEOUT_MS,
6689
+ onSerenaExecutionContext: (context) => {
6690
+ serenaExecutionContext = context;
6442
6691
  }
6443
- }
6444
- const finalWorktreeState = executionResult.worktreePath ? readWorktreeRunState(executionResult.worktreePath) : worktreeState;
6692
+ });
6693
+ const executionResult = {
6694
+ success: true,
6695
+ branch: branchName,
6696
+ worktreePath: builderResult.worktreePath ?? issueWorktreePath(ROOT, branchName),
6697
+ commits: [],
6698
+ logPath: null
6699
+ };
6700
+ const finalWorktreeState = builderResult.worktreeState;
6445
6701
  return {
6446
6702
  issue,
6447
6703
  parentPrdIssue,
@@ -6469,7 +6725,7 @@ async function fetchParentPrdIssue(issue, issueProvider) {
6469
6725
  async function advanceIssueRunReview(options) {
6470
6726
  const accumulatedRefactorPaths = [];
6471
6727
  const reviewResult = await runEffectAndMapExit(
6472
- runReviewWithRefactorLoop({
6728
+ runIssueReviewLoop({
6473
6729
  ...options,
6474
6730
  onRefactorProgress: (progress) => {
6475
6731
  if (progress.refactorArtifactPath) {
@@ -6486,22 +6742,35 @@ async function advanceIssueRunReview(options) {
6486
6742
  }
6487
6743
  })
6488
6744
  );
6489
- await assertCanonicalBaseAncestor({
6745
+ await assertCanonicalBaseAncestor2({
6490
6746
  worktreePath: options.worktreePath,
6491
6747
  baseRef: `origin/${options.target.baseBranch}`,
6492
6748
  stageName: "Review/Refactor",
6493
6749
  logger: options.logger
6494
6750
  });
6495
- updateWorktreeRunState(options.worktreePath, {
6496
- review: {
6497
- lifetimeIterations: reviewResult.lifetimeIterations,
6498
- lastVerdict: reviewResult.verdict,
6499
- lastArtifactPath: reviewResult.artifactPath,
6500
- refactorCompletedForLastReview: reviewResult.refactorCompletedForLastReview,
6501
- exhaustedPreviousRun: reviewResult.exhaustedMaxIterations || reviewResult.verdict === "FAIL" || void 0,
6502
- refactorArtifactPaths: accumulatedRefactorPaths.length > 0 ? accumulatedRefactorPaths : void 0
6503
- }
6504
- });
6751
+ const commonFields = {
6752
+ lifetimeIterations: reviewResult.lifetimeIterations,
6753
+ lastArtifactPath: reviewResult.reviewArtifactPath,
6754
+ refactorCompletedForLastReview: reviewResult.refactorCompletedForLastReview,
6755
+ refactorArtifactPaths: accumulatedRefactorPaths.length > 0 ? accumulatedRefactorPaths : void 0
6756
+ };
6757
+ if (reviewResult.status === "passed" || reviewResult.status === "needs_human") {
6758
+ updateWorktreeRunState(options.worktreePath, {
6759
+ review: {
6760
+ ...commonFields,
6761
+ lastVerdict: reviewResult.reviewVerdict,
6762
+ exhaustedPreviousRun: void 0
6763
+ }
6764
+ });
6765
+ } else {
6766
+ updateWorktreeRunState(options.worktreePath, {
6767
+ review: {
6768
+ ...commonFields,
6769
+ lastVerdict: reviewResult.reviewVerdict,
6770
+ exhaustedPreviousRun: true
6771
+ }
6772
+ });
6773
+ }
6505
6774
  return reviewResult;
6506
6775
  }
6507
6776
  async function completeIssueRun(options) {
@@ -6552,12 +6821,12 @@ async function completeIssueRun(options) {
6552
6821
  prTitle = finalizerFromState.title;
6553
6822
  prBody = finalizerFromState.body;
6554
6823
  } else if (finalizerFromState.artifactPath) {
6555
- if (!existsSync12(finalizerFromState.artifactPath)) {
6824
+ if (!existsSync13(finalizerFromState.artifactPath)) {
6556
6825
  throw new FinalizerFailure({
6557
6826
  message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
6558
6827
  });
6559
6828
  }
6560
- const artifactContent = readFileSync14(
6829
+ const artifactContent = readFileSync15(
6561
6830
  finalizerFromState.artifactPath,
6562
6831
  "utf-8"
6563
6832
  );
@@ -6595,7 +6864,7 @@ async function completeIssueRun(options) {
6595
6864
  prTitle = finalizerResult.title;
6596
6865
  prBody = finalizerResult.body;
6597
6866
  if (executionResult.worktreePath) {
6598
- await assertCanonicalBaseAncestor({
6867
+ await assertCanonicalBaseAncestor2({
6599
6868
  worktreePath: executionResult.worktreePath,
6600
6869
  baseRef: `origin/${effectiveBaseBranch}`,
6601
6870
  stageName: "Finalizer",
@@ -6699,14 +6968,14 @@ async function transitionIssueToHumanHandoff(options) {
6699
6968
  const transitions = makeIssueTransitions2(issueProvider, config);
6700
6969
  await transitions.moveToReadyForHuman(issueNumber);
6701
6970
  const summary = extractHumanHandoffSummary(reviewResult.output);
6702
- const refactorDir = getRefactorArtifactDir(reviewResult.artifactPath);
6971
+ const refactorDir = getRefactorArtifactDir(reviewResult.reviewArtifactPath);
6703
6972
  const comment = [
6704
6973
  "Pourkit stopped the review/refactor loop because human input is needed.",
6705
6974
  "",
6706
6975
  summary,
6707
6976
  "",
6708
6977
  "Artifacts:",
6709
- `- Review: ${reviewResult.artifactPath}`,
6978
+ `- Review: ${reviewResult.reviewArtifactPath}`,
6710
6979
  `- Refactors: ${refactorDir}`
6711
6980
  ].join("\n");
6712
6981
  await issueProvider.commentIssue(issueNumber, comment);
@@ -6735,7 +7004,7 @@ function getRefactorArtifactDir(artifactPath) {
6735
7004
  }
6736
7005
  async function finalizeWorktreeCommit(options) {
6737
7006
  const { worktreePath, baseRef, title, body, logger } = options;
6738
- await assertCanonicalBaseAncestor({
7007
+ await assertCanonicalBaseAncestor2({
6739
7008
  worktreePath,
6740
7009
  baseRef,
6741
7010
  stageName: "final commit",
@@ -6757,9 +7026,9 @@ async function finalizeWorktreeCommit(options) {
6757
7026
  label: "git commit"
6758
7027
  });
6759
7028
  }
6760
- async function assertCanonicalBaseAncestor(options) {
7029
+ async function assertCanonicalBaseAncestor2(options) {
6761
7030
  const { worktreePath, baseRef, stageName, logger } = options;
6762
- await syncRemoteBaseRef(worktreePath, baseRef, logger);
7031
+ await syncRemoteBaseRef2(worktreePath, baseRef, logger);
6763
7032
  try {
6764
7033
  await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
6765
7034
  cwd: worktreePath,
@@ -6821,8 +7090,8 @@ function normalizeGitPath2(path9) {
6821
7090
  function isIssueFinalReviewAuditedPath2(path9) {
6822
7091
  return path9 !== "" && path9 !== WORKTREE_RUN_STATE_PATH && !path9.startsWith(".pourkit/.tmp/") && !path9.startsWith(".pourkit/logs/");
6823
7092
  }
6824
- async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
6825
- const remoteBase = parseRemoteBaseRef(baseRef);
7093
+ async function syncRemoteBaseRef2(worktreePath, baseRef, logger) {
7094
+ const remoteBase = parseRemoteBaseRef2(baseRef);
6826
7095
  if (!remoteBase) {
6827
7096
  return;
6828
7097
  }
@@ -6832,7 +7101,7 @@ async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
6832
7101
  label: "git fetch target"
6833
7102
  });
6834
7103
  }
6835
- function parseRemoteBaseRef(baseRef) {
7104
+ function parseRemoteBaseRef2(baseRef) {
6836
7105
  const [remote, ...branchParts] = baseRef.split("/");
6837
7106
  const branch = branchParts.join("/");
6838
7107
  if (!remote || !branch) {
@@ -6911,15 +7180,6 @@ async function closeNoOpIssue(provider, issueNumber, config, logger) {
6911
7180
  await maybeCloseParentAfterChildCompletion(provider, issueNumber, logger);
6912
7181
  }
6913
7182
  }
6914
- function loadBuilderPrompt(repoRoot2, promptTemplate) {
6915
- const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
6916
- const promptBody = existsSync12(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
6917
- return appendProtectedWorkGuidance(`${promptBody}
6918
-
6919
- ## Shared Run Context
6920
-
6921
- Read the selected issue requirements, planning context (including plan and PRD content when present), comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}`);
6922
- }
6923
7183
 
6924
7184
  // issues/issue-run-lifecycle.ts
6925
7185
  var IssueRunHumanHandoffStop = class extends Error {
@@ -6933,8 +7193,10 @@ async function runIssueLifecycle(options) {
6933
7193
  const startResult = await options.deps.startIssueRun(options.runOptions);
6934
7194
  const reviewAlreadyPassed = startResult.worktreeState?.review.lastVerdict && !startResult.worktreeState.review.exhaustedPreviousRun && (startResult.worktreeState.review.lastVerdict === "PASS" || startResult.worktreeState.review.lastVerdict === "PASS_WITH_NOTES");
6935
7195
  let reviewArtifactPath;
7196
+ let reviewVerdict = void 0;
6936
7197
  if (reviewAlreadyPassed) {
6937
7198
  reviewArtifactPath = startResult.worktreeState.review.lastArtifactPath;
7199
+ reviewVerdict = startResult.worktreeState.review.lastVerdict;
6938
7200
  } else {
6939
7201
  const reviewResult = await options.deps.advanceIssueRunReview({
6940
7202
  executionProvider: options.runOptions.executionProvider,
@@ -6951,28 +7213,34 @@ async function runIssueLifecycle(options) {
6951
7213
  serena: startResult.serena,
6952
7214
  memory: startResult.memory
6953
7215
  });
6954
- if (reviewResult.exhaustedMaxIterations) {
6955
- throw new Error(
6956
- "Review loop exhausted maximum iterations without passing"
6957
- );
6958
- }
6959
- if (reviewResult.verdict === "FAIL") {
6960
- const summary = reviewResult.output?.trim() || "no summary provided";
6961
- throw new Error(`Review loop failed: ${summary}`);
6962
- }
6963
- if (reviewResult.verdict === "NEEDS_HUMAN") {
6964
- await options.deps.transitionReviewNeedsHumanToHandoff({
6965
- issueProvider: options.issueProvider,
6966
- issueNumber: options.issueNumber,
6967
- config: options.config,
6968
- logger: options.logger,
6969
- reviewResult
6970
- });
6971
- throw new IssueRunHumanHandoffStop(
6972
- "Review requires human handoff: NEEDS_HUMAN verdict"
6973
- );
7216
+ switch (reviewResult.status) {
7217
+ case "passed":
7218
+ reviewArtifactPath = reviewResult.reviewArtifactPath;
7219
+ reviewVerdict = reviewResult.reviewVerdict;
7220
+ break;
7221
+ case "needs_human":
7222
+ await options.deps.transitionReviewNeedsHumanToHandoff({
7223
+ issueProvider: options.issueProvider,
7224
+ issueNumber: options.issueNumber,
7225
+ config: options.config,
7226
+ logger: options.logger,
7227
+ reviewResult
7228
+ });
7229
+ throw new IssueRunHumanHandoffStop(
7230
+ "Review requires human handoff: NEEDS_HUMAN verdict"
7231
+ );
7232
+ case "exhausted_max_iterations":
7233
+ await options.deps.transitionReviewNeedsHumanToHandoff({
7234
+ issueProvider: options.issueProvider,
7235
+ issueNumber: options.issueNumber,
7236
+ config: options.config,
7237
+ logger: options.logger,
7238
+ reviewResult
7239
+ });
7240
+ throw new IssueRunHumanHandoffStop(
7241
+ "Review exhausted maximum iterations - requires human handoff"
7242
+ );
6974
7243
  }
6975
- reviewArtifactPath = reviewResult.artifactPath;
6976
7244
  }
6977
7245
  const finalReviewResult = await options.deps.advanceIssueFinalReview({
6978
7246
  executionProvider: options.runOptions.executionProvider,
@@ -6985,6 +7253,7 @@ async function runIssueLifecycle(options) {
6985
7253
  repoRoot: options.repoRoot,
6986
7254
  logger: options.logger,
6987
7255
  reviewArtifactPath,
7256
+ reviewVerdict,
6988
7257
  worktreeState: startResult.worktreeState,
6989
7258
  memory: startResult.memory
6990
7259
  });
@@ -7186,9 +7455,9 @@ import { join as join17 } from "path";
7186
7455
 
7187
7456
  // prd-run/state.ts
7188
7457
  import {
7189
- existsSync as existsSync13,
7458
+ existsSync as existsSync14,
7190
7459
  mkdirSync as mkdirSync7,
7191
- readFileSync as readFileSync15,
7460
+ readFileSync as readFileSync16,
7192
7461
  readdirSync as readdirSync3,
7193
7462
  writeFileSync as writeFileSync4
7194
7463
  } from "fs";
@@ -7277,16 +7546,16 @@ function normalizePrdRunRef(ref) {
7277
7546
  function readPrdRun(repoRoot2, prdRef) {
7278
7547
  const normalized = normalizePrdRunRef(prdRef);
7279
7548
  const recordPath = getRecordPath(repoRoot2, normalized);
7280
- if (!existsSync13(recordPath)) {
7549
+ if (!existsSync14(recordPath)) {
7281
7550
  return { record: null, diagnostics: [] };
7282
7551
  }
7283
7552
  try {
7284
- const raw = JSON.parse(readFileSync15(recordPath, "utf-8"));
7553
+ const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
7285
7554
  const parsed = PrdRunRecordSchema.parse(raw);
7286
7555
  return { record: parsed, diagnostics: [] };
7287
7556
  } catch (error) {
7288
7557
  try {
7289
- const raw = JSON.parse(readFileSync15(recordPath, "utf-8"));
7558
+ const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
7290
7559
  if (raw && typeof raw === "object" && raw.start && typeof raw.start === "object" && raw.start.startBaseBranch === void 0) {
7291
7560
  return {
7292
7561
  record: raw,
@@ -7308,7 +7577,7 @@ function readPrdRun(repoRoot2, prdRef) {
7308
7577
  }
7309
7578
  function listPrdRuns(repoRoot2) {
7310
7579
  const stateDir = join16(repoRoot2, PRD_RUN_STATE_DIR);
7311
- if (!existsSync13(stateDir)) {
7580
+ if (!existsSync14(stateDir)) {
7312
7581
  return { records: [], diagnostics: [] };
7313
7582
  }
7314
7583
  const records = [];
@@ -7320,7 +7589,7 @@ function listPrdRuns(repoRoot2) {
7320
7589
  const recordPath = join16(stateDir, entry.name);
7321
7590
  try {
7322
7591
  const record = PrdRunRecordSchema.parse(
7323
- JSON.parse(readFileSync15(recordPath, "utf-8"))
7592
+ JSON.parse(readFileSync16(recordPath, "utf-8"))
7324
7593
  );
7325
7594
  records.push(record);
7326
7595
  } catch (error) {
@@ -7356,7 +7625,7 @@ import { Match, pipe } from "effect";
7356
7625
 
7357
7626
  // commands/queue.ts
7358
7627
  init_common();
7359
- import { Effect as Effect8 } from "effect";
7628
+ import { Effect as Effect9 } from "effect";
7360
7629
 
7361
7630
  // issues/select-issue.ts
7362
7631
  init_common();
@@ -7529,8 +7798,8 @@ function issueDataToCandidate(issue) {
7529
7798
  function selectNextQueueIssue(options) {
7530
7799
  const { config, issueProvider, logger, prdRef } = options;
7531
7800
  logger.step("info", "Loading candidate issues from provider");
7532
- return Effect8.gen(function* () {
7533
- const candidates = yield* Effect8.tryPromise({
7801
+ return Effect9.gen(function* () {
7802
+ const candidates = yield* Effect9.tryPromise({
7534
7803
  try: () => issueProvider.listCandidates(),
7535
7804
  catch: (e) => {
7536
7805
  if (e instanceof Error) {
@@ -7601,7 +7870,7 @@ function selectNextQueueIssue(options) {
7601
7870
  (c) => c.number === selection.issue.number
7602
7871
  );
7603
7872
  if (!selected) {
7604
- return yield* Effect8.die(
7873
+ return yield* Effect9.die(
7605
7874
  new Error(
7606
7875
  `Selected issue #${selection.issue.number} not found in candidate list`
7607
7876
  )
@@ -7656,8 +7925,8 @@ function makeReconcileDeps(options) {
7656
7925
  };
7657
7926
  }
7658
7927
  function reconcileBlockedEffect(options) {
7659
- return Effect8.gen(function* () {
7660
- const blocked2 = yield* Effect8.tryPromise({
7928
+ return Effect9.gen(function* () {
7929
+ const blocked2 = yield* Effect9.tryPromise({
7661
7930
  try: () => options.issueProvider.listBlockedIssues(),
7662
7931
  catch: (e) => {
7663
7932
  if (e instanceof Error) {
@@ -7671,7 +7940,7 @@ function reconcileBlockedEffect(options) {
7671
7940
  "info",
7672
7941
  `Reconciling ${blocked2.length} blocked issue(s)`
7673
7942
  );
7674
- yield* Effect8.tryPromise({
7943
+ yield* Effect9.tryPromise({
7675
7944
  try: () => reconcileBlockedIssues(blocked2, makeReconcileDeps(options)),
7676
7945
  catch: (e) => {
7677
7946
  if (e instanceof Error) {
@@ -7695,7 +7964,7 @@ function runOneQueueIssueEffect(options) {
7695
7964
  repoRoot: repoRoot2,
7696
7965
  prdRef
7697
7966
  } = options;
7698
- return Effect8.gen(function* () {
7967
+ return Effect9.gen(function* () {
7699
7968
  const outcome = yield* selectNextQueueIssue({
7700
7969
  config,
7701
7970
  issueProvider,
@@ -7711,7 +7980,7 @@ function runOneQueueIssueEffect(options) {
7711
7980
  }
7712
7981
  const { issue: selected } = outcome;
7713
7982
  const baseBranchOverride = options.queueRunContext?.prdBranch;
7714
- const runResult = yield* Effect8.tryPromise({
7983
+ const runResult = yield* Effect9.tryPromise({
7715
7984
  try: () => runIssueCommand({
7716
7985
  issueNumber: selected.number,
7717
7986
  targetName,
@@ -7751,7 +8020,7 @@ function runQueue(options) {
7751
8020
  return runOneQueueIssueEffect(options);
7752
8021
  }
7753
8022
  function runQueueLoopEffect(options, results) {
7754
- return Effect8.gen(function* () {
8023
+ return Effect9.gen(function* () {
7755
8024
  yield* reconcileBlockedEffect(options);
7756
8025
  const outcome = yield* runOneQueueIssueEffect(options);
7757
8026
  if (outcome.selected === null) {
@@ -7768,7 +8037,7 @@ function runQueueLoopEffect(options, results) {
7768
8037
  };
7769
8038
  }
7770
8039
  const newResults = [...results, outcome];
7771
- const processedIssue = yield* Effect8.tryPromise({
8040
+ const processedIssue = yield* Effect9.tryPromise({
7772
8041
  try: () => options.issueProvider.fetchIssue(outcome.selected.number),
7773
8042
  catch: (e) => {
7774
8043
  if (e instanceof Error) {
@@ -9103,7 +9372,7 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
9103
9372
  }
9104
9373
 
9105
9374
  // prd-run/final-review-validation.ts
9106
- import { existsSync as existsSync14, readFileSync as readFileSync16 } from "fs";
9375
+ import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
9107
9376
 
9108
9377
  // commands/prd-run.ts
9109
9378
  async function runPrdRunLaunchCommand(options) {
@@ -9613,7 +9882,7 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
9613
9882
  }
9614
9883
 
9615
9884
  // commands/init.ts
9616
- import { existsSync as existsSync15, statSync } from "fs";
9885
+ import { existsSync as existsSync16, statSync } from "fs";
9617
9886
  import {
9618
9887
  copyFile,
9619
9888
  mkdir as mkdir4,
@@ -9819,7 +10088,17 @@ function buildManagedAgentInstructions(options) {
9819
10088
 
9820
10089
  Use \`fd\` for file discovery, \`rg\` for text search, and direct file reads for focused context.
9821
10090
 
9822
- Use \`opensrc path <package>\` when implementation requires source code for repo dependencies, instead of relying only on package docs or generated type declarations. Search or read files under the returned source path with \`rg\` and direct file reads.
10091
+ For multi-step exploration, prefer the dedicated subagents in \`.opencode/agents\`:
10092
+
10093
+ - Use \`codebase-locator\` when you need to find relevant files, directories, or entry points.
10094
+ - Use \`codebase-analyzer\` when you need to understand how a specific component or flow works.
10095
+ - Use \`codebase-pattern-finder\` when you need examples of similar implementations or test patterns.
10096
+ - Use \`pourkit-docs-locator\` when searching \`.pourkit/\` context docs or ADRs.
10097
+ - Use \`pourkit-docs-analyzer\` to explain specific context docs or ADRs after locating them.
10098
+
10099
+ Use direct \`fd\`/\`rg\`/reads for focused checks; use subagents when the search would otherwise require several discovery/read steps.
10100
+
10101
+ Use \`opensrc path <package-or-repo>\` whenever implementation requires reading external source code, not only package dependencies. If you would otherwise clone a repository to inspect its source, use \`opensrc\` first, then search or read files under the returned source path with \`rg\` and direct file reads.
9823
10102
 
9824
10103
  Follow the project's domain docs and workflow routes documented in the Pourkit managed block below.
9825
10104
 
@@ -9928,7 +10207,7 @@ function resolveSourceAssetPath(sourceRoot, ...segments) {
9928
10207
  path5.join(sourceRoot, "dist", ...segments)
9929
10208
  ];
9930
10209
  for (const candidatePath of candidatePaths) {
9931
- if (existsSync15(candidatePath)) return candidatePath;
10210
+ if (existsSync16(candidatePath)) return candidatePath;
9932
10211
  }
9933
10212
  return path5.join(sourceRoot, "pourkit", ...segments);
9934
10213
  }
@@ -10113,8 +10392,7 @@ function generateConfigTemplate(options) {
10113
10392
  model: "opencode-go/qwen3.6-plus",
10114
10393
  promptTemplate: ".pourkit/managed/prompts/refactor.prompt.md"
10115
10394
  },
10116
- maxIterations: 3,
10117
- passWithNotesRefactorAttempts: 2
10395
+ maxIterations: 3
10118
10396
  },
10119
10397
  issueFinalReview: {
10120
10398
  agent: "pourkit-reviewer",
@@ -10144,7 +10422,7 @@ function generateConfigTemplate(options) {
10144
10422
  }
10145
10423
  const config = {
10146
10424
  $schema: "./schema/pourkit.schema.json",
10147
- schemaVersion: 2,
10425
+ schemaVersion: 3,
10148
10426
  targets: [target],
10149
10427
  workflowPack: {
10150
10428
  version: "1.0.0",
@@ -10291,7 +10569,7 @@ async function computeFileChecksum(filePath) {
10291
10569
  return createHash("sha256").update(content).digest("hex");
10292
10570
  }
10293
10571
  function lockfileExists(root, name) {
10294
- return existsSync15(path5.join(root, name));
10572
+ return existsSync16(path5.join(root, name));
10295
10573
  }
10296
10574
  function detectPackageManager(root) {
10297
10575
  if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
@@ -10335,7 +10613,7 @@ async function discoverLocalSource(sourcePath) {
10335
10613
  async function discoverReadme(root) {
10336
10614
  for (const name of ["README.md", "readme.md"]) {
10337
10615
  const p = path5.join(root, name);
10338
- if (existsSync15(p)) {
10616
+ if (existsSync16(p)) {
10339
10617
  return p;
10340
10618
  }
10341
10619
  }
@@ -10345,7 +10623,7 @@ async function discoverAgentFiles(root) {
10345
10623
  const files = [];
10346
10624
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
10347
10625
  const p = path5.join(root, name);
10348
- if (existsSync15(p)) {
10626
+ if (existsSync16(p)) {
10349
10627
  files.push(p);
10350
10628
  }
10351
10629
  }
@@ -10353,7 +10631,7 @@ async function discoverAgentFiles(root) {
10353
10631
  }
10354
10632
  async function discoverMerlleState(root) {
10355
10633
  const p = path5.join(root, ".pourkit", "state.json");
10356
- return existsSync15(p) ? p : null;
10634
+ return existsSync16(p) ? p : null;
10357
10635
  }
10358
10636
  async function discoverAgentSkills(root) {
10359
10637
  const dirs = [
@@ -10362,7 +10640,7 @@ async function discoverAgentSkills(root) {
10362
10640
  ];
10363
10641
  const found = [];
10364
10642
  for (const d of dirs) {
10365
- if (existsSync15(d)) {
10643
+ if (existsSync16(d)) {
10366
10644
  found.push(d);
10367
10645
  }
10368
10646
  }
@@ -10373,7 +10651,7 @@ async function discoverOpenCodeAgentFiles(sourceRoot) {
10373
10651
  resolveSourceAssetPath(sourceRoot, ".opencode", "agents"),
10374
10652
  path5.join(sourceRoot, "..", ".opencode", "agents")
10375
10653
  ].find(
10376
- (candidate) => existsSync15(candidate) && statSync(candidate).isDirectory()
10654
+ (candidate) => existsSync16(candidate) && statSync(candidate).isDirectory()
10377
10655
  );
10378
10656
  if (!agentsDir) return [];
10379
10657
  const entries = await readdir(agentsDir, { withFileTypes: true });
@@ -10383,12 +10661,12 @@ async function discoverRootDomainDocs(root) {
10383
10661
  const docs = [];
10384
10662
  for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
10385
10663
  const p = path5.join(root, name);
10386
- if (existsSync15(p)) {
10664
+ if (existsSync16(p)) {
10387
10665
  docs.push(p);
10388
10666
  }
10389
10667
  }
10390
10668
  const adrDir = path5.join(root, "docs", "adr");
10391
- if (existsSync15(adrDir)) {
10669
+ if (existsSync16(adrDir)) {
10392
10670
  const entries = await readdir(adrDir, { withFileTypes: true });
10393
10671
  for (const entry of entries) {
10394
10672
  if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -10530,7 +10808,7 @@ async function planInit(options) {
10530
10808
  for (const file of skillFiles) {
10531
10809
  const relPath = path5.relative(s, file);
10532
10810
  const destPath = path5.join(targetRoot, ".agents", "skills", relPath);
10533
- if (!existsSync15(destPath)) {
10811
+ if (!existsSync16(destPath)) {
10534
10812
  operations.push({
10535
10813
  kind: "copy",
10536
10814
  sourcePath: file,
@@ -10593,7 +10871,7 @@ async function planInit(options) {
10593
10871
  });
10594
10872
  }
10595
10873
  if (sourceRoot) {
10596
- if (!existsSync15(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
10874
+ if (!existsSync16(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
10597
10875
  warnings.push(
10598
10876
  `--from-local path does not exist or is not a directory: ${sourceRoot}`
10599
10877
  );
@@ -10658,9 +10936,9 @@ async function planInit(options) {
10658
10936
  "release",
10659
10937
  "skills"
10660
10938
  );
10661
- const srcSkills = existsSync15(packagedManagedSkills) ? [
10939
+ const srcSkills = existsSync16(packagedManagedSkills) ? [
10662
10940
  packagedManagedSkills,
10663
- ...existsSync15(packagedReleaseAddonSkills) ? [packagedReleaseAddonSkills] : []
10941
+ ...existsSync16(packagedReleaseAddonSkills) ? [packagedReleaseAddonSkills] : []
10664
10942
  ] : await discoverAgentSkills(sourceRoot);
10665
10943
  for (const s of srcSkills) {
10666
10944
  const isOpenCode = s.includes(".opencode");
@@ -10683,7 +10961,7 @@ async function planInit(options) {
10683
10961
  "skills",
10684
10962
  relPath
10685
10963
  );
10686
- if (!existsSync15(destPath)) {
10964
+ if (!existsSync16(destPath)) {
10687
10965
  operations.push({
10688
10966
  kind: "copy",
10689
10967
  sourcePath: file,
@@ -10815,7 +11093,7 @@ async function planInit(options) {
10815
11093
  requiresConfirmation: false,
10816
11094
  destructive: false
10817
11095
  });
10818
- } else if (existsSync15(destPath)) {
11096
+ } else if (existsSync16(destPath)) {
10819
11097
  operations.push({
10820
11098
  kind: "skip",
10821
11099
  path: destPath,
@@ -10873,7 +11151,7 @@ async function planInit(options) {
10873
11151
  }
10874
11152
  }
10875
11153
  const contextPath = path5.join(targetRoot, ".pourkit", "CONTEXT.md");
10876
- if (!existsSync15(contextPath) && !merleDestPaths.has(contextPath)) {
11154
+ if (!existsSync16(contextPath) && !merleDestPaths.has(contextPath)) {
10877
11155
  operations.push({
10878
11156
  kind: "create",
10879
11157
  path: contextPath,
@@ -10891,7 +11169,7 @@ async function planInit(options) {
10891
11169
  "adr",
10892
11170
  ".gitkeep"
10893
11171
  );
10894
- if (!existsSync15(adrGitkeep)) {
11172
+ if (!existsSync16(adrGitkeep)) {
10895
11173
  operations.push({
10896
11174
  kind: "create",
10897
11175
  path: adrGitkeep,
@@ -10913,7 +11191,7 @@ async function planInit(options) {
10913
11191
  "docs",
10914
11192
  "agents"
10915
11193
  );
10916
- const srcDocAgents = existsSync15(packagedManagedDocAgents) ? packagedManagedDocAgents : legacyDocAgents;
11194
+ const srcDocAgents = existsSync16(packagedManagedDocAgents) ? packagedManagedDocAgents : legacyDocAgents;
10917
11195
  const tgtManagedDocAgents = path5.join(
10918
11196
  targetRoot,
10919
11197
  ".pourkit",
@@ -10927,12 +11205,12 @@ async function planInit(options) {
10927
11205
  "docs",
10928
11206
  "agents"
10929
11207
  );
10930
- if (existsSync15(srcDocAgents)) {
11208
+ if (existsSync16(srcDocAgents)) {
10931
11209
  const docFiles = await walkDir(srcDocAgents);
10932
11210
  for (const file of docFiles) {
10933
11211
  const relPath = path5.relative(srcDocAgents, file);
10934
11212
  const managedDocDest = path5.join(tgtManagedDocAgents, relPath);
10935
- if (!existsSync15(managedDocDest) && relPath === "triage-labels.md") {
11213
+ if (!existsSync16(managedDocDest) && relPath === "triage-labels.md") {
10936
11214
  operations.push({
10937
11215
  kind: "create",
10938
11216
  path: managedDocDest,
@@ -10944,7 +11222,7 @@ async function planInit(options) {
10944
11222
  options.labels ?? DEFAULT_RUNNER_LABELS
10945
11223
  )
10946
11224
  });
10947
- } else if (!existsSync15(managedDocDest)) {
11225
+ } else if (!existsSync16(managedDocDest)) {
10948
11226
  const checksum = await computeFileChecksum(file);
10949
11227
  operations.push({
10950
11228
  kind: "copy",
@@ -10966,7 +11244,7 @@ async function planInit(options) {
10966
11244
  relPath
10967
11245
  );
10968
11246
  const stubDocDest = path5.join(tgtStubDocAgents, relPath);
10969
- if (existsSync15(stubDocDest)) continue;
11247
+ if (existsSync16(stubDocDest)) continue;
10970
11248
  operations.push({
10971
11249
  kind: "create",
10972
11250
  path: stubDocDest,
@@ -11000,7 +11278,7 @@ Do not edit this file.
11000
11278
  "docs",
11001
11279
  "agents"
11002
11280
  );
11003
- if (existsSync15(packagedReleaseAddonDocs)) {
11281
+ if (existsSync16(packagedReleaseAddonDocs)) {
11004
11282
  const docFiles = await walkDir(packagedReleaseAddonDocs);
11005
11283
  for (const file of docFiles) {
11006
11284
  const relPath = path5.relative(packagedReleaseAddonDocs, file);
@@ -11024,19 +11302,19 @@ Do not edit this file.
11024
11302
  "prompts"
11025
11303
  );
11026
11304
  const legacyPrompts = path5.join(sourceRoot, ".pourkit", "prompts");
11027
- const srcPrompts = existsSync15(packagedManagedPrompts) ? packagedManagedPrompts : legacyPrompts;
11305
+ const srcPrompts = existsSync16(packagedManagedPrompts) ? packagedManagedPrompts : legacyPrompts;
11028
11306
  const tgtManagedPrompts = path5.join(
11029
11307
  targetRoot,
11030
11308
  ".pourkit",
11031
11309
  "managed",
11032
11310
  "prompts"
11033
11311
  );
11034
- if (existsSync15(srcPrompts)) {
11312
+ if (existsSync16(srcPrompts)) {
11035
11313
  const promptFiles = await walkDir(srcPrompts);
11036
11314
  for (const file of promptFiles) {
11037
11315
  const relPath = path5.relative(srcPrompts, file);
11038
11316
  const promptDest = path5.join(tgtManagedPrompts, relPath);
11039
- if (existsSync15(promptDest)) continue;
11317
+ if (existsSync16(promptDest)) continue;
11040
11318
  const checksum = await computeFileChecksum(file);
11041
11319
  operations.push({
11042
11320
  kind: "copy",
@@ -11060,7 +11338,7 @@ Do not edit this file.
11060
11338
  ".sandcastle",
11061
11339
  "Dockerfile"
11062
11340
  );
11063
- if (existsSync15(tgtSandboxDockerfile)) {
11341
+ if (existsSync16(tgtSandboxDockerfile)) {
11064
11342
  operations.push({
11065
11343
  kind: "skip",
11066
11344
  path: tgtSandboxDockerfile,
@@ -11069,7 +11347,7 @@ Do not edit this file.
11069
11347
  requiresConfirmation: false,
11070
11348
  destructive: false
11071
11349
  });
11072
- } else if (existsSync15(srcSandboxDockerfile)) {
11350
+ } else if (existsSync16(srcSandboxDockerfile)) {
11073
11351
  const checksum = await computeFileChecksum(srcSandboxDockerfile);
11074
11352
  operations.push({
11075
11353
  kind: "copy",
@@ -11083,7 +11361,7 @@ Do not edit this file.
11083
11361
  });
11084
11362
  }
11085
11363
  const configJsonPath = path5.join(targetRoot, ".pourkit", "config.json");
11086
- if (!existsSync15(configJsonPath)) {
11364
+ if (!existsSync16(configJsonPath)) {
11087
11365
  const verifyCommands = inferVerificationCommands(
11088
11366
  packageScripts,
11089
11367
  pm || "npm"
@@ -11127,9 +11405,9 @@ Do not edit this file.
11127
11405
  "schema",
11128
11406
  "pourkit.schema.json"
11129
11407
  );
11130
- if (existsSync15(srcSchemaJson)) {
11408
+ if (existsSync16(srcSchemaJson)) {
11131
11409
  const checksum = await computeFileChecksum(srcSchemaJson);
11132
- if (!existsSync15(schemaJsonPath)) {
11410
+ if (!existsSync16(schemaJsonPath)) {
11133
11411
  operations.push({
11134
11412
  kind: "copy",
11135
11413
  sourcePath: srcSchemaJson,
@@ -11162,9 +11440,9 @@ Do not edit this file.
11162
11440
  "schema",
11163
11441
  "pourkit.schema.hash"
11164
11442
  );
11165
- if (existsSync15(srcSchemaHash)) {
11443
+ if (existsSync16(srcSchemaHash)) {
11166
11444
  const checksum = await computeFileChecksum(srcSchemaHash);
11167
- if (!existsSync15(schemaHashPath)) {
11445
+ if (!existsSync16(schemaHashPath)) {
11168
11446
  operations.push({
11169
11447
  kind: "copy",
11170
11448
  sourcePath: srcSchemaHash,
@@ -11190,7 +11468,7 @@ Do not edit this file.
11190
11468
  const hasExistingAgents = operations.some(
11191
11469
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
11192
11470
  );
11193
- if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync15(path5.join(targetRoot, "AGENTS.md"))) {
11471
+ if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync16(path5.join(targetRoot, "AGENTS.md"))) {
11194
11472
  operations.push({
11195
11473
  kind: "create",
11196
11474
  path: path5.join(targetRoot, "AGENTS.md"),
@@ -11206,7 +11484,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
11206
11484
  const hasExistingClaude = operations.some(
11207
11485
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
11208
11486
  );
11209
- if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync15(path5.join(targetRoot, "CLAUDE.md"))) {
11487
+ if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync16(path5.join(targetRoot, "CLAUDE.md"))) {
11210
11488
  operations.push({
11211
11489
  kind: "create",
11212
11490
  path: path5.join(targetRoot, "CLAUDE.md"),
@@ -11221,7 +11499,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
11221
11499
  }
11222
11500
  const gitignoreTarget = path5.join(targetRoot, ".gitignore");
11223
11501
  const gitignoreContent = generateGitignoreBlock();
11224
- if (!existsSync15(gitignoreTarget)) {
11502
+ if (!existsSync16(gitignoreTarget)) {
11225
11503
  operations.push({
11226
11504
  kind: "create",
11227
11505
  path: gitignoreTarget,
@@ -11252,7 +11530,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
11252
11530
  "agents",
11253
11531
  path5.basename(agentFile)
11254
11532
  );
11255
- if (!existsSync15(targetPath)) {
11533
+ if (!existsSync16(targetPath)) {
11256
11534
  operations.push({
11257
11535
  kind: "copy",
11258
11536
  sourcePath: agentFile,
@@ -11275,7 +11553,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
11275
11553
  }
11276
11554
  }
11277
11555
  const manifestPath = path5.join(targetRoot, ".pourkit", "manifest.json");
11278
- if (existsSync15(manifestPath)) {
11556
+ if (existsSync16(manifestPath)) {
11279
11557
  operations.push({
11280
11558
  kind: "skip",
11281
11559
  path: manifestPath,
@@ -11665,7 +11943,7 @@ async function updateManagedBlock(filePath, content) {
11665
11943
  const blockContent = `${MANAGED_BLOCK_BEGIN}
11666
11944
  ${content}${MANAGED_BLOCK_END}
11667
11945
  `;
11668
- if (!existsSync15(filePath)) {
11946
+ if (!existsSync16(filePath)) {
11669
11947
  const dir = path5.dirname(filePath);
11670
11948
  await mkdir4(dir, { recursive: true });
11671
11949
  await writeFileAtomic(filePath, blockContent);
@@ -11694,7 +11972,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
11694
11972
  if (op.requiresConfirmation) continue;
11695
11973
  const relPath = path5.relative(plan.targetRoot, op.path);
11696
11974
  if (relPath === ".pourkit/manifest.json") continue;
11697
- if (existsSync15(op.path)) {
11975
+ if (existsSync16(op.path)) {
11698
11976
  const sha256 = await computeFileChecksum(op.path);
11699
11977
  assets[relPath] = {
11700
11978
  ownership: op.ownership || "managed",
@@ -11740,7 +12018,7 @@ async function applyInitPlan(plan, options) {
11740
12018
  skipped++;
11741
12019
  continue;
11742
12020
  }
11743
- if (existsSync15(op.path) && !op.destructive) {
12021
+ if (existsSync16(op.path) && !op.destructive) {
11744
12022
  skipped++;
11745
12023
  continue;
11746
12024
  }
@@ -11755,7 +12033,7 @@ async function applyInitPlan(plan, options) {
11755
12033
  skipped++;
11756
12034
  continue;
11757
12035
  }
11758
- if (existsSync15(op.path)) {
12036
+ if (existsSync16(op.path)) {
11759
12037
  skipped++;
11760
12038
  continue;
11761
12039
  }
@@ -11784,7 +12062,7 @@ async function applyInitPlan(plan, options) {
11784
12062
  skipped++;
11785
12063
  continue;
11786
12064
  }
11787
- if (existsSync15(op.path)) {
12065
+ if (existsSync16(op.path)) {
11788
12066
  skipped++;
11789
12067
  continue;
11790
12068
  }
@@ -11928,7 +12206,7 @@ async function applyInitFromSource(options) {
11928
12206
  if (!manifestSkipped) {
11929
12207
  const agentFiles = [];
11930
12208
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
11931
- if (existsSync15(path5.join(targetRoot, name))) {
12209
+ if (existsSync16(path5.join(targetRoot, name))) {
11932
12210
  agentFiles.push(path5.join(targetRoot, name));
11933
12211
  }
11934
12212
  }
@@ -12104,18 +12382,18 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
12104
12382
  }
12105
12383
 
12106
12384
  // commands/memory-init.ts
12107
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
12385
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync5 } from "fs";
12108
12386
  import { join as join18 } from "path";
12109
12387
  async function runMemoryInitCommand(options) {
12110
12388
  const cwd = options.cwd ?? process.cwd();
12111
12389
  const configPath = join18(cwd, ".pourkit", "config.json");
12112
- if (!existsSync16(configPath)) {
12390
+ if (!existsSync17(configPath)) {
12113
12391
  return {
12114
12392
  ok: false,
12115
12393
  message: "No .pourkit/config.json found. Run `pourkit init` first to initialize Pourkit, then run `pourkit memory init`."
12116
12394
  };
12117
12395
  }
12118
- const raw = JSON.parse(readFileSync17(configPath, "utf8"));
12396
+ const raw = JSON.parse(readFileSync18(configPath, "utf8"));
12119
12397
  let alreadyEnabled = false;
12120
12398
  if (Object.prototype.hasOwnProperty.call(raw, "memory")) {
12121
12399
  const m = raw.memory;
@@ -12150,7 +12428,7 @@ async function runMemoryInitCommand(options) {
12150
12428
  let updatedAgentFile = false;
12151
12429
  for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
12152
12430
  const agentPath = join18(cwd, fileName);
12153
- if (existsSync16(agentPath)) {
12431
+ if (existsSync17(agentPath)) {
12154
12432
  updateManagedBlockSync(agentPath, managed);
12155
12433
  updatedAgentFile = true;
12156
12434
  }
@@ -12168,11 +12446,11 @@ function updateManagedBlockSync(filePath, content) {
12168
12446
  const blockContent = `${MANAGED_BLOCK_BEGIN}
12169
12447
  ${content}${MANAGED_BLOCK_END}
12170
12448
  `;
12171
- if (!existsSync16(filePath)) {
12449
+ if (!existsSync17(filePath)) {
12172
12450
  writeFileSync5(filePath, blockContent);
12173
12451
  return;
12174
12452
  }
12175
- const existing = readFileSync17(filePath, "utf8");
12453
+ const existing = readFileSync18(filePath, "utf8");
12176
12454
  const beginIdx = existing.indexOf(MANAGED_BLOCK_BEGIN);
12177
12455
  const endIdx = existing.indexOf(MANAGED_BLOCK_END);
12178
12456
  if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
@@ -12301,7 +12579,7 @@ async function runSerenaStatusCommand(options) {
12301
12579
  }
12302
12580
 
12303
12581
  // commands/config-schema.ts
12304
- import { readFileSync as readFileSync18, existsSync as existsSync17 } from "fs";
12582
+ import { readFileSync as readFileSync19, existsSync as existsSync18 } from "fs";
12305
12583
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile2, readdir as readdir2 } from "fs/promises";
12306
12584
  import { resolve as resolve3, dirname as dirname5, relative as relative2 } from "path";
12307
12585
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -12314,7 +12592,7 @@ function resolvePackagedAssetPath2(fileName) {
12314
12592
  resolve3(__dirname3, "schema", fileName),
12315
12593
  resolve3(__dirname3, "../schema", fileName)
12316
12594
  ];
12317
- return candidates.find((candidate) => existsSync17(candidate)) ?? candidates[0];
12595
+ return candidates.find((candidate) => existsSync18(candidate)) ?? candidates[0];
12318
12596
  }
12319
12597
  var PACKAGED_SCHEMA_PATH = resolvePackagedAssetPath2("pourkit.schema.json");
12320
12598
  var PACKAGED_HASH_PATH = resolvePackagedAssetPath2("pourkit.schema.hash");
@@ -12322,7 +12600,7 @@ var _schemaValidator = null;
12322
12600
  var _schemaErrors = null;
12323
12601
  function getSchemaValidator() {
12324
12602
  if (!_schemaValidator) {
12325
- const schema = JSON.parse(readFileSync18(PACKAGED_SCHEMA_PATH, "utf-8"));
12603
+ const schema = JSON.parse(readFileSync19(PACKAGED_SCHEMA_PATH, "utf-8"));
12326
12604
  const ajv = new Ajv2({ strict: true });
12327
12605
  ajv.addKeyword("x-pourkit-schema-version");
12328
12606
  const validate = ajv.compile(schema);
@@ -12336,7 +12614,7 @@ function getSchemaValidator() {
12336
12614
  return _schemaValidator;
12337
12615
  }
12338
12616
  function readPackagedHash() {
12339
- return readFileSync18(PACKAGED_HASH_PATH, "utf-8");
12617
+ return readFileSync19(PACKAGED_HASH_PATH, "utf-8");
12340
12618
  }
12341
12619
  function localSchemaDir(repoRoot2) {
12342
12620
  return resolve3(repoRoot2, ".pourkit/schema");
@@ -12457,7 +12735,7 @@ async function validateWorkflowPack(cwd) {
12457
12735
  const baselineSkills = getBaselineManagedSkillNames();
12458
12736
  for (const docName of catalog.docs) {
12459
12737
  const localPath = resolve3(cwd, `.pourkit/managed/docs/agents/${docName}`);
12460
- if (!existsSync17(localPath)) {
12738
+ if (!existsSync18(localPath)) {
12461
12739
  failures.push({
12462
12740
  severity: "failure",
12463
12741
  kind: "missing_managed_asset",
@@ -12475,7 +12753,7 @@ async function validateWorkflowPack(cwd) {
12475
12753
  cwd,
12476
12754
  `.pourkit/overrides/docs/agents/${docName}`
12477
12755
  );
12478
- if (existsSync17(overridePath)) {
12756
+ if (existsSync18(overridePath)) {
12479
12757
  warnings.push({
12480
12758
  severity: "warning",
12481
12759
  kind: "overridden",
@@ -12498,7 +12776,7 @@ async function validateWorkflowPack(cwd) {
12498
12776
  }
12499
12777
  for (const skillName of baselineSkills) {
12500
12778
  const skillDir = resolve3(cwd, `.pourkit/managed/skills/${skillName}`);
12501
- if (!existsSync17(skillDir)) {
12779
+ if (!existsSync18(skillDir)) {
12502
12780
  failures.push({
12503
12781
  severity: "failure",
12504
12782
  kind: "missing_managed_asset",
@@ -12507,7 +12785,7 @@ async function validateWorkflowPack(cwd) {
12507
12785
  });
12508
12786
  } else {
12509
12787
  const skillSourcePath = resolve3(skillDir, "SKILL.md");
12510
- if (existsSync17(skillSourcePath)) {
12788
+ if (existsSync18(skillSourcePath)) {
12511
12789
  const packagedSkillPath = resolvePackagedManagedPath(
12512
12790
  "skills",
12513
12791
  skillName,
@@ -12522,7 +12800,7 @@ async function validateWorkflowPack(cwd) {
12522
12800
  cwd,
12523
12801
  `.pourkit/overrides/skills/${skillName}/SKILL.md`
12524
12802
  );
12525
- if (existsSync17(overridePath)) {
12803
+ if (existsSync18(overridePath)) {
12526
12804
  warnings.push({
12527
12805
  severity: "warning",
12528
12806
  kind: "overridden",
@@ -12546,7 +12824,7 @@ async function validateWorkflowPack(cwd) {
12546
12824
  }
12547
12825
  for (const promptName of catalog.prompts) {
12548
12826
  const promptPath = resolve3(cwd, `.pourkit/managed/prompts/${promptName}`);
12549
- if (!existsSync17(promptPath)) {
12827
+ if (!existsSync18(promptPath)) {
12550
12828
  failures.push({
12551
12829
  severity: "failure",
12552
12830
  kind: "missing_managed_asset",
@@ -12557,7 +12835,7 @@ async function validateWorkflowPack(cwd) {
12557
12835
  }
12558
12836
  for (const skillName of baselineSkills) {
12559
12837
  const projectionDir = resolve3(cwd, `.agents/skills/${skillName}`);
12560
- if (!existsSync17(projectionDir)) {
12838
+ if (!existsSync18(projectionDir)) {
12561
12839
  warnings.push({
12562
12840
  severity: "warning",
12563
12841
  kind: "projection_stale",
@@ -12566,7 +12844,7 @@ async function validateWorkflowPack(cwd) {
12566
12844
  });
12567
12845
  } else {
12568
12846
  const projectionPath = resolve3(projectionDir, "SKILL.md");
12569
- if (existsSync17(projectionPath)) {
12847
+ if (existsSync18(projectionPath)) {
12570
12848
  const managedSourcePath = resolvePackagedManagedPath(
12571
12849
  "skills",
12572
12850
  skillName,
@@ -12595,7 +12873,7 @@ async function validateWorkflowPack(cwd) {
12595
12873
  }
12596
12874
  }
12597
12875
  const agentsPath = resolve3(cwd, "AGENTS.md");
12598
- if (existsSync17(agentsPath)) {
12876
+ if (existsSync18(agentsPath)) {
12599
12877
  try {
12600
12878
  const agentsContent = await readFile5(agentsPath, "utf-8");
12601
12879
  if (agentsContent.includes(OLD_DOCS_PATH_PATTERN)) {
@@ -12618,7 +12896,7 @@ async function validateWorkflowPack(cwd) {
12618
12896
  }
12619
12897
  }
12620
12898
  const overridesDir = resolve3(cwd, ".pourkit/overrides");
12621
- if (existsSync17(overridesDir)) {
12899
+ if (existsSync18(overridesDir)) {
12622
12900
  const overrideFiles = await walkDir2(overridesDir);
12623
12901
  for (const file of overrideFiles) {
12624
12902
  const relPath = relative2(cwd, file);
@@ -12633,7 +12911,7 @@ async function validateWorkflowPack(cwd) {
12633
12911
  }
12634
12912
  }
12635
12913
  const configPath = resolve3(cwd, ".pourkit/config.json");
12636
- if (existsSync17(configPath)) {
12914
+ if (existsSync18(configPath)) {
12637
12915
  try {
12638
12916
  const configContent = await readFile5(configPath, "utf-8");
12639
12917
  const parsed = JSON.parse(configContent);
@@ -12644,7 +12922,7 @@ async function validateWorkflowPack(cwd) {
12644
12922
  cwd,
12645
12923
  `.pourkit/managed/addons/release/skills/${skillName}`
12646
12924
  );
12647
- if (!existsSync17(addonDir)) {
12925
+ if (!existsSync18(addonDir)) {
12648
12926
  failures.push({
12649
12927
  severity: "failure",
12650
12928
  kind: "release_config_conflict",
@@ -12658,7 +12936,7 @@ async function validateWorkflowPack(cwd) {
12658
12936
  cwd,
12659
12937
  `.pourkit/managed/addons/release/docs/agents/${docName}`
12660
12938
  );
12661
- if (!existsSync17(addonDocPath)) {
12939
+ if (!existsSync18(addonDocPath)) {
12662
12940
  failures.push({
12663
12941
  severity: "failure",
12664
12942
  kind: "release_config_conflict",
@@ -12703,7 +12981,7 @@ async function validateWorkflowPack(cwd) {
12703
12981
  cwd,
12704
12982
  `.pourkit/managed/addons/release/skills/${skillName}`
12705
12983
  );
12706
- if (existsSync17(addonDir)) {
12984
+ if (existsSync18(addonDir)) {
12707
12985
  failures.push({
12708
12986
  severity: "failure",
12709
12987
  kind: "release_config_conflict",
@@ -12717,7 +12995,7 @@ async function validateWorkflowPack(cwd) {
12717
12995
  cwd,
12718
12996
  `.pourkit/managed/addons/release/docs/agents/${docName}`
12719
12997
  );
12720
- if (existsSync17(addonDocPath)) {
12998
+ if (existsSync18(addonDocPath)) {
12721
12999
  failures.push({
12722
13000
  severity: "failure",
12723
13001
  kind: "release_config_conflict",
@@ -12731,7 +13009,7 @@ async function validateWorkflowPack(cwd) {
12731
13009
  }
12732
13010
  }
12733
13011
  const manifestPath = resolve3(cwd, ".pourkit/manifest.json");
12734
- if (existsSync17(manifestPath)) {
13012
+ if (existsSync18(manifestPath)) {
12735
13013
  try {
12736
13014
  const manifestContent = await readFile5(manifestPath, "utf-8");
12737
13015
  const manifest = JSON.parse(manifestContent);
@@ -12754,7 +13032,7 @@ async function validateWorkflowPack(cwd) {
12754
13032
  }
12755
13033
  }
12756
13034
  const pathEscapeOverrideDir = resolve3(cwd, ".pourkit/overrides");
12757
- if (existsSync17(pathEscapeOverrideDir)) {
13035
+ if (existsSync18(pathEscapeOverrideDir)) {
12758
13036
  const overrideFiles = await walkDir2(pathEscapeOverrideDir);
12759
13037
  for (const file of overrideFiles) {
12760
13038
  if (!isPathContained(cwd, file)) {
@@ -12768,7 +13046,7 @@ async function validateWorkflowPack(cwd) {
12768
13046
  }
12769
13047
  }
12770
13048
  const managedDocsDir = resolve3(cwd, ".pourkit/managed/docs/agents");
12771
- if (existsSync17(managedDocsDir)) {
13049
+ if (existsSync18(managedDocsDir)) {
12772
13050
  const docFiles = await walkDir2(managedDocsDir);
12773
13051
  for (const file of docFiles) {
12774
13052
  if (!file.endsWith(".md")) continue;
@@ -12789,12 +13067,12 @@ async function validateWorkflowPack(cwd) {
12789
13067
  }
12790
13068
  }
12791
13069
  const managedSkillsDir = resolve3(cwd, ".pourkit/managed/skills");
12792
- if (existsSync17(managedSkillsDir)) {
13070
+ if (existsSync18(managedSkillsDir)) {
12793
13071
  const skillDirs = await readdir2(managedSkillsDir, { withFileTypes: true });
12794
13072
  for (const entry of skillDirs) {
12795
13073
  if (!entry.isDirectory()) continue;
12796
13074
  const skillFile = resolve3(managedSkillsDir, entry.name, "SKILL.md");
12797
- if (!existsSync17(skillFile)) continue;
13075
+ if (!existsSync18(skillFile)) continue;
12798
13076
  try {
12799
13077
  const content = await readFile5(skillFile, "utf-8");
12800
13078
  const relPath = relative2(cwd, skillFile);
@@ -12812,10 +13090,10 @@ async function validateWorkflowPack(cwd) {
12812
13090
  }
12813
13091
  }
12814
13092
  const managedPromptsDir = resolve3(cwd, ".pourkit/managed/prompts");
12815
- if (existsSync17(managedPromptsDir)) {
13093
+ if (existsSync18(managedPromptsDir)) {
12816
13094
  for (const promptName of catalog.prompts) {
12817
13095
  const promptFile = resolve3(managedPromptsDir, promptName);
12818
- if (!existsSync17(promptFile)) continue;
13096
+ if (!existsSync18(promptFile)) continue;
12819
13097
  try {
12820
13098
  const content = await readFile5(promptFile, "utf-8");
12821
13099
  const relPath = relative2(cwd, promptFile);
@@ -12837,7 +13115,7 @@ async function validateWorkflowPack(cwd) {
12837
13115
  cwd,
12838
13116
  `.pourkit/managed/addons/release/skills/${skillName}/SKILL.md`
12839
13117
  );
12840
- if (!existsSync17(addonSkillFile)) continue;
13118
+ if (!existsSync18(addonSkillFile)) continue;
12841
13119
  try {
12842
13120
  const content = await readFile5(addonSkillFile, "utf-8");
12843
13121
  const relPath = relative2(cwd, addonSkillFile);
@@ -12858,7 +13136,7 @@ async function validateWorkflowPack(cwd) {
12858
13136
  cwd,
12859
13137
  `.pourkit/managed/addons/release/docs/agents/${docName}`
12860
13138
  );
12861
- if (!existsSync17(addonDocFile)) continue;
13139
+ if (!existsSync18(addonDocFile)) continue;
12862
13140
  try {
12863
13141
  const content = await readFile5(addonDocFile, "utf-8");
12864
13142
  const relPath = relative2(cwd, addonDocFile);
@@ -12892,7 +13170,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
12892
13170
  detail: icmPath !== null ? icmPath : "icm not found on PATH"
12893
13171
  });
12894
13172
  const gitignorePath = resolve3(cwd, ".gitignore");
12895
- const gitignoreContent = existsSync17(gitignorePath) ? readFileSync18(gitignorePath, "utf-8") : "";
13173
+ const gitignoreContent = existsSync18(gitignorePath) ? readFileSync19(gitignorePath, "utf-8") : "";
12896
13174
  const hasGitignoreEntry = gitignoreContent.includes(".pourkit/icm/");
12897
13175
  checks.push({
12898
13176
  name: "icm-gitignore",
@@ -12919,8 +13197,8 @@ async function runDoctorCommand(options) {
12919
13197
  const schemaDir = localSchemaDir(repoRootPath);
12920
13198
  const localSchemaPath = resolve3(schemaDir, "pourkit.schema.json");
12921
13199
  const localHashPath = resolve3(schemaDir, "pourkit.schema.hash");
12922
- const localSchemaExists = existsSync17(localSchemaPath);
12923
- const localHashExists = existsSync17(localHashPath);
13200
+ const localSchemaExists = existsSync18(localSchemaPath);
13201
+ const localHashExists = existsSync18(localHashPath);
12924
13202
  let packagedHash = null;
12925
13203
  try {
12926
13204
  packagedHash = readPackagedHash();
@@ -12939,9 +13217,9 @@ async function runDoctorCommand(options) {
12939
13217
  const configPath = resolve3(repoRootPath, ".pourkit/config.json");
12940
13218
  let configValidation;
12941
13219
  let rawMemoryConfig;
12942
- if (existsSync17(configPath)) {
13220
+ if (existsSync18(configPath)) {
12943
13221
  try {
12944
- const raw = JSON.parse(readFileSync18(configPath, "utf-8"));
13222
+ const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
12945
13223
  rawMemoryConfig = raw.memory;
12946
13224
  const validate = getSchemaValidator();
12947
13225
  const valid2 = validate(raw);
@@ -12974,7 +13252,7 @@ async function runDoctorCommand(options) {
12974
13252
  "pourkit.config.mjs",
12975
13253
  "pourkit.config.js",
12976
13254
  "pourkit.json"
12977
- ].filter((p) => existsSync17(resolve3(repoRootPath, p)));
13255
+ ].filter((p) => existsSync18(resolve3(repoRootPath, p)));
12978
13256
  const workflowPack = await validateWorkflowPack(repoRootPath);
12979
13257
  const memory = await validateMemoryHealth(repoRootPath, rawMemoryConfig);
12980
13258
  let recommendation = null;
@@ -13024,7 +13302,7 @@ async function runConfigSyncSchemaCommand(options) {
13024
13302
  const localHashPath = resolve3(schemaDir, "pourkit.schema.hash");
13025
13303
  let schemaWritten = false;
13026
13304
  let hashWritten = false;
13027
- if (existsSync17(localSchemaPath)) {
13305
+ if (existsSync18(localSchemaPath)) {
13028
13306
  const existing = await readFile5(localSchemaPath, "utf-8");
13029
13307
  if (existing !== packagedSchema) {
13030
13308
  await writeFile2(localSchemaPath, packagedSchema, "utf-8");
@@ -13034,7 +13312,7 @@ async function runConfigSyncSchemaCommand(options) {
13034
13312
  await writeFile2(localSchemaPath, packagedSchema, "utf-8");
13035
13313
  schemaWritten = true;
13036
13314
  }
13037
- if (existsSync17(localHashPath)) {
13315
+ if (existsSync18(localHashPath)) {
13038
13316
  const existing = await readFile5(localHashPath, "utf-8");
13039
13317
  if (existing !== packagedHash) {
13040
13318
  await writeFile2(localHashPath, packagedHash, "utf-8");
@@ -13052,7 +13330,7 @@ async function runConfigSyncSchemaCommand(options) {
13052
13330
  }
13053
13331
 
13054
13332
  // commands/workflow-pack-sync.ts
13055
- import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
13333
+ import { existsSync as existsSync19, readdirSync as readdirSync4 } from "fs";
13056
13334
  import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile3, readdir as readdir3 } from "fs/promises";
13057
13335
  import { resolve as resolve4, dirname as dirname6, relative as relative3, normalize as normalize2 } from "path";
13058
13336
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -13175,7 +13453,7 @@ async function syncFile(cwd, targetRelativePath, sourceContent, errors) {
13175
13453
  return false;
13176
13454
  }
13177
13455
  await mkdir6(resolve4(targetPath, ".."), { recursive: true });
13178
- if (existsSync18(targetPath)) {
13456
+ if (existsSync19(targetPath)) {
13179
13457
  const existing = await readFile6(targetPath, "utf-8");
13180
13458
  if (existing === sourceContent) {
13181
13459
  return false;
@@ -13202,7 +13480,7 @@ function walkDirSync(dir) {
13202
13480
  }
13203
13481
  async function detectOverrides(cwd) {
13204
13482
  const overridesDir = resolve4(cwd, OVERRIDES_DIR);
13205
- if (!existsSync18(overridesDir)) {
13483
+ if (!existsSync19(overridesDir)) {
13206
13484
  return [];
13207
13485
  }
13208
13486
  const files = await walkDir3(overridesDir);
@@ -13212,13 +13490,13 @@ async function detectProjectOwnedFiles(cwd) {
13212
13490
  const found = [];
13213
13491
  for (const ownedPath of PROJECT_OWNED_PATHS) {
13214
13492
  const fullPath = resolve4(cwd, ownedPath);
13215
- if (existsSync18(fullPath)) {
13493
+ if (existsSync19(fullPath)) {
13216
13494
  found.push(ownedPath);
13217
13495
  }
13218
13496
  }
13219
13497
  for (const ownedDir of PROJECT_OWNED_DIRECTORIES) {
13220
13498
  const fullDir = resolve4(cwd, ownedDir);
13221
- if (existsSync18(fullDir)) {
13499
+ if (existsSync19(fullDir)) {
13222
13500
  const files = await walkDir3(fullDir);
13223
13501
  for (const file of files) {
13224
13502
  found.push(relative3(cwd, file));
@@ -13370,7 +13648,7 @@ async function runWorkflowPackSyncCommand(options) {
13370
13648
  const fullManagedBlock = `${MANAGED_BLOCK_BEGIN3}${managedBlockContent}${MANAGED_BLOCK_END2}`;
13371
13649
  async function syncManagedAgentFile(fileName, options2) {
13372
13650
  const filePath = resolve4(cwd, fileName);
13373
- if (!existsSync18(filePath)) {
13651
+ if (!existsSync19(filePath)) {
13374
13652
  if (!options2.createIfMissing) return false;
13375
13653
  await writeFile3(filePath, fullManagedBlock + "\n", "utf-8");
13376
13654
  return true;
@@ -14152,7 +14430,7 @@ function formatChecks2(checks) {
14152
14430
  init_common();
14153
14431
 
14154
14432
  // execution/sandcastle-execution.ts
14155
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
14433
+ import { existsSync as existsSync21, mkdirSync as mkdirSync9, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
14156
14434
  import { join as join21 } from "path";
14157
14435
  import { createWorktree, opencode } from "@ai-hero/sandcastle";
14158
14436
  import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
@@ -14180,18 +14458,18 @@ import path7 from "path";
14180
14458
 
14181
14459
  // execution/sandbox-image.ts
14182
14460
  import { createHash as createHash2 } from "crypto";
14183
- import { existsSync as existsSync19, readFileSync as readFileSync19 } from "fs";
14461
+ import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
14184
14462
  import path6 from "path";
14185
14463
  function sandboxImageName(repoRoot2, installIcm) {
14186
14464
  const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
14187
14465
  const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
14188
14466
  const baseName = sanitized || "local";
14189
14467
  const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
14190
- if (!existsSync19(dockerfilePath)) {
14468
+ if (!existsSync20(dockerfilePath)) {
14191
14469
  const base2 = `sandcastle:${baseName}`;
14192
14470
  return installIcm ? `${base2}-icm` : base2;
14193
14471
  }
14194
- const fingerprint = createHash2("sha256").update(readFileSync19(dockerfilePath)).digest("hex").slice(0, 8);
14472
+ const fingerprint = createHash2("sha256").update(readFileSync20(dockerfilePath)).digest("hex").slice(0, 8);
14195
14473
  const base = `sandcastle:${baseName}-${fingerprint}`;
14196
14474
  return installIcm ? `${base}-icm` : base;
14197
14475
  }
@@ -14376,7 +14654,7 @@ var SandcastleExecutionSession = class {
14376
14654
  );
14377
14655
  }
14378
14656
  const hostMemoryDir = join21(repoRoot2, ".pourkit", "icm");
14379
- if (!existsSync20(hostMemoryDir) || !statSync2(hostMemoryDir).isDirectory()) {
14657
+ if (!existsSync21(hostMemoryDir) || !statSync2(hostMemoryDir).isDirectory()) {
14380
14658
  throw new MissingHostMemoryDirError(
14381
14659
  "Memory enabled but host .pourkit/icm/ directory is missing. Run `pourkit memory init` to create the repo-scoped memory store."
14382
14660
  );
@@ -14541,7 +14819,7 @@ function sanitizeBranch(branchName) {
14541
14819
  function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
14542
14820
  return paths.filter((relativePath) => {
14543
14821
  const source = join21(repoRoot2, relativePath);
14544
- if (!existsSync20(source)) {
14822
+ if (!existsSync21(source)) {
14545
14823
  return true;
14546
14824
  }
14547
14825
  try {
@@ -14552,7 +14830,7 @@ function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
14552
14830
  return true;
14553
14831
  }
14554
14832
  const destination = join21(worktreePath, relativePath);
14555
- return !existsSync20(destination);
14833
+ return !existsSync21(destination);
14556
14834
  });
14557
14835
  }
14558
14836
  function formatAgentStreamEvent(event) {
@@ -15342,11 +15620,11 @@ function createCliProgram(version) {
15342
15620
  return program;
15343
15621
  }
15344
15622
  async function resolveCliVersion() {
15345
- if (isPackageVersion("0.0.0-next-20260621045826")) {
15346
- return "0.0.0-next-20260621045826";
15623
+ if (isPackageVersion("0.0.0-next-20260621233457")) {
15624
+ return "0.0.0-next-20260621233457";
15347
15625
  }
15348
- if (isReleaseVersion("0.0.0-next-20260621045826")) {
15349
- return "0.0.0-next-20260621045826";
15626
+ if (isReleaseVersion("0.0.0-next-20260621233457")) {
15627
+ return "0.0.0-next-20260621233457";
15350
15628
  }
15351
15629
  try {
15352
15630
  const root = repoRoot();