feat(dream): add reflection skill and 12h reminder

Replay the last 24h of a repository — git log and reflog, CI, reviews,
memory, session transcripts — score every action as held, broke or unknown
against its evidence, harden the resulting hypotheses with dialectic, and
persist what survives. The objective is a measurably more confident next
run: a class of error counts as closed only once a test, check, hook or
written rule prevents it.

Derived improvements to the code and to the agent instructions are offered
as single-select questions, and the run ends in autotune and autoskill.

The reminder hook stamps into each repository's git dir, so every repo
carries its own twelve-hour cadence and the hook stays silent outside a
git repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-02 19:34:05 +02:00
parent c0f813b792
commit 168a307e4f
6 changed files with 293 additions and 8 deletions

View File

@@ -85,7 +85,7 @@ class TestSettingsRegistration(unittest.TestCase):
encoding="utf-8",
)
data = self._patch()
self.assertEqual(len(data["hooks"]["UserPromptSubmit"]), 2)
self.assertEqual(len(data["hooks"]["UserPromptSubmit"]), 3)
if __name__ == "__main__":

102
tests/test_dream.py Normal file
View File

@@ -0,0 +1,102 @@
"""Validate the dream reminder hook and its settings registration."""
from __future__ import annotations
import json
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SKILL = REPO_ROOT / "skills" / "dream" / "SKILL.md"
HOOK = REPO_ROOT / "skills" / "dream" / "hooks" / "reminder.sh"
PATCHER = REPO_ROOT / "scripts" / "enable-plugins.js"
def _run_hook(cwd: Path, stamp: Path | None = None, interval: str = "43200") -> subprocess.CompletedProcess:
env = {
"PATH": "/usr/bin:/bin",
"HOME": str(cwd),
"DREAM_INTERVAL_SECONDS": interval,
}
if stamp is not None:
env["DREAM_STAMP"] = str(stamp)
return subprocess.run(
["bash", str(HOOK)],
capture_output=True,
text=True,
check=True,
cwd=str(cwd),
env=env,
)
class TestDreamSkill(unittest.TestCase):
def test_skill_is_trigger_only(self):
text = SKILL.read_text(encoding="utf-8")
self.assertIn("name: dream", text)
self.assertIn("multiSelect: false", text)
class TestDreamHook(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.cwd = Path(self.tmp.name)
self.stamp = self.cwd / ".claude" / ".dream-stamp"
def test_first_run_hints_and_stamps(self):
result = _run_hook(self.cwd, self.stamp)
self.assertIn("/dream", result.stdout)
self.assertTrue(self.stamp.is_file())
def test_second_run_is_silent_within_interval(self):
_run_hook(self.cwd, self.stamp)
self.assertEqual(_run_hook(self.cwd, self.stamp).stdout, "")
def test_hint_returns_after_the_interval(self):
_run_hook(self.cwd, self.stamp)
self.assertIn("/dream", _run_hook(self.cwd, self.stamp, interval="0").stdout)
def test_corrupt_stamp_does_not_crash(self):
self.stamp.parent.mkdir(parents=True, exist_ok=True)
self.stamp.write_text("not-a-timestamp\n", encoding="utf-8")
self.assertIn("/dream", _run_hook(self.cwd, self.stamp).stdout)
def test_outside_a_repository_it_stays_silent(self):
self.assertEqual(_run_hook(self.cwd).stdout, "")
@unittest.skipUnless(shutil.which("git"), "git not installed")
def test_stamp_is_per_repository(self):
for name in ("one", "two"):
repo = self.cwd / name
repo.mkdir()
subprocess.run(["git", "init", "-q", str(repo)], check=True, capture_output=True)
self.assertIn("/dream", _run_hook(repo).stdout)
self.assertTrue((repo / ".git" / ".dream-stamp").is_file())
self.assertEqual(_run_hook(repo).stdout, "")
@unittest.skipUnless(shutil.which("node"), "node not installed")
class TestSettingsRegistration(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.settings = Path(self.tmp.name) / "settings.json"
def _patch(self) -> dict:
subprocess.run(["node", str(PATCHER), str(self.settings)], check=True, capture_output=True)
return json.loads(self.settings.read_text(encoding="utf-8"))
def test_hook_registered_once(self):
self._patch()
data = self._patch()
entries = json.dumps(data["hooks"]["UserPromptSubmit"])
self.assertEqual(entries.count("dream/hooks/reminder.sh"), 1)
self.assertEqual(entries.count("autotune/hooks/reminder.sh"), 1)
if __name__ == "__main__":
unittest.main()