@pourkit/cli 0.0.0-next-20260531213458 → 0.0.0-next-20260601224754

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
@@ -333,6 +333,426 @@ var init_common = __esm({
333
333
  }
334
334
  });
335
335
 
336
+ // providers/github-client.ts
337
+ var github_client_exports = {};
338
+ __export(github_client_exports, {
339
+ requireGitHubClient: () => requireGitHubClient,
340
+ resolveGitHubRepository: () => resolveGitHubRepository,
341
+ resolveGitHubToken: () => resolveGitHubToken,
342
+ tryCreateGitHubClient: () => tryCreateGitHubClient
343
+ });
344
+ import { Octokit } from "octokit";
345
+ function resolveGitHubToken(env) {
346
+ const token = env.POURKIT_GITHUB_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN;
347
+ if (!token) {
348
+ throw new Error(
349
+ "GitHub token is required. Set POURKIT_GITHUB_TOKEN, GH_TOKEN, or GITHUB_TOKEN."
350
+ );
351
+ }
352
+ return token;
353
+ }
354
+ async function resolveGitHubRepository(options) {
355
+ if (options?.repository) {
356
+ const parts = options.repository.split("/");
357
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
358
+ throw new Error(
359
+ `Invalid repository format: "${options.repository}". Expected "owner/repo".`
360
+ );
361
+ }
362
+ return { owner: parts[0], repo: parts[1] };
363
+ }
364
+ const env = options?.env ?? process.env;
365
+ const envRepo = env.GITHUB_REPOSITORY;
366
+ if (envRepo) {
367
+ const parts = envRepo.split("/");
368
+ if (parts.length === 2 && parts[0] && parts[1]) {
369
+ return { owner: parts[0], repo: parts[1] };
370
+ }
371
+ throw new Error(
372
+ `Invalid repository format: "${envRepo}". Expected "owner/repo".`
373
+ );
374
+ }
375
+ const { execCapture: execCapture2 } = await Promise.resolve().then(() => (init_common(), common_exports));
376
+ const cwd = options?.cwd;
377
+ try {
378
+ const result = await execCapture2("git", ["remote", "get-url", "origin"], {
379
+ cwd
380
+ });
381
+ const remote = result.stdout.trim();
382
+ const match = remote.match(REMOTE_PATTERN);
383
+ if (match) {
384
+ return { owner: match[1], repo: match[2] };
385
+ }
386
+ } catch {
387
+ }
388
+ throw new Error(
389
+ "Could not resolve GitHub repository. Set GITHUB_REPOSITORY env var or ensure a valid 'origin' remote exists."
390
+ );
391
+ }
392
+ async function requireGitHubClient(options) {
393
+ const env = options?.env ?? process.env;
394
+ const token = resolveGitHubToken(env);
395
+ const repo = await resolveGitHubRepository(options);
396
+ const octokit = new Octokit({ auth: token });
397
+ return { octokit, ...repo };
398
+ }
399
+ async function tryCreateGitHubClient(options) {
400
+ const env = options?.env ?? process.env;
401
+ let token;
402
+ try {
403
+ token = resolveGitHubToken(env);
404
+ } catch {
405
+ return {
406
+ ok: false,
407
+ reason: "missing-token",
408
+ message: "GitHub token is not configured."
409
+ };
410
+ }
411
+ let repo;
412
+ try {
413
+ repo = await resolveGitHubRepository(options);
414
+ } catch (e) {
415
+ const message = e instanceof Error ? e.message : String(e);
416
+ if (message.includes("Invalid repository format")) {
417
+ return { ok: false, reason: "invalid-repository", message };
418
+ }
419
+ return { ok: false, reason: "missing-repository", message };
420
+ }
421
+ const octokit = new Octokit({ auth: token });
422
+ return { ok: true, client: { octokit, ...repo } };
423
+ }
424
+ var REMOTE_PATTERN;
425
+ var init_github_client = __esm({
426
+ "providers/github-client.ts"() {
427
+ "use strict";
428
+ REMOTE_PATTERN = /github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/;
429
+ }
430
+ });
431
+
432
+ // execution/execution-provider.ts
433
+ import { writeFile } from "fs/promises";
434
+ import { dirname as dirname3, join as join7 } from "path";
435
+ async function writeExecutionArtifacts(worktreePath, artifacts) {
436
+ for (const artifact of artifacts) {
437
+ const filePath = join7(worktreePath, artifact.path);
438
+ await ensureDir(dirname3(filePath));
439
+ await writeFile(filePath, artifact.content, "utf-8");
440
+ }
441
+ }
442
+ var init_execution_provider = __esm({
443
+ "execution/execution-provider.ts"() {
444
+ "use strict";
445
+ init_common();
446
+ }
447
+ });
448
+
449
+ // execution/sandbox-image.ts
450
+ import { createHash } from "crypto";
451
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
452
+ import path5 from "path";
453
+ function sandboxImageName(repoRoot2) {
454
+ const dirName = path5.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
455
+ const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
456
+ const baseName = sanitized || "local";
457
+ const dockerfilePath = path5.join(repoRoot2, ".sandcastle", "Dockerfile");
458
+ if (!existsSync5(dockerfilePath)) {
459
+ return `sandcastle:${baseName}`;
460
+ }
461
+ const fingerprint = createHash("sha256").update(readFileSync5(dockerfilePath)).digest("hex").slice(0, 8);
462
+ return `sandcastle:${baseName}-${fingerprint}`;
463
+ }
464
+ var init_sandbox_image = __esm({
465
+ "execution/sandbox-image.ts"() {
466
+ "use strict";
467
+ }
468
+ });
469
+
470
+ // execution/sandbox-image-build.ts
471
+ import path6 from "path";
472
+ async function ensureSandboxImageBuilt(repoRoot2, options) {
473
+ const imageName = sandboxImageName(repoRoot2);
474
+ const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
475
+ if (!options?.force) {
476
+ try {
477
+ await execCapture("docker", ["image", "inspect", imageName]);
478
+ return;
479
+ } catch {
480
+ }
481
+ }
482
+ const buildArgs = ["build", "-t", imageName, "-f", dockerfilePath];
483
+ if (options?.force) {
484
+ buildArgs.push("--pull", "--no-cache");
485
+ }
486
+ buildArgs.push(repoRoot2);
487
+ await execCapture("docker", buildArgs);
488
+ }
489
+ var init_sandbox_image_build = __esm({
490
+ "execution/sandbox-image-build.ts"() {
491
+ "use strict";
492
+ init_common();
493
+ init_sandbox_image();
494
+ }
495
+ });
496
+
497
+ // execution/sandcastle-existing-worktree.ts
498
+ async function createSandboxFromExistingWorktree(options) {
499
+ const sandcastleEntryUrl = import.meta.resolve("@ai-hero/sandcastle");
500
+ const createSandboxUrl = new URL("./createSandbox.js", sandcastleEntryUrl);
501
+ const sandcastleCreateSandbox = await import(createSandboxUrl.href);
502
+ return sandcastleCreateSandbox.createSandboxFromWorktree(options);
503
+ }
504
+ var init_sandcastle_existing_worktree = __esm({
505
+ "execution/sandcastle-existing-worktree.ts"() {
506
+ "use strict";
507
+ }
508
+ });
509
+
510
+ // execution/sandbox-options.ts
511
+ function buildSandboxOptions(repoRoot2, sandbox) {
512
+ const mounts = [];
513
+ if (sandbox.mounts !== void 0) {
514
+ mounts.push(...sandbox.mounts);
515
+ }
516
+ return {
517
+ imageName: sandboxImageName(repoRoot2),
518
+ ...mounts.length > 0 ? { mounts } : {},
519
+ ...sandbox.env !== void 0 ? { env: sandbox.env } : {},
520
+ ...sandbox.idleTimeoutSeconds !== void 0 ? { idleTimeoutSeconds: sandbox.idleTimeoutSeconds } : {}
521
+ };
522
+ }
523
+ var init_sandbox_options = __esm({
524
+ "execution/sandbox-options.ts"() {
525
+ "use strict";
526
+ init_sandbox_image();
527
+ }
528
+ });
529
+
530
+ // execution/opencode-config.ts
531
+ function isSerenaEligibleStage(stage) {
532
+ return stage === "builder" || stage === "refactor";
533
+ }
534
+ function buildSerenaOpenCodeConfig(stage, serena) {
535
+ if (!serena?.available || !isSerenaEligibleStage(stage)) {
536
+ return void 0;
537
+ }
538
+ return {
539
+ mcp: {
540
+ serena: {
541
+ type: "remote",
542
+ url: serena.sandboxMcpUrl,
543
+ enabled: true
544
+ }
545
+ }
546
+ };
547
+ }
548
+ var init_opencode_config = __esm({
549
+ "execution/opencode-config.ts"() {
550
+ "use strict";
551
+ }
552
+ });
553
+
554
+ // execution/sandcastle-execution.ts
555
+ var sandcastle_execution_exports = {};
556
+ __export(sandcastle_execution_exports, {
557
+ SandcastleExecutionProvider: () => SandcastleExecutionProvider
558
+ });
559
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
560
+ import { join as join8 } from "path";
561
+ import { createWorktree, opencode } from "@ai-hero/sandcastle";
562
+ import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
563
+ function resolveSandboxProvider(provider, dockerFactory) {
564
+ if (provider === "docker") {
565
+ return dockerFactory;
566
+ }
567
+ throw new Error(`Unsupported sandbox provider: ${provider}`);
568
+ }
569
+ function sanitizeBranch(branchName) {
570
+ return branchName.replace(/[^A-Za-z0-9._-]/g, "-");
571
+ }
572
+ function savePromptToFile(repoRoot2, stage, iteration, prompt) {
573
+ const promptsDir = join8(repoRoot2, ".pourkit", ".tmp", "prompts");
574
+ mkdirSync4(promptsDir, { recursive: true });
575
+ const timestamp2 = Date.now();
576
+ const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
577
+ const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
578
+ const filePath = join8(promptsDir, filename);
579
+ writeFileSync2(filePath, prompt, "utf-8");
580
+ }
581
+ var SandcastleExecutionProvider, SandcastleExecutionSession;
582
+ var init_sandcastle_execution = __esm({
583
+ "execution/sandcastle-execution.ts"() {
584
+ "use strict";
585
+ init_execution_provider();
586
+ init_sandbox_image_build();
587
+ init_sandcastle_existing_worktree();
588
+ init_sandbox_options();
589
+ init_opencode_config();
590
+ SandcastleExecutionProvider = class {
591
+ async createSession() {
592
+ return new SandcastleExecutionSession();
593
+ }
594
+ async execute(options) {
595
+ const session = await this.createSession();
596
+ try {
597
+ return await session.execute(options);
598
+ } finally {
599
+ await session.close();
600
+ }
601
+ }
602
+ };
603
+ SandcastleExecutionSession = class {
604
+ sandboxHandle = null;
605
+ worktreeHandle = null;
606
+ async close() {
607
+ try {
608
+ await this.sandboxHandle?.close?.();
609
+ } finally {
610
+ this.sandboxHandle = null;
611
+ }
612
+ }
613
+ async execute(options) {
614
+ const {
615
+ stage,
616
+ iteration,
617
+ agent,
618
+ model,
619
+ prompt,
620
+ target,
621
+ repoRoot: repoRoot2,
622
+ branchName,
623
+ worktreePath,
624
+ baseRef,
625
+ sandbox,
626
+ autoApprove = false,
627
+ timeoutMs,
628
+ artifacts = [],
629
+ logger
630
+ } = options;
631
+ const stageLabel = iteration !== void 0 ? `${stage}:${iteration}` : stage;
632
+ logger.step(
633
+ "sandcastle",
634
+ `[${stageLabel}] running agent "${agent}" with model "${model}"`
635
+ );
636
+ try {
637
+ const env = {};
638
+ if (autoApprove) {
639
+ env.OPENCODE_AUTO_APPROVE = "true";
640
+ }
641
+ const serenaConfig = buildSerenaOpenCodeConfig(stage, options.serena);
642
+ if (serenaConfig) {
643
+ env.OPENCODE_CONFIG_CONTENT = JSON.stringify(serenaConfig);
644
+ }
645
+ const logPath = `${repoRoot2}/.pourkit/logs/${sanitizeBranch(branchName)}-${Date.now()}.log`;
646
+ await ensureSandboxImageBuilt(repoRoot2, { force: sandbox.forceRebuild });
647
+ try {
648
+ savePromptToFile(repoRoot2, stage, iteration, prompt);
649
+ } catch {
650
+ }
651
+ const agentProvider = opencode(model, { env, agent });
652
+ const sandboxOptions = buildSandboxOptions(repoRoot2, sandbox);
653
+ const sandboxProvider = resolveSandboxProvider(sandbox.provider, docker);
654
+ const activeSandbox = await this.getOrCreateSandbox({
655
+ repoRoot: repoRoot2,
656
+ branchName,
657
+ baseBranch: baseRef ?? target.baseBranch,
658
+ worktreePath,
659
+ sandboxProvider: sandboxProvider(sandboxOptions),
660
+ setupCommands: target.setupCommands ?? [],
661
+ copyToWorktree: sandbox.copyToWorktree ?? []
662
+ });
663
+ await writeExecutionArtifacts(activeSandbox.worktreePath, artifacts);
664
+ const result = await activeSandbox.run({
665
+ agent: agentProvider,
666
+ prompt,
667
+ maxIterations: 1,
668
+ name: stageLabel,
669
+ logging: {
670
+ type: "file",
671
+ path: logPath,
672
+ onAgentStreamEvent: (event) => {
673
+ if (event.type === "text") {
674
+ logger.raw(event.message);
675
+ } else if (event.type === "toolCall") {
676
+ logger.raw(`${event.name}(${event.formattedArgs})`);
677
+ }
678
+ }
679
+ },
680
+ completionSignal: "<promise>COMPLETE</promise>",
681
+ ...timeoutMs ? { signal: AbortSignal.timeout(timeoutMs) } : {},
682
+ ...sandboxOptions.idleTimeoutSeconds ? { idleTimeoutSeconds: sandboxOptions.idleTimeoutSeconds } : {}
683
+ });
684
+ const commits = result.commits.map((c) => c.sha);
685
+ const resultBranch = result.branch ?? activeSandbox.branch ?? branchName;
686
+ logger.kv("SANDBOX_SUCCESS", "true");
687
+ logger.kv("COMMITS_CREATED", String(commits.length));
688
+ logger.kv("WORKTREE_BRANCH", resultBranch);
689
+ if (result.logFilePath) {
690
+ logger.kv("LOG_FILE", result.logFilePath);
691
+ }
692
+ return {
693
+ success: true,
694
+ branch: resultBranch,
695
+ worktreePath: activeSandbox.worktreePath,
696
+ commits,
697
+ logPath
698
+ };
699
+ } catch (error) {
700
+ logger.step(
701
+ "error",
702
+ `Sandcastle failed: ${error instanceof Error ? error.message : String(error)}`
703
+ );
704
+ return {
705
+ success: false,
706
+ branch: "",
707
+ worktreePath: "",
708
+ commits: [],
709
+ logPath: null,
710
+ error: error instanceof Error ? error.message : String(error)
711
+ };
712
+ }
713
+ }
714
+ async getOrCreateSandbox(options) {
715
+ if (this.sandboxHandle) {
716
+ return this.sandboxHandle;
717
+ }
718
+ const hooks = {
719
+ sandbox: {
720
+ onSandboxReady: options.setupCommands.map((command) => ({
721
+ command: command.command
722
+ }))
723
+ }
724
+ };
725
+ if (options.worktreePath) {
726
+ this.sandboxHandle = await createSandboxFromExistingWorktree({
727
+ branch: options.branchName,
728
+ hostRepoDir: options.repoRoot,
729
+ worktreePath: options.worktreePath,
730
+ sandbox: options.sandboxProvider,
731
+ ...options.copyToWorktree.length > 0 ? { copyToWorktree: options.copyToWorktree } : {},
732
+ hooks
733
+ });
734
+ return this.sandboxHandle;
735
+ }
736
+ this.worktreeHandle = await createWorktree({
737
+ cwd: options.repoRoot,
738
+ branchStrategy: {
739
+ type: "branch",
740
+ branch: options.branchName,
741
+ baseBranch: options.baseBranch
742
+ },
743
+ ...options.copyToWorktree.length > 0 ? { copyToWorktree: options.copyToWorktree } : {}
744
+ });
745
+ this.sandboxHandle = await this.worktreeHandle.createSandbox({
746
+ sandbox: options.sandboxProvider,
747
+ ...options.copyToWorktree.length > 0 ? { copyToWorktree: options.copyToWorktree } : {},
748
+ hooks
749
+ });
750
+ return this.sandboxHandle;
751
+ }
752
+ };
753
+ }
754
+ });
755
+
336
756
  // cli.ts
337
757
  import path8 from "path";
338
758
  import { realpathSync } from "fs";
@@ -997,8 +1417,8 @@ async function cleanupRepository(options) {
997
1417
  }
998
1418
 
999
1419
  // commands/issue-run.ts
1000
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1001
- import { join as join10 } from "path";
1420
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
1421
+ import { join as join12 } from "path";
1002
1422
 
1003
1423
  // pr/templates.ts
1004
1424
  init_common();
@@ -1262,7 +1682,43 @@ function invalidateAfterBaseRefresh(state) {
1262
1682
  }
1263
1683
 
1264
1684
  // failure-resolution/effect-runtime.ts
1265
- import { Effect } from "effect";
1685
+ import { Effect as Effect2 } from "effect";
1686
+
1687
+ // shared/effect-runtime.ts
1688
+ import { Effect, Exit } from "effect";
1689
+ import { UnknownException } from "effect/Cause";
1690
+ var initialized = false;
1691
+ function initializeEffectRuntime() {
1692
+ if (initialized) return;
1693
+ initialized = true;
1694
+ }
1695
+ function runEffect(program) {
1696
+ ensureEffectRuntime();
1697
+ return Effect.runPromiseExit(program);
1698
+ }
1699
+ function ensureEffectRuntime() {
1700
+ if (!initialized) {
1701
+ initializeEffectRuntime();
1702
+ }
1703
+ }
1704
+ function unwrapError(error) {
1705
+ if (error instanceof UnknownException && error.error !== void 0) {
1706
+ return error.error;
1707
+ }
1708
+ return error;
1709
+ }
1710
+ function runEffectAndMapExit(program) {
1711
+ return runEffect(program).then((exit) => {
1712
+ if (Exit.isSuccess(exit)) return exit.value;
1713
+ const cause = exit.cause;
1714
+ if (cause._tag === "Fail") {
1715
+ const error = unwrapError(cause.error);
1716
+ throw error instanceof Error ? error : new Error(String(error));
1717
+ }
1718
+ const defect = cause._tag === "Die" ? cause.defect : cause._tag === "Interrupt" ? "interrupted" : `unhandled cause: ${cause._tag}`;
1719
+ throw defect instanceof Error ? defect : new Error(`Unexpected defect: ${String(defect)}`);
1720
+ });
1721
+ }
1266
1722
 
1267
1723
  // failure-resolution/types.ts
1268
1724
  var RebaseConflict = class extends Error {
@@ -1320,8 +1776,26 @@ var SafetyFailure = class extends Error {
1320
1776
  this.message = args.message;
1321
1777
  }
1322
1778
  };
1323
- var SUPPORTED_DECISIONS = /* @__PURE__ */ new Set([
1324
- "RETRY_STAGE",
1779
+ var ReviewerFailure = class extends Error {
1780
+ _tag = "ReviewerFailure";
1781
+ message;
1782
+ constructor(args) {
1783
+ super(args.message);
1784
+ this.name = "ReviewerFailure";
1785
+ this.message = args.message;
1786
+ }
1787
+ };
1788
+ var FinalizerFailure = class extends Error {
1789
+ _tag = "FinalizerFailure";
1790
+ message;
1791
+ constructor(args) {
1792
+ super(args.message);
1793
+ this.name = "FinalizerFailure";
1794
+ this.message = args.message;
1795
+ }
1796
+ };
1797
+ var SUPPORTED_DECISIONS = /* @__PURE__ */ new Set([
1798
+ "RETRY_STAGE",
1325
1799
  "HANDOFF_TO_HUMAN",
1326
1800
  "FAIL_RUN"
1327
1801
  ]);
@@ -1495,23 +1969,23 @@ function recordStageAttempt(worktreePath, record) {
1495
1969
 
1496
1970
  // failure-resolution/effect-runtime.ts
1497
1971
  function baseRefreshEffect(options) {
1498
- return Effect.promise(() => refreshStaleIssueBranch(options)).pipe(
1499
- Effect.flatMap(
1972
+ return Effect2.promise(() => refreshStaleIssueBranch(options)).pipe(
1973
+ Effect2.flatMap(
1500
1974
  (result) => {
1501
1975
  switch (result.status) {
1502
1976
  case "refreshed":
1503
- return Effect.succeed({ status: "refreshed" });
1977
+ return Effect2.succeed({ status: "refreshed" });
1504
1978
  case "skipped-current":
1505
- return Effect.succeed({ status: "skipped-current" });
1979
+ return Effect2.succeed({ status: "skipped-current" });
1506
1980
  case "conflicted":
1507
- return Effect.fail(
1981
+ return Effect2.fail(
1508
1982
  new RebaseConflict({
1509
1983
  conflictedPaths: result.conflictedPaths,
1510
1984
  message: result.message
1511
1985
  })
1512
1986
  );
1513
1987
  case "refused-published-history":
1514
- return Effect.fail(
1988
+ return Effect2.fail(
1515
1989
  new PublishedHistoryRisk({
1516
1990
  prNumber: result.prNumber,
1517
1991
  prState: result.prState
@@ -1526,8 +2000,8 @@ async function runBaseRefreshAttempt(options) {
1526
2000
  const attemptId = createStageAttemptId();
1527
2001
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1528
2002
  const program = baseRefreshEffect(options).pipe(
1529
- Effect.tapBoth({
1530
- onSuccess: (success) => Effect.sync(() => {
2003
+ Effect2.tapBoth({
2004
+ onSuccess: (success) => Effect2.sync(() => {
1531
2005
  recordStageAttempt(options.worktreePath, {
1532
2006
  id: attemptId,
1533
2007
  stage: "baseRefresh",
@@ -1536,7 +2010,7 @@ async function runBaseRefreshAttempt(options) {
1536
2010
  outcome: "success"
1537
2011
  });
1538
2012
  }),
1539
- onFailure: (failure) => Effect.sync(() => {
2013
+ onFailure: (failure) => Effect2.sync(() => {
1540
2014
  recordStageAttempt(options.worktreePath, {
1541
2015
  id: attemptId,
1542
2016
  stage: "baseRefresh",
@@ -1552,7 +2026,7 @@ async function runBaseRefreshAttempt(options) {
1552
2026
  })
1553
2027
  })
1554
2028
  );
1555
- return Effect.runPromiseExit(program);
2029
+ return runEffect(program);
1556
2030
  }
1557
2031
 
1558
2032
  // commands/conflict-resolution.ts
@@ -1575,7 +2049,7 @@ async function hasUnresolvedConflictMarkers(worktreePath, files) {
1575
2049
  }
1576
2050
 
1577
2051
  // commands/issue-run.ts
1578
- import { Exit as Exit2 } from "effect";
2052
+ import { Exit as Exit3 } from "effect";
1579
2053
 
1580
2054
  // serena/baseline.ts
1581
2055
  init_common();
@@ -2124,8 +2598,7 @@ async function writeRecoveryAttempt(worktreePath, outcome, fingerprint, summary,
2124
2598
  }
2125
2599
 
2126
2600
  // commands/pr-description-agent.ts
2127
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync2 } from "fs";
2128
- import { dirname as dirname3, join as join8 } from "path";
2601
+ import { dirname as dirname4, join as join10 } from "path";
2129
2602
 
2130
2603
  // pr/pr-description.ts
2131
2604
  var CONVENTIONAL_TITLE_PATTERN = /^(feat|fix|perf|refactor|docs|test|chore|ci|build)(\([^)]+\))?!?:\s+\S/;
@@ -2213,68 +2686,363 @@ function inferConventionalType(commitSummaries) {
2213
2686
 
2214
2687
  // pr/pr-description-context.ts
2215
2688
  init_common();
2216
- import { join as join7 } from "path";
2689
+ import { join as join9 } from "path";
2217
2690
  import { readFile } from "fs/promises";
2218
- async function collectFinalizerContext(options) {
2219
- const { targetBase, branchName, worktreePath, reviewArtifactPath, logger } = options;
2220
- const commits = await collectCommitRange(
2221
- targetBase,
2222
- branchName,
2223
- worktreePath,
2224
- logger
2225
- );
2226
- const reviewArtifact = reviewArtifactPath ? await readReviewArtifact(reviewArtifactPath) : "(no review artifact provided)";
2227
- return {
2228
- commits,
2229
- reviewArtifact,
2230
- targetBase,
2231
- branchName
2232
- };
2233
- }
2234
- async function collectCommitRange(targetBase, branchName, worktreePath, logger) {
2235
- const result = await execCapture(
2236
- "git",
2237
- [
2238
- "log",
2239
- `${remoteTargetBase(targetBase)}..${branchName}`,
2240
- "--oneline",
2241
- "--no-decorate"
2242
- ],
2243
- { cwd: worktreePath, logger, label: "git log" }
2244
- );
2245
- const commits = result.stdout.trim();
2246
- if (!commits) {
2247
- logger.step(
2248
- "warn",
2249
- `No commits found between ${targetBase} and ${branchName}, proceeding with empty commit range`
2250
- );
2691
+ import { Effect as Effect4 } from "effect";
2692
+
2693
+ // shared/effect-services.ts
2694
+ import { Context, Effect as Effect3, Layer } from "effect";
2695
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3, rmSync } from "fs";
2696
+ var GitExecutionError = class extends Error {
2697
+ _tag = "GitExecutionError";
2698
+ message;
2699
+ constructor(args) {
2700
+ super(args.message);
2701
+ this.name = "GitExecutionError";
2702
+ this.message = args.message;
2251
2703
  }
2252
- return commits;
2704
+ };
2705
+ var FileSystem = class extends Context.Tag("FileSystem")() {
2706
+ };
2707
+ var FileSystemDefault = Layer.succeed(
2708
+ FileSystem,
2709
+ FileSystem.of({
2710
+ readFile: (path9) => Effect3.try({
2711
+ try: () => readFileSync6(path9, "utf-8"),
2712
+ catch: (error) => new Error(
2713
+ `Failed to read file ${path9}: ${error instanceof Error ? error.message : String(error)}`
2714
+ )
2715
+ }),
2716
+ writeFile: (path9, content) => Effect3.try({
2717
+ try: () => writeFileSync3(path9, content, "utf-8"),
2718
+ catch: (error) => new Error(
2719
+ `Failed to write file ${path9}: ${error instanceof Error ? error.message : String(error)}`
2720
+ )
2721
+ }),
2722
+ exists: (path9) => Effect3.try({
2723
+ try: () => existsSync6(path9),
2724
+ catch: (error) => new Error(
2725
+ `Failed to check existence of ${path9}: ${error instanceof Error ? error.message : String(error)}`
2726
+ )
2727
+ }),
2728
+ mkdir: (path9) => Effect3.try({
2729
+ try: () => mkdirSync5(path9, { recursive: true }),
2730
+ catch: (error) => new Error(
2731
+ `Failed to create directory ${path9}: ${error instanceof Error ? error.message : String(error)}`
2732
+ )
2733
+ }),
2734
+ remove: (path9) => Effect3.try({
2735
+ try: () => rmSync(path9, { recursive: true, force: true }),
2736
+ catch: (error) => new Error(
2737
+ `Failed to remove ${path9}: ${error instanceof Error ? error.message : String(error)}`
2738
+ )
2739
+ })
2740
+ })
2741
+ );
2742
+ var FileSystemTest = Layer.succeed(
2743
+ FileSystem,
2744
+ FileSystem.of({
2745
+ readFile: (path9) => Effect3.succeed(`test content for ${path9}`),
2746
+ writeFile: (_path, _content) => Effect3.void,
2747
+ exists: (_path) => Effect3.succeed(true),
2748
+ mkdir: (_path) => Effect3.void,
2749
+ remove: (_path) => Effect3.void
2750
+ })
2751
+ );
2752
+ var GitOperations = class extends Context.Tag("GitOperations")() {
2753
+ };
2754
+ var GitOperationsDefault = Layer.succeed(
2755
+ GitOperations,
2756
+ GitOperations.of({
2757
+ exec: (args, options) => Effect3.tryPromise({
2758
+ try: async () => {
2759
+ const { execCapture: execCapture2 } = await Promise.resolve().then(() => (init_common(), common_exports));
2760
+ const result = await execCapture2("git", args, {
2761
+ cwd: options?.cwd
2762
+ });
2763
+ return { stdout: result.stdout, stderr: result.stderr };
2764
+ },
2765
+ catch: (error) => new GitExecutionError({
2766
+ message: error instanceof Error ? error.message : `git exec failed: ${String(error)}`
2767
+ })
2768
+ }),
2769
+ revParse: (ref, cwd) => Effect3.tryPromise({
2770
+ try: async () => {
2771
+ const { execCapture: execCapture2 } = await Promise.resolve().then(() => (init_common(), common_exports));
2772
+ const result = await execCapture2("git", ["rev-parse", ref], {
2773
+ cwd
2774
+ });
2775
+ return result.stdout.trim();
2776
+ },
2777
+ catch: (error) => new GitExecutionError({
2778
+ message: error instanceof Error ? error.message : `git rev-parse failed: ${String(error)}`
2779
+ })
2780
+ }),
2781
+ fetch: (remote, branch, cwd) => Effect3.tryPromise({
2782
+ try: async () => {
2783
+ const { execCapture: execCapture2 } = await Promise.resolve().then(() => (init_common(), common_exports));
2784
+ await execCapture2("git", ["fetch", remote, branch], { cwd });
2785
+ },
2786
+ catch: (error) => new GitExecutionError({
2787
+ message: error instanceof Error ? error.message : `git fetch failed: ${String(error)}`
2788
+ })
2789
+ }),
2790
+ mergeBaseIsAncestor: (baseRef, cwd) => Effect3.tryPromise({
2791
+ try: async () => {
2792
+ const { execCapture: execCapture2 } = await Promise.resolve().then(() => (init_common(), common_exports));
2793
+ await execCapture2(
2794
+ "git",
2795
+ ["merge-base", "--is-ancestor", baseRef, "HEAD"],
2796
+ { cwd }
2797
+ );
2798
+ return true;
2799
+ },
2800
+ catch: (error) => new GitExecutionError({
2801
+ message: error instanceof Error ? error.message : `git merge-base failed: ${String(error)}`
2802
+ })
2803
+ }).pipe(
2804
+ Effect3.catchIf(
2805
+ (error) => error instanceof GitExecutionError && error.message.includes("merge-base --is-ancestor") && error.message.includes("exit code: 1"),
2806
+ () => Effect3.succeed(false)
2807
+ )
2808
+ )
2809
+ })
2810
+ );
2811
+ var GitOperationsTest = Layer.succeed(
2812
+ GitOperations,
2813
+ GitOperations.of({
2814
+ exec: (_args, _options) => Effect3.succeed({ stdout: "", stderr: "" }),
2815
+ revParse: (_ref, _cwd) => Effect3.succeed("abc123def456"),
2816
+ fetch: (_remote, _branch, _cwd) => Effect3.void,
2817
+ mergeBaseIsAncestor: (_baseRef, _cwd) => Effect3.succeed(true)
2818
+ })
2819
+ );
2820
+ var GitHubApiClient = class extends Context.Tag("GitHubApiClient")() {
2821
+ };
2822
+ var GitHubApiClientDefault = Layer.succeed(
2823
+ GitHubApiClient,
2824
+ GitHubApiClient.of({
2825
+ getIssue: (owner, repo, issueNumber) => Effect3.tryPromise({
2826
+ try: async () => {
2827
+ const { requireGitHubClient: requireGitHubClient2 } = await Promise.resolve().then(() => (init_github_client(), github_client_exports));
2828
+ const client = await requireGitHubClient2();
2829
+ return await client.octokit.rest.issues.get({
2830
+ owner,
2831
+ repo,
2832
+ issue_number: issueNumber
2833
+ });
2834
+ },
2835
+ catch: (error) => new Error(
2836
+ error instanceof Error ? error.message : `GitHub API getIssue failed: ${String(error)}`
2837
+ )
2838
+ }),
2839
+ createIssueComment: (owner, repo, issueNumber, body) => Effect3.tryPromise({
2840
+ try: async () => {
2841
+ const { requireGitHubClient: requireGitHubClient2 } = await Promise.resolve().then(() => (init_github_client(), github_client_exports));
2842
+ const client = await requireGitHubClient2();
2843
+ return await client.octokit.rest.issues.createComment({
2844
+ owner,
2845
+ repo,
2846
+ issue_number: issueNumber,
2847
+ body
2848
+ });
2849
+ },
2850
+ catch: (error) => new Error(
2851
+ error instanceof Error ? error.message : `GitHub API createIssueComment failed: ${String(error)}`
2852
+ )
2853
+ }),
2854
+ updateIssue: (owner, repo, issueNumber, update) => Effect3.tryPromise({
2855
+ try: async () => {
2856
+ const { requireGitHubClient: requireGitHubClient2 } = await Promise.resolve().then(() => (init_github_client(), github_client_exports));
2857
+ const client = await requireGitHubClient2();
2858
+ return await client.octokit.rest.issues.update({
2859
+ owner,
2860
+ repo,
2861
+ issue_number: issueNumber,
2862
+ ...update
2863
+ });
2864
+ },
2865
+ catch: (error) => new Error(
2866
+ error instanceof Error ? error.message : `GitHub API updateIssue failed: ${String(error)}`
2867
+ )
2868
+ })
2869
+ })
2870
+ );
2871
+ var GitHubApiClientTest = Layer.succeed(
2872
+ GitHubApiClient,
2873
+ GitHubApiClient.of({
2874
+ getIssue: (_owner, _repo, _issueNumber) => Effect3.succeed({ data: { title: "Test Issue", state: "open" } }),
2875
+ createIssueComment: (_owner, _repo, _issueNumber, _body) => Effect3.succeed({ data: { id: 1 } }),
2876
+ updateIssue: (_owner, _repo, _issueNumber, _update) => Effect3.succeed({ data: { number: _issueNumber } })
2877
+ })
2878
+ );
2879
+ var ExecutionProvider = class extends Context.Tag("ExecutionProvider")() {
2880
+ };
2881
+ var ExecutionProviderDefault = Layer.succeed(
2882
+ ExecutionProvider,
2883
+ ExecutionProvider.of({
2884
+ execute: (options) => Effect3.tryPromise({
2885
+ try: async () => {
2886
+ const { SandcastleExecutionProvider: SandcastleExecutionProvider2 } = await Promise.resolve().then(() => (init_sandcastle_execution(), sandcastle_execution_exports));
2887
+ const provider = new SandcastleExecutionProvider2();
2888
+ return await provider.execute(options);
2889
+ },
2890
+ catch: (error) => new Error(
2891
+ error instanceof Error ? error.message : `Execution failed: ${String(error)}`
2892
+ )
2893
+ })
2894
+ })
2895
+ );
2896
+ var ExecutionProviderTest = Layer.succeed(
2897
+ ExecutionProvider,
2898
+ ExecutionProvider.of({
2899
+ execute: (_options) => Effect3.succeed({
2900
+ success: true,
2901
+ branch: "test-branch",
2902
+ worktreePath: _options.worktreePath ?? "/tmp/worktree",
2903
+ commits: ["abc123"],
2904
+ logPath: "/tmp/test-log"
2905
+ })
2906
+ })
2907
+ );
2908
+ var AttemptLogService = class extends Context.Tag("AttemptLogService")() {
2909
+ };
2910
+ var AttemptLogServiceDefault = Layer.succeed(
2911
+ AttemptLogService,
2912
+ AttemptLogService.of({
2913
+ writeEntry: (worktreePath, entry) => Effect3.try({
2914
+ try: () => writeAttemptLog(worktreePath, entry),
2915
+ catch: (error) => new Error(
2916
+ `Failed to write attempt log: ${error instanceof Error ? error.message : String(error)}`
2917
+ )
2918
+ }),
2919
+ readEntries: (worktreePath) => Effect3.try({
2920
+ try: () => readAttemptLog(worktreePath),
2921
+ catch: (error) => new Error(
2922
+ `Failed to read attempt log: ${error instanceof Error ? error.message : String(error)}`
2923
+ )
2924
+ })
2925
+ })
2926
+ );
2927
+ var AttemptLogServiceTest = Layer.succeed(
2928
+ AttemptLogService,
2929
+ AttemptLogService.of({
2930
+ writeEntry: (_worktreePath, _entry) => Effect3.void,
2931
+ readEntries: (_worktreePath) => Effect3.succeed([])
2932
+ })
2933
+ );
2934
+ var WorktreeRunStateService = class extends Context.Tag(
2935
+ "WorktreeRunStateService"
2936
+ )() {
2937
+ };
2938
+ var WorktreeRunStateServiceDefault = Layer.succeed(
2939
+ WorktreeRunStateService,
2940
+ WorktreeRunStateService.of({
2941
+ read: (worktreePath) => Effect3.try({
2942
+ try: () => readWorktreeRunState(worktreePath),
2943
+ catch: (error) => new Error(
2944
+ `Failed to read worktree run state: ${error instanceof Error ? error.message : String(error)}`
2945
+ )
2946
+ }),
2947
+ write: (worktreePath, state) => Effect3.try({
2948
+ try: () => writeWorktreeRunState(worktreePath, state),
2949
+ catch: (error) => new Error(
2950
+ `Failed to write worktree run state: ${error instanceof Error ? error.message : String(error)}`
2951
+ )
2952
+ }),
2953
+ update: (worktreePath, update) => Effect3.try({
2954
+ try: () => updateWorktreeRunState(worktreePath, update),
2955
+ catch: (error) => new Error(
2956
+ `Failed to update worktree run state: ${error instanceof Error ? error.message : String(error)}`
2957
+ )
2958
+ })
2959
+ })
2960
+ );
2961
+ var WorktreeRunStateServiceTestTimestamp = "2026-01-01T00:00:00.000Z";
2962
+ var WorktreeRunStateServiceTest = Layer.succeed(
2963
+ WorktreeRunStateService,
2964
+ WorktreeRunStateService.of({
2965
+ read: (_worktreePath) => Effect3.succeed({
2966
+ issueNumber: 0,
2967
+ targetName: "test",
2968
+ branchName: "test-branch",
2969
+ baseBranch: "main",
2970
+ createdAt: WorktreeRunStateServiceTestTimestamp,
2971
+ updatedAt: WorktreeRunStateServiceTestTimestamp,
2972
+ completedStages: {},
2973
+ review: {
2974
+ lifetimeIterations: 0
2975
+ }
2976
+ }),
2977
+ write: (_worktreePath, _state) => Effect3.void,
2978
+ update: (_worktreePath, _update) => Effect3.void
2979
+ })
2980
+ );
2981
+
2982
+ // pr/pr-description-context.ts
2983
+ function collectFinalizerContextEffect(options) {
2984
+ return Effect4.gen(function* () {
2985
+ const git = yield* GitOperations;
2986
+ const fs = yield* FileSystem;
2987
+ const remoteBase = remoteTargetBase(options.targetBase);
2988
+ const result = yield* git.exec(
2989
+ [
2990
+ "log",
2991
+ `${remoteBase}..${options.branchName}`,
2992
+ "--oneline",
2993
+ "--no-decorate"
2994
+ ],
2995
+ { cwd: options.worktreePath }
2996
+ ).pipe(
2997
+ Effect4.catchAll(
2998
+ (error) => Effect4.fail(
2999
+ new Error(`Failed to collect commit range: ${error.message}`)
3000
+ )
3001
+ )
3002
+ );
3003
+ const commits = result.stdout.trim();
3004
+ if (!commits) {
3005
+ options.logger.step(
3006
+ "warn",
3007
+ `No commits found between ${options.targetBase} and ${options.branchName}, proceeding with empty commit range`
3008
+ );
3009
+ }
3010
+ let reviewArtifact;
3011
+ if (options.reviewArtifactPath) {
3012
+ const content = yield* fs.readFile(options.reviewArtifactPath).pipe(
3013
+ Effect4.catchIf(
3014
+ (error) => error.message.includes("ENOENT") || error.message.includes("no such file"),
3015
+ () => Effect4.fail(
3016
+ new Error(
3017
+ `Review artifact not found at ${options.reviewArtifactPath}. Ensure the review stage completed before running finalizer generation.`
3018
+ )
3019
+ )
3020
+ )
3021
+ );
3022
+ if (!content.trim()) {
3023
+ return yield* Effect4.fail(
3024
+ new Error(
3025
+ `Review artifact at ${options.reviewArtifactPath} is empty. Ensure the review stage produced output before running finalizer generation.`
3026
+ )
3027
+ );
3028
+ }
3029
+ reviewArtifact = content;
3030
+ } else {
3031
+ reviewArtifact = "(no review artifact provided)";
3032
+ }
3033
+ return {
3034
+ commits,
3035
+ reviewArtifact,
3036
+ targetBase: options.targetBase,
3037
+ branchName: options.branchName
3038
+ };
3039
+ });
2253
3040
  }
2254
3041
  function remoteTargetBase(targetBase) {
2255
3042
  return targetBase.includes("/") ? targetBase : `origin/${targetBase}`;
2256
3043
  }
2257
- async function readReviewArtifact(artifactPath) {
2258
- let content;
2259
- try {
2260
- content = await readFile(artifactPath, "utf-8");
2261
- } catch (error) {
2262
- if (error.code === "ENOENT") {
2263
- throw new Error(
2264
- `Review artifact not found at ${artifactPath}. Ensure the review stage completed before running finalizer generation.`
2265
- );
2266
- }
2267
- throw error;
2268
- }
2269
- if (!content.trim()) {
2270
- throw new Error(
2271
- `Review artifact at ${artifactPath} is empty. Ensure the review stage produced output before running finalizer generation.`
2272
- );
2273
- }
2274
- return content;
2275
- }
2276
3044
  function buildFinalizerPrompt(context, promptTemplate) {
2277
- const artifactPathInWorktree = join7(
3045
+ const artifactPathInWorktree = join9(
2278
3046
  ".pourkit",
2279
3047
  ".tmp",
2280
3048
  "finalizer",
@@ -2326,133 +3094,182 @@ Write your finalizer output to: ${artifactPathInWorktree}`;
2326
3094
  }
2327
3095
 
2328
3096
  // commands/pr-description-agent.ts
2329
- async function runFinalizerAgent(options) {
2330
- const {
2331
- executionProvider,
2332
- config,
2333
- target,
2334
- issue,
2335
- builderBranch,
2336
- worktreePath,
2337
- reviewArtifactPath,
2338
- repoRoot: repoRoot2,
2339
- logger
2340
- } = options;
2341
- const strategy = target.strategy;
2342
- const finalizer = strategy.finalize.prDescriptionAgent;
2343
- const context = await collectFinalizerContext({
2344
- targetBase: target.baseBranch,
2345
- branchName: builderBranch,
2346
- worktreePath,
2347
- reviewArtifactPath,
2348
- logger
2349
- });
2350
- const resolvedPrompt = loadFinalizerPrompt(
2351
- repoRoot2,
2352
- finalizer.promptTemplate
3097
+ import { Effect as Effect5, Layer as Layer2 } from "effect";
3098
+ function bridgeExecutionProvider(ep) {
3099
+ return Layer2.succeed(
3100
+ ExecutionProvider,
3101
+ ExecutionProvider.of({
3102
+ execute: (opts) => Effect5.tryPromise({
3103
+ try: () => ep.execute(opts),
3104
+ catch: (e) => new Error(
3105
+ e instanceof Error ? e.message : `Execution failed: ${String(e)}`
3106
+ )
3107
+ })
3108
+ })
2353
3109
  );
2354
- const prompt = buildFinalizerPrompt(context, resolvedPrompt);
2355
- const artifactPathInWorktree = join8(
3110
+ }
3111
+ function runFinalizerAgent(options) {
3112
+ const { executionProvider } = options;
3113
+ const artifactPathInWorktree = join10(
2356
3114
  ".pourkit",
2357
3115
  ".tmp",
2358
3116
  "finalizer",
2359
3117
  "agent-output.md"
2360
3118
  );
2361
- const artifactPath = join8(worktreePath, artifactPathInWorktree);
2362
- prepareArtifactPath(artifactPath);
2363
- let output = "";
2364
- let parsed;
2365
- let lastValidationError;
2366
- for (let attempt = 1; attempt <= strategy.finalize.maxAttempts; attempt++) {
2367
- logger.step(
2368
- "info",
2369
- `Running finalizer agent (${attempt}/${strategy.finalize.maxAttempts})`
3119
+ const artifactPath = join10(options.worktreePath, artifactPathInWorktree);
3120
+ const program = Effect5.gen(function* () {
3121
+ const exec = yield* ExecutionProvider;
3122
+ const fs = yield* FileSystem;
3123
+ const context = yield* collectFinalizerContextEffect({
3124
+ targetBase: options.target.baseBranch,
3125
+ branchName: options.builderBranch,
3126
+ worktreePath: options.worktreePath,
3127
+ reviewArtifactPath: options.reviewArtifactPath,
3128
+ logger: options.logger
3129
+ }).pipe(
3130
+ Effect5.catchAll(
3131
+ (error) => Effect5.fail(
3132
+ new FinalizerFailure({
3133
+ message: `Failed to collect finalizer context: ${error.message}`
3134
+ })
3135
+ )
3136
+ )
2370
3137
  );
2371
- const executionResult = await executionProvider.execute({
2372
- stage: "finalizer",
2373
- agent: finalizer.agent,
2374
- model: finalizer.model,
2375
- prompt,
2376
- target,
2377
- repoRoot: repoRoot2,
2378
- branchName: builderBranch,
2379
- sandbox: config.sandbox,
2380
- autoApprove: true,
2381
- artifactPath: artifactPathInWorktree,
2382
- worktreePath,
2383
- artifacts: [
2384
- buildRunContextArtifact({
2385
- issue,
2386
- target,
2387
- branchName: builderBranch,
2388
- reviewerCriteria: strategy.review.reviewer.criteria,
2389
- sections: STAGE_SECTIONS.finalizer
2390
- })
2391
- ],
2392
- logger
2393
- });
2394
- if (!executionResult.success) {
2395
- throw new Error(
2396
- `Finalizer agent execution failed: ${executionResult.error}`
3138
+ const strategy = options.target.strategy;
3139
+ const finalizer = strategy.finalize.prDescriptionAgent;
3140
+ const resolvedPrompt = yield* loadFinalizerPromptEffect(
3141
+ options.repoRoot,
3142
+ finalizer.promptTemplate,
3143
+ fs
3144
+ );
3145
+ const prompt = buildFinalizerPrompt(context, resolvedPrompt);
3146
+ yield* prepareArtifactPathEffect(artifactPath, fs);
3147
+ let output = "";
3148
+ let parsed;
3149
+ let lastValidationError;
3150
+ for (let attempt = 1; attempt <= strategy.finalize.maxAttempts; attempt++) {
3151
+ options.logger.step(
3152
+ "info",
3153
+ `Running finalizer agent (${attempt}/${strategy.finalize.maxAttempts})`
2397
3154
  );
2398
- }
2399
- try {
2400
- output = readAgentOutput(artifactPath);
2401
- parsed = parsePrDescription(output);
2402
- lastValidationError = void 0;
2403
- break;
2404
- } catch (error) {
2405
- lastValidationError = error instanceof PrDescriptionProtocolError ? new Error(`Finalizer protocol error: ${error.message}`) : error instanceof Error ? error : new Error(String(error));
2406
- if (attempt === strategy.finalize.maxAttempts) {
2407
- break;
3155
+ const executionResult = yield* exec.execute({
3156
+ stage: "finalizer",
3157
+ agent: finalizer.agent,
3158
+ model: finalizer.model,
3159
+ prompt,
3160
+ target: options.target,
3161
+ repoRoot: options.repoRoot,
3162
+ branchName: options.builderBranch,
3163
+ sandbox: options.config.sandbox,
3164
+ autoApprove: true,
3165
+ artifactPath: artifactPathInWorktree,
3166
+ worktreePath: options.worktreePath,
3167
+ artifacts: [
3168
+ buildRunContextArtifact({
3169
+ issue: options.issue,
3170
+ target: options.target,
3171
+ branchName: options.builderBranch,
3172
+ reviewerCriteria: strategy.review.reviewer.criteria,
3173
+ sections: STAGE_SECTIONS.finalizer
3174
+ })
3175
+ ],
3176
+ logger: options.logger
3177
+ });
3178
+ if (!executionResult.success) {
3179
+ return yield* Effect5.fail(
3180
+ new FinalizerFailure({
3181
+ message: `Finalizer agent execution failed: ${executionResult.error}`
3182
+ })
3183
+ );
3184
+ }
3185
+ const readResult = yield* readAgentOutputEffect(artifactPath, fs);
3186
+ if (readResult._tag === "content") {
3187
+ output = readResult.value;
3188
+ try {
3189
+ parsed = parsePrDescription(output);
3190
+ lastValidationError = void 0;
3191
+ break;
3192
+ } catch (error) {
3193
+ lastValidationError = error instanceof PrDescriptionProtocolError ? new Error(`Finalizer protocol error: ${error.message}`) : error instanceof Error ? error : new Error(String(error));
3194
+ if (attempt === strategy.finalize.maxAttempts) {
3195
+ break;
3196
+ }
3197
+ yield* prepareArtifactPathEffect(artifactPath, fs);
3198
+ }
3199
+ } else if (readResult._tag === "empty") {
3200
+ lastValidationError = new Error(
3201
+ `Finalizer agent produced empty output at ${artifactPath}`
3202
+ );
3203
+ if (attempt === strategy.finalize.maxAttempts) {
3204
+ break;
3205
+ }
3206
+ yield* prepareArtifactPathEffect(artifactPath, fs);
3207
+ } else {
3208
+ lastValidationError = new Error(
3209
+ `Finalizer agent did not produce output at ${artifactPath}`
3210
+ );
3211
+ if (attempt === strategy.finalize.maxAttempts) {
3212
+ break;
3213
+ }
3214
+ yield* prepareArtifactPathEffect(artifactPath, fs);
2408
3215
  }
2409
- prepareArtifactPath(artifactPath);
2410
3216
  }
2411
- }
2412
- if (!parsed) {
2413
- throw lastValidationError ?? new Error("Finalizer validation failed");
2414
- }
2415
- await persistGeneratedArtifact(worktreePath, output);
2416
- const title = ensureConventionalPrTitle(parsed.title, context.commits);
2417
- logger.step("info", "Finalizer output generated successfully");
2418
- return {
2419
- title,
2420
- body: parsed.body,
2421
- artifactPath
2422
- };
3217
+ if (!parsed) {
3218
+ return yield* Effect5.fail(
3219
+ new FinalizerFailure({
3220
+ message: lastValidationError?.message ?? "Finalizer validation failed"
3221
+ })
3222
+ );
3223
+ }
3224
+ yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
3225
+ options.logger.step("info", "Finalizer output generated successfully");
3226
+ return {
3227
+ title: ensureConventionalPrTitle(parsed.title, context.commits),
3228
+ body: parsed.body,
3229
+ artifactPath
3230
+ };
3231
+ });
3232
+ const executionLayer = bridgeExecutionProvider(executionProvider);
3233
+ return program.pipe(
3234
+ Effect5.provide(
3235
+ Layer2.mergeAll(executionLayer, FileSystemDefault, GitOperationsDefault)
3236
+ )
3237
+ );
2423
3238
  }
2424
- function loadFinalizerPrompt(repoRoot2, promptTemplate) {
2425
- const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
2426
- if (existsSync5(promptPath)) {
2427
- return readFileSync5(promptPath, "utf-8");
2428
- }
2429
- return promptTemplate;
3239
+ function loadFinalizerPromptEffect(repoRoot2, promptTemplate, fs) {
3240
+ return Effect5.gen(function* () {
3241
+ const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
3242
+ const exists = yield* fs.exists(promptPath).pipe(Effect5.orDie);
3243
+ if (exists) {
3244
+ return yield* fs.readFile(promptPath).pipe(Effect5.orDie);
3245
+ }
3246
+ return promptTemplate;
3247
+ });
2430
3248
  }
2431
- function prepareArtifactPath(artifactPath) {
2432
- mkdirSync4(dirname3(artifactPath), { recursive: true });
2433
- if (existsSync5(artifactPath)) {
2434
- rmSync(artifactPath);
2435
- }
3249
+ function prepareArtifactPathEffect(artifactPath, fs) {
3250
+ return Effect5.gen(function* () {
3251
+ yield* fs.mkdir(dirname4(artifactPath)).pipe(Effect5.orDie);
3252
+ const exists = yield* fs.exists(artifactPath).pipe(Effect5.orDie);
3253
+ if (exists) {
3254
+ yield* fs.remove(artifactPath).pipe(Effect5.orDie);
3255
+ }
3256
+ });
2436
3257
  }
2437
- function readAgentOutput(artifactPath) {
2438
- if (!existsSync5(artifactPath)) {
2439
- throw new Error(
2440
- `Finalizer agent did not produce output at ${artifactPath}`
2441
- );
2442
- }
2443
- const output = readFileSync5(artifactPath, "utf-8");
2444
- if (!output.trim()) {
2445
- throw new Error(`Finalizer agent produced empty output at ${artifactPath}`);
2446
- }
2447
- return output;
3258
+ function readAgentOutputEffect(artifactPath, fs) {
3259
+ return Effect5.gen(function* () {
3260
+ const exists = yield* fs.exists(artifactPath).pipe(Effect5.orDie);
3261
+ if (!exists) return { _tag: "missing" };
3262
+ const output = yield* fs.readFile(artifactPath).pipe(Effect5.catchAll(() => Effect5.succeed("")));
3263
+ if (!output.trim()) return { _tag: "empty" };
3264
+ return { _tag: "content", value: output };
3265
+ });
2448
3266
  }
2449
- async function persistGeneratedArtifact(worktreePath, output) {
2450
- try {
2451
- const dir = join8(worktreePath, ".pourkit", ".tmp", "finalizer");
2452
- mkdirSync4(dir, { recursive: true });
2453
- writeFileSync2(join8(dir, "generated.md"), output, "utf-8");
2454
- } catch {
2455
- }
3267
+ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
3268
+ return Effect5.gen(function* () {
3269
+ const dir = join10(worktreePath, ".pourkit", ".tmp", "finalizer");
3270
+ yield* fs.mkdir(dir).pipe(Effect5.catchAll(() => Effect5.void));
3271
+ yield* fs.writeFile(join10(dir, "generated.md"), output).pipe(Effect5.catchAll(() => Effect5.void));
3272
+ });
2456
3273
  }
2457
3274
 
2458
3275
  // pr/pr-body.ts
@@ -2635,7 +3452,15 @@ function secondsRemaining(deadline, observedAt) {
2635
3452
  }
2636
3453
 
2637
3454
  // issues/merge-coordinator.ts
2638
- async function runMergeCoordinator(options) {
3455
+ import { Effect as Effect6, Either } from "effect";
3456
+ import { UnknownException as UnknownException2 } from "effect/Cause";
3457
+ function unwrapError2(error) {
3458
+ if (error instanceof UnknownException2 && error.error !== void 0) {
3459
+ return error.error;
3460
+ }
3461
+ return error;
3462
+ }
3463
+ function runMergeCoordinator(options) {
2639
3464
  const {
2640
3465
  prProvider,
2641
3466
  logger,
@@ -2646,56 +3471,70 @@ async function runMergeCoordinator(options) {
2646
3471
  } = options;
2647
3472
  const method = options.method ?? "squash";
2648
3473
  const waitForTargetGreen = options.waitForTargetGreen ?? true;
2649
- try {
2650
- await prProvider.waitForPrChecks(prNumber, checkWaitOptions);
2651
- } catch (error) {
2652
- return {
2653
- stage: "merge",
2654
- merged: false,
2655
- error: error instanceof Error ? error : new Error(String(error))
2656
- };
2657
- }
2658
- try {
2659
- await prProvider.mergePr(prNumber, {
2660
- method,
2661
- matchHeadCommit
2662
- });
2663
- } catch (error) {
2664
- return {
2665
- stage: "merge",
2666
- merged: false,
2667
- error: error instanceof Error ? error : new Error(String(error))
2668
- };
2669
- }
2670
- if (waitForTargetGreen) {
2671
- try {
2672
- await waitForBranchChecks(prProvider, logger, {
2673
- branchName: targetBranch,
2674
- checksFoundTimeoutMs: checkWaitOptions.checksFoundTimeoutMs,
2675
- checksCompletionTimeoutMs: checkWaitOptions.checksCompletionTimeoutMs,
2676
- pollIntervalMs: checkWaitOptions.pollIntervalMs
2677
- });
2678
- } catch (error) {
3474
+ return Effect6.gen(function* () {
3475
+ const checksEither = yield* Effect6.either(
3476
+ Effect6.tryPromise(
3477
+ () => prProvider.waitForPrChecks(prNumber, checkWaitOptions)
3478
+ )
3479
+ );
3480
+ if (Either.isLeft(checksEither)) {
3481
+ const rawError = checksEither.left;
3482
+ const error = unwrapError2(rawError);
2679
3483
  return {
2680
- stage: "target-green",
2681
- merged: true,
3484
+ stage: "merge",
3485
+ merged: false,
2682
3486
  error: error instanceof Error ? error : new Error(String(error))
2683
3487
  };
2684
3488
  }
2685
- }
2686
- return { stage: "completed", merged: true };
3489
+ const mergeEither = yield* Effect6.either(
3490
+ Effect6.tryPromise(
3491
+ () => prProvider.mergePr(prNumber, { method, matchHeadCommit })
3492
+ )
3493
+ );
3494
+ if (Either.isLeft(mergeEither)) {
3495
+ const rawError = mergeEither.left;
3496
+ const error = unwrapError2(rawError);
3497
+ return {
3498
+ stage: "merge",
3499
+ merged: false,
3500
+ error: error instanceof Error ? error : new Error(String(error))
3501
+ };
3502
+ }
3503
+ if (waitForTargetGreen) {
3504
+ const targetEither = yield* Effect6.either(
3505
+ Effect6.tryPromise(
3506
+ () => waitForBranchChecks(prProvider, logger, {
3507
+ branchName: targetBranch,
3508
+ checksFoundTimeoutMs: checkWaitOptions.checksFoundTimeoutMs,
3509
+ checksCompletionTimeoutMs: checkWaitOptions.checksCompletionTimeoutMs,
3510
+ pollIntervalMs: checkWaitOptions.pollIntervalMs
3511
+ })
3512
+ )
3513
+ );
3514
+ if (Either.isLeft(targetEither)) {
3515
+ const rawError = targetEither.left;
3516
+ const error = unwrapError2(rawError);
3517
+ return {
3518
+ stage: "target-green",
3519
+ merged: true,
3520
+ error: error instanceof Error ? error : new Error(String(error))
3521
+ };
3522
+ }
3523
+ }
3524
+ return { stage: "completed", merged: true };
3525
+ });
2687
3526
  }
2688
3527
 
2689
3528
  // commands/review.ts
2690
3529
  import {
2691
- existsSync as existsSync6,
2692
- mkdirSync as mkdirSync5,
2693
- readFileSync as readFileSync6,
3530
+ existsSync as existsSync7,
3531
+ mkdirSync as mkdirSync6,
3532
+ readFileSync as readFileSync7,
2694
3533
  readdirSync,
2695
3534
  rmSync as rmSync2,
2696
- writeFileSync as writeFileSync3
3535
+ writeFileSync as writeFileSync4
2697
3536
  } from "fs";
2698
- import { dirname as dirname4, join as join9 } from "path";
3537
+ import { dirname as dirname5, join as join11 } from "path";
2699
3538
 
2700
3539
  // pr/review-verdict.ts
2701
3540
  var ReviewVerdictProtocolError = class extends Error {
@@ -2727,6 +3566,7 @@ function parseReviewVerdict(output) {
2727
3566
  }
2728
3567
 
2729
3568
  // commands/review.ts
3569
+ import { Effect as Effect7, Layer as Layer3 } from "effect";
2730
3570
  var ReviewArtifactValidationError = class extends Error {
2731
3571
  constructor(message) {
2732
3572
  super(message);
@@ -2777,12 +3617,12 @@ function extractLatestFindingIds(reviewOutput, iteration) {
2777
3617
  return ids;
2778
3618
  }
2779
3619
  function validateRefactorArtifact(artifactPath, findingIds) {
2780
- if (!existsSync6(artifactPath)) {
3620
+ if (!existsSync7(artifactPath)) {
2781
3621
  throw new RefactorArtifactValidationError(
2782
3622
  `Refactor artifact missing at ${artifactPath}`
2783
3623
  );
2784
3624
  }
2785
- const content = readFileSync6(artifactPath, "utf-8");
3625
+ const content = readFileSync7(artifactPath, "utf-8");
2786
3626
  if (!content.trim()) {
2787
3627
  throw new RefactorArtifactValidationError("Refactor artifact is empty");
2788
3628
  }
@@ -2881,7 +3721,7 @@ function validateReviewArtifact(output, verdict, iteration, priorRefactorArtifac
2881
3721
  );
2882
3722
  }
2883
3723
  const supersedesCell = cells[1];
2884
- if (supersedesCell !== "-" && supersedesCell !== "n/a" && !supersedesIdRegex.test(supersedesCell)) {
3724
+ if (supersedesCell !== "-" && !supersedesIdRegex.test(supersedesCell)) {
2885
3725
  throw new ReviewArtifactValidationError(
2886
3726
  `Supersedes must be a hyphen for new findings or a valid finding ID, got: ${supersedesCell}`
2887
3727
  );
@@ -2900,16 +3740,25 @@ function validateReviewArtifact(output, verdict, iteration, priorRefactorArtifac
2900
3740
  }
2901
3741
  }
2902
3742
  if (priorRefactorArtifactsProvided) {
2903
- if (!output.includes("## Prior Refactor Response Assessment")) {
3743
+ const verdictIndex = output.indexOf("<verdict>");
3744
+ const assessmentIndex = output.indexOf(
3745
+ "## Prior Refactor Response Assessment"
3746
+ );
3747
+ if (assessmentIndex === -1) {
2904
3748
  throw new ReviewArtifactValidationError(
2905
3749
  "Prior Refactor Artifacts were provided but the review is missing a ## Prior Refactor Response Assessment section"
2906
3750
  );
2907
3751
  }
3752
+ if (verdictIndex !== -1 && assessmentIndex > verdictIndex) {
3753
+ throw new ReviewArtifactValidationError(
3754
+ "## Prior Refactor Response Assessment must appear before <verdict>"
3755
+ );
3756
+ }
2908
3757
  }
2909
3758
  }
2910
- async function runReviewCommand(options) {
3759
+ function runReviewCommandEffect(options) {
2911
3760
  const {
2912
- executionProvider,
3761
+ executionProvider: _executionProvider,
2913
3762
  config,
2914
3763
  target,
2915
3764
  issue,
@@ -2925,87 +3774,145 @@ async function runReviewCommand(options) {
2925
3774
  } = options;
2926
3775
  const reviewer = target.strategy.review.reviewer;
2927
3776
  if (!reviewer) {
2928
- throw new Error("No reviewer config found");
3777
+ return Effect7.fail(
3778
+ new ReviewerFailure({ message: "No reviewer config found" })
3779
+ );
2929
3780
  }
2930
- const artifactPathInWorktree = join9(
3781
+ const artifactPathInWorktree = join11(
2931
3782
  ".pourkit",
2932
3783
  ".tmp",
2933
3784
  "reviewers",
2934
3785
  `iteration-${iteration ?? 1}.md`
2935
3786
  );
2936
- const artifactPath = join9(worktreePath, artifactPathInWorktree);
2937
- prepareReviewArtifactPath(artifactPath);
2938
- const prompt = buildReviewerPrompt(
2939
- repoRoot2,
2940
- reviewer.promptTemplate,
2941
- reviewer.criteria,
2942
- artifactPathInWorktree,
2943
- iteration ?? 1,
2944
- reviewHistory,
2945
- priorRefactorArtifacts,
2946
- humanHandoffResolved,
2947
- priorReviewerArtifacts
2948
- );
2949
- logger.step("info", "Running reviewer");
2950
- const executionResult = await executionProvider.execute({
2951
- stage: "reviewer",
2952
- iteration,
2953
- agent: reviewer.agent,
2954
- model: reviewer.model,
2955
- prompt,
2956
- target,
2957
- repoRoot: repoRoot2,
2958
- branchName: builderBranch,
2959
- sandbox: config.sandbox,
2960
- autoApprove: true,
2961
- artifactPath: artifactPathInWorktree,
2962
- worktreePath,
2963
- artifacts: [
2964
- buildRunContextArtifact({
2965
- issue,
2966
- target,
2967
- branchName: builderBranch,
2968
- reviewerCriteria: reviewer.criteria,
2969
- sections: STAGE_SECTIONS.reviewer
2970
- })
2971
- ],
2972
- logger
2973
- });
2974
- if (!executionResult.success) {
2975
- throw new Error(`Reviewer execution failed: ${executionResult.error}`);
2976
- }
2977
- const output = readReviewArtifact2(artifactPath, executionResult.logPath);
2978
- let verdict;
2979
- try {
2980
- verdict = parseReviewVerdict(output);
2981
- } catch (error) {
2982
- if (error instanceof ReviewVerdictProtocolError) {
2983
- throw new Error(`Review protocol error: ${error.message}`);
3787
+ const artifactPath = join11(worktreePath, artifactPathInWorktree);
3788
+ return Effect7.gen(function* () {
3789
+ const exec = yield* ExecutionProvider;
3790
+ const fs = yield* FileSystem;
3791
+ const prompt = yield* buildReviewerPromptEffect(
3792
+ repoRoot2,
3793
+ reviewer.promptTemplate,
3794
+ reviewer.criteria,
3795
+ artifactPathInWorktree,
3796
+ iteration ?? 1,
3797
+ fs,
3798
+ reviewHistory,
3799
+ priorRefactorArtifacts,
3800
+ humanHandoffResolved,
3801
+ priorReviewerArtifacts
3802
+ );
3803
+ yield* prepareReviewArtifactPathEffect(artifactPath, fs);
3804
+ logger.step("info", "Running reviewer");
3805
+ const executionResult = yield* exec.execute({
3806
+ stage: "reviewer",
3807
+ iteration,
3808
+ agent: reviewer.agent,
3809
+ model: reviewer.model,
3810
+ prompt,
3811
+ target,
3812
+ repoRoot: repoRoot2,
3813
+ branchName: builderBranch,
3814
+ sandbox: config.sandbox,
3815
+ autoApprove: true,
3816
+ artifactPath: artifactPathInWorktree,
3817
+ worktreePath,
3818
+ artifacts: [
3819
+ buildRunContextArtifact({
3820
+ issue,
3821
+ target,
3822
+ branchName: builderBranch,
3823
+ reviewerCriteria: reviewer.criteria,
3824
+ sections: STAGE_SECTIONS.reviewer
3825
+ })
3826
+ ],
3827
+ logger
3828
+ });
3829
+ if (!executionResult.success) {
3830
+ return yield* Effect7.fail(
3831
+ new ReviewerFailure({
3832
+ message: `Reviewer execution failed: ${executionResult.error}`
3833
+ })
3834
+ );
2984
3835
  }
2985
- throw error;
2986
- }
2987
- logger.step("info", `Review verdict: ${verdict}`);
2988
- validateReviewArtifact(
2989
- output,
2990
- verdict,
2991
- iteration ?? 1,
2992
- !!priorRefactorArtifacts
3836
+ const output = yield* readReviewArtifactEffect(
3837
+ artifactPath,
3838
+ executionResult.logPath,
3839
+ fs
3840
+ );
3841
+ const parsedVerdict = yield* Effect7.try({
3842
+ try: () => parseReviewVerdict(output),
3843
+ catch: (error) => {
3844
+ if (error instanceof ReviewVerdictProtocolError) {
3845
+ return new ReviewerFailure({
3846
+ message: `Review protocol error: ${error.message}`
3847
+ });
3848
+ }
3849
+ throw error;
3850
+ }
3851
+ }).pipe(
3852
+ Effect7.catchAll(
3853
+ (error) => error instanceof ReviewerFailure ? Effect7.fail(error) : Effect7.die(error)
3854
+ )
3855
+ );
3856
+ logger.step("info", `Review verdict: ${parsedVerdict}`);
3857
+ try {
3858
+ validateReviewArtifact(
3859
+ output,
3860
+ parsedVerdict,
3861
+ iteration ?? 1,
3862
+ !!priorRefactorArtifacts
3863
+ );
3864
+ } catch (error) {
3865
+ if (error instanceof ReviewArtifactValidationError) {
3866
+ return yield* Effect7.fail(
3867
+ new ReviewerFailure({ message: error.message })
3868
+ );
3869
+ }
3870
+ yield* Effect7.die(error);
3871
+ }
3872
+ return { verdict: parsedVerdict, output, artifactPath };
3873
+ });
3874
+ }
3875
+ function bridgeExecutionProvider2(ep) {
3876
+ return Layer3.succeed(
3877
+ ExecutionProvider,
3878
+ ExecutionProvider.of({
3879
+ execute: (opts) => Effect7.tryPromise({
3880
+ try: () => ep.execute(opts),
3881
+ catch: (e) => new Error(
3882
+ e instanceof Error ? e.message : `Execution failed: ${String(e)}`
3883
+ )
3884
+ })
3885
+ })
2993
3886
  );
2994
- return { verdict, output, artifactPath };
2995
3887
  }
2996
- function buildReviewerPrompt(repoRoot2, promptTemplate, criteria, artifactPathInWorktree, iteration, reviewHistory = [], priorRefactorArtifacts, humanHandoffResolved, priorReviewerArtifacts) {
2997
- const criteriaBlock = renderReviewCriteria(repoRoot2, criteria);
2998
- const { content: renderedTemplate, hasCriteriaPlaceholder } = loadReviewerPromptTemplate(repoRoot2, promptTemplate, criteriaBlock);
2999
- const priorRefactorBlock = priorRefactorArtifacts ? `${priorRefactorArtifacts}` : "";
3000
- const priorReviewerBlock = priorReviewerArtifacts ? `${priorReviewerArtifacts}` : "";
3001
- const humanHandoffBoundary = humanHandoffResolved ? `## Human-Resolved Handoff Boundary
3888
+ function buildReviewerPromptEffect(repoRoot2, promptTemplate, criteria, artifactPathInWorktree, iteration, fs, reviewHistory = [], priorRefactorArtifacts, humanHandoffResolved, priorReviewerArtifacts) {
3889
+ return Effect7.gen(function* () {
3890
+ const criteriaBlock = yield* renderReviewCriteriaEffect(
3891
+ repoRoot2,
3892
+ criteria,
3893
+ fs
3894
+ );
3895
+ const { content: renderedTemplate, hasCriteriaPlaceholder } = yield* loadReviewerPromptTemplateEffect(
3896
+ repoRoot2,
3897
+ promptTemplate,
3898
+ criteriaBlock,
3899
+ fs
3900
+ );
3901
+ const priorRefactorBlock = priorRefactorArtifacts ? `${priorRefactorArtifacts}` : "";
3902
+ const priorRefactorProtocolReminder = priorRefactorArtifacts ? `## Prior Refactor Protocol Reminder
3903
+
3904
+ 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.
3905
+
3906
+ ` : "";
3907
+ const priorReviewerBlock = priorReviewerArtifacts ? `${priorReviewerArtifacts}` : "";
3908
+ const humanHandoffBoundary = humanHandoffResolved ? `## Human-Resolved Handoff Boundary
3002
3909
 
3003
3910
  A prior review emitted \`NEEDS_HUMAN\` and stopped the agent loop. The issue has since been moved back to \`ready-for-agent\`.
3004
3911
 
3005
3912
  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.
3006
3913
 
3007
3914
  ` : "";
3008
- return `${renderedTemplate}
3915
+ return `${renderedTemplate}
3009
3916
 
3010
3917
  ## Shared Run Context
3011
3918
 
@@ -3015,7 +3922,7 @@ ${hasCriteriaPlaceholder ? "" : `## Review Criteria
3015
3922
 
3016
3923
  ${criteriaBlock}
3017
3924
 
3018
- `}${humanHandoffBoundary}${priorReviewerBlock}${renderReviewHistory(reviewHistory)}${priorRefactorBlock}## Output
3925
+ `}${humanHandoffBoundary}${priorReviewerBlock}${renderReviewHistory(reviewHistory)}${priorRefactorBlock}${priorRefactorProtocolReminder}## Output
3019
3926
 
3020
3927
  Write your review to: ${artifactPathInWorktree}
3021
3928
 
@@ -3026,6 +3933,7 @@ End the file with exactly one wrapped verdict token: <verdict>PASS</verdict>, <v
3026
3933
  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).
3027
3934
 
3028
3935
  When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token.`;
3936
+ });
3029
3937
  }
3030
3938
  function renderReviewHistory(reviewHistory) {
3031
3939
  if (reviewHistory.length === 0) {
@@ -3039,150 +3947,147 @@ ${entry.trimEnd()}`).join("\n\n")}
3039
3947
 
3040
3948
  `;
3041
3949
  }
3042
- function renderPriorRefactorArtifacts(worktreePath, currentIteration) {
3043
- const refactorsDir = join9(worktreePath, ".pourkit", ".tmp", "refactors");
3044
- if (!existsSync6(refactorsDir)) {
3045
- return "";
3046
- }
3047
- const files = readdirSync(refactorsDir);
3048
- const iterationFiles = [];
3049
- for (const file of files) {
3050
- const match = file.match(/^iteration-(\d+)\.md$/);
3051
- if (match) {
3052
- const num = parseInt(match[1], 10);
3053
- if (num < currentIteration) {
3054
- const filePath = join9(refactorsDir, file);
3055
- try {
3056
- const content = readFileSync6(filePath, "utf-8");
3057
- if (content.trim()) {
3058
- iterationFiles.push({ num, content });
3059
- }
3060
- } catch {
3061
- }
3062
- }
3063
- }
3064
- }
3065
- if (iterationFiles.length === 0) {
3066
- return "";
3067
- }
3068
- iterationFiles.sort((a, b) => a.num - b.num);
3069
- const iterationsBlocks = iterationFiles.map((f) => `### Refactor Iteration ${f.num}
3950
+ function renderPriorRefactorArtifactsEffect(worktreePath, currentIteration) {
3951
+ return Effect7.gen(function* () {
3952
+ const fb = yield* FileSystem;
3953
+ const refactorsDir = join11(worktreePath, ".pourkit", ".tmp", "refactors");
3954
+ const dirExists = yield* fb.exists(refactorsDir).pipe(Effect7.catchAll(() => Effect7.succeed(false)));
3955
+ if (!dirExists) return "";
3956
+ const iterationFiles = [];
3957
+ for (let i = 0; i < currentIteration; i++) {
3958
+ const filePath = join11(refactorsDir, `iteration-${i}.md`);
3959
+ const fileExists = yield* fb.exists(filePath).pipe(Effect7.catchAll(() => Effect7.succeed(false)));
3960
+ if (!fileExists) continue;
3961
+ const content = yield* fb.readFile(filePath).pipe(Effect7.catchAll(() => Effect7.succeed("")));
3962
+ if (content.trim()) {
3963
+ iterationFiles.push({ num: i, content });
3964
+ }
3965
+ }
3966
+ if (iterationFiles.length === 0) return "";
3967
+ iterationFiles.sort((a, b) => a.num - b.num);
3968
+ const iterationsBlocks = iterationFiles.map((f) => `### Refactor Iteration ${f.num}
3070
3969
 
3071
3970
  ${f.content.trimEnd()}`).join("\n\n");
3072
- return `## Prior Refactor Artifacts
3971
+ return `## Prior Refactor Artifacts
3073
3972
 
3074
3973
  Treat these as conversational context, not source of truth. Inspect the current code independently.
3075
3974
 
3076
3975
  ${iterationsBlocks}
3077
3976
 
3078
3977
  `;
3978
+ });
3079
3979
  }
3080
- function renderPriorReviewerArtifacts(worktreePath, currentIteration) {
3081
- const reviewersDir = join9(worktreePath, ".pourkit", ".tmp", "reviewers");
3082
- if (!existsSync6(reviewersDir)) {
3083
- return "";
3084
- }
3085
- const files = readdirSync(reviewersDir);
3086
- const iterationFiles = [];
3087
- for (const file of files) {
3088
- const match = file.match(/^iteration-(\d+)\.md$/);
3089
- if (match) {
3090
- const num = parseInt(match[1], 10);
3091
- if (num < currentIteration) {
3092
- const filePath = join9(reviewersDir, file);
3093
- try {
3094
- const content = readFileSync6(filePath, "utf-8");
3095
- if (content.trim()) {
3096
- iterationFiles.push({ num, content });
3097
- }
3098
- } catch {
3099
- }
3100
- }
3101
- }
3102
- }
3103
- if (iterationFiles.length === 0) {
3104
- return "";
3105
- }
3106
- iterationFiles.sort((a, b) => a.num - b.num);
3107
- const iterationsBlocks = iterationFiles.map((f) => `### Reviewer Iteration ${f.num}
3980
+ function renderPriorReviewerArtifactsEffect(worktreePath, currentIteration) {
3981
+ return Effect7.gen(function* () {
3982
+ const fb = yield* FileSystem;
3983
+ const reviewersDir = join11(worktreePath, ".pourkit", ".tmp", "reviewers");
3984
+ const dirExists = yield* fb.exists(reviewersDir).pipe(Effect7.catchAll(() => Effect7.succeed(false)));
3985
+ if (!dirExists) return "";
3986
+ const iterationFiles = [];
3987
+ for (let i = 0; i < currentIteration; i++) {
3988
+ const filePath = join11(reviewersDir, `iteration-${i}.md`);
3989
+ const fileExists = yield* fb.exists(filePath).pipe(Effect7.catchAll(() => Effect7.succeed(false)));
3990
+ if (!fileExists) continue;
3991
+ const content = yield* fb.readFile(filePath).pipe(Effect7.catchAll(() => Effect7.succeed("")));
3992
+ if (content.trim()) {
3993
+ iterationFiles.push({ num: i, content });
3994
+ }
3995
+ }
3996
+ if (iterationFiles.length === 0) return "";
3997
+ iterationFiles.sort((a, b) => a.num - b.num);
3998
+ const iterationsBlocks = iterationFiles.map((f) => `### Reviewer Iteration ${f.num}
3108
3999
 
3109
4000
  ${f.content.trimEnd()}`).join("\n\n");
3110
- return `## Prior Reviewer Artifacts
4001
+ return `## Prior Reviewer Artifacts
3111
4002
 
3112
4003
  A prior review was resolved by a human. These artifacts are historical context from before the handoff. Treat them as background, not active findings.
3113
4004
 
3114
4005
  ${iterationsBlocks}
3115
4006
 
3116
4007
  `;
4008
+ });
3117
4009
  }
3118
- function loadReviewerPromptTemplate(repoRoot2, promptTemplate, criteriaBlock) {
3119
- const promptTemplatePath = resolvePromptTemplatePath(
3120
- repoRoot2,
3121
- promptTemplate
3122
- );
3123
- const promptBody = existsSync6(promptTemplatePath) ? readFileSync6(promptTemplatePath, "utf-8") : promptTemplate;
3124
- const hasCriteriaPlaceholder = promptBody.includes("{{REVIEW_CRITERIA}}");
3125
- return {
3126
- content: promptBody.replace(/\{\{REVIEW_CRITERIA\}\}/g, criteriaBlock),
3127
- hasCriteriaPlaceholder
3128
- };
3129
- }
3130
- function renderReviewCriteria(repoRoot2, criteria) {
3131
- return criteria.map((criterion) => {
3132
- const snippetPath = join9(
4010
+ function loadReviewerPromptTemplateEffect(repoRoot2, promptTemplate, criteriaBlock, fs) {
4011
+ return Effect7.gen(function* () {
4012
+ const promptTemplatePath = resolvePromptTemplatePath(
3133
4013
  repoRoot2,
3134
- ".pourkit",
3135
- "prompts",
3136
- `reviewer-${criterion}.snippet.md`
4014
+ promptTemplate
3137
4015
  );
3138
- if (existsSync6(snippetPath)) {
3139
- return readFileSync6(snippetPath, "utf-8").trimEnd();
3140
- }
3141
- return `- ${criterion}`;
3142
- }).join("\n\n");
4016
+ const exists = yield* fs.exists(promptTemplatePath).pipe(Effect7.orDie);
4017
+ const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect7.orDie) : promptTemplate;
4018
+ const hasCriteriaPlaceholder = promptBody.includes("{{REVIEW_CRITERIA}}");
4019
+ return {
4020
+ content: promptBody.replace(/\{\{REVIEW_CRITERIA\}\}/g, criteriaBlock),
4021
+ hasCriteriaPlaceholder
4022
+ };
4023
+ });
3143
4024
  }
3144
- function prepareReviewArtifactPath(artifactPath) {
3145
- mkdirSync5(dirname4(artifactPath), { recursive: true });
3146
- if (existsSync6(artifactPath)) {
3147
- rmSync2(artifactPath);
3148
- }
4025
+ function renderReviewCriteriaEffect(repoRoot2, criteria, fs) {
4026
+ return Effect7.gen(function* () {
4027
+ const parts = [];
4028
+ for (const criterion of criteria) {
4029
+ const snippetPath = join11(
4030
+ repoRoot2,
4031
+ ".pourkit",
4032
+ "prompts",
4033
+ `reviewer-${criterion}.snippet.md`
4034
+ );
4035
+ const exists = yield* fs.exists(snippetPath).pipe(Effect7.orDie);
4036
+ if (exists) {
4037
+ const content = yield* fs.readFile(snippetPath).pipe(Effect7.orDie);
4038
+ parts.push(content.trimEnd());
4039
+ } else {
4040
+ parts.push(`- ${criterion}`);
4041
+ }
4042
+ }
4043
+ return parts.join("\n\n");
4044
+ });
3149
4045
  }
3150
- function recoverReviewOutputFromLog(logPath) {
3151
- if (!existsSync6(logPath)) {
3152
- return null;
3153
- }
3154
- const logContent = readFileSync6(logPath, "utf-8");
4046
+ function recoverReviewOutputFromString(logContent) {
3155
4047
  const startIndex = logContent.indexOf("## Findings");
3156
- if (startIndex === -1) {
3157
- return null;
3158
- }
4048
+ if (startIndex === -1) return null;
3159
4049
  const verdictMatch = logContent.slice(startIndex).match(
3160
4050
  /<verdict>(PASS|PASS_WITH_NOTES|NEEDS_REFACTOR|FAIL|NEEDS_HUMAN)<\/verdict>/
3161
4051
  );
3162
- if (!verdictMatch || verdictMatch.index === void 0) {
3163
- return null;
3164
- }
4052
+ if (!verdictMatch || verdictMatch.index === void 0) return null;
3165
4053
  const recoveredOutput = logContent.slice(startIndex, startIndex + verdictMatch.index + verdictMatch[0].length).trim();
3166
4054
  return recoveredOutput.length > 0 ? recoveredOutput : null;
3167
4055
  }
3168
- function readReviewArtifact2(artifactPath, logPath) {
3169
- if (existsSync6(artifactPath)) {
3170
- const output = readFileSync6(artifactPath, "utf-8");
3171
- if (output.trim()) {
3172
- return output;
4056
+ function prepareReviewArtifactPathEffect(artifactPath, fs) {
4057
+ return Effect7.gen(function* () {
4058
+ yield* fs.mkdir(dirname5(artifactPath)).pipe(Effect7.orDie);
4059
+ const exists = yield* fs.exists(artifactPath).pipe(Effect7.orDie);
4060
+ if (exists) {
4061
+ yield* fs.remove(artifactPath).pipe(Effect7.orDie);
3173
4062
  }
3174
- }
3175
- const recoveredOutput = logPath ? recoverReviewOutputFromLog(logPath) : null;
3176
- if (recoveredOutput) {
3177
- writeFileSync3(artifactPath, recoveredOutput, "utf-8");
3178
- return recoveredOutput;
3179
- }
3180
- if (!existsSync6(artifactPath)) {
3181
- throw new Error(`Reviewer did not produce output at ${artifactPath}`);
3182
- }
3183
- throw new Error(`Reviewer produced empty output at ${artifactPath}`);
4063
+ });
3184
4064
  }
3185
- async function runReviewWithRefactorLoop(options) {
4065
+ function readReviewArtifactEffect(artifactPath, logPath, fs) {
4066
+ return Effect7.gen(function* () {
4067
+ const artifactExists = yield* fs.exists(artifactPath).pipe(Effect7.orDie);
4068
+ if (artifactExists) {
4069
+ const output = yield* fs.readFile(artifactPath).pipe(Effect7.orDie);
4070
+ if (output.trim()) return output;
4071
+ }
4072
+ if (logPath) {
4073
+ const logExists = yield* fs.exists(logPath).pipe(Effect7.orDie);
4074
+ if (logExists) {
4075
+ const logContent = yield* fs.readFile(logPath).pipe(Effect7.orDie);
4076
+ const recovered = recoverReviewOutputFromString(logContent);
4077
+ if (recovered) {
4078
+ yield* fs.writeFile(artifactPath, recovered).pipe(Effect7.orDie);
4079
+ return recovered;
4080
+ }
4081
+ }
4082
+ }
4083
+ return yield* Effect7.fail(
4084
+ new ReviewerFailure({
4085
+ message: artifactExists ? `Reviewer produced empty output at ${artifactPath}` : `Reviewer did not produce output at ${artifactPath}`
4086
+ })
4087
+ );
4088
+ });
4089
+ }
4090
+ function runReviewWithRefactorLoop(options) {
3186
4091
  const {
3187
4092
  executionProvider,
3188
4093
  config,
@@ -3199,247 +4104,325 @@ async function runReviewWithRefactorLoop(options) {
3199
4104
  const strategy = target.strategy;
3200
4105
  const reviewer = strategy.review.reviewer;
3201
4106
  if (!reviewer) {
3202
- throw new Error("No reviewer config found");
4107
+ return Effect7.die(new Error("No reviewer config found"));
3203
4108
  }
3204
4109
  const refactorer = strategy.review.refactor;
3205
4110
  if (!refactorer) {
3206
- throw new Error("No refactorer config found");
4111
+ return Effect7.die(new Error("No refactorer config found"));
3207
4112
  }
3208
4113
  const maxIterations = strategy.review.maxIterations;
3209
4114
  const passWithNotesRefactorAttempts = strategy.review.passWithNotesRefactorAttempts;
3210
- let resolvedStartingIteration = startingLifetimeIteration;
3211
- {
3212
- const reviewersDir = join9(worktreePath, ".pourkit", ".tmp", "reviewers");
3213
- try {
3214
- const files = readdirSync(reviewersDir);
3215
- let maxExistingIteration = 0;
3216
- for (const file of files) {
3217
- const match = file.match(/^iteration-(\d+)\.md$/);
3218
- if (match) {
3219
- const num = parseInt(match[1], 10);
3220
- if (num > maxExistingIteration) {
3221
- maxExistingIteration = num;
3222
- }
3223
- }
3224
- }
3225
- if (maxExistingIteration > resolvedStartingIteration) {
3226
- resolvedStartingIteration = maxExistingIteration;
3227
- }
3228
- } catch {
3229
- }
3230
- }
3231
- const accumulatedRefactorPaths = [];
3232
- let iteration = 0;
3233
- let lastResult = null;
3234
- const reviewHistory = [];
3235
- let passWithNotesRefactorAttemptsRemaining = passWithNotesRefactorAttempts;
3236
- const priorReviewerArtifacts = humanHandoffResolved ? renderPriorReviewerArtifacts(
3237
- worktreePath,
3238
- resolvedStartingIteration + 1
3239
- ) || void 0 : void 0;
3240
- while (iteration < maxIterations) {
3241
- iteration++;
3242
- const lifetimeIteration = resolvedStartingIteration + iteration;
3243
- logger.step("info", `Review iteration ${lifetimeIteration}`);
3244
- const priorRefactorArtifacts = renderPriorRefactorArtifacts(
3245
- worktreePath,
3246
- lifetimeIteration
3247
- );
3248
- const reviewResult = await runReviewCommand({
3249
- executionProvider,
3250
- config,
3251
- target,
3252
- issue,
3253
- builderBranch,
3254
- worktreePath,
3255
- repoRoot: repoRoot2,
3256
- logger,
3257
- iteration: lifetimeIteration,
3258
- priorRefactorArtifacts: priorRefactorArtifacts || void 0,
3259
- humanHandoffResolved,
3260
- priorReviewerArtifacts,
3261
- reviewHistory: reviewer.includeReviewHistory && reviewHistory.length > 0 ? [...reviewHistory] : void 0
3262
- });
3263
- lastResult = reviewResult;
3264
- reviewHistory.push(reviewResult.output);
3265
- await persistIterationArtifact(
3266
- worktreePath,
3267
- reviewResult.output,
3268
- lifetimeIteration
3269
- );
3270
- if (reviewResult.verdict === "PASS") {
3271
- return {
3272
- verdict: reviewResult.verdict,
3273
- output: reviewResult.output,
3274
- artifactPath: reviewResult.artifactPath,
3275
- iterations: iteration,
3276
- lifetimeIterations: lifetimeIteration,
3277
- exhaustedMaxIterations: false,
3278
- refactorCompletedForLastReview: false,
3279
- refactorArtifactPaths: accumulatedRefactorPaths.length > 0 ? accumulatedRefactorPaths : void 0
3280
- };
3281
- }
3282
- if (reviewResult.verdict === "PASS_WITH_NOTES" && passWithNotesRefactorAttemptsRemaining === 0) {
3283
- logger.step(
3284
- "info",
3285
- "PASS_WITH_NOTES refactor attempts exhausted, treating as PASS"
3286
- );
3287
- return {
3288
- verdict: "PASS",
3289
- output: reviewResult.output,
3290
- artifactPath: reviewResult.artifactPath,
3291
- iterations: iteration,
3292
- lifetimeIterations: lifetimeIteration,
3293
- exhaustedMaxIterations: false,
3294
- refactorCompletedForLastReview: false,
3295
- refactorArtifactPaths: accumulatedRefactorPaths.length > 0 ? accumulatedRefactorPaths : void 0
3296
- };
3297
- }
3298
- if (reviewResult.verdict === "PASS_WITH_NOTES") {
3299
- passWithNotesRefactorAttemptsRemaining--;
3300
- logger.step(
3301
- "info",
3302
- `PASS_WITH_NOTES refactor attempts remaining: ${passWithNotesRefactorAttemptsRemaining}`
3303
- );
3304
- }
3305
- if (reviewResult.verdict === "NEEDS_HUMAN") {
3306
- logger.step("info", "NEEDS_HUMAN verdict, stopping review loop");
3307
- return {
3308
- verdict: "NEEDS_HUMAN",
3309
- output: reviewResult.output,
3310
- artifactPath: reviewResult.artifactPath,
3311
- iterations: iteration,
3312
- lifetimeIterations: lifetimeIteration,
3313
- exhaustedMaxIterations: false,
3314
- refactorCompletedForLastReview: false,
3315
- refactorArtifactPaths: accumulatedRefactorPaths.length > 0 ? accumulatedRefactorPaths : void 0
3316
- };
4115
+ let resolvedStartingIteration = startingLifetimeIteration;
4116
+ {
4117
+ const reviewersDir = join11(worktreePath, ".pourkit", ".tmp", "reviewers");
4118
+ try {
4119
+ const files = readdirSync(reviewersDir);
4120
+ let maxExistingIteration = 0;
4121
+ for (const file of files) {
4122
+ const match = file.match(/^iteration-(\d+)\.md$/);
4123
+ if (match) {
4124
+ const num = parseInt(match[1], 10);
4125
+ if (num > maxExistingIteration) {
4126
+ maxExistingIteration = num;
4127
+ }
4128
+ }
4129
+ }
4130
+ if (maxExistingIteration > resolvedStartingIteration) {
4131
+ resolvedStartingIteration = maxExistingIteration;
4132
+ }
4133
+ } catch {
3317
4134
  }
3318
- if (reviewResult.verdict === "NEEDS_REFACTOR" || reviewResult.verdict === "PASS_WITH_NOTES" || reviewResult.verdict === "FAIL") {
3319
- logger.step("info", "Running refactor agent");
3320
- const refactorArtifactPathInWorktree = join9(
3321
- ".pourkit",
3322
- ".tmp",
3323
- "refactors",
3324
- `iteration-${lifetimeIteration}.md`
3325
- );
3326
- const refactorPrompt = buildRefactorPrompt(
3327
- repoRoot2,
3328
- refactorer.promptTemplate,
3329
- reviewResult.output,
3330
- refactorArtifactPathInWorktree
3331
- );
3332
- const refactorResult = await executionProvider.execute({
3333
- stage: "refactor",
3334
- iteration: lifetimeIteration,
3335
- agent: refactorer.agent,
3336
- model: refactorer.model,
3337
- prompt: refactorPrompt,
3338
- target,
3339
- repoRoot: repoRoot2,
3340
- branchName: builderBranch,
3341
- sandbox: config.sandbox,
3342
- autoApprove: true,
3343
- artifactPath: refactorArtifactPathInWorktree,
3344
- worktreePath,
3345
- artifacts: [
3346
- buildRunContextArtifact({
3347
- issue,
3348
- target,
3349
- branchName: builderBranch,
3350
- reviewerCriteria: reviewer.criteria,
3351
- sections: STAGE_SECTIONS.refactor
3352
- })
3353
- ],
3354
- ...serena ? { serena } : {},
3355
- logger
3356
- });
3357
- if (!refactorResult.success) {
3358
- logger.step(
3359
- "warn",
3360
- "Refactor execution failed, transitioning to ready-for-human"
4135
+ }
4136
+ const program = Effect7.gen(function* () {
4137
+ const exec = yield* ExecutionProvider;
4138
+ const fs = yield* FileSystem;
4139
+ const priorReviewerArtifacts = humanHandoffResolved ? (yield* renderPriorReviewerArtifactsEffect(
4140
+ worktreePath,
4141
+ resolvedStartingIteration + 1
4142
+ )) || void 0 : void 0;
4143
+ const initialState = {
4144
+ done: false,
4145
+ iteration: 0,
4146
+ lastResult: null,
4147
+ reviewHistory: [],
4148
+ passWithNotesRefactorAttemptsRemaining: passWithNotesRefactorAttempts,
4149
+ accumulatedRefactorPaths: [],
4150
+ doneResult: null
4151
+ };
4152
+ const finalState = yield* Effect7.iterate(initialState, {
4153
+ while: (s) => !s.done && s.iteration < maxIterations,
4154
+ body: (s) => Effect7.gen(function* () {
4155
+ const iteration = s.iteration + 1;
4156
+ const lifetimeIteration = resolvedStartingIteration + iteration;
4157
+ logger.step("info", `Review iteration ${lifetimeIteration}`);
4158
+ const priorRefactorArtifactsStr = yield* renderPriorRefactorArtifactsEffect(
4159
+ worktreePath,
4160
+ lifetimeIteration
3361
4161
  );
3362
- return {
3363
- verdict: "FAIL",
3364
- output: reviewResult.output,
3365
- artifactPath: reviewResult.artifactPath,
3366
- iterations: iteration,
3367
- lifetimeIterations: resolvedStartingIteration + iteration,
3368
- exhaustedMaxIterations: false,
3369
- refactorCompletedForLastReview: false,
3370
- refactorArtifactPaths: accumulatedRefactorPaths.length > 0 ? accumulatedRefactorPaths : void 0
3371
- };
3372
- }
3373
- const latestFindingIds = extractLatestFindingIds(
3374
- reviewResult.output,
3375
- lifetimeIteration
3376
- );
3377
- const refactorArtifactPath = join9(
3378
- worktreePath,
3379
- refactorArtifactPathInWorktree
3380
- );
3381
- try {
3382
- validateRefactorArtifact(refactorArtifactPath, latestFindingIds);
3383
- } catch (error) {
3384
- if (error instanceof RefactorArtifactValidationError) {
4162
+ const reviewResult = yield* runReviewCommandEffect({
4163
+ executionProvider: null,
4164
+ config,
4165
+ target,
4166
+ issue,
4167
+ builderBranch,
4168
+ worktreePath,
4169
+ repoRoot: repoRoot2,
4170
+ logger,
4171
+ iteration: lifetimeIteration,
4172
+ priorRefactorArtifacts: priorRefactorArtifactsStr || void 0,
4173
+ humanHandoffResolved,
4174
+ priorReviewerArtifacts,
4175
+ reviewHistory: reviewer.includeReviewHistory && s.reviewHistory.length > 0 ? [...s.reviewHistory] : void 0
4176
+ });
4177
+ const updatedHistory = [...s.reviewHistory, reviewResult.output];
4178
+ yield* persistIterationArtifactEffect(
4179
+ worktreePath,
4180
+ reviewResult.output,
4181
+ lifetimeIteration,
4182
+ fs
4183
+ );
4184
+ if (reviewResult.verdict === "PASS") {
4185
+ return {
4186
+ ...s,
4187
+ done: true,
4188
+ iteration,
4189
+ lastResult: reviewResult,
4190
+ reviewHistory: updatedHistory,
4191
+ doneResult: {
4192
+ verdict: reviewResult.verdict,
4193
+ output: reviewResult.output,
4194
+ artifactPath: reviewResult.artifactPath,
4195
+ iterations: iteration,
4196
+ lifetimeIterations: lifetimeIteration,
4197
+ exhaustedMaxIterations: false,
4198
+ refactorCompletedForLastReview: false,
4199
+ refactorArtifactPaths: s.accumulatedRefactorPaths.length > 0 ? [...s.accumulatedRefactorPaths] : void 0
4200
+ }
4201
+ };
4202
+ }
4203
+ if (reviewResult.verdict === "PASS_WITH_NOTES" && s.passWithNotesRefactorAttemptsRemaining === 0) {
3385
4204
  logger.step(
3386
- "warn",
3387
- `Refactor artifact validation failed: ${error.message}`
4205
+ "info",
4206
+ "PASS_WITH_NOTES refactor attempts exhausted, treating as PASS"
3388
4207
  );
3389
4208
  return {
3390
- verdict: "FAIL",
3391
- output: reviewResult.output,
3392
- artifactPath: reviewResult.artifactPath,
3393
- iterations: iteration,
3394
- lifetimeIterations: resolvedStartingIteration + iteration,
3395
- exhaustedMaxIterations: false,
3396
- refactorCompletedForLastReview: false,
3397
- refactorArtifactPaths: accumulatedRefactorPaths.length > 0 ? accumulatedRefactorPaths : void 0
4209
+ ...s,
4210
+ done: true,
4211
+ iteration,
4212
+ lastResult: reviewResult,
4213
+ reviewHistory: updatedHistory,
4214
+ doneResult: {
4215
+ verdict: "PASS",
4216
+ output: reviewResult.output,
4217
+ artifactPath: reviewResult.artifactPath,
4218
+ iterations: iteration,
4219
+ lifetimeIterations: lifetimeIteration,
4220
+ exhaustedMaxIterations: false,
4221
+ refactorCompletedForLastReview: false,
4222
+ refactorArtifactPaths: s.accumulatedRefactorPaths.length > 0 ? [...s.accumulatedRefactorPaths] : void 0
4223
+ }
3398
4224
  };
3399
4225
  }
3400
- throw error;
3401
- }
3402
- accumulatedRefactorPaths.push(refactorArtifactPath);
3403
- if (options.onRefactorProgress) {
3404
- await options.onRefactorProgress({
3405
- lifetimeIterations: resolvedStartingIteration + iteration,
3406
- lastVerdict: reviewResult.verdict,
3407
- lastArtifactPath: reviewResult.artifactPath,
3408
- refactorArtifactPath
3409
- });
3410
- }
4226
+ let updatedPassWithNotesRefactorAttemptsRemaining = s.passWithNotesRefactorAttemptsRemaining;
4227
+ if (reviewResult.verdict === "PASS_WITH_NOTES") {
4228
+ updatedPassWithNotesRefactorAttemptsRemaining--;
4229
+ logger.step(
4230
+ "info",
4231
+ `PASS_WITH_NOTES refactor attempts remaining: ${updatedPassWithNotesRefactorAttemptsRemaining}`
4232
+ );
4233
+ }
4234
+ if (reviewResult.verdict === "NEEDS_HUMAN") {
4235
+ logger.step("info", "NEEDS_HUMAN verdict, stopping review loop");
4236
+ return {
4237
+ ...s,
4238
+ done: true,
4239
+ iteration,
4240
+ lastResult: reviewResult,
4241
+ reviewHistory: updatedHistory,
4242
+ doneResult: {
4243
+ verdict: "NEEDS_HUMAN",
4244
+ output: reviewResult.output,
4245
+ artifactPath: reviewResult.artifactPath,
4246
+ iterations: iteration,
4247
+ lifetimeIterations: lifetimeIteration,
4248
+ exhaustedMaxIterations: false,
4249
+ refactorCompletedForLastReview: false,
4250
+ refactorArtifactPaths: s.accumulatedRefactorPaths.length > 0 ? [...s.accumulatedRefactorPaths] : void 0
4251
+ }
4252
+ };
4253
+ }
4254
+ if (reviewResult.verdict === "NEEDS_REFACTOR" || reviewResult.verdict === "PASS_WITH_NOTES" || reviewResult.verdict === "FAIL") {
4255
+ logger.step("info", "Running refactor agent");
4256
+ const refactorArtifactPathInWorktree = join11(
4257
+ ".pourkit",
4258
+ ".tmp",
4259
+ "refactors",
4260
+ `iteration-${lifetimeIteration}.md`
4261
+ );
4262
+ const refactorPrompt = yield* buildRefactorPromptEffect(
4263
+ repoRoot2,
4264
+ refactorer.promptTemplate,
4265
+ reviewResult.output,
4266
+ refactorArtifactPathInWorktree,
4267
+ fs
4268
+ );
4269
+ const refactorResult = yield* exec.execute({
4270
+ stage: "refactor",
4271
+ iteration: lifetimeIteration,
4272
+ agent: refactorer.agent,
4273
+ model: refactorer.model,
4274
+ prompt: refactorPrompt,
4275
+ target,
4276
+ repoRoot: repoRoot2,
4277
+ branchName: builderBranch,
4278
+ sandbox: config.sandbox,
4279
+ autoApprove: true,
4280
+ artifactPath: refactorArtifactPathInWorktree,
4281
+ worktreePath,
4282
+ artifacts: [
4283
+ buildRunContextArtifact({
4284
+ issue,
4285
+ target,
4286
+ branchName: builderBranch,
4287
+ reviewerCriteria: reviewer.criteria,
4288
+ sections: STAGE_SECTIONS.refactor
4289
+ })
4290
+ ],
4291
+ ...serena ? { serena } : {},
4292
+ logger
4293
+ });
4294
+ if (!refactorResult.success) {
4295
+ logger.step(
4296
+ "warn",
4297
+ "Refactor execution failed, transitioning to ready-for-human"
4298
+ );
4299
+ return {
4300
+ ...s,
4301
+ done: true,
4302
+ iteration,
4303
+ lastResult: reviewResult,
4304
+ reviewHistory: updatedHistory,
4305
+ doneResult: {
4306
+ verdict: "FAIL",
4307
+ output: reviewResult.output,
4308
+ artifactPath: reviewResult.artifactPath,
4309
+ iterations: iteration,
4310
+ lifetimeIterations: resolvedStartingIteration + iteration,
4311
+ exhaustedMaxIterations: false,
4312
+ refactorCompletedForLastReview: false,
4313
+ refactorArtifactPaths: s.accumulatedRefactorPaths.length > 0 ? [...s.accumulatedRefactorPaths] : void 0
4314
+ }
4315
+ };
4316
+ }
4317
+ const latestFindingIds = extractLatestFindingIds(
4318
+ reviewResult.output,
4319
+ lifetimeIteration
4320
+ );
4321
+ const refactorArtifactPath = join11(
4322
+ worktreePath,
4323
+ refactorArtifactPathInWorktree
4324
+ );
4325
+ try {
4326
+ validateRefactorArtifact(refactorArtifactPath, latestFindingIds);
4327
+ } catch (error) {
4328
+ if (error instanceof RefactorArtifactValidationError) {
4329
+ logger.step(
4330
+ "warn",
4331
+ `Refactor artifact validation failed: ${error.message}`
4332
+ );
4333
+ return {
4334
+ ...s,
4335
+ done: true,
4336
+ iteration,
4337
+ lastResult: reviewResult,
4338
+ reviewHistory: updatedHistory,
4339
+ doneResult: {
4340
+ verdict: "FAIL",
4341
+ output: reviewResult.output,
4342
+ artifactPath: reviewResult.artifactPath,
4343
+ iterations: iteration,
4344
+ lifetimeIterations: resolvedStartingIteration + iteration,
4345
+ exhaustedMaxIterations: false,
4346
+ refactorCompletedForLastReview: false,
4347
+ refactorArtifactPaths: s.accumulatedRefactorPaths.length > 0 ? [...s.accumulatedRefactorPaths] : void 0
4348
+ }
4349
+ };
4350
+ }
4351
+ yield* Effect7.die(error);
4352
+ }
4353
+ const updatedAccumulated = [
4354
+ ...s.accumulatedRefactorPaths,
4355
+ refactorArtifactPath
4356
+ ];
4357
+ if (options.onRefactorProgress) {
4358
+ yield* Effect7.promise(
4359
+ () => Promise.resolve(
4360
+ options.onRefactorProgress({
4361
+ lifetimeIterations: resolvedStartingIteration + iteration,
4362
+ lastVerdict: reviewResult.verdict,
4363
+ lastArtifactPath: reviewResult.artifactPath,
4364
+ refactorArtifactPath
4365
+ })
4366
+ )
4367
+ );
4368
+ }
4369
+ return {
4370
+ done: false,
4371
+ iteration,
4372
+ lastResult: reviewResult,
4373
+ reviewHistory: updatedHistory,
4374
+ passWithNotesRefactorAttemptsRemaining: updatedPassWithNotesRefactorAttemptsRemaining,
4375
+ accumulatedRefactorPaths: updatedAccumulated,
4376
+ doneResult: null
4377
+ };
4378
+ }
4379
+ return {
4380
+ done: false,
4381
+ iteration,
4382
+ lastResult: reviewResult,
4383
+ reviewHistory: updatedHistory,
4384
+ passWithNotesRefactorAttemptsRemaining: updatedPassWithNotesRefactorAttemptsRemaining,
4385
+ accumulatedRefactorPaths: s.accumulatedRefactorPaths,
4386
+ doneResult: null
4387
+ };
4388
+ })
4389
+ });
4390
+ if (finalState.done && finalState.doneResult) {
4391
+ return finalState.doneResult;
3411
4392
  }
3412
- }
3413
- logger.step("warn", `Max review iterations (${maxIterations}) exhausted`);
3414
- return {
3415
- verdict: "FAIL",
3416
- output: lastResult?.output ?? "",
3417
- artifactPath: lastResult?.artifactPath ?? "",
3418
- iterations: iteration,
3419
- lifetimeIterations: resolvedStartingIteration + iteration,
3420
- exhaustedMaxIterations: true,
3421
- refactorCompletedForLastReview: true,
3422
- refactorArtifactPaths: accumulatedRefactorPaths.length > 0 ? accumulatedRefactorPaths : void 0
3423
- };
3424
- }
3425
- async function writeArtifact(worktreePath, filename, output) {
3426
- try {
3427
- const dir = join9(worktreePath, ".pourkit", ".tmp", "reviewers");
3428
- mkdirSync5(dir, { recursive: true });
3429
- writeFileSync3(join9(dir, filename), output, "utf-8");
3430
- } catch {
3431
- }
4393
+ logger.step("warn", `Max review iterations (${maxIterations}) exhausted`);
4394
+ return {
4395
+ verdict: "FAIL",
4396
+ output: finalState.lastResult?.output ?? "",
4397
+ artifactPath: finalState.lastResult?.artifactPath ?? "",
4398
+ iterations: finalState.iteration,
4399
+ lifetimeIterations: resolvedStartingIteration + finalState.iteration,
4400
+ exhaustedMaxIterations: true,
4401
+ refactorCompletedForLastReview: true,
4402
+ refactorArtifactPaths: finalState.accumulatedRefactorPaths.length > 0 ? [...finalState.accumulatedRefactorPaths] : void 0
4403
+ };
4404
+ });
4405
+ const executionLayer = bridgeExecutionProvider2(executionProvider);
4406
+ return program.pipe(
4407
+ Effect7.provide(Layer3.merge(executionLayer, FileSystemDefault))
4408
+ );
3432
4409
  }
3433
- async function persistIterationArtifact(worktreePath, output, iteration) {
3434
- await writeArtifact(worktreePath, `iteration-${iteration}.md`, output);
4410
+ function persistIterationArtifactEffect(worktreePath, output, iteration, fs) {
4411
+ return Effect7.gen(function* () {
4412
+ const dir = join11(worktreePath, ".pourkit", ".tmp", "reviewers");
4413
+ yield* fs.mkdir(dir).pipe(Effect7.catchAll(() => Effect7.void));
4414
+ yield* fs.writeFile(join11(dir, `iteration-${iteration}.md`), output).pipe(Effect7.catchAll(() => Effect7.void));
4415
+ });
3435
4416
  }
3436
- function buildRefactorPrompt(repoRoot2, promptTemplate, latestReview, artifactPathInWorktree) {
3437
- const promptTemplatePath = resolvePromptTemplatePath(
3438
- repoRoot2,
3439
- promptTemplate
3440
- );
3441
- const promptBody = existsSync6(promptTemplatePath) ? readFileSync6(promptTemplatePath, "utf-8") : promptTemplate;
3442
- return appendProtectedWorkGuidance(`${promptBody}
4417
+ function buildRefactorPromptEffect(repoRoot2, promptTemplate, latestReview, artifactPathInWorktree, fs) {
4418
+ return Effect7.gen(function* () {
4419
+ const promptTemplatePath = resolvePromptTemplatePath(
4420
+ repoRoot2,
4421
+ promptTemplate
4422
+ );
4423
+ const exists = yield* fs.exists(promptTemplatePath).pipe(Effect7.orDie);
4424
+ const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect7.orDie) : promptTemplate;
4425
+ return appendProtectedWorkGuidance(`${promptBody}
3443
4426
 
3444
4427
  ## Shared Run Context
3445
4428
 
@@ -3454,6 +4437,7 @@ ${latestReview.trimEnd()}
3454
4437
  Write your refactor artifact to: ${artifactPathInWorktree}
3455
4438
 
3456
4439
  When you are done, finish with <promise>COMPLETE</promise>.`);
4440
+ });
3457
4441
  }
3458
4442
 
3459
4443
  // issues/issue-transitions.ts
@@ -3706,7 +4690,7 @@ async function startIssueRun(options) {
3706
4690
  prNumber: existingPr?.number,
3707
4691
  prState: existingPr?.state
3708
4692
  });
3709
- if (Exit2.isSuccess(exit)) {
4693
+ if (Exit3.isSuccess(exit)) {
3710
4694
  const refreshResult = exit.value;
3711
4695
  if (refreshResult.status === "refreshed") {
3712
4696
  if (worktreeState?.completedStages.builder) {
@@ -3858,22 +4842,24 @@ async function startIssueRun(options) {
3858
4842
  }
3859
4843
  async function advanceIssueRunReview(options) {
3860
4844
  const accumulatedRefactorPaths = [];
3861
- const reviewResult = await runReviewWithRefactorLoop({
3862
- ...options,
3863
- onRefactorProgress: async (progress) => {
3864
- if (progress.refactorArtifactPath) {
3865
- accumulatedRefactorPaths.push(progress.refactorArtifactPath);
3866
- }
3867
- updateWorktreeRunState(options.worktreePath, {
3868
- review: {
3869
- lifetimeIterations: progress.lifetimeIterations,
3870
- lastVerdict: progress.lastVerdict,
3871
- lastArtifactPath: progress.lastArtifactPath,
3872
- refactorCompletedForLastReview: true
4845
+ const reviewResult = await runEffectAndMapExit(
4846
+ runReviewWithRefactorLoop({
4847
+ ...options,
4848
+ onRefactorProgress: (progress) => {
4849
+ if (progress.refactorArtifactPath) {
4850
+ accumulatedRefactorPaths.push(progress.refactorArtifactPath);
3873
4851
  }
3874
- });
3875
- }
3876
- });
4852
+ updateWorktreeRunState(options.worktreePath, {
4853
+ review: {
4854
+ lifetimeIterations: progress.lifetimeIterations,
4855
+ lastVerdict: progress.lastVerdict,
4856
+ lastArtifactPath: progress.lastArtifactPath,
4857
+ refactorCompletedForLastReview: true
4858
+ }
4859
+ });
4860
+ }
4861
+ })
4862
+ );
3877
4863
  updateWorktreeRunState(options.worktreePath, {
3878
4864
  review: {
3879
4865
  lifetimeIterations: reviewResult.lifetimeIterations,
@@ -3932,12 +4918,12 @@ async function completeIssueRun(options) {
3932
4918
  prTitle = finalizerFromState.title;
3933
4919
  prBody = finalizerFromState.body;
3934
4920
  } else if (finalizerFromState.artifactPath) {
3935
- if (!existsSync7(finalizerFromState.artifactPath)) {
3936
- throw new Error(
3937
- `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
3938
- );
4921
+ if (!existsSync8(finalizerFromState.artifactPath)) {
4922
+ throw new FinalizerFailure({
4923
+ message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
4924
+ });
3939
4925
  }
3940
- const artifactContent = readFileSync7(
4926
+ const artifactContent = readFileSync8(
3941
4927
  finalizerFromState.artifactPath,
3942
4928
  "utf-8"
3943
4929
  );
@@ -3945,22 +4931,24 @@ async function completeIssueRun(options) {
3945
4931
  prTitle = parsed.title;
3946
4932
  prBody = parsed.body;
3947
4933
  } else {
3948
- throw new Error(
3949
- "Finalizer state is incomplete: missing title, body, and artifactPath"
3950
- );
4934
+ throw new FinalizerFailure({
4935
+ message: "Finalizer state is incomplete: missing title, body, and artifactPath"
4936
+ });
3951
4937
  }
3952
4938
  } else {
3953
- finalizerResult = await runFinalizerAgent({
3954
- executionProvider,
3955
- config,
3956
- target,
3957
- issue,
3958
- builderBranch: branchName,
3959
- worktreePath: executionResult.worktreePath,
3960
- reviewArtifactPath,
3961
- repoRoot: ROOT,
3962
- logger
3963
- });
4939
+ finalizerResult = await runEffectAndMapExit(
4940
+ runFinalizerAgent({
4941
+ executionProvider,
4942
+ config,
4943
+ target,
4944
+ issue,
4945
+ builderBranch: branchName,
4946
+ worktreePath: executionResult.worktreePath,
4947
+ reviewArtifactPath,
4948
+ repoRoot: ROOT,
4949
+ logger
4950
+ })
4951
+ );
3964
4952
  prTitle = finalizerResult.title;
3965
4953
  prBody = finalizerResult.body;
3966
4954
  }
@@ -4070,16 +5058,18 @@ async function completeIssueRun(options) {
4070
5058
  await issueProvider.addLabels(issueNumber, [
4071
5059
  config.labels.prOpenAwaitingMerge
4072
5060
  ]);
4073
- const coordinatorResult = await runMergeCoordinator({
4074
- prProvider,
4075
- logger,
4076
- prNumber: pr.number,
4077
- targetBranch: target.baseBranch,
4078
- matchHeadCommit: pr.headRefOid,
4079
- checkWaitOptions,
4080
- autoMerge: true,
4081
- pr
4082
- });
5061
+ const coordinatorResult = await runEffectAndMapExit(
5062
+ runMergeCoordinator({
5063
+ prProvider,
5064
+ logger,
5065
+ prNumber: pr.number,
5066
+ targetBranch: target.baseBranch,
5067
+ matchHeadCommit: pr.headRefOid,
5068
+ checkWaitOptions,
5069
+ autoMerge: true,
5070
+ pr
5071
+ })
5072
+ );
4083
5073
  if (coordinatorResult.stage === "merge") {
4084
5074
  throw coordinatorResult.error;
4085
5075
  }
@@ -4250,7 +5240,10 @@ async function guardFinalCommitContent(options) {
4250
5240
  label: "git diff --cached secret guard"
4251
5241
  });
4252
5242
  assertNoSecretLikeContent([
4253
- { source: "staged diff", content: stagedDiff.stdout },
5243
+ {
5244
+ source: "staged diff",
5245
+ content: extractAddedDiffContent(stagedDiff.stdout)
5246
+ },
4254
5247
  { source: "commit title", content: title },
4255
5248
  { source: "commit body", content: body }
4256
5249
  ]);
@@ -4263,11 +5256,17 @@ async function guardFinalPublishContent(options) {
4263
5256
  label: "git diff final secret guard"
4264
5257
  });
4265
5258
  assertNoSecretLikeContent([
4266
- { source: "final diff", content: finalDiff.stdout },
5259
+ {
5260
+ source: "final diff",
5261
+ content: extractAddedDiffContent(finalDiff.stdout)
5262
+ },
4267
5263
  { source: "PR title", content: title },
4268
5264
  { source: "PR body", content: body }
4269
5265
  ]);
4270
5266
  }
5267
+ function extractAddedDiffContent(diff) {
5268
+ return diff.split(/\r?\n/).filter((line) => line.startsWith("+") && !line.startsWith("+++")).map((line) => line.slice(1)).join("\n");
5269
+ }
4271
5270
  function assertNoSecretLikeContent(inputs) {
4272
5271
  const findings = inputs.flatMap(
4273
5272
  ({ source, content }) => findSecretLikeContent(source, content ?? "")
@@ -4545,7 +5544,7 @@ async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
4545
5544
  return { mode: "new", branchName, baseRef };
4546
5545
  }
4547
5546
  function issueWorktreePath(root, branchName) {
4548
- return join10(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
5547
+ return join12(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
4549
5548
  }
4550
5549
  function resolveRegisteredIssueWorktreePath(worktreeListPorcelain, root, branchName) {
4551
5550
  const branchWorktreePath = parseWorktreeListPorcelain(
@@ -4573,7 +5572,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
4573
5572
  }
4574
5573
  function loadBuilderPrompt(repoRoot2, promptTemplate) {
4575
5574
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
4576
- const promptBody = existsSync7(promptPath) ? readFileSync7(promptPath, "utf-8") : promptTemplate;
5575
+ const promptBody = existsSync8(promptPath) ? readFileSync8(promptPath, "utf-8") : promptTemplate;
4577
5576
  return appendProtectedWorkGuidance(`${promptBody}
4578
5577
 
4579
5578
  ## Shared Run Context
@@ -4891,7 +5890,18 @@ async function runIssueCommand(options) {
4891
5890
  reviewArtifactPath
4892
5891
  });
4893
5892
  } catch (error) {
4894
- if (!(error instanceof HumanHandoffStop)) {
5893
+ if (error instanceof HumanHandoffStop) {
5894
+ throw error;
5895
+ }
5896
+ if (error instanceof Error && "_tag" in error) {
5897
+ await failIssueRun({
5898
+ issueProvider,
5899
+ issueNumber,
5900
+ config,
5901
+ logger,
5902
+ error: `[${error._tag}] ${error.message}`
5903
+ });
5904
+ } else {
4895
5905
  await failIssueRun({
4896
5906
  issueProvider,
4897
5907
  issueNumber,
@@ -4997,6 +6007,7 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
4997
6007
 
4998
6008
  // commands/queue.ts
4999
6009
  init_common();
6010
+ import { Effect as Effect8 } from "effect";
5000
6011
 
5001
6012
  // issues/select-issue.ts
5002
6013
  init_common();
@@ -5109,6 +6120,13 @@ async function anyBlockerStillOpen(refs, getIssueState) {
5109
6120
  }
5110
6121
 
5111
6122
  // commands/queue.ts
6123
+ var QueueProviderError = class extends Error {
6124
+ _tag = "QueueProviderError";
6125
+ constructor(message) {
6126
+ super(message);
6127
+ this.name = "QueueProviderError";
6128
+ }
6129
+ };
5112
6130
  function issueDataToCandidate(issue) {
5113
6131
  return {
5114
6132
  number: issue.number,
@@ -5117,72 +6135,93 @@ function issueDataToCandidate(issue) {
5117
6135
  createdAt: issue.createdAt ? issue.createdAt.toISOString() : (/* @__PURE__ */ new Date(0)).toISOString()
5118
6136
  };
5119
6137
  }
5120
- async function selectNextQueueIssue(options) {
6138
+ function selectNextQueueIssue(options) {
5121
6139
  const { config, issueProvider, logger, prdRef } = options;
5122
6140
  logger.step("info", "Loading candidate issues from provider");
5123
- const candidates = await issueProvider.listCandidates();
5124
- if (candidates.length === 0) {
5125
- logger.step("warn", "No candidate issues found");
5126
- return {
5127
- ok: false,
5128
- reason: "No candidate issues found.",
5129
- code: "no-candidates"
5130
- };
5131
- }
5132
- logger.raw(`Found ${candidates.length} candidate(s):`);
5133
- for (const c of candidates) {
5134
- logger.raw(` #${c.number}: ${c.title} [${c.labels.join(", ")}]`);
5135
- }
5136
- const scopedCandidates = prdRef ? candidates.filter((issue) => {
5137
- const parsed = parseStackedIssue(issue.title, issue.body);
5138
- for (const warning of parsed.warnings) {
5139
- logger.raw(` #${issue.number}: ${warning}`);
6141
+ return Effect8.gen(function* () {
6142
+ const candidates = yield* Effect8.tryPromise({
6143
+ try: () => issueProvider.listCandidates(),
6144
+ catch: (e) => {
6145
+ if (e instanceof Error) {
6146
+ return new QueueProviderError(e.message);
6147
+ }
6148
+ throw e;
6149
+ }
6150
+ });
6151
+ if (candidates.length === 0) {
6152
+ logger.step("warn", "No candidate issues found");
6153
+ return {
6154
+ ok: false,
6155
+ reason: "No candidate issues found.",
6156
+ code: "no-candidates"
6157
+ };
6158
+ }
6159
+ logger.raw(`Found ${candidates.length} candidate(s):`);
6160
+ for (const c of candidates) {
6161
+ logger.raw(` #${c.number}: ${c.title} [${c.labels.join(", ")}]`);
5140
6162
  }
5141
- if (!parsed.parentRef) {
5142
- logger.raw(
5143
- ` #${issue.number}: no parent found, excluding from PRD scope`
6163
+ const scopedCandidates = prdRef ? candidates.filter((issue) => {
6164
+ const parsed = parseStackedIssue(issue.title, issue.body);
6165
+ for (const warning of parsed.warnings) {
6166
+ logger.raw(` #${issue.number}: ${warning}`);
6167
+ }
6168
+ if (!parsed.parentRef) {
6169
+ logger.raw(
6170
+ ` #${issue.number}: no parent found, excluding from PRD scope`
6171
+ );
6172
+ return false;
6173
+ }
6174
+ const matches = parsed.parentRef === prdRef;
6175
+ if (!matches) {
6176
+ logger.raw(
6177
+ ` #${issue.number}: parent ${parsed.parentRef} does not match ${prdRef}`
6178
+ );
6179
+ }
6180
+ return matches;
6181
+ }) : candidates;
6182
+ if (prdRef) {
6183
+ logger.step(
6184
+ "info",
6185
+ `PRD-scoped selection: ${scopedCandidates.length} of ${candidates.length} candidate(s) match ${prdRef}`
5144
6186
  );
5145
- return false;
5146
6187
  }
5147
- const matches = parsed.parentRef === prdRef;
5148
- if (!matches) {
5149
- logger.raw(
5150
- ` #${issue.number}: parent ${parsed.parentRef} does not match ${prdRef}`
6188
+ if (prdRef && scopedCandidates.length === 0) {
6189
+ logger.step("warn", `No candidate issues found for ${prdRef}.`);
6190
+ return {
6191
+ ok: false,
6192
+ reason: `No candidate issues found for ${prdRef}.`,
6193
+ code: "no-candidates"
6194
+ };
6195
+ }
6196
+ const candidateIssues = scopedCandidates.map(issueDataToCandidate);
6197
+ const selection = selectIssue(candidateIssues, {
6198
+ blockedLabel: config.labels.blocked,
6199
+ agentInProgressLabel: config.labels.agentInProgress
6200
+ });
6201
+ if (!selection.ok) {
6202
+ logger.step("warn", `No runnable issue: ${selection.reason}`);
6203
+ return {
6204
+ ok: false,
6205
+ reason: selection.reason,
6206
+ code: "no-runnable"
6207
+ };
6208
+ }
6209
+ const selected = candidates.find(
6210
+ (c) => c.number === selection.issue.number
6211
+ );
6212
+ if (!selected) {
6213
+ return yield* Effect8.die(
6214
+ new Error(
6215
+ `Selected issue #${selection.issue.number} not found in candidate list`
6216
+ )
5151
6217
  );
5152
6218
  }
5153
- return matches;
5154
- }) : candidates;
5155
- if (prdRef) {
5156
6219
  logger.step(
5157
6220
  "info",
5158
- `PRD-scoped selection: ${scopedCandidates.length} of ${candidates.length} candidate(s) match ${prdRef}`
6221
+ `Selected issue #${selected.number}: ${selected.title}`
5159
6222
  );
5160
- }
5161
- if (prdRef && scopedCandidates.length === 0) {
5162
- logger.step("warn", `No candidate issues found for ${prdRef}.`);
5163
- return {
5164
- ok: false,
5165
- reason: `No candidate issues found for ${prdRef}.`,
5166
- code: "no-candidates"
5167
- };
5168
- }
5169
- const candidateIssues = scopedCandidates.map(issueDataToCandidate);
5170
- const selection = selectIssue(candidateIssues, {
5171
- blockedLabel: config.labels.blocked,
5172
- agentInProgressLabel: config.labels.agentInProgress
6223
+ return { ok: true, issue: selected };
5173
6224
  });
5174
- if (!selection.ok) {
5175
- logger.step("warn", `No runnable issue: ${selection.reason}`);
5176
- return { ok: false, reason: selection.reason, code: "no-runnable" };
5177
- }
5178
- const selected = candidates.find((c) => c.number === selection.issue.number);
5179
- if (!selected) {
5180
- throw new Error(
5181
- `Selected issue #${selection.issue.number} not found in candidate list`
5182
- );
5183
- }
5184
- logger.step("info", `Selected issue #${selected.number}: ${selected.title}`);
5185
- return { ok: true, issue: selected };
5186
6225
  }
5187
6226
  function makeReconcileDeps(options) {
5188
6227
  const transitions = createIssueTransitions(
@@ -5215,17 +6254,35 @@ function makeReconcileDeps(options) {
5215
6254
  readyLabel: options.config.labels.readyForAgent
5216
6255
  };
5217
6256
  }
5218
- async function reconcileBlocked(options) {
5219
- const blocked = await options.issueProvider.listBlockedIssues();
5220
- if (blocked.length > 0) {
5221
- options.logger.step(
5222
- "info",
5223
- `Reconciling ${blocked.length} blocked issue(s)`
5224
- );
5225
- await reconcileBlockedIssues(blocked, makeReconcileDeps(options));
5226
- }
6257
+ function reconcileBlockedEffect(options) {
6258
+ return Effect8.gen(function* () {
6259
+ const blocked = yield* Effect8.tryPromise({
6260
+ try: () => options.issueProvider.listBlockedIssues(),
6261
+ catch: (e) => {
6262
+ if (e instanceof Error) {
6263
+ return new QueueProviderError(e.message);
6264
+ }
6265
+ throw e;
6266
+ }
6267
+ });
6268
+ if (blocked.length > 0) {
6269
+ options.logger.step(
6270
+ "info",
6271
+ `Reconciling ${blocked.length} blocked issue(s)`
6272
+ );
6273
+ yield* Effect8.tryPromise({
6274
+ try: () => reconcileBlockedIssues(blocked, makeReconcileDeps(options)),
6275
+ catch: (e) => {
6276
+ if (e instanceof Error) {
6277
+ return new QueueProviderError(e.message);
6278
+ }
6279
+ throw e;
6280
+ }
6281
+ });
6282
+ }
6283
+ });
5227
6284
  }
5228
- async function runOneQueueIssue(options) {
6285
+ function runOneQueueIssueEffect(options) {
5229
6286
  const {
5230
6287
  targetName,
5231
6288
  config,
@@ -5237,47 +6294,57 @@ async function runOneQueueIssue(options) {
5237
6294
  repoRoot: repoRoot2,
5238
6295
  prdRef
5239
6296
  } = options;
5240
- const outcome = await selectNextQueueIssue({
5241
- config,
5242
- issueProvider,
5243
- logger,
5244
- prdRef
5245
- });
5246
- if (!outcome.ok) {
5247
- return { selected: null, reason: outcome.reason, code: outcome.code };
5248
- }
5249
- const { issue: selected } = outcome;
5250
- const runResult = await runIssueCommand({
5251
- issueNumber: selected.number,
5252
- targetName,
5253
- config,
5254
- issueProvider,
5255
- prProvider,
5256
- executionProvider,
5257
- force,
5258
- logger,
5259
- repoRoot: repoRoot2
6297
+ return Effect8.gen(function* () {
6298
+ const outcome = yield* selectNextQueueIssue({
6299
+ config,
6300
+ issueProvider,
6301
+ logger,
6302
+ prdRef
6303
+ });
6304
+ if (!outcome.ok) {
6305
+ return {
6306
+ selected: null,
6307
+ reason: outcome.reason,
6308
+ code: outcome.code
6309
+ };
6310
+ }
6311
+ const { issue: selected } = outcome;
6312
+ const runResult = yield* Effect8.tryPromise({
6313
+ try: () => runIssueCommand({
6314
+ issueNumber: selected.number,
6315
+ targetName,
6316
+ config,
6317
+ issueProvider,
6318
+ prProvider,
6319
+ executionProvider,
6320
+ force,
6321
+ logger,
6322
+ repoRoot: repoRoot2
6323
+ }),
6324
+ catch: (e) => {
6325
+ throw e;
6326
+ }
6327
+ });
6328
+ logger.raw("Issue completed successfully:");
6329
+ logger.raw(` Branch: ${runResult.branchName}`);
6330
+ if (runResult.noOp) {
6331
+ logger.raw(" Status: no-op (closed without PR)");
6332
+ } else {
6333
+ logger.raw(` PR Title: ${runResult.prTitle}`);
6334
+ logger.raw(` PR Number: ${runResult.prNumber}`);
6335
+ logger.raw(` PR URL: ${runResult.prUrl}`);
6336
+ }
6337
+ logger.raw(` Target: ${runResult.target.name}`);
6338
+ return { selected, runResult };
5260
6339
  });
5261
- logger.raw("Issue completed successfully:");
5262
- logger.raw(` Branch: ${runResult.branchName}`);
5263
- if (runResult.noOp) {
5264
- logger.raw(" Status: no-op (closed without PR)");
5265
- } else {
5266
- logger.raw(` PR Title: ${runResult.prTitle}`);
5267
- logger.raw(` PR Number: ${runResult.prNumber}`);
5268
- logger.raw(` PR URL: ${runResult.prUrl}`);
5269
- }
5270
- logger.raw(` Target: ${runResult.target.name}`);
5271
- return { selected, runResult };
5272
- }
5273
- async function runQueue(options) {
5274
- return runOneQueueIssue(options);
5275
6340
  }
5276
- async function runQueueLoop(options) {
5277
- const results = [];
5278
- while (true) {
5279
- await reconcileBlocked(options);
5280
- const outcome = await runOneQueueIssue(options);
6341
+ function runQueue(options) {
6342
+ return runOneQueueIssueEffect(options);
6343
+ }
6344
+ function runQueueLoopEffect(options, results) {
6345
+ return Effect8.gen(function* () {
6346
+ yield* reconcileBlockedEffect(options);
6347
+ const outcome = yield* runOneQueueIssueEffect(options);
5281
6348
  if (outcome.selected === null) {
5282
6349
  return {
5283
6350
  drained: true,
@@ -5288,18 +6355,29 @@ async function runQueueLoop(options) {
5288
6355
  code: "drained"
5289
6356
  };
5290
6357
  }
5291
- results.push(outcome);
5292
- const processedIssue = await options.issueProvider.fetchIssue(
5293
- outcome.selected.number
5294
- );
6358
+ const newResults = [...results, outcome];
6359
+ const processedIssue = yield* Effect8.tryPromise({
6360
+ try: () => options.issueProvider.fetchIssue(outcome.selected.number),
6361
+ catch: (e) => {
6362
+ if (e instanceof Error) {
6363
+ return new QueueProviderError(e.message);
6364
+ }
6365
+ throw e;
6366
+ }
6367
+ });
5295
6368
  if (processedIssue.state === "closed") {
5296
- await reconcileBlocked(options);
6369
+ yield* reconcileBlockedEffect(options);
5297
6370
  }
5298
- }
6371
+ return yield* runQueueLoopEffect(options, newResults);
6372
+ });
6373
+ }
6374
+ function runQueueLoop(options) {
6375
+ return runQueueLoopEffect(options, []);
5299
6376
  }
5300
6377
 
5301
6378
  // commands/queue-run.ts
5302
6379
  async function runQueueCommand(options) {
6380
+ initializeEffectRuntime();
5303
6381
  const queueOptions = {
5304
6382
  targetName: options.targetName,
5305
6383
  config: options.config,
@@ -5312,9 +6390,9 @@ async function runQueueCommand(options) {
5312
6390
  prdRef: options.prdRef
5313
6391
  };
5314
6392
  if (!options.loop) {
5315
- return runQueue(queueOptions);
6393
+ return runEffectAndMapExit(runQueue(queueOptions));
5316
6394
  }
5317
- return runQueueLoop(queueOptions);
6395
+ return runEffectAndMapExit(runQueueLoop(queueOptions));
5318
6396
  }
5319
6397
 
5320
6398
  // commands/pr-create.ts
@@ -5684,16 +6762,18 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
5684
6762
  matchHeadCommit: pr.headRefOid
5685
6763
  });
5686
6764
  } else {
5687
- const coordinatorResult = await runMergeCoordinator({
5688
- prProvider,
5689
- logger: ownLogger,
5690
- prNumber: pr.number,
5691
- targetBranch: pr.baseRefName,
5692
- matchHeadCommit: pr.headRefOid,
5693
- checkWaitOptions,
5694
- method: options.method,
5695
- waitForTargetGreen: options.targetGreen
5696
- });
6765
+ const coordinatorResult = await runEffectAndMapExit(
6766
+ runMergeCoordinator({
6767
+ prProvider,
6768
+ logger: ownLogger,
6769
+ prNumber: pr.number,
6770
+ targetBranch: pr.baseRefName,
6771
+ matchHeadCommit: pr.headRefOid,
6772
+ checkWaitOptions,
6773
+ method: options.method,
6774
+ waitForTargetGreen: options.targetGreen
6775
+ })
6776
+ );
5697
6777
  if (coordinatorResult.stage !== "completed") {
5698
6778
  throw coordinatorResult.error;
5699
6779
  }
@@ -5716,105 +6796,21 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
5716
6796
  }
5717
6797
 
5718
6798
  // commands/init.ts
5719
- import { existsSync as existsSync8, statSync } from "fs";
6799
+ init_github_client();
6800
+ import { existsSync as existsSync9, statSync } from "fs";
5720
6801
  import {
5721
6802
  copyFile,
5722
6803
  mkdir as mkdir4,
5723
6804
  readFile as readFile4,
5724
6805
  readdir,
5725
6806
  rename,
5726
- writeFile
6807
+ writeFile as writeFile2
5727
6808
  } from "fs/promises";
5728
- import { createHash, randomUUID } from "crypto";
5729
- import path5 from "path";
6809
+ import { createHash as createHash2, randomUUID } from "crypto";
6810
+ import path7 from "path";
5730
6811
  import { execFile as execFile2 } from "child_process";
5731
6812
  import { promisify as promisify2 } from "util";
5732
6813
  import { confirm, isCancel, log, select, text } from "@clack/prompts";
5733
-
5734
- // providers/github-client.ts
5735
- import { Octokit } from "octokit";
5736
- var REMOTE_PATTERN = /github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/;
5737
- function resolveGitHubToken(env) {
5738
- const token = env.POURKIT_GITHUB_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN;
5739
- if (!token) {
5740
- throw new Error(
5741
- "GitHub token is required. Set POURKIT_GITHUB_TOKEN, GH_TOKEN, or GITHUB_TOKEN."
5742
- );
5743
- }
5744
- return token;
5745
- }
5746
- async function resolveGitHubRepository(options) {
5747
- if (options?.repository) {
5748
- const parts = options.repository.split("/");
5749
- if (parts.length !== 2 || !parts[0] || !parts[1]) {
5750
- throw new Error(
5751
- `Invalid repository format: "${options.repository}". Expected "owner/repo".`
5752
- );
5753
- }
5754
- return { owner: parts[0], repo: parts[1] };
5755
- }
5756
- const env = options?.env ?? process.env;
5757
- const envRepo = env.GITHUB_REPOSITORY;
5758
- if (envRepo) {
5759
- const parts = envRepo.split("/");
5760
- if (parts.length === 2 && parts[0] && parts[1]) {
5761
- return { owner: parts[0], repo: parts[1] };
5762
- }
5763
- throw new Error(
5764
- `Invalid repository format: "${envRepo}". Expected "owner/repo".`
5765
- );
5766
- }
5767
- const { execCapture: execCapture2 } = await Promise.resolve().then(() => (init_common(), common_exports));
5768
- const cwd = options?.cwd;
5769
- try {
5770
- const result = await execCapture2("git", ["remote", "get-url", "origin"], {
5771
- cwd
5772
- });
5773
- const remote = result.stdout.trim();
5774
- const match = remote.match(REMOTE_PATTERN);
5775
- if (match) {
5776
- return { owner: match[1], repo: match[2] };
5777
- }
5778
- } catch {
5779
- }
5780
- throw new Error(
5781
- "Could not resolve GitHub repository. Set GITHUB_REPOSITORY env var or ensure a valid 'origin' remote exists."
5782
- );
5783
- }
5784
- async function requireGitHubClient(options) {
5785
- const env = options?.env ?? process.env;
5786
- const token = resolveGitHubToken(env);
5787
- const repo = await resolveGitHubRepository(options);
5788
- const octokit = new Octokit({ auth: token });
5789
- return { octokit, ...repo };
5790
- }
5791
- async function tryCreateGitHubClient(options) {
5792
- const env = options?.env ?? process.env;
5793
- let token;
5794
- try {
5795
- token = resolveGitHubToken(env);
5796
- } catch {
5797
- return {
5798
- ok: false,
5799
- reason: "missing-token",
5800
- message: "GitHub token is not configured."
5801
- };
5802
- }
5803
- let repo;
5804
- try {
5805
- repo = await resolveGitHubRepository(options);
5806
- } catch (e) {
5807
- const message = e instanceof Error ? e.message : String(e);
5808
- if (message.includes("Invalid repository format")) {
5809
- return { ok: false, reason: "invalid-repository", message };
5810
- }
5811
- return { ok: false, reason: "missing-repository", message };
5812
- }
5813
- const octokit = new Octokit({ auth: token });
5814
- return { ok: true, client: { octokit, ...repo } };
5815
- }
5816
-
5817
- // commands/init.ts
5818
6814
  var execFileAsync2 = promisify2(execFile2);
5819
6815
  var NO_TOKEN_LABEL_PROVISIONING_WARNING = "Skipped GitHub label provisioning because no GitHub token was provided.";
5820
6816
  var ALLOWED_VERIFICATION_SCRIPTS = [
@@ -5951,7 +6947,7 @@ function generateConfigTemplate(options) {
5951
6947
  labels: maybeLabels
5952
6948
  } = options;
5953
6949
  const labels = maybeLabels ?? DEFAULT_RUNNER_LABELS;
5954
- const relPath = path5.relative(targetRoot, sourceRoot).replace(/\\/g, "/");
6950
+ const relPath = path7.relative(targetRoot, sourceRoot).replace(/\\/g, "/");
5955
6951
  const importPath = relPath || ".";
5956
6952
  const setupCommand = `${packageManager} install`;
5957
6953
  let setupSection;
@@ -6199,7 +7195,7 @@ _Avoid_: Alternative terms
6199
7195
  `;
6200
7196
  }
6201
7197
  async function generateManagedAgentInstructions(options) {
6202
- const sourcePath = path5.join(options.sourceRoot, "AGENTS.md");
7198
+ const sourcePath = path7.join(options.sourceRoot, "AGENTS.md");
6203
7199
  try {
6204
7200
  return await readFile4(sourcePath, "utf-8");
6205
7201
  } catch {
@@ -6229,7 +7225,7 @@ async function walkDir(dir) {
6229
7225
  const files = [];
6230
7226
  const entries = await readdir(dir, { withFileTypes: true });
6231
7227
  for (const entry of entries) {
6232
- const full = path5.join(dir, entry.name);
7228
+ const full = path7.join(dir, entry.name);
6233
7229
  if (entry.isDirectory()) {
6234
7230
  files.push(...await walkDir(full));
6235
7231
  } else {
@@ -6240,10 +7236,10 @@ async function walkDir(dir) {
6240
7236
  }
6241
7237
  async function computeFileChecksum(filePath) {
6242
7238
  const content = await readFile4(filePath);
6243
- return createHash("sha256").update(content).digest("hex");
7239
+ return createHash2("sha256").update(content).digest("hex");
6244
7240
  }
6245
7241
  function lockfileExists(root, name) {
6246
- return existsSync8(path5.join(root, name));
7242
+ return existsSync9(path7.join(root, name));
6247
7243
  }
6248
7244
  function detectPackageManager(root) {
6249
7245
  if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
@@ -6286,8 +7282,8 @@ async function discoverLocalSource(sourcePath) {
6286
7282
  }
6287
7283
  async function discoverReadme(root) {
6288
7284
  for (const name of ["README.md", "readme.md"]) {
6289
- const p = path5.join(root, name);
6290
- if (existsSync8(p)) {
7285
+ const p = path7.join(root, name);
7286
+ if (existsSync9(p)) {
6291
7287
  return p;
6292
7288
  }
6293
7289
  }
@@ -6296,25 +7292,25 @@ async function discoverReadme(root) {
6296
7292
  async function discoverAgentFiles(root) {
6297
7293
  const files = [];
6298
7294
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
6299
- const p = path5.join(root, name);
6300
- if (existsSync8(p)) {
7295
+ const p = path7.join(root, name);
7296
+ if (existsSync9(p)) {
6301
7297
  files.push(p);
6302
7298
  }
6303
7299
  }
6304
7300
  return files;
6305
7301
  }
6306
7302
  async function discoverMerlleState(root) {
6307
- const p = path5.join(root, ".pourkit", "state.json");
6308
- return existsSync8(p) ? p : null;
7303
+ const p = path7.join(root, ".pourkit", "state.json");
7304
+ return existsSync9(p) ? p : null;
6309
7305
  }
6310
7306
  async function discoverAgentSkills(root) {
6311
7307
  const dirs = [
6312
- path5.join(root, ".agents", "skills"),
6313
- path5.join(root, ".opencode", "skills")
7308
+ path7.join(root, ".agents", "skills"),
7309
+ path7.join(root, ".opencode", "skills")
6314
7310
  ];
6315
7311
  const found = [];
6316
7312
  for (const d of dirs) {
6317
- if (existsSync8(d)) {
7313
+ if (existsSync9(d)) {
6318
7314
  found.push(d);
6319
7315
  }
6320
7316
  }
@@ -6323,17 +7319,17 @@ async function discoverAgentSkills(root) {
6323
7319
  async function discoverRootDomainDocs(root) {
6324
7320
  const docs = [];
6325
7321
  for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
6326
- const p = path5.join(root, name);
6327
- if (existsSync8(p)) {
7322
+ const p = path7.join(root, name);
7323
+ if (existsSync9(p)) {
6328
7324
  docs.push(p);
6329
7325
  }
6330
7326
  }
6331
- const adrDir = path5.join(root, "docs", "adr");
6332
- if (existsSync8(adrDir)) {
7327
+ const adrDir = path7.join(root, "docs", "adr");
7328
+ if (existsSync9(adrDir)) {
6333
7329
  const entries = await readdir(adrDir, { withFileTypes: true });
6334
7330
  for (const entry of entries) {
6335
7331
  if (entry.isFile() && entry.name.endsWith(".md")) {
6336
- docs.push(path5.join(adrDir, entry.name));
7332
+ docs.push(path7.join(adrDir, entry.name));
6337
7333
  }
6338
7334
  }
6339
7335
  }
@@ -6421,7 +7417,7 @@ async function planInit(options) {
6421
7417
  for (const f of agentFiles) {
6422
7418
  if (sourceRoot) {
6423
7419
  const agentFileMode = options.conflictPolicy?.agentFile ?? "both";
6424
- const basename = path5.basename(f);
7420
+ const basename = path7.basename(f);
6425
7421
  if (agentFileMode === "skip" || agentFileMode === "agents" && basename !== "AGENTS.md" || agentFileMode === "claude" && basename !== "CLAUDE.md") {
6426
7422
  operations.push({
6427
7423
  kind: "skip",
@@ -6447,7 +7443,7 @@ async function planInit(options) {
6447
7443
  kind: "skip",
6448
7444
  path: f,
6449
7445
  ownership: "project-owned",
6450
- reason: `Existing agent file: ${path5.basename(f)}`,
7446
+ reason: `Existing agent file: ${path7.basename(f)}`,
6451
7447
  requiresConfirmation: false,
6452
7448
  destructive: false
6453
7449
  });
@@ -6470,15 +7466,15 @@ async function planInit(options) {
6470
7466
  if (s.includes(".opencode") && options.legacySkills) {
6471
7467
  const skillFiles = await walkDir(s);
6472
7468
  for (const file of skillFiles) {
6473
- const relPath = path5.relative(s, file);
6474
- const destPath = path5.join(targetRoot, ".agents", "skills", relPath);
6475
- if (!existsSync8(destPath)) {
7469
+ const relPath = path7.relative(s, file);
7470
+ const destPath = path7.join(targetRoot, ".agents", "skills", relPath);
7471
+ if (!existsSync9(destPath)) {
6476
7472
  operations.push({
6477
7473
  kind: "copy",
6478
7474
  sourcePath: file,
6479
7475
  path: destPath,
6480
7476
  ownership: "project-owned",
6481
- reason: `Migrate legacy skill: ${path5.join(".opencode/skills", relPath)}`,
7477
+ reason: `Migrate legacy skill: ${path7.join(".opencode/skills", relPath)}`,
6482
7478
  requiresConfirmation: false,
6483
7479
  destructive: false
6484
7480
  });
@@ -6535,7 +7531,7 @@ async function planInit(options) {
6535
7531
  });
6536
7532
  }
6537
7533
  if (sourceRoot) {
6538
- if (!existsSync8(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
7534
+ if (!existsSync9(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
6539
7535
  warnings.push(
6540
7536
  `--from-local path does not exist or is not a directory: ${sourceRoot}`
6541
7537
  );
@@ -6596,17 +7592,17 @@ async function planInit(options) {
6596
7592
  continue;
6597
7593
  }
6598
7594
  const targetDirName = ".agents/skills";
6599
- const targetPath = path5.join(targetRoot, targetDirName);
7595
+ const targetPath = path7.join(targetRoot, targetDirName);
6600
7596
  const skillFiles = await walkDir(s);
6601
7597
  for (const file of skillFiles) {
6602
- const relPath = path5.relative(s, file);
6603
- const destPath = path5.join(targetPath, relPath);
7598
+ const relPath = path7.relative(s, file);
7599
+ const destPath = path7.join(targetPath, relPath);
6604
7600
  if (plannedSkillDests.has(destPath)) {
6605
7601
  operations.push({
6606
7602
  kind: "skip",
6607
7603
  path: destPath,
6608
7604
  ownership: "project-owned",
6609
- reason: `Skill destination conflict, skipping source copy: ${path5.join(targetDirName, relPath)}`,
7605
+ reason: `Skill destination conflict, skipping source copy: ${path7.join(targetDirName, relPath)}`,
6610
7606
  requiresConfirmation: false,
6611
7607
  destructive: false,
6612
7608
  conflict: "destination already planned"
@@ -6619,7 +7615,7 @@ async function planInit(options) {
6619
7615
  sourcePath: file,
6620
7616
  path: destPath,
6621
7617
  ownership: "copied-customizable",
6622
- reason: `Copy skill: ${path5.join(targetDirName, relPath)}`,
7618
+ reason: `Copy skill: ${path7.join(targetDirName, relPath)}`,
6623
7619
  requiresConfirmation: false,
6624
7620
  destructive: false,
6625
7621
  checksum
@@ -6631,7 +7627,7 @@ async function planInit(options) {
6631
7627
  operations.push({
6632
7628
  kind: "copy",
6633
7629
  sourcePath: srcReadme,
6634
- path: path5.join(targetRoot, "README.md"),
7630
+ path: path7.join(targetRoot, "README.md"),
6635
7631
  ownership: "project-owned",
6636
7632
  reason: "Copy README.md from source",
6637
7633
  requiresConfirmation: false,
@@ -6642,8 +7638,8 @@ async function planInit(options) {
6642
7638
  const rootDocs = await discoverRootDomainDocs(targetRoot);
6643
7639
  const merleDestPaths = /* @__PURE__ */ new Set();
6644
7640
  for (const docPath of rootDocs) {
6645
- const relPath = path5.relative(targetRoot, docPath);
6646
- const destPath = path5.join(targetRoot, ".pourkit", relPath);
7641
+ const relPath = path7.relative(targetRoot, docPath);
7642
+ const destPath = path7.join(targetRoot, ".pourkit", relPath);
6647
7643
  merleDestPaths.add(destPath);
6648
7644
  if (docsMigration === "skip") {
6649
7645
  operations.push({
@@ -6654,7 +7650,7 @@ async function planInit(options) {
6654
7650
  requiresConfirmation: false,
6655
7651
  destructive: false
6656
7652
  });
6657
- } else if (existsSync8(destPath)) {
7653
+ } else if (existsSync9(destPath)) {
6658
7654
  operations.push({
6659
7655
  kind: "skip",
6660
7656
  path: destPath,
@@ -6690,7 +7686,7 @@ async function planInit(options) {
6690
7686
  let hasPackageJson = true;
6691
7687
  try {
6692
7688
  const pkgContent = await readFile4(
6693
- path5.join(targetRoot, "package.json"),
7689
+ path7.join(targetRoot, "package.json"),
6694
7690
  "utf-8"
6695
7691
  );
6696
7692
  const pkg = JSON.parse(pkgContent);
@@ -6711,8 +7707,8 @@ async function planInit(options) {
6711
7707
  } catch {
6712
7708
  }
6713
7709
  }
6714
- const contextPath = path5.join(targetRoot, ".pourkit", "CONTEXT.md");
6715
- if (!existsSync8(contextPath) && !merleDestPaths.has(contextPath)) {
7710
+ const contextPath = path7.join(targetRoot, ".pourkit", "CONTEXT.md");
7711
+ if (!existsSync9(contextPath) && !merleDestPaths.has(contextPath)) {
6716
7712
  operations.push({
6717
7713
  kind: "create",
6718
7714
  path: contextPath,
@@ -6723,14 +7719,14 @@ async function planInit(options) {
6723
7719
  content: generateContextScaffold()
6724
7720
  });
6725
7721
  }
6726
- const adrGitkeep = path5.join(
7722
+ const adrGitkeep = path7.join(
6727
7723
  targetRoot,
6728
7724
  ".pourkit",
6729
7725
  "docs",
6730
7726
  "adr",
6731
7727
  ".gitkeep"
6732
7728
  );
6733
- if (!existsSync8(adrGitkeep)) {
7729
+ if (!existsSync9(adrGitkeep)) {
6734
7730
  operations.push({
6735
7731
  kind: "create",
6736
7732
  path: adrGitkeep,
@@ -6740,16 +7736,16 @@ async function planInit(options) {
6740
7736
  destructive: false
6741
7737
  });
6742
7738
  }
6743
- const srcDocAgents = path5.join(sourceRoot, ".pourkit", "docs", "agents");
6744
- const tgtDocAgents = path5.join(targetRoot, ".pourkit", "docs", "agents");
6745
- if (existsSync8(srcDocAgents) && !existsSync8(tgtDocAgents)) {
7739
+ const srcDocAgents = path7.join(sourceRoot, ".pourkit", "docs", "agents");
7740
+ const tgtDocAgents = path7.join(targetRoot, ".pourkit", "docs", "agents");
7741
+ if (existsSync9(srcDocAgents) && !existsSync9(tgtDocAgents)) {
6746
7742
  const docFiles = await walkDir(srcDocAgents);
6747
7743
  for (const file of docFiles) {
6748
- const relPath = path5.relative(srcDocAgents, file);
7744
+ const relPath = path7.relative(srcDocAgents, file);
6749
7745
  if (relPath === "triage-labels.md") {
6750
7746
  operations.push({
6751
7747
  kind: "create",
6752
- path: path5.join(tgtDocAgents, relPath),
7748
+ path: path7.join(tgtDocAgents, relPath),
6753
7749
  ownership: "managed",
6754
7750
  reason: "Init triage labels doc",
6755
7751
  requiresConfirmation: false,
@@ -6764,7 +7760,7 @@ async function planInit(options) {
6764
7760
  operations.push({
6765
7761
  kind: "copy",
6766
7762
  sourcePath: file,
6767
- path: path5.join(tgtDocAgents, relPath),
7763
+ path: path7.join(tgtDocAgents, relPath),
6768
7764
  ownership: "managed",
6769
7765
  reason: `Copy agent doc: ${relPath}`,
6770
7766
  requiresConfirmation: false,
@@ -6773,17 +7769,17 @@ async function planInit(options) {
6773
7769
  });
6774
7770
  }
6775
7771
  }
6776
- const srcPrompts = path5.join(sourceRoot, ".pourkit", "prompts");
6777
- const tgtPrompts = path5.join(targetRoot, ".pourkit", "prompts");
6778
- if (existsSync8(srcPrompts) && !existsSync8(tgtPrompts)) {
7772
+ const srcPrompts = path7.join(sourceRoot, ".pourkit", "prompts");
7773
+ const tgtPrompts = path7.join(targetRoot, ".pourkit", "prompts");
7774
+ if (existsSync9(srcPrompts) && !existsSync9(tgtPrompts)) {
6779
7775
  const promptFiles = await walkDir(srcPrompts);
6780
7776
  for (const file of promptFiles) {
6781
- const relPath = path5.relative(srcPrompts, file);
7777
+ const relPath = path7.relative(srcPrompts, file);
6782
7778
  const checksum = await computeFileChecksum(file);
6783
7779
  operations.push({
6784
7780
  kind: "copy",
6785
7781
  sourcePath: file,
6786
- path: path5.join(tgtPrompts, relPath),
7782
+ path: path7.join(tgtPrompts, relPath),
6787
7783
  ownership: "managed",
6788
7784
  reason: `Copy prompt: ${relPath}`,
6789
7785
  requiresConfirmation: false,
@@ -6792,17 +7788,17 @@ async function planInit(options) {
6792
7788
  });
6793
7789
  }
6794
7790
  }
6795
- const srcSandboxDockerfile = path5.join(
7791
+ const srcSandboxDockerfile = path7.join(
6796
7792
  sourceRoot,
6797
7793
  ".sandcastle",
6798
7794
  "Dockerfile"
6799
7795
  );
6800
- const tgtSandboxDockerfile = path5.join(
7796
+ const tgtSandboxDockerfile = path7.join(
6801
7797
  targetRoot,
6802
7798
  ".sandcastle",
6803
7799
  "Dockerfile"
6804
7800
  );
6805
- if (existsSync8(tgtSandboxDockerfile)) {
7801
+ if (existsSync9(tgtSandboxDockerfile)) {
6806
7802
  operations.push({
6807
7803
  kind: "skip",
6808
7804
  path: tgtSandboxDockerfile,
@@ -6811,7 +7807,7 @@ async function planInit(options) {
6811
7807
  requiresConfirmation: false,
6812
7808
  destructive: false
6813
7809
  });
6814
- } else if (existsSync8(srcSandboxDockerfile)) {
7810
+ } else if (existsSync9(srcSandboxDockerfile)) {
6815
7811
  const checksum = await computeFileChecksum(srcSandboxDockerfile);
6816
7812
  operations.push({
6817
7813
  kind: "copy",
@@ -6824,8 +7820,8 @@ async function planInit(options) {
6824
7820
  checksum
6825
7821
  });
6826
7822
  }
6827
- const configTsPath = path5.join(targetRoot, "pourkit.config.ts");
6828
- if (!existsSync8(configTsPath)) {
7823
+ const configTsPath = path7.join(targetRoot, "pourkit.config.ts");
7824
+ if (!existsSync9(configTsPath)) {
6829
7825
  const verifyCommands = inferVerificationCommands(
6830
7826
  packageScripts,
6831
7827
  pm || "npm"
@@ -6862,10 +7858,10 @@ async function planInit(options) {
6862
7858
  const hasExistingAgents = operations.some(
6863
7859
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
6864
7860
  );
6865
- if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync8(path5.join(targetRoot, "AGENTS.md"))) {
7861
+ if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync9(path7.join(targetRoot, "AGENTS.md"))) {
6866
7862
  operations.push({
6867
7863
  kind: "create",
6868
- path: path5.join(targetRoot, "AGENTS.md"),
7864
+ path: path7.join(targetRoot, "AGENTS.md"),
6869
7865
  ownership: "managed",
6870
7866
  reason: "Init AGENTS.md with Pourkit managed block",
6871
7867
  requiresConfirmation: false,
@@ -6878,10 +7874,10 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
6878
7874
  const hasExistingClaude = operations.some(
6879
7875
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
6880
7876
  );
6881
- if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync8(path5.join(targetRoot, "CLAUDE.md"))) {
7877
+ if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync9(path7.join(targetRoot, "CLAUDE.md"))) {
6882
7878
  operations.push({
6883
7879
  kind: "create",
6884
- path: path5.join(targetRoot, "CLAUDE.md"),
7880
+ path: path7.join(targetRoot, "CLAUDE.md"),
6885
7881
  ownership: "managed",
6886
7882
  reason: "Init CLAUDE.md with Pourkit managed block",
6887
7883
  requiresConfirmation: false,
@@ -6891,9 +7887,9 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
6891
7887
  `
6892
7888
  });
6893
7889
  }
6894
- const gitignoreTarget = path5.join(targetRoot, ".gitignore");
7890
+ const gitignoreTarget = path7.join(targetRoot, ".gitignore");
6895
7891
  const gitignoreContent = generateGitignoreBlock();
6896
- if (!existsSync8(gitignoreTarget)) {
7892
+ if (!existsSync9(gitignoreTarget)) {
6897
7893
  operations.push({
6898
7894
  kind: "create",
6899
7895
  path: gitignoreTarget,
@@ -6916,8 +7912,8 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
6916
7912
  content: gitignoreContent
6917
7913
  });
6918
7914
  }
6919
- const openCodePath = path5.join(targetRoot, "opencode.json");
6920
- if (!existsSync8(openCodePath)) {
7915
+ const openCodePath = path7.join(targetRoot, "opencode.json");
7916
+ if (!existsSync9(openCodePath)) {
6921
7917
  operations.push({
6922
7918
  kind: "create",
6923
7919
  path: openCodePath,
@@ -6973,8 +7969,8 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
6973
7969
  });
6974
7970
  }
6975
7971
  }
6976
- const manifestPath = path5.join(targetRoot, ".pourkit", "manifest.json");
6977
- if (existsSync8(manifestPath)) {
7972
+ const manifestPath = path7.join(targetRoot, ".pourkit", "manifest.json");
7973
+ if (existsSync9(manifestPath)) {
6978
7974
  operations.push({
6979
7975
  kind: "skip",
6980
7976
  path: manifestPath,
@@ -7284,7 +8280,7 @@ async function promptForInitChoices(plan, current) {
7284
8280
  }
7285
8281
  async function writeFileAtomic(filePath, content) {
7286
8282
  const tmpPath = `${filePath}.tmp.${randomUUID()}`;
7287
- await writeFile(tmpPath, content, "utf-8");
8283
+ await writeFile2(tmpPath, content, "utf-8");
7288
8284
  await rename(tmpPath, filePath);
7289
8285
  }
7290
8286
  var MANAGED_BLOCK_BEGIN = "<!-- BEGIN POURKIT MANAGED BLOCK -->";
@@ -7293,8 +8289,8 @@ async function updateManagedBlock(filePath, content) {
7293
8289
  const blockContent = `${MANAGED_BLOCK_BEGIN}
7294
8290
  ${content}${MANAGED_BLOCK_END}
7295
8291
  `;
7296
- if (!existsSync8(filePath)) {
7297
- const dir = path5.dirname(filePath);
8292
+ if (!existsSync9(filePath)) {
8293
+ const dir = path7.dirname(filePath);
7298
8294
  await mkdir4(dir, { recursive: true });
7299
8295
  await writeFileAtomic(filePath, blockContent);
7300
8296
  return;
@@ -7312,17 +8308,17 @@ ${content}${MANAGED_BLOCK_END}
7312
8308
  }
7313
8309
  }
7314
8310
  async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
7315
- const manifestDir = path5.join(plan.targetRoot, ".pourkit");
7316
- const manifestPath = path5.join(manifestDir, "manifest.json");
8311
+ const manifestDir = path7.join(plan.targetRoot, ".pourkit");
8312
+ const manifestPath = path7.join(manifestDir, "manifest.json");
7317
8313
  const assets = {};
7318
8314
  for (const op of plan.operations) {
7319
8315
  if (!op.path) continue;
7320
8316
  if (op.kind !== "create" && op.kind !== "copy" && op.kind !== "update" && op.kind !== "move")
7321
8317
  continue;
7322
8318
  if (op.requiresConfirmation) continue;
7323
- const relPath = path5.relative(plan.targetRoot, op.path);
8319
+ const relPath = path7.relative(plan.targetRoot, op.path);
7324
8320
  if (relPath === ".pourkit/manifest.json") continue;
7325
- if (existsSync8(op.path)) {
8321
+ if (existsSync9(op.path)) {
7326
8322
  const sha256 = await computeFileChecksum(op.path);
7327
8323
  assets[relPath] = {
7328
8324
  ownership: op.ownership || "managed",
@@ -7367,11 +8363,11 @@ async function applyInitPlan(plan, options) {
7367
8363
  skipped++;
7368
8364
  continue;
7369
8365
  }
7370
- if (existsSync8(op.path) && !op.destructive) {
8366
+ if (existsSync9(op.path) && !op.destructive) {
7371
8367
  skipped++;
7372
8368
  continue;
7373
8369
  }
7374
- const dir = path5.dirname(op.path);
8370
+ const dir = path7.dirname(op.path);
7375
8371
  await mkdir4(dir, { recursive: true });
7376
8372
  await writeFileAtomic(op.path, op.content ?? "");
7377
8373
  applied++;
@@ -7382,7 +8378,7 @@ async function applyInitPlan(plan, options) {
7382
8378
  skipped++;
7383
8379
  continue;
7384
8380
  }
7385
- if (existsSync8(op.path)) {
8381
+ if (existsSync9(op.path)) {
7386
8382
  skipped++;
7387
8383
  continue;
7388
8384
  }
@@ -7390,7 +8386,7 @@ async function applyInitPlan(plan, options) {
7390
8386
  if (srcStat.isDirectory()) {
7391
8387
  skipped++;
7392
8388
  } else {
7393
- const dir = path5.dirname(op.path);
8389
+ const dir = path7.dirname(op.path);
7394
8390
  await mkdir4(dir, { recursive: true });
7395
8391
  await copyFile(op.sourcePath, op.path);
7396
8392
  applied++;
@@ -7411,11 +8407,11 @@ async function applyInitPlan(plan, options) {
7411
8407
  skipped++;
7412
8408
  continue;
7413
8409
  }
7414
- if (existsSync8(op.path)) {
8410
+ if (existsSync9(op.path)) {
7415
8411
  skipped++;
7416
8412
  continue;
7417
8413
  }
7418
- const dir = path5.dirname(op.path);
8414
+ const dir = path7.dirname(op.path);
7419
8415
  await mkdir4(dir, { recursive: true });
7420
8416
  await rename(op.sourcePath, op.path);
7421
8417
  applied++;
@@ -7546,13 +8542,13 @@ async function applyInitFromSource(options) {
7546
8542
  let manifestWritten = false;
7547
8543
  if (result.errors.length === 0) {
7548
8544
  const manifestSkipped = plan.operations.some(
7549
- (op) => op.kind === "skip" && op.path === path5.join(targetRoot, ".pourkit", "manifest.json")
8545
+ (op) => op.kind === "skip" && op.path === path7.join(targetRoot, ".pourkit", "manifest.json")
7550
8546
  );
7551
8547
  if (!manifestSkipped) {
7552
8548
  const agentFiles = [];
7553
8549
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
7554
- if (existsSync8(path5.join(targetRoot, name))) {
7555
- agentFiles.push(path5.join(targetRoot, name));
8550
+ if (existsSync9(path7.join(targetRoot, name))) {
8551
+ agentFiles.push(path7.join(targetRoot, name));
7556
8552
  }
7557
8553
  }
7558
8554
  const pm = detectPackageManager(targetRoot);
@@ -8366,290 +9362,9 @@ function formatChecks2(checks) {
8366
9362
  }
8367
9363
 
8368
9364
  // cli.ts
9365
+ init_github_client();
8369
9366
  init_common();
8370
-
8371
- // execution/sandcastle-execution.ts
8372
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "fs";
8373
- import { join as join12 } from "path";
8374
- import { createWorktree, opencode } from "@ai-hero/sandcastle";
8375
- import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
8376
-
8377
- // execution/execution-provider.ts
8378
- init_common();
8379
- import { writeFile as writeFile2 } from "fs/promises";
8380
- import { dirname as dirname5, join as join11 } from "path";
8381
- async function writeExecutionArtifacts(worktreePath, artifacts) {
8382
- for (const artifact of artifacts) {
8383
- const filePath = join11(worktreePath, artifact.path);
8384
- await ensureDir(dirname5(filePath));
8385
- await writeFile2(filePath, artifact.content, "utf-8");
8386
- }
8387
- }
8388
-
8389
- // execution/sandbox-image-build.ts
8390
- init_common();
8391
- import path7 from "path";
8392
-
8393
- // execution/sandbox-image.ts
8394
- import { createHash as createHash2 } from "crypto";
8395
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
8396
- import path6 from "path";
8397
- function sandboxImageName(repoRoot2) {
8398
- const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
8399
- const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
8400
- const baseName = sanitized || "local";
8401
- const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
8402
- if (!existsSync9(dockerfilePath)) {
8403
- return `sandcastle:${baseName}`;
8404
- }
8405
- const fingerprint = createHash2("sha256").update(readFileSync8(dockerfilePath)).digest("hex").slice(0, 8);
8406
- return `sandcastle:${baseName}-${fingerprint}`;
8407
- }
8408
-
8409
- // execution/sandbox-image-build.ts
8410
- async function ensureSandboxImageBuilt(repoRoot2, options) {
8411
- const imageName = sandboxImageName(repoRoot2);
8412
- const dockerfilePath = path7.join(repoRoot2, ".sandcastle", "Dockerfile");
8413
- if (!options?.force) {
8414
- try {
8415
- await execCapture("docker", ["image", "inspect", imageName]);
8416
- return;
8417
- } catch {
8418
- }
8419
- }
8420
- const buildArgs = ["build", "-t", imageName, "-f", dockerfilePath];
8421
- if (options?.force) {
8422
- buildArgs.push("--pull", "--no-cache");
8423
- }
8424
- buildArgs.push(repoRoot2);
8425
- await execCapture("docker", buildArgs);
8426
- }
8427
-
8428
- // execution/sandcastle-existing-worktree.ts
8429
- async function createSandboxFromExistingWorktree(options) {
8430
- const sandcastleEntryUrl = import.meta.resolve("@ai-hero/sandcastle");
8431
- const createSandboxUrl = new URL("./createSandbox.js", sandcastleEntryUrl);
8432
- const sandcastleCreateSandbox = await import(createSandboxUrl.href);
8433
- return sandcastleCreateSandbox.createSandboxFromWorktree(options);
8434
- }
8435
-
8436
- // execution/sandbox-options.ts
8437
- function buildSandboxOptions(repoRoot2, sandbox) {
8438
- const mounts = [];
8439
- if (sandbox.mounts !== void 0) {
8440
- mounts.push(...sandbox.mounts);
8441
- }
8442
- return {
8443
- imageName: sandboxImageName(repoRoot2),
8444
- ...mounts.length > 0 ? { mounts } : {},
8445
- ...sandbox.env !== void 0 ? { env: sandbox.env } : {},
8446
- ...sandbox.idleTimeoutSeconds !== void 0 ? { idleTimeoutSeconds: sandbox.idleTimeoutSeconds } : {}
8447
- };
8448
- }
8449
-
8450
- // execution/opencode-config.ts
8451
- function isSerenaEligibleStage(stage) {
8452
- return stage === "builder" || stage === "refactor";
8453
- }
8454
- function buildSerenaOpenCodeConfig(stage, serena) {
8455
- if (!serena?.available || !isSerenaEligibleStage(stage)) {
8456
- return void 0;
8457
- }
8458
- return {
8459
- mcp: {
8460
- serena: {
8461
- type: "remote",
8462
- url: serena.sandboxMcpUrl,
8463
- enabled: true
8464
- }
8465
- }
8466
- };
8467
- }
8468
-
8469
- // execution/sandcastle-execution.ts
8470
- var SandcastleExecutionProvider = class {
8471
- async createSession() {
8472
- return new SandcastleExecutionSession();
8473
- }
8474
- async execute(options) {
8475
- const session = await this.createSession();
8476
- try {
8477
- return await session.execute(options);
8478
- } finally {
8479
- await session.close();
8480
- }
8481
- }
8482
- };
8483
- var SandcastleExecutionSession = class {
8484
- sandboxHandle = null;
8485
- worktreeHandle = null;
8486
- async close() {
8487
- try {
8488
- await this.sandboxHandle?.close?.();
8489
- } finally {
8490
- this.sandboxHandle = null;
8491
- }
8492
- }
8493
- async execute(options) {
8494
- const {
8495
- stage,
8496
- iteration,
8497
- agent,
8498
- model,
8499
- prompt,
8500
- target,
8501
- repoRoot: repoRoot2,
8502
- branchName,
8503
- worktreePath,
8504
- baseRef,
8505
- sandbox,
8506
- autoApprove = false,
8507
- timeoutMs,
8508
- artifacts = [],
8509
- logger
8510
- } = options;
8511
- const stageLabel = iteration !== void 0 ? `${stage}:${iteration}` : stage;
8512
- logger.step(
8513
- "sandcastle",
8514
- `[${stageLabel}] running agent "${agent}" with model "${model}"`
8515
- );
8516
- try {
8517
- const env = {};
8518
- if (autoApprove) {
8519
- env.OPENCODE_AUTO_APPROVE = "true";
8520
- }
8521
- const serenaConfig = buildSerenaOpenCodeConfig(stage, options.serena);
8522
- if (serenaConfig) {
8523
- env.OPENCODE_CONFIG_CONTENT = JSON.stringify(serenaConfig);
8524
- }
8525
- const logPath = `${repoRoot2}/.pourkit/logs/${sanitizeBranch(branchName)}-${Date.now()}.log`;
8526
- await ensureSandboxImageBuilt(repoRoot2, { force: sandbox.forceRebuild });
8527
- try {
8528
- savePromptToFile(repoRoot2, stage, iteration, prompt);
8529
- } catch {
8530
- }
8531
- const agentProvider = opencode(model, { env, agent });
8532
- const sandboxOptions = buildSandboxOptions(repoRoot2, sandbox);
8533
- const sandboxProvider = resolveSandboxProvider(sandbox.provider, docker);
8534
- const activeSandbox = await this.getOrCreateSandbox({
8535
- repoRoot: repoRoot2,
8536
- branchName,
8537
- baseBranch: baseRef ?? target.baseBranch,
8538
- worktreePath,
8539
- sandboxProvider: sandboxProvider(sandboxOptions),
8540
- setupCommands: target.setupCommands ?? [],
8541
- copyToWorktree: sandbox.copyToWorktree ?? []
8542
- });
8543
- await writeExecutionArtifacts(activeSandbox.worktreePath, artifacts);
8544
- const result = await activeSandbox.run({
8545
- agent: agentProvider,
8546
- prompt,
8547
- maxIterations: 1,
8548
- name: stageLabel,
8549
- logging: {
8550
- type: "file",
8551
- path: logPath,
8552
- onAgentStreamEvent: (event) => {
8553
- if (event.type === "text") {
8554
- logger.raw(event.message);
8555
- } else if (event.type === "toolCall") {
8556
- logger.raw(`${event.name}(${event.formattedArgs})`);
8557
- }
8558
- }
8559
- },
8560
- completionSignal: "<promise>COMPLETE</promise>",
8561
- ...timeoutMs ? { signal: AbortSignal.timeout(timeoutMs) } : {},
8562
- ...sandboxOptions.idleTimeoutSeconds ? { idleTimeoutSeconds: sandboxOptions.idleTimeoutSeconds } : {}
8563
- });
8564
- const commits = result.commits.map((c) => c.sha);
8565
- const resultBranch = result.branch ?? activeSandbox.branch ?? branchName;
8566
- logger.kv("SANDBOX_SUCCESS", "true");
8567
- logger.kv("COMMITS_CREATED", String(commits.length));
8568
- logger.kv("WORKTREE_BRANCH", resultBranch);
8569
- if (result.logFilePath) {
8570
- logger.kv("LOG_FILE", result.logFilePath);
8571
- }
8572
- return {
8573
- success: true,
8574
- branch: resultBranch,
8575
- worktreePath: activeSandbox.worktreePath,
8576
- commits,
8577
- logPath
8578
- };
8579
- } catch (error) {
8580
- logger.step(
8581
- "error",
8582
- `Sandcastle failed: ${error instanceof Error ? error.message : String(error)}`
8583
- );
8584
- return {
8585
- success: false,
8586
- branch: "",
8587
- worktreePath: "",
8588
- commits: [],
8589
- logPath: null,
8590
- error: error instanceof Error ? error.message : String(error)
8591
- };
8592
- }
8593
- }
8594
- async getOrCreateSandbox(options) {
8595
- if (this.sandboxHandle) {
8596
- return this.sandboxHandle;
8597
- }
8598
- const hooks = {
8599
- sandbox: {
8600
- onSandboxReady: options.setupCommands.map((command) => ({
8601
- command: command.command
8602
- }))
8603
- }
8604
- };
8605
- if (options.worktreePath) {
8606
- this.sandboxHandle = await createSandboxFromExistingWorktree({
8607
- branch: options.branchName,
8608
- hostRepoDir: options.repoRoot,
8609
- worktreePath: options.worktreePath,
8610
- sandbox: options.sandboxProvider,
8611
- ...options.copyToWorktree.length > 0 ? { copyToWorktree: options.copyToWorktree } : {},
8612
- hooks
8613
- });
8614
- return this.sandboxHandle;
8615
- }
8616
- this.worktreeHandle = await createWorktree({
8617
- cwd: options.repoRoot,
8618
- branchStrategy: {
8619
- type: "branch",
8620
- branch: options.branchName,
8621
- baseBranch: options.baseBranch
8622
- },
8623
- ...options.copyToWorktree.length > 0 ? { copyToWorktree: options.copyToWorktree } : {}
8624
- });
8625
- this.sandboxHandle = await this.worktreeHandle.createSandbox({
8626
- sandbox: options.sandboxProvider,
8627
- ...options.copyToWorktree.length > 0 ? { copyToWorktree: options.copyToWorktree } : {},
8628
- hooks
8629
- });
8630
- return this.sandboxHandle;
8631
- }
8632
- };
8633
- function resolveSandboxProvider(provider, dockerFactory) {
8634
- if (provider === "docker") {
8635
- return dockerFactory;
8636
- }
8637
- throw new Error(`Unsupported sandbox provider: ${provider}`);
8638
- }
8639
- function sanitizeBranch(branchName) {
8640
- return branchName.replace(/[^A-Za-z0-9._-]/g, "-");
8641
- }
8642
- function savePromptToFile(repoRoot2, stage, iteration, prompt) {
8643
- const promptsDir = join12(repoRoot2, ".pourkit", ".tmp", "prompts");
8644
- mkdirSync6(promptsDir, { recursive: true });
8645
- const timestamp2 = Date.now();
8646
- const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
8647
- const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
8648
- const filePath = join12(promptsDir, filename);
8649
- writeFileSync4(filePath, prompt, "utf-8");
8650
- }
8651
-
8652
- // cli.ts
9367
+ init_sandcastle_execution();
8653
9368
  function normalizePrdRef(ref) {
8654
9369
  const normalized = ref.trim().toUpperCase();
8655
9370
  if (!/^PRD-\d+$/.test(normalized)) {
@@ -9076,11 +9791,11 @@ function createCliProgram(version) {
9076
9791
  return program;
9077
9792
  }
9078
9793
  async function resolveCliVersion() {
9079
- if (isPackageVersion("0.0.0-next-20260531213458")) {
9080
- return "0.0.0-next-20260531213458";
9794
+ if (isPackageVersion("0.0.0-next-20260601224754")) {
9795
+ return "0.0.0-next-20260601224754";
9081
9796
  }
9082
- if (isReleaseVersion("0.0.0-next-20260531213458")) {
9083
- return "0.0.0-next-20260531213458";
9797
+ if (isReleaseVersion("0.0.0-next-20260601224754")) {
9798
+ return "0.0.0-next-20260601224754";
9084
9799
  }
9085
9800
  try {
9086
9801
  const root = repoRoot();
@@ -9106,6 +9821,7 @@ async function resolveCliVersion() {
9106
9821
  return DEVELOPMENT_VERSION;
9107
9822
  }
9108
9823
  async function main(argv = process.argv.slice(2)) {
9824
+ initializeEffectRuntime();
9109
9825
  const version = await resolveCliVersion();
9110
9826
  const program = createCliProgram(version);
9111
9827
  if (argv.length === 0) {