lessonweaver 0.3.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.
- lessonweaver/__init__.py +132 -0
- lessonweaver/analysis.py +167 -0
- lessonweaver/cli.py +771 -0
- lessonweaver/compile.py +86 -0
- lessonweaver/detection.py +242 -0
- lessonweaver/export.py +383 -0
- lessonweaver/governance.py +49 -0
- lessonweaver/interview.py +430 -0
- lessonweaver/lint.py +145 -0
- lessonweaver/loader.py +66 -0
- lessonweaver/models.py +708 -0
- lessonweaver/privacy.py +26 -0
- lessonweaver/py.typed +0 -0
- lessonweaver/registry.py +175 -0
- lessonweaver/reporting.py +87 -0
- lessonweaver/retrieval.py +117 -0
- lessonweaver/traces.py +66 -0
- lessonweaver/validation.py +220 -0
- lessonweaver-0.3.0.dist-info/METADATA +420 -0
- lessonweaver-0.3.0.dist-info/RECORD +24 -0
- lessonweaver-0.3.0.dist-info/WHEEL +5 -0
- lessonweaver-0.3.0.dist-info/entry_points.txt +2 -0
- lessonweaver-0.3.0.dist-info/licenses/LICENSE +201 -0
- lessonweaver-0.3.0.dist-info/top_level.txt +1 -0
lessonweaver/__init__.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""lessonweaver public API."""
|
|
2
|
+
|
|
3
|
+
from .analysis import AnalysisFinding, SkillAnalyzer
|
|
4
|
+
from .compile import CompiledContext, InclusionLevel, SkillCompiler
|
|
5
|
+
from .detection import LessonDetector
|
|
6
|
+
from .export import (
|
|
7
|
+
export_agents_md_fragment,
|
|
8
|
+
export_claude_md_snippet,
|
|
9
|
+
export_claude_rule_fragment,
|
|
10
|
+
export_claude_skill_fragment,
|
|
11
|
+
export_claude_skill_md,
|
|
12
|
+
export_codex_skill_directory,
|
|
13
|
+
export_copilot_instruction_fragment,
|
|
14
|
+
export_copilot_path_instruction,
|
|
15
|
+
export_copilot_repo_instruction,
|
|
16
|
+
export_eval_spec_markdown,
|
|
17
|
+
export_guardrail_rule_markdown,
|
|
18
|
+
export_operational_lesson_markdown,
|
|
19
|
+
export_runtime_prompt_snippet,
|
|
20
|
+
export_skillcard_json,
|
|
21
|
+
export_skillcard_markdown,
|
|
22
|
+
export_workflow_recommendation_markdown,
|
|
23
|
+
)
|
|
24
|
+
from .governance import can_promote_skill, promote_skill
|
|
25
|
+
from .interview import LessonInterviewer, apply_review_answer, load_session, save_session
|
|
26
|
+
from .lint import LintFinding, LintSeverity, SkillLinter
|
|
27
|
+
from .loader import SkillLoader
|
|
28
|
+
from .models import (
|
|
29
|
+
ExportArtifact,
|
|
30
|
+
ExportFormat,
|
|
31
|
+
LessonCandidate,
|
|
32
|
+
LessonStatus,
|
|
33
|
+
LoadingPolicy,
|
|
34
|
+
OperationalLesson,
|
|
35
|
+
RecommendedActionType,
|
|
36
|
+
ReviewAnswer,
|
|
37
|
+
ReviewOption,
|
|
38
|
+
ReviewQuestion,
|
|
39
|
+
ReviewSession,
|
|
40
|
+
RiskLevel,
|
|
41
|
+
Scope,
|
|
42
|
+
SensitivityLevel,
|
|
43
|
+
SkillCard,
|
|
44
|
+
SkillStatus,
|
|
45
|
+
SkillUsageEvent,
|
|
46
|
+
StaleSkillReport,
|
|
47
|
+
TraceBundle,
|
|
48
|
+
TraceEvent,
|
|
49
|
+
TraceEventType,
|
|
50
|
+
)
|
|
51
|
+
from .privacy import SimpleRedactor
|
|
52
|
+
from .registry import FileSystemRegistry, LessonRegistry
|
|
53
|
+
from .reporting import SkillReporter
|
|
54
|
+
from .retrieval import RetrievalQuery, RetrievalResult, SkillRetriever
|
|
55
|
+
from .traces import load_trace_bundle, validate_trace_dict
|
|
56
|
+
from .validation import (
|
|
57
|
+
SkillEvalResult,
|
|
58
|
+
ValidationExample,
|
|
59
|
+
ValidationResult,
|
|
60
|
+
ValidationSuite,
|
|
61
|
+
run_validation_suite,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"AnalysisFinding",
|
|
66
|
+
"CompiledContext",
|
|
67
|
+
"ExportArtifact",
|
|
68
|
+
"ExportFormat",
|
|
69
|
+
"FileSystemRegistry",
|
|
70
|
+
"InclusionLevel",
|
|
71
|
+
"LessonCandidate",
|
|
72
|
+
"LessonDetector",
|
|
73
|
+
"LessonInterviewer",
|
|
74
|
+
"LessonRegistry",
|
|
75
|
+
"LessonStatus",
|
|
76
|
+
"LintFinding",
|
|
77
|
+
"LintSeverity",
|
|
78
|
+
"LoadingPolicy",
|
|
79
|
+
"OperationalLesson",
|
|
80
|
+
"RecommendedActionType",
|
|
81
|
+
"RetrievalQuery",
|
|
82
|
+
"RetrievalResult",
|
|
83
|
+
"ReviewAnswer",
|
|
84
|
+
"ReviewOption",
|
|
85
|
+
"ReviewQuestion",
|
|
86
|
+
"ReviewSession",
|
|
87
|
+
"RiskLevel",
|
|
88
|
+
"Scope",
|
|
89
|
+
"SensitivityLevel",
|
|
90
|
+
"SimpleRedactor",
|
|
91
|
+
"SkillAnalyzer",
|
|
92
|
+
"SkillCard",
|
|
93
|
+
"SkillCompiler",
|
|
94
|
+
"SkillEvalResult",
|
|
95
|
+
"SkillLinter",
|
|
96
|
+
"SkillLoader",
|
|
97
|
+
"SkillReporter",
|
|
98
|
+
"SkillRetriever",
|
|
99
|
+
"SkillStatus",
|
|
100
|
+
"SkillUsageEvent",
|
|
101
|
+
"StaleSkillReport",
|
|
102
|
+
"TraceBundle",
|
|
103
|
+
"TraceEvent",
|
|
104
|
+
"TraceEventType",
|
|
105
|
+
"ValidationExample",
|
|
106
|
+
"ValidationResult",
|
|
107
|
+
"ValidationSuite",
|
|
108
|
+
"apply_review_answer",
|
|
109
|
+
"can_promote_skill",
|
|
110
|
+
"export_agents_md_fragment",
|
|
111
|
+
"export_claude_md_snippet",
|
|
112
|
+
"export_claude_rule_fragment",
|
|
113
|
+
"export_claude_skill_fragment",
|
|
114
|
+
"export_claude_skill_md",
|
|
115
|
+
"export_codex_skill_directory",
|
|
116
|
+
"export_copilot_instruction_fragment",
|
|
117
|
+
"export_copilot_path_instruction",
|
|
118
|
+
"export_copilot_repo_instruction",
|
|
119
|
+
"export_eval_spec_markdown",
|
|
120
|
+
"export_guardrail_rule_markdown",
|
|
121
|
+
"export_operational_lesson_markdown",
|
|
122
|
+
"export_runtime_prompt_snippet",
|
|
123
|
+
"export_skillcard_json",
|
|
124
|
+
"export_skillcard_markdown",
|
|
125
|
+
"export_workflow_recommendation_markdown",
|
|
126
|
+
"load_session",
|
|
127
|
+
"load_trace_bundle",
|
|
128
|
+
"promote_skill",
|
|
129
|
+
"run_validation_suite",
|
|
130
|
+
"save_session",
|
|
131
|
+
"validate_trace_dict",
|
|
132
|
+
]
|
lessonweaver/analysis.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Deterministic analysis for duplicate, overlapping, and conflicting skills."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import itertools
|
|
6
|
+
import re
|
|
7
|
+
import string
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
from .models import SkillCard
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(slots=True)
|
|
14
|
+
class AnalysisFinding:
|
|
15
|
+
finding_type: str
|
|
16
|
+
skill_id_a: str
|
|
17
|
+
skill_id_b: str
|
|
18
|
+
reason: str
|
|
19
|
+
confidence: float
|
|
20
|
+
|
|
21
|
+
def to_dict(self) -> dict[str, str | float]:
|
|
22
|
+
return {
|
|
23
|
+
"finding_type": self.finding_type,
|
|
24
|
+
"skill_id_a": self.skill_id_a,
|
|
25
|
+
"skill_id_b": self.skill_id_b,
|
|
26
|
+
"reason": self.reason,
|
|
27
|
+
"confidence": self.confidence,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_TOKEN_RE = re.compile(r"[A-Za-z0-9_']+")
|
|
32
|
+
_STOPWORDS = {
|
|
33
|
+
"a",
|
|
34
|
+
"an",
|
|
35
|
+
"and",
|
|
36
|
+
"before",
|
|
37
|
+
"for",
|
|
38
|
+
"if",
|
|
39
|
+
"in",
|
|
40
|
+
"is",
|
|
41
|
+
"it",
|
|
42
|
+
"must",
|
|
43
|
+
"not",
|
|
44
|
+
"or",
|
|
45
|
+
"the",
|
|
46
|
+
"to",
|
|
47
|
+
"when",
|
|
48
|
+
}
|
|
49
|
+
_POSITIVE_MODALS = {"must", "always", "required"}
|
|
50
|
+
_NEGATIVE_MARKERS = {"never", "avoid"}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _normalize_name(value: str) -> str:
|
|
54
|
+
table = str.maketrans("", "", string.punctuation)
|
|
55
|
+
return " ".join(value.lower().translate(table).split())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _tokens(value: str) -> set[str]:
|
|
59
|
+
return set(_token_list(value))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _token_list(value: str) -> list[str]:
|
|
63
|
+
return [token.lower() for token in _TOKEN_RE.findall(value)]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _applies_tokens(skill: SkillCard) -> set[str]:
|
|
67
|
+
return (
|
|
68
|
+
set().union(*[_tokens(item) for item in skill.applies_when])
|
|
69
|
+
if skill.applies_when
|
|
70
|
+
else set()
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _jaccard(left: set[str], right: set[str]) -> float:
|
|
75
|
+
if not left or not right:
|
|
76
|
+
return 0.0
|
|
77
|
+
return len(left & right) / len(left | right)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _instruction_tokens(skill: SkillCard) -> set[str]:
|
|
81
|
+
tokens = (
|
|
82
|
+
set().union(*[_tokens(item) for item in skill.instructions])
|
|
83
|
+
if skill.instructions
|
|
84
|
+
else set()
|
|
85
|
+
)
|
|
86
|
+
return {token for token in tokens if token not in _STOPWORDS}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _has_positive_modal(skill: SkillCard) -> bool:
|
|
90
|
+
tokens = _token_list(" ".join(skill.instructions))
|
|
91
|
+
for index, token in enumerate(tokens):
|
|
92
|
+
if token == "must" and _next_token(tokens, index) != "not":
|
|
93
|
+
return True
|
|
94
|
+
if token == "required" and _previous_token(tokens, index) != "not":
|
|
95
|
+
return True
|
|
96
|
+
if token == "always":
|
|
97
|
+
return True
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _has_negative_modal(skill: SkillCard) -> bool:
|
|
102
|
+
text = " ".join(skill.instructions).lower()
|
|
103
|
+
tokens = _tokens(text)
|
|
104
|
+
return bool(tokens & _NEGATIVE_MARKERS) or "must not" in text or "do not" in text
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _previous_token(tokens: list[str], index: int) -> str:
|
|
108
|
+
return tokens[index - 1] if index > 0 else ""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _next_token(tokens: list[str], index: int) -> str:
|
|
112
|
+
return tokens[index + 1] if index + 1 < len(tokens) else ""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _contradicts(left: SkillCard, right: SkillCard) -> bool:
|
|
116
|
+
left_subjects = _instruction_tokens(left)
|
|
117
|
+
right_subjects = _instruction_tokens(right)
|
|
118
|
+
if not left_subjects & right_subjects:
|
|
119
|
+
return False
|
|
120
|
+
return (_has_positive_modal(left) and _has_negative_modal(right)) or (
|
|
121
|
+
_has_negative_modal(left) and _has_positive_modal(right)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class SkillAnalyzer:
|
|
126
|
+
"""Find deterministic duplicate, overlap, and contradiction candidates."""
|
|
127
|
+
|
|
128
|
+
def analyze(self, skills: list[SkillCard]) -> list[AnalysisFinding]:
|
|
129
|
+
findings: list[AnalysisFinding] = []
|
|
130
|
+
for left, right in itertools.combinations(skills, 2):
|
|
131
|
+
if _normalize_name(left.name) == _normalize_name(right.name):
|
|
132
|
+
findings.append(
|
|
133
|
+
AnalysisFinding(
|
|
134
|
+
"duplicate",
|
|
135
|
+
left.id,
|
|
136
|
+
right.id,
|
|
137
|
+
"Skills have identical normalized names.",
|
|
138
|
+
1.0,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
left_applies = _applies_tokens(left)
|
|
143
|
+
right_applies = _applies_tokens(right)
|
|
144
|
+
overlap = _jaccard(left_applies, right_applies)
|
|
145
|
+
if overlap >= 0.5:
|
|
146
|
+
findings.append(
|
|
147
|
+
AnalysisFinding(
|
|
148
|
+
"overlap",
|
|
149
|
+
left.id,
|
|
150
|
+
right.id,
|
|
151
|
+
"Skills share at least half of their applies_when tokens.",
|
|
152
|
+
overlap,
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if overlap >= 0.3 and _contradicts(left, right):
|
|
157
|
+
findings.append(
|
|
158
|
+
AnalysisFinding(
|
|
159
|
+
"contradiction",
|
|
160
|
+
left.id,
|
|
161
|
+
right.id,
|
|
162
|
+
"Skills contain conflicting modal guidance over overlapping applicability.",
|
|
163
|
+
max(0.7, overlap),
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return findings
|