@pourkit/cli 0.0.0-next-20260621094003 → 0.0.0-next-20260621233457
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 +476 -310
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +447 -280
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/dist/managed/prompts/builder.prompt.md +5 -1
- package/dist/managed/prompts/issue-final-review.prompt.md +21 -0
- package/dist/managed/prompts/refactor.prompt.md +12 -1
- package/dist/managed/prompts/reviewer.prompt.md +14 -2
- package/dist/schema/pourkit.schema.hash +1 -1
- package/dist/schema/pourkit.schema.json +4 -12
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -442,6 +442,25 @@ function checkRemovedFields(raw) {
|
|
|
442
442
|
);
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
|
+
const strategy = target.strategy;
|
|
446
|
+
if (strategy && typeof strategy === "object") {
|
|
447
|
+
const review = strategy.review;
|
|
448
|
+
if (review && typeof review === "object") {
|
|
449
|
+
if ("passWithNotesRefactorAttempts" in review) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`targets[${i}].strategy.review.passWithNotesRefactorAttempts has been removed; PASS_WITH_NOTES no longer triggers Refactor.`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const reviewer = review.reviewer;
|
|
455
|
+
if (reviewer && typeof reviewer === "object") {
|
|
456
|
+
if ("passWithNotesRefactorAttempts" in reviewer) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
`targets[${i}].strategy.review.reviewer.passWithNotesRefactorAttempts has been removed; PASS_WITH_NOTES no longer triggers Refactor.`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
445
464
|
}
|
|
446
465
|
}
|
|
447
466
|
}
|
|
@@ -773,14 +792,12 @@ function parseConfig(raw) {
|
|
|
773
792
|
reviewReviewer.outputRetries
|
|
774
793
|
),
|
|
775
794
|
criteria: reviewReviewer.criteria,
|
|
776
|
-
includeReviewHistory: reviewReviewer.includeReviewHistory
|
|
777
|
-
passWithNotesRefactorAttempts: reviewReviewer.passWithNotesRefactorAttempts
|
|
795
|
+
includeReviewHistory: reviewReviewer.includeReviewHistory
|
|
778
796
|
},
|
|
779
797
|
refactor: applyStageAgentDefaults(
|
|
780
798
|
reviewRefactor
|
|
781
799
|
),
|
|
782
|
-
maxIterations: review.maxIterations
|
|
783
|
-
passWithNotesRefactorAttempts: review.passWithNotesRefactorAttempts ?? 2
|
|
800
|
+
maxIterations: review.maxIterations
|
|
784
801
|
},
|
|
785
802
|
...verifyLabeled ? { verify: { commands: verifyLabeled } } : {},
|
|
786
803
|
issueFinalReview: {
|
|
@@ -1168,9 +1185,11 @@ function parseReviewVerdict(output) {
|
|
|
1168
1185
|
}
|
|
1169
1186
|
|
|
1170
1187
|
// commands/review.ts
|
|
1188
|
+
import { Effect as Effect3 } from "effect";
|
|
1189
|
+
|
|
1190
|
+
// issues/issue-review-loop.ts
|
|
1171
1191
|
import {
|
|
1172
1192
|
existsSync as existsSync6,
|
|
1173
|
-
mkdirSync as mkdirSync5,
|
|
1174
1193
|
readFileSync as readFileSync6,
|
|
1175
1194
|
readdirSync as readdirSync2,
|
|
1176
1195
|
writeFileSync as writeFileSync3
|
|
@@ -1465,7 +1484,7 @@ function buildRunContextMarkdown(options) {
|
|
|
1465
1484
|
);
|
|
1466
1485
|
}
|
|
1467
1486
|
if (sections.includes("verification-commands")) {
|
|
1468
|
-
parts.push(...renderCommandList(target, "Verification Commands"));
|
|
1487
|
+
parts.push(...renderCommandList(target, "Verification Commands", repoRoot2));
|
|
1469
1488
|
}
|
|
1470
1489
|
if (sections.includes("review-criteria")) {
|
|
1471
1490
|
parts.push(...renderCriteria(reviewerCriteria));
|
|
@@ -1483,14 +1502,27 @@ function buildRunContextMarkdown(options) {
|
|
|
1483
1502
|
}
|
|
1484
1503
|
return parts.join("\n");
|
|
1485
1504
|
}
|
|
1486
|
-
function renderCommandList(target, heading) {
|
|
1505
|
+
function renderCommandList(target, heading, repoRoot2) {
|
|
1487
1506
|
const commands = getVerificationCommands(target);
|
|
1507
|
+
const wrapperCommand = buildRunVerificationCommand(target);
|
|
1488
1508
|
const parts = [
|
|
1489
1509
|
`## ${heading}`,
|
|
1490
1510
|
"",
|
|
1491
|
-
`Run this command from the repository root: \`${
|
|
1511
|
+
`Run this command from the repository root: \`${wrapperCommand}\``,
|
|
1512
|
+
"",
|
|
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.",
|
|
1492
1514
|
""
|
|
1493
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
|
+
}
|
|
1494
1526
|
if (commands.length > 0) {
|
|
1495
1527
|
parts.push(
|
|
1496
1528
|
"Configured commands executed by the wrapper:",
|
|
@@ -1503,6 +1535,17 @@ function renderCommandList(target, heading) {
|
|
|
1503
1535
|
}
|
|
1504
1536
|
return parts;
|
|
1505
1537
|
}
|
|
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
|
+
}
|
|
1506
1549
|
function renderParentPrdContext(issue, parentPrdIssue, repoRoot2) {
|
|
1507
1550
|
const parent = extractIssueSection(issue.body, "Parent");
|
|
1508
1551
|
const parentRef = parent?.match(/\bPRD-\d+\b/i)?.[0]?.toUpperCase() ?? issue.title.match(/^\s*(PRD-\d+)\s*\/\s*I-\d+\b/i)?.[1]?.toUpperCase();
|
|
@@ -1971,7 +2014,7 @@ function validateRecoveryDecision(artifact, allowedDecisions) {
|
|
|
1971
2014
|
return { valid: true, decision };
|
|
1972
2015
|
}
|
|
1973
2016
|
|
|
1974
|
-
//
|
|
2017
|
+
// issues/issue-review-loop.ts
|
|
1975
2018
|
import { Effect as Effect2, Layer as Layer2 } from "effect";
|
|
1976
2019
|
var ReviewArtifactValidationError = class extends Error {
|
|
1977
2020
|
constructor(message) {
|
|
@@ -2162,6 +2205,172 @@ function validateReviewArtifact(output, verdict, iteration, priorRefactorArtifac
|
|
|
2162
2205
|
}
|
|
2163
2206
|
}
|
|
2164
2207
|
}
|
|
2208
|
+
function renderReviewHistory(reviewHistory) {
|
|
2209
|
+
if (reviewHistory.length === 0) {
|
|
2210
|
+
return "";
|
|
2211
|
+
}
|
|
2212
|
+
return `## Review History
|
|
2213
|
+
|
|
2214
|
+
${reviewHistory.map((entry, index) => `### Iteration ${index + 1}
|
|
2215
|
+
|
|
2216
|
+
${entry.trimEnd()}`).join("\n\n")}
|
|
2217
|
+
|
|
2218
|
+
`;
|
|
2219
|
+
}
|
|
2220
|
+
function renderReviewCriteriaEffect(repoRoot2, criteria, fs) {
|
|
2221
|
+
return Effect2.gen(function* () {
|
|
2222
|
+
const parts = [];
|
|
2223
|
+
for (const criterion of criteria) {
|
|
2224
|
+
const snippetPath = join6(
|
|
2225
|
+
repoRoot2,
|
|
2226
|
+
".pourkit",
|
|
2227
|
+
"managed",
|
|
2228
|
+
"prompts",
|
|
2229
|
+
`reviewer-${criterion}.snippet.md`
|
|
2230
|
+
);
|
|
2231
|
+
const exists = yield* fs.exists(snippetPath).pipe(Effect2.orDie);
|
|
2232
|
+
if (exists) {
|
|
2233
|
+
const content = yield* fs.readFile(snippetPath).pipe(Effect2.orDie);
|
|
2234
|
+
parts.push(content.trimEnd());
|
|
2235
|
+
} else {
|
|
2236
|
+
parts.push(`- ${criterion}`);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
return parts.join("\n\n");
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
function loadReviewerPromptTemplateEffect(repoRoot2, promptTemplate, criteriaBlock, fs) {
|
|
2243
|
+
return Effect2.gen(function* () {
|
|
2244
|
+
const promptTemplatePath = resolvePromptTemplatePath(
|
|
2245
|
+
repoRoot2,
|
|
2246
|
+
promptTemplate
|
|
2247
|
+
);
|
|
2248
|
+
const exists = yield* fs.exists(promptTemplatePath).pipe(Effect2.orDie);
|
|
2249
|
+
const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect2.orDie) : promptTemplate;
|
|
2250
|
+
const hasCriteriaPlaceholder = promptBody.includes("{{REVIEW_CRITERIA}}");
|
|
2251
|
+
return {
|
|
2252
|
+
content: promptBody.replace(/\{\{REVIEW_CRITERIA\}\}/g, criteriaBlock),
|
|
2253
|
+
hasCriteriaPlaceholder
|
|
2254
|
+
};
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
function buildReviewerPromptEffect(repoRoot2, promptTemplate, criteria, artifactPathInWorktree, iteration, fs, reviewHistory = [], priorRefactorArtifacts, humanHandoffResolved, priorReviewerArtifacts) {
|
|
2258
|
+
return Effect2.gen(function* () {
|
|
2259
|
+
const criteriaBlock = yield* renderReviewCriteriaEffect(
|
|
2260
|
+
repoRoot2,
|
|
2261
|
+
criteria,
|
|
2262
|
+
fs
|
|
2263
|
+
);
|
|
2264
|
+
const { content: renderedTemplate, hasCriteriaPlaceholder } = yield* loadReviewerPromptTemplateEffect(
|
|
2265
|
+
repoRoot2,
|
|
2266
|
+
promptTemplate,
|
|
2267
|
+
criteriaBlock,
|
|
2268
|
+
fs
|
|
2269
|
+
);
|
|
2270
|
+
const priorRefactorBlock = priorRefactorArtifacts ? `${priorRefactorArtifacts}` : "";
|
|
2271
|
+
const priorRefactorProtocolReminder = priorRefactorArtifacts ? `## Prior Refactor Protocol Reminder
|
|
2272
|
+
|
|
2273
|
+
MANDATORY: because this prompt contains ## Prior Refactor Artifacts, your artifact must contain ## Prior Refactor Response Assessment exactly, before verdict. Section headings must match the names defined in this protocol exactly.
|
|
2274
|
+
|
|
2275
|
+
` : "";
|
|
2276
|
+
const priorReviewerBlock = priorReviewerArtifacts ? `${priorReviewerArtifacts}` : "";
|
|
2277
|
+
const humanHandoffBoundary = humanHandoffResolved ? `## Human-Resolved Handoff Boundary
|
|
2278
|
+
|
|
2279
|
+
A prior review emitted \`NEEDS_HUMAN\` and stopped the agent loop. The issue has since been moved back to \`ready-for-agent\`.
|
|
2280
|
+
|
|
2281
|
+
Before carrying forward old blockers, inspect newer issue comments and the current worktree. Treat prior Reviewer and Refactor Artifacts as historical context, not active findings unless they still apply.
|
|
2282
|
+
|
|
2283
|
+
` : "";
|
|
2284
|
+
return appendProtectedWorkGuidance(`${renderedTemplate}
|
|
2285
|
+
|
|
2286
|
+
## Shared Run Context
|
|
2287
|
+
|
|
2288
|
+
Read the selected issue requirements, planning context (including plan and PRD content when present), branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
2289
|
+
|
|
2290
|
+
## Initial Verification Pass
|
|
2291
|
+
|
|
2292
|
+
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
2293
|
+
- 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
|
+
- Run underlying verification commands directly only when no wrapper path can run, and record why verification used direct commands.
|
|
2296
|
+
- If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
|
|
2297
|
+
- 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.
|
|
2298
|
+
- If no verification commands are configured, note that and proceed with normal review.
|
|
2299
|
+
|
|
2300
|
+
## Scope Evidence Rules
|
|
2301
|
+
|
|
2302
|
+
- Use the Run Context's canonical base ref, for example \`origin/<base>\`, for scope diffs and commit ranges.
|
|
2303
|
+
- Do not use bare PRD/base branch names such as \`PRD-0063\`, \`main\`, or \`dev\` for scope decisions; local branches with those names may be stale.
|
|
2304
|
+
- Only call a file or commit out of scope when it is part of the working branch's delta from the canonical base ref.
|
|
2305
|
+
- Do not recommend reverting accepted sibling/base work. If a file is present or changed on the canonical base ref, it is not this Issue's scope overreach.
|
|
2306
|
+
- If scope evidence is ambiguous, use \`NEEDS_HUMAN\` or ask for a runner/base mismatch decision instead of telling Refactor to revert files.
|
|
2307
|
+
|
|
2308
|
+
${hasCriteriaPlaceholder ? "" : `## Review Criteria
|
|
2309
|
+
|
|
2310
|
+
${criteriaBlock}
|
|
2311
|
+
|
|
2312
|
+
`}${humanHandoffBoundary}${priorReviewerBlock}${renderReviewHistory(reviewHistory)}${priorRefactorBlock}${priorRefactorProtocolReminder}## Output
|
|
2313
|
+
|
|
2314
|
+
Write your review to: ${artifactPathInWorktree}
|
|
2315
|
+
|
|
2316
|
+
Before handoff, run: pourkit validate-artifact reviewer ${artifactPathInWorktree} --iteration ${iteration}${priorRefactorArtifacts ? " --prior-refactor-artifacts" : ""}
|
|
2317
|
+
|
|
2318
|
+
Do not provide a separate chat response. The runner only reads the file above.
|
|
2319
|
+
|
|
2320
|
+
End the file with exactly one wrapped verdict token: <verdict>PASS</verdict>, <verdict>PASS_WITH_NOTES</verdict>, <verdict>NEEDS_REFACTOR</verdict>, <verdict>FAIL</verdict>, or <verdict>NEEDS_HUMAN</verdict>. The verdict token must appear exactly once in the output.
|
|
2321
|
+
|
|
2322
|
+
Findings must include an ID column with values in the format R${iteration}.F{findingNumber} (e.g., R${iteration}.F1, R${iteration}.F2) and a Supersedes column referencing the finding ID being superseded (or a hyphen for new findings).
|
|
2323
|
+
|
|
2324
|
+
When verdict is NEEDS_HUMAN, include Human Handoff Summary and Human Handoff Reason sections before the final verdict token.`);
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
function recoverReviewOutputFromString(logContent) {
|
|
2328
|
+
const startIndex = logContent.indexOf("## Findings");
|
|
2329
|
+
if (startIndex === -1) return null;
|
|
2330
|
+
const verdictMatch = logContent.slice(startIndex).match(
|
|
2331
|
+
/<verdict>(PASS|PASS_WITH_NOTES|NEEDS_REFACTOR|FAIL|NEEDS_HUMAN)<\/verdict>/
|
|
2332
|
+
);
|
|
2333
|
+
if (!verdictMatch || verdictMatch.index === void 0) return null;
|
|
2334
|
+
const recoveredOutput = logContent.slice(startIndex, startIndex + verdictMatch.index + verdictMatch[0].length).trim();
|
|
2335
|
+
return recoveredOutput.length > 0 ? recoveredOutput : null;
|
|
2336
|
+
}
|
|
2337
|
+
function recoverReviewOutputFromLog(logPath) {
|
|
2338
|
+
if (!existsSync6(logPath)) {
|
|
2339
|
+
return null;
|
|
2340
|
+
}
|
|
2341
|
+
const logContent = readFileSync6(logPath, "utf-8");
|
|
2342
|
+
return recoverReviewOutputFromString(logContent);
|
|
2343
|
+
}
|
|
2344
|
+
function readReviewArtifact(artifactPath, logPath) {
|
|
2345
|
+
if (existsSync6(artifactPath)) {
|
|
2346
|
+
const output = readFileSync6(artifactPath, "utf-8");
|
|
2347
|
+
if (output.trim()) {
|
|
2348
|
+
return output;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
const recoveredOutput = logPath ? recoverReviewOutputFromLog(logPath) : null;
|
|
2352
|
+
if (recoveredOutput) {
|
|
2353
|
+
writeFileSync3(artifactPath, recoveredOutput, "utf-8");
|
|
2354
|
+
return recoveredOutput;
|
|
2355
|
+
}
|
|
2356
|
+
if (!existsSync6(artifactPath)) {
|
|
2357
|
+
throw new Error(`Reviewer did not produce output at ${artifactPath}`);
|
|
2358
|
+
}
|
|
2359
|
+
throw new Error(`Reviewer produced empty output at ${artifactPath}`);
|
|
2360
|
+
}
|
|
2361
|
+
function bridgeExecutionProvider(ep) {
|
|
2362
|
+
return Layer2.succeed(
|
|
2363
|
+
ExecutionProvider,
|
|
2364
|
+
ExecutionProvider.of({
|
|
2365
|
+
execute: (opts) => Effect2.tryPromise({
|
|
2366
|
+
try: () => ep.execute(opts),
|
|
2367
|
+
catch: (e) => new Error(
|
|
2368
|
+
e instanceof Error ? e.message : `Execution failed: ${String(e)}`
|
|
2369
|
+
)
|
|
2370
|
+
})
|
|
2371
|
+
})
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2165
2374
|
function runReviewCommandEffect(options) {
|
|
2166
2375
|
const {
|
|
2167
2376
|
executionProvider,
|
|
@@ -2314,100 +2523,43 @@ function runReviewCommandEffect(options) {
|
|
|
2314
2523
|
return { verdict: parsedVerdict, output, artifactPath };
|
|
2315
2524
|
});
|
|
2316
2525
|
}
|
|
2317
|
-
function
|
|
2318
|
-
return Layer2.succeed(
|
|
2319
|
-
ExecutionProvider,
|
|
2320
|
-
ExecutionProvider.of({
|
|
2321
|
-
execute: (opts) => Effect2.tryPromise({
|
|
2322
|
-
try: () => ep.execute(opts),
|
|
2323
|
-
catch: (e) => new Error(
|
|
2324
|
-
e instanceof Error ? e.message : `Execution failed: ${String(e)}`
|
|
2325
|
-
)
|
|
2326
|
-
})
|
|
2327
|
-
})
|
|
2328
|
-
);
|
|
2329
|
-
}
|
|
2330
|
-
function buildReviewerPromptEffect(repoRoot2, promptTemplate, criteria, artifactPathInWorktree, iteration, fs, reviewHistory = [], priorRefactorArtifacts, humanHandoffResolved, priorReviewerArtifacts) {
|
|
2526
|
+
function buildRefactorPromptEffect(repoRoot2, promptTemplate, latestReview, artifactPathInWorktree, iteration, fs) {
|
|
2331
2527
|
return Effect2.gen(function* () {
|
|
2332
|
-
const
|
|
2333
|
-
repoRoot2,
|
|
2334
|
-
criteria,
|
|
2335
|
-
fs
|
|
2336
|
-
);
|
|
2337
|
-
const { content: renderedTemplate, hasCriteriaPlaceholder } = yield* loadReviewerPromptTemplateEffect(
|
|
2528
|
+
const promptTemplatePath = resolvePromptTemplatePath(
|
|
2338
2529
|
repoRoot2,
|
|
2339
|
-
promptTemplate
|
|
2340
|
-
criteriaBlock,
|
|
2341
|
-
fs
|
|
2530
|
+
promptTemplate
|
|
2342
2531
|
);
|
|
2343
|
-
const
|
|
2344
|
-
const
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
` : "";
|
|
2349
|
-
const priorReviewerBlock = priorReviewerArtifacts ? `${priorReviewerArtifacts}` : "";
|
|
2350
|
-
const humanHandoffBoundary = humanHandoffResolved ? `## Human-Resolved Handoff Boundary
|
|
2351
|
-
|
|
2352
|
-
A prior review emitted \`NEEDS_HUMAN\` and stopped the agent loop. The issue has since been moved back to \`ready-for-agent\`.
|
|
2353
|
-
|
|
2354
|
-
Before carrying forward old blockers, inspect newer issue comments and the current worktree. Treat prior Reviewer and Refactor Artifacts as historical context, not active findings unless they still apply.
|
|
2355
|
-
|
|
2356
|
-
` : "";
|
|
2357
|
-
return appendProtectedWorkGuidance(`${renderedTemplate}
|
|
2532
|
+
const exists = yield* fs.exists(promptTemplatePath).pipe(Effect2.orDie);
|
|
2533
|
+
const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect2.orDie) : promptTemplate;
|
|
2534
|
+
const findingIds = extractLatestFindingIds(latestReview, iteration);
|
|
2535
|
+
const findingArgs = findingIds.map((id) => ` --finding-id ${id}`).join("");
|
|
2536
|
+
return appendProtectedWorkGuidance(`${promptBody}
|
|
2358
2537
|
|
|
2359
2538
|
## Shared Run Context
|
|
2360
2539
|
|
|
2361
2540
|
Read the selected issue requirements, planning context (including plan and PRD content when present), branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
2362
2541
|
|
|
2363
|
-
##
|
|
2364
|
-
|
|
2365
|
-
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
2366
|
-
- Before reviewing code, diffs, artifacts, or prior findings, run each configured verification command yourself from the Worktree.
|
|
2367
|
-
- Run the commands exactly as configured. Do not substitute narrower commands unless the configured command cannot run.
|
|
2368
|
-
- If a configured command fails, keep reviewing after recording the failure details; use the failure output as review evidence.
|
|
2369
|
-
- 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.
|
|
2370
|
-
- If no verification commands are configured, note that and proceed with normal review.
|
|
2542
|
+
## Verification Handoff
|
|
2371
2543
|
|
|
2372
|
-
|
|
2544
|
+
- 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
|
+
- Run underlying verification commands directly only when no wrapper path can run, and record why verification used direct commands in the Refactor Artifact.
|
|
2547
|
+
- If configured verification fails because of fixable Issue work, fix the work and rerun verification before handoff.
|
|
2548
|
+
- Failed or blocked verification prohibits <promise>COMPLETE</promise>.
|
|
2373
2549
|
|
|
2374
|
-
|
|
2375
|
-
- Do not use bare PRD/base branch names such as \`PRD-0063\`, \`main\`, or \`dev\` for scope decisions; local branches with those names may be stale.
|
|
2376
|
-
- Only call a file or commit out of scope when it is part of the working branch's delta from the canonical base ref.
|
|
2377
|
-
- Do not recommend reverting accepted sibling/base work. If a file is present or changed on the canonical base ref, it is not this Issue's scope overreach.
|
|
2378
|
-
- If scope evidence is ambiguous, use \`NEEDS_HUMAN\` or ask for a runner/base mismatch decision instead of telling Refactor to revert files.
|
|
2379
|
-
|
|
2380
|
-
${hasCriteriaPlaceholder ? "" : `## Review Criteria
|
|
2381
|
-
|
|
2382
|
-
${criteriaBlock}
|
|
2383
|
-
|
|
2384
|
-
`}${humanHandoffBoundary}${priorReviewerBlock}${renderReviewHistory(reviewHistory)}${priorRefactorBlock}${priorRefactorProtocolReminder}## Output
|
|
2385
|
-
|
|
2386
|
-
Write your review to: ${artifactPathInWorktree}
|
|
2550
|
+
## Latest Review
|
|
2387
2551
|
|
|
2388
|
-
|
|
2552
|
+
${latestReview.trimEnd()}
|
|
2389
2553
|
|
|
2390
|
-
|
|
2554
|
+
## Output
|
|
2391
2555
|
|
|
2392
|
-
|
|
2556
|
+
Write your refactor artifact to: ${artifactPathInWorktree}
|
|
2393
2557
|
|
|
2394
|
-
|
|
2558
|
+
Before handoff, run: pourkit validate-artifact refactor ${artifactPathInWorktree} --iteration ${iteration}${findingArgs}
|
|
2395
2559
|
|
|
2396
|
-
When
|
|
2560
|
+
When you are done, finish with <promise>COMPLETE</promise>.`);
|
|
2397
2561
|
});
|
|
2398
2562
|
}
|
|
2399
|
-
function renderReviewHistory(reviewHistory) {
|
|
2400
|
-
if (reviewHistory.length === 0) {
|
|
2401
|
-
return "";
|
|
2402
|
-
}
|
|
2403
|
-
return `## Review History
|
|
2404
|
-
|
|
2405
|
-
${reviewHistory.map((entry, index) => `### Iteration ${index + 1}
|
|
2406
|
-
|
|
2407
|
-
${entry.trimEnd()}`).join("\n\n")}
|
|
2408
|
-
|
|
2409
|
-
`;
|
|
2410
|
-
}
|
|
2411
2563
|
function renderPriorRefactorArtifactsEffect(worktreePath, currentIteration) {
|
|
2412
2564
|
return Effect2.gen(function* () {
|
|
2413
2565
|
const fb = yield* FileSystem;
|
|
@@ -2468,78 +2620,54 @@ ${iterationsBlocks}
|
|
|
2468
2620
|
`;
|
|
2469
2621
|
});
|
|
2470
2622
|
}
|
|
2471
|
-
function
|
|
2472
|
-
return Effect2.gen(function* () {
|
|
2473
|
-
const promptTemplatePath = resolvePromptTemplatePath(
|
|
2474
|
-
repoRoot2,
|
|
2475
|
-
promptTemplate
|
|
2476
|
-
);
|
|
2477
|
-
const exists = yield* fs.exists(promptTemplatePath).pipe(Effect2.orDie);
|
|
2478
|
-
const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect2.orDie) : promptTemplate;
|
|
2479
|
-
const hasCriteriaPlaceholder = promptBody.includes("{{REVIEW_CRITERIA}}");
|
|
2480
|
-
return {
|
|
2481
|
-
content: promptBody.replace(/\{\{REVIEW_CRITERIA\}\}/g, criteriaBlock),
|
|
2482
|
-
hasCriteriaPlaceholder
|
|
2483
|
-
};
|
|
2484
|
-
});
|
|
2485
|
-
}
|
|
2486
|
-
function renderReviewCriteriaEffect(repoRoot2, criteria, fs) {
|
|
2623
|
+
function persistIterationArtifactEffect(worktreePath, output, iteration, fs) {
|
|
2487
2624
|
return Effect2.gen(function* () {
|
|
2488
|
-
const
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
repoRoot2,
|
|
2492
|
-
".pourkit",
|
|
2493
|
-
"managed",
|
|
2494
|
-
"prompts",
|
|
2495
|
-
`reviewer-${criterion}.snippet.md`
|
|
2496
|
-
);
|
|
2497
|
-
const exists = yield* fs.exists(snippetPath).pipe(Effect2.orDie);
|
|
2498
|
-
if (exists) {
|
|
2499
|
-
const content = yield* fs.readFile(snippetPath).pipe(Effect2.orDie);
|
|
2500
|
-
parts.push(content.trimEnd());
|
|
2501
|
-
} else {
|
|
2502
|
-
parts.push(`- ${criterion}`);
|
|
2503
|
-
}
|
|
2504
|
-
}
|
|
2505
|
-
return parts.join("\n\n");
|
|
2625
|
+
const dir = join6(worktreePath, ".pourkit", ".tmp", "reviewers");
|
|
2626
|
+
yield* fs.mkdir(dir).pipe(Effect2.catchAll(() => Effect2.void));
|
|
2627
|
+
yield* fs.writeFile(join6(dir, `iteration-${iteration}.md`), output).pipe(Effect2.catchAll(() => Effect2.void));
|
|
2506
2628
|
});
|
|
2507
2629
|
}
|
|
2508
|
-
function
|
|
2509
|
-
const
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
}
|
|
2525
|
-
function readReviewArtifact(artifactPath, logPath) {
|
|
2526
|
-
if (existsSync6(artifactPath)) {
|
|
2527
|
-
const output = readFileSync6(artifactPath, "utf-8");
|
|
2528
|
-
if (output.trim()) {
|
|
2529
|
-
return output;
|
|
2530
|
-
}
|
|
2630
|
+
function mapResult(result) {
|
|
2631
|
+
const base = {
|
|
2632
|
+
output: result.output,
|
|
2633
|
+
iterations: result.iterations,
|
|
2634
|
+
lifetimeIterations: result.lifetimeIterations,
|
|
2635
|
+
refactorCompletedForLastReview: result.refactorCompletedForLastReview,
|
|
2636
|
+
refactorArtifactPaths: result.refactorArtifactPaths
|
|
2637
|
+
};
|
|
2638
|
+
if (result.exhaustedMaxIterations) {
|
|
2639
|
+
return {
|
|
2640
|
+
status: "exhausted_max_iterations",
|
|
2641
|
+
reviewVerdict: result.verdict === "FAIL" ? "FAIL" : "NEEDS_REFACTOR",
|
|
2642
|
+
reviewArtifactPath: result.artifactPath,
|
|
2643
|
+
...base,
|
|
2644
|
+
reason: "max_iterations"
|
|
2645
|
+
};
|
|
2531
2646
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2647
|
+
if (result.verdict === "NEEDS_HUMAN") {
|
|
2648
|
+
return {
|
|
2649
|
+
status: "needs_human",
|
|
2650
|
+
reviewVerdict: "NEEDS_HUMAN",
|
|
2651
|
+
reviewArtifactPath: result.artifactPath,
|
|
2652
|
+
...base
|
|
2653
|
+
};
|
|
2536
2654
|
}
|
|
2537
|
-
if (
|
|
2538
|
-
|
|
2655
|
+
if (result.verdict === "FAIL") {
|
|
2656
|
+
return {
|
|
2657
|
+
status: "needs_human",
|
|
2658
|
+
reviewVerdict: "NEEDS_HUMAN",
|
|
2659
|
+
reviewArtifactPath: result.artifactPath,
|
|
2660
|
+
...base
|
|
2661
|
+
};
|
|
2539
2662
|
}
|
|
2540
|
-
|
|
2663
|
+
return {
|
|
2664
|
+
status: "passed",
|
|
2665
|
+
reviewVerdict: result.verdict,
|
|
2666
|
+
reviewArtifactPath: result.artifactPath,
|
|
2667
|
+
...base
|
|
2668
|
+
};
|
|
2541
2669
|
}
|
|
2542
|
-
function
|
|
2670
|
+
function runIssueReviewLoop(options) {
|
|
2543
2671
|
const {
|
|
2544
2672
|
executionProvider,
|
|
2545
2673
|
config,
|
|
@@ -2565,7 +2693,6 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2565
2693
|
return Effect2.die(new Error("No refactorer config found"));
|
|
2566
2694
|
}
|
|
2567
2695
|
const maxIterations = strategy.review.maxIterations;
|
|
2568
|
-
const passWithNotesRefactorAttempts = strategy.review.passWithNotesRefactorAttempts;
|
|
2569
2696
|
let resolvedStartingIteration = startingLifetimeIteration;
|
|
2570
2697
|
{
|
|
2571
2698
|
const reviewersDir = join6(worktreePath, ".pourkit", ".tmp", "reviewers");
|
|
@@ -2598,7 +2725,6 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2598
2725
|
iteration: 0,
|
|
2599
2726
|
lastResult: null,
|
|
2600
2727
|
reviewHistory: [],
|
|
2601
|
-
passWithNotesRefactorAttemptsRemaining: passWithNotesRefactorAttempts,
|
|
2602
2728
|
accumulatedRefactorPaths: [],
|
|
2603
2729
|
doneResult: null
|
|
2604
2730
|
};
|
|
@@ -2654,11 +2780,7 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2654
2780
|
}
|
|
2655
2781
|
};
|
|
2656
2782
|
}
|
|
2657
|
-
if (reviewResult.verdict === "PASS_WITH_NOTES"
|
|
2658
|
-
logger.step(
|
|
2659
|
-
"info",
|
|
2660
|
-
"PASS_WITH_NOTES refactor attempts exhausted, treating as PASS"
|
|
2661
|
-
);
|
|
2783
|
+
if (reviewResult.verdict === "PASS_WITH_NOTES") {
|
|
2662
2784
|
return {
|
|
2663
2785
|
...s,
|
|
2664
2786
|
done: true,
|
|
@@ -2666,7 +2788,7 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2666
2788
|
lastResult: reviewResult,
|
|
2667
2789
|
reviewHistory: updatedHistory,
|
|
2668
2790
|
doneResult: {
|
|
2669
|
-
verdict:
|
|
2791
|
+
verdict: reviewResult.verdict,
|
|
2670
2792
|
output: reviewResult.output,
|
|
2671
2793
|
artifactPath: reviewResult.artifactPath,
|
|
2672
2794
|
iterations: iteration,
|
|
@@ -2677,14 +2799,6 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2677
2799
|
}
|
|
2678
2800
|
};
|
|
2679
2801
|
}
|
|
2680
|
-
let updatedPassWithNotesRefactorAttemptsRemaining = s.passWithNotesRefactorAttemptsRemaining;
|
|
2681
|
-
if (reviewResult.verdict === "PASS_WITH_NOTES") {
|
|
2682
|
-
updatedPassWithNotesRefactorAttemptsRemaining--;
|
|
2683
|
-
logger.step(
|
|
2684
|
-
"info",
|
|
2685
|
-
`PASS_WITH_NOTES refactor attempts remaining: ${updatedPassWithNotesRefactorAttemptsRemaining}`
|
|
2686
|
-
);
|
|
2687
|
-
}
|
|
2688
2802
|
if (reviewResult.verdict === "NEEDS_HUMAN") {
|
|
2689
2803
|
logger.step("info", "NEEDS_HUMAN verdict, stopping review loop");
|
|
2690
2804
|
return {
|
|
@@ -2705,7 +2819,7 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2705
2819
|
}
|
|
2706
2820
|
};
|
|
2707
2821
|
}
|
|
2708
|
-
if (reviewResult.verdict === "NEEDS_REFACTOR" || reviewResult.verdict === "
|
|
2822
|
+
if (reviewResult.verdict === "NEEDS_REFACTOR" || reviewResult.verdict === "FAIL") {
|
|
2709
2823
|
logger.step("info", "Running refactor agent");
|
|
2710
2824
|
const refactorArtifactPathInWorktree = join6(
|
|
2711
2825
|
".pourkit",
|
|
@@ -2861,7 +2975,6 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2861
2975
|
iteration,
|
|
2862
2976
|
lastResult: reviewResult,
|
|
2863
2977
|
reviewHistory: updatedHistory,
|
|
2864
|
-
passWithNotesRefactorAttemptsRemaining: updatedPassWithNotesRefactorAttemptsRemaining,
|
|
2865
2978
|
accumulatedRefactorPaths: updatedAccumulated,
|
|
2866
2979
|
doneResult: null
|
|
2867
2980
|
};
|
|
@@ -2871,7 +2984,6 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2871
2984
|
iteration,
|
|
2872
2985
|
lastResult: reviewResult,
|
|
2873
2986
|
reviewHistory: updatedHistory,
|
|
2874
|
-
passWithNotesRefactorAttemptsRemaining: updatedPassWithNotesRefactorAttemptsRemaining,
|
|
2875
2987
|
accumulatedRefactorPaths: s.accumulatedRefactorPaths,
|
|
2876
2988
|
doneResult: null
|
|
2877
2989
|
};
|
|
@@ -2882,7 +2994,7 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2882
2994
|
}
|
|
2883
2995
|
logger.step("warn", `Max review iterations (${maxIterations}) exhausted`);
|
|
2884
2996
|
return {
|
|
2885
|
-
verdict: "FAIL",
|
|
2997
|
+
verdict: finalState.lastResult?.verdict ?? "FAIL",
|
|
2886
2998
|
output: finalState.lastResult?.output ?? "",
|
|
2887
2999
|
artifactPath: finalState.lastResult?.artifactPath ?? "",
|
|
2888
3000
|
iterations: finalState.iteration,
|
|
@@ -2894,45 +3006,10 @@ function runReviewWithRefactorLoop(options) {
|
|
|
2894
3006
|
});
|
|
2895
3007
|
const executionLayer = bridgeExecutionProvider(executionProvider);
|
|
2896
3008
|
return program.pipe(
|
|
2897
|
-
Effect2.provide(Layer2.merge(executionLayer, FileSystemDefault))
|
|
3009
|
+
Effect2.provide(Layer2.merge(executionLayer, FileSystemDefault)),
|
|
3010
|
+
Effect2.map(mapResult)
|
|
2898
3011
|
);
|
|
2899
3012
|
}
|
|
2900
|
-
function persistIterationArtifactEffect(worktreePath, output, iteration, fs) {
|
|
2901
|
-
return Effect2.gen(function* () {
|
|
2902
|
-
const dir = join6(worktreePath, ".pourkit", ".tmp", "reviewers");
|
|
2903
|
-
yield* fs.mkdir(dir).pipe(Effect2.catchAll(() => Effect2.void));
|
|
2904
|
-
yield* fs.writeFile(join6(dir, `iteration-${iteration}.md`), output).pipe(Effect2.catchAll(() => Effect2.void));
|
|
2905
|
-
});
|
|
2906
|
-
}
|
|
2907
|
-
function buildRefactorPromptEffect(repoRoot2, promptTemplate, latestReview, artifactPathInWorktree, iteration, fs) {
|
|
2908
|
-
return Effect2.gen(function* () {
|
|
2909
|
-
const promptTemplatePath = resolvePromptTemplatePath(
|
|
2910
|
-
repoRoot2,
|
|
2911
|
-
promptTemplate
|
|
2912
|
-
);
|
|
2913
|
-
const exists = yield* fs.exists(promptTemplatePath).pipe(Effect2.orDie);
|
|
2914
|
-
const promptBody = exists ? yield* fs.readFile(promptTemplatePath).pipe(Effect2.orDie) : promptTemplate;
|
|
2915
|
-
const findingIds = extractLatestFindingIds(latestReview, iteration);
|
|
2916
|
-
const findingArgs = findingIds.map((id) => ` --finding-id ${id}`).join("");
|
|
2917
|
-
return appendProtectedWorkGuidance(`${promptBody}
|
|
2918
|
-
|
|
2919
|
-
## Shared Run Context
|
|
2920
|
-
|
|
2921
|
-
Read the selected issue requirements, planning context (including plan and PRD content when present), branch context, verification commands, and artifact paths from: ${RUN_CONTEXT_PATH_IN_WORKTREE}
|
|
2922
|
-
|
|
2923
|
-
## Latest Review
|
|
2924
|
-
|
|
2925
|
-
${latestReview.trimEnd()}
|
|
2926
|
-
|
|
2927
|
-
## Output
|
|
2928
|
-
|
|
2929
|
-
Write your refactor artifact to: ${artifactPathInWorktree}
|
|
2930
|
-
|
|
2931
|
-
Before handoff, run: pourkit validate-artifact refactor ${artifactPathInWorktree} --iteration ${iteration}${findingArgs}
|
|
2932
|
-
|
|
2933
|
-
When you are done, finish with <promise>COMPLETE</promise>.`);
|
|
2934
|
-
});
|
|
2935
|
-
}
|
|
2936
3013
|
|
|
2937
3014
|
// pr/pr-description.ts
|
|
2938
3015
|
var CONVENTIONAL_TITLE_PATTERN = /^(feat|fix|perf|refactor|docs|test|chore|ci|build)(\([^)]+\))?!?:\s+\S/;
|
|
@@ -3183,6 +3260,12 @@ var ISSUE_FINAL_REVIEW_SCOPE_VERDICTS = [
|
|
|
3183
3260
|
"incidental",
|
|
3184
3261
|
"out-of-scope"
|
|
3185
3262
|
];
|
|
3263
|
+
var ISSUE_FINAL_REVIEW_VERIFICATION_METHODS = [
|
|
3264
|
+
"wrapper",
|
|
3265
|
+
"local-wrapper",
|
|
3266
|
+
"commands",
|
|
3267
|
+
"none"
|
|
3268
|
+
];
|
|
3186
3269
|
function invalid(options, reason, diagnostics = []) {
|
|
3187
3270
|
return {
|
|
3188
3271
|
ok: false,
|
|
@@ -3490,6 +3573,15 @@ function validateIssueFinalReviewArtifact(parsed, options) {
|
|
|
3490
3573
|
diagnostics
|
|
3491
3574
|
};
|
|
3492
3575
|
}
|
|
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
|
+
}
|
|
3493
3585
|
diagnostics.push("verification: present");
|
|
3494
3586
|
const verificationWasRecorded = verification.required === true || Array.isArray(verification.commands) && verification.commands.length > 0;
|
|
3495
3587
|
if (parsed.verdict === "pass" && verificationWasRecorded) {
|
|
@@ -3504,6 +3596,20 @@ function validateIssueFinalReviewArtifact(parsed, options) {
|
|
|
3504
3596
|
]
|
|
3505
3597
|
};
|
|
3506
3598
|
}
|
|
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
|
+
}
|
|
3507
3613
|
diagnostics.push("pass verification: valid");
|
|
3508
3614
|
}
|
|
3509
3615
|
if (parsed.selfRetouched === true && parsed.verdict === "pass") {
|
|
@@ -3708,10 +3814,10 @@ init_common();
|
|
|
3708
3814
|
import { Exit as Exit3 } from "effect";
|
|
3709
3815
|
|
|
3710
3816
|
// failure-resolution/effect-runtime.ts
|
|
3711
|
-
import { Effect as
|
|
3817
|
+
import { Effect as Effect5 } from "effect";
|
|
3712
3818
|
|
|
3713
3819
|
// shared/effect-runtime.ts
|
|
3714
|
-
import { Effect as
|
|
3820
|
+
import { Effect as Effect4, Exit } from "effect";
|
|
3715
3821
|
import { UnknownException } from "effect/Cause";
|
|
3716
3822
|
var initialized = false;
|
|
3717
3823
|
function initializeEffectRuntime() {
|
|
@@ -3720,7 +3826,7 @@ function initializeEffectRuntime() {
|
|
|
3720
3826
|
}
|
|
3721
3827
|
function runEffect(program) {
|
|
3722
3828
|
ensureEffectRuntime();
|
|
3723
|
-
return
|
|
3829
|
+
return Effect4.runPromiseExit(program);
|
|
3724
3830
|
}
|
|
3725
3831
|
function ensureEffectRuntime() {
|
|
3726
3832
|
if (!initialized) {
|
|
@@ -3898,23 +4004,23 @@ function recordStageAttempt(worktreePath, record) {
|
|
|
3898
4004
|
|
|
3899
4005
|
// failure-resolution/effect-runtime.ts
|
|
3900
4006
|
function baseRefreshEffect(options) {
|
|
3901
|
-
return
|
|
3902
|
-
|
|
4007
|
+
return Effect5.promise(() => refreshStaleIssueBranch(options)).pipe(
|
|
4008
|
+
Effect5.flatMap(
|
|
3903
4009
|
(result) => {
|
|
3904
4010
|
switch (result.status) {
|
|
3905
4011
|
case "refreshed":
|
|
3906
|
-
return
|
|
4012
|
+
return Effect5.succeed({ status: "refreshed" });
|
|
3907
4013
|
case "skipped-current":
|
|
3908
|
-
return
|
|
4014
|
+
return Effect5.succeed({ status: "skipped-current" });
|
|
3909
4015
|
case "conflicted":
|
|
3910
|
-
return
|
|
4016
|
+
return Effect5.fail(
|
|
3911
4017
|
new RebaseConflict({
|
|
3912
4018
|
conflictedPaths: result.conflictedPaths,
|
|
3913
4019
|
message: result.message
|
|
3914
4020
|
})
|
|
3915
4021
|
);
|
|
3916
4022
|
case "refused-published-history":
|
|
3917
|
-
return
|
|
4023
|
+
return Effect5.fail(
|
|
3918
4024
|
new PublishedHistoryRisk({
|
|
3919
4025
|
prNumber: result.prNumber,
|
|
3920
4026
|
prState: result.prState
|
|
@@ -3929,8 +4035,8 @@ async function runBaseRefreshAttempt(options) {
|
|
|
3929
4035
|
const attemptId = createStageAttemptId();
|
|
3930
4036
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3931
4037
|
const program = baseRefreshEffect(options).pipe(
|
|
3932
|
-
|
|
3933
|
-
onSuccess: (success) =>
|
|
4038
|
+
Effect5.tapBoth({
|
|
4039
|
+
onSuccess: (success) => Effect5.sync(() => {
|
|
3934
4040
|
recordStageAttempt(options.worktreePath, {
|
|
3935
4041
|
id: attemptId,
|
|
3936
4042
|
stage: "baseRefresh",
|
|
@@ -3939,7 +4045,7 @@ async function runBaseRefreshAttempt(options) {
|
|
|
3939
4045
|
outcome: "success"
|
|
3940
4046
|
});
|
|
3941
4047
|
}),
|
|
3942
|
-
onFailure: (failure) =>
|
|
4048
|
+
onFailure: (failure) => Effect5.sync(() => {
|
|
3943
4049
|
recordStageAttempt(options.worktreePath, {
|
|
3944
4050
|
id: attemptId,
|
|
3945
4051
|
stage: "baseRefresh",
|
|
@@ -4862,7 +4968,15 @@ function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
|
4862
4968
|
|
|
4863
4969
|
## Shared Run Context
|
|
4864
4970
|
|
|
4865
|
-
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}
|
|
4971
|
+
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}
|
|
4972
|
+
|
|
4973
|
+
## Verification Handoff
|
|
4974
|
+
|
|
4975
|
+
- 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
|
+
- Run underlying verification commands directly only when no wrapper path can run, and report why verification used direct commands.
|
|
4978
|
+
- If configured verification fails because of fixable Issue work, fix the work and rerun verification before handoff.
|
|
4979
|
+
- Failed or blocked verification prohibits <promise>COMPLETE</promise>.`);
|
|
4866
4980
|
}
|
|
4867
4981
|
async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
|
|
4868
4982
|
const remoteBase = parseRemoteBaseRef(baseRef);
|
|
@@ -4907,9 +5021,9 @@ import { readFileSync as readFileSync12 } from "fs";
|
|
|
4907
5021
|
init_common();
|
|
4908
5022
|
import { join as join10 } from "path";
|
|
4909
5023
|
import { readFile } from "fs/promises";
|
|
4910
|
-
import { Effect as
|
|
5024
|
+
import { Effect as Effect6 } from "effect";
|
|
4911
5025
|
function collectFinalizerContextEffect(options) {
|
|
4912
|
-
return
|
|
5026
|
+
return Effect6.gen(function* () {
|
|
4913
5027
|
const git = yield* GitOperations;
|
|
4914
5028
|
const fs = yield* FileSystem;
|
|
4915
5029
|
const remoteBase = remoteTargetBase(options.targetBase);
|
|
@@ -4922,8 +5036,8 @@ function collectFinalizerContextEffect(options) {
|
|
|
4922
5036
|
],
|
|
4923
5037
|
{ cwd: options.worktreePath }
|
|
4924
5038
|
).pipe(
|
|
4925
|
-
|
|
4926
|
-
(error) =>
|
|
5039
|
+
Effect6.catchAll(
|
|
5040
|
+
(error) => Effect6.fail(
|
|
4927
5041
|
new Error(`Failed to collect commit range: ${error.message}`)
|
|
4928
5042
|
)
|
|
4929
5043
|
)
|
|
@@ -4938,9 +5052,9 @@ function collectFinalizerContextEffect(options) {
|
|
|
4938
5052
|
let reviewArtifact;
|
|
4939
5053
|
if (options.reviewArtifactPath) {
|
|
4940
5054
|
const content = yield* fs.readFile(options.reviewArtifactPath).pipe(
|
|
4941
|
-
|
|
5055
|
+
Effect6.catchIf(
|
|
4942
5056
|
(error) => error.message.includes("ENOENT") || error.message.includes("no such file"),
|
|
4943
|
-
() =>
|
|
5057
|
+
() => Effect6.fail(
|
|
4944
5058
|
new Error(
|
|
4945
5059
|
`Review artifact not found at ${options.reviewArtifactPath}. Ensure the review stage completed before running finalizer generation.`
|
|
4946
5060
|
)
|
|
@@ -4948,7 +5062,7 @@ function collectFinalizerContextEffect(options) {
|
|
|
4948
5062
|
)
|
|
4949
5063
|
);
|
|
4950
5064
|
if (!content.trim()) {
|
|
4951
|
-
return yield*
|
|
5065
|
+
return yield* Effect6.fail(
|
|
4952
5066
|
new Error(
|
|
4953
5067
|
`Review artifact at ${options.reviewArtifactPath} is empty. Ensure the review stage produced output before running finalizer generation.`
|
|
4954
5068
|
)
|
|
@@ -5024,12 +5138,12 @@ Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktre
|
|
|
5024
5138
|
}
|
|
5025
5139
|
|
|
5026
5140
|
// commands/pr-description-agent.ts
|
|
5027
|
-
import { Effect as
|
|
5141
|
+
import { Effect as Effect7, Layer as Layer3 } from "effect";
|
|
5028
5142
|
function bridgeExecutionProvider2(ep) {
|
|
5029
5143
|
return Layer3.succeed(
|
|
5030
5144
|
ExecutionProvider,
|
|
5031
5145
|
ExecutionProvider.of({
|
|
5032
|
-
execute: (opts) =>
|
|
5146
|
+
execute: (opts) => Effect7.tryPromise({
|
|
5033
5147
|
try: () => ep.execute(opts),
|
|
5034
5148
|
catch: (e) => new Error(
|
|
5035
5149
|
e instanceof Error ? e.message : `Execution failed: ${String(e)}`
|
|
@@ -5047,7 +5161,7 @@ function runFinalizerAgent(options) {
|
|
|
5047
5161
|
"agent-output.md"
|
|
5048
5162
|
);
|
|
5049
5163
|
const artifactPath = join11(options.worktreePath, artifactPathInWorktree);
|
|
5050
|
-
const program =
|
|
5164
|
+
const program = Effect7.gen(function* () {
|
|
5051
5165
|
const fs = yield* FileSystem;
|
|
5052
5166
|
const context = yield* collectFinalizerContextEffect({
|
|
5053
5167
|
targetBase: options.targetBaseBranch,
|
|
@@ -5056,8 +5170,8 @@ function runFinalizerAgent(options) {
|
|
|
5056
5170
|
reviewArtifactPath: options.reviewArtifactPath,
|
|
5057
5171
|
logger: options.logger
|
|
5058
5172
|
}).pipe(
|
|
5059
|
-
|
|
5060
|
-
(error) =>
|
|
5173
|
+
Effect7.catchAll(
|
|
5174
|
+
(error) => Effect7.fail(
|
|
5061
5175
|
new FinalizerFailure({
|
|
5062
5176
|
message: `Failed to collect finalizer context: ${error.message}`
|
|
5063
5177
|
})
|
|
@@ -5102,7 +5216,7 @@ function runFinalizerAgent(options) {
|
|
|
5102
5216
|
});
|
|
5103
5217
|
const executionLayer = bridgeExecutionProvider2(executionProvider);
|
|
5104
5218
|
return program.pipe(
|
|
5105
|
-
|
|
5219
|
+
Effect7.provide(
|
|
5106
5220
|
Layer3.mergeAll(executionLayer, FileSystemDefault, GitOperationsDefault)
|
|
5107
5221
|
)
|
|
5108
5222
|
);
|
|
@@ -5112,11 +5226,11 @@ function runPrDescriptionFinalizerCore(options) {
|
|
|
5112
5226
|
options.worktreePath,
|
|
5113
5227
|
options.artifactPathInWorktree
|
|
5114
5228
|
);
|
|
5115
|
-
return
|
|
5229
|
+
return Effect7.gen(function* () {
|
|
5116
5230
|
let parsed;
|
|
5117
5231
|
let lastValidationError;
|
|
5118
5232
|
for (let protocolAttempt = 1; protocolAttempt <= options.maxAttempts; protocolAttempt++) {
|
|
5119
|
-
const retryResult = yield*
|
|
5233
|
+
const retryResult = yield* Effect7.tryPromise({
|
|
5120
5234
|
try: () => executeWithMissingOrEmptyArtifactRetry({
|
|
5121
5235
|
executionProvider: options.executionProvider,
|
|
5122
5236
|
missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(
|
|
@@ -5153,7 +5267,7 @@ function runPrDescriptionFinalizerCore(options) {
|
|
|
5153
5267
|
});
|
|
5154
5268
|
const executionResult = retryResult.executionResult;
|
|
5155
5269
|
if (!executionResult.success) {
|
|
5156
|
-
return yield*
|
|
5270
|
+
return yield* Effect7.fail(
|
|
5157
5271
|
new FinalizerFailure({
|
|
5158
5272
|
message: `Finalizer agent execution failed: ${executionResult.error}`
|
|
5159
5273
|
})
|
|
@@ -5191,7 +5305,7 @@ function runPrDescriptionFinalizerCore(options) {
|
|
|
5191
5305
|
}
|
|
5192
5306
|
}
|
|
5193
5307
|
if (!parsed) {
|
|
5194
|
-
return yield*
|
|
5308
|
+
return yield* Effect7.fail(
|
|
5195
5309
|
new FinalizerFailure({
|
|
5196
5310
|
message: lastValidationError?.message ?? "Finalizer validation failed"
|
|
5197
5311
|
})
|
|
@@ -5206,20 +5320,20 @@ function runPrDescriptionFinalizerCore(options) {
|
|
|
5206
5320
|
});
|
|
5207
5321
|
}
|
|
5208
5322
|
function loadFinalizerPromptEffect(repoRoot2, promptTemplate, fs) {
|
|
5209
|
-
return
|
|
5323
|
+
return Effect7.gen(function* () {
|
|
5210
5324
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
5211
|
-
const exists = yield* fs.exists(promptPath).pipe(
|
|
5325
|
+
const exists = yield* fs.exists(promptPath).pipe(Effect7.orDie);
|
|
5212
5326
|
if (exists) {
|
|
5213
|
-
return yield* fs.readFile(promptPath).pipe(
|
|
5327
|
+
return yield* fs.readFile(promptPath).pipe(Effect7.orDie);
|
|
5214
5328
|
}
|
|
5215
5329
|
return promptTemplate;
|
|
5216
5330
|
});
|
|
5217
5331
|
}
|
|
5218
5332
|
function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
5219
|
-
return
|
|
5333
|
+
return Effect7.gen(function* () {
|
|
5220
5334
|
const dir = join11(worktreePath, ".pourkit", ".tmp", "finalizer");
|
|
5221
|
-
yield* fs.mkdir(dir).pipe(
|
|
5222
|
-
yield* fs.writeFile(join11(dir, "generated.md"), output).pipe(
|
|
5335
|
+
yield* fs.mkdir(dir).pipe(Effect7.catchAll(() => Effect7.void));
|
|
5336
|
+
yield* fs.writeFile(join11(dir, "generated.md"), output).pipe(Effect7.catchAll(() => Effect7.void));
|
|
5223
5337
|
});
|
|
5224
5338
|
}
|
|
5225
5339
|
|
|
@@ -5559,7 +5673,7 @@ function secondsRemaining(deadline, observedAt) {
|
|
|
5559
5673
|
}
|
|
5560
5674
|
|
|
5561
5675
|
// issues/merge-coordinator.ts
|
|
5562
|
-
import { Effect as
|
|
5676
|
+
import { Effect as Effect8, Either } from "effect";
|
|
5563
5677
|
import { UnknownException as UnknownException2 } from "effect/Cause";
|
|
5564
5678
|
function unwrapError2(error) {
|
|
5565
5679
|
if (error instanceof UnknownException2 && error.error !== void 0) {
|
|
@@ -5578,9 +5692,9 @@ function runMergeCoordinator(options) {
|
|
|
5578
5692
|
} = options;
|
|
5579
5693
|
const method = options.method ?? "squash";
|
|
5580
5694
|
const waitForTargetGreen = options.waitForTargetGreen ?? true;
|
|
5581
|
-
return
|
|
5582
|
-
const checksEither = yield*
|
|
5583
|
-
|
|
5695
|
+
return Effect8.gen(function* () {
|
|
5696
|
+
const checksEither = yield* Effect8.either(
|
|
5697
|
+
Effect8.tryPromise(
|
|
5584
5698
|
() => prProvider.waitForPrChecks(prNumber, checkWaitOptions)
|
|
5585
5699
|
)
|
|
5586
5700
|
);
|
|
@@ -5593,8 +5707,8 @@ function runMergeCoordinator(options) {
|
|
|
5593
5707
|
error: error instanceof Error ? error : new Error(String(error))
|
|
5594
5708
|
};
|
|
5595
5709
|
}
|
|
5596
|
-
const mergeEither = yield*
|
|
5597
|
-
|
|
5710
|
+
const mergeEither = yield* Effect8.either(
|
|
5711
|
+
Effect8.tryPromise(
|
|
5598
5712
|
() => prProvider.mergePr(prNumber, { method, matchHeadCommit })
|
|
5599
5713
|
)
|
|
5600
5714
|
);
|
|
@@ -5608,8 +5722,8 @@ function runMergeCoordinator(options) {
|
|
|
5608
5722
|
};
|
|
5609
5723
|
}
|
|
5610
5724
|
if (waitForTargetGreen) {
|
|
5611
|
-
const targetEither = yield*
|
|
5612
|
-
|
|
5725
|
+
const targetEither = yield* Effect8.either(
|
|
5726
|
+
Effect8.tryPromise(
|
|
5613
5727
|
() => waitForBranchChecks(prProvider, logger, {
|
|
5614
5728
|
branchName: targetBranch,
|
|
5615
5729
|
checksFoundTimeoutMs: checkWaitOptions.checksFoundTimeoutMs,
|
|
@@ -5808,6 +5922,9 @@ function buildIssueFinalReviewPrComment(artifact) {
|
|
|
5808
5922
|
`- **Required:** ${formatBoolean(artifact.verification.required)}`,
|
|
5809
5923
|
`- **Passed:** ${formatBoolean(artifact.verification.passed)}`
|
|
5810
5924
|
);
|
|
5925
|
+
if (typeof artifact.verification.method === "string") {
|
|
5926
|
+
lines.push(`- **Method:** ${formatLabel(artifact.verification.method)}`);
|
|
5927
|
+
}
|
|
5811
5928
|
const commands = Array.isArray(artifact.verification.commands) ? artifact.verification.commands.filter(
|
|
5812
5929
|
(command) => typeof command === "string"
|
|
5813
5930
|
) : [];
|
|
@@ -6005,7 +6122,7 @@ function findSecretLikeContent(source, content) {
|
|
|
6005
6122
|
];
|
|
6006
6123
|
for (const { kind, regex } of patterns) {
|
|
6007
6124
|
for (const match of content.matchAll(regex)) {
|
|
6008
|
-
if (isAllowedSecretPlaceholder(match[0])) {
|
|
6125
|
+
if (isAllowedSecretPlaceholder(match[0]) || isAllowedDocumentationTokenReference(match[0])) {
|
|
6009
6126
|
continue;
|
|
6010
6127
|
}
|
|
6011
6128
|
findings.push({ source, kind });
|
|
@@ -6026,6 +6143,9 @@ function isAllowedSecretPlaceholder(value) {
|
|
|
6026
6143
|
"xxxxx"
|
|
6027
6144
|
].some((allowed) => normalized.includes(allowed));
|
|
6028
6145
|
}
|
|
6146
|
+
function isAllowedDocumentationTokenReference(value) {
|
|
6147
|
+
return /^token\s*:\s*["']?<verdict>/i.test(value);
|
|
6148
|
+
}
|
|
6029
6149
|
|
|
6030
6150
|
// commands/issue-final-review-agent.ts
|
|
6031
6151
|
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
@@ -6055,7 +6175,9 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
6055
6175
|
const prompt = loadIssueFinalReviewPrompt({
|
|
6056
6176
|
repoRoot: repoRoot2,
|
|
6057
6177
|
promptTemplate: agent.promptTemplate,
|
|
6058
|
-
validationCommand: `pourkit validate-artifact issue-final-review ${artifactPathInWorktree} --issue-number ${issue.number} --branch-name ${builderBranch} --base-ref origin/${target.baseBranch}
|
|
6178
|
+
validationCommand: `pourkit validate-artifact issue-final-review ${artifactPathInWorktree} --issue-number ${issue.number} --branch-name ${builderBranch} --base-ref origin/${target.baseBranch}`,
|
|
6179
|
+
reviewArtifactPath: options.reviewArtifactPath,
|
|
6180
|
+
reviewVerdict: options.reviewVerdict
|
|
6059
6181
|
});
|
|
6060
6182
|
const entry = await executeWithMissingOrEmptyArtifactRetry({
|
|
6061
6183
|
executionProvider,
|
|
@@ -6151,10 +6273,16 @@ function extractChangedPaths(parsed) {
|
|
|
6151
6273
|
).filter((path9) => typeof path9 === "string") : [];
|
|
6152
6274
|
}
|
|
6153
6275
|
function loadIssueFinalReviewPrompt(options) {
|
|
6154
|
-
const {
|
|
6276
|
+
const {
|
|
6277
|
+
repoRoot: repoRoot2,
|
|
6278
|
+
promptTemplate,
|
|
6279
|
+
validationCommand,
|
|
6280
|
+
reviewArtifactPath,
|
|
6281
|
+
reviewVerdict
|
|
6282
|
+
} = options;
|
|
6155
6283
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
6156
6284
|
const promptBody = existsSync12(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
|
|
6157
|
-
|
|
6285
|
+
let prompt = appendProtectedWorkGuidance(`${promptBody}
|
|
6158
6286
|
|
|
6159
6287
|
## Shared Run Context
|
|
6160
6288
|
|
|
@@ -6163,8 +6291,10 @@ Read the selected issue requirements, planning context (including plan and PRD c
|
|
|
6163
6291
|
## Initial Verification Pass
|
|
6164
6292
|
|
|
6165
6293
|
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
6166
|
-
- Before reviewing code, diffs, artifacts, or prior findings, run
|
|
6167
|
-
-
|
|
6294
|
+
- Before reviewing code, diffs, artifacts, or prior findings, run the verification wrapper from the Worktree.
|
|
6295
|
+
- If the installed Pourkit CLI rejects the worktree config schemaVersion and the Run Context provides a Pourkit self-development fallback, build and run the worktree-local CLI wrapper exactly as instructed.
|
|
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"\`.
|
|
6168
6298
|
- If a configured verification command fails because of fixable Issue work, self-retouch the Worktree, then rerun all configured verification commands.
|
|
6169
6299
|
- Do not return \`pass\` while any configured verification command is failing.
|
|
6170
6300
|
- 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.
|
|
@@ -6186,6 +6316,19 @@ Read the selected issue requirements, planning context (including plan and PRD c
|
|
|
6186
6316
|
- This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
|
|
6187
6317
|
- If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
|
|
6188
6318
|
- Do not finish until the validation command passes.`);
|
|
6319
|
+
if (reviewVerdict) {
|
|
6320
|
+
prompt += `
|
|
6321
|
+
|
|
6322
|
+
## Prior Review Context
|
|
6323
|
+
|
|
6324
|
+
- Reviewer Artifact Path: ${reviewArtifactPath}
|
|
6325
|
+
- Prior Review Verdict: ${reviewVerdict}`;
|
|
6326
|
+
if (reviewVerdict === "PASS_WITH_NOTES") {
|
|
6327
|
+
prompt += `
|
|
6328
|
+
- This Issue passed the prior Reviewer with notes. Read the Reviewer Artifact and inspect its notes before making your final pass decision.`;
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
return prompt;
|
|
6189
6332
|
}
|
|
6190
6333
|
|
|
6191
6334
|
// issues/issue-worktree-resolution.ts
|
|
@@ -6375,6 +6518,7 @@ async function advanceIssueFinalReview(options) {
|
|
|
6375
6518
|
repoRoot: repoRoot2,
|
|
6376
6519
|
logger,
|
|
6377
6520
|
reviewArtifactPath,
|
|
6521
|
+
reviewVerdict,
|
|
6378
6522
|
worktreeState
|
|
6379
6523
|
} = options;
|
|
6380
6524
|
const ifrFromState = worktreeState?.issueFinalReview;
|
|
@@ -6415,6 +6559,7 @@ async function advanceIssueFinalReview(options) {
|
|
|
6415
6559
|
repoRoot: repoRoot2,
|
|
6416
6560
|
logger,
|
|
6417
6561
|
reviewArtifactPath,
|
|
6562
|
+
reviewVerdict,
|
|
6418
6563
|
...options.memory ? { memory: options.memory } : {}
|
|
6419
6564
|
});
|
|
6420
6565
|
await assertCanonicalBaseAncestor2({
|
|
@@ -6580,7 +6725,7 @@ async function fetchParentPrdIssue(issue, issueProvider) {
|
|
|
6580
6725
|
async function advanceIssueRunReview(options) {
|
|
6581
6726
|
const accumulatedRefactorPaths = [];
|
|
6582
6727
|
const reviewResult = await runEffectAndMapExit(
|
|
6583
|
-
|
|
6728
|
+
runIssueReviewLoop({
|
|
6584
6729
|
...options,
|
|
6585
6730
|
onRefactorProgress: (progress) => {
|
|
6586
6731
|
if (progress.refactorArtifactPath) {
|
|
@@ -6603,16 +6748,29 @@ async function advanceIssueRunReview(options) {
|
|
|
6603
6748
|
stageName: "Review/Refactor",
|
|
6604
6749
|
logger: options.logger
|
|
6605
6750
|
});
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6751
|
+
const commonFields = {
|
|
6752
|
+
lifetimeIterations: reviewResult.lifetimeIterations,
|
|
6753
|
+
lastArtifactPath: reviewResult.reviewArtifactPath,
|
|
6754
|
+
refactorCompletedForLastReview: reviewResult.refactorCompletedForLastReview,
|
|
6755
|
+
refactorArtifactPaths: accumulatedRefactorPaths.length > 0 ? accumulatedRefactorPaths : void 0
|
|
6756
|
+
};
|
|
6757
|
+
if (reviewResult.status === "passed" || reviewResult.status === "needs_human") {
|
|
6758
|
+
updateWorktreeRunState(options.worktreePath, {
|
|
6759
|
+
review: {
|
|
6760
|
+
...commonFields,
|
|
6761
|
+
lastVerdict: reviewResult.reviewVerdict,
|
|
6762
|
+
exhaustedPreviousRun: void 0
|
|
6763
|
+
}
|
|
6764
|
+
});
|
|
6765
|
+
} else {
|
|
6766
|
+
updateWorktreeRunState(options.worktreePath, {
|
|
6767
|
+
review: {
|
|
6768
|
+
...commonFields,
|
|
6769
|
+
lastVerdict: reviewResult.reviewVerdict,
|
|
6770
|
+
exhaustedPreviousRun: true
|
|
6771
|
+
}
|
|
6772
|
+
});
|
|
6773
|
+
}
|
|
6616
6774
|
return reviewResult;
|
|
6617
6775
|
}
|
|
6618
6776
|
async function completeIssueRun(options) {
|
|
@@ -6810,14 +6968,14 @@ async function transitionIssueToHumanHandoff(options) {
|
|
|
6810
6968
|
const transitions = makeIssueTransitions2(issueProvider, config);
|
|
6811
6969
|
await transitions.moveToReadyForHuman(issueNumber);
|
|
6812
6970
|
const summary = extractHumanHandoffSummary(reviewResult.output);
|
|
6813
|
-
const refactorDir = getRefactorArtifactDir(reviewResult.
|
|
6971
|
+
const refactorDir = getRefactorArtifactDir(reviewResult.reviewArtifactPath);
|
|
6814
6972
|
const comment = [
|
|
6815
6973
|
"Pourkit stopped the review/refactor loop because human input is needed.",
|
|
6816
6974
|
"",
|
|
6817
6975
|
summary,
|
|
6818
6976
|
"",
|
|
6819
6977
|
"Artifacts:",
|
|
6820
|
-
`- Review: ${reviewResult.
|
|
6978
|
+
`- Review: ${reviewResult.reviewArtifactPath}`,
|
|
6821
6979
|
`- Refactors: ${refactorDir}`
|
|
6822
6980
|
].join("\n");
|
|
6823
6981
|
await issueProvider.commentIssue(issueNumber, comment);
|
|
@@ -7035,8 +7193,10 @@ async function runIssueLifecycle(options) {
|
|
|
7035
7193
|
const startResult = await options.deps.startIssueRun(options.runOptions);
|
|
7036
7194
|
const reviewAlreadyPassed = startResult.worktreeState?.review.lastVerdict && !startResult.worktreeState.review.exhaustedPreviousRun && (startResult.worktreeState.review.lastVerdict === "PASS" || startResult.worktreeState.review.lastVerdict === "PASS_WITH_NOTES");
|
|
7037
7195
|
let reviewArtifactPath;
|
|
7196
|
+
let reviewVerdict = void 0;
|
|
7038
7197
|
if (reviewAlreadyPassed) {
|
|
7039
7198
|
reviewArtifactPath = startResult.worktreeState.review.lastArtifactPath;
|
|
7199
|
+
reviewVerdict = startResult.worktreeState.review.lastVerdict;
|
|
7040
7200
|
} else {
|
|
7041
7201
|
const reviewResult = await options.deps.advanceIssueRunReview({
|
|
7042
7202
|
executionProvider: options.runOptions.executionProvider,
|
|
@@ -7053,28 +7213,34 @@ async function runIssueLifecycle(options) {
|
|
|
7053
7213
|
serena: startResult.serena,
|
|
7054
7214
|
memory: startResult.memory
|
|
7055
7215
|
});
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7216
|
+
switch (reviewResult.status) {
|
|
7217
|
+
case "passed":
|
|
7218
|
+
reviewArtifactPath = reviewResult.reviewArtifactPath;
|
|
7219
|
+
reviewVerdict = reviewResult.reviewVerdict;
|
|
7220
|
+
break;
|
|
7221
|
+
case "needs_human":
|
|
7222
|
+
await options.deps.transitionReviewNeedsHumanToHandoff({
|
|
7223
|
+
issueProvider: options.issueProvider,
|
|
7224
|
+
issueNumber: options.issueNumber,
|
|
7225
|
+
config: options.config,
|
|
7226
|
+
logger: options.logger,
|
|
7227
|
+
reviewResult
|
|
7228
|
+
});
|
|
7229
|
+
throw new IssueRunHumanHandoffStop(
|
|
7230
|
+
"Review requires human handoff: NEEDS_HUMAN verdict"
|
|
7231
|
+
);
|
|
7232
|
+
case "exhausted_max_iterations":
|
|
7233
|
+
await options.deps.transitionReviewNeedsHumanToHandoff({
|
|
7234
|
+
issueProvider: options.issueProvider,
|
|
7235
|
+
issueNumber: options.issueNumber,
|
|
7236
|
+
config: options.config,
|
|
7237
|
+
logger: options.logger,
|
|
7238
|
+
reviewResult
|
|
7239
|
+
});
|
|
7240
|
+
throw new IssueRunHumanHandoffStop(
|
|
7241
|
+
"Review exhausted maximum iterations - requires human handoff"
|
|
7242
|
+
);
|
|
7076
7243
|
}
|
|
7077
|
-
reviewArtifactPath = reviewResult.artifactPath;
|
|
7078
7244
|
}
|
|
7079
7245
|
const finalReviewResult = await options.deps.advanceIssueFinalReview({
|
|
7080
7246
|
executionProvider: options.runOptions.executionProvider,
|
|
@@ -7087,6 +7253,7 @@ async function runIssueLifecycle(options) {
|
|
|
7087
7253
|
repoRoot: options.repoRoot,
|
|
7088
7254
|
logger: options.logger,
|
|
7089
7255
|
reviewArtifactPath,
|
|
7256
|
+
reviewVerdict,
|
|
7090
7257
|
worktreeState: startResult.worktreeState,
|
|
7091
7258
|
memory: startResult.memory
|
|
7092
7259
|
});
|
|
@@ -7458,7 +7625,7 @@ import { Match, pipe } from "effect";
|
|
|
7458
7625
|
|
|
7459
7626
|
// commands/queue.ts
|
|
7460
7627
|
init_common();
|
|
7461
|
-
import { Effect as
|
|
7628
|
+
import { Effect as Effect9 } from "effect";
|
|
7462
7629
|
|
|
7463
7630
|
// issues/select-issue.ts
|
|
7464
7631
|
init_common();
|
|
@@ -7631,8 +7798,8 @@ function issueDataToCandidate(issue) {
|
|
|
7631
7798
|
function selectNextQueueIssue(options) {
|
|
7632
7799
|
const { config, issueProvider, logger, prdRef } = options;
|
|
7633
7800
|
logger.step("info", "Loading candidate issues from provider");
|
|
7634
|
-
return
|
|
7635
|
-
const candidates = yield*
|
|
7801
|
+
return Effect9.gen(function* () {
|
|
7802
|
+
const candidates = yield* Effect9.tryPromise({
|
|
7636
7803
|
try: () => issueProvider.listCandidates(),
|
|
7637
7804
|
catch: (e) => {
|
|
7638
7805
|
if (e instanceof Error) {
|
|
@@ -7703,7 +7870,7 @@ function selectNextQueueIssue(options) {
|
|
|
7703
7870
|
(c) => c.number === selection.issue.number
|
|
7704
7871
|
);
|
|
7705
7872
|
if (!selected) {
|
|
7706
|
-
return yield*
|
|
7873
|
+
return yield* Effect9.die(
|
|
7707
7874
|
new Error(
|
|
7708
7875
|
`Selected issue #${selection.issue.number} not found in candidate list`
|
|
7709
7876
|
)
|
|
@@ -7758,8 +7925,8 @@ function makeReconcileDeps(options) {
|
|
|
7758
7925
|
};
|
|
7759
7926
|
}
|
|
7760
7927
|
function reconcileBlockedEffect(options) {
|
|
7761
|
-
return
|
|
7762
|
-
const blocked2 = yield*
|
|
7928
|
+
return Effect9.gen(function* () {
|
|
7929
|
+
const blocked2 = yield* Effect9.tryPromise({
|
|
7763
7930
|
try: () => options.issueProvider.listBlockedIssues(),
|
|
7764
7931
|
catch: (e) => {
|
|
7765
7932
|
if (e instanceof Error) {
|
|
@@ -7773,7 +7940,7 @@ function reconcileBlockedEffect(options) {
|
|
|
7773
7940
|
"info",
|
|
7774
7941
|
`Reconciling ${blocked2.length} blocked issue(s)`
|
|
7775
7942
|
);
|
|
7776
|
-
yield*
|
|
7943
|
+
yield* Effect9.tryPromise({
|
|
7777
7944
|
try: () => reconcileBlockedIssues(blocked2, makeReconcileDeps(options)),
|
|
7778
7945
|
catch: (e) => {
|
|
7779
7946
|
if (e instanceof Error) {
|
|
@@ -7797,7 +7964,7 @@ function runOneQueueIssueEffect(options) {
|
|
|
7797
7964
|
repoRoot: repoRoot2,
|
|
7798
7965
|
prdRef
|
|
7799
7966
|
} = options;
|
|
7800
|
-
return
|
|
7967
|
+
return Effect9.gen(function* () {
|
|
7801
7968
|
const outcome = yield* selectNextQueueIssue({
|
|
7802
7969
|
config,
|
|
7803
7970
|
issueProvider,
|
|
@@ -7813,7 +7980,7 @@ function runOneQueueIssueEffect(options) {
|
|
|
7813
7980
|
}
|
|
7814
7981
|
const { issue: selected } = outcome;
|
|
7815
7982
|
const baseBranchOverride = options.queueRunContext?.prdBranch;
|
|
7816
|
-
const runResult = yield*
|
|
7983
|
+
const runResult = yield* Effect9.tryPromise({
|
|
7817
7984
|
try: () => runIssueCommand({
|
|
7818
7985
|
issueNumber: selected.number,
|
|
7819
7986
|
targetName,
|
|
@@ -7853,7 +8020,7 @@ function runQueue(options) {
|
|
|
7853
8020
|
return runOneQueueIssueEffect(options);
|
|
7854
8021
|
}
|
|
7855
8022
|
function runQueueLoopEffect(options, results) {
|
|
7856
|
-
return
|
|
8023
|
+
return Effect9.gen(function* () {
|
|
7857
8024
|
yield* reconcileBlockedEffect(options);
|
|
7858
8025
|
const outcome = yield* runOneQueueIssueEffect(options);
|
|
7859
8026
|
if (outcome.selected === null) {
|
|
@@ -7870,7 +8037,7 @@ function runQueueLoopEffect(options, results) {
|
|
|
7870
8037
|
};
|
|
7871
8038
|
}
|
|
7872
8039
|
const newResults = [...results, outcome];
|
|
7873
|
-
const processedIssue = yield*
|
|
8040
|
+
const processedIssue = yield* Effect9.tryPromise({
|
|
7874
8041
|
try: () => options.issueProvider.fetchIssue(outcome.selected.number),
|
|
7875
8042
|
catch: (e) => {
|
|
7876
8043
|
if (e instanceof Error) {
|
|
@@ -10225,8 +10392,7 @@ function generateConfigTemplate(options) {
|
|
|
10225
10392
|
model: "opencode-go/qwen3.6-plus",
|
|
10226
10393
|
promptTemplate: ".pourkit/managed/prompts/refactor.prompt.md"
|
|
10227
10394
|
},
|
|
10228
|
-
maxIterations: 3
|
|
10229
|
-
passWithNotesRefactorAttempts: 2
|
|
10395
|
+
maxIterations: 3
|
|
10230
10396
|
},
|
|
10231
10397
|
issueFinalReview: {
|
|
10232
10398
|
agent: "pourkit-reviewer",
|
|
@@ -10256,7 +10422,7 @@ function generateConfigTemplate(options) {
|
|
|
10256
10422
|
}
|
|
10257
10423
|
const config = {
|
|
10258
10424
|
$schema: "./schema/pourkit.schema.json",
|
|
10259
|
-
schemaVersion:
|
|
10425
|
+
schemaVersion: 3,
|
|
10260
10426
|
targets: [target],
|
|
10261
10427
|
workflowPack: {
|
|
10262
10428
|
version: "1.0.0",
|
|
@@ -15454,11 +15620,11 @@ function createCliProgram(version) {
|
|
|
15454
15620
|
return program;
|
|
15455
15621
|
}
|
|
15456
15622
|
async function resolveCliVersion() {
|
|
15457
|
-
if (isPackageVersion("0.0.0-next-
|
|
15458
|
-
return "0.0.0-next-
|
|
15623
|
+
if (isPackageVersion("0.0.0-next-20260621233457")) {
|
|
15624
|
+
return "0.0.0-next-20260621233457";
|
|
15459
15625
|
}
|
|
15460
|
-
if (isReleaseVersion("0.0.0-next-
|
|
15461
|
-
return "0.0.0-next-
|
|
15626
|
+
if (isReleaseVersion("0.0.0-next-20260621233457")) {
|
|
15627
|
+
return "0.0.0-next-20260621233457";
|
|
15462
15628
|
}
|
|
15463
15629
|
try {
|
|
15464
15630
|
const root = repoRoot();
|