SLOPSHOPPER

memory-enhancement

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

A shopper browsing a rack in a slop shop
README

memory-enhancement

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.

Install

/plugin install memory-enhancement@kwitsch-plugins

Skills

SkillWhat it does
dreamConsolidates 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-improvementReflects 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.

What it does

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.

Source 2 files
hooks/register.ts 48 lines
1import 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};
48
hooks/dream.mjs 40 lines
1// 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