@kudusov.takhir/ba-toolkit 3.1.1 → 3.2.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 +26 -1
- package/COMMANDS.md +4 -2
- package/README.md +21 -13
- package/bin/ba-toolkit.js +416 -7
- package/package.json +2 -2
- package/skills/brief/SKILL.md +2 -0
- package/skills/discovery/SKILL.md +159 -0
- package/skills/publish/SKILL.md +79 -0
- package/skills/references/closing-message.md +2 -0
- package/skills/references/templates/agents-template.md +3 -1
- package/skills/references/templates/discovery-template.md +63 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,30 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [3.2.0] — 2026-04-09
|
|
15
|
+
|
|
16
|
+
### Highlights
|
|
17
|
+
|
|
18
|
+
- **New `/discovery` skill — concept brain-storm before `/brief`** for users who don't yet know what to build.
|
|
19
|
+
- **`ba-toolkit publish` CLI subcommand + `/publish` skill** — one-command Notion (Markdown) and Confluence (HTML) import-ready bundles, zero deps, zero tokens, zero network.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **New `ba-toolkit publish` CLI subcommand + `/publish` skill — Notion / Confluence bundle export.** Bundles every BA Toolkit artifact in the current `output/<slug>/` folder into import-ready files for two destinations: a clean Markdown bundle (`publish/notion/`) for Notion's bulk **Import → Markdown & CSV** dialog, and an HTML bundle with an `index.html` entry point (`publish/confluence/`) for Confluence's **Space settings → Content tools → Import → HTML** tool. **Zero API calls, zero tokens, zero network** — the conversion happens entirely on disk and the user does the upload manually using each tool's native importer. Cross-references between artifacts (`[FR-001](02_srs_<slug>.md#fr-001)`) are rewritten per target: Notion gets `./02_srs_<slug>.md#fr-001`, Confluence gets `02_srs_<slug>.html#fr-001`, and external HTTP/HTTPS links pass through unchanged. `AGENTS.md` (if present) is included as the first page in both bundles, with the `<!-- ba-toolkit:begin managed -->` block stripped so the management markers don't render as visible text. Surface: `ba-toolkit publish [--format notion|confluence|both] [--out PATH] [--dry-run]`. Default format is `both`, default output is `./publish/`. Comes with a thin `skills/publish/SKILL.md` discoverability layer that the AI agent invokes via the Bash tool when the user types `/publish` in any supported agent (Claude Code, Codex, Gemini, Cursor, Windsurf).
|
|
24
|
+
- **In-tree `markdownToHtml` helper in `bin/ba-toolkit.js`.** Pure function, ~190 lines, handles the bounded Markdown surface used by every shipped artifact template: ATX headings (with auto-generated GitHub-style anchor IDs), paragraphs, bold / italic / inline code with placeholder-based stashing so emphasis inside link labels and code spans round-trips correctly, links, single-level unordered and ordered lists, GFM tables with thead/tbody, fenced code blocks (language hint preserved), blockquotes, horizontal rules, and HTML special-character escaping. Out of scope by design: nested lists, images, footnotes, math, raw HTML pass-through. Exported alongside `htmlEscape`, `slugifyHeading`, `rewriteLinks`, `stripManagedBlock`, `compareArtifactFilenames`, and `ARTIFACT_FILE_RE` so the test suite can cover each piece in isolation.
|
|
25
|
+
- **23 new tests covering the publish flow.** 14 unit tests in `test/cli.test.js` for the converter and supporting helpers (one per supported Markdown element class plus link rewriting, managed-block stripping, the `7 < 7a < 8` filename sort, and the artifact-filename regex). 7 integration tests in `test/cli.integration.test.js` that spawn the real CLI against fixture markdown files inside a temp dir and assert the bundle layout, link rewriting in both modes, the Confluence `index.html` ordering, the AGENTS.md-as-first-page rule, the empty-directory error path, the invalid-format error path, and the `--dry-run` no-write contract. The skill-folder-count assertion bumps from `>= 22` to `>= 23` and the existing protocol-link / closing-message / Recommended-marker / 5-rows-cap regression tests auto-cover `skills/publish/SKILL.md`.
|
|
26
|
+
|
|
27
|
+
- **New `/discovery` skill — concept brain-storm before `/brief`.** For users who arrive with only a vague hunch and no fixed domain or feature list, `/discovery` runs a structured concept-discovery interview (problem space, target audience hypotheses, candidate domains, reference products, MVP feature ideas, differentiation angle, open validation questions) and writes a hypothesis document to `00_discovery_{slug}.md`. The artifact ends with a concrete recommendation (chosen domain, project name, slug, scope hint) that flows directly into `/brief` as inline context. Modeled on `/principles` (the only other optional pre-brief skill) — same workflow phases, same closing-message contract, same interview-protocol rules (5-row cap, Recommended marker, user-language variants). Lives at `skills/discovery/SKILL.md` with a matching artifact template at `skills/references/templates/discovery-template.md`. Pipeline lookup table in `closing-message.md` gains a new row `/discovery → /brief`. The `agents-template.md` Pipeline Status table inserts `/discovery` at stage `0` and demotes `/principles` to stage `0a` (mirror of how `/research` sits at stage `7a`) — `/brief` stays at stage `1`, no downstream renumbering, no risk of breaking existing AGENTS.md files for projects that already started. Skill is auto-discovered by the CLI from the `skills/` directory — no `bin/ba-toolkit.js` registration needed.
|
|
28
|
+
- **`/brief` consumes `00_discovery_*.md` if present.** `skills/brief/SKILL.md` Pipeline check phase now loads any existing discovery artifact, extracts the problem space, audience hypotheses, recommended domain, MVP feature hypotheses, and scope hint, and uses them to pre-fill the structured interview per protocol rule 9 — skipping any required topic the discovery already answered. The handoff is real, not a hint to copy-paste context manually.
|
|
29
|
+
- **`example/lumen-goods/00_discovery_lumen-goods.md`** — full concept-discovery walkthrough for the Lumen Goods example project. 8 sections matching the new template, length comparable to `00_principles_lumen-goods.md`. Keeps the lumen-goods example end-to-end consistent with the new pipeline entry point.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **Skill count bumped from 22 to 23** in every place that enumerated skills: `package.json` description, `README.md` (badge, intro, install sections, utility table, minimum-viable-pipeline section), `COMMANDS.md` Utility skills table, `CLAUDE.md` §1 and §4, `docs/USAGE.md` (interview-phase skill list, time-estimate appendix, new "Sharing artifacts with stakeholders" section), `docs/FAQ.md` (new Q/A: "How do I share the artifacts with non-developer stakeholders?"), `bin/ba-toolkit.js` stale comment, `skills/references/templates/agents-template.md` Cross-cutting Tools table (new `/publish [format]` row), `skills/references/closing-message.md` Cross-cutting commands list, and the `test/cli.test.js` skill-folder-count assertion (raised from `>= 22` to `>= 23`).
|
|
34
|
+
- **Skill count bumped from 21 to 22** in every place that enumerated skills: `package.json` description, `README.md` (badge, intro, install sections, example table, pipeline table, minimum-viable-pipeline section now lists three paths instead of two), `COMMANDS.md`, `CLAUDE.md` §1 and §4, `docs/USAGE.md` (interview-phase skill list, AGENTS.md sample, time-estimate appendix), `docs/FAQ.md` (new Q/A: "What if I don't know what to build yet?"), `bin/ba-toolkit.js` two stale comments, and the `test/cli.test.js` skill-folder-count assertion (raised from `>= 20` with stale "~21" comment to `>= 22`).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
14
38
|
## [3.1.1] — 2026-04-09
|
|
15
39
|
|
|
16
40
|
### Changed
|
|
@@ -451,7 +475,8 @@ CI scripts that relied on the old behaviour (`init` creates files only, `install
|
|
|
451
475
|
|
|
452
476
|
---
|
|
453
477
|
|
|
454
|
-
[Unreleased]: https://github.com/TakhirKudusov/ba-toolkit/compare/v3.
|
|
478
|
+
[Unreleased]: https://github.com/TakhirKudusov/ba-toolkit/compare/v3.2.0...HEAD
|
|
479
|
+
[3.2.0]: https://github.com/TakhirKudusov/ba-toolkit/compare/v3.1.1...v3.2.0
|
|
455
480
|
[3.1.1]: https://github.com/TakhirKudusov/ba-toolkit/compare/v3.1.0...v3.1.1
|
|
456
481
|
[3.1.0]: https://github.com/TakhirKudusov/ba-toolkit/compare/v3.0.0...v3.1.0
|
|
457
482
|
[3.0.0]: https://github.com/TakhirKudusov/ba-toolkit/compare/v2.0.0...v3.0.0
|
package/COMMANDS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# BA Toolkit — Command Reference
|
|
2
2
|
|
|
3
|
-
Quick reference for all
|
|
3
|
+
Quick reference for all 23 skills and subcommands.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -10,7 +10,8 @@ Run these in order. Each skill reads the output of all previous steps.
|
|
|
10
10
|
|
|
11
11
|
| # | Command | Output file | What it generates |
|
|
12
12
|
|:---:|---------|-------------|-------------------|
|
|
13
|
-
| 0 | `/
|
|
13
|
+
| 0 | `/discovery` | `00_discovery_{slug}.md` | Concept Discovery: problem space, audience hypotheses, candidate domains, MVP feature ideas, validation questions. Use when you don't yet know what to build — feeds the chosen domain, name, and scope hint into `/brief` |
|
|
14
|
+
| 0a | `/principles` | `00_principles_{slug}.md` | Project constitution: language, ID conventions, DoR, traceability rules, NFR baseline |
|
|
14
15
|
| 1 | `/brief` | `01_brief_{slug}.md` | Project Brief: goals, audience, stakeholders, constraints, risks. Updates the `AGENTS.md` Pipeline Status table (which `ba-toolkit init` already created) |
|
|
15
16
|
| 2 | `/srs` | `02_srs_{slug}.md` | Requirements Specification (IEEE 830): scope, FRs, constraints, assumptions |
|
|
16
17
|
| 3 | `/stories` | `03_stories_{slug}.md` | User Stories grouped by Epics, with priority and FR references |
|
|
@@ -38,6 +39,7 @@ Available at any pipeline stage.
|
|
|
38
39
|
| `/estimate` | `00_estimate_{slug}.md` | Effort estimation for User Stories: Fibonacci SP, T-shirt sizes, or person-days |
|
|
39
40
|
| `/glossary` | `00_glossary_{slug}.md` | Unified project glossary: scans all artifacts, detects terminology drift, undefined terms |
|
|
40
41
|
| `/export [format]` | `export_{slug}_{format}.json` / `.csv` | Export User Stories to Jira, GitHub Issues, Linear, or CSV |
|
|
42
|
+
| `/publish [format]` | `publish/notion/`, `publish/confluence/` | Bundle artifacts for Notion (Markdown bundle) and Confluence (HTML bundle) — drag-and-drop import, no API tokens. Wraps the `ba-toolkit publish` CLI subcommand |
|
|
41
43
|
| `/risk` | `00_risks_{slug}.md` | Risk register: probability × impact matrix, mitigation and contingency per risk |
|
|
42
44
|
| `/sprint` | `00_sprint_{slug}.md` | Sprint plan: stories grouped into sprints by velocity, capacity, and risk priority |
|
|
43
45
|
|
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# 📋 BA Toolkit
|
|
4
4
|
|
|
5
|
-
Structured BA pipeline for AI coding agents —
|
|
5
|
+
Structured BA pipeline for AI coding agents — concept to handoff, 23 skills, 9 domains, one-command Notion + Confluence publish.
|
|
6
6
|
|
|
7
|
-
<img src="https://img.shields.io/badge/skills-
|
|
7
|
+
<img src="https://img.shields.io/badge/skills-23-blue" alt="Skills">
|
|
8
8
|
<img src="https://img.shields.io/badge/domains-9-green" alt="Domains">
|
|
9
9
|
<img src="https://img.shields.io/badge/format-Markdown-orange" alt="Format">
|
|
10
10
|
<img src="https://img.shields.io/badge/language-auto--detect-purple" alt="Language">
|
|
@@ -22,7 +22,7 @@ Structured BA pipeline for AI coding agents — brief to handoff, 21 skills, 9 d
|
|
|
22
22
|
|
|
23
23
|
## What is this
|
|
24
24
|
|
|
25
|
-
BA Toolkit is a set of
|
|
25
|
+
BA Toolkit is a set of 23 interconnected skills that run a full business-analysis pipeline inside your AI coding agent. You can start as early as `/discovery` (a brain-storm step for users who don't yet know what to build) or jump straight to `/brief` if you already have a project in mind, then work all the way through to a development handoff package. Each skill reads the output of the previous ones — maintaining cross-references between artifacts along the chain `FR → US → UC → AC → NFR → Entity → ADR → API → WF → Scenario`. When you're ready to share with non-developer stakeholders, `/publish` (or `ba-toolkit publish`) bundles every artifact into import-ready folders for Notion and Confluence — drag-and-drop, no API tokens.
|
|
26
26
|
|
|
27
27
|
Unlike one-shot prompting, every artifact is written to disk as Markdown, every ID links back to its source, and `/trace` verifies coverage across the whole pipeline. `/clarify` and `/analyze` catch ambiguities and quality gaps with CRITICAL/HIGH severity ratings. Domain references for 9 industries (SaaS, Fintech, E-commerce, Healthcare, Logistics, On-demand, Social/Media, Real Estate, iGaming) plug in automatically at `/brief`.
|
|
28
28
|
|
|
@@ -114,11 +114,11 @@ Reload the CLI after copying.
|
|
|
114
114
|
|
|
115
115
|
### Cursor
|
|
116
116
|
|
|
117
|
-
Cursor has two separate features — Rules (`.cursor/rules/*.mdc`) and [Agent Skills](https://cursor.com/docs/skills) (`.cursor/skills/<skill>/SKILL.md`). BA Toolkit is a set of skills, not rules, so `ba-toolkit install --for cursor` drops the
|
|
117
|
+
Cursor has two separate features — Rules (`.cursor/rules/*.mdc`) and [Agent Skills](https://cursor.com/docs/skills) (`.cursor/skills/<skill>/SKILL.md`). BA Toolkit is a set of skills, not rules, so `ba-toolkit install --for cursor` drops the 23 skills directly into `.cursor/skills/` using the native folder-per-skill `SKILL.md` format — no conversion needed. Reload the Cursor window to pick them up.
|
|
118
118
|
|
|
119
119
|
### Windsurf
|
|
120
120
|
|
|
121
|
-
Windsurf's [Agent Skills](https://docs.windsurf.com/windsurf/cascade/skills) feature loads skills from `.windsurf/skills/<skill>/SKILL.md`, the same folder-per-skill layout as Claude Code and Cursor. `ba-toolkit install --for windsurf` writes the
|
|
121
|
+
Windsurf's [Agent Skills](https://docs.windsurf.com/windsurf/cascade/skills) feature loads skills from `.windsurf/skills/<skill>/SKILL.md`, the same folder-per-skill layout as Claude Code and Cursor. `ba-toolkit install --for windsurf` writes the 23 skills there natively. Reload the Windsurf window to pick them up.
|
|
122
122
|
|
|
123
123
|
### Aider
|
|
124
124
|
|
|
@@ -152,10 +152,11 @@ Your generated artifacts (`01_brief_*.md`, `02_srs_*.md`, …) are untouched by
|
|
|
152
152
|
|
|
153
153
|
## Example output
|
|
154
154
|
|
|
155
|
-
A complete example project — **Lumen Goods** (sustainable home-goods D2C online store) — lives in [`example/lumen-goods/`](example/lumen-goods/). All
|
|
155
|
+
A complete example project — **Lumen Goods** (sustainable home-goods D2C online store) — lives in [`example/lumen-goods/`](example/lumen-goods/). All 16 artifacts are realistic, cross-referenced, and generated by running the full BA Toolkit pipeline.
|
|
156
156
|
|
|
157
157
|
| Artifact | File |
|
|
158
158
|
|---------|------|
|
|
159
|
+
| Concept Discovery | [`00_discovery_lumen-goods.md`](example/lumen-goods/00_discovery_lumen-goods.md) |
|
|
159
160
|
| Project Principles | [`00_principles_lumen-goods.md`](example/lumen-goods/00_principles_lumen-goods.md) |
|
|
160
161
|
| Project Brief | [`01_brief_lumen-goods.md`](example/lumen-goods/01_brief_lumen-goods.md) |
|
|
161
162
|
| Requirements (SRS) | [`02_srs_lumen-goods.md`](example/lumen-goods/02_srs_lumen-goods.md) |
|
|
@@ -180,7 +181,8 @@ Full traceability: FR → US → UC → AC → NFR → Entity → ADR → API
|
|
|
180
181
|
|
|
181
182
|
| # | Command | What it generates | Output file |
|
|
182
183
|
|:---:|---------|-------------------|-------------|
|
|
183
|
-
| 0 | `/
|
|
184
|
+
| 0 | `/discovery` | Concept Discovery — problem space, audience hypotheses, candidate domains, MVP feature ideas, validation questions | `00_discovery_{slug}.md` |
|
|
185
|
+
| 0a | `/principles` | Project Principles — language, ID conventions, DoR, traceability rules, NFR baseline | `00_principles_{slug}.md` |
|
|
184
186
|
| 1 | `/brief` | Project Brief — goals, audience, stakeholders, risks | `01_brief_{slug}.md` |
|
|
185
187
|
| 2 | `/srs` | Requirements Specification (IEEE 830) | `02_srs_{slug}.md` |
|
|
186
188
|
| 3 | `/stories` | User Stories grouped by Epics | `03_stories_{slug}.md` |
|
|
@@ -199,6 +201,7 @@ Full traceability: FR → US → UC → AC → NFR → Entity → ADR → API
|
|
|
199
201
|
| — | `/estimate` | Effort estimation — Fibonacci SP, T-shirt sizes, or person-days | `00_estimate_{slug}.md` |
|
|
200
202
|
| — | `/glossary` | Unified project glossary with terminology drift detection | `00_glossary_{slug}.md` |
|
|
201
203
|
| — | `/export [format]` | Export User Stories to Jira / GitHub Issues / Linear / CSV | `export_{slug}_{format}.json` / `.csv` |
|
|
204
|
+
| — | `/publish [format]` | Bundle artifacts for Notion (Markdown) and Confluence (HTML) — drag-and-drop import, no API tokens | `publish/notion/`, `publish/confluence/` |
|
|
202
205
|
| — | `/risk` | Risk register — probability × impact matrix, mitigation per risk | `00_risks_{slug}.md` |
|
|
203
206
|
| — | `/sprint` | Sprint plan — stories grouped by velocity and capacity with sprint goals | `00_sprint_{slug}.md` |
|
|
204
207
|
|
|
@@ -268,18 +271,23 @@ Every artifact links back to its predecessors, forming the chain `FR → US →
|
|
|
268
271
|
|
|
269
272
|
## Minimum viable pipeline
|
|
270
273
|
|
|
271
|
-
Not every project needs all
|
|
274
|
+
Not every project needs all 23 skills. Three common paths:
|
|
272
275
|
|
|
273
|
-
**
|
|
276
|
+
**Concept-first** (when you don't yet know what to build):
|
|
277
|
+
```
|
|
278
|
+
/discovery → /brief → /srs → /stories → /ac → /nfr → /datadict → /apicontract → /wireframes → /handoff
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Lean** (fastest path to handoff when you already have a project in mind — 9 steps):
|
|
274
282
|
```
|
|
275
283
|
/brief → /srs → /stories → /ac → /nfr → /datadict → /apicontract → /wireframes → /handoff
|
|
276
284
|
```
|
|
277
285
|
|
|
278
|
-
**Full** (complete traceability and quality gates —
|
|
286
|
+
**Full** (complete traceability and quality gates — 16 steps):
|
|
279
287
|
```
|
|
280
|
-
/principles → /brief → /srs → /stories → /usecases → /ac → /nfr → /datadict
|
|
281
|
-
|
|
282
|
-
|
|
288
|
+
/discovery → /principles → /brief → /srs → /stories → /usecases → /ac → /nfr → /datadict
|
|
289
|
+
→ /research → /apicontract → /wireframes → /scenarios
|
|
290
|
+
→ /trace → /analyze → /handoff
|
|
283
291
|
```
|
|
284
292
|
|
|
285
293
|
Use `/clarify` at any step to resolve ambiguities before moving on. Approximate time per step is in [docs/USAGE.md#appendix-time-estimates](docs/USAGE.md#appendix-time-estimates).
|
package/bin/ba-toolkit.js
CHANGED
|
@@ -21,7 +21,7 @@ const PKG = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'),
|
|
|
21
21
|
// All five supported agents — Claude Code, Codex CLI, Gemini CLI,
|
|
22
22
|
// Cursor, and Windsurf — load Agent Skills as direct subfolders of
|
|
23
23
|
// their skills root: `<skills-root>/<skill-name>/SKILL.md`. The toolkit
|
|
24
|
-
// installs the
|
|
24
|
+
// installs the 23 skills natively in this layout for every agent. No
|
|
25
25
|
// .mdc conversion. Confirmed against the Agent Skills documentation
|
|
26
26
|
// for each platform via ctx7 MCP / official docs.
|
|
27
27
|
//
|
|
@@ -523,6 +523,7 @@ function stringFlag(args, key) {
|
|
|
523
523
|
const KNOWN_FLAGS = new Set([
|
|
524
524
|
'name', 'slug', 'domain', 'for', 'no-install',
|
|
525
525
|
'global', 'project', 'dry-run',
|
|
526
|
+
'format', 'out',
|
|
526
527
|
'version', 'v', 'help', 'h',
|
|
527
528
|
]);
|
|
528
529
|
|
|
@@ -1008,19 +1009,22 @@ async function cmdInit(args) {
|
|
|
1008
1009
|
log(' 2. ' + bold(`cd ${outputDir}`) + ' — open your AI agent in this folder.');
|
|
1009
1010
|
log(' Each project has its own AGENTS.md, so two agent windows');
|
|
1010
1011
|
log(' can work on two different projects in the same repo.');
|
|
1011
|
-
log(' 3. Optional: run /
|
|
1012
|
+
log(' 3. Optional: run /discovery if you do not yet know what to build,');
|
|
1013
|
+
log(' or /principles to define project-wide conventions');
|
|
1012
1014
|
log(' 4. Run /brief to start the BA pipeline');
|
|
1013
1015
|
} else if (installed === false) {
|
|
1014
1016
|
log(' 1. Skill install was cancelled. To install later, run:');
|
|
1015
1017
|
log(' ' + gray(`ba-toolkit install --for ${agentId}`));
|
|
1016
1018
|
log(' 2. ' + bold(`cd ${outputDir}`) + ' and open your AI agent there.');
|
|
1017
|
-
log(' 3. Optional: run /
|
|
1019
|
+
log(' 3. Optional: run /discovery if you do not yet know what to build,');
|
|
1020
|
+
log(' or /principles to define project-wide conventions');
|
|
1018
1021
|
log(' 4. Run /brief to start the BA pipeline');
|
|
1019
1022
|
} else {
|
|
1020
1023
|
log(' 1. Install skills for your agent:');
|
|
1021
1024
|
log(' ' + gray('ba-toolkit install --for claude-code'));
|
|
1022
1025
|
log(' 2. ' + bold(`cd ${outputDir}`) + ' and open your AI agent there.');
|
|
1023
|
-
log(' 3. Optional: run /
|
|
1026
|
+
log(' 3. Optional: run /discovery if you do not yet know what to build,');
|
|
1027
|
+
log(' or /principles to define project-wide conventions');
|
|
1024
1028
|
log(' 4. Run /brief to start the BA pipeline');
|
|
1025
1029
|
}
|
|
1026
1030
|
log('');
|
|
@@ -1061,7 +1065,7 @@ function writeManifest(destDir, items) {
|
|
|
1061
1065
|
}
|
|
1062
1066
|
|
|
1063
1067
|
// Detect the v1.x install layout: every previous install path nested
|
|
1064
|
-
//
|
|
1068
|
+
// the v1.x skills under an extra `ba-toolkit/` folder, which made them
|
|
1065
1069
|
// invisible to every agent's skill loader. Returns the absolute paths
|
|
1066
1070
|
// of any legacy folders that still exist for the given agent, so the
|
|
1067
1071
|
// caller can warn the user to clean them up before installing v2.0.
|
|
@@ -1472,6 +1476,385 @@ async function cmdUninstall(args) {
|
|
|
1472
1476
|
log('');
|
|
1473
1477
|
}
|
|
1474
1478
|
|
|
1479
|
+
// --- Publish (Notion / Confluence export) ------------------------------
|
|
1480
|
+
|
|
1481
|
+
// Escape the four HTML special characters that matter inside text
|
|
1482
|
+
// content. Used by markdownToHtml everywhere user-controlled text is
|
|
1483
|
+
// emitted into an HTML attribute or body. Keep this list short and
|
|
1484
|
+
// uncontroversial — any further entity table belongs in a real HTML
|
|
1485
|
+
// library, which we deliberately avoid (zero deps).
|
|
1486
|
+
function htmlEscape(s) {
|
|
1487
|
+
return String(s)
|
|
1488
|
+
.replace(/&/g, '&')
|
|
1489
|
+
.replace(/</g, '<')
|
|
1490
|
+
.replace(/>/g, '>')
|
|
1491
|
+
.replace(/"/g, '"');
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// GitHub-style heading slug: lowercase, spaces → dashes, drop everything
|
|
1495
|
+
// that isn't a word char or dash. Used to give every heading a stable
|
|
1496
|
+
// `id` so cross-references like `02_srs.html#fr-001` resolve.
|
|
1497
|
+
function slugifyHeading(text) {
|
|
1498
|
+
return String(text)
|
|
1499
|
+
.toLowerCase()
|
|
1500
|
+
.replace(/[^\w\s-]/g, '')
|
|
1501
|
+
.trim()
|
|
1502
|
+
.replace(/\s+/g, '-');
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// Convert the BA Toolkit subset of Markdown to plain HTML. Scope is
|
|
1506
|
+
// intentionally small (no nested lists, no images, no raw HTML, no
|
|
1507
|
+
// reference-style links, no footnotes) — these features don't appear in
|
|
1508
|
+
// any shipped artifact template. The aim is a converter small enough to
|
|
1509
|
+
// audit by hand and fully covered by snapshot tests.
|
|
1510
|
+
//
|
|
1511
|
+
// Block pass: walk lines, group them into blocks (paragraph, heading,
|
|
1512
|
+
// list, table, fenced code, blockquote, hr). Inline pass: rewrite
|
|
1513
|
+
// emphasis, links and code spans inside each block's text content.
|
|
1514
|
+
function markdownToHtml(src) {
|
|
1515
|
+
const lines = String(src).replace(/\r\n/g, '\n').split('\n');
|
|
1516
|
+
const out = [];
|
|
1517
|
+
let i = 0;
|
|
1518
|
+
|
|
1519
|
+
// Inline-rewrite a single line of text. Order matters: extract code
|
|
1520
|
+
// spans first so we don't touch their contents, then links, then
|
|
1521
|
+
// emphasis. Each step replaces the matched span with a placeholder,
|
|
1522
|
+
// and a final pass swaps placeholders back so emphasis inside link
|
|
1523
|
+
// text still resolves.
|
|
1524
|
+
function inline(text) {
|
|
1525
|
+
const placeholders = [];
|
|
1526
|
+
const stash = (html) => {
|
|
1527
|
+
placeholders.push(html);
|
|
1528
|
+
return `\u0000${placeholders.length - 1}\u0000`;
|
|
1529
|
+
};
|
|
1530
|
+
// Code spans `x` — stashed first so their contents are immune to
|
|
1531
|
+
// every other inline rule.
|
|
1532
|
+
text = text.replace(/`([^`\n]+)`/g, (_, code) => stash(`<code>${htmlEscape(code)}</code>`));
|
|
1533
|
+
// Links [text](url) — also stashed so the raw <a> tag survives the
|
|
1534
|
+
// final htmlEscape pass.
|
|
1535
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, url) => {
|
|
1536
|
+
// Recursively inline-format the label so emphasis inside links works.
|
|
1537
|
+
const inner = inline(label);
|
|
1538
|
+
return stash(`<a href="${htmlEscape(url)}">${inner}</a>`);
|
|
1539
|
+
});
|
|
1540
|
+
// Bold **x** — stash, otherwise the trailing htmlEscape would
|
|
1541
|
+
// re-escape the `<strong>` tags we just emitted.
|
|
1542
|
+
text = text.replace(/\*\*([^*\n]+)\*\*/g, (_, body) => stash(`<strong>${htmlEscape(body)}</strong>`));
|
|
1543
|
+
// Italic *x* and _x_ — same stashing rule. Narrow patterns avoid
|
|
1544
|
+
// eating any leftover ** (there are none after the bold pass, but
|
|
1545
|
+
// the guard keeps the regex robust against pathological input).
|
|
1546
|
+
text = text.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, (_, pre, body) => `${pre}${stash(`<em>${htmlEscape(body)}</em>`)}`);
|
|
1547
|
+
text = text.replace(/(^|[^_])_([^_\n]+)_(?!_)/g, (_, pre, body) => `${pre}${stash(`<em>${htmlEscape(body)}</em>`)}`);
|
|
1548
|
+
// Escape every character that survived the stashing passes. The
|
|
1549
|
+
// placeholder marker \u0000 is not in the escape list and the
|
|
1550
|
+
// ASCII digits inside the marker are also unaffected, so the swap
|
|
1551
|
+
// below still finds them.
|
|
1552
|
+
text = htmlEscape(text);
|
|
1553
|
+
// Restore placeholders.
|
|
1554
|
+
text = text.replace(/\u0000(\d+)\u0000/g, (_, idx) => placeholders[Number(idx)]);
|
|
1555
|
+
return text;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
while (i < lines.length) {
|
|
1559
|
+
const line = lines[i];
|
|
1560
|
+
|
|
1561
|
+
// Blank lines separate blocks; collapse runs of them.
|
|
1562
|
+
if (/^\s*$/.test(line)) { i++; continue; }
|
|
1563
|
+
|
|
1564
|
+
// Fenced code block ```lang
|
|
1565
|
+
const fenceMatch = /^```(\w*)\s*$/.exec(line);
|
|
1566
|
+
if (fenceMatch) {
|
|
1567
|
+
const lang = fenceMatch[1];
|
|
1568
|
+
const body = [];
|
|
1569
|
+
i++;
|
|
1570
|
+
while (i < lines.length && !/^```\s*$/.test(lines[i])) {
|
|
1571
|
+
body.push(lines[i]);
|
|
1572
|
+
i++;
|
|
1573
|
+
}
|
|
1574
|
+
// Skip the closing fence (or accept EOF as the close).
|
|
1575
|
+
if (i < lines.length) i++;
|
|
1576
|
+
const cls = lang ? ` class="language-${htmlEscape(lang)}"` : '';
|
|
1577
|
+
out.push(`<pre><code${cls}>${htmlEscape(body.join('\n'))}</code></pre>`);
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// Heading # … ######
|
|
1582
|
+
const headingMatch = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
1583
|
+
if (headingMatch) {
|
|
1584
|
+
const level = headingMatch[1].length;
|
|
1585
|
+
const text = headingMatch[2].trim();
|
|
1586
|
+
const id = slugifyHeading(text);
|
|
1587
|
+
out.push(`<h${level} id="${htmlEscape(id)}">${inline(text)}</h${level}>`);
|
|
1588
|
+
i++;
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// Horizontal rule
|
|
1593
|
+
if (/^---+\s*$/.test(line)) {
|
|
1594
|
+
out.push('<hr>');
|
|
1595
|
+
i++;
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// Table — header row + alignment row + body rows. We require both
|
|
1600
|
+
// the header and the alignment row to exist; otherwise treat the
|
|
1601
|
+
// line as a paragraph.
|
|
1602
|
+
if (/^\|.*\|\s*$/.test(line) && i + 1 < lines.length && /^\|[\s:|-]+\|\s*$/.test(lines[i + 1])) {
|
|
1603
|
+
const splitRow = (row) => row.replace(/^\||\|\s*$/g, '').split('|').map((c) => c.trim());
|
|
1604
|
+
const header = splitRow(line);
|
|
1605
|
+
i += 2; // skip header + alignment
|
|
1606
|
+
const body = [];
|
|
1607
|
+
while (i < lines.length && /^\|.*\|\s*$/.test(lines[i])) {
|
|
1608
|
+
body.push(splitRow(lines[i]));
|
|
1609
|
+
i++;
|
|
1610
|
+
}
|
|
1611
|
+
const thead = '<thead><tr>' + header.map((c) => `<th>${inline(c)}</th>`).join('') + '</tr></thead>';
|
|
1612
|
+
const tbody = body.length > 0
|
|
1613
|
+
? '<tbody>' + body.map((row) => '<tr>' + row.map((c) => `<td>${inline(c)}</td>`).join('') + '</tr>').join('') + '</tbody>'
|
|
1614
|
+
: '';
|
|
1615
|
+
out.push(`<table>${thead}${tbody}</table>`);
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// Blockquote
|
|
1620
|
+
if (/^>\s?/.test(line)) {
|
|
1621
|
+
const body = [];
|
|
1622
|
+
while (i < lines.length && /^>\s?/.test(lines[i])) {
|
|
1623
|
+
body.push(lines[i].replace(/^>\s?/, ''));
|
|
1624
|
+
i++;
|
|
1625
|
+
}
|
|
1626
|
+
out.push(`<blockquote><p>${inline(body.join(' '))}</p></blockquote>`);
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// Unordered list
|
|
1631
|
+
if (/^[-*]\s+/.test(line)) {
|
|
1632
|
+
const items = [];
|
|
1633
|
+
while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
|
|
1634
|
+
items.push(lines[i].replace(/^[-*]\s+/, ''));
|
|
1635
|
+
i++;
|
|
1636
|
+
}
|
|
1637
|
+
out.push('<ul>' + items.map((it) => `<li>${inline(it)}</li>`).join('') + '</ul>');
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// Ordered list
|
|
1642
|
+
if (/^\d+\.\s+/.test(line)) {
|
|
1643
|
+
const items = [];
|
|
1644
|
+
while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
|
|
1645
|
+
items.push(lines[i].replace(/^\d+\.\s+/, ''));
|
|
1646
|
+
i++;
|
|
1647
|
+
}
|
|
1648
|
+
out.push('<ol>' + items.map((it) => `<li>${inline(it)}</li>`).join('') + '</ol>');
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// Default: paragraph (greedy until blank line or another block).
|
|
1653
|
+
const para = [];
|
|
1654
|
+
while (i < lines.length && !/^\s*$/.test(lines[i]) && !/^#{1,6}\s/.test(lines[i]) &&
|
|
1655
|
+
!/^```/.test(lines[i]) && !/^[-*]\s/.test(lines[i]) && !/^\d+\.\s/.test(lines[i]) &&
|
|
1656
|
+
!/^>\s?/.test(lines[i]) && !/^---+\s*$/.test(lines[i]) &&
|
|
1657
|
+
!(/^\|.*\|\s*$/.test(lines[i]) && i + 1 < lines.length && /^\|[\s:|-]+\|\s*$/.test(lines[i + 1]))) {
|
|
1658
|
+
para.push(lines[i]);
|
|
1659
|
+
i++;
|
|
1660
|
+
}
|
|
1661
|
+
out.push(`<p>${inline(para.join(' '))}</p>`);
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
return out.join('\n');
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// Match every BA Toolkit artifact filename in a project output dir:
|
|
1668
|
+
// `00_discovery_<slug>.md`, `00_principles_<slug>.md`, `01_brief_<slug>.md`,
|
|
1669
|
+
// `07a_research_<slug>.md`, `11_handoff_<slug>.md`, `00_risks_<slug>.md`,
|
|
1670
|
+
// etc. The pattern is intentionally permissive — anything that starts
|
|
1671
|
+
// with two digits (and an optional letter) followed by `_<word>_` is
|
|
1672
|
+
// in.
|
|
1673
|
+
const ARTIFACT_FILE_RE = /^\d{2}[a-z]?_[a-z]+_.*\.md$/;
|
|
1674
|
+
|
|
1675
|
+
// Numeric-aware sort: 01 < 02 < … < 07 < 07a < 08 < … < 11. Strips the
|
|
1676
|
+
// suffix letter and uses it only as a tiebreaker so `7a` always sorts
|
|
1677
|
+
// after `7` and before `8`.
|
|
1678
|
+
function compareArtifactFilenames(a, b) {
|
|
1679
|
+
const m1 = /^(\d{2})([a-z]?)/.exec(a);
|
|
1680
|
+
const m2 = /^(\d{2})([a-z]?)/.exec(b);
|
|
1681
|
+
const n1 = parseInt(m1[1], 10);
|
|
1682
|
+
const n2 = parseInt(m2[1], 10);
|
|
1683
|
+
if (n1 !== n2) return n1 - n2;
|
|
1684
|
+
if (m1[2] !== m2[2]) return m1[2] < m2[2] ? -1 : 1;
|
|
1685
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
// Rewrite intra-project markdown links inside an artifact body so they
|
|
1689
|
+
// survive the import. Mode is one of:
|
|
1690
|
+
// 'notion' — `[txt](02_srs_x.md#anchor)` → `[txt](./02_srs_x.md#anchor)`
|
|
1691
|
+
// Notion's bulk markdown importer resolves relative
|
|
1692
|
+
// links between files in the same import batch.
|
|
1693
|
+
// 'confluence' — `[txt](02_srs_x.md#anchor)` → `[txt](02_srs_x.html#anchor)`
|
|
1694
|
+
// HTML import expects sibling .html filenames.
|
|
1695
|
+
// External links (http, https, mailto) and anchors-only (#fr-001) pass
|
|
1696
|
+
// through unchanged.
|
|
1697
|
+
function rewriteLinks(body, mode) {
|
|
1698
|
+
return String(body).replace(/\[([^\]]+)\]\(([^)]+)\)/g, (full, label, url) => {
|
|
1699
|
+
if (/^(https?:|mailto:|#|\/)/i.test(url)) return full;
|
|
1700
|
+
if (!/\.md(\b|$|#)/.test(url)) return full;
|
|
1701
|
+
if (mode === 'notion') {
|
|
1702
|
+
const target = url.startsWith('./') || url.startsWith('../') ? url : './' + url;
|
|
1703
|
+
return `[${label}](${target})`;
|
|
1704
|
+
}
|
|
1705
|
+
if (mode === 'confluence') {
|
|
1706
|
+
const target = url.replace(/\.md(\b|$|#)/, '.html$1');
|
|
1707
|
+
return `[${label}](${target})`;
|
|
1708
|
+
}
|
|
1709
|
+
return full;
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// Strip the managed-block markers that `init` writes into AGENTS.md so
|
|
1714
|
+
// the imported page doesn't render the HTML comments as visible text in
|
|
1715
|
+
// importers that don't strip comments. Everything between the markers
|
|
1716
|
+
// (including the markers themselves) is removed.
|
|
1717
|
+
function stripManagedBlock(body) {
|
|
1718
|
+
return String(body).replace(
|
|
1719
|
+
/<!-- ba-toolkit:begin managed -->[\s\S]*?<!-- ba-toolkit:end managed -->\s*/g,
|
|
1720
|
+
'',
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
async function cmdPublish(args) {
|
|
1725
|
+
const formatRaw = (stringFlag(args, 'format') || 'both').toLowerCase();
|
|
1726
|
+
const validFormats = new Set(['notion', 'confluence', 'both']);
|
|
1727
|
+
if (!validFormats.has(formatRaw)) {
|
|
1728
|
+
logError(`Unknown --format value: ${formatRaw}`);
|
|
1729
|
+
log(' Valid formats: ' + cyan('notion') + ', ' + cyan('confluence') + ', ' + cyan('both'));
|
|
1730
|
+
process.exit(1);
|
|
1731
|
+
}
|
|
1732
|
+
const dryRun = args.flags['dry-run'] === true;
|
|
1733
|
+
const cwd = process.cwd();
|
|
1734
|
+
const outDir = stringFlag(args, 'out')
|
|
1735
|
+
? path.resolve(cwd, stringFlag(args, 'out'))
|
|
1736
|
+
: path.join(cwd, 'publish');
|
|
1737
|
+
|
|
1738
|
+
const allFiles = fs.readdirSync(cwd);
|
|
1739
|
+
const artifacts = allFiles.filter((f) => ARTIFACT_FILE_RE.test(f)).sort(compareArtifactFilenames);
|
|
1740
|
+
const hasAgents = allFiles.includes('AGENTS.md');
|
|
1741
|
+
|
|
1742
|
+
if (artifacts.length === 0) {
|
|
1743
|
+
logError(`No BA Toolkit artifacts found in ${cwd}.`);
|
|
1744
|
+
log(' Run this command from inside ' + cyan('output/<slug>/') + ' after generating artifacts.');
|
|
1745
|
+
process.exit(1);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
log('');
|
|
1749
|
+
log(' ' + cyan('BA Toolkit — Publish'));
|
|
1750
|
+
log(' ' + cyan('===================='));
|
|
1751
|
+
log('');
|
|
1752
|
+
log(` source: ${cwd}`);
|
|
1753
|
+
log(` destination: ${outDir}`);
|
|
1754
|
+
log(` format: ${formatRaw}`);
|
|
1755
|
+
log(` artifacts: ${artifacts.length}${hasAgents ? ' (+ AGENTS.md)' : ''}`);
|
|
1756
|
+
if (dryRun) log(' ' + yellow('mode: dry-run (no files will be written)'));
|
|
1757
|
+
log('');
|
|
1758
|
+
|
|
1759
|
+
const writeFile = (relPath, body) => {
|
|
1760
|
+
const abs = path.join(outDir, relPath);
|
|
1761
|
+
if (dryRun) {
|
|
1762
|
+
log(' ' + gray('would write ') + path.relative(cwd, abs));
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
1766
|
+
fs.writeFileSync(abs, body);
|
|
1767
|
+
};
|
|
1768
|
+
|
|
1769
|
+
// Build the ordered file list once. AGENTS.md goes first if present
|
|
1770
|
+
// so the imported workspace has project context up top.
|
|
1771
|
+
const ordered = [];
|
|
1772
|
+
if (hasAgents) ordered.push('AGENTS.md');
|
|
1773
|
+
ordered.push(...artifacts);
|
|
1774
|
+
|
|
1775
|
+
let notionCount = 0;
|
|
1776
|
+
let confluenceCount = 0;
|
|
1777
|
+
|
|
1778
|
+
if (formatRaw === 'notion' || formatRaw === 'both') {
|
|
1779
|
+
for (const filename of ordered) {
|
|
1780
|
+
let body = fs.readFileSync(path.join(cwd, filename), 'utf8');
|
|
1781
|
+
if (filename === 'AGENTS.md') body = stripManagedBlock(body);
|
|
1782
|
+
body = rewriteLinks(body, 'notion');
|
|
1783
|
+
writeFile(path.join('notion', filename), body);
|
|
1784
|
+
notionCount++;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
if (formatRaw === 'confluence' || formatRaw === 'both') {
|
|
1789
|
+
const indexEntries = [];
|
|
1790
|
+
for (const filename of ordered) {
|
|
1791
|
+
let body = fs.readFileSync(path.join(cwd, filename), 'utf8');
|
|
1792
|
+
if (filename === 'AGENTS.md') body = stripManagedBlock(body);
|
|
1793
|
+
body = rewriteLinks(body, 'confluence');
|
|
1794
|
+
const html = markdownToHtml(body);
|
|
1795
|
+
const htmlName = filename.replace(/\.md$/, '.html');
|
|
1796
|
+
// Per-page wrapper. No <head> styles — let Confluence's own page
|
|
1797
|
+
// styling take over after import. Title comes from the filename
|
|
1798
|
+
// so the importer has something to use.
|
|
1799
|
+
const title = filename.replace(/\.md$/, '');
|
|
1800
|
+
const page = `<!DOCTYPE html>
|
|
1801
|
+
<html lang="en">
|
|
1802
|
+
<head><meta charset="utf-8"><title>${htmlEscape(title)}</title></head>
|
|
1803
|
+
<body>
|
|
1804
|
+
${html}
|
|
1805
|
+
</body>
|
|
1806
|
+
</html>`;
|
|
1807
|
+
writeFile(path.join('confluence', htmlName), page);
|
|
1808
|
+
indexEntries.push({ htmlName, title });
|
|
1809
|
+
confluenceCount++;
|
|
1810
|
+
}
|
|
1811
|
+
// index.html — entry point for Confluence's HTML importer. Lists
|
|
1812
|
+
// every page in pipeline order with a basic style block so the
|
|
1813
|
+
// import preview is readable.
|
|
1814
|
+
const indexHtml = `<!DOCTYPE html>
|
|
1815
|
+
<html lang="en">
|
|
1816
|
+
<head>
|
|
1817
|
+
<meta charset="utf-8">
|
|
1818
|
+
<title>BA Toolkit — project artifacts</title>
|
|
1819
|
+
<style>
|
|
1820
|
+
body { font-family: -apple-system, sans-serif; max-width: 720px; margin: 2em auto; padding: 0 1em; color: #222; }
|
|
1821
|
+
h1 { border-bottom: 1px solid #ddd; padding-bottom: 0.3em; }
|
|
1822
|
+
ul { line-height: 1.8; }
|
|
1823
|
+
a { color: #0052cc; text-decoration: none; }
|
|
1824
|
+
a:hover { text-decoration: underline; }
|
|
1825
|
+
code { background: #f4f5f7; padding: 0.1em 0.3em; border-radius: 3px; font-family: monospace; }
|
|
1826
|
+
</style>
|
|
1827
|
+
</head>
|
|
1828
|
+
<body>
|
|
1829
|
+
<h1>BA Toolkit — project artifacts</h1>
|
|
1830
|
+
<p>Generated by <code>ba-toolkit publish</code>. Each link below becomes a page in the imported Confluence space.</p>
|
|
1831
|
+
<ul>
|
|
1832
|
+
${indexEntries.map((e) => ` <li><a href="${htmlEscape(e.htmlName)}">${htmlEscape(e.title)}</a></li>`).join('\n')}
|
|
1833
|
+
</ul>
|
|
1834
|
+
</body>
|
|
1835
|
+
</html>`;
|
|
1836
|
+
writeFile(path.join('confluence', 'index.html'), indexHtml);
|
|
1837
|
+
confluenceCount++;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
log('');
|
|
1841
|
+
if (formatRaw === 'notion' || formatRaw === 'both') {
|
|
1842
|
+
log(` ${green('✓')} Notion bundle: ${path.relative(cwd, path.join(outDir, 'notion'))}/ (${notionCount} files)`);
|
|
1843
|
+
}
|
|
1844
|
+
if (formatRaw === 'confluence' || formatRaw === 'both') {
|
|
1845
|
+
log(` ${green('✓')} Confluence bundle: ${path.relative(cwd, path.join(outDir, 'confluence'))}/ (${confluenceCount} files, including index.html)`);
|
|
1846
|
+
}
|
|
1847
|
+
log('');
|
|
1848
|
+
log(' ' + bold('Next steps:'));
|
|
1849
|
+
if (formatRaw === 'notion' || formatRaw === 'both') {
|
|
1850
|
+
log(' Notion: drag-and-drop ' + cyan(path.relative(cwd, path.join(outDir, 'notion')) + '/') + ' into "Import → Markdown & CSV" in your workspace.');
|
|
1851
|
+
}
|
|
1852
|
+
if (formatRaw === 'confluence' || formatRaw === 'both') {
|
|
1853
|
+
log(' Confluence: zip ' + cyan(path.relative(cwd, path.join(outDir, 'confluence')) + '/') + ' and upload via "Space settings → Content tools → Import → HTML".');
|
|
1854
|
+
}
|
|
1855
|
+
log('');
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1475
1858
|
function cmdHelp() {
|
|
1476
1859
|
log(`${bold('ba-toolkit')} v${PKG.version} — AI-powered Business Analyst pipeline
|
|
1477
1860
|
|
|
@@ -1497,6 +1880,12 @@ ${bold('COMMANDS')}
|
|
|
1497
1880
|
supported agent (project + global) and
|
|
1498
1881
|
report which versions are installed where.
|
|
1499
1882
|
Read-only; no flags.
|
|
1883
|
+
publish [--format <fmt>] Bundle the artifacts in the current
|
|
1884
|
+
output/<slug>/ folder into import-ready
|
|
1885
|
+
files for Notion (markdown) and Confluence
|
|
1886
|
+
(HTML). Format: notion, confluence, or
|
|
1887
|
+
both (default). No API calls, no tokens —
|
|
1888
|
+
the user runs the actual import manually.
|
|
1500
1889
|
|
|
1501
1890
|
${bold('OPTIONS')}
|
|
1502
1891
|
--name <name> init only — skip the project name prompt
|
|
@@ -1514,8 +1903,12 @@ ${bold('OPTIONS')}
|
|
|
1514
1903
|
--project install/uninstall/upgrade — target the
|
|
1515
1904
|
project-level install (default when the
|
|
1516
1905
|
agent supports it)
|
|
1517
|
-
--dry-run init/install/uninstall/upgrade —
|
|
1518
|
-
without writing or removing files
|
|
1906
|
+
--dry-run init/install/uninstall/upgrade/publish —
|
|
1907
|
+
preview without writing or removing files
|
|
1908
|
+
--format <fmt> publish only — notion, confluence, or both
|
|
1909
|
+
(default: both)
|
|
1910
|
+
--out <path> publish only — output directory for the
|
|
1911
|
+
bundles (default: ./publish/)
|
|
1519
1912
|
|
|
1520
1913
|
${bold('GENERAL OPTIONS')}
|
|
1521
1914
|
--version, -v Print version and exit
|
|
@@ -1547,6 +1940,12 @@ ${bold('EXAMPLES')}
|
|
|
1547
1940
|
# See where (and which version) BA Toolkit is installed.
|
|
1548
1941
|
ba-toolkit status
|
|
1549
1942
|
|
|
1943
|
+
# Bundle a project's artifacts for Notion + Confluence import.
|
|
1944
|
+
cd output/my-app
|
|
1945
|
+
ba-toolkit publish
|
|
1946
|
+
ba-toolkit publish --format notion --out ./share
|
|
1947
|
+
ba-toolkit publish --format confluence --dry-run
|
|
1948
|
+
|
|
1550
1949
|
${bold('LEARN MORE')}
|
|
1551
1950
|
https://github.com/TakhirKudusov/ba-toolkit
|
|
1552
1951
|
`);
|
|
@@ -1586,6 +1985,9 @@ async function main() {
|
|
|
1586
1985
|
case 'status':
|
|
1587
1986
|
cmdStatus();
|
|
1588
1987
|
break;
|
|
1988
|
+
case 'publish':
|
|
1989
|
+
await cmdPublish(args);
|
|
1990
|
+
break;
|
|
1589
1991
|
case 'help':
|
|
1590
1992
|
cmdHelp();
|
|
1591
1993
|
break;
|
|
@@ -1616,6 +2018,13 @@ module.exports = {
|
|
|
1616
2018
|
mergeAgentsMd,
|
|
1617
2019
|
menuStep,
|
|
1618
2020
|
renderMenu,
|
|
2021
|
+
markdownToHtml,
|
|
2022
|
+
htmlEscape,
|
|
2023
|
+
slugifyHeading,
|
|
2024
|
+
rewriteLinks,
|
|
2025
|
+
stripManagedBlock,
|
|
2026
|
+
compareArtifactFilenames,
|
|
2027
|
+
ARTIFACT_FILE_RE,
|
|
1619
2028
|
KNOWN_FLAGS,
|
|
1620
2029
|
DOMAINS,
|
|
1621
2030
|
AGENTS,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kudusov.takhir/ba-toolkit",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "AI-powered Business Analyst pipeline —
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"description": "AI-powered Business Analyst pipeline — 23 skills from concept discovery to development handoff, with one-command Notion + Confluence publish. Works with Claude Code, Codex CLI, Gemini CLI, Cursor, and Windsurf.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"business-analyst",
|
|
7
7
|
"requirements",
|
package/skills/brief/SKILL.md
CHANGED
|
@@ -22,6 +22,8 @@ Read `references/environment.md` from the `ba-toolkit` directory to determine th
|
|
|
22
22
|
|
|
23
23
|
No prior artifacts required. If `01_brief_*.md` already exists, warn the user and offer to overwrite or create a new project.
|
|
24
24
|
|
|
25
|
+
If `00_discovery_*.md` exists in the output directory, load it as concept context. Extract the problem space (section 1), target audience hypotheses (section 2), recommended domain (section 3), MVP feature hypotheses (section 5), and the scope hint from section 8, and use them to pre-fill the structured interview questions per protocol rule 9. Skip any required topic that the discovery artifact already answers — only ask the user about gaps and open validation questions. Acknowledge the discovery hand-off in one line at the start of the interview so the user knows their concept work was loaded.
|
|
26
|
+
|
|
25
27
|
If `00_principles_*.md` exists in the output directory, load it and apply its conventions for this and all subsequent pipeline steps (artifact language, ID format, traceability requirements, Definition of Ready, quality gate threshold).
|
|
26
28
|
|
|
27
29
|
### 3. Domain selection
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: discovery
|
|
3
|
+
description: >
|
|
4
|
+
Brain-storm an initial product concept when the user does not yet know what to build, which domain to choose, or which features matter. Use on /discovery command, or when the user asks to "brainstorm an idea", "explore a concept", "validate a project hypothesis", "I'm not sure what to build", "discovery phase", "concept exploration", "shape an idea before the brief". Optional first step of the BA Toolkit pipeline — runs before /brief and /principles. Produces a written concept artifact and recommends a domain, project name, slug, and scope hint to carry into /brief.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /discovery — Concept Discovery
|
|
8
|
+
|
|
9
|
+
Optional entry point of the BA Toolkit pipeline. Runs **before** `/brief` for users who arrive with only a vague hunch — no fixed domain, no committed feature list, no clear audience. Generates a structured concept artifact and ends with a concrete recommendation (domain, project name, slug, scope hint) that flows into `/brief` as inline context.
|
|
10
|
+
|
|
11
|
+
The discovery artifact is a hypothesis document, not a commitment. Its sole job is to converge "I have an idea" into "I have a domain, an audience, and a candidate MVP scope" so the brief is productive instead of paralysing.
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
|
|
15
|
+
### 1. Environment detection
|
|
16
|
+
|
|
17
|
+
Read `references/environment.md` from the `ba-toolkit` directory to determine the output directory for the current platform. If the file is unavailable, apply the default rule: if `/mnt/user-data/outputs/` exists and is writable, save there (Claude.ai); otherwise save to the current working directory.
|
|
18
|
+
|
|
19
|
+
### 2. Pipeline check
|
|
20
|
+
|
|
21
|
+
If `00_discovery_*.md` already exists, load it and offer to:
|
|
22
|
+
- View the current concept.
|
|
23
|
+
- Amend a specific section (`/revise [section]`).
|
|
24
|
+
- Regenerate from scratch.
|
|
25
|
+
|
|
26
|
+
If `01_brief_*.md` already exists in the same output directory, the project has already moved past discovery. Warn the user that discovery is normally the first step and ask whether they really want to backfill a concept document (valid for retrospective concept docs, but uncommon).
|
|
27
|
+
|
|
28
|
+
### 3. Domain catalog
|
|
29
|
+
|
|
30
|
+
The 9 supported domain references live in `references/domains/` (`saas`, `fintech`, `ecommerce`, `healthcare`, `logistics`, `on-demand`, `social-media`, `real-estate`, `igaming`). Discovery does **not** load a single domain reference up front — instead, it offers a shortlist of candidate domains during the interview and lets the user pick one. The chosen domain is recorded in section 8 of the artifact and becomes the working domain for `/brief`.
|
|
31
|
+
|
|
32
|
+
If none of the 9 fits, record the recommended domain as `custom:{name}` and let `/brief` continue with general questions only.
|
|
33
|
+
|
|
34
|
+
### 4. Interview
|
|
35
|
+
|
|
36
|
+
> **Follow the [Interview Protocol](../references/interview-protocol.md):** ask one question at a time, present a 2-column `| ID | Variant |` markdown table of up to 4 domain-appropriate options plus a free-text "Other" row last (5 rows max), mark exactly one row **Recommended** based on the loaded domain reference and prior answers, render variants in the user's language (rule 11), and wait for an answer before asking the next question.
|
|
37
|
+
>
|
|
38
|
+
> **Inline context (protocol rule 9):** if the user wrote text after `/discovery` (e.g., `/discovery I think there's a need for a tool that helps freelance designers track invoices and chase late payments`), parse it as the lead-in answer, acknowledge it in one line, and skip directly to the first structured question that the inline text doesn't already cover.
|
|
39
|
+
>
|
|
40
|
+
> **Open-ended lead-in (protocol rule 8):** if there is NO inline text after `/discovery`, your very first interview question is open-ended and free-text, not a table — `What problem or opportunity are you exploring? Even a vague hunch is fine — one or two sentences about what you've noticed and who it bothers.`. After the user answers, switch to the structured table protocol for all subsequent questions and use the lead-in answer to pre-fill what you can.
|
|
41
|
+
|
|
42
|
+
Cover 5–7 topics in 2 rounds. Do not generate the artifact until you can recommend a single domain with a defensible rationale.
|
|
43
|
+
|
|
44
|
+
**Required topics:**
|
|
45
|
+
1. Problem space — what pain point, gap, or opportunity is being explored.
|
|
46
|
+
2. Target audience hypothesis — 1–2 candidate user segments, as concrete as possible.
|
|
47
|
+
3. Domain shortlist — narrow to 1 of the 9 supported domains (or `custom`) with a one-line rationale; mark one row **Recommended** based on the problem/audience answers so far.
|
|
48
|
+
4. Reference products and analogues — what already exists in this space (3–5 examples), and what's missing or done badly.
|
|
49
|
+
5. MVP feature hypotheses — 5–10 candidate features for a first version. Bullet list, not committed scope.
|
|
50
|
+
6. Differentiation angle — why this idea would beat the existing analogues (one or two sentences).
|
|
51
|
+
7. Open validation questions — what we still don't know and would need to learn before committing (target users, willingness to pay, technical feasibility, etc.).
|
|
52
|
+
|
|
53
|
+
If a topic was already covered by inline context or the lead-in answer, skip it — do not re-ask. Record any topic the user genuinely cannot answer as "unknown — to validate" and move on.
|
|
54
|
+
|
|
55
|
+
### 5. Generation
|
|
56
|
+
|
|
57
|
+
**File:** `00_discovery_{slug}.md` (slug — kebab-case, derived from the project name; if the user has not yet picked a name, propose 2–3 candidates and let them choose).
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
# Discovery: {Project Name}
|
|
61
|
+
|
|
62
|
+
**Slug:** {slug}
|
|
63
|
+
**Date:** {date}
|
|
64
|
+
**Status:** Concept (pre-brief)
|
|
65
|
+
|
|
66
|
+
## 1. Problem Space
|
|
67
|
+
|
|
68
|
+
{One or two paragraphs describing the pain point, gap, or opportunity.
|
|
69
|
+
Focus on what's broken today, who feels the pain, and how often.}
|
|
70
|
+
|
|
71
|
+
## 2. Target Audience Hypotheses
|
|
72
|
+
|
|
73
|
+
- **Primary:** {concrete segment, e.g. "freelance UX designers in EU billing 5–20 clients/month"}
|
|
74
|
+
- **Secondary:** {optional second segment if the interview surfaced one}
|
|
75
|
+
|
|
76
|
+
## 3. Candidate Domains
|
|
77
|
+
|
|
78
|
+
| Domain | Rationale | Fit |
|
|
79
|
+
|--------|-----------|-----|
|
|
80
|
+
| {domain-1} | {one line} | {High / Medium / Low} |
|
|
81
|
+
| {domain-2} | {one line} | {High / Medium / Low} |
|
|
82
|
+
| {domain-3} | {one line} | {High / Medium / Low} |
|
|
83
|
+
|
|
84
|
+
**Recommended domain:** `{chosen-domain}` — {one-line justification anchored in the problem space and audience}.
|
|
85
|
+
|
|
86
|
+
## 4. Reference Products and Analogues
|
|
87
|
+
|
|
88
|
+
| Product | What it does well | What it does badly / misses |
|
|
89
|
+
|---------|-------------------|------------------------------|
|
|
90
|
+
| {name-1} | {one line} | {one line} |
|
|
91
|
+
| {name-2} | {one line} | {one line} |
|
|
92
|
+
| {name-3} | {one line} | {one line} |
|
|
93
|
+
|
|
94
|
+
## 5. MVP Feature Hypotheses
|
|
95
|
+
|
|
96
|
+
Candidate features for a first version. Not committed scope — `/brief` and `/srs` will refine and prioritise.
|
|
97
|
+
|
|
98
|
+
- {feature 1}
|
|
99
|
+
- {feature 2}
|
|
100
|
+
- {feature 3}
|
|
101
|
+
- {feature 4}
|
|
102
|
+
- {feature 5}
|
|
103
|
+
- {…}
|
|
104
|
+
|
|
105
|
+
## 6. Differentiation Angle
|
|
106
|
+
|
|
107
|
+
{One or two sentences on why this idea is worth pursuing given the analogues in section 4. Concrete, not "better UX" or "cheaper".}
|
|
108
|
+
|
|
109
|
+
## 7. Open Validation Questions
|
|
110
|
+
|
|
111
|
+
Things we don't yet know and need to learn before committing real resources.
|
|
112
|
+
|
|
113
|
+
- {question 1 — e.g. "Will the primary segment pay $X/month?"}
|
|
114
|
+
- {question 2}
|
|
115
|
+
- {question 3}
|
|
116
|
+
|
|
117
|
+
## 8. Recommended Next Step
|
|
118
|
+
|
|
119
|
+
- **Project name:** {final name}
|
|
120
|
+
- **Slug:** {final slug}
|
|
121
|
+
- **Domain:** {chosen domain}
|
|
122
|
+
- **Scope hint for `/brief`:** {one-sentence summary that the user can paste as inline context: `/brief {scope hint}`}
|
|
123
|
+
- **Suggested first interview focus in `/brief`:** {1–2 topics from the discovery artifact that are now firm enough to anchor the brief}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 6. AGENTS.md update
|
|
127
|
+
|
|
128
|
+
`ba-toolkit init` already created `AGENTS.md` next to where the artifact lives — typically in the current working directory (the user is expected to `cd output/<slug>` after running init). After saving `00_discovery_{slug}.md`, find the project's `AGENTS.md` (look in cwd first; fall back to walking up the directory tree if cwd has none, for legacy v3.0 single-project layouts).
|
|
129
|
+
|
|
130
|
+
**Update only the `## Pipeline Status` row for `/discovery`** — toggle its status from `⬜ Not started` to `✅ Done` and fill in the artifact filename in the `File` column. **Do not touch the managed block** (`<!-- ba-toolkit:begin managed -->` … `<!-- ba-toolkit:end managed -->`) — that's owned by `ba-toolkit init`. **Do not recreate the file at the repo root.** **Do not add `## Artifacts` / `## Key context` sections** — those are not part of the v3.1+ template and would be ignored by future runs.
|
|
131
|
+
|
|
132
|
+
If you find no `AGENTS.md` at all (neither in cwd nor up the tree), warn the user that the project was likely set up before v3.2 and tell them to run `ba-toolkit init --name "..." --slug {slug}` to scaffold the per-project `AGENTS.md`. Do not create one yourself with arbitrary structure.
|
|
133
|
+
|
|
134
|
+
If the existing `AGENTS.md` predates v3.2 and has no `/discovery` row in its Pipeline Status table, prepend a new row at stage `0` (and renumber the existing `/principles` row to `0a`) — same convention used by `/research` at stage `7a`. Mention the migration in your reply so the user knows their AGENTS.md was updated.
|
|
135
|
+
|
|
136
|
+
### 7. Iterative refinement
|
|
137
|
+
|
|
138
|
+
- `/revise [section]` — rewrite a section.
|
|
139
|
+
- `/expand [section]` — add detail.
|
|
140
|
+
- `/clarify [focus]` — targeted ambiguity pass.
|
|
141
|
+
- `/validate` — check completeness and consistency.
|
|
142
|
+
- `/done` — finalize and update `AGENTS.md`. Next step: `/brief`.
|
|
143
|
+
|
|
144
|
+
### 8. Closing message
|
|
145
|
+
|
|
146
|
+
After saving the artifact, present the following summary to the user (see `references/closing-message.md` for format):
|
|
147
|
+
|
|
148
|
+
- Saved file path.
|
|
149
|
+
- Recommended domain, project name, and slug — confirmed for the pipeline.
|
|
150
|
+
- Count of MVP feature hypotheses captured.
|
|
151
|
+
- List of open validation questions.
|
|
152
|
+
|
|
153
|
+
Available commands for this artifact: `/clarify [focus]` · `/revise [section]` · `/expand [section]` · `/validate` · `/done`
|
|
154
|
+
|
|
155
|
+
Build the `Next step:` block from the pipeline lookup table in `references/closing-message.md` (look up the row where `Current` is `/discovery`). Do not hardcode `/brief` here — the table is the single source of truth. If the user wants stricter conventions before the brief (e.g. specific traceability rules or definition-of-ready policies), suggest running `/principles` between `/discovery` and `/brief`.
|
|
156
|
+
|
|
157
|
+
## Style
|
|
158
|
+
|
|
159
|
+
Formal, neutral, hypothesis-driven. No emoji, slang, or evaluative language ("amazing", "game-changing"). Frame every claim as a hypothesis to validate, not a fact. Generate the artifact in the language of the user's request. Section headings, table headers, and labels also in the user's language.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish
|
|
3
|
+
description: >
|
|
4
|
+
Bundle BA Toolkit artifacts into import-ready files for documentation tools — Notion (markdown bundle) and Confluence (HTML bundle). Use on /publish command, or when the user asks to "export to Notion", "export to Confluence", "publish artifacts", "share with stakeholders", "import into Notion", "import into Confluence", "send the docs to product team". Cross-cutting utility — does not advance the pipeline.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /publish — Notion / Confluence Publish
|
|
8
|
+
|
|
9
|
+
Cross-cutting utility skill. Wraps the `ba-toolkit publish` CLI subcommand, which converts the markdown artifacts in the current `output/<slug>/` folder into folders ready to be dragged into Notion's **Import → Markdown & CSV** dialog or zipped and uploaded via Confluence's **Space settings → Content tools → Import → HTML** tool.
|
|
10
|
+
|
|
11
|
+
The conversion happens entirely on disk — **no API calls, no tokens, no network**. The user does the actual upload manually using each tool's native importer.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
After generating at least one pipeline artifact (`01_brief_*.md` or later), when the user wants to share the artifacts with non-developer stakeholders who live in Notion or Confluence rather than in the codebase.
|
|
16
|
+
|
|
17
|
+
This is **not** the same as `/export`. `/export` produces JSON/CSV for issue trackers (Jira, GitHub Issues, Linear). `/publish` produces markdown/HTML page bundles for documentation tools (Notion, Confluence).
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
### 1. Environment detection
|
|
22
|
+
|
|
23
|
+
The `ba-toolkit publish` subcommand uses the current working directory as the source — no `references/environment.md` lookup needed. Confirm with the user that they are inside their project's `output/<slug>/` folder before running it. If they are not, ask them to `cd output/<slug>` first.
|
|
24
|
+
|
|
25
|
+
### 2. Pipeline check
|
|
26
|
+
|
|
27
|
+
Verify at least one BA Toolkit artifact exists in cwd by listing files matching `[0-9][0-9]*_*.md` (e.g. `01_brief_<slug>.md`). If no artifacts are present, stop and tell the user to run `/brief` (or any later pipeline step) first — there is nothing to publish.
|
|
28
|
+
|
|
29
|
+
### 3. Format selection
|
|
30
|
+
|
|
31
|
+
> **Follow the [Interview Protocol](../references/interview-protocol.md):** ask one question at a time, present a 2-column `| ID | Variant |` markdown table of up to 4 domain-appropriate options plus a free-text "Other" row last (5 rows max), mark exactly one row **Recommended** based on the loaded domain reference and prior answers, render variants in the user's language (rule 11), and wait for an answer before asking the next question.
|
|
32
|
+
>
|
|
33
|
+
> **Inline context (protocol rule 9):** if the user wrote text after `/publish` (e.g., `/publish notion`, `/publish to confluence`, `/publish both`), parse it as the format choice and skip the question entirely.
|
|
34
|
+
|
|
35
|
+
If no format is given, ask:
|
|
36
|
+
|
|
37
|
+
> Which target do you want?
|
|
38
|
+
>
|
|
39
|
+
> | ID | Variant |
|
|
40
|
+
> |----|------------------------------------------------------|
|
|
41
|
+
> | a | Both Notion and Confluence **Recommended** |
|
|
42
|
+
> | b | Notion only |
|
|
43
|
+
> | c | Confluence only |
|
|
44
|
+
> | d | Other — type your own answer |
|
|
45
|
+
|
|
46
|
+
Map the answer to a `--format` value: `a` → `both`, `b` → `notion`, `c` → `confluence`. For free-text answers, accept the verbatim string only if it is one of `notion`, `confluence`, or `both`; otherwise re-ask.
|
|
47
|
+
|
|
48
|
+
### 4. Execution
|
|
49
|
+
|
|
50
|
+
Invoke the CLI subcommand via the agent's Bash tool from the project's `output/<slug>/` directory:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
ba-toolkit publish --format <choice>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Optional flags the user might ask for:
|
|
57
|
+
|
|
58
|
+
- `--out <path>` — write the bundles somewhere other than `./publish/`.
|
|
59
|
+
- `--dry-run` — preview the file list without writing anything.
|
|
60
|
+
|
|
61
|
+
Capture stdout from the command and report it back to the user. The CLI prints a summary of bundle paths, file counts, and the next manual import steps.
|
|
62
|
+
|
|
63
|
+
### 5. Closing message
|
|
64
|
+
|
|
65
|
+
Cross-cutting closing block (no `Next step:` line — see `references/closing-message.md` "Cross-cutting commands" section). Show:
|
|
66
|
+
|
|
67
|
+
- Saved bundle paths and counts per format (taken verbatim from the CLI's stdout summary).
|
|
68
|
+
- The two static "Next steps" lines:
|
|
69
|
+
- **Notion:** drag-and-drop `publish/notion/` into "Import → Markdown & CSV" in your Notion workspace.
|
|
70
|
+
- **Confluence:** zip `publish/confluence/` and upload via "Space settings → Content tools → Import → HTML".
|
|
71
|
+
- Optional one-line "Re-run after pipeline updates" hint: re-running `ba-toolkit publish` overwrites the previous bundle, so users can publish again after `/clarify`, `/revise`, or any later pipeline step.
|
|
72
|
+
|
|
73
|
+
Available commands for this skill: `/publish [format]`
|
|
74
|
+
|
|
75
|
+
Do not build a `Next step:` block — `/publish` is cross-cutting and does not advance the pipeline.
|
|
76
|
+
|
|
77
|
+
## Style
|
|
78
|
+
|
|
79
|
+
Formal, neutral. No emoji. Generate the chat reply in the language of the user's first message. Keep the report under 10 lines: paths, counts, two next-step lines, optional re-run hint.
|
|
@@ -43,6 +43,7 @@ Skills use this table as the single source of truth for the `Next step:` block.
|
|
|
43
43
|
|
|
44
44
|
| Current | Next | What it produces | Time | After that |
|
|
45
45
|
|--------------|-----------------|-----------------------------------------------------------------|--------------|-----------------------------------------------------|
|
|
46
|
+
| /discovery | /brief | Project Brief — goals, audience, stakeholders, constraints | 20–35 min | /srs — Requirements Specification (IEEE 830) |
|
|
46
47
|
| /principles | /brief | Project Brief — goals, audience, stakeholders, constraints | 20–35 min | /srs — Requirements Specification (IEEE 830) |
|
|
47
48
|
| /brief | /srs | Requirements Specification (IEEE 830) — scope, FRs, MoSCoW | 25–40 min | /stories — User Stories grouped by Epics |
|
|
48
49
|
| /srs | /stories | User Stories grouped by Epics, with priority and FR refs | 20–30 min | /usecases — Use Cases with main/alt/exception flows |
|
|
@@ -67,6 +68,7 @@ These skills do not advance the pipeline — they update or report on existing a
|
|
|
67
68
|
- `/estimate` — effort estimation
|
|
68
69
|
- `/glossary` — glossary maintenance
|
|
69
70
|
- `/export` — export to Jira / GitHub Issues / Linear / CSV
|
|
71
|
+
- `/publish` — bundle artifacts for Notion (Markdown) or Confluence (HTML) import
|
|
70
72
|
- `/risk` — risk register
|
|
71
73
|
- `/sprint` — sprint plan
|
|
72
74
|
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
| Stage | Skill | Status | File |
|
|
18
18
|
|-------|-------|--------|------|
|
|
19
|
-
| 0 | /
|
|
19
|
+
| 0 | /discovery | ⬜ Not started | — |
|
|
20
|
+
| 0a | /principles | ⬜ Not started | — |
|
|
20
21
|
| 1 | /brief | ⬜ Not started | — |
|
|
21
22
|
| 2 | /srs | ⬜ Not started | — |
|
|
22
23
|
| 3 | /stories | ⬜ Not started | — |
|
|
@@ -42,6 +43,7 @@ Utilities available throughout the pipeline. No fixed stage — invoke whenever
|
|
|
42
43
|
| /estimate | Effort estimation — Fibonacci SP, T-shirt sizes, or person-days |
|
|
43
44
|
| /glossary | Unified project glossary with terminology drift detection |
|
|
44
45
|
| /export [format] | Export User Stories to Jira / GitHub Issues / Linear / CSV |
|
|
46
|
+
| /publish [format] | Bundle artifacts for Notion (Markdown) or Confluence (HTML) — drag-and-drop import |
|
|
45
47
|
| /risk | Risk register — probability × impact matrix, mitigation per risk |
|
|
46
48
|
| /sprint | Sprint plan — stories grouped by velocity and capacity with sprint goals |
|
|
47
49
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Discovery: [PROJECT_NAME]
|
|
2
|
+
|
|
3
|
+
**Slug:** [SLUG]
|
|
4
|
+
**Date:** [DATE]
|
|
5
|
+
**Status:** Concept (pre-brief)
|
|
6
|
+
|
|
7
|
+
## 1. Problem Space
|
|
8
|
+
|
|
9
|
+
[One or two paragraphs describing the pain point, gap, or opportunity. Focus on what is broken today, who feels the pain, and how often it bites.]
|
|
10
|
+
|
|
11
|
+
## 2. Target Audience Hypotheses
|
|
12
|
+
|
|
13
|
+
- **Primary:** [concrete segment — role, context, scale]
|
|
14
|
+
- **Secondary:** [optional second segment if relevant]
|
|
15
|
+
|
|
16
|
+
## 3. Candidate Domains
|
|
17
|
+
|
|
18
|
+
| Domain | Rationale | Fit |
|
|
19
|
+
|--------|-----------|-----|
|
|
20
|
+
| [domain-1] | [one-line reason this domain might fit] | High / Medium / Low |
|
|
21
|
+
| [domain-2] | [one-line reason] | High / Medium / Low |
|
|
22
|
+
| [domain-3] | [one-line reason] | High / Medium / Low |
|
|
23
|
+
|
|
24
|
+
**Recommended domain:** `[chosen-domain]` — [one-line justification anchored in the problem space and audience above].
|
|
25
|
+
|
|
26
|
+
## 4. Reference Products and Analogues
|
|
27
|
+
|
|
28
|
+
| Product | What it does well | What it does badly / misses |
|
|
29
|
+
|---------|-------------------|------------------------------|
|
|
30
|
+
| [name-1] | [one line] | [one line] |
|
|
31
|
+
| [name-2] | [one line] | [one line] |
|
|
32
|
+
| [name-3] | [one line] | [one line] |
|
|
33
|
+
|
|
34
|
+
## 5. MVP Feature Hypotheses
|
|
35
|
+
|
|
36
|
+
Candidate features for a first version. Not committed scope — `/brief` and `/srs` will refine and prioritise.
|
|
37
|
+
|
|
38
|
+
- [feature 1]
|
|
39
|
+
- [feature 2]
|
|
40
|
+
- [feature 3]
|
|
41
|
+
- [feature 4]
|
|
42
|
+
- [feature 5]
|
|
43
|
+
- [...]
|
|
44
|
+
|
|
45
|
+
## 6. Differentiation Angle
|
|
46
|
+
|
|
47
|
+
[One or two sentences on why this idea is worth pursuing given the analogues in section 4. Concrete and specific — not "better UX" or "cheaper".]
|
|
48
|
+
|
|
49
|
+
## 7. Open Validation Questions
|
|
50
|
+
|
|
51
|
+
Things we do not yet know and need to learn before committing real resources.
|
|
52
|
+
|
|
53
|
+
- [question 1 — e.g. "Will the primary segment pay X per month?"]
|
|
54
|
+
- [question 2]
|
|
55
|
+
- [question 3]
|
|
56
|
+
|
|
57
|
+
## 8. Recommended Next Step
|
|
58
|
+
|
|
59
|
+
- **Project name:** [final name]
|
|
60
|
+
- **Slug:** [final slug]
|
|
61
|
+
- **Domain:** [chosen domain]
|
|
62
|
+
- **Scope hint for `/brief`:** [one-sentence summary the user can paste as inline context: `/brief [scope hint]`]
|
|
63
|
+
- **Suggested first interview focus in `/brief`:** [1–2 topics from this discovery doc that are now firm enough to anchor the brief]
|