@heytherevibin/skillforge 0.8.0 → 0.10.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 +22 -0
- package/CONTRIBUTING.md +30 -19
- package/README.md +243 -235
- 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 +261 -22
- package/python/app/materialize.py +72 -4
- package/python/app/mcp_contract.py +13 -1
- package/python/app/mcp_server.py +124 -27
- package/python/app/route_cli.py +32 -13
- package/python/app/route_eval_harness.py +98 -0
- package/python/app/route_policies.py +110 -0
- package/python/app/route_quality.py +99 -0
- package/python/app/routing_signals.py +60 -0
- package/python/app/weights_cli.py +152 -0
- package/python/fixtures/route_eval/smoke.json +18 -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_quality.py +120 -0
- package/python/tests/test_routing_overlay.py +55 -0
- package/python/tests/test_routing_signals.py +35 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Project routing overlay: notes, excludes, boosts."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from app.route_policies import (
|
|
5
|
+
build_routing_overlay_payload,
|
|
6
|
+
merge_project_notes_into_route_query,
|
|
7
|
+
parse_routing_overlay,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_merge_notes_requires_project_root() -> None:
|
|
12
|
+
rq = merge_project_notes_into_route_query("hello", "note text", None)
|
|
13
|
+
assert rq == "hello"
|
|
14
|
+
rq2 = merge_project_notes_into_route_query("hello", "note text", "")
|
|
15
|
+
assert rq2 == "hello"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_merge_notes_prepends_when_project_set() -> None:
|
|
19
|
+
rq = merge_project_notes_into_route_query("task", "We use Django 5.", "/repo", max_chars=100)
|
|
20
|
+
assert rq.startswith("Project routing notes:\n")
|
|
21
|
+
assert "Django" in rq
|
|
22
|
+
assert "task" in rq
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_parse_routing_overlay_boost_clamp() -> None:
|
|
26
|
+
ex, boosts, notes = parse_routing_overlay(
|
|
27
|
+
{"routing_boosts": {"a": 9.0, "b": -9.0}},
|
|
28
|
+
by_name={"a": 1, "b": 1},
|
|
29
|
+
)
|
|
30
|
+
assert not ex
|
|
31
|
+
assert boosts["a"] == 2.0
|
|
32
|
+
assert boosts["b"] == -2.0
|
|
33
|
+
assert notes == ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_parse_exclude_unknown_with_audit() -> None:
|
|
37
|
+
audit = []
|
|
38
|
+
ex, _b, _n = parse_routing_overlay(
|
|
39
|
+
{"exclude_skills": ["ghost"]},
|
|
40
|
+
by_name={"real": 1},
|
|
41
|
+
audit_out=audit,
|
|
42
|
+
)
|
|
43
|
+
assert "ghost" not in ex
|
|
44
|
+
assert any(a.get("effect") == "unknown_skill" for a in audit)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_build_payload_none_when_empty() -> None:
|
|
48
|
+
assert build_routing_overlay_payload(
|
|
49
|
+
project_root="",
|
|
50
|
+
exclude_skills=frozenset(),
|
|
51
|
+
routing_boosts={},
|
|
52
|
+
project_notes_applied=False,
|
|
53
|
+
project_notes_len=0,
|
|
54
|
+
audit=[],
|
|
55
|
+
) is None
|
|
@@ -7,6 +7,7 @@ import pytest
|
|
|
7
7
|
from app.main import Skill, parse_skill_md
|
|
8
8
|
from app.routing_signals import (
|
|
9
9
|
build_route_query_text,
|
|
10
|
+
host_pick_shortlist_lines,
|
|
10
11
|
keyword_overlap_scores,
|
|
11
12
|
normalize_minmax,
|
|
12
13
|
skill_routing_card,
|
|
@@ -63,6 +64,40 @@ def test_keyword_overlap_scores() -> None:
|
|
|
63
64
|
assert sc[0] > sc[1]
|
|
64
65
|
|
|
65
66
|
|
|
67
|
+
def test_host_pick_shortlist_lines_basic() -> None:
|
|
68
|
+
facets = [
|
|
69
|
+
{
|
|
70
|
+
"name": "alpha-skill",
|
|
71
|
+
"title": "Alpha",
|
|
72
|
+
"cosine_similarity": 0.42,
|
|
73
|
+
"description_preview": "Does alpha testing patterns for flaky CI.",
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
md, rows = host_pick_shortlist_lines(
|
|
77
|
+
prompt="fix flaky tests",
|
|
78
|
+
route_query="fix flaky tests",
|
|
79
|
+
facet_rows=facets,
|
|
80
|
+
max_candidates=5,
|
|
81
|
+
line_chars=90,
|
|
82
|
+
)
|
|
83
|
+
assert "alpha-skill" in md
|
|
84
|
+
assert "fix flaky" in md
|
|
85
|
+
assert len(rows) == 1
|
|
86
|
+
assert rows[0]["name"] == "alpha-skill"
|
|
87
|
+
assert rows[0]["id"] == "alpha-skill"
|
|
88
|
+
assert rows[0]["rank"] == 1
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_normalize_host_picked_main() -> None:
|
|
92
|
+
from app.main import Skill, normalize_host_picked_names
|
|
93
|
+
|
|
94
|
+
a = Skill(name="a", title="A", description="", body="", source="bundled")
|
|
95
|
+
b = Skill(name="b", title="B", description="", body="", source="bundled")
|
|
96
|
+
by_name = {"a": a, "b": b}
|
|
97
|
+
assert normalize_host_picked_names(["b", "a", "b", "unknown"], by_name, 1) == ["b"]
|
|
98
|
+
assert normalize_host_picked_names([], by_name, 7) == []
|
|
99
|
+
|
|
100
|
+
|
|
66
101
|
def test_parse_skill_triggers(tmp_path) -> None:
|
|
67
102
|
md = tmp_path / "my-skill" / "SKILL.md"
|
|
68
103
|
md.parent.mkdir(parents=True, exist_ok=True)
|