feat(devin): add SessionEnd hook for activity tracking (#5)

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.
This commit is contained in:
Ismar
2026-07-16 15:20:36 +02:00
parent 195d95e00c
commit 6e10394e86
4 changed files with 111 additions and 8 deletions

View File

@@ -16,7 +16,10 @@ source into the Claude Code-compatible JSONL the engine reads.
| `harvest_devin.py` | converts Devin ATIF-v1.7 transcripts + agentmemory + `.devin/skills` into JSONL, with `taskKey` + outcome envelopes |
| `judge.py` | reference judge for the deferred/judge branch of the validation gate |
| `mcp-config.example.json` | drop-in MCP server config |
| `devin-rules.snippet.md` | paste into `.devin/rules/skillopt-sleep.md` |
| `install.sh` | copies hooks + rules into a project's `.devin/` and prints the MCP registration command |
| `devin-rules.snippet.md` | copied to `.devin/rules/skillopt-sleep.md` by `install.sh` |
| `hooks/hooks.v1.json` | SessionEnd hook config — copied to `.devin/hooks.v1.json` by `install.sh` |
| `hooks/on-session-end.sh` | best-effort activity marker script (called by the hook) |
## What it harvests
@@ -33,10 +36,20 @@ After `sleep_adopt`, the evolved skill is synced to `.devin/skills/skillopt-slee
Requires Python ≥ 3.10. No third-party packages — the server is pure stdlib.
1. **Register the MCP server.** Use `mcp-config.example.json` as a template; set
`args` to the absolute path of this `mcp_server.py`. The engine is found
automatically (this plugin lives inside the SkillOpt repo). Or via the Devin
CLI:
1. **Install hooks + rules into your project.** From the repo root:
```bash
bash plugins/devin/install.sh /path/to/your/project
```
This copies the SessionEnd hook and rules snippet into the project's
`.devin/` directory and prints the MCP registration command. The hook is
on by default — it logs a cheap activity marker when each session ends so
the next nightly cycle knows there is fresh data to harvest. It is
non-blocking and spends no API budget. Re-run the script to update.
2. **Register the MCP server.** Use `mcp-config.example.json` as a template, or
run the command printed by `install.sh`:
```bash
devin mcp add skillopt-sleep \
@@ -44,9 +57,6 @@ Requires Python ≥ 3.10. No third-party packages — the server is pure stdlib.
-- python3 /abs/path/to/SkillOpt/plugins/devin/mcp_server.py
```
2. **(Optional)** copy `devin-rules.snippet.md` to `.devin/rules/skillopt-sleep.md`
so Devin proactively offers the tools.
3. Ask Devin: *"run the sleep cycle"*, *"what did the last sleep propose?"*, *"adopt it"*.
## Tools

View File

@@ -0,0 +1,14 @@
{
"SessionEnd": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "\"${DEVIN_PROJECT_DIR}/.devin/hooks/on-session-end.sh\"",
"timeout": 5
}
]
}
]
}

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# SkillOpt-Sleep SessionEnd hook for Devin (best-effort, NON-BLOCKING).
#
# This does NOT run the optimizer. It only appends a tiny marker so the next
# nightly cycle knows there is fresh activity to harvest, and (optionally)
# nudges the user once that a sleep cycle is available. It must never fail the
# session or spend API budget.
#
# Install: copy this file and hooks.v1.json into your project's .devin/hooks/
# directory. Devin CLI reads .devin/hooks.v1.json automatically.
set -uo pipefail
STATE_DIR="${HOME}/.skillopt-sleep"
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
# Record that a session just ended (cheap; used for "is there new data?").
printf '%s\t%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${DEVIN_PROJECT_DIR:-${PWD}}" \
>> "$STATE_DIR/session-end.log" 2>/dev/null || true
exit 0

59
plugins/devin/install.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Install the SkillOpt-Sleep Devin integration into a project.
# Copies the SessionEnd hook and rules snippet into .devin/, and prints
# the MCP server registration command. Idempotent.
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$PLUGIN_DIR/../.." && pwd)"
PROJECT="${1:-$(pwd)}"
echo "[install] repo: $REPO_ROOT"
echo "[install] project: $PROJECT"
DEVIN_DIR="$PROJECT/.devin"
mkdir -p "$DEVIN_DIR/hooks" "$DEVIN_DIR/rules"
# 1) SessionEnd hook (on by default — provides activity signal for nightly harvest)
# Merge into existing hooks.v1.json instead of overwriting, so we don't
# destroy other project hooks.
HOOK_SCRIPT_SRC="$PLUGIN_DIR/hooks/on-session-end.sh"
HOOK_SCRIPT_DST="$DEVIN_DIR/hooks/on-session-end.sh"
cp "$HOOK_SCRIPT_SRC" "$HOOK_SCRIPT_DST"
chmod +x "$HOOK_SCRIPT_DST"
echo "[install] hook script -> $HOOK_SCRIPT_DST"
HOOK_CONFIG="$DEVIN_DIR/hooks.v1.json"
if [ -f "$HOOK_CONFIG" ]; then
# Merge our SessionEnd hook into the existing config (jq deep-merge)
if command -v jq >/dev/null 2>&1; then
jq -s '.[0] * .[1]' "$HOOK_CONFIG" "$PLUGIN_DIR/hooks/hooks.v1.json" > "$HOOK_CONFIG.tmp"
mv "$HOOK_CONFIG.tmp" "$HOOK_CONFIG"
echo "[install] session-end hook -> $HOOK_CONFIG (merged)"
else
echo "[install] WARNING: jq not found; cannot merge into existing $HOOK_CONFIG"
echo "[install] Merge this SessionEnd hook manually or install jq:"
echo "[install] cat $PLUGIN_DIR/hooks/hooks.v1.json"
echo "[install] Skipping hook config to avoid overwriting existing hooks."
fi
else
cp "$PLUGIN_DIR/hooks/hooks.v1.json" "$HOOK_CONFIG"
echo "[install] session-end hook -> $HOOK_CONFIG"
fi
# 2) Rules snippet so Devin proactively offers the tools
cp "$PLUGIN_DIR/devin-rules.snippet.md" "$DEVIN_DIR/rules/skillopt-sleep.md"
echo "[install] rules snippet -> $DEVIN_DIR/rules/skillopt-sleep.md"
# 3) Print the MCP server registration command
cat <<EOF
[install] Register the MCP server (run once per machine):
devin mcp add skillopt-sleep \\
--env "SKILLOPT_DEVIN_CLAUDE_HOME=\$HOME/.skillopt-sleep-devin" \\
-- python3 $PLUGIN_DIR/mcp_server.py
Done. Try asking Devin:
Run the sleep cycle for this project.
EOF