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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -301,17 +301,17 @@ function parseWorktreeListPorcelain(text2, branch) {
301
301
  const entries = text2.trim().split("\n\n");
302
302
  for (const entry of entries) {
303
303
  const lines = entry.trim().split("\n");
304
- let path7 = "";
304
+ let path9 = "";
305
305
  let entryBranch = "";
306
306
  for (const line of lines) {
307
307
  if (line.startsWith("worktree ")) {
308
- path7 = line.slice("worktree ".length);
308
+ path9 = line.slice("worktree ".length);
309
309
  } else if (line.startsWith("branch refs/heads/")) {
310
310
  entryBranch = line.slice("branch refs/heads/".length);
311
311
  }
312
312
  }
313
- if (entryBranch === branch && path7) {
314
- return path7;
313
+ if (entryBranch === branch && path9) {
314
+ return path9;
315
315
  }
316
316
  }
317
317
  return null;
@@ -334,7 +334,7 @@ var init_common = __esm({
334
334
  });
335
335
 
336
336
  // cli.ts
337
- import path6 from "path";
337
+ import path8 from "path";
338
338
  import { realpathSync } from "fs";
339
339
  import { pathToFileURL } from "url";
340
340
  import { Command, Option, CommanderError } from "commander";
@@ -368,6 +368,13 @@ var VerificationCommandSchema = z.object({
368
368
  var QueueConfigSchema = z.object({
369
369
  loop: z.boolean().optional()
370
370
  }).strict();
371
+ var FailureResolutionConfigSchema = z.object({
372
+ agent: NonEmptyString,
373
+ model: NonEmptyString,
374
+ promptTemplate: NonEmptyString,
375
+ maxAttemptsPerFailure: z.number().int().positive(),
376
+ failureLimits: z.record(z.string(), z.number().int().positive()).optional()
377
+ }).strict();
371
378
  var ReviewRefactorLoopStrategySchema = z.object({
372
379
  type: z.literal("review-refactor-loop"),
373
380
  implement: z.object({
@@ -379,6 +386,7 @@ var ReviewRefactorLoopStrategySchema = z.object({
379
386
  promptTemplate: NonEmptyString,
380
387
  maxAttempts: z.number().int().positive()
381
388
  }).strict().optional(),
389
+ failureResolution: FailureResolutionConfigSchema,
382
390
  review: z.object({
383
391
  reviewer: ReviewerConfigSchema,
384
392
  refactor: StageAgentConfigSchema,
@@ -398,6 +406,10 @@ var ReviewRefactorLoopStrategySchema = z.object({
398
406
  maxAttempts: z.number().int().positive()
399
407
  }).strict()
400
408
  }).strict();
409
+ var TargetSerenaConfigSchema = z.object({
410
+ enabled: z.boolean().optional(),
411
+ required: z.boolean().optional()
412
+ }).strict();
401
413
  var TargetSchema = z.object({
402
414
  name: NonEmptyString,
403
415
  baseBranch: z.preprocess(
@@ -414,6 +426,7 @@ var TargetSchema = z.object({
414
426
  z.boolean().default(true)
415
427
  ),
416
428
  queue: QueueConfigSchema.optional(),
429
+ serena: TargetSerenaConfigSchema.optional(),
417
430
  strategy: ReviewRefactorLoopStrategySchema
418
431
  }).strict();
419
432
  var LabelsSchema = z.object({
@@ -453,12 +466,21 @@ var CleanupConfigSchema = z.object({
453
466
  worktreeRetentionDays: z.number().int().positive().default(14),
454
467
  logRetentionDays: z.number().int().positive().default(30)
455
468
  }).strict();
469
+ var SerenaConfigSchema = z.object({
470
+ enabled: z.boolean().default(false),
471
+ required: z.boolean().default(false),
472
+ mcpUrl: NonEmptyString.default("http://localhost:9121/mcp"),
473
+ sandboxMcpUrl: NonEmptyString.default("http://localhost:9121/mcp"),
474
+ dataDir: z.string().default(".pourkit/serena/"),
475
+ autoStart: z.boolean().default(false)
476
+ }).strict();
456
477
  var PourkitConfigSchema = z.object({
457
478
  targets: z.array(TargetSchema).min(1),
458
479
  labels: LabelsSchema,
459
480
  sandbox: SandboxSchema,
460
481
  checks: ChecksSchema,
461
- cleanup: CleanupConfigSchema.optional()
482
+ cleanup: CleanupConfigSchema.optional(),
483
+ serena: SerenaConfigSchema.default({})
462
484
  }).strict();
463
485
  var removedFieldReplacements = {
464
486
  "config.implementor": "targets[].strategy.implement.builder",
@@ -523,10 +545,10 @@ function checkRemovedFields(raw) {
523
545
  }
524
546
  }
525
547
  }
526
- function formatZodPath(path7) {
527
- if (path7.length === 0) return "";
548
+ function formatZodPath(path9) {
549
+ if (path9.length === 0) return "";
528
550
  let result = "";
529
- for (const segment of path7) {
551
+ for (const segment of path9) {
530
552
  if (typeof segment === "number") {
531
553
  result += `[${segment}]`;
532
554
  } else {
@@ -537,8 +559,8 @@ function formatZodPath(path7) {
537
559
  }
538
560
  function formatFirstZodError(err) {
539
561
  const issue = err.issues[0];
540
- const path7 = formatZodPath(issue.path);
541
- if (path7 === "targets" && (issue.code === "too_small" || issue.code === "invalid_type")) {
562
+ const path9 = formatZodPath(issue.path);
563
+ if (path9 === "targets" && (issue.code === "too_small" || issue.code === "invalid_type")) {
542
564
  return "Config must have at least one target";
543
565
  }
544
566
  if (issue.path.length >= 3 && issue.path[0] === "targets" && typeof issue.path[1] === "number" && issue.path[2] === "name" && issue.code === z.ZodIssueCode.too_small) {
@@ -547,37 +569,37 @@ function formatFirstZodError(err) {
547
569
  switch (issue.code) {
548
570
  case z.ZodIssueCode.invalid_type: {
549
571
  if (issue.expected === "object") {
550
- return path7 ? `${path7} must be an object` : "Config must be an object";
572
+ return path9 ? `${path9} must be an object` : "Config must be an object";
551
573
  }
552
574
  if (issue.expected === "integer") {
553
- return `${path7} must be an integer`;
575
+ return `${path9} must be an integer`;
554
576
  }
555
577
  if (issue.expected === "string") {
556
- return `${path7} must be a string`;
578
+ return `${path9} must be a string`;
557
579
  }
558
580
  if (issue.expected === "number") {
559
- return `${path7} must be a number`;
581
+ return `${path9} must be a number`;
560
582
  }
561
583
  return issue.message;
562
584
  }
563
585
  case z.ZodIssueCode.too_small:
564
586
  if (issue.type === "string" && issue.minimum === 1) {
565
- return `${path7} must be a non-empty string`;
587
+ return `${path9} must be a non-empty string`;
566
588
  }
567
589
  if (issue.type === "array" && issue.minimum === 1) {
568
- return `${path7} must not be empty`;
590
+ return `${path9} must not be empty`;
569
591
  }
570
592
  if (issue.type === "number") {
571
- return `${path7} must be a positive number`;
593
+ return `${path9} must be a positive number`;
572
594
  }
573
595
  return issue.message;
574
596
  case z.ZodIssueCode.invalid_literal:
575
- return `${path7} must be ${issue.expected}`;
597
+ return `${path9} must be ${issue.expected}`;
576
598
  case z.ZodIssueCode.unrecognized_keys:
577
- const keyPath = path7 ? `${path7}.${issue.keys[0]}` : issue.keys[0];
599
+ const keyPath = path9 ? `${path9}.${issue.keys[0]}` : issue.keys[0];
578
600
  return `${keyPath} is not supported`;
579
601
  case z.ZodIssueCode.custom:
580
- return path7 ? `${path7} ${issue.message}` : issue.message;
602
+ return path9 ? `${path9} ${issue.message}` : issue.message;
581
603
  default:
582
604
  return issue.message;
583
605
  }
@@ -604,9 +626,19 @@ function parseConfig(raw) {
604
626
  "setupCommands",
605
627
  "autoMerge",
606
628
  "queue",
629
+ "serena",
607
630
  "strategy"
608
631
  ]);
609
632
  }
633
+ for (let i = 0; i < rawTargets.length; i++) {
634
+ const t = rawTargets[i];
635
+ const strategy = t?.strategy;
636
+ if (strategy && typeof strategy === "object" && "conflictResolution" in strategy) {
637
+ throw new Error(
638
+ `targets[${i}].strategy.conflictResolution has been removed; use targets[${i}].strategy.failureResolution`
639
+ );
640
+ }
641
+ }
610
642
  if (config.sandbox && typeof config.sandbox === "object") {
611
643
  assertKnownKeys(config.sandbox, "sandbox", [
612
644
  "provider",
@@ -637,17 +669,17 @@ function parseConfig(raw) {
637
669
  setupCommands,
638
670
  autoMerge: t.autoMerge,
639
671
  queue: t.queue,
672
+ serena: t.serena,
640
673
  strategy: {
641
674
  type: "review-refactor-loop",
642
675
  implement: { builder: t.strategy.implement.builder },
643
- ...t.strategy.conflictResolution ? {
644
- conflictResolution: {
645
- agent: t.strategy.conflictResolution.agent,
646
- model: t.strategy.conflictResolution.model,
647
- promptTemplate: t.strategy.conflictResolution.promptTemplate,
648
- maxAttempts: t.strategy.conflictResolution.maxAttempts
649
- }
650
- } : {},
676
+ failureResolution: {
677
+ agent: t.strategy.failureResolution.agent,
678
+ model: t.strategy.failureResolution.model,
679
+ promptTemplate: t.strategy.failureResolution.promptTemplate,
680
+ maxAttemptsPerFailure: t.strategy.failureResolution.maxAttemptsPerFailure,
681
+ failureLimits: t.strategy.failureResolution.failureLimits
682
+ },
651
683
  review: {
652
684
  reviewer: t.strategy.review.reviewer,
653
685
  refactor: t.strategy.review.refactor,
@@ -662,6 +694,19 @@ function parseConfig(raw) {
662
694
  }
663
695
  };
664
696
  });
697
+ const serena = {
698
+ ...data.serena,
699
+ mcpUrl: process.env.POURKIT_SERENA_MCP_URL ?? data.serena.mcpUrl,
700
+ sandboxMcpUrl: process.env.POURKIT_SERENA_SANDBOX_MCP_URL ?? data.serena.sandboxMcpUrl
701
+ };
702
+ if (serena.mcpUrl.trim() === "") {
703
+ throw new Error("POURKIT_SERENA_MCP_URL must be a non-empty string");
704
+ }
705
+ if (serena.sandboxMcpUrl.trim() === "") {
706
+ throw new Error(
707
+ "POURKIT_SERENA_SANDBOX_MCP_URL must be a non-empty string"
708
+ );
709
+ }
665
710
  return {
666
711
  targets,
667
712
  labels: data.labels,
@@ -680,6 +725,7 @@ function parseConfig(raw) {
680
725
  pollIntervalSeconds: data.checks.pollIntervalSeconds ?? 15,
681
726
  issueListLimit: data.checks.issueListLimit ?? 50
682
727
  },
728
+ serena,
683
729
  cleanup: {
684
730
  enabled: data.cleanup?.enabled ?? true,
685
731
  worktreeRetentionDays: data.cleanup?.worktreeRetentionDays ?? 14,
@@ -687,10 +733,10 @@ function parseConfig(raw) {
687
733
  }
688
734
  };
689
735
  }
690
- function assertKnownKeys(value, path7, knownKeys) {
736
+ function assertKnownKeys(value, path9, knownKeys) {
691
737
  for (const key of Object.keys(value)) {
692
738
  if (!knownKeys.includes(key)) {
693
- throw new Error(`${path7}.${key} is not supported`);
739
+ throw new Error(`${path9}.${key} is not supported`);
694
740
  }
695
741
  }
696
742
  }
@@ -698,20 +744,21 @@ function getVerificationCommands(target) {
698
744
  return target.strategy.verify?.commands ?? [];
699
745
  }
700
746
  async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
701
- const { existsSync: existsSync8 } = await import("fs");
702
- const { readFile: readFile4, writeFile: writeFile3, rm } = await import("fs/promises");
703
- const { tmpdir } = await import("os");
747
+ const { existsSync: existsSync10 } = await import("fs");
748
+ const { mkdir: mkdir5, writeFile: writeFile3, rm } = await import("fs/promises");
704
749
  const { join: pjoin, basename } = await import("path");
705
750
  const { pathToFileURL: pathToFileURL2 } = await import("url");
706
751
  const { build } = await import("esbuild");
707
752
  const configPath = pjoin(repoRoot2, configFileName);
708
- if (!existsSync8(configPath)) {
753
+ if (!existsSync10(configPath)) {
709
754
  throw new Error(
710
755
  `No config file found at ${configPath}. Create a ${configFileName} that exports a default PourkitConfig.`
711
756
  );
712
757
  }
758
+ const tmpDir = pjoin(repoRoot2, ".pourkit", ".tmp", "config");
759
+ await mkdir5(tmpDir, { recursive: true });
713
760
  const tmpFile = pjoin(
714
- tmpdir(),
761
+ tmpDir,
715
762
  `pourkit-config-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.mjs`
716
763
  );
717
764
  try {
@@ -836,16 +883,16 @@ function parseWorktreeListPorcelain2(text2) {
836
883
  const entries = text2.trim().split("\n\n");
837
884
  return entries.map((entry) => {
838
885
  const lines = entry.trim().split("\n");
839
- let path7 = "";
886
+ let path9 = "";
840
887
  let branch = "";
841
888
  for (const line of lines) {
842
889
  if (line.startsWith("worktree ")) {
843
- path7 = line.slice("worktree ".length);
890
+ path9 = line.slice("worktree ".length);
844
891
  } else if (line.startsWith("branch refs/heads/")) {
845
892
  branch = line.slice("branch refs/heads/".length);
846
893
  }
847
894
  }
848
- return { path: path7, branch: branch || void 0 };
895
+ return { path: path9, branch: branch || void 0 };
849
896
  }).filter((e) => e.path);
850
897
  }
851
898
  async function listCleanupCandidates(repoRoot2, retentionDays) {
@@ -885,7 +932,7 @@ async function removeStaleWorktree(candidate, repoRoot2, logger) {
885
932
  }
886
933
  }
887
934
  async function removeExpiredFiles(dirPath, retentionDays) {
888
- const { readdir: readdir2, stat, unlink, access } = await import("fs/promises");
935
+ const { readdir: readdir2, stat, unlink, access: access2 } = await import("fs/promises");
889
936
  let entries;
890
937
  try {
891
938
  entries = await readdir2(dirPath);
@@ -899,7 +946,7 @@ async function removeExpiredFiles(dirPath, retentionDays) {
899
946
  try {
900
947
  const stats = await stat(entryPath);
901
948
  if (stats.isFile() && now - stats.mtimeMs > retentionMs) {
902
- await access(entryPath, 4);
949
+ await access2(entryPath, 4);
903
950
  await unlink(entryPath);
904
951
  }
905
952
  } catch {
@@ -950,8 +997,8 @@ async function cleanupRepository(options) {
950
997
  }
951
998
 
952
999
  // commands/issue-run.ts
953
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
954
- import { join as join8 } from "path";
1000
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
1001
+ import { join as join10 } from "path";
955
1002
 
956
1003
  // pr/templates.ts
957
1004
  init_common();
@@ -1014,6 +1061,13 @@ var STAGE_SECTIONS = {
1014
1061
  "branch",
1015
1062
  "verification-commands",
1016
1063
  "artifacts"
1064
+ ],
1065
+ failureResolution: [
1066
+ "issue",
1067
+ "comments",
1068
+ "branch",
1069
+ "verification-commands",
1070
+ "artifacts"
1017
1071
  ]
1018
1072
  };
1019
1073
  function buildRunContextArtifact(options) {
@@ -1207,361 +1261,871 @@ function invalidateAfterBaseRefresh(state) {
1207
1261
  };
1208
1262
  }
1209
1263
 
1210
- // commands/conflict-resolution.ts
1211
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1212
- import { join as join4 } from "path";
1213
- init_common();
1264
+ // failure-resolution/effect-runtime.ts
1265
+ import { Effect } from "effect";
1214
1266
 
1215
- // conflicts/conflict-resolution-artifact.ts
1216
- var ConflictResolutionArtifactProtocolError = class extends Error {
1217
- constructor(message) {
1218
- super(message);
1219
- this.name = "ConflictResolutionArtifactProtocolError";
1267
+ // failure-resolution/types.ts
1268
+ var RebaseConflict = class extends Error {
1269
+ _tag = "RebaseConflict";
1270
+ conflictedPaths;
1271
+ message;
1272
+ constructor(args) {
1273
+ super(args.message);
1274
+ this.name = "RebaseConflict";
1275
+ this.conflictedPaths = args.conflictedPaths;
1276
+ this.message = args.message;
1220
1277
  }
1221
1278
  };
1222
- var VALID_STATUSES = [
1223
- "resolved",
1224
- "ambiguous"
1225
- ];
1226
- var SECTION_HEADING_PATTERN = /^## (Status|Summary|Files|Verification)\s*$/gm;
1227
- function extractSections(output) {
1228
- const sections = [];
1229
- let match;
1230
- const re = new RegExp(SECTION_HEADING_PATTERN);
1231
- while ((match = re.exec(output)) !== null) {
1232
- const heading = match[1];
1233
- sections.push({
1234
- heading,
1235
- startIndex: match.index,
1236
- endIndex: match.index + match[0].length
1279
+ var PublishedHistoryRisk = class extends Error {
1280
+ _tag = "PublishedHistoryRisk";
1281
+ prNumber;
1282
+ prState;
1283
+ constructor(args) {
1284
+ super(`Published PR #${args.prNumber} is ${args.prState}`);
1285
+ this.name = "PublishedHistoryRisk";
1286
+ this.prNumber = args.prNumber;
1287
+ this.prState = args.prState;
1288
+ }
1289
+ };
1290
+ var RecoveryArtifactInvalid = class extends Error {
1291
+ _tag = "RecoveryArtifactInvalid";
1292
+ reason;
1293
+ constructor(args) {
1294
+ super(args.reason);
1295
+ this.name = "RecoveryArtifactInvalid";
1296
+ this.reason = args.reason;
1297
+ }
1298
+ };
1299
+ var ConfigFailure = class extends Error {
1300
+ _tag = "ConfigFailure";
1301
+ configKey;
1302
+ expectedType;
1303
+ message;
1304
+ constructor(args) {
1305
+ super(args.message);
1306
+ this.name = "ConfigFailure";
1307
+ this.configKey = args.configKey;
1308
+ this.expectedType = args.expectedType;
1309
+ this.message = args.message;
1310
+ }
1311
+ };
1312
+ var SafetyFailure = class extends Error {
1313
+ _tag = "SafetyFailure";
1314
+ sensitivityKind;
1315
+ message;
1316
+ constructor(args) {
1317
+ super(args.message);
1318
+ this.name = "SafetyFailure";
1319
+ this.sensitivityKind = args.sensitivityKind;
1320
+ this.message = args.message;
1321
+ }
1322
+ };
1323
+ var SUPPORTED_DECISIONS = /* @__PURE__ */ new Set([
1324
+ "RETRY_STAGE",
1325
+ "HANDOFF_TO_HUMAN",
1326
+ "FAIL_RUN"
1327
+ ]);
1328
+ function isSupportedRecoveryDecision(decision) {
1329
+ return SUPPORTED_DECISIONS.has(decision);
1330
+ }
1331
+ var JSON_BLOCK_RE = /```json\n([\s\S]*?)```/;
1332
+ function parseRecoveryArtifact(markdown, artifactPath) {
1333
+ const match = JSON_BLOCK_RE.exec(markdown);
1334
+ if (!match) {
1335
+ throw new RecoveryArtifactInvalid({
1336
+ reason: `No JSON code block found in artifact at ${artifactPath}`
1237
1337
  });
1238
1338
  }
1239
- return sections;
1240
- }
1241
- function contentAfter(output, sections, section) {
1242
- const index = sections.indexOf(section);
1243
- const nextSection = sections[index + 1];
1244
- const start = section.endIndex;
1245
- const end = nextSection?.startIndex ?? output.length;
1246
- return output.slice(start, end).trim();
1247
- }
1248
- function extractMarker(output) {
1249
- const matches = output.matchAll(
1250
- /<conflict-resolution>\s*(resolved|ambiguous)\s*<\/conflict-resolution>/g
1251
- );
1252
- const results = Array.from(matches);
1253
- if (results.length > 1) {
1254
- throw new ConflictResolutionArtifactProtocolError(
1255
- "Duplicate <conflict-resolution>...</conflict-resolution> markers"
1256
- );
1339
+ let parsed;
1340
+ try {
1341
+ parsed = JSON.parse(match[1]);
1342
+ } catch {
1343
+ throw new RecoveryArtifactInvalid({
1344
+ reason: `Malformed JSON in artifact at ${artifactPath}`
1345
+ });
1257
1346
  }
1258
- return results.length === 1 ? results[0][1] : null;
1259
- }
1260
- function parseFileList(filesContent) {
1261
- return filesContent.split("\n").map((line) => line.trim()).filter((line) => line.startsWith("- ")).map((line) => {
1262
- const rest = line.slice(2).trim();
1263
- const codeMatch = rest.match(/^`([^`]+)`/);
1264
- return codeMatch ? codeMatch[1] : rest;
1265
- });
1347
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1348
+ throw new RecoveryArtifactInvalid({
1349
+ reason: `Expected a JSON object in artifact at ${artifactPath}`
1350
+ });
1351
+ }
1352
+ const json = parsed;
1353
+ if (typeof json.recoveryDecision !== "string" || json.recoveryDecision === "") {
1354
+ throw new RecoveryArtifactInvalid({
1355
+ reason: `Missing or empty recoveryDecision in artifact at ${artifactPath}`
1356
+ });
1357
+ }
1358
+ if (typeof json.summary !== "string" || json.summary === "") {
1359
+ throw new RecoveryArtifactInvalid({
1360
+ reason: `Missing or empty summary in artifact at ${artifactPath}`
1361
+ });
1362
+ }
1363
+ const changedFiles = json.changedFiles;
1364
+ if (!Array.isArray(changedFiles)) {
1365
+ throw new RecoveryArtifactInvalid({
1366
+ reason: `changedFiles must be an array in artifact at ${artifactPath}`
1367
+ });
1368
+ }
1369
+ if (!changedFiles.every((f) => typeof f === "string")) {
1370
+ throw new RecoveryArtifactInvalid({
1371
+ reason: `changedFiles must be an array of strings in artifact at ${artifactPath}`
1372
+ });
1373
+ }
1374
+ if (json.verificationSummary !== void 0 && typeof json.verificationSummary !== "string") {
1375
+ throw new RecoveryArtifactInvalid({
1376
+ reason: `verificationSummary must be a string in artifact at ${artifactPath}`
1377
+ });
1378
+ }
1379
+ if (json.notes !== void 0 && typeof json.notes !== "string") {
1380
+ throw new RecoveryArtifactInvalid({
1381
+ reason: `notes must be a string in artifact at ${artifactPath}`
1382
+ });
1383
+ }
1384
+ if (json.verificationCommands !== void 0) {
1385
+ if (!Array.isArray(json.verificationCommands)) {
1386
+ throw new RecoveryArtifactInvalid({
1387
+ reason: `verificationCommands must be an array in artifact at ${artifactPath}`
1388
+ });
1389
+ }
1390
+ if (!json.verificationCommands.every((c) => typeof c === "string")) {
1391
+ throw new RecoveryArtifactInvalid({
1392
+ reason: `verificationCommands must be an array of strings in artifact at ${artifactPath}`
1393
+ });
1394
+ }
1395
+ }
1396
+ return {
1397
+ raw: markdown,
1398
+ json: {
1399
+ recoveryDecision: json.recoveryDecision,
1400
+ summary: json.summary,
1401
+ changedFiles,
1402
+ verificationSummary: json.verificationSummary,
1403
+ verificationCommands: json.verificationCommands,
1404
+ notes: json.notes
1405
+ },
1406
+ path: artifactPath
1407
+ };
1266
1408
  }
1267
- function parseVerificationTable(content) {
1268
- const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
1269
- const tableLines = lines.filter((l) => l.startsWith("|") && l.endsWith("|"));
1270
- const dataRows = tableLines.slice(2);
1271
- return dataRows.map((row) => {
1272
- const cells = row.split("|").slice(1, -1).map((c) => c.trim());
1409
+ function validateRecoveryDecision(artifact, allowedDecisions) {
1410
+ const decision = artifact.json.recoveryDecision;
1411
+ if (!allowedDecisions.includes(decision)) {
1273
1412
  return {
1274
- command: cells[0] ?? "",
1275
- result: cells[1] ?? "",
1276
- notes: cells[2] ?? ""
1413
+ valid: false,
1414
+ reason: `Decision "${decision}" is not in allowed list: ${allowedDecisions.join(", ")}`
1277
1415
  };
1278
- });
1279
- }
1280
- function parseConflictResolutionArtifact(output) {
1281
- if (!output.trim()) {
1282
- throw new ConflictResolutionArtifactProtocolError(
1283
- "Empty conflict resolution artifact output"
1284
- );
1285
1416
  }
1286
- const sections = extractSections(output);
1287
- const statusSections = sections.filter((s) => s.heading === "Status");
1288
- const summarySections = sections.filter((s) => s.heading === "Summary");
1289
- const filesSections = sections.filter((s) => s.heading === "Files");
1290
- if (statusSections.length > 1) {
1291
- throw new ConflictResolutionArtifactProtocolError(
1292
- 'Duplicate "## Status" sections'
1293
- );
1417
+ if (!isSupportedRecoveryDecision(decision)) {
1418
+ return {
1419
+ valid: false,
1420
+ reason: `Decision "${decision}" is not supported in this slice`
1421
+ };
1294
1422
  }
1295
- if (summarySections.length > 1) {
1296
- throw new ConflictResolutionArtifactProtocolError(
1297
- 'Duplicate "## Summary" sections'
1298
- );
1423
+ return { valid: true, decision };
1424
+ }
1425
+
1426
+ // shared/attempt-log.ts
1427
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
1428
+ import { dirname as dirname2, join as join4 } from "path";
1429
+ var ATTEMPT_LOG_PATH = ".pourkit/attempt-log.jsonl";
1430
+ function writeAttemptLog(worktreePath, entry) {
1431
+ const logPath = join4(worktreePath, ATTEMPT_LOG_PATH);
1432
+ mkdirSync3(dirname2(logPath), { recursive: true });
1433
+ appendFileSync(logPath, JSON.stringify(entry) + "\n", "utf-8");
1434
+ }
1435
+ function readAttemptLog(worktreePath) {
1436
+ const logPath = join4(worktreePath, ATTEMPT_LOG_PATH);
1437
+ if (!existsSync2(logPath)) {
1438
+ return [];
1299
1439
  }
1300
- if (filesSections.length > 1) {
1301
- throw new ConflictResolutionArtifactProtocolError(
1302
- 'Duplicate "## Files" sections'
1303
- );
1440
+ const raw = readFileSync2(logPath, "utf-8");
1441
+ const lines = raw.split("\n").filter((l) => l.length > 0);
1442
+ const entries = [];
1443
+ for (const line of lines) {
1444
+ try {
1445
+ const parsed = JSON.parse(line);
1446
+ if (isValidAttemptLogEntry(parsed)) {
1447
+ entries.push(parsed);
1448
+ }
1449
+ } catch {
1450
+ }
1304
1451
  }
1305
- const verificationSections = sections.filter(
1306
- (s) => s.heading === "Verification"
1452
+ return entries;
1453
+ }
1454
+ function isValidAttemptLogEntry(raw) {
1455
+ if (typeof raw !== "object" || raw === null) return false;
1456
+ const obj = raw;
1457
+ if (obj.attemptType !== "stage" && obj.attemptType !== "recovery")
1458
+ return false;
1459
+ if (typeof obj.fingerprint !== "string") return false;
1460
+ if (typeof obj.timestamp !== "string") return false;
1461
+ if (typeof obj.stage !== "string") return false;
1462
+ if (obj.outcome !== "success" && obj.outcome !== "failure" && obj.outcome !== "handoff")
1463
+ return false;
1464
+ return true;
1465
+ }
1466
+ function recoveryBudgetForFailure(worktreePath, fingerprint, maxAttempts) {
1467
+ const entries = readAttemptLog(worktreePath);
1468
+ const used = entries.filter(
1469
+ (e) => e.attemptType === "recovery" && e.fingerprint === fingerprint
1470
+ ).length;
1471
+ const remaining = Math.max(0, maxAttempts - used);
1472
+ return {
1473
+ used,
1474
+ remaining,
1475
+ exhausted: used >= maxAttempts
1476
+ };
1477
+ }
1478
+ function computeFailureFingerprint(stage, failureType) {
1479
+ return `${stage.toLowerCase()}:${failureType}`;
1480
+ }
1481
+
1482
+ // failure-resolution/stage-attempt.ts
1483
+ function createStageAttemptId() {
1484
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1485
+ }
1486
+ function recordStageAttempt(worktreePath, record) {
1487
+ writeAttemptLog(worktreePath, {
1488
+ attemptType: "stage",
1489
+ fingerprint: record.failureFingerprint ?? `${record.stage}:success`,
1490
+ timestamp: record.completedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1491
+ stage: record.stage,
1492
+ outcome: record.outcome === "handoff" ? "handoff" : record.outcome === "success" ? "success" : "failure"
1493
+ });
1494
+ }
1495
+
1496
+ // failure-resolution/effect-runtime.ts
1497
+ function baseRefreshEffect(options) {
1498
+ return Effect.promise(() => refreshStaleIssueBranch(options)).pipe(
1499
+ Effect.flatMap(
1500
+ (result) => {
1501
+ switch (result.status) {
1502
+ case "refreshed":
1503
+ return Effect.succeed({ status: "refreshed" });
1504
+ case "skipped-current":
1505
+ return Effect.succeed({ status: "skipped-current" });
1506
+ case "conflicted":
1507
+ return Effect.fail(
1508
+ new RebaseConflict({
1509
+ conflictedPaths: result.conflictedPaths,
1510
+ message: result.message
1511
+ })
1512
+ );
1513
+ case "refused-published-history":
1514
+ return Effect.fail(
1515
+ new PublishedHistoryRisk({
1516
+ prNumber: result.prNumber,
1517
+ prState: result.prState
1518
+ })
1519
+ );
1520
+ }
1521
+ }
1522
+ )
1307
1523
  );
1308
- const statusSection = statusSections[0];
1309
- const summarySection = summarySections[0];
1310
- const filesSection = filesSections[0];
1311
- if (!statusSection) {
1312
- throw new ConflictResolutionArtifactProtocolError(
1313
- 'Missing required section "## Status"'
1314
- );
1524
+ }
1525
+ async function runBaseRefreshAttempt(options) {
1526
+ const attemptId = createStageAttemptId();
1527
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1528
+ const program = baseRefreshEffect(options).pipe(
1529
+ Effect.tapBoth({
1530
+ onSuccess: (success) => Effect.sync(() => {
1531
+ recordStageAttempt(options.worktreePath, {
1532
+ id: attemptId,
1533
+ stage: "baseRefresh",
1534
+ startedAt,
1535
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
1536
+ outcome: "success"
1537
+ });
1538
+ }),
1539
+ onFailure: (failure) => Effect.sync(() => {
1540
+ recordStageAttempt(options.worktreePath, {
1541
+ id: attemptId,
1542
+ stage: "baseRefresh",
1543
+ startedAt,
1544
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
1545
+ outcome: "failure",
1546
+ failureFingerprint: computeFailureFingerprint(
1547
+ "baseRefresh",
1548
+ failure._tag
1549
+ ),
1550
+ failureType: failure._tag
1551
+ });
1552
+ })
1553
+ })
1554
+ );
1555
+ return Effect.runPromiseExit(program);
1556
+ }
1557
+
1558
+ // commands/conflict-resolution.ts
1559
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1560
+ import { join as join5 } from "path";
1561
+ init_common();
1562
+ var CONFLICT_MARKER_PATTERN = /<<<<<<<|=======|>>>>>>>/m;
1563
+ async function hasUnresolvedConflictMarkers(worktreePath, files) {
1564
+ for (const file of files) {
1565
+ const filePath = join5(worktreePath, file);
1566
+ try {
1567
+ const content = readFileSync3(filePath, "utf-8");
1568
+ if (CONFLICT_MARKER_PATTERN.test(content)) {
1569
+ return true;
1570
+ }
1571
+ } catch {
1572
+ }
1315
1573
  }
1316
- if (!summarySection) {
1317
- throw new ConflictResolutionArtifactProtocolError(
1318
- 'Missing required section "## Summary"'
1319
- );
1574
+ return false;
1575
+ }
1576
+
1577
+ // commands/issue-run.ts
1578
+ import { Exit as Exit2 } from "effect";
1579
+
1580
+ // serena/baseline.ts
1581
+ init_common();
1582
+ import path3 from "path";
1583
+ import { access, mkdir as mkdir2 } from "fs/promises";
1584
+ function resolveSerenaPaths(repoRoot2, dataDir = ".pourkit/serena/") {
1585
+ const rootDir = path3.isAbsolute(dataDir) ? path3.normalize(dataDir) : path3.resolve(repoRoot2, dataDir);
1586
+ return {
1587
+ rootDir,
1588
+ baselineWorktreePath: path3.join(rootDir, "baseline", "active-repo"),
1589
+ dataDir: path3.join(rootDir, "data")
1590
+ };
1591
+ }
1592
+ async function pathExists(dirPath) {
1593
+ try {
1594
+ await access(dirPath);
1595
+ return true;
1596
+ } catch {
1597
+ return false;
1320
1598
  }
1321
- if (!filesSection) {
1322
- throw new ConflictResolutionArtifactProtocolError(
1323
- 'Missing required section "## Files"'
1324
- );
1599
+ }
1600
+ async function isGitRepoRoot(repoPath) {
1601
+ try {
1602
+ const result = await execCapture("git", ["rev-parse", "--show-toplevel"], {
1603
+ cwd: repoPath,
1604
+ label: "git rev-parse --show-toplevel"
1605
+ });
1606
+ return path3.resolve(result.stdout.trim()) === path3.resolve(repoPath);
1607
+ } catch {
1608
+ return false;
1325
1609
  }
1326
- const statusRaw = contentAfter(output, sections, statusSection);
1327
- const summary = contentAfter(output, sections, summarySection);
1328
- const filesContent = contentAfter(output, sections, filesSection);
1329
- if (!statusRaw) {
1330
- throw new ConflictResolutionArtifactProtocolError(
1331
- '"## Status" section is empty'
1610
+ }
1611
+ async function ensureBaselineWorktree(options) {
1612
+ const paths = resolveSerenaPaths(options.repoRoot, options.dataDir);
1613
+ await mkdir2(paths.rootDir, { recursive: true });
1614
+ await mkdir2(paths.dataDir, { recursive: true });
1615
+ if (!await pathExists(paths.baselineWorktreePath)) {
1616
+ await mkdir2(path3.dirname(paths.baselineWorktreePath), { recursive: true });
1617
+ await execCapture(
1618
+ "git",
1619
+ ["clone", options.repoRoot, paths.baselineWorktreePath],
1620
+ {
1621
+ cwd: options.repoRoot,
1622
+ label: "git clone baseline worktree"
1623
+ }
1332
1624
  );
1625
+ return paths;
1333
1626
  }
1334
- if (!summary) {
1335
- throw new ConflictResolutionArtifactProtocolError(
1336
- '"## Summary" section is empty'
1627
+ if (!await isGitRepoRoot(paths.baselineWorktreePath)) {
1628
+ throw new Error(
1629
+ `Serena baseline worktree exists but is not a git repo: ${paths.baselineWorktreePath}`
1337
1630
  );
1338
1631
  }
1339
- if (!VALID_STATUSES.includes(statusRaw)) {
1340
- throw new ConflictResolutionArtifactProtocolError(
1341
- `Unsupported status "${statusRaw}". Allowed statuses: ${VALID_STATUSES.join(", ")}`
1342
- );
1632
+ return paths;
1633
+ }
1634
+ async function getSerenaBaselineStatus(options) {
1635
+ const remoteName = options.remoteName ?? "origin";
1636
+ const paths = resolveSerenaPaths(options.repoRoot, options.dataDir);
1637
+ const expectedRef = `${remoteName}/${options.baseBranch}`;
1638
+ if (!await pathExists(paths.baselineWorktreePath)) {
1639
+ return {
1640
+ exists: false,
1641
+ baselineWorktreePath: paths.baselineWorktreePath,
1642
+ expectedRef,
1643
+ fresh: false
1644
+ };
1343
1645
  }
1344
- const markerStatus = extractMarker(output);
1345
- if (!markerStatus) {
1346
- throw new ConflictResolutionArtifactProtocolError(
1347
- "Missing <conflict-resolution>...</conflict-resolution> marker"
1348
- );
1646
+ if (!await isGitRepoRoot(paths.baselineWorktreePath)) {
1647
+ return {
1648
+ exists: false,
1649
+ baselineWorktreePath: paths.baselineWorktreePath,
1650
+ expectedRef,
1651
+ fresh: false
1652
+ };
1349
1653
  }
1350
- if (markerStatus !== statusRaw) {
1351
- throw new ConflictResolutionArtifactProtocolError(
1352
- `Conflict resolution status "${statusRaw}" does not match marker "${markerStatus}"`
1353
- );
1654
+ let currentCommit;
1655
+ try {
1656
+ const currentResult = await execCapture("git", ["rev-parse", "HEAD"], {
1657
+ cwd: paths.baselineWorktreePath,
1658
+ label: "git rev-parse HEAD"
1659
+ });
1660
+ currentCommit = currentResult.stdout.trim() || void 0;
1661
+ } catch {
1662
+ return {
1663
+ exists: true,
1664
+ baselineWorktreePath: paths.baselineWorktreePath,
1665
+ expectedRef,
1666
+ fresh: false
1667
+ };
1354
1668
  }
1355
- let verification;
1356
- if (verificationSections.length > 0) {
1357
- const verificationContent = contentAfter(
1358
- output,
1359
- sections,
1360
- verificationSections[0]
1669
+ let expectedCommit;
1670
+ try {
1671
+ const expectedResult = await execCapture(
1672
+ "git",
1673
+ ["rev-parse", expectedRef],
1674
+ {
1675
+ cwd: paths.baselineWorktreePath,
1676
+ label: `git rev-parse ${expectedRef}`
1677
+ }
1361
1678
  );
1362
- verification = parseVerificationTable(verificationContent);
1679
+ expectedCommit = expectedResult.stdout.trim() || void 0;
1680
+ } catch {
1681
+ return {
1682
+ exists: true,
1683
+ baselineWorktreePath: paths.baselineWorktreePath,
1684
+ currentCommit,
1685
+ expectedRef,
1686
+ fresh: false
1687
+ };
1363
1688
  }
1364
1689
  return {
1365
- status: statusRaw,
1366
- summary,
1367
- files: parseFileList(filesContent),
1368
- verification,
1369
- raw: output
1690
+ exists: true,
1691
+ baselineWorktreePath: paths.baselineWorktreePath,
1692
+ currentCommit,
1693
+ expectedRef,
1694
+ fresh: currentCommit === expectedCommit
1370
1695
  };
1371
1696
  }
1697
+ async function refreshSerenaBaseline(options) {
1698
+ const remoteName = options.remoteName ?? "origin";
1699
+ const paths = await ensureBaselineWorktree(options);
1700
+ await execCapture("git", ["fetch", remoteName, options.baseBranch], {
1701
+ cwd: paths.baselineWorktreePath,
1702
+ label: "git fetch baseline branch"
1703
+ });
1704
+ await execCapture(
1705
+ "git",
1706
+ ["checkout", "--detach", `${remoteName}/${options.baseBranch}`],
1707
+ {
1708
+ cwd: paths.baselineWorktreePath,
1709
+ label: "git checkout detached baseline branch"
1710
+ }
1711
+ );
1712
+ return getSerenaBaselineStatus(options);
1713
+ }
1372
1714
 
1373
- // commands/conflict-resolution.ts
1374
- function loadConflictResolutionPrompt(repoRoot2, promptTemplate, artifactPath) {
1375
- const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
1376
- const promptBody = existsSync2(promptPath) ? readFileSync2(promptPath, "utf-8") : promptTemplate;
1377
- return `${promptBody}
1378
-
1379
- ## Shared Run Context
1715
+ // serena/container.ts
1716
+ init_common();
1717
+ import { mkdir as mkdir3 } from "fs/promises";
1718
+ import path4 from "path";
1719
+ var DEFAULT_CONTAINER_NAME = "pourkit-serena-sidecar";
1720
+ var MCP_CONTAINER_PORT = 9121;
1721
+ var DASHBOARD_CONTAINER_PORT = 24282;
1722
+ var SERENA_DATA_MOUNT = "/workspaces/serena-data";
1723
+ function resolveSidecarUrls(options) {
1724
+ return {
1725
+ containerName: options.containerName ?? DEFAULT_CONTAINER_NAME,
1726
+ mcpUrl: options.mcpUrl ?? `http://localhost:${options.mcpPort}/mcp`,
1727
+ dashboardUrl: `http://localhost:${options.dashboardPort}`
1728
+ };
1729
+ }
1730
+ async function inspectSidecarContainer(containerName) {
1731
+ try {
1732
+ const result = await execCapture("docker", ["inspect", containerName]);
1733
+ const parsed = JSON.parse(result.stdout);
1734
+ return {
1735
+ exists: true,
1736
+ running: Boolean(parsed[0]?.State?.Running)
1737
+ };
1738
+ } catch {
1739
+ return {
1740
+ exists: false,
1741
+ running: false
1742
+ };
1743
+ }
1744
+ }
1745
+ async function readSidecarStatus(options) {
1746
+ const { containerName, mcpUrl, dashboardUrl } = resolveSidecarUrls(options);
1747
+ const container = await inspectSidecarContainer(containerName);
1748
+ return {
1749
+ running: container.running,
1750
+ mcpUrl,
1751
+ dashboardUrl,
1752
+ containerName
1753
+ };
1754
+ }
1755
+ function buildStartArgs(options, containerName) {
1756
+ return [
1757
+ "run",
1758
+ "-d",
1759
+ "--name",
1760
+ containerName,
1761
+ "--restart",
1762
+ "unless-stopped",
1763
+ "-p",
1764
+ `${options.mcpPort}:${MCP_CONTAINER_PORT}`,
1765
+ "-p",
1766
+ `${options.dashboardPort}:${DASHBOARD_CONTAINER_PORT}`,
1767
+ "-v",
1768
+ `${options.baselineWorktreePath}:/workspaces/pourkit`,
1769
+ "-v",
1770
+ `${options.dataDir}:${SERENA_DATA_MOUNT}`,
1771
+ "-e",
1772
+ `SERENA_HOME=${SERENA_DATA_MOUNT}/config`,
1773
+ options.image,
1774
+ "serena",
1775
+ "start-mcp-server",
1776
+ "--transport",
1777
+ "streamable-http",
1778
+ "--port",
1779
+ String(MCP_CONTAINER_PORT),
1780
+ "--host",
1781
+ "0.0.0.0"
1782
+ ];
1783
+ }
1784
+ async function getSerenaSidecarStatus(options) {
1785
+ return readSidecarStatus(options);
1786
+ }
1787
+ async function startSerenaSidecar(options) {
1788
+ const { containerName } = resolveSidecarUrls(options);
1789
+ const container = await inspectSidecarContainer(containerName);
1790
+ if (container.exists) {
1791
+ if (!container.running) {
1792
+ await execCapture("docker", ["start", containerName]);
1793
+ }
1794
+ return readSidecarStatus(options);
1795
+ }
1796
+ await execCapture("docker", buildStartArgs(options, containerName));
1797
+ return readSidecarStatus(options);
1798
+ }
1799
+ async function indexSerenaProject(options) {
1800
+ const { containerName } = resolveSidecarUrls(options);
1801
+ await execCapture("docker", [
1802
+ "exec",
1803
+ containerName,
1804
+ "serena",
1805
+ "project",
1806
+ "create",
1807
+ "--language",
1808
+ "typescript",
1809
+ "--index",
1810
+ "/workspaces/pourkit"
1811
+ ]);
1812
+ }
1813
+ async function stopSerenaSidecar(options) {
1814
+ const { containerName } = resolveSidecarUrls(options);
1815
+ try {
1816
+ await execCapture("docker", ["stop", containerName]);
1817
+ } catch {
1818
+ }
1819
+ return readSidecarStatus(options);
1820
+ }
1821
+ async function prepareSerenaSidecarConfig(options) {
1822
+ const configDir = path4.join(options.dataDir, "config");
1823
+ await mkdir3(configDir, { recursive: true });
1824
+ }
1380
1825
 
1381
- Read the selected issue requirements, comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
1826
+ // serena/preflight.ts
1827
+ var SERENA_MCP_PORT = 9121;
1828
+ var SERENA_DASHBOARD_PORT = 24282;
1829
+ var SERENA_IMAGE = "ghcr.io/oraios/serena:latest";
1830
+ function sidecarOptions(paths, mcpUrl) {
1831
+ return {
1832
+ baselineWorktreePath: paths.baselineWorktreePath,
1833
+ dataDir: paths.dataDir,
1834
+ mcpPort: SERENA_MCP_PORT,
1835
+ dashboardPort: SERENA_DASHBOARD_PORT,
1836
+ image: SERENA_IMAGE,
1837
+ mcpUrl
1838
+ };
1839
+ }
1840
+ async function canReachMcp(url) {
1841
+ for (let attempt = 0; attempt < 10; attempt += 1) {
1842
+ try {
1843
+ await fetch(url, { method: "GET", signal: AbortSignal.timeout(500) });
1844
+ return true;
1845
+ } catch {
1846
+ if (attempt < 9) {
1847
+ await new Promise((resolve) => setTimeout(resolve, 100));
1848
+ }
1849
+ }
1850
+ }
1851
+ return false;
1852
+ }
1853
+ function formatError(error) {
1854
+ return error instanceof Error ? error.message : String(error);
1855
+ }
1856
+ async function prepareSerenaForTarget(options) {
1857
+ if (!options.enabled) {
1858
+ return { enabled: false };
1859
+ }
1860
+ try {
1861
+ const paths = await ensureBaselineWorktree({
1862
+ repoRoot: options.repoRoot,
1863
+ dataDir: options.dataDir
1864
+ });
1865
+ await prepareSerenaSidecarConfig({
1866
+ baselineWorktreePath: paths.baselineWorktreePath,
1867
+ dataDir: paths.dataDir
1868
+ });
1869
+ const status = options.autoStart ? await startSerenaSidecar(sidecarOptions(paths, options.mcpUrl)) : await getSerenaSidecarStatus(sidecarOptions(paths, options.mcpUrl));
1870
+ const mcpReachable = await canReachMcp(options.mcpUrl);
1871
+ if (!mcpReachable) {
1872
+ return {
1873
+ enabled: true,
1874
+ available: false,
1875
+ error: status.running ? `Serena MCP is not reachable at ${options.mcpUrl}` : `Serena sidecar is not running for target ${options.targetName}`
1876
+ };
1877
+ }
1878
+ await refreshSerenaBaseline({
1879
+ repoRoot: options.repoRoot,
1880
+ dataDir: options.dataDir,
1881
+ baseBranch: options.baseBranch
1882
+ });
1883
+ return {
1884
+ enabled: true,
1885
+ available: true,
1886
+ mcpUrl: options.mcpUrl
1887
+ };
1888
+ } catch (error) {
1889
+ return {
1890
+ enabled: true,
1891
+ available: false,
1892
+ error: formatError(error)
1893
+ };
1894
+ }
1895
+ }
1382
1896
 
1383
- ## Output
1897
+ // failure-resolution/failure-resolution-agent.ts
1898
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1899
+ import { join as join6 } from "path";
1384
1900
 
1385
- Write your resolution to: ${artifactPath}
1901
+ // failure-resolution/recovery-policy.ts
1902
+ function isSecuritySensitiveFailure(failure) {
1903
+ return failure instanceof PublishedHistoryRisk || failure instanceof SafetyFailure;
1904
+ }
1905
+ async function evaluateRecoveryPolicy(params) {
1906
+ if (isSecuritySensitiveFailure(params.failure)) {
1907
+ return {
1908
+ decision: "HANDOFF_TO_HUMAN",
1909
+ reason: "Security-sensitive failure \u2014 AI recovery bypassed"
1910
+ };
1911
+ }
1912
+ if (params.failure instanceof ConfigFailure) {
1913
+ if (params.agentRecommendedDecision !== "FAIL_RUN" && params.agentRecommendedDecision !== "HANDOFF_TO_HUMAN") {
1914
+ return {
1915
+ decision: "HANDOFF_TO_HUMAN",
1916
+ reason: `ConfigFailure \u2014 agent recommended ${params.agentRecommendedDecision} but config errors are not AI-repairable`
1917
+ };
1918
+ }
1919
+ }
1920
+ const budget = recoveryBudgetForFailure(
1921
+ params.worktreePath,
1922
+ params.fingerprint,
1923
+ params.maxAttempts
1924
+ );
1925
+ if (budget.exhausted) {
1926
+ return {
1927
+ decision: "HANDOFF_TO_HUMAN",
1928
+ reason: `Recovery budget exhausted (${budget.used}/${params.maxAttempts})`
1929
+ };
1930
+ }
1931
+ if (!params.allowedDecisions.includes(params.agentRecommendedDecision)) {
1932
+ return {
1933
+ decision: "HANDOFF_TO_HUMAN",
1934
+ reason: `Agent recommended ${params.agentRecommendedDecision} which is not allowed`
1935
+ };
1936
+ }
1937
+ if (params.agentRecommendedDecision === "FAIL_RUN") {
1938
+ return { decision: "FAIL_RUN", reason: "Agent recommended FAIL_RUN" };
1939
+ }
1940
+ return {
1941
+ decision: params.agentRecommendedDecision,
1942
+ reason: "Agent recommendation accepted"
1943
+ };
1944
+ }
1386
1945
 
1387
- Do not provide a separate chat response. The runner only reads the file above.`;
1946
+ // failure-resolution/failure-resolution-agent.ts
1947
+ function constructFailureResolutionPacket(failure, context) {
1948
+ return {
1949
+ failureType: failure._tag,
1950
+ stageName: context.stageName,
1951
+ attemptNumber: context.attemptNumber,
1952
+ worktreePath: context.worktreePath,
1953
+ branchName: context.branchName,
1954
+ baseBranch: context.baseBranch,
1955
+ conflictedPaths: failure instanceof RebaseConflict ? failure.conflictedPaths : void 0,
1956
+ failureSummary: failure.message,
1957
+ maxAttempts: context.maxAttempts,
1958
+ allowedDecisions: context.allowedDecisions,
1959
+ artifactTarget: context.artifactTarget
1960
+ };
1388
1961
  }
1389
- async function runConflictResolutionOnce(options) {
1962
+ async function runFailureResolutionAgent(options) {
1390
1963
  const {
1391
1964
  executionProvider,
1392
1965
  config,
1393
1966
  target,
1394
- issue,
1395
- branchName,
1967
+ failure,
1968
+ packet,
1396
1969
  worktreePath,
1397
1970
  repoRoot: repoRoot2,
1398
- conflictedPaths,
1399
- attempt,
1400
1971
  logger
1401
1972
  } = options;
1402
- const strategyCr = target.strategy.conflictResolution;
1403
- if (!strategyCr) {
1404
- return { status: "failed", message: "No conflictResolution configured" };
1405
- }
1406
- const artifactPath = `.pourkit/.tmp/conflict-resolution/attempt-${attempt}.md`;
1407
- const prompt = loadConflictResolutionPrompt(
1408
- repoRoot2,
1409
- strategyCr.promptTemplate,
1410
- artifactPath
1411
- );
1412
- const runContextArtifact = buildRunContextArtifact({
1413
- issue,
1414
- target,
1415
- branchName,
1416
- sections: STAGE_SECTIONS.conflictResolution
1417
- });
1973
+ const frConfig = target.strategy.failureResolution;
1974
+ const artifactPath = packet.artifactTarget;
1975
+ const fullArtifactPath = join6(worktreePath, artifactPath);
1976
+ const fingerprint = computeFailureFingerprint(packet.stageName, failure._tag);
1977
+ const prompt = [
1978
+ `# Failure Resolution: ${packet.failureType}`,
1979
+ "",
1980
+ "## Failure Context",
1981
+ "",
1982
+ "```json",
1983
+ JSON.stringify(packet, null, 2),
1984
+ "```",
1985
+ "",
1986
+ "## Instructions",
1987
+ "",
1988
+ `Write your resolution to: ${artifactPath}`,
1989
+ "Include a ```json block with: recoveryDecision, summary, changedFiles, verificationSummary (optional), verificationCommands (optional), notes (optional).",
1990
+ "",
1991
+ "Allowed decisions: " + packet.allowedDecisions.join(", ")
1992
+ ].join("\n");
1418
1993
  const executionResult = await executionProvider.execute({
1419
- stage: "conflictResolution",
1420
- agent: strategyCr.agent,
1421
- model: strategyCr.model,
1994
+ stage: "failureResolution",
1995
+ agent: frConfig.agent,
1996
+ model: frConfig.model,
1422
1997
  prompt,
1423
1998
  target,
1424
1999
  repoRoot: repoRoot2,
1425
- branchName,
2000
+ branchName: packet.branchName,
1426
2001
  sandbox: config.sandbox,
1427
2002
  autoApprove: true,
1428
2003
  worktreePath,
1429
2004
  artifactPath,
1430
- artifacts: [runContextArtifact],
2005
+ artifacts: [],
1431
2006
  logger
1432
2007
  });
1433
2008
  if (!executionResult.success) {
2009
+ await writeRecoveryAttempt(
2010
+ worktreePath,
2011
+ "failure",
2012
+ fingerprint,
2013
+ `Agent execution failed: ${executionResult.error}`,
2014
+ void 0,
2015
+ "HANDOFF_TO_HUMAN",
2016
+ packet.stageName
2017
+ );
1434
2018
  return {
1435
- status: "failed",
1436
- message: executionResult.error ?? "Conflict resolution agent execution failed"
2019
+ status: "handoff",
2020
+ decision: "HANDOFF_TO_HUMAN",
2021
+ reason: `Agent execution failed: ${executionResult.error}`
1437
2022
  };
1438
2023
  }
1439
- const fullArtifactPath = join4(worktreePath, artifactPath);
1440
- if (!existsSync2(fullArtifactPath)) {
2024
+ if (!existsSync4(fullArtifactPath)) {
2025
+ await writeRecoveryAttempt(
2026
+ worktreePath,
2027
+ "failure",
2028
+ fingerprint,
2029
+ "Agent did not write artifact",
2030
+ void 0,
2031
+ "HANDOFF_TO_HUMAN",
2032
+ packet.stageName
2033
+ );
1441
2034
  return {
1442
- status: "failed",
1443
- artifactPath,
1444
- message: "Conflict resolution agent completed but did not write artifact"
2035
+ status: "handoff",
2036
+ decision: "HANDOFF_TO_HUMAN",
2037
+ reason: "Agent did not write artifact"
1445
2038
  };
1446
2039
  }
1447
- let artifactContent;
2040
+ let artifact;
1448
2041
  try {
1449
- artifactContent = readFileSync2(fullArtifactPath, "utf-8");
2042
+ const md = readFileSync4(fullArtifactPath, "utf-8");
2043
+ artifact = parseRecoveryArtifact(md, artifactPath);
1450
2044
  } catch (error) {
2045
+ const reason = error instanceof Error ? error.message : "Failed to parse artifact";
2046
+ await writeRecoveryAttempt(
2047
+ worktreePath,
2048
+ "failure",
2049
+ fingerprint,
2050
+ reason,
2051
+ void 0,
2052
+ "HANDOFF_TO_HUMAN",
2053
+ packet.stageName
2054
+ );
2055
+ return { status: "handoff", decision: "HANDOFF_TO_HUMAN", reason };
2056
+ }
2057
+ const validation = validateRecoveryDecision(
2058
+ artifact,
2059
+ packet.allowedDecisions
2060
+ );
2061
+ if (!validation.valid) {
2062
+ await writeRecoveryAttempt(
2063
+ worktreePath,
2064
+ "failure",
2065
+ fingerprint,
2066
+ validation.reason,
2067
+ void 0,
2068
+ "HANDOFF_TO_HUMAN",
2069
+ packet.stageName
2070
+ );
1451
2071
  return {
1452
- status: "failed",
1453
- artifactPath,
1454
- message: `Failed to read conflict resolution artifact: ${error instanceof Error ? error.message : String(error)}`
2072
+ status: "handoff",
2073
+ decision: "HANDOFF_TO_HUMAN",
2074
+ reason: validation.reason
1455
2075
  };
1456
2076
  }
1457
- let parsed;
1458
- try {
1459
- parsed = parseConflictResolutionArtifact(artifactContent);
1460
- } catch (error) {
1461
- if (error instanceof ConflictResolutionArtifactProtocolError) {
1462
- return {
1463
- status: "failed",
1464
- artifactPath,
1465
- message: `Invalid conflict resolution artifact: ${error.message}`
1466
- };
1467
- }
1468
- throw error;
2077
+ const policyResult = await evaluateRecoveryPolicy({
2078
+ failure,
2079
+ worktreePath,
2080
+ fingerprint,
2081
+ maxAttempts: packet.maxAttempts,
2082
+ agentRecommendedDecision: validation.decision,
2083
+ allowedDecisions: packet.allowedDecisions
2084
+ });
2085
+ await writeRecoveryAttempt(
2086
+ worktreePath,
2087
+ policyResult.decision === "HANDOFF_TO_HUMAN" ? "handoff" : policyResult.decision === "FAIL_RUN" ? "failure" : "success",
2088
+ fingerprint,
2089
+ policyResult.reason,
2090
+ artifactPath,
2091
+ policyResult.decision,
2092
+ packet.stageName
2093
+ );
2094
+ if (policyResult.decision === "HANDOFF_TO_HUMAN") {
2095
+ return {
2096
+ status: "handoff",
2097
+ decision: "HANDOFF_TO_HUMAN",
2098
+ reason: policyResult.reason
2099
+ };
1469
2100
  }
1470
- if (parsed.status === "ambiguous") {
2101
+ if (policyResult.decision === "FAIL_RUN") {
1471
2102
  return {
1472
- status: "ambiguous",
1473
- artifactPath,
1474
- message: parsed.summary
2103
+ status: "fail-run",
2104
+ decision: "FAIL_RUN",
2105
+ reason: policyResult.reason
1475
2106
  };
1476
2107
  }
1477
2108
  return {
1478
- status: "resolved",
1479
- artifactPath,
1480
- files: parsed.files
2109
+ status: "recovered",
2110
+ decision: policyResult.decision,
2111
+ artifact
1481
2112
  };
1482
2113
  }
1483
- var CONFLICT_MARKER_PATTERN = /<<<<<<<|=======|>>>>>>>/m;
1484
- async function hasUnresolvedConflictMarkers(worktreePath, files) {
1485
- for (const file of files) {
1486
- const filePath = join4(worktreePath, file);
1487
- try {
1488
- const content = readFileSync2(filePath, "utf-8");
1489
- if (CONFLICT_MARKER_PATTERN.test(content)) {
1490
- return true;
1491
- }
1492
- } catch {
1493
- }
1494
- }
1495
- return false;
1496
- }
1497
- async function runConflictResolutionLoop(options) {
1498
- const { worktreePath, maxAttempts, logger, initialConflictedPaths } = options;
1499
- let attempt = 0;
1500
- let conflictedPaths = initialConflictedPaths;
1501
- while (attempt < maxAttempts && conflictedPaths.length > 0) {
1502
- attempt++;
1503
- const crResult = await runConflictResolutionOnce({
1504
- ...options,
1505
- conflictedPaths,
1506
- attempt
1507
- });
1508
- if (crResult.status !== "resolved") {
1509
- const message = crResult.status === "ambiguous" ? crResult.message : crResult.message ?? "Conflict resolution agent execution failed";
1510
- return { status: crResult.status, attempts: attempt, message };
1511
- }
1512
- const markersRemain = await hasUnresolvedConflictMarkers(
1513
- worktreePath,
1514
- conflictedPaths
1515
- );
1516
- if (markersRemain) {
1517
- return {
1518
- status: "ambiguous",
1519
- attempts: attempt,
1520
- message: "Conflict resolution agent resolved artifact but conflict markers remain in files"
1521
- };
1522
- }
1523
- await execCapture("git", ["add", ...conflictedPaths], {
1524
- cwd: worktreePath,
1525
- logger,
1526
- label: "git add conflicted paths"
1527
- });
1528
- try {
1529
- await execCapture("git", ["rebase", "--continue"], {
1530
- cwd: worktreePath,
1531
- logger,
1532
- label: "git rebase --continue"
1533
- });
1534
- conflictedPaths = [];
1535
- } catch (error) {
1536
- const statusResult = await execCapture("git", ["status", "--porcelain"], {
1537
- cwd: worktreePath,
1538
- logger,
1539
- label: "git status"
1540
- });
1541
- conflictedPaths = statusResult.stdout.split("\n").filter((line) => /^(AA|DD|UU|AU|UA|DU|UD)\s/.test(line)).map((line) => line.slice(3).trim()).filter(Boolean);
1542
- if (conflictedPaths.length === 0) {
1543
- const rebaseErrorMessage = error instanceof Error ? error.message : String(error);
1544
- return {
1545
- status: "failed",
1546
- attempts: attempt,
1547
- message: `git rebase --continue failed with no remaining conflicts: ${rebaseErrorMessage}`
1548
- };
1549
- }
1550
- }
1551
- }
1552
- if (conflictedPaths.length > 0) {
1553
- return {
1554
- status: "exhausted",
1555
- attempts: attempt,
1556
- message: `Conflict resolution maxAttempts (${maxAttempts}) exhausted with remaining conflicts`
1557
- };
1558
- }
1559
- return { status: "completed", attempts: attempt };
2114
+ async function writeRecoveryAttempt(worktreePath, outcome, fingerprint, summary, artifactRef, decision, stageName = "baseRefresh") {
2115
+ writeAttemptLog(worktreePath, {
2116
+ attemptType: "recovery",
2117
+ fingerprint,
2118
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2119
+ stage: stageName,
2120
+ outcome,
2121
+ artifactRef,
2122
+ decision: decision ?? (outcome === "handoff" ? "HANDOFF_TO_HUMAN" : outcome === "success" ? "RETRY_STAGE" : void 0)
2123
+ });
1560
2124
  }
1561
2125
 
1562
2126
  // commands/pr-description-agent.ts
1563
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
1564
- import { dirname as dirname2, join as join6 } from "path";
2127
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync2 } from "fs";
2128
+ import { dirname as dirname3, join as join8 } from "path";
1565
2129
 
1566
2130
  // pr/pr-description.ts
1567
2131
  var CONVENTIONAL_TITLE_PATTERN = /^(feat|fix|perf|refactor|docs|test|chore|ci|build)(\([^)]+\))?!?:\s+\S/;
@@ -1571,11 +2135,11 @@ var PrDescriptionProtocolError = class extends Error {
1571
2135
  this.name = "PrDescriptionProtocolError";
1572
2136
  }
1573
2137
  };
1574
- var SECTION_HEADING_PATTERN2 = /^## (PR Title|PR Body)\s*$/gm;
1575
- function extractSections2(output) {
2138
+ var SECTION_HEADING_PATTERN = /^## (PR Title|PR Body)\s*$/gm;
2139
+ function extractSections(output) {
1576
2140
  const sections = [];
1577
2141
  let match;
1578
- const re = new RegExp(SECTION_HEADING_PATTERN2);
2142
+ const re = new RegExp(SECTION_HEADING_PATTERN);
1579
2143
  while ((match = re.exec(output)) !== null) {
1580
2144
  const heading = match[1];
1581
2145
  sections.push({
@@ -1586,7 +2150,7 @@ function extractSections2(output) {
1586
2150
  }
1587
2151
  return sections;
1588
2152
  }
1589
- function contentAfter2(output, sections, section) {
2153
+ function contentAfter(output, sections, section) {
1590
2154
  const index = sections.indexOf(section);
1591
2155
  const nextSection = sections[index + 1];
1592
2156
  const start = section.endIndex;
@@ -1594,7 +2158,7 @@ function contentAfter2(output, sections, section) {
1594
2158
  return output.slice(start, end).trim();
1595
2159
  }
1596
2160
  function parsePrDescription(output) {
1597
- const sections = extractSections2(output);
2161
+ const sections = extractSections(output);
1598
2162
  const titleSections = sections.filter((s) => s.heading === "PR Title");
1599
2163
  const bodySections = sections.filter((s) => s.heading === "PR Body");
1600
2164
  if (titleSections.length === 0) {
@@ -1617,8 +2181,8 @@ function parsePrDescription(output) {
1617
2181
  `Duplicate "## PR Body" sections found (${bodySections.length})`
1618
2182
  );
1619
2183
  }
1620
- const title = contentAfter2(output, sections, titleSections[0]);
1621
- const body = contentAfter2(output, sections, bodySections[0]);
2184
+ const title = contentAfter(output, sections, titleSections[0]);
2185
+ const body = contentAfter(output, sections, bodySections[0]);
1622
2186
  if (title.length === 0) {
1623
2187
  throw new PrDescriptionProtocolError('"## PR Title" section is empty');
1624
2188
  }
@@ -1649,7 +2213,7 @@ function inferConventionalType(commitSummaries) {
1649
2213
 
1650
2214
  // pr/pr-description-context.ts
1651
2215
  init_common();
1652
- import { join as join5 } from "path";
2216
+ import { join as join7 } from "path";
1653
2217
  import { readFile } from "fs/promises";
1654
2218
  async function collectFinalizerContext(options) {
1655
2219
  const { targetBase, branchName, worktreePath, reviewArtifactPath, logger } = options;
@@ -1710,7 +2274,7 @@ async function readReviewArtifact(artifactPath) {
1710
2274
  return content;
1711
2275
  }
1712
2276
  function buildFinalizerPrompt(context, promptTemplate) {
1713
- const artifactPathInWorktree = join5(
2277
+ const artifactPathInWorktree = join7(
1714
2278
  ".pourkit",
1715
2279
  ".tmp",
1716
2280
  "finalizer",
@@ -1788,13 +2352,13 @@ async function runFinalizerAgent(options) {
1788
2352
  finalizer.promptTemplate
1789
2353
  );
1790
2354
  const prompt = buildFinalizerPrompt(context, resolvedPrompt);
1791
- const artifactPathInWorktree = join6(
2355
+ const artifactPathInWorktree = join8(
1792
2356
  ".pourkit",
1793
2357
  ".tmp",
1794
2358
  "finalizer",
1795
2359
  "agent-output.md"
1796
2360
  );
1797
- const artifactPath = join6(worktreePath, artifactPathInWorktree);
2361
+ const artifactPath = join8(worktreePath, artifactPathInWorktree);
1798
2362
  prepareArtifactPath(artifactPath);
1799
2363
  let output = "";
1800
2364
  let parsed;
@@ -1859,24 +2423,24 @@ async function runFinalizerAgent(options) {
1859
2423
  }
1860
2424
  function loadFinalizerPrompt(repoRoot2, promptTemplate) {
1861
2425
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
1862
- if (existsSync3(promptPath)) {
1863
- return readFileSync3(promptPath, "utf-8");
2426
+ if (existsSync5(promptPath)) {
2427
+ return readFileSync5(promptPath, "utf-8");
1864
2428
  }
1865
2429
  return promptTemplate;
1866
2430
  }
1867
2431
  function prepareArtifactPath(artifactPath) {
1868
- mkdirSync3(dirname2(artifactPath), { recursive: true });
1869
- if (existsSync3(artifactPath)) {
2432
+ mkdirSync4(dirname3(artifactPath), { recursive: true });
2433
+ if (existsSync5(artifactPath)) {
1870
2434
  rmSync(artifactPath);
1871
2435
  }
1872
2436
  }
1873
2437
  function readAgentOutput(artifactPath) {
1874
- if (!existsSync3(artifactPath)) {
2438
+ if (!existsSync5(artifactPath)) {
1875
2439
  throw new Error(
1876
2440
  `Finalizer agent did not produce output at ${artifactPath}`
1877
2441
  );
1878
2442
  }
1879
- const output = readFileSync3(artifactPath, "utf-8");
2443
+ const output = readFileSync5(artifactPath, "utf-8");
1880
2444
  if (!output.trim()) {
1881
2445
  throw new Error(`Finalizer agent produced empty output at ${artifactPath}`);
1882
2446
  }
@@ -1884,9 +2448,9 @@ function readAgentOutput(artifactPath) {
1884
2448
  }
1885
2449
  async function persistGeneratedArtifact(worktreePath, output) {
1886
2450
  try {
1887
- const dir = join6(worktreePath, ".pourkit", ".tmp", "finalizer");
1888
- mkdirSync3(dir, { recursive: true });
1889
- writeFileSync2(join6(dir, "generated.md"), output, "utf-8");
2451
+ const dir = join8(worktreePath, ".pourkit", ".tmp", "finalizer");
2452
+ mkdirSync4(dir, { recursive: true });
2453
+ writeFileSync2(join8(dir, "generated.md"), output, "utf-8");
1890
2454
  } catch {
1891
2455
  }
1892
2456
  }
@@ -2124,14 +2688,14 @@ async function runMergeCoordinator(options) {
2124
2688
 
2125
2689
  // commands/review.ts
2126
2690
  import {
2127
- existsSync as existsSync4,
2128
- mkdirSync as mkdirSync4,
2129
- readFileSync as readFileSync4,
2691
+ existsSync as existsSync6,
2692
+ mkdirSync as mkdirSync5,
2693
+ readFileSync as readFileSync6,
2130
2694
  readdirSync,
2131
2695
  rmSync as rmSync2,
2132
2696
  writeFileSync as writeFileSync3
2133
2697
  } from "fs";
2134
- import { dirname as dirname3, join as join7 } from "path";
2698
+ import { dirname as dirname4, join as join9 } from "path";
2135
2699
 
2136
2700
  // pr/review-verdict.ts
2137
2701
  var ReviewVerdictProtocolError = class extends Error {
@@ -2213,12 +2777,12 @@ function extractLatestFindingIds(reviewOutput, iteration) {
2213
2777
  return ids;
2214
2778
  }
2215
2779
  function validateRefactorArtifact(artifactPath, findingIds) {
2216
- if (!existsSync4(artifactPath)) {
2780
+ if (!existsSync6(artifactPath)) {
2217
2781
  throw new RefactorArtifactValidationError(
2218
2782
  `Refactor artifact missing at ${artifactPath}`
2219
2783
  );
2220
2784
  }
2221
- const content = readFileSync4(artifactPath, "utf-8");
2785
+ const content = readFileSync6(artifactPath, "utf-8");
2222
2786
  if (!content.trim()) {
2223
2787
  throw new RefactorArtifactValidationError("Refactor artifact is empty");
2224
2788
  }
@@ -2363,13 +2927,13 @@ async function runReviewCommand(options) {
2363
2927
  if (!reviewer) {
2364
2928
  throw new Error("No reviewer config found");
2365
2929
  }
2366
- const artifactPathInWorktree = join7(
2930
+ const artifactPathInWorktree = join9(
2367
2931
  ".pourkit",
2368
2932
  ".tmp",
2369
2933
  "reviewers",
2370
2934
  `iteration-${iteration ?? 1}.md`
2371
2935
  );
2372
- const artifactPath = join7(worktreePath, artifactPathInWorktree);
2936
+ const artifactPath = join9(worktreePath, artifactPathInWorktree);
2373
2937
  prepareReviewArtifactPath(artifactPath);
2374
2938
  const prompt = buildReviewerPrompt(
2375
2939
  repoRoot2,
@@ -2476,8 +3040,8 @@ ${entry.trimEnd()}`).join("\n\n")}
2476
3040
  `;
2477
3041
  }
2478
3042
  function renderPriorRefactorArtifacts(worktreePath, currentIteration) {
2479
- const refactorsDir = join7(worktreePath, ".pourkit", ".tmp", "refactors");
2480
- if (!existsSync4(refactorsDir)) {
3043
+ const refactorsDir = join9(worktreePath, ".pourkit", ".tmp", "refactors");
3044
+ if (!existsSync6(refactorsDir)) {
2481
3045
  return "";
2482
3046
  }
2483
3047
  const files = readdirSync(refactorsDir);
@@ -2487,9 +3051,9 @@ function renderPriorRefactorArtifacts(worktreePath, currentIteration) {
2487
3051
  if (match) {
2488
3052
  const num = parseInt(match[1], 10);
2489
3053
  if (num < currentIteration) {
2490
- const filePath = join7(refactorsDir, file);
3054
+ const filePath = join9(refactorsDir, file);
2491
3055
  try {
2492
- const content = readFileSync4(filePath, "utf-8");
3056
+ const content = readFileSync6(filePath, "utf-8");
2493
3057
  if (content.trim()) {
2494
3058
  iterationFiles.push({ num, content });
2495
3059
  }
@@ -2514,8 +3078,8 @@ ${iterationsBlocks}
2514
3078
  `;
2515
3079
  }
2516
3080
  function renderPriorReviewerArtifacts(worktreePath, currentIteration) {
2517
- const reviewersDir = join7(worktreePath, ".pourkit", ".tmp", "reviewers");
2518
- if (!existsSync4(reviewersDir)) {
3081
+ const reviewersDir = join9(worktreePath, ".pourkit", ".tmp", "reviewers");
3082
+ if (!existsSync6(reviewersDir)) {
2519
3083
  return "";
2520
3084
  }
2521
3085
  const files = readdirSync(reviewersDir);
@@ -2525,9 +3089,9 @@ function renderPriorReviewerArtifacts(worktreePath, currentIteration) {
2525
3089
  if (match) {
2526
3090
  const num = parseInt(match[1], 10);
2527
3091
  if (num < currentIteration) {
2528
- const filePath = join7(reviewersDir, file);
3092
+ const filePath = join9(reviewersDir, file);
2529
3093
  try {
2530
- const content = readFileSync4(filePath, "utf-8");
3094
+ const content = readFileSync6(filePath, "utf-8");
2531
3095
  if (content.trim()) {
2532
3096
  iterationFiles.push({ num, content });
2533
3097
  }
@@ -2556,7 +3120,7 @@ function loadReviewerPromptTemplate(repoRoot2, promptTemplate, criteriaBlock) {
2556
3120
  repoRoot2,
2557
3121
  promptTemplate
2558
3122
  );
2559
- const promptBody = existsSync4(promptTemplatePath) ? readFileSync4(promptTemplatePath, "utf-8") : promptTemplate;
3123
+ const promptBody = existsSync6(promptTemplatePath) ? readFileSync6(promptTemplatePath, "utf-8") : promptTemplate;
2560
3124
  const hasCriteriaPlaceholder = promptBody.includes("{{REVIEW_CRITERIA}}");
2561
3125
  return {
2562
3126
  content: promptBody.replace(/\{\{REVIEW_CRITERIA\}\}/g, criteriaBlock),
@@ -2565,29 +3129,29 @@ function loadReviewerPromptTemplate(repoRoot2, promptTemplate, criteriaBlock) {
2565
3129
  }
2566
3130
  function renderReviewCriteria(repoRoot2, criteria) {
2567
3131
  return criteria.map((criterion) => {
2568
- const snippetPath = join7(
3132
+ const snippetPath = join9(
2569
3133
  repoRoot2,
2570
3134
  ".pourkit",
2571
3135
  "prompts",
2572
3136
  `reviewer-${criterion}.snippet.md`
2573
3137
  );
2574
- if (existsSync4(snippetPath)) {
2575
- return readFileSync4(snippetPath, "utf-8").trimEnd();
3138
+ if (existsSync6(snippetPath)) {
3139
+ return readFileSync6(snippetPath, "utf-8").trimEnd();
2576
3140
  }
2577
3141
  return `- ${criterion}`;
2578
3142
  }).join("\n\n");
2579
3143
  }
2580
3144
  function prepareReviewArtifactPath(artifactPath) {
2581
- mkdirSync4(dirname3(artifactPath), { recursive: true });
2582
- if (existsSync4(artifactPath)) {
3145
+ mkdirSync5(dirname4(artifactPath), { recursive: true });
3146
+ if (existsSync6(artifactPath)) {
2583
3147
  rmSync2(artifactPath);
2584
3148
  }
2585
3149
  }
2586
3150
  function recoverReviewOutputFromLog(logPath) {
2587
- if (!existsSync4(logPath)) {
3151
+ if (!existsSync6(logPath)) {
2588
3152
  return null;
2589
3153
  }
2590
- const logContent = readFileSync4(logPath, "utf-8");
3154
+ const logContent = readFileSync6(logPath, "utf-8");
2591
3155
  const startIndex = logContent.indexOf("## Findings");
2592
3156
  if (startIndex === -1) {
2593
3157
  return null;
@@ -2602,8 +3166,8 @@ function recoverReviewOutputFromLog(logPath) {
2602
3166
  return recoveredOutput.length > 0 ? recoveredOutput : null;
2603
3167
  }
2604
3168
  function readReviewArtifact2(artifactPath, logPath) {
2605
- if (existsSync4(artifactPath)) {
2606
- const output = readFileSync4(artifactPath, "utf-8");
3169
+ if (existsSync6(artifactPath)) {
3170
+ const output = readFileSync6(artifactPath, "utf-8");
2607
3171
  if (output.trim()) {
2608
3172
  return output;
2609
3173
  }
@@ -2613,7 +3177,7 @@ function readReviewArtifact2(artifactPath, logPath) {
2613
3177
  writeFileSync3(artifactPath, recoveredOutput, "utf-8");
2614
3178
  return recoveredOutput;
2615
3179
  }
2616
- if (!existsSync4(artifactPath)) {
3180
+ if (!existsSync6(artifactPath)) {
2617
3181
  throw new Error(`Reviewer did not produce output at ${artifactPath}`);
2618
3182
  }
2619
3183
  throw new Error(`Reviewer produced empty output at ${artifactPath}`);
@@ -2629,7 +3193,8 @@ async function runReviewWithRefactorLoop(options) {
2629
3193
  repoRoot: repoRoot2,
2630
3194
  logger,
2631
3195
  startingLifetimeIteration = 0,
2632
- humanHandoffResolved
3196
+ humanHandoffResolved,
3197
+ serena
2633
3198
  } = options;
2634
3199
  const strategy = target.strategy;
2635
3200
  const reviewer = strategy.review.reviewer;
@@ -2644,7 +3209,7 @@ async function runReviewWithRefactorLoop(options) {
2644
3209
  const passWithNotesRefactorAttempts = strategy.review.passWithNotesRefactorAttempts;
2645
3210
  let resolvedStartingIteration = startingLifetimeIteration;
2646
3211
  {
2647
- const reviewersDir = join7(worktreePath, ".pourkit", ".tmp", "reviewers");
3212
+ const reviewersDir = join9(worktreePath, ".pourkit", ".tmp", "reviewers");
2648
3213
  try {
2649
3214
  const files = readdirSync(reviewersDir);
2650
3215
  let maxExistingIteration = 0;
@@ -2752,7 +3317,7 @@ async function runReviewWithRefactorLoop(options) {
2752
3317
  }
2753
3318
  if (reviewResult.verdict === "NEEDS_REFACTOR" || reviewResult.verdict === "PASS_WITH_NOTES" || reviewResult.verdict === "FAIL") {
2754
3319
  logger.step("info", "Running refactor agent");
2755
- const refactorArtifactPathInWorktree = join7(
3320
+ const refactorArtifactPathInWorktree = join9(
2756
3321
  ".pourkit",
2757
3322
  ".tmp",
2758
3323
  "refactors",
@@ -2786,6 +3351,7 @@ async function runReviewWithRefactorLoop(options) {
2786
3351
  sections: STAGE_SECTIONS.refactor
2787
3352
  })
2788
3353
  ],
3354
+ ...serena ? { serena } : {},
2789
3355
  logger
2790
3356
  });
2791
3357
  if (!refactorResult.success) {
@@ -2808,7 +3374,7 @@ async function runReviewWithRefactorLoop(options) {
2808
3374
  reviewResult.output,
2809
3375
  lifetimeIteration
2810
3376
  );
2811
- const refactorArtifactPath = join7(
3377
+ const refactorArtifactPath = join9(
2812
3378
  worktreePath,
2813
3379
  refactorArtifactPathInWorktree
2814
3380
  );
@@ -2858,9 +3424,9 @@ async function runReviewWithRefactorLoop(options) {
2858
3424
  }
2859
3425
  async function writeArtifact(worktreePath, filename, output) {
2860
3426
  try {
2861
- const dir = join7(worktreePath, ".pourkit", ".tmp", "reviewers");
2862
- mkdirSync4(dir, { recursive: true });
2863
- writeFileSync3(join7(dir, filename), output, "utf-8");
3427
+ const dir = join9(worktreePath, ".pourkit", ".tmp", "reviewers");
3428
+ mkdirSync5(dir, { recursive: true });
3429
+ writeFileSync3(join9(dir, filename), output, "utf-8");
2864
3430
  } catch {
2865
3431
  }
2866
3432
  }
@@ -2872,7 +3438,7 @@ function buildRefactorPrompt(repoRoot2, promptTemplate, latestReview, artifactPa
2872
3438
  repoRoot2,
2873
3439
  promptTemplate
2874
3440
  );
2875
- const promptBody = existsSync4(promptTemplatePath) ? readFileSync4(promptTemplatePath, "utf-8") : promptTemplate;
3441
+ const promptBody = existsSync6(promptTemplatePath) ? readFileSync6(promptTemplatePath, "utf-8") : promptTemplate;
2876
3442
  return appendProtectedWorkGuidance(`${promptBody}
2877
3443
 
2878
3444
  ## Shared Run Context
@@ -3050,6 +3616,16 @@ function checkIssueGates(issue, config, force) {
3050
3616
  }
3051
3617
  return { allowed: true, gates };
3052
3618
  }
3619
+ function resolveSerenaRuntimeConfig(config, target) {
3620
+ return {
3621
+ enabled: target.serena?.enabled ?? config.serena.enabled,
3622
+ required: target.serena?.required ?? config.serena.required,
3623
+ autoStart: config.serena.autoStart,
3624
+ dataDir: config.serena.dataDir,
3625
+ mcpUrl: config.serena.mcpUrl,
3626
+ sandboxMcpUrl: config.serena.sandboxMcpUrl
3627
+ };
3628
+ }
3053
3629
  async function startIssueRun(options) {
3054
3630
  const {
3055
3631
  issueNumber,
@@ -3070,6 +3646,35 @@ async function startIssueRun(options) {
3070
3646
  const target = resolveTarget(config, targetName);
3071
3647
  const branchName = renderBranchName(target.branchTemplate, issue);
3072
3648
  const strategy = target.strategy;
3649
+ const serenaRuntimeConfig = resolveSerenaRuntimeConfig(config, target);
3650
+ const shouldPrepareSerena = serenaRuntimeConfig.enabled || serenaRuntimeConfig.required;
3651
+ let serenaExecutionContext;
3652
+ if (shouldPrepareSerena) {
3653
+ const serenaPreflight = await prepareSerenaForTarget({
3654
+ repoRoot: ROOT,
3655
+ targetName: target.name,
3656
+ baseBranch: target.baseBranch,
3657
+ dataDir: serenaRuntimeConfig.dataDir,
3658
+ mcpUrl: serenaRuntimeConfig.mcpUrl,
3659
+ enabled: shouldPrepareSerena,
3660
+ required: serenaRuntimeConfig.required,
3661
+ autoStart: serenaRuntimeConfig.autoStart,
3662
+ logger
3663
+ });
3664
+ if (serenaPreflight.enabled && serenaPreflight.available) {
3665
+ serenaExecutionContext = {
3666
+ available: true,
3667
+ sandboxMcpUrl: serenaRuntimeConfig.sandboxMcpUrl
3668
+ };
3669
+ }
3670
+ if (serenaPreflight.enabled && !serenaPreflight.available) {
3671
+ const message = `Serena preflight unavailable for target ${target.name}: ${serenaPreflight.error}`;
3672
+ if (serenaRuntimeConfig.required) {
3673
+ throw new Error(message);
3674
+ }
3675
+ logger.step("warn", message);
3676
+ }
3677
+ }
3073
3678
  if (options.resetWorktree) {
3074
3679
  const existingPr = await prProvider.getPr(branchName);
3075
3680
  if (existingPr && existingPr.state === "OPEN") {
@@ -3093,7 +3698,7 @@ async function startIssueRun(options) {
3093
3698
  const worktreeState = resolution.worktreePath ? readWorktreeRunState(resolution.worktreePath) : null;
3094
3699
  if (resolution.mode !== "new") {
3095
3700
  const existingPr = await prProvider.getPr(branchName);
3096
- const refreshResult = await refreshStaleIssueBranch({
3701
+ const exit = await runBaseRefreshAttempt({
3097
3702
  worktreePath: resolution.worktreePath,
3098
3703
  baseBranch: target.baseBranch,
3099
3704
  localGitBaseRef: resolution.baseRef,
@@ -3101,113 +3706,85 @@ async function startIssueRun(options) {
3101
3706
  prNumber: existingPr?.number,
3102
3707
  prState: existingPr?.state
3103
3708
  });
3104
- if (refreshResult.status === "refreshed") {
3105
- if (worktreeState?.completedStages.builder) {
3106
- const invalidatedState = invalidateAfterBaseRefresh(worktreeState);
3107
- writeWorktreeRunState(resolution.worktreePath, invalidatedState);
3108
- }
3109
- } else if (refreshResult.status === "conflicted") {
3110
- if (strategy.conflictResolution && resolution.worktreePath) {
3111
- const crLoopResult = await runConflictResolutionLoop({
3112
- executionProvider,
3113
- config,
3114
- target,
3115
- issue,
3116
- branchName,
3117
- worktreePath: resolution.worktreePath,
3118
- repoRoot: ROOT,
3119
- initialConflictedPaths: refreshResult.conflictedPaths,
3120
- maxAttempts: strategy.conflictResolution.maxAttempts,
3121
- logger
3122
- });
3123
- if (crLoopResult.status === "completed") {
3124
- if (strategy.verify?.commands) {
3125
- for (const cmd of strategy.verify.commands) {
3126
- await execCapture("bash", ["-lc", cmd.command], {
3127
- cwd: resolution.worktreePath,
3128
- logger,
3129
- label: `verify ${cmd.label}`
3130
- });
3131
- }
3132
- }
3133
- if (worktreeState?.completedStages.builder) {
3134
- const invalidatedState = invalidateAfterBaseRefresh(worktreeState);
3135
- writeWorktreeRunState(resolution.worktreePath, invalidatedState);
3136
- }
3137
- } else {
3138
- const failureMessage = crLoopResult.status === "ambiguous" ? `Conflict resolution ambiguous: ${crLoopResult.message}` : crLoopResult.status === "exhausted" ? `Conflict resolution maxAttempts (${strategy.conflictResolution.maxAttempts}) exhausted: ${crLoopResult.message}` : `Conflict resolution failed: ${crLoopResult.message}`;
3139
- const failureStage = "conflictResolution";
3140
- if (worktreeState) {
3141
- updateWorktreeRunState(resolution.worktreePath, {
3142
- lastFailure: {
3143
- stage: failureStage,
3144
- message: failureMessage
3145
- }
3709
+ if (Exit2.isSuccess(exit)) {
3710
+ const refreshResult = exit.value;
3711
+ if (refreshResult.status === "refreshed") {
3712
+ if (worktreeState?.completedStages.builder) {
3713
+ const invalidatedState = invalidateAfterBaseRefresh(worktreeState);
3714
+ writeWorktreeRunState(resolution.worktreePath, invalidatedState);
3715
+ }
3716
+ }
3717
+ } else {
3718
+ const cause = exit.cause;
3719
+ if (cause._tag === "Fail") {
3720
+ const failure = cause.error;
3721
+ if (failure instanceof RebaseConflict) {
3722
+ if (strategy.failureResolution && resolution.worktreePath) {
3723
+ await handleRebaseConflict(failure, {
3724
+ worktreePath: resolution.worktreePath,
3725
+ branchName,
3726
+ target,
3727
+ config,
3728
+ issueNumber,
3729
+ issueProvider,
3730
+ executionProvider,
3731
+ repoRoot: ROOT,
3732
+ worktreeState,
3733
+ logger
3146
3734
  });
3147
3735
  } else {
3148
- writeWorktreeRunState(resolution.worktreePath, {
3149
- issueNumber,
3150
- targetName: target.name,
3151
- branchName,
3152
- baseBranch: target.baseBranch,
3153
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3154
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3155
- completedStages: {},
3156
- review: { lifetimeIterations: 0 },
3157
- lastFailure: {
3158
- stage: failureStage,
3159
- message: failureMessage
3736
+ if (resolution.worktreePath) {
3737
+ if (worktreeState) {
3738
+ updateWorktreeRunState(resolution.worktreePath, {
3739
+ lastFailure: {
3740
+ stage: "baseRefresh",
3741
+ message: `Base refresh conflict detected. Handing off to human: ${failure.message}`
3742
+ }
3743
+ });
3744
+ } else {
3745
+ writeWorktreeRunState(resolution.worktreePath, {
3746
+ issueNumber,
3747
+ targetName: target.name,
3748
+ branchName,
3749
+ baseBranch: target.baseBranch,
3750
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3751
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3752
+ completedStages: {},
3753
+ review: { lifetimeIterations: 0 },
3754
+ lastFailure: {
3755
+ stage: "baseRefresh",
3756
+ message: `Base refresh conflict detected. Handing off to human: ${failure.message}`
3757
+ }
3758
+ });
3160
3759
  }
3161
- });
3760
+ }
3761
+ await transitionIssueToFailureState(
3762
+ issueProvider,
3763
+ issueNumber,
3764
+ config,
3765
+ `Base refresh conflicted: ${failure.message}. Worktree preserved at ${resolution.worktreePath}.`,
3766
+ logger
3767
+ );
3768
+ throw new Error(`Base refresh conflicted: ${failure.message}`);
3162
3769
  }
3163
- await transitionIssueToFailureState(
3164
- issueProvider,
3165
- issueNumber,
3770
+ } else if (failure instanceof PublishedHistoryRisk) {
3771
+ await handlePublishedHistoryRisk(failure, {
3772
+ worktreePath: resolution.worktreePath,
3773
+ branchName,
3774
+ target,
3166
3775
  config,
3167
- failureMessage,
3776
+ issueNumber,
3777
+ issueProvider,
3778
+ worktreeState,
3168
3779
  logger
3169
- );
3170
- throw new Error(failureMessage);
3780
+ });
3781
+ } else {
3782
+ throw new Error(`Base refresh failed: ${failure.message}`);
3171
3783
  }
3172
3784
  } else {
3173
- if (resolution.worktreePath) {
3174
- if (worktreeState) {
3175
- updateWorktreeRunState(resolution.worktreePath, {
3176
- lastFailure: {
3177
- stage: "baseRefresh",
3178
- message: `Base refresh conflict detected. Handing off to human: ${refreshResult.message}`
3179
- }
3180
- });
3181
- } else {
3182
- writeWorktreeRunState(resolution.worktreePath, {
3183
- issueNumber,
3184
- targetName: target.name,
3185
- branchName,
3186
- baseBranch: target.baseBranch,
3187
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3188
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3189
- completedStages: {},
3190
- review: { lifetimeIterations: 0 },
3191
- lastFailure: {
3192
- stage: "baseRefresh",
3193
- message: `Base refresh conflict detected. Handing off to human: ${refreshResult.message}`
3194
- }
3195
- });
3196
- }
3197
- }
3198
- await transitionIssueToFailureState(
3199
- issueProvider,
3200
- issueNumber,
3201
- config,
3202
- `Base refresh conflicted: ${refreshResult.message}. Worktree preserved at ${resolution.worktreePath}.`,
3203
- logger
3204
- );
3205
- throw new Error(`Base refresh conflicted: ${refreshResult.message}`);
3785
+ const defectMessage = cause._tag === "Die" ? `Base refresh failed with unexpected error: ${cause.defect}` : `Base refresh failed with unhandled cause: ${cause._tag}`;
3786
+ throw new Error(defectMessage);
3206
3787
  }
3207
- } else if (refreshResult.status === "refused-published-history") {
3208
- throw new Error(
3209
- `Cannot auto-refresh published history: PR #${refreshResult.prNumber} (${refreshResult.prState}) exists for branch ${branchName}`
3210
- );
3211
3788
  }
3212
3789
  }
3213
3790
  const runContextArtifact = buildRunContextArtifact({
@@ -3232,6 +3809,7 @@ async function startIssueRun(options) {
3232
3809
  branchName,
3233
3810
  ...resolution.mode === "new" ? { baseRef: resolution.baseRef } : {},
3234
3811
  sandbox: config.sandbox,
3812
+ ...serenaExecutionContext ? { serena: serenaExecutionContext } : {},
3235
3813
  autoApprove: true,
3236
3814
  timeoutMs: EXECUTION_TIMEOUT_MS,
3237
3815
  ...resolution.worktreePath ? { worktreePath: resolution.worktreePath } : {},
@@ -3274,7 +3852,8 @@ async function startIssueRun(options) {
3274
3852
  target,
3275
3853
  branchName,
3276
3854
  worktreeState: finalWorktreeState,
3277
- executionResult
3855
+ executionResult,
3856
+ ...serenaExecutionContext ? { serena: serenaExecutionContext } : {}
3278
3857
  };
3279
3858
  }
3280
3859
  async function advanceIssueRunReview(options) {
@@ -3353,12 +3932,12 @@ async function completeIssueRun(options) {
3353
3932
  prTitle = finalizerFromState.title;
3354
3933
  prBody = finalizerFromState.body;
3355
3934
  } else if (finalizerFromState.artifactPath) {
3356
- if (!existsSync5(finalizerFromState.artifactPath)) {
3935
+ if (!existsSync7(finalizerFromState.artifactPath)) {
3357
3936
  throw new Error(
3358
3937
  `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
3359
3938
  );
3360
3939
  }
3361
- const artifactContent = readFileSync5(
3940
+ const artifactContent = readFileSync7(
3362
3941
  finalizerFromState.artifactPath,
3363
3942
  "utf-8"
3364
3943
  );
@@ -3445,6 +4024,13 @@ async function completeIssueRun(options) {
3445
4024
  if (prFromState && prFromState.state === "OPEN") {
3446
4025
  pr = prFromState;
3447
4026
  } else {
4027
+ await guardFinalPublishContent({
4028
+ worktreePath: executionResult.worktreePath,
4029
+ baseRef: `origin/${target.baseBranch}`,
4030
+ title: prTitle,
4031
+ body: finalBody,
4032
+ logger
4033
+ });
3448
4034
  await execCapture("git", ["push", "-u", "origin", branchName], {
3449
4035
  cwd: executionResult.worktreePath,
3450
4036
  logger,
@@ -3644,12 +4230,106 @@ async function finalizeWorktreeCommit(options) {
3644
4230
  logger,
3645
4231
  label: "git add"
3646
4232
  });
4233
+ await guardFinalCommitContent({
4234
+ worktreePath,
4235
+ title,
4236
+ body,
4237
+ logger
4238
+ });
3647
4239
  await execCapture("git", ["commit", "--no-verify", "-m", title, "-m", body], {
3648
4240
  cwd: worktreePath,
3649
4241
  logger,
3650
4242
  label: "git commit"
3651
4243
  });
3652
4244
  }
4245
+ async function guardFinalCommitContent(options) {
4246
+ const { worktreePath, title, body, logger } = options;
4247
+ const stagedDiff = await execCapture("git", ["diff", "--cached"], {
4248
+ cwd: worktreePath,
4249
+ logger,
4250
+ label: "git diff --cached secret guard"
4251
+ });
4252
+ assertNoSecretLikeContent([
4253
+ { source: "staged diff", content: stagedDiff.stdout },
4254
+ { source: "commit title", content: title },
4255
+ { source: "commit body", content: body }
4256
+ ]);
4257
+ }
4258
+ async function guardFinalPublishContent(options) {
4259
+ const { worktreePath, baseRef, title, body, logger } = options;
4260
+ const finalDiff = await execCapture("git", ["diff", `${baseRef}...HEAD`], {
4261
+ cwd: worktreePath,
4262
+ logger,
4263
+ label: "git diff final secret guard"
4264
+ });
4265
+ assertNoSecretLikeContent([
4266
+ { source: "final diff", content: finalDiff.stdout },
4267
+ { source: "PR title", content: title },
4268
+ { source: "PR body", content: body }
4269
+ ]);
4270
+ }
4271
+ function assertNoSecretLikeContent(inputs) {
4272
+ const findings = inputs.flatMap(
4273
+ ({ source, content }) => findSecretLikeContent(source, content ?? "")
4274
+ );
4275
+ if (findings.length === 0) {
4276
+ return;
4277
+ }
4278
+ const summary = findings.slice(0, 5).map((finding) => `${finding.source}: ${finding.kind}`).join("; ");
4279
+ throw new Error(
4280
+ `Secret-like content detected before finalization (${summary}). Redact it before committing, pushing, or creating a PR.`
4281
+ );
4282
+ }
4283
+ function findSecretLikeContent(source, content) {
4284
+ const findings = [];
4285
+ const patterns = [
4286
+ {
4287
+ kind: "private key block",
4288
+ regex: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/gi
4289
+ },
4290
+ {
4291
+ kind: "GitHub token",
4292
+ regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g
4293
+ },
4294
+ {
4295
+ kind: "GitHub fine-grained token",
4296
+ regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g
4297
+ },
4298
+ { kind: "npm token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g },
4299
+ { kind: "OpenAI key", regex: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g },
4300
+ { kind: "AWS access key", regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g },
4301
+ {
4302
+ kind: "authorization bearer token",
4303
+ regex: /\bAuthorization\s*:\s*Bearer\s+[^\s`'\"]{12,}/gi
4304
+ },
4305
+ {
4306
+ kind: "secret assignment",
4307
+ regex: /\b[A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CLIENT[_-]?SECRET|DATABASE[_-]?URL)[A-Z0-9_]*\s*[:=]\s*["']?[^\s"'`]{8,}/gi
4308
+ }
4309
+ ];
4310
+ for (const { kind, regex } of patterns) {
4311
+ for (const match of content.matchAll(regex)) {
4312
+ if (isAllowedSecretPlaceholder(match[0])) {
4313
+ continue;
4314
+ }
4315
+ findings.push({ source, kind });
4316
+ break;
4317
+ }
4318
+ }
4319
+ return findings;
4320
+ }
4321
+ function isAllowedSecretPlaceholder(value) {
4322
+ const normalized = value.toLowerCase();
4323
+ return [
4324
+ "<redacted>",
4325
+ "<example",
4326
+ "example-",
4327
+ "dummy-",
4328
+ "test-",
4329
+ "placeholder",
4330
+ "xxxxx"
4331
+ ].some((allowed) => normalized.includes(allowed));
4332
+ }
3653
4333
  async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
3654
4334
  const remoteBase = parseRemoteBaseRef(baseRef);
3655
4335
  if (!remoteBase) {
@@ -3865,7 +4545,7 @@ async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
3865
4545
  return { mode: "new", branchName, baseRef };
3866
4546
  }
3867
4547
  function issueWorktreePath(root, branchName) {
3868
- return join8(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
4548
+ return join10(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
3869
4549
  }
3870
4550
  function resolveRegisteredIssueWorktreePath(worktreeListPorcelain, root, branchName) {
3871
4551
  const branchWorktreePath = parseWorktreeListPorcelain(
@@ -3893,12 +4573,251 @@ async function syncTargetBranch(root, baseBranch, logger) {
3893
4573
  }
3894
4574
  function loadBuilderPrompt(repoRoot2, promptTemplate) {
3895
4575
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
3896
- const promptBody = existsSync5(promptPath) ? readFileSync5(promptPath, "utf-8") : promptTemplate;
4576
+ const promptBody = existsSync7(promptPath) ? readFileSync7(promptPath, "utf-8") : promptTemplate;
3897
4577
  return appendProtectedWorkGuidance(`${promptBody}
3898
4578
 
3899
4579
  ## Shared Run Context
3900
4580
 
3901
- Read the selected issue requirements, comments, branch context, validation commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}`);
4581
+ Read the selected issue requirements, comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}`);
4582
+ }
4583
+ async function handleRebaseConflict(failure, context) {
4584
+ const frConfig = context.target.strategy.failureResolution;
4585
+ if (!frConfig) {
4586
+ await transitionIssueToFailureState(
4587
+ context.issueProvider,
4588
+ context.issueNumber,
4589
+ context.config,
4590
+ "No failureResolution configured",
4591
+ context.logger
4592
+ );
4593
+ throw new Error("Base refresh conflicted: no failureResolution configured");
4594
+ }
4595
+ const maxAttempts = frConfig.failureLimits?.RebaseConflict ?? frConfig.maxAttemptsPerFailure;
4596
+ let attemptNumber = 0;
4597
+ let currentFailure = failure;
4598
+ while (attemptNumber < maxAttempts) {
4599
+ attemptNumber++;
4600
+ const packet = constructFailureResolutionPacket(currentFailure, {
4601
+ stageName: "baseRefresh",
4602
+ attemptNumber,
4603
+ worktreePath: context.worktreePath,
4604
+ branchName: context.branchName,
4605
+ baseBranch: context.target.baseBranch,
4606
+ maxAttempts,
4607
+ allowedDecisions: ["RETRY_STAGE", "HANDOFF_TO_HUMAN", "FAIL_RUN"],
4608
+ artifactTarget: `.pourkit/.tmp/failure-resolution/attempt-${attemptNumber}.md`
4609
+ });
4610
+ const result = await runFailureResolutionAgent({
4611
+ executionProvider: context.executionProvider,
4612
+ config: context.config,
4613
+ target: context.target,
4614
+ failure: currentFailure,
4615
+ packet,
4616
+ worktreePath: context.worktreePath,
4617
+ repoRoot: context.repoRoot,
4618
+ logger: context.logger
4619
+ });
4620
+ if (result.status === "handoff") {
4621
+ if (context.worktreeState) {
4622
+ updateWorktreeRunState(context.worktreePath, {
4623
+ lastFailure: { stage: "baseRefresh", message: result.reason }
4624
+ });
4625
+ } else {
4626
+ writeWorktreeRunState(context.worktreePath, {
4627
+ issueNumber: context.issueNumber,
4628
+ targetName: context.target.name,
4629
+ branchName: context.branchName,
4630
+ baseBranch: context.target.baseBranch,
4631
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4632
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4633
+ completedStages: {},
4634
+ review: { lifetimeIterations: 0 },
4635
+ lastFailure: { stage: "baseRefresh", message: result.reason }
4636
+ });
4637
+ }
4638
+ await transitionIssueToFailureState(
4639
+ context.issueProvider,
4640
+ context.issueNumber,
4641
+ context.config,
4642
+ result.reason,
4643
+ context.logger
4644
+ );
4645
+ throw new Error(result.reason);
4646
+ }
4647
+ if (result.status === "fail-run") {
4648
+ if (context.worktreeState) {
4649
+ updateWorktreeRunState(context.worktreePath, {
4650
+ lastFailure: { stage: "baseRefresh", message: result.reason }
4651
+ });
4652
+ } else {
4653
+ writeWorktreeRunState(context.worktreePath, {
4654
+ issueNumber: context.issueNumber,
4655
+ targetName: context.target.name,
4656
+ branchName: context.branchName,
4657
+ baseBranch: context.target.baseBranch,
4658
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4659
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4660
+ completedStages: {},
4661
+ review: { lifetimeIterations: 0 },
4662
+ lastFailure: { stage: "baseRefresh", message: result.reason }
4663
+ });
4664
+ }
4665
+ throw new Error(result.reason);
4666
+ }
4667
+ const changedPaths = packet.conflictedPaths ?? currentFailure.conflictedPaths;
4668
+ const markersRemain = await hasUnresolvedConflictMarkers(
4669
+ context.worktreePath,
4670
+ changedPaths
4671
+ );
4672
+ if (markersRemain) {
4673
+ const message = "Conflict resolution agent resolved artifact but conflict markers remain in files";
4674
+ if (context.worktreeState) {
4675
+ updateWorktreeRunState(context.worktreePath, {
4676
+ lastFailure: { stage: "baseRefresh", message }
4677
+ });
4678
+ } else {
4679
+ writeWorktreeRunState(context.worktreePath, {
4680
+ issueNumber: context.issueNumber,
4681
+ targetName: context.target.name,
4682
+ branchName: context.branchName,
4683
+ baseBranch: context.target.baseBranch,
4684
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4685
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4686
+ completedStages: {},
4687
+ review: { lifetimeIterations: 0 },
4688
+ lastFailure: { stage: "baseRefresh", message }
4689
+ });
4690
+ }
4691
+ await transitionIssueToFailureState(
4692
+ context.issueProvider,
4693
+ context.issueNumber,
4694
+ context.config,
4695
+ message,
4696
+ context.logger
4697
+ );
4698
+ throw new Error(message);
4699
+ }
4700
+ await execCapture("git", ["add", ...changedPaths], {
4701
+ cwd: context.worktreePath,
4702
+ logger: context.logger,
4703
+ label: "git add conflicted paths"
4704
+ });
4705
+ try {
4706
+ await execCapture("git", ["rebase", "--continue"], {
4707
+ cwd: context.worktreePath,
4708
+ env: { ...process.env, GIT_EDITOR: "true" },
4709
+ logger: context.logger,
4710
+ label: "git rebase --continue"
4711
+ });
4712
+ if (context.worktreeState?.completedStages.builder) {
4713
+ const invalidatedState = invalidateAfterBaseRefresh(
4714
+ context.worktreeState
4715
+ );
4716
+ writeWorktreeRunState(context.worktreePath, invalidatedState);
4717
+ }
4718
+ return;
4719
+ } catch {
4720
+ const statusResult = await execCapture("git", ["status", "--porcelain"], {
4721
+ cwd: context.worktreePath,
4722
+ logger: context.logger,
4723
+ label: "git status"
4724
+ });
4725
+ const newConflictedPaths = statusResult.stdout.split("\n").filter((line) => /^(AA|DD|UU|AU|UA|DU|UD)\s/.test(line)).map((line) => line.slice(3).trim()).filter(Boolean);
4726
+ if (newConflictedPaths.length === 0) {
4727
+ const message = "git rebase --continue failed with no remaining conflicts";
4728
+ if (context.worktreeState) {
4729
+ updateWorktreeRunState(context.worktreePath, {
4730
+ lastFailure: { stage: "baseRefresh", message }
4731
+ });
4732
+ } else {
4733
+ writeWorktreeRunState(context.worktreePath, {
4734
+ issueNumber: context.issueNumber,
4735
+ targetName: context.target.name,
4736
+ branchName: context.branchName,
4737
+ baseBranch: context.target.baseBranch,
4738
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4739
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4740
+ completedStages: {},
4741
+ review: { lifetimeIterations: 0 },
4742
+ lastFailure: { stage: "baseRefresh", message }
4743
+ });
4744
+ }
4745
+ await transitionIssueToFailureState(
4746
+ context.issueProvider,
4747
+ context.issueNumber,
4748
+ context.config,
4749
+ message,
4750
+ context.logger
4751
+ );
4752
+ throw new Error(message);
4753
+ }
4754
+ currentFailure = new RebaseConflict({
4755
+ conflictedPaths: newConflictedPaths,
4756
+ message: `Rebase conflict in: ${newConflictedPaths.join(", ")}`
4757
+ });
4758
+ }
4759
+ }
4760
+ if (context.worktreeState) {
4761
+ updateWorktreeRunState(context.worktreePath, {
4762
+ lastFailure: {
4763
+ stage: "baseRefresh",
4764
+ message: `Base refresh recovery exhausted after ${maxAttempts} attempts`
4765
+ }
4766
+ });
4767
+ } else {
4768
+ writeWorktreeRunState(context.worktreePath, {
4769
+ issueNumber: context.issueNumber,
4770
+ targetName: context.target.name,
4771
+ branchName: context.branchName,
4772
+ baseBranch: context.target.baseBranch,
4773
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4774
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4775
+ completedStages: {},
4776
+ review: { lifetimeIterations: 0 },
4777
+ lastFailure: {
4778
+ stage: "baseRefresh",
4779
+ message: `Base refresh recovery exhausted after ${maxAttempts} attempts`
4780
+ }
4781
+ });
4782
+ }
4783
+ await transitionIssueToFailureState(
4784
+ context.issueProvider,
4785
+ context.issueNumber,
4786
+ context.config,
4787
+ `Base refresh recovery exhausted after ${maxAttempts} attempts`,
4788
+ context.logger
4789
+ );
4790
+ throw new Error(
4791
+ `Base refresh recovery exhausted after ${maxAttempts} attempts`
4792
+ );
4793
+ }
4794
+ async function handlePublishedHistoryRisk(failure, context) {
4795
+ const message = `Cannot auto-refresh published history: PR #${failure.prNumber} (${failure.prState}) exists for branch ${context.branchName}`;
4796
+ if (context.worktreeState) {
4797
+ updateWorktreeRunState(context.worktreePath, {
4798
+ lastFailure: { stage: "baseRefresh", message }
4799
+ });
4800
+ } else {
4801
+ writeWorktreeRunState(context.worktreePath, {
4802
+ issueNumber: context.issueNumber,
4803
+ targetName: context.target.name,
4804
+ branchName: context.branchName,
4805
+ baseBranch: context.target.baseBranch,
4806
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4807
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4808
+ completedStages: {},
4809
+ review: { lifetimeIterations: 0 },
4810
+ lastFailure: { stage: "baseRefresh", message }
4811
+ });
4812
+ }
4813
+ await transitionIssueToFailureState(
4814
+ context.issueProvider,
4815
+ context.issueNumber,
4816
+ context.config,
4817
+ message,
4818
+ context.logger
4819
+ );
4820
+ throw new Error(message);
3902
4821
  }
3903
4822
 
3904
4823
  // commands/issue.ts
@@ -3941,7 +4860,8 @@ async function runIssueCommand(options) {
3941
4860
  repoRoot: ROOT,
3942
4861
  logger,
3943
4862
  startingLifetimeIteration: lifetimeIterationsFromState,
3944
- humanHandoffResolved
4863
+ humanHandoffResolved,
4864
+ serena: startResult.serena
3945
4865
  });
3946
4866
  if (reviewResult.exhaustedMaxIterations) {
3947
4867
  throw new Error(
@@ -3986,6 +4906,95 @@ async function runIssueCommand(options) {
3986
4906
  }
3987
4907
  }
3988
4908
 
4909
+ // commands/issue-create.ts
4910
+ import { readFile as readFile3 } from "fs/promises";
4911
+ function isFlag(value) {
4912
+ return value.startsWith("--");
4913
+ }
4914
+ function requireFlagValue(flag, value) {
4915
+ if (!value || isFlag(value)) {
4916
+ throw new Error(`${flag} requires a value`);
4917
+ }
4918
+ return value;
4919
+ }
4920
+ function parseIssueCreateArgs(args) {
4921
+ let title;
4922
+ let body;
4923
+ let bodyFile;
4924
+ const labels = [];
4925
+ const assignees = [];
4926
+ const remaining = [];
4927
+ let i = 0;
4928
+ while (i < args.length) {
4929
+ const arg = args[i];
4930
+ if (arg === "--title") {
4931
+ title = requireFlagValue("--title", args[i + 1]);
4932
+ i += 2;
4933
+ } else if (arg === "--body") {
4934
+ body = requireFlagValue("--body", args[i + 1]);
4935
+ i += 2;
4936
+ } else if (arg === "--body-file") {
4937
+ bodyFile = requireFlagValue("--body-file", args[i + 1]);
4938
+ i += 2;
4939
+ } else if (arg === "--label") {
4940
+ const label = requireFlagValue("--label", args[i + 1]);
4941
+ labels.push(label);
4942
+ i += 2;
4943
+ } else if (arg === "--assignee") {
4944
+ const assignee = requireFlagValue("--assignee", args[i + 1]);
4945
+ assignees.push(assignee);
4946
+ i += 2;
4947
+ } else {
4948
+ remaining.push(arg);
4949
+ i++;
4950
+ }
4951
+ }
4952
+ return {
4953
+ options: {
4954
+ title,
4955
+ body,
4956
+ bodyFile,
4957
+ labels,
4958
+ assignees
4959
+ },
4960
+ remaining
4961
+ };
4962
+ }
4963
+ function validateIssueCreateOptions(options) {
4964
+ const errors = [];
4965
+ if (!options.title) {
4966
+ errors.push("--title is required");
4967
+ }
4968
+ if (options.body && options.bodyFile) {
4969
+ errors.push("--body and --body-file cannot be used together");
4970
+ }
4971
+ if (errors.length > 0) {
4972
+ throw new Error(errors.join("; "));
4973
+ }
4974
+ }
4975
+ async function runIssueCreateCommand(args, issueProvider, logger) {
4976
+ const { options, remaining } = parseIssueCreateArgs(args);
4977
+ if (remaining.length > 0) {
4978
+ throw new Error(`Unsupported arguments: ${remaining.join(" ")}`);
4979
+ }
4980
+ validateIssueCreateOptions(options);
4981
+ const renderedBody = options.bodyFile ? await readFile3(options.bodyFile, "utf-8") : options.body ?? "";
4982
+ const createOptions = {
4983
+ title: options.title,
4984
+ body: renderedBody,
4985
+ labels: options.labels.length > 0 ? options.labels : void 0,
4986
+ assignees: options.assignees.length > 0 ? options.assignees : void 0
4987
+ };
4988
+ const created = await issueProvider.createIssue(createOptions);
4989
+ return {
4990
+ options,
4991
+ renderedBody,
4992
+ issueNumber: created.number,
4993
+ issueUrl: created.url,
4994
+ issueTitle: created.title
4995
+ };
4996
+ }
4997
+
3989
4998
  // commands/queue.ts
3990
4999
  init_common();
3991
5000
 
@@ -4423,11 +5432,11 @@ async function runPrWorkflow(options) {
4423
5432
  }
4424
5433
 
4425
5434
  // commands/pr-create.ts
4426
- function isFlag(value) {
5435
+ function isFlag2(value) {
4427
5436
  return value.startsWith("--");
4428
5437
  }
4429
- function requireFlagValue(flag, value) {
4430
- if (!value || isFlag(value)) {
5438
+ function requireFlagValue2(flag, value) {
5439
+ if (!value || isFlag2(value)) {
4431
5440
  throw new Error(`${flag} requires a value`);
4432
5441
  }
4433
5442
  return value;
@@ -4445,25 +5454,25 @@ function parsePrCreateArgs(args) {
4445
5454
  while (i < args.length) {
4446
5455
  const arg = args[i];
4447
5456
  if (arg === "--target") {
4448
- target = requireFlagValue("--target", args[i + 1]);
5457
+ target = requireFlagValue2("--target", args[i + 1]);
4449
5458
  i += 2;
4450
5459
  } else if (arg === "--title") {
4451
- title = requireFlagValue("--title", args[i + 1]);
5460
+ title = requireFlagValue2("--title", args[i + 1]);
4452
5461
  i += 2;
4453
5462
  } else if (arg === "--base") {
4454
- base = requireFlagValue("--base", args[i + 1]);
5463
+ base = requireFlagValue2("--base", args[i + 1]);
4455
5464
  i += 2;
4456
5465
  } else if (arg === "--head") {
4457
- head = requireFlagValue("--head", args[i + 1]);
5466
+ head = requireFlagValue2("--head", args[i + 1]);
4458
5467
  i += 2;
4459
5468
  } else if (arg === "--body") {
4460
- body = requireFlagValue("--body", args[i + 1]);
5469
+ body = requireFlagValue2("--body", args[i + 1]);
4461
5470
  i += 2;
4462
5471
  } else if (arg === "--body-file") {
4463
- bodyFile = requireFlagValue("--body-file", args[i + 1]);
5472
+ bodyFile = requireFlagValue2("--body-file", args[i + 1]);
4464
5473
  i += 2;
4465
5474
  } else if (arg === "--issue") {
4466
- const raw = requireFlagValue("--issue", args[i + 1]);
5475
+ const raw = requireFlagValue2("--issue", args[i + 1]);
4467
5476
  if (!/^\d+$/.test(raw)) {
4468
5477
  throw new Error(`Invalid issue number: ${raw}`);
4469
5478
  }
@@ -4561,17 +5570,17 @@ async function runPrCreateCommand(args, logger, prProvider, config, repoRoot2) {
4561
5570
 
4562
5571
  // commands/pr-merge.ts
4563
5572
  init_common();
4564
- function isFlag2(value) {
5573
+ function isFlag3(value) {
4565
5574
  return value.startsWith("--");
4566
5575
  }
4567
- function requireFlagValue2(flag, value) {
4568
- if (!value || isFlag2(value)) {
5576
+ function requireFlagValue3(flag, value) {
5577
+ if (!value || isFlag3(value)) {
4569
5578
  throw new Error(`${flag} requires a value`);
4570
5579
  }
4571
5580
  return value;
4572
5581
  }
4573
5582
  function parsePrNumber(raw) {
4574
- if (!raw || isFlag2(raw) || !/^\d+$/.test(raw)) {
5583
+ if (!raw || isFlag3(raw) || !/^\d+$/.test(raw)) {
4575
5584
  throw new Error("PR number is required");
4576
5585
  }
4577
5586
  const prNumber = parseInt(raw, 10);
@@ -4591,10 +5600,10 @@ function parsePrMergeArgs(args) {
4591
5600
  while (i < args.length) {
4592
5601
  const arg = args[i];
4593
5602
  if (arg === "--target") {
4594
- target = requireFlagValue2("--target", args[i + 1]);
5603
+ target = requireFlagValue3("--target", args[i + 1]);
4595
5604
  i += 2;
4596
5605
  } else if (arg === "--method") {
4597
- const raw = requireFlagValue2("--method", args[i + 1]);
5606
+ const raw = requireFlagValue3("--method", args[i + 1]);
4598
5607
  if (raw !== "merge" && raw !== "squash" && raw !== "rebase") {
4599
5608
  throw new Error(`Invalid merge method: ${raw}`);
4600
5609
  }
@@ -4707,17 +5716,17 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
4707
5716
  }
4708
5717
 
4709
5718
  // commands/init.ts
4710
- import { existsSync as existsSync6, statSync } from "fs";
5719
+ import { existsSync as existsSync8, statSync } from "fs";
4711
5720
  import {
4712
5721
  copyFile,
4713
- mkdir as mkdir2,
4714
- readFile as readFile3,
5722
+ mkdir as mkdir4,
5723
+ readFile as readFile4,
4715
5724
  readdir,
4716
5725
  rename,
4717
5726
  writeFile
4718
5727
  } from "fs/promises";
4719
5728
  import { createHash, randomUUID } from "crypto";
4720
- import path3 from "path";
5729
+ import path5 from "path";
4721
5730
  import { execFile as execFile2 } from "child_process";
4722
5731
  import { promisify as promisify2 } from "util";
4723
5732
  import { confirm, isCancel, log, select, text } from "@clack/prompts";
@@ -4942,7 +5951,7 @@ function generateConfigTemplate(options) {
4942
5951
  labels: maybeLabels
4943
5952
  } = options;
4944
5953
  const labels = maybeLabels ?? DEFAULT_RUNNER_LABELS;
4945
- const relPath = path3.relative(targetRoot, sourceRoot).replace(/\\/g, "/");
5954
+ const relPath = path5.relative(targetRoot, sourceRoot).replace(/\\/g, "/");
4946
5955
  const importPath = relPath || ".";
4947
5956
  const setupCommand = `${packageManager} install`;
4948
5957
  let setupSection;
@@ -5190,9 +6199,9 @@ _Avoid_: Alternative terms
5190
6199
  `;
5191
6200
  }
5192
6201
  async function generateManagedAgentInstructions(options) {
5193
- const sourcePath = path3.join(options.sourceRoot, "AGENTS.md");
6202
+ const sourcePath = path5.join(options.sourceRoot, "AGENTS.md");
5194
6203
  try {
5195
- return await readFile3(sourcePath, "utf-8");
6204
+ return await readFile4(sourcePath, "utf-8");
5196
6205
  } catch {
5197
6206
  return `## Agent Skills
5198
6207
 
@@ -5220,7 +6229,7 @@ async function walkDir(dir) {
5220
6229
  const files = [];
5221
6230
  const entries = await readdir(dir, { withFileTypes: true });
5222
6231
  for (const entry of entries) {
5223
- const full = path3.join(dir, entry.name);
6232
+ const full = path5.join(dir, entry.name);
5224
6233
  if (entry.isDirectory()) {
5225
6234
  files.push(...await walkDir(full));
5226
6235
  } else {
@@ -5230,11 +6239,11 @@ async function walkDir(dir) {
5230
6239
  return files;
5231
6240
  }
5232
6241
  async function computeFileChecksum(filePath) {
5233
- const content = await readFile3(filePath);
6242
+ const content = await readFile4(filePath);
5234
6243
  return createHash("sha256").update(content).digest("hex");
5235
6244
  }
5236
6245
  function lockfileExists(root, name) {
5237
- return existsSync6(path3.join(root, name));
6246
+ return existsSync8(path5.join(root, name));
5238
6247
  }
5239
6248
  function detectPackageManager(root) {
5240
6249
  if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
@@ -5277,8 +6286,8 @@ async function discoverLocalSource(sourcePath) {
5277
6286
  }
5278
6287
  async function discoverReadme(root) {
5279
6288
  for (const name of ["README.md", "readme.md"]) {
5280
- const p = path3.join(root, name);
5281
- if (existsSync6(p)) {
6289
+ const p = path5.join(root, name);
6290
+ if (existsSync8(p)) {
5282
6291
  return p;
5283
6292
  }
5284
6293
  }
@@ -5287,25 +6296,25 @@ async function discoverReadme(root) {
5287
6296
  async function discoverAgentFiles(root) {
5288
6297
  const files = [];
5289
6298
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
5290
- const p = path3.join(root, name);
5291
- if (existsSync6(p)) {
6299
+ const p = path5.join(root, name);
6300
+ if (existsSync8(p)) {
5292
6301
  files.push(p);
5293
6302
  }
5294
6303
  }
5295
6304
  return files;
5296
6305
  }
5297
6306
  async function discoverMerlleState(root) {
5298
- const p = path3.join(root, ".pourkit", "state.json");
5299
- return existsSync6(p) ? p : null;
6307
+ const p = path5.join(root, ".pourkit", "state.json");
6308
+ return existsSync8(p) ? p : null;
5300
6309
  }
5301
6310
  async function discoverAgentSkills(root) {
5302
6311
  const dirs = [
5303
- path3.join(root, ".agents", "skills"),
5304
- path3.join(root, ".opencode", "skills")
6312
+ path5.join(root, ".agents", "skills"),
6313
+ path5.join(root, ".opencode", "skills")
5305
6314
  ];
5306
6315
  const found = [];
5307
6316
  for (const d of dirs) {
5308
- if (existsSync6(d)) {
6317
+ if (existsSync8(d)) {
5309
6318
  found.push(d);
5310
6319
  }
5311
6320
  }
@@ -5314,17 +6323,17 @@ async function discoverAgentSkills(root) {
5314
6323
  async function discoverRootDomainDocs(root) {
5315
6324
  const docs = [];
5316
6325
  for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
5317
- const p = path3.join(root, name);
5318
- if (existsSync6(p)) {
6326
+ const p = path5.join(root, name);
6327
+ if (existsSync8(p)) {
5319
6328
  docs.push(p);
5320
6329
  }
5321
6330
  }
5322
- const adrDir = path3.join(root, "docs", "adr");
5323
- if (existsSync6(adrDir)) {
6331
+ const adrDir = path5.join(root, "docs", "adr");
6332
+ if (existsSync8(adrDir)) {
5324
6333
  const entries = await readdir(adrDir, { withFileTypes: true });
5325
6334
  for (const entry of entries) {
5326
6335
  if (entry.isFile() && entry.name.endsWith(".md")) {
5327
- docs.push(path3.join(adrDir, entry.name));
6336
+ docs.push(path5.join(adrDir, entry.name));
5328
6337
  }
5329
6338
  }
5330
6339
  }
@@ -5412,7 +6421,7 @@ async function planInit(options) {
5412
6421
  for (const f of agentFiles) {
5413
6422
  if (sourceRoot) {
5414
6423
  const agentFileMode = options.conflictPolicy?.agentFile ?? "both";
5415
- const basename = path3.basename(f);
6424
+ const basename = path5.basename(f);
5416
6425
  if (agentFileMode === "skip" || agentFileMode === "agents" && basename !== "AGENTS.md" || agentFileMode === "claude" && basename !== "CLAUDE.md") {
5417
6426
  operations.push({
5418
6427
  kind: "skip",
@@ -5438,7 +6447,7 @@ async function planInit(options) {
5438
6447
  kind: "skip",
5439
6448
  path: f,
5440
6449
  ownership: "project-owned",
5441
- reason: `Existing agent file: ${path3.basename(f)}`,
6450
+ reason: `Existing agent file: ${path5.basename(f)}`,
5442
6451
  requiresConfirmation: false,
5443
6452
  destructive: false
5444
6453
  });
@@ -5461,15 +6470,15 @@ async function planInit(options) {
5461
6470
  if (s.includes(".opencode") && options.legacySkills) {
5462
6471
  const skillFiles = await walkDir(s);
5463
6472
  for (const file of skillFiles) {
5464
- const relPath = path3.relative(s, file);
5465
- const destPath = path3.join(targetRoot, ".agents", "skills", relPath);
5466
- if (!existsSync6(destPath)) {
6473
+ const relPath = path5.relative(s, file);
6474
+ const destPath = path5.join(targetRoot, ".agents", "skills", relPath);
6475
+ if (!existsSync8(destPath)) {
5467
6476
  operations.push({
5468
6477
  kind: "copy",
5469
6478
  sourcePath: file,
5470
6479
  path: destPath,
5471
6480
  ownership: "project-owned",
5472
- reason: `Migrate legacy skill: ${path3.join(".opencode/skills", relPath)}`,
6481
+ reason: `Migrate legacy skill: ${path5.join(".opencode/skills", relPath)}`,
5473
6482
  requiresConfirmation: false,
5474
6483
  destructive: false
5475
6484
  });
@@ -5526,7 +6535,7 @@ async function planInit(options) {
5526
6535
  });
5527
6536
  }
5528
6537
  if (sourceRoot) {
5529
- if (!existsSync6(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
6538
+ if (!existsSync8(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
5530
6539
  warnings.push(
5531
6540
  `--from-local path does not exist or is not a directory: ${sourceRoot}`
5532
6541
  );
@@ -5587,17 +6596,17 @@ async function planInit(options) {
5587
6596
  continue;
5588
6597
  }
5589
6598
  const targetDirName = ".agents/skills";
5590
- const targetPath = path3.join(targetRoot, targetDirName);
6599
+ const targetPath = path5.join(targetRoot, targetDirName);
5591
6600
  const skillFiles = await walkDir(s);
5592
6601
  for (const file of skillFiles) {
5593
- const relPath = path3.relative(s, file);
5594
- const destPath = path3.join(targetPath, relPath);
6602
+ const relPath = path5.relative(s, file);
6603
+ const destPath = path5.join(targetPath, relPath);
5595
6604
  if (plannedSkillDests.has(destPath)) {
5596
6605
  operations.push({
5597
6606
  kind: "skip",
5598
6607
  path: destPath,
5599
6608
  ownership: "project-owned",
5600
- reason: `Skill destination conflict, skipping source copy: ${path3.join(targetDirName, relPath)}`,
6609
+ reason: `Skill destination conflict, skipping source copy: ${path5.join(targetDirName, relPath)}`,
5601
6610
  requiresConfirmation: false,
5602
6611
  destructive: false,
5603
6612
  conflict: "destination already planned"
@@ -5610,7 +6619,7 @@ async function planInit(options) {
5610
6619
  sourcePath: file,
5611
6620
  path: destPath,
5612
6621
  ownership: "copied-customizable",
5613
- reason: `Copy skill: ${path3.join(targetDirName, relPath)}`,
6622
+ reason: `Copy skill: ${path5.join(targetDirName, relPath)}`,
5614
6623
  requiresConfirmation: false,
5615
6624
  destructive: false,
5616
6625
  checksum
@@ -5622,7 +6631,7 @@ async function planInit(options) {
5622
6631
  operations.push({
5623
6632
  kind: "copy",
5624
6633
  sourcePath: srcReadme,
5625
- path: path3.join(targetRoot, "README.md"),
6634
+ path: path5.join(targetRoot, "README.md"),
5626
6635
  ownership: "project-owned",
5627
6636
  reason: "Copy README.md from source",
5628
6637
  requiresConfirmation: false,
@@ -5633,8 +6642,8 @@ async function planInit(options) {
5633
6642
  const rootDocs = await discoverRootDomainDocs(targetRoot);
5634
6643
  const merleDestPaths = /* @__PURE__ */ new Set();
5635
6644
  for (const docPath of rootDocs) {
5636
- const relPath = path3.relative(targetRoot, docPath);
5637
- const destPath = path3.join(targetRoot, ".pourkit", relPath);
6645
+ const relPath = path5.relative(targetRoot, docPath);
6646
+ const destPath = path5.join(targetRoot, ".pourkit", relPath);
5638
6647
  merleDestPaths.add(destPath);
5639
6648
  if (docsMigration === "skip") {
5640
6649
  operations.push({
@@ -5645,7 +6654,7 @@ async function planInit(options) {
5645
6654
  requiresConfirmation: false,
5646
6655
  destructive: false
5647
6656
  });
5648
- } else if (existsSync6(destPath)) {
6657
+ } else if (existsSync8(destPath)) {
5649
6658
  operations.push({
5650
6659
  kind: "skip",
5651
6660
  path: destPath,
@@ -5680,8 +6689,8 @@ async function planInit(options) {
5680
6689
  let packageScripts = {};
5681
6690
  let hasPackageJson = true;
5682
6691
  try {
5683
- const pkgContent = await readFile3(
5684
- path3.join(targetRoot, "package.json"),
6692
+ const pkgContent = await readFile4(
6693
+ path5.join(targetRoot, "package.json"),
5685
6694
  "utf-8"
5686
6695
  );
5687
6696
  const pkg = JSON.parse(pkgContent);
@@ -5702,8 +6711,8 @@ async function planInit(options) {
5702
6711
  } catch {
5703
6712
  }
5704
6713
  }
5705
- const contextPath = path3.join(targetRoot, ".pourkit", "CONTEXT.md");
5706
- if (!existsSync6(contextPath) && !merleDestPaths.has(contextPath)) {
6714
+ const contextPath = path5.join(targetRoot, ".pourkit", "CONTEXT.md");
6715
+ if (!existsSync8(contextPath) && !merleDestPaths.has(contextPath)) {
5707
6716
  operations.push({
5708
6717
  kind: "create",
5709
6718
  path: contextPath,
@@ -5714,14 +6723,14 @@ async function planInit(options) {
5714
6723
  content: generateContextScaffold()
5715
6724
  });
5716
6725
  }
5717
- const adrGitkeep = path3.join(
6726
+ const adrGitkeep = path5.join(
5718
6727
  targetRoot,
5719
6728
  ".pourkit",
5720
6729
  "docs",
5721
6730
  "adr",
5722
6731
  ".gitkeep"
5723
6732
  );
5724
- if (!existsSync6(adrGitkeep)) {
6733
+ if (!existsSync8(adrGitkeep)) {
5725
6734
  operations.push({
5726
6735
  kind: "create",
5727
6736
  path: adrGitkeep,
@@ -5731,16 +6740,16 @@ async function planInit(options) {
5731
6740
  destructive: false
5732
6741
  });
5733
6742
  }
5734
- const srcDocAgents = path3.join(sourceRoot, ".pourkit", "docs", "agents");
5735
- const tgtDocAgents = path3.join(targetRoot, ".pourkit", "docs", "agents");
5736
- if (existsSync6(srcDocAgents) && !existsSync6(tgtDocAgents)) {
6743
+ const srcDocAgents = path5.join(sourceRoot, ".pourkit", "docs", "agents");
6744
+ const tgtDocAgents = path5.join(targetRoot, ".pourkit", "docs", "agents");
6745
+ if (existsSync8(srcDocAgents) && !existsSync8(tgtDocAgents)) {
5737
6746
  const docFiles = await walkDir(srcDocAgents);
5738
6747
  for (const file of docFiles) {
5739
- const relPath = path3.relative(srcDocAgents, file);
6748
+ const relPath = path5.relative(srcDocAgents, file);
5740
6749
  if (relPath === "triage-labels.md") {
5741
6750
  operations.push({
5742
6751
  kind: "create",
5743
- path: path3.join(tgtDocAgents, relPath),
6752
+ path: path5.join(tgtDocAgents, relPath),
5744
6753
  ownership: "managed",
5745
6754
  reason: "Init triage labels doc",
5746
6755
  requiresConfirmation: false,
@@ -5755,7 +6764,7 @@ async function planInit(options) {
5755
6764
  operations.push({
5756
6765
  kind: "copy",
5757
6766
  sourcePath: file,
5758
- path: path3.join(tgtDocAgents, relPath),
6767
+ path: path5.join(tgtDocAgents, relPath),
5759
6768
  ownership: "managed",
5760
6769
  reason: `Copy agent doc: ${relPath}`,
5761
6770
  requiresConfirmation: false,
@@ -5764,17 +6773,17 @@ async function planInit(options) {
5764
6773
  });
5765
6774
  }
5766
6775
  }
5767
- const srcPrompts = path3.join(sourceRoot, ".pourkit", "prompts");
5768
- const tgtPrompts = path3.join(targetRoot, ".pourkit", "prompts");
5769
- if (existsSync6(srcPrompts) && !existsSync6(tgtPrompts)) {
6776
+ const srcPrompts = path5.join(sourceRoot, ".pourkit", "prompts");
6777
+ const tgtPrompts = path5.join(targetRoot, ".pourkit", "prompts");
6778
+ if (existsSync8(srcPrompts) && !existsSync8(tgtPrompts)) {
5770
6779
  const promptFiles = await walkDir(srcPrompts);
5771
6780
  for (const file of promptFiles) {
5772
- const relPath = path3.relative(srcPrompts, file);
6781
+ const relPath = path5.relative(srcPrompts, file);
5773
6782
  const checksum = await computeFileChecksum(file);
5774
6783
  operations.push({
5775
6784
  kind: "copy",
5776
6785
  sourcePath: file,
5777
- path: path3.join(tgtPrompts, relPath),
6786
+ path: path5.join(tgtPrompts, relPath),
5778
6787
  ownership: "managed",
5779
6788
  reason: `Copy prompt: ${relPath}`,
5780
6789
  requiresConfirmation: false,
@@ -5783,17 +6792,17 @@ async function planInit(options) {
5783
6792
  });
5784
6793
  }
5785
6794
  }
5786
- const srcSandboxDockerfile = path3.join(
6795
+ const srcSandboxDockerfile = path5.join(
5787
6796
  sourceRoot,
5788
6797
  ".sandcastle",
5789
6798
  "Dockerfile"
5790
6799
  );
5791
- const tgtSandboxDockerfile = path3.join(
6800
+ const tgtSandboxDockerfile = path5.join(
5792
6801
  targetRoot,
5793
6802
  ".sandcastle",
5794
6803
  "Dockerfile"
5795
6804
  );
5796
- if (existsSync6(tgtSandboxDockerfile)) {
6805
+ if (existsSync8(tgtSandboxDockerfile)) {
5797
6806
  operations.push({
5798
6807
  kind: "skip",
5799
6808
  path: tgtSandboxDockerfile,
@@ -5802,7 +6811,7 @@ async function planInit(options) {
5802
6811
  requiresConfirmation: false,
5803
6812
  destructive: false
5804
6813
  });
5805
- } else if (existsSync6(srcSandboxDockerfile)) {
6814
+ } else if (existsSync8(srcSandboxDockerfile)) {
5806
6815
  const checksum = await computeFileChecksum(srcSandboxDockerfile);
5807
6816
  operations.push({
5808
6817
  kind: "copy",
@@ -5815,8 +6824,8 @@ async function planInit(options) {
5815
6824
  checksum
5816
6825
  });
5817
6826
  }
5818
- const configTsPath = path3.join(targetRoot, "pourkit.config.ts");
5819
- if (!existsSync6(configTsPath)) {
6827
+ const configTsPath = path5.join(targetRoot, "pourkit.config.ts");
6828
+ if (!existsSync8(configTsPath)) {
5820
6829
  const verifyCommands = inferVerificationCommands(
5821
6830
  packageScripts,
5822
6831
  pm || "npm"
@@ -5853,10 +6862,10 @@ async function planInit(options) {
5853
6862
  const hasExistingAgents = operations.some(
5854
6863
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
5855
6864
  );
5856
- if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync6(path3.join(targetRoot, "AGENTS.md"))) {
6865
+ if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync8(path5.join(targetRoot, "AGENTS.md"))) {
5857
6866
  operations.push({
5858
6867
  kind: "create",
5859
- path: path3.join(targetRoot, "AGENTS.md"),
6868
+ path: path5.join(targetRoot, "AGENTS.md"),
5860
6869
  ownership: "managed",
5861
6870
  reason: "Init AGENTS.md with Pourkit managed block",
5862
6871
  requiresConfirmation: false,
@@ -5869,10 +6878,10 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
5869
6878
  const hasExistingClaude = operations.some(
5870
6879
  (op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
5871
6880
  );
5872
- if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync6(path3.join(targetRoot, "CLAUDE.md"))) {
6881
+ if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync8(path5.join(targetRoot, "CLAUDE.md"))) {
5873
6882
  operations.push({
5874
6883
  kind: "create",
5875
- path: path3.join(targetRoot, "CLAUDE.md"),
6884
+ path: path5.join(targetRoot, "CLAUDE.md"),
5876
6885
  ownership: "managed",
5877
6886
  reason: "Init CLAUDE.md with Pourkit managed block",
5878
6887
  requiresConfirmation: false,
@@ -5882,9 +6891,9 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
5882
6891
  `
5883
6892
  });
5884
6893
  }
5885
- const gitignoreTarget = path3.join(targetRoot, ".gitignore");
6894
+ const gitignoreTarget = path5.join(targetRoot, ".gitignore");
5886
6895
  const gitignoreContent = generateGitignoreBlock();
5887
- if (!existsSync6(gitignoreTarget)) {
6896
+ if (!existsSync8(gitignoreTarget)) {
5888
6897
  operations.push({
5889
6898
  kind: "create",
5890
6899
  path: gitignoreTarget,
@@ -5907,8 +6916,8 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
5907
6916
  content: gitignoreContent
5908
6917
  });
5909
6918
  }
5910
- const openCodePath = path3.join(targetRoot, "opencode.json");
5911
- if (!existsSync6(openCodePath)) {
6919
+ const openCodePath = path5.join(targetRoot, "opencode.json");
6920
+ if (!existsSync8(openCodePath)) {
5912
6921
  operations.push({
5913
6922
  kind: "create",
5914
6923
  path: openCodePath,
@@ -5920,7 +6929,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
5920
6929
  });
5921
6930
  } else {
5922
6931
  try {
5923
- const existingContent = await readFile3(openCodePath, "utf-8");
6932
+ const existingContent = await readFile4(openCodePath, "utf-8");
5924
6933
  const existingConfig = JSON.parse(existingContent);
5925
6934
  if (typeof existingConfig !== "object" || existingConfig === null || Array.isArray(existingConfig)) {
5926
6935
  warnings.push(
@@ -5964,8 +6973,8 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
5964
6973
  });
5965
6974
  }
5966
6975
  }
5967
- const manifestPath = path3.join(targetRoot, ".pourkit", "manifest.json");
5968
- if (existsSync6(manifestPath)) {
6976
+ const manifestPath = path5.join(targetRoot, ".pourkit", "manifest.json");
6977
+ if (existsSync8(manifestPath)) {
5969
6978
  operations.push({
5970
6979
  kind: "skip",
5971
6980
  path: manifestPath,
@@ -6284,13 +7293,13 @@ async function updateManagedBlock(filePath, content) {
6284
7293
  const blockContent = `${MANAGED_BLOCK_BEGIN}
6285
7294
  ${content}${MANAGED_BLOCK_END}
6286
7295
  `;
6287
- if (!existsSync6(filePath)) {
6288
- const dir = path3.dirname(filePath);
6289
- await mkdir2(dir, { recursive: true });
7296
+ if (!existsSync8(filePath)) {
7297
+ const dir = path5.dirname(filePath);
7298
+ await mkdir4(dir, { recursive: true });
6290
7299
  await writeFileAtomic(filePath, blockContent);
6291
7300
  return;
6292
7301
  }
6293
- const existing = await readFile3(filePath, "utf-8");
7302
+ const existing = await readFile4(filePath, "utf-8");
6294
7303
  const beginIdx = existing.indexOf(MANAGED_BLOCK_BEGIN);
6295
7304
  const endIdx = existing.indexOf(MANAGED_BLOCK_END);
6296
7305
  if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
@@ -6303,17 +7312,17 @@ ${content}${MANAGED_BLOCK_END}
6303
7312
  }
6304
7313
  }
6305
7314
  async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
6306
- const manifestDir = path3.join(plan.targetRoot, ".pourkit");
6307
- const manifestPath = path3.join(manifestDir, "manifest.json");
7315
+ const manifestDir = path5.join(plan.targetRoot, ".pourkit");
7316
+ const manifestPath = path5.join(manifestDir, "manifest.json");
6308
7317
  const assets = {};
6309
7318
  for (const op of plan.operations) {
6310
7319
  if (!op.path) continue;
6311
7320
  if (op.kind !== "create" && op.kind !== "copy" && op.kind !== "update" && op.kind !== "move")
6312
7321
  continue;
6313
7322
  if (op.requiresConfirmation) continue;
6314
- const relPath = path3.relative(plan.targetRoot, op.path);
7323
+ const relPath = path5.relative(plan.targetRoot, op.path);
6315
7324
  if (relPath === ".pourkit/manifest.json") continue;
6316
- if (existsSync6(op.path)) {
7325
+ if (existsSync8(op.path)) {
6317
7326
  const sha256 = await computeFileChecksum(op.path);
6318
7327
  assets[relPath] = {
6319
7328
  ownership: op.ownership || "managed",
@@ -6337,7 +7346,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
6337
7346
  packageManager,
6338
7347
  assets
6339
7348
  };
6340
- await mkdir2(manifestDir, { recursive: true });
7349
+ await mkdir4(manifestDir, { recursive: true });
6341
7350
  await writeFileAtomic(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
6342
7351
  return manifest;
6343
7352
  }
@@ -6358,12 +7367,12 @@ async function applyInitPlan(plan, options) {
6358
7367
  skipped++;
6359
7368
  continue;
6360
7369
  }
6361
- if (existsSync6(op.path) && !op.destructive) {
7370
+ if (existsSync8(op.path) && !op.destructive) {
6362
7371
  skipped++;
6363
7372
  continue;
6364
7373
  }
6365
- const dir = path3.dirname(op.path);
6366
- await mkdir2(dir, { recursive: true });
7374
+ const dir = path5.dirname(op.path);
7375
+ await mkdir4(dir, { recursive: true });
6367
7376
  await writeFileAtomic(op.path, op.content ?? "");
6368
7377
  applied++;
6369
7378
  break;
@@ -6373,7 +7382,7 @@ async function applyInitPlan(plan, options) {
6373
7382
  skipped++;
6374
7383
  continue;
6375
7384
  }
6376
- if (existsSync6(op.path)) {
7385
+ if (existsSync8(op.path)) {
6377
7386
  skipped++;
6378
7387
  continue;
6379
7388
  }
@@ -6381,8 +7390,8 @@ async function applyInitPlan(plan, options) {
6381
7390
  if (srcStat.isDirectory()) {
6382
7391
  skipped++;
6383
7392
  } else {
6384
- const dir = path3.dirname(op.path);
6385
- await mkdir2(dir, { recursive: true });
7393
+ const dir = path5.dirname(op.path);
7394
+ await mkdir4(dir, { recursive: true });
6386
7395
  await copyFile(op.sourcePath, op.path);
6387
7396
  applied++;
6388
7397
  }
@@ -6402,12 +7411,12 @@ async function applyInitPlan(plan, options) {
6402
7411
  skipped++;
6403
7412
  continue;
6404
7413
  }
6405
- if (existsSync6(op.path)) {
7414
+ if (existsSync8(op.path)) {
6406
7415
  skipped++;
6407
7416
  continue;
6408
7417
  }
6409
- const dir = path3.dirname(op.path);
6410
- await mkdir2(dir, { recursive: true });
7418
+ const dir = path5.dirname(op.path);
7419
+ await mkdir4(dir, { recursive: true });
6411
7420
  await rename(op.sourcePath, op.path);
6412
7421
  applied++;
6413
7422
  break;
@@ -6537,13 +7546,13 @@ async function applyInitFromSource(options) {
6537
7546
  let manifestWritten = false;
6538
7547
  if (result.errors.length === 0) {
6539
7548
  const manifestSkipped = plan.operations.some(
6540
- (op) => op.kind === "skip" && op.path === path3.join(targetRoot, ".pourkit", "manifest.json")
7549
+ (op) => op.kind === "skip" && op.path === path5.join(targetRoot, ".pourkit", "manifest.json")
6541
7550
  );
6542
7551
  if (!manifestSkipped) {
6543
7552
  const agentFiles = [];
6544
7553
  for (const name of ["AGENTS.md", "CLAUDE.md"]) {
6545
- if (existsSync6(path3.join(targetRoot, name))) {
6546
- agentFiles.push(path3.join(targetRoot, name));
7554
+ if (existsSync8(path5.join(targetRoot, name))) {
7555
+ agentFiles.push(path5.join(targetRoot, name));
6547
7556
  }
6548
7557
  }
6549
7558
  const pm = detectPackageManager(targetRoot);
@@ -6718,6 +7727,121 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
6718
7727
  }
6719
7728
  }
6720
7729
 
7730
+ // commands/serena.ts
7731
+ init_common();
7732
+ var SERENA_MCP_PORT2 = 9121;
7733
+ var SERENA_DASHBOARD_PORT2 = 24282;
7734
+ var SERENA_IMAGE2 = "ghcr.io/oraios/serena:latest";
7735
+ async function resolveSerenaCommandContext(options) {
7736
+ const repoRootPath = options.cwd ? repoRoot(options.cwd) : repoRoot();
7737
+ const config = await loadRepoConfig(repoRootPath);
7738
+ const target = resolveTarget(config, options.target);
7739
+ return {
7740
+ repoRootPath,
7741
+ config,
7742
+ target
7743
+ };
7744
+ }
7745
+ async function resolveSerenaLifecycleContext(options) {
7746
+ const repoRootPath = options.cwd ? repoRoot(options.cwd) : repoRoot();
7747
+ const config = await loadRepoConfig(repoRootPath);
7748
+ return {
7749
+ repoRootPath,
7750
+ config,
7751
+ paths: resolveSerenaPaths(repoRootPath, config.serena.dataDir)
7752
+ };
7753
+ }
7754
+ function buildSerenaSidecarOptions(paths, mcpUrl) {
7755
+ return {
7756
+ baselineWorktreePath: paths.baselineWorktreePath,
7757
+ dataDir: paths.dataDir,
7758
+ mcpPort: SERENA_MCP_PORT2,
7759
+ dashboardPort: SERENA_DASHBOARD_PORT2,
7760
+ image: SERENA_IMAGE2,
7761
+ mcpUrl
7762
+ };
7763
+ }
7764
+ function logSerenaSidecarStatus(heading, status, baselineFreshness) {
7765
+ console.log(`${heading}:`);
7766
+ console.log(` running: ${status.running ? "yes" : "no"}`);
7767
+ console.log(` mcpUrl: ${status.mcpUrl}`);
7768
+ console.log(` dashboardUrl: ${status.dashboardUrl}`);
7769
+ console.log(` containerName: ${status.containerName}`);
7770
+ if (baselineFreshness) {
7771
+ console.log(` Baseline freshness: ${baselineFreshness}`);
7772
+ }
7773
+ }
7774
+ async function runSerenaInitCommand(options) {
7775
+ const { repoRootPath, config, target } = await resolveSerenaCommandContext(options);
7776
+ const paths = await ensureBaselineWorktree({
7777
+ repoRoot: repoRootPath,
7778
+ dataDir: config.serena.dataDir
7779
+ });
7780
+ await refreshSerenaBaseline({
7781
+ repoRoot: repoRootPath,
7782
+ dataDir: config.serena.dataDir,
7783
+ baseBranch: target.baseBranch
7784
+ });
7785
+ await prepareSerenaSidecarConfig({
7786
+ baselineWorktreePath: paths.baselineWorktreePath,
7787
+ dataDir: paths.dataDir
7788
+ });
7789
+ const sidecarOptions2 = buildSerenaSidecarOptions(paths, config.serena.mcpUrl);
7790
+ await startSerenaSidecar(sidecarOptions2);
7791
+ await indexSerenaProject(sidecarOptions2);
7792
+ }
7793
+ async function runSerenaRefreshCommand(options) {
7794
+ const { repoRootPath, config, target } = await resolveSerenaCommandContext(options);
7795
+ await refreshSerenaBaseline({
7796
+ repoRoot: repoRootPath,
7797
+ dataDir: config.serena.dataDir,
7798
+ baseBranch: target.baseBranch
7799
+ });
7800
+ }
7801
+ async function runSerenaStartCommand(options) {
7802
+ const { repoRootPath, config } = await resolveSerenaLifecycleContext(options);
7803
+ const ensuredPaths = await ensureBaselineWorktree({
7804
+ repoRoot: repoRootPath,
7805
+ dataDir: config.serena.dataDir
7806
+ });
7807
+ await prepareSerenaSidecarConfig({
7808
+ baselineWorktreePath: ensuredPaths.baselineWorktreePath,
7809
+ dataDir: ensuredPaths.dataDir
7810
+ });
7811
+ const status = await startSerenaSidecar(
7812
+ buildSerenaSidecarOptions(ensuredPaths, config.serena.mcpUrl)
7813
+ );
7814
+ logSerenaSidecarStatus("Serena sidecar started", status);
7815
+ }
7816
+ async function runSerenaStopCommand(options) {
7817
+ const { config, paths } = await resolveSerenaLifecycleContext(options);
7818
+ const status = await stopSerenaSidecar(
7819
+ buildSerenaSidecarOptions(paths, config.serena.mcpUrl)
7820
+ );
7821
+ logSerenaSidecarStatus("Serena sidecar stopped", status);
7822
+ }
7823
+ async function runSerenaStatusCommand(options) {
7824
+ const { repoRootPath, config, paths } = await resolveSerenaLifecycleContext(options);
7825
+ const status = await getSerenaSidecarStatus(
7826
+ buildSerenaSidecarOptions(paths, config.serena.mcpUrl)
7827
+ );
7828
+ if (options.target) {
7829
+ const target = resolveTarget(config, options.target);
7830
+ const baseline = await getSerenaBaselineStatus({
7831
+ repoRoot: repoRootPath,
7832
+ dataDir: config.serena.dataDir,
7833
+ baseBranch: target.baseBranch
7834
+ });
7835
+ logSerenaSidecarStatus(
7836
+ "Serena sidecar status",
7837
+ status,
7838
+ baseline.fresh ? "fresh" : "stale"
7839
+ );
7840
+ return;
7841
+ }
7842
+ logSerenaSidecarStatus("Serena sidecar status", status);
7843
+ }
7844
+
6721
7845
  // providers/github-provider.ts
6722
7846
  var GitHubIssueProvider = class {
6723
7847
  client;
@@ -6730,6 +7854,17 @@ var GitHubIssueProvider = class {
6730
7854
  this.blockedLabel = options?.blockedLabel ?? "blocked";
6731
7855
  this.issueListLimit = options?.issueListLimit ?? 50;
6732
7856
  }
7857
+ async createIssue(options) {
7858
+ const { data } = await this.client.octokit.rest.issues.create({
7859
+ owner: this.client.owner,
7860
+ repo: this.client.repo,
7861
+ title: options.title,
7862
+ body: options.body ?? "",
7863
+ labels: options.labels,
7864
+ assignees: options.assignees
7865
+ });
7866
+ return { number: data.number, url: data.html_url, title: data.title };
7867
+ }
6733
7868
  async fetchIssue(number) {
6734
7869
  const { data } = await this.client.octokit.rest.issues.get({
6735
7870
  owner: this.client.owner,
@@ -7234,47 +8369,47 @@ function formatChecks2(checks) {
7234
8369
  init_common();
7235
8370
 
7236
8371
  // execution/sandcastle-execution.ts
7237
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
7238
- import { join as join10 } from "path";
8372
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "fs";
8373
+ import { join as join12 } from "path";
7239
8374
  import { createWorktree, opencode } from "@ai-hero/sandcastle";
7240
8375
  import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
7241
8376
 
7242
8377
  // execution/execution-provider.ts
7243
8378
  init_common();
7244
8379
  import { writeFile as writeFile2 } from "fs/promises";
7245
- import { dirname as dirname4, join as join9 } from "path";
8380
+ import { dirname as dirname5, join as join11 } from "path";
7246
8381
  async function writeExecutionArtifacts(worktreePath, artifacts) {
7247
8382
  for (const artifact of artifacts) {
7248
- const filePath = join9(worktreePath, artifact.path);
7249
- await ensureDir(dirname4(filePath));
8383
+ const filePath = join11(worktreePath, artifact.path);
8384
+ await ensureDir(dirname5(filePath));
7250
8385
  await writeFile2(filePath, artifact.content, "utf-8");
7251
8386
  }
7252
8387
  }
7253
8388
 
7254
8389
  // execution/sandbox-image-build.ts
7255
8390
  init_common();
7256
- import path5 from "path";
8391
+ import path7 from "path";
7257
8392
 
7258
8393
  // execution/sandbox-image.ts
7259
8394
  import { createHash as createHash2 } from "crypto";
7260
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
7261
- import path4 from "path";
8395
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
8396
+ import path6 from "path";
7262
8397
  function sandboxImageName(repoRoot2) {
7263
- const dirName = path4.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
8398
+ const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
7264
8399
  const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
7265
8400
  const baseName = sanitized || "local";
7266
- const dockerfilePath = path4.join(repoRoot2, ".sandcastle", "Dockerfile");
7267
- if (!existsSync7(dockerfilePath)) {
8401
+ const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
8402
+ if (!existsSync9(dockerfilePath)) {
7268
8403
  return `sandcastle:${baseName}`;
7269
8404
  }
7270
- const fingerprint = createHash2("sha256").update(readFileSync6(dockerfilePath)).digest("hex").slice(0, 8);
8405
+ const fingerprint = createHash2("sha256").update(readFileSync8(dockerfilePath)).digest("hex").slice(0, 8);
7271
8406
  return `sandcastle:${baseName}-${fingerprint}`;
7272
8407
  }
7273
8408
 
7274
8409
  // execution/sandbox-image-build.ts
7275
8410
  async function ensureSandboxImageBuilt(repoRoot2, options) {
7276
8411
  const imageName = sandboxImageName(repoRoot2);
7277
- const dockerfilePath = path5.join(repoRoot2, ".sandcastle", "Dockerfile");
8412
+ const dockerfilePath = path7.join(repoRoot2, ".sandcastle", "Dockerfile");
7278
8413
  if (!options?.force) {
7279
8414
  try {
7280
8415
  await execCapture("docker", ["image", "inspect", imageName]);
@@ -7312,6 +8447,25 @@ function buildSandboxOptions(repoRoot2, sandbox) {
7312
8447
  };
7313
8448
  }
7314
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
+
7315
8469
  // execution/sandcastle-execution.ts
7316
8470
  var SandcastleExecutionProvider = class {
7317
8471
  async createSession() {
@@ -7364,6 +8518,10 @@ var SandcastleExecutionSession = class {
7364
8518
  if (autoApprove) {
7365
8519
  env.OPENCODE_AUTO_APPROVE = "true";
7366
8520
  }
8521
+ const serenaConfig = buildSerenaOpenCodeConfig(stage, options.serena);
8522
+ if (serenaConfig) {
8523
+ env.OPENCODE_CONFIG_CONTENT = JSON.stringify(serenaConfig);
8524
+ }
7367
8525
  const logPath = `${repoRoot2}/.pourkit/logs/${sanitizeBranch(branchName)}-${Date.now()}.log`;
7368
8526
  await ensureSandboxImageBuilt(repoRoot2, { force: sandbox.forceRebuild });
7369
8527
  try {
@@ -7482,12 +8640,12 @@ function sanitizeBranch(branchName) {
7482
8640
  return branchName.replace(/[^A-Za-z0-9._-]/g, "-");
7483
8641
  }
7484
8642
  function savePromptToFile(repoRoot2, stage, iteration, prompt) {
7485
- const promptsDir = join10(repoRoot2, ".pourkit", ".tmp", "prompts");
7486
- mkdirSync5(promptsDir, { recursive: true });
8643
+ const promptsDir = join12(repoRoot2, ".pourkit", ".tmp", "prompts");
8644
+ mkdirSync6(promptsDir, { recursive: true });
7487
8645
  const timestamp2 = Date.now();
7488
8646
  const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
7489
8647
  const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
7490
- const filePath = join10(promptsDir, filename);
8648
+ const filePath = join12(promptsDir, filename);
7491
8649
  writeFileSync4(filePath, prompt, "utf-8");
7492
8650
  }
7493
8651
 
@@ -7553,19 +8711,91 @@ async function handleError(logger, error) {
7553
8711
  function createCliProgram(version) {
7554
8712
  const program = new Command();
7555
8713
  program.name("pourkit").version(version).exitOverride().description("AI-driven issue-to-PR workflow for GitHub repositories.");
7556
- program.command("issue").argument("<number>", "issue number").requiredOption("--target <name>", "target name").option("--force", "bypass issue gates").option(
8714
+ const issueCommand = program.command("issue");
8715
+ issueCommand.command("create").description("Create a new GitHub issue").requiredOption("--title <title>", "issue title").option("--body <body>", "issue body text").option("--body-file <path>", "file to read issue body from").option(
8716
+ "--label <label>",
8717
+ "label to apply (repeatable)",
8718
+ (value, previous) => {
8719
+ previous.push(value);
8720
+ return previous;
8721
+ },
8722
+ []
8723
+ ).option(
8724
+ "--assignee <login>",
8725
+ "assignee login (repeatable)",
8726
+ (value, previous) => {
8727
+ previous.push(value);
8728
+ return previous;
8729
+ },
8730
+ []
8731
+ ).option("--cwd <path>", "target repository directory").action(
8732
+ async (options) => {
8733
+ const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
8734
+ const logPath = path8.join(
8735
+ targetRepoRoot,
8736
+ ".pourkit",
8737
+ "logs",
8738
+ "issue-create.log"
8739
+ );
8740
+ const logger = createLogger("pourkit", logPath);
8741
+ const client = await requireGitHubClient({ cwd: targetRepoRoot });
8742
+ const issueProvider = new GitHubIssueProvider(client);
8743
+ try {
8744
+ const args = ["--title", options.title];
8745
+ if (options.body) {
8746
+ args.push("--body", options.body);
8747
+ }
8748
+ if (options.bodyFile) {
8749
+ const bodyFileArg = options.cwd && !path8.isAbsolute(options.bodyFile) ? path8.resolve(options.cwd, options.bodyFile) : options.bodyFile;
8750
+ args.push("--body-file", bodyFileArg);
8751
+ }
8752
+ for (const label of options.label) {
8753
+ args.push("--label", label);
8754
+ }
8755
+ for (const assignee of options.assignee) {
8756
+ args.push("--assignee", assignee);
8757
+ }
8758
+ const result = await runIssueCreateCommand(
8759
+ args,
8760
+ issueProvider,
8761
+ logger
8762
+ );
8763
+ logger.raw("Issue created successfully:");
8764
+ logger.raw(` Issue Number: #${result.issueNumber}`);
8765
+ logger.raw(` Issue Title: ${result.issueTitle}`);
8766
+ logger.raw(` Issue URL: ${result.issueUrl}`);
8767
+ await logger.close();
8768
+ } catch (error) {
8769
+ await handleError(logger, error);
8770
+ }
8771
+ }
8772
+ );
8773
+ issueCommand.argument("[value]", "issue number or subcommand").option("--target <name>", "target name").option("--force", "bypass issue gates").option(
7557
8774
  "--reset-worktree",
7558
8775
  "delete local issue worktree and branch before starting"
7559
8776
  ).option("--cwd <path>", "target repository directory").action(
7560
- async (issueNumberRaw, options) => {
8777
+ async (value, options) => {
8778
+ if (!value) {
8779
+ console.error(
8780
+ "Invalid command. Use 'issue <number>' or 'issue create --title ...'"
8781
+ );
8782
+ process.exit(1);
8783
+ }
8784
+ const issueNumberRaw = value;
7561
8785
  const issueNumber = Number.parseInt(issueNumberRaw, 10);
7562
8786
  if (Number.isNaN(issueNumber)) {
7563
8787
  console.error(`Invalid issue number: ${issueNumberRaw}`);
7564
8788
  process.exit(1);
7565
8789
  }
8790
+ if (!options.target) {
8791
+ console.error(
8792
+ "error: required option '--target <name>' not specified"
8793
+ );
8794
+ process.exit(1);
8795
+ }
7566
8796
  const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
7567
8797
  const config = await loadRepoConfig(targetRepoRoot);
7568
- const logPath = path6.join(
8798
+ const logPath = path8.join(
7569
8799
  targetRepoRoot,
7570
8800
  ".pourkit",
7571
8801
  "logs",
@@ -7624,7 +8854,7 @@ function createCliProgram(version) {
7624
8854
  const prdRef = options.prd ? normalizePrdRef(options.prd) : void 0;
7625
8855
  const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
7626
8856
  const config = await loadRepoConfig(targetRepoRoot);
7627
- const logPath = path6.join(
8857
+ const logPath = path8.join(
7628
8858
  targetRepoRoot,
7629
8859
  ".pourkit",
7630
8860
  "logs",
@@ -7721,6 +8951,35 @@ function createCliProgram(version) {
7721
8951
  await runInitCommand(initOptions);
7722
8952
  }
7723
8953
  );
8954
+ const serena = program.command("serena").description("Serena baseline and sidecar commands");
8955
+ serena.command("init").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(async (options) => {
8956
+ await runSerenaInitCommand({
8957
+ target: options.target,
8958
+ cwd: options.cwd
8959
+ });
8960
+ });
8961
+ serena.command("refresh").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(async (options) => {
8962
+ await runSerenaRefreshCommand({
8963
+ target: options.target,
8964
+ cwd: options.cwd
8965
+ });
8966
+ });
8967
+ serena.command("start").option("--cwd <path>", "target repository directory").action(async (options) => {
8968
+ await runSerenaStartCommand({
8969
+ cwd: options.cwd
8970
+ });
8971
+ });
8972
+ serena.command("stop").option("--cwd <path>", "target repository directory").action(async (options) => {
8973
+ await runSerenaStopCommand({
8974
+ cwd: options.cwd
8975
+ });
8976
+ });
8977
+ serena.command("status").option("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(async (options) => {
8978
+ await runSerenaStatusCommand({
8979
+ target: options.target,
8980
+ cwd: options.cwd
8981
+ });
8982
+ });
7724
8983
  const pr = program.command("pr").description("Pull request commands");
7725
8984
  pr.command("create").requiredOption("--target <name>", "target name").requiredOption("--title <title>", "PR title").option("--base <base>", "base branch").option("--head <branch>", "head branch").option("--body <body>", "PR body").option("--body-file <path>", "file to read PR body from").option(
7726
8985
  "--issue <number>",
@@ -7734,7 +8993,7 @@ function createCliProgram(version) {
7734
8993
  async (options) => {
7735
8994
  const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
7736
8995
  const config = await loadRepoConfig(targetRepoRoot);
7737
- const logPath = path6.join(
8996
+ const logPath = path8.join(
7738
8997
  targetRepoRoot,
7739
8998
  ".pourkit",
7740
8999
  "logs",
@@ -7780,7 +9039,7 @@ function createCliProgram(version) {
7780
9039
  async (prNumber, options) => {
7781
9040
  const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
7782
9041
  const config = await loadRepoConfig(targetRepoRoot);
7783
- const logPath = path6.join(
9042
+ const logPath = path8.join(
7784
9043
  targetRepoRoot,
7785
9044
  ".pourkit",
7786
9045
  "logs",
@@ -7817,11 +9076,11 @@ function createCliProgram(version) {
7817
9076
  return program;
7818
9077
  }
7819
9078
  async function resolveCliVersion() {
7820
- if (isPackageVersion("0.0.0-next-20260530000923")) {
7821
- return "0.0.0-next-20260530000923";
9079
+ if (isPackageVersion("0.0.0-next-20260531213458")) {
9080
+ return "0.0.0-next-20260531213458";
7822
9081
  }
7823
- if (isReleaseVersion("0.0.0-next-20260530000923")) {
7824
- return "0.0.0-next-20260530000923";
9082
+ if (isReleaseVersion("0.0.0-next-20260531213458")) {
9083
+ return "0.0.0-next-20260531213458";
7825
9084
  }
7826
9085
  try {
7827
9086
  const root = repoRoot();