@softspark/ai-toolkit 4.26.0 → 4.27.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 +18 -0
- package/README.md +15 -11
- package/app/.claude-plugin/plugin.json +1 -1
- package/benchmarks/ecosystem-doctor-snapshot.json +8 -8
- package/kb/troubleshooting/README.md +5 -3
- package/kb/troubleshooting/plugin-double-load.md +97 -0
- package/llms-full.txt +108 -3
- package/llms.txt +1 -0
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/doctor.py +82 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v4.27.0 — Doctor sees the Claude app plugin (2026-08-21)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Doctor check 11: plugin double-load.** `ai-toolkit doctor` now reads the
|
|
15
|
+
Claude Code plugin registry (`~/.claude/plugins/installed_plugins.json`) and
|
|
16
|
+
`enabledPlugins` in `~/.claude/settings.json`. When an uploaded Claude app
|
|
17
|
+
plugin is active next to the global install, both feed the same session:
|
|
18
|
+
Claude Code merges plugin hooks with user hooks without deduplication, so every
|
|
19
|
+
toolkit hook fires twice per event and skills and agents load twice. The check
|
|
20
|
+
warns with the hook count, and `--fix` disables the plugin for Claude Code
|
|
21
|
+
while leaving the global install authoritative. Previously this passed as a
|
|
22
|
+
healthy install with no signal at all.
|
|
23
|
+
- `kb/troubleshooting/plugin-double-load.md` documents the symptom, the debug-log
|
|
24
|
+
evidence, and the fix.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
10
28
|
## v4.26.0 — Python floor is declared and enforced (2026-08-21)
|
|
11
29
|
|
|
12
30
|
### Added
|
package/README.md
CHANGED
|
@@ -6,19 +6,23 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](tests/)
|
|
10
10
|
|
|
11
|
-
## What's New in v4.
|
|
11
|
+
## What's New in v4.27.0
|
|
12
12
|
|
|
13
|
-
**v4.
|
|
13
|
+
**v4.27.0** teaches `doctor` about a collision it used to report as healthy:
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
- Uploading the Claude app plugin ZIP registers it under `~/.claude/plugins`,
|
|
16
|
+
which Claude Code reads too. The plugin then carries the same skills, agents,
|
|
17
|
+
and hooks as the global install.
|
|
18
|
+
- Claude Code merges plugin hooks with user hooks without deduplication, so
|
|
19
|
+
every toolkit hook fired twice per event and skills and agents loaded twice.
|
|
20
|
+
`doctor` printed `HEALTH CHECK PASSED` through all of it.
|
|
21
|
+
- New check 11 reports the collision with the hook count; `doctor --fix`
|
|
22
|
+
disables the plugin for Claude Code and leaves the global install
|
|
23
|
+
authoritative.
|
|
24
|
+
- `kb/troubleshooting/plugin-double-load.md` documents the debug-log evidence
|
|
25
|
+
and the verification steps.
|
|
22
26
|
|
|
23
27
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
24
28
|
|
|
@@ -188,7 +192,7 @@ ai-toolkit/
|
|
|
188
192
|
│ └── ARCHITECTURE.md # Full system design
|
|
189
193
|
├── kb/ # Reference docs, procedures, plans
|
|
190
194
|
├── scripts/ # Validation, install, evaluation scripts
|
|
191
|
-
├── tests/ # Bats and Python test suite (
|
|
195
|
+
├── tests/ # Bats and Python test suite (1645 tests)
|
|
192
196
|
└── CHANGELOG.md
|
|
193
197
|
```
|
|
194
198
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "ai-toolkit",
|
|
4
4
|
"displayName": "AI Toolkit",
|
|
5
5
|
"description": "Professional-grade engineering skills, agents, rules, and lifecycle guardrails for Claude Code, Claude Chat, and Cowork.",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.27.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "SoftSpark",
|
|
9
9
|
"url": "https://github.com/softspark"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"last_run": "2026-08-
|
|
2
|
+
"last_run": "2026-08-21T15:52:27Z",
|
|
3
3
|
"schema_version": 1,
|
|
4
4
|
"tools": {
|
|
5
5
|
"aider": {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
"claude-app": {
|
|
69
|
-
"docs_hash": "
|
|
69
|
+
"docs_hash": "cacc8b579baa5e86",
|
|
70
70
|
"headings": [
|
|
71
71
|
"Add global and folder instructions",
|
|
72
72
|
"Availability",
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
}
|
|
224
224
|
},
|
|
225
225
|
"codex-cli": {
|
|
226
|
-
"docs_hash": "
|
|
226
|
+
"docs_hash": "df3b133739abfa24",
|
|
227
227
|
"headings": [
|
|
228
228
|
"API",
|
|
229
229
|
"API Reference",
|
|
@@ -397,7 +397,7 @@
|
|
|
397
397
|
}
|
|
398
398
|
},
|
|
399
399
|
"gemini-cli": {
|
|
400
|
-
"docs_hash": "
|
|
400
|
+
"docs_hash": "c66bb0647b70c7cf",
|
|
401
401
|
"headings": [
|
|
402
402
|
"Breadcrumbs",
|
|
403
403
|
"Directory actions",
|
|
@@ -438,7 +438,7 @@
|
|
|
438
438
|
"version": "0.55.1"
|
|
439
439
|
},
|
|
440
440
|
"github-copilot": {
|
|
441
|
-
"docs_hash": "
|
|
441
|
+
"docs_hash": "2e595a058e906644",
|
|
442
442
|
"headings": [
|
|
443
443
|
"About Copilot auto model selection",
|
|
444
444
|
"About Copilot automations",
|
|
@@ -475,7 +475,7 @@
|
|
|
475
475
|
}
|
|
476
476
|
},
|
|
477
477
|
"google-antigravity": {
|
|
478
|
-
"docs_hash": "
|
|
478
|
+
"docs_hash": "8cbf9aaffb6b57bf",
|
|
479
479
|
"headings": [],
|
|
480
480
|
"markers": {
|
|
481
481
|
"AGENTS.md": false,
|
|
@@ -502,7 +502,7 @@
|
|
|
502
502
|
}
|
|
503
503
|
},
|
|
504
504
|
"opencode": {
|
|
505
|
-
"docs_hash": "
|
|
505
|
+
"docs_hash": "3e9f45631e71a28c",
|
|
506
506
|
"headings": [
|
|
507
507
|
"Add features",
|
|
508
508
|
"Ask questions",
|
|
@@ -564,7 +564,7 @@
|
|
|
564
564
|
}
|
|
565
565
|
},
|
|
566
566
|
"windsurf": {
|
|
567
|
-
"docs_hash": "
|
|
567
|
+
"docs_hash": "b08c4f883c276129",
|
|
568
568
|
"headings": [
|
|
569
569
|
"Accounts",
|
|
570
570
|
"Advanced",
|
|
@@ -3,10 +3,12 @@ title: "Troubleshooting"
|
|
|
3
3
|
service: ai-toolkit
|
|
4
4
|
category: troubleshooting
|
|
5
5
|
tags: [troubleshooting, debugging]
|
|
6
|
-
last_updated: "2026-
|
|
7
|
-
description: "Section index for ai-toolkit troubleshooting
|
|
6
|
+
last_updated: "2026-08-21"
|
|
7
|
+
description: "Section index for ai-toolkit troubleshooting, covering the Claude app plugin double-load collision."
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Troubleshooting
|
|
11
11
|
|
|
12
|
-
Problem resolution guides.
|
|
12
|
+
Problem resolution guides.
|
|
13
|
+
|
|
14
|
+
- [Claude App Plugin Loads Twice in Claude Code](plugin-double-load.md) -- hooks fire twice after the plugin ZIP is uploaded next to a global install.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Claude App Plugin Loads Twice in Claude Code"
|
|
3
|
+
category: troubleshooting
|
|
4
|
+
service: ai-toolkit
|
|
5
|
+
tags: [plugin, claude-app, hooks, duplication, doctor]
|
|
6
|
+
created: "2026-08-21"
|
|
7
|
+
last_updated: "2026-08-21"
|
|
8
|
+
description: "Every toolkit hook fires twice and skills and agents load twice after the Claude app plugin ZIP is uploaded on a machine that already has the global install. Cause: the app registers the plugin under ~/.claude/plugins, which Claude Code also reads."
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Claude App Plugin Loads Twice in Claude Code
|
|
12
|
+
|
|
13
|
+
## Symptom
|
|
14
|
+
|
|
15
|
+
After uploading `ai-toolkit-claude-app.zip` through `Customize > Plugins`, Claude
|
|
16
|
+
Code sessions get slower and every hook side effect appears twice: duplicate rows
|
|
17
|
+
in `governance.log`, duplicate session state writes, two Stop gates per turn.
|
|
18
|
+
`ai-toolkit doctor` reports a healthy install because it only inspected
|
|
19
|
+
`~/.claude` and `app/plugins`.
|
|
20
|
+
|
|
21
|
+
## Cause
|
|
22
|
+
|
|
23
|
+
The Claude app writes uploaded plugins into
|
|
24
|
+
`~/.claude/plugins/marketplaces/local-desktop-app-uploads/` and sets
|
|
25
|
+
`enabledPlugins` in `~/.claude/settings.json`. Both paths belong to Claude Code
|
|
26
|
+
as well, so Claude Code loads the plugin on top of the global install. The
|
|
27
|
+
bundle carries the same catalog as `~/.claude`, and plugin hooks merge with user
|
|
28
|
+
hooks without deduplication.
|
|
29
|
+
|
|
30
|
+
Confirmed in a Claude Code debug log (`claude --debug -p ...`, then read
|
|
31
|
+
`~/.claude/debug/latest`):
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Loaded 111 unique skills (... user: 111 ...)
|
|
35
|
+
Total plugin skills loaded: 111 (0 duplicate/user-owned entries skipped)
|
|
36
|
+
Total plugin agents loaded: 44
|
|
37
|
+
Read manifest hooks for plugin ai-toolkit (enabled=true): ./claude-app/hooks/hooks.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The `skills` manifest field adds to the default `skills/` directory instead of
|
|
41
|
+
replacing it, so the plugin contributes its 109 catalog skills plus the 2
|
|
42
|
+
app-only rule skills.
|
|
43
|
+
|
|
44
|
+
## Diagnosis
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
ai-toolkit doctor
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Check 11 reports the collision:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
## 11. Plugin Double-Load
|
|
54
|
+
WARN: ai-toolkit@local-desktop-app-uploads is active next to the global install: 28 toolkit hooks fire twice per event and skills/agents load twice (run: ai-toolkit doctor --fix)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
To see the duplication directly, count hook invocations per source in a fresh
|
|
58
|
+
session log:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
grep -oE "[^\"' ]*hooks/[a-z0-9._-]+\.sh" ~/.claude/debug/latest | sort | uniq -c
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Fix
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
ai-toolkit doctor --fix
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
That sets the plugin to `false` in `enabledPlugins` and leaves the global
|
|
71
|
+
install authoritative. Claude Code then logs
|
|
72
|
+
`enabled=false; will NOT register, plugin is disabled`, and plugin skills and
|
|
73
|
+
agents drop to 0.
|
|
74
|
+
|
|
75
|
+
The global install is the richer surface for Claude Code: it delivers rules as
|
|
76
|
+
real files under `~/.claude/rules/`, which are always in context, while the
|
|
77
|
+
plugin exposes them as an `ai-toolkit-rules` skill the model has to load. The
|
|
78
|
+
plugin also has no `session-context.sh` hook.
|
|
79
|
+
|
|
80
|
+
Keep the plugin enabled only when Claude Code has no global install, for example
|
|
81
|
+
a machine that runs the Claude app alone. In that case the plugin is the single
|
|
82
|
+
source and check 11 stays quiet.
|
|
83
|
+
|
|
84
|
+
## Verification
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
claude --debug -p "ok"
|
|
88
|
+
grep -E "Total plugin (skills|agents) loaded|enabled=" ~/.claude/debug/latest
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Expect `Total plugin skills loaded: 0`, `Total plugin agents loaded: 0`, and the
|
|
92
|
+
`plugin is disabled` line.
|
|
93
|
+
|
|
94
|
+
## Related
|
|
95
|
+
|
|
96
|
+
- `kb/reference/global-install-model.md`
|
|
97
|
+
- `kb/procedures/maintenance-sop.md`
|
package/llms-full.txt
CHANGED
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
- [Unique Features & Differentiators](kb/reference/unique-features.md)
|
|
74
74
|
- [Windows Support](kb/reference/windows-support.md)
|
|
75
75
|
- [Troubleshooting](kb/troubleshooting/README.md)
|
|
76
|
+
- [Claude App Plugin Loads Twice in Claude Code](kb/troubleshooting/plugin-double-load.md)
|
|
76
77
|
|
|
77
78
|
## Skills
|
|
78
79
|
|
|
@@ -16130,13 +16131,117 @@ title: "Troubleshooting"
|
|
|
16130
16131
|
service: ai-toolkit
|
|
16131
16132
|
category: troubleshooting
|
|
16132
16133
|
tags: [troubleshooting, debugging]
|
|
16133
|
-
last_updated: "2026-
|
|
16134
|
-
description: "Section index for ai-toolkit troubleshooting
|
|
16134
|
+
last_updated: "2026-08-21"
|
|
16135
|
+
description: "Section index for ai-toolkit troubleshooting, covering the Claude app plugin double-load collision."
|
|
16135
16136
|
---
|
|
16136
16137
|
|
|
16137
16138
|
# Troubleshooting
|
|
16138
16139
|
|
|
16139
|
-
Problem resolution guides.
|
|
16140
|
+
Problem resolution guides.
|
|
16141
|
+
|
|
16142
|
+
- [Claude App Plugin Loads Twice in Claude Code](plugin-double-load.md) -- hooks fire twice after the plugin ZIP is uploaded next to a global install.
|
|
16143
|
+
|
|
16144
|
+
---
|
|
16145
|
+
|
|
16146
|
+
## kb/troubleshooting/plugin-double-load.md
|
|
16147
|
+
|
|
16148
|
+
---
|
|
16149
|
+
title: "Claude App Plugin Loads Twice in Claude Code"
|
|
16150
|
+
category: troubleshooting
|
|
16151
|
+
service: ai-toolkit
|
|
16152
|
+
tags: [plugin, claude-app, hooks, duplication, doctor]
|
|
16153
|
+
created: "2026-08-21"
|
|
16154
|
+
last_updated: "2026-08-21"
|
|
16155
|
+
description: "Every toolkit hook fires twice and skills and agents load twice after the Claude app plugin ZIP is uploaded on a machine that already has the global install. Cause: the app registers the plugin under ~/.claude/plugins, which Claude Code also reads."
|
|
16156
|
+
---
|
|
16157
|
+
|
|
16158
|
+
# Claude App Plugin Loads Twice in Claude Code
|
|
16159
|
+
|
|
16160
|
+
## Symptom
|
|
16161
|
+
|
|
16162
|
+
After uploading `ai-toolkit-claude-app.zip` through `Customize > Plugins`, Claude
|
|
16163
|
+
Code sessions get slower and every hook side effect appears twice: duplicate rows
|
|
16164
|
+
in `governance.log`, duplicate session state writes, two Stop gates per turn.
|
|
16165
|
+
`ai-toolkit doctor` reports a healthy install because it only inspected
|
|
16166
|
+
`~/.claude` and `app/plugins`.
|
|
16167
|
+
|
|
16168
|
+
## Cause
|
|
16169
|
+
|
|
16170
|
+
The Claude app writes uploaded plugins into
|
|
16171
|
+
`~/.claude/plugins/marketplaces/local-desktop-app-uploads/` and sets
|
|
16172
|
+
`enabledPlugins` in `~/.claude/settings.json`. Both paths belong to Claude Code
|
|
16173
|
+
as well, so Claude Code loads the plugin on top of the global install. The
|
|
16174
|
+
bundle carries the same catalog as `~/.claude`, and plugin hooks merge with user
|
|
16175
|
+
hooks without deduplication.
|
|
16176
|
+
|
|
16177
|
+
Confirmed in a Claude Code debug log (`claude --debug -p ...`, then read
|
|
16178
|
+
`~/.claude/debug/latest`):
|
|
16179
|
+
|
|
16180
|
+
```
|
|
16181
|
+
Loaded 111 unique skills (... user: 111 ...)
|
|
16182
|
+
Total plugin skills loaded: 111 (0 duplicate/user-owned entries skipped)
|
|
16183
|
+
Total plugin agents loaded: 44
|
|
16184
|
+
Read manifest hooks for plugin ai-toolkit (enabled=true): ./claude-app/hooks/hooks.json
|
|
16185
|
+
```
|
|
16186
|
+
|
|
16187
|
+
The `skills` manifest field adds to the default `skills/` directory instead of
|
|
16188
|
+
replacing it, so the plugin contributes its 109 catalog skills plus the 2
|
|
16189
|
+
app-only rule skills.
|
|
16190
|
+
|
|
16191
|
+
## Diagnosis
|
|
16192
|
+
|
|
16193
|
+
```bash
|
|
16194
|
+
ai-toolkit doctor
|
|
16195
|
+
```
|
|
16196
|
+
|
|
16197
|
+
Check 11 reports the collision:
|
|
16198
|
+
|
|
16199
|
+
```
|
|
16200
|
+
## 11. Plugin Double-Load
|
|
16201
|
+
WARN: ai-toolkit@local-desktop-app-uploads is active next to the global install: 28 toolkit hooks fire twice per event and skills/agents load twice (run: ai-toolkit doctor --fix)
|
|
16202
|
+
```
|
|
16203
|
+
|
|
16204
|
+
To see the duplication directly, count hook invocations per source in a fresh
|
|
16205
|
+
session log:
|
|
16206
|
+
|
|
16207
|
+
```bash
|
|
16208
|
+
grep -oE "[^\"' ]*hooks/[a-z0-9._-]+\.sh" ~/.claude/debug/latest | sort | uniq -c
|
|
16209
|
+
```
|
|
16210
|
+
|
|
16211
|
+
## Fix
|
|
16212
|
+
|
|
16213
|
+
```bash
|
|
16214
|
+
ai-toolkit doctor --fix
|
|
16215
|
+
```
|
|
16216
|
+
|
|
16217
|
+
That sets the plugin to `false` in `enabledPlugins` and leaves the global
|
|
16218
|
+
install authoritative. Claude Code then logs
|
|
16219
|
+
`enabled=false; will NOT register, plugin is disabled`, and plugin skills and
|
|
16220
|
+
agents drop to 0.
|
|
16221
|
+
|
|
16222
|
+
The global install is the richer surface for Claude Code: it delivers rules as
|
|
16223
|
+
real files under `~/.claude/rules/`, which are always in context, while the
|
|
16224
|
+
plugin exposes them as an `ai-toolkit-rules` skill the model has to load. The
|
|
16225
|
+
plugin also has no `session-context.sh` hook.
|
|
16226
|
+
|
|
16227
|
+
Keep the plugin enabled only when Claude Code has no global install, for example
|
|
16228
|
+
a machine that runs the Claude app alone. In that case the plugin is the single
|
|
16229
|
+
source and check 11 stays quiet.
|
|
16230
|
+
|
|
16231
|
+
## Verification
|
|
16232
|
+
|
|
16233
|
+
```bash
|
|
16234
|
+
claude --debug -p "ok"
|
|
16235
|
+
grep -E "Total plugin (skills|agents) loaded|enabled=" ~/.claude/debug/latest
|
|
16236
|
+
```
|
|
16237
|
+
|
|
16238
|
+
Expect `Total plugin skills loaded: 0`, `Total plugin agents loaded: 0`, and the
|
|
16239
|
+
`plugin is disabled` line.
|
|
16240
|
+
|
|
16241
|
+
## Related
|
|
16242
|
+
|
|
16243
|
+
- `kb/reference/global-install-model.md`
|
|
16244
|
+
- `kb/procedures/maintenance-sop.md`
|
|
16140
16245
|
|
|
16141
16246
|
---
|
|
16142
16247
|
|
package/llms.txt
CHANGED
|
@@ -73,3 +73,4 @@
|
|
|
73
73
|
- [Unique Features & Differentiators](kb/reference/unique-features.md)
|
|
74
74
|
- [Windows Support](kb/reference/windows-support.md)
|
|
75
75
|
- [Troubleshooting](kb/troubleshooting/README.md)
|
|
76
|
+
- [Claude App Plugin Loads Twice in Claude Code](kb/troubleshooting/plugin-double-load.md)
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.27.0",
|
|
4
4
|
"description": "AI coding toolkit: 109 skills, 44 agents, 12 developer-tool integrations, recoverable native tool-output filtering, Claude Chat/Cowork export, safety constitution, SARIF audit, and signed npm provenance.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
package/scripts/doctor.py
CHANGED
|
@@ -15,6 +15,8 @@ Checks:
|
|
|
15
15
|
7. Benchmark freshness
|
|
16
16
|
8. Stale rules
|
|
17
17
|
9. URL hook sources
|
|
18
|
+
10. Language rules drift (project-local)
|
|
19
|
+
11. Plugin double-load (Claude app plugin vs global install)
|
|
18
20
|
|
|
19
21
|
Exit codes:
|
|
20
22
|
0 all checks pass
|
|
@@ -44,6 +46,7 @@ HOOKS_DIR = _HOOKS_DIR
|
|
|
44
46
|
RULES_DIR = _RULES_DIR
|
|
45
47
|
EXTERNAL_HOOKS_DIR = _EXTERNAL_HOOKS_DIR
|
|
46
48
|
BENCHMARK_DASHBOARD = toolkit_dir / "benchmarks" / "ecosystem-dashboard.json"
|
|
49
|
+
PLUGIN_REGISTRY = CLAUDE_DIR / "plugins" / "installed_plugins.json"
|
|
47
50
|
|
|
48
51
|
VALID_EVENTS = frozenset({
|
|
49
52
|
"SessionStart", "Notification", "PreToolUse", "PostToolUse", "Stop",
|
|
@@ -668,6 +671,84 @@ def check_language_drift(dr: DiagResult) -> None:
|
|
|
668
671
|
dr.warn(f"{lang} detected but {skill} not injected — run: ai-toolkit install --local --lang {lang}")
|
|
669
672
|
|
|
670
673
|
|
|
674
|
+
# ---------------------------------------------------------------------------
|
|
675
|
+
# Check 11: Plugin Double-Load
|
|
676
|
+
# ---------------------------------------------------------------------------
|
|
677
|
+
|
|
678
|
+
def _registered_toolkit_plugins(registry: dict) -> list[str]:
|
|
679
|
+
"""Return ai-toolkit plugin keys from the Claude Code plugin registry."""
|
|
680
|
+
plugins = registry.get("plugins", {})
|
|
681
|
+
if not isinstance(plugins, dict):
|
|
682
|
+
return []
|
|
683
|
+
return [key for key in plugins if str(key).split("@", 1)[0] == "ai-toolkit"]
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def check_plugin_double_load(dr: DiagResult, fix_mode: bool) -> None:
|
|
687
|
+
"""Warn when the Claude app plugin and the global install both feed Claude Code.
|
|
688
|
+
|
|
689
|
+
Uploading the ``claude-app export`` ZIP from the Claude app registers it under
|
|
690
|
+
``~/.claude/plugins``, which Claude Code reads as well. The plugin carries the
|
|
691
|
+
same skills, agents, and hooks as the global install, and Claude Code merges
|
|
692
|
+
plugin hooks with user hooks without deduplication, so every toolkit hook runs
|
|
693
|
+
twice per event.
|
|
694
|
+
"""
|
|
695
|
+
print()
|
|
696
|
+
print("## 11. Plugin Double-Load")
|
|
697
|
+
|
|
698
|
+
if not PLUGIN_REGISTRY.is_file():
|
|
699
|
+
dr.skip("no Claude Code plugin registry")
|
|
700
|
+
return
|
|
701
|
+
try:
|
|
702
|
+
registry = json.loads(PLUGIN_REGISTRY.read_text(encoding="utf-8"))
|
|
703
|
+
except (OSError, json.JSONDecodeError):
|
|
704
|
+
dr.warn(f"{PLUGIN_REGISTRY} is not valid JSON")
|
|
705
|
+
return
|
|
706
|
+
|
|
707
|
+
keys = _registered_toolkit_plugins(registry)
|
|
708
|
+
if not keys:
|
|
709
|
+
dr.ok("no ai-toolkit plugin registered in Claude Code")
|
|
710
|
+
return
|
|
711
|
+
|
|
712
|
+
settings_json = CLAUDE_DIR / "settings.json"
|
|
713
|
+
try:
|
|
714
|
+
settings = json.loads(settings_json.read_text(encoding="utf-8"))
|
|
715
|
+
except (OSError, json.JSONDecodeError):
|
|
716
|
+
settings = {}
|
|
717
|
+
enabled = settings.get("enabledPlugins", {})
|
|
718
|
+
if not isinstance(enabled, dict):
|
|
719
|
+
enabled = {}
|
|
720
|
+
|
|
721
|
+
active = [key for key in keys if enabled.get(key, True)]
|
|
722
|
+
if not active:
|
|
723
|
+
dr.ok("ai-toolkit plugin registered but disabled for Claude Code")
|
|
724
|
+
return
|
|
725
|
+
|
|
726
|
+
hook_count, _ = _installed_toolkit_hook_count(settings)
|
|
727
|
+
if hook_count == 0:
|
|
728
|
+
dr.ok("ai-toolkit plugin active without global hooks (single source)")
|
|
729
|
+
return
|
|
730
|
+
|
|
731
|
+
for key in active:
|
|
732
|
+
dr.warn(
|
|
733
|
+
f"{key} is active next to the global install: {hook_count} toolkit hooks "
|
|
734
|
+
"fire twice per event and skills/agents load twice "
|
|
735
|
+
"(run: ai-toolkit doctor --fix)"
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
if not fix_mode:
|
|
739
|
+
return
|
|
740
|
+
|
|
741
|
+
enabled.update({key: False for key in active})
|
|
742
|
+
settings["enabledPlugins"] = enabled
|
|
743
|
+
try:
|
|
744
|
+
settings_json.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
|
|
745
|
+
except OSError as exc:
|
|
746
|
+
dr.fail(f"could not disable plugin in settings.json: {exc}")
|
|
747
|
+
return
|
|
748
|
+
for key in active:
|
|
749
|
+
dr.fixed(f"disabled {key} for Claude Code (global install stays authoritative)")
|
|
750
|
+
|
|
751
|
+
|
|
671
752
|
# ---------------------------------------------------------------------------
|
|
672
753
|
# Main
|
|
673
754
|
# ---------------------------------------------------------------------------
|
|
@@ -691,6 +772,7 @@ def main() -> None:
|
|
|
691
772
|
check_stale_rules(dr, fix_mode)
|
|
692
773
|
check_url_hooks(dr, fix_mode)
|
|
693
774
|
check_language_drift(dr)
|
|
775
|
+
check_plugin_double_load(dr, fix_mode)
|
|
694
776
|
|
|
695
777
|
# Summary
|
|
696
778
|
print("========================")
|