@pourkit/cli 0.0.0-next-20260608072322 → 0.0.0-next-20260609130908
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 +1449 -420
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +3 -27
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -776,13 +776,17 @@ function getVerificationCommands(target) {
|
|
|
776
776
|
}
|
|
777
777
|
function resolvePrdRunMode(target, opts) {
|
|
778
778
|
if (opts?.localOverride === true) {
|
|
779
|
-
return { mode: "local", source: "cli-override" };
|
|
779
|
+
return { mode: "local", source: "cli-override", targetName: target.name };
|
|
780
780
|
}
|
|
781
781
|
const configMode = target.strategy.prdRun?.mode;
|
|
782
782
|
if (configMode) {
|
|
783
|
-
return {
|
|
783
|
+
return {
|
|
784
|
+
mode: configMode,
|
|
785
|
+
source: "target-config",
|
|
786
|
+
targetName: target.name
|
|
787
|
+
};
|
|
784
788
|
}
|
|
785
|
-
return { mode: "github", source: "default" };
|
|
789
|
+
return { mode: "github", source: "default", targetName: target.name };
|
|
786
790
|
}
|
|
787
791
|
async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
|
|
788
792
|
const { existsSync: existsSync20 } = await import("fs");
|
|
@@ -1350,13 +1354,7 @@ function buildRunContextMarkdown(options) {
|
|
|
1350
1354
|
);
|
|
1351
1355
|
}
|
|
1352
1356
|
if (sections.includes("verification-commands")) {
|
|
1353
|
-
parts.push(
|
|
1354
|
-
...renderCommandList(
|
|
1355
|
-
target,
|
|
1356
|
-
getVerificationCommands(target),
|
|
1357
|
-
"Verification Commands"
|
|
1358
|
-
)
|
|
1359
|
-
);
|
|
1357
|
+
parts.push(...renderCommandList(target, "Verification Commands"));
|
|
1360
1358
|
}
|
|
1361
1359
|
if (sections.includes("review-criteria")) {
|
|
1362
1360
|
parts.push(...renderCriteria(reviewerCriteria));
|
|
@@ -1374,29 +1372,11 @@ function buildRunContextMarkdown(options) {
|
|
|
1374
1372
|
}
|
|
1375
1373
|
return parts.join("\n");
|
|
1376
1374
|
}
|
|
1377
|
-
function renderCommandList(target,
|
|
1378
|
-
if (commands.length === 0) {
|
|
1379
|
-
return [
|
|
1380
|
-
`## ${heading}`,
|
|
1381
|
-
"",
|
|
1382
|
-
`Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
|
|
1383
|
-
"",
|
|
1384
|
-
"Underlying project commands:",
|
|
1385
|
-
"",
|
|
1386
|
-
"(none configured)",
|
|
1387
|
-
""
|
|
1388
|
-
];
|
|
1389
|
-
}
|
|
1375
|
+
function renderCommandList(target, heading) {
|
|
1390
1376
|
return [
|
|
1391
1377
|
`## ${heading}`,
|
|
1392
1378
|
"",
|
|
1393
1379
|
`Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
|
|
1394
|
-
"",
|
|
1395
|
-
"Underlying project commands:",
|
|
1396
|
-
"",
|
|
1397
|
-
"Run these commands from the repository root exactly as written. Do not substitute equivalent scripts from nested package.json files.",
|
|
1398
|
-
"",
|
|
1399
|
-
...commands.map((command) => `- ${command.label}: \`${command.command}\``),
|
|
1400
1380
|
""
|
|
1401
1381
|
];
|
|
1402
1382
|
}
|
|
@@ -6158,10 +6138,57 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as rea
|
|
|
6158
6138
|
import { join as join13 } from "path";
|
|
6159
6139
|
|
|
6160
6140
|
// prd-run/local-branches.ts
|
|
6161
|
-
import { execFileSync } from "child_process";
|
|
6141
|
+
import { execFileSync, spawnSync as spawnSync2 } from "child_process";
|
|
6162
6142
|
function getLocalPrdBranchName(prdId) {
|
|
6163
6143
|
return `local/${prdId}`;
|
|
6164
6144
|
}
|
|
6145
|
+
function materializeLocalPrdBranch(prdId, startBaseCommit, repoRoot2) {
|
|
6146
|
+
const branch = getLocalPrdBranchName(prdId);
|
|
6147
|
+
const root = repoRoot2 ?? process.cwd();
|
|
6148
|
+
const branchExists = (() => {
|
|
6149
|
+
try {
|
|
6150
|
+
return spawnSync2("git", ["rev-parse", "--verify", "--quiet", branch], {
|
|
6151
|
+
cwd: root,
|
|
6152
|
+
encoding: "utf8",
|
|
6153
|
+
stdio: "pipe"
|
|
6154
|
+
}).status === 0;
|
|
6155
|
+
} catch {
|
|
6156
|
+
return false;
|
|
6157
|
+
}
|
|
6158
|
+
})();
|
|
6159
|
+
if (branchExists) {
|
|
6160
|
+
const currentCommit = spawnSync2("git", ["rev-parse", branch], {
|
|
6161
|
+
cwd: root,
|
|
6162
|
+
encoding: "utf8",
|
|
6163
|
+
stdio: "pipe"
|
|
6164
|
+
}).stdout?.toString?.().trim() ?? "";
|
|
6165
|
+
if (currentCommit !== startBaseCommit) {
|
|
6166
|
+
return {
|
|
6167
|
+
ok: false,
|
|
6168
|
+
failureCode: "branch_commit_mismatch",
|
|
6169
|
+
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.`
|
|
6170
|
+
};
|
|
6171
|
+
}
|
|
6172
|
+
return {
|
|
6173
|
+
ok: true,
|
|
6174
|
+
created: false
|
|
6175
|
+
};
|
|
6176
|
+
}
|
|
6177
|
+
const branchResult = spawnSync2("git", ["branch", branch, startBaseCommit], {
|
|
6178
|
+
cwd: root,
|
|
6179
|
+
encoding: "utf8",
|
|
6180
|
+
stdio: "pipe"
|
|
6181
|
+
});
|
|
6182
|
+
if (branchResult.status !== 0) {
|
|
6183
|
+
const stderr = branchResult.stderr?.toString?.() ?? "unknown error";
|
|
6184
|
+
return {
|
|
6185
|
+
ok: false,
|
|
6186
|
+
failureCode: "branch_creation_failed",
|
|
6187
|
+
message: `Failed to create local PRD branch ${branch} at ${startBaseCommit}: ${stderr}`
|
|
6188
|
+
};
|
|
6189
|
+
}
|
|
6190
|
+
return { ok: true, created: true };
|
|
6191
|
+
}
|
|
6165
6192
|
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
|
|
6166
6193
|
var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
|
|
6167
6194
|
function validateLocalBranchName(name) {
|
|
@@ -8307,13 +8334,13 @@ import {
|
|
|
8307
8334
|
lstatSync,
|
|
8308
8335
|
mkdirSync as mkdirSync11,
|
|
8309
8336
|
mkdtempSync,
|
|
8310
|
-
readdirSync as
|
|
8311
|
-
readFileSync as
|
|
8337
|
+
readdirSync as readdirSync5,
|
|
8338
|
+
readFileSync as readFileSync17,
|
|
8312
8339
|
realpathSync,
|
|
8313
8340
|
rmSync as rmSync3
|
|
8314
8341
|
} from "fs";
|
|
8315
|
-
import { spawnSync as
|
|
8316
|
-
import { dirname as dirname5, join as
|
|
8342
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
8343
|
+
import { dirname as dirname5, join as join21, relative as relative2 } from "path";
|
|
8317
8344
|
import { tmpdir } from "os";
|
|
8318
8345
|
|
|
8319
8346
|
// prd-run/state.ts
|
|
@@ -8343,9 +8370,11 @@ var PrdRunRecordSchema = z3.object({
|
|
|
8343
8370
|
"waiting_for_integration",
|
|
8344
8371
|
"finalizing",
|
|
8345
8372
|
"final_reviewed",
|
|
8346
|
-
"complete"
|
|
8373
|
+
"complete",
|
|
8374
|
+
"completed_local_branch"
|
|
8347
8375
|
]),
|
|
8348
8376
|
updatedAt: z3.string().min(1),
|
|
8377
|
+
mode: z3.enum(["github", "local"]).optional(),
|
|
8349
8378
|
blockedGate: z3.enum([
|
|
8350
8379
|
"manifest-location",
|
|
8351
8380
|
"manifest-readiness",
|
|
@@ -8544,7 +8573,20 @@ var LocalPrdRunRecordSchema = z3.object({
|
|
|
8544
8573
|
baseCommit: z3.string().min(1)
|
|
8545
8574
|
}).optional(),
|
|
8546
8575
|
queue: z3.object({ completedAt: z3.string().min(1) }).optional(),
|
|
8547
|
-
finalReview: z3.object({
|
|
8576
|
+
finalReview: z3.object({
|
|
8577
|
+
completedAt: z3.string().min(1),
|
|
8578
|
+
targetName: z3.string().optional(),
|
|
8579
|
+
prdBranch: z3.string().optional(),
|
|
8580
|
+
mergeBase: z3.string().optional(),
|
|
8581
|
+
verdict: z3.enum([
|
|
8582
|
+
"pass_no_changes",
|
|
8583
|
+
"pass_with_retouch",
|
|
8584
|
+
"needs_human_review",
|
|
8585
|
+
"blocked"
|
|
8586
|
+
]).optional(),
|
|
8587
|
+
diagnostics: z3.array(z3.string()).optional(),
|
|
8588
|
+
artifactPath: z3.string().optional()
|
|
8589
|
+
}).optional(),
|
|
8548
8590
|
reconciliation: z3.object({ completedAt: z3.string().min(1) }).optional(),
|
|
8549
8591
|
complete: z3.object({
|
|
8550
8592
|
completedAt: z3.string().min(1),
|
|
@@ -9152,6 +9194,28 @@ function getLocalStorePath3(repoRoot2, prdId) {
|
|
|
9152
9194
|
function getFinalReviewReceiptPath(repoRoot2, prdId) {
|
|
9153
9195
|
return join17(getLocalStorePath3(repoRoot2, prdId), "final-review-receipt.json");
|
|
9154
9196
|
}
|
|
9197
|
+
function computeLocalMergeBase(repoRoot2, prdBranch) {
|
|
9198
|
+
const candidates = ["dev", "main", "master", "HEAD"];
|
|
9199
|
+
for (const base of candidates) {
|
|
9200
|
+
try {
|
|
9201
|
+
const result = execFileSync3("git", ["merge-base", base, prdBranch], {
|
|
9202
|
+
cwd: repoRoot2,
|
|
9203
|
+
encoding: "utf8",
|
|
9204
|
+
stdio: "pipe"
|
|
9205
|
+
});
|
|
9206
|
+
const mergeBase = result.trim();
|
|
9207
|
+
if (mergeBase && mergeBase.length >= 6) {
|
|
9208
|
+
return { ok: true, mergeBase };
|
|
9209
|
+
}
|
|
9210
|
+
} catch {
|
|
9211
|
+
continue;
|
|
9212
|
+
}
|
|
9213
|
+
}
|
|
9214
|
+
return {
|
|
9215
|
+
ok: false,
|
|
9216
|
+
reason: "Could not compute merge base against any known base branch (dev/main/master/HEAD)."
|
|
9217
|
+
};
|
|
9218
|
+
}
|
|
9155
9219
|
async function runLocalFinalReview(prdId, options) {
|
|
9156
9220
|
const root = options?.repoRoot ?? process.cwd();
|
|
9157
9221
|
const commands = options?.verificationCommands ?? [];
|
|
@@ -9164,6 +9228,34 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
9164
9228
|
};
|
|
9165
9229
|
}
|
|
9166
9230
|
const branch = getLocalPrdBranchName(prdId);
|
|
9231
|
+
const mergeBaseResult = computeLocalMergeBase(root, branch);
|
|
9232
|
+
if (!mergeBaseResult.ok) {
|
|
9233
|
+
return {
|
|
9234
|
+
ok: false,
|
|
9235
|
+
failureCode: "merge_base_failed",
|
|
9236
|
+
message: mergeBaseResult.reason
|
|
9237
|
+
};
|
|
9238
|
+
}
|
|
9239
|
+
const semanticResult = validateFinalReviewArtifactSemanticIds(
|
|
9240
|
+
{
|
|
9241
|
+
prdRef: prdId,
|
|
9242
|
+
stage: "prdFinalReview",
|
|
9243
|
+
checkoutBase: branch,
|
|
9244
|
+
reviewBase: mergeBaseResult.mergeBase
|
|
9245
|
+
},
|
|
9246
|
+
{
|
|
9247
|
+
prdRef: prdId,
|
|
9248
|
+
prdBranch: branch,
|
|
9249
|
+
mergeBase: mergeBaseResult.mergeBase
|
|
9250
|
+
}
|
|
9251
|
+
);
|
|
9252
|
+
if (!semanticResult.ok) {
|
|
9253
|
+
return {
|
|
9254
|
+
ok: false,
|
|
9255
|
+
failureCode: "invalid_artifact_semantics",
|
|
9256
|
+
message: semanticResult.errors.join("; ")
|
|
9257
|
+
};
|
|
9258
|
+
}
|
|
9167
9259
|
const failures = [];
|
|
9168
9260
|
for (const cmd of commands) {
|
|
9169
9261
|
try {
|
|
@@ -9175,7 +9267,7 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
9175
9267
|
}
|
|
9176
9268
|
let verdict;
|
|
9177
9269
|
if (commands.length === 0 || failures.length === 0) {
|
|
9178
|
-
const retouchBranch = `local
|
|
9270
|
+
const retouchBranch = `local-${prdId}-final-review-retouch`;
|
|
9179
9271
|
try {
|
|
9180
9272
|
execSync2(`git show-ref --verify --quiet refs/heads/${retouchBranch}`, {
|
|
9181
9273
|
cwd: root,
|
|
@@ -9184,12 +9276,12 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
9184
9276
|
});
|
|
9185
9277
|
verdict = "pass_with_retouch";
|
|
9186
9278
|
} catch {
|
|
9187
|
-
verdict = "
|
|
9279
|
+
verdict = "pass_no_changes";
|
|
9188
9280
|
}
|
|
9189
9281
|
} else if (failures.length === commands.length) {
|
|
9190
9282
|
verdict = "blocked";
|
|
9191
9283
|
} else {
|
|
9192
|
-
const retouchBranch = `local
|
|
9284
|
+
const retouchBranch = `local-${prdId}-final-review-retouch`;
|
|
9193
9285
|
try {
|
|
9194
9286
|
execSync2(`git show-ref --verify --quiet refs/heads/${retouchBranch}`, {
|
|
9195
9287
|
cwd: root,
|
|
@@ -9202,9 +9294,10 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
9202
9294
|
}
|
|
9203
9295
|
}
|
|
9204
9296
|
const receipt = {
|
|
9205
|
-
|
|
9297
|
+
reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9206
9298
|
verdict,
|
|
9207
|
-
branch
|
|
9299
|
+
prdBranch: branch,
|
|
9300
|
+
mergeBase: mergeBaseResult.mergeBase
|
|
9208
9301
|
};
|
|
9209
9302
|
const receiptPath = getFinalReviewReceiptPath(root, prdId);
|
|
9210
9303
|
mkdirSync9(getLocalStorePath3(root, prdId), { recursive: true });
|
|
@@ -9217,10 +9310,10 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
9217
9310
|
}
|
|
9218
9311
|
return result;
|
|
9219
9312
|
}
|
|
9220
|
-
async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
9313
|
+
async function squashFinalReviewRetouch(prdId, repoRoot2, title, body) {
|
|
9221
9314
|
const root = repoRoot2 ?? process.cwd();
|
|
9222
9315
|
const targetBranch = `local/${prdId}`;
|
|
9223
|
-
const retouchBranch = `local
|
|
9316
|
+
const retouchBranch = `local-${prdId}-final-review-retouch`;
|
|
9224
9317
|
try {
|
|
9225
9318
|
execFileSync3(
|
|
9226
9319
|
"git",
|
|
@@ -9267,15 +9360,46 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
9267
9360
|
return;
|
|
9268
9361
|
} catch {
|
|
9269
9362
|
}
|
|
9270
|
-
|
|
9363
|
+
if (title) {
|
|
9364
|
+
const commitBody = body ? `${title}
|
|
9365
|
+
|
|
9366
|
+
${body}` : title;
|
|
9367
|
+
execFileSync3("git", ["commit", "-m", commitBody], {
|
|
9368
|
+
cwd: root,
|
|
9369
|
+
encoding: "utf8",
|
|
9370
|
+
stdio: "pipe"
|
|
9371
|
+
});
|
|
9372
|
+
} else {
|
|
9373
|
+
execFileSync3(
|
|
9374
|
+
"git",
|
|
9375
|
+
["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
|
|
9376
|
+
{
|
|
9377
|
+
cwd: root,
|
|
9378
|
+
encoding: "utf8",
|
|
9379
|
+
stdio: "pipe"
|
|
9380
|
+
}
|
|
9381
|
+
);
|
|
9382
|
+
}
|
|
9383
|
+
const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
|
|
9384
|
+
cwd: root,
|
|
9385
|
+
encoding: "utf8",
|
|
9386
|
+
stdio: "pipe"
|
|
9387
|
+
}).trim();
|
|
9388
|
+
const changedPathsResult = execFileSync3(
|
|
9271
9389
|
"git",
|
|
9272
|
-
["commit", "-
|
|
9390
|
+
["diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"],
|
|
9273
9391
|
{
|
|
9274
9392
|
cwd: root,
|
|
9275
9393
|
encoding: "utf8",
|
|
9276
9394
|
stdio: "pipe"
|
|
9277
9395
|
}
|
|
9278
9396
|
);
|
|
9397
|
+
const changedPaths = changedPathsResult.split(/\r?\n/).filter(Boolean);
|
|
9398
|
+
return {
|
|
9399
|
+
mergeCommit,
|
|
9400
|
+
changedPaths,
|
|
9401
|
+
reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9402
|
+
};
|
|
9279
9403
|
} catch (error) {
|
|
9280
9404
|
const message = error instanceof Error ? error.message : String(error);
|
|
9281
9405
|
if (message.toLowerCase().includes("conflict")) {
|
|
@@ -9287,13 +9411,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
9287
9411
|
|
|
9288
9412
|
// prd-run/local-reconciliation.ts
|
|
9289
9413
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
9290
|
-
import {
|
|
9291
|
-
existsSync as existsSync15,
|
|
9292
|
-
mkdirSync as mkdirSync10,
|
|
9293
|
-
readFileSync as readFileSync15,
|
|
9294
|
-
readdirSync as readdirSync5,
|
|
9295
|
-
writeFileSync as writeFileSync7
|
|
9296
|
-
} from "fs";
|
|
9414
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
|
|
9297
9415
|
import { join as join18 } from "path";
|
|
9298
9416
|
function getLocalStorePath4(repoRoot2, prdId) {
|
|
9299
9417
|
return join18(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
@@ -9304,26 +9422,6 @@ function getFinalReviewReceiptPath2(repoRoot2, prdId) {
|
|
|
9304
9422
|
function getHandoffArtifactPath(repoRoot2, prdId) {
|
|
9305
9423
|
return join18(repoRoot2, ".pourkit", ".tmp", "reconciliation", `${prdId}.json`);
|
|
9306
9424
|
}
|
|
9307
|
-
function getCompletionsDir(repoRoot2, prdId) {
|
|
9308
|
-
const initiativesRoot = join18(
|
|
9309
|
-
repoRoot2,
|
|
9310
|
-
".pourkit",
|
|
9311
|
-
"architecture",
|
|
9312
|
-
"initiatives"
|
|
9313
|
-
);
|
|
9314
|
-
if (existsSync15(initiativesRoot)) {
|
|
9315
|
-
const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join18(initiativesRoot, e.name));
|
|
9316
|
-
for (const dir of dirs) {
|
|
9317
|
-
const prdsDir = join18(dir, "prds");
|
|
9318
|
-
if (!existsSync15(prdsDir)) continue;
|
|
9319
|
-
const prdDirs = readdirSync5(prdsDir, { withFileTypes: true }).filter(
|
|
9320
|
-
(e) => e.isDirectory() && e.name.startsWith(prdId.toLowerCase())
|
|
9321
|
-
);
|
|
9322
|
-
if (prdDirs.length > 0) return join18(dir, "completions");
|
|
9323
|
-
}
|
|
9324
|
-
}
|
|
9325
|
-
return join18(getLocalStorePath4(repoRoot2, prdId), "completions");
|
|
9326
|
-
}
|
|
9327
9425
|
function getReconciliationReceiptPath(repoRoot2, prdId) {
|
|
9328
9426
|
return join18(
|
|
9329
9427
|
getLocalStorePath4(repoRoot2, prdId),
|
|
@@ -9365,6 +9463,20 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9365
9463
|
message: "Handoff artifact validation failed. Check Architect reconciliation output."
|
|
9366
9464
|
};
|
|
9367
9465
|
}
|
|
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
|
+
}
|
|
9368
9480
|
if (handoff.result === "blocked") {
|
|
9369
9481
|
return {
|
|
9370
9482
|
ok: false,
|
|
@@ -9379,48 +9491,6 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9379
9491
|
message: "Handoff artifact validation failed. Check Architect reconciliation output."
|
|
9380
9492
|
};
|
|
9381
9493
|
}
|
|
9382
|
-
if (handoff.result === "changes_produced") {
|
|
9383
|
-
try {
|
|
9384
|
-
const completionsDir = getCompletionsDir(root, prdId);
|
|
9385
|
-
mkdirSync10(completionsDir, { recursive: true });
|
|
9386
|
-
const existing = existsSync15(completionsDir) ? readdirSync5(completionsDir).filter((f) => f.endsWith(".md")) : [];
|
|
9387
|
-
const nextNum = String(existing.length + 1).padStart(3, "0");
|
|
9388
|
-
const filename = `${nextNum}-prd-${prdId.replace("PRD-", "").toLowerCase()}-reconciliation-completion.md`;
|
|
9389
|
-
const filepath = join18(completionsDir, filename);
|
|
9390
|
-
const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
|
|
9391
|
-
const summary = typeof handoff.summary === "string" ? handoff.summary : "";
|
|
9392
|
-
const lines = [
|
|
9393
|
-
`# ${prdId} Reconciliation Completion`,
|
|
9394
|
-
"",
|
|
9395
|
-
`Result: ${handoff.result}`
|
|
9396
|
-
];
|
|
9397
|
-
if (summary) lines.push(`Summary: ${summary}`);
|
|
9398
|
-
lines.push("", "## Changed Planning Paths", "");
|
|
9399
|
-
if (changedPaths.length > 0) {
|
|
9400
|
-
lines.push(...changedPaths.map((p) => `- ${p}`));
|
|
9401
|
-
} else {
|
|
9402
|
-
lines.push("- (none)");
|
|
9403
|
-
}
|
|
9404
|
-
lines.push("");
|
|
9405
|
-
writeFileSync7(filepath, lines.join("\n"), "utf-8");
|
|
9406
|
-
execFileSync4("git", ["add", filepath], {
|
|
9407
|
-
cwd: root,
|
|
9408
|
-
encoding: "utf8",
|
|
9409
|
-
stdio: "pipe"
|
|
9410
|
-
});
|
|
9411
|
-
execFileSync4(
|
|
9412
|
-
"git",
|
|
9413
|
-
["commit", "-m", `docs: ${prdId} reconciliation completion`],
|
|
9414
|
-
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
9415
|
-
);
|
|
9416
|
-
} catch {
|
|
9417
|
-
return {
|
|
9418
|
-
ok: false,
|
|
9419
|
-
failureCode: "completion_commit_failed",
|
|
9420
|
-
message: "Failed to commit completion files."
|
|
9421
|
-
};
|
|
9422
|
-
}
|
|
9423
|
-
}
|
|
9424
9494
|
if (handoff.result === "changes_produced") {
|
|
9425
9495
|
try {
|
|
9426
9496
|
const targetBranch = getLocalPrdBranchName(prdId);
|
|
@@ -9434,7 +9504,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9434
9504
|
return {
|
|
9435
9505
|
ok: false,
|
|
9436
9506
|
failureCode: "squash_merge_failed",
|
|
9437
|
-
message: "
|
|
9507
|
+
message: `Local PRD Branch "${targetBranch}" does not exist. Run prd-run start first to create it.`
|
|
9438
9508
|
};
|
|
9439
9509
|
}
|
|
9440
9510
|
execFileSync4("git", ["checkout", targetBranch], {
|
|
@@ -9457,7 +9527,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9457
9527
|
return {
|
|
9458
9528
|
ok: false,
|
|
9459
9529
|
failureCode: "squash_merge_failed",
|
|
9460
|
-
message:
|
|
9530
|
+
message: `Reconciliation source branch "${sourceBranch}" does not exist. Re-run reconciliation.`
|
|
9461
9531
|
};
|
|
9462
9532
|
}
|
|
9463
9533
|
execFileSync4("git", ["merge", "--squash", sourceBranch], {
|
|
@@ -9478,6 +9548,25 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9478
9548
|
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
9479
9549
|
);
|
|
9480
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();
|
|
9481
9570
|
} catch {
|
|
9482
9571
|
return {
|
|
9483
9572
|
ok: false,
|
|
@@ -9486,18 +9575,21 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9486
9575
|
};
|
|
9487
9576
|
}
|
|
9488
9577
|
}
|
|
9489
|
-
const
|
|
9490
|
-
const
|
|
9491
|
-
|
|
9492
|
-
|
|
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
|
|
9493
9585
|
};
|
|
9494
9586
|
mkdirSync10(getLocalStorePath4(root, prdId), { recursive: true });
|
|
9495
9587
|
writeFileSync7(
|
|
9496
9588
|
getReconciliationReceiptPath(root, prdId),
|
|
9497
|
-
JSON.stringify(
|
|
9589
|
+
JSON.stringify(entry, null, 2),
|
|
9498
9590
|
"utf-8"
|
|
9499
9591
|
);
|
|
9500
|
-
return { ok: true, receipt };
|
|
9592
|
+
return { ok: true, receipt: entry };
|
|
9501
9593
|
}
|
|
9502
9594
|
|
|
9503
9595
|
// prd-run/local-prepare.ts
|
|
@@ -9642,55 +9734,346 @@ async function validateLocalPrepareGates(prdId, repoRoot2) {
|
|
|
9642
9734
|
return { ok: true, gates };
|
|
9643
9735
|
}
|
|
9644
9736
|
|
|
9645
|
-
//
|
|
9646
|
-
|
|
9647
|
-
import {
|
|
9737
|
+
// prd-run/local-queue-loop.ts
|
|
9738
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
|
|
9739
|
+
import { join as join20 } from "path";
|
|
9648
9740
|
|
|
9649
|
-
//
|
|
9741
|
+
// prd-run/local-issue-run.ts
|
|
9742
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
9650
9743
|
init_common();
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
function selectIssue(candidates, options = {}) {
|
|
9659
|
-
const blockedLabel = options.blockedLabel ?? "blocked";
|
|
9660
|
-
const agentInProgressLabel = options.agentInProgressLabel ?? "agent-in-progress";
|
|
9661
|
-
if (candidates.length === 0) {
|
|
9662
|
-
return { ok: false, reason: "No candidate issues provided." };
|
|
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
|
+
);
|
|
9663
9751
|
}
|
|
9664
|
-
const
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
if (unblocked.length === 0) {
|
|
9668
|
-
return { ok: false, reason: "All candidate issues are blocked." };
|
|
9752
|
+
const issue = issuesResult.data.find((i) => i.id === issueId);
|
|
9753
|
+
if (!issue) {
|
|
9754
|
+
throw new Error(`Issue "${issueId}" not found under PRD "${prdId}"`);
|
|
9669
9755
|
}
|
|
9670
|
-
const
|
|
9671
|
-
const
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
);
|
|
9677
|
-
continue;
|
|
9678
|
-
}
|
|
9679
|
-
const typeLabels = issue.labels.filter(
|
|
9680
|
-
(l) => TYPE_LABELS.includes(l)
|
|
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}`
|
|
9681
9762
|
);
|
|
9682
|
-
if (typeLabels.length !== 1) {
|
|
9683
|
-
rejected.push(
|
|
9684
|
-
`Issue #${issue.number} has ${typeLabels.length} type label(s); expected exactly one.`
|
|
9685
|
-
);
|
|
9686
|
-
continue;
|
|
9687
|
-
}
|
|
9688
|
-
valid2.push({ issue, priority: TYPE_PRIORITY[typeLabels[0]] });
|
|
9689
9763
|
}
|
|
9690
|
-
|
|
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) {
|
|
9691
9829
|
return {
|
|
9692
9830
|
ok: false,
|
|
9693
|
-
|
|
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
|
+
|
|
10028
|
+
// commands/queue.ts
|
|
10029
|
+
init_common();
|
|
10030
|
+
import { Effect as Effect8 } from "effect";
|
|
10031
|
+
|
|
10032
|
+
// issues/select-issue.ts
|
|
10033
|
+
init_common();
|
|
10034
|
+
var TYPE_PRIORITY = {
|
|
10035
|
+
"type:bugfix": 1,
|
|
10036
|
+
"type:infra": 2,
|
|
10037
|
+
"type:feature": 3,
|
|
10038
|
+
"type:polish": 4,
|
|
10039
|
+
"type:refactor": 5
|
|
10040
|
+
};
|
|
10041
|
+
function selectIssue(candidates, options = {}) {
|
|
10042
|
+
const blockedLabel = options.blockedLabel ?? "blocked";
|
|
10043
|
+
const agentInProgressLabel = options.agentInProgressLabel ?? "agent-in-progress";
|
|
10044
|
+
if (candidates.length === 0) {
|
|
10045
|
+
return { ok: false, reason: "No candidate issues provided." };
|
|
10046
|
+
}
|
|
10047
|
+
const unblocked = candidates.filter(
|
|
10048
|
+
(issue) => !issue.labels.includes(blockedLabel)
|
|
10049
|
+
);
|
|
10050
|
+
if (unblocked.length === 0) {
|
|
10051
|
+
return { ok: false, reason: "All candidate issues are blocked." };
|
|
10052
|
+
}
|
|
10053
|
+
const valid2 = [];
|
|
10054
|
+
const rejected = [];
|
|
10055
|
+
for (const issue of unblocked) {
|
|
10056
|
+
if (issue.labels.includes(agentInProgressLabel)) {
|
|
10057
|
+
rejected.push(
|
|
10058
|
+
`Issue #${issue.number} is labeled ${agentInProgressLabel}.`
|
|
10059
|
+
);
|
|
10060
|
+
continue;
|
|
10061
|
+
}
|
|
10062
|
+
const typeLabels = issue.labels.filter(
|
|
10063
|
+
(l) => TYPE_LABELS.includes(l)
|
|
10064
|
+
);
|
|
10065
|
+
if (typeLabels.length !== 1) {
|
|
10066
|
+
rejected.push(
|
|
10067
|
+
`Issue #${issue.number} has ${typeLabels.length} type label(s); expected exactly one.`
|
|
10068
|
+
);
|
|
10069
|
+
continue;
|
|
10070
|
+
}
|
|
10071
|
+
valid2.push({ issue, priority: TYPE_PRIORITY[typeLabels[0]] });
|
|
10072
|
+
}
|
|
10073
|
+
if (valid2.length === 0) {
|
|
10074
|
+
return {
|
|
10075
|
+
ok: false,
|
|
10076
|
+
reason: "No runnable issues: " + rejected.join(" ")
|
|
9694
10077
|
};
|
|
9695
10078
|
}
|
|
9696
10079
|
valid2.sort((a, b) => {
|
|
@@ -10164,7 +10547,7 @@ function validateChangedPlanningPaths(paths, options) {
|
|
|
10164
10547
|
continue;
|
|
10165
10548
|
}
|
|
10166
10549
|
if (options?.repoRoot) {
|
|
10167
|
-
const fullPath =
|
|
10550
|
+
const fullPath = join21(options.repoRoot, p);
|
|
10168
10551
|
try {
|
|
10169
10552
|
if (lstatSync(fullPath).isSymbolicLink()) {
|
|
10170
10553
|
rejected.push(p);
|
|
@@ -10275,6 +10658,12 @@ async function validateFinalReviewChildCompleteness(repoRoot2, prdRef, record, i
|
|
|
10275
10658
|
);
|
|
10276
10659
|
continue;
|
|
10277
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
|
+
}
|
|
10278
10667
|
if (issueData.state === "closed") {
|
|
10279
10668
|
continue;
|
|
10280
10669
|
}
|
|
@@ -10331,11 +10720,7 @@ function canRetryFinalReviewBlock(record) {
|
|
|
10331
10720
|
}
|
|
10332
10721
|
function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
10333
10722
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
10334
|
-
return existsSync17(promptPath) ?
|
|
10335
|
-
}
|
|
10336
|
-
function formatVerificationCommands(commands) {
|
|
10337
|
-
if (commands.length === 0) return "No verification commands configured.";
|
|
10338
|
-
return commands.map((cmd) => `- ${cmd.label}: ${cmd.command}`).join("\n");
|
|
10723
|
+
return existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : promptTemplate;
|
|
10339
10724
|
}
|
|
10340
10725
|
function buildFinalReviewPrompt(options) {
|
|
10341
10726
|
return [
|
|
@@ -10351,9 +10736,6 @@ function buildFinalReviewPrompt(options) {
|
|
|
10351
10736
|
"",
|
|
10352
10737
|
`Run this command before handoff when retouch changes are made: pourkit run-verification --target ${options.targetName}`,
|
|
10353
10738
|
"",
|
|
10354
|
-
"Underlying project commands:",
|
|
10355
|
-
formatVerificationCommands(options.verificationCommands),
|
|
10356
|
-
"",
|
|
10357
10739
|
"Evidence Packet (do not infer PRD context from local state files):",
|
|
10358
10740
|
JSON.stringify(options.evidencePacket, null, 2),
|
|
10359
10741
|
"",
|
|
@@ -10385,7 +10767,7 @@ function buildReconciliationBranchName(prdRef) {
|
|
|
10385
10767
|
return `${normalizePrdRunRef2(prdRef)}-reconciliation`;
|
|
10386
10768
|
}
|
|
10387
10769
|
function reconciliationWorktreePath(repoRoot2, branchName) {
|
|
10388
|
-
return
|
|
10770
|
+
return join21(repoRoot2, ".sandcastle", "worktrees", branchName);
|
|
10389
10771
|
}
|
|
10390
10772
|
function reconciliationReceiptWorktreePath(branchName) {
|
|
10391
10773
|
return `.sandcastle/worktrees/${branchName}`;
|
|
@@ -10397,7 +10779,7 @@ function buildFinalReviewBranchName(prdRef) {
|
|
|
10397
10779
|
return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
|
|
10398
10780
|
}
|
|
10399
10781
|
function finalReviewWorktreePath(repoRoot2, branchName) {
|
|
10400
|
-
return
|
|
10782
|
+
return join21(
|
|
10401
10783
|
repoRoot2,
|
|
10402
10784
|
".sandcastle",
|
|
10403
10785
|
"worktrees",
|
|
@@ -10420,7 +10802,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
|
|
|
10420
10802
|
}
|
|
10421
10803
|
paths.add(path9);
|
|
10422
10804
|
};
|
|
10423
|
-
const diffResult =
|
|
10805
|
+
const diffResult = spawnSync3(
|
|
10424
10806
|
"git",
|
|
10425
10807
|
["diff", "--name-only", mergeBase, "--", "."],
|
|
10426
10808
|
{
|
|
@@ -10431,7 +10813,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
|
|
|
10431
10813
|
for (const path9 of diffResult.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
10432
10814
|
addPath(path9);
|
|
10433
10815
|
}
|
|
10434
|
-
const statusResult =
|
|
10816
|
+
const statusResult = spawnSync3(
|
|
10435
10817
|
"git",
|
|
10436
10818
|
["status", "--porcelain", "--untracked-files=all"],
|
|
10437
10819
|
{
|
|
@@ -10452,7 +10834,7 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
10452
10834
|
options.repoRoot,
|
|
10453
10835
|
options.promptTemplate
|
|
10454
10836
|
);
|
|
10455
|
-
const promptBody = existsSync17(promptPath) ?
|
|
10837
|
+
const promptBody = existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : options.promptTemplate;
|
|
10456
10838
|
return [
|
|
10457
10839
|
"# Final Review Retouch PR Finalizer",
|
|
10458
10840
|
"",
|
|
@@ -10478,15 +10860,12 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
10478
10860
|
"Changed paths:",
|
|
10479
10861
|
...options.changedPaths.map((p) => `- ${p}`),
|
|
10480
10862
|
"",
|
|
10481
|
-
"Verification commands given to Final Review:",
|
|
10482
|
-
formatVerificationCommands(options.verificationCommands),
|
|
10483
|
-
"",
|
|
10484
10863
|
promptBody
|
|
10485
10864
|
].join("\n");
|
|
10486
10865
|
}
|
|
10487
10866
|
async function runFinalReviewPrFinalizer(options) {
|
|
10488
10867
|
const finalizer = options.target.strategy.finalize.prDescriptionAgent;
|
|
10489
|
-
const artifactPathInWorktree =
|
|
10868
|
+
const artifactPathInWorktree = join21(
|
|
10490
10869
|
".pourkit",
|
|
10491
10870
|
".tmp",
|
|
10492
10871
|
"finalizer",
|
|
@@ -10507,8 +10886,7 @@ async function runFinalReviewPrFinalizer(options) {
|
|
|
10507
10886
|
prdBranch: options.prdBranch,
|
|
10508
10887
|
mergeBase: options.mergeBase,
|
|
10509
10888
|
summary: options.summary,
|
|
10510
|
-
changedPaths: options.changedPaths
|
|
10511
|
-
verificationCommands: options.verificationCommands
|
|
10889
|
+
changedPaths: options.changedPaths
|
|
10512
10890
|
}),
|
|
10513
10891
|
branchName: options.branchName,
|
|
10514
10892
|
repoRoot: options.repoRoot,
|
|
@@ -10532,7 +10910,7 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10532
10910
|
options.repoRoot,
|
|
10533
10911
|
options.branchName
|
|
10534
10912
|
);
|
|
10535
|
-
const listResult =
|
|
10913
|
+
const listResult = spawnSync3("git", ["worktree", "list", "--porcelain"], {
|
|
10536
10914
|
cwd: options.repoRoot,
|
|
10537
10915
|
encoding: "utf8"
|
|
10538
10916
|
});
|
|
@@ -10568,19 +10946,34 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10568
10946
|
};
|
|
10569
10947
|
}
|
|
10570
10948
|
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10949
|
+
if (options.isLocal) {
|
|
10950
|
+
const branchResult = spawnSync3(
|
|
10951
|
+
"git",
|
|
10952
|
+
["branch", "-f", options.branchName, options.checkoutBase],
|
|
10953
|
+
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
10954
|
+
);
|
|
10955
|
+
if (branchResult.status !== 0) {
|
|
10956
|
+
return {
|
|
10957
|
+
ok: false,
|
|
10958
|
+
reason: `Final Review failed to prepare branch ${options.branchName}.`,
|
|
10959
|
+
diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
|
|
10960
|
+
};
|
|
10961
|
+
}
|
|
10962
|
+
} else {
|
|
10963
|
+
const branchResult = spawnSync3(
|
|
10964
|
+
"git",
|
|
10965
|
+
["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
|
|
10966
|
+
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
10967
|
+
);
|
|
10968
|
+
if (branchResult.status !== 0) {
|
|
10969
|
+
return {
|
|
10970
|
+
ok: false,
|
|
10971
|
+
reason: `Final Review failed to prepare branch ${options.branchName}.`,
|
|
10972
|
+
diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
|
|
10973
|
+
};
|
|
10974
|
+
}
|
|
10582
10975
|
}
|
|
10583
|
-
const addResult =
|
|
10976
|
+
const addResult = spawnSync3(
|
|
10584
10977
|
"git",
|
|
10585
10978
|
["worktree", "add", worktreePath, options.branchName],
|
|
10586
10979
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -10622,7 +11015,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10622
11015
|
if (existingPr && existingPr.state === "OPEN") {
|
|
10623
11016
|
return existingPr;
|
|
10624
11017
|
}
|
|
10625
|
-
const worktreePath = mkdtempSync(
|
|
11018
|
+
const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-retouch-"));
|
|
10626
11019
|
try {
|
|
10627
11020
|
runGitOrThrow(
|
|
10628
11021
|
options.repoRoot,
|
|
@@ -10640,8 +11033,8 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10640
11033
|
"create retouch branch"
|
|
10641
11034
|
);
|
|
10642
11035
|
for (const changedFile of options.changedPaths) {
|
|
10643
|
-
const sourcePath =
|
|
10644
|
-
const targetPath =
|
|
11036
|
+
const sourcePath = join21(options.sourceWorktreePath, changedFile);
|
|
11037
|
+
const targetPath = join21(worktreePath, changedFile);
|
|
10645
11038
|
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
10646
11039
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
10647
11040
|
}
|
|
@@ -10656,13 +11049,13 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10656
11049
|
"commit retouch diff"
|
|
10657
11050
|
);
|
|
10658
11051
|
} finally {
|
|
10659
|
-
|
|
11052
|
+
spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
|
|
10660
11053
|
cwd: options.repoRoot,
|
|
10661
11054
|
encoding: "utf8"
|
|
10662
11055
|
});
|
|
10663
11056
|
rmSync3(worktreePath, { recursive: true, force: true });
|
|
10664
11057
|
}
|
|
10665
|
-
const pushResult =
|
|
11058
|
+
const pushResult = spawnSync3(
|
|
10666
11059
|
"git",
|
|
10667
11060
|
["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
|
|
10668
11061
|
{
|
|
@@ -10690,6 +11083,156 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10690
11083
|
})
|
|
10691
11084
|
});
|
|
10692
11085
|
}
|
|
11086
|
+
async function writeAndVerifyLocalDualReceipt(repoRoot2, prdRef, record, targetName, prdBranch, mergeBase, verdict, reviewedAt, isLocalMode) {
|
|
11087
|
+
if (!isLocalMode) return null;
|
|
11088
|
+
let currentRecord = await readLocalPrdRun(repoRoot2, prdRef);
|
|
11089
|
+
if (!currentRecord) {
|
|
11090
|
+
currentRecord = {
|
|
11091
|
+
prdId: normalizePrdRunRef2(prdRef),
|
|
11092
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11093
|
+
receipts: {},
|
|
11094
|
+
metadata: {}
|
|
11095
|
+
};
|
|
11096
|
+
}
|
|
11097
|
+
const localStoreReceipt = {
|
|
11098
|
+
completedAt: reviewedAt,
|
|
11099
|
+
targetName,
|
|
11100
|
+
prdBranch,
|
|
11101
|
+
mergeBase,
|
|
11102
|
+
verdict,
|
|
11103
|
+
diagnostics: [],
|
|
11104
|
+
artifactPath: ".pourkit/final-review-artifact.json"
|
|
11105
|
+
};
|
|
11106
|
+
await writeLocalPrdRunRecord(repoRoot2, prdRef, {
|
|
11107
|
+
...currentRecord,
|
|
11108
|
+
receipts: {
|
|
11109
|
+
...currentRecord.receipts,
|
|
11110
|
+
finalReview: localStoreReceipt
|
|
11111
|
+
}
|
|
11112
|
+
});
|
|
11113
|
+
const prdRunStateResult = readPrdRun(repoRoot2, prdRef);
|
|
11114
|
+
const localStoreResult = await readLocalPrdRun(repoRoot2, prdRef);
|
|
11115
|
+
if (!prdRunStateResult.record?.finalReview) {
|
|
11116
|
+
const reason = `PRD Run State missing finalReview receipt after write for ${prdRef}.`;
|
|
11117
|
+
writePrdRunRecord(repoRoot2, {
|
|
11118
|
+
...record,
|
|
11119
|
+
prdRef,
|
|
11120
|
+
status: "blocked",
|
|
11121
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11122
|
+
blockedGate: "final-review",
|
|
11123
|
+
blockedReason: reason,
|
|
11124
|
+
diagnostics: [reason],
|
|
11125
|
+
targetName,
|
|
11126
|
+
offendingPaths: []
|
|
11127
|
+
});
|
|
11128
|
+
return {
|
|
11129
|
+
prdRef,
|
|
11130
|
+
status: "blocked",
|
|
11131
|
+
blockedGate: "final-review",
|
|
11132
|
+
blockedReason: reason,
|
|
11133
|
+
diagnostics: [reason],
|
|
11134
|
+
offendingPaths: []
|
|
11135
|
+
};
|
|
11136
|
+
}
|
|
11137
|
+
if (!localStoreResult?.receipts.finalReview) {
|
|
11138
|
+
const reason = `Local PRD Run store missing finalReview receipt after write for ${prdRef}.`;
|
|
11139
|
+
writePrdRunRecord(repoRoot2, {
|
|
11140
|
+
...record,
|
|
11141
|
+
prdRef,
|
|
11142
|
+
status: "blocked",
|
|
11143
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11144
|
+
blockedGate: "final-review",
|
|
11145
|
+
blockedReason: reason,
|
|
11146
|
+
diagnostics: [reason],
|
|
11147
|
+
targetName,
|
|
11148
|
+
offendingPaths: []
|
|
11149
|
+
});
|
|
11150
|
+
return {
|
|
11151
|
+
prdRef,
|
|
11152
|
+
status: "blocked",
|
|
11153
|
+
blockedGate: "final-review",
|
|
11154
|
+
blockedReason: reason,
|
|
11155
|
+
diagnostics: [reason],
|
|
11156
|
+
offendingPaths: []
|
|
11157
|
+
};
|
|
11158
|
+
}
|
|
11159
|
+
const stateFr = prdRunStateResult.record.finalReview;
|
|
11160
|
+
const localFr = localStoreResult.receipts.finalReview;
|
|
11161
|
+
const mismatchFields = [];
|
|
11162
|
+
if (stateFr.targetName !== localFr.targetName)
|
|
11163
|
+
mismatchFields.push("targetName");
|
|
11164
|
+
if (stateFr.prdBranch !== localFr.prdBranch) mismatchFields.push("prdBranch");
|
|
11165
|
+
if (stateFr.mergeBase !== localFr.mergeBase) mismatchFields.push("mergeBase");
|
|
11166
|
+
if (stateFr.verdict !== localFr.verdict) mismatchFields.push("verdict");
|
|
11167
|
+
if (!stateFr.artifactPath !== !localFr.artifactPath)
|
|
11168
|
+
mismatchFields.push("artifactPath");
|
|
11169
|
+
if (!stateFr.diagnostics !== !localFr.diagnostics)
|
|
11170
|
+
mismatchFields.push("diagnostics");
|
|
11171
|
+
if (!stateFr.reviewedAt !== !localFr.completedAt)
|
|
11172
|
+
mismatchFields.push("reviewedTimestamp");
|
|
11173
|
+
if (mismatchFields.length > 0) {
|
|
11174
|
+
const reason = `Dual receipt mismatch on fields: ${mismatchFields.join(", ")}`;
|
|
11175
|
+
writePrdRunRecord(repoRoot2, {
|
|
11176
|
+
...record,
|
|
11177
|
+
prdRef,
|
|
11178
|
+
status: "blocked",
|
|
11179
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11180
|
+
blockedGate: "final-review",
|
|
11181
|
+
blockedReason: reason,
|
|
11182
|
+
diagnostics: [reason],
|
|
11183
|
+
targetName,
|
|
11184
|
+
finalReview: stateFr,
|
|
11185
|
+
offendingPaths: []
|
|
11186
|
+
});
|
|
11187
|
+
return {
|
|
11188
|
+
prdRef,
|
|
11189
|
+
status: "blocked",
|
|
11190
|
+
blockedGate: "final-review",
|
|
11191
|
+
blockedReason: reason,
|
|
11192
|
+
diagnostics: [reason],
|
|
11193
|
+
offendingPaths: []
|
|
11194
|
+
};
|
|
11195
|
+
}
|
|
11196
|
+
return null;
|
|
11197
|
+
}
|
|
11198
|
+
async function verifyLocalDualFinalReviewReceipts(repoRoot2, prdRef) {
|
|
11199
|
+
const prdRunStateResult = readPrdRun(repoRoot2, prdRef);
|
|
11200
|
+
const stateFr = prdRunStateResult.record?.finalReview;
|
|
11201
|
+
const localStoreResult = await readLocalPrdRun(repoRoot2, prdRef);
|
|
11202
|
+
const localFr = localStoreResult?.receipts.finalReview;
|
|
11203
|
+
const diagnostics = [];
|
|
11204
|
+
const validFinalReviewStatuses = /* @__PURE__ */ new Set(["final_reviewed", "succeeded"]);
|
|
11205
|
+
if (!stateFr || !validFinalReviewStatuses.has(stateFr.status)) {
|
|
11206
|
+
diagnostics.push(
|
|
11207
|
+
stateFr ? `PRD Run State finalReview status is "${stateFr.status}". Expected "final_reviewed" or "succeeded".` : "PRD Run State has no finalReview receipt."
|
|
11208
|
+
);
|
|
11209
|
+
return {
|
|
11210
|
+
ok: false,
|
|
11211
|
+
reason: diagnostics[0],
|
|
11212
|
+
diagnostics
|
|
11213
|
+
};
|
|
11214
|
+
}
|
|
11215
|
+
if (!localFr) {
|
|
11216
|
+
diagnostics.push("Local PRD Run Store has no finalReview receipt.");
|
|
11217
|
+
return {
|
|
11218
|
+
ok: false,
|
|
11219
|
+
reason: diagnostics[0],
|
|
11220
|
+
diagnostics
|
|
11221
|
+
};
|
|
11222
|
+
}
|
|
11223
|
+
const mismatchFields = [];
|
|
11224
|
+
if (stateFr.targetName !== localFr.targetName)
|
|
11225
|
+
mismatchFields.push("targetName");
|
|
11226
|
+
if (stateFr.prdBranch !== localFr.prdBranch) mismatchFields.push("prdBranch");
|
|
11227
|
+
if (stateFr.mergeBase !== localFr.mergeBase) mismatchFields.push("mergeBase");
|
|
11228
|
+
if (stateFr.verdict !== localFr.verdict) mismatchFields.push("verdict");
|
|
11229
|
+
if (mismatchFields.length > 0) {
|
|
11230
|
+
const reason = `Dual receipt mismatch on fields: ${mismatchFields.join(", ")}`;
|
|
11231
|
+
diagnostics.push(reason);
|
|
11232
|
+
return { ok: false, reason, diagnostics };
|
|
11233
|
+
}
|
|
11234
|
+
return { ok: true };
|
|
11235
|
+
}
|
|
10693
11236
|
async function runPrdRunFinalReviewCommand(options) {
|
|
10694
11237
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
10695
11238
|
const targetName = options.targetName.trim();
|
|
@@ -10754,6 +11297,39 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10754
11297
|
offendingPaths: []
|
|
10755
11298
|
};
|
|
10756
11299
|
}
|
|
11300
|
+
if (record.mode === "local") {
|
|
11301
|
+
const runnableIssues = await getRunnableLocalIssues(
|
|
11302
|
+
prdRef,
|
|
11303
|
+
options.repoRoot
|
|
11304
|
+
);
|
|
11305
|
+
if (runnableIssues.length > 0) {
|
|
11306
|
+
const reason = `PRD Run ${prdRef} is marked "drained" but ${runnableIssues.length} runnable local Issues remain. Queue drain receipt may be premature.`;
|
|
11307
|
+
writePrdRunRecord(options.repoRoot, {
|
|
11308
|
+
...record,
|
|
11309
|
+
status: "blocked",
|
|
11310
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11311
|
+
blockedGate: "final-review",
|
|
11312
|
+
blockedReason: reason,
|
|
11313
|
+
diagnostics: [
|
|
11314
|
+
`Current status: ${record.status}`,
|
|
11315
|
+
`Runnable issues remaining: ${runnableIssues.length}`
|
|
11316
|
+
],
|
|
11317
|
+
targetName,
|
|
11318
|
+
offendingPaths: []
|
|
11319
|
+
});
|
|
11320
|
+
return {
|
|
11321
|
+
prdRef,
|
|
11322
|
+
status: "blocked",
|
|
11323
|
+
blockedGate: "final-review",
|
|
11324
|
+
blockedReason: reason,
|
|
11325
|
+
diagnostics: [
|
|
11326
|
+
`Current status: ${record.status}`,
|
|
11327
|
+
`Runnable issues remaining: ${runnableIssues.length}`
|
|
11328
|
+
],
|
|
11329
|
+
offendingPaths: []
|
|
11330
|
+
};
|
|
11331
|
+
}
|
|
11332
|
+
}
|
|
10757
11333
|
if (!options.issueProvider) {
|
|
10758
11334
|
const reason = "Missing IssueProvider. Cannot validate child completeness.";
|
|
10759
11335
|
writePrdRunRecord(options.repoRoot, {
|
|
@@ -10862,8 +11438,19 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10862
11438
|
offendingPaths: []
|
|
10863
11439
|
};
|
|
10864
11440
|
}
|
|
10865
|
-
const
|
|
10866
|
-
|
|
11441
|
+
const isLocalMode = record.mode === "local";
|
|
11442
|
+
let mergeBaseResult;
|
|
11443
|
+
let prdBranch;
|
|
11444
|
+
if (isLocalMode) {
|
|
11445
|
+
prdBranch = getLocalPrdBranchName(prdRef);
|
|
11446
|
+
mergeBaseResult = computeLocalFinalReviewMergeBase(
|
|
11447
|
+
options.repoRoot,
|
|
11448
|
+
prdBranch
|
|
11449
|
+
);
|
|
11450
|
+
} else {
|
|
11451
|
+
prdBranch = record.prdBranch ?? prdRef;
|
|
11452
|
+
mergeBaseResult = computeFinalReviewMergeBase(options.repoRoot, prdRef);
|
|
11453
|
+
}
|
|
10867
11454
|
if (!mergeBaseResult.ok) {
|
|
10868
11455
|
writePrdRunRecord(options.repoRoot, {
|
|
10869
11456
|
...record,
|
|
@@ -10876,7 +11463,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10876
11463
|
finalReview: {
|
|
10877
11464
|
status: "blocked",
|
|
10878
11465
|
targetName,
|
|
10879
|
-
prdBranch
|
|
11466
|
+
prdBranch,
|
|
10880
11467
|
mergeBase: mergeBaseResult.mergeBase,
|
|
10881
11468
|
diagnostics: mergeBaseResult.diagnostics,
|
|
10882
11469
|
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -10892,7 +11479,6 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10892
11479
|
offendingPaths: []
|
|
10893
11480
|
};
|
|
10894
11481
|
}
|
|
10895
|
-
const prdBranch = record.prdBranch ?? prdRef;
|
|
10896
11482
|
const manifestResult = readPlanningArtifactManifest(options.repoRoot, prdRef);
|
|
10897
11483
|
if (!manifestResult.ok) {
|
|
10898
11484
|
const reason = `Evidence Packet construction failed: ${manifestResult.reason}`;
|
|
@@ -10952,11 +11538,12 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10952
11538
|
planning: record.planning,
|
|
10953
11539
|
finalReview: startReceipt
|
|
10954
11540
|
});
|
|
10955
|
-
const finalReviewBranchName = buildFinalReviewBranchName(prdBranch);
|
|
11541
|
+
const finalReviewBranchName = isLocalMode ? `${prdBranch.replace(/\//g, "-")}-final-review-retouch` : buildFinalReviewBranchName(prdBranch);
|
|
10956
11542
|
const worktreeResult = ensureFinalReviewWorktree({
|
|
10957
11543
|
repoRoot: options.repoRoot,
|
|
10958
11544
|
branchName: finalReviewBranchName,
|
|
10959
|
-
checkoutBase: prdBranch
|
|
11545
|
+
checkoutBase: prdBranch,
|
|
11546
|
+
isLocal: isLocalMode
|
|
10960
11547
|
});
|
|
10961
11548
|
if (!worktreeResult.ok) {
|
|
10962
11549
|
writePrdRunRecord(options.repoRoot, {
|
|
@@ -10997,8 +11584,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10997
11584
|
repoRoot: options.repoRoot,
|
|
10998
11585
|
promptTemplate: finalReviewConfig.promptTemplate,
|
|
10999
11586
|
targetName,
|
|
11000
|
-
evidencePacket
|
|
11001
|
-
verificationCommands
|
|
11587
|
+
evidencePacket
|
|
11002
11588
|
}),
|
|
11003
11589
|
target: targetConfig,
|
|
11004
11590
|
repoRoot: options.repoRoot,
|
|
@@ -11072,7 +11658,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11072
11658
|
};
|
|
11073
11659
|
}
|
|
11074
11660
|
const resolvedWorktreePath = executionResult.worktreePath;
|
|
11075
|
-
const artifactPath =
|
|
11661
|
+
const artifactPath = join21(
|
|
11076
11662
|
resolvedWorktreePath,
|
|
11077
11663
|
".pourkit/final-review-artifact.json"
|
|
11078
11664
|
);
|
|
@@ -11141,6 +11727,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11141
11727
|
}
|
|
11142
11728
|
const { verdict, summary, diagnostics: artifactDiagnostics } = artifactResult;
|
|
11143
11729
|
if (verdict === "pass_no_changes") {
|
|
11730
|
+
const reviewedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11144
11731
|
const receipt = {
|
|
11145
11732
|
status: "succeeded",
|
|
11146
11733
|
targetName,
|
|
@@ -11149,17 +11736,29 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11149
11736
|
verdict: "pass_no_changes",
|
|
11150
11737
|
artifactPath: ".pourkit/final-review-artifact.json",
|
|
11151
11738
|
diagnostics: [],
|
|
11152
|
-
reviewedAt
|
|
11739
|
+
reviewedAt
|
|
11153
11740
|
};
|
|
11154
11741
|
writePrdRunRecord(options.repoRoot, {
|
|
11155
11742
|
...record,
|
|
11156
11743
|
status: "final_reviewed",
|
|
11157
|
-
updatedAt:
|
|
11744
|
+
updatedAt: reviewedAt,
|
|
11158
11745
|
targetName,
|
|
11159
11746
|
start: record.start,
|
|
11160
11747
|
planning: record.planning,
|
|
11161
11748
|
finalReview: receipt
|
|
11162
11749
|
});
|
|
11750
|
+
const blocked = await writeAndVerifyLocalDualReceipt(
|
|
11751
|
+
options.repoRoot,
|
|
11752
|
+
prdRef,
|
|
11753
|
+
record,
|
|
11754
|
+
targetName,
|
|
11755
|
+
prdBranch,
|
|
11756
|
+
mergeBaseResult.mergeBase,
|
|
11757
|
+
"pass_no_changes",
|
|
11758
|
+
reviewedAt,
|
|
11759
|
+
isLocalMode
|
|
11760
|
+
);
|
|
11761
|
+
if (blocked) return blocked;
|
|
11163
11762
|
return {
|
|
11164
11763
|
prdRef,
|
|
11165
11764
|
status: "final_reviewed",
|
|
@@ -11168,7 +11767,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11168
11767
|
};
|
|
11169
11768
|
}
|
|
11170
11769
|
if (verdict === "pass_with_retouch") {
|
|
11171
|
-
if (!options.prProvider) {
|
|
11770
|
+
if (!options.prProvider && !isLocalMode) {
|
|
11172
11771
|
const reason = "Missing PRProvider. Cannot create retouch PR without a PR provider.";
|
|
11173
11772
|
writePrdRunRecord(options.repoRoot, {
|
|
11174
11773
|
...record,
|
|
@@ -11199,7 +11798,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11199
11798
|
};
|
|
11200
11799
|
}
|
|
11201
11800
|
const autoMerge = options.autoMerge ?? true;
|
|
11202
|
-
const existingRetouchPrNumber = record.finalReview?.retouchPrNumber;
|
|
11801
|
+
const existingRetouchPrNumber = !isLocalMode ? record.finalReview?.retouchPrNumber : void 0;
|
|
11203
11802
|
if (existingRetouchPrNumber && autoMerge) {
|
|
11204
11803
|
const existingPr = await getPrByNumberIfAvailable(
|
|
11205
11804
|
options.prProvider,
|
|
@@ -11285,7 +11884,6 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11285
11884
|
mergeBase: mergeBaseResult.mergeBase,
|
|
11286
11885
|
summary,
|
|
11287
11886
|
changedPaths: scopeResult.changedPaths,
|
|
11288
|
-
verificationCommands,
|
|
11289
11887
|
logger: options.logger
|
|
11290
11888
|
});
|
|
11291
11889
|
if (!finalizerResult.ok) {
|
|
@@ -11317,6 +11915,116 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11317
11915
|
offendingPaths: []
|
|
11318
11916
|
};
|
|
11319
11917
|
}
|
|
11918
|
+
if (isLocalMode) {
|
|
11919
|
+
let localResult;
|
|
11920
|
+
try {
|
|
11921
|
+
const result = await squashFinalReviewRetouch(
|
|
11922
|
+
prdRef,
|
|
11923
|
+
options.repoRoot,
|
|
11924
|
+
finalizerResult.title,
|
|
11925
|
+
finalizerResult.body
|
|
11926
|
+
);
|
|
11927
|
+
if (!result) {
|
|
11928
|
+
const reason = "Retouch branch missing or empty. Cannot squash-merge.";
|
|
11929
|
+
writePrdRunRecord(options.repoRoot, {
|
|
11930
|
+
...record,
|
|
11931
|
+
status: "blocked",
|
|
11932
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11933
|
+
blockedGate: "final-review",
|
|
11934
|
+
blockedReason: reason,
|
|
11935
|
+
diagnostics: [reason],
|
|
11936
|
+
targetName,
|
|
11937
|
+
finalReview: {
|
|
11938
|
+
status: "blocked",
|
|
11939
|
+
targetName,
|
|
11940
|
+
prdBranch,
|
|
11941
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
11942
|
+
verdict: "pass_with_retouch",
|
|
11943
|
+
diagnostics: [reason],
|
|
11944
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11945
|
+
},
|
|
11946
|
+
offendingPaths: []
|
|
11947
|
+
});
|
|
11948
|
+
return {
|
|
11949
|
+
prdRef,
|
|
11950
|
+
status: "blocked",
|
|
11951
|
+
blockedGate: "final-review",
|
|
11952
|
+
blockedReason: reason,
|
|
11953
|
+
diagnostics: [reason],
|
|
11954
|
+
offendingPaths: []
|
|
11955
|
+
};
|
|
11956
|
+
}
|
|
11957
|
+
localResult = result;
|
|
11958
|
+
} catch (error) {
|
|
11959
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11960
|
+
writePrdRunRecord(options.repoRoot, {
|
|
11961
|
+
...record,
|
|
11962
|
+
status: "blocked",
|
|
11963
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11964
|
+
blockedGate: "final-review",
|
|
11965
|
+
blockedReason: `Local retouch squash-merge failed: ${msg}`,
|
|
11966
|
+
diagnostics: [msg],
|
|
11967
|
+
targetName,
|
|
11968
|
+
finalReview: {
|
|
11969
|
+
status: "blocked",
|
|
11970
|
+
targetName,
|
|
11971
|
+
prdBranch,
|
|
11972
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
11973
|
+
verdict: "pass_with_retouch",
|
|
11974
|
+
diagnostics: [msg],
|
|
11975
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11976
|
+
},
|
|
11977
|
+
offendingPaths: []
|
|
11978
|
+
});
|
|
11979
|
+
return {
|
|
11980
|
+
prdRef,
|
|
11981
|
+
status: "blocked",
|
|
11982
|
+
blockedGate: "final-review",
|
|
11983
|
+
blockedReason: `Local retouch squash-merge failed: ${msg}`,
|
|
11984
|
+
diagnostics: [msg],
|
|
11985
|
+
offendingPaths: []
|
|
11986
|
+
};
|
|
11987
|
+
}
|
|
11988
|
+
const receipt2 = {
|
|
11989
|
+
status: "final_reviewed",
|
|
11990
|
+
targetName,
|
|
11991
|
+
prdBranch,
|
|
11992
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
11993
|
+
verdict: "pass_with_retouch",
|
|
11994
|
+
artifactPath: ".pourkit/final-review-artifact.json",
|
|
11995
|
+
diagnostics: [],
|
|
11996
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11997
|
+
retouchMergeCommit: localResult.mergeCommit,
|
|
11998
|
+
changedPaths: localResult.changedPaths
|
|
11999
|
+
};
|
|
12000
|
+
writePrdRunRecord(options.repoRoot, {
|
|
12001
|
+
...record,
|
|
12002
|
+
status: "final_reviewed",
|
|
12003
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12004
|
+
targetName,
|
|
12005
|
+
start: record.start,
|
|
12006
|
+
planning: record.planning,
|
|
12007
|
+
finalReview: receipt2
|
|
12008
|
+
});
|
|
12009
|
+
const blocked = await writeAndVerifyLocalDualReceipt(
|
|
12010
|
+
options.repoRoot,
|
|
12011
|
+
prdRef,
|
|
12012
|
+
record,
|
|
12013
|
+
targetName,
|
|
12014
|
+
prdBranch,
|
|
12015
|
+
mergeBaseResult.mergeBase,
|
|
12016
|
+
"pass_with_retouch",
|
|
12017
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
12018
|
+
isLocalMode
|
|
12019
|
+
);
|
|
12020
|
+
if (blocked) return blocked;
|
|
12021
|
+
return {
|
|
12022
|
+
prdRef,
|
|
12023
|
+
status: "final_reviewed",
|
|
12024
|
+
finalReview: receipt2,
|
|
12025
|
+
diagnostics: []
|
|
12026
|
+
};
|
|
12027
|
+
}
|
|
11320
12028
|
let retouchPr;
|
|
11321
12029
|
try {
|
|
11322
12030
|
retouchPr = await createOrReuseFinalReviewRetouchPr({
|
|
@@ -11656,7 +12364,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11656
12364
|
function runPrdRunValidateFinalReviewCommand(options) {
|
|
11657
12365
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
11658
12366
|
const checkoutBase = options.checkoutBase ?? prdRef;
|
|
11659
|
-
const artifactPath = options.artifactPath ? options.artifactPath :
|
|
12367
|
+
const artifactPath = options.artifactPath ? options.artifactPath : join21(options.repoRoot, ".pourkit", "final-review-artifact.json");
|
|
11660
12368
|
const artifact = parseFinalReviewArtifact(artifactPath);
|
|
11661
12369
|
if (!artifact.ok) {
|
|
11662
12370
|
return {
|
|
@@ -11670,7 +12378,7 @@ function runPrdRunValidateFinalReviewCommand(options) {
|
|
|
11670
12378
|
}
|
|
11671
12379
|
let changedPaths = options.changedPaths;
|
|
11672
12380
|
if (artifact.verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && (!artifact.changedPaths || artifact.changedPaths.length === 0)) {
|
|
11673
|
-
const diffResult =
|
|
12381
|
+
const diffResult = spawnSync3(
|
|
11674
12382
|
"git",
|
|
11675
12383
|
["diff", "--name-only", options.reviewBase, "HEAD", "--", "."],
|
|
11676
12384
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -11822,12 +12530,12 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11822
12530
|
);
|
|
11823
12531
|
const worktreeCwd = worktreePath;
|
|
11824
12532
|
const existingReconciliationReceipt = record.reconciliation;
|
|
11825
|
-
const existingLocalBranch =
|
|
12533
|
+
const existingLocalBranch = spawnSync3(
|
|
11826
12534
|
"git",
|
|
11827
12535
|
["rev-parse", "--verify", "--quiet", reconciliationBranchName],
|
|
11828
12536
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
11829
12537
|
).status === 0;
|
|
11830
|
-
const existingWorktree =
|
|
12538
|
+
const existingWorktree = spawnSync3(
|
|
11831
12539
|
"git",
|
|
11832
12540
|
["worktree", "list", "--porcelain"],
|
|
11833
12541
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -11880,7 +12588,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11880
12588
|
branchAction: "blocked"
|
|
11881
12589
|
};
|
|
11882
12590
|
}
|
|
11883
|
-
const resetResult =
|
|
12591
|
+
const resetResult = spawnSync3(
|
|
11884
12592
|
"git",
|
|
11885
12593
|
["reset", "--hard", `origin/${prdBranch}`],
|
|
11886
12594
|
{ cwd: worktreeCwd, encoding: "utf8" }
|
|
@@ -11923,7 +12631,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11923
12631
|
);
|
|
11924
12632
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11925
12633
|
}
|
|
11926
|
-
const adoptionCheck =
|
|
12634
|
+
const adoptionCheck = spawnSync3(
|
|
11927
12635
|
"git",
|
|
11928
12636
|
[
|
|
11929
12637
|
"merge-base",
|
|
@@ -11971,7 +12679,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11971
12679
|
);
|
|
11972
12680
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11973
12681
|
}
|
|
11974
|
-
const branchResult =
|
|
12682
|
+
const branchResult = spawnSync3(
|
|
11975
12683
|
"git",
|
|
11976
12684
|
["branch", "-f", reconciliationBranchName, `origin/${prdBranch}`],
|
|
11977
12685
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -11986,13 +12694,13 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11986
12694
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11987
12695
|
}
|
|
11988
12696
|
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
11989
|
-
const addResult =
|
|
12697
|
+
const addResult = spawnSync3(
|
|
11990
12698
|
"git",
|
|
11991
12699
|
["worktree", "add", worktreePath, reconciliationBranchName],
|
|
11992
12700
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
11993
12701
|
);
|
|
11994
12702
|
if (addResult.status !== 0) {
|
|
11995
|
-
|
|
12703
|
+
spawnSync3("git", ["branch", "-D", reconciliationBranchName], {
|
|
11996
12704
|
cwd: options.repoRoot,
|
|
11997
12705
|
encoding: "utf8"
|
|
11998
12706
|
});
|
|
@@ -12040,7 +12748,7 @@ function buildReconciliationPrBody(options) {
|
|
|
12040
12748
|
function commitAndPushReconciliationChanges(options) {
|
|
12041
12749
|
const diagnostics = [];
|
|
12042
12750
|
const existingPaths = options.changedPlanningPaths.filter((p) => {
|
|
12043
|
-
return existsSync17(
|
|
12751
|
+
return existsSync17(join21(options.worktreeCwd, p));
|
|
12044
12752
|
});
|
|
12045
12753
|
if (existingPaths.length === 0) {
|
|
12046
12754
|
diagnostics.push(
|
|
@@ -12048,7 +12756,7 @@ function commitAndPushReconciliationChanges(options) {
|
|
|
12048
12756
|
);
|
|
12049
12757
|
return { ok: true, diagnostics: [...diagnostics] };
|
|
12050
12758
|
}
|
|
12051
|
-
const addResult =
|
|
12759
|
+
const addResult = spawnSync3("git", ["add", "--", ...existingPaths], {
|
|
12052
12760
|
cwd: options.worktreeCwd,
|
|
12053
12761
|
encoding: "utf8"
|
|
12054
12762
|
});
|
|
@@ -12065,7 +12773,7 @@ function commitAndPushReconciliationChanges(options) {
|
|
|
12065
12773
|
};
|
|
12066
12774
|
}
|
|
12067
12775
|
const commitTitle = `docs: reconcile ${normalizePrdRunRef2(options.prdRef)} architecture`;
|
|
12068
|
-
const commitResult =
|
|
12776
|
+
const commitResult = spawnSync3("git", ["commit", "-m", commitTitle], {
|
|
12069
12777
|
cwd: options.worktreeCwd,
|
|
12070
12778
|
encoding: "utf8"
|
|
12071
12779
|
});
|
|
@@ -12089,7 +12797,7 @@ function commitAndPushReconciliationChanges(options) {
|
|
|
12089
12797
|
};
|
|
12090
12798
|
}
|
|
12091
12799
|
}
|
|
12092
|
-
const pushResult =
|
|
12800
|
+
const pushResult = spawnSync3(
|
|
12093
12801
|
"git",
|
|
12094
12802
|
[
|
|
12095
12803
|
"push",
|
|
@@ -12183,7 +12891,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12183
12891
|
const record = preflightResult.record;
|
|
12184
12892
|
const prdBranch = record?.prdBranch ?? prdRef;
|
|
12185
12893
|
const mergeBase = preflightResult.mergeBase;
|
|
12186
|
-
const worktreeCwd = preflightResult.worktreePath ?
|
|
12894
|
+
const worktreeCwd = preflightResult.worktreePath ? join21(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
|
|
12187
12895
|
const staleSucceededRecovery = assessStaleSucceededReconciliationReceipt({
|
|
12188
12896
|
receipt: record?.reconciliation,
|
|
12189
12897
|
worktreeCwd,
|
|
@@ -12485,7 +13193,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12485
13193
|
baseRef: prdBranch,
|
|
12486
13194
|
checkoutBase,
|
|
12487
13195
|
reviewBase: mergeBase,
|
|
12488
|
-
worktreePath:
|
|
13196
|
+
worktreePath: join21(options.repoRoot, preflightResult.worktreePath),
|
|
12489
13197
|
sandbox: options.config.sandbox,
|
|
12490
13198
|
artifactPath,
|
|
12491
13199
|
logger: options.logger
|
|
@@ -12586,7 +13294,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12586
13294
|
offendingPaths: []
|
|
12587
13295
|
};
|
|
12588
13296
|
}
|
|
12589
|
-
const fullArtifactPath =
|
|
13297
|
+
const fullArtifactPath = join21(executionResult.worktreePath, artifactPath);
|
|
12590
13298
|
let artifactContent;
|
|
12591
13299
|
try {
|
|
12592
13300
|
artifactContent = JSON.parse(retryResult.artifact.value);
|
|
@@ -12693,11 +13401,11 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12693
13401
|
const artifactResult = artifactContent.result ?? "changes_produced";
|
|
12694
13402
|
const artifactChangedPaths = artifactContent.changedPlanningPaths ?? [];
|
|
12695
13403
|
const artifactNoChangeRationale = artifactContent.noChangeRationale;
|
|
12696
|
-
const gitStatusResult =
|
|
13404
|
+
const gitStatusResult = spawnSync3("git", ["status", "--short"], {
|
|
12697
13405
|
cwd: worktreeCwd,
|
|
12698
13406
|
encoding: "utf8"
|
|
12699
13407
|
});
|
|
12700
|
-
const gitDiffResult =
|
|
13408
|
+
const gitDiffResult = spawnSync3("git", ["diff", "--name-status"], {
|
|
12701
13409
|
cwd: worktreeCwd,
|
|
12702
13410
|
encoding: "utf8"
|
|
12703
13411
|
});
|
|
@@ -13450,7 +14158,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
13450
14158
|
}
|
|
13451
14159
|
function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
13452
14160
|
const normalized = normalizePrdRunRef2(prdRef);
|
|
13453
|
-
const fetchResult =
|
|
14161
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", "dev", normalized], {
|
|
13454
14162
|
cwd: repoRoot2,
|
|
13455
14163
|
encoding: "utf8"
|
|
13456
14164
|
});
|
|
@@ -13470,7 +14178,7 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
|
13470
14178
|
if (fetchResult.stderr?.toString?.()?.trim()) {
|
|
13471
14179
|
fetchDiagnostics.push(fetchResult.stderr.toString().trim());
|
|
13472
14180
|
}
|
|
13473
|
-
const mergeBaseResult =
|
|
14181
|
+
const mergeBaseResult = spawnSync3(
|
|
13474
14182
|
"git",
|
|
13475
14183
|
["merge-base", "origin/dev", `origin/${normalized}`],
|
|
13476
14184
|
{
|
|
@@ -13506,6 +14214,37 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
|
13506
14214
|
}
|
|
13507
14215
|
return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
|
|
13508
14216
|
}
|
|
14217
|
+
function computeLocalFinalReviewMergeBase(repoRoot2, prdBranch) {
|
|
14218
|
+
const mergeBaseResult = spawnSync3("git", ["merge-base", "dev", prdBranch], {
|
|
14219
|
+
cwd: repoRoot2,
|
|
14220
|
+
encoding: "utf8"
|
|
14221
|
+
});
|
|
14222
|
+
if (mergeBaseResult.status !== 0) {
|
|
14223
|
+
const diagnostics = [];
|
|
14224
|
+
if (mergeBaseResult.stderr?.toString?.()?.trim()) {
|
|
14225
|
+
diagnostics.push(mergeBaseResult.stderr.toString().trim());
|
|
14226
|
+
}
|
|
14227
|
+
if (mergeBaseResult.stdout?.toString?.()?.trim()) {
|
|
14228
|
+
diagnostics.push(mergeBaseResult.stdout.toString().trim());
|
|
14229
|
+
}
|
|
14230
|
+
return {
|
|
14231
|
+
ok: false,
|
|
14232
|
+
gate: "final-review",
|
|
14233
|
+
reason: `Final Review merge-base computation failed for dev and ${prdBranch}.`,
|
|
14234
|
+
diagnostics: diagnostics.length > 0 ? diagnostics : ["git merge-base returned non-zero exit status"]
|
|
14235
|
+
};
|
|
14236
|
+
}
|
|
14237
|
+
const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
|
|
14238
|
+
if (!mergeBase) {
|
|
14239
|
+
return {
|
|
14240
|
+
ok: false,
|
|
14241
|
+
gate: "final-review",
|
|
14242
|
+
reason: `Final Review merge-base returned empty result for dev and ${prdBranch}.`,
|
|
14243
|
+
diagnostics: [`merge-base stdout was empty for dev..${prdBranch}`]
|
|
14244
|
+
};
|
|
14245
|
+
}
|
|
14246
|
+
return { ok: true, mergeBase, diagnostics: [] };
|
|
14247
|
+
}
|
|
13509
14248
|
function validateReconciliationArtifact2(artifact, context) {
|
|
13510
14249
|
const errors = [];
|
|
13511
14250
|
if (!artifact.prdRef) {
|
|
@@ -13613,17 +14352,17 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13613
14352
|
const blockerBugs = [];
|
|
13614
14353
|
const blockersFixed = [];
|
|
13615
14354
|
const nonBlockers = [];
|
|
13616
|
-
const prdMirrorPath =
|
|
13617
|
-
const completionsDir =
|
|
14355
|
+
const prdMirrorPath = join21(repoRoot2, PRD_047_PRD_MIRROR_PATH);
|
|
14356
|
+
const completionsDir = join21(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
|
|
13618
14357
|
let escapeHatchPresent = false;
|
|
13619
14358
|
if (existsSync17(completionsDir)) {
|
|
13620
|
-
const files =
|
|
14359
|
+
const files = readdirSync5(completionsDir);
|
|
13621
14360
|
escapeHatchPresent = files.some(
|
|
13622
14361
|
(f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
|
|
13623
14362
|
);
|
|
13624
14363
|
}
|
|
13625
14364
|
if (!escapeHatchPresent) {
|
|
13626
|
-
escapeHatchPresent = existsSync17(
|
|
14365
|
+
escapeHatchPresent = existsSync17(join21(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
|
|
13627
14366
|
}
|
|
13628
14367
|
const prdMirrorExists = existsSync17(prdMirrorPath);
|
|
13629
14368
|
if (!prdMirrorExists) {
|
|
@@ -13632,7 +14371,7 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13632
14371
|
);
|
|
13633
14372
|
}
|
|
13634
14373
|
if (prdMirrorExists) {
|
|
13635
|
-
const mirrorContent =
|
|
14374
|
+
const mirrorContent = readFileSync17(prdMirrorPath, "utf-8");
|
|
13636
14375
|
const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
|
|
13637
14376
|
const hasEscapeHatch = mirrorContent.includes("escape hatch");
|
|
13638
14377
|
if (!hasReconcileCommand) {
|
|
@@ -13646,16 +14385,16 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13646
14385
|
);
|
|
13647
14386
|
}
|
|
13648
14387
|
}
|
|
13649
|
-
const childIssueDir =
|
|
14388
|
+
const childIssueDir = join21(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
|
|
13650
14389
|
const childIssuesExist = existsSync17(childIssueDir);
|
|
13651
14390
|
if (!childIssuesExist) {
|
|
13652
14391
|
nonBlockers.push(
|
|
13653
14392
|
`PRD-0047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
|
|
13654
14393
|
);
|
|
13655
14394
|
}
|
|
13656
|
-
const manifestPath =
|
|
14395
|
+
const manifestPath = join21(repoRoot2, PRD_047_MANIFEST_PATH);
|
|
13657
14396
|
if (existsSync17(manifestPath)) {
|
|
13658
|
-
const manifestContent =
|
|
14397
|
+
const manifestContent = readFileSync17(manifestPath, "utf-8");
|
|
13659
14398
|
if (!manifestContent.includes("ready_for_prepare")) {
|
|
13660
14399
|
nonBlockers.push(
|
|
13661
14400
|
"Planning Artifact Manifest is not marked as ready_for_prepare."
|
|
@@ -13744,6 +14483,17 @@ function runPrdRunAuditCommand(options) {
|
|
|
13744
14483
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
13745
14484
|
return auditPrd047Implementation(options.repoRoot, prdRef);
|
|
13746
14485
|
}
|
|
14486
|
+
async function closeParentGitHubIssue(prdRef, repoRoot2, issueProvider) {
|
|
14487
|
+
const prdResult = await resolveLocalPrdArtifact(prdRef, repoRoot2);
|
|
14488
|
+
const parentIssueNumber = prdResult.ok ? prdResult.data.githubProjection.issueNumber : null;
|
|
14489
|
+
if (!parentIssueNumber) return { closed: false, issueNumber: null };
|
|
14490
|
+
try {
|
|
14491
|
+
await issueProvider.closeIssue(parentIssueNumber);
|
|
14492
|
+
return { closed: true, issueNumber: parentIssueNumber };
|
|
14493
|
+
} catch {
|
|
14494
|
+
return { closed: false, issueNumber: parentIssueNumber };
|
|
14495
|
+
}
|
|
14496
|
+
}
|
|
13747
14497
|
async function runPrdRunLaunchCommand(options) {
|
|
13748
14498
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
13749
14499
|
const attempted = [];
|
|
@@ -13755,10 +14505,11 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13755
14505
|
const terminal = /* @__PURE__ */ new Set([
|
|
13756
14506
|
"waiting_for_integration",
|
|
13757
14507
|
"finalizing",
|
|
13758
|
-
"complete"
|
|
14508
|
+
"complete",
|
|
14509
|
+
"completed_local_branch"
|
|
13759
14510
|
]);
|
|
13760
14511
|
if (terminal.has(existingRecord.record.status)) {
|
|
13761
|
-
|
|
14512
|
+
const result = {
|
|
13762
14513
|
prdRef,
|
|
13763
14514
|
status: existingRecord.record.status,
|
|
13764
14515
|
attempted: [],
|
|
@@ -13768,10 +14519,38 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13768
14519
|
`PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`
|
|
13769
14520
|
]
|
|
13770
14521
|
};
|
|
14522
|
+
if (existingRecord.record.status === "completed_local_branch" && existingRecord.record.prdBranch) {
|
|
14523
|
+
result.prdBranch = existingRecord.record.prdBranch;
|
|
14524
|
+
}
|
|
14525
|
+
return result;
|
|
13771
14526
|
}
|
|
13772
14527
|
}
|
|
13773
14528
|
if (existingRecord.record) {
|
|
13774
14529
|
const status = existingRecord.record.status;
|
|
14530
|
+
const resolvedLaunchMode = options.config ? (() => {
|
|
14531
|
+
try {
|
|
14532
|
+
const target = resolveTarget(options.config, options.targetName);
|
|
14533
|
+
return resolvePrdRunMode(target).mode;
|
|
14534
|
+
} catch {
|
|
14535
|
+
return void 0;
|
|
14536
|
+
}
|
|
14537
|
+
})() : void 0;
|
|
14538
|
+
if (resolvedLaunchMode && existingRecord.record.mode && existingRecord.record.mode !== resolvedLaunchMode) {
|
|
14539
|
+
return {
|
|
14540
|
+
prdRef,
|
|
14541
|
+
status: "blocked",
|
|
14542
|
+
attempted: [],
|
|
14543
|
+
skipped: ["prepare", "start", "queue", "final-review", "reconcile"],
|
|
14544
|
+
resumed: [],
|
|
14545
|
+
diagnostics: [
|
|
14546
|
+
`Recorded mode: ${existingRecord.record.mode}`,
|
|
14547
|
+
`Resolved mode: ${resolvedLaunchMode}`
|
|
14548
|
+
],
|
|
14549
|
+
blockedGate: "branch-state",
|
|
14550
|
+
blockedReason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedLaunchMode}". Resolve by using the correct target or --local-override.`,
|
|
14551
|
+
offendingPaths: []
|
|
14552
|
+
};
|
|
14553
|
+
}
|
|
13775
14554
|
if (status === "drained") {
|
|
13776
14555
|
skipped.push("prepare", "start", "queue");
|
|
13777
14556
|
resumed.push("final-review");
|
|
@@ -13899,182 +14678,216 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13899
14678
|
}
|
|
13900
14679
|
diagnostics.push(...startResult.diagnostics);
|
|
13901
14680
|
attempted.push("queue");
|
|
14681
|
+
if (startResult.status === "drained" && options.issueProvider && existsSync17(join21(options.repoRoot, ".pourkit", "local-prd-runs", prdRef))) {
|
|
14682
|
+
await closeParentGitHubIssue(
|
|
14683
|
+
prdRef,
|
|
14684
|
+
options.repoRoot,
|
|
14685
|
+
options.issueProvider
|
|
14686
|
+
);
|
|
14687
|
+
}
|
|
14688
|
+
}
|
|
14689
|
+
let finalReviewResult;
|
|
14690
|
+
if (!skipped.includes("final-review")) {
|
|
14691
|
+
attempted.push("final-review");
|
|
14692
|
+
if (existingRecord.record?.mode === "local") {
|
|
14693
|
+
const runnableIssues = await getRunnableLocalIssues(
|
|
14694
|
+
prdRef,
|
|
14695
|
+
options.repoRoot
|
|
14696
|
+
);
|
|
14697
|
+
if (runnableIssues.length > 0) {
|
|
14698
|
+
const reason = `Config-local Final Review blocked: Queue not drained (${runnableIssues.length} runnable Issues remain).`;
|
|
14699
|
+
return {
|
|
14700
|
+
prdRef,
|
|
14701
|
+
status: "blocked",
|
|
14702
|
+
attempted,
|
|
14703
|
+
skipped: ["start", "queue", "reconcile"],
|
|
14704
|
+
resumed,
|
|
14705
|
+
diagnostics: [reason],
|
|
14706
|
+
blockedGate: "final-review",
|
|
14707
|
+
blockedReason: reason,
|
|
14708
|
+
offendingPaths: [],
|
|
14709
|
+
prepare: prepareResult,
|
|
14710
|
+
start: startResult,
|
|
14711
|
+
finalReview: {
|
|
14712
|
+
prdRef,
|
|
14713
|
+
status: "blocked",
|
|
14714
|
+
blockedGate: "final-review",
|
|
14715
|
+
blockedReason: reason,
|
|
14716
|
+
diagnostics: [reason],
|
|
14717
|
+
offendingPaths: []
|
|
14718
|
+
}
|
|
14719
|
+
};
|
|
14720
|
+
}
|
|
14721
|
+
}
|
|
14722
|
+
finalReviewResult = await runPrdRunFinalReviewCommand({
|
|
14723
|
+
repoRoot: options.repoRoot,
|
|
14724
|
+
prdRef,
|
|
14725
|
+
targetName: options.targetName,
|
|
14726
|
+
autoMerge: options.autoMerge,
|
|
14727
|
+
issueProvider: options.issueProvider,
|
|
14728
|
+
prProvider: options.prProvider,
|
|
14729
|
+
executionProvider: options.executionProvider,
|
|
14730
|
+
config: options.config,
|
|
14731
|
+
logger: options.logger
|
|
14732
|
+
});
|
|
14733
|
+
const skippedAfterFr = [
|
|
14734
|
+
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
14735
|
+
...skipped.includes("start") ? ["queue"] : [],
|
|
14736
|
+
"reconcile"
|
|
14737
|
+
];
|
|
14738
|
+
if (finalReviewResult.status === "blocked") {
|
|
14739
|
+
return {
|
|
14740
|
+
prdRef,
|
|
14741
|
+
status: "blocked",
|
|
14742
|
+
attempted,
|
|
14743
|
+
skipped: skippedAfterFr,
|
|
14744
|
+
resumed,
|
|
14745
|
+
diagnostics: finalReviewResult.diagnostics,
|
|
14746
|
+
blockedGate: "final-review",
|
|
14747
|
+
blockedReason: finalReviewResult.blockedReason,
|
|
14748
|
+
offendingPaths: finalReviewResult.offendingPaths,
|
|
14749
|
+
prepare: prepareResult,
|
|
14750
|
+
start: startResult,
|
|
14751
|
+
finalReview: finalReviewResult
|
|
14752
|
+
};
|
|
14753
|
+
}
|
|
14754
|
+
if (finalReviewResult.status === "needs_human_review") {
|
|
14755
|
+
return {
|
|
14756
|
+
prdRef,
|
|
14757
|
+
status: "blocked",
|
|
14758
|
+
attempted,
|
|
14759
|
+
skipped: skippedAfterFr,
|
|
14760
|
+
resumed,
|
|
14761
|
+
diagnostics: finalReviewResult.diagnostics,
|
|
14762
|
+
blockedGate: "final-review",
|
|
14763
|
+
blockedReason: finalReviewResult.finalReview.verdict ?? "needs_human_review",
|
|
14764
|
+
offendingPaths: [],
|
|
14765
|
+
prepare: prepareResult,
|
|
14766
|
+
start: startResult,
|
|
14767
|
+
finalReview: finalReviewResult
|
|
14768
|
+
};
|
|
14769
|
+
}
|
|
14770
|
+
diagnostics.push(...finalReviewResult.diagnostics);
|
|
13902
14771
|
}
|
|
13903
|
-
let
|
|
13904
|
-
if (!skipped.includes("
|
|
13905
|
-
attempted.push("
|
|
13906
|
-
const
|
|
14772
|
+
let reconcileResult;
|
|
14773
|
+
if (!skipped.includes("reconcile")) {
|
|
14774
|
+
attempted.push("reconcile");
|
|
14775
|
+
const localStorePath2 = join21(
|
|
13907
14776
|
options.repoRoot,
|
|
13908
14777
|
".pourkit",
|
|
13909
14778
|
"local-prd-runs",
|
|
13910
14779
|
prdRef
|
|
13911
14780
|
);
|
|
13912
|
-
if (existsSync17(
|
|
13913
|
-
const
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
repoRoot: options.repoRoot,
|
|
13919
|
-
verificationCommands
|
|
13920
|
-
});
|
|
13921
|
-
if (!localResult.ok) {
|
|
13922
|
-
const reason = localResult.message ?? "Local Final Review blocked.";
|
|
13923
|
-
writePrdRunRecord(options.repoRoot, {
|
|
13924
|
-
prdRef,
|
|
13925
|
-
status: "blocked",
|
|
13926
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13927
|
-
blockedGate: "final-review",
|
|
13928
|
-
blockedReason: reason,
|
|
13929
|
-
diagnostics: [reason],
|
|
13930
|
-
targetName: options.targetName,
|
|
13931
|
-
offendingPaths: []
|
|
13932
|
-
});
|
|
14781
|
+
if (existsSync17(localStorePath2)) {
|
|
14782
|
+
const prdRecord = readPrdRun(options.repoRoot, prdRef);
|
|
14783
|
+
const finalReviewReceipt = prdRecord.record?.finalReview;
|
|
14784
|
+
const hasCompletedFinalReview = finalReviewReceipt?.status === "final_reviewed" || finalReviewReceipt?.status === "succeeded";
|
|
14785
|
+
if (!hasCompletedFinalReview) {
|
|
14786
|
+
const reason = `Reconciliation blocked: Final Review not completed. Run Final Review before reconciliation.`;
|
|
13933
14787
|
return {
|
|
13934
14788
|
prdRef,
|
|
13935
14789
|
status: "blocked",
|
|
13936
14790
|
attempted,
|
|
13937
|
-
skipped: [
|
|
13938
|
-
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
13939
|
-
...skipped.includes("start") ? ["queue"] : [],
|
|
13940
|
-
"reconcile"
|
|
13941
|
-
],
|
|
14791
|
+
skipped: [...skipped],
|
|
13942
14792
|
resumed,
|
|
13943
14793
|
diagnostics: [reason],
|
|
13944
|
-
blockedGate: "
|
|
14794
|
+
blockedGate: "reconciliation",
|
|
13945
14795
|
blockedReason: reason,
|
|
13946
14796
|
offendingPaths: [],
|
|
13947
14797
|
prepare: prepareResult,
|
|
13948
|
-
start: startResult
|
|
14798
|
+
start: startResult,
|
|
14799
|
+
finalReview: finalReviewResult
|
|
13949
14800
|
};
|
|
13950
14801
|
}
|
|
13951
|
-
if (
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
prdRef,
|
|
13958
|
-
status: "blocked",
|
|
13959
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13960
|
-
blockedGate: "final-review",
|
|
13961
|
-
blockedReason: reason,
|
|
13962
|
-
diagnostics: [reason],
|
|
13963
|
-
targetName: options.targetName,
|
|
13964
|
-
offendingPaths: []
|
|
13965
|
-
});
|
|
14802
|
+
if (prdRecord.record?.mode === "local") {
|
|
14803
|
+
const dualCheck = await verifyLocalDualFinalReviewReceipts(
|
|
14804
|
+
options.repoRoot,
|
|
14805
|
+
prdRef
|
|
14806
|
+
);
|
|
14807
|
+
if (!dualCheck.ok) {
|
|
13966
14808
|
return {
|
|
13967
14809
|
prdRef,
|
|
13968
14810
|
status: "blocked",
|
|
13969
14811
|
attempted,
|
|
13970
|
-
skipped: [
|
|
13971
|
-
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
13972
|
-
...skipped.includes("start") ? ["queue"] : [],
|
|
13973
|
-
"reconcile"
|
|
13974
|
-
],
|
|
14812
|
+
skipped: [...skipped],
|
|
13975
14813
|
resumed,
|
|
13976
|
-
diagnostics:
|
|
13977
|
-
blockedGate: "
|
|
13978
|
-
blockedReason: reason,
|
|
14814
|
+
diagnostics: dualCheck.diagnostics,
|
|
14815
|
+
blockedGate: "reconciliation",
|
|
14816
|
+
blockedReason: dualCheck.reason,
|
|
13979
14817
|
offendingPaths: [],
|
|
13980
14818
|
prepare: prepareResult,
|
|
13981
|
-
start: startResult
|
|
14819
|
+
start: startResult,
|
|
14820
|
+
finalReview: finalReviewResult
|
|
13982
14821
|
};
|
|
13983
14822
|
}
|
|
13984
14823
|
}
|
|
13985
|
-
const
|
|
14824
|
+
const localResult = await runLocalReconciliation(prdRef, {
|
|
14825
|
+
repoRoot: options.repoRoot
|
|
14826
|
+
});
|
|
14827
|
+
if (!localResult.ok) {
|
|
14828
|
+
return {
|
|
14829
|
+
prdRef,
|
|
14830
|
+
status: "blocked",
|
|
14831
|
+
attempted,
|
|
14832
|
+
skipped: [...skipped],
|
|
14833
|
+
resumed,
|
|
14834
|
+
diagnostics: localResult.message ? [localResult.message] : [],
|
|
14835
|
+
blockedGate: "reconciliation",
|
|
14836
|
+
blockedReason: localResult.message ?? "Local reconciliation blocked.",
|
|
14837
|
+
offendingPaths: [],
|
|
14838
|
+
prepare: prepareResult,
|
|
14839
|
+
start: startResult,
|
|
14840
|
+
finalReview: finalReviewResult
|
|
14841
|
+
};
|
|
14842
|
+
}
|
|
13986
14843
|
const receipt = {
|
|
13987
|
-
status: "
|
|
14844
|
+
status: "succeeded",
|
|
14845
|
+
result: localResult.receipt?.result,
|
|
14846
|
+
changedPlanningPaths: localResult.receipt?.changedPlanningPaths,
|
|
13988
14847
|
targetName: options.targetName,
|
|
13989
|
-
prdBranch: `local/${prdRef}`,
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
14848
|
+
prdBranch: localResult.receipt?.prdBranch ?? `local/${prdRef}`,
|
|
14849
|
+
mergeCommit: localResult.receipt?.mergeCommit,
|
|
14850
|
+
reconciledAt: localResult.receipt?.reconciledTimestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
14851
|
+
autoMerge: false,
|
|
14852
|
+
agentIdentity: "pourkit-architect",
|
|
14853
|
+
noChange: localResult.receipt?.result === "no_changes_needed",
|
|
14854
|
+
source: "dev",
|
|
14855
|
+
target: localResult.receipt?.prdBranch ?? `local/${prdRef}`
|
|
13993
14856
|
};
|
|
13994
14857
|
const existingRecord2 = readPrdRun(options.repoRoot, prdRef);
|
|
13995
14858
|
writePrdRunRecord(options.repoRoot, {
|
|
13996
14859
|
prdRef,
|
|
13997
|
-
status: "
|
|
14860
|
+
status: "completed_local_branch",
|
|
13998
14861
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13999
14862
|
targetName: options.targetName,
|
|
14863
|
+
prdBranch: `local/${prdRef}`,
|
|
14000
14864
|
manifestPath: existingRecord2.record?.manifestPath,
|
|
14001
14865
|
planning: existingRecord2.record?.planning,
|
|
14002
14866
|
start: existingRecord2.record?.start,
|
|
14003
|
-
finalReview:
|
|
14867
|
+
finalReview: existingRecord2.record?.finalReview,
|
|
14868
|
+
reconciliation: receipt,
|
|
14869
|
+
scopeChanges: existingRecord2.record?.scopeChanges
|
|
14004
14870
|
});
|
|
14005
|
-
|
|
14871
|
+
reconcileResult = {
|
|
14006
14872
|
prdRef,
|
|
14007
|
-
status: "
|
|
14008
|
-
|
|
14009
|
-
diagnostics:
|
|
14873
|
+
status: "succeeded",
|
|
14874
|
+
reconciliation: receipt,
|
|
14875
|
+
diagnostics: []
|
|
14010
14876
|
};
|
|
14011
|
-
diagnostics.push(
|
|
14877
|
+
diagnostics.push("Local reconciliation completed.");
|
|
14012
14878
|
} else {
|
|
14013
|
-
|
|
14879
|
+
reconcileResult = await runPrdRunReconcileCommand({
|
|
14014
14880
|
repoRoot: options.repoRoot,
|
|
14015
14881
|
prdRef,
|
|
14016
14882
|
targetName: options.targetName,
|
|
14017
|
-
autoMerge: options.autoMerge,
|
|
14018
|
-
issueProvider: options.issueProvider,
|
|
14019
|
-
prProvider: options.prProvider,
|
|
14020
|
-
executionProvider: options.executionProvider,
|
|
14021
14883
|
config: options.config,
|
|
14022
|
-
logger: options.logger
|
|
14884
|
+
logger: options.logger,
|
|
14885
|
+
executionProvider: options.executionProvider,
|
|
14886
|
+
prProvider: options.prProvider,
|
|
14887
|
+
autoMerge: options.autoMerge
|
|
14023
14888
|
});
|
|
14024
14889
|
}
|
|
14025
|
-
|
|
14026
|
-
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
14027
|
-
...skipped.includes("start") ? ["queue"] : [],
|
|
14028
|
-
"reconcile"
|
|
14029
|
-
];
|
|
14030
|
-
if (finalReviewResult.status === "blocked") {
|
|
14031
|
-
return {
|
|
14032
|
-
prdRef,
|
|
14033
|
-
status: "blocked",
|
|
14034
|
-
attempted,
|
|
14035
|
-
skipped: skippedAfterFr,
|
|
14036
|
-
resumed,
|
|
14037
|
-
diagnostics: finalReviewResult.diagnostics,
|
|
14038
|
-
blockedGate: "final-review",
|
|
14039
|
-
blockedReason: finalReviewResult.blockedReason,
|
|
14040
|
-
offendingPaths: finalReviewResult.offendingPaths,
|
|
14041
|
-
prepare: prepareResult,
|
|
14042
|
-
start: startResult,
|
|
14043
|
-
finalReview: finalReviewResult
|
|
14044
|
-
};
|
|
14045
|
-
}
|
|
14046
|
-
if (finalReviewResult.status === "needs_human_review") {
|
|
14047
|
-
return {
|
|
14048
|
-
prdRef,
|
|
14049
|
-
status: "blocked",
|
|
14050
|
-
attempted,
|
|
14051
|
-
skipped: skippedAfterFr,
|
|
14052
|
-
resumed,
|
|
14053
|
-
diagnostics: finalReviewResult.diagnostics,
|
|
14054
|
-
blockedGate: "final-review",
|
|
14055
|
-
blockedReason: finalReviewResult.finalReview.verdict ?? "needs_human_review",
|
|
14056
|
-
offendingPaths: [],
|
|
14057
|
-
prepare: prepareResult,
|
|
14058
|
-
start: startResult,
|
|
14059
|
-
finalReview: finalReviewResult
|
|
14060
|
-
};
|
|
14061
|
-
}
|
|
14062
|
-
diagnostics.push(...finalReviewResult.diagnostics);
|
|
14063
|
-
}
|
|
14064
|
-
let reconcileResult;
|
|
14065
|
-
if (!skipped.includes("reconcile")) {
|
|
14066
|
-
attempted.push("reconcile");
|
|
14067
|
-
reconcileResult = await runPrdRunReconcileCommand({
|
|
14068
|
-
repoRoot: options.repoRoot,
|
|
14069
|
-
prdRef,
|
|
14070
|
-
targetName: options.targetName,
|
|
14071
|
-
config: options.config,
|
|
14072
|
-
logger: options.logger,
|
|
14073
|
-
executionProvider: options.executionProvider,
|
|
14074
|
-
prProvider: options.prProvider,
|
|
14075
|
-
autoMerge: options.autoMerge
|
|
14076
|
-
});
|
|
14077
|
-
if (reconcileResult.status === "blocked") {
|
|
14890
|
+
if (reconcileResult && reconcileResult.status === "blocked") {
|
|
14078
14891
|
return {
|
|
14079
14892
|
prdRef,
|
|
14080
14893
|
status: "blocked",
|
|
@@ -14091,7 +14904,9 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
14091
14904
|
reconcile: reconcileResult
|
|
14092
14905
|
};
|
|
14093
14906
|
}
|
|
14094
|
-
|
|
14907
|
+
if (reconcileResult) {
|
|
14908
|
+
diagnostics.push(...reconcileResult.diagnostics);
|
|
14909
|
+
}
|
|
14095
14910
|
}
|
|
14096
14911
|
const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
14097
14912
|
const reconciliationReceipt = reconcileResult && reconcileResult.status === "succeeded" ? reconcileResult.reconciliation : currentRecord?.reconciliation;
|
|
@@ -14174,6 +14989,29 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
14174
14989
|
reconcile: reconcileResult
|
|
14175
14990
|
};
|
|
14176
14991
|
}
|
|
14992
|
+
const localStorePath = join21(
|
|
14993
|
+
options.repoRoot,
|
|
14994
|
+
".pourkit",
|
|
14995
|
+
"local-prd-runs",
|
|
14996
|
+
prdRef
|
|
14997
|
+
);
|
|
14998
|
+
if (existsSync17(localStorePath)) {
|
|
14999
|
+
return {
|
|
15000
|
+
prdRef,
|
|
15001
|
+
status: "completed_local_branch",
|
|
15002
|
+
prdBranch: `local/${prdRef}`,
|
|
15003
|
+
attempted,
|
|
15004
|
+
skipped,
|
|
15005
|
+
resumed,
|
|
15006
|
+
diagnostics: [
|
|
15007
|
+
`Local PRD Run ${prdRef} completed. Branch: local/${prdRef}.`
|
|
15008
|
+
],
|
|
15009
|
+
prepare: prepareResult,
|
|
15010
|
+
start: startResult,
|
|
15011
|
+
finalReview: finalReviewResult,
|
|
15012
|
+
reconcile: reconcileResult
|
|
15013
|
+
};
|
|
15014
|
+
}
|
|
14177
15015
|
writePrdRunRecord(options.repoRoot, {
|
|
14178
15016
|
prdRef,
|
|
14179
15017
|
status: "waiting_for_integration",
|
|
@@ -14503,7 +15341,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
|
|
|
14503
15341
|
const branch = `local/${normalizedRef}`;
|
|
14504
15342
|
const branchExists = (() => {
|
|
14505
15343
|
try {
|
|
14506
|
-
return
|
|
15344
|
+
return spawnSync3("git", ["rev-parse", "--verify", "--quiet", branch], {
|
|
14507
15345
|
cwd: root,
|
|
14508
15346
|
encoding: "utf8",
|
|
14509
15347
|
stdio: "pipe"
|
|
@@ -14523,7 +15361,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
|
|
|
14523
15361
|
message: `Branch ${branch} already exists without a matching start receipt. Remove it manually or use a different PRD ID.`
|
|
14524
15362
|
};
|
|
14525
15363
|
}
|
|
14526
|
-
const branchResult =
|
|
15364
|
+
const branchResult = spawnSync3("git", ["branch", branch], {
|
|
14527
15365
|
cwd: root,
|
|
14528
15366
|
encoding: "utf8",
|
|
14529
15367
|
stdio: "pipe"
|
|
@@ -14538,7 +15376,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
|
|
|
14538
15376
|
}
|
|
14539
15377
|
const baseCommit = (() => {
|
|
14540
15378
|
try {
|
|
14541
|
-
return
|
|
15379
|
+
return spawnSync3("git", ["rev-parse", "HEAD"], {
|
|
14542
15380
|
cwd: root,
|
|
14543
15381
|
encoding: "utf8",
|
|
14544
15382
|
stdio: "pipe"
|
|
@@ -14660,6 +15498,17 @@ async function runLocalLaunchCommand(prdId, repoRoot2) {
|
|
|
14660
15498
|
};
|
|
14661
15499
|
return { ok: false, failedStage: "finalReview", stages };
|
|
14662
15500
|
}
|
|
15501
|
+
if (result.verdict === "blocked" || result.verdict === "needs_human_review") {
|
|
15502
|
+
stages.finalReview = {
|
|
15503
|
+
ok: false,
|
|
15504
|
+
stage: "finalReview",
|
|
15505
|
+
failureCode: result.verdict === "blocked" ? "final_review_blocked" : "needs_human_review",
|
|
15506
|
+
repairabilityClass: "manual",
|
|
15507
|
+
reason: result.message ?? `Final Review verdict: ${result.verdict}`,
|
|
15508
|
+
repairGuidance: `Fix final review failures and rerun \`pourkit prd-run launch --local ${normalizedRef}\`.`
|
|
15509
|
+
};
|
|
15510
|
+
return { ok: false, failedStage: "finalReview", stages };
|
|
15511
|
+
}
|
|
14663
15512
|
if (result.verdict === "pass_with_retouch") {
|
|
14664
15513
|
try {
|
|
14665
15514
|
await squashFinalReviewRetouch(normalizedRef, root);
|
|
@@ -14779,6 +15628,32 @@ async function runPrdRunStartCommand(options) {
|
|
|
14779
15628
|
);
|
|
14780
15629
|
}
|
|
14781
15630
|
const existingRecord = readPrdRun(options.repoRoot, prdRef);
|
|
15631
|
+
const resolvedMode = options.config ? (() => {
|
|
15632
|
+
try {
|
|
15633
|
+
const target = resolveTarget(options.config, targetName);
|
|
15634
|
+
return resolvePrdRunMode(target).mode;
|
|
15635
|
+
} catch {
|
|
15636
|
+
return void 0;
|
|
15637
|
+
}
|
|
15638
|
+
})() : void 0;
|
|
15639
|
+
if (resolvedMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedMode) {
|
|
15640
|
+
const failure = {
|
|
15641
|
+
ok: false,
|
|
15642
|
+
gate: "branch-state",
|
|
15643
|
+
reason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedMode}". Resolve by using the correct target or --local-override.`,
|
|
15644
|
+
diagnostics: [
|
|
15645
|
+
`Recorded mode: ${existingRecord.record.mode}`,
|
|
15646
|
+
`Resolved mode: ${resolvedMode}`
|
|
15647
|
+
],
|
|
15648
|
+
offendingPaths: []
|
|
15649
|
+
};
|
|
15650
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
15651
|
+
manifestPath: existingRecord.record?.manifestPath,
|
|
15652
|
+
planning: existingRecord.record?.planning,
|
|
15653
|
+
targetName
|
|
15654
|
+
});
|
|
15655
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
15656
|
+
}
|
|
14782
15657
|
if (existingRecord.record?.start) {
|
|
14783
15658
|
const fetchResult2 = fetchOriginDev(options.repoRoot);
|
|
14784
15659
|
if (!fetchResult2.ok) {
|
|
@@ -14814,7 +15689,8 @@ async function runPrdRunStartCommand(options) {
|
|
|
14814
15689
|
...reusedStart
|
|
14815
15690
|
},
|
|
14816
15691
|
{
|
|
14817
|
-
targetName
|
|
15692
|
+
targetName,
|
|
15693
|
+
mode: resolvedMode
|
|
14818
15694
|
}
|
|
14819
15695
|
);
|
|
14820
15696
|
return await processStartResult(
|
|
@@ -14917,6 +15793,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
14917
15793
|
},
|
|
14918
15794
|
{
|
|
14919
15795
|
targetName,
|
|
15796
|
+
mode: resolvedMode,
|
|
14920
15797
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14921
15798
|
planning: existingRecord.record?.planning
|
|
14922
15799
|
}
|
|
@@ -15076,6 +15953,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
15076
15953
|
},
|
|
15077
15954
|
{
|
|
15078
15955
|
targetName,
|
|
15956
|
+
mode: resolvedMode,
|
|
15079
15957
|
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
|
|
15080
15958
|
planning: existingRecord.record?.planning
|
|
15081
15959
|
}
|
|
@@ -15158,6 +16036,68 @@ async function runPrdRunStartCommand(options) {
|
|
|
15158
16036
|
});
|
|
15159
16037
|
return buildBlockedStartResult(prdRef, failure);
|
|
15160
16038
|
}
|
|
16039
|
+
if (resolvedMode === "local") {
|
|
16040
|
+
const localBranchResult = materializeLocalPrdBranch(
|
|
16041
|
+
prdRef,
|
|
16042
|
+
fetchResult.startBaseCommit,
|
|
16043
|
+
options.repoRoot
|
|
16044
|
+
);
|
|
16045
|
+
if (!localBranchResult.ok) {
|
|
16046
|
+
const failure = {
|
|
16047
|
+
ok: false,
|
|
16048
|
+
gate: "branch-state",
|
|
16049
|
+
reason: localBranchResult.message,
|
|
16050
|
+
diagnostics: [localBranchResult.message],
|
|
16051
|
+
offendingPaths: []
|
|
16052
|
+
};
|
|
16053
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
16054
|
+
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
|
|
16055
|
+
planning: existingRecord.record?.planning,
|
|
16056
|
+
targetName
|
|
16057
|
+
});
|
|
16058
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
16059
|
+
}
|
|
16060
|
+
const localStoreDir = join21(
|
|
16061
|
+
options.repoRoot,
|
|
16062
|
+
".pourkit",
|
|
16063
|
+
"local-prd-runs",
|
|
16064
|
+
prdRef
|
|
16065
|
+
);
|
|
16066
|
+
let localStoreReady = false;
|
|
16067
|
+
if (existsSync17(localStoreDir)) {
|
|
16068
|
+
const localStorePath = join21(localStoreDir, "prd.json");
|
|
16069
|
+
try {
|
|
16070
|
+
const content = JSON.parse(readFileSync17(localStorePath, "utf8"));
|
|
16071
|
+
localStoreReady = content?.id === prdRef && content?.kind === "prd";
|
|
16072
|
+
} catch {
|
|
16073
|
+
localStoreReady = false;
|
|
16074
|
+
}
|
|
16075
|
+
}
|
|
16076
|
+
const prereqs = {
|
|
16077
|
+
localBranchName: getLocalPrdBranchName(prdRef),
|
|
16078
|
+
localBranchExists: !localBranchResult.created,
|
|
16079
|
+
localBranchCommit: fetchResult.startBaseCommit,
|
|
16080
|
+
localStoreReady
|
|
16081
|
+
};
|
|
16082
|
+
if (existsSync17(localStoreDir) && !prereqs.localStoreReady) {
|
|
16083
|
+
const failure = {
|
|
16084
|
+
ok: false,
|
|
16085
|
+
gate: "branch-state",
|
|
16086
|
+
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.`,
|
|
16087
|
+
diagnostics: [
|
|
16088
|
+
`Expected store path: .pourkit/local-prd-runs/${prdRef}/prd.json`,
|
|
16089
|
+
`Expected PRD ID: ${prdRef}`
|
|
16090
|
+
],
|
|
16091
|
+
offendingPaths: []
|
|
16092
|
+
};
|
|
16093
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
16094
|
+
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
|
|
16095
|
+
planning: existingRecord.record?.planning,
|
|
16096
|
+
targetName
|
|
16097
|
+
});
|
|
16098
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
16099
|
+
}
|
|
16100
|
+
}
|
|
15161
16101
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15162
16102
|
const start = {
|
|
15163
16103
|
status: "started",
|
|
@@ -15185,6 +16125,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
15185
16125
|
updatedAt,
|
|
15186
16126
|
targetName,
|
|
15187
16127
|
start,
|
|
16128
|
+
mode: resolvedMode,
|
|
15188
16129
|
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath ?? void 0,
|
|
15189
16130
|
planning: existingRecord.record?.planning
|
|
15190
16131
|
});
|
|
@@ -15215,26 +16156,93 @@ async function processStartResult(startResult, options) {
|
|
|
15215
16156
|
} catch {
|
|
15216
16157
|
resolvedMode = void 0;
|
|
15217
16158
|
}
|
|
16159
|
+
const modeForRecord = resolvedMode?.mode;
|
|
15218
16160
|
writePrdRunRecord(repoRoot2, {
|
|
15219
16161
|
prdRef,
|
|
15220
16162
|
status: "running",
|
|
15221
16163
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15222
16164
|
targetName: start.targetName,
|
|
15223
16165
|
start,
|
|
16166
|
+
mode: modeForRecord,
|
|
15224
16167
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15225
16168
|
planning: existingRecord.record?.planning
|
|
15226
16169
|
});
|
|
15227
|
-
const localStorePath =
|
|
16170
|
+
const localStorePath = join21(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
|
|
15228
16171
|
if (existsSync17(localStorePath)) {
|
|
15229
|
-
const
|
|
16172
|
+
const queueResult = await runLocalQueueLoop(
|
|
16173
|
+
prdRef,
|
|
16174
|
+
repoRoot2,
|
|
16175
|
+
options.issueProvider
|
|
16176
|
+
);
|
|
16177
|
+
if (!queueResult.ok) {
|
|
16178
|
+
const failureCode = queueResult.failureCode ?? "queue_error";
|
|
16179
|
+
const reason = queueResult.repairGuidance ?? `Local queue loop blocked: ${failureCode}`;
|
|
16180
|
+
const diagnostics = [
|
|
16181
|
+
`Local queue loop failed with failureCode "${failureCode}".`,
|
|
16182
|
+
...queueResult.repairGuidance ? [`Repair guidance: ${queueResult.repairGuidance}`] : []
|
|
16183
|
+
];
|
|
16184
|
+
writePrdRunRecord(repoRoot2, {
|
|
16185
|
+
prdRef,
|
|
16186
|
+
status: "blocked",
|
|
16187
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16188
|
+
targetName: start.targetName,
|
|
16189
|
+
start,
|
|
16190
|
+
mode: modeForRecord,
|
|
16191
|
+
manifestPath: existingRecord.record?.manifestPath,
|
|
16192
|
+
planning: existingRecord.record?.planning,
|
|
16193
|
+
blockedGate: "queue",
|
|
16194
|
+
blockedReason: reason,
|
|
16195
|
+
diagnostics,
|
|
16196
|
+
offendingPaths: []
|
|
16197
|
+
});
|
|
16198
|
+
return {
|
|
16199
|
+
prdRef,
|
|
16200
|
+
status: "blocked",
|
|
16201
|
+
blockedGate: "queue",
|
|
16202
|
+
blockedReason: reason,
|
|
16203
|
+
diagnostics,
|
|
16204
|
+
offendingPaths: []
|
|
16205
|
+
};
|
|
16206
|
+
}
|
|
16207
|
+
if (queueResult.blockedIssues.length > 0) {
|
|
16208
|
+
const reason = `Queue processed complete but ${queueResult.blockedIssues.length} blocked child issue(s) remain.`;
|
|
16209
|
+
const diagnostics = [
|
|
16210
|
+
reason,
|
|
16211
|
+
`Blocked issues: ${queueResult.blockedIssues.join(", ")}`,
|
|
16212
|
+
"Resolve blocked children before parent close."
|
|
16213
|
+
];
|
|
16214
|
+
writePrdRunRecord(repoRoot2, {
|
|
16215
|
+
prdRef,
|
|
16216
|
+
status: "blocked",
|
|
16217
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16218
|
+
targetName: start.targetName,
|
|
16219
|
+
start,
|
|
16220
|
+
mode: modeForRecord,
|
|
16221
|
+
manifestPath: existingRecord.record?.manifestPath,
|
|
16222
|
+
planning: existingRecord.record?.planning,
|
|
16223
|
+
blockedGate: "queue",
|
|
16224
|
+
blockedReason: reason,
|
|
16225
|
+
diagnostics,
|
|
16226
|
+
offendingPaths: []
|
|
16227
|
+
});
|
|
16228
|
+
return {
|
|
16229
|
+
prdRef,
|
|
16230
|
+
status: "blocked",
|
|
16231
|
+
blockedGate: "queue",
|
|
16232
|
+
blockedReason: reason,
|
|
16233
|
+
diagnostics,
|
|
16234
|
+
offendingPaths: []
|
|
16235
|
+
};
|
|
16236
|
+
}
|
|
15230
16237
|
start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15231
|
-
start.queueProcessedCount =
|
|
16238
|
+
start.queueProcessedCount = queueResult.completedIssues.length + queueResult.blockedIssues.length;
|
|
15232
16239
|
writePrdRunRecord(repoRoot2, {
|
|
15233
16240
|
prdRef,
|
|
15234
16241
|
status: "drained",
|
|
15235
16242
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15236
16243
|
targetName: start.targetName,
|
|
15237
16244
|
start,
|
|
16245
|
+
mode: modeForRecord,
|
|
15238
16246
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15239
16247
|
planning: existingRecord.record?.planning
|
|
15240
16248
|
});
|
|
@@ -15277,6 +16285,7 @@ async function processStartResult(startResult, options) {
|
|
|
15277
16285
|
diagnostics,
|
|
15278
16286
|
targetName: start.targetName,
|
|
15279
16287
|
start,
|
|
16288
|
+
mode: existingRecord.record?.mode,
|
|
15280
16289
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15281
16290
|
planning: existingRecord.record?.planning,
|
|
15282
16291
|
offendingPaths: []
|
|
@@ -15298,6 +16307,7 @@ async function processStartResult(startResult, options) {
|
|
|
15298
16307
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15299
16308
|
targetName: start.targetName,
|
|
15300
16309
|
start,
|
|
16310
|
+
mode: existingRecord.record?.mode,
|
|
15301
16311
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15302
16312
|
planning: existingRecord.record?.planning
|
|
15303
16313
|
});
|
|
@@ -15319,6 +16329,7 @@ async function processStartResult(startResult, options) {
|
|
|
15319
16329
|
diagnostics,
|
|
15320
16330
|
targetName: start.targetName,
|
|
15321
16331
|
start,
|
|
16332
|
+
mode: existingRecord.record?.mode,
|
|
15322
16333
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15323
16334
|
planning: existingRecord.record?.planning,
|
|
15324
16335
|
offendingPaths: []
|
|
@@ -15343,6 +16354,7 @@ async function processStartResult(startResult, options) {
|
|
|
15343
16354
|
diagnostics: outcomeDiagnostics,
|
|
15344
16355
|
targetName: start.targetName,
|
|
15345
16356
|
start,
|
|
16357
|
+
mode: existingRecord.record?.mode,
|
|
15346
16358
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15347
16359
|
planning: existingRecord.record?.planning,
|
|
15348
16360
|
offendingPaths: []
|
|
@@ -15367,6 +16379,7 @@ async function processStartResult(startResult, options) {
|
|
|
15367
16379
|
diagnostics,
|
|
15368
16380
|
targetName: start.targetName,
|
|
15369
16381
|
start,
|
|
16382
|
+
mode: existingRecord.record?.mode,
|
|
15370
16383
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15371
16384
|
planning: existingRecord.record?.planning,
|
|
15372
16385
|
offendingPaths: []
|
|
@@ -15398,12 +16411,13 @@ function persistStartingPrdRunRecord(repoRoot2, prdRef, existingRecord, start, c
|
|
|
15398
16411
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15399
16412
|
targetName: context.targetName,
|
|
15400
16413
|
start,
|
|
16414
|
+
mode: context.mode,
|
|
15401
16415
|
manifestPath: context.manifestPath ?? existingRecord.record?.manifestPath,
|
|
15402
16416
|
planning: context.planning ?? existingRecord.record?.planning
|
|
15403
16417
|
});
|
|
15404
16418
|
}
|
|
15405
16419
|
function inspectRemotePrdBranch(repoRoot2, prdRef) {
|
|
15406
|
-
const result =
|
|
16420
|
+
const result = spawnSync3("git", ["ls-remote", "--heads", "origin", prdRef], {
|
|
15407
16421
|
cwd: repoRoot2,
|
|
15408
16422
|
encoding: "utf8"
|
|
15409
16423
|
});
|
|
@@ -15428,7 +16442,7 @@ function inspectRemotePrdBranch(repoRoot2, prdRef) {
|
|
|
15428
16442
|
}
|
|
15429
16443
|
function ensureAdoptableExistingPrdBranch(repoRoot2, prdRef, prepareMergeCommit) {
|
|
15430
16444
|
const branchRef = `refs/remotes/origin/${prdRef}`;
|
|
15431
|
-
const result =
|
|
16445
|
+
const result = spawnSync3(
|
|
15432
16446
|
"git",
|
|
15433
16447
|
["merge-base", "--is-ancestor", prepareMergeCommit, branchRef],
|
|
15434
16448
|
{
|
|
@@ -15558,7 +16572,7 @@ async function createOrReusePlanningPr(options) {
|
|
|
15558
16572
|
});
|
|
15559
16573
|
}
|
|
15560
16574
|
function fetchOriginDev(repoRoot2) {
|
|
15561
|
-
const fetchResult =
|
|
16575
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
|
|
15562
16576
|
cwd: repoRoot2,
|
|
15563
16577
|
encoding: "utf8"
|
|
15564
16578
|
});
|
|
@@ -15575,7 +16589,7 @@ function fetchOriginDev(repoRoot2) {
|
|
|
15575
16589
|
offendingPaths: []
|
|
15576
16590
|
};
|
|
15577
16591
|
}
|
|
15578
|
-
const revParseResult =
|
|
16592
|
+
const revParseResult = spawnSync3("git", ["rev-parse", "origin/dev"], {
|
|
15579
16593
|
cwd: repoRoot2,
|
|
15580
16594
|
encoding: "utf8"
|
|
15581
16595
|
});
|
|
@@ -15595,7 +16609,7 @@ function fetchOriginDev(repoRoot2) {
|
|
|
15595
16609
|
return { ok: true, startBaseCommit: revParseResult.stdout.trim() };
|
|
15596
16610
|
}
|
|
15597
16611
|
function fetchPrdBranch(repoRoot2, prdRef) {
|
|
15598
|
-
const result =
|
|
16612
|
+
const result = spawnSync3("git", ["fetch", "origin", prdRef], {
|
|
15599
16613
|
cwd: repoRoot2,
|
|
15600
16614
|
encoding: "utf8"
|
|
15601
16615
|
});
|
|
@@ -15615,7 +16629,7 @@ function fetchPrdBranch(repoRoot2, prdRef) {
|
|
|
15615
16629
|
return { ok: true };
|
|
15616
16630
|
}
|
|
15617
16631
|
function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startBaseCommit) {
|
|
15618
|
-
const result =
|
|
16632
|
+
const result = spawnSync3(
|
|
15619
16633
|
"git",
|
|
15620
16634
|
["merge-base", "--is-ancestor", prepareMergeCommit, startBaseRef],
|
|
15621
16635
|
{
|
|
@@ -15642,7 +16656,7 @@ function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startB
|
|
|
15642
16656
|
return { ok: true };
|
|
15643
16657
|
}
|
|
15644
16658
|
async function ensurePrdBranchPublished(repoRoot2, prdRef, startBaseCommit) {
|
|
15645
|
-
const pushResult =
|
|
16659
|
+
const pushResult = spawnSync3(
|
|
15646
16660
|
"git",
|
|
15647
16661
|
["push", "origin", `${startBaseCommit}:refs/heads/${prdRef}`],
|
|
15648
16662
|
{
|
|
@@ -15695,7 +16709,7 @@ function buildPlanningPrBody(options) {
|
|
|
15695
16709
|
].join("\n");
|
|
15696
16710
|
}
|
|
15697
16711
|
function runGitOrThrow(cwd, args, label) {
|
|
15698
|
-
const result =
|
|
16712
|
+
const result = spawnSync3("git", args, { cwd, encoding: "utf8" });
|
|
15699
16713
|
if (result.status !== 0) {
|
|
15700
16714
|
throw new Error(
|
|
15701
16715
|
`Failed to ${label}: ${[
|
|
@@ -15707,7 +16721,7 @@ function runGitOrThrow(cwd, args, label) {
|
|
|
15707
16721
|
}
|
|
15708
16722
|
}
|
|
15709
16723
|
function validatePrepareAutoMergeDevCheckout(repoRoot2) {
|
|
15710
|
-
const branchResult =
|
|
16724
|
+
const branchResult = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
15711
16725
|
cwd: repoRoot2,
|
|
15712
16726
|
encoding: "utf8"
|
|
15713
16727
|
});
|
|
@@ -15738,7 +16752,7 @@ function validatePrepareAutoMergeDevCheckout(repoRoot2) {
|
|
|
15738
16752
|
function syncLocalDevToOriginDev(repoRoot2) {
|
|
15739
16753
|
const branchResult = validatePrepareAutoMergeDevCheckout(repoRoot2);
|
|
15740
16754
|
if (!branchResult.ok) return branchResult;
|
|
15741
|
-
const fetchResult =
|
|
16755
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
|
|
15742
16756
|
cwd: repoRoot2,
|
|
15743
16757
|
encoding: "utf8"
|
|
15744
16758
|
});
|
|
@@ -15754,7 +16768,7 @@ function syncLocalDevToOriginDev(repoRoot2) {
|
|
|
15754
16768
|
offendingPaths: []
|
|
15755
16769
|
};
|
|
15756
16770
|
}
|
|
15757
|
-
const resetResult =
|
|
16771
|
+
const resetResult = spawnSync3("git", ["reset", "--hard", "origin/dev"], {
|
|
15758
16772
|
cwd: repoRoot2,
|
|
15759
16773
|
encoding: "utf8"
|
|
15760
16774
|
});
|
|
@@ -15798,14 +16812,14 @@ function isArchitecturePlanningDiffPath(path9) {
|
|
|
15798
16812
|
return path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/");
|
|
15799
16813
|
}
|
|
15800
16814
|
async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles) {
|
|
15801
|
-
const remoteResult =
|
|
16815
|
+
const remoteResult = spawnSync3("git", ["remote", "get-url", "origin"], {
|
|
15802
16816
|
cwd: repoRoot2,
|
|
15803
16817
|
encoding: "utf8"
|
|
15804
16818
|
});
|
|
15805
16819
|
if (remoteResult.status !== 0) {
|
|
15806
16820
|
return;
|
|
15807
16821
|
}
|
|
15808
|
-
const worktreePath = mkdtempSync(
|
|
16822
|
+
const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-planning-"));
|
|
15809
16823
|
try {
|
|
15810
16824
|
runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
|
|
15811
16825
|
runGitOrThrow(
|
|
@@ -15819,8 +16833,8 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15819
16833
|
"create planning branch"
|
|
15820
16834
|
);
|
|
15821
16835
|
for (const changedFile of changedFiles) {
|
|
15822
|
-
const sourcePath =
|
|
15823
|
-
const targetPath =
|
|
16836
|
+
const sourcePath = join21(repoRoot2, changedFile);
|
|
16837
|
+
const targetPath = join21(worktreePath, changedFile);
|
|
15824
16838
|
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
15825
16839
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
15826
16840
|
}
|
|
@@ -15835,13 +16849,13 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15835
16849
|
"commit planning diff"
|
|
15836
16850
|
);
|
|
15837
16851
|
} finally {
|
|
15838
|
-
|
|
16852
|
+
spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
|
|
15839
16853
|
cwd: repoRoot2,
|
|
15840
16854
|
encoding: "utf8"
|
|
15841
16855
|
});
|
|
15842
16856
|
rmSync3(worktreePath, { recursive: true, force: true });
|
|
15843
16857
|
}
|
|
15844
|
-
const pushResult =
|
|
16858
|
+
const pushResult = spawnSync3(
|
|
15845
16859
|
"git",
|
|
15846
16860
|
["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
|
|
15847
16861
|
{
|
|
@@ -15977,7 +16991,7 @@ function validateStartPrepareReceipt(prdRef, record, options = {}) {
|
|
|
15977
16991
|
function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
|
|
15978
16992
|
for (const artifactPath of listManifestArtifactPaths(repoRoot2, manifest)) {
|
|
15979
16993
|
const repoRelativePath = toRepoRelativePath2(repoRoot2, artifactPath);
|
|
15980
|
-
const result =
|
|
16994
|
+
const result = spawnSync3(
|
|
15981
16995
|
"git",
|
|
15982
16996
|
["show", `origin/dev:${repoRelativePath}`],
|
|
15983
16997
|
{
|
|
@@ -16003,7 +17017,7 @@ function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
|
|
|
16003
17017
|
return { ok: true };
|
|
16004
17018
|
}
|
|
16005
17019
|
function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
|
|
16006
|
-
const result =
|
|
17020
|
+
const result = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
|
|
16007
17021
|
cwd: repoRoot2,
|
|
16008
17022
|
encoding: "utf8"
|
|
16009
17023
|
});
|
|
@@ -16058,11 +17072,11 @@ function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
|
|
|
16058
17072
|
};
|
|
16059
17073
|
}
|
|
16060
17074
|
function collectObservedReconciliationDirtyPaths(options) {
|
|
16061
|
-
const statusResult =
|
|
17075
|
+
const statusResult = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
|
|
16062
17076
|
cwd: options.worktreeCwd,
|
|
16063
17077
|
encoding: "utf8"
|
|
16064
17078
|
});
|
|
16065
|
-
const diffResult =
|
|
17079
|
+
const diffResult = spawnSync3("git", ["diff", "--name-status"], {
|
|
16066
17080
|
cwd: options.worktreeCwd,
|
|
16067
17081
|
encoding: "utf8"
|
|
16068
17082
|
});
|
|
@@ -16096,13 +17110,13 @@ function collectObservedReconciliationDirtyPaths(options) {
|
|
|
16096
17110
|
};
|
|
16097
17111
|
}
|
|
16098
17112
|
function gitRefExists(repoRoot2, ref) {
|
|
16099
|
-
return
|
|
17113
|
+
return spawnSync3("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
16100
17114
|
cwd: repoRoot2,
|
|
16101
17115
|
encoding: "utf8"
|
|
16102
17116
|
}).status === 0;
|
|
16103
17117
|
}
|
|
16104
17118
|
function collectGitDiffNames(repoRoot2, baseRef, headRef) {
|
|
16105
|
-
const result =
|
|
17119
|
+
const result = spawnSync3("git", ["diff", "--name-only", baseRef, headRef], {
|
|
16106
17120
|
cwd: repoRoot2,
|
|
16107
17121
|
encoding: "utf8"
|
|
16108
17122
|
});
|
|
@@ -19163,8 +20177,8 @@ function formatChecks2(checks) {
|
|
|
19163
20177
|
init_common();
|
|
19164
20178
|
|
|
19165
20179
|
// execution/sandcastle-execution.ts
|
|
19166
|
-
import { mkdirSync as mkdirSync12, writeFileSync as
|
|
19167
|
-
import { join as
|
|
20180
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
20181
|
+
import { join as join23 } from "path";
|
|
19168
20182
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
19169
20183
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
19170
20184
|
|
|
@@ -19173,10 +20187,10 @@ init_common();
|
|
|
19173
20187
|
import { mkdtempSync as mkdtempSync2 } from "fs";
|
|
19174
20188
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
19175
20189
|
import { tmpdir as tmpdir2 } from "os";
|
|
19176
|
-
import { dirname as dirname6, join as
|
|
20190
|
+
import { dirname as dirname6, join as join22 } from "path";
|
|
19177
20191
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
19178
20192
|
for (const artifact of artifacts) {
|
|
19179
|
-
const filePath =
|
|
20193
|
+
const filePath = join22(worktreePath, artifact.path);
|
|
19180
20194
|
await ensureDir(dirname6(filePath));
|
|
19181
20195
|
await writeFile3(filePath, artifact.content, "utf-8");
|
|
19182
20196
|
}
|
|
@@ -19188,7 +20202,7 @@ import path7 from "path";
|
|
|
19188
20202
|
|
|
19189
20203
|
// execution/sandbox-image.ts
|
|
19190
20204
|
import { createHash as createHash4 } from "crypto";
|
|
19191
|
-
import { existsSync as existsSync19, readFileSync as
|
|
20205
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
19192
20206
|
import path6 from "path";
|
|
19193
20207
|
function sandboxImageName(repoRoot2) {
|
|
19194
20208
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
@@ -19198,7 +20212,7 @@ function sandboxImageName(repoRoot2) {
|
|
|
19198
20212
|
if (!existsSync19(dockerfilePath)) {
|
|
19199
20213
|
return `sandcastle:${baseName}`;
|
|
19200
20214
|
}
|
|
19201
|
-
const fingerprint = createHash4("sha256").update(
|
|
20215
|
+
const fingerprint = createHash4("sha256").update(readFileSync18(dockerfilePath)).digest("hex").slice(0, 8);
|
|
19202
20216
|
return `sandcastle:${baseName}-${fingerprint}`;
|
|
19203
20217
|
}
|
|
19204
20218
|
|
|
@@ -19458,17 +20472,32 @@ function parseToolCallArgs(formattedArgs) {
|
|
|
19458
20472
|
return { ok: false };
|
|
19459
20473
|
}
|
|
19460
20474
|
}
|
|
20475
|
+
var SUMMARIZED_TOOL_FIELDS = {
|
|
20476
|
+
write: { content: "contentCount" },
|
|
20477
|
+
edit: { oldString: "oldCount", newString: "newCount" }
|
|
20478
|
+
};
|
|
20479
|
+
var SUMMARIZED_TOOL_ARRAY_FIELDS = {
|
|
20480
|
+
todowrite: { todos: "todoCount" }
|
|
20481
|
+
};
|
|
19461
20482
|
function summarizeToolCallArgs(name, args) {
|
|
19462
20483
|
if (!isPlainObject(args)) {
|
|
19463
20484
|
return args;
|
|
19464
20485
|
}
|
|
19465
|
-
|
|
20486
|
+
const fields = SUMMARIZED_TOOL_FIELDS[name.toLowerCase()];
|
|
20487
|
+
const arrayFields = SUMMARIZED_TOOL_ARRAY_FIELDS[name.toLowerCase()];
|
|
20488
|
+
if (!fields && !arrayFields) {
|
|
19466
20489
|
return args;
|
|
19467
20490
|
}
|
|
19468
20491
|
const summarizedArgs = {};
|
|
19469
20492
|
for (const [key, value] of Object.entries(args)) {
|
|
19470
|
-
|
|
19471
|
-
|
|
20493
|
+
const mappedKey = fields?.[key];
|
|
20494
|
+
if (mappedKey && typeof value === "string") {
|
|
20495
|
+
summarizedArgs[mappedKey] = value.length;
|
|
20496
|
+
continue;
|
|
20497
|
+
}
|
|
20498
|
+
const mappedArrayKey = arrayFields?.[key];
|
|
20499
|
+
if (mappedArrayKey && Array.isArray(value)) {
|
|
20500
|
+
summarizedArgs[mappedArrayKey] = value.length;
|
|
19472
20501
|
continue;
|
|
19473
20502
|
}
|
|
19474
20503
|
summarizedArgs[key] = value;
|
|
@@ -19479,13 +20508,13 @@ function isPlainObject(value) {
|
|
|
19479
20508
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19480
20509
|
}
|
|
19481
20510
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
19482
|
-
const promptsDir =
|
|
20511
|
+
const promptsDir = join23(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
19483
20512
|
mkdirSync12(promptsDir, { recursive: true });
|
|
19484
20513
|
const timestamp2 = Date.now();
|
|
19485
20514
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
19486
20515
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
19487
|
-
const filePath =
|
|
19488
|
-
|
|
20516
|
+
const filePath = join23(promptsDir, filename);
|
|
20517
|
+
writeFileSync9(filePath, prompt, "utf-8");
|
|
19489
20518
|
}
|
|
19490
20519
|
|
|
19491
20520
|
// cli.ts
|
|
@@ -20358,11 +21387,11 @@ function createCliProgram(version) {
|
|
|
20358
21387
|
return program;
|
|
20359
21388
|
}
|
|
20360
21389
|
async function resolveCliVersion() {
|
|
20361
|
-
if (isPackageVersion("0.0.0-next-
|
|
20362
|
-
return "0.0.0-next-
|
|
21390
|
+
if (isPackageVersion("0.0.0-next-20260609130908")) {
|
|
21391
|
+
return "0.0.0-next-20260609130908";
|
|
20363
21392
|
}
|
|
20364
|
-
if (isReleaseVersion("0.0.0-next-
|
|
20365
|
-
return "0.0.0-next-
|
|
21393
|
+
if (isReleaseVersion("0.0.0-next-20260609130908")) {
|
|
21394
|
+
return "0.0.0-next-20260609130908";
|
|
20366
21395
|
}
|
|
20367
21396
|
try {
|
|
20368
21397
|
const root = repoRoot();
|