@softspark/ai-toolkit 4.24.0 → 4.24.1
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 +27 -0
- package/README.md +11 -12
- package/app/.claude-plugin/plugin.json +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/plugin.py +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v4.24.1 — a dead link reported itself as installed (2026-08-19)
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`plugin install` treated a dangling symlink as an installed asset.** The guard
|
|
15
|
+
was `if path.exists() or path.is_symlink()`, and a link whose target is gone
|
|
16
|
+
satisfies the second half — so the installer printed `OK agent` / `OK skill`,
|
|
17
|
+
changed nothing, and left the user with a dead link and a green log. This is the
|
|
18
|
+
exact state a toolkit upgrade produces: `npm install -g @softspark/ai-toolkit`
|
|
19
|
+
replaces `app/` wholesale, so every `~/.claude` link into the old package points
|
|
20
|
+
at nothing, and re-running install could not repair it.
|
|
21
|
+
|
|
22
|
+
A dangling link is now dropped before the guard runs and relinked to the
|
|
23
|
+
resolved source, printing `Dangling agent link removed: <name>`. **Live links and
|
|
24
|
+
real files are untouched** — those are user content, and the merge-friendly
|
|
25
|
+
install model keeps them.
|
|
26
|
+
|
|
27
|
+
Surfaced while migrating a pack from `app/plugins/` to the v4.24.0 user root:
|
|
28
|
+
the upgrade broke both links and `plugin install` insisted everything was fine.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- **Two tests.** One builds the post-upgrade state (links pointing into a path
|
|
33
|
+
that no longer exists) and asserts install removes and relinks both; the other
|
|
34
|
+
pins the negative — a real file on the agent name and a live skill link survive
|
|
35
|
+
a reinstall untouched.
|
|
36
|
+
|
|
10
37
|
## v4.24.0 — external packs stop dying on upgrade (2026-08-18)
|
|
11
38
|
|
|
12
39
|
### Added
|
package/README.md
CHANGED
|
@@ -6,18 +6,17 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](tests/)
|
|
10
|
+
|
|
11
|
+
## What's New in v4.24.1
|
|
12
|
+
|
|
13
|
+
**v4.24.1** — `plugin install` accepted a dead symlink as proof of installation.
|
|
14
|
+
The guard asked `exists() or is_symlink()`, and a link with no target still
|
|
15
|
+
answers yes to the second half, so the installer logged `OK` and repaired
|
|
16
|
+
nothing — precisely the state a toolkit upgrade leaves, since replacing `app/`
|
|
17
|
+
breaks every link into it. Dangling links are now dropped and relinked; real
|
|
18
|
+
files and live links stay untouched. Pairs with v4.24.0's user-level pack root,
|
|
19
|
+
which is what stops the breakage happening in the first place.
|
|
21
20
|
|
|
22
21
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
23
22
|
|
|
@@ -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.24.
|
|
6
|
+
"version": "4.24.1",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "SoftSpark",
|
|
9
9
|
"url": "https://github.com/softspark"
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "4.24.
|
|
3
|
+
"version": "4.24.1",
|
|
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/plugin.py
CHANGED
|
@@ -603,10 +603,30 @@ def _ensure_claude_settings() -> Path:
|
|
|
603
603
|
return settings_path
|
|
604
604
|
|
|
605
605
|
|
|
606
|
+
def _clear_dangling_link(path: Path, label: str) -> bool:
|
|
607
|
+
"""Drop a symlink whose target no longer exists, so install can relink it.
|
|
608
|
+
|
|
609
|
+
A toolkit upgrade replaces app/ wholesale, which leaves every ~/.claude link
|
|
610
|
+
into the old package pointing at nothing. The install guard treated such a
|
|
611
|
+
link as installed (`exists() or is_symlink()`), reported OK and repaired
|
|
612
|
+
nothing — the user was left with a dead link and a green log. Only dangling
|
|
613
|
+
links are removed: a live link or a real file is user content and stays.
|
|
614
|
+
"""
|
|
615
|
+
if path.is_symlink() and not path.exists():
|
|
616
|
+
try:
|
|
617
|
+
path.unlink()
|
|
618
|
+
except OSError:
|
|
619
|
+
return False
|
|
620
|
+
print(f" Dangling {label} link removed: {path.name}")
|
|
621
|
+
return True
|
|
622
|
+
return False
|
|
623
|
+
|
|
624
|
+
|
|
606
625
|
def _install_claude_skills(pack: dict, pack_dir: Path, installed_items: list[str]) -> None:
|
|
607
626
|
for skill in pack.get("includes", {}).get("skills", []):
|
|
608
627
|
skill_dir = CLAUDE_DIR / "skills" / skill
|
|
609
628
|
source_dir = _resolve_skill_source(pack_dir, skill)
|
|
629
|
+
_clear_dangling_link(skill_dir, "skill")
|
|
610
630
|
if skill_dir.exists() or skill_dir.is_symlink():
|
|
611
631
|
print(f" OK skill: {skill}")
|
|
612
632
|
elif source_dir:
|
|
@@ -622,6 +642,7 @@ def _install_claude_agents(pack: dict, pack_dir: Path, installed_items: list[str
|
|
|
622
642
|
for agent in pack.get("includes", {}).get("agents", []):
|
|
623
643
|
agent_file = CLAUDE_DIR / "agents" / f"{agent}.md"
|
|
624
644
|
source_file = _resolve_agent_source(pack_dir, agent)
|
|
645
|
+
_clear_dangling_link(agent_file, "agent")
|
|
625
646
|
if agent_file.exists() or agent_file.is_symlink():
|
|
626
647
|
print(f" OK agent: {agent}")
|
|
627
648
|
elif source_file is not None:
|