self-evolve-framework 1.2.0 → 1.4.0
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 +22 -11
- package/bin/cli.js +1 -0
- package/package.json +1 -1
- package/template/skills/ponytail/SKILL.md +133 -0
- package/template/skills/ponytail/scripts/hooks/claude-codex-hooks.json +44 -0
- package/template/skills/ponytail/scripts/hooks/copilot-hooks.json +21 -0
- package/template/skills/ponytail/scripts/hooks/ponytail-activate.js +91 -0
- package/template/skills/ponytail/scripts/hooks/ponytail-config.js +122 -0
- package/template/skills/ponytail/scripts/hooks/ponytail-instructions.js +94 -0
- package/template/skills/ponytail/scripts/hooks/ponytail-mode-tracker.js +55 -0
- package/template/skills/ponytail/scripts/hooks/ponytail-runtime.js +68 -0
- package/template/skills/ponytail/scripts/hooks/ponytail-statusline.ps1 +21 -0
- package/template/skills/ponytail/scripts/hooks/ponytail-statusline.sh +12 -0
- package/template/skills/ponytail/scripts/hooks/ponytail-subagent.js +22 -0
- package/template/skills/ponytail/scripts/mcp/README.md +46 -0
- package/template/skills/ponytail/scripts/mcp/index.js +48 -0
- package/template/skills/ponytail/scripts/mcp/instructions.js +26 -0
- package/template/skills/ponytail/scripts/mcp/package.json +13 -0
- package/template/skills/ponytail/scripts/mcp/test/instructions.test.js +22 -0
- package/template/skills/ponytail-audit/SKILL.md +41 -0
- package/template/skills/ponytail-debt/SKILL.md +44 -0
- package/template/skills/ponytail-gain/SKILL.md +50 -0
- package/template/skills/ponytail-help/SKILL.md +69 -0
- package/template/skills/ponytail-review/SKILL.md +57 -0
- package/template/skills/skillopt-sleep/SKILL.md +48 -34
- package/template/skills/skillopt-sleep/scripts/python/__init__.py +20 -0
- package/template/skills/skillopt-sleep/scripts/python/__main__.py +343 -0
- package/template/skills/skillopt-sleep/scripts/python/backend.py +1371 -0
- package/template/skills/skillopt-sleep/scripts/python/budget.py +75 -0
- package/template/skills/skillopt-sleep/scripts/python/config.py +162 -0
- package/template/skills/skillopt-sleep/scripts/python/consolidate.py +238 -0
- package/template/skills/skillopt-sleep/scripts/python/cycle.py +291 -0
- package/template/skills/skillopt-sleep/scripts/python/dream.py +138 -0
- package/template/skills/skillopt-sleep/scripts/python/experiments/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/python/experiments/gbrain_bench.py +119 -0
- package/template/skills/skillopt-sleep/scripts/python/experiments/personas.py +86 -0
- package/template/skills/skillopt-sleep/scripts/python/experiments/report.py +132 -0
- package/template/skills/skillopt-sleep/scripts/python/experiments/run_experiment.py +178 -0
- package/template/skills/skillopt-sleep/scripts/python/experiments/run_gbrain.py +209 -0
- package/template/skills/skillopt-sleep/scripts/python/experiments/run_transfer.py +155 -0
- package/template/skills/skillopt-sleep/scripts/python/experiments/sweep.py +164 -0
- package/template/skills/skillopt-sleep/scripts/python/gate.py +50 -0
- package/template/skills/skillopt-sleep/scripts/python/harvest.py +304 -0
- package/template/skills/skillopt-sleep/scripts/python/harvest_codex.py +253 -0
- package/template/skills/skillopt-sleep/scripts/python/harvest_sources.py +41 -0
- package/template/skills/skillopt-sleep/scripts/python/judges.py +84 -0
- package/template/skills/skillopt-sleep/scripts/python/llm_miner.py +134 -0
- package/template/skills/skillopt-sleep/scripts/python/memory.py +129 -0
- package/template/skills/skillopt-sleep/scripts/python/mine.py +312 -0
- package/template/skills/skillopt-sleep/scripts/python/replay.py +146 -0
- package/template/skills/skillopt-sleep/scripts/python/rollout.py +153 -0
- package/template/skills/skillopt-sleep/scripts/python/scheduler.py +138 -0
- package/template/skills/skillopt-sleep/scripts/python/slow_update.py +142 -0
- package/template/skills/skillopt-sleep/scripts/python/staging.py +103 -0
- package/template/skills/skillopt-sleep/scripts/python/state.py +96 -0
- package/template/skills/skillopt-sleep/scripts/python/tasks_file.py +81 -0
- package/template/skills/skillopt-sleep/scripts/python/types.py +146 -0
- package/template/skills/skillopt-sleep/scripts/shell/__init__.py +0 -0
- package/template/skills/skillopt-sleep/scripts/shell/eval_only.py +466 -0
- package/template/skills/skillopt-sleep/scripts/shell/materialize_searchqa.py +148 -0
- package/template/skills/skillopt-sleep/scripts/shell/run_alfworld.sh +60 -0
- package/template/skills/skillopt-sleep/scripts/shell/run_searchqa.sh +40 -0
- package/template/skills/skillopt-sleep/scripts/shell/run_spreadsheetbench.sh +39 -0
- package/template/skills/skillopt-sleep/scripts/shell/train.py +556 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""SkillOpt-Sleep — nightly offline self-evolution for a local Claude agent.
|
|
2
|
+
|
|
3
|
+
A Claude Code plugin engine that gives a user's agent a "sleep cycle":
|
|
4
|
+
harvest the day's real session transcripts, mine recurring tasks, replay
|
|
5
|
+
them offline, and consolidate short-term experience into long-term memory
|
|
6
|
+
(CLAUDE.md) and skills (SKILL.md) behind a SkillOpt validation gate.
|
|
7
|
+
|
|
8
|
+
Synthesizes three ideas:
|
|
9
|
+
* SkillOpt — validation-gated bounded text optimization (this repo)
|
|
10
|
+
* Dreams — offline memory consolidation, input never mutated
|
|
11
|
+
* Sleep — short-term experience -> long-term competence, offline
|
|
12
|
+
|
|
13
|
+
Public entry points:
|
|
14
|
+
* skillopt_sleep.cli — `python -m skillopt_sleep ...`
|
|
15
|
+
* skillopt_sleep.cycle.run_sleep_cycle(...)
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
__all__ = ["__version__"]
|
|
20
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""SkillOpt-Sleep — command-line interface.
|
|
2
|
+
|
|
3
|
+
python -m skillopt_sleep run # full cycle: harvest->mine->replay->gate->stage
|
|
4
|
+
python -m skillopt_sleep dry-run # same but report only, no staging/adopt
|
|
5
|
+
python -m skillopt_sleep status # show state + latest staged proposal
|
|
6
|
+
python -m skillopt_sleep adopt # apply the latest staged proposal (with backup)
|
|
7
|
+
python -m skillopt_sleep harvest # just print what would be mined (debug)
|
|
8
|
+
|
|
9
|
+
Common flags:
|
|
10
|
+
--project PATH project to evolve (default: cwd)
|
|
11
|
+
--scope all|invoked harvest scope (default: invoked)
|
|
12
|
+
--max-sessions N cap transcript sessions per run
|
|
13
|
+
--max-tasks N cap mined tasks per run
|
|
14
|
+
--target-skill-path PATH explicit live SKILL.md to stage/adopt
|
|
15
|
+
--tasks-file PATH reviewed TaskRecord JSON file to replay instead of harvesting
|
|
16
|
+
--backend mock|claude|codex|copilot
|
|
17
|
+
--source claude|codex|auto
|
|
18
|
+
--model NAME
|
|
19
|
+
--lookback-hours N
|
|
20
|
+
--auto-adopt
|
|
21
|
+
--json machine-readable output
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import json
|
|
27
|
+
import os
|
|
28
|
+
import sys
|
|
29
|
+
from typing import Any, Dict
|
|
30
|
+
|
|
31
|
+
from skillopt_sleep.config import load_config
|
|
32
|
+
from skillopt_sleep.cycle import run_sleep_cycle
|
|
33
|
+
from skillopt_sleep.harvest_sources import harvest_for_config
|
|
34
|
+
from skillopt_sleep.mine import mine
|
|
35
|
+
from skillopt_sleep.staging import adopt as adopt_staging
|
|
36
|
+
from skillopt_sleep.staging import latest_staging
|
|
37
|
+
from skillopt_sleep.state import SleepState
|
|
38
|
+
from skillopt_sleep.tasks_file import load_tasks_file, make_tasks_payload, write_tasks_file
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _read_text(path: str) -> str:
|
|
42
|
+
try:
|
|
43
|
+
with open(path, encoding="utf-8") as f:
|
|
44
|
+
return f.read()
|
|
45
|
+
except Exception:
|
|
46
|
+
return ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _report_payload(rep, outcome) -> Dict[str, Any]:
|
|
50
|
+
return {
|
|
51
|
+
"night": rep.night,
|
|
52
|
+
"accepted": rep.accepted,
|
|
53
|
+
"gate_action": rep.gate_action,
|
|
54
|
+
"no_edits_reason": getattr(rep, "no_edits_reason", ""),
|
|
55
|
+
"baseline": rep.baseline_score,
|
|
56
|
+
"candidate": rep.candidate_score,
|
|
57
|
+
"n_tasks": rep.n_tasks,
|
|
58
|
+
"n_sessions": rep.n_sessions,
|
|
59
|
+
"n_accepted_edits": len(rep.edits),
|
|
60
|
+
"n_rejected_edits": len(rep.rejected_edits),
|
|
61
|
+
"edits": [e.__dict__ for e in rep.edits],
|
|
62
|
+
"rejected_edits": [e.__dict__ for e in rep.rejected_edits],
|
|
63
|
+
"notes": rep.notes,
|
|
64
|
+
"staging_dir": outcome.staging_dir,
|
|
65
|
+
"adopted": outcome.adopted,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _add_common(p: argparse.ArgumentParser) -> None:
|
|
70
|
+
p.add_argument("--project", default="")
|
|
71
|
+
p.add_argument("--scope", default="", choices=["", "all", "invoked"])
|
|
72
|
+
p.add_argument("--backend", default="", choices=["", "mock", "claude", "codex", "copilot"])
|
|
73
|
+
p.add_argument("--model", default="")
|
|
74
|
+
p.add_argument("--codex-path", default="", help="path to the real @openai/codex binary")
|
|
75
|
+
p.add_argument("--claude-home", default="", help="override ~/.claude (also isolates state)")
|
|
76
|
+
p.add_argument("--codex-home", default="", help="override ~/.codex for archived session harvest")
|
|
77
|
+
p.add_argument("--source", default="", choices=["", "claude", "codex", "auto"],
|
|
78
|
+
help="session transcript source")
|
|
79
|
+
p.add_argument("--lookback-hours", type=int, default=None,
|
|
80
|
+
help="harvest window in hours; 0 = scan full history")
|
|
81
|
+
p.add_argument("--edit-budget", type=int, default=0)
|
|
82
|
+
p.add_argument("--max-sessions", type=int, default=0,
|
|
83
|
+
help="cap harvested sessions before mining; default derives from max tasks")
|
|
84
|
+
p.add_argument("--max-tasks", type=int, default=0,
|
|
85
|
+
help="cap mined tasks for this run")
|
|
86
|
+
p.add_argument("--target-skill-path", default="",
|
|
87
|
+
help="explicit live SKILL.md path to evolve/stage/adopt")
|
|
88
|
+
p.add_argument("--tasks-file", default="",
|
|
89
|
+
help="reviewed TaskRecord JSON file to replay instead of harvesting")
|
|
90
|
+
p.add_argument("--progress", action="store_true",
|
|
91
|
+
help="print phase progress to stderr")
|
|
92
|
+
p.add_argument("--auto-adopt", action="store_true")
|
|
93
|
+
p.add_argument("--json", action="store_true")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _cfg_from_args(args, task_meta: Dict[str, Any] | None = None) -> Any:
|
|
97
|
+
overrides: Dict[str, Any] = {}
|
|
98
|
+
if args.project:
|
|
99
|
+
overrides["invoked_project"] = os.path.abspath(args.project)
|
|
100
|
+
overrides["projects"] = "invoked"
|
|
101
|
+
if args.scope:
|
|
102
|
+
overrides["projects"] = args.scope
|
|
103
|
+
if args.backend:
|
|
104
|
+
overrides["backend"] = args.backend
|
|
105
|
+
if args.model:
|
|
106
|
+
overrides["model"] = args.model
|
|
107
|
+
if getattr(args, "codex_path", ""):
|
|
108
|
+
overrides["codex_path"] = os.path.abspath(args.codex_path)
|
|
109
|
+
if getattr(args, "claude_home", ""):
|
|
110
|
+
overrides["claude_home"] = os.path.abspath(args.claude_home)
|
|
111
|
+
if getattr(args, "codex_home", ""):
|
|
112
|
+
overrides["codex_home"] = os.path.abspath(args.codex_home)
|
|
113
|
+
if getattr(args, "source", ""):
|
|
114
|
+
overrides["transcript_source"] = args.source
|
|
115
|
+
lh = getattr(args, "lookback_hours", None)
|
|
116
|
+
if lh is not None: # --lookback-hours was explicitly passed (0 = full history)
|
|
117
|
+
overrides["lookback_hours"] = lh
|
|
118
|
+
if getattr(args, "edit_budget", 0):
|
|
119
|
+
overrides["edit_budget"] = args.edit_budget
|
|
120
|
+
if getattr(args, "max_sessions", 0):
|
|
121
|
+
overrides["max_sessions_per_night"] = args.max_sessions
|
|
122
|
+
if getattr(args, "max_tasks", 0):
|
|
123
|
+
overrides["max_tasks_per_night"] = args.max_tasks
|
|
124
|
+
target_skill_path = getattr(args, "target_skill_path", "")
|
|
125
|
+
if not target_skill_path and task_meta:
|
|
126
|
+
target_skill_path = str(task_meta.get("target_skill_path") or "")
|
|
127
|
+
if target_skill_path:
|
|
128
|
+
path = os.path.expanduser(target_skill_path)
|
|
129
|
+
if args.project and not os.path.isabs(path):
|
|
130
|
+
path = os.path.join(os.path.abspath(args.project), path)
|
|
131
|
+
overrides["target_skill_path"] = os.path.abspath(path)
|
|
132
|
+
if getattr(args, "progress", False):
|
|
133
|
+
overrides["progress"] = True
|
|
134
|
+
if getattr(args, "auto_adopt", False):
|
|
135
|
+
overrides["auto_adopt"] = True
|
|
136
|
+
return load_config(**overrides)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def cmd_run(args, dry: bool = False) -> int:
|
|
140
|
+
task_meta: Dict[str, Any] = {}
|
|
141
|
+
tasks = None
|
|
142
|
+
if getattr(args, "tasks_file", ""):
|
|
143
|
+
# Load once before config so target_skill_path can default from metadata.
|
|
144
|
+
tasks, task_meta = load_tasks_file(args.tasks_file)
|
|
145
|
+
cfg = _cfg_from_args(args, task_meta=task_meta)
|
|
146
|
+
if getattr(args, "tasks_file", ""):
|
|
147
|
+
tasks, task_meta = load_tasks_file(
|
|
148
|
+
args.tasks_file,
|
|
149
|
+
holdout_fraction=cfg.get("holdout_fraction", 0.34),
|
|
150
|
+
seed=cfg.get("seed", 42),
|
|
151
|
+
)
|
|
152
|
+
if cfg.get("backend", "mock") != "mock" and task_meta.get("reviewed") is not True:
|
|
153
|
+
print(
|
|
154
|
+
"[sleep] refusing real-backend replay from an unreviewed tasks file; "
|
|
155
|
+
"inspect/redact it and set \"reviewed\": true first",
|
|
156
|
+
file=sys.stderr,
|
|
157
|
+
)
|
|
158
|
+
return 2
|
|
159
|
+
outcome = run_sleep_cycle(cfg, seed_tasks=tasks, dry_run=dry)
|
|
160
|
+
rep = outcome.report
|
|
161
|
+
if args.json:
|
|
162
|
+
payload = _report_payload(rep, outcome)
|
|
163
|
+
if task_meta:
|
|
164
|
+
payload["tasks_file"] = task_meta.get("tasks_file", "")
|
|
165
|
+
payload["tasks_reviewed"] = task_meta.get("reviewed", False)
|
|
166
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
167
|
+
else:
|
|
168
|
+
print(f"[sleep] night {rep.night}: {rep.n_sessions} sessions -> {rep.n_tasks} tasks")
|
|
169
|
+
print(f"[sleep] held-out {rep.baseline_score:.3f} -> {rep.candidate_score:.3f} "
|
|
170
|
+
f"=> {rep.gate_action} (accepted={rep.accepted})")
|
|
171
|
+
for e in rep.edits:
|
|
172
|
+
print(f" + [{e.target}/{e.op}] {e.content}")
|
|
173
|
+
if rep.rejected_edits:
|
|
174
|
+
print("[sleep] rejected by gate:")
|
|
175
|
+
for e in rep.rejected_edits:
|
|
176
|
+
print(f" - [{e.target}/{e.op}] {e.content}")
|
|
177
|
+
if outcome.staging_dir:
|
|
178
|
+
print(f"[sleep] staged: {outcome.staging_dir}")
|
|
179
|
+
if not outcome.adopted:
|
|
180
|
+
print("[sleep] review it, then: python -m skillopt_sleep adopt")
|
|
181
|
+
if outcome.adopted:
|
|
182
|
+
print(f"[sleep] auto-adopted: {', '.join(outcome.adopted_paths)}")
|
|
183
|
+
return 0
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def cmd_status(args) -> int:
|
|
187
|
+
cfg = _cfg_from_args(args)
|
|
188
|
+
state = SleepState.load(cfg.state_path)
|
|
189
|
+
project = cfg.get("invoked_project") or os.getcwd()
|
|
190
|
+
latest = latest_staging(project)
|
|
191
|
+
info = {
|
|
192
|
+
"night": state.night,
|
|
193
|
+
"state_path": cfg.state_path,
|
|
194
|
+
"project": project,
|
|
195
|
+
"history_tail": state.data.get("history", [])[-5:],
|
|
196
|
+
"latest_staging": latest,
|
|
197
|
+
"slow_memory_chars": len(state.slow_memory),
|
|
198
|
+
}
|
|
199
|
+
if args.json:
|
|
200
|
+
print(json.dumps(info, ensure_ascii=False, indent=2))
|
|
201
|
+
else:
|
|
202
|
+
print(f"[sleep] nights so far: {state.night}")
|
|
203
|
+
print(f"[sleep] project: {project}")
|
|
204
|
+
if latest:
|
|
205
|
+
print(f"[sleep] latest staged proposal: {latest}")
|
|
206
|
+
rp = os.path.join(latest, "report.md")
|
|
207
|
+
if os.path.exists(rp):
|
|
208
|
+
with open(rp) as f:
|
|
209
|
+
print("\n" + f.read())
|
|
210
|
+
else:
|
|
211
|
+
print("[sleep] no staged proposals yet.")
|
|
212
|
+
return 0
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def cmd_adopt(args) -> int:
|
|
216
|
+
cfg = _cfg_from_args(args)
|
|
217
|
+
project = cfg.get("invoked_project") or os.getcwd()
|
|
218
|
+
target = args.staging or latest_staging(project)
|
|
219
|
+
if not target or not os.path.isdir(target):
|
|
220
|
+
print("[sleep] nothing to adopt (no staging dir).")
|
|
221
|
+
return 1
|
|
222
|
+
updated = adopt_staging(target)
|
|
223
|
+
print(f"[sleep] adopted from {target}")
|
|
224
|
+
for p in updated:
|
|
225
|
+
print(f" -> {p}")
|
|
226
|
+
if not updated:
|
|
227
|
+
print("[sleep] (proposal contained no accepted changes)")
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def cmd_harvest(args) -> int:
|
|
232
|
+
cfg = _cfg_from_args(args)
|
|
233
|
+
session_limit = cfg.get("max_sessions_per_night", 0) or cfg.get("max_tasks_per_night", 40) * 3
|
|
234
|
+
target_skill_path = cfg.managed_skill_path() if cfg.get("target_skill_path", "") else ""
|
|
235
|
+
target_skill_text = _read_text(target_skill_path) if target_skill_path else ""
|
|
236
|
+
max_tasks = cfg.get("max_tasks_per_night", 40)
|
|
237
|
+
candidate_limit = max_tasks
|
|
238
|
+
if cfg.get("target_task_filter", True) and target_skill_text:
|
|
239
|
+
candidate_limit = max(max_tasks, max_tasks * 3)
|
|
240
|
+
digests = harvest_for_config(cfg, limit=session_limit)
|
|
241
|
+
tasks = mine(
|
|
242
|
+
digests,
|
|
243
|
+
max_tasks=max_tasks,
|
|
244
|
+
candidate_limit=candidate_limit,
|
|
245
|
+
holdout_fraction=cfg.get("holdout_fraction", 0.34),
|
|
246
|
+
seed=cfg.get("seed", 42),
|
|
247
|
+
target_skill_text=target_skill_text,
|
|
248
|
+
target_skill_path=target_skill_path,
|
|
249
|
+
)
|
|
250
|
+
payload = make_tasks_payload(
|
|
251
|
+
tasks,
|
|
252
|
+
project=cfg.get("invoked_project") or os.getcwd(),
|
|
253
|
+
transcript_source=cfg.get("transcript_source", ""),
|
|
254
|
+
n_sessions=len(digests),
|
|
255
|
+
target_skill_path=target_skill_path,
|
|
256
|
+
)
|
|
257
|
+
output_path = ""
|
|
258
|
+
if getattr(args, "output", ""):
|
|
259
|
+
output_path = write_tasks_file(args.output, payload)
|
|
260
|
+
if args.json:
|
|
261
|
+
json_payload = dict(payload)
|
|
262
|
+
if output_path:
|
|
263
|
+
json_payload["output"] = output_path
|
|
264
|
+
print(json.dumps(json_payload, ensure_ascii=False, indent=2))
|
|
265
|
+
else:
|
|
266
|
+
print(f"[sleep] {len(digests)} sessions -> {len(tasks)} tasks")
|
|
267
|
+
if output_path:
|
|
268
|
+
print(f"[sleep] wrote reviewed-task draft: {output_path}")
|
|
269
|
+
for t in tasks:
|
|
270
|
+
print(f" [{t.split}/{t.outcome}] {t.intent[:90]}")
|
|
271
|
+
return 0
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def cmd_schedule(args) -> int:
|
|
275
|
+
from skillopt_sleep.scheduler import schedule, list_scheduled
|
|
276
|
+
cfg = _cfg_from_args(args)
|
|
277
|
+
project = cfg.get("invoked_project") or os.getcwd()
|
|
278
|
+
ok, msg = schedule(project, backend=cfg.get("backend", "mock"),
|
|
279
|
+
hour=args.hour, minute=args.minute,
|
|
280
|
+
extra=("--auto-adopt" if getattr(args, "auto_adopt", False) else ""))
|
|
281
|
+
print("[sleep] " + msg)
|
|
282
|
+
cur = list_scheduled()
|
|
283
|
+
if cur:
|
|
284
|
+
print("[sleep] currently scheduled:")
|
|
285
|
+
for ln in cur:
|
|
286
|
+
print(" " + ln[:140])
|
|
287
|
+
return 0 if ok else 1
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def cmd_unschedule(args) -> int:
|
|
291
|
+
from skillopt_sleep.scheduler import unschedule
|
|
292
|
+
cfg = _cfg_from_args(args)
|
|
293
|
+
project = cfg.get("invoked_project") or os.getcwd()
|
|
294
|
+
ok, msg = unschedule(project, all_projects=getattr(args, "all", False))
|
|
295
|
+
print("[sleep] " + msg)
|
|
296
|
+
return 0 if ok else 1
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def main(argv=None) -> int:
|
|
300
|
+
parser = argparse.ArgumentParser(prog="skillopt_sleep", description="SkillOpt-Sleep nightly self-evolution")
|
|
301
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
302
|
+
|
|
303
|
+
p_run = sub.add_parser("run", help="run a full sleep cycle")
|
|
304
|
+
_add_common(p_run)
|
|
305
|
+
p_dry = sub.add_parser("dry-run", help="harvest+mine+replay, report only")
|
|
306
|
+
_add_common(p_dry)
|
|
307
|
+
p_status = sub.add_parser("status", help="show state + latest proposal")
|
|
308
|
+
_add_common(p_status)
|
|
309
|
+
p_adopt = sub.add_parser("adopt", help="apply latest staged proposal")
|
|
310
|
+
_add_common(p_adopt)
|
|
311
|
+
p_adopt.add_argument("--staging", default="", help="specific staging dir")
|
|
312
|
+
p_harvest = sub.add_parser("harvest", help="debug: show mined tasks")
|
|
313
|
+
_add_common(p_harvest)
|
|
314
|
+
p_harvest.add_argument("--output", default="", help="write mined tasks JSON for review")
|
|
315
|
+
p_sched = sub.add_parser("schedule", help="install a nightly cron entry for this project")
|
|
316
|
+
_add_common(p_sched)
|
|
317
|
+
p_sched.add_argument("--hour", type=int, default=3)
|
|
318
|
+
p_sched.add_argument("--minute", type=int, default=17)
|
|
319
|
+
p_unsched = sub.add_parser("unschedule", help="remove the nightly cron entry")
|
|
320
|
+
_add_common(p_unsched)
|
|
321
|
+
p_unsched.add_argument("--all", action="store_true", help="remove all managed entries")
|
|
322
|
+
|
|
323
|
+
args = parser.parse_args(argv)
|
|
324
|
+
if args.cmd == "run":
|
|
325
|
+
return cmd_run(args, dry=False)
|
|
326
|
+
if args.cmd == "dry-run":
|
|
327
|
+
return cmd_run(args, dry=True)
|
|
328
|
+
if args.cmd == "status":
|
|
329
|
+
return cmd_status(args)
|
|
330
|
+
if args.cmd == "adopt":
|
|
331
|
+
return cmd_adopt(args)
|
|
332
|
+
if args.cmd == "harvest":
|
|
333
|
+
return cmd_harvest(args)
|
|
334
|
+
if args.cmd == "schedule":
|
|
335
|
+
return cmd_schedule(args)
|
|
336
|
+
if args.cmd == "unschedule":
|
|
337
|
+
return cmd_unschedule(args)
|
|
338
|
+
parser.print_help()
|
|
339
|
+
return 2
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
if __name__ == "__main__":
|
|
343
|
+
sys.exit(main())
|