@pourkit/cli 0.0.0-next-20260625073725 → 0.0.0-next-20260627024509
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 +2537 -581
- package/dist/cli.js.map +1 -1
- package/dist/e2e/run-live-e2e.js +288 -37
- package/dist/e2e/run-live-e2e.js.map +1 -1
- package/dist/managed/docs/agents/domain.md +2 -0
- package/dist/managed/skills/publish-prd/SKILL.md +162 -19
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -58,20 +58,20 @@ function createLogger(name, filePath) {
|
|
|
58
58
|
);
|
|
59
59
|
},
|
|
60
60
|
async close() {
|
|
61
|
-
await new Promise((
|
|
61
|
+
await new Promise((resolve6) => {
|
|
62
62
|
if (!fileStream) {
|
|
63
|
-
|
|
63
|
+
resolve6();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
const timer = setTimeout(() => {
|
|
67
67
|
if (!fileStream.destroyed) {
|
|
68
68
|
fileStream.destroy();
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
resolve6();
|
|
71
71
|
}, 2e3);
|
|
72
72
|
fileStream.end(() => {
|
|
73
73
|
clearTimeout(timer);
|
|
74
|
-
|
|
74
|
+
resolve6();
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
}
|
|
@@ -266,7 +266,7 @@ async function execJson(command, args, options = {}) {
|
|
|
266
266
|
return JSON.parse(result.stdout);
|
|
267
267
|
}
|
|
268
268
|
function sleep(ms) {
|
|
269
|
-
return new Promise((
|
|
269
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
270
270
|
}
|
|
271
271
|
async function execCaptureWithRetry(command, args, options = {}) {
|
|
272
272
|
const retries = options.retries ?? 3;
|
|
@@ -914,8 +914,8 @@ async function loadRepoConfig(repoRoot2, _configFileName) {
|
|
|
914
914
|
`No Pourkit config found at ${CANONICAL_CONFIG_PATH}. Run pourkit init or create ${CANONICAL_CONFIG_PATH} with "$schema": "./schema/pourkit.schema.json".`
|
|
915
915
|
);
|
|
916
916
|
}
|
|
917
|
-
const { readFile:
|
|
918
|
-
const raw = await
|
|
917
|
+
const { readFile: readFile8 } = await import("fs/promises");
|
|
918
|
+
const raw = await readFile8(configPath, "utf-8");
|
|
919
919
|
let parsed;
|
|
920
920
|
try {
|
|
921
921
|
parsed = JSON.parse(raw);
|
|
@@ -926,10 +926,10 @@ async function loadRepoConfig(repoRoot2, _configFileName) {
|
|
|
926
926
|
return parseConfig(parsed);
|
|
927
927
|
}
|
|
928
928
|
async function loadConfig(configPath) {
|
|
929
|
-
const { readFile:
|
|
929
|
+
const { readFile: readFile8 } = await import("fs/promises");
|
|
930
930
|
const ext = configPath.split(".").pop()?.toLowerCase();
|
|
931
931
|
if (ext === "json") {
|
|
932
|
-
const raw = await
|
|
932
|
+
const raw = await readFile8(configPath, "utf-8");
|
|
933
933
|
return parseConfig(JSON.parse(raw));
|
|
934
934
|
}
|
|
935
935
|
if (ext === "mjs" || ext === "js" || ext === "ts") {
|
|
@@ -1086,10 +1086,10 @@ async function removeStaleWorktree(candidate, repoRoot2, logger) {
|
|
|
1086
1086
|
}
|
|
1087
1087
|
}
|
|
1088
1088
|
async function removeExpiredFiles(dirPath, retentionDays) {
|
|
1089
|
-
const { readdir:
|
|
1089
|
+
const { readdir: readdir5, stat, unlink, access: access2 } = await import("fs/promises");
|
|
1090
1090
|
let entries;
|
|
1091
1091
|
try {
|
|
1092
|
-
entries = await
|
|
1092
|
+
entries = await readdir5(dirPath);
|
|
1093
1093
|
} catch {
|
|
1094
1094
|
return;
|
|
1095
1095
|
}
|
|
@@ -1152,8 +1152,8 @@ async function cleanupRepository(options) {
|
|
|
1152
1152
|
|
|
1153
1153
|
// commands/artifact-validation.ts
|
|
1154
1154
|
import { execSync } from "child_process";
|
|
1155
|
-
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
1156
|
-
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
1155
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
1156
|
+
import { isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "path";
|
|
1157
1157
|
|
|
1158
1158
|
// pr/review-verdict.ts
|
|
1159
1159
|
var ReviewVerdictProtocolError = class extends Error {
|
|
@@ -3237,6 +3237,13 @@ var ISSUE_FINAL_REVIEW_SCOPE_VERDICTS = [
|
|
|
3237
3237
|
"incidental",
|
|
3238
3238
|
"out-of-scope"
|
|
3239
3239
|
];
|
|
3240
|
+
function escapeRegExp(value) {
|
|
3241
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3242
|
+
}
|
|
3243
|
+
function normalizeChildRef(value) {
|
|
3244
|
+
const match = value.match(/^I-0*(\d+)$/i);
|
|
3245
|
+
return match ? `I-${match[1].padStart(2, "0")}` : value.toUpperCase();
|
|
3246
|
+
}
|
|
3240
3247
|
function invalid(options, reason, diagnostics = []) {
|
|
3241
3248
|
return {
|
|
3242
3249
|
ok: false,
|
|
@@ -3586,11 +3593,177 @@ function validateIssueFinalReviewArtifact(parsed, options) {
|
|
|
3586
3593
|
}
|
|
3587
3594
|
return { ok: true, verdict: parsed.verdict };
|
|
3588
3595
|
}
|
|
3596
|
+
function validatePrdPlanWorkspace(options) {
|
|
3597
|
+
const workspacePath = options.workspace || options.artifactPath;
|
|
3598
|
+
const prdRef = options.prdRef;
|
|
3599
|
+
if (!prdRef) {
|
|
3600
|
+
return invalid(options, "PRD plan workspace validation requires --prd-ref");
|
|
3601
|
+
}
|
|
3602
|
+
const diagnostics = [];
|
|
3603
|
+
const prdPath = join7(workspacePath, "PRD.md");
|
|
3604
|
+
if (!existsSync7(prdPath)) {
|
|
3605
|
+
return invalid(options, `PRD plan workspace missing PRD.md at ${prdPath}`);
|
|
3606
|
+
}
|
|
3607
|
+
let prdContent;
|
|
3608
|
+
try {
|
|
3609
|
+
prdContent = readFileSync7(prdPath, "utf-8");
|
|
3610
|
+
} catch {
|
|
3611
|
+
return invalid(options, `Cannot read PRD.md at ${prdPath}`);
|
|
3612
|
+
}
|
|
3613
|
+
const prdFirstLine = prdContent.split("\n")[0];
|
|
3614
|
+
const prdHeadingMatch = prdFirstLine.match(/^# (PRD-\d{4}):.*$/i);
|
|
3615
|
+
if (!prdFirstLine.startsWith("# ") || !prdHeadingMatch) {
|
|
3616
|
+
return invalid(
|
|
3617
|
+
options,
|
|
3618
|
+
"PRD.md must start with a heading: # PRD-NNNN: Title"
|
|
3619
|
+
);
|
|
3620
|
+
}
|
|
3621
|
+
if (prdHeadingMatch[1].toUpperCase() !== prdRef.toUpperCase()) {
|
|
3622
|
+
return invalid(
|
|
3623
|
+
options,
|
|
3624
|
+
`PRD.md heading ${prdHeadingMatch[1].toUpperCase()} does not match --prd-ref ${prdRef}`
|
|
3625
|
+
);
|
|
3626
|
+
}
|
|
3627
|
+
diagnostics.push(`PRD title: ${prdFirstLine.slice(2).trim()}`);
|
|
3628
|
+
const issuesDir = join7(workspacePath, "issues");
|
|
3629
|
+
if (!existsSync7(issuesDir)) {
|
|
3630
|
+
return invalid(
|
|
3631
|
+
options,
|
|
3632
|
+
`PRD plan workspace missing issues/ directory at ${issuesDir}`
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
let childFiles;
|
|
3636
|
+
const CHILD_FILE_PATTERN2 = /^I-0*(\d+)-.+\.md$/i;
|
|
3637
|
+
try {
|
|
3638
|
+
childFiles = readdirSync3(issuesDir).filter(
|
|
3639
|
+
(f) => CHILD_FILE_PATTERN2.test(f) && !f.startsWith(".")
|
|
3640
|
+
);
|
|
3641
|
+
} catch {
|
|
3642
|
+
return invalid(options, `Cannot read issues/ directory at ${issuesDir}`);
|
|
3643
|
+
}
|
|
3644
|
+
childFiles.sort((a, b) => {
|
|
3645
|
+
const aMatch = a.match(CHILD_FILE_PATTERN2);
|
|
3646
|
+
const bMatch = b.match(CHILD_FILE_PATTERN2);
|
|
3647
|
+
if (!aMatch || !bMatch) return 0;
|
|
3648
|
+
return Number(aMatch[1]) - Number(bMatch[1]);
|
|
3649
|
+
});
|
|
3650
|
+
if (childFiles.length === 0) {
|
|
3651
|
+
return invalid(
|
|
3652
|
+
options,
|
|
3653
|
+
`PRD plan workspace must contain at least one issues/I-0N-*.md child Issue`
|
|
3654
|
+
);
|
|
3655
|
+
}
|
|
3656
|
+
const children = [];
|
|
3657
|
+
const CHILD_REF_PATTERN2 = /^\s*(I-0*\d+)\s*$/i;
|
|
3658
|
+
const BLOCKED_BY_SECTION_REGEX2 = /## Blocked by\s*\n([\s\S]*?)(?=\n## |$)/i;
|
|
3659
|
+
const PARENT_SECTION_REGEX2 = /## Parent\s*\n([\s\S]*?)(?=\n## |$)/i;
|
|
3660
|
+
for (const childFile of childFiles) {
|
|
3661
|
+
const childPath = join7(issuesDir, childFile);
|
|
3662
|
+
let childContent;
|
|
3663
|
+
try {
|
|
3664
|
+
childContent = readFileSync7(childPath, "utf-8");
|
|
3665
|
+
} catch {
|
|
3666
|
+
diagnostics.push(`${childFile}: cannot read`);
|
|
3667
|
+
continue;
|
|
3668
|
+
}
|
|
3669
|
+
const childFileMatch = childFile.match(CHILD_FILE_PATTERN2);
|
|
3670
|
+
if (!childFileMatch) continue;
|
|
3671
|
+
const childRef = `I-${childFileMatch[1].padStart(2, "0")}`;
|
|
3672
|
+
const firstChildLine = childContent.split("\n")[0];
|
|
3673
|
+
if (!firstChildLine.startsWith("# ") || !new RegExp(`^# ${prdRef} / ${childRef}:`).test(firstChildLine)) {
|
|
3674
|
+
return invalid(
|
|
3675
|
+
options,
|
|
3676
|
+
`${childFile}: expected heading: # ${prdRef} / ${childRef}: Title`
|
|
3677
|
+
);
|
|
3678
|
+
}
|
|
3679
|
+
const parentSection = childContent.match(PARENT_SECTION_REGEX2)?.[1];
|
|
3680
|
+
const parentPrdRegex = new RegExp(`\\b${escapeRegExp(prdRef)}\\b`, "i");
|
|
3681
|
+
if (!parentSection || !parentPrdRegex.test(parentSection)) {
|
|
3682
|
+
return invalid(
|
|
3683
|
+
options,
|
|
3684
|
+
`${childFile}: missing ## Parent section pointing to ${prdRef}`
|
|
3685
|
+
);
|
|
3686
|
+
}
|
|
3687
|
+
const section = childContent.match(BLOCKED_BY_SECTION_REGEX2)?.[1];
|
|
3688
|
+
const blockedByRefs = [];
|
|
3689
|
+
if (section) {
|
|
3690
|
+
for (const rawLine of section.split("\n")) {
|
|
3691
|
+
const line = rawLine.trim();
|
|
3692
|
+
if (!line) continue;
|
|
3693
|
+
for (const part of line.split(",")) {
|
|
3694
|
+
const trimmed = part.trim();
|
|
3695
|
+
const match = trimmed.match(CHILD_REF_PATTERN2);
|
|
3696
|
+
if (match) {
|
|
3697
|
+
blockedByRefs.push(match[1].toUpperCase());
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
children.push({ childRef, blockedByRefs, sourcePath: childFile });
|
|
3703
|
+
diagnostics.push(`${childFile}: ${childRef}`);
|
|
3704
|
+
}
|
|
3705
|
+
const validChildRefs = new Set(children.map((c) => c.childRef));
|
|
3706
|
+
for (const child of children) {
|
|
3707
|
+
for (const blockedRef of child.blockedByRefs) {
|
|
3708
|
+
if (!validChildRefs.has(blockedRef.toUpperCase())) {
|
|
3709
|
+
return invalid(
|
|
3710
|
+
options,
|
|
3711
|
+
`Child ${child.childRef} references nonexistent child ${blockedRef} in Blocked by section`,
|
|
3712
|
+
[child.sourcePath]
|
|
3713
|
+
);
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
diagnostics.push(`children: ${children.length} total`);
|
|
3718
|
+
return valid(options, diagnostics);
|
|
3719
|
+
}
|
|
3720
|
+
function validatePrdPlan(content, options) {
|
|
3721
|
+
const firstLine = content.split("\n")[0];
|
|
3722
|
+
if (!firstLine.startsWith("# ") || !/^# PRD-\d{4}:/.test(firstLine)) {
|
|
3723
|
+
return invalid(
|
|
3724
|
+
options,
|
|
3725
|
+
"PRD plan must start with a heading: # PRD-NNNN: Title"
|
|
3726
|
+
);
|
|
3727
|
+
}
|
|
3728
|
+
const title = firstLine.slice(2).trim();
|
|
3729
|
+
return valid(options, [`title: ${title}`]);
|
|
3730
|
+
}
|
|
3731
|
+
function validatePrdChildIssue(content, options) {
|
|
3732
|
+
const firstLine = content.split("\n")[0];
|
|
3733
|
+
const match = firstLine.match(/^# (PRD-\d{4}) \/ (I-\d+):/);
|
|
3734
|
+
if (!firstLine.startsWith("# ") || !match) {
|
|
3735
|
+
return invalid(
|
|
3736
|
+
options,
|
|
3737
|
+
"Child issue must start with a heading: # PRD-NNNN / I-0N: Title"
|
|
3738
|
+
);
|
|
3739
|
+
}
|
|
3740
|
+
if (options.childId) {
|
|
3741
|
+
const normalizedHeadingChildId = normalizeChildRef(match[2]);
|
|
3742
|
+
const normalizedChildId = normalizeChildRef(options.childId);
|
|
3743
|
+
if (normalizedHeadingChildId !== normalizedChildId) {
|
|
3744
|
+
return invalid(
|
|
3745
|
+
options,
|
|
3746
|
+
`Child issue heading ${normalizedHeadingChildId} does not match --child-id ${normalizedChildId}`
|
|
3747
|
+
);
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
const title = firstLine.slice(2).trim();
|
|
3751
|
+
return valid(options, [`title: ${title}`]);
|
|
3752
|
+
}
|
|
3589
3753
|
function validateAgentArtifact(options) {
|
|
3754
|
+
if (options.kind === "prd-plan-workspace") {
|
|
3755
|
+
return validatePrdPlanWorkspace(options);
|
|
3756
|
+
}
|
|
3590
3757
|
const artifact = readArtifact(options);
|
|
3591
3758
|
if (!artifact.ok) return artifact.result;
|
|
3592
3759
|
try {
|
|
3593
3760
|
switch (options.kind) {
|
|
3761
|
+
case "prd-plan": {
|
|
3762
|
+
return validatePrdPlan(artifact.content, options);
|
|
3763
|
+
}
|
|
3764
|
+
case "prd-child-issue": {
|
|
3765
|
+
return validatePrdChildIssue(artifact.content, options);
|
|
3766
|
+
}
|
|
3594
3767
|
case "reviewer": {
|
|
3595
3768
|
const iteration = options.iteration ?? 1;
|
|
3596
3769
|
const verdict = parseReviewVerdict(artifact.content);
|
|
@@ -3697,10 +3870,12 @@ function validateAgentArtifact(options) {
|
|
|
3697
3870
|
function runValidateArtifactCommand(options) {
|
|
3698
3871
|
const artifactPath = resolve2(options.repoRoot, options.artifactPath);
|
|
3699
3872
|
const latestReviewArtifactPath = options.latestReviewArtifactPath ? resolve2(options.repoRoot, options.latestReviewArtifactPath) : void 0;
|
|
3873
|
+
const workspace = options.workspace ? resolve2(options.repoRoot, options.workspace) : void 0;
|
|
3700
3874
|
return validateAgentArtifact({
|
|
3701
3875
|
...options,
|
|
3702
3876
|
artifactPath,
|
|
3703
3877
|
latestReviewArtifactPath,
|
|
3878
|
+
workspace,
|
|
3704
3879
|
worktreePath: options.repoRoot,
|
|
3705
3880
|
baseRef: options.baseRef
|
|
3706
3881
|
});
|
|
@@ -3878,15 +4053,15 @@ function invalidateAfterBaseRefresh(state) {
|
|
|
3878
4053
|
|
|
3879
4054
|
// shared/attempt-log.ts
|
|
3880
4055
|
import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync8 } from "fs";
|
|
3881
|
-
import { dirname as dirname4, join as
|
|
4056
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
3882
4057
|
var ATTEMPT_LOG_PATH = ".pourkit/attempt-log.jsonl";
|
|
3883
4058
|
function writeAttemptLog(worktreePath, entry) {
|
|
3884
|
-
const logPath =
|
|
4059
|
+
const logPath = join8(worktreePath, ATTEMPT_LOG_PATH);
|
|
3885
4060
|
mkdirSync6(dirname4(logPath), { recursive: true });
|
|
3886
4061
|
appendFileSync(logPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
3887
4062
|
}
|
|
3888
4063
|
function readAttemptLog(worktreePath) {
|
|
3889
|
-
const logPath =
|
|
4064
|
+
const logPath = join8(worktreePath, ATTEMPT_LOG_PATH);
|
|
3890
4065
|
if (!existsSync8(logPath)) {
|
|
3891
4066
|
return [];
|
|
3892
4067
|
}
|
|
@@ -4010,12 +4185,12 @@ async function runBaseRefreshAttempt(options) {
|
|
|
4010
4185
|
|
|
4011
4186
|
// commands/conflict-resolution.ts
|
|
4012
4187
|
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
4013
|
-
import { join as
|
|
4188
|
+
import { join as join9 } from "path";
|
|
4014
4189
|
init_common();
|
|
4015
4190
|
var CONFLICT_MARKER_PATTERN2 = /<<<<<<<|=======|>>>>>>>/m;
|
|
4016
4191
|
async function hasUnresolvedConflictMarkers(worktreePath, files) {
|
|
4017
4192
|
for (const file of files) {
|
|
4018
|
-
const filePath =
|
|
4193
|
+
const filePath = join9(worktreePath, file);
|
|
4019
4194
|
try {
|
|
4020
4195
|
const content = readFileSync9(filePath, "utf-8");
|
|
4021
4196
|
if (CONFLICT_MARKER_PATTERN2.test(content)) {
|
|
@@ -4029,7 +4204,7 @@ async function hasUnresolvedConflictMarkers(worktreePath, files) {
|
|
|
4029
4204
|
|
|
4030
4205
|
// failure-resolution/failure-resolution-agent.ts
|
|
4031
4206
|
import { readFileSync as readFileSync10 } from "fs";
|
|
4032
|
-
import { join as
|
|
4207
|
+
import { join as join10 } from "path";
|
|
4033
4208
|
|
|
4034
4209
|
// failure-resolution/recovery-policy.ts
|
|
4035
4210
|
function isSecuritySensitiveFailure(failure) {
|
|
@@ -4105,7 +4280,7 @@ async function runFailureResolutionAgent(options) {
|
|
|
4105
4280
|
} = options;
|
|
4106
4281
|
const frConfig = target.strategy.failureResolution;
|
|
4107
4282
|
const artifactPath = packet.artifactTarget;
|
|
4108
|
-
const fullArtifactPath =
|
|
4283
|
+
const fullArtifactPath = join10(worktreePath, artifactPath);
|
|
4109
4284
|
const fingerprint = computeFailureFingerprint(packet.stageName, failure._tag);
|
|
4110
4285
|
const prompt = [
|
|
4111
4286
|
`# Failure Resolution: ${packet.failureType}`,
|
|
@@ -4731,7 +4906,7 @@ async function canReachMcp(url) {
|
|
|
4731
4906
|
return true;
|
|
4732
4907
|
} catch {
|
|
4733
4908
|
if (attempt < 9) {
|
|
4734
|
-
await new Promise((
|
|
4909
|
+
await new Promise((resolve6) => setTimeout(resolve6, 100));
|
|
4735
4910
|
}
|
|
4736
4911
|
}
|
|
4737
4912
|
}
|
|
@@ -4958,11 +5133,11 @@ async function assertCanonicalBaseAncestor(options) {
|
|
|
4958
5133
|
|
|
4959
5134
|
// issues/pr-description-agent.ts
|
|
4960
5135
|
import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
|
|
4961
|
-
import { isAbsolute as isAbsolute4, join as
|
|
5136
|
+
import { isAbsolute as isAbsolute4, join as join13 } from "path";
|
|
4962
5137
|
|
|
4963
5138
|
// pr/pr-description-context.ts
|
|
4964
5139
|
init_common();
|
|
4965
|
-
import { join as
|
|
5140
|
+
import { join as join11 } from "path";
|
|
4966
5141
|
import { readFile } from "fs/promises";
|
|
4967
5142
|
import { Effect as Effect6 } from "effect";
|
|
4968
5143
|
function collectFinalizerContextEffect(options) {
|
|
@@ -5027,7 +5202,7 @@ function remoteTargetBase(targetBase) {
|
|
|
5027
5202
|
return targetBase.includes("/") ? targetBase : `origin/${targetBase}`;
|
|
5028
5203
|
}
|
|
5029
5204
|
function buildFinalizerPrompt(context, promptTemplate) {
|
|
5030
|
-
const artifactPathInWorktree =
|
|
5205
|
+
const artifactPathInWorktree = join11(
|
|
5031
5206
|
".pourkit",
|
|
5032
5207
|
".tmp",
|
|
5033
5208
|
"finalizer",
|
|
@@ -5082,9 +5257,9 @@ Before handoff, run: pourkit validate-artifact finalizer ${artifactPathInWorktre
|
|
|
5082
5257
|
|
|
5083
5258
|
// issues/issue-final-review.ts
|
|
5084
5259
|
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
5085
|
-
import { isAbsolute as isAbsolute3, join as
|
|
5260
|
+
import { isAbsolute as isAbsolute3, join as join12, relative as relative2, sep as sep2 } from "path";
|
|
5086
5261
|
init_common();
|
|
5087
|
-
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH =
|
|
5262
|
+
var ISSUE_FINAL_REVIEW_ARTIFACT_PATH = join12(
|
|
5088
5263
|
".pourkit",
|
|
5089
5264
|
".tmp",
|
|
5090
5265
|
"issue-final-review",
|
|
@@ -5102,7 +5277,7 @@ async function runIssueFinalReview(options) {
|
|
|
5102
5277
|
logger
|
|
5103
5278
|
} = options;
|
|
5104
5279
|
const artifactPathInWorktree = ISSUE_FINAL_REVIEW_ARTIFACT_PATH;
|
|
5105
|
-
const artifactPath =
|
|
5280
|
+
const artifactPath = join12(worktreePath, artifactPathInWorktree);
|
|
5106
5281
|
const ifrFromState = options.worktreeState?.issueFinalReview;
|
|
5107
5282
|
if (ifrFromState?.completed && ifrFromState.verdict === "pass") {
|
|
5108
5283
|
if (!ifrFromState.artifactPath) {
|
|
@@ -5110,7 +5285,7 @@ async function runIssueFinalReview(options) {
|
|
|
5110
5285
|
"Issue Final Review state is incomplete: missing artifactPath"
|
|
5111
5286
|
);
|
|
5112
5287
|
}
|
|
5113
|
-
const cachedArtifactPath = isAbsolute3(ifrFromState.artifactPath) ? ifrFromState.artifactPath :
|
|
5288
|
+
const cachedArtifactPath = isAbsolute3(ifrFromState.artifactPath) ? ifrFromState.artifactPath : join12(worktreePath, ifrFromState.artifactPath);
|
|
5114
5289
|
const validation2 = validateAgentArtifact({
|
|
5115
5290
|
kind: "issue-final-review",
|
|
5116
5291
|
artifactPath: cachedArtifactPath,
|
|
@@ -5505,20 +5680,20 @@ function loadFinalizerPromptEffect(repoRoot2, promptTemplate, fs) {
|
|
|
5505
5680
|
}
|
|
5506
5681
|
function persistGeneratedArtifactEffect(worktreePath, output, fs) {
|
|
5507
5682
|
return Effect7.gen(function* () {
|
|
5508
|
-
const dir =
|
|
5683
|
+
const dir = join13(worktreePath, ".pourkit", ".tmp", "finalizer");
|
|
5509
5684
|
yield* fs.mkdir(dir).pipe(Effect7.catchAll(() => Effect7.void));
|
|
5510
|
-
yield* fs.writeFile(
|
|
5685
|
+
yield* fs.writeFile(join13(dir, "generated.md"), output).pipe(Effect7.catchAll(() => Effect7.void));
|
|
5511
5686
|
});
|
|
5512
5687
|
}
|
|
5513
5688
|
function runPrDescriptionAgent(options) {
|
|
5514
5689
|
const { executionProvider } = options;
|
|
5515
|
-
const artifactPathInWorktree =
|
|
5690
|
+
const artifactPathInWorktree = join13(
|
|
5516
5691
|
".pourkit",
|
|
5517
5692
|
".tmp",
|
|
5518
5693
|
"finalizer",
|
|
5519
5694
|
"agent-output.md"
|
|
5520
5695
|
);
|
|
5521
|
-
const artifactPath =
|
|
5696
|
+
const artifactPath = join13(options.worktreePath, artifactPathInWorktree);
|
|
5522
5697
|
const finalizerFromState = options.worktreeState?.finalizer?.completed ? options.worktreeState.finalizer : null;
|
|
5523
5698
|
if (finalizerFromState) {
|
|
5524
5699
|
return Effect7.gen(function* () {
|
|
@@ -5529,7 +5704,7 @@ function runPrDescriptionAgent(options) {
|
|
|
5529
5704
|
})
|
|
5530
5705
|
);
|
|
5531
5706
|
}
|
|
5532
|
-
const resolvedArtifactPath = isAbsolute4(finalizerFromState.artifactPath) ? finalizerFromState.artifactPath :
|
|
5707
|
+
const resolvedArtifactPath = isAbsolute4(finalizerFromState.artifactPath) ? finalizerFromState.artifactPath : join13(options.worktreePath, finalizerFromState.artifactPath);
|
|
5533
5708
|
if (!existsSync12(resolvedArtifactPath)) {
|
|
5534
5709
|
return yield* Effect7.fail(
|
|
5535
5710
|
new FinalizerFailure({
|
|
@@ -5651,7 +5826,7 @@ function runPrDescriptionAgent(options) {
|
|
|
5651
5826
|
);
|
|
5652
5827
|
}
|
|
5653
5828
|
function runPrDescriptionFinalizerCore(options) {
|
|
5654
|
-
const artifactPath =
|
|
5829
|
+
const artifactPath = join13(
|
|
5655
5830
|
options.worktreePath,
|
|
5656
5831
|
options.artifactPathInWorktree
|
|
5657
5832
|
);
|
|
@@ -5752,7 +5927,7 @@ function runPrDescriptionFinalizerCore(options) {
|
|
|
5752
5927
|
// issues/github-issue-publication.ts
|
|
5753
5928
|
init_common();
|
|
5754
5929
|
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
5755
|
-
import { isAbsolute as isAbsolute5, join as
|
|
5930
|
+
import { isAbsolute as isAbsolute5, join as join14 } from "path";
|
|
5756
5931
|
|
|
5757
5932
|
// issues/issue-transitions.ts
|
|
5758
5933
|
function createIssueTransitions(deps, labels) {
|
|
@@ -5810,6 +5985,42 @@ function createIssueTransitions(deps, labels) {
|
|
|
5810
5985
|
};
|
|
5811
5986
|
}
|
|
5812
5987
|
|
|
5988
|
+
// beads/client.ts
|
|
5989
|
+
init_common();
|
|
5990
|
+
async function runBd(options) {
|
|
5991
|
+
return execCapture("bd", options.args, {
|
|
5992
|
+
cwd: options.repoRoot,
|
|
5993
|
+
logger: options.logger,
|
|
5994
|
+
label: options.label
|
|
5995
|
+
});
|
|
5996
|
+
}
|
|
5997
|
+
async function runBdJson(options) {
|
|
5998
|
+
return execJson("bd", options.args, {
|
|
5999
|
+
cwd: options.repoRoot,
|
|
6000
|
+
logger: options.logger,
|
|
6001
|
+
label: options.label
|
|
6002
|
+
});
|
|
6003
|
+
}
|
|
6004
|
+
|
|
6005
|
+
// beads/prd-completion.ts
|
|
6006
|
+
async function closeBeadsPrdChild(options) {
|
|
6007
|
+
const { repoRoot: repoRoot2, childId, reason, logger } = options;
|
|
6008
|
+
if (!childId || childId.trim().length === 0) {
|
|
6009
|
+
throw new Error(
|
|
6010
|
+
"childId is required and must be a non-empty Beads ID string"
|
|
6011
|
+
);
|
|
6012
|
+
}
|
|
6013
|
+
if (!reason || reason.trim().length === 0) {
|
|
6014
|
+
throw new Error("reason is required and must be a non-empty string");
|
|
6015
|
+
}
|
|
6016
|
+
const response = await runBdJson({
|
|
6017
|
+
repoRoot: repoRoot2,
|
|
6018
|
+
args: ["close", childId, "--reason", reason, "--json"],
|
|
6019
|
+
logger
|
|
6020
|
+
});
|
|
6021
|
+
return { ok: true, childId, response };
|
|
6022
|
+
}
|
|
6023
|
+
|
|
5813
6024
|
// issues/stacked-issue.ts
|
|
5814
6025
|
var BODY_PARENT_SECTION_REGEX = /## Parent\s*\n([\s\S]*?)(?=\n## |$)/i;
|
|
5815
6026
|
var AFFECTED_CODE_PATHS_SECTION_REGEX = /## Affected code paths\s*\n([\s\S]*?)(?=\n## |$)/i;
|
|
@@ -6227,7 +6438,7 @@ function readIssueFinalReviewArtifact(worktreeState, worktreePath) {
|
|
|
6227
6438
|
if (!configuredPath) {
|
|
6228
6439
|
return null;
|
|
6229
6440
|
}
|
|
6230
|
-
const artifactPath = isAbsolute5(configuredPath) ? configuredPath :
|
|
6441
|
+
const artifactPath = isAbsolute5(configuredPath) ? configuredPath : join14(worktreePath, configuredPath);
|
|
6231
6442
|
if (!existsSync13(artifactPath)) return null;
|
|
6232
6443
|
let parsed;
|
|
6233
6444
|
try {
|
|
@@ -6334,19 +6545,56 @@ function formatTableCell(value) {
|
|
|
6334
6545
|
return typeof value === "string" && value.trim() !== "" ? value.trim().replace(/\s+/g, " ").replace(/\|/g, "\\|") : "Unknown";
|
|
6335
6546
|
}
|
|
6336
6547
|
async function performCleanupAndClosure(options, pr) {
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6548
|
+
if (options.beadsCompletion) {
|
|
6549
|
+
await closeBeadsPrdChild({
|
|
6550
|
+
repoRoot: options.repoRoot,
|
|
6551
|
+
childId: options.beadsCompletion.childId,
|
|
6552
|
+
reason: `Issue #${options.issue.number} merged`,
|
|
6553
|
+
logger: options.logger
|
|
6554
|
+
});
|
|
6555
|
+
}
|
|
6556
|
+
try {
|
|
6557
|
+
await options.issueProvider.removeLabel(
|
|
6558
|
+
options.issue.number,
|
|
6559
|
+
options.labels.agentInProgress
|
|
6560
|
+
);
|
|
6561
|
+
} catch (error) {
|
|
6562
|
+
if (options.beadsCompletion) {
|
|
6563
|
+
throw new Error(
|
|
6564
|
+
`Projection failure after Beads close: GitHub Issue #${options.issue.number} label ${options.labels.agentInProgress} removal failed (Beads child ${options.beadsCompletion.childId} remains completed). ${error instanceof Error ? error.message : String(error)}`
|
|
6565
|
+
);
|
|
6566
|
+
}
|
|
6567
|
+
options.logger.step(
|
|
6568
|
+
"warn",
|
|
6569
|
+
`Failed to remove ${options.labels.agentInProgress} label (cleanup): ${error instanceof Error ? error.message : String(error)}`
|
|
6570
|
+
);
|
|
6571
|
+
}
|
|
6572
|
+
try {
|
|
6573
|
+
await options.issueProvider.removeLabel(
|
|
6574
|
+
options.issue.number,
|
|
6575
|
+
options.labels.prOpenAwaitingMerge
|
|
6576
|
+
);
|
|
6577
|
+
} catch (error) {
|
|
6578
|
+
if (options.beadsCompletion) {
|
|
6579
|
+
throw new Error(
|
|
6580
|
+
`Projection failure after Beads close: GitHub Issue #${options.issue.number} label ${options.labels.prOpenAwaitingMerge} removal failed (Beads child ${options.beadsCompletion.childId} remains completed). ${error instanceof Error ? error.message : String(error)}`
|
|
6581
|
+
);
|
|
6582
|
+
}
|
|
6583
|
+
options.logger.step(
|
|
6584
|
+
"warn",
|
|
6585
|
+
`Failed to remove ${options.labels.prOpenAwaitingMerge} label (cleanup): ${error instanceof Error ? error.message : String(error)}`
|
|
6586
|
+
);
|
|
6587
|
+
}
|
|
6345
6588
|
let childCloseSucceeded = false;
|
|
6346
6589
|
try {
|
|
6347
6590
|
await options.issueProvider.closeIssue(options.issue.number);
|
|
6348
6591
|
childCloseSucceeded = true;
|
|
6349
6592
|
} catch (error) {
|
|
6593
|
+
if (options.beadsCompletion) {
|
|
6594
|
+
throw new Error(
|
|
6595
|
+
`Projection failure after Beads close: GitHub Issue #${options.issue.number} close failed (Beads child ${options.beadsCompletion.childId} remains completed). ${error instanceof Error ? error.message : String(error)}`
|
|
6596
|
+
);
|
|
6597
|
+
}
|
|
6350
6598
|
options.logger.step(
|
|
6351
6599
|
"warn",
|
|
6352
6600
|
`Issue #${options.issue.number} could not be closed: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -6545,7 +6793,7 @@ async function runFinalCommit(options) {
|
|
|
6545
6793
|
|
|
6546
6794
|
// issues/issue-worktree-resolution.ts
|
|
6547
6795
|
init_common();
|
|
6548
|
-
import { join as
|
|
6796
|
+
import { join as join15 } from "path";
|
|
6549
6797
|
async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
|
|
6550
6798
|
const worktreeList = await execCapture(
|
|
6551
6799
|
"git",
|
|
@@ -6603,7 +6851,7 @@ async function resolveIssueWorktree(root, branchName, baseBranch, logger) {
|
|
|
6603
6851
|
return { mode: "new", branchName, baseRef };
|
|
6604
6852
|
}
|
|
6605
6853
|
function issueWorktreePath(root, branchName) {
|
|
6606
|
-
return
|
|
6854
|
+
return join15(root, ".sandcastle", "worktrees", branchName.replace(/\//g, "-"));
|
|
6607
6855
|
}
|
|
6608
6856
|
function resolveRegisteredIssueWorktreePath(worktreeListPorcelain, root, branchName) {
|
|
6609
6857
|
const branchWorktreePath = parseWorktreeListPorcelain(
|
|
@@ -6996,7 +7244,9 @@ async function completeIssueRun(options) {
|
|
|
6996
7244
|
blocked: config.labels.blocked
|
|
6997
7245
|
},
|
|
6998
7246
|
checkWaitOptions,
|
|
6999
|
-
secretLikeContentDetectionEnabled: true
|
|
7247
|
+
secretLikeContentDetectionEnabled: true,
|
|
7248
|
+
repoRoot: ROOT,
|
|
7249
|
+
beadsCompletion: options.beadsIssueRunContext
|
|
7000
7250
|
});
|
|
7001
7251
|
return {
|
|
7002
7252
|
branchName,
|
|
@@ -7399,141 +7649,994 @@ async function runIssueCreateCommand(args, issueProvider, logger) {
|
|
|
7399
7649
|
};
|
|
7400
7650
|
}
|
|
7401
7651
|
|
|
7402
|
-
// commands/prd-
|
|
7403
|
-
|
|
7404
|
-
import {
|
|
7405
|
-
|
|
7406
|
-
// prd-run/state.ts
|
|
7407
|
-
import {
|
|
7408
|
-
existsSync as existsSync14,
|
|
7409
|
-
mkdirSync as mkdirSync7,
|
|
7410
|
-
readFileSync as readFileSync15,
|
|
7411
|
-
readdirSync as readdirSync3,
|
|
7412
|
-
writeFileSync as writeFileSync4
|
|
7413
|
-
} from "fs";
|
|
7414
|
-
import { join as join15 } from "path";
|
|
7415
|
-
import { z as z2 } from "zod";
|
|
7652
|
+
// commands/prd-plan-workspace-publish.ts
|
|
7653
|
+
init_common();
|
|
7654
|
+
import { isAbsolute as isAbsolute6, relative as relative3, resolve as resolve3 } from "path";
|
|
7416
7655
|
|
|
7417
|
-
// prd-
|
|
7418
|
-
import {
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
"final-review"
|
|
7434
|
-
]),
|
|
7435
|
-
blockedStage: z.string().min(1),
|
|
7436
|
-
failureCode: z.string().regex(
|
|
7437
|
-
/^[a-z][a-z0-9-]*$/,
|
|
7438
|
-
"failureCode must be kebab-case (lowercase letters, digits, hyphens)"
|
|
7439
|
-
),
|
|
7440
|
-
humanReadableReason: z.string().min(1),
|
|
7441
|
-
repairability: z.enum([
|
|
7442
|
-
"automatic-retry-safe",
|
|
7443
|
-
"operator-action",
|
|
7444
|
-
"unsupported-state"
|
|
7445
|
-
]),
|
|
7446
|
-
nextAction: z.string().min(1),
|
|
7447
|
-
nextRecommendedCommand: z.string().min(1),
|
|
7448
|
-
diagnostics: z.array(z.string()).optional(),
|
|
7449
|
-
offendingPaths: z.array(z.string()).optional(),
|
|
7450
|
-
issue: z.object({
|
|
7451
|
-
number: z.number().int().positive(),
|
|
7452
|
-
title: z.string().optional(),
|
|
7453
|
-
branch: z.string().optional(),
|
|
7454
|
-
worktreePath: z.string().optional()
|
|
7455
|
-
}).strict().optional(),
|
|
7456
|
-
pr: z.object({
|
|
7457
|
-
number: z.number().int().positive().optional(),
|
|
7458
|
-
url: z.string().url().optional()
|
|
7459
|
-
}).strict().optional(),
|
|
7460
|
-
attemptHistory: z.array(PrdRunRepairAttemptSchema).optional(),
|
|
7461
|
-
targetName: z.string().min(1).optional(),
|
|
7462
|
-
baseBranch: z.string().min(1).optional()
|
|
7463
|
-
}).strict();
|
|
7464
|
-
function buildPrdRunRepairGuidance(input) {
|
|
7465
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7466
|
-
return PrdRunRepairGuidanceSchema.parse({
|
|
7467
|
-
createdAt: now,
|
|
7468
|
-
updatedAt: now,
|
|
7469
|
-
blockedGate: input.blockedGate,
|
|
7470
|
-
blockedStage: input.blockedStage,
|
|
7471
|
-
failureCode: input.failureCode,
|
|
7472
|
-
humanReadableReason: input.humanReadableReason,
|
|
7473
|
-
repairability: input.repairability,
|
|
7474
|
-
nextAction: input.nextAction,
|
|
7475
|
-
nextRecommendedCommand: input.nextRecommendedCommand,
|
|
7476
|
-
diagnostics: input.diagnostics,
|
|
7477
|
-
offendingPaths: input.offendingPaths,
|
|
7478
|
-
issue: input.issue,
|
|
7479
|
-
pr: input.pr,
|
|
7480
|
-
targetName: input.targetName,
|
|
7481
|
-
baseBranch: input.baseBranch,
|
|
7482
|
-
attemptHistory: void 0
|
|
7483
|
-
});
|
|
7656
|
+
// prd-plan-workspace/parser.ts
|
|
7657
|
+
import { readFile as readFile4, readdir } from "fs/promises";
|
|
7658
|
+
import { join as join16 } from "path";
|
|
7659
|
+
var CHILD_FILE_PATTERN = /^I-0*(\d+)-.+\.md$/i;
|
|
7660
|
+
var CHILD_REF_PATTERN = /^\s*(I-0*\d+)\s*$/i;
|
|
7661
|
+
var BLOCKED_BY_SECTION_REGEX = /## Blocked by\s*\n([\s\S]*?)(?=\n## |$)/i;
|
|
7662
|
+
var PARENT_SECTION_REGEX = /## Parent\s*\n([\s\S]*?)(?=\n## |$)/i;
|
|
7663
|
+
function escapeRegExp2(value) {
|
|
7664
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7665
|
+
}
|
|
7666
|
+
function derivePrdBeadsId(prdRef) {
|
|
7667
|
+
const match = prdRef.match(/^PRD-0*(\d+)$/i);
|
|
7668
|
+
if (!match) {
|
|
7669
|
+
return prdRef.toLowerCase();
|
|
7670
|
+
}
|
|
7671
|
+
return `bd-${match[1].padStart(4, "0")}`;
|
|
7484
7672
|
}
|
|
7485
|
-
function
|
|
7486
|
-
const
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
}
|
|
7492
|
-
const history = [...guidance.attemptHistory ?? [], newAttempt].slice(-5);
|
|
7493
|
-
return PrdRunRepairGuidanceSchema.parse({
|
|
7494
|
-
...guidance,
|
|
7495
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7496
|
-
attemptHistory: history
|
|
7497
|
-
});
|
|
7673
|
+
function deriveChildBeadsId(prdRef, childRef) {
|
|
7674
|
+
const prdMatch = prdRef.match(/^PRD-0*(\d+)$/i);
|
|
7675
|
+
const childMatch = childRef.match(/^I-0*(\d+)$/i);
|
|
7676
|
+
if (!prdMatch || !childMatch) {
|
|
7677
|
+
return `${prdRef.toLowerCase()}.${childRef.toLowerCase()}`;
|
|
7678
|
+
}
|
|
7679
|
+
return `bd-${prdMatch[1].padStart(4, "0")}.${childMatch[1]}`;
|
|
7498
7680
|
}
|
|
7499
|
-
function
|
|
7500
|
-
const
|
|
7501
|
-
if (
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
blockedStage: "unknown",
|
|
7513
|
-
failureCode: "malformed-repair-guidance",
|
|
7514
|
-
humanReadableReason: "Repair guidance state is malformed or invalid. Inspect the PRD Run record.",
|
|
7515
|
-
repairability: "unsupported-state",
|
|
7516
|
-
nextAction: "inspect-prd-run-state",
|
|
7517
|
-
nextRecommendedCommand: `pourkit prd-run status ${record.prdRef}`,
|
|
7518
|
-
diagnostics: [
|
|
7519
|
-
`Validation error: ${parsed.error?.message ?? "Unknown schema error"}`
|
|
7520
|
-
]
|
|
7681
|
+
function parseChildRefsFromBody(body) {
|
|
7682
|
+
const section = body.match(BLOCKED_BY_SECTION_REGEX)?.[1];
|
|
7683
|
+
if (!section) return [];
|
|
7684
|
+
const refs = [];
|
|
7685
|
+
for (const rawLine of section.split("\n")) {
|
|
7686
|
+
const line = rawLine.trim();
|
|
7687
|
+
if (!line) continue;
|
|
7688
|
+
const parts = line.split(",");
|
|
7689
|
+
for (const part of parts) {
|
|
7690
|
+
const trimmed = part.trim();
|
|
7691
|
+
const match = trimmed.match(CHILD_REF_PATTERN);
|
|
7692
|
+
if (match) {
|
|
7693
|
+
refs.push(match[1].toUpperCase());
|
|
7521
7694
|
}
|
|
7522
|
-
}
|
|
7523
|
-
}
|
|
7524
|
-
return
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
return refs;
|
|
7698
|
+
}
|
|
7699
|
+
function extractTitleBody(content) {
|
|
7700
|
+
const firstLine = content.split("\n")[0];
|
|
7701
|
+
const title = firstLine.startsWith("# ") ? firstLine.slice(2).trim() : "";
|
|
7702
|
+
const body = content.slice(firstLine.length).trim();
|
|
7703
|
+
return { title, body };
|
|
7704
|
+
}
|
|
7705
|
+
function hasParentSectionPointingToPrd(content, prdRef) {
|
|
7706
|
+
const section = content.match(PARENT_SECTION_REGEX)?.[1];
|
|
7707
|
+
if (!section) return false;
|
|
7708
|
+
return new RegExp(`\\b${escapeRegExp2(prdRef)}\\b`, "i").test(section);
|
|
7709
|
+
}
|
|
7710
|
+
async function parsePrdPlanWorkspace(options) {
|
|
7711
|
+
const { workspacePath, prdRef } = options;
|
|
7712
|
+
const diagnostics = [];
|
|
7713
|
+
const prdPath = join16(workspacePath, "PRD.md");
|
|
7714
|
+
const issuesDir = join16(workspacePath, "issues");
|
|
7715
|
+
let prdContent;
|
|
7716
|
+
try {
|
|
7717
|
+
prdContent = await readFile4(prdPath, "utf-8");
|
|
7718
|
+
} catch {
|
|
7719
|
+
diagnostics.push({
|
|
7720
|
+
severity: "error",
|
|
7721
|
+
message: `Missing PRD.md in workspace`,
|
|
7722
|
+
sourcePath: prdPath
|
|
7723
|
+
});
|
|
7724
|
+
return { ok: false, diagnostics };
|
|
7725
|
+
}
|
|
7726
|
+
const prdFile = extractTitleBody(prdContent);
|
|
7727
|
+
const prdHeadingMatch = prdFile.title.match(/^(PRD-\d{4}):/i);
|
|
7728
|
+
if (!prdHeadingMatch) {
|
|
7729
|
+
diagnostics.push({
|
|
7730
|
+
severity: "error",
|
|
7731
|
+
message: "PRD.md must start with a heading: # PRD-NNNN: Title",
|
|
7732
|
+
sourcePath: prdPath
|
|
7733
|
+
});
|
|
7734
|
+
} else if (prdHeadingMatch[1].toUpperCase() !== prdRef.toUpperCase()) {
|
|
7735
|
+
diagnostics.push({
|
|
7736
|
+
severity: "error",
|
|
7737
|
+
message: `PRD.md heading ${prdHeadingMatch[1].toUpperCase()} does not match ${prdRef}`,
|
|
7738
|
+
sourcePath: prdPath
|
|
7739
|
+
});
|
|
7740
|
+
}
|
|
7741
|
+
const parent = {
|
|
7742
|
+
prdRef,
|
|
7743
|
+
title: prdFile.title,
|
|
7744
|
+
body: prdFile.body,
|
|
7745
|
+
sourcePath: prdPath,
|
|
7746
|
+
beadsId: derivePrdBeadsId(prdRef)
|
|
7747
|
+
};
|
|
7748
|
+
let childFiles;
|
|
7749
|
+
try {
|
|
7750
|
+
childFiles = (await readdir(issuesDir)).filter(
|
|
7751
|
+
(f) => CHILD_FILE_PATTERN.test(f) && !f.startsWith(".")
|
|
7752
|
+
);
|
|
7753
|
+
} catch {
|
|
7754
|
+
diagnostics.push({
|
|
7755
|
+
severity: "error",
|
|
7756
|
+
message: `Missing issues/ directory`,
|
|
7757
|
+
sourcePath: issuesDir
|
|
7758
|
+
});
|
|
7759
|
+
return { ok: false, diagnostics };
|
|
7760
|
+
}
|
|
7761
|
+
childFiles.sort((a, b) => {
|
|
7762
|
+
const aMatch = a.match(CHILD_FILE_PATTERN);
|
|
7763
|
+
const bMatch = b.match(CHILD_FILE_PATTERN);
|
|
7764
|
+
if (!aMatch || !bMatch) return 0;
|
|
7765
|
+
return Number(aMatch[1]) - Number(bMatch[1]);
|
|
7766
|
+
});
|
|
7767
|
+
const children = [];
|
|
7768
|
+
for (const childFile of childFiles) {
|
|
7769
|
+
const childPath = join16(issuesDir, childFile);
|
|
7770
|
+
const childContent = await readFile4(childPath, "utf-8");
|
|
7771
|
+
const childFileData = extractTitleBody(childContent);
|
|
7772
|
+
const childRefMatch = childFile.match(CHILD_FILE_PATTERN);
|
|
7773
|
+
const childRef = childRefMatch ? `I-${childRefMatch[1].padStart(2, "0")}` : "";
|
|
7774
|
+
const childNumber = childRefMatch ? Number(childRefMatch[1]) : 0;
|
|
7775
|
+
const blockedByRefs = parseChildRefsFromBody(childContent);
|
|
7776
|
+
if (!childFileData.title.match(
|
|
7777
|
+
new RegExp(`^${escapeRegExp2(prdRef)} / ${escapeRegExp2(childRef)}:`)
|
|
7778
|
+
)) {
|
|
7779
|
+
diagnostics.push({
|
|
7780
|
+
severity: "error",
|
|
7781
|
+
message: `${childFile}: expected heading: # ${prdRef} / ${childRef}: Title`,
|
|
7782
|
+
sourcePath: childPath
|
|
7783
|
+
});
|
|
7784
|
+
}
|
|
7785
|
+
if (!hasParentSectionPointingToPrd(childContent, prdRef)) {
|
|
7786
|
+
diagnostics.push({
|
|
7787
|
+
severity: "error",
|
|
7788
|
+
message: `${childFile}: missing ## Parent section pointing to ${prdRef}`,
|
|
7789
|
+
sourcePath: childPath
|
|
7790
|
+
});
|
|
7791
|
+
}
|
|
7792
|
+
children.push({
|
|
7793
|
+
childRef,
|
|
7794
|
+
childNumber,
|
|
7795
|
+
title: childFileData.title,
|
|
7796
|
+
body: childFileData.body,
|
|
7797
|
+
sourcePath: childPath,
|
|
7798
|
+
beadsId: deriveChildBeadsId(prdRef, childRef),
|
|
7799
|
+
blockedByRefs
|
|
7800
|
+
});
|
|
7801
|
+
}
|
|
7802
|
+
const validChildRefs = new Set(children.map((c) => c.childRef));
|
|
7803
|
+
for (const child of children) {
|
|
7804
|
+
for (const blockedRef of child.blockedByRefs) {
|
|
7805
|
+
if (!validChildRefs.has(blockedRef.toUpperCase())) {
|
|
7806
|
+
diagnostics.push({
|
|
7807
|
+
severity: "error",
|
|
7808
|
+
message: `Child ${child.childRef} references nonexistent child ${blockedRef} in Blocked by`,
|
|
7809
|
+
sourcePath: child.sourcePath
|
|
7810
|
+
});
|
|
7811
|
+
}
|
|
7812
|
+
}
|
|
7813
|
+
}
|
|
7814
|
+
const model = {
|
|
7815
|
+
prdRef,
|
|
7816
|
+
workspacePath,
|
|
7817
|
+
parent,
|
|
7818
|
+
children,
|
|
7819
|
+
diagnostics
|
|
7820
|
+
};
|
|
7821
|
+
if (diagnostics.length > 0) {
|
|
7822
|
+
return { ok: false, diagnostics };
|
|
7823
|
+
}
|
|
7824
|
+
return { ok: true, workspace: model };
|
|
7825
|
+
}
|
|
7826
|
+
|
|
7827
|
+
// prd-plan-workspace/publish-receipt.ts
|
|
7828
|
+
import { mkdir as mkdir4, writeFile } from "fs/promises";
|
|
7829
|
+
import { dirname as dirname5, join as join17 } from "path";
|
|
7830
|
+
function yamlEntry(key, value, indent) {
|
|
7831
|
+
const pad = " ".repeat(indent);
|
|
7832
|
+
if (value === null || value === void 0) {
|
|
7833
|
+
return `${pad}${key}: ~`;
|
|
7834
|
+
}
|
|
7835
|
+
if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
7836
|
+
return `${pad}${key}: ${value}`;
|
|
7837
|
+
}
|
|
7838
|
+
if (Array.isArray(value)) {
|
|
7839
|
+
if (value.length === 0) {
|
|
7840
|
+
return `${pad}${key}: []`;
|
|
7841
|
+
}
|
|
7842
|
+
const itemPad = " ".repeat(indent + 2);
|
|
7843
|
+
const innerIndent = indent + 4;
|
|
7844
|
+
const lines = value.map((item) => {
|
|
7845
|
+
if (typeof item === "object" && item !== null) {
|
|
7846
|
+
const entries = Object.entries(item);
|
|
7847
|
+
const firstLine = `${itemPad}- ${entries[0][0]}: ${entries[0][1]}`;
|
|
7848
|
+
const restLines = entries.slice(1).map(([k, v]) => yamlEntry(k, v, innerIndent));
|
|
7849
|
+
return [firstLine, ...restLines].join("\n");
|
|
7850
|
+
}
|
|
7851
|
+
return `${itemPad}- ${String(item)}`;
|
|
7852
|
+
});
|
|
7853
|
+
return `${pad}${key}:
|
|
7854
|
+
${lines.join("\n")}`;
|
|
7855
|
+
}
|
|
7856
|
+
if (typeof value === "object") {
|
|
7857
|
+
const inner = Object.entries(value).map(([k, v]) => yamlEntry(k, v, indent + 2)).join("\n");
|
|
7858
|
+
return `${pad}${key}:
|
|
7859
|
+
${inner}`;
|
|
7860
|
+
}
|
|
7861
|
+
return `${pad}${key}: ${String(value)}`;
|
|
7862
|
+
}
|
|
7863
|
+
function renderPrdPublishReceipt(options) {
|
|
7864
|
+
const data = {
|
|
7865
|
+
workspace: options.workspacePath,
|
|
7866
|
+
route: "prd",
|
|
7867
|
+
parent: {
|
|
7868
|
+
prd_ref: options.parent.prdRef,
|
|
7869
|
+
beads_id: options.parent.beadsId,
|
|
7870
|
+
number: options.parent.githubIssue.number,
|
|
7871
|
+
url: options.parent.githubIssue.url
|
|
7872
|
+
},
|
|
7873
|
+
children: options.children.map((c) => ({
|
|
7874
|
+
child_ref: c.childRef,
|
|
7875
|
+
beads_id: c.beadsId,
|
|
7876
|
+
number: c.githubIssue.number,
|
|
7877
|
+
url: c.githubIssue.url,
|
|
7878
|
+
source_file: c.sourceFile
|
|
7879
|
+
})),
|
|
7880
|
+
dependency_mappings: options.dependencyMappings.map((d) => ({
|
|
7881
|
+
from: d.from,
|
|
7882
|
+
to: d.to,
|
|
7883
|
+
beads_edge_type: d.beadsEdgeType
|
|
7884
|
+
})),
|
|
7885
|
+
publication_timestamp: options.publicationTimestamp,
|
|
7886
|
+
authored_bodies_mutated: false,
|
|
7887
|
+
warnings: options.warnings
|
|
7888
|
+
};
|
|
7889
|
+
return Object.entries(data).map(([k, v]) => yamlEntry(k, v, 0)).join("\n") + "\n";
|
|
7890
|
+
}
|
|
7891
|
+
async function writePrdPublishReceipt(options) {
|
|
7892
|
+
const receipt = renderPrdPublishReceipt(options);
|
|
7893
|
+
const publishPath = join17(options.workspaceRoot, "PUBLISH.md");
|
|
7894
|
+
await mkdir4(dirname5(publishPath), { recursive: true });
|
|
7895
|
+
await writeFile(publishPath, receipt, "utf-8");
|
|
7896
|
+
return publishPath;
|
|
7897
|
+
}
|
|
7898
|
+
|
|
7899
|
+
// beads/preflight.ts
|
|
7900
|
+
init_common();
|
|
7901
|
+
async function checkBeadsAvailability(options) {
|
|
7902
|
+
try {
|
|
7903
|
+
await execCapture("which", ["bd"], {
|
|
7904
|
+
cwd: options.repoRoot,
|
|
7905
|
+
logger: options.logger
|
|
7906
|
+
});
|
|
7907
|
+
} catch {
|
|
7908
|
+
return {
|
|
7909
|
+
available: false,
|
|
7910
|
+
error: "bd CLI is not installed or not on PATH",
|
|
7911
|
+
repairGuidance: "Install Beads CLI (bd), then run your workflow again."
|
|
7912
|
+
};
|
|
7913
|
+
}
|
|
7914
|
+
try {
|
|
7915
|
+
await runBd({
|
|
7916
|
+
repoRoot: options.repoRoot,
|
|
7917
|
+
args: ["ready", "--json"],
|
|
7918
|
+
logger: options.logger
|
|
7919
|
+
});
|
|
7920
|
+
} catch {
|
|
7921
|
+
return {
|
|
7922
|
+
available: false,
|
|
7923
|
+
error: "Beads repository state is unusable",
|
|
7924
|
+
repairGuidance: "Run 'bd init' in the repository root to initialize Beads state, then run your workflow again."
|
|
7925
|
+
};
|
|
7926
|
+
}
|
|
7927
|
+
return { available: true };
|
|
7928
|
+
}
|
|
7929
|
+
|
|
7930
|
+
// beads/prd-publication.ts
|
|
7931
|
+
function isMissingIssueMessage(value) {
|
|
7932
|
+
return /no issues? found matching/i.test(value);
|
|
7933
|
+
}
|
|
7934
|
+
function parentMetadata(options) {
|
|
7935
|
+
return {
|
|
7936
|
+
pourkit: {
|
|
7937
|
+
prdRef: options.workspace.prdRef,
|
|
7938
|
+
workspacePath: options.workspace.workspacePath,
|
|
7939
|
+
sourcePath: options.workspace.parent.sourcePath,
|
|
7940
|
+
...options.githubIssue ? {
|
|
7941
|
+
githubIssueNumber: options.githubIssue.number,
|
|
7942
|
+
githubIssueUrl: options.githubIssue.url
|
|
7943
|
+
} : {}
|
|
7944
|
+
}
|
|
7945
|
+
};
|
|
7946
|
+
}
|
|
7947
|
+
function childMetadata(options) {
|
|
7948
|
+
return {
|
|
7949
|
+
pourkit: {
|
|
7950
|
+
prdRef: options.workspace.prdRef,
|
|
7951
|
+
childRef: options.child.childRef,
|
|
7952
|
+
workspacePath: options.workspace.workspacePath,
|
|
7953
|
+
sourcePath: options.child.sourcePath,
|
|
7954
|
+
...options.githubIssue ? {
|
|
7955
|
+
githubIssueNumber: options.githubIssue.number,
|
|
7956
|
+
githubIssueUrl: options.githubIssue.url
|
|
7957
|
+
} : {}
|
|
7958
|
+
}
|
|
7959
|
+
};
|
|
7960
|
+
}
|
|
7961
|
+
async function beadExists(options) {
|
|
7962
|
+
try {
|
|
7963
|
+
const result = await runBdJson({
|
|
7964
|
+
repoRoot: options.repoRoot,
|
|
7965
|
+
args: ["show", options.beadsId, "--json"],
|
|
7966
|
+
logger: options.logger
|
|
7967
|
+
});
|
|
7968
|
+
if (result?.error) {
|
|
7969
|
+
if (isMissingIssueMessage(result.error)) return false;
|
|
7970
|
+
throw new Error(result.error);
|
|
7971
|
+
}
|
|
7972
|
+
if (result?.id !== options.beadsId) {
|
|
7973
|
+
throw new Error(
|
|
7974
|
+
`Expected Beads issue ${options.beadsId}, but bd show returned ${result?.id ?? "no id"}.`
|
|
7975
|
+
);
|
|
7976
|
+
}
|
|
7977
|
+
return true;
|
|
7978
|
+
} catch (error) {
|
|
7979
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7980
|
+
if (isMissingIssueMessage(message)) return false;
|
|
7981
|
+
throw error;
|
|
7982
|
+
}
|
|
7983
|
+
}
|
|
7984
|
+
function assertExpectedBeadId(options) {
|
|
7985
|
+
if (options.response?.id !== options.expectedId) {
|
|
7986
|
+
throw new Error(
|
|
7987
|
+
`${options.action} returned ${options.response?.id ?? "no id"}; expected ${options.expectedId}.`
|
|
7988
|
+
);
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
async function createOrUpdateParent(options) {
|
|
7992
|
+
const exists = await beadExists({
|
|
7993
|
+
repoRoot: options.repoRoot,
|
|
7994
|
+
beadsId: options.workspace.parent.beadsId,
|
|
7995
|
+
logger: options.logger
|
|
7996
|
+
});
|
|
7997
|
+
const response = await runBdJson({
|
|
7998
|
+
repoRoot: options.repoRoot,
|
|
7999
|
+
args: exists ? [
|
|
8000
|
+
"update",
|
|
8001
|
+
options.workspace.parent.beadsId,
|
|
8002
|
+
"--title",
|
|
8003
|
+
options.workspace.parent.title,
|
|
8004
|
+
"--description",
|
|
8005
|
+
options.workspace.parent.body,
|
|
8006
|
+
"-t",
|
|
8007
|
+
"epic",
|
|
8008
|
+
"--metadata",
|
|
8009
|
+
JSON.stringify(parentMetadata({ workspace: options.workspace })),
|
|
8010
|
+
"--json"
|
|
8011
|
+
] : [
|
|
8012
|
+
"create",
|
|
8013
|
+
"--id",
|
|
8014
|
+
options.workspace.parent.beadsId,
|
|
8015
|
+
"--title",
|
|
8016
|
+
options.workspace.parent.title,
|
|
8017
|
+
"-t",
|
|
8018
|
+
"epic",
|
|
8019
|
+
"--description",
|
|
8020
|
+
options.workspace.parent.body,
|
|
8021
|
+
"--metadata",
|
|
8022
|
+
JSON.stringify(parentMetadata({ workspace: options.workspace })),
|
|
8023
|
+
"--json"
|
|
8024
|
+
],
|
|
8025
|
+
logger: options.logger
|
|
8026
|
+
});
|
|
8027
|
+
if (!exists) {
|
|
8028
|
+
assertExpectedBeadId({
|
|
8029
|
+
expectedId: options.workspace.parent.beadsId,
|
|
8030
|
+
response,
|
|
8031
|
+
action: "bd create parent"
|
|
8032
|
+
});
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
async function createOrUpdateChild(options) {
|
|
8036
|
+
const exists = await beadExists({
|
|
8037
|
+
repoRoot: options.repoRoot,
|
|
8038
|
+
beadsId: options.child.beadsId,
|
|
8039
|
+
logger: options.logger
|
|
8040
|
+
});
|
|
8041
|
+
const response = await runBdJson({
|
|
8042
|
+
repoRoot: options.repoRoot,
|
|
8043
|
+
args: exists ? [
|
|
8044
|
+
"update",
|
|
8045
|
+
options.child.beadsId,
|
|
8046
|
+
"--title",
|
|
8047
|
+
options.child.title,
|
|
8048
|
+
"--description",
|
|
8049
|
+
options.child.body,
|
|
8050
|
+
"--parent",
|
|
8051
|
+
options.workspace.parent.beadsId,
|
|
8052
|
+
"--metadata",
|
|
8053
|
+
JSON.stringify(
|
|
8054
|
+
childMetadata({
|
|
8055
|
+
workspace: options.workspace,
|
|
8056
|
+
child: options.child
|
|
8057
|
+
})
|
|
8058
|
+
),
|
|
8059
|
+
"--json"
|
|
8060
|
+
] : [
|
|
8061
|
+
"create",
|
|
8062
|
+
"--parent",
|
|
8063
|
+
options.workspace.parent.beadsId,
|
|
8064
|
+
"--title",
|
|
8065
|
+
options.child.title,
|
|
8066
|
+
"--description",
|
|
8067
|
+
options.child.body,
|
|
8068
|
+
"--metadata",
|
|
8069
|
+
JSON.stringify(
|
|
8070
|
+
childMetadata({
|
|
8071
|
+
workspace: options.workspace,
|
|
8072
|
+
child: options.child
|
|
8073
|
+
})
|
|
8074
|
+
),
|
|
8075
|
+
"--json"
|
|
8076
|
+
],
|
|
8077
|
+
logger: options.logger
|
|
8078
|
+
});
|
|
8079
|
+
if (!exists) {
|
|
8080
|
+
assertExpectedBeadId({
|
|
8081
|
+
expectedId: options.child.beadsId,
|
|
8082
|
+
response,
|
|
8083
|
+
action: "bd create child"
|
|
8084
|
+
});
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8087
|
+
async function registerPrdWorkspaceInBeads(options) {
|
|
8088
|
+
const { repoRoot: repoRoot2, workspace, logger } = options;
|
|
8089
|
+
const preflight = await checkBeadsAvailability({ repoRoot: repoRoot2, logger });
|
|
8090
|
+
if (!preflight.available) {
|
|
8091
|
+
throw new Error(
|
|
8092
|
+
`Beads is not available: ${preflight.error}. ${preflight.repairGuidance}`
|
|
8093
|
+
);
|
|
8094
|
+
}
|
|
8095
|
+
await createOrUpdateParent({
|
|
8096
|
+
repoRoot: repoRoot2,
|
|
8097
|
+
workspace,
|
|
8098
|
+
logger
|
|
8099
|
+
});
|
|
8100
|
+
const children = [];
|
|
8101
|
+
const dependencyMappings = [];
|
|
8102
|
+
for (const child of workspace.children) {
|
|
8103
|
+
await createOrUpdateChild({
|
|
8104
|
+
repoRoot: repoRoot2,
|
|
8105
|
+
workspace,
|
|
8106
|
+
child,
|
|
8107
|
+
logger
|
|
8108
|
+
});
|
|
8109
|
+
children.push({
|
|
8110
|
+
childRef: child.childRef,
|
|
8111
|
+
beadsId: child.beadsId,
|
|
8112
|
+
sourcePath: child.sourcePath
|
|
8113
|
+
});
|
|
8114
|
+
}
|
|
8115
|
+
for (const child of workspace.children) {
|
|
8116
|
+
for (const blockedRef of child.blockedByRefs) {
|
|
8117
|
+
const blocker = workspace.children.find((c) => c.childRef === blockedRef);
|
|
8118
|
+
if (!blocker) continue;
|
|
8119
|
+
await runBdJson({
|
|
8120
|
+
repoRoot: repoRoot2,
|
|
8121
|
+
args: ["dep", "add", child.beadsId, blocker.beadsId, "--json"],
|
|
8122
|
+
logger
|
|
8123
|
+
});
|
|
8124
|
+
dependencyMappings.push({
|
|
8125
|
+
from: child.childRef,
|
|
8126
|
+
to: blocker.childRef,
|
|
8127
|
+
beadsEdgeType: "blocks"
|
|
8128
|
+
});
|
|
8129
|
+
}
|
|
8130
|
+
}
|
|
8131
|
+
return {
|
|
8132
|
+
parent: {
|
|
8133
|
+
prdRef: workspace.prdRef,
|
|
8134
|
+
beadsId: workspace.parent.beadsId
|
|
8135
|
+
},
|
|
8136
|
+
children,
|
|
8137
|
+
dependencyMappings
|
|
8138
|
+
};
|
|
8139
|
+
}
|
|
8140
|
+
async function backfillPrdWorkspaceBeadsGithubMetadata(options) {
|
|
8141
|
+
const childIssueByRef = new Map(
|
|
8142
|
+
options.childGithubIssues.map((child) => [
|
|
8143
|
+
child.childRef,
|
|
8144
|
+
child.githubIssue
|
|
8145
|
+
])
|
|
8146
|
+
);
|
|
8147
|
+
await runBdJson({
|
|
8148
|
+
repoRoot: options.repoRoot,
|
|
8149
|
+
args: [
|
|
8150
|
+
"update",
|
|
8151
|
+
options.workspace.parent.beadsId,
|
|
8152
|
+
"--metadata",
|
|
8153
|
+
JSON.stringify(
|
|
8154
|
+
parentMetadata({
|
|
8155
|
+
workspace: options.workspace,
|
|
8156
|
+
githubIssue: options.parentGithubIssue
|
|
8157
|
+
})
|
|
8158
|
+
),
|
|
8159
|
+
"--json"
|
|
8160
|
+
],
|
|
8161
|
+
logger: options.logger
|
|
8162
|
+
});
|
|
8163
|
+
for (const child of options.workspace.children) {
|
|
8164
|
+
const githubIssue = childIssueByRef.get(child.childRef);
|
|
8165
|
+
if (!githubIssue) {
|
|
8166
|
+
throw new Error(
|
|
8167
|
+
`Missing GitHub issue projection for Beads child ${child.beadsId} (${child.childRef}).`
|
|
8168
|
+
);
|
|
8169
|
+
}
|
|
8170
|
+
await runBdJson({
|
|
8171
|
+
repoRoot: options.repoRoot,
|
|
8172
|
+
args: [
|
|
8173
|
+
"update",
|
|
8174
|
+
child.beadsId,
|
|
8175
|
+
"--metadata",
|
|
8176
|
+
JSON.stringify(
|
|
8177
|
+
childMetadata({
|
|
8178
|
+
workspace: options.workspace,
|
|
8179
|
+
child,
|
|
8180
|
+
githubIssue
|
|
8181
|
+
})
|
|
8182
|
+
),
|
|
8183
|
+
"--json"
|
|
8184
|
+
],
|
|
8185
|
+
logger: options.logger
|
|
8186
|
+
});
|
|
8187
|
+
}
|
|
8188
|
+
}
|
|
8189
|
+
|
|
8190
|
+
// commands/prd-plan-workspace-publish.ts
|
|
8191
|
+
var DEFAULT_LABELS = {
|
|
8192
|
+
readyForAgent: "ready-for-agent",
|
|
8193
|
+
blocked: "blocked",
|
|
8194
|
+
needsTriage: "needs-triage"
|
|
8195
|
+
};
|
|
8196
|
+
function normalizePrdRef(prdRef) {
|
|
8197
|
+
const match = prdRef.trim().toUpperCase().match(/^PRD-(\d+)$/);
|
|
8198
|
+
if (!match) {
|
|
8199
|
+
throw new Error(
|
|
8200
|
+
`Invalid PRD ref "${prdRef}". Expected format: PRD-<number> (e.g., PRD-0078)`
|
|
8201
|
+
);
|
|
8202
|
+
}
|
|
8203
|
+
return `PRD-${match[1].padStart(4, "0")}`;
|
|
8204
|
+
}
|
|
8205
|
+
function repoRelativePath(repoRoot2, absolutePath) {
|
|
8206
|
+
const rel = relative3(repoRoot2, absolutePath).replace(/\\/g, "/");
|
|
8207
|
+
if (rel === "") return ".";
|
|
8208
|
+
if (rel === ".." || rel.startsWith("../") || isAbsolute6(rel)) {
|
|
8209
|
+
throw new Error(`Path is outside the repository: ${absolutePath}`);
|
|
8210
|
+
}
|
|
8211
|
+
return rel;
|
|
8212
|
+
}
|
|
8213
|
+
function resolveWorkspaceRoot(options) {
|
|
8214
|
+
const workspaceRoot = isAbsolute6(options.workspacePath) ? options.workspacePath : resolve3(options.repoRoot, options.workspacePath);
|
|
8215
|
+
const workspaceRelPath = repoRelativePath(options.repoRoot, workspaceRoot);
|
|
8216
|
+
if (!workspaceRelPath.startsWith(".pourkit/plans/")) {
|
|
8217
|
+
throw new Error(
|
|
8218
|
+
`PRD Plan Workspace must be under .pourkit/plans/: ${workspaceRelPath}`
|
|
8219
|
+
);
|
|
8220
|
+
}
|
|
8221
|
+
return { workspaceRoot, workspaceRelPath };
|
|
8222
|
+
}
|
|
8223
|
+
function relativizeWorkspaceModel(options) {
|
|
8224
|
+
return {
|
|
8225
|
+
...options.workspace,
|
|
8226
|
+
workspacePath: repoRelativePath(
|
|
8227
|
+
options.repoRoot,
|
|
8228
|
+
resolve3(options.workspace.workspacePath)
|
|
8229
|
+
),
|
|
8230
|
+
parent: {
|
|
8231
|
+
...options.workspace.parent,
|
|
8232
|
+
sourcePath: repoRelativePath(
|
|
8233
|
+
options.repoRoot,
|
|
8234
|
+
resolve3(options.workspace.parent.sourcePath)
|
|
8235
|
+
)
|
|
8236
|
+
},
|
|
8237
|
+
children: options.workspace.children.map((child) => ({
|
|
8238
|
+
...child,
|
|
8239
|
+
sourcePath: repoRelativePath(options.repoRoot, resolve3(child.sourcePath))
|
|
8240
|
+
}))
|
|
8241
|
+
};
|
|
8242
|
+
}
|
|
8243
|
+
function formatValidationFailure(result) {
|
|
8244
|
+
const diagnostics = result.diagnostics.length ? ` Diagnostics: ${result.diagnostics.join("; ")}` : "";
|
|
8245
|
+
return `PRD Plan Workspace Publication Gate failed: ${result.reason}.${diagnostics}`;
|
|
8246
|
+
}
|
|
8247
|
+
function extractTypeLabels(body) {
|
|
8248
|
+
const labelsLine = body.match(/^Labels:\s*(.+)$/im)?.[1];
|
|
8249
|
+
if (!labelsLine) return [];
|
|
8250
|
+
const backtickLabels = Array.from(labelsLine.matchAll(/`([^`]+)`/g)).map(
|
|
8251
|
+
(match) => match[1].trim()
|
|
8252
|
+
);
|
|
8253
|
+
const labels = backtickLabels.length > 0 ? backtickLabels : labelsLine.split(/[\s,]+/).map((label) => label.trim());
|
|
8254
|
+
return labels.filter(
|
|
8255
|
+
(label) => TYPE_LABELS.includes(label)
|
|
8256
|
+
);
|
|
8257
|
+
}
|
|
8258
|
+
function unique(values) {
|
|
8259
|
+
return Array.from(new Set(values));
|
|
8260
|
+
}
|
|
8261
|
+
function logStep(logger, level, message) {
|
|
8262
|
+
logger?.step?.(level, message);
|
|
8263
|
+
}
|
|
8264
|
+
function childLabels(options) {
|
|
8265
|
+
const typeLabels = extractTypeLabels(options.body);
|
|
8266
|
+
let typeLabel = typeLabels[0];
|
|
8267
|
+
if (!typeLabel) {
|
|
8268
|
+
typeLabel = "type:feature";
|
|
8269
|
+
options.warnings.push(
|
|
8270
|
+
`${options.childRef}: no type:* label found; defaulted to type:feature.`
|
|
8271
|
+
);
|
|
8272
|
+
} else if (typeLabels.length > 1) {
|
|
8273
|
+
options.warnings.push(
|
|
8274
|
+
`${options.childRef}: multiple type:* labels found; kept ${typeLabel}.`
|
|
8275
|
+
);
|
|
8276
|
+
}
|
|
8277
|
+
return unique([
|
|
8278
|
+
options.labels.readyForAgent,
|
|
8279
|
+
typeLabel,
|
|
8280
|
+
...options.blocked ? [options.labels.blocked] : []
|
|
8281
|
+
]);
|
|
8282
|
+
}
|
|
8283
|
+
async function syncManagedLabels(options) {
|
|
8284
|
+
const desired = new Set(options.desiredLabels);
|
|
8285
|
+
const managed = /* @__PURE__ */ new Set([
|
|
8286
|
+
options.labels.readyForAgent,
|
|
8287
|
+
options.labels.blocked,
|
|
8288
|
+
options.labels.needsTriage,
|
|
8289
|
+
...TYPE_LABELS
|
|
8290
|
+
]);
|
|
8291
|
+
for (const label of options.existingLabels) {
|
|
8292
|
+
if (managed.has(label) && !desired.has(label)) {
|
|
8293
|
+
await options.issueProvider.removeLabel(options.issueNumber, label);
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
const existing = new Set(options.existingLabels);
|
|
8297
|
+
const missing = options.desiredLabels.filter((label) => !existing.has(label));
|
|
8298
|
+
if (missing.length > 0) {
|
|
8299
|
+
await options.issueProvider.addLabels(options.issueNumber, missing);
|
|
8300
|
+
}
|
|
8301
|
+
}
|
|
8302
|
+
async function upsertGithubIssue(options) {
|
|
8303
|
+
const existing = await options.issueProvider.resolveIssueByCanonicalRef(
|
|
8304
|
+
options.canonicalRef
|
|
8305
|
+
);
|
|
8306
|
+
if (existing) {
|
|
8307
|
+
const updated = await options.issueProvider.updateIssue(existing.number, {
|
|
8308
|
+
title: options.title,
|
|
8309
|
+
body: options.body
|
|
8310
|
+
});
|
|
8311
|
+
await syncManagedLabels({
|
|
8312
|
+
issueProvider: options.issueProvider,
|
|
8313
|
+
issueNumber: existing.number,
|
|
8314
|
+
existingLabels: existing.labels,
|
|
8315
|
+
desiredLabels: options.labels,
|
|
8316
|
+
labels: options.publishLabels
|
|
8317
|
+
});
|
|
8318
|
+
return { ...updated, labels: options.labels };
|
|
8319
|
+
}
|
|
8320
|
+
const created = await options.issueProvider.createIssue({
|
|
8321
|
+
title: options.title,
|
|
8322
|
+
body: options.body,
|
|
8323
|
+
labels: options.labels
|
|
8324
|
+
});
|
|
8325
|
+
return { ...created, labels: options.labels };
|
|
8326
|
+
}
|
|
8327
|
+
async function publishWorkspaceToGithub(options) {
|
|
8328
|
+
const parent = await upsertGithubIssue({
|
|
8329
|
+
issueProvider: options.issueProvider,
|
|
8330
|
+
canonicalRef: options.workspace.prdRef,
|
|
8331
|
+
title: options.workspace.parent.title,
|
|
8332
|
+
body: options.workspace.parent.body,
|
|
8333
|
+
labels: [options.labels.needsTriage],
|
|
8334
|
+
publishLabels: options.labels
|
|
8335
|
+
});
|
|
8336
|
+
const children = [];
|
|
8337
|
+
for (const child of options.workspace.children) {
|
|
8338
|
+
const labels = childLabels({
|
|
8339
|
+
body: child.body,
|
|
8340
|
+
blocked: child.blockedByRefs.length > 0,
|
|
8341
|
+
labels: options.labels,
|
|
8342
|
+
warnings: options.warnings,
|
|
8343
|
+
childRef: child.childRef
|
|
8344
|
+
});
|
|
8345
|
+
const githubIssue = await upsertGithubIssue({
|
|
8346
|
+
issueProvider: options.issueProvider,
|
|
8347
|
+
canonicalRef: `${options.workspace.prdRef} / ${child.childRef}`,
|
|
8348
|
+
title: child.title,
|
|
8349
|
+
body: child.body,
|
|
8350
|
+
labels,
|
|
8351
|
+
publishLabels: options.labels
|
|
8352
|
+
});
|
|
8353
|
+
children.push({ childRef: child.childRef, githubIssue });
|
|
8354
|
+
}
|
|
8355
|
+
return { parent, children };
|
|
8356
|
+
}
|
|
8357
|
+
async function runPrdPlanWorkspacePublishCommand(options) {
|
|
8358
|
+
const prdRef = normalizePrdRef(options.prdRef);
|
|
8359
|
+
const labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
8360
|
+
const { workspaceRoot, workspaceRelPath } = resolveWorkspaceRoot({
|
|
8361
|
+
repoRoot: options.repoRoot,
|
|
8362
|
+
workspacePath: options.workspacePath
|
|
8363
|
+
});
|
|
8364
|
+
logStep(
|
|
8365
|
+
options.logger,
|
|
8366
|
+
"info",
|
|
8367
|
+
`Publishing PRD Plan Workspace ${workspaceRelPath} for ${prdRef}.`
|
|
8368
|
+
);
|
|
8369
|
+
logStep(
|
|
8370
|
+
options.logger,
|
|
8371
|
+
"info",
|
|
8372
|
+
`Running PRD Plan Workspace Publication Gate for ${prdRef}.`
|
|
8373
|
+
);
|
|
8374
|
+
const validation = runValidateArtifactCommand({
|
|
8375
|
+
repoRoot: options.repoRoot,
|
|
8376
|
+
kind: "prd-plan-workspace",
|
|
8377
|
+
artifactPath: workspaceRoot,
|
|
8378
|
+
workspace: workspaceRoot,
|
|
8379
|
+
prdRef
|
|
8380
|
+
});
|
|
8381
|
+
if (!validation.ok) {
|
|
8382
|
+
throw new Error(formatValidationFailure(validation));
|
|
8383
|
+
}
|
|
8384
|
+
const parsed = await parsePrdPlanWorkspace({
|
|
8385
|
+
workspacePath: workspaceRoot,
|
|
8386
|
+
prdRef
|
|
8387
|
+
});
|
|
8388
|
+
if (!parsed.ok) {
|
|
8389
|
+
throw new Error(
|
|
8390
|
+
`PRD Plan Workspace parse failed: ${parsed.diagnostics.map((d) => d.message).join("; ")}`
|
|
8391
|
+
);
|
|
8392
|
+
}
|
|
8393
|
+
if (parsed.workspace.children.length === 0) {
|
|
8394
|
+
throw new Error(
|
|
8395
|
+
"PRD Plan Workspace must contain at least one child Issue."
|
|
8396
|
+
);
|
|
8397
|
+
}
|
|
8398
|
+
const workspace = relativizeWorkspaceModel({
|
|
8399
|
+
repoRoot: options.repoRoot,
|
|
8400
|
+
workspace: parsed.workspace
|
|
8401
|
+
});
|
|
8402
|
+
logStep(
|
|
8403
|
+
options.logger,
|
|
8404
|
+
"info",
|
|
8405
|
+
`Publication gate passed for ${prdRef}: ${workspace.children.length} child Issue(s).`
|
|
8406
|
+
);
|
|
8407
|
+
logStep(
|
|
8408
|
+
options.logger,
|
|
8409
|
+
"info",
|
|
8410
|
+
`Registering Beads Work Graph for ${prdRef}: ${workspace.parent.beadsId}.`
|
|
8411
|
+
);
|
|
8412
|
+
const beadsPublication = await registerPrdWorkspaceInBeads({
|
|
8413
|
+
repoRoot: options.repoRoot,
|
|
8414
|
+
workspace,
|
|
8415
|
+
logger: options.logger
|
|
8416
|
+
});
|
|
8417
|
+
logStep(
|
|
8418
|
+
options.logger,
|
|
8419
|
+
"info",
|
|
8420
|
+
`Registered Beads Work Graph for ${prdRef}: ${beadsPublication.children.length} child bead(s), ${beadsPublication.dependencyMappings.length} dependency edge(s).`
|
|
8421
|
+
);
|
|
8422
|
+
const warnings = [];
|
|
8423
|
+
logStep(options.logger, "info", `Projecting ${prdRef} to GitHub Issues.`);
|
|
8424
|
+
const githubPublication = await publishWorkspaceToGithub({
|
|
8425
|
+
workspace,
|
|
8426
|
+
issueProvider: options.issueProvider,
|
|
8427
|
+
labels,
|
|
8428
|
+
warnings
|
|
8429
|
+
});
|
|
8430
|
+
logStep(
|
|
8431
|
+
options.logger,
|
|
8432
|
+
"info",
|
|
8433
|
+
`Projected ${prdRef} to GitHub: parent #${githubPublication.parent.number}, ${githubPublication.children.length} child Issue(s).`
|
|
8434
|
+
);
|
|
8435
|
+
logStep(
|
|
8436
|
+
options.logger,
|
|
8437
|
+
"info",
|
|
8438
|
+
`Backfilling Beads metadata for ${prdRef} with GitHub Issue numbers and URLs.`
|
|
8439
|
+
);
|
|
8440
|
+
await backfillPrdWorkspaceBeadsGithubMetadata({
|
|
8441
|
+
repoRoot: options.repoRoot,
|
|
8442
|
+
workspace,
|
|
8443
|
+
parentGithubIssue: githubPublication.parent,
|
|
8444
|
+
childGithubIssues: githubPublication.children.map((child) => ({
|
|
8445
|
+
childRef: child.childRef,
|
|
8446
|
+
githubIssue: child.githubIssue
|
|
8447
|
+
})),
|
|
8448
|
+
logger: options.logger
|
|
8449
|
+
});
|
|
8450
|
+
logStep(options.logger, "info", `Backfilled Beads metadata for ${prdRef}.`);
|
|
8451
|
+
const children = workspace.children.map((child) => {
|
|
8452
|
+
const githubChild = githubPublication.children.find(
|
|
8453
|
+
(published) => published.childRef === child.childRef
|
|
8454
|
+
);
|
|
8455
|
+
if (!githubChild) {
|
|
8456
|
+
throw new Error(
|
|
8457
|
+
`Missing GitHub publication result for ${child.childRef}.`
|
|
8458
|
+
);
|
|
8459
|
+
}
|
|
8460
|
+
return {
|
|
8461
|
+
childRef: child.childRef,
|
|
8462
|
+
beadsId: child.beadsId,
|
|
8463
|
+
githubIssue: githubChild.githubIssue,
|
|
8464
|
+
sourceFile: child.sourcePath
|
|
8465
|
+
};
|
|
8466
|
+
});
|
|
8467
|
+
const publishPath = await writePrdPublishReceipt({
|
|
8468
|
+
workspaceRoot,
|
|
8469
|
+
workspacePath: workspace.workspacePath,
|
|
8470
|
+
parent: {
|
|
8471
|
+
prdRef,
|
|
8472
|
+
beadsId: beadsPublication.parent.beadsId,
|
|
8473
|
+
githubIssue: githubPublication.parent
|
|
8474
|
+
},
|
|
8475
|
+
children,
|
|
8476
|
+
dependencyMappings: beadsPublication.dependencyMappings,
|
|
8477
|
+
publicationTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8478
|
+
authoredBodiesMutated: false,
|
|
8479
|
+
warnings
|
|
8480
|
+
});
|
|
8481
|
+
logStep(
|
|
8482
|
+
options.logger,
|
|
8483
|
+
"info",
|
|
8484
|
+
`Wrote PRD publication receipt for ${prdRef}: ${publishPath}.`
|
|
8485
|
+
);
|
|
8486
|
+
return {
|
|
8487
|
+
status: "published",
|
|
8488
|
+
prdRef,
|
|
8489
|
+
workspacePath: workspace.workspacePath,
|
|
8490
|
+
parent: {
|
|
8491
|
+
prdRef,
|
|
8492
|
+
beadsId: beadsPublication.parent.beadsId,
|
|
8493
|
+
githubIssue: githubPublication.parent
|
|
8494
|
+
},
|
|
8495
|
+
children,
|
|
8496
|
+
dependencyMappings: beadsPublication.dependencyMappings,
|
|
8497
|
+
publishPath,
|
|
8498
|
+
warnings
|
|
8499
|
+
};
|
|
8500
|
+
}
|
|
8501
|
+
|
|
8502
|
+
// commands/prd-run.ts
|
|
8503
|
+
import { lstatSync, realpathSync } from "fs";
|
|
8504
|
+
import { join as join19 } from "path";
|
|
8505
|
+
|
|
8506
|
+
// prd-run/state.ts
|
|
8507
|
+
import {
|
|
8508
|
+
existsSync as existsSync14,
|
|
8509
|
+
mkdirSync as mkdirSync7,
|
|
8510
|
+
readFileSync as readFileSync15,
|
|
8511
|
+
readdirSync as readdirSync4,
|
|
8512
|
+
writeFileSync as writeFileSync4
|
|
8513
|
+
} from "fs";
|
|
8514
|
+
import { join as join18 } from "path";
|
|
8515
|
+
import { z as z2 } from "zod";
|
|
8516
|
+
|
|
8517
|
+
// prd-run/repair-guidance.ts
|
|
8518
|
+
import { z } from "zod";
|
|
8519
|
+
var PrdRunRepairAttemptSchema = z.object({
|
|
8520
|
+
attemptedAt: z.string().min(1),
|
|
8521
|
+
outcome: z.enum(["blocked", "projection-failed", "unsupported-state"]),
|
|
8522
|
+
failureCode: z.string().min(1),
|
|
8523
|
+
summary: z.string().min(1)
|
|
8524
|
+
}).strict();
|
|
8525
|
+
var PrdRunRepairGuidanceSchema = z.object({
|
|
8526
|
+
createdAt: z.string().min(1),
|
|
8527
|
+
updatedAt: z.string().min(1),
|
|
8528
|
+
blockedGate: z.enum([
|
|
8529
|
+
"receipt-check",
|
|
8530
|
+
"branch-state",
|
|
8531
|
+
"git",
|
|
8532
|
+
"queue",
|
|
8533
|
+
"final-review"
|
|
8534
|
+
]),
|
|
8535
|
+
blockedStage: z.string().min(1),
|
|
8536
|
+
failureCode: z.string().regex(
|
|
8537
|
+
/^[a-z][a-z0-9-]*$/,
|
|
8538
|
+
"failureCode must be kebab-case (lowercase letters, digits, hyphens)"
|
|
8539
|
+
),
|
|
8540
|
+
humanReadableReason: z.string().min(1),
|
|
8541
|
+
repairability: z.enum([
|
|
8542
|
+
"automatic-retry-safe",
|
|
8543
|
+
"operator-action",
|
|
8544
|
+
"unsupported-state"
|
|
8545
|
+
]),
|
|
8546
|
+
nextAction: z.string().min(1),
|
|
8547
|
+
nextRecommendedCommand: z.string().min(1),
|
|
8548
|
+
diagnostics: z.array(z.string()).optional(),
|
|
8549
|
+
offendingPaths: z.array(z.string()).optional(),
|
|
8550
|
+
issue: z.object({
|
|
8551
|
+
number: z.number().int().positive(),
|
|
8552
|
+
title: z.string().optional(),
|
|
8553
|
+
branch: z.string().optional(),
|
|
8554
|
+
worktreePath: z.string().optional(),
|
|
8555
|
+
beadsChildId: z.string().min(1).optional(),
|
|
8556
|
+
beadsChildRef: z.string().min(1).optional(),
|
|
8557
|
+
beadsChildClosed: z.boolean().optional()
|
|
8558
|
+
}).strict().optional(),
|
|
8559
|
+
pr: z.object({
|
|
8560
|
+
number: z.number().int().positive().optional(),
|
|
8561
|
+
url: z.string().url().optional()
|
|
8562
|
+
}).strict().optional(),
|
|
8563
|
+
attemptHistory: z.array(PrdRunRepairAttemptSchema).optional(),
|
|
8564
|
+
targetName: z.string().min(1).optional(),
|
|
8565
|
+
baseBranch: z.string().min(1).optional()
|
|
8566
|
+
}).strict();
|
|
8567
|
+
function buildPrdRunRepairGuidance(input) {
|
|
8568
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8569
|
+
return PrdRunRepairGuidanceSchema.parse({
|
|
8570
|
+
createdAt: now,
|
|
8571
|
+
updatedAt: now,
|
|
8572
|
+
blockedGate: input.blockedGate,
|
|
8573
|
+
blockedStage: input.blockedStage,
|
|
8574
|
+
failureCode: input.failureCode,
|
|
8575
|
+
humanReadableReason: input.humanReadableReason,
|
|
8576
|
+
repairability: input.repairability,
|
|
8577
|
+
nextAction: input.nextAction,
|
|
8578
|
+
nextRecommendedCommand: input.nextRecommendedCommand,
|
|
8579
|
+
diagnostics: input.diagnostics,
|
|
8580
|
+
offendingPaths: input.offendingPaths,
|
|
8581
|
+
issue: input.issue,
|
|
8582
|
+
pr: input.pr,
|
|
8583
|
+
targetName: input.targetName,
|
|
8584
|
+
baseBranch: input.baseBranch,
|
|
8585
|
+
attemptHistory: void 0
|
|
8586
|
+
});
|
|
8587
|
+
}
|
|
8588
|
+
function appendPrdRunRepairAttempt(guidance, attempt) {
|
|
8589
|
+
const newAttempt = {
|
|
8590
|
+
attemptedAt: attempt.attemptedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8591
|
+
outcome: attempt.outcome,
|
|
8592
|
+
failureCode: attempt.failureCode,
|
|
8593
|
+
summary: attempt.summary
|
|
8594
|
+
};
|
|
8595
|
+
const history = [...guidance.attemptHistory ?? [], newAttempt].slice(-5);
|
|
8596
|
+
return PrdRunRepairGuidanceSchema.parse({
|
|
8597
|
+
...guidance,
|
|
8598
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8599
|
+
attemptHistory: history
|
|
8600
|
+
});
|
|
8601
|
+
}
|
|
8602
|
+
function validateActivePrdRunRepairGuidance(record) {
|
|
8603
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8604
|
+
if (record.repairGuidance) {
|
|
8605
|
+
const parsed = PrdRunRepairGuidanceSchema.safeParse(record.repairGuidance);
|
|
8606
|
+
if (parsed.success) {
|
|
8607
|
+
return { ok: true, guidance: parsed.data };
|
|
8608
|
+
}
|
|
8609
|
+
return {
|
|
8610
|
+
ok: false,
|
|
8611
|
+
guidance: {
|
|
8612
|
+
createdAt: now,
|
|
8613
|
+
updatedAt: now,
|
|
8614
|
+
blockedGate: record.blockedGate ?? "queue",
|
|
8615
|
+
blockedStage: "unknown",
|
|
8616
|
+
failureCode: "malformed-repair-guidance",
|
|
8617
|
+
humanReadableReason: "Repair guidance state is malformed or invalid. Inspect the PRD Run record.",
|
|
8618
|
+
repairability: "unsupported-state",
|
|
8619
|
+
nextAction: "inspect-prd-run-state",
|
|
8620
|
+
nextRecommendedCommand: `pourkit prd-run status ${record.prdRef}`,
|
|
8621
|
+
diagnostics: [
|
|
8622
|
+
`Validation error: ${parsed.error?.message ?? "Unknown schema error"}`
|
|
8623
|
+
]
|
|
8624
|
+
}
|
|
8625
|
+
};
|
|
8626
|
+
}
|
|
8627
|
+
return {
|
|
8628
|
+
ok: false,
|
|
8629
|
+
guidance: {
|
|
8630
|
+
createdAt: now,
|
|
8631
|
+
updatedAt: now,
|
|
8632
|
+
blockedGate: record.blockedGate ?? "queue",
|
|
8633
|
+
blockedStage: "unknown",
|
|
8634
|
+
failureCode: "missing-repair-guidance",
|
|
8635
|
+
humanReadableReason: "No active repair guidance found for a blocked or unresolved record.",
|
|
8636
|
+
repairability: "unsupported-state",
|
|
8637
|
+
nextAction: "inspect-prd-run-state",
|
|
8638
|
+
nextRecommendedCommand: `pourkit prd-run status ${record.prdRef}`,
|
|
8639
|
+
diagnostics: record.diagnostics ? [...record.diagnostics] : []
|
|
7537
8640
|
}
|
|
7538
8641
|
};
|
|
7539
8642
|
}
|
|
@@ -7716,17 +8819,17 @@ function readPrdRun(repoRoot2, prdRef) {
|
|
|
7716
8819
|
}
|
|
7717
8820
|
}
|
|
7718
8821
|
function listPrdRuns(repoRoot2) {
|
|
7719
|
-
const stateDir =
|
|
8822
|
+
const stateDir = join18(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7720
8823
|
if (!existsSync14(stateDir)) {
|
|
7721
8824
|
return { records: [], diagnostics: [] };
|
|
7722
8825
|
}
|
|
7723
8826
|
const records = [];
|
|
7724
8827
|
const diagnostics = [];
|
|
7725
|
-
for (const entry of
|
|
8828
|
+
for (const entry of readdirSync4(stateDir, { withFileTypes: true }).sort(
|
|
7726
8829
|
(left, right) => left.name.localeCompare(right.name)
|
|
7727
8830
|
)) {
|
|
7728
8831
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
7729
|
-
const recordPath =
|
|
8832
|
+
const recordPath = join18(stateDir, entry.name);
|
|
7730
8833
|
try {
|
|
7731
8834
|
const record = PrdRunRecordSchema.parse(
|
|
7732
8835
|
JSON.parse(readFileSync15(recordPath, "utf-8"))
|
|
@@ -7743,13 +8846,13 @@ function listPrdRuns(repoRoot2) {
|
|
|
7743
8846
|
function writePrdRunRecord(repoRoot2, record) {
|
|
7744
8847
|
const normalized = normalizePrdRunRef(record.prdRef);
|
|
7745
8848
|
const parsed = PrdRunRecordSchema.parse({ ...record, prdRef: normalized });
|
|
7746
|
-
const stateDir =
|
|
8849
|
+
const stateDir = join18(repoRoot2, PRD_RUN_STATE_DIR);
|
|
7747
8850
|
const recordPath = getRecordPath(repoRoot2, normalized);
|
|
7748
8851
|
mkdirSync7(stateDir, { recursive: true });
|
|
7749
8852
|
writeFileSync4(recordPath, JSON.stringify(parsed, null, 2), "utf-8");
|
|
7750
8853
|
}
|
|
7751
8854
|
function getRecordPath(repoRoot2, prdRef) {
|
|
7752
|
-
return
|
|
8855
|
+
return join18(
|
|
7753
8856
|
repoRoot2,
|
|
7754
8857
|
PRD_RUN_STATE_DIR,
|
|
7755
8858
|
`${normalizePrdRunRef(prdRef)}.json`
|
|
@@ -7919,9 +9022,11 @@ async function anyBlockerStillOpen(refs, getIssueState) {
|
|
|
7919
9022
|
// commands/queue.ts
|
|
7920
9023
|
var QueueProviderError = class extends Error {
|
|
7921
9024
|
_tag = "QueueProviderError";
|
|
7922
|
-
|
|
9025
|
+
issue;
|
|
9026
|
+
constructor(message, issue) {
|
|
7923
9027
|
super(message);
|
|
7924
9028
|
this.name = "QueueProviderError";
|
|
9029
|
+
this.issue = issue;
|
|
7925
9030
|
}
|
|
7926
9031
|
};
|
|
7927
9032
|
function issueDataToCandidate(issue) {
|
|
@@ -8154,11 +9259,14 @@ function runOneQueueIssueEffect(options) {
|
|
|
8154
9259
|
catch: (e) => {
|
|
8155
9260
|
if (e instanceof Error && e.name === "HumanHandoffStop") {
|
|
8156
9261
|
return new QueueProviderError(
|
|
8157
|
-
`Issue Final Review requires human handoff: ${e.message}
|
|
9262
|
+
`Issue Final Review requires human handoff: ${e.message}`,
|
|
9263
|
+
selectedIssue
|
|
8158
9264
|
);
|
|
8159
9265
|
}
|
|
8160
|
-
|
|
8161
|
-
|
|
9266
|
+
return new QueueProviderError(
|
|
9267
|
+
e instanceof Error ? e.message : String(e),
|
|
9268
|
+
selectedIssue
|
|
9269
|
+
);
|
|
8162
9270
|
}
|
|
8163
9271
|
});
|
|
8164
9272
|
logger.raw("Issue completed successfully:");
|
|
@@ -8247,6 +9355,26 @@ async function runQueueCommand(options) {
|
|
|
8247
9355
|
}
|
|
8248
9356
|
return runEffectAndMapExit(runQueueLoop(queueOptions));
|
|
8249
9357
|
}
|
|
9358
|
+
async function runClaimedPrdIssueCommand(options) {
|
|
9359
|
+
const baseBranchOverride = options.queueRunContext.prdBranch;
|
|
9360
|
+
const runResult = await runIssueCommand({
|
|
9361
|
+
issueNumber: options.issue.number,
|
|
9362
|
+
targetName: options.targetName,
|
|
9363
|
+
config: options.config,
|
|
9364
|
+
issueProvider: options.issueProvider,
|
|
9365
|
+
prProvider: options.prProvider,
|
|
9366
|
+
executionProvider: options.executionProvider,
|
|
9367
|
+
force: options.force,
|
|
9368
|
+
logger: options.logger,
|
|
9369
|
+
repoRoot: options.repoRoot,
|
|
9370
|
+
...baseBranchOverride ? { baseBranchOverride } : {},
|
|
9371
|
+
...options.queueRunContext.beadsChild ? { beadsIssueRunContext: options.queueRunContext.beadsChild } : {}
|
|
9372
|
+
});
|
|
9373
|
+
return {
|
|
9374
|
+
selected: options.issue,
|
|
9375
|
+
runResult
|
|
9376
|
+
};
|
|
9377
|
+
}
|
|
8250
9378
|
|
|
8251
9379
|
// prd-run/evidence.ts
|
|
8252
9380
|
function validatePrdRunStartEvidence(record, context) {
|
|
@@ -8459,6 +9587,185 @@ function validatePrdRunTerminalEvidence(record, context) {
|
|
|
8459
9587
|
return { ok: true };
|
|
8460
9588
|
}
|
|
8461
9589
|
|
|
9590
|
+
// beads/prd-ready.ts
|
|
9591
|
+
function derivePrdEpicId(prdRef) {
|
|
9592
|
+
const trimmed = prdRef.trim().toUpperCase();
|
|
9593
|
+
const match = trimmed.match(/^(?:PRD-)?(\d+)$/);
|
|
9594
|
+
if (!match) {
|
|
9595
|
+
throw new Error(
|
|
9596
|
+
`Invalid PRD ref "${prdRef}". Expected format: PRD-<number> (e.g., PRD-0078)`
|
|
9597
|
+
);
|
|
9598
|
+
}
|
|
9599
|
+
const padded = match[1].padStart(4, "0");
|
|
9600
|
+
return `bd-${padded}`;
|
|
9601
|
+
}
|
|
9602
|
+
async function claimReadyPrdChild(options) {
|
|
9603
|
+
const epicId = derivePrdEpicId(options.prdRef);
|
|
9604
|
+
const result = await runBdJson({
|
|
9605
|
+
repoRoot: options.repoRoot,
|
|
9606
|
+
args: ["ready", "--parent", epicId, "--claim", "--json"],
|
|
9607
|
+
logger: options.logger
|
|
9608
|
+
});
|
|
9609
|
+
if (!result || !result.id) {
|
|
9610
|
+
return { ok: false, epicId, reason: "no-ready-child" };
|
|
9611
|
+
}
|
|
9612
|
+
return {
|
|
9613
|
+
ok: true,
|
|
9614
|
+
epicId,
|
|
9615
|
+
child: {
|
|
9616
|
+
id: result.id,
|
|
9617
|
+
title: result.title,
|
|
9618
|
+
metadata: result.metadata
|
|
9619
|
+
}
|
|
9620
|
+
};
|
|
9621
|
+
}
|
|
9622
|
+
function isMissingIssueMessage2(value) {
|
|
9623
|
+
return /no issues? found matching/i.test(value);
|
|
9624
|
+
}
|
|
9625
|
+
function getPourkitPrdRef(metadata) {
|
|
9626
|
+
if (!metadata || typeof metadata !== "object") return void 0;
|
|
9627
|
+
const pourkit = metadata.pourkit;
|
|
9628
|
+
if (!pourkit || typeof pourkit !== "object") return void 0;
|
|
9629
|
+
const prdRef = pourkit.prdRef;
|
|
9630
|
+
return typeof prdRef === "string" ? prdRef : void 0;
|
|
9631
|
+
}
|
|
9632
|
+
async function checkPrdBeadsEpicPresence(options) {
|
|
9633
|
+
const expectedPrdRef = options.prdRef.trim().toUpperCase();
|
|
9634
|
+
const epicId = derivePrdEpicId(expectedPrdRef);
|
|
9635
|
+
let result;
|
|
9636
|
+
try {
|
|
9637
|
+
result = await runBdJson({
|
|
9638
|
+
repoRoot: options.repoRoot,
|
|
9639
|
+
args: ["show", epicId, "--json"],
|
|
9640
|
+
logger: options.logger
|
|
9641
|
+
});
|
|
9642
|
+
} catch (error) {
|
|
9643
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9644
|
+
if (isMissingIssueMessage2(message)) {
|
|
9645
|
+
return { ok: true, epicId, exists: false, reason: "missing-epic" };
|
|
9646
|
+
}
|
|
9647
|
+
return { ok: false, epicId, reason: message };
|
|
9648
|
+
}
|
|
9649
|
+
if (!result) {
|
|
9650
|
+
return {
|
|
9651
|
+
ok: false,
|
|
9652
|
+
epicId,
|
|
9653
|
+
reason: `bd show ${epicId} returned no result.`
|
|
9654
|
+
};
|
|
9655
|
+
}
|
|
9656
|
+
if (result.error) {
|
|
9657
|
+
if (isMissingIssueMessage2(result.error)) {
|
|
9658
|
+
return { ok: true, epicId, exists: false, reason: "missing-epic" };
|
|
9659
|
+
}
|
|
9660
|
+
return { ok: false, epicId, reason: result.error };
|
|
9661
|
+
}
|
|
9662
|
+
if (result.id !== epicId) {
|
|
9663
|
+
return {
|
|
9664
|
+
ok: false,
|
|
9665
|
+
epicId,
|
|
9666
|
+
reason: `Expected Beads epic ${epicId}, but bd show returned ${result.id ?? "no id"}.`
|
|
9667
|
+
};
|
|
9668
|
+
}
|
|
9669
|
+
const metadataPrdRef = getPourkitPrdRef(result.metadata)?.toUpperCase();
|
|
9670
|
+
if (metadataPrdRef !== expectedPrdRef) {
|
|
9671
|
+
return {
|
|
9672
|
+
ok: false,
|
|
9673
|
+
epicId,
|
|
9674
|
+
reason: `Beads epic ${epicId} is missing matching pourkit.prdRef metadata for ${expectedPrdRef}.`
|
|
9675
|
+
};
|
|
9676
|
+
}
|
|
9677
|
+
return { ok: true, epicId, exists: true };
|
|
9678
|
+
}
|
|
9679
|
+
|
|
9680
|
+
// beads/issue-context.ts
|
|
9681
|
+
function parsePourkitBeadsMetadata(metadata) {
|
|
9682
|
+
if (typeof metadata !== "object" || metadata === null) {
|
|
9683
|
+
return { ok: false, reason: "metadata must be an object" };
|
|
9684
|
+
}
|
|
9685
|
+
const obj = metadata;
|
|
9686
|
+
if (typeof obj.pourkit !== "object" || obj.pourkit === null) {
|
|
9687
|
+
return {
|
|
9688
|
+
ok: false,
|
|
9689
|
+
reason: "metadata must contain top-level pourkit object"
|
|
9690
|
+
};
|
|
9691
|
+
}
|
|
9692
|
+
const pourkit = obj.pourkit;
|
|
9693
|
+
if (typeof pourkit.prdRef !== "string" || pourkit.prdRef.trim() === "") {
|
|
9694
|
+
return { ok: false, reason: "pourkit.prdRef must be a non-empty string" };
|
|
9695
|
+
}
|
|
9696
|
+
if (typeof pourkit.childRef !== "string" || !/^I-\d+$/.test(pourkit.childRef)) {
|
|
9697
|
+
return { ok: false, reason: "pourkit.childRef must be an I-0N string" };
|
|
9698
|
+
}
|
|
9699
|
+
if (typeof pourkit.githubIssueNumber !== "number" || !Number.isInteger(pourkit.githubIssueNumber) || pourkit.githubIssueNumber <= 0) {
|
|
9700
|
+
return {
|
|
9701
|
+
ok: false,
|
|
9702
|
+
reason: "pourkit.githubIssueNumber must be a positive integer"
|
|
9703
|
+
};
|
|
9704
|
+
}
|
|
9705
|
+
if (pourkit.workspacePath !== void 0 && typeof pourkit.workspacePath !== "string") {
|
|
9706
|
+
return {
|
|
9707
|
+
ok: false,
|
|
9708
|
+
reason: "pourkit.workspacePath must be a string when present"
|
|
9709
|
+
};
|
|
9710
|
+
}
|
|
9711
|
+
if (pourkit.sourcePath !== void 0 && typeof pourkit.sourcePath !== "string") {
|
|
9712
|
+
return {
|
|
9713
|
+
ok: false,
|
|
9714
|
+
reason: "pourkit.sourcePath must be a string when present"
|
|
9715
|
+
};
|
|
9716
|
+
}
|
|
9717
|
+
if (pourkit.githubIssueUrl !== void 0 && typeof pourkit.githubIssueUrl !== "string") {
|
|
9718
|
+
return {
|
|
9719
|
+
ok: false,
|
|
9720
|
+
reason: "pourkit.githubIssueUrl must be a string when present"
|
|
9721
|
+
};
|
|
9722
|
+
}
|
|
9723
|
+
return {
|
|
9724
|
+
ok: true,
|
|
9725
|
+
prdRef: pourkit.prdRef,
|
|
9726
|
+
childRef: pourkit.childRef,
|
|
9727
|
+
githubIssueNumber: pourkit.githubIssueNumber,
|
|
9728
|
+
workspacePath: pourkit.workspacePath,
|
|
9729
|
+
sourcePath: pourkit.sourcePath,
|
|
9730
|
+
githubIssueUrl: pourkit.githubIssueUrl
|
|
9731
|
+
};
|
|
9732
|
+
}
|
|
9733
|
+
async function buildIssueContextFromBeadsChild(options) {
|
|
9734
|
+
const parsed = parsePourkitBeadsMetadata(options.child.metadata);
|
|
9735
|
+
if (!parsed.ok) {
|
|
9736
|
+
return { ok: false, reason: parsed.reason };
|
|
9737
|
+
}
|
|
9738
|
+
if (options.expectedPrdRef !== void 0 && parsed.prdRef !== options.expectedPrdRef) {
|
|
9739
|
+
return {
|
|
9740
|
+
ok: false,
|
|
9741
|
+
reason: `PRD ref mismatch: expected "${options.expectedPrdRef}", got "${parsed.prdRef}"`
|
|
9742
|
+
};
|
|
9743
|
+
}
|
|
9744
|
+
try {
|
|
9745
|
+
const issue = await options.issueProvider.fetchIssue(
|
|
9746
|
+
parsed.githubIssueNumber
|
|
9747
|
+
);
|
|
9748
|
+
return {
|
|
9749
|
+
ok: true,
|
|
9750
|
+
issue,
|
|
9751
|
+
beads: {
|
|
9752
|
+
prdRef: parsed.prdRef,
|
|
9753
|
+
childRef: parsed.childRef,
|
|
9754
|
+
childId: options.child.id,
|
|
9755
|
+
workspacePath: parsed.workspacePath,
|
|
9756
|
+
sourcePath: parsed.sourcePath,
|
|
9757
|
+
githubIssueNumber: parsed.githubIssueNumber,
|
|
9758
|
+
githubIssueUrl: parsed.githubIssueUrl
|
|
9759
|
+
}
|
|
9760
|
+
};
|
|
9761
|
+
} catch (error) {
|
|
9762
|
+
return {
|
|
9763
|
+
ok: false,
|
|
9764
|
+
reason: `Failed to fetch GitHub issue #${parsed.githubIssueNumber}: ${error instanceof Error ? error.message : String(error)}`
|
|
9765
|
+
};
|
|
9766
|
+
}
|
|
9767
|
+
}
|
|
9768
|
+
|
|
8462
9769
|
// prd-run/coordinator.ts
|
|
8463
9770
|
function canRetryFinalReviewBlock(record) {
|
|
8464
9771
|
if (record.status !== "blocked" || record.blockedGate !== "final-review") {
|
|
@@ -9252,6 +10559,44 @@ function validateRecordedIssueResumeEvidence(options) {
|
|
|
9252
10559
|
baseBranch: guidance.baseBranch
|
|
9253
10560
|
});
|
|
9254
10561
|
}
|
|
10562
|
+
var QUEUE_FAILURE_CODES_WITHOUT_SELECTED_ISSUE = /* @__PURE__ */ new Set([
|
|
10563
|
+
"missing-github-providers",
|
|
10564
|
+
"missing-config",
|
|
10565
|
+
"zero-processed",
|
|
10566
|
+
"queue-blocked",
|
|
10567
|
+
"queue-exception",
|
|
10568
|
+
"missing-drain-evidence",
|
|
10569
|
+
"missing-queue-started-at",
|
|
10570
|
+
"missing-queue-command",
|
|
10571
|
+
"missing-queue-drained-at",
|
|
10572
|
+
"missing-queue-processed-count",
|
|
10573
|
+
"prd-branch-mismatch",
|
|
10574
|
+
"missing-child-finalization-evidence"
|
|
10575
|
+
]);
|
|
10576
|
+
function canRetryQueueGuidanceWithoutIssue(options) {
|
|
10577
|
+
const { guidance, startReceipt } = options;
|
|
10578
|
+
if (guidance.blockedGate !== "queue") return false;
|
|
10579
|
+
if (QUEUE_FAILURE_CODES_WITHOUT_SELECTED_ISSUE.has(guidance.failureCode)) {
|
|
10580
|
+
return true;
|
|
10581
|
+
}
|
|
10582
|
+
return guidance.failureCode === "missing-issue-identity" && !startReceipt?.queueStartedAt;
|
|
10583
|
+
}
|
|
10584
|
+
function extractIssueFromQueueError(error) {
|
|
10585
|
+
if (!error || typeof error !== "object" || !("issue" in error)) {
|
|
10586
|
+
return void 0;
|
|
10587
|
+
}
|
|
10588
|
+
const issue = error.issue;
|
|
10589
|
+
if (!issue || typeof issue !== "object") return void 0;
|
|
10590
|
+
const number = issue.number;
|
|
10591
|
+
if (typeof number !== "number" || !Number.isInteger(number) || number <= 0) {
|
|
10592
|
+
return void 0;
|
|
10593
|
+
}
|
|
10594
|
+
const title = issue.title;
|
|
10595
|
+
return {
|
|
10596
|
+
number,
|
|
10597
|
+
...typeof title === "string" ? { title } : {}
|
|
10598
|
+
};
|
|
10599
|
+
}
|
|
9255
10600
|
async function attemptRecordedIssueLabelProjection(options, prdRef, resumeIssueNumber, originalGuidance) {
|
|
9256
10601
|
if (resumeIssueNumber === void 0) return true;
|
|
9257
10602
|
try {
|
|
@@ -9277,8 +10622,109 @@ async function attemptRecordedIssueLabelProjection(options, prdRef, resumeIssueN
|
|
|
9277
10622
|
});
|
|
9278
10623
|
}
|
|
9279
10624
|
}
|
|
9280
|
-
return false;
|
|
10625
|
+
return false;
|
|
10626
|
+
}
|
|
10627
|
+
}
|
|
10628
|
+
function repairGuidanceRequiresOpenBeadsChild(guidance) {
|
|
10629
|
+
return Boolean(
|
|
10630
|
+
guidance?.issue?.beadsChildId && guidance.issue.beadsChildClosed !== true
|
|
10631
|
+
);
|
|
10632
|
+
}
|
|
10633
|
+
function logCoordinatorStep(logger, level, message) {
|
|
10634
|
+
logger?.step?.(level, message);
|
|
10635
|
+
}
|
|
10636
|
+
async function selectPrdRunCoordinatorMode(options) {
|
|
10637
|
+
const preflight = await checkBeadsAvailability({
|
|
10638
|
+
repoRoot: options.repoRoot,
|
|
10639
|
+
logger: options.logger
|
|
10640
|
+
});
|
|
10641
|
+
if (!preflight.available) {
|
|
10642
|
+
if (!options.requiresBeads) {
|
|
10643
|
+
logCoordinatorStep(
|
|
10644
|
+
options.logger,
|
|
10645
|
+
"info",
|
|
10646
|
+
`PRD Run ${options.prdRef} queue drain mode: GitHub-compatible (Beads unavailable: ${preflight.error}).`
|
|
10647
|
+
);
|
|
10648
|
+
return { mode: "github" };
|
|
10649
|
+
}
|
|
10650
|
+
logCoordinatorStep(
|
|
10651
|
+
options.logger,
|
|
10652
|
+
"warn",
|
|
10653
|
+
`PRD Run ${options.prdRef} queue drain blocked: active Beads child state requires Beads, but Beads is unavailable (${preflight.error}).`
|
|
10654
|
+
);
|
|
10655
|
+
return {
|
|
10656
|
+
mode: "blocked",
|
|
10657
|
+
guidance: buildPrdRunRepairGuidance({
|
|
10658
|
+
blockedGate: "queue",
|
|
10659
|
+
blockedStage: "queue-drain",
|
|
10660
|
+
failureCode: "beads-unavailable",
|
|
10661
|
+
humanReadableReason: `Beads is not available: ${preflight.error}. ${preflight.repairGuidance}`,
|
|
10662
|
+
repairability: "operator-action",
|
|
10663
|
+
nextAction: "inspect-and-retry",
|
|
10664
|
+
nextRecommendedCommand: `pourkit prd-run status ${options.prdRef}`,
|
|
10665
|
+
diagnostics: [preflight.error, preflight.repairGuidance]
|
|
10666
|
+
})
|
|
10667
|
+
};
|
|
10668
|
+
}
|
|
10669
|
+
const epicPresence = await checkPrdBeadsEpicPresence({
|
|
10670
|
+
repoRoot: options.repoRoot,
|
|
10671
|
+
prdRef: options.prdRef,
|
|
10672
|
+
logger: options.logger
|
|
10673
|
+
});
|
|
10674
|
+
if (epicPresence.ok && epicPresence.exists) {
|
|
10675
|
+
logCoordinatorStep(
|
|
10676
|
+
options.logger,
|
|
10677
|
+
"info",
|
|
10678
|
+
`PRD Run ${options.prdRef} queue drain mode: Beads-backed (found epic ${epicPresence.epicId}).`
|
|
10679
|
+
);
|
|
10680
|
+
return { mode: "beads" };
|
|
10681
|
+
}
|
|
10682
|
+
if (epicPresence.ok && !epicPresence.exists) {
|
|
10683
|
+
if (!options.requiresBeads) {
|
|
10684
|
+
logCoordinatorStep(
|
|
10685
|
+
options.logger,
|
|
10686
|
+
"info",
|
|
10687
|
+
`PRD Run ${options.prdRef} queue drain mode: GitHub-compatible (Beads epic ${epicPresence.epicId} not found).`
|
|
10688
|
+
);
|
|
10689
|
+
return { mode: "github" };
|
|
10690
|
+
}
|
|
10691
|
+
logCoordinatorStep(
|
|
10692
|
+
options.logger,
|
|
10693
|
+
"warn",
|
|
10694
|
+
`PRD Run ${options.prdRef} queue drain blocked: Beads epic ${epicPresence.epicId} is missing but active Beads child state prevents GitHub fallback.`
|
|
10695
|
+
);
|
|
10696
|
+
return {
|
|
10697
|
+
mode: "blocked",
|
|
10698
|
+
guidance: buildPrdRunRepairGuidance({
|
|
10699
|
+
blockedGate: "queue",
|
|
10700
|
+
blockedStage: "queue-drain",
|
|
10701
|
+
failureCode: "beads-epic-missing",
|
|
10702
|
+
humanReadableReason: `Beads epic ${epicPresence.epicId} for ${options.prdRef} is missing, but this PRD Run has active Beads child state and cannot safely fall back to GitHub selection.`,
|
|
10703
|
+
repairability: "operator-action",
|
|
10704
|
+
nextAction: "inspect-and-retry",
|
|
10705
|
+
nextRecommendedCommand: `pourkit prd-run status ${options.prdRef}`,
|
|
10706
|
+
diagnostics: [`Missing Beads epic: ${epicPresence.epicId}`]
|
|
10707
|
+
})
|
|
10708
|
+
};
|
|
9281
10709
|
}
|
|
10710
|
+
logCoordinatorStep(
|
|
10711
|
+
options.logger,
|
|
10712
|
+
"warn",
|
|
10713
|
+
`PRD Run ${options.prdRef} queue drain blocked: failed to verify Beads epic ${epicPresence.epicId} (${epicPresence.reason}).`
|
|
10714
|
+
);
|
|
10715
|
+
return {
|
|
10716
|
+
mode: "blocked",
|
|
10717
|
+
guidance: buildPrdRunRepairGuidance({
|
|
10718
|
+
blockedGate: "queue",
|
|
10719
|
+
blockedStage: "queue-drain",
|
|
10720
|
+
failureCode: "beads-epic-check-failed",
|
|
10721
|
+
humanReadableReason: `Failed to verify Beads epic for ${options.prdRef}: ${epicPresence.reason}`,
|
|
10722
|
+
repairability: "operator-action",
|
|
10723
|
+
nextAction: "inspect-and-retry",
|
|
10724
|
+
nextRecommendedCommand: `pourkit prd-run status ${options.prdRef}`,
|
|
10725
|
+
diagnostics: [epicPresence.reason]
|
|
10726
|
+
})
|
|
10727
|
+
};
|
|
9282
10728
|
}
|
|
9283
10729
|
async function launchPrdRun(options) {
|
|
9284
10730
|
const prdRef = normalizePrdRunRef(options.prdRef);
|
|
@@ -9502,7 +10948,10 @@ async function launchPrdRun(options) {
|
|
|
9502
10948
|
}
|
|
9503
10949
|
if (validation.guidance.issue?.number) {
|
|
9504
10950
|
resumeIssueNumber = validation.guidance.issue.number;
|
|
9505
|
-
} else {
|
|
10951
|
+
} else if (!canRetryQueueGuidanceWithoutIssue({
|
|
10952
|
+
guidance: validation.guidance,
|
|
10953
|
+
startReceipt
|
|
10954
|
+
})) {
|
|
9506
10955
|
return buildLaunchBlockedOutcome(
|
|
9507
10956
|
options.repoRoot,
|
|
9508
10957
|
prdRef,
|
|
@@ -9537,7 +10986,37 @@ async function launchPrdRun(options) {
|
|
|
9537
10986
|
start: startReceipt,
|
|
9538
10987
|
repairGuidance: currentRecord?.repairGuidance
|
|
9539
10988
|
});
|
|
9540
|
-
|
|
10989
|
+
const modeSelection = await selectPrdRunCoordinatorMode({
|
|
10990
|
+
repoRoot: options.repoRoot,
|
|
10991
|
+
prdRef,
|
|
10992
|
+
logger: options.logger,
|
|
10993
|
+
requiresBeads: repairGuidanceRequiresOpenBeadsChild(
|
|
10994
|
+
currentRecord?.repairGuidance
|
|
10995
|
+
)
|
|
10996
|
+
});
|
|
10997
|
+
if (modeSelection.mode === "blocked") {
|
|
10998
|
+
return buildLaunchBlockedOutcome(
|
|
10999
|
+
options.repoRoot,
|
|
11000
|
+
prdRef,
|
|
11001
|
+
modeSelection.guidance,
|
|
11002
|
+
plan,
|
|
11003
|
+
startOutcome,
|
|
11004
|
+
targetName,
|
|
11005
|
+
startReceipt
|
|
11006
|
+
);
|
|
11007
|
+
}
|
|
11008
|
+
if (modeSelection.mode === "github") {
|
|
11009
|
+
return await launchGithubQueueDrain(
|
|
11010
|
+
options,
|
|
11011
|
+
prdRef,
|
|
11012
|
+
startReceipt,
|
|
11013
|
+
targetName,
|
|
11014
|
+
plan,
|
|
11015
|
+
startOutcome,
|
|
11016
|
+
resumeIssueNumber
|
|
11017
|
+
);
|
|
11018
|
+
}
|
|
11019
|
+
return await launchBeadsQueueDrain(
|
|
9541
11020
|
options,
|
|
9542
11021
|
prdRef,
|
|
9543
11022
|
startReceipt,
|
|
@@ -9556,21 +11035,299 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9556
11035
|
buildPrdRunRepairGuidance({
|
|
9557
11036
|
blockedGate: "queue",
|
|
9558
11037
|
blockedStage: "queue-drain",
|
|
9559
|
-
failureCode: "missing-github-providers",
|
|
9560
|
-
humanReadableReason: reason,
|
|
11038
|
+
failureCode: "missing-github-providers",
|
|
11039
|
+
humanReadableReason: reason,
|
|
11040
|
+
repairability: "operator-action",
|
|
11041
|
+
nextAction: "inspect-and-retry",
|
|
11042
|
+
nextRecommendedCommand: "pourkit prd-run launch --with-providers",
|
|
11043
|
+
diagnostics: [
|
|
11044
|
+
"Missing one or more required providers for GitHub-backed mode."
|
|
11045
|
+
]
|
|
11046
|
+
}),
|
|
11047
|
+
plan,
|
|
11048
|
+
startOutcome,
|
|
11049
|
+
targetName,
|
|
11050
|
+
startReceipt
|
|
11051
|
+
);
|
|
11052
|
+
}
|
|
11053
|
+
if (!startReceipt) {
|
|
11054
|
+
return buildLaunchBlockedOutcome(
|
|
11055
|
+
options.repoRoot,
|
|
11056
|
+
prdRef,
|
|
11057
|
+
buildPrdRunRepairGuidance({
|
|
11058
|
+
blockedGate: "branch-state",
|
|
11059
|
+
blockedStage: "start-validation",
|
|
11060
|
+
failureCode: "missing-start-receipt",
|
|
11061
|
+
humanReadableReason: `PRD Run ${prdRef} is missing start receipt before GitHub queue drain.`,
|
|
11062
|
+
repairability: "automatic-retry-safe",
|
|
11063
|
+
nextAction: "rerun-prd-run-launch",
|
|
11064
|
+
nextRecommendedCommand: "pourkit prd-run launch"
|
|
11065
|
+
}),
|
|
11066
|
+
plan,
|
|
11067
|
+
startOutcome,
|
|
11068
|
+
targetName
|
|
11069
|
+
);
|
|
11070
|
+
}
|
|
11071
|
+
if (!options.config) {
|
|
11072
|
+
const reason = `PRD Run ${prdRef} requires config for GitHub-backed queue drain.`;
|
|
11073
|
+
return buildLaunchBlockedOutcome(
|
|
11074
|
+
options.repoRoot,
|
|
11075
|
+
prdRef,
|
|
11076
|
+
buildPrdRunRepairGuidance({
|
|
11077
|
+
blockedGate: "queue",
|
|
11078
|
+
blockedStage: "queue-drain",
|
|
11079
|
+
failureCode: "missing-config",
|
|
11080
|
+
humanReadableReason: reason,
|
|
11081
|
+
repairability: "operator-action",
|
|
11082
|
+
nextAction: "inspect-and-retry",
|
|
11083
|
+
nextRecommendedCommand: "pourkit prd-run launch --with-config",
|
|
11084
|
+
diagnostics: ["Missing config for GitHub-backed mode."]
|
|
11085
|
+
}),
|
|
11086
|
+
plan,
|
|
11087
|
+
startOutcome,
|
|
11088
|
+
targetName,
|
|
11089
|
+
startReceipt
|
|
11090
|
+
);
|
|
11091
|
+
}
|
|
11092
|
+
const issueProvider = options.issueProvider;
|
|
11093
|
+
const prProvider = options.prProvider;
|
|
11094
|
+
const executionProvider = options.executionProvider;
|
|
11095
|
+
const logger = options.logger;
|
|
11096
|
+
const originalGuidance = readPrdRun(options.repoRoot, prdRef).record?.repairGuidance;
|
|
11097
|
+
logCoordinatorStep(
|
|
11098
|
+
logger,
|
|
11099
|
+
"info",
|
|
11100
|
+
`Starting GitHub-compatible PRD queue drain for ${prdRef}.`
|
|
11101
|
+
);
|
|
11102
|
+
try {
|
|
11103
|
+
const outcome = await runQueueCommand({
|
|
11104
|
+
targetName,
|
|
11105
|
+
config: options.config,
|
|
11106
|
+
issueProvider,
|
|
11107
|
+
prProvider,
|
|
11108
|
+
executionProvider,
|
|
11109
|
+
force: false,
|
|
11110
|
+
loop: true,
|
|
11111
|
+
logger,
|
|
11112
|
+
repoRoot: options.repoRoot,
|
|
11113
|
+
prdRef,
|
|
11114
|
+
queueRunContext: {
|
|
11115
|
+
prdRef,
|
|
11116
|
+
prdBranch: startReceipt.prdBranch,
|
|
11117
|
+
...resumeIssueNumber !== void 0 ? { resumeIssueNumber } : {}
|
|
11118
|
+
}
|
|
11119
|
+
});
|
|
11120
|
+
if (outcome.selected === null && outcome.code === "drained") {
|
|
11121
|
+
if (outcome.processedCount === 0) {
|
|
11122
|
+
const diagnostics = [
|
|
11123
|
+
"Queue drained without processing issues. No PRD-scoped candidates or runnable Issues."
|
|
11124
|
+
];
|
|
11125
|
+
const result2 = buildLaunchBlockedOutcome(
|
|
11126
|
+
options.repoRoot,
|
|
11127
|
+
prdRef,
|
|
11128
|
+
buildPrdRunRepairGuidance({
|
|
11129
|
+
blockedGate: "queue",
|
|
11130
|
+
blockedStage: "queue-drain",
|
|
11131
|
+
failureCode: "zero-processed",
|
|
11132
|
+
humanReadableReason: diagnostics[0],
|
|
11133
|
+
repairability: "automatic-retry-safe",
|
|
11134
|
+
nextAction: "rerun-prd-run-launch",
|
|
11135
|
+
nextRecommendedCommand: "pourkit prd-run launch",
|
|
11136
|
+
diagnostics
|
|
11137
|
+
}),
|
|
11138
|
+
plan,
|
|
11139
|
+
startOutcome,
|
|
11140
|
+
targetName,
|
|
11141
|
+
startReceipt
|
|
11142
|
+
);
|
|
11143
|
+
const projectionOk2 = await attemptRecordedIssueLabelProjection(
|
|
11144
|
+
options,
|
|
11145
|
+
prdRef,
|
|
11146
|
+
resumeIssueNumber,
|
|
11147
|
+
originalGuidance
|
|
11148
|
+
);
|
|
11149
|
+
if (!projectionOk2 && originalGuidance) {
|
|
11150
|
+
const updatedRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
11151
|
+
return {
|
|
11152
|
+
kind: "blocked",
|
|
11153
|
+
prdRef,
|
|
11154
|
+
guidance: updatedRecord.repairGuidance,
|
|
11155
|
+
start: startOutcome,
|
|
11156
|
+
resume: plan
|
|
11157
|
+
};
|
|
11158
|
+
}
|
|
11159
|
+
return result2;
|
|
11160
|
+
}
|
|
11161
|
+
startReceipt.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11162
|
+
startReceipt.queueProcessedCount = outcome.processedCount;
|
|
11163
|
+
const prdBranch = startReceipt.prdBranch ?? prdRef;
|
|
11164
|
+
writeDrainedRecord(options.repoRoot, prdRef, startReceipt, targetName);
|
|
11165
|
+
const drainRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
11166
|
+
if (drainRecord) {
|
|
11167
|
+
const drainContext = {
|
|
11168
|
+
prdRef,
|
|
11169
|
+
baseBranch: startReceipt.startBaseBranch,
|
|
11170
|
+
prdBranch,
|
|
11171
|
+
allChildIssuesFinalizedIntoPrdBranch: outcome.allProcessedIssuesFinalizedIntoPrdBranch === true
|
|
11172
|
+
};
|
|
11173
|
+
const terminalEvidence = validatePrdRunTerminalEvidence(
|
|
11174
|
+
drainRecord,
|
|
11175
|
+
drainContext
|
|
11176
|
+
);
|
|
11177
|
+
if (!terminalEvidence.ok) {
|
|
11178
|
+
return buildLaunchBlockedOutcome(
|
|
11179
|
+
options.repoRoot,
|
|
11180
|
+
prdRef,
|
|
11181
|
+
terminalEvidence.guidance,
|
|
11182
|
+
plan,
|
|
11183
|
+
startOutcome,
|
|
11184
|
+
targetName,
|
|
11185
|
+
startReceipt
|
|
11186
|
+
);
|
|
11187
|
+
}
|
|
11188
|
+
}
|
|
11189
|
+
writeTerminalRecord(
|
|
11190
|
+
options.repoRoot,
|
|
11191
|
+
prdRef,
|
|
11192
|
+
"completed_prd_branch",
|
|
11193
|
+
targetName,
|
|
11194
|
+
startReceipt,
|
|
11195
|
+
prdBranch
|
|
11196
|
+
);
|
|
11197
|
+
return {
|
|
11198
|
+
kind: "completed",
|
|
11199
|
+
prdRef,
|
|
11200
|
+
status: "completed_prd_branch",
|
|
11201
|
+
prdBranch,
|
|
11202
|
+
diagnostics: [
|
|
11203
|
+
`PRD Run completed on branch ${prdBranch}. Next: checkout ${prdBranch} for human review, or use release promotion workflow when ready.`
|
|
11204
|
+
],
|
|
11205
|
+
start: startOutcome,
|
|
11206
|
+
resume: plan
|
|
11207
|
+
};
|
|
11208
|
+
}
|
|
11209
|
+
if (outcome.selected === null) {
|
|
11210
|
+
const diagnostics = [outcome.reason];
|
|
11211
|
+
const result2 = buildLaunchBlockedOutcome(
|
|
11212
|
+
options.repoRoot,
|
|
11213
|
+
prdRef,
|
|
11214
|
+
buildPrdRunRepairGuidance({
|
|
11215
|
+
blockedGate: "queue",
|
|
11216
|
+
blockedStage: "queue-drain",
|
|
11217
|
+
failureCode: "queue-blocked",
|
|
11218
|
+
humanReadableReason: outcome.reason,
|
|
11219
|
+
repairability: "automatic-retry-safe",
|
|
11220
|
+
nextAction: "rerun-prd-run-launch",
|
|
11221
|
+
nextRecommendedCommand: "pourkit prd-run launch",
|
|
11222
|
+
diagnostics
|
|
11223
|
+
}),
|
|
11224
|
+
plan,
|
|
11225
|
+
startOutcome,
|
|
11226
|
+
targetName,
|
|
11227
|
+
startReceipt
|
|
11228
|
+
);
|
|
11229
|
+
const projectionOk2 = await attemptRecordedIssueLabelProjection(
|
|
11230
|
+
options,
|
|
11231
|
+
prdRef,
|
|
11232
|
+
resumeIssueNumber,
|
|
11233
|
+
originalGuidance
|
|
11234
|
+
);
|
|
11235
|
+
if (!projectionOk2 && originalGuidance) {
|
|
11236
|
+
const updatedRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
11237
|
+
return {
|
|
11238
|
+
kind: "blocked",
|
|
11239
|
+
prdRef,
|
|
11240
|
+
guidance: updatedRecord.repairGuidance,
|
|
11241
|
+
start: startOutcome,
|
|
11242
|
+
resume: plan
|
|
11243
|
+
};
|
|
11244
|
+
}
|
|
11245
|
+
return result2;
|
|
11246
|
+
}
|
|
11247
|
+
const outcomeReason = `Unexpected queue outcome: selected issue ${outcome.selected.number}`;
|
|
11248
|
+
const outcomeDiagnostics = [outcomeReason];
|
|
11249
|
+
const result = buildLaunchBlockedOutcome(
|
|
11250
|
+
options.repoRoot,
|
|
11251
|
+
prdRef,
|
|
11252
|
+
buildPrdRunRepairGuidance({
|
|
11253
|
+
blockedGate: "queue",
|
|
11254
|
+
blockedStage: "queue-drain",
|
|
11255
|
+
failureCode: "unexpected-queue-outcome",
|
|
11256
|
+
humanReadableReason: outcomeReason,
|
|
9561
11257
|
repairability: "operator-action",
|
|
9562
11258
|
nextAction: "inspect-and-retry",
|
|
9563
|
-
nextRecommendedCommand: "pourkit prd-run launch
|
|
9564
|
-
diagnostics:
|
|
9565
|
-
|
|
9566
|
-
|
|
11259
|
+
nextRecommendedCommand: "pourkit prd-run launch",
|
|
11260
|
+
diagnostics: outcomeDiagnostics,
|
|
11261
|
+
issue: {
|
|
11262
|
+
number: outcome.selected.number,
|
|
11263
|
+
title: outcome.selected.title
|
|
11264
|
+
}
|
|
11265
|
+
}),
|
|
11266
|
+
plan,
|
|
11267
|
+
startOutcome,
|
|
11268
|
+
targetName,
|
|
11269
|
+
startReceipt
|
|
11270
|
+
);
|
|
11271
|
+
const projectionOk = await attemptRecordedIssueLabelProjection(
|
|
11272
|
+
options,
|
|
11273
|
+
prdRef,
|
|
11274
|
+
resumeIssueNumber,
|
|
11275
|
+
originalGuidance
|
|
11276
|
+
);
|
|
11277
|
+
if (!projectionOk && originalGuidance) {
|
|
11278
|
+
const updatedRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
11279
|
+
return {
|
|
11280
|
+
kind: "blocked",
|
|
11281
|
+
prdRef,
|
|
11282
|
+
guidance: updatedRecord.repairGuidance,
|
|
11283
|
+
start: startOutcome,
|
|
11284
|
+
resume: plan
|
|
11285
|
+
};
|
|
11286
|
+
}
|
|
11287
|
+
return result;
|
|
11288
|
+
} catch (error) {
|
|
11289
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11290
|
+
const diagnostics = [msg];
|
|
11291
|
+
const issue = extractIssueFromQueueError(error);
|
|
11292
|
+
const result = buildLaunchBlockedOutcome(
|
|
11293
|
+
options.repoRoot,
|
|
11294
|
+
prdRef,
|
|
11295
|
+
buildPrdRunRepairGuidance({
|
|
11296
|
+
blockedGate: "queue",
|
|
11297
|
+
blockedStage: "queue-drain",
|
|
11298
|
+
failureCode: "queue-exception",
|
|
11299
|
+
humanReadableReason: msg,
|
|
11300
|
+
repairability: "automatic-retry-safe",
|
|
11301
|
+
nextAction: "rerun-prd-run-launch",
|
|
11302
|
+
nextRecommendedCommand: "pourkit prd-run launch",
|
|
11303
|
+
diagnostics,
|
|
11304
|
+
issue
|
|
9567
11305
|
}),
|
|
9568
11306
|
plan,
|
|
9569
11307
|
startOutcome,
|
|
9570
11308
|
targetName,
|
|
9571
11309
|
startReceipt
|
|
9572
11310
|
);
|
|
11311
|
+
const projectionOk = await attemptRecordedIssueLabelProjection(
|
|
11312
|
+
options,
|
|
11313
|
+
prdRef,
|
|
11314
|
+
resumeIssueNumber,
|
|
11315
|
+
originalGuidance
|
|
11316
|
+
);
|
|
11317
|
+
if (!projectionOk && originalGuidance) {
|
|
11318
|
+
const updatedRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
11319
|
+
return {
|
|
11320
|
+
kind: "blocked",
|
|
11321
|
+
prdRef,
|
|
11322
|
+
guidance: updatedRecord.repairGuidance,
|
|
11323
|
+
start: startOutcome,
|
|
11324
|
+
resume: plan
|
|
11325
|
+
};
|
|
11326
|
+
}
|
|
11327
|
+
return result;
|
|
9573
11328
|
}
|
|
11329
|
+
}
|
|
11330
|
+
async function launchBeadsQueueDrain(options, prdRef, startReceipt, targetName, plan, startOutcome, resumeIssueNumber) {
|
|
9574
11331
|
if (!startReceipt) {
|
|
9575
11332
|
return buildLaunchBlockedOutcome(
|
|
9576
11333
|
options.repoRoot,
|
|
@@ -9579,7 +11336,7 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9579
11336
|
blockedGate: "branch-state",
|
|
9580
11337
|
blockedStage: "start-validation",
|
|
9581
11338
|
failureCode: "missing-start-receipt",
|
|
9582
|
-
humanReadableReason: `PRD Run ${prdRef} is missing start receipt before
|
|
11339
|
+
humanReadableReason: `PRD Run ${prdRef} is missing start receipt before Beads queue drain.`,
|
|
9583
11340
|
repairability: "automatic-retry-safe",
|
|
9584
11341
|
nextAction: "rerun-prd-run-launch",
|
|
9585
11342
|
nextRecommendedCommand: "pourkit prd-run launch"
|
|
@@ -9590,7 +11347,6 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9590
11347
|
);
|
|
9591
11348
|
}
|
|
9592
11349
|
if (!options.config) {
|
|
9593
|
-
const reason = `PRD Run ${prdRef} requires config for GitHub-backed queue drain.`;
|
|
9594
11350
|
return buildLaunchBlockedOutcome(
|
|
9595
11351
|
options.repoRoot,
|
|
9596
11352
|
prdRef,
|
|
@@ -9598,11 +11354,54 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9598
11354
|
blockedGate: "queue",
|
|
9599
11355
|
blockedStage: "queue-drain",
|
|
9600
11356
|
failureCode: "missing-config",
|
|
9601
|
-
humanReadableReason:
|
|
11357
|
+
humanReadableReason: `PRD Run ${prdRef} requires config for Beads-backed queue drain.`,
|
|
9602
11358
|
repairability: "operator-action",
|
|
9603
11359
|
nextAction: "inspect-and-retry",
|
|
9604
11360
|
nextRecommendedCommand: "pourkit prd-run launch --with-config",
|
|
9605
|
-
diagnostics: ["Missing config for
|
|
11361
|
+
diagnostics: ["Missing config for Beads-backed mode."]
|
|
11362
|
+
}),
|
|
11363
|
+
plan,
|
|
11364
|
+
startOutcome,
|
|
11365
|
+
targetName,
|
|
11366
|
+
startReceipt
|
|
11367
|
+
);
|
|
11368
|
+
}
|
|
11369
|
+
if (!options.issueProvider) {
|
|
11370
|
+
return buildLaunchBlockedOutcome(
|
|
11371
|
+
options.repoRoot,
|
|
11372
|
+
prdRef,
|
|
11373
|
+
buildPrdRunRepairGuidance({
|
|
11374
|
+
blockedGate: "queue",
|
|
11375
|
+
blockedStage: "queue-drain",
|
|
11376
|
+
failureCode: "missing-issue-provider",
|
|
11377
|
+
humanReadableReason: `PRD Run ${prdRef} requires issueProvider for Beads-backed queue drain.`,
|
|
11378
|
+
repairability: "operator-action",
|
|
11379
|
+
nextAction: "inspect-and-retry",
|
|
11380
|
+
nextRecommendedCommand: "pourkit prd-run launch --with-providers",
|
|
11381
|
+
diagnostics: ["Missing issueProvider for Beads-backed mode."]
|
|
11382
|
+
}),
|
|
11383
|
+
plan,
|
|
11384
|
+
startOutcome,
|
|
11385
|
+
targetName,
|
|
11386
|
+
startReceipt
|
|
11387
|
+
);
|
|
11388
|
+
}
|
|
11389
|
+
if (!options.prProvider || !options.executionProvider || !options.logger) {
|
|
11390
|
+
const reason = `PRD Run ${prdRef} requires prProvider, executionProvider, and logger for Beads-backed queue drain.`;
|
|
11391
|
+
return buildLaunchBlockedOutcome(
|
|
11392
|
+
options.repoRoot,
|
|
11393
|
+
prdRef,
|
|
11394
|
+
buildPrdRunRepairGuidance({
|
|
11395
|
+
blockedGate: "queue",
|
|
11396
|
+
blockedStage: "queue-drain",
|
|
11397
|
+
failureCode: "missing-providers",
|
|
11398
|
+
humanReadableReason: reason,
|
|
11399
|
+
repairability: "operator-action",
|
|
11400
|
+
nextAction: "inspect-and-retry",
|
|
11401
|
+
nextRecommendedCommand: "pourkit prd-run launch --with-providers",
|
|
11402
|
+
diagnostics: [
|
|
11403
|
+
"Missing one or more required providers for Beads-backed mode."
|
|
11404
|
+
]
|
|
9606
11405
|
}),
|
|
9607
11406
|
plan,
|
|
9608
11407
|
startOutcome,
|
|
@@ -9614,166 +11413,262 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9614
11413
|
const prProvider = options.prProvider;
|
|
9615
11414
|
const executionProvider = options.executionProvider;
|
|
9616
11415
|
const logger = options.logger;
|
|
11416
|
+
const processedResults = [];
|
|
9617
11417
|
const originalGuidance = readPrdRun(options.repoRoot, prdRef).record?.repairGuidance;
|
|
9618
|
-
|
|
9619
|
-
|
|
11418
|
+
logCoordinatorStep(
|
|
11419
|
+
logger,
|
|
11420
|
+
"info",
|
|
11421
|
+
`Starting Beads-backed PRD queue drain for ${prdRef}.`
|
|
11422
|
+
);
|
|
11423
|
+
const preflight = await checkBeadsAvailability({
|
|
11424
|
+
repoRoot: options.repoRoot,
|
|
11425
|
+
logger
|
|
11426
|
+
});
|
|
11427
|
+
if (!preflight.available) {
|
|
11428
|
+
return buildLaunchBlockedOutcome(
|
|
11429
|
+
options.repoRoot,
|
|
11430
|
+
prdRef,
|
|
11431
|
+
buildPrdRunRepairGuidance({
|
|
11432
|
+
blockedGate: "queue",
|
|
11433
|
+
blockedStage: "queue-drain",
|
|
11434
|
+
failureCode: "beads-unavailable",
|
|
11435
|
+
humanReadableReason: `Beads is not available: ${preflight.error}. ${preflight.repairGuidance}`,
|
|
11436
|
+
repairability: "operator-action",
|
|
11437
|
+
nextAction: "inspect-and-retry",
|
|
11438
|
+
nextRecommendedCommand: `pourkit prd-run status ${prdRef}`,
|
|
11439
|
+
diagnostics: [preflight.error, preflight.repairGuidance]
|
|
11440
|
+
}),
|
|
11441
|
+
plan,
|
|
11442
|
+
startOutcome,
|
|
9620
11443
|
targetName,
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
logger,
|
|
9628
|
-
repoRoot: options.repoRoot,
|
|
11444
|
+
startReceipt
|
|
11445
|
+
);
|
|
11446
|
+
}
|
|
11447
|
+
if (resumeIssueNumber !== void 0) {
|
|
11448
|
+
const resumeIssue = originalGuidance?.issue;
|
|
11449
|
+
const resumeBeadsContext = resumeIssue?.beadsChildId && resumeIssue.beadsChildRef && !resumeIssue.beadsChildClosed ? {
|
|
9629
11450
|
prdRef,
|
|
9630
|
-
|
|
11451
|
+
childRef: resumeIssue.beadsChildRef,
|
|
11452
|
+
childId: resumeIssue.beadsChildId,
|
|
11453
|
+
githubIssueNumber: resumeIssueNumber
|
|
11454
|
+
} : void 0;
|
|
11455
|
+
let fetchedIssue;
|
|
11456
|
+
try {
|
|
11457
|
+
fetchedIssue = await issueProvider.fetchIssue(resumeIssueNumber);
|
|
11458
|
+
} catch (error) {
|
|
11459
|
+
return buildLaunchBlockedOutcome(
|
|
11460
|
+
options.repoRoot,
|
|
9631
11461
|
prdRef,
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
"
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
options,
|
|
11462
|
+
buildPrdRunRepairGuidance({
|
|
11463
|
+
blockedGate: "queue",
|
|
11464
|
+
blockedStage: "queue-drain",
|
|
11465
|
+
failureCode: "resume-fetch-failed",
|
|
11466
|
+
humanReadableReason: `Failed to fetch resume issue #${resumeIssueNumber} from Beads metadata.`,
|
|
11467
|
+
repairability: "automatic-retry-safe",
|
|
11468
|
+
nextAction: "rerun-prd-run-launch",
|
|
11469
|
+
nextRecommendedCommand: "pourkit prd-run launch",
|
|
11470
|
+
diagnostics: [error instanceof Error ? error.message : String(error)]
|
|
11471
|
+
}),
|
|
11472
|
+
plan,
|
|
11473
|
+
startOutcome,
|
|
11474
|
+
targetName,
|
|
11475
|
+
startReceipt
|
|
11476
|
+
);
|
|
11477
|
+
}
|
|
11478
|
+
try {
|
|
11479
|
+
const runResult = await runClaimedPrdIssueCommand({
|
|
11480
|
+
targetName,
|
|
11481
|
+
config: options.config,
|
|
11482
|
+
issueProvider,
|
|
11483
|
+
prProvider,
|
|
11484
|
+
executionProvider,
|
|
11485
|
+
force: false,
|
|
11486
|
+
logger,
|
|
11487
|
+
repoRoot: options.repoRoot,
|
|
11488
|
+
issue: fetchedIssue,
|
|
11489
|
+
queueRunContext: {
|
|
9661
11490
|
prdRef,
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
);
|
|
9665
|
-
if (!projectionOk2 && originalGuidance) {
|
|
9666
|
-
const updatedRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
9667
|
-
return {
|
|
9668
|
-
kind: "blocked",
|
|
9669
|
-
prdRef,
|
|
9670
|
-
guidance: updatedRecord.repairGuidance,
|
|
9671
|
-
start: startOutcome,
|
|
9672
|
-
resume: plan
|
|
9673
|
-
};
|
|
11491
|
+
prdBranch: startReceipt.prdBranch,
|
|
11492
|
+
...resumeBeadsContext ? { beadsChild: resumeBeadsContext } : {}
|
|
9674
11493
|
}
|
|
9675
|
-
|
|
9676
|
-
|
|
11494
|
+
});
|
|
11495
|
+
processedResults.push({
|
|
11496
|
+
beadsChildId: resumeIssue?.beadsChildId ?? "resumed",
|
|
11497
|
+
issueNumber: runResult.selected.number,
|
|
11498
|
+
publicationStatus: runResult.runResult.publicationStatus
|
|
11499
|
+
});
|
|
9677
11500
|
startReceipt.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9678
|
-
startReceipt.queueProcessedCount =
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
11501
|
+
startReceipt.queueProcessedCount = processedResults.length;
|
|
11502
|
+
} catch (error) {
|
|
11503
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11504
|
+
return buildLaunchBlockedOutcome(
|
|
11505
|
+
options.repoRoot,
|
|
11506
|
+
prdRef,
|
|
11507
|
+
buildPrdRunRepairGuidance({
|
|
11508
|
+
blockedGate: "queue",
|
|
11509
|
+
blockedStage: "queue-drain",
|
|
11510
|
+
failureCode: "resume-issue-failed",
|
|
11511
|
+
humanReadableReason: msg,
|
|
11512
|
+
repairability: "automatic-retry-safe",
|
|
11513
|
+
nextAction: "rerun-prd-run-launch",
|
|
11514
|
+
nextRecommendedCommand: "pourkit prd-run launch",
|
|
11515
|
+
diagnostics: [msg],
|
|
11516
|
+
issue: resumeIssue ? {
|
|
11517
|
+
number: fetchedIssue.number,
|
|
11518
|
+
title: fetchedIssue.title,
|
|
11519
|
+
beadsChildId: resumeIssue.beadsChildId,
|
|
11520
|
+
beadsChildRef: resumeIssue.beadsChildRef,
|
|
11521
|
+
beadsChildClosed: resumeIssue.beadsChildClosed || msg.startsWith("Projection failure after Beads close")
|
|
11522
|
+
} : void 0
|
|
11523
|
+
}),
|
|
11524
|
+
plan,
|
|
11525
|
+
startOutcome,
|
|
11526
|
+
targetName,
|
|
11527
|
+
startReceipt
|
|
11528
|
+
);
|
|
11529
|
+
}
|
|
11530
|
+
}
|
|
11531
|
+
let beadsClaimAttempted = false;
|
|
11532
|
+
while (true) {
|
|
11533
|
+
const claimResult = await claimReadyPrdChild({
|
|
11534
|
+
repoRoot: options.repoRoot,
|
|
11535
|
+
prdRef,
|
|
11536
|
+
logger
|
|
11537
|
+
});
|
|
11538
|
+
if (!claimResult.ok) {
|
|
11539
|
+
if (claimResult.reason === "no-ready-child") {
|
|
11540
|
+
logCoordinatorStep(
|
|
11541
|
+
logger,
|
|
11542
|
+
"info",
|
|
11543
|
+
`Beads-backed PRD queue drain for ${prdRef} found no additional ready child work.`
|
|
9692
11544
|
);
|
|
9693
|
-
|
|
9694
|
-
return buildLaunchBlockedOutcome(
|
|
9695
|
-
options.repoRoot,
|
|
9696
|
-
prdRef,
|
|
9697
|
-
terminalEvidence.guidance,
|
|
9698
|
-
plan,
|
|
9699
|
-
startOutcome,
|
|
9700
|
-
targetName,
|
|
9701
|
-
startReceipt
|
|
9702
|
-
);
|
|
9703
|
-
}
|
|
11545
|
+
break;
|
|
9704
11546
|
}
|
|
9705
|
-
|
|
11547
|
+
return buildLaunchBlockedOutcome(
|
|
9706
11548
|
options.repoRoot,
|
|
9707
11549
|
prdRef,
|
|
9708
|
-
|
|
11550
|
+
buildPrdRunRepairGuidance({
|
|
11551
|
+
blockedGate: "queue",
|
|
11552
|
+
blockedStage: "queue-drain",
|
|
11553
|
+
failureCode: "beads-claim-failed",
|
|
11554
|
+
humanReadableReason: `Beads claim failed for PRD ${prdRef} epic ${claimResult.epicId}.`,
|
|
11555
|
+
repairability: "operator-action",
|
|
11556
|
+
nextAction: "inspect-and-retry",
|
|
11557
|
+
nextRecommendedCommand: `pourkit prd-run status ${prdRef}`,
|
|
11558
|
+
diagnostics: [`Beads claim error for epic ${claimResult.epicId}.`]
|
|
11559
|
+
}),
|
|
11560
|
+
plan,
|
|
11561
|
+
startOutcome,
|
|
9709
11562
|
targetName,
|
|
9710
|
-
startReceipt
|
|
9711
|
-
prdBranch
|
|
11563
|
+
startReceipt
|
|
9712
11564
|
);
|
|
9713
|
-
|
|
9714
|
-
|
|
11565
|
+
}
|
|
11566
|
+
beadsClaimAttempted = true;
|
|
11567
|
+
logCoordinatorStep(
|
|
11568
|
+
logger,
|
|
11569
|
+
"info",
|
|
11570
|
+
`Claimed Beads child ${claimResult.child.id} for ${prdRef}; mapping to GitHub Issue context.`
|
|
11571
|
+
);
|
|
11572
|
+
const contextResult = await buildIssueContextFromBeadsChild({
|
|
11573
|
+
child: claimResult.child,
|
|
11574
|
+
issueProvider,
|
|
11575
|
+
expectedPrdRef: prdRef
|
|
11576
|
+
});
|
|
11577
|
+
if (!contextResult.ok) {
|
|
11578
|
+
return buildLaunchBlockedOutcome(
|
|
11579
|
+
options.repoRoot,
|
|
9715
11580
|
prdRef,
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
11581
|
+
buildPrdRunRepairGuidance({
|
|
11582
|
+
blockedGate: "queue",
|
|
11583
|
+
blockedStage: "queue-drain",
|
|
11584
|
+
failureCode: "beads-context-mapping-failed",
|
|
11585
|
+
humanReadableReason: `Failed to map Beads child ${claimResult.child.id} to Issue context: ${contextResult.reason}`,
|
|
11586
|
+
repairability: "operator-action",
|
|
11587
|
+
nextAction: "inspect-and-retry",
|
|
11588
|
+
nextRecommendedCommand: `pourkit prd-run status ${prdRef}`,
|
|
11589
|
+
diagnostics: [contextResult.reason]
|
|
11590
|
+
}),
|
|
11591
|
+
plan,
|
|
11592
|
+
startOutcome,
|
|
11593
|
+
targetName,
|
|
11594
|
+
startReceipt
|
|
11595
|
+
);
|
|
9724
11596
|
}
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
11597
|
+
try {
|
|
11598
|
+
logCoordinatorStep(
|
|
11599
|
+
logger,
|
|
11600
|
+
"info",
|
|
11601
|
+
`Running GitHub Issue #${contextResult.issue.number} from Beads child ${contextResult.beads.childId}.`
|
|
11602
|
+
);
|
|
11603
|
+
const runResult = await runClaimedPrdIssueCommand({
|
|
11604
|
+
targetName,
|
|
11605
|
+
config: options.config,
|
|
11606
|
+
issueProvider,
|
|
11607
|
+
prProvider,
|
|
11608
|
+
executionProvider,
|
|
11609
|
+
force: false,
|
|
11610
|
+
logger,
|
|
11611
|
+
repoRoot: options.repoRoot,
|
|
11612
|
+
issue: contextResult.issue,
|
|
11613
|
+
queueRunContext: {
|
|
11614
|
+
prdRef,
|
|
11615
|
+
prdBranch: startReceipt.prdBranch,
|
|
11616
|
+
beadsChild: contextResult.beads
|
|
11617
|
+
}
|
|
11618
|
+
});
|
|
11619
|
+
processedResults.push({
|
|
11620
|
+
beadsChildId: claimResult.child.id,
|
|
11621
|
+
issueNumber: runResult.selected.number,
|
|
11622
|
+
publicationStatus: runResult.runResult.publicationStatus
|
|
11623
|
+
});
|
|
11624
|
+
} catch (error) {
|
|
11625
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11626
|
+
const beadsChildClosed = msg.startsWith(
|
|
11627
|
+
"Projection failure after Beads close"
|
|
11628
|
+
);
|
|
11629
|
+
return buildLaunchBlockedOutcome(
|
|
9728
11630
|
options.repoRoot,
|
|
9729
11631
|
prdRef,
|
|
9730
11632
|
buildPrdRunRepairGuidance({
|
|
9731
11633
|
blockedGate: "queue",
|
|
9732
11634
|
blockedStage: "queue-drain",
|
|
9733
|
-
failureCode: "
|
|
9734
|
-
humanReadableReason:
|
|
11635
|
+
failureCode: "beads-issue-run-failed",
|
|
11636
|
+
humanReadableReason: msg,
|
|
9735
11637
|
repairability: "automatic-retry-safe",
|
|
9736
11638
|
nextAction: "rerun-prd-run-launch",
|
|
9737
11639
|
nextRecommendedCommand: "pourkit prd-run launch",
|
|
9738
|
-
diagnostics
|
|
11640
|
+
diagnostics: [msg],
|
|
11641
|
+
issue: {
|
|
11642
|
+
number: contextResult.issue.number,
|
|
11643
|
+
title: contextResult.issue.title,
|
|
11644
|
+
beadsChildId: contextResult.beads.childId,
|
|
11645
|
+
beadsChildRef: contextResult.beads.childRef,
|
|
11646
|
+
beadsChildClosed
|
|
11647
|
+
}
|
|
9739
11648
|
}),
|
|
9740
11649
|
plan,
|
|
9741
11650
|
startOutcome,
|
|
9742
11651
|
targetName,
|
|
9743
11652
|
startReceipt
|
|
9744
11653
|
);
|
|
9745
|
-
const projectionOk2 = await attemptRecordedIssueLabelProjection(
|
|
9746
|
-
options,
|
|
9747
|
-
prdRef,
|
|
9748
|
-
resumeIssueNumber,
|
|
9749
|
-
originalGuidance
|
|
9750
|
-
);
|
|
9751
|
-
if (!projectionOk2 && originalGuidance) {
|
|
9752
|
-
const updatedRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
9753
|
-
return {
|
|
9754
|
-
kind: "blocked",
|
|
9755
|
-
prdRef,
|
|
9756
|
-
guidance: updatedRecord.repairGuidance,
|
|
9757
|
-
start: startOutcome,
|
|
9758
|
-
resume: plan
|
|
9759
|
-
};
|
|
9760
|
-
}
|
|
9761
|
-
return result2;
|
|
9762
11654
|
}
|
|
9763
|
-
|
|
9764
|
-
|
|
11655
|
+
}
|
|
11656
|
+
if (processedResults.length === 0 && !beadsClaimAttempted) {
|
|
11657
|
+
const diagnostics = [
|
|
11658
|
+
"Beads-backed PRD drain found no ready children. Zero child work is not terminal completion evidence."
|
|
11659
|
+
];
|
|
9765
11660
|
const result = buildLaunchBlockedOutcome(
|
|
9766
11661
|
options.repoRoot,
|
|
9767
11662
|
prdRef,
|
|
9768
11663
|
buildPrdRunRepairGuidance({
|
|
9769
11664
|
blockedGate: "queue",
|
|
9770
11665
|
blockedStage: "queue-drain",
|
|
9771
|
-
failureCode: "
|
|
9772
|
-
humanReadableReason:
|
|
9773
|
-
repairability: "
|
|
9774
|
-
nextAction: "
|
|
11666
|
+
failureCode: "zero-processed",
|
|
11667
|
+
humanReadableReason: diagnostics[0],
|
|
11668
|
+
repairability: "automatic-retry-safe",
|
|
11669
|
+
nextAction: "rerun-prd-run-launch",
|
|
9775
11670
|
nextRecommendedCommand: "pourkit prd-run launch",
|
|
9776
|
-
diagnostics
|
|
11671
|
+
diagnostics
|
|
9777
11672
|
}),
|
|
9778
11673
|
plan,
|
|
9779
11674
|
startOutcome,
|
|
@@ -9797,45 +11692,54 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9797
11692
|
};
|
|
9798
11693
|
}
|
|
9799
11694
|
return result;
|
|
9800
|
-
}
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
blockedStage: "queue-drain",
|
|
9809
|
-
failureCode: "queue-exception",
|
|
9810
|
-
humanReadableReason: msg,
|
|
9811
|
-
repairability: "automatic-retry-safe",
|
|
9812
|
-
nextAction: "rerun-prd-run-launch",
|
|
9813
|
-
nextRecommendedCommand: "pourkit prd-run launch",
|
|
9814
|
-
diagnostics
|
|
9815
|
-
}),
|
|
9816
|
-
plan,
|
|
9817
|
-
startOutcome,
|
|
9818
|
-
targetName,
|
|
9819
|
-
startReceipt
|
|
9820
|
-
);
|
|
9821
|
-
const projectionOk = await attemptRecordedIssueLabelProjection(
|
|
9822
|
-
options,
|
|
11695
|
+
}
|
|
11696
|
+
startReceipt.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11697
|
+
startReceipt.queueProcessedCount = processedResults.length;
|
|
11698
|
+
const prdBranch = startReceipt.prdBranch ?? prdRef;
|
|
11699
|
+
writeDrainedRecord(options.repoRoot, prdRef, startReceipt, targetName);
|
|
11700
|
+
const drainRecord = readPrdRun(options.repoRoot, prdRef).record;
|
|
11701
|
+
if (drainRecord) {
|
|
11702
|
+
const drainContext = {
|
|
9823
11703
|
prdRef,
|
|
9824
|
-
|
|
9825
|
-
|
|
11704
|
+
baseBranch: startReceipt.startBaseBranch,
|
|
11705
|
+
prdBranch,
|
|
11706
|
+
allChildIssuesFinalizedIntoPrdBranch: processedResults.length > 0 && processedResults.every((r) => r.publicationStatus === "merged")
|
|
11707
|
+
};
|
|
11708
|
+
const terminalEvidence = validatePrdRunTerminalEvidence(
|
|
11709
|
+
drainRecord,
|
|
11710
|
+
drainContext
|
|
9826
11711
|
);
|
|
9827
|
-
if (!
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
kind: "blocked",
|
|
11712
|
+
if (!terminalEvidence.ok) {
|
|
11713
|
+
return buildLaunchBlockedOutcome(
|
|
11714
|
+
options.repoRoot,
|
|
9831
11715
|
prdRef,
|
|
9832
|
-
guidance
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
11716
|
+
terminalEvidence.guidance,
|
|
11717
|
+
plan,
|
|
11718
|
+
startOutcome,
|
|
11719
|
+
targetName,
|
|
11720
|
+
startReceipt
|
|
11721
|
+
);
|
|
9836
11722
|
}
|
|
9837
|
-
return result;
|
|
9838
11723
|
}
|
|
11724
|
+
writeTerminalRecord(
|
|
11725
|
+
options.repoRoot,
|
|
11726
|
+
prdRef,
|
|
11727
|
+
"completed_prd_branch",
|
|
11728
|
+
targetName,
|
|
11729
|
+
startReceipt,
|
|
11730
|
+
prdBranch
|
|
11731
|
+
);
|
|
11732
|
+
return {
|
|
11733
|
+
kind: "completed",
|
|
11734
|
+
prdRef,
|
|
11735
|
+
status: "completed_prd_branch",
|
|
11736
|
+
prdBranch,
|
|
11737
|
+
diagnostics: [
|
|
11738
|
+
`PRD Run completed on branch ${prdBranch}. Next: checkout ${prdBranch} for human review, or use release promotion workflow when ready.`
|
|
11739
|
+
],
|
|
11740
|
+
start: startOutcome,
|
|
11741
|
+
resume: plan
|
|
11742
|
+
};
|
|
9839
11743
|
}
|
|
9840
11744
|
|
|
9841
11745
|
// prd-run/final-review-validation.ts
|
|
@@ -10365,11 +12269,11 @@ async function runPrMergeCommand(args, logger, prProvider, config) {
|
|
|
10365
12269
|
import { existsSync as existsSync16, statSync } from "fs";
|
|
10366
12270
|
import {
|
|
10367
12271
|
copyFile,
|
|
10368
|
-
mkdir as
|
|
10369
|
-
readFile as
|
|
10370
|
-
readdir,
|
|
12272
|
+
mkdir as mkdir5,
|
|
12273
|
+
readFile as readFile5,
|
|
12274
|
+
readdir as readdir2,
|
|
10371
12275
|
rename,
|
|
10372
|
-
writeFile
|
|
12276
|
+
writeFile as writeFile2
|
|
10373
12277
|
} from "fs/promises";
|
|
10374
12278
|
import { createHash, randomUUID } from "crypto";
|
|
10375
12279
|
import path5 from "path";
|
|
@@ -10674,6 +12578,50 @@ icm --db .pourkit/icm/memories.db topics # list a
|
|
|
10674
12578
|
<!-- icm:end -->`;
|
|
10675
12579
|
}
|
|
10676
12580
|
|
|
12581
|
+
// shared/managed-block.ts
|
|
12582
|
+
var MANAGED_BLOCK_BEGIN = "<!-- BEGIN POURKIT MANAGED BLOCK -->";
|
|
12583
|
+
var MANAGED_BLOCK_END = "<!-- END POURKIT MANAGED BLOCK -->";
|
|
12584
|
+
var MANAGED_BLOCK_PATTERN = new RegExp(
|
|
12585
|
+
`${escapeRegExp3(MANAGED_BLOCK_BEGIN)}\\r?\\n[\\s\\S]*?${escapeRegExp3(
|
|
12586
|
+
MANAGED_BLOCK_END
|
|
12587
|
+
)}[ \\t]*(?:\\r?\\n)?`,
|
|
12588
|
+
"g"
|
|
12589
|
+
);
|
|
12590
|
+
function escapeRegExp3(value) {
|
|
12591
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12592
|
+
}
|
|
12593
|
+
function buildManagedBlock(content) {
|
|
12594
|
+
const body = content.endsWith("\n") ? content : `${content}
|
|
12595
|
+
`;
|
|
12596
|
+
return `${MANAGED_BLOCK_BEGIN}
|
|
12597
|
+
${body}${MANAGED_BLOCK_END}
|
|
12598
|
+
`;
|
|
12599
|
+
}
|
|
12600
|
+
function upsertManagedBlock(existing, content) {
|
|
12601
|
+
const blockContent = buildManagedBlock(content);
|
|
12602
|
+
const matches = [...existing.matchAll(MANAGED_BLOCK_PATTERN)];
|
|
12603
|
+
if (matches.length === 0) {
|
|
12604
|
+
if (existing.length === 0) {
|
|
12605
|
+
return blockContent;
|
|
12606
|
+
}
|
|
12607
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
12608
|
+
return `${existing}${separator}${blockContent}`;
|
|
12609
|
+
}
|
|
12610
|
+
let next = "";
|
|
12611
|
+
let cursor = 0;
|
|
12612
|
+
for (const [index, match] of matches.entries()) {
|
|
12613
|
+
const start = match.index ?? 0;
|
|
12614
|
+
const end = start + match[0].length;
|
|
12615
|
+
next += existing.slice(cursor, start);
|
|
12616
|
+
if (index === 0) {
|
|
12617
|
+
next += blockContent;
|
|
12618
|
+
}
|
|
12619
|
+
cursor = end;
|
|
12620
|
+
}
|
|
12621
|
+
next += existing.slice(cursor);
|
|
12622
|
+
return next;
|
|
12623
|
+
}
|
|
12624
|
+
|
|
10677
12625
|
// commands/init.ts
|
|
10678
12626
|
var execFileAsync2 = promisify2(execFile2);
|
|
10679
12627
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
@@ -11020,7 +12968,7 @@ function generateGitignoreBlock() {
|
|
|
11020
12968
|
}
|
|
11021
12969
|
async function walkDir(dir) {
|
|
11022
12970
|
const files = [];
|
|
11023
|
-
const entries = await
|
|
12971
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
11024
12972
|
for (const entry of entries) {
|
|
11025
12973
|
const full = path5.join(dir, entry.name);
|
|
11026
12974
|
if (entry.isDirectory()) {
|
|
@@ -11032,7 +12980,7 @@ async function walkDir(dir) {
|
|
|
11032
12980
|
return files;
|
|
11033
12981
|
}
|
|
11034
12982
|
async function computeFileChecksum(filePath) {
|
|
11035
|
-
const content = await
|
|
12983
|
+
const content = await readFile5(filePath);
|
|
11036
12984
|
return createHash("sha256").update(content).digest("hex");
|
|
11037
12985
|
}
|
|
11038
12986
|
function lockfileExists(root, name) {
|
|
@@ -11121,7 +13069,7 @@ async function discoverOpenCodeAgentFiles(sourceRoot) {
|
|
|
11121
13069
|
(candidate) => existsSync16(candidate) && statSync(candidate).isDirectory()
|
|
11122
13070
|
);
|
|
11123
13071
|
if (!agentsDir) return [];
|
|
11124
|
-
const entries = await
|
|
13072
|
+
const entries = await readdir2(agentsDir, { withFileTypes: true });
|
|
11125
13073
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => path5.join(agentsDir, entry.name));
|
|
11126
13074
|
}
|
|
11127
13075
|
async function discoverRootDomainDocs(root) {
|
|
@@ -11134,7 +13082,7 @@ async function discoverRootDomainDocs(root) {
|
|
|
11134
13082
|
}
|
|
11135
13083
|
const adrDir = path5.join(root, "docs", "adr");
|
|
11136
13084
|
if (existsSync16(adrDir)) {
|
|
11137
|
-
const entries = await
|
|
13085
|
+
const entries = await readdir2(adrDir, { withFileTypes: true });
|
|
11138
13086
|
for (const entry of entries) {
|
|
11139
13087
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
11140
13088
|
docs.push(path5.join(adrDir, entry.name));
|
|
@@ -11449,7 +13397,7 @@ async function planInit(options) {
|
|
|
11449
13397
|
"skills"
|
|
11450
13398
|
);
|
|
11451
13399
|
const projectionSkillsDir = path5.join(targetRoot, ".agents", "skills");
|
|
11452
|
-
const entries = await
|
|
13400
|
+
const entries = await readdir2(s, { withFileTypes: true });
|
|
11453
13401
|
const skillNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
11454
13402
|
const addonManagedDir = path5.join(
|
|
11455
13403
|
targetRoot,
|
|
@@ -11515,7 +13463,7 @@ async function planInit(options) {
|
|
|
11515
13463
|
});
|
|
11516
13464
|
continue;
|
|
11517
13465
|
}
|
|
11518
|
-
const sourceContent = await
|
|
13466
|
+
const sourceContent = await readFile5(file, "utf-8");
|
|
11519
13467
|
const managedSourcePath = isReleaseAddon ? `.pourkit/managed/addons/release/skills/${skillName}/${relPath}` : `.pourkit/managed/skills/${skillName}/${relPath}`;
|
|
11520
13468
|
operations.push({
|
|
11521
13469
|
kind: "create",
|
|
@@ -11594,7 +13542,7 @@ async function planInit(options) {
|
|
|
11594
13542
|
}
|
|
11595
13543
|
let hasPackageJson = true;
|
|
11596
13544
|
try {
|
|
11597
|
-
await
|
|
13545
|
+
await readFile5(path5.join(targetRoot, "package.json"), "utf-8");
|
|
11598
13546
|
} catch {
|
|
11599
13547
|
hasPackageJson = false;
|
|
11600
13548
|
}
|
|
@@ -11930,9 +13878,7 @@ Do not edit this file.
|
|
|
11930
13878
|
reason: "Init AGENTS.md with Pourkit managed block",
|
|
11931
13879
|
requiresConfirmation: false,
|
|
11932
13880
|
destructive: false,
|
|
11933
|
-
content:
|
|
11934
|
-
${managedAgentContent}${MANAGED_BLOCK_END}
|
|
11935
|
-
`
|
|
13881
|
+
content: buildManagedBlock(managedAgentContent)
|
|
11936
13882
|
});
|
|
11937
13883
|
}
|
|
11938
13884
|
const hasExistingClaude = operations.some(
|
|
@@ -11946,9 +13892,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
11946
13892
|
reason: "Init CLAUDE.md with Pourkit managed block",
|
|
11947
13893
|
requiresConfirmation: false,
|
|
11948
13894
|
destructive: false,
|
|
11949
|
-
content:
|
|
11950
|
-
${managedAgentContent}${MANAGED_BLOCK_END}
|
|
11951
|
-
`
|
|
13895
|
+
content: buildManagedBlock(managedAgentContent)
|
|
11952
13896
|
});
|
|
11953
13897
|
}
|
|
11954
13898
|
const gitignoreTarget = path5.join(targetRoot, ".gitignore");
|
|
@@ -11961,9 +13905,7 @@ ${managedAgentContent}${MANAGED_BLOCK_END}
|
|
|
11961
13905
|
reason: "Init .gitignore with Pourkit entries",
|
|
11962
13906
|
requiresConfirmation: false,
|
|
11963
13907
|
destructive: false,
|
|
11964
|
-
content:
|
|
11965
|
-
${gitignoreContent}${MANAGED_BLOCK_END}
|
|
11966
|
-
`
|
|
13908
|
+
content: buildManagedBlock(gitignoreContent)
|
|
11967
13909
|
});
|
|
11968
13910
|
} else {
|
|
11969
13911
|
operations.push({
|
|
@@ -12388,32 +14330,18 @@ async function promptForInitChoices(plan, current) {
|
|
|
12388
14330
|
}
|
|
12389
14331
|
async function writeFileAtomic(filePath, content) {
|
|
12390
14332
|
const tmpPath = `${filePath}.tmp.${randomUUID()}`;
|
|
12391
|
-
await
|
|
14333
|
+
await writeFile2(tmpPath, content, "utf-8");
|
|
12392
14334
|
await rename(tmpPath, filePath);
|
|
12393
14335
|
}
|
|
12394
|
-
var MANAGED_BLOCK_BEGIN = "<!-- BEGIN POURKIT MANAGED BLOCK -->";
|
|
12395
|
-
var MANAGED_BLOCK_END = "<!-- END POURKIT MANAGED BLOCK -->";
|
|
12396
14336
|
async function updateManagedBlock(filePath, content) {
|
|
12397
|
-
const blockContent = `${MANAGED_BLOCK_BEGIN}
|
|
12398
|
-
${content}${MANAGED_BLOCK_END}
|
|
12399
|
-
`;
|
|
12400
14337
|
if (!existsSync16(filePath)) {
|
|
12401
14338
|
const dir = path5.dirname(filePath);
|
|
12402
|
-
await
|
|
12403
|
-
await writeFileAtomic(filePath,
|
|
14339
|
+
await mkdir5(dir, { recursive: true });
|
|
14340
|
+
await writeFileAtomic(filePath, buildManagedBlock(content));
|
|
12404
14341
|
return;
|
|
12405
14342
|
}
|
|
12406
|
-
const existing = await
|
|
12407
|
-
|
|
12408
|
-
const endIdx = existing.indexOf(MANAGED_BLOCK_END);
|
|
12409
|
-
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
|
|
12410
|
-
const before = existing.slice(0, beginIdx);
|
|
12411
|
-
const after = existing.slice(endIdx + MANAGED_BLOCK_END.length);
|
|
12412
|
-
await writeFileAtomic(filePath, before + blockContent + after);
|
|
12413
|
-
} else {
|
|
12414
|
-
const suffix = existing.endsWith("\n") ? "" : "\n";
|
|
12415
|
-
await writeFileAtomic(filePath, existing + suffix + blockContent + "\n");
|
|
12416
|
-
}
|
|
14343
|
+
const existing = await readFile5(filePath, "utf-8");
|
|
14344
|
+
await writeFileAtomic(filePath, upsertManagedBlock(existing, content));
|
|
12417
14345
|
}
|
|
12418
14346
|
async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
12419
14347
|
const manifestDir = path5.join(plan.targetRoot, ".pourkit");
|
|
@@ -12451,7 +14379,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
|
12451
14379
|
workflowPack: buildWorkflowPackMetadata(plan.enabledAddons ?? []),
|
|
12452
14380
|
assets
|
|
12453
14381
|
};
|
|
12454
|
-
await
|
|
14382
|
+
await mkdir5(manifestDir, { recursive: true });
|
|
12455
14383
|
await writeFileAtomic(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
12456
14384
|
return manifest;
|
|
12457
14385
|
}
|
|
@@ -12477,7 +14405,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12477
14405
|
continue;
|
|
12478
14406
|
}
|
|
12479
14407
|
const dir = path5.dirname(op.path);
|
|
12480
|
-
await
|
|
14408
|
+
await mkdir5(dir, { recursive: true });
|
|
12481
14409
|
await writeFileAtomic(op.path, op.content ?? "");
|
|
12482
14410
|
applied++;
|
|
12483
14411
|
break;
|
|
@@ -12496,7 +14424,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12496
14424
|
skipped++;
|
|
12497
14425
|
} else {
|
|
12498
14426
|
const dir = path5.dirname(op.path);
|
|
12499
|
-
await
|
|
14427
|
+
await mkdir5(dir, { recursive: true });
|
|
12500
14428
|
await copyFile(op.sourcePath, op.path);
|
|
12501
14429
|
applied++;
|
|
12502
14430
|
}
|
|
@@ -12521,7 +14449,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12521
14449
|
continue;
|
|
12522
14450
|
}
|
|
12523
14451
|
const dir = path5.dirname(op.path);
|
|
12524
|
-
await
|
|
14452
|
+
await mkdir5(dir, { recursive: true });
|
|
12525
14453
|
await rename(op.sourcePath, op.path);
|
|
12526
14454
|
applied++;
|
|
12527
14455
|
break;
|
|
@@ -12838,13 +14766,13 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
|
|
|
12838
14766
|
// commands/memory-init.ts
|
|
12839
14767
|
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
|
|
12840
14768
|
import { execFile as execFile3 } from "child_process";
|
|
12841
|
-
import { join as
|
|
14769
|
+
import { join as join20 } from "path";
|
|
12842
14770
|
import { promisify as promisify3 } from "util";
|
|
12843
14771
|
var execFileAsync3 = promisify3(execFile3);
|
|
12844
14772
|
async function runMemoryInitCommand(options) {
|
|
12845
14773
|
const cwd = options.cwd ?? process.cwd();
|
|
12846
14774
|
const execCommand = options.execCommand ?? execFileAsync3;
|
|
12847
|
-
const configPath =
|
|
14775
|
+
const configPath = join20(cwd, ".pourkit", "config.json");
|
|
12848
14776
|
if (!existsSync17(configPath)) {
|
|
12849
14777
|
return {
|
|
12850
14778
|
ok: false,
|
|
@@ -12876,8 +14804,8 @@ async function runMemoryInitCommand(options) {
|
|
|
12876
14804
|
const next = { ...raw, memory };
|
|
12877
14805
|
writeFileSync5(configPath, JSON.stringify(next, null, 2) + "\n");
|
|
12878
14806
|
}
|
|
12879
|
-
const memoryDir =
|
|
12880
|
-
const memoryDbPath =
|
|
14807
|
+
const memoryDir = join20(cwd, ".pourkit", "icm");
|
|
14808
|
+
const memoryDbPath = join20(memoryDir, "memories.db");
|
|
12881
14809
|
mkdirSync8(memoryDir, { recursive: true });
|
|
12882
14810
|
let dbInitialized = false;
|
|
12883
14811
|
let warning;
|
|
@@ -12891,7 +14819,7 @@ async function runMemoryInitCommand(options) {
|
|
|
12891
14819
|
} catch {
|
|
12892
14820
|
warning = "ICM is not available or could not initialize .pourkit/icm/memories.db. Install ICM and run `pourkit doctor` to verify repo-scoped memory setup.";
|
|
12893
14821
|
}
|
|
12894
|
-
const gitignorePath =
|
|
14822
|
+
const gitignorePath = join20(cwd, ".gitignore");
|
|
12895
14823
|
updateManagedBlockSync(gitignorePath, generateGitignoreBlock());
|
|
12896
14824
|
const managed = await generateManagedAgentInstructions({
|
|
12897
14825
|
sourceRoot: cwd,
|
|
@@ -12899,14 +14827,14 @@ async function runMemoryInitCommand(options) {
|
|
|
12899
14827
|
});
|
|
12900
14828
|
let updatedAgentFile = false;
|
|
12901
14829
|
for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
|
|
12902
|
-
const agentPath =
|
|
14830
|
+
const agentPath = join20(cwd, fileName);
|
|
12903
14831
|
if (existsSync17(agentPath)) {
|
|
12904
14832
|
updateManagedBlockSync(agentPath, managed);
|
|
12905
14833
|
updatedAgentFile = true;
|
|
12906
14834
|
}
|
|
12907
14835
|
}
|
|
12908
14836
|
if (!updatedAgentFile) {
|
|
12909
|
-
updateManagedBlockSync(
|
|
14837
|
+
updateManagedBlockSync(join20(cwd, "AGENTS.md"), managed);
|
|
12910
14838
|
}
|
|
12911
14839
|
return {
|
|
12912
14840
|
ok: true,
|
|
@@ -12917,24 +14845,12 @@ async function runMemoryInitCommand(options) {
|
|
|
12917
14845
|
};
|
|
12918
14846
|
}
|
|
12919
14847
|
function updateManagedBlockSync(filePath, content) {
|
|
12920
|
-
const blockContent = `${MANAGED_BLOCK_BEGIN}
|
|
12921
|
-
${content}${MANAGED_BLOCK_END}
|
|
12922
|
-
`;
|
|
12923
14848
|
if (!existsSync17(filePath)) {
|
|
12924
|
-
writeFileSync5(filePath,
|
|
14849
|
+
writeFileSync5(filePath, buildManagedBlock(content));
|
|
12925
14850
|
return;
|
|
12926
14851
|
}
|
|
12927
14852
|
const existing = readFileSync17(filePath, "utf8");
|
|
12928
|
-
|
|
12929
|
-
const endIdx = existing.indexOf(MANAGED_BLOCK_END);
|
|
12930
|
-
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
|
|
12931
|
-
const before = existing.slice(0, beginIdx);
|
|
12932
|
-
const after = existing.slice(endIdx + MANAGED_BLOCK_END.length);
|
|
12933
|
-
writeFileSync5(filePath, before + blockContent + after);
|
|
12934
|
-
return;
|
|
12935
|
-
}
|
|
12936
|
-
const suffix = existing.endsWith("\n") ? "" : "\n";
|
|
12937
|
-
writeFileSync5(filePath, `${existing}${suffix}${blockContent}`);
|
|
14853
|
+
writeFileSync5(filePath, upsertManagedBlock(existing, content));
|
|
12938
14854
|
}
|
|
12939
14855
|
|
|
12940
14856
|
// commands/serena.ts
|
|
@@ -13054,17 +14970,17 @@ async function runSerenaStatusCommand(options) {
|
|
|
13054
14970
|
|
|
13055
14971
|
// commands/config-schema.ts
|
|
13056
14972
|
import { readFileSync as readFileSync18, existsSync as existsSync18 } from "fs";
|
|
13057
|
-
import { mkdir as
|
|
13058
|
-
import { resolve as
|
|
14973
|
+
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile3, readdir as readdir3 } from "fs/promises";
|
|
14974
|
+
import { resolve as resolve4, dirname as dirname6, relative as relative4 } from "path";
|
|
13059
14975
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13060
14976
|
import Ajv2 from "ajv";
|
|
13061
14977
|
init_common();
|
|
13062
14978
|
var __filename3 = fileURLToPath3(import.meta.url);
|
|
13063
|
-
var __dirname3 =
|
|
14979
|
+
var __dirname3 = dirname6(__filename3);
|
|
13064
14980
|
function resolvePackagedAssetPath2(fileName) {
|
|
13065
14981
|
const candidates = [
|
|
13066
|
-
|
|
13067
|
-
|
|
14982
|
+
resolve4(__dirname3, "schema", fileName),
|
|
14983
|
+
resolve4(__dirname3, "../schema", fileName)
|
|
13068
14984
|
];
|
|
13069
14985
|
return candidates.find((candidate) => existsSync18(candidate)) ?? candidates[0];
|
|
13070
14986
|
}
|
|
@@ -13091,15 +15007,15 @@ function readPackagedHash() {
|
|
|
13091
15007
|
return readFileSync18(PACKAGED_HASH_PATH, "utf-8");
|
|
13092
15008
|
}
|
|
13093
15009
|
function localSchemaDir(repoRoot2) {
|
|
13094
|
-
return
|
|
15010
|
+
return resolve4(repoRoot2, ".pourkit/schema");
|
|
13095
15011
|
}
|
|
13096
15012
|
var MANAGED_BLOCK_BEGIN2 = "<!-- BEGIN POURKIT MANAGED BLOCK -->\n";
|
|
13097
15013
|
var MANAGED_BLOCK_BEGIN_MARKER = MANAGED_BLOCK_BEGIN2.trim();
|
|
13098
15014
|
var MANAGED_BLOCK_END_MARKER = "<!-- END POURKIT MANAGED BLOCK -->";
|
|
13099
15015
|
var OLD_DOCS_PATH_PATTERN = ".pourkit/docs/agents/";
|
|
13100
15016
|
function resolvePackagedManagedPath(...segments) {
|
|
13101
|
-
const bundledPath =
|
|
13102
|
-
const sourcePath =
|
|
15017
|
+
const bundledPath = resolve4(__dirname3, "managed", ...segments);
|
|
15018
|
+
const sourcePath = resolve4(__dirname3, "..", "managed", ...segments);
|
|
13103
15019
|
const candidates = [bundledPath, sourcePath];
|
|
13104
15020
|
return candidates.find((candidate) => existsSync18(candidate)) ?? bundledPath;
|
|
13105
15021
|
}
|
|
@@ -13117,21 +15033,21 @@ function resolvePackagedReleaseAddonDocPath(docName) {
|
|
|
13117
15033
|
}
|
|
13118
15034
|
async function readPackagedTextFile(filePath) {
|
|
13119
15035
|
try {
|
|
13120
|
-
return await
|
|
15036
|
+
return await readFile6(filePath, "utf-8");
|
|
13121
15037
|
} catch {
|
|
13122
15038
|
return null;
|
|
13123
15039
|
}
|
|
13124
15040
|
}
|
|
13125
15041
|
function isPathContained(parent, child) {
|
|
13126
|
-
const relativePath =
|
|
15042
|
+
const relativePath = relative4(parent, child);
|
|
13127
15043
|
return !relativePath.startsWith("..") && !relativePath.startsWith("../");
|
|
13128
15044
|
}
|
|
13129
15045
|
async function walkDir2(dir) {
|
|
13130
15046
|
const files = [];
|
|
13131
15047
|
try {
|
|
13132
|
-
const entries = await
|
|
15048
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
13133
15049
|
for (const entry of entries) {
|
|
13134
|
-
const full =
|
|
15050
|
+
const full = resolve4(dir, entry.name);
|
|
13135
15051
|
if (entry.isDirectory()) {
|
|
13136
15052
|
files.push(...await walkDir2(full));
|
|
13137
15053
|
} else {
|
|
@@ -13213,7 +15129,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13213
15129
|
const catalog = getWorkflowPackCatalog();
|
|
13214
15130
|
const baselineSkills = getBaselineManagedSkillNames();
|
|
13215
15131
|
for (const docName of catalog.docs) {
|
|
13216
|
-
const localPath =
|
|
15132
|
+
const localPath = resolve4(cwd, `.pourkit/managed/docs/agents/${docName}`);
|
|
13217
15133
|
if (!existsSync18(localPath)) {
|
|
13218
15134
|
failures.push({
|
|
13219
15135
|
severity: "failure",
|
|
@@ -13226,9 +15142,9 @@ async function validateWorkflowPack(cwd) {
|
|
|
13226
15142
|
const packagedContent = await readPackagedTextFile(packagedPath);
|
|
13227
15143
|
if (packagedContent !== null) {
|
|
13228
15144
|
try {
|
|
13229
|
-
const localContent = await
|
|
15145
|
+
const localContent = await readFile6(localPath, "utf-8");
|
|
13230
15146
|
if (localContent !== packagedContent) {
|
|
13231
|
-
const overridePath =
|
|
15147
|
+
const overridePath = resolve4(
|
|
13232
15148
|
cwd,
|
|
13233
15149
|
`.pourkit/overrides/docs/agents/${docName}`
|
|
13234
15150
|
);
|
|
@@ -13254,7 +15170,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13254
15170
|
}
|
|
13255
15171
|
}
|
|
13256
15172
|
for (const skillName of baselineSkills) {
|
|
13257
|
-
const skillDir =
|
|
15173
|
+
const skillDir = resolve4(cwd, `.pourkit/managed/skills/${skillName}`);
|
|
13258
15174
|
if (!existsSync18(skillDir)) {
|
|
13259
15175
|
failures.push({
|
|
13260
15176
|
severity: "failure",
|
|
@@ -13263,7 +15179,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13263
15179
|
message: `Missing managed skill: ${skillName}`
|
|
13264
15180
|
});
|
|
13265
15181
|
} else {
|
|
13266
|
-
const skillSourcePath =
|
|
15182
|
+
const skillSourcePath = resolve4(skillDir, "SKILL.md");
|
|
13267
15183
|
if (existsSync18(skillSourcePath)) {
|
|
13268
15184
|
const packagedSkillPath = resolvePackagedManagedPath(
|
|
13269
15185
|
"skills",
|
|
@@ -13273,9 +15189,9 @@ async function validateWorkflowPack(cwd) {
|
|
|
13273
15189
|
const packagedContent = await readPackagedTextFile(packagedSkillPath);
|
|
13274
15190
|
if (packagedContent !== null) {
|
|
13275
15191
|
try {
|
|
13276
|
-
const localContent = await
|
|
15192
|
+
const localContent = await readFile6(skillSourcePath, "utf-8");
|
|
13277
15193
|
if (localContent !== packagedContent) {
|
|
13278
|
-
const overridePath =
|
|
15194
|
+
const overridePath = resolve4(
|
|
13279
15195
|
cwd,
|
|
13280
15196
|
`.pourkit/overrides/skills/${skillName}/SKILL.md`
|
|
13281
15197
|
);
|
|
@@ -13302,7 +15218,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13302
15218
|
}
|
|
13303
15219
|
}
|
|
13304
15220
|
for (const promptName of catalog.prompts) {
|
|
13305
|
-
const promptPath =
|
|
15221
|
+
const promptPath = resolve4(cwd, `.pourkit/managed/prompts/${promptName}`);
|
|
13306
15222
|
if (!existsSync18(promptPath)) {
|
|
13307
15223
|
failures.push({
|
|
13308
15224
|
severity: "failure",
|
|
@@ -13313,7 +15229,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13313
15229
|
}
|
|
13314
15230
|
}
|
|
13315
15231
|
for (const skillName of baselineSkills) {
|
|
13316
|
-
const projectionDir =
|
|
15232
|
+
const projectionDir = resolve4(cwd, `.agents/skills/${skillName}`);
|
|
13317
15233
|
if (!existsSync18(projectionDir)) {
|
|
13318
15234
|
warnings.push({
|
|
13319
15235
|
severity: "warning",
|
|
@@ -13322,7 +15238,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13322
15238
|
message: `Missing skill projection for ${skillName}. Run 'pourkit sync workflow-pack' to regenerate.`
|
|
13323
15239
|
});
|
|
13324
15240
|
} else {
|
|
13325
|
-
const projectionPath =
|
|
15241
|
+
const projectionPath = resolve4(projectionDir, "SKILL.md");
|
|
13326
15242
|
if (existsSync18(projectionPath)) {
|
|
13327
15243
|
const managedSourcePath = resolvePackagedManagedPath(
|
|
13328
15244
|
"skills",
|
|
@@ -13332,7 +15248,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13332
15248
|
const packagedContent = await readPackagedTextFile(managedSourcePath);
|
|
13333
15249
|
if (packagedContent !== null) {
|
|
13334
15250
|
try {
|
|
13335
|
-
const projectionContent = await
|
|
15251
|
+
const projectionContent = await readFile6(projectionPath, "utf-8");
|
|
13336
15252
|
const managedSourceRelativePath = `.pourkit/managed/skills/${skillName}/SKILL.md`;
|
|
13337
15253
|
if (projectionContent !== buildOpenCodeSkillProjectionContent(
|
|
13338
15254
|
managedSourceRelativePath,
|
|
@@ -13351,10 +15267,10 @@ async function validateWorkflowPack(cwd) {
|
|
|
13351
15267
|
}
|
|
13352
15268
|
}
|
|
13353
15269
|
}
|
|
13354
|
-
const agentsPath =
|
|
15270
|
+
const agentsPath = resolve4(cwd, "AGENTS.md");
|
|
13355
15271
|
if (existsSync18(agentsPath)) {
|
|
13356
15272
|
try {
|
|
13357
|
-
const agentsContent = await
|
|
15273
|
+
const agentsContent = await readFile6(agentsPath, "utf-8");
|
|
13358
15274
|
if (agentsContent.includes(OLD_DOCS_PATH_PATTERN)) {
|
|
13359
15275
|
failures.push({
|
|
13360
15276
|
severity: "failure",
|
|
@@ -13374,11 +15290,11 @@ async function validateWorkflowPack(cwd) {
|
|
|
13374
15290
|
} catch {
|
|
13375
15291
|
}
|
|
13376
15292
|
}
|
|
13377
|
-
const overridesDir =
|
|
15293
|
+
const overridesDir = resolve4(cwd, ".pourkit/overrides");
|
|
13378
15294
|
if (existsSync18(overridesDir)) {
|
|
13379
15295
|
const overrideFiles = await walkDir2(overridesDir);
|
|
13380
15296
|
for (const file of overrideFiles) {
|
|
13381
|
-
const relPath =
|
|
15297
|
+
const relPath = relative4(cwd, file);
|
|
13382
15298
|
if (!warnings.some((w) => w.kind === "overridden" && w.path === relPath)) {
|
|
13383
15299
|
warnings.push({
|
|
13384
15300
|
severity: "warning",
|
|
@@ -13389,15 +15305,15 @@ async function validateWorkflowPack(cwd) {
|
|
|
13389
15305
|
}
|
|
13390
15306
|
}
|
|
13391
15307
|
}
|
|
13392
|
-
const configPath =
|
|
15308
|
+
const configPath = resolve4(cwd, ".pourkit/config.json");
|
|
13393
15309
|
if (existsSync18(configPath)) {
|
|
13394
15310
|
try {
|
|
13395
|
-
const configContent = await
|
|
15311
|
+
const configContent = await readFile6(configPath, "utf-8");
|
|
13396
15312
|
const parsed = JSON.parse(configContent);
|
|
13397
15313
|
const releaseEnabled = parsed.releaseWorkflow?.enabled === true;
|
|
13398
15314
|
if (releaseEnabled) {
|
|
13399
15315
|
for (const skillName of catalog.releaseAddonSkills) {
|
|
13400
|
-
const addonDir =
|
|
15316
|
+
const addonDir = resolve4(
|
|
13401
15317
|
cwd,
|
|
13402
15318
|
`.pourkit/managed/addons/release/skills/${skillName}`
|
|
13403
15319
|
);
|
|
@@ -13411,7 +15327,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13411
15327
|
}
|
|
13412
15328
|
}
|
|
13413
15329
|
for (const docName of catalog.releaseAddonDocs) {
|
|
13414
|
-
const addonDocPath =
|
|
15330
|
+
const addonDocPath = resolve4(
|
|
13415
15331
|
cwd,
|
|
13416
15332
|
`.pourkit/managed/addons/release/docs/agents/${docName}`
|
|
13417
15333
|
);
|
|
@@ -13427,7 +15343,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13427
15343
|
resolvePackagedReleaseAddonDocPath(docName)
|
|
13428
15344
|
);
|
|
13429
15345
|
if (packagedContent !== null) {
|
|
13430
|
-
const localContent = await
|
|
15346
|
+
const localContent = await readFile6(addonDocPath, "utf-8");
|
|
13431
15347
|
if (localContent !== packagedContent) {
|
|
13432
15348
|
failures.push({
|
|
13433
15349
|
severity: "failure",
|
|
@@ -13456,7 +15372,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13456
15372
|
}
|
|
13457
15373
|
} else {
|
|
13458
15374
|
for (const skillName of catalog.releaseAddonSkills) {
|
|
13459
|
-
const addonDir =
|
|
15375
|
+
const addonDir = resolve4(
|
|
13460
15376
|
cwd,
|
|
13461
15377
|
`.pourkit/managed/addons/release/skills/${skillName}`
|
|
13462
15378
|
);
|
|
@@ -13470,7 +15386,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13470
15386
|
}
|
|
13471
15387
|
}
|
|
13472
15388
|
for (const docName of catalog.releaseAddonDocs) {
|
|
13473
|
-
const addonDocPath =
|
|
15389
|
+
const addonDocPath = resolve4(
|
|
13474
15390
|
cwd,
|
|
13475
15391
|
`.pourkit/managed/addons/release/docs/agents/${docName}`
|
|
13476
15392
|
);
|
|
@@ -13487,16 +15403,16 @@ async function validateWorkflowPack(cwd) {
|
|
|
13487
15403
|
} catch {
|
|
13488
15404
|
}
|
|
13489
15405
|
}
|
|
13490
|
-
const manifestPath =
|
|
15406
|
+
const manifestPath = resolve4(cwd, ".pourkit/manifest.json");
|
|
13491
15407
|
if (existsSync18(manifestPath)) {
|
|
13492
15408
|
try {
|
|
13493
|
-
const manifestContent = await
|
|
15409
|
+
const manifestContent = await readFile6(manifestPath, "utf-8");
|
|
13494
15410
|
const manifest = JSON.parse(manifestContent);
|
|
13495
15411
|
const wp = manifest.workflowPack;
|
|
13496
15412
|
if (wp?.projections) {
|
|
13497
15413
|
for (const proj of wp.projections) {
|
|
13498
15414
|
if (proj.path) {
|
|
13499
|
-
const resolved =
|
|
15415
|
+
const resolved = resolve4(cwd, proj.path);
|
|
13500
15416
|
if (!isPathContained(cwd, resolved)) {
|
|
13501
15417
|
failures.push({
|
|
13502
15418
|
severity: "failure",
|
|
@@ -13510,7 +15426,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13510
15426
|
} catch {
|
|
13511
15427
|
}
|
|
13512
15428
|
}
|
|
13513
|
-
const pathEscapeOverrideDir =
|
|
15429
|
+
const pathEscapeOverrideDir = resolve4(cwd, ".pourkit/overrides");
|
|
13514
15430
|
if (existsSync18(pathEscapeOverrideDir)) {
|
|
13515
15431
|
const overrideFiles = await walkDir2(pathEscapeOverrideDir);
|
|
13516
15432
|
for (const file of overrideFiles) {
|
|
@@ -13518,20 +15434,20 @@ async function validateWorkflowPack(cwd) {
|
|
|
13518
15434
|
failures.push({
|
|
13519
15435
|
severity: "failure",
|
|
13520
15436
|
kind: "path_escape",
|
|
13521
|
-
path:
|
|
13522
|
-
message: `Override file path escapes repository: ${
|
|
15437
|
+
path: relative4(cwd, file),
|
|
15438
|
+
message: `Override file path escapes repository: ${relative4(cwd, file)}`
|
|
13523
15439
|
});
|
|
13524
15440
|
}
|
|
13525
15441
|
}
|
|
13526
15442
|
}
|
|
13527
|
-
const managedDocsDir =
|
|
15443
|
+
const managedDocsDir = resolve4(cwd, ".pourkit/managed/docs/agents");
|
|
13528
15444
|
if (existsSync18(managedDocsDir)) {
|
|
13529
15445
|
const docFiles = await walkDir2(managedDocsDir);
|
|
13530
15446
|
for (const file of docFiles) {
|
|
13531
15447
|
if (!file.endsWith(".md")) continue;
|
|
13532
15448
|
try {
|
|
13533
|
-
const content = await
|
|
13534
|
-
const relPath =
|
|
15449
|
+
const content = await readFile6(file, "utf-8");
|
|
15450
|
+
const relPath = relative4(cwd, file);
|
|
13535
15451
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13536
15452
|
for (const v of violations) {
|
|
13537
15453
|
failures.push({
|
|
@@ -13545,16 +15461,16 @@ async function validateWorkflowPack(cwd) {
|
|
|
13545
15461
|
}
|
|
13546
15462
|
}
|
|
13547
15463
|
}
|
|
13548
|
-
const managedSkillsDir =
|
|
15464
|
+
const managedSkillsDir = resolve4(cwd, ".pourkit/managed/skills");
|
|
13549
15465
|
if (existsSync18(managedSkillsDir)) {
|
|
13550
|
-
const skillDirs = await
|
|
15466
|
+
const skillDirs = await readdir3(managedSkillsDir, { withFileTypes: true });
|
|
13551
15467
|
for (const entry of skillDirs) {
|
|
13552
15468
|
if (!entry.isDirectory()) continue;
|
|
13553
|
-
const skillFile =
|
|
15469
|
+
const skillFile = resolve4(managedSkillsDir, entry.name, "SKILL.md");
|
|
13554
15470
|
if (!existsSync18(skillFile)) continue;
|
|
13555
15471
|
try {
|
|
13556
|
-
const content = await
|
|
13557
|
-
const relPath =
|
|
15472
|
+
const content = await readFile6(skillFile, "utf-8");
|
|
15473
|
+
const relPath = relative4(cwd, skillFile);
|
|
13558
15474
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13559
15475
|
for (const v of violations) {
|
|
13560
15476
|
failures.push({
|
|
@@ -13568,14 +15484,14 @@ async function validateWorkflowPack(cwd) {
|
|
|
13568
15484
|
}
|
|
13569
15485
|
}
|
|
13570
15486
|
}
|
|
13571
|
-
const managedPromptsDir =
|
|
15487
|
+
const managedPromptsDir = resolve4(cwd, ".pourkit/managed/prompts");
|
|
13572
15488
|
if (existsSync18(managedPromptsDir)) {
|
|
13573
15489
|
for (const promptName of catalog.prompts) {
|
|
13574
|
-
const promptFile =
|
|
15490
|
+
const promptFile = resolve4(managedPromptsDir, promptName);
|
|
13575
15491
|
if (!existsSync18(promptFile)) continue;
|
|
13576
15492
|
try {
|
|
13577
|
-
const content = await
|
|
13578
|
-
const relPath =
|
|
15493
|
+
const content = await readFile6(promptFile, "utf-8");
|
|
15494
|
+
const relPath = relative4(cwd, promptFile);
|
|
13579
15495
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13580
15496
|
for (const v of violations) {
|
|
13581
15497
|
failures.push({
|
|
@@ -13590,14 +15506,14 @@ async function validateWorkflowPack(cwd) {
|
|
|
13590
15506
|
}
|
|
13591
15507
|
}
|
|
13592
15508
|
for (const skillName of catalog.releaseAddonSkills) {
|
|
13593
|
-
const addonSkillFile =
|
|
15509
|
+
const addonSkillFile = resolve4(
|
|
13594
15510
|
cwd,
|
|
13595
15511
|
`.pourkit/managed/addons/release/skills/${skillName}/SKILL.md`
|
|
13596
15512
|
);
|
|
13597
15513
|
if (!existsSync18(addonSkillFile)) continue;
|
|
13598
15514
|
try {
|
|
13599
|
-
const content = await
|
|
13600
|
-
const relPath =
|
|
15515
|
+
const content = await readFile6(addonSkillFile, "utf-8");
|
|
15516
|
+
const relPath = relative4(cwd, addonSkillFile);
|
|
13601
15517
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13602
15518
|
for (const v of violations) {
|
|
13603
15519
|
failures.push({
|
|
@@ -13611,14 +15527,14 @@ async function validateWorkflowPack(cwd) {
|
|
|
13611
15527
|
}
|
|
13612
15528
|
}
|
|
13613
15529
|
for (const docName of catalog.releaseAddonDocs) {
|
|
13614
|
-
const addonDocFile =
|
|
15530
|
+
const addonDocFile = resolve4(
|
|
13615
15531
|
cwd,
|
|
13616
15532
|
`.pourkit/managed/addons/release/docs/agents/${docName}`
|
|
13617
15533
|
);
|
|
13618
15534
|
if (!existsSync18(addonDocFile)) continue;
|
|
13619
15535
|
try {
|
|
13620
|
-
const content = await
|
|
13621
|
-
const relPath =
|
|
15536
|
+
const content = await readFile6(addonDocFile, "utf-8");
|
|
15537
|
+
const relPath = relative4(cwd, addonDocFile);
|
|
13622
15538
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13623
15539
|
for (const v of violations) {
|
|
13624
15540
|
failures.push({
|
|
@@ -13648,7 +15564,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
13648
15564
|
ok: icmPath !== null,
|
|
13649
15565
|
detail: icmPath !== null ? icmPath : "icm not found on PATH"
|
|
13650
15566
|
});
|
|
13651
|
-
const gitignorePath =
|
|
15567
|
+
const gitignorePath = resolve4(cwd, ".gitignore");
|
|
13652
15568
|
const gitignoreContent = existsSync18(gitignorePath) ? readFileSync18(gitignorePath, "utf-8") : "";
|
|
13653
15569
|
const hasGitignoreEntry = gitignoreContent.includes(".pourkit/icm/");
|
|
13654
15570
|
checks.push({
|
|
@@ -13656,7 +15572,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
13656
15572
|
ok: hasGitignoreEntry,
|
|
13657
15573
|
detail: hasGitignoreEntry ? ".gitignore covers .pourkit/icm/" : ".gitignore does not cover .pourkit/icm/"
|
|
13658
15574
|
});
|
|
13659
|
-
const memoryDbPath =
|
|
15575
|
+
const memoryDbPath = resolve4(cwd, ".pourkit", "icm", "memories.db");
|
|
13660
15576
|
const hasMemoryDb = existsSync18(memoryDbPath);
|
|
13661
15577
|
checks.push({
|
|
13662
15578
|
name: "icm-db-exists",
|
|
@@ -13670,7 +15586,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
13670
15586
|
detail: shapeValid ? "memory: { enabled: true, provider: icm }" : `invalid memory config: enabled=${memoryConfig.enabled}, provider=${memoryConfig.provider}`
|
|
13671
15587
|
});
|
|
13672
15588
|
const staleAgentFiles = ["AGENTS.md", "CLAUDE.md"].filter((fileName) => {
|
|
13673
|
-
const filePath =
|
|
15589
|
+
const filePath = resolve4(cwd, fileName);
|
|
13674
15590
|
if (!existsSync18(filePath)) return false;
|
|
13675
15591
|
const content = readFileSync18(filePath, "utf-8");
|
|
13676
15592
|
const managedBlock = extractManagedBlockContent(content);
|
|
@@ -13706,8 +15622,8 @@ function extractManagedBlockContent(content) {
|
|
|
13706
15622
|
async function runDoctorCommand(options) {
|
|
13707
15623
|
const repoRootPath = options.cwd ?? process.cwd();
|
|
13708
15624
|
const schemaDir = localSchemaDir(repoRootPath);
|
|
13709
|
-
const localSchemaPath =
|
|
13710
|
-
const localHashPath =
|
|
15625
|
+
const localSchemaPath = resolve4(schemaDir, "pourkit.schema.json");
|
|
15626
|
+
const localHashPath = resolve4(schemaDir, "pourkit.schema.hash");
|
|
13711
15627
|
const localSchemaExists = existsSync18(localSchemaPath);
|
|
13712
15628
|
const localHashExists = existsSync18(localHashPath);
|
|
13713
15629
|
let packagedHash = null;
|
|
@@ -13718,14 +15634,14 @@ async function runDoctorCommand(options) {
|
|
|
13718
15634
|
let localHashContent = null;
|
|
13719
15635
|
if (localHashExists) {
|
|
13720
15636
|
try {
|
|
13721
|
-
localHashContent = await
|
|
15637
|
+
localHashContent = await readFile6(localHashPath, "utf-8");
|
|
13722
15638
|
} catch {
|
|
13723
15639
|
}
|
|
13724
15640
|
}
|
|
13725
15641
|
const schema = !localSchemaExists ? "missing" : !packagedHash || !localHashContent ? "missing" : localHashContent.trim() === packagedHash.trim() ? "fresh" : "stale";
|
|
13726
15642
|
const hash = !localHashExists ? "missing" : !packagedHash ? "missing" : localHashContent && localHashContent.trim() === packagedHash.trim() ? "fresh" : "stale";
|
|
13727
15643
|
const overall = schema === "fresh" && hash === "fresh" ? "fresh" : schema === "missing" || hash === "missing" ? "missing" : "stale";
|
|
13728
|
-
const configPath =
|
|
15644
|
+
const configPath = resolve4(repoRootPath, ".pourkit/config.json");
|
|
13729
15645
|
let configValidation;
|
|
13730
15646
|
let rawMemoryConfig;
|
|
13731
15647
|
if (existsSync18(configPath)) {
|
|
@@ -13763,7 +15679,7 @@ async function runDoctorCommand(options) {
|
|
|
13763
15679
|
"pourkit.config.mjs",
|
|
13764
15680
|
"pourkit.config.js",
|
|
13765
15681
|
"pourkit.json"
|
|
13766
|
-
].filter((p) => existsSync18(
|
|
15682
|
+
].filter((p) => existsSync18(resolve4(repoRootPath, p)));
|
|
13767
15683
|
const workflowPack = await validateWorkflowPack(repoRootPath);
|
|
13768
15684
|
const memory = await validateMemoryHealth(repoRootPath, rawMemoryConfig);
|
|
13769
15685
|
let recommendation = null;
|
|
@@ -13806,31 +15722,31 @@ async function runDoctorCommand(options) {
|
|
|
13806
15722
|
async function runConfigSyncSchemaCommand(options) {
|
|
13807
15723
|
const repoRootPath = options.cwd ?? process.cwd();
|
|
13808
15724
|
const schemaDir = localSchemaDir(repoRootPath);
|
|
13809
|
-
await
|
|
13810
|
-
const packagedSchema = await
|
|
13811
|
-
const packagedHash = await
|
|
13812
|
-
const localSchemaPath =
|
|
13813
|
-
const localHashPath =
|
|
15725
|
+
await mkdir6(schemaDir, { recursive: true });
|
|
15726
|
+
const packagedSchema = await readFile6(PACKAGED_SCHEMA_PATH, "utf-8");
|
|
15727
|
+
const packagedHash = await readFile6(PACKAGED_HASH_PATH, "utf-8");
|
|
15728
|
+
const localSchemaPath = resolve4(schemaDir, "pourkit.schema.json");
|
|
15729
|
+
const localHashPath = resolve4(schemaDir, "pourkit.schema.hash");
|
|
13814
15730
|
let schemaWritten = false;
|
|
13815
15731
|
let hashWritten = false;
|
|
13816
15732
|
if (existsSync18(localSchemaPath)) {
|
|
13817
|
-
const existing = await
|
|
15733
|
+
const existing = await readFile6(localSchemaPath, "utf-8");
|
|
13818
15734
|
if (existing !== packagedSchema) {
|
|
13819
|
-
await
|
|
15735
|
+
await writeFile3(localSchemaPath, packagedSchema, "utf-8");
|
|
13820
15736
|
schemaWritten = true;
|
|
13821
15737
|
}
|
|
13822
15738
|
} else {
|
|
13823
|
-
await
|
|
15739
|
+
await writeFile3(localSchemaPath, packagedSchema, "utf-8");
|
|
13824
15740
|
schemaWritten = true;
|
|
13825
15741
|
}
|
|
13826
15742
|
if (existsSync18(localHashPath)) {
|
|
13827
|
-
const existing = await
|
|
15743
|
+
const existing = await readFile6(localHashPath, "utf-8");
|
|
13828
15744
|
if (existing !== packagedHash) {
|
|
13829
|
-
await
|
|
15745
|
+
await writeFile3(localHashPath, packagedHash, "utf-8");
|
|
13830
15746
|
hashWritten = true;
|
|
13831
15747
|
}
|
|
13832
15748
|
} else {
|
|
13833
|
-
await
|
|
15749
|
+
await writeFile3(localHashPath, packagedHash, "utf-8");
|
|
13834
15750
|
hashWritten = true;
|
|
13835
15751
|
}
|
|
13836
15752
|
return {
|
|
@@ -13841,12 +15757,12 @@ async function runConfigSyncSchemaCommand(options) {
|
|
|
13841
15757
|
}
|
|
13842
15758
|
|
|
13843
15759
|
// commands/workflow-pack-sync.ts
|
|
13844
|
-
import { existsSync as existsSync19, readdirSync as
|
|
13845
|
-
import { mkdir as
|
|
13846
|
-
import { resolve as
|
|
15760
|
+
import { existsSync as existsSync19, readdirSync as readdirSync5 } from "fs";
|
|
15761
|
+
import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile4, readdir as readdir4 } from "fs/promises";
|
|
15762
|
+
import { resolve as resolve5, dirname as dirname7, relative as relative5, normalize as normalize2 } from "path";
|
|
13847
15763
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13848
15764
|
var __filename4 = fileURLToPath4(import.meta.url);
|
|
13849
|
-
var __dirname4 =
|
|
15765
|
+
var __dirname4 = dirname7(__filename4);
|
|
13850
15766
|
var PROJECT_OWNED_PATHS = [".pourkit/CONTEXT.md", ".pourkit/CONTEXT-MAP.md"];
|
|
13851
15767
|
var PROJECT_OWNED_DIRECTORIES = [
|
|
13852
15768
|
".pourkit/docs/adr",
|
|
@@ -13854,11 +15770,9 @@ var PROJECT_OWNED_DIRECTORIES = [
|
|
|
13854
15770
|
".pourkit/handoffs"
|
|
13855
15771
|
];
|
|
13856
15772
|
var OVERRIDES_DIR = ".pourkit/overrides";
|
|
13857
|
-
var MANAGED_BLOCK_BEGIN3 = "<!-- BEGIN POURKIT MANAGED BLOCK -->\n";
|
|
13858
|
-
var MANAGED_BLOCK_END2 = "\n<!-- END POURKIT MANAGED BLOCK -->";
|
|
13859
15773
|
function resolvePackagedManagedPath2(...segments) {
|
|
13860
|
-
const bundledPath =
|
|
13861
|
-
const sourcePath =
|
|
15774
|
+
const bundledPath = resolve5(__dirname4, "managed", ...segments);
|
|
15775
|
+
const sourcePath = resolve5(__dirname4, "..", "managed", ...segments);
|
|
13862
15776
|
const candidates = [bundledPath, sourcePath];
|
|
13863
15777
|
return candidates.find((candidate) => existsSync19(candidate)) ?? bundledPath;
|
|
13864
15778
|
}
|
|
@@ -13882,13 +15796,13 @@ function resolvePackagedManagedPromptPath(promptName) {
|
|
|
13882
15796
|
}
|
|
13883
15797
|
async function readPackagedTextFile2(filePath) {
|
|
13884
15798
|
try {
|
|
13885
|
-
return await
|
|
15799
|
+
return await readFile7(filePath, "utf-8");
|
|
13886
15800
|
} catch {
|
|
13887
15801
|
return null;
|
|
13888
15802
|
}
|
|
13889
15803
|
}
|
|
13890
15804
|
function isPathContained2(parent, child) {
|
|
13891
|
-
const relativePath =
|
|
15805
|
+
const relativePath = relative5(parent, child);
|
|
13892
15806
|
return !relativePath.startsWith("..") && !relativePath.startsWith("../");
|
|
13893
15807
|
}
|
|
13894
15808
|
function isProjectOwnedPath(relativePath) {
|
|
@@ -13939,9 +15853,9 @@ function checkForPathEscape(cwd, resolvedPath) {
|
|
|
13939
15853
|
async function walkDir3(dir) {
|
|
13940
15854
|
const files = [];
|
|
13941
15855
|
try {
|
|
13942
|
-
const entries = await
|
|
15856
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
13943
15857
|
for (const entry of entries) {
|
|
13944
|
-
const full =
|
|
15858
|
+
const full = resolve5(dir, entry.name);
|
|
13945
15859
|
if (entry.isDirectory()) {
|
|
13946
15860
|
files.push(...await walkDir3(full));
|
|
13947
15861
|
} else {
|
|
@@ -13959,28 +15873,28 @@ async function syncFile(cwd, targetRelativePath, sourceContent, errors) {
|
|
|
13959
15873
|
if (sourceContent === null) {
|
|
13960
15874
|
return false;
|
|
13961
15875
|
}
|
|
13962
|
-
const targetPath =
|
|
15876
|
+
const targetPath = resolve5(cwd, targetRelativePath);
|
|
13963
15877
|
const escapeError = checkForPathEscape(cwd, targetPath);
|
|
13964
15878
|
if (escapeError) {
|
|
13965
15879
|
errors.push(escapeError);
|
|
13966
15880
|
return false;
|
|
13967
15881
|
}
|
|
13968
|
-
await
|
|
15882
|
+
await mkdir7(resolve5(targetPath, ".."), { recursive: true });
|
|
13969
15883
|
if (existsSync19(targetPath)) {
|
|
13970
|
-
const existing = await
|
|
15884
|
+
const existing = await readFile7(targetPath, "utf-8");
|
|
13971
15885
|
if (existing === sourceContent) {
|
|
13972
15886
|
return false;
|
|
13973
15887
|
}
|
|
13974
15888
|
}
|
|
13975
|
-
await
|
|
15889
|
+
await writeFile4(targetPath, sourceContent, "utf-8");
|
|
13976
15890
|
return true;
|
|
13977
15891
|
}
|
|
13978
15892
|
function walkDirSync(dir) {
|
|
13979
15893
|
const result = [];
|
|
13980
15894
|
try {
|
|
13981
|
-
const entries =
|
|
15895
|
+
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
13982
15896
|
for (const entry of entries) {
|
|
13983
|
-
const full =
|
|
15897
|
+
const full = resolve5(dir, entry.name);
|
|
13984
15898
|
if (entry.isDirectory()) {
|
|
13985
15899
|
result.push(...walkDirSync(full));
|
|
13986
15900
|
} else {
|
|
@@ -13992,27 +15906,27 @@ function walkDirSync(dir) {
|
|
|
13992
15906
|
return result;
|
|
13993
15907
|
}
|
|
13994
15908
|
async function detectOverrides(cwd) {
|
|
13995
|
-
const overridesDir =
|
|
15909
|
+
const overridesDir = resolve5(cwd, OVERRIDES_DIR);
|
|
13996
15910
|
if (!existsSync19(overridesDir)) {
|
|
13997
15911
|
return [];
|
|
13998
15912
|
}
|
|
13999
15913
|
const files = await walkDir3(overridesDir);
|
|
14000
|
-
return files.map((f) =>
|
|
15914
|
+
return files.map((f) => relative5(cwd, f));
|
|
14001
15915
|
}
|
|
14002
15916
|
async function detectProjectOwnedFiles(cwd) {
|
|
14003
15917
|
const found = [];
|
|
14004
15918
|
for (const ownedPath of PROJECT_OWNED_PATHS) {
|
|
14005
|
-
const fullPath =
|
|
15919
|
+
const fullPath = resolve5(cwd, ownedPath);
|
|
14006
15920
|
if (existsSync19(fullPath)) {
|
|
14007
15921
|
found.push(ownedPath);
|
|
14008
15922
|
}
|
|
14009
15923
|
}
|
|
14010
15924
|
for (const ownedDir of PROJECT_OWNED_DIRECTORIES) {
|
|
14011
|
-
const fullDir =
|
|
15925
|
+
const fullDir = resolve5(cwd, ownedDir);
|
|
14012
15926
|
if (existsSync19(fullDir)) {
|
|
14013
15927
|
const files = await walkDir3(fullDir);
|
|
14014
15928
|
for (const file of files) {
|
|
14015
|
-
found.push(
|
|
15929
|
+
found.push(relative5(cwd, file));
|
|
14016
15930
|
}
|
|
14017
15931
|
}
|
|
14018
15932
|
}
|
|
@@ -14020,8 +15934,8 @@ async function detectProjectOwnedFiles(cwd) {
|
|
|
14020
15934
|
}
|
|
14021
15935
|
async function isReleaseWorkflowEnabled(cwd) {
|
|
14022
15936
|
try {
|
|
14023
|
-
const configPath =
|
|
14024
|
-
const content = await
|
|
15937
|
+
const configPath = resolve5(cwd, ".pourkit", "config.json");
|
|
15938
|
+
const content = await readFile7(configPath, "utf-8");
|
|
14025
15939
|
const parsed = JSON.parse(content);
|
|
14026
15940
|
return parsed.releaseWorkflow?.enabled === true;
|
|
14027
15941
|
} catch {
|
|
@@ -14150,7 +16064,7 @@ async function runWorkflowPackSyncCommand(options) {
|
|
|
14150
16064
|
const schemaResult = await runConfigSyncSchemaCommand({ cwd });
|
|
14151
16065
|
let configMemory;
|
|
14152
16066
|
try {
|
|
14153
|
-
const configPath =
|
|
16067
|
+
const configPath = resolve5(cwd, ".pourkit", "config.json");
|
|
14154
16068
|
const config = await loadConfig(configPath);
|
|
14155
16069
|
if (config.memory?.enabled === true && config.memory?.provider === "icm") {
|
|
14156
16070
|
configMemory = config.memory;
|
|
@@ -14158,34 +16072,19 @@ async function runWorkflowPackSyncCommand(options) {
|
|
|
14158
16072
|
} catch {
|
|
14159
16073
|
}
|
|
14160
16074
|
const managedBlockContent = generateManagedBlockContent(configMemory);
|
|
14161
|
-
const fullManagedBlock =
|
|
16075
|
+
const fullManagedBlock = buildManagedBlock(managedBlockContent);
|
|
14162
16076
|
async function syncManagedAgentFile(fileName, options2) {
|
|
14163
|
-
const filePath =
|
|
16077
|
+
const filePath = resolve5(cwd, fileName);
|
|
14164
16078
|
if (!existsSync19(filePath)) {
|
|
14165
16079
|
if (!options2.createIfMissing) return false;
|
|
14166
|
-
await
|
|
16080
|
+
await writeFile4(filePath, fullManagedBlock, "utf-8");
|
|
14167
16081
|
return true;
|
|
14168
16082
|
}
|
|
14169
|
-
const existing = await
|
|
14170
|
-
const
|
|
14171
|
-
|
|
14172
|
-
|
|
14173
|
-
|
|
14174
|
-
const afterBlock = existing.slice(endIdx + MANAGED_BLOCK_END2.length);
|
|
14175
|
-
const newContent = `${beforeBlock}${fullManagedBlock}${afterBlock}`;
|
|
14176
|
-
if (newContent !== existing) {
|
|
14177
|
-
await writeFile3(filePath, newContent, "utf-8");
|
|
14178
|
-
return true;
|
|
14179
|
-
}
|
|
14180
|
-
} else {
|
|
14181
|
-
const newContent = `${existing}
|
|
14182
|
-
|
|
14183
|
-
${fullManagedBlock}
|
|
14184
|
-
`;
|
|
14185
|
-
if (newContent !== existing) {
|
|
14186
|
-
await writeFile3(filePath, newContent, "utf-8");
|
|
14187
|
-
return true;
|
|
14188
|
-
}
|
|
16083
|
+
const existing = await readFile7(filePath, "utf-8");
|
|
16084
|
+
const newContent = upsertManagedBlock(existing, managedBlockContent);
|
|
16085
|
+
if (newContent !== existing) {
|
|
16086
|
+
await writeFile4(filePath, newContent, "utf-8");
|
|
16087
|
+
return true;
|
|
14189
16088
|
}
|
|
14190
16089
|
return false;
|
|
14191
16090
|
}
|
|
@@ -14197,11 +16096,11 @@ ${fullManagedBlock}
|
|
|
14197
16096
|
}
|
|
14198
16097
|
const enabledAddons = releaseEnabled ? ["release"] : [];
|
|
14199
16098
|
const manifestMeta = buildWorkflowPackMetadata(enabledAddons);
|
|
14200
|
-
const manifestPath =
|
|
16099
|
+
const manifestPath = resolve5(cwd, ".pourkit", "manifest.json");
|
|
14201
16100
|
let manifestWritten = false;
|
|
14202
16101
|
let previousManifestWorkflowPack = void 0;
|
|
14203
16102
|
try {
|
|
14204
|
-
const existingRaw = await
|
|
16103
|
+
const existingRaw = await readFile7(manifestPath, "utf-8").catch(() => null);
|
|
14205
16104
|
if (existingRaw) {
|
|
14206
16105
|
const existing = JSON.parse(existingRaw);
|
|
14207
16106
|
previousManifestWorkflowPack = existing.workflowPack;
|
|
@@ -14210,13 +16109,13 @@ ${fullManagedBlock}
|
|
|
14210
16109
|
}
|
|
14211
16110
|
if (previousManifestWorkflowPack === void 0 || JSON.stringify(previousManifestWorkflowPack) !== JSON.stringify(manifestMeta)) {
|
|
14212
16111
|
try {
|
|
14213
|
-
const existingRaw = await
|
|
16112
|
+
const existingRaw = await readFile7(manifestPath, "utf-8").catch(
|
|
14214
16113
|
() => null
|
|
14215
16114
|
);
|
|
14216
16115
|
let manifest = existingRaw ? JSON.parse(existingRaw) : {};
|
|
14217
16116
|
manifest.workflowPack = manifestMeta;
|
|
14218
|
-
await
|
|
14219
|
-
await
|
|
16117
|
+
await mkdir7(resolve5(manifestPath, ".."), { recursive: true });
|
|
16118
|
+
await writeFile4(
|
|
14220
16119
|
manifestPath,
|
|
14221
16120
|
JSON.stringify(manifest, null, 2) + "\n",
|
|
14222
16121
|
"utf-8"
|
|
@@ -14265,7 +16164,7 @@ async function syncSkill(skillName, catalog, acc, ctx) {
|
|
|
14265
16164
|
return;
|
|
14266
16165
|
}
|
|
14267
16166
|
for (const sourceFile of skillFiles) {
|
|
14268
|
-
const relPath =
|
|
16167
|
+
const relPath = relative5(skillDir, sourceFile);
|
|
14269
16168
|
const targetRelativePath = `.pourkit/managed/skills/${skillName}/${relPath}`;
|
|
14270
16169
|
if (isProjectOwnedPath(targetRelativePath)) {
|
|
14271
16170
|
skippedProjectOwned.push(targetRelativePath);
|
|
@@ -14341,7 +16240,7 @@ async function syncAddonSkill(skillName, acc, ctx) {
|
|
|
14341
16240
|
return;
|
|
14342
16241
|
}
|
|
14343
16242
|
for (const sourceFile of skillFiles) {
|
|
14344
|
-
const relPath =
|
|
16243
|
+
const relPath = relative5(addonSkillDir, sourceFile);
|
|
14345
16244
|
const targetRelativePath = `.pourkit/managed/addons/release/skills/${skillName}/${relPath}`;
|
|
14346
16245
|
if (isProjectOwnedPath(targetRelativePath)) {
|
|
14347
16246
|
skippedProjectOwned.push(targetRelativePath);
|
|
@@ -14422,6 +16321,16 @@ var GitHubIssueProvider = class {
|
|
|
14422
16321
|
});
|
|
14423
16322
|
return { number: data.number, url: data.html_url, title: data.title };
|
|
14424
16323
|
}
|
|
16324
|
+
async updateIssue(issueNumber, options) {
|
|
16325
|
+
const { data } = await this.client.octokit.rest.issues.update({
|
|
16326
|
+
owner: this.client.owner,
|
|
16327
|
+
repo: this.client.repo,
|
|
16328
|
+
issue_number: issueNumber,
|
|
16329
|
+
...options.title !== void 0 ? { title: options.title } : {},
|
|
16330
|
+
...options.body !== void 0 ? { body: options.body } : {}
|
|
16331
|
+
});
|
|
16332
|
+
return { number: data.number, url: data.html_url, title: data.title };
|
|
16333
|
+
}
|
|
14425
16334
|
async fetchIssue(number) {
|
|
14426
16335
|
const { data } = await this.client.octokit.rest.issues.get({
|
|
14427
16336
|
owner: this.client.owner,
|
|
@@ -14944,21 +16853,21 @@ init_common();
|
|
|
14944
16853
|
|
|
14945
16854
|
// execution/sandcastle-execution.ts
|
|
14946
16855
|
import { existsSync as existsSync21, mkdirSync as mkdirSync9, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
14947
|
-
import { join as
|
|
16856
|
+
import { join as join23 } from "path";
|
|
14948
16857
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
14949
16858
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
14950
16859
|
|
|
14951
16860
|
// execution/execution-provider.ts
|
|
14952
16861
|
init_common();
|
|
14953
16862
|
import { mkdtempSync } from "fs";
|
|
14954
|
-
import { writeFile as
|
|
16863
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
14955
16864
|
import { tmpdir } from "os";
|
|
14956
|
-
import { dirname as
|
|
16865
|
+
import { dirname as dirname8, join as join21 } from "path";
|
|
14957
16866
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
14958
16867
|
for (const artifact of artifacts) {
|
|
14959
|
-
const filePath =
|
|
14960
|
-
await ensureDir(
|
|
14961
|
-
await
|
|
16868
|
+
const filePath = join21(worktreePath, artifact.path);
|
|
16869
|
+
await ensureDir(dirname8(filePath));
|
|
16870
|
+
await writeFile5(filePath, artifact.content, "utf-8");
|
|
14962
16871
|
}
|
|
14963
16872
|
}
|
|
14964
16873
|
|
|
@@ -15018,7 +16927,7 @@ async function createSandboxFromExistingWorktree(options) {
|
|
|
15018
16927
|
}
|
|
15019
16928
|
|
|
15020
16929
|
// execution/sandbox-options.ts
|
|
15021
|
-
import { join as
|
|
16930
|
+
import { join as join22 } from "path";
|
|
15022
16931
|
var POURKIT_ICM_CONTAINER_DIR = "/home/agent/.pourkit/icm";
|
|
15023
16932
|
function buildSandboxOptions(repoRoot2, sandbox, memory) {
|
|
15024
16933
|
const mounts = [];
|
|
@@ -15027,12 +16936,12 @@ function buildSandboxOptions(repoRoot2, sandbox, memory) {
|
|
|
15027
16936
|
}
|
|
15028
16937
|
if (memory?.available) {
|
|
15029
16938
|
mounts.push({
|
|
15030
|
-
hostPath:
|
|
16939
|
+
hostPath: join22(repoRoot2, ".pourkit", "icm"),
|
|
15031
16940
|
sandboxPath: POURKIT_ICM_CONTAINER_DIR,
|
|
15032
16941
|
readonly: false
|
|
15033
16942
|
});
|
|
15034
16943
|
mounts.push({
|
|
15035
|
-
hostPath:
|
|
16944
|
+
hostPath: join22(repoRoot2, ".pourkit", "icm", "cache"),
|
|
15036
16945
|
sandboxPath: "/home/agent/.cache/icm",
|
|
15037
16946
|
readonly: false
|
|
15038
16947
|
});
|
|
@@ -15166,13 +17075,13 @@ var SandcastleExecutionSession = class {
|
|
|
15166
17075
|
"Memory enabled but memory context is invalid. Run `pourkit memory init` or install ICM for host planning use."
|
|
15167
17076
|
);
|
|
15168
17077
|
}
|
|
15169
|
-
const hostMemoryDir =
|
|
17078
|
+
const hostMemoryDir = join23(repoRoot2, ".pourkit", "icm");
|
|
15170
17079
|
if (!existsSync21(hostMemoryDir) || !statSync2(hostMemoryDir).isDirectory()) {
|
|
15171
17080
|
throw new MissingHostMemoryDirError(
|
|
15172
17081
|
"Memory enabled but host .pourkit/icm/ directory is missing. Run `pourkit memory init` to create the repo-scoped memory store."
|
|
15173
17082
|
);
|
|
15174
17083
|
}
|
|
15175
|
-
mkdirSync9(
|
|
17084
|
+
mkdirSync9(join23(hostMemoryDir, "cache"), { recursive: true });
|
|
15176
17085
|
}
|
|
15177
17086
|
const sandboxOptions = buildSandboxOptions(
|
|
15178
17087
|
repoRoot2,
|
|
@@ -15331,7 +17240,7 @@ function sanitizeBranch(branchName) {
|
|
|
15331
17240
|
}
|
|
15332
17241
|
function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
15333
17242
|
return paths.filter((relativePath) => {
|
|
15334
|
-
const source =
|
|
17243
|
+
const source = join23(repoRoot2, relativePath);
|
|
15335
17244
|
if (!existsSync21(source)) {
|
|
15336
17245
|
return true;
|
|
15337
17246
|
}
|
|
@@ -15342,7 +17251,7 @@ function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
|
15342
17251
|
} catch {
|
|
15343
17252
|
return true;
|
|
15344
17253
|
}
|
|
15345
|
-
const destination =
|
|
17254
|
+
const destination = join23(worktreePath, relativePath);
|
|
15346
17255
|
return !existsSync21(destination);
|
|
15347
17256
|
});
|
|
15348
17257
|
}
|
|
@@ -15407,17 +17316,17 @@ function isPlainObject(value) {
|
|
|
15407
17316
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15408
17317
|
}
|
|
15409
17318
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
15410
|
-
const promptsDir =
|
|
17319
|
+
const promptsDir = join23(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
15411
17320
|
mkdirSync9(promptsDir, { recursive: true });
|
|
15412
17321
|
const timestamp2 = Date.now();
|
|
15413
17322
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
15414
17323
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
15415
|
-
const filePath =
|
|
17324
|
+
const filePath = join23(promptsDir, filename);
|
|
15416
17325
|
writeFileSync6(filePath, prompt, "utf-8");
|
|
15417
17326
|
}
|
|
15418
17327
|
|
|
15419
17328
|
// cli.ts
|
|
15420
|
-
function
|
|
17329
|
+
function normalizePrdRef2(ref) {
|
|
15421
17330
|
const normalized = ref.trim().toUpperCase();
|
|
15422
17331
|
if (!/^PRD-\d+$/.test(normalized)) {
|
|
15423
17332
|
throw new Error(
|
|
@@ -15482,6 +17391,7 @@ function createCliProgram(version) {
|
|
|
15482
17391
|
const program = new Command();
|
|
15483
17392
|
program.name("pourkit").version(version).exitOverride().description("AI-driven issue-to-PR workflow for GitHub repositories.");
|
|
15484
17393
|
const issueCommand = program.command("issue");
|
|
17394
|
+
const prdPlanWorkspaceCommand = program.command("prd-plan-workspace").description("PRD Plan Workspace commands");
|
|
15485
17395
|
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) => {
|
|
15486
17396
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15487
17397
|
const logPath = path8.join(
|
|
@@ -15513,7 +17423,7 @@ function createCliProgram(version) {
|
|
|
15513
17423
|
});
|
|
15514
17424
|
program.command("validate-artifact").description("Validate an agent handoff artifact").argument(
|
|
15515
17425
|
"<kind>",
|
|
15516
|
-
"artifact kind: reviewer, refactor, finalizer, conflict-resolution, failure-resolution,
|
|
17426
|
+
"artifact kind: reviewer, refactor, finalizer, conflict-resolution, failure-resolution, issue-final-review, prd-plan, prd-child-issue, or prd-plan-workspace"
|
|
15517
17427
|
).argument("<artifactPath>", "artifact path to validate").argument("[extraPaths...]", "unused compatibility argument").option("--iteration <number>", "review/refactor iteration", (value) => {
|
|
15518
17428
|
const parsed = Number.parseInt(value, 10);
|
|
15519
17429
|
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
@@ -15566,7 +17476,13 @@ function createCliProgram(version) {
|
|
|
15566
17476
|
).option(
|
|
15567
17477
|
"--base-ref <ref>",
|
|
15568
17478
|
"canonical base ref for issue-final-review changedFiles coverage validation"
|
|
15569
|
-
).option(
|
|
17479
|
+
).option(
|
|
17480
|
+
"--prd-ref <ref>",
|
|
17481
|
+
"PRD reference for PRD plan validation artifacts"
|
|
17482
|
+
).option(
|
|
17483
|
+
"--workspace <path>",
|
|
17484
|
+
"PRD Plan Workspace path for prd-plan-workspace validation"
|
|
17485
|
+
).option("--child-id <id>", "child issue ID for prd-child-issue validation").option("--no-check-conflict-markers", "skip conflict marker scan").option("--cwd <path>", "target repository directory").action(
|
|
15570
17486
|
(kind, artifactPath, _extraPaths, options) => {
|
|
15571
17487
|
const allowedKinds = /* @__PURE__ */ new Set([
|
|
15572
17488
|
"reviewer",
|
|
@@ -15574,7 +17490,10 @@ function createCliProgram(version) {
|
|
|
15574
17490
|
"finalizer",
|
|
15575
17491
|
"conflict-resolution",
|
|
15576
17492
|
"failure-resolution",
|
|
15577
|
-
"issue-final-review"
|
|
17493
|
+
"issue-final-review",
|
|
17494
|
+
"prd-plan",
|
|
17495
|
+
"prd-child-issue",
|
|
17496
|
+
"prd-plan-workspace"
|
|
15578
17497
|
]);
|
|
15579
17498
|
if (!allowedKinds.has(kind)) {
|
|
15580
17499
|
throw new CommanderError(
|
|
@@ -15596,7 +17515,10 @@ function createCliProgram(version) {
|
|
|
15596
17515
|
issueNumber: options.issueNumber,
|
|
15597
17516
|
branchName: options.branchName,
|
|
15598
17517
|
baseRef: options.baseRef,
|
|
15599
|
-
checkConflictMarkers: options.checkConflictMarkers
|
|
17518
|
+
checkConflictMarkers: options.checkConflictMarkers,
|
|
17519
|
+
prdRef: options.prdRef,
|
|
17520
|
+
workspace: options.workspace,
|
|
17521
|
+
childId: options.childId
|
|
15600
17522
|
});
|
|
15601
17523
|
console.log(JSON.stringify(result, null, 2));
|
|
15602
17524
|
if (!result.ok) {
|
|
@@ -15604,6 +17526,40 @@ function createCliProgram(version) {
|
|
|
15604
17526
|
}
|
|
15605
17527
|
}
|
|
15606
17528
|
);
|
|
17529
|
+
prdPlanWorkspaceCommand.command("publish").description("Publish a PRD Plan Workspace through Beads and GitHub").argument("<workspace>", "PRD Plan Workspace path").requiredOption("--prd-ref <ref>", "PRD reference to publish").option("--cwd <path>", "target repository directory").action(
|
|
17530
|
+
async (workspace, options) => {
|
|
17531
|
+
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
17532
|
+
const logPath = path8.join(
|
|
17533
|
+
targetRepoRoot,
|
|
17534
|
+
".pourkit",
|
|
17535
|
+
"logs",
|
|
17536
|
+
"prd-plan-workspace-publish.log"
|
|
17537
|
+
);
|
|
17538
|
+
const logger = createLogger("pourkit", logPath);
|
|
17539
|
+
try {
|
|
17540
|
+
const config = await loadRepoConfig(targetRepoRoot);
|
|
17541
|
+
const client = await requireGitHubClient({ cwd: targetRepoRoot });
|
|
17542
|
+
const issueProvider = new GitHubIssueProvider(client, {
|
|
17543
|
+
readyForAgentLabel: config.labels.readyForAgent,
|
|
17544
|
+
blockedLabel: config.labels.blocked,
|
|
17545
|
+
issueListLimit: config.checks.issueListLimit
|
|
17546
|
+
});
|
|
17547
|
+
const result = await runPrdPlanWorkspacePublishCommand({
|
|
17548
|
+
repoRoot: targetRepoRoot,
|
|
17549
|
+
workspacePath: workspace,
|
|
17550
|
+
prdRef: options.prdRef,
|
|
17551
|
+
issueProvider,
|
|
17552
|
+
labels: config.labels,
|
|
17553
|
+
logger
|
|
17554
|
+
});
|
|
17555
|
+
console.log(JSON.stringify(result, null, 2));
|
|
17556
|
+
} catch (error) {
|
|
17557
|
+
await handleError(logger, error);
|
|
17558
|
+
} finally {
|
|
17559
|
+
await logger.close();
|
|
17560
|
+
}
|
|
17561
|
+
}
|
|
17562
|
+
);
|
|
15607
17563
|
issueCommand.command("create").description("Create a new GitHub issue").requiredOption("--title <title>", "issue title").option("--body <body>", "issue body text").option("--body-file <path>", "file to read issue body from").option(
|
|
15608
17564
|
"--label <label>",
|
|
15609
17565
|
"label to apply (repeatable)",
|
|
@@ -15743,7 +17699,7 @@ function createCliProgram(version) {
|
|
|
15743
17699
|
);
|
|
15744
17700
|
program.command("queue-run").requiredOption("--target <name>", "target name").option("--force", "bypass issue gates").option("--loop", "process runnable issues until the queue is drained").option("--cwd <path>", "target repository directory").option("--prd <ref>", "limit queue selection to child issues under a PRD").action(
|
|
15745
17701
|
async (options) => {
|
|
15746
|
-
const prdRef = options.prd ?
|
|
17702
|
+
const prdRef = options.prd ? normalizePrdRef2(options.prd) : void 0;
|
|
15747
17703
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15748
17704
|
const config = await loadRepoConfig(targetRepoRoot);
|
|
15749
17705
|
const logPath = path8.join(
|
|
@@ -15811,7 +17767,7 @@ function createCliProgram(version) {
|
|
|
15811
17767
|
prdRun.command("start").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--base <branch>", "override target baseBranch for this PRD Run").option("--adopt-existing-branch", "adopt an existing PRD branch").option("--cwd <path>", "target repository directory").action(
|
|
15812
17768
|
async (prdRef, options) => {
|
|
15813
17769
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15814
|
-
const normalizedPrdRef =
|
|
17770
|
+
const normalizedPrdRef = normalizePrdRef2(prdRef);
|
|
15815
17771
|
const logPath = path8.join(
|
|
15816
17772
|
targetRepoRoot,
|
|
15817
17773
|
".pourkit",
|
|
@@ -15857,7 +17813,7 @@ function createCliProgram(version) {
|
|
|
15857
17813
|
prdRun.command("launch").argument("<prdRef>", "PRD ref").requiredOption("--target <name>", "target name").option("--base <branch>", "override target baseBranch for this PRD Run").option("--cwd <path>", "target repository directory").option("--no-auto-merge", "skip auto-merge for manual review").option("--adopt-existing-branch", "adopt an existing PRD branch").action(
|
|
15858
17814
|
async (prdRef, options) => {
|
|
15859
17815
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15860
|
-
const normalizedPrdRef =
|
|
17816
|
+
const normalizedPrdRef = normalizePrdRef2(prdRef);
|
|
15861
17817
|
const logPath = path8.join(
|
|
15862
17818
|
targetRepoRoot,
|
|
15863
17819
|
".pourkit",
|
|
@@ -16133,11 +18089,11 @@ function createCliProgram(version) {
|
|
|
16133
18089
|
return program;
|
|
16134
18090
|
}
|
|
16135
18091
|
async function resolveCliVersion() {
|
|
16136
|
-
if (isPackageVersion("0.0.0-next-
|
|
16137
|
-
return "0.0.0-next-
|
|
18092
|
+
if (isPackageVersion("0.0.0-next-20260627024509")) {
|
|
18093
|
+
return "0.0.0-next-20260627024509";
|
|
16138
18094
|
}
|
|
16139
|
-
if (isReleaseVersion("0.0.0-next-
|
|
16140
|
-
return "0.0.0-next-
|
|
18095
|
+
if (isReleaseVersion("0.0.0-next-20260627024509")) {
|
|
18096
|
+
return "0.0.0-next-20260627024509";
|
|
16141
18097
|
}
|
|
16142
18098
|
try {
|
|
16143
18099
|
const root = repoRoot();
|