@softspark/ai-toolkit 3.0.1 → 3.0.2
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 +23 -0
- package/README.md +13 -6
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/skills/hook-creator/SKILL.md +7 -3
- package/app/skills/introspect/SKILL.md +1 -1
- package/benchmarks/ecosystem-doctor-snapshot.json +13 -13
- package/bin/ai-toolkit.js +5 -1
- package/kb/reference/cli-reference.md +14 -3
- package/kb/reference/competitive-features-implementation.md +9 -9
- package/kb/reference/hooks-catalog.md +8 -2
- package/kb/reference/windows-support.md +50 -0
- package/llms-full.txt +87 -14
- package/llms.txt +1 -0
- package/manifest.json +1 -1
- package/package.json +6 -2
- package/scripts/_common.py +21 -0
- package/scripts/check_deps.py +14 -0
- package/scripts/stats.py +126 -39
- package/scripts/validate.py +160 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v3.0.2 — Validation, Windows, Telemetry, and generate:all parity (2026-04-24)
|
|
11
|
+
|
|
12
|
+
Patch release that closes known validation gaps, expands the hook-surface contracts, adds Windows dependency hints, exposes local product telemetry, and restores `npm run generate:all` parity with the CLI so registered custom rules reach every editor.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Hook surface validation** — `scripts/validate.py` now accepts the current Claude Code hook event surface, including `PostToolUseFailure`, `PostToolBatch`, and `UserPromptExpansion`.
|
|
17
|
+
- **Hook handler contracts** — validation now checks supported handler types (`command`, `http`, `prompt`, `agent`, `mcp_tool`), required handler fields, and prompt/agent event compatibility.
|
|
18
|
+
- **Language rule validation** — `scripts/validate.py` now checks `app/rules/<language>/*.md` frontmatter, category names, filename/category alignment, directory/language alignment, and required category coverage.
|
|
19
|
+
- **Windows dependency hints** — `_common.detect_os()` recognizes Windows package managers (`winget`, Chocolatey, Scoop), and `check_deps.py` emits package install commands for Python, Git, Node, SQLite, and supported optional tooling.
|
|
20
|
+
- **Product telemetry summary** — `ai-toolkit stats --summary` reports local usage telemetry; `--summary --json` emits machine-readable totals, coverage, unused catalog count, recent activity, and top skills.
|
|
21
|
+
- **Dedicated gap tests** — added contracts for structured rules, Windows dependency support, council/brand-voice/introspect skill coverage, and `npm run generate:all` directory-rule generator coverage. Total test count: 945 -> 960.
|
|
22
|
+
- **Windows Support KB** — new `kb/reference/windows-support.md` documents WSL, Git Bash, package managers, and hook runtime constraints.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **`package.json` `generate:all`** — now invokes all directory-based rule generators (`generate_cursor_mdc.py`, `generate_windsurf_rules.py`, `generate_roo_rules.py`, `generate_augment_rules.py`) so registered custom rules in `~/.softspark/ai-toolkit/rules/` propagate to every editor that supports per-rule module files. Restores parity with `ai-toolkit generate-all` (CLI).
|
|
27
|
+
- **`app/skills/hook-creator/SKILL.md`** — documents the expanded hook event and handler type surface.
|
|
28
|
+
- **`README.md` and `kb/reference/cli-reference.md`** — document `stats --summary` and Windows support.
|
|
29
|
+
- **`kb/reference/hooks-catalog.md`** — documents the validated hook surface and non-command handler support.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
10
33
|
## v3.0.1 — Release SOP Deep-Coverage Checks (2026-04-24)
|
|
11
34
|
|
|
12
35
|
Doc-only patch. No code, generators, or runtime behavior changed.
|
package/README.md
CHANGED
|
@@ -6,16 +6,19 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](tests/)
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
## What's New in v3.0.
|
|
13
|
+
## What's New in v3.0.2
|
|
14
14
|
|
|
15
|
-
**3.0.
|
|
15
|
+
**3.0.2 is a patch release** that closes known validation gaps, expands the validated hook surface, adds Windows dependency hints, exposes local product telemetry, and restores `npm run generate:all` parity with the CLI.
|
|
16
16
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
17
|
+
- `validate.py` now checks hook handler types, required handler fields, prompt/agent event compatibility, and structured language-rule frontmatter/category coverage.
|
|
18
|
+
- Hook docs and validation now cover `PostToolUseFailure`, `PostToolBatch`, `UserPromptExpansion`, plus `command`, `http`, `prompt`, `agent`, and `mcp_tool` handler types.
|
|
19
|
+
- `ai-toolkit stats --summary` reports local product telemetry: total invocations, unique skills used, catalog coverage, unused skills, recent activity, and top skills.
|
|
20
|
+
- Windows dependency detection now emits package hints for `winget`, Chocolatey, and Scoop; WSL remains the recommended runtime for Bash hooks.
|
|
21
|
+
- `npm run generate:all` now invokes every directory-based rule generator, so registered custom rules in `~/.softspark/ai-toolkit/rules/` propagate to every editor that supports per-rule module files.
|
|
19
22
|
|
|
20
23
|
### Carried from v3.0.0 (feature release)
|
|
21
24
|
|
|
@@ -66,6 +69,8 @@ npx @softspark/ai-toolkit install
|
|
|
66
69
|
|
|
67
70
|
**That's it.** Claude Code picks up 99 skills, 44 agents, quality hooks, and the safety constitution automatically.
|
|
68
71
|
|
|
72
|
+
**Windows:** WSL is the recommended runtime. Native Windows works when Git Bash is available for hook scripts; dependency hints cover `winget`, Chocolatey, and Scoop. See [Windows Support](kb/reference/windows-support.md).
|
|
73
|
+
|
|
69
74
|
### Update
|
|
70
75
|
|
|
71
76
|
```bash
|
|
@@ -161,7 +166,7 @@ ai-toolkit/
|
|
|
161
166
|
│ └── ARCHITECTURE.md # Full system design
|
|
162
167
|
├── kb/ # Reference docs, procedures, plans
|
|
163
168
|
├── scripts/ # Validation, install, evaluation scripts
|
|
164
|
-
├── tests/ # Bats test suite (
|
|
169
|
+
├── tests/ # Bats test suite (960 tests)
|
|
165
170
|
└── CHANGELOG.md
|
|
166
171
|
```
|
|
167
172
|
|
|
@@ -187,6 +192,8 @@ ai-toolkit/
|
|
|
187
192
|
|
|
188
193
|
**Persistent memory** — `memory-pack` plugin: SQLite + FTS5 search across past sessions.
|
|
189
194
|
|
|
195
|
+
**Local product telemetry** — `ai-toolkit stats --summary` reports total invocations, skill coverage, unused catalog skills, recent activity, and top skills from local usage data.
|
|
196
|
+
|
|
190
197
|
**Persona presets** — 4 roles (backend-lead, frontend-lead, devops-eng, junior-dev) adjust style and priorities.
|
|
191
198
|
|
|
192
199
|
**Config inheritance** — Enterprise `extends` system with constitution immutability and enforcement constraints. See [Enterprise Config Guide](kb/reference/enterprise-config-guide.md).
|
|
@@ -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.0.
|
|
4
|
+
"version": "3.0.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SoftSpark",
|
|
7
7
|
"url": "https://github.com/softspark"
|
|
@@ -30,6 +30,8 @@ Create a new Claude Code hook following ai-toolkit conventions.
|
|
|
30
30
|
|-------|-----------|---------|-------------|
|
|
31
31
|
| `PreToolUse` | Before a tool executes | tool name (e.g. `Bash`) or `if:` rule | Safety guards, validation, `"defer"` for headless |
|
|
32
32
|
| `PostToolUse` | After a tool executes | tool name | Feedback loops, logging, format-on-save |
|
|
33
|
+
| `PostToolUseFailure` | After a tool fails | tool name | Failure telemetry, recovery hints |
|
|
34
|
+
| `PostToolBatch` | After a batch of tool calls completes | any | Batch summaries, aggregate validation |
|
|
33
35
|
|
|
34
36
|
### Turn lifecycle
|
|
35
37
|
|
|
@@ -37,6 +39,7 @@ Create a new Claude Code hook following ai-toolkit conventions.
|
|
|
37
39
|
|-------|-----------|---------|-------------|
|
|
38
40
|
| `Stop` | Claude finishes responding | any | Quality checks, session save |
|
|
39
41
|
| `StopFailure` | Turn ends due to an API error (rate limit, auth) | any | Alerting, fallback behavior |
|
|
42
|
+
| `UserPromptExpansion` | Claude expands or rewrites a submitted prompt | any | Prompt policy and context shaping |
|
|
40
43
|
|
|
41
44
|
### Subagent lifecycle
|
|
42
45
|
|
|
@@ -88,12 +91,13 @@ Create a new Claude Code hook following ai-toolkit conventions.
|
|
|
88
91
|
|
|
89
92
|
## Hook Handler Types
|
|
90
93
|
|
|
91
|
-
Claude Code supports
|
|
94
|
+
Claude Code supports five handler `type` values in `hooks.json`:
|
|
92
95
|
|
|
93
96
|
| Type | Purpose | Required fields |
|
|
94
97
|
|------|---------|-----------------|
|
|
95
98
|
| `command` | Run a shell script / binary | `command` (path + args) |
|
|
96
|
-
| `
|
|
99
|
+
| `http` | Call a local or remote HTTP endpoint | `url` |
|
|
100
|
+
| `prompt` | Inject a prompt to the fast inline model and use its verdict | `prompt` |
|
|
97
101
|
| `agent` | Spawn a full subagent to evaluate the event (must target `Stop` / `SubagentStop`) | `agent` (agent name) |
|
|
98
102
|
| `mcp_tool` | Invoke an MCP tool directly (no subprocess) | `server`, `tool`, `arguments` |
|
|
99
103
|
|
|
@@ -137,7 +141,7 @@ Claude Code supports four handler `type` values in `hooks.json`:
|
|
|
137
141
|
Required fields:
|
|
138
142
|
- `_source`: always `"ai-toolkit"` (used by merge/strip logic)
|
|
139
143
|
- `matcher`: tool name or regex for Pre/PostToolUse, empty string for global events
|
|
140
|
-
- `hooks[].type`: `"command"`, `"prompt"`, `"agent"`, or `"mcp_tool"` (ai-toolkit uses `"command"`)
|
|
144
|
+
- `hooks[].type`: `"command"`, `"http"`, `"prompt"`, `"agent"`, or `"mcp_tool"` (ai-toolkit uses `"command"`)
|
|
141
145
|
- `hooks[].command`: path to script using `$HOME/.softspark/ai-toolkit/hooks/` prefix (for `type: command`)
|
|
142
146
|
|
|
143
147
|
Optional fields (read from Claude Code docs, not emitted by ai-toolkit by default):
|
|
@@ -61,7 +61,7 @@ Answer these three questions:
|
|
|
61
61
|
|
|
62
62
|
## Step 4: Select Recovery Action
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
Choose the **smallest recovery action** and apply the smallest possible fix — do not restart from scratch unless absolutely necessary:
|
|
65
65
|
|
|
66
66
|
| Pattern | Recovery Action |
|
|
67
67
|
|---------|----------------|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"last_run": "2026-04-
|
|
2
|
+
"last_run": "2026-04-24T08:04:02Z",
|
|
3
3
|
"schema_version": 1,
|
|
4
4
|
"tools": {
|
|
5
5
|
"aider": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"augment": {
|
|
27
|
-
"docs_hash": "
|
|
27
|
+
"docs_hash": "5754553b0e436fd7",
|
|
28
28
|
"headings": [
|
|
29
29
|
"Agent",
|
|
30
30
|
"Code Completions",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"claude-code": {
|
|
60
|
-
"docs_hash": "
|
|
60
|
+
"docs_hash": "0c111b1b1f28960d",
|
|
61
61
|
"headings": [
|
|
62
62
|
"Claude Code overview",
|
|
63
63
|
"\u200bGet started",
|
|
@@ -102,10 +102,10 @@
|
|
|
102
102
|
"slash command": true,
|
|
103
103
|
"sub-agent": true
|
|
104
104
|
},
|
|
105
|
-
"version": "2.1.
|
|
105
|
+
"version": "2.1.119 (Claude Code)"
|
|
106
106
|
},
|
|
107
107
|
"cline": {
|
|
108
|
-
"docs_hash": "
|
|
108
|
+
"docs_hash": "17c24d27216f70cf",
|
|
109
109
|
"headings": [
|
|
110
110
|
"Cline CLI",
|
|
111
111
|
"Cline Documentation",
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
}
|
|
136
136
|
},
|
|
137
137
|
"codex-cli": {
|
|
138
|
-
"docs_hash": "
|
|
138
|
+
"docs_hash": "7a7f5cdf9411c5e2",
|
|
139
139
|
"headings": [
|
|
140
140
|
"About",
|
|
141
141
|
"Contributing",
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
"Packages 0",
|
|
155
155
|
"Provide feedback",
|
|
156
156
|
"Quickstart",
|
|
157
|
-
"Releases
|
|
157
|
+
"Releases 735",
|
|
158
158
|
"Repository files navigation",
|
|
159
159
|
"Resources",
|
|
160
160
|
"Saved searches",
|
|
@@ -184,10 +184,10 @@
|
|
|
184
184
|
"mcp_servers": false,
|
|
185
185
|
"sandbox": true
|
|
186
186
|
},
|
|
187
|
-
"version": "codex-cli 0.
|
|
187
|
+
"version": "codex-cli 0.124.0"
|
|
188
188
|
},
|
|
189
189
|
"cursor": {
|
|
190
|
-
"docs_hash": "
|
|
190
|
+
"docs_hash": "a94b8e8f685b0831",
|
|
191
191
|
"headings": [],
|
|
192
192
|
"markers": {
|
|
193
193
|
".cursor/rules": false,
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
}
|
|
204
204
|
},
|
|
205
205
|
"gemini-cli": {
|
|
206
|
-
"docs_hash": "
|
|
206
|
+
"docs_hash": "9415ea08e344013d",
|
|
207
207
|
"headings": [
|
|
208
208
|
"Breadcrumbs",
|
|
209
209
|
"Directory actions",
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
}
|
|
243
243
|
},
|
|
244
244
|
"github-copilot": {
|
|
245
|
-
"docs_hash": "
|
|
245
|
+
"docs_hash": "8bcd2db0db925807",
|
|
246
246
|
"headings": [
|
|
247
247
|
"About Copilot auto model selection",
|
|
248
248
|
"About Copilot integrations",
|
|
@@ -290,7 +290,7 @@
|
|
|
290
290
|
}
|
|
291
291
|
},
|
|
292
292
|
"opencode": {
|
|
293
|
-
"docs_hash": "
|
|
293
|
+
"docs_hash": "760fbf466036892f",
|
|
294
294
|
"headings": [
|
|
295
295
|
"Add features",
|
|
296
296
|
"Ask questions",
|
|
@@ -351,7 +351,7 @@
|
|
|
351
351
|
}
|
|
352
352
|
},
|
|
353
353
|
"windsurf": {
|
|
354
|
-
"docs_hash": "
|
|
354
|
+
"docs_hash": "81d0aafbf97b18c8",
|
|
355
355
|
"headings": [
|
|
356
356
|
"Advanced",
|
|
357
357
|
"App Deploys",
|
package/bin/ai-toolkit.js
CHANGED
|
@@ -74,7 +74,7 @@ const COMMANDS = {
|
|
|
74
74
|
benchmark: 'Benchmark toolkit (--my-config to compare your setup vs defaults vs ecosystem)',
|
|
75
75
|
'benchmark-ecosystem': 'Generate ecosystem benchmark snapshot (GitHub metadata + offline fallback)',
|
|
76
76
|
evaluate: 'Run skill evaluation suite',
|
|
77
|
-
stats: 'Show skill usage statistics (--
|
|
77
|
+
stats: 'Show skill usage statistics (--summary for product telemetry, --reset to clear)',
|
|
78
78
|
create: 'Scaffold new skill from template (e.g. create skill my-lint --template=linter)',
|
|
79
79
|
mcp: 'Manage MCP templates and install native editor MCP configs',
|
|
80
80
|
config: 'Manage config inheritance (validate, diff, init, create-base, check)',
|
|
@@ -280,6 +280,10 @@ function showHelp() {
|
|
|
280
280
|
console.log(' Remove from .mcp.json or native editor configs');
|
|
281
281
|
console.log('\nOptions for doctor:');
|
|
282
282
|
console.log(' --fix Auto-repair detected issues');
|
|
283
|
+
console.log('\nOptions for stats:');
|
|
284
|
+
console.log(' --summary Show aggregated product telemetry (usage coverage, top skills, unused catalog)');
|
|
285
|
+
console.log(' --json Emit raw stats, or machine-readable telemetry when combined with --summary');
|
|
286
|
+
console.log(' --reset Clear local usage stats');
|
|
283
287
|
console.log('\nOptions for eject:');
|
|
284
288
|
console.log(' [target-dir] Target directory (default: current directory)');
|
|
285
289
|
console.log('');
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
title: "CLI Reference"
|
|
3
3
|
category: reference
|
|
4
4
|
service: ai-toolkit
|
|
5
|
-
tags: [cli, commands, reference, install, update, plugin, mcp]
|
|
5
|
+
tags: [cli, commands, reference, install, update, plugin, mcp, telemetry]
|
|
6
6
|
created: "2026-04-13"
|
|
7
|
-
last_updated: "2026-04-
|
|
7
|
+
last_updated: "2026-04-24"
|
|
8
8
|
description: "Complete CLI reference for all ai-toolkit commands, options, and flags."
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -111,7 +111,7 @@ Usage: ai-toolkit <command> [options]
|
|
|
111
111
|
|
|
112
112
|
| Command | Description |
|
|
113
113
|
|---------|-------------|
|
|
114
|
-
| `stats` | Show skill usage statistics (`--reset` to clear, `--json` for raw output) |
|
|
114
|
+
| `stats` | Show skill usage statistics (`--summary` for product telemetry, `--reset` to clear, `--json` for raw output) |
|
|
115
115
|
| `benchmark --my-config` | Compare your config vs defaults vs ecosystem |
|
|
116
116
|
| `benchmark-ecosystem` | Generate ecosystem benchmark snapshot |
|
|
117
117
|
| `create skill <name>` | Scaffold new skill from template (`--template=linter\|reviewer\|generator\|workflow\|knowledge`) |
|
|
@@ -119,6 +119,17 @@ Usage: ai-toolkit <command> [options]
|
|
|
119
119
|
| `compile-slm` | Compile toolkit into minimal SLM system prompt (`--budget`, `--model-size`, `--dry-run`) |
|
|
120
120
|
| `evaluate` | Run skill evaluation suite |
|
|
121
121
|
|
|
122
|
+
### `stats`
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
ai-toolkit stats # table of local skill usage
|
|
126
|
+
ai-toolkit stats --summary # product telemetry summary
|
|
127
|
+
ai-toolkit stats --summary --json # machine-readable telemetry
|
|
128
|
+
ai-toolkit stats --reset # clear local stats
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`--summary` reports total invocations, unique skills used, catalog coverage, unused catalog skills, active skills in the last 7 days, and top skills. Data stays local in `~/.softspark/ai-toolkit/stats.json`.
|
|
132
|
+
|
|
122
133
|
## Install / Update Options
|
|
123
134
|
|
|
124
135
|
```bash
|
|
@@ -13,7 +13,7 @@ tags:
|
|
|
13
13
|
doc_type: plan
|
|
14
14
|
status: completed
|
|
15
15
|
created: "2026-04-07"
|
|
16
|
-
last_updated: "2026-04-
|
|
16
|
+
last_updated: "2026-04-24"
|
|
17
17
|
completion: "100%"
|
|
18
18
|
description: "Implementation plan for features identified from competitive analysis of everything-claude-code and claude-mem. Focus on learning system, language rules, advanced hooks, MCP templates, and rag-mcp integration. COMPLETED: 8/9 features shipped (1 skipped). See kb/reference/ for permanent documentation."
|
|
19
19
|
---
|
|
@@ -142,8 +142,8 @@ app/rules/
|
|
|
142
142
|
- [x] 13 languages × 5 rule files created (70 files: 13 dirs × 5 + 5 common)
|
|
143
143
|
- [x] `ai-toolkit install --local` auto-detects language and injects rules (two-phase: marker files + extension scan)
|
|
144
144
|
- [x] Manual override: `ai-toolkit install --local --lang typescript` (with aliases: go→golang, c++→cpp, cs→csharp)
|
|
145
|
-
- [
|
|
146
|
-
- [
|
|
145
|
+
- [x] validate.py checks rules format
|
|
146
|
+
- [x] Tests: dedicated `tests/test_rules.bats`
|
|
147
147
|
|
|
148
148
|
---
|
|
149
149
|
|
|
@@ -497,7 +497,7 @@ context: fork
|
|
|
497
497
|
- [x] `/council` invocable
|
|
498
498
|
- [x] 4 perspectives generated
|
|
499
499
|
- [x] Structured output with recommendation
|
|
500
|
-
- [
|
|
500
|
+
- [x] Tests: dedicated council skill contract
|
|
501
501
|
|
|
502
502
|
---
|
|
503
503
|
|
|
@@ -516,7 +516,7 @@ context: fork
|
|
|
516
516
|
**Success Criteria:**
|
|
517
517
|
- [x] Skill auto-loads when writing docs/content
|
|
518
518
|
- [x] Anti-trope list prevents generic LLM rhetoric
|
|
519
|
-
- [
|
|
519
|
+
- [x] Tests: dedicated brand-voice skill contract
|
|
520
520
|
|
|
521
521
|
---
|
|
522
522
|
|
|
@@ -538,7 +538,7 @@ context: fork
|
|
|
538
538
|
- [x] `/introspect` invocable when agent is stuck
|
|
539
539
|
- [x] Classifies failure pattern
|
|
540
540
|
- [x] Suggests recovery action
|
|
541
|
-
- [
|
|
541
|
+
- [x] Tests: dedicated introspect skill contract
|
|
542
542
|
|
|
543
543
|
---
|
|
544
544
|
|
|
@@ -660,9 +660,9 @@ npx @softspark/ai-toolkit inject-hook ./rag-mcp-hooks.json # NEW
|
|
|
660
660
|
|
|
661
661
|
All major features shipped. Outstanding items:
|
|
662
662
|
|
|
663
|
-
1. [
|
|
664
|
-
2. [
|
|
665
|
-
3. [
|
|
663
|
+
1. [x] `validate.py` checks rules format (1.1)
|
|
664
|
+
2. [x] Dedicated `test_rules` test file exists (1.1)
|
|
665
|
+
3. [x] Dedicated tests exist for council, brand-voice, introspect skills (3.1-3.3)
|
|
666
666
|
4. [x] `observe-session.sh` lives in rag-mcp (consumer), not ai-toolkit — by design
|
|
667
667
|
|
|
668
668
|
---
|
|
@@ -3,9 +3,9 @@ title: "Hooks Catalog"
|
|
|
3
3
|
category: reference
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [hooks, quality, safety, enforcement, settings.json]
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.5.0"
|
|
7
7
|
created: "2026-03-27"
|
|
8
|
-
last_updated: "2026-04-
|
|
8
|
+
last_updated: "2026-04-24"
|
|
9
9
|
description: "Complete reference of all ai-toolkit hooks: events, scripts, installation, and runtime behavior."
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -15,6 +15,12 @@ description: "Complete reference of all ai-toolkit hooks: events, scripts, insta
|
|
|
15
15
|
|
|
16
16
|
ai-toolkit provides 21 global hook entries across 12 lifecycle events that enforce quality, safety, and workflow rules across all Claude Code sessions. Hooks are merged into `~/.claude/settings.json` on install, with logic in standalone scripts at `~/.softspark/ai-toolkit/hooks/`.
|
|
17
17
|
|
|
18
|
+
## Supported Surface
|
|
19
|
+
|
|
20
|
+
`scripts/validate.py` validates both event names and handler shapes before release. The accepted lifecycle surface includes `PostToolUseFailure`, `PostToolBatch`, and `UserPromptExpansion` in addition to the installed ai-toolkit events below.
|
|
21
|
+
|
|
22
|
+
Supported handler types are `command`, `http`, `prompt`, `agent`, and `mcp_tool`. ai-toolkit ships command hooks by default; non-command handlers are validated so external consumers can safely inject richer hook definitions through `inject-hook`.
|
|
23
|
+
|
|
18
24
|
## Installation
|
|
19
25
|
|
|
20
26
|
```bash
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Windows Support"
|
|
3
|
+
category: reference
|
|
4
|
+
service: ai-toolkit
|
|
5
|
+
tags: [windows, wsl, install, dependencies, hooks]
|
|
6
|
+
created: "2026-04-24"
|
|
7
|
+
last_updated: "2026-04-24"
|
|
8
|
+
description: "Windows support model for ai-toolkit: WSL, Git Bash, dependency detection, and hook runtime constraints."
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Windows Support
|
|
12
|
+
|
|
13
|
+
ai-toolkit supports Windows through two practical modes:
|
|
14
|
+
|
|
15
|
+
1. **WSL recommended** — best compatibility for Bash hooks, POSIX paths, symlinks, and editor configs.
|
|
16
|
+
2. **Native Windows with Git Bash** — supported for CLI usage when Bash is available on `PATH`.
|
|
17
|
+
|
|
18
|
+
## Dependency Detection
|
|
19
|
+
|
|
20
|
+
`scripts/check_deps.py` now emits install hints for Windows package managers:
|
|
21
|
+
|
|
22
|
+
| Manager | Command Prefix |
|
|
23
|
+
|---------|----------------|
|
|
24
|
+
| winget | `winget install` |
|
|
25
|
+
| Chocolatey | `choco install -y` |
|
|
26
|
+
| Scoop | `scoop install` |
|
|
27
|
+
|
|
28
|
+
Required dependency package IDs:
|
|
29
|
+
|
|
30
|
+
| Dependency | winget | Chocolatey | Scoop |
|
|
31
|
+
|------------|--------|------------|-------|
|
|
32
|
+
| Python 3 | `Python.Python.3` | `python` | `python` |
|
|
33
|
+
| Git | `Git.Git` | `git` | `git` |
|
|
34
|
+
| Node.js | `OpenJS.NodeJS` | `nodejs` | `nodejs` |
|
|
35
|
+
|
|
36
|
+
## Hook Runtime
|
|
37
|
+
|
|
38
|
+
ai-toolkit hooks are Bash scripts. On Windows, use WSL or Git Bash so Claude Code can execute `~/.softspark/ai-toolkit/hooks/*.sh`.
|
|
39
|
+
|
|
40
|
+
Cross-platform hooks should keep the Bash entrypoint small and delegate complex work to Python or Node when Windows behavior diverges.
|
|
41
|
+
|
|
42
|
+
## Verification
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
ai-toolkit doctor
|
|
46
|
+
python3 scripts/check_deps.py
|
|
47
|
+
python3 scripts/validate.py
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The Windows support contract is covered by `tests/test_windows_support.bats`.
|
package/llms-full.txt
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
- [Supported Tools Registry](kb/reference/supported-tools-registry.md)
|
|
59
59
|
- [Config Sync](kb/reference/sync.md)
|
|
60
60
|
- [Unique Features & Differentiators](kb/reference/unique-features.md)
|
|
61
|
+
- [Windows Support](kb/reference/windows-support.md)
|
|
61
62
|
- [Troubleshooting](kb/troubleshooting/README.md)
|
|
62
63
|
|
|
63
64
|
## Skills
|
|
@@ -6208,9 +6209,9 @@ The reference benchmark set is intentionally curated:
|
|
|
6208
6209
|
title: "CLI Reference"
|
|
6209
6210
|
category: reference
|
|
6210
6211
|
service: ai-toolkit
|
|
6211
|
-
tags: [cli, commands, reference, install, update, plugin, mcp]
|
|
6212
|
+
tags: [cli, commands, reference, install, update, plugin, mcp, telemetry]
|
|
6212
6213
|
created: "2026-04-13"
|
|
6213
|
-
last_updated: "2026-04-
|
|
6214
|
+
last_updated: "2026-04-24"
|
|
6214
6215
|
description: "Complete CLI reference for all ai-toolkit commands, options, and flags."
|
|
6215
6216
|
---
|
|
6216
6217
|
|
|
@@ -6317,7 +6318,7 @@ Usage: ai-toolkit <command> [options]
|
|
|
6317
6318
|
|
|
6318
6319
|
| Command | Description |
|
|
6319
6320
|
|---------|-------------|
|
|
6320
|
-
| `stats` | Show skill usage statistics (`--reset` to clear, `--json` for raw output) |
|
|
6321
|
+
| `stats` | Show skill usage statistics (`--summary` for product telemetry, `--reset` to clear, `--json` for raw output) |
|
|
6321
6322
|
| `benchmark --my-config` | Compare your config vs defaults vs ecosystem |
|
|
6322
6323
|
| `benchmark-ecosystem` | Generate ecosystem benchmark snapshot |
|
|
6323
6324
|
| `create skill <name>` | Scaffold new skill from template (`--template=linter\|reviewer\|generator\|workflow\|knowledge`) |
|
|
@@ -6325,6 +6326,17 @@ Usage: ai-toolkit <command> [options]
|
|
|
6325
6326
|
| `compile-slm` | Compile toolkit into minimal SLM system prompt (`--budget`, `--model-size`, `--dry-run`) |
|
|
6326
6327
|
| `evaluate` | Run skill evaluation suite |
|
|
6327
6328
|
|
|
6329
|
+
### `stats`
|
|
6330
|
+
|
|
6331
|
+
```bash
|
|
6332
|
+
ai-toolkit stats # table of local skill usage
|
|
6333
|
+
ai-toolkit stats --summary # product telemetry summary
|
|
6334
|
+
ai-toolkit stats --summary --json # machine-readable telemetry
|
|
6335
|
+
ai-toolkit stats --reset # clear local stats
|
|
6336
|
+
```
|
|
6337
|
+
|
|
6338
|
+
`--summary` reports total invocations, unique skills used, catalog coverage, unused catalog skills, active skills in the last 7 days, and top skills. Data stays local in `~/.softspark/ai-toolkit/stats.json`.
|
|
6339
|
+
|
|
6328
6340
|
## Install / Update Options
|
|
6329
6341
|
|
|
6330
6342
|
```bash
|
|
@@ -6538,7 +6550,7 @@ tags:
|
|
|
6538
6550
|
doc_type: plan
|
|
6539
6551
|
status: completed
|
|
6540
6552
|
created: "2026-04-07"
|
|
6541
|
-
last_updated: "2026-04-
|
|
6553
|
+
last_updated: "2026-04-24"
|
|
6542
6554
|
completion: "100%"
|
|
6543
6555
|
description: "Implementation plan for features identified from competitive analysis of everything-claude-code and claude-mem. Focus on learning system, language rules, advanced hooks, MCP templates, and rag-mcp integration. COMPLETED: 8/9 features shipped (1 skipped). See kb/reference/ for permanent documentation."
|
|
6544
6556
|
---
|
|
@@ -6667,8 +6679,8 @@ app/rules/
|
|
|
6667
6679
|
- [x] 13 languages × 5 rule files created (70 files: 13 dirs × 5 + 5 common)
|
|
6668
6680
|
- [x] `ai-toolkit install --local` auto-detects language and injects rules (two-phase: marker files + extension scan)
|
|
6669
6681
|
- [x] Manual override: `ai-toolkit install --local --lang typescript` (with aliases: go→golang, c++→cpp, cs→csharp)
|
|
6670
|
-
- [
|
|
6671
|
-
- [
|
|
6682
|
+
- [x] validate.py checks rules format
|
|
6683
|
+
- [x] Tests: dedicated `tests/test_rules.bats`
|
|
6672
6684
|
|
|
6673
6685
|
---
|
|
6674
6686
|
|
|
@@ -7022,7 +7034,7 @@ context: fork
|
|
|
7022
7034
|
- [x] `/council` invocable
|
|
7023
7035
|
- [x] 4 perspectives generated
|
|
7024
7036
|
- [x] Structured output with recommendation
|
|
7025
|
-
- [
|
|
7037
|
+
- [x] Tests: dedicated council skill contract
|
|
7026
7038
|
|
|
7027
7039
|
---
|
|
7028
7040
|
|
|
@@ -7041,7 +7053,7 @@ context: fork
|
|
|
7041
7053
|
**Success Criteria:**
|
|
7042
7054
|
- [x] Skill auto-loads when writing docs/content
|
|
7043
7055
|
- [x] Anti-trope list prevents generic LLM rhetoric
|
|
7044
|
-
- [
|
|
7056
|
+
- [x] Tests: dedicated brand-voice skill contract
|
|
7045
7057
|
|
|
7046
7058
|
---
|
|
7047
7059
|
|
|
@@ -7063,7 +7075,7 @@ context: fork
|
|
|
7063
7075
|
- [x] `/introspect` invocable when agent is stuck
|
|
7064
7076
|
- [x] Classifies failure pattern
|
|
7065
7077
|
- [x] Suggests recovery action
|
|
7066
|
-
- [
|
|
7078
|
+
- [x] Tests: dedicated introspect skill contract
|
|
7067
7079
|
|
|
7068
7080
|
---
|
|
7069
7081
|
|
|
@@ -7185,9 +7197,9 @@ npx @softspark/ai-toolkit inject-hook ./rag-mcp-hooks.json # NEW
|
|
|
7185
7197
|
|
|
7186
7198
|
All major features shipped. Outstanding items:
|
|
7187
7199
|
|
|
7188
|
-
1. [
|
|
7189
|
-
2. [
|
|
7190
|
-
3. [
|
|
7200
|
+
1. [x] `validate.py` checks rules format (1.1)
|
|
7201
|
+
2. [x] Dedicated `test_rules` test file exists (1.1)
|
|
7202
|
+
3. [x] Dedicated tests exist for council, brand-voice, introspect skills (3.1-3.3)
|
|
7191
7203
|
4. [x] `observe-session.sh` lives in rag-mcp (consumer), not ai-toolkit — by design
|
|
7192
7204
|
|
|
7193
7205
|
---
|
|
@@ -8159,9 +8171,9 @@ title: "Hooks Catalog"
|
|
|
8159
8171
|
category: reference
|
|
8160
8172
|
service: ai-toolkit
|
|
8161
8173
|
tags: [hooks, quality, safety, enforcement, settings.json]
|
|
8162
|
-
version: "1.
|
|
8174
|
+
version: "1.5.0"
|
|
8163
8175
|
created: "2026-03-27"
|
|
8164
|
-
last_updated: "2026-04-
|
|
8176
|
+
last_updated: "2026-04-24"
|
|
8165
8177
|
description: "Complete reference of all ai-toolkit hooks: events, scripts, installation, and runtime behavior."
|
|
8166
8178
|
---
|
|
8167
8179
|
|
|
@@ -8171,6 +8183,12 @@ description: "Complete reference of all ai-toolkit hooks: events, scripts, insta
|
|
|
8171
8183
|
|
|
8172
8184
|
ai-toolkit provides 21 global hook entries across 12 lifecycle events that enforce quality, safety, and workflow rules across all Claude Code sessions. Hooks are merged into `~/.claude/settings.json` on install, with logic in standalone scripts at `~/.softspark/ai-toolkit/hooks/`.
|
|
8173
8185
|
|
|
8186
|
+
## Supported Surface
|
|
8187
|
+
|
|
8188
|
+
`scripts/validate.py` validates both event names and handler shapes before release. The accepted lifecycle surface includes `PostToolUseFailure`, `PostToolBatch`, and `UserPromptExpansion` in addition to the installed ai-toolkit events below.
|
|
8189
|
+
|
|
8190
|
+
Supported handler types are `command`, `http`, `prompt`, `agent`, and `mcp_tool`. ai-toolkit ships command hooks by default; non-command handlers are validated so external consumers can safely inject richer hook definitions through `inject-hook`.
|
|
8191
|
+
|
|
8174
8192
|
## Installation
|
|
8175
8193
|
|
|
8176
8194
|
```bash
|
|
@@ -11326,6 +11344,61 @@ Agents follow a research-before-action protocol enforced via rules:
|
|
|
11326
11344
|
|
|
11327
11345
|
---
|
|
11328
11346
|
|
|
11347
|
+
## kb/reference/windows-support.md
|
|
11348
|
+
|
|
11349
|
+
---
|
|
11350
|
+
title: "Windows Support"
|
|
11351
|
+
category: reference
|
|
11352
|
+
service: ai-toolkit
|
|
11353
|
+
tags: [windows, wsl, install, dependencies, hooks]
|
|
11354
|
+
created: "2026-04-24"
|
|
11355
|
+
last_updated: "2026-04-24"
|
|
11356
|
+
description: "Windows support model for ai-toolkit: WSL, Git Bash, dependency detection, and hook runtime constraints."
|
|
11357
|
+
---
|
|
11358
|
+
|
|
11359
|
+
# Windows Support
|
|
11360
|
+
|
|
11361
|
+
ai-toolkit supports Windows through two practical modes:
|
|
11362
|
+
|
|
11363
|
+
1. **WSL recommended** — best compatibility for Bash hooks, POSIX paths, symlinks, and editor configs.
|
|
11364
|
+
2. **Native Windows with Git Bash** — supported for CLI usage when Bash is available on `PATH`.
|
|
11365
|
+
|
|
11366
|
+
## Dependency Detection
|
|
11367
|
+
|
|
11368
|
+
`scripts/check_deps.py` now emits install hints for Windows package managers:
|
|
11369
|
+
|
|
11370
|
+
| Manager | Command Prefix |
|
|
11371
|
+
|---------|----------------|
|
|
11372
|
+
| winget | `winget install` |
|
|
11373
|
+
| Chocolatey | `choco install -y` |
|
|
11374
|
+
| Scoop | `scoop install` |
|
|
11375
|
+
|
|
11376
|
+
Required dependency package IDs:
|
|
11377
|
+
|
|
11378
|
+
| Dependency | winget | Chocolatey | Scoop |
|
|
11379
|
+
|------------|--------|------------|-------|
|
|
11380
|
+
| Python 3 | `Python.Python.3` | `python` | `python` |
|
|
11381
|
+
| Git | `Git.Git` | `git` | `git` |
|
|
11382
|
+
| Node.js | `OpenJS.NodeJS` | `nodejs` | `nodejs` |
|
|
11383
|
+
|
|
11384
|
+
## Hook Runtime
|
|
11385
|
+
|
|
11386
|
+
ai-toolkit hooks are Bash scripts. On Windows, use WSL or Git Bash so Claude Code can execute `~/.softspark/ai-toolkit/hooks/*.sh`.
|
|
11387
|
+
|
|
11388
|
+
Cross-platform hooks should keep the Bash entrypoint small and delegate complex work to Python or Node when Windows behavior diverges.
|
|
11389
|
+
|
|
11390
|
+
## Verification
|
|
11391
|
+
|
|
11392
|
+
```bash
|
|
11393
|
+
ai-toolkit doctor
|
|
11394
|
+
python3 scripts/check_deps.py
|
|
11395
|
+
python3 scripts/validate.py
|
|
11396
|
+
```
|
|
11397
|
+
|
|
11398
|
+
The Windows support contract is covered by `tests/test_windows_support.bats`.
|
|
11399
|
+
|
|
11400
|
+
---
|
|
11401
|
+
|
|
11329
11402
|
## kb/troubleshooting/README.md
|
|
11330
11403
|
|
|
11331
11404
|
---
|
package/llms.txt
CHANGED
|
@@ -58,4 +58,5 @@
|
|
|
58
58
|
- [Supported Tools Registry](kb/reference/supported-tools-registry.md)
|
|
59
59
|
- [Config Sync](kb/reference/sync.md)
|
|
60
60
|
- [Unique Features & Differentiators](kb/reference/unique-features.md)
|
|
61
|
+
- [Windows Support](kb/reference/windows-support.md)
|
|
61
62
|
- [Troubleshooting](kb/troubleshooting/README.md)
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "AI coding toolkit: 99 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",
|
|
@@ -62,7 +62,11 @@
|
|
|
62
62
|
"generate:codex-rules": "python3 scripts/generate_codex_rules.py .",
|
|
63
63
|
"generate:opencode-agents": "python3 scripts/generate_opencode_agents.py .",
|
|
64
64
|
"generate:opencode-commands": "python3 scripts/generate_opencode_commands.py .",
|
|
65
|
-
"generate:
|
|
65
|
+
"generate:cursor-mdc": "python3 scripts/generate_cursor_mdc.py .",
|
|
66
|
+
"generate:windsurf-rules": "python3 scripts/generate_windsurf_rules.py .",
|
|
67
|
+
"generate:roo-rules": "python3 scripts/generate_roo_rules.py .",
|
|
68
|
+
"generate:augment-rules": "python3 scripts/generate_augment_rules.py .",
|
|
69
|
+
"generate:all": "npm run generate:agents && npm run generate:codex-rules && npm run generate:opencode-agents && npm run generate:opencode-commands && 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:cline && npm run generate:roo && npm run generate:roo-rules && npm run generate:aider && npm run generate:augment-rules && npm run generate:llms"
|
|
66
70
|
},
|
|
67
71
|
"files": [
|
|
68
72
|
"bin/",
|
package/scripts/_common.py
CHANGED
|
@@ -165,6 +165,27 @@ def detect_os() -> dict[str, str]:
|
|
|
165
165
|
"install_cmd": install_cmd,
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
if system == "windows":
|
|
169
|
+
managers = (
|
|
170
|
+
("winget", "winget install"),
|
|
171
|
+
("choco", "choco install -y"),
|
|
172
|
+
("scoop", "scoop install"),
|
|
173
|
+
)
|
|
174
|
+
for manager, install_cmd in managers:
|
|
175
|
+
if shutil.which(manager):
|
|
176
|
+
return {
|
|
177
|
+
"os": "Windows",
|
|
178
|
+
"distro": platform.version(),
|
|
179
|
+
"pkg_manager": manager,
|
|
180
|
+
"install_cmd": install_cmd,
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
"os": "Windows",
|
|
184
|
+
"distro": platform.version(),
|
|
185
|
+
"pkg_manager": "none",
|
|
186
|
+
"install_cmd": "",
|
|
187
|
+
}
|
|
188
|
+
|
|
168
189
|
return {
|
|
169
190
|
"os": system,
|
|
170
191
|
"distro": "",
|
package/scripts/check_deps.py
CHANGED
|
@@ -37,6 +37,9 @@ REQUIRED = [
|
|
|
37
37
|
"pacman": "python3",
|
|
38
38
|
"apk": "python3",
|
|
39
39
|
"zypper": "python3",
|
|
40
|
+
"winget": "Python.Python.3",
|
|
41
|
+
"choco": "python",
|
|
42
|
+
"scoop": "python",
|
|
40
43
|
},
|
|
41
44
|
"reason": "All toolkit scripts run on Python 3 (stdlib only, no pip needed)",
|
|
42
45
|
},
|
|
@@ -50,6 +53,9 @@ REQUIRED = [
|
|
|
50
53
|
"pacman": "git",
|
|
51
54
|
"apk": "git",
|
|
52
55
|
"zypper": "git",
|
|
56
|
+
"winget": "Git.Git",
|
|
57
|
+
"choco": "git",
|
|
58
|
+
"scoop": "git",
|
|
53
59
|
},
|
|
54
60
|
"reason": "Version control — hooks, commits, PR workflows",
|
|
55
61
|
},
|
|
@@ -64,6 +70,9 @@ REQUIRED = [
|
|
|
64
70
|
"pacman": "nodejs",
|
|
65
71
|
"apk": "nodejs",
|
|
66
72
|
"zypper": "nodejs18",
|
|
73
|
+
"winget": "OpenJS.NodeJS",
|
|
74
|
+
"choco": "nodejs",
|
|
75
|
+
"scoop": "nodejs",
|
|
67
76
|
},
|
|
68
77
|
"reason": "CLI entry point (bin/ai-toolkit.js)",
|
|
69
78
|
},
|
|
@@ -80,6 +89,9 @@ OPTIONAL = [
|
|
|
80
89
|
"pacman": "sqlite",
|
|
81
90
|
"apk": "sqlite",
|
|
82
91
|
"zypper": "sqlite3",
|
|
92
|
+
"winget": "SQLite.SQLite",
|
|
93
|
+
"choco": "sqlite",
|
|
94
|
+
"scoop": "sqlite",
|
|
83
95
|
},
|
|
84
96
|
"reason": "Memory plugin pack (session persistence via SQLite + FTS5)",
|
|
85
97
|
},
|
|
@@ -93,6 +105,8 @@ OPTIONAL = [
|
|
|
93
105
|
"pacman": "bash-bats",
|
|
94
106
|
"apk": "bats",
|
|
95
107
|
"zypper": "bats",
|
|
108
|
+
"choco": "bats",
|
|
109
|
+
"scoop": "bats",
|
|
96
110
|
},
|
|
97
111
|
"reason": "Running toolkit test suite (npm test)",
|
|
98
112
|
},
|
package/scripts/stats.py
CHANGED
|
@@ -5,11 +5,13 @@ Reads ~/.softspark/ai-toolkit/stats.json (populated by track-usage.sh hook)
|
|
|
5
5
|
and displays a sorted table of skill invocations.
|
|
6
6
|
|
|
7
7
|
Options:
|
|
8
|
-
--reset
|
|
9
|
-
--json
|
|
8
|
+
--reset Clear all stats
|
|
9
|
+
--json Output raw JSON
|
|
10
|
+
--summary Output product telemetry summary
|
|
10
11
|
"""
|
|
11
12
|
from __future__ import annotations
|
|
12
13
|
|
|
14
|
+
from datetime import datetime, timedelta
|
|
13
15
|
import json
|
|
14
16
|
import sys
|
|
15
17
|
from pathlib import Path
|
|
@@ -18,14 +20,120 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
18
20
|
from paths import STATS_FILE as _STATS_FILE
|
|
19
21
|
|
|
20
22
|
STATS_FILE = _STATS_FILE
|
|
23
|
+
TOOLKIT_DIR = Path(__file__).resolve().parent.parent
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load_stats() -> dict:
|
|
27
|
+
"""Load the stats file, returning an empty dict when absent."""
|
|
28
|
+
if not STATS_FILE.is_file():
|
|
29
|
+
return {}
|
|
30
|
+
with open(STATS_FILE, encoding="utf-8") as f:
|
|
31
|
+
return json.load(f)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _aggregate_rows(data: dict) -> list[tuple[str, dict]]:
|
|
35
|
+
"""Normalize supported stats formats into sorted (name, info) rows."""
|
|
36
|
+
if not data:
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
# Handle both formats: {skill: {count, last_used}} and {loop_runs: [...]}
|
|
40
|
+
if "loop_runs" in data:
|
|
41
|
+
agg: dict[str, dict] = {}
|
|
42
|
+
for run in data.get("loop_runs", []):
|
|
43
|
+
cmd = run.get("command", "unknown")
|
|
44
|
+
iters = run.get("iterations", [])
|
|
45
|
+
if cmd not in agg:
|
|
46
|
+
agg[cmd] = {"count": 0, "last_used": "unknown"}
|
|
47
|
+
agg[cmd]["count"] += len(iters) if iters else 1
|
|
48
|
+
started = run.get("started_at", "")
|
|
49
|
+
if started > agg[cmd]["last_used"]:
|
|
50
|
+
agg[cmd]["last_used"] = started
|
|
51
|
+
return sorted(agg.items(), key=lambda x: x[1].get("count", 0), reverse=True)
|
|
52
|
+
|
|
53
|
+
skill_data = {k: v for k, v in data.items() if isinstance(v, dict)}
|
|
54
|
+
return sorted(skill_data.items(), key=lambda x: x[1].get("count", 0), reverse=True)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _catalog_skill_names() -> set[str]:
|
|
58
|
+
"""Return skill directory names from the installed toolkit catalog."""
|
|
59
|
+
skills_dir = TOOLKIT_DIR / "app" / "skills"
|
|
60
|
+
if not skills_dir.is_dir():
|
|
61
|
+
return set()
|
|
62
|
+
return {
|
|
63
|
+
p.name
|
|
64
|
+
for p in skills_dir.iterdir()
|
|
65
|
+
if p.is_dir() and not p.name.startswith("_") and (p / "SKILL.md").is_file()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _parse_datetime(value: str) -> datetime | None:
|
|
70
|
+
"""Parse stats timestamps emitted by toolkit hooks and loop runs."""
|
|
71
|
+
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%SZ"):
|
|
72
|
+
try:
|
|
73
|
+
return datetime.strptime(value[:19] + ("Z" if fmt.endswith("Z") else ""), fmt)
|
|
74
|
+
except ValueError:
|
|
75
|
+
continue
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _build_summary(rows: list[tuple[str, dict]]) -> dict:
|
|
80
|
+
"""Build machine-readable product telemetry from aggregated rows."""
|
|
81
|
+
catalog = _catalog_skill_names()
|
|
82
|
+
used_names = {name for name, _ in rows}
|
|
83
|
+
used_catalog = used_names & catalog if catalog else used_names
|
|
84
|
+
total = sum(int(info.get("count", 0) or 0) for _, info in rows)
|
|
85
|
+
|
|
86
|
+
cutoff = datetime.now() - timedelta(days=7)
|
|
87
|
+
active_7d = 0
|
|
88
|
+
for _, info in rows:
|
|
89
|
+
last_used = _parse_datetime(str(info.get("last_used", "")))
|
|
90
|
+
if last_used and last_used >= cutoff:
|
|
91
|
+
active_7d += 1
|
|
92
|
+
|
|
93
|
+
catalog_total = len(catalog)
|
|
94
|
+
unused = max(catalog_total - len(used_catalog), 0) if catalog_total else 0
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"totalInvocations": total,
|
|
98
|
+
"uniqueSkillsUsed": len(used_names),
|
|
99
|
+
"catalogSkillsTotal": catalog_total,
|
|
100
|
+
"unusedCatalogSkills": unused,
|
|
101
|
+
"catalogCoveragePct": round((len(used_catalog) / catalog_total) * 100, 1) if catalog_total else 0,
|
|
102
|
+
"activeSkills7d": active_7d,
|
|
103
|
+
"topSkills": [
|
|
104
|
+
{
|
|
105
|
+
"name": name,
|
|
106
|
+
"count": int(info.get("count", 0) or 0),
|
|
107
|
+
"lastUsed": info.get("last_used", "unknown"),
|
|
108
|
+
}
|
|
109
|
+
for name, info in rows[:5]
|
|
110
|
+
],
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _print_summary(summary: dict) -> None:
|
|
115
|
+
"""Print a compact human-readable product telemetry summary."""
|
|
116
|
+
print("Product Telemetry")
|
|
117
|
+
print("=================")
|
|
118
|
+
print(f"Total invocations: {summary['totalInvocations']}")
|
|
119
|
+
print(f"Unique skills used: {summary['uniqueSkillsUsed']}")
|
|
120
|
+
print(f"Catalog skills total: {summary['catalogSkillsTotal']}")
|
|
121
|
+
print(f"Unused catalog skills: {summary['unusedCatalogSkills']}")
|
|
122
|
+
print(f"Catalog coverage: {summary['catalogCoveragePct']}%")
|
|
123
|
+
print(f"Active skills in last 7 days: {summary['activeSkills7d']}")
|
|
124
|
+
if summary["topSkills"]:
|
|
125
|
+
print()
|
|
126
|
+
print("Top skills:")
|
|
127
|
+
for item in summary["topSkills"]:
|
|
128
|
+
print(f"- {item['name']}: {item['count']} ({item['lastUsed']})")
|
|
21
129
|
|
|
22
130
|
|
|
23
131
|
def main() -> None:
|
|
24
132
|
"""Display, export, or reset usage statistics."""
|
|
25
|
-
|
|
133
|
+
args = set(sys.argv[1:])
|
|
26
134
|
|
|
27
135
|
# --reset
|
|
28
|
-
if
|
|
136
|
+
if "--reset" in args:
|
|
29
137
|
if STATS_FILE.is_file():
|
|
30
138
|
STATS_FILE.unlink()
|
|
31
139
|
print("Stats reset.")
|
|
@@ -33,12 +141,20 @@ def main() -> None:
|
|
|
33
141
|
print("No stats file found.")
|
|
34
142
|
return
|
|
35
143
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
144
|
+
data = _load_stats()
|
|
145
|
+
rows = _aggregate_rows(data)
|
|
146
|
+
|
|
147
|
+
if "--summary" in args:
|
|
148
|
+
summary = _build_summary(rows)
|
|
149
|
+
if "--json" in args:
|
|
150
|
+
print(json.dumps(summary, indent=2))
|
|
40
151
|
else:
|
|
41
|
-
|
|
152
|
+
_print_summary(summary)
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
# --json
|
|
156
|
+
if "--json" in args:
|
|
157
|
+
print(STATS_FILE.read_text(encoding="utf-8") if STATS_FILE.is_file() else "{}")
|
|
42
158
|
return
|
|
43
159
|
|
|
44
160
|
# Default: pretty-print table
|
|
@@ -53,39 +169,10 @@ def main() -> None:
|
|
|
53
169
|
print("========================")
|
|
54
170
|
print()
|
|
55
171
|
|
|
56
|
-
|
|
57
|
-
data: dict = json.load(f)
|
|
58
|
-
|
|
59
|
-
if not data:
|
|
172
|
+
if not rows:
|
|
60
173
|
print("No invocations recorded.")
|
|
61
174
|
return
|
|
62
175
|
|
|
63
|
-
# Handle both formats: {skill: {count, last_used}} and {loop_runs: [...]}
|
|
64
|
-
if "loop_runs" in data:
|
|
65
|
-
runs = data["loop_runs"]
|
|
66
|
-
if not runs:
|
|
67
|
-
print("No invocations recorded.")
|
|
68
|
-
return
|
|
69
|
-
# Aggregate loop_runs by command
|
|
70
|
-
agg: dict[str, dict] = {}
|
|
71
|
-
for run in runs:
|
|
72
|
-
cmd = run.get("command", "unknown")
|
|
73
|
-
iters = run.get("iterations", [])
|
|
74
|
-
if cmd not in agg:
|
|
75
|
-
agg[cmd] = {"count": 0, "last_used": "unknown"}
|
|
76
|
-
agg[cmd]["count"] += len(iters) if iters else 1
|
|
77
|
-
started = run.get("started_at", "")
|
|
78
|
-
if started > agg[cmd]["last_used"]:
|
|
79
|
-
agg[cmd]["last_used"] = started
|
|
80
|
-
rows = sorted(agg.items(), key=lambda x: x[1]["count"], reverse=True)
|
|
81
|
-
else:
|
|
82
|
-
# Original format: {skill_name: {count, last_used}}
|
|
83
|
-
skill_data = {k: v for k, v in data.items() if isinstance(v, dict)}
|
|
84
|
-
if not skill_data:
|
|
85
|
-
print("No invocations recorded.")
|
|
86
|
-
return
|
|
87
|
-
rows = sorted(skill_data.items(), key=lambda x: x[1].get("count", 0), reverse=True)
|
|
88
|
-
|
|
89
176
|
print(f"{'Skill':<30} {'Count':>6} {'Last Used':<20}")
|
|
90
177
|
print("-" * 60)
|
|
91
178
|
for name, info in rows:
|
package/scripts/validate.py
CHANGED
|
@@ -42,9 +42,9 @@ VALID_HOOK_EVENTS = frozenset({
|
|
|
42
42
|
# Core lifecycle
|
|
43
43
|
"SessionStart", "SessionEnd", "UserPromptSubmit", "Notification",
|
|
44
44
|
# Tool lifecycle
|
|
45
|
-
"PreToolUse", "PostToolUse",
|
|
45
|
+
"PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch",
|
|
46
46
|
# Turn lifecycle
|
|
47
|
-
"Stop", "StopFailure",
|
|
47
|
+
"Stop", "StopFailure", "UserPromptExpansion",
|
|
48
48
|
# Subagent lifecycle
|
|
49
49
|
"SubagentStart", "SubagentStop",
|
|
50
50
|
# Compaction
|
|
@@ -61,11 +61,67 @@ VALID_HOOK_EVENTS = frozenset({
|
|
|
61
61
|
"Setup", "InstructionsLoaded",
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
+
VALID_HOOK_TYPES = frozenset({
|
|
65
|
+
"command",
|
|
66
|
+
"http",
|
|
67
|
+
"prompt",
|
|
68
|
+
"agent",
|
|
69
|
+
"mcp_tool",
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
HOOK_TYPE_EVENTS = {
|
|
73
|
+
"agent": frozenset({"Stop", "SubagentStop"}),
|
|
74
|
+
"prompt": frozenset({
|
|
75
|
+
"PreToolUse",
|
|
76
|
+
"PostToolUse",
|
|
77
|
+
"PostToolUseFailure",
|
|
78
|
+
"PostToolBatch",
|
|
79
|
+
"UserPromptSubmit",
|
|
80
|
+
"UserPromptExpansion",
|
|
81
|
+
"Stop",
|
|
82
|
+
"SubagentStop",
|
|
83
|
+
}),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
HOOK_REQUIRED_FIELDS = {
|
|
87
|
+
"command": ("command",),
|
|
88
|
+
"http": ("url",),
|
|
89
|
+
"prompt": ("prompt",),
|
|
90
|
+
"agent": ("agent",),
|
|
91
|
+
"mcp_tool": ("server", "tool", "arguments"),
|
|
92
|
+
}
|
|
93
|
+
|
|
64
94
|
VALID_KB_CATEGORIES = frozenset({
|
|
65
95
|
"reference", "howto", "procedures", "troubleshooting", "best-practices",
|
|
66
96
|
"planning",
|
|
67
97
|
})
|
|
68
98
|
|
|
99
|
+
VALID_RULE_CATEGORIES = frozenset({
|
|
100
|
+
"coding-style",
|
|
101
|
+
"testing",
|
|
102
|
+
"security",
|
|
103
|
+
"performance",
|
|
104
|
+
"git-workflow",
|
|
105
|
+
"patterns",
|
|
106
|
+
"frameworks",
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
COMMON_RULE_CATEGORIES = frozenset({
|
|
110
|
+
"coding-style",
|
|
111
|
+
"testing",
|
|
112
|
+
"security",
|
|
113
|
+
"performance",
|
|
114
|
+
"git-workflow",
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
LANGUAGE_RULE_CATEGORIES = frozenset({
|
|
118
|
+
"coding-style",
|
|
119
|
+
"testing",
|
|
120
|
+
"security",
|
|
121
|
+
"patterns",
|
|
122
|
+
"frameworks",
|
|
123
|
+
})
|
|
124
|
+
|
|
69
125
|
PLANNED_ASSETS = [
|
|
70
126
|
"app/.claude-plugin/plugin.json",
|
|
71
127
|
"scripts/doctor.py",
|
|
@@ -354,8 +410,49 @@ def validate_legacy_commands(tk_dir: Path, vr: ValidationResult) -> None:
|
|
|
354
410
|
print()
|
|
355
411
|
|
|
356
412
|
|
|
413
|
+
def _validate_hook_handler(event: str, hook: dict, vr: ValidationResult) -> None:
|
|
414
|
+
"""Validate one hooks.json handler object."""
|
|
415
|
+
hook_type = hook.get("type")
|
|
416
|
+
if not hook_type:
|
|
417
|
+
vr.error(f"{event}: hook entry missing type")
|
|
418
|
+
return
|
|
419
|
+
|
|
420
|
+
if hook_type not in VALID_HOOK_TYPES:
|
|
421
|
+
vr.error(f"Unsupported hook handler type '{hook_type}' for event {event}")
|
|
422
|
+
return
|
|
423
|
+
|
|
424
|
+
allowed_events = HOOK_TYPE_EVENTS.get(hook_type)
|
|
425
|
+
if allowed_events is not None and event not in allowed_events:
|
|
426
|
+
vr.error(f"Hook type '{hook_type}' is not supported for event {event}")
|
|
427
|
+
|
|
428
|
+
for field in HOOK_REQUIRED_FIELDS.get(hook_type, ()):
|
|
429
|
+
if field not in hook:
|
|
430
|
+
vr.error(f"{event}: hook type '{hook_type}' missing required field '{field}'")
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _validate_hook_entries(event: str, entries: object, vr: ValidationResult) -> None:
|
|
434
|
+
"""Validate hooks.json matcher entries for one event."""
|
|
435
|
+
if not isinstance(entries, list):
|
|
436
|
+
vr.error(f"{event}: expected list of hook matcher entries")
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
for index, entry in enumerate(entries):
|
|
440
|
+
if not isinstance(entry, dict):
|
|
441
|
+
vr.error(f"{event}[{index}]: expected hook matcher entry object")
|
|
442
|
+
continue
|
|
443
|
+
hooks = entry.get("hooks")
|
|
444
|
+
if not isinstance(hooks, list) or not hooks:
|
|
445
|
+
vr.error(f"{event}[{index}]: missing non-empty hooks list")
|
|
446
|
+
continue
|
|
447
|
+
for hook_index, hook in enumerate(hooks):
|
|
448
|
+
if not isinstance(hook, dict):
|
|
449
|
+
vr.error(f"{event}[{index}].hooks[{hook_index}]: expected hook object")
|
|
450
|
+
continue
|
|
451
|
+
_validate_hook_handler(event, hook, vr)
|
|
452
|
+
|
|
453
|
+
|
|
357
454
|
def validate_hook_events(tk_dir: Path, vr: ValidationResult) -> None:
|
|
358
|
-
"""Validate hook event names in hooks.json."""
|
|
455
|
+
"""Validate hook event names and handler shapes in hooks.json."""
|
|
359
456
|
print("## Hook Events")
|
|
360
457
|
hooks_file = tk_dir / "app" / "hooks.json"
|
|
361
458
|
|
|
@@ -373,9 +470,10 @@ def validate_hook_events(tk_dir: Path, vr: ValidationResult) -> None:
|
|
|
373
470
|
return
|
|
374
471
|
|
|
375
472
|
hooks = data.get("hooks", {})
|
|
376
|
-
for event in hooks:
|
|
473
|
+
for event, entries in hooks.items():
|
|
377
474
|
if event in VALID_HOOK_EVENTS:
|
|
378
475
|
print(f" OK: {event}")
|
|
476
|
+
_validate_hook_entries(event, entries, vr)
|
|
379
477
|
else:
|
|
380
478
|
vr.error(f"Unknown hook event: {event}")
|
|
381
479
|
|
|
@@ -386,6 +484,63 @@ def validate_hook_events(tk_dir: Path, vr: ValidationResult) -> None:
|
|
|
386
484
|
print()
|
|
387
485
|
|
|
388
486
|
|
|
487
|
+
def validate_language_rules(tk_dir: Path, vr: ValidationResult) -> None:
|
|
488
|
+
"""Validate structured language-rule directories under app/rules."""
|
|
489
|
+
print("## Language Rules")
|
|
490
|
+
rules_dir = tk_dir / "app" / "rules"
|
|
491
|
+
rule_count = 0
|
|
492
|
+
rule_errors = 0
|
|
493
|
+
|
|
494
|
+
if not rules_dir.is_dir():
|
|
495
|
+
vr.error("app/rules directory not found")
|
|
496
|
+
print()
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
for rule_dir in sorted(p for p in rules_dir.iterdir() if p.is_dir()):
|
|
500
|
+
language = rule_dir.name
|
|
501
|
+
expected = COMMON_RULE_CATEGORIES if language == "common" else LANGUAGE_RULE_CATEGORIES
|
|
502
|
+
seen: set[str] = set()
|
|
503
|
+
|
|
504
|
+
for rule_file in sorted(rule_dir.glob("*.md")):
|
|
505
|
+
rel = str(rule_file.relative_to(tk_dir))
|
|
506
|
+
rule_count += 1
|
|
507
|
+
if not _has_frontmatter(rule_file):
|
|
508
|
+
vr.error(f"{rel} - Missing YAML frontmatter")
|
|
509
|
+
rule_errors += 1
|
|
510
|
+
continue
|
|
511
|
+
|
|
512
|
+
fm_lines = _parse_frontmatter_lines(rule_file)
|
|
513
|
+
for field in ("language", "category", "version"):
|
|
514
|
+
if not _fm_has(fm_lines, field):
|
|
515
|
+
vr.error(f"{rel} - Missing required field: {field}")
|
|
516
|
+
rule_errors += 1
|
|
517
|
+
|
|
518
|
+
rule_language = _fm_field(fm_lines, "language").strip()
|
|
519
|
+
if rule_language and rule_language != language:
|
|
520
|
+
vr.error(f"{rel} language '{rule_language}' does not match directory '{language}'")
|
|
521
|
+
rule_errors += 1
|
|
522
|
+
|
|
523
|
+
category = _fm_field(fm_lines, "category").strip()
|
|
524
|
+
if category:
|
|
525
|
+
seen.add(category)
|
|
526
|
+
if category not in VALID_RULE_CATEGORIES:
|
|
527
|
+
vr.error(f"{rel} has invalid rule category '{category}'")
|
|
528
|
+
rule_errors += 1
|
|
529
|
+
if rule_file.stem != category:
|
|
530
|
+
vr.error(f"{rel} filename does not match category '{category}'")
|
|
531
|
+
rule_errors += 1
|
|
532
|
+
|
|
533
|
+
for category in sorted(expected - seen):
|
|
534
|
+
vr.error(f"app/rules/{language} missing required rule category: {category}")
|
|
535
|
+
rule_errors += 1
|
|
536
|
+
|
|
537
|
+
if rule_errors == 0:
|
|
538
|
+
print(f" OK: {rule_count} rule files validated")
|
|
539
|
+
else:
|
|
540
|
+
print(f" Found: {rule_count} rule files ({rule_errors} with errors)")
|
|
541
|
+
print()
|
|
542
|
+
|
|
543
|
+
|
|
389
544
|
def validate_planned_assets(tk_dir: Path, vr: ValidationResult) -> None:
|
|
390
545
|
"""Validate that planned assets exist and are non-empty."""
|
|
391
546
|
print("## Planned Assets")
|
|
@@ -781,6 +936,7 @@ def _run_all_checks(tk_dir: Path, vr: ValidationResult) -> tuple[int, int, str]:
|
|
|
781
936
|
skill_count = validate_skills(tk_dir, vr)
|
|
782
937
|
validate_legacy_commands(tk_dir, vr)
|
|
783
938
|
validate_hook_events(tk_dir, vr)
|
|
939
|
+
validate_language_rules(tk_dir, vr)
|
|
784
940
|
validate_planned_assets(tk_dir, vr)
|
|
785
941
|
validate_plugin_packs(tk_dir, vr)
|
|
786
942
|
validate_kb_documents(tk_dir, vr)
|