@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 +58 -1
- package/README.md +20 -11
- package/bin/ba-toolkit.js +307 -184
- package/package.json +1 -1
- package/skills/ac/SKILL.md +1 -1
- package/skills/analyze/SKILL.md +1 -1
- package/skills/apicontract/SKILL.md +1 -1
- package/skills/brief/SKILL.md +1 -1
- package/skills/clarify/SKILL.md +1 -1
- package/skills/datadict/SKILL.md +1 -1
- package/skills/estimate/SKILL.md +1 -1
- package/skills/export/SKILL.md +1 -1
- package/skills/glossary/SKILL.md +1 -1
- package/skills/handoff/SKILL.md +1 -1
- package/skills/nfr/SKILL.md +1 -1
- package/skills/principles/SKILL.md +1 -1
- package/skills/research/SKILL.md +1 -1
- package/skills/risk/SKILL.md +1 -1
- package/skills/scenarios/SKILL.md +1 -1
- package/skills/sprint/SKILL.md +1 -1
- package/skills/srs/SKILL.md +1 -1
- package/skills/stories/SKILL.md +1 -1
- package/skills/trace/SKILL.md +1 -1
- package/skills/usecases/SKILL.md +1 -1
- package/skills/wireframes/SKILL.md +1 -1
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/
|
|
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
|
-
#
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
91
|
+
mkdir -p ~/.gemini/skills
|
|
92
|
+
cp -R ba-toolkit/skills/. ~/.gemini/skills/
|
|
85
93
|
|
|
86
94
|
# Or project-only
|
|
87
|
-
|
|
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 -
|
|
197
|
-
| **OpenAI Codex CLI** | Native | `cp -
|
|
198
|
-
| **Gemini CLI** | Native | Copy `skills
|
|
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
|
|
25
|
-
globalPath: path.join(os.homedir(), '.claude', 'skills'
|
|
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'
|
|
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
|
|
39
|
-
globalPath: path.join(os.homedir(), '.gemini', 'skills'
|
|
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
|
|
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
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
}
|
|
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
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
//
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
|
|
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
|
|
630
|
-
const p = path.join(destDir,
|
|
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
|
|
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,
|
|
708
|
+
path.join(destDir, MANIFEST_FILENAME),
|
|
646
709
|
JSON.stringify(payload, null, 2) + '\n',
|
|
647
710
|
);
|
|
648
711
|
}
|
|
649
712
|
|
|
650
|
-
//
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
//
|
|
655
|
-
|
|
656
|
-
const
|
|
657
|
-
if (
|
|
658
|
-
|
|
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 (
|
|
669
|
-
|
|
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
|
-
|
|
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
|
-
|
|
694
|
-
|
|
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
|
-
|
|
702
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
779
|
-
//
|
|
780
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
|
|
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) =>
|
|
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
|
|
1007
|
+
log(' ' + gray(`Run 'ba-toolkit install --for ${agentId}' first.`));
|
|
876
1008
|
log('');
|
|
877
1009
|
return;
|
|
878
1010
|
}
|
|
879
1011
|
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
885
|
-
|
|
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
|
|
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: ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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(
|
|
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 ${
|
|
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
|
-
|
|
1003
|
-
log(' ' + green(`Removed ${
|
|
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
|
-
|
|
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": "
|
|
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",
|
package/skills/ac/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/analyze/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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:
|
|
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
|
---
|
package/skills/brief/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/clarify/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/datadict/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/estimate/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/export/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/glossary/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/handoff/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/nfr/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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:
|
|
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
|
---
|
package/skills/research/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/risk/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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:
|
|
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
|
---
|
package/skills/sprint/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/srs/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/stories/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/trace/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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
|
---
|
package/skills/usecases/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
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:
|
|
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
|
---
|