A dream skill that runs a natural-language-triggered, four-phase memory-consolidation cycle over this project's auto-memory files…

A dream skill that consolidates this project's auto-memory files, a self-improvement skill that reflects on a session's own efficiency, plus a turn-end/session-start mods hook pair that nudges the next session to run a dream cycle.
/plugin install memory-enhancement@kwitsch-plugins
| Skill | What it does |
|---|---|
dream | Consolidates this project's auto-memory files in four phases (orient, gather signal, consolidate, update the MEMORY.md index), optionally compressing touched detail files via cc-knowledge's cc-compress when that plugin is enabled. |
self-improvement | Reflects on the current session's own tool calls and reasoning to find concrete ways the task could have been solved faster or more efficiently, reports a summary to the user, and saves durable, deduped lessons as feedback memory. |
Say "dream" (or describe wanting to consolidate/clean up memory) and the dream skill runs four phases over ~/.claude/projects/<project>/memory/: orient, gather signal from recent session transcripts, consolidate (merge duplicates, drop stale entries, resolve contradictions, author a new memory file for any signal with no existing memory home, back up each existing changed detail file alongside itself), and update the MEMORY.md index -- keeping it under its 200-line load cutoff. If cc-knowledge is enabled, touched detail files also get compressed caveman-style via its cc-compress skill -- no hard dependency, silent no-op if that plugin isn't installed. Only files that actually need a change are touched -- untouched memory files are left byte-for-byte alone.
An in-process mods hooks module (no hook process is spawned) flags each finished main-loop turn's project as dream-due; at the next genuinely new session (not a same-session /clear, /compact, --resume/--continue, or a mid-session plugin reload) it consumes that flag and nudges Claude to run a cycle. On by default -- set auto_dream to false to keep the flag silent.
hooks/register.ts 48 lines1import type { EngineInterface, Register } from "claude-code";
2import { NUDGE, dueKey, isAutoDreamEnabled } from "./dream.mjs";
3
4// The session-start root (CLAUDE_PROJECT_DIR's semantics): $.session.root() follows /cd and worktree moves,
5// which would flag a throwaway worktree instead of the project. A reload re-captures it from root().
6let startRoot: string | undefined;
7
8// This project's dream-due key in the plugin's own $.store.
9async function projectKey($: EngineInterface): Promise<string> {
10 const root = startRoot ?? (await $.session.root());
11 // realpath, so symlink variants of one project dir share a key; the dir as given when it does not resolve
12 const real = (await $.fs.stat(root, { resolve: true }).catch(() => undefined))?.realPath ?? root;
13 return dueKey(real);
14}
15
16export const register: Register = (on, options) => {
17 // Stop equivalent: every main-loop turn that ended normally flags the next session as dream-due.
18 on("turn.complete", async ($, e, next) => {
19 const result = await next(e);
20 if (e.agentId !== undefined || e.reason === "aborted" || e.reason === "error") return result;
21 try {
22 const key = await projectKey($);
23 if ((await $.store.get(key)) === undefined) await $.store.set(key, true);
24 } catch {
25 // fail open: a flag hiccup never touches the turn
26 }
27 return result;
28 });
29
30 // SessionStart(startup) equivalent: session.start also fires on --resume/--continue and on a mid-session
31 // plugin reload; a transcript that already holds user turns is not a fresh startup, so it keeps the flag.
32 on("session.start", async ($, e, next) => {
33 const result = await next(e);
34 try {
35 startRoot = await $.session.root();
36 if (!isAutoDreamEnabled(options.auto_dream)) return result;
37 if ((await $.session.turns()) > 0) return result;
38 const key = await projectKey($);
39 if ((await $.store.get(key)) === undefined) return result;
40 await $.store.delete(key); // consumed once; a failed delete throws before the nudge
41 await $.session.append({ message: { type: "user", content: [{ type: "text", text: NUDGE }] } });
42 } catch {
43 // fail open: never block session start
44 }
45 return result;
46 });
47};
48hooks/dream.mjs 40 lines1// Pure logic for the auto-dream mod (hooks/register.ts). Zero-dep, no `$`:
2// register.ts does every store, file system and session read.
3
4/**
5 * Fail-open: only `false` (the userConfig boolean) or the literal string
6 * "false" disables. The nudge creates no state, so an unset value is enabled.
7 * @param {unknown} value
8 * @returns {boolean}
9 */
10export function isAutoDreamEnabled(value) {
11 return value !== false && value !== "false";
12}
13
14export const NUDGE =
15 "A memory dream cycle is due for this project. Run one now: " +
16 "orient (locate the memory directory from this session's own auto-memory " +
17 "system-prompt block), gather signal (targeted grep over the most recent " +
18 "main-session transcripts for corrections/preferences/decisions), " +
19 "consolidate (merge duplicate memories, drop stale entries, resolve " +
20 "contradictions, author a new memory file for any signal with no " +
21 "existing memory home, back up each changed file alongside itself " +
22 "before writing, compress new/changed non-MEMORY.md detail files via " +
23 "cc-compress when available), then update the MEMORY.md index (keep it " +
24 "under 200 lines / 25KB). Only touch files that actually need a change.";
25
26/**
27 * The project's `$.store` key: `dream-due:` plus the first 8 hex chars of
28 * sha256 over the project dir's realpath (the 0.4.x flag file's hash).
29 * @param {string} realProjectDir
30 * @returns {Promise<string>}
31 */
32export async function dueKey(realProjectDir) {
33 // Web Crypto globals (node, bun and the mods worker all have them); the root tsconfig's lib is ES2022 only.
34 /** @type {{ crypto: { subtle: { digest(alg: string, data: Uint8Array): Promise<ArrayBuffer> } }, TextEncoder: new () => { encode(s: string): Uint8Array } }} */
35 const web = /** @type {any} */ (globalThis);
36 const digest = await web.crypto.subtle.digest("SHA-256", new web.TextEncoder().encode(realProjectDir));
37 const hex = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
38 return `dream-due:${hex.slice(0, 8)}`;
39}
40