@softspark/ai-toolkit 4.23.0 → 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 +62 -0
- package/README.md +12 -44
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/skills/documentation-standards/SKILL.md +14 -4
- package/app/surface.json +2 -0
- package/kb/procedures/maintenance-sop.md +26 -4
- package/kb/reference/architecture-overview.md +5 -2
- package/kb/reference/plugin-pack-conventions.md +42 -3
- package/kb/reference/skill-templates.md +37 -4
- package/kb/reference/unique-features.md +24 -1
- package/llms-full.txt +135 -15
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/plugin.py +56 -15
- package/scripts/plugin_schema.py +14 -2
- package/scripts/validate.py +15 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,68 @@ 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
|
+
|
|
36
|
+
## v4.23.1 — a pack could ship an agent nobody installed (2026-08-18)
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **`plugin install` never linked an agent the pack shipped itself.**
|
|
41
|
+
`_install_claude_agents` resolved `includes.agents` only against the toolkit's
|
|
42
|
+
own `app/agents/<name>.md`, so a self-contained pack got `WARN agent not found`
|
|
43
|
+
and the agent silently stayed uninstalled. The asymmetry was visible inside the
|
|
44
|
+
same file: skills already resolved through `_resolve_skill_source`, which falls
|
|
45
|
+
back to the pack directory, and `_remove_claude_pack_links` already unlinked
|
|
46
|
+
agent symlinks pointing into a pack — a removal path for links the installer
|
|
47
|
+
could never create.
|
|
48
|
+
|
|
49
|
+
Agents now go through `_resolve_agent_source`, mirroring skills: a core agent in
|
|
50
|
+
`app/agents` still wins, otherwise `<pack>/agents/<name>.md` is linked. Packs
|
|
51
|
+
that merely reference a core agent behave exactly as before.
|
|
52
|
+
|
|
53
|
+
- **`validate.py` reported self-contained packs as broken.** `validate_references`
|
|
54
|
+
checked `app/agents` and `app/skills` only, so a pack shipping its own assets
|
|
55
|
+
drew `References missing agent: X` and `References missing skill: Y` even though
|
|
56
|
+
the installer handled the skill correctly. It now also accepts
|
|
57
|
+
`<pack>/agents/<name>.md` and `<pack>/skills/<name>/SKILL.md`; a genuinely
|
|
58
|
+
missing reference still errors. `pack_dir` is an optional argument, so existing
|
|
59
|
+
callers are unaffected.
|
|
60
|
+
|
|
61
|
+
Found by the post-release smoke test of a downstream pack (`legal-pl-pack`),
|
|
62
|
+
not by a user report.
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- **Three tests covering the class.** `tests/test_plugin.bats` builds a throwaway
|
|
67
|
+
toolkit whose only pack ships its own agent and skill, then asserts the agent is
|
|
68
|
+
linked into the pack (not `app/agents`), that removal drops the link, and that
|
|
69
|
+
reference validation accepts pack-shipped assets while still failing on a
|
|
70
|
+
genuinely missing one.
|
|
71
|
+
|
|
10
72
|
## v4.23.0 — a compliance scanner that says nothing must say why (2026-08-06)
|
|
11
73
|
|
|
12
74
|
### Fixed
|
package/README.md
CHANGED
|
@@ -6,50 +6,18 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[` expands to the shell's directory, not the skill's.
|
|
9
|
+
[](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.
|
|
53
21
|
|
|
54
22
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
55
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.
|
|
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,
|
|
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
|
|
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
|
|
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: "SOP: AI Toolkit Maintenance"
|
|
|
3
3
|
category: procedures
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [sop, maintenance, agents, skills, install]
|
|
6
|
-
version: "3.
|
|
6
|
+
version: "3.4.0"
|
|
7
7
|
created: "2026-03-23"
|
|
8
|
-
last_updated: "2026-
|
|
8
|
+
last_updated: "2026-08-06"
|
|
9
9
|
description: "Standard operating procedures for installing, maintaining, and evolving the ai-toolkit."
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -209,8 +209,30 @@ that runtime should receive the change.
|
|
|
209
209
|
user-invocable: false # knowledge skill
|
|
210
210
|
---
|
|
211
211
|
```
|
|
212
|
-
2.
|
|
213
|
-
|
|
212
|
+
2. If the skill ships an executable, put it in `app/skills/<skill-name>/scripts/`
|
|
213
|
+
and invoke it **only** as:
|
|
214
|
+
```bash
|
|
215
|
+
python3 ${CLAUDE_SKILL_DIR}/scripts/<name>.py [args]
|
|
216
|
+
```
|
|
217
|
+
`${CLAUDE_SKILL_DIR}` is the sole path that resolves once the skill is
|
|
218
|
+
installed. `scripts/<name>.py` resolves against the user's working directory,
|
|
219
|
+
`app/skills/<name>/scripts/…` against a repo they do not have, and
|
|
220
|
+
`$(dirname "$0")` against the shell's directory — all three look right in the
|
|
221
|
+
source and fail for every user. Nine skills shipped with one of them before
|
|
222
|
+
`validate.py` started failing the build on it.
|
|
223
|
+
|
|
224
|
+
Match the interpreter to the file (`python3` for `.py`, never `bash`, never
|
|
225
|
+
bare `python`). The frontmatter `scripts:` list stays relative — it declares
|
|
226
|
+
ownership rather than running anything.
|
|
227
|
+
|
|
228
|
+
Add `--help` handling. A script that treats `--help` as a positional argument
|
|
229
|
+
greets the user with a traceback, and one that reads stdin must answer an
|
|
230
|
+
empty stdin with an error rather than blocking forever.
|
|
231
|
+
3. Update `kb/reference/skills-catalog.md` and `app/ARCHITECTURE.md`
|
|
232
|
+
4. Run `scripts/validate.py` — it checks the invocation, the body budget, and
|
|
233
|
+
`reference/` link resolution
|
|
234
|
+
5. Run `python3 scripts/surface_manifest.py` before the next release to adopt the
|
|
235
|
+
new skill into the protected surface
|
|
214
236
|
|
|
215
237
|
## Adding a New Hook
|
|
216
238
|
|
|
@@ -3,7 +3,7 @@ title: "AI Toolkit - Architecture Overview"
|
|
|
3
3
|
category: reference
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [architecture, overview, design, structure]
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.7.0"
|
|
7
7
|
created: "2026-03-23"
|
|
8
8
|
last_updated: "2026-08-06"
|
|
9
9
|
description: "Architecture of ai-toolkit: directory layout, Claude app export, global install model, editor-aware MCP install, Codex translation layer, skill tiers, and integration with projects."
|
|
@@ -50,7 +50,10 @@ ai-toolkit/
|
|
|
50
50
|
inject_section_cli.py # Marker-based content injection (canonical implementation)
|
|
51
51
|
_common.py # Shared helper for generators (frontmatter, agents/skills emission)
|
|
52
52
|
merge-hooks.py # JSON merge for hooks into settings.json (inject/strip modes)
|
|
53
|
-
validate.py # Toolkit integrity check
|
|
53
|
+
validate.py # Toolkit integrity check (+ skill body budget, script-invocation gate)
|
|
54
|
+
surface_manifest.py # Public-surface snapshot vs app/surface.json; removals fail the build
|
|
55
|
+
check_split.py # Split gate: proves a SKILL.md -> reference/ refactor lost nothing
|
|
56
|
+
sync_badges.py # Derives README count badges from the tree (runs inside generate:all)
|
|
54
57
|
evaluate_skills.py # Skill quality report
|
|
55
58
|
generate_agents_md.py # Regenerates AGENTS.md
|
|
56
59
|
generate_cursor_rules.py # Generates .cursorrules (sources _common.py)
|
|
@@ -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.
|
|
6
|
+
version: "1.2.0"
|
|
7
7
|
created: "2026-03-28"
|
|
8
|
-
last_updated: "2026-
|
|
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
|
|
|
@@ -3,10 +3,10 @@ title: "AI Toolkit - Skill Templates"
|
|
|
3
3
|
category: reference
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [templates, scaffolding, create, skills]
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.1.0"
|
|
7
7
|
created: "2026-03-29"
|
|
8
|
-
last_updated: "2026-
|
|
9
|
-
description: "5 skill templates for scaffolding new skills: linter, reviewer, generator, workflow, knowledge."
|
|
8
|
+
last_updated: "2026-08-06"
|
|
9
|
+
description: "5 skill templates for scaffolding new skills: linter, reviewer, generator, workflow, knowledge. Includes the ${CLAUDE_SKILL_DIR} rule for shipping executable scripts."
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Skill Templates
|
|
@@ -47,4 +47,37 @@ Templates are stored in `app/templates/skill/{type}/SKILL.md.template`.
|
|
|
47
47
|
|
|
48
48
|
1. Edit the generated `app/skills/{name}/SKILL.md`
|
|
49
49
|
2. Add `reference/` or `templates/` subdirectories if needed
|
|
50
|
-
3.
|
|
50
|
+
3. If the skill ships an executable, invoke it **only** through
|
|
51
|
+
`${CLAUDE_SKILL_DIR}` — see [Shipping a script](#shipping-a-script)
|
|
52
|
+
4. Run `ai-toolkit validate` to verify
|
|
53
|
+
|
|
54
|
+
## Shipping a script
|
|
55
|
+
|
|
56
|
+
Put executables in `app/skills/{name}/scripts/` and document them as:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
python3 ${CLAUDE_SKILL_DIR}/scripts/{name}.py [args]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
That is the only path that resolves once a skill is installed and symlinked into
|
|
63
|
+
`~/.claude/skills/`. Three other spellings look correct in the repo and fail for
|
|
64
|
+
every user:
|
|
65
|
+
|
|
66
|
+
| Spelling | Resolves against | Result after install |
|
|
67
|
+
|----------|------------------|----------------------|
|
|
68
|
+
| `scripts/foo.py` | the user's working directory | not found |
|
|
69
|
+
| `app/skills/{name}/scripts/foo.py` | a repo the user does not have | not found |
|
|
70
|
+
| `$(dirname "$0")/scripts/foo.py` | the shell's directory | not found |
|
|
71
|
+
|
|
72
|
+
Nine skills shipped with one of these before `validate.py` began failing the
|
|
73
|
+
build on it. The check also enforces that the interpreter matches the file — a
|
|
74
|
+
`.py` run through `bash` is an error, and bare `python` a warning, because it is
|
|
75
|
+
missing or Python 2 on many systems.
|
|
76
|
+
|
|
77
|
+
The frontmatter `scripts:` list stays relative. It declares which files the skill
|
|
78
|
+
owns; it does not run them.
|
|
79
|
+
|
|
80
|
+
**Give the script `--help`.** A script that treats `--help` as a positional
|
|
81
|
+
argument answers with a traceback, and a stdin filter with no input must return
|
|
82
|
+
an error rather than blocking forever — the post-release SOP probes every shipped
|
|
83
|
+
script exactly this way.
|
|
@@ -4,7 +4,7 @@ category: reference
|
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [features, differentiators, constitution, hooks, security, tdd, memory]
|
|
6
6
|
created: "2026-04-13"
|
|
7
|
-
last_updated: "2026-
|
|
7
|
+
last_updated: "2026-08-06"
|
|
8
8
|
description: "Detailed description of ai-toolkit's unique features: constitution enforcement, hooks system, security scanning, effort budgeting, quality gates, and more."
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -109,6 +109,29 @@ The `Stop` hook runs after every response across 5 languages:
|
|
|
109
109
|
| Dart | dart analyze | dart analyze |
|
|
110
110
|
| Go | go vet | go vet |
|
|
111
111
|
|
|
112
|
+
## 5b. Repo-Integrity Gates (`validate.py` + `npm test`)
|
|
113
|
+
|
|
114
|
+
Four gates that fail the build rather than documenting a rule and hoping. Each was
|
|
115
|
+
added after the thing it checks had already shipped broken.
|
|
116
|
+
|
|
117
|
+
| Gate | What it fails on | Why prose was not enough |
|
|
118
|
+
|------|------------------|--------------------------|
|
|
119
|
+
| **Public surface** (`surface_manifest.py` + `app/surface.json`) | a skill, agent, CLI command, frontmatter field, hook, KB category or pack disappearing | `BACKWARD_COMPATIBILITY.md` listed the surfaces; renaming one still left every check green |
|
|
120
|
+
| **Script invocation** (`validate.py`) | a skill running its own script by any path other than `${CLAUDE_SKILL_DIR}`, or through the wrong interpreter | nine skills shipped invocations that resolved only in the repo, never on an installed machine |
|
|
121
|
+
| **Skill body budget** (`validate.py`) | a `SKILL.md` body over 20,000 bytes; warns over 18,000 | the body loads on every trigger match, including accidental ones; three skills sat at 24–35 KB |
|
|
122
|
+
| **Split integrity** (`check_split.py`) | a body → `reference/` refactor that lost a fenced code line, a required section, the description, or a working link | run by hand during a split; caught a heading corrupted inside a fenced example on first use |
|
|
123
|
+
|
|
124
|
+
The surface check is deliberately one-directional: **removals fail, additions pass.**
|
|
125
|
+
A surface nobody has installed has no users to break, and letting additions through
|
|
126
|
+
silently is what stops the gate from becoming a tax people learn to bypass. The
|
|
127
|
+
manifest is never auto-regenerated — if `generate:all` rewrote it, deleting a skill
|
|
128
|
+
would delete its entry in the same breath and the check would prove nothing.
|
|
129
|
+
|
|
130
|
+
`sync_badges.py` closes the matching hole in the other direction: README count
|
|
131
|
+
badges are derived from the tree inside `generate:all`, before `validate.py --strict`
|
|
132
|
+
reads them, so adding a test can no longer redden the build until someone edits a
|
|
133
|
+
number by hand.
|
|
134
|
+
|
|
112
135
|
## 6. Iron Law Enforcement
|
|
113
136
|
|
|
114
137
|
Three skills enforce non-negotiable quality gates with anti-rationalization tables:
|
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,
|
|
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.
|
|
@@ -6527,9 +6527,9 @@ title: "SOP: AI Toolkit Maintenance"
|
|
|
6527
6527
|
category: procedures
|
|
6528
6528
|
service: ai-toolkit
|
|
6529
6529
|
tags: [sop, maintenance, agents, skills, install]
|
|
6530
|
-
version: "3.
|
|
6530
|
+
version: "3.4.0"
|
|
6531
6531
|
created: "2026-03-23"
|
|
6532
|
-
last_updated: "2026-
|
|
6532
|
+
last_updated: "2026-08-06"
|
|
6533
6533
|
description: "Standard operating procedures for installing, maintaining, and evolving the ai-toolkit."
|
|
6534
6534
|
---
|
|
6535
6535
|
|
|
@@ -6733,8 +6733,30 @@ that runtime should receive the change.
|
|
|
6733
6733
|
user-invocable: false # knowledge skill
|
|
6734
6734
|
---
|
|
6735
6735
|
```
|
|
6736
|
-
2.
|
|
6737
|
-
|
|
6736
|
+
2. If the skill ships an executable, put it in `app/skills/<skill-name>/scripts/`
|
|
6737
|
+
and invoke it **only** as:
|
|
6738
|
+
```bash
|
|
6739
|
+
python3 ${CLAUDE_SKILL_DIR}/scripts/<name>.py [args]
|
|
6740
|
+
```
|
|
6741
|
+
`${CLAUDE_SKILL_DIR}` is the sole path that resolves once the skill is
|
|
6742
|
+
installed. `scripts/<name>.py` resolves against the user's working directory,
|
|
6743
|
+
`app/skills/<name>/scripts/…` against a repo they do not have, and
|
|
6744
|
+
`$(dirname "$0")` against the shell's directory — all three look right in the
|
|
6745
|
+
source and fail for every user. Nine skills shipped with one of them before
|
|
6746
|
+
`validate.py` started failing the build on it.
|
|
6747
|
+
|
|
6748
|
+
Match the interpreter to the file (`python3` for `.py`, never `bash`, never
|
|
6749
|
+
bare `python`). The frontmatter `scripts:` list stays relative — it declares
|
|
6750
|
+
ownership rather than running anything.
|
|
6751
|
+
|
|
6752
|
+
Add `--help` handling. A script that treats `--help` as a positional argument
|
|
6753
|
+
greets the user with a traceback, and one that reads stdin must answer an
|
|
6754
|
+
empty stdin with an error rather than blocking forever.
|
|
6755
|
+
3. Update `kb/reference/skills-catalog.md` and `app/ARCHITECTURE.md`
|
|
6756
|
+
4. Run `scripts/validate.py` — it checks the invocation, the body budget, and
|
|
6757
|
+
`reference/` link resolution
|
|
6758
|
+
5. Run `python3 scripts/surface_manifest.py` before the next release to adopt the
|
|
6759
|
+
new skill into the protected surface
|
|
6738
6760
|
|
|
6739
6761
|
## Adding a New Hook
|
|
6740
6762
|
|
|
@@ -8783,7 +8805,7 @@ title: "AI Toolkit - Architecture Overview"
|
|
|
8783
8805
|
category: reference
|
|
8784
8806
|
service: ai-toolkit
|
|
8785
8807
|
tags: [architecture, overview, design, structure]
|
|
8786
|
-
version: "1.
|
|
8808
|
+
version: "1.7.0"
|
|
8787
8809
|
created: "2026-03-23"
|
|
8788
8810
|
last_updated: "2026-08-06"
|
|
8789
8811
|
description: "Architecture of ai-toolkit: directory layout, Claude app export, global install model, editor-aware MCP install, Codex translation layer, skill tiers, and integration with projects."
|
|
@@ -8830,7 +8852,10 @@ ai-toolkit/
|
|
|
8830
8852
|
inject_section_cli.py # Marker-based content injection (canonical implementation)
|
|
8831
8853
|
_common.py # Shared helper for generators (frontmatter, agents/skills emission)
|
|
8832
8854
|
merge-hooks.py # JSON merge for hooks into settings.json (inject/strip modes)
|
|
8833
|
-
validate.py # Toolkit integrity check
|
|
8855
|
+
validate.py # Toolkit integrity check (+ skill body budget, script-invocation gate)
|
|
8856
|
+
surface_manifest.py # Public-surface snapshot vs app/surface.json; removals fail the build
|
|
8857
|
+
check_split.py # Split gate: proves a SKILL.md -> reference/ refactor lost nothing
|
|
8858
|
+
sync_badges.py # Derives README count badges from the tree (runs inside generate:all)
|
|
8834
8859
|
evaluate_skills.py # Skill quality report
|
|
8835
8860
|
generate_agents_md.py # Regenerates AGENTS.md
|
|
8836
8861
|
generate_cursor_rules.py # Generates .cursorrules (sources _common.py)
|
|
@@ -14324,9 +14349,9 @@ title: "Plugin Pack Conventions"
|
|
|
14324
14349
|
category: reference
|
|
14325
14350
|
service: ai-toolkit
|
|
14326
14351
|
tags: [plugins, plugin-packs, conventions, manifests, hooks, policy-packs]
|
|
14327
|
-
version: "1.
|
|
14352
|
+
version: "1.2.0"
|
|
14328
14353
|
created: "2026-03-28"
|
|
14329
|
-
last_updated: "2026-
|
|
14354
|
+
last_updated: "2026-08-18"
|
|
14330
14355
|
description: "Conventions for experimental ai-toolkit plugin packs, policy packs, hook packs, and plugin-creator scaffolding across Claude Code and Codex runtimes."
|
|
14331
14356
|
---
|
|
14332
14357
|
|
|
@@ -14334,7 +14359,46 @@ description: "Conventions for experimental ai-toolkit plugin packs, policy packs
|
|
|
14334
14359
|
|
|
14335
14360
|
## Purpose
|
|
14336
14361
|
|
|
14337
|
-
`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.
|
|
14338
14402
|
|
|
14339
14403
|
## Pack Types
|
|
14340
14404
|
|
|
@@ -14588,10 +14652,10 @@ title: "AI Toolkit - Skill Templates"
|
|
|
14588
14652
|
category: reference
|
|
14589
14653
|
service: ai-toolkit
|
|
14590
14654
|
tags: [templates, scaffolding, create, skills]
|
|
14591
|
-
version: "1.
|
|
14655
|
+
version: "1.1.0"
|
|
14592
14656
|
created: "2026-03-29"
|
|
14593
|
-
last_updated: "2026-
|
|
14594
|
-
description: "5 skill templates for scaffolding new skills: linter, reviewer, generator, workflow, knowledge."
|
|
14657
|
+
last_updated: "2026-08-06"
|
|
14658
|
+
description: "5 skill templates for scaffolding new skills: linter, reviewer, generator, workflow, knowledge. Includes the ${CLAUDE_SKILL_DIR} rule for shipping executable scripts."
|
|
14595
14659
|
---
|
|
14596
14660
|
|
|
14597
14661
|
# Skill Templates
|
|
@@ -14632,7 +14696,40 @@ Templates are stored in `app/templates/skill/{type}/SKILL.md.template`.
|
|
|
14632
14696
|
|
|
14633
14697
|
1. Edit the generated `app/skills/{name}/SKILL.md`
|
|
14634
14698
|
2. Add `reference/` or `templates/` subdirectories if needed
|
|
14635
|
-
3.
|
|
14699
|
+
3. If the skill ships an executable, invoke it **only** through
|
|
14700
|
+
`${CLAUDE_SKILL_DIR}` — see [Shipping a script](#shipping-a-script)
|
|
14701
|
+
4. Run `ai-toolkit validate` to verify
|
|
14702
|
+
|
|
14703
|
+
## Shipping a script
|
|
14704
|
+
|
|
14705
|
+
Put executables in `app/skills/{name}/scripts/` and document them as:
|
|
14706
|
+
|
|
14707
|
+
```bash
|
|
14708
|
+
python3 ${CLAUDE_SKILL_DIR}/scripts/{name}.py [args]
|
|
14709
|
+
```
|
|
14710
|
+
|
|
14711
|
+
That is the only path that resolves once a skill is installed and symlinked into
|
|
14712
|
+
`~/.claude/skills/`. Three other spellings look correct in the repo and fail for
|
|
14713
|
+
every user:
|
|
14714
|
+
|
|
14715
|
+
| Spelling | Resolves against | Result after install |
|
|
14716
|
+
|----------|------------------|----------------------|
|
|
14717
|
+
| `scripts/foo.py` | the user's working directory | not found |
|
|
14718
|
+
| `app/skills/{name}/scripts/foo.py` | a repo the user does not have | not found |
|
|
14719
|
+
| `$(dirname "$0")/scripts/foo.py` | the shell's directory | not found |
|
|
14720
|
+
|
|
14721
|
+
Nine skills shipped with one of these before `validate.py` began failing the
|
|
14722
|
+
build on it. The check also enforces that the interpreter matches the file — a
|
|
14723
|
+
`.py` run through `bash` is an error, and bare `python` a warning, because it is
|
|
14724
|
+
missing or Python 2 on many systems.
|
|
14725
|
+
|
|
14726
|
+
The frontmatter `scripts:` list stays relative. It declares which files the skill
|
|
14727
|
+
owns; it does not run them.
|
|
14728
|
+
|
|
14729
|
+
**Give the script `--help`.** A script that treats `--help` as a positional
|
|
14730
|
+
argument answers with a traceback, and a stdin filter with no input must return
|
|
14731
|
+
an error rather than blocking forever — the post-release SOP probes every shipped
|
|
14732
|
+
script exactly this way.
|
|
14636
14733
|
|
|
14637
14734
|
---
|
|
14638
14735
|
|
|
@@ -15457,7 +15554,7 @@ category: reference
|
|
|
15457
15554
|
service: ai-toolkit
|
|
15458
15555
|
tags: [features, differentiators, constitution, hooks, security, tdd, memory]
|
|
15459
15556
|
created: "2026-04-13"
|
|
15460
|
-
last_updated: "2026-
|
|
15557
|
+
last_updated: "2026-08-06"
|
|
15461
15558
|
description: "Detailed description of ai-toolkit's unique features: constitution enforcement, hooks system, security scanning, effort budgeting, quality gates, and more."
|
|
15462
15559
|
---
|
|
15463
15560
|
|
|
@@ -15562,6 +15659,29 @@ The `Stop` hook runs after every response across 5 languages:
|
|
|
15562
15659
|
| Dart | dart analyze | dart analyze |
|
|
15563
15660
|
| Go | go vet | go vet |
|
|
15564
15661
|
|
|
15662
|
+
## 5b. Repo-Integrity Gates (`validate.py` + `npm test`)
|
|
15663
|
+
|
|
15664
|
+
Four gates that fail the build rather than documenting a rule and hoping. Each was
|
|
15665
|
+
added after the thing it checks had already shipped broken.
|
|
15666
|
+
|
|
15667
|
+
| Gate | What it fails on | Why prose was not enough |
|
|
15668
|
+
|------|------------------|--------------------------|
|
|
15669
|
+
| **Public surface** (`surface_manifest.py` + `app/surface.json`) | a skill, agent, CLI command, frontmatter field, hook, KB category or pack disappearing | `BACKWARD_COMPATIBILITY.md` listed the surfaces; renaming one still left every check green |
|
|
15670
|
+
| **Script invocation** (`validate.py`) | a skill running its own script by any path other than `${CLAUDE_SKILL_DIR}`, or through the wrong interpreter | nine skills shipped invocations that resolved only in the repo, never on an installed machine |
|
|
15671
|
+
| **Skill body budget** (`validate.py`) | a `SKILL.md` body over 20,000 bytes; warns over 18,000 | the body loads on every trigger match, including accidental ones; three skills sat at 24–35 KB |
|
|
15672
|
+
| **Split integrity** (`check_split.py`) | a body → `reference/` refactor that lost a fenced code line, a required section, the description, or a working link | run by hand during a split; caught a heading corrupted inside a fenced example on first use |
|
|
15673
|
+
|
|
15674
|
+
The surface check is deliberately one-directional: **removals fail, additions pass.**
|
|
15675
|
+
A surface nobody has installed has no users to break, and letting additions through
|
|
15676
|
+
silently is what stops the gate from becoming a tax people learn to bypass. The
|
|
15677
|
+
manifest is never auto-regenerated — if `generate:all` rewrote it, deleting a skill
|
|
15678
|
+
would delete its entry in the same breath and the check would prove nothing.
|
|
15679
|
+
|
|
15680
|
+
`sync_badges.py` closes the matching hole in the other direction: README count
|
|
15681
|
+
badges are derived from the tree inside `generate:all`, before `validate.py --strict`
|
|
15682
|
+
reads them, so adding a test can no longer redden the build until someone edits a
|
|
15683
|
+
number by hand.
|
|
15684
|
+
|
|
15565
15685
|
## 6. Iron Law Enforcement
|
|
15566
15686
|
|
|
15567
15687
|
Three skills enforce non-negotiable quality gates with anti-rationalization tables:
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "4.
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
|
@@ -215,6 +239,23 @@ def _resolve_skill_source(pack_dir: Path, skill: str) -> Path | None:
|
|
|
215
239
|
return None
|
|
216
240
|
|
|
217
241
|
|
|
242
|
+
def _resolve_agent_source(pack_dir: Path, agent: str) -> Path | None:
|
|
243
|
+
"""Locate the agent file a pack references.
|
|
244
|
+
|
|
245
|
+
Mirrors _resolve_skill_source: a core agent in app/agents wins, otherwise the
|
|
246
|
+
pack may ship its own under <pack>/agents/. Resolving only against app/agents
|
|
247
|
+
is what made every pack-shipped agent print "WARN agent not found" while
|
|
248
|
+
_remove_claude_pack_links already knew how to unlink one.
|
|
249
|
+
"""
|
|
250
|
+
core = app_dir / "agents" / f"{agent}.md"
|
|
251
|
+
plugin = pack_dir / "agents" / f"{agent}.md"
|
|
252
|
+
if core.is_file():
|
|
253
|
+
return core
|
|
254
|
+
if plugin.is_file():
|
|
255
|
+
return plugin
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
|
|
218
259
|
def _resolve_rule_source(pack_dir: Path, rule_name: str) -> tuple[Path, bool] | None:
|
|
219
260
|
candidates = [
|
|
220
261
|
(pack_dir / f"{rule_name}.md", False),
|
|
@@ -577,13 +618,13 @@ def _install_claude_skills(pack: dict, pack_dir: Path, installed_items: list[str
|
|
|
577
618
|
print(f" WARN skill not found: {skill}")
|
|
578
619
|
|
|
579
620
|
|
|
580
|
-
def _install_claude_agents(pack: dict, installed_items: list[str]) -> None:
|
|
621
|
+
def _install_claude_agents(pack: dict, pack_dir: Path, installed_items: list[str]) -> None:
|
|
581
622
|
for agent in pack.get("includes", {}).get("agents", []):
|
|
582
623
|
agent_file = CLAUDE_DIR / "agents" / f"{agent}.md"
|
|
583
|
-
source_file =
|
|
624
|
+
source_file = _resolve_agent_source(pack_dir, agent)
|
|
584
625
|
if agent_file.exists() or agent_file.is_symlink():
|
|
585
626
|
print(f" OK agent: {agent}")
|
|
586
|
-
elif source_file
|
|
627
|
+
elif source_file is not None:
|
|
587
628
|
agent_file.parent.mkdir(parents=True, exist_ok=True)
|
|
588
629
|
agent_file.symlink_to(source_file)
|
|
589
630
|
print(f" Linked agent: {agent}")
|
|
@@ -676,7 +717,7 @@ def install_pack_claude(name: str, pack: dict, pack_dir: Path) -> bool:
|
|
|
676
717
|
rule_specs = _resolve_pack_rules(pack, pack_dir)
|
|
677
718
|
|
|
678
719
|
_ensure_core_hook_scripts()
|
|
679
|
-
_install_claude_agents(pack, installed_items)
|
|
720
|
+
_install_claude_agents(pack, pack_dir, installed_items)
|
|
680
721
|
_install_claude_skills(pack, pack_dir, installed_items)
|
|
681
722
|
_copy_plugin_hook_scripts(name, hook_specs, installed_items)
|
|
682
723
|
_copy_plugin_scripts(name, pack_dir, installed_items)
|
package/scripts/plugin_schema.py
CHANGED
|
@@ -139,20 +139,32 @@ def validate_references(
|
|
|
139
139
|
data: dict,
|
|
140
140
|
agents_dir: Path,
|
|
141
141
|
skills_dir: Path,
|
|
142
|
+
pack_dir: Path | None = None,
|
|
142
143
|
) -> list[str]:
|
|
143
144
|
"""Validate that referenced agents and skills exist.
|
|
144
145
|
|
|
146
|
+
A pack may either reference a core asset (app/agents, app/skills) or ship its
|
|
147
|
+
own under <pack>/agents and <pack>/skills. Checking only the core directories
|
|
148
|
+
reported every self-contained pack as broken even though the installer links
|
|
149
|
+
its skills fine. pack_dir stays optional so existing callers keep working.
|
|
150
|
+
|
|
145
151
|
Returns a list of error messages.
|
|
146
152
|
"""
|
|
147
153
|
errors: list[str] = []
|
|
148
154
|
includes = data.get("includes", {})
|
|
149
155
|
|
|
150
156
|
for agent in includes.get("agents", []):
|
|
151
|
-
|
|
157
|
+
candidates = [agents_dir / f"{agent}.md"]
|
|
158
|
+
if pack_dir is not None:
|
|
159
|
+
candidates.append(pack_dir / "agents" / f"{agent}.md")
|
|
160
|
+
if not any(c.is_file() for c in candidates):
|
|
152
161
|
errors.append(f"References missing agent: {agent}")
|
|
153
162
|
|
|
154
163
|
for skill in includes.get("skills", []):
|
|
155
|
-
|
|
164
|
+
candidates = [skills_dir / skill / "SKILL.md"]
|
|
165
|
+
if pack_dir is not None:
|
|
166
|
+
candidates.append(pack_dir / "skills" / skill / "SKILL.md")
|
|
167
|
+
if not any(c.is_file() for c in candidates):
|
|
156
168
|
errors.append(f"References missing skill: {skill}")
|
|
157
169
|
|
|
158
170
|
return errors
|
package/scripts/validate.py
CHANGED
|
@@ -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
|
-
#
|
|
101
|
-
#
|
|
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.
|
|
@@ -764,6 +775,7 @@ def _validate_pack_refs(tk_dir: Path, pack_path: Path, d: dict,
|
|
|
764
775
|
d,
|
|
765
776
|
agents_dir=tk_dir / "app" / "agents",
|
|
766
777
|
skills_dir=tk_dir / "app" / "skills",
|
|
778
|
+
pack_dir=pack_path,
|
|
767
779
|
)
|
|
768
780
|
for err in ref_errors:
|
|
769
781
|
vr.error(f"app/plugins/{pack_name}/plugin.json {err}")
|