@softspark/ai-toolkit 4.23.1 → 4.24.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 CHANGED
@@ -7,6 +7,32 @@ Versioning follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## v4.24.0 — external packs stop dying on upgrade (2026-08-18)
11
+
12
+ ### Added
13
+
14
+ - **`~/.softspark/ai-toolkit/plugins/` is a second pack root.** Until now
15
+ `plugin.py` scanned exactly one directory — `app/plugins/`, inside the npm
16
+ package — so the only way to add a pack from its own repository was to drop it
17
+ in there, where the next `npm install -g @softspark/ai-toolkit` deleted it along
18
+ with the rest of `app/`. External packs now live in the user-owned data dir and
19
+ survive every upgrade. `plugin list`, `install`, `update`, `remove` and `status`
20
+ all see both roots; `AI_TOOLKIT_HOME` moves the user root with the data dir.
21
+
22
+ A **core pack wins a name collision**, so an external directory cannot shadow
23
+ shipped behaviour. Each pack dict carries `_root` (`core` or `user`) — `_source`
24
+ was already taken as the hook-ownership marker.
25
+
26
+ The entry may be a directory or a **symlink**, which is what gives an external
27
+ pack a versioned update path: point it at an npm-installed package and
28
+ `npm install -g <pack>@latest` rewrites the target in place, with nothing to
29
+ re-link. Documented in `kb/reference/plugin-pack-conventions.md`
30
+ (*Where Packs Live*), including the per-scope registry caveat when one package
31
+ in a scope is public and another private.
32
+
33
+ - **Three tests.** A pack in the user root is listed, installs with both its agent
34
+ and skill linked back into that root, and loses a name collision to a core pack.
35
+
10
36
  ## v4.23.1 — a pack could ship an agent nobody installed (2026-08-18)
11
37
 
12
38
  ### Fixed
package/README.md CHANGED
@@ -6,18 +6,18 @@
6
6
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
7
7
  [![Skills](https://img.shields.io/badge/skills-109-brightgreen)](app/skills/)
8
8
  [![Agents](https://img.shields.io/badge/agents-44-blue)](app/agents/)
9
- [![Tests](https://img.shields.io/badge/tests-1519%20passing-success)](tests/)
10
-
11
- ## What's New in v4.23.1
12
-
13
- **v4.23.1** — a plugin pack that shipped its own agent never got it installed.
14
- `plugin install` resolved `includes.agents` against the toolkit's own
15
- `app/agents/`, printed `WARN agent not found`, and moved on while the removal
16
- path already knew how to unlink a pack-owned agent, and skills had resolved from
17
- the pack directory all along. `validate.py` had the same blind spot for both
18
- agents and skills, so a self-contained pack was reported broken twice over.
19
- Agents now resolve from the pack first-class, core assets still win, and
20
- validation accepts either. Found by a downstream pack's post-release smoke test.
9
+ [![Tests](https://img.shields.io/badge/tests-1522%20passing-success)](tests/)
10
+
11
+ ## What's New in v4.24.0
12
+
13
+ **v4.24.0** — a plugin pack maintained in its own repository had nowhere safe to
14
+ live: the only directory the toolkit scanned was `app/plugins/`, inside the npm
15
+ package, so every `npm install -g` upgrade deleted it. Packs now also load from
16
+ `~/.softspark/ai-toolkit/plugins/`, which the user owns and upgrades never touch.
17
+ The entry can be a symlink to an npm-installed package, so updating an external
18
+ pack is one `npm install -g` with nothing to re-link. Core packs still win a name
19
+ collision. Ships with v4.23.1's fix for pack-shipped agents, which the same
20
+ external-pack workflow depends on.
21
21
 
22
22
  See [CHANGELOG.md](CHANGELOG.md) for full history.
23
23
 
@@ -3,7 +3,7 @@
3
3
  "name": "ai-toolkit",
4
4
  "displayName": "AI Toolkit",
5
5
  "description": "Professional-grade engineering skills, agents, rules, and lifecycle guardrails for Claude Code, Claude Chat, and Cowork.",
6
- "version": "4.23.1",
6
+ "version": "4.24.0",
7
7
  "author": {
8
8
  "name": "SoftSpark",
9
9
  "url": "https://github.com/softspark"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: documentation-standards
3
- description: "KB conventions: YAML frontmatter, 8-category taxonomy (reference/howto/procedures/troubleshooting/best-practices/decisions/runbooks/planning). Triggers: kb/, SOP, runbook, howto, frontmatter, knowledge base."
3
+ description: "KB conventions: YAML frontmatter, 10-category taxonomy (reference/howto/procedures/troubleshooting/best-practices/decisions/runbooks/planning/business/templates). Triggers: kb/, SOP, runbook, howto, frontmatter, knowledge base."
4
4
  effort: medium
5
5
  user-invocable: false
6
6
  allowed-tools: Read
@@ -17,7 +17,7 @@ Every document in `kb/` MUST start with YAML frontmatter:
17
17
  ```yaml
18
18
  ---
19
19
  title: "Document Title" # REQUIRED — English, descriptive
20
- category: reference # REQUIRED — one of 5 valid categories
20
+ category: reference # REQUIRED — one of the 10 valid categories
21
21
  service: ai-toolkit # REQUIRED — service identifier
22
22
  tags: [tag1, tag2, tag3] # REQUIRED — minimum 1, recommended 3+
23
23
  last_updated: "YYYY-MM-DD" # REQUIRED — ISO format
@@ -53,11 +53,21 @@ and never authoritative on its own.
53
53
  | `decisions` | `kb/decisions/` | Architecture decision records and design rationale | `adr-004-kb-migration.md` |
54
54
  | `runbooks` | `kb/runbooks/` | Procedures run against a live system, usually under pressure | `deployment.md`, `incident-response.md` |
55
55
  | `planning` | `kb/planning/` | Roadmaps, PRDs, work not yet done | `q3-roadmap.md` |
56
+ | `business` | `kb/business/` | Domain model, requirements, use cases, user stories | `domain-model.md`, `user-stories.md` |
57
+ | `templates` | `kb/templates/` | Reusable document templates | `adr-template.md`, `sop-template.md` |
56
58
 
57
59
  **Rule:** A document filed under one of the directories above MUST declare that
58
60
  category. The rule is scoped to those directories deliberately: `kb/history/`
59
- and similar are lifecycle locations rather than types, and a finished plan filed
60
- under `history/completed/` is still a `planning` document.
61
+ and `kb/summaries/` are lifecycle and runtime locations rather than types, and a
62
+ finished plan filed under `history/completed/` is still a `planning` document.
63
+
64
+ **Templates carry placeholders on purpose.** A file under `templates/` exists to
65
+ be copied, so a literal `YYYY-MM-DD` date and `[placeholder]` body text are
66
+ correct there rather than defects. Every other convention still applies.
67
+
68
+ This taxonomy lives in three places: ai-toolkit's `scripts/validate.py`,
69
+ rag-mcp's `scripts/validate_kb_frontmatter.py`, and this document. They are one
70
+ list, and a change belongs in all three.
61
71
 
62
72
  ## Naming Conventions
63
73
 
package/app/surface.json CHANGED
@@ -230,12 +230,14 @@
230
230
  ],
231
231
  "kb_categories": [
232
232
  "best-practices",
233
+ "business",
233
234
  "decisions",
234
235
  "howto",
235
236
  "planning",
236
237
  "procedures",
237
238
  "reference",
238
239
  "runbooks",
240
+ "templates",
239
241
  "troubleshooting"
240
242
  ],
241
243
  "cli_commands": [
@@ -3,9 +3,9 @@ title: "Plugin Pack Conventions"
3
3
  category: reference
4
4
  service: ai-toolkit
5
5
  tags: [plugins, plugin-packs, conventions, manifests, hooks, policy-packs]
6
- version: "1.1.0"
6
+ version: "1.2.0"
7
7
  created: "2026-03-28"
8
- last_updated: "2026-07-14"
8
+ last_updated: "2026-08-18"
9
9
  description: "Conventions for experimental ai-toolkit plugin packs, policy packs, hook packs, and plugin-creator scaffolding across Claude Code and Codex runtimes."
10
10
  ---
11
11
 
@@ -13,7 +13,46 @@ description: "Conventions for experimental ai-toolkit plugin packs, policy packs
13
13
 
14
14
  ## Purpose
15
15
 
16
- `ai-toolkit` includes experimental plugin packs under `app/plugins/` for Claude Code and optional global Codex layering. These internal pack manifests are distinct from the official uploadable Claude app plugin built by `ai-toolkit claude-app export`.
16
+ `ai-toolkit` includes experimental plugin packs under `app/plugins/` for Claude Code and optional global Codex layering, and discovers external packs under `~/.softspark/ai-toolkit/plugins/` (see *Where Packs Live*). These internal pack manifests are distinct from the official uploadable Claude app plugin built by `ai-toolkit claude-app export`.
17
+
18
+ ## Where Packs Live
19
+
20
+ | Root | Owner | Survives `npm install -g @softspark/ai-toolkit` | Use for |
21
+ |------|-------|------------------------------------------------|---------|
22
+ | `app/plugins/<pack>/` | this repository | no — the upgrade replaces `app/` wholesale | packs shipped with the toolkit (`memory-pack`, `enterprise-pack`) |
23
+ | `~/.softspark/ai-toolkit/plugins/<pack>/` | the user | **yes** | external / private packs maintained in their own repository |
24
+
25
+ Both roots are scanned by `plugin list`, `install`, `update`, `remove` and
26
+ `status`. A **core pack wins a name collision**, so an external directory cannot
27
+ shadow shipped behaviour. `AI_TOOLKIT_HOME` moves the user root along with the
28
+ rest of the toolkit data dir, which is what the test suite uses.
29
+
30
+ Before v4.24.0 there was only the first root, so the only way to add an external
31
+ pack was to drop it inside the npm package — where the next toolkit upgrade
32
+ deleted it. That is what the user root exists to end.
33
+
34
+ ### Installing an external pack
35
+
36
+ The entry may be a directory **or a symlink**, which is what gives an external
37
+ pack a versioned update path:
38
+
39
+ ```bash
40
+ # Distributed as a package (private registry example)
41
+ npm install -g @softspark/<pack> --registry=https://npm.pkg.github.com
42
+ mkdir -p ~/.softspark/ai-toolkit/plugins
43
+ ln -s "$(npm root -g)/@softspark/<pack>" ~/.softspark/ai-toolkit/plugins/<pack>
44
+ ai-toolkit plugin install --editor claude <pack>
45
+
46
+ # Update later: the symlink target is rewritten in place, nothing to re-link
47
+ npm install -g @softspark/<pack>@latest --registry=https://npm.pkg.github.com
48
+ ```
49
+
50
+ A plain `git clone` into the same directory works too; update it with `git pull`.
51
+
52
+ > **Scope note:** `npm` resolves a registry per **scope**, not per package. If one
53
+ > package in a scope lives on public npm and another on a private registry, the
54
+ > private one needs `--registry=` on every command — a scope-wide
55
+ > `npm config set @scope:registry` would misroute the public one.
17
56
 
18
57
  ## Pack Types
19
58
 
package/llms-full.txt CHANGED
@@ -109,7 +109,7 @@
109
109
  - **design-engineering**: UI craftsmanship: animation rules, easing, micro-interactions, state polish. Triggers: animation, transition, ease-out, motion, micro-interaction, hover, loading state, UI polish.
110
110
  - **docker-devops**: Docker/K8s: Dockerfile, multi-stage, compose, manifests, Helm. Triggers: Docker, Dockerfile, container, Kubernetes, k8s, compose, Helm, pod.
111
111
  - **docs**: Generates/updates README, API docs, architecture notes. Triggers: docs, README, API docs, architecture note, documentation.
112
- - **documentation-standards**: KB conventions: YAML frontmatter, 8-category taxonomy (reference/howto/procedures/troubleshooting/best-practices/decisions/runbooks/planning). Triggers: kb/, SOP, runbook, howto, frontmatter, knowledge base.
112
+ - **documentation-standards**: KB conventions: YAML frontmatter, 10-category taxonomy (reference/howto/procedures/troubleshooting/best-practices/decisions/runbooks/planning/business/templates). Triggers: kb/, SOP, runbook, howto, frontmatter, knowledge base.
113
113
  - **ecommerce-patterns**: E-commerce: cart, checkout, payments (Stripe/Adyen), order state, inventory, promos, tax. Triggers: cart, checkout, SKU, payment, Stripe, Shopify, Medusa, Magento, coupon, refund.
114
114
  - **evaluate**: Evaluates RAG retrieval and LLM-as-judge metrics (faithfulness, relevancy, context precision). Triggers: measure RAG quality, knowledge gap, RAG eval, golden dataset.
115
115
  - **evolve**: Analyzes agent/skill failures, drafts prompt/permission fixes. Triggers: improve agent, refine skill, system prompt, optimize agent.
@@ -14349,9 +14349,9 @@ title: "Plugin Pack Conventions"
14349
14349
  category: reference
14350
14350
  service: ai-toolkit
14351
14351
  tags: [plugins, plugin-packs, conventions, manifests, hooks, policy-packs]
14352
- version: "1.1.0"
14352
+ version: "1.2.0"
14353
14353
  created: "2026-03-28"
14354
- last_updated: "2026-07-14"
14354
+ last_updated: "2026-08-18"
14355
14355
  description: "Conventions for experimental ai-toolkit plugin packs, policy packs, hook packs, and plugin-creator scaffolding across Claude Code and Codex runtimes."
14356
14356
  ---
14357
14357
 
@@ -14359,7 +14359,46 @@ description: "Conventions for experimental ai-toolkit plugin packs, policy packs
14359
14359
 
14360
14360
  ## Purpose
14361
14361
 
14362
- `ai-toolkit` includes experimental plugin packs under `app/plugins/` for Claude Code and optional global Codex layering. These internal pack manifests are distinct from the official uploadable Claude app plugin built by `ai-toolkit claude-app export`.
14362
+ `ai-toolkit` includes experimental plugin packs under `app/plugins/` for Claude Code and optional global Codex layering, and discovers external packs under `~/.softspark/ai-toolkit/plugins/` (see *Where Packs Live*). These internal pack manifests are distinct from the official uploadable Claude app plugin built by `ai-toolkit claude-app export`.
14363
+
14364
+ ## Where Packs Live
14365
+
14366
+ | Root | Owner | Survives `npm install -g @softspark/ai-toolkit` | Use for |
14367
+ |------|-------|------------------------------------------------|---------|
14368
+ | `app/plugins/<pack>/` | this repository | no — the upgrade replaces `app/` wholesale | packs shipped with the toolkit (`memory-pack`, `enterprise-pack`) |
14369
+ | `~/.softspark/ai-toolkit/plugins/<pack>/` | the user | **yes** | external / private packs maintained in their own repository |
14370
+
14371
+ Both roots are scanned by `plugin list`, `install`, `update`, `remove` and
14372
+ `status`. A **core pack wins a name collision**, so an external directory cannot
14373
+ shadow shipped behaviour. `AI_TOOLKIT_HOME` moves the user root along with the
14374
+ rest of the toolkit data dir, which is what the test suite uses.
14375
+
14376
+ Before v4.24.0 there was only the first root, so the only way to add an external
14377
+ pack was to drop it inside the npm package — where the next toolkit upgrade
14378
+ deleted it. That is what the user root exists to end.
14379
+
14380
+ ### Installing an external pack
14381
+
14382
+ The entry may be a directory **or a symlink**, which is what gives an external
14383
+ pack a versioned update path:
14384
+
14385
+ ```bash
14386
+ # Distributed as a package (private registry example)
14387
+ npm install -g @softspark/<pack> --registry=https://npm.pkg.github.com
14388
+ mkdir -p ~/.softspark/ai-toolkit/plugins
14389
+ ln -s "$(npm root -g)/@softspark/<pack>" ~/.softspark/ai-toolkit/plugins/<pack>
14390
+ ai-toolkit plugin install --editor claude <pack>
14391
+
14392
+ # Update later: the symlink target is rewritten in place, nothing to re-link
14393
+ npm install -g @softspark/<pack>@latest --registry=https://npm.pkg.github.com
14394
+ ```
14395
+
14396
+ A plain `git clone` into the same directory works too; update it with `git pull`.
14397
+
14398
+ > **Scope note:** `npm` resolves a registry per **scope**, not per package. If one
14399
+ > package in a scope lives on public npm and another on a private registry, the
14400
+ > private one needs `--registry=` on every command — a scope-wide
14401
+ > `npm config set @scope:registry` would misroute the public one.
14363
14402
 
14364
14403
  ## Pack Types
14365
14404
 
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.23.1",
2
+ "version": "4.24.0",
3
3
  "components": {
4
4
  "agents": {
5
5
  "description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "4.23.1",
3
+ "version": "4.24.0",
4
4
  "description": "AI coding toolkit: 109 skills, 44 agents, 12 developer-tool integrations, recoverable native tool-output filtering, Claude Chat/Cowork export, safety constitution, SARIF audit, and signed npm provenance.",
5
5
  "keywords": [
6
6
  "claude",
package/scripts/plugin.py CHANGED
@@ -60,6 +60,12 @@ from plugin_schema import resolve_hook_event
60
60
 
61
61
 
62
62
  PLUGINS_DIR = app_dir / "plugins"
63
+ # External packs live outside the npm package so a toolkit upgrade cannot delete
64
+ # them: `npm install -g @softspark/ai-toolkit` replaces app/ wholesale, taking any
65
+ # third-party pack dropped in there with it. TOOLKIT_DATA_DIR is user-owned and
66
+ # survives. The entry may be a directory or a symlink (e.g. to an npm-installed
67
+ # pack), which is how an external pack gets a versioned update path.
68
+ USER_PLUGINS_DIR = TOOLKIT_DATA_DIR / "plugins"
63
69
  CLAUDE_DIR = Path.home() / ".claude"
64
70
  CODEX_ROOT = Path.home()
65
71
  CODEX_HOME = Path(os.environ.get("CODEX_HOME", CODEX_ROOT / ".codex")).expanduser()
@@ -178,22 +184,40 @@ def _set_installed(state: dict, editor: str, names: list[str]) -> None:
178
184
  # Plugin discovery
179
185
  # ---------------------------------------------------------------------------
180
186
 
187
+ def plugin_roots() -> list[Path]:
188
+ """Directories scanned for packs, in precedence order.
189
+
190
+ Core packs ship inside the npm package; user packs live under
191
+ TOOLKIT_DATA_DIR and outlive every toolkit upgrade. A core pack wins a name
192
+ collision so a stray external directory cannot shadow shipped behaviour.
193
+ """
194
+ return [PLUGINS_DIR, USER_PLUGINS_DIR]
195
+
196
+
181
197
  def list_available() -> list[dict]:
182
- """List all available plugin packs."""
198
+ """List all available plugin packs, core first, then user-installed."""
183
199
  packs: list[dict] = []
184
- if not PLUGINS_DIR.is_dir():
185
- return packs
186
- for d in sorted(PLUGINS_DIR.iterdir()):
187
- manifest = d / "plugin.json"
188
- if not manifest.is_file():
200
+ seen: set[str] = set()
201
+ for root in plugin_roots():
202
+ if not root.is_dir():
189
203
  continue
190
- try:
191
- with open(manifest, encoding="utf-8") as f:
192
- data = json.load(f)
204
+ for d in sorted(root.iterdir()):
205
+ manifest = d / "plugin.json"
206
+ if not manifest.is_file():
207
+ continue
208
+ try:
209
+ with open(manifest, encoding="utf-8") as f:
210
+ data = json.load(f)
211
+ except (json.JSONDecodeError, OSError):
212
+ continue
213
+ name = data.get("name")
214
+ if not name or name in seen:
215
+ continue
216
+ seen.add(name)
193
217
  data["_dir"] = str(d)
218
+ # "_source" is taken: hook entries use it as an ownership marker.
219
+ data["_root"] = "core" if root == PLUGINS_DIR else "user"
194
220
  packs.append(data)
195
- except (json.JSONDecodeError, OSError):
196
- continue
197
221
  return packs
198
222
 
199
223
 
@@ -97,15 +97,26 @@ HOOK_REQUIRED_FIELDS = {
97
97
  }
98
98
 
99
99
  # The taxonomy. `app/skills/documentation-standards/SKILL.md` documents the same
100
- # eight and is what authors read; this set is what rejects a typo. They are one
101
- # list in two places, so a change belongs in both.
100
+ # list and is what authors read; this set is what rejects a typo. rag-mcp holds
101
+ # a third copy in `scripts/validate_kb_frontmatter.py`, which runs against
102
+ # knowledge bases this validator never sees. They are one list in three places,
103
+ # so a change belongs in all of them.
102
104
  #
103
105
  # `decisions` and `runbooks` were missing until v4.21.0 while the kb-migration
104
106
  # SOP had been telling people to create those directories for months, so a
105
107
  # correctly-filed ADR failed validation.
108
+ #
109
+ # `business` and `templates` were missing for the same reason: rag-mcp's
110
+ # `add-new-repository-to-rag-mcp.md` mandates `kb/business/` and
111
+ # `kb/templates/`. A directory an SOP mandates must be a category the validator
112
+ # accepts, or the two documents contradict each other.
113
+ #
114
+ # `history/` and `summaries/` are deliberately absent. They are lifecycle and
115
+ # runtime locations, not types: a finished plan under `history/completed/` is
116
+ # still a `planning` document and declares that.
106
117
  VALID_KB_CATEGORIES = frozenset({
107
118
  "reference", "howto", "procedures", "troubleshooting", "best-practices",
108
- "decisions", "runbooks", "planning",
119
+ "decisions", "runbooks", "planning", "business", "templates",
109
120
  })
110
121
 
111
122
  # Skill body budget, in bytes after the frontmatter block.