@pourkit/cli 0.0.0-next-20260607184355 → 0.0.0-next-20260608072322
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 +953 -385
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +443 -55
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -376,6 +376,7 @@ var VerificationCommandSchema = z.object({
|
|
|
376
376
|
command: d.command,
|
|
377
377
|
label: d.label && d.label.trim() !== "" ? d.label : void 0
|
|
378
378
|
}));
|
|
379
|
+
var PrdRunModeSchema = z.enum(["github", "local"]);
|
|
379
380
|
var QueueConfigSchema = z.object({
|
|
380
381
|
loop: z.boolean().optional()
|
|
381
382
|
}).strict();
|
|
@@ -422,6 +423,7 @@ var ReviewRefactorLoopStrategySchema = z.object({
|
|
|
422
423
|
maxAttempts: z.number().int().positive()
|
|
423
424
|
}).strict(),
|
|
424
425
|
prdRun: z.object({
|
|
426
|
+
mode: PrdRunModeSchema.optional(),
|
|
425
427
|
// Uses promptTemplate (canonical StageAgentConfig field), not prompt as Issue contract may suggest
|
|
426
428
|
finalReview: StageAgentConfigSchema,
|
|
427
429
|
reconciliation: StageAgentConfigSchema.optional()
|
|
@@ -715,6 +717,7 @@ function parseConfig(raw) {
|
|
|
715
717
|
},
|
|
716
718
|
...t.strategy.prdRun ? {
|
|
717
719
|
prdRun: {
|
|
720
|
+
...t.strategy.prdRun.mode ? { mode: t.strategy.prdRun.mode } : {},
|
|
718
721
|
finalReview: t.strategy.prdRun.finalReview,
|
|
719
722
|
...t.strategy.prdRun.reconciliation ? { reconciliation: t.strategy.prdRun.reconciliation } : {}
|
|
720
723
|
}
|
|
@@ -771,14 +774,24 @@ function assertKnownKeys(value, path9, knownKeys) {
|
|
|
771
774
|
function getVerificationCommands(target) {
|
|
772
775
|
return target.strategy.verify?.commands ?? [];
|
|
773
776
|
}
|
|
777
|
+
function resolvePrdRunMode(target, opts) {
|
|
778
|
+
if (opts?.localOverride === true) {
|
|
779
|
+
return { mode: "local", source: "cli-override" };
|
|
780
|
+
}
|
|
781
|
+
const configMode = target.strategy.prdRun?.mode;
|
|
782
|
+
if (configMode) {
|
|
783
|
+
return { mode: configMode, source: "target-config" };
|
|
784
|
+
}
|
|
785
|
+
return { mode: "github", source: "default" };
|
|
786
|
+
}
|
|
774
787
|
async function loadRepoConfig(repoRoot2, configFileName = "pourkit.config.ts") {
|
|
775
|
-
const { existsSync:
|
|
788
|
+
const { existsSync: existsSync20 } = await import("fs");
|
|
776
789
|
const { mkdir: mkdir6, writeFile: writeFile4, rm } = await import("fs/promises");
|
|
777
790
|
const { join: pjoin, basename } = await import("path");
|
|
778
791
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
779
792
|
const { build } = await import("esbuild");
|
|
780
793
|
const configPath = pjoin(repoRoot2, configFileName);
|
|
781
|
-
if (!
|
|
794
|
+
if (!existsSync20(configPath)) {
|
|
782
795
|
throw new Error(
|
|
783
796
|
`No config file found at ${configPath}. Create a ${configFileName} that exports a default PourkitConfig.`
|
|
784
797
|
);
|
|
@@ -1142,6 +1155,77 @@ function prepareArtifactPath(artifactPath) {
|
|
|
1142
1155
|
rmSync(artifactPath, { recursive: true, force: true });
|
|
1143
1156
|
}
|
|
1144
1157
|
|
|
1158
|
+
// commands/run-verification.ts
|
|
1159
|
+
init_common();
|
|
1160
|
+
function buildRunVerificationCommand(target) {
|
|
1161
|
+
return `pourkit run-verification --target ${target.name}`;
|
|
1162
|
+
}
|
|
1163
|
+
async function runVerificationCommand(command, cwd, logger) {
|
|
1164
|
+
try {
|
|
1165
|
+
const result = await execCapture("bash", ["-lc", command.command], {
|
|
1166
|
+
cwd,
|
|
1167
|
+
logger,
|
|
1168
|
+
label: command.label
|
|
1169
|
+
});
|
|
1170
|
+
return {
|
|
1171
|
+
label: command.label,
|
|
1172
|
+
command: command.command,
|
|
1173
|
+
ok: true,
|
|
1174
|
+
stdout: result.stdout,
|
|
1175
|
+
stderr: result.stderr
|
|
1176
|
+
};
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
return {
|
|
1179
|
+
label: command.label,
|
|
1180
|
+
command: command.command,
|
|
1181
|
+
ok: false,
|
|
1182
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
async function runVerificationCommands(options) {
|
|
1187
|
+
const commands = getVerificationCommands(options.target);
|
|
1188
|
+
const diagnostics = [];
|
|
1189
|
+
if (commands.length === 0) {
|
|
1190
|
+
diagnostics.push(
|
|
1191
|
+
`No verification commands configured for target "${options.target.name}".`
|
|
1192
|
+
);
|
|
1193
|
+
return {
|
|
1194
|
+
ok: true,
|
|
1195
|
+
target: options.target.name,
|
|
1196
|
+
commands: [],
|
|
1197
|
+
diagnostics
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
const results = [];
|
|
1201
|
+
for (const command of commands) {
|
|
1202
|
+
const result = await runVerificationCommand(
|
|
1203
|
+
command,
|
|
1204
|
+
options.cwd,
|
|
1205
|
+
options.logger
|
|
1206
|
+
);
|
|
1207
|
+
results.push(result);
|
|
1208
|
+
if (!result.ok) {
|
|
1209
|
+
diagnostics.push(`Verification failed for "${command.label}".`);
|
|
1210
|
+
return {
|
|
1211
|
+
ok: false,
|
|
1212
|
+
target: options.target.name,
|
|
1213
|
+
commands: results,
|
|
1214
|
+
diagnostics
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
diagnostics.push(
|
|
1219
|
+
`Verification passed for target "${options.target.name}".`
|
|
1220
|
+
);
|
|
1221
|
+
return {
|
|
1222
|
+
ok: true,
|
|
1223
|
+
target: options.target.name,
|
|
1224
|
+
commands: results,
|
|
1225
|
+
diagnostics
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1145
1229
|
// shared/run-context.ts
|
|
1146
1230
|
var RUN_CONTEXT_PATH_IN_WORKTREE = ".pourkit/.tmp/run-context.md";
|
|
1147
1231
|
var ALL_RUN_CONTEXT_SECTIONS = [
|
|
@@ -1268,6 +1352,7 @@ function buildRunContextMarkdown(options) {
|
|
|
1268
1352
|
if (sections.includes("verification-commands")) {
|
|
1269
1353
|
parts.push(
|
|
1270
1354
|
...renderCommandList(
|
|
1355
|
+
target,
|
|
1271
1356
|
getVerificationCommands(target),
|
|
1272
1357
|
"Verification Commands"
|
|
1273
1358
|
)
|
|
@@ -1289,13 +1374,26 @@ function buildRunContextMarkdown(options) {
|
|
|
1289
1374
|
}
|
|
1290
1375
|
return parts.join("\n");
|
|
1291
1376
|
}
|
|
1292
|
-
function renderCommandList(commands, heading) {
|
|
1377
|
+
function renderCommandList(target, commands, heading) {
|
|
1293
1378
|
if (commands.length === 0) {
|
|
1294
|
-
return [
|
|
1379
|
+
return [
|
|
1380
|
+
`## ${heading}`,
|
|
1381
|
+
"",
|
|
1382
|
+
`Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
|
|
1383
|
+
"",
|
|
1384
|
+
"Underlying project commands:",
|
|
1385
|
+
"",
|
|
1386
|
+
"(none configured)",
|
|
1387
|
+
""
|
|
1388
|
+
];
|
|
1295
1389
|
}
|
|
1296
1390
|
return [
|
|
1297
1391
|
`## ${heading}`,
|
|
1298
1392
|
"",
|
|
1393
|
+
`Run this command from the repository root: \`${buildRunVerificationCommand(target)}\``,
|
|
1394
|
+
"",
|
|
1395
|
+
"Underlying project commands:",
|
|
1396
|
+
"",
|
|
1299
1397
|
"Run these commands from the repository root exactly as written. Do not substitute equivalent scripts from nested package.json files.",
|
|
1300
1398
|
"",
|
|
1301
1399
|
...commands.map((command) => `- ${command.label}: \`${command.command}\``),
|
|
@@ -2079,7 +2177,7 @@ ${criteriaBlock}
|
|
|
2079
2177
|
|
|
2080
2178
|
Write your review to: ${artifactPathInWorktree}
|
|
2081
2179
|
|
|
2082
|
-
Before handoff, run:
|
|
2180
|
+
Before handoff, run: pourkit validate-artifact reviewer ${artifactPathInWorktree} --iteration ${iteration}${priorRefactorArtifacts ? " --prior-refactor-artifacts" : ""}
|
|
2083
2181
|
|
|
2084
2182
|
Do not provide a separate chat response. The runner only reads the file above.
|
|
2085
2183
|
|
|
@@ -2615,7 +2713,7 @@ ${latestReview.trimEnd()}
|
|
|
2615
2713
|
|
|
2616
2714
|
Write your refactor artifact to: ${artifactPathInWorktree}
|
|
2617
2715
|
|
|
2618
|
-
Before handoff, run:
|
|
2716
|
+
Before handoff, run: pourkit validate-artifact refactor ${artifactPathInWorktree} --iteration ${iteration}${findingArgs}
|
|
2619
2717
|
|
|
2620
2718
|
When you are done, finish with <promise>COMPLETE</promise>.`);
|
|
2621
2719
|
});
|
|
@@ -2867,8 +2965,9 @@ function parseConflictResolutionArtifact(output) {
|
|
|
2867
2965
|
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
2868
2966
|
import { dirname as dirname3, isAbsolute, join as join6, relative } from "path";
|
|
2869
2967
|
import { z as z2 } from "zod";
|
|
2968
|
+
var PRD_REF_FOUR_DIGIT = /^PRD-\d{4,}$/;
|
|
2870
2969
|
var PlanningArtifactManifestSchema = z2.object({
|
|
2871
|
-
prdRef: z2.string().regex(
|
|
2970
|
+
prdRef: z2.string().regex(PRD_REF_FOUR_DIGIT),
|
|
2872
2971
|
parentIssue: z2.object({
|
|
2873
2972
|
number: z2.number().int().positive(),
|
|
2874
2973
|
url: z2.string().url(),
|
|
@@ -2927,7 +3026,15 @@ function readPlanningArtifactManifest(repoRoot2, prdRef) {
|
|
|
2927
3026
|
if (!parsed.ok) {
|
|
2928
3027
|
return parsed;
|
|
2929
3028
|
}
|
|
2930
|
-
|
|
3029
|
+
const readiness = validatePlanningArtifactManifestReadiness(parsed.manifest);
|
|
3030
|
+
if (!readiness.ok) {
|
|
3031
|
+
return readiness;
|
|
3032
|
+
}
|
|
3033
|
+
return {
|
|
3034
|
+
ok: true,
|
|
3035
|
+
manifest: readiness.manifest,
|
|
3036
|
+
...location.locationDiagnostics?.length ? { warnings: location.locationDiagnostics } : {}
|
|
3037
|
+
};
|
|
2931
3038
|
}
|
|
2932
3039
|
function validatePlanningArtifactManifestAtPath(repoRoot2, manifestPath) {
|
|
2933
3040
|
let content;
|
|
@@ -2984,6 +3091,10 @@ function validatePlanningArtifactManifestReadiness(manifest) {
|
|
|
2984
3091
|
}
|
|
2985
3092
|
return { ok: true, manifest: schemaResult.data };
|
|
2986
3093
|
}
|
|
3094
|
+
function deriveOldFormatRef(prdRef) {
|
|
3095
|
+
const match = prdRef.match(/^PRD-0(\d{3})$/);
|
|
3096
|
+
return match ? `PRD-${match[1]}` : null;
|
|
3097
|
+
}
|
|
2987
3098
|
function findPlanningArtifactManifestLocation(repoRoot2, prdRef) {
|
|
2988
3099
|
const initiativesRoot = join6(
|
|
2989
3100
|
repoRoot2,
|
|
@@ -3010,26 +3121,35 @@ function findPlanningArtifactManifestLocation(repoRoot2, prdRef) {
|
|
|
3010
3121
|
}).filter((entry) => entry.isDirectory()).map((entry) => join6(initiativesRoot, entry.name)).sort();
|
|
3011
3122
|
const diagnostics = [];
|
|
3012
3123
|
const collatedOffendingPaths = [];
|
|
3124
|
+
const oldFormatRef = deriveOldFormatRef(prdRef);
|
|
3013
3125
|
for (const initiativeDir of initiativeDirs) {
|
|
3014
3126
|
const indexPath = join6(initiativeDir, "INDEX.md");
|
|
3015
3127
|
if (!existsSync5(indexPath)) continue;
|
|
3016
3128
|
const indexContent = readFileSync5(indexPath, "utf-8");
|
|
3017
|
-
const
|
|
3129
|
+
const lines = indexContent.split(/\r?\n/);
|
|
3130
|
+
const fourDigitLine = lines.find(
|
|
3018
3131
|
(line) => line.includes(prdRef) && line.includes("planning-manifest.md")
|
|
3019
3132
|
);
|
|
3133
|
+
const threeDigitLine = oldFormatRef ? lines.find(
|
|
3134
|
+
(line) => line.includes(oldFormatRef) && line.includes("planning-manifest.md")
|
|
3135
|
+
) : null;
|
|
3136
|
+
const manifestLine = fourDigitLine ?? threeDigitLine;
|
|
3020
3137
|
if (!manifestLine) continue;
|
|
3021
3138
|
const manifestPath = extractBacktickedPath(manifestLine);
|
|
3022
3139
|
if (!manifestPath) {
|
|
3023
3140
|
diagnostics.push(
|
|
3024
|
-
`INDEX.md matched ${prdRef} but did not expose a manifest path: ${toRepoRelativePath(repoRoot2, indexPath)}`
|
|
3141
|
+
`INDEX.md matched ${prdRef}${oldFormatRef ? ` (or ${oldFormatRef})` : ""} but did not expose a manifest path: ${toRepoRelativePath(repoRoot2, indexPath)}`
|
|
3025
3142
|
);
|
|
3026
3143
|
collatedOffendingPaths.push(toRepoRelativePath(repoRoot2, indexPath));
|
|
3027
3144
|
continue;
|
|
3028
3145
|
}
|
|
3029
|
-
const
|
|
3146
|
+
const expectedFourDigitPattern = new RegExp(
|
|
3030
3147
|
`^prds/${prdRef}-[^/]+/planning-manifest\\.md$`
|
|
3031
3148
|
);
|
|
3032
|
-
|
|
3149
|
+
const expectedThreeDigitPattern = oldFormatRef ? new RegExp(`^prds/${oldFormatRef}-[^/]+/planning-manifest\\.md$`) : null;
|
|
3150
|
+
const matchedFourDigit = expectedFourDigitPattern.test(manifestPath);
|
|
3151
|
+
const matchedThreeDigit = expectedThreeDigitPattern?.test(manifestPath) ?? false;
|
|
3152
|
+
if (!matchedFourDigit && !matchedThreeDigit) {
|
|
3033
3153
|
diagnostics.push(
|
|
3034
3154
|
`INDEX.md path does not match expected pattern prds/${prdRef}-<slug>/planning-manifest.md. Found: ${manifestPath} (${toRepoRelativePath(repoRoot2, indexPath)})`
|
|
3035
3155
|
);
|
|
@@ -3049,7 +3169,16 @@ function findPlanningArtifactManifestLocation(repoRoot2, prdRef) {
|
|
|
3049
3169
|
);
|
|
3050
3170
|
continue;
|
|
3051
3171
|
}
|
|
3052
|
-
|
|
3172
|
+
if (!matchedFourDigit && matchedThreeDigit) {
|
|
3173
|
+
diagnostics.push(
|
|
3174
|
+
`Old-format PRD ref ${oldFormatRef} found in INDEX.md at ${toRepoRelativePath(repoRoot2, indexPath)} for PRD ${prdRef}; should be migrated to ${prdRef}`
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
return {
|
|
3178
|
+
ok: true,
|
|
3179
|
+
manifestPath: absoluteManifestPath,
|
|
3180
|
+
...diagnostics.length > 0 ? { locationDiagnostics: [...diagnostics] } : {}
|
|
3181
|
+
};
|
|
3053
3182
|
}
|
|
3054
3183
|
if (diagnostics.length > 0) {
|
|
3055
3184
|
return {
|
|
@@ -3464,10 +3593,10 @@ function normalizePrdRunRef(ref) {
|
|
|
3464
3593
|
const match = trimmed.match(/^(?:PRD-)?(\d+)$/);
|
|
3465
3594
|
if (!match) {
|
|
3466
3595
|
throw new Error(
|
|
3467
|
-
`Invalid PRD ref "${ref}". Expected format: PRD-<number> (e.g., PRD-
|
|
3596
|
+
`Invalid PRD ref "${ref}". Expected format: PRD-<number> (e.g., PRD-0043)`
|
|
3468
3597
|
);
|
|
3469
3598
|
}
|
|
3470
|
-
return `PRD-${match[1].padStart(
|
|
3599
|
+
return `PRD-${match[1].padStart(4, "0")}`;
|
|
3471
3600
|
}
|
|
3472
3601
|
function normalizeRepoPath(pathValue) {
|
|
3473
3602
|
return pathValue.split("\\").join("/");
|
|
@@ -3867,8 +3996,8 @@ function validateLocalPrepare(prdPath, issuePaths, options) {
|
|
|
3867
3996
|
return { ...triageResult, gate: "triage_consistency" };
|
|
3868
3997
|
}
|
|
3869
3998
|
const protectedBranchNames = ["dev", "next", "main"];
|
|
3870
|
-
const barePrdPattern = /^PRD-\d{
|
|
3871
|
-
const localPrdBranchPattern = /^local\/PRD-\d{
|
|
3999
|
+
const barePrdPattern = /^PRD-\d{4}$/;
|
|
4000
|
+
const localPrdBranchPattern = /^local\/PRD-\d{4}$/;
|
|
3872
4001
|
const dirtyRaw = tryExecSync("git status --porcelain", options?.repoRoot);
|
|
3873
4002
|
if (dirtyRaw !== null && dirtyRaw.length > 0) {
|
|
3874
4003
|
return {
|
|
@@ -3903,7 +4032,7 @@ function validateLocalPrepare(prdPath, issuePaths, options) {
|
|
|
3903
4032
|
ok: false,
|
|
3904
4033
|
gate: "branch_safety",
|
|
3905
4034
|
failureCode: "branch_unsafe",
|
|
3906
|
-
message: `PRD localPrdBranch "${localPrdBranch}" matches bare PRD-
|
|
4035
|
+
message: `PRD localPrdBranch "${localPrdBranch}" matches bare PRD-0000N pattern without local/ prefix`
|
|
3907
4036
|
};
|
|
3908
4037
|
}
|
|
3909
4038
|
if (!localPrdBranchPattern.test(localPrdBranch)) {
|
|
@@ -3911,7 +4040,7 @@ function validateLocalPrepare(prdPath, issuePaths, options) {
|
|
|
3911
4040
|
ok: false,
|
|
3912
4041
|
gate: "branch_safety",
|
|
3913
4042
|
failureCode: "branch_unsafe",
|
|
3914
|
-
message: `PRD localPrdBranch "${localPrdBranch}" must match local/PRD-
|
|
4043
|
+
message: `PRD localPrdBranch "${localPrdBranch}" must match local/PRD-0000N pattern`
|
|
3915
4044
|
};
|
|
3916
4045
|
}
|
|
3917
4046
|
const branchExists = tryExecSync(
|
|
@@ -3965,7 +4094,7 @@ function validateLocalPrepare(prdPath, issuePaths, options) {
|
|
|
3965
4094
|
gate: "branch_safety",
|
|
3966
4095
|
failureCode: "branch_unsafe",
|
|
3967
4096
|
offendingPath: issuePath,
|
|
3968
|
-
message: `Issue branch "${branchName}" matches bare PRD-
|
|
4097
|
+
message: `Issue branch "${branchName}" matches bare PRD-0000N pattern without local/ prefix`
|
|
3969
4098
|
};
|
|
3970
4099
|
}
|
|
3971
4100
|
}
|
|
@@ -4409,12 +4538,12 @@ function validateLocalIssue(path9) {
|
|
|
4409
4538
|
message: "id must be a non-empty string"
|
|
4410
4539
|
};
|
|
4411
4540
|
}
|
|
4412
|
-
if (typeof data.parentPrdId !== "string" || !/^PRD-\d{
|
|
4541
|
+
if (typeof data.parentPrdId !== "string" || !/^PRD-\d{4}$/.test(data.parentPrdId.trim())) {
|
|
4413
4542
|
return {
|
|
4414
4543
|
ok: false,
|
|
4415
4544
|
failureCode: "invalid_issue_artifact",
|
|
4416
4545
|
offendingPath: path9,
|
|
4417
|
-
message: "parentPrdId must match PRD-
|
|
4546
|
+
message: "parentPrdId must match PRD-0000N format"
|
|
4418
4547
|
};
|
|
4419
4548
|
}
|
|
4420
4549
|
if (typeof data.title !== "string" || data.title.trim() === "") {
|
|
@@ -4507,12 +4636,12 @@ function validateLocalIssue(path9) {
|
|
|
4507
4636
|
message: "dependencyIssueIds must be an array"
|
|
4508
4637
|
};
|
|
4509
4638
|
}
|
|
4510
|
-
if (typeof data.branchName !== "string" || !/^local\/PRD-\d{
|
|
4639
|
+
if (typeof data.branchName !== "string" || !/^local\/PRD-\d{4}\/I-\d{2}-[a-z0-9-]+$/.test(data.branchName)) {
|
|
4511
4640
|
return {
|
|
4512
4641
|
ok: false,
|
|
4513
4642
|
failureCode: "invalid_issue_artifact",
|
|
4514
4643
|
offendingPath: path9,
|
|
4515
|
-
message: "branchName must match local/PRD-
|
|
4644
|
+
message: "branchName must match local/PRD-0000N/I-0N-slug pattern"
|
|
4516
4645
|
};
|
|
4517
4646
|
}
|
|
4518
4647
|
if (!(data.blockedReason === null || typeof data.blockedReason === "string")) {
|
|
@@ -4628,7 +4757,7 @@ function validateLocalIssue(path9) {
|
|
|
4628
4757
|
}
|
|
4629
4758
|
return { ok: true };
|
|
4630
4759
|
}
|
|
4631
|
-
var PRD_REF_PATTERN = /^PRD-\d
|
|
4760
|
+
var PRD_REF_PATTERN = /^PRD-\d{4}$/;
|
|
4632
4761
|
var EVIDENCE_HASH_PATTERN = /^[0-9a-f]{64}$/;
|
|
4633
4762
|
var RECONCILIATION_TMP_PATH_PREFIX = ".pourkit/.tmp/reconciliation/";
|
|
4634
4763
|
function validateReconciliationArtifact(artifact) {
|
|
@@ -4639,7 +4768,7 @@ function validateReconciliationArtifact(artifact) {
|
|
|
4639
4768
|
const a = artifact;
|
|
4640
4769
|
if (typeof a.prdRef !== "string" || !PRD_REF_PATTERN.test(a.prdRef)) {
|
|
4641
4770
|
errors.push(
|
|
4642
|
-
`prdRef must match PRD-
|
|
4771
|
+
`prdRef must match PRD-0000N format, got ${JSON.stringify(a.prdRef)}`
|
|
4643
4772
|
);
|
|
4644
4773
|
}
|
|
4645
4774
|
if (a.stage !== "prdReconciliation") {
|
|
@@ -4651,7 +4780,7 @@ function validateReconciliationArtifact(artifact) {
|
|
|
4651
4780
|
errors.push("checkoutBase must be a non-empty string");
|
|
4652
4781
|
} else if (!PRD_REF_PATTERN.test(a.checkoutBase.trim())) {
|
|
4653
4782
|
errors.push(
|
|
4654
|
-
`checkoutBase must match active PRD branch format PRD-
|
|
4783
|
+
`checkoutBase must match active PRD branch format PRD-0000N, got ${JSON.stringify(a.checkoutBase)}`
|
|
4655
4784
|
);
|
|
4656
4785
|
}
|
|
4657
4786
|
if (typeof a.mergeBase !== "string" || a.mergeBase.trim() === "") {
|
|
@@ -4680,6 +4809,19 @@ function validateReconciliationArtifact(artifact) {
|
|
|
4680
4809
|
);
|
|
4681
4810
|
}
|
|
4682
4811
|
}
|
|
4812
|
+
if (a.result === "no_changes_needed") {
|
|
4813
|
+
const paths = a.changedPlanningPaths ?? [];
|
|
4814
|
+
if (Array.isArray(paths) && paths.length > 0) {
|
|
4815
|
+
errors.push(
|
|
4816
|
+
`Reconciliation artifact result is "no_changes_needed" but changedPlanningPaths is not empty (${paths.length} paths).`
|
|
4817
|
+
);
|
|
4818
|
+
}
|
|
4819
|
+
if (!a.noChangeRationale || typeof a.noChangeRationale !== "string") {
|
|
4820
|
+
errors.push(
|
|
4821
|
+
'Reconciliation artifact result is "no_changes_needed" but noChangeRationale is missing or not a string.'
|
|
4822
|
+
);
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4683
4825
|
if (!Array.isArray(a.completionArtifactPaths) || !a.completionArtifactPaths.every((p) => typeof p === "string")) {
|
|
4684
4826
|
errors.push("completionArtifactPaths must be an array of strings");
|
|
4685
4827
|
}
|
|
@@ -4835,8 +4977,8 @@ function validateLocalTriage(prdPath, issuePaths) {
|
|
|
4835
4977
|
}
|
|
4836
4978
|
|
|
4837
4979
|
// commands/issue-run.ts
|
|
4838
|
-
import { existsSync as
|
|
4839
|
-
import { join as
|
|
4980
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
|
|
4981
|
+
import { join as join14 } from "path";
|
|
4840
4982
|
|
|
4841
4983
|
// pr/templates.ts
|
|
4842
4984
|
init_common();
|
|
@@ -5537,7 +5679,7 @@ async function runFailureResolutionAgent(options) {
|
|
|
5537
5679
|
"",
|
|
5538
5680
|
"Allowed decisions: " + packet.allowedDecisions.join(", "),
|
|
5539
5681
|
"",
|
|
5540
|
-
`Before handoff, run:
|
|
5682
|
+
`Before handoff, run: pourkit validate-artifact failure-resolution ${artifactPath} ${packet.allowedDecisions.map((decision) => `--allowed-decision ${decision}`).join(" ")}`
|
|
5541
5683
|
].join("\n");
|
|
5542
5684
|
const retryResult = await executeWithMissingOrEmptyArtifactRetry({
|
|
5543
5685
|
executionProvider,
|
|
@@ -5809,7 +5951,7 @@ Rules:
|
|
|
5809
5951
|
|
|
5810
5952
|
Write your finalizer output to: ${artifactPathInWorktree}
|
|
5811
5953
|
|
|
5812
|
-
Before handoff, run:
|
|
5954
|
+
Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktree}`;
|
|
5813
5955
|
}
|
|
5814
5956
|
|
|
5815
5957
|
// commands/pr-description-agent.ts
|
|
@@ -6010,6 +6152,293 @@ function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
|
6010
6152
|
});
|
|
6011
6153
|
}
|
|
6012
6154
|
|
|
6155
|
+
// prd-run/local-merge-coordinator.ts
|
|
6156
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
6157
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
6158
|
+
import { join as join13 } from "path";
|
|
6159
|
+
|
|
6160
|
+
// prd-run/local-branches.ts
|
|
6161
|
+
import { execFileSync } from "child_process";
|
|
6162
|
+
function getLocalPrdBranchName(prdId) {
|
|
6163
|
+
return `local/${prdId}`;
|
|
6164
|
+
}
|
|
6165
|
+
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
|
|
6166
|
+
var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{4}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
|
|
6167
|
+
function validateLocalBranchName(name) {
|
|
6168
|
+
if (!name || name.length === 0) {
|
|
6169
|
+
return {
|
|
6170
|
+
ok: false,
|
|
6171
|
+
failureCode: "invalid_format",
|
|
6172
|
+
message: "Branch name is empty."
|
|
6173
|
+
};
|
|
6174
|
+
}
|
|
6175
|
+
if (isProtectedBranch(name)) {
|
|
6176
|
+
return {
|
|
6177
|
+
ok: false,
|
|
6178
|
+
failureCode: "protected_branch",
|
|
6179
|
+
message: `Branch "${name}" is protected.`
|
|
6180
|
+
};
|
|
6181
|
+
}
|
|
6182
|
+
if (!LOCAL_BRANCH_PATTERN.test(name)) {
|
|
6183
|
+
return {
|
|
6184
|
+
ok: false,
|
|
6185
|
+
failureCode: "invalid_format",
|
|
6186
|
+
message: `Branch "${name}" does not match the local branch pattern.`
|
|
6187
|
+
};
|
|
6188
|
+
}
|
|
6189
|
+
return { ok: true };
|
|
6190
|
+
}
|
|
6191
|
+
function isProtectedBranch(name) {
|
|
6192
|
+
return PROTECTED_BRANCHES.has(name);
|
|
6193
|
+
}
|
|
6194
|
+
function hasRemoteBackedCollision(localName, repoRoot2) {
|
|
6195
|
+
const match = localName.match(/^local\/(PRD-\d{4})/);
|
|
6196
|
+
if (!match) {
|
|
6197
|
+
return Promise.resolve({
|
|
6198
|
+
ok: false,
|
|
6199
|
+
failureCode: "invalid_format",
|
|
6200
|
+
message: `Cannot extract PRD ref from "${localName}".`
|
|
6201
|
+
});
|
|
6202
|
+
}
|
|
6203
|
+
const prdRef = match[1];
|
|
6204
|
+
try {
|
|
6205
|
+
execFileSync(
|
|
6206
|
+
"git",
|
|
6207
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${prdRef}`],
|
|
6208
|
+
{
|
|
6209
|
+
cwd: repoRoot2 ?? process.cwd(),
|
|
6210
|
+
encoding: "utf8",
|
|
6211
|
+
stdio: "pipe"
|
|
6212
|
+
}
|
|
6213
|
+
);
|
|
6214
|
+
return Promise.resolve({
|
|
6215
|
+
ok: false,
|
|
6216
|
+
failureCode: "remote_backed_collision",
|
|
6217
|
+
message: `Branch "${prdRef}" exists locally.`
|
|
6218
|
+
});
|
|
6219
|
+
} catch {
|
|
6220
|
+
}
|
|
6221
|
+
try {
|
|
6222
|
+
execFileSync(
|
|
6223
|
+
"git",
|
|
6224
|
+
["show-ref", "--verify", "--quiet", `refs/remotes/origin/${prdRef}`],
|
|
6225
|
+
{
|
|
6226
|
+
cwd: repoRoot2 ?? process.cwd(),
|
|
6227
|
+
encoding: "utf8",
|
|
6228
|
+
stdio: "pipe"
|
|
6229
|
+
}
|
|
6230
|
+
);
|
|
6231
|
+
return Promise.resolve({
|
|
6232
|
+
ok: false,
|
|
6233
|
+
failureCode: "remote_backed_collision",
|
|
6234
|
+
message: `Remote branch "origin/${prdRef}" exists.`
|
|
6235
|
+
});
|
|
6236
|
+
} catch {
|
|
6237
|
+
}
|
|
6238
|
+
return Promise.resolve({ ok: true });
|
|
6239
|
+
}
|
|
6240
|
+
|
|
6241
|
+
// prd-run/local-merge-coordinator.ts
|
|
6242
|
+
function getLocalStorePath(repoRoot2, prdId) {
|
|
6243
|
+
return join13(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
6244
|
+
}
|
|
6245
|
+
function getMergeReceiptPath(repoRoot2, prdId, issueId) {
|
|
6246
|
+
return join13(
|
|
6247
|
+
getLocalStorePath(repoRoot2, prdId),
|
|
6248
|
+
"merge-receipts",
|
|
6249
|
+
`${issueId}.json`
|
|
6250
|
+
);
|
|
6251
|
+
}
|
|
6252
|
+
function getIssueArtifactPath(repoRoot2, prdId, issueId) {
|
|
6253
|
+
return join13(getLocalStorePath(repoRoot2, prdId), "issues", `${issueId}.json`);
|
|
6254
|
+
}
|
|
6255
|
+
function readIssueBranchName(repoRoot2, prdId, issueId) {
|
|
6256
|
+
const issuePath = getIssueArtifactPath(repoRoot2, prdId, issueId);
|
|
6257
|
+
if (!existsSync9(issuePath)) return null;
|
|
6258
|
+
try {
|
|
6259
|
+
const content = readFileSync11(issuePath, "utf-8");
|
|
6260
|
+
const parsed = JSON.parse(content);
|
|
6261
|
+
return typeof parsed.branchName === "string" && parsed.branchName ? parsed.branchName : null;
|
|
6262
|
+
} catch {
|
|
6263
|
+
return null;
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
async function hasLocalIssueMergeReceipt(prdId, issueId, repoRoot2) {
|
|
6267
|
+
const root = repoRoot2 ?? process.cwd();
|
|
6268
|
+
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
6269
|
+
if (!existsSync9(receiptPath)) return null;
|
|
6270
|
+
try {
|
|
6271
|
+
const content = readFileSync11(receiptPath, "utf-8");
|
|
6272
|
+
const parsed = JSON.parse(content);
|
|
6273
|
+
if (typeof parsed.prdId === "string" && typeof parsed.issueId === "string" && typeof parsed.stage === "string" && typeof parsed.sourceBranch === "string" && typeof parsed.localPrdBranch === "string" && typeof parsed.mergeCommit === "string" && typeof parsed.completedAt === "string") {
|
|
6274
|
+
return parsed;
|
|
6275
|
+
}
|
|
6276
|
+
return null;
|
|
6277
|
+
} catch {
|
|
6278
|
+
return null;
|
|
6279
|
+
}
|
|
6280
|
+
}
|
|
6281
|
+
async function squashMergeLocalIssue(prdId, issueId, input, repoRoot2) {
|
|
6282
|
+
const root = repoRoot2 ?? process.cwd();
|
|
6283
|
+
const receiptPath = getMergeReceiptPath(root, prdId, issueId);
|
|
6284
|
+
if (existsSync9(receiptPath)) {
|
|
6285
|
+
try {
|
|
6286
|
+
const existing = JSON.parse(
|
|
6287
|
+
readFileSync11(receiptPath, "utf-8")
|
|
6288
|
+
);
|
|
6289
|
+
if (existing.prdId === prdId && existing.issueId === issueId && existing.mergeCommit) {
|
|
6290
|
+
return {
|
|
6291
|
+
ok: false,
|
|
6292
|
+
failureCode: "already_merged",
|
|
6293
|
+
repairGuidance: `Issue ${issueId} was already merged into ${existing.localPrdBranch}. Merge commit: ${existing.mergeCommit}`
|
|
6294
|
+
};
|
|
6295
|
+
}
|
|
6296
|
+
return {
|
|
6297
|
+
ok: false,
|
|
6298
|
+
failureCode: "invalid_receipt",
|
|
6299
|
+
repairGuidance: `Merge receipt for ${issueId} belongs to different prd/issue. Remove it manually: ${receiptPath}`
|
|
6300
|
+
};
|
|
6301
|
+
} catch {
|
|
6302
|
+
return {
|
|
6303
|
+
ok: false,
|
|
6304
|
+
failureCode: "invalid_receipt",
|
|
6305
|
+
repairGuidance: `Merge receipt for ${issueId} is corrupted. Remove it manually: ${receiptPath}`
|
|
6306
|
+
};
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
const sourceBranch = input?.sourceBranch ?? readIssueBranchName(root, prdId, issueId);
|
|
6310
|
+
if (!sourceBranch) {
|
|
6311
|
+
return {
|
|
6312
|
+
ok: false,
|
|
6313
|
+
failureCode: "not_found",
|
|
6314
|
+
repairGuidance: `No source branch found for issue ${issueId} under PRD ${prdId}. Ensure the issue artifact exists with a valid branchName field.`
|
|
6315
|
+
};
|
|
6316
|
+
}
|
|
6317
|
+
try {
|
|
6318
|
+
execFileSync2(
|
|
6319
|
+
"git",
|
|
6320
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${sourceBranch}`],
|
|
6321
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6322
|
+
);
|
|
6323
|
+
} catch {
|
|
6324
|
+
return {
|
|
6325
|
+
ok: false,
|
|
6326
|
+
failureCode: "not_found",
|
|
6327
|
+
repairGuidance: `Source branch "${sourceBranch}" does not exist locally. Check that the branch was created and not deleted.`
|
|
6328
|
+
};
|
|
6329
|
+
}
|
|
6330
|
+
const targetBranch = getLocalPrdBranchName(prdId);
|
|
6331
|
+
try {
|
|
6332
|
+
execFileSync2(
|
|
6333
|
+
"git",
|
|
6334
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
|
|
6335
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6336
|
+
);
|
|
6337
|
+
} catch {
|
|
6338
|
+
return {
|
|
6339
|
+
ok: false,
|
|
6340
|
+
failureCode: "not_found",
|
|
6341
|
+
repairGuidance: `Local PRD branch "${targetBranch}" does not exist. Run \`prd-run start\` first to create it.`
|
|
6342
|
+
};
|
|
6343
|
+
}
|
|
6344
|
+
try {
|
|
6345
|
+
execFileSync2("git", ["checkout", targetBranch], {
|
|
6346
|
+
cwd: root,
|
|
6347
|
+
encoding: "utf8",
|
|
6348
|
+
stdio: "pipe"
|
|
6349
|
+
});
|
|
6350
|
+
let preMergeHead;
|
|
6351
|
+
try {
|
|
6352
|
+
preMergeHead = execFileSync2("git", ["rev-parse", "HEAD"], {
|
|
6353
|
+
cwd: root,
|
|
6354
|
+
encoding: "utf8",
|
|
6355
|
+
stdio: "pipe"
|
|
6356
|
+
}).trim();
|
|
6357
|
+
} catch {
|
|
6358
|
+
return {
|
|
6359
|
+
ok: false,
|
|
6360
|
+
failureCode: "merge_error",
|
|
6361
|
+
repairGuidance: "Failed to read current HEAD before merge."
|
|
6362
|
+
};
|
|
6363
|
+
}
|
|
6364
|
+
execFileSync2("git", ["merge", "--squash", sourceBranch], {
|
|
6365
|
+
cwd: root,
|
|
6366
|
+
encoding: "utf8",
|
|
6367
|
+
stdio: "pipe"
|
|
6368
|
+
});
|
|
6369
|
+
if (input) {
|
|
6370
|
+
execFileSync2(
|
|
6371
|
+
"git",
|
|
6372
|
+
["commit", "-m", input.finalizerTitle, "-m", input.finalizerBody],
|
|
6373
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6374
|
+
);
|
|
6375
|
+
} else {
|
|
6376
|
+
execFileSync2(
|
|
6377
|
+
"git",
|
|
6378
|
+
["commit", "-m", `Squash merge ${sourceBranch} into ${targetBranch}`],
|
|
6379
|
+
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
6380
|
+
);
|
|
6381
|
+
}
|
|
6382
|
+
const revParseResult = execFileSync2("git", ["rev-parse", "HEAD"], {
|
|
6383
|
+
cwd: root,
|
|
6384
|
+
encoding: "utf8",
|
|
6385
|
+
stdio: "pipe"
|
|
6386
|
+
});
|
|
6387
|
+
const mergeCommit = revParseResult.trim();
|
|
6388
|
+
const receipt = {
|
|
6389
|
+
prdId,
|
|
6390
|
+
issueId,
|
|
6391
|
+
stage: "child-issue",
|
|
6392
|
+
sourceBranch,
|
|
6393
|
+
localPrdBranch: targetBranch,
|
|
6394
|
+
mergeCommit,
|
|
6395
|
+
finalizerArtifactPath: input?.finalizerArtifactPath ?? "",
|
|
6396
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6397
|
+
};
|
|
6398
|
+
try {
|
|
6399
|
+
const receiptsDir = join13(
|
|
6400
|
+
root,
|
|
6401
|
+
".pourkit",
|
|
6402
|
+
"local-prd-runs",
|
|
6403
|
+
prdId,
|
|
6404
|
+
"merge-receipts"
|
|
6405
|
+
);
|
|
6406
|
+
mkdirSync7(receiptsDir, { recursive: true });
|
|
6407
|
+
writeFileSync4(receiptPath, JSON.stringify(receipt, null, 2), "utf-8");
|
|
6408
|
+
} catch {
|
|
6409
|
+
try {
|
|
6410
|
+
execFileSync2("git", ["reset", "--hard", preMergeHead], {
|
|
6411
|
+
cwd: root,
|
|
6412
|
+
encoding: "utf8",
|
|
6413
|
+
stdio: "pipe"
|
|
6414
|
+
});
|
|
6415
|
+
} catch {
|
|
6416
|
+
}
|
|
6417
|
+
return {
|
|
6418
|
+
ok: false,
|
|
6419
|
+
failureCode: "receipt_error",
|
|
6420
|
+
repairGuidance: "Failed to write merge receipt. Check disk space and permissions on .pourkit/local-prd-runs/. The merge commit has been rolled back."
|
|
6421
|
+
};
|
|
6422
|
+
}
|
|
6423
|
+
return { ok: true, receipt };
|
|
6424
|
+
} catch (error) {
|
|
6425
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6426
|
+
if (message.toLowerCase().includes("conflict")) {
|
|
6427
|
+
return {
|
|
6428
|
+
ok: false,
|
|
6429
|
+
failureCode: "conflict",
|
|
6430
|
+
repairGuidance: "Merge conflict during squash. Resolve conflicts in the working tree, then retry."
|
|
6431
|
+
};
|
|
6432
|
+
}
|
|
6433
|
+
const stderr = error instanceof Error && "stderr" in error ? error.stderr : void 0;
|
|
6434
|
+
return {
|
|
6435
|
+
ok: false,
|
|
6436
|
+
failureCode: "merge_error",
|
|
6437
|
+
repairGuidance: stderr ? `Merge failed: ${stderr}` : `Merge failed: ${message}`
|
|
6438
|
+
};
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
|
|
6013
6442
|
// pr/pr-body.ts
|
|
6014
6443
|
import { readFile as readFile2 } from "fs/promises";
|
|
6015
6444
|
var DEFAULT_MANUAL_PR_BODY = `## Summary
|
|
@@ -6379,7 +6808,8 @@ function parseAffectedCodePaths(body) {
|
|
|
6379
6808
|
return Array.from(paths);
|
|
6380
6809
|
}
|
|
6381
6810
|
function normalizeParentRef(ref) {
|
|
6382
|
-
|
|
6811
|
+
if (!ref) return void 0;
|
|
6812
|
+
return ref.trim().toUpperCase().replace(/^(PRD-)(\d+)$/, (_, p, n) => `${p}${n.padStart(4, "0")}`);
|
|
6383
6813
|
}
|
|
6384
6814
|
function looksLikeRepoPath(value) {
|
|
6385
6815
|
return /[./][A-Za-z0-9_-]/.test(value) && !value.includes(":");
|
|
@@ -6741,44 +7171,75 @@ async function completeIssueRun(options) {
|
|
|
6741
7171
|
let prTitle = issue.title;
|
|
6742
7172
|
let prBody;
|
|
6743
7173
|
let finalizerResult;
|
|
7174
|
+
const isLocalModeForFinalizer = options.prdRunMode?.mode === "local";
|
|
6744
7175
|
const finalizerFromState = worktreeState?.finalizer?.completed ? worktreeState.finalizer : null;
|
|
6745
7176
|
if (finalizerFromState) {
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
if (
|
|
7177
|
+
try {
|
|
7178
|
+
if (finalizerFromState.title && finalizerFromState.body) {
|
|
7179
|
+
prTitle = finalizerFromState.title;
|
|
7180
|
+
prBody = finalizerFromState.body;
|
|
7181
|
+
} else if (finalizerFromState.artifactPath) {
|
|
7182
|
+
if (!existsSync10(finalizerFromState.artifactPath)) {
|
|
7183
|
+
throw new FinalizerFailure({
|
|
7184
|
+
message: `Finalizer artifact missing at ${finalizerFromState.artifactPath}`
|
|
7185
|
+
});
|
|
7186
|
+
}
|
|
7187
|
+
const artifactContent = readFileSync12(
|
|
7188
|
+
finalizerFromState.artifactPath,
|
|
7189
|
+
"utf-8"
|
|
7190
|
+
);
|
|
7191
|
+
const parsed = parsePrDescription(artifactContent);
|
|
7192
|
+
prTitle = parsed.title;
|
|
7193
|
+
prBody = parsed.body;
|
|
7194
|
+
} else {
|
|
6751
7195
|
throw new FinalizerFailure({
|
|
6752
|
-
message:
|
|
7196
|
+
message: "Finalizer state is incomplete: missing title, body, and artifactPath"
|
|
6753
7197
|
});
|
|
6754
7198
|
}
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
7199
|
+
} catch (error) {
|
|
7200
|
+
if (isLocalModeForFinalizer) {
|
|
7201
|
+
return {
|
|
7202
|
+
branchName,
|
|
7203
|
+
target,
|
|
7204
|
+
issue,
|
|
7205
|
+
noOp: false,
|
|
7206
|
+
mode: "local",
|
|
7207
|
+
failureCode: "finalizer_failed",
|
|
7208
|
+
repairGuidance: error instanceof Error ? error.message : "Finalizer state is invalid"
|
|
7209
|
+
};
|
|
7210
|
+
}
|
|
7211
|
+
throw error;
|
|
6766
7212
|
}
|
|
6767
7213
|
} else {
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
7214
|
+
try {
|
|
7215
|
+
finalizerResult = await runEffectAndMapExit(
|
|
7216
|
+
runFinalizerAgent({
|
|
7217
|
+
executionProvider,
|
|
7218
|
+
config,
|
|
7219
|
+
target,
|
|
7220
|
+
issue,
|
|
7221
|
+
builderBranch: branchName,
|
|
7222
|
+
targetBaseBranch: effectiveBaseBranch,
|
|
7223
|
+
worktreePath: executionResult.worktreePath,
|
|
7224
|
+
reviewArtifactPath,
|
|
7225
|
+
repoRoot: ROOT,
|
|
7226
|
+
logger
|
|
7227
|
+
})
|
|
7228
|
+
);
|
|
7229
|
+
} catch (error) {
|
|
7230
|
+
if (isLocalModeForFinalizer) {
|
|
7231
|
+
return {
|
|
7232
|
+
branchName,
|
|
7233
|
+
target,
|
|
7234
|
+
issue,
|
|
7235
|
+
noOp: false,
|
|
7236
|
+
mode: "local",
|
|
7237
|
+
failureCode: "finalizer_failed",
|
|
7238
|
+
repairGuidance: error instanceof Error ? error.message : "Finalizer execution failed"
|
|
7239
|
+
};
|
|
7240
|
+
}
|
|
7241
|
+
throw error;
|
|
7242
|
+
}
|
|
6782
7243
|
prTitle = finalizerResult.title;
|
|
6783
7244
|
prBody = finalizerResult.body;
|
|
6784
7245
|
}
|
|
@@ -6823,6 +7284,113 @@ async function completeIssueRun(options) {
|
|
|
6823
7284
|
});
|
|
6824
7285
|
}
|
|
6825
7286
|
}
|
|
7287
|
+
const isLocalMode = options.prdRunMode?.mode === "local";
|
|
7288
|
+
if (isLocalMode) {
|
|
7289
|
+
const parsed = parseStackedIssue(issue.title, issue.body);
|
|
7290
|
+
const prdId = parsed.parentRef;
|
|
7291
|
+
if (!prdId) {
|
|
7292
|
+
return {
|
|
7293
|
+
branchName,
|
|
7294
|
+
target,
|
|
7295
|
+
issue,
|
|
7296
|
+
noOp: false,
|
|
7297
|
+
mode: "local",
|
|
7298
|
+
failureCode: "finalizer_failed",
|
|
7299
|
+
repairGuidance: "Local mode requires a PRD reference (## Parent) in the issue body or title. Add the parent PRD reference and retry."
|
|
7300
|
+
};
|
|
7301
|
+
}
|
|
7302
|
+
const finalizerFromWorktreeState = worktreeState?.finalizer;
|
|
7303
|
+
if (finalizerFromWorktreeState && !finalizerFromWorktreeState.completed) {
|
|
7304
|
+
return {
|
|
7305
|
+
branchName,
|
|
7306
|
+
target,
|
|
7307
|
+
issue,
|
|
7308
|
+
noOp: false,
|
|
7309
|
+
mode: "local",
|
|
7310
|
+
failureCode: "finalizer_failed",
|
|
7311
|
+
repairGuidance: "Finalizer did not complete on previous run. Re-run the issue finalizer and try again."
|
|
7312
|
+
};
|
|
7313
|
+
}
|
|
7314
|
+
if (!prTitle || !finalBody) {
|
|
7315
|
+
return {
|
|
7316
|
+
branchName,
|
|
7317
|
+
target,
|
|
7318
|
+
issue,
|
|
7319
|
+
noOp: false,
|
|
7320
|
+
mode: "local",
|
|
7321
|
+
failureCode: "finalizer_failed",
|
|
7322
|
+
repairGuidance: "Finalizer produced empty title or body. Re-run the issue finalizer and try again."
|
|
7323
|
+
};
|
|
7324
|
+
}
|
|
7325
|
+
const existingReceipt = await hasLocalIssueMergeReceipt(
|
|
7326
|
+
prdId,
|
|
7327
|
+
`issue-${issueNumber}`,
|
|
7328
|
+
ROOT
|
|
7329
|
+
);
|
|
7330
|
+
if (existingReceipt) {
|
|
7331
|
+
return {
|
|
7332
|
+
branchName,
|
|
7333
|
+
target,
|
|
7334
|
+
issue,
|
|
7335
|
+
noOp: false,
|
|
7336
|
+
mode: "local",
|
|
7337
|
+
failureCode: "already_merged",
|
|
7338
|
+
localPrdBranch: existingReceipt.localPrdBranch,
|
|
7339
|
+
mergeCommit: existingReceipt.mergeCommit,
|
|
7340
|
+
receiptPath: join14(
|
|
7341
|
+
ROOT,
|
|
7342
|
+
".pourkit",
|
|
7343
|
+
"local-prd-runs",
|
|
7344
|
+
prdId,
|
|
7345
|
+
"merge-receipts",
|
|
7346
|
+
`issue-${issueNumber}.json`
|
|
7347
|
+
)
|
|
7348
|
+
};
|
|
7349
|
+
}
|
|
7350
|
+
const mergeResult = await squashMergeLocalIssue(
|
|
7351
|
+
prdId,
|
|
7352
|
+
`issue-${issueNumber}`,
|
|
7353
|
+
{
|
|
7354
|
+
finalizerTitle: prTitle,
|
|
7355
|
+
finalizerBody: finalBody,
|
|
7356
|
+
finalizerArtifactPath: worktreeState?.finalizer?.artifactPath ?? "",
|
|
7357
|
+
sourceBranch: branchName
|
|
7358
|
+
},
|
|
7359
|
+
ROOT
|
|
7360
|
+
);
|
|
7361
|
+
if (!mergeResult.ok) {
|
|
7362
|
+
return {
|
|
7363
|
+
branchName,
|
|
7364
|
+
target,
|
|
7365
|
+
issue,
|
|
7366
|
+
noOp: false,
|
|
7367
|
+
mode: "local",
|
|
7368
|
+
failureCode: mergeResult.failureCode,
|
|
7369
|
+
repairGuidance: mergeResult.repairGuidance ?? `Local squash-merge failed: ${mergeResult.failureCode}`
|
|
7370
|
+
};
|
|
7371
|
+
}
|
|
7372
|
+
await issueProvider.removeLabel(
|
|
7373
|
+
issueNumber,
|
|
7374
|
+
config.labels.agentInProgress
|
|
7375
|
+
);
|
|
7376
|
+
return {
|
|
7377
|
+
branchName,
|
|
7378
|
+
target,
|
|
7379
|
+
issue,
|
|
7380
|
+
noOp: false,
|
|
7381
|
+
mode: "local",
|
|
7382
|
+
localPrdBranch: getLocalPrdBranchName(prdId),
|
|
7383
|
+
mergeCommit: mergeResult.receipt.mergeCommit,
|
|
7384
|
+
receiptPath: join14(
|
|
7385
|
+
ROOT,
|
|
7386
|
+
".pourkit",
|
|
7387
|
+
"local-prd-runs",
|
|
7388
|
+
prdId,
|
|
7389
|
+
"merge-receipts",
|
|
7390
|
+
`issue-${issueNumber}.json`
|
|
7391
|
+
)
|
|
7392
|
+
};
|
|
7393
|
+
}
|
|
6826
7394
|
let pr;
|
|
6827
7395
|
if (worktreeState?.pr?.merged) {
|
|
6828
7396
|
mergeCompleted = true;
|
|
@@ -7264,7 +7832,7 @@ async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
|
|
|
7264
7832
|
return { mode: "new", branchName, baseRef };
|
|
7265
7833
|
}
|
|
7266
7834
|
function issueWorktreePath(root, branchName) {
|
|
7267
|
-
return
|
|
7835
|
+
return join14(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
|
|
7268
7836
|
}
|
|
7269
7837
|
function resolveRegisteredIssueWorktreePath(worktreeListPorcelain, root, branchName) {
|
|
7270
7838
|
const branchWorktreePath = parseWorktreeListPorcelain(
|
|
@@ -7292,7 +7860,7 @@ async function syncTargetBranch(root, baseBranch, logger) {
|
|
|
7292
7860
|
}
|
|
7293
7861
|
function loadBuilderPrompt(repoRoot2, promptTemplate) {
|
|
7294
7862
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
7295
|
-
const promptBody =
|
|
7863
|
+
const promptBody = existsSync10(promptPath) ? readFileSync12(promptPath, "utf-8") : promptTemplate;
|
|
7296
7864
|
return appendProtectedWorkGuidance(`${promptBody}
|
|
7297
7865
|
|
|
7298
7866
|
## Shared Run Context
|
|
@@ -7735,33 +8303,36 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
|
|
|
7735
8303
|
// commands/prd-run.ts
|
|
7736
8304
|
import {
|
|
7737
8305
|
cpSync,
|
|
7738
|
-
existsSync as
|
|
8306
|
+
existsSync as existsSync17,
|
|
7739
8307
|
lstatSync,
|
|
7740
|
-
mkdirSync as
|
|
8308
|
+
mkdirSync as mkdirSync11,
|
|
7741
8309
|
mkdtempSync,
|
|
7742
8310
|
readdirSync as readdirSync6,
|
|
7743
|
-
readFileSync as
|
|
8311
|
+
readFileSync as readFileSync16,
|
|
7744
8312
|
realpathSync,
|
|
7745
8313
|
rmSync as rmSync3
|
|
7746
8314
|
} from "fs";
|
|
7747
8315
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
7748
|
-
import { dirname as dirname5, join as
|
|
8316
|
+
import { dirname as dirname5, join as join20, relative as relative2 } from "path";
|
|
7749
8317
|
import { tmpdir } from "os";
|
|
7750
8318
|
|
|
7751
8319
|
// prd-run/state.ts
|
|
7752
8320
|
import {
|
|
7753
|
-
existsSync as
|
|
7754
|
-
mkdirSync as
|
|
7755
|
-
readFileSync as
|
|
8321
|
+
existsSync as existsSync11,
|
|
8322
|
+
mkdirSync as mkdirSync8,
|
|
8323
|
+
readFileSync as readFileSync13,
|
|
7756
8324
|
readdirSync as readdirSync4,
|
|
7757
|
-
writeFileSync as
|
|
8325
|
+
writeFileSync as writeFileSync5
|
|
7758
8326
|
} from "fs";
|
|
7759
8327
|
import { mkdir as mkdir4, readFile as readFile4, writeFile } from "fs/promises";
|
|
7760
|
-
import { join as
|
|
8328
|
+
import { join as join15 } from "path";
|
|
7761
8329
|
import { z as z3 } from "zod";
|
|
7762
8330
|
var PRD_RUN_STATE_DIR = ".pourkit/prd-runs";
|
|
7763
8331
|
var PrdRunRecordSchema = z3.object({
|
|
7764
|
-
prdRef: z3.string().regex(
|
|
8332
|
+
prdRef: z3.string().regex(
|
|
8333
|
+
/^PRD-\d{4}$/,
|
|
8334
|
+
"PRD ref must use four-digit format (e.g., PRD-0052)"
|
|
8335
|
+
),
|
|
7765
8336
|
status: z3.enum([
|
|
7766
8337
|
"blocked",
|
|
7767
8338
|
"preparing",
|
|
@@ -7890,20 +8461,20 @@ function normalizePrdRunRef2(ref) {
|
|
|
7890
8461
|
const match = trimmed.match(/^(?:PRD-)?(\d+)$/);
|
|
7891
8462
|
if (!match) {
|
|
7892
8463
|
throw new Error(
|
|
7893
|
-
`Invalid PRD ref "${ref}". Expected format: PRD-<number> (e.g., PRD-
|
|
8464
|
+
`Invalid PRD ref "${ref}". Expected format: PRD-<number> (e.g., PRD-0043)`
|
|
7894
8465
|
);
|
|
7895
8466
|
}
|
|
7896
|
-
return `PRD-${match[1].padStart(
|
|
8467
|
+
return `PRD-${match[1].padStart(4, "0")}`;
|
|
7897
8468
|
}
|
|
7898
8469
|
function readPrdRun(repoRoot2, prdRef) {
|
|
7899
8470
|
const normalized = normalizePrdRunRef2(prdRef);
|
|
7900
8471
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7901
|
-
if (!
|
|
8472
|
+
if (!existsSync11(recordPath)) {
|
|
7902
8473
|
return { record: null, diagnostics: [] };
|
|
7903
8474
|
}
|
|
7904
8475
|
try {
|
|
7905
8476
|
const parsed = PrdRunRecordSchema.parse(
|
|
7906
|
-
JSON.parse(
|
|
8477
|
+
JSON.parse(readFileSync13(recordPath, "utf-8"))
|
|
7907
8478
|
);
|
|
7908
8479
|
return { record: parsed, diagnostics: [] };
|
|
7909
8480
|
} catch (error) {
|
|
@@ -7917,8 +8488,8 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7917
8488
|
}
|
|
7918
8489
|
}
|
|
7919
8490
|
function listPrdRuns(repoRoot2) {
|
|
7920
|
-
const stateDir =
|
|
7921
|
-
if (!
|
|
8491
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
8492
|
+
if (!existsSync11(stateDir)) {
|
|
7922
8493
|
return { records: [], diagnostics: [] };
|
|
7923
8494
|
}
|
|
7924
8495
|
const records = [];
|
|
@@ -7927,10 +8498,10 @@ function listPrdRuns(repoRoot2) {
|
|
|
7927
8498
|
(left, right) => left.name.localeCompare(right.name)
|
|
7928
8499
|
)) {
|
|
7929
8500
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
7930
|
-
const recordPath =
|
|
8501
|
+
const recordPath = join15(stateDir, entry.name);
|
|
7931
8502
|
try {
|
|
7932
8503
|
const record = PrdRunRecordSchema.parse(
|
|
7933
|
-
JSON.parse(
|
|
8504
|
+
JSON.parse(readFileSync13(recordPath, "utf-8"))
|
|
7934
8505
|
);
|
|
7935
8506
|
records.push(record);
|
|
7936
8507
|
} catch (error) {
|
|
@@ -7943,10 +8514,10 @@ function listPrdRuns(repoRoot2) {
|
|
|
7943
8514
|
}
|
|
7944
8515
|
function writePrdRunRecord(repoRoot2, record) {
|
|
7945
8516
|
const normalized = normalizePrdRunRef2(record.prdRef);
|
|
7946
|
-
const stateDir =
|
|
8517
|
+
const stateDir = join15(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7947
8518
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7948
|
-
|
|
7949
|
-
|
|
8519
|
+
mkdirSync8(stateDir, { recursive: true });
|
|
8520
|
+
writeFileSync5(
|
|
7950
8521
|
recordPath,
|
|
7951
8522
|
JSON.stringify({ ...record, prdRef: normalized }, null, 2),
|
|
7952
8523
|
"utf-8"
|
|
@@ -7954,7 +8525,7 @@ function writePrdRunRecord(repoRoot2, record) {
|
|
|
7954
8525
|
}
|
|
7955
8526
|
var LOCAL_PRD_RUN_STATE_DIR = ".pourkit/local-prd-runs";
|
|
7956
8527
|
var LocalPrdRunRecordSchema = z3.object({
|
|
7957
|
-
prdId: z3.string().regex(/^PRD-\d
|
|
8528
|
+
prdId: z3.string().regex(/^PRD-\d{4}$/, "PRD id must use four-digit format (e.g., PRD-0052)"),
|
|
7958
8529
|
createdAt: z3.string().min(1),
|
|
7959
8530
|
receipts: z3.object({
|
|
7960
8531
|
prepare: z3.object({
|
|
@@ -7983,8 +8554,8 @@ var LocalPrdRunRecordSchema = z3.object({
|
|
|
7983
8554
|
}),
|
|
7984
8555
|
metadata: z3.record(z3.unknown())
|
|
7985
8556
|
});
|
|
7986
|
-
function
|
|
7987
|
-
return
|
|
8557
|
+
function getLocalStorePath2(repoRoot2, prdId) {
|
|
8558
|
+
return join15(
|
|
7988
8559
|
repoRoot2,
|
|
7989
8560
|
LOCAL_PRD_RUN_STATE_DIR,
|
|
7990
8561
|
`${normalizePrdRunRef2(prdId)}.json`
|
|
@@ -7992,8 +8563,8 @@ function getLocalStorePath(repoRoot2, prdId) {
|
|
|
7992
8563
|
}
|
|
7993
8564
|
async function readLocalPrdRun(repoRoot2, prdId) {
|
|
7994
8565
|
const normalized = normalizePrdRunRef2(prdId);
|
|
7995
|
-
const recordPath =
|
|
7996
|
-
if (!
|
|
8566
|
+
const recordPath = getLocalStorePath2(repoRoot2, normalized);
|
|
8567
|
+
if (!existsSync11(recordPath)) {
|
|
7997
8568
|
return null;
|
|
7998
8569
|
}
|
|
7999
8570
|
try {
|
|
@@ -8005,16 +8576,16 @@ async function readLocalPrdRun(repoRoot2, prdId) {
|
|
|
8005
8576
|
}
|
|
8006
8577
|
async function writeLocalPrdRunRecord(repoRoot2, prdId, record) {
|
|
8007
8578
|
const normalized = normalizePrdRunRef2(prdId);
|
|
8008
|
-
const storeDir =
|
|
8579
|
+
const storeDir = join15(repoRoot2, LOCAL_PRD_RUN_STATE_DIR);
|
|
8009
8580
|
await mkdir4(storeDir, { recursive: true });
|
|
8010
8581
|
await writeFile(
|
|
8011
|
-
|
|
8582
|
+
getLocalStorePath2(repoRoot2, normalized),
|
|
8012
8583
|
JSON.stringify({ ...record, prdId: normalized }, null, 2),
|
|
8013
8584
|
"utf-8"
|
|
8014
8585
|
);
|
|
8015
8586
|
}
|
|
8016
8587
|
function getRecordPath(repoRoot2, prdRef) {
|
|
8017
|
-
return
|
|
8588
|
+
return join15(
|
|
8018
8589
|
repoRoot2,
|
|
8019
8590
|
PRD_RUN_STATE_DIR,
|
|
8020
8591
|
`${normalizePrdRunRef2(prdRef)}.json`
|
|
@@ -8147,9 +8718,9 @@ function redactSensitiveValues(input) {
|
|
|
8147
8718
|
}
|
|
8148
8719
|
|
|
8149
8720
|
// prd-run/final-review-validation.ts
|
|
8150
|
-
import { existsSync as
|
|
8721
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
8151
8722
|
function parseFinalReviewArtifact(artifactPath) {
|
|
8152
|
-
if (!
|
|
8723
|
+
if (!existsSync12(artifactPath)) {
|
|
8153
8724
|
return {
|
|
8154
8725
|
ok: false,
|
|
8155
8726
|
reason: "Final Review artifact not found.",
|
|
@@ -8158,7 +8729,7 @@ function parseFinalReviewArtifact(artifactPath) {
|
|
|
8158
8729
|
}
|
|
8159
8730
|
let content;
|
|
8160
8731
|
try {
|
|
8161
|
-
content =
|
|
8732
|
+
content = readFileSync14(artifactPath, "utf-8");
|
|
8162
8733
|
} catch (error) {
|
|
8163
8734
|
return {
|
|
8164
8735
|
ok: false,
|
|
@@ -8338,8 +8909,8 @@ function isRetouchScopePath(path9) {
|
|
|
8338
8909
|
init_common();
|
|
8339
8910
|
|
|
8340
8911
|
// prd-run/local-artifacts.ts
|
|
8341
|
-
import { existsSync as
|
|
8342
|
-
import { join as
|
|
8912
|
+
import { existsSync as existsSync13 } from "fs";
|
|
8913
|
+
import { join as join16 } from "path";
|
|
8343
8914
|
var REQUIRED_PRD_FIELDS = [
|
|
8344
8915
|
"schemaVersion",
|
|
8345
8916
|
"kind",
|
|
@@ -8372,13 +8943,13 @@ var REQUIRED_ISSUE_FIELDS = [
|
|
|
8372
8943
|
"githubProjection"
|
|
8373
8944
|
];
|
|
8374
8945
|
function prdStorePath(repoRoot2, prdId) {
|
|
8375
|
-
return
|
|
8946
|
+
return join16(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8376
8947
|
}
|
|
8377
8948
|
function prdArtifactPath(repoRoot2, prdId) {
|
|
8378
|
-
return
|
|
8949
|
+
return join16(prdStorePath(repoRoot2, prdId), "prd.json");
|
|
8379
8950
|
}
|
|
8380
8951
|
function issueArtifactPath(repoRoot2, prdId, issueId) {
|
|
8381
|
-
return
|
|
8952
|
+
return join16(prdStorePath(repoRoot2, prdId), "issues", `${issueId}.json`);
|
|
8382
8953
|
}
|
|
8383
8954
|
function hasRequiredFields(data, requiredFields) {
|
|
8384
8955
|
for (const field of requiredFields) {
|
|
@@ -8391,7 +8962,7 @@ function hasRequiredFields(data, requiredFields) {
|
|
|
8391
8962
|
async function resolveLocalPrdArtifact(prdId, repoRoot2) {
|
|
8392
8963
|
const root = repoRoot2 ?? process.cwd();
|
|
8393
8964
|
const prdPath = prdArtifactPath(root, prdId);
|
|
8394
|
-
if (!
|
|
8965
|
+
if (!existsSync13(prdPath)) {
|
|
8395
8966
|
return {
|
|
8396
8967
|
ok: false,
|
|
8397
8968
|
failureCode: "missing_prd_artifact",
|
|
@@ -8436,7 +9007,7 @@ async function resolveLocalIssueArtifacts(prdId, repoRoot2) {
|
|
|
8436
9007
|
const issues = [];
|
|
8437
9008
|
for (const childId of childIssueIds) {
|
|
8438
9009
|
const issuePath = issueArtifactPath(root, prdId, childId);
|
|
8439
|
-
if (!
|
|
9010
|
+
if (!existsSync13(issuePath)) {
|
|
8440
9011
|
return {
|
|
8441
9012
|
ok: false,
|
|
8442
9013
|
failureCode: "missing_child_issue",
|
|
@@ -8572,97 +9143,14 @@ async function getRunnableLocalIssues(prdId, repoRoot2) {
|
|
|
8572
9143
|
}
|
|
8573
9144
|
|
|
8574
9145
|
// prd-run/local-final-review.ts
|
|
8575
|
-
import { execFileSync as
|
|
8576
|
-
import { mkdirSync as
|
|
8577
|
-
import { join as
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
import { execFileSync } from "child_process";
|
|
8581
|
-
function getLocalPrdBranchName(prdId) {
|
|
8582
|
-
return `local/${prdId}`;
|
|
8583
|
-
}
|
|
8584
|
-
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["dev", "next", "main"]);
|
|
8585
|
-
var LOCAL_BRANCH_PATTERN = /^local\/PRD-\d{3}(\/(I-\d{2}(-[a-z0-9-]+)?)?)?$/;
|
|
8586
|
-
function validateLocalBranchName(name) {
|
|
8587
|
-
if (!name || name.length === 0) {
|
|
8588
|
-
return {
|
|
8589
|
-
ok: false,
|
|
8590
|
-
failureCode: "invalid_format",
|
|
8591
|
-
message: "Branch name is empty."
|
|
8592
|
-
};
|
|
8593
|
-
}
|
|
8594
|
-
if (isProtectedBranch(name)) {
|
|
8595
|
-
return {
|
|
8596
|
-
ok: false,
|
|
8597
|
-
failureCode: "protected_branch",
|
|
8598
|
-
message: `Branch "${name}" is protected.`
|
|
8599
|
-
};
|
|
8600
|
-
}
|
|
8601
|
-
if (!LOCAL_BRANCH_PATTERN.test(name)) {
|
|
8602
|
-
return {
|
|
8603
|
-
ok: false,
|
|
8604
|
-
failureCode: "invalid_format",
|
|
8605
|
-
message: `Branch "${name}" does not match the local branch pattern.`
|
|
8606
|
-
};
|
|
8607
|
-
}
|
|
8608
|
-
return { ok: true };
|
|
8609
|
-
}
|
|
8610
|
-
function isProtectedBranch(name) {
|
|
8611
|
-
return PROTECTED_BRANCHES.has(name);
|
|
8612
|
-
}
|
|
8613
|
-
function hasRemoteBackedCollision(localName, repoRoot2) {
|
|
8614
|
-
const match = localName.match(/^local\/(PRD-\d{3})/);
|
|
8615
|
-
if (!match) {
|
|
8616
|
-
return Promise.resolve({
|
|
8617
|
-
ok: false,
|
|
8618
|
-
failureCode: "invalid_format",
|
|
8619
|
-
message: `Cannot extract PRD ref from "${localName}".`
|
|
8620
|
-
});
|
|
8621
|
-
}
|
|
8622
|
-
const prdRef = match[1];
|
|
8623
|
-
try {
|
|
8624
|
-
execFileSync(
|
|
8625
|
-
"git",
|
|
8626
|
-
["show-ref", "--verify", "--quiet", `refs/heads/${prdRef}`],
|
|
8627
|
-
{
|
|
8628
|
-
cwd: repoRoot2 ?? process.cwd(),
|
|
8629
|
-
encoding: "utf8",
|
|
8630
|
-
stdio: "pipe"
|
|
8631
|
-
}
|
|
8632
|
-
);
|
|
8633
|
-
return Promise.resolve({
|
|
8634
|
-
ok: false,
|
|
8635
|
-
failureCode: "remote_backed_collision",
|
|
8636
|
-
message: `Branch "${prdRef}" exists locally.`
|
|
8637
|
-
});
|
|
8638
|
-
} catch {
|
|
8639
|
-
}
|
|
8640
|
-
try {
|
|
8641
|
-
execFileSync(
|
|
8642
|
-
"git",
|
|
8643
|
-
["show-ref", "--verify", "--quiet", `refs/remotes/origin/${prdRef}`],
|
|
8644
|
-
{
|
|
8645
|
-
cwd: repoRoot2 ?? process.cwd(),
|
|
8646
|
-
encoding: "utf8",
|
|
8647
|
-
stdio: "pipe"
|
|
8648
|
-
}
|
|
8649
|
-
);
|
|
8650
|
-
return Promise.resolve({
|
|
8651
|
-
ok: false,
|
|
8652
|
-
failureCode: "remote_backed_collision",
|
|
8653
|
-
message: `Remote branch "origin/${prdRef}" exists.`
|
|
8654
|
-
});
|
|
8655
|
-
} catch {
|
|
8656
|
-
}
|
|
8657
|
-
return Promise.resolve({ ok: true });
|
|
8658
|
-
}
|
|
8659
|
-
|
|
8660
|
-
// prd-run/local-final-review.ts
|
|
8661
|
-
function getLocalStorePath2(repoRoot2, prdId) {
|
|
8662
|
-
return join16(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
9146
|
+
import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
|
|
9147
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
9148
|
+
import { join as join17 } from "path";
|
|
9149
|
+
function getLocalStorePath3(repoRoot2, prdId) {
|
|
9150
|
+
return join17(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8663
9151
|
}
|
|
8664
9152
|
function getFinalReviewReceiptPath(repoRoot2, prdId) {
|
|
8665
|
-
return
|
|
9153
|
+
return join17(getLocalStorePath3(repoRoot2, prdId), "final-review-receipt.json");
|
|
8666
9154
|
}
|
|
8667
9155
|
async function runLocalFinalReview(prdId, options) {
|
|
8668
9156
|
const root = options?.repoRoot ?? process.cwd();
|
|
@@ -8719,8 +9207,8 @@ async function runLocalFinalReview(prdId, options) {
|
|
|
8719
9207
|
branch
|
|
8720
9208
|
};
|
|
8721
9209
|
const receiptPath = getFinalReviewReceiptPath(root, prdId);
|
|
8722
|
-
|
|
8723
|
-
|
|
9210
|
+
mkdirSync9(getLocalStorePath3(root, prdId), { recursive: true });
|
|
9211
|
+
writeFileSync6(receiptPath, JSON.stringify(receipt, null, 2), "utf-8");
|
|
8724
9212
|
const result = { ok: true, verdict, receipt };
|
|
8725
9213
|
if (failures.length > 0) {
|
|
8726
9214
|
result.message = failures.map(
|
|
@@ -8734,7 +9222,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8734
9222
|
const targetBranch = `local/${prdId}`;
|
|
8735
9223
|
const retouchBranch = `local/${prdId}/retouch`;
|
|
8736
9224
|
try {
|
|
8737
|
-
|
|
9225
|
+
execFileSync3(
|
|
8738
9226
|
"git",
|
|
8739
9227
|
["show-ref", "--verify", "--quiet", `refs/heads/${retouchBranch}`],
|
|
8740
9228
|
{
|
|
@@ -8747,7 +9235,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8747
9235
|
return;
|
|
8748
9236
|
}
|
|
8749
9237
|
try {
|
|
8750
|
-
|
|
9238
|
+
execFileSync3(
|
|
8751
9239
|
"git",
|
|
8752
9240
|
["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
|
|
8753
9241
|
{
|
|
@@ -8760,18 +9248,18 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8760
9248
|
return;
|
|
8761
9249
|
}
|
|
8762
9250
|
try {
|
|
8763
|
-
|
|
9251
|
+
execFileSync3("git", ["checkout", targetBranch], {
|
|
8764
9252
|
cwd: root,
|
|
8765
9253
|
encoding: "utf8",
|
|
8766
9254
|
stdio: "pipe"
|
|
8767
9255
|
});
|
|
8768
|
-
|
|
9256
|
+
execFileSync3("git", ["merge", "--squash", retouchBranch], {
|
|
8769
9257
|
cwd: root,
|
|
8770
9258
|
encoding: "utf8",
|
|
8771
9259
|
stdio: "pipe"
|
|
8772
9260
|
});
|
|
8773
9261
|
try {
|
|
8774
|
-
|
|
9262
|
+
execFileSync3("git", ["diff", "--cached", "--quiet"], {
|
|
8775
9263
|
cwd: root,
|
|
8776
9264
|
encoding: "utf8",
|
|
8777
9265
|
stdio: "pipe"
|
|
@@ -8779,7 +9267,7 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8779
9267
|
return;
|
|
8780
9268
|
} catch {
|
|
8781
9269
|
}
|
|
8782
|
-
|
|
9270
|
+
execFileSync3(
|
|
8783
9271
|
"git",
|
|
8784
9272
|
["commit", "-m", `Squash merge ${retouchBranch} into ${targetBranch}`],
|
|
8785
9273
|
{
|
|
@@ -8798,53 +9286,53 @@ async function squashFinalReviewRetouch(prdId, repoRoot2) {
|
|
|
8798
9286
|
}
|
|
8799
9287
|
|
|
8800
9288
|
// prd-run/local-reconciliation.ts
|
|
8801
|
-
import { execFileSync as
|
|
9289
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
8802
9290
|
import {
|
|
8803
|
-
existsSync as
|
|
8804
|
-
mkdirSync as
|
|
8805
|
-
readFileSync as
|
|
9291
|
+
existsSync as existsSync15,
|
|
9292
|
+
mkdirSync as mkdirSync10,
|
|
9293
|
+
readFileSync as readFileSync15,
|
|
8806
9294
|
readdirSync as readdirSync5,
|
|
8807
|
-
writeFileSync as
|
|
9295
|
+
writeFileSync as writeFileSync7
|
|
8808
9296
|
} from "fs";
|
|
8809
|
-
import { join as
|
|
8810
|
-
function
|
|
8811
|
-
return
|
|
9297
|
+
import { join as join18 } from "path";
|
|
9298
|
+
function getLocalStorePath4(repoRoot2, prdId) {
|
|
9299
|
+
return join18(repoRoot2, ".pourkit", "local-prd-runs", prdId);
|
|
8812
9300
|
}
|
|
8813
9301
|
function getFinalReviewReceiptPath2(repoRoot2, prdId) {
|
|
8814
|
-
return
|
|
9302
|
+
return join18(getLocalStorePath4(repoRoot2, prdId), "final-review-receipt.json");
|
|
8815
9303
|
}
|
|
8816
9304
|
function getHandoffArtifactPath(repoRoot2, prdId) {
|
|
8817
|
-
return
|
|
9305
|
+
return join18(repoRoot2, ".pourkit", ".tmp", "reconciliation", `${prdId}.json`);
|
|
8818
9306
|
}
|
|
8819
9307
|
function getCompletionsDir(repoRoot2, prdId) {
|
|
8820
|
-
const initiativesRoot =
|
|
9308
|
+
const initiativesRoot = join18(
|
|
8821
9309
|
repoRoot2,
|
|
8822
9310
|
".pourkit",
|
|
8823
9311
|
"architecture",
|
|
8824
9312
|
"initiatives"
|
|
8825
9313
|
);
|
|
8826
|
-
if (
|
|
8827
|
-
const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) =>
|
|
9314
|
+
if (existsSync15(initiativesRoot)) {
|
|
9315
|
+
const dirs = readdirSync5(initiativesRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join18(initiativesRoot, e.name));
|
|
8828
9316
|
for (const dir of dirs) {
|
|
8829
|
-
const prdsDir =
|
|
8830
|
-
if (!
|
|
9317
|
+
const prdsDir = join18(dir, "prds");
|
|
9318
|
+
if (!existsSync15(prdsDir)) continue;
|
|
8831
9319
|
const prdDirs = readdirSync5(prdsDir, { withFileTypes: true }).filter(
|
|
8832
9320
|
(e) => e.isDirectory() && e.name.startsWith(prdId.toLowerCase())
|
|
8833
9321
|
);
|
|
8834
|
-
if (prdDirs.length > 0) return
|
|
9322
|
+
if (prdDirs.length > 0) return join18(dir, "completions");
|
|
8835
9323
|
}
|
|
8836
9324
|
}
|
|
8837
|
-
return
|
|
9325
|
+
return join18(getLocalStorePath4(repoRoot2, prdId), "completions");
|
|
8838
9326
|
}
|
|
8839
9327
|
function getReconciliationReceiptPath(repoRoot2, prdId) {
|
|
8840
|
-
return
|
|
8841
|
-
|
|
9328
|
+
return join18(
|
|
9329
|
+
getLocalStorePath4(repoRoot2, prdId),
|
|
8842
9330
|
"reconciliation-receipt.json"
|
|
8843
9331
|
);
|
|
8844
9332
|
}
|
|
8845
9333
|
async function runLocalReconciliation(prdId, options) {
|
|
8846
9334
|
const root = options?.repoRoot ?? process.cwd();
|
|
8847
|
-
if (!
|
|
9335
|
+
if (!existsSync15(getFinalReviewReceiptPath2(root, prdId))) {
|
|
8848
9336
|
return {
|
|
8849
9337
|
ok: false,
|
|
8850
9338
|
failureCode: "missing_final_review",
|
|
@@ -8852,7 +9340,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8852
9340
|
};
|
|
8853
9341
|
}
|
|
8854
9342
|
try {
|
|
8855
|
-
|
|
9343
|
+
execFileSync4("pourkit-architect", ["reconcile", prdId], {
|
|
8856
9344
|
cwd: root,
|
|
8857
9345
|
encoding: "utf8",
|
|
8858
9346
|
stdio: "pipe"
|
|
@@ -8868,7 +9356,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8868
9356
|
}
|
|
8869
9357
|
let handoff;
|
|
8870
9358
|
try {
|
|
8871
|
-
const content =
|
|
9359
|
+
const content = readFileSync15(getHandoffArtifactPath(root, prdId), "utf-8");
|
|
8872
9360
|
handoff = JSON.parse(content);
|
|
8873
9361
|
} catch {
|
|
8874
9362
|
return {
|
|
@@ -8894,11 +9382,11 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8894
9382
|
if (handoff.result === "changes_produced") {
|
|
8895
9383
|
try {
|
|
8896
9384
|
const completionsDir = getCompletionsDir(root, prdId);
|
|
8897
|
-
|
|
8898
|
-
const existing =
|
|
9385
|
+
mkdirSync10(completionsDir, { recursive: true });
|
|
9386
|
+
const existing = existsSync15(completionsDir) ? readdirSync5(completionsDir).filter((f) => f.endsWith(".md")) : [];
|
|
8899
9387
|
const nextNum = String(existing.length + 1).padStart(3, "0");
|
|
8900
9388
|
const filename = `${nextNum}-prd-${prdId.replace("PRD-", "").toLowerCase()}-reconciliation-completion.md`;
|
|
8901
|
-
const filepath =
|
|
9389
|
+
const filepath = join18(completionsDir, filename);
|
|
8902
9390
|
const changedPaths = Array.isArray(handoff.changedPlanningPaths) ? handoff.changedPlanningPaths : [];
|
|
8903
9391
|
const summary = typeof handoff.summary === "string" ? handoff.summary : "";
|
|
8904
9392
|
const lines = [
|
|
@@ -8914,13 +9402,13 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8914
9402
|
lines.push("- (none)");
|
|
8915
9403
|
}
|
|
8916
9404
|
lines.push("");
|
|
8917
|
-
|
|
8918
|
-
|
|
9405
|
+
writeFileSync7(filepath, lines.join("\n"), "utf-8");
|
|
9406
|
+
execFileSync4("git", ["add", filepath], {
|
|
8919
9407
|
cwd: root,
|
|
8920
9408
|
encoding: "utf8",
|
|
8921
9409
|
stdio: "pipe"
|
|
8922
9410
|
});
|
|
8923
|
-
|
|
9411
|
+
execFileSync4(
|
|
8924
9412
|
"git",
|
|
8925
9413
|
["commit", "-m", `docs: ${prdId} reconciliation completion`],
|
|
8926
9414
|
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
@@ -8937,7 +9425,7 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8937
9425
|
try {
|
|
8938
9426
|
const targetBranch = getLocalPrdBranchName(prdId);
|
|
8939
9427
|
try {
|
|
8940
|
-
|
|
9428
|
+
execFileSync4(
|
|
8941
9429
|
"git",
|
|
8942
9430
|
["show-ref", "--verify", "--quiet", `refs/heads/${targetBranch}`],
|
|
8943
9431
|
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
@@ -8949,14 +9437,14 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8949
9437
|
message: "Squash merge into local PRD Branch failed."
|
|
8950
9438
|
};
|
|
8951
9439
|
}
|
|
8952
|
-
|
|
9440
|
+
execFileSync4("git", ["checkout", targetBranch], {
|
|
8953
9441
|
cwd: root,
|
|
8954
9442
|
encoding: "utf8",
|
|
8955
9443
|
stdio: "pipe"
|
|
8956
9444
|
});
|
|
8957
9445
|
const sourceBranch = `local/${prdId}/reconciliation`;
|
|
8958
9446
|
try {
|
|
8959
|
-
|
|
9447
|
+
execFileSync4(
|
|
8960
9448
|
"git",
|
|
8961
9449
|
["show-ref", "--verify", "--quiet", `refs/heads/${sourceBranch}`],
|
|
8962
9450
|
{
|
|
@@ -8972,19 +9460,19 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
8972
9460
|
message: "Squash merge into local PRD Branch failed."
|
|
8973
9461
|
};
|
|
8974
9462
|
}
|
|
8975
|
-
|
|
9463
|
+
execFileSync4("git", ["merge", "--squash", sourceBranch], {
|
|
8976
9464
|
cwd: root,
|
|
8977
9465
|
encoding: "utf8",
|
|
8978
9466
|
stdio: "pipe"
|
|
8979
9467
|
});
|
|
8980
9468
|
try {
|
|
8981
|
-
|
|
9469
|
+
execFileSync4("git", ["diff", "--cached", "--quiet"], {
|
|
8982
9470
|
cwd: root,
|
|
8983
9471
|
encoding: "utf8",
|
|
8984
9472
|
stdio: "pipe"
|
|
8985
9473
|
});
|
|
8986
9474
|
} catch {
|
|
8987
|
-
|
|
9475
|
+
execFileSync4(
|
|
8988
9476
|
"git",
|
|
8989
9477
|
["commit", "-m", `Squash merge ${sourceBranch} into ${targetBranch}`],
|
|
8990
9478
|
{ cwd: root, encoding: "utf8", stdio: "pipe" }
|
|
@@ -9003,8 +9491,8 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9003
9491
|
reconciledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9004
9492
|
branch
|
|
9005
9493
|
};
|
|
9006
|
-
|
|
9007
|
-
|
|
9494
|
+
mkdirSync10(getLocalStorePath4(root, prdId), { recursive: true });
|
|
9495
|
+
writeFileSync7(
|
|
9008
9496
|
getReconciliationReceiptPath(root, prdId),
|
|
9009
9497
|
JSON.stringify(receipt, null, 2),
|
|
9010
9498
|
"utf-8"
|
|
@@ -9013,8 +9501,8 @@ async function runLocalReconciliation(prdId, options) {
|
|
|
9013
9501
|
}
|
|
9014
9502
|
|
|
9015
9503
|
// prd-run/local-prepare.ts
|
|
9016
|
-
import { existsSync as
|
|
9017
|
-
import { join as
|
|
9504
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9505
|
+
import { join as join19 } from "path";
|
|
9018
9506
|
var VALID_TRIAGE_LABELS2 = /* @__PURE__ */ new Set([
|
|
9019
9507
|
"needs-triage",
|
|
9020
9508
|
"ready-for-agent",
|
|
@@ -9032,8 +9520,8 @@ function fail(gate, failureCode, repairGuidance, gates) {
|
|
|
9032
9520
|
async function validateLocalPrepareGates(prdId, repoRoot2) {
|
|
9033
9521
|
const root = repoRoot2 ?? process.cwd();
|
|
9034
9522
|
const gates = {};
|
|
9035
|
-
const storePath =
|
|
9036
|
-
if (!
|
|
9523
|
+
const storePath = join19(root, ".pourkit", "local-prd-runs", prdId);
|
|
9524
|
+
if (!existsSync16(storePath)) {
|
|
9037
9525
|
return fail(
|
|
9038
9526
|
"store_shape",
|
|
9039
9527
|
"local_prepare_store_shape_failed",
|
|
@@ -9459,6 +9947,7 @@ function runOneQueueIssueEffect(options) {
|
|
|
9459
9947
|
}
|
|
9460
9948
|
const { issue: selected } = outcome;
|
|
9461
9949
|
const baseBranchOverride = options.queueRunContext?.prdBranch;
|
|
9950
|
+
const prdRunMode = options.prdRunMode ?? options.queueRunContext?.prdRunMode;
|
|
9462
9951
|
const runResult = yield* Effect8.tryPromise({
|
|
9463
9952
|
try: () => runIssueCommand({
|
|
9464
9953
|
issueNumber: selected.number,
|
|
@@ -9470,7 +9959,8 @@ function runOneQueueIssueEffect(options) {
|
|
|
9470
9959
|
force,
|
|
9471
9960
|
logger,
|
|
9472
9961
|
repoRoot: repoRoot2,
|
|
9473
|
-
...baseBranchOverride ? { baseBranchOverride } : {}
|
|
9962
|
+
...baseBranchOverride ? { baseBranchOverride } : {},
|
|
9963
|
+
...prdRunMode ? { prdRunMode } : {}
|
|
9474
9964
|
}),
|
|
9475
9965
|
catch: (e) => {
|
|
9476
9966
|
if (e instanceof Error) return e;
|
|
@@ -9481,6 +9971,10 @@ function runOneQueueIssueEffect(options) {
|
|
|
9481
9971
|
logger.raw(` Branch: ${runResult.branchName}`);
|
|
9482
9972
|
if (runResult.noOp) {
|
|
9483
9973
|
logger.raw(" Status: no-op (closed without PR)");
|
|
9974
|
+
} else if (runResult.mode === "local") {
|
|
9975
|
+
logger.raw(` Local PRD Branch: ${runResult.localPrdBranch}`);
|
|
9976
|
+
logger.raw(` Merge Commit: ${runResult.mergeCommit}`);
|
|
9977
|
+
logger.raw(` Receipt: ${runResult.receiptPath}`);
|
|
9484
9978
|
} else {
|
|
9485
9979
|
logger.raw(` PR Title: ${runResult.prTitle}`);
|
|
9486
9980
|
logger.raw(` PR Number: ${runResult.prNumber}`);
|
|
@@ -9540,7 +10034,8 @@ async function runQueueCommand(options) {
|
|
|
9540
10034
|
logger: options.logger,
|
|
9541
10035
|
repoRoot: options.repoRoot,
|
|
9542
10036
|
prdRef: options.prdRef,
|
|
9543
|
-
queueRunContext: options.queueRunContext
|
|
10037
|
+
queueRunContext: options.queueRunContext,
|
|
10038
|
+
prdRunMode: options.queueRunContext?.prdRunMode
|
|
9544
10039
|
};
|
|
9545
10040
|
if (!options.loop) {
|
|
9546
10041
|
return runEffectAndMapExit(runQueue(queueOptions));
|
|
@@ -9669,7 +10164,7 @@ function validateChangedPlanningPaths(paths, options) {
|
|
|
9669
10164
|
continue;
|
|
9670
10165
|
}
|
|
9671
10166
|
if (options?.repoRoot) {
|
|
9672
|
-
const fullPath =
|
|
10167
|
+
const fullPath = join20(options.repoRoot, p);
|
|
9673
10168
|
try {
|
|
9674
10169
|
if (lstatSync(fullPath).isSymbolicLink()) {
|
|
9675
10170
|
rejected.push(p);
|
|
@@ -9694,7 +10189,7 @@ function assessStaleSucceededReconciliationReceipt(options) {
|
|
|
9694
10189
|
if (!receipt || receipt.status !== "succeeded" || receipt.prNumber || receipt.mergeCommit) {
|
|
9695
10190
|
return { mode: "none" };
|
|
9696
10191
|
}
|
|
9697
|
-
const observedDiff =
|
|
10192
|
+
const observedDiff = collectObservedReconciliationDirtyPaths({
|
|
9698
10193
|
worktreeCwd: options.worktreeCwd,
|
|
9699
10194
|
ignoredPrdRunRecordRef: options.prdRef
|
|
9700
10195
|
});
|
|
@@ -9705,23 +10200,14 @@ function assessStaleSucceededReconciliationReceipt(options) {
|
|
|
9705
10200
|
offendingPaths: []
|
|
9706
10201
|
};
|
|
9707
10202
|
}
|
|
9708
|
-
if (observedDiff.
|
|
9709
|
-
return {
|
|
9710
|
-
mode: "blocked",
|
|
9711
|
-
diagnostics: [
|
|
9712
|
-
"Reconciliation worktree contains unpublished changes outside safe planning scope."
|
|
9713
|
-
],
|
|
9714
|
-
offendingPaths: observedDiff.rejectedPaths
|
|
9715
|
-
};
|
|
9716
|
-
}
|
|
9717
|
-
if (observedDiff.changedPlanningPaths.length === 0) {
|
|
10203
|
+
if (observedDiff.dirtyPaths.length === 0) {
|
|
9718
10204
|
return { mode: "none" };
|
|
9719
10205
|
}
|
|
9720
10206
|
return {
|
|
9721
10207
|
mode: "recoverable",
|
|
9722
|
-
|
|
10208
|
+
dirtyPaths: observedDiff.dirtyPaths,
|
|
9723
10209
|
diagnostics: [
|
|
9724
|
-
"Recovered stale reconciliation success receipt from
|
|
10210
|
+
"Recovered stale reconciliation success receipt from dirty reconciliation worktree."
|
|
9725
10211
|
]
|
|
9726
10212
|
};
|
|
9727
10213
|
}
|
|
@@ -9845,7 +10331,7 @@ function canRetryFinalReviewBlock(record) {
|
|
|
9845
10331
|
}
|
|
9846
10332
|
function loadFinalReviewPrompt(repoRoot2, promptTemplate) {
|
|
9847
10333
|
const promptPath = resolvePromptTemplatePath(repoRoot2, promptTemplate);
|
|
9848
|
-
return
|
|
10334
|
+
return existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : promptTemplate;
|
|
9849
10335
|
}
|
|
9850
10336
|
function formatVerificationCommands(commands) {
|
|
9851
10337
|
if (commands.length === 0) return "No verification commands configured.";
|
|
@@ -9858,12 +10344,14 @@ function buildFinalReviewPrompt(options) {
|
|
|
9858
10344
|
`Worktree checkout base: ${options.evidencePacket.prdBranch}`,
|
|
9859
10345
|
`Review only this range: ${options.evidencePacket.mergeBase}..HEAD`,
|
|
9860
10346
|
"Do not compare against current target branch HEAD; the merge base is the review baseline.",
|
|
9861
|
-
`Before handoff, run:
|
|
10347
|
+
`Before handoff, run: pourkit prd-run validate-final-review ${options.evidencePacket.prdRef} --checkout-base ${options.evidencePacket.prdBranch} --review-base ${options.evidencePacket.mergeBase}`,
|
|
9862
10348
|
"Fix any validation failures before handing off.",
|
|
9863
10349
|
"",
|
|
9864
|
-
"##
|
|
10350
|
+
"## Verification",
|
|
10351
|
+
"",
|
|
10352
|
+
`Run this command before handoff when retouch changes are made: pourkit run-verification --target ${options.targetName}`,
|
|
9865
10353
|
"",
|
|
9866
|
-
"
|
|
10354
|
+
"Underlying project commands:",
|
|
9867
10355
|
formatVerificationCommands(options.verificationCommands),
|
|
9868
10356
|
"",
|
|
9869
10357
|
"Evidence Packet (do not infer PRD context from local state files):",
|
|
@@ -9890,14 +10378,14 @@ function buildReconciliationPrompt(options) {
|
|
|
9890
10378
|
options.artifactPath,
|
|
9891
10379
|
"",
|
|
9892
10380
|
"Self-validation command:",
|
|
9893
|
-
`validate-artifact reconciliation ${options.artifactPath}`
|
|
10381
|
+
`pourkit validate-artifact reconciliation ${options.artifactPath}`
|
|
9894
10382
|
].join("\n");
|
|
9895
10383
|
}
|
|
9896
10384
|
function buildReconciliationBranchName(prdRef) {
|
|
9897
10385
|
return `${normalizePrdRunRef2(prdRef)}-reconciliation`;
|
|
9898
10386
|
}
|
|
9899
10387
|
function reconciliationWorktreePath(repoRoot2, branchName) {
|
|
9900
|
-
return
|
|
10388
|
+
return join20(repoRoot2, ".sandcastle", "worktrees", branchName);
|
|
9901
10389
|
}
|
|
9902
10390
|
function reconciliationReceiptWorktreePath(branchName) {
|
|
9903
10391
|
return `.sandcastle/worktrees/${branchName}`;
|
|
@@ -9909,7 +10397,7 @@ function buildFinalReviewBranchName(prdRef) {
|
|
|
9909
10397
|
return `pourkit/${normalizePrdRunRef2(prdRef).toLowerCase()}-final-review`;
|
|
9910
10398
|
}
|
|
9911
10399
|
function finalReviewWorktreePath(repoRoot2, branchName) {
|
|
9912
|
-
return
|
|
10400
|
+
return join20(
|
|
9913
10401
|
repoRoot2,
|
|
9914
10402
|
".sandcastle",
|
|
9915
10403
|
"worktrees",
|
|
@@ -9964,7 +10452,7 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
9964
10452
|
options.repoRoot,
|
|
9965
10453
|
options.promptTemplate
|
|
9966
10454
|
);
|
|
9967
|
-
const promptBody =
|
|
10455
|
+
const promptBody = existsSync17(promptPath) ? readFileSync16(promptPath, "utf-8") : options.promptTemplate;
|
|
9968
10456
|
return [
|
|
9969
10457
|
"# Final Review Retouch PR Finalizer",
|
|
9970
10458
|
"",
|
|
@@ -9998,7 +10486,7 @@ function buildFinalReviewFinalizerPrompt(options) {
|
|
|
9998
10486
|
}
|
|
9999
10487
|
async function runFinalReviewPrFinalizer(options) {
|
|
10000
10488
|
const finalizer = options.target.strategy.finalize.prDescriptionAgent;
|
|
10001
|
-
const artifactPathInWorktree =
|
|
10489
|
+
const artifactPathInWorktree = join20(
|
|
10002
10490
|
".pourkit",
|
|
10003
10491
|
".tmp",
|
|
10004
10492
|
"finalizer",
|
|
@@ -10072,14 +10560,14 @@ function ensureFinalReviewWorktree(options) {
|
|
|
10072
10560
|
}
|
|
10073
10561
|
return { ok: true, worktreePath: registeredPath };
|
|
10074
10562
|
}
|
|
10075
|
-
if (
|
|
10563
|
+
if (existsSync17(worktreePath)) {
|
|
10076
10564
|
return {
|
|
10077
10565
|
ok: false,
|
|
10078
10566
|
reason: "Final Review worktree path exists but is not registered with git.",
|
|
10079
10567
|
diagnostics: [`stale worktree path: ${worktreePath}`]
|
|
10080
10568
|
};
|
|
10081
10569
|
}
|
|
10082
|
-
|
|
10570
|
+
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
10083
10571
|
const branchResult = spawnSync2(
|
|
10084
10572
|
"git",
|
|
10085
10573
|
["branch", "-f", options.branchName, `origin/${options.checkoutBase}`],
|
|
@@ -10134,7 +10622,7 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10134
10622
|
if (existingPr && existingPr.state === "OPEN") {
|
|
10135
10623
|
return existingPr;
|
|
10136
10624
|
}
|
|
10137
|
-
const worktreePath = mkdtempSync(
|
|
10625
|
+
const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-retouch-"));
|
|
10138
10626
|
try {
|
|
10139
10627
|
runGitOrThrow(
|
|
10140
10628
|
options.repoRoot,
|
|
@@ -10152,9 +10640,9 @@ async function createOrReuseFinalReviewRetouchPr(options) {
|
|
|
10152
10640
|
"create retouch branch"
|
|
10153
10641
|
);
|
|
10154
10642
|
for (const changedFile of options.changedPaths) {
|
|
10155
|
-
const sourcePath =
|
|
10156
|
-
const targetPath =
|
|
10157
|
-
|
|
10643
|
+
const sourcePath = join20(options.sourceWorktreePath, changedFile);
|
|
10644
|
+
const targetPath = join20(worktreePath, changedFile);
|
|
10645
|
+
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
10158
10646
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
10159
10647
|
}
|
|
10160
10648
|
runGitOrThrow(
|
|
@@ -10508,6 +10996,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10508
10996
|
prompt: buildFinalReviewPrompt({
|
|
10509
10997
|
repoRoot: options.repoRoot,
|
|
10510
10998
|
promptTemplate: finalReviewConfig.promptTemplate,
|
|
10999
|
+
targetName,
|
|
10511
11000
|
evidencePacket,
|
|
10512
11001
|
verificationCommands
|
|
10513
11002
|
}),
|
|
@@ -10583,7 +11072,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
10583
11072
|
};
|
|
10584
11073
|
}
|
|
10585
11074
|
const resolvedWorktreePath = executionResult.worktreePath;
|
|
10586
|
-
const artifactPath =
|
|
11075
|
+
const artifactPath = join20(
|
|
10587
11076
|
resolvedWorktreePath,
|
|
10588
11077
|
".pourkit/final-review-artifact.json"
|
|
10589
11078
|
);
|
|
@@ -11167,7 +11656,7 @@ async function runPrdRunFinalReviewCommand(options) {
|
|
|
11167
11656
|
function runPrdRunValidateFinalReviewCommand(options) {
|
|
11168
11657
|
const prdRef = normalizePrdRunRef2(options.prdRef);
|
|
11169
11658
|
const checkoutBase = options.checkoutBase ?? prdRef;
|
|
11170
|
-
const artifactPath = options.artifactPath ? options.artifactPath :
|
|
11659
|
+
const artifactPath = options.artifactPath ? options.artifactPath : join20(options.repoRoot, ".pourkit", "final-review-artifact.json");
|
|
11171
11660
|
const artifact = parseFinalReviewArtifact(artifactPath);
|
|
11172
11661
|
if (!artifact.ok) {
|
|
11173
11662
|
return {
|
|
@@ -11475,7 +11964,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11475
11964
|
);
|
|
11476
11965
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11477
11966
|
}
|
|
11478
|
-
if (
|
|
11967
|
+
if (existsSync17(worktreePath)) {
|
|
11479
11968
|
diagnostics.push(
|
|
11480
11969
|
"Reconciliation worktree path exists but is not registered with git.",
|
|
11481
11970
|
`stale worktree path: ${worktreePath}`
|
|
@@ -11496,7 +11985,7 @@ async function runReconcilePreflightAndSetup(options) {
|
|
|
11496
11985
|
);
|
|
11497
11986
|
return { ok: false, diagnostics, branchAction: "blocked" };
|
|
11498
11987
|
}
|
|
11499
|
-
|
|
11988
|
+
mkdirSync11(dirname5(worktreePath), { recursive: true });
|
|
11500
11989
|
const addResult = spawnSync2(
|
|
11501
11990
|
"git",
|
|
11502
11991
|
["worktree", "add", worktreePath, reconciliationBranchName],
|
|
@@ -11551,7 +12040,7 @@ function buildReconciliationPrBody(options) {
|
|
|
11551
12040
|
function commitAndPushReconciliationChanges(options) {
|
|
11552
12041
|
const diagnostics = [];
|
|
11553
12042
|
const existingPaths = options.changedPlanningPaths.filter((p) => {
|
|
11554
|
-
return
|
|
12043
|
+
return existsSync17(join20(options.worktreeCwd, p));
|
|
11555
12044
|
});
|
|
11556
12045
|
if (existingPaths.length === 0) {
|
|
11557
12046
|
diagnostics.push(
|
|
@@ -11694,7 +12183,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
11694
12183
|
const record = preflightResult.record;
|
|
11695
12184
|
const prdBranch = record?.prdBranch ?? prdRef;
|
|
11696
12185
|
const mergeBase = preflightResult.mergeBase;
|
|
11697
|
-
const worktreeCwd = preflightResult.worktreePath ?
|
|
12186
|
+
const worktreeCwd = preflightResult.worktreePath ? join20(options.repoRoot, preflightResult.worktreePath) : options.repoRoot;
|
|
11698
12187
|
const staleSucceededRecovery = assessStaleSucceededReconciliationReceipt({
|
|
11699
12188
|
receipt: record?.reconciliation,
|
|
11700
12189
|
worktreeCwd,
|
|
@@ -11996,7 +12485,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
11996
12485
|
baseRef: prdBranch,
|
|
11997
12486
|
checkoutBase,
|
|
11998
12487
|
reviewBase: mergeBase,
|
|
11999
|
-
worktreePath:
|
|
12488
|
+
worktreePath: join20(options.repoRoot, preflightResult.worktreePath),
|
|
12000
12489
|
sandbox: options.config.sandbox,
|
|
12001
12490
|
artifactPath,
|
|
12002
12491
|
logger: options.logger
|
|
@@ -12097,7 +12586,7 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12097
12586
|
offendingPaths: []
|
|
12098
12587
|
};
|
|
12099
12588
|
}
|
|
12100
|
-
const fullArtifactPath =
|
|
12589
|
+
const fullArtifactPath = join20(executionResult.worktreePath, artifactPath);
|
|
12101
12590
|
let artifactContent;
|
|
12102
12591
|
try {
|
|
12103
12592
|
artifactContent = JSON.parse(retryResult.artifact.value);
|
|
@@ -12219,11 +12708,27 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12219
12708
|
gitStatusShortLines,
|
|
12220
12709
|
gitDiffNameStatusLines
|
|
12221
12710
|
});
|
|
12711
|
+
const observedWorktreeDiff = collectObservedReconciliationDirtyPaths({
|
|
12712
|
+
worktreeCwd,
|
|
12713
|
+
ignoredPrdRunRecordRef: prdRef
|
|
12714
|
+
});
|
|
12222
12715
|
let outcomeResult;
|
|
12223
12716
|
let outcomeDiagnostics = [];
|
|
12224
12717
|
let outcomeChangedPaths = [];
|
|
12225
12718
|
let outcomeRationale;
|
|
12226
|
-
if (
|
|
12719
|
+
if (!observedWorktreeDiff.ok) {
|
|
12720
|
+
outcomeResult = "blocked";
|
|
12721
|
+
outcomeDiagnostics.push(...observedWorktreeDiff.diagnostics);
|
|
12722
|
+
} else if (observedWorktreeDiff.dirtyPaths.length > 0) {
|
|
12723
|
+
outcomeResult = "changes_produced";
|
|
12724
|
+
outcomeChangedPaths = [...observedWorktreeDiff.dirtyPaths];
|
|
12725
|
+
outcomeRationale = void 0;
|
|
12726
|
+
if (artifactResult === "no_changes_needed") {
|
|
12727
|
+
outcomeDiagnostics.push(
|
|
12728
|
+
"Dirty reconciliation worktree observed after agent run; treating reconciliation result as changes_produced."
|
|
12729
|
+
);
|
|
12730
|
+
}
|
|
12731
|
+
} else if (artifactResult === "no_changes_needed") {
|
|
12227
12732
|
outcomeResult = "no_changes_needed";
|
|
12228
12733
|
outcomeChangedPaths = [];
|
|
12229
12734
|
outcomeRationale = artifactNoChangeRationale ?? "No changes needed.";
|
|
@@ -12244,11 +12749,11 @@ async function runPrdRunReconcileCommand(options) {
|
|
|
12244
12749
|
}
|
|
12245
12750
|
if (staleSucceededRecovery.mode === "recoverable" && artifactResult === "no_changes_needed") {
|
|
12246
12751
|
outcomeResult = "changes_produced";
|
|
12247
|
-
outcomeChangedPaths = [...staleSucceededRecovery.
|
|
12752
|
+
outcomeChangedPaths = [...staleSucceededRecovery.dirtyPaths];
|
|
12248
12753
|
outcomeRationale = void 0;
|
|
12249
12754
|
outcomeDiagnostics.push(...staleSucceededRecovery.diagnostics);
|
|
12250
12755
|
}
|
|
12251
|
-
if (!pathValidation.ok && outcomeResult !== "blocked") {
|
|
12756
|
+
if (!pathValidation.ok && outcomeResult !== "blocked" && observedWorktreeDiff.ok && observedWorktreeDiff.dirtyPaths.length === 0) {
|
|
12252
12757
|
outcomeResult = "blocked";
|
|
12253
12758
|
outcomeDiagnostics.push(
|
|
12254
12759
|
`Unsafe changed planning paths: ${(pathValidation.rejected ?? []).join(", ")}`
|
|
@@ -13099,58 +13604,58 @@ function validateReconciliationArtifact2(artifact, context) {
|
|
|
13099
13604
|
}
|
|
13100
13605
|
return { ok: true };
|
|
13101
13606
|
}
|
|
13102
|
-
var PRD_047_PRD_MIRROR_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-
|
|
13103
|
-
var PRD_047_CHILD_ISSUE_DIR = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-
|
|
13607
|
+
var PRD_047_PRD_MIRROR_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-0047-prd-reconciliation-run/PRD.md";
|
|
13608
|
+
var PRD_047_CHILD_ISSUE_DIR = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-0047-prd-reconciliation-run/issues/";
|
|
13104
13609
|
var PRD_047_ESCAPE_HATCH_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/completions/006-prd-047-prd-reconciliation-run-status.md";
|
|
13105
|
-
var PRD_047_MANIFEST_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-
|
|
13610
|
+
var PRD_047_MANIFEST_PATH = ".pourkit/architecture/initiatives/prd-run-lifecycle-and-integration-gate/prds/PRD-0047-prd-reconciliation-run/planning-manifest.md";
|
|
13106
13611
|
function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
13107
13612
|
const normalizedRef = normalizePrdRunRef2(prdRef);
|
|
13108
13613
|
const blockerBugs = [];
|
|
13109
13614
|
const blockersFixed = [];
|
|
13110
13615
|
const nonBlockers = [];
|
|
13111
|
-
const prdMirrorPath =
|
|
13112
|
-
const completionsDir =
|
|
13616
|
+
const prdMirrorPath = join20(repoRoot2, PRD_047_PRD_MIRROR_PATH);
|
|
13617
|
+
const completionsDir = join20(repoRoot2, dirname5(PRD_047_ESCAPE_HATCH_PATH));
|
|
13113
13618
|
let escapeHatchPresent = false;
|
|
13114
|
-
if (
|
|
13619
|
+
if (existsSync17(completionsDir)) {
|
|
13115
13620
|
const files = readdirSync6(completionsDir);
|
|
13116
13621
|
escapeHatchPresent = files.some(
|
|
13117
13622
|
(f) => f.endsWith(".md") && (f.includes("prd-047") || f.includes("prd-reconciliation"))
|
|
13118
13623
|
);
|
|
13119
13624
|
}
|
|
13120
13625
|
if (!escapeHatchPresent) {
|
|
13121
|
-
escapeHatchPresent =
|
|
13626
|
+
escapeHatchPresent = existsSync17(join20(repoRoot2, PRD_047_ESCAPE_HATCH_PATH));
|
|
13122
13627
|
}
|
|
13123
|
-
const prdMirrorExists =
|
|
13628
|
+
const prdMirrorExists = existsSync17(prdMirrorPath);
|
|
13124
13629
|
if (!prdMirrorExists) {
|
|
13125
13630
|
nonBlockers.push(
|
|
13126
|
-
`PRD-
|
|
13631
|
+
`PRD-0047 PRD mirror not found at ${PRD_047_PRD_MIRROR_PATH}. Cannot verify scope alignment from published mirrors.`
|
|
13127
13632
|
);
|
|
13128
13633
|
}
|
|
13129
13634
|
if (prdMirrorExists) {
|
|
13130
|
-
const mirrorContent =
|
|
13635
|
+
const mirrorContent = readFileSync16(prdMirrorPath, "utf-8");
|
|
13131
13636
|
const hasReconcileCommand = mirrorContent.includes("prd-run reconcile");
|
|
13132
13637
|
const hasEscapeHatch = mirrorContent.includes("escape hatch");
|
|
13133
13638
|
if (!hasReconcileCommand) {
|
|
13134
13639
|
blockerBugs.push(
|
|
13135
|
-
"PRD-
|
|
13640
|
+
"PRD-0047 PRD mirror does not reference prd-run reconcile command."
|
|
13136
13641
|
);
|
|
13137
13642
|
}
|
|
13138
13643
|
if (!hasEscapeHatch) {
|
|
13139
13644
|
nonBlockers.push(
|
|
13140
|
-
"PRD-
|
|
13645
|
+
"PRD-0047 PRD mirror does not reference escape hatch mechanism."
|
|
13141
13646
|
);
|
|
13142
13647
|
}
|
|
13143
13648
|
}
|
|
13144
|
-
const childIssueDir =
|
|
13145
|
-
const childIssuesExist =
|
|
13649
|
+
const childIssueDir = join20(repoRoot2, PRD_047_CHILD_ISSUE_DIR);
|
|
13650
|
+
const childIssuesExist = existsSync17(childIssueDir);
|
|
13146
13651
|
if (!childIssuesExist) {
|
|
13147
13652
|
nonBlockers.push(
|
|
13148
|
-
`PRD-
|
|
13653
|
+
`PRD-0047 child Issue mirrors not found at ${PRD_047_CHILD_ISSUE_DIR}.`
|
|
13149
13654
|
);
|
|
13150
13655
|
}
|
|
13151
|
-
const manifestPath =
|
|
13152
|
-
if (
|
|
13153
|
-
const manifestContent =
|
|
13656
|
+
const manifestPath = join20(repoRoot2, PRD_047_MANIFEST_PATH);
|
|
13657
|
+
if (existsSync17(manifestPath)) {
|
|
13658
|
+
const manifestContent = readFileSync16(manifestPath, "utf-8");
|
|
13154
13659
|
if (!manifestContent.includes("ready_for_prepare")) {
|
|
13155
13660
|
nonBlockers.push(
|
|
13156
13661
|
"Planning Artifact Manifest is not marked as ready_for_prepare."
|
|
@@ -13216,16 +13721,16 @@ function auditPrd047Implementation(repoRoot2, prdRef) {
|
|
|
13216
13721
|
}
|
|
13217
13722
|
if (!prdMirrorExists && !childIssuesExist && !prdRunRecord.record) {
|
|
13218
13723
|
nonBlockers.push(
|
|
13219
|
-
"Insufficient evidence to verify PRD-
|
|
13724
|
+
"Insufficient evidence to verify PRD-0047 scope alignment: mirrors and PRD Run record are missing."
|
|
13220
13725
|
);
|
|
13221
13726
|
}
|
|
13222
13727
|
if (escapeHatchPresent) {
|
|
13223
13728
|
blockersFixed.push(
|
|
13224
|
-
"PRD-
|
|
13729
|
+
"PRD-0047 escape-hatch lifecycle violation record confirmed present."
|
|
13225
13730
|
);
|
|
13226
13731
|
} else {
|
|
13227
13732
|
nonBlockers.push(
|
|
13228
|
-
"PRD-
|
|
13733
|
+
"PRD-0047 escape-hatch lifecycle violation record is missing. Expected at " + PRD_047_ESCAPE_HATCH_PATH + "."
|
|
13229
13734
|
);
|
|
13230
13735
|
}
|
|
13231
13736
|
return {
|
|
@@ -13398,13 +13903,13 @@ async function runPrdRunLaunchCommand(options) {
|
|
|
13398
13903
|
let finalReviewResult;
|
|
13399
13904
|
if (!skipped.includes("final-review")) {
|
|
13400
13905
|
attempted.push("final-review");
|
|
13401
|
-
const localStorePath =
|
|
13906
|
+
const localStorePath = join20(
|
|
13402
13907
|
options.repoRoot,
|
|
13403
13908
|
".pourkit",
|
|
13404
13909
|
"local-prd-runs",
|
|
13405
13910
|
prdRef
|
|
13406
13911
|
);
|
|
13407
|
-
if (
|
|
13912
|
+
if (existsSync17(localStorePath)) {
|
|
13408
13913
|
const targetConfig = options.config?.targets?.find(
|
|
13409
13914
|
(t) => t.name === options.targetName
|
|
13410
13915
|
);
|
|
@@ -14703,6 +15208,13 @@ async function processStartResult(startResult, options) {
|
|
|
14703
15208
|
start.queueStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14704
15209
|
start.queueCommand = "queue-run";
|
|
14705
15210
|
const existingRecord = readPrdRun(repoRoot2, prdRef);
|
|
15211
|
+
let resolvedMode;
|
|
15212
|
+
try {
|
|
15213
|
+
const target = options.config ? resolveTarget(options.config, start.targetName) : null;
|
|
15214
|
+
resolvedMode = target?.strategy ? resolvePrdRunMode(target) : void 0;
|
|
15215
|
+
} catch {
|
|
15216
|
+
resolvedMode = void 0;
|
|
15217
|
+
}
|
|
14706
15218
|
writePrdRunRecord(repoRoot2, {
|
|
14707
15219
|
prdRef,
|
|
14708
15220
|
status: "running",
|
|
@@ -14712,8 +15224,8 @@ async function processStartResult(startResult, options) {
|
|
|
14712
15224
|
manifestPath: existingRecord.record?.manifestPath,
|
|
14713
15225
|
planning: existingRecord.record?.planning
|
|
14714
15226
|
});
|
|
14715
|
-
const localStorePath =
|
|
14716
|
-
if (
|
|
15227
|
+
const localStorePath = join20(repoRoot2, ".pourkit", "local-prd-runs", prdRef);
|
|
15228
|
+
if (existsSync17(localStorePath)) {
|
|
14717
15229
|
const localIssues = await getRunnableLocalIssues(prdRef, repoRoot2);
|
|
14718
15230
|
start.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14719
15231
|
start.queueProcessedCount = localIssues.length;
|
|
@@ -14747,7 +15259,8 @@ async function processStartResult(startResult, options) {
|
|
|
14747
15259
|
prdRef,
|
|
14748
15260
|
queueRunContext: {
|
|
14749
15261
|
prdRef,
|
|
14750
|
-
prdBranch: start.prdBranch
|
|
15262
|
+
prdBranch: start.prdBranch,
|
|
15263
|
+
prdRunMode: resolvedMode
|
|
14751
15264
|
}
|
|
14752
15265
|
});
|
|
14753
15266
|
if (outcome.selected === null && outcome.code === "drained") {
|
|
@@ -15292,7 +15805,7 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15292
15805
|
if (remoteResult.status !== 0) {
|
|
15293
15806
|
return;
|
|
15294
15807
|
}
|
|
15295
|
-
const worktreePath = mkdtempSync(
|
|
15808
|
+
const worktreePath = mkdtempSync(join20(tmpdir(), "pourkit-planning-"));
|
|
15296
15809
|
try {
|
|
15297
15810
|
runGitOrThrow(repoRoot2, ["fetch", "origin", "dev"], "fetch origin/dev");
|
|
15298
15811
|
runGitOrThrow(
|
|
@@ -15306,9 +15819,9 @@ async function ensurePlanningBranchPublished(repoRoot2, branchName, changedFiles
|
|
|
15306
15819
|
"create planning branch"
|
|
15307
15820
|
);
|
|
15308
15821
|
for (const changedFile of changedFiles) {
|
|
15309
|
-
const sourcePath =
|
|
15310
|
-
const targetPath =
|
|
15311
|
-
|
|
15822
|
+
const sourcePath = join20(repoRoot2, changedFile);
|
|
15823
|
+
const targetPath = join20(worktreePath, changedFile);
|
|
15824
|
+
mkdirSync11(dirname5(targetPath), { recursive: true });
|
|
15312
15825
|
cpSync(sourcePath, targetPath, { recursive: true });
|
|
15313
15826
|
}
|
|
15314
15827
|
runGitOrThrow(
|
|
@@ -15544,7 +16057,7 @@ function collectGitChangedFiles(repoRoot2, ignoredPrdRunRecordRef) {
|
|
|
15544
16057
|
changedFiles: Array.from(new Set(changedFiles))
|
|
15545
16058
|
};
|
|
15546
16059
|
}
|
|
15547
|
-
function
|
|
16060
|
+
function collectObservedReconciliationDirtyPaths(options) {
|
|
15548
16061
|
const statusResult = spawnSync2("git", ["status", "--porcelain=v1", "-uall"], {
|
|
15549
16062
|
cwd: options.worktreeCwd,
|
|
15550
16063
|
encoding: "utf8"
|
|
@@ -15577,27 +16090,9 @@ function collectObservedReconciliationPlanningDiff(options) {
|
|
|
15577
16090
|
)
|
|
15578
16091
|
)
|
|
15579
16092
|
);
|
|
15580
|
-
const intrinsicallyRejectedPaths = candidatePaths.filter(
|
|
15581
|
-
(path9) => !isAllowedPlanningPath(path9) || isBlockedPlanningPath(path9) || hasDirectoryTraversal(path9)
|
|
15582
|
-
);
|
|
15583
|
-
const allowedCandidatePaths = candidatePaths.filter(
|
|
15584
|
-
(path9) => !intrinsicallyRejectedPaths.includes(path9)
|
|
15585
|
-
);
|
|
15586
|
-
const validation = validateChangedPlanningPaths(allowedCandidatePaths, {
|
|
15587
|
-
repoRoot: options.worktreeCwd,
|
|
15588
|
-
gitStatusShortLines,
|
|
15589
|
-
gitDiffNameStatusLines
|
|
15590
|
-
});
|
|
15591
|
-
const rejectedPaths = Array.from(
|
|
15592
|
-
/* @__PURE__ */ new Set([
|
|
15593
|
-
...intrinsicallyRejectedPaths,
|
|
15594
|
-
...validation.ok ? [] : validation.rejected ?? []
|
|
15595
|
-
])
|
|
15596
|
-
).sort();
|
|
15597
16093
|
return {
|
|
15598
16094
|
ok: true,
|
|
15599
|
-
|
|
15600
|
-
rejectedPaths
|
|
16095
|
+
dirtyPaths: candidatePaths.sort()
|
|
15601
16096
|
};
|
|
15602
16097
|
}
|
|
15603
16098
|
function gitRefExists(repoRoot2, ref) {
|
|
@@ -15622,7 +16117,7 @@ function parseGitStatusLine(line) {
|
|
|
15622
16117
|
return { status, path: path9.replace(/^"|"$/g, "") };
|
|
15623
16118
|
}
|
|
15624
16119
|
function validateManifestArtifactExistence(repoRoot2, manifest) {
|
|
15625
|
-
const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !
|
|
16120
|
+
const missingPaths = listManifestArtifactPaths(repoRoot2, manifest).filter((path9) => !existsSync17(path9)).map((path9) => toRepoRelativePath2(repoRoot2, path9));
|
|
15626
16121
|
if (missingPaths.length > 0) {
|
|
15627
16122
|
return {
|
|
15628
16123
|
ok: false,
|
|
@@ -16038,7 +16533,7 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
|
|
|
16038
16533
|
}
|
|
16039
16534
|
|
|
16040
16535
|
// commands/init.ts
|
|
16041
|
-
import { existsSync as
|
|
16536
|
+
import { existsSync as existsSync18, statSync } from "fs";
|
|
16042
16537
|
import {
|
|
16043
16538
|
copyFile,
|
|
16044
16539
|
mkdir as mkdir5,
|
|
@@ -16541,7 +17036,7 @@ async function computeFileChecksum(filePath) {
|
|
|
16541
17036
|
return createHash3("sha256").update(content).digest("hex");
|
|
16542
17037
|
}
|
|
16543
17038
|
function lockfileExists(root, name) {
|
|
16544
|
-
return
|
|
17039
|
+
return existsSync18(path5.join(root, name));
|
|
16545
17040
|
}
|
|
16546
17041
|
function detectPackageManager(root) {
|
|
16547
17042
|
if (lockfileExists(root, "pnpm-lock.yaml")) return "pnpm";
|
|
@@ -16585,7 +17080,7 @@ async function discoverLocalSource(sourcePath) {
|
|
|
16585
17080
|
async function discoverReadme(root) {
|
|
16586
17081
|
for (const name of ["README.md", "readme.md"]) {
|
|
16587
17082
|
const p = path5.join(root, name);
|
|
16588
|
-
if (
|
|
17083
|
+
if (existsSync18(p)) {
|
|
16589
17084
|
return p;
|
|
16590
17085
|
}
|
|
16591
17086
|
}
|
|
@@ -16595,7 +17090,7 @@ async function discoverAgentFiles(root) {
|
|
|
16595
17090
|
const files = [];
|
|
16596
17091
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
16597
17092
|
const p = path5.join(root, name);
|
|
16598
|
-
if (
|
|
17093
|
+
if (existsSync18(p)) {
|
|
16599
17094
|
files.push(p);
|
|
16600
17095
|
}
|
|
16601
17096
|
}
|
|
@@ -16603,7 +17098,7 @@ async function discoverAgentFiles(root) {
|
|
|
16603
17098
|
}
|
|
16604
17099
|
async function discoverMerlleState(root) {
|
|
16605
17100
|
const p = path5.join(root, ".pourkit", "state.json");
|
|
16606
|
-
return
|
|
17101
|
+
return existsSync18(p) ? p : null;
|
|
16607
17102
|
}
|
|
16608
17103
|
async function discoverAgentSkills(root) {
|
|
16609
17104
|
const dirs = [
|
|
@@ -16612,7 +17107,7 @@ async function discoverAgentSkills(root) {
|
|
|
16612
17107
|
];
|
|
16613
17108
|
const found = [];
|
|
16614
17109
|
for (const d of dirs) {
|
|
16615
|
-
if (
|
|
17110
|
+
if (existsSync18(d)) {
|
|
16616
17111
|
found.push(d);
|
|
16617
17112
|
}
|
|
16618
17113
|
}
|
|
@@ -16622,12 +17117,12 @@ async function discoverRootDomainDocs(root) {
|
|
|
16622
17117
|
const docs = [];
|
|
16623
17118
|
for (const name of ["CONTEXT.md", "CONTEXT-MAP.md"]) {
|
|
16624
17119
|
const p = path5.join(root, name);
|
|
16625
|
-
if (
|
|
17120
|
+
if (existsSync18(p)) {
|
|
16626
17121
|
docs.push(p);
|
|
16627
17122
|
}
|
|
16628
17123
|
}
|
|
16629
17124
|
const adrDir = path5.join(root, "docs", "adr");
|
|
16630
|
-
if (
|
|
17125
|
+
if (existsSync18(adrDir)) {
|
|
16631
17126
|
const entries = await readdir(adrDir, { withFileTypes: true });
|
|
16632
17127
|
for (const entry of entries) {
|
|
16633
17128
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -16770,7 +17265,7 @@ async function planInit(options) {
|
|
|
16770
17265
|
for (const file of skillFiles) {
|
|
16771
17266
|
const relPath = path5.relative(s, file);
|
|
16772
17267
|
const destPath = path5.join(targetRoot, ".agents", "skills", relPath);
|
|
16773
|
-
if (!
|
|
17268
|
+
if (!existsSync18(destPath)) {
|
|
16774
17269
|
operations.push({
|
|
16775
17270
|
kind: "copy",
|
|
16776
17271
|
sourcePath: file,
|
|
@@ -16833,7 +17328,7 @@ async function planInit(options) {
|
|
|
16833
17328
|
});
|
|
16834
17329
|
}
|
|
16835
17330
|
if (sourceRoot) {
|
|
16836
|
-
if (!
|
|
17331
|
+
if (!existsSync18(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
|
|
16837
17332
|
warnings.push(
|
|
16838
17333
|
`--from-local path does not exist or is not a directory: ${sourceRoot}`
|
|
16839
17334
|
);
|
|
@@ -16952,7 +17447,7 @@ async function planInit(options) {
|
|
|
16952
17447
|
requiresConfirmation: false,
|
|
16953
17448
|
destructive: false
|
|
16954
17449
|
});
|
|
16955
|
-
} else if (
|
|
17450
|
+
} else if (existsSync18(destPath)) {
|
|
16956
17451
|
operations.push({
|
|
16957
17452
|
kind: "skip",
|
|
16958
17453
|
path: destPath,
|
|
@@ -17010,7 +17505,7 @@ async function planInit(options) {
|
|
|
17010
17505
|
}
|
|
17011
17506
|
}
|
|
17012
17507
|
const contextPath = path5.join(targetRoot, ".pourkit", "CONTEXT.md");
|
|
17013
|
-
if (!
|
|
17508
|
+
if (!existsSync18(contextPath) && !merleDestPaths.has(contextPath)) {
|
|
17014
17509
|
operations.push({
|
|
17015
17510
|
kind: "create",
|
|
17016
17511
|
path: contextPath,
|
|
@@ -17028,7 +17523,7 @@ async function planInit(options) {
|
|
|
17028
17523
|
"adr",
|
|
17029
17524
|
".gitkeep"
|
|
17030
17525
|
);
|
|
17031
|
-
if (!
|
|
17526
|
+
if (!existsSync18(adrGitkeep)) {
|
|
17032
17527
|
operations.push({
|
|
17033
17528
|
kind: "create",
|
|
17034
17529
|
path: adrGitkeep,
|
|
@@ -17040,7 +17535,7 @@ async function planInit(options) {
|
|
|
17040
17535
|
}
|
|
17041
17536
|
const srcDocAgents = path5.join(sourceRoot, ".pourkit", "docs", "agents");
|
|
17042
17537
|
const tgtDocAgents = path5.join(targetRoot, ".pourkit", "docs", "agents");
|
|
17043
|
-
if (
|
|
17538
|
+
if (existsSync18(srcDocAgents) && !existsSync18(tgtDocAgents)) {
|
|
17044
17539
|
const docFiles = await walkDir(srcDocAgents);
|
|
17045
17540
|
for (const file of docFiles) {
|
|
17046
17541
|
const relPath = path5.relative(srcDocAgents, file);
|
|
@@ -17073,7 +17568,7 @@ async function planInit(options) {
|
|
|
17073
17568
|
}
|
|
17074
17569
|
const srcPrompts = path5.join(sourceRoot, ".pourkit", "prompts");
|
|
17075
17570
|
const tgtPrompts = path5.join(targetRoot, ".pourkit", "prompts");
|
|
17076
|
-
if (
|
|
17571
|
+
if (existsSync18(srcPrompts) && !existsSync18(tgtPrompts)) {
|
|
17077
17572
|
const promptFiles = await walkDir(srcPrompts);
|
|
17078
17573
|
for (const file of promptFiles) {
|
|
17079
17574
|
const relPath = path5.relative(srcPrompts, file);
|
|
@@ -17100,7 +17595,7 @@ async function planInit(options) {
|
|
|
17100
17595
|
".sandcastle",
|
|
17101
17596
|
"Dockerfile"
|
|
17102
17597
|
);
|
|
17103
|
-
if (
|
|
17598
|
+
if (existsSync18(tgtSandboxDockerfile)) {
|
|
17104
17599
|
operations.push({
|
|
17105
17600
|
kind: "skip",
|
|
17106
17601
|
path: tgtSandboxDockerfile,
|
|
@@ -17109,7 +17604,7 @@ async function planInit(options) {
|
|
|
17109
17604
|
requiresConfirmation: false,
|
|
17110
17605
|
destructive: false
|
|
17111
17606
|
});
|
|
17112
|
-
} else if (
|
|
17607
|
+
} else if (existsSync18(srcSandboxDockerfile)) {
|
|
17113
17608
|
const checksum = await computeFileChecksum(srcSandboxDockerfile);
|
|
17114
17609
|
operations.push({
|
|
17115
17610
|
kind: "copy",
|
|
@@ -17123,7 +17618,7 @@ async function planInit(options) {
|
|
|
17123
17618
|
});
|
|
17124
17619
|
}
|
|
17125
17620
|
const configTsPath = path5.join(targetRoot, "pourkit.config.ts");
|
|
17126
|
-
if (!
|
|
17621
|
+
if (!existsSync18(configTsPath)) {
|
|
17127
17622
|
const verifyCommands = inferVerificationCommands(
|
|
17128
17623
|
packageScripts,
|
|
17129
17624
|
pm || "npm"
|
|
@@ -17160,7 +17655,7 @@ async function planInit(options) {
|
|
|
17160
17655
|
const hasExistingAgents = operations.some(
|
|
17161
17656
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("AGENTS.md")
|
|
17162
17657
|
);
|
|
17163
|
-
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !
|
|
17658
|
+
if ((agentFileMode === "agents" || agentFileMode === "both") && !hasExistingAgents && !existsSync18(path5.join(targetRoot, "AGENTS.md"))) {
|
|
17164
17659
|
operations.push({
|
|
17165
17660
|
kind: "create",
|
|
17166
17661
|
path: path5.join(targetRoot, "AGENTS.md"),
|
|
@@ -17176,7 +17671,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
17176
17671
|
const hasExistingClaude = operations.some(
|
|
17177
17672
|
(op) => (op.kind === "skip" || op.kind === "update") && op.path?.endsWith("CLAUDE.md")
|
|
17178
17673
|
);
|
|
17179
|
-
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !
|
|
17674
|
+
if ((agentFileMode === "claude" || agentFileMode === "both") && !hasExistingClaude && !existsSync18(path5.join(targetRoot, "CLAUDE.md"))) {
|
|
17180
17675
|
operations.push({
|
|
17181
17676
|
kind: "create",
|
|
17182
17677
|
path: path5.join(targetRoot, "CLAUDE.md"),
|
|
@@ -17191,7 +17686,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
17191
17686
|
}
|
|
17192
17687
|
const gitignoreTarget = path5.join(targetRoot, ".gitignore");
|
|
17193
17688
|
const gitignoreContent = generateGitignoreBlock();
|
|
17194
|
-
if (!
|
|
17689
|
+
if (!existsSync18(gitignoreTarget)) {
|
|
17195
17690
|
operations.push({
|
|
17196
17691
|
kind: "create",
|
|
17197
17692
|
path: gitignoreTarget,
|
|
@@ -17215,7 +17710,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
17215
17710
|
});
|
|
17216
17711
|
}
|
|
17217
17712
|
const openCodePath = path5.join(targetRoot, "opencode.json");
|
|
17218
|
-
if (!
|
|
17713
|
+
if (!existsSync18(openCodePath)) {
|
|
17219
17714
|
operations.push({
|
|
17220
17715
|
kind: "create",
|
|
17221
17716
|
path: openCodePath,
|
|
@@ -17272,7 +17767,7 @@ ${gitignoreContent}${MANAGED_BLOCK_END}
|
|
|
17272
17767
|
}
|
|
17273
17768
|
}
|
|
17274
17769
|
const manifestPath = path5.join(targetRoot, ".pourkit", "manifest.json");
|
|
17275
|
-
if (
|
|
17770
|
+
if (existsSync18(manifestPath)) {
|
|
17276
17771
|
operations.push({
|
|
17277
17772
|
kind: "skip",
|
|
17278
17773
|
path: manifestPath,
|
|
@@ -17591,7 +18086,7 @@ async function updateManagedBlock(filePath, content) {
|
|
|
17591
18086
|
const blockContent = `${MANAGED_BLOCK_BEGIN}
|
|
17592
18087
|
${content}${MANAGED_BLOCK_END}
|
|
17593
18088
|
`;
|
|
17594
|
-
if (!
|
|
18089
|
+
if (!existsSync18(filePath)) {
|
|
17595
18090
|
const dir = path5.dirname(filePath);
|
|
17596
18091
|
await mkdir5(dir, { recursive: true });
|
|
17597
18092
|
await writeFileAtomic(filePath, blockContent);
|
|
@@ -17620,7 +18115,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
|
17620
18115
|
if (op.requiresConfirmation) continue;
|
|
17621
18116
|
const relPath = path5.relative(plan.targetRoot, op.path);
|
|
17622
18117
|
if (relPath === ".pourkit/manifest.json") continue;
|
|
17623
|
-
if (
|
|
18118
|
+
if (existsSync18(op.path)) {
|
|
17624
18119
|
const sha256 = await computeFileChecksum(op.path);
|
|
17625
18120
|
assets[relPath] = {
|
|
17626
18121
|
ownership: op.ownership || "managed",
|
|
@@ -17665,7 +18160,7 @@ async function applyInitPlan(plan, options) {
|
|
|
17665
18160
|
skipped++;
|
|
17666
18161
|
continue;
|
|
17667
18162
|
}
|
|
17668
|
-
if (
|
|
18163
|
+
if (existsSync18(op.path) && !op.destructive) {
|
|
17669
18164
|
skipped++;
|
|
17670
18165
|
continue;
|
|
17671
18166
|
}
|
|
@@ -17680,7 +18175,7 @@ async function applyInitPlan(plan, options) {
|
|
|
17680
18175
|
skipped++;
|
|
17681
18176
|
continue;
|
|
17682
18177
|
}
|
|
17683
|
-
if (
|
|
18178
|
+
if (existsSync18(op.path)) {
|
|
17684
18179
|
skipped++;
|
|
17685
18180
|
continue;
|
|
17686
18181
|
}
|
|
@@ -17709,7 +18204,7 @@ async function applyInitPlan(plan, options) {
|
|
|
17709
18204
|
skipped++;
|
|
17710
18205
|
continue;
|
|
17711
18206
|
}
|
|
17712
|
-
if (
|
|
18207
|
+
if (existsSync18(op.path)) {
|
|
17713
18208
|
skipped++;
|
|
17714
18209
|
continue;
|
|
17715
18210
|
}
|
|
@@ -17849,7 +18344,7 @@ async function applyInitFromSource(options) {
|
|
|
17849
18344
|
if (!manifestSkipped) {
|
|
17850
18345
|
const agentFiles = [];
|
|
17851
18346
|
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
17852
|
-
if (
|
|
18347
|
+
if (existsSync18(path5.join(targetRoot, name))) {
|
|
17853
18348
|
agentFiles.push(path5.join(targetRoot, name));
|
|
17854
18349
|
}
|
|
17855
18350
|
}
|
|
@@ -18668,8 +19163,8 @@ function formatChecks2(checks) {
|
|
|
18668
19163
|
init_common();
|
|
18669
19164
|
|
|
18670
19165
|
// execution/sandcastle-execution.ts
|
|
18671
|
-
import { mkdirSync as
|
|
18672
|
-
import { join as
|
|
19166
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync8 } from "fs";
|
|
19167
|
+
import { join as join22 } from "path";
|
|
18673
19168
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
18674
19169
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
18675
19170
|
|
|
@@ -18678,10 +19173,10 @@ init_common();
|
|
|
18678
19173
|
import { mkdtempSync as mkdtempSync2 } from "fs";
|
|
18679
19174
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
18680
19175
|
import { tmpdir as tmpdir2 } from "os";
|
|
18681
|
-
import { dirname as dirname6, join as
|
|
19176
|
+
import { dirname as dirname6, join as join21 } from "path";
|
|
18682
19177
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
18683
19178
|
for (const artifact of artifacts) {
|
|
18684
|
-
const filePath =
|
|
19179
|
+
const filePath = join21(worktreePath, artifact.path);
|
|
18685
19180
|
await ensureDir(dirname6(filePath));
|
|
18686
19181
|
await writeFile3(filePath, artifact.content, "utf-8");
|
|
18687
19182
|
}
|
|
@@ -18693,17 +19188,17 @@ import path7 from "path";
|
|
|
18693
19188
|
|
|
18694
19189
|
// execution/sandbox-image.ts
|
|
18695
19190
|
import { createHash as createHash4 } from "crypto";
|
|
18696
|
-
import { existsSync as
|
|
19191
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
|
|
18697
19192
|
import path6 from "path";
|
|
18698
19193
|
function sandboxImageName(repoRoot2) {
|
|
18699
19194
|
const dirName = path6.basename(repoRoot2.replace(/[\\/]+$/, "")) || "local";
|
|
18700
19195
|
const sanitized = dirName.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
|
|
18701
19196
|
const baseName = sanitized || "local";
|
|
18702
19197
|
const dockerfilePath = path6.join(repoRoot2, ".sandcastle", "Dockerfile");
|
|
18703
|
-
if (!
|
|
19198
|
+
if (!existsSync19(dockerfilePath)) {
|
|
18704
19199
|
return `sandcastle:${baseName}`;
|
|
18705
19200
|
}
|
|
18706
|
-
const fingerprint = createHash4("sha256").update(
|
|
19201
|
+
const fingerprint = createHash4("sha256").update(readFileSync17(dockerfilePath)).digest("hex").slice(0, 8);
|
|
18707
19202
|
return `sandcastle:${baseName}-${fingerprint}`;
|
|
18708
19203
|
}
|
|
18709
19204
|
|
|
@@ -18853,11 +19348,7 @@ var SandcastleExecutionSession = class {
|
|
|
18853
19348
|
type: "file",
|
|
18854
19349
|
path: logPath,
|
|
18855
19350
|
onAgentStreamEvent: (event) => {
|
|
18856
|
-
|
|
18857
|
-
logger.raw(event.message);
|
|
18858
|
-
} else if (event.type === "toolCall") {
|
|
18859
|
-
logger.raw(`${event.name}(${event.formattedArgs})`);
|
|
18860
|
-
}
|
|
19351
|
+
logger.raw(formatAgentStreamEvent(event));
|
|
18861
19352
|
}
|
|
18862
19353
|
},
|
|
18863
19354
|
completionSignal: "<promise>COMPLETE</promise>",
|
|
@@ -18942,14 +19433,59 @@ function resolveSandboxProvider(provider, dockerFactory) {
|
|
|
18942
19433
|
function sanitizeBranch(branchName) {
|
|
18943
19434
|
return branchName.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
18944
19435
|
}
|
|
19436
|
+
function formatAgentStreamEvent(event) {
|
|
19437
|
+
if (event.type === "text") {
|
|
19438
|
+
return JSON.stringify({ type: "text", textCount: event.message.length });
|
|
19439
|
+
}
|
|
19440
|
+
const parsedArgs = parseToolCallArgs(event.formattedArgs);
|
|
19441
|
+
if (!parsedArgs.ok) {
|
|
19442
|
+
return JSON.stringify({
|
|
19443
|
+
type: "toolCall",
|
|
19444
|
+
name: event.name,
|
|
19445
|
+
argsTextCount: event.formattedArgs.length
|
|
19446
|
+
});
|
|
19447
|
+
}
|
|
19448
|
+
return JSON.stringify({
|
|
19449
|
+
type: "toolCall",
|
|
19450
|
+
name: event.name,
|
|
19451
|
+
args: summarizeToolCallArgs(event.name, parsedArgs.value)
|
|
19452
|
+
});
|
|
19453
|
+
}
|
|
19454
|
+
function parseToolCallArgs(formattedArgs) {
|
|
19455
|
+
try {
|
|
19456
|
+
return { ok: true, value: JSON.parse(formattedArgs) };
|
|
19457
|
+
} catch {
|
|
19458
|
+
return { ok: false };
|
|
19459
|
+
}
|
|
19460
|
+
}
|
|
19461
|
+
function summarizeToolCallArgs(name, args) {
|
|
19462
|
+
if (!isPlainObject(args)) {
|
|
19463
|
+
return args;
|
|
19464
|
+
}
|
|
19465
|
+
if (name.toLowerCase() !== "write") {
|
|
19466
|
+
return args;
|
|
19467
|
+
}
|
|
19468
|
+
const summarizedArgs = {};
|
|
19469
|
+
for (const [key, value] of Object.entries(args)) {
|
|
19470
|
+
if (key === "content" && typeof value === "string") {
|
|
19471
|
+
summarizedArgs.contentCount = value.length;
|
|
19472
|
+
continue;
|
|
19473
|
+
}
|
|
19474
|
+
summarizedArgs[key] = value;
|
|
19475
|
+
}
|
|
19476
|
+
return summarizedArgs;
|
|
19477
|
+
}
|
|
19478
|
+
function isPlainObject(value) {
|
|
19479
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19480
|
+
}
|
|
18945
19481
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
18946
|
-
const promptsDir =
|
|
18947
|
-
|
|
19482
|
+
const promptsDir = join22(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
19483
|
+
mkdirSync12(promptsDir, { recursive: true });
|
|
18948
19484
|
const timestamp2 = Date.now();
|
|
18949
19485
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
18950
19486
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
18951
|
-
const filePath =
|
|
18952
|
-
|
|
19487
|
+
const filePath = join22(promptsDir, filename);
|
|
19488
|
+
writeFileSync8(filePath, prompt, "utf-8");
|
|
18953
19489
|
}
|
|
18954
19490
|
|
|
18955
19491
|
// cli.ts
|
|
@@ -18957,10 +19493,13 @@ function normalizePrdRef(ref) {
|
|
|
18957
19493
|
const normalized = ref.trim().toUpperCase();
|
|
18958
19494
|
if (!/^PRD-\d+$/.test(normalized)) {
|
|
18959
19495
|
throw new Error(
|
|
18960
|
-
`Invalid PRD ref "${ref}". Expected format: PRD-<number> (e.g., PRD-
|
|
19496
|
+
`Invalid PRD ref "${ref}". Expected format: PRD-<number> (e.g., PRD-0021)`
|
|
18961
19497
|
);
|
|
18962
19498
|
}
|
|
18963
|
-
return normalized
|
|
19499
|
+
return normalized.replace(
|
|
19500
|
+
/^(PRD-)(\d+)$/,
|
|
19501
|
+
(_, p, n) => `${p}${n.padStart(4, "0")}`
|
|
19502
|
+
);
|
|
18964
19503
|
}
|
|
18965
19504
|
function buildPrCreateArgs(options) {
|
|
18966
19505
|
const args = ["--target", options.target, "--title", options.title];
|
|
@@ -19015,6 +19554,35 @@ function createCliProgram(version) {
|
|
|
19015
19554
|
const program = new Command();
|
|
19016
19555
|
program.name("pourkit").version(version).exitOverride().description("AI-driven issue-to-PR workflow for GitHub repositories.");
|
|
19017
19556
|
const issueCommand = program.command("issue");
|
|
19557
|
+
program.command("run-verification").description("Run configured verification commands for a target").option("--target <name>", "target name").option("--cwd <path>", "target repository directory").action(async (options) => {
|
|
19558
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
19559
|
+
const logPath = path8.join(
|
|
19560
|
+
targetRepoRoot,
|
|
19561
|
+
".pourkit",
|
|
19562
|
+
"logs",
|
|
19563
|
+
"run-verification.log"
|
|
19564
|
+
);
|
|
19565
|
+
const logger = createLogger("pourkit", logPath);
|
|
19566
|
+
let failed = false;
|
|
19567
|
+
try {
|
|
19568
|
+
const config = await loadRepoConfig(targetRepoRoot);
|
|
19569
|
+
const target = resolveTarget(config, options.target);
|
|
19570
|
+
const result = await runVerificationCommands({
|
|
19571
|
+
target,
|
|
19572
|
+
cwd: targetRepoRoot,
|
|
19573
|
+
logger
|
|
19574
|
+
});
|
|
19575
|
+
console.log(JSON.stringify(result, null, 2));
|
|
19576
|
+
failed = !result.ok;
|
|
19577
|
+
} catch (error) {
|
|
19578
|
+
await handleError(logger, error);
|
|
19579
|
+
} finally {
|
|
19580
|
+
await logger.close();
|
|
19581
|
+
}
|
|
19582
|
+
if (failed) {
|
|
19583
|
+
process.exit(1);
|
|
19584
|
+
}
|
|
19585
|
+
});
|
|
19018
19586
|
program.command("validate-artifact").description("Validate an agent handoff artifact").argument(
|
|
19019
19587
|
"<kind>",
|
|
19020
19588
|
"artifact kind: reviewer, refactor, finalizer, conflict-resolution, failure-resolution, reconciliation, planning-manifest, local-prd, local-issue, local-triage, or local-prepare"
|
|
@@ -19790,11 +20358,11 @@ function createCliProgram(version) {
|
|
|
19790
20358
|
return program;
|
|
19791
20359
|
}
|
|
19792
20360
|
async function resolveCliVersion() {
|
|
19793
|
-
if (isPackageVersion("0.0.0-next-
|
|
19794
|
-
return "0.0.0-next-
|
|
20361
|
+
if (isPackageVersion("0.0.0-next-20260608072322")) {
|
|
20362
|
+
return "0.0.0-next-20260608072322";
|
|
19795
20363
|
}
|
|
19796
|
-
if (isReleaseVersion("0.0.0-next-
|
|
19797
|
-
return "0.0.0-next-
|
|
20364
|
+
if (isReleaseVersion("0.0.0-next-20260608072322")) {
|
|
20365
|
+
return "0.0.0-next-20260608072322";
|
|
19798
20366
|
}
|
|
19799
20367
|
try {
|
|
19800
20368
|
const root = repoRoot();
|