@softspark/ai-toolkit 4.31.0 → 4.32.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 +121 -0
- package/README.md +21 -19
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/claude-app/hooks/hooks.json +4 -2
- package/app/claude-app/skills/ai-toolkit-rules/SKILL.md +30 -16
- package/app/hooks/quality-gate.sh +9 -2
- package/app/hooks.json +4 -2
- package/app/rules/common/git-team.md +33 -0
- package/app/rules/common/git-workflow.md +6 -20
- package/app/rules/common/performance.md +25 -1
- package/app/rules/common/testing.md +7 -1
- package/app/skills/analyze/scripts/complexity.py +3 -0
- package/app/skills/deploy/scripts/pre_deploy_check.py +13 -6
- package/app/skills/docs/scripts/doc-inventory.py +3 -0
- package/app/skills/explain/scripts/dependency-graph.py +3 -0
- package/app/skills/migrate/scripts/migration-status.py +3 -0
- package/app/skills/refactor/scripts/refactor-scan.py +3 -0
- package/benchmarks/ecosystem-doctor-snapshot.json +17 -15
- package/bin/ai-toolkit.js +2 -0
- package/kb/procedures/sop-maintenance.md +6 -3
- package/kb/reference/cli-reference.md +3 -2
- package/kb/reference/global-install-model.md +16 -3
- package/kb/reference/hooks-catalog.md +5 -3
- package/kb/reference/language-rules.md +28 -10
- package/kb/reference/plugin-pack-conventions.md +3 -3
- package/kb/reference/unique-features.md +2 -1
- package/llms-full.txt +63 -25
- package/manifest.json +2 -2
- package/package.json +5 -2
- package/scripts/benchmark_ecosystem.py +0 -1
- package/scripts/check_split.py +11 -9
- package/scripts/claude_app.py +5 -7
- package/scripts/codex_skill_adapter.py +4 -12
- package/scripts/compile_slm.py +10 -26
- package/scripts/doctor.py +322 -0
- package/scripts/evaluate_skills.py +1 -1
- package/scripts/frontmatter.py +452 -29
- package/scripts/generate_augment_rules.py +4 -4
- package/scripts/generate_cursor_mdc.py +2 -3
- package/scripts/generate_language_rules_skills.py +8 -14
- package/scripts/generate_llms_txt.py +1 -15
- package/scripts/generate_opencode_agents.py +0 -1
- package/scripts/generate_opencode_skills.py +2 -20
- package/scripts/generate_windsurf_rules.py +0 -1
- package/scripts/generator_base.py +0 -1
- package/scripts/inject_hook_cli.py +15 -2
- package/scripts/inject_mcp_cli.py +1 -2
- package/scripts/install.py +32 -1
- package/scripts/install_git_hooks.py +0 -1
- package/scripts/install_steps/ai_tools.py +65 -25
- package/scripts/install_steps/markers.py +6 -6
- package/scripts/install_steps/skill_scope.py +188 -0
- package/scripts/instruction_core.py +5 -8
- package/scripts/merge-hooks.py +13 -3
- package/scripts/pack_codebase.py +1 -1
- package/scripts/plugin.py +128 -16
- package/scripts/surface_manifest.py +6 -7
- package/scripts/validate.py +180 -11
package/llms-full.txt
CHANGED
|
@@ -7204,9 +7204,9 @@ title: "SOP: AI Toolkit Maintenance"
|
|
|
7204
7204
|
category: procedures
|
|
7205
7205
|
service: ai-toolkit
|
|
7206
7206
|
tags: [sop, maintenance, agents, skills, install]
|
|
7207
|
-
version: "3.
|
|
7207
|
+
version: "3.5.0"
|
|
7208
7208
|
created: "2026-03-23"
|
|
7209
|
-
last_updated: "2026-
|
|
7209
|
+
last_updated: "2026-09-04"
|
|
7210
7210
|
description: "Standard operating procedures for installing, maintaining, and evolving the ai-toolkit."
|
|
7211
7211
|
---
|
|
7212
7212
|
|
|
@@ -7430,7 +7430,9 @@ that runtime should receive the change.
|
|
|
7430
7430
|
greets the user with a traceback, and one that reads stdin must answer an
|
|
7431
7431
|
empty stdin with an error rather than blocking forever.
|
|
7432
7432
|
3. Update `kb/reference/skills-catalog.md` and `app/ARCHITECTURE.md`
|
|
7433
|
-
4. Run `scripts/validate.py` — it checks the invocation, the body budget,
|
|
7433
|
+
4. Run `scripts/validate.py` — it checks the invocation, the body budget, the
|
|
7434
|
+
description budget (warn over 400 characters, error over 1024; unquoted
|
|
7435
|
+
descriptions containing `: ` or ` #` are rejected, use `>-`), and
|
|
7434
7436
|
`reference/` link resolution
|
|
7435
7437
|
5. Run `python3 scripts/surface_manifest.py` before the next release to adopt the
|
|
7436
7438
|
new skill into the protected surface
|
|
@@ -7506,6 +7508,7 @@ bats tests/test_install.bats tests/test_codex.bats
|
|
|
7506
7508
|
bats tests/test_claude_app.bats tests/test_hooks_per_editor.bats
|
|
7507
7509
|
python3 scripts/claude_app.py verify
|
|
7508
7510
|
python3 scripts/validate.py --strict
|
|
7511
|
+
npm run test:py # pytest over scripts/ logic incl. the frontmatter corpus test (dev venv, see CONTRIBUTING.md)
|
|
7509
7512
|
```
|
|
7510
7513
|
|
|
7511
7514
|
## Managing Plugins
|
|
@@ -10453,7 +10456,7 @@ category: reference
|
|
|
10453
10456
|
service: ai-toolkit
|
|
10454
10457
|
tags: [cli, commands, reference, install, update, plugin, mcp, telemetry]
|
|
10455
10458
|
created: "2026-04-13"
|
|
10456
|
-
last_updated: "2026-
|
|
10459
|
+
last_updated: "2026-09-04"
|
|
10457
10460
|
description: "Complete CLI reference for all ai-toolkit commands, options, and flags."
|
|
10458
10461
|
---
|
|
10459
10462
|
|
|
@@ -10468,6 +10471,7 @@ Usage: ai-toolkit <command> [options]
|
|
|
10468
10471
|
| Command | Description |
|
|
10469
10472
|
|---------|-------------|
|
|
10470
10473
|
| `install` | First-time global install into `~/.claude/` + Cursor, Windsurf, Gemini |
|
|
10474
|
+
| `install --language-skills detected\|all` | `detected` (default): turn off `<lang>-rules`/`<lang>-patterns` skills for languages no registered project uses, via `skillOverrides` in `~/.claude/settings.json` (entries are tracked in `state.json` and restored when a project brings the language back; a user's own override is never touched); `all`: keep every language skill on. The choice persists across `install`/`update` |
|
|
10471
10475
|
| `install --local` | Claude Code configs only; add `--editors all` or `--editors cursor,aider` for other tools |
|
|
10472
10476
|
| `update` | Re-apply toolkit after `npm install -g @softspark/ai-toolkit@latest` |
|
|
10473
10477
|
| `update --local` | Re-apply + auto-detect editors from existing project files |
|
|
@@ -10475,7 +10479,7 @@ Usage: ai-toolkit <command> [options]
|
|
|
10475
10479
|
| `status` | Show installed modules and version |
|
|
10476
10480
|
| `uninstall` | Remove toolkit from `~/.claude/` |
|
|
10477
10481
|
| `validate` | Verify toolkit integrity (`--strict` for CI-grade, warnings = errors) |
|
|
10478
|
-
| `doctor` | Diagnose install health, hooks, quick-win assets,
|
|
10482
|
+
| `doctor` | Diagnose install health, hooks, quick-win assets, artifact drift, context budget (est. resident tokens of the skill/agent listings and user rules, plus skills with zero recorded use; read-only, prints the `skillOverrides` key to paste), and permission rules (`permissions.allow` wildcards on interpreters, task runners, package installs, `gh api`, `curl`, `git fetch`, destructive commands; warns only, never edits) |
|
|
10479
10483
|
| `doctor --fix` | Auto-repair broken symlinks, missing hooks, stale artifacts |
|
|
10480
10484
|
| `eject [dir]` | Export standalone config (no symlinks, no toolkit dependency) |
|
|
10481
10485
|
| `claude-app export [--output FILE] [--no-custom-rules] [--verify]` | Build an uploadable Claude Chat/Desktop/Cowork plugin ZIP and global-instructions file |
|
|
@@ -12780,9 +12784,9 @@ title: "Global Install Model"
|
|
|
12780
12784
|
category: reference
|
|
12781
12785
|
service: ai-toolkit
|
|
12782
12786
|
tags: [install, global, claude, codex, plugins, local-setup]
|
|
12783
|
-
version: "3.
|
|
12787
|
+
version: "3.5.0"
|
|
12784
12788
|
created: "2026-03-26"
|
|
12785
|
-
last_updated: "2026-
|
|
12789
|
+
last_updated: "2026-09-04"
|
|
12786
12790
|
description: "Reference description of Claude Code global install, Claude app plugin export, project-local editor setup, global Codex plugin layering, and command responsibilities in ai-toolkit."
|
|
12787
12791
|
---
|
|
12788
12792
|
|
|
@@ -12794,6 +12798,19 @@ description: "Reference description of Claude Code global install, Claude app pl
|
|
|
12794
12798
|
|
|
12795
12799
|
That means one machine-level install provides agents, skills, hooks, constitution, and rule files to every project without committing toolkit boilerplate into each repository.
|
|
12796
12800
|
|
|
12801
|
+
Language knowledge skills are the one part of the global install that is
|
|
12802
|
+
scoped by evidence (v4.32.0). Every `<lang>-rules` / `<lang>-patterns` skill is
|
|
12803
|
+
still symlinked, but `install` and `update` read the languages detected across
|
|
12804
|
+
the projects registered in `projects.json` and set `skillOverrides: off` in
|
|
12805
|
+
`~/.claude/settings.json` for the others, so their descriptions stop loading
|
|
12806
|
+
into every session. The entries the toolkit wrote are listed in `state.json`
|
|
12807
|
+
under `managed_skill_overrides`; only those are ever removed again (when a
|
|
12808
|
+
newly registered project brings the language back), a user's own override is
|
|
12809
|
+
left alone. With no registered project on disk nothing is disabled.
|
|
12810
|
+
`--language-skills all` restores everything and persists the choice
|
|
12811
|
+
(`language_skill_scope` in `state.json`); `ai-toolkit doctor` reports the
|
|
12812
|
+
resulting listing size and zero-use skills.
|
|
12813
|
+
|
|
12797
12814
|
Other editor targets are opt-in and only use documented file surfaces. Cursor
|
|
12798
12815
|
rules stay project-local because Cursor's global user rules are managed through
|
|
12799
12816
|
the settings UI, not a stable merge-safe file. Codex supports both project and
|
|
@@ -12836,7 +12853,7 @@ The `--profile` flag controls how much of each editor's native surface is activa
|
|
|
12836
12853
|
|---------|-----------|----------|
|
|
12837
12854
|
| `minimal` | Smallest editor surface. Copilot still receives its root instructions, native agents, and self-contained skills; Codex still receives instructions, agents, skills, and native safety hooks. | You want the smallest supported footprint. |
|
|
12838
12855
|
| `standard` (default) | Claude Code + editor rule files. Includes Gemini and Antigravity hooks plus native Copilot instructions, agents, portable skills, and hooks. | Day-to-day installs. Most users. |
|
|
12839
|
-
| `strict` | Everything in `standard` plus git-hook wiring for commit-time safety checks
|
|
12856
|
+
| `strict` | Everything in `standard` plus git-hook wiring for commit-time safety checks and the `git-team` common rule (feature branches, PR size, required approval, review SLA). | Tight team with zero tolerance for drift. A solo maintainer releasing straight to `main` wants `standard`, which keeps the solo-safe `git-workflow` core only. |
|
|
12840
12857
|
| `full` | Every native surface across every editor: hooks, sub-agents, custom commands, skill pointers for Cursor / Windsurf / Gemini / Augment / Antigravity. | You want maximum coverage and understand that each editor will carry generated files under its own layout. |
|
|
12841
12858
|
|
|
12842
12859
|
Codex installs materialize the full skill catalog under `.agents/skills/`
|
|
@@ -13225,9 +13242,9 @@ title: "Hooks Catalog"
|
|
|
13225
13242
|
category: reference
|
|
13226
13243
|
service: ai-toolkit
|
|
13227
13244
|
tags: [hooks, quality, safety, enforcement, settings.json]
|
|
13228
|
-
version: "1.
|
|
13245
|
+
version: "1.12.0"
|
|
13229
13246
|
created: "2026-03-27"
|
|
13230
|
-
last_updated: "2026-
|
|
13247
|
+
last_updated: "2026-09-04"
|
|
13231
13248
|
description: "Complete reference of all ai-toolkit hooks: events, scripts, installation, and runtime behavior."
|
|
13232
13249
|
---
|
|
13233
13250
|
|
|
@@ -13412,6 +13429,7 @@ Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
|
13412
13429
|
| Matcher | *(all)* |
|
|
13413
13430
|
| Script | `~/.softspark/ai-toolkit/hooks/quality-check.sh` |
|
|
13414
13431
|
| Fires | After every Claude response |
|
|
13432
|
+
| Mode | Background (`"async": true`). The linter (`npx tsc`, `ruff`) was the bulk of a 3.3 s median Stop chain; its output is advisory and reaches you when it finishes instead of holding the turn open |
|
|
13415
13433
|
|
|
13416
13434
|
**Action:** Runs language-appropriate linter:
|
|
13417
13435
|
- Python: `ruff check .`
|
|
@@ -13430,6 +13448,7 @@ Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
|
13430
13448
|
| Matcher | *(all)* |
|
|
13431
13449
|
| Script | `~/.softspark/ai-toolkit/hooks/save-session.sh` |
|
|
13432
13450
|
| Fires | After every Claude response |
|
|
13451
|
+
| Mode | Background (`"async": true`): writes a file and exits 0, nothing to wait for |
|
|
13433
13452
|
|
|
13434
13453
|
**Action:** Writes enriched session context to the per-repo session store (`~/.softspark/ai-toolkit/sessions/<repo-key>/session-context.md`) for cross-session persistence. Captures:
|
|
13435
13454
|
- Session ID and last assistant message (first 5 lines)
|
|
@@ -13447,7 +13466,7 @@ Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
|
13447
13466
|
| Script | `~/.softspark/ai-toolkit/hooks/quality-gate.sh` |
|
|
13448
13467
|
| Fires | Before Claude is allowed to finish a response |
|
|
13449
13468
|
|
|
13450
|
-
**Action:** Runs lint/typecheck. **Blocks stopping (exit 2)** if errors found, so Claude must continue and fix the issues. Missing local tooling is reported as skipped rather than blocking the session.
|
|
13469
|
+
**Action:** Runs lint/typecheck. **Blocks stopping (exit 2)** if errors found, so Claude must continue and fix the issues. Missing local tooling is reported as skipped rather than blocking the session. Ruff runs only when the project configured it (`ruff.toml`, `.ruff.toml`, or a `[tool.ruff]` table in `pyproject.toml`); a `pyproject.toml` that only carries build or pytest/mypy metadata does not trigger `ruff check .` under whatever configuration the machine resolves (v4.32.0).
|
|
13451
13470
|
|
|
13452
13471
|
Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
13453
13472
|
|
|
@@ -14045,9 +14064,9 @@ title: "Language Rules System"
|
|
|
14045
14064
|
category: reference
|
|
14046
14065
|
service: ai-toolkit
|
|
14047
14066
|
tags: [rules, languages, coding-style, testing, patterns, security]
|
|
14048
|
-
version: "2.
|
|
14067
|
+
version: "2.2.0"
|
|
14049
14068
|
created: "2026-04-07"
|
|
14050
|
-
last_updated: "2026-
|
|
14069
|
+
last_updated: "2026-09-04"
|
|
14051
14070
|
description: "Reference for the language-specific rules system: 13 per-language rule sets shipped as knowledge skills, plus common rules installed as Claude Code path-scoped project rules."
|
|
14052
14071
|
---
|
|
14053
14072
|
|
|
@@ -14097,7 +14116,7 @@ app/rules/
|
|
|
14097
14116
|
└── medplum/
|
|
14098
14117
|
```
|
|
14099
14118
|
|
|
14100
|
-
**Total: 13 per-language directories × 5 files + 1 common directory ×
|
|
14119
|
+
**Total: 13 per-language directories × 5 files + 1 common directory × 6 files (5 in every profile + `git-team` in `strict`) + 3 standalone files** (see README.md for canonical count). Per-language directories ship as `<lang>-rules` knowledge skills; the common directory is installed as Claude Code `.claude/rules/ai-toolkit-*.md` files.
|
|
14101
14120
|
|
|
14102
14121
|
## Supported Languages
|
|
14103
14122
|
|
|
@@ -14187,13 +14206,16 @@ Common rules are installed as path-scoped Claude Code rule files:
|
|
|
14187
14206
|
```
|
|
14188
14207
|
.claude/rules/
|
|
14189
14208
|
├── ai-toolkit-coding-style.md
|
|
14209
|
+
├── ai-toolkit-git-team.md # --profile strict only
|
|
14190
14210
|
├── ai-toolkit-git-workflow.md
|
|
14191
14211
|
├── ai-toolkit-performance.md
|
|
14192
14212
|
├── ai-toolkit-security.md
|
|
14193
14213
|
└── ai-toolkit-testing.md
|
|
14194
14214
|
```
|
|
14195
14215
|
|
|
14196
|
-
|
|
14216
|
+
A source rule may also carry `profiles:` (same block-list form). `git-team` declares `profiles: ["strict"]`: branching, pull-request, and review conventions for teams, kept out of `standard` so a solo maintainer who releases straight to `main` is not told to open PRs against themselves. Rerunning `install --local` with a different profile adds or removes the managed file. Only the Claude Code local install honours `profiles`; the Claude app export, `compile-slm`, and editor `lang-common` bundles still receive every common rule (see `DECISIONS.md`, 2026-09-04).
|
|
14217
|
+
|
|
14218
|
+
Each file's `paths` frontmatter is copied from the source rule in `app/rules/common/<category>.md`. A source rule without a `paths` block is always-on:
|
|
14197
14219
|
|
|
14198
14220
|
```yaml
|
|
14199
14221
|
---
|
|
@@ -14202,6 +14224,18 @@ paths:
|
|
|
14202
14224
|
---
|
|
14203
14225
|
```
|
|
14204
14226
|
|
|
14227
|
+
A source rule with a `paths` block is path-scoped, so Claude Code loads it only when a matching file is touched. As of v4.32.0 `testing` (`**/*.test.*`, `**/*.spec.*`, `**/test_*`, `**/*_test.*`, `**/tests/**`) and `performance` (source-file extensions plus `**/*.sql`) are scoped; `coding-style`, `git-workflow`, and `security` stay always-on because they carry prohibitions that must hold in every session. To change a scope, edit the source frontmatter; `validate.py` rejects inline lists and unquoted globs because the installer reads only the block form:
|
|
14228
|
+
|
|
14229
|
+
```yaml
|
|
14230
|
+
---
|
|
14231
|
+
language: common
|
|
14232
|
+
category: testing
|
|
14233
|
+
version: "1.1.0"
|
|
14234
|
+
paths:
|
|
14235
|
+
- "**/tests/**"
|
|
14236
|
+
---
|
|
14237
|
+
```
|
|
14238
|
+
|
|
14205
14239
|
The project `CLAUDE.md` receives only a compact index between a single named marker (the per-language markers from v1.x are no longer used):
|
|
14206
14240
|
|
|
14207
14241
|
```
|
|
@@ -14209,10 +14243,13 @@ The project `CLAUDE.md` receives only a compact index between a single named mar
|
|
|
14209
14243
|
# Language Rules
|
|
14210
14244
|
|
|
14211
14245
|
Common ai-toolkit rules live in `.claude/rules/ai-toolkit-*.md`
|
|
14212
|
-
with Claude Code `paths` frontmatter
|
|
14213
|
-
|
|
14246
|
+
with Claude Code `paths` frontmatter instead of expanding this
|
|
14247
|
+
CLAUDE.md. Always-on rules load in every session; path-scoped rules
|
|
14248
|
+
load only when a matching file is touched.
|
|
14214
14249
|
|
|
14215
|
-
|
|
14250
|
+
Always-on: `.claude/rules/ai-toolkit-coding-style.md`, ...
|
|
14251
|
+
|
|
14252
|
+
Path-scoped: `.claude/rules/ai-toolkit-performance.md`, ...
|
|
14216
14253
|
|
|
14217
14254
|
Language-specific rules live in `<lang>-rules` knowledge skills (e.g.
|
|
14218
14255
|
`python-rules`, `typescript-rules`) and load automatically when their
|
|
@@ -14249,7 +14286,7 @@ Language rules are tracked as modules in `manifest.json`:
|
|
|
14249
14286
|
|
|
14250
14287
|
| Module | Description |
|
|
14251
14288
|
|--------|-------------|
|
|
14252
|
-
| `rules-common` | Common coding rules (5
|
|
14289
|
+
| `rules-common` | Common coding rules (6 files: 5 in every profile, `git-team` in `strict` only), included in `standard` profile |
|
|
14253
14290
|
| `rules-typescript` | TypeScript-specific rules |
|
|
14254
14291
|
| `rules-python` | Python-specific rules |
|
|
14255
14292
|
| `rules-golang` | Go-specific rules |
|
|
@@ -14270,9 +14307,9 @@ Language rules are tracked as modules in `manifest.json`:
|
|
|
14270
14307
|
|---|---|---|---|
|
|
14271
14308
|
| Source | `app/rules/common/` | `app/rules/<lang>/` | `app/skills/<name>/SKILL.md` |
|
|
14272
14309
|
| Delivery to Claude | Path-scoped `.claude/rules/ai-toolkit-*.md` files (`--local`) + compact `CLAUDE.md` index | Generated as `<lang>-rules` knowledge skills, loaded contextually | Loaded contextually by description match |
|
|
14273
|
-
| Visibility |
|
|
14310
|
+
| Visibility | Always-on (`coding-style`, `git-workflow`, `security`) or loaded when a matching file is touched (`testing`, `performance`) | Loaded when triggers match (file extensions, framework names) | Loaded when triggers match |
|
|
14274
14311
|
| Scope | Language-agnostic standards (security, git, testing, perf, style) | Per-language coding-style, frameworks, patterns, security, testing | Domain skills (testing, debugging, RAG, etc.) |
|
|
14275
|
-
| Install | `ai-toolkit install --local` | Global install (skills directory is symlinked) | Global install |
|
|
14312
|
+
| Install | `ai-toolkit install --local` | Global install (skills directory is symlinked); skills for languages no registered project uses are turned off via `skillOverrides` (`--language-skills detected`, the default) unless `--language-skills all` was chosen | Global install |
|
|
14276
14313
|
| Other editors | Inlined into editor-specific rule files | Inlined into editor-specific rule files (still full content, not skills) | N/A |
|
|
14277
14314
|
|
|
14278
14315
|
Per-language content delivered as a knowledge skill is the same Markdown that other editors receive inlined. The split exists only for Claude, where the Agent Skills progressive-disclosure mechanism keeps the system prompt small.
|
|
@@ -15818,9 +15855,9 @@ title: "Plugin Pack Conventions"
|
|
|
15818
15855
|
category: reference
|
|
15819
15856
|
service: ai-toolkit
|
|
15820
15857
|
tags: [plugins, plugin-packs, conventions, manifests, hooks, policy-packs]
|
|
15821
|
-
version: "1.
|
|
15858
|
+
version: "1.4.0"
|
|
15822
15859
|
created: "2026-03-28"
|
|
15823
|
-
last_updated: "2026-
|
|
15860
|
+
last_updated: "2026-09-04"
|
|
15824
15861
|
description: "Conventions for experimental ai-toolkit plugin packs, policy packs, hook packs, and plugin-creator scaffolding across supported editors."
|
|
15825
15862
|
---
|
|
15826
15863
|
|
|
@@ -15982,7 +16019,7 @@ ai-toolkit plugin status --editor all # show installed packs with r
|
|
|
15982
16019
|
1. **Claude Code target**: strips plugin hook entries from `~/.claude/settings.json` and removes plugin-local rule sections from `~/.claude/CLAUDE.md`
|
|
15983
16020
|
2. **Codex target**: strips only command handlers carrying the exact `AI_TOOLKIT_HOOK_OWNER=ai-toolkit-plugin-<pack>` marker from `$CODEX_HOME/hooks.json`, removes owned `$CODEX_HOME/ai-toolkit-hooks/plugin-<pack>-*` assets, and removes the pack's marker-bounded sections from `$CODEX_HOME/AGENTS.md`
|
|
15984
16021
|
3. **Cursor/Gemini rules**: removes only exact content recorded in `rule_ownership`; foreign or user-modified files/sections are preserved with a warning
|
|
15985
|
-
4. **Claude/shared assets** (`~/.softspark/ai-toolkit/hooks/plugin-*`, `plugin-scripts/<pack>/`) are removed only when no remaining runtime still uses that pack
|
|
16022
|
+
4. **Claude/shared assets** (`~/.softspark/ai-toolkit/hooks/plugin-*`, `plugin-scripts/<pack>/`) are removed only when no remaining runtime still uses that pack, and only when the file on disk is byte-, mode- and inode-identical to what install recorded in `shared_asset_ownership`; a file the user edited is preserved and named in a `WARN preserved changed plugin asset` line. Before v4.32.1 the Claude and Codex install paths never recorded that ownership, so removal preserved every hook and script as "untracked" and each pack left 4-5 files behind (found by the post-release SOP on v4.32.0)
|
|
15986
16023
|
5. **Updates** `plugins.json` state per runtime
|
|
15987
16024
|
6. **Leaves** core agents/skills untouched (they belong to the base install)
|
|
15988
16025
|
7. **Leaves** plugin data intact (e.g. `memory.db` — use `clean` to prune)
|
|
@@ -17070,7 +17107,7 @@ category: reference
|
|
|
17070
17107
|
service: ai-toolkit
|
|
17071
17108
|
tags: [features, differentiators, constitution, hooks, security, tdd, memory]
|
|
17072
17109
|
created: "2026-04-13"
|
|
17073
|
-
last_updated: "2026-
|
|
17110
|
+
last_updated: "2026-09-04"
|
|
17074
17111
|
description: "Detailed description of ai-toolkit's unique features: constitution enforcement, hooks system, security scanning, effort budgeting, quality gates, and more."
|
|
17075
17112
|
---
|
|
17076
17113
|
|
|
@@ -17185,6 +17222,7 @@ added after the thing it checks had already shipped broken.
|
|
|
17185
17222
|
| **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 |
|
|
17186
17223
|
| **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 |
|
|
17187
17224
|
| **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 |
|
|
17225
|
+
| **Skill description budget** (`validate.py`) | a description over 1,024 characters (the Agent Skills cap; the runtime truncates past it), or an unquoted description containing `: ` or ` #` (strict YAML drops every field, `allowed-tools` included); warns over 400; applied to plugin-pack skills too | two installed skills sat at 791 and 883 characters and made up 8% of the model's skill listing on their own |
|
|
17188
17226
|
| **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 |
|
|
17189
17227
|
|
|
17190
17228
|
The surface check is deliberately one-directional: **removals fail, additions pass.**
|
package/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.
|
|
2
|
+
"version": "4.32.1",
|
|
3
3
|
"components": {
|
|
4
4
|
"agents": {
|
|
5
5
|
"description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"default": true
|
|
142
142
|
},
|
|
143
143
|
"rules-common": {
|
|
144
|
-
"description": "Common coding rules (
|
|
144
|
+
"description": "Common coding rules (6 files; git-team ships with the strict profile only)",
|
|
145
145
|
"default": true
|
|
146
146
|
},
|
|
147
147
|
"rules-typescript": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.32.1",
|
|
4
4
|
"description": "AI coding toolkit: 109 skills, 44 agents, 14 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",
|
|
@@ -75,7 +75,10 @@
|
|
|
75
75
|
"generate:all": "npm run generate:language-rules && npm run generate:claude-app && npm run generate:agents && npm run generate:opencode-agents && npm run generate:opencode-commands && npm run generate:opencode-skills && npm run generate:cursor && npm run generate:cursor-mdc && npm run generate:windsurf && npm run generate:windsurf-rules && npm run generate:copilot && npm run generate:gemini && npm run generate:gemini-agents && npm run generate:cline && npm run generate:cline-hooks && npm run generate:roo && npm run generate:roo-rules && npm run generate:aider && npm run generate:augment-rules && npm run generate:antigravity && npm run generate:llms && npm run generate:badges",
|
|
76
76
|
"generate:badges": "python3 scripts/sync_badges.py",
|
|
77
77
|
"surface": "python3 scripts/surface_manifest.py",
|
|
78
|
-
"surface:update": "python3 scripts/surface_manifest.py --update"
|
|
78
|
+
"surface:update": "python3 scripts/surface_manifest.py --update",
|
|
79
|
+
"test:py": "python3 -m pytest tests/python",
|
|
80
|
+
"lint:py": "ruff check scripts --select E,F --ignore E501,E402 --line-length 120 --extend-exclude app/skills/*/scripts,benchmarks",
|
|
81
|
+
"typecheck:py": "python3 -m mypy"
|
|
79
82
|
},
|
|
80
83
|
"files": [
|
|
81
84
|
"bin/",
|
package/scripts/check_split.py
CHANGED
|
@@ -54,6 +54,8 @@ from pathlib import Path
|
|
|
54
54
|
|
|
55
55
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
56
56
|
from _common import toolkit_dir as default_toolkit_dir
|
|
57
|
+
from frontmatter import FrontmatterError
|
|
58
|
+
from frontmatter import split_frontmatter as _split_frontmatter
|
|
57
59
|
|
|
58
60
|
# Sections that carry prescriptive process and environment-specific traps.
|
|
59
61
|
# They must load on every run, so they never move down into reference/.
|
|
@@ -75,16 +77,16 @@ NON_FILE_PREFIXES = ("#", "/", "mailto:", "tel:", "$")
|
|
|
75
77
|
# ---------------------------------------------------------------------------
|
|
76
78
|
|
|
77
79
|
def split_frontmatter(text: str) -> tuple[str, str]:
|
|
78
|
-
"""Return (frontmatter_block, body). Frontmatter is '' when absent.
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
"""Return (frontmatter_block, body). Frontmatter is '' when absent.
|
|
81
|
+
|
|
82
|
+
An unterminated block counts as body rather than being swallowed, so the
|
|
83
|
+
split gate still compares every byte of such a file.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
block, body = _split_frontmatter(text)
|
|
87
|
+
except FrontmatterError:
|
|
81
88
|
return "", text
|
|
82
|
-
|
|
83
|
-
if lines[idx].strip() == "---":
|
|
84
|
-
return "\n".join(lines[1:idx]), "\n".join(lines[idx + 1:])
|
|
85
|
-
# Unterminated frontmatter — treat the whole file as body rather than
|
|
86
|
-
# silently swallowing it.
|
|
87
|
-
return "", text
|
|
89
|
+
return block or "", body
|
|
88
90
|
|
|
89
91
|
|
|
90
92
|
def description_of(frontmatter: str) -> str:
|
package/scripts/claude_app.py
CHANGED
|
@@ -27,6 +27,9 @@ import tempfile
|
|
|
27
27
|
import zipfile
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
|
|
30
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
31
|
+
from frontmatter import split_frontmatter # noqa: E402
|
|
32
|
+
|
|
30
33
|
|
|
31
34
|
TOOLKIT_DIR = Path(__file__).resolve().parent.parent
|
|
32
35
|
APP_DIR = TOOLKIT_DIR / "app"
|
|
@@ -55,13 +58,8 @@ def _write_text(path: Path, content: str) -> None:
|
|
|
55
58
|
|
|
56
59
|
|
|
57
60
|
def _strip_frontmatter(content: str) -> str:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return content.strip()
|
|
61
|
-
for index in range(1, len(lines)):
|
|
62
|
-
if lines[index].strip() == "---":
|
|
63
|
-
return "\n".join(lines[index + 1 :]).strip()
|
|
64
|
-
return content.strip()
|
|
61
|
+
"""Body of a rule file with surrounding whitespace removed."""
|
|
62
|
+
return split_frontmatter(content)[1].strip()
|
|
65
63
|
|
|
66
64
|
|
|
67
65
|
def render_plugin_hooks() -> str:
|
|
@@ -280,8 +280,10 @@ def _build_portable_skill_text(skill_file: Path, platform: str) -> str:
|
|
|
280
280
|
if invocation_lines:
|
|
281
281
|
rendered_frontmatter += "\n" + "\n".join(invocation_lines)
|
|
282
282
|
else:
|
|
283
|
-
frontmatter
|
|
284
|
-
|
|
283
|
+
# Native skills pass their frontmatter through untouched. The old
|
|
284
|
+
# line-by-line re-render flattened nested keys (`hooks:` blocks) into
|
|
285
|
+
# top-level `key: ` lines, which is not the document the author wrote.
|
|
286
|
+
rendered_frontmatter = match.group("frontmatter").strip("\n")
|
|
285
287
|
|
|
286
288
|
return f"---\n{rendered_frontmatter}\n---\n{body.rstrip()}\n"
|
|
287
289
|
|
|
@@ -1112,16 +1114,6 @@ def _is_relative_to(path: Path, parent: Path) -> bool:
|
|
|
1112
1114
|
return False
|
|
1113
1115
|
|
|
1114
1116
|
|
|
1115
|
-
def _parse_frontmatter(frontmatter_text: str) -> list[tuple[str, str]]:
|
|
1116
|
-
entries: list[tuple[str, str]] = []
|
|
1117
|
-
for line in frontmatter_text.splitlines():
|
|
1118
|
-
if ":" not in line:
|
|
1119
|
-
continue
|
|
1120
|
-
key, value = line.split(":", 1)
|
|
1121
|
-
entries.append((key.strip(), value.strip()))
|
|
1122
|
-
return entries
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
1117
|
def _invocation_metadata_lines(frontmatter_text: str) -> list[str]:
|
|
1126
1118
|
"""Return exact canonical invocation lines, rejecting duplicate fields."""
|
|
1127
1119
|
fields = ("user-invocable", "disable-model-invocation")
|
package/scripts/compile_slm.py
CHANGED
|
@@ -35,17 +35,16 @@ import re
|
|
|
35
35
|
import sys
|
|
36
36
|
import urllib.request
|
|
37
37
|
import urllib.error
|
|
38
|
-
from dataclasses import dataclass
|
|
38
|
+
from dataclasses import dataclass
|
|
39
39
|
from pathlib import Path
|
|
40
40
|
|
|
41
41
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
42
|
+
from frontmatter import split_frontmatter
|
|
42
43
|
from _common import (
|
|
43
|
-
toolkit_dir,
|
|
44
44
|
app_dir,
|
|
45
45
|
agents_dir,
|
|
46
46
|
skills_dir,
|
|
47
47
|
frontmatter_field,
|
|
48
|
-
frontmatter_block,
|
|
49
48
|
)
|
|
50
49
|
|
|
51
50
|
# ---------------------------------------------------------------------------
|
|
@@ -209,16 +208,6 @@ def _read_file_text(path: Path) -> str:
|
|
|
209
208
|
return ""
|
|
210
209
|
|
|
211
210
|
|
|
212
|
-
def _strip_frontmatter(text: str) -> str:
|
|
213
|
-
"""Remove YAML frontmatter (--- delimited) from text."""
|
|
214
|
-
if not text.startswith("---"):
|
|
215
|
-
return text
|
|
216
|
-
end = text.find("---", 3)
|
|
217
|
-
if end == -1:
|
|
218
|
-
return text
|
|
219
|
-
return text[end + 3:].lstrip("\n")
|
|
220
|
-
|
|
221
|
-
|
|
222
211
|
def _parse_persona_skills(persona_path: Path) -> list[str]:
|
|
223
212
|
"""Extract preferred skill names from a persona file."""
|
|
224
213
|
text = _read_file_text(persona_path)
|
|
@@ -252,7 +241,7 @@ def parse_components(
|
|
|
252
241
|
constitution_path = app_dir / "constitution.md"
|
|
253
242
|
if constitution_path.is_file():
|
|
254
243
|
text = _read_file_text(constitution_path)
|
|
255
|
-
body =
|
|
244
|
+
body = split_frontmatter(text)[1].lstrip("\n")
|
|
256
245
|
components.append(Component(
|
|
257
246
|
name="Constitution",
|
|
258
247
|
type="constitution",
|
|
@@ -289,7 +278,7 @@ def parse_components(
|
|
|
289
278
|
# --- Persona definition (score=0.90) ---
|
|
290
279
|
if persona_path and persona_path.is_file():
|
|
291
280
|
text = _read_file_text(persona_path)
|
|
292
|
-
body =
|
|
281
|
+
body = split_frontmatter(text)[1].lstrip("\n")
|
|
293
282
|
components.append(Component(
|
|
294
283
|
name=f"Persona: {persona}",
|
|
295
284
|
type="persona",
|
|
@@ -310,7 +299,7 @@ def parse_components(
|
|
|
310
299
|
if common_rules_dir.is_dir():
|
|
311
300
|
for rule_file in sorted(common_rules_dir.glob("*.md")):
|
|
312
301
|
text = _read_file_text(rule_file)
|
|
313
|
-
body =
|
|
302
|
+
body = split_frontmatter(text)[1].lstrip("\n")
|
|
314
303
|
components.append(Component(
|
|
315
304
|
name=f"Rule: common/{rule_file.stem}",
|
|
316
305
|
type="rule",
|
|
@@ -332,7 +321,7 @@ def parse_components(
|
|
|
332
321
|
continue
|
|
333
322
|
for rule_file in sorted(lang_dir.glob("*.md")):
|
|
334
323
|
text = _read_file_text(rule_file)
|
|
335
|
-
body =
|
|
324
|
+
body = split_frontmatter(text)[1].lstrip("\n")
|
|
336
325
|
components.append(Component(
|
|
337
326
|
name=f"Rule: {lang}/{rule_file.stem}",
|
|
338
327
|
type="rule",
|
|
@@ -355,7 +344,6 @@ def parse_components(
|
|
|
355
344
|
skill_name = frontmatter_field(skill_file, "name")
|
|
356
345
|
if not skill_name:
|
|
357
346
|
skill_name = skill_dir.name
|
|
358
|
-
description = frontmatter_field(skill_file, "description")
|
|
359
347
|
user_invocable = frontmatter_field(skill_file, "user-invocable")
|
|
360
348
|
|
|
361
349
|
# Skip non-user-invocable knowledge skills for SLM
|
|
@@ -364,7 +352,7 @@ def parse_components(
|
|
|
364
352
|
|
|
365
353
|
# Build a compact skill summary (name + description)
|
|
366
354
|
text = _read_file_text(skill_file)
|
|
367
|
-
body =
|
|
355
|
+
body = split_frontmatter(text)[1].lstrip("\n")
|
|
368
356
|
|
|
369
357
|
# Persona relevance boost
|
|
370
358
|
p_relevance = 0.7 if skill_name in persona_skills else 0.3
|
|
@@ -391,9 +379,8 @@ def parse_components(
|
|
|
391
379
|
agent_name = frontmatter_field(agent_file, "name")
|
|
392
380
|
if not agent_name:
|
|
393
381
|
agent_name = agent_file.stem
|
|
394
|
-
description = frontmatter_field(agent_file, "description")
|
|
395
382
|
text = _read_file_text(agent_file)
|
|
396
|
-
body =
|
|
383
|
+
body = split_frontmatter(text)[1].lstrip("\n")
|
|
397
384
|
|
|
398
385
|
# Persona relevance: match if agent skills overlap persona skills
|
|
399
386
|
agent_skills_str = frontmatter_field(agent_file, "skills")
|
|
@@ -457,7 +444,7 @@ def compress_component(component: Component, level_config: dict[str, object]) ->
|
|
|
457
444
|
return
|
|
458
445
|
|
|
459
446
|
# Strip frontmatter (already done in parsing, but safety check)
|
|
460
|
-
text =
|
|
447
|
+
text = split_frontmatter(text)[1].lstrip("\n")
|
|
461
448
|
|
|
462
449
|
# Strip examples
|
|
463
450
|
strip_examples = level_config.get("strip_examples", True)
|
|
@@ -611,9 +598,6 @@ def pack_components(
|
|
|
611
598
|
|
|
612
599
|
# Constitution budget guard
|
|
613
600
|
if fixed_tokens > effective_budget:
|
|
614
|
-
constitution_tokens = sum(
|
|
615
|
-
c.tokens_compressed for c in fixed if c.type == "constitution"
|
|
616
|
-
)
|
|
617
601
|
print(
|
|
618
602
|
f"ERROR: Constitution + safety rules alone require {fixed_tokens} tokens, "
|
|
619
603
|
f"exceeding budget of {effective_budget} (budget={budget} × {BUDGET_SAFETY_MARGIN}).\n"
|
|
@@ -995,7 +979,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
995
979
|
level = args.level or str(model_config["level"])
|
|
996
980
|
|
|
997
981
|
# Parse languages
|
|
998
|
-
languages = [
|
|
982
|
+
languages = [lang.strip() for lang in args.lang.split(",") if lang.strip()] if args.lang else []
|
|
999
983
|
|
|
1000
984
|
# Parse components
|
|
1001
985
|
components = parse_components(persona=args.persona, languages=languages)
|