@softspark/ai-toolkit 4.2.1 → 4.2.3
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 +31 -0
- package/README.md +7 -8
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/agents/seo-specialist.md +3 -3
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/inject_hook_cli.py +39 -3
- package/scripts/merge-hooks.py +23 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v4.2.3 - SEO FAQ rich results deprecation (2026-05-12)
|
|
11
|
+
|
|
12
|
+
Patch release. Updates `seo-specialist` guidance after Google deprecated FAQ rich results.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **SEO structured data guidance** - `seo-specialist` now treats `FAQPage` as deprecated for Google rich results and recommends keeping it only when explicitly targeting non-Google engines.
|
|
17
|
+
- **Citability schema checklist** - Prioritizes `Article`, `HowTo`, `QAPage`, and `Person` schema guidance for Google-facing SEO/GEO work.
|
|
18
|
+
|
|
19
|
+
### Verification
|
|
20
|
+
|
|
21
|
+
- Source checked against [Google Search Central `FAQPage` structured data documentation](https://developers.google.com/search/docs/appearance/structured-data/faqpage).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## v4.2.2 - Legacy hook duplicate cleanup (2026-05-12)
|
|
26
|
+
|
|
27
|
+
Patch release. Cleans up legacy untagged hook duplicates found during post-release verification of v4.2.1.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **Legacy notification hook cleanup** - `scripts/merge-hooks.py` now removes old direct `osascript` notification hooks from pre-script installs while preserving user-owned notification hooks.
|
|
32
|
+
- **External hook duplicate cleanup** - `scripts/inject_hook_cli.py` now removes legacy untagged entries matching the same event, matcher, and handler payload as the external hook being re-injected.
|
|
33
|
+
|
|
34
|
+
### Verification
|
|
35
|
+
|
|
36
|
+
- `bats tests/test_merge_hooks_statusline.bats tests/test_inject_hook.bats`: 42/42 passing.
|
|
37
|
+
- Local `~/.claude/settings.json`: 0 behavior duplicates, 0 exact duplicates, 0 untagged hook groups after cleanup.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
10
41
|
## v4.2.1 - Claude Code hook enforcement and README cleanup (2026-05-12)
|
|
11
42
|
|
|
12
43
|
Patch release. Tightens Claude Code runtime enforcement, fixes duplicated hook cleanup during install/update, and removes the README hero image for release.
|
package/README.md
CHANGED
|
@@ -6,16 +6,15 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](tests/)
|
|
10
10
|
|
|
11
|
-
## What's New in v4.2.
|
|
11
|
+
## What's New in v4.2.3
|
|
12
12
|
|
|
13
|
-
Patch release
|
|
13
|
+
Patch release updating SEO guidance after Google's FAQ rich result deprecation.
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **README cleanup**: removed the hero image block for a lighter release README.
|
|
15
|
+
- **SEO agent guidance**: `seo-specialist` now treats `FAQPage` as deprecated for Google rich results.
|
|
16
|
+
- **Schema recommendations**: citability guidance now prioritizes `Article`, `HowTo`, `QAPage`, and `Person` for Google-facing surfaces.
|
|
17
|
+
- **GEO audit checklist**: FAQ markup is now scoped to non-Google engines instead of recommended as a default.
|
|
19
18
|
|
|
20
19
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
21
20
|
|
|
@@ -148,7 +147,7 @@ ai-toolkit/
|
|
|
148
147
|
│ └── ARCHITECTURE.md # Full system design
|
|
149
148
|
├── kb/ # Reference docs, procedures, plans
|
|
150
149
|
├── scripts/ # Validation, install, evaluation scripts
|
|
151
|
-
├── tests/ # Bats test suite (
|
|
150
|
+
├── tests/ # Bats test suite (1051 tests)
|
|
152
151
|
└── CHANGELOG.md
|
|
153
152
|
```
|
|
154
153
|
|
|
@@ -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": "4.2.
|
|
4
|
+
"version": "4.2.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SoftSpark",
|
|
7
7
|
"url": "https://github.com/softspark"
|
|
@@ -39,7 +39,7 @@ Optimization for both classical search engines AND generative engines (ChatGPT,
|
|
|
39
39
|
|
|
40
40
|
### Structured Data
|
|
41
41
|
- Schema.org markup
|
|
42
|
-
- Rich snippets
|
|
42
|
+
- Rich snippets (note: Google dropped FAQ rich results on May 7, 2026; Search Console API support removed August 2026)
|
|
43
43
|
- Knowledge graph
|
|
44
44
|
- Breadcrumbs
|
|
45
45
|
|
|
@@ -157,7 +157,7 @@ Goal: get cited by ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews
|
|
|
157
157
|
5. **Named author + author bio with credentials.** Authority signal that survives synthesis.
|
|
158
158
|
6. **Original quotes from named experts.** Quotation blocks are over-represented in LLM citations.
|
|
159
159
|
7. **Comparison tables with explicit labels.** Tables are extracted whole; vague prose is summarised away.
|
|
160
|
-
8. **Schema.org `Article`, `
|
|
160
|
+
8. **Schema.org `Article`, `HowTo`, `QAPage`, `Person`.** Mandatory for entity disambiguation. `FAQPage` schema: Google dropped FAQ rich results on **May 7, 2026** (Search Console API removed August 2026) — keep markup only if targeting other engines (Bing, etc.).
|
|
161
161
|
9. **`llms.txt` at site root.** Curated index of canonical pages for LLM crawlers (Anthropic-proposed convention).
|
|
162
162
|
10. **Stable URLs with semantic slugs.** LLMs cache citations — URL changes erase your authority overnight.
|
|
163
163
|
11. **Cross-linking with descriptive anchors.** "this article" is dead anchor text; use the actual claim.
|
|
@@ -189,7 +189,7 @@ Goal: get cited by ChatGPT, Claude, Perplexity, Gemini, and Google AI Overviews
|
|
|
189
189
|
- [ ] At least one comparison table OR numbered list per long-form page
|
|
190
190
|
- [ ] Author block with real name (not "Admin"), role, and ≥30 words of bio
|
|
191
191
|
- [ ] Schema.org `Article` with `author` (`Person` + `sameAs`), `datePublished`, `dateModified`
|
|
192
|
-
- [ ] Schema.org `FAQPage
|
|
192
|
+
- [ ] ~~Schema.org `FAQPage`~~ — Google dropped FAQ rich results on **May 7, 2026**; Search Console reporting ends June 2026, API removed August 2026. Remove or keep only if targeting non-Google engines.
|
|
193
193
|
- [ ] `dateModified` updated AND visible "Updated: [date]" text present
|
|
194
194
|
- [ ] `llms.txt` at site root with curated canonical URLs
|
|
195
195
|
- [ ] Page contains at least 3 unique data points (numbers, dates, version strings)
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.3",
|
|
4
4
|
"description": "AI coding toolkit: 107 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",
|
|
@@ -120,20 +120,56 @@ def _entry_source(entry: dict) -> str | None:
|
|
|
120
120
|
return None
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
def
|
|
123
|
+
def _entry_signature(entry: dict) -> tuple:
|
|
124
|
+
"""Return behavior-defining hook fields without source tags."""
|
|
125
|
+
handlers = []
|
|
126
|
+
for hook in entry.get("hooks", []):
|
|
127
|
+
if not isinstance(hook, dict):
|
|
128
|
+
handlers.append(hook)
|
|
129
|
+
continue
|
|
130
|
+
handlers.append(tuple(sorted(
|
|
131
|
+
(key, value)
|
|
132
|
+
for key, value in hook.items()
|
|
133
|
+
if key != "_source"
|
|
134
|
+
)))
|
|
135
|
+
return (entry.get("matcher", ""), tuple(handlers))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def strip_source(hooks: dict, source: str, replacement_hooks: dict | None = None) -> dict:
|
|
124
139
|
"""Remove all entries whose ``_source`` matches *source*.
|
|
125
140
|
|
|
126
141
|
Args:
|
|
127
142
|
hooks: Existing hooks dict (event-name -> list of entries).
|
|
128
143
|
source: Source tag to strip.
|
|
144
|
+
replacement_hooks: Optional hook entries being re-injected. When
|
|
145
|
+
provided, untagged legacy entries with the same event/matcher/
|
|
146
|
+
handler payload are stripped as belonging to the same source.
|
|
129
147
|
|
|
130
148
|
Returns:
|
|
131
149
|
New hooks dict with matching entries removed. Empty event lists
|
|
132
150
|
are omitted.
|
|
133
151
|
"""
|
|
152
|
+
legacy_signatures: dict[str, set[tuple]] = {}
|
|
153
|
+
if replacement_hooks:
|
|
154
|
+
for event, entries in replacement_hooks.items():
|
|
155
|
+
legacy_signatures[event] = {
|
|
156
|
+
_entry_signature(entry)
|
|
157
|
+
for entry in entries
|
|
158
|
+
if isinstance(entry, dict)
|
|
159
|
+
}
|
|
160
|
+
|
|
134
161
|
result: dict = {}
|
|
135
162
|
for event, entries in hooks.items():
|
|
136
|
-
|
|
163
|
+
signatures = legacy_signatures.get(event, set())
|
|
164
|
+
filtered = [
|
|
165
|
+
e for e in entries
|
|
166
|
+
if _entry_source(e) != source
|
|
167
|
+
and not (
|
|
168
|
+
isinstance(e, dict)
|
|
169
|
+
and _entry_source(e) is None
|
|
170
|
+
and _entry_signature(e) in signatures
|
|
171
|
+
)
|
|
172
|
+
]
|
|
137
173
|
if filtered:
|
|
138
174
|
result[event] = filtered
|
|
139
175
|
return result
|
|
@@ -174,7 +210,7 @@ def merge_hooks(new_hooks: dict, existing_hooks: dict, source: str) -> dict:
|
|
|
174
210
|
Returns:
|
|
175
211
|
Merged hooks dict.
|
|
176
212
|
"""
|
|
177
|
-
merged = strip_source(existing_hooks, source)
|
|
213
|
+
merged = strip_source(existing_hooks, source, new_hooks)
|
|
178
214
|
for event, entries in new_hooks.items():
|
|
179
215
|
if event not in merged:
|
|
180
216
|
merged[event] = []
|
package/scripts/merge-hooks.py
CHANGED
|
@@ -27,6 +27,23 @@ import os
|
|
|
27
27
|
import sys
|
|
28
28
|
|
|
29
29
|
SOURCE_TAG = "ai-toolkit"
|
|
30
|
+
LEGACY_TOOLKIT_HOOKS = {
|
|
31
|
+
"Notification": [
|
|
32
|
+
{
|
|
33
|
+
"matcher": "",
|
|
34
|
+
"hooks": [
|
|
35
|
+
{
|
|
36
|
+
"type": "command",
|
|
37
|
+
"command": (
|
|
38
|
+
"osascript -e 'display notification "
|
|
39
|
+
'"Claude Code needs your attention" with title "Claude Code"'
|
|
40
|
+
"'"
|
|
41
|
+
),
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
}
|
|
30
47
|
|
|
31
48
|
|
|
32
49
|
def load_json(path: str) -> dict:
|
|
@@ -95,6 +112,12 @@ def strip_toolkit(hooks: dict, toolkit_hooks: dict | None = None) -> dict:
|
|
|
95
112
|
for entry in entries
|
|
96
113
|
if isinstance(entry, dict)
|
|
97
114
|
}
|
|
115
|
+
for event, entries in LEGACY_TOOLKIT_HOOKS.items():
|
|
116
|
+
legacy_signatures.setdefault(event, set()).update(
|
|
117
|
+
_entry_signature(entry)
|
|
118
|
+
for entry in entries
|
|
119
|
+
if isinstance(entry, dict)
|
|
120
|
+
)
|
|
98
121
|
|
|
99
122
|
result = {}
|
|
100
123
|
for event, entries in hooks.items():
|