@heytherevibin/skillforge 0.7.0 → 0.10.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 +29 -0
- package/CONTRIBUTING.md +30 -19
- package/README.md +248 -198
- package/RELEASING.md +19 -7
- package/SECURITY.md +61 -13
- package/STRATEGY.md +40 -14
- package/bin/cli.js +112 -5
- package/ci/bundle-gate.json +4 -0
- package/lib/host-setup.js +312 -0
- package/lib/templates/claude-code-skillforge-global.md +19 -0
- package/lib/templates/cursor-skillforge-global.md +16 -0
- package/package.json +3 -2
- package/python/app/eval_cli.py +133 -0
- package/python/app/feedback_meta.py +96 -0
- package/python/app/health_cli.py +160 -0
- package/python/app/main.py +502 -26
- package/python/app/materialize.py +72 -4
- package/python/app/mcp_contract.py +13 -1
- package/python/app/mcp_server.py +344 -25
- package/python/app/route_cli.py +32 -13
- package/python/app/route_eval_harness.py +98 -0
- package/python/app/route_policies.py +243 -0
- package/python/app/route_quality.py +99 -0
- package/python/app/routing_signals.py +155 -0
- package/python/app/weights_cli.py +152 -0
- package/python/fixtures/route_eval/smoke.json +18 -0
- package/python/requirements.txt +1 -0
- package/python/tests/test_feedback_weights.py +77 -0
- package/python/tests/test_materialize.py +51 -0
- package/python/tests/test_mcp_contract.py +117 -0
- package/python/tests/test_route_eval_harness.py +45 -0
- package/python/tests/test_route_policies.py +115 -0
- package/python/tests/test_route_quality.py +120 -0
- package/python/tests/test_routing_overlay.py +55 -0
- package/python/tests/test_routing_signals.py +112 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Preflight / health checks for Skillforge (paths, catalog, optional full router load)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sqlite3
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from app.db_paths import resolve_orchestrator_db
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _bundled_skills_dir() -> Path:
|
|
15
|
+
return Path(os.getenv("SKILLFORGE_BUNDLED_SKILLS", "./skills"))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _user_skills_dir() -> Path:
|
|
19
|
+
return Path(os.getenv("SKILLFORGE_USER_SKILLS", str(Path.home() / ".skillforge" / "skills")))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _parse_args(argv: list[str] | None) -> argparse.Namespace:
|
|
23
|
+
p = argparse.ArgumentParser(
|
|
24
|
+
description="Check Skillforge install paths, skill catalog, and optionally load the embedding router."
|
|
25
|
+
)
|
|
26
|
+
p.add_argument(
|
|
27
|
+
"--quick",
|
|
28
|
+
action="store_true",
|
|
29
|
+
help="Skip embedding model load (fast; checks paths + skill file counts only).",
|
|
30
|
+
)
|
|
31
|
+
p.add_argument(
|
|
32
|
+
"--project-root",
|
|
33
|
+
default="",
|
|
34
|
+
help="If set, also checks <root>/.skillforge/ DB path resolution.",
|
|
35
|
+
)
|
|
36
|
+
p.add_argument("--json", action="store_true", help="Machine-readable output on stdout.")
|
|
37
|
+
return p.parse_args(argv)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _count_skill_md(root: Path) -> int:
|
|
41
|
+
if not root.is_dir():
|
|
42
|
+
return 0
|
|
43
|
+
return sum(1 for _ in root.glob("*/SKILL.md"))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run_health(*, quick: bool, project_root: str, json_out: bool) -> int:
|
|
47
|
+
checks: list[dict] = []
|
|
48
|
+
failed = False
|
|
49
|
+
|
|
50
|
+
bundled = _bundled_skills_dir()
|
|
51
|
+
user_skills = _user_skills_dir()
|
|
52
|
+
bundled_n = _count_skill_md(bundled)
|
|
53
|
+
|
|
54
|
+
b_ok = bundled.is_dir() and bundled_n > 0
|
|
55
|
+
checks.append({
|
|
56
|
+
"name": "bundled_skills",
|
|
57
|
+
"ok": b_ok,
|
|
58
|
+
"path": str(bundled.resolve()) if bundled.exists() else str(bundled),
|
|
59
|
+
"skill_md_count": bundled_n,
|
|
60
|
+
})
|
|
61
|
+
if not b_ok:
|
|
62
|
+
failed = True
|
|
63
|
+
|
|
64
|
+
user_n = _count_skill_md(user_skills)
|
|
65
|
+
u_ok = True
|
|
66
|
+
u_err: str | None = None
|
|
67
|
+
if not user_skills.is_dir():
|
|
68
|
+
try:
|
|
69
|
+
user_skills.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
except OSError as e:
|
|
71
|
+
u_ok = False
|
|
72
|
+
u_err = str(e)
|
|
73
|
+
failed = True
|
|
74
|
+
checks.append({
|
|
75
|
+
"name": "user_skills",
|
|
76
|
+
"ok": u_ok,
|
|
77
|
+
"path": str(user_skills),
|
|
78
|
+
"skill_md_count": user_n,
|
|
79
|
+
"error": u_err,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
pr = (project_root or "").strip() or None
|
|
83
|
+
db_path = resolve_orchestrator_db(pr)
|
|
84
|
+
db_ok = True
|
|
85
|
+
db_err: str | None = None
|
|
86
|
+
try:
|
|
87
|
+
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
con = sqlite3.connect(str(db_path))
|
|
89
|
+
try:
|
|
90
|
+
con.execute("SELECT 1")
|
|
91
|
+
finally:
|
|
92
|
+
con.close()
|
|
93
|
+
except OSError as e:
|
|
94
|
+
db_ok = False
|
|
95
|
+
db_err = str(e)
|
|
96
|
+
failed = True
|
|
97
|
+
checks.append({
|
|
98
|
+
"name": "orchestrator_db",
|
|
99
|
+
"ok": db_ok,
|
|
100
|
+
"path": str(db_path),
|
|
101
|
+
"error": db_err,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
router_skill_count: int | None = None
|
|
105
|
+
if not quick:
|
|
106
|
+
try:
|
|
107
|
+
from app.main import build_router_and_skills
|
|
108
|
+
|
|
109
|
+
_router, skills = build_router_and_skills(log=not json_out, log_prefix="[skillforge-health]")
|
|
110
|
+
router_skill_count = len(skills)
|
|
111
|
+
if router_skill_count <= 0:
|
|
112
|
+
failed = True
|
|
113
|
+
checks.append({
|
|
114
|
+
"name": "router_load",
|
|
115
|
+
"ok": router_skill_count > 0,
|
|
116
|
+
"skill_count": router_skill_count,
|
|
117
|
+
"error": None if router_skill_count and router_skill_count > 0 else "empty catalog",
|
|
118
|
+
})
|
|
119
|
+
except Exception as e:
|
|
120
|
+
failed = True
|
|
121
|
+
checks.append({
|
|
122
|
+
"name": "router_load",
|
|
123
|
+
"ok": False,
|
|
124
|
+
"skill_count": None,
|
|
125
|
+
"error": str(e),
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
payload = {
|
|
129
|
+
"ok": not failed,
|
|
130
|
+
"quick": quick,
|
|
131
|
+
"checks": checks,
|
|
132
|
+
}
|
|
133
|
+
if json_out:
|
|
134
|
+
print(json.dumps(payload, indent=2))
|
|
135
|
+
else:
|
|
136
|
+
for c in checks:
|
|
137
|
+
sym = "✓" if c.get("ok") else "✗"
|
|
138
|
+
print(f"{sym} {c['name']}", file=sys.stderr)
|
|
139
|
+
if c.get("path"):
|
|
140
|
+
print(f" path: {c['path']}", file=sys.stderr)
|
|
141
|
+
if c.get("skill_md_count") is not None:
|
|
142
|
+
print(f" SKILL.md count: {c['skill_md_count']}", file=sys.stderr)
|
|
143
|
+
if c.get("skill_count") is not None:
|
|
144
|
+
print(f" router skills: {c['skill_count']}", file=sys.stderr)
|
|
145
|
+
if c.get("error"):
|
|
146
|
+
print(f" error: {c['error']}", file=sys.stderr)
|
|
147
|
+
print("health: ok" if not failed else "health: failed", file=sys.stderr)
|
|
148
|
+
|
|
149
|
+
return 0 if not failed else 1
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def main(argv: list[str] | None = None) -> None:
|
|
153
|
+
args = _parse_args(argv)
|
|
154
|
+
raise SystemExit(
|
|
155
|
+
run_health(quick=bool(args.quick), project_root=args.project_root, json_out=bool(args.json))
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
main()
|