@pourkit/cli 0.0.0-next-20260608072322 → 0.0.0-next-20260608221925
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1070 -259
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +1 -1
- 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");
|
|
@@ -6158,10 +6162,57 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as rea
|
|
|
6158
6162
|
import { join as join13 } from "path";
|
|
6159
6163
|
|
|
6160
6164
|
// prd-run/local-branches.ts
|
|
6161
|
-
import { execFileSync } from "child_process";
|
|
6165
|
+
import { execFileSync, spawnSync as spawnSync2 } from "child_process";
|
|
6162
6166
|
function getLocalPrdBranchName(prdId) {
|
|
6163
6167
|
return `local/${prdId}`;
|
|
6164
6168
|
}
|
|
6169
|
+
function materializeLocalPrdBranch(prdId, startBaseCommit, repoRoot2) {
|
|
6170
|
+
const branch = getLocalPrdBranchName(prdId);
|
|
6171
|
+
const root = repoRoot2 ?? process.cwd();
|
|
6172
|
+
const branchExists = (() => {
|
|
6173
|
+
try {
|
|
6174
|
+
return spawnSync2("git", ["rev-parse", "--verify", "--quiet", branch], {
|
|
6175
|
+
cwd: root,
|
|
6176
|
+
encoding: "utf8",
|
|
6177
|
+
stdio: "pipe"
|
|
6178
|
+
}).status === 0;
|
|
6179
|
+
} catch {
|
|
6180
|
+
return false;
|
|
6181
|
+
}
|
|
6182
|
+
})();
|
|
6183
|
+
if (branchExists) {
|
|
6184
|
+
const currentCommit = spawnSync2("git", ["rev-parse", branch], {
|
|
6185
|
+
cwd: root,
|
|
6186
|
+
encoding: "utf8",
|
|
6187
|
+
stdio: "pipe"
|
|
6188
|
+
}).stdout?.toString?.().trim() ?? "";
|
|
6189
|
+
if (currentCommit !== startBaseCommit) {
|
|
6190
|
+
return {
|
|
6191
|
+
ok: false,
|
|
6192
|
+
failureCode: "branch_commit_mismatch",
|
|
6193
|
+
message: `Local PRD branch ${branch} exists at commit ${currentCommit} but expected commit ${startBaseCommit}. The branch has diverged from the start base. To resolve, delete the local branch and rerun start, or manually reset it to the expected commit.`
|
|
6194
|
+
};
|
|
6195
|
+
}
|
|
6196
|
+
return {
|
|
6197
|
+
ok: true,
|
|
6198
|
+
created: false
|
|
6199
|
+
};
|
|
6200
|
+
}
|
|
6201
|
+
const branchResult = spawnSync2("git", ["branch", branch, startBaseCommit], {
|
|
6202
|
+
cwd: root,
|
|
6203
|
+
encoding: "utf8",
|
|
6204
|
+
stdio: "pipe"
|
|
6205
|
+
});
|
|
6206
|
+
if (branchResult.status !== 0) {
|
|
6207
|
+
const stderr = branchResult.stderr?.toString?.() ?? "unknown error";
|
|
6208
|
+
return {
|
|
6209
|
+
ok: false,
|
|
6210
|
+
failureCode: "branch_creation_failed",
|
|
6211
|
+
message: `Failed to create local PRD branch ${branch} at ${startBaseCommit}: ${stderr}`
|
|
6212
|
+
};
|
|
6213
|
+
}
|
|
6214
|
+
return { ok: true, created: true };
|
|
6215
|
+
}
|
|
6165
6216
|
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
|
|
6166
6217
|
var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
|
|
6167
6218
|
function validateLocalBranchName(name) {
|
|
@@ -8307,13 +8358,13 @@ import {
|
|
|
8307
8358
|
lstatSync,
|
|
8308
8359
|
mkdirSync as mkdirSync11,
|
|
8309
8360
|
mkdtempSync,
|
|
8310
|
-
readdirSync as
|
|
8311
|
-
readFileSync as
|
|
8361
|
+
readdirSync as readdirSync5,
|
|
8362
|
+
readFileSync as readFileSync17,
|
|
8312
8363
|
realpathSync,
|
|
8313
8364
|
rmSync as rmSync3
|
|
8314
8365
|
} from "fs";
|
|
8315
|
-
import { spawnSync as
|
|
8316
|
-
import { dirname as dirname5, join as
|
|
8366
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
8367
|
+
import { dirname as dirname5, join as join21, relative as relative2 } from "path";
|
|
8317
8368
|
import { tmpdir } from "os";
|
|
8318
8369
|
|
|
8319
8370
|
// prd-run/state.ts
|
|
@@ -8343,9 +8394,11 @@ var PrdRunRecordSchema = z3.object({
|
|
|
8343
8394
|
"waiting_for_integration",
|
|
8344
8395
|
"finalizing",
|
|
8345
8396
|
"final_reviewed",
|
|
8346
|
-
"complete"
|
|
8397
|
+
"complete",
|
|
8398
|
+
"completed_local_branch"
|
|
8347
8399
|
]),
|
|
8348
8400
|
updatedAt: z3.string().min(1),
|
|
8401
|
+
mode: z3.enum(["github", "local"]).optional(),
|
|
8349
8402
|
blockedGate: z3.enum([
|
|
8350
8403
|
"manifest-location",
|
|
8351
8404
|
"manifest-readiness",
|
|
@@ -9152,6 +9205,28 @@ function getLocalStorePath3(repoRoot2, prdId) {
|
|
|
9152
9205
|
function getFinalReviewReceiptPath(repoRoot2, prdId) {
|
|
9153
9206
|
return join17(getLocalStorePath3(repoRoot2, prdId), "final-review-receipt.json");
|
|
9154
9207
|
}
|
|
9208
|
+
function computeLocalMergeBase(repoRoot2, prdBranch) {
|
|
9209
|
+
const candidates = ["dev", "main", "master", "HEAD"];
|
|
9210
|
+
for (const base of candidates) {
|
|
9211
|
+
try {
|
|
9212
|
+
const result = execFileSync3("git", ["merge-base", base, prdBranch], {
|
|
9213
|
+
cwd: repoRoot2,
|
|
9214
|
+
encoding: "utf8",
|
|
9215
|
+
stdio: "pipe"
|
|
9216
|
+
});
|
|
9217
|
+
const mergeBase = result.trim();
|
|
9218
|
+
if (mergeBase && mergeBase.length >= 6) {
|
|
9219
|
+
return { ok: true, mergeBase };
|
|
9220
|
+
}
|
|
9221
|
+
} catch {
|
|
9222
|
+
continue;
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9225
|
+
return {
|
|
9226
|
+
ok: false,
|
|
9227
|
+
reason: "Could not compute merge base against any known base branch (dev/main/master/HEAD)."
|
|
9228
|
+
};
|
|
9229
|
+
}
|
|
9155
9230
|
async function runLocalFinalReview(prdId, options) {
|
|
9156
9231
|
const root = options?.repoRoot ?? process.cwd();
|
|
9157
9232
|
const commands = options?.verificationCommands ?? [];
|
|
@@ -9164,6 +9239,34 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
9164
9239
|
};
|
|
9165
9240
|
}
|
|
9166
9241
|
const branch = getLocalPrdBranchName(prdId);
|
|
9242
|
+
const mergeBaseResult = computeLocalMergeBase(root, branch);
|
|
9243
|
+
if (!mergeBaseResult.ok) {
|
|
9244
|
+
return {
|
|
9245
|
+
ok: false,
|
|
9246
|
+
failureCode: "merge_base_failed",
|
|
9247
|
+
message: mergeBaseResult.reason
|
|
9248
|
+
};
|
|
9249
|
+
}
|
|
9250
|
+
const semanticResult = validateFinalReviewArtifactSemanticIds(
|
|
9251
|
+
{
|
|
9252
|
+
prdRef: prdId,
|
|
9253
|
+
stage: "prdFinalReview",
|
|
9254
|
+
checkoutBase: branch,
|
|
9255
|
+
reviewBase: mergeBaseResult.mergeBase
|
|
9256
|
+
},
|
|
9257
|
+
{
|
|
9258
|
+
prdRef: prdId,
|
|
9259
|
+
prdBranch: branch,
|
|
9260
|
+
mergeBase: mergeBaseResult.mergeBase
|
|
9261
|
+
}
|
|
9262
|
+
);
|
|
9263
|
+
if (!semanticResult.ok) {
|
|
9264
|
+
return {
|
|
9265
|
+
ok: false,
|
|
9266
|
+
failureCode: "invalid_artifact_semantics",
|
|
9267
|
+
message: semanticResult.errors.join("; ")
|
|
9268
|
+
};
|
|
9269
|
+
}
|
|
9167
9270
|
const failures = [];
|
|
9168
9271
|
for (const cmd of commands) {
|
|
9169
9272
|
try {
|
|
@@ -9184,7 +9287,7 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
9184
9287
|
});
|
|
9185
9288
|
verdict = "pass_with_retouch";
|
|
9186
9289
|
} catch {
|
|
9187
|
-
verdict = "
|
|
9290
|
+
verdict = "pass_no_changes";
|
|
9188
9291
|
}
|
|
9189
9292
|
} else if (failures.length === commands.length) {
|
|
9190
9293
|
verdict = "blocked";
|
|
@@ -9202,9 +9305,10 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
9202
9305
|
}
|
|
9203
9306
|
}
|
|
9204
9307
|
const receipt = {
|
|
9205
|
-
|
|
9308
|
+
reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9206
9309
|
verdict,
|
|
9207
|
-
branch
|
|
9310
|
+
prdBranch: branch,
|
|
9311
|
+
mergeBase: mergeBaseResult.mergeBase
|
|
9208
9312
|
};
|
|
9209
9313
|
const receiptPath = getFinalReviewReceiptPath(root, prdId);
|
|
9210
9314
|
mkdirSync9(getLocalStorePath3(root, prdId), { recursive: true });
|
|
@@ -9276,6 +9380,26 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
9276
9380
|
stdio: "pipe"
|
|
9277
9381
|
}
|
|
9278
9382
|
);
|
|
9383
|
+
const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
|
|
9384
|
+
cwd: root,
|
|
9385
|
+
encoding: "utf8",
|
|
9386
|
+
stdio: "pipe"
|
|
9387
|
+
}).trim();
|
|
9388
|
+
const changedPathsResult = execFileSync3(
|
|
9389
|
+
"git",
|
|
9390
|
+
["diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"],
|
|
9391
|
+
{
|
|
9392
|
+
cwd: root,
|
|
9393
|
+
encoding: "utf8",
|
|
9394
|
+
stdio: "pipe"
|
|
9395
|
+
}
|
|
9396
|
+
);
|
|
9397
|
+
const changedPaths = changedPathsResult.split(/\r?\n/).filter(Boolean);
|
|
9398
|
+
return {
|
|
9399
|
+
mergeCommit,
|
|
9400
|
+
changedPaths,
|
|
9401
|
+
reviewedTimestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9402
|
+
};
|
|
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,78 +9734,369 @@ 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)
|
|
9694
9834
|
};
|
|
9695
9835
|
}
|
|
9696
|
-
valid2.sort((a, b) => {
|
|
9697
|
-
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
9698
|
-
const ageCmp = a.issue.createdAt.localeCompare(b.issue.createdAt);
|
|
9699
|
-
if (ageCmp !== 0) return ageCmp;
|
|
9700
|
-
return a.issue.number - b.issue.number;
|
|
9701
|
-
});
|
|
9702
|
-
return { ok: true, issue: valid2[0].issue };
|
|
9703
9836
|
}
|
|
9704
9837
|
|
|
9705
|
-
//
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
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(" ")
|
|
10077
|
+
};
|
|
10078
|
+
}
|
|
10079
|
+
valid2.sort((a, b) => {
|
|
10080
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
10081
|
+
const ageCmp = a.issue.createdAt.localeCompare(b.issue.createdAt);
|
|
10082
|
+
if (ageCmp !== 0) return ageCmp;
|
|
10083
|
+
return a.issue.number - b.issue.number;
|
|
10084
|
+
});
|
|
10085
|
+
return { ok: true, issue: valid2[0].issue };
|
|
10086
|
+
}
|
|
10087
|
+
|
|
10088
|
+
// issues/blocked-issue.ts
|
|
10089
|
+
function parseBlockedBy(body) {
|
|
10090
|
+
if (!body) return [];
|
|
10091
|
+
const bm = body.match(/## Blocked by\s*\n([\s\S]*?)(?=\n## |$)/i);
|
|
10092
|
+
if (!bm) return [];
|
|
10093
|
+
const refs = [];
|
|
10094
|
+
const re = /#(\d+)/g;
|
|
10095
|
+
let m;
|
|
10096
|
+
while ((m = re.exec(bm[1])) !== null) {
|
|
10097
|
+
refs.push(Number(m[1]));
|
|
10098
|
+
}
|
|
10099
|
+
return refs;
|
|
9717
10100
|
}
|
|
9718
10101
|
async function reconcileBlockedIssue(issue, deps) {
|
|
9719
10102
|
const blockers = parseBlockedBy(issue.body);
|
|
@@ -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,7 +10720,7 @@ function canRetryFinalReviewBlock(record) {
|
|
|
10331
10720
|
}
|
|
10332
10721
|
function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
10333
10722
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
10334
|
-
return existsSync17(promptPath) ?
|
|
10723
|
+
return existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : promptTemplate;
|
|
10335
10724
|
}
|
|
10336
10725
|
function formatVerificationCommands(commands) {
|
|
10337
10726
|
if (commands.length === 0) return "No verification commands configured.";
|
|
@@ -10385,7 +10774,7 @@ function buildReconciliationBranchName(prdRef) {
|
|
|
10385
10774
|
return `${normalizePrdRunRef2(prdRef)}-reconciliation`;
|
|
10386
10775
|
}
|
|
10387
10776
|
function reconciliationWorktreePath(repoRoot2, branchName) {
|
|
10388
|
-
return
|
|
10777
|
+
return join21(repoRoot2, ".sandcastle", "worktrees", branchName);
|
|
10389
10778
|
}
|
|
10390
10779
|
function reconciliationReceiptWorktreePath(branchName) {
|
|
10391
10780
|
return `.sandcastle/worktrees/${branchName}`;
|
|
@@ -10397,7 +10786,7 @@ function buildFinalReviewBranchName(prdRef) {
|
|
|
10397
10786
|
return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
|
|
10398
10787
|
}
|
|
10399
10788
|
function finalReviewWorktreePath(repoRoot2, branchName) {
|
|
10400
|
-
return
|
|
10789
|
+
return join21(
|
|
10401
10790
|
repoRoot2,
|
|
10402
10791
|
".sandcastle",
|
|
10403
10792
|
"worktrees",
|
|
@@ -10420,7 +10809,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
|
|
|
10420
10809
|
}
|
|
10421
10810
|
paths.add(path9);
|
|
10422
10811
|
};
|
|
10423
|
-
const diffResult =
|
|
10812
|
+
const diffResult = spawnSync3(
|
|
10424
10813
|
"git",
|
|
10425
10814
|
["diff", "--name-only", mergeBase, "--", "."],
|
|
10426
10815
|
{
|
|
@@ -10431,7 +10820,7 @@ function listFinalReviewChangedPaths(worktreePath, mergeBase) {
|
|
|
10431
10820
|
for (const path9 of diffResult.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
10432
10821
|
addPath(path9);
|
|
10433
10822
|
}
|
|
10434
|
-
const statusResult =
|
|
10823
|
+
const statusResult = spawnSync3(
|
|
10435
10824
|
"git",
|
|
10436
10825
|
["status", "--porcelain", "--untracked-files=all"],
|
|
10437
10826
|
{
|
|
@@ -10452,7 +10841,7 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
10452
10841
|
options.repoRoot,
|
|
10453
10842
|
options.promptTemplate
|
|
10454
10843
|
);
|
|
10455
|
-
const promptBody = existsSync17(promptPath) ?
|
|
10844
|
+
const promptBody = existsSync17(promptPath) ? readFileSync17(promptPath, "utf-8") : options.promptTemplate;
|
|
10456
10845
|
return [
|
|
10457
10846
|
"# Final Review Retouch PR Finalizer",
|
|
10458
10847
|
"",
|
|
@@ -10486,7 +10875,7 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
10486
10875
|
}
|
|
10487
10876
|
async function runFinalReviewPrFinalizer(options) {
|
|
10488
10877
|
const finalizer = options.target.strategy.finalize.prDescriptionAgent;
|
|
10489
|
-
const artifactPathInWorktree =
|
|
10878
|
+
const artifactPathInWorktree = join21(
|
|
10490
10879
|
".pourkit",
|
|
10491
10880
|
".tmp",
|
|
10492
10881
|
"finalizer",
|
|
@@ -10532,7 +10921,7 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10532
10921
|
options.repoRoot,
|
|
10533
10922
|
options.branchName
|
|
10534
10923
|
);
|
|
10535
|
-
const listResult =
|
|
10924
|
+
const listResult = spawnSync3("git", ["worktree", "list", "--porcelain"], {
|
|
10536
10925
|
cwd: options.repoRoot,
|
|
10537
10926
|
encoding: "utf8"
|
|
10538
10927
|
});
|
|
@@ -10568,7 +10957,7 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10568
10957
|
};
|
|
10569
10958
|
}
|
|
10570
10959
|
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
10571
|
-
const branchResult =
|
|
10960
|
+
const branchResult = spawnSync3(
|
|
10572
10961
|
"git",
|
|
10573
10962
|
["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
|
|
10574
10963
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -10580,7 +10969,7 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10580
10969
|
diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
|
|
10581
10970
|
};
|
|
10582
10971
|
}
|
|
10583
|
-
const addResult =
|
|
10972
|
+
const addResult = spawnSync3(
|
|
10584
10973
|
"git",
|
|
10585
10974
|
["worktree", "add", worktreePath, options.branchName],
|
|
10586
10975
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -10622,7 +11011,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10622
11011
|
if (existingPr && existingPr.state === "OPEN") {
|
|
10623
11012
|
return existingPr;
|
|
10624
11013
|
}
|
|
10625
|
-
const worktreePath = mkdtempSync(
|
|
11014
|
+
const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-retouch-"));
|
|
10626
11015
|
try {
|
|
10627
11016
|
runGitOrThrow(
|
|
10628
11017
|
options.repoRoot,
|
|
@@ -10640,8 +11029,8 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10640
11029
|
"create retouch branch"
|
|
10641
11030
|
);
|
|
10642
11031
|
for (const changedFile of options.changedPaths) {
|
|
10643
|
-
const sourcePath =
|
|
10644
|
-
const targetPath =
|
|
11032
|
+
const sourcePath = join21(options.sourceWorktreePath, changedFile);
|
|
11033
|
+
const targetPath = join21(worktreePath, changedFile);
|
|
10645
11034
|
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
10646
11035
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
10647
11036
|
}
|
|
@@ -10656,13 +11045,13 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10656
11045
|
"commit retouch diff"
|
|
10657
11046
|
);
|
|
10658
11047
|
} finally {
|
|
10659
|
-
|
|
11048
|
+
spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
|
|
10660
11049
|
cwd: options.repoRoot,
|
|
10661
11050
|
encoding: "utf8"
|
|
10662
11051
|
});
|
|
10663
11052
|
rmSync3(worktreePath, { recursive: true, force: true });
|
|
10664
11053
|
}
|
|
10665
|
-
const pushResult =
|
|
11054
|
+
const pushResult = spawnSync3(
|
|
10666
11055
|
"git",
|
|
10667
11056
|
["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
|
|
10668
11057
|
{
|
|
@@ -11072,7 +11461,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11072
11461
|
};
|
|
11073
11462
|
}
|
|
11074
11463
|
const resolvedWorktreePath = executionResult.worktreePath;
|
|
11075
|
-
const artifactPath =
|
|
11464
|
+
const artifactPath = join21(
|
|
11076
11465
|
resolvedWorktreePath,
|
|
11077
11466
|
".pourkit/final-review-artifact.json"
|
|
11078
11467
|
);
|
|
@@ -11656,7 +12045,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11656
12045
|
function runPrdRunValidateFinalReviewCommand(options) {
|
|
11657
12046
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
11658
12047
|
const checkoutBase = options.checkoutBase ?? prdRef;
|
|
11659
|
-
const artifactPath = options.artifactPath ? options.artifactPath :
|
|
12048
|
+
const artifactPath = options.artifactPath ? options.artifactPath : join21(options.repoRoot, ".pourkit", "final-review-artifact.json");
|
|
11660
12049
|
const artifact = parseFinalReviewArtifact(artifactPath);
|
|
11661
12050
|
if (!artifact.ok) {
|
|
11662
12051
|
return {
|
|
@@ -11670,7 +12059,7 @@ function runPrdRunValidateFinalReviewCommand(options) {
|
|
|
11670
12059
|
}
|
|
11671
12060
|
let changedPaths = options.changedPaths;
|
|
11672
12061
|
if (artifact.verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && (!artifact.changedPaths || artifact.changedPaths.length === 0)) {
|
|
11673
|
-
const diffResult =
|
|
12062
|
+
const diffResult = spawnSync3(
|
|
11674
12063
|
"git",
|
|
11675
12064
|
["diff", "--name-only", options.reviewBase, "HEAD", "--", "."],
|
|
11676
12065
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -11822,12 +12211,12 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11822
12211
|
);
|
|
11823
12212
|
const worktreeCwd = worktreePath;
|
|
11824
12213
|
const existingReconciliationReceipt = record.reconciliation;
|
|
11825
|
-
const existingLocalBranch =
|
|
12214
|
+
const existingLocalBranch = spawnSync3(
|
|
11826
12215
|
"git",
|
|
11827
12216
|
["rev-parse", "--verify", "--quiet", reconciliationBranchName],
|
|
11828
12217
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
11829
12218
|
).status === 0;
|
|
11830
|
-
const existingWorktree =
|
|
12219
|
+
const existingWorktree = spawnSync3(
|
|
11831
12220
|
"git",
|
|
11832
12221
|
["worktree", "list", "--porcelain"],
|
|
11833
12222
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -11880,7 +12269,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11880
12269
|
branchAction: "blocked"
|
|
11881
12270
|
};
|
|
11882
12271
|
}
|
|
11883
|
-
const resetResult =
|
|
12272
|
+
const resetResult = spawnSync3(
|
|
11884
12273
|
"git",
|
|
11885
12274
|
["reset", "--hard", `origin/${prdBranch}`],
|
|
11886
12275
|
{ cwd: worktreeCwd, encoding: "utf8" }
|
|
@@ -11923,7 +12312,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11923
12312
|
);
|
|
11924
12313
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11925
12314
|
}
|
|
11926
|
-
const adoptionCheck =
|
|
12315
|
+
const adoptionCheck = spawnSync3(
|
|
11927
12316
|
"git",
|
|
11928
12317
|
[
|
|
11929
12318
|
"merge-base",
|
|
@@ -11971,7 +12360,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11971
12360
|
);
|
|
11972
12361
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11973
12362
|
}
|
|
11974
|
-
const branchResult =
|
|
12363
|
+
const branchResult = spawnSync3(
|
|
11975
12364
|
"git",
|
|
11976
12365
|
["branch", "-f", reconciliationBranchName, `origin/${prdBranch}`],
|
|
11977
12366
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
@@ -11986,13 +12375,13 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11986
12375
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11987
12376
|
}
|
|
11988
12377
|
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
11989
|
-
const addResult =
|
|
12378
|
+
const addResult = spawnSync3(
|
|
11990
12379
|
"git",
|
|
11991
12380
|
["worktree", "add", worktreePath, reconciliationBranchName],
|
|
11992
12381
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
11993
12382
|
);
|
|
11994
12383
|
if (addResult.status !== 0) {
|
|
11995
|
-
|
|
12384
|
+
spawnSync3("git", ["branch", "-D", reconciliationBranchName], {
|
|
11996
12385
|
cwd: options.repoRoot,
|
|
11997
12386
|
encoding: "utf8"
|
|
11998
12387
|
});
|
|
@@ -12040,7 +12429,7 @@ function buildReconciliationPrBody(options) {
|
|
|
12040
12429
|
function commitAndPushReconciliationChanges(options) {
|
|
12041
12430
|
const diagnostics = [];
|
|
12042
12431
|
const existingPaths = options.changedPlanningPaths.filter((p) => {
|
|
12043
|
-
return existsSync17(
|
|
12432
|
+
return existsSync17(join21(options.worktreeCwd, p));
|
|
12044
12433
|
});
|
|
12045
12434
|
if (existingPaths.length === 0) {
|
|
12046
12435
|
diagnostics.push(
|
|
@@ -12048,7 +12437,7 @@ function commitAndPushReconciliationChanges(options) {
|
|
|
12048
12437
|
);
|
|
12049
12438
|
return { ok: true, diagnostics: [...diagnostics] };
|
|
12050
12439
|
}
|
|
12051
|
-
const addResult =
|
|
12440
|
+
const addResult = spawnSync3("git", ["add", "--", ...existingPaths], {
|
|
12052
12441
|
cwd: options.worktreeCwd,
|
|
12053
12442
|
encoding: "utf8"
|
|
12054
12443
|
});
|
|
@@ -12065,7 +12454,7 @@ function commitAndPushReconciliationChanges(options) {
|
|
|
12065
12454
|
};
|
|
12066
12455
|
}
|
|
12067
12456
|
const commitTitle = `docs: reconcile ${normalizePrdRunRef2(options.prdRef)} architecture`;
|
|
12068
|
-
const commitResult =
|
|
12457
|
+
const commitResult = spawnSync3("git", ["commit", "-m", commitTitle], {
|
|
12069
12458
|
cwd: options.worktreeCwd,
|
|
12070
12459
|
encoding: "utf8"
|
|
12071
12460
|
});
|
|
@@ -12089,7 +12478,7 @@ function commitAndPushReconciliationChanges(options) {
|
|
|
12089
12478
|
};
|
|
12090
12479
|
}
|
|
12091
12480
|
}
|
|
12092
|
-
const pushResult =
|
|
12481
|
+
const pushResult = spawnSync3(
|
|
12093
12482
|
"git",
|
|
12094
12483
|
[
|
|
12095
12484
|
"push",
|
|
@@ -12183,7 +12572,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12183
12572
|
const record = preflightResult.record;
|
|
12184
12573
|
const prdBranch = record?.prdBranch ?? prdRef;
|
|
12185
12574
|
const mergeBase = preflightResult.mergeBase;
|
|
12186
|
-
const worktreeCwd = preflightResult.worktreePath ?
|
|
12575
|
+
const worktreeCwd = preflightResult.worktreePath ? join21(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
|
|
12187
12576
|
const staleSucceededRecovery = assessStaleSucceededReconciliationReceipt({
|
|
12188
12577
|
receipt: record?.reconciliation,
|
|
12189
12578
|
worktreeCwd,
|
|
@@ -12485,7 +12874,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12485
12874
|
baseRef: prdBranch,
|
|
12486
12875
|
checkoutBase,
|
|
12487
12876
|
reviewBase: mergeBase,
|
|
12488
|
-
worktreePath:
|
|
12877
|
+
worktreePath: join21(options.repoRoot, preflightResult.worktreePath),
|
|
12489
12878
|
sandbox: options.config.sandbox,
|
|
12490
12879
|
artifactPath,
|
|
12491
12880
|
logger: options.logger
|
|
@@ -12586,7 +12975,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12586
12975
|
offendingPaths: []
|
|
12587
12976
|
};
|
|
12588
12977
|
}
|
|
12589
|
-
const fullArtifactPath =
|
|
12978
|
+
const fullArtifactPath = join21(executionResult.worktreePath, artifactPath);
|
|
12590
12979
|
let artifactContent;
|
|
12591
12980
|
try {
|
|
12592
12981
|
artifactContent = JSON.parse(retryResult.artifact.value);
|
|
@@ -12693,11 +13082,11 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12693
13082
|
const artifactResult = artifactContent.result ?? "changes_produced";
|
|
12694
13083
|
const artifactChangedPaths = artifactContent.changedPlanningPaths ?? [];
|
|
12695
13084
|
const artifactNoChangeRationale = artifactContent.noChangeRationale;
|
|
12696
|
-
const gitStatusResult =
|
|
13085
|
+
const gitStatusResult = spawnSync3("git", ["status", "--short"], {
|
|
12697
13086
|
cwd: worktreeCwd,
|
|
12698
13087
|
encoding: "utf8"
|
|
12699
13088
|
});
|
|
12700
|
-
const gitDiffResult =
|
|
13089
|
+
const gitDiffResult = spawnSync3("git", ["diff", "--name-status"], {
|
|
12701
13090
|
cwd: worktreeCwd,
|
|
12702
13091
|
encoding: "utf8"
|
|
12703
13092
|
});
|
|
@@ -13450,7 +13839,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
13450
13839
|
}
|
|
13451
13840
|
function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
13452
13841
|
const normalized = normalizePrdRunRef2(prdRef);
|
|
13453
|
-
const fetchResult =
|
|
13842
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", "dev", normalized], {
|
|
13454
13843
|
cwd: repoRoot2,
|
|
13455
13844
|
encoding: "utf8"
|
|
13456
13845
|
});
|
|
@@ -13470,7 +13859,7 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
|
13470
13859
|
if (fetchResult.stderr?.toString?.()?.trim()) {
|
|
13471
13860
|
fetchDiagnostics.push(fetchResult.stderr.toString().trim());
|
|
13472
13861
|
}
|
|
13473
|
-
const mergeBaseResult =
|
|
13862
|
+
const mergeBaseResult = spawnSync3(
|
|
13474
13863
|
"git",
|
|
13475
13864
|
["merge-base", "origin/dev", `origin/${normalized}`],
|
|
13476
13865
|
{
|
|
@@ -13613,17 +14002,17 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13613
14002
|
const blockerBugs = [];
|
|
13614
14003
|
const blockersFixed = [];
|
|
13615
14004
|
const nonBlockers = [];
|
|
13616
|
-
const prdMirrorPath =
|
|
13617
|
-
const completionsDir =
|
|
14005
|
+
const prdMirrorPath = join21(repoRoot2, PRD_047_PRD_MIRROR_PATH);
|
|
14006
|
+
const completionsDir = join21(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
|
|
13618
14007
|
let escapeHatchPresent = false;
|
|
13619
14008
|
if (existsSync17(completionsDir)) {
|
|
13620
|
-
const files =
|
|
14009
|
+
const files = readdirSync5(completionsDir);
|
|
13621
14010
|
escapeHatchPresent = files.some(
|
|
13622
14011
|
(f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
|
|
13623
14012
|
);
|
|
13624
14013
|
}
|
|
13625
14014
|
if (!escapeHatchPresent) {
|
|
13626
|
-
escapeHatchPresent = existsSync17(
|
|
14015
|
+
escapeHatchPresent = existsSync17(join21(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
|
|
13627
14016
|
}
|
|
13628
14017
|
const prdMirrorExists = existsSync17(prdMirrorPath);
|
|
13629
14018
|
if (!prdMirrorExists) {
|
|
@@ -13632,7 +14021,7 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13632
14021
|
);
|
|
13633
14022
|
}
|
|
13634
14023
|
if (prdMirrorExists) {
|
|
13635
|
-
const mirrorContent =
|
|
14024
|
+
const mirrorContent = readFileSync17(prdMirrorPath, "utf-8");
|
|
13636
14025
|
const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
|
|
13637
14026
|
const hasEscapeHatch = mirrorContent.includes("escape hatch");
|
|
13638
14027
|
if (!hasReconcileCommand) {
|
|
@@ -13646,16 +14035,16 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13646
14035
|
);
|
|
13647
14036
|
}
|
|
13648
14037
|
}
|
|
13649
|
-
const childIssueDir =
|
|
14038
|
+
const childIssueDir = join21(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
|
|
13650
14039
|
const childIssuesExist = existsSync17(childIssueDir);
|
|
13651
14040
|
if (!childIssuesExist) {
|
|
13652
14041
|
nonBlockers.push(
|
|
13653
14042
|
`PRD-0047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
|
|
13654
14043
|
);
|
|
13655
14044
|
}
|
|
13656
|
-
const manifestPath =
|
|
14045
|
+
const manifestPath = join21(repoRoot2, PRD_047_MANIFEST_PATH);
|
|
13657
14046
|
if (existsSync17(manifestPath)) {
|
|
13658
|
-
const manifestContent =
|
|
14047
|
+
const manifestContent = readFileSync17(manifestPath, "utf-8");
|
|
13659
14048
|
if (!manifestContent.includes("ready_for_prepare")) {
|
|
13660
14049
|
nonBlockers.push(
|
|
13661
14050
|
"Planning Artifact Manifest is not marked as ready_for_prepare."
|
|
@@ -13744,6 +14133,17 @@ function runPrdRunAuditCommand(options) {
|
|
|
13744
14133
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
13745
14134
|
return auditPrd047Implementation(options.repoRoot, prdRef);
|
|
13746
14135
|
}
|
|
14136
|
+
async function closeParentGitHubIssue(prdRef, repoRoot2, issueProvider) {
|
|
14137
|
+
const prdResult = await resolveLocalPrdArtifact(prdRef, repoRoot2);
|
|
14138
|
+
const parentIssueNumber = prdResult.ok ? prdResult.data.githubProjection.issueNumber : null;
|
|
14139
|
+
if (!parentIssueNumber) return { closed: false, issueNumber: null };
|
|
14140
|
+
try {
|
|
14141
|
+
await issueProvider.closeIssue(parentIssueNumber);
|
|
14142
|
+
return { closed: true, issueNumber: parentIssueNumber };
|
|
14143
|
+
} catch {
|
|
14144
|
+
return { closed: false, issueNumber: parentIssueNumber };
|
|
14145
|
+
}
|
|
14146
|
+
}
|
|
13747
14147
|
async function runPrdRunLaunchCommand(options) {
|
|
13748
14148
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
13749
14149
|
const attempted = [];
|
|
@@ -13755,10 +14155,11 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13755
14155
|
const terminal = /* @__PURE__ */ new Set([
|
|
13756
14156
|
"waiting_for_integration",
|
|
13757
14157
|
"finalizing",
|
|
13758
|
-
"complete"
|
|
14158
|
+
"complete",
|
|
14159
|
+
"completed_local_branch"
|
|
13759
14160
|
]);
|
|
13760
14161
|
if (terminal.has(existingRecord.record.status)) {
|
|
13761
|
-
|
|
14162
|
+
const result = {
|
|
13762
14163
|
prdRef,
|
|
13763
14164
|
status: existingRecord.record.status,
|
|
13764
14165
|
attempted: [],
|
|
@@ -13768,10 +14169,38 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13768
14169
|
`PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`
|
|
13769
14170
|
]
|
|
13770
14171
|
};
|
|
14172
|
+
if (existingRecord.record.status === "completed_local_branch" && existingRecord.record.prdBranch) {
|
|
14173
|
+
result.prdBranch = existingRecord.record.prdBranch;
|
|
14174
|
+
}
|
|
14175
|
+
return result;
|
|
13771
14176
|
}
|
|
13772
14177
|
}
|
|
13773
14178
|
if (existingRecord.record) {
|
|
13774
14179
|
const status = existingRecord.record.status;
|
|
14180
|
+
const resolvedLaunchMode = options.config ? (() => {
|
|
14181
|
+
try {
|
|
14182
|
+
const target = resolveTarget(options.config, options.targetName);
|
|
14183
|
+
return resolvePrdRunMode(target).mode;
|
|
14184
|
+
} catch {
|
|
14185
|
+
return void 0;
|
|
14186
|
+
}
|
|
14187
|
+
})() : void 0;
|
|
14188
|
+
if (resolvedLaunchMode && existingRecord.record.mode && existingRecord.record.mode !== resolvedLaunchMode) {
|
|
14189
|
+
return {
|
|
14190
|
+
prdRef,
|
|
14191
|
+
status: "blocked",
|
|
14192
|
+
attempted: [],
|
|
14193
|
+
skipped: ["prepare", "start", "queue", "final-review", "reconcile"],
|
|
14194
|
+
resumed: [],
|
|
14195
|
+
diagnostics: [
|
|
14196
|
+
`Recorded mode: ${existingRecord.record.mode}`,
|
|
14197
|
+
`Resolved mode: ${resolvedLaunchMode}`
|
|
14198
|
+
],
|
|
14199
|
+
blockedGate: "branch-state",
|
|
14200
|
+
blockedReason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedLaunchMode}". Resolve by using the correct target or --local-override.`,
|
|
14201
|
+
offendingPaths: []
|
|
14202
|
+
};
|
|
14203
|
+
}
|
|
13775
14204
|
if (status === "drained") {
|
|
13776
14205
|
skipped.push("prepare", "start", "queue");
|
|
13777
14206
|
resumed.push("final-review");
|
|
@@ -13899,17 +14328,59 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13899
14328
|
}
|
|
13900
14329
|
diagnostics.push(...startResult.diagnostics);
|
|
13901
14330
|
attempted.push("queue");
|
|
14331
|
+
if (startResult.status === "drained" && options.issueProvider && existsSync17(join21(options.repoRoot, ".pourkit", "local-prd-runs", prdRef))) {
|
|
14332
|
+
await closeParentGitHubIssue(
|
|
14333
|
+
prdRef,
|
|
14334
|
+
options.repoRoot,
|
|
14335
|
+
options.issueProvider
|
|
14336
|
+
);
|
|
14337
|
+
}
|
|
13902
14338
|
}
|
|
13903
14339
|
let finalReviewResult;
|
|
13904
14340
|
if (!skipped.includes("final-review")) {
|
|
13905
14341
|
attempted.push("final-review");
|
|
13906
|
-
const
|
|
14342
|
+
const localStorePath2 = join21(
|
|
13907
14343
|
options.repoRoot,
|
|
13908
14344
|
".pourkit",
|
|
13909
14345
|
"local-prd-runs",
|
|
13910
14346
|
prdRef
|
|
13911
14347
|
);
|
|
13912
|
-
if (existsSync17(
|
|
14348
|
+
if (existsSync17(localStorePath2)) {
|
|
14349
|
+
const prdRecord = readPrdRun(options.repoRoot, prdRef);
|
|
14350
|
+
if (prdRecord.record && prdRecord.record.status !== "drained" && !canRetryFinalReviewBlock(prdRecord.record)) {
|
|
14351
|
+
const reason = `Final Review blocked: Queue drain not completed. Status is "${prdRecord.record.status}". Finish all child Issues before Final Review.`;
|
|
14352
|
+
writePrdRunRecord(options.repoRoot, {
|
|
14353
|
+
prdRef,
|
|
14354
|
+
status: "blocked",
|
|
14355
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14356
|
+
blockedGate: "final-review",
|
|
14357
|
+
blockedReason: reason,
|
|
14358
|
+
diagnostics: [
|
|
14359
|
+
`Current status: ${prdRecord.record.status}. Queue drain receipt is missing. Run child Issues through queue-run before Final Review.`
|
|
14360
|
+
],
|
|
14361
|
+
targetName: options.targetName,
|
|
14362
|
+
offendingPaths: []
|
|
14363
|
+
});
|
|
14364
|
+
return {
|
|
14365
|
+
prdRef,
|
|
14366
|
+
status: "blocked",
|
|
14367
|
+
attempted,
|
|
14368
|
+
skipped: [
|
|
14369
|
+
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
14370
|
+
...skipped.includes("start") ? ["queue"] : [],
|
|
14371
|
+
"reconcile"
|
|
14372
|
+
],
|
|
14373
|
+
resumed,
|
|
14374
|
+
diagnostics: [
|
|
14375
|
+
`Current status: ${prdRecord.record.status}. Queue drain receipt is missing. Run child Issues through queue-run before Final Review.`
|
|
14376
|
+
],
|
|
14377
|
+
blockedGate: "final-review",
|
|
14378
|
+
blockedReason: reason,
|
|
14379
|
+
offendingPaths: [],
|
|
14380
|
+
prepare: prepareResult,
|
|
14381
|
+
start: startResult
|
|
14382
|
+
};
|
|
14383
|
+
}
|
|
13913
14384
|
const targetConfig = options.config?.targets?.find(
|
|
13914
14385
|
(t) => t.name === options.targetName
|
|
13915
14386
|
);
|
|
@@ -13948,9 +14419,48 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13948
14419
|
start: startResult
|
|
13949
14420
|
};
|
|
13950
14421
|
}
|
|
14422
|
+
if (localResult.verdict === "blocked" || localResult.verdict === "needs_human_review") {
|
|
14423
|
+
const reason = localResult.message ?? `Local Final Review verdict: ${localResult.verdict}`;
|
|
14424
|
+
writePrdRunRecord(options.repoRoot, {
|
|
14425
|
+
prdRef,
|
|
14426
|
+
status: "blocked",
|
|
14427
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14428
|
+
blockedGate: "final-review",
|
|
14429
|
+
blockedReason: reason,
|
|
14430
|
+
diagnostics: [reason],
|
|
14431
|
+
targetName: options.targetName,
|
|
14432
|
+
offendingPaths: []
|
|
14433
|
+
});
|
|
14434
|
+
return {
|
|
14435
|
+
prdRef,
|
|
14436
|
+
status: "blocked",
|
|
14437
|
+
attempted,
|
|
14438
|
+
skipped: [
|
|
14439
|
+
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
14440
|
+
...skipped.includes("start") ? ["queue"] : [],
|
|
14441
|
+
"reconcile"
|
|
14442
|
+
],
|
|
14443
|
+
resumed,
|
|
14444
|
+
diagnostics: [reason],
|
|
14445
|
+
blockedGate: "final-review",
|
|
14446
|
+
blockedReason: reason,
|
|
14447
|
+
offendingPaths: [],
|
|
14448
|
+
prepare: prepareResult,
|
|
14449
|
+
start: startResult
|
|
14450
|
+
};
|
|
14451
|
+
}
|
|
14452
|
+
let retouchMergeCommit;
|
|
14453
|
+
let changedPaths;
|
|
13951
14454
|
if (localResult.verdict === "pass_with_retouch") {
|
|
13952
14455
|
try {
|
|
13953
|
-
await squashFinalReviewRetouch(
|
|
14456
|
+
const evidence = await squashFinalReviewRetouch(
|
|
14457
|
+
prdRef,
|
|
14458
|
+
options.repoRoot
|
|
14459
|
+
);
|
|
14460
|
+
if (evidence) {
|
|
14461
|
+
retouchMergeCommit = evidence.mergeCommit;
|
|
14462
|
+
changedPaths = evidence.changedPaths;
|
|
14463
|
+
}
|
|
13954
14464
|
} catch (error) {
|
|
13955
14465
|
const reason = error instanceof Error ? error.message : String(error);
|
|
13956
14466
|
writePrdRunRecord(options.repoRoot, {
|
|
@@ -13982,14 +14492,16 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13982
14492
|
};
|
|
13983
14493
|
}
|
|
13984
14494
|
}
|
|
13985
|
-
const mappedVerdict = localResult.verdict === "pass" ? "pass_no_changes" : localResult.verdict;
|
|
13986
14495
|
const receipt = {
|
|
13987
14496
|
status: "final_reviewed",
|
|
13988
14497
|
targetName: options.targetName,
|
|
13989
14498
|
prdBranch: `local/${prdRef}`,
|
|
14499
|
+
mergeBase: localResult.receipt?.mergeBase,
|
|
13990
14500
|
diagnostics: [],
|
|
13991
14501
|
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13992
|
-
verdict:
|
|
14502
|
+
verdict: localResult.verdict ?? "pass_no_changes",
|
|
14503
|
+
retouchMergeCommit,
|
|
14504
|
+
changedPaths
|
|
13993
14505
|
};
|
|
13994
14506
|
const existingRecord2 = readPrdRun(options.repoRoot, prdRef);
|
|
13995
14507
|
writePrdRunRecord(options.repoRoot, {
|
|
@@ -14064,17 +14576,100 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
14064
14576
|
let reconcileResult;
|
|
14065
14577
|
if (!skipped.includes("reconcile")) {
|
|
14066
14578
|
attempted.push("reconcile");
|
|
14067
|
-
|
|
14068
|
-
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
|
|
14579
|
+
const localStorePath2 = join21(
|
|
14580
|
+
options.repoRoot,
|
|
14581
|
+
".pourkit",
|
|
14582
|
+
"local-prd-runs",
|
|
14583
|
+
prdRef
|
|
14584
|
+
);
|
|
14585
|
+
if (existsSync17(localStorePath2)) {
|
|
14586
|
+
const prdRecord = readPrdRun(options.repoRoot, prdRef);
|
|
14587
|
+
const finalReviewReceipt = prdRecord.record?.finalReview;
|
|
14588
|
+
const hasCompletedFinalReview = finalReviewReceipt?.status === "final_reviewed" || finalReviewReceipt?.status === "succeeded";
|
|
14589
|
+
if (!hasCompletedFinalReview) {
|
|
14590
|
+
const reason = `Reconciliation blocked: Final Review not completed. Run Final Review before reconciliation.`;
|
|
14591
|
+
return {
|
|
14592
|
+
prdRef,
|
|
14593
|
+
status: "blocked",
|
|
14594
|
+
attempted,
|
|
14595
|
+
skipped: [...skipped],
|
|
14596
|
+
resumed,
|
|
14597
|
+
diagnostics: [reason],
|
|
14598
|
+
blockedGate: "reconciliation",
|
|
14599
|
+
blockedReason: reason,
|
|
14600
|
+
offendingPaths: [],
|
|
14601
|
+
prepare: prepareResult,
|
|
14602
|
+
start: startResult,
|
|
14603
|
+
finalReview: finalReviewResult
|
|
14604
|
+
};
|
|
14605
|
+
}
|
|
14606
|
+
const localResult = await runLocalReconciliation(prdRef, {
|
|
14607
|
+
repoRoot: options.repoRoot
|
|
14608
|
+
});
|
|
14609
|
+
if (!localResult.ok) {
|
|
14610
|
+
return {
|
|
14611
|
+
prdRef,
|
|
14612
|
+
status: "blocked",
|
|
14613
|
+
attempted,
|
|
14614
|
+
skipped: [...skipped],
|
|
14615
|
+
resumed,
|
|
14616
|
+
diagnostics: localResult.message ? [localResult.message] : [],
|
|
14617
|
+
blockedGate: "reconciliation",
|
|
14618
|
+
blockedReason: localResult.message ?? "Local reconciliation blocked.",
|
|
14619
|
+
offendingPaths: [],
|
|
14620
|
+
prepare: prepareResult,
|
|
14621
|
+
start: startResult,
|
|
14622
|
+
finalReview: finalReviewResult
|
|
14623
|
+
};
|
|
14624
|
+
}
|
|
14625
|
+
const receipt = {
|
|
14626
|
+
status: "succeeded",
|
|
14627
|
+
result: localResult.receipt?.result,
|
|
14628
|
+
changedPlanningPaths: localResult.receipt?.changedPlanningPaths,
|
|
14629
|
+
targetName: options.targetName,
|
|
14630
|
+
prdBranch: localResult.receipt?.prdBranch ?? `local/${prdRef}`,
|
|
14631
|
+
mergeCommit: localResult.receipt?.mergeCommit,
|
|
14632
|
+
reconciledAt: localResult.receipt?.reconciledTimestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
14633
|
+
autoMerge: false,
|
|
14634
|
+
agentIdentity: "pourkit-architect",
|
|
14635
|
+
noChange: localResult.receipt?.result === "no_changes_needed",
|
|
14636
|
+
source: "dev",
|
|
14637
|
+
target: localResult.receipt?.prdBranch ?? `local/${prdRef}`
|
|
14638
|
+
};
|
|
14639
|
+
const existingRecord2 = readPrdRun(options.repoRoot, prdRef);
|
|
14640
|
+
writePrdRunRecord(options.repoRoot, {
|
|
14641
|
+
prdRef,
|
|
14642
|
+
status: "completed_local_branch",
|
|
14643
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14644
|
+
targetName: options.targetName,
|
|
14645
|
+
prdBranch: `local/${prdRef}`,
|
|
14646
|
+
manifestPath: existingRecord2.record?.manifestPath,
|
|
14647
|
+
planning: existingRecord2.record?.planning,
|
|
14648
|
+
start: existingRecord2.record?.start,
|
|
14649
|
+
finalReview: existingRecord2.record?.finalReview,
|
|
14650
|
+
reconciliation: receipt,
|
|
14651
|
+
scopeChanges: existingRecord2.record?.scopeChanges
|
|
14652
|
+
});
|
|
14653
|
+
reconcileResult = {
|
|
14654
|
+
prdRef,
|
|
14655
|
+
status: "succeeded",
|
|
14656
|
+
reconciliation: receipt,
|
|
14657
|
+
diagnostics: []
|
|
14658
|
+
};
|
|
14659
|
+
diagnostics.push("Local reconciliation completed.");
|
|
14660
|
+
} else {
|
|
14661
|
+
reconcileResult = await runPrdRunReconcileCommand({
|
|
14662
|
+
repoRoot: options.repoRoot,
|
|
14663
|
+
prdRef,
|
|
14664
|
+
targetName: options.targetName,
|
|
14665
|
+
config: options.config,
|
|
14666
|
+
logger: options.logger,
|
|
14667
|
+
executionProvider: options.executionProvider,
|
|
14668
|
+
prProvider: options.prProvider,
|
|
14669
|
+
autoMerge: options.autoMerge
|
|
14670
|
+
});
|
|
14671
|
+
}
|
|
14672
|
+
if (reconcileResult && reconcileResult.status === "blocked") {
|
|
14078
14673
|
return {
|
|
14079
14674
|
prdRef,
|
|
14080
14675
|
status: "blocked",
|
|
@@ -14091,7 +14686,9 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
14091
14686
|
reconcile: reconcileResult
|
|
14092
14687
|
};
|
|
14093
14688
|
}
|
|
14094
|
-
|
|
14689
|
+
if (reconcileResult) {
|
|
14690
|
+
diagnostics.push(...reconcileResult.diagnostics);
|
|
14691
|
+
}
|
|
14095
14692
|
}
|
|
14096
14693
|
const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
14097
14694
|
const reconciliationReceipt = reconcileResult && reconcileResult.status === "succeeded" ? reconcileResult.reconciliation : currentRecord?.reconciliation;
|
|
@@ -14174,6 +14771,29 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
14174
14771
|
reconcile: reconcileResult
|
|
14175
14772
|
};
|
|
14176
14773
|
}
|
|
14774
|
+
const localStorePath = join21(
|
|
14775
|
+
options.repoRoot,
|
|
14776
|
+
".pourkit",
|
|
14777
|
+
"local-prd-runs",
|
|
14778
|
+
prdRef
|
|
14779
|
+
);
|
|
14780
|
+
if (existsSync17(localStorePath)) {
|
|
14781
|
+
return {
|
|
14782
|
+
prdRef,
|
|
14783
|
+
status: "completed_local_branch",
|
|
14784
|
+
prdBranch: `local/${prdRef}`,
|
|
14785
|
+
attempted,
|
|
14786
|
+
skipped,
|
|
14787
|
+
resumed,
|
|
14788
|
+
diagnostics: [
|
|
14789
|
+
`Local PRD Run ${prdRef} completed. Branch: local/${prdRef}.`
|
|
14790
|
+
],
|
|
14791
|
+
prepare: prepareResult,
|
|
14792
|
+
start: startResult,
|
|
14793
|
+
finalReview: finalReviewResult,
|
|
14794
|
+
reconcile: reconcileResult
|
|
14795
|
+
};
|
|
14796
|
+
}
|
|
14177
14797
|
writePrdRunRecord(options.repoRoot, {
|
|
14178
14798
|
prdRef,
|
|
14179
14799
|
status: "waiting_for_integration",
|
|
@@ -14503,7 +15123,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
|
|
|
14503
15123
|
const branch = `local/${normalizedRef}`;
|
|
14504
15124
|
const branchExists = (() => {
|
|
14505
15125
|
try {
|
|
14506
|
-
return
|
|
15126
|
+
return spawnSync3("git", ["rev-parse", "--verify", "--quiet", branch], {
|
|
14507
15127
|
cwd: root,
|
|
14508
15128
|
encoding: "utf8",
|
|
14509
15129
|
stdio: "pipe"
|
|
@@ -14523,7 +15143,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
|
|
|
14523
15143
|
message: `Branch ${branch} already exists without a matching start receipt. Remove it manually or use a different PRD ID.`
|
|
14524
15144
|
};
|
|
14525
15145
|
}
|
|
14526
|
-
const branchResult =
|
|
15146
|
+
const branchResult = spawnSync3("git", ["branch", branch], {
|
|
14527
15147
|
cwd: root,
|
|
14528
15148
|
encoding: "utf8",
|
|
14529
15149
|
stdio: "pipe"
|
|
@@ -14538,7 +15158,7 @@ async function runLocalStartCommand(prdId, repoRoot2) {
|
|
|
14538
15158
|
}
|
|
14539
15159
|
const baseCommit = (() => {
|
|
14540
15160
|
try {
|
|
14541
|
-
return
|
|
15161
|
+
return spawnSync3("git", ["rev-parse", "HEAD"], {
|
|
14542
15162
|
cwd: root,
|
|
14543
15163
|
encoding: "utf8",
|
|
14544
15164
|
stdio: "pipe"
|
|
@@ -14660,6 +15280,17 @@ async function runLocalLaunchCommand(prdId, repoRoot2) {
|
|
|
14660
15280
|
};
|
|
14661
15281
|
return { ok: false, failedStage: "finalReview", stages };
|
|
14662
15282
|
}
|
|
15283
|
+
if (result.verdict === "blocked" || result.verdict === "needs_human_review") {
|
|
15284
|
+
stages.finalReview = {
|
|
15285
|
+
ok: false,
|
|
15286
|
+
stage: "finalReview",
|
|
15287
|
+
failureCode: result.verdict === "blocked" ? "final_review_blocked" : "needs_human_review",
|
|
15288
|
+
repairabilityClass: "manual",
|
|
15289
|
+
reason: result.message ?? `Final Review verdict: ${result.verdict}`,
|
|
15290
|
+
repairGuidance: `Fix final review failures and rerun \`pourkit prd-run launch --local ${normalizedRef}\`.`
|
|
15291
|
+
};
|
|
15292
|
+
return { ok: false, failedStage: "finalReview", stages };
|
|
15293
|
+
}
|
|
14663
15294
|
if (result.verdict === "pass_with_retouch") {
|
|
14664
15295
|
try {
|
|
14665
15296
|
await squashFinalReviewRetouch(normalizedRef, root);
|
|
@@ -14779,6 +15410,32 @@ async function runPrdRunStartCommand(options) {
|
|
|
14779
15410
|
);
|
|
14780
15411
|
}
|
|
14781
15412
|
const existingRecord = readPrdRun(options.repoRoot, prdRef);
|
|
15413
|
+
const resolvedMode = options.config ? (() => {
|
|
15414
|
+
try {
|
|
15415
|
+
const target = resolveTarget(options.config, targetName);
|
|
15416
|
+
return resolvePrdRunMode(target).mode;
|
|
15417
|
+
} catch {
|
|
15418
|
+
return void 0;
|
|
15419
|
+
}
|
|
15420
|
+
})() : void 0;
|
|
15421
|
+
if (resolvedMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedMode) {
|
|
15422
|
+
const failure = {
|
|
15423
|
+
ok: false,
|
|
15424
|
+
gate: "branch-state",
|
|
15425
|
+
reason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedMode}". Resolve by using the correct target or --local-override.`,
|
|
15426
|
+
diagnostics: [
|
|
15427
|
+
`Recorded mode: ${existingRecord.record.mode}`,
|
|
15428
|
+
`Resolved mode: ${resolvedMode}`
|
|
15429
|
+
],
|
|
15430
|
+
offendingPaths: []
|
|
15431
|
+
};
|
|
15432
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
15433
|
+
manifestPath: existingRecord.record?.manifestPath,
|
|
15434
|
+
planning: existingRecord.record?.planning,
|
|
15435
|
+
targetName
|
|
15436
|
+
});
|
|
15437
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
15438
|
+
}
|
|
14782
15439
|
if (existingRecord.record?.start) {
|
|
14783
15440
|
const fetchResult2 = fetchOriginDev(options.repoRoot);
|
|
14784
15441
|
if (!fetchResult2.ok) {
|
|
@@ -14814,7 +15471,8 @@ async function runPrdRunStartCommand(options) {
|
|
|
14814
15471
|
...reusedStart
|
|
14815
15472
|
},
|
|
14816
15473
|
{
|
|
14817
|
-
targetName
|
|
15474
|
+
targetName,
|
|
15475
|
+
mode: resolvedMode
|
|
14818
15476
|
}
|
|
14819
15477
|
);
|
|
14820
15478
|
return await processStartResult(
|
|
@@ -14917,6 +15575,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
14917
15575
|
},
|
|
14918
15576
|
{
|
|
14919
15577
|
targetName,
|
|
15578
|
+
mode: resolvedMode,
|
|
14920
15579
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14921
15580
|
planning: existingRecord.record?.planning
|
|
14922
15581
|
}
|
|
@@ -15076,6 +15735,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
15076
15735
|
},
|
|
15077
15736
|
{
|
|
15078
15737
|
targetName,
|
|
15738
|
+
mode: resolvedMode,
|
|
15079
15739
|
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
|
|
15080
15740
|
planning: existingRecord.record?.planning
|
|
15081
15741
|
}
|
|
@@ -15158,6 +15818,68 @@ async function runPrdRunStartCommand(options) {
|
|
|
15158
15818
|
});
|
|
15159
15819
|
return buildBlockedStartResult(prdRef, failure);
|
|
15160
15820
|
}
|
|
15821
|
+
if (resolvedMode === "local") {
|
|
15822
|
+
const localBranchResult = materializeLocalPrdBranch(
|
|
15823
|
+
prdRef,
|
|
15824
|
+
fetchResult.startBaseCommit,
|
|
15825
|
+
options.repoRoot
|
|
15826
|
+
);
|
|
15827
|
+
if (!localBranchResult.ok) {
|
|
15828
|
+
const failure = {
|
|
15829
|
+
ok: false,
|
|
15830
|
+
gate: "branch-state",
|
|
15831
|
+
reason: localBranchResult.message,
|
|
15832
|
+
diagnostics: [localBranchResult.message],
|
|
15833
|
+
offendingPaths: []
|
|
15834
|
+
};
|
|
15835
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
15836
|
+
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
|
|
15837
|
+
planning: existingRecord.record?.planning,
|
|
15838
|
+
targetName
|
|
15839
|
+
});
|
|
15840
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
15841
|
+
}
|
|
15842
|
+
const localStoreDir = join21(
|
|
15843
|
+
options.repoRoot,
|
|
15844
|
+
".pourkit",
|
|
15845
|
+
"local-prd-runs",
|
|
15846
|
+
prdRef
|
|
15847
|
+
);
|
|
15848
|
+
let localStoreReady = false;
|
|
15849
|
+
if (existsSync17(localStoreDir)) {
|
|
15850
|
+
const localStorePath = join21(localStoreDir, "prd.json");
|
|
15851
|
+
try {
|
|
15852
|
+
const content = JSON.parse(readFileSync17(localStorePath, "utf8"));
|
|
15853
|
+
localStoreReady = content?.id === prdRef && content?.kind === "prd";
|
|
15854
|
+
} catch {
|
|
15855
|
+
localStoreReady = false;
|
|
15856
|
+
}
|
|
15857
|
+
}
|
|
15858
|
+
const prereqs = {
|
|
15859
|
+
localBranchName: getLocalPrdBranchName(prdRef),
|
|
15860
|
+
localBranchExists: !localBranchResult.created,
|
|
15861
|
+
localBranchCommit: fetchResult.startBaseCommit,
|
|
15862
|
+
localStoreReady
|
|
15863
|
+
};
|
|
15864
|
+
if (existsSync17(localStoreDir) && !prereqs.localStoreReady) {
|
|
15865
|
+
const failure = {
|
|
15866
|
+
ok: false,
|
|
15867
|
+
gate: "branch-state",
|
|
15868
|
+
reason: `Local PRD Run Store not ready for ${prdRef}. Expected valid prd.json at .pourkit/local-prd-runs/${prdRef}/prd.json with matching PRD ID. Ensure Local PRD Run Store is initialized before starting.`,
|
|
15869
|
+
diagnostics: [
|
|
15870
|
+
`Expected store path: .pourkit/local-prd-runs/${prdRef}/prd.json`,
|
|
15871
|
+
`Expected PRD ID: ${prdRef}`
|
|
15872
|
+
],
|
|
15873
|
+
offendingPaths: []
|
|
15874
|
+
};
|
|
15875
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
15876
|
+
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath,
|
|
15877
|
+
planning: existingRecord.record?.planning,
|
|
15878
|
+
targetName
|
|
15879
|
+
});
|
|
15880
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
15881
|
+
}
|
|
15882
|
+
}
|
|
15161
15883
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15162
15884
|
const start = {
|
|
15163
15885
|
status: "started",
|
|
@@ -15185,6 +15907,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
15185
15907
|
updatedAt,
|
|
15186
15908
|
targetName,
|
|
15187
15909
|
start,
|
|
15910
|
+
mode: resolvedMode,
|
|
15188
15911
|
manifestPath: existingRecord.record?.manifestPath ?? manifest?.manifestPath ?? void 0,
|
|
15189
15912
|
planning: existingRecord.record?.planning
|
|
15190
15913
|
});
|
|
@@ -15215,26 +15938,93 @@ async function processStartResult(startResult, options) {
|
|
|
15215
15938
|
} catch {
|
|
15216
15939
|
resolvedMode = void 0;
|
|
15217
15940
|
}
|
|
15941
|
+
const modeForRecord = resolvedMode?.mode;
|
|
15218
15942
|
writePrdRunRecord(repoRoot2, {
|
|
15219
15943
|
prdRef,
|
|
15220
15944
|
status: "running",
|
|
15221
15945
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15222
15946
|
targetName: start.targetName,
|
|
15223
15947
|
start,
|
|
15948
|
+
mode: modeForRecord,
|
|
15224
15949
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15225
15950
|
planning: existingRecord.record?.planning
|
|
15226
15951
|
});
|
|
15227
|
-
const localStorePath =
|
|
15952
|
+
const localStorePath = join21(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
|
|
15228
15953
|
if (existsSync17(localStorePath)) {
|
|
15229
|
-
const
|
|
15954
|
+
const queueResult = await runLocalQueueLoop(
|
|
15955
|
+
prdRef,
|
|
15956
|
+
repoRoot2,
|
|
15957
|
+
options.issueProvider
|
|
15958
|
+
);
|
|
15959
|
+
if (!queueResult.ok) {
|
|
15960
|
+
const failureCode = queueResult.failureCode ?? "queue_error";
|
|
15961
|
+
const reason = queueResult.repairGuidance ?? `Local queue loop blocked: ${failureCode}`;
|
|
15962
|
+
const diagnostics = [
|
|
15963
|
+
`Local queue loop failed with failureCode "${failureCode}".`,
|
|
15964
|
+
...queueResult.repairGuidance ? [`Repair guidance: ${queueResult.repairGuidance}`] : []
|
|
15965
|
+
];
|
|
15966
|
+
writePrdRunRecord(repoRoot2, {
|
|
15967
|
+
prdRef,
|
|
15968
|
+
status: "blocked",
|
|
15969
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15970
|
+
targetName: start.targetName,
|
|
15971
|
+
start,
|
|
15972
|
+
mode: modeForRecord,
|
|
15973
|
+
manifestPath: existingRecord.record?.manifestPath,
|
|
15974
|
+
planning: existingRecord.record?.planning,
|
|
15975
|
+
blockedGate: "queue",
|
|
15976
|
+
blockedReason: reason,
|
|
15977
|
+
diagnostics,
|
|
15978
|
+
offendingPaths: []
|
|
15979
|
+
});
|
|
15980
|
+
return {
|
|
15981
|
+
prdRef,
|
|
15982
|
+
status: "blocked",
|
|
15983
|
+
blockedGate: "queue",
|
|
15984
|
+
blockedReason: reason,
|
|
15985
|
+
diagnostics,
|
|
15986
|
+
offendingPaths: []
|
|
15987
|
+
};
|
|
15988
|
+
}
|
|
15989
|
+
if (queueResult.blockedIssues.length > 0) {
|
|
15990
|
+
const reason = `Queue processed complete but ${queueResult.blockedIssues.length} blocked child issue(s) remain.`;
|
|
15991
|
+
const diagnostics = [
|
|
15992
|
+
reason,
|
|
15993
|
+
`Blocked issues: ${queueResult.blockedIssues.join(", ")}`,
|
|
15994
|
+
"Resolve blocked children before parent close."
|
|
15995
|
+
];
|
|
15996
|
+
writePrdRunRecord(repoRoot2, {
|
|
15997
|
+
prdRef,
|
|
15998
|
+
status: "blocked",
|
|
15999
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16000
|
+
targetName: start.targetName,
|
|
16001
|
+
start,
|
|
16002
|
+
mode: modeForRecord,
|
|
16003
|
+
manifestPath: existingRecord.record?.manifestPath,
|
|
16004
|
+
planning: existingRecord.record?.planning,
|
|
16005
|
+
blockedGate: "queue",
|
|
16006
|
+
blockedReason: reason,
|
|
16007
|
+
diagnostics,
|
|
16008
|
+
offendingPaths: []
|
|
16009
|
+
});
|
|
16010
|
+
return {
|
|
16011
|
+
prdRef,
|
|
16012
|
+
status: "blocked",
|
|
16013
|
+
blockedGate: "queue",
|
|
16014
|
+
blockedReason: reason,
|
|
16015
|
+
diagnostics,
|
|
16016
|
+
offendingPaths: []
|
|
16017
|
+
};
|
|
16018
|
+
}
|
|
15230
16019
|
start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15231
|
-
start.queueProcessedCount =
|
|
16020
|
+
start.queueProcessedCount = queueResult.completedIssues.length + queueResult.blockedIssues.length;
|
|
15232
16021
|
writePrdRunRecord(repoRoot2, {
|
|
15233
16022
|
prdRef,
|
|
15234
16023
|
status: "drained",
|
|
15235
16024
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15236
16025
|
targetName: start.targetName,
|
|
15237
16026
|
start,
|
|
16027
|
+
mode: modeForRecord,
|
|
15238
16028
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15239
16029
|
planning: existingRecord.record?.planning
|
|
15240
16030
|
});
|
|
@@ -15277,6 +16067,7 @@ async function processStartResult(startResult, options) {
|
|
|
15277
16067
|
diagnostics,
|
|
15278
16068
|
targetName: start.targetName,
|
|
15279
16069
|
start,
|
|
16070
|
+
mode: existingRecord.record?.mode,
|
|
15280
16071
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15281
16072
|
planning: existingRecord.record?.planning,
|
|
15282
16073
|
offendingPaths: []
|
|
@@ -15298,6 +16089,7 @@ async function processStartResult(startResult, options) {
|
|
|
15298
16089
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15299
16090
|
targetName: start.targetName,
|
|
15300
16091
|
start,
|
|
16092
|
+
mode: existingRecord.record?.mode,
|
|
15301
16093
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15302
16094
|
planning: existingRecord.record?.planning
|
|
15303
16095
|
});
|
|
@@ -15319,6 +16111,7 @@ async function processStartResult(startResult, options) {
|
|
|
15319
16111
|
diagnostics,
|
|
15320
16112
|
targetName: start.targetName,
|
|
15321
16113
|
start,
|
|
16114
|
+
mode: existingRecord.record?.mode,
|
|
15322
16115
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15323
16116
|
planning: existingRecord.record?.planning,
|
|
15324
16117
|
offendingPaths: []
|
|
@@ -15343,6 +16136,7 @@ async function processStartResult(startResult, options) {
|
|
|
15343
16136
|
diagnostics: outcomeDiagnostics,
|
|
15344
16137
|
targetName: start.targetName,
|
|
15345
16138
|
start,
|
|
16139
|
+
mode: existingRecord.record?.mode,
|
|
15346
16140
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15347
16141
|
planning: existingRecord.record?.planning,
|
|
15348
16142
|
offendingPaths: []
|
|
@@ -15367,6 +16161,7 @@ async function processStartResult(startResult, options) {
|
|
|
15367
16161
|
diagnostics,
|
|
15368
16162
|
targetName: start.targetName,
|
|
15369
16163
|
start,
|
|
16164
|
+
mode: existingRecord.record?.mode,
|
|
15370
16165
|
manifestPath: existingRecord.record?.manifestPath,
|
|
15371
16166
|
planning: existingRecord.record?.planning,
|
|
15372
16167
|
offendingPaths: []
|
|
@@ -15398,12 +16193,13 @@ function persistStartingPrdRunRecord(repoRoot2, prdRef, existingRecord, start, c
|
|
|
15398
16193
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15399
16194
|
targetName: context.targetName,
|
|
15400
16195
|
start,
|
|
16196
|
+
mode: context.mode,
|
|
15401
16197
|
manifestPath: context.manifestPath ?? existingRecord.record?.manifestPath,
|
|
15402
16198
|
planning: context.planning ?? existingRecord.record?.planning
|
|
15403
16199
|
});
|
|
15404
16200
|
}
|
|
15405
16201
|
function inspectRemotePrdBranch(repoRoot2, prdRef) {
|
|
15406
|
-
const result =
|
|
16202
|
+
const result = spawnSync3("git", ["ls-remote", "--heads", "origin", prdRef], {
|
|
15407
16203
|
cwd: repoRoot2,
|
|
15408
16204
|
encoding: "utf8"
|
|
15409
16205
|
});
|
|
@@ -15428,7 +16224,7 @@ function inspectRemotePrdBranch(repoRoot2, prdRef) {
|
|
|
15428
16224
|
}
|
|
15429
16225
|
function ensureAdoptableExistingPrdBranch(repoRoot2, prdRef, prepareMergeCommit) {
|
|
15430
16226
|
const branchRef = `refs/remotes/origin/${prdRef}`;
|
|
15431
|
-
const result =
|
|
16227
|
+
const result = spawnSync3(
|
|
15432
16228
|
"git",
|
|
15433
16229
|
["merge-base", "--is-ancestor", prepareMergeCommit, branchRef],
|
|
15434
16230
|
{
|
|
@@ -15558,7 +16354,7 @@ async function createOrReusePlanningPr(options) {
|
|
|
15558
16354
|
});
|
|
15559
16355
|
}
|
|
15560
16356
|
function fetchOriginDev(repoRoot2) {
|
|
15561
|
-
const fetchResult =
|
|
16357
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
|
|
15562
16358
|
cwd: repoRoot2,
|
|
15563
16359
|
encoding: "utf8"
|
|
15564
16360
|
});
|
|
@@ -15575,7 +16371,7 @@ function fetchOriginDev(repoRoot2) {
|
|
|
15575
16371
|
offendingPaths: []
|
|
15576
16372
|
};
|
|
15577
16373
|
}
|
|
15578
|
-
const revParseResult =
|
|
16374
|
+
const revParseResult = spawnSync3("git", ["rev-parse", "origin/dev"], {
|
|
15579
16375
|
cwd: repoRoot2,
|
|
15580
16376
|
encoding: "utf8"
|
|
15581
16377
|
});
|
|
@@ -15595,7 +16391,7 @@ function fetchOriginDev(repoRoot2) {
|
|
|
15595
16391
|
return { ok: true, startBaseCommit: revParseResult.stdout.trim() };
|
|
15596
16392
|
}
|
|
15597
16393
|
function fetchPrdBranch(repoRoot2, prdRef) {
|
|
15598
|
-
const result =
|
|
16394
|
+
const result = spawnSync3("git", ["fetch", "origin", prdRef], {
|
|
15599
16395
|
cwd: repoRoot2,
|
|
15600
16396
|
encoding: "utf8"
|
|
15601
16397
|
});
|
|
@@ -15615,7 +16411,7 @@ function fetchPrdBranch(repoRoot2, prdRef) {
|
|
|
15615
16411
|
return { ok: true };
|
|
15616
16412
|
}
|
|
15617
16413
|
function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startBaseCommit) {
|
|
15618
|
-
const result =
|
|
16414
|
+
const result = spawnSync3(
|
|
15619
16415
|
"git",
|
|
15620
16416
|
["merge-base", "--is-ancestor", prepareMergeCommit, startBaseRef],
|
|
15621
16417
|
{
|
|
@@ -15642,7 +16438,7 @@ function ensureAncestorGuard(repoRoot2, prepareMergeCommit, startBaseRef, startB
|
|
|
15642
16438
|
return { ok: true };
|
|
15643
16439
|
}
|
|
15644
16440
|
async function ensurePrdBranchPublished(repoRoot2, prdRef, startBaseCommit) {
|
|
15645
|
-
const pushResult =
|
|
16441
|
+
const pushResult = spawnSync3(
|
|
15646
16442
|
"git",
|
|
15647
16443
|
["push", "origin", `${startBaseCommit}:refs/heads/${prdRef}`],
|
|
15648
16444
|
{
|
|
@@ -15695,7 +16491,7 @@ function buildPlanningPrBody(options) {
|
|
|
15695
16491
|
].join("\n");
|
|
15696
16492
|
}
|
|
15697
16493
|
function runGitOrThrow(cwd, args, label) {
|
|
15698
|
-
const result =
|
|
16494
|
+
const result = spawnSync3("git", args, { cwd, encoding: "utf8" });
|
|
15699
16495
|
if (result.status !== 0) {
|
|
15700
16496
|
throw new Error(
|
|
15701
16497
|
`Failed to ${label}: ${[
|
|
@@ -15707,7 +16503,7 @@ function runGitOrThrow(cwd, args, label) {
|
|
|
15707
16503
|
}
|
|
15708
16504
|
}
|
|
15709
16505
|
function validatePrepareAutoMergeDevCheckout(repoRoot2) {
|
|
15710
|
-
const branchResult =
|
|
16506
|
+
const branchResult = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
15711
16507
|
cwd: repoRoot2,
|
|
15712
16508
|
encoding: "utf8"
|
|
15713
16509
|
});
|
|
@@ -15738,7 +16534,7 @@ function validatePrepareAutoMergeDevCheckout(repoRoot2) {
|
|
|
15738
16534
|
function syncLocalDevToOriginDev(repoRoot2) {
|
|
15739
16535
|
const branchResult = validatePrepareAutoMergeDevCheckout(repoRoot2);
|
|
15740
16536
|
if (!branchResult.ok) return branchResult;
|
|
15741
|
-
const fetchResult =
|
|
16537
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", "dev"], {
|
|
15742
16538
|
cwd: repoRoot2,
|
|
15743
16539
|
encoding: "utf8"
|
|
15744
16540
|
});
|
|
@@ -15754,7 +16550,7 @@ function syncLocalDevToOriginDev(repoRoot2) {
|
|
|
15754
16550
|
offendingPaths: []
|
|
15755
16551
|
};
|
|
15756
16552
|
}
|
|
15757
|
-
const resetResult =
|
|
16553
|
+
const resetResult = spawnSync3("git", ["reset", "--hard", "origin/dev"], {
|
|
15758
16554
|
cwd: repoRoot2,
|
|
15759
16555
|
encoding: "utf8"
|
|
15760
16556
|
});
|
|
@@ -15798,14 +16594,14 @@ function isArchitecturePlanningDiffPath(path9) {
|
|
|
15798
16594
|
return path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/");
|
|
15799
16595
|
}
|
|
15800
16596
|
async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles) {
|
|
15801
|
-
const remoteResult =
|
|
16597
|
+
const remoteResult = spawnSync3("git", ["remote", "get-url", "origin"], {
|
|
15802
16598
|
cwd: repoRoot2,
|
|
15803
16599
|
encoding: "utf8"
|
|
15804
16600
|
});
|
|
15805
16601
|
if (remoteResult.status !== 0) {
|
|
15806
16602
|
return;
|
|
15807
16603
|
}
|
|
15808
|
-
const worktreePath = mkdtempSync(
|
|
16604
|
+
const worktreePath = mkdtempSync(join21(tmpdir(), "pourkit-planning-"));
|
|
15809
16605
|
try {
|
|
15810
16606
|
runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
|
|
15811
16607
|
runGitOrThrow(
|
|
@@ -15819,8 +16615,8 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15819
16615
|
"create planning branch"
|
|
15820
16616
|
);
|
|
15821
16617
|
for (const changedFile of changedFiles) {
|
|
15822
|
-
const sourcePath =
|
|
15823
|
-
const targetPath =
|
|
16618
|
+
const sourcePath = join21(repoRoot2, changedFile);
|
|
16619
|
+
const targetPath = join21(worktreePath, changedFile);
|
|
15824
16620
|
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
15825
16621
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
15826
16622
|
}
|
|
@@ -15835,13 +16631,13 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15835
16631
|
"commit planning diff"
|
|
15836
16632
|
);
|
|
15837
16633
|
} finally {
|
|
15838
|
-
|
|
16634
|
+
spawnSync3("git", ["worktree", "remove", "--force", worktreePath], {
|
|
15839
16635
|
cwd: repoRoot2,
|
|
15840
16636
|
encoding: "utf8"
|
|
15841
16637
|
});
|
|
15842
16638
|
rmSync3(worktreePath, { recursive: true, force: true });
|
|
15843
16639
|
}
|
|
15844
|
-
const pushResult =
|
|
16640
|
+
const pushResult = spawnSync3(
|
|
15845
16641
|
"git",
|
|
15846
16642
|
["push", "--force", "origin", `${branchName}:refs/heads/${branchName}`],
|
|
15847
16643
|
{
|
|
@@ -15977,7 +16773,7 @@ function validateStartPrepareReceipt(prdRef, record, options = {}) {
|
|
|
15977
16773
|
function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
|
|
15978
16774
|
for (const artifactPath of listManifestArtifactPaths(repoRoot2, manifest)) {
|
|
15979
16775
|
const repoRelativePath = toRepoRelativePath2(repoRoot2, artifactPath);
|
|
15980
|
-
const result =
|
|
16776
|
+
const result = spawnSync3(
|
|
15981
16777
|
"git",
|
|
15982
16778
|
["show", `origin/dev:${repoRelativePath}`],
|
|
15983
16779
|
{
|
|
@@ -16003,7 +16799,7 @@ function validateStartArtifactExistenceOnOriginDev(repoRoot2, manifest) {
|
|
|
16003
16799
|
return { ok: true };
|
|
16004
16800
|
}
|
|
16005
16801
|
function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
|
|
16006
|
-
const result =
|
|
16802
|
+
const result = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
|
|
16007
16803
|
cwd: repoRoot2,
|
|
16008
16804
|
encoding: "utf8"
|
|
16009
16805
|
});
|
|
@@ -16058,11 +16854,11 @@ function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
|
|
|
16058
16854
|
};
|
|
16059
16855
|
}
|
|
16060
16856
|
function collectObservedReconciliationDirtyPaths(options) {
|
|
16061
|
-
const statusResult =
|
|
16857
|
+
const statusResult = spawnSync3("git", ["status", "--porcelain=v1", "-uall"], {
|
|
16062
16858
|
cwd: options.worktreeCwd,
|
|
16063
16859
|
encoding: "utf8"
|
|
16064
16860
|
});
|
|
16065
|
-
const diffResult =
|
|
16861
|
+
const diffResult = spawnSync3("git", ["diff", "--name-status"], {
|
|
16066
16862
|
cwd: options.worktreeCwd,
|
|
16067
16863
|
encoding: "utf8"
|
|
16068
16864
|
});
|
|
@@ -16096,13 +16892,13 @@ function collectObservedReconciliationDirtyPaths(options) {
|
|
|
16096
16892
|
};
|
|
16097
16893
|
}
|
|
16098
16894
|
function gitRefExists(repoRoot2, ref) {
|
|
16099
|
-
return
|
|
16895
|
+
return spawnSync3("git", ["rev-parse", "--verify", "--quiet", ref], {
|
|
16100
16896
|
cwd: repoRoot2,
|
|
16101
16897
|
encoding: "utf8"
|
|
16102
16898
|
}).status === 0;
|
|
16103
16899
|
}
|
|
16104
16900
|
function collectGitDiffNames(repoRoot2, baseRef, headRef) {
|
|
16105
|
-
const result =
|
|
16901
|
+
const result = spawnSync3("git", ["diff", "--name-only", baseRef, headRef], {
|
|
16106
16902
|
cwd: repoRoot2,
|
|
16107
16903
|
encoding: "utf8"
|
|
16108
16904
|
});
|
|
@@ -19163,8 +19959,8 @@ function formatChecks2(checks) {
|
|
|
19163
19959
|
init_common();
|
|
19164
19960
|
|
|
19165
19961
|
// execution/sandcastle-execution.ts
|
|
19166
|
-
import { mkdirSync as mkdirSync12, writeFileSync as
|
|
19167
|
-
import { join as
|
|
19962
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
19963
|
+
import { join as join23 } from "path";
|
|
19168
19964
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
19169
19965
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
19170
19966
|
|
|
@@ -19173,10 +19969,10 @@ init_common();
|
|
|
19173
19969
|
import { mkdtempSync as mkdtempSync2 } from "fs";
|
|
19174
19970
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
19175
19971
|
import { tmpdir as tmpdir2 } from "os";
|
|
19176
|
-
import { dirname as dirname6, join as
|
|
19972
|
+
import { dirname as dirname6, join as join22 } from "path";
|
|
19177
19973
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
19178
19974
|
for (const artifact of artifacts) {
|
|
19179
|
-
const filePath =
|
|
19975
|
+
const filePath = join22(worktreePath, artifact.path);
|
|
19180
19976
|
await ensureDir(dirname6(filePath));
|
|
19181
19977
|
await writeFile3(filePath, artifact.content, "utf-8");
|
|
19182
19978
|
}
|
|
@@ -19188,7 +19984,7 @@ import path7 from "path";
|
|
|
19188
19984
|
|
|
19189
19985
|
// execution/sandbox-image.ts
|
|
19190
19986
|
import { createHash as createHash4 } from "crypto";
|
|
19191
|
-
import { existsSync as existsSync19, readFileSync as
|
|
19987
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
19192
19988
|
import path6 from "path";
|
|
19193
19989
|
function sandboxImageName(repoRoot2) {
|
|
19194
19990
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
@@ -19198,7 +19994,7 @@ function sandboxImageName(repoRoot2) {
|
|
|
19198
19994
|
if (!existsSync19(dockerfilePath)) {
|
|
19199
19995
|
return `sandcastle:${baseName}`;
|
|
19200
19996
|
}
|
|
19201
|
-
const fingerprint = createHash4("sha256").update(
|
|
19997
|
+
const fingerprint = createHash4("sha256").update(readFileSync18(dockerfilePath)).digest("hex").slice(0, 8);
|
|
19202
19998
|
return `sandcastle:${baseName}-${fingerprint}`;
|
|
19203
19999
|
}
|
|
19204
20000
|
|
|
@@ -19458,17 +20254,32 @@ function parseToolCallArgs(formattedArgs) {
|
|
|
19458
20254
|
return { ok: false };
|
|
19459
20255
|
}
|
|
19460
20256
|
}
|
|
20257
|
+
var SUMMARIZED_TOOL_FIELDS = {
|
|
20258
|
+
write: { content: "contentCount" },
|
|
20259
|
+
edit: { oldString: "oldCount", newString: "newCount" }
|
|
20260
|
+
};
|
|
20261
|
+
var SUMMARIZED_TOOL_ARRAY_FIELDS = {
|
|
20262
|
+
todowrite: { todos: "todoCount" }
|
|
20263
|
+
};
|
|
19461
20264
|
function summarizeToolCallArgs(name, args) {
|
|
19462
20265
|
if (!isPlainObject(args)) {
|
|
19463
20266
|
return args;
|
|
19464
20267
|
}
|
|
19465
|
-
|
|
20268
|
+
const fields = SUMMARIZED_TOOL_FIELDS[name.toLowerCase()];
|
|
20269
|
+
const arrayFields = SUMMARIZED_TOOL_ARRAY_FIELDS[name.toLowerCase()];
|
|
20270
|
+
if (!fields && !arrayFields) {
|
|
19466
20271
|
return args;
|
|
19467
20272
|
}
|
|
19468
20273
|
const summarizedArgs = {};
|
|
19469
20274
|
for (const [key, value] of Object.entries(args)) {
|
|
19470
|
-
|
|
19471
|
-
|
|
20275
|
+
const mappedKey = fields?.[key];
|
|
20276
|
+
if (mappedKey && typeof value === "string") {
|
|
20277
|
+
summarizedArgs[mappedKey] = value.length;
|
|
20278
|
+
continue;
|
|
20279
|
+
}
|
|
20280
|
+
const mappedArrayKey = arrayFields?.[key];
|
|
20281
|
+
if (mappedArrayKey && Array.isArray(value)) {
|
|
20282
|
+
summarizedArgs[mappedArrayKey] = value.length;
|
|
19472
20283
|
continue;
|
|
19473
20284
|
}
|
|
19474
20285
|
summarizedArgs[key] = value;
|
|
@@ -19479,13 +20290,13 @@ function isPlainObject(value) {
|
|
|
19479
20290
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19480
20291
|
}
|
|
19481
20292
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
19482
|
-
const promptsDir =
|
|
20293
|
+
const promptsDir = join23(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
19483
20294
|
mkdirSync12(promptsDir, { recursive: true });
|
|
19484
20295
|
const timestamp2 = Date.now();
|
|
19485
20296
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
19486
20297
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
19487
|
-
const filePath =
|
|
19488
|
-
|
|
20298
|
+
const filePath = join23(promptsDir, filename);
|
|
20299
|
+
writeFileSync9(filePath, prompt, "utf-8");
|
|
19489
20300
|
}
|
|
19490
20301
|
|
|
19491
20302
|
// cli.ts
|
|
@@ -20358,11 +21169,11 @@ function createCliProgram(version) {
|
|
|
20358
21169
|
return program;
|
|
20359
21170
|
}
|
|
20360
21171
|
async function resolveCliVersion() {
|
|
20361
|
-
if (isPackageVersion("0.0.0-next-
|
|
20362
|
-
return "0.0.0-next-
|
|
21172
|
+
if (isPackageVersion("0.0.0-next-20260608221925")) {
|
|
21173
|
+
return "0.0.0-next-20260608221925";
|
|
20363
21174
|
}
|
|
20364
|
-
if (isReleaseVersion("0.0.0-next-
|
|
20365
|
-
return "0.0.0-next-
|
|
21175
|
+
if (isReleaseVersion("0.0.0-next-20260608221925")) {
|
|
21176
|
+
return "0.0.0-next-20260608221925";
|
|
20366
21177
|
}
|
|
20367
21178
|
try {
|
|
20368
21179
|
const root = repoRoot();
|