@pourkit/cli 0.0.0-next-20260622055621 → 0.0.0-next-20260622235247
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 +708 -727
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +649 -668
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3740,10 +3740,6 @@ function shellQuote(value) {
|
|
|
3740
3740
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
3741
3741
|
}
|
|
3742
3742
|
|
|
3743
|
-
// commands/issue-run.ts
|
|
3744
|
-
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
|
|
3745
|
-
import { isAbsolute as isAbsolute4, join as join15 } from "path";
|
|
3746
|
-
|
|
3747
3743
|
// pr/templates.ts
|
|
3748
3744
|
init_common();
|
|
3749
3745
|
function renderBranchName(template, issue) {
|
|
@@ -4960,9 +4956,9 @@ async function assertCanonicalBaseAncestor(options) {
|
|
|
4960
4956
|
}
|
|
4961
4957
|
}
|
|
4962
4958
|
|
|
4963
|
-
//
|
|
4964
|
-
import {
|
|
4965
|
-
import {
|
|
4959
|
+
// issues/pr-description-agent.ts
|
|
4960
|
+
import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
|
|
4961
|
+
import { isAbsolute as isAbsolute4, join as join12 } from "path";
|
|
4966
4962
|
|
|
4967
4963
|
// pr/pr-description-context.ts
|
|
4968
4964
|
init_common();
|
|
@@ -5084,204 +5080,323 @@ Write your finalizer output to: ${artifactPathInWorktree}
|
|
|
5084
5080
|
Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktree}`;
|
|
5085
5081
|
}
|
|
5086
5082
|
|
|
5087
|
-
//
|
|
5088
|
-
import {
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
);
|
|
5110
|
-
const
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5083
|
+
// issues/issue-final-review.ts
|
|
5084
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
5085
|
+
import { isAbsolute as isAbsolute3, join as join11 } from "path";
|
|
5086
|
+
init_common();
|
|
5087
|
+
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join11(
|
|
5088
|
+
".pourkit",
|
|
5089
|
+
".tmp",
|
|
5090
|
+
"issue-final-review",
|
|
5091
|
+
"agent-output.json"
|
|
5092
|
+
);
|
|
5093
|
+
async function runIssueFinalReview(options) {
|
|
5094
|
+
const {
|
|
5095
|
+
executionProvider,
|
|
5096
|
+
target,
|
|
5097
|
+
issue,
|
|
5098
|
+
parentPrdIssue,
|
|
5099
|
+
builderBranch,
|
|
5100
|
+
worktreePath,
|
|
5101
|
+
repoRoot: repoRoot2,
|
|
5102
|
+
logger
|
|
5103
|
+
} = options;
|
|
5104
|
+
const artifactPathInWorktree = ISSUE_FINAL_REVIEW_ARTIFACT_PATH;
|
|
5105
|
+
const artifactPath = join11(worktreePath, artifactPathInWorktree);
|
|
5106
|
+
const ifrFromState = options.worktreeState?.issueFinalReview;
|
|
5107
|
+
if (ifrFromState?.completed && ifrFromState.verdict === "pass") {
|
|
5108
|
+
if (!ifrFromState.artifactPath) {
|
|
5109
|
+
throw new Error(
|
|
5110
|
+
"Issue Final Review state is incomplete: missing artifactPath"
|
|
5111
|
+
);
|
|
5112
|
+
}
|
|
5113
|
+
const cachedArtifactPath = isAbsolute3(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join11(worktreePath, ifrFromState.artifactPath);
|
|
5114
|
+
const validation2 = validateAgentArtifact({
|
|
5115
|
+
kind: "issue-final-review",
|
|
5116
|
+
artifactPath: cachedArtifactPath,
|
|
5117
|
+
issueNumber: issue.number,
|
|
5118
|
+
branchName: builderBranch
|
|
5119
|
+
});
|
|
5120
|
+
if (!validation2.ok) {
|
|
5121
|
+
throw new Error(
|
|
5122
|
+
`Issue Final Review state artifact is invalid: ${validation2.reason}`
|
|
5123
|
+
);
|
|
5124
|
+
}
|
|
5125
|
+
return {
|
|
5126
|
+
status: "skipped",
|
|
5127
|
+
verdict: "pass",
|
|
5128
|
+
artifactPath: cachedArtifactPath,
|
|
5129
|
+
selfRetouched: ifrFromState.selfRetouched ?? false,
|
|
5130
|
+
changedPaths: ifrFromState.changedPaths ?? [],
|
|
5131
|
+
verificationPassed: ifrFromState.verificationPassed ?? false
|
|
5132
|
+
};
|
|
5133
|
+
}
|
|
5134
|
+
const strategy = target.strategy;
|
|
5135
|
+
const issueFinalReview = strategy.issueFinalReview;
|
|
5136
|
+
const agent = issueFinalReview;
|
|
5137
|
+
const prompt = loadIssueFinalReviewPrompt({
|
|
5138
|
+
repoRoot: repoRoot2,
|
|
5139
|
+
promptTemplate: agent.promptTemplate,
|
|
5140
|
+
validationCommand: `pourkit validate-artifact issue-final-review ${artifactPathInWorktree} --issue-number ${issue.number} --branch-name ${builderBranch} --base-ref origin/${target.baseBranch}`,
|
|
5141
|
+
reviewArtifactPath: options.reviewArtifactPath,
|
|
5142
|
+
reviewVerdict: options.reviewVerdict
|
|
5143
|
+
});
|
|
5144
|
+
const entry = await executeWithMissingOrEmptyArtifactRetry({
|
|
5145
|
+
executionProvider,
|
|
5146
|
+
missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(agent),
|
|
5147
|
+
logger,
|
|
5148
|
+
runningMessage: () => "Running Issue Final Review agent",
|
|
5149
|
+
retryMessage: (attempt, total) => `Retrying Issue Final Review after empty output (${attempt}/${total})`,
|
|
5150
|
+
executionOptions: {
|
|
5151
|
+
stage: "issueFinalReview",
|
|
5152
|
+
agent: agent.agent,
|
|
5153
|
+
model: agent.model,
|
|
5154
|
+
variant: agent.variant,
|
|
5155
|
+
env: agent.env,
|
|
5142
5156
|
prompt,
|
|
5143
|
-
|
|
5144
|
-
repoRoot:
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5157
|
+
target,
|
|
5158
|
+
repoRoot: repoRoot2,
|
|
5159
|
+
branchName: builderBranch,
|
|
5160
|
+
sandbox: options.config.sandbox,
|
|
5161
|
+
autoApprove: true,
|
|
5162
|
+
artifactPath: artifactPathInWorktree,
|
|
5163
|
+
worktreePath,
|
|
5148
5164
|
artifacts: [
|
|
5149
5165
|
buildRunContextArtifact({
|
|
5150
|
-
issue
|
|
5151
|
-
|
|
5152
|
-
|
|
5166
|
+
issue,
|
|
5167
|
+
parentPrdIssue,
|
|
5168
|
+
target,
|
|
5169
|
+
branchName: builderBranch,
|
|
5170
|
+
repoRoot: repoRoot2,
|
|
5153
5171
|
reviewerCriteria: strategy.review.reviewer.criteria,
|
|
5154
|
-
sections: STAGE_SECTIONS.
|
|
5172
|
+
sections: STAGE_SECTIONS.issueFinalReview
|
|
5155
5173
|
})
|
|
5156
5174
|
],
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
}
|
|
5160
|
-
const output = readFileSync12(artifactPath, "utf-8");
|
|
5161
|
-
yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
|
|
5162
|
-
return result;
|
|
5175
|
+
...options.memory ? { memory: options.memory } : {},
|
|
5176
|
+
logger
|
|
5177
|
+
}
|
|
5163
5178
|
});
|
|
5164
|
-
const
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
)
|
|
5179
|
+
const executionResult = entry.executionResult;
|
|
5180
|
+
if (!executionResult.success) {
|
|
5181
|
+
throw new Error(
|
|
5182
|
+
`Issue Final Review agent execution failed: ${executionResult.error}`
|
|
5183
|
+
);
|
|
5184
|
+
}
|
|
5185
|
+
let content;
|
|
5186
|
+
if (entry.artifact._tag === "content") {
|
|
5187
|
+
content = entry.artifact.value;
|
|
5188
|
+
} else if (entry.artifact._tag === "empty") {
|
|
5189
|
+
throw new Error(
|
|
5190
|
+
`Issue Final Review agent produced empty output at ${artifactPath}`
|
|
5191
|
+
);
|
|
5192
|
+
} else {
|
|
5193
|
+
throw new Error(
|
|
5194
|
+
`Issue Final Review agent did not produce output at ${artifactPath}`
|
|
5195
|
+
);
|
|
5196
|
+
}
|
|
5197
|
+
const validation = validateAgentArtifact({
|
|
5198
|
+
kind: "issue-final-review",
|
|
5199
|
+
artifactPath,
|
|
5200
|
+
issueNumber: issue.number,
|
|
5201
|
+
branchName: builderBranch
|
|
5202
|
+
});
|
|
5203
|
+
if (!validation.ok) {
|
|
5204
|
+
throw new Error(
|
|
5205
|
+
`Issue Final Review artifact validation failed: ${validation.reason}`
|
|
5206
|
+
);
|
|
5207
|
+
}
|
|
5208
|
+
const parsed = JSON.parse(content);
|
|
5209
|
+
const verdict = parsed.verdict;
|
|
5210
|
+
const baseRef = `origin/${target.baseBranch}`;
|
|
5211
|
+
await assertCanonicalBaseAncestor2({
|
|
5212
|
+
worktreePath,
|
|
5213
|
+
baseRef,
|
|
5214
|
+
stageName: "Issue Final Review",
|
|
5215
|
+
logger
|
|
5216
|
+
});
|
|
5217
|
+
if (verdict === "pass") {
|
|
5218
|
+
const changedPaths = await completeIssueFinalReviewChangedPaths({
|
|
5219
|
+
worktreePath,
|
|
5220
|
+
baseRef,
|
|
5221
|
+
declaredChangedPaths: extractChangedPaths(parsed),
|
|
5222
|
+
logger
|
|
5223
|
+
});
|
|
5224
|
+
updateWorktreeRunState(worktreePath, {
|
|
5225
|
+
issueFinalReview: {
|
|
5226
|
+
completed: true,
|
|
5227
|
+
verdict: "pass",
|
|
5228
|
+
artifactPath,
|
|
5229
|
+
selfRetouched: parsed.selfRetouched === true,
|
|
5230
|
+
changedPaths,
|
|
5231
|
+
verificationPassed: parsed.verification?.passed === true
|
|
5232
|
+
}
|
|
5233
|
+
});
|
|
5234
|
+
return {
|
|
5235
|
+
status: "passed",
|
|
5236
|
+
verdict: "pass",
|
|
5237
|
+
artifactPath,
|
|
5238
|
+
selfRetouched: parsed.selfRetouched === true,
|
|
5239
|
+
changedPaths,
|
|
5240
|
+
verificationPassed: parsed.verification?.passed === true
|
|
5241
|
+
};
|
|
5242
|
+
}
|
|
5243
|
+
if (verdict === "needs_human_review") {
|
|
5244
|
+
return {
|
|
5245
|
+
status: "needs_human_review",
|
|
5246
|
+
verdict: "needs_human_review",
|
|
5247
|
+
artifactPath,
|
|
5248
|
+
needsHumanReason: parsed.needsHumanReason,
|
|
5249
|
+
selfRetouched: parsed.selfRetouched === true,
|
|
5250
|
+
changedPaths: extractChangedPaths(parsed)
|
|
5251
|
+
};
|
|
5252
|
+
}
|
|
5253
|
+
throw new Error(
|
|
5254
|
+
`Unknown Issue Final Review verdict: ${JSON.stringify(verdict)}`
|
|
5169
5255
|
);
|
|
5170
5256
|
}
|
|
5171
|
-
function
|
|
5172
|
-
const
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
prompt: options.prompt,
|
|
5196
|
-
target: options.target,
|
|
5197
|
-
repoRoot: options.repoRoot,
|
|
5198
|
-
branchName: options.branchName,
|
|
5199
|
-
sandbox: options.config.sandbox,
|
|
5200
|
-
autoApprove: true,
|
|
5201
|
-
artifactPath: options.artifactPathInWorktree,
|
|
5202
|
-
worktreePath: options.worktreePath,
|
|
5203
|
-
artifacts: options.artifacts,
|
|
5204
|
-
baseRef: options.baseRef,
|
|
5205
|
-
checkoutBase: options.checkoutBase,
|
|
5206
|
-
reviewBase: options.reviewBase,
|
|
5207
|
-
...options.memory ? { memory: options.memory } : {},
|
|
5208
|
-
logger: options.logger
|
|
5209
|
-
}
|
|
5210
|
-
}),
|
|
5211
|
-
catch: (error) => new FinalizerFailure({
|
|
5212
|
-
message: error instanceof Error ? error.message : String(error)
|
|
5213
|
-
})
|
|
5214
|
-
});
|
|
5215
|
-
const executionResult = retryResult.executionResult;
|
|
5216
|
-
if (!executionResult.success) {
|
|
5217
|
-
return yield* Effect7.fail(
|
|
5218
|
-
new FinalizerFailure({
|
|
5219
|
-
message: `Finalizer agent execution failed: ${executionResult.error}`
|
|
5220
|
-
})
|
|
5221
|
-
);
|
|
5222
|
-
}
|
|
5223
|
-
if (retryResult.artifact._tag === "content") {
|
|
5224
|
-
const validation = validateAgentArtifact({
|
|
5225
|
-
kind: "finalizer",
|
|
5226
|
-
artifactPath
|
|
5227
|
-
});
|
|
5228
|
-
if (validation.ok) {
|
|
5229
|
-
try {
|
|
5230
|
-
parsed = parsePrDescription(retryResult.artifact.value);
|
|
5231
|
-
lastValidationError = void 0;
|
|
5232
|
-
break;
|
|
5233
|
-
} catch (error) {
|
|
5234
|
-
lastValidationError = error instanceof PrDescriptionProtocolError ? new Error(`Finalizer protocol error: ${error.message}`) : error instanceof Error ? error : new Error(String(error));
|
|
5235
|
-
}
|
|
5236
|
-
} else {
|
|
5237
|
-
lastValidationError = new Error(
|
|
5238
|
-
`Finalizer protocol error: ${validation.reason}`
|
|
5239
|
-
);
|
|
5240
|
-
if (protocolAttempt === options.maxAttempts) break;
|
|
5241
|
-
}
|
|
5242
|
-
} else if (retryResult.artifact._tag === "empty") {
|
|
5243
|
-
lastValidationError = new Error(
|
|
5244
|
-
`Finalizer agent produced empty output at ${artifactPath}`
|
|
5245
|
-
);
|
|
5246
|
-
break;
|
|
5247
|
-
} else {
|
|
5248
|
-
lastValidationError = new Error(
|
|
5249
|
-
`Finalizer agent did not produce output at ${artifactPath}`
|
|
5250
|
-
);
|
|
5251
|
-
break;
|
|
5252
|
-
}
|
|
5253
|
-
}
|
|
5254
|
-
if (!parsed) {
|
|
5255
|
-
return yield* Effect7.fail(
|
|
5256
|
-
new FinalizerFailure({
|
|
5257
|
-
message: lastValidationError?.message ?? "Finalizer validation failed"
|
|
5258
|
-
})
|
|
5259
|
-
);
|
|
5260
|
-
}
|
|
5261
|
-
options.logger.step("info", "Finalizer output generated successfully");
|
|
5262
|
-
return {
|
|
5263
|
-
title: ensureConventionalPrTitle(parsed.title, options.commitSummaries),
|
|
5264
|
-
body: parsed.body,
|
|
5265
|
-
artifactPath
|
|
5266
|
-
};
|
|
5257
|
+
async function assertCanonicalBaseAncestor2(options) {
|
|
5258
|
+
const { worktreePath, baseRef, stageName, logger } = options;
|
|
5259
|
+
await syncRemoteBaseRef2(worktreePath, baseRef, logger);
|
|
5260
|
+
try {
|
|
5261
|
+
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
5262
|
+
cwd: worktreePath,
|
|
5263
|
+
logger,
|
|
5264
|
+
label: "git merge-base --is-ancestor"
|
|
5265
|
+
});
|
|
5266
|
+
} catch {
|
|
5267
|
+
throw new Error(
|
|
5268
|
+
`Cannot continue after ${stageName}: ${baseRef} is not an ancestor of HEAD. An agent may have moved the issue branch behind the canonical base; refresh the branch onto the latest target base before continuing.`
|
|
5269
|
+
);
|
|
5270
|
+
}
|
|
5271
|
+
}
|
|
5272
|
+
async function syncRemoteBaseRef2(worktreePath, baseRef, logger) {
|
|
5273
|
+
const remoteBase = parseRemoteBaseRef2(baseRef);
|
|
5274
|
+
if (!remoteBase) {
|
|
5275
|
+
return;
|
|
5276
|
+
}
|
|
5277
|
+
await execCapture("git", ["fetch", remoteBase.remote, remoteBase.branch], {
|
|
5278
|
+
cwd: worktreePath,
|
|
5279
|
+
logger,
|
|
5280
|
+
label: "git fetch target"
|
|
5267
5281
|
});
|
|
5268
5282
|
}
|
|
5269
|
-
function
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5283
|
+
function parseRemoteBaseRef2(baseRef) {
|
|
5284
|
+
const [remote, ...branchParts] = baseRef.split("/");
|
|
5285
|
+
const branch = branchParts.join("/");
|
|
5286
|
+
if (!remote || !branch) {
|
|
5287
|
+
return null;
|
|
5288
|
+
}
|
|
5289
|
+
return { remote, branch };
|
|
5290
|
+
}
|
|
5291
|
+
async function completeIssueFinalReviewChangedPaths(options) {
|
|
5292
|
+
const actualChangedPaths = await listIssueFinalReviewChangedPaths2(options);
|
|
5293
|
+
const declared = new Set(options.declaredChangedPaths.map(normalizeGitPath2));
|
|
5294
|
+
const missing = actualChangedPaths.filter((path9) => !declared.has(path9));
|
|
5295
|
+
if (missing.length === 0) {
|
|
5296
|
+
return actualChangedPaths;
|
|
5297
|
+
}
|
|
5298
|
+
options.logger.step(
|
|
5299
|
+
"warn",
|
|
5300
|
+
`Issue Final Review pass omitted ${missing.length} changed file(s) from changedFiles; using git inventory for finalization state.`
|
|
5301
|
+
);
|
|
5302
|
+
options.logger.kv("MISSING_CHANGED_FILES", missing.join(", "));
|
|
5303
|
+
return actualChangedPaths;
|
|
5304
|
+
}
|
|
5305
|
+
async function listIssueFinalReviewChangedPaths2(options) {
|
|
5306
|
+
const trackedDiff = await execCapture(
|
|
5307
|
+
"git",
|
|
5308
|
+
["diff-index", "--name-only", options.baseRef, "--"],
|
|
5309
|
+
{
|
|
5310
|
+
cwd: options.worktreePath,
|
|
5311
|
+
logger: options.logger,
|
|
5312
|
+
label: "git diff-index Issue Final Review paths"
|
|
5275
5313
|
}
|
|
5276
|
-
|
|
5277
|
-
|
|
5314
|
+
);
|
|
5315
|
+
const untrackedFiles = await execCapture(
|
|
5316
|
+
"git",
|
|
5317
|
+
["ls-files", "--others", "--exclude-standard"],
|
|
5318
|
+
{
|
|
5319
|
+
cwd: options.worktreePath,
|
|
5320
|
+
logger: options.logger,
|
|
5321
|
+
label: "git ls-files Issue Final Review paths"
|
|
5322
|
+
}
|
|
5323
|
+
);
|
|
5324
|
+
return Array.from(
|
|
5325
|
+
/* @__PURE__ */ new Set([
|
|
5326
|
+
...parseGitPathList2(trackedDiff.stdout),
|
|
5327
|
+
...parseGitPathList2(untrackedFiles.stdout)
|
|
5328
|
+
])
|
|
5329
|
+
).filter(isIssueFinalReviewAuditedPath2);
|
|
5278
5330
|
}
|
|
5279
|
-
function
|
|
5280
|
-
return
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5331
|
+
function parseGitPathList2(output) {
|
|
5332
|
+
return output.split(/\r?\n/).map(normalizeGitPath2).filter((path9) => path9.length > 0);
|
|
5333
|
+
}
|
|
5334
|
+
function normalizeGitPath2(path9) {
|
|
5335
|
+
return path9.replace(/^"|"$/g, "").replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
5336
|
+
}
|
|
5337
|
+
function isIssueFinalReviewAuditedPath2(path9) {
|
|
5338
|
+
return path9 !== "" && path9 !== WORKTREE_RUN_STATE_PATH && !path9.startsWith(".pourkit/.tmp/") && !path9.startsWith(".pourkit/logs/");
|
|
5339
|
+
}
|
|
5340
|
+
function extractChangedPaths(parsed) {
|
|
5341
|
+
return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
|
|
5342
|
+
(file) => file && typeof file === "object" ? file.path : void 0
|
|
5343
|
+
).filter((path9) => typeof path9 === "string") : [];
|
|
5344
|
+
}
|
|
5345
|
+
function loadIssueFinalReviewPrompt(options) {
|
|
5346
|
+
const {
|
|
5347
|
+
repoRoot: repoRoot2,
|
|
5348
|
+
promptTemplate,
|
|
5349
|
+
validationCommand,
|
|
5350
|
+
reviewArtifactPath,
|
|
5351
|
+
reviewVerdict
|
|
5352
|
+
} = options;
|
|
5353
|
+
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
5354
|
+
const promptBody = existsSync11(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
|
|
5355
|
+
let prompt = appendProtectedWorkGuidance(`${promptBody}
|
|
5356
|
+
|
|
5357
|
+
## Shared Run Context
|
|
5358
|
+
|
|
5359
|
+
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}
|
|
5360
|
+
|
|
5361
|
+
## Initial Verification Pass
|
|
5362
|
+
|
|
5363
|
+
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
5364
|
+
- Before reviewing code, diffs, artifacts, or prior findings, run the verification wrapper from the Worktree.
|
|
5365
|
+
- Do not substitute direct underlying commands unless no wrapper path can run; if you must use direct commands, explain why in \`verification.summary\`.
|
|
5366
|
+
- If a configured verification command fails because of fixable Issue work, self-retouch the Worktree, then rerun all configured verification commands.
|
|
5367
|
+
- Do not return \`pass\` while any configured verification command is failing.
|
|
5368
|
+
- 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.
|
|
5369
|
+
- If no verification commands are configured, note that and proceed with normal review.
|
|
5370
|
+
|
|
5371
|
+
## Changed Files Audit
|
|
5372
|
+
|
|
5373
|
+
- Read the Branch section of ${RUN_CONTEXT_PATH_IN_WORKTREE} and use its Canonical Base Ref for the changed-file inventory.
|
|
5374
|
+
- Before writing \`changedFiles\`, run \`git diff-index --name-only <canonical-base-ref> --\` from the Worktree to enumerate tracked files changed by the Issue.
|
|
5375
|
+
- Also run \`git ls-files --others --exclude-standard\` from the Worktree to enumerate untracked files changed by the Issue.
|
|
5376
|
+
- Treat the union of those command outputs, excluding only \`.pourkit/.tmp/**\`, \`.pourkit/logs/**\`, and \`.pourkit/state.json\`, as the required \`changedFiles\` inventory.
|
|
5377
|
+
- Include every file from that inventory in \`changedFiles\`, including generated files, managed operating assets, docs, prompts, schema files, and large batches.
|
|
5378
|
+
- 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.
|
|
5379
|
+
- Do not return \`pass\` until \`changedFiles\` covers the full inventory.
|
|
5380
|
+
|
|
5381
|
+
## Required Artifact Validation
|
|
5382
|
+
|
|
5383
|
+
- Before handoff, run exactly: \`${validationCommand}\`
|
|
5384
|
+
- This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
|
|
5385
|
+
- If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
|
|
5386
|
+
- Do not finish until the validation command passes.`);
|
|
5387
|
+
if (reviewVerdict) {
|
|
5388
|
+
prompt += `
|
|
5389
|
+
|
|
5390
|
+
## Prior Review Context
|
|
5391
|
+
|
|
5392
|
+
- Reviewer Artifact Path: ${reviewArtifactPath}
|
|
5393
|
+
- Prior Review Verdict: ${reviewVerdict}`;
|
|
5394
|
+
if (reviewVerdict === "PASS_WITH_NOTES") {
|
|
5395
|
+
prompt += `
|
|
5396
|
+
- This Issue passed the prior Reviewer with notes. Read the Reviewer Artifact and inspect its notes before making your final pass decision.`;
|
|
5397
|
+
}
|
|
5398
|
+
}
|
|
5399
|
+
return prompt;
|
|
5285
5400
|
}
|
|
5286
5401
|
|
|
5287
5402
|
// pr/pr-body.ts
|
|
@@ -5352,10 +5467,281 @@ function extractClosingRefs(body) {
|
|
|
5352
5467
|
return refs;
|
|
5353
5468
|
}
|
|
5354
5469
|
|
|
5470
|
+
// issues/pr-description-agent.ts
|
|
5471
|
+
import { Effect as Effect7, Layer as Layer3 } from "effect";
|
|
5472
|
+
function bridgeExecutionProvider2(ep) {
|
|
5473
|
+
return Layer3.succeed(
|
|
5474
|
+
ExecutionProvider,
|
|
5475
|
+
ExecutionProvider.of({
|
|
5476
|
+
execute: (opts) => Effect7.tryPromise({
|
|
5477
|
+
try: () => ep.execute(opts),
|
|
5478
|
+
catch: (e) => new Error(
|
|
5479
|
+
e instanceof Error ? e.message : `Execution failed: ${String(e)}`
|
|
5480
|
+
)
|
|
5481
|
+
})
|
|
5482
|
+
})
|
|
5483
|
+
);
|
|
5484
|
+
}
|
|
5485
|
+
function loadFinalizerPromptEffect(repoRoot2, promptTemplate, fs) {
|
|
5486
|
+
return Effect7.gen(function* () {
|
|
5487
|
+
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
5488
|
+
const exists = yield* fs.exists(promptPath).pipe(Effect7.orDie);
|
|
5489
|
+
if (exists) {
|
|
5490
|
+
return yield* fs.readFile(promptPath).pipe(Effect7.orDie);
|
|
5491
|
+
}
|
|
5492
|
+
return promptTemplate;
|
|
5493
|
+
});
|
|
5494
|
+
}
|
|
5495
|
+
function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
5496
|
+
return Effect7.gen(function* () {
|
|
5497
|
+
const dir = join12(worktreePath, ".pourkit", ".tmp", "finalizer");
|
|
5498
|
+
yield* fs.mkdir(dir).pipe(Effect7.catchAll(() => Effect7.void));
|
|
5499
|
+
yield* fs.writeFile(join12(dir, "generated.md"), output).pipe(Effect7.catchAll(() => Effect7.void));
|
|
5500
|
+
});
|
|
5501
|
+
}
|
|
5502
|
+
function runPrDescriptionAgent(options) {
|
|
5503
|
+
const { executionProvider } = options;
|
|
5504
|
+
const artifactPathInWorktree = join12(
|
|
5505
|
+
".pourkit",
|
|
5506
|
+
".tmp",
|
|
5507
|
+
"finalizer",
|
|
5508
|
+
"agent-output.md"
|
|
5509
|
+
);
|
|
5510
|
+
const artifactPath = join12(options.worktreePath, artifactPathInWorktree);
|
|
5511
|
+
const finalizerFromState = options.worktreeState?.finalizer?.completed ? options.worktreeState.finalizer : null;
|
|
5512
|
+
if (finalizerFromState) {
|
|
5513
|
+
return Effect7.gen(function* () {
|
|
5514
|
+
if (!finalizerFromState.artifactPath) {
|
|
5515
|
+
return yield* Effect7.fail(
|
|
5516
|
+
new FinalizerFailure({
|
|
5517
|
+
message: "Finalizer artifact missing: artifactPath not set in completed finalizer state"
|
|
5518
|
+
})
|
|
5519
|
+
);
|
|
5520
|
+
}
|
|
5521
|
+
const resolvedArtifactPath = isAbsolute4(finalizerFromState.artifactPath) ? finalizerFromState.artifactPath : join12(options.worktreePath, finalizerFromState.artifactPath);
|
|
5522
|
+
if (!existsSync12(resolvedArtifactPath)) {
|
|
5523
|
+
return yield* Effect7.fail(
|
|
5524
|
+
new FinalizerFailure({
|
|
5525
|
+
message: `Finalizer artifact missing: the artifact does not exist at \`${resolvedArtifactPath}\``
|
|
5526
|
+
})
|
|
5527
|
+
);
|
|
5528
|
+
}
|
|
5529
|
+
const validation = validateAgentArtifact({
|
|
5530
|
+
kind: "finalizer",
|
|
5531
|
+
artifactPath: resolvedArtifactPath
|
|
5532
|
+
});
|
|
5533
|
+
if (!validation.ok) {
|
|
5534
|
+
return yield* Effect7.fail(
|
|
5535
|
+
new FinalizerFailure({
|
|
5536
|
+
message: `Finalizer state artifact is invalid: ${validation.reason}`
|
|
5537
|
+
})
|
|
5538
|
+
);
|
|
5539
|
+
}
|
|
5540
|
+
if (!finalizerFromState.title || !finalizerFromState.body) {
|
|
5541
|
+
return yield* Effect7.fail(
|
|
5542
|
+
new FinalizerFailure({
|
|
5543
|
+
message: "Finalizer state is incomplete: missing stored title or body"
|
|
5544
|
+
})
|
|
5545
|
+
);
|
|
5546
|
+
}
|
|
5547
|
+
return {
|
|
5548
|
+
status: "skipped",
|
|
5549
|
+
title: finalizerFromState.title,
|
|
5550
|
+
body: finalizerFromState.body,
|
|
5551
|
+
artifactPath: resolvedArtifactPath
|
|
5552
|
+
};
|
|
5553
|
+
});
|
|
5554
|
+
}
|
|
5555
|
+
const program = Effect7.gen(function* () {
|
|
5556
|
+
const fs = yield* FileSystem;
|
|
5557
|
+
const context = yield* collectFinalizerContextEffect({
|
|
5558
|
+
targetBase: options.targetBaseBranch,
|
|
5559
|
+
branchName: options.builderBranch,
|
|
5560
|
+
worktreePath: options.worktreePath,
|
|
5561
|
+
reviewArtifactPath: options.reviewArtifactPath,
|
|
5562
|
+
logger: options.logger
|
|
5563
|
+
}).pipe(
|
|
5564
|
+
Effect7.catchAll(
|
|
5565
|
+
(error) => Effect7.fail(
|
|
5566
|
+
new FinalizerFailure({
|
|
5567
|
+
message: `Failed to collect finalizer context: ${error.message}`
|
|
5568
|
+
})
|
|
5569
|
+
)
|
|
5570
|
+
)
|
|
5571
|
+
);
|
|
5572
|
+
const strategy = options.target.strategy;
|
|
5573
|
+
const finalizer = strategy.finalize.prDescriptionAgent;
|
|
5574
|
+
const resolvedPrompt = yield* loadFinalizerPromptEffect(
|
|
5575
|
+
options.repoRoot,
|
|
5576
|
+
finalizer.promptTemplate,
|
|
5577
|
+
fs
|
|
5578
|
+
);
|
|
5579
|
+
const prompt = buildFinalizerPrompt(context, resolvedPrompt);
|
|
5580
|
+
const result = yield* runPrDescriptionFinalizerCore({
|
|
5581
|
+
executionProvider: options.executionProvider,
|
|
5582
|
+
config: options.config,
|
|
5583
|
+
target: options.target,
|
|
5584
|
+
finalizer,
|
|
5585
|
+
maxAttempts: strategy.finalize.maxAttempts,
|
|
5586
|
+
prompt,
|
|
5587
|
+
branchName: options.builderBranch,
|
|
5588
|
+
repoRoot: options.repoRoot,
|
|
5589
|
+
worktreePath: options.worktreePath,
|
|
5590
|
+
artifactPathInWorktree,
|
|
5591
|
+
commitSummaries: context.commits,
|
|
5592
|
+
artifacts: [
|
|
5593
|
+
buildRunContextArtifact({
|
|
5594
|
+
issue: options.issue,
|
|
5595
|
+
target: options.target,
|
|
5596
|
+
branchName: options.builderBranch,
|
|
5597
|
+
reviewerCriteria: strategy.review.reviewer.criteria,
|
|
5598
|
+
sections: STAGE_SECTIONS.finalizer
|
|
5599
|
+
})
|
|
5600
|
+
],
|
|
5601
|
+
logger: options.logger,
|
|
5602
|
+
...options.memory ? { memory: options.memory } : {}
|
|
5603
|
+
});
|
|
5604
|
+
const output = readFileSync13(artifactPath, "utf-8");
|
|
5605
|
+
yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
|
|
5606
|
+
const body = ensureClosingRefs(result.body, options.issue.number);
|
|
5607
|
+
yield* Effect7.tryPromise({
|
|
5608
|
+
try: () => assertCanonicalBaseAncestor2({
|
|
5609
|
+
worktreePath: options.worktreePath,
|
|
5610
|
+
baseRef: `origin/${options.targetBaseBranch}`,
|
|
5611
|
+
stageName: "Finalizer",
|
|
5612
|
+
logger: options.logger
|
|
5613
|
+
}),
|
|
5614
|
+
catch: (error) => new FinalizerFailure({
|
|
5615
|
+
message: error instanceof Error ? error.message : String(error)
|
|
5616
|
+
})
|
|
5617
|
+
});
|
|
5618
|
+
yield* Effect7.sync(() => {
|
|
5619
|
+
updateWorktreeRunState(options.worktreePath, {
|
|
5620
|
+
finalizer: {
|
|
5621
|
+
completed: true,
|
|
5622
|
+
artifactPath: result.artifactPath,
|
|
5623
|
+
title: result.title,
|
|
5624
|
+
body
|
|
5625
|
+
}
|
|
5626
|
+
});
|
|
5627
|
+
});
|
|
5628
|
+
return {
|
|
5629
|
+
status: "completed",
|
|
5630
|
+
title: result.title,
|
|
5631
|
+
body,
|
|
5632
|
+
artifactPath: result.artifactPath
|
|
5633
|
+
};
|
|
5634
|
+
});
|
|
5635
|
+
const executionLayer = bridgeExecutionProvider2(executionProvider);
|
|
5636
|
+
return program.pipe(
|
|
5637
|
+
Effect7.provide(
|
|
5638
|
+
Layer3.mergeAll(executionLayer, FileSystemDefault, GitOperationsDefault)
|
|
5639
|
+
)
|
|
5640
|
+
);
|
|
5641
|
+
}
|
|
5642
|
+
function runPrDescriptionFinalizerCore(options) {
|
|
5643
|
+
const artifactPath = join12(
|
|
5644
|
+
options.worktreePath,
|
|
5645
|
+
options.artifactPathInWorktree
|
|
5646
|
+
);
|
|
5647
|
+
return Effect7.gen(function* () {
|
|
5648
|
+
let parsed;
|
|
5649
|
+
let lastValidationError;
|
|
5650
|
+
for (let protocolAttempt = 1; protocolAttempt <= options.maxAttempts; protocolAttempt++) {
|
|
5651
|
+
const retryResult = yield* Effect7.tryPromise({
|
|
5652
|
+
try: () => executeWithMissingOrEmptyArtifactRetry({
|
|
5653
|
+
executionProvider: options.executionProvider,
|
|
5654
|
+
missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(
|
|
5655
|
+
options.finalizer
|
|
5656
|
+
),
|
|
5657
|
+
logger: options.logger,
|
|
5658
|
+
runningMessage: () => `Running finalizer agent (${protocolAttempt}/${options.maxAttempts})`,
|
|
5659
|
+
retryMessage: (attempt, total) => `Retrying finalizer after empty output (${attempt}/${total})`,
|
|
5660
|
+
executionOptions: {
|
|
5661
|
+
stage: "finalizer",
|
|
5662
|
+
agent: options.finalizer.agent,
|
|
5663
|
+
model: options.finalizer.model,
|
|
5664
|
+
variant: options.finalizer.variant,
|
|
5665
|
+
env: options.finalizer.env,
|
|
5666
|
+
prompt: options.prompt,
|
|
5667
|
+
target: options.target,
|
|
5668
|
+
repoRoot: options.repoRoot,
|
|
5669
|
+
branchName: options.branchName,
|
|
5670
|
+
sandbox: options.config.sandbox,
|
|
5671
|
+
autoApprove: true,
|
|
5672
|
+
artifactPath: options.artifactPathInWorktree,
|
|
5673
|
+
worktreePath: options.worktreePath,
|
|
5674
|
+
artifacts: options.artifacts,
|
|
5675
|
+
baseRef: options.baseRef,
|
|
5676
|
+
checkoutBase: options.checkoutBase,
|
|
5677
|
+
reviewBase: options.reviewBase,
|
|
5678
|
+
...options.memory ? { memory: options.memory } : {},
|
|
5679
|
+
logger: options.logger
|
|
5680
|
+
}
|
|
5681
|
+
}),
|
|
5682
|
+
catch: (error) => new FinalizerFailure({
|
|
5683
|
+
message: error instanceof Error ? error.message : String(error)
|
|
5684
|
+
})
|
|
5685
|
+
});
|
|
5686
|
+
const executionResult = retryResult.executionResult;
|
|
5687
|
+
if (!executionResult.success) {
|
|
5688
|
+
return yield* Effect7.fail(
|
|
5689
|
+
new FinalizerFailure({
|
|
5690
|
+
message: `Finalizer agent execution failed: ${executionResult.error}`
|
|
5691
|
+
})
|
|
5692
|
+
);
|
|
5693
|
+
}
|
|
5694
|
+
if (retryResult.artifact._tag === "content") {
|
|
5695
|
+
const validation = validateAgentArtifact({
|
|
5696
|
+
kind: "finalizer",
|
|
5697
|
+
artifactPath
|
|
5698
|
+
});
|
|
5699
|
+
if (validation.ok) {
|
|
5700
|
+
try {
|
|
5701
|
+
parsed = parsePrDescription(retryResult.artifact.value);
|
|
5702
|
+
lastValidationError = void 0;
|
|
5703
|
+
break;
|
|
5704
|
+
} catch (error) {
|
|
5705
|
+
lastValidationError = error instanceof PrDescriptionProtocolError ? new Error(`Finalizer protocol error: ${error.message}`) : error instanceof Error ? error : new Error(String(error));
|
|
5706
|
+
}
|
|
5707
|
+
} else {
|
|
5708
|
+
lastValidationError = new Error(
|
|
5709
|
+
`Finalizer protocol error: ${validation.reason}`
|
|
5710
|
+
);
|
|
5711
|
+
if (protocolAttempt === options.maxAttempts) break;
|
|
5712
|
+
}
|
|
5713
|
+
} else if (retryResult.artifact._tag === "empty") {
|
|
5714
|
+
lastValidationError = new Error(
|
|
5715
|
+
`Finalizer agent produced empty output at ${artifactPath}`
|
|
5716
|
+
);
|
|
5717
|
+
break;
|
|
5718
|
+
} else {
|
|
5719
|
+
lastValidationError = new Error(
|
|
5720
|
+
`Finalizer agent did not produce output at ${artifactPath}`
|
|
5721
|
+
);
|
|
5722
|
+
break;
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
if (!parsed) {
|
|
5726
|
+
return yield* Effect7.fail(
|
|
5727
|
+
new FinalizerFailure({
|
|
5728
|
+
message: lastValidationError?.message ?? "Finalizer validation failed"
|
|
5729
|
+
})
|
|
5730
|
+
);
|
|
5731
|
+
}
|
|
5732
|
+
options.logger.step("info", "Finalizer output generated successfully");
|
|
5733
|
+
return {
|
|
5734
|
+
title: ensureConventionalPrTitle(parsed.title, options.commitSummaries),
|
|
5735
|
+
body: parsed.body,
|
|
5736
|
+
artifactPath
|
|
5737
|
+
};
|
|
5738
|
+
});
|
|
5739
|
+
}
|
|
5740
|
+
|
|
5355
5741
|
// issues/github-issue-publication.ts
|
|
5356
5742
|
init_common();
|
|
5357
|
-
import { existsSync as
|
|
5358
|
-
import { isAbsolute as
|
|
5743
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
5744
|
+
import { isAbsolute as isAbsolute5, join as join13 } from "path";
|
|
5359
5745
|
|
|
5360
5746
|
// issues/issue-transitions.ts
|
|
5361
5747
|
function createIssueTransitions(deps, labels) {
|
|
@@ -5830,11 +6216,11 @@ function readIssueFinalReviewArtifact(worktreeState, worktreePath) {
|
|
|
5830
6216
|
if (!configuredPath) {
|
|
5831
6217
|
return null;
|
|
5832
6218
|
}
|
|
5833
|
-
const artifactPath =
|
|
5834
|
-
if (!
|
|
6219
|
+
const artifactPath = isAbsolute5(configuredPath) ? configuredPath : join13(worktreePath, configuredPath);
|
|
6220
|
+
if (!existsSync13(artifactPath)) return null;
|
|
5835
6221
|
let parsed;
|
|
5836
6222
|
try {
|
|
5837
|
-
parsed = JSON.parse(
|
|
6223
|
+
parsed = JSON.parse(readFileSync14(artifactPath, "utf-8"));
|
|
5838
6224
|
} catch {
|
|
5839
6225
|
return null;
|
|
5840
6226
|
}
|
|
@@ -6034,243 +6420,61 @@ function assertNoSecretLikeContent(inputs) {
|
|
|
6034
6420
|
}
|
|
6035
6421
|
const summary = findings.slice(0, 5).map((finding) => `${finding.source}: ${finding.kind}`).join("; ");
|
|
6036
6422
|
throw new Error(
|
|
6037
|
-
`Secret-like content detected before finalization (${summary}). Redact it before committing, pushing, or creating a PR.`
|
|
6038
|
-
);
|
|
6039
|
-
}
|
|
6040
|
-
function findSecretLikeContent(source, content) {
|
|
6041
|
-
const findings = [];
|
|
6042
|
-
const patterns = [
|
|
6043
|
-
{
|
|
6044
|
-
kind: "private key block",
|
|
6045
|
-
regex: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/gi
|
|
6046
|
-
},
|
|
6047
|
-
{
|
|
6048
|
-
kind: "GitHub token",
|
|
6049
|
-
regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g
|
|
6050
|
-
},
|
|
6051
|
-
{
|
|
6052
|
-
kind: "GitHub fine-grained token",
|
|
6053
|
-
regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g
|
|
6054
|
-
},
|
|
6055
|
-
{ kind: "npm token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g },
|
|
6056
|
-
{ kind: "OpenAI key", regex: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g },
|
|
6057
|
-
{ kind: "AWS access key", regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g },
|
|
6058
|
-
{
|
|
6059
|
-
kind: "authorization bearer token",
|
|
6060
|
-
regex: /\bAuthorization\s*:\s*Bearer\s+[^\s`'\"]{12,}/gi
|
|
6061
|
-
},
|
|
6062
|
-
{
|
|
6063
|
-
kind: "secret assignment",
|
|
6064
|
-
regex: /\b[A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CLIENT[_-]?SECRET|DATABASE[_-]?URL)[A-Z0-9_]*\s*[:=]\s*["']?[^\s"'`]{8,}/gi
|
|
6065
|
-
}
|
|
6066
|
-
];
|
|
6067
|
-
for (const { kind, regex } of patterns) {
|
|
6068
|
-
for (const match of content.matchAll(regex)) {
|
|
6069
|
-
if (isAllowedSecretPlaceholder(match[0]) || isAllowedDocumentationTokenReference(match[0])) {
|
|
6070
|
-
continue;
|
|
6071
|
-
}
|
|
6072
|
-
findings.push({ source, kind });
|
|
6073
|
-
break;
|
|
6074
|
-
}
|
|
6075
|
-
}
|
|
6076
|
-
return findings;
|
|
6077
|
-
}
|
|
6078
|
-
function isAllowedSecretPlaceholder(value) {
|
|
6079
|
-
const normalized = value.toLowerCase();
|
|
6080
|
-
return [
|
|
6081
|
-
"<redacted>",
|
|
6082
|
-
"<example",
|
|
6083
|
-
"example-",
|
|
6084
|
-
"dummy-",
|
|
6085
|
-
"test-",
|
|
6086
|
-
"placeholder",
|
|
6087
|
-
"xxxxx"
|
|
6088
|
-
].some((allowed) => normalized.includes(allowed));
|
|
6089
|
-
}
|
|
6090
|
-
function isAllowedDocumentationTokenReference(value) {
|
|
6091
|
-
return /^token\s*:\s*["']?<verdict>/i.test(value);
|
|
6092
|
-
}
|
|
6093
|
-
|
|
6094
|
-
// commands/issue-final-review-agent.ts
|
|
6095
|
-
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
6096
|
-
import { join as join13 } from "path";
|
|
6097
|
-
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join13(
|
|
6098
|
-
".pourkit",
|
|
6099
|
-
".tmp",
|
|
6100
|
-
"issue-final-review",
|
|
6101
|
-
"agent-output.json"
|
|
6102
|
-
);
|
|
6103
|
-
async function runIssueFinalReviewAgent(options) {
|
|
6104
|
-
const {
|
|
6105
|
-
executionProvider,
|
|
6106
|
-
target,
|
|
6107
|
-
issue,
|
|
6108
|
-
parentPrdIssue,
|
|
6109
|
-
builderBranch,
|
|
6110
|
-
worktreePath,
|
|
6111
|
-
repoRoot: repoRoot2,
|
|
6112
|
-
logger
|
|
6113
|
-
} = options;
|
|
6114
|
-
const artifactPathInWorktree = ISSUE_FINAL_REVIEW_ARTIFACT_PATH;
|
|
6115
|
-
const artifactPath = join13(worktreePath, artifactPathInWorktree);
|
|
6116
|
-
const strategy = target.strategy;
|
|
6117
|
-
const issueFinalReview = strategy.issueFinalReview;
|
|
6118
|
-
const agent = issueFinalReview;
|
|
6119
|
-
const prompt = loadIssueFinalReviewPrompt({
|
|
6120
|
-
repoRoot: repoRoot2,
|
|
6121
|
-
promptTemplate: agent.promptTemplate,
|
|
6122
|
-
validationCommand: `pourkit validate-artifact issue-final-review ${artifactPathInWorktree} --issue-number ${issue.number} --branch-name ${builderBranch} --base-ref origin/${target.baseBranch}`,
|
|
6123
|
-
reviewArtifactPath: options.reviewArtifactPath,
|
|
6124
|
-
reviewVerdict: options.reviewVerdict
|
|
6125
|
-
});
|
|
6126
|
-
const entry = await executeWithMissingOrEmptyArtifactRetry({
|
|
6127
|
-
executionProvider,
|
|
6128
|
-
missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(agent),
|
|
6129
|
-
logger,
|
|
6130
|
-
runningMessage: () => "Running Issue Final Review agent",
|
|
6131
|
-
retryMessage: (attempt, total) => `Retrying Issue Final Review after empty output (${attempt}/${total})`,
|
|
6132
|
-
executionOptions: {
|
|
6133
|
-
stage: "issueFinalReview",
|
|
6134
|
-
agent: agent.agent,
|
|
6135
|
-
model: agent.model,
|
|
6136
|
-
variant: agent.variant,
|
|
6137
|
-
env: agent.env,
|
|
6138
|
-
prompt,
|
|
6139
|
-
target,
|
|
6140
|
-
repoRoot: repoRoot2,
|
|
6141
|
-
branchName: builderBranch,
|
|
6142
|
-
sandbox: options.config.sandbox,
|
|
6143
|
-
autoApprove: true,
|
|
6144
|
-
artifactPath: artifactPathInWorktree,
|
|
6145
|
-
worktreePath,
|
|
6146
|
-
artifacts: [
|
|
6147
|
-
buildRunContextArtifact({
|
|
6148
|
-
issue,
|
|
6149
|
-
parentPrdIssue,
|
|
6150
|
-
target,
|
|
6151
|
-
branchName: builderBranch,
|
|
6152
|
-
repoRoot: repoRoot2,
|
|
6153
|
-
reviewerCriteria: strategy.review.reviewer.criteria,
|
|
6154
|
-
sections: STAGE_SECTIONS.issueFinalReview
|
|
6155
|
-
})
|
|
6156
|
-
],
|
|
6157
|
-
...options.memory ? { memory: options.memory } : {},
|
|
6158
|
-
logger
|
|
6159
|
-
}
|
|
6160
|
-
});
|
|
6161
|
-
const executionResult = entry.executionResult;
|
|
6162
|
-
if (!executionResult.success) {
|
|
6163
|
-
throw new Error(
|
|
6164
|
-
`Issue Final Review agent execution failed: ${executionResult.error}`
|
|
6165
|
-
);
|
|
6166
|
-
}
|
|
6167
|
-
let content;
|
|
6168
|
-
if (entry.artifact._tag === "content") {
|
|
6169
|
-
content = entry.artifact.value;
|
|
6170
|
-
} else if (entry.artifact._tag === "empty") {
|
|
6171
|
-
throw new Error(
|
|
6172
|
-
`Issue Final Review agent produced empty output at ${artifactPath}`
|
|
6173
|
-
);
|
|
6174
|
-
} else {
|
|
6175
|
-
throw new Error(
|
|
6176
|
-
`Issue Final Review agent did not produce output at ${artifactPath}`
|
|
6177
|
-
);
|
|
6178
|
-
}
|
|
6179
|
-
const validation = validateAgentArtifact({
|
|
6180
|
-
kind: "issue-final-review",
|
|
6181
|
-
artifactPath,
|
|
6182
|
-
issueNumber: issue.number,
|
|
6183
|
-
branchName: builderBranch
|
|
6184
|
-
});
|
|
6185
|
-
if (!validation.ok) {
|
|
6186
|
-
throw new Error(
|
|
6187
|
-
`Issue Final Review artifact validation failed: ${validation.reason}`
|
|
6188
|
-
);
|
|
6189
|
-
}
|
|
6190
|
-
const parsed = JSON.parse(content);
|
|
6191
|
-
const verdict = parsed.verdict;
|
|
6192
|
-
if (verdict === "pass") {
|
|
6193
|
-
return {
|
|
6194
|
-
verdict: "pass",
|
|
6195
|
-
artifactPath,
|
|
6196
|
-
selfRetouched: parsed.selfRetouched === true,
|
|
6197
|
-
changedPaths: extractChangedPaths(parsed),
|
|
6198
|
-
verificationPassed: parsed.verification?.passed === true
|
|
6199
|
-
};
|
|
6200
|
-
}
|
|
6201
|
-
if (verdict === "needs_human_review") {
|
|
6202
|
-
return {
|
|
6203
|
-
verdict: "needs_human_review",
|
|
6204
|
-
artifactPath,
|
|
6205
|
-
needsHumanReason: parsed.needsHumanReason,
|
|
6206
|
-
selfRetouched: parsed.selfRetouched === true,
|
|
6207
|
-
changedPaths: extractChangedPaths(parsed)
|
|
6208
|
-
};
|
|
6209
|
-
}
|
|
6210
|
-
throw new Error(
|
|
6211
|
-
`Unknown Issue Final Review verdict: ${JSON.stringify(verdict)}`
|
|
6212
|
-
);
|
|
6213
|
-
}
|
|
6214
|
-
function extractChangedPaths(parsed) {
|
|
6215
|
-
return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
|
|
6216
|
-
(file) => file && typeof file === "object" ? file.path : void 0
|
|
6217
|
-
).filter((path9) => typeof path9 === "string") : [];
|
|
6218
|
-
}
|
|
6219
|
-
function loadIssueFinalReviewPrompt(options) {
|
|
6220
|
-
const {
|
|
6221
|
-
repoRoot: repoRoot2,
|
|
6222
|
-
promptTemplate,
|
|
6223
|
-
validationCommand,
|
|
6224
|
-
reviewArtifactPath,
|
|
6225
|
-
reviewVerdict
|
|
6226
|
-
} = options;
|
|
6227
|
-
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
6228
|
-
const promptBody = existsSync12(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
|
|
6229
|
-
let prompt = appendProtectedWorkGuidance(`${promptBody}
|
|
6230
|
-
|
|
6231
|
-
## Shared Run Context
|
|
6232
|
-
|
|
6233
|
-
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}
|
|
6234
|
-
|
|
6235
|
-
## Initial Verification Pass
|
|
6236
|
-
|
|
6237
|
-
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
6238
|
-
- Before reviewing code, diffs, artifacts, or prior findings, run the verification wrapper from the Worktree.
|
|
6239
|
-
- Do not substitute direct underlying commands unless no wrapper path can run; if you must use direct commands, explain why in \`verification.summary\`.
|
|
6240
|
-
- If a configured verification command fails because of fixable Issue work, self-retouch the Worktree, then rerun all configured verification commands.
|
|
6241
|
-
- Do not return \`pass\` while any configured verification command is failing.
|
|
6242
|
-
- 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.
|
|
6243
|
-
- If no verification commands are configured, note that and proceed with normal review.
|
|
6244
|
-
|
|
6245
|
-
## Changed Files Audit
|
|
6246
|
-
|
|
6247
|
-
- Read the Branch section of ${RUN_CONTEXT_PATH_IN_WORKTREE} and use its Canonical Base Ref for the changed-file inventory.
|
|
6248
|
-
- Before writing \`changedFiles\`, run \`git diff-index --name-only <canonical-base-ref> --\` from the Worktree to enumerate tracked files changed by the Issue.
|
|
6249
|
-
- Also run \`git ls-files --others --exclude-standard\` from the Worktree to enumerate untracked files changed by the Issue.
|
|
6250
|
-
- Treat the union of those command outputs, excluding only \`.pourkit/.tmp/**\`, \`.pourkit/logs/**\`, and \`.pourkit/state.json\`, as the required \`changedFiles\` inventory.
|
|
6251
|
-
- Include every file from that inventory in \`changedFiles\`, including generated files, managed operating assets, docs, prompts, schema files, and large batches.
|
|
6252
|
-
- 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.
|
|
6253
|
-
- Do not return \`pass\` until \`changedFiles\` covers the full inventory.
|
|
6254
|
-
|
|
6255
|
-
## Required Artifact Validation
|
|
6256
|
-
|
|
6257
|
-
- Before handoff, run exactly: \`${validationCommand}\`
|
|
6258
|
-
- This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
|
|
6259
|
-
- If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
|
|
6260
|
-
- Do not finish until the validation command passes.`);
|
|
6261
|
-
if (reviewVerdict) {
|
|
6262
|
-
prompt += `
|
|
6263
|
-
|
|
6264
|
-
## Prior Review Context
|
|
6265
|
-
|
|
6266
|
-
- Reviewer Artifact Path: ${reviewArtifactPath}
|
|
6267
|
-
- Prior Review Verdict: ${reviewVerdict}`;
|
|
6268
|
-
if (reviewVerdict === "PASS_WITH_NOTES") {
|
|
6269
|
-
prompt += `
|
|
6270
|
-
- This Issue passed the prior Reviewer with notes. Read the Reviewer Artifact and inspect its notes before making your final pass decision.`;
|
|
6423
|
+
`Secret-like content detected before finalization (${summary}). Redact it before committing, pushing, or creating a PR.`
|
|
6424
|
+
);
|
|
6425
|
+
}
|
|
6426
|
+
function findSecretLikeContent(source, content) {
|
|
6427
|
+
const findings = [];
|
|
6428
|
+
const patterns = [
|
|
6429
|
+
{
|
|
6430
|
+
kind: "private key block",
|
|
6431
|
+
regex: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/gi
|
|
6432
|
+
},
|
|
6433
|
+
{
|
|
6434
|
+
kind: "GitHub token",
|
|
6435
|
+
regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g
|
|
6436
|
+
},
|
|
6437
|
+
{
|
|
6438
|
+
kind: "GitHub fine-grained token",
|
|
6439
|
+
regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g
|
|
6440
|
+
},
|
|
6441
|
+
{ kind: "npm token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g },
|
|
6442
|
+
{ kind: "OpenAI key", regex: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g },
|
|
6443
|
+
{ kind: "AWS access key", regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g },
|
|
6444
|
+
{
|
|
6445
|
+
kind: "authorization bearer token",
|
|
6446
|
+
regex: /\bAuthorization\s*:\s*Bearer\s+[^\s`'\"]{12,}/gi
|
|
6447
|
+
},
|
|
6448
|
+
{
|
|
6449
|
+
kind: "secret assignment",
|
|
6450
|
+
regex: /\b[A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CLIENT[_-]?SECRET|DATABASE[_-]?URL)[A-Z0-9_]*\s*[:=]\s*["']?[^\s"'`]{8,}/gi
|
|
6451
|
+
}
|
|
6452
|
+
];
|
|
6453
|
+
for (const { kind, regex } of patterns) {
|
|
6454
|
+
for (const match of content.matchAll(regex)) {
|
|
6455
|
+
if (isAllowedSecretPlaceholder(match[0]) || isAllowedDocumentationTokenReference(match[0])) {
|
|
6456
|
+
continue;
|
|
6457
|
+
}
|
|
6458
|
+
findings.push({ source, kind });
|
|
6459
|
+
break;
|
|
6271
6460
|
}
|
|
6272
6461
|
}
|
|
6273
|
-
return
|
|
6462
|
+
return findings;
|
|
6463
|
+
}
|
|
6464
|
+
function isAllowedSecretPlaceholder(value) {
|
|
6465
|
+
const normalized = value.toLowerCase();
|
|
6466
|
+
return [
|
|
6467
|
+
"<redacted>",
|
|
6468
|
+
"<example",
|
|
6469
|
+
"example-",
|
|
6470
|
+
"dummy-",
|
|
6471
|
+
"test-",
|
|
6472
|
+
"placeholder",
|
|
6473
|
+
"xxxxx"
|
|
6474
|
+
].some((allowed) => normalized.includes(allowed));
|
|
6475
|
+
}
|
|
6476
|
+
function isAllowedDocumentationTokenReference(value) {
|
|
6477
|
+
return /^token\s*:\s*["']?<verdict>/i.test(value);
|
|
6274
6478
|
}
|
|
6275
6479
|
|
|
6276
6480
|
// issues/issue-worktree-resolution.ts
|
|
@@ -6449,90 +6653,7 @@ function assertIssueFinalReviewPassed(worktreeState) {
|
|
|
6449
6653
|
}
|
|
6450
6654
|
}
|
|
6451
6655
|
async function advanceIssueFinalReview(options) {
|
|
6452
|
-
|
|
6453
|
-
executionProvider,
|
|
6454
|
-
config,
|
|
6455
|
-
target,
|
|
6456
|
-
issue,
|
|
6457
|
-
parentPrdIssue,
|
|
6458
|
-
builderBranch,
|
|
6459
|
-
worktreePath,
|
|
6460
|
-
repoRoot: repoRoot2,
|
|
6461
|
-
logger,
|
|
6462
|
-
reviewArtifactPath,
|
|
6463
|
-
reviewVerdict,
|
|
6464
|
-
worktreeState
|
|
6465
|
-
} = options;
|
|
6466
|
-
const ifrFromState = worktreeState?.issueFinalReview;
|
|
6467
|
-
if (ifrFromState?.completed && ifrFromState.verdict === "pass") {
|
|
6468
|
-
if (!ifrFromState.artifactPath) {
|
|
6469
|
-
throw new Error(
|
|
6470
|
-
"Issue Final Review state is incomplete: missing artifactPath"
|
|
6471
|
-
);
|
|
6472
|
-
}
|
|
6473
|
-
const artifactPath = isAbsolute4(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join15(worktreePath, ifrFromState.artifactPath);
|
|
6474
|
-
const validation = validateAgentArtifact({
|
|
6475
|
-
kind: "issue-final-review",
|
|
6476
|
-
artifactPath,
|
|
6477
|
-
issueNumber: issue.number,
|
|
6478
|
-
branchName: builderBranch
|
|
6479
|
-
});
|
|
6480
|
-
if (!validation.ok) {
|
|
6481
|
-
throw new Error(
|
|
6482
|
-
`Issue Final Review state artifact is invalid: ${validation.reason}`
|
|
6483
|
-
);
|
|
6484
|
-
}
|
|
6485
|
-
return {
|
|
6486
|
-
verdict: "pass",
|
|
6487
|
-
artifactPath,
|
|
6488
|
-
selfRetouched: ifrFromState.selfRetouched ?? false,
|
|
6489
|
-
changedPaths: ifrFromState.changedPaths ?? [],
|
|
6490
|
-
verificationPassed: ifrFromState.verificationPassed ?? false
|
|
6491
|
-
};
|
|
6492
|
-
}
|
|
6493
|
-
const result = await runIssueFinalReviewAgent({
|
|
6494
|
-
executionProvider,
|
|
6495
|
-
config,
|
|
6496
|
-
target,
|
|
6497
|
-
issue,
|
|
6498
|
-
parentPrdIssue,
|
|
6499
|
-
builderBranch,
|
|
6500
|
-
worktreePath,
|
|
6501
|
-
repoRoot: repoRoot2,
|
|
6502
|
-
logger,
|
|
6503
|
-
reviewArtifactPath,
|
|
6504
|
-
reviewVerdict,
|
|
6505
|
-
...options.memory ? { memory: options.memory } : {}
|
|
6506
|
-
});
|
|
6507
|
-
await assertCanonicalBaseAncestor2({
|
|
6508
|
-
worktreePath,
|
|
6509
|
-
baseRef: `origin/${target.baseBranch}`,
|
|
6510
|
-
stageName: "Issue Final Review",
|
|
6511
|
-
logger
|
|
6512
|
-
});
|
|
6513
|
-
if (result.verdict === "pass") {
|
|
6514
|
-
const changedPaths = await completeIssueFinalReviewChangedPaths({
|
|
6515
|
-
worktreePath,
|
|
6516
|
-
baseRef: `origin/${target.baseBranch}`,
|
|
6517
|
-
declaredChangedPaths: result.changedPaths,
|
|
6518
|
-
logger
|
|
6519
|
-
});
|
|
6520
|
-
updateWorktreeRunState(worktreePath, {
|
|
6521
|
-
issueFinalReview: {
|
|
6522
|
-
completed: true,
|
|
6523
|
-
verdict: "pass",
|
|
6524
|
-
artifactPath: result.artifactPath,
|
|
6525
|
-
selfRetouched: result.selfRetouched,
|
|
6526
|
-
changedPaths,
|
|
6527
|
-
verificationPassed: result.verificationPassed
|
|
6528
|
-
}
|
|
6529
|
-
});
|
|
6530
|
-
return {
|
|
6531
|
-
...result,
|
|
6532
|
-
changedPaths
|
|
6533
|
-
};
|
|
6534
|
-
}
|
|
6535
|
-
return result;
|
|
6656
|
+
return runIssueFinalReview(options);
|
|
6536
6657
|
}
|
|
6537
6658
|
async function startIssueRun(options) {
|
|
6538
6659
|
const {
|
|
@@ -6734,9 +6855,11 @@ async function completeIssueRun(options) {
|
|
|
6734
6855
|
checksCompletionTimeoutMs: config.checks.checksCompletionTimeoutSeconds * 1e3,
|
|
6735
6856
|
pollIntervalMs: config.checks.pollIntervalSeconds * 1e3
|
|
6736
6857
|
};
|
|
6737
|
-
const
|
|
6858
|
+
const readCurrentWorktreeState = () => executionResult.worktreePath ? readWorktreeRunState(executionResult.worktreePath) ?? worktreeState : worktreeState;
|
|
6859
|
+
const ifrState = readCurrentWorktreeState();
|
|
6738
6860
|
assertIssueFinalReviewPassed(ifrState);
|
|
6739
|
-
|
|
6861
|
+
const stateBeforeCompletion = readCurrentWorktreeState();
|
|
6862
|
+
if (executionResult.worktreePath && !stateBeforeCompletion?.finalCommit?.completed && !stateBeforeCompletion?.pr?.created && !await hasWorktreeChanges(
|
|
6740
6863
|
executionResult.worktreePath,
|
|
6741
6864
|
`origin/${effectiveBaseBranch}`,
|
|
6742
6865
|
logger
|
|
@@ -6753,92 +6876,33 @@ async function completeIssueRun(options) {
|
|
|
6753
6876
|
noOp: true
|
|
6754
6877
|
};
|
|
6755
6878
|
}
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
finalizerFromState.artifactPath,
|
|
6773
|
-
"utf-8"
|
|
6774
|
-
);
|
|
6775
|
-
const parsed = parsePrDescription(artifactContent);
|
|
6776
|
-
prTitle = parsed.title;
|
|
6777
|
-
prBody = parsed.body;
|
|
6778
|
-
} else {
|
|
6779
|
-
throw new FinalizerFailure({
|
|
6780
|
-
message: "Finalizer state is incomplete: missing title, body, and artifactPath"
|
|
6781
|
-
});
|
|
6782
|
-
}
|
|
6783
|
-
} catch (error) {
|
|
6784
|
-
throw error;
|
|
6785
|
-
}
|
|
6786
|
-
} else {
|
|
6787
|
-
try {
|
|
6788
|
-
finalizerResult = await runEffectAndMapExit(
|
|
6789
|
-
runFinalizerAgent({
|
|
6790
|
-
executionProvider,
|
|
6791
|
-
config,
|
|
6792
|
-
target,
|
|
6793
|
-
issue,
|
|
6794
|
-
builderBranch: branchName,
|
|
6795
|
-
targetBaseBranch: effectiveBaseBranch,
|
|
6796
|
-
worktreePath: executionResult.worktreePath,
|
|
6797
|
-
reviewArtifactPath,
|
|
6798
|
-
repoRoot: ROOT,
|
|
6799
|
-
logger,
|
|
6800
|
-
...startResult.memory ? { memory: startResult.memory } : {}
|
|
6801
|
-
})
|
|
6802
|
-
);
|
|
6803
|
-
} catch (error) {
|
|
6804
|
-
throw error;
|
|
6805
|
-
}
|
|
6806
|
-
prTitle = finalizerResult.title;
|
|
6807
|
-
prBody = finalizerResult.body;
|
|
6808
|
-
if (executionResult.worktreePath) {
|
|
6809
|
-
await assertCanonicalBaseAncestor2({
|
|
6810
|
-
worktreePath: executionResult.worktreePath,
|
|
6811
|
-
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6812
|
-
stageName: "Finalizer",
|
|
6813
|
-
logger
|
|
6814
|
-
});
|
|
6815
|
-
}
|
|
6816
|
-
}
|
|
6817
|
-
prTitle = ensureConventionalPrTitle(
|
|
6818
|
-
prTitle,
|
|
6819
|
-
executionResult.commits.join("\n")
|
|
6820
|
-
);
|
|
6821
|
-
const finalBody = ensureClosingRefs(
|
|
6822
|
-
prBody ?? `Closes #${issue.number}`,
|
|
6823
|
-
issue.number
|
|
6879
|
+
const currentWorktreeState = readCurrentWorktreeState();
|
|
6880
|
+
const prDescriptionAgentResult = await runEffectAndMapExit(
|
|
6881
|
+
runPrDescriptionAgent({
|
|
6882
|
+
executionProvider,
|
|
6883
|
+
config,
|
|
6884
|
+
target,
|
|
6885
|
+
issue,
|
|
6886
|
+
builderBranch: branchName,
|
|
6887
|
+
targetBaseBranch: effectiveBaseBranch,
|
|
6888
|
+
worktreePath: executionResult.worktreePath,
|
|
6889
|
+
reviewArtifactPath,
|
|
6890
|
+
repoRoot: ROOT,
|
|
6891
|
+
logger,
|
|
6892
|
+
worktreeState: currentWorktreeState,
|
|
6893
|
+
...startResult.memory ? { memory: startResult.memory } : {}
|
|
6894
|
+
})
|
|
6824
6895
|
);
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
artifactPath: finalizerResult.artifactPath,
|
|
6830
|
-
title: prTitle,
|
|
6831
|
-
body: prBody
|
|
6832
|
-
}
|
|
6833
|
-
});
|
|
6834
|
-
}
|
|
6835
|
-
const finalCommitFromState = worktreeState?.finalCommit?.completed;
|
|
6896
|
+
const prTitle = prDescriptionAgentResult.title;
|
|
6897
|
+
const prBody = prDescriptionAgentResult.body;
|
|
6898
|
+
const stateBeforeFinalCommit = readCurrentWorktreeState();
|
|
6899
|
+
const finalCommitFromState = stateBeforeFinalCommit?.finalCommit?.completed;
|
|
6836
6900
|
if (!finalCommitFromState) {
|
|
6837
6901
|
await finalizeWorktreeCommit({
|
|
6838
6902
|
worktreePath: executionResult.worktreePath,
|
|
6839
6903
|
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6840
6904
|
title: prTitle,
|
|
6841
|
-
body:
|
|
6905
|
+
body: prBody,
|
|
6842
6906
|
logger
|
|
6843
6907
|
});
|
|
6844
6908
|
if (executionResult.worktreePath) {
|
|
@@ -6866,9 +6930,9 @@ async function completeIssueRun(options) {
|
|
|
6866
6930
|
baseBranch: effectiveBaseBranch,
|
|
6867
6931
|
autoMerge: target.autoMerge !== false,
|
|
6868
6932
|
worktreePath: executionResult.worktreePath,
|
|
6869
|
-
worktreeState,
|
|
6933
|
+
worktreeState: readCurrentWorktreeState(),
|
|
6870
6934
|
prTitle,
|
|
6871
|
-
prBody
|
|
6935
|
+
prBody,
|
|
6872
6936
|
issueProvider,
|
|
6873
6937
|
prProvider,
|
|
6874
6938
|
logger,
|
|
@@ -6968,89 +7032,6 @@ async function finalizeWorktreeCommit(options) {
|
|
|
6968
7032
|
label: "git commit"
|
|
6969
7033
|
});
|
|
6970
7034
|
}
|
|
6971
|
-
async function assertCanonicalBaseAncestor2(options) {
|
|
6972
|
-
const { worktreePath, baseRef, stageName, logger } = options;
|
|
6973
|
-
await syncRemoteBaseRef2(worktreePath, baseRef, logger);
|
|
6974
|
-
try {
|
|
6975
|
-
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
6976
|
-
cwd: worktreePath,
|
|
6977
|
-
logger,
|
|
6978
|
-
label: "git merge-base --is-ancestor"
|
|
6979
|
-
});
|
|
6980
|
-
} catch {
|
|
6981
|
-
throw new Error(
|
|
6982
|
-
`Cannot continue after ${stageName}: ${baseRef} is not an ancestor of HEAD. An agent may have moved the issue branch behind the canonical base; refresh the branch onto the latest target base before continuing.`
|
|
6983
|
-
);
|
|
6984
|
-
}
|
|
6985
|
-
}
|
|
6986
|
-
async function completeIssueFinalReviewChangedPaths(options) {
|
|
6987
|
-
const actualChangedPaths = await listIssueFinalReviewChangedPaths2(options);
|
|
6988
|
-
const declared = new Set(options.declaredChangedPaths.map(normalizeGitPath2));
|
|
6989
|
-
const missing = actualChangedPaths.filter((path9) => !declared.has(path9));
|
|
6990
|
-
if (missing.length === 0) {
|
|
6991
|
-
return actualChangedPaths;
|
|
6992
|
-
}
|
|
6993
|
-
options.logger.step(
|
|
6994
|
-
"warn",
|
|
6995
|
-
`Issue Final Review pass omitted ${missing.length} changed file(s) from changedFiles; using git inventory for finalization state.`
|
|
6996
|
-
);
|
|
6997
|
-
options.logger.kv("MISSING_CHANGED_FILES", missing.join(", "));
|
|
6998
|
-
return actualChangedPaths;
|
|
6999
|
-
}
|
|
7000
|
-
async function listIssueFinalReviewChangedPaths2(options) {
|
|
7001
|
-
const trackedDiff = await execCapture(
|
|
7002
|
-
"git",
|
|
7003
|
-
["diff-index", "--name-only", options.baseRef, "--"],
|
|
7004
|
-
{
|
|
7005
|
-
cwd: options.worktreePath,
|
|
7006
|
-
logger: options.logger,
|
|
7007
|
-
label: "git diff-index Issue Final Review paths"
|
|
7008
|
-
}
|
|
7009
|
-
);
|
|
7010
|
-
const untrackedFiles = await execCapture(
|
|
7011
|
-
"git",
|
|
7012
|
-
["ls-files", "--others", "--exclude-standard"],
|
|
7013
|
-
{
|
|
7014
|
-
cwd: options.worktreePath,
|
|
7015
|
-
logger: options.logger,
|
|
7016
|
-
label: "git ls-files Issue Final Review paths"
|
|
7017
|
-
}
|
|
7018
|
-
);
|
|
7019
|
-
return Array.from(
|
|
7020
|
-
/* @__PURE__ */ new Set([
|
|
7021
|
-
...parseGitPathList2(trackedDiff.stdout),
|
|
7022
|
-
...parseGitPathList2(untrackedFiles.stdout)
|
|
7023
|
-
])
|
|
7024
|
-
).filter(isIssueFinalReviewAuditedPath2);
|
|
7025
|
-
}
|
|
7026
|
-
function parseGitPathList2(output) {
|
|
7027
|
-
return output.split(/\r?\n/).map(normalizeGitPath2).filter((path9) => path9.length > 0);
|
|
7028
|
-
}
|
|
7029
|
-
function normalizeGitPath2(path9) {
|
|
7030
|
-
return path9.replace(/^"|"$/g, "").replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
7031
|
-
}
|
|
7032
|
-
function isIssueFinalReviewAuditedPath2(path9) {
|
|
7033
|
-
return path9 !== "" && path9 !== WORKTREE_RUN_STATE_PATH && !path9.startsWith(".pourkit/.tmp/") && !path9.startsWith(".pourkit/logs/");
|
|
7034
|
-
}
|
|
7035
|
-
async function syncRemoteBaseRef2(worktreePath, baseRef, logger) {
|
|
7036
|
-
const remoteBase = parseRemoteBaseRef2(baseRef);
|
|
7037
|
-
if (!remoteBase) {
|
|
7038
|
-
return;
|
|
7039
|
-
}
|
|
7040
|
-
await execCapture("git", ["fetch", remoteBase.remote, remoteBase.branch], {
|
|
7041
|
-
cwd: worktreePath,
|
|
7042
|
-
logger,
|
|
7043
|
-
label: "git fetch target"
|
|
7044
|
-
});
|
|
7045
|
-
}
|
|
7046
|
-
function parseRemoteBaseRef2(baseRef) {
|
|
7047
|
-
const [remote, ...branchParts] = baseRef.split("/");
|
|
7048
|
-
const branch = branchParts.join("/");
|
|
7049
|
-
if (!remote || !branch) {
|
|
7050
|
-
return null;
|
|
7051
|
-
}
|
|
7052
|
-
return { remote, branch };
|
|
7053
|
-
}
|
|
7054
7035
|
function makeIssueTransitions2(provider, config) {
|
|
7055
7036
|
return createIssueTransitions(
|
|
7056
7037
|
{
|
|
@@ -7199,7 +7180,7 @@ async function runIssueLifecycle(options) {
|
|
|
7199
7180
|
worktreeState: startResult.worktreeState,
|
|
7200
7181
|
memory: startResult.memory
|
|
7201
7182
|
});
|
|
7202
|
-
if (finalReviewResult.
|
|
7183
|
+
if (finalReviewResult.status === "needs_human_review") {
|
|
7203
7184
|
await options.deps.transitionIssueFinalReviewNeedsHumanToHandoff({
|
|
7204
7185
|
issueNumber: options.issueNumber,
|
|
7205
7186
|
finalReviewResult
|
|
@@ -7393,17 +7374,17 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
|
|
|
7393
7374
|
|
|
7394
7375
|
// commands/prd-run.ts
|
|
7395
7376
|
import { lstatSync, realpathSync } from "fs";
|
|
7396
|
-
import { join as
|
|
7377
|
+
import { join as join16 } from "path";
|
|
7397
7378
|
|
|
7398
7379
|
// prd-run/state.ts
|
|
7399
7380
|
import {
|
|
7400
7381
|
existsSync as existsSync14,
|
|
7401
7382
|
mkdirSync as mkdirSync7,
|
|
7402
|
-
readFileSync as
|
|
7383
|
+
readFileSync as readFileSync15,
|
|
7403
7384
|
readdirSync as readdirSync3,
|
|
7404
7385
|
writeFileSync as writeFileSync4
|
|
7405
7386
|
} from "fs";
|
|
7406
|
-
import { join as
|
|
7387
|
+
import { join as join15 } from "path";
|
|
7407
7388
|
import { z } from "zod";
|
|
7408
7389
|
var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
|
|
7409
7390
|
var PrdRunRecordSchema = z.object({
|
|
@@ -7492,12 +7473,12 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7492
7473
|
return { record: null, diagnostics: [] };
|
|
7493
7474
|
}
|
|
7494
7475
|
try {
|
|
7495
|
-
const raw = JSON.parse(
|
|
7476
|
+
const raw = JSON.parse(readFileSync15(recordPath, "utf-8"));
|
|
7496
7477
|
const parsed = PrdRunRecordSchema.parse(raw);
|
|
7497
7478
|
return { record: parsed, diagnostics: [] };
|
|
7498
7479
|
} catch (error) {
|
|
7499
7480
|
try {
|
|
7500
|
-
const raw = JSON.parse(
|
|
7481
|
+
const raw = JSON.parse(readFileSync15(recordPath, "utf-8"));
|
|
7501
7482
|
if (raw && typeof raw === "object" && raw.start && typeof raw.start === "object" && raw.start.startBaseBranch === void 0) {
|
|
7502
7483
|
return {
|
|
7503
7484
|
record: raw,
|
|
@@ -7518,7 +7499,7 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7518
7499
|
}
|
|
7519
7500
|
}
|
|
7520
7501
|
function listPrdRuns(repoRoot2) {
|
|
7521
|
-
const stateDir =
|
|
7502
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7522
7503
|
if (!existsSync14(stateDir)) {
|
|
7523
7504
|
return { records: [], diagnostics: [] };
|
|
7524
7505
|
}
|
|
@@ -7528,10 +7509,10 @@ function listPrdRuns(repoRoot2) {
|
|
|
7528
7509
|
(left, right) => left.name.localeCompare(right.name)
|
|
7529
7510
|
)) {
|
|
7530
7511
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
7531
|
-
const recordPath =
|
|
7512
|
+
const recordPath = join15(stateDir, entry.name);
|
|
7532
7513
|
try {
|
|
7533
7514
|
const record = PrdRunRecordSchema.parse(
|
|
7534
|
-
JSON.parse(
|
|
7515
|
+
JSON.parse(readFileSync15(recordPath, "utf-8"))
|
|
7535
7516
|
);
|
|
7536
7517
|
records.push(record);
|
|
7537
7518
|
} catch (error) {
|
|
@@ -7544,7 +7525,7 @@ function listPrdRuns(repoRoot2) {
|
|
|
7544
7525
|
}
|
|
7545
7526
|
function writePrdRunRecord(repoRoot2, record) {
|
|
7546
7527
|
const normalized = normalizePrdRunRef(record.prdRef);
|
|
7547
|
-
const stateDir =
|
|
7528
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7548
7529
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7549
7530
|
mkdirSync7(stateDir, { recursive: true });
|
|
7550
7531
|
writeFileSync4(
|
|
@@ -7554,7 +7535,7 @@ function writePrdRunRecord(repoRoot2, record) {
|
|
|
7554
7535
|
);
|
|
7555
7536
|
}
|
|
7556
7537
|
function getRecordPath(repoRoot2, prdRef) {
|
|
7557
|
-
return
|
|
7538
|
+
return join15(
|
|
7558
7539
|
repoRoot2,
|
|
7559
7540
|
PRD_RUN_STATE_DIR,
|
|
7560
7541
|
`${normalizePrdRunRef(prdRef)}.json`
|
|
@@ -9314,7 +9295,7 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9314
9295
|
}
|
|
9315
9296
|
|
|
9316
9297
|
// prd-run/final-review-validation.ts
|
|
9317
|
-
import { existsSync as existsSync15, readFileSync as
|
|
9298
|
+
import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
|
|
9318
9299
|
|
|
9319
9300
|
// commands/prd-run.ts
|
|
9320
9301
|
async function runPrdRunLaunchCommand(options) {
|
|
@@ -12324,18 +12305,18 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
|
|
|
12324
12305
|
}
|
|
12325
12306
|
|
|
12326
12307
|
// commands/memory-init.ts
|
|
12327
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as
|
|
12328
|
-
import { join as
|
|
12308
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
|
|
12309
|
+
import { join as join17 } from "path";
|
|
12329
12310
|
async function runMemoryInitCommand(options) {
|
|
12330
12311
|
const cwd = options.cwd ?? process.cwd();
|
|
12331
|
-
const configPath =
|
|
12312
|
+
const configPath = join17(cwd, ".pourkit", "config.json");
|
|
12332
12313
|
if (!existsSync17(configPath)) {
|
|
12333
12314
|
return {
|
|
12334
12315
|
ok: false,
|
|
12335
12316
|
message: "No .pourkit/config.json found. Run `pourkit init` first to initialize Pourkit, then run `pourkit memory init`."
|
|
12336
12317
|
};
|
|
12337
12318
|
}
|
|
12338
|
-
const raw = JSON.parse(
|
|
12319
|
+
const raw = JSON.parse(readFileSync17(configPath, "utf8"));
|
|
12339
12320
|
let alreadyEnabled = false;
|
|
12340
12321
|
if (Object.prototype.hasOwnProperty.call(raw, "memory")) {
|
|
12341
12322
|
const m = raw.memory;
|
|
@@ -12360,8 +12341,8 @@ async function runMemoryInitCommand(options) {
|
|
|
12360
12341
|
const next = { ...raw, memory };
|
|
12361
12342
|
writeFileSync5(configPath, JSON.stringify(next, null, 2) + "\n");
|
|
12362
12343
|
}
|
|
12363
|
-
mkdirSync8(
|
|
12364
|
-
const gitignorePath =
|
|
12344
|
+
mkdirSync8(join17(cwd, ".pourkit", "icm"), { recursive: true });
|
|
12345
|
+
const gitignorePath = join17(cwd, ".gitignore");
|
|
12365
12346
|
updateManagedBlockSync(gitignorePath, generateGitignoreBlock());
|
|
12366
12347
|
const managed = await generateManagedAgentInstructions({
|
|
12367
12348
|
sourceRoot: cwd,
|
|
@@ -12369,14 +12350,14 @@ async function runMemoryInitCommand(options) {
|
|
|
12369
12350
|
});
|
|
12370
12351
|
let updatedAgentFile = false;
|
|
12371
12352
|
for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
|
|
12372
|
-
const agentPath =
|
|
12353
|
+
const agentPath = join17(cwd, fileName);
|
|
12373
12354
|
if (existsSync17(agentPath)) {
|
|
12374
12355
|
updateManagedBlockSync(agentPath, managed);
|
|
12375
12356
|
updatedAgentFile = true;
|
|
12376
12357
|
}
|
|
12377
12358
|
}
|
|
12378
12359
|
if (!updatedAgentFile) {
|
|
12379
|
-
updateManagedBlockSync(
|
|
12360
|
+
updateManagedBlockSync(join17(cwd, "AGENTS.md"), managed);
|
|
12380
12361
|
}
|
|
12381
12362
|
return {
|
|
12382
12363
|
ok: true,
|
|
@@ -12392,7 +12373,7 @@ ${content}${MANAGED_BLOCK_END}
|
|
|
12392
12373
|
writeFileSync5(filePath, blockContent);
|
|
12393
12374
|
return;
|
|
12394
12375
|
}
|
|
12395
|
-
const existing =
|
|
12376
|
+
const existing = readFileSync17(filePath, "utf8");
|
|
12396
12377
|
const beginIdx = existing.indexOf(MANAGED_BLOCK_BEGIN);
|
|
12397
12378
|
const endIdx = existing.indexOf(MANAGED_BLOCK_END);
|
|
12398
12379
|
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
|
|
@@ -12521,7 +12502,7 @@ async function runSerenaStatusCommand(options) {
|
|
|
12521
12502
|
}
|
|
12522
12503
|
|
|
12523
12504
|
// commands/config-schema.ts
|
|
12524
|
-
import { readFileSync as
|
|
12505
|
+
import { readFileSync as readFileSync18, existsSync as existsSync18 } from "fs";
|
|
12525
12506
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile2, readdir as readdir2 } from "fs/promises";
|
|
12526
12507
|
import { resolve as resolve3, dirname as dirname5, relative as relative2 } from "path";
|
|
12527
12508
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -12542,7 +12523,7 @@ var _schemaValidator = null;
|
|
|
12542
12523
|
var _schemaErrors = null;
|
|
12543
12524
|
function getSchemaValidator() {
|
|
12544
12525
|
if (!_schemaValidator) {
|
|
12545
|
-
const schema = JSON.parse(
|
|
12526
|
+
const schema = JSON.parse(readFileSync18(PACKAGED_SCHEMA_PATH, "utf-8"));
|
|
12546
12527
|
const ajv = new Ajv2({ strict: true });
|
|
12547
12528
|
ajv.addKeyword("x-pourkit-schema-version");
|
|
12548
12529
|
const validate = ajv.compile(schema);
|
|
@@ -12556,7 +12537,7 @@ function getSchemaValidator() {
|
|
|
12556
12537
|
return _schemaValidator;
|
|
12557
12538
|
}
|
|
12558
12539
|
function readPackagedHash() {
|
|
12559
|
-
return
|
|
12540
|
+
return readFileSync18(PACKAGED_HASH_PATH, "utf-8");
|
|
12560
12541
|
}
|
|
12561
12542
|
function localSchemaDir(repoRoot2) {
|
|
12562
12543
|
return resolve3(repoRoot2, ".pourkit/schema");
|
|
@@ -13112,7 +13093,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
13112
13093
|
detail: icmPath !== null ? icmPath : "icm not found on PATH"
|
|
13113
13094
|
});
|
|
13114
13095
|
const gitignorePath = resolve3(cwd, ".gitignore");
|
|
13115
|
-
const gitignoreContent = existsSync18(gitignorePath) ?
|
|
13096
|
+
const gitignoreContent = existsSync18(gitignorePath) ? readFileSync18(gitignorePath, "utf-8") : "";
|
|
13116
13097
|
const hasGitignoreEntry = gitignoreContent.includes(".pourkit/icm/");
|
|
13117
13098
|
checks.push({
|
|
13118
13099
|
name: "icm-gitignore",
|
|
@@ -13161,7 +13142,7 @@ async function runDoctorCommand(options) {
|
|
|
13161
13142
|
let rawMemoryConfig;
|
|
13162
13143
|
if (existsSync18(configPath)) {
|
|
13163
13144
|
try {
|
|
13164
|
-
const raw = JSON.parse(
|
|
13145
|
+
const raw = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
13165
13146
|
rawMemoryConfig = raw.memory;
|
|
13166
13147
|
const validate = getSchemaValidator();
|
|
13167
13148
|
const valid2 = validate(raw);
|
|
@@ -14373,7 +14354,7 @@ init_common();
|
|
|
14373
14354
|
|
|
14374
14355
|
// execution/sandcastle-execution.ts
|
|
14375
14356
|
import { existsSync as existsSync21, mkdirSync as mkdirSync9, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
14376
|
-
import { join as
|
|
14357
|
+
import { join as join20 } from "path";
|
|
14377
14358
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
14378
14359
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
14379
14360
|
|
|
@@ -14382,10 +14363,10 @@ init_common();
|
|
|
14382
14363
|
import { mkdtempSync } from "fs";
|
|
14383
14364
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
14384
14365
|
import { tmpdir } from "os";
|
|
14385
|
-
import { dirname as dirname7, join as
|
|
14366
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
14386
14367
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
14387
14368
|
for (const artifact of artifacts) {
|
|
14388
|
-
const filePath =
|
|
14369
|
+
const filePath = join18(worktreePath, artifact.path);
|
|
14389
14370
|
await ensureDir(dirname7(filePath));
|
|
14390
14371
|
await writeFile4(filePath, artifact.content, "utf-8");
|
|
14391
14372
|
}
|
|
@@ -14400,7 +14381,7 @@ import path7 from "path";
|
|
|
14400
14381
|
|
|
14401
14382
|
// execution/sandbox-image.ts
|
|
14402
14383
|
import { createHash as createHash2 } from "crypto";
|
|
14403
|
-
import { existsSync as existsSync20, readFileSync as
|
|
14384
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
|
|
14404
14385
|
import path6 from "path";
|
|
14405
14386
|
function sandboxImageName(repoRoot2, installIcm) {
|
|
14406
14387
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
@@ -14411,7 +14392,7 @@ function sandboxImageName(repoRoot2, installIcm) {
|
|
|
14411
14392
|
const base2 = `sandcastle:${baseName}`;
|
|
14412
14393
|
return installIcm ? `${base2}-icm` : base2;
|
|
14413
14394
|
}
|
|
14414
|
-
const fingerprint = createHash2("sha256").update(
|
|
14395
|
+
const fingerprint = createHash2("sha256").update(readFileSync19(dockerfilePath)).digest("hex").slice(0, 8);
|
|
14415
14396
|
const base = `sandcastle:${baseName}-${fingerprint}`;
|
|
14416
14397
|
return installIcm ? `${base}-icm` : base;
|
|
14417
14398
|
}
|
|
@@ -14447,7 +14428,7 @@ async function createSandboxFromExistingWorktree(options) {
|
|
|
14447
14428
|
}
|
|
14448
14429
|
|
|
14449
14430
|
// execution/sandbox-options.ts
|
|
14450
|
-
import { join as
|
|
14431
|
+
import { join as join19 } from "path";
|
|
14451
14432
|
var POURKIT_ICM_CONTAINER_DIR = "/home/agent/.pourkit/icm";
|
|
14452
14433
|
function buildSandboxOptions(repoRoot2, sandbox, memory) {
|
|
14453
14434
|
const mounts = [];
|
|
@@ -14456,12 +14437,12 @@ function buildSandboxOptions(repoRoot2, sandbox, memory) {
|
|
|
14456
14437
|
}
|
|
14457
14438
|
if (memory?.available) {
|
|
14458
14439
|
mounts.push({
|
|
14459
|
-
hostPath:
|
|
14440
|
+
hostPath: join19(repoRoot2, ".pourkit", "icm"),
|
|
14460
14441
|
sandboxPath: POURKIT_ICM_CONTAINER_DIR,
|
|
14461
14442
|
readonly: false
|
|
14462
14443
|
});
|
|
14463
14444
|
mounts.push({
|
|
14464
|
-
hostPath:
|
|
14445
|
+
hostPath: join19(repoRoot2, ".pourkit", "icm", "cache"),
|
|
14465
14446
|
sandboxPath: "/home/agent/.cache/icm",
|
|
14466
14447
|
readonly: false
|
|
14467
14448
|
});
|
|
@@ -14595,13 +14576,13 @@ var SandcastleExecutionSession = class {
|
|
|
14595
14576
|
"Memory enabled but memory context is invalid. Run `pourkit memory init` or install ICM for host planning use."
|
|
14596
14577
|
);
|
|
14597
14578
|
}
|
|
14598
|
-
const hostMemoryDir =
|
|
14579
|
+
const hostMemoryDir = join20(repoRoot2, ".pourkit", "icm");
|
|
14599
14580
|
if (!existsSync21(hostMemoryDir) || !statSync2(hostMemoryDir).isDirectory()) {
|
|
14600
14581
|
throw new MissingHostMemoryDirError(
|
|
14601
14582
|
"Memory enabled but host .pourkit/icm/ directory is missing. Run `pourkit memory init` to create the repo-scoped memory store."
|
|
14602
14583
|
);
|
|
14603
14584
|
}
|
|
14604
|
-
mkdirSync9(
|
|
14585
|
+
mkdirSync9(join20(hostMemoryDir, "cache"), { recursive: true });
|
|
14605
14586
|
}
|
|
14606
14587
|
const sandboxOptions = buildSandboxOptions(
|
|
14607
14588
|
repoRoot2,
|
|
@@ -14760,7 +14741,7 @@ function sanitizeBranch(branchName) {
|
|
|
14760
14741
|
}
|
|
14761
14742
|
function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
14762
14743
|
return paths.filter((relativePath) => {
|
|
14763
|
-
const source =
|
|
14744
|
+
const source = join20(repoRoot2, relativePath);
|
|
14764
14745
|
if (!existsSync21(source)) {
|
|
14765
14746
|
return true;
|
|
14766
14747
|
}
|
|
@@ -14771,7 +14752,7 @@ function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
|
14771
14752
|
} catch {
|
|
14772
14753
|
return true;
|
|
14773
14754
|
}
|
|
14774
|
-
const destination =
|
|
14755
|
+
const destination = join20(worktreePath, relativePath);
|
|
14775
14756
|
return !existsSync21(destination);
|
|
14776
14757
|
});
|
|
14777
14758
|
}
|
|
@@ -14836,12 +14817,12 @@ function isPlainObject(value) {
|
|
|
14836
14817
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14837
14818
|
}
|
|
14838
14819
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
14839
|
-
const promptsDir =
|
|
14820
|
+
const promptsDir = join20(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
14840
14821
|
mkdirSync9(promptsDir, { recursive: true });
|
|
14841
14822
|
const timestamp2 = Date.now();
|
|
14842
14823
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
14843
14824
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
14844
|
-
const filePath =
|
|
14825
|
+
const filePath = join20(promptsDir, filename);
|
|
14845
14826
|
writeFileSync6(filePath, prompt, "utf-8");
|
|
14846
14827
|
}
|
|
14847
14828
|
|
|
@@ -15562,11 +15543,11 @@ function createCliProgram(version) {
|
|
|
15562
15543
|
return program;
|
|
15563
15544
|
}
|
|
15564
15545
|
async function resolveCliVersion() {
|
|
15565
|
-
if (isPackageVersion("0.0.0-next-
|
|
15566
|
-
return "0.0.0-next-
|
|
15546
|
+
if (isPackageVersion("0.0.0-next-20260622235247")) {
|
|
15547
|
+
return "0.0.0-next-20260622235247";
|
|
15567
15548
|
}
|
|
15568
|
-
if (isReleaseVersion("0.0.0-next-
|
|
15569
|
-
return "0.0.0-next-
|
|
15549
|
+
if (isReleaseVersion("0.0.0-next-20260622235247")) {
|
|
15550
|
+
return "0.0.0-next-20260622235247";
|
|
15570
15551
|
}
|
|
15571
15552
|
try {
|
|
15572
15553
|
const root = repoRoot();
|