@softspark/ai-toolkit 1.3.3 → 1.3.5

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.5 — Patch (2026-04-07)
11
+
12
+ ### Fixed
13
+ - **Stats not counting**: `track-usage.sh` and `user-prompt-submit.sh` now read prompt from stdin JSON (`.prompt` field) instead of non-existent `CLAUDE_USER_PROMPT` env var. Skill invocations are now properly tracked in `~/.ai-toolkit/stats.json`.
14
+
15
+ ---
16
+
17
+ ## v1.3.4 — Patch (2026-04-07)
18
+
19
+ ### Fixed
20
+ - **skills-catalog.md**: Added 6 missing language pattern skills (rust, java, csharp, kotlin, swift, ruby) to Development section (10→16). Total now correctly sums to 90.
21
+
22
+ ---
23
+
10
24
  ## v1.3.3 — Patch (2026-04-07)
11
25
 
12
26
  ### Fixed
@@ -24,8 +24,17 @@ log_event() {
24
24
  local EVENT="$1"
25
25
  local DETAIL="$2"
26
26
  mkdir -p "$LOG_DIR"
27
- printf '{"timestamp":"%s","event":"%s","tool":"%s","detail":"%s","session":"%s"}\n' \
28
- "$TIMESTAMP" "$EVENT" "$TOOL_NAME" "$DETAIL" "$SESSION" >> "$LOG_FILE"
27
+ if command -v jq >/dev/null 2>&1; then
28
+ jq -nc --arg ts "$TIMESTAMP" --arg ev "$EVENT" --arg tl "$TOOL_NAME" --arg dt "$DETAIL" --arg ss "$SESSION" \
29
+ '{"timestamp":$ts,"event":$ev,"tool":$tl,"detail":$dt,"session":$ss}' >> "$LOG_FILE"
30
+ else
31
+ # Escape double quotes in variable fields for basic JSON safety
32
+ local SAFE_DETAIL="${DETAIL//\"/\\\"}"
33
+ local SAFE_TOOL="${TOOL_NAME//\"/\\\"}"
34
+ local SAFE_SESSION="${SESSION//\"/\\\"}"
35
+ printf '{"timestamp":"%s","event":"%s","tool":"%s","detail":"%s","session":"%s"}\n' \
36
+ "$TIMESTAMP" "$EVENT" "$SAFE_TOOL" "$SAFE_DETAIL" "$SAFE_SESSION" >> "$LOG_FILE"
37
+ fi
29
38
  }
30
39
 
31
40
  case "$TOOL_NAME" in
@@ -8,8 +8,10 @@
8
8
  # shellcheck source=_profile-check.sh
9
9
  source "$(dirname "$0")/_profile-check.sh"
10
10
 
11
- FILE_PATH="${CLAUDE_TOOL_INPUT_FILE_PATH:-}"
12
- TOOL_NAME="${CLAUDE_TOOL_NAME:-unknown}"
11
+ # Read from stdin (Claude Code passes JSON with .tool_name, .tool_input)
12
+ INPUT=$(cat)
13
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"' 2>/dev/null)
14
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
13
15
 
14
16
  if [ -z "$FILE_PATH" ]; then
15
17
  echo "PostToolUse: ${TOOL_NAME} completed. Consider validating lint, tests, and docs if behavior changed."
@@ -11,8 +11,11 @@ source "$(dirname "$0")/_profile-check.sh"
11
11
  SAVE_DIR="$HOME/.ai-toolkit/compactions"
12
12
  mkdir -p "$SAVE_DIR"
13
13
 
14
+ # Read from stdin (Claude Code passes JSON with .session_id)
15
+ INPUT=$(cat)
14
16
  TIMESTAMP=$(date -u +"%Y-%m-%d_%H-%M-%S")
15
- SESSION="${CLAUDE_SESSION_ID:-$$}"
17
+ SESSION=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
18
+ [ -z "$SESSION" ] && SESSION="$$"
16
19
  SAVE_FILE="$SAVE_DIR/${TIMESTAMP}_${SESSION}.txt"
17
20
 
18
21
  # Gather context
@@ -7,17 +7,22 @@
7
7
  # shellcheck source=_profile-check.sh
8
8
  source "$(dirname "$0")/_profile-check.sh"
9
9
 
10
+ # Read from stdin (Claude Code passes JSON with .session_id, .last_assistant_message)
11
+ INPUT=$(cat)
12
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
13
+ LAST_MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // "No summary available"' 2>/dev/null | head -5)
14
+
10
15
  SESSION_FILE=".claude/session-context.md"
11
16
 
12
- if [ -n "${CLAUDE_SESSION_ID:-}" ]; then
17
+ if [ -n "$SESSION_ID" ]; then
13
18
  mkdir -p .claude
14
19
  cat > "$SESSION_FILE" << EOF
15
20
  # Session Context
16
21
  Updated: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
17
- Session: ${CLAUDE_SESSION_ID:-unknown}
22
+ Session: ${SESSION_ID}
18
23
 
19
- ## Last Active Task
20
- ${CLAUDE_TASK_DESCRIPTION:-No task description available}
24
+ ## Last Assistant Message
25
+ ${LAST_MSG}
21
26
  EOF
22
27
  fi
23
28
 
@@ -8,7 +8,10 @@
8
8
  # shellcheck source=_profile-check.sh
9
9
  source "$(dirname "$0")/_profile-check.sh"
10
10
 
11
- SESSION="${CLAUDE_SESSION_ID:-$$}"
11
+ # Read from stdin (Claude Code passes JSON with .session_id)
12
+ INPUT=$(cat)
13
+ SESSION=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
14
+ [ -z "$SESSION" ] && SESSION="$$"
12
15
  SESSIONS_DIR="$HOME/.ai-toolkit/sessions"
13
16
  mkdir -p "$SESSIONS_DIR"
14
17
 
@@ -8,9 +8,11 @@
8
8
  # shellcheck source=_profile-check.sh
9
9
  source "$(dirname "$0")/_profile-check.sh"
10
10
 
11
- SUBAGENT="${CLAUDE_SUBAGENT_NAME:-${CLAUDE_TOOL_INPUT_SUBAGENT_TYPE:-subagent}}"
11
+ # Read from stdin (Claude Code passes JSON with .agent_id, .agent_type)
12
+ INPUT=$(cat)
13
+ AGENT_TYPE=$(echo "$INPUT" | jq -r '.agent_type // "subagent"' 2>/dev/null)
12
14
 
13
- echo "SubagentStart: ${SUBAGENT} owns a narrow scope. Read only the necessary files first, cite evidence, and return explicit validation notes with any edits."
15
+ echo "SubagentStart: ${AGENT_TYPE} owns a narrow scope. Read only the necessary files first, cite evidence, and return explicit validation notes with any edits."
14
16
 
15
17
  exit 0
16
18
 
@@ -8,9 +8,11 @@
8
8
  # shellcheck source=_profile-check.sh
9
9
  source "$(dirname "$0")/_profile-check.sh"
10
10
 
11
- SUBAGENT="${CLAUDE_SUBAGENT_NAME:-${CLAUDE_TOOL_INPUT_SUBAGENT_TYPE:-subagent}}"
11
+ # Read from stdin (Claude Code passes JSON with .agent_id, .agent_type)
12
+ INPUT=$(cat)
13
+ AGENT_TYPE=$(echo "$INPUT" | jq -r '.agent_type // "subagent"' 2>/dev/null)
12
14
 
13
- echo "SubagentStop: ${SUBAGENT} should report findings, exact files touched, tests run, remaining risks, and any docs that must be updated by the lead agent."
15
+ echo "SubagentStop: ${AGENT_TYPE} should report findings, exact files touched, tests run, remaining risks, and any docs that must be updated by the lead agent."
14
16
 
15
17
  exit 0
16
18
 
@@ -8,7 +8,11 @@
8
8
  # Uses atomic write via python3 os.replace() to prevent corruption.
9
9
 
10
10
  STATS_FILE="${HOME}/.ai-toolkit/stats.json"
11
- PROMPT_TEXT="${CLAUDE_USER_PROMPT:-${CLAUDE_PROMPT:-}}"
11
+
12
+ # Read prompt from stdin (Claude Code passes JSON with .prompt field)
13
+ INPUT=$(cat)
14
+ PROMPT_TEXT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null)
15
+ [ -z "$PROMPT_TEXT" ] && exit 0
12
16
 
13
17
  # Only track if prompt starts with a slash command
14
18
  SKILL_NAME=$(printf '%s' "$PROMPT_TEXT" | grep -oE '^/[a-z][a-z0-9-]*' | head -1 | sed 's|^/||')
@@ -8,7 +8,9 @@
8
8
  # shellcheck source=_profile-check.sh
9
9
  source "$(dirname "$0")/_profile-check.sh"
10
10
 
11
- PROMPT_TEXT="${CLAUDE_USER_PROMPT:-${CLAUDE_PROMPT:-}}"
11
+ # Read prompt from stdin (Claude Code passes JSON with .prompt field)
12
+ INPUT=$(cat)
13
+ PROMPT_TEXT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null)
12
14
  LOWERED="$(printf '%s' "$PROMPT_TEXT" | tr '[:upper:]' '[:lower:]')"
13
15
 
14
16
  echo "STOP. Execute Step 0 before responding: check your CLAUDE.md for search-first rules. If search-first rules exist, call the required search tool NOW — before any other tool or text output. If you skip this step, the user will interrupt you."
package/bin/ai-toolkit.js CHANGED
@@ -8,6 +8,11 @@ const fs = require('fs');
8
8
  const TOOLKIT_DIR = path.dirname(__dirname);
9
9
  const CWD = process.cwd();
10
10
 
11
+ if (!process.env.HOME) {
12
+ console.error('Error: HOME environment variable is not set');
13
+ process.exit(1);
14
+ }
15
+
11
16
  // ---------------------------------------------------------------------------
12
17
  // Generator map: command name -> { script, dest, mkdir? }
13
18
  // Used by individual generator commands, `generate-all`, and the default case.
@@ -3,7 +3,7 @@ title: "AI Toolkit - Skills Catalog"
3
3
  category: reference
4
4
  service: ai-toolkit
5
5
  tags: [skills, domain-knowledge, catalog, task-skills, hybrid-skills]
6
- version: "1.3.0"
6
+ version: "1.3.3"
7
7
  created: "2026-03-23"
8
8
  last_updated: "2026-04-07"
9
9
  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."
@@ -113,7 +113,7 @@ Hybrid skills combine slash-command invocation with domain knowledge that agents
113
113
  | `application-deploy` | 3 | Deploy → smoke test → release notes |
114
114
  | `proactive-troubleshooting` | 4 | Investigate → check perf → preventive fix → docs |
115
115
 
116
- ## Knowledge Skills - Development (10)
116
+ ## Knowledge Skills - Development (16)
117
117
 
118
118
  | Skill | Directory | Domain |
119
119
  |-------|-----------|--------|
@@ -124,6 +124,12 @@ Hybrid skills combine slash-command invocation with domain knowledge that agents
124
124
  | **ecommerce-patterns** | `skills/ecommerce-patterns/` | E-commerce: catalog, cart, checkout, payments |
125
125
  | **clean-code** | `skills/clean-code/` | Multi-language code quality: Python, TS, PHP, Go, Dart |
126
126
  | **typescript-patterns** | `skills/typescript-patterns/` | TypeScript/JavaScript patterns for frontend and backend |
127
+ | **rust-patterns** | `skills/rust-patterns/` | Ownership, borrowing, error handling, Cargo, tokio, serde |
128
+ | **java-patterns** | `skills/java-patterns/` | Records, sealed classes, Stream API, Spring Boot, JUnit 5 |
129
+ | **csharp-patterns** | `skills/csharp-patterns/` | Nullable refs, async/await, ASP.NET Core, EF Core |
130
+ | **kotlin-patterns** | `skills/kotlin-patterns/` | Coroutines, DSLs, sealed classes, Ktor, MockK |
131
+ | **swift-patterns** | `skills/swift-patterns/` | Protocol-oriented, SwiftUI, async/await, SPM |
132
+ | **ruby-patterns** | `skills/ruby-patterns/` | Blocks, Rails conventions, RSpec, ActiveRecord |
127
133
  | **design-engineering** | `skills/design-engineering/` | UI polish, animation craft, easing, transforms, accessibility |
128
134
  | **documentation-standards** | `skills/documentation-standards/` | KB document conventions, frontmatter validation, category taxonomy |
129
135
  | **brand-voice** | `skills/brand-voice/` | Anti-trope list, voice principles, LLM rhetoric prevention |
package/llms-full.txt CHANGED
@@ -3343,10 +3343,10 @@ The `common/` directory uses the same structure except `frameworks.md` is replac
3343
3343
 
3344
3344
  ## Auto-Detection
3345
3345
 
3346
- When `--auto-detect` is passed, `scripts/install_steps/detect_language.py` scans the current directory for known marker files and selects the matching language module:
3346
+ `--local` automatically enables language auto-detection. `scripts/install_steps/detect_language.py` scans the current directory for known marker files and selects matching language modules:
3347
3347
 
3348
3348
  ```bash
3349
- ai-toolkit install --local --auto-detect
3349
+ ai-toolkit install --local # auto-detects language (--auto-detect is implied)
3350
3350
  ```
3351
3351
 
3352
3352
  Detection logic (first match wins when multiple markers are present):
@@ -3368,14 +3368,14 @@ Common rules are always injected regardless of detected language.
3368
3368
  ## Installation
3369
3369
 
3370
3370
  ```bash
3371
- # Auto-detect language from project files
3372
- ai-toolkit install --local --auto-detect
3371
+ # Auto-detect language from project files (default with --local)
3372
+ ai-toolkit install --local
3373
3373
 
3374
3374
  # Explicitly select a language
3375
3375
  ai-toolkit install --local --lang typescript
3376
3376
 
3377
- # Install without language rules
3378
- ai-toolkit install --local
3377
+ # Skip auto-detect, install specific modules only
3378
+ ai-toolkit install --local --modules core,agents
3379
3379
  ```
3380
3380
 
3381
3381
  Language rules are injected into the project `CLAUDE.md` between named markers:
@@ -3491,8 +3491,8 @@ ai-toolkit install --profile standard
3491
3491
  # Module-based install (new)
3492
3492
  ai-toolkit install --modules core,agents,rules-typescript
3493
3493
 
3494
- # Auto-detect project language and install matching rules
3495
- ai-toolkit install --local --auto-detect
3494
+ # --local implies --auto-detect (language rules auto-detected)
3495
+ ai-toolkit install --local
3496
3496
 
3497
3497
  # Show currently installed modules and their state
3498
3498
  ai-toolkit status
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
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",
@@ -14,6 +14,7 @@ Arguments:
14
14
  """
15
15
  from __future__ import annotations
16
16
 
17
+ import re
17
18
  import shutil
18
19
  import sys
19
20
  from pathlib import Path
@@ -33,6 +34,10 @@ def main() -> None:
33
34
  sys.exit(1)
34
35
 
35
36
  rule_name = sys.argv[2] if len(sys.argv) > 2 else rule_file.stem
37
+ rule_name = re.sub(r"[^a-zA-Z0-9_-]", "", rule_name)
38
+ if not rule_name:
39
+ print("Error: rule name is empty after sanitization", file=sys.stderr)
40
+ sys.exit(1)
36
41
  rules_dir = Path.home() / ".ai-toolkit" / "rules"
37
42
  rules_dir.mkdir(parents=True, exist_ok=True)
38
43
 
package/scripts/plugin.py CHANGED
@@ -588,6 +588,9 @@ def _parse_clean_args(args: list[str]) -> tuple[list[str], int]:
588
588
  if args[i] == "--days" and i + 1 < len(args):
589
589
  try:
590
590
  days = int(args[i + 1])
591
+ if days <= 0:
592
+ print(f" ERROR: --days must be positive, got {days}")
593
+ sys.exit(1)
591
594
  except ValueError:
592
595
  print(f" ERROR: --days requires a number, got '{args[i + 1]}'")
593
596
  sys.exit(1)
package/scripts/sync.py CHANGED
@@ -66,9 +66,23 @@ def do_import(source: str) -> None:
66
66
  """Import config from file or URL."""
67
67
  tmpfile: str | None = None
68
68
 
69
- if source.startswith("http"):
70
- tmpfile = tempfile.mktemp(suffix=".json")
71
- urllib.request.urlretrieve(source, tmpfile)
69
+ if source.startswith("https://") or source.startswith("http://"):
70
+ if not source.startswith("https://"):
71
+ print("Error: HTTP imports are not supported. Use HTTPS for security.", file=sys.stderr)
72
+ sys.exit(1)
73
+ tmp_fd, tmpfile = tempfile.mkstemp(suffix=".json")
74
+ os.close(tmp_fd)
75
+ try:
76
+ import ssl
77
+ ctx = ssl.create_default_context()
78
+ with urllib.request.urlopen(source, timeout=30, context=ctx) as resp:
79
+ data = resp.read(10 * 1024 * 1024) # 10MB max
80
+ with open(tmpfile, 'wb') as f:
81
+ f.write(data)
82
+ except Exception:
83
+ if os.path.exists(tmpfile):
84
+ os.unlink(tmpfile)
85
+ raise
72
86
  source = tmpfile
73
87
 
74
88
  source_path = Path(source)
@@ -112,7 +126,8 @@ def do_push() -> None:
112
126
  print("Error: gh not authenticated. Run: gh auth login")
113
127
  sys.exit(1)
114
128
 
115
- tmpfile = tempfile.mktemp(suffix=".json")
129
+ tmp_fd, tmpfile = tempfile.mkstemp(suffix=".json")
130
+ os.close(tmp_fd)
116
131
  Path(tmpfile).write_text(do_export(), encoding="utf-8")
117
132
 
118
133
  if GIST_ID_FILE.is_file():
@@ -153,12 +168,14 @@ def do_pull(gist_id: str = "") -> None:
153
168
  print("Usage: ai-toolkit sync --pull <gist-id>")
154
169
  sys.exit(1)
155
170
 
156
- tmpfile = tempfile.mktemp(suffix=".json")
157
- subprocess.run(
158
- ["gh", "gist", "view", gist_id, "-f", "ai-toolkit-config.json"],
159
- stdout=open(tmpfile, "w"),
160
- check=True,
161
- )
171
+ tmp_fd, tmpfile = tempfile.mkstemp(suffix=".json")
172
+ os.close(tmp_fd)
173
+ with open(tmpfile, "w") as f:
174
+ subprocess.run(
175
+ ["gh", "gist", "view", gist_id, "-f", "ai-toolkit-config.json"],
176
+ stdout=f,
177
+ check=True,
178
+ )
162
179
  do_import(tmpfile)
163
180
  os.unlink(tmpfile)
164
181
 
@@ -30,7 +30,7 @@ from plugin_schema import validate_references as _validate_plugin_references
30
30
  # ---------------------------------------------------------------------------
31
31
 
32
32
  VALID_TOOLS = frozenset({
33
- "Read", "Write", "Edit", "Bash", "Grep", "Glob", "Agent",
33
+ "Read", "Write", "Edit", "MultiEdit", "Bash", "Grep", "Glob", "Agent",
34
34
  "WebSearch", "WebFetch", "TodoRead", "TodoWrite",
35
35
  "TeamCreate", "TeamDelete", "SendMessage",
36
36
  "TaskCreate", "TaskList", "TaskUpdate", "TaskGet", "TaskOutput", "TaskStop",