@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 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("custom", "config.yaml"),
3262
- join11("custom", "workflows", "ticket-lifecycle", "workflow.yaml"),
3263
- join11("bmm", "workflows", "workflow-status", "workflow.yaml")
3428
+ join11("config.toml"),
3429
+ join11("_config", "manifest.yaml"),
3430
+ join11("bmm", "config.yaml")
3264
3431
  ];
3265
- const missing = sentinels.filter((file) => existsSync9(join11(sourceRoot, file)) && !existsSync9(join11(targetRoot, 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
- copyMissingRecursive(join11(ctx.pjanglerRoot, "templates", "commonproject", "_bmad"), join11(ctx.repoRoot, "_bmad"), changedFiles, ctx.dryRun);
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 ? "Copied missing BMAD scaffold files" : "No changes required",
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",
@@ -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("custom", "config.yaml"),
3189
- join12("custom", "workflows", "ticket-lifecycle", "workflow.yaml"),
3190
- join12("bmm", "workflows", "workflow-status", "workflow.yaml")
3355
+ join12("config.toml"),
3356
+ join12("_config", "manifest.yaml"),
3357
+ join12("bmm", "config.yaml")
3191
3358
  ];
3192
- const missing = sentinels.filter((file) => existsSync9(join12(sourceRoot, file)) && !existsSync9(join12(targetRoot, 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
- copyMissingRecursive(join12(ctx.pjanglerRoot, "templates", "commonproject", "_bmad"), join12(ctx.repoRoot, "_bmad"), changedFiles, ctx.dryRun);
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 ? "Copied missing BMAD scaffold files" : "No changes required",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delorenj/pjangler",
3
- "version": "1.2.13",
3
+ "version": "1.2.14",
4
4
  "description": "Project subsystem bootstrapper CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",