@pourkit/cli 0.0.0-next-20260614201555 → 0.0.0-next-20260615001408
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 +1601 -868
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +189 -10
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3186,6 +3186,11 @@ function parseConflictResolutionArtifact(output) {
|
|
|
3186
3186
|
|
|
3187
3187
|
// commands/artifact-validation.ts
|
|
3188
3188
|
var CONFLICT_MARKER_PATTERN = /<<<<<<<|=======|>>>>>>>/m;
|
|
3189
|
+
var ISSUE_FINAL_REVIEW_SCOPE_VERDICTS = [
|
|
3190
|
+
"in-scope",
|
|
3191
|
+
"incidental",
|
|
3192
|
+
"out-of-scope"
|
|
3193
|
+
];
|
|
3189
3194
|
function invalid(options, reason, diagnostics = []) {
|
|
3190
3195
|
return {
|
|
3191
3196
|
ok: false,
|
|
@@ -3203,6 +3208,20 @@ function valid(options, diagnostics = []) {
|
|
|
3203
3208
|
diagnostics
|
|
3204
3209
|
};
|
|
3205
3210
|
}
|
|
3211
|
+
function validateRepoRelativePathField(pathValue, fieldName) {
|
|
3212
|
+
if (typeof pathValue !== "string") {
|
|
3213
|
+
return { ok: false, reason: `${fieldName} must be a string` };
|
|
3214
|
+
}
|
|
3215
|
+
const normalized = pathValue.replace(/\\/g, "/");
|
|
3216
|
+
const segments = normalized.split("/");
|
|
3217
|
+
if (normalized.trim() === "" || normalized === "." || normalized === ".." || isAbsolute3(pathValue) || normalized.startsWith("/") || segments.some((segment) => segment === "..")) {
|
|
3218
|
+
return {
|
|
3219
|
+
ok: false,
|
|
3220
|
+
reason: `${fieldName} must not contain absolute paths or path traversal: ${pathValue}`
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
return { ok: true };
|
|
3224
|
+
}
|
|
3206
3225
|
function readArtifact(options) {
|
|
3207
3226
|
if (!existsSync7(options.artifactPath)) {
|
|
3208
3227
|
return {
|
|
@@ -3271,27 +3290,142 @@ function validateIssueFinalReviewArtifact(parsed, options) {
|
|
|
3271
3290
|
};
|
|
3272
3291
|
}
|
|
3273
3292
|
diagnostics.push("summary: present");
|
|
3274
|
-
if (!Array.isArray(parsed.
|
|
3293
|
+
if (!Array.isArray(parsed.changedFiles)) {
|
|
3294
|
+
return {
|
|
3295
|
+
ok: false,
|
|
3296
|
+
reason: "changedFiles must be an array",
|
|
3297
|
+
diagnostics
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
const outOfScopeChangedFiles = [];
|
|
3301
|
+
for (const [index, changedFile] of parsed.changedFiles.entries()) {
|
|
3302
|
+
if (!changedFile || typeof changedFile !== "object") {
|
|
3303
|
+
return {
|
|
3304
|
+
ok: false,
|
|
3305
|
+
reason: `changedFiles[${index}] must be an object`,
|
|
3306
|
+
diagnostics
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
const file = changedFile;
|
|
3310
|
+
const pathValidation = validateRepoRelativePathField(
|
|
3311
|
+
file.path,
|
|
3312
|
+
`changedFiles[${index}].path`
|
|
3313
|
+
);
|
|
3314
|
+
if (!pathValidation.ok) {
|
|
3315
|
+
return { ok: false, reason: pathValidation.reason, diagnostics };
|
|
3316
|
+
}
|
|
3317
|
+
if (typeof file.change !== "string" || file.change.trim() === "") {
|
|
3318
|
+
return {
|
|
3319
|
+
ok: false,
|
|
3320
|
+
reason: `changedFiles[${index}].change must be a non-empty string`,
|
|
3321
|
+
diagnostics
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
if (!ISSUE_FINAL_REVIEW_SCOPE_VERDICTS.includes(file.scopeVerdict)) {
|
|
3325
|
+
return {
|
|
3326
|
+
ok: false,
|
|
3327
|
+
reason: `changedFiles[${index}].scopeVerdict must be "in-scope", "incidental", or "out-of-scope"`,
|
|
3328
|
+
diagnostics
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
if (typeof file.revertFlag !== "boolean") {
|
|
3332
|
+
return {
|
|
3333
|
+
ok: false,
|
|
3334
|
+
reason: `changedFiles[${index}].revertFlag must be a boolean`,
|
|
3335
|
+
diagnostics
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
if (file.scopeVerdict === "out-of-scope") {
|
|
3339
|
+
outOfScopeChangedFiles.push(file.path);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
diagnostics.push(
|
|
3343
|
+
`changedFiles: ${parsed.changedFiles.length} files`
|
|
3344
|
+
);
|
|
3345
|
+
if (!Array.isArray(parsed.outOfScopeFiles) || !parsed.outOfScopeFiles.every((p) => typeof p === "string")) {
|
|
3275
3346
|
return {
|
|
3276
3347
|
ok: false,
|
|
3277
|
-
reason: "
|
|
3348
|
+
reason: "outOfScopeFiles must be an array of strings",
|
|
3278
3349
|
diagnostics
|
|
3279
3350
|
};
|
|
3280
3351
|
}
|
|
3281
|
-
for (const p of parsed.
|
|
3282
|
-
const
|
|
3283
|
-
|
|
3284
|
-
|
|
3352
|
+
for (const p of parsed.outOfScopeFiles) {
|
|
3353
|
+
const pathValidation = validateRepoRelativePathField(p, "outOfScopeFiles");
|
|
3354
|
+
if (!pathValidation.ok) {
|
|
3355
|
+
return { ok: false, reason: pathValidation.reason, diagnostics };
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
for (const p of outOfScopeChangedFiles) {
|
|
3359
|
+
if (!parsed.outOfScopeFiles.includes(p)) {
|
|
3285
3360
|
return {
|
|
3286
3361
|
ok: false,
|
|
3287
|
-
reason: `
|
|
3362
|
+
reason: `outOfScopeFiles must include changedFiles path marked out-of-scope: ${p}`,
|
|
3288
3363
|
diagnostics
|
|
3289
3364
|
};
|
|
3290
3365
|
}
|
|
3291
3366
|
}
|
|
3292
3367
|
diagnostics.push(
|
|
3293
|
-
`
|
|
3368
|
+
`outOfScopeFiles: ${parsed.outOfScopeFiles.length} paths`
|
|
3294
3369
|
);
|
|
3370
|
+
if (!Array.isArray(parsed.badReverts)) {
|
|
3371
|
+
return {
|
|
3372
|
+
ok: false,
|
|
3373
|
+
reason: "badReverts must be an array",
|
|
3374
|
+
diagnostics
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3377
|
+
for (const [index, badRevert] of parsed.badReverts.entries()) {
|
|
3378
|
+
if (typeof badRevert === "string") {
|
|
3379
|
+
if (badRevert.trim() === "") {
|
|
3380
|
+
return {
|
|
3381
|
+
ok: false,
|
|
3382
|
+
reason: `badReverts[${index}] must not be empty`,
|
|
3383
|
+
diagnostics
|
|
3384
|
+
};
|
|
3385
|
+
}
|
|
3386
|
+
continue;
|
|
3387
|
+
}
|
|
3388
|
+
if (!badRevert || typeof badRevert !== "object") {
|
|
3389
|
+
return {
|
|
3390
|
+
ok: false,
|
|
3391
|
+
reason: `badReverts[${index}] must be a string or object`,
|
|
3392
|
+
diagnostics
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
const entry = badRevert;
|
|
3396
|
+
const pathValidation = validateRepoRelativePathField(
|
|
3397
|
+
entry.path,
|
|
3398
|
+
`badReverts[${index}].path`
|
|
3399
|
+
);
|
|
3400
|
+
if (!pathValidation.ok) {
|
|
3401
|
+
return { ok: false, reason: pathValidation.reason, diagnostics };
|
|
3402
|
+
}
|
|
3403
|
+
const description = entry.hunk ?? entry.description;
|
|
3404
|
+
if (typeof description !== "string" || description.trim() === "") {
|
|
3405
|
+
return {
|
|
3406
|
+
ok: false,
|
|
3407
|
+
reason: `badReverts[${index}] must include a non-empty hunk or description`,
|
|
3408
|
+
diagnostics
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
diagnostics.push(`badReverts: ${parsed.badReverts.length}`);
|
|
3413
|
+
if (parsed.verdict === "pass") {
|
|
3414
|
+
if (parsed.outOfScopeFiles.length > 0) {
|
|
3415
|
+
return {
|
|
3416
|
+
ok: false,
|
|
3417
|
+
reason: "pass verdict requires outOfScopeFiles to be empty",
|
|
3418
|
+
diagnostics
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
if (parsed.badReverts.length > 0) {
|
|
3422
|
+
return {
|
|
3423
|
+
ok: false,
|
|
3424
|
+
reason: "pass verdict requires badReverts to be empty",
|
|
3425
|
+
diagnostics
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3295
3429
|
if (typeof parsed.selfRetouched !== "boolean") {
|
|
3296
3430
|
return {
|
|
3297
3431
|
ok: false,
|
|
@@ -6054,7 +6188,7 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
6054
6188
|
verdict: "pass",
|
|
6055
6189
|
artifactPath,
|
|
6056
6190
|
selfRetouched: parsed.selfRetouched === true,
|
|
6057
|
-
changedPaths: parsed
|
|
6191
|
+
changedPaths: extractChangedPaths(parsed),
|
|
6058
6192
|
verificationPassed: parsed.verification?.passed === true
|
|
6059
6193
|
};
|
|
6060
6194
|
}
|
|
@@ -6064,13 +6198,18 @@ async function runIssueFinalReviewAgent(options) {
|
|
|
6064
6198
|
artifactPath,
|
|
6065
6199
|
needsHumanReason: parsed.needsHumanReason,
|
|
6066
6200
|
selfRetouched: parsed.selfRetouched === true,
|
|
6067
|
-
changedPaths: parsed
|
|
6201
|
+
changedPaths: extractChangedPaths(parsed)
|
|
6068
6202
|
};
|
|
6069
6203
|
}
|
|
6070
6204
|
throw new Error(
|
|
6071
6205
|
`Unknown Issue Final Review verdict: ${JSON.stringify(verdict)}`
|
|
6072
6206
|
);
|
|
6073
6207
|
}
|
|
6208
|
+
function extractChangedPaths(parsed) {
|
|
6209
|
+
return Array.isArray(parsed.changedFiles) ? parsed.changedFiles.map(
|
|
6210
|
+
(file) => file && typeof file === "object" ? file.path : void 0
|
|
6211
|
+
).filter((path9) => typeof path9 === "string") : [];
|
|
6212
|
+
}
|
|
6074
6213
|
function loadIssueFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
6075
6214
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
6076
6215
|
const promptBody = existsSync11(promptPath) ? readFileSync13(promptPath, "utf-8") : promptTemplate;
|
|
@@ -6971,6 +7110,25 @@ async function completeIssueRun(options) {
|
|
|
6971
7110
|
});
|
|
6972
7111
|
}
|
|
6973
7112
|
}
|
|
7113
|
+
if (executionResult.worktreePath && !worktreeState?.pr?.issueFinalReviewSummaryCommented) {
|
|
7114
|
+
const summary = readIssueFinalReviewSummary(
|
|
7115
|
+
ifrState,
|
|
7116
|
+
executionResult.worktreePath
|
|
7117
|
+
);
|
|
7118
|
+
if (summary) {
|
|
7119
|
+
await prProvider.commentPr?.(
|
|
7120
|
+
pr.number,
|
|
7121
|
+
buildIssueFinalReviewPrComment(summary)
|
|
7122
|
+
);
|
|
7123
|
+
if (prProvider.commentPr) {
|
|
7124
|
+
updateWorktreeRunState(executionResult.worktreePath, {
|
|
7125
|
+
pr: {
|
|
7126
|
+
issueFinalReviewSummaryCommented: true
|
|
7127
|
+
}
|
|
7128
|
+
});
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
}
|
|
6974
7132
|
if (target.autoMerge === false) {
|
|
6975
7133
|
await prProvider.waitForPrChecks(pr.number, checkWaitOptions);
|
|
6976
7134
|
const transitions = makeIssueTransitions(issueProvider, config);
|
|
@@ -7124,6 +7282,19 @@ function extractHumanHandoffSummary(artifactContent) {
|
|
|
7124
7282
|
}
|
|
7125
7283
|
return summaryLines.join("\n").trim() || "(No Human Handoff Summary provided)";
|
|
7126
7284
|
}
|
|
7285
|
+
function readIssueFinalReviewSummary(worktreeState, worktreePath) {
|
|
7286
|
+
const configuredPath = worktreeState?.issueFinalReview?.artifactPath;
|
|
7287
|
+
if (!configuredPath) {
|
|
7288
|
+
return null;
|
|
7289
|
+
}
|
|
7290
|
+
const artifactPath = isAbsolute4(configuredPath) ? configuredPath : join15(worktreePath, configuredPath);
|
|
7291
|
+
if (!existsSync12(artifactPath)) return null;
|
|
7292
|
+
const parsed = JSON.parse(readFileSync14(artifactPath, "utf-8"));
|
|
7293
|
+
return typeof parsed.summary === "string" && parsed.summary.trim() !== "" ? parsed.summary.trim() : null;
|
|
7294
|
+
}
|
|
7295
|
+
function buildIssueFinalReviewPrComment(summary) {
|
|
7296
|
+
return ["## Issue Final Review", "", summary].join("\n");
|
|
7297
|
+
}
|
|
7127
7298
|
function getRefactorArtifactDir(artifactPath) {
|
|
7128
7299
|
return artifactPath.replace(/\/reviewers\//, "/refactors/").replace(/\/[^/]+$/, "");
|
|
7129
7300
|
}
|
|
@@ -7900,10 +8071,8 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
|
|
|
7900
8071
|
}
|
|
7901
8072
|
|
|
7902
8073
|
// commands/prd-run.ts
|
|
7903
|
-
import {
|
|
7904
|
-
import {
|
|
7905
|
-
import { join as join19, relative as relative2 } from "path";
|
|
7906
|
-
import { Match, pipe } from "effect";
|
|
8074
|
+
import { lstatSync, realpathSync } from "fs";
|
|
8075
|
+
import { join as join20 } from "path";
|
|
7907
8076
|
|
|
7908
8077
|
// prd-run/state.ts
|
|
7909
8078
|
import {
|
|
@@ -8123,6 +8292,12 @@ function getRecordPath(repoRoot2, prdRef) {
|
|
|
8123
8292
|
);
|
|
8124
8293
|
}
|
|
8125
8294
|
|
|
8295
|
+
// prd-run/coordinator.ts
|
|
8296
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
8297
|
+
import { existsSync as existsSync15, readFileSync as readFileSync17 } from "fs";
|
|
8298
|
+
import { join as join19 } from "path";
|
|
8299
|
+
import { Match, pipe } from "effect";
|
|
8300
|
+
|
|
8126
8301
|
// prd-run/local-queue-loop.ts
|
|
8127
8302
|
import { readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
|
|
8128
8303
|
import { join as join18 } from "path";
|
|
@@ -8981,7 +9156,7 @@ function makeReconcileDeps(options) {
|
|
|
8981
9156
|
}
|
|
8982
9157
|
function reconcileBlockedEffect(options) {
|
|
8983
9158
|
return Effect8.gen(function* () {
|
|
8984
|
-
const
|
|
9159
|
+
const blocked2 = yield* Effect8.tryPromise({
|
|
8985
9160
|
try: () => options.issueProvider.listBlockedIssues(),
|
|
8986
9161
|
catch: (e) => {
|
|
8987
9162
|
if (e instanceof Error) {
|
|
@@ -8990,13 +9165,13 @@ function reconcileBlockedEffect(options) {
|
|
|
8990
9165
|
throw e;
|
|
8991
9166
|
}
|
|
8992
9167
|
});
|
|
8993
|
-
if (
|
|
9168
|
+
if (blocked2.length > 0) {
|
|
8994
9169
|
options.logger.step(
|
|
8995
9170
|
"info",
|
|
8996
|
-
`Reconciling ${
|
|
9171
|
+
`Reconciling ${blocked2.length} blocked issue(s)`
|
|
8997
9172
|
);
|
|
8998
9173
|
yield* Effect8.tryPromise({
|
|
8999
|
-
try: () => reconcileBlockedIssues(
|
|
9174
|
+
try: () => reconcileBlockedIssues(blocked2, makeReconcileDeps(options)),
|
|
9000
9175
|
catch: (e) => {
|
|
9001
9176
|
if (e instanceof Error) {
|
|
9002
9177
|
return new QueueProviderError(e.message);
|
|
@@ -9136,11 +9311,19 @@ async function runQueueCommand(options) {
|
|
|
9136
9311
|
return runEffectAndMapExit(runQueueLoop(queueOptions));
|
|
9137
9312
|
}
|
|
9138
9313
|
|
|
9139
|
-
// prd-run/
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9314
|
+
// prd-run/coordinator.ts
|
|
9315
|
+
function canRetryFinalReviewBlock(record) {
|
|
9316
|
+
if (record.status !== "blocked" || record.blockedGate !== "final-review") {
|
|
9317
|
+
return false;
|
|
9318
|
+
}
|
|
9319
|
+
if (record.start?.queueDrainedAt || record.finalReview) {
|
|
9320
|
+
return true;
|
|
9321
|
+
}
|
|
9322
|
+
return Boolean(
|
|
9323
|
+
record.start && !record.blockedReason?.includes("not drained")
|
|
9324
|
+
);
|
|
9325
|
+
}
|
|
9326
|
+
function planPrdRunLaunchResume(record) {
|
|
9144
9327
|
if (!record) return { attempted: [], skipped: [], resumed: [] };
|
|
9145
9328
|
return pipe(
|
|
9146
9329
|
record,
|
|
@@ -9167,7 +9350,7 @@ function planLaunchResume(record) {
|
|
|
9167
9350
|
})),
|
|
9168
9351
|
Match.when({ status: "final_reviewed" }, () => ({
|
|
9169
9352
|
attempted: [],
|
|
9170
|
-
skipped: [
|
|
9353
|
+
skipped: [],
|
|
9171
9354
|
resumed: [],
|
|
9172
9355
|
blocked: "final_reviewed-incompatible"
|
|
9173
9356
|
})),
|
|
@@ -9180,7 +9363,7 @@ function planLaunchResume(record) {
|
|
|
9180
9363
|
})
|
|
9181
9364
|
),
|
|
9182
9365
|
Match.when(
|
|
9183
|
-
(value) => value.status === "starting" || value.status === "running"
|
|
9366
|
+
(value) => value.status === "starting" || value.status === "running",
|
|
9184
9367
|
(value) => value.start ? {
|
|
9185
9368
|
attempted: [],
|
|
9186
9369
|
skipped: [],
|
|
@@ -9192,6 +9375,27 @@ function planLaunchResume(record) {
|
|
|
9192
9375
|
blocked: "missing-start-receipt"
|
|
9193
9376
|
}
|
|
9194
9377
|
),
|
|
9378
|
+
Match.when(
|
|
9379
|
+
(value) => value.status === "blocked" && value.blockedGate === "branch-state",
|
|
9380
|
+
() => ({
|
|
9381
|
+
attempted: [],
|
|
9382
|
+
skipped: [],
|
|
9383
|
+
resumed: ["start"]
|
|
9384
|
+
})
|
|
9385
|
+
),
|
|
9386
|
+
Match.when(
|
|
9387
|
+
(value) => value.status === "blocked" && value.blockedGate === "queue",
|
|
9388
|
+
(value) => value.start ? {
|
|
9389
|
+
attempted: [],
|
|
9390
|
+
skipped: ["start"],
|
|
9391
|
+
resumed: ["queue"]
|
|
9392
|
+
} : {
|
|
9393
|
+
attempted: [],
|
|
9394
|
+
skipped: ["start", "queue"],
|
|
9395
|
+
resumed: [],
|
|
9396
|
+
blocked: "missing-start-receipt"
|
|
9397
|
+
}
|
|
9398
|
+
),
|
|
9195
9399
|
Match.orElse(() => ({
|
|
9196
9400
|
attempted: [],
|
|
9197
9401
|
skipped: [],
|
|
@@ -9199,19 +9403,109 @@ function planLaunchResume(record) {
|
|
|
9199
9403
|
}))
|
|
9200
9404
|
);
|
|
9201
9405
|
}
|
|
9406
|
+
function resolvePrdRunBaseBranch(options) {
|
|
9407
|
+
const raw = options.baseBranchOverride ?? options.targetBaseBranch;
|
|
9408
|
+
const trimmed = raw.trim();
|
|
9409
|
+
if (!trimmed) {
|
|
9410
|
+
return {
|
|
9411
|
+
ok: false,
|
|
9412
|
+
gate: "branch-state",
|
|
9413
|
+
reason: `Invalid PRD Run base branch: value is empty after trimming.`,
|
|
9414
|
+
diagnostics: [`Provided branch value: "${raw}"`],
|
|
9415
|
+
offendingPaths: []
|
|
9416
|
+
};
|
|
9417
|
+
}
|
|
9418
|
+
if (trimmed.startsWith("origin/")) {
|
|
9419
|
+
return {
|
|
9420
|
+
ok: false,
|
|
9421
|
+
gate: "branch-state",
|
|
9422
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" must not include "origin/" prefix. Use just the branch lane name (e.g. "dev" instead of "origin/dev").`,
|
|
9423
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
9424
|
+
offendingPaths: []
|
|
9425
|
+
};
|
|
9426
|
+
}
|
|
9427
|
+
if (/^(refs\/|heads\/|remotes\/|tags\/)/.test(trimmed)) {
|
|
9428
|
+
return {
|
|
9429
|
+
ok: false,
|
|
9430
|
+
gate: "branch-state",
|
|
9431
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" must be a simple branch lane name, not a ref specification.`,
|
|
9432
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
9433
|
+
offendingPaths: []
|
|
9434
|
+
};
|
|
9435
|
+
}
|
|
9436
|
+
if (/^[0-9a-f]{7,40}$/i.test(trimmed)) {
|
|
9437
|
+
return {
|
|
9438
|
+
ok: false,
|
|
9439
|
+
gate: "branch-state",
|
|
9440
|
+
reason: `Invalid PRD Run base branch: value looks like a commit SHA rather than a branch lane name.`,
|
|
9441
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
9442
|
+
offendingPaths: []
|
|
9443
|
+
};
|
|
9444
|
+
}
|
|
9445
|
+
const segments = trimmed.split("/");
|
|
9446
|
+
for (const segment of segments) {
|
|
9447
|
+
if (segment === "." || segment === ".." || segment === "") {
|
|
9448
|
+
return {
|
|
9449
|
+
ok: false,
|
|
9450
|
+
gate: "branch-state",
|
|
9451
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" must not contain ".", "..", or empty segments.`,
|
|
9452
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
9453
|
+
offendingPaths: []
|
|
9454
|
+
};
|
|
9455
|
+
}
|
|
9456
|
+
}
|
|
9457
|
+
if (/[~^:?*\[\\]/.test(trimmed)) {
|
|
9458
|
+
return {
|
|
9459
|
+
ok: false,
|
|
9460
|
+
gate: "branch-state",
|
|
9461
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" contains invalid characters (~ ^ : ? * [ \\).`,
|
|
9462
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
9463
|
+
offendingPaths: []
|
|
9464
|
+
};
|
|
9465
|
+
}
|
|
9466
|
+
if (/[\x00-\x1f\x7f]/.test(trimmed)) {
|
|
9467
|
+
return {
|
|
9468
|
+
ok: false,
|
|
9469
|
+
gate: "branch-state",
|
|
9470
|
+
reason: `Invalid PRD Run base branch: value contains control characters.`,
|
|
9471
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
9472
|
+
offendingPaths: []
|
|
9473
|
+
};
|
|
9474
|
+
}
|
|
9475
|
+
if (trimmed.endsWith("/")) {
|
|
9476
|
+
return {
|
|
9477
|
+
ok: false,
|
|
9478
|
+
gate: "branch-state",
|
|
9479
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" must not end with a trailing slash.`,
|
|
9480
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
9481
|
+
offendingPaths: []
|
|
9482
|
+
};
|
|
9483
|
+
}
|
|
9484
|
+
if (trimmed.endsWith(".lock")) {
|
|
9485
|
+
return {
|
|
9486
|
+
ok: false,
|
|
9487
|
+
gate: "branch-state",
|
|
9488
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" must not end with ".lock" suffix.`,
|
|
9489
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
9490
|
+
offendingPaths: []
|
|
9491
|
+
};
|
|
9492
|
+
}
|
|
9493
|
+
const source = options.baseBranchOverride !== void 0 ? "override" : "target";
|
|
9494
|
+
return { ok: true, baseBranch: trimmed, source };
|
|
9495
|
+
}
|
|
9202
9496
|
function validateLocalStartStore(repoRoot2, prdRef) {
|
|
9203
9497
|
const localStoreDir = join19(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
|
|
9498
|
+
const localStorePath = join19(localStoreDir, "prd.json");
|
|
9204
9499
|
let localStoreReady = false;
|
|
9205
|
-
if (
|
|
9206
|
-
const localStorePath = join19(localStoreDir, "prd.json");
|
|
9500
|
+
if (existsSync15(localStorePath)) {
|
|
9207
9501
|
try {
|
|
9208
|
-
const content = JSON.parse(
|
|
9502
|
+
const content = JSON.parse(readFileSync17(localStorePath, "utf8"));
|
|
9209
9503
|
localStoreReady = content?.id === prdRef && content?.kind === "prd";
|
|
9210
9504
|
} catch {
|
|
9211
9505
|
localStoreReady = false;
|
|
9212
9506
|
}
|
|
9213
9507
|
}
|
|
9214
|
-
if (
|
|
9508
|
+
if (!localStoreReady) {
|
|
9215
9509
|
return {
|
|
9216
9510
|
ok: false,
|
|
9217
9511
|
gate: "branch-state",
|
|
@@ -9225,213 +9519,178 @@ function validateLocalStartStore(repoRoot2, prdRef) {
|
|
|
9225
9519
|
}
|
|
9226
9520
|
return { ok: true };
|
|
9227
9521
|
}
|
|
9228
|
-
function
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
return true;
|
|
9522
|
+
function fetchOriginBranch(repoRoot2, baseBranch) {
|
|
9523
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", baseBranch], {
|
|
9524
|
+
cwd: repoRoot2,
|
|
9525
|
+
encoding: "utf8"
|
|
9526
|
+
});
|
|
9527
|
+
if (fetchResult.status !== 0) {
|
|
9528
|
+
return {
|
|
9529
|
+
ok: false,
|
|
9530
|
+
gate: "git",
|
|
9531
|
+
reason: `PRD Run start failed while fetching origin/${baseBranch}.`,
|
|
9532
|
+
diagnostics: [
|
|
9533
|
+
fetchResult.error instanceof Error ? fetchResult.error.message : void 0,
|
|
9534
|
+
fetchResult.stderr?.toString?.() ?? String(fetchResult.stderr ?? ""),
|
|
9535
|
+
fetchResult.stdout?.toString?.() ?? String(fetchResult.stdout ?? "")
|
|
9536
|
+
].filter((value) => Boolean(value && value.trim())),
|
|
9537
|
+
offendingPaths: []
|
|
9538
|
+
};
|
|
9246
9539
|
}
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
const attempted = [];
|
|
9254
|
-
const skipped = [];
|
|
9255
|
-
const resumed = [];
|
|
9256
|
-
const diagnostics = [];
|
|
9257
|
-
const existingRecord = readPrdRun(options.repoRoot, prdRef);
|
|
9258
|
-
const resolvedLaunchMode = options.config ? (() => {
|
|
9259
|
-
try {
|
|
9260
|
-
const target = resolveTarget(options.config, options.targetName);
|
|
9261
|
-
return resolvePrdRunMode(target).mode;
|
|
9262
|
-
} catch {
|
|
9263
|
-
return void 0;
|
|
9540
|
+
const revParseResult = spawnSync3(
|
|
9541
|
+
"git",
|
|
9542
|
+
["rev-parse", `origin/${baseBranch}`],
|
|
9543
|
+
{
|
|
9544
|
+
cwd: repoRoot2,
|
|
9545
|
+
encoding: "utf8"
|
|
9264
9546
|
}
|
|
9265
|
-
|
|
9266
|
-
if (
|
|
9547
|
+
);
|
|
9548
|
+
if (revParseResult.status !== 0) {
|
|
9267
9549
|
return {
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
skipped: ["start", "queue"],
|
|
9272
|
-
resumed: [],
|
|
9550
|
+
ok: false,
|
|
9551
|
+
gate: "git",
|
|
9552
|
+
reason: `PRD Run start failed while resolving origin/${baseBranch}.`,
|
|
9273
9553
|
diagnostics: [
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
blockedReason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedLaunchMode}". Resolve by using the correct target.`,
|
|
9554
|
+
revParseResult.error instanceof Error ? revParseResult.error.message : void 0,
|
|
9555
|
+
revParseResult.stderr?.toString?.() ?? String(revParseResult.stderr ?? ""),
|
|
9556
|
+
revParseResult.stdout?.toString?.() ?? String(revParseResult.stdout ?? "")
|
|
9557
|
+
].filter((value) => Boolean(value && value.trim())),
|
|
9279
9558
|
offendingPaths: []
|
|
9280
9559
|
};
|
|
9281
9560
|
}
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9561
|
+
return { ok: true, startBaseCommit: revParseResult.stdout.trim() };
|
|
9562
|
+
}
|
|
9563
|
+
function inspectRemotePrdBranch(repoRoot2, prdRef) {
|
|
9564
|
+
const result = spawnSync3("git", ["ls-remote", "--heads", "origin", prdRef], {
|
|
9565
|
+
cwd: repoRoot2,
|
|
9566
|
+
encoding: "utf8"
|
|
9567
|
+
});
|
|
9568
|
+
if (result.status !== 0) {
|
|
9286
9569
|
return {
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
skipped,
|
|
9291
|
-
resumed,
|
|
9570
|
+
ok: false,
|
|
9571
|
+
gate: "git",
|
|
9572
|
+
reason: `PRD Run start failed while inspecting remote branch ${prdRef}.`,
|
|
9292
9573
|
diagnostics: [
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9574
|
+
result.error instanceof Error ? result.error.message : void 0,
|
|
9575
|
+
result.stderr?.toString?.() ?? String(result.stderr ?? ""),
|
|
9576
|
+
result.stdout?.toString?.() ?? String(result.stdout ?? "")
|
|
9577
|
+
].filter((value) => Boolean(value && value.trim())),
|
|
9297
9578
|
offendingPaths: []
|
|
9298
9579
|
};
|
|
9299
9580
|
}
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
status: "blocked",
|
|
9304
|
-
attempted: [],
|
|
9305
|
-
skipped: [],
|
|
9306
|
-
resumed: [],
|
|
9307
|
-
diagnostics: [
|
|
9308
|
-
`PRD Run ${prdRef} has obsolete status "final_reviewed". PRD-wide Final Review has been removed from the launch lifecycle.`
|
|
9309
|
-
],
|
|
9310
|
-
blockedGate: "final-review",
|
|
9311
|
-
blockedReason: `PRD Run ${prdRef} has status "final_reviewed", which is no longer a valid launch status. PRD-wide Final Review has been removed from the launch lifecycle. Update the PRD Run state to "drained" and rerun launch, or start a new PRD Run.`,
|
|
9312
|
-
offendingPaths: []
|
|
9313
|
-
};
|
|
9581
|
+
const headSha = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0]?.split(/\s+/)[0];
|
|
9582
|
+
if (!headSha) {
|
|
9583
|
+
return { ok: true, exists: false };
|
|
9314
9584
|
}
|
|
9315
|
-
|
|
9585
|
+
return { ok: true, exists: true, headSha };
|
|
9586
|
+
}
|
|
9587
|
+
function fetchPrdBranch(repoRoot2, prdRef) {
|
|
9588
|
+
const result = spawnSync3("git", ["fetch", "origin", prdRef], {
|
|
9589
|
+
cwd: repoRoot2,
|
|
9590
|
+
encoding: "utf8"
|
|
9591
|
+
});
|
|
9592
|
+
if (result.status !== 0) {
|
|
9316
9593
|
return {
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
skipped,
|
|
9321
|
-
resumed,
|
|
9594
|
+
ok: false,
|
|
9595
|
+
gate: "git",
|
|
9596
|
+
reason: `PRD Run start failed while fetching origin/${prdRef}.`,
|
|
9322
9597
|
diagnostics: [
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
] : []
|
|
9329
|
-
],
|
|
9330
|
-
prdBranch: existingRecord.record.prdBranch
|
|
9598
|
+
result.error instanceof Error ? result.error.message : void 0,
|
|
9599
|
+
result.stderr?.toString?.() ?? String(result.stderr ?? ""),
|
|
9600
|
+
result.stdout?.toString?.() ?? String(result.stdout ?? "")
|
|
9601
|
+
].filter((value) => Boolean(value && value.trim())),
|
|
9602
|
+
offendingPaths: []
|
|
9331
9603
|
};
|
|
9332
9604
|
}
|
|
9333
|
-
|
|
9334
|
-
|
|
9335
|
-
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
prProvider: options.prProvider,
|
|
9343
|
-
executionProvider: options.executionProvider,
|
|
9344
|
-
logger: options.logger,
|
|
9345
|
-
adoptExistingBranch: options.adoptExistingBranch,
|
|
9346
|
-
baseBranchOverride: options.baseBranchOverride
|
|
9347
|
-
});
|
|
9348
|
-
const skippedAfterStart = ["queue"];
|
|
9349
|
-
if (startResult.status === "blocked") {
|
|
9350
|
-
return {
|
|
9351
|
-
prdRef,
|
|
9352
|
-
status: "blocked",
|
|
9353
|
-
attempted,
|
|
9354
|
-
skipped: skippedAfterStart,
|
|
9355
|
-
resumed,
|
|
9356
|
-
diagnostics: startResult.diagnostics,
|
|
9357
|
-
blockedGate: startResult.blockedGate,
|
|
9358
|
-
blockedReason: startResult.blockedReason,
|
|
9359
|
-
offendingPaths: startResult.offendingPaths,
|
|
9360
|
-
start: startResult
|
|
9361
|
-
};
|
|
9362
|
-
}
|
|
9363
|
-
if (startResult.status === "starting") {
|
|
9364
|
-
return {
|
|
9365
|
-
prdRef,
|
|
9366
|
-
status: "starting",
|
|
9367
|
-
attempted,
|
|
9368
|
-
skipped: skippedAfterStart,
|
|
9369
|
-
resumed,
|
|
9370
|
-
diagnostics: startResult.diagnostics,
|
|
9371
|
-
start: startResult
|
|
9372
|
-
};
|
|
9605
|
+
return { ok: true };
|
|
9606
|
+
}
|
|
9607
|
+
async function ensurePrdBranchPublished(repoRoot2, prdRef, startBaseCommit) {
|
|
9608
|
+
const pushResult = spawnSync3(
|
|
9609
|
+
"git",
|
|
9610
|
+
["push", "origin", `${startBaseCommit}:refs/heads/${prdRef}`],
|
|
9611
|
+
{
|
|
9612
|
+
cwd: repoRoot2,
|
|
9613
|
+
encoding: "utf8"
|
|
9373
9614
|
}
|
|
9374
|
-
diagnostics.push(...startResult.diagnostics);
|
|
9375
|
-
attempted.push("queue");
|
|
9376
|
-
}
|
|
9377
|
-
const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
9378
|
-
const localStorePath = join19(
|
|
9379
|
-
options.repoRoot,
|
|
9380
|
-
".pourkit",
|
|
9381
|
-
"local-prd-runs",
|
|
9382
|
-
prdRef
|
|
9383
9615
|
);
|
|
9384
|
-
if (
|
|
9385
|
-
|
|
9386
|
-
prdRef
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
start: currentRecord?.start,
|
|
9393
|
-
scopeChanges: currentRecord?.scopeChanges
|
|
9394
|
-
});
|
|
9395
|
-
return {
|
|
9396
|
-
prdRef,
|
|
9397
|
-
status: "completed_local_branch",
|
|
9398
|
-
prdBranch: currentRecord?.prdBranch ?? `local/${prdRef}`,
|
|
9399
|
-
attempted,
|
|
9400
|
-
skipped,
|
|
9401
|
-
resumed,
|
|
9402
|
-
diagnostics: [
|
|
9403
|
-
`Local PRD Run ${prdRef} completed. Branch: local/${prdRef}.`
|
|
9404
|
-
],
|
|
9405
|
-
start: startResult
|
|
9406
|
-
};
|
|
9616
|
+
if (pushResult.status !== 0) {
|
|
9617
|
+
throw new Error(
|
|
9618
|
+
`Failed to create PRD Branch ${prdRef} from ${startBaseCommit}: ${[
|
|
9619
|
+
pushResult.error instanceof Error ? pushResult.error.message : void 0,
|
|
9620
|
+
pushResult.stderr?.toString?.() ?? String(pushResult.stderr ?? ""),
|
|
9621
|
+
pushResult.stdout?.toString?.() ?? String(pushResult.stdout ?? "")
|
|
9622
|
+
].filter((value) => Boolean(value && value.trim())).join(" ")}`
|
|
9623
|
+
);
|
|
9407
9624
|
}
|
|
9408
|
-
|
|
9625
|
+
}
|
|
9626
|
+
function buildStartReceipt(options) {
|
|
9627
|
+
return {
|
|
9628
|
+
status: options.status,
|
|
9629
|
+
targetName: options.targetName,
|
|
9630
|
+
prdBranch: options.prdBranch,
|
|
9631
|
+
startBaseBranch: options.startBaseBranch,
|
|
9632
|
+
startBaseCommit: options.startBaseCommit,
|
|
9633
|
+
branchAction: options.branchAction,
|
|
9634
|
+
startedAt: options.startedAt,
|
|
9635
|
+
refreshReceipts: []
|
|
9636
|
+
};
|
|
9637
|
+
}
|
|
9638
|
+
function buildBlockedOutcome(repoRoot2, prdRef, guidance, targetName) {
|
|
9639
|
+
writePrdRunRecord(repoRoot2, {
|
|
9409
9640
|
prdRef,
|
|
9410
|
-
status: "
|
|
9641
|
+
status: "blocked",
|
|
9411
9642
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9643
|
+
blockedGate: guidance.blockedGate,
|
|
9644
|
+
blockedReason: guidance.reason,
|
|
9645
|
+
diagnostics: [...guidance.diagnostics ?? []],
|
|
9646
|
+
offendingPaths: [...guidance.offendingPaths ?? []],
|
|
9647
|
+
targetName
|
|
9416
9648
|
});
|
|
9417
|
-
return {
|
|
9649
|
+
return { kind: "blocked", prdRef, guidance };
|
|
9650
|
+
}
|
|
9651
|
+
function persistStartingPrdRunRecord(repoRoot2, prdRef, existingRecord, start, context) {
|
|
9652
|
+
const {
|
|
9653
|
+
blockedGate: _blockedGate,
|
|
9654
|
+
blockedReason: _blockedReason,
|
|
9655
|
+
diagnostics: _diagnostics,
|
|
9656
|
+
offendingPaths: _offendingPaths,
|
|
9657
|
+
prdBranch: _prdBranch,
|
|
9658
|
+
start: _start,
|
|
9659
|
+
...cleanRecord
|
|
9660
|
+
} = existingRecord.record ?? { prdRef };
|
|
9661
|
+
writePrdRunRecord(repoRoot2, {
|
|
9662
|
+
...cleanRecord,
|
|
9418
9663
|
prdRef,
|
|
9419
|
-
status: "
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
],
|
|
9426
|
-
start: startResult,
|
|
9427
|
-
prdBranch: currentRecord?.prdBranch ?? prdRef
|
|
9428
|
-
};
|
|
9664
|
+
status: "starting",
|
|
9665
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9666
|
+
targetName: context.targetName,
|
|
9667
|
+
start,
|
|
9668
|
+
mode: context.mode
|
|
9669
|
+
});
|
|
9429
9670
|
}
|
|
9430
|
-
|
|
9671
|
+
function blocked(repoRoot2, prdRef, gate, failureCode, reason, diagnostics, targetName) {
|
|
9672
|
+
return buildBlockedOutcome(
|
|
9673
|
+
repoRoot2,
|
|
9674
|
+
prdRef,
|
|
9675
|
+
{
|
|
9676
|
+
blockedGate: gate,
|
|
9677
|
+
failureCode,
|
|
9678
|
+
reason,
|
|
9679
|
+
diagnostics: diagnostics ? [...diagnostics] : [],
|
|
9680
|
+
repairability: "operator-action"
|
|
9681
|
+
},
|
|
9682
|
+
targetName
|
|
9683
|
+
);
|
|
9684
|
+
}
|
|
9685
|
+
async function startPrdRun(options) {
|
|
9431
9686
|
const prdRef = normalizePrdRunRef(options.prdRef);
|
|
9432
9687
|
const targetName = options.targetName.trim();
|
|
9433
9688
|
if (!targetName) {
|
|
9434
|
-
|
|
9689
|
+
return blocked(
|
|
9690
|
+
options.repoRoot,
|
|
9691
|
+
prdRef,
|
|
9692
|
+
"branch-state",
|
|
9693
|
+
"invalid_target_name",
|
|
9435
9694
|
`Invalid target name "${options.targetName}". Expected a non-empty target name.`
|
|
9436
9695
|
);
|
|
9437
9696
|
}
|
|
@@ -9445,20 +9704,18 @@ async function runPrdRunStartCommand(options) {
|
|
|
9445
9704
|
}
|
|
9446
9705
|
})() : void 0;
|
|
9447
9706
|
if (resolvedMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedMode) {
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9707
|
+
return blocked(
|
|
9708
|
+
options.repoRoot,
|
|
9709
|
+
prdRef,
|
|
9710
|
+
"branch-state",
|
|
9711
|
+
"mode_mismatch",
|
|
9712
|
+
`PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedMode}". Resolve by using the correct target.`,
|
|
9713
|
+
[
|
|
9453
9714
|
`Recorded mode: ${existingRecord.record.mode}`,
|
|
9454
9715
|
`Resolved mode: ${resolvedMode}`
|
|
9455
9716
|
],
|
|
9456
|
-
offendingPaths: []
|
|
9457
|
-
};
|
|
9458
|
-
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
9459
9717
|
targetName
|
|
9460
|
-
|
|
9461
|
-
return buildBlockedStartResult(prdRef, failure);
|
|
9718
|
+
);
|
|
9462
9719
|
}
|
|
9463
9720
|
const resolvedTarget = options.config ? resolveTarget(options.config, targetName) : null;
|
|
9464
9721
|
const targetBaseBranch = resolvedTarget?.baseBranch ?? "main";
|
|
@@ -9467,57 +9724,75 @@ async function runPrdRunStartCommand(options) {
|
|
|
9467
9724
|
baseBranchOverride: options.baseBranchOverride
|
|
9468
9725
|
});
|
|
9469
9726
|
if (!baseResolution.ok) {
|
|
9470
|
-
|
|
9727
|
+
return blocked(
|
|
9728
|
+
options.repoRoot,
|
|
9729
|
+
prdRef,
|
|
9730
|
+
baseResolution.gate,
|
|
9731
|
+
"base_resolution_failed",
|
|
9732
|
+
baseResolution.reason,
|
|
9733
|
+
baseResolution.diagnostics,
|
|
9471
9734
|
targetName
|
|
9472
|
-
|
|
9473
|
-
return buildBlockedStartResult(prdRef, baseResolution);
|
|
9735
|
+
);
|
|
9474
9736
|
}
|
|
9475
9737
|
if (existingRecord.record?.start) {
|
|
9476
9738
|
const recordedBaseBranch = existingRecord.record.start.startBaseBranch;
|
|
9477
9739
|
if (!recordedBaseBranch) {
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
],
|
|
9485
|
-
offendingPaths: []
|
|
9486
|
-
};
|
|
9487
|
-
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
9740
|
+
return blocked(
|
|
9741
|
+
options.repoRoot,
|
|
9742
|
+
prdRef,
|
|
9743
|
+
"branch-state",
|
|
9744
|
+
"missing_start_base_branch",
|
|
9745
|
+
`PRD Run ${prdRef} has a start receipt without startBaseBranch. Run prd-run start to create a fresh start receipt.`,
|
|
9746
|
+
[`Recorded start: ${JSON.stringify(existingRecord.record.start)}`],
|
|
9488
9747
|
targetName
|
|
9489
|
-
|
|
9490
|
-
return buildBlockedStartResult(prdRef, failure);
|
|
9748
|
+
);
|
|
9491
9749
|
}
|
|
9492
9750
|
if (recordedBaseBranch !== baseResolution.baseBranch) {
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9751
|
+
return blocked(
|
|
9752
|
+
options.repoRoot,
|
|
9753
|
+
prdRef,
|
|
9754
|
+
"branch-state",
|
|
9755
|
+
"base_mismatch",
|
|
9756
|
+
`PRD Run ${prdRef} start receipt records startBaseBranch "${recordedBaseBranch}" but current base resolution resolves to "${baseResolution.baseBranch}". Use --base with the matching value or create a fresh start receipt.`,
|
|
9757
|
+
[
|
|
9498
9758
|
`Recorded startBaseBranch: ${recordedBaseBranch}`,
|
|
9499
9759
|
`Current base resolution: ${baseResolution.baseBranch} (source: ${baseResolution.source})`
|
|
9500
9760
|
],
|
|
9501
|
-
offendingPaths: []
|
|
9502
|
-
};
|
|
9503
|
-
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
9504
9761
|
targetName
|
|
9505
|
-
|
|
9506
|
-
|
|
9762
|
+
);
|
|
9763
|
+
}
|
|
9764
|
+
const currentStatus = existingRecord.record.status;
|
|
9765
|
+
if (currentStatus === "running" || currentStatus === "drained" || currentStatus === "completed_prd_branch" || currentStatus === "completed_local_branch" || currentStatus === "complete") {
|
|
9766
|
+
return {
|
|
9767
|
+
kind: "started",
|
|
9768
|
+
prdRef,
|
|
9769
|
+
mode: resolvedMode,
|
|
9770
|
+
start: existingRecord.record.start,
|
|
9771
|
+
diagnostics: [],
|
|
9772
|
+
preservedStatus: currentStatus
|
|
9773
|
+
};
|
|
9507
9774
|
}
|
|
9508
9775
|
const fetchResult2 = fetchOriginBranch(options.repoRoot, recordedBaseBranch);
|
|
9509
9776
|
if (!fetchResult2.ok) {
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9777
|
+
return blocked(
|
|
9778
|
+
options.repoRoot,
|
|
9779
|
+
prdRef,
|
|
9780
|
+
fetchResult2.gate,
|
|
9781
|
+
"fetch_failed",
|
|
9782
|
+
fetchResult2.reason,
|
|
9783
|
+
fetchResult2.diagnostics
|
|
9784
|
+
);
|
|
9514
9785
|
}
|
|
9515
9786
|
const branchResult2 = inspectRemotePrdBranch(options.repoRoot, prdRef);
|
|
9516
9787
|
if (!branchResult2.ok) {
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9788
|
+
return blocked(
|
|
9789
|
+
options.repoRoot,
|
|
9790
|
+
prdRef,
|
|
9791
|
+
branchResult2.gate,
|
|
9792
|
+
"branch_inspection_failed",
|
|
9793
|
+
branchResult2.reason,
|
|
9794
|
+
branchResult2.diagnostics
|
|
9795
|
+
);
|
|
9521
9796
|
}
|
|
9522
9797
|
if (branchResult2.exists) {
|
|
9523
9798
|
const matchingStart = existingRecord.record.start;
|
|
@@ -9531,53 +9806,42 @@ async function runPrdRunStartCommand(options) {
|
|
|
9531
9806
|
options.repoRoot,
|
|
9532
9807
|
prdRef,
|
|
9533
9808
|
existingRecord,
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
},
|
|
9537
|
-
{
|
|
9538
|
-
targetName,
|
|
9539
|
-
mode: resolvedMode
|
|
9540
|
-
}
|
|
9541
|
-
);
|
|
9542
|
-
return await processStartResult(
|
|
9543
|
-
{
|
|
9544
|
-
prdRef,
|
|
9545
|
-
status: "starting",
|
|
9546
|
-
start: reusedStart,
|
|
9547
|
-
diagnostics: []
|
|
9548
|
-
},
|
|
9549
|
-
options
|
|
9809
|
+
reusedStart,
|
|
9810
|
+
{ targetName, mode: resolvedMode }
|
|
9550
9811
|
);
|
|
9812
|
+
return {
|
|
9813
|
+
kind: "started",
|
|
9814
|
+
prdRef,
|
|
9815
|
+
mode: resolvedMode,
|
|
9816
|
+
start: reusedStart,
|
|
9817
|
+
diagnostics: []
|
|
9818
|
+
};
|
|
9551
9819
|
}
|
|
9552
9820
|
if (!options.adoptExistingBranch) {
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
`
|
|
9821
|
+
return blocked(
|
|
9822
|
+
options.repoRoot,
|
|
9823
|
+
prdRef,
|
|
9824
|
+
"branch-state",
|
|
9825
|
+
"unrecorded_branch_exists",
|
|
9826
|
+
`PRD Branch ${prdRef} already exists remotely but PRD Run state does not record the same branch and start base commit. Use --adopt-existing-branch to adopt it.`,
|
|
9827
|
+
[
|
|
9828
|
+
`Existing remote branch: ${prdRef}`,
|
|
9829
|
+
`Recorded start branch: ${matchingStart.prdBranch}`,
|
|
9830
|
+
`Recorded start base commit: ${matchingStart.startBaseCommit}`,
|
|
9561
9831
|
`Current origin/${recordedBaseBranch} head: ${fetchResult2.startBaseCommit}`
|
|
9562
|
-
]
|
|
9563
|
-
|
|
9564
|
-
};
|
|
9565
|
-
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
9566
|
-
targetName
|
|
9567
|
-
});
|
|
9568
|
-
return buildBlockedStartResult(prdRef, failure);
|
|
9832
|
+
]
|
|
9833
|
+
);
|
|
9569
9834
|
}
|
|
9570
9835
|
const fetchPrdResult = fetchPrdBranch(options.repoRoot, prdRef);
|
|
9571
9836
|
if (!fetchPrdResult.ok) {
|
|
9572
|
-
|
|
9837
|
+
return blocked(
|
|
9573
9838
|
options.repoRoot,
|
|
9574
9839
|
prdRef,
|
|
9575
|
-
fetchPrdResult,
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9840
|
+
fetchPrdResult.gate,
|
|
9841
|
+
"fetch_prd_branch_failed",
|
|
9842
|
+
fetchPrdResult.reason,
|
|
9843
|
+
fetchPrdResult.diagnostics
|
|
9579
9844
|
);
|
|
9580
|
-
return buildBlockedStartResult(prdRef, fetchPrdResult);
|
|
9581
9845
|
}
|
|
9582
9846
|
const ancestryResult = spawnSync3(
|
|
9583
9847
|
"git",
|
|
@@ -9590,20 +9854,17 @@ async function runPrdRunStartCommand(options) {
|
|
|
9590
9854
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
9591
9855
|
);
|
|
9592
9856
|
if (ancestryResult.status !== 0) {
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9857
|
+
return blocked(
|
|
9858
|
+
options.repoRoot,
|
|
9859
|
+
prdRef,
|
|
9860
|
+
"branch-state",
|
|
9861
|
+
"ancestry_check_failed",
|
|
9862
|
+
`PRD Branch ${prdRef} base ancestry check failed: origin/${recordedBaseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
|
|
9863
|
+
[
|
|
9598
9864
|
`Base branch: origin/${recordedBaseBranch}`,
|
|
9599
9865
|
`PRD branch: origin/${prdRef}`
|
|
9600
|
-
]
|
|
9601
|
-
|
|
9602
|
-
};
|
|
9603
|
-
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
9604
|
-
targetName
|
|
9605
|
-
});
|
|
9606
|
-
return buildBlockedStartResult(prdRef, failure);
|
|
9866
|
+
]
|
|
9867
|
+
);
|
|
9607
9868
|
}
|
|
9608
9869
|
const adoptedStart = buildStartReceipt({
|
|
9609
9870
|
status: "adopted",
|
|
@@ -9618,23 +9879,16 @@ async function runPrdRunStartCommand(options) {
|
|
|
9618
9879
|
options.repoRoot,
|
|
9619
9880
|
prdRef,
|
|
9620
9881
|
existingRecord,
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
},
|
|
9624
|
-
{
|
|
9625
|
-
targetName,
|
|
9626
|
-
mode: resolvedMode
|
|
9627
|
-
}
|
|
9628
|
-
);
|
|
9629
|
-
return await processStartResult(
|
|
9630
|
-
{
|
|
9631
|
-
prdRef,
|
|
9632
|
-
status: "starting",
|
|
9633
|
-
start: adoptedStart,
|
|
9634
|
-
diagnostics: []
|
|
9635
|
-
},
|
|
9636
|
-
options
|
|
9882
|
+
adoptedStart,
|
|
9883
|
+
{ targetName, mode: resolvedMode }
|
|
9637
9884
|
);
|
|
9885
|
+
return {
|
|
9886
|
+
kind: "started",
|
|
9887
|
+
prdRef,
|
|
9888
|
+
mode: resolvedMode,
|
|
9889
|
+
start: adoptedStart,
|
|
9890
|
+
diagnostics: []
|
|
9891
|
+
};
|
|
9638
9892
|
}
|
|
9639
9893
|
}
|
|
9640
9894
|
const fetchResult = fetchOriginBranch(
|
|
@@ -9642,12 +9896,27 @@ async function runPrdRunStartCommand(options) {
|
|
|
9642
9896
|
baseResolution.baseBranch
|
|
9643
9897
|
);
|
|
9644
9898
|
if (!fetchResult.ok) {
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9899
|
+
return blocked(
|
|
9900
|
+
options.repoRoot,
|
|
9901
|
+
prdRef,
|
|
9902
|
+
fetchResult.gate,
|
|
9903
|
+
"fetch_failed",
|
|
9904
|
+
fetchResult.reason,
|
|
9905
|
+
fetchResult.diagnostics
|
|
9906
|
+
);
|
|
9649
9907
|
}
|
|
9650
9908
|
if (resolvedMode === "local") {
|
|
9909
|
+
const localStoreResult = validateLocalStartStore(options.repoRoot, prdRef);
|
|
9910
|
+
if (!localStoreResult.ok) {
|
|
9911
|
+
return blocked(
|
|
9912
|
+
options.repoRoot,
|
|
9913
|
+
prdRef,
|
|
9914
|
+
localStoreResult.gate,
|
|
9915
|
+
"local_store_invalid",
|
|
9916
|
+
localStoreResult.reason,
|
|
9917
|
+
localStoreResult.diagnostics
|
|
9918
|
+
);
|
|
9919
|
+
}
|
|
9651
9920
|
const localBranchName = getLocalPrdBranchName(prdRef);
|
|
9652
9921
|
const localBranchResult = materializeLocalPrdBranch(
|
|
9653
9922
|
prdRef,
|
|
@@ -9655,29 +9924,14 @@ async function runPrdRunStartCommand(options) {
|
|
|
9655
9924
|
options.repoRoot
|
|
9656
9925
|
);
|
|
9657
9926
|
if (!localBranchResult.ok) {
|
|
9658
|
-
|
|
9659
|
-
ok: false,
|
|
9660
|
-
gate: "branch-state",
|
|
9661
|
-
reason: localBranchResult.message,
|
|
9662
|
-
diagnostics: [localBranchResult.message],
|
|
9663
|
-
offendingPaths: []
|
|
9664
|
-
};
|
|
9665
|
-
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
9666
|
-
targetName
|
|
9667
|
-
});
|
|
9668
|
-
return buildBlockedStartResult(prdRef, failure);
|
|
9669
|
-
}
|
|
9670
|
-
const localStoreResult = validateLocalStartStore(options.repoRoot, prdRef);
|
|
9671
|
-
if (!localStoreResult.ok) {
|
|
9672
|
-
persistBlockedPrdRunStartRecord(
|
|
9927
|
+
return blocked(
|
|
9673
9928
|
options.repoRoot,
|
|
9674
9929
|
prdRef,
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9930
|
+
"branch-state",
|
|
9931
|
+
"local_branch_failed",
|
|
9932
|
+
localBranchResult.message,
|
|
9933
|
+
[localBranchResult.message]
|
|
9679
9934
|
);
|
|
9680
|
-
return buildBlockedStartResult(prdRef, localStoreResult);
|
|
9681
9935
|
}
|
|
9682
9936
|
const updatedAt2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
9683
9937
|
const start2 = buildStartReceipt({
|
|
@@ -9694,58 +9948,53 @@ async function runPrdRunStartCommand(options) {
|
|
|
9694
9948
|
prdRef,
|
|
9695
9949
|
existingRecord,
|
|
9696
9950
|
start2,
|
|
9697
|
-
{
|
|
9698
|
-
targetName,
|
|
9699
|
-
mode: resolvedMode
|
|
9700
|
-
}
|
|
9701
|
-
);
|
|
9702
|
-
return await processStartResult(
|
|
9703
|
-
{
|
|
9704
|
-
prdRef,
|
|
9705
|
-
status: "starting",
|
|
9706
|
-
start: start2,
|
|
9707
|
-
diagnostics: []
|
|
9708
|
-
},
|
|
9709
|
-
options
|
|
9951
|
+
{ targetName, mode: resolvedMode }
|
|
9710
9952
|
);
|
|
9953
|
+
return {
|
|
9954
|
+
kind: "started",
|
|
9955
|
+
prdRef,
|
|
9956
|
+
mode: resolvedMode,
|
|
9957
|
+
start: start2,
|
|
9958
|
+
diagnostics: []
|
|
9959
|
+
};
|
|
9711
9960
|
}
|
|
9712
9961
|
const branchResult = inspectRemotePrdBranch(options.repoRoot, prdRef);
|
|
9713
9962
|
if (!branchResult.ok) {
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9963
|
+
return blocked(
|
|
9964
|
+
options.repoRoot,
|
|
9965
|
+
prdRef,
|
|
9966
|
+
branchResult.gate,
|
|
9967
|
+
"branch_inspection_failed",
|
|
9968
|
+
branchResult.reason,
|
|
9969
|
+
branchResult.diagnostics
|
|
9970
|
+
);
|
|
9718
9971
|
}
|
|
9719
9972
|
if (branchResult.exists) {
|
|
9720
9973
|
if (!options.adoptExistingBranch) {
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9974
|
+
return blocked(
|
|
9975
|
+
options.repoRoot,
|
|
9976
|
+
prdRef,
|
|
9977
|
+
"branch-state",
|
|
9978
|
+
"unrecorded_branch_exists",
|
|
9979
|
+
`PRD Branch ${prdRef} already exists remotely but PRD Run state does not record the same branch and start base commit. Use --adopt-existing-branch to adopt it.`,
|
|
9980
|
+
[
|
|
9726
9981
|
`Existing remote branch: ${prdRef}`,
|
|
9727
9982
|
`Recorded start branch: ${existingRecord.record?.start?.prdBranch ?? "(missing)"}`,
|
|
9728
9983
|
`Recorded start base commit: ${existingRecord.record?.start?.startBaseCommit ?? "(missing)"}`,
|
|
9729
9984
|
`Current origin/${baseResolution.baseBranch} head: ${fetchResult.startBaseCommit}`
|
|
9730
|
-
]
|
|
9731
|
-
|
|
9732
|
-
};
|
|
9733
|
-
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
9734
|
-
targetName
|
|
9735
|
-
});
|
|
9736
|
-
return buildBlockedStartResult(prdRef, failure);
|
|
9985
|
+
]
|
|
9986
|
+
);
|
|
9737
9987
|
}
|
|
9738
9988
|
const fetchPrdResult = fetchPrdBranch(options.repoRoot, prdRef);
|
|
9739
9989
|
if (!fetchPrdResult.ok) {
|
|
9740
|
-
|
|
9990
|
+
return blocked(
|
|
9741
9991
|
options.repoRoot,
|
|
9742
9992
|
prdRef,
|
|
9743
|
-
fetchPrdResult,
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9993
|
+
fetchPrdResult.gate,
|
|
9994
|
+
"fetch_prd_branch_failed",
|
|
9995
|
+
fetchPrdResult.reason,
|
|
9996
|
+
fetchPrdResult.diagnostics
|
|
9747
9997
|
);
|
|
9748
|
-
return buildBlockedStartResult(prdRef, fetchPrdResult);
|
|
9749
9998
|
}
|
|
9750
9999
|
const ancestryResult = spawnSync3(
|
|
9751
10000
|
"git",
|
|
@@ -9758,227 +10007,811 @@ async function runPrdRunStartCommand(options) {
|
|
|
9758
10007
|
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
9759
10008
|
);
|
|
9760
10009
|
if (ancestryResult.status !== 0) {
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
10010
|
+
return blocked(
|
|
10011
|
+
options.repoRoot,
|
|
10012
|
+
prdRef,
|
|
10013
|
+
"branch-state",
|
|
10014
|
+
"ancestry_check_failed",
|
|
10015
|
+
`PRD Branch ${prdRef} base ancestry check failed: origin/${baseResolution.baseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
|
|
10016
|
+
[
|
|
9766
10017
|
`Base branch: origin/${baseResolution.baseBranch}`,
|
|
9767
10018
|
`PRD branch: origin/${prdRef}`
|
|
10019
|
+
]
|
|
10020
|
+
);
|
|
10021
|
+
}
|
|
10022
|
+
const adoptedStart = buildStartReceipt({
|
|
10023
|
+
status: "adopted",
|
|
10024
|
+
targetName,
|
|
10025
|
+
prdBranch: prdRef,
|
|
10026
|
+
startBaseBranch: baseResolution.baseBranch,
|
|
10027
|
+
startBaseCommit: fetchResult.startBaseCommit,
|
|
10028
|
+
branchAction: "adopted",
|
|
10029
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10030
|
+
});
|
|
10031
|
+
persistStartingPrdRunRecord(
|
|
10032
|
+
options.repoRoot,
|
|
10033
|
+
prdRef,
|
|
10034
|
+
existingRecord,
|
|
10035
|
+
adoptedStart,
|
|
10036
|
+
{ targetName, mode: resolvedMode }
|
|
10037
|
+
);
|
|
10038
|
+
return {
|
|
10039
|
+
kind: "started",
|
|
10040
|
+
prdRef,
|
|
10041
|
+
mode: resolvedMode,
|
|
10042
|
+
start: adoptedStart,
|
|
10043
|
+
diagnostics: []
|
|
10044
|
+
};
|
|
10045
|
+
}
|
|
10046
|
+
try {
|
|
10047
|
+
await ensurePrdBranchPublished(
|
|
10048
|
+
options.repoRoot,
|
|
10049
|
+
prdRef,
|
|
10050
|
+
fetchResult.startBaseCommit
|
|
10051
|
+
);
|
|
10052
|
+
} catch (error) {
|
|
10053
|
+
return blocked(
|
|
10054
|
+
options.repoRoot,
|
|
10055
|
+
prdRef,
|
|
10056
|
+
"git",
|
|
10057
|
+
"branch_creation_failed",
|
|
10058
|
+
`Failed to create PRD Branch ${prdRef} from ${fetchResult.startBaseCommit}.`,
|
|
10059
|
+
[error instanceof Error ? error.message : String(error)].filter(
|
|
10060
|
+
(v) => Boolean(v && v.trim())
|
|
10061
|
+
)
|
|
10062
|
+
);
|
|
10063
|
+
}
|
|
10064
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10065
|
+
const start = buildStartReceipt({
|
|
10066
|
+
status: "started",
|
|
10067
|
+
targetName,
|
|
10068
|
+
prdBranch: prdRef,
|
|
10069
|
+
startBaseBranch: baseResolution.baseBranch,
|
|
10070
|
+
startBaseCommit: fetchResult.startBaseCommit,
|
|
10071
|
+
branchAction: "created",
|
|
10072
|
+
startedAt: updatedAt
|
|
10073
|
+
});
|
|
10074
|
+
persistStartingPrdRunRecord(options.repoRoot, prdRef, existingRecord, start, {
|
|
10075
|
+
targetName,
|
|
10076
|
+
mode: resolvedMode
|
|
10077
|
+
});
|
|
10078
|
+
return {
|
|
10079
|
+
kind: "started",
|
|
10080
|
+
prdRef,
|
|
10081
|
+
mode: resolvedMode,
|
|
10082
|
+
start,
|
|
10083
|
+
diagnostics: []
|
|
10084
|
+
};
|
|
10085
|
+
}
|
|
10086
|
+
function buildLaunchBlockedOutcome(prdRef, guidance, resume, start) {
|
|
10087
|
+
return {
|
|
10088
|
+
kind: "blocked",
|
|
10089
|
+
prdRef,
|
|
10090
|
+
guidance,
|
|
10091
|
+
start,
|
|
10092
|
+
resume
|
|
10093
|
+
};
|
|
10094
|
+
}
|
|
10095
|
+
function writeDrainedRecord(repoRoot2, prdRef, startReceipt, targetName, mode) {
|
|
10096
|
+
writePrdRunRecord(repoRoot2, {
|
|
10097
|
+
prdRef,
|
|
10098
|
+
status: "drained",
|
|
10099
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10100
|
+
targetName,
|
|
10101
|
+
start: startReceipt,
|
|
10102
|
+
mode
|
|
10103
|
+
});
|
|
10104
|
+
}
|
|
10105
|
+
function writeTerminalRecord(repoRoot2, prdRef, status, targetName, startReceipt, prdBranch, mode) {
|
|
10106
|
+
writePrdRunRecord(repoRoot2, {
|
|
10107
|
+
prdRef,
|
|
10108
|
+
status,
|
|
10109
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10110
|
+
targetName,
|
|
10111
|
+
prdBranch,
|
|
10112
|
+
start: startReceipt,
|
|
10113
|
+
mode
|
|
10114
|
+
});
|
|
10115
|
+
}
|
|
10116
|
+
function validatePrdRunStartEvidence(record, context) {
|
|
10117
|
+
if (!record.start) {
|
|
10118
|
+
return {
|
|
10119
|
+
ok: false,
|
|
10120
|
+
guidance: {
|
|
10121
|
+
blockedGate: "branch-state",
|
|
10122
|
+
failureCode: "missing_start_evidence",
|
|
10123
|
+
reason: `PRD Run ${context.prdRef} has no start receipt.`,
|
|
10124
|
+
repairability: "automatic-retry-safe",
|
|
10125
|
+
diagnostics: [],
|
|
10126
|
+
offendingPaths: []
|
|
10127
|
+
}
|
|
10128
|
+
};
|
|
10129
|
+
}
|
|
10130
|
+
if (!record.start.startBaseBranch) {
|
|
10131
|
+
return {
|
|
10132
|
+
ok: false,
|
|
10133
|
+
guidance: {
|
|
10134
|
+
blockedGate: "branch-state",
|
|
10135
|
+
failureCode: "missing_start_base_branch",
|
|
10136
|
+
reason: `PRD Run ${context.prdRef} start receipt missing startBaseBranch.`,
|
|
10137
|
+
repairability: "operator-action",
|
|
10138
|
+
diagnostics: [],
|
|
10139
|
+
offendingPaths: []
|
|
10140
|
+
}
|
|
10141
|
+
};
|
|
10142
|
+
}
|
|
10143
|
+
if (record.start.startBaseBranch !== context.baseBranch) {
|
|
10144
|
+
return {
|
|
10145
|
+
ok: false,
|
|
10146
|
+
guidance: {
|
|
10147
|
+
blockedGate: "branch-state",
|
|
10148
|
+
failureCode: "base_mismatch",
|
|
10149
|
+
reason: `PRD Run ${context.prdRef} startBaseBranch "${record.start.startBaseBranch}" does not match expected "${context.baseBranch}".`,
|
|
10150
|
+
repairability: "operator-action",
|
|
10151
|
+
diagnostics: [
|
|
10152
|
+
`Recorded startBaseBranch: ${record.start.startBaseBranch}`,
|
|
10153
|
+
`Expected baseBranch: ${context.baseBranch}`
|
|
10154
|
+
],
|
|
10155
|
+
offendingPaths: []
|
|
10156
|
+
}
|
|
10157
|
+
};
|
|
10158
|
+
}
|
|
10159
|
+
if (!record.start.prdBranch) {
|
|
10160
|
+
return {
|
|
10161
|
+
ok: false,
|
|
10162
|
+
guidance: {
|
|
10163
|
+
blockedGate: "branch-state",
|
|
10164
|
+
failureCode: "missing_prd_branch",
|
|
10165
|
+
reason: `PRD Run ${context.prdRef} start receipt missing prdBranch.`,
|
|
10166
|
+
repairability: "operator-action",
|
|
10167
|
+
diagnostics: [],
|
|
10168
|
+
offendingPaths: []
|
|
10169
|
+
}
|
|
10170
|
+
};
|
|
10171
|
+
}
|
|
10172
|
+
if (record.mode && record.mode !== context.mode) {
|
|
10173
|
+
return {
|
|
10174
|
+
ok: false,
|
|
10175
|
+
guidance: {
|
|
10176
|
+
blockedGate: "branch-state",
|
|
10177
|
+
failureCode: "mode_mismatch",
|
|
10178
|
+
reason: `PRD Run ${context.prdRef} mode mismatch: recorded "${record.mode}" but expected "${context.mode}".`,
|
|
10179
|
+
repairability: "operator-action",
|
|
10180
|
+
diagnostics: [
|
|
10181
|
+
`Recorded mode: ${record.mode}`,
|
|
10182
|
+
`Expected mode: ${context.mode}`
|
|
10183
|
+
],
|
|
10184
|
+
offendingPaths: []
|
|
10185
|
+
}
|
|
10186
|
+
};
|
|
10187
|
+
}
|
|
10188
|
+
if (record.prdRef !== context.prdRef) {
|
|
10189
|
+
return {
|
|
10190
|
+
ok: false,
|
|
10191
|
+
guidance: {
|
|
10192
|
+
blockedGate: "branch-state",
|
|
10193
|
+
failureCode: "prd_ref_mismatch",
|
|
10194
|
+
reason: `PRD Run prdRef mismatch: "${record.prdRef}" does not match expected "${context.prdRef}".`,
|
|
10195
|
+
repairability: "operator-action",
|
|
10196
|
+
diagnostics: [
|
|
10197
|
+
`Recorded prdRef: ${record.prdRef}`,
|
|
10198
|
+
`Expected prdRef: ${context.prdRef}`
|
|
10199
|
+
],
|
|
10200
|
+
offendingPaths: []
|
|
10201
|
+
}
|
|
10202
|
+
};
|
|
10203
|
+
}
|
|
10204
|
+
return { ok: true };
|
|
10205
|
+
}
|
|
10206
|
+
function validatePrdRunDrainEvidence(record, context) {
|
|
10207
|
+
const start = record.start;
|
|
10208
|
+
if (!start) {
|
|
10209
|
+
return {
|
|
10210
|
+
ok: false,
|
|
10211
|
+
guidance: {
|
|
10212
|
+
blockedGate: "queue",
|
|
10213
|
+
failureCode: "missing_drain_evidence",
|
|
10214
|
+
reason: `PRD Run ${context.prdRef} has no start receipt for drain validation.`,
|
|
10215
|
+
repairability: "automatic-retry-safe",
|
|
10216
|
+
diagnostics: [],
|
|
10217
|
+
offendingPaths: []
|
|
10218
|
+
}
|
|
10219
|
+
};
|
|
10220
|
+
}
|
|
10221
|
+
if (!start.queueStartedAt) {
|
|
10222
|
+
return {
|
|
10223
|
+
ok: false,
|
|
10224
|
+
guidance: {
|
|
10225
|
+
blockedGate: "queue",
|
|
10226
|
+
failureCode: "missing_queue_started_at",
|
|
10227
|
+
reason: `PRD Run ${context.prdRef} drain evidence missing queueStartedAt.`,
|
|
10228
|
+
repairability: "automatic-retry-safe",
|
|
10229
|
+
diagnostics: [],
|
|
10230
|
+
offendingPaths: []
|
|
10231
|
+
}
|
|
10232
|
+
};
|
|
10233
|
+
}
|
|
10234
|
+
if (start.queueCommand !== "queue-run") {
|
|
10235
|
+
return {
|
|
10236
|
+
ok: false,
|
|
10237
|
+
guidance: {
|
|
10238
|
+
blockedGate: "queue",
|
|
10239
|
+
failureCode: "missing_queue_command",
|
|
10240
|
+
reason: `PRD Run ${context.prdRef} drain evidence missing queue-run command.`,
|
|
10241
|
+
repairability: "automatic-retry-safe",
|
|
10242
|
+
diagnostics: [],
|
|
10243
|
+
offendingPaths: []
|
|
10244
|
+
}
|
|
10245
|
+
};
|
|
10246
|
+
}
|
|
10247
|
+
if (!start.queueDrainedAt) {
|
|
10248
|
+
return {
|
|
10249
|
+
ok: false,
|
|
10250
|
+
guidance: {
|
|
10251
|
+
blockedGate: "queue",
|
|
10252
|
+
failureCode: "missing_queue_drained_at",
|
|
10253
|
+
reason: `PRD Run ${context.prdRef} drain evidence missing queueDrainedAt.`,
|
|
10254
|
+
repairability: "automatic-retry-safe",
|
|
10255
|
+
diagnostics: [],
|
|
10256
|
+
offendingPaths: []
|
|
10257
|
+
}
|
|
10258
|
+
};
|
|
10259
|
+
}
|
|
10260
|
+
if (typeof start.queueProcessedCount !== "number") {
|
|
10261
|
+
return {
|
|
10262
|
+
ok: false,
|
|
10263
|
+
guidance: {
|
|
10264
|
+
blockedGate: "queue",
|
|
10265
|
+
failureCode: "missing_queue_processed_count",
|
|
10266
|
+
reason: `PRD Run ${context.prdRef} drain evidence missing queueProcessedCount.`,
|
|
10267
|
+
repairability: "automatic-retry-safe",
|
|
10268
|
+
diagnostics: [],
|
|
10269
|
+
offendingPaths: []
|
|
10270
|
+
}
|
|
10271
|
+
};
|
|
10272
|
+
}
|
|
10273
|
+
if (start.queueProcessedCount <= 0) {
|
|
10274
|
+
return {
|
|
10275
|
+
ok: false,
|
|
10276
|
+
guidance: {
|
|
10277
|
+
blockedGate: "queue",
|
|
10278
|
+
failureCode: "zero_processed",
|
|
10279
|
+
reason: `PRD Run ${context.prdRef} queue drain processed zero issues.`,
|
|
10280
|
+
repairability: "automatic-retry-safe",
|
|
10281
|
+
diagnostics: [`queueProcessedCount: ${start.queueProcessedCount}`],
|
|
10282
|
+
offendingPaths: []
|
|
10283
|
+
}
|
|
10284
|
+
};
|
|
10285
|
+
}
|
|
10286
|
+
if (start.prdBranch !== context.prdBranch) {
|
|
10287
|
+
return {
|
|
10288
|
+
ok: false,
|
|
10289
|
+
guidance: {
|
|
10290
|
+
blockedGate: "queue",
|
|
10291
|
+
failureCode: "prd_branch_mismatch",
|
|
10292
|
+
reason: `PRD Run ${context.prdRef} prdBranch "${start.prdBranch}" does not match expected "${context.prdBranch}".`,
|
|
10293
|
+
repairability: "operator-action",
|
|
10294
|
+
diagnostics: [
|
|
10295
|
+
`Recorded prdBranch: ${start.prdBranch}`,
|
|
10296
|
+
`Expected prdBranch: ${context.prdBranch}`
|
|
10297
|
+
],
|
|
10298
|
+
offendingPaths: []
|
|
10299
|
+
}
|
|
10300
|
+
};
|
|
10301
|
+
}
|
|
10302
|
+
return { ok: true };
|
|
10303
|
+
}
|
|
10304
|
+
async function launchPrdRun(options) {
|
|
10305
|
+
const prdRef = normalizePrdRunRef(options.prdRef);
|
|
10306
|
+
const existingRecord = readPrdRun(options.repoRoot, prdRef);
|
|
10307
|
+
const resolvedLaunchMode = options.config ? (() => {
|
|
10308
|
+
try {
|
|
10309
|
+
const target = resolveTarget(options.config, options.targetName);
|
|
10310
|
+
return resolvePrdRunMode(target).mode;
|
|
10311
|
+
} catch {
|
|
10312
|
+
return void 0;
|
|
10313
|
+
}
|
|
10314
|
+
})() : void 0;
|
|
10315
|
+
if (resolvedLaunchMode && existingRecord.record?.mode && existingRecord.record.mode !== resolvedLaunchMode) {
|
|
10316
|
+
return buildLaunchBlockedOutcome(
|
|
10317
|
+
prdRef,
|
|
10318
|
+
{
|
|
10319
|
+
blockedGate: "branch-state",
|
|
10320
|
+
failureCode: "mode_mismatch",
|
|
10321
|
+
reason: `PRD Run ${prdRef} mode mismatch: recorded "${existingRecord.record.mode}" but resolved "${resolvedLaunchMode}". Resolve by using the correct target.`,
|
|
10322
|
+
repairability: "operator-action",
|
|
10323
|
+
diagnostics: [
|
|
10324
|
+
`Recorded mode: ${existingRecord.record.mode}`,
|
|
10325
|
+
`Resolved mode: ${resolvedLaunchMode}`
|
|
10326
|
+
],
|
|
10327
|
+
offendingPaths: []
|
|
10328
|
+
},
|
|
10329
|
+
{ attempted: [], skipped: ["start", "queue"], resumed: [] }
|
|
10330
|
+
);
|
|
10331
|
+
}
|
|
10332
|
+
const plan = planPrdRunLaunchResume(existingRecord.record);
|
|
10333
|
+
if (plan.blocked === "final_reviewed-incompatible") {
|
|
10334
|
+
return buildLaunchBlockedOutcome(
|
|
10335
|
+
prdRef,
|
|
10336
|
+
{
|
|
10337
|
+
blockedGate: "final-review",
|
|
10338
|
+
failureCode: "final_reviewed_incompatible",
|
|
10339
|
+
reason: `PRD Run ${prdRef} has status "final_reviewed", which is no longer a valid launch status. PRD-wide Final Review has been removed from the launch lifecycle. Update the PRD Run state to "drained" and rerun launch, or start a new PRD Run.`,
|
|
10340
|
+
repairability: "unsupported-state",
|
|
10341
|
+
diagnostics: [
|
|
10342
|
+
`PRD Run ${prdRef} has obsolete status "final_reviewed". PRD-wide Final Review has been removed from the launch lifecycle.`
|
|
10343
|
+
],
|
|
10344
|
+
offendingPaths: []
|
|
10345
|
+
},
|
|
10346
|
+
plan
|
|
10347
|
+
);
|
|
10348
|
+
}
|
|
10349
|
+
if (existingRecord.record?.status === "completed_prd_branch" || existingRecord.record?.status === "complete" || existingRecord.record?.status === "completed_local_branch") {
|
|
10350
|
+
return {
|
|
10351
|
+
kind: "already-terminal",
|
|
10352
|
+
prdRef,
|
|
10353
|
+
status: existingRecord.record.status === "complete" ? "complete" : existingRecord.record.status,
|
|
10354
|
+
prdBranch: existingRecord.record.prdBranch,
|
|
10355
|
+
diagnostics: [
|
|
10356
|
+
`PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`
|
|
10357
|
+
],
|
|
10358
|
+
resume: plan
|
|
10359
|
+
};
|
|
10360
|
+
}
|
|
10361
|
+
if (existingRecord.record?.status === "drained") {
|
|
10362
|
+
const record = existingRecord.record;
|
|
10363
|
+
if (!record.start) {
|
|
10364
|
+
return buildLaunchBlockedOutcome(
|
|
10365
|
+
prdRef,
|
|
10366
|
+
{
|
|
10367
|
+
blockedGate: "queue",
|
|
10368
|
+
failureCode: "missing_drain_evidence",
|
|
10369
|
+
reason: `PRD Run ${prdRef} is drained but missing start receipt.`,
|
|
10370
|
+
repairability: "automatic-retry-safe",
|
|
10371
|
+
diagnostics: [],
|
|
10372
|
+
offendingPaths: []
|
|
10373
|
+
},
|
|
10374
|
+
plan
|
|
10375
|
+
);
|
|
10376
|
+
}
|
|
10377
|
+
const drainMode = resolvedLaunchMode ?? record.mode ?? "github";
|
|
10378
|
+
const resolvedTarget = options.config ? resolveTarget(options.config, options.targetName) : null;
|
|
10379
|
+
const targetBaseBranch = resolvedTarget?.baseBranch ?? "main";
|
|
10380
|
+
const baseResolution = resolvePrdRunBaseBranch({
|
|
10381
|
+
targetBaseBranch,
|
|
10382
|
+
baseBranchOverride: options.baseBranchOverride
|
|
10383
|
+
});
|
|
10384
|
+
if (!baseResolution.ok) {
|
|
10385
|
+
return buildLaunchBlockedOutcome(
|
|
10386
|
+
prdRef,
|
|
10387
|
+
{
|
|
10388
|
+
blockedGate: baseResolution.gate,
|
|
10389
|
+
failureCode: "base_resolution_failed",
|
|
10390
|
+
reason: baseResolution.reason,
|
|
10391
|
+
repairability: "operator-action",
|
|
10392
|
+
diagnostics: [...baseResolution.diagnostics],
|
|
10393
|
+
offendingPaths: [...baseResolution.offendingPaths]
|
|
10394
|
+
},
|
|
10395
|
+
plan
|
|
10396
|
+
);
|
|
10397
|
+
}
|
|
10398
|
+
const drainContext = {
|
|
10399
|
+
prdRef,
|
|
10400
|
+
mode: drainMode,
|
|
10401
|
+
baseBranch: baseResolution.baseBranch,
|
|
10402
|
+
prdBranch: record.start.prdBranch
|
|
10403
|
+
};
|
|
10404
|
+
const startEvidence = validatePrdRunStartEvidence(record, drainContext);
|
|
10405
|
+
if (!startEvidence.ok) {
|
|
10406
|
+
return buildLaunchBlockedOutcome(prdRef, startEvidence.guidance, plan);
|
|
10407
|
+
}
|
|
10408
|
+
const drainEvidence = validatePrdRunDrainEvidence(record, drainContext);
|
|
10409
|
+
if (!drainEvidence.ok) {
|
|
10410
|
+
return buildLaunchBlockedOutcome(prdRef, drainEvidence.guidance, plan);
|
|
10411
|
+
}
|
|
10412
|
+
const terminalStatus = drainMode === "local" ? "completed_local_branch" : "completed_prd_branch";
|
|
10413
|
+
writeTerminalRecord(
|
|
10414
|
+
options.repoRoot,
|
|
10415
|
+
prdRef,
|
|
10416
|
+
terminalStatus,
|
|
10417
|
+
record.targetName ?? options.targetName,
|
|
10418
|
+
record.start,
|
|
10419
|
+
record.start.prdBranch,
|
|
10420
|
+
drainMode
|
|
10421
|
+
);
|
|
10422
|
+
return {
|
|
10423
|
+
kind: "completed",
|
|
10424
|
+
prdRef,
|
|
10425
|
+
status: terminalStatus,
|
|
10426
|
+
prdBranch: record.start.prdBranch,
|
|
10427
|
+
diagnostics: [
|
|
10428
|
+
`PRD Run ${prdRef} completed from drained state. Branch: ${record.start.prdBranch}.`
|
|
10429
|
+
],
|
|
10430
|
+
resume: plan
|
|
10431
|
+
};
|
|
10432
|
+
}
|
|
10433
|
+
if (plan.blocked === "missing-start-receipt") {
|
|
10434
|
+
return buildLaunchBlockedOutcome(
|
|
10435
|
+
prdRef,
|
|
10436
|
+
{
|
|
10437
|
+
blockedGate: "branch-state",
|
|
10438
|
+
failureCode: "missing_start_receipt",
|
|
10439
|
+
reason: `PRD Run ${prdRef} is missing branch/start state. Run prd-run start or rerun prd-run launch.`,
|
|
10440
|
+
repairability: "automatic-retry-safe",
|
|
10441
|
+
diagnostics: [
|
|
10442
|
+
`PRD Run ${prdRef} is in status "${existingRecord.record?.status}" but has no start receipt.`
|
|
9768
10443
|
],
|
|
9769
10444
|
offendingPaths: []
|
|
10445
|
+
},
|
|
10446
|
+
plan
|
|
10447
|
+
);
|
|
10448
|
+
}
|
|
10449
|
+
let startOutcome;
|
|
10450
|
+
if (!plan.skipped.includes("start")) {
|
|
10451
|
+
startOutcome = await startPrdRun({
|
|
10452
|
+
repoRoot: options.repoRoot,
|
|
10453
|
+
prdRef,
|
|
10454
|
+
targetName: options.targetName,
|
|
10455
|
+
config: options.config,
|
|
10456
|
+
issueProvider: options.issueProvider,
|
|
10457
|
+
prProvider: options.prProvider,
|
|
10458
|
+
executionProvider: options.executionProvider,
|
|
10459
|
+
logger: options.logger,
|
|
10460
|
+
adoptExistingBranch: options.adoptExistingBranch,
|
|
10461
|
+
baseBranchOverride: options.baseBranchOverride
|
|
10462
|
+
});
|
|
10463
|
+
if (startOutcome.kind === "blocked") {
|
|
10464
|
+
return {
|
|
10465
|
+
kind: "blocked",
|
|
10466
|
+
prdRef,
|
|
10467
|
+
guidance: startOutcome.guidance,
|
|
10468
|
+
start: startOutcome,
|
|
10469
|
+
resume: plan
|
|
9770
10470
|
};
|
|
9771
|
-
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
9772
|
-
targetName
|
|
9773
|
-
});
|
|
9774
|
-
return buildBlockedStartResult(prdRef, failure);
|
|
9775
10471
|
}
|
|
9776
|
-
|
|
9777
|
-
|
|
10472
|
+
}
|
|
10473
|
+
const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
10474
|
+
const startReceipt = currentRecord?.start;
|
|
10475
|
+
const targetName = currentRecord?.targetName ?? options.targetName;
|
|
10476
|
+
const explicitMode = resolvedLaunchMode ?? currentRecord?.mode;
|
|
10477
|
+
if (startReceipt) {
|
|
10478
|
+
startReceipt.queueStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10479
|
+
startReceipt.queueCommand = "queue-run";
|
|
10480
|
+
}
|
|
10481
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10482
|
+
prdRef,
|
|
10483
|
+
status: "running",
|
|
10484
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10485
|
+
targetName,
|
|
10486
|
+
start: startReceipt,
|
|
10487
|
+
mode: explicitMode
|
|
10488
|
+
});
|
|
10489
|
+
if (explicitMode === "local") {
|
|
10490
|
+
return await launchLocalQueueDrain(
|
|
10491
|
+
options,
|
|
10492
|
+
prdRef,
|
|
10493
|
+
startReceipt,
|
|
10494
|
+
targetName,
|
|
10495
|
+
explicitMode,
|
|
10496
|
+
plan,
|
|
10497
|
+
startOutcome
|
|
10498
|
+
);
|
|
10499
|
+
}
|
|
10500
|
+
return await launchGithubQueueDrain(
|
|
10501
|
+
options,
|
|
10502
|
+
prdRef,
|
|
10503
|
+
startReceipt,
|
|
10504
|
+
targetName,
|
|
10505
|
+
explicitMode,
|
|
10506
|
+
plan,
|
|
10507
|
+
startOutcome
|
|
10508
|
+
);
|
|
10509
|
+
}
|
|
10510
|
+
async function launchLocalQueueDrain(options, prdRef, startReceipt, targetName, mode, plan, startOutcome) {
|
|
10511
|
+
if (!startReceipt) {
|
|
10512
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10513
|
+
prdRef,
|
|
10514
|
+
status: "blocked",
|
|
10515
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10516
|
+
targetName,
|
|
10517
|
+
blockedGate: "branch-state",
|
|
10518
|
+
blockedReason: `PRD Run ${prdRef} is missing start receipt before local queue drain.`,
|
|
10519
|
+
diagnostics: [],
|
|
10520
|
+
offendingPaths: []
|
|
10521
|
+
});
|
|
10522
|
+
return buildLaunchBlockedOutcome(
|
|
10523
|
+
prdRef,
|
|
10524
|
+
{
|
|
10525
|
+
blockedGate: "branch-state",
|
|
10526
|
+
failureCode: "missing_start_receipt",
|
|
10527
|
+
reason: `PRD Run ${prdRef} is missing start receipt before local queue drain.`,
|
|
10528
|
+
repairability: "automatic-retry-safe",
|
|
10529
|
+
diagnostics: [],
|
|
10530
|
+
offendingPaths: []
|
|
10531
|
+
},
|
|
10532
|
+
plan,
|
|
10533
|
+
startOutcome
|
|
10534
|
+
);
|
|
10535
|
+
}
|
|
10536
|
+
const localStoreDir = join19(
|
|
10537
|
+
options.repoRoot,
|
|
10538
|
+
".pourkit",
|
|
10539
|
+
"local-prd-runs",
|
|
10540
|
+
prdRef
|
|
10541
|
+
);
|
|
10542
|
+
let localStoreReady = false;
|
|
10543
|
+
if (existsSync15(localStoreDir)) {
|
|
10544
|
+
const localStorePath = join19(localStoreDir, "prd.json");
|
|
10545
|
+
try {
|
|
10546
|
+
const content = JSON.parse(readFileSync17(localStorePath, "utf8"));
|
|
10547
|
+
localStoreReady = content?.id === prdRef && content?.kind === "prd";
|
|
10548
|
+
} catch {
|
|
10549
|
+
localStoreReady = false;
|
|
10550
|
+
}
|
|
10551
|
+
}
|
|
10552
|
+
if (!localStoreReady) {
|
|
10553
|
+
const 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.`;
|
|
10554
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10555
|
+
prdRef,
|
|
10556
|
+
status: "blocked",
|
|
10557
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10558
|
+
targetName,
|
|
10559
|
+
start: startReceipt,
|
|
10560
|
+
mode,
|
|
10561
|
+
blockedGate: "branch-state",
|
|
10562
|
+
blockedReason: reason,
|
|
10563
|
+
diagnostics: [
|
|
10564
|
+
`Expected store path: .pourkit/local-prd-runs/${prdRef}/prd.json`,
|
|
10565
|
+
`Expected PRD ID: ${prdRef}`
|
|
10566
|
+
],
|
|
10567
|
+
offendingPaths: []
|
|
10568
|
+
});
|
|
10569
|
+
return buildLaunchBlockedOutcome(
|
|
10570
|
+
prdRef,
|
|
10571
|
+
{
|
|
10572
|
+
blockedGate: "branch-state",
|
|
10573
|
+
failureCode: "local_store_invalid",
|
|
10574
|
+
reason,
|
|
10575
|
+
repairability: "operator-action",
|
|
10576
|
+
diagnostics: [
|
|
10577
|
+
`Expected store path: .pourkit/local-prd-runs/${prdRef}/prd.json`,
|
|
10578
|
+
`Expected PRD ID: ${prdRef}`
|
|
10579
|
+
],
|
|
10580
|
+
offendingPaths: []
|
|
10581
|
+
},
|
|
10582
|
+
plan,
|
|
10583
|
+
startOutcome
|
|
10584
|
+
);
|
|
10585
|
+
}
|
|
10586
|
+
const queueResult = await runLocalQueueLoop(
|
|
10587
|
+
prdRef,
|
|
10588
|
+
options.repoRoot,
|
|
10589
|
+
options.issueProvider
|
|
10590
|
+
);
|
|
10591
|
+
if (!queueResult.ok) {
|
|
10592
|
+
const failureCode = queueResult.failureCode ?? "queue_error";
|
|
10593
|
+
const reason = queueResult.repairGuidance ?? `Local queue loop blocked: ${failureCode}`;
|
|
10594
|
+
const diagnostics = [
|
|
10595
|
+
`Local queue loop failed with failureCode "${failureCode}".`,
|
|
10596
|
+
...queueResult.repairGuidance ? [`Repair guidance: ${queueResult.repairGuidance}`] : []
|
|
10597
|
+
];
|
|
10598
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10599
|
+
prdRef,
|
|
10600
|
+
status: "blocked",
|
|
10601
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9778
10602
|
targetName,
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
10603
|
+
start: startReceipt,
|
|
10604
|
+
mode,
|
|
10605
|
+
blockedGate: "queue",
|
|
10606
|
+
blockedReason: reason,
|
|
10607
|
+
diagnostics,
|
|
10608
|
+
offendingPaths: []
|
|
9784
10609
|
});
|
|
9785
|
-
|
|
9786
|
-
options.repoRoot,
|
|
10610
|
+
return buildLaunchBlockedOutcome(
|
|
9787
10611
|
prdRef,
|
|
9788
|
-
existingRecord,
|
|
9789
10612
|
{
|
|
9790
|
-
|
|
10613
|
+
blockedGate: "queue",
|
|
10614
|
+
failureCode,
|
|
10615
|
+
reason,
|
|
10616
|
+
repairability: "automatic-retry-safe",
|
|
10617
|
+
diagnostics,
|
|
10618
|
+
offendingPaths: []
|
|
9791
10619
|
},
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
mode: resolvedMode
|
|
9795
|
-
}
|
|
10620
|
+
plan,
|
|
10621
|
+
startOutcome
|
|
9796
10622
|
);
|
|
9797
|
-
|
|
10623
|
+
}
|
|
10624
|
+
if (queueResult.blockedIssues.length > 0) {
|
|
10625
|
+
const reason = `Queue processed complete but ${queueResult.blockedIssues.length} blocked child issue(s) remain.`;
|
|
10626
|
+
const diagnostics = [
|
|
10627
|
+
reason,
|
|
10628
|
+
`Blocked issues: ${queueResult.blockedIssues.join(", ")}`,
|
|
10629
|
+
"Resolve blocked children before parent close."
|
|
10630
|
+
];
|
|
10631
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10632
|
+
prdRef,
|
|
10633
|
+
status: "blocked",
|
|
10634
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10635
|
+
targetName,
|
|
10636
|
+
start: startReceipt,
|
|
10637
|
+
mode,
|
|
10638
|
+
blockedGate: "queue",
|
|
10639
|
+
blockedReason: reason,
|
|
10640
|
+
diagnostics,
|
|
10641
|
+
offendingPaths: []
|
|
10642
|
+
});
|
|
10643
|
+
return buildLaunchBlockedOutcome(
|
|
10644
|
+
prdRef,
|
|
9798
10645
|
{
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
10646
|
+
blockedGate: "queue",
|
|
10647
|
+
failureCode: "blocked_child_issues",
|
|
10648
|
+
reason,
|
|
10649
|
+
repairability: "operator-action",
|
|
10650
|
+
diagnostics,
|
|
10651
|
+
offendingPaths: []
|
|
9803
10652
|
},
|
|
9804
|
-
|
|
10653
|
+
plan,
|
|
10654
|
+
startOutcome
|
|
9805
10655
|
);
|
|
9806
10656
|
}
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
10657
|
+
startReceipt.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10658
|
+
startReceipt.queueProcessedCount = queueResult.completedIssues.length + queueResult.blockedIssues.length;
|
|
10659
|
+
const prdBranch = startReceipt.prdBranch ?? `local/${prdRef}`;
|
|
10660
|
+
writeDrainedRecord(options.repoRoot, prdRef, startReceipt, targetName, mode);
|
|
10661
|
+
const drainRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
10662
|
+
if (drainRecord) {
|
|
10663
|
+
const drainContext = {
|
|
9810
10664
|
prdRef,
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
const failure = {
|
|
9815
|
-
ok: false,
|
|
9816
|
-
gate: "git",
|
|
9817
|
-
reason: `Failed to create PRD Branch ${prdRef} from ${fetchResult.startBaseCommit}.`,
|
|
9818
|
-
diagnostics: [
|
|
9819
|
-
error instanceof Error ? error.message : String(error)
|
|
9820
|
-
].filter((value) => Boolean(value && value.trim())),
|
|
9821
|
-
offendingPaths: []
|
|
10665
|
+
mode,
|
|
10666
|
+
baseBranch: startReceipt.startBaseBranch,
|
|
10667
|
+
prdBranch
|
|
9822
10668
|
};
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
10669
|
+
const startEvidence = validatePrdRunStartEvidence(
|
|
10670
|
+
drainRecord,
|
|
10671
|
+
drainContext
|
|
10672
|
+
);
|
|
10673
|
+
if (!startEvidence.ok) {
|
|
10674
|
+
return buildLaunchBlockedOutcome(
|
|
10675
|
+
prdRef,
|
|
10676
|
+
startEvidence.guidance,
|
|
10677
|
+
plan,
|
|
10678
|
+
startOutcome
|
|
10679
|
+
);
|
|
10680
|
+
}
|
|
10681
|
+
const drainEvidence = validatePrdRunDrainEvidence(
|
|
10682
|
+
drainRecord,
|
|
10683
|
+
drainContext
|
|
10684
|
+
);
|
|
10685
|
+
if (!drainEvidence.ok) {
|
|
10686
|
+
return buildLaunchBlockedOutcome(
|
|
10687
|
+
prdRef,
|
|
10688
|
+
drainEvidence.guidance,
|
|
10689
|
+
plan,
|
|
10690
|
+
startOutcome
|
|
10691
|
+
);
|
|
10692
|
+
}
|
|
9827
10693
|
}
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
status: "started",
|
|
9831
|
-
targetName,
|
|
9832
|
-
prdBranch: prdRef,
|
|
9833
|
-
startBaseBranch: baseResolution.baseBranch,
|
|
9834
|
-
startBaseCommit: fetchResult.startBaseCommit,
|
|
9835
|
-
branchAction: "created",
|
|
9836
|
-
startedAt: updatedAt
|
|
9837
|
-
});
|
|
9838
|
-
const {
|
|
9839
|
-
blockedGate: _blockedGate,
|
|
9840
|
-
blockedReason: _blockedReason,
|
|
9841
|
-
diagnostics: _diagnostics,
|
|
9842
|
-
offendingPaths: _offendingPaths,
|
|
9843
|
-
prdBranch: _prdBranch,
|
|
9844
|
-
start: _start,
|
|
9845
|
-
...cleanRecord
|
|
9846
|
-
} = existingRecord.record ?? { prdRef };
|
|
9847
|
-
writePrdRunRecord(options.repoRoot, {
|
|
9848
|
-
...cleanRecord,
|
|
10694
|
+
writeTerminalRecord(
|
|
10695
|
+
options.repoRoot,
|
|
9849
10696
|
prdRef,
|
|
9850
|
-
|
|
9851
|
-
updatedAt,
|
|
10697
|
+
"completed_local_branch",
|
|
9852
10698
|
targetName,
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
return await processStartResult(
|
|
9857
|
-
{
|
|
9858
|
-
prdRef,
|
|
9859
|
-
status: "starting",
|
|
9860
|
-
start,
|
|
9861
|
-
diagnostics: []
|
|
9862
|
-
},
|
|
9863
|
-
options
|
|
10699
|
+
startReceipt,
|
|
10700
|
+
prdBranch,
|
|
10701
|
+
mode
|
|
9864
10702
|
);
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
if (!options.config) {
|
|
9868
|
-
return startResult;
|
|
9869
|
-
}
|
|
9870
|
-
const prdRef = startResult.prdRef;
|
|
9871
|
-
const start = startResult.start;
|
|
9872
|
-
const repoRoot2 = options.repoRoot;
|
|
9873
|
-
start.queueStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9874
|
-
start.queueCommand = "queue-run";
|
|
9875
|
-
const existingRecord = readPrdRun(repoRoot2, prdRef);
|
|
9876
|
-
let resolvedMode;
|
|
9877
|
-
try {
|
|
9878
|
-
const target = options.config ? resolveTarget(options.config, start.targetName) : null;
|
|
9879
|
-
resolvedMode = target?.strategy ? resolvePrdRunMode(target) : void 0;
|
|
9880
|
-
} catch {
|
|
9881
|
-
resolvedMode = void 0;
|
|
9882
|
-
}
|
|
9883
|
-
const modeForRecord = resolvedMode?.mode;
|
|
9884
|
-
if (modeForRecord !== "local" && (!options.issueProvider || !options.prProvider || !options.executionProvider || !options.logger)) {
|
|
9885
|
-
return startResult;
|
|
9886
|
-
}
|
|
9887
|
-
writePrdRunRecord(repoRoot2, {
|
|
10703
|
+
return {
|
|
10704
|
+
kind: "completed",
|
|
9888
10705
|
prdRef,
|
|
9889
|
-
status: "
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
start,
|
|
9893
|
-
|
|
9894
|
-
}
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
10706
|
+
status: "completed_local_branch",
|
|
10707
|
+
prdBranch,
|
|
10708
|
+
diagnostics: [`Local PRD Run ${prdRef} completed. Branch: ${prdBranch}.`],
|
|
10709
|
+
start: startOutcome,
|
|
10710
|
+
resume: plan
|
|
10711
|
+
};
|
|
10712
|
+
}
|
|
10713
|
+
async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName, mode, plan, startOutcome) {
|
|
10714
|
+
if (!options.issueProvider || !options.prProvider || !options.executionProvider || !options.logger) {
|
|
10715
|
+
const reason = `PRD Run ${prdRef} requires issueProvider, prProvider, executionProvider, and logger for GitHub-backed queue drain.`;
|
|
10716
|
+
writePrdRunRecord(options.repoRoot, {
|
|
9898
10717
|
prdRef,
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
mode: modeForRecord,
|
|
9916
|
-
blockedGate: "queue",
|
|
9917
|
-
blockedReason: reason,
|
|
9918
|
-
diagnostics,
|
|
9919
|
-
offendingPaths: []
|
|
9920
|
-
});
|
|
9921
|
-
return {
|
|
9922
|
-
prdRef,
|
|
9923
|
-
status: "blocked",
|
|
10718
|
+
status: "blocked",
|
|
10719
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10720
|
+
targetName,
|
|
10721
|
+
start: startReceipt,
|
|
10722
|
+
mode,
|
|
10723
|
+
blockedGate: "queue",
|
|
10724
|
+
blockedReason: reason,
|
|
10725
|
+
diagnostics: [
|
|
10726
|
+
"Missing one or more required providers for GitHub-backed mode.",
|
|
10727
|
+
"Provide --issue-provider, --pr-provider, --execution-provider, and --logger options."
|
|
10728
|
+
],
|
|
10729
|
+
offendingPaths: []
|
|
10730
|
+
});
|
|
10731
|
+
return buildLaunchBlockedOutcome(
|
|
10732
|
+
prdRef,
|
|
10733
|
+
{
|
|
9924
10734
|
blockedGate: "queue",
|
|
9925
|
-
|
|
9926
|
-
diagnostics,
|
|
9927
|
-
offendingPaths: []
|
|
9928
|
-
};
|
|
9929
|
-
}
|
|
9930
|
-
if (queueResult.blockedIssues.length > 0) {
|
|
9931
|
-
const reason = `Queue processed complete but ${queueResult.blockedIssues.length} blocked child issue(s) remain.`;
|
|
9932
|
-
const diagnostics = [
|
|
10735
|
+
failureCode: "missing_github_providers",
|
|
9933
10736
|
reason,
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
prdRef,
|
|
9939
|
-
status: "blocked",
|
|
9940
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9941
|
-
targetName: start.targetName,
|
|
9942
|
-
start,
|
|
9943
|
-
mode: modeForRecord,
|
|
9944
|
-
blockedGate: "queue",
|
|
9945
|
-
blockedReason: reason,
|
|
9946
|
-
diagnostics,
|
|
10737
|
+
repairability: "operator-action",
|
|
10738
|
+
diagnostics: [
|
|
10739
|
+
"Missing one or more required providers for GitHub-backed mode."
|
|
10740
|
+
],
|
|
9947
10741
|
offendingPaths: []
|
|
9948
|
-
}
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
10742
|
+
},
|
|
10743
|
+
plan,
|
|
10744
|
+
startOutcome
|
|
10745
|
+
);
|
|
10746
|
+
}
|
|
10747
|
+
if (!startReceipt) {
|
|
10748
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10749
|
+
prdRef,
|
|
10750
|
+
status: "blocked",
|
|
10751
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10752
|
+
targetName,
|
|
10753
|
+
mode,
|
|
10754
|
+
blockedGate: "branch-state",
|
|
10755
|
+
blockedReason: `PRD Run ${prdRef} is missing start receipt before GitHub queue drain.`,
|
|
10756
|
+
diagnostics: [],
|
|
10757
|
+
offendingPaths: []
|
|
10758
|
+
});
|
|
10759
|
+
return buildLaunchBlockedOutcome(
|
|
10760
|
+
prdRef,
|
|
10761
|
+
{
|
|
10762
|
+
blockedGate: "branch-state",
|
|
10763
|
+
failureCode: "missing_start_receipt",
|
|
10764
|
+
reason: `PRD Run ${prdRef} is missing start receipt before GitHub queue drain.`,
|
|
10765
|
+
repairability: "automatic-retry-safe",
|
|
10766
|
+
diagnostics: [],
|
|
9955
10767
|
offendingPaths: []
|
|
9956
|
-
}
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
10768
|
+
},
|
|
10769
|
+
plan,
|
|
10770
|
+
startOutcome
|
|
10771
|
+
);
|
|
10772
|
+
}
|
|
10773
|
+
if (!options.config) {
|
|
10774
|
+
const reason = `PRD Run ${prdRef} requires config for GitHub-backed queue drain.`;
|
|
10775
|
+
writePrdRunRecord(options.repoRoot, {
|
|
9961
10776
|
prdRef,
|
|
9962
|
-
status: "
|
|
10777
|
+
status: "blocked",
|
|
9963
10778
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9964
|
-
targetName
|
|
9965
|
-
start,
|
|
9966
|
-
mode
|
|
10779
|
+
targetName,
|
|
10780
|
+
start: startReceipt,
|
|
10781
|
+
mode,
|
|
10782
|
+
blockedGate: "queue",
|
|
10783
|
+
blockedReason: reason,
|
|
10784
|
+
diagnostics: ["Missing config for GitHub-backed mode."],
|
|
10785
|
+
offendingPaths: []
|
|
9967
10786
|
});
|
|
9968
|
-
return
|
|
10787
|
+
return buildLaunchBlockedOutcome(
|
|
9969
10788
|
prdRef,
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
10789
|
+
{
|
|
10790
|
+
blockedGate: "queue",
|
|
10791
|
+
failureCode: "missing_config",
|
|
10792
|
+
reason,
|
|
10793
|
+
repairability: "operator-action",
|
|
10794
|
+
diagnostics: ["Missing config for GitHub-backed mode."],
|
|
10795
|
+
offendingPaths: []
|
|
10796
|
+
},
|
|
10797
|
+
plan,
|
|
10798
|
+
startOutcome
|
|
10799
|
+
);
|
|
9974
10800
|
}
|
|
9975
10801
|
const issueProvider = options.issueProvider;
|
|
9976
10802
|
const prProvider = options.prProvider;
|
|
9977
10803
|
const executionProvider = options.executionProvider;
|
|
9978
10804
|
const logger = options.logger;
|
|
10805
|
+
let resolvedModeForQueue;
|
|
10806
|
+
try {
|
|
10807
|
+
const target = resolveTarget(options.config, options.targetName);
|
|
10808
|
+
resolvedModeForQueue = resolvePrdRunMode(target);
|
|
10809
|
+
} catch {
|
|
10810
|
+
resolvedModeForQueue = void 0;
|
|
10811
|
+
}
|
|
9979
10812
|
try {
|
|
9980
10813
|
const outcome = await runQueueCommand({
|
|
9981
|
-
targetName
|
|
10814
|
+
targetName,
|
|
9982
10815
|
config: options.config,
|
|
9983
10816
|
issueProvider,
|
|
9984
10817
|
prProvider,
|
|
@@ -9986,12 +10819,12 @@ async function processStartResult(startResult, options) {
|
|
|
9986
10819
|
force: false,
|
|
9987
10820
|
loop: true,
|
|
9988
10821
|
logger,
|
|
9989
|
-
repoRoot:
|
|
10822
|
+
repoRoot: options.repoRoot,
|
|
9990
10823
|
prdRef,
|
|
9991
10824
|
queueRunContext: {
|
|
9992
10825
|
prdRef,
|
|
9993
|
-
prdBranch:
|
|
9994
|
-
prdRunMode:
|
|
10826
|
+
prdBranch: startReceipt.prdBranch,
|
|
10827
|
+
prdRunMode: resolvedModeForQueue
|
|
9995
10828
|
}
|
|
9996
10829
|
});
|
|
9997
10830
|
if (outcome.selected === null && outcome.code === "drained") {
|
|
@@ -9999,327 +10832,259 @@ async function processStartResult(startResult, options) {
|
|
|
9999
10832
|
const diagnostics = [
|
|
10000
10833
|
"Queue drained without processing issues. No PRD-scoped candidates or runnable Issues."
|
|
10001
10834
|
];
|
|
10002
|
-
writePrdRunRecord(
|
|
10835
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10003
10836
|
prdRef,
|
|
10004
10837
|
status: "blocked",
|
|
10005
10838
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10839
|
+
targetName,
|
|
10840
|
+
start: startReceipt,
|
|
10841
|
+
mode,
|
|
10006
10842
|
blockedGate: "queue",
|
|
10007
10843
|
blockedReason: diagnostics[0],
|
|
10008
10844
|
diagnostics,
|
|
10009
|
-
targetName: start.targetName,
|
|
10010
|
-
start,
|
|
10011
|
-
mode: existingRecord.record?.mode,
|
|
10012
10845
|
offendingPaths: []
|
|
10013
10846
|
});
|
|
10014
|
-
return
|
|
10847
|
+
return buildLaunchBlockedOutcome(
|
|
10015
10848
|
prdRef,
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10849
|
+
{
|
|
10850
|
+
blockedGate: "queue",
|
|
10851
|
+
failureCode: "zero_processed",
|
|
10852
|
+
reason: diagnostics[0],
|
|
10853
|
+
repairability: "automatic-retry-safe",
|
|
10854
|
+
diagnostics,
|
|
10855
|
+
offendingPaths: []
|
|
10856
|
+
},
|
|
10857
|
+
plan,
|
|
10858
|
+
startOutcome
|
|
10859
|
+
);
|
|
10860
|
+
}
|
|
10861
|
+
startReceipt.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10862
|
+
startReceipt.queueProcessedCount = outcome.processedCount;
|
|
10863
|
+
const prdBranch = startReceipt.prdBranch ?? prdRef;
|
|
10864
|
+
writeDrainedRecord(
|
|
10865
|
+
options.repoRoot,
|
|
10866
|
+
prdRef,
|
|
10867
|
+
startReceipt,
|
|
10868
|
+
targetName,
|
|
10869
|
+
mode
|
|
10870
|
+
);
|
|
10871
|
+
const drainRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
10872
|
+
if (drainRecord) {
|
|
10873
|
+
const drainMode = mode ?? "github";
|
|
10874
|
+
const drainContext = {
|
|
10875
|
+
prdRef,
|
|
10876
|
+
mode: drainMode,
|
|
10877
|
+
baseBranch: startReceipt.startBaseBranch,
|
|
10878
|
+
prdBranch
|
|
10021
10879
|
};
|
|
10880
|
+
const startEvidence = validatePrdRunStartEvidence(
|
|
10881
|
+
drainRecord,
|
|
10882
|
+
drainContext
|
|
10883
|
+
);
|
|
10884
|
+
if (!startEvidence.ok) {
|
|
10885
|
+
return buildLaunchBlockedOutcome(
|
|
10886
|
+
prdRef,
|
|
10887
|
+
startEvidence.guidance,
|
|
10888
|
+
plan,
|
|
10889
|
+
startOutcome
|
|
10890
|
+
);
|
|
10891
|
+
}
|
|
10892
|
+
const drainEvidence = validatePrdRunDrainEvidence(
|
|
10893
|
+
drainRecord,
|
|
10894
|
+
drainContext
|
|
10895
|
+
);
|
|
10896
|
+
if (!drainEvidence.ok) {
|
|
10897
|
+
return buildLaunchBlockedOutcome(
|
|
10898
|
+
prdRef,
|
|
10899
|
+
drainEvidence.guidance,
|
|
10900
|
+
plan,
|
|
10901
|
+
startOutcome
|
|
10902
|
+
);
|
|
10903
|
+
}
|
|
10022
10904
|
}
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
writePrdRunRecord(repoRoot2, {
|
|
10905
|
+
writeTerminalRecord(
|
|
10906
|
+
options.repoRoot,
|
|
10026
10907
|
prdRef,
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
mode
|
|
10032
|
-
|
|
10908
|
+
"completed_prd_branch",
|
|
10909
|
+
targetName,
|
|
10910
|
+
startReceipt,
|
|
10911
|
+
prdBranch,
|
|
10912
|
+
mode
|
|
10913
|
+
);
|
|
10033
10914
|
return {
|
|
10915
|
+
kind: "completed",
|
|
10034
10916
|
prdRef,
|
|
10035
|
-
status: "
|
|
10036
|
-
|
|
10037
|
-
diagnostics: [
|
|
10917
|
+
status: "completed_prd_branch",
|
|
10918
|
+
prdBranch,
|
|
10919
|
+
diagnostics: [
|
|
10920
|
+
`PRD Run completed on branch ${prdBranch}. Next: checkout ${prdBranch} for human review, or use release promotion workflow when ready.`
|
|
10921
|
+
],
|
|
10922
|
+
start: startOutcome,
|
|
10923
|
+
resume: plan
|
|
10038
10924
|
};
|
|
10039
10925
|
}
|
|
10040
10926
|
if (outcome.selected === null) {
|
|
10041
10927
|
const diagnostics = [outcome.reason];
|
|
10042
|
-
writePrdRunRecord(
|
|
10928
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10043
10929
|
prdRef,
|
|
10044
10930
|
status: "blocked",
|
|
10045
10931
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10932
|
+
targetName,
|
|
10933
|
+
start: startReceipt,
|
|
10934
|
+
mode,
|
|
10046
10935
|
blockedGate: "queue",
|
|
10047
10936
|
blockedReason: outcome.reason,
|
|
10048
10937
|
diagnostics,
|
|
10049
|
-
targetName: start.targetName,
|
|
10050
|
-
start,
|
|
10051
|
-
mode: existingRecord.record?.mode,
|
|
10052
10938
|
offendingPaths: []
|
|
10053
10939
|
});
|
|
10054
|
-
return
|
|
10940
|
+
return buildLaunchBlockedOutcome(
|
|
10055
10941
|
prdRef,
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
|
|
10942
|
+
{
|
|
10943
|
+
blockedGate: "queue",
|
|
10944
|
+
failureCode: "queue_blocked",
|
|
10945
|
+
reason: outcome.reason,
|
|
10946
|
+
repairability: "automatic-retry-safe",
|
|
10947
|
+
diagnostics,
|
|
10948
|
+
offendingPaths: []
|
|
10949
|
+
},
|
|
10950
|
+
plan,
|
|
10951
|
+
startOutcome
|
|
10952
|
+
);
|
|
10062
10953
|
}
|
|
10063
10954
|
const outcomeReason = `Unexpected queue outcome: selected issue ${outcome.selected.number}`;
|
|
10064
10955
|
const outcomeDiagnostics = [outcomeReason];
|
|
10065
|
-
writePrdRunRecord(
|
|
10956
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10066
10957
|
prdRef,
|
|
10067
10958
|
status: "blocked",
|
|
10068
10959
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10960
|
+
targetName,
|
|
10961
|
+
start: startReceipt,
|
|
10962
|
+
mode,
|
|
10069
10963
|
blockedGate: "queue",
|
|
10070
10964
|
blockedReason: outcomeReason,
|
|
10071
10965
|
diagnostics: outcomeDiagnostics,
|
|
10072
|
-
targetName: start.targetName,
|
|
10073
|
-
start,
|
|
10074
|
-
mode: existingRecord.record?.mode,
|
|
10075
10966
|
offendingPaths: []
|
|
10076
10967
|
});
|
|
10077
|
-
return
|
|
10968
|
+
return buildLaunchBlockedOutcome(
|
|
10078
10969
|
prdRef,
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10970
|
+
{
|
|
10971
|
+
blockedGate: "queue",
|
|
10972
|
+
failureCode: "unexpected_queue_outcome",
|
|
10973
|
+
reason: outcomeReason,
|
|
10974
|
+
repairability: "operator-action",
|
|
10975
|
+
diagnostics: outcomeDiagnostics,
|
|
10976
|
+
offendingPaths: []
|
|
10977
|
+
},
|
|
10978
|
+
plan,
|
|
10979
|
+
startOutcome
|
|
10980
|
+
);
|
|
10085
10981
|
} catch (error) {
|
|
10086
10982
|
const msg = error instanceof Error ? error.message : String(error);
|
|
10087
10983
|
const diagnostics = [msg];
|
|
10088
|
-
writePrdRunRecord(
|
|
10089
|
-
prdRef,
|
|
10090
|
-
status: "blocked",
|
|
10091
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10092
|
-
blockedGate: "queue",
|
|
10093
|
-
blockedReason: msg,
|
|
10094
|
-
diagnostics,
|
|
10095
|
-
targetName: start.targetName,
|
|
10096
|
-
start,
|
|
10097
|
-
mode: existingRecord.record?.mode,
|
|
10098
|
-
offendingPaths: []
|
|
10099
|
-
});
|
|
10100
|
-
return {
|
|
10984
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10101
10985
|
prdRef,
|
|
10102
10986
|
status: "blocked",
|
|
10987
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10988
|
+
targetName,
|
|
10989
|
+
start: startReceipt,
|
|
10990
|
+
mode,
|
|
10103
10991
|
blockedGate: "queue",
|
|
10104
10992
|
blockedReason: msg,
|
|
10105
10993
|
diagnostics,
|
|
10106
10994
|
offendingPaths: []
|
|
10107
|
-
};
|
|
10995
|
+
});
|
|
10996
|
+
return buildLaunchBlockedOutcome(
|
|
10997
|
+
prdRef,
|
|
10998
|
+
{
|
|
10999
|
+
blockedGate: "queue",
|
|
11000
|
+
failureCode: "queue_exception",
|
|
11001
|
+
reason: msg,
|
|
11002
|
+
repairability: "automatic-retry-safe",
|
|
11003
|
+
diagnostics,
|
|
11004
|
+
offendingPaths: []
|
|
11005
|
+
},
|
|
11006
|
+
plan,
|
|
11007
|
+
startOutcome
|
|
11008
|
+
);
|
|
10108
11009
|
}
|
|
10109
11010
|
}
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
...cleanRecord
|
|
10119
|
-
} = existingRecord.record ?? { prdRef };
|
|
10120
|
-
writePrdRunRecord(repoRoot2, {
|
|
10121
|
-
...cleanRecord,
|
|
10122
|
-
prdRef,
|
|
10123
|
-
status: "starting",
|
|
10124
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10125
|
-
targetName: context.targetName,
|
|
10126
|
-
start,
|
|
10127
|
-
mode: context.mode
|
|
10128
|
-
});
|
|
10129
|
-
}
|
|
10130
|
-
function inspectRemotePrdBranch(repoRoot2, prdRef) {
|
|
10131
|
-
const result = spawnSync3("git", ["ls-remote", "--heads", "origin", prdRef], {
|
|
10132
|
-
cwd: repoRoot2,
|
|
10133
|
-
encoding: "utf8"
|
|
10134
|
-
});
|
|
10135
|
-
if (result.status !== 0) {
|
|
10136
|
-
return {
|
|
10137
|
-
ok: false,
|
|
10138
|
-
gate: "git",
|
|
10139
|
-
reason: `PRD Run start failed while inspecting remote branch ${prdRef}.`,
|
|
10140
|
-
diagnostics: [
|
|
10141
|
-
result.error instanceof Error ? result.error.message : void 0,
|
|
10142
|
-
result.stderr?.toString?.() ?? String(result.stderr ?? ""),
|
|
10143
|
-
result.stdout?.toString?.() ?? String(result.stdout ?? "")
|
|
10144
|
-
].filter((value) => Boolean(value && value.trim())),
|
|
10145
|
-
offendingPaths: []
|
|
10146
|
-
};
|
|
10147
|
-
}
|
|
10148
|
-
const headSha = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0]?.split(/\s+/)[0];
|
|
10149
|
-
if (!headSha) {
|
|
10150
|
-
return { ok: true, exists: false };
|
|
10151
|
-
}
|
|
10152
|
-
return { ok: true, exists: true, headSha };
|
|
11011
|
+
|
|
11012
|
+
// prd-run/final-review-validation.ts
|
|
11013
|
+
import { existsSync as existsSync16, readFileSync as readFileSync18 } from "fs";
|
|
11014
|
+
|
|
11015
|
+
// commands/prd-run.ts
|
|
11016
|
+
async function runPrdRunLaunchCommand(options) {
|
|
11017
|
+
const outcome = await launchPrdRun(options);
|
|
11018
|
+
return mapLaunchPrdRunOutcomeToCommandResult(outcome, options);
|
|
10153
11019
|
}
|
|
10154
|
-
function
|
|
10155
|
-
const
|
|
10156
|
-
|
|
10157
|
-
if (!trimmed) {
|
|
10158
|
-
return {
|
|
10159
|
-
ok: false,
|
|
10160
|
-
gate: "branch-state",
|
|
10161
|
-
reason: `Invalid PRD Run base branch: value is empty after trimming.`,
|
|
10162
|
-
diagnostics: [`Provided branch value: "${raw}"`],
|
|
10163
|
-
offendingPaths: []
|
|
10164
|
-
};
|
|
10165
|
-
}
|
|
10166
|
-
if (trimmed.startsWith("origin/")) {
|
|
10167
|
-
return {
|
|
10168
|
-
ok: false,
|
|
10169
|
-
gate: "branch-state",
|
|
10170
|
-
reason: `Invalid PRD Run base branch: "${trimmed}" must not include "origin/" prefix. Use just the branch lane name (e.g. "dev" instead of "origin/dev").`,
|
|
10171
|
-
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
10172
|
-
offendingPaths: []
|
|
10173
|
-
};
|
|
10174
|
-
}
|
|
10175
|
-
if (/^(refs\/|heads\/|remotes\/|tags\/)/.test(trimmed)) {
|
|
10176
|
-
return {
|
|
10177
|
-
ok: false,
|
|
10178
|
-
gate: "branch-state",
|
|
10179
|
-
reason: `Invalid PRD Run base branch: "${trimmed}" must be a simple branch lane name, not a ref specification.`,
|
|
10180
|
-
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
10181
|
-
offendingPaths: []
|
|
10182
|
-
};
|
|
10183
|
-
}
|
|
10184
|
-
if (/^[0-9a-f]{7,40}$/i.test(trimmed)) {
|
|
10185
|
-
return {
|
|
10186
|
-
ok: false,
|
|
10187
|
-
gate: "branch-state",
|
|
10188
|
-
reason: `Invalid PRD Run base branch: value looks like a commit SHA rather than a branch lane name.`,
|
|
10189
|
-
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
10190
|
-
offendingPaths: []
|
|
10191
|
-
};
|
|
10192
|
-
}
|
|
10193
|
-
const segments = trimmed.split("/");
|
|
10194
|
-
for (const segment of segments) {
|
|
10195
|
-
if (segment === "." || segment === ".." || segment === "") {
|
|
10196
|
-
return {
|
|
10197
|
-
ok: false,
|
|
10198
|
-
gate: "branch-state",
|
|
10199
|
-
reason: `Invalid PRD Run base branch: "${trimmed}" must not contain ".", "..", or empty segments.`,
|
|
10200
|
-
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
10201
|
-
offendingPaths: []
|
|
10202
|
-
};
|
|
10203
|
-
}
|
|
10204
|
-
}
|
|
10205
|
-
if (/[~^:?*\[\\]/.test(trimmed)) {
|
|
10206
|
-
return {
|
|
10207
|
-
ok: false,
|
|
10208
|
-
gate: "branch-state",
|
|
10209
|
-
reason: `Invalid PRD Run base branch: "${trimmed}" contains invalid characters (~ ^ : ? * [ \\).`,
|
|
10210
|
-
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
10211
|
-
offendingPaths: []
|
|
10212
|
-
};
|
|
10213
|
-
}
|
|
10214
|
-
if (/[\x00-\x1f\x7f]/.test(trimmed)) {
|
|
10215
|
-
return {
|
|
10216
|
-
ok: false,
|
|
10217
|
-
gate: "branch-state",
|
|
10218
|
-
reason: `Invalid PRD Run base branch: value contains control characters.`,
|
|
10219
|
-
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
10220
|
-
offendingPaths: []
|
|
10221
|
-
};
|
|
10222
|
-
}
|
|
10223
|
-
if (trimmed.endsWith("/")) {
|
|
10224
|
-
return {
|
|
10225
|
-
ok: false,
|
|
10226
|
-
gate: "branch-state",
|
|
10227
|
-
reason: `Invalid PRD Run base branch: "${trimmed}" must not end with a trailing slash.`,
|
|
10228
|
-
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
10229
|
-
offendingPaths: []
|
|
10230
|
-
};
|
|
10231
|
-
}
|
|
10232
|
-
if (trimmed.endsWith(".lock")) {
|
|
10233
|
-
return {
|
|
10234
|
-
ok: false,
|
|
10235
|
-
gate: "branch-state",
|
|
10236
|
-
reason: `Invalid PRD Run base branch: "${trimmed}" must not end with ".lock" suffix.`,
|
|
10237
|
-
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
10238
|
-
offendingPaths: []
|
|
10239
|
-
};
|
|
10240
|
-
}
|
|
10241
|
-
const source = options.baseBranchOverride !== void 0 ? "override" : "target";
|
|
10242
|
-
return { ok: true, baseBranch: trimmed, source };
|
|
11020
|
+
async function runPrdRunStartCommand(options) {
|
|
11021
|
+
const outcome = await startPrdRun(options);
|
|
11022
|
+
return mapStartPrdRunOutcomeToCommandResult(outcome);
|
|
10243
11023
|
}
|
|
10244
|
-
function
|
|
10245
|
-
const
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
11024
|
+
function mapLaunchPrdRunOutcomeToCommandResult(outcome, options) {
|
|
11025
|
+
const prdRef = normalizePrdRunRef(options.prdRef);
|
|
11026
|
+
if (outcome.kind === "already-terminal") {
|
|
11027
|
+
const diagnostics = [
|
|
11028
|
+
`PRD Run ${prdRef} is already in status "${outcome.status}".`,
|
|
11029
|
+
...outcome.status === "completed_prd_branch" ? [buildCompletedPrdBranchHint(outcome.prdBranch ?? prdRef)] : [],
|
|
11030
|
+
...outcome.status === "completed_local_branch" ? [
|
|
11031
|
+
`Local PRD Run ${prdRef} completed. Branch: ${outcome.prdBranch ?? `local/${prdRef}`}.`
|
|
11032
|
+
] : []
|
|
11033
|
+
];
|
|
10250
11034
|
return {
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
].filter((value) => Boolean(value && value.trim())),
|
|
10259
|
-
offendingPaths: []
|
|
11035
|
+
prdRef,
|
|
11036
|
+
status: outcome.status,
|
|
11037
|
+
attempted: [...outcome.resume.attempted],
|
|
11038
|
+
skipped: [...outcome.resume.skipped],
|
|
11039
|
+
resumed: [...outcome.resume.resumed],
|
|
11040
|
+
diagnostics,
|
|
11041
|
+
prdBranch: outcome.prdBranch
|
|
10260
11042
|
};
|
|
10261
11043
|
}
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
["rev-parse", `origin/${baseBranch}`],
|
|
10265
|
-
{
|
|
10266
|
-
cwd: repoRoot2,
|
|
10267
|
-
encoding: "utf8"
|
|
10268
|
-
}
|
|
10269
|
-
);
|
|
10270
|
-
if (revParseResult.status !== 0) {
|
|
11044
|
+
if (outcome.kind === "blocked") {
|
|
11045
|
+
const diagnostics = outcome.guidance.diagnostics ? [...outcome.guidance.diagnostics] : [outcome.guidance.reason];
|
|
10271
11046
|
return {
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
offendingPaths: []
|
|
11047
|
+
prdRef,
|
|
11048
|
+
status: "blocked",
|
|
11049
|
+
attempted: [...outcome.resume.attempted],
|
|
11050
|
+
skipped: [...outcome.resume.skipped],
|
|
11051
|
+
resumed: [...outcome.resume.resumed],
|
|
11052
|
+
diagnostics,
|
|
11053
|
+
blockedGate: outcome.guidance.blockedGate,
|
|
11054
|
+
blockedReason: outcome.guidance.reason,
|
|
11055
|
+
offendingPaths: outcome.guidance.offendingPaths ? [...outcome.guidance.offendingPaths] : [],
|
|
11056
|
+
start: outcome.start ? mapStartPrdRunOutcomeToCommandResult(outcome.start) : void 0
|
|
10281
11057
|
};
|
|
10282
11058
|
}
|
|
10283
|
-
return {
|
|
11059
|
+
return {
|
|
11060
|
+
prdRef,
|
|
11061
|
+
status: outcome.status,
|
|
11062
|
+
attempted: [...outcome.resume.attempted],
|
|
11063
|
+
skipped: [...outcome.resume.skipped],
|
|
11064
|
+
resumed: [...outcome.resume.resumed],
|
|
11065
|
+
diagnostics: [...outcome.diagnostics],
|
|
11066
|
+
prdBranch: outcome.prdBranch,
|
|
11067
|
+
start: outcome.start ? mapStartPrdRunOutcomeToCommandResult(outcome.start) : void 0
|
|
11068
|
+
};
|
|
10284
11069
|
}
|
|
10285
|
-
function
|
|
10286
|
-
|
|
10287
|
-
cwd: repoRoot2,
|
|
10288
|
-
encoding: "utf8"
|
|
10289
|
-
});
|
|
10290
|
-
if (result.status !== 0) {
|
|
11070
|
+
function mapStartPrdRunOutcomeToCommandResult(outcome) {
|
|
11071
|
+
if (outcome.kind === "started") {
|
|
10291
11072
|
return {
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
diagnostics: [
|
|
10296
|
-
result.error instanceof Error ? result.error.message : void 0,
|
|
10297
|
-
result.stderr?.toString?.() ?? String(result.stderr ?? ""),
|
|
10298
|
-
result.stdout?.toString?.() ?? String(result.stdout ?? "")
|
|
10299
|
-
].filter((value) => Boolean(value && value.trim())),
|
|
10300
|
-
offendingPaths: []
|
|
11073
|
+
prdRef: outcome.prdRef,
|
|
11074
|
+
status: "starting",
|
|
11075
|
+
start: outcome.start,
|
|
11076
|
+
diagnostics: [...outcome.diagnostics]
|
|
10301
11077
|
};
|
|
10302
11078
|
}
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
}
|
|
10313
|
-
);
|
|
10314
|
-
if (pushResult.status !== 0) {
|
|
10315
|
-
throw new Error(
|
|
10316
|
-
`Failed to create PRD Branch ${prdRef} from ${startBaseCommit}: ${[
|
|
10317
|
-
pushResult.error instanceof Error ? pushResult.error.message : void 0,
|
|
10318
|
-
pushResult.stderr?.toString?.() ?? String(pushResult.stderr ?? ""),
|
|
10319
|
-
pushResult.stdout?.toString?.() ?? String(pushResult.stdout ?? "")
|
|
10320
|
-
].filter((value) => Boolean(value && value.trim())).join(" ")}`
|
|
10321
|
-
);
|
|
10322
|
-
}
|
|
11079
|
+
const diagnostics = outcome.guidance.diagnostics ? [...outcome.guidance.diagnostics] : [outcome.guidance.reason];
|
|
11080
|
+
return {
|
|
11081
|
+
prdRef: outcome.prdRef,
|
|
11082
|
+
status: "blocked",
|
|
11083
|
+
blockedGate: outcome.guidance.blockedGate,
|
|
11084
|
+
blockedReason: outcome.guidance.reason,
|
|
11085
|
+
diagnostics,
|
|
11086
|
+
offendingPaths: outcome.guidance.offendingPaths ? [...outcome.guidance.offendingPaths] : []
|
|
11087
|
+
};
|
|
10323
11088
|
}
|
|
10324
11089
|
function runPrdRunStatusCommand(options) {
|
|
10325
11090
|
const prdRef = normalizePrdRunRef(options.prdRef);
|
|
@@ -10336,49 +11101,9 @@ function runPrdRunStatusCommand(options) {
|
|
|
10336
11101
|
function runPrdRunListCommand(options) {
|
|
10337
11102
|
return listPrdRuns(options.repoRoot);
|
|
10338
11103
|
}
|
|
10339
|
-
function persistBlockedPrdRunStartRecord(repoRoot2, prdRef, failure, context = {}) {
|
|
10340
|
-
writePrdRunRecord(repoRoot2, {
|
|
10341
|
-
prdRef,
|
|
10342
|
-
status: "blocked",
|
|
10343
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10344
|
-
blockedGate: coercePrdRunRecordBlockedGate(failure.gate),
|
|
10345
|
-
blockedReason: failure.reason,
|
|
10346
|
-
diagnostics: buildBlockedDiagnostics(failure),
|
|
10347
|
-
offendingPaths: failure.offendingPaths,
|
|
10348
|
-
targetName: context.targetName
|
|
10349
|
-
});
|
|
10350
|
-
}
|
|
10351
|
-
function coercePrdRunRecordBlockedGate(gate) {
|
|
10352
|
-
switch (gate) {
|
|
10353
|
-
case "receipt-check":
|
|
10354
|
-
case "branch-state":
|
|
10355
|
-
case "git":
|
|
10356
|
-
case "queue":
|
|
10357
|
-
case "final-review":
|
|
10358
|
-
return gate;
|
|
10359
|
-
default:
|
|
10360
|
-
return "branch-state";
|
|
10361
|
-
}
|
|
10362
|
-
}
|
|
10363
11104
|
function buildCompletedPrdBranchHint(prdBranch) {
|
|
10364
11105
|
return `PRD Run completed on branch ${prdBranch}. Next: checkout ${prdBranch} for human review, or use release promotion workflow when ready.`;
|
|
10365
11106
|
}
|
|
10366
|
-
function buildBlockedDiagnostics(failure) {
|
|
10367
|
-
return failure.diagnostics ?? [
|
|
10368
|
-
failure.reason,
|
|
10369
|
-
...failure.offendingPaths.map((path9) => `Offending path: ${path9}`)
|
|
10370
|
-
];
|
|
10371
|
-
}
|
|
10372
|
-
function buildBlockedStartResult(prdRef, failure, manifestPath) {
|
|
10373
|
-
return {
|
|
10374
|
-
prdRef,
|
|
10375
|
-
status: "blocked",
|
|
10376
|
-
blockedGate: failure.gate,
|
|
10377
|
-
blockedReason: failure.reason,
|
|
10378
|
-
diagnostics: buildBlockedDiagnostics(failure),
|
|
10379
|
-
offendingPaths: failure.offendingPaths.length > 0 ? failure.offendingPaths : manifestPath ? [manifestPath] : []
|
|
10380
|
-
};
|
|
10381
|
-
}
|
|
10382
11107
|
|
|
10383
11108
|
// commands/pr-create.ts
|
|
10384
11109
|
init_common();
|
|
@@ -13326,6 +14051,14 @@ var GitHubPRProvider = class {
|
|
|
13326
14051
|
this.logger.kv("PR_URL", pr.url);
|
|
13327
14052
|
return pr;
|
|
13328
14053
|
}
|
|
14054
|
+
async commentPr(prNumber, body) {
|
|
14055
|
+
await this.client.octokit.rest.issues.createComment({
|
|
14056
|
+
owner: this.client.owner,
|
|
14057
|
+
repo: this.client.repo,
|
|
14058
|
+
issue_number: prNumber,
|
|
14059
|
+
body
|
|
14060
|
+
});
|
|
14061
|
+
}
|
|
13329
14062
|
async getPr(branchName) {
|
|
13330
14063
|
try {
|
|
13331
14064
|
const { data } = await this.client.octokit.rest.pulls.list({
|
|
@@ -13631,7 +14364,7 @@ init_common();
|
|
|
13631
14364
|
|
|
13632
14365
|
// execution/sandcastle-execution.ts
|
|
13633
14366
|
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
13634
|
-
import { join as
|
|
14367
|
+
import { join as join22 } from "path";
|
|
13635
14368
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
13636
14369
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
13637
14370
|
|
|
@@ -13640,10 +14373,10 @@ init_common();
|
|
|
13640
14373
|
import { mkdtempSync } from "fs";
|
|
13641
14374
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
13642
14375
|
import { tmpdir } from "os";
|
|
13643
|
-
import { dirname as dirname6, join as
|
|
14376
|
+
import { dirname as dirname6, join as join21 } from "path";
|
|
13644
14377
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
13645
14378
|
for (const artifact of artifacts) {
|
|
13646
|
-
const filePath =
|
|
14379
|
+
const filePath = join21(worktreePath, artifact.path);
|
|
13647
14380
|
await ensureDir(dirname6(filePath));
|
|
13648
14381
|
await writeFile4(filePath, artifact.content, "utf-8");
|
|
13649
14382
|
}
|
|
@@ -13961,12 +14694,12 @@ function isPlainObject(value) {
|
|
|
13961
14694
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13962
14695
|
}
|
|
13963
14696
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
13964
|
-
const promptsDir =
|
|
14697
|
+
const promptsDir = join22(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
13965
14698
|
mkdirSync9(promptsDir, { recursive: true });
|
|
13966
14699
|
const timestamp2 = Date.now();
|
|
13967
14700
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
13968
14701
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
13969
|
-
const filePath =
|
|
14702
|
+
const filePath = join22(promptsDir, filename);
|
|
13970
14703
|
writeFileSync7(filePath, prompt, "utf-8");
|
|
13971
14704
|
}
|
|
13972
14705
|
|
|
@@ -14660,11 +15393,11 @@ function createCliProgram(version) {
|
|
|
14660
15393
|
return program;
|
|
14661
15394
|
}
|
|
14662
15395
|
async function resolveCliVersion() {
|
|
14663
|
-
if (isPackageVersion("0.0.0-next-
|
|
14664
|
-
return "0.0.0-next-
|
|
15396
|
+
if (isPackageVersion("0.0.0-next-20260615001408")) {
|
|
15397
|
+
return "0.0.0-next-20260615001408";
|
|
14665
15398
|
}
|
|
14666
|
-
if (isReleaseVersion("0.0.0-next-
|
|
14667
|
-
return "0.0.0-next-
|
|
15399
|
+
if (isReleaseVersion("0.0.0-next-20260615001408")) {
|
|
15400
|
+
return "0.0.0-next-20260615001408";
|
|
14668
15401
|
}
|
|
14669
15402
|
try {
|
|
14670
15403
|
const root = repoRoot();
|