@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 +925 -1027
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +1003 -1105
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
3962
|
-
import {
|
|
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
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
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
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
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
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
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 :
|
|
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
|
|
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
|
-
|
|
6345
|
+
let worktreeState = resolution.worktreePath ? readWorktreeRunState(resolution.worktreePath) : null;
|
|
6031
6346
|
if (resolution.mode !== "new") {
|
|
6032
|
-
|
|
6033
|
-
const exit = await runBaseRefreshAttempt({
|
|
6347
|
+
await runIssueBaseRefresh({
|
|
6034
6348
|
worktreePath: resolution.worktreePath,
|
|
6035
|
-
|
|
6036
|
-
|
|
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
|
-
|
|
6039
|
-
|
|
6361
|
+
memory: memoryExecutionContext,
|
|
6362
|
+
transitionIssueToFailureState: (reason) => transitionIssueToFailureState(
|
|
6363
|
+
issueProvider,
|
|
6364
|
+
issueNumber,
|
|
6365
|
+
config,
|
|
6366
|
+
reason,
|
|
6367
|
+
logger
|
|
6368
|
+
)
|
|
6040
6369
|
});
|
|
6041
|
-
|
|
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
|
-
|
|
6772
|
-
|
|
6773
|
-
const
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
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
|
-
|
|
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
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
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
|
-
//
|
|
7041
|
-
var
|
|
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
|
|
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
|
-
|
|
7060
|
-
const
|
|
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
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
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
|
-
|
|
6956
|
+
"Review loop exhausted maximum iterations without passing"
|
|
7096
6957
|
);
|
|
7097
6958
|
}
|
|
7098
6959
|
if (reviewResult.verdict === "FAIL") {
|
|
7099
|
-
|
|
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
|
|
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
|
|
7110
|
-
|
|
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:
|
|
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
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
12108
|
+
import { join as join18 } from "path";
|
|
12211
12109
|
async function runMemoryInitCommand(options) {
|
|
12212
12110
|
const cwd = options.cwd ?? process.cwd();
|
|
12213
|
-
const configPath =
|
|
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(
|
|
12246
|
-
const gitignorePath =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
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:
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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-
|
|
15448
|
-
return "0.0.0-next-
|
|
15345
|
+
if (isPackageVersion("0.0.0-next-20260621045826")) {
|
|
15346
|
+
return "0.0.0-next-20260621045826";
|
|
15449
15347
|
}
|
|
15450
|
-
if (isReleaseVersion("0.0.0-next-
|
|
15451
|
-
return "0.0.0-next-
|
|
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();
|