skillager 0.1.0__py3-none-any.whl

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.
skillager/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Skillager package."""
2
+
3
+ __version__ = "0.1.0"
skillager/__main__.py ADDED
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+
8
+ if __name__ == "__main__":
9
+ raise SystemExit(main(sys.argv[1:]))
skillager/audience.py ADDED
@@ -0,0 +1,133 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class AudienceSignal:
9
+ audience: str
10
+ weight: int
11
+ reason: str
12
+
13
+
14
+ DEV_SIGNALS = [
15
+ ("commit", 3, "mentions commit workflow"),
16
+ ("pre-land", 3, "mentions pre-land workflow"),
17
+ ("land this", 3, "mentions landing workflow"),
18
+ ("ship it", 2, "mentions shipping workflow"),
19
+ ("review gate", 3, "mentions review gate"),
20
+ ("code-review", 3, "mentions code review"),
21
+ ("code review", 3, "mentions code review"),
22
+ ("maintainer", 3, "mentions maintainer work"),
23
+ ("kernel", 2, "mentions kernel work"),
24
+ ("cuda", 2, "mentions CUDA development"),
25
+ ("dispatch", 2, "mentions internal dispatch"),
26
+ ("wiring", 2, "mentions internal wiring"),
27
+ ("autonomous-execution", 2, "mentions agent execution workflow"),
28
+ ("intake-router", 2, "mentions internal routing"),
29
+ ("precision-compliance", 2, "mentions precision/compliance workflow"),
30
+ ]
31
+
32
+ USER_SIGNALS = [
33
+ ("gis-domain", 3, "mentions GIS domain guidance"),
34
+ ("domain", 2, "mentions domain guidance"),
35
+ ("concept", 2, "mentions conceptual guidance"),
36
+ ("library usage", 3, "mentions library usage"),
37
+ ("how to use", 3, "mentions usage guidance"),
38
+ ("api", 2, "mentions API usage"),
39
+ ("example", 1, "mentions examples"),
40
+ ("tutorial", 2, "mentions tutorial guidance"),
41
+ ("dataframe", 2, "mentions dataframe usage"),
42
+ ]
43
+
44
+
45
+ def classify_audience(skill: Any) -> dict[str, Any]:
46
+ """Classify intended audience using only inert metadata and path-derived signals."""
47
+ text = _classification_text(skill)
48
+ signals = _signals(text)
49
+ declared = _declared_audiences(skill)
50
+ for audience in declared:
51
+ signals.append(AudienceSignal(audience, 2, f"declared audience: {audience}"))
52
+
53
+ scores: dict[str, int] = {"user": 0, "dev": 0}
54
+ reasons: dict[str, list[str]] = {"user": [], "dev": []}
55
+ for signal in signals:
56
+ if signal.audience not in scores:
57
+ continue
58
+ scores[signal.audience] += signal.weight
59
+ if signal.reason not in reasons[signal.audience]:
60
+ reasons[signal.audience].append(signal.reason)
61
+
62
+ audience = "unknown"
63
+ confidence = "low"
64
+ selected_reasons: list[str] = []
65
+ if scores["dev"] > scores["user"] and scores["dev"] >= 2:
66
+ audience = "dev"
67
+ confidence = _confidence(scores["dev"], scores["user"])
68
+ selected_reasons = reasons["dev"][:3]
69
+ elif scores["user"] > scores["dev"] and scores["user"] >= 2:
70
+ audience = "user"
71
+ confidence = _confidence(scores["user"], scores["dev"])
72
+ selected_reasons = reasons["user"][:3]
73
+ elif declared:
74
+ audience = "unknown"
75
+ selected_reasons = [f"conflicting or weak declared audience: {', '.join(declared)}"]
76
+ else:
77
+ selected_reasons = ["no strong audience signals in metadata"]
78
+
79
+ return {
80
+ "audience": audience,
81
+ "confidence": confidence,
82
+ "reasons": selected_reasons,
83
+ "scores": scores,
84
+ "method": "metadata-heuristic",
85
+ }
86
+
87
+
88
+ def _classification_text(skill: Any) -> str:
89
+ parts = [
90
+ getattr(skill, "id", ""),
91
+ getattr(skill, "name", ""),
92
+ getattr(skill, "summary", ""),
93
+ str(getattr(skill, "entrypoint", "")),
94
+ str(getattr(skill, "root", "")),
95
+ str(getattr(skill, "package", "") or ""),
96
+ ]
97
+ source = getattr(skill, "source", {}) or {}
98
+ if isinstance(source, dict):
99
+ parts.extend(str(value) for value in source.values())
100
+ return " ".join(parts).lower().replace("_", "-")
101
+
102
+
103
+ def _declared_audiences(skill: Any) -> list[str]:
104
+ if getattr(skill, "inferred", False):
105
+ return []
106
+ result = []
107
+ for item in getattr(skill, "audience", []) or []:
108
+ value = item.lower()
109
+ if value in {"developer", "maintainer", "maintainers"}:
110
+ value = "dev"
111
+ if value in {"user", "dev"} and value not in result:
112
+ result.append(value)
113
+ return result
114
+
115
+
116
+ def _signals(text: str) -> list[AudienceSignal]:
117
+ result: list[AudienceSignal] = []
118
+ for needle, weight, reason in DEV_SIGNALS:
119
+ if needle in text:
120
+ result.append(AudienceSignal("dev", weight, reason))
121
+ for needle, weight, reason in USER_SIGNALS:
122
+ if needle in text:
123
+ result.append(AudienceSignal("user", weight, reason))
124
+ return result
125
+
126
+
127
+ def _confidence(score: int, other_score: int) -> str:
128
+ margin = score - other_score
129
+ if score >= 5 and margin >= 3:
130
+ return "high"
131
+ if score >= 3 and margin >= 2:
132
+ return "medium"
133
+ return "low"