@dzhechkov/skills-bto 1.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/bin/cli.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ require('../src/cli.js');
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@dzhechkov/skills-bto",
3
+ "version": "1.0.0",
4
+ "description": "Build-Test-Optimize skill pack for Claude Code — structured BTO pipeline with quality gates, testing strategies, and optimization workflows",
5
+ "main": "src/cli.js",
6
+ "bin": {
7
+ "skills-bto": "./bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "templates/"
13
+ ],
14
+ "scripts": {
15
+ "test": "node bin/cli.js doctor",
16
+ "prepublishOnly": "node scripts/sync-templates.js"
17
+ },
18
+ "keywords": [
19
+ "claude",
20
+ "claude-code",
21
+ "ai",
22
+ "bto",
23
+ "build-test-optimize",
24
+ "skills",
25
+ "quality-gates",
26
+ "testing",
27
+ "optimization",
28
+ "skill-pack"
29
+ ],
30
+ "author": "dzhechko",
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=16.0.0"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/dzhechko/product-keysarium-2026"
38
+ },
39
+ "homepage": "https://github.com/dzhechko/product-keysarium-2026#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/dzhechko/product-keysarium-2026/issues"
42
+ }
43
+ }
package/src/cli.js ADDED
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { bold, cyan, dim, green, yellow, red, info, error } = require('./utils');
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Banner
8
+ // ---------------------------------------------------------------------------
9
+ function showBanner() {
10
+ console.log('');
11
+ console.log(cyan('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557'));
12
+ console.log(cyan('\u2551') + bold(' DZ-SKILLS-BTO \u2014 Build\u00B7Test\u00B7Optimize for Claude Code ') + cyan('\u2551'));
13
+ console.log(cyan('\u2551') + ' Structured BTO workflow with quality gates ' + cyan('\u2551'));
14
+ console.log(cyan('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D'));
15
+ console.log('');
16
+ }
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Version
20
+ // ---------------------------------------------------------------------------
21
+ function getVersion() {
22
+ const pkg = require('../package.json');
23
+ return pkg.version;
24
+ }
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Help
28
+ // ---------------------------------------------------------------------------
29
+ function showHelp() {
30
+ console.log(bold('Usage:') + ' npx @dzhechkov/skills-bto ' + cyan('<command>') + dim(' [options]'));
31
+ console.log('');
32
+ console.log(bold('Commands:'));
33
+ console.log(' ' + green('init') + ' Install BTO skill pack into current project ' + dim('(default)'));
34
+ console.log(' ' + green('update') + ' Update BTO skill pack to latest version');
35
+ console.log(' ' + green('remove') + ' Remove BTO skill pack from project');
36
+ console.log(' ' + green('list') + ' List installed BTO components');
37
+ console.log(' ' + green('doctor') + ' Check BTO installation health');
38
+ console.log('');
39
+ console.log(bold('Options:'));
40
+ console.log(' ' + yellow('--force') + ' Overwrite existing files without prompting');
41
+ console.log(' ' + yellow('--dry-run') + ' Show what would be done without making changes');
42
+ console.log(' ' + yellow('--help, -h') + ' Show this help message');
43
+ console.log(' ' + yellow('--version, -v') + ' Show version number');
44
+ console.log('');
45
+ console.log(bold('Examples:'));
46
+ console.log(dim(' $ ') + 'npx @dzhechkov/skills-bto init');
47
+ console.log(dim(' $ ') + 'npx @dzhechkov/skills-bto init --force');
48
+ console.log(dim(' $ ') + 'npx @dzhechkov/skills-bto update --dry-run');
49
+ console.log(dim(' $ ') + 'npx @dzhechkov/skills-bto doctor');
50
+ console.log('');
51
+ console.log(bold('Integration:'));
52
+ console.log(' Works alongside @dzhechkov/keysarium. Install both for the full toolkit.');
53
+ console.log('');
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Parse arguments
58
+ // ---------------------------------------------------------------------------
59
+ function parseArgs(argv) {
60
+ const args = argv.slice(2);
61
+
62
+ const flags = {
63
+ force: false,
64
+ dryRun: false,
65
+ targetDir: process.cwd(),
66
+ };
67
+
68
+ let command = null;
69
+
70
+ for (const arg of args) {
71
+ switch (arg) {
72
+ case '--force':
73
+ flags.force = true;
74
+ break;
75
+ case '--dry-run':
76
+ flags.dryRun = true;
77
+ break;
78
+ case '--help':
79
+ case '-h':
80
+ command = 'help';
81
+ break;
82
+ case '--version':
83
+ case '-v':
84
+ command = 'version';
85
+ break;
86
+ default:
87
+ if (!arg.startsWith('-') && command === null) {
88
+ command = arg;
89
+ }
90
+ break;
91
+ }
92
+ }
93
+
94
+ return { command, flags };
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Main
99
+ // ---------------------------------------------------------------------------
100
+ function main() {
101
+ const { command, flags } = parseArgs(process.argv);
102
+
103
+ // Handle version early (no banner)
104
+ if (command === 'version') {
105
+ console.log(getVersion());
106
+ return;
107
+ }
108
+
109
+ showBanner();
110
+
111
+ // Handle help early
112
+ if (command === 'help') {
113
+ showHelp();
114
+ return;
115
+ }
116
+
117
+ // Route to command handlers
118
+ const resolvedCommand = command || 'init';
119
+
120
+ switch (resolvedCommand) {
121
+ case 'init':
122
+ require('./commands/init')(flags);
123
+ break;
124
+
125
+ case 'update':
126
+ require('./commands/update')(flags);
127
+ break;
128
+
129
+ case 'remove':
130
+ require('./commands/remove')(flags);
131
+ break;
132
+
133
+ case 'list':
134
+ require('./commands/list')(flags);
135
+ break;
136
+
137
+ case 'doctor':
138
+ require('./commands/doctor')(flags);
139
+ break;
140
+
141
+ default:
142
+ error(`Unknown command: "${resolvedCommand}"`);
143
+ console.log('');
144
+ showHelp();
145
+ process.exitCode = 1;
146
+ break;
147
+ }
148
+ }
149
+
150
+ main();
@@ -0,0 +1,366 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const {
6
+ green, red, yellow, cyan, bold, dim,
7
+ info, success, warn, error: logError,
8
+ fileExists, readJSON, readManifest,
9
+ MANIFEST_FILE, getComponentFilter, COMPONENTS,
10
+ } = require('../utils');
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Check definitions
14
+ // ---------------------------------------------------------------------------
15
+
16
+ /**
17
+ * Run a single check. Returns { name, passed, detail, fix }.
18
+ */
19
+ function check(name, fn) {
20
+ try {
21
+ const result = fn();
22
+ return {
23
+ name,
24
+ passed: result.passed,
25
+ detail: result.detail || '',
26
+ fix: result.fix || '',
27
+ };
28
+ } catch (err) {
29
+ return {
30
+ name,
31
+ passed: false,
32
+ detail: `Exception: ${err.message}`,
33
+ fix: 'Investigate the error above.',
34
+ };
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Check 1: Verify every file in manifest exists on disk.
40
+ */
41
+ function checkFilesExist(manifest, targetDir) {
42
+ return check('Files exist', () => {
43
+ const files = manifest.files || [];
44
+ const missing = [];
45
+
46
+ for (const relPath of files) {
47
+ const absPath = path.join(targetDir, relPath);
48
+ if (!fileExists(absPath)) {
49
+ missing.push(relPath);
50
+ }
51
+ }
52
+
53
+ if (missing.length === 0) {
54
+ return {
55
+ passed: true,
56
+ detail: `All ${files.length} manifest files present`,
57
+ };
58
+ }
59
+
60
+ return {
61
+ passed: false,
62
+ detail: `${missing.length} file(s) missing: ${missing.slice(0, 5).join(', ')}${missing.length > 5 ? '...' : ''}`,
63
+ fix: `Run ${cyan('@dzhechkov/skills-bto update')} to restore missing files.`,
64
+ };
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Check 2: BTO skill directory has SKILL.md.
70
+ */
71
+ function checkBtoSkillComplete(targetDir) {
72
+ return check('BTO skill pack', () => {
73
+ const btoSkillDir = path.join(targetDir, '.claude', 'skills', 'bto');
74
+
75
+ if (!fileExists(btoSkillDir)) {
76
+ return {
77
+ passed: false,
78
+ detail: '.claude/skills/bto/ directory not found',
79
+ fix: `Run ${cyan('@dzhechkov/skills-bto update')} to restore BTO skill.`,
80
+ };
81
+ }
82
+
83
+ const skillMdPath = path.join(btoSkillDir, 'SKILL.md');
84
+ if (!fileExists(skillMdPath)) {
85
+ return {
86
+ passed: false,
87
+ detail: '.claude/skills/bto/SKILL.md not found',
88
+ fix: `Run ${cyan('@dzhechkov/skills-bto update')} to restore SKILL.md.`,
89
+ };
90
+ }
91
+
92
+ // Check for BTO sub-modules
93
+ let entries;
94
+ try {
95
+ entries = fs.readdirSync(btoSkillDir);
96
+ } catch {
97
+ return {
98
+ passed: false,
99
+ detail: 'Cannot read .claude/skills/bto/ directory',
100
+ fix: 'Check directory permissions.',
101
+ };
102
+ }
103
+
104
+ const mdFiles = entries.filter((e) => e.endsWith('.md'));
105
+
106
+ return {
107
+ passed: true,
108
+ detail: `SKILL.md present, ${mdFiles.length} file(s) in skill pack`,
109
+ };
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Check 3: BTO commands are present (bto*.md files in .claude/commands/).
115
+ */
116
+ function checkBtoCommandsPresent(targetDir) {
117
+ return check('BTO commands', () => {
118
+ const commandsDir = path.join(targetDir, '.claude', 'commands');
119
+
120
+ if (!fileExists(commandsDir)) {
121
+ return {
122
+ passed: false,
123
+ detail: '.claude/commands/ directory not found',
124
+ fix: `Run ${cyan('@dzhechkov/skills-bto update')} to restore commands.`,
125
+ };
126
+ }
127
+
128
+ let entries;
129
+ try {
130
+ entries = fs.readdirSync(commandsDir);
131
+ } catch {
132
+ return {
133
+ passed: false,
134
+ detail: 'Cannot read .claude/commands/ directory',
135
+ fix: 'Check directory permissions.',
136
+ };
137
+ }
138
+
139
+ // Filter for BTO commands only (bto*.md)
140
+ const btoCommands = entries.filter((e) => e.startsWith('bto') && e.endsWith('.md'));
141
+
142
+ if (btoCommands.length === 0) {
143
+ return {
144
+ passed: false,
145
+ detail: 'No BTO command files found (expected bto*.md)',
146
+ fix: `Run ${cyan('@dzhechkov/skills-bto update')} to restore BTO commands.`,
147
+ };
148
+ }
149
+
150
+ return {
151
+ passed: true,
152
+ detail: `${btoCommands.length} BTO command(s): ${btoCommands.join(', ')}`,
153
+ };
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Check 4: BTO rules are present (bto-*.md files in .claude/rules/).
159
+ */
160
+ function checkBtoRulesPresent(targetDir) {
161
+ return check('BTO rules', () => {
162
+ const rulesDir = path.join(targetDir, '.claude', 'rules');
163
+
164
+ if (!fileExists(rulesDir)) {
165
+ return {
166
+ passed: false,
167
+ detail: '.claude/rules/ directory not found',
168
+ fix: `Run ${cyan('@dzhechkov/skills-bto update')} to restore rules.`,
169
+ };
170
+ }
171
+
172
+ let entries;
173
+ try {
174
+ entries = fs.readdirSync(rulesDir);
175
+ } catch {
176
+ return {
177
+ passed: false,
178
+ detail: 'Cannot read .claude/rules/ directory',
179
+ fix: 'Check directory permissions.',
180
+ };
181
+ }
182
+
183
+ // Filter for BTO rules only (bto-*.md)
184
+ const btoRules = entries.filter((e) => e.startsWith('bto-') && e.endsWith('.md'));
185
+
186
+ if (btoRules.length === 0) {
187
+ return {
188
+ passed: false,
189
+ detail: 'No BTO rule files found (expected bto-*.md)',
190
+ fix: `Run ${cyan('@dzhechkov/skills-bto update')} to restore BTO rules.`,
191
+ };
192
+ }
193
+
194
+ return {
195
+ passed: true,
196
+ detail: `${btoRules.length} BTO rule(s): ${btoRules.join(', ')}`,
197
+ };
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Check 5: BTO agent templates are present (bto-*.md files in .claude/agents/).
203
+ */
204
+ function checkBtoAgentsPresent(targetDir) {
205
+ return check('BTO agents', () => {
206
+ const agentsDir = path.join(targetDir, '.claude', 'agents');
207
+
208
+ if (!fileExists(agentsDir)) {
209
+ return {
210
+ passed: false,
211
+ detail: '.claude/agents/ directory not found',
212
+ fix: `Run ${cyan('@dzhechkov/skills-bto update')} to restore agents.`,
213
+ };
214
+ }
215
+
216
+ let entries;
217
+ try {
218
+ entries = fs.readdirSync(agentsDir);
219
+ } catch {
220
+ return {
221
+ passed: false,
222
+ detail: 'Cannot read .claude/agents/ directory',
223
+ fix: 'Check directory permissions.',
224
+ };
225
+ }
226
+
227
+ // Filter for BTO agents only (bto-*.md)
228
+ const btoAgents = entries.filter((e) => e.startsWith('bto-') && e.endsWith('.md'));
229
+
230
+ if (btoAgents.length === 0) {
231
+ return {
232
+ passed: false,
233
+ detail: 'No BTO agent files found (expected bto-*.md)',
234
+ fix: `Run ${cyan('@dzhechkov/skills-bto update')} to restore BTO agent templates.`,
235
+ };
236
+ }
237
+
238
+ return {
239
+ passed: true,
240
+ detail: `${btoAgents.length} BTO agent(s): ${btoAgents.join(', ')}`,
241
+ };
242
+ });
243
+ }
244
+
245
+ /**
246
+ * Check 6: Keysarium integration status.
247
+ * This is informational — not a pass/fail check, always passes.
248
+ */
249
+ function checkKeysariumIntegration(targetDir) {
250
+ return check('Keysarium integration', () => {
251
+ const keysariumPath = path.join(targetDir, '.keysarium.json');
252
+
253
+ if (!fileExists(keysariumPath)) {
254
+ return {
255
+ passed: true,
256
+ detail: 'Not installed (standalone BTO mode)',
257
+ };
258
+ }
259
+
260
+ const keysariumManifest = readJSON(keysariumPath);
261
+ if (!keysariumManifest) {
262
+ return {
263
+ passed: true,
264
+ detail: 'Detected but manifest unreadable',
265
+ };
266
+ }
267
+
268
+ return {
269
+ passed: true,
270
+ detail: `Active (Keysarium v${keysariumManifest.version})`,
271
+ };
272
+ });
273
+ }
274
+
275
+ // ---------------------------------------------------------------------------
276
+ // Main command
277
+ // ---------------------------------------------------------------------------
278
+
279
+ /**
280
+ * `@dzhechkov/skills-bto doctor` — Health check for the BTO skill pack installation.
281
+ *
282
+ * @param {object} options
283
+ * @param {string} options.targetDir — Project root directory
284
+ */
285
+ async function run(options) {
286
+ const { targetDir } = options;
287
+ const manifestPath = path.join(targetDir, MANIFEST_FILE);
288
+
289
+ // ── a) Check manifest exists ───────────────────────────────────────────
290
+ if (!fileExists(manifestPath)) {
291
+ logError('BTO skill pack is not installed in this directory.');
292
+ info(`Run ${cyan('@dzhechkov/skills-bto init')} to install.`);
293
+ process.exit(1);
294
+ }
295
+
296
+ const manifest = readManifest(targetDir);
297
+ if (!manifest) {
298
+ logError(`Failed to read ${MANIFEST_FILE} — file may be corrupted.`);
299
+ process.exit(1);
300
+ }
301
+
302
+ console.log('');
303
+ info(bold(`Running health checks for @dzhechkov/skills-bto v${manifest.version}...`));
304
+ console.log('');
305
+
306
+ // ── b) Run all checks ─────────────────────────────────────────────────
307
+ const results = [
308
+ checkFilesExist(manifest, targetDir),
309
+ checkBtoSkillComplete(targetDir),
310
+ checkBtoCommandsPresent(targetDir),
311
+ checkBtoRulesPresent(targetDir),
312
+ checkBtoAgentsPresent(targetDir),
313
+ checkKeysariumIntegration(targetDir),
314
+ ];
315
+
316
+ // ── c) Display results ─────────────────────────────────────────────────
317
+ let passedCount = 0;
318
+ const failures = [];
319
+
320
+ for (const result of results) {
321
+ const icon = result.passed ? green('\u2713') : red('\u2717');
322
+ const statusColor = result.passed ? green : red;
323
+ const detailStr = result.detail ? dim(` — ${result.detail}`) : '';
324
+
325
+ console.log(` ${icon} ${statusColor(result.name)}${detailStr}`);
326
+
327
+ if (result.passed) {
328
+ passedCount++;
329
+ } else {
330
+ failures.push(result);
331
+ }
332
+ }
333
+
334
+ // ── d) Summary ─────────────────────────────────────────────────────────
335
+ console.log('');
336
+ const total = results.length;
337
+
338
+ if (passedCount === total) {
339
+ success(bold(`${passedCount}/${total} checks passed — BTO installation is healthy!`));
340
+ console.log('');
341
+ process.exit(0);
342
+ }
343
+
344
+ warn(bold(`${passedCount}/${total} checks passed`));
345
+ console.log('');
346
+
347
+ // ── e) Show fix suggestions ────────────────────────────────────────────
348
+ console.log(bold('Fix suggestions:'));
349
+ console.log('');
350
+
351
+ for (const failure of failures) {
352
+ console.log(` ${red('\u2717')} ${bold(failure.name)}`);
353
+ if (failure.detail) {
354
+ console.log(` ${dim(failure.detail)}`);
355
+ }
356
+ if (failure.fix) {
357
+ console.log(` ${yellow('Fix:')} ${failure.fix}`);
358
+ }
359
+ console.log('');
360
+ }
361
+
362
+ process.exit(1);
363
+ }
364
+
365
+ module.exports = run;
366
+ module.exports.run = run;