@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 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: 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-1516%20passing-success)](tests/)
10
-
11
- ## What's New in v4.23.0
12
-
13
- **v4.23.0** — `hipaa-validate` scanned a project without a manifest silently
14
- clean: its language-tagged patterns never ran, and the report said `HIGH: 0`.
15
- Zero reads as compliant. Detection now falls back to file extensions, the summary
16
- says how the language was decided, and an undecidable run warns that a zero means
17
- unscanned. Manifest-declaring projects are unaffected. Found by the post-release
18
- SOP, not by a user.
19
-
20
- ## What's New in v4.22.1
21
-
22
- **v4.22.1** — nine skills, not four, could not find their own scripts. v4.22.0
23
- grepped for one spelling of the bug and missed five more, a `.py` file run
24
- through `bash`, and a script that crashed on `--help`. `validate.py` now fails
25
- the build on the entire class, and the post-release SOP runs every skill's
26
- documented command from the installed copy — which is how these were found.
27
-
28
- ## What's New in v4.22.0
29
-
30
- v4.22.0 turns compatibility promises into tests, and makes two validator skills
31
- actually run the scanners they ship.
32
-
33
- - **The public surface is checked, not documented.** `app/surface.json` pins 275
34
- entries — skills, agents, frontmatter fields, CLI commands, hook scripts and
35
- events, KB categories, plugin packs. Removing any of them fails `npm test`.
36
- Adding is free: a surface nobody has installed has no users to break.
37
- `BACKWARD_COMPATIBILITY.md` says which surfaces are load-bearing and how to
38
- deprecate one properly.
39
- - **`a11y-validate` and `seo-validate` shipped scanners that nothing invoked.**
40
- 1,196 lines of working Python sat on disk while the model was told to grep the
41
- pattern tables by hand. Both now run their script for a deterministic baseline,
42
- then take an explicit manual pass — with a measured coverage table saying which
43
- criteria the script handles and which are yours.
44
- - **`/brainstorm`** — the first planning skill allowed to conclude "do not build
45
- this". Every other one produces an artifact; a pipeline that cannot say no is a
46
- yes-machine.
47
- - **Skill bodies have a budget.** Error above 20,000 bytes, warn above 18,000,
48
- headroom printed every run. Three validator skills were split into
49
- `reference/`, taking 47 KB off the hot path, with `scripts/check_split.py`
50
- proving nothing was lost.
51
- - **Four skills invoked their scripts through a path that never resolved.**
52
- `$(dirname "$0")` expands to the shell's directory, not the skill's.
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.
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.0",
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.3.0"
6
+ version: "3.4.0"
7
7
  created: "2026-03-23"
8
- last_updated: "2026-07-14"
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. Update `kb/reference/skills-catalog.md` and `app/ARCHITECTURE.md`
213
- 3. Run `scripts/validate.py`
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.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.0.0"
6
+ version: "1.1.0"
7
7
  created: "2026-03-29"
8
- last_updated: "2026-04-01"
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. Run `ai-toolkit validate` to verify
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-07-10"
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.3.0"
6530
+ version: "3.4.0"
6531
6531
  created: "2026-03-23"
6532
- last_updated: "2026-07-14"
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. Update `kb/reference/skills-catalog.md` and `app/ARCHITECTURE.md`
6737
- 3. Run `scripts/validate.py`
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.6.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.0.0"
14616
+ version: "1.1.0"
14592
14617
  created: "2026-03-29"
14593
- last_updated: "2026-04-01"
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. Run `ai-toolkit validate` to verify
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-07-10"
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.23.0",
2
+ "version": "4.23.1",
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.0",
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 = app_dir / "agents" / f"{agent}.md"
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.is_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)
@@ -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
- if not (agents_dir / f"{agent}.md").is_file():
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
- if not (skills_dir / skill / "SKILL.md").is_file():
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
@@ -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}")