@pourkit/cli 0.0.0-next-20260625084151 → 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 +2404 -443
- 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,143 +7649,996 @@ 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
|
-
createdAt: z.string().min(1),
|
|
7427
|
-
updatedAt: z.string().min(1),
|
|
7428
|
-
blockedGate: z.enum([
|
|
7429
|
-
"receipt-check",
|
|
7430
|
-
"branch-state",
|
|
7431
|
-
"git",
|
|
7432
|
-
"queue",
|
|
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, "\\$&");
|
|
7484
7665
|
}
|
|
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
|
-
});
|
|
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")}`;
|
|
7498
7672
|
}
|
|
7499
|
-
function
|
|
7500
|
-
const
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
return { ok: true, guidance: parsed.data };
|
|
7505
|
-
}
|
|
7506
|
-
return {
|
|
7507
|
-
ok: false,
|
|
7508
|
-
guidance: {
|
|
7509
|
-
createdAt: now,
|
|
7510
|
-
updatedAt: now,
|
|
7511
|
-
blockedGate: record.blockedGate ?? "queue",
|
|
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
|
-
]
|
|
7521
|
-
}
|
|
7522
|
-
};
|
|
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()}`;
|
|
7523
7678
|
}
|
|
7524
|
-
return {
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7679
|
+
return `bd-${prdMatch[1].padStart(4, "0")}.${childMatch[1]}`;
|
|
7680
|
+
}
|
|
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());
|
|
7694
|
+
}
|
|
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] : []
|
|
8640
|
+
}
|
|
8641
|
+
};
|
|
7539
8642
|
}
|
|
7540
8643
|
function renderPrdRunRepairGuidance(guidance) {
|
|
7541
8644
|
const lines = [];
|
|
@@ -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 {
|
|
@@ -9280,6 +10625,107 @@ async function attemptRecordedIssueLabelProjection(options, prdRef, resumeIssueN
|
|
|
9280
10625
|
return false;
|
|
9281
10626
|
}
|
|
9282
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
|
+
};
|
|
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
|
+
};
|
|
10728
|
+
}
|
|
9283
10729
|
async function launchPrdRun(options) {
|
|
9284
10730
|
const prdRef = normalizePrdRunRef(options.prdRef);
|
|
9285
10731
|
const existingRecord = readPrdRun(options.repoRoot, 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,
|
|
@@ -9529,15 +10978,45 @@ async function launchPrdRun(options) {
|
|
|
9529
10978
|
startReceipt.queueStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9530
10979
|
startReceipt.queueCommand = "queue-run";
|
|
9531
10980
|
}
|
|
9532
|
-
writePrdRunRecord(options.repoRoot, {
|
|
9533
|
-
prdRef,
|
|
9534
|
-
status: "running",
|
|
9535
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9536
|
-
targetName,
|
|
9537
|
-
start: startReceipt,
|
|
9538
|
-
repairGuidance: currentRecord?.repairGuidance
|
|
9539
|
-
});
|
|
9540
|
-
|
|
10981
|
+
writePrdRunRecord(options.repoRoot, {
|
|
10982
|
+
prdRef,
|
|
10983
|
+
status: "running",
|
|
10984
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10985
|
+
targetName,
|
|
10986
|
+
start: startReceipt,
|
|
10987
|
+
repairGuidance: currentRecord?.repairGuidance
|
|
10988
|
+
});
|
|
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,
|
|
@@ -9615,6 +11094,11 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9615
11094
|
const executionProvider = options.executionProvider;
|
|
9616
11095
|
const logger = options.logger;
|
|
9617
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
|
+
);
|
|
9618
11102
|
try {
|
|
9619
11103
|
const outcome = await runQueueCommand({
|
|
9620
11104
|
targetName,
|
|
@@ -9702,78 +11186,489 @@ async function launchGithubQueueDrain(options, prdRef, startReceipt, targetName,
|
|
|
9702
11186
|
);
|
|
9703
11187
|
}
|
|
9704
11188
|
}
|
|
9705
|
-
writeTerminalRecord(
|
|
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,
|
|
11257
|
+
repairability: "operator-action",
|
|
11258
|
+
nextAction: "inspect-and-retry",
|
|
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
|
|
11305
|
+
}),
|
|
11306
|
+
plan,
|
|
11307
|
+
startOutcome,
|
|
11308
|
+
targetName,
|
|
11309
|
+
startReceipt
|
|
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;
|
|
11328
|
+
}
|
|
11329
|
+
}
|
|
11330
|
+
async function launchBeadsQueueDrain(options, prdRef, startReceipt, targetName, plan, startOutcome, resumeIssueNumber) {
|
|
11331
|
+
if (!startReceipt) {
|
|
11332
|
+
return buildLaunchBlockedOutcome(
|
|
11333
|
+
options.repoRoot,
|
|
11334
|
+
prdRef,
|
|
11335
|
+
buildPrdRunRepairGuidance({
|
|
11336
|
+
blockedGate: "branch-state",
|
|
11337
|
+
blockedStage: "start-validation",
|
|
11338
|
+
failureCode: "missing-start-receipt",
|
|
11339
|
+
humanReadableReason: `PRD Run ${prdRef} is missing start receipt before Beads queue drain.`,
|
|
11340
|
+
repairability: "automatic-retry-safe",
|
|
11341
|
+
nextAction: "rerun-prd-run-launch",
|
|
11342
|
+
nextRecommendedCommand: "pourkit prd-run launch"
|
|
11343
|
+
}),
|
|
11344
|
+
plan,
|
|
11345
|
+
startOutcome,
|
|
11346
|
+
targetName
|
|
11347
|
+
);
|
|
11348
|
+
}
|
|
11349
|
+
if (!options.config) {
|
|
11350
|
+
return buildLaunchBlockedOutcome(
|
|
11351
|
+
options.repoRoot,
|
|
11352
|
+
prdRef,
|
|
11353
|
+
buildPrdRunRepairGuidance({
|
|
11354
|
+
blockedGate: "queue",
|
|
11355
|
+
blockedStage: "queue-drain",
|
|
11356
|
+
failureCode: "missing-config",
|
|
11357
|
+
humanReadableReason: `PRD Run ${prdRef} requires config for Beads-backed queue drain.`,
|
|
11358
|
+
repairability: "operator-action",
|
|
11359
|
+
nextAction: "inspect-and-retry",
|
|
11360
|
+
nextRecommendedCommand: "pourkit prd-run launch --with-config",
|
|
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
|
+
]
|
|
11405
|
+
}),
|
|
11406
|
+
plan,
|
|
11407
|
+
startOutcome,
|
|
11408
|
+
targetName,
|
|
11409
|
+
startReceipt
|
|
11410
|
+
);
|
|
11411
|
+
}
|
|
11412
|
+
const issueProvider = options.issueProvider;
|
|
11413
|
+
const prProvider = options.prProvider;
|
|
11414
|
+
const executionProvider = options.executionProvider;
|
|
11415
|
+
const logger = options.logger;
|
|
11416
|
+
const processedResults = [];
|
|
11417
|
+
const originalGuidance = readPrdRun(options.repoRoot, prdRef).record?.repairGuidance;
|
|
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,
|
|
11443
|
+
targetName,
|
|
11444
|
+
startReceipt
|
|
11445
|
+
);
|
|
11446
|
+
}
|
|
11447
|
+
if (resumeIssueNumber !== void 0) {
|
|
11448
|
+
const resumeIssue = originalGuidance?.issue;
|
|
11449
|
+
const resumeBeadsContext = resumeIssue?.beadsChildId && resumeIssue.beadsChildRef && !resumeIssue.beadsChildClosed ? {
|
|
11450
|
+
prdRef,
|
|
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,
|
|
11461
|
+
prdRef,
|
|
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: {
|
|
11490
|
+
prdRef,
|
|
11491
|
+
prdBranch: startReceipt.prdBranch,
|
|
11492
|
+
...resumeBeadsContext ? { beadsChild: resumeBeadsContext } : {}
|
|
11493
|
+
}
|
|
11494
|
+
});
|
|
11495
|
+
processedResults.push({
|
|
11496
|
+
beadsChildId: resumeIssue?.beadsChildId ?? "resumed",
|
|
11497
|
+
issueNumber: runResult.selected.number,
|
|
11498
|
+
publicationStatus: runResult.runResult.publicationStatus
|
|
11499
|
+
});
|
|
11500
|
+
startReceipt.queueDrainedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
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.`
|
|
11544
|
+
);
|
|
11545
|
+
break;
|
|
11546
|
+
}
|
|
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";
|
|
@@ -10678,12 +12582,12 @@ icm --db .pourkit/icm/memories.db topics # list a
|
|
|
10678
12582
|
var MANAGED_BLOCK_BEGIN = "<!-- BEGIN POURKIT MANAGED BLOCK -->";
|
|
10679
12583
|
var MANAGED_BLOCK_END = "<!-- END POURKIT MANAGED BLOCK -->";
|
|
10680
12584
|
var MANAGED_BLOCK_PATTERN = new RegExp(
|
|
10681
|
-
`${
|
|
12585
|
+
`${escapeRegExp3(MANAGED_BLOCK_BEGIN)}\\r?\\n[\\s\\S]*?${escapeRegExp3(
|
|
10682
12586
|
MANAGED_BLOCK_END
|
|
10683
12587
|
)}[ \\t]*(?:\\r?\\n)?`,
|
|
10684
12588
|
"g"
|
|
10685
12589
|
);
|
|
10686
|
-
function
|
|
12590
|
+
function escapeRegExp3(value) {
|
|
10687
12591
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10688
12592
|
}
|
|
10689
12593
|
function buildManagedBlock(content) {
|
|
@@ -11064,7 +12968,7 @@ function generateGitignoreBlock() {
|
|
|
11064
12968
|
}
|
|
11065
12969
|
async function walkDir(dir) {
|
|
11066
12970
|
const files = [];
|
|
11067
|
-
const entries = await
|
|
12971
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
11068
12972
|
for (const entry of entries) {
|
|
11069
12973
|
const full = path5.join(dir, entry.name);
|
|
11070
12974
|
if (entry.isDirectory()) {
|
|
@@ -11076,7 +12980,7 @@ async function walkDir(dir) {
|
|
|
11076
12980
|
return files;
|
|
11077
12981
|
}
|
|
11078
12982
|
async function computeFileChecksum(filePath) {
|
|
11079
|
-
const content = await
|
|
12983
|
+
const content = await readFile5(filePath);
|
|
11080
12984
|
return createHash("sha256").update(content).digest("hex");
|
|
11081
12985
|
}
|
|
11082
12986
|
function lockfileExists(root, name) {
|
|
@@ -11165,7 +13069,7 @@ async function discoverOpenCodeAgentFiles(sourceRoot) {
|
|
|
11165
13069
|
(candidate) => existsSync16(candidate) && statSync(candidate).isDirectory()
|
|
11166
13070
|
);
|
|
11167
13071
|
if (!agentsDir) return [];
|
|
11168
|
-
const entries = await
|
|
13072
|
+
const entries = await readdir2(agentsDir, { withFileTypes: true });
|
|
11169
13073
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => path5.join(agentsDir, entry.name));
|
|
11170
13074
|
}
|
|
11171
13075
|
async function discoverRootDomainDocs(root) {
|
|
@@ -11178,7 +13082,7 @@ async function discoverRootDomainDocs(root) {
|
|
|
11178
13082
|
}
|
|
11179
13083
|
const adrDir = path5.join(root, "docs", "adr");
|
|
11180
13084
|
if (existsSync16(adrDir)) {
|
|
11181
|
-
const entries = await
|
|
13085
|
+
const entries = await readdir2(adrDir, { withFileTypes: true });
|
|
11182
13086
|
for (const entry of entries) {
|
|
11183
13087
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
11184
13088
|
docs.push(path5.join(adrDir, entry.name));
|
|
@@ -11493,7 +13397,7 @@ async function planInit(options) {
|
|
|
11493
13397
|
"skills"
|
|
11494
13398
|
);
|
|
11495
13399
|
const projectionSkillsDir = path5.join(targetRoot, ".agents", "skills");
|
|
11496
|
-
const entries = await
|
|
13400
|
+
const entries = await readdir2(s, { withFileTypes: true });
|
|
11497
13401
|
const skillNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
11498
13402
|
const addonManagedDir = path5.join(
|
|
11499
13403
|
targetRoot,
|
|
@@ -11559,7 +13463,7 @@ async function planInit(options) {
|
|
|
11559
13463
|
});
|
|
11560
13464
|
continue;
|
|
11561
13465
|
}
|
|
11562
|
-
const sourceContent = await
|
|
13466
|
+
const sourceContent = await readFile5(file, "utf-8");
|
|
11563
13467
|
const managedSourcePath = isReleaseAddon ? `.pourkit/managed/addons/release/skills/${skillName}/${relPath}` : `.pourkit/managed/skills/${skillName}/${relPath}`;
|
|
11564
13468
|
operations.push({
|
|
11565
13469
|
kind: "create",
|
|
@@ -11638,7 +13542,7 @@ async function planInit(options) {
|
|
|
11638
13542
|
}
|
|
11639
13543
|
let hasPackageJson = true;
|
|
11640
13544
|
try {
|
|
11641
|
-
await
|
|
13545
|
+
await readFile5(path5.join(targetRoot, "package.json"), "utf-8");
|
|
11642
13546
|
} catch {
|
|
11643
13547
|
hasPackageJson = false;
|
|
11644
13548
|
}
|
|
@@ -12426,17 +14330,17 @@ async function promptForInitChoices(plan, current) {
|
|
|
12426
14330
|
}
|
|
12427
14331
|
async function writeFileAtomic(filePath, content) {
|
|
12428
14332
|
const tmpPath = `${filePath}.tmp.${randomUUID()}`;
|
|
12429
|
-
await
|
|
14333
|
+
await writeFile2(tmpPath, content, "utf-8");
|
|
12430
14334
|
await rename(tmpPath, filePath);
|
|
12431
14335
|
}
|
|
12432
14336
|
async function updateManagedBlock(filePath, content) {
|
|
12433
14337
|
if (!existsSync16(filePath)) {
|
|
12434
14338
|
const dir = path5.dirname(filePath);
|
|
12435
|
-
await
|
|
14339
|
+
await mkdir5(dir, { recursive: true });
|
|
12436
14340
|
await writeFileAtomic(filePath, buildManagedBlock(content));
|
|
12437
14341
|
return;
|
|
12438
14342
|
}
|
|
12439
|
-
const existing = await
|
|
14343
|
+
const existing = await readFile5(filePath, "utf-8");
|
|
12440
14344
|
await writeFileAtomic(filePath, upsertManagedBlock(existing, content));
|
|
12441
14345
|
}
|
|
12442
14346
|
async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
@@ -12475,7 +14379,7 @@ async function writeManifest(plan, sourceMeta, agentFiles, packageManager) {
|
|
|
12475
14379
|
workflowPack: buildWorkflowPackMetadata(plan.enabledAddons ?? []),
|
|
12476
14380
|
assets
|
|
12477
14381
|
};
|
|
12478
|
-
await
|
|
14382
|
+
await mkdir5(manifestDir, { recursive: true });
|
|
12479
14383
|
await writeFileAtomic(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
12480
14384
|
return manifest;
|
|
12481
14385
|
}
|
|
@@ -12501,7 +14405,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12501
14405
|
continue;
|
|
12502
14406
|
}
|
|
12503
14407
|
const dir = path5.dirname(op.path);
|
|
12504
|
-
await
|
|
14408
|
+
await mkdir5(dir, { recursive: true });
|
|
12505
14409
|
await writeFileAtomic(op.path, op.content ?? "");
|
|
12506
14410
|
applied++;
|
|
12507
14411
|
break;
|
|
@@ -12520,7 +14424,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12520
14424
|
skipped++;
|
|
12521
14425
|
} else {
|
|
12522
14426
|
const dir = path5.dirname(op.path);
|
|
12523
|
-
await
|
|
14427
|
+
await mkdir5(dir, { recursive: true });
|
|
12524
14428
|
await copyFile(op.sourcePath, op.path);
|
|
12525
14429
|
applied++;
|
|
12526
14430
|
}
|
|
@@ -12545,7 +14449,7 @@ async function applyInitPlan(plan, options) {
|
|
|
12545
14449
|
continue;
|
|
12546
14450
|
}
|
|
12547
14451
|
const dir = path5.dirname(op.path);
|
|
12548
|
-
await
|
|
14452
|
+
await mkdir5(dir, { recursive: true });
|
|
12549
14453
|
await rename(op.sourcePath, op.path);
|
|
12550
14454
|
applied++;
|
|
12551
14455
|
break;
|
|
@@ -12862,13 +14766,13 @@ Init applied: ${result.applied} operations applied, ${result.skipped} skipped.`
|
|
|
12862
14766
|
// commands/memory-init.ts
|
|
12863
14767
|
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync5 } from "fs";
|
|
12864
14768
|
import { execFile as execFile3 } from "child_process";
|
|
12865
|
-
import { join as
|
|
14769
|
+
import { join as join20 } from "path";
|
|
12866
14770
|
import { promisify as promisify3 } from "util";
|
|
12867
14771
|
var execFileAsync3 = promisify3(execFile3);
|
|
12868
14772
|
async function runMemoryInitCommand(options) {
|
|
12869
14773
|
const cwd = options.cwd ?? process.cwd();
|
|
12870
14774
|
const execCommand = options.execCommand ?? execFileAsync3;
|
|
12871
|
-
const configPath =
|
|
14775
|
+
const configPath = join20(cwd, ".pourkit", "config.json");
|
|
12872
14776
|
if (!existsSync17(configPath)) {
|
|
12873
14777
|
return {
|
|
12874
14778
|
ok: false,
|
|
@@ -12900,8 +14804,8 @@ async function runMemoryInitCommand(options) {
|
|
|
12900
14804
|
const next = { ...raw, memory };
|
|
12901
14805
|
writeFileSync5(configPath, JSON.stringify(next, null, 2) + "\n");
|
|
12902
14806
|
}
|
|
12903
|
-
const memoryDir =
|
|
12904
|
-
const memoryDbPath =
|
|
14807
|
+
const memoryDir = join20(cwd, ".pourkit", "icm");
|
|
14808
|
+
const memoryDbPath = join20(memoryDir, "memories.db");
|
|
12905
14809
|
mkdirSync8(memoryDir, { recursive: true });
|
|
12906
14810
|
let dbInitialized = false;
|
|
12907
14811
|
let warning;
|
|
@@ -12915,7 +14819,7 @@ async function runMemoryInitCommand(options) {
|
|
|
12915
14819
|
} catch {
|
|
12916
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.";
|
|
12917
14821
|
}
|
|
12918
|
-
const gitignorePath =
|
|
14822
|
+
const gitignorePath = join20(cwd, ".gitignore");
|
|
12919
14823
|
updateManagedBlockSync(gitignorePath, generateGitignoreBlock());
|
|
12920
14824
|
const managed = await generateManagedAgentInstructions({
|
|
12921
14825
|
sourceRoot: cwd,
|
|
@@ -12923,14 +14827,14 @@ async function runMemoryInitCommand(options) {
|
|
|
12923
14827
|
});
|
|
12924
14828
|
let updatedAgentFile = false;
|
|
12925
14829
|
for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
|
|
12926
|
-
const agentPath =
|
|
14830
|
+
const agentPath = join20(cwd, fileName);
|
|
12927
14831
|
if (existsSync17(agentPath)) {
|
|
12928
14832
|
updateManagedBlockSync(agentPath, managed);
|
|
12929
14833
|
updatedAgentFile = true;
|
|
12930
14834
|
}
|
|
12931
14835
|
}
|
|
12932
14836
|
if (!updatedAgentFile) {
|
|
12933
|
-
updateManagedBlockSync(
|
|
14837
|
+
updateManagedBlockSync(join20(cwd, "AGENTS.md"), managed);
|
|
12934
14838
|
}
|
|
12935
14839
|
return {
|
|
12936
14840
|
ok: true,
|
|
@@ -13066,17 +14970,17 @@ async function runSerenaStatusCommand(options) {
|
|
|
13066
14970
|
|
|
13067
14971
|
// commands/config-schema.ts
|
|
13068
14972
|
import { readFileSync as readFileSync18, existsSync as existsSync18 } from "fs";
|
|
13069
|
-
import { mkdir as
|
|
13070
|
-
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";
|
|
13071
14975
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13072
14976
|
import Ajv2 from "ajv";
|
|
13073
14977
|
init_common();
|
|
13074
14978
|
var __filename3 = fileURLToPath3(import.meta.url);
|
|
13075
|
-
var __dirname3 =
|
|
14979
|
+
var __dirname3 = dirname6(__filename3);
|
|
13076
14980
|
function resolvePackagedAssetPath2(fileName) {
|
|
13077
14981
|
const candidates = [
|
|
13078
|
-
|
|
13079
|
-
|
|
14982
|
+
resolve4(__dirname3, "schema", fileName),
|
|
14983
|
+
resolve4(__dirname3, "../schema", fileName)
|
|
13080
14984
|
];
|
|
13081
14985
|
return candidates.find((candidate) => existsSync18(candidate)) ?? candidates[0];
|
|
13082
14986
|
}
|
|
@@ -13103,15 +15007,15 @@ function readPackagedHash() {
|
|
|
13103
15007
|
return readFileSync18(PACKAGED_HASH_PATH, "utf-8");
|
|
13104
15008
|
}
|
|
13105
15009
|
function localSchemaDir(repoRoot2) {
|
|
13106
|
-
return
|
|
15010
|
+
return resolve4(repoRoot2, ".pourkit/schema");
|
|
13107
15011
|
}
|
|
13108
15012
|
var MANAGED_BLOCK_BEGIN2 = "<!-- BEGIN POURKIT MANAGED BLOCK -->\n";
|
|
13109
15013
|
var MANAGED_BLOCK_BEGIN_MARKER = MANAGED_BLOCK_BEGIN2.trim();
|
|
13110
15014
|
var MANAGED_BLOCK_END_MARKER = "<!-- END POURKIT MANAGED BLOCK -->";
|
|
13111
15015
|
var OLD_DOCS_PATH_PATTERN = ".pourkit/docs/agents/";
|
|
13112
15016
|
function resolvePackagedManagedPath(...segments) {
|
|
13113
|
-
const bundledPath =
|
|
13114
|
-
const sourcePath =
|
|
15017
|
+
const bundledPath = resolve4(__dirname3, "managed", ...segments);
|
|
15018
|
+
const sourcePath = resolve4(__dirname3, "..", "managed", ...segments);
|
|
13115
15019
|
const candidates = [bundledPath, sourcePath];
|
|
13116
15020
|
return candidates.find((candidate) => existsSync18(candidate)) ?? bundledPath;
|
|
13117
15021
|
}
|
|
@@ -13129,21 +15033,21 @@ function resolvePackagedReleaseAddonDocPath(docName) {
|
|
|
13129
15033
|
}
|
|
13130
15034
|
async function readPackagedTextFile(filePath) {
|
|
13131
15035
|
try {
|
|
13132
|
-
return await
|
|
15036
|
+
return await readFile6(filePath, "utf-8");
|
|
13133
15037
|
} catch {
|
|
13134
15038
|
return null;
|
|
13135
15039
|
}
|
|
13136
15040
|
}
|
|
13137
15041
|
function isPathContained(parent, child) {
|
|
13138
|
-
const relativePath =
|
|
15042
|
+
const relativePath = relative4(parent, child);
|
|
13139
15043
|
return !relativePath.startsWith("..") && !relativePath.startsWith("../");
|
|
13140
15044
|
}
|
|
13141
15045
|
async function walkDir2(dir) {
|
|
13142
15046
|
const files = [];
|
|
13143
15047
|
try {
|
|
13144
|
-
const entries = await
|
|
15048
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
13145
15049
|
for (const entry of entries) {
|
|
13146
|
-
const full =
|
|
15050
|
+
const full = resolve4(dir, entry.name);
|
|
13147
15051
|
if (entry.isDirectory()) {
|
|
13148
15052
|
files.push(...await walkDir2(full));
|
|
13149
15053
|
} else {
|
|
@@ -13225,7 +15129,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13225
15129
|
const catalog = getWorkflowPackCatalog();
|
|
13226
15130
|
const baselineSkills = getBaselineManagedSkillNames();
|
|
13227
15131
|
for (const docName of catalog.docs) {
|
|
13228
|
-
const localPath =
|
|
15132
|
+
const localPath = resolve4(cwd, `.pourkit/managed/docs/agents/${docName}`);
|
|
13229
15133
|
if (!existsSync18(localPath)) {
|
|
13230
15134
|
failures.push({
|
|
13231
15135
|
severity: "failure",
|
|
@@ -13238,9 +15142,9 @@ async function validateWorkflowPack(cwd) {
|
|
|
13238
15142
|
const packagedContent = await readPackagedTextFile(packagedPath);
|
|
13239
15143
|
if (packagedContent !== null) {
|
|
13240
15144
|
try {
|
|
13241
|
-
const localContent = await
|
|
15145
|
+
const localContent = await readFile6(localPath, "utf-8");
|
|
13242
15146
|
if (localContent !== packagedContent) {
|
|
13243
|
-
const overridePath =
|
|
15147
|
+
const overridePath = resolve4(
|
|
13244
15148
|
cwd,
|
|
13245
15149
|
`.pourkit/overrides/docs/agents/${docName}`
|
|
13246
15150
|
);
|
|
@@ -13266,7 +15170,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13266
15170
|
}
|
|
13267
15171
|
}
|
|
13268
15172
|
for (const skillName of baselineSkills) {
|
|
13269
|
-
const skillDir =
|
|
15173
|
+
const skillDir = resolve4(cwd, `.pourkit/managed/skills/${skillName}`);
|
|
13270
15174
|
if (!existsSync18(skillDir)) {
|
|
13271
15175
|
failures.push({
|
|
13272
15176
|
severity: "failure",
|
|
@@ -13275,7 +15179,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13275
15179
|
message: `Missing managed skill: ${skillName}`
|
|
13276
15180
|
});
|
|
13277
15181
|
} else {
|
|
13278
|
-
const skillSourcePath =
|
|
15182
|
+
const skillSourcePath = resolve4(skillDir, "SKILL.md");
|
|
13279
15183
|
if (existsSync18(skillSourcePath)) {
|
|
13280
15184
|
const packagedSkillPath = resolvePackagedManagedPath(
|
|
13281
15185
|
"skills",
|
|
@@ -13285,9 +15189,9 @@ async function validateWorkflowPack(cwd) {
|
|
|
13285
15189
|
const packagedContent = await readPackagedTextFile(packagedSkillPath);
|
|
13286
15190
|
if (packagedContent !== null) {
|
|
13287
15191
|
try {
|
|
13288
|
-
const localContent = await
|
|
15192
|
+
const localContent = await readFile6(skillSourcePath, "utf-8");
|
|
13289
15193
|
if (localContent !== packagedContent) {
|
|
13290
|
-
const overridePath =
|
|
15194
|
+
const overridePath = resolve4(
|
|
13291
15195
|
cwd,
|
|
13292
15196
|
`.pourkit/overrides/skills/${skillName}/SKILL.md`
|
|
13293
15197
|
);
|
|
@@ -13314,7 +15218,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13314
15218
|
}
|
|
13315
15219
|
}
|
|
13316
15220
|
for (const promptName of catalog.prompts) {
|
|
13317
|
-
const promptPath =
|
|
15221
|
+
const promptPath = resolve4(cwd, `.pourkit/managed/prompts/${promptName}`);
|
|
13318
15222
|
if (!existsSync18(promptPath)) {
|
|
13319
15223
|
failures.push({
|
|
13320
15224
|
severity: "failure",
|
|
@@ -13325,7 +15229,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13325
15229
|
}
|
|
13326
15230
|
}
|
|
13327
15231
|
for (const skillName of baselineSkills) {
|
|
13328
|
-
const projectionDir =
|
|
15232
|
+
const projectionDir = resolve4(cwd, `.agents/skills/${skillName}`);
|
|
13329
15233
|
if (!existsSync18(projectionDir)) {
|
|
13330
15234
|
warnings.push({
|
|
13331
15235
|
severity: "warning",
|
|
@@ -13334,7 +15238,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13334
15238
|
message: `Missing skill projection for ${skillName}. Run 'pourkit sync workflow-pack' to regenerate.`
|
|
13335
15239
|
});
|
|
13336
15240
|
} else {
|
|
13337
|
-
const projectionPath =
|
|
15241
|
+
const projectionPath = resolve4(projectionDir, "SKILL.md");
|
|
13338
15242
|
if (existsSync18(projectionPath)) {
|
|
13339
15243
|
const managedSourcePath = resolvePackagedManagedPath(
|
|
13340
15244
|
"skills",
|
|
@@ -13344,7 +15248,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13344
15248
|
const packagedContent = await readPackagedTextFile(managedSourcePath);
|
|
13345
15249
|
if (packagedContent !== null) {
|
|
13346
15250
|
try {
|
|
13347
|
-
const projectionContent = await
|
|
15251
|
+
const projectionContent = await readFile6(projectionPath, "utf-8");
|
|
13348
15252
|
const managedSourceRelativePath = `.pourkit/managed/skills/${skillName}/SKILL.md`;
|
|
13349
15253
|
if (projectionContent !== buildOpenCodeSkillProjectionContent(
|
|
13350
15254
|
managedSourceRelativePath,
|
|
@@ -13363,10 +15267,10 @@ async function validateWorkflowPack(cwd) {
|
|
|
13363
15267
|
}
|
|
13364
15268
|
}
|
|
13365
15269
|
}
|
|
13366
|
-
const agentsPath =
|
|
15270
|
+
const agentsPath = resolve4(cwd, "AGENTS.md");
|
|
13367
15271
|
if (existsSync18(agentsPath)) {
|
|
13368
15272
|
try {
|
|
13369
|
-
const agentsContent = await
|
|
15273
|
+
const agentsContent = await readFile6(agentsPath, "utf-8");
|
|
13370
15274
|
if (agentsContent.includes(OLD_DOCS_PATH_PATTERN)) {
|
|
13371
15275
|
failures.push({
|
|
13372
15276
|
severity: "failure",
|
|
@@ -13386,11 +15290,11 @@ async function validateWorkflowPack(cwd) {
|
|
|
13386
15290
|
} catch {
|
|
13387
15291
|
}
|
|
13388
15292
|
}
|
|
13389
|
-
const overridesDir =
|
|
15293
|
+
const overridesDir = resolve4(cwd, ".pourkit/overrides");
|
|
13390
15294
|
if (existsSync18(overridesDir)) {
|
|
13391
15295
|
const overrideFiles = await walkDir2(overridesDir);
|
|
13392
15296
|
for (const file of overrideFiles) {
|
|
13393
|
-
const relPath =
|
|
15297
|
+
const relPath = relative4(cwd, file);
|
|
13394
15298
|
if (!warnings.some((w) => w.kind === "overridden" && w.path === relPath)) {
|
|
13395
15299
|
warnings.push({
|
|
13396
15300
|
severity: "warning",
|
|
@@ -13401,15 +15305,15 @@ async function validateWorkflowPack(cwd) {
|
|
|
13401
15305
|
}
|
|
13402
15306
|
}
|
|
13403
15307
|
}
|
|
13404
|
-
const configPath =
|
|
15308
|
+
const configPath = resolve4(cwd, ".pourkit/config.json");
|
|
13405
15309
|
if (existsSync18(configPath)) {
|
|
13406
15310
|
try {
|
|
13407
|
-
const configContent = await
|
|
15311
|
+
const configContent = await readFile6(configPath, "utf-8");
|
|
13408
15312
|
const parsed = JSON.parse(configContent);
|
|
13409
15313
|
const releaseEnabled = parsed.releaseWorkflow?.enabled === true;
|
|
13410
15314
|
if (releaseEnabled) {
|
|
13411
15315
|
for (const skillName of catalog.releaseAddonSkills) {
|
|
13412
|
-
const addonDir =
|
|
15316
|
+
const addonDir = resolve4(
|
|
13413
15317
|
cwd,
|
|
13414
15318
|
`.pourkit/managed/addons/release/skills/${skillName}`
|
|
13415
15319
|
);
|
|
@@ -13423,7 +15327,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13423
15327
|
}
|
|
13424
15328
|
}
|
|
13425
15329
|
for (const docName of catalog.releaseAddonDocs) {
|
|
13426
|
-
const addonDocPath =
|
|
15330
|
+
const addonDocPath = resolve4(
|
|
13427
15331
|
cwd,
|
|
13428
15332
|
`.pourkit/managed/addons/release/docs/agents/${docName}`
|
|
13429
15333
|
);
|
|
@@ -13439,7 +15343,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13439
15343
|
resolvePackagedReleaseAddonDocPath(docName)
|
|
13440
15344
|
);
|
|
13441
15345
|
if (packagedContent !== null) {
|
|
13442
|
-
const localContent = await
|
|
15346
|
+
const localContent = await readFile6(addonDocPath, "utf-8");
|
|
13443
15347
|
if (localContent !== packagedContent) {
|
|
13444
15348
|
failures.push({
|
|
13445
15349
|
severity: "failure",
|
|
@@ -13468,7 +15372,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13468
15372
|
}
|
|
13469
15373
|
} else {
|
|
13470
15374
|
for (const skillName of catalog.releaseAddonSkills) {
|
|
13471
|
-
const addonDir =
|
|
15375
|
+
const addonDir = resolve4(
|
|
13472
15376
|
cwd,
|
|
13473
15377
|
`.pourkit/managed/addons/release/skills/${skillName}`
|
|
13474
15378
|
);
|
|
@@ -13482,7 +15386,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13482
15386
|
}
|
|
13483
15387
|
}
|
|
13484
15388
|
for (const docName of catalog.releaseAddonDocs) {
|
|
13485
|
-
const addonDocPath =
|
|
15389
|
+
const addonDocPath = resolve4(
|
|
13486
15390
|
cwd,
|
|
13487
15391
|
`.pourkit/managed/addons/release/docs/agents/${docName}`
|
|
13488
15392
|
);
|
|
@@ -13499,16 +15403,16 @@ async function validateWorkflowPack(cwd) {
|
|
|
13499
15403
|
} catch {
|
|
13500
15404
|
}
|
|
13501
15405
|
}
|
|
13502
|
-
const manifestPath =
|
|
15406
|
+
const manifestPath = resolve4(cwd, ".pourkit/manifest.json");
|
|
13503
15407
|
if (existsSync18(manifestPath)) {
|
|
13504
15408
|
try {
|
|
13505
|
-
const manifestContent = await
|
|
15409
|
+
const manifestContent = await readFile6(manifestPath, "utf-8");
|
|
13506
15410
|
const manifest = JSON.parse(manifestContent);
|
|
13507
15411
|
const wp = manifest.workflowPack;
|
|
13508
15412
|
if (wp?.projections) {
|
|
13509
15413
|
for (const proj of wp.projections) {
|
|
13510
15414
|
if (proj.path) {
|
|
13511
|
-
const resolved =
|
|
15415
|
+
const resolved = resolve4(cwd, proj.path);
|
|
13512
15416
|
if (!isPathContained(cwd, resolved)) {
|
|
13513
15417
|
failures.push({
|
|
13514
15418
|
severity: "failure",
|
|
@@ -13522,7 +15426,7 @@ async function validateWorkflowPack(cwd) {
|
|
|
13522
15426
|
} catch {
|
|
13523
15427
|
}
|
|
13524
15428
|
}
|
|
13525
|
-
const pathEscapeOverrideDir =
|
|
15429
|
+
const pathEscapeOverrideDir = resolve4(cwd, ".pourkit/overrides");
|
|
13526
15430
|
if (existsSync18(pathEscapeOverrideDir)) {
|
|
13527
15431
|
const overrideFiles = await walkDir2(pathEscapeOverrideDir);
|
|
13528
15432
|
for (const file of overrideFiles) {
|
|
@@ -13530,20 +15434,20 @@ async function validateWorkflowPack(cwd) {
|
|
|
13530
15434
|
failures.push({
|
|
13531
15435
|
severity: "failure",
|
|
13532
15436
|
kind: "path_escape",
|
|
13533
|
-
path:
|
|
13534
|
-
message: `Override file path escapes repository: ${
|
|
15437
|
+
path: relative4(cwd, file),
|
|
15438
|
+
message: `Override file path escapes repository: ${relative4(cwd, file)}`
|
|
13535
15439
|
});
|
|
13536
15440
|
}
|
|
13537
15441
|
}
|
|
13538
15442
|
}
|
|
13539
|
-
const managedDocsDir =
|
|
15443
|
+
const managedDocsDir = resolve4(cwd, ".pourkit/managed/docs/agents");
|
|
13540
15444
|
if (existsSync18(managedDocsDir)) {
|
|
13541
15445
|
const docFiles = await walkDir2(managedDocsDir);
|
|
13542
15446
|
for (const file of docFiles) {
|
|
13543
15447
|
if (!file.endsWith(".md")) continue;
|
|
13544
15448
|
try {
|
|
13545
|
-
const content = await
|
|
13546
|
-
const relPath =
|
|
15449
|
+
const content = await readFile6(file, "utf-8");
|
|
15450
|
+
const relPath = relative4(cwd, file);
|
|
13547
15451
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13548
15452
|
for (const v of violations) {
|
|
13549
15453
|
failures.push({
|
|
@@ -13557,16 +15461,16 @@ async function validateWorkflowPack(cwd) {
|
|
|
13557
15461
|
}
|
|
13558
15462
|
}
|
|
13559
15463
|
}
|
|
13560
|
-
const managedSkillsDir =
|
|
15464
|
+
const managedSkillsDir = resolve4(cwd, ".pourkit/managed/skills");
|
|
13561
15465
|
if (existsSync18(managedSkillsDir)) {
|
|
13562
|
-
const skillDirs = await
|
|
15466
|
+
const skillDirs = await readdir3(managedSkillsDir, { withFileTypes: true });
|
|
13563
15467
|
for (const entry of skillDirs) {
|
|
13564
15468
|
if (!entry.isDirectory()) continue;
|
|
13565
|
-
const skillFile =
|
|
15469
|
+
const skillFile = resolve4(managedSkillsDir, entry.name, "SKILL.md");
|
|
13566
15470
|
if (!existsSync18(skillFile)) continue;
|
|
13567
15471
|
try {
|
|
13568
|
-
const content = await
|
|
13569
|
-
const relPath =
|
|
15472
|
+
const content = await readFile6(skillFile, "utf-8");
|
|
15473
|
+
const relPath = relative4(cwd, skillFile);
|
|
13570
15474
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13571
15475
|
for (const v of violations) {
|
|
13572
15476
|
failures.push({
|
|
@@ -13580,14 +15484,14 @@ async function validateWorkflowPack(cwd) {
|
|
|
13580
15484
|
}
|
|
13581
15485
|
}
|
|
13582
15486
|
}
|
|
13583
|
-
const managedPromptsDir =
|
|
15487
|
+
const managedPromptsDir = resolve4(cwd, ".pourkit/managed/prompts");
|
|
13584
15488
|
if (existsSync18(managedPromptsDir)) {
|
|
13585
15489
|
for (const promptName of catalog.prompts) {
|
|
13586
|
-
const promptFile =
|
|
15490
|
+
const promptFile = resolve4(managedPromptsDir, promptName);
|
|
13587
15491
|
if (!existsSync18(promptFile)) continue;
|
|
13588
15492
|
try {
|
|
13589
|
-
const content = await
|
|
13590
|
-
const relPath =
|
|
15493
|
+
const content = await readFile6(promptFile, "utf-8");
|
|
15494
|
+
const relPath = relative4(cwd, promptFile);
|
|
13591
15495
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13592
15496
|
for (const v of violations) {
|
|
13593
15497
|
failures.push({
|
|
@@ -13602,14 +15506,14 @@ async function validateWorkflowPack(cwd) {
|
|
|
13602
15506
|
}
|
|
13603
15507
|
}
|
|
13604
15508
|
for (const skillName of catalog.releaseAddonSkills) {
|
|
13605
|
-
const addonSkillFile =
|
|
15509
|
+
const addonSkillFile = resolve4(
|
|
13606
15510
|
cwd,
|
|
13607
15511
|
`.pourkit/managed/addons/release/skills/${skillName}/SKILL.md`
|
|
13608
15512
|
);
|
|
13609
15513
|
if (!existsSync18(addonSkillFile)) continue;
|
|
13610
15514
|
try {
|
|
13611
|
-
const content = await
|
|
13612
|
-
const relPath =
|
|
15515
|
+
const content = await readFile6(addonSkillFile, "utf-8");
|
|
15516
|
+
const relPath = relative4(cwd, addonSkillFile);
|
|
13613
15517
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13614
15518
|
for (const v of violations) {
|
|
13615
15519
|
failures.push({
|
|
@@ -13623,14 +15527,14 @@ async function validateWorkflowPack(cwd) {
|
|
|
13623
15527
|
}
|
|
13624
15528
|
}
|
|
13625
15529
|
for (const docName of catalog.releaseAddonDocs) {
|
|
13626
|
-
const addonDocFile =
|
|
15530
|
+
const addonDocFile = resolve4(
|
|
13627
15531
|
cwd,
|
|
13628
15532
|
`.pourkit/managed/addons/release/docs/agents/${docName}`
|
|
13629
15533
|
);
|
|
13630
15534
|
if (!existsSync18(addonDocFile)) continue;
|
|
13631
15535
|
try {
|
|
13632
|
-
const content = await
|
|
13633
|
-
const relPath =
|
|
15536
|
+
const content = await readFile6(addonDocFile, "utf-8");
|
|
15537
|
+
const relPath = relative4(cwd, addonDocFile);
|
|
13634
15538
|
const violations = scanManagedPolicyText(content, relPath);
|
|
13635
15539
|
for (const v of violations) {
|
|
13636
15540
|
failures.push({
|
|
@@ -13660,7 +15564,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
13660
15564
|
ok: icmPath !== null,
|
|
13661
15565
|
detail: icmPath !== null ? icmPath : "icm not found on PATH"
|
|
13662
15566
|
});
|
|
13663
|
-
const gitignorePath =
|
|
15567
|
+
const gitignorePath = resolve4(cwd, ".gitignore");
|
|
13664
15568
|
const gitignoreContent = existsSync18(gitignorePath) ? readFileSync18(gitignorePath, "utf-8") : "";
|
|
13665
15569
|
const hasGitignoreEntry = gitignoreContent.includes(".pourkit/icm/");
|
|
13666
15570
|
checks.push({
|
|
@@ -13668,7 +15572,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
13668
15572
|
ok: hasGitignoreEntry,
|
|
13669
15573
|
detail: hasGitignoreEntry ? ".gitignore covers .pourkit/icm/" : ".gitignore does not cover .pourkit/icm/"
|
|
13670
15574
|
});
|
|
13671
|
-
const memoryDbPath =
|
|
15575
|
+
const memoryDbPath = resolve4(cwd, ".pourkit", "icm", "memories.db");
|
|
13672
15576
|
const hasMemoryDb = existsSync18(memoryDbPath);
|
|
13673
15577
|
checks.push({
|
|
13674
15578
|
name: "icm-db-exists",
|
|
@@ -13682,7 +15586,7 @@ async function validateMemoryHealth(cwd, memoryConfig) {
|
|
|
13682
15586
|
detail: shapeValid ? "memory: { enabled: true, provider: icm }" : `invalid memory config: enabled=${memoryConfig.enabled}, provider=${memoryConfig.provider}`
|
|
13683
15587
|
});
|
|
13684
15588
|
const staleAgentFiles = ["AGENTS.md", "CLAUDE.md"].filter((fileName) => {
|
|
13685
|
-
const filePath =
|
|
15589
|
+
const filePath = resolve4(cwd, fileName);
|
|
13686
15590
|
if (!existsSync18(filePath)) return false;
|
|
13687
15591
|
const content = readFileSync18(filePath, "utf-8");
|
|
13688
15592
|
const managedBlock = extractManagedBlockContent(content);
|
|
@@ -13718,8 +15622,8 @@ function extractManagedBlockContent(content) {
|
|
|
13718
15622
|
async function runDoctorCommand(options) {
|
|
13719
15623
|
const repoRootPath = options.cwd ?? process.cwd();
|
|
13720
15624
|
const schemaDir = localSchemaDir(repoRootPath);
|
|
13721
|
-
const localSchemaPath =
|
|
13722
|
-
const localHashPath =
|
|
15625
|
+
const localSchemaPath = resolve4(schemaDir, "pourkit.schema.json");
|
|
15626
|
+
const localHashPath = resolve4(schemaDir, "pourkit.schema.hash");
|
|
13723
15627
|
const localSchemaExists = existsSync18(localSchemaPath);
|
|
13724
15628
|
const localHashExists = existsSync18(localHashPath);
|
|
13725
15629
|
let packagedHash = null;
|
|
@@ -13730,14 +15634,14 @@ async function runDoctorCommand(options) {
|
|
|
13730
15634
|
let localHashContent = null;
|
|
13731
15635
|
if (localHashExists) {
|
|
13732
15636
|
try {
|
|
13733
|
-
localHashContent = await
|
|
15637
|
+
localHashContent = await readFile6(localHashPath, "utf-8");
|
|
13734
15638
|
} catch {
|
|
13735
15639
|
}
|
|
13736
15640
|
}
|
|
13737
15641
|
const schema = !localSchemaExists ? "missing" : !packagedHash || !localHashContent ? "missing" : localHashContent.trim() === packagedHash.trim() ? "fresh" : "stale";
|
|
13738
15642
|
const hash = !localHashExists ? "missing" : !packagedHash ? "missing" : localHashContent && localHashContent.trim() === packagedHash.trim() ? "fresh" : "stale";
|
|
13739
15643
|
const overall = schema === "fresh" && hash === "fresh" ? "fresh" : schema === "missing" || hash === "missing" ? "missing" : "stale";
|
|
13740
|
-
const configPath =
|
|
15644
|
+
const configPath = resolve4(repoRootPath, ".pourkit/config.json");
|
|
13741
15645
|
let configValidation;
|
|
13742
15646
|
let rawMemoryConfig;
|
|
13743
15647
|
if (existsSync18(configPath)) {
|
|
@@ -13775,7 +15679,7 @@ async function runDoctorCommand(options) {
|
|
|
13775
15679
|
"pourkit.config.mjs",
|
|
13776
15680
|
"pourkit.config.js",
|
|
13777
15681
|
"pourkit.json"
|
|
13778
|
-
].filter((p) => existsSync18(
|
|
15682
|
+
].filter((p) => existsSync18(resolve4(repoRootPath, p)));
|
|
13779
15683
|
const workflowPack = await validateWorkflowPack(repoRootPath);
|
|
13780
15684
|
const memory = await validateMemoryHealth(repoRootPath, rawMemoryConfig);
|
|
13781
15685
|
let recommendation = null;
|
|
@@ -13818,31 +15722,31 @@ async function runDoctorCommand(options) {
|
|
|
13818
15722
|
async function runConfigSyncSchemaCommand(options) {
|
|
13819
15723
|
const repoRootPath = options.cwd ?? process.cwd();
|
|
13820
15724
|
const schemaDir = localSchemaDir(repoRootPath);
|
|
13821
|
-
await
|
|
13822
|
-
const packagedSchema = await
|
|
13823
|
-
const packagedHash = await
|
|
13824
|
-
const localSchemaPath =
|
|
13825
|
-
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");
|
|
13826
15730
|
let schemaWritten = false;
|
|
13827
15731
|
let hashWritten = false;
|
|
13828
15732
|
if (existsSync18(localSchemaPath)) {
|
|
13829
|
-
const existing = await
|
|
15733
|
+
const existing = await readFile6(localSchemaPath, "utf-8");
|
|
13830
15734
|
if (existing !== packagedSchema) {
|
|
13831
|
-
await
|
|
15735
|
+
await writeFile3(localSchemaPath, packagedSchema, "utf-8");
|
|
13832
15736
|
schemaWritten = true;
|
|
13833
15737
|
}
|
|
13834
15738
|
} else {
|
|
13835
|
-
await
|
|
15739
|
+
await writeFile3(localSchemaPath, packagedSchema, "utf-8");
|
|
13836
15740
|
schemaWritten = true;
|
|
13837
15741
|
}
|
|
13838
15742
|
if (existsSync18(localHashPath)) {
|
|
13839
|
-
const existing = await
|
|
15743
|
+
const existing = await readFile6(localHashPath, "utf-8");
|
|
13840
15744
|
if (existing !== packagedHash) {
|
|
13841
|
-
await
|
|
15745
|
+
await writeFile3(localHashPath, packagedHash, "utf-8");
|
|
13842
15746
|
hashWritten = true;
|
|
13843
15747
|
}
|
|
13844
15748
|
} else {
|
|
13845
|
-
await
|
|
15749
|
+
await writeFile3(localHashPath, packagedHash, "utf-8");
|
|
13846
15750
|
hashWritten = true;
|
|
13847
15751
|
}
|
|
13848
15752
|
return {
|
|
@@ -13853,12 +15757,12 @@ async function runConfigSyncSchemaCommand(options) {
|
|
|
13853
15757
|
}
|
|
13854
15758
|
|
|
13855
15759
|
// commands/workflow-pack-sync.ts
|
|
13856
|
-
import { existsSync as existsSync19, readdirSync as
|
|
13857
|
-
import { mkdir as
|
|
13858
|
-
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";
|
|
13859
15763
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13860
15764
|
var __filename4 = fileURLToPath4(import.meta.url);
|
|
13861
|
-
var __dirname4 =
|
|
15765
|
+
var __dirname4 = dirname7(__filename4);
|
|
13862
15766
|
var PROJECT_OWNED_PATHS = [".pourkit/CONTEXT.md", ".pourkit/CONTEXT-MAP.md"];
|
|
13863
15767
|
var PROJECT_OWNED_DIRECTORIES = [
|
|
13864
15768
|
".pourkit/docs/adr",
|
|
@@ -13867,8 +15771,8 @@ var PROJECT_OWNED_DIRECTORIES = [
|
|
|
13867
15771
|
];
|
|
13868
15772
|
var OVERRIDES_DIR = ".pourkit/overrides";
|
|
13869
15773
|
function resolvePackagedManagedPath2(...segments) {
|
|
13870
|
-
const bundledPath =
|
|
13871
|
-
const sourcePath =
|
|
15774
|
+
const bundledPath = resolve5(__dirname4, "managed", ...segments);
|
|
15775
|
+
const sourcePath = resolve5(__dirname4, "..", "managed", ...segments);
|
|
13872
15776
|
const candidates = [bundledPath, sourcePath];
|
|
13873
15777
|
return candidates.find((candidate) => existsSync19(candidate)) ?? bundledPath;
|
|
13874
15778
|
}
|
|
@@ -13892,13 +15796,13 @@ function resolvePackagedManagedPromptPath(promptName) {
|
|
|
13892
15796
|
}
|
|
13893
15797
|
async function readPackagedTextFile2(filePath) {
|
|
13894
15798
|
try {
|
|
13895
|
-
return await
|
|
15799
|
+
return await readFile7(filePath, "utf-8");
|
|
13896
15800
|
} catch {
|
|
13897
15801
|
return null;
|
|
13898
15802
|
}
|
|
13899
15803
|
}
|
|
13900
15804
|
function isPathContained2(parent, child) {
|
|
13901
|
-
const relativePath =
|
|
15805
|
+
const relativePath = relative5(parent, child);
|
|
13902
15806
|
return !relativePath.startsWith("..") && !relativePath.startsWith("../");
|
|
13903
15807
|
}
|
|
13904
15808
|
function isProjectOwnedPath(relativePath) {
|
|
@@ -13949,9 +15853,9 @@ function checkForPathEscape(cwd, resolvedPath) {
|
|
|
13949
15853
|
async function walkDir3(dir) {
|
|
13950
15854
|
const files = [];
|
|
13951
15855
|
try {
|
|
13952
|
-
const entries = await
|
|
15856
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
13953
15857
|
for (const entry of entries) {
|
|
13954
|
-
const full =
|
|
15858
|
+
const full = resolve5(dir, entry.name);
|
|
13955
15859
|
if (entry.isDirectory()) {
|
|
13956
15860
|
files.push(...await walkDir3(full));
|
|
13957
15861
|
} else {
|
|
@@ -13969,28 +15873,28 @@ async function syncFile(cwd, targetRelativePath, sourceContent, errors) {
|
|
|
13969
15873
|
if (sourceContent === null) {
|
|
13970
15874
|
return false;
|
|
13971
15875
|
}
|
|
13972
|
-
const targetPath =
|
|
15876
|
+
const targetPath = resolve5(cwd, targetRelativePath);
|
|
13973
15877
|
const escapeError = checkForPathEscape(cwd, targetPath);
|
|
13974
15878
|
if (escapeError) {
|
|
13975
15879
|
errors.push(escapeError);
|
|
13976
15880
|
return false;
|
|
13977
15881
|
}
|
|
13978
|
-
await
|
|
15882
|
+
await mkdir7(resolve5(targetPath, ".."), { recursive: true });
|
|
13979
15883
|
if (existsSync19(targetPath)) {
|
|
13980
|
-
const existing = await
|
|
15884
|
+
const existing = await readFile7(targetPath, "utf-8");
|
|
13981
15885
|
if (existing === sourceContent) {
|
|
13982
15886
|
return false;
|
|
13983
15887
|
}
|
|
13984
15888
|
}
|
|
13985
|
-
await
|
|
15889
|
+
await writeFile4(targetPath, sourceContent, "utf-8");
|
|
13986
15890
|
return true;
|
|
13987
15891
|
}
|
|
13988
15892
|
function walkDirSync(dir) {
|
|
13989
15893
|
const result = [];
|
|
13990
15894
|
try {
|
|
13991
|
-
const entries =
|
|
15895
|
+
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
13992
15896
|
for (const entry of entries) {
|
|
13993
|
-
const full =
|
|
15897
|
+
const full = resolve5(dir, entry.name);
|
|
13994
15898
|
if (entry.isDirectory()) {
|
|
13995
15899
|
result.push(...walkDirSync(full));
|
|
13996
15900
|
} else {
|
|
@@ -14002,27 +15906,27 @@ function walkDirSync(dir) {
|
|
|
14002
15906
|
return result;
|
|
14003
15907
|
}
|
|
14004
15908
|
async function detectOverrides(cwd) {
|
|
14005
|
-
const overridesDir =
|
|
15909
|
+
const overridesDir = resolve5(cwd, OVERRIDES_DIR);
|
|
14006
15910
|
if (!existsSync19(overridesDir)) {
|
|
14007
15911
|
return [];
|
|
14008
15912
|
}
|
|
14009
15913
|
const files = await walkDir3(overridesDir);
|
|
14010
|
-
return files.map((f) =>
|
|
15914
|
+
return files.map((f) => relative5(cwd, f));
|
|
14011
15915
|
}
|
|
14012
15916
|
async function detectProjectOwnedFiles(cwd) {
|
|
14013
15917
|
const found = [];
|
|
14014
15918
|
for (const ownedPath of PROJECT_OWNED_PATHS) {
|
|
14015
|
-
const fullPath =
|
|
15919
|
+
const fullPath = resolve5(cwd, ownedPath);
|
|
14016
15920
|
if (existsSync19(fullPath)) {
|
|
14017
15921
|
found.push(ownedPath);
|
|
14018
15922
|
}
|
|
14019
15923
|
}
|
|
14020
15924
|
for (const ownedDir of PROJECT_OWNED_DIRECTORIES) {
|
|
14021
|
-
const fullDir =
|
|
15925
|
+
const fullDir = resolve5(cwd, ownedDir);
|
|
14022
15926
|
if (existsSync19(fullDir)) {
|
|
14023
15927
|
const files = await walkDir3(fullDir);
|
|
14024
15928
|
for (const file of files) {
|
|
14025
|
-
found.push(
|
|
15929
|
+
found.push(relative5(cwd, file));
|
|
14026
15930
|
}
|
|
14027
15931
|
}
|
|
14028
15932
|
}
|
|
@@ -14030,8 +15934,8 @@ async function detectProjectOwnedFiles(cwd) {
|
|
|
14030
15934
|
}
|
|
14031
15935
|
async function isReleaseWorkflowEnabled(cwd) {
|
|
14032
15936
|
try {
|
|
14033
|
-
const configPath =
|
|
14034
|
-
const content = await
|
|
15937
|
+
const configPath = resolve5(cwd, ".pourkit", "config.json");
|
|
15938
|
+
const content = await readFile7(configPath, "utf-8");
|
|
14035
15939
|
const parsed = JSON.parse(content);
|
|
14036
15940
|
return parsed.releaseWorkflow?.enabled === true;
|
|
14037
15941
|
} catch {
|
|
@@ -14160,7 +16064,7 @@ async function runWorkflowPackSyncCommand(options) {
|
|
|
14160
16064
|
const schemaResult = await runConfigSyncSchemaCommand({ cwd });
|
|
14161
16065
|
let configMemory;
|
|
14162
16066
|
try {
|
|
14163
|
-
const configPath =
|
|
16067
|
+
const configPath = resolve5(cwd, ".pourkit", "config.json");
|
|
14164
16068
|
const config = await loadConfig(configPath);
|
|
14165
16069
|
if (config.memory?.enabled === true && config.memory?.provider === "icm") {
|
|
14166
16070
|
configMemory = config.memory;
|
|
@@ -14170,16 +16074,16 @@ async function runWorkflowPackSyncCommand(options) {
|
|
|
14170
16074
|
const managedBlockContent = generateManagedBlockContent(configMemory);
|
|
14171
16075
|
const fullManagedBlock = buildManagedBlock(managedBlockContent);
|
|
14172
16076
|
async function syncManagedAgentFile(fileName, options2) {
|
|
14173
|
-
const filePath =
|
|
16077
|
+
const filePath = resolve5(cwd, fileName);
|
|
14174
16078
|
if (!existsSync19(filePath)) {
|
|
14175
16079
|
if (!options2.createIfMissing) return false;
|
|
14176
|
-
await
|
|
16080
|
+
await writeFile4(filePath, fullManagedBlock, "utf-8");
|
|
14177
16081
|
return true;
|
|
14178
16082
|
}
|
|
14179
|
-
const existing = await
|
|
16083
|
+
const existing = await readFile7(filePath, "utf-8");
|
|
14180
16084
|
const newContent = upsertManagedBlock(existing, managedBlockContent);
|
|
14181
16085
|
if (newContent !== existing) {
|
|
14182
|
-
await
|
|
16086
|
+
await writeFile4(filePath, newContent, "utf-8");
|
|
14183
16087
|
return true;
|
|
14184
16088
|
}
|
|
14185
16089
|
return false;
|
|
@@ -14192,11 +16096,11 @@ async function runWorkflowPackSyncCommand(options) {
|
|
|
14192
16096
|
}
|
|
14193
16097
|
const enabledAddons = releaseEnabled ? ["release"] : [];
|
|
14194
16098
|
const manifestMeta = buildWorkflowPackMetadata(enabledAddons);
|
|
14195
|
-
const manifestPath =
|
|
16099
|
+
const manifestPath = resolve5(cwd, ".pourkit", "manifest.json");
|
|
14196
16100
|
let manifestWritten = false;
|
|
14197
16101
|
let previousManifestWorkflowPack = void 0;
|
|
14198
16102
|
try {
|
|
14199
|
-
const existingRaw = await
|
|
16103
|
+
const existingRaw = await readFile7(manifestPath, "utf-8").catch(() => null);
|
|
14200
16104
|
if (existingRaw) {
|
|
14201
16105
|
const existing = JSON.parse(existingRaw);
|
|
14202
16106
|
previousManifestWorkflowPack = existing.workflowPack;
|
|
@@ -14205,13 +16109,13 @@ async function runWorkflowPackSyncCommand(options) {
|
|
|
14205
16109
|
}
|
|
14206
16110
|
if (previousManifestWorkflowPack === void 0 || JSON.stringify(previousManifestWorkflowPack) !== JSON.stringify(manifestMeta)) {
|
|
14207
16111
|
try {
|
|
14208
|
-
const existingRaw = await
|
|
16112
|
+
const existingRaw = await readFile7(manifestPath, "utf-8").catch(
|
|
14209
16113
|
() => null
|
|
14210
16114
|
);
|
|
14211
16115
|
let manifest = existingRaw ? JSON.parse(existingRaw) : {};
|
|
14212
16116
|
manifest.workflowPack = manifestMeta;
|
|
14213
|
-
await
|
|
14214
|
-
await
|
|
16117
|
+
await mkdir7(resolve5(manifestPath, ".."), { recursive: true });
|
|
16118
|
+
await writeFile4(
|
|
14215
16119
|
manifestPath,
|
|
14216
16120
|
JSON.stringify(manifest, null, 2) + "\n",
|
|
14217
16121
|
"utf-8"
|
|
@@ -14260,7 +16164,7 @@ async function syncSkill(skillName, catalog, acc, ctx) {
|
|
|
14260
16164
|
return;
|
|
14261
16165
|
}
|
|
14262
16166
|
for (const sourceFile of skillFiles) {
|
|
14263
|
-
const relPath =
|
|
16167
|
+
const relPath = relative5(skillDir, sourceFile);
|
|
14264
16168
|
const targetRelativePath = `.pourkit/managed/skills/${skillName}/${relPath}`;
|
|
14265
16169
|
if (isProjectOwnedPath(targetRelativePath)) {
|
|
14266
16170
|
skippedProjectOwned.push(targetRelativePath);
|
|
@@ -14336,7 +16240,7 @@ async function syncAddonSkill(skillName, acc, ctx) {
|
|
|
14336
16240
|
return;
|
|
14337
16241
|
}
|
|
14338
16242
|
for (const sourceFile of skillFiles) {
|
|
14339
|
-
const relPath =
|
|
16243
|
+
const relPath = relative5(addonSkillDir, sourceFile);
|
|
14340
16244
|
const targetRelativePath = `.pourkit/managed/addons/release/skills/${skillName}/${relPath}`;
|
|
14341
16245
|
if (isProjectOwnedPath(targetRelativePath)) {
|
|
14342
16246
|
skippedProjectOwned.push(targetRelativePath);
|
|
@@ -14417,6 +16321,16 @@ var GitHubIssueProvider = class {
|
|
|
14417
16321
|
});
|
|
14418
16322
|
return { number: data.number, url: data.html_url, title: data.title };
|
|
14419
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
|
+
}
|
|
14420
16334
|
async fetchIssue(number) {
|
|
14421
16335
|
const { data } = await this.client.octokit.rest.issues.get({
|
|
14422
16336
|
owner: this.client.owner,
|
|
@@ -14939,21 +16853,21 @@ init_common();
|
|
|
14939
16853
|
|
|
14940
16854
|
// execution/sandcastle-execution.ts
|
|
14941
16855
|
import { existsSync as existsSync21, mkdirSync as mkdirSync9, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
14942
|
-
import { join as
|
|
16856
|
+
import { join as join23 } from "path";
|
|
14943
16857
|
import { createWorktree, opencode } from "@ai-hero/sandcastle";
|
|
14944
16858
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
14945
16859
|
|
|
14946
16860
|
// execution/execution-provider.ts
|
|
14947
16861
|
init_common();
|
|
14948
16862
|
import { mkdtempSync } from "fs";
|
|
14949
|
-
import { writeFile as
|
|
16863
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
14950
16864
|
import { tmpdir } from "os";
|
|
14951
|
-
import { dirname as
|
|
16865
|
+
import { dirname as dirname8, join as join21 } from "path";
|
|
14952
16866
|
async function writeExecutionArtifacts(worktreePath, artifacts) {
|
|
14953
16867
|
for (const artifact of artifacts) {
|
|
14954
|
-
const filePath =
|
|
14955
|
-
await ensureDir(
|
|
14956
|
-
await
|
|
16868
|
+
const filePath = join21(worktreePath, artifact.path);
|
|
16869
|
+
await ensureDir(dirname8(filePath));
|
|
16870
|
+
await writeFile5(filePath, artifact.content, "utf-8");
|
|
14957
16871
|
}
|
|
14958
16872
|
}
|
|
14959
16873
|
|
|
@@ -15013,7 +16927,7 @@ async function createSandboxFromExistingWorktree(options) {
|
|
|
15013
16927
|
}
|
|
15014
16928
|
|
|
15015
16929
|
// execution/sandbox-options.ts
|
|
15016
|
-
import { join as
|
|
16930
|
+
import { join as join22 } from "path";
|
|
15017
16931
|
var POURKIT_ICM_CONTAINER_DIR = "/home/agent/.pourkit/icm";
|
|
15018
16932
|
function buildSandboxOptions(repoRoot2, sandbox, memory) {
|
|
15019
16933
|
const mounts = [];
|
|
@@ -15022,12 +16936,12 @@ function buildSandboxOptions(repoRoot2, sandbox, memory) {
|
|
|
15022
16936
|
}
|
|
15023
16937
|
if (memory?.available) {
|
|
15024
16938
|
mounts.push({
|
|
15025
|
-
hostPath:
|
|
16939
|
+
hostPath: join22(repoRoot2, ".pourkit", "icm"),
|
|
15026
16940
|
sandboxPath: POURKIT_ICM_CONTAINER_DIR,
|
|
15027
16941
|
readonly: false
|
|
15028
16942
|
});
|
|
15029
16943
|
mounts.push({
|
|
15030
|
-
hostPath:
|
|
16944
|
+
hostPath: join22(repoRoot2, ".pourkit", "icm", "cache"),
|
|
15031
16945
|
sandboxPath: "/home/agent/.cache/icm",
|
|
15032
16946
|
readonly: false
|
|
15033
16947
|
});
|
|
@@ -15161,13 +17075,13 @@ var SandcastleExecutionSession = class {
|
|
|
15161
17075
|
"Memory enabled but memory context is invalid. Run `pourkit memory init` or install ICM for host planning use."
|
|
15162
17076
|
);
|
|
15163
17077
|
}
|
|
15164
|
-
const hostMemoryDir =
|
|
17078
|
+
const hostMemoryDir = join23(repoRoot2, ".pourkit", "icm");
|
|
15165
17079
|
if (!existsSync21(hostMemoryDir) || !statSync2(hostMemoryDir).isDirectory()) {
|
|
15166
17080
|
throw new MissingHostMemoryDirError(
|
|
15167
17081
|
"Memory enabled but host .pourkit/icm/ directory is missing. Run `pourkit memory init` to create the repo-scoped memory store."
|
|
15168
17082
|
);
|
|
15169
17083
|
}
|
|
15170
|
-
mkdirSync9(
|
|
17084
|
+
mkdirSync9(join23(hostMemoryDir, "cache"), { recursive: true });
|
|
15171
17085
|
}
|
|
15172
17086
|
const sandboxOptions = buildSandboxOptions(
|
|
15173
17087
|
repoRoot2,
|
|
@@ -15326,7 +17240,7 @@ function sanitizeBranch(branchName) {
|
|
|
15326
17240
|
}
|
|
15327
17241
|
function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
15328
17242
|
return paths.filter((relativePath) => {
|
|
15329
|
-
const source =
|
|
17243
|
+
const source = join23(repoRoot2, relativePath);
|
|
15330
17244
|
if (!existsSync21(source)) {
|
|
15331
17245
|
return true;
|
|
15332
17246
|
}
|
|
@@ -15337,7 +17251,7 @@ function safeCopyToWorktreePaths(paths, repoRoot2, worktreePath) {
|
|
|
15337
17251
|
} catch {
|
|
15338
17252
|
return true;
|
|
15339
17253
|
}
|
|
15340
|
-
const destination =
|
|
17254
|
+
const destination = join23(worktreePath, relativePath);
|
|
15341
17255
|
return !existsSync21(destination);
|
|
15342
17256
|
});
|
|
15343
17257
|
}
|
|
@@ -15402,17 +17316,17 @@ function isPlainObject(value) {
|
|
|
15402
17316
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15403
17317
|
}
|
|
15404
17318
|
function savePromptToFile(repoRoot2, stage, iteration, prompt) {
|
|
15405
|
-
const promptsDir =
|
|
17319
|
+
const promptsDir = join23(repoRoot2, ".pourkit", ".tmp", "prompts");
|
|
15406
17320
|
mkdirSync9(promptsDir, { recursive: true });
|
|
15407
17321
|
const timestamp2 = Date.now();
|
|
15408
17322
|
const iterationSuffix = iteration !== void 0 ? `-iteration-${iteration}` : "";
|
|
15409
17323
|
const filename = `${stage}${iterationSuffix}-${timestamp2}.md`;
|
|
15410
|
-
const filePath =
|
|
17324
|
+
const filePath = join23(promptsDir, filename);
|
|
15411
17325
|
writeFileSync6(filePath, prompt, "utf-8");
|
|
15412
17326
|
}
|
|
15413
17327
|
|
|
15414
17328
|
// cli.ts
|
|
15415
|
-
function
|
|
17329
|
+
function normalizePrdRef2(ref) {
|
|
15416
17330
|
const normalized = ref.trim().toUpperCase();
|
|
15417
17331
|
if (!/^PRD-\d+$/.test(normalized)) {
|
|
15418
17332
|
throw new Error(
|
|
@@ -15477,6 +17391,7 @@ function createCliProgram(version) {
|
|
|
15477
17391
|
const program = new Command();
|
|
15478
17392
|
program.name("pourkit").version(version).exitOverride().description("AI-driven issue-to-PR workflow for GitHub repositories.");
|
|
15479
17393
|
const issueCommand = program.command("issue");
|
|
17394
|
+
const prdPlanWorkspaceCommand = program.command("prd-plan-workspace").description("PRD Plan Workspace commands");
|
|
15480
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) => {
|
|
15481
17396
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15482
17397
|
const logPath = path8.join(
|
|
@@ -15508,7 +17423,7 @@ function createCliProgram(version) {
|
|
|
15508
17423
|
});
|
|
15509
17424
|
program.command("validate-artifact").description("Validate an agent handoff artifact").argument(
|
|
15510
17425
|
"<kind>",
|
|
15511
|
-
"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"
|
|
15512
17427
|
).argument("<artifactPath>", "artifact path to validate").argument("[extraPaths...]", "unused compatibility argument").option("--iteration <number>", "review/refactor iteration", (value) => {
|
|
15513
17428
|
const parsed = Number.parseInt(value, 10);
|
|
15514
17429
|
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
@@ -15561,7 +17476,13 @@ function createCliProgram(version) {
|
|
|
15561
17476
|
).option(
|
|
15562
17477
|
"--base-ref <ref>",
|
|
15563
17478
|
"canonical base ref for issue-final-review changedFiles coverage validation"
|
|
15564
|
-
).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(
|
|
15565
17486
|
(kind, artifactPath, _extraPaths, options) => {
|
|
15566
17487
|
const allowedKinds = /* @__PURE__ */ new Set([
|
|
15567
17488
|
"reviewer",
|
|
@@ -15569,7 +17490,10 @@ function createCliProgram(version) {
|
|
|
15569
17490
|
"finalizer",
|
|
15570
17491
|
"conflict-resolution",
|
|
15571
17492
|
"failure-resolution",
|
|
15572
|
-
"issue-final-review"
|
|
17493
|
+
"issue-final-review",
|
|
17494
|
+
"prd-plan",
|
|
17495
|
+
"prd-child-issue",
|
|
17496
|
+
"prd-plan-workspace"
|
|
15573
17497
|
]);
|
|
15574
17498
|
if (!allowedKinds.has(kind)) {
|
|
15575
17499
|
throw new CommanderError(
|
|
@@ -15591,7 +17515,10 @@ function createCliProgram(version) {
|
|
|
15591
17515
|
issueNumber: options.issueNumber,
|
|
15592
17516
|
branchName: options.branchName,
|
|
15593
17517
|
baseRef: options.baseRef,
|
|
15594
|
-
checkConflictMarkers: options.checkConflictMarkers
|
|
17518
|
+
checkConflictMarkers: options.checkConflictMarkers,
|
|
17519
|
+
prdRef: options.prdRef,
|
|
17520
|
+
workspace: options.workspace,
|
|
17521
|
+
childId: options.childId
|
|
15595
17522
|
});
|
|
15596
17523
|
console.log(JSON.stringify(result, null, 2));
|
|
15597
17524
|
if (!result.ok) {
|
|
@@ -15599,6 +17526,40 @@ function createCliProgram(version) {
|
|
|
15599
17526
|
}
|
|
15600
17527
|
}
|
|
15601
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
|
+
);
|
|
15602
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(
|
|
15603
17564
|
"--label <label>",
|
|
15604
17565
|
"label to apply (repeatable)",
|
|
@@ -15738,7 +17699,7 @@ function createCliProgram(version) {
|
|
|
15738
17699
|
);
|
|
15739
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(
|
|
15740
17701
|
async (options) => {
|
|
15741
|
-
const prdRef = options.prd ?
|
|
17702
|
+
const prdRef = options.prd ? normalizePrdRef2(options.prd) : void 0;
|
|
15742
17703
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15743
17704
|
const config = await loadRepoConfig(targetRepoRoot);
|
|
15744
17705
|
const logPath = path8.join(
|
|
@@ -15806,7 +17767,7 @@ function createCliProgram(version) {
|
|
|
15806
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(
|
|
15807
17768
|
async (prdRef, options) => {
|
|
15808
17769
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15809
|
-
const normalizedPrdRef =
|
|
17770
|
+
const normalizedPrdRef = normalizePrdRef2(prdRef);
|
|
15810
17771
|
const logPath = path8.join(
|
|
15811
17772
|
targetRepoRoot,
|
|
15812
17773
|
".pourkit",
|
|
@@ -15852,7 +17813,7 @@ function createCliProgram(version) {
|
|
|
15852
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(
|
|
15853
17814
|
async (prdRef, options) => {
|
|
15854
17815
|
const targetRepoRoot = options.cwd ? repoRoot(options.cwd) : repoRoot();
|
|
15855
|
-
const normalizedPrdRef =
|
|
17816
|
+
const normalizedPrdRef = normalizePrdRef2(prdRef);
|
|
15856
17817
|
const logPath = path8.join(
|
|
15857
17818
|
targetRepoRoot,
|
|
15858
17819
|
".pourkit",
|
|
@@ -16128,11 +18089,11 @@ function createCliProgram(version) {
|
|
|
16128
18089
|
return program;
|
|
16129
18090
|
}
|
|
16130
18091
|
async function resolveCliVersion() {
|
|
16131
|
-
if (isPackageVersion("0.0.0-next-
|
|
16132
|
-
return "0.0.0-next-
|
|
18092
|
+
if (isPackageVersion("0.0.0-next-20260627024509")) {
|
|
18093
|
+
return "0.0.0-next-20260627024509";
|
|
16133
18094
|
}
|
|
16134
|
-
if (isReleaseVersion("0.0.0-next-
|
|
16135
|
-
return "0.0.0-next-
|
|
18095
|
+
if (isReleaseVersion("0.0.0-next-20260627024509")) {
|
|
18096
|
+
return "0.0.0-next-20260627024509";
|
|
16136
18097
|
}
|
|
16137
18098
|
try {
|
|
16138
18099
|
const root = repoRoot();
|