@softspark/ai-toolkit 1.3.5 → 1.3.7

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,20 @@ Versioning follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## v1.3.7 — Update Notifications (2026-04-07)
11
+
12
+ ### Added
13
+ - **Update notifications**: `session-start.sh` hook checks npm for newer versions (cached 24h, non-blocking). `ai-toolkit status` shows installed vs latest version with upgrade command.
14
+
15
+ ---
16
+
17
+ ## v1.3.6 — Patch (2026-04-07)
18
+
19
+ ### Fixed
20
+ - **Tests updated**: All hook tests now pass stdin JSON instead of env vars (matching hook changes from v1.3.5). 327/327 tests pass.
21
+
22
+ ---
23
+
10
24
  ## v1.3.5 — Patch (2026-04-07)
11
25
 
12
26
  ### Fixed
@@ -8,7 +8,14 @@
8
8
  echo "MANDATORY: Before answering ANY technical question, apply ALL rules from your CLAUDE.md files (global + project). Follow the exact order of operations defined there. Do NOT skip mandatory steps even if you think you already know the answer."
9
9
  echo "REMINDER: When writing features or fixing bugs, ensure tests cover the changes. When modifying API, config, or setup, update relevant documentation. Propose these steps to the user — do not silently skip them."
10
10
 
11
- # 2. Load session context (if available)
11
+ # 2. Check for updates (cached, max once per 24h, non-blocking)
12
+ TOOLKIT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
13
+ VERSION_MSG=$(python3 "$TOOLKIT_DIR/scripts/version_check.py" 2>/dev/null)
14
+ if [ -n "$VERSION_MSG" ]; then
15
+ echo "$VERSION_MSG"
16
+ fi
17
+
18
+ # 3. Load session context (if available)
12
19
  SESSION_FILE=".claude/session-context.md"
13
20
  if [ -f "$SESSION_FILE" ]; then
14
21
  echo "=== Session Context ==="
@@ -32,17 +32,17 @@ AI Toolkit Config Benchmark
32
32
  ========================
33
33
 
34
34
  ## Your Configuration (~/.claude/)
35
- Agents: 47
35
+ Agents: 44
36
36
  Skills: 80
37
37
  Hooks: 12
38
38
 
39
39
  ## Toolkit Totals
40
- Agents: 47
40
+ Agents: 44
41
41
  Skills: 80
42
42
  Hooks: 12
43
43
 
44
44
  ## Coverage
45
- Agents: 100% (47 / 47)
45
+ Agents: 100% (44 / 44)
46
46
  Skills: 100% (80 / 80)
47
47
  Hooks: 100% (12 / 12)
48
48
 
@@ -384,7 +384,7 @@ All entries are tagged with "_source": "<source-name>" for idempotent updates.
384
384
  "required": true
385
385
  },
386
386
  "agents": {
387
- "description": "47 specialized agents",
387
+ "description": "44 specialized agents",
388
388
  "files": ["app/agents/*.md"],
389
389
  "required": false,
390
390
  "default": true
@@ -24,7 +24,7 @@ Modules are defined in `manifest.json` at the repository root. There are 17 modu
24
24
  | Module | Description | In Profile |
25
25
  |--------|-------------|-----------|
26
26
  | `core` | Core hooks and essential skills | minimal, standard, strict, full |
27
- | `agents` | 47 specialized agents | standard, strict, full |
27
+ | `agents` | 44 specialized agents | standard, strict, full |
28
28
  | `skills` | 90 skills (task, hybrid, knowledge) | standard, strict, full |
29
29
  | `rules-common` | Common coding rules (5 files) | standard, strict, full |
30
30
  | `rules-typescript` | TypeScript-specific rules (5 files) | auto-detect |
package/llms-full.txt CHANGED
@@ -1199,17 +1199,17 @@ AI Toolkit Config Benchmark
1199
1199
  ========================
1200
1200
 
1201
1201
  ## Your Configuration (~/.claude/)
1202
- Agents: 47
1202
+ Agents: 44
1203
1203
  Skills: 80
1204
1204
  Hooks: 12
1205
1205
 
1206
1206
  ## Toolkit Totals
1207
- Agents: 47
1207
+ Agents: 44
1208
1208
  Skills: 80
1209
1209
  Hooks: 12
1210
1210
 
1211
1211
  ## Coverage
1212
- Agents: 100% (47 / 47)
1212
+ Agents: 100% (44 / 44)
1213
1213
  Skills: 100% (80 / 80)
1214
1214
  Hooks: 100% (12 / 12)
1215
1215
 
@@ -1907,7 +1907,7 @@ All entries are tagged with "_source": "<source-name>" for idempotent updates.
1907
1907
  "required": true
1908
1908
  },
1909
1909
  "agents": {
1910
- "description": "47 specialized agents",
1910
+ "description": "44 specialized agents",
1911
1911
  "files": ["app/agents/*.md"],
1912
1912
  "required": false,
1913
1913
  "default": true
@@ -3454,7 +3454,7 @@ Modules are defined in `manifest.json` at the repository root. There are 17 modu
3454
3454
  | Module | Description | In Profile |
3455
3455
  |--------|-------------|-----------|
3456
3456
  | `core` | Core hooks and essential skills | minimal, standard, strict, full |
3457
- | `agents` | 47 specialized agents | standard, strict, full |
3457
+ | `agents` | 44 specialized agents | standard, strict, full |
3458
3458
  | `skills` | 90 skills (task, hybrid, knowledge) | standard, strict, full |
3459
3459
  | `rules-common` | Common coding rules (5 files) | standard, strict, full |
3460
3460
  | `rules-typescript` | TypeScript-specific rules (5 files) | auto-detect |
@@ -4067,7 +4067,7 @@ title: "AI Toolkit - Skills Catalog"
4067
4067
  category: reference
4068
4068
  service: ai-toolkit
4069
4069
  tags: [skills, domain-knowledge, catalog, task-skills, hybrid-skills]
4070
- version: "1.3.0"
4070
+ version: "1.3.3"
4071
4071
  created: "2026-03-23"
4072
4072
  last_updated: "2026-04-07"
4073
4073
  description: "Complete catalog of 90 skills: 28 task, 30 hybrid, 32 knowledge. Includes effort levels, skill-scoped hooks, executable scripts, security auditor, and persona presets."
@@ -4177,7 +4177,7 @@ Hybrid skills combine slash-command invocation with domain knowledge that agents
4177
4177
  | `application-deploy` | 3 | Deploy → smoke test → release notes |
4178
4178
  | `proactive-troubleshooting` | 4 | Investigate → check perf → preventive fix → docs |
4179
4179
 
4180
- ## Knowledge Skills - Development (10)
4180
+ ## Knowledge Skills - Development (16)
4181
4181
 
4182
4182
  | Skill | Directory | Domain |
4183
4183
  |-------|-----------|--------|
@@ -4188,6 +4188,12 @@ Hybrid skills combine slash-command invocation with domain knowledge that agents
4188
4188
  | **ecommerce-patterns** | `skills/ecommerce-patterns/` | E-commerce: catalog, cart, checkout, payments |
4189
4189
  | **clean-code** | `skills/clean-code/` | Multi-language code quality: Python, TS, PHP, Go, Dart |
4190
4190
  | **typescript-patterns** | `skills/typescript-patterns/` | TypeScript/JavaScript patterns for frontend and backend |
4191
+ | **rust-patterns** | `skills/rust-patterns/` | Ownership, borrowing, error handling, Cargo, tokio, serde |
4192
+ | **java-patterns** | `skills/java-patterns/` | Records, sealed classes, Stream API, Spring Boot, JUnit 5 |
4193
+ | **csharp-patterns** | `skills/csharp-patterns/` | Nullable refs, async/await, ASP.NET Core, EF Core |
4194
+ | **kotlin-patterns** | `skills/kotlin-patterns/` | Coroutines, DSLs, sealed classes, Ktor, MockK |
4195
+ | **swift-patterns** | `skills/swift-patterns/` | Protocol-oriented, SwiftUI, async/await, SPM |
4196
+ | **ruby-patterns** | `skills/ruby-patterns/` | Blocks, Rails conventions, RSpec, ActiveRecord |
4191
4197
  | **design-engineering** | `skills/design-engineering/` | UI polish, animation craft, easing, transforms, accessibility |
4192
4198
  | **documentation-standards** | `skills/documentation-standards/` | KB document conventions, frontmatter validation, category taxonomy |
4193
4199
  | **brand-voice** | `skills/brand-voice/` | Anti-trope list, voice principles, LLM rhetoric prevention |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "description": "Professional-grade AI coding toolkit: 90 skills, 44 agents, multi-platform support (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo Code, Aider, Augment), machine-enforced safety constitution, persona presets, skill security auditor, expanded lifecycle hooks, 11 plugin packs, and benchmark tooling.",
5
5
  "keywords": [
6
6
  "claude",
@@ -166,11 +166,11 @@ def install_local_project(rules_dir: Path, dry_run: bool, reset: bool,
166
166
 
167
167
 
168
168
  def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> None:
169
- """Inject language-specific rules into project's CLAUDE.md.
169
+ """Inject language-specific rule summary into project's .claude/CLAUDE.md.
170
170
 
171
- Concatenates all .md files from matching app/rules/<lang>/ directories
172
- into a single marker-injected section in the project's .claude/CLAUDE.md.
173
- Also always includes common rules.
171
+ Instead of injecting full rule content (hundreds of lines), injects a
172
+ compact summary with the key rules per category. Full rules are available
173
+ as knowledge skills that Claude auto-loads contextually.
174
174
  """
175
175
  if not language_modules:
176
176
  return
@@ -179,19 +179,35 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
179
179
  if not rules_src.is_dir():
180
180
  return
181
181
 
182
- # Always include common + detected languages
183
- lang_dirs: list[str] = ["common"]
182
+ # Detect language names
183
+ langs: list[str] = []
184
184
  for mod in language_modules:
185
- # module name format: "rules-typescript" → dir "typescript"
186
185
  if mod.startswith("rules-"):
187
- lang_dirs.append(mod[6:])
186
+ langs.append(mod[6:])
188
187
 
189
- # Collect all rule content
190
- parts: list[str] = []
191
- for lang_dir in lang_dirs:
188
+ if not langs:
189
+ return
190
+
191
+ # Build compact summary: first 5 key rules from each category
192
+ lines: list[str] = ["# Language Rules (auto-detected)", ""]
193
+ lines.append(f"Detected: **{', '.join(langs)}** + common rules.")
194
+ lines.append("")
195
+ lines.append("Detailed rules available as knowledge skills (auto-loaded by Claude).")
196
+ lines.append("")
197
+
198
+ # Deduplicate: common + unique language dirs
199
+ all_dirs: list[str] = ["common"]
200
+ for l in langs:
201
+ if l not in all_dirs:
202
+ all_dirs.append(l)
203
+
204
+ for lang_dir in all_dirs:
192
205
  lang_path = rules_src / lang_dir
193
206
  if not lang_path.is_dir():
194
207
  continue
208
+ lang_label = lang_dir.capitalize() if lang_dir != "common" else "Common"
209
+ lines.append(f"## {lang_label} Rules")
210
+ lines.append("")
195
211
  for rule_file in sorted(lang_path.glob("*.md")):
196
212
  content = rule_file.read_text(encoding="utf-8").strip()
197
213
  # Strip YAML frontmatter
@@ -199,15 +215,16 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
199
215
  end = content.find("---", 3)
200
216
  if end != -1:
201
217
  content = content[end + 3:].strip()
202
- if content:
203
- parts.append(content)
204
-
205
- if not parts:
206
- return
207
-
208
- # Write concatenated rules to a temp file, then inject as a section
218
+ # Extract first 3 bullet points as key rules
219
+ bullets = [l.strip() for l in content.splitlines() if l.strip().startswith("- ")][:3]
220
+ if bullets:
221
+ category = rule_file.stem.replace("-", " ").title()
222
+ lines.append(f"**{category}:** {' | '.join(b.lstrip('- ') for b in bullets)}")
223
+ lines.append("")
224
+
225
+ # Write summary to temp file, then inject as section
209
226
  import tempfile
210
- combined = "\n\n".join(parts)
227
+ combined = "\n".join(lines)
211
228
  with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False,
212
229
  encoding="utf-8") as tmp:
213
230
  tmp.write(combined)
@@ -215,8 +232,8 @@ def _inject_language_rules(cwd: Path, language_modules: list[str] | None) -> Non
215
232
 
216
233
  try:
217
234
  inject_section(tmp_path, cwd / ".claude" / "CLAUDE.md", "language-rules")
218
- detected = ", ".join(d for d in lang_dirs if d != "common")
219
- print(f" Injected: language rules (common + {detected})")
235
+ lang_names = [l for l in langs if l != "common"]
236
+ print(f" Injected: language rules summary (common + {', '.join(lang_names)})")
220
237
  finally:
221
238
  tmp_path.unlink(missing_ok=True)
222
239
 
@@ -115,3 +115,18 @@ def print_status() -> None:
115
115
  # Strip "rules-" prefix for readability
116
116
  langs = [m.replace("rules-", "") for m in detected]
117
117
  print(f" Detected: {', '.join(langs)}")
118
+
119
+ # Check for updates
120
+ try:
121
+ import sys as _sys
122
+ _sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
123
+ from version_check import check
124
+ result = check()
125
+ if result["update_available"]:
126
+ print()
127
+ print(f" Update available: {result['installed']} -> {result['latest']}")
128
+ print(f" Run: npm install -g @softspark/ai-toolkit@latest && ai-toolkit update")
129
+ else:
130
+ print(f" Latest: {result['latest']} (up to date)")
131
+ except Exception:
132
+ pass # version check is optional
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env python3
2
+ """Check if a newer version of ai-toolkit is available on npm.
3
+
4
+ Caches the result for 24 hours to avoid hitting npm on every session.
5
+
6
+ Usage:
7
+ version_check.py # Print update notice if available (for hooks)
8
+ version_check.py --status # Print detailed version info (for ai-toolkit status)
9
+ version_check.py --force # Force re-check (ignore cache)
10
+
11
+ Exit codes:
12
+ 0 Up to date (or check skipped/failed)
13
+ 1 Update available
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import os
19
+ import subprocess
20
+ import sys
21
+ import time
22
+ from pathlib import Path
23
+
24
+ CACHE_FILE = Path.home() / ".ai-toolkit" / "version-check.json"
25
+ CACHE_TTL = 86400 # 24 hours
26
+ PACKAGE_NAME = "@softspark/ai-toolkit"
27
+
28
+
29
+ def _get_installed_version() -> str:
30
+ """Read version from the toolkit's own package.json."""
31
+ pkg = Path(__file__).resolve().parent.parent / "package.json"
32
+ if pkg.is_file():
33
+ with open(pkg, encoding="utf-8") as f:
34
+ return json.load(f).get("version", "unknown")
35
+ return "unknown"
36
+
37
+
38
+ def _get_cached() -> dict | None:
39
+ """Read cached version check result."""
40
+ if not CACHE_FILE.is_file():
41
+ return None
42
+ try:
43
+ with open(CACHE_FILE, encoding="utf-8") as f:
44
+ data = json.load(f)
45
+ checked_at = data.get("checked_at", 0)
46
+ if time.time() - checked_at > CACHE_TTL:
47
+ return None # expired
48
+ return data
49
+ except (json.JSONDecodeError, OSError):
50
+ return None
51
+
52
+
53
+ def _save_cache(installed: str, latest: str) -> None:
54
+ """Save version check result to cache."""
55
+ CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
56
+ data = {
57
+ "installed": installed,
58
+ "latest": latest,
59
+ "checked_at": time.time(),
60
+ }
61
+ tmp = str(CACHE_FILE) + ".tmp"
62
+ with open(tmp, "w", encoding="utf-8") as f:
63
+ json.dump(data, f)
64
+ os.replace(tmp, str(CACHE_FILE))
65
+
66
+
67
+ def _fetch_latest_version() -> str | None:
68
+ """Fetch latest version from npm registry (2s timeout)."""
69
+ try:
70
+ result = subprocess.run(
71
+ ["npm", "view", PACKAGE_NAME, "version"],
72
+ capture_output=True, text=True, timeout=5,
73
+ )
74
+ if result.returncode == 0:
75
+ return result.stdout.strip()
76
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
77
+ pass
78
+ return None
79
+
80
+
81
+ def _parse_semver(version: str) -> tuple[int, ...]:
82
+ """Parse version string to comparable tuple."""
83
+ try:
84
+ return tuple(int(x) for x in version.split("."))
85
+ except (ValueError, AttributeError):
86
+ return (0, 0, 0)
87
+
88
+
89
+ def _is_newer(latest: str, installed: str) -> bool:
90
+ """Check if latest is newer than installed."""
91
+ return _parse_semver(latest) > _parse_semver(installed)
92
+
93
+
94
+ def check(force: bool = False) -> dict:
95
+ """Check for updates. Returns {installed, latest, update_available}."""
96
+ installed = _get_installed_version()
97
+
98
+ if not force:
99
+ cached = _get_cached()
100
+ if cached:
101
+ return {
102
+ "installed": installed,
103
+ "latest": cached.get("latest", installed),
104
+ "update_available": _is_newer(cached.get("latest", ""), installed),
105
+ "cached": True,
106
+ }
107
+
108
+ latest = _fetch_latest_version()
109
+ if latest:
110
+ _save_cache(installed, latest)
111
+ return {
112
+ "installed": installed,
113
+ "latest": latest,
114
+ "update_available": _is_newer(latest, installed),
115
+ "cached": False,
116
+ }
117
+
118
+ return {
119
+ "installed": installed,
120
+ "latest": "unknown",
121
+ "update_available": False,
122
+ "cached": False,
123
+ }
124
+
125
+
126
+ def main() -> None:
127
+ force = "--force" in sys.argv
128
+ status_mode = "--status" in sys.argv
129
+
130
+ result = check(force=force)
131
+
132
+ if status_mode:
133
+ print(f" Installed: {result['installed']}")
134
+ print(f" Latest: {result['latest']}")
135
+ if result["update_available"]:
136
+ print(f" Update: {result['installed']} -> {result['latest']}")
137
+ print(f" Run: npm install -g {PACKAGE_NAME}@latest && ai-toolkit update")
138
+ else:
139
+ print(" Update: up to date")
140
+ sys.exit(1 if result["update_available"] else 0)
141
+
142
+ # Hook mode: only print if update available
143
+ if result["update_available"]:
144
+ print(
145
+ f"ai-toolkit update available: {result['installed']} -> {result['latest']} "
146
+ f"(npm install -g {PACKAGE_NAME}@latest)"
147
+ )
148
+ sys.exit(1)
149
+
150
+ sys.exit(0)
151
+
152
+
153
+ if __name__ == "__main__":
154
+ main()