@delorenj/pjangler 1.2.13 → 1.2.14
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/index.js +305 -7
- package/dist/mcp-server.js +305 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2121,6 +2121,7 @@ import { basename as basename3, dirname as dirname6, join as join11, relative, r
|
|
|
2121
2121
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2122
2122
|
import { homedir as homedir5 } from "node:os";
|
|
2123
2123
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
2124
|
+
import YAML2 from "yaml";
|
|
2124
2125
|
var LINK_AGENTFILES_BLOCK = `# This block will handle the linking of
|
|
2125
2126
|
# agent files to the main AGENTS.md file.
|
|
2126
2127
|
#
|
|
@@ -2890,6 +2891,173 @@ function checkUnit(unit) {
|
|
|
2890
2891
|
const active = systemctlUser(["is-active", unit]).ok;
|
|
2891
2892
|
return { enabled, active };
|
|
2892
2893
|
}
|
|
2894
|
+
var BMAD_NPM_PACKAGE = "bmad-method";
|
|
2895
|
+
var BMAD_TARGET_CHANNEL = "next";
|
|
2896
|
+
var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
|
|
2897
|
+
var BMAD_INSTALL_TOOLS = [
|
|
2898
|
+
"claude-code",
|
|
2899
|
+
"codex",
|
|
2900
|
+
"cursor",
|
|
2901
|
+
"github-copilot",
|
|
2902
|
+
"adal",
|
|
2903
|
+
"antigravity-cli",
|
|
2904
|
+
"auggie",
|
|
2905
|
+
"goose",
|
|
2906
|
+
"cline",
|
|
2907
|
+
"codebuddy",
|
|
2908
|
+
"codewhale",
|
|
2909
|
+
"command-code",
|
|
2910
|
+
"crush",
|
|
2911
|
+
"droid",
|
|
2912
|
+
"firebender",
|
|
2913
|
+
"gemini",
|
|
2914
|
+
"antigravity",
|
|
2915
|
+
"hermes",
|
|
2916
|
+
"bob",
|
|
2917
|
+
"iflow",
|
|
2918
|
+
"junie",
|
|
2919
|
+
"kilo",
|
|
2920
|
+
"kimi-code",
|
|
2921
|
+
"kiro",
|
|
2922
|
+
"kode",
|
|
2923
|
+
"mistral-vibe",
|
|
2924
|
+
"mux",
|
|
2925
|
+
"neovate",
|
|
2926
|
+
"ona",
|
|
2927
|
+
"openclaw",
|
|
2928
|
+
"opencode",
|
|
2929
|
+
"openhands",
|
|
2930
|
+
"pi",
|
|
2931
|
+
"pochi",
|
|
2932
|
+
"qoder",
|
|
2933
|
+
"qwen",
|
|
2934
|
+
"replit",
|
|
2935
|
+
"roo",
|
|
2936
|
+
"rovo-dev",
|
|
2937
|
+
"cortex",
|
|
2938
|
+
"amp",
|
|
2939
|
+
"trae",
|
|
2940
|
+
"warp",
|
|
2941
|
+
"windsurf",
|
|
2942
|
+
"zencoder"
|
|
2943
|
+
];
|
|
2944
|
+
function bmadInstallArgs(repoRoot) {
|
|
2945
|
+
return [
|
|
2946
|
+
"-y",
|
|
2947
|
+
`${BMAD_NPM_PACKAGE}@${BMAD_TARGET_CHANNEL}`,
|
|
2948
|
+
"install",
|
|
2949
|
+
"--yes",
|
|
2950
|
+
"--directory",
|
|
2951
|
+
repoRoot,
|
|
2952
|
+
"--modules",
|
|
2953
|
+
"bmm,bmb,cis",
|
|
2954
|
+
"--tools",
|
|
2955
|
+
BMAD_INSTALL_TOOLS.join(",")
|
|
2956
|
+
];
|
|
2957
|
+
}
|
|
2958
|
+
function runBmadInstall(repoRoot) {
|
|
2959
|
+
const result = spawnSync6("npx", bmadInstallArgs(repoRoot), { encoding: "utf8" });
|
|
2960
|
+
if (result.status !== 0) {
|
|
2961
|
+
return { ok: false, error: result.stderr || result.error?.message || "Unknown error" };
|
|
2962
|
+
}
|
|
2963
|
+
return { ok: true };
|
|
2964
|
+
}
|
|
2965
|
+
function readInstalledBmadVersion(repoRoot) {
|
|
2966
|
+
const raw = safeReadText(join11(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
2967
|
+
if (!raw) return void 0;
|
|
2968
|
+
try {
|
|
2969
|
+
const parsed = YAML2.parse(raw);
|
|
2970
|
+
const version = parsed?.installation?.version;
|
|
2971
|
+
return typeof version === "string" && version.trim() ? version.trim() : void 0;
|
|
2972
|
+
} catch {
|
|
2973
|
+
return void 0;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
function bmadCachePath(homeDir) {
|
|
2977
|
+
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join11(homeDir, ".cache");
|
|
2978
|
+
return join11(cacheRoot, "pjangler", "bmad-dist-tags.json");
|
|
2979
|
+
}
|
|
2980
|
+
function readBmadDistTagsCache(homeDir) {
|
|
2981
|
+
const raw = safeReadText(bmadCachePath(homeDir));
|
|
2982
|
+
if (!raw) return void 0;
|
|
2983
|
+
try {
|
|
2984
|
+
const parsed = JSON.parse(raw);
|
|
2985
|
+
if (parsed && typeof parsed.fetchedAt === "number" && parsed.distTags && typeof parsed.distTags === "object") {
|
|
2986
|
+
return parsed;
|
|
2987
|
+
}
|
|
2988
|
+
} catch {
|
|
2989
|
+
}
|
|
2990
|
+
return void 0;
|
|
2991
|
+
}
|
|
2992
|
+
function fetchBmadDistTags() {
|
|
2993
|
+
const result = spawnSync6("npm", ["view", BMAD_NPM_PACKAGE, "dist-tags", "--json"], {
|
|
2994
|
+
encoding: "utf8",
|
|
2995
|
+
timeout: 8e3
|
|
2996
|
+
});
|
|
2997
|
+
if (result.status !== 0 || !result.stdout.trim()) return void 0;
|
|
2998
|
+
try {
|
|
2999
|
+
const parsed = JSON.parse(result.stdout);
|
|
3000
|
+
const obj = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
3001
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
3002
|
+
const tags = {};
|
|
3003
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3004
|
+
if (typeof value === "string") tags[key] = value;
|
|
3005
|
+
}
|
|
3006
|
+
return Object.keys(tags).length ? tags : void 0;
|
|
3007
|
+
} catch {
|
|
3008
|
+
return void 0;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
function resolveBmadDistTags(homeDir) {
|
|
3012
|
+
const cached = readBmadDistTagsCache(homeDir);
|
|
3013
|
+
if (cached && Date.now() - cached.fetchedAt < BMAD_DIST_TAGS_TTL_MS) {
|
|
3014
|
+
return { distTags: cached.distTags, stale: false };
|
|
3015
|
+
}
|
|
3016
|
+
const fetched = fetchBmadDistTags();
|
|
3017
|
+
if (fetched) {
|
|
3018
|
+
try {
|
|
3019
|
+
const path = bmadCachePath(homeDir);
|
|
3020
|
+
mkdirSync6(dirname6(path), { recursive: true });
|
|
3021
|
+
writeFileSync6(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
|
|
3022
|
+
} catch {
|
|
3023
|
+
}
|
|
3024
|
+
return { distTags: fetched, stale: false };
|
|
3025
|
+
}
|
|
3026
|
+
if (cached) return { distTags: cached.distTags, stale: true };
|
|
3027
|
+
return void 0;
|
|
3028
|
+
}
|
|
3029
|
+
function compareBmadVersions(a, b) {
|
|
3030
|
+
const parse = (v) => {
|
|
3031
|
+
const [core = "0", pre = ""] = v.replace(/^v/, "").split("-", 2);
|
|
3032
|
+
const parts = core.split(".");
|
|
3033
|
+
const n = (i) => parseInt(parts[i] ?? "0", 10) || 0;
|
|
3034
|
+
return { nums: [n(0), n(1), n(2)], pre };
|
|
3035
|
+
};
|
|
3036
|
+
const pa = parse(a);
|
|
3037
|
+
const pb = parse(b);
|
|
3038
|
+
for (let i = 0; i < 3; i++) {
|
|
3039
|
+
if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] - pb.nums[i];
|
|
3040
|
+
}
|
|
3041
|
+
if (pa.pre === pb.pre) return 0;
|
|
3042
|
+
if (!pa.pre) return 1;
|
|
3043
|
+
if (!pb.pre) return -1;
|
|
3044
|
+
const ida = pa.pre.split(".");
|
|
3045
|
+
const idb = pb.pre.split(".");
|
|
3046
|
+
for (let i = 0; i < Math.max(ida.length, idb.length); i++) {
|
|
3047
|
+
const xa = ida[i];
|
|
3048
|
+
const xb = idb[i];
|
|
3049
|
+
if (xa === void 0) return -1;
|
|
3050
|
+
if (xb === void 0) return 1;
|
|
3051
|
+
const na = Number(xa);
|
|
3052
|
+
const nb = Number(xb);
|
|
3053
|
+
if (!Number.isNaN(na) && !Number.isNaN(nb)) {
|
|
3054
|
+
if (na !== nb) return na - nb;
|
|
3055
|
+
} else if (xa !== xb) {
|
|
3056
|
+
return xa < xb ? -1 : 1;
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
return 0;
|
|
3060
|
+
}
|
|
2893
3061
|
var RULES = [
|
|
2894
3062
|
{
|
|
2895
3063
|
id: "mise.config-root",
|
|
@@ -3254,15 +3422,14 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
3254
3422
|
id: "bmad.scaffold",
|
|
3255
3423
|
title: "BMAD modules/docs scaffold",
|
|
3256
3424
|
audit: (ctx) => {
|
|
3257
|
-
const sourceRoot = join11(ctx.pjanglerRoot, "templates", "commonproject", "_bmad");
|
|
3258
3425
|
const targetRoot = join11(ctx.repoRoot, "_bmad");
|
|
3259
3426
|
const sentinels = [
|
|
3260
3427
|
join11("core", "config.yaml"),
|
|
3261
|
-
join11("
|
|
3262
|
-
join11("
|
|
3263
|
-
join11("bmm", "
|
|
3428
|
+
join11("config.toml"),
|
|
3429
|
+
join11("_config", "manifest.yaml"),
|
|
3430
|
+
join11("bmm", "config.yaml")
|
|
3264
3431
|
];
|
|
3265
|
-
const missing = sentinels.filter((file) =>
|
|
3432
|
+
const missing = sentinels.filter((file) => !existsSync9(join11(targetRoot, file)));
|
|
3266
3433
|
return {
|
|
3267
3434
|
id: "bmad.scaffold",
|
|
3268
3435
|
title: "BMAD modules/docs scaffold",
|
|
@@ -3274,17 +3441,148 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
3274
3441
|
},
|
|
3275
3442
|
migrate: (ctx, finding) => {
|
|
3276
3443
|
const changedFiles = [];
|
|
3277
|
-
|
|
3444
|
+
if (ctx.dryRun) {
|
|
3445
|
+
for (const detail of finding.details) {
|
|
3446
|
+
changedFiles.push(join11(ctx.repoRoot, detail));
|
|
3447
|
+
}
|
|
3448
|
+
return {
|
|
3449
|
+
id: finding.id,
|
|
3450
|
+
title: finding.title,
|
|
3451
|
+
status: changedFiles.length ? "applied" : "noop",
|
|
3452
|
+
summary: changedFiles.length ? "Would run non-interactive bmad-method install" : "No changes required",
|
|
3453
|
+
changedFiles,
|
|
3454
|
+
details: [
|
|
3455
|
+
`Would run: npx ${bmadInstallArgs(ctx.repoRoot).join(" ").replace(BMAD_INSTALL_TOOLS.join(","), "...")}`
|
|
3456
|
+
]
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
const install = runBmadInstall(ctx.repoRoot);
|
|
3460
|
+
if (!install.ok) {
|
|
3461
|
+
return {
|
|
3462
|
+
id: finding.id,
|
|
3463
|
+
title: finding.title,
|
|
3464
|
+
status: "blocked",
|
|
3465
|
+
summary: `Failed to run bmad-method install`,
|
|
3466
|
+
changedFiles: [],
|
|
3467
|
+
details: [install.error ?? "Unknown error"]
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
for (const detail of finding.details) {
|
|
3471
|
+
if (existsSync9(join11(ctx.repoRoot, detail))) {
|
|
3472
|
+
changedFiles.push(join11(ctx.repoRoot, detail));
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3278
3475
|
return {
|
|
3279
3476
|
id: finding.id,
|
|
3280
3477
|
title: finding.title,
|
|
3281
3478
|
status: changedFiles.length ? "applied" : "noop",
|
|
3282
|
-
summary: changedFiles.length ? "
|
|
3479
|
+
summary: changedFiles.length ? "Installed BMAD scaffold via non-interactive installer" : "No changes required",
|
|
3283
3480
|
changedFiles,
|
|
3284
3481
|
details: []
|
|
3285
3482
|
};
|
|
3286
3483
|
}
|
|
3287
3484
|
},
|
|
3485
|
+
{
|
|
3486
|
+
id: "bmad.version",
|
|
3487
|
+
title: "BMAD version currency",
|
|
3488
|
+
audit: (ctx) => {
|
|
3489
|
+
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
3490
|
+
if (!installed) {
|
|
3491
|
+
return {
|
|
3492
|
+
id: "bmad.version",
|
|
3493
|
+
title: "BMAD version currency",
|
|
3494
|
+
status: "skip",
|
|
3495
|
+
summary: existsSync9(join11(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
|
|
3496
|
+
details: [],
|
|
3497
|
+
fixable: false
|
|
3498
|
+
};
|
|
3499
|
+
}
|
|
3500
|
+
const resolved = resolveBmadDistTags(ctx.homeDir);
|
|
3501
|
+
const available = resolved?.distTags?.[BMAD_TARGET_CHANNEL];
|
|
3502
|
+
if (!available) {
|
|
3503
|
+
return {
|
|
3504
|
+
id: "bmad.version",
|
|
3505
|
+
title: "BMAD version currency",
|
|
3506
|
+
status: "skip",
|
|
3507
|
+
summary: `BMAD ${installed} installed; latest ${BMAD_TARGET_CHANNEL} version unknown (npm unreachable)`,
|
|
3508
|
+
details: [`Could not resolve ${BMAD_NPM_PACKAGE}@${BMAD_TARGET_CHANNEL} from npm`],
|
|
3509
|
+
fixable: false
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
const staleNote = resolved.stale ? ` ${glyph.dot} cached` : "";
|
|
3513
|
+
if (compareBmadVersions(installed, available) >= 0) {
|
|
3514
|
+
return {
|
|
3515
|
+
id: "bmad.version",
|
|
3516
|
+
title: "BMAD version currency",
|
|
3517
|
+
status: "pass",
|
|
3518
|
+
summary: `BMAD ${installed} is current (${BMAD_TARGET_CHANNEL} ${available})${staleNote}`,
|
|
3519
|
+
details: [],
|
|
3520
|
+
fixable: false
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
return {
|
|
3524
|
+
id: "bmad.version",
|
|
3525
|
+
title: "BMAD version currency",
|
|
3526
|
+
status: "warn",
|
|
3527
|
+
summary: `BMAD ${installed} is behind ${BMAD_TARGET_CHANNEL} ${available} \u2014 upgrade available`,
|
|
3528
|
+
details: [
|
|
3529
|
+
`installed: ${installed}`,
|
|
3530
|
+
`available: ${available} (${BMAD_NPM_PACKAGE}@${BMAD_TARGET_CHANNEL})`,
|
|
3531
|
+
resolved.distTags.latest ? `stable latest: ${resolved.distTags.latest}` : "",
|
|
3532
|
+
"run `pj migrate bmad.version` to upgrade"
|
|
3533
|
+
].filter(Boolean),
|
|
3534
|
+
fixable: true
|
|
3535
|
+
};
|
|
3536
|
+
},
|
|
3537
|
+
migrate: (ctx, finding) => {
|
|
3538
|
+
if (finding.status !== "warn") {
|
|
3539
|
+
return {
|
|
3540
|
+
id: finding.id,
|
|
3541
|
+
title: finding.title,
|
|
3542
|
+
status: "noop",
|
|
3543
|
+
summary: finding.status === "skip" ? finding.summary : "BMAD already current",
|
|
3544
|
+
changedFiles: [],
|
|
3545
|
+
details: []
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
3549
|
+
const available = resolveBmadDistTags(ctx.homeDir)?.distTags?.[BMAD_TARGET_CHANNEL];
|
|
3550
|
+
const manifestPath = join11(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
3551
|
+
if (ctx.dryRun) {
|
|
3552
|
+
return {
|
|
3553
|
+
id: finding.id,
|
|
3554
|
+
title: finding.title,
|
|
3555
|
+
status: "applied",
|
|
3556
|
+
summary: `Would upgrade BMAD ${installed ?? "?"} -> ${available ?? BMAD_TARGET_CHANNEL}`,
|
|
3557
|
+
changedFiles: [manifestPath],
|
|
3558
|
+
details: [
|
|
3559
|
+
`Would run: npx ${bmadInstallArgs(ctx.repoRoot).join(" ").replace(BMAD_INSTALL_TOOLS.join(","), "...")}`
|
|
3560
|
+
]
|
|
3561
|
+
};
|
|
3562
|
+
}
|
|
3563
|
+
const install = runBmadInstall(ctx.repoRoot);
|
|
3564
|
+
if (!install.ok) {
|
|
3565
|
+
return {
|
|
3566
|
+
id: finding.id,
|
|
3567
|
+
title: finding.title,
|
|
3568
|
+
status: "blocked",
|
|
3569
|
+
summary: "Failed to upgrade BMAD via installer",
|
|
3570
|
+
changedFiles: [],
|
|
3571
|
+
details: [install.error ?? "Unknown error"]
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
const nowInstalled = readInstalledBmadVersion(ctx.repoRoot);
|
|
3575
|
+
const upgraded = Boolean(nowInstalled && installed && compareBmadVersions(nowInstalled, installed) > 0);
|
|
3576
|
+
return {
|
|
3577
|
+
id: finding.id,
|
|
3578
|
+
title: finding.title,
|
|
3579
|
+
status: upgraded ? "applied" : "noop",
|
|
3580
|
+
summary: upgraded ? `Upgraded BMAD ${installed} -> ${nowInstalled}` : `BMAD reinstalled (${nowInstalled ?? "?"})`,
|
|
3581
|
+
changedFiles: upgraded ? [manifestPath] : [],
|
|
3582
|
+
details: []
|
|
3583
|
+
};
|
|
3584
|
+
}
|
|
3585
|
+
},
|
|
3288
3586
|
{
|
|
3289
3587
|
id: "hermes.pm-scaffold",
|
|
3290
3588
|
title: "Hermes PM scaffold parity",
|
package/dist/mcp-server.js
CHANGED
|
@@ -2048,6 +2048,7 @@ import { basename as basename3, dirname as dirname7, join as join12, relative, r
|
|
|
2048
2048
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
2049
2049
|
import { homedir as homedir5 } from "node:os";
|
|
2050
2050
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
2051
|
+
import YAML2 from "yaml";
|
|
2051
2052
|
var LINK_AGENTFILES_BLOCK = `# This block will handle the linking of
|
|
2052
2053
|
# agent files to the main AGENTS.md file.
|
|
2053
2054
|
#
|
|
@@ -2817,6 +2818,173 @@ function checkUnit(unit) {
|
|
|
2817
2818
|
const active = systemctlUser(["is-active", unit]).ok;
|
|
2818
2819
|
return { enabled, active };
|
|
2819
2820
|
}
|
|
2821
|
+
var BMAD_NPM_PACKAGE = "bmad-method";
|
|
2822
|
+
var BMAD_TARGET_CHANNEL = "next";
|
|
2823
|
+
var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
|
|
2824
|
+
var BMAD_INSTALL_TOOLS = [
|
|
2825
|
+
"claude-code",
|
|
2826
|
+
"codex",
|
|
2827
|
+
"cursor",
|
|
2828
|
+
"github-copilot",
|
|
2829
|
+
"adal",
|
|
2830
|
+
"antigravity-cli",
|
|
2831
|
+
"auggie",
|
|
2832
|
+
"goose",
|
|
2833
|
+
"cline",
|
|
2834
|
+
"codebuddy",
|
|
2835
|
+
"codewhale",
|
|
2836
|
+
"command-code",
|
|
2837
|
+
"crush",
|
|
2838
|
+
"droid",
|
|
2839
|
+
"firebender",
|
|
2840
|
+
"gemini",
|
|
2841
|
+
"antigravity",
|
|
2842
|
+
"hermes",
|
|
2843
|
+
"bob",
|
|
2844
|
+
"iflow",
|
|
2845
|
+
"junie",
|
|
2846
|
+
"kilo",
|
|
2847
|
+
"kimi-code",
|
|
2848
|
+
"kiro",
|
|
2849
|
+
"kode",
|
|
2850
|
+
"mistral-vibe",
|
|
2851
|
+
"mux",
|
|
2852
|
+
"neovate",
|
|
2853
|
+
"ona",
|
|
2854
|
+
"openclaw",
|
|
2855
|
+
"opencode",
|
|
2856
|
+
"openhands",
|
|
2857
|
+
"pi",
|
|
2858
|
+
"pochi",
|
|
2859
|
+
"qoder",
|
|
2860
|
+
"qwen",
|
|
2861
|
+
"replit",
|
|
2862
|
+
"roo",
|
|
2863
|
+
"rovo-dev",
|
|
2864
|
+
"cortex",
|
|
2865
|
+
"amp",
|
|
2866
|
+
"trae",
|
|
2867
|
+
"warp",
|
|
2868
|
+
"windsurf",
|
|
2869
|
+
"zencoder"
|
|
2870
|
+
];
|
|
2871
|
+
function bmadInstallArgs(repoRoot) {
|
|
2872
|
+
return [
|
|
2873
|
+
"-y",
|
|
2874
|
+
`${BMAD_NPM_PACKAGE}@${BMAD_TARGET_CHANNEL}`,
|
|
2875
|
+
"install",
|
|
2876
|
+
"--yes",
|
|
2877
|
+
"--directory",
|
|
2878
|
+
repoRoot,
|
|
2879
|
+
"--modules",
|
|
2880
|
+
"bmm,bmb,cis",
|
|
2881
|
+
"--tools",
|
|
2882
|
+
BMAD_INSTALL_TOOLS.join(",")
|
|
2883
|
+
];
|
|
2884
|
+
}
|
|
2885
|
+
function runBmadInstall(repoRoot) {
|
|
2886
|
+
const result = spawnSync6("npx", bmadInstallArgs(repoRoot), { encoding: "utf8" });
|
|
2887
|
+
if (result.status !== 0) {
|
|
2888
|
+
return { ok: false, error: result.stderr || result.error?.message || "Unknown error" };
|
|
2889
|
+
}
|
|
2890
|
+
return { ok: true };
|
|
2891
|
+
}
|
|
2892
|
+
function readInstalledBmadVersion(repoRoot) {
|
|
2893
|
+
const raw = safeReadText(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
|
|
2894
|
+
if (!raw) return void 0;
|
|
2895
|
+
try {
|
|
2896
|
+
const parsed = YAML2.parse(raw);
|
|
2897
|
+
const version = parsed?.installation?.version;
|
|
2898
|
+
return typeof version === "string" && version.trim() ? version.trim() : void 0;
|
|
2899
|
+
} catch {
|
|
2900
|
+
return void 0;
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
function bmadCachePath(homeDir) {
|
|
2904
|
+
const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join12(homeDir, ".cache");
|
|
2905
|
+
return join12(cacheRoot, "pjangler", "bmad-dist-tags.json");
|
|
2906
|
+
}
|
|
2907
|
+
function readBmadDistTagsCache(homeDir) {
|
|
2908
|
+
const raw = safeReadText(bmadCachePath(homeDir));
|
|
2909
|
+
if (!raw) return void 0;
|
|
2910
|
+
try {
|
|
2911
|
+
const parsed = JSON.parse(raw);
|
|
2912
|
+
if (parsed && typeof parsed.fetchedAt === "number" && parsed.distTags && typeof parsed.distTags === "object") {
|
|
2913
|
+
return parsed;
|
|
2914
|
+
}
|
|
2915
|
+
} catch {
|
|
2916
|
+
}
|
|
2917
|
+
return void 0;
|
|
2918
|
+
}
|
|
2919
|
+
function fetchBmadDistTags() {
|
|
2920
|
+
const result = spawnSync6("npm", ["view", BMAD_NPM_PACKAGE, "dist-tags", "--json"], {
|
|
2921
|
+
encoding: "utf8",
|
|
2922
|
+
timeout: 8e3
|
|
2923
|
+
});
|
|
2924
|
+
if (result.status !== 0 || !result.stdout.trim()) return void 0;
|
|
2925
|
+
try {
|
|
2926
|
+
const parsed = JSON.parse(result.stdout);
|
|
2927
|
+
const obj = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
2928
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
2929
|
+
const tags = {};
|
|
2930
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2931
|
+
if (typeof value === "string") tags[key] = value;
|
|
2932
|
+
}
|
|
2933
|
+
return Object.keys(tags).length ? tags : void 0;
|
|
2934
|
+
} catch {
|
|
2935
|
+
return void 0;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
function resolveBmadDistTags(homeDir) {
|
|
2939
|
+
const cached = readBmadDistTagsCache(homeDir);
|
|
2940
|
+
if (cached && Date.now() - cached.fetchedAt < BMAD_DIST_TAGS_TTL_MS) {
|
|
2941
|
+
return { distTags: cached.distTags, stale: false };
|
|
2942
|
+
}
|
|
2943
|
+
const fetched = fetchBmadDistTags();
|
|
2944
|
+
if (fetched) {
|
|
2945
|
+
try {
|
|
2946
|
+
const path = bmadCachePath(homeDir);
|
|
2947
|
+
mkdirSync6(dirname7(path), { recursive: true });
|
|
2948
|
+
writeFileSync6(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
|
|
2949
|
+
} catch {
|
|
2950
|
+
}
|
|
2951
|
+
return { distTags: fetched, stale: false };
|
|
2952
|
+
}
|
|
2953
|
+
if (cached) return { distTags: cached.distTags, stale: true };
|
|
2954
|
+
return void 0;
|
|
2955
|
+
}
|
|
2956
|
+
function compareBmadVersions(a, b) {
|
|
2957
|
+
const parse = (v) => {
|
|
2958
|
+
const [core = "0", pre = ""] = v.replace(/^v/, "").split("-", 2);
|
|
2959
|
+
const parts = core.split(".");
|
|
2960
|
+
const n = (i) => parseInt(parts[i] ?? "0", 10) || 0;
|
|
2961
|
+
return { nums: [n(0), n(1), n(2)], pre };
|
|
2962
|
+
};
|
|
2963
|
+
const pa = parse(a);
|
|
2964
|
+
const pb = parse(b);
|
|
2965
|
+
for (let i = 0; i < 3; i++) {
|
|
2966
|
+
if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] - pb.nums[i];
|
|
2967
|
+
}
|
|
2968
|
+
if (pa.pre === pb.pre) return 0;
|
|
2969
|
+
if (!pa.pre) return 1;
|
|
2970
|
+
if (!pb.pre) return -1;
|
|
2971
|
+
const ida = pa.pre.split(".");
|
|
2972
|
+
const idb = pb.pre.split(".");
|
|
2973
|
+
for (let i = 0; i < Math.max(ida.length, idb.length); i++) {
|
|
2974
|
+
const xa = ida[i];
|
|
2975
|
+
const xb = idb[i];
|
|
2976
|
+
if (xa === void 0) return -1;
|
|
2977
|
+
if (xb === void 0) return 1;
|
|
2978
|
+
const na = Number(xa);
|
|
2979
|
+
const nb = Number(xb);
|
|
2980
|
+
if (!Number.isNaN(na) && !Number.isNaN(nb)) {
|
|
2981
|
+
if (na !== nb) return na - nb;
|
|
2982
|
+
} else if (xa !== xb) {
|
|
2983
|
+
return xa < xb ? -1 : 1;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
return 0;
|
|
2987
|
+
}
|
|
2820
2988
|
var RULES = [
|
|
2821
2989
|
{
|
|
2822
2990
|
id: "mise.config-root",
|
|
@@ -3181,15 +3349,14 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
3181
3349
|
id: "bmad.scaffold",
|
|
3182
3350
|
title: "BMAD modules/docs scaffold",
|
|
3183
3351
|
audit: (ctx) => {
|
|
3184
|
-
const sourceRoot = join12(ctx.pjanglerRoot, "templates", "commonproject", "_bmad");
|
|
3185
3352
|
const targetRoot = join12(ctx.repoRoot, "_bmad");
|
|
3186
3353
|
const sentinels = [
|
|
3187
3354
|
join12("core", "config.yaml"),
|
|
3188
|
-
join12("
|
|
3189
|
-
join12("
|
|
3190
|
-
join12("bmm", "
|
|
3355
|
+
join12("config.toml"),
|
|
3356
|
+
join12("_config", "manifest.yaml"),
|
|
3357
|
+
join12("bmm", "config.yaml")
|
|
3191
3358
|
];
|
|
3192
|
-
const missing = sentinels.filter((file) =>
|
|
3359
|
+
const missing = sentinels.filter((file) => !existsSync9(join12(targetRoot, file)));
|
|
3193
3360
|
return {
|
|
3194
3361
|
id: "bmad.scaffold",
|
|
3195
3362
|
title: "BMAD modules/docs scaffold",
|
|
@@ -3201,17 +3368,148 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
|
|
|
3201
3368
|
},
|
|
3202
3369
|
migrate: (ctx, finding) => {
|
|
3203
3370
|
const changedFiles = [];
|
|
3204
|
-
|
|
3371
|
+
if (ctx.dryRun) {
|
|
3372
|
+
for (const detail of finding.details) {
|
|
3373
|
+
changedFiles.push(join12(ctx.repoRoot, detail));
|
|
3374
|
+
}
|
|
3375
|
+
return {
|
|
3376
|
+
id: finding.id,
|
|
3377
|
+
title: finding.title,
|
|
3378
|
+
status: changedFiles.length ? "applied" : "noop",
|
|
3379
|
+
summary: changedFiles.length ? "Would run non-interactive bmad-method install" : "No changes required",
|
|
3380
|
+
changedFiles,
|
|
3381
|
+
details: [
|
|
3382
|
+
`Would run: npx ${bmadInstallArgs(ctx.repoRoot).join(" ").replace(BMAD_INSTALL_TOOLS.join(","), "...")}`
|
|
3383
|
+
]
|
|
3384
|
+
};
|
|
3385
|
+
}
|
|
3386
|
+
const install = runBmadInstall(ctx.repoRoot);
|
|
3387
|
+
if (!install.ok) {
|
|
3388
|
+
return {
|
|
3389
|
+
id: finding.id,
|
|
3390
|
+
title: finding.title,
|
|
3391
|
+
status: "blocked",
|
|
3392
|
+
summary: `Failed to run bmad-method install`,
|
|
3393
|
+
changedFiles: [],
|
|
3394
|
+
details: [install.error ?? "Unknown error"]
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
for (const detail of finding.details) {
|
|
3398
|
+
if (existsSync9(join12(ctx.repoRoot, detail))) {
|
|
3399
|
+
changedFiles.push(join12(ctx.repoRoot, detail));
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3205
3402
|
return {
|
|
3206
3403
|
id: finding.id,
|
|
3207
3404
|
title: finding.title,
|
|
3208
3405
|
status: changedFiles.length ? "applied" : "noop",
|
|
3209
|
-
summary: changedFiles.length ? "
|
|
3406
|
+
summary: changedFiles.length ? "Installed BMAD scaffold via non-interactive installer" : "No changes required",
|
|
3210
3407
|
changedFiles,
|
|
3211
3408
|
details: []
|
|
3212
3409
|
};
|
|
3213
3410
|
}
|
|
3214
3411
|
},
|
|
3412
|
+
{
|
|
3413
|
+
id: "bmad.version",
|
|
3414
|
+
title: "BMAD version currency",
|
|
3415
|
+
audit: (ctx) => {
|
|
3416
|
+
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
3417
|
+
if (!installed) {
|
|
3418
|
+
return {
|
|
3419
|
+
id: "bmad.version",
|
|
3420
|
+
title: "BMAD version currency",
|
|
3421
|
+
status: "skip",
|
|
3422
|
+
summary: existsSync9(join12(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
|
|
3423
|
+
details: [],
|
|
3424
|
+
fixable: false
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
const resolved = resolveBmadDistTags(ctx.homeDir);
|
|
3428
|
+
const available = resolved?.distTags?.[BMAD_TARGET_CHANNEL];
|
|
3429
|
+
if (!available) {
|
|
3430
|
+
return {
|
|
3431
|
+
id: "bmad.version",
|
|
3432
|
+
title: "BMAD version currency",
|
|
3433
|
+
status: "skip",
|
|
3434
|
+
summary: `BMAD ${installed} installed; latest ${BMAD_TARGET_CHANNEL} version unknown (npm unreachable)`,
|
|
3435
|
+
details: [`Could not resolve ${BMAD_NPM_PACKAGE}@${BMAD_TARGET_CHANNEL} from npm`],
|
|
3436
|
+
fixable: false
|
|
3437
|
+
};
|
|
3438
|
+
}
|
|
3439
|
+
const staleNote = resolved.stale ? ` ${glyph.dot} cached` : "";
|
|
3440
|
+
if (compareBmadVersions(installed, available) >= 0) {
|
|
3441
|
+
return {
|
|
3442
|
+
id: "bmad.version",
|
|
3443
|
+
title: "BMAD version currency",
|
|
3444
|
+
status: "pass",
|
|
3445
|
+
summary: `BMAD ${installed} is current (${BMAD_TARGET_CHANNEL} ${available})${staleNote}`,
|
|
3446
|
+
details: [],
|
|
3447
|
+
fixable: false
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
return {
|
|
3451
|
+
id: "bmad.version",
|
|
3452
|
+
title: "BMAD version currency",
|
|
3453
|
+
status: "warn",
|
|
3454
|
+
summary: `BMAD ${installed} is behind ${BMAD_TARGET_CHANNEL} ${available} \u2014 upgrade available`,
|
|
3455
|
+
details: [
|
|
3456
|
+
`installed: ${installed}`,
|
|
3457
|
+
`available: ${available} (${BMAD_NPM_PACKAGE}@${BMAD_TARGET_CHANNEL})`,
|
|
3458
|
+
resolved.distTags.latest ? `stable latest: ${resolved.distTags.latest}` : "",
|
|
3459
|
+
"run `pj migrate bmad.version` to upgrade"
|
|
3460
|
+
].filter(Boolean),
|
|
3461
|
+
fixable: true
|
|
3462
|
+
};
|
|
3463
|
+
},
|
|
3464
|
+
migrate: (ctx, finding) => {
|
|
3465
|
+
if (finding.status !== "warn") {
|
|
3466
|
+
return {
|
|
3467
|
+
id: finding.id,
|
|
3468
|
+
title: finding.title,
|
|
3469
|
+
status: "noop",
|
|
3470
|
+
summary: finding.status === "skip" ? finding.summary : "BMAD already current",
|
|
3471
|
+
changedFiles: [],
|
|
3472
|
+
details: []
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
const installed = readInstalledBmadVersion(ctx.repoRoot);
|
|
3476
|
+
const available = resolveBmadDistTags(ctx.homeDir)?.distTags?.[BMAD_TARGET_CHANNEL];
|
|
3477
|
+
const manifestPath = join12(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
|
|
3478
|
+
if (ctx.dryRun) {
|
|
3479
|
+
return {
|
|
3480
|
+
id: finding.id,
|
|
3481
|
+
title: finding.title,
|
|
3482
|
+
status: "applied",
|
|
3483
|
+
summary: `Would upgrade BMAD ${installed ?? "?"} -> ${available ?? BMAD_TARGET_CHANNEL}`,
|
|
3484
|
+
changedFiles: [manifestPath],
|
|
3485
|
+
details: [
|
|
3486
|
+
`Would run: npx ${bmadInstallArgs(ctx.repoRoot).join(" ").replace(BMAD_INSTALL_TOOLS.join(","), "...")}`
|
|
3487
|
+
]
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3490
|
+
const install = runBmadInstall(ctx.repoRoot);
|
|
3491
|
+
if (!install.ok) {
|
|
3492
|
+
return {
|
|
3493
|
+
id: finding.id,
|
|
3494
|
+
title: finding.title,
|
|
3495
|
+
status: "blocked",
|
|
3496
|
+
summary: "Failed to upgrade BMAD via installer",
|
|
3497
|
+
changedFiles: [],
|
|
3498
|
+
details: [install.error ?? "Unknown error"]
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
const nowInstalled = readInstalledBmadVersion(ctx.repoRoot);
|
|
3502
|
+
const upgraded = Boolean(nowInstalled && installed && compareBmadVersions(nowInstalled, installed) > 0);
|
|
3503
|
+
return {
|
|
3504
|
+
id: finding.id,
|
|
3505
|
+
title: finding.title,
|
|
3506
|
+
status: upgraded ? "applied" : "noop",
|
|
3507
|
+
summary: upgraded ? `Upgraded BMAD ${installed} -> ${nowInstalled}` : `BMAD reinstalled (${nowInstalled ?? "?"})`,
|
|
3508
|
+
changedFiles: upgraded ? [manifestPath] : [],
|
|
3509
|
+
details: []
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
},
|
|
3215
3513
|
{
|
|
3216
3514
|
id: "hermes.pm-scaffold",
|
|
3217
3515
|
title: "Hermes PM scaffold parity",
|