@pourkit/cli 0.0.0-next-20260611235559 → 0.0.0-next-20260613012953
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 +641 -285
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +237 -29
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1041,7 +1041,7 @@ async function cleanupRepository(options) {
|
|
|
1041
1041
|
// commands/artifact-validation.ts
|
|
1042
1042
|
import { createHash } from "crypto";
|
|
1043
1043
|
import { execSync } from "child_process";
|
|
1044
|
-
import { existsSync as
|
|
1044
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
|
|
1045
1045
|
import { isAbsolute, join as join6, resolve } from "path";
|
|
1046
1046
|
|
|
1047
1047
|
// pr/review-verdict.ts
|
|
@@ -2938,6 +2938,194 @@ function parseConflictResolutionArtifact(output) {
|
|
|
2938
2938
|
};
|
|
2939
2939
|
}
|
|
2940
2940
|
|
|
2941
|
+
// prd-run/final-review-validation.ts
|
|
2942
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
2943
|
+
function parseFinalReviewArtifact(artifactPath) {
|
|
2944
|
+
if (!existsSync5(artifactPath)) {
|
|
2945
|
+
return {
|
|
2946
|
+
ok: false,
|
|
2947
|
+
reason: "Final Review artifact not found.",
|
|
2948
|
+
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
let content;
|
|
2952
|
+
try {
|
|
2953
|
+
content = readFileSync5(artifactPath, "utf-8");
|
|
2954
|
+
} catch (error) {
|
|
2955
|
+
return {
|
|
2956
|
+
ok: false,
|
|
2957
|
+
reason: "Final Review artifact could not be read.",
|
|
2958
|
+
diagnostics: [
|
|
2959
|
+
error instanceof Error ? error.message : String(error),
|
|
2960
|
+
`Artifact path: ${artifactPath}`
|
|
2961
|
+
]
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
let parsed;
|
|
2965
|
+
try {
|
|
2966
|
+
parsed = JSON.parse(content);
|
|
2967
|
+
} catch {
|
|
2968
|
+
return {
|
|
2969
|
+
ok: false,
|
|
2970
|
+
reason: "Final Review artifact is not valid JSON.",
|
|
2971
|
+
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
const verdict = parsed.verdict;
|
|
2975
|
+
if (!verdict || typeof verdict !== "string") {
|
|
2976
|
+
return {
|
|
2977
|
+
ok: false,
|
|
2978
|
+
reason: "Final Review artifact is missing a verdict field.",
|
|
2979
|
+
diagnostics: [`Artifact content preview: ${content.slice(0, 300)}`]
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2982
|
+
const allowedVerdicts = [
|
|
2983
|
+
"pass_no_changes",
|
|
2984
|
+
"pass_with_retouch",
|
|
2985
|
+
"needs_human_review",
|
|
2986
|
+
"blocked"
|
|
2987
|
+
];
|
|
2988
|
+
if (!allowedVerdicts.includes(verdict)) {
|
|
2989
|
+
return {
|
|
2990
|
+
ok: false,
|
|
2991
|
+
reason: `Final Review artifact has unsupported verdict "${verdict}".`,
|
|
2992
|
+
diagnostics: [`Allowed verdicts: ${allowedVerdicts.join(", ")}`]
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
const summary = typeof parsed.summary === "string" ? parsed.summary : typeof parsed.reason === "string" ? parsed.reason : String(verdict);
|
|
2996
|
+
const diagnostics = Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [];
|
|
2997
|
+
const changedPaths = Array.isArray(parsed.changedPaths) ? parsed.changedPaths : void 0;
|
|
2998
|
+
const isAutoSummary = !parsed.summary && !parsed.reason;
|
|
2999
|
+
if (verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && isAutoSummary) {
|
|
3000
|
+
return {
|
|
3001
|
+
ok: false,
|
|
3002
|
+
reason: 'Final Review artifact with "pass_with_retouch" verdict requires changed paths.',
|
|
3003
|
+
diagnostics: [
|
|
3004
|
+
"pass_with_retouch verdict must include a changedPaths array in the artifact or provide a summary/reason with enough context for git diff fallback.",
|
|
3005
|
+
"Provide changedPaths or a descriptive summary in the artifact."
|
|
3006
|
+
]
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
const prdRef = typeof parsed.prdRef === "string" && parsed.prdRef.trim() ? parsed.prdRef.trim() : void 0;
|
|
3010
|
+
const stage = typeof parsed.stage === "string" && parsed.stage.trim() ? parsed.stage.trim() : void 0;
|
|
3011
|
+
const checkoutBase = typeof parsed.checkoutBase === "string" && parsed.checkoutBase.trim() ? parsed.checkoutBase.trim() : void 0;
|
|
3012
|
+
const reviewBase = typeof parsed.reviewBase === "string" && parsed.reviewBase.trim() ? parsed.reviewBase.trim() : void 0;
|
|
3013
|
+
return {
|
|
3014
|
+
ok: true,
|
|
3015
|
+
verdict,
|
|
3016
|
+
summary,
|
|
3017
|
+
diagnostics,
|
|
3018
|
+
changedPaths,
|
|
3019
|
+
prdRef,
|
|
3020
|
+
stage,
|
|
3021
|
+
checkoutBase,
|
|
3022
|
+
reviewBase
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
function validateFinalReviewArtifactSemanticIds(artifact, context) {
|
|
3026
|
+
const errors = [];
|
|
3027
|
+
if (!artifact.prdRef) {
|
|
3028
|
+
errors.push("Final Review artifact is missing prdRef.");
|
|
3029
|
+
} else if (artifact.prdRef !== context.prdRef) {
|
|
3030
|
+
errors.push(
|
|
3031
|
+
`Final Review artifact prdRef "${artifact.prdRef}" does not match active PRD Run "${context.prdRef}".`
|
|
3032
|
+
);
|
|
3033
|
+
}
|
|
3034
|
+
if (!artifact.stage) {
|
|
3035
|
+
errors.push("Final Review artifact is missing stage field.");
|
|
3036
|
+
} else if (artifact.stage !== "prdFinalReview") {
|
|
3037
|
+
errors.push(
|
|
3038
|
+
`Final Review artifact stage "${artifact.stage}" is not "prdFinalReview".`
|
|
3039
|
+
);
|
|
3040
|
+
}
|
|
3041
|
+
if (!artifact.checkoutBase) {
|
|
3042
|
+
errors.push("Final Review artifact is missing checkoutBase.");
|
|
3043
|
+
} else if (artifact.checkoutBase !== context.prdBranch) {
|
|
3044
|
+
errors.push(
|
|
3045
|
+
`Final Review artifact checkoutBase "${artifact.checkoutBase}" does not match active PRD Branch "${context.prdBranch}".`
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
if (!artifact.reviewBase) {
|
|
3049
|
+
errors.push("Final Review artifact is missing reviewBase.");
|
|
3050
|
+
} else if (artifact.reviewBase !== context.mergeBase) {
|
|
3051
|
+
errors.push(
|
|
3052
|
+
`Final Review artifact reviewBase "${artifact.reviewBase}" does not match active merge base "${context.mergeBase}".`
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
if (errors.length > 0) {
|
|
3056
|
+
return { ok: false, errors, blockedGate: "final-review" };
|
|
3057
|
+
}
|
|
3058
|
+
return { ok: true };
|
|
3059
|
+
}
|
|
3060
|
+
function validateFinalReviewRetouchScope(changedPaths) {
|
|
3061
|
+
if (!changedPaths || changedPaths.length === 0) {
|
|
3062
|
+
return {
|
|
3063
|
+
ok: false,
|
|
3064
|
+
reason: "Final Review retouch scope validation failed. No changed paths available. Provide changed paths or run Final Review with a summary that includes changed paths.",
|
|
3065
|
+
diagnostics: ["Changed paths list is empty or undefined."],
|
|
3066
|
+
offendingPaths: []
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
const offendingPaths = changedPaths.filter((p) => !isRetouchScopePath(p));
|
|
3070
|
+
const allowedPaths = changedPaths.filter((p) => isRetouchScopePath(p));
|
|
3071
|
+
if (allowedPaths.length === 0) {
|
|
3072
|
+
return {
|
|
3073
|
+
ok: false,
|
|
3074
|
+
reason: "Final Review retouch scope validation failed. No implementation, test, or Changeset paths found for retouch.",
|
|
3075
|
+
diagnostics: [
|
|
3076
|
+
"All changed paths are prohibited for retouch.",
|
|
3077
|
+
...changedPaths.map((p) => ` - ${p}`)
|
|
3078
|
+
],
|
|
3079
|
+
offendingPaths
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
if (offendingPaths.length > 0) {
|
|
3083
|
+
return {
|
|
3084
|
+
ok: false,
|
|
3085
|
+
reason: "Final Review retouch scope validation failed. Prohibited paths cannot be included in retouch PR.",
|
|
3086
|
+
diagnostics: [
|
|
3087
|
+
"Prohibited paths found:",
|
|
3088
|
+
...offendingPaths.map((p) => ` - ${p}`)
|
|
3089
|
+
],
|
|
3090
|
+
offendingPaths
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
return { ok: true, changedPaths };
|
|
3094
|
+
}
|
|
3095
|
+
function validateFinalReviewAgentOutputs(options) {
|
|
3096
|
+
const artifact = parseFinalReviewArtifact(options.artifactPath);
|
|
3097
|
+
if (!artifact.ok) {
|
|
3098
|
+
return { ...artifact, offendingPaths: [] };
|
|
3099
|
+
}
|
|
3100
|
+
const semanticResult = validateFinalReviewArtifactSemanticIds(
|
|
3101
|
+
artifact,
|
|
3102
|
+
options.context
|
|
3103
|
+
);
|
|
3104
|
+
if (!semanticResult.ok) {
|
|
3105
|
+
return {
|
|
3106
|
+
ok: false,
|
|
3107
|
+
reason: "Final Review artifact has mismatched semantic IDs.",
|
|
3108
|
+
diagnostics: semanticResult.errors,
|
|
3109
|
+
offendingPaths: []
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
if (artifact.verdict !== "pass_with_retouch") {
|
|
3113
|
+
return { ok: true, artifact };
|
|
3114
|
+
}
|
|
3115
|
+
const changedPaths = options.changedPaths && options.changedPaths.length > 0 ? options.changedPaths : artifact.changedPaths;
|
|
3116
|
+
const retouch = validateFinalReviewRetouchScope(changedPaths ?? []);
|
|
3117
|
+
if (!retouch.ok) {
|
|
3118
|
+
return retouch;
|
|
3119
|
+
}
|
|
3120
|
+
return { ok: true, artifact, retouch };
|
|
3121
|
+
}
|
|
3122
|
+
function isRetouchScopePath(path9) {
|
|
3123
|
+
if (path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/adr/") || /^\.pourkit\/prd-runs\/[^/]+\.json$/.test(path9)) {
|
|
3124
|
+
return false;
|
|
3125
|
+
}
|
|
3126
|
+
return true;
|
|
3127
|
+
}
|
|
3128
|
+
|
|
2941
3129
|
// commands/artifact-validation.ts
|
|
2942
3130
|
var CONFLICT_MARKER_PATTERN = /<<<<<<<|=======|>>>>>>>/m;
|
|
2943
3131
|
function invalid(options, reason, diagnostics = []) {
|
|
@@ -2958,14 +3146,14 @@ function valid(options, diagnostics = []) {
|
|
|
2958
3146
|
};
|
|
2959
3147
|
}
|
|
2960
3148
|
function readArtifact(options) {
|
|
2961
|
-
if (!
|
|
3149
|
+
if (!existsSync6(options.artifactPath)) {
|
|
2962
3150
|
return {
|
|
2963
3151
|
ok: false,
|
|
2964
3152
|
result: invalid(options, `Artifact missing at ${options.artifactPath}`)
|
|
2965
3153
|
};
|
|
2966
3154
|
}
|
|
2967
3155
|
try {
|
|
2968
|
-
const content =
|
|
3156
|
+
const content = readFileSync6(options.artifactPath, "utf-8");
|
|
2969
3157
|
if (!content.trim()) {
|
|
2970
3158
|
return { ok: false, result: invalid(options, "Artifact is empty") };
|
|
2971
3159
|
}
|
|
@@ -2998,7 +3186,7 @@ function validateAgentArtifact(options) {
|
|
|
2998
3186
|
case "refactor": {
|
|
2999
3187
|
let findingIds = options.findingIds ?? [];
|
|
3000
3188
|
if (findingIds.length === 0 && options.latestReviewArtifactPath) {
|
|
3001
|
-
const latestReview =
|
|
3189
|
+
const latestReview = readFileSync6(
|
|
3002
3190
|
options.latestReviewArtifactPath,
|
|
3003
3191
|
"utf-8"
|
|
3004
3192
|
);
|
|
@@ -3024,7 +3212,7 @@ function validateAgentArtifact(options) {
|
|
|
3024
3212
|
const filePath = resolve(base, file);
|
|
3025
3213
|
try {
|
|
3026
3214
|
return CONFLICT_MARKER_PATTERN.test(
|
|
3027
|
-
|
|
3215
|
+
readFileSync6(filePath, "utf-8")
|
|
3028
3216
|
);
|
|
3029
3217
|
} catch {
|
|
3030
3218
|
return false;
|
|
@@ -3040,6 +3228,26 @@ function validateAgentArtifact(options) {
|
|
|
3040
3228
|
}
|
|
3041
3229
|
return valid(options, [`status: ${parsed.status}`]);
|
|
3042
3230
|
}
|
|
3231
|
+
case "final-review": {
|
|
3232
|
+
if (!options.prdRef || !options.checkoutBase || !options.reviewBase) {
|
|
3233
|
+
return invalid(
|
|
3234
|
+
options,
|
|
3235
|
+
"Final Review artifact validation requires --prd-ref, --checkout-base, and --review-base."
|
|
3236
|
+
);
|
|
3237
|
+
}
|
|
3238
|
+
const result = validateFinalReviewAgentOutputs({
|
|
3239
|
+
artifactPath: options.artifactPath,
|
|
3240
|
+
context: {
|
|
3241
|
+
prdRef: options.prdRef,
|
|
3242
|
+
prdBranch: options.checkoutBase,
|
|
3243
|
+
mergeBase: options.reviewBase
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
3246
|
+
if (!result.ok) {
|
|
3247
|
+
return invalid(options, result.reason, result.diagnostics);
|
|
3248
|
+
}
|
|
3249
|
+
return valid(options, [`verdict: ${result.artifact.verdict}`]);
|
|
3250
|
+
}
|
|
3043
3251
|
case "failure-resolution": {
|
|
3044
3252
|
const parsed = parseRecoveryArtifact(
|
|
3045
3253
|
artifact.content,
|
|
@@ -3145,7 +3353,7 @@ function runLocalValidateArtifactCommand(options) {
|
|
|
3145
3353
|
}
|
|
3146
3354
|
function readLocalArtifact(path9, failureCode) {
|
|
3147
3355
|
try {
|
|
3148
|
-
const raw =
|
|
3356
|
+
const raw = readFileSync6(path9, "utf-8");
|
|
3149
3357
|
const parsed = JSON.parse(raw);
|
|
3150
3358
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3151
3359
|
return {
|
|
@@ -3844,7 +4052,7 @@ function validateLocalTriage(prdPath, issuePaths) {
|
|
|
3844
4052
|
}
|
|
3845
4053
|
|
|
3846
4054
|
// commands/issue-run.ts
|
|
3847
|
-
import { existsSync as
|
|
4055
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
|
|
3848
4056
|
import { join as join13 } from "path";
|
|
3849
4057
|
|
|
3850
4058
|
// pr/templates.ts
|
|
@@ -3980,7 +4188,7 @@ function runEffectAndMapExit(program) {
|
|
|
3980
4188
|
}
|
|
3981
4189
|
|
|
3982
4190
|
// shared/attempt-log.ts
|
|
3983
|
-
import { appendFileSync, existsSync as
|
|
4191
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7 } from "fs";
|
|
3984
4192
|
import { dirname as dirname3, join as join7 } from "path";
|
|
3985
4193
|
var ATTEMPT_LOG_PATH = ".pourkit/attempt-log.jsonl";
|
|
3986
4194
|
function writeAttemptLog(worktreePath, entry) {
|
|
@@ -3990,10 +4198,10 @@ function writeAttemptLog(worktreePath, entry) {
|
|
|
3990
4198
|
}
|
|
3991
4199
|
function readAttemptLog(worktreePath) {
|
|
3992
4200
|
const logPath = join7(worktreePath, ATTEMPT_LOG_PATH);
|
|
3993
|
-
if (!
|
|
4201
|
+
if (!existsSync7(logPath)) {
|
|
3994
4202
|
return [];
|
|
3995
4203
|
}
|
|
3996
|
-
const raw =
|
|
4204
|
+
const raw = readFileSync7(logPath, "utf-8");
|
|
3997
4205
|
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
3998
4206
|
const entries = [];
|
|
3999
4207
|
for (const line of lines) {
|
|
@@ -4112,7 +4320,7 @@ async function runBaseRefreshAttempt(options) {
|
|
|
4112
4320
|
}
|
|
4113
4321
|
|
|
4114
4322
|
// commands/conflict-resolution.ts
|
|
4115
|
-
import { existsSync as
|
|
4323
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
4116
4324
|
import { join as join8 } from "path";
|
|
4117
4325
|
init_common();
|
|
4118
4326
|
var CONFLICT_MARKER_PATTERN2 = /<<<<<<<|=======|>>>>>>>/m;
|
|
@@ -4120,7 +4328,7 @@ async function hasUnresolvedConflictMarkers(worktreePath, files) {
|
|
|
4120
4328
|
for (const file of files) {
|
|
4121
4329
|
const filePath = join8(worktreePath, file);
|
|
4122
4330
|
try {
|
|
4123
|
-
const content =
|
|
4331
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
4124
4332
|
if (CONFLICT_MARKER_PATTERN2.test(content)) {
|
|
4125
4333
|
return true;
|
|
4126
4334
|
}
|
|
@@ -4451,7 +4659,7 @@ async function prepareSerenaForTarget(options) {
|
|
|
4451
4659
|
}
|
|
4452
4660
|
|
|
4453
4661
|
// failure-resolution/failure-resolution-agent.ts
|
|
4454
|
-
import { readFileSync as
|
|
4662
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
4455
4663
|
import { join as join9 } from "path";
|
|
4456
4664
|
|
|
4457
4665
|
// failure-resolution/recovery-policy.ts
|
|
@@ -4605,7 +4813,7 @@ async function runFailureResolutionAgent(options) {
|
|
|
4605
4813
|
}
|
|
4606
4814
|
let artifact;
|
|
4607
4815
|
try {
|
|
4608
|
-
const md =
|
|
4816
|
+
const md = readFileSync9(fullArtifactPath, "utf-8");
|
|
4609
4817
|
const validation2 = validateAgentArtifact({
|
|
4610
4818
|
kind: "failure-resolution",
|
|
4611
4819
|
artifactPath: fullArtifactPath,
|
|
@@ -4699,7 +4907,7 @@ async function writeRecoveryAttempt(worktreePath, outcome, fingerprint, summary,
|
|
|
4699
4907
|
|
|
4700
4908
|
// commands/pr-description-agent.ts
|
|
4701
4909
|
import { join as join11 } from "path";
|
|
4702
|
-
import { readFileSync as
|
|
4910
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
4703
4911
|
|
|
4704
4912
|
// pr/pr-description-context.ts
|
|
4705
4913
|
init_common();
|
|
@@ -4893,7 +5101,7 @@ function runFinalizerAgent(options) {
|
|
|
4893
5101
|
],
|
|
4894
5102
|
logger: options.logger
|
|
4895
5103
|
});
|
|
4896
|
-
const output =
|
|
5104
|
+
const output = readFileSync10(artifactPath, "utf-8");
|
|
4897
5105
|
yield* persistGeneratedArtifactEffect(options.worktreePath, output, fs);
|
|
4898
5106
|
return result;
|
|
4899
5107
|
});
|
|
@@ -5021,7 +5229,7 @@ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
|
5021
5229
|
|
|
5022
5230
|
// prd-run/local-merge-coordinator.ts
|
|
5023
5231
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
5024
|
-
import { existsSync as
|
|
5232
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
5025
5233
|
import { join as join12 } from "path";
|
|
5026
5234
|
|
|
5027
5235
|
// prd-run/local-branches.ts
|
|
@@ -5122,9 +5330,9 @@ function getIssueArtifactPath(repoRoot2, prdId, issueId) {
|
|
|
5122
5330
|
}
|
|
5123
5331
|
function readIssueBranchName(repoRoot2, prdId, issueId) {
|
|
5124
5332
|
const issuePath = getIssueArtifactPath(repoRoot2, prdId, issueId);
|
|
5125
|
-
if (!
|
|
5333
|
+
if (!existsSync9(issuePath)) return null;
|
|
5126
5334
|
try {
|
|
5127
|
-
const content =
|
|
5335
|
+
const content = readFileSync11(issuePath, "utf-8");
|
|
5128
5336
|
const parsed = JSON.parse(content);
|
|
5129
5337
|
return typeof parsed.branchName === "string" && parsed.branchName ? parsed.branchName : null;
|
|
5130
5338
|
} catch {
|
|
@@ -5134,9 +5342,9 @@ function readIssueBranchName(repoRoot2, prdId, issueId) {
|
|
|
5134
5342
|
async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
|
|
5135
5343
|
const root = repoRoot2 ?? process.cwd();
|
|
5136
5344
|
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
5137
|
-
if (!
|
|
5345
|
+
if (!existsSync9(receiptPath)) return null;
|
|
5138
5346
|
try {
|
|
5139
|
-
const content =
|
|
5347
|
+
const content = readFileSync11(receiptPath, "utf-8");
|
|
5140
5348
|
const parsed = JSON.parse(content);
|
|
5141
5349
|
if (typeof parsed.prdId === "string" && typeof parsed.issueId === "string" && typeof parsed.stage === "string" && typeof parsed.sourceBranch === "string" && typeof parsed.localPrdBranch === "string" && typeof parsed.mergeCommit === "string" && typeof parsed.completedAt === "string") {
|
|
5142
5350
|
return parsed;
|
|
@@ -5149,10 +5357,10 @@ async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
|
|
|
5149
5357
|
async function squashMergeLocalIssue(prdId, issueId, input, repoRoot2) {
|
|
5150
5358
|
const root = repoRoot2 ?? process.cwd();
|
|
5151
5359
|
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
5152
|
-
if (
|
|
5360
|
+
if (existsSync9(receiptPath)) {
|
|
5153
5361
|
try {
|
|
5154
5362
|
const existing = JSON.parse(
|
|
5155
|
-
|
|
5363
|
+
readFileSync11(receiptPath, "utf-8")
|
|
5156
5364
|
);
|
|
5157
5365
|
if (existing.prdId === prdId && existing.issueId === issueId && existing.mergeCommit) {
|
|
5158
5366
|
return {
|
|
@@ -6047,12 +6255,12 @@ async function completeIssueRun(options) {
|
|
|
6047
6255
|
prTitle = finalizerFromState.title;
|
|
6048
6256
|
prBody = finalizerFromState.body;
|
|
6049
6257
|
} else if (finalizerFromState.artifactPath) {
|
|
6050
|
-
if (!
|
|
6258
|
+
if (!existsSync10(finalizerFromState.artifactPath)) {
|
|
6051
6259
|
throw new FinalizerFailure({
|
|
6052
6260
|
message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
|
|
6053
6261
|
});
|
|
6054
6262
|
}
|
|
6055
|
-
const artifactContent =
|
|
6263
|
+
const artifactContent = readFileSync12(
|
|
6056
6264
|
finalizerFromState.artifactPath,
|
|
6057
6265
|
"utf-8"
|
|
6058
6266
|
);
|
|
@@ -6749,7 +6957,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
|
|
|
6749
6957
|
}
|
|
6750
6958
|
function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
6751
6959
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
6752
|
-
const promptBody =
|
|
6960
|
+
const promptBody = existsSync10(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
|
|
6753
6961
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
6754
6962
|
|
|
6755
6963
|
## Shared Run Context
|
|
@@ -7207,9 +7415,9 @@ import { Match, pipe } from "effect";
|
|
|
7207
7415
|
|
|
7208
7416
|
// prd-run/state.ts
|
|
7209
7417
|
import {
|
|
7210
|
-
existsSync as
|
|
7418
|
+
existsSync as existsSync11,
|
|
7211
7419
|
mkdirSync as mkdirSync8,
|
|
7212
|
-
readFileSync as
|
|
7420
|
+
readFileSync as readFileSync13,
|
|
7213
7421
|
readdirSync as readdirSync3,
|
|
7214
7422
|
writeFileSync as writeFileSync5
|
|
7215
7423
|
} from "fs";
|
|
@@ -7245,6 +7453,7 @@ var PrdRunRecordSchema = z2.object({
|
|
|
7245
7453
|
status: z2.enum(["started", "succeeded", "adopted", "reused"]),
|
|
7246
7454
|
targetName: z2.string().min(1),
|
|
7247
7455
|
prdBranch: z2.string().min(1),
|
|
7456
|
+
startBaseBranch: z2.string().min(1),
|
|
7248
7457
|
startBaseCommit: z2.string().min(1).optional(),
|
|
7249
7458
|
branchAction: z2.enum(["created", "reused", "adopted"]).optional(),
|
|
7250
7459
|
startedAt: z2.string().min(1).optional(),
|
|
@@ -7303,15 +7512,26 @@ function normalizePrdRunRef(ref) {
|
|
|
7303
7512
|
function readPrdRun(repoRoot2, prdRef) {
|
|
7304
7513
|
const normalized = normalizePrdRunRef(prdRef);
|
|
7305
7514
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7306
|
-
if (!
|
|
7515
|
+
if (!existsSync11(recordPath)) {
|
|
7307
7516
|
return { record: null, diagnostics: [] };
|
|
7308
7517
|
}
|
|
7309
7518
|
try {
|
|
7310
|
-
const
|
|
7311
|
-
|
|
7312
|
-
);
|
|
7519
|
+
const raw = JSON.parse(readFileSync13(recordPath, "utf-8"));
|
|
7520
|
+
const parsed = PrdRunRecordSchema.parse(raw);
|
|
7313
7521
|
return { record: parsed, diagnostics: [] };
|
|
7314
7522
|
} catch (error) {
|
|
7523
|
+
try {
|
|
7524
|
+
const raw = JSON.parse(readFileSync13(recordPath, "utf-8"));
|
|
7525
|
+
if (raw && typeof raw === "object" && raw.start && typeof raw.start === "object" && raw.start.startBaseBranch === void 0) {
|
|
7526
|
+
return {
|
|
7527
|
+
record: raw,
|
|
7528
|
+
diagnostics: [
|
|
7529
|
+
`Legacy PRD Run record at ${recordPath} is missing startBaseBranch.`
|
|
7530
|
+
]
|
|
7531
|
+
};
|
|
7532
|
+
}
|
|
7533
|
+
} catch {
|
|
7534
|
+
}
|
|
7315
7535
|
return {
|
|
7316
7536
|
record: null,
|
|
7317
7537
|
diagnostics: [
|
|
@@ -7323,7 +7543,7 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7323
7543
|
}
|
|
7324
7544
|
function listPrdRuns(repoRoot2) {
|
|
7325
7545
|
const stateDir = join14(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7326
|
-
if (!
|
|
7546
|
+
if (!existsSync11(stateDir)) {
|
|
7327
7547
|
return { records: [], diagnostics: [] };
|
|
7328
7548
|
}
|
|
7329
7549
|
const records = [];
|
|
@@ -7335,7 +7555,7 @@ function listPrdRuns(repoRoot2) {
|
|
|
7335
7555
|
const recordPath = join14(stateDir, entry.name);
|
|
7336
7556
|
try {
|
|
7337
7557
|
const record = PrdRunRecordSchema.parse(
|
|
7338
|
-
JSON.parse(
|
|
7558
|
+
JSON.parse(readFileSync13(recordPath, "utf-8"))
|
|
7339
7559
|
);
|
|
7340
7560
|
records.push(record);
|
|
7341
7561
|
} catch (error) {
|
|
@@ -7403,7 +7623,7 @@ function getLocalStorePath2(repoRoot2, prdId) {
|
|
|
7403
7623
|
async function readLocalPrdRun(repoRoot2, prdId) {
|
|
7404
7624
|
const normalized = normalizePrdRunRef(prdId);
|
|
7405
7625
|
const recordPath = getLocalStorePath2(repoRoot2, normalized);
|
|
7406
|
-
if (!
|
|
7626
|
+
if (!existsSync11(recordPath)) {
|
|
7407
7627
|
return null;
|
|
7408
7628
|
}
|
|
7409
7629
|
try {
|
|
@@ -7512,191 +7732,30 @@ function buildEvidencePacket(input) {
|
|
|
7512
7732
|
prdBranch: input.prdBranch.trim(),
|
|
7513
7733
|
mergeBase,
|
|
7514
7734
|
planningManifestPath: input.planningManifestPath.trim(),
|
|
7515
|
-
planningManifestFacts: {
|
|
7516
|
-
parentPrdIssueUrl: input.planningManifestFacts.parentPrdIssueUrl.trim(),
|
|
7517
|
-
childIssueCount: input.planningManifestFacts.childIssueCount
|
|
7518
|
-
},
|
|
7519
|
-
stage: stageResult.data,
|
|
7520
|
-
stageReceipts: input.stageReceipts
|
|
7521
|
-
};
|
|
7522
|
-
return packet;
|
|
7523
|
-
}
|
|
7524
|
-
var TOKEN_LIKE_PATTERNS = [
|
|
7525
|
-
/gh[ps]_[A-Za-z0-9]{20,}/g,
|
|
7526
|
-
/token[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
7527
|
-
/secret[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
7528
|
-
/credential[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
7529
|
-
/key[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
7530
|
-
/-----BEGIN (RSA |EC )?PRIVATE KEY-----/g,
|
|
7531
|
-
/xox[baprs]-[A-Za-z0-9_-]{10,}/g
|
|
7532
|
-
];
|
|
7533
|
-
function redactSensitiveValues(input) {
|
|
7534
|
-
let redacted = input;
|
|
7535
|
-
for (const pattern of TOKEN_LIKE_PATTERNS) {
|
|
7536
|
-
redacted = redacted.replace(pattern, "[REDACTED]");
|
|
7537
|
-
}
|
|
7538
|
-
return redacted;
|
|
7539
|
-
}
|
|
7540
|
-
|
|
7541
|
-
// prd-run/final-review-validation.ts
|
|
7542
|
-
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
7543
|
-
function parseFinalReviewArtifact(artifactPath) {
|
|
7544
|
-
if (!existsSync11(artifactPath)) {
|
|
7545
|
-
return {
|
|
7546
|
-
ok: false,
|
|
7547
|
-
reason: "Final Review artifact not found.",
|
|
7548
|
-
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
7549
|
-
};
|
|
7550
|
-
}
|
|
7551
|
-
let content;
|
|
7552
|
-
try {
|
|
7553
|
-
content = readFileSync13(artifactPath, "utf-8");
|
|
7554
|
-
} catch (error) {
|
|
7555
|
-
return {
|
|
7556
|
-
ok: false,
|
|
7557
|
-
reason: "Final Review artifact could not be read.",
|
|
7558
|
-
diagnostics: [
|
|
7559
|
-
error instanceof Error ? error.message : String(error),
|
|
7560
|
-
`Artifact path: ${artifactPath}`
|
|
7561
|
-
]
|
|
7562
|
-
};
|
|
7563
|
-
}
|
|
7564
|
-
let parsed;
|
|
7565
|
-
try {
|
|
7566
|
-
parsed = JSON.parse(content);
|
|
7567
|
-
} catch {
|
|
7568
|
-
return {
|
|
7569
|
-
ok: false,
|
|
7570
|
-
reason: "Final Review artifact is not valid JSON.",
|
|
7571
|
-
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
7572
|
-
};
|
|
7573
|
-
}
|
|
7574
|
-
const verdict = parsed.verdict;
|
|
7575
|
-
if (!verdict || typeof verdict !== "string") {
|
|
7576
|
-
return {
|
|
7577
|
-
ok: false,
|
|
7578
|
-
reason: "Final Review artifact is missing a verdict field.",
|
|
7579
|
-
diagnostics: [`Artifact content preview: ${content.slice(0, 300)}`]
|
|
7580
|
-
};
|
|
7581
|
-
}
|
|
7582
|
-
const allowedVerdicts = [
|
|
7583
|
-
"pass_no_changes",
|
|
7584
|
-
"pass_with_retouch",
|
|
7585
|
-
"needs_human_review",
|
|
7586
|
-
"blocked"
|
|
7587
|
-
];
|
|
7588
|
-
if (!allowedVerdicts.includes(verdict)) {
|
|
7589
|
-
return {
|
|
7590
|
-
ok: false,
|
|
7591
|
-
reason: `Final Review artifact has unsupported verdict "${verdict}".`,
|
|
7592
|
-
diagnostics: [`Allowed verdicts: ${allowedVerdicts.join(", ")}`]
|
|
7593
|
-
};
|
|
7594
|
-
}
|
|
7595
|
-
const summary = typeof parsed.summary === "string" ? parsed.summary : typeof parsed.reason === "string" ? parsed.reason : String(verdict);
|
|
7596
|
-
const diagnostics = Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [];
|
|
7597
|
-
const changedPaths = Array.isArray(parsed.changedPaths) ? parsed.changedPaths : void 0;
|
|
7598
|
-
const isAutoSummary = !parsed.summary && !parsed.reason;
|
|
7599
|
-
if (verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && isAutoSummary) {
|
|
7600
|
-
return {
|
|
7601
|
-
ok: false,
|
|
7602
|
-
reason: 'Final Review artifact with "pass_with_retouch" verdict requires changed paths.',
|
|
7603
|
-
diagnostics: [
|
|
7604
|
-
"pass_with_retouch verdict must include a changedPaths array in the artifact or provide a summary/reason with enough context for git diff fallback.",
|
|
7605
|
-
"Provide changedPaths or a descriptive summary in the artifact."
|
|
7606
|
-
]
|
|
7607
|
-
};
|
|
7608
|
-
}
|
|
7609
|
-
const prdRef = typeof parsed.prdRef === "string" && parsed.prdRef.trim() ? parsed.prdRef.trim() : void 0;
|
|
7610
|
-
const stage = typeof parsed.stage === "string" && parsed.stage.trim() ? parsed.stage.trim() : void 0;
|
|
7611
|
-
const checkoutBase = typeof parsed.checkoutBase === "string" && parsed.checkoutBase.trim() ? parsed.checkoutBase.trim() : void 0;
|
|
7612
|
-
const reviewBase = typeof parsed.reviewBase === "string" && parsed.reviewBase.trim() ? parsed.reviewBase.trim() : void 0;
|
|
7613
|
-
return {
|
|
7614
|
-
ok: true,
|
|
7615
|
-
verdict,
|
|
7616
|
-
summary,
|
|
7617
|
-
diagnostics,
|
|
7618
|
-
changedPaths,
|
|
7619
|
-
prdRef,
|
|
7620
|
-
stage,
|
|
7621
|
-
checkoutBase,
|
|
7622
|
-
reviewBase
|
|
7623
|
-
};
|
|
7624
|
-
}
|
|
7625
|
-
function validateFinalReviewArtifactSemanticIds(artifact, context) {
|
|
7626
|
-
const errors = [];
|
|
7627
|
-
if (!artifact.prdRef) {
|
|
7628
|
-
errors.push("Final Review artifact is missing prdRef.");
|
|
7629
|
-
} else if (artifact.prdRef !== context.prdRef) {
|
|
7630
|
-
errors.push(
|
|
7631
|
-
`Final Review artifact prdRef "${artifact.prdRef}" does not match active PRD Run "${context.prdRef}".`
|
|
7632
|
-
);
|
|
7633
|
-
}
|
|
7634
|
-
if (!artifact.stage) {
|
|
7635
|
-
errors.push("Final Review artifact is missing stage field.");
|
|
7636
|
-
} else if (artifact.stage !== "prdFinalReview") {
|
|
7637
|
-
errors.push(
|
|
7638
|
-
`Final Review artifact stage "${artifact.stage}" is not "prdFinalReview".`
|
|
7639
|
-
);
|
|
7640
|
-
}
|
|
7641
|
-
if (!artifact.checkoutBase) {
|
|
7642
|
-
errors.push("Final Review artifact is missing checkoutBase.");
|
|
7643
|
-
} else if (artifact.checkoutBase !== context.prdBranch) {
|
|
7644
|
-
errors.push(
|
|
7645
|
-
`Final Review artifact checkoutBase "${artifact.checkoutBase}" does not match active PRD Branch "${context.prdBranch}".`
|
|
7646
|
-
);
|
|
7647
|
-
}
|
|
7648
|
-
if (!artifact.reviewBase) {
|
|
7649
|
-
errors.push("Final Review artifact is missing reviewBase.");
|
|
7650
|
-
} else if (artifact.reviewBase !== context.mergeBase) {
|
|
7651
|
-
errors.push(
|
|
7652
|
-
`Final Review artifact reviewBase "${artifact.reviewBase}" does not match active merge base "${context.mergeBase}".`
|
|
7653
|
-
);
|
|
7654
|
-
}
|
|
7655
|
-
if (errors.length > 0) {
|
|
7656
|
-
return { ok: false, errors, blockedGate: "final-review" };
|
|
7657
|
-
}
|
|
7658
|
-
return { ok: true };
|
|
7659
|
-
}
|
|
7660
|
-
function validateFinalReviewRetouchScope(changedPaths) {
|
|
7661
|
-
if (!changedPaths || changedPaths.length === 0) {
|
|
7662
|
-
return {
|
|
7663
|
-
ok: false,
|
|
7664
|
-
reason: "Final Review retouch scope validation failed. No changed paths available. Provide changed paths or run Final Review with a summary that includes changed paths.",
|
|
7665
|
-
diagnostics: ["Changed paths list is empty or undefined."],
|
|
7666
|
-
offendingPaths: []
|
|
7667
|
-
};
|
|
7668
|
-
}
|
|
7669
|
-
const offendingPaths = changedPaths.filter((p) => !isRetouchScopePath(p));
|
|
7670
|
-
const allowedPaths = changedPaths.filter((p) => isRetouchScopePath(p));
|
|
7671
|
-
if (allowedPaths.length === 0) {
|
|
7672
|
-
return {
|
|
7673
|
-
ok: false,
|
|
7674
|
-
reason: "Final Review retouch scope validation failed. No implementation, test, or Changeset paths found for retouch.",
|
|
7675
|
-
diagnostics: [
|
|
7676
|
-
"All changed paths are prohibited for retouch.",
|
|
7677
|
-
...changedPaths.map((p) => ` - ${p}`)
|
|
7678
|
-
],
|
|
7679
|
-
offendingPaths
|
|
7680
|
-
};
|
|
7681
|
-
}
|
|
7682
|
-
if (offendingPaths.length > 0) {
|
|
7683
|
-
return {
|
|
7684
|
-
ok: false,
|
|
7685
|
-
reason: "Final Review retouch scope validation failed. Prohibited paths cannot be included in retouch PR.",
|
|
7686
|
-
diagnostics: [
|
|
7687
|
-
"Prohibited paths found:",
|
|
7688
|
-
...offendingPaths.map((p) => ` - ${p}`)
|
|
7689
|
-
],
|
|
7690
|
-
offendingPaths
|
|
7691
|
-
};
|
|
7692
|
-
}
|
|
7693
|
-
return { ok: true, changedPaths };
|
|
7735
|
+
planningManifestFacts: {
|
|
7736
|
+
parentPrdIssueUrl: input.planningManifestFacts.parentPrdIssueUrl.trim(),
|
|
7737
|
+
childIssueCount: input.planningManifestFacts.childIssueCount
|
|
7738
|
+
},
|
|
7739
|
+
stage: stageResult.data,
|
|
7740
|
+
stageReceipts: input.stageReceipts
|
|
7741
|
+
};
|
|
7742
|
+
return packet;
|
|
7694
7743
|
}
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7744
|
+
var TOKEN_LIKE_PATTERNS = [
|
|
7745
|
+
/gh[ps]_[A-Za-z0-9]{20,}/g,
|
|
7746
|
+
/token[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
7747
|
+
/secret[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
7748
|
+
/credential[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
7749
|
+
/key[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
7750
|
+
/-----BEGIN (RSA |EC )?PRIVATE KEY-----/g,
|
|
7751
|
+
/xox[baprs]-[A-Za-z0-9_-]{10,}/g
|
|
7752
|
+
];
|
|
7753
|
+
function redactSensitiveValues(input) {
|
|
7754
|
+
let redacted = input;
|
|
7755
|
+
for (const pattern of TOKEN_LIKE_PATTERNS) {
|
|
7756
|
+
redacted = redacted.replace(pattern, "[REDACTED]");
|
|
7698
7757
|
}
|
|
7699
|
-
return
|
|
7758
|
+
return redacted;
|
|
7700
7759
|
}
|
|
7701
7760
|
|
|
7702
7761
|
// commands/prd-run.ts
|
|
@@ -8886,6 +8945,7 @@ function buildStartReceipt(options) {
|
|
|
8886
8945
|
status: options.status,
|
|
8887
8946
|
targetName: options.targetName,
|
|
8888
8947
|
prdBranch: options.prdBranch,
|
|
8948
|
+
startBaseBranch: options.startBaseBranch,
|
|
8889
8949
|
startBaseCommit: options.startBaseCommit,
|
|
8890
8950
|
branchAction: options.branchAction,
|
|
8891
8951
|
startedAt: options.startedAt,
|
|
@@ -8914,7 +8974,7 @@ function buildFinalReviewPrompt(options) {
|
|
|
8914
8974
|
`Worktree checkout base: ${options.evidencePacket.prdBranch}`,
|
|
8915
8975
|
`Review only this range: ${options.evidencePacket.mergeBase}..HEAD`,
|
|
8916
8976
|
"Do not compare against current target branch HEAD; the merge base is the review baseline.",
|
|
8917
|
-
`Before handoff, run: pourkit
|
|
8977
|
+
`Before handoff, run: pourkit validate-artifact final-review .pourkit/final-review-artifact.json --prd-ref ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
|
|
8918
8978
|
"Fix any validation failures before handing off.",
|
|
8919
8979
|
"",
|
|
8920
8980
|
"## Verification",
|
|
@@ -8941,40 +9001,6 @@ function finalReviewWorktreePath(repoRoot2, branchName) {
|
|
|
8941
9001
|
branchName.replace(/\//g, "-")
|
|
8942
9002
|
);
|
|
8943
9003
|
}
|
|
8944
|
-
function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
8945
|
-
const result = spawnSync3(
|
|
8946
|
-
"git",
|
|
8947
|
-
["merge-base", "origin/dev", `origin/${prdRef}`],
|
|
8948
|
-
{
|
|
8949
|
-
cwd: repoRoot2,
|
|
8950
|
-
encoding: "utf8"
|
|
8951
|
-
}
|
|
8952
|
-
);
|
|
8953
|
-
if (result.status !== 0) {
|
|
8954
|
-
return {
|
|
8955
|
-
ok: false,
|
|
8956
|
-
gate: "final-review",
|
|
8957
|
-
reason: `Unable to fetch refs and compute Final Review merge-base for ${prdRef}.`,
|
|
8958
|
-
diagnostics: collectSpawnDiagnostics(result, "git merge-base failed")
|
|
8959
|
-
};
|
|
8960
|
-
}
|
|
8961
|
-
return { ok: true, mergeBase: result.stdout.trim(), diagnostics: [] };
|
|
8962
|
-
}
|
|
8963
|
-
function computeLocalFinalReviewMergeBase(repoRoot2, localPrdBranch) {
|
|
8964
|
-
const result = spawnSync3("git", ["merge-base", "HEAD", localPrdBranch], {
|
|
8965
|
-
cwd: repoRoot2,
|
|
8966
|
-
encoding: "utf8"
|
|
8967
|
-
});
|
|
8968
|
-
if (result.status !== 0) {
|
|
8969
|
-
return {
|
|
8970
|
-
ok: false,
|
|
8971
|
-
gate: "final-review",
|
|
8972
|
-
reason: `Unable to compute Final Review merge-base for ${localPrdBranch}.`,
|
|
8973
|
-
diagnostics: collectSpawnDiagnostics(result, "git merge-base failed")
|
|
8974
|
-
};
|
|
8975
|
-
}
|
|
8976
|
-
return { ok: true, mergeBase: result.stdout.trim(), diagnostics: [] };
|
|
8977
|
-
}
|
|
8978
9004
|
function collectSpawnDiagnostics(result, fallback) {
|
|
8979
9005
|
const diagnostics = [];
|
|
8980
9006
|
const stderr = result.stderr?.toString?.().trim();
|
|
@@ -9411,7 +9437,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
9411
9437
|
options.repoRoot,
|
|
9412
9438
|
prdRef
|
|
9413
9439
|
);
|
|
9414
|
-
if (readDiagnostics.length > 0) {
|
|
9440
|
+
if (readDiagnostics.length > 0 && !record) {
|
|
9415
9441
|
const reason = `PRD Run ${prdRef} has a malformed record and cannot proceed to Final Review.`;
|
|
9416
9442
|
writePrdRunRecord(options.repoRoot, {
|
|
9417
9443
|
prdRef,
|
|
@@ -9617,7 +9643,33 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
9617
9643
|
);
|
|
9618
9644
|
} else {
|
|
9619
9645
|
prdBranch = record.prdBranch ?? prdRef;
|
|
9620
|
-
|
|
9646
|
+
const startBaseBranch = record.start?.startBaseBranch;
|
|
9647
|
+
if (!startBaseBranch) {
|
|
9648
|
+
const reason = `PRD Run ${prdRef} has a start receipt without startBaseBranch. This record predates target-owned PRD Run base branches and cannot proceed to Final Review.`;
|
|
9649
|
+
writePrdRunRecord(options.repoRoot, {
|
|
9650
|
+
...record,
|
|
9651
|
+
status: "blocked",
|
|
9652
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9653
|
+
blockedGate: "final-review",
|
|
9654
|
+
blockedReason: reason,
|
|
9655
|
+
diagnostics: [reason],
|
|
9656
|
+
targetName,
|
|
9657
|
+
offendingPaths: []
|
|
9658
|
+
});
|
|
9659
|
+
return {
|
|
9660
|
+
prdRef,
|
|
9661
|
+
status: "blocked",
|
|
9662
|
+
blockedGate: "final-review",
|
|
9663
|
+
blockedReason: reason,
|
|
9664
|
+
diagnostics: [reason],
|
|
9665
|
+
offendingPaths: []
|
|
9666
|
+
};
|
|
9667
|
+
}
|
|
9668
|
+
mergeBaseResult = computeFinalReviewMergeBase(
|
|
9669
|
+
options.repoRoot,
|
|
9670
|
+
prdRef,
|
|
9671
|
+
startBaseBranch
|
|
9672
|
+
);
|
|
9621
9673
|
}
|
|
9622
9674
|
if (!mergeBaseResult.ok) {
|
|
9623
9675
|
writePrdRunRecord(options.repoRoot, {
|
|
@@ -9666,7 +9718,10 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
9666
9718
|
childIssueCount: relatedIssues.length
|
|
9667
9719
|
},
|
|
9668
9720
|
stage: "prdFinalReview",
|
|
9669
|
-
stageReceipts: {
|
|
9721
|
+
stageReceipts: {
|
|
9722
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
9723
|
+
startBaseBranch: record.start?.startBaseBranch
|
|
9724
|
+
}
|
|
9670
9725
|
});
|
|
9671
9726
|
const startReceipt = {
|
|
9672
9727
|
status: "started",
|
|
@@ -10502,6 +10557,99 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10502
10557
|
offendingPaths: []
|
|
10503
10558
|
};
|
|
10504
10559
|
}
|
|
10560
|
+
function computeFinalReviewMergeBase(repoRoot2, prdRef, baseBranch) {
|
|
10561
|
+
const normalized = normalizePrdRunRef(prdRef);
|
|
10562
|
+
const fetchResult = spawnSync3(
|
|
10563
|
+
"git",
|
|
10564
|
+
["fetch", "origin", baseBranch, normalized],
|
|
10565
|
+
{
|
|
10566
|
+
cwd: repoRoot2,
|
|
10567
|
+
encoding: "utf8"
|
|
10568
|
+
}
|
|
10569
|
+
);
|
|
10570
|
+
const fetchDiagnostics = [];
|
|
10571
|
+
if (fetchResult.status !== 0) {
|
|
10572
|
+
const stderr = fetchResult.stderr?.toString?.() ?? "";
|
|
10573
|
+
const stdout = fetchResult.stdout?.toString?.() ?? "";
|
|
10574
|
+
if (stderr.trim()) fetchDiagnostics.push(stderr.trim());
|
|
10575
|
+
if (stdout.trim()) fetchDiagnostics.push(stdout.trim());
|
|
10576
|
+
return {
|
|
10577
|
+
ok: false,
|
|
10578
|
+
gate: "final-review",
|
|
10579
|
+
reason: `Final Review failed to fetch origin/${baseBranch} and origin/${normalized}.`,
|
|
10580
|
+
diagnostics: fetchDiagnostics.length > 0 ? fetchDiagnostics : ["git fetch failed"]
|
|
10581
|
+
};
|
|
10582
|
+
}
|
|
10583
|
+
if (fetchResult.stderr?.toString?.()?.trim()) {
|
|
10584
|
+
fetchDiagnostics.push(fetchResult.stderr.toString().trim());
|
|
10585
|
+
}
|
|
10586
|
+
const mergeBaseResult = spawnSync3(
|
|
10587
|
+
"git",
|
|
10588
|
+
["merge-base", `origin/${baseBranch}`, `origin/${normalized}`],
|
|
10589
|
+
{
|
|
10590
|
+
cwd: repoRoot2,
|
|
10591
|
+
encoding: "utf8"
|
|
10592
|
+
}
|
|
10593
|
+
);
|
|
10594
|
+
const mergeBaseDiagnostics = [];
|
|
10595
|
+
if (mergeBaseResult.status !== 0) {
|
|
10596
|
+
if (mergeBaseResult.stderr?.toString?.()?.trim()) {
|
|
10597
|
+
mergeBaseDiagnostics.push(mergeBaseResult.stderr.toString().trim());
|
|
10598
|
+
}
|
|
10599
|
+
if (mergeBaseResult.stdout?.toString?.()?.trim()) {
|
|
10600
|
+
mergeBaseDiagnostics.push(mergeBaseResult.stdout.toString().trim());
|
|
10601
|
+
}
|
|
10602
|
+
return {
|
|
10603
|
+
ok: false,
|
|
10604
|
+
gate: "final-review",
|
|
10605
|
+
reason: `Final Review merge-base computation failed for origin/${baseBranch} and origin/${normalized}.`,
|
|
10606
|
+
diagnostics: mergeBaseDiagnostics.length > 0 ? mergeBaseDiagnostics : ["git merge-base returned non-zero exit status"]
|
|
10607
|
+
};
|
|
10608
|
+
}
|
|
10609
|
+
const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
|
|
10610
|
+
if (!mergeBase) {
|
|
10611
|
+
return {
|
|
10612
|
+
ok: false,
|
|
10613
|
+
gate: "final-review",
|
|
10614
|
+
reason: `Final Review merge-base returned empty result for origin/${baseBranch} and origin/${normalized}.`,
|
|
10615
|
+
diagnostics: [
|
|
10616
|
+
`merge-base stdout was empty for origin/${baseBranch}..origin/${normalized}`
|
|
10617
|
+
]
|
|
10618
|
+
};
|
|
10619
|
+
}
|
|
10620
|
+
return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
|
|
10621
|
+
}
|
|
10622
|
+
function computeLocalFinalReviewMergeBase(repoRoot2, prdBranch) {
|
|
10623
|
+
const mergeBaseResult = spawnSync3("git", ["merge-base", "dev", prdBranch], {
|
|
10624
|
+
cwd: repoRoot2,
|
|
10625
|
+
encoding: "utf8"
|
|
10626
|
+
});
|
|
10627
|
+
if (mergeBaseResult.status !== 0) {
|
|
10628
|
+
const diagnostics = [];
|
|
10629
|
+
if (mergeBaseResult.stderr?.toString?.()?.trim()) {
|
|
10630
|
+
diagnostics.push(mergeBaseResult.stderr.toString().trim());
|
|
10631
|
+
}
|
|
10632
|
+
if (mergeBaseResult.stdout?.toString?.()?.trim()) {
|
|
10633
|
+
diagnostics.push(mergeBaseResult.stdout.toString().trim());
|
|
10634
|
+
}
|
|
10635
|
+
return {
|
|
10636
|
+
ok: false,
|
|
10637
|
+
gate: "final-review",
|
|
10638
|
+
reason: `Final Review merge-base computation failed for dev and ${prdBranch}.`,
|
|
10639
|
+
diagnostics: diagnostics.length > 0 ? diagnostics : ["git merge-base returned non-zero exit status"]
|
|
10640
|
+
};
|
|
10641
|
+
}
|
|
10642
|
+
const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
|
|
10643
|
+
if (!mergeBase) {
|
|
10644
|
+
return {
|
|
10645
|
+
ok: false,
|
|
10646
|
+
gate: "final-review",
|
|
10647
|
+
reason: `Final Review merge-base returned empty result for dev and ${prdBranch}.`,
|
|
10648
|
+
diagnostics: [`merge-base stdout was empty for dev..${prdBranch}`]
|
|
10649
|
+
};
|
|
10650
|
+
}
|
|
10651
|
+
return { ok: true, mergeBase, diagnostics: [] };
|
|
10652
|
+
}
|
|
10505
10653
|
async function runPrdRunLaunchCommand(options) {
|
|
10506
10654
|
const prdRef = normalizePrdRunRef(options.prdRef);
|
|
10507
10655
|
const attempted = [];
|
|
@@ -10576,7 +10724,8 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
10576
10724
|
prProvider: options.prProvider,
|
|
10577
10725
|
executionProvider: options.executionProvider,
|
|
10578
10726
|
logger: options.logger,
|
|
10579
|
-
adoptExistingBranch: options.adoptExistingBranch
|
|
10727
|
+
adoptExistingBranch: options.adoptExistingBranch,
|
|
10728
|
+
baseBranchOverride: options.baseBranchOverride
|
|
10580
10729
|
});
|
|
10581
10730
|
const skippedAfterStart = [
|
|
10582
10731
|
"queue",
|
|
@@ -10738,7 +10887,7 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
10738
10887
|
skipped,
|
|
10739
10888
|
resumed,
|
|
10740
10889
|
diagnostics: [
|
|
10741
|
-
|
|
10890
|
+
`Integration Gate is not implemented. PRD Run is waiting for manual integration back to ${currentRecord?.start?.startBaseBranch ?? "(missing startBaseBranch)"}.`
|
|
10742
10891
|
],
|
|
10743
10892
|
start: startResult,
|
|
10744
10893
|
finalReview: finalReviewResult
|
|
@@ -10777,8 +10926,52 @@ async function runPrdRunStartCommand(options) {
|
|
|
10777
10926
|
});
|
|
10778
10927
|
return buildBlockedStartResult(prdRef, failure);
|
|
10779
10928
|
}
|
|
10929
|
+
const resolvedTarget = options.config ? resolveTarget(options.config, targetName) : null;
|
|
10930
|
+
const targetBaseBranch = resolvedTarget?.baseBranch ?? "main";
|
|
10931
|
+
const baseResolution = resolvePrdRunBaseBranch({
|
|
10932
|
+
targetBaseBranch,
|
|
10933
|
+
baseBranchOverride: options.baseBranchOverride
|
|
10934
|
+
});
|
|
10935
|
+
if (!baseResolution.ok) {
|
|
10936
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, baseResolution, {
|
|
10937
|
+
targetName
|
|
10938
|
+
});
|
|
10939
|
+
return buildBlockedStartResult(prdRef, baseResolution);
|
|
10940
|
+
}
|
|
10780
10941
|
if (existingRecord.record?.start) {
|
|
10781
|
-
const
|
|
10942
|
+
const recordedBaseBranch = existingRecord.record.start.startBaseBranch;
|
|
10943
|
+
if (!recordedBaseBranch) {
|
|
10944
|
+
const failure = {
|
|
10945
|
+
ok: false,
|
|
10946
|
+
gate: "branch-state",
|
|
10947
|
+
reason: `PRD Run ${prdRef} has a start receipt without startBaseBranch. Run prd-run start to create a fresh start receipt.`,
|
|
10948
|
+
diagnostics: [
|
|
10949
|
+
`Recorded start: ${JSON.stringify(existingRecord.record.start)}`
|
|
10950
|
+
],
|
|
10951
|
+
offendingPaths: []
|
|
10952
|
+
};
|
|
10953
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
10954
|
+
targetName
|
|
10955
|
+
});
|
|
10956
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
10957
|
+
}
|
|
10958
|
+
if (recordedBaseBranch !== baseResolution.baseBranch) {
|
|
10959
|
+
const failure = {
|
|
10960
|
+
ok: false,
|
|
10961
|
+
gate: "branch-state",
|
|
10962
|
+
reason: `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.`,
|
|
10963
|
+
diagnostics: [
|
|
10964
|
+
`Recorded startBaseBranch: ${recordedBaseBranch}`,
|
|
10965
|
+
`Current base resolution: ${baseResolution.baseBranch} (source: ${baseResolution.source})`
|
|
10966
|
+
],
|
|
10967
|
+
offendingPaths: []
|
|
10968
|
+
};
|
|
10969
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
10970
|
+
targetName
|
|
10971
|
+
});
|
|
10972
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
10973
|
+
}
|
|
10974
|
+
const fetchResult2 = fetchOriginBranch(options.repoRoot, recordedBaseBranch);
|
|
10782
10975
|
if (!fetchResult2.ok) {
|
|
10783
10976
|
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, fetchResult2, {
|
|
10784
10977
|
targetName
|
|
@@ -10831,7 +11024,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
10831
11024
|
`Existing remote branch: ${prdRef}`,
|
|
10832
11025
|
`Recorded start branch: ${existingRecord.record.start.prdBranch}`,
|
|
10833
11026
|
`Recorded start base commit: ${existingRecord.record.start.startBaseCommit}`,
|
|
10834
|
-
`Current origin
|
|
11027
|
+
`Current origin/${recordedBaseBranch} head: ${fetchResult2.startBaseCommit}`
|
|
10835
11028
|
],
|
|
10836
11029
|
offendingPaths: []
|
|
10837
11030
|
};
|
|
@@ -10852,15 +11045,41 @@ async function runPrdRunStartCommand(options) {
|
|
|
10852
11045
|
);
|
|
10853
11046
|
return buildBlockedStartResult(prdRef, fetchPrdResult);
|
|
10854
11047
|
}
|
|
10855
|
-
const
|
|
11048
|
+
const ancestryResult = spawnSync3(
|
|
11049
|
+
"git",
|
|
11050
|
+
[
|
|
11051
|
+
"merge-base",
|
|
11052
|
+
"--is-ancestor",
|
|
11053
|
+
`origin/${recordedBaseBranch}`,
|
|
11054
|
+
`origin/${prdRef}`
|
|
11055
|
+
],
|
|
11056
|
+
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
11057
|
+
);
|
|
11058
|
+
if (ancestryResult.status !== 0) {
|
|
11059
|
+
const failure = {
|
|
11060
|
+
ok: false,
|
|
11061
|
+
gate: "branch-state",
|
|
11062
|
+
reason: `PRD Branch ${prdRef} base ancestry check failed: origin/${recordedBaseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
|
|
11063
|
+
diagnostics: [
|
|
11064
|
+
`Base branch: origin/${recordedBaseBranch}`,
|
|
11065
|
+
`PRD branch: origin/${prdRef}`
|
|
11066
|
+
],
|
|
11067
|
+
offendingPaths: []
|
|
11068
|
+
};
|
|
11069
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
11070
|
+
targetName
|
|
11071
|
+
});
|
|
11072
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
11073
|
+
}
|
|
11074
|
+
const adoptedStart = buildStartReceipt({
|
|
10856
11075
|
status: "adopted",
|
|
10857
11076
|
targetName,
|
|
10858
11077
|
prdBranch: prdRef,
|
|
11078
|
+
startBaseBranch: recordedBaseBranch,
|
|
10859
11079
|
startBaseCommit: fetchResult2.startBaseCommit,
|
|
10860
11080
|
branchAction: "adopted",
|
|
10861
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10862
|
-
|
|
10863
|
-
};
|
|
11081
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11082
|
+
});
|
|
10864
11083
|
persistStartingPrdRunRecord(
|
|
10865
11084
|
options.repoRoot,
|
|
10866
11085
|
prdRef,
|
|
@@ -10884,7 +11103,10 @@ async function runPrdRunStartCommand(options) {
|
|
|
10884
11103
|
);
|
|
10885
11104
|
}
|
|
10886
11105
|
}
|
|
10887
|
-
const fetchResult =
|
|
11106
|
+
const fetchResult = fetchOriginBranch(
|
|
11107
|
+
options.repoRoot,
|
|
11108
|
+
baseResolution.baseBranch
|
|
11109
|
+
);
|
|
10888
11110
|
if (!fetchResult.ok) {
|
|
10889
11111
|
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, fetchResult, {
|
|
10890
11112
|
targetName
|
|
@@ -10928,6 +11150,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
10928
11150
|
status: "started",
|
|
10929
11151
|
targetName,
|
|
10930
11152
|
prdBranch: localBranchName,
|
|
11153
|
+
startBaseBranch: baseResolution.baseBranch,
|
|
10931
11154
|
startBaseCommit: fetchResult.startBaseCommit,
|
|
10932
11155
|
branchAction: localBranchResult.created ? "created" : "reused",
|
|
10933
11156
|
startedAt: updatedAt2
|
|
@@ -10969,7 +11192,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
10969
11192
|
`Existing remote branch: ${prdRef}`,
|
|
10970
11193
|
`Recorded start branch: ${existingRecord.record?.start?.prdBranch ?? "(missing)"}`,
|
|
10971
11194
|
`Recorded start base commit: ${existingRecord.record?.start?.startBaseCommit ?? "(missing)"}`,
|
|
10972
|
-
`Current origin
|
|
11195
|
+
`Current origin/${baseResolution.baseBranch} head: ${fetchResult.startBaseCommit}`
|
|
10973
11196
|
],
|
|
10974
11197
|
offendingPaths: []
|
|
10975
11198
|
};
|
|
@@ -10990,15 +11213,41 @@ async function runPrdRunStartCommand(options) {
|
|
|
10990
11213
|
);
|
|
10991
11214
|
return buildBlockedStartResult(prdRef, fetchPrdResult);
|
|
10992
11215
|
}
|
|
10993
|
-
const
|
|
11216
|
+
const ancestryResult = spawnSync3(
|
|
11217
|
+
"git",
|
|
11218
|
+
[
|
|
11219
|
+
"merge-base",
|
|
11220
|
+
"--is-ancestor",
|
|
11221
|
+
`origin/${baseResolution.baseBranch}`,
|
|
11222
|
+
`origin/${prdRef}`
|
|
11223
|
+
],
|
|
11224
|
+
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
11225
|
+
);
|
|
11226
|
+
if (ancestryResult.status !== 0) {
|
|
11227
|
+
const failure = {
|
|
11228
|
+
ok: false,
|
|
11229
|
+
gate: "branch-state",
|
|
11230
|
+
reason: `PRD Branch ${prdRef} base ancestry check failed: origin/${baseResolution.baseBranch} is not an ancestor of origin/${prdRef}. Cannot adopt.`,
|
|
11231
|
+
diagnostics: [
|
|
11232
|
+
`Base branch: origin/${baseResolution.baseBranch}`,
|
|
11233
|
+
`PRD branch: origin/${prdRef}`
|
|
11234
|
+
],
|
|
11235
|
+
offendingPaths: []
|
|
11236
|
+
};
|
|
11237
|
+
persistBlockedPrdRunStartRecord(options.repoRoot, prdRef, failure, {
|
|
11238
|
+
targetName
|
|
11239
|
+
});
|
|
11240
|
+
return buildBlockedStartResult(prdRef, failure);
|
|
11241
|
+
}
|
|
11242
|
+
const adoptedStart = buildStartReceipt({
|
|
10994
11243
|
status: "adopted",
|
|
10995
11244
|
targetName,
|
|
10996
11245
|
prdBranch: prdRef,
|
|
11246
|
+
startBaseBranch: baseResolution.baseBranch,
|
|
10997
11247
|
startBaseCommit: fetchResult.startBaseCommit,
|
|
10998
11248
|
branchAction: "adopted",
|
|
10999
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11000
|
-
|
|
11001
|
-
};
|
|
11249
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11250
|
+
});
|
|
11002
11251
|
persistStartingPrdRunRecord(
|
|
11003
11252
|
options.repoRoot,
|
|
11004
11253
|
prdRef,
|
|
@@ -11047,6 +11296,7 @@ async function runPrdRunStartCommand(options) {
|
|
|
11047
11296
|
status: "started",
|
|
11048
11297
|
targetName,
|
|
11049
11298
|
prdBranch: prdRef,
|
|
11299
|
+
startBaseBranch: baseResolution.baseBranch,
|
|
11050
11300
|
startBaseCommit: fetchResult.startBaseCommit,
|
|
11051
11301
|
branchAction: "created",
|
|
11052
11302
|
startedAt: updatedAt
|
|
@@ -11367,8 +11617,98 @@ function inspectRemotePrdBranch(repoRoot2, prdRef) {
|
|
|
11367
11617
|
}
|
|
11368
11618
|
return { ok: true, exists: true, headSha };
|
|
11369
11619
|
}
|
|
11370
|
-
function
|
|
11371
|
-
const
|
|
11620
|
+
function resolvePrdRunBaseBranch(options) {
|
|
11621
|
+
const raw = options.baseBranchOverride ?? options.targetBaseBranch;
|
|
11622
|
+
const trimmed = raw.trim();
|
|
11623
|
+
if (!trimmed) {
|
|
11624
|
+
return {
|
|
11625
|
+
ok: false,
|
|
11626
|
+
gate: "branch-state",
|
|
11627
|
+
reason: `Invalid PRD Run base branch: value is empty after trimming.`,
|
|
11628
|
+
diagnostics: [`Provided branch value: "${raw}"`],
|
|
11629
|
+
offendingPaths: []
|
|
11630
|
+
};
|
|
11631
|
+
}
|
|
11632
|
+
if (trimmed.startsWith("origin/")) {
|
|
11633
|
+
return {
|
|
11634
|
+
ok: false,
|
|
11635
|
+
gate: "branch-state",
|
|
11636
|
+
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").`,
|
|
11637
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
11638
|
+
offendingPaths: []
|
|
11639
|
+
};
|
|
11640
|
+
}
|
|
11641
|
+
if (/^(refs\/|heads\/|remotes\/|tags\/)/.test(trimmed)) {
|
|
11642
|
+
return {
|
|
11643
|
+
ok: false,
|
|
11644
|
+
gate: "branch-state",
|
|
11645
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" must be a simple branch lane name, not a ref specification.`,
|
|
11646
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
11647
|
+
offendingPaths: []
|
|
11648
|
+
};
|
|
11649
|
+
}
|
|
11650
|
+
if (/^[0-9a-f]{7,40}$/i.test(trimmed)) {
|
|
11651
|
+
return {
|
|
11652
|
+
ok: false,
|
|
11653
|
+
gate: "branch-state",
|
|
11654
|
+
reason: `Invalid PRD Run base branch: value looks like a commit SHA rather than a branch lane name.`,
|
|
11655
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
11656
|
+
offendingPaths: []
|
|
11657
|
+
};
|
|
11658
|
+
}
|
|
11659
|
+
const segments = trimmed.split("/");
|
|
11660
|
+
for (const segment of segments) {
|
|
11661
|
+
if (segment === "." || segment === ".." || segment === "") {
|
|
11662
|
+
return {
|
|
11663
|
+
ok: false,
|
|
11664
|
+
gate: "branch-state",
|
|
11665
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" must not contain ".", "..", or empty segments.`,
|
|
11666
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
11667
|
+
offendingPaths: []
|
|
11668
|
+
};
|
|
11669
|
+
}
|
|
11670
|
+
}
|
|
11671
|
+
if (/[~^:?*\[\\]/.test(trimmed)) {
|
|
11672
|
+
return {
|
|
11673
|
+
ok: false,
|
|
11674
|
+
gate: "branch-state",
|
|
11675
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" contains invalid characters (~ ^ : ? * [ \\).`,
|
|
11676
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
11677
|
+
offendingPaths: []
|
|
11678
|
+
};
|
|
11679
|
+
}
|
|
11680
|
+
if (/[\x00-\x1f\x7f]/.test(trimmed)) {
|
|
11681
|
+
return {
|
|
11682
|
+
ok: false,
|
|
11683
|
+
gate: "branch-state",
|
|
11684
|
+
reason: `Invalid PRD Run base branch: value contains control characters.`,
|
|
11685
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
11686
|
+
offendingPaths: []
|
|
11687
|
+
};
|
|
11688
|
+
}
|
|
11689
|
+
if (trimmed.endsWith("/")) {
|
|
11690
|
+
return {
|
|
11691
|
+
ok: false,
|
|
11692
|
+
gate: "branch-state",
|
|
11693
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" must not end with a trailing slash.`,
|
|
11694
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
11695
|
+
offendingPaths: []
|
|
11696
|
+
};
|
|
11697
|
+
}
|
|
11698
|
+
if (trimmed.endsWith(".lock")) {
|
|
11699
|
+
return {
|
|
11700
|
+
ok: false,
|
|
11701
|
+
gate: "branch-state",
|
|
11702
|
+
reason: `Invalid PRD Run base branch: "${trimmed}" must not end with ".lock" suffix.`,
|
|
11703
|
+
diagnostics: [`Provided branch value: "${trimmed}"`],
|
|
11704
|
+
offendingPaths: []
|
|
11705
|
+
};
|
|
11706
|
+
}
|
|
11707
|
+
const source = options.baseBranchOverride !== void 0 ? "override" : "target";
|
|
11708
|
+
return { ok: true, baseBranch: trimmed, source };
|
|
11709
|
+
}
|
|
11710
|
+
function fetchOriginBranch(repoRoot2, baseBranch) {
|
|
11711
|
+
const fetchResult = spawnSync3("git", ["fetch", "origin", baseBranch], {
|
|
11372
11712
|
cwd: repoRoot2,
|
|
11373
11713
|
encoding: "utf8"
|
|
11374
11714
|
});
|
|
@@ -11376,7 +11716,7 @@ function fetchOriginDev(repoRoot2) {
|
|
|
11376
11716
|
return {
|
|
11377
11717
|
ok: false,
|
|
11378
11718
|
gate: "git",
|
|
11379
|
-
reason:
|
|
11719
|
+
reason: `PRD Run start failed while fetching origin/${baseBranch}.`,
|
|
11380
11720
|
diagnostics: [
|
|
11381
11721
|
fetchResult.error instanceof Error ? fetchResult.error.message : void 0,
|
|
11382
11722
|
fetchResult.stderr?.toString?.() ?? String(fetchResult.stderr ?? ""),
|
|
@@ -11385,15 +11725,19 @@ function fetchOriginDev(repoRoot2) {
|
|
|
11385
11725
|
offendingPaths: []
|
|
11386
11726
|
};
|
|
11387
11727
|
}
|
|
11388
|
-
const revParseResult = spawnSync3(
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11728
|
+
const revParseResult = spawnSync3(
|
|
11729
|
+
"git",
|
|
11730
|
+
["rev-parse", `origin/${baseBranch}`],
|
|
11731
|
+
{
|
|
11732
|
+
cwd: repoRoot2,
|
|
11733
|
+
encoding: "utf8"
|
|
11734
|
+
}
|
|
11735
|
+
);
|
|
11392
11736
|
if (revParseResult.status !== 0) {
|
|
11393
11737
|
return {
|
|
11394
11738
|
ok: false,
|
|
11395
11739
|
gate: "git",
|
|
11396
|
-
reason:
|
|
11740
|
+
reason: `PRD Run start failed while resolving origin/${baseBranch}.`,
|
|
11397
11741
|
diagnostics: [
|
|
11398
11742
|
revParseResult.error instanceof Error ? revParseResult.error.message : void 0,
|
|
11399
11743
|
revParseResult.stderr?.toString?.() ?? String(revParseResult.stderr ?? ""),
|
|
@@ -14979,7 +15323,7 @@ function createCliProgram(version) {
|
|
|
14979
15323
|
});
|
|
14980
15324
|
program.command("validate-artifact").description("Validate an agent handoff artifact").argument(
|
|
14981
15325
|
"<kind>",
|
|
14982
|
-
"artifact kind: reviewer, refactor, finalizer, conflict-resolution, failure-resolution, local-prd, local-issue, or local-triage"
|
|
15326
|
+
"artifact kind: reviewer, refactor, finalizer, conflict-resolution, final-review, failure-resolution, local-prd, local-issue, or local-triage"
|
|
14983
15327
|
).argument("<artifactPath>", "artifact path to validate").argument("[extraPaths...]", "additional artifact paths (for local-triage)").option("--iteration <number>", "review/refactor iteration", (value) => {
|
|
14984
15328
|
const parsed = Number.parseInt(value, 10);
|
|
14985
15329
|
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
@@ -15012,6 +15356,12 @@ function createCliProgram(version) {
|
|
|
15012
15356
|
return previous;
|
|
15013
15357
|
},
|
|
15014
15358
|
[]
|
|
15359
|
+
).option("--prd-ref <ref>", "PRD ref for final-review artifacts").option(
|
|
15360
|
+
"--checkout-base <ref>",
|
|
15361
|
+
"checkout base / PRD branch for final-review artifacts"
|
|
15362
|
+
).option(
|
|
15363
|
+
"--review-base <ref>",
|
|
15364
|
+
"review merge base for final-review artifacts"
|
|
15015
15365
|
).option("--no-check-conflict-markers", "skip conflict marker scan").option("--cwd <path>", "target repository directory").action(
|
|
15016
15366
|
(kind, artifactPath, extraPaths, options) => {
|
|
15017
15367
|
const allowedKinds = /* @__PURE__ */ new Set([
|
|
@@ -15019,6 +15369,7 @@ function createCliProgram(version) {
|
|
|
15019
15369
|
"refactor",
|
|
15020
15370
|
"finalizer",
|
|
15021
15371
|
"conflict-resolution",
|
|
15372
|
+
"final-review",
|
|
15022
15373
|
"failure-resolution",
|
|
15023
15374
|
"local-prd",
|
|
15024
15375
|
"local-issue",
|
|
@@ -15051,6 +15402,9 @@ function createCliProgram(version) {
|
|
|
15051
15402
|
findingIds: options.findingId,
|
|
15052
15403
|
latestReviewArtifactPath: options.latestReviewArtifact,
|
|
15053
15404
|
allowedDecisions: options.allowedDecision,
|
|
15405
|
+
prdRef: options.prdRef,
|
|
15406
|
+
checkoutBase: options.checkoutBase,
|
|
15407
|
+
reviewBase: options.reviewBase,
|
|
15054
15408
|
checkConflictMarkers: options.checkConflictMarkers
|
|
15055
15409
|
});
|
|
15056
15410
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -15263,7 +15617,7 @@ function createCliProgram(version) {
|
|
|
15263
15617
|
}
|
|
15264
15618
|
);
|
|
15265
15619
|
const prdRun = program.command("prd-run").description("PRD Run commands");
|
|
15266
|
-
prdRun.command("start").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--adopt-existing-branch", "adopt an existing PRD branch").option("--cwd <path>", "target repository directory").action(
|
|
15620
|
+
prdRun.command("start").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--base <branch>", "override target baseBranch for this PRD Run").option("--adopt-existing-branch", "adopt an existing PRD branch").option("--cwd <path>", "target repository directory").action(
|
|
15267
15621
|
async (prdRef, options) => {
|
|
15268
15622
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15269
15623
|
const normalizedPrdRef = normalizePrdRef(prdRef);
|
|
@@ -15288,6 +15642,7 @@ function createCliProgram(version) {
|
|
|
15288
15642
|
repoRoot: targetRepoRoot,
|
|
15289
15643
|
prdRef: normalizedPrdRef,
|
|
15290
15644
|
targetName: options.target,
|
|
15645
|
+
baseBranchOverride: options.base,
|
|
15291
15646
|
adoptExistingBranch: options.adoptExistingBranch ?? false,
|
|
15292
15647
|
config,
|
|
15293
15648
|
issueProvider,
|
|
@@ -15308,7 +15663,7 @@ function createCliProgram(version) {
|
|
|
15308
15663
|
}
|
|
15309
15664
|
}
|
|
15310
15665
|
);
|
|
15311
|
-
prdRun.command("launch").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").option("--no-auto-merge", "skip auto-merge for manual review").option("--adopt-existing-branch", "adopt an existing PRD branch").action(
|
|
15666
|
+
prdRun.command("launch").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--base <branch>", "override target baseBranch for this PRD Run").option("--cwd <path>", "target repository directory").option("--no-auto-merge", "skip auto-merge for manual review").option("--adopt-existing-branch", "adopt an existing PRD branch").action(
|
|
15312
15667
|
async (prdRef, options) => {
|
|
15313
15668
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15314
15669
|
const normalizedPrdRef = normalizePrdRef(prdRef);
|
|
@@ -15335,6 +15690,7 @@ function createCliProgram(version) {
|
|
|
15335
15690
|
repoRoot: targetRepoRoot,
|
|
15336
15691
|
prdRef: normalizedPrdRef,
|
|
15337
15692
|
targetName: options.target,
|
|
15693
|
+
baseBranchOverride: options.base,
|
|
15338
15694
|
autoMerge: options.autoMerge ?? true,
|
|
15339
15695
|
adoptExistingBranch: options.adoptExistingBranch ?? false,
|
|
15340
15696
|
config,
|
|
@@ -15530,11 +15886,11 @@ function createCliProgram(version) {
|
|
|
15530
15886
|
return program;
|
|
15531
15887
|
}
|
|
15532
15888
|
async function resolveCliVersion() {
|
|
15533
|
-
if (isPackageVersion("0.0.0-next-
|
|
15534
|
-
return "0.0.0-next-
|
|
15889
|
+
if (isPackageVersion("0.0.0-next-20260613012953")) {
|
|
15890
|
+
return "0.0.0-next-20260613012953";
|
|
15535
15891
|
}
|
|
15536
|
-
if (isReleaseVersion("0.0.0-next-
|
|
15537
|
-
return "0.0.0-next-
|
|
15892
|
+
if (isReleaseVersion("0.0.0-next-20260613012953")) {
|
|
15893
|
+
return "0.0.0-next-20260613012953";
|
|
15538
15894
|
}
|
|
15539
15895
|
try {
|
|
15540
15896
|
const root = repoRoot();
|