@softspark/ai-toolkit 4.17.0 → 4.19.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 +89 -0
- package/README.md +12 -10
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/plugins/README.md +16 -4
- package/benchmarks/ecosystem-doctor-snapshot.json +8 -8
- package/bin/ai-toolkit.js +16 -1
- package/kb/history/completed/rtk-pack-integration-20260726.md +710 -0
- package/kb/history/completed/rtk-pack-retirement-20260727.md +168 -0
- package/kb/procedures/maintenance-sop.md +1 -1
- package/kb/procedures/post-release-testing-sop.md +217 -0
- package/kb/procedures/release-preparation-sop.md +20 -7
- package/kb/reference/cli-reference.md +1 -1
- package/kb/reference/plugin-pack-conventions.md +26 -2
- package/llms-full.txt +1161 -11
- package/llms.txt +3 -0
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/audit_skills.py +21 -0
- package/scripts/generate_cursor_hooks.py +44 -0
- package/scripts/generate_gemini_hooks.py +44 -0
- package/scripts/install.py +14 -1
- package/scripts/plugin.py +403 -49
- package/scripts/uninstall.py +29 -0
package/llms.txt
CHANGED
|
@@ -21,12 +21,15 @@
|
|
|
21
21
|
- [Plan: Offline-First SLM Profile — Lightweight Mode for Local Models](kb/history/completed/offline-slm-profile-plan-20260411.md)
|
|
22
22
|
- [Retirement: Native Tool-Output Filter](kb/history/completed/output-filter-retirement-20260726.md)
|
|
23
23
|
- [Plan: Output & Token Discipline](kb/history/completed/output-token-discipline-plan-20260504.md)
|
|
24
|
+
- [rtk Pack Integration](kb/history/completed/rtk-pack-integration-20260726.md)
|
|
25
|
+
- [Retirement: rtk-pack](kb/history/completed/rtk-pack-retirement-20260727.md)
|
|
24
26
|
- [How-To Guides](kb/howto/README.md)
|
|
25
27
|
- [Plan: Cloud Security Pack — Multi-Cloud Audit](kb/planning/cloud-security-pack-plan.md)
|
|
26
28
|
- [Plan: Drop Cascade hooks after 2026-07-01 sunset](kb/planning/drop-cascade-hooks-after-sunset.md)
|
|
27
29
|
- [PRD: MCP Context Trim v4.0](kb/planning/mcp-context-trim-v4-prd.md)
|
|
28
30
|
- [SOP: Ecosystem Sync](kb/procedures/ecosystem-sync-sop.md)
|
|
29
31
|
- [SOP: AI Toolkit Maintenance](kb/procedures/maintenance-sop.md)
|
|
32
|
+
- [SOP: Post-Release Testing](kb/procedures/post-release-testing-sop.md)
|
|
30
33
|
- [SOP: Release Preparation](kb/procedures/release-preparation-sop.md)
|
|
31
34
|
- [SOP: Release Verification](kb/procedures/release-verification-sop.md)
|
|
32
35
|
- [Agents Catalog](kb/reference/agents-catalog.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.19.0",
|
|
4
4
|
"description": "AI coding toolkit: 108 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/audit_skills.py
CHANGED
|
@@ -454,6 +454,27 @@ def audit(toolkit_root: Path) -> list[Finding]:
|
|
|
454
454
|
scan_secrets(agent_md, findings)
|
|
455
455
|
scan_unicode(agent_md, findings)
|
|
456
456
|
|
|
457
|
+
# Scan plugin packs. Pack code ships and executes exactly like skill code,
|
|
458
|
+
# and a pack may carry an install script that runs at install time, so
|
|
459
|
+
# leaving app/plugins out of the HIGH gate exempted the highest-risk code
|
|
460
|
+
# in the repo from the check that exists to catch it.
|
|
461
|
+
plugins = app / "plugins"
|
|
462
|
+
if plugins.is_dir():
|
|
463
|
+
for pack_dir in sorted(plugins.iterdir()):
|
|
464
|
+
if not pack_dir.is_dir():
|
|
465
|
+
continue
|
|
466
|
+
for py in sorted(pack_dir.rglob("*.py")):
|
|
467
|
+
scan_file_patterns(py, PYTHON_HIGH, "HIGH", findings)
|
|
468
|
+
scan_file_patterns(py, PYTHON_WARN, "WARN", findings)
|
|
469
|
+
scan_secrets(py, findings)
|
|
470
|
+
for sh in sorted(pack_dir.rglob("*.sh")):
|
|
471
|
+
scan_file_patterns(sh, BASH_HIGH, "HIGH", findings)
|
|
472
|
+
scan_file_patterns(sh, BASH_WARN, "WARN", findings)
|
|
473
|
+
scan_secrets(sh, findings)
|
|
474
|
+
for md in sorted(pack_dir.rglob("*.md")):
|
|
475
|
+
scan_secrets(md, findings)
|
|
476
|
+
scan_unicode(md, findings)
|
|
477
|
+
|
|
457
478
|
# Unicode safety across the rest of the shipped prompt surface.
|
|
458
479
|
for extra in ("rules", "personas", "mcp-templates"):
|
|
459
480
|
extra_dir = app / extra
|
|
@@ -490,3 +490,47 @@ def main() -> None:
|
|
|
490
490
|
|
|
491
491
|
if __name__ == "__main__":
|
|
492
492
|
main()
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _cleanup_config_path(target_dir: Path) -> Path | None:
|
|
496
|
+
return Path(target_dir).expanduser() / ".cursor" / "hooks.json"
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def _cleanup_write(path: Path, document: dict) -> None:
|
|
500
|
+
path.write_text(json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def cleanup(target_dir: Path) -> None:
|
|
504
|
+
"""Strip this toolkit's hook entries for an uninstall or profile downgrade.
|
|
505
|
+
|
|
506
|
+
Only entries tagged with SOURCE_TAG are removed; user and plugin-pack
|
|
507
|
+
entries are left in place. The file is deleted only when nothing survives,
|
|
508
|
+
so an uninstall does not take a user's own configuration with it.
|
|
509
|
+
"""
|
|
510
|
+
config = _cleanup_config_path(target_dir)
|
|
511
|
+
if config is None or not config.is_file() or config.is_symlink():
|
|
512
|
+
return
|
|
513
|
+
try:
|
|
514
|
+
with open(config, encoding="utf-8") as handle:
|
|
515
|
+
document = json.load(handle)
|
|
516
|
+
except (OSError, json.JSONDecodeError):
|
|
517
|
+
return
|
|
518
|
+
if not isinstance(document, dict):
|
|
519
|
+
return
|
|
520
|
+
|
|
521
|
+
hooks = document.get("hooks")
|
|
522
|
+
if not isinstance(hooks, dict):
|
|
523
|
+
return
|
|
524
|
+
survivors = strip_toolkit_hooks(hooks)
|
|
525
|
+
if survivors == hooks:
|
|
526
|
+
return
|
|
527
|
+
|
|
528
|
+
if survivors:
|
|
529
|
+
document["hooks"] = survivors
|
|
530
|
+
else:
|
|
531
|
+
document.pop("hooks", None)
|
|
532
|
+
|
|
533
|
+
if document:
|
|
534
|
+
_cleanup_write(config, document)
|
|
535
|
+
else:
|
|
536
|
+
config.unlink()
|
|
@@ -179,3 +179,47 @@ def main() -> None:
|
|
|
179
179
|
|
|
180
180
|
if __name__ == "__main__":
|
|
181
181
|
main()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _cleanup_config_path(target_dir: Path) -> Path | None:
|
|
185
|
+
return Path(target_dir).expanduser() / ".gemini" / "settings.json"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _cleanup_write(path: Path, document: dict) -> None:
|
|
189
|
+
_write_settings_atomic(path, document)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def cleanup(target_dir: Path) -> None:
|
|
193
|
+
"""Strip this toolkit's hook entries for an uninstall or profile downgrade.
|
|
194
|
+
|
|
195
|
+
Only entries tagged with SOURCE_TAG are removed; user and plugin-pack
|
|
196
|
+
entries are left in place. The file is deleted only when nothing survives,
|
|
197
|
+
so an uninstall does not take a user's own configuration with it.
|
|
198
|
+
"""
|
|
199
|
+
config = _cleanup_config_path(target_dir)
|
|
200
|
+
if config is None or not config.is_file() or config.is_symlink():
|
|
201
|
+
return
|
|
202
|
+
try:
|
|
203
|
+
with open(config, encoding="utf-8") as handle:
|
|
204
|
+
document = json.load(handle)
|
|
205
|
+
except (OSError, json.JSONDecodeError):
|
|
206
|
+
return
|
|
207
|
+
if not isinstance(document, dict):
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
hooks = document.get("hooks")
|
|
211
|
+
if not isinstance(hooks, dict):
|
|
212
|
+
return
|
|
213
|
+
survivors = strip_toolkit_hooks(hooks)
|
|
214
|
+
if survivors == hooks:
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
if survivors:
|
|
218
|
+
document["hooks"] = survivors
|
|
219
|
+
else:
|
|
220
|
+
document.pop("hooks", None)
|
|
221
|
+
|
|
222
|
+
if document:
|
|
223
|
+
_cleanup_write(config, document)
|
|
224
|
+
else:
|
|
225
|
+
config.unlink()
|
package/scripts/install.py
CHANGED
|
@@ -286,7 +286,20 @@ def parse_args(argv: list[str]) -> dict:
|
|
|
286
286
|
print(f"Unknown option: {arg}")
|
|
287
287
|
sys.exit(1)
|
|
288
288
|
else:
|
|
289
|
-
|
|
289
|
+
# A bare word that is not an existing directory is almost never a
|
|
290
|
+
# deliberate install target: `ai-toolkit install <pack>` reads like
|
|
291
|
+
# `plugin install <pack>` and silently writes a full toolkit tree
|
|
292
|
+
# into ./<pack>. Require an existing directory or an explicit path
|
|
293
|
+
# separator, so creating a new top-level directory has to be asked
|
|
294
|
+
# for rather than typed by accident.
|
|
295
|
+
candidate = Path(arg)
|
|
296
|
+
looks_like_path = "/" in arg or "\\" in arg or arg.startswith("~") or arg == "."
|
|
297
|
+
if not candidate.is_dir() and not looks_like_path:
|
|
298
|
+
print(f"Refusing to install into a new directory named '{arg}'.")
|
|
299
|
+
print(f" If you meant a plugin pack: ai-toolkit plugin install {arg}")
|
|
300
|
+
print(f" If you really meant a path: ai-toolkit install ./{arg}")
|
|
301
|
+
sys.exit(1)
|
|
302
|
+
cfg["target_dir"] = candidate
|
|
290
303
|
i += 1
|
|
291
304
|
return cfg
|
|
292
305
|
|