@pourkit/cli 0.0.0-next-20260622201317 → 0.0.0-next-20260623021955
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 +893 -908
- 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/dist/managed/skills/grill-with-docs/SKILL.md +19 -0
- package/dist/managed/skills/publish-prd/SKILL.md +105 -18
- package/dist/managed/skills/to-oneshot/SKILL.md +153 -0
- 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
|
}
|
|
@@ -5859,554 +6246,235 @@ function buildIssueFinalReviewPrComment(artifact) {
|
|
|
5859
6246
|
lines.push(`**Verdict:** ${formatLabel(artifact.verdict)}`, "");
|
|
5860
6247
|
}
|
|
5861
6248
|
lines.push("**Summary**", "", artifact.summary, "");
|
|
5862
|
-
if (artifact.needsHumanReason) {
|
|
5863
|
-
lines.push("### Human Review Needed", "", artifact.needsHumanReason, "");
|
|
5864
|
-
}
|
|
5865
|
-
if (artifact.verification) {
|
|
5866
|
-
lines.push("### Verification", "");
|
|
5867
|
-
lines.push(
|
|
5868
|
-
`- **Required:** ${formatBoolean(artifact.verification.required)}`,
|
|
5869
|
-
`- **Passed:** ${formatBoolean(artifact.verification.passed)}`
|
|
5870
|
-
);
|
|
5871
|
-
const commands = Array.isArray(artifact.verification.commands) ? artifact.verification.commands.filter(
|
|
5872
|
-
(command) => typeof command === "string"
|
|
5873
|
-
) : [];
|
|
5874
|
-
lines.push(
|
|
5875
|
-
`- **Commands:** ${commands.length > 0 ? commands.map((command) => `\`${command}\``).join(", ") : "None"}`
|
|
5876
|
-
);
|
|
5877
|
-
if (typeof artifact.verification.summary === "string" && artifact.verification.summary.trim() !== "") {
|
|
5878
|
-
lines.push(`- **Notes:** ${artifact.verification.summary.trim()}`);
|
|
5879
|
-
}
|
|
5880
|
-
lines.push("");
|
|
5881
|
-
}
|
|
5882
|
-
if (artifact.changedFiles) {
|
|
5883
|
-
lines.push("### Changed Files", "");
|
|
5884
|
-
if (artifact.changedFiles.length === 0) {
|
|
5885
|
-
lines.push("No changed files reported.", "");
|
|
5886
|
-
} else {
|
|
5887
|
-
lines.push(
|
|
5888
|
-
"| File | Scope | Revert Flag | Change |",
|
|
5889
|
-
"| --- | --- | --- | --- |"
|
|
5890
|
-
);
|
|
5891
|
-
for (const file of artifact.changedFiles) {
|
|
5892
|
-
lines.push(
|
|
5893
|
-
`| ${formatPath(file.path)} | ${formatLabel(file.scopeVerdict)} | ${formatBoolean(file.revertFlag)} | ${formatTableCell(file.change)} |`
|
|
5894
|
-
);
|
|
5895
|
-
}
|
|
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;
|
|
6073
|
-
}
|
|
6074
|
-
}
|
|
6075
|
-
return findings;
|
|
6076
|
-
}
|
|
6077
|
-
function isAllowedSecretPlaceholder(value) {
|
|
6078
|
-
const normalized = value.toLowerCase();
|
|
6079
|
-
return [
|
|
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"
|
|
6121
|
-
);
|
|
6249
|
+
if (artifact.needsHumanReason) {
|
|
6250
|
+
lines.push("### Human Review Needed", "", artifact.needsHumanReason, "");
|
|
6251
|
+
}
|
|
6252
|
+
if (artifact.verification) {
|
|
6253
|
+
lines.push("### Verification", "");
|
|
6254
|
+
lines.push(
|
|
6255
|
+
`- **Required:** ${formatBoolean(artifact.verification.required)}`,
|
|
6256
|
+
`- **Passed:** ${formatBoolean(artifact.verification.passed)}`
|
|
6257
|
+
);
|
|
6258
|
+
const commands = Array.isArray(artifact.verification.commands) ? artifact.verification.commands.filter(
|
|
6259
|
+
(command) => typeof command === "string"
|
|
6260
|
+
) : [];
|
|
6261
|
+
lines.push(
|
|
6262
|
+
`- **Commands:** ${commands.length > 0 ? commands.map((command) => `\`${command}\``).join(", ") : "None"}`
|
|
6263
|
+
);
|
|
6264
|
+
if (typeof artifact.verification.summary === "string" && artifact.verification.summary.trim() !== "") {
|
|
6265
|
+
lines.push(`- **Notes:** ${artifact.verification.summary.trim()}`);
|
|
6122
6266
|
}
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
}
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6267
|
+
lines.push("");
|
|
6268
|
+
}
|
|
6269
|
+
if (artifact.changedFiles) {
|
|
6270
|
+
lines.push("### Changed Files", "");
|
|
6271
|
+
if (artifact.changedFiles.length === 0) {
|
|
6272
|
+
lines.push("No changed files reported.", "");
|
|
6273
|
+
} else {
|
|
6274
|
+
lines.push(
|
|
6275
|
+
"| File | Scope | Revert Flag | Change |",
|
|
6276
|
+
"| --- | --- | --- | --- |"
|
|
6133
6277
|
);
|
|
6278
|
+
for (const file of artifact.changedFiles) {
|
|
6279
|
+
lines.push(
|
|
6280
|
+
`| ${formatPath(file.path)} | ${formatLabel(file.scopeVerdict)} | ${formatBoolean(file.revertFlag)} | ${formatTableCell(file.change)} |`
|
|
6281
|
+
);
|
|
6282
|
+
}
|
|
6283
|
+
lines.push("");
|
|
6134
6284
|
}
|
|
6135
|
-
return {
|
|
6136
|
-
status: "skipped",
|
|
6137
|
-
verdict: "pass",
|
|
6138
|
-
artifactPath: cachedArtifactPath,
|
|
6139
|
-
selfRetouched: ifrFromState.selfRetouched ?? false,
|
|
6140
|
-
changedPaths: ifrFromState.changedPaths ?? [],
|
|
6141
|
-
verificationPassed: ifrFromState.verificationPassed ?? false
|
|
6142
|
-
};
|
|
6143
6285
|
}
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
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
|
|
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)}`
|
|
6291
|
+
);
|
|
6187
6292
|
}
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
)
|
|
6293
|
+
if (artifact.outOfScopeFiles) {
|
|
6294
|
+
lines.push(
|
|
6295
|
+
`- **Out-of-scope files:** ${formatPathList(artifact.outOfScopeFiles)}`
|
|
6296
|
+
);
|
|
6297
|
+
}
|
|
6298
|
+
if (artifact.badReverts) {
|
|
6299
|
+
lines.push(
|
|
6300
|
+
`- **Bad reverts:** ${formatCount(artifact.badReverts.length)}`
|
|
6301
|
+
);
|
|
6302
|
+
}
|
|
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"
|
|
6265
6365
|
);
|
|
6266
6366
|
}
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
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
|
+
}
|
|
6383
|
+
);
|
|
6281
6384
|
}
|
|
6282
|
-
async function
|
|
6283
|
-
const
|
|
6284
|
-
|
|
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) {
|
|
@@ -9907,6 +9918,7 @@ var BASELINE_MANAGED_SKILLS = [
|
|
|
9907
9918
|
"ship-current-changes",
|
|
9908
9919
|
"tdd",
|
|
9909
9920
|
"to-issues",
|
|
9921
|
+
"to-oneshot",
|
|
9910
9922
|
"to-prd",
|
|
9911
9923
|
"triage",
|
|
9912
9924
|
"write-a-skill",
|
|
@@ -10136,21 +10148,13 @@ var BASELINE_SKILL_NAMES = [
|
|
|
10136
10148
|
"ship-current-changes",
|
|
10137
10149
|
"tdd",
|
|
10138
10150
|
"to-issues",
|
|
10151
|
+
"to-oneshot",
|
|
10139
10152
|
"to-prd",
|
|
10140
10153
|
"triage",
|
|
10141
10154
|
"write-a-skill",
|
|
10142
10155
|
"zoom-out"
|
|
10143
10156
|
];
|
|
10144
10157
|
var NO_TOKEN_LABEL_PROVISIONING_WARNING = "Skipped GitHub label provisioning because no GitHub token was provided.";
|
|
10145
|
-
var ALLOWED_VERIFICATION_SCRIPTS = [
|
|
10146
|
-
"typecheck",
|
|
10147
|
-
"lint",
|
|
10148
|
-
"test",
|
|
10149
|
-
"test:agent",
|
|
10150
|
-
"build",
|
|
10151
|
-
"check",
|
|
10152
|
-
"prettier:check"
|
|
10153
|
-
];
|
|
10154
10158
|
var DEFAULT_RUNNER_LABELS = {
|
|
10155
10159
|
readyForAgent: "ready-for-agent",
|
|
10156
10160
|
agentInProgress: "agent-in-progress",
|
|
@@ -10251,64 +10255,63 @@ function resolveCanonicalLabels(runnerLabels) {
|
|
|
10251
10255
|
return { ...meta, name };
|
|
10252
10256
|
});
|
|
10253
10257
|
}
|
|
10254
|
-
function inferVerificationCommands(scripts, pm) {
|
|
10255
|
-
const pmPrefix = pm === "npm" ? "npm run" : pm === "pnpm" ? "pnpm run" : pm === "yarn" ? "yarn" : pm === "bun" ? "bun run" : "npm run";
|
|
10256
|
-
const allowlist = ALLOWED_VERIFICATION_SCRIPTS;
|
|
10257
|
-
const matched = allowlist.filter((s) => s in scripts);
|
|
10258
|
-
const commands = [];
|
|
10259
|
-
for (const script of matched) {
|
|
10260
|
-
if (script === "test" && matched.includes("test:agent")) continue;
|
|
10261
|
-
commands.push({ label: script, command: `${pmPrefix} ${script}` });
|
|
10262
|
-
}
|
|
10263
|
-
return commands;
|
|
10264
|
-
}
|
|
10265
10258
|
function generateConfigTemplate(options) {
|
|
10266
10259
|
const {
|
|
10267
10260
|
baseBranch,
|
|
10268
10261
|
packageManager,
|
|
10269
|
-
verificationCommands,
|
|
10270
10262
|
hasPackageJson = true,
|
|
10271
10263
|
labels: maybeLabels
|
|
10272
10264
|
} = options;
|
|
10273
10265
|
const labels = maybeLabels ?? DEFAULT_RUNNER_LABELS;
|
|
10274
|
-
const setupCommand =
|
|
10266
|
+
const setupCommand = `HUSKY=0 ${packageManager} install`;
|
|
10267
|
+
const runCommand = packageManager === "yarn" ? "yarn" : `${packageManager} run`;
|
|
10268
|
+
const verificationCommands = [
|
|
10269
|
+
{ command: `${runCommand} lint`, label: "lint" },
|
|
10270
|
+
{ command: `${runCommand} test`, label: "tests" },
|
|
10271
|
+
{ command: `${runCommand} build`, label: "build" }
|
|
10272
|
+
];
|
|
10275
10273
|
const target = {
|
|
10276
10274
|
name: "default",
|
|
10277
10275
|
baseBranch,
|
|
10278
10276
|
branchTemplate: "pourkit/{{issue.number}}/{{issue.slug}}",
|
|
10279
|
-
autoMerge:
|
|
10277
|
+
autoMerge: true,
|
|
10280
10278
|
strategy: {
|
|
10281
10279
|
type: "review-refactor-loop",
|
|
10282
10280
|
implement: {
|
|
10283
10281
|
builder: {
|
|
10284
10282
|
agent: "pourkit-builder",
|
|
10285
10283
|
model: "opencode-go/deepseek-v4-flash",
|
|
10284
|
+
variant: "medium",
|
|
10286
10285
|
promptTemplate: ".pourkit/managed/prompts/builder.prompt.md"
|
|
10287
10286
|
}
|
|
10288
10287
|
},
|
|
10289
10288
|
failureResolution: {
|
|
10290
10289
|
agent: "pourkit-failure-resolution-agent",
|
|
10291
10290
|
model: "opencode-go/mimo-v2.5",
|
|
10291
|
+
variant: "high",
|
|
10292
10292
|
promptTemplate: ".pourkit/managed/prompts/failure-resolution.prompt.md",
|
|
10293
10293
|
maxAttemptsPerFailure: 1
|
|
10294
10294
|
},
|
|
10295
10295
|
review: {
|
|
10296
10296
|
reviewer: {
|
|
10297
10297
|
agent: "pourkit-reviewer",
|
|
10298
|
-
model: "opencode-go/
|
|
10298
|
+
model: "opencode-go/mimo-v2.5",
|
|
10299
|
+
variant: "high",
|
|
10299
10300
|
promptTemplate: ".pourkit/managed/prompts/reviewer.prompt.md",
|
|
10300
10301
|
criteria: ["correctness", "scope", "tests", "quality"]
|
|
10301
10302
|
},
|
|
10302
10303
|
refactor: {
|
|
10303
10304
|
agent: "pourkit-refactor",
|
|
10304
|
-
model: "opencode-go/
|
|
10305
|
+
model: "opencode-go/deepseek-v4-flash",
|
|
10306
|
+
variant: "medium",
|
|
10305
10307
|
promptTemplate: ".pourkit/managed/prompts/refactor.prompt.md"
|
|
10306
10308
|
},
|
|
10307
|
-
maxIterations:
|
|
10309
|
+
maxIterations: 20
|
|
10308
10310
|
},
|
|
10309
10311
|
issueFinalReview: {
|
|
10310
10312
|
agent: "pourkit-reviewer",
|
|
10311
|
-
model: "opencode-go/
|
|
10313
|
+
model: "opencode-go/kimi-k2.7-code",
|
|
10314
|
+
variant: "max",
|
|
10312
10315
|
promptTemplate: ".pourkit/managed/prompts/issue-final-review.prompt.md",
|
|
10313
10316
|
maxAttempts: 3
|
|
10314
10317
|
},
|
|
@@ -10327,11 +10330,9 @@ function generateConfigTemplate(options) {
|
|
|
10327
10330
|
{ command: setupCommand, label: "install" }
|
|
10328
10331
|
];
|
|
10329
10332
|
}
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
};
|
|
10334
|
-
}
|
|
10333
|
+
target.strategy.verify = {
|
|
10334
|
+
commands: verificationCommands
|
|
10335
|
+
};
|
|
10335
10336
|
const config = {
|
|
10336
10337
|
$schema: "./schema/pourkit.schema.json",
|
|
10337
10338
|
schemaVersion: 3,
|
|
@@ -10353,10 +10354,12 @@ function generateConfigTemplate(options) {
|
|
|
10353
10354
|
agentInProgress: labels.agentInProgress,
|
|
10354
10355
|
blocked: labels.blocked,
|
|
10355
10356
|
prOpenAwaitingMerge: labels.prOpenAwaitingMerge,
|
|
10356
|
-
readyForHuman: labels.readyForHuman
|
|
10357
|
+
readyForHuman: labels.readyForHuman,
|
|
10358
|
+
needsTriage: "needs-triage"
|
|
10357
10359
|
},
|
|
10358
10360
|
sandbox: {
|
|
10359
10361
|
provider: "docker",
|
|
10362
|
+
forceRebuild: true,
|
|
10360
10363
|
copyToWorktree: ["node_modules"],
|
|
10361
10364
|
mounts: [
|
|
10362
10365
|
{
|
|
@@ -10374,18 +10377,13 @@ function generateConfigTemplate(options) {
|
|
|
10374
10377
|
HOME: "/home/agent",
|
|
10375
10378
|
XDG_DATA_HOME: "/home/agent/.local/share",
|
|
10376
10379
|
XDG_CONFIG_HOME: "/home/agent/.config",
|
|
10377
|
-
XDG_STATE_HOME: "/home/agent/.local/state"
|
|
10378
|
-
XDG_CACHE_HOME: "/home/agent/.cache"
|
|
10380
|
+
XDG_STATE_HOME: "/home/agent/.local/state"
|
|
10379
10381
|
},
|
|
10380
|
-
idleTimeoutSeconds:
|
|
10382
|
+
idleTimeoutSeconds: 800
|
|
10381
10383
|
},
|
|
10382
10384
|
checks: {
|
|
10383
10385
|
requiredLabels: [],
|
|
10384
|
-
allowedAuthors: []
|
|
10385
|
-
checksFoundTimeoutSeconds: 60,
|
|
10386
|
-
checksCompletionTimeoutSeconds: 1800,
|
|
10387
|
-
pollIntervalSeconds: 15,
|
|
10388
|
-
issueListLimit: 50
|
|
10386
|
+
allowedAuthors: []
|
|
10389
10387
|
}
|
|
10390
10388
|
};
|
|
10391
10389
|
return JSON.stringify(config, null, 2) + "\n";
|
|
@@ -11037,17 +11035,9 @@ async function planInit(options) {
|
|
|
11037
11035
|
});
|
|
11038
11036
|
}
|
|
11039
11037
|
}
|
|
11040
|
-
let packageScripts = {};
|
|
11041
11038
|
let hasPackageJson = true;
|
|
11042
11039
|
try {
|
|
11043
|
-
|
|
11044
|
-
path5.join(targetRoot, "package.json"),
|
|
11045
|
-
"utf-8"
|
|
11046
|
-
);
|
|
11047
|
-
const pkg = JSON.parse(pkgContent);
|
|
11048
|
-
if (pkg.scripts && typeof pkg.scripts === "object") {
|
|
11049
|
-
packageScripts = pkg.scripts;
|
|
11050
|
-
}
|
|
11040
|
+
await readFile4(path5.join(targetRoot, "package.json"), "utf-8");
|
|
11051
11041
|
} catch {
|
|
11052
11042
|
hasPackageJson = false;
|
|
11053
11043
|
}
|
|
@@ -11274,14 +11264,9 @@ Do not edit this file.
|
|
|
11274
11264
|
}
|
|
11275
11265
|
const configJsonPath = path5.join(targetRoot, ".pourkit", "config.json");
|
|
11276
11266
|
if (!existsSync16(configJsonPath)) {
|
|
11277
|
-
const verifyCommands = inferVerificationCommands(
|
|
11278
|
-
packageScripts,
|
|
11279
|
-
pm || "npm"
|
|
11280
|
-
);
|
|
11281
11267
|
const configContent = generateConfigTemplate({
|
|
11282
11268
|
packageManager: pm || "npm",
|
|
11283
11269
|
baseBranch,
|
|
11284
|
-
verificationCommands: verifyCommands,
|
|
11285
11270
|
hasPackageJson,
|
|
11286
11271
|
labels: options.labels,
|
|
11287
11272
|
releaseWorkflow: options.releaseWorkflow,
|
|
@@ -12294,7 +12279,7 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
|
|
|
12294
12279
|
}
|
|
12295
12280
|
|
|
12296
12281
|
// commands/memory-init.ts
|
|
12297
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as
|
|
12282
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
|
|
12298
12283
|
import { join as join17 } from "path";
|
|
12299
12284
|
async function runMemoryInitCommand(options) {
|
|
12300
12285
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -12305,7 +12290,7 @@ async function runMemoryInitCommand(options) {
|
|
|
12305
12290
|
message: "No .pourkit/config.json found. Run `pourkit init` first to initialize Pourkit, then run `pourkit memory init`."
|
|
12306
12291
|
};
|
|
12307
12292
|
}
|
|
12308
|
-
const raw = JSON.parse(
|
|
12293
|
+
const raw = JSON.parse(readFileSync17(configPath, "utf8"));
|
|
12309
12294
|
let alreadyEnabled = false;
|
|
12310
12295
|
if (Object.prototype.hasOwnProperty.call(raw, "memory")) {
|
|
12311
12296
|
const m = raw.memory;
|
|
@@ -12362,7 +12347,7 @@ ${content}${MANAGED_BLOCK_END}
|
|
|
12362
12347
|
writeFileSync5(filePath, blockContent);
|
|
12363
12348
|
return;
|
|
12364
12349
|
}
|
|
12365
|
-
const existing =
|
|
12350
|
+
const existing = readFileSync17(filePath, "utf8");
|
|
12366
12351
|
const beginIdx = existing.indexOf(MANAGED_BLOCK_BEGIN);
|
|
12367
12352
|
const endIdx = existing.indexOf(MANAGED_BLOCK_END);
|
|
12368
12353
|
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
|
|
@@ -12491,7 +12476,7 @@ async function runSerenaStatusCommand(options) {
|
|
|
12491
12476
|
}
|
|
12492
12477
|
|
|
12493
12478
|
// commands/config-schema.ts
|
|
12494
|
-
import { readFileSync as
|
|
12479
|
+
import { readFileSync as readFileSync18, existsSync as existsSync18 } from "fs";
|
|
12495
12480
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile2, readdir as readdir2 } from "fs/promises";
|
|
12496
12481
|
import { resolve as resolve3, dirname as dirname5, relative as relative2 } from "path";
|
|
12497
12482
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -12512,7 +12497,7 @@ var _schemaValidator = null;
|
|
|
12512
12497
|
var _schemaErrors = null;
|
|
12513
12498
|
function getSchemaValidator() {
|
|
12514
12499
|
if (!_schemaValidator) {
|
|
12515
|
-
const schema = JSON.parse(
|
|
12500
|
+
const schema = JSON.parse(readFileSync18(PACKAGED_SCHEMA_PATH, "utf-8"));
|
|
12516
12501
|
const ajv = new Ajv2({ strict: true });
|
|
12517
12502
|
ajv.addKeyword("x-pourkit-schema-version");
|
|
12518
12503
|
const validate = ajv.compile(schema);
|
|
@@ -12526,7 +12511,7 @@ function getSchemaValidator() {
|
|
|
12526
12511
|
return _schemaValidator;
|
|
12527
12512
|
}
|
|
12528
12513
|
function readPackagedHash() {
|
|
12529
|
-
return
|
|
12514
|
+
return readFileSync18(PACKAGED_HASH_PATH, "utf-8");
|
|
12530
12515
|
}
|
|
12531
12516
|
function localSchemaDir(repoRoot2) {
|
|
12532
12517
|
return resolve3(repoRoot2, ".pourkit/schema");
|
|
@@ -13082,7 +13067,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
13082
13067
|
detail: icmPath !== null ? icmPath : "icm not found on PATH"
|
|
13083
13068
|
});
|
|
13084
13069
|
const gitignorePath = resolve3(cwd, ".gitignore");
|
|
13085
|
-
const gitignoreContent = existsSync18(gitignorePath) ?
|
|
13070
|
+
const gitignoreContent = existsSync18(gitignorePath) ? readFileSync18(gitignorePath, "utf-8") : "";
|
|
13086
13071
|
const hasGitignoreEntry = gitignoreContent.includes(".pourkit/icm/");
|
|
13087
13072
|
checks.push({
|
|
13088
13073
|
name: "icm-gitignore",
|
|
@@ -13131,7 +13116,7 @@ async function runDoctorCommand(options) {
|
|
|
13131
13116
|
let rawMemoryConfig;
|
|
13132
13117
|
if (existsSync18(configPath)) {
|
|
13133
13118
|
try {
|
|
13134
|
-
const raw = JSON.parse(
|
|
13119
|
+
const raw = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
13135
13120
|
rawMemoryConfig = raw.memory;
|
|
13136
13121
|
const validate = getSchemaValidator();
|
|
13137
13122
|
const valid2 = validate(raw);
|
|
@@ -14370,7 +14355,7 @@ import path7 from "path";
|
|
|
14370
14355
|
|
|
14371
14356
|
// execution/sandbox-image.ts
|
|
14372
14357
|
import { createHash as createHash2 } from "crypto";
|
|
14373
|
-
import { existsSync as existsSync20, readFileSync as
|
|
14358
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
|
|
14374
14359
|
import path6 from "path";
|
|
14375
14360
|
function sandboxImageName(repoRoot2, installIcm) {
|
|
14376
14361
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
@@ -14381,7 +14366,7 @@ function sandboxImageName(repoRoot2, installIcm) {
|
|
|
14381
14366
|
const base2 = `sandcastle:${baseName}`;
|
|
14382
14367
|
return installIcm ? `${base2}-icm` : base2;
|
|
14383
14368
|
}
|
|
14384
|
-
const fingerprint = createHash2("sha256").update(
|
|
14369
|
+
const fingerprint = createHash2("sha256").update(readFileSync19(dockerfilePath)).digest("hex").slice(0, 8);
|
|
14385
14370
|
const base = `sandcastle:${baseName}-${fingerprint}`;
|
|
14386
14371
|
return installIcm ? `${base}-icm` : base;
|
|
14387
14372
|
}
|
|
@@ -15532,11 +15517,11 @@ function createCliProgram(version) {
|
|
|
15532
15517
|
return program;
|
|
15533
15518
|
}
|
|
15534
15519
|
async function resolveCliVersion() {
|
|
15535
|
-
if (isPackageVersion("0.0.0-next-
|
|
15536
|
-
return "0.0.0-next-
|
|
15520
|
+
if (isPackageVersion("0.0.0-next-20260623021955")) {
|
|
15521
|
+
return "0.0.0-next-20260623021955";
|
|
15537
15522
|
}
|
|
15538
|
-
if (isReleaseVersion("0.0.0-next-
|
|
15539
|
-
return "0.0.0-next-
|
|
15523
|
+
if (isReleaseVersion("0.0.0-next-20260623021955")) {
|
|
15524
|
+
return "0.0.0-next-20260623021955";
|
|
15540
15525
|
}
|
|
15541
15526
|
try {
|
|
15542
15527
|
const root = repoRoot();
|