See what a risky command would change before it runs.

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
| Package | Role |
|---|---|
medical-math-engine | 16 deterministic calculators, pre-LLM interception, reply verification |
medical-context-packer | identifier redaction and packing of long pasted records |
medical-quality-gates | /spec /plan /build /test /review /constraints, progressive-disclosure skills, pause-for-confirmation gate |
medical-diagrams | clinical_diagram: editorial HTML+SVG, 4px grid, WCAG AA, no Mermaid |
medical-agent-reach | read-only PubMed, WHO, CDC and transcripts over an HTTPS allowlist |
medical-cloud | Kaggle provisioning (/cloud), tunnel gateway, heartbeat, remediation, remote scripts |
bundle/medical | the profile layer that wires it all together |
Rendered diagram samples are in medicode/examples/.
/cloud setup <kaggle-username> <kaggle-api-token>. Get the token from kaggle.com → Settings → API. The Kaggle account must be phone-verified./cloud prepare once (about 2 h, runs on Kaggle, the laptop can sleep)./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.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.
| Path | Change |
|---|---|
packages/medical/* | 6 new plugin packages |
packages/bundle/medical | the dsh-medical profile bundle |
packages/boot/app-boot/src/profile.ts | adds the medical profile template |
apps/desktop/src/project-manager.ts | Desktop composes medical instead of web |
apps/cli/package.json | depends on @deepseek-ai/dsh-medical |
tsconfig.host.json, tsconfig.base.json, pnpm-lock.yaml | project 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.
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.
| Check | Result |
|---|---|
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 packages | clean |
dsh medical --dump-config | composes base → web-app → medical as intended |
Fresh clone of upstream d743267 → apply-to-fork.sh → tests + strict tsc | 29 files / 5755 tests pass, tsc clean |
| This repository's tree (medical + bundle suites) | 145 / 145 |
| Full build + live boot + real Kaggle GPU run | not done here: the build sandbox had 2 GB RAM, and the monorepo build OOMs. Run it on a dev machine. |
/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.See SPEC.md for the locked specification and the full requirement → implementation map.
hooks/blast-radius.mjs 128 lines1// Blast Radius, from "Getting started with Claude Code mods"
2// (https://claude.dev/blog/getting-started-with-claude-code-mods/, Anthropic, 2026-10-01).
3// The tool.call hook adds a waitArgv option for DSH's portable timer; the rest of the module
4// (classify, measure, the pane and band trees) is completed to the post's
5// description of the mod, which is marked where it starts.
6
7// Blast Radius: see what a risky command would change before it runs.
8
9// —— completed to the post's description (not in the published excerpt) ——
10
11const RISKS = [
12 { pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)\b/, kind: "delete", what: "delete files recursively" },
13 { pattern: /\bgit\s+reset\s+--hard\b/, kind: "reset", what: "discard uncommitted changes" },
14 { pattern: /\bgit\s+clean\b/, kind: "clean", what: "delete untracked files" },
15 { pattern: /\bgit\s+push\b[^\n]*\s(--force|-f)\b/, kind: "force-push", what: "rewrite a remote branch" },
16 { pattern: /\b(migrate|manage\.py\s+migrate|prisma\s+migrate|rails\s+db:migrate)\b/, kind: "migrate", what: "change the database schema" },
17];
18
19// What the hook is holding: one command at a time, until a button decides.
20let held = null;
21
22export function register(on, { waitArgv = ["sleep", "0.25"] } = {}) {
23 on("tool.call", { tool: "Bash" }, async ($, e, next) => {
24 const risk = classify(String(e.command ?? ""));
25 if (risk === null) return next(e); // everything else runs as normal
26
27 const report = await measure($, risk, await $.session.cwd()); // git status, git clean -n, du, ...
28 held = { command: e.command, risk, report, decision: null };
29 const opened = await $.ui.open({ id: "blast-radius", title: "Blast Radius", focus: true });
30 if (!opened.isPlaced) held.where = "band"; // too narrow for a pane: draw above the prompt
31
32 while (held.decision === null && !next.signal.aborted) {
33 await $.process.run(waitArgv); // time inside $ calls doesn't count against the hook's time limit
34 }
35 if (held.decision === "proceed") return next(e); // let it run
36 return { deny: `Blast Radius held this command: the user pressed Cancel. It would have: ${report.summary}.` };
37 });
38
39 // —— completed to the post's description (not in the published excerpt) ——
40
41 on("ui.render", { component: "Pane" }, ($, e, next) => {
42 if (e.requestId !== "blast-radius" || held === null || held.decision !== null) return next(e);
43 return report($, e);
44 });
45
46 on("ui.render", { component: "AbovePrompt" }, ($, e, next) => {
47 if (held === null || held.decision !== null || held.where !== "band") return next(e);
48 return report($, e);
49 });
50}
51
52function classify(command) {
53 const risk = RISKS.find((r) => r.pattern.test(command));
54 return risk ? { kind: risk.kind, what: risk.what, command } : null;
55}
56
57async function measure($, risk, cwd) {
58 const lines = [];
59 let summary = `${risk.what} in ${cwd}`;
60 if (risk.kind === "delete") {
61 const target = risk.command.match(/\brm\s+-\S+\s+(\S+)/)?.[1];
62 const du = await run($, ["du", "-sh", target ?? "."]);
63 const size = du.stdout.trim().split(/\s+/)[0] ?? "?";
64 const count = await run($, ["find", target ?? ".", "-type", "f"]);
65 const files = count.stdout.split("\n").filter(Boolean).length;
66 summary = `delete ${files} file(s) (${size}) under ${target ?? cwd}`;
67 lines.push(...count.stdout.split("\n").filter(Boolean).slice(0, 9));
68 } else if (risk.kind === "reset") {
69 const status = await run($, ["git", "status", "--porcelain"]);
70 const changed = status.stdout.split("\n").filter(Boolean);
71 summary = `discard uncommitted changes to ${changed.length} file(s)`;
72 lines.push(...changed.slice(0, 9));
73 } else if (risk.kind === "clean") {
74 const dry = await run($, ["git", "clean", "-n"]);
75 const removed = dry.stdout.split("\n").filter(Boolean);
76 summary = `delete ${removed.length} untracked path(s)`;
77 lines.push(...removed.slice(0, 9));
78 } else if (risk.kind === "force-push") {
79 const ahead = await run($, ["git", "log", "--oneline", "HEAD..origin/main"]);
80 const commits = ahead.stdout.split("\n").filter(Boolean);
81 summary = `rewrite the remote branch, dropping ${commits.length} commit(s) not on this branch`;
82 lines.push(...commits.slice(0, 9));
83 } else if (risk.kind === "migrate") {
84 const pending = await run($, ["sh", "-c", "python manage.py showmigrations --plan 2>/dev/null | grep '\\[ \\]' || true"]);
85 const migrations = pending.stdout.split("\n").filter(Boolean);
86 summary = `apply ${migrations.length} pending migration(s)`;
87 lines.push(...migrations.slice(0, 9));
88 }
89 return { summary, lines };
90}
91
92// A dry run that fails (no git, no python, no such path) still lets the user decide.
93async function run($, argv) {
94 try {
95 return await $.process.run(argv);
96 } catch {
97 return { exitCode: 1, stdout: "", stderr: "" };
98 }
99}
100
101function decide(decision) {
102 if (held !== null) held.decision = decision;
103}
104
105function report($, e) {
106 const { Box, Text, Button } = $.ui.resolve(e);
107 const detail = held.report.lines.map((line) => Text({ dimColor: true, children: ` ${line}` }));
108 return Box({
109 flexDirection: "column",
110 border: true,
111 borderColor: "yellow",
112 paddingX: 1,
113 children: [
114 Text({ color: "yellow", bold: true, children: `Blast Radius: ${held.command}` }),
115 Text({ children: `Would ${held.report.summary}.` }),
116 ...detail,
117 Box({
118 flexDirection: "row",
119 gap: 2,
120 children: [
121 Button({ label: "Proceed", hotkey: "1", onPress: () => decide("proceed") }),
122 Button({ label: "Cancel", hotkey: "2", onPress: () => decide("cancel") }),
123 ],
124 }),
125 ],
126 });
127}
128