agent2learn 0.1.2__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.
- agent2learn/__init__.py +3 -0
- agent2learn/_release.py +19 -0
- agent2learn/aipolicy.py +182 -0
- agent2learn/api.py +590 -0
- agent2learn/audit.py +358 -0
- agent2learn/auth/__init__.py +282 -0
- agent2learn/auth/cdp.py +1067 -0
- agent2learn/auth/paste.py +378 -0
- agent2learn/calendar.py +525 -0
- agent2learn/calibrate.py +347 -0
- agent2learn/check.py +1091 -0
- agent2learn/cli.py +2039 -0
- agent2learn/clock.py +39 -0
- agent2learn/config.py +205 -0
- agent2learn/console.py +229 -0
- agent2learn/convert.py +1223 -0
- agent2learn/doctor.py +1167 -0
- agent2learn/errors.py +32 -0
- agent2learn/ground.py +735 -0
- agent2learn/index.py +614 -0
- agent2learn/ingest.py +3229 -0
- agent2learn/locations.py +247 -0
- agent2learn/outlines.py +754 -0
- agent2learn/paths.py +683 -0
- agent2learn/pipeline.py +392 -0
- agent2learn/privacy.py +1123 -0
- agent2learn/schools/__init__.py +29 -0
- agent2learn/schools/_base.py +194 -0
- agent2learn/schools/generic.py +78 -0
- agent2learn/schools/uwaterloo.py +66 -0
- agent2learn/session.py +373 -0
- agent2learn/skills.py +1081 -0
- agent2learn/snapshot.py +399 -0
- agent2learn/submit.py +1047 -0
- agent2learn/transactions.py +157 -0
- agent2learn/upgrade.py +288 -0
- agent2learn/vault.py +1134 -0
- agent2learn-0.1.2.data/data/a2l-coursework/SKILL.md +52 -0
- agent2learn-0.1.2.data/data/a2l-setup/SKILL.md +27 -0
- agent2learn-0.1.2.data/data/a2l-study/SKILL.md +27 -0
- agent2learn-0.1.2.data/data/a2l-sync/SKILL.md +30 -0
- agent2learn-0.1.2.dist-info/METADATA +186 -0
- agent2learn-0.1.2.dist-info/RECORD +46 -0
- agent2learn-0.1.2.dist-info/WHEEL +4 -0
- agent2learn-0.1.2.dist-info/entry_points.txt +3 -0
- agent2learn-0.1.2.dist-info/licenses/LICENSE +202 -0
agent2learn/__init__.py
ADDED
agent2learn/_release.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Build-time release switches.
|
|
2
|
+
|
|
3
|
+
These are constants a release engineer sets deliberately, not user configuration and not
|
|
4
|
+
something a test may reach around. ``submit.py`` reads :data:`SUBMISSION_AVAILABLE` through
|
|
5
|
+
``submit.release_capability()``; tests exercise the upload path by passing an explicit
|
|
6
|
+
``SubmissionCapability`` instead, so the production check is never monkeypatched away.
|
|
7
|
+
|
|
8
|
+
``SUBMISSION_AVAILABLE`` stays ``False`` until a supervised, designated non-graded upload has
|
|
9
|
+
passed against a real instance for that exact release candidate. If the gate cannot pass, the
|
|
10
|
+
published build is rebuilt with the mutating path disabled and every artifact test is rerun.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Final
|
|
16
|
+
|
|
17
|
+
SUBMISSION_AVAILABLE: Final[bool] = False
|
|
18
|
+
|
|
19
|
+
__all__ = ["SUBMISSION_AVAILABLE"]
|
agent2learn/aipolicy.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# ruff: noqa: E501
|
|
2
|
+
"""Informational AI-policy surfacing from already-rendered local outlines."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from agent2learn import paths
|
|
13
|
+
|
|
14
|
+
AI_POLICY_SCHEMA_VERSION = 1
|
|
15
|
+
_KEYWORDS = re.compile(
|
|
16
|
+
r"generative ai|chatgpt|artificial intelligence|genai|large language model", re.IGNORECASE
|
|
17
|
+
)
|
|
18
|
+
_HEADING = re.compile(r"^\s{0,3}#{1,6}\s+")
|
|
19
|
+
_POLICY_START = "<!-- a2l:ai-policy:start -->"
|
|
20
|
+
_POLICY_END = "<!-- a2l:ai-policy:end -->"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def surface_ai_policy(
|
|
24
|
+
course_dir: Path, outline: Path | None, *, root: Path | None = None
|
|
25
|
+
) -> dict[str, object]:
|
|
26
|
+
"""Record a local observation without classifying, scoring, or enforcing a policy."""
|
|
27
|
+
record = _scan_outline(course_dir, outline)
|
|
28
|
+
_write_record(course_dir, record, root=root)
|
|
29
|
+
return record
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _scan_outline(course_dir: Path, outline: Path | None) -> dict[str, object]:
|
|
33
|
+
if outline is None:
|
|
34
|
+
return _unavailable()
|
|
35
|
+
try:
|
|
36
|
+
with open(os.fspath(paths.long_path(outline)), encoding="utf-8", newline="") as handle:
|
|
37
|
+
lines = handle.read().splitlines()
|
|
38
|
+
except (FileNotFoundError, OSError, UnicodeError):
|
|
39
|
+
return _unavailable()
|
|
40
|
+
|
|
41
|
+
heading_match = next(
|
|
42
|
+
(
|
|
43
|
+
(index, line)
|
|
44
|
+
for index, line in enumerate(lines)
|
|
45
|
+
if _HEADING.match(line) and _KEYWORDS.search(line)
|
|
46
|
+
),
|
|
47
|
+
None,
|
|
48
|
+
)
|
|
49
|
+
if heading_match is not None:
|
|
50
|
+
start, _ = heading_match
|
|
51
|
+
end = next(
|
|
52
|
+
(index for index in range(start + 1, len(lines)) if _HEADING.match(lines[index])),
|
|
53
|
+
len(lines),
|
|
54
|
+
)
|
|
55
|
+
text = "\n".join(lines[start:end]).strip()
|
|
56
|
+
return _found(course_dir, outline, text, start)
|
|
57
|
+
|
|
58
|
+
for offset, block in _paragraph_blocks(lines):
|
|
59
|
+
if _KEYWORDS.search("\n".join(block)):
|
|
60
|
+
return _found(course_dir, outline, "\n".join(block).strip(), offset)
|
|
61
|
+
return {
|
|
62
|
+
"schema_version": AI_POLICY_SCHEMA_VERSION,
|
|
63
|
+
"status": "not_found_in_scanned_outline",
|
|
64
|
+
"text": None,
|
|
65
|
+
"source": None,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _paragraph_blocks(lines: Sequence[str]) -> list[tuple[int, list[str]]]:
|
|
70
|
+
blocks: list[tuple[int, list[str]]] = []
|
|
71
|
+
current: list[str] = []
|
|
72
|
+
start = 0
|
|
73
|
+
for index, line in enumerate(lines):
|
|
74
|
+
if line.strip():
|
|
75
|
+
if not current:
|
|
76
|
+
start = index
|
|
77
|
+
current.append(line)
|
|
78
|
+
elif current:
|
|
79
|
+
blocks.append((start, current))
|
|
80
|
+
current = []
|
|
81
|
+
if current:
|
|
82
|
+
blocks.append((start, current))
|
|
83
|
+
return blocks
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _found(course_dir: Path, outline: Path, text: str, line: int) -> dict[str, object]:
|
|
87
|
+
try:
|
|
88
|
+
source = f"{outline.relative_to(course_dir).as_posix()}:{line + 1}"
|
|
89
|
+
except ValueError:
|
|
90
|
+
source = f"{outline.name}:{line + 1}"
|
|
91
|
+
return {
|
|
92
|
+
"schema_version": AI_POLICY_SCHEMA_VERSION,
|
|
93
|
+
"status": "found",
|
|
94
|
+
"text": text,
|
|
95
|
+
"source": source,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _unavailable() -> dict[str, object]:
|
|
100
|
+
return {
|
|
101
|
+
"schema_version": AI_POLICY_SCHEMA_VERSION,
|
|
102
|
+
"status": "outline_unavailable",
|
|
103
|
+
"text": None,
|
|
104
|
+
"source": None,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _write_record(course_dir: Path, record: dict[str, object], *, root: Path | None = None) -> None:
|
|
109
|
+
destination = course_dir / "_meta" / "ai_policy.json"
|
|
110
|
+
paths.ensure_dir(destination.parent, root=root)
|
|
111
|
+
paths.atomic_write_text(
|
|
112
|
+
destination,
|
|
113
|
+
json.dumps(record, ensure_ascii=False, sort_keys=True, indent=2, separators=(",", ": "))
|
|
114
|
+
+ "\n",
|
|
115
|
+
root=root,
|
|
116
|
+
)
|
|
117
|
+
_surface_index_line(course_dir, record, root=root)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def surface_course_ai_policy(
|
|
121
|
+
course_dir: Path, outlines: Sequence[Path], *, root: Path | None = None
|
|
122
|
+
) -> dict[str, object]:
|
|
123
|
+
"""Record the first found clause across successful local outline renders.
|
|
124
|
+
|
|
125
|
+
All supplied paths were successfully rendered by the outline boundary. An empty set is
|
|
126
|
+
therefore unavailable coverage; a non-empty set with no keyword is a scanned no-match.
|
|
127
|
+
"""
|
|
128
|
+
if not outlines:
|
|
129
|
+
return surface_ai_policy(course_dir, None, root=root)
|
|
130
|
+
last: dict[str, object] | None = None
|
|
131
|
+
for outline in sorted(outlines, key=lambda value: value.as_posix()):
|
|
132
|
+
record = _scan_outline(course_dir, outline)
|
|
133
|
+
if record["status"] == "found":
|
|
134
|
+
_write_record(course_dir, record, root=root)
|
|
135
|
+
return record
|
|
136
|
+
last = record
|
|
137
|
+
assert last is not None
|
|
138
|
+
_write_record(course_dir, last, root=root)
|
|
139
|
+
return last
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _surface_index_line(
|
|
143
|
+
course_dir: Path, record: dict[str, object], *, root: Path | None = None
|
|
144
|
+
) -> None:
|
|
145
|
+
index = course_dir / "INDEX.md"
|
|
146
|
+
try:
|
|
147
|
+
with open(os.fspath(paths.long_path(index)), encoding="utf-8", newline="") as handle:
|
|
148
|
+
lines = handle.read().splitlines()
|
|
149
|
+
except FileNotFoundError:
|
|
150
|
+
return
|
|
151
|
+
cleaned: list[str] = []
|
|
152
|
+
index_position = 0
|
|
153
|
+
while index_position < len(lines):
|
|
154
|
+
if lines[index_position] == _POLICY_START:
|
|
155
|
+
index_position += 1
|
|
156
|
+
while index_position < len(lines) and lines[index_position] != _POLICY_END:
|
|
157
|
+
index_position += 1
|
|
158
|
+
index_position += 1
|
|
159
|
+
continue
|
|
160
|
+
if lines[index_position].strip() == "## AI policy":
|
|
161
|
+
index_position += 1
|
|
162
|
+
while index_position < len(lines) and not _HEADING.match(lines[index_position]):
|
|
163
|
+
index_position += 1
|
|
164
|
+
continue
|
|
165
|
+
cleaned.append(lines[index_position])
|
|
166
|
+
index_position += 1
|
|
167
|
+
status = str(record["status"])
|
|
168
|
+
detail = str(record["source"]) if record["source"] is not None else status
|
|
169
|
+
while cleaned and not cleaned[-1].strip():
|
|
170
|
+
cleaned.pop()
|
|
171
|
+
cleaned.extend(
|
|
172
|
+
[
|
|
173
|
+
"",
|
|
174
|
+
_POLICY_START,
|
|
175
|
+
"## AI policy",
|
|
176
|
+
"",
|
|
177
|
+
f"- AI policy: {status} — {detail}",
|
|
178
|
+
_POLICY_END,
|
|
179
|
+
"",
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
paths.atomic_write_text(index, "\n".join(cleaned), root=root)
|