@softspark/ai-toolkit 4.12.0 → 4.13.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +33 -15
  3. package/app/.claude-plugin/plugin.json +11 -19
  4. package/app/ARCHITECTURE.md +6 -6
  5. package/app/claude-app/global-instructions.md +10 -0
  6. package/app/claude-app/hooks/hooks.json +284 -0
  7. package/app/claude-app/skills/ai-toolkit-rules/SKILL.md +359 -0
  8. package/app/hooks/config-desync-guard.sh +63 -23
  9. package/app/plugins/README.md +4 -1
  10. package/benchmarks/ecosystem-doctor-snapshot.json +91 -25
  11. package/bin/ai-toolkit.js +21 -3
  12. package/kb/planning/drop-cascade-hooks-after-sunset.md +13 -8
  13. package/kb/procedures/ecosystem-sync-sop.md +5 -5
  14. package/kb/procedures/maintenance-sop.md +42 -4
  15. package/kb/procedures/release-preparation-sop.md +5 -1
  16. package/kb/procedures/release-verification-sop.md +25 -9
  17. package/kb/reference/architecture-overview.md +7 -2
  18. package/kb/reference/claude-ecosystem-expansion-foundations.md +12 -3
  19. package/kb/reference/cli-reference.md +4 -2
  20. package/kb/reference/global-install-model.md +24 -3
  21. package/kb/reference/hooks-catalog.md +2 -3
  22. package/kb/reference/plugin-pack-conventions.md +5 -5
  23. package/kb/reference/skills-catalog.md +2 -0
  24. package/kb/reference/supported-tools-registry.md +30 -13
  25. package/kb/reference/unique-features.md +3 -2
  26. package/llms-full.txt +179 -60
  27. package/manifest.json +8 -8
  28. package/package.json +4 -3
  29. package/scripts/claude_app.py +347 -0
  30. package/scripts/doctor.py +85 -4
  31. package/scripts/ecosystem_tools.json +33 -5
  32. package/scripts/generate_devin_hooks.py +3 -4
  33. package/scripts/generate_windsurf_skills.py +5 -6
  34. package/scripts/install.py +23 -21
  35. package/scripts/install_steps/ai_tools.py +96 -6
  36. package/scripts/install_steps/install_state.py +2 -0
  37. package/scripts/update_projects.py +12 -1
  38. package/scripts/validate.py +34 -2
  39. package/AGENTS.md +0 -655
  40. package/scripts/generate_windsurf_hooks.py +0 -152
@@ -1,152 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Generate .windsurf/hooks.json for Windsurf Cascade.
3
-
4
- DEPRECATED SURFACE: the Cascade agent is available only through 2026-07-01
5
- (Devin Local is the default agent since the 2026-06-02 Devin Desktop rebrand),
6
- and this hooks.json schema is Cascade-scoped. Migrate to the Devin CLI
7
- lifecycle-hooks surface (docs.devin.ai/cli/extensibility/hooks/*) before that
8
- date; tracked in the ecosystem registry status_note for windsurf.
9
-
10
- Writes `<target>/.windsurf/hooks.json`. Existing user hook entries are
11
- preserved; only entries tagged `_source: ai-toolkit` are replaced on
12
- regeneration. Windsurf merges system / user / workspace hooks at runtime, so
13
- this file is safe to live alongside `~/.codeium/windsurf/hooks.json`.
14
-
15
- Windsurf Cascade events (per docs.windsurf.com/windsurf/cascade/hooks.md):
16
- pre_read_code, post_read_code, pre_write_code, post_write_code,
17
- pre_run_command, post_run_command, pre_mcp_tool_use, post_mcp_tool_use,
18
- pre_user_prompt, post_cascade_response,
19
- post_cascade_response_with_transcript, post_setup_worktree
20
- (12 total).
21
-
22
- Pre-hooks can block via exit code 2 (see `guard-destructive.sh`). Each entry
23
- takes `command` (macOS/Linux) + optional `powershell` (Windows). We only emit
24
- `command` because our hook scripts are bash-only.
25
-
26
- Usage:
27
- python3 scripts/generate_windsurf_hooks.py [target-dir]
28
- """
29
- from __future__ import annotations
30
-
31
- import json
32
- import sys
33
- from pathlib import Path
34
-
35
- HOOKS_PREFIX = '"$HOME/.softspark/ai-toolkit/hooks/'
36
- SOURCE_TAG = "ai-toolkit"
37
-
38
- # Event -> list of script names.
39
- WINDSURF_HOOKS: dict[str, list[str]] = {
40
- "pre_read_code": [
41
- "guard-path.sh",
42
- ],
43
- "pre_write_code": [
44
- "guard-path.sh",
45
- "guard-config.sh",
46
- ],
47
- "post_write_code": [
48
- "post-tool-use.sh",
49
- "governance-capture.sh",
50
- "test-cohesion.sh",
51
- ],
52
- "pre_run_command": [
53
- "guard-destructive.sh",
54
- "commit-quality.sh",
55
- "revert-guard.sh",
56
- ],
57
- "post_run_command": [
58
- "governance-capture.sh",
59
- ],
60
- "pre_mcp_tool_use": [
61
- "guard-config.sh",
62
- ],
63
- "post_mcp_tool_use": [
64
- "search-tracker.sh",
65
- ],
66
- "pre_user_prompt": [
67
- "user-prompt-submit.sh",
68
- "track-usage.sh",
69
- ],
70
- "post_cascade_response": [
71
- "quality-check.sh",
72
- "save-session.sh",
73
- "stop-search-check.sh",
74
- ],
75
- }
76
-
77
-
78
- def build_hook_entry(script: str) -> dict:
79
- """Build a single Windsurf hook entry.
80
-
81
- Uses `bash -c` semantics via the raw command (Windsurf already runs the
82
- macOS/Linux `command` via `bash -c`).
83
- """
84
- return {
85
- "_source": SOURCE_TAG,
86
- "command": f"{HOOKS_PREFIX}{script}\"",
87
- "show_output": False,
88
- }
89
-
90
-
91
- def build_toolkit_hooks() -> dict[str, list[dict]]:
92
- return {event: [build_hook_entry(s) for s in scripts]
93
- for event, scripts in WINDSURF_HOOKS.items()}
94
-
95
-
96
- def _is_toolkit_entry(entry: dict) -> bool:
97
- return isinstance(entry, dict) and entry.get("_source") == SOURCE_TAG
98
-
99
-
100
- def strip_toolkit_hooks(hooks: dict) -> dict:
101
- kept: dict = {}
102
- for event, entries in hooks.items():
103
- if not isinstance(entries, list):
104
- kept[event] = entries
105
- continue
106
- survivors = [e for e in entries if not _is_toolkit_entry(e)]
107
- if survivors:
108
- kept[event] = survivors
109
- return kept
110
-
111
-
112
- def merge_hooks(existing: dict, toolkit: dict) -> dict:
113
- merged = strip_toolkit_hooks(existing)
114
- for event, entries in toolkit.items():
115
- merged.setdefault(event, []).extend(entries)
116
- return merged
117
-
118
-
119
- def generate(target_dir: Path) -> Path:
120
- ws_dir = target_dir / ".windsurf"
121
- ws_dir.mkdir(parents=True, exist_ok=True)
122
- path = ws_dir / "hooks.json"
123
-
124
- doc: dict = {}
125
- if path.is_file():
126
- try:
127
- with open(path, encoding="utf-8") as f:
128
- doc = json.load(f)
129
- if not isinstance(doc, dict):
130
- doc = {}
131
- except (json.JSONDecodeError, OSError):
132
- doc = {}
133
-
134
- existing_hooks = doc.get("hooks") if isinstance(doc.get("hooks"), dict) else {}
135
- doc["hooks"] = merge_hooks(existing_hooks or {}, build_toolkit_hooks())
136
-
137
- with open(path, "w", encoding="utf-8") as f:
138
- json.dump(doc, f, indent=4, ensure_ascii=False, sort_keys=True)
139
- f.write("\n")
140
- return path
141
-
142
-
143
- def main() -> None:
144
- target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd()
145
- path = generate(target)
146
- total = sum(len(v) for v in WINDSURF_HOOKS.values())
147
- print(f"Generated: {path.relative_to(target) if path.is_relative_to(target) else path} "
148
- f"({total} hooks across {len(WINDSURF_HOOKS)} events)")
149
-
150
-
151
- if __name__ == "__main__":
152
- main()