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