self-evolve-framework 1.5.0 → 1.6.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/README.md +33 -23
- package/bin/cli.js +24 -67
- package/package.json +1 -1
- package/template/rules/knowledge/README.md +56 -0
- package/template/rules/knowledge/ant-design-vue.mdc +54 -0
- package/template/rules/knowledge/general.mdc +56 -0
- package/template/rules/knowledge/rust.mdc +55 -0
- package/template/rules/knowledge/shadcn-svelte.mdc +64 -0
- package/template/rules/knowledge/svelte.mdc +135 -0
- package/template/rules/knowledge/tailwind.mdc +66 -0
- package/template/rules/knowledge/tauri.mdc +65 -0
- package/template/rules/knowledge/typescript.mdc +41 -0
- package/template/rules/knowledge/vue.mdc +48 -0
- package/template/rules/ponytail.mdc +23 -98
- package/template/rules/self-evolve.mdc +24 -0
- package/template/skills/impeccable/SKILL.md +47 -174
- package/template/skills/skillopt-sleep/SKILL.md +34 -48
- package/template/skills/impeccable/agents/impeccable_asset_producer.toml +0 -92
- package/template/skills/impeccable/agents/impeccable_manual_edit_applier.toml +0 -95
- package/template/skills/impeccable/agents/openai.yaml +0 -4
- package/template/skills/impeccable/reference/adapt.md +0 -311
- package/template/skills/impeccable/reference/animate.md +0 -201
- package/template/skills/impeccable/reference/audit.md +0 -133
- package/template/skills/impeccable/reference/bolder.md +0 -113
- package/template/skills/impeccable/reference/brand.md +0 -108
- package/template/skills/impeccable/reference/clarify.md +0 -288
- package/template/skills/impeccable/reference/codex.md +0 -105
- package/template/skills/impeccable/reference/colorize.md +0 -257
- package/template/skills/impeccable/reference/craft.md +0 -123
- package/template/skills/impeccable/reference/critique.md +0 -790
- package/template/skills/impeccable/reference/delight.md +0 -302
- package/template/skills/impeccable/reference/distill.md +0 -111
- package/template/skills/impeccable/reference/document.md +0 -429
- package/template/skills/impeccable/reference/extract.md +0 -69
- package/template/skills/impeccable/reference/harden.md +0 -347
- package/template/skills/impeccable/reference/hooks.md +0 -90
- package/template/skills/impeccable/reference/init.md +0 -172
- package/template/skills/impeccable/reference/interaction-design.md +0 -189
- package/template/skills/impeccable/reference/layout.md +0 -161
- package/template/skills/impeccable/reference/live.md +0 -720
- package/template/skills/impeccable/reference/onboard.md +0 -234
- package/template/skills/impeccable/reference/optimize.md +0 -258
- package/template/skills/impeccable/reference/overdrive.md +0 -130
- package/template/skills/impeccable/reference/polish.md +0 -241
- package/template/skills/impeccable/reference/product.md +0 -60
- package/template/skills/impeccable/reference/quieter.md +0 -99
- package/template/skills/impeccable/reference/shape.md +0 -165
- package/template/skills/impeccable/reference/typeset.md +0 -279
- package/template/skills/impeccable/scripts/command-metadata.json +0 -94
- package/template/skills/impeccable/scripts/context-signals.mjs +0 -225
- package/template/skills/impeccable/scripts/context.mjs +0 -961
- package/template/skills/impeccable/scripts/critique-storage.mjs +0 -242
- package/template/skills/impeccable/scripts/detect-csp.mjs +0 -198
- package/template/skills/impeccable/scripts/detect.mjs +0 -21
- package/template/skills/impeccable/scripts/detector/browser/injected/index.mjs +0 -1937
- package/template/skills/impeccable/scripts/detector/cli/main.mjs +0 -290
- package/template/skills/impeccable/scripts/detector/design-system.mjs +0 -750
- package/template/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +0 -5138
- package/template/skills/impeccable/scripts/detector/detect-antipatterns.mjs +0 -50
- package/template/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +0 -277
- package/template/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +0 -568
- package/template/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +0 -1015
- package/template/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +0 -234
- package/template/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +0 -189
- package/template/skills/impeccable/scripts/detector/findings.mjs +0 -12
- package/template/skills/impeccable/scripts/detector/node/file-system.mjs +0 -198
- package/template/skills/impeccable/scripts/detector/profile/profiler.mjs +0 -166
- package/template/skills/impeccable/scripts/detector/registry/antipatterns.mjs +0 -448
- package/template/skills/impeccable/scripts/detector/rules/checks.mjs +0 -2671
- package/template/skills/impeccable/scripts/detector/shared/color.mjs +0 -124
- package/template/skills/impeccable/scripts/detector/shared/constants.mjs +0 -101
- package/template/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +0 -148
- package/template/skills/impeccable/scripts/detector/shared/page.mjs +0 -7
- package/template/skills/impeccable/scripts/hook-admin.mjs +0 -661
- package/template/skills/impeccable/scripts/hook-before-edit.mjs +0 -476
- package/template/skills/impeccable/scripts/hook-lib.mjs +0 -1632
- package/template/skills/impeccable/scripts/hook.mjs +0 -61
- package/template/skills/impeccable/scripts/lib/design-parser.mjs +0 -842
- package/template/skills/impeccable/scripts/lib/impeccable-config.mjs +0 -638
- package/template/skills/impeccable/scripts/lib/impeccable-paths.mjs +0 -128
- package/template/skills/impeccable/scripts/lib/is-generated.mjs +0 -69
- package/template/skills/impeccable/scripts/lib/target-args.mjs +0 -42
- package/template/skills/impeccable/scripts/live/browser-script-parts.mjs +0 -49
- package/template/skills/impeccable/scripts/live/completion.mjs +0 -19
- package/template/skills/impeccable/scripts/live/event-validation.mjs +0 -137
- package/template/skills/impeccable/scripts/live/insert-ui.mjs +0 -458
- package/template/skills/impeccable/scripts/live/manual-apply.mjs +0 -939
- package/template/skills/impeccable/scripts/live/manual-edit-routes.mjs +0 -357
- package/template/skills/impeccable/scripts/live/manual-edits-buffer.mjs +0 -152
- package/template/skills/impeccable/scripts/live/session-store.mjs +0 -289
- package/template/skills/impeccable/scripts/live/svelte-component.mjs +0 -826
- package/template/skills/impeccable/scripts/live/sveltekit-adapter.mjs +0 -274
- package/template/skills/impeccable/scripts/live/ui-core.mjs +0 -180
- package/template/skills/impeccable/scripts/live/vocabulary.mjs +0 -36
- package/template/skills/impeccable/scripts/live-accept.mjs +0 -812
- package/template/skills/impeccable/scripts/live-browser-dom.js +0 -146
- package/template/skills/impeccable/scripts/live-browser-session.js +0 -123
- package/template/skills/impeccable/scripts/live-browser.js +0 -11173
- package/template/skills/impeccable/scripts/live-commit-manual-edits.mjs +0 -1241
- package/template/skills/impeccable/scripts/live-complete.mjs +0 -75
- package/template/skills/impeccable/scripts/live-copy-edit-agent.mjs +0 -683
- package/template/skills/impeccable/scripts/live-discard-manual-edits.mjs +0 -51
- package/template/skills/impeccable/scripts/live-inject.mjs +0 -583
- package/template/skills/impeccable/scripts/live-insert.mjs +0 -272
- package/template/skills/impeccable/scripts/live-manual-edit-evidence.mjs +0 -363
- package/template/skills/impeccable/scripts/live-poll.mjs +0 -384
- package/template/skills/impeccable/scripts/live-resume.mjs +0 -94
- package/template/skills/impeccable/scripts/live-server.mjs +0 -1135
- package/template/skills/impeccable/scripts/live-status.mjs +0 -61
- package/template/skills/impeccable/scripts/live-target.mjs +0 -30
- package/template/skills/impeccable/scripts/live-wrap.mjs +0 -894
- package/template/skills/impeccable/scripts/live.mjs +0 -297
- package/template/skills/impeccable/scripts/modern-screenshot.umd.js +0 -14
- package/template/skills/impeccable/scripts/palette.mjs +0 -633
- package/template/skills/impeccable/scripts/pin.mjs +0 -214
- package/template/skills/skillopt-sleep/scripts/python/__init__.py +0 -20
- package/template/skills/skillopt-sleep/scripts/python/__main__.py +0 -343
- package/template/skills/skillopt-sleep/scripts/python/backend.py +0 -1371
- package/template/skills/skillopt-sleep/scripts/python/budget.py +0 -75
- package/template/skills/skillopt-sleep/scripts/python/config.py +0 -162
- package/template/skills/skillopt-sleep/scripts/python/consolidate.py +0 -238
- package/template/skills/skillopt-sleep/scripts/python/cycle.py +0 -291
- package/template/skills/skillopt-sleep/scripts/python/dream.py +0 -138
- package/template/skills/skillopt-sleep/scripts/python/experiments/__init__.py +0 -1
- package/template/skills/skillopt-sleep/scripts/python/experiments/gbrain_bench.py +0 -119
- package/template/skills/skillopt-sleep/scripts/python/experiments/personas.py +0 -86
- package/template/skills/skillopt-sleep/scripts/python/experiments/report.py +0 -132
- package/template/skills/skillopt-sleep/scripts/python/experiments/run_experiment.py +0 -178
- package/template/skills/skillopt-sleep/scripts/python/experiments/run_gbrain.py +0 -209
- package/template/skills/skillopt-sleep/scripts/python/experiments/run_transfer.py +0 -155
- package/template/skills/skillopt-sleep/scripts/python/experiments/sweep.py +0 -164
- package/template/skills/skillopt-sleep/scripts/python/gate.py +0 -50
- package/template/skills/skillopt-sleep/scripts/python/harvest.py +0 -304
- package/template/skills/skillopt-sleep/scripts/python/harvest_codex.py +0 -253
- package/template/skills/skillopt-sleep/scripts/python/harvest_sources.py +0 -41
- package/template/skills/skillopt-sleep/scripts/python/judges.py +0 -84
- package/template/skills/skillopt-sleep/scripts/python/llm_miner.py +0 -134
- package/template/skills/skillopt-sleep/scripts/python/memory.py +0 -129
- package/template/skills/skillopt-sleep/scripts/python/mine.py +0 -312
- package/template/skills/skillopt-sleep/scripts/python/replay.py +0 -146
- package/template/skills/skillopt-sleep/scripts/python/rollout.py +0 -153
- package/template/skills/skillopt-sleep/scripts/python/scheduler.py +0 -138
- package/template/skills/skillopt-sleep/scripts/python/slow_update.py +0 -142
- package/template/skills/skillopt-sleep/scripts/python/staging.py +0 -103
- package/template/skills/skillopt-sleep/scripts/python/state.py +0 -96
- package/template/skills/skillopt-sleep/scripts/python/tasks_file.py +0 -81
- package/template/skills/skillopt-sleep/scripts/python/types.py +0 -146
- package/template/skills/skillopt-sleep/scripts/shell/__init__.py +0 -0
- package/template/skills/skillopt-sleep/scripts/shell/eval_only.py +0 -466
- package/template/skills/skillopt-sleep/scripts/shell/materialize_searchqa.py +0 -148
- package/template/skills/skillopt-sleep/scripts/shell/run_alfworld.sh +0 -60
- package/template/skills/skillopt-sleep/scripts/shell/run_searchqa.sh +0 -40
- package/template/skills/skillopt-sleep/scripts/shell/run_spreadsheetbench.sh +0 -39
- package/template/skills/skillopt-sleep/scripts/shell/train.py +0 -556
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
"""SkillOpt-Sleep Codex Desktop session harvesting.
|
|
2
|
-
|
|
3
|
-
Reads Codex Desktop archived session JSONL files and normalizes them into
|
|
4
|
-
``SessionDigest`` records without copying developer/system instructions, tool
|
|
5
|
-
arguments, or raw tool outputs.
|
|
6
|
-
"""
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import os
|
|
10
|
-
import re
|
|
11
|
-
from typing import Any, Dict, Iterable, List, Optional
|
|
12
|
-
|
|
13
|
-
from skillopt_sleep.harvest import (
|
|
14
|
-
_detect_feedback,
|
|
15
|
-
_is_meta_prompt,
|
|
16
|
-
_iter_jsonl,
|
|
17
|
-
_project_matches,
|
|
18
|
-
)
|
|
19
|
-
from skillopt_sleep.types import SessionDigest
|
|
20
|
-
|
|
21
|
-
_SECRET_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
22
|
-
(re.compile(r"sk-[A-Za-z0-9_-]{10,}"), "[REDACTED_OPENAI_KEY]"),
|
|
23
|
-
(re.compile(r"(?i)(Authorization:\s*Bearer\s+)[^\s\"']+"), r"\1[REDACTED]"),
|
|
24
|
-
(re.compile(r"(?i)(Authorization:\s*Basic\s+)[^\s\"']+"), r"\1[REDACTED]"),
|
|
25
|
-
(
|
|
26
|
-
re.compile(r"(?i)\b(api[_-]?key|token|password|secret)\b(\s*[:=]\s*)[^\s\"']+"),
|
|
27
|
-
r"\1\2[REDACTED]",
|
|
28
|
-
),
|
|
29
|
-
(
|
|
30
|
-
re.compile(r"(?i)\b(api[_-]?key|token|password|secret)\b(\s+)[^\s\"']+"),
|
|
31
|
-
r"\1\2[REDACTED]",
|
|
32
|
-
),
|
|
33
|
-
(
|
|
34
|
-
re.compile(
|
|
35
|
-
r"-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----",
|
|
36
|
-
re.DOTALL,
|
|
37
|
-
),
|
|
38
|
-
"[REDACTED_PRIVATE_KEY]",
|
|
39
|
-
),
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _payload(rec: Dict[str, Any]) -> Dict[str, Any]:
|
|
44
|
-
payload = rec.get("payload")
|
|
45
|
-
return payload if isinstance(payload, dict) else {}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _timestamp(rec: Dict[str, Any], payload: Dict[str, Any]) -> str:
|
|
49
|
-
for value in (
|
|
50
|
-
payload.get("timestamp"),
|
|
51
|
-
rec.get("timestamp"),
|
|
52
|
-
payload.get("started_at"),
|
|
53
|
-
payload.get("completed_at"),
|
|
54
|
-
):
|
|
55
|
-
if isinstance(value, str) and value:
|
|
56
|
-
return value
|
|
57
|
-
return ""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _text_from_any(content: Any) -> str:
|
|
61
|
-
if isinstance(content, str):
|
|
62
|
-
return content
|
|
63
|
-
if isinstance(content, list):
|
|
64
|
-
parts: List[str] = []
|
|
65
|
-
for item in content:
|
|
66
|
-
if isinstance(item, str):
|
|
67
|
-
parts.append(item)
|
|
68
|
-
elif isinstance(item, dict):
|
|
69
|
-
if item.get("type") == "text" and item.get("text"):
|
|
70
|
-
parts.append(str(item["text"]))
|
|
71
|
-
elif item.get("text"):
|
|
72
|
-
parts.append(str(item["text"]))
|
|
73
|
-
return "\n".join(parts)
|
|
74
|
-
if isinstance(content, dict):
|
|
75
|
-
if content.get("text"):
|
|
76
|
-
return str(content["text"])
|
|
77
|
-
if content.get("content"):
|
|
78
|
-
return _text_from_any(content["content"])
|
|
79
|
-
return ""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _strip_codex_meta(text: str) -> str:
|
|
83
|
-
stripped = text.strip()
|
|
84
|
-
if not stripped:
|
|
85
|
-
return ""
|
|
86
|
-
if stripped.startswith("<codex_internal_context"):
|
|
87
|
-
return ""
|
|
88
|
-
if stripped.startswith("<environment_context"):
|
|
89
|
-
return ""
|
|
90
|
-
if stripped.startswith("# AGENTS.md instructions") or "--- project-doc ---" in stripped:
|
|
91
|
-
for marker in ("</environment_context>", "</INSTRUCTIONS>"):
|
|
92
|
-
idx = stripped.rfind(marker)
|
|
93
|
-
if idx == -1:
|
|
94
|
-
continue
|
|
95
|
-
tail = stripped[idx + len(marker):].strip()
|
|
96
|
-
if tail and not tail.startswith("<"):
|
|
97
|
-
return tail
|
|
98
|
-
return ""
|
|
99
|
-
return stripped
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _sanitize_text(text: str) -> str:
|
|
103
|
-
sanitized = _strip_codex_meta(text).replace("\x00", "").strip()
|
|
104
|
-
if not sanitized or _is_meta_prompt(sanitized):
|
|
105
|
-
return ""
|
|
106
|
-
for pattern, replacement in _SECRET_PATTERNS:
|
|
107
|
-
sanitized = pattern.sub(replacement, sanitized)
|
|
108
|
-
return sanitized
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def _sanitize_tool_name(name: str) -> str:
|
|
112
|
-
return re.sub(r"[^A-Za-z0-9_.:-]+", "_", name)[:80]
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _tool_name(payload: Dict[str, Any]) -> str:
|
|
116
|
-
payload_type = payload.get("type")
|
|
117
|
-
name = payload.get("name")
|
|
118
|
-
if isinstance(name, str) and name:
|
|
119
|
-
return _sanitize_tool_name(name)
|
|
120
|
-
if payload_type == "exec_command_end":
|
|
121
|
-
return "exec_command"
|
|
122
|
-
if payload_type == "patch_apply_end":
|
|
123
|
-
return "apply_patch"
|
|
124
|
-
if payload_type == "web_search_call":
|
|
125
|
-
return "web_search"
|
|
126
|
-
if payload_type == "tool_search_call":
|
|
127
|
-
return "tool_search"
|
|
128
|
-
if isinstance(payload_type, str) and payload_type.endswith("_tool_call"):
|
|
129
|
-
return _sanitize_tool_name(payload_type)
|
|
130
|
-
return ""
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def _dedup(xs: Iterable[str]) -> List[str]:
|
|
134
|
-
seen = set()
|
|
135
|
-
out: List[str] = []
|
|
136
|
-
for x in xs:
|
|
137
|
-
if x not in seen:
|
|
138
|
-
seen.add(x)
|
|
139
|
-
out.append(x)
|
|
140
|
-
return out
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def digest_codex_archived_session(path: str, project: str = "") -> Optional[SessionDigest]:
|
|
144
|
-
"""Build a ``SessionDigest`` from one Codex Desktop archived session."""
|
|
145
|
-
session_id = os.path.splitext(os.path.basename(path))[0]
|
|
146
|
-
started = ""
|
|
147
|
-
ended = ""
|
|
148
|
-
session_project = ""
|
|
149
|
-
user_prompts: List[str] = []
|
|
150
|
-
assistant_finals: List[str] = []
|
|
151
|
-
tools: List[str] = []
|
|
152
|
-
feedback: List[str] = []
|
|
153
|
-
n_user = 0
|
|
154
|
-
n_asst = 0
|
|
155
|
-
|
|
156
|
-
for rec in _iter_jsonl(path):
|
|
157
|
-
payload = _payload(rec)
|
|
158
|
-
payload_type = payload.get("type")
|
|
159
|
-
ts = _timestamp(rec, payload)
|
|
160
|
-
if ts:
|
|
161
|
-
if not started:
|
|
162
|
-
started = ts
|
|
163
|
-
ended = ts
|
|
164
|
-
cwd = payload.get("cwd")
|
|
165
|
-
if isinstance(cwd, str) and cwd:
|
|
166
|
-
if not session_project:
|
|
167
|
-
session_project = cwd
|
|
168
|
-
if project and _project_matches(cwd, "invoked", project):
|
|
169
|
-
session_project = cwd
|
|
170
|
-
|
|
171
|
-
role = payload.get("role")
|
|
172
|
-
text = ""
|
|
173
|
-
output_role = ""
|
|
174
|
-
if payload_type == "user_message":
|
|
175
|
-
text = _text_from_any(payload.get("message"))
|
|
176
|
-
output_role = "user"
|
|
177
|
-
elif payload_type == "agent_message":
|
|
178
|
-
text = _text_from_any(payload.get("message"))
|
|
179
|
-
output_role = "assistant"
|
|
180
|
-
elif payload_type == "message" and role in {"user", "assistant"}:
|
|
181
|
-
text = _text_from_any(payload.get("content"))
|
|
182
|
-
output_role = str(role)
|
|
183
|
-
else:
|
|
184
|
-
tool = _tool_name(payload)
|
|
185
|
-
if tool:
|
|
186
|
-
tools.append(tool)
|
|
187
|
-
continue
|
|
188
|
-
|
|
189
|
-
sanitized = _sanitize_text(text)
|
|
190
|
-
if not sanitized:
|
|
191
|
-
continue
|
|
192
|
-
if output_role == "user":
|
|
193
|
-
n_user += 1
|
|
194
|
-
user_prompts.append(sanitized)
|
|
195
|
-
feedback.extend(_detect_feedback(sanitized))
|
|
196
|
-
elif output_role == "assistant":
|
|
197
|
-
n_asst += 1
|
|
198
|
-
assistant_finals.append(sanitized)
|
|
199
|
-
|
|
200
|
-
if project and not _project_matches(session_project or "", "invoked", project):
|
|
201
|
-
return None
|
|
202
|
-
if n_user == 0 and n_asst == 0:
|
|
203
|
-
return None
|
|
204
|
-
|
|
205
|
-
return SessionDigest(
|
|
206
|
-
session_id=session_id,
|
|
207
|
-
project=session_project,
|
|
208
|
-
started_at=started,
|
|
209
|
-
ended_at=ended,
|
|
210
|
-
user_prompts=user_prompts,
|
|
211
|
-
assistant_finals=assistant_finals[-5:],
|
|
212
|
-
tools_used=_dedup(tools),
|
|
213
|
-
files_touched=[],
|
|
214
|
-
feedback_signals=feedback,
|
|
215
|
-
n_user_turns=n_user,
|
|
216
|
-
n_assistant_turns=n_asst,
|
|
217
|
-
raw_path=path,
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def harvest_codex(
|
|
222
|
-
archived_sessions_dir: str,
|
|
223
|
-
*,
|
|
224
|
-
scope: Any = "all",
|
|
225
|
-
invoked_project: str = "",
|
|
226
|
-
since_iso: Optional[str] = None,
|
|
227
|
-
limit: int = 0,
|
|
228
|
-
) -> List[SessionDigest]:
|
|
229
|
-
"""Walk ``~/.codex/archived_sessions`` and return matching digests."""
|
|
230
|
-
digests: List[SessionDigest] = []
|
|
231
|
-
if not os.path.isdir(archived_sessions_dir):
|
|
232
|
-
return digests
|
|
233
|
-
|
|
234
|
-
paths = [
|
|
235
|
-
os.path.join(archived_sessions_dir, fn)
|
|
236
|
-
for fn in os.listdir(archived_sessions_dir)
|
|
237
|
-
if fn.endswith(".jsonl")
|
|
238
|
-
]
|
|
239
|
-
paths.sort(key=lambda p: os.path.getmtime(p), reverse=True)
|
|
240
|
-
|
|
241
|
-
project_hint = invoked_project if scope == "invoked" else ""
|
|
242
|
-
for path in paths:
|
|
243
|
-
digest = digest_codex_archived_session(path, project=project_hint)
|
|
244
|
-
if digest is None:
|
|
245
|
-
continue
|
|
246
|
-
if not _project_matches(digest.project or "", scope, invoked_project):
|
|
247
|
-
continue
|
|
248
|
-
if since_iso and digest.ended_at and digest.ended_at < since_iso:
|
|
249
|
-
continue
|
|
250
|
-
digests.append(digest)
|
|
251
|
-
if limit and len(digests) >= limit:
|
|
252
|
-
break
|
|
253
|
-
return digests
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"""Source selection for SkillOpt-Sleep transcript harvesting."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from typing import Optional
|
|
5
|
-
|
|
6
|
-
from skillopt_sleep.harvest import harvest
|
|
7
|
-
from skillopt_sleep.harvest_codex import harvest_codex
|
|
8
|
-
from skillopt_sleep.types import SessionDigest
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def harvest_for_config(cfg, *, since_iso: Optional[str] = None, limit: int = 0) -> list[SessionDigest]:
|
|
12
|
-
source = cfg.get("transcript_source", "claude")
|
|
13
|
-
scope = cfg.get("projects", "invoked")
|
|
14
|
-
invoked_project = cfg.get("invoked_project", "")
|
|
15
|
-
|
|
16
|
-
if source == "codex":
|
|
17
|
-
return harvest_codex(
|
|
18
|
-
cfg.codex_archived_sessions_dir,
|
|
19
|
-
scope=scope,
|
|
20
|
-
invoked_project=invoked_project,
|
|
21
|
-
since_iso=since_iso,
|
|
22
|
-
limit=limit,
|
|
23
|
-
)
|
|
24
|
-
if source == "auto":
|
|
25
|
-
codex_digests = harvest_codex(
|
|
26
|
-
cfg.codex_archived_sessions_dir,
|
|
27
|
-
scope=scope,
|
|
28
|
-
invoked_project=invoked_project,
|
|
29
|
-
since_iso=since_iso,
|
|
30
|
-
limit=limit,
|
|
31
|
-
)
|
|
32
|
-
if codex_digests:
|
|
33
|
-
return codex_digests
|
|
34
|
-
|
|
35
|
-
return harvest(
|
|
36
|
-
cfg.transcripts_dir,
|
|
37
|
-
scope=scope,
|
|
38
|
-
invoked_project=invoked_project,
|
|
39
|
-
since_iso=since_iso,
|
|
40
|
-
limit=limit,
|
|
41
|
-
)
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"""SkillOpt-Sleep — rule-based judges (gbrain-evals compatible).
|
|
2
|
-
|
|
3
|
-
Implements the programmatic check operators used by gbrain-evals'
|
|
4
|
-
skillopt-v1 benchmark so we can score skill outputs locally, with NO judge
|
|
5
|
-
API call:
|
|
6
|
-
|
|
7
|
-
* section_present <name> — a markdown heading containing <name> exists
|
|
8
|
-
* regex <pattern> — the pattern matches the response
|
|
9
|
-
* max_chars <n> — response length <= n
|
|
10
|
-
* min_chars <n> — response length >= n
|
|
11
|
-
* contains <text> — substring present (case-insensitive)
|
|
12
|
-
* tool_called <name> — a tool with <name> was invoked (needs a tool loop;
|
|
13
|
-
in single-shot replay we approximate via an
|
|
14
|
-
explicit "TOOL_CALL: <name>" marker the agent emits)
|
|
15
|
-
|
|
16
|
-
A task whose judge is {"kind": "rule", "checks": [...]} passes (hard=1.0) iff
|
|
17
|
-
ALL checks pass; soft = fraction of checks passed. This mirrors gbrain's
|
|
18
|
-
all-checks-must-pass rule scoring and gives the gate a smooth signal.
|
|
19
|
-
"""
|
|
20
|
-
from __future__ import annotations
|
|
21
|
-
|
|
22
|
-
import re
|
|
23
|
-
from typing import Any, Dict, List, Tuple
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _section_present(response: str, name: str) -> bool:
|
|
27
|
-
# a markdown heading line (#, ##, ...) or bold line that contains `name`
|
|
28
|
-
pat = re.compile(
|
|
29
|
-
r"(?im)^\s{0,3}(#{1,6}\s*.*%s|\*\*.*%s.*\*\*\s*:?)\s*$" % (re.escape(name), re.escape(name))
|
|
30
|
-
)
|
|
31
|
-
if pat.search(response or ""):
|
|
32
|
-
return True
|
|
33
|
-
# also accept "Name:" style label at line start
|
|
34
|
-
label = re.compile(r"(?im)^\s*%s\s*:" % re.escape(name))
|
|
35
|
-
return bool(label.search(response or ""))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _check(op: str, arg: Any, response: str, tools_called: List[str]) -> bool:
|
|
39
|
-
r = response or ""
|
|
40
|
-
if op == "section_present":
|
|
41
|
-
return _section_present(r, str(arg))
|
|
42
|
-
if op == "regex":
|
|
43
|
-
try:
|
|
44
|
-
return bool(re.search(str(arg), r))
|
|
45
|
-
except re.error:
|
|
46
|
-
return False
|
|
47
|
-
if op == "max_chars":
|
|
48
|
-
return len(r) <= int(arg)
|
|
49
|
-
if op == "min_chars":
|
|
50
|
-
return len(r) >= int(arg)
|
|
51
|
-
if op == "contains":
|
|
52
|
-
return str(arg).lower() in r.lower()
|
|
53
|
-
if op == "tool_called":
|
|
54
|
-
name = str(arg).lower()
|
|
55
|
-
if any(name == t.lower() for t in tools_called):
|
|
56
|
-
return True
|
|
57
|
-
# single-shot approximation: the agent emits an explicit marker
|
|
58
|
-
return bool(re.search(r"(?i)\btool_call\s*:\s*%s\b" % re.escape(name), r))
|
|
59
|
-
# unknown op: do not block
|
|
60
|
-
return True
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def score_rule_judge(
|
|
64
|
-
judge: Dict[str, Any],
|
|
65
|
-
response: str,
|
|
66
|
-
tools_called: List[str] | None = None,
|
|
67
|
-
) -> Tuple[float, float, str]:
|
|
68
|
-
"""Return (hard, soft, rationale) for a gbrain-style rule judge."""
|
|
69
|
-
checks = (judge or {}).get("checks", []) or []
|
|
70
|
-
if not checks:
|
|
71
|
-
return 0.0, 0.0, "no checks"
|
|
72
|
-
tools_called = tools_called or []
|
|
73
|
-
passed = 0
|
|
74
|
-
failed_desc: List[str] = []
|
|
75
|
-
for c in checks:
|
|
76
|
-
ok = _check(c.get("op", ""), c.get("arg"), response, tools_called)
|
|
77
|
-
if ok:
|
|
78
|
-
passed += 1
|
|
79
|
-
else:
|
|
80
|
-
failed_desc.append(f"{c.get('op')}={c.get('arg')}")
|
|
81
|
-
soft = passed / len(checks)
|
|
82
|
-
hard = 1.0 if passed == len(checks) else 0.0
|
|
83
|
-
rationale = "all checks passed" if hard else "failed: " + ", ".join(failed_desc)
|
|
84
|
-
return hard, soft, rationale
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
"""SkillOpt-Sleep — LLM-backed task miner.
|
|
2
|
-
|
|
3
|
-
The heuristic miner (mine.py) produces TaskRecords without a checkable
|
|
4
|
-
reference, so real harvested transcripts can't show measurable lift. This
|
|
5
|
-
module uses an optimizer backend to turn session digests into TaskRecords
|
|
6
|
-
WITH a checkable rubric judge — the missing piece for real-data improvement.
|
|
7
|
-
|
|
8
|
-
For each recurring intent it extracts:
|
|
9
|
-
* a clean, generalized `intent` (the reusable task, stripped of one-off specifics)
|
|
10
|
-
* a `rubric` (what a good answer must satisfy) -> stored as a rule judge of
|
|
11
|
-
`contains`/`regex`/`section_present` checks the local judge can score, OR a
|
|
12
|
-
free-text rubric scored by the backend's judge() when no programmatic check fits
|
|
13
|
-
* a preference signal (was the user satisfied?) to weight failures
|
|
14
|
-
|
|
15
|
-
It is deliberately conservative: it only emits a task when it can name a
|
|
16
|
-
concrete, checkable success criterion, so the gate has real signal. Tasks it
|
|
17
|
-
can't make checkable are dropped (logged), not faked.
|
|
18
|
-
"""
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import json
|
|
22
|
-
import re
|
|
23
|
-
from typing import Any, Callable, Dict, List
|
|
24
|
-
|
|
25
|
-
from skillopt_sleep.backend import Backend, _extract_json
|
|
26
|
-
from skillopt_sleep.types import SessionDigest, TaskRecord
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
_MINER_PROMPT = """You are mining a user's past AI-assistant sessions to find RECURRING tasks
|
|
30
|
-
worth optimizing a skill for. From the session below, extract 0-3 reusable tasks.
|
|
31
|
-
|
|
32
|
-
A good task is something the user asks for repeatedly or had to correct, where a
|
|
33
|
-
GENERAL rule would help next time (formatting, structure, tool-use, conventions).
|
|
34
|
-
Skip one-off or purely exploratory requests.
|
|
35
|
-
|
|
36
|
-
For each task return:
|
|
37
|
-
- "intent": the reusable request, generalized (no one-off specifics)
|
|
38
|
-
- "checks": a list of programmatic success checks a grader can run on a future
|
|
39
|
-
answer. Each check is one of:
|
|
40
|
-
{"op":"section_present","arg":"<heading text>"}
|
|
41
|
-
{"op":"regex","arg":"<python regex the answer must match>"}
|
|
42
|
-
{"op":"contains","arg":"<substring the answer must contain>"}
|
|
43
|
-
{"op":"max_chars","arg":<int>}
|
|
44
|
-
Only include checks you are confident a GOOD answer must satisfy.
|
|
45
|
-
- "rubric": a one-sentence description of what a good answer looks like
|
|
46
|
-
- "satisfied": true/false — did the user seem satisfied with the assistant's answer?
|
|
47
|
-
|
|
48
|
-
Return ONLY a JSON array (possibly empty). No prose.
|
|
49
|
-
|
|
50
|
-
# Session
|
|
51
|
-
project: __PROJECT__
|
|
52
|
-
user prompts:
|
|
53
|
-
__PROMPTS__
|
|
54
|
-
assistant final (last):
|
|
55
|
-
__FINAL__
|
|
56
|
-
feedback signals: __FEEDBACK__
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _digest_to_prompt(d: SessionDigest) -> str:
|
|
61
|
-
prompts = "\n".join(f" - {p[:240]}" for p in d.user_prompts[:6]) or " (none)"
|
|
62
|
-
final = (d.assistant_finals[-1][:400] if d.assistant_finals else "(none)")
|
|
63
|
-
return (
|
|
64
|
-
_MINER_PROMPT
|
|
65
|
-
.replace("__PROJECT__", d.project or "(unknown)")
|
|
66
|
-
.replace("__PROMPTS__", prompts)
|
|
67
|
-
.replace("__FINAL__", final)
|
|
68
|
-
.replace("__FEEDBACK__", ", ".join(d.feedback_signals[:6]) or "(none)")
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _mk_task(d: SessionDigest, obj: Dict[str, Any], idx: int) -> TaskRecord | None:
|
|
73
|
-
intent = str(obj.get("intent", "")).strip()
|
|
74
|
-
if len(intent) < 8:
|
|
75
|
-
return None
|
|
76
|
-
checks = obj.get("checks") or []
|
|
77
|
-
rubric = str(obj.get("rubric", "")).strip()
|
|
78
|
-
satisfied = bool(obj.get("satisfied", False))
|
|
79
|
-
|
|
80
|
-
# keep only well-formed checks
|
|
81
|
-
clean_checks = []
|
|
82
|
-
for c in checks:
|
|
83
|
-
if isinstance(c, dict) and c.get("op") in {
|
|
84
|
-
"section_present", "regex", "contains", "max_chars", "min_chars",
|
|
85
|
-
}:
|
|
86
|
-
clean_checks.append({"op": c["op"], "arg": c.get("arg")})
|
|
87
|
-
|
|
88
|
-
import hashlib
|
|
89
|
-
tid = "llm_" + hashlib.sha256((d.project + intent).encode()).hexdigest()[:12]
|
|
90
|
-
|
|
91
|
-
if clean_checks:
|
|
92
|
-
return TaskRecord(
|
|
93
|
-
id=tid, project=d.project, intent=intent,
|
|
94
|
-
reference_kind="rule", judge={"kind": "rule", "checks": clean_checks},
|
|
95
|
-
outcome="success" if satisfied else "fail",
|
|
96
|
-
tags=["mined:llm"], source_sessions=[d.session_id],
|
|
97
|
-
)
|
|
98
|
-
if rubric:
|
|
99
|
-
return TaskRecord(
|
|
100
|
-
id=tid, project=d.project, intent=intent,
|
|
101
|
-
reference_kind="rubric", reference=rubric,
|
|
102
|
-
outcome="success" if satisfied else "fail",
|
|
103
|
-
tags=["mined:llm"], source_sessions=[d.session_id],
|
|
104
|
-
)
|
|
105
|
-
return None # not checkable -> drop
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def make_llm_miner(
|
|
109
|
-
backend: Backend,
|
|
110
|
-
*,
|
|
111
|
-
max_sessions: int = 20,
|
|
112
|
-
max_tasks: int = 40,
|
|
113
|
-
) -> Callable[[List[SessionDigest]], List[TaskRecord]]:
|
|
114
|
-
"""Return an llm_miner(digests) -> list[TaskRecord] bound to a backend."""
|
|
115
|
-
|
|
116
|
-
def _miner(digests: List[SessionDigest]) -> List[TaskRecord]:
|
|
117
|
-
out: List[TaskRecord] = []
|
|
118
|
-
for d in digests[:max_sessions]:
|
|
119
|
-
if not d.user_prompts:
|
|
120
|
-
continue
|
|
121
|
-
raw = backend._call(_digest_to_prompt(d), max_tokens=800) # type: ignore[attr-defined]
|
|
122
|
-
arr = _extract_json(raw, "array")
|
|
123
|
-
if not isinstance(arr, list):
|
|
124
|
-
continue
|
|
125
|
-
for i, obj in enumerate(arr[:3]):
|
|
126
|
-
if isinstance(obj, dict):
|
|
127
|
-
t = _mk_task(d, obj, i)
|
|
128
|
-
if t is not None:
|
|
129
|
-
out.append(t)
|
|
130
|
-
if len(out) >= max_tasks:
|
|
131
|
-
return out
|
|
132
|
-
return out
|
|
133
|
-
|
|
134
|
-
return _miner
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
"""SkillOpt-Sleep — skill/memory document manipulation.
|
|
2
|
-
|
|
3
|
-
Applies bounded EditRecords to a skill (SKILL.md body) or memory (CLAUDE.md)
|
|
4
|
-
document, and provides Dream-style consolidation helpers (dedup near-identical
|
|
5
|
-
lines, drop contradictions). All edits live inside a protected, clearly-marked
|
|
6
|
-
region so the sleep cycle never clobbers the user's hand-written content.
|
|
7
|
-
"""
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import re
|
|
11
|
-
from typing import List, Tuple
|
|
12
|
-
|
|
13
|
-
from skillopt_sleep.types import EditRecord
|
|
14
|
-
|
|
15
|
-
LEARNED_START = "<!-- SKILLOPT-SLEEP:LEARNED START -->"
|
|
16
|
-
LEARNED_END = "<!-- SKILLOPT-SLEEP:LEARNED END -->"
|
|
17
|
-
_BANNER = (
|
|
18
|
-
"_This block is maintained by SkillOpt-Sleep. Edits here are proposed "
|
|
19
|
-
"offline, validated against your past tasks, and adopted only after you "
|
|
20
|
-
"approve them. Hand-edits outside this block are never touched._"
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def extract_learned(doc: str) -> str:
|
|
25
|
-
s = doc.find(LEARNED_START)
|
|
26
|
-
e = doc.find(LEARNED_END)
|
|
27
|
-
if s == -1 or e == -1:
|
|
28
|
-
return ""
|
|
29
|
-
return doc[s + len(LEARNED_START):e].strip()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _strip_learned(doc: str) -> str:
|
|
33
|
-
while True:
|
|
34
|
-
s = doc.find(LEARNED_START)
|
|
35
|
-
if s == -1:
|
|
36
|
-
break
|
|
37
|
-
e = doc.find(LEARNED_END, s)
|
|
38
|
-
if e == -1:
|
|
39
|
-
doc = doc[:s]
|
|
40
|
-
break
|
|
41
|
-
doc = doc[:s] + doc[e + len(LEARNED_END):]
|
|
42
|
-
while "\n\n\n" in doc:
|
|
43
|
-
doc = doc.replace("\n\n\n", "\n\n")
|
|
44
|
-
return doc.rstrip()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def set_learned(doc: str, learned_lines: List[str]) -> str:
|
|
48
|
-
"""Replace the protected learned region with the given bullet lines."""
|
|
49
|
-
base = _strip_learned(doc)
|
|
50
|
-
body = "\n".join(f"- {ln.strip().lstrip('- ').strip()}" for ln in learned_lines if ln.strip())
|
|
51
|
-
block = (
|
|
52
|
-
f"\n\n{LEARNED_START}\n"
|
|
53
|
-
f"## Learned preferences & procedures\n\n{_BANNER}\n\n{body}\n"
|
|
54
|
-
f"{LEARNED_END}\n"
|
|
55
|
-
)
|
|
56
|
-
return (base + block).lstrip("\n")
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def current_learned_lines(doc: str) -> List[str]:
|
|
60
|
-
inner = extract_learned(doc)
|
|
61
|
-
lines: List[str] = []
|
|
62
|
-
for ln in inner.splitlines():
|
|
63
|
-
ln = ln.strip()
|
|
64
|
-
if ln.startswith("- "):
|
|
65
|
-
lines.append(ln[2:].strip())
|
|
66
|
-
return lines
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _norm(s: str) -> str:
|
|
70
|
-
return re.sub(r"\s+", " ", (s or "").lower()).strip()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def apply_edits(doc: str, edits: List[EditRecord]) -> Tuple[str, List[EditRecord]]:
|
|
74
|
-
"""Apply add/delete/replace edits to the protected learned region.
|
|
75
|
-
|
|
76
|
-
Returns (new_doc, applied_edits). Dedups: an `add` whose content already
|
|
77
|
-
exists (normalized) is skipped. `delete`/`replace` match on normalized
|
|
78
|
-
anchor substring.
|
|
79
|
-
"""
|
|
80
|
-
lines = current_learned_lines(doc)
|
|
81
|
-
norm_set = {_norm(line) for line in lines}
|
|
82
|
-
applied: List[EditRecord] = []
|
|
83
|
-
|
|
84
|
-
for e in edits:
|
|
85
|
-
op = (e.op or "add").lower()
|
|
86
|
-
if op == "add":
|
|
87
|
-
if _norm(e.content) in norm_set or not e.content.strip():
|
|
88
|
-
continue
|
|
89
|
-
lines.append(e.content.strip())
|
|
90
|
-
norm_set.add(_norm(e.content))
|
|
91
|
-
applied.append(e)
|
|
92
|
-
elif op == "delete":
|
|
93
|
-
anchor = _norm(e.anchor or e.content)
|
|
94
|
-
keep = [line for line in lines if anchor not in _norm(line)]
|
|
95
|
-
if len(keep) != len(lines):
|
|
96
|
-
lines = keep
|
|
97
|
-
norm_set = {_norm(line) for line in lines}
|
|
98
|
-
applied.append(e)
|
|
99
|
-
elif op == "replace":
|
|
100
|
-
anchor = _norm(e.anchor)
|
|
101
|
-
new_lines = []
|
|
102
|
-
changed = False
|
|
103
|
-
for line in lines:
|
|
104
|
-
if anchor and anchor in _norm(line):
|
|
105
|
-
new_lines.append(e.content.strip())
|
|
106
|
-
changed = True
|
|
107
|
-
else:
|
|
108
|
-
new_lines.append(line)
|
|
109
|
-
if changed:
|
|
110
|
-
lines = new_lines
|
|
111
|
-
norm_set = {_norm(line) for line in lines}
|
|
112
|
-
applied.append(e)
|
|
113
|
-
|
|
114
|
-
return set_learned(doc, lines), applied
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def ensure_skill_scaffold(doc: str, *, name: str, description: str) -> str:
|
|
118
|
-
"""Ensure a SKILL.md has YAML frontmatter so local agents load it."""
|
|
119
|
-
if doc.lstrip().startswith("---"):
|
|
120
|
-
return doc
|
|
121
|
-
fm = (
|
|
122
|
-
"---\n"
|
|
123
|
-
f"name: {name}\n"
|
|
124
|
-
f"description: {description}\n"
|
|
125
|
-
"---\n\n"
|
|
126
|
-
f"# {name}\n\n"
|
|
127
|
-
"Preferences and procedures learned from your past local agent sessions.\n"
|
|
128
|
-
)
|
|
129
|
-
return fm + doc
|