@kudusov.takhir/ba-toolkit 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/bin/ba-toolkit.js +105 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,18 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [1.3.2] — 2026-04-08
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **`bin/ba-toolkit.js` `cmdInit` ignored `runInstall` return value.** When the user declined the "Overwrite? (y/N)" prompt for an existing skill destination, `runInstall` returned `false`, but `cmdInit` ignored it and printed the success path ("Project is ready. Next steps: Restart Claude Code to load the new skills") even though no skills were installed. The next-steps block now branches on the install result and tells the user how to retry: `ba-toolkit install --for {agentId}`.
|
|
19
|
+
- **`parseArgs` did not accept `--key=value` form.** The hand-rolled parser only understood `--key value` (space-separated). Users typing the GNU long-option style accepted by git/npm/gh (`--name=MyApp`, `--domain=saas`) silently lost the value — the flag was stored as `name=MyApp` set to `true` and the script then prompted for the project name interactively. Both forms now work and can be mixed in a single invocation. Splits on the first `=` only, so values containing further `=` characters are preserved (e.g. `--name="Foo=Bar"`).
|
|
20
|
+
- **No SIGINT handler / readline lifecycle issues.** Each `prompt()` call previously created a fresh `readline.Interface` and closed it after the answer. Hitting Ctrl+C mid-prompt killed Node abruptly and left some terminals in raw mode. Piped stdin that closed mid-flow caused the next prompt's promise to hang forever and the process to exit silently with no error message. Fixed by switching to a single shared `readline.Interface` per CLI invocation, adding a `process.on('SIGINT')` handler that prints a clean `Cancelled.` message and exits with code 130, and rejecting in-flight prompt promises with `err.code = 'INPUT_CLOSED'` when the input stream closes prematurely. The outer `main().catch(...)` filters this code to print a friendly two-line message instead of a Node stack trace.
|
|
21
|
+
- **Silent failure when slug could not be derived from a non-ASCII name.** `sanitiseSlug` strips everything outside `[a-z0-9-]`, so `--name "Проект Космос"` or `--name "🚀"` produced an empty derived slug. In the non-interactive path the script then exited with a generic `Invalid or empty slug` error with no clue about why. Now in the non-interactive path it prints `Cannot derive a slug from "{name}" — it contains no ASCII letters or digits` plus a one-line workaround (`Pass an explicit slug with --slug, e.g. --slug my-project`). In the interactive path it prints a gray hint above the manual slug prompt explaining we couldn't auto-derive.
|
|
22
|
+
- **`AGENTS.md` template was missing 8 of the 21 skills.** `renderAgentsMd` emitted a "Pipeline Status" table with 13 rows — the 12 numbered stages + the `7a` sub-step. The 8 cross-cutting utilities added in v1.1 and v1.2 (`/trace`, `/clarify`, `/analyze`, `/estimate`, `/glossary`, `/export`, `/risk`, `/sprint`) were missing entirely. Since `AGENTS.md` is the project context every AI agent reads on entry to a new session, those 8 skills were effectively invisible — agents didn't know they existed without re-reading README. Added a second "Cross-cutting Tools" section below the pipeline table listing all 8, with descriptions copied from the canonical README pipeline table. A `MAINTENANCE` comment above the function reminds future-me to update both tables when adding a new skill.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
14
26
|
## [1.3.1] — 2026-04-08
|
|
15
27
|
|
|
16
28
|
### Fixed
|
|
@@ -234,7 +246,8 @@ CI scripts that relied on the old behaviour (`init` creates files only, `install
|
|
|
234
246
|
|
|
235
247
|
---
|
|
236
248
|
|
|
237
|
-
[Unreleased]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.3.
|
|
249
|
+
[Unreleased]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.3.2...HEAD
|
|
250
|
+
[1.3.2]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.3.1...v1.3.2
|
|
238
251
|
[1.3.1]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.3.0...v1.3.1
|
|
239
252
|
[1.3.0]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.2.5...v1.3.0
|
|
240
253
|
[1.2.5]: https://github.com/TakhirKudusov/ba-toolkit/compare/v1.2.4...v1.2.5
|
package/bin/ba-toolkit.js
CHANGED
|
@@ -94,6 +94,15 @@ function parseArgs(argv) {
|
|
|
94
94
|
break;
|
|
95
95
|
}
|
|
96
96
|
if (a.startsWith('--')) {
|
|
97
|
+
// Support --key=value form (in addition to --key value). The `=` must
|
|
98
|
+
// come after at least one character of key, so `--=value` and `--`
|
|
99
|
+
// alone fall through. Splits on the FIRST `=` only — values may
|
|
100
|
+
// contain further `=` characters.
|
|
101
|
+
const eqIdx = a.indexOf('=');
|
|
102
|
+
if (eqIdx > 2) {
|
|
103
|
+
args.flags[a.slice(2, eqIdx)] = a.slice(eqIdx + 1);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
97
106
|
const key = a.slice(2);
|
|
98
107
|
const next = argv[i + 1];
|
|
99
108
|
if (next !== undefined && !next.startsWith('-')) {
|
|
@@ -114,16 +123,42 @@ function parseArgs(argv) {
|
|
|
114
123
|
|
|
115
124
|
// --- Prompt helper -----------------------------------------------------
|
|
116
125
|
|
|
126
|
+
// Shared across all prompts in a single CLI invocation. Creating a new
|
|
127
|
+
// readline.Interface for every question (the previous approach) made Ctrl+C
|
|
128
|
+
// handling unreliable, leaked listeners on stdin, and broke when stdin was
|
|
129
|
+
// piped (EOF on the second create). One interface per process, closed by
|
|
130
|
+
// closeReadline() once main() finishes (or by the SIGINT handler).
|
|
131
|
+
let sharedRl = null;
|
|
132
|
+
|
|
117
133
|
function prompt(question) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
134
|
+
if (!sharedRl) {
|
|
135
|
+
sharedRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
136
|
+
}
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
let answered = false;
|
|
139
|
+
const onClose = () => {
|
|
140
|
+
if (!answered) {
|
|
141
|
+
const err = new Error('input stream closed before answer');
|
|
142
|
+
err.code = 'INPUT_CLOSED';
|
|
143
|
+
reject(err);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
sharedRl.once('close', onClose);
|
|
147
|
+
sharedRl.question(question, (answer) => {
|
|
148
|
+
answered = true;
|
|
149
|
+
sharedRl.removeListener('close', onClose);
|
|
122
150
|
resolve(answer.trim());
|
|
123
151
|
});
|
|
124
152
|
});
|
|
125
153
|
}
|
|
126
154
|
|
|
155
|
+
function closeReadline() {
|
|
156
|
+
if (sharedRl) {
|
|
157
|
+
sharedRl.close();
|
|
158
|
+
sharedRl = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
127
162
|
// --- Utilities ---------------------------------------------------------
|
|
128
163
|
|
|
129
164
|
function sanitiseSlug(input) {
|
|
@@ -222,6 +257,12 @@ function skillToMdc(srcPath, destPath) {
|
|
|
222
257
|
return { destPath: newDestPath, content: mdcFrontmatter + body };
|
|
223
258
|
}
|
|
224
259
|
|
|
260
|
+
// MAINTENANCE: when adding a new skill, update BOTH tables below.
|
|
261
|
+
// - Sequential pipeline stages (numbered 0-11 + 7a sub-step) go in
|
|
262
|
+
// "Pipeline Status" — they have a per-project status that progresses.
|
|
263
|
+
// - Cross-cutting utilities (no fixed stage) go in "Cross-cutting Tools".
|
|
264
|
+
// The README.md pipeline table is the canonical source of truth for the
|
|
265
|
+
// 21-skill list and ordering; keep this template in sync with it.
|
|
225
266
|
function renderAgentsMd({ name, slug, domain }) {
|
|
226
267
|
return `# BA Toolkit — Project Context
|
|
227
268
|
|
|
@@ -253,6 +294,21 @@ function renderAgentsMd({ name, slug, domain }) {
|
|
|
253
294
|
| 10 | /scenarios | ⬜ Not started | — |
|
|
254
295
|
| 11 | /handoff | ⬜ Not started | — |
|
|
255
296
|
|
|
297
|
+
## Cross-cutting Tools
|
|
298
|
+
|
|
299
|
+
Utilities available throughout the pipeline. No fixed stage — invoke whenever they help. See README.md for the prerequisites of each.
|
|
300
|
+
|
|
301
|
+
| Tool | Purpose |
|
|
302
|
+
|------|---------|
|
|
303
|
+
| /trace | Traceability Matrix + coverage gaps |
|
|
304
|
+
| /clarify [focus] | Targeted ambiguity resolution for any artifact |
|
|
305
|
+
| /analyze | Cross-artifact quality report with severity-rated findings |
|
|
306
|
+
| /estimate | Effort estimation — Fibonacci SP, T-shirt sizes, or person-days |
|
|
307
|
+
| /glossary | Unified project glossary with terminology drift detection |
|
|
308
|
+
| /export [format] | Export User Stories to Jira / GitHub Issues / Linear / CSV |
|
|
309
|
+
| /risk | Risk register — probability × impact matrix, mitigation per risk |
|
|
310
|
+
| /sprint | Sprint plan — stories grouped by velocity and capacity with sprint goals |
|
|
311
|
+
|
|
256
312
|
## Key Constraints
|
|
257
313
|
|
|
258
314
|
- Domain: ${domain}
|
|
@@ -293,12 +349,22 @@ async function cmdInit(args) {
|
|
|
293
349
|
if (!slug) {
|
|
294
350
|
const derived = sanitiseSlug(name);
|
|
295
351
|
if (nameFromFlag) {
|
|
296
|
-
// Non-interactive path
|
|
352
|
+
// Non-interactive path. Either accept the derived slug, or fail
|
|
353
|
+
// loudly with a hint when the name has no ASCII letters/digits to
|
|
354
|
+
// derive from (e.g. `--name "Проект"` or `--name "🚀"`). Without
|
|
355
|
+
// this branch the user got an opaque "Invalid or empty slug" with
|
|
356
|
+
// no clue why.
|
|
357
|
+
if (!derived) {
|
|
358
|
+
logError(`Cannot derive a slug from "${name}" — it contains no ASCII letters or digits.`);
|
|
359
|
+
log('Pass an explicit slug with --slug, e.g. --slug my-project');
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
297
362
|
slug = derived;
|
|
298
363
|
} else if (derived) {
|
|
299
364
|
const custom = await prompt(` Project slug [${cyan(derived)}]: `);
|
|
300
365
|
slug = custom || derived;
|
|
301
366
|
} else {
|
|
367
|
+
log(' ' + gray(`(could not derive a slug from "${name}" — please type one manually)`));
|
|
302
368
|
slug = await prompt(' Project slug (lowercase, hyphens only): ');
|
|
303
369
|
}
|
|
304
370
|
}
|
|
@@ -389,9 +455,13 @@ async function cmdInit(args) {
|
|
|
389
455
|
}
|
|
390
456
|
|
|
391
457
|
// --- 6. Install skills for the selected agent ---
|
|
458
|
+
// installed: null = no install attempted (--no-install or no agentId),
|
|
459
|
+
// true = install succeeded,
|
|
460
|
+
// false = install was cancelled (e.g. user declined overwrite).
|
|
461
|
+
let installed = null;
|
|
392
462
|
if (!skipInstall && agentId) {
|
|
393
463
|
log('');
|
|
394
|
-
await runInstall({
|
|
464
|
+
installed = await runInstall({
|
|
395
465
|
agentId,
|
|
396
466
|
isGlobal: !!args.flags.global,
|
|
397
467
|
isProject: !!args.flags.project,
|
|
@@ -405,10 +475,16 @@ async function cmdInit(args) {
|
|
|
405
475
|
log(' ' + cyan(`Project '${name}' (${slug}) is ready.`));
|
|
406
476
|
log('');
|
|
407
477
|
log(' ' + yellow('Next steps:'));
|
|
408
|
-
if (
|
|
478
|
+
if (installed === true) {
|
|
409
479
|
log(' 1. ' + AGENTS[agentId].restartHint);
|
|
410
480
|
log(' 2. Optional: run /principles to define project-wide conventions');
|
|
411
481
|
log(' 3. Run /brief to start the BA pipeline');
|
|
482
|
+
} else if (installed === false) {
|
|
483
|
+
log(' 1. Skill install was cancelled. To install later, run:');
|
|
484
|
+
log(' ' + gray(`ba-toolkit install --for ${agentId}`));
|
|
485
|
+
log(' 2. Open your AI assistant (Claude, Cursor, etc.)');
|
|
486
|
+
log(' 3. Optional: run /principles to define project-wide conventions');
|
|
487
|
+
log(' 4. Run /brief to start the BA pipeline');
|
|
412
488
|
} else {
|
|
413
489
|
log(' 1. Install skills for your agent:');
|
|
414
490
|
log(' ' + gray('ba-toolkit install --for claude-code'));
|
|
@@ -597,7 +673,26 @@ async function main() {
|
|
|
597
673
|
}
|
|
598
674
|
}
|
|
599
675
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
676
|
+
// Clean exit on Ctrl+C: print on a fresh line so we don't append to a
|
|
677
|
+
// half-typed prompt, close the readline interface so the terminal is
|
|
678
|
+
// returned to a sane state, then exit with the conventional 130 code.
|
|
679
|
+
process.on('SIGINT', () => {
|
|
680
|
+
console.log('\n ' + yellow('Cancelled.'));
|
|
681
|
+
closeReadline();
|
|
682
|
+
process.exit(130);
|
|
603
683
|
});
|
|
684
|
+
|
|
685
|
+
main()
|
|
686
|
+
.then(() => {
|
|
687
|
+
closeReadline();
|
|
688
|
+
})
|
|
689
|
+
.catch((err) => {
|
|
690
|
+
closeReadline();
|
|
691
|
+
if (err && err.code === 'INPUT_CLOSED') {
|
|
692
|
+
logError('Input stream closed before all prompts could be answered.');
|
|
693
|
+
log('Pass remaining values as flags (e.g. --name, --domain, --for) or run interactively.');
|
|
694
|
+
process.exit(1);
|
|
695
|
+
}
|
|
696
|
+
logError(err && (err.stack || err.message) || String(err));
|
|
697
|
+
process.exit(1);
|
|
698
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kudusov.takhir/ba-toolkit",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
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",
|