Parse the command with shlex.split and run with shell=False, and restrict the executable to a python/python3 allow-list. This removes shell metacharacter injection (the benchmark only needs Python to manipulate spreadsheets). Adds tests for the allow-list gate and non-interpretation of shell metacharacters.
The last-resort _split fallback used the full task list when every task was
held-out test, so train and val both silently became the test set. Drop that
fallback so empty non-test slices stay empty, matching the hold-out contract.
Co-authored-by: Cursor <cursoragent@cursor.com>
* skillopt_sleep/evidence.py — append-only, thread-safe, redacted
evidence.jsonl per night: harvest sessions -> miner exchanges (verbatim
prompt/reply) -> mined tasks with checks -> split assignment -> every
replay attempt (phase-tagged, cache hits marked) -> per-task scores with
failing checks named -> reflect exchanges + parsed edits -> gate trials
and the final decision with its score arithmetic -> staged artifacts.
Config-gated (evidence_log, default on; evidence_max_chars cap).
* skillopt_sleep/prompts.py — central registry of the four LLM prompt
templates (miner/attempt/judge/reflect), byte-identical defaults to the
previously inlined strings; user overrides in prompts.json take effect
on the next model call (mtime-checked), no restart needed.
* cycle.py builds dual backends from config (optimizer_*/target_*),
pre-creates the staging dir so evidence lands beside the report;
staging.new_staging_dir() de-collides same-second runs;
latest_staging() now skips non-adoptable (evidence-only) folders.
* tests/test_sleep_evidence.py — 8 no-network stdlib tests: chain
completeness, redaction/truncation/ordering, disable flag, prompt
override round-trip + live effect, no-tasks-night adoption guard.
The Claude Code plugin ships a SessionEnd hook that logs a cheap timestamp
+ PWD marker to ~/.skillopt-sleep/session-end.log so the next nightly cycle
knows there is fresh activity to harvest. The Devin plugin had no
equivalent — nightly runs relied on cron alone with no activity signal.
Devin CLI supports SessionEnd hooks via .devin/hooks.v1.json (same JSON
format as Claude Code), firing with {"reason": "..."} on stdin and setting
DEVIN_PROJECT_DIR to the project root.
Add hooks/hooks.v1.json (SessionEnd hook config) and
hooks/on-session-end.sh (mirrors the Claude Code on-session-end.sh: append
timestamp + project dir, non-blocking, no API spend). Update README install
instructions with the optional copy step.
The Devin MCP server's backend enum only listed mock/claude/codex/copilot,
excluding the handoff backend that was merged to the engine in #125. This
made the subscription-friendly, no-API-key path unavailable to Devin users
while Claude Code had a dedicated /skillopt-sleep-handoff command for it.
Add "handoff" to the backend enum in _TOOL_SCHEMA so sleep_run accepts
backend: "handoff". The engine already handles the prompt/answer file loop
(exit code 3 + .skillopt-sleep-handoff/); the MCP server needs no special
handling — it passes through the engine output showing pending prompts.
Update the README, rules snippet, and test_backends_in_enum accordingly.