@softspark/ai-toolkit 1.8.0 → 2.0.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 +47 -13
- package/README.md +69 -17
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/ARCHITECTURE.md +7 -1
- package/app/hooks/governance-capture.sh +1 -1
- package/app/hooks/pre-compact-save.sh +1 -1
- package/app/hooks/session-context.sh +1 -1
- package/app/hooks/track-usage.sh +2 -2
- package/app/hooks.json +20 -20
- package/app/plugins/memory-pack/README.md +1 -1
- package/app/plugins/memory-pack/hooks/observation-capture.sh +2 -2
- package/app/plugins/memory-pack/hooks/session-summary.sh +1 -1
- package/app/plugins/memory-pack/scripts/init_db.py +2 -2
- package/app/plugins/memory-pack/skills/mem-search/SKILL.md +3 -3
- package/app/skills/hook-creator/SKILL.md +2 -2
- package/app/skills/mem-search/SKILL.md +3 -3
- package/app/skills/repeat/SKILL.md +1 -1
- package/bin/ai-toolkit.js +32 -3
- package/kb/history/completed/enterprise-config-inheritance-plan-20260412.md +32 -34
- package/kb/history/completed/offline-slm-profile-plan-20260411.md +12 -12
- package/kb/planning/cloud-security-pack-plan.md +3 -3
- package/kb/procedures/maintenance-sop.md +3 -3
- package/kb/reference/architecture-overview.md +5 -5
- package/kb/reference/benchmark-config.md +1 -1
- package/kb/reference/competitive-features-implementation.md +2 -2
- package/kb/reference/enterprise-config-guide.md +9 -9
- package/kb/reference/extension-api.md +1 -1
- package/kb/reference/global-install-model.md +2 -2
- package/kb/reference/hooks-catalog.md +30 -30
- package/kb/reference/integrations.md +3 -3
- package/kb/reference/manifest-install.md +4 -4
- package/kb/reference/plugin-pack-conventions.md +5 -5
- package/kb/reference/stats.md +3 -3
- package/kb/reference/sync.md +3 -3
- package/llms-full.txt +118 -899
- package/llms.txt +0 -1
- package/manifest.json +4 -4
- package/package.json +1 -1
- package/scripts/add_rule.py +3 -2
- package/scripts/benchmark_config.py +3 -1
- package/scripts/compile_slm.py +9 -7
- package/scripts/config_cli.py +7 -7
- package/scripts/config_lock.py +12 -4
- package/scripts/config_merger.py +1 -1
- package/scripts/config_resolver.py +20 -7
- package/scripts/config_scaffold.py +4 -4
- package/scripts/config_validator.py +3 -3
- package/scripts/dir_rules_shared.py +1 -1
- package/scripts/doctor.py +3 -2
- package/scripts/install.py +25 -6
- package/scripts/install_git_hooks.py +1 -1
- package/scripts/install_steps/ai_tools.py +4 -4
- package/scripts/install_steps/hooks.py +1 -1
- package/scripts/install_steps/install_state.py +8 -6
- package/scripts/install_steps/project_registry.py +142 -0
- package/scripts/migrate.py +199 -0
- package/scripts/paths.py +50 -0
- package/scripts/plugin.py +8 -6
- package/scripts/projects_cli.py +110 -0
- package/scripts/remove_rule.py +5 -4
- package/scripts/stats.py +3 -2
- package/scripts/sync.py +3 -2
- package/scripts/update_projects.py +141 -0
- package/scripts/version_check.py +5 -2
- package/kb/planning/local-dashboard-plan.md +0 -773
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Update all registered projects in parallel.
|
|
3
|
+
|
|
4
|
+
Reads ~/.softspark/ai-toolkit/projects.json and runs install.py --local in each
|
|
5
|
+
project directory concurrently using a thread pool.
|
|
6
|
+
|
|
7
|
+
Stdlib-only — no external dependencies.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
20
|
+
from install_steps.project_registry import get_active_projects, prune_stale
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _update_project(project: dict[str, Any], install_script: str, extra_args: list[str]) -> dict:
|
|
24
|
+
"""Run install --local in a single project. Returns result dict."""
|
|
25
|
+
project_path = project["path"]
|
|
26
|
+
start = time.monotonic()
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
proc = subprocess.run(
|
|
30
|
+
["python3", install_script, "--local"] + extra_args,
|
|
31
|
+
cwd=project_path,
|
|
32
|
+
capture_output=True,
|
|
33
|
+
text=True,
|
|
34
|
+
timeout=120,
|
|
35
|
+
)
|
|
36
|
+
elapsed = time.monotonic() - start
|
|
37
|
+
return {
|
|
38
|
+
"path": project_path,
|
|
39
|
+
"profile": project.get("profile", ""),
|
|
40
|
+
"extends": project.get("extends", ""),
|
|
41
|
+
"success": proc.returncode == 0,
|
|
42
|
+
"elapsed": round(elapsed, 1),
|
|
43
|
+
"output": proc.stdout,
|
|
44
|
+
"error": proc.stderr if proc.returncode != 0 else "",
|
|
45
|
+
}
|
|
46
|
+
except subprocess.TimeoutExpired:
|
|
47
|
+
return {
|
|
48
|
+
"path": project_path,
|
|
49
|
+
"success": False,
|
|
50
|
+
"elapsed": 120.0,
|
|
51
|
+
"output": "",
|
|
52
|
+
"error": "Timed out after 120s",
|
|
53
|
+
}
|
|
54
|
+
except OSError as e:
|
|
55
|
+
return {
|
|
56
|
+
"path": project_path,
|
|
57
|
+
"success": False,
|
|
58
|
+
"elapsed": 0,
|
|
59
|
+
"output": "",
|
|
60
|
+
"error": str(e),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main() -> None:
|
|
65
|
+
"""Update all registered projects."""
|
|
66
|
+
# Parse args
|
|
67
|
+
verbose = "--verbose" in sys.argv or "-v" in sys.argv
|
|
68
|
+
json_output = "--json" in sys.argv
|
|
69
|
+
extra_args = [a for a in sys.argv[1:] if a not in ("--verbose", "-v", "--json")]
|
|
70
|
+
|
|
71
|
+
# Prune stale projects first
|
|
72
|
+
pruned = prune_stale()
|
|
73
|
+
if pruned and not json_output:
|
|
74
|
+
for p in pruned:
|
|
75
|
+
print(f" Pruned stale project: {p}")
|
|
76
|
+
|
|
77
|
+
# Get active projects
|
|
78
|
+
projects = get_active_projects()
|
|
79
|
+
|
|
80
|
+
if not projects:
|
|
81
|
+
if json_output:
|
|
82
|
+
print(json.dumps({"projects": [], "summary": "No registered projects"}))
|
|
83
|
+
else:
|
|
84
|
+
print(" No registered projects.")
|
|
85
|
+
print(" Run 'ai-toolkit install --local' in a project to register it.")
|
|
86
|
+
sys.exit(0)
|
|
87
|
+
|
|
88
|
+
install_script = str(Path(__file__).resolve().parent / "install.py")
|
|
89
|
+
|
|
90
|
+
if not json_output:
|
|
91
|
+
print(f" Updating {len(projects)} registered project(s)...")
|
|
92
|
+
print()
|
|
93
|
+
|
|
94
|
+
# Run in parallel (max 8 workers — don't overwhelm the system)
|
|
95
|
+
max_workers = min(len(projects), 8)
|
|
96
|
+
results: list[dict] = []
|
|
97
|
+
|
|
98
|
+
with ThreadPoolExecutor(max_workers=max_workers) as pool:
|
|
99
|
+
futures = {
|
|
100
|
+
pool.submit(_update_project, p, install_script, extra_args): p
|
|
101
|
+
for p in projects
|
|
102
|
+
}
|
|
103
|
+
for future in as_completed(futures):
|
|
104
|
+
result = future.result()
|
|
105
|
+
results.append(result)
|
|
106
|
+
|
|
107
|
+
if not json_output:
|
|
108
|
+
status = "✓" if result["success"] else "✗"
|
|
109
|
+
path_short = result["path"].replace(str(Path.home()), "~")
|
|
110
|
+
extends_info = f" (extends: {result.get('extends', '')})" if result.get("extends") else ""
|
|
111
|
+
print(f" {status} {path_short}{extends_info} ({result['elapsed']}s)")
|
|
112
|
+
|
|
113
|
+
if verbose and result["output"]:
|
|
114
|
+
for line in result["output"].strip().split("\n"):
|
|
115
|
+
print(f" {line}")
|
|
116
|
+
|
|
117
|
+
if result.get("error"):
|
|
118
|
+
for line in result["error"].strip().split("\n"):
|
|
119
|
+
print(f" ERROR: {line}")
|
|
120
|
+
|
|
121
|
+
# Summary
|
|
122
|
+
passed = sum(1 for r in results if r["success"])
|
|
123
|
+
failed = len(results) - passed
|
|
124
|
+
|
|
125
|
+
if json_output:
|
|
126
|
+
print(json.dumps({
|
|
127
|
+
"projects": results,
|
|
128
|
+
"total": len(results),
|
|
129
|
+
"passed": passed,
|
|
130
|
+
"failed": failed,
|
|
131
|
+
}, indent=2))
|
|
132
|
+
else:
|
|
133
|
+
print()
|
|
134
|
+
print(f" Updated: {passed}/{len(results)} projects" +
|
|
135
|
+
(f" ({failed} failed)" if failed else ""))
|
|
136
|
+
|
|
137
|
+
sys.exit(1 if failed else 0)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
main()
|
package/scripts/version_check.py
CHANGED
|
@@ -21,7 +21,10 @@ import sys
|
|
|
21
21
|
import time
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
25
|
+
from paths import VERSION_CHECK_FILE, STATE_FILE
|
|
26
|
+
|
|
27
|
+
CACHE_FILE = VERSION_CHECK_FILE
|
|
25
28
|
CACHE_TTL = 86400 # 24 hours
|
|
26
29
|
PACKAGE_NAME = "@softspark/ai-toolkit"
|
|
27
30
|
|
|
@@ -34,7 +37,7 @@ def _get_installed_version() -> str:
|
|
|
34
37
|
installed/updated, while package.json may be newer if the npm package
|
|
35
38
|
was upgraded but `ai-toolkit update` was not run yet.
|
|
36
39
|
"""
|
|
37
|
-
state_file =
|
|
40
|
+
state_file = STATE_FILE
|
|
38
41
|
if state_file.is_file():
|
|
39
42
|
try:
|
|
40
43
|
with open(state_file, encoding="utf-8") as f:
|