@softspark/ai-toolkit 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,39 @@ Versioning follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## v3.3.0 — External-source visibility & skill-listing budget default (2026-05-06)
11
+
12
+ Minor release. Two coordinated install-time additions: `ai-toolkit status` now surfaces every registered external rule and hook, and fresh installs / updates set a sane `skillListingBudgetFraction` so all 112 skill descriptions stay loadable in Claude Code.
13
+
14
+ ### Added
15
+
16
+ - **`ai-toolkit status` shows external sources** — `scripts/install_steps/install_state.py` reads `~/.softspark/ai-toolkit/rules/sources.json` and `~/.softspark/ai-toolkit/hooks/external/sources.json` and prints an `External sources:` section listing each registered rule/hook with its source URL and last-fetched timestamp. The section is omitted entirely when no external sources are registered. Closes the long-standing gap where `add-rule`/`inject-hook` had no read-side counterpart.
17
+ - **`skillListingBudgetFraction` default in `~/.claude/settings.json`** — new `_install_skill_listing_budget()` step in `scripts/install_steps/hooks.py` writes `"skillListingBudgetFraction": 0.02` on install/update, but only when the key is absent. Existing user values are never overwritten. Prevents Claude Code's default 1% budget from truncating ~60 of the toolkit's 112 skill descriptions.
18
+
19
+ ### Changed
20
+
21
+ - **`ai-toolkit status` help text** — updated in `bin/ai-toolkit.js` to mention external rules/hooks alongside modules, version, and profile.
22
+
23
+
24
+
25
+ Patch release. Lint-only fix in the default status line hook — no behavior change.
26
+
27
+ ### Fixed
28
+
29
+ - **`app/hooks/ai-toolkit-statusline.sh` shellcheck warnings** — removed the unused `C_GRAY` color variable (SC2034) and replaced the deprecated `[ p -o q ]` form with `{ [ p ] || [ q ]; }` (SC2166). CI shellcheck gate at `--severity=warning` now passes clean across `app/hooks/*.sh`.
30
+
31
+ ---
32
+
33
+ ## v3.2.1 — Statusline shellcheck cleanup (2026-05-04)
34
+
35
+ Patch release. Lint-only fix in the default status line hook — no behavior change.
36
+
37
+ ### Fixed
38
+
39
+ - **`app/hooks/ai-toolkit-statusline.sh` shellcheck warnings** — removed the unused `C_GRAY` color variable (SC2034) and replaced the deprecated `[ p -o q ]` form with `{ [ p ] || [ q ]; }` (SC2166). CI shellcheck gate at `--severity=warning` now passes clean across `app/hooks/*.sh`.
40
+
41
+ ---
42
+
10
43
  ## v3.2.0 — Output discipline & token-aware status line (2026-05-04)
11
44
 
12
45
  Minor release. Two coordinated additions: per-response output discipline and a real-token status line installed by default.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
  [![Skills](https://img.shields.io/badge/skills-112-brightgreen)](app/skills/)
8
8
  [![Agents](https://img.shields.io/badge/agents-44-blue)](app/agents/)
9
- [![Tests](https://img.shields.io/badge/tests-1038%20passing-success)](tests/)
9
+ [![Tests](https://img.shields.io/badge/tests-1042%20passing-success)](tests/)
10
10
 
11
11
  <p align="center">
12
12
  <img src="assets/ai-toolkit-readme-hero.png" alt="ai-toolkit 3 README hero showing one toolkit for 12 AI editors" width="900">
@@ -14,14 +14,13 @@
14
14
 
15
15
  ---
16
16
 
17
- ## What's New in v3.2.0
17
+ ## What's New in v3.3.0
18
18
 
19
- Minor release adding response-length governance and a token-aware status line installed by default.
19
+ Minor release. Two install-time additions: visibility for external rules/hooks and a sane default for Claude Code's skill listing budget.
20
20
 
21
- - **Output modes for `brand-voice`**`concise` (≤60% tokens) and `strict` (≤40%) modes governing conversational responses, with rule files in `app/skills/brand-voice/modes/` and a `measure.py` evaluator that runs against fixtures with load-bearing-fact assertions.
22
- - **Default comprehensive status line** — `ai-toolkit install` now wires `~/.claude/settings.json` to a single line showing cwd, git, a 10-cell context-window progress bar (green <70% / orange 70–89% / red ≥90%), `↑input ↓output` token arrows, effort level, and model. All data read directly from Claude Code's statusLine stdin — no JSONL parsing, ~50 ms cold start. User-customized statusLine entries are preserved untouched. Cost segment opt-in via `AI_TOOLKIT_STATUSLINE_SHOW_COST=1`.
23
- - **Real token telemetry** — `scripts/session_token_stats.py` parses Claude Code session JSONL for `/briefing --tokens` reporting and baseline capture. Stdlib-only.
24
- - **Opt-outs via env vars** — `AI_TOOLKIT_STATUSLINE_DISABLE`, `_NO_TOKENS`, `_NO_GIT`, `_NO_EFFORT`, `_NO_COLOR`, `_SHOW_COST` (opt-in), `_DUMP` (debug).
21
+ - **`ai-toolkit status` lists external sources** every rule and hook registered via `add-rule` / `inject-hook` is now visible in `status` output with its source URL and last-fetched timestamp. No more grepping `~/.softspark/ai-toolkit/*/sources.json` by hand.
22
+ - **`skillListingBudgetFraction = 0.02` set by default** — install/update writes the key into `~/.claude/settings.json` only when missing, so all 112 skill descriptions stay loadable in Claude Code without truncation. User-set values are preserved.
23
+ - **Updated `status` help text** — `bin/ai-toolkit.js` now mentions external rules/hooks alongside modules, version, and profile.
25
24
 
26
25
  See [CHANGELOG.md](CHANGELOG.md) for full history.
27
26
 
@@ -154,7 +153,7 @@ ai-toolkit/
154
153
  │ └── ARCHITECTURE.md # Full system design
155
154
  ├── kb/ # Reference docs, procedures, plans
156
155
  ├── scripts/ # Validation, install, evaluation scripts
157
- ├── tests/ # Bats test suite (1038 tests)
156
+ ├── tests/ # Bats test suite (1042 tests)
158
157
  └── CHANGELOG.md
159
158
  ```
160
159
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ai-toolkit",
3
3
  "description": "Professional-grade Claude Code toolkit with persona presets, skill security auditor, expanded lifecycle hooks, experimental opt-in plugin packs, benchmark harvesting, and multi-tool support.",
4
- "version": "3.2.0",
4
+ "version": "3.3.0",
5
5
  "author": {
6
6
  "name": "SoftSpark",
7
7
  "url": "https://github.com/softspark"
@@ -39,7 +39,7 @@ set -u
39
39
  # renders the statusline. So always emit colors unless explicitly disabled.
40
40
  if [ "${AI_TOOLKIT_STATUSLINE_NO_COLOR:-0}" = "1" ]; then
41
41
  C_RESET="" C_GREEN="" C_BOLD_GREEN="" C_CYAN="" C_BLUE="" C_RED=""
42
- C_YELLOW="" C_MAGENTA="" C_DIM="" C_GRAY="" C_BOLD_YELLOW="" C_ORANGE=""
42
+ C_YELLOW="" C_MAGENTA="" C_DIM="" C_BOLD_YELLOW="" C_ORANGE=""
43
43
  else
44
44
  C_RESET=$'\033[0m'
45
45
  C_GREEN=$'\033[0;32m'
@@ -51,7 +51,6 @@ else
51
51
  C_BOLD_YELLOW=$'\033[1;33m'
52
52
  C_MAGENTA=$'\033[0;35m'
53
53
  C_DIM=$'\033[2m'
54
- C_GRAY=$'\033[0;37m'
55
54
  C_ORANGE=$'\033[38;5;208m'
56
55
  fi
57
56
 
@@ -160,8 +159,8 @@ fi
160
159
  # ↑ green = upload (input tokens sent to model)
161
160
  # ↓ red = download (output tokens received from model)
162
161
  SEG_TOKENS=""
163
- if [ "${AI_TOOLKIT_STATUSLINE_NO_TOKENS:-0}" != "1" ] && \
164
- [ -n "$IN_TOK" ] && [ "$IN_TOK" != "0" -o "$OUT_TOK" != "0" ]; then
162
+ if [ "${AI_TOOLKIT_STATUSLINE_NO_TOKENS:-0}" != "1" ] && [ -n "$IN_TOK" ] && \
163
+ { [ "$IN_TOK" != "0" ] || [ "$OUT_TOK" != "0" ]; }; then
165
164
  SEG_TOKENS=" ${C_BOLD_GREEN}\xe2\x86\x91${IN_TOK}${C_RESET} ${C_RED}\xe2\x86\x93${OUT_TOK}${C_RESET}"
166
165
  if [ "${AI_TOOLKIT_STATUSLINE_SHOW_COST:-0}" = "1" ] && [ -n "$COST" ]; then
167
166
  SEG_TOKENS+=" ${C_BOLD_GREEN}\$${COST}${C_RESET}"
@@ -1,5 +1,5 @@
1
1
  {
2
- "last_run": "2026-05-04T08:36:05Z",
2
+ "last_run": "2026-05-06T08:17:20Z",
3
3
  "schema_version": 1,
4
4
  "tools": {
5
5
  "aider": {
@@ -24,13 +24,12 @@
24
24
  }
25
25
  },
26
26
  "augment": {
27
- "docs_hash": "f6b4c7fd64936879",
27
+ "docs_hash": "ac138d99235e1f3e",
28
28
  "headings": [
29
29
  "Agent",
30
30
  "Code Completions",
31
31
  "Documentation Index",
32
32
  "Introduction",
33
- "Next Edit",
34
33
  "\u200bAuggie CLI",
35
34
  "\u200bGet started in minutes",
36
35
  "\u200bJetBrains IDEs",
@@ -43,7 +42,7 @@
43
42
  "Agent mode": false,
44
43
  "Auggie CLI": true,
45
44
  "MCP": true,
46
- "Next Edit": true,
45
+ "Next Edit": false,
47
46
  "PostToolUse": false,
48
47
  "PreToolUse": false,
49
48
  "SKILL.md": false,
@@ -58,7 +57,7 @@
58
57
  }
59
58
  },
60
59
  "claude-code": {
61
- "docs_hash": "7d2930e5f97e4126",
60
+ "docs_hash": "789113e1bc4cd34c",
62
61
  "headings": [
63
62
  "Claude Code overview",
64
63
  "Documentation Index",
@@ -104,10 +103,10 @@
104
103
  "slash command": true,
105
104
  "sub-agent": true
106
105
  },
107
- "version": "2.1.126 (Claude Code)"
106
+ "version": "2.1.131 (Claude Code)"
108
107
  },
109
108
  "cline": {
110
- "docs_hash": "eec0384d180243aa",
109
+ "docs_hash": "b4009dc8e6d40af9",
111
110
  "headings": [
112
111
  "Cline CLI",
113
112
  "Cline Documentation",
@@ -138,7 +137,7 @@
138
137
  }
139
138
  },
140
139
  "codex-cli": {
141
- "docs_hash": "5a4b2d4eeee1d675",
140
+ "docs_hash": "c6b51936294d38e7",
142
141
  "headings": [
143
142
  "About",
144
143
  "Contributing",
@@ -157,7 +156,7 @@
157
156
  "Packages 0",
158
157
  "Provide feedback",
159
158
  "Quickstart",
160
- "Releases 757",
159
+ "Releases 765",
161
160
  "Repository files navigation",
162
161
  "Resources",
163
162
  "Saved searches",
@@ -187,10 +186,10 @@
187
186
  "mcp_servers": false,
188
187
  "sandbox": true
189
188
  },
190
- "version": "codex-cli 0.125.0"
189
+ "version": "codex-cli 0.128.0"
191
190
  },
192
191
  "cursor": {
193
- "docs_hash": "4c3678f1353e012f",
192
+ "docs_hash": "8ed1f5cf7441b6c0",
194
193
  "headings": [],
195
194
  "markers": {
196
195
  ".cursor/rules": false,
@@ -206,7 +205,7 @@
206
205
  }
207
206
  },
208
207
  "gemini-cli": {
209
- "docs_hash": "20c254199b42f18c",
208
+ "docs_hash": "21a2f14121ff27fa",
210
209
  "headings": [
211
210
  "Breadcrumbs",
212
211
  "Directory actions",
@@ -245,7 +244,7 @@
245
244
  }
246
245
  },
247
246
  "github-copilot": {
248
- "docs_hash": "6467b14babd06b26",
247
+ "docs_hash": "99a5ab7bd5f7556f",
249
248
  "headings": [
250
249
  "About Copilot auto model selection",
251
250
  "About Copilot integrations",
@@ -293,7 +292,7 @@
293
292
  }
294
293
  },
295
294
  "opencode": {
296
- "docs_hash": "c8c233106f166444",
295
+ "docs_hash": "fe845a4bffb6371b",
297
296
  "headings": [
298
297
  "Add features",
299
298
  "Ask questions",
@@ -354,7 +353,7 @@
354
353
  }
355
354
  },
356
355
  "windsurf": {
357
- "docs_hash": "167ee1fc000f03d9",
356
+ "docs_hash": "9b2879646f041aea",
358
357
  "headings": [
359
358
  "Advanced",
360
359
  "App Deploys",
package/bin/ai-toolkit.js CHANGED
@@ -62,7 +62,7 @@ const SCRIPT_COMMANDS = {
62
62
  const COMMANDS = {
63
63
  install: 'First-time global install into ~/.claude/ (use --local for project-local configs only)',
64
64
  update: 'Re-apply toolkit with saved modules from state.json (use --local for project-local only)',
65
- status: 'Show installed modules, version, and profile from state.json',
65
+ status: 'Show installed modules, version, profile, and registered external rules/hooks',
66
66
  reset: 'Wipe and recreate project-local configs from scratch (requires --local)',
67
67
  uninstall: 'Remove ai-toolkit from ~/.claude/',
68
68
  'add-rule': 'Register a rule file or URL in ~/.softspark/ai-toolkit/rules/ (URL rules auto-refresh on update)',
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.2.0",
2
+ "version": "3.3.0",
3
3
  "components": {
4
4
  "agents": {
5
5
  "description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "AI coding toolkit: 112 skills, 44 agents, 12-editor write-through (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo, Aider, Augment, Antigravity, Codex, opencode), machine-enforced safety constitution, SARIF audit, signed npm provenance.",
5
5
  "keywords": [
6
6
  "claude",
@@ -26,6 +26,7 @@ def install_hooks(claude_dir: Path, hooks_scripts_dir: Path,
26
26
  if output_styles_dir.is_dir():
27
27
  print(" Would install: output styles to ~/.claude/output-styles/ + set outputStyle in settings.json")
28
28
  print(" Would set: env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 in settings.json")
29
+ print(" Would set: skillListingBudgetFraction=0.02 in settings.json (only if absent)")
29
30
  return
30
31
 
31
32
  _copy_hook_scripts(claude_dir, hooks_scripts_dir)
@@ -44,6 +45,7 @@ def install_hooks(claude_dir: Path, hooks_scripts_dir: Path,
44
45
 
45
46
  _install_output_styles(claude_dir)
46
47
  _install_env_vars(claude_dir)
48
+ _install_skill_listing_budget(claude_dir)
47
49
 
48
50
 
49
51
  def _copy_hook_scripts(claude_dir: Path, hooks_scripts_dir: Path) -> None:
@@ -151,3 +153,27 @@ def _install_env_vars(claude_dir: Path) -> None:
151
153
  with open(settings_path, "w", encoding="utf-8") as f:
152
154
  json.dump(settings_data, f, indent=4)
153
155
  print(" Set: env vars in settings.json (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)")
156
+
157
+
158
+ # Default for skill listing budget. 0.02 (~2% of context) keeps all 112+ skill
159
+ # descriptions loadable without truncation. Only applied if the user has not
160
+ # already set the key — explicit user overrides are preserved.
161
+ SKILL_LISTING_BUDGET_DEFAULT = 0.02
162
+
163
+
164
+ def _install_skill_listing_budget(claude_dir: Path) -> None:
165
+ """Set skillListingBudgetFraction default if user has not configured one."""
166
+ settings_path = claude_dir / "settings.json"
167
+ if settings_path.is_file():
168
+ with open(settings_path, encoding="utf-8") as f:
169
+ settings_data = json.load(f)
170
+ else:
171
+ settings_data = {}
172
+
173
+ if "skillListingBudgetFraction" in settings_data:
174
+ return
175
+
176
+ settings_data["skillListingBudgetFraction"] = SKILL_LISTING_BUDGET_DEFAULT
177
+ with open(settings_path, "w", encoding="utf-8") as f:
178
+ json.dump(settings_data, f, indent=4)
179
+ print(f" Set: skillListingBudgetFraction = {SKILL_LISTING_BUDGET_DEFAULT} in settings.json")
@@ -7,7 +7,27 @@ from datetime import datetime, timezone
7
7
  from pathlib import Path
8
8
 
9
9
  sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
10
- from paths import TOOLKIT_DATA_DIR, STATE_FILE
10
+ from paths import TOOLKIT_DATA_DIR, STATE_FILE, RULES_DIR, EXTERNAL_HOOKS_DIR
11
+
12
+
13
+ def _load_sources(sources_file: Path, key: str) -> list[tuple[str, str, str]]:
14
+ """Read a sources.json registry and return (name, url, fetched_at) tuples."""
15
+ if not sources_file.is_file():
16
+ return []
17
+ try:
18
+ with open(sources_file, encoding="utf-8") as f:
19
+ data = json.load(f)
20
+ except (json.JSONDecodeError, OSError):
21
+ return []
22
+ entries = data.get(key, {}) if isinstance(data, dict) else {}
23
+ if not isinstance(entries, dict):
24
+ return []
25
+ out: list[tuple[str, str, str]] = []
26
+ for name, meta in sorted(entries.items()):
27
+ if not isinstance(meta, dict):
28
+ continue
29
+ out.append((name, str(meta.get("url", "")), str(meta.get("fetched_at", ""))))
30
+ return out
11
31
 
12
32
 
13
33
  def _state_path() -> Path:
@@ -204,6 +224,18 @@ def print_status() -> None:
204
224
  if mcp:
205
225
  print(f" MCP: {', '.join(mcp)}")
206
226
 
227
+ ext_rules = _load_sources(RULES_DIR / "sources.json", "rules")
228
+ ext_hooks = _load_sources(EXTERNAL_HOOKS_DIR / "sources.json", "hooks")
229
+ if ext_rules or ext_hooks:
230
+ print()
231
+ print(" External sources:")
232
+ for name, url, fetched_at in ext_rules:
233
+ stamp = f" ({fetched_at})" if fetched_at else ""
234
+ print(f" rule {name} <- {url}{stamp}")
235
+ for name, url, fetched_at in ext_hooks:
236
+ stamp = f" ({fetched_at})" if fetched_at else ""
237
+ print(f" hook {name} <- {url}{stamp}")
238
+
207
239
  extends = state.get("extends")
208
240
  if extends:
209
241
  print(f" Extends: {extends.get('source', 'unknown')}")