@pourkit/cli 0.0.0-next-20260607223610 → 0.0.0-next-20260608072322
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 +864 -347
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +423 -50
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -376,6 +376,7 @@ var VerificationCommandSchema = z.object({
|
|
|
376
376
|
command: d.command,
|
|
377
377
|
label: d.label && d.label.trim() !== "" ? d.label : void 0
|
|
378
378
|
}));
|
|
379
|
+
var PrdRunModeSchema = z.enum(["github", "local"]);
|
|
379
380
|
var QueueConfigSchema = z.object({
|
|
380
381
|
loop: z.boolean().optional()
|
|
381
382
|
}).strict();
|
|
@@ -422,6 +423,7 @@ var ReviewRefactorLoopStrategySchema = z.object({
|
|
|
422
423
|
maxAttempts: z.number().int().positive()
|
|
423
424
|
}).strict(),
|
|
424
425
|
prdRun: z.object({
|
|
426
|
+
mode: PrdRunModeSchema.optional(),
|
|
425
427
|
// Uses promptTemplate (canonical StageAgentConfig field), not prompt as Issue contract may suggest
|
|
426
428
|
finalReview: StageAgentConfigSchema,
|
|
427
429
|
reconciliation: StageAgentConfigSchema.optional()
|
|
@@ -715,6 +717,7 @@ function parseConfig(raw) {
|
|
|
715
717
|
},
|
|
716
718
|
...t.strategy.prdRun ? {
|
|
717
719
|
prdRun: {
|
|
720
|
+
...t.strategy.prdRun.mode ? { mode: t.strategy.prdRun.mode } : {},
|
|
718
721
|
finalReview: t.strategy.prdRun.finalReview,
|
|
719
722
|
...t.strategy.prdRun.reconciliation ? { reconciliation: t.strategy.prdRun.reconciliation } : {}
|
|
720
723
|
}
|
|
@@ -771,14 +774,24 @@ function assertKnownKeys(value, path9, knownKeys) {
|
|
|
771
774
|
function getVerificationCommands(target) {
|
|
772
775
|
return target.strategy.verify?.commands ?? [];
|
|
773
776
|
}
|
|
777
|
+
function resolvePrdRunMode(target, opts) {
|
|
778
|
+
if (opts?.localOverride === true) {
|
|
779
|
+
return { mode: "local", source: "cli-override" };
|
|
780
|
+
}
|
|
781
|
+
const configMode = target.strategy.prdRun?.mode;
|
|
782
|
+
if (configMode) {
|
|
783
|
+
return { mode: configMode, source: "target-config" };
|
|
784
|
+
}
|
|
785
|
+
return { mode: "github", source: "default" };
|
|
786
|
+
}
|
|
774
787
|
async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
|
|
775
|
-
const { existsSync:
|
|
788
|
+
const { existsSync: existsSync20 } = await import("fs");
|
|
776
789
|
const { mkdir: mkdir6, writeFile: writeFile4, rm } = await import("fs/promises");
|
|
777
790
|
const { join: pjoin, basename } = await import("path");
|
|
778
791
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
779
792
|
const { build } = await import("esbuild");
|
|
780
793
|
const configPath = pjoin(repoRoot2, configFileName);
|
|
781
|
-
if (!
|
|
794
|
+
if (!existsSync20(configPath)) {
|
|
782
795
|
throw new Error(
|
|
783
796
|
`No config file found at ${configPath}. Create a ${configFileName} that exports a default PourkitConfig.`
|
|
784
797
|
);
|
|
@@ -1142,6 +1155,77 @@ function prepareArtifactPath(artifactPath) {
|
|
|
1142
1155
|
rmSync(artifactPath, { recursive: true, force: true });
|
|
1143
1156
|
}
|
|
1144
1157
|
|
|
1158
|
+
// commands/run-verification.ts
|
|
1159
|
+
init_common();
|
|
1160
|
+
function buildRunVerificationCommand(target) {
|
|
1161
|
+
return `pourkit run-verification --target ${target.name}`;
|
|
1162
|
+
}
|
|
1163
|
+
async function runVerificationCommand(command, cwd, logger) {
|
|
1164
|
+
try {
|
|
1165
|
+
const result = await execCapture("bash", ["-lc", command.command], {
|
|
1166
|
+
cwd,
|
|
1167
|
+
logger,
|
|
1168
|
+
label: command.label
|
|
1169
|
+
});
|
|
1170
|
+
return {
|
|
1171
|
+
label: command.label,
|
|
1172
|
+
command: command.command,
|
|
1173
|
+
ok: true,
|
|
1174
|
+
stdout: result.stdout,
|
|
1175
|
+
stderr: result.stderr
|
|
1176
|
+
};
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
return {
|
|
1179
|
+
label: command.label,
|
|
1180
|
+
command: command.command,
|
|
1181
|
+
ok: false,
|
|
1182
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
async function runVerificationCommands(options) {
|
|
1187
|
+
const commands = getVerificationCommands(options.target);
|
|
1188
|
+
const diagnostics = [];
|
|
1189
|
+
if (commands.length === 0) {
|
|
1190
|
+
diagnostics.push(
|
|
1191
|
+
`No verification commands configured for target "${options.target.name}".`
|
|
1192
|
+
);
|
|
1193
|
+
return {
|
|
1194
|
+
ok: true,
|
|
1195
|
+
target: options.target.name,
|
|
1196
|
+
commands: [],
|
|
1197
|
+
diagnostics
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
const results = [];
|
|
1201
|
+
for (const command of commands) {
|
|
1202
|
+
const result = await runVerificationCommand(
|
|
1203
|
+
command,
|
|
1204
|
+
options.cwd,
|
|
1205
|
+
options.logger
|
|
1206
|
+
);
|
|
1207
|
+
results.push(result);
|
|
1208
|
+
if (!result.ok) {
|
|
1209
|
+
diagnostics.push(`Verification failed for "${command.label}".`);
|
|
1210
|
+
return {
|
|
1211
|
+
ok: false,
|
|
1212
|
+
target: options.target.name,
|
|
1213
|
+
commands: results,
|
|
1214
|
+
diagnostics
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
diagnostics.push(
|
|
1219
|
+
`Verification passed for target "${options.target.name}".`
|
|
1220
|
+
);
|
|
1221
|
+
return {
|
|
1222
|
+
ok: true,
|
|
1223
|
+
target: options.target.name,
|
|
1224
|
+
commands: results,
|
|
1225
|
+
diagnostics
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1145
1229
|
// shared/run-context.ts
|
|
1146
1230
|
var RUN_CONTEXT_PATH_IN_WORKTREE = ".pourkit/.tmp/run-context.md";
|
|
1147
1231
|
var ALL_RUN_CONTEXT_SECTIONS = [
|
|
@@ -1268,6 +1352,7 @@ function buildRunContextMarkdown(options) {
|
|
|
1268
1352
|
if (sections.includes("verification-commands")) {
|
|
1269
1353
|
parts.push(
|
|
1270
1354
|
...renderCommandList(
|
|
1355
|
+
target,
|
|
1271
1356
|
getVerificationCommands(target),
|
|
1272
1357
|
"Verification Commands"
|
|
1273
1358
|
)
|
|
@@ -1289,13 +1374,26 @@ function buildRunContextMarkdown(options) {
|
|
|
1289
1374
|
}
|
|
1290
1375
|
return parts.join("\n");
|
|
1291
1376
|
}
|
|
1292
|
-
function renderCommandList(commands, heading) {
|
|
1377
|
+
function renderCommandList(target, commands, heading) {
|
|
1293
1378
|
if (commands.length === 0) {
|
|
1294
|
-
return [
|
|
1379
|
+
return [
|
|
1380
|
+
`## ${heading}`,
|
|
1381
|
+
"",
|
|
1382
|
+
`Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
|
|
1383
|
+
"",
|
|
1384
|
+
"Underlying project commands:",
|
|
1385
|
+
"",
|
|
1386
|
+
"(none configured)",
|
|
1387
|
+
""
|
|
1388
|
+
];
|
|
1295
1389
|
}
|
|
1296
1390
|
return [
|
|
1297
1391
|
`## ${heading}`,
|
|
1298
1392
|
"",
|
|
1393
|
+
`Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
|
|
1394
|
+
"",
|
|
1395
|
+
"Underlying project commands:",
|
|
1396
|
+
"",
|
|
1299
1397
|
"Run these commands from the repository root exactly as written. Do not substitute equivalent scripts from nested package.json files.",
|
|
1300
1398
|
"",
|
|
1301
1399
|
...commands.map((command) => `- ${command.label}: \`${command.command}\``),
|
|
@@ -2079,7 +2177,7 @@ ${criteriaBlock}
|
|
|
2079
2177
|
|
|
2080
2178
|
Write your review to: ${artifactPathInWorktree}
|
|
2081
2179
|
|
|
2082
|
-
Before handoff, run:
|
|
2180
|
+
Before handoff, run: pourkit validate-artifact reviewer ${artifactPathInWorktree} --iteration ${iteration}${priorRefactorArtifacts ? " --prior-refactor-artifacts" : ""}
|
|
2083
2181
|
|
|
2084
2182
|
Do not provide a separate chat response. The runner only reads the file above.
|
|
2085
2183
|
|
|
@@ -2615,7 +2713,7 @@ ${latestReview.trimEnd()}
|
|
|
2615
2713
|
|
|
2616
2714
|
Write your refactor artifact to: ${artifactPathInWorktree}
|
|
2617
2715
|
|
|
2618
|
-
Before handoff, run:
|
|
2716
|
+
Before handoff, run: pourkit validate-artifact refactor ${artifactPathInWorktree} --iteration ${iteration}${findingArgs}
|
|
2619
2717
|
|
|
2620
2718
|
When you are done, finish with <promise>COMPLETE</promise>.`);
|
|
2621
2719
|
});
|
|
@@ -4879,8 +4977,8 @@ function validateLocalTriage(prdPath, issuePaths) {
|
|
|
4879
4977
|
}
|
|
4880
4978
|
|
|
4881
4979
|
// commands/issue-run.ts
|
|
4882
|
-
import { existsSync as
|
|
4883
|
-
import { join as
|
|
4980
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
|
|
4981
|
+
import { join as join14 } from "path";
|
|
4884
4982
|
|
|
4885
4983
|
// pr/templates.ts
|
|
4886
4984
|
init_common();
|
|
@@ -5581,7 +5679,7 @@ async function runFailureResolutionAgent(options) {
|
|
|
5581
5679
|
"",
|
|
5582
5680
|
"Allowed decisions: " + packet.allowedDecisions.join(", "),
|
|
5583
5681
|
"",
|
|
5584
|
-
`Before handoff, run:
|
|
5682
|
+
`Before handoff, run: pourkit validate-artifact failure-resolution ${artifactPath} ${packet.allowedDecisions.map((decision) => `--allowed-decision ${decision}`).join(" ")}`
|
|
5585
5683
|
].join("\n");
|
|
5586
5684
|
const retryResult = await executeWithMissingOrEmptyArtifactRetry({
|
|
5587
5685
|
executionProvider,
|
|
@@ -5853,7 +5951,7 @@ Rules:
|
|
|
5853
5951
|
|
|
5854
5952
|
Write your finalizer output to: ${artifactPathInWorktree}
|
|
5855
5953
|
|
|
5856
|
-
Before handoff, run:
|
|
5954
|
+
Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktree}`;
|
|
5857
5955
|
}
|
|
5858
5956
|
|
|
5859
5957
|
// commands/pr-description-agent.ts
|
|
@@ -6054,6 +6152,293 @@ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
|
6054
6152
|
});
|
|
6055
6153
|
}
|
|
6056
6154
|
|
|
6155
|
+
// prd-run/local-merge-coordinator.ts
|
|
6156
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
6157
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
6158
|
+
import { join as join13 } from "path";
|
|
6159
|
+
|
|
6160
|
+
// prd-run/local-branches.ts
|
|
6161
|
+
import { execFileSync } from "child_process";
|
|
6162
|
+
function getLocalPrdBranchName(prdId) {
|
|
6163
|
+
return `local/${prdId}`;
|
|
6164
|
+
}
|
|
6165
|
+
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
|
|
6166
|
+
var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
|
|
6167
|
+
function validateLocalBranchName(name) {
|
|
6168
|
+
if (!name || name.length === 0) {
|
|
6169
|
+
return {
|
|
6170
|
+
ok: false,
|
|
6171
|
+
failureCode: "invalid_format",
|
|
6172
|
+
message: "Branch name is empty."
|
|
6173
|
+
};
|
|
6174
|
+
}
|
|
6175
|
+
if (isProtectedBranch(name)) {
|
|
6176
|
+
return {
|
|
6177
|
+
ok: false,
|
|
6178
|
+
failureCode: "protected_branch",
|
|
6179
|
+
message: `Branch "${name}" is protected.`
|
|
6180
|
+
};
|
|
6181
|
+
}
|
|
6182
|
+
if (!LOCAL_BRANCH_PATTERN.test(name)) {
|
|
6183
|
+
return {
|
|
6184
|
+
ok: false,
|
|
6185
|
+
failureCode: "invalid_format",
|
|
6186
|
+
message: `Branch "${name}" does not match the local branch pattern.`
|
|
6187
|
+
};
|
|
6188
|
+
}
|
|
6189
|
+
return { ok: true };
|
|
6190
|
+
}
|
|
6191
|
+
function isProtectedBranch(name) {
|
|
6192
|
+
return PROTECTED_BRANCHES.has(name);
|
|
6193
|
+
}
|
|
6194
|
+
function hasRemoteBackedCollision(localName, repoRoot2) {
|
|
6195
|
+
const match = localName.match(/^local\/(PRD-\d{4})/);
|
|
6196
|
+
if (!match) {
|
|
6197
|
+
return Promise.resolve({
|
|
6198
|
+
ok: false,
|
|
6199
|
+
failureCode: "invalid_format",
|
|
6200
|
+
message: `Cannot extract PRD ref from "${localName}".`
|
|
6201
|
+
});
|
|
6202
|
+
}
|
|
6203
|
+
const prdRef = match[1];
|
|
6204
|
+
try {
|
|
6205
|
+
execFileSync(
|
|
6206
|
+
"git",
|
|
6207
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${prdRef}`],
|
|
6208
|
+
{
|
|
6209
|
+
cwd: repoRoot2 ?? process.cwd(),
|
|
6210
|
+
encoding: "utf8",
|
|
6211
|
+
stdio: "pipe"
|
|
6212
|
+
}
|
|
6213
|
+
);
|
|
6214
|
+
return Promise.resolve({
|
|
6215
|
+
ok: false,
|
|
6216
|
+
failureCode: "remote_backed_collision",
|
|
6217
|
+
message: `Branch "${prdRef}" exists locally.`
|
|
6218
|
+
});
|
|
6219
|
+
} catch {
|
|
6220
|
+
}
|
|
6221
|
+
try {
|
|
6222
|
+
execFileSync(
|
|
6223
|
+
"git",
|
|
6224
|
+
["show-ref", "--verify", "--quiet", `refs/remotes/origin/${prdRef}`],
|
|
6225
|
+
{
|
|
6226
|
+
cwd: repoRoot2 ?? process.cwd(),
|
|
6227
|
+
encoding: "utf8",
|
|
6228
|
+
stdio: "pipe"
|
|
6229
|
+
}
|
|
6230
|
+
);
|
|
6231
|
+
return Promise.resolve({
|
|
6232
|
+
ok: false,
|
|
6233
|
+
failureCode: "remote_backed_collision",
|
|
6234
|
+
message: `Remote branch "origin/${prdRef}" exists.`
|
|
6235
|
+
});
|
|
6236
|
+
} catch {
|
|
6237
|
+
}
|
|
6238
|
+
return Promise.resolve({ ok: true });
|
|
6239
|
+
}
|
|
6240
|
+
|
|
6241
|
+
// prd-run/local-merge-coordinator.ts
|
|
6242
|
+
function getLocalStorePath(repoRoot2, prdId) {
|
|
6243
|
+
return join13(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
6244
|
+
}
|
|
6245
|
+
function getMergeReceiptPath(repoRoot2, prdId, issueId) {
|
|
6246
|
+
return join13(
|
|
6247
|
+
getLocalStorePath(repoRoot2, prdId),
|
|
6248
|
+
"merge-receipts",
|
|
6249
|
+
`${issueId}.json`
|
|
6250
|
+
);
|
|
6251
|
+
}
|
|
6252
|
+
function getIssueArtifactPath(repoRoot2, prdId, issueId) {
|
|
6253
|
+
return join13(getLocalStorePath(repoRoot2, prdId), "issues", `${issueId}.json`);
|
|
6254
|
+
}
|
|
6255
|
+
function readIssueBranchName(repoRoot2, prdId, issueId) {
|
|
6256
|
+
const issuePath = getIssueArtifactPath(repoRoot2, prdId, issueId);
|
|
6257
|
+
if (!existsSync9(issuePath)) return null;
|
|
6258
|
+
try {
|
|
6259
|
+
const content = readFileSync11(issuePath, "utf-8");
|
|
6260
|
+
const parsed = JSON.parse(content);
|
|
6261
|
+
return typeof parsed.branchName === "string" && parsed.branchName ? parsed.branchName : null;
|
|
6262
|
+
} catch {
|
|
6263
|
+
return null;
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
|
|
6267
|
+
const root = repoRoot2 ?? process.cwd();
|
|
6268
|
+
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
6269
|
+
if (!existsSync9(receiptPath)) return null;
|
|
6270
|
+
try {
|
|
6271
|
+
const content = readFileSync11(receiptPath, "utf-8");
|
|
6272
|
+
const parsed = JSON.parse(content);
|
|
6273
|
+
if (typeof parsed.prdId === "string" && typeof parsed.issueId === "string" && typeof parsed.stage === "string" && typeof parsed.sourceBranch === "string" && typeof parsed.localPrdBranch === "string" && typeof parsed.mergeCommit === "string" && typeof parsed.completedAt === "string") {
|
|
6274
|
+
return parsed;
|
|
6275
|
+
}
|
|
6276
|
+
return null;
|
|
6277
|
+
} catch {
|
|
6278
|
+
return null;
|
|
6279
|
+
}
|
|
6280
|
+
}
|
|
6281
|
+
async function squashMergeLocalIssue(prdId, issueId, input, repoRoot2) {
|
|
6282
|
+
const root = repoRoot2 ?? process.cwd();
|
|
6283
|
+
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
6284
|
+
if (existsSync9(receiptPath)) {
|
|
6285
|
+
try {
|
|
6286
|
+
const existing = JSON.parse(
|
|
6287
|
+
readFileSync11(receiptPath, "utf-8")
|
|
6288
|
+
);
|
|
6289
|
+
if (existing.prdId === prdId && existing.issueId === issueId && existing.mergeCommit) {
|
|
6290
|
+
return {
|
|
6291
|
+
ok: false,
|
|
6292
|
+
failureCode: "already_merged",
|
|
6293
|
+
repairGuidance: `Issue ${issueId} was already merged into ${existing.localPrdBranch}. Merge commit: ${existing.mergeCommit}`
|
|
6294
|
+
};
|
|
6295
|
+
}
|
|
6296
|
+
return {
|
|
6297
|
+
ok: false,
|
|
6298
|
+
failureCode: "invalid_receipt",
|
|
6299
|
+
repairGuidance: `Merge receipt for ${issueId} belongs to different prd/issue. Remove it manually: ${receiptPath}`
|
|
6300
|
+
};
|
|
6301
|
+
} catch {
|
|
6302
|
+
return {
|
|
6303
|
+
ok: false,
|
|
6304
|
+
failureCode: "invalid_receipt",
|
|
6305
|
+
repairGuidance: `Merge receipt for ${issueId} is corrupted. Remove it manually: ${receiptPath}`
|
|
6306
|
+
};
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
const sourceBranch = input?.sourceBranch ?? readIssueBranchName(root, prdId, issueId);
|
|
6310
|
+
if (!sourceBranch) {
|
|
6311
|
+
return {
|
|
6312
|
+
ok: false,
|
|
6313
|
+
failureCode: "not_found",
|
|
6314
|
+
repairGuidance: `No source branch found for issue ${issueId} under PRD ${prdId}. Ensure the issue artifact exists with a valid branchName field.`
|
|
6315
|
+
};
|
|
6316
|
+
}
|
|
6317
|
+
try {
|
|
6318
|
+
execFileSync2(
|
|
6319
|
+
"git",
|
|
6320
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${sourceBranch}`],
|
|
6321
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6322
|
+
);
|
|
6323
|
+
} catch {
|
|
6324
|
+
return {
|
|
6325
|
+
ok: false,
|
|
6326
|
+
failureCode: "not_found",
|
|
6327
|
+
repairGuidance: `Source branch "${sourceBranch}" does not exist locally. Check that the branch was created and not deleted.`
|
|
6328
|
+
};
|
|
6329
|
+
}
|
|
6330
|
+
const targetBranch = getLocalPrdBranchName(prdId);
|
|
6331
|
+
try {
|
|
6332
|
+
execFileSync2(
|
|
6333
|
+
"git",
|
|
6334
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
|
|
6335
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6336
|
+
);
|
|
6337
|
+
} catch {
|
|
6338
|
+
return {
|
|
6339
|
+
ok: false,
|
|
6340
|
+
failureCode: "not_found",
|
|
6341
|
+
repairGuidance: `Local PRD branch "${targetBranch}" does not exist. Run \`prd-run start\` first to create it.`
|
|
6342
|
+
};
|
|
6343
|
+
}
|
|
6344
|
+
try {
|
|
6345
|
+
execFileSync2("git", ["checkout", targetBranch], {
|
|
6346
|
+
cwd: root,
|
|
6347
|
+
encoding: "utf8",
|
|
6348
|
+
stdio: "pipe"
|
|
6349
|
+
});
|
|
6350
|
+
let preMergeHead;
|
|
6351
|
+
try {
|
|
6352
|
+
preMergeHead = execFileSync2("git", ["rev-parse", "HEAD"], {
|
|
6353
|
+
cwd: root,
|
|
6354
|
+
encoding: "utf8",
|
|
6355
|
+
stdio: "pipe"
|
|
6356
|
+
}).trim();
|
|
6357
|
+
} catch {
|
|
6358
|
+
return {
|
|
6359
|
+
ok: false,
|
|
6360
|
+
failureCode: "merge_error",
|
|
6361
|
+
repairGuidance: "Failed to read current HEAD before merge."
|
|
6362
|
+
};
|
|
6363
|
+
}
|
|
6364
|
+
execFileSync2("git", ["merge", "--squash", sourceBranch], {
|
|
6365
|
+
cwd: root,
|
|
6366
|
+
encoding: "utf8",
|
|
6367
|
+
stdio: "pipe"
|
|
6368
|
+
});
|
|
6369
|
+
if (input) {
|
|
6370
|
+
execFileSync2(
|
|
6371
|
+
"git",
|
|
6372
|
+
["commit", "-m", input.finalizerTitle, "-m", input.finalizerBody],
|
|
6373
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6374
|
+
);
|
|
6375
|
+
} else {
|
|
6376
|
+
execFileSync2(
|
|
6377
|
+
"git",
|
|
6378
|
+
["commit", "-m", `Squash merge ${sourceBranch} into ${targetBranch}`],
|
|
6379
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6380
|
+
);
|
|
6381
|
+
}
|
|
6382
|
+
const revParseResult = execFileSync2("git", ["rev-parse", "HEAD"], {
|
|
6383
|
+
cwd: root,
|
|
6384
|
+
encoding: "utf8",
|
|
6385
|
+
stdio: "pipe"
|
|
6386
|
+
});
|
|
6387
|
+
const mergeCommit = revParseResult.trim();
|
|
6388
|
+
const receipt = {
|
|
6389
|
+
prdId,
|
|
6390
|
+
issueId,
|
|
6391
|
+
stage: "child-issue",
|
|
6392
|
+
sourceBranch,
|
|
6393
|
+
localPrdBranch: targetBranch,
|
|
6394
|
+
mergeCommit,
|
|
6395
|
+
finalizerArtifactPath: input?.finalizerArtifactPath ?? "",
|
|
6396
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6397
|
+
};
|
|
6398
|
+
try {
|
|
6399
|
+
const receiptsDir = join13(
|
|
6400
|
+
root,
|
|
6401
|
+
".pourkit",
|
|
6402
|
+
"local-prd-runs",
|
|
6403
|
+
prdId,
|
|
6404
|
+
"merge-receipts"
|
|
6405
|
+
);
|
|
6406
|
+
mkdirSync7(receiptsDir, { recursive: true });
|
|
6407
|
+
writeFileSync4(receiptPath, JSON.stringify(receipt, null, 2), "utf-8");
|
|
6408
|
+
} catch {
|
|
6409
|
+
try {
|
|
6410
|
+
execFileSync2("git", ["reset", "--hard", preMergeHead], {
|
|
6411
|
+
cwd: root,
|
|
6412
|
+
encoding: "utf8",
|
|
6413
|
+
stdio: "pipe"
|
|
6414
|
+
});
|
|
6415
|
+
} catch {
|
|
6416
|
+
}
|
|
6417
|
+
return {
|
|
6418
|
+
ok: false,
|
|
6419
|
+
failureCode: "receipt_error",
|
|
6420
|
+
repairGuidance: "Failed to write merge receipt. Check disk space and permissions on .pourkit/local-prd-runs/. The merge commit has been rolled back."
|
|
6421
|
+
};
|
|
6422
|
+
}
|
|
6423
|
+
return { ok: true, receipt };
|
|
6424
|
+
} catch (error) {
|
|
6425
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6426
|
+
if (message.toLowerCase().includes("conflict")) {
|
|
6427
|
+
return {
|
|
6428
|
+
ok: false,
|
|
6429
|
+
failureCode: "conflict",
|
|
6430
|
+
repairGuidance: "Merge conflict during squash. Resolve conflicts in the working tree, then retry."
|
|
6431
|
+
};
|
|
6432
|
+
}
|
|
6433
|
+
const stderr = error instanceof Error && "stderr" in error ? error.stderr : void 0;
|
|
6434
|
+
return {
|
|
6435
|
+
ok: false,
|
|
6436
|
+
failureCode: "merge_error",
|
|
6437
|
+
repairGuidance: stderr ? `Merge failed: ${stderr}` : `Merge failed: ${message}`
|
|
6438
|
+
};
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
|
|
6057
6442
|
// pr/pr-body.ts
|
|
6058
6443
|
import { readFile as readFile2 } from "fs/promises";
|
|
6059
6444
|
var DEFAULT_MANUAL_PR_BODY = `## Summary
|
|
@@ -6786,44 +7171,75 @@ async function completeIssueRun(options) {
|
|
|
6786
7171
|
let prTitle = issue.title;
|
|
6787
7172
|
let prBody;
|
|
6788
7173
|
let finalizerResult;
|
|
7174
|
+
const isLocalModeForFinalizer = options.prdRunMode?.mode === "local";
|
|
6789
7175
|
const finalizerFromState = worktreeState?.finalizer?.completed ? worktreeState.finalizer : null;
|
|
6790
7176
|
if (finalizerFromState) {
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
if (
|
|
7177
|
+
try {
|
|
7178
|
+
if (finalizerFromState.title && finalizerFromState.body) {
|
|
7179
|
+
prTitle = finalizerFromState.title;
|
|
7180
|
+
prBody = finalizerFromState.body;
|
|
7181
|
+
} else if (finalizerFromState.artifactPath) {
|
|
7182
|
+
if (!existsSync10(finalizerFromState.artifactPath)) {
|
|
7183
|
+
throw new FinalizerFailure({
|
|
7184
|
+
message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
|
|
7185
|
+
});
|
|
7186
|
+
}
|
|
7187
|
+
const artifactContent = readFileSync12(
|
|
7188
|
+
finalizerFromState.artifactPath,
|
|
7189
|
+
"utf-8"
|
|
7190
|
+
);
|
|
7191
|
+
const parsed = parsePrDescription(artifactContent);
|
|
7192
|
+
prTitle = parsed.title;
|
|
7193
|
+
prBody = parsed.body;
|
|
7194
|
+
} else {
|
|
6796
7195
|
throw new FinalizerFailure({
|
|
6797
|
-
message:
|
|
7196
|
+
message: "Finalizer state is incomplete: missing title, body, and artifactPath"
|
|
6798
7197
|
});
|
|
6799
7198
|
}
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
7199
|
+
} catch (error) {
|
|
7200
|
+
if (isLocalModeForFinalizer) {
|
|
7201
|
+
return {
|
|
7202
|
+
branchName,
|
|
7203
|
+
target,
|
|
7204
|
+
issue,
|
|
7205
|
+
noOp: false,
|
|
7206
|
+
mode: "local",
|
|
7207
|
+
failureCode: "finalizer_failed",
|
|
7208
|
+
repairGuidance: error instanceof Error ? error.message : "Finalizer state is invalid"
|
|
7209
|
+
};
|
|
7210
|
+
}
|
|
7211
|
+
throw error;
|
|
6811
7212
|
}
|
|
6812
7213
|
} else {
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
7214
|
+
try {
|
|
7215
|
+
finalizerResult = await runEffectAndMapExit(
|
|
7216
|
+
runFinalizerAgent({
|
|
7217
|
+
executionProvider,
|
|
7218
|
+
config,
|
|
7219
|
+
target,
|
|
7220
|
+
issue,
|
|
7221
|
+
builderBranch: branchName,
|
|
7222
|
+
targetBaseBranch: effectiveBaseBranch,
|
|
7223
|
+
worktreePath: executionResult.worktreePath,
|
|
7224
|
+
reviewArtifactPath,
|
|
7225
|
+
repoRoot: ROOT,
|
|
7226
|
+
logger
|
|
7227
|
+
})
|
|
7228
|
+
);
|
|
7229
|
+
} catch (error) {
|
|
7230
|
+
if (isLocalModeForFinalizer) {
|
|
7231
|
+
return {
|
|
7232
|
+
branchName,
|
|
7233
|
+
target,
|
|
7234
|
+
issue,
|
|
7235
|
+
noOp: false,
|
|
7236
|
+
mode: "local",
|
|
7237
|
+
failureCode: "finalizer_failed",
|
|
7238
|
+
repairGuidance: error instanceof Error ? error.message : "Finalizer execution failed"
|
|
7239
|
+
};
|
|
7240
|
+
}
|
|
7241
|
+
throw error;
|
|
7242
|
+
}
|
|
6827
7243
|
prTitle = finalizerResult.title;
|
|
6828
7244
|
prBody = finalizerResult.body;
|
|
6829
7245
|
}
|
|
@@ -6868,6 +7284,113 @@ async function completeIssueRun(options) {
|
|
|
6868
7284
|
});
|
|
6869
7285
|
}
|
|
6870
7286
|
}
|
|
7287
|
+
const isLocalMode = options.prdRunMode?.mode === "local";
|
|
7288
|
+
if (isLocalMode) {
|
|
7289
|
+
const parsed = parseStackedIssue(issue.title, issue.body);
|
|
7290
|
+
const prdId = parsed.parentRef;
|
|
7291
|
+
if (!prdId) {
|
|
7292
|
+
return {
|
|
7293
|
+
branchName,
|
|
7294
|
+
target,
|
|
7295
|
+
issue,
|
|
7296
|
+
noOp: false,
|
|
7297
|
+
mode: "local",
|
|
7298
|
+
failureCode: "finalizer_failed",
|
|
7299
|
+
repairGuidance: "Local mode requires a PRD reference (## Parent) in the issue body or title. Add the parent PRD reference and retry."
|
|
7300
|
+
};
|
|
7301
|
+
}
|
|
7302
|
+
const finalizerFromWorktreeState = worktreeState?.finalizer;
|
|
7303
|
+
if (finalizerFromWorktreeState && !finalizerFromWorktreeState.completed) {
|
|
7304
|
+
return {
|
|
7305
|
+
branchName,
|
|
7306
|
+
target,
|
|
7307
|
+
issue,
|
|
7308
|
+
noOp: false,
|
|
7309
|
+
mode: "local",
|
|
7310
|
+
failureCode: "finalizer_failed",
|
|
7311
|
+
repairGuidance: "Finalizer did not complete on previous run. Re-run the issue finalizer and try again."
|
|
7312
|
+
};
|
|
7313
|
+
}
|
|
7314
|
+
if (!prTitle || !finalBody) {
|
|
7315
|
+
return {
|
|
7316
|
+
branchName,
|
|
7317
|
+
target,
|
|
7318
|
+
issue,
|
|
7319
|
+
noOp: false,
|
|
7320
|
+
mode: "local",
|
|
7321
|
+
failureCode: "finalizer_failed",
|
|
7322
|
+
repairGuidance: "Finalizer produced empty title or body. Re-run the issue finalizer and try again."
|
|
7323
|
+
};
|
|
7324
|
+
}
|
|
7325
|
+
const existingReceipt = await hasLocalIssueMergeReceipt(
|
|
7326
|
+
prdId,
|
|
7327
|
+
`issue-${issueNumber}`,
|
|
7328
|
+
ROOT
|
|
7329
|
+
);
|
|
7330
|
+
if (existingReceipt) {
|
|
7331
|
+
return {
|
|
7332
|
+
branchName,
|
|
7333
|
+
target,
|
|
7334
|
+
issue,
|
|
7335
|
+
noOp: false,
|
|
7336
|
+
mode: "local",
|
|
7337
|
+
failureCode: "already_merged",
|
|
7338
|
+
localPrdBranch: existingReceipt.localPrdBranch,
|
|
7339
|
+
mergeCommit: existingReceipt.mergeCommit,
|
|
7340
|
+
receiptPath: join14(
|
|
7341
|
+
ROOT,
|
|
7342
|
+
".pourkit",
|
|
7343
|
+
"local-prd-runs",
|
|
7344
|
+
prdId,
|
|
7345
|
+
"merge-receipts",
|
|
7346
|
+
`issue-${issueNumber}.json`
|
|
7347
|
+
)
|
|
7348
|
+
};
|
|
7349
|
+
}
|
|
7350
|
+
const mergeResult = await squashMergeLocalIssue(
|
|
7351
|
+
prdId,
|
|
7352
|
+
`issue-${issueNumber}`,
|
|
7353
|
+
{
|
|
7354
|
+
finalizerTitle: prTitle,
|
|
7355
|
+
finalizerBody: finalBody,
|
|
7356
|
+
finalizerArtifactPath: worktreeState?.finalizer?.artifactPath ?? "",
|
|
7357
|
+
sourceBranch: branchName
|
|
7358
|
+
},
|
|
7359
|
+
ROOT
|
|
7360
|
+
);
|
|
7361
|
+
if (!mergeResult.ok) {
|
|
7362
|
+
return {
|
|
7363
|
+
branchName,
|
|
7364
|
+
target,
|
|
7365
|
+
issue,
|
|
7366
|
+
noOp: false,
|
|
7367
|
+
mode: "local",
|
|
7368
|
+
failureCode: mergeResult.failureCode,
|
|
7369
|
+
repairGuidance: mergeResult.repairGuidance ?? `Local squash-merge failed: ${mergeResult.failureCode}`
|
|
7370
|
+
};
|
|
7371
|
+
}
|
|
7372
|
+
await issueProvider.removeLabel(
|
|
7373
|
+
issueNumber,
|
|
7374
|
+
config.labels.agentInProgress
|
|
7375
|
+
);
|
|
7376
|
+
return {
|
|
7377
|
+
branchName,
|
|
7378
|
+
target,
|
|
7379
|
+
issue,
|
|
7380
|
+
noOp: false,
|
|
7381
|
+
mode: "local",
|
|
7382
|
+
localPrdBranch: getLocalPrdBranchName(prdId),
|
|
7383
|
+
mergeCommit: mergeResult.receipt.mergeCommit,
|
|
7384
|
+
receiptPath: join14(
|
|
7385
|
+
ROOT,
|
|
7386
|
+
".pourkit",
|
|
7387
|
+
"local-prd-runs",
|
|
7388
|
+
prdId,
|
|
7389
|
+
"merge-receipts",
|
|
7390
|
+
`issue-${issueNumber}.json`
|
|
7391
|
+
)
|
|
7392
|
+
};
|
|
7393
|
+
}
|
|
6871
7394
|
let pr;
|
|
6872
7395
|
if (worktreeState?.pr?.merged) {
|
|
6873
7396
|
mergeCompleted = true;
|
|
@@ -7309,7 +7832,7 @@ async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
|
|
|
7309
7832
|
return { mode: "new", branchName, baseRef };
|
|
7310
7833
|
}
|
|
7311
7834
|
function issueWorktreePath(root, branchName) {
|
|
7312
|
-
return
|
|
7835
|
+
return join14(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
|
|
7313
7836
|
}
|
|
7314
7837
|
function resolveRegisteredIssueWorktreePath(worktreeListPorcelain, root, branchName) {
|
|
7315
7838
|
const branchWorktreePath = parseWorktreeListPorcelain(
|
|
@@ -7337,7 +7860,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
|
|
|
7337
7860
|
}
|
|
7338
7861
|
function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
7339
7862
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
7340
|
-
const promptBody =
|
|
7863
|
+
const promptBody = existsSync10(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
|
|
7341
7864
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
7342
7865
|
|
|
7343
7866
|
## Shared Run Context
|
|
@@ -7780,29 +8303,29 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
|
|
|
7780
8303
|
// commands/prd-run.ts
|
|
7781
8304
|
import {
|
|
7782
8305
|
cpSync,
|
|
7783
|
-
existsSync as
|
|
8306
|
+
existsSync as existsSync17,
|
|
7784
8307
|
lstatSync,
|
|
7785
|
-
mkdirSync as
|
|
8308
|
+
mkdirSync as mkdirSync11,
|
|
7786
8309
|
mkdtempSync,
|
|
7787
8310
|
readdirSync as readdirSync6,
|
|
7788
|
-
readFileSync as
|
|
8311
|
+
readFileSync as readFileSync16,
|
|
7789
8312
|
realpathSync,
|
|
7790
8313
|
rmSync as rmSync3
|
|
7791
8314
|
} from "fs";
|
|
7792
8315
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
7793
|
-
import { dirname as dirname5, join as
|
|
8316
|
+
import { dirname as dirname5, join as join20, relative as relative2 } from "path";
|
|
7794
8317
|
import { tmpdir } from "os";
|
|
7795
8318
|
|
|
7796
8319
|
// prd-run/state.ts
|
|
7797
8320
|
import {
|
|
7798
|
-
existsSync as
|
|
7799
|
-
mkdirSync as
|
|
7800
|
-
readFileSync as
|
|
8321
|
+
existsSync as existsSync11,
|
|
8322
|
+
mkdirSync as mkdirSync8,
|
|
8323
|
+
readFileSync as readFileSync13,
|
|
7801
8324
|
readdirSync as readdirSync4,
|
|
7802
|
-
writeFileSync as
|
|
8325
|
+
writeFileSync as writeFileSync5
|
|
7803
8326
|
} from "fs";
|
|
7804
8327
|
import { mkdir as mkdir4, readFile as readFile4, writeFile } from "fs/promises";
|
|
7805
|
-
import { join as
|
|
8328
|
+
import { join as join15 } from "path";
|
|
7806
8329
|
import { z as z3 } from "zod";
|
|
7807
8330
|
var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
|
|
7808
8331
|
var PrdRunRecordSchema = z3.object({
|
|
@@ -7946,12 +8469,12 @@ function normalizePrdRunRef2(ref) {
|
|
|
7946
8469
|
function readPrdRun(repoRoot2, prdRef) {
|
|
7947
8470
|
const normalized = normalizePrdRunRef2(prdRef);
|
|
7948
8471
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7949
|
-
if (!
|
|
8472
|
+
if (!existsSync11(recordPath)) {
|
|
7950
8473
|
return { record: null, diagnostics: [] };
|
|
7951
8474
|
}
|
|
7952
8475
|
try {
|
|
7953
8476
|
const parsed = PrdRunRecordSchema.parse(
|
|
7954
|
-
JSON.parse(
|
|
8477
|
+
JSON.parse(readFileSync13(recordPath, "utf-8"))
|
|
7955
8478
|
);
|
|
7956
8479
|
return { record: parsed, diagnostics: [] };
|
|
7957
8480
|
} catch (error) {
|
|
@@ -7965,8 +8488,8 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7965
8488
|
}
|
|
7966
8489
|
}
|
|
7967
8490
|
function listPrdRuns(repoRoot2) {
|
|
7968
|
-
const stateDir =
|
|
7969
|
-
if (!
|
|
8491
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
8492
|
+
if (!existsSync11(stateDir)) {
|
|
7970
8493
|
return { records: [], diagnostics: [] };
|
|
7971
8494
|
}
|
|
7972
8495
|
const records = [];
|
|
@@ -7975,10 +8498,10 @@ function listPrdRuns(repoRoot2) {
|
|
|
7975
8498
|
(left, right) => left.name.localeCompare(right.name)
|
|
7976
8499
|
)) {
|
|
7977
8500
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
7978
|
-
const recordPath =
|
|
8501
|
+
const recordPath = join15(stateDir, entry.name);
|
|
7979
8502
|
try {
|
|
7980
8503
|
const record = PrdRunRecordSchema.parse(
|
|
7981
|
-
JSON.parse(
|
|
8504
|
+
JSON.parse(readFileSync13(recordPath, "utf-8"))
|
|
7982
8505
|
);
|
|
7983
8506
|
records.push(record);
|
|
7984
8507
|
} catch (error) {
|
|
@@ -7991,10 +8514,10 @@ function listPrdRuns(repoRoot2) {
|
|
|
7991
8514
|
}
|
|
7992
8515
|
function writePrdRunRecord(repoRoot2, record) {
|
|
7993
8516
|
const normalized = normalizePrdRunRef2(record.prdRef);
|
|
7994
|
-
const stateDir =
|
|
8517
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7995
8518
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7996
|
-
|
|
7997
|
-
|
|
8519
|
+
mkdirSync8(stateDir, { recursive: true });
|
|
8520
|
+
writeFileSync5(
|
|
7998
8521
|
recordPath,
|
|
7999
8522
|
JSON.stringify({ ...record, prdRef: normalized }, null, 2),
|
|
8000
8523
|
"utf-8"
|
|
@@ -8031,8 +8554,8 @@ var LocalPrdRunRecordSchema = z3.object({
|
|
|
8031
8554
|
}),
|
|
8032
8555
|
metadata: z3.record(z3.unknown())
|
|
8033
8556
|
});
|
|
8034
|
-
function
|
|
8035
|
-
return
|
|
8557
|
+
function getLocalStorePath2(repoRoot2, prdId) {
|
|
8558
|
+
return join15(
|
|
8036
8559
|
repoRoot2,
|
|
8037
8560
|
LOCAL_PRD_RUN_STATE_DIR,
|
|
8038
8561
|
`${normalizePrdRunRef2(prdId)}.json`
|
|
@@ -8040,8 +8563,8 @@ function getLocalStorePath(repoRoot2, prdId) {
|
|
|
8040
8563
|
}
|
|
8041
8564
|
async function readLocalPrdRun(repoRoot2, prdId) {
|
|
8042
8565
|
const normalized = normalizePrdRunRef2(prdId);
|
|
8043
|
-
const recordPath =
|
|
8044
|
-
if (!
|
|
8566
|
+
const recordPath = getLocalStorePath2(repoRoot2, normalized);
|
|
8567
|
+
if (!existsSync11(recordPath)) {
|
|
8045
8568
|
return null;
|
|
8046
8569
|
}
|
|
8047
8570
|
try {
|
|
@@ -8053,16 +8576,16 @@ async function readLocalPrdRun(repoRoot2, prdId) {
|
|
|
8053
8576
|
}
|
|
8054
8577
|
async function writeLocalPrdRunRecord(repoRoot2, prdId, record) {
|
|
8055
8578
|
const normalized = normalizePrdRunRef2(prdId);
|
|
8056
|
-
const storeDir =
|
|
8579
|
+
const storeDir = join15(repoRoot2, LOCAL_PRD_RUN_STATE_DIR);
|
|
8057
8580
|
await mkdir4(storeDir, { recursive: true });
|
|
8058
8581
|
await writeFile(
|
|
8059
|
-
|
|
8582
|
+
getLocalStorePath2(repoRoot2, normalized),
|
|
8060
8583
|
JSON.stringify({ ...record, prdId: normalized }, null, 2),
|
|
8061
8584
|
"utf-8"
|
|
8062
8585
|
);
|
|
8063
8586
|
}
|
|
8064
8587
|
function getRecordPath(repoRoot2, prdRef) {
|
|
8065
|
-
return
|
|
8588
|
+
return join15(
|
|
8066
8589
|
repoRoot2,
|
|
8067
8590
|
PRD_RUN_STATE_DIR,
|
|
8068
8591
|
`${normalizePrdRunRef2(prdRef)}.json`
|
|
@@ -8195,9 +8718,9 @@ function redactSensitiveValues(input) {
|
|
|
8195
8718
|
}
|
|
8196
8719
|
|
|
8197
8720
|
// prd-run/final-review-validation.ts
|
|
8198
|
-
import { existsSync as
|
|
8721
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
8199
8722
|
function parseFinalReviewArtifact(artifactPath) {
|
|
8200
|
-
if (!
|
|
8723
|
+
if (!existsSync12(artifactPath)) {
|
|
8201
8724
|
return {
|
|
8202
8725
|
ok: false,
|
|
8203
8726
|
reason: "Final Review artifact not found.",
|
|
@@ -8206,7 +8729,7 @@ function parseFinalReviewArtifact(artifactPath) {
|
|
|
8206
8729
|
}
|
|
8207
8730
|
let content;
|
|
8208
8731
|
try {
|
|
8209
|
-
content =
|
|
8732
|
+
content = readFileSync14(artifactPath, "utf-8");
|
|
8210
8733
|
} catch (error) {
|
|
8211
8734
|
return {
|
|
8212
8735
|
ok: false,
|
|
@@ -8386,8 +8909,8 @@ function isRetouchScopePath(path9) {
|
|
|
8386
8909
|
init_common();
|
|
8387
8910
|
|
|
8388
8911
|
// prd-run/local-artifacts.ts
|
|
8389
|
-
import { existsSync as
|
|
8390
|
-
import { join as
|
|
8912
|
+
import { existsSync as existsSync13 } from "fs";
|
|
8913
|
+
import { join as join16 } from "path";
|
|
8391
8914
|
var REQUIRED_PRD_FIELDS = [
|
|
8392
8915
|
"schemaVersion",
|
|
8393
8916
|
"kind",
|
|
@@ -8420,13 +8943,13 @@ var REQUIRED_ISSUE_FIELDS = [
|
|
|
8420
8943
|
"githubProjection"
|
|
8421
8944
|
];
|
|
8422
8945
|
function prdStorePath(repoRoot2, prdId) {
|
|
8423
|
-
return
|
|
8946
|
+
return join16(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8424
8947
|
}
|
|
8425
8948
|
function prdArtifactPath(repoRoot2, prdId) {
|
|
8426
|
-
return
|
|
8949
|
+
return join16(prdStorePath(repoRoot2, prdId), "prd.json");
|
|
8427
8950
|
}
|
|
8428
8951
|
function issueArtifactPath(repoRoot2, prdId, issueId) {
|
|
8429
|
-
return
|
|
8952
|
+
return join16(prdStorePath(repoRoot2, prdId), "issues", `${issueId}.json`);
|
|
8430
8953
|
}
|
|
8431
8954
|
function hasRequiredFields(data, requiredFields) {
|
|
8432
8955
|
for (const field of requiredFields) {
|
|
@@ -8439,7 +8962,7 @@ function hasRequiredFields(data, requiredFields) {
|
|
|
8439
8962
|
async function resolveLocalPrdArtifact(prdId, repoRoot2) {
|
|
8440
8963
|
const root = repoRoot2 ?? process.cwd();
|
|
8441
8964
|
const prdPath = prdArtifactPath(root, prdId);
|
|
8442
|
-
if (!
|
|
8965
|
+
if (!existsSync13(prdPath)) {
|
|
8443
8966
|
return {
|
|
8444
8967
|
ok: false,
|
|
8445
8968
|
failureCode: "missing_prd_artifact",
|
|
@@ -8484,7 +9007,7 @@ async function resolveLocalIssueArtifacts(prdId, repoRoot2) {
|
|
|
8484
9007
|
const issues = [];
|
|
8485
9008
|
for (const childId of childIssueIds) {
|
|
8486
9009
|
const issuePath = issueArtifactPath(root, prdId, childId);
|
|
8487
|
-
if (!
|
|
9010
|
+
if (!existsSync13(issuePath)) {
|
|
8488
9011
|
return {
|
|
8489
9012
|
ok: false,
|
|
8490
9013
|
failureCode: "missing_child_issue",
|
|
@@ -8620,97 +9143,14 @@ async function getRunnableLocalIssues(prdId, repoRoot2) {
|
|
|
8620
9143
|
}
|
|
8621
9144
|
|
|
8622
9145
|
// prd-run/local-final-review.ts
|
|
8623
|
-
import { execFileSync as
|
|
8624
|
-
import { mkdirSync as
|
|
8625
|
-
import { join as
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
import { execFileSync } from "child_process";
|
|
8629
|
-
function getLocalPrdBranchName(prdId) {
|
|
8630
|
-
return `local/${prdId}`;
|
|
8631
|
-
}
|
|
8632
|
-
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
|
|
8633
|
-
var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
|
|
8634
|
-
function validateLocalBranchName(name) {
|
|
8635
|
-
if (!name || name.length === 0) {
|
|
8636
|
-
return {
|
|
8637
|
-
ok: false,
|
|
8638
|
-
failureCode: "invalid_format",
|
|
8639
|
-
message: "Branch name is empty."
|
|
8640
|
-
};
|
|
8641
|
-
}
|
|
8642
|
-
if (isProtectedBranch(name)) {
|
|
8643
|
-
return {
|
|
8644
|
-
ok: false,
|
|
8645
|
-
failureCode: "protected_branch",
|
|
8646
|
-
message: `Branch "${name}" is protected.`
|
|
8647
|
-
};
|
|
8648
|
-
}
|
|
8649
|
-
if (!LOCAL_BRANCH_PATTERN.test(name)) {
|
|
8650
|
-
return {
|
|
8651
|
-
ok: false,
|
|
8652
|
-
failureCode: "invalid_format",
|
|
8653
|
-
message: `Branch "${name}" does not match the local branch pattern.`
|
|
8654
|
-
};
|
|
8655
|
-
}
|
|
8656
|
-
return { ok: true };
|
|
8657
|
-
}
|
|
8658
|
-
function isProtectedBranch(name) {
|
|
8659
|
-
return PROTECTED_BRANCHES.has(name);
|
|
8660
|
-
}
|
|
8661
|
-
function hasRemoteBackedCollision(localName, repoRoot2) {
|
|
8662
|
-
const match = localName.match(/^local\/(PRD-\d{4})/);
|
|
8663
|
-
if (!match) {
|
|
8664
|
-
return Promise.resolve({
|
|
8665
|
-
ok: false,
|
|
8666
|
-
failureCode: "invalid_format",
|
|
8667
|
-
message: `Cannot extract PRD ref from "${localName}".`
|
|
8668
|
-
});
|
|
8669
|
-
}
|
|
8670
|
-
const prdRef = match[1];
|
|
8671
|
-
try {
|
|
8672
|
-
execFileSync(
|
|
8673
|
-
"git",
|
|
8674
|
-
["show-ref", "--verify", "--quiet", `refs/heads/${prdRef}`],
|
|
8675
|
-
{
|
|
8676
|
-
cwd: repoRoot2 ?? process.cwd(),
|
|
8677
|
-
encoding: "utf8",
|
|
8678
|
-
stdio: "pipe"
|
|
8679
|
-
}
|
|
8680
|
-
);
|
|
8681
|
-
return Promise.resolve({
|
|
8682
|
-
ok: false,
|
|
8683
|
-
failureCode: "remote_backed_collision",
|
|
8684
|
-
message: `Branch "${prdRef}" exists locally.`
|
|
8685
|
-
});
|
|
8686
|
-
} catch {
|
|
8687
|
-
}
|
|
8688
|
-
try {
|
|
8689
|
-
execFileSync(
|
|
8690
|
-
"git",
|
|
8691
|
-
["show-ref", "--verify", "--quiet", `refs/remotes/origin/${prdRef}`],
|
|
8692
|
-
{
|
|
8693
|
-
cwd: repoRoot2 ?? process.cwd(),
|
|
8694
|
-
encoding: "utf8",
|
|
8695
|
-
stdio: "pipe"
|
|
8696
|
-
}
|
|
8697
|
-
);
|
|
8698
|
-
return Promise.resolve({
|
|
8699
|
-
ok: false,
|
|
8700
|
-
failureCode: "remote_backed_collision",
|
|
8701
|
-
message: `Remote branch "origin/${prdRef}" exists.`
|
|
8702
|
-
});
|
|
8703
|
-
} catch {
|
|
8704
|
-
}
|
|
8705
|
-
return Promise.resolve({ ok: true });
|
|
8706
|
-
}
|
|
8707
|
-
|
|
8708
|
-
// prd-run/local-final-review.ts
|
|
8709
|
-
function getLocalStorePath2(repoRoot2, prdId) {
|
|
8710
|
-
return join16(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
9146
|
+
import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
|
|
9147
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
9148
|
+
import { join as join17 } from "path";
|
|
9149
|
+
function getLocalStorePath3(repoRoot2, prdId) {
|
|
9150
|
+
return join17(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8711
9151
|
}
|
|
8712
9152
|
function getFinalReviewReceiptPath(repoRoot2, prdId) {
|
|
8713
|
-
return
|
|
9153
|
+
return join17(getLocalStorePath3(repoRoot2, prdId), "final-review-receipt.json");
|
|
8714
9154
|
}
|
|
8715
9155
|
async function runLocalFinalReview(prdId, options) {
|
|
8716
9156
|
const root = options?.repoRoot ?? process.cwd();
|
|
@@ -8767,8 +9207,8 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
8767
9207
|
branch
|
|
8768
9208
|
};
|
|
8769
9209
|
const receiptPath = getFinalReviewReceiptPath(root, prdId);
|
|
8770
|
-
|
|
8771
|
-
|
|
9210
|
+
mkdirSync9(getLocalStorePath3(root, prdId), { recursive: true });
|
|
9211
|
+
writeFileSync6(receiptPath, JSON.stringify(receipt, null, 2), "utf-8");
|
|
8772
9212
|
const result = { ok: true, verdict, receipt };
|
|
8773
9213
|
if (failures.length > 0) {
|
|
8774
9214
|
result.message = failures.map(
|
|
@@ -8782,7 +9222,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8782
9222
|
const targetBranch = `local/${prdId}`;
|
|
8783
9223
|
const retouchBranch = `local/${prdId}/retouch`;
|
|
8784
9224
|
try {
|
|
8785
|
-
|
|
9225
|
+
execFileSync3(
|
|
8786
9226
|
"git",
|
|
8787
9227
|
["show-ref", "--verify", "--quiet", `refs/heads/${retouchBranch}`],
|
|
8788
9228
|
{
|
|
@@ -8795,7 +9235,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8795
9235
|
return;
|
|
8796
9236
|
}
|
|
8797
9237
|
try {
|
|
8798
|
-
|
|
9238
|
+
execFileSync3(
|
|
8799
9239
|
"git",
|
|
8800
9240
|
["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
|
|
8801
9241
|
{
|
|
@@ -8808,18 +9248,18 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8808
9248
|
return;
|
|
8809
9249
|
}
|
|
8810
9250
|
try {
|
|
8811
|
-
|
|
9251
|
+
execFileSync3("git", ["checkout", targetBranch], {
|
|
8812
9252
|
cwd: root,
|
|
8813
9253
|
encoding: "utf8",
|
|
8814
9254
|
stdio: "pipe"
|
|
8815
9255
|
});
|
|
8816
|
-
|
|
9256
|
+
execFileSync3("git", ["merge", "--squash", retouchBranch], {
|
|
8817
9257
|
cwd: root,
|
|
8818
9258
|
encoding: "utf8",
|
|
8819
9259
|
stdio: "pipe"
|
|
8820
9260
|
});
|
|
8821
9261
|
try {
|
|
8822
|
-
|
|
9262
|
+
execFileSync3("git", ["diff", "--cached", "--quiet"], {
|
|
8823
9263
|
cwd: root,
|
|
8824
9264
|
encoding: "utf8",
|
|
8825
9265
|
stdio: "pipe"
|
|
@@ -8827,7 +9267,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8827
9267
|
return;
|
|
8828
9268
|
} catch {
|
|
8829
9269
|
}
|
|
8830
|
-
|
|
9270
|
+
execFileSync3(
|
|
8831
9271
|
"git",
|
|
8832
9272
|
["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
|
|
8833
9273
|
{
|
|
@@ -8846,53 +9286,53 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8846
9286
|
}
|
|
8847
9287
|
|
|
8848
9288
|
// prd-run/local-reconciliation.ts
|
|
8849
|
-
import { execFileSync as
|
|
9289
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
8850
9290
|
import {
|
|
8851
|
-
existsSync as
|
|
8852
|
-
mkdirSync as
|
|
8853
|
-
readFileSync as
|
|
9291
|
+
existsSync as existsSync15,
|
|
9292
|
+
mkdirSync as mkdirSync10,
|
|
9293
|
+
readFileSync as readFileSync15,
|
|
8854
9294
|
readdirSync as readdirSync5,
|
|
8855
|
-
writeFileSync as
|
|
9295
|
+
writeFileSync as writeFileSync7
|
|
8856
9296
|
} from "fs";
|
|
8857
|
-
import { join as
|
|
8858
|
-
function
|
|
8859
|
-
return
|
|
9297
|
+
import { join as join18 } from "path";
|
|
9298
|
+
function getLocalStorePath4(repoRoot2, prdId) {
|
|
9299
|
+
return join18(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8860
9300
|
}
|
|
8861
9301
|
function getFinalReviewReceiptPath2(repoRoot2, prdId) {
|
|
8862
|
-
return
|
|
9302
|
+
return join18(getLocalStorePath4(repoRoot2, prdId), "final-review-receipt.json");
|
|
8863
9303
|
}
|
|
8864
9304
|
function getHandoffArtifactPath(repoRoot2, prdId) {
|
|
8865
|
-
return
|
|
9305
|
+
return join18(repoRoot2, ".pourkit", ".tmp", "reconciliation", `${prdId}.json`);
|
|
8866
9306
|
}
|
|
8867
9307
|
function getCompletionsDir(repoRoot2, prdId) {
|
|
8868
|
-
const initiativesRoot =
|
|
9308
|
+
const initiativesRoot = join18(
|
|
8869
9309
|
repoRoot2,
|
|
8870
9310
|
".pourkit",
|
|
8871
9311
|
"architecture",
|
|
8872
9312
|
"initiatives"
|
|
8873
9313
|
);
|
|
8874
|
-
if (
|
|
8875
|
-
const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) =>
|
|
9314
|
+
if (existsSync15(initiativesRoot)) {
|
|
9315
|
+
const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join18(initiativesRoot, e.name));
|
|
8876
9316
|
for (const dir of dirs) {
|
|
8877
|
-
const prdsDir =
|
|
8878
|
-
if (!
|
|
9317
|
+
const prdsDir = join18(dir, "prds");
|
|
9318
|
+
if (!existsSync15(prdsDir)) continue;
|
|
8879
9319
|
const prdDirs = readdirSync5(prdsDir, { withFileTypes: true }).filter(
|
|
8880
9320
|
(e) => e.isDirectory() && e.name.startsWith(prdId.toLowerCase())
|
|
8881
9321
|
);
|
|
8882
|
-
if (prdDirs.length > 0) return
|
|
9322
|
+
if (prdDirs.length > 0) return join18(dir, "completions");
|
|
8883
9323
|
}
|
|
8884
9324
|
}
|
|
8885
|
-
return
|
|
9325
|
+
return join18(getLocalStorePath4(repoRoot2, prdId), "completions");
|
|
8886
9326
|
}
|
|
8887
9327
|
function getReconciliationReceiptPath(repoRoot2, prdId) {
|
|
8888
|
-
return
|
|
8889
|
-
|
|
9328
|
+
return join18(
|
|
9329
|
+
getLocalStorePath4(repoRoot2, prdId),
|
|
8890
9330
|
"reconciliation-receipt.json"
|
|
8891
9331
|
);
|
|
8892
9332
|
}
|
|
8893
9333
|
async function runLocalReconciliation(prdId, options) {
|
|
8894
9334
|
const root = options?.repoRoot ?? process.cwd();
|
|
8895
|
-
if (!
|
|
9335
|
+
if (!existsSync15(getFinalReviewReceiptPath2(root, prdId))) {
|
|
8896
9336
|
return {
|
|
8897
9337
|
ok: false,
|
|
8898
9338
|
failureCode: "missing_final_review",
|
|
@@ -8900,7 +9340,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8900
9340
|
};
|
|
8901
9341
|
}
|
|
8902
9342
|
try {
|
|
8903
|
-
|
|
9343
|
+
execFileSync4("pourkit-architect", ["reconcile", prdId], {
|
|
8904
9344
|
cwd: root,
|
|
8905
9345
|
encoding: "utf8",
|
|
8906
9346
|
stdio: "pipe"
|
|
@@ -8916,7 +9356,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8916
9356
|
}
|
|
8917
9357
|
let handoff;
|
|
8918
9358
|
try {
|
|
8919
|
-
const content =
|
|
9359
|
+
const content = readFileSync15(getHandoffArtifactPath(root, prdId), "utf-8");
|
|
8920
9360
|
handoff = JSON.parse(content);
|
|
8921
9361
|
} catch {
|
|
8922
9362
|
return {
|
|
@@ -8942,11 +9382,11 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8942
9382
|
if (handoff.result === "changes_produced") {
|
|
8943
9383
|
try {
|
|
8944
9384
|
const completionsDir = getCompletionsDir(root, prdId);
|
|
8945
|
-
|
|
8946
|
-
const existing =
|
|
9385
|
+
mkdirSync10(completionsDir, { recursive: true });
|
|
9386
|
+
const existing = existsSync15(completionsDir) ? readdirSync5(completionsDir).filter((f) => f.endsWith(".md")) : [];
|
|
8947
9387
|
const nextNum = String(existing.length + 1).padStart(3, "0");
|
|
8948
9388
|
const filename = `${nextNum}-prd-${prdId.replace("PRD-", "").toLowerCase()}-reconciliation-completion.md`;
|
|
8949
|
-
const filepath =
|
|
9389
|
+
const filepath = join18(completionsDir, filename);
|
|
8950
9390
|
const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
|
|
8951
9391
|
const summary = typeof handoff.summary === "string" ? handoff.summary : "";
|
|
8952
9392
|
const lines = [
|
|
@@ -8962,13 +9402,13 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8962
9402
|
lines.push("- (none)");
|
|
8963
9403
|
}
|
|
8964
9404
|
lines.push("");
|
|
8965
|
-
|
|
8966
|
-
|
|
9405
|
+
writeFileSync7(filepath, lines.join("\n"), "utf-8");
|
|
9406
|
+
execFileSync4("git", ["add", filepath], {
|
|
8967
9407
|
cwd: root,
|
|
8968
9408
|
encoding: "utf8",
|
|
8969
9409
|
stdio: "pipe"
|
|
8970
9410
|
});
|
|
8971
|
-
|
|
9411
|
+
execFileSync4(
|
|
8972
9412
|
"git",
|
|
8973
9413
|
["commit", "-m", `docs: ${prdId} reconciliation completion`],
|
|
8974
9414
|
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
@@ -8985,7 +9425,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8985
9425
|
try {
|
|
8986
9426
|
const targetBranch = getLocalPrdBranchName(prdId);
|
|
8987
9427
|
try {
|
|
8988
|
-
|
|
9428
|
+
execFileSync4(
|
|
8989
9429
|
"git",
|
|
8990
9430
|
["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
|
|
8991
9431
|
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
@@ -8997,14 +9437,14 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8997
9437
|
message: "Squash merge into local PRD Branch failed."
|
|
8998
9438
|
};
|
|
8999
9439
|
}
|
|
9000
|
-
|
|
9440
|
+
execFileSync4("git", ["checkout", targetBranch], {
|
|
9001
9441
|
cwd: root,
|
|
9002
9442
|
encoding: "utf8",
|
|
9003
9443
|
stdio: "pipe"
|
|
9004
9444
|
});
|
|
9005
9445
|
const sourceBranch = `local/${prdId}/reconciliation`;
|
|
9006
9446
|
try {
|
|
9007
|
-
|
|
9447
|
+
execFileSync4(
|
|
9008
9448
|
"git",
|
|
9009
9449
|
["show-ref", "--verify", "--quiet", `refs/heads/${sourceBranch}`],
|
|
9010
9450
|
{
|
|
@@ -9020,19 +9460,19 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9020
9460
|
message: "Squash merge into local PRD Branch failed."
|
|
9021
9461
|
};
|
|
9022
9462
|
}
|
|
9023
|
-
|
|
9463
|
+
execFileSync4("git", ["merge", "--squash", sourceBranch], {
|
|
9024
9464
|
cwd: root,
|
|
9025
9465
|
encoding: "utf8",
|
|
9026
9466
|
stdio: "pipe"
|
|
9027
9467
|
});
|
|
9028
9468
|
try {
|
|
9029
|
-
|
|
9469
|
+
execFileSync4("git", ["diff", "--cached", "--quiet"], {
|
|
9030
9470
|
cwd: root,
|
|
9031
9471
|
encoding: "utf8",
|
|
9032
9472
|
stdio: "pipe"
|
|
9033
9473
|
});
|
|
9034
9474
|
} catch {
|
|
9035
|
-
|
|
9475
|
+
execFileSync4(
|
|
9036
9476
|
"git",
|
|
9037
9477
|
["commit", "-m", `Squash merge ${sourceBranch} into ${targetBranch}`],
|
|
9038
9478
|
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
@@ -9051,8 +9491,8 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9051
9491
|
reconciledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9052
9492
|
branch
|
|
9053
9493
|
};
|
|
9054
|
-
|
|
9055
|
-
|
|
9494
|
+
mkdirSync10(getLocalStorePath4(root, prdId), { recursive: true });
|
|
9495
|
+
writeFileSync7(
|
|
9056
9496
|
getReconciliationReceiptPath(root, prdId),
|
|
9057
9497
|
JSON.stringify(receipt, null, 2),
|
|
9058
9498
|
"utf-8"
|
|
@@ -9061,8 +9501,8 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9061
9501
|
}
|
|
9062
9502
|
|
|
9063
9503
|
// prd-run/local-prepare.ts
|
|
9064
|
-
import { existsSync as
|
|
9065
|
-
import { join as
|
|
9504
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9505
|
+
import { join as join19 } from "path";
|
|
9066
9506
|
var VALID_TRIAGE_LABELS2 = /* @__PURE__ */ new Set([
|
|
9067
9507
|
"needs-triage",
|
|
9068
9508
|
"ready-for-agent",
|
|
@@ -9080,8 +9520,8 @@ function fail(gate, failureCode, repairGuidance, gates) {
|
|
|
9080
9520
|
async function validateLocalPrepareGates(prdId, repoRoot2) {
|
|
9081
9521
|
const root = repoRoot2 ?? process.cwd();
|
|
9082
9522
|
const gates = {};
|
|
9083
|
-
const storePath =
|
|
9084
|
-
if (!
|
|
9523
|
+
const storePath = join19(root, ".pourkit", "local-prd-runs", prdId);
|
|
9524
|
+
if (!existsSync16(storePath)) {
|
|
9085
9525
|
return fail(
|
|
9086
9526
|
"store_shape",
|
|
9087
9527
|
"local_prepare_store_shape_failed",
|
|
@@ -9507,6 +9947,7 @@ function runOneQueueIssueEffect(options) {
|
|
|
9507
9947
|
}
|
|
9508
9948
|
const { issue: selected } = outcome;
|
|
9509
9949
|
const baseBranchOverride = options.queueRunContext?.prdBranch;
|
|
9950
|
+
const prdRunMode = options.prdRunMode ?? options.queueRunContext?.prdRunMode;
|
|
9510
9951
|
const runResult = yield* Effect8.tryPromise({
|
|
9511
9952
|
try: () => runIssueCommand({
|
|
9512
9953
|
issueNumber: selected.number,
|
|
@@ -9518,7 +9959,8 @@ function runOneQueueIssueEffect(options) {
|
|
|
9518
9959
|
force,
|
|
9519
9960
|
logger,
|
|
9520
9961
|
repoRoot: repoRoot2,
|
|
9521
|
-
...baseBranchOverride ? { baseBranchOverride } : {}
|
|
9962
|
+
...baseBranchOverride ? { baseBranchOverride } : {},
|
|
9963
|
+
...prdRunMode ? { prdRunMode } : {}
|
|
9522
9964
|
}),
|
|
9523
9965
|
catch: (e) => {
|
|
9524
9966
|
if (e instanceof Error) return e;
|
|
@@ -9529,6 +9971,10 @@ function runOneQueueIssueEffect(options) {
|
|
|
9529
9971
|
logger.raw(` Branch: ${runResult.branchName}`);
|
|
9530
9972
|
if (runResult.noOp) {
|
|
9531
9973
|
logger.raw(" Status: no-op (closed without PR)");
|
|
9974
|
+
} else if (runResult.mode === "local") {
|
|
9975
|
+
logger.raw(` Local PRD Branch: ${runResult.localPrdBranch}`);
|
|
9976
|
+
logger.raw(` Merge Commit: ${runResult.mergeCommit}`);
|
|
9977
|
+
logger.raw(` Receipt: ${runResult.receiptPath}`);
|
|
9532
9978
|
} else {
|
|
9533
9979
|
logger.raw(` PR Title: ${runResult.prTitle}`);
|
|
9534
9980
|
logger.raw(` PR Number: ${runResult.prNumber}`);
|
|
@@ -9588,7 +10034,8 @@ async function runQueueCommand(options) {
|
|
|
9588
10034
|
logger: options.logger,
|
|
9589
10035
|
repoRoot: options.repoRoot,
|
|
9590
10036
|
prdRef: options.prdRef,
|
|
9591
|
-
queueRunContext: options.queueRunContext
|
|
10037
|
+
queueRunContext: options.queueRunContext,
|
|
10038
|
+
prdRunMode: options.queueRunContext?.prdRunMode
|
|
9592
10039
|
};
|
|
9593
10040
|
if (!options.loop) {
|
|
9594
10041
|
return runEffectAndMapExit(runQueue(queueOptions));
|
|
@@ -9717,7 +10164,7 @@ function validateChangedPlanningPaths(paths, options) {
|
|
|
9717
10164
|
continue;
|
|
9718
10165
|
}
|
|
9719
10166
|
if (options?.repoRoot) {
|
|
9720
|
-
const fullPath =
|
|
10167
|
+
const fullPath = join20(options.repoRoot, p);
|
|
9721
10168
|
try {
|
|
9722
10169
|
if (lstatSync(fullPath).isSymbolicLink()) {
|
|
9723
10170
|
rejected.push(p);
|
|
@@ -9742,7 +10189,7 @@ function assessStaleSucceededReconciliationReceipt(options) {
|
|
|
9742
10189
|
if (!receipt || receipt.status !== "succeeded" || receipt.prNumber || receipt.mergeCommit) {
|
|
9743
10190
|
return { mode: "none" };
|
|
9744
10191
|
}
|
|
9745
|
-
const observedDiff =
|
|
10192
|
+
const observedDiff = collectObservedReconciliationDirtyPaths({
|
|
9746
10193
|
worktreeCwd: options.worktreeCwd,
|
|
9747
10194
|
ignoredPrdRunRecordRef: options.prdRef
|
|
9748
10195
|
});
|
|
@@ -9753,23 +10200,14 @@ function assessStaleSucceededReconciliationReceipt(options) {
|
|
|
9753
10200
|
offendingPaths: []
|
|
9754
10201
|
};
|
|
9755
10202
|
}
|
|
9756
|
-
if (observedDiff.
|
|
9757
|
-
return {
|
|
9758
|
-
mode: "blocked",
|
|
9759
|
-
diagnostics: [
|
|
9760
|
-
"Reconciliation worktree contains unpublished changes outside safe planning scope."
|
|
9761
|
-
],
|
|
9762
|
-
offendingPaths: observedDiff.rejectedPaths
|
|
9763
|
-
};
|
|
9764
|
-
}
|
|
9765
|
-
if (observedDiff.changedPlanningPaths.length === 0) {
|
|
10203
|
+
if (observedDiff.dirtyPaths.length === 0) {
|
|
9766
10204
|
return { mode: "none" };
|
|
9767
10205
|
}
|
|
9768
10206
|
return {
|
|
9769
10207
|
mode: "recoverable",
|
|
9770
|
-
|
|
10208
|
+
dirtyPaths: observedDiff.dirtyPaths,
|
|
9771
10209
|
diagnostics: [
|
|
9772
|
-
"Recovered stale reconciliation success receipt from
|
|
10210
|
+
"Recovered stale reconciliation success receipt from dirty reconciliation worktree."
|
|
9773
10211
|
]
|
|
9774
10212
|
};
|
|
9775
10213
|
}
|
|
@@ -9893,7 +10331,7 @@ function canRetryFinalReviewBlock(record) {
|
|
|
9893
10331
|
}
|
|
9894
10332
|
function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
9895
10333
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
9896
|
-
return
|
|
10334
|
+
return existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : promptTemplate;
|
|
9897
10335
|
}
|
|
9898
10336
|
function formatVerificationCommands(commands) {
|
|
9899
10337
|
if (commands.length === 0) return "No verification commands configured.";
|
|
@@ -9906,12 +10344,14 @@ function buildFinalReviewPrompt(options) {
|
|
|
9906
10344
|
`Worktree checkout base: ${options.evidencePacket.prdBranch}`,
|
|
9907
10345
|
`Review only this range: ${options.evidencePacket.mergeBase}..HEAD`,
|
|
9908
10346
|
"Do not compare against current target branch HEAD; the merge base is the review baseline.",
|
|
9909
|
-
`Before handoff, run:
|
|
10347
|
+
`Before handoff, run: pourkit prd-run validate-final-review ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
|
|
9910
10348
|
"Fix any validation failures before handing off.",
|
|
9911
10349
|
"",
|
|
9912
|
-
"##
|
|
10350
|
+
"## Verification",
|
|
10351
|
+
"",
|
|
10352
|
+
`Run this command before handoff when retouch changes are made: pourkit run-verification --target ${options.targetName}`,
|
|
9913
10353
|
"",
|
|
9914
|
-
"
|
|
10354
|
+
"Underlying project commands:",
|
|
9915
10355
|
formatVerificationCommands(options.verificationCommands),
|
|
9916
10356
|
"",
|
|
9917
10357
|
"Evidence Packet (do not infer PRD context from local state files):",
|
|
@@ -9938,14 +10378,14 @@ function buildReconciliationPrompt(options) {
|
|
|
9938
10378
|
options.artifactPath,
|
|
9939
10379
|
"",
|
|
9940
10380
|
"Self-validation command:",
|
|
9941
|
-
`validate-artifact reconciliation ${options.artifactPath}`
|
|
10381
|
+
`pourkit validate-artifact reconciliation ${options.artifactPath}`
|
|
9942
10382
|
].join("\n");
|
|
9943
10383
|
}
|
|
9944
10384
|
function buildReconciliationBranchName(prdRef) {
|
|
9945
10385
|
return `${normalizePrdRunRef2(prdRef)}-reconciliation`;
|
|
9946
10386
|
}
|
|
9947
10387
|
function reconciliationWorktreePath(repoRoot2, branchName) {
|
|
9948
|
-
return
|
|
10388
|
+
return join20(repoRoot2, ".sandcastle", "worktrees", branchName);
|
|
9949
10389
|
}
|
|
9950
10390
|
function reconciliationReceiptWorktreePath(branchName) {
|
|
9951
10391
|
return `.sandcastle/worktrees/${branchName}`;
|
|
@@ -9957,7 +10397,7 @@ function buildFinalReviewBranchName(prdRef) {
|
|
|
9957
10397
|
return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
|
|
9958
10398
|
}
|
|
9959
10399
|
function finalReviewWorktreePath(repoRoot2, branchName) {
|
|
9960
|
-
return
|
|
10400
|
+
return join20(
|
|
9961
10401
|
repoRoot2,
|
|
9962
10402
|
".sandcastle",
|
|
9963
10403
|
"worktrees",
|
|
@@ -10012,7 +10452,7 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
10012
10452
|
options.repoRoot,
|
|
10013
10453
|
options.promptTemplate
|
|
10014
10454
|
);
|
|
10015
|
-
const promptBody =
|
|
10455
|
+
const promptBody = existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : options.promptTemplate;
|
|
10016
10456
|
return [
|
|
10017
10457
|
"# Final Review Retouch PR Finalizer",
|
|
10018
10458
|
"",
|
|
@@ -10046,7 +10486,7 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
10046
10486
|
}
|
|
10047
10487
|
async function runFinalReviewPrFinalizer(options) {
|
|
10048
10488
|
const finalizer = options.target.strategy.finalize.prDescriptionAgent;
|
|
10049
|
-
const artifactPathInWorktree =
|
|
10489
|
+
const artifactPathInWorktree = join20(
|
|
10050
10490
|
".pourkit",
|
|
10051
10491
|
".tmp",
|
|
10052
10492
|
"finalizer",
|
|
@@ -10120,14 +10560,14 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10120
10560
|
}
|
|
10121
10561
|
return { ok: true, worktreePath: registeredPath };
|
|
10122
10562
|
}
|
|
10123
|
-
if (
|
|
10563
|
+
if (existsSync17(worktreePath)) {
|
|
10124
10564
|
return {
|
|
10125
10565
|
ok: false,
|
|
10126
10566
|
reason: "Final Review worktree path exists but is not registered with git.",
|
|
10127
10567
|
diagnostics: [`stale worktree path: ${worktreePath}`]
|
|
10128
10568
|
};
|
|
10129
10569
|
}
|
|
10130
|
-
|
|
10570
|
+
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
10131
10571
|
const branchResult = spawnSync2(
|
|
10132
10572
|
"git",
|
|
10133
10573
|
["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
|
|
@@ -10182,7 +10622,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10182
10622
|
if (existingPr && existingPr.state === "OPEN") {
|
|
10183
10623
|
return existingPr;
|
|
10184
10624
|
}
|
|
10185
|
-
const worktreePath = mkdtempSync(
|
|
10625
|
+
const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-retouch-"));
|
|
10186
10626
|
try {
|
|
10187
10627
|
runGitOrThrow(
|
|
10188
10628
|
options.repoRoot,
|
|
@@ -10200,9 +10640,9 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10200
10640
|
"create retouch branch"
|
|
10201
10641
|
);
|
|
10202
10642
|
for (const changedFile of options.changedPaths) {
|
|
10203
|
-
const sourcePath =
|
|
10204
|
-
const targetPath =
|
|
10205
|
-
|
|
10643
|
+
const sourcePath = join20(options.sourceWorktreePath, changedFile);
|
|
10644
|
+
const targetPath = join20(worktreePath, changedFile);
|
|
10645
|
+
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
10206
10646
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
10207
10647
|
}
|
|
10208
10648
|
runGitOrThrow(
|
|
@@ -10556,6 +10996,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10556
10996
|
prompt: buildFinalReviewPrompt({
|
|
10557
10997
|
repoRoot: options.repoRoot,
|
|
10558
10998
|
promptTemplate: finalReviewConfig.promptTemplate,
|
|
10999
|
+
targetName,
|
|
10559
11000
|
evidencePacket,
|
|
10560
11001
|
verificationCommands
|
|
10561
11002
|
}),
|
|
@@ -10631,7 +11072,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10631
11072
|
};
|
|
10632
11073
|
}
|
|
10633
11074
|
const resolvedWorktreePath = executionResult.worktreePath;
|
|
10634
|
-
const artifactPath =
|
|
11075
|
+
const artifactPath = join20(
|
|
10635
11076
|
resolvedWorktreePath,
|
|
10636
11077
|
".pourkit/final-review-artifact.json"
|
|
10637
11078
|
);
|
|
@@ -11215,7 +11656,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11215
11656
|
function runPrdRunValidateFinalReviewCommand(options) {
|
|
11216
11657
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
11217
11658
|
const checkoutBase = options.checkoutBase ?? prdRef;
|
|
11218
|
-
const artifactPath = options.artifactPath ? options.artifactPath :
|
|
11659
|
+
const artifactPath = options.artifactPath ? options.artifactPath : join20(options.repoRoot, ".pourkit", "final-review-artifact.json");
|
|
11219
11660
|
const artifact = parseFinalReviewArtifact(artifactPath);
|
|
11220
11661
|
if (!artifact.ok) {
|
|
11221
11662
|
return {
|
|
@@ -11523,7 +11964,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11523
11964
|
);
|
|
11524
11965
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11525
11966
|
}
|
|
11526
|
-
if (
|
|
11967
|
+
if (existsSync17(worktreePath)) {
|
|
11527
11968
|
diagnostics.push(
|
|
11528
11969
|
"Reconciliation worktree path exists but is not registered with git.",
|
|
11529
11970
|
`stale worktree path: ${worktreePath}`
|
|
@@ -11544,7 +11985,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11544
11985
|
);
|
|
11545
11986
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11546
11987
|
}
|
|
11547
|
-
|
|
11988
|
+
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
11548
11989
|
const addResult = spawnSync2(
|
|
11549
11990
|
"git",
|
|
11550
11991
|
["worktree", "add", worktreePath, reconciliationBranchName],
|
|
@@ -11599,7 +12040,7 @@ function buildReconciliationPrBody(options) {
|
|
|
11599
12040
|
function commitAndPushReconciliationChanges(options) {
|
|
11600
12041
|
const diagnostics = [];
|
|
11601
12042
|
const existingPaths = options.changedPlanningPaths.filter((p) => {
|
|
11602
|
-
return
|
|
12043
|
+
return existsSync17(join20(options.worktreeCwd, p));
|
|
11603
12044
|
});
|
|
11604
12045
|
if (existingPaths.length === 0) {
|
|
11605
12046
|
diagnostics.push(
|
|
@@ -11742,7 +12183,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
11742
12183
|
const record = preflightResult.record;
|
|
11743
12184
|
const prdBranch = record?.prdBranch ?? prdRef;
|
|
11744
12185
|
const mergeBase = preflightResult.mergeBase;
|
|
11745
|
-
const worktreeCwd = preflightResult.worktreePath ?
|
|
12186
|
+
const worktreeCwd = preflightResult.worktreePath ? join20(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
|
|
11746
12187
|
const staleSucceededRecovery = assessStaleSucceededReconciliationReceipt({
|
|
11747
12188
|
receipt: record?.reconciliation,
|
|
11748
12189
|
worktreeCwd,
|
|
@@ -12044,7 +12485,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12044
12485
|
baseRef: prdBranch,
|
|
12045
12486
|
checkoutBase,
|
|
12046
12487
|
reviewBase: mergeBase,
|
|
12047
|
-
worktreePath:
|
|
12488
|
+
worktreePath: join20(options.repoRoot, preflightResult.worktreePath),
|
|
12048
12489
|
sandbox: options.config.sandbox,
|
|
12049
12490
|
artifactPath,
|
|
12050
12491
|
logger: options.logger
|
|
@@ -12145,7 +12586,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12145
12586
|
offendingPaths: []
|
|
12146
12587
|
};
|
|
12147
12588
|
}
|
|
12148
|
-
const fullArtifactPath =
|
|
12589
|
+
const fullArtifactPath = join20(executionResult.worktreePath, artifactPath);
|
|
12149
12590
|
let artifactContent;
|
|
12150
12591
|
try {
|
|
12151
12592
|
artifactContent = JSON.parse(retryResult.artifact.value);
|
|
@@ -12267,11 +12708,27 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12267
12708
|
gitStatusShortLines,
|
|
12268
12709
|
gitDiffNameStatusLines
|
|
12269
12710
|
});
|
|
12711
|
+
const observedWorktreeDiff = collectObservedReconciliationDirtyPaths({
|
|
12712
|
+
worktreeCwd,
|
|
12713
|
+
ignoredPrdRunRecordRef: prdRef
|
|
12714
|
+
});
|
|
12270
12715
|
let outcomeResult;
|
|
12271
12716
|
let outcomeDiagnostics = [];
|
|
12272
12717
|
let outcomeChangedPaths = [];
|
|
12273
12718
|
let outcomeRationale;
|
|
12274
|
-
if (
|
|
12719
|
+
if (!observedWorktreeDiff.ok) {
|
|
12720
|
+
outcomeResult = "blocked";
|
|
12721
|
+
outcomeDiagnostics.push(...observedWorktreeDiff.diagnostics);
|
|
12722
|
+
} else if (observedWorktreeDiff.dirtyPaths.length > 0) {
|
|
12723
|
+
outcomeResult = "changes_produced";
|
|
12724
|
+
outcomeChangedPaths = [...observedWorktreeDiff.dirtyPaths];
|
|
12725
|
+
outcomeRationale = void 0;
|
|
12726
|
+
if (artifactResult === "no_changes_needed") {
|
|
12727
|
+
outcomeDiagnostics.push(
|
|
12728
|
+
"Dirty reconciliation worktree observed after agent run; treating reconciliation result as changes_produced."
|
|
12729
|
+
);
|
|
12730
|
+
}
|
|
12731
|
+
} else if (artifactResult === "no_changes_needed") {
|
|
12275
12732
|
outcomeResult = "no_changes_needed";
|
|
12276
12733
|
outcomeChangedPaths = [];
|
|
12277
12734
|
outcomeRationale = artifactNoChangeRationale ?? "No changes needed.";
|
|
@@ -12292,11 +12749,11 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12292
12749
|
}
|
|
12293
12750
|
if (staleSucceededRecovery.mode === "recoverable" && artifactResult === "no_changes_needed") {
|
|
12294
12751
|
outcomeResult = "changes_produced";
|
|
12295
|
-
outcomeChangedPaths = [...staleSucceededRecovery.
|
|
12752
|
+
outcomeChangedPaths = [...staleSucceededRecovery.dirtyPaths];
|
|
12296
12753
|
outcomeRationale = void 0;
|
|
12297
12754
|
outcomeDiagnostics.push(...staleSucceededRecovery.diagnostics);
|
|
12298
12755
|
}
|
|
12299
|
-
if (!pathValidation.ok && outcomeResult !== "blocked") {
|
|
12756
|
+
if (!pathValidation.ok && outcomeResult !== "blocked" && observedWorktreeDiff.ok && observedWorktreeDiff.dirtyPaths.length === 0) {
|
|
12300
12757
|
outcomeResult = "blocked";
|
|
12301
12758
|
outcomeDiagnostics.push(
|
|
12302
12759
|
`Unsafe changed planning paths: ${(pathValidation.rejected ?? []).join(", ")}`
|
|
@@ -13156,26 +13613,26 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13156
13613
|
const blockerBugs = [];
|
|
13157
13614
|
const blockersFixed = [];
|
|
13158
13615
|
const nonBlockers = [];
|
|
13159
|
-
const prdMirrorPath =
|
|
13160
|
-
const completionsDir =
|
|
13616
|
+
const prdMirrorPath = join20(repoRoot2, PRD_047_PRD_MIRROR_PATH);
|
|
13617
|
+
const completionsDir = join20(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
|
|
13161
13618
|
let escapeHatchPresent = false;
|
|
13162
|
-
if (
|
|
13619
|
+
if (existsSync17(completionsDir)) {
|
|
13163
13620
|
const files = readdirSync6(completionsDir);
|
|
13164
13621
|
escapeHatchPresent = files.some(
|
|
13165
13622
|
(f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
|
|
13166
13623
|
);
|
|
13167
13624
|
}
|
|
13168
13625
|
if (!escapeHatchPresent) {
|
|
13169
|
-
escapeHatchPresent =
|
|
13626
|
+
escapeHatchPresent = existsSync17(join20(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
|
|
13170
13627
|
}
|
|
13171
|
-
const prdMirrorExists =
|
|
13628
|
+
const prdMirrorExists = existsSync17(prdMirrorPath);
|
|
13172
13629
|
if (!prdMirrorExists) {
|
|
13173
13630
|
nonBlockers.push(
|
|
13174
13631
|
`PRD-0047 PRD mirror not found at ${PRD_047_PRD_MIRROR_PATH}. Cannot verify scope alignment from published mirrors.`
|
|
13175
13632
|
);
|
|
13176
13633
|
}
|
|
13177
13634
|
if (prdMirrorExists) {
|
|
13178
|
-
const mirrorContent =
|
|
13635
|
+
const mirrorContent = readFileSync16(prdMirrorPath, "utf-8");
|
|
13179
13636
|
const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
|
|
13180
13637
|
const hasEscapeHatch = mirrorContent.includes("escape hatch");
|
|
13181
13638
|
if (!hasReconcileCommand) {
|
|
@@ -13189,16 +13646,16 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13189
13646
|
);
|
|
13190
13647
|
}
|
|
13191
13648
|
}
|
|
13192
|
-
const childIssueDir =
|
|
13193
|
-
const childIssuesExist =
|
|
13649
|
+
const childIssueDir = join20(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
|
|
13650
|
+
const childIssuesExist = existsSync17(childIssueDir);
|
|
13194
13651
|
if (!childIssuesExist) {
|
|
13195
13652
|
nonBlockers.push(
|
|
13196
13653
|
`PRD-0047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
|
|
13197
13654
|
);
|
|
13198
13655
|
}
|
|
13199
|
-
const manifestPath =
|
|
13200
|
-
if (
|
|
13201
|
-
const manifestContent =
|
|
13656
|
+
const manifestPath = join20(repoRoot2, PRD_047_MANIFEST_PATH);
|
|
13657
|
+
if (existsSync17(manifestPath)) {
|
|
13658
|
+
const manifestContent = readFileSync16(manifestPath, "utf-8");
|
|
13202
13659
|
if (!manifestContent.includes("ready_for_prepare")) {
|
|
13203
13660
|
nonBlockers.push(
|
|
13204
13661
|
"Planning Artifact Manifest is not marked as ready_for_prepare."
|
|
@@ -13446,13 +13903,13 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13446
13903
|
let finalReviewResult;
|
|
13447
13904
|
if (!skipped.includes("final-review")) {
|
|
13448
13905
|
attempted.push("final-review");
|
|
13449
|
-
const localStorePath =
|
|
13906
|
+
const localStorePath = join20(
|
|
13450
13907
|
options.repoRoot,
|
|
13451
13908
|
".pourkit",
|
|
13452
13909
|
"local-prd-runs",
|
|
13453
13910
|
prdRef
|
|
13454
13911
|
);
|
|
13455
|
-
if (
|
|
13912
|
+
if (existsSync17(localStorePath)) {
|
|
13456
13913
|
const targetConfig = options.config?.targets?.find(
|
|
13457
13914
|
(t) => t.name === options.targetName
|
|
13458
13915
|
);
|
|
@@ -14751,6 +15208,13 @@ async function processStartResult(startResult, options) {
|
|
|
14751
15208
|
start.queueStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14752
15209
|
start.queueCommand = "queue-run";
|
|
14753
15210
|
const existingRecord = readPrdRun(repoRoot2, prdRef);
|
|
15211
|
+
let resolvedMode;
|
|
15212
|
+
try {
|
|
15213
|
+
const target = options.config ? resolveTarget(options.config, start.targetName) : null;
|
|
15214
|
+
resolvedMode = target?.strategy ? resolvePrdRunMode(target) : void 0;
|
|
15215
|
+
} catch {
|
|
15216
|
+
resolvedMode = void 0;
|
|
15217
|
+
}
|
|
14754
15218
|
writePrdRunRecord(repoRoot2, {
|
|
14755
15219
|
prdRef,
|
|
14756
15220
|
status: "running",
|
|
@@ -14760,8 +15224,8 @@ async function processStartResult(startResult, options) {
|
|
|
14760
15224
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14761
15225
|
planning: existingRecord.record?.planning
|
|
14762
15226
|
});
|
|
14763
|
-
const localStorePath =
|
|
14764
|
-
if (
|
|
15227
|
+
const localStorePath = join20(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
|
|
15228
|
+
if (existsSync17(localStorePath)) {
|
|
14765
15229
|
const localIssues = await getRunnableLocalIssues(prdRef, repoRoot2);
|
|
14766
15230
|
start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14767
15231
|
start.queueProcessedCount = localIssues.length;
|
|
@@ -14795,7 +15259,8 @@ async function processStartResult(startResult, options) {
|
|
|
14795
15259
|
prdRef,
|
|
14796
15260
|
queueRunContext: {
|
|
14797
15261
|
prdRef,
|
|
14798
|
-
prdBranch: start.prdBranch
|
|
15262
|
+
prdBranch: start.prdBranch,
|
|
15263
|
+
prdRunMode: resolvedMode
|
|
14799
15264
|
}
|
|
14800
15265
|
});
|
|
14801
15266
|
if (outcome.selected === null && outcome.code === "drained") {
|
|
@@ -15340,7 +15805,7 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15340
15805
|
if (remoteResult.status !== 0) {
|
|
15341
15806
|
return;
|
|
15342
15807
|
}
|
|
15343
|
-
const worktreePath = mkdtempSync(
|
|
15808
|
+
const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-planning-"));
|
|
15344
15809
|
try {
|
|
15345
15810
|
runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
|
|
15346
15811
|
runGitOrThrow(
|
|
@@ -15354,9 +15819,9 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15354
15819
|
"create planning branch"
|
|
15355
15820
|
);
|
|
15356
15821
|
for (const changedFile of changedFiles) {
|
|
15357
|
-
const sourcePath =
|
|
15358
|
-
const targetPath =
|
|
15359
|
-
|
|
15822
|
+
const sourcePath = join20(repoRoot2, changedFile);
|
|
15823
|
+
const targetPath = join20(worktreePath, changedFile);
|
|
15824
|
+
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
15360
15825
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
15361
15826
|
}
|
|
15362
15827
|
runGitOrThrow(
|
|
@@ -15592,7 +16057,7 @@ function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
|
|
|
15592
16057
|
changedFiles: Array.from(new Set(changedFiles))
|
|
15593
16058
|
};
|
|
15594
16059
|
}
|
|
15595
|
-
function
|
|
16060
|
+
function collectObservedReconciliationDirtyPaths(options) {
|
|
15596
16061
|
const statusResult = spawnSync2("git", ["status", "--porcelain=v1", "-uall"], {
|
|
15597
16062
|
cwd: options.worktreeCwd,
|
|
15598
16063
|
encoding: "utf8"
|
|
@@ -15625,27 +16090,9 @@ function collectObservedReconciliationPlanningDiff(options) {
|
|
|
15625
16090
|
)
|
|
15626
16091
|
)
|
|
15627
16092
|
);
|
|
15628
|
-
const intrinsicallyRejectedPaths = candidatePaths.filter(
|
|
15629
|
-
(path9) => !isAllowedPlanningPath(path9) || isBlockedPlanningPath(path9) || hasDirectoryTraversal(path9)
|
|
15630
|
-
);
|
|
15631
|
-
const allowedCandidatePaths = candidatePaths.filter(
|
|
15632
|
-
(path9) => !intrinsicallyRejectedPaths.includes(path9)
|
|
15633
|
-
);
|
|
15634
|
-
const validation = validateChangedPlanningPaths(allowedCandidatePaths, {
|
|
15635
|
-
repoRoot: options.worktreeCwd,
|
|
15636
|
-
gitStatusShortLines,
|
|
15637
|
-
gitDiffNameStatusLines
|
|
15638
|
-
});
|
|
15639
|
-
const rejectedPaths = Array.from(
|
|
15640
|
-
/* @__PURE__ */ new Set([
|
|
15641
|
-
...intrinsicallyRejectedPaths,
|
|
15642
|
-
...validation.ok ? [] : validation.rejected ?? []
|
|
15643
|
-
])
|
|
15644
|
-
).sort();
|
|
15645
16093
|
return {
|
|
15646
16094
|
ok: true,
|
|
15647
|
-
|
|
15648
|
-
rejectedPaths
|
|
16095
|
+
dirtyPaths: candidatePaths.sort()
|
|
15649
16096
|
};
|
|
15650
16097
|
}
|
|
15651
16098
|
function gitRefExists(repoRoot2, ref) {
|
|
@@ -15670,7 +16117,7 @@ function parseGitStatusLine(line) {
|
|
|
15670
16117
|
return { status, path: path9.replace(/^"|"$/g, "") };
|
|
15671
16118
|
}
|
|
15672
16119
|
function validateManifestArtifactExistence(repoRoot2, manifest) {
|
|
15673
|
-
const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !
|
|
16120
|
+
const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !existsSync17(path9)).map((path9) => toRepoRelativePath2(repoRoot2, path9));
|
|
15674
16121
|
if (missingPaths.length > 0) {
|
|
15675
16122
|
return {
|
|
15676
16123
|
ok: false,
|
|
@@ -16086,7 +16533,7 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
|
|
|
16086
16533
|
}
|
|
16087
16534
|
|
|
16088
16535
|
// commands/init.ts
|
|
16089
|
-
import { existsSync as
|
|
16536
|
+
import { existsSync as existsSync18, statSync } from "fs";
|
|
16090
16537
|
import {
|
|
16091
16538
|
copyFile,
|
|
16092
16539
|
mkdir as mkdir5,
|
|
@@ -16589,7 +17036,7 @@ async function computeFileChecksum(filePath) {
|
|
|
16589
17036
|
return createHash3("sha256").update(content).digest("hex");
|
|
16590
17037
|
}
|
|
16591
17038
|
function lockfileExists(root, name) {
|
|
16592
|
-
return
|
|
17039
|
+
return existsSync18(path5.join(root, name));
|
|
16593
17040
|
}
|
|
16594
17041
|
function detectPackageManager(root) {
|
|
16595
17042
|
if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
|
|
@@ -16633,7 +17080,7 @@ async function discoverLocalSource(sourcePath) {
|
|
|
16633
17080
|
async function discoverReadme(root) {
|
|
16634
17081
|
for (const name of ["README.md", "readme.md"]) {
|
|
16635
17082
|
const p = path5.join(root, name);
|
|
16636
|
-
if (
|
|
17083
|
+
if (existsSync18(p)) {
|
|
16637
17084
|
return p;
|
|
16638
17085
|
}
|
|
16639
17086
|
}
|
|
@@ -16643,7 +17090,7 @@ async function discoverAgentFiles(root) {
|
|
|
16643
17090
|
const files = [];
|
|
16644
17091
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
16645
17092
|
const p = path5.join(root, name);
|
|
16646
|
-
if (
|
|
17093
|
+
if (existsSync18(p)) {
|
|
16647
17094
|
files.push(p);
|
|
16648
17095
|
}
|
|
16649
17096
|
}
|
|
@@ -16651,7 +17098,7 @@ async function discoverAgentFiles(root) {
|
|
|
16651
17098
|
}
|
|
16652
17099
|
async function discoverMerlleState(root) {
|
|
16653
17100
|
const p = path5.join(root, ".pourkit", "state.json");
|
|
16654
|
-
return
|
|
17101
|
+
return existsSync18(p) ? p : null;
|
|
16655
17102
|
}
|
|
16656
17103
|
async function discoverAgentSkills(root) {
|
|
16657
17104
|
const dirs = [
|
|
@@ -16660,7 +17107,7 @@ async function discoverAgentSkills(root) {
|
|
|
16660
17107
|
];
|
|
16661
17108
|
const found = [];
|
|
16662
17109
|
for (const d of dirs) {
|
|
16663
|
-
if (
|
|
17110
|
+
if (existsSync18(d)) {
|
|
16664
17111
|
found.push(d);
|
|
16665
17112
|
}
|
|
16666
17113
|
}
|
|
@@ -16670,12 +17117,12 @@ async function discoverRootDomainDocs(root) {
|
|
|
16670
17117
|
const docs = [];
|
|
16671
17118
|
for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
|
|
16672
17119
|
const p = path5.join(root, name);
|
|
16673
|
-
if (
|
|
17120
|
+
if (existsSync18(p)) {
|
|
16674
17121
|
docs.push(p);
|
|
16675
17122
|
}
|
|
16676
17123
|
}
|
|
16677
17124
|
const adrDir = path5.join(root, "docs", "adr");
|
|
16678
|
-
if (
|
|
17125
|
+
if (existsSync18(adrDir)) {
|
|
16679
17126
|
const entries = await readdir(adrDir, { withFileTypes: true });
|
|
16680
17127
|
for (const entry of entries) {
|
|
16681
17128
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -16818,7 +17265,7 @@ async function planInit(options) {
|
|
|
16818
17265
|
for (const file of skillFiles) {
|
|
16819
17266
|
const relPath = path5.relative(s, file);
|
|
16820
17267
|
const destPath = path5.join(targetRoot, ".agents", "skills", relPath);
|
|
16821
|
-
if (!
|
|
17268
|
+
if (!existsSync18(destPath)) {
|
|
16822
17269
|
operations.push({
|
|
16823
17270
|
kind: "copy",
|
|
16824
17271
|
sourcePath: file,
|
|
@@ -16881,7 +17328,7 @@ async function planInit(options) {
|
|
|
16881
17328
|
});
|
|
16882
17329
|
}
|
|
16883
17330
|
if (sourceRoot) {
|
|
16884
|
-
if (!
|
|
17331
|
+
if (!existsSync18(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
|
|
16885
17332
|
warnings.push(
|
|
16886
17333
|
`--from-local path does not exist or is not a directory: ${sourceRoot}`
|
|
16887
17334
|
);
|
|
@@ -17000,7 +17447,7 @@ async function planInit(options) {
|
|
|
17000
17447
|
requiresConfirmation: false,
|
|
17001
17448
|
destructive: false
|
|
17002
17449
|
});
|
|
17003
|
-
} else if (
|
|
17450
|
+
} else if (existsSync18(destPath)) {
|
|
17004
17451
|
operations.push({
|
|
17005
17452
|
kind: "skip",
|
|
17006
17453
|
path: destPath,
|
|
@@ -17058,7 +17505,7 @@ async function planInit(options) {
|
|
|
17058
17505
|
}
|
|
17059
17506
|
}
|
|
17060
17507
|
const contextPath = path5.join(targetRoot, ".pourkit", "CONTEXT.md");
|
|
17061
|
-
if (!
|
|
17508
|
+
if (!existsSync18(contextPath) && !merleDestPaths.has(contextPath)) {
|
|
17062
17509
|
operations.push({
|
|
17063
17510
|
kind: "create",
|
|
17064
17511
|
path: contextPath,
|
|
@@ -17076,7 +17523,7 @@ async function planInit(options) {
|
|
|
17076
17523
|
"adr",
|
|
17077
17524
|
".gitkeep"
|
|
17078
17525
|
);
|
|
17079
|
-
if (!
|
|
17526
|
+
if (!existsSync18(adrGitkeep)) {
|
|
17080
17527
|
operations.push({
|
|
17081
17528
|
kind: "create",
|
|
17082
17529
|
path: adrGitkeep,
|
|
@@ -17088,7 +17535,7 @@ async function planInit(options) {
|
|
|
17088
17535
|
}
|
|
17089
17536
|
const srcDocAgents = path5.join(sourceRoot, ".pourkit", "docs", "agents");
|
|
17090
17537
|
const tgtDocAgents = path5.join(targetRoot, ".pourkit", "docs", "agents");
|
|
17091
|
-
if (
|
|
17538
|
+
if (existsSync18(srcDocAgents) && !existsSync18(tgtDocAgents)) {
|
|
17092
17539
|
const docFiles = await walkDir(srcDocAgents);
|
|
17093
17540
|
for (const file of docFiles) {
|
|
17094
17541
|
const relPath = path5.relative(srcDocAgents, file);
|
|
@@ -17121,7 +17568,7 @@ async function planInit(options) {
|
|
|
17121
17568
|
}
|
|
17122
17569
|
const srcPrompts = path5.join(sourceRoot, ".pourkit", "prompts");
|
|
17123
17570
|
const tgtPrompts = path5.join(targetRoot, ".pourkit", "prompts");
|
|
17124
|
-
if (
|
|
17571
|
+
if (existsSync18(srcPrompts) && !existsSync18(tgtPrompts)) {
|
|
17125
17572
|
const promptFiles = await walkDir(srcPrompts);
|
|
17126
17573
|
for (const file of promptFiles) {
|
|
17127
17574
|
const relPath = path5.relative(srcPrompts, file);
|
|
@@ -17148,7 +17595,7 @@ async function planInit(options) {
|
|
|
17148
17595
|
".sandcastle",
|
|
17149
17596
|
"Dockerfile"
|
|
17150
17597
|
);
|
|
17151
|
-
if (
|
|
17598
|
+
if (existsSync18(tgtSandboxDockerfile)) {
|
|
17152
17599
|
operations.push({
|
|
17153
17600
|
kind: "skip",
|
|
17154
17601
|
path: tgtSandboxDockerfile,
|
|
@@ -17157,7 +17604,7 @@ async function planInit(options) {
|
|
|
17157
17604
|
requiresConfirmation: false,
|
|
17158
17605
|
destructive: false
|
|
17159
17606
|
});
|
|
17160
|
-
} else if (
|
|
17607
|
+
} else if (existsSync18(srcSandboxDockerfile)) {
|
|
17161
17608
|
const checksum = await computeFileChecksum(srcSandboxDockerfile);
|
|
17162
17609
|
operations.push({
|
|
17163
17610
|
kind: "copy",
|
|
@@ -17171,7 +17618,7 @@ async function planInit(options) {
|
|
|
17171
17618
|
});
|
|
17172
17619
|
}
|
|
17173
17620
|
const configTsPath = path5.join(targetRoot, "pourkit.config.ts");
|
|
17174
|
-
if (!
|
|
17621
|
+
if (!existsSync18(configTsPath)) {
|
|
17175
17622
|
const verifyCommands = inferVerificationCommands(
|
|
17176
17623
|
packageScripts,
|
|
17177
17624
|
pm || "npm"
|
|
@@ -17208,7 +17655,7 @@ async function planInit(options) {
|
|
|
17208
17655
|
const hasExistingAgents = operations.some(
|
|
17209
17656
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
|
|
17210
17657
|
);
|
|
17211
|
-
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !
|
|
17658
|
+
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync18(path5.join(targetRoot, "AGENTS.md"))) {
|
|
17212
17659
|
operations.push({
|
|
17213
17660
|
kind: "create",
|
|
17214
17661
|
path: path5.join(targetRoot, "AGENTS.md"),
|
|
@@ -17224,7 +17671,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
17224
17671
|
const hasExistingClaude = operations.some(
|
|
17225
17672
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
|
|
17226
17673
|
);
|
|
17227
|
-
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !
|
|
17674
|
+
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync18(path5.join(targetRoot, "CLAUDE.md"))) {
|
|
17228
17675
|
operations.push({
|
|
17229
17676
|
kind: "create",
|
|
17230
17677
|
path: path5.join(targetRoot, "CLAUDE.md"),
|
|
@@ -17239,7 +17686,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
17239
17686
|
}
|
|
17240
17687
|
const gitignoreTarget = path5.join(targetRoot, ".gitignore");
|
|
17241
17688
|
const gitignoreContent = generateGitignoreBlock();
|
|
17242
|
-
if (!
|
|
17689
|
+
if (!existsSync18(gitignoreTarget)) {
|
|
17243
17690
|
operations.push({
|
|
17244
17691
|
kind: "create",
|
|
17245
17692
|
path: gitignoreTarget,
|
|
@@ -17263,7 +17710,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
17263
17710
|
});
|
|
17264
17711
|
}
|
|
17265
17712
|
const openCodePath = path5.join(targetRoot, "opencode.json");
|
|
17266
|
-
if (!
|
|
17713
|
+
if (!existsSync18(openCodePath)) {
|
|
17267
17714
|
operations.push({
|
|
17268
17715
|
kind: "create",
|
|
17269
17716
|
path: openCodePath,
|
|
@@ -17320,7 +17767,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
17320
17767
|
}
|
|
17321
17768
|
}
|
|
17322
17769
|
const manifestPath = path5.join(targetRoot, ".pourkit", "manifest.json");
|
|
17323
|
-
if (
|
|
17770
|
+
if (existsSync18(manifestPath)) {
|
|
17324
17771
|
operations.push({
|
|
17325
17772
|
kind: "skip",
|
|
17326
17773
|
path: manifestPath,
|
|
@@ -17639,7 +18086,7 @@ async function updateManagedBlock(filePath, content) {
|
|
|
17639
18086
|
const blockContent = `${MANAGED_BLOCK_BEGIN}
|
|
17640
18087
|
${content}${MANAGED_BLOCK_END}
|
|
17641
18088
|
`;
|
|
17642
|
-
if (!
|
|
18089
|
+
if (!existsSync18(filePath)) {
|
|
17643
18090
|
const dir = path5.dirname(filePath);
|
|
17644
18091
|
await mkdir5(dir, { recursive: true });
|
|
17645
18092
|
await writeFileAtomic(filePath, blockContent);
|
|
@@ -17668,7 +18115,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
|
17668
18115
|
if (op.requiresConfirmation) continue;
|
|
17669
18116
|
const relPath = path5.relative(plan.targetRoot, op.path);
|
|
17670
18117
|
if (relPath === ".pourkit/manifest.json") continue;
|
|
17671
|
-
if (
|
|
18118
|
+
if (existsSync18(op.path)) {
|
|
17672
18119
|
const sha256 = await computeFileChecksum(op.path);
|
|
17673
18120
|
assets[relPath] = {
|
|
17674
18121
|
ownership: op.ownership || "managed",
|
|
@@ -17713,7 +18160,7 @@ async function applyInitPlan(plan, options) {
|
|
|
17713
18160
|
skipped++;
|
|
17714
18161
|
continue;
|
|
17715
18162
|
}
|
|
17716
|
-
if (
|
|
18163
|
+
if (existsSync18(op.path) && !op.destructive) {
|
|
17717
18164
|
skipped++;
|
|
17718
18165
|
continue;
|
|
17719
18166
|
}
|
|
@@ -17728,7 +18175,7 @@ async function applyInitPlan(plan, options) {
|
|
|
17728
18175
|
skipped++;
|
|
17729
18176
|
continue;
|
|
17730
18177
|
}
|
|
17731
|
-
if (
|
|
18178
|
+
if (existsSync18(op.path)) {
|
|
17732
18179
|
skipped++;
|
|
17733
18180
|
continue;
|
|
17734
18181
|
}
|
|
@@ -17757,7 +18204,7 @@ async function applyInitPlan(plan, options) {
|
|
|
17757
18204
|
skipped++;
|
|
17758
18205
|
continue;
|
|
17759
18206
|
}
|
|
17760
|
-
if (
|
|
18207
|
+
if (existsSync18(op.path)) {
|
|
17761
18208
|
skipped++;
|
|
17762
18209
|
continue;
|
|
17763
18210
|
}
|
|
@@ -17897,7 +18344,7 @@ async function applyInitFromSource(options) {
|
|
|
17897
18344
|
if (!manifestSkipped) {
|
|
17898
18345
|
const agentFiles = [];
|
|
17899
18346
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
17900
|
-
if (
|
|
18347
|
+
if (existsSync18(path5.join(targetRoot, name))) {
|
|
17901
18348
|
agentFiles.push(path5.join(targetRoot, name));
|
|
17902
18349
|
}
|
|
17903
18350
|
}
|
|
@@ -18716,8 +19163,8 @@ function formatChecks2(checks) {
|
|
|
18716
19163
|
init_common();
|
|
18717
19164
|
|
|
18718
19165
|
// execution/sandcastle-execution.ts
|
|
18719
|
-
import { mkdirSync as
|
|
18720
|
-
import { join as
|
|
19166
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync8 } from "fs";
|
|
19167
|
+
import { join as join22 } from "path";
|
|
18721
19168
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
18722
19169
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
18723
19170
|
|
|
@@ -18726,10 +19173,10 @@ init_common();
|
|
|
18726
19173
|
import { mkdtempSync as mkdtempSync2 } from "fs";
|
|
18727
19174
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
18728
19175
|
import { tmpdir as tmpdir2 } from "os";
|
|
18729
|
-
import { dirname as dirname6, join as
|
|
19176
|
+
import { dirname as dirname6, join as join21 } from "path";
|
|
18730
19177
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
18731
19178
|
for (const artifact of artifacts) {
|
|
18732
|
-
const filePath =
|
|
19179
|
+
const filePath = join21(worktreePath, artifact.path);
|
|
18733
19180
|
await ensureDir(dirname6(filePath));
|
|
18734
19181
|
await writeFile3(filePath, artifact.content, "utf-8");
|
|
18735
19182
|
}
|
|
@@ -18741,17 +19188,17 @@ import path7 from "path";
|
|
|
18741
19188
|
|
|
18742
19189
|
// execution/sandbox-image.ts
|
|
18743
19190
|
import { createHash as createHash4 } from "crypto";
|
|
18744
|
-
import { existsSync as
|
|
19191
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
|
|
18745
19192
|
import path6 from "path";
|
|
18746
19193
|
function sandboxImageName(repoRoot2) {
|
|
18747
19194
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
18748
19195
|
const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
|
|
18749
19196
|
const baseName = sanitized || "local";
|
|
18750
19197
|
const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
|
|
18751
|
-
if (!
|
|
19198
|
+
if (!existsSync19(dockerfilePath)) {
|
|
18752
19199
|
return `sandcastle:${baseName}`;
|
|
18753
19200
|
}
|
|
18754
|
-
const fingerprint = createHash4("sha256").update(
|
|
19201
|
+
const fingerprint = createHash4("sha256").update(readFileSync17(dockerfilePath)).digest("hex").slice(0, 8);
|
|
18755
19202
|
return `sandcastle:${baseName}-${fingerprint}`;
|
|
18756
19203
|
}
|
|
18757
19204
|
|
|
@@ -18901,11 +19348,7 @@ var SandcastleExecutionSession = class {
|
|
|
18901
19348
|
type: "file",
|
|
18902
19349
|
path: logPath,
|
|
18903
19350
|
onAgentStreamEvent: (event) => {
|
|
18904
|
-
|
|
18905
|
-
logger.raw(event.message);
|
|
18906
|
-
} else if (event.type === "toolCall") {
|
|
18907
|
-
logger.raw(`${event.name}(${event.formattedArgs})`);
|
|
18908
|
-
}
|
|
19351
|
+
logger.raw(formatAgentStreamEvent(event));
|
|
18909
19352
|
}
|
|
18910
19353
|
},
|
|
18911
19354
|
completionSignal: "<promise>COMPLETE</promise>",
|
|
@@ -18990,14 +19433,59 @@ function resolveSandboxProvider(provider, dockerFactory) {
|
|
|
18990
19433
|
function sanitizeBranch(branchName) {
|
|
18991
19434
|
return branchName.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
18992
19435
|
}
|
|
19436
|
+
function formatAgentStreamEvent(event) {
|
|
19437
|
+
if (event.type === "text") {
|
|
19438
|
+
return JSON.stringify({ type: "text", textCount: event.message.length });
|
|
19439
|
+
}
|
|
19440
|
+
const parsedArgs = parseToolCallArgs(event.formattedArgs);
|
|
19441
|
+
if (!parsedArgs.ok) {
|
|
19442
|
+
return JSON.stringify({
|
|
19443
|
+
type: "toolCall",
|
|
19444
|
+
name: event.name,
|
|
19445
|
+
argsTextCount: event.formattedArgs.length
|
|
19446
|
+
});
|
|
19447
|
+
}
|
|
19448
|
+
return JSON.stringify({
|
|
19449
|
+
type: "toolCall",
|
|
19450
|
+
name: event.name,
|
|
19451
|
+
args: summarizeToolCallArgs(event.name, parsedArgs.value)
|
|
19452
|
+
});
|
|
19453
|
+
}
|
|
19454
|
+
function parseToolCallArgs(formattedArgs) {
|
|
19455
|
+
try {
|
|
19456
|
+
return { ok: true, value: JSON.parse(formattedArgs) };
|
|
19457
|
+
} catch {
|
|
19458
|
+
return { ok: false };
|
|
19459
|
+
}
|
|
19460
|
+
}
|
|
19461
|
+
function summarizeToolCallArgs(name, args) {
|
|
19462
|
+
if (!isPlainObject(args)) {
|
|
19463
|
+
return args;
|
|
19464
|
+
}
|
|
19465
|
+
if (name.toLowerCase() !== "write") {
|
|
19466
|
+
return args;
|
|
19467
|
+
}
|
|
19468
|
+
const summarizedArgs = {};
|
|
19469
|
+
for (const [key, value] of Object.entries(args)) {
|
|
19470
|
+
if (key === "content" && typeof value === "string") {
|
|
19471
|
+
summarizedArgs.contentCount = value.length;
|
|
19472
|
+
continue;
|
|
19473
|
+
}
|
|
19474
|
+
summarizedArgs[key] = value;
|
|
19475
|
+
}
|
|
19476
|
+
return summarizedArgs;
|
|
19477
|
+
}
|
|
19478
|
+
function isPlainObject(value) {
|
|
19479
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19480
|
+
}
|
|
18993
19481
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
18994
|
-
const promptsDir =
|
|
18995
|
-
|
|
19482
|
+
const promptsDir = join22(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
19483
|
+
mkdirSync12(promptsDir, { recursive: true });
|
|
18996
19484
|
const timestamp2 = Date.now();
|
|
18997
19485
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
18998
19486
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
18999
|
-
const filePath =
|
|
19000
|
-
|
|
19487
|
+
const filePath = join22(promptsDir, filename);
|
|
19488
|
+
writeFileSync8(filePath, prompt, "utf-8");
|
|
19001
19489
|
}
|
|
19002
19490
|
|
|
19003
19491
|
// cli.ts
|
|
@@ -19066,6 +19554,35 @@ function createCliProgram(version) {
|
|
|
19066
19554
|
const program = new Command();
|
|
19067
19555
|
program.name("pourkit").version(version).exitOverride().description("AI-driven issue-to-PR workflow for GitHub repositories.");
|
|
19068
19556
|
const issueCommand = program.command("issue");
|
|
19557
|
+
program.command("run-verification").description("Run configured verification commands for a target").option("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
19558
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
19559
|
+
const logPath = path8.join(
|
|
19560
|
+
targetRepoRoot,
|
|
19561
|
+
".pourkit",
|
|
19562
|
+
"logs",
|
|
19563
|
+
"run-verification.log"
|
|
19564
|
+
);
|
|
19565
|
+
const logger = createLogger("pourkit", logPath);
|
|
19566
|
+
let failed = false;
|
|
19567
|
+
try {
|
|
19568
|
+
const config = await loadRepoConfig(targetRepoRoot);
|
|
19569
|
+
const target = resolveTarget(config, options.target);
|
|
19570
|
+
const result = await runVerificationCommands({
|
|
19571
|
+
target,
|
|
19572
|
+
cwd: targetRepoRoot,
|
|
19573
|
+
logger
|
|
19574
|
+
});
|
|
19575
|
+
console.log(JSON.stringify(result, null, 2));
|
|
19576
|
+
failed = !result.ok;
|
|
19577
|
+
} catch (error) {
|
|
19578
|
+
await handleError(logger, error);
|
|
19579
|
+
} finally {
|
|
19580
|
+
await logger.close();
|
|
19581
|
+
}
|
|
19582
|
+
if (failed) {
|
|
19583
|
+
process.exit(1);
|
|
19584
|
+
}
|
|
19585
|
+
});
|
|
19069
19586
|
program.command("validate-artifact").description("Validate an agent handoff artifact").argument(
|
|
19070
19587
|
"<kind>",
|
|
19071
19588
|
"artifact kind: reviewer, refactor, finalizer, conflict-resolution, failure-resolution, reconciliation, planning-manifest, local-prd, local-issue, local-triage, or local-prepare"
|
|
@@ -19841,11 +20358,11 @@ function createCliProgram(version) {
|
|
|
19841
20358
|
return program;
|
|
19842
20359
|
}
|
|
19843
20360
|
async function resolveCliVersion() {
|
|
19844
|
-
if (isPackageVersion("0.0.0-next-
|
|
19845
|
-
return "0.0.0-next-
|
|
20361
|
+
if (isPackageVersion("0.0.0-next-20260608072322")) {
|
|
20362
|
+
return "0.0.0-next-20260608072322";
|
|
19846
20363
|
}
|
|
19847
|
-
if (isReleaseVersion("0.0.0-next-
|
|
19848
|
-
return "0.0.0-next-
|
|
20364
|
+
if (isReleaseVersion("0.0.0-next-20260608072322")) {
|
|
20365
|
+
return "0.0.0-next-20260608072322";
|
|
19849
20366
|
}
|
|
19850
20367
|
try {
|
|
19851
20368
|
const root = repoRoot();
|