@kudusov.takhir/ba-toolkit 1.5.0 → 2.0.0

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/CHANGELOG.md CHANGED
@@ -11,6 +11,62 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
11
11
 
12
12
  ---
13
13
 
14
+ ## [2.0.0] — 2026-04-09
15
+
16
+ ### ⚠️ BREAKING — install layout dropped the `ba-toolkit/` wrapper
17
+
18
+ **Every previous version of this package was broken.** The CLI installed skills under a `ba-toolkit/` wrapper folder (e.g. `.claude/skills/ba-toolkit/brief/SKILL.md`), but Claude Code, Codex CLI, Gemini CLI, Cursor and Windsurf all expect skills as **direct children** of their skills/rules root (`.claude/skills/<skill-name>/SKILL.md`). The wrapper made every shipped skill invisible to the agent — `/brief`, `/srs`, etc. silently did nothing because the agent didn't know they existed. The README, examples and all documentation referenced `/brief`, but the install path made it impossible for that command to ever resolve. Confirmed against the [official Claude Code skills documentation](https://code.claude.com/docs/en/skills.md): *"Each skill is a directory with `SKILL.md` as the entrypoint"* under `.claude/skills/<skill-name>/`, with no nested-namespace support.
19
+
20
+ v2.0 fixes this for real:
21
+
22
+ - **All 5 agent install paths drop the `ba-toolkit/` wrapper.** New paths:
23
+ - Claude Code: `.claude/skills/` (project) and `~/.claude/skills/` (global)
24
+ - OpenAI Codex CLI: `~/.codex/skills/` (global only)
25
+ - Gemini CLI: `.gemini/skills/` and `~/.gemini/skills/`
26
+ - Cursor: `.cursor/rules/` (project only, flat .mdc files)
27
+ - Windsurf: `.windsurf/rules/` (project only, flat .mdc files)
28
+ - **Cursor and Windsurf layouts are now flat.** Previously each skill was nested under `.cursor/rules/ba-toolkit/<skill>/<skill>.mdc` (3 levels). Now each becomes a single `.mdc` file at the rules root: `.cursor/rules/<skill>.mdc`. Cursor's rule loader expects flat `.mdc` files, never recurses.
29
+ - **SKILL.md `name:` field renamed.** The 21 SKILL.md files used `name: ba-brief`, `name: ba-srs`, etc. But Claude Code derives the slash command from the `name` field — so the actual command was `/ba-brief`, not `/brief` as the README promised. All 21 files now use bare names (`name: brief`, `name: srs`, ...), and the slash commands `/brief`, `/srs`, `/ac`, … finally match the documentation.
30
+ - **Manifest replaces sentinel.** `runInstall` now writes `.ba-toolkit-manifest.json` listing every item it owns at the destination root. `cmdUninstall` and `cmdUpgrade` read the manifest and remove **only** those items — the destination directory is now shared with the user's other skills, so we can never `rm -rf` the whole root. Without a manifest, uninstall and upgrade refuse to do anything destructive. Manifest format:
31
+ ```json
32
+ {
33
+ "version": "2.0.0",
34
+ "installedAt": "2026-04-09T...",
35
+ "format": "skill",
36
+ "items": ["brief", "srs", "ac", ..., "references"]
37
+ }
38
+ ```
39
+ - **Legacy v1.x detection.** `runInstall`, `cmdUpgrade`, `cmdUninstall`, and `cmdStatus` now detect the old `ba-toolkit/` wrapper folder if it's still on disk and print a yellow warning with the exact `rm -rf` command to clean it up. The legacy folder is never auto-deleted — it might contain user state.
40
+
41
+ ### Migration from v1.x
42
+
43
+ If you have a v1.x install on disk, the legacy `ba-toolkit/` wrapper will be sitting in the same parent directory as the new layout — it doesn't conflict with v2.0 file-wise, but it never worked, so you should remove it:
44
+
45
+ ```bash
46
+ # Manual cleanup (one-time, per agent that had a v1.x install):
47
+ rm -rf .claude/skills/ba-toolkit # claude-code project-level
48
+ rm -rf ~/.claude/skills/ba-toolkit # claude-code global
49
+ rm -rf ~/.codex/skills/ba-toolkit # codex
50
+ rm -rf .gemini/skills/ba-toolkit # gemini project-level
51
+ rm -rf ~/.gemini/skills/ba-toolkit # gemini global
52
+ rm -rf .cursor/rules/ba-toolkit # cursor
53
+ rm -rf .windsurf/rules/ba-toolkit # windsurf
54
+
55
+ # Then reinstall with v2.0:
56
+ ba-toolkit install --for claude-code
57
+ ```
58
+
59
+ `ba-toolkit status` lists any legacy wrappers it finds on the system as a warning row, so you can audit before cleaning up.
60
+
61
+ ### Internal
62
+
63
+ - **`copySkills(srcRoot, destRoot, { format })` replaces the previous `copyDir` + `skillToMdc`** combo. The new copy logic understands the per-format layout (folder-per-skill for `skill` format, flat `.mdc` files for `mdc` format) and reads the SKILL.md frontmatter to derive the canonical skill name from the `name:` field. References go to `<destRoot>/references/` regardless of format — non-`.mdc` files there are ignored by Cursor's rule loader.
64
+ - **Manifest helpers** (`readManifest`, `writeManifest`, `removeManifestItems`) replace the previous `readSentinel` / `writeSentinel`. The sentinel was a 2-field marker; the manifest is also the source of truth for what to remove on uninstall.
65
+ - **`detectLegacyInstall(agent)`** helper, used by every command that touches the install layout.
66
+ - **Test count 91 → 95.** Sentinel tests replaced with manifest tests; new tests for `detectLegacyInstall` (positive and negative cases) and `skillToMdcContent` (frontmatter + body extraction). All 95 tests pass.
67
+
68
+ ---
69
+
14
70
  ## [1.5.0] — 2026-04-08
15
71
 
16
72
  ### Added
@@ -294,7 +350,8 @@ CI scripts that relied on the old behaviour (`init` creates files only, `install
294
350
 
295
351
  ---
296
352
 
297
- [Unreleased]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.5.0...HEAD
353
+ [Unreleased]: https://github.com/TakhirKudusov/ba-toolkit/compare/v2.0.0...HEAD
354
+ [2.0.0]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.5.0...v2.0.0
298
355
  [1.5.0]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.4.0...v1.5.0
299
356
  [1.4.0]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.3.2...v1.4.0
300
357
  [1.3.2]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.3.1...v1.3.2
package/README.md CHANGED
@@ -55,36 +55,45 @@ Supported agents: `claude-code`, `codex`, `gemini`, `cursor`, `windsurf`. Cursor
55
55
 
56
56
  Use these if you can't use npm or want to track a specific git commit.
57
57
 
58
+ **Important — v2.0 layout:** the skills go directly under the agent's skills root, not nested under a `ba-toolkit/` wrapper folder. Versions before v2.0 used a wrapper, which made every skill invisible to the agent. If you're upgrading from v1.x, remove the legacy wrapper folder first.
59
+
58
60
  ### Claude Code CLI
59
61
 
60
62
  ```bash
61
63
  git clone https://github.com/TakhirKudusov/ba-toolkit.git
62
- cp -r ba-toolkit/skills/ /path/to/project/.claude/skills/ba-toolkit/
63
64
 
64
- # Or install globally:
65
- cp -r ba-toolkit/skills/ ~/.claude/skills/ba-toolkit/
65
+ # Project-level: copy the contents of skills/ into .claude/skills/
66
+ mkdir -p /path/to/project/.claude/skills
67
+ cp -R ba-toolkit/skills/. /path/to/project/.claude/skills/
68
+
69
+ # Or globally:
70
+ mkdir -p ~/.claude/skills
71
+ cp -R ba-toolkit/skills/. ~/.claude/skills/
66
72
  ```
67
73
 
68
- Keep the full tree: skill folders (`brief/`, `srs/`, …) must stay together with `references/` in the same parent directory.
74
+ Each skill folder (`brief/`, `srs/`, …) lands as a direct child of `.claude/skills/`, and the `references/` folder sits next to them. If you have other skills installed in the same directory, they're left alone.
69
75
 
70
76
  ### OpenAI Codex CLI
71
77
 
72
78
  Skills load from `$CODEX_HOME/skills` (default `~/.codex/skills`):
73
79
 
74
80
  ```bash
75
- cp -r ba-toolkit/skills/ ~/.codex/skills/ba-toolkit/
81
+ mkdir -p ~/.codex/skills
82
+ cp -R ba-toolkit/skills/. ~/.codex/skills/
76
83
  ```
77
84
 
78
- If you use a custom Codex home, set `CODEX_HOME` and copy under `$CODEX_HOME/skills/ba-toolkit/`.
85
+ If you use a custom Codex home, set `CODEX_HOME` and copy under `$CODEX_HOME/skills/`.
79
86
 
80
87
  ### Google Gemini CLI
81
88
 
82
89
  ```bash
83
90
  # User-wide (all projects)
84
- cp -r ba-toolkit/skills/ ~/.gemini/skills/ba-toolkit/
91
+ mkdir -p ~/.gemini/skills
92
+ cp -R ba-toolkit/skills/. ~/.gemini/skills/
85
93
 
86
94
  # Or project-only
87
- cp -r ba-toolkit/skills/ /path/to/project/.gemini/skills/ba-toolkit/
95
+ mkdir -p /path/to/project/.gemini/skills
96
+ cp -R ba-toolkit/skills/. /path/to/project/.gemini/skills/
88
97
  ```
89
98
 
90
99
  Reload the CLI after copying.
@@ -193,9 +202,9 @@ BA Toolkit uses the open Agent Skills specification (`SKILL.md` format) publishe
193
202
 
194
203
  | Platform | Support | Installation |
195
204
  |----------|:-------:|-------------|
196
- | **Claude Code** | Native | `cp -r skills/ .claude/skills/ba-toolkit/` |
197
- | **OpenAI Codex CLI** | Native | `cp -r skills/ ~/.codex/skills/ba-toolkit/` |
198
- | **Gemini CLI** | Native | Copy `skills/` to `~/.gemini/skills/ba-toolkit/` (user) or `.gemini/skills/ba-toolkit/` (workspace) |
205
+ | **Claude Code** | Native | `cp -R skills/. .claude/skills/` |
206
+ | **OpenAI Codex CLI** | Native | `cp -R skills/. ~/.codex/skills/` |
207
+ | **Gemini CLI** | Native | Copy `skills/.` contents to `~/.gemini/skills/` (user) or `.gemini/skills/` (workspace) |
199
208
  | **Cursor** | Convert | `SKILL.md` → `.mdc` rules in `.cursor/rules/` |
200
209
  | **Windsurf** | Convert | `SKILL.md` → rules in `.windsurf/rules/` |
201
210
  | **Aider** | Convert | `SKILL.md` → conventions file |
package/bin/ba-toolkit.js CHANGED
@@ -18,38 +18,54 @@ const PACKAGE_ROOT = path.resolve(__dirname, '..');
18
18
  const SKILLS_DIR = path.join(PACKAGE_ROOT, 'skills');
19
19
  const PKG = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
20
20
 
21
+ // In v2.0 the install paths dropped the previous `ba-toolkit/` wrapper
22
+ // directory. Claude Code, Codex CLI, and Gemini CLI all expect skills
23
+ // to be discoverable as direct subfolders of their skills root —
24
+ // `.claude/skills/<skill-name>/SKILL.md`, not nested one level deeper.
25
+ // The wrapper made all 21 skills invisible to every agent.
26
+ //
27
+ // Cursor and Windsurf load `.mdc` rule files directly from their rules
28
+ // root, so v2.0 also flattens that layout: the per-skill subfolders
29
+ // produced by the previous version are gone, and rules sit at
30
+ // `.cursor/rules/<skill-name>.mdc`.
31
+ //
32
+ // To stay safe sharing the skills root with the user's other skills /
33
+ // rules, every install also drops a `.ba-toolkit-manifest.json` next to
34
+ // the installed items. uninstall and upgrade read this manifest to
35
+ // remove only what the toolkit owns; without it they refuse to touch
36
+ // anything.
21
37
  const AGENTS = {
22
38
  'claude-code': {
23
39
  name: 'Claude Code',
24
- projectPath: '.claude/skills/ba-toolkit',
25
- globalPath: path.join(os.homedir(), '.claude', 'skills', 'ba-toolkit'),
40
+ projectPath: '.claude/skills',
41
+ globalPath: path.join(os.homedir(), '.claude', 'skills'),
26
42
  format: 'skill',
27
43
  restartHint: 'Restart Claude Code to load the new skills.',
28
44
  },
29
45
  codex: {
30
46
  name: 'OpenAI Codex CLI',
31
47
  projectPath: null, // Codex uses only global
32
- globalPath: path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills', 'ba-toolkit'),
48
+ globalPath: path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills'),
33
49
  format: 'skill',
34
50
  restartHint: 'Restart the Codex CLI to load the new skills.',
35
51
  },
36
52
  gemini: {
37
53
  name: 'Google Gemini CLI',
38
- projectPath: '.gemini/skills/ba-toolkit',
39
- globalPath: path.join(os.homedir(), '.gemini', 'skills', 'ba-toolkit'),
54
+ projectPath: '.gemini/skills',
55
+ globalPath: path.join(os.homedir(), '.gemini', 'skills'),
40
56
  format: 'skill',
41
57
  restartHint: 'Reload Gemini CLI to pick up the new skills.',
42
58
  },
43
59
  cursor: {
44
60
  name: 'Cursor',
45
- projectPath: '.cursor/rules/ba-toolkit',
61
+ projectPath: '.cursor/rules',
46
62
  globalPath: null, // Cursor rules are project-scoped
47
63
  format: 'mdc',
48
64
  restartHint: 'Reload the Cursor window to apply new rules.',
49
65
  },
50
66
  windsurf: {
51
67
  name: 'Windsurf',
52
- projectPath: '.windsurf/rules/ba-toolkit',
68
+ projectPath: '.windsurf/rules',
53
69
  globalPath: null,
54
70
  format: 'mdc',
55
71
  restartHint: 'Reload the Windsurf window to apply new rules.',
@@ -296,35 +312,21 @@ function today() {
296
312
  return new Date().toISOString().slice(0, 10);
297
313
  }
298
314
 
299
- function copyDir(src, dest, { dryRun = false, transform = null } = {}) {
300
- if (!fs.existsSync(src)) {
301
- throw new Error(`Source directory not found: ${src}`);
302
- }
303
- const copied = [];
304
- (function walk(s, d) {
305
- if (!dryRun) fs.mkdirSync(d, { recursive: true });
306
- for (const entry of fs.readdirSync(s, { withFileTypes: true })) {
307
- const srcPath = path.join(s, entry.name);
308
- let destPath = path.join(d, entry.name);
309
- if (entry.isDirectory()) {
310
- walk(srcPath, destPath);
311
- continue;
312
- }
313
- if (transform) {
314
- const result = transform(srcPath, destPath);
315
- if (!result) continue;
316
- destPath = result.destPath;
317
- if (!dryRun) {
318
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
319
- fs.writeFileSync(destPath, result.content);
320
- }
321
- } else {
322
- if (!dryRun) fs.copyFileSync(srcPath, destPath);
323
- }
324
- copied.push(destPath);
315
+ // Generic recursive copy that mirrors src into dest. Used by copySkills
316
+ // for the references/ folder and for skill-format skill folders, where
317
+ // the source structure is preserved verbatim.
318
+ function copyDirRecursive(src, dest, { dryRun, copied }) {
319
+ if (!dryRun) fs.mkdirSync(dest, { recursive: true });
320
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
321
+ const sp = path.join(src, entry.name);
322
+ const dp = path.join(dest, entry.name);
323
+ if (entry.isDirectory()) {
324
+ copyDirRecursive(sp, dp, { dryRun, copied });
325
+ } else {
326
+ if (!dryRun) fs.copyFileSync(sp, dp);
327
+ copied.push(dp);
325
328
  }
326
- })(src, dest);
327
- return copied;
329
+ }
328
330
  }
329
331
 
330
332
  // Minimal YAML frontmatter parser for SKILL.md files.
@@ -400,19 +402,74 @@ function parseSkillFrontmatter(content) {
400
402
  };
401
403
  }
402
404
 
403
- // Transform SKILL.md .mdc for Cursor / Windsurf.
404
- // Other files (references/, templates/) are copied as-is.
405
- function skillToMdc(srcPath, destPath) {
406
- const base = path.basename(srcPath);
407
- if (base !== 'SKILL.md') {
408
- return { destPath, content: fs.readFileSync(srcPath) };
405
+ // Transform a SKILL.md file's contents to the Cursor/Windsurf .mdc rule
406
+ // format: replace the YAML frontmatter with the two fields the rule
407
+ // loader expects (description, alwaysApply), keep the body unchanged.
408
+ function skillToMdcContent(content) {
409
+ const { description, body } = parseSkillFrontmatter(content);
410
+ return `---\ndescription: ${description}\nalwaysApply: false\n---\n\n` + body;
411
+ }
412
+
413
+ // Install the package's skills/ tree into the given destination, picking
414
+ // the layout the target agent expects.
415
+ //
416
+ // For 'skill' format (Claude Code, Codex, Gemini): each source skill
417
+ // folder lands as `<destRoot>/<skillName>/SKILL.md`. The references/
418
+ // folder is copied as-is to `<destRoot>/references/`.
419
+ //
420
+ // For 'mdc' format (Cursor, Windsurf): each source skill folder is
421
+ // flattened to a single `<destRoot>/<skillName>.mdc` file containing
422
+ // the transformed content. References still go to `<destRoot>/references/`
423
+ // — non-.mdc files there are ignored by the rule loaders, but the LLM
424
+ // can still find them at runtime via the Read tool.
425
+ //
426
+ // Skill names come from the SKILL.md `name:` frontmatter field, falling
427
+ // back to the source folder name. Returns:
428
+ // { copied, items }
429
+ // where `copied` is the list of absolute file paths written and `items`
430
+ // is the list of top-level entries in destRoot that the toolkit owns
431
+ // (used to write the manifest).
432
+ function copySkills(srcRoot, destRoot, { format, dryRun = false }) {
433
+ if (!fs.existsSync(srcRoot)) {
434
+ throw new Error(`Source directory not found: ${srcRoot}`);
409
435
  }
410
- const content = fs.readFileSync(srcPath, 'utf8');
411
- const { name, description, body } = parseSkillFrontmatter(content);
412
- const ruleName = name || path.basename(path.dirname(srcPath));
413
- const mdcFrontmatter = `---\ndescription: ${description}\nalwaysApply: false\n---\n\n`;
414
- const newDestPath = path.join(path.dirname(destPath), `${ruleName}.mdc`);
415
- return { destPath: newDestPath, content: mdcFrontmatter + body };
436
+ const copied = [];
437
+ const items = [];
438
+
439
+ if (!dryRun) fs.mkdirSync(destRoot, { recursive: true });
440
+
441
+ for (const entry of fs.readdirSync(srcRoot, { withFileTypes: true })) {
442
+ if (!entry.isDirectory()) continue;
443
+ const srcPath = path.join(srcRoot, entry.name);
444
+
445
+ if (entry.name === 'references') {
446
+ const refDest = path.join(destRoot, 'references');
447
+ copyDirRecursive(srcPath, refDest, { dryRun, copied });
448
+ items.push('references');
449
+ continue;
450
+ }
451
+
452
+ // Skill folder. Read its SKILL.md to get the canonical name.
453
+ const skillMdSrc = path.join(srcPath, 'SKILL.md');
454
+ if (!fs.existsSync(skillMdSrc)) continue; // not a skill folder
455
+ const content = fs.readFileSync(skillMdSrc, 'utf8');
456
+ const { name } = parseSkillFrontmatter(content);
457
+ const skillName = name || entry.name;
458
+
459
+ if (format === 'mdc') {
460
+ const transformed = skillToMdcContent(content);
461
+ const destFile = path.join(destRoot, `${skillName}.mdc`);
462
+ if (!dryRun) fs.writeFileSync(destFile, transformed);
463
+ copied.push(destFile);
464
+ items.push(`${skillName}.mdc`);
465
+ } else {
466
+ const skillDestDir = path.join(destRoot, skillName);
467
+ copyDirRecursive(srcPath, skillDestDir, { dryRun, copied });
468
+ items.push(skillName);
469
+ }
470
+ }
471
+
472
+ return { copied, items };
416
473
  }
417
474
 
418
475
  // Path to the AGENTS.md template file. Lives next to the rest of the
@@ -620,14 +677,18 @@ async function cmdInit(args) {
620
677
  log('');
621
678
  }
622
679
 
623
- // Marker file written into the install destination after a successful copy.
624
- // Lets `upgrade` and `status` (future command) tell which package version
625
- // is currently installed without diffing every file. Hidden file with no
626
- // `.md` / `.mdc` extension so the agent's skill loader ignores it.
627
- const SENTINEL_FILENAME = '.ba-toolkit-version';
680
+ // Manifest written into the install destination after a successful copy.
681
+ // Replaces the v1.x version sentinel now also tracks WHICH items
682
+ // belong to BA Toolkit, so uninstall/upgrade can selectively remove
683
+ // only what we own without touching the user's other skills sitting in
684
+ // the same directory.
685
+ //
686
+ // Hidden filename with no `.md` / `.mdc` extension so the skill loader
687
+ // of every supported agent ignores it.
688
+ const MANIFEST_FILENAME = '.ba-toolkit-manifest.json';
628
689
 
629
- function readSentinel(destDir) {
630
- const p = path.join(destDir, SENTINEL_FILENAME);
690
+ function readManifest(destDir) {
691
+ const p = path.join(destDir, MANIFEST_FILENAME);
631
692
  if (!fs.existsSync(p)) return null;
632
693
  try {
633
694
  return JSON.parse(fs.readFileSync(p, 'utf8'));
@@ -636,45 +697,52 @@ function readSentinel(destDir) {
636
697
  }
637
698
  }
638
699
 
639
- function writeSentinel(destDir) {
700
+ function writeManifest(destDir, format, items) {
640
701
  const payload = {
641
702
  version: PKG.version,
642
703
  installedAt: new Date().toISOString(),
704
+ format,
705
+ items,
643
706
  };
644
707
  fs.writeFileSync(
645
- path.join(destDir, SENTINEL_FILENAME),
708
+ path.join(destDir, MANIFEST_FILENAME),
646
709
  JSON.stringify(payload, null, 2) + '\n',
647
710
  );
648
711
  }
649
712
 
650
- // Core install logic. Shared between `cmdInstall` (standalone), `cmdInit`
651
- // (full setup), and `cmdUpgrade`. Returns true on success, false if the
652
- // user declined to overwrite an existing destination. Pass `force: true`
653
- // to skip the overwrite prompt `cmdUpgrade` uses this because it has
654
- // already wiped the destination and explicitly knows the overwrite is ok.
655
- async function runInstall({ agentId, isGlobal, isProject, dryRun, showHeader = true, force = false }) {
656
- const agent = AGENTS[agentId];
657
- if (!agent) {
658
- logError(`Unknown agent: ${agentId}`);
659
- log('Supported: ' + Object.keys(AGENTS).join(', '));
660
- process.exit(1);
661
- }
662
-
663
- let effectiveGlobal = !!isGlobal;
664
- if (!isGlobal && !isProject) {
665
- // Default: project-level if supported, otherwise global
666
- effectiveGlobal = !agent.projectPath;
713
+ // Detect the v1.x install layout: every previous install path nested
714
+ // our 21 skills under an extra `ba-toolkit/` folder, which made them
715
+ // invisible to every agent's skill loader. Returns the absolute paths
716
+ // of any legacy folders that still exist for the given agent, so the
717
+ // caller can warn the user to clean them up before installing v2.0.
718
+ function detectLegacyInstall(agent) {
719
+ const candidates = [];
720
+ if (agent.projectPath) {
721
+ candidates.push(path.resolve(process.cwd(), agent.projectPath, 'ba-toolkit'));
667
722
  }
668
- if (effectiveGlobal && !agent.globalPath) {
669
- logError(`${agent.name} does not support --global install.`);
670
- process.exit(1);
671
- }
672
- if (!effectiveGlobal && !agent.projectPath) {
673
- logError(`${agent.name} does not support project-level install. Use --global.`);
674
- process.exit(1);
723
+ if (agent.globalPath) {
724
+ candidates.push(path.join(agent.globalPath, 'ba-toolkit'));
675
725
  }
726
+ return candidates.filter((p) => fs.existsSync(p));
727
+ }
676
728
 
677
- const destDir = effectiveGlobal ? agent.globalPath : path.resolve(process.cwd(), agent.projectPath);
729
+ // Core install logic. Shared between `cmdInstall` (standalone), `cmdInit`
730
+ // (full setup), and `cmdUpgrade`. Returns true on success, false if the
731
+ // user declined to overwrite an existing install. Pass `force: true` to
732
+ // skip the overwrite prompt — cmdUpgrade uses this because it has
733
+ // already removed the previous install via the manifest.
734
+ //
735
+ // v2.0 model: the destination directory is shared with the user's other
736
+ // skills. We never wipe destDir wholesale. Before copying we read the
737
+ // manifest (if any) and remove only the items the previous v2.0 install
738
+ // owned, then copy the new tree and write a fresh manifest. Items that
739
+ // don't appear in the manifest are not ours and never get touched.
740
+ async function runInstall({ agentId, isGlobal, isProject, dryRun, showHeader = true, force = false }) {
741
+ const { agent, destDir, effectiveGlobal } = resolveAgentDestination({
742
+ agentId,
743
+ isGlobal,
744
+ isProject,
745
+ });
678
746
 
679
747
  if (showHeader) {
680
748
  log('');
@@ -690,34 +758,88 @@ async function runInstall({ agentId, isGlobal, isProject, dryRun, showHeader = t
690
758
  log(` format: ${agent.format === 'mdc' ? '.mdc (converted from SKILL.md)' : 'SKILL.md (native)'}`);
691
759
  if (dryRun) log(' ' + yellow('mode: dry-run (no files will be written)'));
692
760
 
693
- if (fs.existsSync(destDir) && !dryRun && !force) {
694
- const answer = await prompt(` ${destDir} already exists. Overwrite? (y/N): `);
761
+ // Warn about a v1.x wrapper folder if one is sitting in the same
762
+ // location. Don't auto-delete could be the user's working state.
763
+ warnLegacyInstall(agent);
764
+
765
+ // If a previous v2.0 install lives here, ask before replacing it.
766
+ // The manifest is the only signal that the directory contains our
767
+ // files; without it we treat the install as fresh and let copySkills
768
+ // happily add to whatever's already there.
769
+ const existingManifest = readManifest(destDir);
770
+ if (existingManifest && !dryRun && !force) {
771
+ log(` existing: v${existingManifest.version} (${existingManifest.items.length} items)`);
772
+ const answer = await prompt(' Replace existing BA Toolkit install? (y/N): ');
695
773
  if (answer.toLowerCase() !== 'y') {
696
774
  log(' cancelled.');
697
775
  return false;
698
776
  }
699
777
  }
700
778
 
701
- const transform = agent.format === 'mdc' ? skillToMdc : null;
702
- let copied;
779
+ // Selectively remove the previous install's items (and only those)
780
+ // before copying the new tree. Sentinel-style file is removed too.
781
+ if (existingManifest && !dryRun) {
782
+ removeManifestItems(destDir, existingManifest);
783
+ }
784
+
785
+ let result;
703
786
  try {
704
- copied = copyDir(SKILLS_DIR, destDir, { dryRun, transform });
787
+ result = copySkills(SKILLS_DIR, destDir, { format: agent.format, dryRun });
705
788
  } catch (err) {
706
789
  logError(err.message);
707
790
  process.exit(1);
708
791
  }
709
792
 
710
793
  if (!dryRun) {
711
- writeSentinel(destDir);
794
+ writeManifest(destDir, agent.format, result.items);
712
795
  }
713
796
 
714
- log(' ' + green(`${dryRun ? 'would copy' : 'copied'} ${copied.length} files.`));
797
+ log(' ' + green(`${dryRun ? 'would copy' : 'copied'} ${result.copied.length} files (${result.items.length} items).`));
715
798
  if (!dryRun && agent.format === 'mdc') {
716
799
  log(' ' + gray('SKILL.md files converted to .mdc rule format.'));
717
800
  }
718
801
  return true;
719
802
  }
720
803
 
804
+ // Remove every item listed in the given manifest from destDir, then
805
+ // remove the manifest file itself. Items are paths relative to destDir
806
+ // — for 'skill' format they're folder names (`brief`, `srs`, ...,
807
+ // `references`), for 'mdc' they're file names (`brief.mdc`, ...,
808
+ // plus the `references` folder). Anything not in the manifest is left
809
+ // alone, including the user's other skills/rules in the same directory.
810
+ function removeManifestItems(destDir, manifest) {
811
+ for (const item of manifest.items) {
812
+ const p = path.join(destDir, item);
813
+ if (fs.existsSync(p)) {
814
+ fs.rmSync(p, { recursive: true, force: true });
815
+ }
816
+ }
817
+ const manifestPath = path.join(destDir, MANIFEST_FILENAME);
818
+ if (fs.existsSync(manifestPath)) {
819
+ fs.rmSync(manifestPath, { force: true });
820
+ }
821
+ }
822
+
823
+ // Print a yellow warning if the v1.x wrapper directory is still around
824
+ // in the same skills root. The wrapper made every shipped skill
825
+ // invisible to the agent, so any user upgrading from v1 needs to
826
+ // remove it manually before v2.0 can install correctly.
827
+ function warnLegacyInstall(agent) {
828
+ const legacy = detectLegacyInstall(agent);
829
+ if (legacy.length === 0) return;
830
+ log('');
831
+ log(' ' + yellow('! Legacy v1.x install detected — must be removed before v2.0 will work:'));
832
+ for (const p of legacy) {
833
+ log(' ' + yellow(` ${p}`));
834
+ }
835
+ log(' ' + yellow(' v2.0 dropped the ba-toolkit/ wrapper folder; the agent ignored every skill nested under it.'));
836
+ log(' ' + yellow(' Remove the legacy folder manually:'));
837
+ for (const p of legacy) {
838
+ log(' ' + gray(` rm -rf "${p}"`));
839
+ }
840
+ log('');
841
+ }
842
+
721
843
  async function cmdInstall(args) {
722
844
  const agentId = stringFlag(args, 'for');
723
845
  if (!agentId) {
@@ -775,74 +897,82 @@ function cmdStatus() {
775
897
  log(` scanning from: ${process.cwd()}`);
776
898
  log('');
777
899
 
778
- // Walk every (agent × scope) combination and collect the ones whose
779
- // destination directory actually exists. Project-scope paths resolve
780
- // against the current working directory; global paths are absolute.
900
+ // Walk every (agent × scope) combination and collect the ones that
901
+ // have a v2.0 manifest. Project-scope paths resolve against the
902
+ // current working directory; global paths are absolute.
781
903
  const rows = [];
904
+ const legacyRows = [];
905
+
906
+ const checkLocation = (agent, agentId, scope, dir) => {
907
+ if (!fs.existsSync(dir)) return;
908
+ const manifest = readManifest(dir);
909
+ if (manifest) {
910
+ rows.push({
911
+ agentName: agent.name,
912
+ agentId,
913
+ scope,
914
+ path: dir,
915
+ version: manifest.version,
916
+ installedAt: manifest.installedAt,
917
+ itemCount: manifest.items.length,
918
+ });
919
+ }
920
+ };
921
+
782
922
  for (const [agentId, agent] of Object.entries(AGENTS)) {
783
923
  if (agent.projectPath) {
784
924
  const projectDir = path.resolve(process.cwd(), agent.projectPath);
785
- if (fs.existsSync(projectDir)) {
786
- const sentinel = readSentinel(projectDir);
787
- rows.push({
788
- agentName: agent.name,
789
- agentId,
790
- scope: 'project',
791
- path: projectDir,
792
- version: sentinel ? sentinel.version : null,
793
- installedAt: sentinel ? sentinel.installedAt : null,
794
- });
795
- }
925
+ checkLocation(agent, agentId, 'project', projectDir);
796
926
  }
797
927
  if (agent.globalPath) {
798
- if (fs.existsSync(agent.globalPath)) {
799
- const sentinel = readSentinel(agent.globalPath);
800
- rows.push({
801
- agentName: agent.name,
802
- agentId,
803
- scope: 'global',
804
- path: agent.globalPath,
805
- version: sentinel ? sentinel.version : null,
806
- installedAt: sentinel ? sentinel.installedAt : null,
807
- });
808
- }
928
+ checkLocation(agent, agentId, 'global', agent.globalPath);
929
+ }
930
+ // Surface any v1.x wrapper folders as a separate "legacy" row.
931
+ for (const legacyPath of detectLegacyInstall(agent)) {
932
+ legacyRows.push({ agentName: agent.name, agentId, path: legacyPath });
809
933
  }
810
934
  }
811
935
 
812
- if (rows.length === 0) {
936
+ if (rows.length === 0 && legacyRows.length === 0) {
813
937
  log(' ' + gray('No BA Toolkit installations found in any known location.'));
814
938
  log(' ' + gray("Run 'ba-toolkit install --for <agent>' to install one."));
815
939
  log('');
816
940
  return;
817
941
  }
818
942
 
819
- log(` Found ${bold(rows.length)} installation${rows.length === 1 ? '' : 's'}:`);
820
- log('');
821
-
822
- for (const row of rows) {
823
- let versionLabel;
824
- if (!row.version) {
825
- versionLabel = gray('(unknown — pre-1.4 install with no sentinel)');
826
- } else if (row.version === PKG.version) {
827
- versionLabel = green(row.version + ' (current)');
828
- } else {
829
- versionLabel = yellow(row.version + ' (outdated)');
830
- }
831
- log(` ${bold(row.agentName)} ${gray('(' + row.agentId + ', ' + row.scope + ')')}`);
832
- log(` path: ${row.path}`);
833
- log(` version: ${versionLabel}`);
834
- if (row.installedAt) {
943
+ if (rows.length > 0) {
944
+ log(` Found ${bold(rows.length)} installation${rows.length === 1 ? '' : 's'}:`);
945
+ log('');
946
+ for (const row of rows) {
947
+ const versionLabel = row.version === PKG.version
948
+ ? green(row.version + ' (current)')
949
+ : yellow(row.version + ' (outdated)');
950
+ log(` ${bold(row.agentName)} ${gray('(' + row.agentId + ', ' + row.scope + ')')}`);
951
+ log(` path: ${row.path}`);
952
+ log(` version: ${versionLabel}`);
953
+ log(` items: ${row.itemCount}`);
835
954
  log(` installed: ${gray(row.installedAt)}`);
955
+ log('');
836
956
  }
957
+ }
958
+
959
+ if (legacyRows.length > 0) {
960
+ log(' ' + yellow(`Found ${legacyRows.length} legacy v1.x install${legacyRows.length === 1 ? '' : 's'} (broken — invisible to the agent):`));
837
961
  log('');
962
+ for (const row of legacyRows) {
963
+ log(` ${bold(row.agentName)} ${gray('(' + row.agentId + ', legacy wrapper)')}`);
964
+ log(` path: ${row.path}`);
965
+ log(` fix: ` + gray(`rm -rf "${row.path}" && ba-toolkit install --for ${row.agentId}`));
966
+ log('');
967
+ }
838
968
  }
839
969
 
840
- const stale = rows.filter((r) => !r.version || r.version !== PKG.version);
970
+ const stale = rows.filter((r) => r.version !== PKG.version);
841
971
  if (stale.length > 0) {
842
972
  log(' ' + yellow(`${stale.length} installation${stale.length === 1 ? '' : 's'} not at version ${PKG.version}.`));
843
973
  log(' ' + gray("Run 'ba-toolkit upgrade --for <agent>' to refresh."));
844
974
  log('');
845
- } else {
975
+ } else if (rows.length > 0) {
846
976
  log(' ' + green('All installations are up to date.'));
847
977
  log('');
848
978
  }
@@ -869,54 +999,47 @@ async function cmdUpgrade(args) {
869
999
  log(` destination: ${destDir}`);
870
1000
  log(` scope: ${effectiveGlobal ? 'global (user-wide)' : 'project-level'}`);
871
1001
 
1002
+ warnLegacyInstall(agent);
1003
+
872
1004
  if (!fs.existsSync(destDir)) {
873
1005
  log('');
874
1006
  log(' ' + gray(`No installation found at ${destDir}.`));
875
- log(' ' + gray(`Run \`ba-toolkit install --for ${agentId}\` first.`));
1007
+ log(' ' + gray(`Run 'ba-toolkit install --for ${agentId}' first.`));
876
1008
  log('');
877
1009
  return;
878
1010
  }
879
1011
 
880
- const sentinel = readSentinel(destDir);
881
- const currentVersion = PKG.version;
882
- const installedVersion = sentinel ? sentinel.version : null;
1012
+ const manifest = readManifest(destDir);
1013
+ if (!manifest) {
1014
+ log('');
1015
+ log(' ' + gray('No BA Toolkit manifest found in this destination.'));
1016
+ log(' ' + gray(`Run 'ba-toolkit install --for ${agentId}' to install fresh.`));
1017
+ log('');
1018
+ return;
1019
+ }
883
1020
 
884
- if (installedVersion === currentVersion) {
885
- log(` installed: ${installedVersion} (current)`);
1021
+ const currentVersion = PKG.version;
1022
+ if (manifest.version === currentVersion) {
1023
+ log(` installed: ${manifest.version} (current)`);
886
1024
  log(` package: ${currentVersion}`);
887
1025
  log('');
888
1026
  log(' ' + green('Already up to date.'));
889
- log(' ' + gray(`To force a clean reinstall, run \`ba-toolkit install --for ${agentId}\`.`));
1027
+ log(' ' + gray(`To force a clean reinstall, run 'ba-toolkit install --for ${agentId}'.`));
890
1028
  log('');
891
1029
  return;
892
1030
  }
893
1031
 
894
- log(` installed: ${installedVersion || gray('(unknown — pre-1.4 install with no sentinel)')}`);
1032
+ log(` installed: ${manifest.version}`);
895
1033
  log(` package: ${currentVersion}`);
1034
+ log(` items: ${manifest.items.length}`);
896
1035
  if (dryRun) log(' ' + yellow('mode: dry-run (no files will be written)'));
897
1036
  log('');
898
1037
 
899
- // Safety: same guard as cmdUninstall — never rmSync anything that
900
- // doesn't look like a ba-toolkit folder.
901
- if (path.basename(destDir) !== 'ba-toolkit') {
902
- logError(`Refusing to upgrade suspicious destination (not a ba-toolkit folder): ${destDir}`);
903
- process.exit(1);
904
- }
905
-
906
- // Count files in the existing install for the dry-run preview.
907
1038
  if (dryRun) {
908
- let existingCount = 0;
909
- (function walk(d) {
910
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
911
- const p = path.join(d, entry.name);
912
- if (entry.isDirectory()) walk(p);
913
- else existingCount++;
914
- }
915
- })(destDir);
916
- log(' ' + yellow(`would remove ${existingCount} existing files`));
1039
+ log(' ' + yellow(`would remove ${manifest.items.length} previously-installed items, then re-copy the new tree`));
917
1040
  } else {
918
- log(' ' + green('Removing previous install...'));
919
- fs.rmSync(destDir, { recursive: true, force: true });
1041
+ log(' ' + green('Removing previous install (manifest-driven)...'));
1042
+ removeManifestItems(destDir, manifest);
920
1043
  }
921
1044
 
922
1045
  const ok = await runInstall({
@@ -956,51 +1079,49 @@ async function cmdUninstall(args) {
956
1079
  log(` destination: ${destDir}`);
957
1080
  log(` scope: ${effectiveGlobal ? 'global (user-wide)' : 'project-level'}`);
958
1081
  if (dryRun) log(' ' + yellow('mode: dry-run (no files will be removed)'));
959
- log('');
960
1082
 
961
- // Safety: this is the only place in the CLI that calls fs.rmSync with
962
- // recursive: true. Refuse to proceed unless the destination is clearly
963
- // a ba-toolkit folder (the install paths in AGENTS all end in
964
- // `ba-toolkit/`). Without this check, a corrupted AGENTS entry or a
965
- // future bug could turn this into `rm -rf $HOME`.
966
- if (path.basename(destDir) !== 'ba-toolkit') {
967
- logError(`Refusing to remove suspicious destination (not a ba-toolkit folder): ${destDir}`);
968
- process.exit(1);
969
- }
1083
+ warnLegacyInstall(agent);
970
1084
 
971
1085
  if (!fs.existsSync(destDir)) {
1086
+ log('');
972
1087
  log(' ' + gray(`Nothing to uninstall — ${destDir} does not exist.`));
973
1088
  log('');
974
1089
  return;
975
1090
  }
976
1091
 
977
- // Count files for the preview message and final confirmation.
978
- let fileCount = 0;
979
- (function walk(d) {
980
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
981
- const p = path.join(d, entry.name);
982
- if (entry.isDirectory()) walk(p);
983
- else fileCount++;
984
- }
985
- })(destDir);
1092
+ // The manifest is the proof we own anything in this directory. The
1093
+ // destination is shared with the user's other skills/rules, so we
1094
+ // can't just `rm -rf destDir` — we'd nuke unrelated files. Without
1095
+ // a manifest, refuse and tell the user.
1096
+ const manifest = readManifest(destDir);
1097
+ if (!manifest) {
1098
+ log('');
1099
+ log(' ' + gray('No BA Toolkit manifest found in this destination.'));
1100
+ log(' ' + gray('Either nothing was installed here, or the install pre-dates v2.0.'));
1101
+ log(' ' + gray('For a v1.x legacy wrapper, see the warning above (if any) for the path to remove manually.'));
1102
+ log('');
1103
+ return;
1104
+ }
986
1105
 
987
- log(` Found ${bold(fileCount)} files in the destination.`);
1106
+ log('');
1107
+ log(` installed: ${manifest.version}`);
1108
+ log(` items: ${bold(manifest.items.length)} (${manifest.items.slice(0, 5).join(', ')}${manifest.items.length > 5 ? ', …' : ''})`);
988
1109
 
989
1110
  if (dryRun) {
990
- log(' ' + yellow(`would remove ${fileCount} files from ${destDir}.`));
1111
+ log(' ' + yellow(`would remove ${manifest.items.length} items + the manifest from ${destDir}`));
991
1112
  log('');
992
1113
  return;
993
1114
  }
994
1115
 
995
1116
  log('');
996
- const answer = await prompt(` Remove ${destDir}? (y/N): `);
1117
+ const answer = await prompt(` Remove ${manifest.items.length} BA Toolkit items from ${destDir}? (y/N): `);
997
1118
  if (answer.toLowerCase() !== 'y') {
998
1119
  log(' Cancelled.');
999
1120
  log('');
1000
1121
  return;
1001
1122
  }
1002
- fs.rmSync(destDir, { recursive: true, force: true });
1003
- log(' ' + green(`Removed ${fileCount} files from ${destDir}.`));
1123
+ removeManifestItems(destDir, manifest);
1124
+ log(' ' + green(`Removed ${manifest.items.length} items.`));
1004
1125
  log(' ' + yellow(agent.restartHint));
1005
1126
  log('');
1006
1127
  }
@@ -1143,7 +1264,9 @@ module.exports = {
1143
1264
  levenshtein,
1144
1265
  closestMatch,
1145
1266
  parseSkillFrontmatter,
1146
- readSentinel,
1267
+ skillToMdcContent,
1268
+ readManifest,
1269
+ detectLegacyInstall,
1147
1270
  renderAgentsMd,
1148
1271
  KNOWN_FLAGS,
1149
1272
  DOMAINS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudusov.takhir/ba-toolkit",
3
- "version": "1.5.0",
3
+ "version": "2.0.0",
4
4
  "description": "AI-powered Business Analyst pipeline — 21 skills from project brief to development handoff. Works with Claude Code, Codex CLI, Gemini CLI, Cursor, and Windsurf.",
5
5
  "keywords": [
6
6
  "business-analyst",
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-ac
2
+ name: ac
3
3
  description: >
4
4
  Generate Acceptance Criteria in Given/When/Then (Gherkin) format for each User Story. Use on /ac command, or when the user asks for "acceptance criteria", "given when then", "gherkin scenarios", "write AC", "definition of done", "how to verify a story", "test scenarios for stories". Fifth step of the BA Toolkit pipeline.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-analyze
2
+ name: analyze
3
3
  description: >
4
4
  Cross-artifact quality analysis across all BA Toolkit pipeline artifacts. Use on /analyze command, or when the user asks for "quality check", "analyze artifacts", "find inconsistencies", "cross-artifact check", "check quality", "find duplicates", "terminology check", "coverage analysis", "what is inconsistent". Available at any pipeline stage after /srs. Generates a structured finding report with severity levels.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-apicontract
2
+ name: apicontract
3
3
  description: >
4
4
  Generate API contracts: endpoints, methods, parameters, request/response schemas, error codes. Markdown format approximating OpenAPI. Use on /apicontract command, or when the user asks for "API contract", "describe API", "endpoints", "REST API", "WebSocket API", "describe API", "integration contract", "swagger", "describe requests and responses", "webhook contract", "API specification". Eighth step of the BA Toolkit pipeline.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-brief
2
+ name: brief
3
3
  description: >
4
4
  Generate a high-level Project Brief for projects in any domain (SaaS, Fintech, E-commerce, Healthcare, Logistics, and others). Use this skill when the user enters /brief, or asks to "create a project brief", "describe the project", "start a new project", "project brief", or mentions the starting stage of the analytical pipeline. Also triggers on requests like "begin with a brief", "describe the product", "form a product description". First step of the BA Toolkit pipeline.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-clarify
2
+ name: clarify
3
3
  description: >
4
4
  Targeted ambiguity-resolution pass over a BA Toolkit artifact. Use on /clarify command, or when the user asks to "clarify requirements", "find ambiguities", "what is unclear", "check vague terms", "resolve ambiguities", "what needs clarification". Can be focused on a specific area: /clarify security, /clarify FR-012. Cross-cutting command available at any pipeline stage after the first artifact exists.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-datadict
2
+ name: datadict
3
3
  description: >
4
4
  Generate a Data Dictionary: entities, attributes, types, constraints, relationships. Use on /datadict command, or when the user asks for "data dictionary", "data model", "data schema", "describe entities", "ER model", "database structure", "describe tables", "entity attributes", "entity relationships", "domain model". Seventh step of the BA Toolkit pipeline.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-estimate
2
+ name: estimate
3
3
  description: >
4
4
  Effort estimation for BA Toolkit User Stories. Use on /estimate command, or when the user asks to "estimate stories", "add story points", "size the backlog", "estimate effort", "T-shirt sizing", "planning poker". Can target all stories or a specific epic/story. Run after /stories or /ac for best accuracy.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-export
2
+ name: export
3
3
  description: >
4
4
  Export BA Toolkit artifacts to external formats for import into issue trackers and project management tools. Use on /export command, or when the user asks to "export to Jira", "create GitHub issues", "export stories", "generate Linear tickets", "export to CSV", "import into tracker". Run after /stories and /ac for full export with acceptance criteria.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-glossary
2
+ name: glossary
3
3
  description: >
4
4
  Unified project glossary extraction and maintenance for BA Toolkit projects. Use on /glossary command, or when the user asks to "build a glossary", "extract terms", "create a glossary", "consolidate terminology", "find terminology drift", "what terms are defined". Cross-cutting command — can run at any pipeline stage once at least one artifact exists.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-handoff
2
+ name: handoff
3
3
  description: >
4
4
  Generate a development handoff package summarising the entire BA Toolkit pipeline: artifact inventory, MVP scope, open items, top risks, and recommended next steps. Use on /handoff command, or when the user asks to "prepare handoff", "create handoff document", "summarise the pipeline", "package for developers", "ready for development", "export to Jira", "what is left to do", "pipeline summary". Optional final step — available after /wireframes.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-nfr
2
+ name: nfr
3
3
  description: >
4
4
  Generate Non-functional Requirements (NFR): performance, security, availability, scalability, compliance, localization. Use on /nfr command, or when the user asks for "non-functional requirements", "NFR", "performance requirements", "security requirements", "SLA", "compliance requirements", "load requirements", "uptime requirements", "regulatory requirements", "GDPR". Sixth step of the BA Toolkit pipeline.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-principles
2
+ name: principles
3
3
  description: >
4
4
  Define project-level principles that govern the entire BA Toolkit pipeline: artifact language, ID conventions, traceability requirements, Definition of Ready per artifact type, mandatory NFR categories, and quality gates. Use on /principles command, or when the user asks to "set project standards", "define conventions", "establish principles", "set up pipeline rules", "configure traceability requirements", "define definition of ready". Optional step — run before /brief or immediately after it. All subsequent skills load and apply these principles automatically.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-research
2
+ name: research
3
3
  description: >
4
4
  Generate a technology and constraints research document before committing to API and data design. Covers integration options, API style choices, data storage alternatives, regulatory constraints, and ADR (Architecture Decision Records) with rationale. Use on /research command, or when the user asks for "technology research", "tech decisions", "architecture options", "integration research", "compare options", "what stack to use", "ADR", "architecture decision", "research constraints". Optional step — run after /datadict and before /apicontract.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-risk
2
+ name: risk
3
3
  description: >
4
4
  Dedicated risk register for BA Toolkit projects. Use on /risk command, or when the user asks to "identify risks", "create a risk register", "assess project risks", "list risks", "risk matrix". Cross-cutting command — can run at any pipeline stage once Brief or SRS exists. Re-run after Research to capture technical risks.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-scenarios
2
+ name: scenarios
3
3
  description: >
4
4
  Generate end-to-end validation scenarios linking user stories, acceptance criteria, API endpoints, and wireframes into complete user journeys for acceptance testing. Use on /scenarios command, or when the user asks for "validation scenarios", "end-to-end scenarios", "user journeys", "acceptance test scenarios", "E2E scenarios", "test cases", "walkthrough scenarios", "happy path scenarios", "test the product", "QA scenarios". Optional step — run after /wireframes.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-sprint
2
+ name: sprint
3
3
  description: >
4
4
  Sprint planning for BA Toolkit projects. Use on /sprint command, or when the user asks to "create a sprint plan", "plan sprints", "organise backlog into sprints", "sprint breakdown", "velocity planning", "release plan". Run after /estimate (required) and /risk (recommended). Generates 00_sprint_{slug}.md with sprint goals, story assignments, and capacity summary.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-srs
2
+ name: srs
3
3
  description: >
4
4
  Generate a Software Requirements Specification (SRS) based on the Project Brief. Adapted IEEE 830 format. Use on /srs command, or when the user asks for "requirements specification", "SRS", "functional requirements", "system requirements", "describe requirements", "write a technical specification". Second step of the BA Toolkit pipeline.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-stories
2
+ name: stories
3
3
  description: >
4
4
  Generate User Stories based on the SRS. Format: "As a [role], I want [action], so that [value]". Use on /stories command, or when the user asks for "user stories", "create stories", "write user stories", "story decomposition", "epics and stories", "backlog", "break requirements into stories". Third step of the BA Toolkit pipeline.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-trace
2
+ name: trace
3
3
  description: >
4
4
  Build and update the traceability matrix across all BA Toolkit pipeline artifacts: FR ↔ US ↔ UC ↔ AC ↔ NFR ↔ Data Entity ↔ API Endpoint ↔ Wireframe. Use on /trace command, or when the user asks for "traceability matrix", "requirements traceability", "coverage check", "uncovered requirements", "artifact links", "check coverage", "find missing requirements", "what is not covered". Cross-cutting command available at any stage after /stories.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-usecases
2
+ name: usecases
3
3
  description: >
4
4
  Generate Use Cases (Cockburn format) based on User Stories. Use on /usecases command, or when the user asks for "use cases", "scenarios", "describe scenarios", "interaction flows", "main and alternative flows", "describe system behavior", "user interaction scenario". Fourth step of the BA Toolkit pipeline.
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ba-wireframes
2
+ name: wireframes
3
3
  description: >
4
4
  Generate textual wireframe descriptions: screen structure, element placement, navigation, states (loading, empty, error). Use on /wireframes command, or when the user asks for "wireframe descriptions", "wireframes", "screen descriptions", "interface structure", "screen layouts", "UI description", "screen specification", "describe screens", "text prototype", "designer specification", "page descriptions". Ninth and final step of the BA Toolkit pipeline.
5
5
  ---