@ikunin/sprintpilot 2.3.1 → 2.3.2

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.2
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 };
@@ -1464,6 +1524,16 @@ async function runInstall(options = {}) {
1464
1524
  totalInstalled += toolInstalled;
1465
1525
  }
1466
1526
 
1527
+ // v2.3.2 — sweep Sprintpilot-namespace skills that fell out of the
1528
+ // manifest between releases. Backed up to .sprintpilot-backups/ before
1529
+ // removal. Skipped on dry-run except for reporting what would happen.
1530
+ const orphans = await pruneOrphanSkillsFromToolDir(skillsDir, allSkills, backupDir, ts, {
1531
+ dryRun,
1532
+ });
1533
+ for (const name of orphans) {
1534
+ console.log(` ${dryRun ? 'Would remove' : 'Removed'} orphan skill: ${name}`);
1535
+ }
1536
+
1467
1537
  await installSystemPrompt(tool, projectRoot, ADDON_DIR, ctx, { dryRun });
1468
1538
  console.log('');
1469
1539
  }
@@ -1760,5 +1830,7 @@ module.exports = {
1760
1830
  snapshotUserOwnedFiles,
1761
1831
  applyUserOwnedFiles,
1762
1832
  verifySkillManifest,
1833
+ pruneOrphanSkillsFromToolDir,
1834
+ SPRINTPILOT_SKILL_PREFIXES,
1763
1835
  },
1764
1836
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikunin/sprintpilot",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
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": {