@pourkit/cli 0.0.0-next-20260607223610 → 0.0.0-next-20260608221925
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 +1900 -572
- 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,28 @@ 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", targetName: target.name };
|
|
780
|
+
}
|
|
781
|
+
const configMode = target.strategy.prdRun?.mode;
|
|
782
|
+
if (configMode) {
|
|
783
|
+
return {
|
|
784
|
+
mode: configMode,
|
|
785
|
+
source: "target-config",
|
|
786
|
+
targetName: target.name
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
return { mode: "github", source: "default", targetName: target.name };
|
|
790
|
+
}
|
|
774
791
|
async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
|
|
775
|
-
const { existsSync:
|
|
792
|
+
const { existsSync: existsSync20 } = await import("fs");
|
|
776
793
|
const { mkdir: mkdir6, writeFile: writeFile4, rm } = await import("fs/promises");
|
|
777
794
|
const { join: pjoin, basename } = await import("path");
|
|
778
795
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
779
796
|
const { build } = await import("esbuild");
|
|
780
797
|
const configPath = pjoin(repoRoot2, configFileName);
|
|
781
|
-
if (!
|
|
798
|
+
if (!existsSync20(configPath)) {
|
|
782
799
|
throw new Error(
|
|
783
800
|
`No config file found at ${configPath}. Create a ${configFileName} that exports a default PourkitConfig.`
|
|
784
801
|
);
|
|
@@ -1142,6 +1159,77 @@ function prepareArtifactPath(artifactPath) {
|
|
|
1142
1159
|
rmSync(artifactPath, { recursive: true, force: true });
|
|
1143
1160
|
}
|
|
1144
1161
|
|
|
1162
|
+
// commands/run-verification.ts
|
|
1163
|
+
init_common();
|
|
1164
|
+
function buildRunVerificationCommand(target) {
|
|
1165
|
+
return `pourkit run-verification --target ${target.name}`;
|
|
1166
|
+
}
|
|
1167
|
+
async function runVerificationCommand(command, cwd, logger) {
|
|
1168
|
+
try {
|
|
1169
|
+
const result = await execCapture("bash", ["-lc", command.command], {
|
|
1170
|
+
cwd,
|
|
1171
|
+
logger,
|
|
1172
|
+
label: command.label
|
|
1173
|
+
});
|
|
1174
|
+
return {
|
|
1175
|
+
label: command.label,
|
|
1176
|
+
command: command.command,
|
|
1177
|
+
ok: true,
|
|
1178
|
+
stdout: result.stdout,
|
|
1179
|
+
stderr: result.stderr
|
|
1180
|
+
};
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
return {
|
|
1183
|
+
label: command.label,
|
|
1184
|
+
command: command.command,
|
|
1185
|
+
ok: false,
|
|
1186
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
async function runVerificationCommands(options) {
|
|
1191
|
+
const commands = getVerificationCommands(options.target);
|
|
1192
|
+
const diagnostics = [];
|
|
1193
|
+
if (commands.length === 0) {
|
|
1194
|
+
diagnostics.push(
|
|
1195
|
+
`No verification commands configured for target "${options.target.name}".`
|
|
1196
|
+
);
|
|
1197
|
+
return {
|
|
1198
|
+
ok: true,
|
|
1199
|
+
target: options.target.name,
|
|
1200
|
+
commands: [],
|
|
1201
|
+
diagnostics
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
const results = [];
|
|
1205
|
+
for (const command of commands) {
|
|
1206
|
+
const result = await runVerificationCommand(
|
|
1207
|
+
command,
|
|
1208
|
+
options.cwd,
|
|
1209
|
+
options.logger
|
|
1210
|
+
);
|
|
1211
|
+
results.push(result);
|
|
1212
|
+
if (!result.ok) {
|
|
1213
|
+
diagnostics.push(`Verification failed for "${command.label}".`);
|
|
1214
|
+
return {
|
|
1215
|
+
ok: false,
|
|
1216
|
+
target: options.target.name,
|
|
1217
|
+
commands: results,
|
|
1218
|
+
diagnostics
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
diagnostics.push(
|
|
1223
|
+
`Verification passed for target "${options.target.name}".`
|
|
1224
|
+
);
|
|
1225
|
+
return {
|
|
1226
|
+
ok: true,
|
|
1227
|
+
target: options.target.name,
|
|
1228
|
+
commands: results,
|
|
1229
|
+
diagnostics
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1145
1233
|
// shared/run-context.ts
|
|
1146
1234
|
var RUN_CONTEXT_PATH_IN_WORKTREE = ".pourkit/.tmp/run-context.md";
|
|
1147
1235
|
var ALL_RUN_CONTEXT_SECTIONS = [
|
|
@@ -1268,6 +1356,7 @@ function buildRunContextMarkdown(options) {
|
|
|
1268
1356
|
if (sections.includes("verification-commands")) {
|
|
1269
1357
|
parts.push(
|
|
1270
1358
|
...renderCommandList(
|
|
1359
|
+
target,
|
|
1271
1360
|
getVerificationCommands(target),
|
|
1272
1361
|
"Verification Commands"
|
|
1273
1362
|
)
|
|
@@ -1289,13 +1378,26 @@ function buildRunContextMarkdown(options) {
|
|
|
1289
1378
|
}
|
|
1290
1379
|
return parts.join("\n");
|
|
1291
1380
|
}
|
|
1292
|
-
function renderCommandList(commands, heading) {
|
|
1381
|
+
function renderCommandList(target, commands, heading) {
|
|
1293
1382
|
if (commands.length === 0) {
|
|
1294
|
-
return [
|
|
1383
|
+
return [
|
|
1384
|
+
`## ${heading}`,
|
|
1385
|
+
"",
|
|
1386
|
+
`Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
|
|
1387
|
+
"",
|
|
1388
|
+
"Underlying project commands:",
|
|
1389
|
+
"",
|
|
1390
|
+
"(none configured)",
|
|
1391
|
+
""
|
|
1392
|
+
];
|
|
1295
1393
|
}
|
|
1296
1394
|
return [
|
|
1297
1395
|
`## ${heading}`,
|
|
1298
1396
|
"",
|
|
1397
|
+
`Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
|
|
1398
|
+
"",
|
|
1399
|
+
"Underlying project commands:",
|
|
1400
|
+
"",
|
|
1299
1401
|
"Run these commands from the repository root exactly as written. Do not substitute equivalent scripts from nested package.json files.",
|
|
1300
1402
|
"",
|
|
1301
1403
|
...commands.map((command) => `- ${command.label}: \`${command.command}\``),
|
|
@@ -2079,7 +2181,7 @@ ${criteriaBlock}
|
|
|
2079
2181
|
|
|
2080
2182
|
Write your review to: ${artifactPathInWorktree}
|
|
2081
2183
|
|
|
2082
|
-
Before handoff, run:
|
|
2184
|
+
Before handoff, run: pourkit validate-artifact reviewer ${artifactPathInWorktree} --iteration ${iteration}${priorRefactorArtifacts ? " --prior-refactor-artifacts" : ""}
|
|
2083
2185
|
|
|
2084
2186
|
Do not provide a separate chat response. The runner only reads the file above.
|
|
2085
2187
|
|
|
@@ -2615,7 +2717,7 @@ ${latestReview.trimEnd()}
|
|
|
2615
2717
|
|
|
2616
2718
|
Write your refactor artifact to: ${artifactPathInWorktree}
|
|
2617
2719
|
|
|
2618
|
-
Before handoff, run:
|
|
2720
|
+
Before handoff, run: pourkit validate-artifact refactor ${artifactPathInWorktree} --iteration ${iteration}${findingArgs}
|
|
2619
2721
|
|
|
2620
2722
|
When you are done, finish with <promise>COMPLETE</promise>.`);
|
|
2621
2723
|
});
|
|
@@ -4879,8 +4981,8 @@ function validateLocalTriage(prdPath, issuePaths) {
|
|
|
4879
4981
|
}
|
|
4880
4982
|
|
|
4881
4983
|
// commands/issue-run.ts
|
|
4882
|
-
import { existsSync as
|
|
4883
|
-
import { join as
|
|
4984
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
|
|
4985
|
+
import { join as join14 } from "path";
|
|
4884
4986
|
|
|
4885
4987
|
// pr/templates.ts
|
|
4886
4988
|
init_common();
|
|
@@ -5581,7 +5683,7 @@ async function runFailureResolutionAgent(options) {
|
|
|
5581
5683
|
"",
|
|
5582
5684
|
"Allowed decisions: " + packet.allowedDecisions.join(", "),
|
|
5583
5685
|
"",
|
|
5584
|
-
`Before handoff, run:
|
|
5686
|
+
`Before handoff, run: pourkit validate-artifact failure-resolution ${artifactPath} ${packet.allowedDecisions.map((decision) => `--allowed-decision ${decision}`).join(" ")}`
|
|
5585
5687
|
].join("\n");
|
|
5586
5688
|
const retryResult = await executeWithMissingOrEmptyArtifactRetry({
|
|
5587
5689
|
executionProvider,
|
|
@@ -5853,7 +5955,7 @@ Rules:
|
|
|
5853
5955
|
|
|
5854
5956
|
Write your finalizer output to: ${artifactPathInWorktree}
|
|
5855
5957
|
|
|
5856
|
-
Before handoff, run:
|
|
5958
|
+
Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktree}`;
|
|
5857
5959
|
}
|
|
5858
5960
|
|
|
5859
5961
|
// commands/pr-description-agent.ts
|
|
@@ -6054,110 +6156,444 @@ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
|
6054
6156
|
});
|
|
6055
6157
|
}
|
|
6056
6158
|
|
|
6057
|
-
//
|
|
6058
|
-
import {
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
- Summarize why this branch exists.
|
|
6062
|
-
|
|
6063
|
-
## Changes
|
|
6159
|
+
// prd-run/local-merge-coordinator.ts
|
|
6160
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
6161
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
6162
|
+
import { join as join13 } from "path";
|
|
6064
6163
|
|
|
6065
|
-
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
bodyText = appendClosingRef(bodyText, options.issue);
|
|
6070
|
-
return bodyText;
|
|
6164
|
+
// prd-run/local-branches.ts
|
|
6165
|
+
import { execFileSync, spawnSync as spawnSync2 } from "child_process";
|
|
6166
|
+
function getLocalPrdBranchName(prdId) {
|
|
6167
|
+
return `local/${prdId}`;
|
|
6071
6168
|
}
|
|
6072
|
-
function
|
|
6073
|
-
const
|
|
6074
|
-
|
|
6075
|
-
|
|
6169
|
+
function materializeLocalPrdBranch(prdId, startBaseCommit, repoRoot2) {
|
|
6170
|
+
const branch = getLocalPrdBranchName(prdId);
|
|
6171
|
+
const root = repoRoot2 ?? process.cwd();
|
|
6172
|
+
const branchExists = (() => {
|
|
6173
|
+
try {
|
|
6174
|
+
return spawnSync2("git", ["rev-parse", "--verify", "--quiet", branch], {
|
|
6175
|
+
cwd: root,
|
|
6176
|
+
encoding: "utf8",
|
|
6177
|
+
stdio: "pipe"
|
|
6178
|
+
}).status === 0;
|
|
6179
|
+
} catch {
|
|
6180
|
+
return false;
|
|
6181
|
+
}
|
|
6182
|
+
})();
|
|
6183
|
+
if (branchExists) {
|
|
6184
|
+
const currentCommit = spawnSync2("git", ["rev-parse", branch], {
|
|
6185
|
+
cwd: root,
|
|
6186
|
+
encoding: "utf8",
|
|
6187
|
+
stdio: "pipe"
|
|
6188
|
+
}).stdout?.toString?.().trim() ?? "";
|
|
6189
|
+
if (currentCommit !== startBaseCommit) {
|
|
6190
|
+
return {
|
|
6191
|
+
ok: false,
|
|
6192
|
+
failureCode: "branch_commit_mismatch",
|
|
6193
|
+
message: `Local PRD branch ${branch} exists at commit ${currentCommit} but expected commit ${startBaseCommit}. The branch has diverged from the start base. To resolve, delete the local branch and rerun start, or manually reset it to the expected commit.`
|
|
6194
|
+
};
|
|
6195
|
+
}
|
|
6196
|
+
return {
|
|
6197
|
+
ok: true,
|
|
6198
|
+
created: false
|
|
6199
|
+
};
|
|
6076
6200
|
}
|
|
6077
|
-
|
|
6078
|
-
|
|
6201
|
+
const branchResult = spawnSync2("git", ["branch", branch, startBaseCommit], {
|
|
6202
|
+
cwd: root,
|
|
6203
|
+
encoding: "utf8",
|
|
6204
|
+
stdio: "pipe"
|
|
6205
|
+
});
|
|
6206
|
+
if (branchResult.status !== 0) {
|
|
6207
|
+
const stderr = branchResult.stderr?.toString?.() ?? "unknown error";
|
|
6208
|
+
return {
|
|
6209
|
+
ok: false,
|
|
6210
|
+
failureCode: "branch_creation_failed",
|
|
6211
|
+
message: `Failed to create local PRD branch ${branch} at ${startBaseCommit}: ${stderr}`
|
|
6212
|
+
};
|
|
6079
6213
|
}
|
|
6080
|
-
return
|
|
6081
|
-
|
|
6082
|
-
Closes #${issue}`;
|
|
6083
|
-
}
|
|
6084
|
-
function stripClosingRefs(body) {
|
|
6085
|
-
const linePattern = /^[ \t]*[-*+]\s+(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s*:?\s*#\d+\s*$/gim;
|
|
6086
|
-
let result = body.replace(linePattern, "");
|
|
6087
|
-
const inlinePattern = /(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s*:?\s*#\d+/gi;
|
|
6088
|
-
result = result.replace(inlinePattern, "").trimEnd();
|
|
6089
|
-
return result;
|
|
6214
|
+
return { ok: true, created: true };
|
|
6090
6215
|
}
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6216
|
+
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
|
|
6217
|
+
var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
|
|
6218
|
+
function validateLocalBranchName(name) {
|
|
6219
|
+
if (!name || name.length === 0) {
|
|
6220
|
+
return {
|
|
6221
|
+
ok: false,
|
|
6222
|
+
failureCode: "invalid_format",
|
|
6223
|
+
message: "Branch name is empty."
|
|
6224
|
+
};
|
|
6094
6225
|
}
|
|
6095
|
-
if (
|
|
6096
|
-
return
|
|
6226
|
+
if (isProtectedBranch(name)) {
|
|
6227
|
+
return {
|
|
6228
|
+
ok: false,
|
|
6229
|
+
failureCode: "protected_branch",
|
|
6230
|
+
message: `Branch "${name}" is protected.`
|
|
6231
|
+
};
|
|
6097
6232
|
}
|
|
6098
|
-
if (
|
|
6099
|
-
return
|
|
6233
|
+
if (!LOCAL_BRANCH_PATTERN.test(name)) {
|
|
6234
|
+
return {
|
|
6235
|
+
ok: false,
|
|
6236
|
+
failureCode: "invalid_format",
|
|
6237
|
+
message: `Branch "${name}" does not match the local branch pattern.`
|
|
6238
|
+
};
|
|
6100
6239
|
}
|
|
6101
|
-
return
|
|
6240
|
+
return { ok: true };
|
|
6102
6241
|
}
|
|
6103
|
-
function
|
|
6104
|
-
|
|
6105
|
-
|
|
6242
|
+
function isProtectedBranch(name) {
|
|
6243
|
+
return PROTECTED_BRANCHES.has(name);
|
|
6244
|
+
}
|
|
6245
|
+
function hasRemoteBackedCollision(localName, repoRoot2) {
|
|
6246
|
+
const match = localName.match(/^local\/(PRD-\d{4})/);
|
|
6247
|
+
if (!match) {
|
|
6248
|
+
return Promise.resolve({
|
|
6249
|
+
ok: false,
|
|
6250
|
+
failureCode: "invalid_format",
|
|
6251
|
+
message: `Cannot extract PRD ref from "${localName}".`
|
|
6252
|
+
});
|
|
6106
6253
|
}
|
|
6107
|
-
const
|
|
6108
|
-
|
|
6109
|
-
|
|
6254
|
+
const prdRef = match[1];
|
|
6255
|
+
try {
|
|
6256
|
+
execFileSync(
|
|
6257
|
+
"git",
|
|
6258
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${prdRef}`],
|
|
6259
|
+
{
|
|
6260
|
+
cwd: repoRoot2 ?? process.cwd(),
|
|
6261
|
+
encoding: "utf8",
|
|
6262
|
+
stdio: "pipe"
|
|
6263
|
+
}
|
|
6264
|
+
);
|
|
6265
|
+
return Promise.resolve({
|
|
6266
|
+
ok: false,
|
|
6267
|
+
failureCode: "remote_backed_collision",
|
|
6268
|
+
message: `Branch "${prdRef}" exists locally.`
|
|
6269
|
+
});
|
|
6270
|
+
} catch {
|
|
6110
6271
|
}
|
|
6111
|
-
|
|
6272
|
+
try {
|
|
6273
|
+
execFileSync(
|
|
6274
|
+
"git",
|
|
6275
|
+
["show-ref", "--verify", "--quiet", `refs/remotes/origin/${prdRef}`],
|
|
6276
|
+
{
|
|
6277
|
+
cwd: repoRoot2 ?? process.cwd(),
|
|
6278
|
+
encoding: "utf8",
|
|
6279
|
+
stdio: "pipe"
|
|
6280
|
+
}
|
|
6281
|
+
);
|
|
6282
|
+
return Promise.resolve({
|
|
6283
|
+
ok: false,
|
|
6284
|
+
failureCode: "remote_backed_collision",
|
|
6285
|
+
message: `Remote branch "origin/${prdRef}" exists.`
|
|
6286
|
+
});
|
|
6287
|
+
} catch {
|
|
6288
|
+
}
|
|
6289
|
+
return Promise.resolve({ ok: true });
|
|
6290
|
+
}
|
|
6112
6291
|
|
|
6113
|
-
|
|
6292
|
+
// prd-run/local-merge-coordinator.ts
|
|
6293
|
+
function getLocalStorePath(repoRoot2, prdId) {
|
|
6294
|
+
return join13(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
6114
6295
|
}
|
|
6115
|
-
function
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6296
|
+
function getMergeReceiptPath(repoRoot2, prdId, issueId) {
|
|
6297
|
+
return join13(
|
|
6298
|
+
getLocalStorePath(repoRoot2, prdId),
|
|
6299
|
+
"merge-receipts",
|
|
6300
|
+
`${issueId}.json`
|
|
6301
|
+
);
|
|
6302
|
+
}
|
|
6303
|
+
function getIssueArtifactPath(repoRoot2, prdId, issueId) {
|
|
6304
|
+
return join13(getLocalStorePath(repoRoot2, prdId), "issues", `${issueId}.json`);
|
|
6305
|
+
}
|
|
6306
|
+
function readIssueBranchName(repoRoot2, prdId, issueId) {
|
|
6307
|
+
const issuePath = getIssueArtifactPath(repoRoot2, prdId, issueId);
|
|
6308
|
+
if (!existsSync9(issuePath)) return null;
|
|
6309
|
+
try {
|
|
6310
|
+
const content = readFileSync11(issuePath, "utf-8");
|
|
6311
|
+
const parsed = JSON.parse(content);
|
|
6312
|
+
return typeof parsed.branchName === "string" && parsed.branchName ? parsed.branchName : null;
|
|
6313
|
+
} catch {
|
|
6314
|
+
return null;
|
|
6121
6315
|
}
|
|
6122
|
-
return refs;
|
|
6123
6316
|
}
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
}
|
|
6159
|
-
|
|
6160
|
-
|
|
6317
|
+
async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
|
|
6318
|
+
const root = repoRoot2 ?? process.cwd();
|
|
6319
|
+
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
6320
|
+
if (!existsSync9(receiptPath)) return null;
|
|
6321
|
+
try {
|
|
6322
|
+
const content = readFileSync11(receiptPath, "utf-8");
|
|
6323
|
+
const parsed = JSON.parse(content);
|
|
6324
|
+
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") {
|
|
6325
|
+
return parsed;
|
|
6326
|
+
}
|
|
6327
|
+
return null;
|
|
6328
|
+
} catch {
|
|
6329
|
+
return null;
|
|
6330
|
+
}
|
|
6331
|
+
}
|
|
6332
|
+
async function squashMergeLocalIssue(prdId, issueId, input, repoRoot2) {
|
|
6333
|
+
const root = repoRoot2 ?? process.cwd();
|
|
6334
|
+
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
6335
|
+
if (existsSync9(receiptPath)) {
|
|
6336
|
+
try {
|
|
6337
|
+
const existing = JSON.parse(
|
|
6338
|
+
readFileSync11(receiptPath, "utf-8")
|
|
6339
|
+
);
|
|
6340
|
+
if (existing.prdId === prdId && existing.issueId === issueId && existing.mergeCommit) {
|
|
6341
|
+
return {
|
|
6342
|
+
ok: false,
|
|
6343
|
+
failureCode: "already_merged",
|
|
6344
|
+
repairGuidance: `Issue ${issueId} was already merged into ${existing.localPrdBranch}. Merge commit: ${existing.mergeCommit}`
|
|
6345
|
+
};
|
|
6346
|
+
}
|
|
6347
|
+
return {
|
|
6348
|
+
ok: false,
|
|
6349
|
+
failureCode: "invalid_receipt",
|
|
6350
|
+
repairGuidance: `Merge receipt for ${issueId} belongs to different prd/issue. Remove it manually: ${receiptPath}`
|
|
6351
|
+
};
|
|
6352
|
+
} catch {
|
|
6353
|
+
return {
|
|
6354
|
+
ok: false,
|
|
6355
|
+
failureCode: "invalid_receipt",
|
|
6356
|
+
repairGuidance: `Merge receipt for ${issueId} is corrupted. Remove it manually: ${receiptPath}`
|
|
6357
|
+
};
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6360
|
+
const sourceBranch = input?.sourceBranch ?? readIssueBranchName(root, prdId, issueId);
|
|
6361
|
+
if (!sourceBranch) {
|
|
6362
|
+
return {
|
|
6363
|
+
ok: false,
|
|
6364
|
+
failureCode: "not_found",
|
|
6365
|
+
repairGuidance: `No source branch found for issue ${issueId} under PRD ${prdId}. Ensure the issue artifact exists with a valid branchName field.`
|
|
6366
|
+
};
|
|
6367
|
+
}
|
|
6368
|
+
try {
|
|
6369
|
+
execFileSync2(
|
|
6370
|
+
"git",
|
|
6371
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${sourceBranch}`],
|
|
6372
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6373
|
+
);
|
|
6374
|
+
} catch {
|
|
6375
|
+
return {
|
|
6376
|
+
ok: false,
|
|
6377
|
+
failureCode: "not_found",
|
|
6378
|
+
repairGuidance: `Source branch "${sourceBranch}" does not exist locally. Check that the branch was created and not deleted.`
|
|
6379
|
+
};
|
|
6380
|
+
}
|
|
6381
|
+
const targetBranch = getLocalPrdBranchName(prdId);
|
|
6382
|
+
try {
|
|
6383
|
+
execFileSync2(
|
|
6384
|
+
"git",
|
|
6385
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
|
|
6386
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6387
|
+
);
|
|
6388
|
+
} catch {
|
|
6389
|
+
return {
|
|
6390
|
+
ok: false,
|
|
6391
|
+
failureCode: "not_found",
|
|
6392
|
+
repairGuidance: `Local PRD branch "${targetBranch}" does not exist. Run \`prd-run start\` first to create it.`
|
|
6393
|
+
};
|
|
6394
|
+
}
|
|
6395
|
+
try {
|
|
6396
|
+
execFileSync2("git", ["checkout", targetBranch], {
|
|
6397
|
+
cwd: root,
|
|
6398
|
+
encoding: "utf8",
|
|
6399
|
+
stdio: "pipe"
|
|
6400
|
+
});
|
|
6401
|
+
let preMergeHead;
|
|
6402
|
+
try {
|
|
6403
|
+
preMergeHead = execFileSync2("git", ["rev-parse", "HEAD"], {
|
|
6404
|
+
cwd: root,
|
|
6405
|
+
encoding: "utf8",
|
|
6406
|
+
stdio: "pipe"
|
|
6407
|
+
}).trim();
|
|
6408
|
+
} catch {
|
|
6409
|
+
return {
|
|
6410
|
+
ok: false,
|
|
6411
|
+
failureCode: "merge_error",
|
|
6412
|
+
repairGuidance: "Failed to read current HEAD before merge."
|
|
6413
|
+
};
|
|
6414
|
+
}
|
|
6415
|
+
execFileSync2("git", ["merge", "--squash", sourceBranch], {
|
|
6416
|
+
cwd: root,
|
|
6417
|
+
encoding: "utf8",
|
|
6418
|
+
stdio: "pipe"
|
|
6419
|
+
});
|
|
6420
|
+
if (input) {
|
|
6421
|
+
execFileSync2(
|
|
6422
|
+
"git",
|
|
6423
|
+
["commit", "-m", input.finalizerTitle, "-m", input.finalizerBody],
|
|
6424
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6425
|
+
);
|
|
6426
|
+
} else {
|
|
6427
|
+
execFileSync2(
|
|
6428
|
+
"git",
|
|
6429
|
+
["commit", "-m", `Squash merge ${sourceBranch} into ${targetBranch}`],
|
|
6430
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6431
|
+
);
|
|
6432
|
+
}
|
|
6433
|
+
const revParseResult = execFileSync2("git", ["rev-parse", "HEAD"], {
|
|
6434
|
+
cwd: root,
|
|
6435
|
+
encoding: "utf8",
|
|
6436
|
+
stdio: "pipe"
|
|
6437
|
+
});
|
|
6438
|
+
const mergeCommit = revParseResult.trim();
|
|
6439
|
+
const receipt = {
|
|
6440
|
+
prdId,
|
|
6441
|
+
issueId,
|
|
6442
|
+
stage: "child-issue",
|
|
6443
|
+
sourceBranch,
|
|
6444
|
+
localPrdBranch: targetBranch,
|
|
6445
|
+
mergeCommit,
|
|
6446
|
+
finalizerArtifactPath: input?.finalizerArtifactPath ?? "",
|
|
6447
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6448
|
+
};
|
|
6449
|
+
try {
|
|
6450
|
+
const receiptsDir = join13(
|
|
6451
|
+
root,
|
|
6452
|
+
".pourkit",
|
|
6453
|
+
"local-prd-runs",
|
|
6454
|
+
prdId,
|
|
6455
|
+
"merge-receipts"
|
|
6456
|
+
);
|
|
6457
|
+
mkdirSync7(receiptsDir, { recursive: true });
|
|
6458
|
+
writeFileSync4(receiptPath, JSON.stringify(receipt, null, 2), "utf-8");
|
|
6459
|
+
} catch {
|
|
6460
|
+
try {
|
|
6461
|
+
execFileSync2("git", ["reset", "--hard", preMergeHead], {
|
|
6462
|
+
cwd: root,
|
|
6463
|
+
encoding: "utf8",
|
|
6464
|
+
stdio: "pipe"
|
|
6465
|
+
});
|
|
6466
|
+
} catch {
|
|
6467
|
+
}
|
|
6468
|
+
return {
|
|
6469
|
+
ok: false,
|
|
6470
|
+
failureCode: "receipt_error",
|
|
6471
|
+
repairGuidance: "Failed to write merge receipt. Check disk space and permissions on .pourkit/local-prd-runs/. The merge commit has been rolled back."
|
|
6472
|
+
};
|
|
6473
|
+
}
|
|
6474
|
+
return { ok: true, receipt };
|
|
6475
|
+
} catch (error) {
|
|
6476
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6477
|
+
if (message.toLowerCase().includes("conflict")) {
|
|
6478
|
+
return {
|
|
6479
|
+
ok: false,
|
|
6480
|
+
failureCode: "conflict",
|
|
6481
|
+
repairGuidance: "Merge conflict during squash. Resolve conflicts in the working tree, then retry."
|
|
6482
|
+
};
|
|
6483
|
+
}
|
|
6484
|
+
const stderr = error instanceof Error && "stderr" in error ? error.stderr : void 0;
|
|
6485
|
+
return {
|
|
6486
|
+
ok: false,
|
|
6487
|
+
failureCode: "merge_error",
|
|
6488
|
+
repairGuidance: stderr ? `Merge failed: ${stderr}` : `Merge failed: ${message}`
|
|
6489
|
+
};
|
|
6490
|
+
}
|
|
6491
|
+
}
|
|
6492
|
+
|
|
6493
|
+
// pr/pr-body.ts
|
|
6494
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
6495
|
+
var DEFAULT_MANUAL_PR_BODY = `## Summary
|
|
6496
|
+
|
|
6497
|
+
- Summarize why this branch exists.
|
|
6498
|
+
|
|
6499
|
+
## Changes
|
|
6500
|
+
|
|
6501
|
+
- Summarize the final net change in this branch.`;
|
|
6502
|
+
async function buildPrBody(input) {
|
|
6503
|
+
const { defaultBody, options } = input;
|
|
6504
|
+
let bodyText = await resolveBodyText(defaultBody, options);
|
|
6505
|
+
bodyText = appendClosingRef(bodyText, options.issue);
|
|
6506
|
+
return bodyText;
|
|
6507
|
+
}
|
|
6508
|
+
function ensureClosingRefs(body, issue) {
|
|
6509
|
+
const stripped = stripClosingRefs(body || "");
|
|
6510
|
+
if (issue === void 0) {
|
|
6511
|
+
return stripped;
|
|
6512
|
+
}
|
|
6513
|
+
if (!stripped) {
|
|
6514
|
+
return `Closes #${issue}`;
|
|
6515
|
+
}
|
|
6516
|
+
return `${stripped}
|
|
6517
|
+
|
|
6518
|
+
Closes #${issue}`;
|
|
6519
|
+
}
|
|
6520
|
+
function stripClosingRefs(body) {
|
|
6521
|
+
const linePattern = /^[ \t]*[-*+]\s+(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s*:?\s*#\d+\s*$/gim;
|
|
6522
|
+
let result = body.replace(linePattern, "");
|
|
6523
|
+
const inlinePattern = /(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s*:?\s*#\d+/gi;
|
|
6524
|
+
result = result.replace(inlinePattern, "").trimEnd();
|
|
6525
|
+
return result;
|
|
6526
|
+
}
|
|
6527
|
+
async function resolveBodyText(defaultBody, options) {
|
|
6528
|
+
if (options.body && options.bodyFile) {
|
|
6529
|
+
throw new Error("--body and --body-file cannot be used together");
|
|
6530
|
+
}
|
|
6531
|
+
if (options.body !== void 0) {
|
|
6532
|
+
return options.body;
|
|
6533
|
+
}
|
|
6534
|
+
if (options.bodyFile !== void 0) {
|
|
6535
|
+
return await readFile2(options.bodyFile, "utf-8");
|
|
6536
|
+
}
|
|
6537
|
+
return defaultBody;
|
|
6538
|
+
}
|
|
6539
|
+
function appendClosingRef(body, issue) {
|
|
6540
|
+
if (issue === void 0) {
|
|
6541
|
+
return body;
|
|
6542
|
+
}
|
|
6543
|
+
const existingRefs = extractClosingRefs(body);
|
|
6544
|
+
if (existingRefs.has(issue)) {
|
|
6545
|
+
return body;
|
|
6546
|
+
}
|
|
6547
|
+
return `${body}
|
|
6548
|
+
|
|
6549
|
+
Closes #${issue}`;
|
|
6550
|
+
}
|
|
6551
|
+
function extractClosingRefs(body) {
|
|
6552
|
+
const refs = /* @__PURE__ */ new Set();
|
|
6553
|
+
const pattern = /(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s*:?\s*#(\d+)/gi;
|
|
6554
|
+
let match;
|
|
6555
|
+
while ((match = pattern.exec(body)) !== null) {
|
|
6556
|
+
refs.add(parseInt(match[1], 10));
|
|
6557
|
+
}
|
|
6558
|
+
return refs;
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
// issues/target-green.ts
|
|
6562
|
+
init_common();
|
|
6563
|
+
var RED_CHECK_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
6564
|
+
"FAILURE",
|
|
6565
|
+
"CANCELLED",
|
|
6566
|
+
"TIMED_OUT",
|
|
6567
|
+
"STARTUP_FAILURE",
|
|
6568
|
+
"ACTION_REQUIRED",
|
|
6569
|
+
"STALE"
|
|
6570
|
+
]);
|
|
6571
|
+
async function waitForBranchChecks(prProvider, logger, options) {
|
|
6572
|
+
const checksFoundTimeoutMs = options.checksFoundTimeoutMs ?? 60 * 1e3;
|
|
6573
|
+
const checksCompletionTimeoutMs = options.checksCompletionTimeoutMs ?? 5 * 60 * 1e3;
|
|
6574
|
+
const pollIntervalMs = options.pollIntervalMs ?? 15 * 1e3;
|
|
6575
|
+
const stableHeadMs = options.stableHeadMs ?? pollIntervalMs;
|
|
6576
|
+
let lastHeadSha = "";
|
|
6577
|
+
let headStableSince = 0;
|
|
6578
|
+
let checksFoundDeadline = 0;
|
|
6579
|
+
let checksCompletionDeadline = 0;
|
|
6580
|
+
let checksDiscovered = false;
|
|
6581
|
+
logger.step("wait", `waiting for ${options.branchName} to be green`);
|
|
6582
|
+
while (true) {
|
|
6583
|
+
const observedAt = Date.now();
|
|
6584
|
+
const status = await prProvider.getBranchStatus(options.branchName);
|
|
6585
|
+
if (status.headSha !== lastHeadSha) {
|
|
6586
|
+
logger.step(
|
|
6587
|
+
"info",
|
|
6588
|
+
`branch head changed to ${status.headSha.substring(0, 7)}`
|
|
6589
|
+
);
|
|
6590
|
+
lastHeadSha = status.headSha;
|
|
6591
|
+
headStableSince = observedAt;
|
|
6592
|
+
if (checksFoundDeadline === 0) {
|
|
6593
|
+
checksFoundDeadline = observedAt + checksFoundTimeoutMs;
|
|
6594
|
+
}
|
|
6595
|
+
checksCompletionDeadline = 0;
|
|
6596
|
+
checksDiscovered = false;
|
|
6161
6597
|
}
|
|
6162
6598
|
if (status.checks.length > 0 && !checksDiscovered) {
|
|
6163
6599
|
checksDiscovered = true;
|
|
@@ -6786,44 +7222,75 @@ async function completeIssueRun(options) {
|
|
|
6786
7222
|
let prTitle = issue.title;
|
|
6787
7223
|
let prBody;
|
|
6788
7224
|
let finalizerResult;
|
|
7225
|
+
const isLocalModeForFinalizer = options.prdRunMode?.mode === "local";
|
|
6789
7226
|
const finalizerFromState = worktreeState?.finalizer?.completed ? worktreeState.finalizer : null;
|
|
6790
7227
|
if (finalizerFromState) {
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
if (
|
|
7228
|
+
try {
|
|
7229
|
+
if (finalizerFromState.title && finalizerFromState.body) {
|
|
7230
|
+
prTitle = finalizerFromState.title;
|
|
7231
|
+
prBody = finalizerFromState.body;
|
|
7232
|
+
} else if (finalizerFromState.artifactPath) {
|
|
7233
|
+
if (!existsSync10(finalizerFromState.artifactPath)) {
|
|
7234
|
+
throw new FinalizerFailure({
|
|
7235
|
+
message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
|
|
7236
|
+
});
|
|
7237
|
+
}
|
|
7238
|
+
const artifactContent = readFileSync12(
|
|
7239
|
+
finalizerFromState.artifactPath,
|
|
7240
|
+
"utf-8"
|
|
7241
|
+
);
|
|
7242
|
+
const parsed = parsePrDescription(artifactContent);
|
|
7243
|
+
prTitle = parsed.title;
|
|
7244
|
+
prBody = parsed.body;
|
|
7245
|
+
} else {
|
|
6796
7246
|
throw new FinalizerFailure({
|
|
6797
|
-
message:
|
|
7247
|
+
message: "Finalizer state is incomplete: missing title, body, and artifactPath"
|
|
6798
7248
|
});
|
|
6799
7249
|
}
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
7250
|
+
} catch (error) {
|
|
7251
|
+
if (isLocalModeForFinalizer) {
|
|
7252
|
+
return {
|
|
7253
|
+
branchName,
|
|
7254
|
+
target,
|
|
7255
|
+
issue,
|
|
7256
|
+
noOp: false,
|
|
7257
|
+
mode: "local",
|
|
7258
|
+
failureCode: "finalizer_failed",
|
|
7259
|
+
repairGuidance: error instanceof Error ? error.message : "Finalizer state is invalid"
|
|
7260
|
+
};
|
|
7261
|
+
}
|
|
7262
|
+
throw error;
|
|
6811
7263
|
}
|
|
6812
7264
|
} else {
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
7265
|
+
try {
|
|
7266
|
+
finalizerResult = await runEffectAndMapExit(
|
|
7267
|
+
runFinalizerAgent({
|
|
7268
|
+
executionProvider,
|
|
7269
|
+
config,
|
|
7270
|
+
target,
|
|
7271
|
+
issue,
|
|
7272
|
+
builderBranch: branchName,
|
|
7273
|
+
targetBaseBranch: effectiveBaseBranch,
|
|
7274
|
+
worktreePath: executionResult.worktreePath,
|
|
7275
|
+
reviewArtifactPath,
|
|
7276
|
+
repoRoot: ROOT,
|
|
7277
|
+
logger
|
|
7278
|
+
})
|
|
7279
|
+
);
|
|
7280
|
+
} catch (error) {
|
|
7281
|
+
if (isLocalModeForFinalizer) {
|
|
7282
|
+
return {
|
|
7283
|
+
branchName,
|
|
7284
|
+
target,
|
|
7285
|
+
issue,
|
|
7286
|
+
noOp: false,
|
|
7287
|
+
mode: "local",
|
|
7288
|
+
failureCode: "finalizer_failed",
|
|
7289
|
+
repairGuidance: error instanceof Error ? error.message : "Finalizer execution failed"
|
|
7290
|
+
};
|
|
7291
|
+
}
|
|
7292
|
+
throw error;
|
|
7293
|
+
}
|
|
6827
7294
|
prTitle = finalizerResult.title;
|
|
6828
7295
|
prBody = finalizerResult.body;
|
|
6829
7296
|
}
|
|
@@ -6868,6 +7335,113 @@ async function completeIssueRun(options) {
|
|
|
6868
7335
|
});
|
|
6869
7336
|
}
|
|
6870
7337
|
}
|
|
7338
|
+
const isLocalMode = options.prdRunMode?.mode === "local";
|
|
7339
|
+
if (isLocalMode) {
|
|
7340
|
+
const parsed = parseStackedIssue(issue.title, issue.body);
|
|
7341
|
+
const prdId = parsed.parentRef;
|
|
7342
|
+
if (!prdId) {
|
|
7343
|
+
return {
|
|
7344
|
+
branchName,
|
|
7345
|
+
target,
|
|
7346
|
+
issue,
|
|
7347
|
+
noOp: false,
|
|
7348
|
+
mode: "local",
|
|
7349
|
+
failureCode: "finalizer_failed",
|
|
7350
|
+
repairGuidance: "Local mode requires a PRD reference (## Parent) in the issue body or title. Add the parent PRD reference and retry."
|
|
7351
|
+
};
|
|
7352
|
+
}
|
|
7353
|
+
const finalizerFromWorktreeState = worktreeState?.finalizer;
|
|
7354
|
+
if (finalizerFromWorktreeState && !finalizerFromWorktreeState.completed) {
|
|
7355
|
+
return {
|
|
7356
|
+
branchName,
|
|
7357
|
+
target,
|
|
7358
|
+
issue,
|
|
7359
|
+
noOp: false,
|
|
7360
|
+
mode: "local",
|
|
7361
|
+
failureCode: "finalizer_failed",
|
|
7362
|
+
repairGuidance: "Finalizer did not complete on previous run. Re-run the issue finalizer and try again."
|
|
7363
|
+
};
|
|
7364
|
+
}
|
|
7365
|
+
if (!prTitle || !finalBody) {
|
|
7366
|
+
return {
|
|
7367
|
+
branchName,
|
|
7368
|
+
target,
|
|
7369
|
+
issue,
|
|
7370
|
+
noOp: false,
|
|
7371
|
+
mode: "local",
|
|
7372
|
+
failureCode: "finalizer_failed",
|
|
7373
|
+
repairGuidance: "Finalizer produced empty title or body. Re-run the issue finalizer and try again."
|
|
7374
|
+
};
|
|
7375
|
+
}
|
|
7376
|
+
const existingReceipt = await hasLocalIssueMergeReceipt(
|
|
7377
|
+
prdId,
|
|
7378
|
+
`issue-${issueNumber}`,
|
|
7379
|
+
ROOT
|
|
7380
|
+
);
|
|
7381
|
+
if (existingReceipt) {
|
|
7382
|
+
return {
|
|
7383
|
+
branchName,
|
|
7384
|
+
target,
|
|
7385
|
+
issue,
|
|
7386
|
+
noOp: false,
|
|
7387
|
+
mode: "local",
|
|
7388
|
+
failureCode: "already_merged",
|
|
7389
|
+
localPrdBranch: existingReceipt.localPrdBranch,
|
|
7390
|
+
mergeCommit: existingReceipt.mergeCommit,
|
|
7391
|
+
receiptPath: join14(
|
|
7392
|
+
ROOT,
|
|
7393
|
+
".pourkit",
|
|
7394
|
+
"local-prd-runs",
|
|
7395
|
+
prdId,
|
|
7396
|
+
"merge-receipts",
|
|
7397
|
+
`issue-${issueNumber}.json`
|
|
7398
|
+
)
|
|
7399
|
+
};
|
|
7400
|
+
}
|
|
7401
|
+
const mergeResult = await squashMergeLocalIssue(
|
|
7402
|
+
prdId,
|
|
7403
|
+
`issue-${issueNumber}`,
|
|
7404
|
+
{
|
|
7405
|
+
finalizerTitle: prTitle,
|
|
7406
|
+
finalizerBody: finalBody,
|
|
7407
|
+
finalizerArtifactPath: worktreeState?.finalizer?.artifactPath ?? "",
|
|
7408
|
+
sourceBranch: branchName
|
|
7409
|
+
},
|
|
7410
|
+
ROOT
|
|
7411
|
+
);
|
|
7412
|
+
if (!mergeResult.ok) {
|
|
7413
|
+
return {
|
|
7414
|
+
branchName,
|
|
7415
|
+
target,
|
|
7416
|
+
issue,
|
|
7417
|
+
noOp: false,
|
|
7418
|
+
mode: "local",
|
|
7419
|
+
failureCode: mergeResult.failureCode,
|
|
7420
|
+
repairGuidance: mergeResult.repairGuidance ?? `Local squash-merge failed: ${mergeResult.failureCode}`
|
|
7421
|
+
};
|
|
7422
|
+
}
|
|
7423
|
+
await issueProvider.removeLabel(
|
|
7424
|
+
issueNumber,
|
|
7425
|
+
config.labels.agentInProgress
|
|
7426
|
+
);
|
|
7427
|
+
return {
|
|
7428
|
+
branchName,
|
|
7429
|
+
target,
|
|
7430
|
+
issue,
|
|
7431
|
+
noOp: false,
|
|
7432
|
+
mode: "local",
|
|
7433
|
+
localPrdBranch: getLocalPrdBranchName(prdId),
|
|
7434
|
+
mergeCommit: mergeResult.receipt.mergeCommit,
|
|
7435
|
+
receiptPath: join14(
|
|
7436
|
+
ROOT,
|
|
7437
|
+
".pourkit",
|
|
7438
|
+
"local-prd-runs",
|
|
7439
|
+
prdId,
|
|
7440
|
+
"merge-receipts",
|
|
7441
|
+
`issue-${issueNumber}.json`
|
|
7442
|
+
)
|
|
7443
|
+
};
|
|
7444
|
+
}
|
|
6871
7445
|
let pr;
|
|
6872
7446
|
if (worktreeState?.pr?.merged) {
|
|
6873
7447
|
mergeCompleted = true;
|
|
@@ -7309,7 +7883,7 @@ async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
|
|
|
7309
7883
|
return { mode: "new", branchName, baseRef };
|
|
7310
7884
|
}
|
|
7311
7885
|
function issueWorktreePath(root, branchName) {
|
|
7312
|
-
return
|
|
7886
|
+
return join14(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
|
|
7313
7887
|
}
|
|
7314
7888
|
function resolveRegisteredIssueWorktreePath(worktreeListPorcelain, root, branchName) {
|
|
7315
7889
|
const branchWorktreePath = parseWorktreeListPorcelain(
|
|
@@ -7337,7 +7911,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
|
|
|
7337
7911
|
}
|
|
7338
7912
|
function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
7339
7913
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
7340
|
-
const promptBody =
|
|
7914
|
+
const promptBody = existsSync10(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
|
|
7341
7915
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
7342
7916
|
|
|
7343
7917
|
## Shared Run Context
|
|
@@ -7780,29 +8354,29 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
|
|
|
7780
8354
|
// commands/prd-run.ts
|
|
7781
8355
|
import {
|
|
7782
8356
|
cpSync,
|
|
7783
|
-
existsSync as
|
|
8357
|
+
existsSync as existsSync17,
|
|
7784
8358
|
lstatSync,
|
|
7785
|
-
mkdirSync as
|
|
8359
|
+
mkdirSync as mkdirSync11,
|
|
7786
8360
|
mkdtempSync,
|
|
7787
|
-
readdirSync as
|
|
7788
|
-
readFileSync as
|
|
8361
|
+
readdirSync as readdirSync5,
|
|
8362
|
+
readFileSync as readFileSync17,
|
|
7789
8363
|
realpathSync,
|
|
7790
8364
|
rmSync as rmSync3
|
|
7791
8365
|
} from "fs";
|
|
7792
|
-
import { spawnSync as
|
|
7793
|
-
import { dirname as dirname5, join as
|
|
8366
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
8367
|
+
import { dirname as dirname5, join as join21, relative as relative2 } from "path";
|
|
7794
8368
|
import { tmpdir } from "os";
|
|
7795
8369
|
|
|
7796
8370
|
// prd-run/state.ts
|
|
7797
8371
|
import {
|
|
7798
|
-
existsSync as
|
|
7799
|
-
mkdirSync as
|
|
7800
|
-
readFileSync as
|
|
8372
|
+
existsSync as existsSync11,
|
|
8373
|
+
mkdirSync as mkdirSync8,
|
|
8374
|
+
readFileSync as readFileSync13,
|
|
7801
8375
|
readdirSync as readdirSync4,
|
|
7802
|
-
writeFileSync as
|
|
8376
|
+
writeFileSync as writeFileSync5
|
|
7803
8377
|
} from "fs";
|
|
7804
8378
|
import { mkdir as mkdir4, readFile as readFile4, writeFile } from "fs/promises";
|
|
7805
|
-
import { join as
|
|
8379
|
+
import { join as join15 } from "path";
|
|
7806
8380
|
import { z as z3 } from "zod";
|
|
7807
8381
|
var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
|
|
7808
8382
|
var PrdRunRecordSchema = z3.object({
|
|
@@ -7820,9 +8394,11 @@ var PrdRunRecordSchema = z3.object({
|
|
|
7820
8394
|
"waiting_for_integration",
|
|
7821
8395
|
"finalizing",
|
|
7822
8396
|
"final_reviewed",
|
|
7823
|
-
"complete"
|
|
8397
|
+
"complete",
|
|
8398
|
+
"completed_local_branch"
|
|
7824
8399
|
]),
|
|
7825
8400
|
updatedAt: z3.string().min(1),
|
|
8401
|
+
mode: z3.enum(["github", "local"]).optional(),
|
|
7826
8402
|
blockedGate: z3.enum([
|
|
7827
8403
|
"manifest-location",
|
|
7828
8404
|
"manifest-readiness",
|
|
@@ -7946,12 +8522,12 @@ function normalizePrdRunRef2(ref) {
|
|
|
7946
8522
|
function readPrdRun(repoRoot2, prdRef) {
|
|
7947
8523
|
const normalized = normalizePrdRunRef2(prdRef);
|
|
7948
8524
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7949
|
-
if (!
|
|
8525
|
+
if (!existsSync11(recordPath)) {
|
|
7950
8526
|
return { record: null, diagnostics: [] };
|
|
7951
8527
|
}
|
|
7952
8528
|
try {
|
|
7953
8529
|
const parsed = PrdRunRecordSchema.parse(
|
|
7954
|
-
JSON.parse(
|
|
8530
|
+
JSON.parse(readFileSync13(recordPath, "utf-8"))
|
|
7955
8531
|
);
|
|
7956
8532
|
return { record: parsed, diagnostics: [] };
|
|
7957
8533
|
} catch (error) {
|
|
@@ -7965,8 +8541,8 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7965
8541
|
}
|
|
7966
8542
|
}
|
|
7967
8543
|
function listPrdRuns(repoRoot2) {
|
|
7968
|
-
const stateDir =
|
|
7969
|
-
if (!
|
|
8544
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
8545
|
+
if (!existsSync11(stateDir)) {
|
|
7970
8546
|
return { records: [], diagnostics: [] };
|
|
7971
8547
|
}
|
|
7972
8548
|
const records = [];
|
|
@@ -7975,10 +8551,10 @@ function listPrdRuns(repoRoot2) {
|
|
|
7975
8551
|
(left, right) => left.name.localeCompare(right.name)
|
|
7976
8552
|
)) {
|
|
7977
8553
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
7978
|
-
const recordPath =
|
|
8554
|
+
const recordPath = join15(stateDir, entry.name);
|
|
7979
8555
|
try {
|
|
7980
8556
|
const record = PrdRunRecordSchema.parse(
|
|
7981
|
-
JSON.parse(
|
|
8557
|
+
JSON.parse(readFileSync13(recordPath, "utf-8"))
|
|
7982
8558
|
);
|
|
7983
8559
|
records.push(record);
|
|
7984
8560
|
} catch (error) {
|
|
@@ -7991,10 +8567,10 @@ function listPrdRuns(repoRoot2) {
|
|
|
7991
8567
|
}
|
|
7992
8568
|
function writePrdRunRecord(repoRoot2, record) {
|
|
7993
8569
|
const normalized = normalizePrdRunRef2(record.prdRef);
|
|
7994
|
-
const stateDir =
|
|
8570
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7995
8571
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7996
|
-
|
|
7997
|
-
|
|
8572
|
+
mkdirSync8(stateDir, { recursive: true });
|
|
8573
|
+
writeFileSync5(
|
|
7998
8574
|
recordPath,
|
|
7999
8575
|
JSON.stringify({ ...record, prdRef: normalized }, null, 2),
|
|
8000
8576
|
"utf-8"
|
|
@@ -8031,8 +8607,8 @@ var LocalPrdRunRecordSchema = z3.object({
|
|
|
8031
8607
|
}),
|
|
8032
8608
|
metadata: z3.record(z3.unknown())
|
|
8033
8609
|
});
|
|
8034
|
-
function
|
|
8035
|
-
return
|
|
8610
|
+
function getLocalStorePath2(repoRoot2, prdId) {
|
|
8611
|
+
return join15(
|
|
8036
8612
|
repoRoot2,
|
|
8037
8613
|
LOCAL_PRD_RUN_STATE_DIR,
|
|
8038
8614
|
`${normalizePrdRunRef2(prdId)}.json`
|
|
@@ -8040,8 +8616,8 @@ function getLocalStorePath(repoRoot2, prdId) {
|
|
|
8040
8616
|
}
|
|
8041
8617
|
async function readLocalPrdRun(repoRoot2, prdId) {
|
|
8042
8618
|
const normalized = normalizePrdRunRef2(prdId);
|
|
8043
|
-
const recordPath =
|
|
8044
|
-
if (!
|
|
8619
|
+
const recordPath = getLocalStorePath2(repoRoot2, normalized);
|
|
8620
|
+
if (!existsSync11(recordPath)) {
|
|
8045
8621
|
return null;
|
|
8046
8622
|
}
|
|
8047
8623
|
try {
|
|
@@ -8053,16 +8629,16 @@ async function readLocalPrdRun(repoRoot2, prdId) {
|
|
|
8053
8629
|
}
|
|
8054
8630
|
async function writeLocalPrdRunRecord(repoRoot2, prdId, record) {
|
|
8055
8631
|
const normalized = normalizePrdRunRef2(prdId);
|
|
8056
|
-
const storeDir =
|
|
8632
|
+
const storeDir = join15(repoRoot2, LOCAL_PRD_RUN_STATE_DIR);
|
|
8057
8633
|
await mkdir4(storeDir, { recursive: true });
|
|
8058
8634
|
await writeFile(
|
|
8059
|
-
|
|
8635
|
+
getLocalStorePath2(repoRoot2, normalized),
|
|
8060
8636
|
JSON.stringify({ ...record, prdId: normalized }, null, 2),
|
|
8061
8637
|
"utf-8"
|
|
8062
8638
|
);
|
|
8063
8639
|
}
|
|
8064
8640
|
function getRecordPath(repoRoot2, prdRef) {
|
|
8065
|
-
return
|
|
8641
|
+
return join15(
|
|
8066
8642
|
repoRoot2,
|
|
8067
8643
|
PRD_RUN_STATE_DIR,
|
|
8068
8644
|
`${normalizePrdRunRef2(prdRef)}.json`
|
|
@@ -8195,9 +8771,9 @@ function redactSensitiveValues(input) {
|
|
|
8195
8771
|
}
|
|
8196
8772
|
|
|
8197
8773
|
// prd-run/final-review-validation.ts
|
|
8198
|
-
import { existsSync as
|
|
8774
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
8199
8775
|
function parseFinalReviewArtifact(artifactPath) {
|
|
8200
|
-
if (!
|
|
8776
|
+
if (!existsSync12(artifactPath)) {
|
|
8201
8777
|
return {
|
|
8202
8778
|
ok: false,
|
|
8203
8779
|
reason: "Final Review artifact not found.",
|
|
@@ -8206,7 +8782,7 @@ function parseFinalReviewArtifact(artifactPath) {
|
|
|
8206
8782
|
}
|
|
8207
8783
|
let content;
|
|
8208
8784
|
try {
|
|
8209
|
-
content =
|
|
8785
|
+
content = readFileSync14(artifactPath, "utf-8");
|
|
8210
8786
|
} catch (error) {
|
|
8211
8787
|
return {
|
|
8212
8788
|
ok: false,
|
|
@@ -8386,8 +8962,8 @@ function isRetouchScopePath(path9) {
|
|
|
8386
8962
|
init_common();
|
|
8387
8963
|
|
|
8388
8964
|
// prd-run/local-artifacts.ts
|
|
8389
|
-
import { existsSync as
|
|
8390
|
-
import { join as
|
|
8965
|
+
import { existsSync as existsSync13 } from "fs";
|
|
8966
|
+
import { join as join16 } from "path";
|
|
8391
8967
|
var REQUIRED_PRD_FIELDS = [
|
|
8392
8968
|
"schemaVersion",
|
|
8393
8969
|
"kind",
|
|
@@ -8420,13 +8996,13 @@ var REQUIRED_ISSUE_FIELDS = [
|
|
|
8420
8996
|
"githubProjection"
|
|
8421
8997
|
];
|
|
8422
8998
|
function prdStorePath(repoRoot2, prdId) {
|
|
8423
|
-
return
|
|
8999
|
+
return join16(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8424
9000
|
}
|
|
8425
9001
|
function prdArtifactPath(repoRoot2, prdId) {
|
|
8426
|
-
return
|
|
9002
|
+
return join16(prdStorePath(repoRoot2, prdId), "prd.json");
|
|
8427
9003
|
}
|
|
8428
9004
|
function issueArtifactPath(repoRoot2, prdId, issueId) {
|
|
8429
|
-
return
|
|
9005
|
+
return join16(prdStorePath(repoRoot2, prdId), "issues", `${issueId}.json`);
|
|
8430
9006
|
}
|
|
8431
9007
|
function hasRequiredFields(data, requiredFields) {
|
|
8432
9008
|
for (const field of requiredFields) {
|
|
@@ -8439,7 +9015,7 @@ function hasRequiredFields(data, requiredFields) {
|
|
|
8439
9015
|
async function resolveLocalPrdArtifact(prdId, repoRoot2) {
|
|
8440
9016
|
const root = repoRoot2 ?? process.cwd();
|
|
8441
9017
|
const prdPath = prdArtifactPath(root, prdId);
|
|
8442
|
-
if (!
|
|
9018
|
+
if (!existsSync13(prdPath)) {
|
|
8443
9019
|
return {
|
|
8444
9020
|
ok: false,
|
|
8445
9021
|
failureCode: "missing_prd_artifact",
|
|
@@ -8484,7 +9060,7 @@ async function resolveLocalIssueArtifacts(prdId, repoRoot2) {
|
|
|
8484
9060
|
const issues = [];
|
|
8485
9061
|
for (const childId of childIssueIds) {
|
|
8486
9062
|
const issuePath = issueArtifactPath(root, prdId, childId);
|
|
8487
|
-
if (!
|
|
9063
|
+
if (!existsSync13(issuePath)) {
|
|
8488
9064
|
return {
|
|
8489
9065
|
ok: false,
|
|
8490
9066
|
failureCode: "missing_child_issue",
|
|
@@ -8620,97 +9196,36 @@ async function getRunnableLocalIssues(prdId, repoRoot2) {
|
|
|
8620
9196
|
}
|
|
8621
9197
|
|
|
8622
9198
|
// 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 };
|
|
9199
|
+
import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
|
|
9200
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
9201
|
+
import { join as join17 } from "path";
|
|
9202
|
+
function getLocalStorePath3(repoRoot2, prdId) {
|
|
9203
|
+
return join17(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8657
9204
|
}
|
|
8658
|
-
function
|
|
8659
|
-
return
|
|
9205
|
+
function getFinalReviewReceiptPath(repoRoot2, prdId) {
|
|
9206
|
+
return join17(getLocalStorePath3(repoRoot2, prdId), "final-review-receipt.json");
|
|
8660
9207
|
}
|
|
8661
|
-
function
|
|
8662
|
-
const
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
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(),
|
|
9208
|
+
function computeLocalMergeBase(repoRoot2, prdBranch) {
|
|
9209
|
+
const candidates = ["dev", "main", "master", "HEAD"];
|
|
9210
|
+
for (const base of candidates) {
|
|
9211
|
+
try {
|
|
9212
|
+
const result = execFileSync3("git", ["merge-base", base, prdBranch], {
|
|
9213
|
+
cwd: repoRoot2,
|
|
8694
9214
|
encoding: "utf8",
|
|
8695
9215
|
stdio: "pipe"
|
|
9216
|
+
});
|
|
9217
|
+
const mergeBase = result.trim();
|
|
9218
|
+
if (mergeBase && mergeBase.length >= 6) {
|
|
9219
|
+
return { ok: true, mergeBase };
|
|
8696
9220
|
}
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
failureCode: "remote_backed_collision",
|
|
8701
|
-
message: `Remote branch "origin/${prdRef}" exists.`
|
|
8702
|
-
});
|
|
8703
|
-
} catch {
|
|
9221
|
+
} catch {
|
|
9222
|
+
continue;
|
|
9223
|
+
}
|
|
8704
9224
|
}
|
|
8705
|
-
return
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
function getLocalStorePath2(repoRoot2, prdId) {
|
|
8710
|
-
return join16(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8711
|
-
}
|
|
8712
|
-
function getFinalReviewReceiptPath(repoRoot2, prdId) {
|
|
8713
|
-
return join16(getLocalStorePath2(repoRoot2, prdId), "final-review-receipt.json");
|
|
9225
|
+
return {
|
|
9226
|
+
ok: false,
|
|
9227
|
+
reason: "Could not compute merge base against any known base branch (dev/main/master/HEAD)."
|
|
9228
|
+
};
|
|
8714
9229
|
}
|
|
8715
9230
|
async function runLocalFinalReview(prdId, options) {
|
|
8716
9231
|
const root = options?.repoRoot ?? process.cwd();
|
|
@@ -8724,6 +9239,34 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
8724
9239
|
};
|
|
8725
9240
|
}
|
|
8726
9241
|
const branch = getLocalPrdBranchName(prdId);
|
|
9242
|
+
const mergeBaseResult = computeLocalMergeBase(root, branch);
|
|
9243
|
+
if (!mergeBaseResult.ok) {
|
|
9244
|
+
return {
|
|
9245
|
+
ok: false,
|
|
9246
|
+
failureCode: "merge_base_failed",
|
|
9247
|
+
message: mergeBaseResult.reason
|
|
9248
|
+
};
|
|
9249
|
+
}
|
|
9250
|
+
const semanticResult = validateFinalReviewArtifactSemanticIds(
|
|
9251
|
+
{
|
|
9252
|
+
prdRef: prdId,
|
|
9253
|
+
stage: "prdFinalReview",
|
|
9254
|
+
checkoutBase: branch,
|
|
9255
|
+
reviewBase: mergeBaseResult.mergeBase
|
|
9256
|
+
},
|
|
9257
|
+
{
|
|
9258
|
+
prdRef: prdId,
|
|
9259
|
+
prdBranch: branch,
|
|
9260
|
+
mergeBase: mergeBaseResult.mergeBase
|
|
9261
|
+
}
|
|
9262
|
+
);
|
|
9263
|
+
if (!semanticResult.ok) {
|
|
9264
|
+
return {
|
|
9265
|
+
ok: false,
|
|
9266
|
+
failureCode: "invalid_artifact_semantics",
|
|
9267
|
+
message: semanticResult.errors.join("; ")
|
|
9268
|
+
};
|
|
9269
|
+
}
|
|
8727
9270
|
const failures = [];
|
|
8728
9271
|
for (const cmd of commands) {
|
|
8729
9272
|
try {
|
|
@@ -8744,7 +9287,7 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
8744
9287
|
});
|
|
8745
9288
|
verdict = "pass_with_retouch";
|
|
8746
9289
|
} catch {
|
|
8747
|
-
verdict = "
|
|
9290
|
+
verdict = "pass_no_changes";
|
|
8748
9291
|
}
|
|
8749
9292
|
} else if (failures.length === commands.length) {
|
|
8750
9293
|
verdict = "blocked";
|
|
@@ -8762,13 +9305,14 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
8762
9305
|
}
|
|
8763
9306
|
}
|
|
8764
9307
|
const receipt = {
|
|
8765
|
-
|
|
9308
|
+
reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8766
9309
|
verdict,
|
|
8767
|
-
branch
|
|
9310
|
+
prdBranch: branch,
|
|
9311
|
+
mergeBase: mergeBaseResult.mergeBase
|
|
8768
9312
|
};
|
|
8769
9313
|
const receiptPath = getFinalReviewReceiptPath(root, prdId);
|
|
8770
|
-
|
|
8771
|
-
|
|
9314
|
+
mkdirSync9(getLocalStorePath3(root, prdId), { recursive: true });
|
|
9315
|
+
writeFileSync6(receiptPath, JSON.stringify(receipt, null, 2), "utf-8");
|
|
8772
9316
|
const result = { ok: true, verdict, receipt };
|
|
8773
9317
|
if (failures.length > 0) {
|
|
8774
9318
|
result.message = failures.map(
|
|
@@ -8782,7 +9326,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8782
9326
|
const targetBranch = `local/${prdId}`;
|
|
8783
9327
|
const retouchBranch = `local/${prdId}/retouch`;
|
|
8784
9328
|
try {
|
|
8785
|
-
|
|
9329
|
+
execFileSync3(
|
|
8786
9330
|
"git",
|
|
8787
9331
|
["show-ref", "--verify", "--quiet", `refs/heads/${retouchBranch}`],
|
|
8788
9332
|
{
|
|
@@ -8795,7 +9339,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8795
9339
|
return;
|
|
8796
9340
|
}
|
|
8797
9341
|
try {
|
|
8798
|
-
|
|
9342
|
+
execFileSync3(
|
|
8799
9343
|
"git",
|
|
8800
9344
|
["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
|
|
8801
9345
|
{
|
|
@@ -8808,18 +9352,18 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8808
9352
|
return;
|
|
8809
9353
|
}
|
|
8810
9354
|
try {
|
|
8811
|
-
|
|
9355
|
+
execFileSync3("git", ["checkout", targetBranch], {
|
|
8812
9356
|
cwd: root,
|
|
8813
9357
|
encoding: "utf8",
|
|
8814
9358
|
stdio: "pipe"
|
|
8815
9359
|
});
|
|
8816
|
-
|
|
9360
|
+
execFileSync3("git", ["merge", "--squash", retouchBranch], {
|
|
8817
9361
|
cwd: root,
|
|
8818
9362
|
encoding: "utf8",
|
|
8819
9363
|
stdio: "pipe"
|
|
8820
9364
|
});
|
|
8821
9365
|
try {
|
|
8822
|
-
|
|
9366
|
+
execFileSync3("git", ["diff", "--cached", "--quiet"], {
|
|
8823
9367
|
cwd: root,
|
|
8824
9368
|
encoding: "utf8",
|
|
8825
9369
|
stdio: "pipe"
|
|
@@ -8827,7 +9371,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8827
9371
|
return;
|
|
8828
9372
|
} catch {
|
|
8829
9373
|
}
|
|
8830
|
-
|
|
9374
|
+
execFileSync3(
|
|
8831
9375
|
"git",
|
|
8832
9376
|
["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
|
|
8833
9377
|
{
|
|
@@ -8836,6 +9380,26 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8836
9380
|
stdio: "pipe"
|
|
8837
9381
|
}
|
|
8838
9382
|
);
|
|
9383
|
+
const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
|
|
9384
|
+
cwd: root,
|
|
9385
|
+
encoding: "utf8",
|
|
9386
|
+
stdio: "pipe"
|
|
9387
|
+
}).trim();
|
|
9388
|
+
const changedPathsResult = execFileSync3(
|
|
9389
|
+
"git",
|
|
9390
|
+
["diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"],
|
|
9391
|
+
{
|
|
9392
|
+
cwd: root,
|
|
9393
|
+
encoding: "utf8",
|
|
9394
|
+
stdio: "pipe"
|
|
9395
|
+
}
|
|
9396
|
+
);
|
|
9397
|
+
const changedPaths = changedPathsResult.split(/\r?\n/).filter(Boolean);
|
|
9398
|
+
return {
|
|
9399
|
+
mergeCommit,
|
|
9400
|
+
changedPaths,
|
|
9401
|
+
reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9402
|
+
};
|
|
8839
9403
|
} catch (error) {
|
|
8840
9404
|
const message = error instanceof Error ? error.message : String(error);
|
|
8841
9405
|
if (message.toLowerCase().includes("conflict")) {
|
|
@@ -8846,53 +9410,27 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8846
9410
|
}
|
|
8847
9411
|
|
|
8848
9412
|
// prd-run/local-reconciliation.ts
|
|
8849
|
-
import { execFileSync as
|
|
8850
|
-
import {
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
readdirSync as readdirSync5,
|
|
8855
|
-
writeFileSync as writeFileSync6
|
|
8856
|
-
} from "fs";
|
|
8857
|
-
import { join as join17 } from "path";
|
|
8858
|
-
function getLocalStorePath3(repoRoot2, prdId) {
|
|
8859
|
-
return join17(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
9413
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
9414
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
|
|
9415
|
+
import { join as join18 } from "path";
|
|
9416
|
+
function getLocalStorePath4(repoRoot2, prdId) {
|
|
9417
|
+
return join18(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8860
9418
|
}
|
|
8861
9419
|
function getFinalReviewReceiptPath2(repoRoot2, prdId) {
|
|
8862
|
-
return
|
|
9420
|
+
return join18(getLocalStorePath4(repoRoot2, prdId), "final-review-receipt.json");
|
|
8863
9421
|
}
|
|
8864
9422
|
function getHandoffArtifactPath(repoRoot2, prdId) {
|
|
8865
|
-
return
|
|
8866
|
-
}
|
|
8867
|
-
function getCompletionsDir(repoRoot2, prdId) {
|
|
8868
|
-
const initiativesRoot = join17(
|
|
8869
|
-
repoRoot2,
|
|
8870
|
-
".pourkit",
|
|
8871
|
-
"architecture",
|
|
8872
|
-
"initiatives"
|
|
8873
|
-
);
|
|
8874
|
-
if (existsSync14(initiativesRoot)) {
|
|
8875
|
-
const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join17(initiativesRoot, e.name));
|
|
8876
|
-
for (const dir of dirs) {
|
|
8877
|
-
const prdsDir = join17(dir, "prds");
|
|
8878
|
-
if (!existsSync14(prdsDir)) continue;
|
|
8879
|
-
const prdDirs = readdirSync5(prdsDir, { withFileTypes: true }).filter(
|
|
8880
|
-
(e) => e.isDirectory() && e.name.startsWith(prdId.toLowerCase())
|
|
8881
|
-
);
|
|
8882
|
-
if (prdDirs.length > 0) return join17(dir, "completions");
|
|
8883
|
-
}
|
|
8884
|
-
}
|
|
8885
|
-
return join17(getLocalStorePath3(repoRoot2, prdId), "completions");
|
|
9423
|
+
return join18(repoRoot2, ".pourkit", ".tmp", "reconciliation", `${prdId}.json`);
|
|
8886
9424
|
}
|
|
8887
9425
|
function getReconciliationReceiptPath(repoRoot2, prdId) {
|
|
8888
|
-
return
|
|
8889
|
-
|
|
9426
|
+
return join18(
|
|
9427
|
+
getLocalStorePath4(repoRoot2, prdId),
|
|
8890
9428
|
"reconciliation-receipt.json"
|
|
8891
9429
|
);
|
|
8892
9430
|
}
|
|
8893
9431
|
async function runLocalReconciliation(prdId, options) {
|
|
8894
9432
|
const root = options?.repoRoot ?? process.cwd();
|
|
8895
|
-
if (!
|
|
9433
|
+
if (!existsSync15(getFinalReviewReceiptPath2(root, prdId))) {
|
|
8896
9434
|
return {
|
|
8897
9435
|
ok: false,
|
|
8898
9436
|
failureCode: "missing_final_review",
|
|
@@ -8900,7 +9438,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8900
9438
|
};
|
|
8901
9439
|
}
|
|
8902
9440
|
try {
|
|
8903
|
-
|
|
9441
|
+
execFileSync4("pourkit-architect", ["reconcile", prdId], {
|
|
8904
9442
|
cwd: root,
|
|
8905
9443
|
encoding: "utf8",
|
|
8906
9444
|
stdio: "pipe"
|
|
@@ -8916,7 +9454,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8916
9454
|
}
|
|
8917
9455
|
let handoff;
|
|
8918
9456
|
try {
|
|
8919
|
-
const content =
|
|
9457
|
+
const content = readFileSync15(getHandoffArtifactPath(root, prdId), "utf-8");
|
|
8920
9458
|
handoff = JSON.parse(content);
|
|
8921
9459
|
} catch {
|
|
8922
9460
|
return {
|
|
@@ -8925,67 +9463,39 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8925
9463
|
message: "Handoff artifact validation failed. Check Architect reconciliation output."
|
|
8926
9464
|
};
|
|
8927
9465
|
}
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
9466
|
+
const validResults = [
|
|
9467
|
+
"no_changes_needed",
|
|
9468
|
+
"changes_produced",
|
|
9469
|
+
"blocked"
|
|
9470
|
+
];
|
|
9471
|
+
if (!validResults.includes(
|
|
9472
|
+
handoff.result
|
|
9473
|
+
)) {
|
|
9474
|
+
return {
|
|
9475
|
+
ok: false,
|
|
9476
|
+
failureCode: "invalid_handoff",
|
|
9477
|
+
message: "Handoff artifact validation failed. Check Architect reconciliation output."
|
|
9478
|
+
};
|
|
9479
|
+
}
|
|
9480
|
+
if (handoff.result === "blocked") {
|
|
9481
|
+
return {
|
|
9482
|
+
ok: false,
|
|
9483
|
+
failureCode: "reconciliation_blocked",
|
|
9484
|
+
message: "Architect reconciliation is blocked."
|
|
9485
|
+
};
|
|
9486
|
+
}
|
|
9487
|
+
if (handoff.result === "changes_produced" && (!Array.isArray(handoff.changedPlanningPaths) || handoff.changedPlanningPaths.length === 0)) {
|
|
8936
9488
|
return {
|
|
8937
9489
|
ok: false,
|
|
8938
9490
|
failureCode: "invalid_handoff",
|
|
8939
9491
|
message: "Handoff artifact validation failed. Check Architect reconciliation output."
|
|
8940
9492
|
};
|
|
8941
9493
|
}
|
|
8942
|
-
if (handoff.result === "changes_produced") {
|
|
8943
|
-
try {
|
|
8944
|
-
const completionsDir = getCompletionsDir(root, prdId);
|
|
8945
|
-
mkdirSync9(completionsDir, { recursive: true });
|
|
8946
|
-
const existing = existsSync14(completionsDir) ? readdirSync5(completionsDir).filter((f) => f.endsWith(".md")) : [];
|
|
8947
|
-
const nextNum = String(existing.length + 1).padStart(3, "0");
|
|
8948
|
-
const filename = `${nextNum}-prd-${prdId.replace("PRD-", "").toLowerCase()}-reconciliation-completion.md`;
|
|
8949
|
-
const filepath = join17(completionsDir, filename);
|
|
8950
|
-
const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
|
|
8951
|
-
const summary = typeof handoff.summary === "string" ? handoff.summary : "";
|
|
8952
|
-
const lines = [
|
|
8953
|
-
`# ${prdId} Reconciliation Completion`,
|
|
8954
|
-
"",
|
|
8955
|
-
`Result: ${handoff.result}`
|
|
8956
|
-
];
|
|
8957
|
-
if (summary) lines.push(`Summary: ${summary}`);
|
|
8958
|
-
lines.push("", "## Changed Planning Paths", "");
|
|
8959
|
-
if (changedPaths.length > 0) {
|
|
8960
|
-
lines.push(...changedPaths.map((p) => `- ${p}`));
|
|
8961
|
-
} else {
|
|
8962
|
-
lines.push("- (none)");
|
|
8963
|
-
}
|
|
8964
|
-
lines.push("");
|
|
8965
|
-
writeFileSync6(filepath, lines.join("\n"), "utf-8");
|
|
8966
|
-
execFileSync3("git", ["add", filepath], {
|
|
8967
|
-
cwd: root,
|
|
8968
|
-
encoding: "utf8",
|
|
8969
|
-
stdio: "pipe"
|
|
8970
|
-
});
|
|
8971
|
-
execFileSync3(
|
|
8972
|
-
"git",
|
|
8973
|
-
["commit", "-m", `docs: ${prdId} reconciliation completion`],
|
|
8974
|
-
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
8975
|
-
);
|
|
8976
|
-
} catch {
|
|
8977
|
-
return {
|
|
8978
|
-
ok: false,
|
|
8979
|
-
failureCode: "completion_commit_failed",
|
|
8980
|
-
message: "Failed to commit completion files."
|
|
8981
|
-
};
|
|
8982
|
-
}
|
|
8983
|
-
}
|
|
8984
9494
|
if (handoff.result === "changes_produced") {
|
|
8985
9495
|
try {
|
|
8986
9496
|
const targetBranch = getLocalPrdBranchName(prdId);
|
|
8987
9497
|
try {
|
|
8988
|
-
|
|
9498
|
+
execFileSync4(
|
|
8989
9499
|
"git",
|
|
8990
9500
|
["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
|
|
8991
9501
|
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
@@ -8994,17 +9504,17 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8994
9504
|
return {
|
|
8995
9505
|
ok: false,
|
|
8996
9506
|
failureCode: "squash_merge_failed",
|
|
8997
|
-
message: "
|
|
9507
|
+
message: `Local PRD Branch "${targetBranch}" does not exist. Run prd-run start first to create it.`
|
|
8998
9508
|
};
|
|
8999
9509
|
}
|
|
9000
|
-
|
|
9510
|
+
execFileSync4("git", ["checkout", targetBranch], {
|
|
9001
9511
|
cwd: root,
|
|
9002
9512
|
encoding: "utf8",
|
|
9003
9513
|
stdio: "pipe"
|
|
9004
9514
|
});
|
|
9005
9515
|
const sourceBranch = `local/${prdId}/reconciliation`;
|
|
9006
9516
|
try {
|
|
9007
|
-
|
|
9517
|
+
execFileSync4(
|
|
9008
9518
|
"git",
|
|
9009
9519
|
["show-ref", "--verify", "--quiet", `refs/heads/${sourceBranch}`],
|
|
9010
9520
|
{
|
|
@@ -9017,27 +9527,46 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9017
9527
|
return {
|
|
9018
9528
|
ok: false,
|
|
9019
9529
|
failureCode: "squash_merge_failed",
|
|
9020
|
-
message:
|
|
9530
|
+
message: `Reconciliation source branch "${sourceBranch}" does not exist. Re-run reconciliation.`
|
|
9021
9531
|
};
|
|
9022
9532
|
}
|
|
9023
|
-
|
|
9533
|
+
execFileSync4("git", ["merge", "--squash", sourceBranch], {
|
|
9024
9534
|
cwd: root,
|
|
9025
9535
|
encoding: "utf8",
|
|
9026
9536
|
stdio: "pipe"
|
|
9027
9537
|
});
|
|
9028
9538
|
try {
|
|
9029
|
-
|
|
9539
|
+
execFileSync4("git", ["diff", "--cached", "--quiet"], {
|
|
9030
9540
|
cwd: root,
|
|
9031
9541
|
encoding: "utf8",
|
|
9032
9542
|
stdio: "pipe"
|
|
9033
9543
|
});
|
|
9034
9544
|
} catch {
|
|
9035
|
-
|
|
9545
|
+
execFileSync4(
|
|
9036
9546
|
"git",
|
|
9037
9547
|
["commit", "-m", `Squash merge ${sourceBranch} into ${targetBranch}`],
|
|
9038
9548
|
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
9039
9549
|
);
|
|
9040
9550
|
}
|
|
9551
|
+
} catch (error) {
|
|
9552
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
9553
|
+
return {
|
|
9554
|
+
ok: false,
|
|
9555
|
+
failureCode: "squash_merge_failed",
|
|
9556
|
+
message: `Squash merge into local PRD Branch failed: ${message}`
|
|
9557
|
+
};
|
|
9558
|
+
}
|
|
9559
|
+
}
|
|
9560
|
+
const prdBranch = getLocalPrdBranchName(prdId);
|
|
9561
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9562
|
+
let mergeCommit;
|
|
9563
|
+
if (handoff.result === "changes_produced") {
|
|
9564
|
+
try {
|
|
9565
|
+
mergeCommit = execFileSync4("git", ["rev-parse", "HEAD"], {
|
|
9566
|
+
cwd: root,
|
|
9567
|
+
encoding: "utf8",
|
|
9568
|
+
stdio: "pipe"
|
|
9569
|
+
}).trim();
|
|
9041
9570
|
} catch {
|
|
9042
9571
|
return {
|
|
9043
9572
|
ok: false,
|
|
@@ -9046,23 +9575,26 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9046
9575
|
};
|
|
9047
9576
|
}
|
|
9048
9577
|
}
|
|
9049
|
-
const
|
|
9050
|
-
const
|
|
9051
|
-
|
|
9052
|
-
|
|
9578
|
+
const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
|
|
9579
|
+
const entry = {
|
|
9580
|
+
result: handoff.result ?? "blocked",
|
|
9581
|
+
prdBranch,
|
|
9582
|
+
...changedPaths.length > 0 ? { changedPlanningPaths: changedPaths } : {},
|
|
9583
|
+
...mergeCommit ? { mergeCommit } : {},
|
|
9584
|
+
reconciledTimestamp: now
|
|
9053
9585
|
};
|
|
9054
|
-
|
|
9055
|
-
|
|
9586
|
+
mkdirSync10(getLocalStorePath4(root, prdId), { recursive: true });
|
|
9587
|
+
writeFileSync7(
|
|
9056
9588
|
getReconciliationReceiptPath(root, prdId),
|
|
9057
|
-
JSON.stringify(
|
|
9589
|
+
JSON.stringify(entry, null, 2),
|
|
9058
9590
|
"utf-8"
|
|
9059
9591
|
);
|
|
9060
|
-
return { ok: true, receipt };
|
|
9592
|
+
return { ok: true, receipt: entry };
|
|
9061
9593
|
}
|
|
9062
9594
|
|
|
9063
9595
|
// prd-run/local-prepare.ts
|
|
9064
|
-
import { existsSync as
|
|
9065
|
-
import { join as
|
|
9596
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9597
|
+
import { join as join19 } from "path";
|
|
9066
9598
|
var VALID_TRIAGE_LABELS2 = /* @__PURE__ */ new Set([
|
|
9067
9599
|
"needs-triage",
|
|
9068
9600
|
"ready-for-agent",
|
|
@@ -9080,8 +9612,8 @@ function fail(gate, failureCode, repairGuidance, gates) {
|
|
|
9080
9612
|
async function validateLocalPrepareGates(prdId, repoRoot2) {
|
|
9081
9613
|
const root = repoRoot2 ?? process.cwd();
|
|
9082
9614
|
const gates = {};
|
|
9083
|
-
const storePath =
|
|
9084
|
-
if (!
|
|
9615
|
+
const storePath = join19(root, ".pourkit", "local-prd-runs", prdId);
|
|
9616
|
+
if (!existsSync16(storePath)) {
|
|
9085
9617
|
return fail(
|
|
9086
9618
|
"store_shape",
|
|
9087
9619
|
"local_prepare_store_shape_failed",
|
|
@@ -9202,6 +9734,297 @@ async function validateLocalPrepareGates(prdId, repoRoot2) {
|
|
|
9202
9734
|
return { ok: true, gates };
|
|
9203
9735
|
}
|
|
9204
9736
|
|
|
9737
|
+
// prd-run/local-queue-loop.ts
|
|
9738
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
|
|
9739
|
+
import { join as join20 } from "path";
|
|
9740
|
+
|
|
9741
|
+
// prd-run/local-issue-run.ts
|
|
9742
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
9743
|
+
init_common();
|
|
9744
|
+
async function createLocalIssueBranch(prdId, issueId, repoRoot2) {
|
|
9745
|
+
const root = repoRoot2 ?? process.cwd();
|
|
9746
|
+
const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
|
|
9747
|
+
if (!issuesResult.ok) {
|
|
9748
|
+
throw new Error(
|
|
9749
|
+
`Cannot create local issue branch: ${issuesResult.message}`
|
|
9750
|
+
);
|
|
9751
|
+
}
|
|
9752
|
+
const issue = issuesResult.data.find((i) => i.id === issueId);
|
|
9753
|
+
if (!issue) {
|
|
9754
|
+
throw new Error(`Issue "${issueId}" not found under PRD "${prdId}"`);
|
|
9755
|
+
}
|
|
9756
|
+
const slug = slugify(issue.title);
|
|
9757
|
+
const branchName = `local/${prdId}/${issueId}-${slug}`;
|
|
9758
|
+
const validation = validateLocalBranchName(branchName);
|
|
9759
|
+
if (!validation.ok) {
|
|
9760
|
+
throw new Error(
|
|
9761
|
+
`Invalid local issue branch name "${branchName}": ${validation.message}`
|
|
9762
|
+
);
|
|
9763
|
+
}
|
|
9764
|
+
const localPrdBranch = `local/${prdId}`;
|
|
9765
|
+
try {
|
|
9766
|
+
execFileSync5(
|
|
9767
|
+
"git",
|
|
9768
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${localPrdBranch}`],
|
|
9769
|
+
{
|
|
9770
|
+
cwd: root,
|
|
9771
|
+
encoding: "utf8",
|
|
9772
|
+
stdio: "pipe"
|
|
9773
|
+
}
|
|
9774
|
+
);
|
|
9775
|
+
} catch {
|
|
9776
|
+
throw new Error(
|
|
9777
|
+
`Local PRD branch "${localPrdBranch}" does not exist. Create it first.`
|
|
9778
|
+
);
|
|
9779
|
+
}
|
|
9780
|
+
let branchExists = false;
|
|
9781
|
+
try {
|
|
9782
|
+
execFileSync5(
|
|
9783
|
+
"git",
|
|
9784
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
9785
|
+
{
|
|
9786
|
+
cwd: root,
|
|
9787
|
+
encoding: "utf8",
|
|
9788
|
+
stdio: "pipe"
|
|
9789
|
+
}
|
|
9790
|
+
);
|
|
9791
|
+
branchExists = true;
|
|
9792
|
+
} catch {
|
|
9793
|
+
}
|
|
9794
|
+
if (!branchExists) {
|
|
9795
|
+
execFileSync5("git", ["checkout", "-b", branchName, localPrdBranch], {
|
|
9796
|
+
cwd: root,
|
|
9797
|
+
encoding: "utf8",
|
|
9798
|
+
stdio: "pipe"
|
|
9799
|
+
});
|
|
9800
|
+
}
|
|
9801
|
+
return { branchName, issue };
|
|
9802
|
+
}
|
|
9803
|
+
async function runLocalIssue(prdId, issueId, builder, repoRoot2) {
|
|
9804
|
+
try {
|
|
9805
|
+
const root = repoRoot2 ?? process.cwd();
|
|
9806
|
+
const { branchName: branch, issue } = await createLocalIssueBranch(
|
|
9807
|
+
prdId,
|
|
9808
|
+
issueId,
|
|
9809
|
+
root
|
|
9810
|
+
);
|
|
9811
|
+
if (builder) {
|
|
9812
|
+
const result = await builder.execute({
|
|
9813
|
+
prdId,
|
|
9814
|
+
issueId,
|
|
9815
|
+
branch,
|
|
9816
|
+
bodyMarkdown: issue.bodyMarkdown
|
|
9817
|
+
});
|
|
9818
|
+
if (!result.ok) {
|
|
9819
|
+
return {
|
|
9820
|
+
ok: false,
|
|
9821
|
+
issueId,
|
|
9822
|
+
branch,
|
|
9823
|
+
failureCode: result.error ?? "Builder execution failed"
|
|
9824
|
+
};
|
|
9825
|
+
}
|
|
9826
|
+
}
|
|
9827
|
+
return { ok: true, issueId, branch };
|
|
9828
|
+
} catch (error) {
|
|
9829
|
+
return {
|
|
9830
|
+
ok: false,
|
|
9831
|
+
issueId,
|
|
9832
|
+
branch: "",
|
|
9833
|
+
failureCode: error instanceof Error ? error.message : String(error)
|
|
9834
|
+
};
|
|
9835
|
+
}
|
|
9836
|
+
}
|
|
9837
|
+
|
|
9838
|
+
// prd-run/local-queue-loop.ts
|
|
9839
|
+
var CHILD_CLEANUP_LABELS = ["agent-in-progress", "pr-open-awaiting-merge"];
|
|
9840
|
+
function getIssueArtifactPath2(repoRoot2, prdId, issueId) {
|
|
9841
|
+
return join20(
|
|
9842
|
+
repoRoot2,
|
|
9843
|
+
".pourkit",
|
|
9844
|
+
"local-prd-runs",
|
|
9845
|
+
prdId,
|
|
9846
|
+
"issues",
|
|
9847
|
+
`${issueId}.json`
|
|
9848
|
+
);
|
|
9849
|
+
}
|
|
9850
|
+
function readIssueArtifact(repoRoot2, prdId, issueId) {
|
|
9851
|
+
try {
|
|
9852
|
+
const content = readFileSync16(
|
|
9853
|
+
getIssueArtifactPath2(repoRoot2, prdId, issueId),
|
|
9854
|
+
"utf-8"
|
|
9855
|
+
);
|
|
9856
|
+
return JSON.parse(content);
|
|
9857
|
+
} catch {
|
|
9858
|
+
return null;
|
|
9859
|
+
}
|
|
9860
|
+
}
|
|
9861
|
+
function writeIssueArtifact(repoRoot2, prdId, issue) {
|
|
9862
|
+
writeFileSync8(
|
|
9863
|
+
getIssueArtifactPath2(repoRoot2, prdId, issue.id),
|
|
9864
|
+
JSON.stringify(issue, null, 2),
|
|
9865
|
+
"utf-8"
|
|
9866
|
+
);
|
|
9867
|
+
}
|
|
9868
|
+
async function reconcileLocalBlockedIssues(prdId, repoRoot2) {
|
|
9869
|
+
const root = repoRoot2 ?? process.cwd();
|
|
9870
|
+
const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
|
|
9871
|
+
if (!issuesResult.ok) return;
|
|
9872
|
+
const issues = issuesResult.data;
|
|
9873
|
+
for (const issue of issues) {
|
|
9874
|
+
const isBlocked = issue.status === "blocked";
|
|
9875
|
+
if (!isBlocked) continue;
|
|
9876
|
+
if (issue.dependencyIssueIds.length === 0) continue;
|
|
9877
|
+
const depResults = await Promise.all(
|
|
9878
|
+
issue.dependencyIssueIds.map(
|
|
9879
|
+
(depId) => hasLocalIssueMergeReceipt(prdId, depId, root)
|
|
9880
|
+
)
|
|
9881
|
+
);
|
|
9882
|
+
if (!depResults.every(Boolean)) continue;
|
|
9883
|
+
const updated = readIssueArtifact(root, prdId, issue.id);
|
|
9884
|
+
if (!updated) continue;
|
|
9885
|
+
updated.blockedReason = null;
|
|
9886
|
+
updated.labels = updated.labels.filter((l) => l !== "blocked");
|
|
9887
|
+
if (!updated.labels.includes("ready-for-agent")) {
|
|
9888
|
+
updated.labels.push("ready-for-agent");
|
|
9889
|
+
}
|
|
9890
|
+
if (hasValidLabels(updated.labels)) {
|
|
9891
|
+
updated.status = "ready-for-agent";
|
|
9892
|
+
} else {
|
|
9893
|
+
updated.status = "needs-triage";
|
|
9894
|
+
updated.labels = updated.labels.filter((l) => l !== "ready-for-agent");
|
|
9895
|
+
if (!updated.labels.includes("needs-triage")) {
|
|
9896
|
+
updated.labels.push("needs-triage");
|
|
9897
|
+
}
|
|
9898
|
+
}
|
|
9899
|
+
updated.receipts.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9900
|
+
writeIssueArtifact(root, prdId, updated);
|
|
9901
|
+
}
|
|
9902
|
+
}
|
|
9903
|
+
async function cleanupChildGitHubIssue(issue, issueProvider) {
|
|
9904
|
+
const issueNumber = issue.githubProjection.issueNumber;
|
|
9905
|
+
if (!issueNumber) return { ok: true };
|
|
9906
|
+
if (!issueProvider) {
|
|
9907
|
+
return {
|
|
9908
|
+
ok: false,
|
|
9909
|
+
failureCode: "missing_issue_provider",
|
|
9910
|
+
repairGuidance: "Issue provider is not available. Cannot close child GitHub Issue or remove labels. Configure a GitHub Issue provider to enable cleanup parity."
|
|
9911
|
+
};
|
|
9912
|
+
}
|
|
9913
|
+
for (const label of CHILD_CLEANUP_LABELS) {
|
|
9914
|
+
try {
|
|
9915
|
+
await issueProvider.removeLabel(issueNumber, label);
|
|
9916
|
+
} catch {
|
|
9917
|
+
}
|
|
9918
|
+
}
|
|
9919
|
+
try {
|
|
9920
|
+
await issueProvider.closeIssue(issueNumber);
|
|
9921
|
+
} catch {
|
|
9922
|
+
}
|
|
9923
|
+
return { ok: true };
|
|
9924
|
+
}
|
|
9925
|
+
async function runLocalQueueLoop(prdId, repoRoot2, issueProvider) {
|
|
9926
|
+
const root = repoRoot2 ?? process.cwd();
|
|
9927
|
+
const completedIssues = [];
|
|
9928
|
+
const blockedIssues = [];
|
|
9929
|
+
await reconcileLocalBlockedIssues(prdId, root);
|
|
9930
|
+
for (; ; ) {
|
|
9931
|
+
const runnable = await getRunnableLocalIssues(prdId, root);
|
|
9932
|
+
if (runnable.length === 0) {
|
|
9933
|
+
const issuesResult = await resolveLocalIssueArtifacts(prdId, root);
|
|
9934
|
+
if (issuesResult.ok) {
|
|
9935
|
+
for (const issue2 of issuesResult.data) {
|
|
9936
|
+
if (issue2.status === "blocked") {
|
|
9937
|
+
blockedIssues.push(issue2.id);
|
|
9938
|
+
}
|
|
9939
|
+
}
|
|
9940
|
+
}
|
|
9941
|
+
break;
|
|
9942
|
+
}
|
|
9943
|
+
const issue = runnable[0];
|
|
9944
|
+
const runResult = await runLocalIssue(prdId, issue.id, void 0, root);
|
|
9945
|
+
if (!runResult.ok) {
|
|
9946
|
+
return {
|
|
9947
|
+
ok: false,
|
|
9948
|
+
completedIssues,
|
|
9949
|
+
blockedIssues,
|
|
9950
|
+
failureCode: runResult.failureCode ?? `Issue ${issue.id} failed`,
|
|
9951
|
+
repairGuidance: `Local issue run failed for ${issue.id}. Check the issue artifact and branch state.`,
|
|
9952
|
+
blockedGate: "queue"
|
|
9953
|
+
};
|
|
9954
|
+
}
|
|
9955
|
+
const mergeResult = await squashMergeLocalIssue(
|
|
9956
|
+
prdId,
|
|
9957
|
+
issue.id,
|
|
9958
|
+
void 0,
|
|
9959
|
+
root
|
|
9960
|
+
);
|
|
9961
|
+
if (!mergeResult.ok) {
|
|
9962
|
+
if (mergeResult.failureCode === "already_merged") {
|
|
9963
|
+
const alreadyMergedArtifact = readIssueArtifact(root, prdId, issue.id);
|
|
9964
|
+
if (alreadyMergedArtifact) {
|
|
9965
|
+
alreadyMergedArtifact.status = "complete";
|
|
9966
|
+
alreadyMergedArtifact.receipts.completedAt = mergeResult.receipt?.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
9967
|
+
alreadyMergedArtifact.receipts.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9968
|
+
writeIssueArtifact(root, prdId, alreadyMergedArtifact);
|
|
9969
|
+
const cleanupResult = await cleanupChildGitHubIssue(
|
|
9970
|
+
alreadyMergedArtifact,
|
|
9971
|
+
issueProvider
|
|
9972
|
+
);
|
|
9973
|
+
if (!cleanupResult.ok) {
|
|
9974
|
+
return {
|
|
9975
|
+
ok: false,
|
|
9976
|
+
completedIssues,
|
|
9977
|
+
blockedIssues,
|
|
9978
|
+
failureCode: cleanupResult.failureCode ?? "child_cleanup_failed",
|
|
9979
|
+
repairGuidance: cleanupResult.repairGuidance ?? `Child GitHub Issue cleanup failed for ${issue.id}.`,
|
|
9980
|
+
blockedGate: "queue"
|
|
9981
|
+
};
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
completedIssues.push(issue.id);
|
|
9985
|
+
await reconcileLocalBlockedIssues(prdId, root);
|
|
9986
|
+
continue;
|
|
9987
|
+
}
|
|
9988
|
+
return {
|
|
9989
|
+
ok: false,
|
|
9990
|
+
completedIssues,
|
|
9991
|
+
blockedIssues,
|
|
9992
|
+
failureCode: mergeResult.failureCode ?? `Merge of ${issue.id} failed`,
|
|
9993
|
+
repairGuidance: mergeResult.repairGuidance,
|
|
9994
|
+
blockedGate: "queue"
|
|
9995
|
+
};
|
|
9996
|
+
}
|
|
9997
|
+
const completedArtifact = readIssueArtifact(root, prdId, issue.id);
|
|
9998
|
+
if (completedArtifact) {
|
|
9999
|
+
completedArtifact.status = "complete";
|
|
10000
|
+
completedArtifact.receipts.completedAt = mergeResult.receipt?.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10001
|
+
completedArtifact.receipts.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10002
|
+
writeIssueArtifact(root, prdId, completedArtifact);
|
|
10003
|
+
const cleanupResult = await cleanupChildGitHubIssue(
|
|
10004
|
+
completedArtifact,
|
|
10005
|
+
issueProvider
|
|
10006
|
+
);
|
|
10007
|
+
if (!cleanupResult.ok) {
|
|
10008
|
+
return {
|
|
10009
|
+
ok: false,
|
|
10010
|
+
completedIssues,
|
|
10011
|
+
blockedIssues,
|
|
10012
|
+
failureCode: cleanupResult.failureCode ?? "child_cleanup_failed",
|
|
10013
|
+
repairGuidance: cleanupResult.repairGuidance ?? `Child GitHub Issue cleanup failed for ${issue.id}.`,
|
|
10014
|
+
blockedGate: "queue"
|
|
10015
|
+
};
|
|
10016
|
+
}
|
|
10017
|
+
}
|
|
10018
|
+
completedIssues.push(issue.id);
|
|
10019
|
+
await reconcileLocalBlockedIssues(prdId, root);
|
|
10020
|
+
}
|
|
10021
|
+
return {
|
|
10022
|
+
ok: true,
|
|
10023
|
+
completedIssues,
|
|
10024
|
+
blockedIssues
|
|
10025
|
+
};
|
|
10026
|
+
}
|
|
10027
|
+
|
|
9205
10028
|
// commands/queue.ts
|
|
9206
10029
|
init_common();
|
|
9207
10030
|
import { Effect as Effect8 } from "effect";
|
|
@@ -9507,6 +10330,7 @@ function runOneQueueIssueEffect(options) {
|
|
|
9507
10330
|
}
|
|
9508
10331
|
const { issue: selected } = outcome;
|
|
9509
10332
|
const baseBranchOverride = options.queueRunContext?.prdBranch;
|
|
10333
|
+
const prdRunMode = options.prdRunMode ?? options.queueRunContext?.prdRunMode;
|
|
9510
10334
|
const runResult = yield* Effect8.tryPromise({
|
|
9511
10335
|
try: () => runIssueCommand({
|
|
9512
10336
|
issueNumber: selected.number,
|
|
@@ -9518,7 +10342,8 @@ function runOneQueueIssueEffect(options) {
|
|
|
9518
10342
|
force,
|
|
9519
10343
|
logger,
|
|
9520
10344
|
repoRoot: repoRoot2,
|
|
9521
|
-
...baseBranchOverride ? { baseBranchOverride } : {}
|
|
10345
|
+
...baseBranchOverride ? { baseBranchOverride } : {},
|
|
10346
|
+
...prdRunMode ? { prdRunMode } : {}
|
|
9522
10347
|
}),
|
|
9523
10348
|
catch: (e) => {
|
|
9524
10349
|
if (e instanceof Error) return e;
|
|
@@ -9529,6 +10354,10 @@ function runOneQueueIssueEffect(options) {
|
|
|
9529
10354
|
logger.raw(` Branch: ${runResult.branchName}`);
|
|
9530
10355
|
if (runResult.noOp) {
|
|
9531
10356
|
logger.raw(" Status: no-op (closed without PR)");
|
|
10357
|
+
} else if (runResult.mode === "local") {
|
|
10358
|
+
logger.raw(` Local PRD Branch: ${runResult.localPrdBranch}`);
|
|
10359
|
+
logger.raw(` Merge Commit: ${runResult.mergeCommit}`);
|
|
10360
|
+
logger.raw(` Receipt: ${runResult.receiptPath}`);
|
|
9532
10361
|
} else {
|
|
9533
10362
|
logger.raw(` PR Title: ${runResult.prTitle}`);
|
|
9534
10363
|
logger.raw(` PR Number: ${runResult.prNumber}`);
|
|
@@ -9588,7 +10417,8 @@ async function runQueueCommand(options) {
|
|
|
9588
10417
|
logger: options.logger,
|
|
9589
10418
|
repoRoot: options.repoRoot,
|
|
9590
10419
|
prdRef: options.prdRef,
|
|
9591
|
-
queueRunContext: options.queueRunContext
|
|
10420
|
+
queueRunContext: options.queueRunContext,
|
|
10421
|
+
prdRunMode: options.queueRunContext?.prdRunMode
|
|
9592
10422
|
};
|
|
9593
10423
|
if (!options.loop) {
|
|
9594
10424
|
return runEffectAndMapExit(runQueue(queueOptions));
|
|
@@ -9717,7 +10547,7 @@ function validateChangedPlanningPaths(paths, options) {
|
|
|
9717
10547
|
continue;
|
|
9718
10548
|
}
|
|
9719
10549
|
if (options?.repoRoot) {
|
|
9720
|
-
const fullPath =
|
|
10550
|
+
const fullPath = join21(options.repoRoot, p);
|
|
9721
10551
|
try {
|
|
9722
10552
|
if (lstatSync(fullPath).isSymbolicLink()) {
|
|
9723
10553
|
rejected.push(p);
|
|
@@ -9742,7 +10572,7 @@ function assessStaleSucceededReconciliationReceipt(options) {
|
|
|
9742
10572
|
if (!receipt || receipt.status !== "succeeded" || receipt.prNumber || receipt.mergeCommit) {
|
|
9743
10573
|
return { mode: "none" };
|
|
9744
10574
|
}
|
|
9745
|
-
const observedDiff =
|
|
10575
|
+
const observedDiff = collectObservedReconciliationDirtyPaths({
|
|
9746
10576
|
worktreeCwd: options.worktreeCwd,
|
|
9747
10577
|
ignoredPrdRunRecordRef: options.prdRef
|
|
9748
10578
|
});
|
|
@@ -9753,23 +10583,14 @@ function assessStaleSucceededReconciliationReceipt(options) {
|
|
|
9753
10583
|
offendingPaths: []
|
|
9754
10584
|
};
|
|
9755
10585
|
}
|
|
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) {
|
|
10586
|
+
if (observedDiff.dirtyPaths.length === 0) {
|
|
9766
10587
|
return { mode: "none" };
|
|
9767
10588
|
}
|
|
9768
10589
|
return {
|
|
9769
10590
|
mode: "recoverable",
|
|
9770
|
-
|
|
10591
|
+
dirtyPaths: observedDiff.dirtyPaths,
|
|
9771
10592
|
diagnostics: [
|
|
9772
|
-
"Recovered stale reconciliation success receipt from
|
|
10593
|
+
"Recovered stale reconciliation success receipt from dirty reconciliation worktree."
|
|
9773
10594
|
]
|
|
9774
10595
|
};
|
|
9775
10596
|
}
|
|
@@ -9837,6 +10658,12 @@ async function validateFinalReviewChildCompleteness(repoRoot2, prdRef, record, i
|
|
|
9837
10658
|
);
|
|
9838
10659
|
continue;
|
|
9839
10660
|
}
|
|
10661
|
+
if (!issueData || typeof issueData.state !== "string") {
|
|
10662
|
+
incompleteChildren.push(
|
|
10663
|
+
`${child.id} (#${child.number}): failed to fetch issue state`
|
|
10664
|
+
);
|
|
10665
|
+
continue;
|
|
10666
|
+
}
|
|
9840
10667
|
if (issueData.state === "closed") {
|
|
9841
10668
|
continue;
|
|
9842
10669
|
}
|
|
@@ -9893,7 +10720,7 @@ function canRetryFinalReviewBlock(record) {
|
|
|
9893
10720
|
}
|
|
9894
10721
|
function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
9895
10722
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
9896
|
-
return
|
|
10723
|
+
return existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : promptTemplate;
|
|
9897
10724
|
}
|
|
9898
10725
|
function formatVerificationCommands(commands) {
|
|
9899
10726
|
if (commands.length === 0) return "No verification commands configured.";
|
|
@@ -9906,12 +10733,14 @@ function buildFinalReviewPrompt(options) {
|
|
|
9906
10733
|
`Worktree checkout base: ${options.evidencePacket.prdBranch}`,
|
|
9907
10734
|
`Review only this range: ${options.evidencePacket.mergeBase}..HEAD`,
|
|
9908
10735
|
"Do not compare against current target branch HEAD; the merge base is the review baseline.",
|
|
9909
|
-
`Before handoff, run:
|
|
10736
|
+
`Before handoff, run: pourkit prd-run validate-final-review ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
|
|
9910
10737
|
"Fix any validation failures before handing off.",
|
|
9911
10738
|
"",
|
|
9912
|
-
"##
|
|
10739
|
+
"## Verification",
|
|
10740
|
+
"",
|
|
10741
|
+
`Run this command before handoff when retouch changes are made: pourkit run-verification --target ${options.targetName}`,
|
|
9913
10742
|
"",
|
|
9914
|
-
"
|
|
10743
|
+
"Underlying project commands:",
|
|
9915
10744
|
formatVerificationCommands(options.verificationCommands),
|
|
9916
10745
|
"",
|
|
9917
10746
|
"Evidence Packet (do not infer PRD context from local state files):",
|
|
@@ -9938,14 +10767,14 @@ function buildReconciliationPrompt(options) {
|
|
|
9938
10767
|
options.artifactPath,
|
|
9939
10768
|
"",
|
|
9940
10769
|
"Self-validation command:",
|
|
9941
|
-
`validate-artifact reconciliation ${options.artifactPath}`
|
|
10770
|
+
`pourkit validate-artifact reconciliation ${options.artifactPath}`
|
|
9942
10771
|
].join("\n");
|
|
9943
10772
|
}
|
|
9944
10773
|
function buildReconciliationBranchName(prdRef) {
|
|
9945
10774
|
return `${normalizePrdRunRef2(prdRef)}-reconciliation`;
|
|
9946
10775
|
}
|
|
9947
10776
|
function reconciliationWorktreePath(repoRoot2, branchName) {
|
|
9948
|
-
return
|
|
10777
|
+
return join21(repoRoot2, ".sandcastle", "worktrees", branchName);
|
|
9949
10778
|
}
|
|
9950
10779
|
function reconciliationReceiptWorktreePath(branchName) {
|
|
9951
10780
|
return `.sandcastle/worktrees/${branchName}`;
|
|
@@ -9957,7 +10786,7 @@ function buildFinalReviewBranchName(prdRef) {
|
|
|
9957
10786
|
return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
|
|
9958
10787
|
}
|
|
9959
10788
|
function finalReviewWorktreePath(repoRoot2, branchName) {
|
|
9960
|
-
return
|
|
10789
|
+
return join21(
|
|
9961
10790
|
repoRoot2,
|
|
9962
10791
|
".sandcastle",
|
|
9963
10792
|
"worktrees",
|
|
@@ -9980,7 +10809,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
|
|
|
9980
10809
|
}
|
|
9981
10810
|
paths.add(path9);
|
|
9982
10811
|
};
|
|
9983
|
-
const diffResult =
|
|
10812
|
+
const diffResult = spawnSync3(
|
|
9984
10813
|
"git",
|
|
9985
10814
|
["diff", "--name-only", mergeBase, "--", "."],
|
|
9986
10815
|
{
|
|
@@ -9991,7 +10820,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
|
|
|
9991
10820
|
for (const path9 of diffResult.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
9992
10821
|
addPath(path9);
|
|
9993
10822
|
}
|
|
9994
|
-
const statusResult =
|
|
10823
|
+
const statusResult = spawnSync3(
|
|
9995
10824
|
"git",
|
|
9996
10825
|
["status", "--porcelain", "--untracked-files=all"],
|
|
9997
10826
|
{
|
|
@@ -10012,7 +10841,7 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
10012
10841
|
options.repoRoot,
|
|
10013
10842
|
options.promptTemplate
|
|
10014
10843
|
);
|
|
10015
|
-
const promptBody =
|
|
10844
|
+
const promptBody = existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : options.promptTemplate;
|
|
10016
10845
|
return [
|
|
10017
10846
|
"# Final Review Retouch PR Finalizer",
|
|
10018
10847
|
"",
|
|
@@ -10046,7 +10875,7 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
10046
10875
|
}
|
|
10047
10876
|
async function runFinalReviewPrFinalizer(options) {
|
|
10048
10877
|
const finalizer = options.target.strategy.finalize.prDescriptionAgent;
|
|
10049
|
-
const artifactPathInWorktree =
|
|
10878
|
+
const artifactPathInWorktree = join21(
|
|
10050
10879
|
".pourkit",
|
|
10051
10880
|
".tmp",
|
|
10052
10881
|
"finalizer",
|
|
@@ -10092,7 +10921,7 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10092
10921
|
options.repoRoot,
|
|
10093
10922
|
options.branchName
|
|
10094
10923
|
);
|
|
10095
|
-
const listResult =
|
|
10924
|
+
const listResult = spawnSync3("git", ["worktree", "list", "--porcelain"], {
|
|
10096
10925
|
cwd: options.repoRoot,
|
|
10097
10926
|
encoding: "utf8"
|
|
10098
10927
|
});
|
|
@@ -10120,15 +10949,15 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10120
10949
|
}
|
|
10121
10950
|
return { ok: true, worktreePath: registeredPath };
|
|
10122
10951
|
}
|
|
10123
|
-
if (
|
|
10952
|
+
if (existsSync17(worktreePath)) {
|
|
10124
10953
|
return {
|
|
10125
10954
|
ok: false,
|
|
10126
10955
|
reason: "Final Review worktree path exists but is not registered with git.",
|
|
10127
10956
|
diagnostics: [`stale worktree path: ${worktreePath}`]
|
|
10128
10957
|
};
|
|
10129
10958
|
}
|
|
10130
|
-
|
|
10131
|
-
const branchResult =
|
|
10959
|
+
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
10960
|
+
const branchResult = spawnSync3(
|
|
10132
10961
|
"git",
|
|
10133
10962
|
["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
|
|
10134
10963
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -10140,7 +10969,7 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10140
10969
|
diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
|
|
10141
10970
|
};
|
|
10142
10971
|
}
|
|
10143
|
-
const addResult =
|
|
10972
|
+
const addResult = spawnSync3(
|
|
10144
10973
|
"git",
|
|
10145
10974
|
["worktree", "add", worktreePath, options.branchName],
|
|
10146
10975
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -10182,7 +11011,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10182
11011
|
if (existingPr && existingPr.state === "OPEN") {
|
|
10183
11012
|
return existingPr;
|
|
10184
11013
|
}
|
|
10185
|
-
const worktreePath = mkdtempSync(
|
|
11014
|
+
const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-retouch-"));
|
|
10186
11015
|
try {
|
|
10187
11016
|
runGitOrThrow(
|
|
10188
11017
|
options.repoRoot,
|
|
@@ -10200,9 +11029,9 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10200
11029
|
"create retouch branch"
|
|
10201
11030
|
);
|
|
10202
11031
|
for (const changedFile of options.changedPaths) {
|
|
10203
|
-
const sourcePath =
|
|
10204
|
-
const targetPath =
|
|
10205
|
-
|
|
11032
|
+
const sourcePath = join21(options.sourceWorktreePath, changedFile);
|
|
11033
|
+
const targetPath = join21(worktreePath, changedFile);
|
|
11034
|
+
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
10206
11035
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
10207
11036
|
}
|
|
10208
11037
|
runGitOrThrow(
|
|
@@ -10216,13 +11045,13 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10216
11045
|
"commit retouch diff"
|
|
10217
11046
|
);
|
|
10218
11047
|
} finally {
|
|
10219
|
-
|
|
11048
|
+
spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
|
|
10220
11049
|
cwd: options.repoRoot,
|
|
10221
11050
|
encoding: "utf8"
|
|
10222
11051
|
});
|
|
10223
11052
|
rmSync3(worktreePath, { recursive: true, force: true });
|
|
10224
11053
|
}
|
|
10225
|
-
const pushResult =
|
|
11054
|
+
const pushResult = spawnSync3(
|
|
10226
11055
|
"git",
|
|
10227
11056
|
["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
|
|
10228
11057
|
{
|
|
@@ -10556,6 +11385,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10556
11385
|
prompt: buildFinalReviewPrompt({
|
|
10557
11386
|
repoRoot: options.repoRoot,
|
|
10558
11387
|
promptTemplate: finalReviewConfig.promptTemplate,
|
|
11388
|
+
targetName,
|
|
10559
11389
|
evidencePacket,
|
|
10560
11390
|
verificationCommands
|
|
10561
11391
|
}),
|
|
@@ -10631,7 +11461,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10631
11461
|
};
|
|
10632
11462
|
}
|
|
10633
11463
|
const resolvedWorktreePath = executionResult.worktreePath;
|
|
10634
|
-
const artifactPath =
|
|
11464
|
+
const artifactPath = join21(
|
|
10635
11465
|
resolvedWorktreePath,
|
|
10636
11466
|
".pourkit/final-review-artifact.json"
|
|
10637
11467
|
);
|
|
@@ -11215,7 +12045,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11215
12045
|
function runPrdRunValidateFinalReviewCommand(options) {
|
|
11216
12046
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
11217
12047
|
const checkoutBase = options.checkoutBase ?? prdRef;
|
|
11218
|
-
const artifactPath = options.artifactPath ? options.artifactPath :
|
|
12048
|
+
const artifactPath = options.artifactPath ? options.artifactPath : join21(options.repoRoot, ".pourkit", "final-review-artifact.json");
|
|
11219
12049
|
const artifact = parseFinalReviewArtifact(artifactPath);
|
|
11220
12050
|
if (!artifact.ok) {
|
|
11221
12051
|
return {
|
|
@@ -11229,7 +12059,7 @@ function runPrdRunValidateFinalReviewCommand(options) {
|
|
|
11229
12059
|
}
|
|
11230
12060
|
let changedPaths = options.changedPaths;
|
|
11231
12061
|
if (artifact.verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && (!artifact.changedPaths || artifact.changedPaths.length === 0)) {
|
|
11232
|
-
const diffResult =
|
|
12062
|
+
const diffResult = spawnSync3(
|
|
11233
12063
|
"git",
|
|
11234
12064
|
["diff", "--name-only", options.reviewBase, "HEAD", "--", "."],
|
|
11235
12065
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -11381,12 +12211,12 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11381
12211
|
);
|
|
11382
12212
|
const worktreeCwd = worktreePath;
|
|
11383
12213
|
const existingReconciliationReceipt = record.reconciliation;
|
|
11384
|
-
const existingLocalBranch =
|
|
12214
|
+
const existingLocalBranch = spawnSync3(
|
|
11385
12215
|
"git",
|
|
11386
12216
|
["rev-parse", "--verify", "--quiet", reconciliationBranchName],
|
|
11387
12217
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
11388
12218
|
).status === 0;
|
|
11389
|
-
const existingWorktree =
|
|
12219
|
+
const existingWorktree = spawnSync3(
|
|
11390
12220
|
"git",
|
|
11391
12221
|
["worktree", "list", "--porcelain"],
|
|
11392
12222
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -11439,7 +12269,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11439
12269
|
branchAction: "blocked"
|
|
11440
12270
|
};
|
|
11441
12271
|
}
|
|
11442
|
-
const resetResult =
|
|
12272
|
+
const resetResult = spawnSync3(
|
|
11443
12273
|
"git",
|
|
11444
12274
|
["reset", "--hard", `origin/${prdBranch}`],
|
|
11445
12275
|
{ cwd: worktreeCwd, encoding: "utf8" }
|
|
@@ -11482,7 +12312,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11482
12312
|
);
|
|
11483
12313
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11484
12314
|
}
|
|
11485
|
-
const adoptionCheck =
|
|
12315
|
+
const adoptionCheck = spawnSync3(
|
|
11486
12316
|
"git",
|
|
11487
12317
|
[
|
|
11488
12318
|
"merge-base",
|
|
@@ -11523,14 +12353,14 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11523
12353
|
);
|
|
11524
12354
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11525
12355
|
}
|
|
11526
|
-
if (
|
|
12356
|
+
if (existsSync17(worktreePath)) {
|
|
11527
12357
|
diagnostics.push(
|
|
11528
12358
|
"Reconciliation worktree path exists but is not registered with git.",
|
|
11529
12359
|
`stale worktree path: ${worktreePath}`
|
|
11530
12360
|
);
|
|
11531
12361
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11532
12362
|
}
|
|
11533
|
-
const branchResult =
|
|
12363
|
+
const branchResult = spawnSync3(
|
|
11534
12364
|
"git",
|
|
11535
12365
|
["branch", "-f", reconciliationBranchName, `origin/${prdBranch}`],
|
|
11536
12366
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -11544,14 +12374,14 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11544
12374
|
);
|
|
11545
12375
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11546
12376
|
}
|
|
11547
|
-
|
|
11548
|
-
const addResult =
|
|
12377
|
+
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
12378
|
+
const addResult = spawnSync3(
|
|
11549
12379
|
"git",
|
|
11550
12380
|
["worktree", "add", worktreePath, reconciliationBranchName],
|
|
11551
12381
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
11552
12382
|
);
|
|
11553
12383
|
if (addResult.status !== 0) {
|
|
11554
|
-
|
|
12384
|
+
spawnSync3("git", ["branch", "-D", reconciliationBranchName], {
|
|
11555
12385
|
cwd: options.repoRoot,
|
|
11556
12386
|
encoding: "utf8"
|
|
11557
12387
|
});
|
|
@@ -11599,7 +12429,7 @@ function buildReconciliationPrBody(options) {
|
|
|
11599
12429
|
function commitAndPushReconciliationChanges(options) {
|
|
11600
12430
|
const diagnostics = [];
|
|
11601
12431
|
const existingPaths = options.changedPlanningPaths.filter((p) => {
|
|
11602
|
-
return
|
|
12432
|
+
return existsSync17(join21(options.worktreeCwd, p));
|
|
11603
12433
|
});
|
|
11604
12434
|
if (existingPaths.length === 0) {
|
|
11605
12435
|
diagnostics.push(
|
|
@@ -11607,7 +12437,7 @@ function commitAndPushReconciliationChanges(options) {
|
|
|
11607
12437
|
);
|
|
11608
12438
|
return { ok: true, diagnostics: [...diagnostics] };
|
|
11609
12439
|
}
|
|
11610
|
-
const addResult =
|
|
12440
|
+
const addResult = spawnSync3("git", ["add", "--", ...existingPaths], {
|
|
11611
12441
|
cwd: options.worktreeCwd,
|
|
11612
12442
|
encoding: "utf8"
|
|
11613
12443
|
});
|
|
@@ -11624,7 +12454,7 @@ function commitAndPushReconciliationChanges(options) {
|
|
|
11624
12454
|
};
|
|
11625
12455
|
}
|
|
11626
12456
|
const commitTitle = `docs: reconcile ${normalizePrdRunRef2(options.prdRef)} architecture`;
|
|
11627
|
-
const commitResult =
|
|
12457
|
+
const commitResult = spawnSync3("git", ["commit", "-m", commitTitle], {
|
|
11628
12458
|
cwd: options.worktreeCwd,
|
|
11629
12459
|
encoding: "utf8"
|
|
11630
12460
|
});
|
|
@@ -11648,7 +12478,7 @@ function commitAndPushReconciliationChanges(options) {
|
|
|
11648
12478
|
};
|
|
11649
12479
|
}
|
|
11650
12480
|
}
|
|
11651
|
-
const pushResult =
|
|
12481
|
+
const pushResult = spawnSync3(
|
|
11652
12482
|
"git",
|
|
11653
12483
|
[
|
|
11654
12484
|
"push",
|
|
@@ -11742,7 +12572,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
11742
12572
|
const record = preflightResult.record;
|
|
11743
12573
|
const prdBranch = record?.prdBranch ?? prdRef;
|
|
11744
12574
|
const mergeBase = preflightResult.mergeBase;
|
|
11745
|
-
const worktreeCwd = preflightResult.worktreePath ?
|
|
12575
|
+
const worktreeCwd = preflightResult.worktreePath ? join21(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
|
|
11746
12576
|
const staleSucceededRecovery = assessStaleSucceededReconciliationReceipt({
|
|
11747
12577
|
receipt: record?.reconciliation,
|
|
11748
12578
|
worktreeCwd,
|
|
@@ -12044,7 +12874,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12044
12874
|
baseRef: prdBranch,
|
|
12045
12875
|
checkoutBase,
|
|
12046
12876
|
reviewBase: mergeBase,
|
|
12047
|
-
worktreePath:
|
|
12877
|
+
worktreePath: join21(options.repoRoot, preflightResult.worktreePath),
|
|
12048
12878
|
sandbox: options.config.sandbox,
|
|
12049
12879
|
artifactPath,
|
|
12050
12880
|
logger: options.logger
|
|
@@ -12145,7 +12975,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12145
12975
|
offendingPaths: []
|
|
12146
12976
|
};
|
|
12147
12977
|
}
|
|
12148
|
-
const fullArtifactPath =
|
|
12978
|
+
const fullArtifactPath = join21(executionResult.worktreePath, artifactPath);
|
|
12149
12979
|
let artifactContent;
|
|
12150
12980
|
try {
|
|
12151
12981
|
artifactContent = JSON.parse(retryResult.artifact.value);
|
|
@@ -12252,11 +13082,11 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12252
13082
|
const artifactResult = artifactContent.result ?? "changes_produced";
|
|
12253
13083
|
const artifactChangedPaths = artifactContent.changedPlanningPaths ?? [];
|
|
12254
13084
|
const artifactNoChangeRationale = artifactContent.noChangeRationale;
|
|
12255
|
-
const gitStatusResult =
|
|
13085
|
+
const gitStatusResult = spawnSync3("git", ["status", "--short"], {
|
|
12256
13086
|
cwd: worktreeCwd,
|
|
12257
13087
|
encoding: "utf8"
|
|
12258
13088
|
});
|
|
12259
|
-
const gitDiffResult =
|
|
13089
|
+
const gitDiffResult = spawnSync3("git", ["diff", "--name-status"], {
|
|
12260
13090
|
cwd: worktreeCwd,
|
|
12261
13091
|
encoding: "utf8"
|
|
12262
13092
|
});
|
|
@@ -12267,11 +13097,27 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12267
13097
|
gitStatusShortLines,
|
|
12268
13098
|
gitDiffNameStatusLines
|
|
12269
13099
|
});
|
|
13100
|
+
const observedWorktreeDiff = collectObservedReconciliationDirtyPaths({
|
|
13101
|
+
worktreeCwd,
|
|
13102
|
+
ignoredPrdRunRecordRef: prdRef
|
|
13103
|
+
});
|
|
12270
13104
|
let outcomeResult;
|
|
12271
13105
|
let outcomeDiagnostics = [];
|
|
12272
13106
|
let outcomeChangedPaths = [];
|
|
12273
13107
|
let outcomeRationale;
|
|
12274
|
-
if (
|
|
13108
|
+
if (!observedWorktreeDiff.ok) {
|
|
13109
|
+
outcomeResult = "blocked";
|
|
13110
|
+
outcomeDiagnostics.push(...observedWorktreeDiff.diagnostics);
|
|
13111
|
+
} else if (observedWorktreeDiff.dirtyPaths.length > 0) {
|
|
13112
|
+
outcomeResult = "changes_produced";
|
|
13113
|
+
outcomeChangedPaths = [...observedWorktreeDiff.dirtyPaths];
|
|
13114
|
+
outcomeRationale = void 0;
|
|
13115
|
+
if (artifactResult === "no_changes_needed") {
|
|
13116
|
+
outcomeDiagnostics.push(
|
|
13117
|
+
"Dirty reconciliation worktree observed after agent run; treating reconciliation result as changes_produced."
|
|
13118
|
+
);
|
|
13119
|
+
}
|
|
13120
|
+
} else if (artifactResult === "no_changes_needed") {
|
|
12275
13121
|
outcomeResult = "no_changes_needed";
|
|
12276
13122
|
outcomeChangedPaths = [];
|
|
12277
13123
|
outcomeRationale = artifactNoChangeRationale ?? "No changes needed.";
|
|
@@ -12292,11 +13138,11 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12292
13138
|
}
|
|
12293
13139
|
if (staleSucceededRecovery.mode === "recoverable" && artifactResult === "no_changes_needed") {
|
|
12294
13140
|
outcomeResult = "changes_produced";
|
|
12295
|
-
outcomeChangedPaths = [...staleSucceededRecovery.
|
|
13141
|
+
outcomeChangedPaths = [...staleSucceededRecovery.dirtyPaths];
|
|
12296
13142
|
outcomeRationale = void 0;
|
|
12297
13143
|
outcomeDiagnostics.push(...staleSucceededRecovery.diagnostics);
|
|
12298
13144
|
}
|
|
12299
|
-
if (!pathValidation.ok && outcomeResult !== "blocked") {
|
|
13145
|
+
if (!pathValidation.ok && outcomeResult !== "blocked" && observedWorktreeDiff.ok && observedWorktreeDiff.dirtyPaths.length === 0) {
|
|
12300
13146
|
outcomeResult = "blocked";
|
|
12301
13147
|
outcomeDiagnostics.push(
|
|
12302
13148
|
`Unsafe changed planning paths: ${(pathValidation.rejected ?? []).join(", ")}`
|
|
@@ -12993,7 +13839,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12993
13839
|
}
|
|
12994
13840
|
function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
12995
13841
|
const normalized = normalizePrdRunRef2(prdRef);
|
|
12996
|
-
const fetchResult =
|
|
13842
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", "dev", normalized], {
|
|
12997
13843
|
cwd: repoRoot2,
|
|
12998
13844
|
encoding: "utf8"
|
|
12999
13845
|
});
|
|
@@ -13013,7 +13859,7 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
|
13013
13859
|
if (fetchResult.stderr?.toString?.()?.trim()) {
|
|
13014
13860
|
fetchDiagnostics.push(fetchResult.stderr.toString().trim());
|
|
13015
13861
|
}
|
|
13016
|
-
const mergeBaseResult =
|
|
13862
|
+
const mergeBaseResult = spawnSync3(
|
|
13017
13863
|
"git",
|
|
13018
13864
|
["merge-base", "origin/dev", `origin/${normalized}`],
|
|
13019
13865
|
{
|
|
@@ -13156,26 +14002,26 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13156
14002
|
const blockerBugs = [];
|
|
13157
14003
|
const blockersFixed = [];
|
|
13158
14004
|
const nonBlockers = [];
|
|
13159
|
-
const prdMirrorPath =
|
|
13160
|
-
const completionsDir =
|
|
14005
|
+
const prdMirrorPath = join21(repoRoot2, PRD_047_PRD_MIRROR_PATH);
|
|
14006
|
+
const completionsDir = join21(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
|
|
13161
14007
|
let escapeHatchPresent = false;
|
|
13162
|
-
if (
|
|
13163
|
-
const files =
|
|
14008
|
+
if (existsSync17(completionsDir)) {
|
|
14009
|
+
const files = readdirSync5(completionsDir);
|
|
13164
14010
|
escapeHatchPresent = files.some(
|
|
13165
14011
|
(f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
|
|
13166
14012
|
);
|
|
13167
14013
|
}
|
|
13168
14014
|
if (!escapeHatchPresent) {
|
|
13169
|
-
escapeHatchPresent =
|
|
14015
|
+
escapeHatchPresent = existsSync17(join21(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
|
|
13170
14016
|
}
|
|
13171
|
-
const prdMirrorExists =
|
|
14017
|
+
const prdMirrorExists = existsSync17(prdMirrorPath);
|
|
13172
14018
|
if (!prdMirrorExists) {
|
|
13173
14019
|
nonBlockers.push(
|
|
13174
14020
|
`PRD-0047 PRD mirror not found at ${PRD_047_PRD_MIRROR_PATH}. Cannot verify scope alignment from published mirrors.`
|
|
13175
14021
|
);
|
|
13176
14022
|
}
|
|
13177
14023
|
if (prdMirrorExists) {
|
|
13178
|
-
const mirrorContent =
|
|
14024
|
+
const mirrorContent = readFileSync17(prdMirrorPath, "utf-8");
|
|
13179
14025
|
const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
|
|
13180
14026
|
const hasEscapeHatch = mirrorContent.includes("escape hatch");
|
|
13181
14027
|
if (!hasReconcileCommand) {
|
|
@@ -13189,16 +14035,16 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13189
14035
|
);
|
|
13190
14036
|
}
|
|
13191
14037
|
}
|
|
13192
|
-
const childIssueDir =
|
|
13193
|
-
const childIssuesExist =
|
|
14038
|
+
const childIssueDir = join21(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
|
|
14039
|
+
const childIssuesExist = existsSync17(childIssueDir);
|
|
13194
14040
|
if (!childIssuesExist) {
|
|
13195
14041
|
nonBlockers.push(
|
|
13196
14042
|
`PRD-0047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
|
|
13197
14043
|
);
|
|
13198
14044
|
}
|
|
13199
|
-
const manifestPath =
|
|
13200
|
-
if (
|
|
13201
|
-
const manifestContent =
|
|
14045
|
+
const manifestPath = join21(repoRoot2, PRD_047_MANIFEST_PATH);
|
|
14046
|
+
if (existsSync17(manifestPath)) {
|
|
14047
|
+
const manifestContent = readFileSync17(manifestPath, "utf-8");
|
|
13202
14048
|
if (!manifestContent.includes("ready_for_prepare")) {
|
|
13203
14049
|
nonBlockers.push(
|
|
13204
14050
|
"Planning Artifact Manifest is not marked as ready_for_prepare."
|
|
@@ -13287,6 +14133,17 @@ function runPrdRunAuditCommand(options) {
|
|
|
13287
14133
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
13288
14134
|
return auditPrd047Implementation(options.repoRoot, prdRef);
|
|
13289
14135
|
}
|
|
14136
|
+
async function closeParentGitHubIssue(prdRef, repoRoot2, issueProvider) {
|
|
14137
|
+
const prdResult = await resolveLocalPrdArtifact(prdRef, repoRoot2);
|
|
14138
|
+
const parentIssueNumber = prdResult.ok ? prdResult.data.githubProjection.issueNumber : null;
|
|
14139
|
+
if (!parentIssueNumber) return { closed: false, issueNumber: null };
|
|
14140
|
+
try {
|
|
14141
|
+
await issueProvider.closeIssue(parentIssueNumber);
|
|
14142
|
+
return { closed: true, issueNumber: parentIssueNumber };
|
|
14143
|
+
} catch {
|
|
14144
|
+
return { closed: false, issueNumber: parentIssueNumber };
|
|
14145
|
+
}
|
|
14146
|
+
}
|
|
13290
14147
|
async function runPrdRunLaunchCommand(options) {
|
|
13291
14148
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
13292
14149
|
const attempted = [];
|
|
@@ -13298,10 +14155,11 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13298
14155
|
const terminal = /* @__PURE__ */ new Set([
|
|
13299
14156
|
"waiting_for_integration",
|
|
13300
14157
|
"finalizing",
|
|
13301
|
-
"complete"
|
|
14158
|
+
"complete",
|
|
14159
|
+
"completed_local_branch"
|
|
13302
14160
|
]);
|
|
13303
14161
|
if (terminal.has(existingRecord.record.status)) {
|
|
13304
|
-
|
|
14162
|
+
const result = {
|
|
13305
14163
|
prdRef,
|
|
13306
14164
|
status: existingRecord.record.status,
|
|
13307
14165
|
attempted: [],
|
|
@@ -13311,10 +14169,38 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13311
14169
|
`PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`
|
|
13312
14170
|
]
|
|
13313
14171
|
};
|
|
14172
|
+
if (existingRecord.record.status === "completed_local_branch" && existingRecord.record.prdBranch) {
|
|
14173
|
+
result.prdBranch = existingRecord.record.prdBranch;
|
|
14174
|
+
}
|
|
14175
|
+
return result;
|
|
13314
14176
|
}
|
|
13315
14177
|
}
|
|
13316
14178
|
if (existingRecord.record) {
|
|
13317
14179
|
const status = existingRecord.record.status;
|
|
14180
|
+
const resolvedLaunchMode = options.config ? (() => {
|
|
14181
|
+
try {
|
|
14182
|
+
const target = resolveTarget(options.config, options.targetName);
|
|
14183
|
+
return resolvePrdRunMode(target).mode;
|
|
14184
|
+
} catch {
|
|
14185
|
+
return void 0;
|
|
14186
|
+
}
|
|
14187
|
+
})() : void 0;
|
|
14188
|
+
if (resolvedLaunchMode && existingRecord.record.mode && existingRecord.record.mode !== resolvedLaunchMode) {
|
|
14189
|
+
return {
|
|
14190
|
+
prdRef,
|
|
14191
|
+
status: "blocked",
|
|
14192
|
+
attempted: [],
|
|
14193
|
+
skipped: ["prepare", "start", "queue", "final-review", "reconcile"],
|
|
14194
|
+
resumed: [],
|
|
14195
|
+
diagnostics: [
|
|
14196
|
+
`Recorded mode: ${existingRecord.record.mode}`,
|
|
14197
|
+
`Resolved mode: ${resolvedLaunchMode}`
|
|
14198
|
+
],
|
|
14199
|
+
blockedGate: "branch-state",
|
|
14200
|
+
blockedReason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedLaunchMode}". Resolve by using the correct target or --local-override.`,
|
|
14201
|
+
offendingPaths: []
|
|
14202
|
+
};
|
|
14203
|
+
}
|
|
13318
14204
|
if (status === "drained") {
|
|
13319
14205
|
skipped.push("prepare", "start", "queue");
|
|
13320
14206
|
resumed.push("final-review");
|
|
@@ -13442,17 +14328,59 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13442
14328
|
}
|
|
13443
14329
|
diagnostics.push(...startResult.diagnostics);
|
|
13444
14330
|
attempted.push("queue");
|
|
14331
|
+
if (startResult.status === "drained" && options.issueProvider && existsSync17(join21(options.repoRoot, ".pourkit", "local-prd-runs", prdRef))) {
|
|
14332
|
+
await closeParentGitHubIssue(
|
|
14333
|
+
prdRef,
|
|
14334
|
+
options.repoRoot,
|
|
14335
|
+
options.issueProvider
|
|
14336
|
+
);
|
|
14337
|
+
}
|
|
13445
14338
|
}
|
|
13446
14339
|
let finalReviewResult;
|
|
13447
14340
|
if (!skipped.includes("final-review")) {
|
|
13448
14341
|
attempted.push("final-review");
|
|
13449
|
-
const
|
|
14342
|
+
const localStorePath2 = join21(
|
|
13450
14343
|
options.repoRoot,
|
|
13451
14344
|
".pourkit",
|
|
13452
14345
|
"local-prd-runs",
|
|
13453
14346
|
prdRef
|
|
13454
14347
|
);
|
|
13455
|
-
if (
|
|
14348
|
+
if (existsSync17(localStorePath2)) {
|
|
14349
|
+
const prdRecord = readPrdRun(options.repoRoot, prdRef);
|
|
14350
|
+
if (prdRecord.record && prdRecord.record.status !== "drained" && !canRetryFinalReviewBlock(prdRecord.record)) {
|
|
14351
|
+
const reason = `Final Review blocked: Queue drain not completed. Status is "${prdRecord.record.status}". Finish all child Issues before Final Review.`;
|
|
14352
|
+
writePrdRunRecord(options.repoRoot, {
|
|
14353
|
+
prdRef,
|
|
14354
|
+
status: "blocked",
|
|
14355
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14356
|
+
blockedGate: "final-review",
|
|
14357
|
+
blockedReason: reason,
|
|
14358
|
+
diagnostics: [
|
|
14359
|
+
`Current status: ${prdRecord.record.status}. Queue drain receipt is missing. Run child Issues through queue-run before Final Review.`
|
|
14360
|
+
],
|
|
14361
|
+
targetName: options.targetName,
|
|
14362
|
+
offendingPaths: []
|
|
14363
|
+
});
|
|
14364
|
+
return {
|
|
14365
|
+
prdRef,
|
|
14366
|
+
status: "blocked",
|
|
14367
|
+
attempted,
|
|
14368
|
+
skipped: [
|
|
14369
|
+
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
14370
|
+
...skipped.includes("start") ? ["queue"] : [],
|
|
14371
|
+
"reconcile"
|
|
14372
|
+
],
|
|
14373
|
+
resumed,
|
|
14374
|
+
diagnostics: [
|
|
14375
|
+
`Current status: ${prdRecord.record.status}. Queue drain receipt is missing. Run child Issues through queue-run before Final Review.`
|
|
14376
|
+
],
|
|
14377
|
+
blockedGate: "final-review",
|
|
14378
|
+
blockedReason: reason,
|
|
14379
|
+
offendingPaths: [],
|
|
14380
|
+
prepare: prepareResult,
|
|
14381
|
+
start: startResult
|
|
14382
|
+
};
|
|
14383
|
+
}
|
|
13456
14384
|
const targetConfig = options.config?.targets?.find(
|
|
13457
14385
|
(t) => t.name === options.targetName
|
|
13458
14386
|
);
|
|
@@ -13491,9 +14419,48 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13491
14419
|
start: startResult
|
|
13492
14420
|
};
|
|
13493
14421
|
}
|
|
14422
|
+
if (localResult.verdict === "blocked" || localResult.verdict === "needs_human_review") {
|
|
14423
|
+
const reason = localResult.message ?? `Local Final Review verdict: ${localResult.verdict}`;
|
|
14424
|
+
writePrdRunRecord(options.repoRoot, {
|
|
14425
|
+
prdRef,
|
|
14426
|
+
status: "blocked",
|
|
14427
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14428
|
+
blockedGate: "final-review",
|
|
14429
|
+
blockedReason: reason,
|
|
14430
|
+
diagnostics: [reason],
|
|
14431
|
+
targetName: options.targetName,
|
|
14432
|
+
offendingPaths: []
|
|
14433
|
+
});
|
|
14434
|
+
return {
|
|
14435
|
+
prdRef,
|
|
14436
|
+
status: "blocked",
|
|
14437
|
+
attempted,
|
|
14438
|
+
skipped: [
|
|
14439
|
+
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
14440
|
+
...skipped.includes("start") ? ["queue"] : [],
|
|
14441
|
+
"reconcile"
|
|
14442
|
+
],
|
|
14443
|
+
resumed,
|
|
14444
|
+
diagnostics: [reason],
|
|
14445
|
+
blockedGate: "final-review",
|
|
14446
|
+
blockedReason: reason,
|
|
14447
|
+
offendingPaths: [],
|
|
14448
|
+
prepare: prepareResult,
|
|
14449
|
+
start: startResult
|
|
14450
|
+
};
|
|
14451
|
+
}
|
|
14452
|
+
let retouchMergeCommit;
|
|
14453
|
+
let changedPaths;
|
|
13494
14454
|
if (localResult.verdict === "pass_with_retouch") {
|
|
13495
14455
|
try {
|
|
13496
|
-
await squashFinalReviewRetouch(
|
|
14456
|
+
const evidence = await squashFinalReviewRetouch(
|
|
14457
|
+
prdRef,
|
|
14458
|
+
options.repoRoot
|
|
14459
|
+
);
|
|
14460
|
+
if (evidence) {
|
|
14461
|
+
retouchMergeCommit = evidence.mergeCommit;
|
|
14462
|
+
changedPaths = evidence.changedPaths;
|
|
14463
|
+
}
|
|
13497
14464
|
} catch (error) {
|
|
13498
14465
|
const reason = error instanceof Error ? error.message : String(error);
|
|
13499
14466
|
writePrdRunRecord(options.repoRoot, {
|
|
@@ -13525,14 +14492,16 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13525
14492
|
};
|
|
13526
14493
|
}
|
|
13527
14494
|
}
|
|
13528
|
-
const mappedVerdict = localResult.verdict === "pass" ? "pass_no_changes" : localResult.verdict;
|
|
13529
14495
|
const receipt = {
|
|
13530
14496
|
status: "final_reviewed",
|
|
13531
14497
|
targetName: options.targetName,
|
|
13532
14498
|
prdBranch: `local/${prdRef}`,
|
|
14499
|
+
mergeBase: localResult.receipt?.mergeBase,
|
|
13533
14500
|
diagnostics: [],
|
|
13534
14501
|
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13535
|
-
verdict:
|
|
14502
|
+
verdict: localResult.verdict ?? "pass_no_changes",
|
|
14503
|
+
retouchMergeCommit,
|
|
14504
|
+
changedPaths
|
|
13536
14505
|
};
|
|
13537
14506
|
const existingRecord2 = readPrdRun(options.repoRoot, prdRef);
|
|
13538
14507
|
writePrdRunRecord(options.repoRoot, {
|
|
@@ -13607,17 +14576,100 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13607
14576
|
let reconcileResult;
|
|
13608
14577
|
if (!skipped.includes("reconcile")) {
|
|
13609
14578
|
attempted.push("reconcile");
|
|
13610
|
-
|
|
13611
|
-
|
|
13612
|
-
|
|
13613
|
-
|
|
13614
|
-
|
|
13615
|
-
|
|
13616
|
-
|
|
13617
|
-
|
|
13618
|
-
|
|
13619
|
-
|
|
13620
|
-
|
|
14579
|
+
const localStorePath2 = join21(
|
|
14580
|
+
options.repoRoot,
|
|
14581
|
+
".pourkit",
|
|
14582
|
+
"local-prd-runs",
|
|
14583
|
+
prdRef
|
|
14584
|
+
);
|
|
14585
|
+
if (existsSync17(localStorePath2)) {
|
|
14586
|
+
const prdRecord = readPrdRun(options.repoRoot, prdRef);
|
|
14587
|
+
const finalReviewReceipt = prdRecord.record?.finalReview;
|
|
14588
|
+
const hasCompletedFinalReview = finalReviewReceipt?.status === "final_reviewed" || finalReviewReceipt?.status === "succeeded";
|
|
14589
|
+
if (!hasCompletedFinalReview) {
|
|
14590
|
+
const reason = `Reconciliation blocked: Final Review not completed. Run Final Review before reconciliation.`;
|
|
14591
|
+
return {
|
|
14592
|
+
prdRef,
|
|
14593
|
+
status: "blocked",
|
|
14594
|
+
attempted,
|
|
14595
|
+
skipped: [...skipped],
|
|
14596
|
+
resumed,
|
|
14597
|
+
diagnostics: [reason],
|
|
14598
|
+
blockedGate: "reconciliation",
|
|
14599
|
+
blockedReason: reason,
|
|
14600
|
+
offendingPaths: [],
|
|
14601
|
+
prepare: prepareResult,
|
|
14602
|
+
start: startResult,
|
|
14603
|
+
finalReview: finalReviewResult
|
|
14604
|
+
};
|
|
14605
|
+
}
|
|
14606
|
+
const localResult = await runLocalReconciliation(prdRef, {
|
|
14607
|
+
repoRoot: options.repoRoot
|
|
14608
|
+
});
|
|
14609
|
+
if (!localResult.ok) {
|
|
14610
|
+
return {
|
|
14611
|
+
prdRef,
|
|
14612
|
+
status: "blocked",
|
|
14613
|
+
attempted,
|
|
14614
|
+
skipped: [...skipped],
|
|
14615
|
+
resumed,
|
|
14616
|
+
diagnostics: localResult.message ? [localResult.message] : [],
|
|
14617
|
+
blockedGate: "reconciliation",
|
|
14618
|
+
blockedReason: localResult.message ?? "Local reconciliation blocked.",
|
|
14619
|
+
offendingPaths: [],
|
|
14620
|
+
prepare: prepareResult,
|
|
14621
|
+
start: startResult,
|
|
14622
|
+
finalReview: finalReviewResult
|
|
14623
|
+
};
|
|
14624
|
+
}
|
|
14625
|
+
const receipt = {
|
|
14626
|
+
status: "succeeded",
|
|
14627
|
+
result: localResult.receipt?.result,
|
|
14628
|
+
changedPlanningPaths: localResult.receipt?.changedPlanningPaths,
|
|
14629
|
+
targetName: options.targetName,
|
|
14630
|
+
prdBranch: localResult.receipt?.prdBranch ?? `local/${prdRef}`,
|
|
14631
|
+
mergeCommit: localResult.receipt?.mergeCommit,
|
|
14632
|
+
reconciledAt: localResult.receipt?.reconciledTimestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
14633
|
+
autoMerge: false,
|
|
14634
|
+
agentIdentity: "pourkit-architect",
|
|
14635
|
+
noChange: localResult.receipt?.result === "no_changes_needed",
|
|
14636
|
+
source: "dev",
|
|
14637
|
+
target: localResult.receipt?.prdBranch ?? `local/${prdRef}`
|
|
14638
|
+
};
|
|
14639
|
+
const existingRecord2 = readPrdRun(options.repoRoot, prdRef);
|
|
14640
|
+
writePrdRunRecord(options.repoRoot, {
|
|
14641
|
+
prdRef,
|
|
14642
|
+
status: "completed_local_branch",
|
|
14643
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14644
|
+
targetName: options.targetName,
|
|
14645
|
+
prdBranch: `local/${prdRef}`,
|
|
14646
|
+
manifestPath: existingRecord2.record?.manifestPath,
|
|
14647
|
+
planning: existingRecord2.record?.planning,
|
|
14648
|
+
start: existingRecord2.record?.start,
|
|
14649
|
+
finalReview: existingRecord2.record?.finalReview,
|
|
14650
|
+
reconciliation: receipt,
|
|
14651
|
+
scopeChanges: existingRecord2.record?.scopeChanges
|
|
14652
|
+
});
|
|
14653
|
+
reconcileResult = {
|
|
14654
|
+
prdRef,
|
|
14655
|
+
status: "succeeded",
|
|
14656
|
+
reconciliation: receipt,
|
|
14657
|
+
diagnostics: []
|
|
14658
|
+
};
|
|
14659
|
+
diagnostics.push("Local reconciliation completed.");
|
|
14660
|
+
} else {
|
|
14661
|
+
reconcileResult = await runPrdRunReconcileCommand({
|
|
14662
|
+
repoRoot: options.repoRoot,
|
|
14663
|
+
prdRef,
|
|
14664
|
+
targetName: options.targetName,
|
|
14665
|
+
config: options.config,
|
|
14666
|
+
logger: options.logger,
|
|
14667
|
+
executionProvider: options.executionProvider,
|
|
14668
|
+
prProvider: options.prProvider,
|
|
14669
|
+
autoMerge: options.autoMerge
|
|
14670
|
+
});
|
|
14671
|
+
}
|
|
14672
|
+
if (reconcileResult && reconcileResult.status === "blocked") {
|
|
13621
14673
|
return {
|
|
13622
14674
|
prdRef,
|
|
13623
14675
|
status: "blocked",
|
|
@@ -13634,7 +14686,9 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13634
14686
|
reconcile: reconcileResult
|
|
13635
14687
|
};
|
|
13636
14688
|
}
|
|
13637
|
-
|
|
14689
|
+
if (reconcileResult) {
|
|
14690
|
+
diagnostics.push(...reconcileResult.diagnostics);
|
|
14691
|
+
}
|
|
13638
14692
|
}
|
|
13639
14693
|
const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
13640
14694
|
const reconciliationReceipt = reconcileResult && reconcileResult.status === "succeeded" ? reconcileResult.reconciliation : currentRecord?.reconciliation;
|
|
@@ -13717,6 +14771,29 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13717
14771
|
reconcile: reconcileResult
|
|
13718
14772
|
};
|
|
13719
14773
|
}
|
|
14774
|
+
const localStorePath = join21(
|
|
14775
|
+
options.repoRoot,
|
|
14776
|
+
".pourkit",
|
|
14777
|
+
"local-prd-runs",
|
|
14778
|
+
prdRef
|
|
14779
|
+
);
|
|
14780
|
+
if (existsSync17(localStorePath)) {
|
|
14781
|
+
return {
|
|
14782
|
+
prdRef,
|
|
14783
|
+
status: "completed_local_branch",
|
|
14784
|
+
prdBranch: `local/${prdRef}`,
|
|
14785
|
+
attempted,
|
|
14786
|
+
skipped,
|
|
14787
|
+
resumed,
|
|
14788
|
+
diagnostics: [
|
|
14789
|
+
`Local PRD Run ${prdRef} completed. Branch: local/${prdRef}.`
|
|
14790
|
+
],
|
|
14791
|
+
prepare: prepareResult,
|
|
14792
|
+
start: startResult,
|
|
14793
|
+
finalReview: finalReviewResult,
|
|
14794
|
+
reconcile: reconcileResult
|
|
14795
|
+
};
|
|
14796
|
+
}
|
|
13720
14797
|
writePrdRunRecord(options.repoRoot, {
|
|
13721
14798
|
prdRef,
|
|
13722
14799
|
status: "waiting_for_integration",
|
|
@@ -14046,7 +15123,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
|
|
|
14046
15123
|
const branch = `local/${normalizedRef}`;
|
|
14047
15124
|
const branchExists = (() => {
|
|
14048
15125
|
try {
|
|
14049
|
-
return
|
|
15126
|
+
return spawnSync3("git", ["rev-parse", "--verify", "--quiet", branch], {
|
|
14050
15127
|
cwd: root,
|
|
14051
15128
|
encoding: "utf8",
|
|
14052
15129
|
stdio: "pipe"
|
|
@@ -14066,7 +15143,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
|
|
|
14066
15143
|
message: `Branch ${branch} already exists without a matching start receipt. Remove it manually or use a different PRD ID.`
|
|
14067
15144
|
};
|
|
14068
15145
|
}
|
|
14069
|
-
const branchResult =
|
|
15146
|
+
const branchResult = spawnSync3("git", ["branch", branch], {
|
|
14070
15147
|
cwd: root,
|
|
14071
15148
|
encoding: "utf8",
|
|
14072
15149
|
stdio: "pipe"
|
|
@@ -14081,7 +15158,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
|
|
|
14081
15158
|
}
|
|
14082
15159
|
const baseCommit = (() => {
|
|
14083
15160
|
try {
|
|
14084
|
-
return
|
|
15161
|
+
return spawnSync3("git", ["rev-parse", "HEAD"], {
|
|
14085
15162
|
cwd: root,
|
|
14086
15163
|
encoding: "utf8",
|
|
14087
15164
|
stdio: "pipe"
|
|
@@ -14203,6 +15280,17 @@ async function runLocalLaunchCommand(prdId, repoRoot2) {
|
|
|
14203
15280
|
};
|
|
14204
15281
|
return { ok: false, failedStage: "finalReview", stages };
|
|
14205
15282
|
}
|
|
15283
|
+
if (result.verdict === "blocked" || result.verdict === "needs_human_review") {
|
|
15284
|
+
stages.finalReview = {
|
|
15285
|
+
ok: false,
|
|
15286
|
+
stage: "finalReview",
|
|
15287
|
+
failureCode: result.verdict === "blocked" ? "final_review_blocked" : "needs_human_review",
|
|
15288
|
+
repairabilityClass: "manual",
|
|
15289
|
+
reason: result.message ?? `Final Review verdict: ${result.verdict}`,
|
|
15290
|
+
repairGuidance: `Fix final review failures and rerun \`pourkit prd-run launch --local ${normalizedRef}\`.`
|
|
15291
|
+
};
|
|
15292
|
+
return { ok: false, failedStage: "finalReview", stages };
|
|
15293
|
+
}
|
|
14206
15294
|
if (result.verdict === "pass_with_retouch") {
|
|
14207
15295
|
try {
|
|
14208
15296
|
await squashFinalReviewRetouch(normalizedRef, root);
|
|
@@ -14322,6 +15410,32 @@ async function runPrdRunStartCommand(options) {
|
|
|
14322
15410
|
);
|
|
14323
15411
|
}
|
|
14324
15412
|
const existingRecord = readPrdRun(options.repoRoot, prdRef);
|
|
15413
|
+
const resolvedMode = options.config ? (() => {
|
|
15414
|
+
try {
|
|
15415
|
+
const target = resolveTarget(options.config, targetName);
|
|
15416
|
+
return resolvePrdRunMode(target).mode;
|
|
15417
|
+
} catch {
|
|
15418
|
+
return void 0;
|
|
15419
|
+
}
|
|
15420
|
+
})() : void 0;
|
|
15421
|
+
if (resolvedMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedMode) {
|
|
15422
|
+
const failure = {
|
|
15423
|
+
ok: false,
|
|
15424
|
+
gate: "branch-state",
|
|
15425
|
+
reason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedMode}". Resolve by using the correct target or --local-override.`,
|
|
15426
|
+
diagnostics: [
|
|
15427
|
+
`Recorded mode: ${existingRecord.record.mode}`,
|
|
15428
|
+
`Resolved mode: ${resolvedMode}`
|
|
15429
|
+
],
|
|
15430
|
+
offendingPaths: []
|
|
15431
|
+
};
|
|
15432
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
15433
|
+
manifestPath: existingRecord.record?.manifestPath,
|
|
15434
|
+
planning: existingRecord.record?.planning,
|
|
15435
|
+
targetName
|
|
15436
|
+
});
|
|
15437
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
15438
|
+
}
|
|
14325
15439
|
if (existingRecord.record?.start) {
|
|
14326
15440
|
const fetchResult2 = fetchOriginDev(options.repoRoot);
|
|
14327
15441
|
if (!fetchResult2.ok) {
|
|
@@ -14357,7 +15471,8 @@ async function runPrdRunStartCommand(options) {
|
|
|
14357
15471
|
...reusedStart
|
|
14358
15472
|
},
|
|
14359
15473
|
{
|
|
14360
|
-
targetName
|
|
15474
|
+
targetName,
|
|
15475
|
+
mode: resolvedMode
|
|
14361
15476
|
}
|
|
14362
15477
|
);
|
|
14363
15478
|
return await processStartResult(
|
|
@@ -14460,6 +15575,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
14460
15575
|
},
|
|
14461
15576
|
{
|
|
14462
15577
|
targetName,
|
|
15578
|
+
mode: resolvedMode,
|
|
14463
15579
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14464
15580
|
planning: existingRecord.record?.planning
|
|
14465
15581
|
}
|
|
@@ -14619,6 +15735,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
14619
15735
|
},
|
|
14620
15736
|
{
|
|
14621
15737
|
targetName,
|
|
15738
|
+
mode: resolvedMode,
|
|
14622
15739
|
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
|
|
14623
15740
|
planning: existingRecord.record?.planning
|
|
14624
15741
|
}
|
|
@@ -14701,6 +15818,68 @@ async function runPrdRunStartCommand(options) {
|
|
|
14701
15818
|
});
|
|
14702
15819
|
return buildBlockedStartResult(prdRef, failure);
|
|
14703
15820
|
}
|
|
15821
|
+
if (resolvedMode === "local") {
|
|
15822
|
+
const localBranchResult = materializeLocalPrdBranch(
|
|
15823
|
+
prdRef,
|
|
15824
|
+
fetchResult.startBaseCommit,
|
|
15825
|
+
options.repoRoot
|
|
15826
|
+
);
|
|
15827
|
+
if (!localBranchResult.ok) {
|
|
15828
|
+
const failure = {
|
|
15829
|
+
ok: false,
|
|
15830
|
+
gate: "branch-state",
|
|
15831
|
+
reason: localBranchResult.message,
|
|
15832
|
+
diagnostics: [localBranchResult.message],
|
|
15833
|
+
offendingPaths: []
|
|
15834
|
+
};
|
|
15835
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
15836
|
+
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
|
|
15837
|
+
planning: existingRecord.record?.planning,
|
|
15838
|
+
targetName
|
|
15839
|
+
});
|
|
15840
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
15841
|
+
}
|
|
15842
|
+
const localStoreDir = join21(
|
|
15843
|
+
options.repoRoot,
|
|
15844
|
+
".pourkit",
|
|
15845
|
+
"local-prd-runs",
|
|
15846
|
+
prdRef
|
|
15847
|
+
);
|
|
15848
|
+
let localStoreReady = false;
|
|
15849
|
+
if (existsSync17(localStoreDir)) {
|
|
15850
|
+
const localStorePath = join21(localStoreDir, "prd.json");
|
|
15851
|
+
try {
|
|
15852
|
+
const content = JSON.parse(readFileSync17(localStorePath, "utf8"));
|
|
15853
|
+
localStoreReady = content?.id === prdRef && content?.kind === "prd";
|
|
15854
|
+
} catch {
|
|
15855
|
+
localStoreReady = false;
|
|
15856
|
+
}
|
|
15857
|
+
}
|
|
15858
|
+
const prereqs = {
|
|
15859
|
+
localBranchName: getLocalPrdBranchName(prdRef),
|
|
15860
|
+
localBranchExists: !localBranchResult.created,
|
|
15861
|
+
localBranchCommit: fetchResult.startBaseCommit,
|
|
15862
|
+
localStoreReady
|
|
15863
|
+
};
|
|
15864
|
+
if (existsSync17(localStoreDir) && !prereqs.localStoreReady) {
|
|
15865
|
+
const failure = {
|
|
15866
|
+
ok: false,
|
|
15867
|
+
gate: "branch-state",
|
|
15868
|
+
reason: `Local PRD Run Store not ready for ${prdRef}. Expected valid prd.json at .pourkit/local-prd-runs/${prdRef}/prd.json with matching PRD ID. Ensure Local PRD Run Store is initialized before starting.`,
|
|
15869
|
+
diagnostics: [
|
|
15870
|
+
`Expected store path: .pourkit/local-prd-runs/${prdRef}/prd.json`,
|
|
15871
|
+
`Expected PRD ID: ${prdRef}`
|
|
15872
|
+
],
|
|
15873
|
+
offendingPaths: []
|
|
15874
|
+
};
|
|
15875
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
15876
|
+
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
|
|
15877
|
+
planning: existingRecord.record?.planning,
|
|
15878
|
+
targetName
|
|
15879
|
+
});
|
|
15880
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
15881
|
+
}
|
|
15882
|
+
}
|
|
14704
15883
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14705
15884
|
const start = {
|
|
14706
15885
|
status: "started",
|
|
@@ -14728,6 +15907,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
14728
15907
|
updatedAt,
|
|
14729
15908
|
targetName,
|
|
14730
15909
|
start,
|
|
15910
|
+
mode: resolvedMode,
|
|
14731
15911
|
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath ?? void 0,
|
|
14732
15912
|
planning: existingRecord.record?.planning
|
|
14733
15913
|
});
|
|
@@ -14751,26 +15931,100 @@ async function processStartResult(startResult, options) {
|
|
|
14751
15931
|
start.queueStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14752
15932
|
start.queueCommand = "queue-run";
|
|
14753
15933
|
const existingRecord = readPrdRun(repoRoot2, prdRef);
|
|
15934
|
+
let resolvedMode;
|
|
15935
|
+
try {
|
|
15936
|
+
const target = options.config ? resolveTarget(options.config, start.targetName) : null;
|
|
15937
|
+
resolvedMode = target?.strategy ? resolvePrdRunMode(target) : void 0;
|
|
15938
|
+
} catch {
|
|
15939
|
+
resolvedMode = void 0;
|
|
15940
|
+
}
|
|
15941
|
+
const modeForRecord = resolvedMode?.mode;
|
|
14754
15942
|
writePrdRunRecord(repoRoot2, {
|
|
14755
15943
|
prdRef,
|
|
14756
15944
|
status: "running",
|
|
14757
15945
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14758
15946
|
targetName: start.targetName,
|
|
14759
15947
|
start,
|
|
15948
|
+
mode: modeForRecord,
|
|
14760
15949
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14761
15950
|
planning: existingRecord.record?.planning
|
|
14762
15951
|
});
|
|
14763
|
-
const localStorePath =
|
|
14764
|
-
if (
|
|
14765
|
-
const
|
|
15952
|
+
const localStorePath = join21(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
|
|
15953
|
+
if (existsSync17(localStorePath)) {
|
|
15954
|
+
const queueResult = await runLocalQueueLoop(
|
|
15955
|
+
prdRef,
|
|
15956
|
+
repoRoot2,
|
|
15957
|
+
options.issueProvider
|
|
15958
|
+
);
|
|
15959
|
+
if (!queueResult.ok) {
|
|
15960
|
+
const failureCode = queueResult.failureCode ?? "queue_error";
|
|
15961
|
+
const reason = queueResult.repairGuidance ?? `Local queue loop blocked: ${failureCode}`;
|
|
15962
|
+
const diagnostics = [
|
|
15963
|
+
`Local queue loop failed with failureCode "${failureCode}".`,
|
|
15964
|
+
...queueResult.repairGuidance ? [`Repair guidance: ${queueResult.repairGuidance}`] : []
|
|
15965
|
+
];
|
|
15966
|
+
writePrdRunRecord(repoRoot2, {
|
|
15967
|
+
prdRef,
|
|
15968
|
+
status: "blocked",
|
|
15969
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15970
|
+
targetName: start.targetName,
|
|
15971
|
+
start,
|
|
15972
|
+
mode: modeForRecord,
|
|
15973
|
+
manifestPath: existingRecord.record?.manifestPath,
|
|
15974
|
+
planning: existingRecord.record?.planning,
|
|
15975
|
+
blockedGate: "queue",
|
|
15976
|
+
blockedReason: reason,
|
|
15977
|
+
diagnostics,
|
|
15978
|
+
offendingPaths: []
|
|
15979
|
+
});
|
|
15980
|
+
return {
|
|
15981
|
+
prdRef,
|
|
15982
|
+
status: "blocked",
|
|
15983
|
+
blockedGate: "queue",
|
|
15984
|
+
blockedReason: reason,
|
|
15985
|
+
diagnostics,
|
|
15986
|
+
offendingPaths: []
|
|
15987
|
+
};
|
|
15988
|
+
}
|
|
15989
|
+
if (queueResult.blockedIssues.length > 0) {
|
|
15990
|
+
const reason = `Queue processed complete but ${queueResult.blockedIssues.length} blocked child issue(s) remain.`;
|
|
15991
|
+
const diagnostics = [
|
|
15992
|
+
reason,
|
|
15993
|
+
`Blocked issues: ${queueResult.blockedIssues.join(", ")}`,
|
|
15994
|
+
"Resolve blocked children before parent close."
|
|
15995
|
+
];
|
|
15996
|
+
writePrdRunRecord(repoRoot2, {
|
|
15997
|
+
prdRef,
|
|
15998
|
+
status: "blocked",
|
|
15999
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16000
|
+
targetName: start.targetName,
|
|
16001
|
+
start,
|
|
16002
|
+
mode: modeForRecord,
|
|
16003
|
+
manifestPath: existingRecord.record?.manifestPath,
|
|
16004
|
+
planning: existingRecord.record?.planning,
|
|
16005
|
+
blockedGate: "queue",
|
|
16006
|
+
blockedReason: reason,
|
|
16007
|
+
diagnostics,
|
|
16008
|
+
offendingPaths: []
|
|
16009
|
+
});
|
|
16010
|
+
return {
|
|
16011
|
+
prdRef,
|
|
16012
|
+
status: "blocked",
|
|
16013
|
+
blockedGate: "queue",
|
|
16014
|
+
blockedReason: reason,
|
|
16015
|
+
diagnostics,
|
|
16016
|
+
offendingPaths: []
|
|
16017
|
+
};
|
|
16018
|
+
}
|
|
14766
16019
|
start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14767
|
-
start.queueProcessedCount =
|
|
16020
|
+
start.queueProcessedCount = queueResult.completedIssues.length + queueResult.blockedIssues.length;
|
|
14768
16021
|
writePrdRunRecord(repoRoot2, {
|
|
14769
16022
|
prdRef,
|
|
14770
16023
|
status: "drained",
|
|
14771
16024
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14772
16025
|
targetName: start.targetName,
|
|
14773
16026
|
start,
|
|
16027
|
+
mode: modeForRecord,
|
|
14774
16028
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14775
16029
|
planning: existingRecord.record?.planning
|
|
14776
16030
|
});
|
|
@@ -14795,7 +16049,8 @@ async function processStartResult(startResult, options) {
|
|
|
14795
16049
|
prdRef,
|
|
14796
16050
|
queueRunContext: {
|
|
14797
16051
|
prdRef,
|
|
14798
|
-
prdBranch: start.prdBranch
|
|
16052
|
+
prdBranch: start.prdBranch,
|
|
16053
|
+
prdRunMode: resolvedMode
|
|
14799
16054
|
}
|
|
14800
16055
|
});
|
|
14801
16056
|
if (outcome.selected === null && outcome.code === "drained") {
|
|
@@ -14812,6 +16067,7 @@ async function processStartResult(startResult, options) {
|
|
|
14812
16067
|
diagnostics,
|
|
14813
16068
|
targetName: start.targetName,
|
|
14814
16069
|
start,
|
|
16070
|
+
mode: existingRecord.record?.mode,
|
|
14815
16071
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14816
16072
|
planning: existingRecord.record?.planning,
|
|
14817
16073
|
offendingPaths: []
|
|
@@ -14833,6 +16089,7 @@ async function processStartResult(startResult, options) {
|
|
|
14833
16089
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14834
16090
|
targetName: start.targetName,
|
|
14835
16091
|
start,
|
|
16092
|
+
mode: existingRecord.record?.mode,
|
|
14836
16093
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14837
16094
|
planning: existingRecord.record?.planning
|
|
14838
16095
|
});
|
|
@@ -14854,6 +16111,7 @@ async function processStartResult(startResult, options) {
|
|
|
14854
16111
|
diagnostics,
|
|
14855
16112
|
targetName: start.targetName,
|
|
14856
16113
|
start,
|
|
16114
|
+
mode: existingRecord.record?.mode,
|
|
14857
16115
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14858
16116
|
planning: existingRecord.record?.planning,
|
|
14859
16117
|
offendingPaths: []
|
|
@@ -14878,6 +16136,7 @@ async function processStartResult(startResult, options) {
|
|
|
14878
16136
|
diagnostics: outcomeDiagnostics,
|
|
14879
16137
|
targetName: start.targetName,
|
|
14880
16138
|
start,
|
|
16139
|
+
mode: existingRecord.record?.mode,
|
|
14881
16140
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14882
16141
|
planning: existingRecord.record?.planning,
|
|
14883
16142
|
offendingPaths: []
|
|
@@ -14902,6 +16161,7 @@ async function processStartResult(startResult, options) {
|
|
|
14902
16161
|
diagnostics,
|
|
14903
16162
|
targetName: start.targetName,
|
|
14904
16163
|
start,
|
|
16164
|
+
mode: existingRecord.record?.mode,
|
|
14905
16165
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14906
16166
|
planning: existingRecord.record?.planning,
|
|
14907
16167
|
offendingPaths: []
|
|
@@ -14933,12 +16193,13 @@ function persistStartingPrdRunRecord(repoRoot2, prdRef, existingRecord, start, c
|
|
|
14933
16193
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14934
16194
|
targetName: context.targetName,
|
|
14935
16195
|
start,
|
|
16196
|
+
mode: context.mode,
|
|
14936
16197
|
manifestPath: context.manifestPath ?? existingRecord.record?.manifestPath,
|
|
14937
16198
|
planning: context.planning ?? existingRecord.record?.planning
|
|
14938
16199
|
});
|
|
14939
16200
|
}
|
|
14940
16201
|
function inspectRemotePrdBranch(repoRoot2, prdRef) {
|
|
14941
|
-
const result =
|
|
16202
|
+
const result = spawnSync3("git", ["ls-remote", "--heads", "origin", prdRef], {
|
|
14942
16203
|
cwd: repoRoot2,
|
|
14943
16204
|
encoding: "utf8"
|
|
14944
16205
|
});
|
|
@@ -14963,7 +16224,7 @@ function inspectRemotePrdBranch(repoRoot2, prdRef) {
|
|
|
14963
16224
|
}
|
|
14964
16225
|
function ensureAdoptableExistingPrdBranch(repoRoot2, prdRef, prepareMergeCommit) {
|
|
14965
16226
|
const branchRef = `refs/remotes/origin/${prdRef}`;
|
|
14966
|
-
const result =
|
|
16227
|
+
const result = spawnSync3(
|
|
14967
16228
|
"git",
|
|
14968
16229
|
["merge-base", "--is-ancestor", prepareMergeCommit, branchRef],
|
|
14969
16230
|
{
|
|
@@ -15093,7 +16354,7 @@ async function createOrReusePlanningPr(options) {
|
|
|
15093
16354
|
});
|
|
15094
16355
|
}
|
|
15095
16356
|
function fetchOriginDev(repoRoot2) {
|
|
15096
|
-
const fetchResult =
|
|
16357
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
|
|
15097
16358
|
cwd: repoRoot2,
|
|
15098
16359
|
encoding: "utf8"
|
|
15099
16360
|
});
|
|
@@ -15110,7 +16371,7 @@ function fetchOriginDev(repoRoot2) {
|
|
|
15110
16371
|
offendingPaths: []
|
|
15111
16372
|
};
|
|
15112
16373
|
}
|
|
15113
|
-
const revParseResult =
|
|
16374
|
+
const revParseResult = spawnSync3("git", ["rev-parse", "origin/dev"], {
|
|
15114
16375
|
cwd: repoRoot2,
|
|
15115
16376
|
encoding: "utf8"
|
|
15116
16377
|
});
|
|
@@ -15130,7 +16391,7 @@ function fetchOriginDev(repoRoot2) {
|
|
|
15130
16391
|
return { ok: true, startBaseCommit: revParseResult.stdout.trim() };
|
|
15131
16392
|
}
|
|
15132
16393
|
function fetchPrdBranch(repoRoot2, prdRef) {
|
|
15133
|
-
const result =
|
|
16394
|
+
const result = spawnSync3("git", ["fetch", "origin", prdRef], {
|
|
15134
16395
|
cwd: repoRoot2,
|
|
15135
16396
|
encoding: "utf8"
|
|
15136
16397
|
});
|
|
@@ -15150,7 +16411,7 @@ function fetchPrdBranch(repoRoot2, prdRef) {
|
|
|
15150
16411
|
return { ok: true };
|
|
15151
16412
|
}
|
|
15152
16413
|
function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startBaseCommit) {
|
|
15153
|
-
const result =
|
|
16414
|
+
const result = spawnSync3(
|
|
15154
16415
|
"git",
|
|
15155
16416
|
["merge-base", "--is-ancestor", prepareMergeCommit, startBaseRef],
|
|
15156
16417
|
{
|
|
@@ -15177,7 +16438,7 @@ function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startB
|
|
|
15177
16438
|
return { ok: true };
|
|
15178
16439
|
}
|
|
15179
16440
|
async function ensurePrdBranchPublished(repoRoot2, prdRef, startBaseCommit) {
|
|
15180
|
-
const pushResult =
|
|
16441
|
+
const pushResult = spawnSync3(
|
|
15181
16442
|
"git",
|
|
15182
16443
|
["push", "origin", `${startBaseCommit}:refs/heads/${prdRef}`],
|
|
15183
16444
|
{
|
|
@@ -15230,7 +16491,7 @@ function buildPlanningPrBody(options) {
|
|
|
15230
16491
|
].join("\n");
|
|
15231
16492
|
}
|
|
15232
16493
|
function runGitOrThrow(cwd, args, label) {
|
|
15233
|
-
const result =
|
|
16494
|
+
const result = spawnSync3("git", args, { cwd, encoding: "utf8" });
|
|
15234
16495
|
if (result.status !== 0) {
|
|
15235
16496
|
throw new Error(
|
|
15236
16497
|
`Failed to ${label}: ${[
|
|
@@ -15242,7 +16503,7 @@ function runGitOrThrow(cwd, args, label) {
|
|
|
15242
16503
|
}
|
|
15243
16504
|
}
|
|
15244
16505
|
function validatePrepareAutoMergeDevCheckout(repoRoot2) {
|
|
15245
|
-
const branchResult =
|
|
16506
|
+
const branchResult = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
15246
16507
|
cwd: repoRoot2,
|
|
15247
16508
|
encoding: "utf8"
|
|
15248
16509
|
});
|
|
@@ -15273,7 +16534,7 @@ function validatePrepareAutoMergeDevCheckout(repoRoot2) {
|
|
|
15273
16534
|
function syncLocalDevToOriginDev(repoRoot2) {
|
|
15274
16535
|
const branchResult = validatePrepareAutoMergeDevCheckout(repoRoot2);
|
|
15275
16536
|
if (!branchResult.ok) return branchResult;
|
|
15276
|
-
const fetchResult =
|
|
16537
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
|
|
15277
16538
|
cwd: repoRoot2,
|
|
15278
16539
|
encoding: "utf8"
|
|
15279
16540
|
});
|
|
@@ -15289,7 +16550,7 @@ function syncLocalDevToOriginDev(repoRoot2) {
|
|
|
15289
16550
|
offendingPaths: []
|
|
15290
16551
|
};
|
|
15291
16552
|
}
|
|
15292
|
-
const resetResult =
|
|
16553
|
+
const resetResult = spawnSync3("git", ["reset", "--hard", "origin/dev"], {
|
|
15293
16554
|
cwd: repoRoot2,
|
|
15294
16555
|
encoding: "utf8"
|
|
15295
16556
|
});
|
|
@@ -15333,14 +16594,14 @@ function isArchitecturePlanningDiffPath(path9) {
|
|
|
15333
16594
|
return path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/");
|
|
15334
16595
|
}
|
|
15335
16596
|
async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles) {
|
|
15336
|
-
const remoteResult =
|
|
16597
|
+
const remoteResult = spawnSync3("git", ["remote", "get-url", "origin"], {
|
|
15337
16598
|
cwd: repoRoot2,
|
|
15338
16599
|
encoding: "utf8"
|
|
15339
16600
|
});
|
|
15340
16601
|
if (remoteResult.status !== 0) {
|
|
15341
16602
|
return;
|
|
15342
16603
|
}
|
|
15343
|
-
const worktreePath = mkdtempSync(
|
|
16604
|
+
const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-planning-"));
|
|
15344
16605
|
try {
|
|
15345
16606
|
runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
|
|
15346
16607
|
runGitOrThrow(
|
|
@@ -15354,9 +16615,9 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15354
16615
|
"create planning branch"
|
|
15355
16616
|
);
|
|
15356
16617
|
for (const changedFile of changedFiles) {
|
|
15357
|
-
const sourcePath =
|
|
15358
|
-
const targetPath =
|
|
15359
|
-
|
|
16618
|
+
const sourcePath = join21(repoRoot2, changedFile);
|
|
16619
|
+
const targetPath = join21(worktreePath, changedFile);
|
|
16620
|
+
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
15360
16621
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
15361
16622
|
}
|
|
15362
16623
|
runGitOrThrow(
|
|
@@ -15370,13 +16631,13 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15370
16631
|
"commit planning diff"
|
|
15371
16632
|
);
|
|
15372
16633
|
} finally {
|
|
15373
|
-
|
|
16634
|
+
spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
|
|
15374
16635
|
cwd: repoRoot2,
|
|
15375
16636
|
encoding: "utf8"
|
|
15376
16637
|
});
|
|
15377
16638
|
rmSync3(worktreePath, { recursive: true, force: true });
|
|
15378
16639
|
}
|
|
15379
|
-
const pushResult =
|
|
16640
|
+
const pushResult = spawnSync3(
|
|
15380
16641
|
"git",
|
|
15381
16642
|
["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
|
|
15382
16643
|
{
|
|
@@ -15512,7 +16773,7 @@ function validateStartPrepareReceipt(prdRef, record, options = {}) {
|
|
|
15512
16773
|
function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
|
|
15513
16774
|
for (const artifactPath of listManifestArtifactPaths(repoRoot2, manifest)) {
|
|
15514
16775
|
const repoRelativePath = toRepoRelativePath2(repoRoot2, artifactPath);
|
|
15515
|
-
const result =
|
|
16776
|
+
const result = spawnSync3(
|
|
15516
16777
|
"git",
|
|
15517
16778
|
["show", `origin/dev:${repoRelativePath}`],
|
|
15518
16779
|
{
|
|
@@ -15538,7 +16799,7 @@ function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
|
|
|
15538
16799
|
return { ok: true };
|
|
15539
16800
|
}
|
|
15540
16801
|
function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
|
|
15541
|
-
const result =
|
|
16802
|
+
const result = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
|
|
15542
16803
|
cwd: repoRoot2,
|
|
15543
16804
|
encoding: "utf8"
|
|
15544
16805
|
});
|
|
@@ -15592,12 +16853,12 @@ function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
|
|
|
15592
16853
|
changedFiles: Array.from(new Set(changedFiles))
|
|
15593
16854
|
};
|
|
15594
16855
|
}
|
|
15595
|
-
function
|
|
15596
|
-
const statusResult =
|
|
16856
|
+
function collectObservedReconciliationDirtyPaths(options) {
|
|
16857
|
+
const statusResult = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
|
|
15597
16858
|
cwd: options.worktreeCwd,
|
|
15598
16859
|
encoding: "utf8"
|
|
15599
16860
|
});
|
|
15600
|
-
const diffResult =
|
|
16861
|
+
const diffResult = spawnSync3("git", ["diff", "--name-status"], {
|
|
15601
16862
|
cwd: options.worktreeCwd,
|
|
15602
16863
|
encoding: "utf8"
|
|
15603
16864
|
});
|
|
@@ -15625,37 +16886,19 @@ function collectObservedReconciliationPlanningDiff(options) {
|
|
|
15625
16886
|
)
|
|
15626
16887
|
)
|
|
15627
16888
|
);
|
|
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
16889
|
return {
|
|
15646
16890
|
ok: true,
|
|
15647
|
-
|
|
15648
|
-
rejectedPaths
|
|
16891
|
+
dirtyPaths: candidatePaths.sort()
|
|
15649
16892
|
};
|
|
15650
16893
|
}
|
|
15651
16894
|
function gitRefExists(repoRoot2, ref) {
|
|
15652
|
-
return
|
|
16895
|
+
return spawnSync3("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
15653
16896
|
cwd: repoRoot2,
|
|
15654
16897
|
encoding: "utf8"
|
|
15655
16898
|
}).status === 0;
|
|
15656
16899
|
}
|
|
15657
16900
|
function collectGitDiffNames(repoRoot2, baseRef, headRef) {
|
|
15658
|
-
const result =
|
|
16901
|
+
const result = spawnSync3("git", ["diff", "--name-only", baseRef, headRef], {
|
|
15659
16902
|
cwd: repoRoot2,
|
|
15660
16903
|
encoding: "utf8"
|
|
15661
16904
|
});
|
|
@@ -15670,7 +16913,7 @@ function parseGitStatusLine(line) {
|
|
|
15670
16913
|
return { status, path: path9.replace(/^"|"$/g, "") };
|
|
15671
16914
|
}
|
|
15672
16915
|
function validateManifestArtifactExistence(repoRoot2, manifest) {
|
|
15673
|
-
const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !
|
|
16916
|
+
const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !existsSync17(path9)).map((path9) => toRepoRelativePath2(repoRoot2, path9));
|
|
15674
16917
|
if (missingPaths.length > 0) {
|
|
15675
16918
|
return {
|
|
15676
16919
|
ok: false,
|
|
@@ -16086,7 +17329,7 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
|
|
|
16086
17329
|
}
|
|
16087
17330
|
|
|
16088
17331
|
// commands/init.ts
|
|
16089
|
-
import { existsSync as
|
|
17332
|
+
import { existsSync as existsSync18, statSync } from "fs";
|
|
16090
17333
|
import {
|
|
16091
17334
|
copyFile,
|
|
16092
17335
|
mkdir as mkdir5,
|
|
@@ -16589,7 +17832,7 @@ async function computeFileChecksum(filePath) {
|
|
|
16589
17832
|
return createHash3("sha256").update(content).digest("hex");
|
|
16590
17833
|
}
|
|
16591
17834
|
function lockfileExists(root, name) {
|
|
16592
|
-
return
|
|
17835
|
+
return existsSync18(path5.join(root, name));
|
|
16593
17836
|
}
|
|
16594
17837
|
function detectPackageManager(root) {
|
|
16595
17838
|
if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
|
|
@@ -16633,7 +17876,7 @@ async function discoverLocalSource(sourcePath) {
|
|
|
16633
17876
|
async function discoverReadme(root) {
|
|
16634
17877
|
for (const name of ["README.md", "readme.md"]) {
|
|
16635
17878
|
const p = path5.join(root, name);
|
|
16636
|
-
if (
|
|
17879
|
+
if (existsSync18(p)) {
|
|
16637
17880
|
return p;
|
|
16638
17881
|
}
|
|
16639
17882
|
}
|
|
@@ -16643,7 +17886,7 @@ async function discoverAgentFiles(root) {
|
|
|
16643
17886
|
const files = [];
|
|
16644
17887
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
16645
17888
|
const p = path5.join(root, name);
|
|
16646
|
-
if (
|
|
17889
|
+
if (existsSync18(p)) {
|
|
16647
17890
|
files.push(p);
|
|
16648
17891
|
}
|
|
16649
17892
|
}
|
|
@@ -16651,7 +17894,7 @@ async function discoverAgentFiles(root) {
|
|
|
16651
17894
|
}
|
|
16652
17895
|
async function discoverMerlleState(root) {
|
|
16653
17896
|
const p = path5.join(root, ".pourkit", "state.json");
|
|
16654
|
-
return
|
|
17897
|
+
return existsSync18(p) ? p : null;
|
|
16655
17898
|
}
|
|
16656
17899
|
async function discoverAgentSkills(root) {
|
|
16657
17900
|
const dirs = [
|
|
@@ -16660,7 +17903,7 @@ async function discoverAgentSkills(root) {
|
|
|
16660
17903
|
];
|
|
16661
17904
|
const found = [];
|
|
16662
17905
|
for (const d of dirs) {
|
|
16663
|
-
if (
|
|
17906
|
+
if (existsSync18(d)) {
|
|
16664
17907
|
found.push(d);
|
|
16665
17908
|
}
|
|
16666
17909
|
}
|
|
@@ -16670,12 +17913,12 @@ async function discoverRootDomainDocs(root) {
|
|
|
16670
17913
|
const docs = [];
|
|
16671
17914
|
for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
|
|
16672
17915
|
const p = path5.join(root, name);
|
|
16673
|
-
if (
|
|
17916
|
+
if (existsSync18(p)) {
|
|
16674
17917
|
docs.push(p);
|
|
16675
17918
|
}
|
|
16676
17919
|
}
|
|
16677
17920
|
const adrDir = path5.join(root, "docs", "adr");
|
|
16678
|
-
if (
|
|
17921
|
+
if (existsSync18(adrDir)) {
|
|
16679
17922
|
const entries = await readdir(adrDir, { withFileTypes: true });
|
|
16680
17923
|
for (const entry of entries) {
|
|
16681
17924
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -16818,7 +18061,7 @@ async function planInit(options) {
|
|
|
16818
18061
|
for (const file of skillFiles) {
|
|
16819
18062
|
const relPath = path5.relative(s, file);
|
|
16820
18063
|
const destPath = path5.join(targetRoot, ".agents", "skills", relPath);
|
|
16821
|
-
if (!
|
|
18064
|
+
if (!existsSync18(destPath)) {
|
|
16822
18065
|
operations.push({
|
|
16823
18066
|
kind: "copy",
|
|
16824
18067
|
sourcePath: file,
|
|
@@ -16881,7 +18124,7 @@ async function planInit(options) {
|
|
|
16881
18124
|
});
|
|
16882
18125
|
}
|
|
16883
18126
|
if (sourceRoot) {
|
|
16884
|
-
if (!
|
|
18127
|
+
if (!existsSync18(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
|
|
16885
18128
|
warnings.push(
|
|
16886
18129
|
`--from-local path does not exist or is not a directory: ${sourceRoot}`
|
|
16887
18130
|
);
|
|
@@ -17000,7 +18243,7 @@ async function planInit(options) {
|
|
|
17000
18243
|
requiresConfirmation: false,
|
|
17001
18244
|
destructive: false
|
|
17002
18245
|
});
|
|
17003
|
-
} else if (
|
|
18246
|
+
} else if (existsSync18(destPath)) {
|
|
17004
18247
|
operations.push({
|
|
17005
18248
|
kind: "skip",
|
|
17006
18249
|
path: destPath,
|
|
@@ -17058,7 +18301,7 @@ async function planInit(options) {
|
|
|
17058
18301
|
}
|
|
17059
18302
|
}
|
|
17060
18303
|
const contextPath = path5.join(targetRoot, ".pourkit", "CONTEXT.md");
|
|
17061
|
-
if (!
|
|
18304
|
+
if (!existsSync18(contextPath) && !merleDestPaths.has(contextPath)) {
|
|
17062
18305
|
operations.push({
|
|
17063
18306
|
kind: "create",
|
|
17064
18307
|
path: contextPath,
|
|
@@ -17076,7 +18319,7 @@ async function planInit(options) {
|
|
|
17076
18319
|
"adr",
|
|
17077
18320
|
".gitkeep"
|
|
17078
18321
|
);
|
|
17079
|
-
if (!
|
|
18322
|
+
if (!existsSync18(adrGitkeep)) {
|
|
17080
18323
|
operations.push({
|
|
17081
18324
|
kind: "create",
|
|
17082
18325
|
path: adrGitkeep,
|
|
@@ -17088,7 +18331,7 @@ async function planInit(options) {
|
|
|
17088
18331
|
}
|
|
17089
18332
|
const srcDocAgents = path5.join(sourceRoot, ".pourkit", "docs", "agents");
|
|
17090
18333
|
const tgtDocAgents = path5.join(targetRoot, ".pourkit", "docs", "agents");
|
|
17091
|
-
if (
|
|
18334
|
+
if (existsSync18(srcDocAgents) && !existsSync18(tgtDocAgents)) {
|
|
17092
18335
|
const docFiles = await walkDir(srcDocAgents);
|
|
17093
18336
|
for (const file of docFiles) {
|
|
17094
18337
|
const relPath = path5.relative(srcDocAgents, file);
|
|
@@ -17121,7 +18364,7 @@ async function planInit(options) {
|
|
|
17121
18364
|
}
|
|
17122
18365
|
const srcPrompts = path5.join(sourceRoot, ".pourkit", "prompts");
|
|
17123
18366
|
const tgtPrompts = path5.join(targetRoot, ".pourkit", "prompts");
|
|
17124
|
-
if (
|
|
18367
|
+
if (existsSync18(srcPrompts) && !existsSync18(tgtPrompts)) {
|
|
17125
18368
|
const promptFiles = await walkDir(srcPrompts);
|
|
17126
18369
|
for (const file of promptFiles) {
|
|
17127
18370
|
const relPath = path5.relative(srcPrompts, file);
|
|
@@ -17148,7 +18391,7 @@ async function planInit(options) {
|
|
|
17148
18391
|
".sandcastle",
|
|
17149
18392
|
"Dockerfile"
|
|
17150
18393
|
);
|
|
17151
|
-
if (
|
|
18394
|
+
if (existsSync18(tgtSandboxDockerfile)) {
|
|
17152
18395
|
operations.push({
|
|
17153
18396
|
kind: "skip",
|
|
17154
18397
|
path: tgtSandboxDockerfile,
|
|
@@ -17157,7 +18400,7 @@ async function planInit(options) {
|
|
|
17157
18400
|
requiresConfirmation: false,
|
|
17158
18401
|
destructive: false
|
|
17159
18402
|
});
|
|
17160
|
-
} else if (
|
|
18403
|
+
} else if (existsSync18(srcSandboxDockerfile)) {
|
|
17161
18404
|
const checksum = await computeFileChecksum(srcSandboxDockerfile);
|
|
17162
18405
|
operations.push({
|
|
17163
18406
|
kind: "copy",
|
|
@@ -17171,7 +18414,7 @@ async function planInit(options) {
|
|
|
17171
18414
|
});
|
|
17172
18415
|
}
|
|
17173
18416
|
const configTsPath = path5.join(targetRoot, "pourkit.config.ts");
|
|
17174
|
-
if (!
|
|
18417
|
+
if (!existsSync18(configTsPath)) {
|
|
17175
18418
|
const verifyCommands = inferVerificationCommands(
|
|
17176
18419
|
packageScripts,
|
|
17177
18420
|
pm || "npm"
|
|
@@ -17208,7 +18451,7 @@ async function planInit(options) {
|
|
|
17208
18451
|
const hasExistingAgents = operations.some(
|
|
17209
18452
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
|
|
17210
18453
|
);
|
|
17211
|
-
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !
|
|
18454
|
+
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync18(path5.join(targetRoot, "AGENTS.md"))) {
|
|
17212
18455
|
operations.push({
|
|
17213
18456
|
kind: "create",
|
|
17214
18457
|
path: path5.join(targetRoot, "AGENTS.md"),
|
|
@@ -17224,7 +18467,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
17224
18467
|
const hasExistingClaude = operations.some(
|
|
17225
18468
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
|
|
17226
18469
|
);
|
|
17227
|
-
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !
|
|
18470
|
+
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync18(path5.join(targetRoot, "CLAUDE.md"))) {
|
|
17228
18471
|
operations.push({
|
|
17229
18472
|
kind: "create",
|
|
17230
18473
|
path: path5.join(targetRoot, "CLAUDE.md"),
|
|
@@ -17239,7 +18482,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
17239
18482
|
}
|
|
17240
18483
|
const gitignoreTarget = path5.join(targetRoot, ".gitignore");
|
|
17241
18484
|
const gitignoreContent = generateGitignoreBlock();
|
|
17242
|
-
if (!
|
|
18485
|
+
if (!existsSync18(gitignoreTarget)) {
|
|
17243
18486
|
operations.push({
|
|
17244
18487
|
kind: "create",
|
|
17245
18488
|
path: gitignoreTarget,
|
|
@@ -17263,7 +18506,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
17263
18506
|
});
|
|
17264
18507
|
}
|
|
17265
18508
|
const openCodePath = path5.join(targetRoot, "opencode.json");
|
|
17266
|
-
if (!
|
|
18509
|
+
if (!existsSync18(openCodePath)) {
|
|
17267
18510
|
operations.push({
|
|
17268
18511
|
kind: "create",
|
|
17269
18512
|
path: openCodePath,
|
|
@@ -17320,7 +18563,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
17320
18563
|
}
|
|
17321
18564
|
}
|
|
17322
18565
|
const manifestPath = path5.join(targetRoot, ".pourkit", "manifest.json");
|
|
17323
|
-
if (
|
|
18566
|
+
if (existsSync18(manifestPath)) {
|
|
17324
18567
|
operations.push({
|
|
17325
18568
|
kind: "skip",
|
|
17326
18569
|
path: manifestPath,
|
|
@@ -17639,7 +18882,7 @@ async function updateManagedBlock(filePath, content) {
|
|
|
17639
18882
|
const blockContent = `${MANAGED_BLOCK_BEGIN}
|
|
17640
18883
|
${content}${MANAGED_BLOCK_END}
|
|
17641
18884
|
`;
|
|
17642
|
-
if (!
|
|
18885
|
+
if (!existsSync18(filePath)) {
|
|
17643
18886
|
const dir = path5.dirname(filePath);
|
|
17644
18887
|
await mkdir5(dir, { recursive: true });
|
|
17645
18888
|
await writeFileAtomic(filePath, blockContent);
|
|
@@ -17668,7 +18911,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
|
17668
18911
|
if (op.requiresConfirmation) continue;
|
|
17669
18912
|
const relPath = path5.relative(plan.targetRoot, op.path);
|
|
17670
18913
|
if (relPath === ".pourkit/manifest.json") continue;
|
|
17671
|
-
if (
|
|
18914
|
+
if (existsSync18(op.path)) {
|
|
17672
18915
|
const sha256 = await computeFileChecksum(op.path);
|
|
17673
18916
|
assets[relPath] = {
|
|
17674
18917
|
ownership: op.ownership || "managed",
|
|
@@ -17713,7 +18956,7 @@ async function applyInitPlan(plan, options) {
|
|
|
17713
18956
|
skipped++;
|
|
17714
18957
|
continue;
|
|
17715
18958
|
}
|
|
17716
|
-
if (
|
|
18959
|
+
if (existsSync18(op.path) && !op.destructive) {
|
|
17717
18960
|
skipped++;
|
|
17718
18961
|
continue;
|
|
17719
18962
|
}
|
|
@@ -17728,7 +18971,7 @@ async function applyInitPlan(plan, options) {
|
|
|
17728
18971
|
skipped++;
|
|
17729
18972
|
continue;
|
|
17730
18973
|
}
|
|
17731
|
-
if (
|
|
18974
|
+
if (existsSync18(op.path)) {
|
|
17732
18975
|
skipped++;
|
|
17733
18976
|
continue;
|
|
17734
18977
|
}
|
|
@@ -17757,7 +19000,7 @@ async function applyInitPlan(plan, options) {
|
|
|
17757
19000
|
skipped++;
|
|
17758
19001
|
continue;
|
|
17759
19002
|
}
|
|
17760
|
-
if (
|
|
19003
|
+
if (existsSync18(op.path)) {
|
|
17761
19004
|
skipped++;
|
|
17762
19005
|
continue;
|
|
17763
19006
|
}
|
|
@@ -17897,7 +19140,7 @@ async function applyInitFromSource(options) {
|
|
|
17897
19140
|
if (!manifestSkipped) {
|
|
17898
19141
|
const agentFiles = [];
|
|
17899
19142
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
17900
|
-
if (
|
|
19143
|
+
if (existsSync18(path5.join(targetRoot, name))) {
|
|
17901
19144
|
agentFiles.push(path5.join(targetRoot, name));
|
|
17902
19145
|
}
|
|
17903
19146
|
}
|
|
@@ -18716,8 +19959,8 @@ function formatChecks2(checks) {
|
|
|
18716
19959
|
init_common();
|
|
18717
19960
|
|
|
18718
19961
|
// execution/sandcastle-execution.ts
|
|
18719
|
-
import { mkdirSync as
|
|
18720
|
-
import { join as
|
|
19962
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
19963
|
+
import { join as join23 } from "path";
|
|
18721
19964
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
18722
19965
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
18723
19966
|
|
|
@@ -18726,10 +19969,10 @@ init_common();
|
|
|
18726
19969
|
import { mkdtempSync as mkdtempSync2 } from "fs";
|
|
18727
19970
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
18728
19971
|
import { tmpdir as tmpdir2 } from "os";
|
|
18729
|
-
import { dirname as dirname6, join as
|
|
19972
|
+
import { dirname as dirname6, join as join22 } from "path";
|
|
18730
19973
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
18731
19974
|
for (const artifact of artifacts) {
|
|
18732
|
-
const filePath =
|
|
19975
|
+
const filePath = join22(worktreePath, artifact.path);
|
|
18733
19976
|
await ensureDir(dirname6(filePath));
|
|
18734
19977
|
await writeFile3(filePath, artifact.content, "utf-8");
|
|
18735
19978
|
}
|
|
@@ -18741,17 +19984,17 @@ import path7 from "path";
|
|
|
18741
19984
|
|
|
18742
19985
|
// execution/sandbox-image.ts
|
|
18743
19986
|
import { createHash as createHash4 } from "crypto";
|
|
18744
|
-
import { existsSync as
|
|
19987
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
18745
19988
|
import path6 from "path";
|
|
18746
19989
|
function sandboxImageName(repoRoot2) {
|
|
18747
19990
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
18748
19991
|
const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
|
|
18749
19992
|
const baseName = sanitized || "local";
|
|
18750
19993
|
const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
|
|
18751
|
-
if (!
|
|
19994
|
+
if (!existsSync19(dockerfilePath)) {
|
|
18752
19995
|
return `sandcastle:${baseName}`;
|
|
18753
19996
|
}
|
|
18754
|
-
const fingerprint = createHash4("sha256").update(
|
|
19997
|
+
const fingerprint = createHash4("sha256").update(readFileSync18(dockerfilePath)).digest("hex").slice(0, 8);
|
|
18755
19998
|
return `sandcastle:${baseName}-${fingerprint}`;
|
|
18756
19999
|
}
|
|
18757
20000
|
|
|
@@ -18901,11 +20144,7 @@ var SandcastleExecutionSession = class {
|
|
|
18901
20144
|
type: "file",
|
|
18902
20145
|
path: logPath,
|
|
18903
20146
|
onAgentStreamEvent: (event) => {
|
|
18904
|
-
|
|
18905
|
-
logger.raw(event.message);
|
|
18906
|
-
} else if (event.type === "toolCall") {
|
|
18907
|
-
logger.raw(`${event.name}(${event.formattedArgs})`);
|
|
18908
|
-
}
|
|
20147
|
+
logger.raw(formatAgentStreamEvent(event));
|
|
18909
20148
|
}
|
|
18910
20149
|
},
|
|
18911
20150
|
completionSignal: "<promise>COMPLETE</promise>",
|
|
@@ -18990,14 +20229,74 @@ function resolveSandboxProvider(provider, dockerFactory) {
|
|
|
18990
20229
|
function sanitizeBranch(branchName) {
|
|
18991
20230
|
return branchName.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
18992
20231
|
}
|
|
20232
|
+
function formatAgentStreamEvent(event) {
|
|
20233
|
+
if (event.type === "text") {
|
|
20234
|
+
return JSON.stringify({ type: "text", textCount: event.message.length });
|
|
20235
|
+
}
|
|
20236
|
+
const parsedArgs = parseToolCallArgs(event.formattedArgs);
|
|
20237
|
+
if (!parsedArgs.ok) {
|
|
20238
|
+
return JSON.stringify({
|
|
20239
|
+
type: "toolCall",
|
|
20240
|
+
name: event.name,
|
|
20241
|
+
argsTextCount: event.formattedArgs.length
|
|
20242
|
+
});
|
|
20243
|
+
}
|
|
20244
|
+
return JSON.stringify({
|
|
20245
|
+
type: "toolCall",
|
|
20246
|
+
name: event.name,
|
|
20247
|
+
args: summarizeToolCallArgs(event.name, parsedArgs.value)
|
|
20248
|
+
});
|
|
20249
|
+
}
|
|
20250
|
+
function parseToolCallArgs(formattedArgs) {
|
|
20251
|
+
try {
|
|
20252
|
+
return { ok: true, value: JSON.parse(formattedArgs) };
|
|
20253
|
+
} catch {
|
|
20254
|
+
return { ok: false };
|
|
20255
|
+
}
|
|
20256
|
+
}
|
|
20257
|
+
var SUMMARIZED_TOOL_FIELDS = {
|
|
20258
|
+
write: { content: "contentCount" },
|
|
20259
|
+
edit: { oldString: "oldCount", newString: "newCount" }
|
|
20260
|
+
};
|
|
20261
|
+
var SUMMARIZED_TOOL_ARRAY_FIELDS = {
|
|
20262
|
+
todowrite: { todos: "todoCount" }
|
|
20263
|
+
};
|
|
20264
|
+
function summarizeToolCallArgs(name, args) {
|
|
20265
|
+
if (!isPlainObject(args)) {
|
|
20266
|
+
return args;
|
|
20267
|
+
}
|
|
20268
|
+
const fields = SUMMARIZED_TOOL_FIELDS[name.toLowerCase()];
|
|
20269
|
+
const arrayFields = SUMMARIZED_TOOL_ARRAY_FIELDS[name.toLowerCase()];
|
|
20270
|
+
if (!fields && !arrayFields) {
|
|
20271
|
+
return args;
|
|
20272
|
+
}
|
|
20273
|
+
const summarizedArgs = {};
|
|
20274
|
+
for (const [key, value] of Object.entries(args)) {
|
|
20275
|
+
const mappedKey = fields?.[key];
|
|
20276
|
+
if (mappedKey && typeof value === "string") {
|
|
20277
|
+
summarizedArgs[mappedKey] = value.length;
|
|
20278
|
+
continue;
|
|
20279
|
+
}
|
|
20280
|
+
const mappedArrayKey = arrayFields?.[key];
|
|
20281
|
+
if (mappedArrayKey && Array.isArray(value)) {
|
|
20282
|
+
summarizedArgs[mappedArrayKey] = value.length;
|
|
20283
|
+
continue;
|
|
20284
|
+
}
|
|
20285
|
+
summarizedArgs[key] = value;
|
|
20286
|
+
}
|
|
20287
|
+
return summarizedArgs;
|
|
20288
|
+
}
|
|
20289
|
+
function isPlainObject(value) {
|
|
20290
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20291
|
+
}
|
|
18993
20292
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
18994
|
-
const promptsDir =
|
|
18995
|
-
|
|
20293
|
+
const promptsDir = join23(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
20294
|
+
mkdirSync12(promptsDir, { recursive: true });
|
|
18996
20295
|
const timestamp2 = Date.now();
|
|
18997
20296
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
18998
20297
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
18999
|
-
const filePath =
|
|
19000
|
-
|
|
20298
|
+
const filePath = join23(promptsDir, filename);
|
|
20299
|
+
writeFileSync9(filePath, prompt, "utf-8");
|
|
19001
20300
|
}
|
|
19002
20301
|
|
|
19003
20302
|
// cli.ts
|
|
@@ -19066,6 +20365,35 @@ function createCliProgram(version) {
|
|
|
19066
20365
|
const program = new Command();
|
|
19067
20366
|
program.name("pourkit").version(version).exitOverride().description("AI-driven issue-to-PR workflow for GitHub repositories.");
|
|
19068
20367
|
const issueCommand = program.command("issue");
|
|
20368
|
+
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) => {
|
|
20369
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
20370
|
+
const logPath = path8.join(
|
|
20371
|
+
targetRepoRoot,
|
|
20372
|
+
".pourkit",
|
|
20373
|
+
"logs",
|
|
20374
|
+
"run-verification.log"
|
|
20375
|
+
);
|
|
20376
|
+
const logger = createLogger("pourkit", logPath);
|
|
20377
|
+
let failed = false;
|
|
20378
|
+
try {
|
|
20379
|
+
const config = await loadRepoConfig(targetRepoRoot);
|
|
20380
|
+
const target = resolveTarget(config, options.target);
|
|
20381
|
+
const result = await runVerificationCommands({
|
|
20382
|
+
target,
|
|
20383
|
+
cwd: targetRepoRoot,
|
|
20384
|
+
logger
|
|
20385
|
+
});
|
|
20386
|
+
console.log(JSON.stringify(result, null, 2));
|
|
20387
|
+
failed = !result.ok;
|
|
20388
|
+
} catch (error) {
|
|
20389
|
+
await handleError(logger, error);
|
|
20390
|
+
} finally {
|
|
20391
|
+
await logger.close();
|
|
20392
|
+
}
|
|
20393
|
+
if (failed) {
|
|
20394
|
+
process.exit(1);
|
|
20395
|
+
}
|
|
20396
|
+
});
|
|
19069
20397
|
program.command("validate-artifact").description("Validate an agent handoff artifact").argument(
|
|
19070
20398
|
"<kind>",
|
|
19071
20399
|
"artifact kind: reviewer, refactor, finalizer, conflict-resolution, failure-resolution, reconciliation, planning-manifest, local-prd, local-issue, local-triage, or local-prepare"
|
|
@@ -19841,11 +21169,11 @@ function createCliProgram(version) {
|
|
|
19841
21169
|
return program;
|
|
19842
21170
|
}
|
|
19843
21171
|
async function resolveCliVersion() {
|
|
19844
|
-
if (isPackageVersion("0.0.0-next-
|
|
19845
|
-
return "0.0.0-next-
|
|
21172
|
+
if (isPackageVersion("0.0.0-next-20260608221925")) {
|
|
21173
|
+
return "0.0.0-next-20260608221925";
|
|
19846
21174
|
}
|
|
19847
|
-
if (isReleaseVersion("0.0.0-next-
|
|
19848
|
-
return "0.0.0-next-
|
|
21175
|
+
if (isReleaseVersion("0.0.0-next-20260608221925")) {
|
|
21176
|
+
return "0.0.0-next-20260608221925";
|
|
19849
21177
|
}
|
|
19850
21178
|
try {
|
|
19851
21179
|
const root = repoRoot();
|