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

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
@@ -3673,7 +3673,7 @@ function shellQuote(value) {
3673
3673
 
3674
3674
  // commands/issue-run.ts
3675
3675
  import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
3676
- import { isAbsolute as isAbsolute4, join as join14 } from "path";
3676
+ import { isAbsolute as isAbsolute4, join as join15 } from "path";
3677
3677
 
3678
3678
  // pr/templates.ts
3679
3679
  init_common();
@@ -3688,7 +3688,50 @@ function renderTemplate(template, issue) {
3688
3688
  // commands/issue-run.ts
3689
3689
  init_common();
3690
3690
 
3691
- // commands/base-refresh.ts
3691
+ // issues/issue-base-refresh.ts
3692
+ init_common();
3693
+ import { Exit as Exit3 } from "effect";
3694
+
3695
+ // failure-resolution/effect-runtime.ts
3696
+ import { Effect as Effect4 } from "effect";
3697
+
3698
+ // shared/effect-runtime.ts
3699
+ import { Effect as Effect3, Exit } from "effect";
3700
+ import { UnknownException } from "effect/Cause";
3701
+ var initialized = false;
3702
+ function initializeEffectRuntime() {
3703
+ if (initialized) return;
3704
+ initialized = true;
3705
+ }
3706
+ function runEffect(program) {
3707
+ ensureEffectRuntime();
3708
+ return Effect3.runPromiseExit(program);
3709
+ }
3710
+ function ensureEffectRuntime() {
3711
+ if (!initialized) {
3712
+ initializeEffectRuntime();
3713
+ }
3714
+ }
3715
+ function unwrapError(error) {
3716
+ if (error instanceof UnknownException && error.error !== void 0) {
3717
+ return error.error;
3718
+ }
3719
+ return error;
3720
+ }
3721
+ function runEffectAndMapExit(program) {
3722
+ return runEffect(program).then((exit) => {
3723
+ if (Exit.isSuccess(exit)) return exit.value;
3724
+ const cause = exit.cause;
3725
+ if (cause._tag === "Fail") {
3726
+ const error = unwrapError(cause.error);
3727
+ throw error instanceof Error ? error : new Error(String(error));
3728
+ }
3729
+ const defect = cause._tag === "Die" ? cause.defect : cause._tag === "Interrupt" ? "interrupted" : `unhandled cause: ${cause._tag}`;
3730
+ throw defect instanceof Error ? defect : new Error(`Unexpected defect: ${String(defect)}`);
3731
+ });
3732
+ }
3733
+
3734
+ // issues/base-refresh.ts
3692
3735
  init_common();
3693
3736
  async function isIssueBranchStale(worktreePath, baseBranch, logger) {
3694
3737
  try {
@@ -3768,45 +3811,6 @@ function invalidateAfterBaseRefresh(state) {
3768
3811
  };
3769
3812
  }
3770
3813
 
3771
- // failure-resolution/effect-runtime.ts
3772
- import { Effect as Effect4 } from "effect";
3773
-
3774
- // shared/effect-runtime.ts
3775
- import { Effect as Effect3, Exit } from "effect";
3776
- import { UnknownException } from "effect/Cause";
3777
- var initialized = false;
3778
- function initializeEffectRuntime() {
3779
- if (initialized) return;
3780
- initialized = true;
3781
- }
3782
- function runEffect(program) {
3783
- ensureEffectRuntime();
3784
- return Effect3.runPromiseExit(program);
3785
- }
3786
- function ensureEffectRuntime() {
3787
- if (!initialized) {
3788
- initializeEffectRuntime();
3789
- }
3790
- }
3791
- function unwrapError(error) {
3792
- if (error instanceof UnknownException && error.error !== void 0) {
3793
- return error.error;
3794
- }
3795
- return error;
3796
- }
3797
- function runEffectAndMapExit(program) {
3798
- return runEffect(program).then((exit) => {
3799
- if (Exit.isSuccess(exit)) return exit.value;
3800
- const cause = exit.cause;
3801
- if (cause._tag === "Fail") {
3802
- const error = unwrapError(cause.error);
3803
- throw error instanceof Error ? error : new Error(String(error));
3804
- }
3805
- const defect = cause._tag === "Die" ? cause.defect : cause._tag === "Interrupt" ? "interrupted" : `unhandled cause: ${cause._tag}`;
3806
- throw defect instanceof Error ? defect : new Error(`Unexpected defect: ${String(defect)}`);
3807
- });
3808
- }
3809
-
3810
3814
  // shared/attempt-log.ts
3811
3815
  import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync8 } from "fs";
3812
3816
  import { dirname as dirname4, join as join7 } from "path";
@@ -3958,8 +3962,438 @@ async function hasUnresolvedConflictMarkers(worktreePath, files) {
3958
3962
  return false;
3959
3963
  }
3960
3964
 
3961
- // commands/issue-run.ts
3962
- import { Exit as Exit3 } from "effect";
3965
+ // failure-resolution/failure-resolution-agent.ts
3966
+ import { readFileSync as readFileSync10 } from "fs";
3967
+ import { join as join9 } from "path";
3968
+
3969
+ // failure-resolution/recovery-policy.ts
3970
+ function isSecuritySensitiveFailure(failure) {
3971
+ return failure instanceof PublishedHistoryRisk || failure instanceof SafetyFailure;
3972
+ }
3973
+ async function evaluateRecoveryPolicy(params) {
3974
+ if (isSecuritySensitiveFailure(params.failure)) {
3975
+ return {
3976
+ decision: "HANDOFF_TO_HUMAN",
3977
+ reason: "Security-sensitive failure \u2014 AI recovery bypassed"
3978
+ };
3979
+ }
3980
+ if (params.failure instanceof ConfigFailure) {
3981
+ if (params.agentRecommendedDecision !== "FAIL_RUN" && params.agentRecommendedDecision !== "HANDOFF_TO_HUMAN") {
3982
+ return {
3983
+ decision: "HANDOFF_TO_HUMAN",
3984
+ reason: `ConfigFailure \u2014 agent recommended ${params.agentRecommendedDecision} but config errors are not AI-repairable`
3985
+ };
3986
+ }
3987
+ }
3988
+ const budget = recoveryBudgetForFailure(
3989
+ params.worktreePath,
3990
+ params.fingerprint,
3991
+ params.maxAttempts
3992
+ );
3993
+ if (budget.exhausted) {
3994
+ return {
3995
+ decision: "HANDOFF_TO_HUMAN",
3996
+ reason: `Recovery budget exhausted (${budget.used}/${params.maxAttempts})`
3997
+ };
3998
+ }
3999
+ if (!params.allowedDecisions.includes(params.agentRecommendedDecision)) {
4000
+ return {
4001
+ decision: "HANDOFF_TO_HUMAN",
4002
+ reason: `Agent recommended ${params.agentRecommendedDecision} which is not allowed`
4003
+ };
4004
+ }
4005
+ if (params.agentRecommendedDecision === "FAIL_RUN") {
4006
+ return { decision: "FAIL_RUN", reason: "Agent recommended FAIL_RUN" };
4007
+ }
4008
+ return {
4009
+ decision: params.agentRecommendedDecision,
4010
+ reason: "Agent recommendation accepted"
4011
+ };
4012
+ }
4013
+
4014
+ // failure-resolution/failure-resolution-agent.ts
4015
+ function constructFailureResolutionPacket(failure, context) {
4016
+ return {
4017
+ failureType: failure._tag,
4018
+ stageName: context.stageName,
4019
+ attemptNumber: context.attemptNumber,
4020
+ worktreePath: context.worktreePath,
4021
+ branchName: context.branchName,
4022
+ baseBranch: context.baseBranch,
4023
+ conflictedPaths: failure instanceof RebaseConflict ? failure.conflictedPaths : void 0,
4024
+ failureSummary: failure.message,
4025
+ maxAttempts: context.maxAttempts,
4026
+ allowedDecisions: context.allowedDecisions,
4027
+ artifactTarget: context.artifactTarget
4028
+ };
4029
+ }
4030
+ async function runFailureResolutionAgent(options) {
4031
+ const {
4032
+ executionProvider,
4033
+ config,
4034
+ target,
4035
+ failure,
4036
+ packet,
4037
+ worktreePath,
4038
+ repoRoot: repoRoot2,
4039
+ logger
4040
+ } = options;
4041
+ const frConfig = target.strategy.failureResolution;
4042
+ const artifactPath = packet.artifactTarget;
4043
+ const fullArtifactPath = join9(worktreePath, artifactPath);
4044
+ const fingerprint = computeFailureFingerprint(packet.stageName, failure._tag);
4045
+ const prompt = [
4046
+ `# Failure Resolution: ${packet.failureType}`,
4047
+ "",
4048
+ "## Failure Context",
4049
+ "",
4050
+ "```json",
4051
+ JSON.stringify(packet, null, 2),
4052
+ "```",
4053
+ "",
4054
+ "## Instructions",
4055
+ "",
4056
+ `Write your resolution to: ${artifactPath}`,
4057
+ "Include a ```json block with: recoveryDecision, summary, changedFiles, verificationSummary (optional), verificationCommands (optional), notes (optional).",
4058
+ "",
4059
+ "Allowed decisions: " + packet.allowedDecisions.join(", "),
4060
+ "",
4061
+ `Before handoff, run: pourkit validate-artifact failure-resolution ${artifactPath} ${packet.allowedDecisions.map((decision) => `--allowed-decision ${decision}`).join(" ")}`
4062
+ ].join("\n");
4063
+ const retryResult = await executeWithMissingOrEmptyArtifactRetry({
4064
+ executionProvider,
4065
+ missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(frConfig),
4066
+ executionOptions: {
4067
+ stage: "failureResolution",
4068
+ agent: frConfig.agent,
4069
+ model: frConfig.model,
4070
+ variant: frConfig.variant,
4071
+ env: frConfig.env,
4072
+ prompt,
4073
+ target,
4074
+ repoRoot: repoRoot2,
4075
+ branchName: packet.branchName,
4076
+ sandbox: config.sandbox,
4077
+ autoApprove: true,
4078
+ worktreePath,
4079
+ artifactPath,
4080
+ artifacts: [],
4081
+ ...options.memory ? { memory: options.memory } : {},
4082
+ logger
4083
+ }
4084
+ });
4085
+ const executionResult = retryResult.executionResult;
4086
+ if (!executionResult.success) {
4087
+ await writeRecoveryAttempt(
4088
+ worktreePath,
4089
+ "failure",
4090
+ fingerprint,
4091
+ `Agent execution failed: ${executionResult.error}`,
4092
+ void 0,
4093
+ "HANDOFF_TO_HUMAN",
4094
+ packet.stageName
4095
+ );
4096
+ return {
4097
+ status: "handoff",
4098
+ decision: "HANDOFF_TO_HUMAN",
4099
+ reason: `Agent execution failed: ${executionResult.error}`
4100
+ };
4101
+ }
4102
+ if (retryResult.artifact._tag !== "content") {
4103
+ const reason = retryResult.artifact._tag === "empty" ? "Agent wrote empty artifact" : "Agent did not write artifact";
4104
+ await writeRecoveryAttempt(
4105
+ worktreePath,
4106
+ "failure",
4107
+ fingerprint,
4108
+ reason,
4109
+ void 0,
4110
+ "HANDOFF_TO_HUMAN",
4111
+ packet.stageName
4112
+ );
4113
+ return {
4114
+ status: "handoff",
4115
+ decision: "HANDOFF_TO_HUMAN",
4116
+ reason
4117
+ };
4118
+ }
4119
+ let artifact;
4120
+ try {
4121
+ const md = readFileSync10(fullArtifactPath, "utf-8");
4122
+ const validation2 = validateAgentArtifact({
4123
+ kind: "failure-resolution",
4124
+ artifactPath: fullArtifactPath,
4125
+ allowedDecisions: [...packet.allowedDecisions]
4126
+ });
4127
+ if (!validation2.ok) {
4128
+ throw new Error(validation2.reason);
4129
+ }
4130
+ artifact = parseRecoveryArtifact(md, artifactPath);
4131
+ } catch (error) {
4132
+ const reason = error instanceof Error ? error.message : "Failed to parse artifact";
4133
+ await writeRecoveryAttempt(
4134
+ worktreePath,
4135
+ "failure",
4136
+ fingerprint,
4137
+ reason,
4138
+ void 0,
4139
+ "HANDOFF_TO_HUMAN",
4140
+ packet.stageName
4141
+ );
4142
+ return { status: "handoff", decision: "HANDOFF_TO_HUMAN", reason };
4143
+ }
4144
+ const validation = validateRecoveryDecision(
4145
+ artifact,
4146
+ packet.allowedDecisions
4147
+ );
4148
+ if (!validation.valid) {
4149
+ await writeRecoveryAttempt(
4150
+ worktreePath,
4151
+ "failure",
4152
+ fingerprint,
4153
+ validation.reason,
4154
+ void 0,
4155
+ "HANDOFF_TO_HUMAN",
4156
+ packet.stageName
4157
+ );
4158
+ return {
4159
+ status: "handoff",
4160
+ decision: "HANDOFF_TO_HUMAN",
4161
+ reason: validation.reason
4162
+ };
4163
+ }
4164
+ const policyResult = await evaluateRecoveryPolicy({
4165
+ failure,
4166
+ worktreePath,
4167
+ fingerprint,
4168
+ maxAttempts: packet.maxAttempts,
4169
+ agentRecommendedDecision: validation.decision,
4170
+ allowedDecisions: packet.allowedDecisions
4171
+ });
4172
+ await writeRecoveryAttempt(
4173
+ worktreePath,
4174
+ policyResult.decision === "HANDOFF_TO_HUMAN" ? "handoff" : policyResult.decision === "FAIL_RUN" ? "failure" : "success",
4175
+ fingerprint,
4176
+ policyResult.reason,
4177
+ artifactPath,
4178
+ policyResult.decision,
4179
+ packet.stageName
4180
+ );
4181
+ if (policyResult.decision === "HANDOFF_TO_HUMAN") {
4182
+ return {
4183
+ status: "handoff",
4184
+ decision: "HANDOFF_TO_HUMAN",
4185
+ reason: policyResult.reason
4186
+ };
4187
+ }
4188
+ if (policyResult.decision === "FAIL_RUN") {
4189
+ return {
4190
+ status: "fail-run",
4191
+ decision: "FAIL_RUN",
4192
+ reason: policyResult.reason
4193
+ };
4194
+ }
4195
+ return {
4196
+ status: "recovered",
4197
+ decision: policyResult.decision,
4198
+ artifact
4199
+ };
4200
+ }
4201
+ async function writeRecoveryAttempt(worktreePath, outcome, fingerprint, summary, artifactRef, decision, stageName = "baseRefresh") {
4202
+ writeAttemptLog(worktreePath, {
4203
+ attemptType: "recovery",
4204
+ fingerprint,
4205
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4206
+ stage: stageName,
4207
+ outcome,
4208
+ artifactRef,
4209
+ decision: decision ?? (outcome === "handoff" ? "HANDOFF_TO_HUMAN" : outcome === "success" ? "RETRY_STAGE" : void 0)
4210
+ });
4211
+ }
4212
+
4213
+ // issues/issue-base-refresh.ts
4214
+ async function runIssueBaseRefresh(options) {
4215
+ const existingPr = await options.prProvider.getPr(options.branchName);
4216
+ const exit = await runBaseRefreshAttempt({
4217
+ worktreePath: options.worktreePath,
4218
+ baseBranch: options.effectiveBaseBranch,
4219
+ localGitBaseRef: options.baseRef,
4220
+ logger: options.logger,
4221
+ prNumber: existingPr?.number,
4222
+ prState: existingPr?.state
4223
+ });
4224
+ if (Exit3.isSuccess(exit)) {
4225
+ const refreshResult = exit.value;
4226
+ if (refreshResult.status === "refreshed") {
4227
+ if (options.worktreeState?.completedStages.builder) {
4228
+ const invalidatedState = invalidateAfterBaseRefresh(
4229
+ options.worktreeState
4230
+ );
4231
+ writeWorktreeRunState(options.worktreePath, invalidatedState);
4232
+ }
4233
+ }
4234
+ return;
4235
+ }
4236
+ const cause = exit.cause;
4237
+ if (cause._tag !== "Fail") {
4238
+ const defectMessage = cause._tag === "Die" ? `Base refresh failed with unexpected error: ${String(cause.defect)}` : `Base refresh failed with unhandled cause: ${cause._tag}`;
4239
+ throw new Error(defectMessage);
4240
+ }
4241
+ const failure = cause.error;
4242
+ if (failure instanceof RebaseConflict) {
4243
+ const frConfig = options.target.strategy.failureResolution;
4244
+ if (frConfig && options.worktreePath) {
4245
+ await handleRebaseConflict(failure, options);
4246
+ } else {
4247
+ writeConflictHandoffState(options, failure);
4248
+ await options.transitionIssueToFailureState(
4249
+ `Base refresh conflicted: ${failure.message}. Worktree preserved at ${options.worktreePath}.`
4250
+ );
4251
+ throw new Error(`Base refresh conflicted: ${failure.message}`);
4252
+ }
4253
+ return;
4254
+ }
4255
+ if (failure instanceof PublishedHistoryRisk) {
4256
+ await handlePublishedHistoryRisk(failure, options);
4257
+ return;
4258
+ }
4259
+ throw new Error(`Base refresh failed: ${failure.message}`);
4260
+ }
4261
+ async function handleRebaseConflict(failure, options) {
4262
+ const frConfig = options.target.strategy.failureResolution;
4263
+ const maxAttempts = frConfig.failureLimits?.RebaseConflict ?? frConfig.maxAttemptsPerFailure;
4264
+ let attemptNumber = 0;
4265
+ let currentFailure = failure;
4266
+ while (attemptNumber < maxAttempts) {
4267
+ attemptNumber++;
4268
+ const packet = constructFailureResolutionPacket(currentFailure, {
4269
+ stageName: "baseRefresh",
4270
+ attemptNumber,
4271
+ worktreePath: options.worktreePath,
4272
+ branchName: options.branchName,
4273
+ baseBranch: options.effectiveBaseBranch,
4274
+ maxAttempts,
4275
+ allowedDecisions: ["RETRY_STAGE", "HANDOFF_TO_HUMAN", "FAIL_RUN"],
4276
+ artifactTarget: `.pourkit/.tmp/failure-resolution/attempt-${attemptNumber}.md`
4277
+ });
4278
+ const result = await runFailureResolutionAgent({
4279
+ executionProvider: options.executionProvider,
4280
+ config: options.config,
4281
+ target: options.target,
4282
+ failure: currentFailure,
4283
+ packet,
4284
+ worktreePath: options.worktreePath,
4285
+ repoRoot: options.repoRoot,
4286
+ logger: options.logger,
4287
+ ...options.memory ? { memory: options.memory } : {}
4288
+ });
4289
+ if (result.status === "handoff") {
4290
+ writeFailureState(options, result.reason);
4291
+ await options.transitionIssueToFailureState(result.reason);
4292
+ throw new Error(result.reason);
4293
+ }
4294
+ if (result.status === "fail-run") {
4295
+ writeFailureState(options, result.reason);
4296
+ throw new Error(result.reason);
4297
+ }
4298
+ const changedPaths = packet.conflictedPaths ?? currentFailure.conflictedPaths;
4299
+ const markersRemain = await hasUnresolvedConflictMarkers(
4300
+ options.worktreePath,
4301
+ changedPaths
4302
+ );
4303
+ if (markersRemain) {
4304
+ const message2 = "Conflict resolution agent resolved artifact but conflict markers remain in files";
4305
+ writeFailureState(options, message2);
4306
+ await options.transitionIssueToFailureState(message2);
4307
+ throw new Error(message2);
4308
+ }
4309
+ await execCapture("git", ["add", ...changedPaths], {
4310
+ cwd: options.worktreePath,
4311
+ logger: options.logger,
4312
+ label: "git add conflicted paths"
4313
+ });
4314
+ try {
4315
+ await execCapture("git", ["rebase", "--continue"], {
4316
+ cwd: options.worktreePath,
4317
+ env: { ...process.env, GIT_EDITOR: "true" },
4318
+ logger: options.logger,
4319
+ label: "git rebase --continue"
4320
+ });
4321
+ if (options.worktreeState?.completedStages.builder) {
4322
+ const invalidatedState = invalidateAfterBaseRefresh(
4323
+ options.worktreeState
4324
+ );
4325
+ writeWorktreeRunState(options.worktreePath, invalidatedState);
4326
+ }
4327
+ return;
4328
+ } catch {
4329
+ const statusResult = await execCapture("git", ["status", "--porcelain"], {
4330
+ cwd: options.worktreePath,
4331
+ logger: options.logger,
4332
+ label: "git status"
4333
+ });
4334
+ 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);
4335
+ if (newConflictedPaths.length === 0) {
4336
+ const message2 = "git rebase --continue failed with no remaining conflicts";
4337
+ writeFailureState(options, message2);
4338
+ await options.transitionIssueToFailureState(message2);
4339
+ throw new Error(message2);
4340
+ }
4341
+ currentFailure = new RebaseConflict({
4342
+ conflictedPaths: newConflictedPaths,
4343
+ message: `Rebase conflict in: ${newConflictedPaths.join(", ")}`
4344
+ });
4345
+ }
4346
+ }
4347
+ const message = `Base refresh recovery exhausted after ${maxAttempts} attempts`;
4348
+ writeFailureState(options, message);
4349
+ await options.transitionIssueToFailureState(message);
4350
+ throw new Error(message);
4351
+ }
4352
+ async function handlePublishedHistoryRisk(failure, options) {
4353
+ const message = `Cannot auto-refresh published history: PR #${failure.prNumber} (${failure.prState}) exists for branch ${options.branchName}`;
4354
+ writeFailureState(options, message);
4355
+ await options.transitionIssueToFailureState(message);
4356
+ throw new Error(message);
4357
+ }
4358
+ function writeConflictHandoffState(options, failure) {
4359
+ const message = `Base refresh conflict detected. Handing off to human: ${failure.message}`;
4360
+ if (options.worktreeState) {
4361
+ updateWorktreeRunState(options.worktreePath, {
4362
+ lastFailure: { stage: "baseRefresh", message }
4363
+ });
4364
+ } else {
4365
+ writeWorktreeRunState(options.worktreePath, {
4366
+ issueNumber: options.issueNumber,
4367
+ targetName: options.target.name,
4368
+ branchName: options.branchName,
4369
+ baseBranch: options.effectiveBaseBranch,
4370
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4371
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4372
+ completedStages: {},
4373
+ review: { lifetimeIterations: 0 },
4374
+ lastFailure: { stage: "baseRefresh", message }
4375
+ });
4376
+ }
4377
+ }
4378
+ function writeFailureState(options, message) {
4379
+ if (options.worktreeState) {
4380
+ updateWorktreeRunState(options.worktreePath, {
4381
+ lastFailure: { stage: "baseRefresh", message }
4382
+ });
4383
+ } else {
4384
+ writeWorktreeRunState(options.worktreePath, {
4385
+ issueNumber: options.issueNumber,
4386
+ targetName: options.target.name,
4387
+ branchName: options.branchName,
4388
+ baseBranch: options.effectiveBaseBranch,
4389
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4390
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4391
+ completedStages: {},
4392
+ review: { lifetimeIterations: 0 },
4393
+ lastFailure: { stage: "baseRefresh", message }
4394
+ });
4395
+ }
4396
+ }
3963
4397
 
3964
4398
  // serena/baseline.ts
3965
4399
  init_common();
@@ -4220,310 +4654,62 @@ function sidecarOptions(paths, mcpUrl) {
4220
4654
  image: SERENA_IMAGE,
4221
4655
  mcpUrl
4222
4656
  };
4223
- }
4224
- async function canReachMcp(url) {
4225
- for (let attempt = 0; attempt < 10; attempt += 1) {
4226
- try {
4227
- await fetch(url, { method: "GET", signal: AbortSignal.timeout(500) });
4228
- return true;
4229
- } catch {
4230
- if (attempt < 9) {
4231
- await new Promise((resolve5) => setTimeout(resolve5, 100));
4232
- }
4233
- }
4234
- }
4235
- return false;
4236
- }
4237
- function formatError(error) {
4238
- return error instanceof Error ? error.message : String(error);
4239
- }
4240
- async function prepareSerenaForTarget(options) {
4241
- if (!options.enabled) {
4242
- return { enabled: false };
4243
- }
4244
- try {
4245
- const paths = await ensureBaselineWorktree({
4246
- repoRoot: options.repoRoot,
4247
- dataDir: options.dataDir
4248
- });
4249
- await prepareSerenaSidecarConfig({
4250
- baselineWorktreePath: paths.baselineWorktreePath,
4251
- dataDir: paths.dataDir
4252
- });
4253
- const status = options.autoStart ? await startSerenaSidecar(sidecarOptions(paths, options.mcpUrl)) : await getSerenaSidecarStatus(sidecarOptions(paths, options.mcpUrl));
4254
- const mcpReachable = await canReachMcp(options.mcpUrl);
4255
- if (!mcpReachable) {
4256
- return {
4257
- enabled: true,
4258
- available: false,
4259
- error: status.running ? `Serena MCP is not reachable at ${options.mcpUrl}` : `Serena sidecar is not running for target ${options.targetName}`
4260
- };
4261
- }
4262
- await refreshSerenaBaseline({
4263
- repoRoot: options.repoRoot,
4264
- dataDir: options.dataDir,
4265
- baseBranch: options.baseBranch
4266
- });
4267
- return {
4268
- enabled: true,
4269
- available: true,
4270
- mcpUrl: options.mcpUrl
4271
- };
4272
- } catch (error) {
4273
- return {
4274
- enabled: true,
4275
- available: false,
4276
- error: formatError(error)
4277
- };
4278
- }
4279
- }
4280
-
4281
- // failure-resolution/failure-resolution-agent.ts
4282
- import { readFileSync as readFileSync10 } from "fs";
4283
- import { join as join9 } from "path";
4284
-
4285
- // failure-resolution/recovery-policy.ts
4286
- function isSecuritySensitiveFailure(failure) {
4287
- return failure instanceof PublishedHistoryRisk || failure instanceof SafetyFailure;
4288
- }
4289
- async function evaluateRecoveryPolicy(params) {
4290
- if (isSecuritySensitiveFailure(params.failure)) {
4291
- return {
4292
- decision: "HANDOFF_TO_HUMAN",
4293
- reason: "Security-sensitive failure \u2014 AI recovery bypassed"
4294
- };
4295
- }
4296
- if (params.failure instanceof ConfigFailure) {
4297
- if (params.agentRecommendedDecision !== "FAIL_RUN" && params.agentRecommendedDecision !== "HANDOFF_TO_HUMAN") {
4298
- return {
4299
- decision: "HANDOFF_TO_HUMAN",
4300
- reason: `ConfigFailure \u2014 agent recommended ${params.agentRecommendedDecision} but config errors are not AI-repairable`
4301
- };
4302
- }
4303
- }
4304
- const budget = recoveryBudgetForFailure(
4305
- params.worktreePath,
4306
- params.fingerprint,
4307
- params.maxAttempts
4308
- );
4309
- if (budget.exhausted) {
4310
- return {
4311
- decision: "HANDOFF_TO_HUMAN",
4312
- reason: `Recovery budget exhausted (${budget.used}/${params.maxAttempts})`
4313
- };
4314
- }
4315
- if (!params.allowedDecisions.includes(params.agentRecommendedDecision)) {
4316
- return {
4317
- decision: "HANDOFF_TO_HUMAN",
4318
- reason: `Agent recommended ${params.agentRecommendedDecision} which is not allowed`
4319
- };
4320
- }
4321
- if (params.agentRecommendedDecision === "FAIL_RUN") {
4322
- return { decision: "FAIL_RUN", reason: "Agent recommended FAIL_RUN" };
4323
- }
4324
- return {
4325
- decision: params.agentRecommendedDecision,
4326
- reason: "Agent recommendation accepted"
4327
- };
4328
- }
4329
-
4330
- // failure-resolution/failure-resolution-agent.ts
4331
- function constructFailureResolutionPacket(failure, context) {
4332
- return {
4333
- failureType: failure._tag,
4334
- stageName: context.stageName,
4335
- attemptNumber: context.attemptNumber,
4336
- worktreePath: context.worktreePath,
4337
- branchName: context.branchName,
4338
- baseBranch: context.baseBranch,
4339
- conflictedPaths: failure instanceof RebaseConflict ? failure.conflictedPaths : void 0,
4340
- failureSummary: failure.message,
4341
- maxAttempts: context.maxAttempts,
4342
- allowedDecisions: context.allowedDecisions,
4343
- artifactTarget: context.artifactTarget
4344
- };
4345
- }
4346
- async function runFailureResolutionAgent(options) {
4347
- const {
4348
- executionProvider,
4349
- config,
4350
- target,
4351
- failure,
4352
- packet,
4353
- worktreePath,
4354
- repoRoot: repoRoot2,
4355
- logger
4356
- } = options;
4357
- const frConfig = target.strategy.failureResolution;
4358
- const artifactPath = packet.artifactTarget;
4359
- const fullArtifactPath = join9(worktreePath, artifactPath);
4360
- const fingerprint = computeFailureFingerprint(packet.stageName, failure._tag);
4361
- const prompt = [
4362
- `# Failure Resolution: ${packet.failureType}`,
4363
- "",
4364
- "## Failure Context",
4365
- "",
4366
- "```json",
4367
- JSON.stringify(packet, null, 2),
4368
- "```",
4369
- "",
4370
- "## Instructions",
4371
- "",
4372
- `Write your resolution to: ${artifactPath}`,
4373
- "Include a ```json block with: recoveryDecision, summary, changedFiles, verificationSummary (optional), verificationCommands (optional), notes (optional).",
4374
- "",
4375
- "Allowed decisions: " + packet.allowedDecisions.join(", "),
4376
- "",
4377
- `Before handoff, run: pourkit validate-artifact failure-resolution ${artifactPath} ${packet.allowedDecisions.map((decision) => `--allowed-decision ${decision}`).join(" ")}`
4378
- ].join("\n");
4379
- const retryResult = await executeWithMissingOrEmptyArtifactRetry({
4380
- executionProvider,
4381
- missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(frConfig),
4382
- executionOptions: {
4383
- stage: "failureResolution",
4384
- agent: frConfig.agent,
4385
- model: frConfig.model,
4386
- variant: frConfig.variant,
4387
- env: frConfig.env,
4388
- prompt,
4389
- target,
4390
- repoRoot: repoRoot2,
4391
- branchName: packet.branchName,
4392
- sandbox: config.sandbox,
4393
- autoApprove: true,
4394
- worktreePath,
4395
- artifactPath,
4396
- artifacts: [],
4397
- ...options.memory ? { memory: options.memory } : {},
4398
- logger
4399
- }
4400
- });
4401
- const executionResult = retryResult.executionResult;
4402
- if (!executionResult.success) {
4403
- await writeRecoveryAttempt(
4404
- worktreePath,
4405
- "failure",
4406
- fingerprint,
4407
- `Agent execution failed: ${executionResult.error}`,
4408
- void 0,
4409
- "HANDOFF_TO_HUMAN",
4410
- packet.stageName
4411
- );
4412
- return {
4413
- status: "handoff",
4414
- decision: "HANDOFF_TO_HUMAN",
4415
- reason: `Agent execution failed: ${executionResult.error}`
4416
- };
4417
- }
4418
- if (retryResult.artifact._tag !== "content") {
4419
- const reason = retryResult.artifact._tag === "empty" ? "Agent wrote empty artifact" : "Agent did not write artifact";
4420
- await writeRecoveryAttempt(
4421
- worktreePath,
4422
- "failure",
4423
- fingerprint,
4424
- reason,
4425
- void 0,
4426
- "HANDOFF_TO_HUMAN",
4427
- packet.stageName
4428
- );
4429
- return {
4430
- status: "handoff",
4431
- decision: "HANDOFF_TO_HUMAN",
4432
- reason
4433
- };
4434
- }
4435
- let artifact;
4436
- try {
4437
- const md = readFileSync10(fullArtifactPath, "utf-8");
4438
- const validation2 = validateAgentArtifact({
4439
- kind: "failure-resolution",
4440
- artifactPath: fullArtifactPath,
4441
- allowedDecisions: [...packet.allowedDecisions]
4442
- });
4443
- if (!validation2.ok) {
4444
- throw new Error(validation2.reason);
4445
- }
4446
- artifact = parseRecoveryArtifact(md, artifactPath);
4447
- } catch (error) {
4448
- const reason = error instanceof Error ? error.message : "Failed to parse artifact";
4449
- await writeRecoveryAttempt(
4450
- worktreePath,
4451
- "failure",
4452
- fingerprint,
4453
- reason,
4454
- void 0,
4455
- "HANDOFF_TO_HUMAN",
4456
- packet.stageName
4457
- );
4458
- return { status: "handoff", decision: "HANDOFF_TO_HUMAN", reason };
4459
- }
4460
- const validation = validateRecoveryDecision(
4461
- artifact,
4462
- packet.allowedDecisions
4463
- );
4464
- if (!validation.valid) {
4465
- await writeRecoveryAttempt(
4466
- worktreePath,
4467
- "failure",
4468
- fingerprint,
4469
- validation.reason,
4470
- void 0,
4471
- "HANDOFF_TO_HUMAN",
4472
- packet.stageName
4473
- );
4474
- return {
4475
- status: "handoff",
4476
- decision: "HANDOFF_TO_HUMAN",
4477
- reason: validation.reason
4478
- };
4657
+ }
4658
+ async function canReachMcp(url) {
4659
+ for (let attempt = 0; attempt < 10; attempt += 1) {
4660
+ try {
4661
+ await fetch(url, { method: "GET", signal: AbortSignal.timeout(500) });
4662
+ return true;
4663
+ } catch {
4664
+ if (attempt < 9) {
4665
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
4666
+ }
4667
+ }
4479
4668
  }
4480
- const policyResult = await evaluateRecoveryPolicy({
4481
- failure,
4482
- worktreePath,
4483
- fingerprint,
4484
- maxAttempts: packet.maxAttempts,
4485
- agentRecommendedDecision: validation.decision,
4486
- allowedDecisions: packet.allowedDecisions
4487
- });
4488
- await writeRecoveryAttempt(
4489
- worktreePath,
4490
- policyResult.decision === "HANDOFF_TO_HUMAN" ? "handoff" : policyResult.decision === "FAIL_RUN" ? "failure" : "success",
4491
- fingerprint,
4492
- policyResult.reason,
4493
- artifactPath,
4494
- policyResult.decision,
4495
- packet.stageName
4496
- );
4497
- if (policyResult.decision === "HANDOFF_TO_HUMAN") {
4669
+ return false;
4670
+ }
4671
+ function formatError(error) {
4672
+ return error instanceof Error ? error.message : String(error);
4673
+ }
4674
+ async function prepareSerenaForTarget(options) {
4675
+ if (!options.enabled) {
4676
+ return { enabled: false };
4677
+ }
4678
+ try {
4679
+ const paths = await ensureBaselineWorktree({
4680
+ repoRoot: options.repoRoot,
4681
+ dataDir: options.dataDir
4682
+ });
4683
+ await prepareSerenaSidecarConfig({
4684
+ baselineWorktreePath: paths.baselineWorktreePath,
4685
+ dataDir: paths.dataDir
4686
+ });
4687
+ const status = options.autoStart ? await startSerenaSidecar(sidecarOptions(paths, options.mcpUrl)) : await getSerenaSidecarStatus(sidecarOptions(paths, options.mcpUrl));
4688
+ const mcpReachable = await canReachMcp(options.mcpUrl);
4689
+ if (!mcpReachable) {
4690
+ return {
4691
+ enabled: true,
4692
+ available: false,
4693
+ error: status.running ? `Serena MCP is not reachable at ${options.mcpUrl}` : `Serena sidecar is not running for target ${options.targetName}`
4694
+ };
4695
+ }
4696
+ await refreshSerenaBaseline({
4697
+ repoRoot: options.repoRoot,
4698
+ dataDir: options.dataDir,
4699
+ baseBranch: options.baseBranch
4700
+ });
4498
4701
  return {
4499
- status: "handoff",
4500
- decision: "HANDOFF_TO_HUMAN",
4501
- reason: policyResult.reason
4702
+ enabled: true,
4703
+ available: true,
4704
+ mcpUrl: options.mcpUrl
4502
4705
  };
4503
- }
4504
- if (policyResult.decision === "FAIL_RUN") {
4706
+ } catch (error) {
4505
4707
  return {
4506
- status: "fail-run",
4507
- decision: "FAIL_RUN",
4508
- reason: policyResult.reason
4708
+ enabled: true,
4709
+ available: false,
4710
+ error: formatError(error)
4509
4711
  };
4510
4712
  }
4511
- return {
4512
- status: "recovered",
4513
- decision: policyResult.decision,
4514
- artifact
4515
- };
4516
- }
4517
- async function writeRecoveryAttempt(worktreePath, outcome, fingerprint, summary, artifactRef, decision, stageName = "baseRefresh") {
4518
- writeAttemptLog(worktreePath, {
4519
- attemptType: "recovery",
4520
- fingerprint,
4521
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4522
- stage: stageName,
4523
- outcome,
4524
- artifactRef,
4525
- decision: decision ?? (outcome === "handoff" ? "HANDOFF_TO_HUMAN" : outcome === "success" ? "RETRY_STAGE" : void 0)
4526
- });
4527
4713
  }
4528
4714
 
4529
4715
  // commands/pr-description-agent.ts
@@ -5759,59 +5945,188 @@ async function runIssueFinalReviewAgent(options) {
5759
5945
  verificationPassed: parsed.verification?.passed === true
5760
5946
  };
5761
5947
  }
5762
- if (verdict === "needs_human_review") {
5763
- return {
5764
- verdict: "needs_human_review",
5765
- artifactPath,
5766
- needsHumanReason: parsed.needsHumanReason,
5767
- selfRetouched: parsed.selfRetouched === true,
5768
- changedPaths: extractChangedPaths(parsed)
5769
- };
5948
+ if (verdict === "needs_human_review") {
5949
+ return {
5950
+ verdict: "needs_human_review",
5951
+ artifactPath,
5952
+ needsHumanReason: parsed.needsHumanReason,
5953
+ selfRetouched: parsed.selfRetouched === true,
5954
+ changedPaths: extractChangedPaths(parsed)
5955
+ };
5956
+ }
5957
+ throw new Error(
5958
+ `Unknown Issue Final Review verdict: ${JSON.stringify(verdict)}`
5959
+ );
5960
+ }
5961
+ function extractChangedPaths(parsed) {
5962
+ return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
5963
+ (file) => file && typeof file === "object" ? file.path : void 0
5964
+ ).filter((path9) => typeof path9 === "string") : [];
5965
+ }
5966
+ function loadIssueFinalReviewPrompt(options) {
5967
+ const { repoRoot: repoRoot2, promptTemplate, validationCommand } = options;
5968
+ const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
5969
+ const promptBody = existsSync11(promptPath) ? readFileSync13(promptPath, "utf-8") : promptTemplate;
5970
+ return appendProtectedWorkGuidance(`${promptBody}
5971
+
5972
+ ## Shared Run Context
5973
+
5974
+ Read the selected issue requirements, planning context (including plan and PRD content when present), comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
5975
+
5976
+ ## Initial Verification Pass
5977
+
5978
+ - First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
5979
+ - Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
5980
+ - Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
5981
+ - If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
5982
+ - If a command cannot run because the environment is missing required setup, dependencies, or scripts outside agent control, treat it as a human handoff blocker.
5983
+ - If no verification commands are configured, note that and proceed with normal review.
5984
+
5985
+ ## Changed Files Audit
5986
+
5987
+ - Read the Branch section of ${RUN_CONTEXT_PATH_IN_WORKTREE} and use its Canonical Base Ref for the changed-file inventory.
5988
+ - Before writing \`changedFiles\`, run \`git diff-index --name-only <canonical-base-ref> --\` from the Worktree to enumerate tracked files changed by the Issue.
5989
+ - Also run \`git ls-files --others --exclude-standard\` from the Worktree to enumerate untracked files changed by the Issue.
5990
+ - Treat the union of those command outputs, excluding only \`.pourkit/.tmp/**\`, \`.pourkit/logs/**\`, and \`.pourkit/state.json\`, as the required \`changedFiles\` inventory.
5991
+ - Include every file from that inventory in \`changedFiles\`, including generated files, managed operating assets, docs, prompts, schema files, and large batches.
5992
+ - If any file from the inventory is out-of-scope, mark it \`out-of-scope\` and either self-retouch it away or return \`needs_human_review\`; do not omit it.
5993
+ - Do not return \`pass\` until \`changedFiles\` covers the full inventory.
5994
+
5995
+ ## Required Artifact Validation
5996
+
5997
+ - Before handoff, run exactly: \`${validationCommand}\`
5998
+ - This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
5999
+ - If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
6000
+ - Do not finish until the validation command passes.`);
6001
+ }
6002
+
6003
+ // issues/issue-worktree-resolution.ts
6004
+ init_common();
6005
+ import { join as join14 } from "path";
6006
+ async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
6007
+ const worktreeList = await execCapture(
6008
+ "git",
6009
+ ["worktree", "list", "--porcelain"],
6010
+ {
6011
+ cwd: root,
6012
+ logger,
6013
+ label: "git worktree list"
6014
+ }
6015
+ );
6016
+ const existingWorktreePath = resolveRegisteredIssueWorktreePath(
6017
+ worktreeList.stdout,
6018
+ root,
6019
+ branchName
6020
+ );
6021
+ if (existingWorktreePath) {
6022
+ const baseRef2 = await syncTargetBranch(root, baseBranch, logger);
6023
+ return {
6024
+ mode: "existing-worktree",
6025
+ branchName,
6026
+ baseRef: baseRef2,
6027
+ worktreePath: existingWorktreePath
6028
+ };
6029
+ }
6030
+ let branchExists = false;
6031
+ try {
6032
+ await execCapture(
6033
+ "git",
6034
+ ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
6035
+ {
6036
+ cwd: root,
6037
+ logger,
6038
+ label: "git branch exists"
6039
+ }
6040
+ );
6041
+ branchExists = true;
6042
+ } catch {
6043
+ }
6044
+ if (branchExists) {
6045
+ const worktreePath = issueWorktreePath(root, branchName);
6046
+ await execCapture("git", ["worktree", "add", worktreePath, branchName], {
6047
+ cwd: root,
6048
+ logger,
6049
+ label: "git worktree add"
6050
+ });
6051
+ const baseRef2 = await syncTargetBranch(root, baseBranch, logger);
6052
+ return {
6053
+ mode: "existing-branch",
6054
+ branchName,
6055
+ baseRef: baseRef2,
6056
+ worktreePath
6057
+ };
6058
+ }
6059
+ const baseRef = await syncTargetBranch(root, baseBranch, logger);
6060
+ return { mode: "new", branchName, baseRef };
6061
+ }
6062
+ function issueWorktreePath(root, branchName) {
6063
+ return join14(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
6064
+ }
6065
+ function resolveRegisteredIssueWorktreePath(worktreeListPorcelain, root, branchName) {
6066
+ const branchWorktreePath = parseWorktreeListPorcelain(
6067
+ worktreeListPorcelain,
6068
+ branchName
6069
+ );
6070
+ if (branchWorktreePath) return branchWorktreePath;
6071
+ const expectedWorktreePath = issueWorktreePath(root, branchName);
6072
+ const entries = worktreeListPorcelain.trim().split("\n\n");
6073
+ for (const entry of entries) {
6074
+ if (entry.trim().split("\n").some((line) => line === `worktree ${expectedWorktreePath}`)) {
6075
+ return expectedWorktreePath;
6076
+ }
6077
+ }
6078
+ return null;
6079
+ }
6080
+ async function syncTargetBranch(root, baseBranch, logger) {
6081
+ logger.step("git", `syncing target branch: ${baseBranch}`);
6082
+ await execCapture("git", ["fetch", "origin", baseBranch], {
6083
+ cwd: root,
6084
+ logger,
6085
+ label: "git fetch target"
6086
+ });
6087
+ return `origin/${baseBranch}`;
6088
+ }
6089
+ async function resetIssueWorktree(root, branchName, logger) {
6090
+ const worktreeList = await execCapture(
6091
+ "git",
6092
+ ["worktree", "list", "--porcelain"],
6093
+ { cwd: root, logger, label: "git worktree list" }
6094
+ );
6095
+ const existingWorktreePath = resolveRegisteredIssueWorktreePath(
6096
+ worktreeList.stdout,
6097
+ root,
6098
+ branchName
6099
+ );
6100
+ if (existingWorktreePath) {
6101
+ logger.step("git", `removing existing worktree: ${existingWorktreePath}`);
6102
+ await execCapture(
6103
+ "git",
6104
+ ["worktree", "remove", "--force", existingWorktreePath],
6105
+ {
6106
+ cwd: root,
6107
+ logger,
6108
+ label: "git worktree remove"
6109
+ }
6110
+ );
6111
+ }
6112
+ let branchExists = false;
6113
+ try {
6114
+ await execCapture(
6115
+ "git",
6116
+ ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
6117
+ { cwd: root, logger, label: "git branch exists" }
6118
+ );
6119
+ branchExists = true;
6120
+ } catch {
6121
+ }
6122
+ if (branchExists) {
6123
+ logger.step("git", `deleting local branch: ${branchName}`);
6124
+ await execCapture("git", ["branch", "-D", branchName], {
6125
+ cwd: root,
6126
+ logger,
6127
+ label: "git branch -D"
6128
+ });
5770
6129
  }
5771
- throw new Error(
5772
- `Unknown Issue Final Review verdict: ${JSON.stringify(verdict)}`
5773
- );
5774
- }
5775
- function extractChangedPaths(parsed) {
5776
- return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
5777
- (file) => file && typeof file === "object" ? file.path : void 0
5778
- ).filter((path9) => typeof path9 === "string") : [];
5779
- }
5780
- function loadIssueFinalReviewPrompt(options) {
5781
- const { repoRoot: repoRoot2, promptTemplate, validationCommand } = options;
5782
- const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
5783
- const promptBody = existsSync11(promptPath) ? readFileSync13(promptPath, "utf-8") : promptTemplate;
5784
- return appendProtectedWorkGuidance(`${promptBody}
5785
-
5786
- ## Shared Run Context
5787
-
5788
- Read the selected issue requirements, planning context (including plan and PRD content when present), comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
5789
-
5790
- ## Initial Verification Pass
5791
-
5792
- - First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
5793
- - Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
5794
- - Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
5795
- - If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
5796
- - If a command cannot run because the environment is missing required setup, dependencies, or scripts outside agent control, treat it as a human handoff blocker.
5797
- - If no verification commands are configured, note that and proceed with normal review.
5798
-
5799
- ## Changed Files Audit
5800
-
5801
- - Read the Branch section of ${RUN_CONTEXT_PATH_IN_WORKTREE} and use its Canonical Base Ref for the changed-file inventory.
5802
- - Before writing \`changedFiles\`, run \`git diff-index --name-only <canonical-base-ref> --\` from the Worktree to enumerate tracked files changed by the Issue.
5803
- - Also run \`git ls-files --others --exclude-standard\` from the Worktree to enumerate untracked files changed by the Issue.
5804
- - Treat the union of those command outputs, excluding only \`.pourkit/.tmp/**\`, \`.pourkit/logs/**\`, and \`.pourkit/state.json\`, as the required \`changedFiles\` inventory.
5805
- - Include every file from that inventory in \`changedFiles\`, including generated files, managed operating assets, docs, prompts, schema files, and large batches.
5806
- - If any file from the inventory is out-of-scope, mark it \`out-of-scope\` and either self-retouch it away or return \`needs_human_review\`; do not omit it.
5807
- - Do not return \`pass\` until \`changedFiles\` covers the full inventory.
5808
-
5809
- ## Required Artifact Validation
5810
-
5811
- - Before handoff, run exactly: \`${validationCommand}\`
5812
- - This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
5813
- - If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
5814
- - Do not finish until the validation command passes.`);
5815
6130
  }
5816
6131
 
5817
6132
  // commands/issue-run.ts
@@ -5891,7 +6206,7 @@ async function advanceIssueFinalReview(options) {
5891
6206
  "Issue Final Review state is incomplete: missing artifactPath"
5892
6207
  );
5893
6208
  }
5894
- const artifactPath = isAbsolute4(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join14(worktreePath, ifrFromState.artifactPath);
6209
+ const artifactPath = isAbsolute4(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join15(worktreePath, ifrFromState.artifactPath);
5895
6210
  const validation = validateAgentArtifact({
5896
6211
  kind: "issue-final-review",
5897
6212
  artifactPath,
@@ -6014,7 +6329,7 @@ async function startIssueRun(options) {
6014
6329
  `Cannot reset worktree: open PR #${existingPr.number} exists for branch ${branchName}. Close the PR first or omit --reset-worktree.`
6015
6330
  );
6016
6331
  }
6017
- await resetLocalBranchState(ROOT, branchName, logger);
6332
+ await resetIssueWorktree(ROOT, branchName, logger);
6018
6333
  await syncTargetBranch(ROOT, effectiveBaseBranch, logger);
6019
6334
  }
6020
6335
  const prompt = loadBuilderPrompt(
@@ -6027,98 +6342,32 @@ async function startIssueRun(options) {
6027
6342
  effectiveBaseBranch,
6028
6343
  logger
6029
6344
  );
6030
- const worktreeState = resolution.worktreePath ? readWorktreeRunState(resolution.worktreePath) : null;
6345
+ let worktreeState = resolution.worktreePath ? readWorktreeRunState(resolution.worktreePath) : null;
6031
6346
  if (resolution.mode !== "new") {
6032
- const existingPr = await prProvider.getPr(branchName);
6033
- const exit = await runBaseRefreshAttempt({
6347
+ await runIssueBaseRefresh({
6034
6348
  worktreePath: resolution.worktreePath,
6035
- baseBranch: effectiveBaseBranch,
6036
- localGitBaseRef: resolution.baseRef,
6349
+ branchName,
6350
+ baseRef: resolution.baseRef,
6351
+ effectiveBaseBranch,
6352
+ target: effectiveTarget,
6353
+ config,
6354
+ issueNumber,
6355
+ prProvider,
6356
+ issueProvider,
6357
+ executionProvider,
6358
+ repoRoot: ROOT,
6359
+ worktreeState,
6037
6360
  logger,
6038
- prNumber: existingPr?.number,
6039
- prState: existingPr?.state
6361
+ memory: memoryExecutionContext,
6362
+ transitionIssueToFailureState: (reason) => transitionIssueToFailureState(
6363
+ issueProvider,
6364
+ issueNumber,
6365
+ config,
6366
+ reason,
6367
+ logger
6368
+ )
6040
6369
  });
6041
- if (Exit3.isSuccess(exit)) {
6042
- const refreshResult = exit.value;
6043
- if (refreshResult.status === "refreshed") {
6044
- if (worktreeState?.completedStages.builder) {
6045
- const invalidatedState = invalidateAfterBaseRefresh(worktreeState);
6046
- writeWorktreeRunState(resolution.worktreePath, invalidatedState);
6047
- }
6048
- }
6049
- } else {
6050
- const cause = exit.cause;
6051
- if (cause._tag === "Fail") {
6052
- const failure = cause.error;
6053
- if (failure instanceof RebaseConflict) {
6054
- if (strategy.failureResolution && resolution.worktreePath) {
6055
- await handleRebaseConflict(failure, {
6056
- worktreePath: resolution.worktreePath,
6057
- branchName,
6058
- target,
6059
- config,
6060
- issueNumber,
6061
- issueProvider,
6062
- executionProvider,
6063
- repoRoot: ROOT,
6064
- worktreeState,
6065
- logger,
6066
- memory: memoryExecutionContext
6067
- });
6068
- } else {
6069
- if (resolution.worktreePath) {
6070
- if (worktreeState) {
6071
- updateWorktreeRunState(resolution.worktreePath, {
6072
- lastFailure: {
6073
- stage: "baseRefresh",
6074
- message: `Base refresh conflict detected. Handing off to human: ${failure.message}`
6075
- }
6076
- });
6077
- } else {
6078
- writeWorktreeRunState(resolution.worktreePath, {
6079
- issueNumber,
6080
- targetName: target.name,
6081
- branchName,
6082
- baseBranch: effectiveBaseBranch,
6083
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6084
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6085
- completedStages: {},
6086
- review: { lifetimeIterations: 0 },
6087
- lastFailure: {
6088
- stage: "baseRefresh",
6089
- message: `Base refresh conflict detected. Handing off to human: ${failure.message}`
6090
- }
6091
- });
6092
- }
6093
- }
6094
- await transitionIssueToFailureState(
6095
- issueProvider,
6096
- issueNumber,
6097
- config,
6098
- `Base refresh conflicted: ${failure.message}. Worktree preserved at ${resolution.worktreePath}.`,
6099
- logger
6100
- );
6101
- throw new Error(`Base refresh conflicted: ${failure.message}`);
6102
- }
6103
- } else if (failure instanceof PublishedHistoryRisk) {
6104
- await handlePublishedHistoryRisk(failure, {
6105
- worktreePath: resolution.worktreePath,
6106
- branchName,
6107
- target,
6108
- config,
6109
- issueNumber,
6110
- issueProvider,
6111
- worktreeState,
6112
- logger
6113
- });
6114
- } else {
6115
- throw new Error(`Base refresh failed: ${failure.message}`);
6116
- }
6117
- } else {
6118
- const defectMessage = cause._tag === "Die" ? `Base refresh failed with unexpected error: ${cause.defect}` : `Base refresh failed with unhandled cause: ${cause._tag}`;
6119
- throw new Error(defectMessage);
6120
- }
6121
- }
6370
+ worktreeState = resolution.worktreePath ? readWorktreeRunState(resolution.worktreePath) : null;
6122
6371
  }
6123
6372
  const runContextArtifact = buildRunContextArtifact({
6124
6373
  issue,
@@ -6611,181 +6860,56 @@ function makeIssueTransitions2(provider, config) {
6611
6860
  }
6612
6861
  async function transitionIssueToFailureState(provider, issueNumber, config, errorMessage, logger) {
6613
6862
  const transitions = makeIssueTransitions2(provider, config);
6614
- await transitions.moveToReadyForHuman(issueNumber);
6615
- logger.step(
6616
- "error",
6617
- `Issue ${issueNumber} transitioned to ${config.labels.readyForHuman}: ${errorMessage}`
6618
- );
6619
- }
6620
- async function hasWorktreeChanges(worktreePath, baseRef, logger) {
6621
- const diffResult = await execCapture(
6622
- "git",
6623
- ["diff", "--name-only", baseRef, "--"],
6624
- {
6625
- cwd: worktreePath,
6626
- logger,
6627
- label: "git diff --name-only"
6628
- }
6629
- );
6630
- if (diffResult.stdout.trim().length > 0) {
6631
- return true;
6632
- }
6633
- const statusResult = await execCapture(
6634
- "git",
6635
- ["status", "--porcelain", "--untracked-files=all"],
6636
- {
6637
- cwd: worktreePath,
6638
- logger,
6639
- label: "git status --porcelain"
6640
- }
6641
- );
6642
- return statusResult.stdout.trim().length > 0;
6643
- }
6644
- async function closeNoOpIssue(provider, issueNumber, config, logger) {
6645
- const transitions = makeIssueTransitions2(provider, config);
6646
- let closeSucceeded = false;
6647
- try {
6648
- await transitions.closeCompleted(issueNumber);
6649
- closeSucceeded = true;
6650
- logger.step("info", `Issue #${issueNumber} closed (no changes required)`);
6651
- } catch (error) {
6652
- logger.step(
6653
- "warn",
6654
- `Issue #${issueNumber} could not be closed: ${error instanceof Error ? error.message : String(error)}`
6655
- );
6656
- logger.step(
6657
- "info",
6658
- `Issue #${issueNumber} completed with no changes required`
6659
- );
6660
- }
6661
- if (closeSucceeded) {
6662
- await maybeCloseParentAfterChildCompletion(provider, issueNumber, logger);
6663
- }
6664
- }
6665
- async function resetLocalBranchState(root, branchName, logger) {
6666
- const worktreeList = await execCapture(
6667
- "git",
6668
- ["worktree", "list", "--porcelain"],
6669
- { cwd: root, logger, label: "git worktree list" }
6670
- );
6671
- const existingWorktreePath = resolveRegisteredIssueWorktreePath(
6672
- worktreeList.stdout,
6673
- root,
6674
- branchName
6675
- );
6676
- if (existingWorktreePath) {
6677
- logger.step("git", `removing existing worktree: ${existingWorktreePath}`);
6678
- await execCapture(
6679
- "git",
6680
- ["worktree", "remove", "--force", existingWorktreePath],
6681
- {
6682
- cwd: root,
6683
- logger,
6684
- label: "git worktree remove"
6685
- }
6686
- );
6687
- }
6688
- let branchExists = false;
6689
- try {
6690
- await execCapture(
6691
- "git",
6692
- ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
6693
- { cwd: root, logger, label: "git branch exists" }
6694
- );
6695
- branchExists = true;
6696
- } catch {
6697
- }
6698
- if (branchExists) {
6699
- logger.step("git", `deleting local branch: ${branchName}`);
6700
- await execCapture("git", ["branch", "-D", branchName], {
6701
- cwd: root,
6702
- logger,
6703
- label: "git branch -D"
6704
- });
6705
- }
6706
- }
6707
- async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
6708
- const worktreeList = await execCapture(
6709
- "git",
6710
- ["worktree", "list", "--porcelain"],
6711
- {
6712
- cwd: root,
6713
- logger,
6714
- label: "git worktree list"
6715
- }
6716
- );
6717
- const existingWorktreePath = resolveRegisteredIssueWorktreePath(
6718
- worktreeList.stdout,
6719
- root,
6720
- branchName
6721
- );
6722
- if (existingWorktreePath) {
6723
- const baseRef2 = await syncTargetBranch(root, baseBranch, logger);
6724
- return {
6725
- mode: "existing-worktree",
6726
- branchName,
6727
- baseRef: baseRef2,
6728
- worktreePath: existingWorktreePath
6729
- };
6730
- }
6731
- let branchExists = false;
6732
- try {
6733
- await execCapture(
6734
- "git",
6735
- ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
6736
- {
6737
- cwd: root,
6738
- logger,
6739
- label: "git branch exists"
6740
- }
6741
- );
6742
- branchExists = true;
6743
- } catch {
6744
- }
6745
- if (branchExists) {
6746
- const worktreePath = issueWorktreePath(root, branchName);
6747
- await execCapture("git", ["worktree", "add", worktreePath, branchName], {
6748
- cwd: root,
6749
- logger,
6750
- label: "git worktree add"
6751
- });
6752
- const baseRef2 = await syncTargetBranch(root, baseBranch, logger);
6753
- return {
6754
- mode: "existing-branch",
6755
- branchName,
6756
- baseRef: baseRef2,
6757
- worktreePath
6758
- };
6759
- }
6760
- const baseRef = await syncTargetBranch(root, baseBranch, logger);
6761
- return { mode: "new", branchName, baseRef };
6762
- }
6763
- function issueWorktreePath(root, branchName) {
6764
- return join14(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
6765
- }
6766
- function resolveRegisteredIssueWorktreePath(worktreeListPorcelain, root, branchName) {
6767
- const branchWorktreePath = parseWorktreeListPorcelain(
6768
- worktreeListPorcelain,
6769
- branchName
6863
+ await transitions.moveToReadyForHuman(issueNumber);
6864
+ logger.step(
6865
+ "error",
6866
+ `Issue ${issueNumber} transitioned to ${config.labels.readyForHuman}: ${errorMessage}`
6770
6867
  );
6771
- if (branchWorktreePath) return branchWorktreePath;
6772
- const expectedWorktreePath = issueWorktreePath(root, branchName);
6773
- const entries = worktreeListPorcelain.trim().split("\n\n");
6774
- for (const entry of entries) {
6775
- if (entry.trim().split("\n").some((line) => line === `worktree ${expectedWorktreePath}`)) {
6776
- return expectedWorktreePath;
6868
+ }
6869
+ async function hasWorktreeChanges(worktreePath, baseRef, logger) {
6870
+ const diffResult = await execCapture(
6871
+ "git",
6872
+ ["diff", "--name-only", baseRef, "--"],
6873
+ {
6874
+ cwd: worktreePath,
6875
+ logger,
6876
+ label: "git diff --name-only"
6777
6877
  }
6878
+ );
6879
+ if (diffResult.stdout.trim().length > 0) {
6880
+ return true;
6778
6881
  }
6779
- return null;
6882
+ const statusResult = await execCapture(
6883
+ "git",
6884
+ ["status", "--porcelain", "--untracked-files=all"],
6885
+ {
6886
+ cwd: worktreePath,
6887
+ logger,
6888
+ label: "git status --porcelain"
6889
+ }
6890
+ );
6891
+ return statusResult.stdout.trim().length > 0;
6780
6892
  }
6781
- async function syncTargetBranch(root, baseBranch, logger) {
6782
- logger.step("git", `syncing target branch: ${baseBranch}`);
6783
- await execCapture("git", ["fetch", "origin", baseBranch], {
6784
- cwd: root,
6785
- logger,
6786
- label: "git fetch target"
6787
- });
6788
- return `origin/${baseBranch}`;
6893
+ async function closeNoOpIssue(provider, issueNumber, config, logger) {
6894
+ const transitions = makeIssueTransitions2(provider, config);
6895
+ let closeSucceeded = false;
6896
+ try {
6897
+ await transitions.closeCompleted(issueNumber);
6898
+ closeSucceeded = true;
6899
+ logger.step("info", `Issue #${issueNumber} closed (no changes required)`);
6900
+ } catch (error) {
6901
+ logger.step(
6902
+ "warn",
6903
+ `Issue #${issueNumber} could not be closed: ${error instanceof Error ? error.message : String(error)}`
6904
+ );
6905
+ logger.step(
6906
+ "info",
6907
+ `Issue #${issueNumber} completed with no changes required`
6908
+ );
6909
+ }
6910
+ if (closeSucceeded) {
6911
+ await maybeCloseParentAfterChildCompletion(provider, issueNumber, logger);
6912
+ }
6789
6913
  }
6790
6914
  function loadBuilderPrompt(repoRoot2, promptTemplate) {
6791
6915
  const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
@@ -6796,398 +6920,172 @@ function loadBuilderPrompt(repoRoot2, promptTemplate) {
6796
6920
 
6797
6921
  Read the selected issue requirements, planning context (including plan and PRD content when present), comments, branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}`);
6798
6922
  }
6799
- async function handleRebaseConflict(failure, context) {
6800
- const frConfig = context.target.strategy.failureResolution;
6801
- if (!frConfig) {
6802
- await transitionIssueToFailureState(
6803
- context.issueProvider,
6804
- context.issueNumber,
6805
- context.config,
6806
- "No failureResolution configured",
6807
- context.logger
6808
- );
6809
- throw new Error("Base refresh conflicted: no failureResolution configured");
6810
- }
6811
- const maxAttempts = frConfig.failureLimits?.RebaseConflict ?? frConfig.maxAttemptsPerFailure;
6812
- let attemptNumber = 0;
6813
- let currentFailure = failure;
6814
- while (attemptNumber < maxAttempts) {
6815
- attemptNumber++;
6816
- const packet = constructFailureResolutionPacket(currentFailure, {
6817
- stageName: "baseRefresh",
6818
- attemptNumber,
6819
- worktreePath: context.worktreePath,
6820
- branchName: context.branchName,
6821
- baseBranch: context.target.baseBranch,
6822
- maxAttempts,
6823
- allowedDecisions: ["RETRY_STAGE", "HANDOFF_TO_HUMAN", "FAIL_RUN"],
6824
- artifactTarget: `.pourkit/.tmp/failure-resolution/attempt-${attemptNumber}.md`
6825
- });
6826
- const result = await runFailureResolutionAgent({
6827
- executionProvider: context.executionProvider,
6828
- config: context.config,
6829
- target: context.target,
6830
- failure: currentFailure,
6831
- packet,
6832
- worktreePath: context.worktreePath,
6833
- repoRoot: context.repoRoot,
6834
- logger: context.logger,
6835
- ...context.memory ? { memory: context.memory } : {}
6836
- });
6837
- if (result.status === "handoff") {
6838
- if (context.worktreeState) {
6839
- updateWorktreeRunState(context.worktreePath, {
6840
- lastFailure: { stage: "baseRefresh", message: result.reason }
6841
- });
6842
- } else {
6843
- writeWorktreeRunState(context.worktreePath, {
6844
- issueNumber: context.issueNumber,
6845
- targetName: context.target.name,
6846
- branchName: context.branchName,
6847
- baseBranch: context.target.baseBranch,
6848
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6849
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6850
- completedStages: {},
6851
- review: { lifetimeIterations: 0 },
6852
- lastFailure: { stage: "baseRefresh", message: result.reason }
6853
- });
6854
- }
6855
- await transitionIssueToFailureState(
6856
- context.issueProvider,
6857
- context.issueNumber,
6858
- context.config,
6859
- result.reason,
6860
- context.logger
6861
- );
6862
- throw new Error(result.reason);
6863
- }
6864
- if (result.status === "fail-run") {
6865
- if (context.worktreeState) {
6866
- updateWorktreeRunState(context.worktreePath, {
6867
- lastFailure: { stage: "baseRefresh", message: result.reason }
6868
- });
6869
- } else {
6870
- writeWorktreeRunState(context.worktreePath, {
6871
- issueNumber: context.issueNumber,
6872
- targetName: context.target.name,
6873
- branchName: context.branchName,
6874
- baseBranch: context.target.baseBranch,
6875
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6876
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6877
- completedStages: {},
6878
- review: { lifetimeIterations: 0 },
6879
- lastFailure: { stage: "baseRefresh", message: result.reason }
6880
- });
6881
- }
6882
- throw new Error(result.reason);
6883
- }
6884
- const changedPaths = packet.conflictedPaths ?? currentFailure.conflictedPaths;
6885
- const markersRemain = await hasUnresolvedConflictMarkers(
6886
- context.worktreePath,
6887
- changedPaths
6888
- );
6889
- if (markersRemain) {
6890
- const message = "Conflict resolution agent resolved artifact but conflict markers remain in files";
6891
- if (context.worktreeState) {
6892
- updateWorktreeRunState(context.worktreePath, {
6893
- lastFailure: { stage: "baseRefresh", message }
6894
- });
6895
- } else {
6896
- writeWorktreeRunState(context.worktreePath, {
6897
- issueNumber: context.issueNumber,
6898
- targetName: context.target.name,
6899
- branchName: context.branchName,
6900
- baseBranch: context.target.baseBranch,
6901
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6902
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6903
- completedStages: {},
6904
- review: { lifetimeIterations: 0 },
6905
- lastFailure: { stage: "baseRefresh", message }
6906
- });
6907
- }
6908
- await transitionIssueToFailureState(
6909
- context.issueProvider,
6910
- context.issueNumber,
6911
- context.config,
6912
- message,
6913
- context.logger
6914
- );
6915
- throw new Error(message);
6916
- }
6917
- await execCapture("git", ["add", ...changedPaths], {
6918
- cwd: context.worktreePath,
6919
- logger: context.logger,
6920
- label: "git add conflicted paths"
6921
- });
6922
- try {
6923
- await execCapture("git", ["rebase", "--continue"], {
6924
- cwd: context.worktreePath,
6925
- env: { ...process.env, GIT_EDITOR: "true" },
6926
- logger: context.logger,
6927
- label: "git rebase --continue"
6928
- });
6929
- if (context.worktreeState?.completedStages.builder) {
6930
- const invalidatedState = invalidateAfterBaseRefresh(
6931
- context.worktreeState
6932
- );
6933
- writeWorktreeRunState(context.worktreePath, invalidatedState);
6934
- }
6935
- return;
6936
- } catch {
6937
- const statusResult = await execCapture("git", ["status", "--porcelain"], {
6938
- cwd: context.worktreePath,
6939
- logger: context.logger,
6940
- label: "git status"
6941
- });
6942
- 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);
6943
- if (newConflictedPaths.length === 0) {
6944
- const message = "git rebase --continue failed with no remaining conflicts";
6945
- if (context.worktreeState) {
6946
- updateWorktreeRunState(context.worktreePath, {
6947
- lastFailure: { stage: "baseRefresh", message }
6948
- });
6949
- } else {
6950
- writeWorktreeRunState(context.worktreePath, {
6951
- issueNumber: context.issueNumber,
6952
- targetName: context.target.name,
6953
- branchName: context.branchName,
6954
- baseBranch: context.target.baseBranch,
6955
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6956
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6957
- completedStages: {},
6958
- review: { lifetimeIterations: 0 },
6959
- lastFailure: { stage: "baseRefresh", message }
6960
- });
6961
- }
6962
- await transitionIssueToFailureState(
6963
- context.issueProvider,
6964
- context.issueNumber,
6965
- context.config,
6966
- message,
6967
- context.logger
6968
- );
6969
- throw new Error(message);
6970
- }
6971
- currentFailure = new RebaseConflict({
6972
- conflictedPaths: newConflictedPaths,
6973
- message: `Rebase conflict in: ${newConflictedPaths.join(", ")}`
6974
- });
6975
- }
6976
- }
6977
- if (context.worktreeState) {
6978
- updateWorktreeRunState(context.worktreePath, {
6979
- lastFailure: {
6980
- stage: "baseRefresh",
6981
- message: `Base refresh recovery exhausted after ${maxAttempts} attempts`
6982
- }
6983
- });
6984
- } else {
6985
- writeWorktreeRunState(context.worktreePath, {
6986
- issueNumber: context.issueNumber,
6987
- targetName: context.target.name,
6988
- branchName: context.branchName,
6989
- baseBranch: context.target.baseBranch,
6990
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6991
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6992
- completedStages: {},
6993
- review: { lifetimeIterations: 0 },
6994
- lastFailure: {
6995
- stage: "baseRefresh",
6996
- message: `Base refresh recovery exhausted after ${maxAttempts} attempts`
6997
- }
6998
- });
6999
- }
7000
- await transitionIssueToFailureState(
7001
- context.issueProvider,
7002
- context.issueNumber,
7003
- context.config,
7004
- `Base refresh recovery exhausted after ${maxAttempts} attempts`,
7005
- context.logger
7006
- );
7007
- throw new Error(
7008
- `Base refresh recovery exhausted after ${maxAttempts} attempts`
7009
- );
7010
- }
7011
- async function handlePublishedHistoryRisk(failure, context) {
7012
- const message = `Cannot auto-refresh published history: PR #${failure.prNumber} (${failure.prState}) exists for branch ${context.branchName}`;
7013
- if (context.worktreeState) {
7014
- updateWorktreeRunState(context.worktreePath, {
7015
- lastFailure: { stage: "baseRefresh", message }
7016
- });
7017
- } else {
7018
- writeWorktreeRunState(context.worktreePath, {
7019
- issueNumber: context.issueNumber,
7020
- targetName: context.target.name,
7021
- branchName: context.branchName,
7022
- baseBranch: context.target.baseBranch,
7023
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7024
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7025
- completedStages: {},
7026
- review: { lifetimeIterations: 0 },
7027
- lastFailure: { stage: "baseRefresh", message }
7028
- });
7029
- }
7030
- await transitionIssueToFailureState(
7031
- context.issueProvider,
7032
- context.issueNumber,
7033
- context.config,
7034
- message,
7035
- context.logger
7036
- );
7037
- throw new Error(message);
7038
- }
7039
6923
 
7040
- // commands/issue.ts
7041
- var HumanHandoffStop = class extends Error {
6924
+ // issues/issue-run-lifecycle.ts
6925
+ var IssueRunHumanHandoffStop = class extends Error {
7042
6926
  constructor(message) {
7043
6927
  super(message);
7044
6928
  this.name = "HumanHandoffStop";
7045
6929
  }
7046
6930
  };
7047
- async function runIssueCommand(options) {
7048
- const {
7049
- issueNumber,
7050
- config,
7051
- issueProvider,
7052
- prProvider,
7053
- executionProvider,
7054
- logger
7055
- } = options;
7056
- const ROOT = options.repoRoot;
7057
- let executionSession;
6931
+ async function runIssueLifecycle(options) {
7058
6932
  try {
7059
- executionSession = await executionProvider.createSession?.();
7060
- const runOptions = executionSession ? { ...options, executionProvider: executionSession } : options;
7061
- const startResult = await startIssueRun(runOptions);
7062
- const {
7063
- issue,
7064
- parentPrdIssue,
7065
- target,
7066
- effectiveTarget,
7067
- branchName,
7068
- worktreeState,
7069
- executionResult
7070
- } = startResult;
6933
+ const startResult = await options.deps.startIssueRun(options.runOptions);
6934
+ const reviewAlreadyPassed = startResult.worktreeState?.review.lastVerdict && !startResult.worktreeState.review.exhaustedPreviousRun && (startResult.worktreeState.review.lastVerdict === "PASS" || startResult.worktreeState.review.lastVerdict === "PASS_WITH_NOTES");
7071
6935
  let reviewArtifactPath;
7072
- const reviewAlreadyPassed = worktreeState?.review.lastVerdict && ["PASS", "PASS_WITH_NOTES"].includes(worktreeState.review.lastVerdict) && !worktreeState.review.exhaustedPreviousRun;
7073
6936
  if (reviewAlreadyPassed) {
7074
- reviewArtifactPath = worktreeState.review.lastArtifactPath;
6937
+ reviewArtifactPath = startResult.worktreeState.review.lastArtifactPath;
7075
6938
  } else {
7076
- const lifetimeIterationsFromState = worktreeState?.review.lifetimeIterations ?? 0;
7077
- const humanHandoffResolved = worktreeState?.review.lastVerdict === "NEEDS_HUMAN";
7078
- const reviewResult = await advanceIssueRunReview({
7079
- executionProvider: runOptions.executionProvider,
7080
- config,
7081
- target: effectiveTarget,
7082
- issue,
7083
- parentPrdIssue,
7084
- builderBranch: branchName,
7085
- worktreePath: executionResult.worktreePath,
7086
- repoRoot: ROOT,
7087
- logger,
7088
- startingLifetimeIteration: lifetimeIterationsFromState,
7089
- humanHandoffResolved,
6939
+ const reviewResult = await options.deps.advanceIssueRunReview({
6940
+ executionProvider: options.runOptions.executionProvider,
6941
+ config: options.config,
6942
+ target: startResult.effectiveTarget,
6943
+ issue: startResult.issue,
6944
+ parentPrdIssue: startResult.parentPrdIssue,
6945
+ builderBranch: startResult.branchName,
6946
+ worktreePath: startResult.executionResult.worktreePath,
6947
+ repoRoot: options.repoRoot,
6948
+ logger: options.logger,
6949
+ startingLifetimeIteration: startResult.worktreeState?.review.lifetimeIterations ?? 0,
6950
+ humanHandoffResolved: startResult.worktreeState?.review.lastVerdict === "NEEDS_HUMAN",
7090
6951
  serena: startResult.serena,
7091
6952
  memory: startResult.memory
7092
6953
  });
7093
6954
  if (reviewResult.exhaustedMaxIterations) {
7094
6955
  throw new Error(
7095
- `Max review iterations (${reviewResult.iterations}) exhausted`
6956
+ "Review loop exhausted maximum iterations without passing"
7096
6957
  );
7097
6958
  }
7098
6959
  if (reviewResult.verdict === "FAIL") {
7099
- throw new Error(`Review failed with FAIL verdict`);
6960
+ const summary = reviewResult.output?.trim() || "no summary provided";
6961
+ throw new Error(`Review loop failed: ${summary}`);
7100
6962
  }
7101
6963
  if (reviewResult.verdict === "NEEDS_HUMAN") {
7102
- await transitionIssueToHumanHandoff({
7103
- issueProvider,
7104
- issueNumber,
7105
- config,
7106
- logger,
6964
+ await options.deps.transitionReviewNeedsHumanToHandoff({
6965
+ issueProvider: options.issueProvider,
6966
+ issueNumber: options.issueNumber,
6967
+ config: options.config,
6968
+ logger: options.logger,
7107
6969
  reviewResult
7108
6970
  });
7109
- throw new HumanHandoffStop(
7110
- `Review requires human handoff: NEEDS_HUMAN verdict`
6971
+ throw new IssueRunHumanHandoffStop(
6972
+ "Review requires human handoff: NEEDS_HUMAN verdict"
7111
6973
  );
7112
6974
  }
7113
6975
  reviewArtifactPath = reviewResult.artifactPath;
7114
6976
  }
7115
- const finalReviewResult = await advanceIssueFinalReview({
7116
- executionProvider: runOptions.executionProvider,
7117
- config,
7118
- target: effectiveTarget,
7119
- issue,
7120
- parentPrdIssue,
7121
- builderBranch: branchName,
7122
- worktreePath: executionResult.worktreePath,
7123
- repoRoot: ROOT,
7124
- logger,
6977
+ const finalReviewResult = await options.deps.advanceIssueFinalReview({
6978
+ executionProvider: options.runOptions.executionProvider,
6979
+ config: options.config,
6980
+ target: startResult.effectiveTarget,
6981
+ issue: startResult.issue,
6982
+ parentPrdIssue: startResult.parentPrdIssue,
6983
+ builderBranch: startResult.branchName,
6984
+ worktreePath: startResult.executionResult.worktreePath,
6985
+ repoRoot: options.repoRoot,
6986
+ logger: options.logger,
7125
6987
  reviewArtifactPath,
7126
- worktreeState,
6988
+ worktreeState: startResult.worktreeState,
7127
6989
  memory: startResult.memory
7128
6990
  });
7129
6991
  if (finalReviewResult.verdict === "needs_human_review") {
7130
- const transitions = createIssueTransitions(
7131
- {
7132
- fetchIssue: issueProvider.fetchIssue.bind(issueProvider),
7133
- addLabels: issueProvider.addLabels.bind(issueProvider),
7134
- removeLabel: issueProvider.removeLabel.bind(issueProvider),
7135
- closeIssue: issueProvider.closeIssue.bind(issueProvider)
7136
- },
7137
- {
7138
- blocked: config.labels.blocked,
7139
- readyForAgent: config.labels.readyForAgent,
7140
- needsTriage: config.labels.needsTriage,
7141
- agentInProgress: config.labels.agentInProgress,
7142
- readyForHuman: config.labels.readyForHuman,
7143
- prOpenAwaitingMerge: config.labels.prOpenAwaitingMerge
7144
- }
7145
- );
7146
- await transitions.moveToReadyForHuman(issueNumber);
7147
- const comment = [
7148
- "Pourkit stopped the Issue Final Review because human review is needed.",
7149
- "",
7150
- finalReviewResult.needsHumanReason,
7151
- "",
7152
- "Artifacts:",
7153
- `- Issue Final Review: ${finalReviewResult.artifactPath}`
7154
- ].join("\n");
7155
- await issueProvider.commentIssue(issueNumber, comment);
7156
- logger.step(
7157
- "info",
7158
- `Issue Final Review requires human handoff for issue ${issueNumber}`
7159
- );
7160
- throw new HumanHandoffStop(
6992
+ await options.deps.transitionIssueFinalReviewNeedsHumanToHandoff({
6993
+ issueNumber: options.issueNumber,
6994
+ finalReviewResult
6995
+ });
6996
+ throw new IssueRunHumanHandoffStop(
7161
6997
  `Issue Final Review requires human handoff: ${finalReviewResult.needsHumanReason}`
7162
6998
  );
7163
6999
  }
7164
- return await completeIssueRun({
7165
- ...runOptions,
7000
+ return await options.deps.completeIssueRun({
7001
+ ...options.runOptions,
7166
7002
  startResult,
7167
7003
  reviewArtifactPath
7168
7004
  });
7169
7005
  } catch (error) {
7170
- if (error instanceof HumanHandoffStop) {
7006
+ if (error instanceof IssueRunHumanHandoffStop) {
7171
7007
  throw error;
7172
7008
  }
7173
7009
  if (error instanceof Error && "_tag" in error) {
7174
- await failIssueRun({
7175
- issueProvider,
7176
- issueNumber,
7177
- config,
7178
- logger,
7010
+ await options.deps.failIssueRun({
7011
+ issueProvider: options.issueProvider,
7012
+ issueNumber: options.issueNumber,
7013
+ config: options.config,
7014
+ logger: options.logger,
7179
7015
  error: `[${error._tag}] ${error.message}`
7180
7016
  });
7181
7017
  } else {
7182
- await failIssueRun({
7183
- issueProvider,
7184
- issueNumber,
7185
- config,
7186
- logger,
7018
+ await options.deps.failIssueRun({
7019
+ issueProvider: options.issueProvider,
7020
+ issueNumber: options.issueNumber,
7021
+ config: options.config,
7022
+ logger: options.logger,
7187
7023
  error: error instanceof Error ? error : String(error)
7188
7024
  });
7189
7025
  }
7190
7026
  throw error;
7027
+ }
7028
+ }
7029
+
7030
+ // commands/issue.ts
7031
+ async function runIssueCommand(options) {
7032
+ const { issueNumber, config, issueProvider, executionProvider, logger } = options;
7033
+ const ROOT = options.repoRoot;
7034
+ let executionSession;
7035
+ try {
7036
+ executionSession = await executionProvider.createSession?.();
7037
+ const runOptions = executionSession ? { ...options, executionProvider: executionSession } : options;
7038
+ return await runIssueLifecycle({
7039
+ issueNumber,
7040
+ config,
7041
+ issueProvider,
7042
+ logger,
7043
+ repoRoot: ROOT,
7044
+ runOptions,
7045
+ deps: {
7046
+ startIssueRun,
7047
+ advanceIssueRunReview,
7048
+ advanceIssueFinalReview,
7049
+ completeIssueRun,
7050
+ failIssueRun,
7051
+ transitionReviewNeedsHumanToHandoff: transitionIssueToHumanHandoff,
7052
+ transitionIssueFinalReviewNeedsHumanToHandoff: async ({
7053
+ issueNumber: issueNumber2,
7054
+ finalReviewResult
7055
+ }) => {
7056
+ const transitions = createIssueTransitions(
7057
+ {
7058
+ fetchIssue: issueProvider.fetchIssue.bind(issueProvider),
7059
+ addLabels: issueProvider.addLabels.bind(issueProvider),
7060
+ removeLabel: issueProvider.removeLabel.bind(issueProvider),
7061
+ closeIssue: issueProvider.closeIssue.bind(issueProvider)
7062
+ },
7063
+ {
7064
+ blocked: config.labels.blocked,
7065
+ readyForAgent: config.labels.readyForAgent,
7066
+ needsTriage: config.labels.needsTriage,
7067
+ agentInProgress: config.labels.agentInProgress,
7068
+ readyForHuman: config.labels.readyForHuman,
7069
+ prOpenAwaitingMerge: config.labels.prOpenAwaitingMerge
7070
+ }
7071
+ );
7072
+ await transitions.moveToReadyForHuman(issueNumber2);
7073
+ const comment = [
7074
+ "Pourkit stopped the Issue Final Review because human review is needed.",
7075
+ "",
7076
+ finalReviewResult.needsHumanReason,
7077
+ "",
7078
+ "Artifacts:",
7079
+ `- Issue Final Review: ${finalReviewResult.artifactPath}`
7080
+ ].join("\n");
7081
+ await issueProvider.commentIssue(issueNumber2, comment);
7082
+ logger.step(
7083
+ "info",
7084
+ `Issue Final Review requires human handoff for issue ${issueNumber2}`
7085
+ );
7086
+ }
7087
+ }
7088
+ });
7191
7089
  } finally {
7192
7090
  await executionSession?.close();
7193
7091
  }
@@ -7284,7 +7182,7 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
7284
7182
 
7285
7183
  // commands/prd-run.ts
7286
7184
  import { lstatSync, realpathSync } from "fs";
7287
- import { join as join16 } from "path";
7185
+ import { join as join17 } from "path";
7288
7186
 
7289
7187
  // prd-run/state.ts
7290
7188
  import {
@@ -7294,7 +7192,7 @@ import {
7294
7192
  readdirSync as readdirSync3,
7295
7193
  writeFileSync as writeFileSync4
7296
7194
  } from "fs";
7297
- import { join as join15 } from "path";
7195
+ import { join as join16 } from "path";
7298
7196
  import { z } from "zod";
7299
7197
  var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
7300
7198
  var PrdRunRecordSchema = z.object({
@@ -7409,7 +7307,7 @@ function readPrdRun(repoRoot2, prdRef) {
7409
7307
  }
7410
7308
  }
7411
7309
  function listPrdRuns(repoRoot2) {
7412
- const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
7310
+ const stateDir = join16(repoRoot2, PRD_RUN_STATE_DIR);
7413
7311
  if (!existsSync13(stateDir)) {
7414
7312
  return { records: [], diagnostics: [] };
7415
7313
  }
@@ -7419,7 +7317,7 @@ function listPrdRuns(repoRoot2) {
7419
7317
  (left, right) => left.name.localeCompare(right.name)
7420
7318
  )) {
7421
7319
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
7422
- const recordPath = join15(stateDir, entry.name);
7320
+ const recordPath = join16(stateDir, entry.name);
7423
7321
  try {
7424
7322
  const record = PrdRunRecordSchema.parse(
7425
7323
  JSON.parse(readFileSync15(recordPath, "utf-8"))
@@ -7435,7 +7333,7 @@ function listPrdRuns(repoRoot2) {
7435
7333
  }
7436
7334
  function writePrdRunRecord(repoRoot2, record) {
7437
7335
  const normalized = normalizePrdRunRef(record.prdRef);
7438
- const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
7336
+ const stateDir = join16(repoRoot2, PRD_RUN_STATE_DIR);
7439
7337
  const recordPath = getRecordPath(repoRoot2, normalized);
7440
7338
  mkdirSync7(stateDir, { recursive: true });
7441
7339
  writeFileSync4(
@@ -7445,7 +7343,7 @@ function writePrdRunRecord(repoRoot2, record) {
7445
7343
  );
7446
7344
  }
7447
7345
  function getRecordPath(repoRoot2, prdRef) {
7448
- return join15(
7346
+ return join16(
7449
7347
  repoRoot2,
7450
7348
  PRD_RUN_STATE_DIR,
7451
7349
  `${normalizePrdRunRef(prdRef)}.json`
@@ -12207,10 +12105,10 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
12207
12105
 
12208
12106
  // commands/memory-init.ts
12209
12107
  import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
12210
- import { join as join17 } from "path";
12108
+ import { join as join18 } from "path";
12211
12109
  async function runMemoryInitCommand(options) {
12212
12110
  const cwd = options.cwd ?? process.cwd();
12213
- const configPath = join17(cwd, ".pourkit", "config.json");
12111
+ const configPath = join18(cwd, ".pourkit", "config.json");
12214
12112
  if (!existsSync16(configPath)) {
12215
12113
  return {
12216
12114
  ok: false,
@@ -12242,8 +12140,8 @@ async function runMemoryInitCommand(options) {
12242
12140
  const next = { ...raw, memory };
12243
12141
  writeFileSync5(configPath, JSON.stringify(next, null, 2) + "\n");
12244
12142
  }
12245
- mkdirSync8(join17(cwd, ".pourkit", "icm"), { recursive: true });
12246
- const gitignorePath = join17(cwd, ".gitignore");
12143
+ mkdirSync8(join18(cwd, ".pourkit", "icm"), { recursive: true });
12144
+ const gitignorePath = join18(cwd, ".gitignore");
12247
12145
  updateManagedBlockSync(gitignorePath, generateGitignoreBlock());
12248
12146
  const managed = await generateManagedAgentInstructions({
12249
12147
  sourceRoot: cwd,
@@ -12251,14 +12149,14 @@ async function runMemoryInitCommand(options) {
12251
12149
  });
12252
12150
  let updatedAgentFile = false;
12253
12151
  for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
12254
- const agentPath = join17(cwd, fileName);
12152
+ const agentPath = join18(cwd, fileName);
12255
12153
  if (existsSync16(agentPath)) {
12256
12154
  updateManagedBlockSync(agentPath, managed);
12257
12155
  updatedAgentFile = true;
12258
12156
  }
12259
12157
  }
12260
12158
  if (!updatedAgentFile) {
12261
- updateManagedBlockSync(join17(cwd, "AGENTS.md"), managed);
12159
+ updateManagedBlockSync(join18(cwd, "AGENTS.md"), managed);
12262
12160
  }
12263
12161
  return {
12264
12162
  ok: true,
@@ -14255,7 +14153,7 @@ init_common();
14255
14153
 
14256
14154
  // execution/sandcastle-execution.ts
14257
14155
  import { existsSync as existsSync20, mkdirSync as mkdirSync9, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
14258
- import { join as join20 } from "path";
14156
+ import { join as join21 } from "path";
14259
14157
  import { createWorktree, opencode } from "@ai-hero/sandcastle";
14260
14158
  import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
14261
14159
 
@@ -14264,10 +14162,10 @@ init_common();
14264
14162
  import { mkdtempSync } from "fs";
14265
14163
  import { writeFile as writeFile4 } from "fs/promises";
14266
14164
  import { tmpdir } from "os";
14267
- import { dirname as dirname7, join as join18 } from "path";
14165
+ import { dirname as dirname7, join as join19 } from "path";
14268
14166
  async function writeExecutionArtifacts(worktreePath, artifacts) {
14269
14167
  for (const artifact of artifacts) {
14270
- const filePath = join18(worktreePath, artifact.path);
14168
+ const filePath = join19(worktreePath, artifact.path);
14271
14169
  await ensureDir(dirname7(filePath));
14272
14170
  await writeFile4(filePath, artifact.content, "utf-8");
14273
14171
  }
@@ -14329,7 +14227,7 @@ async function createSandboxFromExistingWorktree(options) {
14329
14227
  }
14330
14228
 
14331
14229
  // execution/sandbox-options.ts
14332
- import { join as join19 } from "path";
14230
+ import { join as join20 } from "path";
14333
14231
  var POURKIT_ICM_CONTAINER_DIR = "/home/agent/.pourkit/icm";
14334
14232
  function buildSandboxOptions(repoRoot2, sandbox, memory) {
14335
14233
  const mounts = [];
@@ -14338,12 +14236,12 @@ function buildSandboxOptions(repoRoot2, sandbox, memory) {
14338
14236
  }
14339
14237
  if (memory?.available) {
14340
14238
  mounts.push({
14341
- hostPath: join19(repoRoot2, ".pourkit", "icm"),
14239
+ hostPath: join20(repoRoot2, ".pourkit", "icm"),
14342
14240
  sandboxPath: POURKIT_ICM_CONTAINER_DIR,
14343
14241
  readonly: false
14344
14242
  });
14345
14243
  mounts.push({
14346
- hostPath: join19(repoRoot2, ".pourkit", "icm", "cache"),
14244
+ hostPath: join20(repoRoot2, ".pourkit", "icm", "cache"),
14347
14245
  sandboxPath: "/home/agent/.cache/icm",
14348
14246
  readonly: false
14349
14247
  });
@@ -14477,13 +14375,13 @@ var SandcastleExecutionSession = class {
14477
14375
  "Memory enabled but memory context is invalid. Run `pourkit memory init` or install ICM for host planning use."
14478
14376
  );
14479
14377
  }
14480
- const hostMemoryDir = join20(repoRoot2, ".pourkit", "icm");
14378
+ const hostMemoryDir = join21(repoRoot2, ".pourkit", "icm");
14481
14379
  if (!existsSync20(hostMemoryDir) || !statSync2(hostMemoryDir).isDirectory()) {
14482
14380
  throw new MissingHostMemoryDirError(
14483
14381
  "Memory enabled but host .pourkit/icm/ directory is missing. Run `pourkit memory init` to create the repo-scoped memory store."
14484
14382
  );
14485
14383
  }
14486
- mkdirSync9(join20(hostMemoryDir, "cache"), { recursive: true });
14384
+ mkdirSync9(join21(hostMemoryDir, "cache"), { recursive: true });
14487
14385
  }
14488
14386
  const sandboxOptions = buildSandboxOptions(
14489
14387
  repoRoot2,
@@ -14642,7 +14540,7 @@ function sanitizeBranch(branchName) {
14642
14540
  }
14643
14541
  function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
14644
14542
  return paths.filter((relativePath) => {
14645
- const source = join20(repoRoot2, relativePath);
14543
+ const source = join21(repoRoot2, relativePath);
14646
14544
  if (!existsSync20(source)) {
14647
14545
  return true;
14648
14546
  }
@@ -14653,7 +14551,7 @@ function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
14653
14551
  } catch {
14654
14552
  return true;
14655
14553
  }
14656
- const destination = join20(worktreePath, relativePath);
14554
+ const destination = join21(worktreePath, relativePath);
14657
14555
  return !existsSync20(destination);
14658
14556
  });
14659
14557
  }
@@ -14718,12 +14616,12 @@ function isPlainObject(value) {
14718
14616
  return typeof value === "object" && value !== null && !Array.isArray(value);
14719
14617
  }
14720
14618
  function savePromptToFile(repoRoot2, stage, iteration, prompt) {
14721
- const promptsDir = join20(repoRoot2, ".pourkit", ".tmp", "prompts");
14619
+ const promptsDir = join21(repoRoot2, ".pourkit", ".tmp", "prompts");
14722
14620
  mkdirSync9(promptsDir, { recursive: true });
14723
14621
  const timestamp2 = Date.now();
14724
14622
  const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
14725
14623
  const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
14726
- const filePath = join20(promptsDir, filename);
14624
+ const filePath = join21(promptsDir, filename);
14727
14625
  writeFileSync6(filePath, prompt, "utf-8");
14728
14626
  }
14729
14627
 
@@ -15444,11 +15342,11 @@ function createCliProgram(version) {
15444
15342
  return program;
15445
15343
  }
15446
15344
  async function resolveCliVersion() {
15447
- if (isPackageVersion("0.0.0-next-20260620201514")) {
15448
- return "0.0.0-next-20260620201514";
15345
+ if (isPackageVersion("0.0.0-next-20260621045826")) {
15346
+ return "0.0.0-next-20260621045826";
15449
15347
  }
15450
- if (isReleaseVersion("0.0.0-next-20260620201514")) {
15451
- return "0.0.0-next-20260620201514";
15348
+ if (isReleaseVersion("0.0.0-next-20260621045826")) {
15349
+ return "0.0.0-next-20260621045826";
15452
15350
  }
15453
15351
  try {
15454
15352
  const root = repoRoot();