@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 +1984 -1268
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +1701 -810
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
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
|
|
1001
|
-
import { join as
|
|
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
|
|
1324
|
-
"
|
|
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
|
|
1499
|
-
|
|
1972
|
+
return Effect2.promise(() => refreshStaleIssueBranch(options)).pipe(
|
|
1973
|
+
Effect2.flatMap(
|
|
1500
1974
|
(result) => {
|
|
1501
1975
|
switch (result.status) {
|
|
1502
1976
|
case "refreshed":
|
|
1503
|
-
return
|
|
1977
|
+
return Effect2.succeed({ status: "refreshed" });
|
|
1504
1978
|
case "skipped-current":
|
|
1505
|
-
return
|
|
1979
|
+
return Effect2.succeed({ status: "skipped-current" });
|
|
1506
1980
|
case "conflicted":
|
|
1507
|
-
return
|
|
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
|
|
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
|
-
|
|
1530
|
-
onSuccess: (success) =>
|
|
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) =>
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
2689
|
+
import { join as join9 } from "path";
|
|
2217
2690
|
import { readFile } from "fs/promises";
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
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
|
-
|
|
2355
|
-
|
|
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 =
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
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
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
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
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
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
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
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
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
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
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
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
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
);
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
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
|
-
|
|
2450
|
-
|
|
2451
|
-
const dir =
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
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: "
|
|
2681
|
-
merged:
|
|
3484
|
+
stage: "merge",
|
|
3485
|
+
merged: false,
|
|
2682
3486
|
error: error instanceof Error ? error : new Error(String(error))
|
|
2683
3487
|
};
|
|
2684
3488
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
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
|
|
2692
|
-
mkdirSync as
|
|
2693
|
-
readFileSync as
|
|
3530
|
+
existsSync as existsSync7,
|
|
3531
|
+
mkdirSync as mkdirSync6,
|
|
3532
|
+
readFileSync as readFileSync7,
|
|
2694
3533
|
readdirSync,
|
|
2695
3534
|
rmSync as rmSync2,
|
|
2696
|
-
writeFileSync as
|
|
3535
|
+
writeFileSync as writeFileSync4
|
|
2697
3536
|
} from "fs";
|
|
2698
|
-
import { dirname as
|
|
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 (!
|
|
3620
|
+
if (!existsSync7(artifactPath)) {
|
|
2781
3621
|
throw new RefactorArtifactValidationError(
|
|
2782
3622
|
`Refactor artifact missing at ${artifactPath}`
|
|
2783
3623
|
);
|
|
2784
3624
|
}
|
|
2785
|
-
const content =
|
|
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 !== "-" &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3777
|
+
return Effect7.fail(
|
|
3778
|
+
new ReviewerFailure({ message: "No reviewer config found" })
|
|
3779
|
+
);
|
|
2929
3780
|
}
|
|
2930
|
-
const artifactPathInWorktree =
|
|
3781
|
+
const artifactPathInWorktree = join11(
|
|
2931
3782
|
".pourkit",
|
|
2932
3783
|
".tmp",
|
|
2933
3784
|
"reviewers",
|
|
2934
3785
|
`iteration-${iteration ?? 1}.md`
|
|
2935
3786
|
);
|
|
2936
|
-
const artifactPath =
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
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
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
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
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
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
|
-
|
|
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
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
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
|
-
|
|
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
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
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
|
-
|
|
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
|
|
3119
|
-
|
|
3120
|
-
|
|
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
|
-
|
|
3135
|
-
"prompts",
|
|
3136
|
-
`reviewer-${criterion}.snippet.md`
|
|
4014
|
+
promptTemplate
|
|
3137
4015
|
);
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
}
|
|
3141
|
-
return
|
|
3142
|
-
|
|
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
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
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
|
|
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
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4107
|
+
return Effect7.die(new Error("No reviewer config found"));
|
|
3203
4108
|
}
|
|
3204
4109
|
const refactorer = strategy.review.refactor;
|
|
3205
4110
|
if (!refactorer) {
|
|
3206
|
-
|
|
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 =
|
|
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
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
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
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
if (
|
|
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
|
-
"
|
|
3387
|
-
|
|
4205
|
+
"info",
|
|
4206
|
+
"PASS_WITH_NOTES refactor attempts exhausted, treating as PASS"
|
|
3388
4207
|
);
|
|
3389
4208
|
return {
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
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
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
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
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
};
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
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
|
-
|
|
3434
|
-
|
|
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
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
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 (
|
|
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
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
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 (!
|
|
3936
|
-
throw new
|
|
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 =
|
|
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
|
|
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
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
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
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
6138
|
+
function selectNextQueueIssue(options) {
|
|
5121
6139
|
const { config, issueProvider, logger, prdRef } = options;
|
|
5122
6140
|
logger.step("info", "Loading candidate issues from provider");
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
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
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
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
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
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
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
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
|
|
5292
|
-
const processedIssue =
|
|
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
|
-
|
|
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
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
7239
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
6244
7240
|
}
|
|
6245
7241
|
function lockfileExists(root, name) {
|
|
6246
|
-
return
|
|
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 =
|
|
6290
|
-
if (
|
|
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 =
|
|
6300
|
-
if (
|
|
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 =
|
|
6308
|
-
return
|
|
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
|
-
|
|
6313
|
-
|
|
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 (
|
|
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 =
|
|
6327
|
-
if (
|
|
7322
|
+
const p = path7.join(root, name);
|
|
7323
|
+
if (existsSync9(p)) {
|
|
6328
7324
|
docs.push(p);
|
|
6329
7325
|
}
|
|
6330
7326
|
}
|
|
6331
|
-
const adrDir =
|
|
6332
|
-
if (
|
|
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(
|
|
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 =
|
|
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: ${
|
|
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 =
|
|
6474
|
-
const destPath =
|
|
6475
|
-
if (!
|
|
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: ${
|
|
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 (!
|
|
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 =
|
|
7595
|
+
const targetPath = path7.join(targetRoot, targetDirName);
|
|
6600
7596
|
const skillFiles = await walkDir(s);
|
|
6601
7597
|
for (const file of skillFiles) {
|
|
6602
|
-
const relPath =
|
|
6603
|
-
const destPath =
|
|
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: ${
|
|
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: ${
|
|
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:
|
|
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 =
|
|
6646
|
-
const destPath =
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
6715
|
-
if (!
|
|
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 =
|
|
7722
|
+
const adrGitkeep = path7.join(
|
|
6727
7723
|
targetRoot,
|
|
6728
7724
|
".pourkit",
|
|
6729
7725
|
"docs",
|
|
6730
7726
|
"adr",
|
|
6731
7727
|
".gitkeep"
|
|
6732
7728
|
);
|
|
6733
|
-
if (!
|
|
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 =
|
|
6744
|
-
const tgtDocAgents =
|
|
6745
|
-
if (
|
|
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 =
|
|
7744
|
+
const relPath = path7.relative(srcDocAgents, file);
|
|
6749
7745
|
if (relPath === "triage-labels.md") {
|
|
6750
7746
|
operations.push({
|
|
6751
7747
|
kind: "create",
|
|
6752
|
-
path:
|
|
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:
|
|
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 =
|
|
6777
|
-
const tgtPrompts =
|
|
6778
|
-
if (
|
|
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 =
|
|
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:
|
|
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 =
|
|
7791
|
+
const srcSandboxDockerfile = path7.join(
|
|
6796
7792
|
sourceRoot,
|
|
6797
7793
|
".sandcastle",
|
|
6798
7794
|
"Dockerfile"
|
|
6799
7795
|
);
|
|
6800
|
-
const tgtSandboxDockerfile =
|
|
7796
|
+
const tgtSandboxDockerfile = path7.join(
|
|
6801
7797
|
targetRoot,
|
|
6802
7798
|
".sandcastle",
|
|
6803
7799
|
"Dockerfile"
|
|
6804
7800
|
);
|
|
6805
|
-
if (
|
|
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 (
|
|
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 =
|
|
6828
|
-
if (!
|
|
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 && !
|
|
7861
|
+
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync9(path7.join(targetRoot, "AGENTS.md"))) {
|
|
6866
7862
|
operations.push({
|
|
6867
7863
|
kind: "create",
|
|
6868
|
-
path:
|
|
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 && !
|
|
7877
|
+
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync9(path7.join(targetRoot, "CLAUDE.md"))) {
|
|
6882
7878
|
operations.push({
|
|
6883
7879
|
kind: "create",
|
|
6884
|
-
path:
|
|
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 =
|
|
7890
|
+
const gitignoreTarget = path7.join(targetRoot, ".gitignore");
|
|
6895
7891
|
const gitignoreContent = generateGitignoreBlock();
|
|
6896
|
-
if (!
|
|
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 =
|
|
6920
|
-
if (!
|
|
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 =
|
|
6977
|
-
if (
|
|
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
|
|
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 (!
|
|
7297
|
-
const dir =
|
|
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 =
|
|
7316
|
-
const manifestPath =
|
|
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 =
|
|
8319
|
+
const relPath = path7.relative(plan.targetRoot, op.path);
|
|
7324
8320
|
if (relPath === ".pourkit/manifest.json") continue;
|
|
7325
|
-
if (
|
|
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 (
|
|
8366
|
+
if (existsSync9(op.path) && !op.destructive) {
|
|
7371
8367
|
skipped++;
|
|
7372
8368
|
continue;
|
|
7373
8369
|
}
|
|
7374
|
-
const dir =
|
|
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 (
|
|
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 =
|
|
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 (
|
|
8410
|
+
if (existsSync9(op.path)) {
|
|
7415
8411
|
skipped++;
|
|
7416
8412
|
continue;
|
|
7417
8413
|
}
|
|
7418
|
-
const dir =
|
|
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 ===
|
|
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 (
|
|
7555
|
-
agentFiles.push(
|
|
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-
|
|
9080
|
-
return "0.0.0-next-
|
|
9794
|
+
if (isPackageVersion("0.0.0-next-20260601224754")) {
|
|
9795
|
+
return "0.0.0-next-20260601224754";
|
|
9081
9796
|
}
|
|
9082
|
-
if (isReleaseVersion("0.0.0-next-
|
|
9083
|
-
return "0.0.0-next-
|
|
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) {
|