@softspark/ai-toolkit 4.23.0 → 4.23.1
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 +36 -0
- package/README.md +12 -44
- package/app/.claude-plugin/plugin.json +1 -1
- package/kb/procedures/maintenance-sop.md +26 -4
- package/kb/reference/architecture-overview.md +5 -2
- package/kb/reference/skill-templates.md +37 -4
- package/kb/reference/unique-features.md +24 -1
- package/llms-full.txt +92 -11
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/plugin.py +21 -4
- package/scripts/plugin_schema.py +14 -2
- package/scripts/validate.py +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v4.23.1 — a pack could ship an agent nobody installed (2026-08-18)
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`plugin install` never linked an agent the pack shipped itself.**
|
|
15
|
+
`_install_claude_agents` resolved `includes.agents` only against the toolkit's
|
|
16
|
+
own `app/agents/<name>.md`, so a self-contained pack got `WARN agent not found`
|
|
17
|
+
and the agent silently stayed uninstalled. The asymmetry was visible inside the
|
|
18
|
+
same file: skills already resolved through `_resolve_skill_source`, which falls
|
|
19
|
+
back to the pack directory, and `_remove_claude_pack_links` already unlinked
|
|
20
|
+
agent symlinks pointing into a pack — a removal path for links the installer
|
|
21
|
+
could never create.
|
|
22
|
+
|
|
23
|
+
Agents now go through `_resolve_agent_source`, mirroring skills: a core agent in
|
|
24
|
+
`app/agents` still wins, otherwise `<pack>/agents/<name>.md` is linked. Packs
|
|
25
|
+
that merely reference a core agent behave exactly as before.
|
|
26
|
+
|
|
27
|
+
- **`validate.py` reported self-contained packs as broken.** `validate_references`
|
|
28
|
+
checked `app/agents` and `app/skills` only, so a pack shipping its own assets
|
|
29
|
+
drew `References missing agent: X` and `References missing skill: Y` even though
|
|
30
|
+
the installer handled the skill correctly. It now also accepts
|
|
31
|
+
`<pack>/agents/<name>.md` and `<pack>/skills/<name>/SKILL.md`; a genuinely
|
|
32
|
+
missing reference still errors. `pack_dir` is an optional argument, so existing
|
|
33
|
+
callers are unaffected.
|
|
34
|
+
|
|
35
|
+
Found by the post-release smoke test of a downstream pack (`legal-pl-pack`),
|
|
36
|
+
not by a user report.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **Three tests covering the class.** `tests/test_plugin.bats` builds a throwaway
|
|
41
|
+
toolkit whose only pack ships its own agent and skill, then asserts the agent is
|
|
42
|
+
linked into the pack (not `app/agents`), that removal drops the link, and that
|
|
43
|
+
reference validation accepts pack-shipped assets while still failing on a
|
|
44
|
+
genuinely missing one.
|
|
45
|
+
|
|
10
46
|
## v4.23.0 — a compliance scanner that says nothing must say why (2026-08-06)
|
|
11
47
|
|
|
12
48
|
### 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.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.
|
|
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.23.
|
|
6
|
+
"version": "4.23.1",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "SoftSpark",
|
|
9
9
|
"url": "https://github.com/softspark"
|
|
@@ -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,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
|
@@ -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)
|
|
@@ -14588,10 +14613,10 @@ title: "AI Toolkit - Skill Templates"
|
|
|
14588
14613
|
category: reference
|
|
14589
14614
|
service: ai-toolkit
|
|
14590
14615
|
tags: [templates, scaffolding, create, skills]
|
|
14591
|
-
version: "1.
|
|
14616
|
+
version: "1.1.0"
|
|
14592
14617
|
created: "2026-03-29"
|
|
14593
|
-
last_updated: "2026-
|
|
14594
|
-
description: "5 skill templates for scaffolding new skills: linter, reviewer, generator, workflow, knowledge."
|
|
14618
|
+
last_updated: "2026-08-06"
|
|
14619
|
+
description: "5 skill templates for scaffolding new skills: linter, reviewer, generator, workflow, knowledge. Includes the ${CLAUDE_SKILL_DIR} rule for shipping executable scripts."
|
|
14595
14620
|
---
|
|
14596
14621
|
|
|
14597
14622
|
# Skill Templates
|
|
@@ -14632,7 +14657,40 @@ Templates are stored in `app/templates/skill/{type}/SKILL.md.template`.
|
|
|
14632
14657
|
|
|
14633
14658
|
1. Edit the generated `app/skills/{name}/SKILL.md`
|
|
14634
14659
|
2. Add `reference/` or `templates/` subdirectories if needed
|
|
14635
|
-
3.
|
|
14660
|
+
3. If the skill ships an executable, invoke it **only** through
|
|
14661
|
+
`${CLAUDE_SKILL_DIR}` — see [Shipping a script](#shipping-a-script)
|
|
14662
|
+
4. Run `ai-toolkit validate` to verify
|
|
14663
|
+
|
|
14664
|
+
## Shipping a script
|
|
14665
|
+
|
|
14666
|
+
Put executables in `app/skills/{name}/scripts/` and document them as:
|
|
14667
|
+
|
|
14668
|
+
```bash
|
|
14669
|
+
python3 ${CLAUDE_SKILL_DIR}/scripts/{name}.py [args]
|
|
14670
|
+
```
|
|
14671
|
+
|
|
14672
|
+
That is the only path that resolves once a skill is installed and symlinked into
|
|
14673
|
+
`~/.claude/skills/`. Three other spellings look correct in the repo and fail for
|
|
14674
|
+
every user:
|
|
14675
|
+
|
|
14676
|
+
| Spelling | Resolves against | Result after install |
|
|
14677
|
+
|----------|------------------|----------------------|
|
|
14678
|
+
| `scripts/foo.py` | the user's working directory | not found |
|
|
14679
|
+
| `app/skills/{name}/scripts/foo.py` | a repo the user does not have | not found |
|
|
14680
|
+
| `$(dirname "$0")/scripts/foo.py` | the shell's directory | not found |
|
|
14681
|
+
|
|
14682
|
+
Nine skills shipped with one of these before `validate.py` began failing the
|
|
14683
|
+
build on it. The check also enforces that the interpreter matches the file — a
|
|
14684
|
+
`.py` run through `bash` is an error, and bare `python` a warning, because it is
|
|
14685
|
+
missing or Python 2 on many systems.
|
|
14686
|
+
|
|
14687
|
+
The frontmatter `scripts:` list stays relative. It declares which files the skill
|
|
14688
|
+
owns; it does not run them.
|
|
14689
|
+
|
|
14690
|
+
**Give the script `--help`.** A script that treats `--help` as a positional
|
|
14691
|
+
argument answers with a traceback, and a stdin filter with no input must return
|
|
14692
|
+
an error rather than blocking forever — the post-release SOP probes every shipped
|
|
14693
|
+
script exactly this way.
|
|
14636
14694
|
|
|
14637
14695
|
---
|
|
14638
14696
|
|
|
@@ -15457,7 +15515,7 @@ category: reference
|
|
|
15457
15515
|
service: ai-toolkit
|
|
15458
15516
|
tags: [features, differentiators, constitution, hooks, security, tdd, memory]
|
|
15459
15517
|
created: "2026-04-13"
|
|
15460
|
-
last_updated: "2026-
|
|
15518
|
+
last_updated: "2026-08-06"
|
|
15461
15519
|
description: "Detailed description of ai-toolkit's unique features: constitution enforcement, hooks system, security scanning, effort budgeting, quality gates, and more."
|
|
15462
15520
|
---
|
|
15463
15521
|
|
|
@@ -15562,6 +15620,29 @@ The `Stop` hook runs after every response across 5 languages:
|
|
|
15562
15620
|
| Dart | dart analyze | dart analyze |
|
|
15563
15621
|
| Go | go vet | go vet |
|
|
15564
15622
|
|
|
15623
|
+
## 5b. Repo-Integrity Gates (`validate.py` + `npm test`)
|
|
15624
|
+
|
|
15625
|
+
Four gates that fail the build rather than documenting a rule and hoping. Each was
|
|
15626
|
+
added after the thing it checks had already shipped broken.
|
|
15627
|
+
|
|
15628
|
+
| Gate | What it fails on | Why prose was not enough |
|
|
15629
|
+
|------|------------------|--------------------------|
|
|
15630
|
+
| **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 |
|
|
15631
|
+
| **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 |
|
|
15632
|
+
| **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 |
|
|
15633
|
+
| **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 |
|
|
15634
|
+
|
|
15635
|
+
The surface check is deliberately one-directional: **removals fail, additions pass.**
|
|
15636
|
+
A surface nobody has installed has no users to break, and letting additions through
|
|
15637
|
+
silently is what stops the gate from becoming a tax people learn to bypass. The
|
|
15638
|
+
manifest is never auto-regenerated — if `generate:all` rewrote it, deleting a skill
|
|
15639
|
+
would delete its entry in the same breath and the check would prove nothing.
|
|
15640
|
+
|
|
15641
|
+
`sync_badges.py` closes the matching hole in the other direction: README count
|
|
15642
|
+
badges are derived from the tree inside `generate:all`, before `validate.py --strict`
|
|
15643
|
+
reads them, so adding a test can no longer redden the build until someone edits a
|
|
15644
|
+
number by hand.
|
|
15645
|
+
|
|
15565
15646
|
## 6. Iron Law Enforcement
|
|
15566
15647
|
|
|
15567
15648
|
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.23.
|
|
3
|
+
"version": "4.23.1",
|
|
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
|
@@ -215,6 +215,23 @@ def _resolve_skill_source(pack_dir: Path, skill: str) -> Path | None:
|
|
|
215
215
|
return None
|
|
216
216
|
|
|
217
217
|
|
|
218
|
+
def _resolve_agent_source(pack_dir: Path, agent: str) -> Path | None:
|
|
219
|
+
"""Locate the agent file a pack references.
|
|
220
|
+
|
|
221
|
+
Mirrors _resolve_skill_source: a core agent in app/agents wins, otherwise the
|
|
222
|
+
pack may ship its own under <pack>/agents/. Resolving only against app/agents
|
|
223
|
+
is what made every pack-shipped agent print "WARN agent not found" while
|
|
224
|
+
_remove_claude_pack_links already knew how to unlink one.
|
|
225
|
+
"""
|
|
226
|
+
core = app_dir / "agents" / f"{agent}.md"
|
|
227
|
+
plugin = pack_dir / "agents" / f"{agent}.md"
|
|
228
|
+
if core.is_file():
|
|
229
|
+
return core
|
|
230
|
+
if plugin.is_file():
|
|
231
|
+
return plugin
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
|
|
218
235
|
def _resolve_rule_source(pack_dir: Path, rule_name: str) -> tuple[Path, bool] | None:
|
|
219
236
|
candidates = [
|
|
220
237
|
(pack_dir / f"{rule_name}.md", False),
|
|
@@ -577,13 +594,13 @@ def _install_claude_skills(pack: dict, pack_dir: Path, installed_items: list[str
|
|
|
577
594
|
print(f" WARN skill not found: {skill}")
|
|
578
595
|
|
|
579
596
|
|
|
580
|
-
def _install_claude_agents(pack: dict, installed_items: list[str]) -> None:
|
|
597
|
+
def _install_claude_agents(pack: dict, pack_dir: Path, installed_items: list[str]) -> None:
|
|
581
598
|
for agent in pack.get("includes", {}).get("agents", []):
|
|
582
599
|
agent_file = CLAUDE_DIR / "agents" / f"{agent}.md"
|
|
583
|
-
source_file =
|
|
600
|
+
source_file = _resolve_agent_source(pack_dir, agent)
|
|
584
601
|
if agent_file.exists() or agent_file.is_symlink():
|
|
585
602
|
print(f" OK agent: {agent}")
|
|
586
|
-
elif source_file
|
|
603
|
+
elif source_file is not None:
|
|
587
604
|
agent_file.parent.mkdir(parents=True, exist_ok=True)
|
|
588
605
|
agent_file.symlink_to(source_file)
|
|
589
606
|
print(f" Linked agent: {agent}")
|
|
@@ -676,7 +693,7 @@ def install_pack_claude(name: str, pack: dict, pack_dir: Path) -> bool:
|
|
|
676
693
|
rule_specs = _resolve_pack_rules(pack, pack_dir)
|
|
677
694
|
|
|
678
695
|
_ensure_core_hook_scripts()
|
|
679
|
-
_install_claude_agents(pack, installed_items)
|
|
696
|
+
_install_claude_agents(pack, pack_dir, installed_items)
|
|
680
697
|
_install_claude_skills(pack, pack_dir, installed_items)
|
|
681
698
|
_copy_plugin_hook_scripts(name, hook_specs, installed_items)
|
|
682
699
|
_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
|
@@ -764,6 +764,7 @@ def _validate_pack_refs(tk_dir: Path, pack_path: Path, d: dict,
|
|
|
764
764
|
d,
|
|
765
765
|
agents_dir=tk_dir / "app" / "agents",
|
|
766
766
|
skills_dir=tk_dir / "app" / "skills",
|
|
767
|
+
pack_dir=pack_path,
|
|
767
768
|
)
|
|
768
769
|
for err in ref_errors:
|
|
769
770
|
vr.error(f"app/plugins/{pack_name}/plugin.json {err}")
|