@ikunin/sprintpilot 2.3.1 → 2.3.3

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.
@@ -1,6 +1,6 @@
1
1
  addon:
2
2
  name: sprintpilot
3
- version: 2.3.1
3
+ version: 2.3.3
4
4
  description: Sprintpilot — autopilot and multi-agent addon for BMad Method (git workflow, parallel agents, autonomous story execution)
5
5
  bmad_compatibility: ">=6.2.0"
6
6
  modules:
@@ -517,6 +517,59 @@ async function evictV1SkillsFromToolDirs(projectRoot, { dryRun = false } = {}) {
517
517
  return removed;
518
518
  }
519
519
 
520
+ // Skills owned by Sprintpilot all live under one of these prefixes. Used
521
+ // by the orphan-prune sweep to scope which skill dirs the installer is
522
+ // allowed to touch. Anything outside these prefixes (BMad's own skills,
523
+ // user-authored skills, other addons) is left strictly alone.
524
+ const SPRINTPILOT_SKILL_PREFIXES = ['sprint-autopilot-', 'sprintpilot-'];
525
+
526
+ // v2.3.2 — orphan prune. After the per-tool deploy loop has installed the
527
+ // current set of skills, walk the tool's skills/ dir and remove any
528
+ // Sprintpilot-namespace skill dirs that fell out of the manifest between
529
+ // releases. Without this, skills removed in a later release (e.g.
530
+ // sprintpilot-code-review and sprintpilot-party-mode dropped in v2.3.1)
531
+ // linger in users' tool dirs forever, polluting the slash-command picker
532
+ // and pointing at stale internal code.
533
+ //
534
+ // Returns the list of orphan names (relative). Backs up before removing,
535
+ // using the same .sprintpilot-backups/ convention as the install loop.
536
+ async function pruneOrphanSkillsFromToolDir(
537
+ skillsDir,
538
+ currentSkills,
539
+ backupDir,
540
+ ts,
541
+ { dryRun = false } = {},
542
+ ) {
543
+ if (!(await fs.pathExists(skillsDir))) return [];
544
+ const orphans = [];
545
+ const currentSet = new Set(currentSkills);
546
+ let entries;
547
+ try {
548
+ entries = await fs.readdir(skillsDir, { withFileTypes: true });
549
+ } catch {
550
+ return [];
551
+ }
552
+ for (const entry of entries) {
553
+ if (!entry.isDirectory()) continue;
554
+ const name = entry.name;
555
+ if (!SPRINTPILOT_SKILL_PREFIXES.some((p) => name.startsWith(p))) continue;
556
+ if (currentSet.has(name)) continue;
557
+ if (dryRun) {
558
+ orphans.push(name);
559
+ continue;
560
+ }
561
+ const target = path.join(skillsDir, name);
562
+ try {
563
+ await backupSkill(target, backupDir, ts);
564
+ await fs.remove(target);
565
+ orphans.push(name);
566
+ } catch {
567
+ // Best-effort: skip on failure rather than aborting the install.
568
+ }
569
+ }
570
+ return orphans;
571
+ }
572
+
520
573
  // Best-effort detection of a lingering global install of the v1 npm
521
574
  // package. When `npm ls -g --json` exits non-zero (e.g. ELSPROBLEMS from
522
575
  // unrelated peerDep warnings), it still writes valid JSON to stdout, so
@@ -948,18 +1001,25 @@ async function readExistingComplexityProfile(projectRoot, v1Snapshot) {
948
1001
  return m[1];
949
1002
  }
950
1003
 
951
- // v2.3.0 — post-install hygiene check. Cross-reference the project's
952
- // _Sprintpilot/manifest.yaml `installed_skills` against what actually
953
- // landed under `_Sprintpilot/skills/<name>/SKILL.md`. Catches the
954
- // classic "added skill to manifest but forgot to ship the files" bug
955
- // at install time rather than at first invocation.
1004
+ // v2.3.0 — packaging-hygiene check. Cross-reference the project's
1005
+ // _Sprintpilot/manifest.yaml `installed_skills` against what the npm
1006
+ // package actually ships under `<bundleDir>/skills/<name>/SKILL.md`.
1007
+ // Catches the classic "added skill to manifest but forgot to ship the
1008
+ // files" bug at install time rather than at first invocation.
1009
+ //
1010
+ // IMPORTANT: this checks the BUNDLE (the npm package's source tree
1011
+ // pointed to by `bundleDir`, default `ADDON_DIR`), not the project's
1012
+ // _Sprintpilot/. The project never gets `skills/` copied to its
1013
+ // _Sprintpilot/ — skills only land in per-tool dirs (.claude/skills/,
1014
+ // .cursor/skills/, ...). Earlier versions wrongly checked the project
1015
+ // path, which caused a false-positive WARN on every install. Fixed in
1016
+ // v2.3.2.
956
1017
  //
957
1018
  // Returns { missing: string[] } — empty array means everything is
958
1019
  // wired correctly. The caller chooses how to surface mismatches
959
1020
  // (warning vs fail). We never fail the install on a mismatch; it's
960
- // hygiene, not correctness — the skill just won't appear under the
961
- // host tool's /-command if it's missing on disk.
962
- async function verifySkillManifest(projectRoot) {
1021
+ // hygiene, not correctness.
1022
+ async function verifySkillManifest(projectRoot, bundleDir = ADDON_DIR) {
963
1023
  const manifestPath = path.join(projectRoot, PROJECT_ADDON_DIR_NAME, 'manifest.yaml');
964
1024
  if (!(await fs.pathExists(manifestPath))) {
965
1025
  return { missing: [] };
@@ -982,7 +1042,7 @@ async function verifySkillManifest(projectRoot) {
982
1042
  }
983
1043
  const missing = [];
984
1044
  for (const name of skillNames) {
985
- const skillFile = path.join(projectRoot, PROJECT_ADDON_DIR_NAME, 'skills', name, 'SKILL.md');
1045
+ const skillFile = path.join(bundleDir, 'skills', name, 'SKILL.md');
986
1046
  if (!(await fs.pathExists(skillFile))) missing.push(name);
987
1047
  }
988
1048
  return { missing };
@@ -1350,6 +1410,17 @@ async function runInstall(options = {}) {
1350
1410
  }
1351
1411
  }
1352
1412
 
1413
+ // v2.3.3 — Sprintpilot bundles js-yaml into _Sprintpilot/node_modules/
1414
+ // at install time (see step 6b below). Guard against users committing it.
1415
+ const nodeModulesResult = await addIgnoreEntry(
1416
+ ignore.path,
1417
+ '_Sprintpilot/node_modules/',
1418
+ { dryRun },
1419
+ );
1420
+ if (nodeModulesResult.added && !dryRun) {
1421
+ console.log(`Added '_Sprintpilot/node_modules/' to ${path.basename(ignore.path)}`);
1422
+ }
1423
+
1353
1424
  // 5. Install skills per tool
1354
1425
  let totalInstalled = 0;
1355
1426
  const allSkills = await listSkills();
@@ -1464,6 +1535,16 @@ async function runInstall(options = {}) {
1464
1535
  totalInstalled += toolInstalled;
1465
1536
  }
1466
1537
 
1538
+ // v2.3.2 — sweep Sprintpilot-namespace skills that fell out of the
1539
+ // manifest between releases. Backed up to .sprintpilot-backups/ before
1540
+ // removal. Skipped on dry-run except for reporting what would happen.
1541
+ const orphans = await pruneOrphanSkillsFromToolDir(skillsDir, allSkills, backupDir, ts, {
1542
+ dryRun,
1543
+ });
1544
+ for (const name of orphans) {
1545
+ console.log(` ${dryRun ? 'Would remove' : 'Removed'} orphan skill: ${name}`);
1546
+ }
1547
+
1467
1548
  await installSystemPrompt(tool, projectRoot, ADDON_DIR, ctx, { dryRun });
1468
1549
  console.log('');
1469
1550
  }
@@ -1497,6 +1578,32 @@ async function runInstall(options = {}) {
1497
1578
  }
1498
1579
  console.log('Runtime resources installed to _Sprintpilot/');
1499
1580
 
1581
+ // 6b. v2.3.3 — bundle js-yaml into _Sprintpilot/node_modules/.
1582
+ // Two runtime scripts (sprint-plan.js, infer-dependencies.js)
1583
+ // require('js-yaml') for full YAML parse/dump support that the
1584
+ // in-tree yaml-lite intentionally doesn't cover. Without this,
1585
+ // `/sprintpilot-plan-sprint` and dependency inference crash with
1586
+ // MODULE_NOT_FOUND on every invocation in the consumer project.
1587
+ //
1588
+ // We resolve js-yaml from Sprintpilot's own node_modules (where
1589
+ // npm placed it when the user ran `npx @ikunin/sprintpilot`) and
1590
+ // copy the whole package into <projectRoot>/_Sprintpilot/node_modules/js-yaml/.
1591
+ // Node's require walk finds it from _Sprintpilot/scripts/ at runtime.
1592
+ try {
1593
+ const jsYamlPkgJson = require.resolve('js-yaml/package.json');
1594
+ const jsYamlSrc = path.dirname(jsYamlPkgJson);
1595
+ const jsYamlDest = path.join(targetAddonDir, 'node_modules', 'js-yaml');
1596
+ await fs.remove(jsYamlDest);
1597
+ await fs.copy(jsYamlSrc, jsYamlDest, { overwrite: true });
1598
+ console.log(' Bundled js-yaml → _Sprintpilot/node_modules/js-yaml/');
1599
+ } catch (err) {
1600
+ console.warn(
1601
+ pc.yellow(
1602
+ ` WARN: failed to bundle js-yaml (${err.message || err}). sprint-plan and dependency inference will fail at runtime.`,
1603
+ ),
1604
+ );
1605
+ }
1606
+
1500
1607
  // 6a. Re-apply v1 module-config snapshot (if any) — MUST happen after
1501
1608
  // step 6 because step 6 wrote pristine bundled configs that would
1502
1609
  // otherwise clobber the user's values. On failure, persist the
@@ -1760,5 +1867,7 @@ module.exports = {
1760
1867
  snapshotUserOwnedFiles,
1761
1868
  applyUserOwnedFiles,
1762
1869
  verifySkillManifest,
1870
+ pruneOrphanSkillsFromToolDir,
1871
+ SPRINTPILOT_SKILL_PREFIXES,
1763
1872
  },
1764
1873
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikunin/sprintpilot",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "Sprintpilot — autopilot and multi-agent addon for BMad Method v6: git workflow, parallel agents, autonomous story execution",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {