@pourkit/cli 0.0.0-next-20260604153710 → 0.0.0-next-20260604225844
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 +1680 -692
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +39 -4
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -430,7 +430,9 @@ var init_github_client = __esm({
|
|
|
430
430
|
});
|
|
431
431
|
|
|
432
432
|
// execution/execution-provider.ts
|
|
433
|
+
import { mkdtempSync } from "fs";
|
|
433
434
|
import { writeFile } from "fs/promises";
|
|
435
|
+
import { tmpdir } from "os";
|
|
434
436
|
import { dirname as dirname3, join as join7 } from "path";
|
|
435
437
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
436
438
|
for (const artifact of artifacts) {
|
|
@@ -898,13 +900,19 @@ var SerenaConfigSchema = z.object({
|
|
|
898
900
|
dataDir: z.string().default(".pourkit/serena/"),
|
|
899
901
|
autoStart: z.boolean().default(false)
|
|
900
902
|
}).strict();
|
|
903
|
+
var SecurityConfigSchema = z.object({
|
|
904
|
+
secretLikeContentDetection: z.object({
|
|
905
|
+
enabled: z.boolean().default(true)
|
|
906
|
+
}).strict().default({})
|
|
907
|
+
}).strict();
|
|
901
908
|
var PourkitConfigSchema = z.object({
|
|
902
909
|
targets: z.array(TargetSchema).min(1),
|
|
903
910
|
labels: LabelsSchema,
|
|
904
911
|
sandbox: SandboxSchema,
|
|
905
912
|
checks: ChecksSchema,
|
|
906
913
|
cleanup: CleanupConfigSchema.optional(),
|
|
907
|
-
serena: SerenaConfigSchema.default({})
|
|
914
|
+
serena: SerenaConfigSchema.default({}),
|
|
915
|
+
security: SecurityConfigSchema.default({})
|
|
908
916
|
}).strict();
|
|
909
917
|
var removedFieldReplacements = {
|
|
910
918
|
"config.implementor": "targets[].strategy.implement.builder",
|
|
@@ -1151,6 +1159,7 @@ function parseConfig(raw) {
|
|
|
1151
1159
|
issueListLimit: data.checks.issueListLimit ?? 50
|
|
1152
1160
|
},
|
|
1153
1161
|
serena,
|
|
1162
|
+
security: data.security,
|
|
1154
1163
|
cleanup: {
|
|
1155
1164
|
enabled: data.cleanup?.enabled ?? true,
|
|
1156
1165
|
worktreeRetentionDays: data.cleanup?.worktreeRetentionDays ?? 14,
|
|
@@ -1169,13 +1178,13 @@ function getVerificationCommands(target) {
|
|
|
1169
1178
|
return target.strategy.verify?.commands ?? [];
|
|
1170
1179
|
}
|
|
1171
1180
|
async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
|
|
1172
|
-
const { existsSync:
|
|
1181
|
+
const { existsSync: existsSync14 } = await import("fs");
|
|
1173
1182
|
const { mkdir: mkdir5, writeFile: writeFile3, rm } = await import("fs/promises");
|
|
1174
1183
|
const { join: pjoin, basename } = await import("path");
|
|
1175
1184
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
1176
1185
|
const { build } = await import("esbuild");
|
|
1177
1186
|
const configPath = pjoin(repoRoot2, configFileName);
|
|
1178
|
-
if (!
|
|
1187
|
+
if (!existsSync14(configPath)) {
|
|
1179
1188
|
throw new Error(
|
|
1180
1189
|
`No config file found at ${configPath}. Create a ${configFileName} that exports a default PourkitConfig.`
|
|
1181
1190
|
);
|
|
@@ -5013,6 +5022,7 @@ async function completeIssueRun(options) {
|
|
|
5013
5022
|
baseRef: `origin/${effectiveBaseBranch}`,
|
|
5014
5023
|
title: prTitle,
|
|
5015
5024
|
body: finalBody,
|
|
5025
|
+
secretLikeContentDetectionEnabled: isSecretLikeContentDetectionEnabled(config),
|
|
5016
5026
|
logger
|
|
5017
5027
|
});
|
|
5018
5028
|
if (executionResult.worktreePath) {
|
|
@@ -5053,6 +5063,7 @@ async function completeIssueRun(options) {
|
|
|
5053
5063
|
baseRef: `origin/${effectiveBaseBranch}`,
|
|
5054
5064
|
title: prTitle,
|
|
5055
5065
|
body: finalBody,
|
|
5066
|
+
secretLikeContentDetectionEnabled: isSecretLikeContentDetectionEnabled(config),
|
|
5056
5067
|
logger
|
|
5057
5068
|
});
|
|
5058
5069
|
await execCapture("git", ["push", "-u", "origin", branchName], {
|
|
@@ -5233,7 +5244,14 @@ function getRefactorArtifactDir(artifactPath) {
|
|
|
5233
5244
|
return artifactPath.replace(/\/reviewers\//, "/refactors/").replace(/\/[^/]+$/, "");
|
|
5234
5245
|
}
|
|
5235
5246
|
async function finalizeWorktreeCommit(options) {
|
|
5236
|
-
const {
|
|
5247
|
+
const {
|
|
5248
|
+
worktreePath,
|
|
5249
|
+
baseRef,
|
|
5250
|
+
title,
|
|
5251
|
+
body,
|
|
5252
|
+
secretLikeContentDetectionEnabled,
|
|
5253
|
+
logger
|
|
5254
|
+
} = options;
|
|
5237
5255
|
await syncRemoteBaseRef(worktreePath, baseRef, logger);
|
|
5238
5256
|
try {
|
|
5239
5257
|
await execCapture("git", ["merge-base", "--is-ancestor", baseRef, "HEAD"], {
|
|
@@ -5260,6 +5278,7 @@ async function finalizeWorktreeCommit(options) {
|
|
|
5260
5278
|
worktreePath,
|
|
5261
5279
|
title,
|
|
5262
5280
|
body,
|
|
5281
|
+
enabled: secretLikeContentDetectionEnabled,
|
|
5263
5282
|
logger
|
|
5264
5283
|
});
|
|
5265
5284
|
await execCapture("git", ["commit", "--no-verify", "-m", title, "-m", body], {
|
|
@@ -5269,7 +5288,10 @@ async function finalizeWorktreeCommit(options) {
|
|
|
5269
5288
|
});
|
|
5270
5289
|
}
|
|
5271
5290
|
async function guardFinalCommitContent(options) {
|
|
5272
|
-
const { worktreePath, title, body, logger } = options;
|
|
5291
|
+
const { worktreePath, title, body, enabled, logger } = options;
|
|
5292
|
+
if (!enabled) {
|
|
5293
|
+
return;
|
|
5294
|
+
}
|
|
5273
5295
|
const stagedDiff = await execCapture("git", ["diff", "--cached"], {
|
|
5274
5296
|
cwd: worktreePath,
|
|
5275
5297
|
logger,
|
|
@@ -5285,7 +5307,17 @@ async function guardFinalCommitContent(options) {
|
|
|
5285
5307
|
]);
|
|
5286
5308
|
}
|
|
5287
5309
|
async function guardFinalPublishContent(options) {
|
|
5288
|
-
const {
|
|
5310
|
+
const {
|
|
5311
|
+
worktreePath,
|
|
5312
|
+
baseRef,
|
|
5313
|
+
title,
|
|
5314
|
+
body,
|
|
5315
|
+
secretLikeContentDetectionEnabled,
|
|
5316
|
+
logger
|
|
5317
|
+
} = options;
|
|
5318
|
+
if (!secretLikeContentDetectionEnabled) {
|
|
5319
|
+
return;
|
|
5320
|
+
}
|
|
5289
5321
|
const finalDiff = await execCapture("git", ["diff", `${baseRef}...HEAD`], {
|
|
5290
5322
|
cwd: worktreePath,
|
|
5291
5323
|
logger,
|
|
@@ -5315,6 +5347,9 @@ function assertNoSecretLikeContent(inputs) {
|
|
|
5315
5347
|
`Secret-like content detected before finalization (${summary}). Redact it before committing, pushing, or creating a PR.`
|
|
5316
5348
|
);
|
|
5317
5349
|
}
|
|
5350
|
+
function isSecretLikeContentDetectionEnabled(config) {
|
|
5351
|
+
return config.security?.secretLikeContentDetection.enabled !== false;
|
|
5352
|
+
}
|
|
5318
5353
|
function findSecretLikeContent(source, content) {
|
|
5319
5354
|
const findings = [];
|
|
5320
5355
|
const patterns = [
|
|
@@ -6044,15 +6079,16 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
|
|
|
6044
6079
|
// commands/prd-run.ts
|
|
6045
6080
|
import {
|
|
6046
6081
|
cpSync,
|
|
6047
|
-
existsSync as
|
|
6082
|
+
existsSync as existsSync12,
|
|
6048
6083
|
mkdirSync as mkdirSync8,
|
|
6049
|
-
mkdtempSync,
|
|
6050
|
-
|
|
6084
|
+
mkdtempSync as mkdtempSync2,
|
|
6085
|
+
readdirSync as readdirSync4,
|
|
6086
|
+
readFileSync as readFileSync12,
|
|
6051
6087
|
rmSync as rmSync3
|
|
6052
6088
|
} from "fs";
|
|
6053
6089
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
6054
6090
|
import { dirname as dirname7, join as join15, relative as relative2 } from "path";
|
|
6055
|
-
import { tmpdir } from "os";
|
|
6091
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
6056
6092
|
|
|
6057
6093
|
// prd-run/planning-manifest.ts
|
|
6058
6094
|
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
|
|
@@ -6678,7 +6714,8 @@ var PrdRunRecordSchema = z3.object({
|
|
|
6678
6714
|
"branch-state",
|
|
6679
6715
|
"git",
|
|
6680
6716
|
"queue",
|
|
6681
|
-
"final-review"
|
|
6717
|
+
"final-review",
|
|
6718
|
+
"reconciliation"
|
|
6682
6719
|
]).optional(),
|
|
6683
6720
|
targetName: z3.string().min(1).optional(),
|
|
6684
6721
|
prdBranch: z3.string().min(1).optional(),
|
|
@@ -6735,6 +6772,14 @@ var PrdRunRecordSchema = z3.object({
|
|
|
6735
6772
|
autoMerge: z3.boolean().optional(),
|
|
6736
6773
|
changedPaths: z3.array(z3.string()).optional()
|
|
6737
6774
|
}).strict().optional(),
|
|
6775
|
+
reconciliation: z3.object({
|
|
6776
|
+
status: z3.enum(["succeeded", "blocked"]),
|
|
6777
|
+
targetName: z3.string().min(1),
|
|
6778
|
+
prdBranch: z3.string().min(1),
|
|
6779
|
+
mergeBase: z3.string().min(1).optional(),
|
|
6780
|
+
diagnostics: z3.array(z3.string()).optional(),
|
|
6781
|
+
reconciledAt: z3.string().min(1).optional()
|
|
6782
|
+
}).strict().optional(),
|
|
6738
6783
|
scopeChanges: z3.array(
|
|
6739
6784
|
z3.object({
|
|
6740
6785
|
issueNumber: z3.number().int().positive(),
|
|
@@ -6820,6 +6865,300 @@ function getRecordPath(repoRoot2, prdRef) {
|
|
|
6820
6865
|
);
|
|
6821
6866
|
}
|
|
6822
6867
|
|
|
6868
|
+
// prd-run/evidence-packet.ts
|
|
6869
|
+
import { z as z4 } from "zod";
|
|
6870
|
+
var PRD_REF_REGEX2 = /^PRD-\d{3,4}$/;
|
|
6871
|
+
var StageSchema = z4.enum(["prdFinalReview", "prdReconciliation"]);
|
|
6872
|
+
var EvidencePacketSchema = z4.object({
|
|
6873
|
+
prdRef: z4.string().regex(PRD_REF_REGEX2),
|
|
6874
|
+
prdBranch: z4.string().min(1),
|
|
6875
|
+
mergeBase: z4.string().min(6),
|
|
6876
|
+
planningManifestPath: z4.string().min(1),
|
|
6877
|
+
planningManifestFacts: z4.object({
|
|
6878
|
+
parentPrdIssueUrl: z4.string().min(1),
|
|
6879
|
+
childIssueCount: z4.number().int().nonnegative()
|
|
6880
|
+
}),
|
|
6881
|
+
stage: StageSchema,
|
|
6882
|
+
stageReceipts: z4.record(z4.string(), z4.unknown())
|
|
6883
|
+
}).strict();
|
|
6884
|
+
function buildEvidencePacket(input) {
|
|
6885
|
+
const requiredFields = [
|
|
6886
|
+
"prdRef",
|
|
6887
|
+
"prdBranch",
|
|
6888
|
+
"mergeBase",
|
|
6889
|
+
"planningManifestPath",
|
|
6890
|
+
"stage"
|
|
6891
|
+
];
|
|
6892
|
+
for (const field of requiredFields) {
|
|
6893
|
+
const value = input[field];
|
|
6894
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
6895
|
+
throw new Error(
|
|
6896
|
+
`Evidence Packet construction failed: "${field}" is required and must be a non-empty string.`
|
|
6897
|
+
);
|
|
6898
|
+
}
|
|
6899
|
+
}
|
|
6900
|
+
if (!input.planningManifestFacts || typeof input.planningManifestFacts.parentPrdIssueUrl !== "string" || input.planningManifestFacts.parentPrdIssueUrl.trim().length === 0) {
|
|
6901
|
+
throw new Error(
|
|
6902
|
+
'Evidence Packet construction failed: "planningManifestFacts.parentPrdIssueUrl" is required and must be a non-empty string.'
|
|
6903
|
+
);
|
|
6904
|
+
}
|
|
6905
|
+
if (typeof input.planningManifestFacts.childIssueCount !== "number" || !Number.isInteger(input.planningManifestFacts.childIssueCount) || input.planningManifestFacts.childIssueCount < 0) {
|
|
6906
|
+
throw new Error(
|
|
6907
|
+
'Evidence Packet construction failed: "planningManifestFacts.childIssueCount" is required and must be a non-negative integer.'
|
|
6908
|
+
);
|
|
6909
|
+
}
|
|
6910
|
+
if (!PRD_REF_REGEX2.test(input.prdRef.trim())) {
|
|
6911
|
+
throw new Error(
|
|
6912
|
+
`Evidence Packet construction failed: "prdRef" must match PRD-\\d{3,4} format, got "${input.prdRef}".`
|
|
6913
|
+
);
|
|
6914
|
+
}
|
|
6915
|
+
const stageResult = StageSchema.safeParse(input.stage);
|
|
6916
|
+
if (!stageResult.success) {
|
|
6917
|
+
throw new Error(
|
|
6918
|
+
`Evidence Packet construction failed: "stage" must be one of "prdFinalReview" or "prdReconciliation", got "${input.stage}".`
|
|
6919
|
+
);
|
|
6920
|
+
}
|
|
6921
|
+
if (stageResult.data === "prdFinalReview") {
|
|
6922
|
+
const hasMergeBaseReceipt = "mergeBase" in input.stageReceipts;
|
|
6923
|
+
const hasFinalReviewRef = "finalReviewRef" in input.stageReceipts;
|
|
6924
|
+
if (!hasMergeBaseReceipt && !hasFinalReviewRef) {
|
|
6925
|
+
throw new Error(
|
|
6926
|
+
"Evidence Packet construction failed: Final Review stage receipts must include at minimum a mergeBase or finalReviewRef receipt."
|
|
6927
|
+
);
|
|
6928
|
+
}
|
|
6929
|
+
}
|
|
6930
|
+
if (stageResult.data === "prdReconciliation") {
|
|
6931
|
+
if (!("finalReviewReceipt" in input.stageReceipts)) {
|
|
6932
|
+
throw new Error(
|
|
6933
|
+
"Evidence Packet construction failed: Reconciliation stage receipts must include at minimum a finalReviewReceipt."
|
|
6934
|
+
);
|
|
6935
|
+
}
|
|
6936
|
+
}
|
|
6937
|
+
const mergeBase = input.mergeBase.trim();
|
|
6938
|
+
if (mergeBase.length < 6) {
|
|
6939
|
+
throw new Error(
|
|
6940
|
+
`Evidence Packet construction failed: "mergeBase" must be a non-empty commit SHA with at least 6 characters, got "${mergeBase}".`
|
|
6941
|
+
);
|
|
6942
|
+
}
|
|
6943
|
+
const packet = {
|
|
6944
|
+
prdRef: input.prdRef.trim(),
|
|
6945
|
+
prdBranch: input.prdBranch.trim(),
|
|
6946
|
+
mergeBase,
|
|
6947
|
+
planningManifestPath: input.planningManifestPath.trim(),
|
|
6948
|
+
planningManifestFacts: {
|
|
6949
|
+
parentPrdIssueUrl: input.planningManifestFacts.parentPrdIssueUrl.trim(),
|
|
6950
|
+
childIssueCount: input.planningManifestFacts.childIssueCount
|
|
6951
|
+
},
|
|
6952
|
+
stage: stageResult.data,
|
|
6953
|
+
stageReceipts: input.stageReceipts
|
|
6954
|
+
};
|
|
6955
|
+
return packet;
|
|
6956
|
+
}
|
|
6957
|
+
var TOKEN_LIKE_PATTERNS = [
|
|
6958
|
+
/gh[ps]_[A-Za-z0-9]{36}/g,
|
|
6959
|
+
/token[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
6960
|
+
/secret[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
6961
|
+
/credential[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
6962
|
+
/key[=:_\s][A-Za-z0-9_-]{20,}/gi,
|
|
6963
|
+
/-----BEGIN (RSA |EC )?PRIVATE KEY-----/g,
|
|
6964
|
+
/xox[baprs]-[A-Za-z0-9_-]{10,}/g
|
|
6965
|
+
];
|
|
6966
|
+
function redactSensitiveValues(input) {
|
|
6967
|
+
let redacted = input;
|
|
6968
|
+
for (const pattern of TOKEN_LIKE_PATTERNS) {
|
|
6969
|
+
redacted = redacted.replace(pattern, "[REDACTED]");
|
|
6970
|
+
}
|
|
6971
|
+
return redacted;
|
|
6972
|
+
}
|
|
6973
|
+
|
|
6974
|
+
// prd-run/final-review-validation.ts
|
|
6975
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
6976
|
+
function parseFinalReviewArtifact(artifactPath) {
|
|
6977
|
+
if (!existsSync11(artifactPath)) {
|
|
6978
|
+
return {
|
|
6979
|
+
ok: false,
|
|
6980
|
+
reason: "Final Review artifact not found.",
|
|
6981
|
+
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
6982
|
+
};
|
|
6983
|
+
}
|
|
6984
|
+
let content;
|
|
6985
|
+
try {
|
|
6986
|
+
content = readFileSync11(artifactPath, "utf-8");
|
|
6987
|
+
} catch (error) {
|
|
6988
|
+
return {
|
|
6989
|
+
ok: false,
|
|
6990
|
+
reason: "Final Review artifact could not be read.",
|
|
6991
|
+
diagnostics: [
|
|
6992
|
+
error instanceof Error ? error.message : String(error),
|
|
6993
|
+
`Artifact path: ${artifactPath}`
|
|
6994
|
+
]
|
|
6995
|
+
};
|
|
6996
|
+
}
|
|
6997
|
+
let parsed;
|
|
6998
|
+
try {
|
|
6999
|
+
parsed = JSON.parse(content);
|
|
7000
|
+
} catch {
|
|
7001
|
+
return {
|
|
7002
|
+
ok: false,
|
|
7003
|
+
reason: "Final Review artifact is not valid JSON.",
|
|
7004
|
+
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
7005
|
+
};
|
|
7006
|
+
}
|
|
7007
|
+
const verdict = parsed.verdict;
|
|
7008
|
+
if (!verdict || typeof verdict !== "string") {
|
|
7009
|
+
return {
|
|
7010
|
+
ok: false,
|
|
7011
|
+
reason: "Final Review artifact is missing a verdict field.",
|
|
7012
|
+
diagnostics: [`Artifact content preview: ${content.slice(0, 300)}`]
|
|
7013
|
+
};
|
|
7014
|
+
}
|
|
7015
|
+
const allowedVerdicts = [
|
|
7016
|
+
"pass_no_changes",
|
|
7017
|
+
"pass_with_retouch",
|
|
7018
|
+
"needs_human_review",
|
|
7019
|
+
"blocked"
|
|
7020
|
+
];
|
|
7021
|
+
if (!allowedVerdicts.includes(verdict)) {
|
|
7022
|
+
return {
|
|
7023
|
+
ok: false,
|
|
7024
|
+
reason: `Final Review artifact has unsupported verdict "${verdict}".`,
|
|
7025
|
+
diagnostics: [`Allowed verdicts: ${allowedVerdicts.join(", ")}`]
|
|
7026
|
+
};
|
|
7027
|
+
}
|
|
7028
|
+
const summary = typeof parsed.summary === "string" ? parsed.summary : typeof parsed.reason === "string" ? parsed.reason : String(verdict);
|
|
7029
|
+
const diagnostics = Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [];
|
|
7030
|
+
const changedPaths = Array.isArray(parsed.changedPaths) ? parsed.changedPaths : void 0;
|
|
7031
|
+
const isAutoSummary = !parsed.summary && !parsed.reason;
|
|
7032
|
+
if (verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && isAutoSummary) {
|
|
7033
|
+
return {
|
|
7034
|
+
ok: false,
|
|
7035
|
+
reason: 'Final Review artifact with "pass_with_retouch" verdict requires changed paths.',
|
|
7036
|
+
diagnostics: [
|
|
7037
|
+
"pass_with_retouch verdict must include a changedPaths array in the artifact or provide a summary/reason with enough context for git diff fallback.",
|
|
7038
|
+
"Provide changedPaths or a descriptive summary in the artifact."
|
|
7039
|
+
]
|
|
7040
|
+
};
|
|
7041
|
+
}
|
|
7042
|
+
const prdRef = typeof parsed.prdRef === "string" && parsed.prdRef.trim() ? parsed.prdRef.trim() : void 0;
|
|
7043
|
+
const stage = typeof parsed.stage === "string" && parsed.stage.trim() ? parsed.stage.trim() : void 0;
|
|
7044
|
+
const checkoutBase = typeof parsed.checkoutBase === "string" && parsed.checkoutBase.trim() ? parsed.checkoutBase.trim() : void 0;
|
|
7045
|
+
const reviewBase = typeof parsed.reviewBase === "string" && parsed.reviewBase.trim() ? parsed.reviewBase.trim() : void 0;
|
|
7046
|
+
return {
|
|
7047
|
+
ok: true,
|
|
7048
|
+
verdict,
|
|
7049
|
+
summary,
|
|
7050
|
+
diagnostics,
|
|
7051
|
+
changedPaths,
|
|
7052
|
+
prdRef,
|
|
7053
|
+
stage,
|
|
7054
|
+
checkoutBase,
|
|
7055
|
+
reviewBase
|
|
7056
|
+
};
|
|
7057
|
+
}
|
|
7058
|
+
function validateFinalReviewArtifactSemanticIds(artifact, context) {
|
|
7059
|
+
const errors = [];
|
|
7060
|
+
if (!artifact.prdRef) {
|
|
7061
|
+
errors.push("Final Review artifact is missing prdRef.");
|
|
7062
|
+
} else if (artifact.prdRef !== context.prdRef) {
|
|
7063
|
+
errors.push(
|
|
7064
|
+
`Final Review artifact prdRef "${artifact.prdRef}" does not match active PRD Run "${context.prdRef}".`
|
|
7065
|
+
);
|
|
7066
|
+
}
|
|
7067
|
+
if (!artifact.stage) {
|
|
7068
|
+
errors.push("Final Review artifact is missing stage field.");
|
|
7069
|
+
} else if (artifact.stage !== "prdFinalReview") {
|
|
7070
|
+
errors.push(
|
|
7071
|
+
`Final Review artifact stage "${artifact.stage}" is not "prdFinalReview".`
|
|
7072
|
+
);
|
|
7073
|
+
}
|
|
7074
|
+
if (!artifact.checkoutBase) {
|
|
7075
|
+
errors.push("Final Review artifact is missing checkoutBase.");
|
|
7076
|
+
} else if (artifact.checkoutBase !== context.prdBranch) {
|
|
7077
|
+
errors.push(
|
|
7078
|
+
`Final Review artifact checkoutBase "${artifact.checkoutBase}" does not match active PRD Branch "${context.prdBranch}".`
|
|
7079
|
+
);
|
|
7080
|
+
}
|
|
7081
|
+
if (!artifact.reviewBase) {
|
|
7082
|
+
errors.push("Final Review artifact is missing reviewBase.");
|
|
7083
|
+
} else if (artifact.reviewBase !== context.mergeBase) {
|
|
7084
|
+
errors.push(
|
|
7085
|
+
`Final Review artifact reviewBase "${artifact.reviewBase}" does not match active merge base "${context.mergeBase}".`
|
|
7086
|
+
);
|
|
7087
|
+
}
|
|
7088
|
+
if (errors.length > 0) {
|
|
7089
|
+
return { ok: false, errors, blockedGate: "final-review" };
|
|
7090
|
+
}
|
|
7091
|
+
return { ok: true };
|
|
7092
|
+
}
|
|
7093
|
+
function validateFinalReviewRetouchScope(changedPaths) {
|
|
7094
|
+
if (!changedPaths || changedPaths.length === 0) {
|
|
7095
|
+
return {
|
|
7096
|
+
ok: false,
|
|
7097
|
+
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.",
|
|
7098
|
+
diagnostics: ["Changed paths list is empty or undefined."],
|
|
7099
|
+
offendingPaths: []
|
|
7100
|
+
};
|
|
7101
|
+
}
|
|
7102
|
+
const offendingPaths = changedPaths.filter((p) => !isRetouchScopePath(p));
|
|
7103
|
+
const allowedPaths = changedPaths.filter((p) => isRetouchScopePath(p));
|
|
7104
|
+
if (allowedPaths.length === 0) {
|
|
7105
|
+
return {
|
|
7106
|
+
ok: false,
|
|
7107
|
+
reason: "Final Review retouch scope validation failed. No implementation, test, or Changeset paths found for retouch.",
|
|
7108
|
+
diagnostics: [
|
|
7109
|
+
"All changed paths are prohibited for retouch.",
|
|
7110
|
+
...changedPaths.map((p) => ` - ${p}`)
|
|
7111
|
+
],
|
|
7112
|
+
offendingPaths
|
|
7113
|
+
};
|
|
7114
|
+
}
|
|
7115
|
+
if (offendingPaths.length > 0) {
|
|
7116
|
+
return {
|
|
7117
|
+
ok: false,
|
|
7118
|
+
reason: "Final Review retouch scope validation failed. Prohibited paths cannot be included in retouch PR.",
|
|
7119
|
+
diagnostics: [
|
|
7120
|
+
"Prohibited paths found:",
|
|
7121
|
+
...offendingPaths.map((p) => ` - ${p}`)
|
|
7122
|
+
],
|
|
7123
|
+
offendingPaths
|
|
7124
|
+
};
|
|
7125
|
+
}
|
|
7126
|
+
return { ok: true, changedPaths };
|
|
7127
|
+
}
|
|
7128
|
+
function validateFinalReviewAgentOutputs(options) {
|
|
7129
|
+
const artifact = parseFinalReviewArtifact(options.artifactPath);
|
|
7130
|
+
if (!artifact.ok) {
|
|
7131
|
+
return { ...artifact, offendingPaths: [] };
|
|
7132
|
+
}
|
|
7133
|
+
const semanticResult = validateFinalReviewArtifactSemanticIds(
|
|
7134
|
+
artifact,
|
|
7135
|
+
options.context
|
|
7136
|
+
);
|
|
7137
|
+
if (!semanticResult.ok) {
|
|
7138
|
+
return {
|
|
7139
|
+
ok: false,
|
|
7140
|
+
reason: "Final Review artifact has mismatched semantic IDs.",
|
|
7141
|
+
diagnostics: semanticResult.errors,
|
|
7142
|
+
offendingPaths: []
|
|
7143
|
+
};
|
|
7144
|
+
}
|
|
7145
|
+
if (artifact.verdict !== "pass_with_retouch") {
|
|
7146
|
+
return { ok: true, artifact };
|
|
7147
|
+
}
|
|
7148
|
+
const changedPaths = options.changedPaths && options.changedPaths.length > 0 ? options.changedPaths : artifact.changedPaths;
|
|
7149
|
+
const retouch = validateFinalReviewRetouchScope(changedPaths ?? []);
|
|
7150
|
+
if (!retouch.ok) {
|
|
7151
|
+
return retouch;
|
|
7152
|
+
}
|
|
7153
|
+
return { ok: true, artifact, retouch };
|
|
7154
|
+
}
|
|
7155
|
+
function isRetouchScopePath(path9) {
|
|
7156
|
+
if (path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/adr/") || /^\.pourkit\/prd-runs\/[^/]+\.json$/.test(path9)) {
|
|
7157
|
+
return false;
|
|
7158
|
+
}
|
|
7159
|
+
return true;
|
|
7160
|
+
}
|
|
7161
|
+
|
|
6823
7162
|
// commands/prd-run.ts
|
|
6824
7163
|
init_common();
|
|
6825
7164
|
|
|
@@ -7338,19 +7677,20 @@ function canRetryFinalReviewBlock(record) {
|
|
|
7338
7677
|
}
|
|
7339
7678
|
function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
7340
7679
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
7341
|
-
return
|
|
7680
|
+
return existsSync12(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
|
|
7342
7681
|
}
|
|
7343
7682
|
function buildFinalReviewPrompt(options) {
|
|
7344
7683
|
return [
|
|
7345
7684
|
"# Active PRD Final Review Context",
|
|
7346
7685
|
"",
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7686
|
+
`Worktree checkout base: ${options.evidencePacket.prdBranch}`,
|
|
7687
|
+
`Review only this range: ${options.evidencePacket.mergeBase}..HEAD`,
|
|
7688
|
+
"Do not compare against current target branch HEAD; the merge base is the review baseline.",
|
|
7689
|
+
`Before handoff, run: npm run pourkit:validate-final-review -- ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
|
|
7690
|
+
"Fix any validation failures before handing off.",
|
|
7352
7691
|
"",
|
|
7353
|
-
"
|
|
7692
|
+
"Evidence Packet (do not infer PRD context from local state files):",
|
|
7693
|
+
JSON.stringify(options.evidencePacket, null, 2),
|
|
7354
7694
|
"",
|
|
7355
7695
|
loadFinalReviewPrompt(options.repoRoot, options.promptTemplate)
|
|
7356
7696
|
].join("\n");
|
|
@@ -7358,6 +7698,95 @@ function buildFinalReviewPrompt(options) {
|
|
|
7358
7698
|
function buildRetouchBranchName(prdRef) {
|
|
7359
7699
|
return `prd-run/${prdRef}-final-review-retouch`;
|
|
7360
7700
|
}
|
|
7701
|
+
function buildFinalReviewBranchName(prdRef) {
|
|
7702
|
+
return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
|
|
7703
|
+
}
|
|
7704
|
+
function finalReviewWorktreePath(repoRoot2, branchName) {
|
|
7705
|
+
return join15(
|
|
7706
|
+
repoRoot2,
|
|
7707
|
+
".sandcastle",
|
|
7708
|
+
"worktrees",
|
|
7709
|
+
branchName.replace(/\//g, "-")
|
|
7710
|
+
);
|
|
7711
|
+
}
|
|
7712
|
+
function collectSpawnDiagnostics(result, fallback) {
|
|
7713
|
+
const diagnostics = [];
|
|
7714
|
+
const stderr = result.stderr?.toString?.().trim();
|
|
7715
|
+
const stdout = result.stdout?.toString?.().trim();
|
|
7716
|
+
if (stderr) diagnostics.push(stderr);
|
|
7717
|
+
if (stdout) diagnostics.push(stdout);
|
|
7718
|
+
return diagnostics.length > 0 ? diagnostics : [fallback];
|
|
7719
|
+
}
|
|
7720
|
+
function ensureFinalReviewWorktree(options) {
|
|
7721
|
+
const worktreePath = finalReviewWorktreePath(
|
|
7722
|
+
options.repoRoot,
|
|
7723
|
+
options.branchName
|
|
7724
|
+
);
|
|
7725
|
+
const listResult = spawnSync2("git", ["worktree", "list", "--porcelain"], {
|
|
7726
|
+
cwd: options.repoRoot,
|
|
7727
|
+
encoding: "utf8"
|
|
7728
|
+
});
|
|
7729
|
+
if (listResult.status !== 0) {
|
|
7730
|
+
return {
|
|
7731
|
+
ok: false,
|
|
7732
|
+
reason: "Final Review failed to list git worktrees.",
|
|
7733
|
+
diagnostics: collectSpawnDiagnostics(
|
|
7734
|
+
listResult,
|
|
7735
|
+
"git worktree list failed"
|
|
7736
|
+
)
|
|
7737
|
+
};
|
|
7738
|
+
}
|
|
7739
|
+
const registeredPath = parseWorktreeListPorcelain(
|
|
7740
|
+
listResult.stdout?.toString?.() ?? "",
|
|
7741
|
+
options.branchName
|
|
7742
|
+
);
|
|
7743
|
+
if (registeredPath) {
|
|
7744
|
+
if (registeredPath !== worktreePath) {
|
|
7745
|
+
return {
|
|
7746
|
+
ok: false,
|
|
7747
|
+
reason: `Final Review branch ${options.branchName} already has a registered worktree outside .sandcastle/worktrees.`,
|
|
7748
|
+
diagnostics: [`registered worktree: ${registeredPath}`]
|
|
7749
|
+
};
|
|
7750
|
+
}
|
|
7751
|
+
return { ok: true, worktreePath: registeredPath };
|
|
7752
|
+
}
|
|
7753
|
+
if (existsSync12(worktreePath)) {
|
|
7754
|
+
return {
|
|
7755
|
+
ok: false,
|
|
7756
|
+
reason: "Final Review worktree path exists but is not registered with git.",
|
|
7757
|
+
diagnostics: [`stale worktree path: ${worktreePath}`]
|
|
7758
|
+
};
|
|
7759
|
+
}
|
|
7760
|
+
mkdirSync8(dirname7(worktreePath), { recursive: true });
|
|
7761
|
+
const branchResult = spawnSync2(
|
|
7762
|
+
"git",
|
|
7763
|
+
["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
|
|
7764
|
+
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
7765
|
+
);
|
|
7766
|
+
if (branchResult.status !== 0) {
|
|
7767
|
+
return {
|
|
7768
|
+
ok: false,
|
|
7769
|
+
reason: `Final Review failed to prepare branch ${options.branchName}.`,
|
|
7770
|
+
diagnostics: collectSpawnDiagnostics(branchResult, "git branch failed")
|
|
7771
|
+
};
|
|
7772
|
+
}
|
|
7773
|
+
const addResult = spawnSync2(
|
|
7774
|
+
"git",
|
|
7775
|
+
["worktree", "add", worktreePath, options.branchName],
|
|
7776
|
+
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
7777
|
+
);
|
|
7778
|
+
if (addResult.status !== 0) {
|
|
7779
|
+
return {
|
|
7780
|
+
ok: false,
|
|
7781
|
+
reason: `Final Review failed to create worktree for ${options.branchName}.`,
|
|
7782
|
+
diagnostics: collectSpawnDiagnostics(
|
|
7783
|
+
addResult,
|
|
7784
|
+
"git worktree add failed"
|
|
7785
|
+
)
|
|
7786
|
+
};
|
|
7787
|
+
}
|
|
7788
|
+
return { ok: true, worktreePath };
|
|
7789
|
+
}
|
|
7361
7790
|
function buildRetouchPrTitle(prdRef) {
|
|
7362
7791
|
return `chore: ${prdRef} Final Review retouch`;
|
|
7363
7792
|
}
|
|
@@ -7383,7 +7812,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
7383
7812
|
if (existingPr && existingPr.state === "OPEN") {
|
|
7384
7813
|
return existingPr;
|
|
7385
7814
|
}
|
|
7386
|
-
const worktreePath =
|
|
7815
|
+
const worktreePath = mkdtempSync2(join15(tmpdir2(), "pourkit-retouch-"));
|
|
7387
7816
|
try {
|
|
7388
7817
|
runGitOrThrow(
|
|
7389
7818
|
options.repoRoot,
|
|
@@ -7653,53 +8082,245 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
7653
8082
|
};
|
|
7654
8083
|
}
|
|
7655
8084
|
const prdBranch = record.prdBranch ?? prdRef;
|
|
7656
|
-
const
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
options.repoRoot,
|
|
7660
|
-
["worktree", "add", "--detach", worktreePath, `origin/${prdBranch}`],
|
|
7661
|
-
"create final review worktree from PRD branch"
|
|
7662
|
-
);
|
|
7663
|
-
const startReceipt = {
|
|
7664
|
-
status: "started",
|
|
7665
|
-
targetName,
|
|
7666
|
-
prdBranch,
|
|
7667
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
7668
|
-
diagnostics: [],
|
|
7669
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7670
|
-
};
|
|
8085
|
+
const manifestResult = readPlanningArtifactManifest(options.repoRoot, prdRef);
|
|
8086
|
+
if (!manifestResult.ok) {
|
|
8087
|
+
const reason = `Evidence Packet construction failed: ${manifestResult.reason}`;
|
|
7671
8088
|
writePrdRunRecord(options.repoRoot, {
|
|
7672
8089
|
...record,
|
|
7673
|
-
status: "
|
|
8090
|
+
status: "blocked",
|
|
7674
8091
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8092
|
+
blockedGate: "final-review",
|
|
8093
|
+
blockedReason: reason,
|
|
8094
|
+
diagnostics: manifestResult.diagnostics,
|
|
7675
8095
|
targetName,
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
});
|
|
7680
|
-
const executionResult = await options.executionProvider.execute({
|
|
7681
|
-
stage: "prdFinalReview",
|
|
7682
|
-
agent: finalReviewConfig.agent,
|
|
7683
|
-
model: finalReviewConfig.model,
|
|
7684
|
-
prompt: buildFinalReviewPrompt({
|
|
7685
|
-
repoRoot: options.repoRoot,
|
|
7686
|
-
promptTemplate: finalReviewConfig.promptTemplate,
|
|
7687
|
-
prdRef,
|
|
8096
|
+
finalReview: {
|
|
8097
|
+
status: "blocked",
|
|
8098
|
+
targetName,
|
|
7688
8099
|
prdBranch,
|
|
7689
8100
|
mergeBase: mergeBaseResult.mergeBase,
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
8101
|
+
diagnostics: manifestResult.diagnostics,
|
|
8102
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8103
|
+
},
|
|
8104
|
+
offendingPaths: manifestResult.offendingPaths
|
|
8105
|
+
});
|
|
8106
|
+
return {
|
|
8107
|
+
prdRef,
|
|
8108
|
+
status: "blocked",
|
|
8109
|
+
blockedGate: "final-review",
|
|
8110
|
+
blockedReason: reason,
|
|
8111
|
+
diagnostics: manifestResult.diagnostics,
|
|
8112
|
+
offendingPaths: manifestResult.offendingPaths
|
|
8113
|
+
};
|
|
8114
|
+
}
|
|
8115
|
+
const evidencePacket = buildEvidencePacket({
|
|
8116
|
+
prdRef,
|
|
8117
|
+
prdBranch,
|
|
8118
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8119
|
+
planningManifestPath: manifestResult.manifest.manifestPath,
|
|
8120
|
+
planningManifestFacts: {
|
|
8121
|
+
parentPrdIssueUrl: manifestResult.manifest.parentIssue.url,
|
|
8122
|
+
childIssueCount: manifestResult.manifest.childIssues.length
|
|
8123
|
+
},
|
|
8124
|
+
stage: "prdFinalReview",
|
|
8125
|
+
stageReceipts: { mergeBase: mergeBaseResult.mergeBase }
|
|
8126
|
+
});
|
|
8127
|
+
const startReceipt = {
|
|
8128
|
+
status: "started",
|
|
8129
|
+
targetName,
|
|
8130
|
+
prdBranch,
|
|
8131
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8132
|
+
diagnostics: [],
|
|
8133
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8134
|
+
};
|
|
8135
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8136
|
+
...record,
|
|
8137
|
+
status: "running",
|
|
8138
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8139
|
+
targetName,
|
|
8140
|
+
start: record.start,
|
|
8141
|
+
planning: record.planning,
|
|
8142
|
+
finalReview: startReceipt
|
|
8143
|
+
});
|
|
8144
|
+
const finalReviewBranchName = buildFinalReviewBranchName(prdBranch);
|
|
8145
|
+
const worktreeResult = ensureFinalReviewWorktree({
|
|
8146
|
+
repoRoot: options.repoRoot,
|
|
8147
|
+
branchName: finalReviewBranchName,
|
|
8148
|
+
checkoutBase: prdBranch
|
|
8149
|
+
});
|
|
8150
|
+
if (!worktreeResult.ok) {
|
|
8151
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8152
|
+
...record,
|
|
8153
|
+
status: "blocked",
|
|
8154
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8155
|
+
blockedGate: "final-review",
|
|
8156
|
+
blockedReason: worktreeResult.reason,
|
|
8157
|
+
diagnostics: worktreeResult.diagnostics,
|
|
8158
|
+
targetName,
|
|
8159
|
+
start: record.start,
|
|
8160
|
+
planning: record.planning,
|
|
8161
|
+
finalReview: {
|
|
8162
|
+
...startReceipt,
|
|
8163
|
+
status: "blocked",
|
|
8164
|
+
diagnostics: worktreeResult.diagnostics
|
|
8165
|
+
}
|
|
8166
|
+
});
|
|
8167
|
+
return {
|
|
8168
|
+
prdRef,
|
|
8169
|
+
status: "blocked",
|
|
8170
|
+
blockedGate: "final-review",
|
|
8171
|
+
blockedReason: worktreeResult.reason,
|
|
8172
|
+
diagnostics: worktreeResult.diagnostics,
|
|
8173
|
+
offendingPaths: []
|
|
8174
|
+
};
|
|
8175
|
+
}
|
|
8176
|
+
const executionResult = await options.executionProvider.execute({
|
|
8177
|
+
stage: "prdFinalReview",
|
|
8178
|
+
agent: finalReviewConfig.agent,
|
|
8179
|
+
model: finalReviewConfig.model,
|
|
8180
|
+
prompt: buildFinalReviewPrompt({
|
|
7693
8181
|
repoRoot: options.repoRoot,
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
8182
|
+
promptTemplate: finalReviewConfig.promptTemplate,
|
|
8183
|
+
evidencePacket
|
|
8184
|
+
}),
|
|
8185
|
+
target: targetConfig,
|
|
8186
|
+
repoRoot: options.repoRoot,
|
|
8187
|
+
branchName: finalReviewBranchName,
|
|
8188
|
+
baseRef: prdBranch,
|
|
8189
|
+
checkoutBase: prdBranch,
|
|
8190
|
+
reviewBase: mergeBaseResult.mergeBase,
|
|
8191
|
+
worktreePath: worktreeResult.worktreePath,
|
|
8192
|
+
sandbox: options.config.sandbox,
|
|
8193
|
+
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8194
|
+
logger: options.logger
|
|
8195
|
+
});
|
|
8196
|
+
if (!executionResult.success) {
|
|
8197
|
+
const reason = executionResult.error ?? "Final Review execution failed.";
|
|
8198
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8199
|
+
...record,
|
|
8200
|
+
status: "blocked",
|
|
8201
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8202
|
+
blockedGate: "final-review",
|
|
8203
|
+
blockedReason: reason,
|
|
8204
|
+
diagnostics: [reason],
|
|
8205
|
+
targetName,
|
|
8206
|
+
finalReview: {
|
|
8207
|
+
status: "blocked",
|
|
8208
|
+
targetName,
|
|
8209
|
+
prdBranch,
|
|
8210
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8211
|
+
diagnostics: [reason],
|
|
8212
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8213
|
+
},
|
|
8214
|
+
offendingPaths: []
|
|
8215
|
+
});
|
|
8216
|
+
return {
|
|
8217
|
+
prdRef,
|
|
8218
|
+
status: "blocked",
|
|
8219
|
+
blockedGate: "final-review",
|
|
8220
|
+
blockedReason: reason,
|
|
8221
|
+
diagnostics: [reason],
|
|
8222
|
+
offendingPaths: []
|
|
8223
|
+
};
|
|
8224
|
+
}
|
|
8225
|
+
const resolvedWorktreePath = executionResult.worktreePath;
|
|
8226
|
+
const artifactPath = join15(
|
|
8227
|
+
resolvedWorktreePath,
|
|
8228
|
+
".pourkit/final-review-artifact.json"
|
|
8229
|
+
);
|
|
8230
|
+
const artifactResult = parseFinalReviewArtifact(artifactPath);
|
|
8231
|
+
if (!artifactResult.ok) {
|
|
8232
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8233
|
+
...record,
|
|
8234
|
+
status: "blocked",
|
|
8235
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8236
|
+
blockedGate: "final-review",
|
|
8237
|
+
blockedReason: artifactResult.reason,
|
|
8238
|
+
diagnostics: artifactResult.diagnostics,
|
|
8239
|
+
targetName,
|
|
8240
|
+
finalReview: {
|
|
8241
|
+
status: "blocked",
|
|
8242
|
+
targetName,
|
|
8243
|
+
prdBranch,
|
|
8244
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8245
|
+
diagnostics: artifactResult.diagnostics,
|
|
8246
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8247
|
+
},
|
|
8248
|
+
offendingPaths: []
|
|
8249
|
+
});
|
|
8250
|
+
return {
|
|
8251
|
+
prdRef,
|
|
8252
|
+
status: "blocked",
|
|
8253
|
+
blockedGate: "final-review",
|
|
8254
|
+
blockedReason: artifactResult.reason,
|
|
8255
|
+
diagnostics: artifactResult.diagnostics,
|
|
8256
|
+
offendingPaths: []
|
|
8257
|
+
};
|
|
8258
|
+
}
|
|
8259
|
+
const semanticResult = validateFinalReviewArtifactSemanticIds(
|
|
8260
|
+
artifactResult,
|
|
8261
|
+
{ prdRef, prdBranch, mergeBase: mergeBaseResult.mergeBase }
|
|
8262
|
+
);
|
|
8263
|
+
if (!semanticResult.ok) {
|
|
8264
|
+
const diagnostics = semanticResult.errors.map(redactSensitiveValues);
|
|
8265
|
+
const reason = "Final Review artifact has mismatched semantic IDs.";
|
|
8266
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8267
|
+
...record,
|
|
8268
|
+
status: "blocked",
|
|
8269
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8270
|
+
blockedGate: "final-review",
|
|
8271
|
+
blockedReason: reason,
|
|
8272
|
+
diagnostics,
|
|
8273
|
+
targetName,
|
|
8274
|
+
finalReview: {
|
|
8275
|
+
status: "blocked",
|
|
8276
|
+
targetName,
|
|
8277
|
+
prdBranch,
|
|
8278
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8279
|
+
diagnostics,
|
|
8280
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8281
|
+
},
|
|
8282
|
+
offendingPaths: []
|
|
8283
|
+
});
|
|
8284
|
+
return {
|
|
8285
|
+
prdRef,
|
|
8286
|
+
status: "blocked",
|
|
8287
|
+
blockedGate: "final-review",
|
|
8288
|
+
blockedReason: reason,
|
|
8289
|
+
diagnostics,
|
|
8290
|
+
offendingPaths: []
|
|
8291
|
+
};
|
|
8292
|
+
}
|
|
8293
|
+
const { verdict, summary, diagnostics: artifactDiagnostics } = artifactResult;
|
|
8294
|
+
if (verdict === "pass_no_changes") {
|
|
8295
|
+
const receipt = {
|
|
8296
|
+
status: "succeeded",
|
|
8297
|
+
targetName,
|
|
8298
|
+
prdBranch,
|
|
8299
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8300
|
+
verdict: "pass_no_changes",
|
|
7698
8301
|
artifactPath: ".pourkit/final-review-artifact.json",
|
|
7699
|
-
|
|
8302
|
+
diagnostics: [],
|
|
8303
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8304
|
+
};
|
|
8305
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8306
|
+
...record,
|
|
8307
|
+
status: "final_reviewed",
|
|
8308
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8309
|
+
targetName,
|
|
8310
|
+
start: record.start,
|
|
8311
|
+
planning: record.planning,
|
|
8312
|
+
finalReview: receipt
|
|
7700
8313
|
});
|
|
7701
|
-
|
|
7702
|
-
|
|
8314
|
+
return {
|
|
8315
|
+
prdRef,
|
|
8316
|
+
status: "final_reviewed",
|
|
8317
|
+
finalReview: receipt,
|
|
8318
|
+
diagnostics: []
|
|
8319
|
+
};
|
|
8320
|
+
}
|
|
8321
|
+
if (verdict === "pass_with_retouch") {
|
|
8322
|
+
if (!options.prProvider) {
|
|
8323
|
+
const reason = "Missing PRProvider. Cannot create retouch PR without a PR provider.";
|
|
7703
8324
|
writePrdRunRecord(options.repoRoot, {
|
|
7704
8325
|
...record,
|
|
7705
8326
|
status: "blocked",
|
|
@@ -7713,6 +8334,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
7713
8334
|
targetName,
|
|
7714
8335
|
prdBranch,
|
|
7715
8336
|
mergeBase: mergeBaseResult.mergeBase,
|
|
8337
|
+
verdict: "pass_with_retouch",
|
|
7716
8338
|
diagnostics: [reason],
|
|
7717
8339
|
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7718
8340
|
},
|
|
@@ -7727,26 +8349,118 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
7727
8349
|
offendingPaths: []
|
|
7728
8350
|
};
|
|
7729
8351
|
}
|
|
7730
|
-
const
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
8352
|
+
const autoMerge = options.autoMerge ?? true;
|
|
8353
|
+
const existingRetouchPrNumber = record.finalReview?.retouchPrNumber;
|
|
8354
|
+
if (existingRetouchPrNumber && autoMerge) {
|
|
8355
|
+
const existingPr = await getPrByNumberIfAvailable(
|
|
8356
|
+
options.prProvider,
|
|
8357
|
+
existingRetouchPrNumber
|
|
8358
|
+
);
|
|
8359
|
+
if (existingPr?.state === "MERGED" && existingPr.mergeCommitSha) {
|
|
8360
|
+
const receipt2 = {
|
|
8361
|
+
status: "final_reviewed",
|
|
8362
|
+
targetName,
|
|
8363
|
+
prdBranch,
|
|
8364
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8365
|
+
verdict: "pass_with_retouch",
|
|
8366
|
+
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8367
|
+
diagnostics: [],
|
|
8368
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8369
|
+
retouchPrNumber: existingRetouchPrNumber,
|
|
8370
|
+
retouchPrUrl: existingPr.url,
|
|
8371
|
+
retouchMergeCommit: existingPr.mergeCommitSha,
|
|
8372
|
+
autoMerge,
|
|
8373
|
+
changedPaths: record.finalReview?.changedPaths ?? []
|
|
8374
|
+
};
|
|
8375
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8376
|
+
...record,
|
|
8377
|
+
status: "final_reviewed",
|
|
8378
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8379
|
+
targetName,
|
|
8380
|
+
start: record.start,
|
|
8381
|
+
planning: record.planning,
|
|
8382
|
+
finalReview: receipt2
|
|
8383
|
+
});
|
|
8384
|
+
return {
|
|
8385
|
+
prdRef,
|
|
8386
|
+
status: "final_reviewed",
|
|
8387
|
+
finalReview: receipt2,
|
|
8388
|
+
diagnostics: []
|
|
8389
|
+
};
|
|
8390
|
+
}
|
|
8391
|
+
}
|
|
8392
|
+
const changedPathsFromArtifact = artifactResult.changedPaths;
|
|
8393
|
+
let resolvedChangedPaths;
|
|
8394
|
+
if (changedPathsFromArtifact && changedPathsFromArtifact.length > 0) {
|
|
8395
|
+
resolvedChangedPaths = changedPathsFromArtifact;
|
|
8396
|
+
} else {
|
|
8397
|
+
const diffResult = spawnSync2(
|
|
8398
|
+
"git",
|
|
8399
|
+
["diff", "--name-only", mergeBaseResult.mergeBase, "HEAD", "--", "."],
|
|
8400
|
+
{
|
|
8401
|
+
cwd: resolvedWorktreePath,
|
|
8402
|
+
encoding: "utf8"
|
|
8403
|
+
}
|
|
8404
|
+
);
|
|
8405
|
+
resolvedChangedPaths = diffResult.stdout.split(/\r?\n/).filter(Boolean);
|
|
8406
|
+
}
|
|
8407
|
+
const scopeResult = validateFinalReviewRetouchScope(resolvedChangedPaths);
|
|
8408
|
+
if (!scopeResult.ok) {
|
|
7736
8409
|
writePrdRunRecord(options.repoRoot, {
|
|
7737
8410
|
...record,
|
|
7738
8411
|
status: "blocked",
|
|
7739
8412
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7740
8413
|
blockedGate: "final-review",
|
|
7741
|
-
blockedReason:
|
|
7742
|
-
diagnostics:
|
|
8414
|
+
blockedReason: scopeResult.reason,
|
|
8415
|
+
diagnostics: scopeResult.diagnostics,
|
|
8416
|
+
targetName,
|
|
8417
|
+
finalReview: {
|
|
8418
|
+
status: "blocked",
|
|
8419
|
+
targetName,
|
|
8420
|
+
prdBranch,
|
|
8421
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8422
|
+
verdict: "pass_with_retouch",
|
|
8423
|
+
diagnostics: scopeResult.diagnostics,
|
|
8424
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8425
|
+
},
|
|
8426
|
+
offendingPaths: scopeResult.offendingPaths
|
|
8427
|
+
});
|
|
8428
|
+
return {
|
|
8429
|
+
prdRef,
|
|
8430
|
+
status: "blocked",
|
|
8431
|
+
blockedGate: "final-review",
|
|
8432
|
+
blockedReason: scopeResult.reason,
|
|
8433
|
+
diagnostics: scopeResult.diagnostics,
|
|
8434
|
+
offendingPaths: scopeResult.offendingPaths
|
|
8435
|
+
};
|
|
8436
|
+
}
|
|
8437
|
+
let retouchPr;
|
|
8438
|
+
try {
|
|
8439
|
+
retouchPr = await createOrReuseFinalReviewRetouchPr({
|
|
8440
|
+
repoRoot: options.repoRoot,
|
|
8441
|
+
sourceWorktreePath: resolvedWorktreePath,
|
|
8442
|
+
prdRef,
|
|
8443
|
+
changedPaths: scopeResult.changedPaths,
|
|
8444
|
+
summary,
|
|
8445
|
+
prProvider: options.prProvider
|
|
8446
|
+
});
|
|
8447
|
+
} catch (error) {
|
|
8448
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8449
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8450
|
+
...record,
|
|
8451
|
+
status: "blocked",
|
|
8452
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8453
|
+
blockedGate: "final-review",
|
|
8454
|
+
blockedReason: `Failed to create retouch PR: ${msg}`,
|
|
8455
|
+
diagnostics: [msg],
|
|
7743
8456
|
targetName,
|
|
7744
8457
|
finalReview: {
|
|
7745
8458
|
status: "blocked",
|
|
7746
8459
|
targetName,
|
|
7747
8460
|
prdBranch,
|
|
7748
8461
|
mergeBase: mergeBaseResult.mergeBase,
|
|
7749
|
-
|
|
8462
|
+
verdict: "pass_with_retouch",
|
|
8463
|
+
diagnostics: [msg],
|
|
7750
8464
|
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7751
8465
|
},
|
|
7752
8466
|
offendingPaths: []
|
|
@@ -7755,501 +8469,555 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
7755
8469
|
prdRef,
|
|
7756
8470
|
status: "blocked",
|
|
7757
8471
|
blockedGate: "final-review",
|
|
7758
|
-
blockedReason:
|
|
7759
|
-
diagnostics:
|
|
8472
|
+
blockedReason: `Failed to create retouch PR: ${msg}`,
|
|
8473
|
+
diagnostics: [msg],
|
|
7760
8474
|
offendingPaths: []
|
|
7761
8475
|
};
|
|
7762
8476
|
}
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
diagnostics: artifactDiagnostics
|
|
7767
|
-
} = artifactResult;
|
|
7768
|
-
if (verdict === "pass_no_changes") {
|
|
7769
|
-
const receipt = {
|
|
7770
|
-
status: "succeeded",
|
|
8477
|
+
if (!autoMerge) {
|
|
8478
|
+
const receipt2 = {
|
|
8479
|
+
status: "needs_human_review",
|
|
7771
8480
|
targetName,
|
|
7772
8481
|
prdBranch,
|
|
7773
8482
|
mergeBase: mergeBaseResult.mergeBase,
|
|
7774
|
-
verdict: "
|
|
8483
|
+
verdict: "pass_with_retouch",
|
|
7775
8484
|
artifactPath: ".pourkit/final-review-artifact.json",
|
|
7776
8485
|
diagnostics: [],
|
|
7777
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8486
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8487
|
+
retouchPrNumber: retouchPr.number,
|
|
8488
|
+
retouchPrUrl: retouchPr.url,
|
|
8489
|
+
autoMerge: false,
|
|
8490
|
+
changedPaths: scopeResult.changedPaths
|
|
7778
8491
|
};
|
|
7779
8492
|
writePrdRunRecord(options.repoRoot, {
|
|
7780
8493
|
...record,
|
|
7781
|
-
status: "
|
|
8494
|
+
status: "blocked",
|
|
7782
8495
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8496
|
+
blockedGate: "final-review",
|
|
8497
|
+
blockedReason: `Retouch PR #${retouchPr.number} created but auto-merge disabled. PRD Final Review will not advance until the PR is merged.`,
|
|
8498
|
+
diagnostics: [
|
|
8499
|
+
`Retouch PR #${retouchPr.number}: ${retouchPr.url}`,
|
|
8500
|
+
"Auto-merge disabled. Merge the PR manually or rerun with auto-merge enabled."
|
|
8501
|
+
],
|
|
7783
8502
|
targetName,
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
finalReview: receipt
|
|
8503
|
+
finalReview: receipt2,
|
|
8504
|
+
offendingPaths: []
|
|
7787
8505
|
});
|
|
7788
8506
|
return {
|
|
7789
8507
|
prdRef,
|
|
7790
|
-
status: "
|
|
7791
|
-
finalReview:
|
|
7792
|
-
diagnostics: [
|
|
8508
|
+
status: "needs_human_review",
|
|
8509
|
+
finalReview: receipt2,
|
|
8510
|
+
diagnostics: [
|
|
8511
|
+
`Retouch PR #${retouchPr.number}: ${retouchPr.url}`,
|
|
8512
|
+
"Auto-merge disabled. Merge the PR manually or rerun with auto-merge enabled."
|
|
8513
|
+
]
|
|
7793
8514
|
};
|
|
7794
8515
|
}
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7802
|
-
blockedGate: "final-review",
|
|
7803
|
-
blockedReason: reason,
|
|
7804
|
-
diagnostics: [reason],
|
|
7805
|
-
targetName,
|
|
7806
|
-
finalReview: {
|
|
7807
|
-
status: "blocked",
|
|
7808
|
-
targetName,
|
|
7809
|
-
prdBranch,
|
|
7810
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
7811
|
-
verdict: "pass_with_retouch",
|
|
7812
|
-
diagnostics: [reason],
|
|
7813
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7814
|
-
},
|
|
7815
|
-
offendingPaths: []
|
|
7816
|
-
});
|
|
7817
|
-
return {
|
|
7818
|
-
prdRef,
|
|
7819
|
-
status: "blocked",
|
|
7820
|
-
blockedGate: "final-review",
|
|
7821
|
-
blockedReason: reason,
|
|
7822
|
-
diagnostics: [reason],
|
|
7823
|
-
offendingPaths: []
|
|
7824
|
-
};
|
|
7825
|
-
}
|
|
7826
|
-
const autoMerge = options.autoMerge ?? true;
|
|
7827
|
-
const existingRetouchPrNumber = record.finalReview?.retouchPrNumber;
|
|
7828
|
-
if (existingRetouchPrNumber && autoMerge) {
|
|
7829
|
-
const existingPr = await getPrByNumberIfAvailable(
|
|
7830
|
-
options.prProvider,
|
|
7831
|
-
existingRetouchPrNumber
|
|
7832
|
-
);
|
|
7833
|
-
if (existingPr?.state === "MERGED" && existingPr.mergeCommitSha) {
|
|
7834
|
-
const receipt2 = {
|
|
7835
|
-
status: "final_reviewed",
|
|
7836
|
-
targetName,
|
|
7837
|
-
prdBranch,
|
|
7838
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
7839
|
-
verdict: "pass_with_retouch",
|
|
7840
|
-
artifactPath: ".pourkit/final-review-artifact.json",
|
|
7841
|
-
diagnostics: [],
|
|
7842
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7843
|
-
retouchPrNumber: existingRetouchPrNumber,
|
|
7844
|
-
retouchPrUrl: existingPr.url,
|
|
7845
|
-
retouchMergeCommit: existingPr.mergeCommitSha,
|
|
7846
|
-
autoMerge,
|
|
7847
|
-
changedPaths: record.finalReview?.changedPaths ?? []
|
|
7848
|
-
};
|
|
7849
|
-
writePrdRunRecord(options.repoRoot, {
|
|
7850
|
-
...record,
|
|
7851
|
-
status: "final_reviewed",
|
|
7852
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7853
|
-
targetName,
|
|
7854
|
-
start: record.start,
|
|
7855
|
-
planning: record.planning,
|
|
7856
|
-
finalReview: receipt2
|
|
7857
|
-
});
|
|
7858
|
-
return {
|
|
7859
|
-
prdRef,
|
|
7860
|
-
status: "final_reviewed",
|
|
7861
|
-
finalReview: receipt2,
|
|
7862
|
-
diagnostics: []
|
|
7863
|
-
};
|
|
7864
|
-
}
|
|
7865
|
-
}
|
|
7866
|
-
const changedPathsFromArtifact = artifactResult.changedPaths;
|
|
7867
|
-
let resolvedChangedPaths;
|
|
7868
|
-
if (changedPathsFromArtifact && changedPathsFromArtifact.length > 0) {
|
|
7869
|
-
resolvedChangedPaths = changedPathsFromArtifact;
|
|
7870
|
-
} else {
|
|
7871
|
-
const diffResult = spawnSync2(
|
|
7872
|
-
"git",
|
|
7873
|
-
["diff", "--name-only", mergeBaseResult.mergeBase, "HEAD", "--", "."],
|
|
7874
|
-
{
|
|
7875
|
-
cwd: worktreePath,
|
|
7876
|
-
encoding: "utf8"
|
|
7877
|
-
}
|
|
7878
|
-
);
|
|
7879
|
-
resolvedChangedPaths = diffResult.stdout.split(/\r?\n/).filter(Boolean);
|
|
7880
|
-
}
|
|
7881
|
-
const scopeResult = validateFinalReviewRetouchScope(resolvedChangedPaths);
|
|
7882
|
-
if (!scopeResult.ok) {
|
|
7883
|
-
writePrdRunRecord(options.repoRoot, {
|
|
7884
|
-
...record,
|
|
7885
|
-
status: "blocked",
|
|
7886
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7887
|
-
blockedGate: "final-review",
|
|
7888
|
-
blockedReason: scopeResult.reason,
|
|
7889
|
-
diagnostics: scopeResult.diagnostics,
|
|
7890
|
-
targetName,
|
|
7891
|
-
finalReview: {
|
|
7892
|
-
status: "blocked",
|
|
7893
|
-
targetName,
|
|
7894
|
-
prdBranch,
|
|
7895
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
7896
|
-
verdict: "pass_with_retouch",
|
|
7897
|
-
diagnostics: scopeResult.diagnostics,
|
|
7898
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7899
|
-
},
|
|
7900
|
-
offendingPaths: scopeResult.offendingPaths
|
|
7901
|
-
});
|
|
7902
|
-
return {
|
|
7903
|
-
prdRef,
|
|
7904
|
-
status: "blocked",
|
|
7905
|
-
blockedGate: "final-review",
|
|
7906
|
-
blockedReason: scopeResult.reason,
|
|
7907
|
-
diagnostics: scopeResult.diagnostics,
|
|
7908
|
-
offendingPaths: scopeResult.offendingPaths
|
|
7909
|
-
};
|
|
7910
|
-
}
|
|
7911
|
-
let retouchPr;
|
|
7912
|
-
try {
|
|
7913
|
-
retouchPr = await createOrReuseFinalReviewRetouchPr({
|
|
7914
|
-
repoRoot: options.repoRoot,
|
|
7915
|
-
sourceWorktreePath: worktreePath,
|
|
7916
|
-
prdRef,
|
|
7917
|
-
changedPaths: scopeResult.changedPaths,
|
|
7918
|
-
summary,
|
|
7919
|
-
prProvider: options.prProvider
|
|
7920
|
-
});
|
|
7921
|
-
} catch (error) {
|
|
7922
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
7923
|
-
writePrdRunRecord(options.repoRoot, {
|
|
7924
|
-
...record,
|
|
7925
|
-
status: "blocked",
|
|
7926
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7927
|
-
blockedGate: "final-review",
|
|
7928
|
-
blockedReason: `Failed to create retouch PR: ${msg}`,
|
|
7929
|
-
diagnostics: [msg],
|
|
7930
|
-
targetName,
|
|
7931
|
-
finalReview: {
|
|
7932
|
-
status: "blocked",
|
|
7933
|
-
targetName,
|
|
7934
|
-
prdBranch,
|
|
7935
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
7936
|
-
verdict: "pass_with_retouch",
|
|
7937
|
-
diagnostics: [msg],
|
|
7938
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7939
|
-
},
|
|
7940
|
-
offendingPaths: []
|
|
7941
|
-
});
|
|
7942
|
-
return {
|
|
7943
|
-
prdRef,
|
|
7944
|
-
status: "blocked",
|
|
7945
|
-
blockedGate: "final-review",
|
|
7946
|
-
blockedReason: `Failed to create retouch PR: ${msg}`,
|
|
7947
|
-
diagnostics: [msg],
|
|
7948
|
-
offendingPaths: []
|
|
7949
|
-
};
|
|
7950
|
-
}
|
|
7951
|
-
if (!autoMerge) {
|
|
7952
|
-
const receipt2 = {
|
|
7953
|
-
status: "needs_human_review",
|
|
7954
|
-
targetName,
|
|
7955
|
-
prdBranch,
|
|
7956
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
7957
|
-
verdict: "pass_with_retouch",
|
|
7958
|
-
artifactPath: ".pourkit/final-review-artifact.json",
|
|
7959
|
-
diagnostics: [],
|
|
7960
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7961
|
-
retouchPrNumber: retouchPr.number,
|
|
7962
|
-
retouchPrUrl: retouchPr.url,
|
|
7963
|
-
autoMerge: false,
|
|
7964
|
-
changedPaths: scopeResult.changedPaths
|
|
7965
|
-
};
|
|
7966
|
-
writePrdRunRecord(options.repoRoot, {
|
|
7967
|
-
...record,
|
|
7968
|
-
status: "blocked",
|
|
7969
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7970
|
-
blockedGate: "final-review",
|
|
7971
|
-
blockedReason: `Retouch PR #${retouchPr.number} created but auto-merge disabled. PRD Final Review will not advance until the PR is merged.`,
|
|
7972
|
-
diagnostics: [
|
|
7973
|
-
`Retouch PR #${retouchPr.number}: ${retouchPr.url}`,
|
|
7974
|
-
"Auto-merge disabled. Merge the PR manually or rerun with auto-merge enabled."
|
|
7975
|
-
],
|
|
7976
|
-
targetName,
|
|
7977
|
-
finalReview: receipt2,
|
|
7978
|
-
offendingPaths: []
|
|
7979
|
-
});
|
|
7980
|
-
return {
|
|
7981
|
-
prdRef,
|
|
7982
|
-
status: "needs_human_review",
|
|
7983
|
-
finalReview: receipt2,
|
|
7984
|
-
diagnostics: [
|
|
7985
|
-
`Retouch PR #${retouchPr.number}: ${retouchPr.url}`,
|
|
7986
|
-
"Auto-merge disabled. Merge the PR manually or rerun with auto-merge enabled."
|
|
7987
|
-
]
|
|
7988
|
-
};
|
|
7989
|
-
}
|
|
7990
|
-
try {
|
|
7991
|
-
await options.prProvider.waitForPrChecks(retouchPr.number);
|
|
7992
|
-
} catch (error) {
|
|
7993
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
7994
|
-
const receipt2 = {
|
|
7995
|
-
status: "started",
|
|
7996
|
-
targetName,
|
|
7997
|
-
prdBranch,
|
|
7998
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
7999
|
-
verdict: "pass_with_retouch",
|
|
8000
|
-
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8001
|
-
diagnostics: [msg],
|
|
8002
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8003
|
-
retouchPrNumber: retouchPr.number,
|
|
8004
|
-
retouchPrUrl: retouchPr.url,
|
|
8005
|
-
autoMerge: true,
|
|
8006
|
-
changedPaths: scopeResult.changedPaths
|
|
8007
|
-
};
|
|
8008
|
-
writePrdRunRecord(options.repoRoot, {
|
|
8009
|
-
...record,
|
|
8010
|
-
status: "blocked",
|
|
8011
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8012
|
-
blockedGate: "final-review",
|
|
8013
|
-
blockedReason: `Retouch PR #${retouchPr.number} checks failed: ${msg}`,
|
|
8014
|
-
diagnostics: [msg],
|
|
8015
|
-
targetName,
|
|
8016
|
-
finalReview: receipt2,
|
|
8017
|
-
offendingPaths: []
|
|
8018
|
-
});
|
|
8019
|
-
return {
|
|
8020
|
-
prdRef,
|
|
8021
|
-
status: "blocked",
|
|
8022
|
-
blockedGate: "final-review",
|
|
8023
|
-
blockedReason: `Retouch PR #${retouchPr.number} checks failed: ${msg}`,
|
|
8024
|
-
diagnostics: [msg],
|
|
8025
|
-
offendingPaths: []
|
|
8026
|
-
};
|
|
8027
|
-
}
|
|
8028
|
-
try {
|
|
8029
|
-
await options.prProvider.mergePr(retouchPr.number, {
|
|
8030
|
-
method: "squash"
|
|
8031
|
-
});
|
|
8032
|
-
} catch (error) {
|
|
8033
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
8034
|
-
const receipt2 = {
|
|
8035
|
-
status: "started",
|
|
8036
|
-
targetName,
|
|
8037
|
-
prdBranch,
|
|
8038
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
8039
|
-
verdict: "pass_with_retouch",
|
|
8040
|
-
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8041
|
-
diagnostics: [msg],
|
|
8042
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8043
|
-
retouchPrNumber: retouchPr.number,
|
|
8044
|
-
retouchPrUrl: retouchPr.url,
|
|
8045
|
-
autoMerge: true,
|
|
8046
|
-
changedPaths: scopeResult.changedPaths
|
|
8047
|
-
};
|
|
8048
|
-
writePrdRunRecord(options.repoRoot, {
|
|
8049
|
-
...record,
|
|
8050
|
-
status: "blocked",
|
|
8051
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8052
|
-
blockedGate: "final-review",
|
|
8053
|
-
blockedReason: `Retouch PR #${retouchPr.number} merge failed: ${msg}`,
|
|
8054
|
-
diagnostics: [msg],
|
|
8055
|
-
targetName,
|
|
8056
|
-
finalReview: receipt2,
|
|
8057
|
-
offendingPaths: []
|
|
8058
|
-
});
|
|
8059
|
-
return {
|
|
8060
|
-
prdRef,
|
|
8061
|
-
status: "blocked",
|
|
8062
|
-
blockedGate: "final-review",
|
|
8063
|
-
blockedReason: `Retouch PR #${retouchPr.number} merge failed: ${msg}`,
|
|
8064
|
-
diagnostics: [msg],
|
|
8065
|
-
offendingPaths: []
|
|
8066
|
-
};
|
|
8067
|
-
}
|
|
8068
|
-
let mergedPr = null;
|
|
8069
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
8070
|
-
try {
|
|
8071
|
-
const candidate = await options.prProvider.getPr(
|
|
8072
|
-
retouchPr.headRefName
|
|
8073
|
-
);
|
|
8074
|
-
if (candidate?.state === "MERGED" && candidate.mergeCommitSha) {
|
|
8075
|
-
mergedPr = candidate;
|
|
8076
|
-
break;
|
|
8077
|
-
}
|
|
8078
|
-
} catch {
|
|
8079
|
-
}
|
|
8080
|
-
if (attempt < 2) {
|
|
8081
|
-
await sleep(1e3);
|
|
8082
|
-
}
|
|
8083
|
-
}
|
|
8084
|
-
const mergeCommitSha = mergedPr?.mergeCommitSha;
|
|
8085
|
-
if (!mergeCommitSha) {
|
|
8086
|
-
const receipt2 = {
|
|
8087
|
-
status: "started",
|
|
8088
|
-
targetName,
|
|
8089
|
-
prdBranch,
|
|
8090
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
8091
|
-
verdict: "pass_with_retouch",
|
|
8092
|
-
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8093
|
-
diagnostics: [
|
|
8094
|
-
`Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
|
|
8095
|
-
],
|
|
8096
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8097
|
-
retouchPrNumber: retouchPr.number,
|
|
8098
|
-
retouchPrUrl: retouchPr.url,
|
|
8099
|
-
autoMerge: true,
|
|
8100
|
-
changedPaths: scopeResult.changedPaths
|
|
8101
|
-
};
|
|
8102
|
-
writePrdRunRecord(options.repoRoot, {
|
|
8103
|
-
...record,
|
|
8104
|
-
status: "blocked",
|
|
8105
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8106
|
-
blockedGate: "final-review",
|
|
8107
|
-
blockedReason: `Retouch PR #${retouchPr.number} merged without exposing a merge commit SHA.`,
|
|
8108
|
-
diagnostics: [
|
|
8109
|
-
`Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
|
|
8110
|
-
],
|
|
8111
|
-
targetName,
|
|
8112
|
-
finalReview: receipt2,
|
|
8113
|
-
offendingPaths: []
|
|
8114
|
-
});
|
|
8115
|
-
return {
|
|
8116
|
-
prdRef,
|
|
8117
|
-
status: "blocked",
|
|
8118
|
-
blockedGate: "final-review",
|
|
8119
|
-
blockedReason: `Retouch PR #${retouchPr.number} merged without exposing a merge commit SHA.`,
|
|
8120
|
-
diagnostics: [
|
|
8121
|
-
`Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
|
|
8122
|
-
],
|
|
8123
|
-
offendingPaths: []
|
|
8124
|
-
};
|
|
8125
|
-
}
|
|
8126
|
-
const receipt = {
|
|
8127
|
-
status: "final_reviewed",
|
|
8516
|
+
try {
|
|
8517
|
+
await options.prProvider.waitForPrChecks(retouchPr.number);
|
|
8518
|
+
} catch (error) {
|
|
8519
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8520
|
+
const receipt2 = {
|
|
8521
|
+
status: "started",
|
|
8128
8522
|
targetName,
|
|
8129
8523
|
prdBranch,
|
|
8130
8524
|
mergeBase: mergeBaseResult.mergeBase,
|
|
8131
8525
|
verdict: "pass_with_retouch",
|
|
8132
8526
|
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8133
|
-
diagnostics: [],
|
|
8527
|
+
diagnostics: [msg],
|
|
8134
8528
|
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8135
8529
|
retouchPrNumber: retouchPr.number,
|
|
8136
8530
|
retouchPrUrl: retouchPr.url,
|
|
8137
|
-
retouchMergeCommit: mergeCommitSha,
|
|
8138
8531
|
autoMerge: true,
|
|
8139
8532
|
changedPaths: scopeResult.changedPaths
|
|
8140
8533
|
};
|
|
8141
8534
|
writePrdRunRecord(options.repoRoot, {
|
|
8142
8535
|
...record,
|
|
8143
|
-
status: "
|
|
8536
|
+
status: "blocked",
|
|
8144
8537
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8538
|
+
blockedGate: "final-review",
|
|
8539
|
+
blockedReason: `Retouch PR #${retouchPr.number} checks failed: ${msg}`,
|
|
8540
|
+
diagnostics: [msg],
|
|
8145
8541
|
targetName,
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
finalReview: receipt
|
|
8542
|
+
finalReview: receipt2,
|
|
8543
|
+
offendingPaths: []
|
|
8149
8544
|
});
|
|
8150
8545
|
return {
|
|
8151
8546
|
prdRef,
|
|
8152
|
-
status: "
|
|
8153
|
-
|
|
8154
|
-
|
|
8547
|
+
status: "blocked",
|
|
8548
|
+
blockedGate: "final-review",
|
|
8549
|
+
blockedReason: `Retouch PR #${retouchPr.number} checks failed: ${msg}`,
|
|
8550
|
+
diagnostics: [msg],
|
|
8551
|
+
offendingPaths: []
|
|
8155
8552
|
};
|
|
8156
8553
|
}
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8554
|
+
try {
|
|
8555
|
+
await options.prProvider.mergePr(retouchPr.number, {
|
|
8556
|
+
method: "squash"
|
|
8557
|
+
});
|
|
8558
|
+
} catch (error) {
|
|
8559
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8560
|
+
const receipt2 = {
|
|
8561
|
+
status: "started",
|
|
8161
8562
|
targetName,
|
|
8162
8563
|
prdBranch,
|
|
8163
8564
|
mergeBase: mergeBaseResult.mergeBase,
|
|
8164
|
-
verdict: "
|
|
8565
|
+
verdict: "pass_with_retouch",
|
|
8165
8566
|
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8166
|
-
diagnostics:
|
|
8167
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8567
|
+
diagnostics: [msg],
|
|
8568
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8569
|
+
retouchPrNumber: retouchPr.number,
|
|
8570
|
+
retouchPrUrl: retouchPr.url,
|
|
8571
|
+
autoMerge: true,
|
|
8572
|
+
changedPaths: scopeResult.changedPaths
|
|
8168
8573
|
};
|
|
8169
8574
|
writePrdRunRecord(options.repoRoot, {
|
|
8170
8575
|
...record,
|
|
8171
8576
|
status: "blocked",
|
|
8172
8577
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8173
8578
|
blockedGate: "final-review",
|
|
8174
|
-
blockedReason:
|
|
8175
|
-
diagnostics: [
|
|
8579
|
+
blockedReason: `Retouch PR #${retouchPr.number} merge failed: ${msg}`,
|
|
8580
|
+
diagnostics: [msg],
|
|
8176
8581
|
targetName,
|
|
8177
|
-
finalReview:
|
|
8582
|
+
finalReview: receipt2,
|
|
8178
8583
|
offendingPaths: []
|
|
8179
8584
|
});
|
|
8180
8585
|
return {
|
|
8181
8586
|
prdRef,
|
|
8182
|
-
status: "
|
|
8183
|
-
|
|
8184
|
-
|
|
8587
|
+
status: "blocked",
|
|
8588
|
+
blockedGate: "final-review",
|
|
8589
|
+
blockedReason: `Retouch PR #${retouchPr.number} merge failed: ${msg}`,
|
|
8590
|
+
diagnostics: [msg],
|
|
8591
|
+
offendingPaths: []
|
|
8185
8592
|
};
|
|
8186
8593
|
}
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8594
|
+
let mergedPr = null;
|
|
8595
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
8596
|
+
try {
|
|
8597
|
+
const candidate = await options.prProvider.getPr(
|
|
8598
|
+
retouchPr.headRefName
|
|
8599
|
+
);
|
|
8600
|
+
if (candidate?.state === "MERGED" && candidate.mergeCommitSha) {
|
|
8601
|
+
mergedPr = candidate;
|
|
8602
|
+
break;
|
|
8603
|
+
}
|
|
8604
|
+
} catch {
|
|
8605
|
+
}
|
|
8606
|
+
if (attempt < 2) {
|
|
8607
|
+
await sleep(1e3);
|
|
8608
|
+
}
|
|
8609
|
+
}
|
|
8610
|
+
const mergeCommitSha = mergedPr?.mergeCommitSha;
|
|
8611
|
+
if (!mergeCommitSha) {
|
|
8612
|
+
const receipt2 = {
|
|
8613
|
+
status: "started",
|
|
8191
8614
|
targetName,
|
|
8192
8615
|
prdBranch,
|
|
8193
8616
|
mergeBase: mergeBaseResult.mergeBase,
|
|
8194
|
-
verdict: "
|
|
8617
|
+
verdict: "pass_with_retouch",
|
|
8195
8618
|
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8196
|
-
diagnostics:
|
|
8197
|
-
|
|
8619
|
+
diagnostics: [
|
|
8620
|
+
`Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
|
|
8621
|
+
],
|
|
8622
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8623
|
+
retouchPrNumber: retouchPr.number,
|
|
8624
|
+
retouchPrUrl: retouchPr.url,
|
|
8625
|
+
autoMerge: true,
|
|
8626
|
+
changedPaths: scopeResult.changedPaths
|
|
8198
8627
|
};
|
|
8199
8628
|
writePrdRunRecord(options.repoRoot, {
|
|
8200
8629
|
...record,
|
|
8201
8630
|
status: "blocked",
|
|
8202
8631
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8203
8632
|
blockedGate: "final-review",
|
|
8204
|
-
blockedReason:
|
|
8205
|
-
diagnostics: [
|
|
8633
|
+
blockedReason: `Retouch PR #${retouchPr.number} merged without exposing a merge commit SHA.`,
|
|
8634
|
+
diagnostics: [
|
|
8635
|
+
`Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
|
|
8636
|
+
],
|
|
8206
8637
|
targetName,
|
|
8207
|
-
finalReview:
|
|
8638
|
+
finalReview: receipt2,
|
|
8208
8639
|
offendingPaths: []
|
|
8209
8640
|
});
|
|
8210
8641
|
return {
|
|
8211
8642
|
prdRef,
|
|
8212
8643
|
status: "blocked",
|
|
8213
8644
|
blockedGate: "final-review",
|
|
8214
|
-
blockedReason:
|
|
8215
|
-
diagnostics: [
|
|
8645
|
+
blockedReason: `Retouch PR #${retouchPr.number} merged without exposing a merge commit SHA.`,
|
|
8646
|
+
diagnostics: [
|
|
8647
|
+
`Retouch PR #${retouchPr.number} merged but merge commit SHA not available.`
|
|
8648
|
+
],
|
|
8216
8649
|
offendingPaths: []
|
|
8217
8650
|
};
|
|
8218
8651
|
}
|
|
8219
|
-
const
|
|
8652
|
+
const receipt = {
|
|
8653
|
+
status: "final_reviewed",
|
|
8654
|
+
targetName,
|
|
8655
|
+
prdBranch,
|
|
8656
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8657
|
+
verdict: "pass_with_retouch",
|
|
8658
|
+
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8659
|
+
diagnostics: [],
|
|
8660
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8661
|
+
retouchPrNumber: retouchPr.number,
|
|
8662
|
+
retouchPrUrl: retouchPr.url,
|
|
8663
|
+
retouchMergeCommit: mergeCommitSha,
|
|
8664
|
+
autoMerge: true,
|
|
8665
|
+
changedPaths: scopeResult.changedPaths
|
|
8666
|
+
};
|
|
8667
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8668
|
+
...record,
|
|
8669
|
+
status: "final_reviewed",
|
|
8670
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8671
|
+
targetName,
|
|
8672
|
+
start: record.start,
|
|
8673
|
+
planning: record.planning,
|
|
8674
|
+
finalReview: receipt
|
|
8675
|
+
});
|
|
8676
|
+
return {
|
|
8677
|
+
prdRef,
|
|
8678
|
+
status: "final_reviewed",
|
|
8679
|
+
finalReview: receipt,
|
|
8680
|
+
diagnostics: []
|
|
8681
|
+
};
|
|
8682
|
+
}
|
|
8683
|
+
if (verdict === "needs_human_review") {
|
|
8684
|
+
const reason = summary || "Final Review requires human review.";
|
|
8685
|
+
const receipt = {
|
|
8686
|
+
status: "needs_human_review",
|
|
8687
|
+
targetName,
|
|
8688
|
+
prdBranch,
|
|
8689
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8690
|
+
verdict: "needs_human_review",
|
|
8691
|
+
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8692
|
+
diagnostics: artifactDiagnostics.length > 0 ? artifactDiagnostics : [reason],
|
|
8693
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8694
|
+
};
|
|
8220
8695
|
writePrdRunRecord(options.repoRoot, {
|
|
8221
8696
|
...record,
|
|
8222
8697
|
status: "blocked",
|
|
8223
8698
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8224
8699
|
blockedGate: "final-review",
|
|
8225
|
-
blockedReason:
|
|
8226
|
-
diagnostics: [
|
|
8700
|
+
blockedReason: reason,
|
|
8701
|
+
diagnostics: [reason],
|
|
8227
8702
|
targetName,
|
|
8228
|
-
finalReview:
|
|
8229
|
-
status: "blocked",
|
|
8230
|
-
targetName,
|
|
8231
|
-
prdBranch,
|
|
8232
|
-
mergeBase: mergeBaseResult.mergeBase,
|
|
8233
|
-
diagnostics: [unsupportedReason],
|
|
8234
|
-
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8235
|
-
},
|
|
8703
|
+
finalReview: receipt,
|
|
8236
8704
|
offendingPaths: []
|
|
8237
8705
|
});
|
|
8238
8706
|
return {
|
|
8239
8707
|
prdRef,
|
|
8708
|
+
status: "needs_human_review",
|
|
8709
|
+
finalReview: receipt,
|
|
8710
|
+
diagnostics: [reason]
|
|
8711
|
+
};
|
|
8712
|
+
}
|
|
8713
|
+
if (verdict === "blocked") {
|
|
8714
|
+
const reason = summary || "Final Review blocked.";
|
|
8715
|
+
const receipt = {
|
|
8240
8716
|
status: "blocked",
|
|
8717
|
+
targetName,
|
|
8718
|
+
prdBranch,
|
|
8719
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8720
|
+
verdict: "blocked",
|
|
8721
|
+
artifactPath: ".pourkit/final-review-artifact.json",
|
|
8722
|
+
diagnostics: artifactDiagnostics.length > 0 ? artifactDiagnostics : [reason],
|
|
8723
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8724
|
+
};
|
|
8725
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8726
|
+
...record,
|
|
8727
|
+
status: "blocked",
|
|
8728
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8241
8729
|
blockedGate: "final-review",
|
|
8242
|
-
blockedReason:
|
|
8730
|
+
blockedReason: reason,
|
|
8731
|
+
diagnostics: [reason],
|
|
8732
|
+
targetName,
|
|
8733
|
+
finalReview: receipt,
|
|
8734
|
+
offendingPaths: []
|
|
8735
|
+
});
|
|
8736
|
+
return {
|
|
8737
|
+
prdRef,
|
|
8738
|
+
status: "blocked",
|
|
8739
|
+
blockedGate: "final-review",
|
|
8740
|
+
blockedReason: reason,
|
|
8741
|
+
diagnostics: [reason],
|
|
8742
|
+
offendingPaths: []
|
|
8743
|
+
};
|
|
8744
|
+
}
|
|
8745
|
+
const unsupportedReason = `Final Review returned unsupported verdict "${verdict}".`;
|
|
8746
|
+
writePrdRunRecord(options.repoRoot, {
|
|
8747
|
+
...record,
|
|
8748
|
+
status: "blocked",
|
|
8749
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8750
|
+
blockedGate: "final-review",
|
|
8751
|
+
blockedReason: unsupportedReason,
|
|
8752
|
+
diagnostics: [unsupportedReason],
|
|
8753
|
+
targetName,
|
|
8754
|
+
finalReview: {
|
|
8755
|
+
status: "blocked",
|
|
8756
|
+
targetName,
|
|
8757
|
+
prdBranch,
|
|
8758
|
+
mergeBase: mergeBaseResult.mergeBase,
|
|
8243
8759
|
diagnostics: [unsupportedReason],
|
|
8760
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8761
|
+
},
|
|
8762
|
+
offendingPaths: []
|
|
8763
|
+
});
|
|
8764
|
+
return {
|
|
8765
|
+
prdRef,
|
|
8766
|
+
status: "blocked",
|
|
8767
|
+
blockedGate: "final-review",
|
|
8768
|
+
blockedReason: unsupportedReason,
|
|
8769
|
+
diagnostics: [unsupportedReason],
|
|
8770
|
+
offendingPaths: []
|
|
8771
|
+
};
|
|
8772
|
+
}
|
|
8773
|
+
function runPrdRunValidateFinalReviewCommand(options) {
|
|
8774
|
+
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
8775
|
+
const checkoutBase = options.checkoutBase ?? prdRef;
|
|
8776
|
+
const artifactPath = options.artifactPath ? options.artifactPath : join15(options.repoRoot, ".pourkit", "final-review-artifact.json");
|
|
8777
|
+
const artifact = parseFinalReviewArtifact(artifactPath);
|
|
8778
|
+
if (!artifact.ok) {
|
|
8779
|
+
return {
|
|
8780
|
+
prdRef,
|
|
8781
|
+
status: "invalid",
|
|
8782
|
+
artifactPath,
|
|
8783
|
+
reason: artifact.reason,
|
|
8784
|
+
diagnostics: artifact.diagnostics,
|
|
8244
8785
|
offendingPaths: []
|
|
8245
8786
|
};
|
|
8246
|
-
}
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8787
|
+
}
|
|
8788
|
+
let changedPaths = options.changedPaths;
|
|
8789
|
+
if (artifact.verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && (!artifact.changedPaths || artifact.changedPaths.length === 0)) {
|
|
8790
|
+
const diffResult = spawnSync2(
|
|
8791
|
+
"git",
|
|
8792
|
+
["diff", "--name-only", options.reviewBase, "HEAD", "--", "."],
|
|
8793
|
+
{ cwd: options.repoRoot, encoding: "utf8" }
|
|
8794
|
+
);
|
|
8795
|
+
if (diffResult.status !== 0) {
|
|
8796
|
+
return {
|
|
8797
|
+
prdRef,
|
|
8798
|
+
status: "invalid",
|
|
8799
|
+
artifactPath,
|
|
8800
|
+
reason: "Final Review retouch path discovery failed.",
|
|
8801
|
+
diagnostics: collectSpawnDiagnostics(diffResult, "git diff failed"),
|
|
8802
|
+
offendingPaths: []
|
|
8803
|
+
};
|
|
8804
|
+
}
|
|
8805
|
+
changedPaths = diffResult.stdout.split(/\r?\n/).filter(Boolean);
|
|
8806
|
+
}
|
|
8807
|
+
const result = validateFinalReviewAgentOutputs({
|
|
8808
|
+
artifactPath,
|
|
8809
|
+
context: {
|
|
8810
|
+
prdRef,
|
|
8811
|
+
prdBranch: checkoutBase,
|
|
8812
|
+
mergeBase: options.reviewBase
|
|
8813
|
+
},
|
|
8814
|
+
changedPaths
|
|
8815
|
+
});
|
|
8816
|
+
if (!result.ok) {
|
|
8817
|
+
return {
|
|
8818
|
+
prdRef,
|
|
8819
|
+
status: "invalid",
|
|
8820
|
+
artifactPath,
|
|
8821
|
+
reason: result.reason,
|
|
8822
|
+
diagnostics: result.diagnostics,
|
|
8823
|
+
offendingPaths: result.offendingPaths
|
|
8824
|
+
};
|
|
8825
|
+
}
|
|
8826
|
+
return {
|
|
8827
|
+
prdRef,
|
|
8828
|
+
status: "valid",
|
|
8829
|
+
artifactPath,
|
|
8830
|
+
verdict: result.artifact.verdict,
|
|
8831
|
+
diagnostics: [],
|
|
8832
|
+
changedPaths: result.retouch?.changedPaths
|
|
8833
|
+
};
|
|
8834
|
+
}
|
|
8835
|
+
async function runPrdRunReconcileCommand(options) {
|
|
8836
|
+
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
8837
|
+
const targetName = options.targetName.trim();
|
|
8838
|
+
if (!targetName) {
|
|
8839
|
+
throw new Error(
|
|
8840
|
+
`Invalid target name "${options.targetName}". Expected a non-empty target name.`
|
|
8841
|
+
);
|
|
8842
|
+
}
|
|
8843
|
+
const { record, diagnostics: readDiagnostics } = readPrdRun(
|
|
8844
|
+
options.repoRoot,
|
|
8845
|
+
prdRef
|
|
8846
|
+
);
|
|
8847
|
+
if (readDiagnostics.length > 0) {
|
|
8848
|
+
const reason = `PRD Run ${prdRef} has a malformed record and cannot reconcile.`;
|
|
8849
|
+
return {
|
|
8850
|
+
prdRef,
|
|
8851
|
+
status: "blocked",
|
|
8852
|
+
blockedGate: "reconciliation",
|
|
8853
|
+
blockedReason: reason,
|
|
8854
|
+
diagnostics: readDiagnostics,
|
|
8855
|
+
offendingPaths: []
|
|
8856
|
+
};
|
|
8857
|
+
}
|
|
8858
|
+
if (!record) {
|
|
8859
|
+
return {
|
|
8860
|
+
prdRef,
|
|
8861
|
+
status: "blocked",
|
|
8862
|
+
blockedGate: "reconciliation",
|
|
8863
|
+
blockedReason: `PRD Run ${prdRef} does not exist. Run prd-run prepare/start/launch first.`,
|
|
8864
|
+
diagnostics: [`No PRD Run record found for ${prdRef}`],
|
|
8865
|
+
offendingPaths: []
|
|
8866
|
+
};
|
|
8867
|
+
}
|
|
8868
|
+
if (record.status !== "final_reviewed" && record.blockedGate !== "reconciliation") {
|
|
8869
|
+
const reason = `PRD Run ${prdRef} has not completed Final Review. Reconciliation requires status "final_reviewed", got "${record.status}".`;
|
|
8870
|
+
return {
|
|
8871
|
+
prdRef,
|
|
8872
|
+
status: "blocked",
|
|
8873
|
+
blockedGate: "reconciliation",
|
|
8874
|
+
blockedReason: reason,
|
|
8875
|
+
diagnostics: [`Current status: ${record.status}`],
|
|
8876
|
+
offendingPaths: []
|
|
8877
|
+
};
|
|
8878
|
+
}
|
|
8879
|
+
if (!record.finalReview) {
|
|
8880
|
+
const reason = `PRD Run ${prdRef} has no Final Review receipt. Cannot reconcile without a completed Final Review.`;
|
|
8881
|
+
return {
|
|
8882
|
+
prdRef,
|
|
8883
|
+
status: "blocked",
|
|
8884
|
+
blockedGate: "reconciliation",
|
|
8885
|
+
blockedReason: reason,
|
|
8886
|
+
diagnostics: ["Missing finalReview receipt in PRD Run record."],
|
|
8887
|
+
offendingPaths: []
|
|
8888
|
+
};
|
|
8889
|
+
}
|
|
8890
|
+
const finalReviewReceipt = record.finalReview;
|
|
8891
|
+
if (finalReviewReceipt.status !== "final_reviewed" && finalReviewReceipt.status !== "succeeded") {
|
|
8892
|
+
const reason = `PRD Run ${prdRef} Final Review receipt has status "${finalReviewReceipt.status}"; expected "final_reviewed" or "succeeded".`;
|
|
8893
|
+
return {
|
|
8894
|
+
prdRef,
|
|
8895
|
+
status: "blocked",
|
|
8896
|
+
blockedGate: "reconciliation",
|
|
8897
|
+
blockedReason: reason,
|
|
8898
|
+
diagnostics: [
|
|
8899
|
+
`Final Review status: ${finalReviewReceipt.status}`,
|
|
8900
|
+
`Verdict: ${finalReviewReceipt.verdict ?? "unknown"}`
|
|
8901
|
+
],
|
|
8902
|
+
offendingPaths: []
|
|
8903
|
+
};
|
|
8904
|
+
}
|
|
8905
|
+
const prdBranch = record.prdBranch ?? prdRef;
|
|
8906
|
+
const mergeBase = finalReviewReceipt.mergeBase;
|
|
8907
|
+
if (!mergeBase || mergeBase.length < 6) {
|
|
8908
|
+
const reason = `PRD Run ${prdRef} Final Review receipt is missing a valid merge base.`;
|
|
8909
|
+
return {
|
|
8910
|
+
prdRef,
|
|
8911
|
+
status: "blocked",
|
|
8912
|
+
blockedGate: "reconciliation",
|
|
8913
|
+
blockedReason: reason,
|
|
8914
|
+
diagnostics: [
|
|
8915
|
+
mergeBase ? `Merge base "${mergeBase}" is too short.` : "Merge base is missing."
|
|
8916
|
+
],
|
|
8917
|
+
offendingPaths: []
|
|
8918
|
+
};
|
|
8919
|
+
}
|
|
8920
|
+
const manifestResult = readPlanningArtifactManifest(options.repoRoot, prdRef);
|
|
8921
|
+
if (!manifestResult.ok) {
|
|
8922
|
+
const reason = `Reconciliation Evidence Packet construction failed: ${manifestResult.reason}`;
|
|
8923
|
+
return {
|
|
8924
|
+
prdRef,
|
|
8925
|
+
status: "blocked",
|
|
8926
|
+
blockedGate: "reconciliation",
|
|
8927
|
+
blockedReason: reason,
|
|
8928
|
+
diagnostics: manifestResult.diagnostics,
|
|
8929
|
+
offendingPaths: manifestResult.offendingPaths
|
|
8930
|
+
};
|
|
8931
|
+
}
|
|
8932
|
+
const manifest = manifestResult.manifest;
|
|
8933
|
+
let evidencePacket;
|
|
8934
|
+
try {
|
|
8935
|
+
evidencePacket = buildEvidencePacket({
|
|
8936
|
+
prdRef,
|
|
8937
|
+
prdBranch,
|
|
8938
|
+
mergeBase,
|
|
8939
|
+
planningManifestPath: manifest.manifestPath,
|
|
8940
|
+
planningManifestFacts: {
|
|
8941
|
+
parentPrdIssueUrl: manifest.parentIssue.url,
|
|
8942
|
+
childIssueCount: manifest.childIssues.length
|
|
8943
|
+
},
|
|
8944
|
+
stage: "prdReconciliation",
|
|
8945
|
+
stageReceipts: { finalReviewReceipt }
|
|
8250
8946
|
});
|
|
8251
|
-
|
|
8947
|
+
} catch (error) {
|
|
8948
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
8949
|
+
const reason = `Reconciliation Evidence Packet construction failed: ${msg}`;
|
|
8950
|
+
return {
|
|
8951
|
+
prdRef,
|
|
8952
|
+
status: "blocked",
|
|
8953
|
+
blockedGate: "reconciliation",
|
|
8954
|
+
blockedReason: reason,
|
|
8955
|
+
diagnostics: [msg],
|
|
8956
|
+
offendingPaths: []
|
|
8957
|
+
};
|
|
8958
|
+
}
|
|
8959
|
+
if (evidencePacket.stage !== "prdReconciliation") {
|
|
8960
|
+
return {
|
|
8961
|
+
prdRef,
|
|
8962
|
+
status: "blocked",
|
|
8963
|
+
blockedGate: "reconciliation",
|
|
8964
|
+
blockedReason: `Evidence Packet stage is "${evidencePacket.stage}", expected "prdReconciliation".`,
|
|
8965
|
+
diagnostics: [`Stage: ${evidencePacket.stage}`],
|
|
8966
|
+
offendingPaths: []
|
|
8967
|
+
};
|
|
8252
8968
|
}
|
|
8969
|
+
const expectedRef = prdRef;
|
|
8970
|
+
if (evidencePacket.prdRef !== expectedRef) {
|
|
8971
|
+
return {
|
|
8972
|
+
prdRef,
|
|
8973
|
+
status: "blocked",
|
|
8974
|
+
blockedGate: "reconciliation",
|
|
8975
|
+
blockedReason: `Evidence Packet PRD ref "${evidencePacket.prdRef}" does not match active PRD Run "${expectedRef}".`,
|
|
8976
|
+
diagnostics: [
|
|
8977
|
+
`Evidence Packet PRD ref: ${evidencePacket.prdRef}`,
|
|
8978
|
+
`Expected: ${expectedRef}`
|
|
8979
|
+
],
|
|
8980
|
+
offendingPaths: []
|
|
8981
|
+
};
|
|
8982
|
+
}
|
|
8983
|
+
if (evidencePacket.prdBranch !== prdBranch) {
|
|
8984
|
+
return {
|
|
8985
|
+
prdRef,
|
|
8986
|
+
status: "blocked",
|
|
8987
|
+
blockedGate: "reconciliation",
|
|
8988
|
+
blockedReason: `Evidence Packet PRD branch "${evidencePacket.prdBranch}" does not match active PRD Branch "${prdBranch}".`,
|
|
8989
|
+
diagnostics: [
|
|
8990
|
+
`Evidence Packet branch: ${evidencePacket.prdBranch}`,
|
|
8991
|
+
`Expected: ${prdBranch}`
|
|
8992
|
+
],
|
|
8993
|
+
offendingPaths: []
|
|
8994
|
+
};
|
|
8995
|
+
}
|
|
8996
|
+
const receipt = {
|
|
8997
|
+
status: "blocked",
|
|
8998
|
+
targetName,
|
|
8999
|
+
prdBranch,
|
|
9000
|
+
mergeBase,
|
|
9001
|
+
diagnostics: [
|
|
9002
|
+
"Reconciliation automation is not fully implemented. Full reconciliation requires Worktree creation, Architect invocation, output validation, and PR creation, none of which are implemented in this version."
|
|
9003
|
+
]
|
|
9004
|
+
};
|
|
9005
|
+
writePrdRunRecord(options.repoRoot, {
|
|
9006
|
+
...record,
|
|
9007
|
+
status: "final_reviewed",
|
|
9008
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9009
|
+
targetName,
|
|
9010
|
+
start: record.start,
|
|
9011
|
+
planning: record.planning,
|
|
9012
|
+
finalReview: record.finalReview,
|
|
9013
|
+
reconciliation: receipt
|
|
9014
|
+
});
|
|
9015
|
+
return {
|
|
9016
|
+
prdRef,
|
|
9017
|
+
status: "reconciled",
|
|
9018
|
+
reconciliation: receipt,
|
|
9019
|
+
diagnostics: ["Reconciliation automation is not fully implemented."]
|
|
9020
|
+
};
|
|
8253
9021
|
}
|
|
8254
9022
|
function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
8255
9023
|
const normalized = normalizePrdRunRef2(prdRef);
|
|
@@ -8285,97 +9053,169 @@ function computeFinalReviewMergeBase(repoRoot2, prdRef) {
|
|
|
8285
9053
|
if (mergeBaseResult.status !== 0) {
|
|
8286
9054
|
if (mergeBaseResult.stderr?.toString?.()?.trim()) {
|
|
8287
9055
|
mergeBaseDiagnostics.push(mergeBaseResult.stderr.toString().trim());
|
|
8288
|
-
}
|
|
8289
|
-
if (mergeBaseResult.stdout?.toString?.()?.trim()) {
|
|
8290
|
-
mergeBaseDiagnostics.push(mergeBaseResult.stdout.toString().trim());
|
|
8291
|
-
}
|
|
8292
|
-
return {
|
|
8293
|
-
ok: false,
|
|
8294
|
-
gate: "final-review",
|
|
8295
|
-
reason: `Final Review merge-base computation failed for origin/dev and origin/${normalized}.`,
|
|
8296
|
-
diagnostics: mergeBaseDiagnostics.length > 0 ? mergeBaseDiagnostics : ["git merge-base returned non-zero exit status"]
|
|
8297
|
-
};
|
|
8298
|
-
}
|
|
8299
|
-
const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
|
|
8300
|
-
if (!mergeBase) {
|
|
8301
|
-
return {
|
|
8302
|
-
ok: false,
|
|
8303
|
-
gate: "final-review",
|
|
8304
|
-
reason: `Final Review merge-base returned empty result for origin/dev and origin/${normalized}.`,
|
|
8305
|
-
diagnostics: [
|
|
8306
|
-
`merge-base stdout was empty for origin/dev..origin/${normalized}`
|
|
8307
|
-
]
|
|
8308
|
-
};
|
|
8309
|
-
}
|
|
8310
|
-
return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
|
|
8311
|
-
}
|
|
8312
|
-
function parseFinalReviewArtifact(artifactPath) {
|
|
8313
|
-
if (!existsSync11(artifactPath)) {
|
|
8314
|
-
return {
|
|
8315
|
-
ok: false,
|
|
8316
|
-
reason: "Final Review artifact not found.",
|
|
8317
|
-
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
8318
|
-
};
|
|
8319
|
-
}
|
|
8320
|
-
let content;
|
|
8321
|
-
try {
|
|
8322
|
-
content = readFileSync11(artifactPath, "utf-8");
|
|
8323
|
-
} catch (error) {
|
|
8324
|
-
return {
|
|
8325
|
-
ok: false,
|
|
8326
|
-
reason: "Final Review artifact could not be read.",
|
|
8327
|
-
diagnostics: [
|
|
8328
|
-
error instanceof Error ? error.message : String(error),
|
|
8329
|
-
`Artifact path: ${artifactPath}`
|
|
8330
|
-
]
|
|
8331
|
-
};
|
|
8332
|
-
}
|
|
8333
|
-
let parsed;
|
|
8334
|
-
try {
|
|
8335
|
-
parsed = JSON.parse(content);
|
|
8336
|
-
} catch {
|
|
8337
|
-
return {
|
|
8338
|
-
ok: false,
|
|
8339
|
-
reason: "Final Review artifact is not valid JSON.",
|
|
8340
|
-
diagnostics: [`Artifact path: ${artifactPath}`]
|
|
8341
|
-
};
|
|
8342
|
-
}
|
|
8343
|
-
const verdict = parsed.verdict;
|
|
8344
|
-
if (!verdict || typeof verdict !== "string") {
|
|
8345
|
-
return {
|
|
8346
|
-
ok: false,
|
|
8347
|
-
reason: "Final Review artifact is missing a verdict field.",
|
|
8348
|
-
diagnostics: [`Artifact content preview: ${content.slice(0, 300)}`]
|
|
8349
|
-
};
|
|
8350
|
-
}
|
|
8351
|
-
const allowedVerdicts = [
|
|
8352
|
-
"pass_no_changes",
|
|
8353
|
-
"pass_with_retouch",
|
|
8354
|
-
"needs_human_review",
|
|
8355
|
-
"blocked"
|
|
8356
|
-
];
|
|
8357
|
-
if (!allowedVerdicts.includes(verdict)) {
|
|
9056
|
+
}
|
|
9057
|
+
if (mergeBaseResult.stdout?.toString?.()?.trim()) {
|
|
9058
|
+
mergeBaseDiagnostics.push(mergeBaseResult.stdout.toString().trim());
|
|
9059
|
+
}
|
|
8358
9060
|
return {
|
|
8359
9061
|
ok: false,
|
|
8360
|
-
|
|
8361
|
-
|
|
9062
|
+
gate: "final-review",
|
|
9063
|
+
reason: `Final Review merge-base computation failed for origin/dev and origin/${normalized}.`,
|
|
9064
|
+
diagnostics: mergeBaseDiagnostics.length > 0 ? mergeBaseDiagnostics : ["git merge-base returned non-zero exit status"]
|
|
8362
9065
|
};
|
|
8363
9066
|
}
|
|
8364
|
-
const
|
|
8365
|
-
|
|
8366
|
-
const changedPaths = Array.isArray(parsed.changedPaths) ? parsed.changedPaths : void 0;
|
|
8367
|
-
const isAutoSummary = !parsed.summary && !parsed.reason;
|
|
8368
|
-
if (verdict === "pass_with_retouch" && (!changedPaths || changedPaths.length === 0) && isAutoSummary) {
|
|
9067
|
+
const mergeBase = mergeBaseResult.stdout?.toString?.()?.trim();
|
|
9068
|
+
if (!mergeBase) {
|
|
8369
9069
|
return {
|
|
8370
9070
|
ok: false,
|
|
8371
|
-
|
|
9071
|
+
gate: "final-review",
|
|
9072
|
+
reason: `Final Review merge-base returned empty result for origin/dev and origin/${normalized}.`,
|
|
8372
9073
|
diagnostics: [
|
|
8373
|
-
|
|
8374
|
-
"Provide changedPaths or a descriptive summary in the artifact."
|
|
9074
|
+
`merge-base stdout was empty for origin/dev..origin/${normalized}`
|
|
8375
9075
|
]
|
|
8376
9076
|
};
|
|
8377
9077
|
}
|
|
8378
|
-
return { ok: true,
|
|
9078
|
+
return { ok: true, mergeBase, diagnostics: fetchDiagnostics };
|
|
9079
|
+
}
|
|
9080
|
+
var PRD_047_PRD_MIRROR_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-047-prd-reconciliation-run/PRD.md";
|
|
9081
|
+
var PRD_047_CHILD_ISSUE_DIR = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-047-prd-reconciliation-run/issues/";
|
|
9082
|
+
var PRD_047_ESCAPE_HATCH_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/completions/006-prd-047-prd-reconciliation-run-status.md";
|
|
9083
|
+
var PRD_047_MANIFEST_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-047-prd-reconciliation-run/planning-manifest.md";
|
|
9084
|
+
function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
9085
|
+
const normalizedRef = normalizePrdRunRef2(prdRef);
|
|
9086
|
+
const blockerBugs = [];
|
|
9087
|
+
const blockersFixed = [];
|
|
9088
|
+
const nonBlockers = [];
|
|
9089
|
+
const prdMirrorPath = join15(repoRoot2, PRD_047_PRD_MIRROR_PATH);
|
|
9090
|
+
const completionsDir = join15(repoRoot2, dirname7(PRD_047_ESCAPE_HATCH_PATH));
|
|
9091
|
+
let escapeHatchPresent = false;
|
|
9092
|
+
if (existsSync12(completionsDir)) {
|
|
9093
|
+
const files = readdirSync4(completionsDir);
|
|
9094
|
+
escapeHatchPresent = files.some(
|
|
9095
|
+
(f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
|
|
9096
|
+
);
|
|
9097
|
+
}
|
|
9098
|
+
if (!escapeHatchPresent) {
|
|
9099
|
+
escapeHatchPresent = existsSync12(join15(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
|
|
9100
|
+
}
|
|
9101
|
+
const prdMirrorExists = existsSync12(prdMirrorPath);
|
|
9102
|
+
if (!prdMirrorExists) {
|
|
9103
|
+
nonBlockers.push(
|
|
9104
|
+
`PRD-047 PRD mirror not found at ${PRD_047_PRD_MIRROR_PATH}. Cannot verify scope alignment from published mirrors.`
|
|
9105
|
+
);
|
|
9106
|
+
}
|
|
9107
|
+
if (prdMirrorExists) {
|
|
9108
|
+
const mirrorContent = readFileSync12(prdMirrorPath, "utf-8");
|
|
9109
|
+
const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
|
|
9110
|
+
const hasEscapeHatch = mirrorContent.includes("escape hatch");
|
|
9111
|
+
if (!hasReconcileCommand) {
|
|
9112
|
+
blockerBugs.push(
|
|
9113
|
+
"PRD-047 PRD mirror does not reference prd-run reconcile command."
|
|
9114
|
+
);
|
|
9115
|
+
}
|
|
9116
|
+
if (!hasEscapeHatch) {
|
|
9117
|
+
nonBlockers.push(
|
|
9118
|
+
"PRD-047 PRD mirror does not reference escape hatch mechanism."
|
|
9119
|
+
);
|
|
9120
|
+
}
|
|
9121
|
+
}
|
|
9122
|
+
const childIssueDir = join15(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
|
|
9123
|
+
const childIssuesExist = existsSync12(childIssueDir);
|
|
9124
|
+
if (!childIssuesExist) {
|
|
9125
|
+
nonBlockers.push(
|
|
9126
|
+
`PRD-047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
|
|
9127
|
+
);
|
|
9128
|
+
}
|
|
9129
|
+
const manifestPath = join15(repoRoot2, PRD_047_MANIFEST_PATH);
|
|
9130
|
+
if (existsSync12(manifestPath)) {
|
|
9131
|
+
const manifestContent = readFileSync12(manifestPath, "utf-8");
|
|
9132
|
+
if (!manifestContent.includes("ready_for_prepare")) {
|
|
9133
|
+
nonBlockers.push(
|
|
9134
|
+
"Planning Artifact Manifest is not marked as ready_for_prepare."
|
|
9135
|
+
);
|
|
9136
|
+
}
|
|
9137
|
+
if (!manifestContent.includes("Publication Receipt")) {
|
|
9138
|
+
nonBlockers.push(
|
|
9139
|
+
"Planning Artifact Manifest is missing parent PRD publication receipt."
|
|
9140
|
+
);
|
|
9141
|
+
}
|
|
9142
|
+
if (!manifestContent.includes("Triage Receipt")) {
|
|
9143
|
+
nonBlockers.push(
|
|
9144
|
+
"Planning Artifact Manifest is missing child Issue triage receipt."
|
|
9145
|
+
);
|
|
9146
|
+
}
|
|
9147
|
+
} else {
|
|
9148
|
+
nonBlockers.push(
|
|
9149
|
+
"Planning Artifact Manifest not found. Cannot verify publication receipts."
|
|
9150
|
+
);
|
|
9151
|
+
}
|
|
9152
|
+
const prdRunRecord = readPrdRun(repoRoot2, normalizedRef);
|
|
9153
|
+
if (prdRunRecord.record && prdRunRecord.record.reconciliation) {
|
|
9154
|
+
if (prdRunRecord.record.reconciliation.status === "succeeded") {
|
|
9155
|
+
blockerBugs.push(
|
|
9156
|
+
"Fabricated reconciliation success: reconciliation receipt has status 'succeeded' but full reconciliation automation is not implemented."
|
|
9157
|
+
);
|
|
9158
|
+
}
|
|
9159
|
+
}
|
|
9160
|
+
if (prdRunRecord.record && prdRunRecord.record.finalReview) {
|
|
9161
|
+
if (prdRunRecord.record.finalReview.status === "succeeded" && (!prdRunRecord.record.finalReview.artifactPath || !prdRunRecord.record.finalReview.reviewedAt)) {
|
|
9162
|
+
blockerBugs.push(
|
|
9163
|
+
"Fabricated Final Review receipt: status is 'succeeded' without evidence of agent execution (missing artifactPath or reviewedAt)."
|
|
9164
|
+
);
|
|
9165
|
+
}
|
|
9166
|
+
}
|
|
9167
|
+
if (prdRunRecord.record && prdRunRecord.record.start) {
|
|
9168
|
+
const start = prdRunRecord.record.start;
|
|
9169
|
+
if (start.prdBranch !== normalizedRef) {
|
|
9170
|
+
blockerBugs.push(
|
|
9171
|
+
`PRD Branch mismatch: start receipt branch "${start.prdBranch}" does not match PRD ref "${normalizedRef}".`
|
|
9172
|
+
);
|
|
9173
|
+
}
|
|
9174
|
+
if (!start.startBaseCommit) {
|
|
9175
|
+
blockerBugs.push(
|
|
9176
|
+
`PRD Run start receipt is missing startBaseCommit for branch "${start.prdBranch}".`
|
|
9177
|
+
);
|
|
9178
|
+
}
|
|
9179
|
+
if (!start.branchAction) {
|
|
9180
|
+
blockerBugs.push(
|
|
9181
|
+
`PRD Run start receipt is missing branchAction for branch "${start.prdBranch}".`
|
|
9182
|
+
);
|
|
9183
|
+
}
|
|
9184
|
+
}
|
|
9185
|
+
if (prdRunRecord.record && prdRunRecord.record.status === "waiting_for_integration" && !prdRunRecord.record.reconciliation) {
|
|
9186
|
+
blockerBugs.push(
|
|
9187
|
+
"PRD Run is in 'waiting_for_integration' status without a reconciliation receipt."
|
|
9188
|
+
);
|
|
9189
|
+
}
|
|
9190
|
+
if (!prdRunRecord.record) {
|
|
9191
|
+
nonBlockers.push(
|
|
9192
|
+
`No PRD Run record found for ${normalizedRef}. Cannot verify implementation state.`
|
|
9193
|
+
);
|
|
9194
|
+
}
|
|
9195
|
+
if (!prdMirrorExists && !childIssuesExist && !prdRunRecord.record) {
|
|
9196
|
+
nonBlockers.push(
|
|
9197
|
+
"Insufficient evidence to verify PRD-047 scope alignment: mirrors and PRD Run record are missing."
|
|
9198
|
+
);
|
|
9199
|
+
}
|
|
9200
|
+
if (escapeHatchPresent) {
|
|
9201
|
+
blockersFixed.push(
|
|
9202
|
+
"PRD-047 escape-hatch lifecycle violation record confirmed present."
|
|
9203
|
+
);
|
|
9204
|
+
} else {
|
|
9205
|
+
nonBlockers.push(
|
|
9206
|
+
"PRD-047 escape-hatch lifecycle violation record is missing. Expected at " + PRD_047_ESCAPE_HATCH_PATH + "."
|
|
9207
|
+
);
|
|
9208
|
+
}
|
|
9209
|
+
return {
|
|
9210
|
+
blockerBugs,
|
|
9211
|
+
blockersFixed,
|
|
9212
|
+
nonBlockers,
|
|
9213
|
+
escapeHatchPresent
|
|
9214
|
+
};
|
|
9215
|
+
}
|
|
9216
|
+
function runPrdRunAuditCommand(options) {
|
|
9217
|
+
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
9218
|
+
return auditPrd047Implementation(options.repoRoot, prdRef);
|
|
8379
9219
|
}
|
|
8380
9220
|
async function runPrdRunLaunchCommand(options) {
|
|
8381
9221
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
@@ -8386,18 +9226,16 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
8386
9226
|
const existingRecord = readPrdRun(options.repoRoot, prdRef);
|
|
8387
9227
|
if (existingRecord.record) {
|
|
8388
9228
|
const terminal = /* @__PURE__ */ new Set([
|
|
8389
|
-
"drained",
|
|
8390
9229
|
"waiting_for_integration",
|
|
8391
9230
|
"finalizing",
|
|
8392
|
-
"complete"
|
|
8393
|
-
"final_reviewed"
|
|
9231
|
+
"complete"
|
|
8394
9232
|
]);
|
|
8395
9233
|
if (terminal.has(existingRecord.record.status)) {
|
|
8396
9234
|
return {
|
|
8397
9235
|
prdRef,
|
|
8398
9236
|
status: existingRecord.record.status,
|
|
8399
9237
|
attempted: [],
|
|
8400
|
-
skipped: ["prepare", "start", "queue"],
|
|
9238
|
+
skipped: ["prepare", "start", "queue", "final-review", "reconcile"],
|
|
8401
9239
|
resumed: [],
|
|
8402
9240
|
diagnostics: [
|
|
8403
9241
|
`PRD Run ${prdRef} is already in status "${existingRecord.record.status}".`
|
|
@@ -8407,28 +9245,42 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
8407
9245
|
}
|
|
8408
9246
|
if (existingRecord.record) {
|
|
8409
9247
|
const status = existingRecord.record.status;
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
9248
|
+
if (status === "drained") {
|
|
9249
|
+
skipped.push("prepare", "start", "queue");
|
|
9250
|
+
resumed.push("final-review");
|
|
9251
|
+
} else if (status === "final_reviewed") {
|
|
9252
|
+
skipped.push("prepare", "start", "queue", "final-review");
|
|
9253
|
+
resumed.push("reconcile");
|
|
9254
|
+
} else if (status === "blocked" && existingRecord.record.blockedGate === "reconciliation") {
|
|
9255
|
+
skipped.push("prepare", "start", "queue", "final-review");
|
|
9256
|
+
resumed.push("reconcile");
|
|
9257
|
+
} else if (status === "blocked" && existingRecord.record.blockedGate === "final-review" && canRetryFinalReviewBlock(existingRecord.record)) {
|
|
9258
|
+
skipped.push("prepare", "start", "queue");
|
|
9259
|
+
resumed.push("final-review");
|
|
9260
|
+
} else {
|
|
9261
|
+
const resumableStartBlock = status === "blocked" && (existingRecord.record.blockedGate === "queue" || existingRecord.record.blockedGate === "branch-state");
|
|
9262
|
+
if (status === "starting" || status === "running" || resumableStartBlock) {
|
|
9263
|
+
if (existingRecord.record.start) {
|
|
9264
|
+
skipped.push("prepare");
|
|
9265
|
+
resumed.push("start");
|
|
9266
|
+
} else {
|
|
9267
|
+
return {
|
|
9268
|
+
prdRef,
|
|
9269
|
+
status: "blocked",
|
|
9270
|
+
attempted: [],
|
|
9271
|
+
skipped: ["prepare", "start", "queue", "final-review", "reconcile"],
|
|
9272
|
+
resumed: [],
|
|
9273
|
+
diagnostics: [
|
|
9274
|
+
`PRD Run ${prdRef} is in status "${status}" but has no start receipt.`
|
|
9275
|
+
],
|
|
9276
|
+
blockedGate: "branch-state",
|
|
9277
|
+
blockedReason: `PRD Run ${prdRef} is in status "${status}" without a start receipt. Cannot resume launch.`,
|
|
9278
|
+
offendingPaths: []
|
|
9279
|
+
};
|
|
9280
|
+
}
|
|
9281
|
+
} else if (status === "ready_to_start") {
|
|
8413
9282
|
skipped.push("prepare");
|
|
8414
|
-
resumed.push("start");
|
|
8415
|
-
} else {
|
|
8416
|
-
return {
|
|
8417
|
-
prdRef,
|
|
8418
|
-
status: "blocked",
|
|
8419
|
-
attempted: [],
|
|
8420
|
-
skipped: ["prepare", "start", "queue"],
|
|
8421
|
-
resumed: [],
|
|
8422
|
-
diagnostics: [
|
|
8423
|
-
`PRD Run ${prdRef} is in status "${status}" but has no start receipt.`
|
|
8424
|
-
],
|
|
8425
|
-
blockedGate: "branch-state",
|
|
8426
|
-
blockedReason: `PRD Run ${prdRef} is in status "${status}" without a start receipt. Cannot resume launch.`,
|
|
8427
|
-
offendingPaths: []
|
|
8428
|
-
};
|
|
8429
9283
|
}
|
|
8430
|
-
} else if (status === "ready_to_start") {
|
|
8431
|
-
skipped.push("prepare");
|
|
8432
9284
|
}
|
|
8433
9285
|
}
|
|
8434
9286
|
if (existingRecord.record && existingRecord.record.status !== "blocked" && !skipped.includes("prepare")) {
|
|
@@ -8470,57 +9322,166 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
8470
9322
|
}
|
|
8471
9323
|
diagnostics.push(...prepareResult.diagnostics);
|
|
8472
9324
|
}
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
config: options.config,
|
|
8479
|
-
issueProvider: options.issueProvider,
|
|
8480
|
-
prProvider: options.prProvider,
|
|
8481
|
-
executionProvider: options.executionProvider,
|
|
8482
|
-
logger: options.logger,
|
|
8483
|
-
skipPrepareCheck: options.skipPrepareCheck,
|
|
8484
|
-
adoptExistingBranch: options.adoptExistingBranch
|
|
8485
|
-
});
|
|
8486
|
-
const skippedAfterStart = skipped.includes("prepare") ? [...skipped, "queue"] : ["queue"];
|
|
8487
|
-
if (startResult.status === "blocked") {
|
|
8488
|
-
return {
|
|
9325
|
+
let startResult;
|
|
9326
|
+
if (!skipped.includes("start")) {
|
|
9327
|
+
attempted.push("start");
|
|
9328
|
+
startResult = await runPrdRunStartCommand({
|
|
9329
|
+
repoRoot: options.repoRoot,
|
|
8489
9330
|
prdRef,
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
9331
|
+
targetName: options.targetName,
|
|
9332
|
+
config: options.config,
|
|
9333
|
+
issueProvider: options.issueProvider,
|
|
9334
|
+
prProvider: options.prProvider,
|
|
9335
|
+
executionProvider: options.executionProvider,
|
|
9336
|
+
logger: options.logger,
|
|
9337
|
+
skipPrepareCheck: options.skipPrepareCheck,
|
|
9338
|
+
adoptExistingBranch: options.adoptExistingBranch
|
|
9339
|
+
});
|
|
9340
|
+
const skippedAfterStart = [
|
|
9341
|
+
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
9342
|
+
"queue",
|
|
9343
|
+
"final-review",
|
|
9344
|
+
"reconcile"
|
|
9345
|
+
];
|
|
9346
|
+
if (startResult.status === "blocked") {
|
|
9347
|
+
return {
|
|
9348
|
+
prdRef,
|
|
9349
|
+
status: "blocked",
|
|
9350
|
+
attempted,
|
|
9351
|
+
skipped: skippedAfterStart,
|
|
9352
|
+
resumed,
|
|
9353
|
+
diagnostics: startResult.diagnostics,
|
|
9354
|
+
blockedGate: startResult.blockedGate,
|
|
9355
|
+
blockedReason: startResult.blockedReason,
|
|
9356
|
+
offendingPaths: startResult.offendingPaths,
|
|
9357
|
+
prepare: prepareResult,
|
|
9358
|
+
start: startResult
|
|
9359
|
+
};
|
|
9360
|
+
}
|
|
9361
|
+
if (startResult.status === "starting") {
|
|
9362
|
+
return {
|
|
9363
|
+
prdRef,
|
|
9364
|
+
status: "starting",
|
|
9365
|
+
attempted,
|
|
9366
|
+
skipped: skippedAfterStart,
|
|
9367
|
+
resumed,
|
|
9368
|
+
diagnostics: startResult.diagnostics,
|
|
9369
|
+
prepare: prepareResult,
|
|
9370
|
+
start: startResult
|
|
9371
|
+
};
|
|
9372
|
+
}
|
|
9373
|
+
diagnostics.push(...startResult.diagnostics);
|
|
9374
|
+
attempted.push("queue");
|
|
9375
|
+
}
|
|
9376
|
+
let finalReviewResult;
|
|
9377
|
+
if (!skipped.includes("final-review")) {
|
|
9378
|
+
attempted.push("final-review");
|
|
9379
|
+
finalReviewResult = await runPrdRunFinalReviewCommand({
|
|
9380
|
+
repoRoot: options.repoRoot,
|
|
8504
9381
|
prdRef,
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
};
|
|
9382
|
+
targetName: options.targetName,
|
|
9383
|
+
autoMerge: options.autoMerge,
|
|
9384
|
+
issueProvider: options.issueProvider,
|
|
9385
|
+
prProvider: options.prProvider,
|
|
9386
|
+
executionProvider: options.executionProvider,
|
|
9387
|
+
config: options.config,
|
|
9388
|
+
logger: options.logger
|
|
9389
|
+
});
|
|
9390
|
+
const skippedAfterFr = [
|
|
9391
|
+
...skipped.includes("prepare") ? ["prepare"] : [],
|
|
9392
|
+
...skipped.includes("start") ? ["queue"] : [],
|
|
9393
|
+
"reconcile"
|
|
9394
|
+
];
|
|
9395
|
+
if (finalReviewResult.status === "blocked") {
|
|
9396
|
+
return {
|
|
9397
|
+
prdRef,
|
|
9398
|
+
status: "blocked",
|
|
9399
|
+
attempted,
|
|
9400
|
+
skipped: skippedAfterFr,
|
|
9401
|
+
resumed,
|
|
9402
|
+
diagnostics: finalReviewResult.diagnostics,
|
|
9403
|
+
blockedGate: "final-review",
|
|
9404
|
+
blockedReason: finalReviewResult.blockedReason,
|
|
9405
|
+
offendingPaths: finalReviewResult.offendingPaths,
|
|
9406
|
+
prepare: prepareResult,
|
|
9407
|
+
start: startResult,
|
|
9408
|
+
finalReview: finalReviewResult
|
|
9409
|
+
};
|
|
9410
|
+
}
|
|
9411
|
+
if (finalReviewResult.status === "needs_human_review") {
|
|
9412
|
+
return {
|
|
9413
|
+
prdRef,
|
|
9414
|
+
status: "blocked",
|
|
9415
|
+
attempted,
|
|
9416
|
+
skipped: skippedAfterFr,
|
|
9417
|
+
resumed,
|
|
9418
|
+
diagnostics: finalReviewResult.diagnostics,
|
|
9419
|
+
blockedGate: "final-review",
|
|
9420
|
+
blockedReason: finalReviewResult.finalReview.verdict ?? "needs_human_review",
|
|
9421
|
+
offendingPaths: [],
|
|
9422
|
+
prepare: prepareResult,
|
|
9423
|
+
start: startResult,
|
|
9424
|
+
finalReview: finalReviewResult
|
|
9425
|
+
};
|
|
9426
|
+
}
|
|
9427
|
+
diagnostics.push(...finalReviewResult.diagnostics);
|
|
9428
|
+
}
|
|
9429
|
+
let reconcileResult;
|
|
9430
|
+
if (!skipped.includes("reconcile")) {
|
|
9431
|
+
attempted.push("reconcile");
|
|
9432
|
+
reconcileResult = await runPrdRunReconcileCommand({
|
|
9433
|
+
repoRoot: options.repoRoot,
|
|
9434
|
+
prdRef,
|
|
9435
|
+
targetName: options.targetName,
|
|
9436
|
+
config: options.config,
|
|
9437
|
+
logger: options.logger
|
|
9438
|
+
});
|
|
9439
|
+
if (reconcileResult.status === "blocked") {
|
|
9440
|
+
return {
|
|
9441
|
+
prdRef,
|
|
9442
|
+
status: "blocked",
|
|
9443
|
+
attempted,
|
|
9444
|
+
skipped: [...skipped],
|
|
9445
|
+
resumed,
|
|
9446
|
+
diagnostics: reconcileResult.diagnostics,
|
|
9447
|
+
blockedGate: "reconciliation",
|
|
9448
|
+
blockedReason: reconcileResult.blockedReason,
|
|
9449
|
+
offendingPaths: reconcileResult.offendingPaths,
|
|
9450
|
+
prepare: prepareResult,
|
|
9451
|
+
start: startResult,
|
|
9452
|
+
finalReview: finalReviewResult,
|
|
9453
|
+
reconcile: reconcileResult
|
|
9454
|
+
};
|
|
9455
|
+
}
|
|
9456
|
+
diagnostics.push(...reconcileResult.diagnostics);
|
|
8513
9457
|
}
|
|
8514
|
-
|
|
9458
|
+
const currentRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
9459
|
+
writePrdRunRecord(options.repoRoot, {
|
|
9460
|
+
prdRef,
|
|
9461
|
+
status: "waiting_for_integration",
|
|
9462
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9463
|
+
targetName: currentRecord?.targetName ?? options.targetName,
|
|
9464
|
+
prdBranch: currentRecord?.prdBranch,
|
|
9465
|
+
manifestPath: currentRecord?.manifestPath,
|
|
9466
|
+
planning: currentRecord?.planning,
|
|
9467
|
+
start: currentRecord?.start,
|
|
9468
|
+
finalReview: currentRecord?.finalReview,
|
|
9469
|
+
reconciliation: currentRecord?.reconciliation,
|
|
9470
|
+
scopeChanges: currentRecord?.scopeChanges
|
|
9471
|
+
});
|
|
8515
9472
|
return {
|
|
8516
9473
|
prdRef,
|
|
8517
|
-
status: "
|
|
9474
|
+
status: "waiting_for_integration",
|
|
8518
9475
|
attempted,
|
|
8519
9476
|
skipped,
|
|
8520
9477
|
resumed,
|
|
8521
|
-
diagnostics:
|
|
9478
|
+
diagnostics: [
|
|
9479
|
+
"Integration Gate is not implemented. PRD Run is waiting for manual integration."
|
|
9480
|
+
],
|
|
8522
9481
|
prepare: prepareResult,
|
|
8523
|
-
start: startResult
|
|
9482
|
+
start: startResult,
|
|
9483
|
+
finalReview: finalReviewResult,
|
|
9484
|
+
reconcile: reconcileResult
|
|
8524
9485
|
};
|
|
8525
9486
|
}
|
|
8526
9487
|
async function runPrdRunPrepareCommand(options) {
|
|
@@ -9690,47 +10651,6 @@ function validateArchitecturePlanningDiffScope(changedFiles) {
|
|
|
9690
10651
|
function isArchitecturePlanningDiffPath(path9) {
|
|
9691
10652
|
return path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/adr/");
|
|
9692
10653
|
}
|
|
9693
|
-
function isRetouchScopePath(path9) {
|
|
9694
|
-
if (path9.startsWith(".pourkit/architecture/") || path9 === ".pourkit/CONTEXT.md" || path9.startsWith(".pourkit/docs/adr/") || /^\.pourkit\/prd-runs\/[^/]+\.json$/.test(path9)) {
|
|
9695
|
-
return false;
|
|
9696
|
-
}
|
|
9697
|
-
return true;
|
|
9698
|
-
}
|
|
9699
|
-
function validateFinalReviewRetouchScope(changedPaths) {
|
|
9700
|
-
if (!changedPaths || changedPaths.length === 0) {
|
|
9701
|
-
return {
|
|
9702
|
-
ok: false,
|
|
9703
|
-
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.",
|
|
9704
|
-
diagnostics: ["Changed paths list is empty or undefined."],
|
|
9705
|
-
offendingPaths: []
|
|
9706
|
-
};
|
|
9707
|
-
}
|
|
9708
|
-
const offendingPaths = changedPaths.filter((p) => !isRetouchScopePath(p));
|
|
9709
|
-
const allowedPaths = changedPaths.filter((p) => isRetouchScopePath(p));
|
|
9710
|
-
if (allowedPaths.length === 0) {
|
|
9711
|
-
return {
|
|
9712
|
-
ok: false,
|
|
9713
|
-
reason: "Final Review retouch scope validation failed. No implementation, test, or Changeset paths found for retouch.",
|
|
9714
|
-
diagnostics: [
|
|
9715
|
-
"All changed paths are prohibited for retouch.",
|
|
9716
|
-
...changedPaths.map((p) => ` - ${p}`)
|
|
9717
|
-
],
|
|
9718
|
-
offendingPaths
|
|
9719
|
-
};
|
|
9720
|
-
}
|
|
9721
|
-
if (offendingPaths.length > 0) {
|
|
9722
|
-
return {
|
|
9723
|
-
ok: false,
|
|
9724
|
-
reason: "Final Review retouch scope validation failed. Prohibited paths cannot be included in retouch PR.",
|
|
9725
|
-
diagnostics: [
|
|
9726
|
-
"Prohibited paths found:",
|
|
9727
|
-
...offendingPaths.map((p) => ` - ${p}`)
|
|
9728
|
-
],
|
|
9729
|
-
offendingPaths
|
|
9730
|
-
};
|
|
9731
|
-
}
|
|
9732
|
-
return { ok: true, changedPaths };
|
|
9733
|
-
}
|
|
9734
10654
|
async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles) {
|
|
9735
10655
|
const remoteResult = spawnSync2("git", ["remote", "get-url", "origin"], {
|
|
9736
10656
|
cwd: repoRoot2,
|
|
@@ -9739,7 +10659,7 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
9739
10659
|
if (remoteResult.status !== 0) {
|
|
9740
10660
|
return;
|
|
9741
10661
|
}
|
|
9742
|
-
const worktreePath =
|
|
10662
|
+
const worktreePath = mkdtempSync2(join15(tmpdir2(), "pourkit-planning-"));
|
|
9743
10663
|
try {
|
|
9744
10664
|
runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
|
|
9745
10665
|
runGitOrThrow(
|
|
@@ -10013,7 +10933,7 @@ function parseGitStatusLine(line) {
|
|
|
10013
10933
|
return { status, path: path9.replace(/^"|"$/g, "") };
|
|
10014
10934
|
}
|
|
10015
10935
|
function validateManifestArtifactExistence(repoRoot2, manifest) {
|
|
10016
|
-
const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !
|
|
10936
|
+
const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !existsSync12(path9)).map((path9) => toRepoRelativePath2(repoRoot2, path9));
|
|
10017
10937
|
if (missingPaths.length > 0) {
|
|
10018
10938
|
return {
|
|
10019
10939
|
ok: false,
|
|
@@ -10430,7 +11350,7 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
|
|
|
10430
11350
|
|
|
10431
11351
|
// commands/init.ts
|
|
10432
11352
|
init_github_client();
|
|
10433
|
-
import { existsSync as
|
|
11353
|
+
import { existsSync as existsSync13, statSync } from "fs";
|
|
10434
11354
|
import {
|
|
10435
11355
|
copyFile,
|
|
10436
11356
|
mkdir as mkdir4,
|
|
@@ -10872,7 +11792,7 @@ async function computeFileChecksum(filePath) {
|
|
|
10872
11792
|
return createHash2("sha256").update(content).digest("hex");
|
|
10873
11793
|
}
|
|
10874
11794
|
function lockfileExists(root, name) {
|
|
10875
|
-
return
|
|
11795
|
+
return existsSync13(path7.join(root, name));
|
|
10876
11796
|
}
|
|
10877
11797
|
function detectPackageManager(root) {
|
|
10878
11798
|
if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
|
|
@@ -10916,7 +11836,7 @@ async function discoverLocalSource(sourcePath) {
|
|
|
10916
11836
|
async function discoverReadme(root) {
|
|
10917
11837
|
for (const name of ["README.md", "readme.md"]) {
|
|
10918
11838
|
const p = path7.join(root, name);
|
|
10919
|
-
if (
|
|
11839
|
+
if (existsSync13(p)) {
|
|
10920
11840
|
return p;
|
|
10921
11841
|
}
|
|
10922
11842
|
}
|
|
@@ -10926,7 +11846,7 @@ async function discoverAgentFiles(root) {
|
|
|
10926
11846
|
const files = [];
|
|
10927
11847
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
10928
11848
|
const p = path7.join(root, name);
|
|
10929
|
-
if (
|
|
11849
|
+
if (existsSync13(p)) {
|
|
10930
11850
|
files.push(p);
|
|
10931
11851
|
}
|
|
10932
11852
|
}
|
|
@@ -10934,7 +11854,7 @@ async function discoverAgentFiles(root) {
|
|
|
10934
11854
|
}
|
|
10935
11855
|
async function discoverMerlleState(root) {
|
|
10936
11856
|
const p = path7.join(root, ".pourkit", "state.json");
|
|
10937
|
-
return
|
|
11857
|
+
return existsSync13(p) ? p : null;
|
|
10938
11858
|
}
|
|
10939
11859
|
async function discoverAgentSkills(root) {
|
|
10940
11860
|
const dirs = [
|
|
@@ -10943,7 +11863,7 @@ async function discoverAgentSkills(root) {
|
|
|
10943
11863
|
];
|
|
10944
11864
|
const found = [];
|
|
10945
11865
|
for (const d of dirs) {
|
|
10946
|
-
if (
|
|
11866
|
+
if (existsSync13(d)) {
|
|
10947
11867
|
found.push(d);
|
|
10948
11868
|
}
|
|
10949
11869
|
}
|
|
@@ -10953,12 +11873,12 @@ async function discoverRootDomainDocs(root) {
|
|
|
10953
11873
|
const docs = [];
|
|
10954
11874
|
for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
|
|
10955
11875
|
const p = path7.join(root, name);
|
|
10956
|
-
if (
|
|
11876
|
+
if (existsSync13(p)) {
|
|
10957
11877
|
docs.push(p);
|
|
10958
11878
|
}
|
|
10959
11879
|
}
|
|
10960
11880
|
const adrDir = path7.join(root, "docs", "adr");
|
|
10961
|
-
if (
|
|
11881
|
+
if (existsSync13(adrDir)) {
|
|
10962
11882
|
const entries = await readdir(adrDir, { withFileTypes: true });
|
|
10963
11883
|
for (const entry of entries) {
|
|
10964
11884
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -11101,7 +12021,7 @@ async function planInit(options) {
|
|
|
11101
12021
|
for (const file of skillFiles) {
|
|
11102
12022
|
const relPath = path7.relative(s, file);
|
|
11103
12023
|
const destPath = path7.join(targetRoot, ".agents", "skills", relPath);
|
|
11104
|
-
if (!
|
|
12024
|
+
if (!existsSync13(destPath)) {
|
|
11105
12025
|
operations.push({
|
|
11106
12026
|
kind: "copy",
|
|
11107
12027
|
sourcePath: file,
|
|
@@ -11164,7 +12084,7 @@ async function planInit(options) {
|
|
|
11164
12084
|
});
|
|
11165
12085
|
}
|
|
11166
12086
|
if (sourceRoot) {
|
|
11167
|
-
if (!
|
|
12087
|
+
if (!existsSync13(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
|
|
11168
12088
|
warnings.push(
|
|
11169
12089
|
`--from-local path does not exist or is not a directory: ${sourceRoot}`
|
|
11170
12090
|
);
|
|
@@ -11283,7 +12203,7 @@ async function planInit(options) {
|
|
|
11283
12203
|
requiresConfirmation: false,
|
|
11284
12204
|
destructive: false
|
|
11285
12205
|
});
|
|
11286
|
-
} else if (
|
|
12206
|
+
} else if (existsSync13(destPath)) {
|
|
11287
12207
|
operations.push({
|
|
11288
12208
|
kind: "skip",
|
|
11289
12209
|
path: destPath,
|
|
@@ -11341,7 +12261,7 @@ async function planInit(options) {
|
|
|
11341
12261
|
}
|
|
11342
12262
|
}
|
|
11343
12263
|
const contextPath = path7.join(targetRoot, ".pourkit", "CONTEXT.md");
|
|
11344
|
-
if (!
|
|
12264
|
+
if (!existsSync13(contextPath) && !merleDestPaths.has(contextPath)) {
|
|
11345
12265
|
operations.push({
|
|
11346
12266
|
kind: "create",
|
|
11347
12267
|
path: contextPath,
|
|
@@ -11359,7 +12279,7 @@ async function planInit(options) {
|
|
|
11359
12279
|
"adr",
|
|
11360
12280
|
".gitkeep"
|
|
11361
12281
|
);
|
|
11362
|
-
if (!
|
|
12282
|
+
if (!existsSync13(adrGitkeep)) {
|
|
11363
12283
|
operations.push({
|
|
11364
12284
|
kind: "create",
|
|
11365
12285
|
path: adrGitkeep,
|
|
@@ -11371,7 +12291,7 @@ async function planInit(options) {
|
|
|
11371
12291
|
}
|
|
11372
12292
|
const srcDocAgents = path7.join(sourceRoot, ".pourkit", "docs", "agents");
|
|
11373
12293
|
const tgtDocAgents = path7.join(targetRoot, ".pourkit", "docs", "agents");
|
|
11374
|
-
if (
|
|
12294
|
+
if (existsSync13(srcDocAgents) && !existsSync13(tgtDocAgents)) {
|
|
11375
12295
|
const docFiles = await walkDir(srcDocAgents);
|
|
11376
12296
|
for (const file of docFiles) {
|
|
11377
12297
|
const relPath = path7.relative(srcDocAgents, file);
|
|
@@ -11404,7 +12324,7 @@ async function planInit(options) {
|
|
|
11404
12324
|
}
|
|
11405
12325
|
const srcPrompts = path7.join(sourceRoot, ".pourkit", "prompts");
|
|
11406
12326
|
const tgtPrompts = path7.join(targetRoot, ".pourkit", "prompts");
|
|
11407
|
-
if (
|
|
12327
|
+
if (existsSync13(srcPrompts) && !existsSync13(tgtPrompts)) {
|
|
11408
12328
|
const promptFiles = await walkDir(srcPrompts);
|
|
11409
12329
|
for (const file of promptFiles) {
|
|
11410
12330
|
const relPath = path7.relative(srcPrompts, file);
|
|
@@ -11431,7 +12351,7 @@ async function planInit(options) {
|
|
|
11431
12351
|
".sandcastle",
|
|
11432
12352
|
"Dockerfile"
|
|
11433
12353
|
);
|
|
11434
|
-
if (
|
|
12354
|
+
if (existsSync13(tgtSandboxDockerfile)) {
|
|
11435
12355
|
operations.push({
|
|
11436
12356
|
kind: "skip",
|
|
11437
12357
|
path: tgtSandboxDockerfile,
|
|
@@ -11440,7 +12360,7 @@ async function planInit(options) {
|
|
|
11440
12360
|
requiresConfirmation: false,
|
|
11441
12361
|
destructive: false
|
|
11442
12362
|
});
|
|
11443
|
-
} else if (
|
|
12363
|
+
} else if (existsSync13(srcSandboxDockerfile)) {
|
|
11444
12364
|
const checksum = await computeFileChecksum(srcSandboxDockerfile);
|
|
11445
12365
|
operations.push({
|
|
11446
12366
|
kind: "copy",
|
|
@@ -11454,7 +12374,7 @@ async function planInit(options) {
|
|
|
11454
12374
|
});
|
|
11455
12375
|
}
|
|
11456
12376
|
const configTsPath = path7.join(targetRoot, "pourkit.config.ts");
|
|
11457
|
-
if (!
|
|
12377
|
+
if (!existsSync13(configTsPath)) {
|
|
11458
12378
|
const verifyCommands = inferVerificationCommands(
|
|
11459
12379
|
packageScripts,
|
|
11460
12380
|
pm || "npm"
|
|
@@ -11491,7 +12411,7 @@ async function planInit(options) {
|
|
|
11491
12411
|
const hasExistingAgents = operations.some(
|
|
11492
12412
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
|
|
11493
12413
|
);
|
|
11494
|
-
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !
|
|
12414
|
+
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync13(path7.join(targetRoot, "AGENTS.md"))) {
|
|
11495
12415
|
operations.push({
|
|
11496
12416
|
kind: "create",
|
|
11497
12417
|
path: path7.join(targetRoot, "AGENTS.md"),
|
|
@@ -11507,7 +12427,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
11507
12427
|
const hasExistingClaude = operations.some(
|
|
11508
12428
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
|
|
11509
12429
|
);
|
|
11510
|
-
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !
|
|
12430
|
+
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync13(path7.join(targetRoot, "CLAUDE.md"))) {
|
|
11511
12431
|
operations.push({
|
|
11512
12432
|
kind: "create",
|
|
11513
12433
|
path: path7.join(targetRoot, "CLAUDE.md"),
|
|
@@ -11522,7 +12442,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
11522
12442
|
}
|
|
11523
12443
|
const gitignoreTarget = path7.join(targetRoot, ".gitignore");
|
|
11524
12444
|
const gitignoreContent = generateGitignoreBlock();
|
|
11525
|
-
if (!
|
|
12445
|
+
if (!existsSync13(gitignoreTarget)) {
|
|
11526
12446
|
operations.push({
|
|
11527
12447
|
kind: "create",
|
|
11528
12448
|
path: gitignoreTarget,
|
|
@@ -11546,7 +12466,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
11546
12466
|
});
|
|
11547
12467
|
}
|
|
11548
12468
|
const openCodePath = path7.join(targetRoot, "opencode.json");
|
|
11549
|
-
if (!
|
|
12469
|
+
if (!existsSync13(openCodePath)) {
|
|
11550
12470
|
operations.push({
|
|
11551
12471
|
kind: "create",
|
|
11552
12472
|
path: openCodePath,
|
|
@@ -11603,7 +12523,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
11603
12523
|
}
|
|
11604
12524
|
}
|
|
11605
12525
|
const manifestPath = path7.join(targetRoot, ".pourkit", "manifest.json");
|
|
11606
|
-
if (
|
|
12526
|
+
if (existsSync13(manifestPath)) {
|
|
11607
12527
|
operations.push({
|
|
11608
12528
|
kind: "skip",
|
|
11609
12529
|
path: manifestPath,
|
|
@@ -11922,7 +12842,7 @@ async function updateManagedBlock(filePath, content) {
|
|
|
11922
12842
|
const blockContent = `${MANAGED_BLOCK_BEGIN}
|
|
11923
12843
|
${content}${MANAGED_BLOCK_END}
|
|
11924
12844
|
`;
|
|
11925
|
-
if (!
|
|
12845
|
+
if (!existsSync13(filePath)) {
|
|
11926
12846
|
const dir = path7.dirname(filePath);
|
|
11927
12847
|
await mkdir4(dir, { recursive: true });
|
|
11928
12848
|
await writeFileAtomic(filePath, blockContent);
|
|
@@ -11951,7 +12871,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
|
11951
12871
|
if (op.requiresConfirmation) continue;
|
|
11952
12872
|
const relPath = path7.relative(plan.targetRoot, op.path);
|
|
11953
12873
|
if (relPath === ".pourkit/manifest.json") continue;
|
|
11954
|
-
if (
|
|
12874
|
+
if (existsSync13(op.path)) {
|
|
11955
12875
|
const sha256 = await computeFileChecksum(op.path);
|
|
11956
12876
|
assets[relPath] = {
|
|
11957
12877
|
ownership: op.ownership || "managed",
|
|
@@ -11996,7 +12916,7 @@ async function applyInitPlan(plan, options) {
|
|
|
11996
12916
|
skipped++;
|
|
11997
12917
|
continue;
|
|
11998
12918
|
}
|
|
11999
|
-
if (
|
|
12919
|
+
if (existsSync13(op.path) && !op.destructive) {
|
|
12000
12920
|
skipped++;
|
|
12001
12921
|
continue;
|
|
12002
12922
|
}
|
|
@@ -12011,7 +12931,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12011
12931
|
skipped++;
|
|
12012
12932
|
continue;
|
|
12013
12933
|
}
|
|
12014
|
-
if (
|
|
12934
|
+
if (existsSync13(op.path)) {
|
|
12015
12935
|
skipped++;
|
|
12016
12936
|
continue;
|
|
12017
12937
|
}
|
|
@@ -12040,7 +12960,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12040
12960
|
skipped++;
|
|
12041
12961
|
continue;
|
|
12042
12962
|
}
|
|
12043
|
-
if (
|
|
12963
|
+
if (existsSync13(op.path)) {
|
|
12044
12964
|
skipped++;
|
|
12045
12965
|
continue;
|
|
12046
12966
|
}
|
|
@@ -12180,7 +13100,7 @@ async function applyInitFromSource(options) {
|
|
|
12180
13100
|
if (!manifestSkipped) {
|
|
12181
13101
|
const agentFiles = [];
|
|
12182
13102
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
12183
|
-
if (
|
|
13103
|
+
if (existsSync13(path7.join(targetRoot, name))) {
|
|
12184
13104
|
agentFiles.push(path7.join(targetRoot, name));
|
|
12185
13105
|
}
|
|
12186
13106
|
}
|
|
@@ -13411,11 +14331,46 @@ function createCliProgram(version) {
|
|
|
13411
14331
|
});
|
|
13412
14332
|
console.log(JSON.stringify(result, null, 2));
|
|
13413
14333
|
});
|
|
14334
|
+
prdRun.command("audit").argument("<prdRef>", "PRD ref").option("--cwd <path>", "target repository directory").action(async (prdRef, options) => {
|
|
14335
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
14336
|
+
const result = runPrdRunAuditCommand({
|
|
14337
|
+
repoRoot: targetRepoRoot,
|
|
14338
|
+
prdRef
|
|
14339
|
+
});
|
|
14340
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14341
|
+
});
|
|
13414
14342
|
prdRun.command("list").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
13415
14343
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
13416
14344
|
const result = runPrdRunListCommand({ repoRoot: targetRepoRoot });
|
|
13417
14345
|
console.log(JSON.stringify(result, null, 2));
|
|
13418
14346
|
});
|
|
14347
|
+
prdRun.command("validate-final-review").argument("<prdRef>", "PRD ref").requiredOption("--review-base <sha>", "merge-base SHA used for review").option("--checkout-base <ref>", "PRD Branch checkout base").option("--artifact-path <path>", "artifact path to validate").option(
|
|
14348
|
+
"--changed-path <path>",
|
|
14349
|
+
"changed path to validate for retouch scope (repeatable)",
|
|
14350
|
+
(value, previous) => {
|
|
14351
|
+
previous.push(value);
|
|
14352
|
+
return previous;
|
|
14353
|
+
},
|
|
14354
|
+
[]
|
|
14355
|
+
).option("--cwd <path>", "target repository directory").action(
|
|
14356
|
+
(prdRef, options) => {
|
|
14357
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
14358
|
+
const normalizedPrdRef = normalizePrdRef(prdRef);
|
|
14359
|
+
const artifactPath = options.artifactPath ? path8.isAbsolute(options.artifactPath) ? options.artifactPath : path8.resolve(targetRepoRoot, options.artifactPath) : void 0;
|
|
14360
|
+
const result = runPrdRunValidateFinalReviewCommand({
|
|
14361
|
+
repoRoot: targetRepoRoot,
|
|
14362
|
+
prdRef: normalizedPrdRef,
|
|
14363
|
+
checkoutBase: options.checkoutBase,
|
|
14364
|
+
reviewBase: options.reviewBase,
|
|
14365
|
+
artifactPath,
|
|
14366
|
+
changedPaths: options.changedPath
|
|
14367
|
+
});
|
|
14368
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14369
|
+
if (result.status === "invalid") {
|
|
14370
|
+
process.exitCode = 1;
|
|
14371
|
+
}
|
|
14372
|
+
}
|
|
14373
|
+
);
|
|
13419
14374
|
prdRun.command("final-review").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").option("--no-auto-merge", "disable auto-merge").action(
|
|
13420
14375
|
async (prdRef, options) => {
|
|
13421
14376
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
@@ -13463,6 +14418,39 @@ function createCliProgram(version) {
|
|
|
13463
14418
|
}
|
|
13464
14419
|
}
|
|
13465
14420
|
);
|
|
14421
|
+
prdRun.command("reconcile").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(
|
|
14422
|
+
async (prdRef, options) => {
|
|
14423
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
14424
|
+
const normalizedPrdRef = normalizePrdRef(prdRef);
|
|
14425
|
+
const logPath = path8.join(
|
|
14426
|
+
targetRepoRoot,
|
|
14427
|
+
".pourkit",
|
|
14428
|
+
"logs",
|
|
14429
|
+
"prd-run-reconcile.log"
|
|
14430
|
+
);
|
|
14431
|
+
const logger = createLogger("pourkit", logPath);
|
|
14432
|
+
try {
|
|
14433
|
+
const config = await loadRepoConfig(targetRepoRoot);
|
|
14434
|
+
const result = await runPrdRunReconcileCommand({
|
|
14435
|
+
repoRoot: targetRepoRoot,
|
|
14436
|
+
prdRef: normalizedPrdRef,
|
|
14437
|
+
targetName: options.target,
|
|
14438
|
+
config,
|
|
14439
|
+
logger
|
|
14440
|
+
});
|
|
14441
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14442
|
+
if (result.status === "blocked") {
|
|
14443
|
+
process.exitCode = 1;
|
|
14444
|
+
}
|
|
14445
|
+
} catch (error) {
|
|
14446
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
14447
|
+
console.error(`Error: ${msg}`);
|
|
14448
|
+
process.exit(1);
|
|
14449
|
+
} finally {
|
|
14450
|
+
await logger.close();
|
|
14451
|
+
}
|
|
14452
|
+
}
|
|
14453
|
+
);
|
|
13466
14454
|
program.command("init").description("Initialize .pourkit layout in a target repo").option("--dry-run", "print the init plan without applying changes").option("--json", "output machine-readable JSON plan").option("--from-local <path>", "local source repo to copy artifacts from").option("--cwd <path>", "target repository directory").addOption(
|
|
13467
14455
|
new Option(
|
|
13468
14456
|
"--docs-migration <mode>",
|
|
@@ -13624,11 +14612,11 @@ function createCliProgram(version) {
|
|
|
13624
14612
|
return program;
|
|
13625
14613
|
}
|
|
13626
14614
|
async function resolveCliVersion() {
|
|
13627
|
-
if (isPackageVersion("0.0.0-next-
|
|
13628
|
-
return "0.0.0-next-
|
|
14615
|
+
if (isPackageVersion("0.0.0-next-20260604225844")) {
|
|
14616
|
+
return "0.0.0-next-20260604225844";
|
|
13629
14617
|
}
|
|
13630
|
-
if (isReleaseVersion("0.0.0-next-
|
|
13631
|
-
return "0.0.0-next-
|
|
14618
|
+
if (isReleaseVersion("0.0.0-next-20260604225844")) {
|
|
14619
|
+
return "0.0.0-next-20260604225844";
|
|
13632
14620
|
}
|
|
13633
14621
|
try {
|
|
13634
14622
|
const root = repoRoot();
|