boost-skill-cli 1.0.7__tar.gz → 1.0.15__tar.gz
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.
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/.gitignore +5 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/CLAUDE.md +22 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/PKG-INFO +56 -5
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/README.md +55 -4
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/_version.py +2 -2
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/cli.py +6 -3
- boost_skill_cli-1.0.15/boost_cli/commands/bmad.py +403 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/commands/configuration.py +17 -5
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/commands/discovery.py +41 -3
- boost_skill_cli-1.0.15/boost_cli/commands/hooks.py +92 -0
- boost_skill_cli-1.0.15/boost_cli/core/claude_settings.py +203 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/config.py +2 -2
- boost_skill_cli-1.0.15/boost_cli/core/rag.py +394 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_skill_cli.egg-info/PKG-INFO +56 -5
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_skill_cli.egg-info/SOURCES.txt +13 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/docs/index.html +4 -3
- boost_skill_cli-1.0.15/docs/roadmap.html +469 -0
- boost_skill_cli-1.0.15/style/README.md +92 -0
- boost_skill_cli-1.0.15/style/boost.css +352 -0
- boost_skill_cli-1.0.15/style/boost.js +55 -0
- boost_skill_cli-1.0.15/style/demo.html +140 -0
- boost_skill_cli-1.0.15/tests/functional/test_cli_bmad.py +223 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/functional/test_cli_configuration.py +22 -3
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/functional/test_cli_discovery.py +38 -0
- boost_skill_cli-1.0.15/tests/functional/test_cli_hooks.py +58 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/functional/test_everyday_loop.py +1 -1
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/smoke.sh +1 -0
- boost_skill_cli-1.0.15/tests/unit/test_claude_settings.py +124 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_config.py +1 -1
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_errors_and_cli_table.py +8 -8
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_frontmatter.py +103 -0
- boost_skill_cli-1.0.15/tests/unit/test_rag.py +824 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/CONTRIBUTING.md +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/LICENSE +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/MANIFEST.in +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/Makefile +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/SECURITY.md +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/__init__.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/__main__.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/commands/__init__.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/commands/info.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/commands/intelligence.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/commands/pkg.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/commands/quality.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/commands/taps.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/commands/team.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/__init__.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/agents.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/ai.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/catalog.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/frontmatter.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/gitutil.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/journal.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/lockfile.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/logs.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/output.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/paths.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/policy.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/registry.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/store.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/core/util.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/data/registries.json +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_cli/errors.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_skill_cli.egg-info/dependency_links.txt +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_skill_cli.egg-info/entry_points.txt +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/boost_skill_cli.egg-info/top_level.txt +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/docs/.claude/settings.local.json +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/docs/DEBUGGING.md +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/docs/demo.gif +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/docs/demo.tape +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/docs/rag-architecture.md +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/pyproject.toml +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/scripts/build_registries.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/scripts/mutation_gate.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/setup.cfg +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/conftest.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/functional/test_cli_info.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/functional/test_cli_intelligence.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/functional/test_cli_pkg.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/functional/test_cli_quality.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/functional/test_cli_taps.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/functional/test_cli_team.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/make_fixture.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_agents.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_ai.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_catalog.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_gitutil.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_journal.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_lockfile.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_logs.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_mutation_hardening.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_output.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_paths.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_policy.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_registry.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_store.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_util.py +0 -0
- {boost_skill_cli-1.0.7 → boost_skill_cli-1.0.15}/tests/unit/test_version.py +0 -0
|
@@ -50,6 +50,28 @@ line coverage. Target `boost_cli/core` behavior with assertions, not just import
|
|
|
50
50
|
`workflow` (commands/agents/workflows Markdown). **Only `skill` installs** —
|
|
51
51
|
`store.install` refuses non-skill kinds; rules/workflows are search/tap-only.
|
|
52
52
|
|
|
53
|
+
## Parallel work & concurrent loops
|
|
54
|
+
|
|
55
|
+
More than one agent (or `/loop` session) may be working this repo at once. To
|
|
56
|
+
keep them from clobbering each other:
|
|
57
|
+
|
|
58
|
+
- **Never edit a checkout that has someone else's branch checked out.** The user
|
|
59
|
+
works live in `~/boost`; long-running loops use a separate `git worktree`
|
|
60
|
+
(e.g. `~/boost-loop`, which needs its **own** `.venv` because mutmut wants an
|
|
61
|
+
editable `pip install -e .` pointed at that tree). Run `git worktree list` and
|
|
62
|
+
`git status` before touching a tree — staged or unstaged changes mean another
|
|
63
|
+
agent is mid-edit there, so pick a different worktree.
|
|
64
|
+
- **Branch off `origin/main`, one topic per branch.** `git fetch origin main`
|
|
65
|
+
then `git checkout -B loop/<topic> origin/main`. Keep changes additive and
|
|
66
|
+
file-scoped so parallel branches merge without conflicts.
|
|
67
|
+
- **Coordinate through PRs, not the working tree.** `gh pr merge` is server-side
|
|
68
|
+
and won't touch anyone's local checkout. Before committing a file, inspect
|
|
69
|
+
`git status` / `git diff` — if another session has it staged or dirty, leave
|
|
70
|
+
it to that owner rather than committing over their in-flight work.
|
|
71
|
+
- **Every merge to `main` cuts a PyPI release.** Land one coherent change per PR,
|
|
72
|
+
never merge onto a red release, and after merging confirm the publish workflow
|
|
73
|
+
goes green — not just the PR checks.
|
|
74
|
+
|
|
53
75
|
## Layout
|
|
54
76
|
|
|
55
77
|
- `boost_cli/commands/` — CLI command groups · `boost_cli/core/` — engine (the mutation-gated code)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boost-skill-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.15
|
|
4
4
|
Summary: boost — Homebrew for AI coding skills
|
|
5
5
|
Author: Jonathan Reyes
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -718,7 +718,11 @@ pulled from GitHub-hosted registries, then hooks them straight into
|
|
|
718
718
|
skill files into three different agent folders and hoping you remember to
|
|
719
719
|
update all of them, you run one command.
|
|
720
720
|
|
|
721
|
-
|
|
721
|
+
<p align="center">
|
|
722
|
+
<img src="docs/demo.gif" alt="boost demo: tap a registry, search for a skill, install it into every agent, then run doctor" width="820">
|
|
723
|
+
<br>
|
|
724
|
+
<sub><em>tap a registry → search → install into every agent → doctor — one flow</em></sub>
|
|
725
|
+
</p>
|
|
722
726
|
|
|
723
727
|
## What's a "skill," exactly?
|
|
724
728
|
|
|
@@ -797,7 +801,7 @@ boost doctor # sanity check: broken links, lock drift, stale taps
|
|
|
797
801
|
boost bundle dump > Boostfile # everyone else runs: boost bundle install
|
|
798
802
|
```
|
|
799
803
|
|
|
800
|
-
##
|
|
804
|
+
## 75 commands, organized into 8 groups
|
|
801
805
|
|
|
802
806
|
`boost --help` prints the full grouped command list; for a visual tour see
|
|
803
807
|
[`docs/overview.html`](docs/overview.html).
|
|
@@ -805,12 +809,12 @@ boost bundle dump > Boostfile # everyone else runs: boost bundle install
|
|
|
805
809
|
| Group | Commands |
|
|
806
810
|
|---|---|
|
|
807
811
|
| **Package Management** | install · uninstall · sync · update · reinstall · bundle · import · migrate · pin · unpin · snapshot · export |
|
|
808
|
-
| **Discovery & Search** | search · discover · recommend · browse · index · trending · stats · count |
|
|
812
|
+
| **Discovery & Search** | search · reindex · discover · recommend · browse · index · trending · stats · count |
|
|
809
813
|
| **Skill Information** | list · info · cat · edit · preview · explain · log · home · deps · tag |
|
|
810
814
|
| **Registry (Taps)** | tap · untap · taps · outdated |
|
|
811
815
|
| **Intelligence** | distill · simulate · infer · absorb · evolve · context · focus · impact |
|
|
812
816
|
| **Quality & Health** | doctor · lint · audit · verify · drift · test · fingerprint · quarantine · decay · heal · conflict · changelog · attest · health |
|
|
813
|
-
| **Configuration** | config · clean · create · policy · onboard · completions · schedule · serve · mcp · self-update |
|
|
817
|
+
| **Configuration** | config · clean · create · policy · onboard · completions · schedule · serve · mcp · hooks · bmad · self-update |
|
|
814
818
|
| **Team & Collaboration** | cohort · profile · protocol · pulse · replay · who |
|
|
815
819
|
|
|
816
820
|
The AI-assisted commands (`search --smart`, `explain`, `distill`, `infer`,
|
|
@@ -819,6 +823,53 @@ available on your PATH (or `ANTHROPIC_API_KEY` is set), and fall back to
|
|
|
819
823
|
plain heuristics when it isn't — so the tool still works without an API key,
|
|
820
824
|
just less cleverly.
|
|
821
825
|
|
|
826
|
+
## Claude Code hooks & the BMAD Method
|
|
827
|
+
|
|
828
|
+
`boost hooks` manages Claude Code hooks in `settings.json` at either scope —
|
|
829
|
+
`--scope project` (`./.claude/settings.json`) or `--scope global`
|
|
830
|
+
(`~/.claude/settings.json`). boost only ever touches hooks it created (tagged
|
|
831
|
+
with a `# boost:<name>` marker in the command), never your own, and snapshots
|
|
832
|
+
the prior file before each write.
|
|
833
|
+
|
|
834
|
+
```bash
|
|
835
|
+
boost hooks add SessionStart -c 'echo hello' -n greet --scope project
|
|
836
|
+
boost hooks list
|
|
837
|
+
boost hooks remove -n greet --scope project
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
`boost bmad` installs and manages the [BMAD Method](https://bmadcode.com/)
|
|
841
|
+
(agentic Analyst / PM / Architect / Dev / QA personas + workflows) as a
|
|
842
|
+
scope-aware, toggleable startup experience. Provisioning is delegated to the
|
|
843
|
+
canonical `npx bmad-method install` (needs Node.js 20.12+); boost owns scope,
|
|
844
|
+
the startup toggle, and teardown.
|
|
845
|
+
|
|
846
|
+
```bash
|
|
847
|
+
boost bmad install --scope project # skills + per-project _bmad/ runtime
|
|
848
|
+
boost bmad install --scope global # skills into ~/.claude/skills for every session
|
|
849
|
+
boost bmad init # add the _bmad/ runtime to the current repo
|
|
850
|
+
boost bmad startup on # inject light BMAD orientation on session start
|
|
851
|
+
boost bmad startup off # stop injecting it (skills stay installed)
|
|
852
|
+
boost bmad disable / enable # quarantine / restore skills (recoverable)
|
|
853
|
+
boost bmad uninstall # delete skills + _bmad/ for a scope
|
|
854
|
+
boost bmad doctor # what's installed where
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
When startup is on, a `SessionStart` hook runs `boost bmad orient`, which prints
|
|
858
|
+
a short orientation into the session only while the toggle is enabled. Global
|
|
859
|
+
installs stage the installer in a temp dir and copy only the `bmad-*` skills, so
|
|
860
|
+
`$HOME` never gets a stray `_bmad/` — the workflow runtime stays per-project.
|
|
861
|
+
|
|
862
|
+
## The boost style
|
|
863
|
+
|
|
864
|
+
The visual identity behind the docs and demos ships as a small, dependency-free
|
|
865
|
+
design system in [`style/`](style/) — the *Aurora living-glass* look: a
|
|
866
|
+
cyan → violet → pink triad on a near-black ground, an ambient aurora that drifts
|
|
867
|
+
behind the page, and glass cards that light up under the cursor. Drop
|
|
868
|
+
[`style/boost.css`](style/boost.css) (and the optional
|
|
869
|
+
[`style/boost.js`](style/boost.js)) into any static page; see
|
|
870
|
+
[`style/demo.html`](style/demo.html) for the whole system on one page and
|
|
871
|
+
[`style/README.md`](style/README.md) for the tokens and class reference.
|
|
872
|
+
|
|
822
873
|
## Working on boost
|
|
823
874
|
|
|
824
875
|
```bash
|
|
@@ -14,7 +14,11 @@ pulled from GitHub-hosted registries, then hooks them straight into
|
|
|
14
14
|
skill files into three different agent folders and hoping you remember to
|
|
15
15
|
update all of them, you run one command.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src="docs/demo.gif" alt="boost demo: tap a registry, search for a skill, install it into every agent, then run doctor" width="820">
|
|
19
|
+
<br>
|
|
20
|
+
<sub><em>tap a registry → search → install into every agent → doctor — one flow</em></sub>
|
|
21
|
+
</p>
|
|
18
22
|
|
|
19
23
|
## What's a "skill," exactly?
|
|
20
24
|
|
|
@@ -93,7 +97,7 @@ boost doctor # sanity check: broken links, lock drift, stale taps
|
|
|
93
97
|
boost bundle dump > Boostfile # everyone else runs: boost bundle install
|
|
94
98
|
```
|
|
95
99
|
|
|
96
|
-
##
|
|
100
|
+
## 75 commands, organized into 8 groups
|
|
97
101
|
|
|
98
102
|
`boost --help` prints the full grouped command list; for a visual tour see
|
|
99
103
|
[`docs/overview.html`](docs/overview.html).
|
|
@@ -101,12 +105,12 @@ boost bundle dump > Boostfile # everyone else runs: boost bundle install
|
|
|
101
105
|
| Group | Commands |
|
|
102
106
|
|---|---|
|
|
103
107
|
| **Package Management** | install · uninstall · sync · update · reinstall · bundle · import · migrate · pin · unpin · snapshot · export |
|
|
104
|
-
| **Discovery & Search** | search · discover · recommend · browse · index · trending · stats · count |
|
|
108
|
+
| **Discovery & Search** | search · reindex · discover · recommend · browse · index · trending · stats · count |
|
|
105
109
|
| **Skill Information** | list · info · cat · edit · preview · explain · log · home · deps · tag |
|
|
106
110
|
| **Registry (Taps)** | tap · untap · taps · outdated |
|
|
107
111
|
| **Intelligence** | distill · simulate · infer · absorb · evolve · context · focus · impact |
|
|
108
112
|
| **Quality & Health** | doctor · lint · audit · verify · drift · test · fingerprint · quarantine · decay · heal · conflict · changelog · attest · health |
|
|
109
|
-
| **Configuration** | config · clean · create · policy · onboard · completions · schedule · serve · mcp · self-update |
|
|
113
|
+
| **Configuration** | config · clean · create · policy · onboard · completions · schedule · serve · mcp · hooks · bmad · self-update |
|
|
110
114
|
| **Team & Collaboration** | cohort · profile · protocol · pulse · replay · who |
|
|
111
115
|
|
|
112
116
|
The AI-assisted commands (`search --smart`, `explain`, `distill`, `infer`,
|
|
@@ -115,6 +119,53 @@ available on your PATH (or `ANTHROPIC_API_KEY` is set), and fall back to
|
|
|
115
119
|
plain heuristics when it isn't — so the tool still works without an API key,
|
|
116
120
|
just less cleverly.
|
|
117
121
|
|
|
122
|
+
## Claude Code hooks & the BMAD Method
|
|
123
|
+
|
|
124
|
+
`boost hooks` manages Claude Code hooks in `settings.json` at either scope —
|
|
125
|
+
`--scope project` (`./.claude/settings.json`) or `--scope global`
|
|
126
|
+
(`~/.claude/settings.json`). boost only ever touches hooks it created (tagged
|
|
127
|
+
with a `# boost:<name>` marker in the command), never your own, and snapshots
|
|
128
|
+
the prior file before each write.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
boost hooks add SessionStart -c 'echo hello' -n greet --scope project
|
|
132
|
+
boost hooks list
|
|
133
|
+
boost hooks remove -n greet --scope project
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`boost bmad` installs and manages the [BMAD Method](https://bmadcode.com/)
|
|
137
|
+
(agentic Analyst / PM / Architect / Dev / QA personas + workflows) as a
|
|
138
|
+
scope-aware, toggleable startup experience. Provisioning is delegated to the
|
|
139
|
+
canonical `npx bmad-method install` (needs Node.js 20.12+); boost owns scope,
|
|
140
|
+
the startup toggle, and teardown.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
boost bmad install --scope project # skills + per-project _bmad/ runtime
|
|
144
|
+
boost bmad install --scope global # skills into ~/.claude/skills for every session
|
|
145
|
+
boost bmad init # add the _bmad/ runtime to the current repo
|
|
146
|
+
boost bmad startup on # inject light BMAD orientation on session start
|
|
147
|
+
boost bmad startup off # stop injecting it (skills stay installed)
|
|
148
|
+
boost bmad disable / enable # quarantine / restore skills (recoverable)
|
|
149
|
+
boost bmad uninstall # delete skills + _bmad/ for a scope
|
|
150
|
+
boost bmad doctor # what's installed where
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
When startup is on, a `SessionStart` hook runs `boost bmad orient`, which prints
|
|
154
|
+
a short orientation into the session only while the toggle is enabled. Global
|
|
155
|
+
installs stage the installer in a temp dir and copy only the `bmad-*` skills, so
|
|
156
|
+
`$HOME` never gets a stray `_bmad/` — the workflow runtime stays per-project.
|
|
157
|
+
|
|
158
|
+
## The boost style
|
|
159
|
+
|
|
160
|
+
The visual identity behind the docs and demos ships as a small, dependency-free
|
|
161
|
+
design system in [`style/`](style/) — the *Aurora living-glass* look: a
|
|
162
|
+
cyan → violet → pink triad on a near-black ground, an ambient aurora that drifts
|
|
163
|
+
behind the page, and glass cards that light up under the cursor. Drop
|
|
164
|
+
[`style/boost.css`](style/boost.css) (and the optional
|
|
165
|
+
[`style/boost.js`](style/boost.js)) into any static page; see
|
|
166
|
+
[`style/demo.html`](style/demo.html) for the whole system on one page and
|
|
167
|
+
[`style/README.md`](style/README.md) for the tokens and class reference.
|
|
168
|
+
|
|
118
169
|
## Working on boost
|
|
119
170
|
|
|
120
171
|
```bash
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.15'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 15)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""boost CLI dispatcher —
|
|
1
|
+
"""boost CLI dispatcher — 75 commands across 8 groups.
|
|
2
2
|
|
|
3
3
|
Each command lives in boost_cli/commands/<module>.py as
|
|
4
4
|
def cmd_<name_with_underscores>(argv: list[str]) -> int
|
|
@@ -60,8 +60,9 @@ COMMANDS = [
|
|
|
60
60
|
("unpin", "pkg", "pkg", "Allow a pinned skill to update again"),
|
|
61
61
|
("snapshot", "pkg", "pkg", "Save & restore whole skill environments"),
|
|
62
62
|
("export", "pkg", "pkg", "Package skills as shareable zip/tar archives"),
|
|
63
|
-
# Discovery & Search (
|
|
63
|
+
# Discovery & Search (9)
|
|
64
64
|
("search", "find", "discovery", "Search skills across tap registries (AI-ranked)"),
|
|
65
|
+
("reindex", "find", "discovery", "Build/refresh the full-content search index"),
|
|
65
66
|
("discover", "find", "discovery", "Browse & search the GitHub-wide skill discovery index"),
|
|
66
67
|
("recommend", "find", "discovery", "Suggest skills based on your project's tech stack"),
|
|
67
68
|
("browse", "find", "discovery", "Interactive full-screen TUI with fuzzy search"),
|
|
@@ -109,7 +110,7 @@ COMMANDS = [
|
|
|
109
110
|
("changelog", "chk", "quality", "Show a skill's upstream change history"),
|
|
110
111
|
("attest", "chk", "quality", "Display/verify the install record for skills"),
|
|
111
112
|
("health", "chk", "quality", "Dashboard of skill-environment health"),
|
|
112
|
-
# Configuration (
|
|
113
|
+
# Configuration (12)
|
|
113
114
|
("config", "cfg", "configuration", "Display or modify boost configuration"),
|
|
114
115
|
("clean", "cfg", "configuration", "Clear stale caches & broken symlinks"),
|
|
115
116
|
("create", "cfg", "configuration", "Scaffold a new skill from a template"),
|
|
@@ -119,6 +120,8 @@ COMMANDS = [
|
|
|
119
120
|
("schedule", "cfg", "configuration", "Manage automatic skill-sync scheduling"),
|
|
120
121
|
("serve", "cfg", "configuration", "Serve the skill catalog over HTTP (port 8787)"),
|
|
121
122
|
("mcp", "cfg", "configuration", "Register boost as an MCP server for Claude Code"),
|
|
123
|
+
("hooks", "cfg", "hooks", "Manage Claude Code hooks (scope-aware) in settings.json"),
|
|
124
|
+
("bmad", "cfg", "bmad", "Install & manage the BMAD Method (scope-aware, toggleable)"),
|
|
122
125
|
("self-update", "cfg", "configuration", "Update boost itself to the latest version"),
|
|
123
126
|
# Team & Collaboration (6)
|
|
124
127
|
("cohort", "team", "team", "Controlled skill rollouts & team A/B testing"),
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""boost bmad — install & manage the BMAD Method, scope-aware and toggleable.
|
|
2
|
+
|
|
3
|
+
BMAD ships as Claude Code skills PLUS a per-project `_bmad/` config/runtime that
|
|
4
|
+
the skills read on activation. boost owns scope, the startup toggle, and teardown;
|
|
5
|
+
full provisioning is delegated to the canonical `npx bmad-method install`.
|
|
6
|
+
|
|
7
|
+
boost bmad install --scope project # skills + _bmad/ runtime in this repo
|
|
8
|
+
boost bmad install --scope global # skills into ~/.claude/skills (+ toggle)
|
|
9
|
+
boost bmad init # add _bmad/ runtime to the current repo
|
|
10
|
+
boost bmad startup on|off|status # toggle the SessionStart orientation
|
|
11
|
+
boost bmad disable / enable # quarantine / restore skills (recoverable)
|
|
12
|
+
boost bmad uninstall # delete skills + _bmad/ for a scope
|
|
13
|
+
boost bmad doctor # what's installed where
|
|
14
|
+
|
|
15
|
+
Global installs stage the installer in a temp dir and copy only the `bmad-*`
|
|
16
|
+
skills into ~/.claude/skills, so $HOME never gets a stray `_bmad/`. The `_bmad/`
|
|
17
|
+
runtime is per-project (`boost bmad init`).
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import getpass
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import re
|
|
26
|
+
import shlex
|
|
27
|
+
import shutil
|
|
28
|
+
import subprocess
|
|
29
|
+
import tempfile
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
from ..core import claude_settings as cs
|
|
33
|
+
from ..core import journal
|
|
34
|
+
from ..core import output as out
|
|
35
|
+
from ..core import paths, util
|
|
36
|
+
from ..errors import BoostError
|
|
37
|
+
|
|
38
|
+
DEFAULT_MODULES = "bmm"
|
|
39
|
+
HOOK_NAME = "bmad"
|
|
40
|
+
HOOK_MATCHER = "startup|resume|clear"
|
|
41
|
+
|
|
42
|
+
ORIENT = """[BMAD MODE ACTIVE — light orientation]
|
|
43
|
+
This environment has the BMAD Method installed as Claude Code skills. Be
|
|
44
|
+
BMAD-aware without hijacking simple requests: use the workflow when the user is
|
|
45
|
+
planning or building a feature; answer trivial one-off asks directly.
|
|
46
|
+
|
|
47
|
+
Entry point: invoke the `bmad-help` skill to assess state and recommend a step.
|
|
48
|
+
|
|
49
|
+
Persona agents (skills): bmad-agent-analyst (Mary, research/brief),
|
|
50
|
+
bmad-agent-pm (John, PRD), bmad-agent-architect (Winston, architecture),
|
|
51
|
+
bmad-agent-ux-designer (Sally), bmad-agent-dev (Amelia, implementation),
|
|
52
|
+
bmad-agent-tech-writer (Paige).
|
|
53
|
+
|
|
54
|
+
Phases: Analysis (bmad-brainstorming -> bmad-product-brief -> research) ->
|
|
55
|
+
Planning (bmad-prd -> bmad-architecture -> bmad-create-epics-and-stories) ->
|
|
56
|
+
Build (bmad-sprint-planning -> bmad-create-story -> bmad-dev-story ->
|
|
57
|
+
bmad-code-review -> bmad-qa-generate-e2e-tests -> bmad-retrospective).
|
|
58
|
+
|
|
59
|
+
Full workflows need a per-project `_bmad/` runtime — run `boost bmad init` in a
|
|
60
|
+
repo if it isn't there yet. Turn this orientation off with `boost bmad startup off`."""
|
|
61
|
+
|
|
62
|
+
_ACTIONS = ("install", "init", "startup", "orient", "uninstall",
|
|
63
|
+
"disable", "enable", "doctor", "status")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cmd_bmad(argv) -> int:
|
|
67
|
+
p = argparse.ArgumentParser(
|
|
68
|
+
prog="boost bmad",
|
|
69
|
+
description="Install & manage the BMAD Method (scope-aware, toggleable)")
|
|
70
|
+
p.add_argument("action", choices=_ACTIONS, help=" | ".join(_ACTIONS))
|
|
71
|
+
p.add_argument("value", nargs="?", help="for startup: on | off | status")
|
|
72
|
+
p.add_argument("-s", "--scope", choices=("project", "global"), default=None,
|
|
73
|
+
help="target scope (default: project)")
|
|
74
|
+
p.add_argument("--modules", default=DEFAULT_MODULES,
|
|
75
|
+
help="BMAD modules to install (default: %s)" % DEFAULT_MODULES)
|
|
76
|
+
p.add_argument("-y", "--yes", action="store_true",
|
|
77
|
+
help="skip confirmation prompts")
|
|
78
|
+
p.add_argument("--startup", action="store_true",
|
|
79
|
+
help="enable the startup toggle right after install")
|
|
80
|
+
args = p.parse_args(argv)
|
|
81
|
+
|
|
82
|
+
if args.action == "install":
|
|
83
|
+
return _install(args.scope, args.modules, args.startup)
|
|
84
|
+
if args.action == "init":
|
|
85
|
+
return _init(args.modules, args.startup)
|
|
86
|
+
if args.action == "startup":
|
|
87
|
+
return _startup(args.value or "status", args.scope)
|
|
88
|
+
if args.action == "orient":
|
|
89
|
+
return _orient(args.scope)
|
|
90
|
+
if args.action == "uninstall":
|
|
91
|
+
return _uninstall(args.scope, args.yes)
|
|
92
|
+
if args.action == "disable":
|
|
93
|
+
return _disable(args.scope)
|
|
94
|
+
if args.action == "enable":
|
|
95
|
+
return _enable(args.scope)
|
|
96
|
+
return _doctor() # doctor | status
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ------------------------------------------------------------------- provisioning
|
|
100
|
+
|
|
101
|
+
def _require_npx() -> None:
|
|
102
|
+
if not shutil.which("npx"):
|
|
103
|
+
raise BoostError(
|
|
104
|
+
"npx not found — BMAD is provisioned via `npx bmad-method install`",
|
|
105
|
+
hint="install Node.js 20.12+ (e.g. `brew install node`), then retry")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _run_installer(directory: Path, modules: str) -> "subprocess.CompletedProcess":
|
|
109
|
+
cmd = ["npx", "--yes", "bmad-method@latest", "install",
|
|
110
|
+
"--yes", "--directory", str(directory),
|
|
111
|
+
"--tools", "claude-code", "--modules", modules,
|
|
112
|
+
"--user-name", _whoami()]
|
|
113
|
+
try:
|
|
114
|
+
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=900)
|
|
115
|
+
except (OSError, subprocess.TimeoutExpired) as e:
|
|
116
|
+
raise BoostError("bmad install failed: %s" % e,
|
|
117
|
+
hint="run it yourself: " + " ".join(cmd))
|
|
118
|
+
if proc.returncode != 0:
|
|
119
|
+
tail = (proc.stderr or proc.stdout or "").strip().splitlines()
|
|
120
|
+
raise BoostError(
|
|
121
|
+
"bmad install failed: %s" % (tail[-1] if tail else "unknown error"),
|
|
122
|
+
hint="run it yourself: " + " ".join(cmd))
|
|
123
|
+
return proc
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _whoami() -> str:
|
|
127
|
+
try:
|
|
128
|
+
return getpass.getuser()
|
|
129
|
+
except Exception:
|
|
130
|
+
return "developer"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _parse_version(text: str) -> str:
|
|
134
|
+
m = re.search(r"v(\d+\.\d+\.\d+)", text or "")
|
|
135
|
+
return m.group(1) if m else "unknown"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _install(scope, modules, do_startup) -> int:
|
|
139
|
+
scope = scope or "project"
|
|
140
|
+
_require_npx()
|
|
141
|
+
if scope == "global":
|
|
142
|
+
n = _copy_global_skills(modules)
|
|
143
|
+
_set_scope_state("global", installed=True, skills=n,
|
|
144
|
+
modules=modules.split(","), installed_at=util.now_iso())
|
|
145
|
+
out.ok("installed %d BMAD skill(s) globally → %s"
|
|
146
|
+
% (n, _skills_dir("global")))
|
|
147
|
+
out.dim(" run `boost bmad init` in a project for its _bmad/ workflow runtime")
|
|
148
|
+
else:
|
|
149
|
+
ver, n = _install_project_runtime(modules)
|
|
150
|
+
out.ok("installed BMAD in %s (%d skills, v%s)" % (Path.cwd(), n, ver))
|
|
151
|
+
if do_startup:
|
|
152
|
+
_startup("on", scope)
|
|
153
|
+
return 0
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _init(modules, do_startup) -> int:
|
|
157
|
+
_require_npx()
|
|
158
|
+
ver, n = _install_project_runtime(modules)
|
|
159
|
+
out.ok("BMAD runtime ready in %s (_bmad/, %d skills, v%s)"
|
|
160
|
+
% (Path.cwd(), n, ver))
|
|
161
|
+
if do_startup:
|
|
162
|
+
_startup("on", "project")
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _install_project_runtime(modules):
|
|
167
|
+
target = Path.cwd()
|
|
168
|
+
proc = _run_installer(target, modules)
|
|
169
|
+
ver = _parse_version((proc.stdout or "") + (proc.stderr or ""))
|
|
170
|
+
n = _count_skills(_skills_dir("project"))
|
|
171
|
+
_set_scope_state("project", installed=True, skills=n, version=ver,
|
|
172
|
+
modules=modules.split(","), installed_at=util.now_iso())
|
|
173
|
+
return ver, n
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _copy_global_skills(modules) -> int:
|
|
177
|
+
"""Stage the installer in a temp dir; copy only bmad-* skills into ~/.claude."""
|
|
178
|
+
stage = Path(tempfile.mkdtemp(prefix="boost-bmad-"))
|
|
179
|
+
try:
|
|
180
|
+
_run_installer(stage, modules)
|
|
181
|
+
src = stage / ".claude" / "skills"
|
|
182
|
+
dest = _skills_dir("global")
|
|
183
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
184
|
+
n = 0
|
|
185
|
+
for d in sorted(src.glob("bmad-*")):
|
|
186
|
+
if not d.is_dir():
|
|
187
|
+
continue
|
|
188
|
+
t = dest / d.name
|
|
189
|
+
if t.exists():
|
|
190
|
+
shutil.rmtree(t)
|
|
191
|
+
shutil.copytree(d, t)
|
|
192
|
+
n += 1
|
|
193
|
+
return n
|
|
194
|
+
finally:
|
|
195
|
+
shutil.rmtree(stage, ignore_errors=True)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ----------------------------------------------------------------------- toggle
|
|
199
|
+
|
|
200
|
+
def _startup(value, scope) -> int:
|
|
201
|
+
scope = scope or "project"
|
|
202
|
+
if value == "on":
|
|
203
|
+
cmd = "%s bmad orient --scope %s" % (
|
|
204
|
+
shlex.quote(str(paths.launcher())), scope)
|
|
205
|
+
cs.add_hook(scope, "SessionStart", HOOK_NAME, cmd, matcher=HOOK_MATCHER)
|
|
206
|
+
_set_scope_state(scope, startup=True)
|
|
207
|
+
journal.log("bmad-startup", "on", scope=scope)
|
|
208
|
+
out.ok("BMAD startup ON (%s) — new sessions get orientation" % scope)
|
|
209
|
+
out.dim(" hook → %s" % cs.settings_path(scope))
|
|
210
|
+
return 0
|
|
211
|
+
if value == "off":
|
|
212
|
+
cs.remove_hook(scope, "SessionStart", HOOK_NAME)
|
|
213
|
+
_set_scope_state(scope, startup=False)
|
|
214
|
+
journal.log("bmad-startup", "off", scope=scope)
|
|
215
|
+
out.ok("BMAD startup OFF (%s) — skills stay installed" % scope)
|
|
216
|
+
return 0
|
|
217
|
+
return _status(scope)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _orient(scope) -> int:
|
|
221
|
+
"""SessionStart hook target: print orientation iff enabled (else silent)."""
|
|
222
|
+
scope = scope or "project"
|
|
223
|
+
try:
|
|
224
|
+
if _get_scope_state(scope).get("startup"):
|
|
225
|
+
print(ORIENT)
|
|
226
|
+
except Exception:
|
|
227
|
+
pass # a hook must never break the session
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _status(scope) -> int:
|
|
232
|
+
st = _get_scope_state(scope)
|
|
233
|
+
hook = cs.has_hook(scope, "SessionStart", HOOK_NAME)
|
|
234
|
+
n = _count_skills(_skills_dir(scope))
|
|
235
|
+
out.heading("BMAD startup — %s" % scope)
|
|
236
|
+
out.kv("enabled", str(bool(st.get("startup"))))
|
|
237
|
+
out.kv("hook", "present" if hook else "absent")
|
|
238
|
+
out.kv("skills", str(n))
|
|
239
|
+
out.kv("installed", str(bool(st.get("installed"))))
|
|
240
|
+
return 0
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ------------------------------------------------------------------- teardown
|
|
244
|
+
|
|
245
|
+
def _uninstall(scope, yes) -> int:
|
|
246
|
+
scope = scope or "project"
|
|
247
|
+
if not (yes or out.confirm(
|
|
248
|
+
"Remove BMAD (%s)? deletes bmad-* skills%s" % (
|
|
249
|
+
scope, " + _bmad/, _bmad-output/" if scope == "project" else ""))):
|
|
250
|
+
out.info("aborted")
|
|
251
|
+
return 0
|
|
252
|
+
cs.remove_hook(scope, "SessionStart", HOOK_NAME)
|
|
253
|
+
removed = _rm_skills(_skills_dir(scope))
|
|
254
|
+
if scope == "project":
|
|
255
|
+
for extra in ("_bmad", "_bmad-output"):
|
|
256
|
+
pth = Path.cwd() / extra
|
|
257
|
+
if pth.exists():
|
|
258
|
+
shutil.rmtree(pth, ignore_errors=True)
|
|
259
|
+
removed += 1
|
|
260
|
+
_clear_scope_state(scope)
|
|
261
|
+
journal.log("bmad-uninstall", scope, count=removed)
|
|
262
|
+
out.ok("removed BMAD (%s): %d item(s)" % (scope, removed))
|
|
263
|
+
return 0
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _disable(scope) -> int:
|
|
267
|
+
"""Quarantine: turn startup off and move skills aside (recoverable)."""
|
|
268
|
+
scope = scope or "project"
|
|
269
|
+
cs.remove_hook(scope, "SessionStart", HOOK_NAME)
|
|
270
|
+
qdir = _quarantine_dir(scope)
|
|
271
|
+
qdir.mkdir(parents=True, exist_ok=True)
|
|
272
|
+
moved = 0
|
|
273
|
+
for d in sorted(_skills_dir(scope).glob("bmad-*")):
|
|
274
|
+
target = qdir / d.name
|
|
275
|
+
if target.exists():
|
|
276
|
+
shutil.rmtree(target)
|
|
277
|
+
shutil.move(str(d), str(target))
|
|
278
|
+
moved += 1
|
|
279
|
+
_set_scope_state(scope, startup=False, disabled=True)
|
|
280
|
+
journal.log("bmad-disable", scope, count=moved)
|
|
281
|
+
out.ok("quarantined %d BMAD skill(s) (%s)" % (moved, scope))
|
|
282
|
+
out.dim(" restore with `boost bmad enable --scope %s`" % scope)
|
|
283
|
+
return 0
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _enable(scope) -> int:
|
|
287
|
+
scope = scope or "project"
|
|
288
|
+
qdir = _quarantine_dir(scope)
|
|
289
|
+
restored = 0
|
|
290
|
+
if qdir.exists():
|
|
291
|
+
dest = _skills_dir(scope)
|
|
292
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
293
|
+
for d in sorted(qdir.glob("bmad-*")):
|
|
294
|
+
target = dest / d.name
|
|
295
|
+
if target.exists():
|
|
296
|
+
shutil.rmtree(target)
|
|
297
|
+
shutil.move(str(d), str(target))
|
|
298
|
+
restored += 1
|
|
299
|
+
_set_scope_state(scope, disabled=False)
|
|
300
|
+
if restored:
|
|
301
|
+
journal.log("bmad-enable", scope, count=restored)
|
|
302
|
+
out.ok("restored %d BMAD skill(s) (%s)" % (restored, scope))
|
|
303
|
+
else:
|
|
304
|
+
out.warn("nothing quarantined for %s scope" % scope)
|
|
305
|
+
return 0
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _doctor() -> int:
|
|
309
|
+
out.heading("BMAD status")
|
|
310
|
+
out.kv("npx", shutil.which("npx") or "MISSING")
|
|
311
|
+
out.kv("node", shutil.which("node") or "MISSING")
|
|
312
|
+
for scope in ("global", "project"):
|
|
313
|
+
st = _get_scope_state(scope)
|
|
314
|
+
n = _count_skills(_skills_dir(scope))
|
|
315
|
+
hook = cs.has_hook(scope, "SessionStart", HOOK_NAME)
|
|
316
|
+
out.kv(scope, "skills=%d startup=%s hook=%s installed=%s"
|
|
317
|
+
% (n, bool(st.get("startup")), hook, bool(st.get("installed"))))
|
|
318
|
+
out.dim(" project = %s" % Path.cwd())
|
|
319
|
+
return 0
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# -------------------------------------------------------------------- helpers
|
|
323
|
+
|
|
324
|
+
def _skills_dir(scope) -> Path:
|
|
325
|
+
base = paths.home() if scope == "global" else Path.cwd()
|
|
326
|
+
return base / ".claude" / "skills"
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _count_skills(skills_dir: Path) -> int:
|
|
330
|
+
if not skills_dir.exists():
|
|
331
|
+
return 0
|
|
332
|
+
return sum(1 for d in skills_dir.glob("bmad-*") if d.is_dir())
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _rm_skills(skills_dir: Path) -> int:
|
|
336
|
+
n = 0
|
|
337
|
+
if skills_dir.exists():
|
|
338
|
+
for d in sorted(skills_dir.glob("bmad-*")):
|
|
339
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
340
|
+
n += 1
|
|
341
|
+
return n
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _quarantine_dir(scope) -> Path:
|
|
345
|
+
key = "global" if scope == "global" else _fs_slug(str(Path.cwd().resolve()))
|
|
346
|
+
return paths.state_dir() / "bmad-quarantine" / key
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _fs_slug(p: str) -> str:
|
|
350
|
+
return p.replace(os.sep, "__").strip("_") or "root"
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# ---------------------------------------------------------------------- state
|
|
354
|
+
|
|
355
|
+
def _state_path() -> Path:
|
|
356
|
+
return paths.state_dir() / "bmad.json"
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _state_read() -> dict:
|
|
360
|
+
p = _state_path()
|
|
361
|
+
if not p.exists():
|
|
362
|
+
return {"global": {}, "projects": {}}
|
|
363
|
+
try:
|
|
364
|
+
d = json.loads(p.read_text())
|
|
365
|
+
except (json.JSONDecodeError, OSError):
|
|
366
|
+
return {"global": {}, "projects": {}}
|
|
367
|
+
d.setdefault("global", {})
|
|
368
|
+
d.setdefault("projects", {})
|
|
369
|
+
return d
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _state_write(d: dict) -> None:
|
|
373
|
+
paths.ensure_dirs()
|
|
374
|
+
_state_path().write_text(json.dumps(d, indent=2) + "\n")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _proj_key() -> str:
|
|
378
|
+
return str(Path.cwd().resolve())
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _get_scope_state(scope) -> dict:
|
|
382
|
+
d = _state_read()
|
|
383
|
+
if scope == "global":
|
|
384
|
+
return d["global"]
|
|
385
|
+
return d["projects"].get(_proj_key(), {})
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _set_scope_state(scope, **patch) -> None:
|
|
389
|
+
d = _state_read()
|
|
390
|
+
if scope == "global":
|
|
391
|
+
d["global"].update(patch)
|
|
392
|
+
else:
|
|
393
|
+
d["projects"].setdefault(_proj_key(), {}).update(patch)
|
|
394
|
+
_state_write(d)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _clear_scope_state(scope) -> None:
|
|
398
|
+
d = _state_read()
|
|
399
|
+
if scope == "global":
|
|
400
|
+
d["global"] = {}
|
|
401
|
+
else:
|
|
402
|
+
d["projects"].pop(_proj_key(), None)
|
|
403
|
+
_state_write(d)
|