@pourkit/cli 0.0.0-next-20260621045826 → 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 +889 -611
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +769 -500
- 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 +35 -7
- 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: {
|
|
@@ -880,11 +897,11 @@ var OBSOLETE_CONFIG_PATHS = [
|
|
|
880
897
|
];
|
|
881
898
|
var CANONICAL_CONFIG_PATH = ".pourkit/config.json";
|
|
882
899
|
async function loadRepoConfig(repoRoot2, _configFileName) {
|
|
883
|
-
const { existsSync:
|
|
900
|
+
const { existsSync: existsSync22 } = await import("fs");
|
|
884
901
|
const { join: pjoin } = await import("path");
|
|
885
902
|
for (const obPath of OBSOLETE_CONFIG_PATHS) {
|
|
886
903
|
const fullPath = pjoin(repoRoot2, obPath);
|
|
887
|
-
if (
|
|
904
|
+
if (existsSync22(fullPath)) {
|
|
888
905
|
const isRootJson = obPath === "pourkit.json";
|
|
889
906
|
throw new Error(
|
|
890
907
|
isRootJson ? `Found root ${obPath}, but Pourkit config now lives at ${CANONICAL_CONFIG_PATH}. Move the file and update "$schema" to "./schema/pourkit.schema.json".` : `Found ${obPath}, but executable config is no longer supported. Move configuration to ${CANONICAL_CONFIG_PATH} with "$schema": "./schema/pourkit.schema.json".`
|
|
@@ -892,7 +909,7 @@ async function loadRepoConfig(repoRoot2, _configFileName) {
|
|
|
892
909
|
}
|
|
893
910
|
}
|
|
894
911
|
const configPath = pjoin(repoRoot2, CANONICAL_CONFIG_PATH);
|
|
895
|
-
if (!
|
|
912
|
+
if (!existsSync22(configPath)) {
|
|
896
913
|
throw new Error(
|
|
897
914
|
`No Pourkit config found at ${CANONICAL_CONFIG_PATH}. Run pourkit init or create ${CANONICAL_CONFIG_PATH} with "$schema": "./schema/pourkit.schema.json".`
|
|
898
915
|
);
|
|
@@ -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
|
|
2474
|
-
|
|
2475
|
-
|
|
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) {
|
|
2487
|
-
return Effect2.gen(function* () {
|
|
2488
|
-
const parts = [];
|
|
2489
|
-
for (const criterion of criteria) {
|
|
2490
|
-
const snippetPath = join6(
|
|
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");
|
|
2506
|
-
});
|
|
2507
|
-
}
|
|
2508
|
-
function recoverReviewOutputFromString(logContent) {
|
|
2509
|
-
const startIndex = logContent.indexOf("## Findings");
|
|
2510
|
-
if (startIndex === -1) return null;
|
|
2511
|
-
const verdictMatch = logContent.slice(startIndex).match(
|
|
2512
|
-
/<verdict>(PASS|PASS_WITH_NOTES|NEEDS_REFACTOR|FAIL|NEEDS_HUMAN)<\/verdict>/
|
|
2513
|
-
);
|
|
2514
|
-
if (!verdictMatch || verdictMatch.index === void 0) return null;
|
|
2515
|
-
const recoveredOutput = logContent.slice(startIndex, startIndex + verdictMatch.index + verdictMatch[0].length).trim();
|
|
2516
|
-
return recoveredOutput.length > 0 ? recoveredOutput : null;
|
|
2517
|
-
}
|
|
2518
|
-
function recoverReviewOutputFromLog(logPath) {
|
|
2519
|
-
if (!existsSync6(logPath)) {
|
|
2520
|
-
return null;
|
|
2521
|
-
}
|
|
2522
|
-
const logContent = readFileSync6(logPath, "utf-8");
|
|
2523
|
-
return recoverReviewOutputFromString(logContent);
|
|
2623
|
+
function persistIterationArtifactEffect(worktreePath, output, iteration, fs) {
|
|
2624
|
+
return Effect2.gen(function* () {
|
|
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));
|
|
2628
|
+
});
|
|
2524
2629
|
}
|
|
2525
|
-
function
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
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,9 +3573,47 @@ 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;
|
|
3587
|
+
if (parsed.verdict === "pass" && verificationWasRecorded) {
|
|
3495
3588
|
if (verification.required !== true || verification.passed !== true) {
|
|
3589
|
+
return {
|
|
3590
|
+
ok: false,
|
|
3591
|
+
reason: "Issue Final Review pass with verification evidence requires verification.required === true and verification.passed === true",
|
|
3592
|
+
diagnostics: [
|
|
3593
|
+
...diagnostics,
|
|
3594
|
+
`verification.required: ${verification.required}`,
|
|
3595
|
+
`verification.passed: ${verification.passed}`
|
|
3596
|
+
]
|
|
3597
|
+
};
|
|
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
|
+
}
|
|
3613
|
+
diagnostics.push("pass verification: valid");
|
|
3614
|
+
}
|
|
3615
|
+
if (parsed.selfRetouched === true && parsed.verdict === "pass") {
|
|
3616
|
+
if (!verificationWasRecorded) {
|
|
3496
3617
|
return {
|
|
3497
3618
|
ok: false,
|
|
3498
3619
|
reason: "Self-retouched pass requires verification.required === true and verification.passed === true",
|
|
@@ -3672,7 +3793,7 @@ function shellQuote(value) {
|
|
|
3672
3793
|
}
|
|
3673
3794
|
|
|
3674
3795
|
// commands/issue-run.ts
|
|
3675
|
-
import { existsSync as
|
|
3796
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
|
|
3676
3797
|
import { isAbsolute as isAbsolute4, join as join15 } from "path";
|
|
3677
3798
|
|
|
3678
3799
|
// pr/templates.ts
|
|
@@ -3693,10 +3814,10 @@ init_common();
|
|
|
3693
3814
|
import { Exit as Exit3 } from "effect";
|
|
3694
3815
|
|
|
3695
3816
|
// failure-resolution/effect-runtime.ts
|
|
3696
|
-
import { Effect as
|
|
3817
|
+
import { Effect as Effect5 } from "effect";
|
|
3697
3818
|
|
|
3698
3819
|
// shared/effect-runtime.ts
|
|
3699
|
-
import { Effect as
|
|
3820
|
+
import { Effect as Effect4, Exit } from "effect";
|
|
3700
3821
|
import { UnknownException } from "effect/Cause";
|
|
3701
3822
|
var initialized = false;
|
|
3702
3823
|
function initializeEffectRuntime() {
|
|
@@ -3705,7 +3826,7 @@ function initializeEffectRuntime() {
|
|
|
3705
3826
|
}
|
|
3706
3827
|
function runEffect(program) {
|
|
3707
3828
|
ensureEffectRuntime();
|
|
3708
|
-
return
|
|
3829
|
+
return Effect4.runPromiseExit(program);
|
|
3709
3830
|
}
|
|
3710
3831
|
function ensureEffectRuntime() {
|
|
3711
3832
|
if (!initialized) {
|
|
@@ -3883,23 +4004,23 @@ function recordStageAttempt(worktreePath, record) {
|
|
|
3883
4004
|
|
|
3884
4005
|
// failure-resolution/effect-runtime.ts
|
|
3885
4006
|
function baseRefreshEffect(options) {
|
|
3886
|
-
return
|
|
3887
|
-
|
|
4007
|
+
return Effect5.promise(() => refreshStaleIssueBranch(options)).pipe(
|
|
4008
|
+
Effect5.flatMap(
|
|
3888
4009
|
(result) => {
|
|
3889
4010
|
switch (result.status) {
|
|
3890
4011
|
case "refreshed":
|
|
3891
|
-
return
|
|
4012
|
+
return Effect5.succeed({ status: "refreshed" });
|
|
3892
4013
|
case "skipped-current":
|
|
3893
|
-
return
|
|
4014
|
+
return Effect5.succeed({ status: "skipped-current" });
|
|
3894
4015
|
case "conflicted":
|
|
3895
|
-
return
|
|
4016
|
+
return Effect5.fail(
|
|
3896
4017
|
new RebaseConflict({
|
|
3897
4018
|
conflictedPaths: result.conflictedPaths,
|
|
3898
4019
|
message: result.message
|
|
3899
4020
|
})
|
|
3900
4021
|
);
|
|
3901
4022
|
case "refused-published-history":
|
|
3902
|
-
return
|
|
4023
|
+
return Effect5.fail(
|
|
3903
4024
|
new PublishedHistoryRisk({
|
|
3904
4025
|
prNumber: result.prNumber,
|
|
3905
4026
|
prState: result.prState
|
|
@@ -3914,8 +4035,8 @@ async function runBaseRefreshAttempt(options) {
|
|
|
3914
4035
|
const attemptId = createStageAttemptId();
|
|
3915
4036
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3916
4037
|
const program = baseRefreshEffect(options).pipe(
|
|
3917
|
-
|
|
3918
|
-
onSuccess: (success) =>
|
|
4038
|
+
Effect5.tapBoth({
|
|
4039
|
+
onSuccess: (success) => Effect5.sync(() => {
|
|
3919
4040
|
recordStageAttempt(options.worktreePath, {
|
|
3920
4041
|
id: attemptId,
|
|
3921
4042
|
stage: "baseRefresh",
|
|
@@ -3924,7 +4045,7 @@ async function runBaseRefreshAttempt(options) {
|
|
|
3924
4045
|
outcome: "success"
|
|
3925
4046
|
});
|
|
3926
4047
|
}),
|
|
3927
|
-
onFailure: (failure) =>
|
|
4048
|
+
onFailure: (failure) => Effect5.sync(() => {
|
|
3928
4049
|
recordStageAttempt(options.worktreePath, {
|
|
3929
4050
|
id: attemptId,
|
|
3930
4051
|
stage: "baseRefresh",
|
|
@@ -4395,6 +4516,10 @@ function writeFailureState(options, message) {
|
|
|
4395
4516
|
}
|
|
4396
4517
|
}
|
|
4397
4518
|
|
|
4519
|
+
// issues/issue-builder.ts
|
|
4520
|
+
init_common();
|
|
4521
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
|
|
4522
|
+
|
|
4398
4523
|
// serena/baseline.ts
|
|
4399
4524
|
init_common();
|
|
4400
4525
|
import path3 from "path";
|
|
@@ -4647,82 +4772,258 @@ var SERENA_DASHBOARD_PORT = 24282;
|
|
|
4647
4772
|
var SERENA_IMAGE = "ghcr.io/oraios/serena:latest";
|
|
4648
4773
|
function sidecarOptions(paths, mcpUrl) {
|
|
4649
4774
|
return {
|
|
4650
|
-
baselineWorktreePath: paths.baselineWorktreePath,
|
|
4651
|
-
dataDir: paths.dataDir,
|
|
4652
|
-
mcpPort: SERENA_MCP_PORT,
|
|
4653
|
-
dashboardPort: SERENA_DASHBOARD_PORT,
|
|
4654
|
-
image: SERENA_IMAGE,
|
|
4655
|
-
mcpUrl
|
|
4775
|
+
baselineWorktreePath: paths.baselineWorktreePath,
|
|
4776
|
+
dataDir: paths.dataDir,
|
|
4777
|
+
mcpPort: SERENA_MCP_PORT,
|
|
4778
|
+
dashboardPort: SERENA_DASHBOARD_PORT,
|
|
4779
|
+
image: SERENA_IMAGE,
|
|
4780
|
+
mcpUrl
|
|
4781
|
+
};
|
|
4782
|
+
}
|
|
4783
|
+
async function canReachMcp(url) {
|
|
4784
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
4785
|
+
try {
|
|
4786
|
+
await fetch(url, { method: "GET", signal: AbortSignal.timeout(500) });
|
|
4787
|
+
return true;
|
|
4788
|
+
} catch {
|
|
4789
|
+
if (attempt < 9) {
|
|
4790
|
+
await new Promise((resolve5) => setTimeout(resolve5, 100));
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
return false;
|
|
4795
|
+
}
|
|
4796
|
+
function formatError(error) {
|
|
4797
|
+
return error instanceof Error ? error.message : String(error);
|
|
4798
|
+
}
|
|
4799
|
+
async function prepareSerenaForTarget(options) {
|
|
4800
|
+
if (!options.enabled) {
|
|
4801
|
+
return { enabled: false };
|
|
4802
|
+
}
|
|
4803
|
+
try {
|
|
4804
|
+
const paths = await ensureBaselineWorktree({
|
|
4805
|
+
repoRoot: options.repoRoot,
|
|
4806
|
+
dataDir: options.dataDir
|
|
4807
|
+
});
|
|
4808
|
+
await prepareSerenaSidecarConfig({
|
|
4809
|
+
baselineWorktreePath: paths.baselineWorktreePath,
|
|
4810
|
+
dataDir: paths.dataDir
|
|
4811
|
+
});
|
|
4812
|
+
const status = options.autoStart ? await startSerenaSidecar(sidecarOptions(paths, options.mcpUrl)) : await getSerenaSidecarStatus(sidecarOptions(paths, options.mcpUrl));
|
|
4813
|
+
const mcpReachable = await canReachMcp(options.mcpUrl);
|
|
4814
|
+
if (!mcpReachable) {
|
|
4815
|
+
return {
|
|
4816
|
+
enabled: true,
|
|
4817
|
+
available: false,
|
|
4818
|
+
error: status.running ? `Serena MCP is not reachable at ${options.mcpUrl}` : `Serena sidecar is not running for target ${options.targetName}`
|
|
4819
|
+
};
|
|
4820
|
+
}
|
|
4821
|
+
await refreshSerenaBaseline({
|
|
4822
|
+
repoRoot: options.repoRoot,
|
|
4823
|
+
dataDir: options.dataDir,
|
|
4824
|
+
baseBranch: options.baseBranch
|
|
4825
|
+
});
|
|
4826
|
+
return {
|
|
4827
|
+
enabled: true,
|
|
4828
|
+
available: true,
|
|
4829
|
+
mcpUrl: options.mcpUrl
|
|
4830
|
+
};
|
|
4831
|
+
} catch (error) {
|
|
4832
|
+
return {
|
|
4833
|
+
enabled: true,
|
|
4834
|
+
available: false,
|
|
4835
|
+
error: formatError(error)
|
|
4836
|
+
};
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
|
|
4840
|
+
// issues/issue-builder.ts
|
|
4841
|
+
async function runIssueBuilder(options) {
|
|
4842
|
+
const {
|
|
4843
|
+
repoRoot: repoRoot2,
|
|
4844
|
+
issueNumber,
|
|
4845
|
+
target,
|
|
4846
|
+
config,
|
|
4847
|
+
branchName,
|
|
4848
|
+
worktreePath,
|
|
4849
|
+
baseRef,
|
|
4850
|
+
worktreeState,
|
|
4851
|
+
restartBuilder,
|
|
4852
|
+
executionProvider,
|
|
4853
|
+
runContextArtifact,
|
|
4854
|
+
logger,
|
|
4855
|
+
memory,
|
|
4856
|
+
prepareSerena,
|
|
4857
|
+
onSerenaExecutionContext,
|
|
4858
|
+
timeoutMs
|
|
4859
|
+
} = options;
|
|
4860
|
+
const serenaRuntimeConfig = {
|
|
4861
|
+
enabled: target.serena?.enabled ?? config.serena.enabled,
|
|
4862
|
+
required: target.serena?.required ?? config.serena.required,
|
|
4863
|
+
autoStart: config.serena.autoStart,
|
|
4864
|
+
dataDir: config.serena.dataDir,
|
|
4865
|
+
mcpUrl: config.serena.mcpUrl,
|
|
4866
|
+
sandboxMcpUrl: config.serena.sandboxMcpUrl
|
|
4867
|
+
};
|
|
4868
|
+
let serenaExecutionContext;
|
|
4869
|
+
if (serenaRuntimeConfig.enabled || serenaRuntimeConfig.required) {
|
|
4870
|
+
const serenaPreflight = await (prepareSerena ?? prepareSerenaForTarget)({
|
|
4871
|
+
repoRoot: repoRoot2,
|
|
4872
|
+
targetName: target.name,
|
|
4873
|
+
baseBranch: target.baseBranch,
|
|
4874
|
+
dataDir: serenaRuntimeConfig.dataDir,
|
|
4875
|
+
mcpUrl: serenaRuntimeConfig.mcpUrl,
|
|
4876
|
+
enabled: serenaRuntimeConfig.enabled || serenaRuntimeConfig.required,
|
|
4877
|
+
required: serenaRuntimeConfig.required,
|
|
4878
|
+
autoStart: serenaRuntimeConfig.autoStart,
|
|
4879
|
+
logger
|
|
4880
|
+
});
|
|
4881
|
+
if (serenaPreflight.enabled && serenaPreflight.available) {
|
|
4882
|
+
serenaExecutionContext = {
|
|
4883
|
+
available: true,
|
|
4884
|
+
sandboxMcpUrl: serenaRuntimeConfig.sandboxMcpUrl
|
|
4885
|
+
};
|
|
4886
|
+
onSerenaExecutionContext?.(serenaExecutionContext);
|
|
4887
|
+
}
|
|
4888
|
+
if (serenaPreflight.enabled && !serenaPreflight.available) {
|
|
4889
|
+
const message = `Serena preflight unavailable for target ${target.name}: ${serenaPreflight.error}`;
|
|
4890
|
+
if (serenaRuntimeConfig.required) {
|
|
4891
|
+
throw new Error(message);
|
|
4892
|
+
}
|
|
4893
|
+
logger.step("warn", message);
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
const shouldRunBuilder = restartBuilder || worktreeState === null || !worktreeState.completedStages.builder;
|
|
4897
|
+
if (!shouldRunBuilder) {
|
|
4898
|
+
return {
|
|
4899
|
+
status: "skipped",
|
|
4900
|
+
...worktreePath ? { worktreePath } : {},
|
|
4901
|
+
worktreeState
|
|
4902
|
+
};
|
|
4903
|
+
}
|
|
4904
|
+
const strategy = target.strategy;
|
|
4905
|
+
const prompt = loadBuilderPrompt(
|
|
4906
|
+
repoRoot2,
|
|
4907
|
+
strategy.implement.builder.promptTemplate
|
|
4908
|
+
);
|
|
4909
|
+
const executionResult = await executionProvider.execute({
|
|
4910
|
+
stage: "builder",
|
|
4911
|
+
agent: strategy.implement.builder.agent,
|
|
4912
|
+
model: strategy.implement.builder.model,
|
|
4913
|
+
variant: strategy.implement.builder.variant,
|
|
4914
|
+
env: strategy.implement.builder.env,
|
|
4915
|
+
prompt,
|
|
4916
|
+
target,
|
|
4917
|
+
repoRoot: repoRoot2,
|
|
4918
|
+
branchName,
|
|
4919
|
+
...baseRef ? { baseRef } : {},
|
|
4920
|
+
sandbox: config.sandbox,
|
|
4921
|
+
...serenaExecutionContext ? { serena: serenaExecutionContext } : {},
|
|
4922
|
+
...memory ? { memory } : {},
|
|
4923
|
+
autoApprove: true,
|
|
4924
|
+
timeoutMs,
|
|
4925
|
+
...worktreePath ? { worktreePath } : {},
|
|
4926
|
+
artifacts: [runContextArtifact],
|
|
4927
|
+
logger
|
|
4928
|
+
});
|
|
4929
|
+
if (!executionResult.success) {
|
|
4930
|
+
throw new Error(`Sandcastle failed: ${executionResult.error}`);
|
|
4931
|
+
}
|
|
4932
|
+
const effectiveWorktreePath = worktreePath ?? executionResult.worktreePath;
|
|
4933
|
+
if (effectiveWorktreePath) {
|
|
4934
|
+
await assertCanonicalBaseAncestor({
|
|
4935
|
+
worktreePath: effectiveWorktreePath,
|
|
4936
|
+
baseRef: `origin/${target.baseBranch}`,
|
|
4937
|
+
stageName: "Builder",
|
|
4938
|
+
logger
|
|
4939
|
+
});
|
|
4940
|
+
if (worktreeState) {
|
|
4941
|
+
updateWorktreeRunState(effectiveWorktreePath, {
|
|
4942
|
+
completedStages: { builder: true }
|
|
4943
|
+
});
|
|
4944
|
+
} else {
|
|
4945
|
+
writeWorktreeRunState(effectiveWorktreePath, {
|
|
4946
|
+
issueNumber,
|
|
4947
|
+
targetName: target.name,
|
|
4948
|
+
branchName,
|
|
4949
|
+
baseBranch: target.baseBranch,
|
|
4950
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4951
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4952
|
+
completedStages: { builder: true },
|
|
4953
|
+
review: { lifetimeIterations: 0 }
|
|
4954
|
+
});
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
const refreshedState = effectiveWorktreePath ? readWorktreeRunState(effectiveWorktreePath) : worktreeState;
|
|
4958
|
+
return {
|
|
4959
|
+
status: "ran",
|
|
4960
|
+
...effectiveWorktreePath ? { worktreePath: effectiveWorktreePath } : {},
|
|
4961
|
+
worktreeState: refreshedState
|
|
4656
4962
|
};
|
|
4657
4963
|
}
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4964
|
+
function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
4965
|
+
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
4966
|
+
const promptBody = existsSync10(promptPath) ? readFileSync11(promptPath, "utf-8") : promptTemplate;
|
|
4967
|
+
return appendProtectedWorkGuidance(`${promptBody}
|
|
4968
|
+
|
|
4969
|
+
## Shared Run Context
|
|
4970
|
+
|
|
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>.`);
|
|
4670
4980
|
}
|
|
4671
|
-
function
|
|
4672
|
-
|
|
4981
|
+
async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
|
|
4982
|
+
const remoteBase = parseRemoteBaseRef(baseRef);
|
|
4983
|
+
if (!remoteBase) {
|
|
4984
|
+
return;
|
|
4985
|
+
}
|
|
4986
|
+
await execCapture("git", ["fetch", remoteBase.remote, remoteBase.branch], {
|
|
4987
|
+
cwd: worktreePath,
|
|
4988
|
+
logger,
|
|
4989
|
+
label: "git fetch target"
|
|
4990
|
+
});
|
|
4673
4991
|
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4992
|
+
function parseRemoteBaseRef(baseRef) {
|
|
4993
|
+
const [remote, ...branchParts] = baseRef.split("/");
|
|
4994
|
+
const branch = branchParts.join("/");
|
|
4995
|
+
if (!remote || !branch) {
|
|
4996
|
+
return null;
|
|
4677
4997
|
}
|
|
4998
|
+
return { remote, branch };
|
|
4999
|
+
}
|
|
5000
|
+
async function assertCanonicalBaseAncestor(options) {
|
|
5001
|
+
const { worktreePath, baseRef, stageName, logger } = options;
|
|
5002
|
+
await syncRemoteBaseRef(worktreePath, baseRef, logger);
|
|
4678
5003
|
try {
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
await prepareSerenaSidecarConfig({
|
|
4684
|
-
baselineWorktreePath: paths.baselineWorktreePath,
|
|
4685
|
-
dataDir: paths.dataDir
|
|
4686
|
-
});
|
|
4687
|
-
const status = options.autoStart ? await startSerenaSidecar(sidecarOptions(paths, options.mcpUrl)) : await getSerenaSidecarStatus(sidecarOptions(paths, options.mcpUrl));
|
|
4688
|
-
const mcpReachable = await canReachMcp(options.mcpUrl);
|
|
4689
|
-
if (!mcpReachable) {
|
|
4690
|
-
return {
|
|
4691
|
-
enabled: true,
|
|
4692
|
-
available: false,
|
|
4693
|
-
error: status.running ? `Serena MCP is not reachable at ${options.mcpUrl}` : `Serena sidecar is not running for target ${options.targetName}`
|
|
4694
|
-
};
|
|
4695
|
-
}
|
|
4696
|
-
await refreshSerenaBaseline({
|
|
4697
|
-
repoRoot: options.repoRoot,
|
|
4698
|
-
dataDir: options.dataDir,
|
|
4699
|
-
baseBranch: options.baseBranch
|
|
5004
|
+
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
5005
|
+
cwd: worktreePath,
|
|
5006
|
+
logger,
|
|
5007
|
+
label: "git merge-base --is-ancestor"
|
|
4700
5008
|
});
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
};
|
|
4706
|
-
} catch (error) {
|
|
4707
|
-
return {
|
|
4708
|
-
enabled: true,
|
|
4709
|
-
available: false,
|
|
4710
|
-
error: formatError(error)
|
|
4711
|
-
};
|
|
5009
|
+
} catch {
|
|
5010
|
+
throw new Error(
|
|
5011
|
+
`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.`
|
|
5012
|
+
);
|
|
4712
5013
|
}
|
|
4713
5014
|
}
|
|
4714
5015
|
|
|
4715
5016
|
// commands/pr-description-agent.ts
|
|
4716
5017
|
import { join as join11 } from "path";
|
|
4717
|
-
import { readFileSync as
|
|
5018
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
4718
5019
|
|
|
4719
5020
|
// pr/pr-description-context.ts
|
|
4720
5021
|
init_common();
|
|
4721
5022
|
import { join as join10 } from "path";
|
|
4722
5023
|
import { readFile } from "fs/promises";
|
|
4723
|
-
import { Effect as
|
|
5024
|
+
import { Effect as Effect6 } from "effect";
|
|
4724
5025
|
function collectFinalizerContextEffect(options) {
|
|
4725
|
-
return
|
|
5026
|
+
return Effect6.gen(function* () {
|
|
4726
5027
|
const git = yield* GitOperations;
|
|
4727
5028
|
const fs = yield* FileSystem;
|
|
4728
5029
|
const remoteBase = remoteTargetBase(options.targetBase);
|
|
@@ -4735,8 +5036,8 @@ function collectFinalizerContextEffect(options) {
|
|
|
4735
5036
|
],
|
|
4736
5037
|
{ cwd: options.worktreePath }
|
|
4737
5038
|
).pipe(
|
|
4738
|
-
|
|
4739
|
-
(error) =>
|
|
5039
|
+
Effect6.catchAll(
|
|
5040
|
+
(error) => Effect6.fail(
|
|
4740
5041
|
new Error(`Failed to collect commit range: ${error.message}`)
|
|
4741
5042
|
)
|
|
4742
5043
|
)
|
|
@@ -4751,9 +5052,9 @@ function collectFinalizerContextEffect(options) {
|
|
|
4751
5052
|
let reviewArtifact;
|
|
4752
5053
|
if (options.reviewArtifactPath) {
|
|
4753
5054
|
const content = yield* fs.readFile(options.reviewArtifactPath).pipe(
|
|
4754
|
-
|
|
5055
|
+
Effect6.catchIf(
|
|
4755
5056
|
(error) => error.message.includes("ENOENT") || error.message.includes("no such file"),
|
|
4756
|
-
() =>
|
|
5057
|
+
() => Effect6.fail(
|
|
4757
5058
|
new Error(
|
|
4758
5059
|
`Review artifact not found at ${options.reviewArtifactPath}. Ensure the review stage completed before running finalizer generation.`
|
|
4759
5060
|
)
|
|
@@ -4761,7 +5062,7 @@ function collectFinalizerContextEffect(options) {
|
|
|
4761
5062
|
)
|
|
4762
5063
|
);
|
|
4763
5064
|
if (!content.trim()) {
|
|
4764
|
-
return yield*
|
|
5065
|
+
return yield* Effect6.fail(
|
|
4765
5066
|
new Error(
|
|
4766
5067
|
`Review artifact at ${options.reviewArtifactPath} is empty. Ensure the review stage produced output before running finalizer generation.`
|
|
4767
5068
|
)
|
|
@@ -4837,12 +5138,12 @@ Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktre
|
|
|
4837
5138
|
}
|
|
4838
5139
|
|
|
4839
5140
|
// commands/pr-description-agent.ts
|
|
4840
|
-
import { Effect as
|
|
5141
|
+
import { Effect as Effect7, Layer as Layer3 } from "effect";
|
|
4841
5142
|
function bridgeExecutionProvider2(ep) {
|
|
4842
5143
|
return Layer3.succeed(
|
|
4843
5144
|
ExecutionProvider,
|
|
4844
5145
|
ExecutionProvider.of({
|
|
4845
|
-
execute: (opts) =>
|
|
5146
|
+
execute: (opts) => Effect7.tryPromise({
|
|
4846
5147
|
try: () => ep.execute(opts),
|
|
4847
5148
|
catch: (e) => new Error(
|
|
4848
5149
|
e instanceof Error ? e.message : `Execution failed: ${String(e)}`
|
|
@@ -4860,7 +5161,7 @@ function runFinalizerAgent(options) {
|
|
|
4860
5161
|
"agent-output.md"
|
|
4861
5162
|
);
|
|
4862
5163
|
const artifactPath = join11(options.worktreePath, artifactPathInWorktree);
|
|
4863
|
-
const program =
|
|
5164
|
+
const program = Effect7.gen(function* () {
|
|
4864
5165
|
const fs = yield* FileSystem;
|
|
4865
5166
|
const context = yield* collectFinalizerContextEffect({
|
|
4866
5167
|
targetBase: options.targetBaseBranch,
|
|
@@ -4869,8 +5170,8 @@ function runFinalizerAgent(options) {
|
|
|
4869
5170
|
reviewArtifactPath: options.reviewArtifactPath,
|
|
4870
5171
|
logger: options.logger
|
|
4871
5172
|
}).pipe(
|
|
4872
|
-
|
|
4873
|
-
(error) =>
|
|
5173
|
+
Effect7.catchAll(
|
|
5174
|
+
(error) => Effect7.fail(
|
|
4874
5175
|
new FinalizerFailure({
|
|
4875
5176
|
message: `Failed to collect finalizer context: ${error.message}`
|
|
4876
5177
|
})
|
|
@@ -4909,13 +5210,13 @@ function runFinalizerAgent(options) {
|
|
|
4909
5210
|
logger: options.logger,
|
|
4910
5211
|
...options.memory ? { memory: options.memory } : {}
|
|
4911
5212
|
});
|
|
4912
|
-
const output =
|
|
5213
|
+
const output = readFileSync12(artifactPath, "utf-8");
|
|
4913
5214
|
yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
|
|
4914
5215
|
return result;
|
|
4915
5216
|
});
|
|
4916
5217
|
const executionLayer = bridgeExecutionProvider2(executionProvider);
|
|
4917
5218
|
return program.pipe(
|
|
4918
|
-
|
|
5219
|
+
Effect7.provide(
|
|
4919
5220
|
Layer3.mergeAll(executionLayer, FileSystemDefault, GitOperationsDefault)
|
|
4920
5221
|
)
|
|
4921
5222
|
);
|
|
@@ -4925,11 +5226,11 @@ function runPrDescriptionFinalizerCore(options) {
|
|
|
4925
5226
|
options.worktreePath,
|
|
4926
5227
|
options.artifactPathInWorktree
|
|
4927
5228
|
);
|
|
4928
|
-
return
|
|
5229
|
+
return Effect7.gen(function* () {
|
|
4929
5230
|
let parsed;
|
|
4930
5231
|
let lastValidationError;
|
|
4931
5232
|
for (let protocolAttempt = 1; protocolAttempt <= options.maxAttempts; protocolAttempt++) {
|
|
4932
|
-
const retryResult = yield*
|
|
5233
|
+
const retryResult = yield* Effect7.tryPromise({
|
|
4933
5234
|
try: () => executeWithMissingOrEmptyArtifactRetry({
|
|
4934
5235
|
executionProvider: options.executionProvider,
|
|
4935
5236
|
missingOrEmptyRetries: resolveMissingOrEmptyOutputRetries(
|
|
@@ -4966,7 +5267,7 @@ function runPrDescriptionFinalizerCore(options) {
|
|
|
4966
5267
|
});
|
|
4967
5268
|
const executionResult = retryResult.executionResult;
|
|
4968
5269
|
if (!executionResult.success) {
|
|
4969
|
-
return yield*
|
|
5270
|
+
return yield* Effect7.fail(
|
|
4970
5271
|
new FinalizerFailure({
|
|
4971
5272
|
message: `Finalizer agent execution failed: ${executionResult.error}`
|
|
4972
5273
|
})
|
|
@@ -5004,7 +5305,7 @@ function runPrDescriptionFinalizerCore(options) {
|
|
|
5004
5305
|
}
|
|
5005
5306
|
}
|
|
5006
5307
|
if (!parsed) {
|
|
5007
|
-
return yield*
|
|
5308
|
+
return yield* Effect7.fail(
|
|
5008
5309
|
new FinalizerFailure({
|
|
5009
5310
|
message: lastValidationError?.message ?? "Finalizer validation failed"
|
|
5010
5311
|
})
|
|
@@ -5019,20 +5320,20 @@ function runPrDescriptionFinalizerCore(options) {
|
|
|
5019
5320
|
});
|
|
5020
5321
|
}
|
|
5021
5322
|
function loadFinalizerPromptEffect(repoRoot2, promptTemplate, fs) {
|
|
5022
|
-
return
|
|
5323
|
+
return Effect7.gen(function* () {
|
|
5023
5324
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
5024
|
-
const exists = yield* fs.exists(promptPath).pipe(
|
|
5325
|
+
const exists = yield* fs.exists(promptPath).pipe(Effect7.orDie);
|
|
5025
5326
|
if (exists) {
|
|
5026
|
-
return yield* fs.readFile(promptPath).pipe(
|
|
5327
|
+
return yield* fs.readFile(promptPath).pipe(Effect7.orDie);
|
|
5027
5328
|
}
|
|
5028
5329
|
return promptTemplate;
|
|
5029
5330
|
});
|
|
5030
5331
|
}
|
|
5031
5332
|
function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
5032
|
-
return
|
|
5333
|
+
return Effect7.gen(function* () {
|
|
5033
5334
|
const dir = join11(worktreePath, ".pourkit", ".tmp", "finalizer");
|
|
5034
|
-
yield* fs.mkdir(dir).pipe(
|
|
5035
|
-
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));
|
|
5036
5337
|
});
|
|
5037
5338
|
}
|
|
5038
5339
|
|
|
@@ -5106,7 +5407,7 @@ function extractClosingRefs(body) {
|
|
|
5106
5407
|
|
|
5107
5408
|
// issues/github-issue-publication.ts
|
|
5108
5409
|
init_common();
|
|
5109
|
-
import { existsSync as
|
|
5410
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
5110
5411
|
import { isAbsolute as isAbsolute3, join as join12 } from "path";
|
|
5111
5412
|
|
|
5112
5413
|
// issues/issue-transitions.ts
|
|
@@ -5372,7 +5673,7 @@ function secondsRemaining(deadline, observedAt) {
|
|
|
5372
5673
|
}
|
|
5373
5674
|
|
|
5374
5675
|
// issues/merge-coordinator.ts
|
|
5375
|
-
import { Effect as
|
|
5676
|
+
import { Effect as Effect8, Either } from "effect";
|
|
5376
5677
|
import { UnknownException as UnknownException2 } from "effect/Cause";
|
|
5377
5678
|
function unwrapError2(error) {
|
|
5378
5679
|
if (error instanceof UnknownException2 && error.error !== void 0) {
|
|
@@ -5391,9 +5692,9 @@ function runMergeCoordinator(options) {
|
|
|
5391
5692
|
} = options;
|
|
5392
5693
|
const method = options.method ?? "squash";
|
|
5393
5694
|
const waitForTargetGreen = options.waitForTargetGreen ?? true;
|
|
5394
|
-
return
|
|
5395
|
-
const checksEither = yield*
|
|
5396
|
-
|
|
5695
|
+
return Effect8.gen(function* () {
|
|
5696
|
+
const checksEither = yield* Effect8.either(
|
|
5697
|
+
Effect8.tryPromise(
|
|
5397
5698
|
() => prProvider.waitForPrChecks(prNumber, checkWaitOptions)
|
|
5398
5699
|
)
|
|
5399
5700
|
);
|
|
@@ -5406,8 +5707,8 @@ function runMergeCoordinator(options) {
|
|
|
5406
5707
|
error: error instanceof Error ? error : new Error(String(error))
|
|
5407
5708
|
};
|
|
5408
5709
|
}
|
|
5409
|
-
const mergeEither = yield*
|
|
5410
|
-
|
|
5710
|
+
const mergeEither = yield* Effect8.either(
|
|
5711
|
+
Effect8.tryPromise(
|
|
5411
5712
|
() => prProvider.mergePr(prNumber, { method, matchHeadCommit })
|
|
5412
5713
|
)
|
|
5413
5714
|
);
|
|
@@ -5421,8 +5722,8 @@ function runMergeCoordinator(options) {
|
|
|
5421
5722
|
};
|
|
5422
5723
|
}
|
|
5423
5724
|
if (waitForTargetGreen) {
|
|
5424
|
-
const targetEither = yield*
|
|
5425
|
-
|
|
5725
|
+
const targetEither = yield* Effect8.either(
|
|
5726
|
+
Effect8.tryPromise(
|
|
5426
5727
|
() => waitForBranchChecks(prProvider, logger, {
|
|
5427
5728
|
branchName: targetBranch,
|
|
5428
5729
|
checksFoundTimeoutMs: checkWaitOptions.checksFoundTimeoutMs,
|
|
@@ -5583,10 +5884,10 @@ function readIssueFinalReviewArtifact(worktreeState, worktreePath) {
|
|
|
5583
5884
|
return null;
|
|
5584
5885
|
}
|
|
5585
5886
|
const artifactPath = isAbsolute3(configuredPath) ? configuredPath : join12(worktreePath, configuredPath);
|
|
5586
|
-
if (!
|
|
5887
|
+
if (!existsSync11(artifactPath)) return null;
|
|
5587
5888
|
let parsed;
|
|
5588
5889
|
try {
|
|
5589
|
-
parsed = JSON.parse(
|
|
5890
|
+
parsed = JSON.parse(readFileSync13(artifactPath, "utf-8"));
|
|
5590
5891
|
} catch {
|
|
5591
5892
|
return null;
|
|
5592
5893
|
}
|
|
@@ -5621,6 +5922,9 @@ function buildIssueFinalReviewPrComment(artifact) {
|
|
|
5621
5922
|
`- **Required:** ${formatBoolean(artifact.verification.required)}`,
|
|
5622
5923
|
`- **Passed:** ${formatBoolean(artifact.verification.passed)}`
|
|
5623
5924
|
);
|
|
5925
|
+
if (typeof artifact.verification.method === "string") {
|
|
5926
|
+
lines.push(`- **Method:** ${formatLabel(artifact.verification.method)}`);
|
|
5927
|
+
}
|
|
5624
5928
|
const commands = Array.isArray(artifact.verification.commands) ? artifact.verification.commands.filter(
|
|
5625
5929
|
(command) => typeof command === "string"
|
|
5626
5930
|
) : [];
|
|
@@ -5818,7 +6122,7 @@ function findSecretLikeContent(source, content) {
|
|
|
5818
6122
|
];
|
|
5819
6123
|
for (const { kind, regex } of patterns) {
|
|
5820
6124
|
for (const match of content.matchAll(regex)) {
|
|
5821
|
-
if (isAllowedSecretPlaceholder(match[0])) {
|
|
6125
|
+
if (isAllowedSecretPlaceholder(match[0]) || isAllowedDocumentationTokenReference(match[0])) {
|
|
5822
6126
|
continue;
|
|
5823
6127
|
}
|
|
5824
6128
|
findings.push({ source, kind });
|
|
@@ -5839,9 +6143,12 @@ function isAllowedSecretPlaceholder(value) {
|
|
|
5839
6143
|
"xxxxx"
|
|
5840
6144
|
].some((allowed) => normalized.includes(allowed));
|
|
5841
6145
|
}
|
|
6146
|
+
function isAllowedDocumentationTokenReference(value) {
|
|
6147
|
+
return /^token\s*:\s*["']?<verdict>/i.test(value);
|
|
6148
|
+
}
|
|
5842
6149
|
|
|
5843
6150
|
// commands/issue-final-review-agent.ts
|
|
5844
|
-
import { existsSync as
|
|
6151
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
5845
6152
|
import { join as join13 } from "path";
|
|
5846
6153
|
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join13(
|
|
5847
6154
|
".pourkit",
|
|
@@ -5868,7 +6175,9 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
5868
6175
|
const prompt = loadIssueFinalReviewPrompt({
|
|
5869
6176
|
repoRoot: repoRoot2,
|
|
5870
6177
|
promptTemplate: agent.promptTemplate,
|
|
5871
|
-
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
|
|
5872
6181
|
});
|
|
5873
6182
|
const entry = await executeWithMissingOrEmptyArtifactRetry({
|
|
5874
6183
|
executionProvider,
|
|
@@ -5964,10 +6273,16 @@ function extractChangedPaths(parsed) {
|
|
|
5964
6273
|
).filter((path9) => typeof path9 === "string") : [];
|
|
5965
6274
|
}
|
|
5966
6275
|
function loadIssueFinalReviewPrompt(options) {
|
|
5967
|
-
const {
|
|
6276
|
+
const {
|
|
6277
|
+
repoRoot: repoRoot2,
|
|
6278
|
+
promptTemplate,
|
|
6279
|
+
validationCommand,
|
|
6280
|
+
reviewArtifactPath,
|
|
6281
|
+
reviewVerdict
|
|
6282
|
+
} = options;
|
|
5968
6283
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
5969
|
-
const promptBody =
|
|
5970
|
-
|
|
6284
|
+
const promptBody = existsSync12(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
|
|
6285
|
+
let prompt = appendProtectedWorkGuidance(`${promptBody}
|
|
5971
6286
|
|
|
5972
6287
|
## Shared Run Context
|
|
5973
6288
|
|
|
@@ -5976,9 +6291,12 @@ Read the selected issue requirements, planning context (including plan and PRD c
|
|
|
5976
6291
|
## Initial Verification Pass
|
|
5977
6292
|
|
|
5978
6293
|
- First read ${RUN_CONTEXT_PATH_IN_WORKTREE} only far enough to identify the configured verification commands.
|
|
5979
|
-
- Before reviewing code, diffs, artifacts, or prior findings, run
|
|
5980
|
-
-
|
|
5981
|
-
-
|
|
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"\`.
|
|
6298
|
+
- If a configured verification command fails because of fixable Issue work, self-retouch the Worktree, then rerun all configured verification commands.
|
|
6299
|
+
- Do not return \`pass\` while any configured verification command is failing.
|
|
5982
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.
|
|
5983
6301
|
- If no verification commands are configured, note that and proceed with normal review.
|
|
5984
6302
|
|
|
@@ -5998,6 +6316,19 @@ Read the selected issue requirements, planning context (including plan and PRD c
|
|
|
5998
6316
|
- This validation checks both the artifact shape and \`changedFiles\` coverage against the canonical Git diff.
|
|
5999
6317
|
- If validation fails, fix the artifact or safely retouch the worktree as needed, then rerun the same command.
|
|
6000
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;
|
|
6001
6332
|
}
|
|
6002
6333
|
|
|
6003
6334
|
// issues/issue-worktree-resolution.ts
|
|
@@ -6167,16 +6498,6 @@ function checkIssueGates(issue, config, force) {
|
|
|
6167
6498
|
}
|
|
6168
6499
|
return { allowed: true, gates };
|
|
6169
6500
|
}
|
|
6170
|
-
function resolveSerenaRuntimeConfig(config, target) {
|
|
6171
|
-
return {
|
|
6172
|
-
enabled: target.serena?.enabled ?? config.serena.enabled,
|
|
6173
|
-
required: target.serena?.required ?? config.serena.required,
|
|
6174
|
-
autoStart: config.serena.autoStart,
|
|
6175
|
-
dataDir: config.serena.dataDir,
|
|
6176
|
-
mcpUrl: config.serena.mcpUrl,
|
|
6177
|
-
sandboxMcpUrl: config.serena.sandboxMcpUrl
|
|
6178
|
-
};
|
|
6179
|
-
}
|
|
6180
6501
|
function assertIssueFinalReviewPassed(worktreeState) {
|
|
6181
6502
|
const ifr = worktreeState?.issueFinalReview;
|
|
6182
6503
|
if (!ifr || !ifr.completed || ifr.verdict !== "pass") {
|
|
@@ -6197,6 +6518,7 @@ async function advanceIssueFinalReview(options) {
|
|
|
6197
6518
|
repoRoot: repoRoot2,
|
|
6198
6519
|
logger,
|
|
6199
6520
|
reviewArtifactPath,
|
|
6521
|
+
reviewVerdict,
|
|
6200
6522
|
worktreeState
|
|
6201
6523
|
} = options;
|
|
6202
6524
|
const ifrFromState = worktreeState?.issueFinalReview;
|
|
@@ -6237,9 +6559,10 @@ async function advanceIssueFinalReview(options) {
|
|
|
6237
6559
|
repoRoot: repoRoot2,
|
|
6238
6560
|
logger,
|
|
6239
6561
|
reviewArtifactPath,
|
|
6562
|
+
reviewVerdict,
|
|
6240
6563
|
...options.memory ? { memory: options.memory } : {}
|
|
6241
6564
|
});
|
|
6242
|
-
await
|
|
6565
|
+
await assertCanonicalBaseAncestor2({
|
|
6243
6566
|
worktreePath,
|
|
6244
6567
|
baseRef: `origin/${target.baseBranch}`,
|
|
6245
6568
|
stageName: "Issue Final Review",
|
|
@@ -6292,35 +6615,7 @@ async function startIssueRun(options) {
|
|
|
6292
6615
|
const effectiveTarget = { ...target, baseBranch: effectiveBaseBranch };
|
|
6293
6616
|
const branchName = renderBranchName(target.branchTemplate, issue);
|
|
6294
6617
|
const strategy = target.strategy;
|
|
6295
|
-
const serenaRuntimeConfig = resolveSerenaRuntimeConfig(config, target);
|
|
6296
|
-
const shouldPrepareSerena = serenaRuntimeConfig.enabled || serenaRuntimeConfig.required;
|
|
6297
6618
|
let serenaExecutionContext;
|
|
6298
|
-
if (shouldPrepareSerena) {
|
|
6299
|
-
const serenaPreflight = await prepareSerenaForTarget({
|
|
6300
|
-
repoRoot: ROOT,
|
|
6301
|
-
targetName: target.name,
|
|
6302
|
-
baseBranch: target.baseBranch,
|
|
6303
|
-
dataDir: serenaRuntimeConfig.dataDir,
|
|
6304
|
-
mcpUrl: serenaRuntimeConfig.mcpUrl,
|
|
6305
|
-
enabled: shouldPrepareSerena,
|
|
6306
|
-
required: serenaRuntimeConfig.required,
|
|
6307
|
-
autoStart: serenaRuntimeConfig.autoStart,
|
|
6308
|
-
logger
|
|
6309
|
-
});
|
|
6310
|
-
if (serenaPreflight.enabled && serenaPreflight.available) {
|
|
6311
|
-
serenaExecutionContext = {
|
|
6312
|
-
available: true,
|
|
6313
|
-
sandboxMcpUrl: serenaRuntimeConfig.sandboxMcpUrl
|
|
6314
|
-
};
|
|
6315
|
-
}
|
|
6316
|
-
if (serenaPreflight.enabled && !serenaPreflight.available) {
|
|
6317
|
-
const message = `Serena preflight unavailable for target ${target.name}: ${serenaPreflight.error}`;
|
|
6318
|
-
if (serenaRuntimeConfig.required) {
|
|
6319
|
-
throw new Error(message);
|
|
6320
|
-
}
|
|
6321
|
-
logger.step("warn", message);
|
|
6322
|
-
}
|
|
6323
|
-
}
|
|
6324
6619
|
const memoryExecutionContext = config.memory?.enabled ? { available: true, sandboxDbPath: "/home/agent/.pourkit/icm/memories.db" } : void 0;
|
|
6325
6620
|
if (options.resetWorktree) {
|
|
6326
6621
|
const existingPr = await prProvider.getPr(branchName);
|
|
@@ -6332,10 +6627,6 @@ async function startIssueRun(options) {
|
|
|
6332
6627
|
await resetIssueWorktree(ROOT, branchName, logger);
|
|
6333
6628
|
await syncTargetBranch(ROOT, effectiveBaseBranch, logger);
|
|
6334
6629
|
}
|
|
6335
|
-
const prompt = loadBuilderPrompt(
|
|
6336
|
-
ROOT,
|
|
6337
|
-
strategy.implement.builder.promptTemplate
|
|
6338
|
-
);
|
|
6339
6630
|
const resolution = await resolveIssueWorktree(
|
|
6340
6631
|
ROOT,
|
|
6341
6632
|
branchName,
|
|
@@ -6380,68 +6671,33 @@ async function startIssueRun(options) {
|
|
|
6380
6671
|
});
|
|
6381
6672
|
await issueProvider.addLabels(issueNumber, [config.labels.agentInProgress]);
|
|
6382
6673
|
await issueProvider.removeLabel(issueNumber, config.labels.readyForAgent);
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
autoApprove: true,
|
|
6401
|
-
timeoutMs: EXECUTION_TIMEOUT_MS,
|
|
6402
|
-
...resolution.worktreePath ? { worktreePath: resolution.worktreePath } : {},
|
|
6403
|
-
artifacts: [runContextArtifact],
|
|
6404
|
-
logger
|
|
6405
|
-
});
|
|
6406
|
-
if (!executionResult.success) {
|
|
6407
|
-
throw new Error(`Sandcastle failed: ${executionResult.error}`);
|
|
6408
|
-
}
|
|
6409
|
-
} else {
|
|
6410
|
-
executionResult = {
|
|
6411
|
-
success: true,
|
|
6412
|
-
branch: branchName,
|
|
6413
|
-
worktreePath: resolution.worktreePath,
|
|
6414
|
-
commits: [],
|
|
6415
|
-
logPath: null
|
|
6416
|
-
};
|
|
6417
|
-
}
|
|
6418
|
-
if (executionResult.worktreePath) {
|
|
6419
|
-
if (shouldRunBuilder) {
|
|
6420
|
-
await assertCanonicalBaseAncestor({
|
|
6421
|
-
worktreePath: executionResult.worktreePath,
|
|
6422
|
-
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6423
|
-
stageName: "Builder",
|
|
6424
|
-
logger
|
|
6425
|
-
});
|
|
6426
|
-
}
|
|
6427
|
-
if (worktreeState) {
|
|
6428
|
-
updateWorktreeRunState(executionResult.worktreePath, {
|
|
6429
|
-
completedStages: { builder: true }
|
|
6430
|
-
});
|
|
6431
|
-
} else {
|
|
6432
|
-
writeWorktreeRunState(executionResult.worktreePath, {
|
|
6433
|
-
issueNumber,
|
|
6434
|
-
targetName: target.name,
|
|
6435
|
-
branchName,
|
|
6436
|
-
baseBranch: effectiveBaseBranch,
|
|
6437
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6438
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6439
|
-
completedStages: { builder: true },
|
|
6440
|
-
review: { lifetimeIterations: 0 }
|
|
6441
|
-
});
|
|
6674
|
+
const builderResult = await runIssueBuilder({
|
|
6675
|
+
repoRoot: ROOT,
|
|
6676
|
+
issueNumber,
|
|
6677
|
+
target: effectiveTarget,
|
|
6678
|
+
config,
|
|
6679
|
+
branchName,
|
|
6680
|
+
...resolution.worktreePath ? { worktreePath: resolution.worktreePath } : {},
|
|
6681
|
+
...resolution.mode === "new" ? { baseRef: resolution.baseRef } : {},
|
|
6682
|
+
worktreeState,
|
|
6683
|
+
restartBuilder: resolution.mode === "new",
|
|
6684
|
+
executionProvider,
|
|
6685
|
+
runContextArtifact,
|
|
6686
|
+
logger,
|
|
6687
|
+
...memoryExecutionContext ? { memory: memoryExecutionContext } : {},
|
|
6688
|
+
timeoutMs: EXECUTION_TIMEOUT_MS,
|
|
6689
|
+
onSerenaExecutionContext: (context) => {
|
|
6690
|
+
serenaExecutionContext = context;
|
|
6442
6691
|
}
|
|
6443
|
-
}
|
|
6444
|
-
const
|
|
6692
|
+
});
|
|
6693
|
+
const executionResult = {
|
|
6694
|
+
success: true,
|
|
6695
|
+
branch: branchName,
|
|
6696
|
+
worktreePath: builderResult.worktreePath ?? issueWorktreePath(ROOT, branchName),
|
|
6697
|
+
commits: [],
|
|
6698
|
+
logPath: null
|
|
6699
|
+
};
|
|
6700
|
+
const finalWorktreeState = builderResult.worktreeState;
|
|
6445
6701
|
return {
|
|
6446
6702
|
issue,
|
|
6447
6703
|
parentPrdIssue,
|
|
@@ -6469,7 +6725,7 @@ async function fetchParentPrdIssue(issue, issueProvider) {
|
|
|
6469
6725
|
async function advanceIssueRunReview(options) {
|
|
6470
6726
|
const accumulatedRefactorPaths = [];
|
|
6471
6727
|
const reviewResult = await runEffectAndMapExit(
|
|
6472
|
-
|
|
6728
|
+
runIssueReviewLoop({
|
|
6473
6729
|
...options,
|
|
6474
6730
|
onRefactorProgress: (progress) => {
|
|
6475
6731
|
if (progress.refactorArtifactPath) {
|
|
@@ -6486,22 +6742,35 @@ async function advanceIssueRunReview(options) {
|
|
|
6486
6742
|
}
|
|
6487
6743
|
})
|
|
6488
6744
|
);
|
|
6489
|
-
await
|
|
6745
|
+
await assertCanonicalBaseAncestor2({
|
|
6490
6746
|
worktreePath: options.worktreePath,
|
|
6491
6747
|
baseRef: `origin/${options.target.baseBranch}`,
|
|
6492
6748
|
stageName: "Review/Refactor",
|
|
6493
6749
|
logger: options.logger
|
|
6494
6750
|
});
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
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
|
+
}
|
|
6505
6774
|
return reviewResult;
|
|
6506
6775
|
}
|
|
6507
6776
|
async function completeIssueRun(options) {
|
|
@@ -6552,12 +6821,12 @@ async function completeIssueRun(options) {
|
|
|
6552
6821
|
prTitle = finalizerFromState.title;
|
|
6553
6822
|
prBody = finalizerFromState.body;
|
|
6554
6823
|
} else if (finalizerFromState.artifactPath) {
|
|
6555
|
-
if (!
|
|
6824
|
+
if (!existsSync13(finalizerFromState.artifactPath)) {
|
|
6556
6825
|
throw new FinalizerFailure({
|
|
6557
6826
|
message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
|
|
6558
6827
|
});
|
|
6559
6828
|
}
|
|
6560
|
-
const artifactContent =
|
|
6829
|
+
const artifactContent = readFileSync15(
|
|
6561
6830
|
finalizerFromState.artifactPath,
|
|
6562
6831
|
"utf-8"
|
|
6563
6832
|
);
|
|
@@ -6595,7 +6864,7 @@ async function completeIssueRun(options) {
|
|
|
6595
6864
|
prTitle = finalizerResult.title;
|
|
6596
6865
|
prBody = finalizerResult.body;
|
|
6597
6866
|
if (executionResult.worktreePath) {
|
|
6598
|
-
await
|
|
6867
|
+
await assertCanonicalBaseAncestor2({
|
|
6599
6868
|
worktreePath: executionResult.worktreePath,
|
|
6600
6869
|
baseRef: `origin/${effectiveBaseBranch}`,
|
|
6601
6870
|
stageName: "Finalizer",
|
|
@@ -6699,14 +6968,14 @@ async function transitionIssueToHumanHandoff(options) {
|
|
|
6699
6968
|
const transitions = makeIssueTransitions2(issueProvider, config);
|
|
6700
6969
|
await transitions.moveToReadyForHuman(issueNumber);
|
|
6701
6970
|
const summary = extractHumanHandoffSummary(reviewResult.output);
|
|
6702
|
-
const refactorDir = getRefactorArtifactDir(reviewResult.
|
|
6971
|
+
const refactorDir = getRefactorArtifactDir(reviewResult.reviewArtifactPath);
|
|
6703
6972
|
const comment = [
|
|
6704
6973
|
"Pourkit stopped the review/refactor loop because human input is needed.",
|
|
6705
6974
|
"",
|
|
6706
6975
|
summary,
|
|
6707
6976
|
"",
|
|
6708
6977
|
"Artifacts:",
|
|
6709
|
-
`- Review: ${reviewResult.
|
|
6978
|
+
`- Review: ${reviewResult.reviewArtifactPath}`,
|
|
6710
6979
|
`- Refactors: ${refactorDir}`
|
|
6711
6980
|
].join("\n");
|
|
6712
6981
|
await issueProvider.commentIssue(issueNumber, comment);
|
|
@@ -6735,7 +7004,7 @@ function getRefactorArtifactDir(artifactPath) {
|
|
|
6735
7004
|
}
|
|
6736
7005
|
async function finalizeWorktreeCommit(options) {
|
|
6737
7006
|
const { worktreePath, baseRef, title, body, logger } = options;
|
|
6738
|
-
await
|
|
7007
|
+
await assertCanonicalBaseAncestor2({
|
|
6739
7008
|
worktreePath,
|
|
6740
7009
|
baseRef,
|
|
6741
7010
|
stageName: "final commit",
|
|
@@ -6757,9 +7026,9 @@ async function finalizeWorktreeCommit(options) {
|
|
|
6757
7026
|
label: "git commit"
|
|
6758
7027
|
});
|
|
6759
7028
|
}
|
|
6760
|
-
async function
|
|
7029
|
+
async function assertCanonicalBaseAncestor2(options) {
|
|
6761
7030
|
const { worktreePath, baseRef, stageName, logger } = options;
|
|
6762
|
-
await
|
|
7031
|
+
await syncRemoteBaseRef2(worktreePath, baseRef, logger);
|
|
6763
7032
|
try {
|
|
6764
7033
|
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
6765
7034
|
cwd: worktreePath,
|
|
@@ -6821,8 +7090,8 @@ function normalizeGitPath2(path9) {
|
|
|
6821
7090
|
function isIssueFinalReviewAuditedPath2(path9) {
|
|
6822
7091
|
return path9 !== "" && path9 !== WORKTREE_RUN_STATE_PATH && !path9.startsWith(".pourkit/.tmp/") && !path9.startsWith(".pourkit/logs/");
|
|
6823
7092
|
}
|
|
6824
|
-
async function
|
|
6825
|
-
const remoteBase =
|
|
7093
|
+
async function syncRemoteBaseRef2(worktreePath, baseRef, logger) {
|
|
7094
|
+
const remoteBase = parseRemoteBaseRef2(baseRef);
|
|
6826
7095
|
if (!remoteBase) {
|
|
6827
7096
|
return;
|
|
6828
7097
|
}
|
|
@@ -6832,7 +7101,7 @@ async function syncRemoteBaseRef(worktreePath, baseRef, logger) {
|
|
|
6832
7101
|
label: "git fetch target"
|
|
6833
7102
|
});
|
|
6834
7103
|
}
|
|
6835
|
-
function
|
|
7104
|
+
function parseRemoteBaseRef2(baseRef) {
|
|
6836
7105
|
const [remote, ...branchParts] = baseRef.split("/");
|
|
6837
7106
|
const branch = branchParts.join("/");
|
|
6838
7107
|
if (!remote || !branch) {
|
|
@@ -6911,15 +7180,6 @@ async function closeNoOpIssue(provider, issueNumber, config, logger) {
|
|
|
6911
7180
|
await maybeCloseParentAfterChildCompletion(provider, issueNumber, logger);
|
|
6912
7181
|
}
|
|
6913
7182
|
}
|
|
6914
|
-
function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
6915
|
-
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
6916
|
-
const promptBody = existsSync12(promptPath) ? readFileSync14(promptPath, "utf-8") : promptTemplate;
|
|
6917
|
-
return appendProtectedWorkGuidance(`${promptBody}
|
|
6918
|
-
|
|
6919
|
-
## Shared Run Context
|
|
6920
|
-
|
|
6921
|
-
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}`);
|
|
6922
|
-
}
|
|
6923
7183
|
|
|
6924
7184
|
// issues/issue-run-lifecycle.ts
|
|
6925
7185
|
var IssueRunHumanHandoffStop = class extends Error {
|
|
@@ -6933,8 +7193,10 @@ async function runIssueLifecycle(options) {
|
|
|
6933
7193
|
const startResult = await options.deps.startIssueRun(options.runOptions);
|
|
6934
7194
|
const reviewAlreadyPassed = startResult.worktreeState?.review.lastVerdict && !startResult.worktreeState.review.exhaustedPreviousRun && (startResult.worktreeState.review.lastVerdict === "PASS" || startResult.worktreeState.review.lastVerdict === "PASS_WITH_NOTES");
|
|
6935
7195
|
let reviewArtifactPath;
|
|
7196
|
+
let reviewVerdict = void 0;
|
|
6936
7197
|
if (reviewAlreadyPassed) {
|
|
6937
7198
|
reviewArtifactPath = startResult.worktreeState.review.lastArtifactPath;
|
|
7199
|
+
reviewVerdict = startResult.worktreeState.review.lastVerdict;
|
|
6938
7200
|
} else {
|
|
6939
7201
|
const reviewResult = await options.deps.advanceIssueRunReview({
|
|
6940
7202
|
executionProvider: options.runOptions.executionProvider,
|
|
@@ -6951,28 +7213,34 @@ async function runIssueLifecycle(options) {
|
|
|
6951
7213
|
serena: startResult.serena,
|
|
6952
7214
|
memory: startResult.memory
|
|
6953
7215
|
});
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
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
|
+
);
|
|
6974
7243
|
}
|
|
6975
|
-
reviewArtifactPath = reviewResult.artifactPath;
|
|
6976
7244
|
}
|
|
6977
7245
|
const finalReviewResult = await options.deps.advanceIssueFinalReview({
|
|
6978
7246
|
executionProvider: options.runOptions.executionProvider,
|
|
@@ -6985,6 +7253,7 @@ async function runIssueLifecycle(options) {
|
|
|
6985
7253
|
repoRoot: options.repoRoot,
|
|
6986
7254
|
logger: options.logger,
|
|
6987
7255
|
reviewArtifactPath,
|
|
7256
|
+
reviewVerdict,
|
|
6988
7257
|
worktreeState: startResult.worktreeState,
|
|
6989
7258
|
memory: startResult.memory
|
|
6990
7259
|
});
|
|
@@ -7186,9 +7455,9 @@ import { join as join17 } from "path";
|
|
|
7186
7455
|
|
|
7187
7456
|
// prd-run/state.ts
|
|
7188
7457
|
import {
|
|
7189
|
-
existsSync as
|
|
7458
|
+
existsSync as existsSync14,
|
|
7190
7459
|
mkdirSync as mkdirSync7,
|
|
7191
|
-
readFileSync as
|
|
7460
|
+
readFileSync as readFileSync16,
|
|
7192
7461
|
readdirSync as readdirSync3,
|
|
7193
7462
|
writeFileSync as writeFileSync4
|
|
7194
7463
|
} from "fs";
|
|
@@ -7277,16 +7546,16 @@ function normalizePrdRunRef(ref) {
|
|
|
7277
7546
|
function readPrdRun(repoRoot2, prdRef) {
|
|
7278
7547
|
const normalized = normalizePrdRunRef(prdRef);
|
|
7279
7548
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7280
|
-
if (!
|
|
7549
|
+
if (!existsSync14(recordPath)) {
|
|
7281
7550
|
return { record: null, diagnostics: [] };
|
|
7282
7551
|
}
|
|
7283
7552
|
try {
|
|
7284
|
-
const raw = JSON.parse(
|
|
7553
|
+
const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
|
|
7285
7554
|
const parsed = PrdRunRecordSchema.parse(raw);
|
|
7286
7555
|
return { record: parsed, diagnostics: [] };
|
|
7287
7556
|
} catch (error) {
|
|
7288
7557
|
try {
|
|
7289
|
-
const raw = JSON.parse(
|
|
7558
|
+
const raw = JSON.parse(readFileSync16(recordPath, "utf-8"));
|
|
7290
7559
|
if (raw && typeof raw === "object" && raw.start && typeof raw.start === "object" && raw.start.startBaseBranch === void 0) {
|
|
7291
7560
|
return {
|
|
7292
7561
|
record: raw,
|
|
@@ -7308,7 +7577,7 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7308
7577
|
}
|
|
7309
7578
|
function listPrdRuns(repoRoot2) {
|
|
7310
7579
|
const stateDir = join16(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7311
|
-
if (!
|
|
7580
|
+
if (!existsSync14(stateDir)) {
|
|
7312
7581
|
return { records: [], diagnostics: [] };
|
|
7313
7582
|
}
|
|
7314
7583
|
const records = [];
|
|
@@ -7320,7 +7589,7 @@ function listPrdRuns(repoRoot2) {
|
|
|
7320
7589
|
const recordPath = join16(stateDir, entry.name);
|
|
7321
7590
|
try {
|
|
7322
7591
|
const record = PrdRunRecordSchema.parse(
|
|
7323
|
-
JSON.parse(
|
|
7592
|
+
JSON.parse(readFileSync16(recordPath, "utf-8"))
|
|
7324
7593
|
);
|
|
7325
7594
|
records.push(record);
|
|
7326
7595
|
} catch (error) {
|
|
@@ -7356,7 +7625,7 @@ import { Match, pipe } from "effect";
|
|
|
7356
7625
|
|
|
7357
7626
|
// commands/queue.ts
|
|
7358
7627
|
init_common();
|
|
7359
|
-
import { Effect as
|
|
7628
|
+
import { Effect as Effect9 } from "effect";
|
|
7360
7629
|
|
|
7361
7630
|
// issues/select-issue.ts
|
|
7362
7631
|
init_common();
|
|
@@ -7529,8 +7798,8 @@ function issueDataToCandidate(issue) {
|
|
|
7529
7798
|
function selectNextQueueIssue(options) {
|
|
7530
7799
|
const { config, issueProvider, logger, prdRef } = options;
|
|
7531
7800
|
logger.step("info", "Loading candidate issues from provider");
|
|
7532
|
-
return
|
|
7533
|
-
const candidates = yield*
|
|
7801
|
+
return Effect9.gen(function* () {
|
|
7802
|
+
const candidates = yield* Effect9.tryPromise({
|
|
7534
7803
|
try: () => issueProvider.listCandidates(),
|
|
7535
7804
|
catch: (e) => {
|
|
7536
7805
|
if (e instanceof Error) {
|
|
@@ -7601,7 +7870,7 @@ function selectNextQueueIssue(options) {
|
|
|
7601
7870
|
(c) => c.number === selection.issue.number
|
|
7602
7871
|
);
|
|
7603
7872
|
if (!selected) {
|
|
7604
|
-
return yield*
|
|
7873
|
+
return yield* Effect9.die(
|
|
7605
7874
|
new Error(
|
|
7606
7875
|
`Selected issue #${selection.issue.number} not found in candidate list`
|
|
7607
7876
|
)
|
|
@@ -7656,8 +7925,8 @@ function makeReconcileDeps(options) {
|
|
|
7656
7925
|
};
|
|
7657
7926
|
}
|
|
7658
7927
|
function reconcileBlockedEffect(options) {
|
|
7659
|
-
return
|
|
7660
|
-
const blocked2 = yield*
|
|
7928
|
+
return Effect9.gen(function* () {
|
|
7929
|
+
const blocked2 = yield* Effect9.tryPromise({
|
|
7661
7930
|
try: () => options.issueProvider.listBlockedIssues(),
|
|
7662
7931
|
catch: (e) => {
|
|
7663
7932
|
if (e instanceof Error) {
|
|
@@ -7671,7 +7940,7 @@ function reconcileBlockedEffect(options) {
|
|
|
7671
7940
|
"info",
|
|
7672
7941
|
`Reconciling ${blocked2.length} blocked issue(s)`
|
|
7673
7942
|
);
|
|
7674
|
-
yield*
|
|
7943
|
+
yield* Effect9.tryPromise({
|
|
7675
7944
|
try: () => reconcileBlockedIssues(blocked2, makeReconcileDeps(options)),
|
|
7676
7945
|
catch: (e) => {
|
|
7677
7946
|
if (e instanceof Error) {
|
|
@@ -7695,7 +7964,7 @@ function runOneQueueIssueEffect(options) {
|
|
|
7695
7964
|
repoRoot: repoRoot2,
|
|
7696
7965
|
prdRef
|
|
7697
7966
|
} = options;
|
|
7698
|
-
return
|
|
7967
|
+
return Effect9.gen(function* () {
|
|
7699
7968
|
const outcome = yield* selectNextQueueIssue({
|
|
7700
7969
|
config,
|
|
7701
7970
|
issueProvider,
|
|
@@ -7711,7 +7980,7 @@ function runOneQueueIssueEffect(options) {
|
|
|
7711
7980
|
}
|
|
7712
7981
|
const { issue: selected } = outcome;
|
|
7713
7982
|
const baseBranchOverride = options.queueRunContext?.prdBranch;
|
|
7714
|
-
const runResult = yield*
|
|
7983
|
+
const runResult = yield* Effect9.tryPromise({
|
|
7715
7984
|
try: () => runIssueCommand({
|
|
7716
7985
|
issueNumber: selected.number,
|
|
7717
7986
|
targetName,
|
|
@@ -7751,7 +8020,7 @@ function runQueue(options) {
|
|
|
7751
8020
|
return runOneQueueIssueEffect(options);
|
|
7752
8021
|
}
|
|
7753
8022
|
function runQueueLoopEffect(options, results) {
|
|
7754
|
-
return
|
|
8023
|
+
return Effect9.gen(function* () {
|
|
7755
8024
|
yield* reconcileBlockedEffect(options);
|
|
7756
8025
|
const outcome = yield* runOneQueueIssueEffect(options);
|
|
7757
8026
|
if (outcome.selected === null) {
|
|
@@ -7768,7 +8037,7 @@ function runQueueLoopEffect(options, results) {
|
|
|
7768
8037
|
};
|
|
7769
8038
|
}
|
|
7770
8039
|
const newResults = [...results, outcome];
|
|
7771
|
-
const processedIssue = yield*
|
|
8040
|
+
const processedIssue = yield* Effect9.tryPromise({
|
|
7772
8041
|
try: () => options.issueProvider.fetchIssue(outcome.selected.number),
|
|
7773
8042
|
catch: (e) => {
|
|
7774
8043
|
if (e instanceof Error) {
|
|
@@ -9103,7 +9372,7 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9103
9372
|
}
|
|
9104
9373
|
|
|
9105
9374
|
// prd-run/final-review-validation.ts
|
|
9106
|
-
import { existsSync as
|
|
9375
|
+
import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
|
|
9107
9376
|
|
|
9108
9377
|
// commands/prd-run.ts
|
|
9109
9378
|
async function runPrdRunLaunchCommand(options) {
|
|
@@ -9613,7 +9882,7 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
|
|
|
9613
9882
|
}
|
|
9614
9883
|
|
|
9615
9884
|
// commands/init.ts
|
|
9616
|
-
import { existsSync as
|
|
9885
|
+
import { existsSync as existsSync16, statSync } from "fs";
|
|
9617
9886
|
import {
|
|
9618
9887
|
copyFile,
|
|
9619
9888
|
mkdir as mkdir4,
|
|
@@ -9819,7 +10088,17 @@ function buildManagedAgentInstructions(options) {
|
|
|
9819
10088
|
|
|
9820
10089
|
Use \`fd\` for file discovery, \`rg\` for text search, and direct file reads for focused context.
|
|
9821
10090
|
|
|
9822
|
-
|
|
10091
|
+
For multi-step exploration, prefer the dedicated subagents in \`.opencode/agents\`:
|
|
10092
|
+
|
|
10093
|
+
- Use \`codebase-locator\` when you need to find relevant files, directories, or entry points.
|
|
10094
|
+
- Use \`codebase-analyzer\` when you need to understand how a specific component or flow works.
|
|
10095
|
+
- Use \`codebase-pattern-finder\` when you need examples of similar implementations or test patterns.
|
|
10096
|
+
- Use \`pourkit-docs-locator\` when searching \`.pourkit/\` context docs or ADRs.
|
|
10097
|
+
- Use \`pourkit-docs-analyzer\` to explain specific context docs or ADRs after locating them.
|
|
10098
|
+
|
|
10099
|
+
Use direct \`fd\`/\`rg\`/reads for focused checks; use subagents when the search would otherwise require several discovery/read steps.
|
|
10100
|
+
|
|
10101
|
+
Use \`opensrc path <package-or-repo>\` whenever implementation requires reading external source code, not only package dependencies. If you would otherwise clone a repository to inspect its source, use \`opensrc\` first, then search or read files under the returned source path with \`rg\` and direct file reads.
|
|
9823
10102
|
|
|
9824
10103
|
Follow the project's domain docs and workflow routes documented in the Pourkit managed block below.
|
|
9825
10104
|
|
|
@@ -9928,7 +10207,7 @@ function resolveSourceAssetPath(sourceRoot, ...segments) {
|
|
|
9928
10207
|
path5.join(sourceRoot, "dist", ...segments)
|
|
9929
10208
|
];
|
|
9930
10209
|
for (const candidatePath of candidatePaths) {
|
|
9931
|
-
if (
|
|
10210
|
+
if (existsSync16(candidatePath)) return candidatePath;
|
|
9932
10211
|
}
|
|
9933
10212
|
return path5.join(sourceRoot, "pourkit", ...segments);
|
|
9934
10213
|
}
|
|
@@ -10113,8 +10392,7 @@ function generateConfigTemplate(options) {
|
|
|
10113
10392
|
model: "opencode-go/qwen3.6-plus",
|
|
10114
10393
|
promptTemplate: ".pourkit/managed/prompts/refactor.prompt.md"
|
|
10115
10394
|
},
|
|
10116
|
-
maxIterations: 3
|
|
10117
|
-
passWithNotesRefactorAttempts: 2
|
|
10395
|
+
maxIterations: 3
|
|
10118
10396
|
},
|
|
10119
10397
|
issueFinalReview: {
|
|
10120
10398
|
agent: "pourkit-reviewer",
|
|
@@ -10144,7 +10422,7 @@ function generateConfigTemplate(options) {
|
|
|
10144
10422
|
}
|
|
10145
10423
|
const config = {
|
|
10146
10424
|
$schema: "./schema/pourkit.schema.json",
|
|
10147
|
-
schemaVersion:
|
|
10425
|
+
schemaVersion: 3,
|
|
10148
10426
|
targets: [target],
|
|
10149
10427
|
workflowPack: {
|
|
10150
10428
|
version: "1.0.0",
|
|
@@ -10291,7 +10569,7 @@ async function computeFileChecksum(filePath) {
|
|
|
10291
10569
|
return createHash("sha256").update(content).digest("hex");
|
|
10292
10570
|
}
|
|
10293
10571
|
function lockfileExists(root, name) {
|
|
10294
|
-
return
|
|
10572
|
+
return existsSync16(path5.join(root, name));
|
|
10295
10573
|
}
|
|
10296
10574
|
function detectPackageManager(root) {
|
|
10297
10575
|
if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
|
|
@@ -10335,7 +10613,7 @@ async function discoverLocalSource(sourcePath) {
|
|
|
10335
10613
|
async function discoverReadme(root) {
|
|
10336
10614
|
for (const name of ["README.md", "readme.md"]) {
|
|
10337
10615
|
const p = path5.join(root, name);
|
|
10338
|
-
if (
|
|
10616
|
+
if (existsSync16(p)) {
|
|
10339
10617
|
return p;
|
|
10340
10618
|
}
|
|
10341
10619
|
}
|
|
@@ -10345,7 +10623,7 @@ async function discoverAgentFiles(root) {
|
|
|
10345
10623
|
const files = [];
|
|
10346
10624
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
10347
10625
|
const p = path5.join(root, name);
|
|
10348
|
-
if (
|
|
10626
|
+
if (existsSync16(p)) {
|
|
10349
10627
|
files.push(p);
|
|
10350
10628
|
}
|
|
10351
10629
|
}
|
|
@@ -10353,7 +10631,7 @@ async function discoverAgentFiles(root) {
|
|
|
10353
10631
|
}
|
|
10354
10632
|
async function discoverMerlleState(root) {
|
|
10355
10633
|
const p = path5.join(root, ".pourkit", "state.json");
|
|
10356
|
-
return
|
|
10634
|
+
return existsSync16(p) ? p : null;
|
|
10357
10635
|
}
|
|
10358
10636
|
async function discoverAgentSkills(root) {
|
|
10359
10637
|
const dirs = [
|
|
@@ -10362,7 +10640,7 @@ async function discoverAgentSkills(root) {
|
|
|
10362
10640
|
];
|
|
10363
10641
|
const found = [];
|
|
10364
10642
|
for (const d of dirs) {
|
|
10365
|
-
if (
|
|
10643
|
+
if (existsSync16(d)) {
|
|
10366
10644
|
found.push(d);
|
|
10367
10645
|
}
|
|
10368
10646
|
}
|
|
@@ -10373,7 +10651,7 @@ async function discoverOpenCodeAgentFiles(sourceRoot) {
|
|
|
10373
10651
|
resolveSourceAssetPath(sourceRoot, ".opencode", "agents"),
|
|
10374
10652
|
path5.join(sourceRoot, "..", ".opencode", "agents")
|
|
10375
10653
|
].find(
|
|
10376
|
-
(candidate) =>
|
|
10654
|
+
(candidate) => existsSync16(candidate) && statSync(candidate).isDirectory()
|
|
10377
10655
|
);
|
|
10378
10656
|
if (!agentsDir) return [];
|
|
10379
10657
|
const entries = await readdir(agentsDir, { withFileTypes: true });
|
|
@@ -10383,12 +10661,12 @@ async function discoverRootDomainDocs(root) {
|
|
|
10383
10661
|
const docs = [];
|
|
10384
10662
|
for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
|
|
10385
10663
|
const p = path5.join(root, name);
|
|
10386
|
-
if (
|
|
10664
|
+
if (existsSync16(p)) {
|
|
10387
10665
|
docs.push(p);
|
|
10388
10666
|
}
|
|
10389
10667
|
}
|
|
10390
10668
|
const adrDir = path5.join(root, "docs", "adr");
|
|
10391
|
-
if (
|
|
10669
|
+
if (existsSync16(adrDir)) {
|
|
10392
10670
|
const entries = await readdir(adrDir, { withFileTypes: true });
|
|
10393
10671
|
for (const entry of entries) {
|
|
10394
10672
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -10530,7 +10808,7 @@ async function planInit(options) {
|
|
|
10530
10808
|
for (const file of skillFiles) {
|
|
10531
10809
|
const relPath = path5.relative(s, file);
|
|
10532
10810
|
const destPath = path5.join(targetRoot, ".agents", "skills", relPath);
|
|
10533
|
-
if (!
|
|
10811
|
+
if (!existsSync16(destPath)) {
|
|
10534
10812
|
operations.push({
|
|
10535
10813
|
kind: "copy",
|
|
10536
10814
|
sourcePath: file,
|
|
@@ -10593,7 +10871,7 @@ async function planInit(options) {
|
|
|
10593
10871
|
});
|
|
10594
10872
|
}
|
|
10595
10873
|
if (sourceRoot) {
|
|
10596
|
-
if (!
|
|
10874
|
+
if (!existsSync16(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
|
|
10597
10875
|
warnings.push(
|
|
10598
10876
|
`--from-local path does not exist or is not a directory: ${sourceRoot}`
|
|
10599
10877
|
);
|
|
@@ -10658,9 +10936,9 @@ async function planInit(options) {
|
|
|
10658
10936
|
"release",
|
|
10659
10937
|
"skills"
|
|
10660
10938
|
);
|
|
10661
|
-
const srcSkills =
|
|
10939
|
+
const srcSkills = existsSync16(packagedManagedSkills) ? [
|
|
10662
10940
|
packagedManagedSkills,
|
|
10663
|
-
...
|
|
10941
|
+
...existsSync16(packagedReleaseAddonSkills) ? [packagedReleaseAddonSkills] : []
|
|
10664
10942
|
] : await discoverAgentSkills(sourceRoot);
|
|
10665
10943
|
for (const s of srcSkills) {
|
|
10666
10944
|
const isOpenCode = s.includes(".opencode");
|
|
@@ -10683,7 +10961,7 @@ async function planInit(options) {
|
|
|
10683
10961
|
"skills",
|
|
10684
10962
|
relPath
|
|
10685
10963
|
);
|
|
10686
|
-
if (!
|
|
10964
|
+
if (!existsSync16(destPath)) {
|
|
10687
10965
|
operations.push({
|
|
10688
10966
|
kind: "copy",
|
|
10689
10967
|
sourcePath: file,
|
|
@@ -10815,7 +11093,7 @@ async function planInit(options) {
|
|
|
10815
11093
|
requiresConfirmation: false,
|
|
10816
11094
|
destructive: false
|
|
10817
11095
|
});
|
|
10818
|
-
} else if (
|
|
11096
|
+
} else if (existsSync16(destPath)) {
|
|
10819
11097
|
operations.push({
|
|
10820
11098
|
kind: "skip",
|
|
10821
11099
|
path: destPath,
|
|
@@ -10873,7 +11151,7 @@ async function planInit(options) {
|
|
|
10873
11151
|
}
|
|
10874
11152
|
}
|
|
10875
11153
|
const contextPath = path5.join(targetRoot, ".pourkit", "CONTEXT.md");
|
|
10876
|
-
if (!
|
|
11154
|
+
if (!existsSync16(contextPath) && !merleDestPaths.has(contextPath)) {
|
|
10877
11155
|
operations.push({
|
|
10878
11156
|
kind: "create",
|
|
10879
11157
|
path: contextPath,
|
|
@@ -10891,7 +11169,7 @@ async function planInit(options) {
|
|
|
10891
11169
|
"adr",
|
|
10892
11170
|
".gitkeep"
|
|
10893
11171
|
);
|
|
10894
|
-
if (!
|
|
11172
|
+
if (!existsSync16(adrGitkeep)) {
|
|
10895
11173
|
operations.push({
|
|
10896
11174
|
kind: "create",
|
|
10897
11175
|
path: adrGitkeep,
|
|
@@ -10913,7 +11191,7 @@ async function planInit(options) {
|
|
|
10913
11191
|
"docs",
|
|
10914
11192
|
"agents"
|
|
10915
11193
|
);
|
|
10916
|
-
const srcDocAgents =
|
|
11194
|
+
const srcDocAgents = existsSync16(packagedManagedDocAgents) ? packagedManagedDocAgents : legacyDocAgents;
|
|
10917
11195
|
const tgtManagedDocAgents = path5.join(
|
|
10918
11196
|
targetRoot,
|
|
10919
11197
|
".pourkit",
|
|
@@ -10927,12 +11205,12 @@ async function planInit(options) {
|
|
|
10927
11205
|
"docs",
|
|
10928
11206
|
"agents"
|
|
10929
11207
|
);
|
|
10930
|
-
if (
|
|
11208
|
+
if (existsSync16(srcDocAgents)) {
|
|
10931
11209
|
const docFiles = await walkDir(srcDocAgents);
|
|
10932
11210
|
for (const file of docFiles) {
|
|
10933
11211
|
const relPath = path5.relative(srcDocAgents, file);
|
|
10934
11212
|
const managedDocDest = path5.join(tgtManagedDocAgents, relPath);
|
|
10935
|
-
if (!
|
|
11213
|
+
if (!existsSync16(managedDocDest) && relPath === "triage-labels.md") {
|
|
10936
11214
|
operations.push({
|
|
10937
11215
|
kind: "create",
|
|
10938
11216
|
path: managedDocDest,
|
|
@@ -10944,7 +11222,7 @@ async function planInit(options) {
|
|
|
10944
11222
|
options.labels ?? DEFAULT_RUNNER_LABELS
|
|
10945
11223
|
)
|
|
10946
11224
|
});
|
|
10947
|
-
} else if (!
|
|
11225
|
+
} else if (!existsSync16(managedDocDest)) {
|
|
10948
11226
|
const checksum = await computeFileChecksum(file);
|
|
10949
11227
|
operations.push({
|
|
10950
11228
|
kind: "copy",
|
|
@@ -10966,7 +11244,7 @@ async function planInit(options) {
|
|
|
10966
11244
|
relPath
|
|
10967
11245
|
);
|
|
10968
11246
|
const stubDocDest = path5.join(tgtStubDocAgents, relPath);
|
|
10969
|
-
if (
|
|
11247
|
+
if (existsSync16(stubDocDest)) continue;
|
|
10970
11248
|
operations.push({
|
|
10971
11249
|
kind: "create",
|
|
10972
11250
|
path: stubDocDest,
|
|
@@ -11000,7 +11278,7 @@ Do not edit this file.
|
|
|
11000
11278
|
"docs",
|
|
11001
11279
|
"agents"
|
|
11002
11280
|
);
|
|
11003
|
-
if (
|
|
11281
|
+
if (existsSync16(packagedReleaseAddonDocs)) {
|
|
11004
11282
|
const docFiles = await walkDir(packagedReleaseAddonDocs);
|
|
11005
11283
|
for (const file of docFiles) {
|
|
11006
11284
|
const relPath = path5.relative(packagedReleaseAddonDocs, file);
|
|
@@ -11024,19 +11302,19 @@ Do not edit this file.
|
|
|
11024
11302
|
"prompts"
|
|
11025
11303
|
);
|
|
11026
11304
|
const legacyPrompts = path5.join(sourceRoot, ".pourkit", "prompts");
|
|
11027
|
-
const srcPrompts =
|
|
11305
|
+
const srcPrompts = existsSync16(packagedManagedPrompts) ? packagedManagedPrompts : legacyPrompts;
|
|
11028
11306
|
const tgtManagedPrompts = path5.join(
|
|
11029
11307
|
targetRoot,
|
|
11030
11308
|
".pourkit",
|
|
11031
11309
|
"managed",
|
|
11032
11310
|
"prompts"
|
|
11033
11311
|
);
|
|
11034
|
-
if (
|
|
11312
|
+
if (existsSync16(srcPrompts)) {
|
|
11035
11313
|
const promptFiles = await walkDir(srcPrompts);
|
|
11036
11314
|
for (const file of promptFiles) {
|
|
11037
11315
|
const relPath = path5.relative(srcPrompts, file);
|
|
11038
11316
|
const promptDest = path5.join(tgtManagedPrompts, relPath);
|
|
11039
|
-
if (
|
|
11317
|
+
if (existsSync16(promptDest)) continue;
|
|
11040
11318
|
const checksum = await computeFileChecksum(file);
|
|
11041
11319
|
operations.push({
|
|
11042
11320
|
kind: "copy",
|
|
@@ -11060,7 +11338,7 @@ Do not edit this file.
|
|
|
11060
11338
|
".sandcastle",
|
|
11061
11339
|
"Dockerfile"
|
|
11062
11340
|
);
|
|
11063
|
-
if (
|
|
11341
|
+
if (existsSync16(tgtSandboxDockerfile)) {
|
|
11064
11342
|
operations.push({
|
|
11065
11343
|
kind: "skip",
|
|
11066
11344
|
path: tgtSandboxDockerfile,
|
|
@@ -11069,7 +11347,7 @@ Do not edit this file.
|
|
|
11069
11347
|
requiresConfirmation: false,
|
|
11070
11348
|
destructive: false
|
|
11071
11349
|
});
|
|
11072
|
-
} else if (
|
|
11350
|
+
} else if (existsSync16(srcSandboxDockerfile)) {
|
|
11073
11351
|
const checksum = await computeFileChecksum(srcSandboxDockerfile);
|
|
11074
11352
|
operations.push({
|
|
11075
11353
|
kind: "copy",
|
|
@@ -11083,7 +11361,7 @@ Do not edit this file.
|
|
|
11083
11361
|
});
|
|
11084
11362
|
}
|
|
11085
11363
|
const configJsonPath = path5.join(targetRoot, ".pourkit", "config.json");
|
|
11086
|
-
if (!
|
|
11364
|
+
if (!existsSync16(configJsonPath)) {
|
|
11087
11365
|
const verifyCommands = inferVerificationCommands(
|
|
11088
11366
|
packageScripts,
|
|
11089
11367
|
pm || "npm"
|
|
@@ -11127,9 +11405,9 @@ Do not edit this file.
|
|
|
11127
11405
|
"schema",
|
|
11128
11406
|
"pourkit.schema.json"
|
|
11129
11407
|
);
|
|
11130
|
-
if (
|
|
11408
|
+
if (existsSync16(srcSchemaJson)) {
|
|
11131
11409
|
const checksum = await computeFileChecksum(srcSchemaJson);
|
|
11132
|
-
if (!
|
|
11410
|
+
if (!existsSync16(schemaJsonPath)) {
|
|
11133
11411
|
operations.push({
|
|
11134
11412
|
kind: "copy",
|
|
11135
11413
|
sourcePath: srcSchemaJson,
|
|
@@ -11162,9 +11440,9 @@ Do not edit this file.
|
|
|
11162
11440
|
"schema",
|
|
11163
11441
|
"pourkit.schema.hash"
|
|
11164
11442
|
);
|
|
11165
|
-
if (
|
|
11443
|
+
if (existsSync16(srcSchemaHash)) {
|
|
11166
11444
|
const checksum = await computeFileChecksum(srcSchemaHash);
|
|
11167
|
-
if (!
|
|
11445
|
+
if (!existsSync16(schemaHashPath)) {
|
|
11168
11446
|
operations.push({
|
|
11169
11447
|
kind: "copy",
|
|
11170
11448
|
sourcePath: srcSchemaHash,
|
|
@@ -11190,7 +11468,7 @@ Do not edit this file.
|
|
|
11190
11468
|
const hasExistingAgents = operations.some(
|
|
11191
11469
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
|
|
11192
11470
|
);
|
|
11193
|
-
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !
|
|
11471
|
+
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync16(path5.join(targetRoot, "AGENTS.md"))) {
|
|
11194
11472
|
operations.push({
|
|
11195
11473
|
kind: "create",
|
|
11196
11474
|
path: path5.join(targetRoot, "AGENTS.md"),
|
|
@@ -11206,7 +11484,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
11206
11484
|
const hasExistingClaude = operations.some(
|
|
11207
11485
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
|
|
11208
11486
|
);
|
|
11209
|
-
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !
|
|
11487
|
+
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync16(path5.join(targetRoot, "CLAUDE.md"))) {
|
|
11210
11488
|
operations.push({
|
|
11211
11489
|
kind: "create",
|
|
11212
11490
|
path: path5.join(targetRoot, "CLAUDE.md"),
|
|
@@ -11221,7 +11499,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
11221
11499
|
}
|
|
11222
11500
|
const gitignoreTarget = path5.join(targetRoot, ".gitignore");
|
|
11223
11501
|
const gitignoreContent = generateGitignoreBlock();
|
|
11224
|
-
if (!
|
|
11502
|
+
if (!existsSync16(gitignoreTarget)) {
|
|
11225
11503
|
operations.push({
|
|
11226
11504
|
kind: "create",
|
|
11227
11505
|
path: gitignoreTarget,
|
|
@@ -11252,7 +11530,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
11252
11530
|
"agents",
|
|
11253
11531
|
path5.basename(agentFile)
|
|
11254
11532
|
);
|
|
11255
|
-
if (!
|
|
11533
|
+
if (!existsSync16(targetPath)) {
|
|
11256
11534
|
operations.push({
|
|
11257
11535
|
kind: "copy",
|
|
11258
11536
|
sourcePath: agentFile,
|
|
@@ -11275,7 +11553,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
11275
11553
|
}
|
|
11276
11554
|
}
|
|
11277
11555
|
const manifestPath = path5.join(targetRoot, ".pourkit", "manifest.json");
|
|
11278
|
-
if (
|
|
11556
|
+
if (existsSync16(manifestPath)) {
|
|
11279
11557
|
operations.push({
|
|
11280
11558
|
kind: "skip",
|
|
11281
11559
|
path: manifestPath,
|
|
@@ -11665,7 +11943,7 @@ async function updateManagedBlock(filePath, content) {
|
|
|
11665
11943
|
const blockContent = `${MANAGED_BLOCK_BEGIN}
|
|
11666
11944
|
${content}${MANAGED_BLOCK_END}
|
|
11667
11945
|
`;
|
|
11668
|
-
if (!
|
|
11946
|
+
if (!existsSync16(filePath)) {
|
|
11669
11947
|
const dir = path5.dirname(filePath);
|
|
11670
11948
|
await mkdir4(dir, { recursive: true });
|
|
11671
11949
|
await writeFileAtomic(filePath, blockContent);
|
|
@@ -11694,7 +11972,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
|
11694
11972
|
if (op.requiresConfirmation) continue;
|
|
11695
11973
|
const relPath = path5.relative(plan.targetRoot, op.path);
|
|
11696
11974
|
if (relPath === ".pourkit/manifest.json") continue;
|
|
11697
|
-
if (
|
|
11975
|
+
if (existsSync16(op.path)) {
|
|
11698
11976
|
const sha256 = await computeFileChecksum(op.path);
|
|
11699
11977
|
assets[relPath] = {
|
|
11700
11978
|
ownership: op.ownership || "managed",
|
|
@@ -11740,7 +12018,7 @@ async function applyInitPlan(plan, options) {
|
|
|
11740
12018
|
skipped++;
|
|
11741
12019
|
continue;
|
|
11742
12020
|
}
|
|
11743
|
-
if (
|
|
12021
|
+
if (existsSync16(op.path) && !op.destructive) {
|
|
11744
12022
|
skipped++;
|
|
11745
12023
|
continue;
|
|
11746
12024
|
}
|
|
@@ -11755,7 +12033,7 @@ async function applyInitPlan(plan, options) {
|
|
|
11755
12033
|
skipped++;
|
|
11756
12034
|
continue;
|
|
11757
12035
|
}
|
|
11758
|
-
if (
|
|
12036
|
+
if (existsSync16(op.path)) {
|
|
11759
12037
|
skipped++;
|
|
11760
12038
|
continue;
|
|
11761
12039
|
}
|
|
@@ -11784,7 +12062,7 @@ async function applyInitPlan(plan, options) {
|
|
|
11784
12062
|
skipped++;
|
|
11785
12063
|
continue;
|
|
11786
12064
|
}
|
|
11787
|
-
if (
|
|
12065
|
+
if (existsSync16(op.path)) {
|
|
11788
12066
|
skipped++;
|
|
11789
12067
|
continue;
|
|
11790
12068
|
}
|
|
@@ -11928,7 +12206,7 @@ async function applyInitFromSource(options) {
|
|
|
11928
12206
|
if (!manifestSkipped) {
|
|
11929
12207
|
const agentFiles = [];
|
|
11930
12208
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
11931
|
-
if (
|
|
12209
|
+
if (existsSync16(path5.join(targetRoot, name))) {
|
|
11932
12210
|
agentFiles.push(path5.join(targetRoot, name));
|
|
11933
12211
|
}
|
|
11934
12212
|
}
|
|
@@ -12104,18 +12382,18 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
|
|
|
12104
12382
|
}
|
|
12105
12383
|
|
|
12106
12384
|
// commands/memory-init.ts
|
|
12107
|
-
import { existsSync as
|
|
12385
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync5 } from "fs";
|
|
12108
12386
|
import { join as join18 } from "path";
|
|
12109
12387
|
async function runMemoryInitCommand(options) {
|
|
12110
12388
|
const cwd = options.cwd ?? process.cwd();
|
|
12111
12389
|
const configPath = join18(cwd, ".pourkit", "config.json");
|
|
12112
|
-
if (!
|
|
12390
|
+
if (!existsSync17(configPath)) {
|
|
12113
12391
|
return {
|
|
12114
12392
|
ok: false,
|
|
12115
12393
|
message: "No .pourkit/config.json found. Run `pourkit init` first to initialize Pourkit, then run `pourkit memory init`."
|
|
12116
12394
|
};
|
|
12117
12395
|
}
|
|
12118
|
-
const raw = JSON.parse(
|
|
12396
|
+
const raw = JSON.parse(readFileSync18(configPath, "utf8"));
|
|
12119
12397
|
let alreadyEnabled = false;
|
|
12120
12398
|
if (Object.prototype.hasOwnProperty.call(raw, "memory")) {
|
|
12121
12399
|
const m = raw.memory;
|
|
@@ -12150,7 +12428,7 @@ async function runMemoryInitCommand(options) {
|
|
|
12150
12428
|
let updatedAgentFile = false;
|
|
12151
12429
|
for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
|
|
12152
12430
|
const agentPath = join18(cwd, fileName);
|
|
12153
|
-
if (
|
|
12431
|
+
if (existsSync17(agentPath)) {
|
|
12154
12432
|
updateManagedBlockSync(agentPath, managed);
|
|
12155
12433
|
updatedAgentFile = true;
|
|
12156
12434
|
}
|
|
@@ -12168,11 +12446,11 @@ function updateManagedBlockSync(filePath, content) {
|
|
|
12168
12446
|
const blockContent = `${MANAGED_BLOCK_BEGIN}
|
|
12169
12447
|
${content}${MANAGED_BLOCK_END}
|
|
12170
12448
|
`;
|
|
12171
|
-
if (!
|
|
12449
|
+
if (!existsSync17(filePath)) {
|
|
12172
12450
|
writeFileSync5(filePath, blockContent);
|
|
12173
12451
|
return;
|
|
12174
12452
|
}
|
|
12175
|
-
const existing =
|
|
12453
|
+
const existing = readFileSync18(filePath, "utf8");
|
|
12176
12454
|
const beginIdx = existing.indexOf(MANAGED_BLOCK_BEGIN);
|
|
12177
12455
|
const endIdx = existing.indexOf(MANAGED_BLOCK_END);
|
|
12178
12456
|
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
|
|
@@ -12301,7 +12579,7 @@ async function runSerenaStatusCommand(options) {
|
|
|
12301
12579
|
}
|
|
12302
12580
|
|
|
12303
12581
|
// commands/config-schema.ts
|
|
12304
|
-
import { readFileSync as
|
|
12582
|
+
import { readFileSync as readFileSync19, existsSync as existsSync18 } from "fs";
|
|
12305
12583
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile2, readdir as readdir2 } from "fs/promises";
|
|
12306
12584
|
import { resolve as resolve3, dirname as dirname5, relative as relative2 } from "path";
|
|
12307
12585
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -12314,7 +12592,7 @@ function resolvePackagedAssetPath2(fileName) {
|
|
|
12314
12592
|
resolve3(__dirname3, "schema", fileName),
|
|
12315
12593
|
resolve3(__dirname3, "../schema", fileName)
|
|
12316
12594
|
];
|
|
12317
|
-
return candidates.find((candidate) =>
|
|
12595
|
+
return candidates.find((candidate) => existsSync18(candidate)) ?? candidates[0];
|
|
12318
12596
|
}
|
|
12319
12597
|
var PACKAGED_SCHEMA_PATH = resolvePackagedAssetPath2("pourkit.schema.json");
|
|
12320
12598
|
var PACKAGED_HASH_PATH = resolvePackagedAssetPath2("pourkit.schema.hash");
|
|
@@ -12322,7 +12600,7 @@ var _schemaValidator = null;
|
|
|
12322
12600
|
var _schemaErrors = null;
|
|
12323
12601
|
function getSchemaValidator() {
|
|
12324
12602
|
if (!_schemaValidator) {
|
|
12325
|
-
const schema = JSON.parse(
|
|
12603
|
+
const schema = JSON.parse(readFileSync19(PACKAGED_SCHEMA_PATH, "utf-8"));
|
|
12326
12604
|
const ajv = new Ajv2({ strict: true });
|
|
12327
12605
|
ajv.addKeyword("x-pourkit-schema-version");
|
|
12328
12606
|
const validate = ajv.compile(schema);
|
|
@@ -12336,7 +12614,7 @@ function getSchemaValidator() {
|
|
|
12336
12614
|
return _schemaValidator;
|
|
12337
12615
|
}
|
|
12338
12616
|
function readPackagedHash() {
|
|
12339
|
-
return
|
|
12617
|
+
return readFileSync19(PACKAGED_HASH_PATH, "utf-8");
|
|
12340
12618
|
}
|
|
12341
12619
|
function localSchemaDir(repoRoot2) {
|
|
12342
12620
|
return resolve3(repoRoot2, ".pourkit/schema");
|
|
@@ -12457,7 +12735,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12457
12735
|
const baselineSkills = getBaselineManagedSkillNames();
|
|
12458
12736
|
for (const docName of catalog.docs) {
|
|
12459
12737
|
const localPath = resolve3(cwd, `.pourkit/managed/docs/agents/${docName}`);
|
|
12460
|
-
if (!
|
|
12738
|
+
if (!existsSync18(localPath)) {
|
|
12461
12739
|
failures.push({
|
|
12462
12740
|
severity: "failure",
|
|
12463
12741
|
kind: "missing_managed_asset",
|
|
@@ -12475,7 +12753,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12475
12753
|
cwd,
|
|
12476
12754
|
`.pourkit/overrides/docs/agents/${docName}`
|
|
12477
12755
|
);
|
|
12478
|
-
if (
|
|
12756
|
+
if (existsSync18(overridePath)) {
|
|
12479
12757
|
warnings.push({
|
|
12480
12758
|
severity: "warning",
|
|
12481
12759
|
kind: "overridden",
|
|
@@ -12498,7 +12776,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12498
12776
|
}
|
|
12499
12777
|
for (const skillName of baselineSkills) {
|
|
12500
12778
|
const skillDir = resolve3(cwd, `.pourkit/managed/skills/${skillName}`);
|
|
12501
|
-
if (!
|
|
12779
|
+
if (!existsSync18(skillDir)) {
|
|
12502
12780
|
failures.push({
|
|
12503
12781
|
severity: "failure",
|
|
12504
12782
|
kind: "missing_managed_asset",
|
|
@@ -12507,7 +12785,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12507
12785
|
});
|
|
12508
12786
|
} else {
|
|
12509
12787
|
const skillSourcePath = resolve3(skillDir, "SKILL.md");
|
|
12510
|
-
if (
|
|
12788
|
+
if (existsSync18(skillSourcePath)) {
|
|
12511
12789
|
const packagedSkillPath = resolvePackagedManagedPath(
|
|
12512
12790
|
"skills",
|
|
12513
12791
|
skillName,
|
|
@@ -12522,7 +12800,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12522
12800
|
cwd,
|
|
12523
12801
|
`.pourkit/overrides/skills/${skillName}/SKILL.md`
|
|
12524
12802
|
);
|
|
12525
|
-
if (
|
|
12803
|
+
if (existsSync18(overridePath)) {
|
|
12526
12804
|
warnings.push({
|
|
12527
12805
|
severity: "warning",
|
|
12528
12806
|
kind: "overridden",
|
|
@@ -12546,7 +12824,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12546
12824
|
}
|
|
12547
12825
|
for (const promptName of catalog.prompts) {
|
|
12548
12826
|
const promptPath = resolve3(cwd, `.pourkit/managed/prompts/${promptName}`);
|
|
12549
|
-
if (!
|
|
12827
|
+
if (!existsSync18(promptPath)) {
|
|
12550
12828
|
failures.push({
|
|
12551
12829
|
severity: "failure",
|
|
12552
12830
|
kind: "missing_managed_asset",
|
|
@@ -12557,7 +12835,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12557
12835
|
}
|
|
12558
12836
|
for (const skillName of baselineSkills) {
|
|
12559
12837
|
const projectionDir = resolve3(cwd, `.agents/skills/${skillName}`);
|
|
12560
|
-
if (!
|
|
12838
|
+
if (!existsSync18(projectionDir)) {
|
|
12561
12839
|
warnings.push({
|
|
12562
12840
|
severity: "warning",
|
|
12563
12841
|
kind: "projection_stale",
|
|
@@ -12566,7 +12844,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12566
12844
|
});
|
|
12567
12845
|
} else {
|
|
12568
12846
|
const projectionPath = resolve3(projectionDir, "SKILL.md");
|
|
12569
|
-
if (
|
|
12847
|
+
if (existsSync18(projectionPath)) {
|
|
12570
12848
|
const managedSourcePath = resolvePackagedManagedPath(
|
|
12571
12849
|
"skills",
|
|
12572
12850
|
skillName,
|
|
@@ -12595,7 +12873,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12595
12873
|
}
|
|
12596
12874
|
}
|
|
12597
12875
|
const agentsPath = resolve3(cwd, "AGENTS.md");
|
|
12598
|
-
if (
|
|
12876
|
+
if (existsSync18(agentsPath)) {
|
|
12599
12877
|
try {
|
|
12600
12878
|
const agentsContent = await readFile5(agentsPath, "utf-8");
|
|
12601
12879
|
if (agentsContent.includes(OLD_DOCS_PATH_PATTERN)) {
|
|
@@ -12618,7 +12896,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12618
12896
|
}
|
|
12619
12897
|
}
|
|
12620
12898
|
const overridesDir = resolve3(cwd, ".pourkit/overrides");
|
|
12621
|
-
if (
|
|
12899
|
+
if (existsSync18(overridesDir)) {
|
|
12622
12900
|
const overrideFiles = await walkDir2(overridesDir);
|
|
12623
12901
|
for (const file of overrideFiles) {
|
|
12624
12902
|
const relPath = relative2(cwd, file);
|
|
@@ -12633,7 +12911,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12633
12911
|
}
|
|
12634
12912
|
}
|
|
12635
12913
|
const configPath = resolve3(cwd, ".pourkit/config.json");
|
|
12636
|
-
if (
|
|
12914
|
+
if (existsSync18(configPath)) {
|
|
12637
12915
|
try {
|
|
12638
12916
|
const configContent = await readFile5(configPath, "utf-8");
|
|
12639
12917
|
const parsed = JSON.parse(configContent);
|
|
@@ -12644,7 +12922,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12644
12922
|
cwd,
|
|
12645
12923
|
`.pourkit/managed/addons/release/skills/${skillName}`
|
|
12646
12924
|
);
|
|
12647
|
-
if (!
|
|
12925
|
+
if (!existsSync18(addonDir)) {
|
|
12648
12926
|
failures.push({
|
|
12649
12927
|
severity: "failure",
|
|
12650
12928
|
kind: "release_config_conflict",
|
|
@@ -12658,7 +12936,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12658
12936
|
cwd,
|
|
12659
12937
|
`.pourkit/managed/addons/release/docs/agents/${docName}`
|
|
12660
12938
|
);
|
|
12661
|
-
if (!
|
|
12939
|
+
if (!existsSync18(addonDocPath)) {
|
|
12662
12940
|
failures.push({
|
|
12663
12941
|
severity: "failure",
|
|
12664
12942
|
kind: "release_config_conflict",
|
|
@@ -12703,7 +12981,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12703
12981
|
cwd,
|
|
12704
12982
|
`.pourkit/managed/addons/release/skills/${skillName}`
|
|
12705
12983
|
);
|
|
12706
|
-
if (
|
|
12984
|
+
if (existsSync18(addonDir)) {
|
|
12707
12985
|
failures.push({
|
|
12708
12986
|
severity: "failure",
|
|
12709
12987
|
kind: "release_config_conflict",
|
|
@@ -12717,7 +12995,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12717
12995
|
cwd,
|
|
12718
12996
|
`.pourkit/managed/addons/release/docs/agents/${docName}`
|
|
12719
12997
|
);
|
|
12720
|
-
if (
|
|
12998
|
+
if (existsSync18(addonDocPath)) {
|
|
12721
12999
|
failures.push({
|
|
12722
13000
|
severity: "failure",
|
|
12723
13001
|
kind: "release_config_conflict",
|
|
@@ -12731,7 +13009,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12731
13009
|
}
|
|
12732
13010
|
}
|
|
12733
13011
|
const manifestPath = resolve3(cwd, ".pourkit/manifest.json");
|
|
12734
|
-
if (
|
|
13012
|
+
if (existsSync18(manifestPath)) {
|
|
12735
13013
|
try {
|
|
12736
13014
|
const manifestContent = await readFile5(manifestPath, "utf-8");
|
|
12737
13015
|
const manifest = JSON.parse(manifestContent);
|
|
@@ -12754,7 +13032,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12754
13032
|
}
|
|
12755
13033
|
}
|
|
12756
13034
|
const pathEscapeOverrideDir = resolve3(cwd, ".pourkit/overrides");
|
|
12757
|
-
if (
|
|
13035
|
+
if (existsSync18(pathEscapeOverrideDir)) {
|
|
12758
13036
|
const overrideFiles = await walkDir2(pathEscapeOverrideDir);
|
|
12759
13037
|
for (const file of overrideFiles) {
|
|
12760
13038
|
if (!isPathContained(cwd, file)) {
|
|
@@ -12768,7 +13046,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12768
13046
|
}
|
|
12769
13047
|
}
|
|
12770
13048
|
const managedDocsDir = resolve3(cwd, ".pourkit/managed/docs/agents");
|
|
12771
|
-
if (
|
|
13049
|
+
if (existsSync18(managedDocsDir)) {
|
|
12772
13050
|
const docFiles = await walkDir2(managedDocsDir);
|
|
12773
13051
|
for (const file of docFiles) {
|
|
12774
13052
|
if (!file.endsWith(".md")) continue;
|
|
@@ -12789,12 +13067,12 @@ async function validateWorkflowPack(cwd) {
|
|
|
12789
13067
|
}
|
|
12790
13068
|
}
|
|
12791
13069
|
const managedSkillsDir = resolve3(cwd, ".pourkit/managed/skills");
|
|
12792
|
-
if (
|
|
13070
|
+
if (existsSync18(managedSkillsDir)) {
|
|
12793
13071
|
const skillDirs = await readdir2(managedSkillsDir, { withFileTypes: true });
|
|
12794
13072
|
for (const entry of skillDirs) {
|
|
12795
13073
|
if (!entry.isDirectory()) continue;
|
|
12796
13074
|
const skillFile = resolve3(managedSkillsDir, entry.name, "SKILL.md");
|
|
12797
|
-
if (!
|
|
13075
|
+
if (!existsSync18(skillFile)) continue;
|
|
12798
13076
|
try {
|
|
12799
13077
|
const content = await readFile5(skillFile, "utf-8");
|
|
12800
13078
|
const relPath = relative2(cwd, skillFile);
|
|
@@ -12812,10 +13090,10 @@ async function validateWorkflowPack(cwd) {
|
|
|
12812
13090
|
}
|
|
12813
13091
|
}
|
|
12814
13092
|
const managedPromptsDir = resolve3(cwd, ".pourkit/managed/prompts");
|
|
12815
|
-
if (
|
|
13093
|
+
if (existsSync18(managedPromptsDir)) {
|
|
12816
13094
|
for (const promptName of catalog.prompts) {
|
|
12817
13095
|
const promptFile = resolve3(managedPromptsDir, promptName);
|
|
12818
|
-
if (!
|
|
13096
|
+
if (!existsSync18(promptFile)) continue;
|
|
12819
13097
|
try {
|
|
12820
13098
|
const content = await readFile5(promptFile, "utf-8");
|
|
12821
13099
|
const relPath = relative2(cwd, promptFile);
|
|
@@ -12837,7 +13115,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12837
13115
|
cwd,
|
|
12838
13116
|
`.pourkit/managed/addons/release/skills/${skillName}/SKILL.md`
|
|
12839
13117
|
);
|
|
12840
|
-
if (!
|
|
13118
|
+
if (!existsSync18(addonSkillFile)) continue;
|
|
12841
13119
|
try {
|
|
12842
13120
|
const content = await readFile5(addonSkillFile, "utf-8");
|
|
12843
13121
|
const relPath = relative2(cwd, addonSkillFile);
|
|
@@ -12858,7 +13136,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
12858
13136
|
cwd,
|
|
12859
13137
|
`.pourkit/managed/addons/release/docs/agents/${docName}`
|
|
12860
13138
|
);
|
|
12861
|
-
if (!
|
|
13139
|
+
if (!existsSync18(addonDocFile)) continue;
|
|
12862
13140
|
try {
|
|
12863
13141
|
const content = await readFile5(addonDocFile, "utf-8");
|
|
12864
13142
|
const relPath = relative2(cwd, addonDocFile);
|
|
@@ -12892,7 +13170,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
12892
13170
|
detail: icmPath !== null ? icmPath : "icm not found on PATH"
|
|
12893
13171
|
});
|
|
12894
13172
|
const gitignorePath = resolve3(cwd, ".gitignore");
|
|
12895
|
-
const gitignoreContent =
|
|
13173
|
+
const gitignoreContent = existsSync18(gitignorePath) ? readFileSync19(gitignorePath, "utf-8") : "";
|
|
12896
13174
|
const hasGitignoreEntry = gitignoreContent.includes(".pourkit/icm/");
|
|
12897
13175
|
checks.push({
|
|
12898
13176
|
name: "icm-gitignore",
|
|
@@ -12919,8 +13197,8 @@ async function runDoctorCommand(options) {
|
|
|
12919
13197
|
const schemaDir = localSchemaDir(repoRootPath);
|
|
12920
13198
|
const localSchemaPath = resolve3(schemaDir, "pourkit.schema.json");
|
|
12921
13199
|
const localHashPath = resolve3(schemaDir, "pourkit.schema.hash");
|
|
12922
|
-
const localSchemaExists =
|
|
12923
|
-
const localHashExists =
|
|
13200
|
+
const localSchemaExists = existsSync18(localSchemaPath);
|
|
13201
|
+
const localHashExists = existsSync18(localHashPath);
|
|
12924
13202
|
let packagedHash = null;
|
|
12925
13203
|
try {
|
|
12926
13204
|
packagedHash = readPackagedHash();
|
|
@@ -12939,9 +13217,9 @@ async function runDoctorCommand(options) {
|
|
|
12939
13217
|
const configPath = resolve3(repoRootPath, ".pourkit/config.json");
|
|
12940
13218
|
let configValidation;
|
|
12941
13219
|
let rawMemoryConfig;
|
|
12942
|
-
if (
|
|
13220
|
+
if (existsSync18(configPath)) {
|
|
12943
13221
|
try {
|
|
12944
|
-
const raw = JSON.parse(
|
|
13222
|
+
const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
12945
13223
|
rawMemoryConfig = raw.memory;
|
|
12946
13224
|
const validate = getSchemaValidator();
|
|
12947
13225
|
const valid2 = validate(raw);
|
|
@@ -12974,7 +13252,7 @@ async function runDoctorCommand(options) {
|
|
|
12974
13252
|
"pourkit.config.mjs",
|
|
12975
13253
|
"pourkit.config.js",
|
|
12976
13254
|
"pourkit.json"
|
|
12977
|
-
].filter((p) =>
|
|
13255
|
+
].filter((p) => existsSync18(resolve3(repoRootPath, p)));
|
|
12978
13256
|
const workflowPack = await validateWorkflowPack(repoRootPath);
|
|
12979
13257
|
const memory = await validateMemoryHealth(repoRootPath, rawMemoryConfig);
|
|
12980
13258
|
let recommendation = null;
|
|
@@ -13024,7 +13302,7 @@ async function runConfigSyncSchemaCommand(options) {
|
|
|
13024
13302
|
const localHashPath = resolve3(schemaDir, "pourkit.schema.hash");
|
|
13025
13303
|
let schemaWritten = false;
|
|
13026
13304
|
let hashWritten = false;
|
|
13027
|
-
if (
|
|
13305
|
+
if (existsSync18(localSchemaPath)) {
|
|
13028
13306
|
const existing = await readFile5(localSchemaPath, "utf-8");
|
|
13029
13307
|
if (existing !== packagedSchema) {
|
|
13030
13308
|
await writeFile2(localSchemaPath, packagedSchema, "utf-8");
|
|
@@ -13034,7 +13312,7 @@ async function runConfigSyncSchemaCommand(options) {
|
|
|
13034
13312
|
await writeFile2(localSchemaPath, packagedSchema, "utf-8");
|
|
13035
13313
|
schemaWritten = true;
|
|
13036
13314
|
}
|
|
13037
|
-
if (
|
|
13315
|
+
if (existsSync18(localHashPath)) {
|
|
13038
13316
|
const existing = await readFile5(localHashPath, "utf-8");
|
|
13039
13317
|
if (existing !== packagedHash) {
|
|
13040
13318
|
await writeFile2(localHashPath, packagedHash, "utf-8");
|
|
@@ -13052,7 +13330,7 @@ async function runConfigSyncSchemaCommand(options) {
|
|
|
13052
13330
|
}
|
|
13053
13331
|
|
|
13054
13332
|
// commands/workflow-pack-sync.ts
|
|
13055
|
-
import { existsSync as
|
|
13333
|
+
import { existsSync as existsSync19, readdirSync as readdirSync4 } from "fs";
|
|
13056
13334
|
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile3, readdir as readdir3 } from "fs/promises";
|
|
13057
13335
|
import { resolve as resolve4, dirname as dirname6, relative as relative3, normalize as normalize2 } from "path";
|
|
13058
13336
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -13175,7 +13453,7 @@ async function syncFile(cwd, targetRelativePath, sourceContent, errors) {
|
|
|
13175
13453
|
return false;
|
|
13176
13454
|
}
|
|
13177
13455
|
await mkdir6(resolve4(targetPath, ".."), { recursive: true });
|
|
13178
|
-
if (
|
|
13456
|
+
if (existsSync19(targetPath)) {
|
|
13179
13457
|
const existing = await readFile6(targetPath, "utf-8");
|
|
13180
13458
|
if (existing === sourceContent) {
|
|
13181
13459
|
return false;
|
|
@@ -13202,7 +13480,7 @@ function walkDirSync(dir) {
|
|
|
13202
13480
|
}
|
|
13203
13481
|
async function detectOverrides(cwd) {
|
|
13204
13482
|
const overridesDir = resolve4(cwd, OVERRIDES_DIR);
|
|
13205
|
-
if (!
|
|
13483
|
+
if (!existsSync19(overridesDir)) {
|
|
13206
13484
|
return [];
|
|
13207
13485
|
}
|
|
13208
13486
|
const files = await walkDir3(overridesDir);
|
|
@@ -13212,13 +13490,13 @@ async function detectProjectOwnedFiles(cwd) {
|
|
|
13212
13490
|
const found = [];
|
|
13213
13491
|
for (const ownedPath of PROJECT_OWNED_PATHS) {
|
|
13214
13492
|
const fullPath = resolve4(cwd, ownedPath);
|
|
13215
|
-
if (
|
|
13493
|
+
if (existsSync19(fullPath)) {
|
|
13216
13494
|
found.push(ownedPath);
|
|
13217
13495
|
}
|
|
13218
13496
|
}
|
|
13219
13497
|
for (const ownedDir of PROJECT_OWNED_DIRECTORIES) {
|
|
13220
13498
|
const fullDir = resolve4(cwd, ownedDir);
|
|
13221
|
-
if (
|
|
13499
|
+
if (existsSync19(fullDir)) {
|
|
13222
13500
|
const files = await walkDir3(fullDir);
|
|
13223
13501
|
for (const file of files) {
|
|
13224
13502
|
found.push(relative3(cwd, file));
|
|
@@ -13370,7 +13648,7 @@ async function runWorkflowPackSyncCommand(options) {
|
|
|
13370
13648
|
const fullManagedBlock = `${MANAGED_BLOCK_BEGIN3}${managedBlockContent}${MANAGED_BLOCK_END2}`;
|
|
13371
13649
|
async function syncManagedAgentFile(fileName, options2) {
|
|
13372
13650
|
const filePath = resolve4(cwd, fileName);
|
|
13373
|
-
if (!
|
|
13651
|
+
if (!existsSync19(filePath)) {
|
|
13374
13652
|
if (!options2.createIfMissing) return false;
|
|
13375
13653
|
await writeFile3(filePath, fullManagedBlock + "\n", "utf-8");
|
|
13376
13654
|
return true;
|
|
@@ -14152,7 +14430,7 @@ function formatChecks2(checks) {
|
|
|
14152
14430
|
init_common();
|
|
14153
14431
|
|
|
14154
14432
|
// execution/sandcastle-execution.ts
|
|
14155
|
-
import { existsSync as
|
|
14433
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync9, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
14156
14434
|
import { join as join21 } from "path";
|
|
14157
14435
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
14158
14436
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
@@ -14180,18 +14458,18 @@ import path7 from "path";
|
|
|
14180
14458
|
|
|
14181
14459
|
// execution/sandbox-image.ts
|
|
14182
14460
|
import { createHash as createHash2 } from "crypto";
|
|
14183
|
-
import { existsSync as
|
|
14461
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
|
|
14184
14462
|
import path6 from "path";
|
|
14185
14463
|
function sandboxImageName(repoRoot2, installIcm) {
|
|
14186
14464
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
14187
14465
|
const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
|
|
14188
14466
|
const baseName = sanitized || "local";
|
|
14189
14467
|
const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
|
|
14190
|
-
if (!
|
|
14468
|
+
if (!existsSync20(dockerfilePath)) {
|
|
14191
14469
|
const base2 = `sandcastle:${baseName}`;
|
|
14192
14470
|
return installIcm ? `${base2}-icm` : base2;
|
|
14193
14471
|
}
|
|
14194
|
-
const fingerprint = createHash2("sha256").update(
|
|
14472
|
+
const fingerprint = createHash2("sha256").update(readFileSync20(dockerfilePath)).digest("hex").slice(0, 8);
|
|
14195
14473
|
const base = `sandcastle:${baseName}-${fingerprint}`;
|
|
14196
14474
|
return installIcm ? `${base}-icm` : base;
|
|
14197
14475
|
}
|
|
@@ -14376,7 +14654,7 @@ var SandcastleExecutionSession = class {
|
|
|
14376
14654
|
);
|
|
14377
14655
|
}
|
|
14378
14656
|
const hostMemoryDir = join21(repoRoot2, ".pourkit", "icm");
|
|
14379
|
-
if (!
|
|
14657
|
+
if (!existsSync21(hostMemoryDir) || !statSync2(hostMemoryDir).isDirectory()) {
|
|
14380
14658
|
throw new MissingHostMemoryDirError(
|
|
14381
14659
|
"Memory enabled but host .pourkit/icm/ directory is missing. Run `pourkit memory init` to create the repo-scoped memory store."
|
|
14382
14660
|
);
|
|
@@ -14541,7 +14819,7 @@ function sanitizeBranch(branchName) {
|
|
|
14541
14819
|
function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
14542
14820
|
return paths.filter((relativePath) => {
|
|
14543
14821
|
const source = join21(repoRoot2, relativePath);
|
|
14544
|
-
if (!
|
|
14822
|
+
if (!existsSync21(source)) {
|
|
14545
14823
|
return true;
|
|
14546
14824
|
}
|
|
14547
14825
|
try {
|
|
@@ -14552,7 +14830,7 @@ function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
|
14552
14830
|
return true;
|
|
14553
14831
|
}
|
|
14554
14832
|
const destination = join21(worktreePath, relativePath);
|
|
14555
|
-
return !
|
|
14833
|
+
return !existsSync21(destination);
|
|
14556
14834
|
});
|
|
14557
14835
|
}
|
|
14558
14836
|
function formatAgentStreamEvent(event) {
|
|
@@ -15342,11 +15620,11 @@ function createCliProgram(version) {
|
|
|
15342
15620
|
return program;
|
|
15343
15621
|
}
|
|
15344
15622
|
async function resolveCliVersion() {
|
|
15345
|
-
if (isPackageVersion("0.0.0-next-
|
|
15346
|
-
return "0.0.0-next-
|
|
15623
|
+
if (isPackageVersion("0.0.0-next-20260621233457")) {
|
|
15624
|
+
return "0.0.0-next-20260621233457";
|
|
15347
15625
|
}
|
|
15348
|
-
if (isReleaseVersion("0.0.0-next-
|
|
15349
|
-
return "0.0.0-next-
|
|
15626
|
+
if (isReleaseVersion("0.0.0-next-20260621233457")) {
|
|
15627
|
+
return "0.0.0-next-20260621233457";
|
|
15350
15628
|
}
|
|
15351
15629
|
try {
|
|
15352
15630
|
const root = repoRoot();
|