SLOPSHOPPER

replay-theater

Step through the last turn's edits, one diff at a time.

newpanebandguardcommand
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · replay-theater
│ ┃ Replay Theater ✕ › fix the failing auth test and add an audit log call │ ┃ Replay Theater 1 2 3 │ ┃ /work/app/src/auth.ts (step 1 of 3) ⏺ Read(src/auth.ts) │ ┃ - if (!claims) throw new Error('invalid ⎿ Read 6 lines │ ┃ token') ⏺ Update(src/auth.ts) │ ┃ + if (!claims || claims.exp < Date.now() / ⎿ Added 2 lines, removed 1 line │ ┃ 1000) throw new Error('invalid token') ⏺ Bash(bun test) │ ┃ + await audit('refresh', claims.sub) ⎿ 3 pass, 1 fail │ ┃ [ Prev ] [ Next ] [ Close ] │ ● Done. refresh now rejects expired claims and logs an audit event. │ │ ✻ Worked for 42s · done 4:20 PM │ │ › /replay │ ⎿ replay-theater: Replaying │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Pane · Replay Theater
Replay Theater 1 2 3 /work/app/src/auth.ts (step 1 of 3) - if (!claims) throw new Error('invalid token') + if (!claims || claims.exp < Date.now() / 1000) throw new Error('invalid token') + await audit('refresh', claims.sub) [ Prev ] [ Next ] [ Close ]
README

MediCode

A zero-cost, split-brain clinical decision-support assistant. MediCode is a fork of DeepSeek Harness (DSH) at upstream commit d743267 (v0.2.1-alpha.2). It uses DSH's own architecture (Cordis plugins, profile bundles, llm-pi-ai providers, Electron Desktop) rather than a separate app.

MediCode is an independent community project, not affiliated with or endorsed by DeepSeek. The upstream README is kept as README.dsh.md. The licence is MIT, inherited from upstream (see LICENSE).

Not a medical device. It supports a licensed clinician's judgement and does not replace it. Read Limitations before using it with real patient data.

┌──────────── clinician's laptop (Desktop app, no terminal) ───────────┐
│ dsh profile "medical" = dsh-base → dsh-web-app → dsh-medical         │
│                                                                      │
│  user msg ─► context-packer (redact + pack) ─► math-engine intercept │
│            (deterministic GFR/CrCl/BSA/mg-kg/drip, before the LLM)   │
│  model  ◄─► tools: clinical_calc · pubmed_* · medical_feed ·         │
│             transcript_extract · clinical_diagram · skills           │
│  reply  ─► math verify + quality gates (steer / pause & ask)         │
│                                                                      │
│  llm-pi-ai "medcloud" ─► 127.0.0.1:47800 gateway (local key)         │
└───────────────────────────────┬──────────────────────────────────────┘
                                │ HTTPS · SESSION_BEARER_TOKEN · curl/8.0.0 UA
                                ▼  heartbeat every 3 min
   Cloudflare quick tunnel ─► Kaggle private kernel, 2× T4
                               GPU0 vLLM: II-Medical-8B GPTQ-W4A16 (Marlin), <think>
                               GPU1 llama.cpp: MedGemma 1.5 4B (vision)
   Groq (optional): only plans boot retries from scrubbed logs, never clinical text

Packages

PackageRole
medical-math-engine16 deterministic calculators, pre-LLM interception, reply verification
medical-context-packeridentifier redaction and packing of long pasted records
medical-quality-gates/spec /plan /build /test /review /constraints, progressive-disclosure skills, pause-for-confirmation gate
medical-diagramsclinical_diagram: editorial HTML+SVG, 4px grid, WCAG AA, no Mermaid
medical-agent-reachread-only PubMed, WHO, CDC and transcripts over an HTTPS allowlist
medical-cloudKaggle provisioning (/cloud), tunnel gateway, heartbeat, remediation, remote scripts
bundle/medicalthe profile layer that wires it all together

Rendered diagram samples are in medicode/examples/.

For clinicians

  1. Build and run MediCode (see below; there is no prebuilt installer yet). It opens in the clinical preset.
  2. In the chat, run /cloud setup <kaggle-username> <kaggle-api-token>. Get the token from kaggle.com → Settings → API. The Kaggle account must be phone-verified.
  3. Run /cloud prepare once (about 2 h, runs on Kaggle, the laptop can sleep).
  4. Run /cloud start at the start of a session (about 10 min) and /cloud stop at the end. The node also shuts itself down after 20 min idle.

For developers

Requirements: Node ^22.19 or >=24, pnpm 11, Python 3 (only for the remote-script tests), and about 8 GB of RAM for the full build.

git clone https://github.com/KeshavCracks/MediCode.git
cd MediCode
pnpm install
pnpm vitest run packages/medical packages/bundle/medical   # 145 tests
pnpm run build                                             # full monorepo build
node apps/cli/lib/bin.js medical                           # web UI on http://localhost:3080

You can also run it from source without the full library build: node --import tsx/esm apps/cli/src/bin.ts medical. The web UI still needs its client bundles, which pnpm run build produces.

What the fork changes

PathChange
packages/medical/*6 new plugin packages
packages/bundle/medicalthe dsh-medical profile bundle
packages/boot/app-boot/src/profile.tsadds the medical profile template
apps/desktop/src/project-manager.tsDesktop composes medical instead of web
apps/cli/package.jsondepends on @deepseek-ai/dsh-medical
tsconfig.host.json, tsconfig.base.json, pnpm-lock.yamlproject references, path aliases, lockfile
.github/workflows/removed; upstream CI targets DeepSeek's own runners and secrets

To move to a newer upstream, download medicode-overlay-*.zip from the Releases page, then run ./scripts/apply-to-fork.sh <fresh-upstream-clone>. The script is idempotent and only touches the files listed above.

Desktop installers

None are published yet. The Desktop app installs its runtime from npm at the release version. A MediCode installer therefore needs the medical packages published under a scope you own (rename them out of @deepseek-ai first) before it can be packaged. Until then, use the CLI and web UI above.

Verification status

CheckResult
Medical plugin suites (math, packing, gates, diagrams, reach, cloud incl. tunnel auth and real router.py e2e)138 / 138
Bundle suite (patch parse, deps, every row's config against the plugin's real schema, skills path, provider ↔ cloud names)7 / 7
Upstream checks touched by the fork (app-boot, desktop project-manager, reference/catalog/alias scripts)5718 passed, 1 skipped (upstream)
tsc strict (noUncheckedIndexedAccess, exactOptionalPropertyTypes) on all medical packagesclean
dsh medical --dump-configcomposes base → web-app → medical as intended
Fresh clone of upstream d743267 → apply-to-fork.sh → tests + strict tsc29 files / 5755 tests pass, tsc clean
This repository's tree (medical + bundle suites)145 / 145
Full build + live boot + real Kaggle GPU runnot done here: the build sandbox had 2 GB RAM, and the monorepo build OOMs. Run it on a dev machine.

Limitations

  • Models. II-Medical-8B replaces the brief's Med-R1-8B. It ships BF16, so /cloud prepare quantizes it once. MedGemma runs through llama.cpp because vLLM can't serve it on T4, and it is under Google's Health AI Developer Foundations terms, which you must accept.
  • Kaggle. About 30 GPU-hours a week, 12 h sessions, possible queues. Prompts and images are processed on Kaggle (Google) infrastructure, so check that your governance allows it. The session token sits in the private kernel source and rotates on every start.
  • Heuristic components. Identifier redaction, the outbound query scrubber, calculation-request extraction, gate checks and the remediation heuristics are pattern-based. They reduce risk but are not certified. The deterministic calculators are the only "exact" part.
  • Packing replaces the pasted text the model sees with the packed version. The original stays in the local session log.
  • External sources. The WHO JSON endpoints are undocumented, and YouTube may answer with a consent redirect in some regions. All fetched content is fenced as untrusted.
  • Groq is optional and only ever sees scrubbed boot logs and knob values, never clinical content.

See SPEC.md for the locked specification and the full requirement → implementation map.

Source 1 files
hooks/replay-theater.mjs 134 lines
1// Replay Theater, from "Getting started with Claude Code mods"
2// (https://claude.dev/blog/getting-started-with-claude-code-mods/, Anthropic, 2026-10-01).
3// The post publishes the five hooks that record edits and register /replay
4// unchanged; the rest of the module (stepsFor, the diff, openReplay, the pane
5// and the hint) is completed to the post's description, marked where it starts.
6
7// Replay Theater: step through the last turn's edits, one diff at a time.
8
9// —— completed to the post's description (not in the published excerpt) ——
10
11const EDIT_TOOLS = new Set(["Edit", "Write"]);
12
13// Edits of the running turn, the sealed replay of the last one, and where the pane is.
14const state = { pending: [], replay: [], index: 0, open: false, placed: false };
15
16export function register(on) {
17  on("tool.call", async ($, e, next) => {
18    if (EDIT_TOOLS.has(e.tool)) state.pending.push(...(await stepsFor($, e)));  // old/new text → diff
19    return next(e);                                                              // the edit runs untouched
20  });
21
22  on("turn.start", ($, e, next) => { if (!e.agentId) state.pending = []; return next(e); });
23
24  on("turn.complete", async ($, e, next) => {
25    const r = await next(e);
26    if (!e.agentId && state.pending.length) state.replay = state.pending;       // one replay per turn
27    return r;
28  });
29
30  on("session.start", async ($, e, next) => {
31    const r = await next(e);
32    await $.command.register({ name: "replay", description: "Step through the last turn's file edits" });
33    return r;
34  });
35  on("command.run", { command: "replay" }, async ($, e) => ({ text: (await openReplay($)) ? "Replaying" : "No edits" }));
36
37  // —— completed to the post's description (not in the published excerpt) ——
38
39  on("ui.render", { component: "Pane" }, ($, e, next) => {
40    if (e.requestId !== "replay" || !state.open) return next(e);
41    return theater($, e);
42  });
43
44  on("ui.render", { component: "AbovePrompt" }, ($, e, next) => {
45    if (state.open && !state.placed) return theater($, e);          // no pane could be placed: the same tree, inline
46    if (state.open || state.replay.length === 0) return next(e);
47    const { Box, Text, Button } = $.ui.resolve(e);
48    const n = state.replay.length;
49    return Box({
50      flexDirection: "row",
51      paddingX: 1,
52      gap: 1,
53      children: [
54        Text({ color: "magenta", bold: true, children: "Replay Theater" }),
55        Text({ dimColor: true, children: `${n} edit${n === 1 ? "" : "s"} this turn — press r or type /replay` }),
56        Button({ label: "Replay", hotkey: "r", onPress: () => openReplay($) }),
57      ],
58    });
59  });
60}
61
62// One replay step per edit: the file, and the text before and after.
63async function stepsFor($, e) {
64  const file = String(e.file_path ?? "");
65  if (e.tool === "Edit") {
66    return [{ file, before: String(e.old_string ?? ""), after: String(e.new_string ?? "") }];
67  }
68  // For a Write, the old contents are read just before the write lands, so the diff is real.
69  const before = await $.fs.read(file).catch(() => "");
70  return [{ file, before, after: String(e.content ?? "") }];
71}
72
73async function openReplay($) {
74  if (state.replay.length === 0) return false;
75  state.index = 0;
76  state.open = true;
77  const opened = await $.ui.open({ id: "replay", title: "Replay Theater", focus: true });
78  state.placed = opened.isPlaced;
79  $.ui.invalidate("ui.render");
80  return true;
81}
82
83function step(delta, $) {
84  state.index = Math.min(Math.max(state.index + delta, 0), state.replay.length - 1);
85  $.ui.invalidate("ui.render");
86}
87
88async function close($) {
89  state.open = false;
90  await $.ui.close({ id: "replay" });
91  $.ui.invalidate("ui.render");
92}
93
94// Lines the edit removed and added, as a unified diff shows them.
95function diffLines(before, after) {
96  const old = before.split("\n");
97  const neu = after.split("\n");
98  const common = new Set(neu);
99  const commonOld = new Set(old);
100  return [
101    ...old.filter((line) => !common.has(line)).map((line) => ({ sign: "-", line })),
102    ...neu.filter((line) => !commonOld.has(line)).map((line) => ({ sign: "+", line })),
103  ];
104}
105
106function theater($, e) {
107  const { Box, Text, Button } = $.ui.resolve(e);
108  const current = state.replay[state.index];
109  const strip = state.replay.map((_, i) => Text({ color: i === state.index ? "magenta" : undefined, bold: i === state.index, children: ` ${i + 1} ` }));
110  const diff = diffLines(current.before, current.after).map(({ sign, line }) =>
111    Text({ color: sign === "+" ? "green" : "red", children: `${sign} ${line}` }),
112  );
113  return Box({
114    flexDirection: "column",
115    border: true,
116    borderColor: "magenta",
117    paddingX: 1,
118    children: [
119      Box({ flexDirection: "row", children: [Text({ bold: true, children: "Replay Theater " }), ...strip] }),
120      Text({ dimColor: true, children: `${current.file}  (step ${state.index + 1} of ${state.replay.length})` }),
121      ...diff,
122      Box({
123        flexDirection: "row",
124        gap: 2,
125        children: [
126          Button({ label: "Prev", hotkey: "p", disabled: state.index === 0, onPress: () => step(-1, $) }),
127          Button({ label: "Next", hotkey: "n", disabled: state.index >= state.replay.length - 1, onPress: () => step(1, $) }),
128          Button({ label: "Close", hotkey: "q", onPress: () => close($) }),
129        ],
130      }),
131    ],
132  });
133}
134