@pourkit/cli 0.0.0-next-20260622201317 → 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 +829 -818
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +813 -802
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3740,9 +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
|
-
|
|
3746
3743
|
// pr/templates.ts
|
|
3747
3744
|
init_common();
|
|
3748
3745
|
function renderBranchName(template, issue) {
|
|
@@ -4959,9 +4956,9 @@ async function assertCanonicalBaseAncestor(options) {
|
|
|
4959
4956
|
}
|
|
4960
4957
|
}
|
|
4961
4958
|
|
|
4962
|
-
//
|
|
4963
|
-
import {
|
|
4964
|
-
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";
|
|
4965
4962
|
|
|
4966
4963
|
// pr/pr-description-context.ts
|
|
4967
4964
|
init_common();
|
|
@@ -5083,204 +5080,323 @@ Write your finalizer output to: ${artifactPathInWorktree}
|
|
|
5083
5080
|
Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktree}`;
|
|
5084
5081
|
}
|
|
5085
5082
|
|
|
5086
|
-
//
|
|
5087
|
-
import {
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
);
|
|
5109
|
-
const
|
|
5110
|
-
|
|
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
|
-
|
|
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,
|
|
5141
5156
|
prompt,
|
|
5142
|
-
|
|
5143
|
-
repoRoot:
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5157
|
+
target,
|
|
5158
|
+
repoRoot: repoRoot2,
|
|
5159
|
+
branchName: builderBranch,
|
|
5160
|
+
sandbox: options.config.sandbox,
|
|
5161
|
+
autoApprove: true,
|
|
5162
|
+
artifactPath: artifactPathInWorktree,
|
|
5163
|
+
worktreePath,
|
|
5147
5164
|
artifacts: [
|
|
5148
5165
|
buildRunContextArtifact({
|
|
5149
|
-
issue
|
|
5150
|
-
|
|
5151
|
-
|
|
5166
|
+
issue,
|
|
5167
|
+
parentPrdIssue,
|
|
5168
|
+
target,
|
|
5169
|
+
branchName: builderBranch,
|
|
5170
|
+
repoRoot: repoRoot2,
|
|
5152
5171
|
reviewerCriteria: strategy.review.reviewer.criteria,
|
|
5153
|
-
sections: STAGE_SECTIONS.
|
|
5172
|
+
sections: STAGE_SECTIONS.issueFinalReview
|
|
5154
5173
|
})
|
|
5155
5174
|
],
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
}
|
|
5159
|
-
const output = readFileSync12(artifactPath, "utf-8");
|
|
5160
|
-
yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
|
|
5161
|
-
return result;
|
|
5175
|
+
...options.memory ? { memory: options.memory } : {},
|
|
5176
|
+
logger
|
|
5177
|
+
}
|
|
5162
5178
|
});
|
|
5163
|
-
const
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
)
|
|
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)}`
|
|
5168
5255
|
);
|
|
5169
5256
|
}
|
|
5170
|
-
function
|
|
5171
|
-
const
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
prompt: options.prompt,
|
|
5195
|
-
target: options.target,
|
|
5196
|
-
repoRoot: options.repoRoot,
|
|
5197
|
-
branchName: options.branchName,
|
|
5198
|
-
sandbox: options.config.sandbox,
|
|
5199
|
-
autoApprove: true,
|
|
5200
|
-
artifactPath: options.artifactPathInWorktree,
|
|
5201
|
-
worktreePath: options.worktreePath,
|
|
5202
|
-
artifacts: options.artifacts,
|
|
5203
|
-
baseRef: options.baseRef,
|
|
5204
|
-
checkoutBase: options.checkoutBase,
|
|
5205
|
-
reviewBase: options.reviewBase,
|
|
5206
|
-
...options.memory ? { memory: options.memory } : {},
|
|
5207
|
-
logger: options.logger
|
|
5208
|
-
}
|
|
5209
|
-
}),
|
|
5210
|
-
catch: (error) => new FinalizerFailure({
|
|
5211
|
-
message: error instanceof Error ? error.message : String(error)
|
|
5212
|
-
})
|
|
5213
|
-
});
|
|
5214
|
-
const executionResult = retryResult.executionResult;
|
|
5215
|
-
if (!executionResult.success) {
|
|
5216
|
-
return yield* Effect7.fail(
|
|
5217
|
-
new FinalizerFailure({
|
|
5218
|
-
message: `Finalizer agent execution failed: ${executionResult.error}`
|
|
5219
|
-
})
|
|
5220
|
-
);
|
|
5221
|
-
}
|
|
5222
|
-
if (retryResult.artifact._tag === "content") {
|
|
5223
|
-
const validation = validateAgentArtifact({
|
|
5224
|
-
kind: "finalizer",
|
|
5225
|
-
artifactPath
|
|
5226
|
-
});
|
|
5227
|
-
if (validation.ok) {
|
|
5228
|
-
try {
|
|
5229
|
-
parsed = parsePrDescription(retryResult.artifact.value);
|
|
5230
|
-
lastValidationError = void 0;
|
|
5231
|
-
break;
|
|
5232
|
-
} catch (error) {
|
|
5233
|
-
lastValidationError = error instanceof PrDescriptionProtocolError ? new Error(`Finalizer protocol error: ${error.message}`) : error instanceof Error ? error : new Error(String(error));
|
|
5234
|
-
}
|
|
5235
|
-
} else {
|
|
5236
|
-
lastValidationError = new Error(
|
|
5237
|
-
`Finalizer protocol error: ${validation.reason}`
|
|
5238
|
-
);
|
|
5239
|
-
if (protocolAttempt === options.maxAttempts) break;
|
|
5240
|
-
}
|
|
5241
|
-
} else if (retryResult.artifact._tag === "empty") {
|
|
5242
|
-
lastValidationError = new Error(
|
|
5243
|
-
`Finalizer agent produced empty output at ${artifactPath}`
|
|
5244
|
-
);
|
|
5245
|
-
break;
|
|
5246
|
-
} else {
|
|
5247
|
-
lastValidationError = new Error(
|
|
5248
|
-
`Finalizer agent did not produce output at ${artifactPath}`
|
|
5249
|
-
);
|
|
5250
|
-
break;
|
|
5251
|
-
}
|
|
5252
|
-
}
|
|
5253
|
-
if (!parsed) {
|
|
5254
|
-
return yield* Effect7.fail(
|
|
5255
|
-
new FinalizerFailure({
|
|
5256
|
-
message: lastValidationError?.message ?? "Finalizer validation failed"
|
|
5257
|
-
})
|
|
5258
|
-
);
|
|
5259
|
-
}
|
|
5260
|
-
options.logger.step("info", "Finalizer output generated successfully");
|
|
5261
|
-
return {
|
|
5262
|
-
title: ensureConventionalPrTitle(parsed.title, options.commitSummaries),
|
|
5263
|
-
body: parsed.body,
|
|
5264
|
-
artifactPath
|
|
5265
|
-
};
|
|
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"
|
|
5266
5281
|
});
|
|
5267
5282
|
}
|
|
5268
|
-
function
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
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"
|
|
5274
5313
|
}
|
|
5275
|
-
|
|
5276
|
-
|
|
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);
|
|
5277
5330
|
}
|
|
5278
|
-
function
|
|
5279
|
-
return
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
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;
|
|
5284
5400
|
}
|
|
5285
5401
|
|
|
5286
5402
|
// pr/pr-body.ts
|
|
@@ -5351,44 +5467,315 @@ function extractClosingRefs(body) {
|
|
|
5351
5467
|
return refs;
|
|
5352
5468
|
}
|
|
5353
5469
|
|
|
5354
|
-
// issues/
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
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
|
+
|
|
5741
|
+
// issues/github-issue-publication.ts
|
|
5742
|
+
init_common();
|
|
5743
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
5744
|
+
import { isAbsolute as isAbsolute5, join as join13 } from "path";
|
|
5745
|
+
|
|
5746
|
+
// issues/issue-transitions.ts
|
|
5747
|
+
function createIssueTransitions(deps, labels) {
|
|
5748
|
+
return {
|
|
5749
|
+
async removeBlocked(issueNumber) {
|
|
5750
|
+
await deps.removeLabel(issueNumber, labels.blocked);
|
|
5751
|
+
},
|
|
5752
|
+
async addReadyForAgent(issueNumber) {
|
|
5753
|
+
const issue = await deps.fetchIssue(issueNumber);
|
|
5754
|
+
if (!issue.labels.includes(labels.readyForAgent)) {
|
|
5755
|
+
await deps.addLabels(issueNumber, [labels.readyForAgent]);
|
|
5756
|
+
}
|
|
5757
|
+
},
|
|
5758
|
+
async moveToNeedsTriage(issueNumber) {
|
|
5759
|
+
if (deps.updateLabels) {
|
|
5760
|
+
await deps.updateLabels(
|
|
5761
|
+
issueNumber,
|
|
5762
|
+
[labels.blocked, labels.readyForAgent],
|
|
5763
|
+
[labels.needsTriage]
|
|
5764
|
+
);
|
|
5765
|
+
} else {
|
|
5766
|
+
await deps.removeLabel(issueNumber, labels.blocked);
|
|
5767
|
+
const issue = await deps.fetchIssue(issueNumber);
|
|
5768
|
+
if (issue.labels.includes(labels.readyForAgent)) {
|
|
5769
|
+
await deps.removeLabel(issueNumber, labels.readyForAgent);
|
|
5770
|
+
}
|
|
5771
|
+
await deps.addLabels(issueNumber, [labels.needsTriage]);
|
|
5772
|
+
}
|
|
5773
|
+
},
|
|
5774
|
+
async moveToReadyForHuman(issueNumber) {
|
|
5775
|
+
try {
|
|
5776
|
+
await deps.removeLabel(issueNumber, labels.agentInProgress);
|
|
5777
|
+
} catch {
|
|
5778
|
+
}
|
|
5392
5779
|
try {
|
|
5393
5780
|
await deps.removeLabel(issueNumber, labels.readyForAgent);
|
|
5394
5781
|
} catch {
|
|
@@ -5829,11 +6216,11 @@ function readIssueFinalReviewArtifact(worktreeState, worktreePath) {
|
|
|
5829
6216
|
if (!configuredPath) {
|
|
5830
6217
|
return null;
|
|
5831
6218
|
}
|
|
5832
|
-
const artifactPath =
|
|
5833
|
-
if (!
|
|
6219
|
+
const artifactPath = isAbsolute5(configuredPath) ? configuredPath : join13(worktreePath, configuredPath);
|
|
6220
|
+
if (!existsSync13(artifactPath)) return null;
|
|
5834
6221
|
let parsed;
|
|
5835
6222
|
try {
|
|
5836
|
-
parsed = JSON.parse(
|
|
6223
|
+
parsed = JSON.parse(readFileSync14(artifactPath, "utf-8"));
|
|
5837
6224
|
} catch {
|
|
5838
6225
|
return null;
|
|
5839
6226
|
}
|
|
@@ -5893,520 +6280,201 @@ function buildIssueFinalReviewPrComment(artifact) {
|
|
|
5893
6280
|
`| ${formatPath(file.path)} | ${formatLabel(file.scopeVerdict)} | ${formatBoolean(file.revertFlag)} | ${formatTableCell(file.change)} |`
|
|
5894
6281
|
);
|
|
5895
6282
|
}
|
|
5896
|
-
lines.push("");
|
|
5897
|
-
}
|
|
5898
|
-
}
|
|
5899
|
-
if (typeof artifact.selfRetouched === "boolean" || artifact.outOfScopeFiles || artifact.badReverts) {
|
|
5900
|
-
lines.push("### Scope And Reverts", "");
|
|
5901
|
-
if (typeof artifact.selfRetouched === "boolean") {
|
|
5902
|
-
lines.push(
|
|
5903
|
-
`- **Self-retouched:** ${formatBoolean(artifact.selfRetouched)}`
|
|
5904
|
-
);
|
|
5905
|
-
}
|
|
5906
|
-
if (artifact.outOfScopeFiles) {
|
|
5907
|
-
lines.push(
|
|
5908
|
-
`- **Out-of-scope files:** ${formatPathList(artifact.outOfScopeFiles)}`
|
|
5909
|
-
);
|
|
5910
|
-
}
|
|
5911
|
-
if (artifact.badReverts) {
|
|
5912
|
-
lines.push(
|
|
5913
|
-
`- **Bad reverts:** ${formatCount(artifact.badReverts.length)}`
|
|
5914
|
-
);
|
|
5915
|
-
}
|
|
5916
|
-
lines.push("");
|
|
5917
|
-
}
|
|
5918
|
-
return lines.join("\n").trimEnd();
|
|
5919
|
-
}
|
|
5920
|
-
function formatLabel(value) {
|
|
5921
|
-
return typeof value === "string" && value.trim() !== "" ? value.trim().replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) : "Unknown";
|
|
5922
|
-
}
|
|
5923
|
-
function formatBoolean(value) {
|
|
5924
|
-
return value === true ? "Yes" : value === false ? "No" : "Unknown";
|
|
5925
|
-
}
|
|
5926
|
-
function formatPath(value) {
|
|
5927
|
-
return typeof value === "string" && value.trim() !== "" ? `\`${value.trim()}\`` : "Unknown";
|
|
5928
|
-
}
|
|
5929
|
-
function formatPathList(paths) {
|
|
5930
|
-
return paths.length > 0 ? paths.map((path9) => `\`${path9}\``).join(", ") : "None";
|
|
5931
|
-
}
|
|
5932
|
-
function formatCount(count) {
|
|
5933
|
-
return count === 0 ? "None" : String(count);
|
|
5934
|
-
}
|
|
5935
|
-
function formatTableCell(value) {
|
|
5936
|
-
return typeof value === "string" && value.trim() !== "" ? value.trim().replace(/\s+/g, " ").replace(/\|/g, "\\|") : "Unknown";
|
|
5937
|
-
}
|
|
5938
|
-
async function performCleanupAndClosure(options, pr) {
|
|
5939
|
-
await options.issueProvider.removeLabel(
|
|
5940
|
-
options.issue.number,
|
|
5941
|
-
options.labels.agentInProgress
|
|
5942
|
-
);
|
|
5943
|
-
await options.issueProvider.removeLabel(
|
|
5944
|
-
options.issue.number,
|
|
5945
|
-
options.labels.prOpenAwaitingMerge
|
|
5946
|
-
);
|
|
5947
|
-
let childCloseSucceeded = false;
|
|
5948
|
-
try {
|
|
5949
|
-
await options.issueProvider.closeIssue(options.issue.number);
|
|
5950
|
-
childCloseSucceeded = true;
|
|
5951
|
-
} catch (error) {
|
|
5952
|
-
options.logger.step(
|
|
5953
|
-
"warn",
|
|
5954
|
-
`Issue #${options.issue.number} could not be closed: ${error instanceof Error ? error.message : String(error)}`
|
|
5955
|
-
);
|
|
5956
|
-
}
|
|
5957
|
-
if (childCloseSucceeded) {
|
|
5958
|
-
await maybeCloseParentAfterChildCompletion(
|
|
5959
|
-
options.issueProvider,
|
|
5960
|
-
options.issue.number,
|
|
5961
|
-
options.logger
|
|
5962
|
-
);
|
|
5963
|
-
}
|
|
5964
|
-
return {
|
|
5965
|
-
status: "merged",
|
|
5966
|
-
prNumber: pr.number,
|
|
5967
|
-
prUrl: pr.url,
|
|
5968
|
-
prTitle: options.prTitle,
|
|
5969
|
-
prBody: options.prBody
|
|
5970
|
-
};
|
|
5971
|
-
}
|
|
5972
|
-
function assertRecordedMergedPrDetails(state) {
|
|
5973
|
-
if (typeof state.pr?.number === "number" && typeof state.pr.url === "string") {
|
|
5974
|
-
return;
|
|
5975
|
-
}
|
|
5976
|
-
throw new Error(
|
|
5977
|
-
"Lifecycle invariant: resumed merged GitHub Issue Publication requires recorded PR number and URL"
|
|
5978
|
-
);
|
|
5979
|
-
}
|
|
5980
|
-
function makeIssueTransitions(issueProvider, labels) {
|
|
5981
|
-
return createIssueTransitions(
|
|
5982
|
-
{
|
|
5983
|
-
fetchIssue: issueProvider.fetchIssue.bind(issueProvider),
|
|
5984
|
-
addLabels: issueProvider.addLabels.bind(issueProvider),
|
|
5985
|
-
removeLabel: issueProvider.removeLabel.bind(issueProvider),
|
|
5986
|
-
closeIssue: issueProvider.closeIssue.bind(issueProvider)
|
|
5987
|
-
},
|
|
5988
|
-
{
|
|
5989
|
-
blocked: labels.blocked,
|
|
5990
|
-
readyForAgent: labels.readyForAgent,
|
|
5991
|
-
needsTriage: labels.needsTriage,
|
|
5992
|
-
agentInProgress: labels.agentInProgress,
|
|
5993
|
-
readyForHuman: labels.readyForHuman,
|
|
5994
|
-
prOpenAwaitingMerge: labels.prOpenAwaitingMerge
|
|
5995
|
-
}
|
|
5996
|
-
);
|
|
5997
|
-
}
|
|
5998
|
-
async function guardFinalPublishContent(options) {
|
|
5999
|
-
const {
|
|
6000
|
-
worktreePath,
|
|
6001
|
-
baseRef,
|
|
6002
|
-
title,
|
|
6003
|
-
body,
|
|
6004
|
-
secretLikeContentDetectionEnabled,
|
|
6005
|
-
logger
|
|
6006
|
-
} = options;
|
|
6007
|
-
if (!secretLikeContentDetectionEnabled) {
|
|
6008
|
-
return;
|
|
6009
|
-
}
|
|
6010
|
-
const finalDiff = await execCapture("git", ["diff", `${baseRef}...HEAD`], {
|
|
6011
|
-
cwd: worktreePath,
|
|
6012
|
-
logger,
|
|
6013
|
-
label: "git diff final secret guard"
|
|
6014
|
-
});
|
|
6015
|
-
assertNoSecretLikeContent([
|
|
6016
|
-
{
|
|
6017
|
-
source: "final diff",
|
|
6018
|
-
content: extractAddedDiffContent(finalDiff.stdout)
|
|
6019
|
-
},
|
|
6020
|
-
{ source: "PR title", content: title },
|
|
6021
|
-
{ source: "PR body", content: body }
|
|
6022
|
-
]);
|
|
6023
|
-
}
|
|
6024
|
-
function extractAddedDiffContent(diff) {
|
|
6025
|
-
return diff.split(/\r?\n/).filter((line) => line.startsWith("+") && !line.startsWith("+++")).map((line) => line.slice(1)).join("\n");
|
|
6026
|
-
}
|
|
6027
|
-
function assertNoSecretLikeContent(inputs) {
|
|
6028
|
-
const findings = inputs.flatMap(
|
|
6029
|
-
({ source, content }) => findSecretLikeContent(source, content ?? "")
|
|
6030
|
-
);
|
|
6031
|
-
if (findings.length === 0) {
|
|
6032
|
-
return;
|
|
6033
|
-
}
|
|
6034
|
-
const summary = findings.slice(0, 5).map((finding) => `${finding.source}: ${finding.kind}`).join("; ");
|
|
6035
|
-
throw new Error(
|
|
6036
|
-
`Secret-like content detected before finalization (${summary}). Redact it before committing, pushing, or creating a PR.`
|
|
6037
|
-
);
|
|
6038
|
-
}
|
|
6039
|
-
function findSecretLikeContent(source, content) {
|
|
6040
|
-
const findings = [];
|
|
6041
|
-
const patterns = [
|
|
6042
|
-
{
|
|
6043
|
-
kind: "private key block",
|
|
6044
|
-
regex: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/gi
|
|
6045
|
-
},
|
|
6046
|
-
{
|
|
6047
|
-
kind: "GitHub token",
|
|
6048
|
-
regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g
|
|
6049
|
-
},
|
|
6050
|
-
{
|
|
6051
|
-
kind: "GitHub fine-grained token",
|
|
6052
|
-
regex: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g
|
|
6053
|
-
},
|
|
6054
|
-
{ kind: "npm token", regex: /\bnpm_[A-Za-z0-9]{20,}\b/g },
|
|
6055
|
-
{ kind: "OpenAI key", regex: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g },
|
|
6056
|
-
{ kind: "AWS access key", regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g },
|
|
6057
|
-
{
|
|
6058
|
-
kind: "authorization bearer token",
|
|
6059
|
-
regex: /\bAuthorization\s*:\s*Bearer\s+[^\s`'\"]{12,}/gi
|
|
6060
|
-
},
|
|
6061
|
-
{
|
|
6062
|
-
kind: "secret assignment",
|
|
6063
|
-
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
|
|
6064
|
-
}
|
|
6065
|
-
];
|
|
6066
|
-
for (const { kind, regex } of patterns) {
|
|
6067
|
-
for (const match of content.matchAll(regex)) {
|
|
6068
|
-
if (isAllowedSecretPlaceholder(match[0]) || isAllowedDocumentationTokenReference(match[0])) {
|
|
6069
|
-
continue;
|
|
6070
|
-
}
|
|
6071
|
-
findings.push({ source, kind });
|
|
6072
|
-
break;
|
|
6283
|
+
lines.push("");
|
|
6073
6284
|
}
|
|
6074
6285
|
}
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
"<redacted>",
|
|
6081
|
-
"<example",
|
|
6082
|
-
"example-",
|
|
6083
|
-
"dummy-",
|
|
6084
|
-
"test-",
|
|
6085
|
-
"placeholder",
|
|
6086
|
-
"xxxxx"
|
|
6087
|
-
].some((allowed) => normalized.includes(allowed));
|
|
6088
|
-
}
|
|
6089
|
-
function isAllowedDocumentationTokenReference(value) {
|
|
6090
|
-
return /^token\s*:\s*["']?<verdict>/i.test(value);
|
|
6091
|
-
}
|
|
6092
|
-
|
|
6093
|
-
// issues/issue-final-review.ts
|
|
6094
|
-
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
6095
|
-
import { isAbsolute as isAbsolute4, join as join13 } from "path";
|
|
6096
|
-
init_common();
|
|
6097
|
-
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join13(
|
|
6098
|
-
".pourkit",
|
|
6099
|
-
".tmp",
|
|
6100
|
-
"issue-final-review",
|
|
6101
|
-
"agent-output.json"
|
|
6102
|
-
);
|
|
6103
|
-
async function runIssueFinalReview(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 ifrFromState = options.worktreeState?.issueFinalReview;
|
|
6117
|
-
if (ifrFromState?.completed && ifrFromState.verdict === "pass") {
|
|
6118
|
-
if (!ifrFromState.artifactPath) {
|
|
6119
|
-
throw new Error(
|
|
6120
|
-
"Issue Final Review state is incomplete: missing artifactPath"
|
|
6286
|
+
if (typeof artifact.selfRetouched === "boolean" || artifact.outOfScopeFiles || artifact.badReverts) {
|
|
6287
|
+
lines.push("### Scope And Reverts", "");
|
|
6288
|
+
if (typeof artifact.selfRetouched === "boolean") {
|
|
6289
|
+
lines.push(
|
|
6290
|
+
`- **Self-retouched:** ${formatBoolean(artifact.selfRetouched)}`
|
|
6121
6291
|
);
|
|
6122
6292
|
}
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
artifactPath: cachedArtifactPath,
|
|
6127
|
-
issueNumber: issue.number,
|
|
6128
|
-
branchName: builderBranch
|
|
6129
|
-
});
|
|
6130
|
-
if (!validation2.ok) {
|
|
6131
|
-
throw new Error(
|
|
6132
|
-
`Issue Final Review state artifact is invalid: ${validation2.reason}`
|
|
6293
|
+
if (artifact.outOfScopeFiles) {
|
|
6294
|
+
lines.push(
|
|
6295
|
+
`- **Out-of-scope files:** ${formatPathList(artifact.outOfScopeFiles)}`
|
|
6133
6296
|
);
|
|
6134
6297
|
}
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
selfRetouched: ifrFromState.selfRetouched ?? false,
|
|
6140
|
-
changedPaths: ifrFromState.changedPaths ?? [],
|
|
6141
|
-
verificationPassed: ifrFromState.verificationPassed ?? false
|
|
6142
|
-
};
|
|
6143
|
-
}
|
|
6144
|
-
const strategy = target.strategy;
|
|
6145
|
-
const issueFinalReview = strategy.issueFinalReview;
|
|
6146
|
-
const agent = issueFinalReview;
|
|
6147
|
-
const prompt = loadIssueFinalReviewPrompt({
|
|
6148
|
-
repoRoot: repoRoot2,
|
|
6149
|
-
promptTemplate: agent.promptTemplate,
|
|
6150
|
-
validationCommand: `pourkit validate-artifact issue-final-review ${artifactPathInWorktree} --issue-number ${issue.number} --branch-name ${builderBranch} --base-ref origin/${target.baseBranch}`,
|
|
6151
|
-
reviewArtifactPath: options.reviewArtifactPath,
|
|
6152
|
-
reviewVerdict: options.reviewVerdict
|
|
6153
|
-
});
|
|
6154
|
-
const entry = await executeWithMissingOrEmptyArtifactRetry({
|
|
6155
|
-
executionProvider,
|
|
6156
|
-
missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(agent),
|
|
6157
|
-
logger,
|
|
6158
|
-
runningMessage: () => "Running Issue Final Review agent",
|
|
6159
|
-
retryMessage: (attempt, total) => `Retrying Issue Final Review after empty output (${attempt}/${total})`,
|
|
6160
|
-
executionOptions: {
|
|
6161
|
-
stage: "issueFinalReview",
|
|
6162
|
-
agent: agent.agent,
|
|
6163
|
-
model: agent.model,
|
|
6164
|
-
variant: agent.variant,
|
|
6165
|
-
env: agent.env,
|
|
6166
|
-
prompt,
|
|
6167
|
-
target,
|
|
6168
|
-
repoRoot: repoRoot2,
|
|
6169
|
-
branchName: builderBranch,
|
|
6170
|
-
sandbox: options.config.sandbox,
|
|
6171
|
-
autoApprove: true,
|
|
6172
|
-
artifactPath: artifactPathInWorktree,
|
|
6173
|
-
worktreePath,
|
|
6174
|
-
artifacts: [
|
|
6175
|
-
buildRunContextArtifact({
|
|
6176
|
-
issue,
|
|
6177
|
-
parentPrdIssue,
|
|
6178
|
-
target,
|
|
6179
|
-
branchName: builderBranch,
|
|
6180
|
-
repoRoot: repoRoot2,
|
|
6181
|
-
reviewerCriteria: strategy.review.reviewer.criteria,
|
|
6182
|
-
sections: STAGE_SECTIONS.issueFinalReview
|
|
6183
|
-
})
|
|
6184
|
-
],
|
|
6185
|
-
...options.memory ? { memory: options.memory } : {},
|
|
6186
|
-
logger
|
|
6298
|
+
if (artifact.badReverts) {
|
|
6299
|
+
lines.push(
|
|
6300
|
+
`- **Bad reverts:** ${formatCount(artifact.badReverts.length)}`
|
|
6301
|
+
);
|
|
6187
6302
|
}
|
|
6188
|
-
|
|
6189
|
-
const executionResult = entry.executionResult;
|
|
6190
|
-
if (!executionResult.success) {
|
|
6191
|
-
throw new Error(
|
|
6192
|
-
`Issue Final Review agent execution failed: ${executionResult.error}`
|
|
6193
|
-
);
|
|
6303
|
+
lines.push("");
|
|
6194
6304
|
}
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6305
|
+
return lines.join("\n").trimEnd();
|
|
6306
|
+
}
|
|
6307
|
+
function formatLabel(value) {
|
|
6308
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim().replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) : "Unknown";
|
|
6309
|
+
}
|
|
6310
|
+
function formatBoolean(value) {
|
|
6311
|
+
return value === true ? "Yes" : value === false ? "No" : "Unknown";
|
|
6312
|
+
}
|
|
6313
|
+
function formatPath(value) {
|
|
6314
|
+
return typeof value === "string" && value.trim() !== "" ? `\`${value.trim()}\`` : "Unknown";
|
|
6315
|
+
}
|
|
6316
|
+
function formatPathList(paths) {
|
|
6317
|
+
return paths.length > 0 ? paths.map((path9) => `\`${path9}\``).join(", ") : "None";
|
|
6318
|
+
}
|
|
6319
|
+
function formatCount(count) {
|
|
6320
|
+
return count === 0 ? "None" : String(count);
|
|
6321
|
+
}
|
|
6322
|
+
function formatTableCell(value) {
|
|
6323
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim().replace(/\s+/g, " ").replace(/\|/g, "\\|") : "Unknown";
|
|
6324
|
+
}
|
|
6325
|
+
async function performCleanupAndClosure(options, pr) {
|
|
6326
|
+
await options.issueProvider.removeLabel(
|
|
6327
|
+
options.issue.number,
|
|
6328
|
+
options.labels.agentInProgress
|
|
6329
|
+
);
|
|
6330
|
+
await options.issueProvider.removeLabel(
|
|
6331
|
+
options.issue.number,
|
|
6332
|
+
options.labels.prOpenAwaitingMerge
|
|
6333
|
+
);
|
|
6334
|
+
let childCloseSucceeded = false;
|
|
6335
|
+
try {
|
|
6336
|
+
await options.issueProvider.closeIssue(options.issue.number);
|
|
6337
|
+
childCloseSucceeded = true;
|
|
6338
|
+
} catch (error) {
|
|
6339
|
+
options.logger.step(
|
|
6340
|
+
"warn",
|
|
6341
|
+
`Issue #${options.issue.number} could not be closed: ${error instanceof Error ? error.message : String(error)}`
|
|
6205
6342
|
);
|
|
6206
6343
|
}
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
});
|
|
6213
|
-
if (!validation.ok) {
|
|
6214
|
-
throw new Error(
|
|
6215
|
-
`Issue Final Review artifact validation failed: ${validation.reason}`
|
|
6344
|
+
if (childCloseSucceeded) {
|
|
6345
|
+
await maybeCloseParentAfterChildCompletion(
|
|
6346
|
+
options.issueProvider,
|
|
6347
|
+
options.issue.number,
|
|
6348
|
+
options.logger
|
|
6216
6349
|
);
|
|
6217
6350
|
}
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
if (
|
|
6228
|
-
|
|
6229
|
-
worktreePath,
|
|
6230
|
-
baseRef,
|
|
6231
|
-
declaredChangedPaths: extractChangedPaths(parsed),
|
|
6232
|
-
logger
|
|
6233
|
-
});
|
|
6234
|
-
updateWorktreeRunState(worktreePath, {
|
|
6235
|
-
issueFinalReview: {
|
|
6236
|
-
completed: true,
|
|
6237
|
-
verdict: "pass",
|
|
6238
|
-
artifactPath,
|
|
6239
|
-
selfRetouched: parsed.selfRetouched === true,
|
|
6240
|
-
changedPaths,
|
|
6241
|
-
verificationPassed: parsed.verification?.passed === true
|
|
6242
|
-
}
|
|
6243
|
-
});
|
|
6244
|
-
return {
|
|
6245
|
-
status: "passed",
|
|
6246
|
-
verdict: "pass",
|
|
6247
|
-
artifactPath,
|
|
6248
|
-
selfRetouched: parsed.selfRetouched === true,
|
|
6249
|
-
changedPaths,
|
|
6250
|
-
verificationPassed: parsed.verification?.passed === true
|
|
6251
|
-
};
|
|
6252
|
-
}
|
|
6253
|
-
if (verdict === "needs_human_review") {
|
|
6254
|
-
return {
|
|
6255
|
-
status: "needs_human_review",
|
|
6256
|
-
verdict: "needs_human_review",
|
|
6257
|
-
artifactPath,
|
|
6258
|
-
needsHumanReason: parsed.needsHumanReason,
|
|
6259
|
-
selfRetouched: parsed.selfRetouched === true,
|
|
6260
|
-
changedPaths: extractChangedPaths(parsed)
|
|
6261
|
-
};
|
|
6351
|
+
return {
|
|
6352
|
+
status: "merged",
|
|
6353
|
+
prNumber: pr.number,
|
|
6354
|
+
prUrl: pr.url,
|
|
6355
|
+
prTitle: options.prTitle,
|
|
6356
|
+
prBody: options.prBody
|
|
6357
|
+
};
|
|
6358
|
+
}
|
|
6359
|
+
function assertRecordedMergedPrDetails(state) {
|
|
6360
|
+
if (typeof state.pr?.number === "number" && typeof state.pr.url === "string") {
|
|
6361
|
+
return;
|
|
6262
6362
|
}
|
|
6263
6363
|
throw new Error(
|
|
6264
|
-
|
|
6364
|
+
"Lifecycle invariant: resumed merged GitHub Issue Publication requires recorded PR number and URL"
|
|
6365
|
+
);
|
|
6366
|
+
}
|
|
6367
|
+
function makeIssueTransitions(issueProvider, labels) {
|
|
6368
|
+
return createIssueTransitions(
|
|
6369
|
+
{
|
|
6370
|
+
fetchIssue: issueProvider.fetchIssue.bind(issueProvider),
|
|
6371
|
+
addLabels: issueProvider.addLabels.bind(issueProvider),
|
|
6372
|
+
removeLabel: issueProvider.removeLabel.bind(issueProvider),
|
|
6373
|
+
closeIssue: issueProvider.closeIssue.bind(issueProvider)
|
|
6374
|
+
},
|
|
6375
|
+
{
|
|
6376
|
+
blocked: labels.blocked,
|
|
6377
|
+
readyForAgent: labels.readyForAgent,
|
|
6378
|
+
needsTriage: labels.needsTriage,
|
|
6379
|
+
agentInProgress: labels.agentInProgress,
|
|
6380
|
+
readyForHuman: labels.readyForHuman,
|
|
6381
|
+
prOpenAwaitingMerge: labels.prOpenAwaitingMerge
|
|
6382
|
+
}
|
|
6265
6383
|
);
|
|
6266
6384
|
}
|
|
6267
|
-
async function
|
|
6268
|
-
const {
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
throw new Error(
|
|
6278
|
-
`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.`
|
|
6279
|
-
);
|
|
6280
|
-
}
|
|
6281
|
-
}
|
|
6282
|
-
async function syncRemoteBaseRef2(worktreePath, baseRef, logger) {
|
|
6283
|
-
const remoteBase = parseRemoteBaseRef2(baseRef);
|
|
6284
|
-
if (!remoteBase) {
|
|
6385
|
+
async function guardFinalPublishContent(options) {
|
|
6386
|
+
const {
|
|
6387
|
+
worktreePath,
|
|
6388
|
+
baseRef,
|
|
6389
|
+
title,
|
|
6390
|
+
body,
|
|
6391
|
+
secretLikeContentDetectionEnabled,
|
|
6392
|
+
logger
|
|
6393
|
+
} = options;
|
|
6394
|
+
if (!secretLikeContentDetectionEnabled) {
|
|
6285
6395
|
return;
|
|
6286
6396
|
}
|
|
6287
|
-
await execCapture("git", ["
|
|
6397
|
+
const finalDiff = await execCapture("git", ["diff", `${baseRef}...HEAD`], {
|
|
6288
6398
|
cwd: worktreePath,
|
|
6289
6399
|
logger,
|
|
6290
|
-
label: "git
|
|
6400
|
+
label: "git diff final secret guard"
|
|
6291
6401
|
});
|
|
6402
|
+
assertNoSecretLikeContent([
|
|
6403
|
+
{
|
|
6404
|
+
source: "final diff",
|
|
6405
|
+
content: extractAddedDiffContent(finalDiff.stdout)
|
|
6406
|
+
},
|
|
6407
|
+
{ source: "PR title", content: title },
|
|
6408
|
+
{ source: "PR body", content: body }
|
|
6409
|
+
]);
|
|
6292
6410
|
}
|
|
6293
|
-
function
|
|
6294
|
-
|
|
6295
|
-
const branch = branchParts.join("/");
|
|
6296
|
-
if (!remote || !branch) {
|
|
6297
|
-
return null;
|
|
6298
|
-
}
|
|
6299
|
-
return { remote, branch };
|
|
6411
|
+
function extractAddedDiffContent(diff) {
|
|
6412
|
+
return diff.split(/\r?\n/).filter((line) => line.startsWith("+") && !line.startsWith("+++")).map((line) => line.slice(1)).join("\n");
|
|
6300
6413
|
}
|
|
6301
|
-
|
|
6302
|
-
const
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
if (
|
|
6306
|
-
return
|
|
6414
|
+
function assertNoSecretLikeContent(inputs) {
|
|
6415
|
+
const findings = inputs.flatMap(
|
|
6416
|
+
({ source, content }) => findSecretLikeContent(source, content ?? "")
|
|
6417
|
+
);
|
|
6418
|
+
if (findings.length === 0) {
|
|
6419
|
+
return;
|
|
6307
6420
|
}
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
`
|
|
6421
|
+
const summary = findings.slice(0, 5).map((finding) => `${finding.source}: ${finding.kind}`).join("; ");
|
|
6422
|
+
throw new Error(
|
|
6423
|
+
`Secret-like content detected before finalization (${summary}). Redact it before committing, pushing, or creating a PR.`
|
|
6311
6424
|
);
|
|
6312
|
-
options.logger.kv("MISSING_CHANGED_FILES", missing.join(", "));
|
|
6313
|
-
return actualChangedPaths;
|
|
6314
6425
|
}
|
|
6315
|
-
|
|
6316
|
-
const
|
|
6317
|
-
|
|
6318
|
-
["diff-index", "--name-only", options.baseRef, "--"],
|
|
6426
|
+
function findSecretLikeContent(source, content) {
|
|
6427
|
+
const findings = [];
|
|
6428
|
+
const patterns = [
|
|
6319
6429
|
{
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
}
|
|
6324
|
-
);
|
|
6325
|
-
const untrackedFiles = await execCapture(
|
|
6326
|
-
"git",
|
|
6327
|
-
["ls-files", "--others", "--exclude-standard"],
|
|
6430
|
+
kind: "private key block",
|
|
6431
|
+
regex: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/gi
|
|
6432
|
+
},
|
|
6328
6433
|
{
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
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
|
|
6332
6451
|
}
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
function parseGitPathList2(output) {
|
|
6342
|
-
return output.split(/\r?\n/).map(normalizeGitPath2).filter((path9) => path9.length > 0);
|
|
6343
|
-
}
|
|
6344
|
-
function normalizeGitPath2(path9) {
|
|
6345
|
-
return path9.replace(/^"|"$/g, "").replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
6346
|
-
}
|
|
6347
|
-
function isIssueFinalReviewAuditedPath2(path9) {
|
|
6348
|
-
return path9 !== "" && path9 !== WORKTREE_RUN_STATE_PATH && !path9.startsWith(".pourkit/.tmp/") && !path9.startsWith(".pourkit/logs/");
|
|
6349
|
-
}
|
|
6350
|
-
function extractChangedPaths(parsed) {
|
|
6351
|
-
return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
|
|
6352
|
-
(file) => file && typeof file === "object" ? file.path : void 0
|
|
6353
|
-
).filter((path9) => typeof path9 === "string") : [];
|
|
6354
|
-
}
|
|
6355
|
-
function loadIssueFinalReviewPrompt(options) {
|
|
6356
|
-
const {
|
|
6357
|
-
repoRoot: repoRoot2,
|
|
6358
|
-
promptTemplate,
|
|
6359
|
-
validationCommand,
|
|
6360
|
-
reviewArtifactPath,
|
|
6361
|
-
reviewVerdict
|
|
6362
|
-
} = options;
|
|
6363
|
-
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
6364
|
-
const promptBody = existsSync12(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
|
|
6365
|
-
let prompt = appendProtectedWorkGuidance(`${promptBody}
|
|
6366
|
-
|
|
6367
|
-
## Shared Run Context
|
|
6368
|
-
|
|
6369
|
-
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}
|
|
6370
|
-
|
|
6371
|
-
## Initial Verification Pass
|
|
6372
|
-
|
|
6373
|
-
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
6374
|
-
- Before reviewing code, diffs, artifacts, or prior findings, run the verification wrapper from the Worktree.
|
|
6375
|
-
- Do not substitute direct underlying commands unless no wrapper path can run; if you must use direct commands, explain why in \`verification.summary\`.
|
|
6376
|
-
- If a configured verification command fails because of fixable Issue work, self-retouch the Worktree, then rerun all configured verification commands.
|
|
6377
|
-
- Do not return \`pass\` while any configured verification command is failing.
|
|
6378
|
-
- 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.
|
|
6379
|
-
- If no verification commands are configured, note that and proceed with normal review.
|
|
6380
|
-
|
|
6381
|
-
## Changed Files Audit
|
|
6382
|
-
|
|
6383
|
-
- Read the Branch section of ${RUN_CONTEXT_PATH_IN_WORKTREE} and use its Canonical Base Ref for the changed-file inventory.
|
|
6384
|
-
- Before writing \`changedFiles\`, run \`git diff-index --name-only <canonical-base-ref> --\` from the Worktree to enumerate tracked files changed by the Issue.
|
|
6385
|
-
- Also run \`git ls-files --others --exclude-standard\` from the Worktree to enumerate untracked files changed by the Issue.
|
|
6386
|
-
- Treat the union of those command outputs, excluding only \`.pourkit/.tmp/**\`, \`.pourkit/logs/**\`, and \`.pourkit/state.json\`, as the required \`changedFiles\` inventory.
|
|
6387
|
-
- Include every file from that inventory in \`changedFiles\`, including generated files, managed operating assets, docs, prompts, schema files, and large batches.
|
|
6388
|
-
- 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.
|
|
6389
|
-
- Do not return \`pass\` until \`changedFiles\` covers the full inventory.
|
|
6390
|
-
|
|
6391
|
-
## Required Artifact Validation
|
|
6392
|
-
|
|
6393
|
-
- Before handoff, run exactly: \`${validationCommand}\`
|
|
6394
|
-
- This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
|
|
6395
|
-
- If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
|
|
6396
|
-
- Do not finish until the validation command passes.`);
|
|
6397
|
-
if (reviewVerdict) {
|
|
6398
|
-
prompt += `
|
|
6399
|
-
|
|
6400
|
-
## Prior Review Context
|
|
6401
|
-
|
|
6402
|
-
- Reviewer Artifact Path: ${reviewArtifactPath}
|
|
6403
|
-
- Prior Review Verdict: ${reviewVerdict}`;
|
|
6404
|
-
if (reviewVerdict === "PASS_WITH_NOTES") {
|
|
6405
|
-
prompt += `
|
|
6406
|
-
- This Issue passed the prior Reviewer with notes. Read the Reviewer Artifact and inspect its notes before making your final pass decision.`;
|
|
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;
|
|
6407
6460
|
}
|
|
6408
6461
|
}
|
|
6409
|
-
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);
|
|
6410
6478
|
}
|
|
6411
6479
|
|
|
6412
6480
|
// issues/issue-worktree-resolution.ts
|
|
@@ -6787,9 +6855,11 @@ async function completeIssueRun(options) {
|
|
|
6787
6855
|
checksCompletionTimeoutMs: config.checks.checksCompletionTimeoutSeconds * 1e3,
|
|
6788
6856
|
pollIntervalMs: config.checks.pollIntervalSeconds * 1e3
|
|
6789
6857
|
};
|
|
6790
|
-
const
|
|
6858
|
+
const readCurrentWorktreeState = () => executionResult.worktreePath ? readWorktreeRunState(executionResult.worktreePath) ?? worktreeState : worktreeState;
|
|
6859
|
+
const ifrState = readCurrentWorktreeState();
|
|
6791
6860
|
assertIssueFinalReviewPassed(ifrState);
|
|
6792
|
-
|
|
6861
|
+
const stateBeforeCompletion = readCurrentWorktreeState();
|
|
6862
|
+
if (executionResult.worktreePath && !stateBeforeCompletion?.finalCommit?.completed && !stateBeforeCompletion?.pr?.created && !await hasWorktreeChanges(
|
|
6793
6863
|
executionResult.worktreePath,
|
|
6794
6864
|
`origin/${effectiveBaseBranch}`,
|
|
6795
6865
|
logger
|
|
@@ -6806,92 +6876,33 @@ async function completeIssueRun(options) {
|
|
|
6806
6876
|
noOp: true
|
|
6807
6877
|
};
|
|
6808
6878
|
}
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
finalizerFromState.artifactPath,
|
|
6826
|
-
"utf-8"
|
|
6827
|
-
);
|
|
6828
|
-
const parsed = parsePrDescription(artifactContent);
|
|
6829
|
-
prTitle = parsed.title;
|
|
6830
|
-
prBody = parsed.body;
|
|
6831
|
-
} else {
|
|
6832
|
-
throw new FinalizerFailure({
|
|
6833
|
-
message: "Finalizer state is incomplete: missing title, body, and artifactPath"
|
|
6834
|
-
});
|
|
6835
|
-
}
|
|
6836
|
-
} catch (error) {
|
|
6837
|
-
throw error;
|
|
6838
|
-
}
|
|
6839
|
-
} else {
|
|
6840
|
-
try {
|
|
6841
|
-
finalizerResult = await runEffectAndMapExit(
|
|
6842
|
-
runFinalizerAgent({
|
|
6843
|
-
executionProvider,
|
|
6844
|
-
config,
|
|
6845
|
-
target,
|
|
6846
|
-
issue,
|
|
6847
|
-
builderBranch: branchName,
|
|
6848
|
-
targetBaseBranch: effectiveBaseBranch,
|
|
6849
|
-
worktreePath: executionResult.worktreePath,
|
|
6850
|
-
reviewArtifactPath,
|
|
6851
|
-
repoRoot: ROOT,
|
|
6852
|
-
logger,
|
|
6853
|
-
...startResult.memory ? { memory: startResult.memory } : {}
|
|
6854
|
-
})
|
|
6855
|
-
);
|
|
6856
|
-
} catch (error) {
|
|
6857
|
-
throw error;
|
|
6858
|
-
}
|
|
6859
|
-
prTitle = finalizerResult.title;
|
|
6860
|
-
prBody = finalizerResult.body;
|
|
6861
|
-
if (executionResult.worktreePath) {
|
|
6862
|
-
await assertCanonicalBaseAncestor2({
|
|
6863
|
-
worktreePath: executionResult.worktreePath,
|
|
6864
|
-
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6865
|
-
stageName: "Finalizer",
|
|
6866
|
-
logger
|
|
6867
|
-
});
|
|
6868
|
-
}
|
|
6869
|
-
}
|
|
6870
|
-
prTitle = ensureConventionalPrTitle(
|
|
6871
|
-
prTitle,
|
|
6872
|
-
executionResult.commits.join("\n")
|
|
6873
|
-
);
|
|
6874
|
-
const finalBody = ensureClosingRefs(
|
|
6875
|
-
prBody ?? `Closes #${issue.number}`,
|
|
6876
|
-
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
|
+
})
|
|
6877
6895
|
);
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
artifactPath: finalizerResult.artifactPath,
|
|
6883
|
-
title: prTitle,
|
|
6884
|
-
body: prBody
|
|
6885
|
-
}
|
|
6886
|
-
});
|
|
6887
|
-
}
|
|
6888
|
-
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;
|
|
6889
6900
|
if (!finalCommitFromState) {
|
|
6890
6901
|
await finalizeWorktreeCommit({
|
|
6891
6902
|
worktreePath: executionResult.worktreePath,
|
|
6892
6903
|
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6893
6904
|
title: prTitle,
|
|
6894
|
-
body:
|
|
6905
|
+
body: prBody,
|
|
6895
6906
|
logger
|
|
6896
6907
|
});
|
|
6897
6908
|
if (executionResult.worktreePath) {
|
|
@@ -6919,9 +6930,9 @@ async function completeIssueRun(options) {
|
|
|
6919
6930
|
baseBranch: effectiveBaseBranch,
|
|
6920
6931
|
autoMerge: target.autoMerge !== false,
|
|
6921
6932
|
worktreePath: executionResult.worktreePath,
|
|
6922
|
-
worktreeState,
|
|
6933
|
+
worktreeState: readCurrentWorktreeState(),
|
|
6923
6934
|
prTitle,
|
|
6924
|
-
prBody
|
|
6935
|
+
prBody,
|
|
6925
6936
|
issueProvider,
|
|
6926
6937
|
prProvider,
|
|
6927
6938
|
logger,
|
|
@@ -7369,7 +7380,7 @@ import { join as join16 } from "path";
|
|
|
7369
7380
|
import {
|
|
7370
7381
|
existsSync as existsSync14,
|
|
7371
7382
|
mkdirSync as mkdirSync7,
|
|
7372
|
-
readFileSync as
|
|
7383
|
+
readFileSync as readFileSync15,
|
|
7373
7384
|
readdirSync as readdirSync3,
|
|
7374
7385
|
writeFileSync as writeFileSync4
|
|
7375
7386
|
} from "fs";
|
|
@@ -7462,12 +7473,12 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7462
7473
|
return { record: null, diagnostics: [] };
|
|
7463
7474
|
}
|
|
7464
7475
|
try {
|
|
7465
|
-
const raw = JSON.parse(
|
|
7476
|
+
const raw = JSON.parse(readFileSync15(recordPath, "utf-8"));
|
|
7466
7477
|
const parsed = PrdRunRecordSchema.parse(raw);
|
|
7467
7478
|
return { record: parsed, diagnostics: [] };
|
|
7468
7479
|
} catch (error) {
|
|
7469
7480
|
try {
|
|
7470
|
-
const raw = JSON.parse(
|
|
7481
|
+
const raw = JSON.parse(readFileSync15(recordPath, "utf-8"));
|
|
7471
7482
|
if (raw && typeof raw === "object" && raw.start && typeof raw.start === "object" && raw.start.startBaseBranch === void 0) {
|
|
7472
7483
|
return {
|
|
7473
7484
|
record: raw,
|
|
@@ -7501,7 +7512,7 @@ function listPrdRuns(repoRoot2) {
|
|
|
7501
7512
|
const recordPath = join15(stateDir, entry.name);
|
|
7502
7513
|
try {
|
|
7503
7514
|
const record = PrdRunRecordSchema.parse(
|
|
7504
|
-
JSON.parse(
|
|
7515
|
+
JSON.parse(readFileSync15(recordPath, "utf-8"))
|
|
7505
7516
|
);
|
|
7506
7517
|
records.push(record);
|
|
7507
7518
|
} catch (error) {
|
|
@@ -9284,7 +9295,7 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9284
9295
|
}
|
|
9285
9296
|
|
|
9286
9297
|
// prd-run/final-review-validation.ts
|
|
9287
|
-
import { existsSync as existsSync15, readFileSync as
|
|
9298
|
+
import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
|
|
9288
9299
|
|
|
9289
9300
|
// commands/prd-run.ts
|
|
9290
9301
|
async function runPrdRunLaunchCommand(options) {
|
|
@@ -12294,7 +12305,7 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
|
|
|
12294
12305
|
}
|
|
12295
12306
|
|
|
12296
12307
|
// commands/memory-init.ts
|
|
12297
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as
|
|
12308
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
|
|
12298
12309
|
import { join as join17 } from "path";
|
|
12299
12310
|
async function runMemoryInitCommand(options) {
|
|
12300
12311
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -12305,7 +12316,7 @@ async function runMemoryInitCommand(options) {
|
|
|
12305
12316
|
message: "No .pourkit/config.json found. Run `pourkit init` first to initialize Pourkit, then run `pourkit memory init`."
|
|
12306
12317
|
};
|
|
12307
12318
|
}
|
|
12308
|
-
const raw = JSON.parse(
|
|
12319
|
+
const raw = JSON.parse(readFileSync17(configPath, "utf8"));
|
|
12309
12320
|
let alreadyEnabled = false;
|
|
12310
12321
|
if (Object.prototype.hasOwnProperty.call(raw, "memory")) {
|
|
12311
12322
|
const m = raw.memory;
|
|
@@ -12362,7 +12373,7 @@ ${content}${MANAGED_BLOCK_END}
|
|
|
12362
12373
|
writeFileSync5(filePath, blockContent);
|
|
12363
12374
|
return;
|
|
12364
12375
|
}
|
|
12365
|
-
const existing =
|
|
12376
|
+
const existing = readFileSync17(filePath, "utf8");
|
|
12366
12377
|
const beginIdx = existing.indexOf(MANAGED_BLOCK_BEGIN);
|
|
12367
12378
|
const endIdx = existing.indexOf(MANAGED_BLOCK_END);
|
|
12368
12379
|
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
|
|
@@ -12491,7 +12502,7 @@ async function runSerenaStatusCommand(options) {
|
|
|
12491
12502
|
}
|
|
12492
12503
|
|
|
12493
12504
|
// commands/config-schema.ts
|
|
12494
|
-
import { readFileSync as
|
|
12505
|
+
import { readFileSync as readFileSync18, existsSync as existsSync18 } from "fs";
|
|
12495
12506
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile2, readdir as readdir2 } from "fs/promises";
|
|
12496
12507
|
import { resolve as resolve3, dirname as dirname5, relative as relative2 } from "path";
|
|
12497
12508
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -12512,7 +12523,7 @@ var _schemaValidator = null;
|
|
|
12512
12523
|
var _schemaErrors = null;
|
|
12513
12524
|
function getSchemaValidator() {
|
|
12514
12525
|
if (!_schemaValidator) {
|
|
12515
|
-
const schema = JSON.parse(
|
|
12526
|
+
const schema = JSON.parse(readFileSync18(PACKAGED_SCHEMA_PATH, "utf-8"));
|
|
12516
12527
|
const ajv = new Ajv2({ strict: true });
|
|
12517
12528
|
ajv.addKeyword("x-pourkit-schema-version");
|
|
12518
12529
|
const validate = ajv.compile(schema);
|
|
@@ -12526,7 +12537,7 @@ function getSchemaValidator() {
|
|
|
12526
12537
|
return _schemaValidator;
|
|
12527
12538
|
}
|
|
12528
12539
|
function readPackagedHash() {
|
|
12529
|
-
return
|
|
12540
|
+
return readFileSync18(PACKAGED_HASH_PATH, "utf-8");
|
|
12530
12541
|
}
|
|
12531
12542
|
function localSchemaDir(repoRoot2) {
|
|
12532
12543
|
return resolve3(repoRoot2, ".pourkit/schema");
|
|
@@ -13082,7 +13093,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
13082
13093
|
detail: icmPath !== null ? icmPath : "icm not found on PATH"
|
|
13083
13094
|
});
|
|
13084
13095
|
const gitignorePath = resolve3(cwd, ".gitignore");
|
|
13085
|
-
const gitignoreContent = existsSync18(gitignorePath) ?
|
|
13096
|
+
const gitignoreContent = existsSync18(gitignorePath) ? readFileSync18(gitignorePath, "utf-8") : "";
|
|
13086
13097
|
const hasGitignoreEntry = gitignoreContent.includes(".pourkit/icm/");
|
|
13087
13098
|
checks.push({
|
|
13088
13099
|
name: "icm-gitignore",
|
|
@@ -13131,7 +13142,7 @@ async function runDoctorCommand(options) {
|
|
|
13131
13142
|
let rawMemoryConfig;
|
|
13132
13143
|
if (existsSync18(configPath)) {
|
|
13133
13144
|
try {
|
|
13134
|
-
const raw = JSON.parse(
|
|
13145
|
+
const raw = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
13135
13146
|
rawMemoryConfig = raw.memory;
|
|
13136
13147
|
const validate = getSchemaValidator();
|
|
13137
13148
|
const valid2 = validate(raw);
|
|
@@ -14370,7 +14381,7 @@ import path7 from "path";
|
|
|
14370
14381
|
|
|
14371
14382
|
// execution/sandbox-image.ts
|
|
14372
14383
|
import { createHash as createHash2 } from "crypto";
|
|
14373
|
-
import { existsSync as existsSync20, readFileSync as
|
|
14384
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
|
|
14374
14385
|
import path6 from "path";
|
|
14375
14386
|
function sandboxImageName(repoRoot2, installIcm) {
|
|
14376
14387
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
@@ -14381,7 +14392,7 @@ function sandboxImageName(repoRoot2, installIcm) {
|
|
|
14381
14392
|
const base2 = `sandcastle:${baseName}`;
|
|
14382
14393
|
return installIcm ? `${base2}-icm` : base2;
|
|
14383
14394
|
}
|
|
14384
|
-
const fingerprint = createHash2("sha256").update(
|
|
14395
|
+
const fingerprint = createHash2("sha256").update(readFileSync19(dockerfilePath)).digest("hex").slice(0, 8);
|
|
14385
14396
|
const base = `sandcastle:${baseName}-${fingerprint}`;
|
|
14386
14397
|
return installIcm ? `${base}-icm` : base;
|
|
14387
14398
|
}
|
|
@@ -15532,11 +15543,11 @@ function createCliProgram(version) {
|
|
|
15532
15543
|
return program;
|
|
15533
15544
|
}
|
|
15534
15545
|
async function resolveCliVersion() {
|
|
15535
|
-
if (isPackageVersion("0.0.0-next-
|
|
15536
|
-
return "0.0.0-next-
|
|
15546
|
+
if (isPackageVersion("0.0.0-next-20260622235247")) {
|
|
15547
|
+
return "0.0.0-next-20260622235247";
|
|
15537
15548
|
}
|
|
15538
|
-
if (isReleaseVersion("0.0.0-next-
|
|
15539
|
-
return "0.0.0-next-
|
|
15549
|
+
if (isReleaseVersion("0.0.0-next-20260622235247")) {
|
|
15550
|
+
return "0.0.0-next-20260622235247";
|
|
15540
15551
|
}
|
|
15541
15552
|
try {
|
|
15542
15553
|
const root = repoRoot();
|