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.
Files changed (154) hide show
  1. package/README.md +33 -23
  2. package/bin/cli.js +24 -67
  3. package/package.json +1 -1
  4. package/template/rules/knowledge/README.md +56 -0
  5. package/template/rules/knowledge/ant-design-vue.mdc +54 -0
  6. package/template/rules/knowledge/general.mdc +56 -0
  7. package/template/rules/knowledge/rust.mdc +55 -0
  8. package/template/rules/knowledge/shadcn-svelte.mdc +64 -0
  9. package/template/rules/knowledge/svelte.mdc +135 -0
  10. package/template/rules/knowledge/tailwind.mdc +66 -0
  11. package/template/rules/knowledge/tauri.mdc +65 -0
  12. package/template/rules/knowledge/typescript.mdc +41 -0
  13. package/template/rules/knowledge/vue.mdc +48 -0
  14. package/template/rules/ponytail.mdc +23 -98
  15. package/template/rules/self-evolve.mdc +24 -0
  16. package/template/skills/impeccable/SKILL.md +47 -174
  17. package/template/skills/skillopt-sleep/SKILL.md +34 -48
  18. package/template/skills/impeccable/agents/impeccable_asset_producer.toml +0 -92
  19. package/template/skills/impeccable/agents/impeccable_manual_edit_applier.toml +0 -95
  20. package/template/skills/impeccable/agents/openai.yaml +0 -4
  21. package/template/skills/impeccable/reference/adapt.md +0 -311
  22. package/template/skills/impeccable/reference/animate.md +0 -201
  23. package/template/skills/impeccable/reference/audit.md +0 -133
  24. package/template/skills/impeccable/reference/bolder.md +0 -113
  25. package/template/skills/impeccable/reference/brand.md +0 -108
  26. package/template/skills/impeccable/reference/clarify.md +0 -288
  27. package/template/skills/impeccable/reference/codex.md +0 -105
  28. package/template/skills/impeccable/reference/colorize.md +0 -257
  29. package/template/skills/impeccable/reference/craft.md +0 -123
  30. package/template/skills/impeccable/reference/critique.md +0 -790
  31. package/template/skills/impeccable/reference/delight.md +0 -302
  32. package/template/skills/impeccable/reference/distill.md +0 -111
  33. package/template/skills/impeccable/reference/document.md +0 -429
  34. package/template/skills/impeccable/reference/extract.md +0 -69
  35. package/template/skills/impeccable/reference/harden.md +0 -347
  36. package/template/skills/impeccable/reference/hooks.md +0 -90
  37. package/template/skills/impeccable/reference/init.md +0 -172
  38. package/template/skills/impeccable/reference/interaction-design.md +0 -189
  39. package/template/skills/impeccable/reference/layout.md +0 -161
  40. package/template/skills/impeccable/reference/live.md +0 -720
  41. package/template/skills/impeccable/reference/onboard.md +0 -234
  42. package/template/skills/impeccable/reference/optimize.md +0 -258
  43. package/template/skills/impeccable/reference/overdrive.md +0 -130
  44. package/template/skills/impeccable/reference/polish.md +0 -241
  45. package/template/skills/impeccable/reference/product.md +0 -60
  46. package/template/skills/impeccable/reference/quieter.md +0 -99
  47. package/template/skills/impeccable/reference/shape.md +0 -165
  48. package/template/skills/impeccable/reference/typeset.md +0 -279
  49. package/template/skills/impeccable/scripts/command-metadata.json +0 -94
  50. package/template/skills/impeccable/scripts/context-signals.mjs +0 -225
  51. package/template/skills/impeccable/scripts/context.mjs +0 -961
  52. package/template/skills/impeccable/scripts/critique-storage.mjs +0 -242
  53. package/template/skills/impeccable/scripts/detect-csp.mjs +0 -198
  54. package/template/skills/impeccable/scripts/detect.mjs +0 -21
  55. package/template/skills/impeccable/scripts/detector/browser/injected/index.mjs +0 -1937
  56. package/template/skills/impeccable/scripts/detector/cli/main.mjs +0 -290
  57. package/template/skills/impeccable/scripts/detector/design-system.mjs +0 -750
  58. package/template/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +0 -5138
  59. package/template/skills/impeccable/scripts/detector/detect-antipatterns.mjs +0 -50
  60. package/template/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +0 -277
  61. package/template/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +0 -568
  62. package/template/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +0 -1015
  63. package/template/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +0 -234
  64. package/template/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +0 -189
  65. package/template/skills/impeccable/scripts/detector/findings.mjs +0 -12
  66. package/template/skills/impeccable/scripts/detector/node/file-system.mjs +0 -198
  67. package/template/skills/impeccable/scripts/detector/profile/profiler.mjs +0 -166
  68. package/template/skills/impeccable/scripts/detector/registry/antipatterns.mjs +0 -448
  69. package/template/skills/impeccable/scripts/detector/rules/checks.mjs +0 -2671
  70. package/template/skills/impeccable/scripts/detector/shared/color.mjs +0 -124
  71. package/template/skills/impeccable/scripts/detector/shared/constants.mjs +0 -101
  72. package/template/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +0 -148
  73. package/template/skills/impeccable/scripts/detector/shared/page.mjs +0 -7
  74. package/template/skills/impeccable/scripts/hook-admin.mjs +0 -661
  75. package/template/skills/impeccable/scripts/hook-before-edit.mjs +0 -476
  76. package/template/skills/impeccable/scripts/hook-lib.mjs +0 -1632
  77. package/template/skills/impeccable/scripts/hook.mjs +0 -61
  78. package/template/skills/impeccable/scripts/lib/design-parser.mjs +0 -842
  79. package/template/skills/impeccable/scripts/lib/impeccable-config.mjs +0 -638
  80. package/template/skills/impeccable/scripts/lib/impeccable-paths.mjs +0 -128
  81. package/template/skills/impeccable/scripts/lib/is-generated.mjs +0 -69
  82. package/template/skills/impeccable/scripts/lib/target-args.mjs +0 -42
  83. package/template/skills/impeccable/scripts/live/browser-script-parts.mjs +0 -49
  84. package/template/skills/impeccable/scripts/live/completion.mjs +0 -19
  85. package/template/skills/impeccable/scripts/live/event-validation.mjs +0 -137
  86. package/template/skills/impeccable/scripts/live/insert-ui.mjs +0 -458
  87. package/template/skills/impeccable/scripts/live/manual-apply.mjs +0 -939
  88. package/template/skills/impeccable/scripts/live/manual-edit-routes.mjs +0 -357
  89. package/template/skills/impeccable/scripts/live/manual-edits-buffer.mjs +0 -152
  90. package/template/skills/impeccable/scripts/live/session-store.mjs +0 -289
  91. package/template/skills/impeccable/scripts/live/svelte-component.mjs +0 -826
  92. package/template/skills/impeccable/scripts/live/sveltekit-adapter.mjs +0 -274
  93. package/template/skills/impeccable/scripts/live/ui-core.mjs +0 -180
  94. package/template/skills/impeccable/scripts/live/vocabulary.mjs +0 -36
  95. package/template/skills/impeccable/scripts/live-accept.mjs +0 -812
  96. package/template/skills/impeccable/scripts/live-browser-dom.js +0 -146
  97. package/template/skills/impeccable/scripts/live-browser-session.js +0 -123
  98. package/template/skills/impeccable/scripts/live-browser.js +0 -11173
  99. package/template/skills/impeccable/scripts/live-commit-manual-edits.mjs +0 -1241
  100. package/template/skills/impeccable/scripts/live-complete.mjs +0 -75
  101. package/template/skills/impeccable/scripts/live-copy-edit-agent.mjs +0 -683
  102. package/template/skills/impeccable/scripts/live-discard-manual-edits.mjs +0 -51
  103. package/template/skills/impeccable/scripts/live-inject.mjs +0 -583
  104. package/template/skills/impeccable/scripts/live-insert.mjs +0 -272
  105. package/template/skills/impeccable/scripts/live-manual-edit-evidence.mjs +0 -363
  106. package/template/skills/impeccable/scripts/live-poll.mjs +0 -384
  107. package/template/skills/impeccable/scripts/live-resume.mjs +0 -94
  108. package/template/skills/impeccable/scripts/live-server.mjs +0 -1135
  109. package/template/skills/impeccable/scripts/live-status.mjs +0 -61
  110. package/template/skills/impeccable/scripts/live-target.mjs +0 -30
  111. package/template/skills/impeccable/scripts/live-wrap.mjs +0 -894
  112. package/template/skills/impeccable/scripts/live.mjs +0 -297
  113. package/template/skills/impeccable/scripts/modern-screenshot.umd.js +0 -14
  114. package/template/skills/impeccable/scripts/palette.mjs +0 -633
  115. package/template/skills/impeccable/scripts/pin.mjs +0 -214
  116. package/template/skills/skillopt-sleep/scripts/python/__init__.py +0 -20
  117. package/template/skills/skillopt-sleep/scripts/python/__main__.py +0 -343
  118. package/template/skills/skillopt-sleep/scripts/python/backend.py +0 -1371
  119. package/template/skills/skillopt-sleep/scripts/python/budget.py +0 -75
  120. package/template/skills/skillopt-sleep/scripts/python/config.py +0 -162
  121. package/template/skills/skillopt-sleep/scripts/python/consolidate.py +0 -238
  122. package/template/skills/skillopt-sleep/scripts/python/cycle.py +0 -291
  123. package/template/skills/skillopt-sleep/scripts/python/dream.py +0 -138
  124. package/template/skills/skillopt-sleep/scripts/python/experiments/__init__.py +0 -1
  125. package/template/skills/skillopt-sleep/scripts/python/experiments/gbrain_bench.py +0 -119
  126. package/template/skills/skillopt-sleep/scripts/python/experiments/personas.py +0 -86
  127. package/template/skills/skillopt-sleep/scripts/python/experiments/report.py +0 -132
  128. package/template/skills/skillopt-sleep/scripts/python/experiments/run_experiment.py +0 -178
  129. package/template/skills/skillopt-sleep/scripts/python/experiments/run_gbrain.py +0 -209
  130. package/template/skills/skillopt-sleep/scripts/python/experiments/run_transfer.py +0 -155
  131. package/template/skills/skillopt-sleep/scripts/python/experiments/sweep.py +0 -164
  132. package/template/skills/skillopt-sleep/scripts/python/gate.py +0 -50
  133. package/template/skills/skillopt-sleep/scripts/python/harvest.py +0 -304
  134. package/template/skills/skillopt-sleep/scripts/python/harvest_codex.py +0 -253
  135. package/template/skills/skillopt-sleep/scripts/python/harvest_sources.py +0 -41
  136. package/template/skills/skillopt-sleep/scripts/python/judges.py +0 -84
  137. package/template/skills/skillopt-sleep/scripts/python/llm_miner.py +0 -134
  138. package/template/skills/skillopt-sleep/scripts/python/memory.py +0 -129
  139. package/template/skills/skillopt-sleep/scripts/python/mine.py +0 -312
  140. package/template/skills/skillopt-sleep/scripts/python/replay.py +0 -146
  141. package/template/skills/skillopt-sleep/scripts/python/rollout.py +0 -153
  142. package/template/skills/skillopt-sleep/scripts/python/scheduler.py +0 -138
  143. package/template/skills/skillopt-sleep/scripts/python/slow_update.py +0 -142
  144. package/template/skills/skillopt-sleep/scripts/python/staging.py +0 -103
  145. package/template/skills/skillopt-sleep/scripts/python/state.py +0 -96
  146. package/template/skills/skillopt-sleep/scripts/python/tasks_file.py +0 -81
  147. package/template/skills/skillopt-sleep/scripts/python/types.py +0 -146
  148. package/template/skills/skillopt-sleep/scripts/shell/__init__.py +0 -0
  149. package/template/skills/skillopt-sleep/scripts/shell/eval_only.py +0 -466
  150. package/template/skills/skillopt-sleep/scripts/shell/materialize_searchqa.py +0 -148
  151. package/template/skills/skillopt-sleep/scripts/shell/run_alfworld.sh +0 -60
  152. package/template/skills/skillopt-sleep/scripts/shell/run_searchqa.sh +0 -40
  153. package/template/skills/skillopt-sleep/scripts/shell/run_spreadsheetbench.sh +0 -39
  154. 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