@pourkit/cli 0.0.0-next-20260621233457 → 0.0.0-next-20260622201317
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 +172 -260
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +149 -237
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/dist/managed/prompts/builder.prompt.md +1 -1
- package/dist/managed/prompts/issue-final-review.prompt.md +0 -7
- package/dist/managed/prompts/refactor.prompt.md +1 -1
- package/dist/managed/prompts/reviewer.prompt.md +2 -2
- package/package.json +5 -2
package/dist/cli.js
CHANGED
|
@@ -1513,16 +1513,6 @@ function renderCommandList(target, heading, repoRoot2) {
|
|
|
1513
1513
|
"Use the wrapper as the configured verification method for Builder, Reviewer, Refactor, and Issue Final Review handoffs. Run the underlying commands directly only when no wrapper path can run, and record that degraded path in the role's artifact or handoff notes.",
|
|
1514
1514
|
""
|
|
1515
1515
|
];
|
|
1516
|
-
if (isPourkitSourceRepo(repoRoot2)) {
|
|
1517
|
-
parts.push(
|
|
1518
|
-
"Pourkit self-development fallback:",
|
|
1519
|
-
"",
|
|
1520
|
-
`- If \`${wrapperCommand}\` fails before running configured commands because the installed CLI rejects this worktree's config schemaVersion, build and rerun the same target with the worktree-local CLI: \`npm run build --workspace pourkit\`, then \`node pourkit/dist/cli.js run-verification --target ${target.name}\`.`,
|
|
1521
|
-
'- Treat local CLI wrapper success as valid configured verification and record it as `verification.method: "local-wrapper"` in Issue Final Review artifacts, or as local-wrapper verification in Builder/Reviewer/Refactor handoff notes.',
|
|
1522
|
-
'- Fall back to the underlying configured commands only if the worktree-local CLI cannot be built or invoked; record that degraded path as `verification.method: "commands"` in Issue Final Review artifacts, or as direct-command verification in Builder/Reviewer/Refactor handoff notes, and explain why the wrapper path could not run.',
|
|
1523
|
-
""
|
|
1524
|
-
);
|
|
1525
|
-
}
|
|
1526
1516
|
if (commands.length > 0) {
|
|
1527
1517
|
parts.push(
|
|
1528
1518
|
"Configured commands executed by the wrapper:",
|
|
@@ -1535,17 +1525,6 @@ function renderCommandList(target, heading, repoRoot2) {
|
|
|
1535
1525
|
}
|
|
1536
1526
|
return parts;
|
|
1537
1527
|
}
|
|
1538
|
-
function isPourkitSourceRepo(repoRoot2) {
|
|
1539
|
-
if (!repoRoot2) return false;
|
|
1540
|
-
const packagePath = join5(repoRoot2, "pourkit", "package.json");
|
|
1541
|
-
if (!existsSync4(packagePath)) return false;
|
|
1542
|
-
try {
|
|
1543
|
-
const parsed = JSON.parse(readFileSync4(packagePath, "utf-8"));
|
|
1544
|
-
return parsed.name === "@pourkit/cli";
|
|
1545
|
-
} catch {
|
|
1546
|
-
return false;
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
1528
|
function renderParentPrdContext(issue, parentPrdIssue, repoRoot2) {
|
|
1550
1529
|
const parent = extractIssueSection(issue.body, "Parent");
|
|
1551
1530
|
const parentRef = parent?.match(/\bPRD-\d+\b/i)?.[0]?.toUpperCase() ?? issue.title.match(/^\s*(PRD-\d+)\s*\/\s*I-\d+\b/i)?.[1]?.toUpperCase();
|
|
@@ -2291,7 +2270,6 @@ Read the selected issue requirements, planning context (including plan and PRD c
|
|
|
2291
2270
|
|
|
2292
2271
|
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
2293
2272
|
- Before reviewing code, diffs, artifacts, or prior findings, run the configured verification wrapper yourself from the Worktree.
|
|
2294
|
-
- If ${RUN_CONTEXT_PATH_IN_WORKTREE} provides a worktree-local CLI fallback, use that fallback before considering direct command execution.
|
|
2295
2273
|
- Run underlying verification commands directly only when no wrapper path can run, and record why verification used direct commands.
|
|
2296
2274
|
- If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
|
|
2297
2275
|
- 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.
|
|
@@ -2542,7 +2520,6 @@ Read the selected issue requirements, planning context (including plan and PRD c
|
|
|
2542
2520
|
## Verification Handoff
|
|
2543
2521
|
|
|
2544
2522
|
- Run the verification wrapper listed in ${RUN_CONTEXT_PATH_IN_WORKTREE} after making changes.
|
|
2545
|
-
- If ${RUN_CONTEXT_PATH_IN_WORKTREE} provides a worktree-local CLI fallback, use that fallback before considering direct command execution.
|
|
2546
2523
|
- Run underlying verification commands directly only when no wrapper path can run, and record why verification used direct commands in the Refactor Artifact.
|
|
2547
2524
|
- If configured verification fails because of fixable Issue work, fix the work and rerun verification before handoff.
|
|
2548
2525
|
- Failed or blocked verification prohibits <promise>COMPLETE</promise>.
|
|
@@ -3260,12 +3237,6 @@ var ISSUE_FINAL_REVIEW_SCOPE_VERDICTS = [
|
|
|
3260
3237
|
"incidental",
|
|
3261
3238
|
"out-of-scope"
|
|
3262
3239
|
];
|
|
3263
|
-
var ISSUE_FINAL_REVIEW_VERIFICATION_METHODS = [
|
|
3264
|
-
"wrapper",
|
|
3265
|
-
"local-wrapper",
|
|
3266
|
-
"commands",
|
|
3267
|
-
"none"
|
|
3268
|
-
];
|
|
3269
3240
|
function invalid(options, reason, diagnostics = []) {
|
|
3270
3241
|
return {
|
|
3271
3242
|
ok: false,
|
|
@@ -3573,15 +3544,6 @@ function validateIssueFinalReviewArtifact(parsed, options) {
|
|
|
3573
3544
|
diagnostics
|
|
3574
3545
|
};
|
|
3575
3546
|
}
|
|
3576
|
-
if ("method" in verification && !ISSUE_FINAL_REVIEW_VERIFICATION_METHODS.includes(
|
|
3577
|
-
verification.method
|
|
3578
|
-
)) {
|
|
3579
|
-
return {
|
|
3580
|
-
ok: false,
|
|
3581
|
-
reason: 'verification.method must be "wrapper", "local-wrapper", "commands", or "none"',
|
|
3582
|
-
diagnostics
|
|
3583
|
-
};
|
|
3584
|
-
}
|
|
3585
3547
|
diagnostics.push("verification: present");
|
|
3586
3548
|
const verificationWasRecorded = verification.required === true || Array.isArray(verification.commands) && verification.commands.length > 0;
|
|
3587
3549
|
if (parsed.verdict === "pass" && verificationWasRecorded) {
|
|
@@ -3596,20 +3558,6 @@ function validateIssueFinalReviewArtifact(parsed, options) {
|
|
|
3596
3558
|
]
|
|
3597
3559
|
};
|
|
3598
3560
|
}
|
|
3599
|
-
if (typeof verification.method !== "string") {
|
|
3600
|
-
return {
|
|
3601
|
-
ok: false,
|
|
3602
|
-
reason: "Issue Final Review pass with verification evidence requires verification.method",
|
|
3603
|
-
diagnostics
|
|
3604
|
-
};
|
|
3605
|
-
}
|
|
3606
|
-
if (verification.method === "none") {
|
|
3607
|
-
return {
|
|
3608
|
-
ok: false,
|
|
3609
|
-
reason: 'Issue Final Review pass with verification evidence requires verification.method other than "none"',
|
|
3610
|
-
diagnostics
|
|
3611
|
-
};
|
|
3612
|
-
}
|
|
3613
3561
|
diagnostics.push("pass verification: valid");
|
|
3614
3562
|
}
|
|
3615
3563
|
if (parsed.selfRetouched === true && parsed.verdict === "pass") {
|
|
@@ -3794,7 +3742,6 @@ function shellQuote(value) {
|
|
|
3794
3742
|
|
|
3795
3743
|
// commands/issue-run.ts
|
|
3796
3744
|
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
|
|
3797
|
-
import { isAbsolute as isAbsolute4, join as join15 } from "path";
|
|
3798
3745
|
|
|
3799
3746
|
// pr/templates.ts
|
|
3800
3747
|
init_common();
|
|
@@ -4973,7 +4920,6 @@ Read the selected issue requirements, planning context (including plan and PRD c
|
|
|
4973
4920
|
## Verification Handoff
|
|
4974
4921
|
|
|
4975
4922
|
- Run the verification wrapper listed in ${RUN_CONTEXT_PATH_IN_WORKTREE}.
|
|
4976
|
-
- If ${RUN_CONTEXT_PATH_IN_WORKTREE} provides a worktree-local CLI fallback, use that fallback before considering direct command execution.
|
|
4977
4923
|
- Run underlying verification commands directly only when no wrapper path can run, and report why verification used direct commands.
|
|
4978
4924
|
- If configured verification fails because of fixable Issue work, fix the work and rerun verification before handoff.
|
|
4979
4925
|
- Failed or blocked verification prohibits <promise>COMPLETE</promise>.`);
|
|
@@ -5922,9 +5868,6 @@ function buildIssueFinalReviewPrComment(artifact) {
|
|
|
5922
5868
|
`- **Required:** ${formatBoolean(artifact.verification.required)}`,
|
|
5923
5869
|
`- **Passed:** ${formatBoolean(artifact.verification.passed)}`
|
|
5924
5870
|
);
|
|
5925
|
-
if (typeof artifact.verification.method === "string") {
|
|
5926
|
-
lines.push(`- **Method:** ${formatLabel(artifact.verification.method)}`);
|
|
5927
|
-
}
|
|
5928
5871
|
const commands = Array.isArray(artifact.verification.commands) ? artifact.verification.commands.filter(
|
|
5929
5872
|
(command) => typeof command === "string"
|
|
5930
5873
|
) : [];
|
|
@@ -6147,16 +6090,17 @@ function isAllowedDocumentationTokenReference(value) {
|
|
|
6147
6090
|
return /^token\s*:\s*["']?<verdict>/i.test(value);
|
|
6148
6091
|
}
|
|
6149
6092
|
|
|
6150
|
-
//
|
|
6093
|
+
// issues/issue-final-review.ts
|
|
6151
6094
|
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
6152
|
-
import { join as join13 } from "path";
|
|
6095
|
+
import { isAbsolute as isAbsolute4, join as join13 } from "path";
|
|
6096
|
+
init_common();
|
|
6153
6097
|
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join13(
|
|
6154
6098
|
".pourkit",
|
|
6155
6099
|
".tmp",
|
|
6156
6100
|
"issue-final-review",
|
|
6157
6101
|
"agent-output.json"
|
|
6158
6102
|
);
|
|
6159
|
-
async function
|
|
6103
|
+
async function runIssueFinalReview(options) {
|
|
6160
6104
|
const {
|
|
6161
6105
|
executionProvider,
|
|
6162
6106
|
target,
|
|
@@ -6169,6 +6113,34 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
6169
6113
|
} = options;
|
|
6170
6114
|
const artifactPathInWorktree = ISSUE_FINAL_REVIEW_ARTIFACT_PATH;
|
|
6171
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
|
+
);
|
|
6122
|
+
}
|
|
6123
|
+
const cachedArtifactPath = isAbsolute4(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join13(worktreePath, ifrFromState.artifactPath);
|
|
6124
|
+
const validation2 = validateAgentArtifact({
|
|
6125
|
+
kind: "issue-final-review",
|
|
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}`
|
|
6133
|
+
);
|
|
6134
|
+
}
|
|
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
|
+
}
|
|
6172
6144
|
const strategy = target.strategy;
|
|
6173
6145
|
const issueFinalReview = strategy.issueFinalReview;
|
|
6174
6146
|
const agent = issueFinalReview;
|
|
@@ -6245,17 +6217,42 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
6245
6217
|
}
|
|
6246
6218
|
const parsed = JSON.parse(content);
|
|
6247
6219
|
const verdict = parsed.verdict;
|
|
6220
|
+
const baseRef = `origin/${target.baseBranch}`;
|
|
6221
|
+
await assertCanonicalBaseAncestor2({
|
|
6222
|
+
worktreePath,
|
|
6223
|
+
baseRef,
|
|
6224
|
+
stageName: "Issue Final Review",
|
|
6225
|
+
logger
|
|
6226
|
+
});
|
|
6248
6227
|
if (verdict === "pass") {
|
|
6228
|
+
const changedPaths = await completeIssueFinalReviewChangedPaths({
|
|
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
|
+
});
|
|
6249
6244
|
return {
|
|
6245
|
+
status: "passed",
|
|
6250
6246
|
verdict: "pass",
|
|
6251
6247
|
artifactPath,
|
|
6252
6248
|
selfRetouched: parsed.selfRetouched === true,
|
|
6253
|
-
changedPaths
|
|
6249
|
+
changedPaths,
|
|
6254
6250
|
verificationPassed: parsed.verification?.passed === true
|
|
6255
6251
|
};
|
|
6256
6252
|
}
|
|
6257
6253
|
if (verdict === "needs_human_review") {
|
|
6258
6254
|
return {
|
|
6255
|
+
status: "needs_human_review",
|
|
6259
6256
|
verdict: "needs_human_review",
|
|
6260
6257
|
artifactPath,
|
|
6261
6258
|
needsHumanReason: parsed.needsHumanReason,
|
|
@@ -6267,6 +6264,89 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
6267
6264
|
`Unknown Issue Final Review verdict: ${JSON.stringify(verdict)}`
|
|
6268
6265
|
);
|
|
6269
6266
|
}
|
|
6267
|
+
async function assertCanonicalBaseAncestor2(options) {
|
|
6268
|
+
const { worktreePath, baseRef, stageName, logger } = options;
|
|
6269
|
+
await syncRemoteBaseRef2(worktreePath, baseRef, logger);
|
|
6270
|
+
try {
|
|
6271
|
+
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
6272
|
+
cwd: worktreePath,
|
|
6273
|
+
logger,
|
|
6274
|
+
label: "git merge-base --is-ancestor"
|
|
6275
|
+
});
|
|
6276
|
+
} catch {
|
|
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) {
|
|
6285
|
+
return;
|
|
6286
|
+
}
|
|
6287
|
+
await execCapture("git", ["fetch", remoteBase.remote, remoteBase.branch], {
|
|
6288
|
+
cwd: worktreePath,
|
|
6289
|
+
logger,
|
|
6290
|
+
label: "git fetch target"
|
|
6291
|
+
});
|
|
6292
|
+
}
|
|
6293
|
+
function parseRemoteBaseRef2(baseRef) {
|
|
6294
|
+
const [remote, ...branchParts] = baseRef.split("/");
|
|
6295
|
+
const branch = branchParts.join("/");
|
|
6296
|
+
if (!remote || !branch) {
|
|
6297
|
+
return null;
|
|
6298
|
+
}
|
|
6299
|
+
return { remote, branch };
|
|
6300
|
+
}
|
|
6301
|
+
async function completeIssueFinalReviewChangedPaths(options) {
|
|
6302
|
+
const actualChangedPaths = await listIssueFinalReviewChangedPaths2(options);
|
|
6303
|
+
const declared = new Set(options.declaredChangedPaths.map(normalizeGitPath2));
|
|
6304
|
+
const missing = actualChangedPaths.filter((path9) => !declared.has(path9));
|
|
6305
|
+
if (missing.length === 0) {
|
|
6306
|
+
return actualChangedPaths;
|
|
6307
|
+
}
|
|
6308
|
+
options.logger.step(
|
|
6309
|
+
"warn",
|
|
6310
|
+
`Issue Final Review pass omitted ${missing.length} changed file(s) from changedFiles; using git inventory for finalization state.`
|
|
6311
|
+
);
|
|
6312
|
+
options.logger.kv("MISSING_CHANGED_FILES", missing.join(", "));
|
|
6313
|
+
return actualChangedPaths;
|
|
6314
|
+
}
|
|
6315
|
+
async function listIssueFinalReviewChangedPaths2(options) {
|
|
6316
|
+
const trackedDiff = await execCapture(
|
|
6317
|
+
"git",
|
|
6318
|
+
["diff-index", "--name-only", options.baseRef, "--"],
|
|
6319
|
+
{
|
|
6320
|
+
cwd: options.worktreePath,
|
|
6321
|
+
logger: options.logger,
|
|
6322
|
+
label: "git diff-index Issue Final Review paths"
|
|
6323
|
+
}
|
|
6324
|
+
);
|
|
6325
|
+
const untrackedFiles = await execCapture(
|
|
6326
|
+
"git",
|
|
6327
|
+
["ls-files", "--others", "--exclude-standard"],
|
|
6328
|
+
{
|
|
6329
|
+
cwd: options.worktreePath,
|
|
6330
|
+
logger: options.logger,
|
|
6331
|
+
label: "git ls-files Issue Final Review paths"
|
|
6332
|
+
}
|
|
6333
|
+
);
|
|
6334
|
+
return Array.from(
|
|
6335
|
+
/* @__PURE__ */ new Set([
|
|
6336
|
+
...parseGitPathList2(trackedDiff.stdout),
|
|
6337
|
+
...parseGitPathList2(untrackedFiles.stdout)
|
|
6338
|
+
])
|
|
6339
|
+
).filter(isIssueFinalReviewAuditedPath2);
|
|
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
|
+
}
|
|
6270
6350
|
function extractChangedPaths(parsed) {
|
|
6271
6351
|
return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
|
|
6272
6352
|
(file) => file && typeof file === "object" ? file.path : void 0
|
|
@@ -6292,9 +6372,7 @@ Read the selected issue requirements, planning context (including plan and PRD c
|
|
|
6292
6372
|
|
|
6293
6373
|
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
6294
6374
|
- Before reviewing code, diffs, artifacts, or prior findings, run the verification wrapper from the Worktree.
|
|
6295
|
-
-
|
|
6296
|
-
- Do not substitute direct underlying commands unless neither wrapper path can run; if you must use direct commands, record degraded verification with \`verification.method: "commands"\` and explain why in \`verification.summary\`.
|
|
6297
|
-
- Record successful wrapper verification with \`verification.method: "wrapper"\`, or worktree-local CLI wrapper verification with \`verification.method: "local-wrapper"\`.
|
|
6375
|
+
- Do not substitute direct underlying commands unless no wrapper path can run; if you must use direct commands, explain why in \`verification.summary\`.
|
|
6298
6376
|
- If a configured verification command fails because of fixable Issue work, self-retouch the Worktree, then rerun all configured verification commands.
|
|
6299
6377
|
- Do not return \`pass\` while any configured verification command is failing.
|
|
6300
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.
|
|
@@ -6507,90 +6585,7 @@ function assertIssueFinalReviewPassed(worktreeState) {
|
|
|
6507
6585
|
}
|
|
6508
6586
|
}
|
|
6509
6587
|
async function advanceIssueFinalReview(options) {
|
|
6510
|
-
|
|
6511
|
-
executionProvider,
|
|
6512
|
-
config,
|
|
6513
|
-
target,
|
|
6514
|
-
issue,
|
|
6515
|
-
parentPrdIssue,
|
|
6516
|
-
builderBranch,
|
|
6517
|
-
worktreePath,
|
|
6518
|
-
repoRoot: repoRoot2,
|
|
6519
|
-
logger,
|
|
6520
|
-
reviewArtifactPath,
|
|
6521
|
-
reviewVerdict,
|
|
6522
|
-
worktreeState
|
|
6523
|
-
} = options;
|
|
6524
|
-
const ifrFromState = worktreeState?.issueFinalReview;
|
|
6525
|
-
if (ifrFromState?.completed && ifrFromState.verdict === "pass") {
|
|
6526
|
-
if (!ifrFromState.artifactPath) {
|
|
6527
|
-
throw new Error(
|
|
6528
|
-
"Issue Final Review state is incomplete: missing artifactPath"
|
|
6529
|
-
);
|
|
6530
|
-
}
|
|
6531
|
-
const artifactPath = isAbsolute4(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join15(worktreePath, ifrFromState.artifactPath);
|
|
6532
|
-
const validation = validateAgentArtifact({
|
|
6533
|
-
kind: "issue-final-review",
|
|
6534
|
-
artifactPath,
|
|
6535
|
-
issueNumber: issue.number,
|
|
6536
|
-
branchName: builderBranch
|
|
6537
|
-
});
|
|
6538
|
-
if (!validation.ok) {
|
|
6539
|
-
throw new Error(
|
|
6540
|
-
`Issue Final Review state artifact is invalid: ${validation.reason}`
|
|
6541
|
-
);
|
|
6542
|
-
}
|
|
6543
|
-
return {
|
|
6544
|
-
verdict: "pass",
|
|
6545
|
-
artifactPath,
|
|
6546
|
-
selfRetouched: ifrFromState.selfRetouched ?? false,
|
|
6547
|
-
changedPaths: ifrFromState.changedPaths ?? [],
|
|
6548
|
-
verificationPassed: ifrFromState.verificationPassed ?? false
|
|
6549
|
-
};
|
|
6550
|
-
}
|
|
6551
|
-
const result = await runIssueFinalReviewAgent({
|
|
6552
|
-
executionProvider,
|
|
6553
|
-
config,
|
|
6554
|
-
target,
|
|
6555
|
-
issue,
|
|
6556
|
-
parentPrdIssue,
|
|
6557
|
-
builderBranch,
|
|
6558
|
-
worktreePath,
|
|
6559
|
-
repoRoot: repoRoot2,
|
|
6560
|
-
logger,
|
|
6561
|
-
reviewArtifactPath,
|
|
6562
|
-
reviewVerdict,
|
|
6563
|
-
...options.memory ? { memory: options.memory } : {}
|
|
6564
|
-
});
|
|
6565
|
-
await assertCanonicalBaseAncestor2({
|
|
6566
|
-
worktreePath,
|
|
6567
|
-
baseRef: `origin/${target.baseBranch}`,
|
|
6568
|
-
stageName: "Issue Final Review",
|
|
6569
|
-
logger
|
|
6570
|
-
});
|
|
6571
|
-
if (result.verdict === "pass") {
|
|
6572
|
-
const changedPaths = await completeIssueFinalReviewChangedPaths({
|
|
6573
|
-
worktreePath,
|
|
6574
|
-
baseRef: `origin/${target.baseBranch}`,
|
|
6575
|
-
declaredChangedPaths: result.changedPaths,
|
|
6576
|
-
logger
|
|
6577
|
-
});
|
|
6578
|
-
updateWorktreeRunState(worktreePath, {
|
|
6579
|
-
issueFinalReview: {
|
|
6580
|
-
completed: true,
|
|
6581
|
-
verdict: "pass",
|
|
6582
|
-
artifactPath: result.artifactPath,
|
|
6583
|
-
selfRetouched: result.selfRetouched,
|
|
6584
|
-
changedPaths,
|
|
6585
|
-
verificationPassed: result.verificationPassed
|
|
6586
|
-
}
|
|
6587
|
-
});
|
|
6588
|
-
return {
|
|
6589
|
-
...result,
|
|
6590
|
-
changedPaths
|
|
6591
|
-
};
|
|
6592
|
-
}
|
|
6593
|
-
return result;
|
|
6588
|
+
return runIssueFinalReview(options);
|
|
6594
6589
|
}
|
|
6595
6590
|
async function startIssueRun(options) {
|
|
6596
6591
|
const {
|
|
@@ -7026,89 +7021,6 @@ async function finalizeWorktreeCommit(options) {
|
|
|
7026
7021
|
label: "git commit"
|
|
7027
7022
|
});
|
|
7028
7023
|
}
|
|
7029
|
-
async function assertCanonicalBaseAncestor2(options) {
|
|
7030
|
-
const { worktreePath, baseRef, stageName, logger } = options;
|
|
7031
|
-
await syncRemoteBaseRef2(worktreePath, baseRef, logger);
|
|
7032
|
-
try {
|
|
7033
|
-
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
7034
|
-
cwd: worktreePath,
|
|
7035
|
-
logger,
|
|
7036
|
-
label: "git merge-base --is-ancestor"
|
|
7037
|
-
});
|
|
7038
|
-
} catch {
|
|
7039
|
-
throw new Error(
|
|
7040
|
-
`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.`
|
|
7041
|
-
);
|
|
7042
|
-
}
|
|
7043
|
-
}
|
|
7044
|
-
async function completeIssueFinalReviewChangedPaths(options) {
|
|
7045
|
-
const actualChangedPaths = await listIssueFinalReviewChangedPaths2(options);
|
|
7046
|
-
const declared = new Set(options.declaredChangedPaths.map(normalizeGitPath2));
|
|
7047
|
-
const missing = actualChangedPaths.filter((path9) => !declared.has(path9));
|
|
7048
|
-
if (missing.length === 0) {
|
|
7049
|
-
return actualChangedPaths;
|
|
7050
|
-
}
|
|
7051
|
-
options.logger.step(
|
|
7052
|
-
"warn",
|
|
7053
|
-
`Issue Final Review pass omitted ${missing.length} changed file(s) from changedFiles; using git inventory for finalization state.`
|
|
7054
|
-
);
|
|
7055
|
-
options.logger.kv("MISSING_CHANGED_FILES", missing.join(", "));
|
|
7056
|
-
return actualChangedPaths;
|
|
7057
|
-
}
|
|
7058
|
-
async function listIssueFinalReviewChangedPaths2(options) {
|
|
7059
|
-
const trackedDiff = await execCapture(
|
|
7060
|
-
"git",
|
|
7061
|
-
["diff-index", "--name-only", options.baseRef, "--"],
|
|
7062
|
-
{
|
|
7063
|
-
cwd: options.worktreePath,
|
|
7064
|
-
logger: options.logger,
|
|
7065
|
-
label: "git diff-index Issue Final Review paths"
|
|
7066
|
-
}
|
|
7067
|
-
);
|
|
7068
|
-
const untrackedFiles = await execCapture(
|
|
7069
|
-
"git",
|
|
7070
|
-
["ls-files", "--others", "--exclude-standard"],
|
|
7071
|
-
{
|
|
7072
|
-
cwd: options.worktreePath,
|
|
7073
|
-
logger: options.logger,
|
|
7074
|
-
label: "git ls-files Issue Final Review paths"
|
|
7075
|
-
}
|
|
7076
|
-
);
|
|
7077
|
-
return Array.from(
|
|
7078
|
-
/* @__PURE__ */ new Set([
|
|
7079
|
-
...parseGitPathList2(trackedDiff.stdout),
|
|
7080
|
-
...parseGitPathList2(untrackedFiles.stdout)
|
|
7081
|
-
])
|
|
7082
|
-
).filter(isIssueFinalReviewAuditedPath2);
|
|
7083
|
-
}
|
|
7084
|
-
function parseGitPathList2(output) {
|
|
7085
|
-
return output.split(/\r?\n/).map(normalizeGitPath2).filter((path9) => path9.length > 0);
|
|
7086
|
-
}
|
|
7087
|
-
function normalizeGitPath2(path9) {
|
|
7088
|
-
return path9.replace(/^"|"$/g, "").replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
7089
|
-
}
|
|
7090
|
-
function isIssueFinalReviewAuditedPath2(path9) {
|
|
7091
|
-
return path9 !== "" && path9 !== WORKTREE_RUN_STATE_PATH && !path9.startsWith(".pourkit/.tmp/") && !path9.startsWith(".pourkit/logs/");
|
|
7092
|
-
}
|
|
7093
|
-
async function syncRemoteBaseRef2(worktreePath, baseRef, logger) {
|
|
7094
|
-
const remoteBase = parseRemoteBaseRef2(baseRef);
|
|
7095
|
-
if (!remoteBase) {
|
|
7096
|
-
return;
|
|
7097
|
-
}
|
|
7098
|
-
await execCapture("git", ["fetch", remoteBase.remote, remoteBase.branch], {
|
|
7099
|
-
cwd: worktreePath,
|
|
7100
|
-
logger,
|
|
7101
|
-
label: "git fetch target"
|
|
7102
|
-
});
|
|
7103
|
-
}
|
|
7104
|
-
function parseRemoteBaseRef2(baseRef) {
|
|
7105
|
-
const [remote, ...branchParts] = baseRef.split("/");
|
|
7106
|
-
const branch = branchParts.join("/");
|
|
7107
|
-
if (!remote || !branch) {
|
|
7108
|
-
return null;
|
|
7109
|
-
}
|
|
7110
|
-
return { remote, branch };
|
|
7111
|
-
}
|
|
7112
7024
|
function makeIssueTransitions2(provider, config) {
|
|
7113
7025
|
return createIssueTransitions(
|
|
7114
7026
|
{
|
|
@@ -7257,7 +7169,7 @@ async function runIssueLifecycle(options) {
|
|
|
7257
7169
|
worktreeState: startResult.worktreeState,
|
|
7258
7170
|
memory: startResult.memory
|
|
7259
7171
|
});
|
|
7260
|
-
if (finalReviewResult.
|
|
7172
|
+
if (finalReviewResult.status === "needs_human_review") {
|
|
7261
7173
|
await options.deps.transitionIssueFinalReviewNeedsHumanToHandoff({
|
|
7262
7174
|
issueNumber: options.issueNumber,
|
|
7263
7175
|
finalReviewResult
|
|
@@ -7451,7 +7363,7 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
|
|
|
7451
7363
|
|
|
7452
7364
|
// commands/prd-run.ts
|
|
7453
7365
|
import { lstatSync, realpathSync } from "fs";
|
|
7454
|
-
import { join as
|
|
7366
|
+
import { join as join16 } from "path";
|
|
7455
7367
|
|
|
7456
7368
|
// prd-run/state.ts
|
|
7457
7369
|
import {
|
|
@@ -7461,7 +7373,7 @@ import {
|
|
|
7461
7373
|
readdirSync as readdirSync3,
|
|
7462
7374
|
writeFileSync as writeFileSync4
|
|
7463
7375
|
} from "fs";
|
|
7464
|
-
import { join as
|
|
7376
|
+
import { join as join15 } from "path";
|
|
7465
7377
|
import { z } from "zod";
|
|
7466
7378
|
var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
|
|
7467
7379
|
var PrdRunRecordSchema = z.object({
|
|
@@ -7576,7 +7488,7 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7576
7488
|
}
|
|
7577
7489
|
}
|
|
7578
7490
|
function listPrdRuns(repoRoot2) {
|
|
7579
|
-
const stateDir =
|
|
7491
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7580
7492
|
if (!existsSync14(stateDir)) {
|
|
7581
7493
|
return { records: [], diagnostics: [] };
|
|
7582
7494
|
}
|
|
@@ -7586,7 +7498,7 @@ function listPrdRuns(repoRoot2) {
|
|
|
7586
7498
|
(left, right) => left.name.localeCompare(right.name)
|
|
7587
7499
|
)) {
|
|
7588
7500
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
7589
|
-
const recordPath =
|
|
7501
|
+
const recordPath = join15(stateDir, entry.name);
|
|
7590
7502
|
try {
|
|
7591
7503
|
const record = PrdRunRecordSchema.parse(
|
|
7592
7504
|
JSON.parse(readFileSync16(recordPath, "utf-8"))
|
|
@@ -7602,7 +7514,7 @@ function listPrdRuns(repoRoot2) {
|
|
|
7602
7514
|
}
|
|
7603
7515
|
function writePrdRunRecord(repoRoot2, record) {
|
|
7604
7516
|
const normalized = normalizePrdRunRef(record.prdRef);
|
|
7605
|
-
const stateDir =
|
|
7517
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7606
7518
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7607
7519
|
mkdirSync7(stateDir, { recursive: true });
|
|
7608
7520
|
writeFileSync4(
|
|
@@ -7612,7 +7524,7 @@ function writePrdRunRecord(repoRoot2, record) {
|
|
|
7612
7524
|
);
|
|
7613
7525
|
}
|
|
7614
7526
|
function getRecordPath(repoRoot2, prdRef) {
|
|
7615
|
-
return
|
|
7527
|
+
return join15(
|
|
7616
7528
|
repoRoot2,
|
|
7617
7529
|
PRD_RUN_STATE_DIR,
|
|
7618
7530
|
`${normalizePrdRunRef(prdRef)}.json`
|
|
@@ -12383,10 +12295,10 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
|
|
|
12383
12295
|
|
|
12384
12296
|
// commands/memory-init.ts
|
|
12385
12297
|
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync5 } from "fs";
|
|
12386
|
-
import { join as
|
|
12298
|
+
import { join as join17 } from "path";
|
|
12387
12299
|
async function runMemoryInitCommand(options) {
|
|
12388
12300
|
const cwd = options.cwd ?? process.cwd();
|
|
12389
|
-
const configPath =
|
|
12301
|
+
const configPath = join17(cwd, ".pourkit", "config.json");
|
|
12390
12302
|
if (!existsSync17(configPath)) {
|
|
12391
12303
|
return {
|
|
12392
12304
|
ok: false,
|
|
@@ -12418,8 +12330,8 @@ async function runMemoryInitCommand(options) {
|
|
|
12418
12330
|
const next = { ...raw, memory };
|
|
12419
12331
|
writeFileSync5(configPath, JSON.stringify(next, null, 2) + "\n");
|
|
12420
12332
|
}
|
|
12421
|
-
mkdirSync8(
|
|
12422
|
-
const gitignorePath =
|
|
12333
|
+
mkdirSync8(join17(cwd, ".pourkit", "icm"), { recursive: true });
|
|
12334
|
+
const gitignorePath = join17(cwd, ".gitignore");
|
|
12423
12335
|
updateManagedBlockSync(gitignorePath, generateGitignoreBlock());
|
|
12424
12336
|
const managed = await generateManagedAgentInstructions({
|
|
12425
12337
|
sourceRoot: cwd,
|
|
@@ -12427,14 +12339,14 @@ async function runMemoryInitCommand(options) {
|
|
|
12427
12339
|
});
|
|
12428
12340
|
let updatedAgentFile = false;
|
|
12429
12341
|
for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
|
|
12430
|
-
const agentPath =
|
|
12342
|
+
const agentPath = join17(cwd, fileName);
|
|
12431
12343
|
if (existsSync17(agentPath)) {
|
|
12432
12344
|
updateManagedBlockSync(agentPath, managed);
|
|
12433
12345
|
updatedAgentFile = true;
|
|
12434
12346
|
}
|
|
12435
12347
|
}
|
|
12436
12348
|
if (!updatedAgentFile) {
|
|
12437
|
-
updateManagedBlockSync(
|
|
12349
|
+
updateManagedBlockSync(join17(cwd, "AGENTS.md"), managed);
|
|
12438
12350
|
}
|
|
12439
12351
|
return {
|
|
12440
12352
|
ok: true,
|
|
@@ -14431,7 +14343,7 @@ init_common();
|
|
|
14431
14343
|
|
|
14432
14344
|
// execution/sandcastle-execution.ts
|
|
14433
14345
|
import { existsSync as existsSync21, mkdirSync as mkdirSync9, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
14434
|
-
import { join as
|
|
14346
|
+
import { join as join20 } from "path";
|
|
14435
14347
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
14436
14348
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
14437
14349
|
|
|
@@ -14440,10 +14352,10 @@ init_common();
|
|
|
14440
14352
|
import { mkdtempSync } from "fs";
|
|
14441
14353
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
14442
14354
|
import { tmpdir } from "os";
|
|
14443
|
-
import { dirname as dirname7, join as
|
|
14355
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
14444
14356
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
14445
14357
|
for (const artifact of artifacts) {
|
|
14446
|
-
const filePath =
|
|
14358
|
+
const filePath = join18(worktreePath, artifact.path);
|
|
14447
14359
|
await ensureDir(dirname7(filePath));
|
|
14448
14360
|
await writeFile4(filePath, artifact.content, "utf-8");
|
|
14449
14361
|
}
|
|
@@ -14505,7 +14417,7 @@ async function createSandboxFromExistingWorktree(options) {
|
|
|
14505
14417
|
}
|
|
14506
14418
|
|
|
14507
14419
|
// execution/sandbox-options.ts
|
|
14508
|
-
import { join as
|
|
14420
|
+
import { join as join19 } from "path";
|
|
14509
14421
|
var POURKIT_ICM_CONTAINER_DIR = "/home/agent/.pourkit/icm";
|
|
14510
14422
|
function buildSandboxOptions(repoRoot2, sandbox, memory) {
|
|
14511
14423
|
const mounts = [];
|
|
@@ -14514,12 +14426,12 @@ function buildSandboxOptions(repoRoot2, sandbox, memory) {
|
|
|
14514
14426
|
}
|
|
14515
14427
|
if (memory?.available) {
|
|
14516
14428
|
mounts.push({
|
|
14517
|
-
hostPath:
|
|
14429
|
+
hostPath: join19(repoRoot2, ".pourkit", "icm"),
|
|
14518
14430
|
sandboxPath: POURKIT_ICM_CONTAINER_DIR,
|
|
14519
14431
|
readonly: false
|
|
14520
14432
|
});
|
|
14521
14433
|
mounts.push({
|
|
14522
|
-
hostPath:
|
|
14434
|
+
hostPath: join19(repoRoot2, ".pourkit", "icm", "cache"),
|
|
14523
14435
|
sandboxPath: "/home/agent/.cache/icm",
|
|
14524
14436
|
readonly: false
|
|
14525
14437
|
});
|
|
@@ -14653,13 +14565,13 @@ var SandcastleExecutionSession = class {
|
|
|
14653
14565
|
"Memory enabled but memory context is invalid. Run `pourkit memory init` or install ICM for host planning use."
|
|
14654
14566
|
);
|
|
14655
14567
|
}
|
|
14656
|
-
const hostMemoryDir =
|
|
14568
|
+
const hostMemoryDir = join20(repoRoot2, ".pourkit", "icm");
|
|
14657
14569
|
if (!existsSync21(hostMemoryDir) || !statSync2(hostMemoryDir).isDirectory()) {
|
|
14658
14570
|
throw new MissingHostMemoryDirError(
|
|
14659
14571
|
"Memory enabled but host .pourkit/icm/ directory is missing. Run `pourkit memory init` to create the repo-scoped memory store."
|
|
14660
14572
|
);
|
|
14661
14573
|
}
|
|
14662
|
-
mkdirSync9(
|
|
14574
|
+
mkdirSync9(join20(hostMemoryDir, "cache"), { recursive: true });
|
|
14663
14575
|
}
|
|
14664
14576
|
const sandboxOptions = buildSandboxOptions(
|
|
14665
14577
|
repoRoot2,
|
|
@@ -14818,7 +14730,7 @@ function sanitizeBranch(branchName) {
|
|
|
14818
14730
|
}
|
|
14819
14731
|
function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
14820
14732
|
return paths.filter((relativePath) => {
|
|
14821
|
-
const source =
|
|
14733
|
+
const source = join20(repoRoot2, relativePath);
|
|
14822
14734
|
if (!existsSync21(source)) {
|
|
14823
14735
|
return true;
|
|
14824
14736
|
}
|
|
@@ -14829,7 +14741,7 @@ function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
|
14829
14741
|
} catch {
|
|
14830
14742
|
return true;
|
|
14831
14743
|
}
|
|
14832
|
-
const destination =
|
|
14744
|
+
const destination = join20(worktreePath, relativePath);
|
|
14833
14745
|
return !existsSync21(destination);
|
|
14834
14746
|
});
|
|
14835
14747
|
}
|
|
@@ -14894,12 +14806,12 @@ function isPlainObject(value) {
|
|
|
14894
14806
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14895
14807
|
}
|
|
14896
14808
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
14897
|
-
const promptsDir =
|
|
14809
|
+
const promptsDir = join20(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
14898
14810
|
mkdirSync9(promptsDir, { recursive: true });
|
|
14899
14811
|
const timestamp2 = Date.now();
|
|
14900
14812
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
14901
14813
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
14902
|
-
const filePath =
|
|
14814
|
+
const filePath = join20(promptsDir, filename);
|
|
14903
14815
|
writeFileSync6(filePath, prompt, "utf-8");
|
|
14904
14816
|
}
|
|
14905
14817
|
|
|
@@ -15620,11 +15532,11 @@ function createCliProgram(version) {
|
|
|
15620
15532
|
return program;
|
|
15621
15533
|
}
|
|
15622
15534
|
async function resolveCliVersion() {
|
|
15623
|
-
if (isPackageVersion("0.0.0-next-
|
|
15624
|
-
return "0.0.0-next-
|
|
15535
|
+
if (isPackageVersion("0.0.0-next-20260622201317")) {
|
|
15536
|
+
return "0.0.0-next-20260622201317";
|
|
15625
15537
|
}
|
|
15626
|
-
if (isReleaseVersion("0.0.0-next-
|
|
15627
|
-
return "0.0.0-next-
|
|
15538
|
+
if (isReleaseVersion("0.0.0-next-20260622201317")) {
|
|
15539
|
+
return "0.0.0-next-20260622201317";
|
|
15628
15540
|
}
|
|
15629
15541
|
try {
|
|
15630
15542
|
const root = repoRoot();
|