@ericrisco/rsc 3.0.6 → 3.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,6 +31,27 @@ npx @ericrisco/rsc@latest onboard
31
31
 
32
32
  </div>
33
33
 
34
+ ## 🐋 New: DeepSeek Harness
35
+
36
+ [DeepSeek Harness](https://www.npmjs.com/package/@deepseek-ai/dsh) (`dsh`) is now a supported
37
+ assistant, the 18th. Pick it in the wizard or pass `--target deepseek`:
38
+
39
+ ```bash
40
+ npx @ericrisco/rsc@latest onboard --target deepseek
41
+ ```
42
+
43
+ - **Skills** go to `.dsh/skills/`, where dsh finds them on its own.
44
+ - **Instructions** go in the rsc block of `AGENTS.md`, which dsh already reads.
45
+ - **Session memory, knowledge sync and the update notice** run from dsh's own hooks. dsh has no
46
+ hooks per project, so rsc adds one marked block to `~/.dsh/cordis.patch.yml` once per machine. That
47
+ block runs the hooks of whichever project the session is in. It works in `dsh web` with several
48
+ projects open, and in a project without rsc it does nothing. Restart dsh once after the first
49
+ install. To turn it off, delete the block.
50
+
51
+ Details: [targets table](#multi-target).
52
+
53
+ ---
54
+
34
55
  ## Your agent needs a harness. It should not improvise one.
35
56
 
36
57
  A model is only the brain. Real work also needs project memory, tools, domain knowledge, rules and
@@ -89,6 +110,7 @@ someone runs `rsc git-permissions on`.
89
110
  | Gemini | `.gemini/settings.json` → `tools.allowed` |
90
111
  | OpenCode | `opencode.json` → `permission.bash` (force-push → `ask`) |
91
112
  | Cursor | Not covered: its CLI matches only the first word, so allowing a push would allow every git command. |
113
+ | DeepSeek Harness | Not covered: dsh has no per-command allow list. Its own Permissions selector (`/permission`) sets approval for every command at once. |
92
114
 
93
115
  ```bash
94
116
  rsc git-permissions status # decided or not, and wired per assistant
@@ -241,8 +263,8 @@ no method deciding whether each piece belongs. rsc-harness keeps that constructi
241
263
  - **Specialists follow the stack.** The four base agents stay small; installing a
242
264
  supported stack adds only its reviewer and build resolver. `rsc add go`, for
243
265
  example, adds the Go pair without pulling reviewers for every other language.
244
- - **A new local session continues the old one.** Claude Code, Codex, Gemini CLI
245
- and OpenCode load a bounded checkpoint for the current branch and worktree at
266
+ - **A new local session continues the old one.** Claude Code, Codex, Gemini CLI,
267
+ OpenCode and DeepSeek Harness load a bounded checkpoint for the current branch and worktree at
246
268
  session start. Cursor desktop uses an assisted read-before-action fallback.
247
269
  - **Honestly good.** Every skill was built by a research → spec → implement →
248
270
  *adversarial review* pipeline and had to clear an objective rubric
@@ -262,7 +284,8 @@ When a new session opens in the same checkout, that state is injected before the
262
284
  first agent action. A completed edit is preserved even if the previous client
263
285
  closed before its normal session-end event.
264
286
 
265
- - **Full:** Claude Code, Codex, Gemini CLI and OpenCode. Codex asks you to inspect
287
+ - **Full:** Claude Code, Codex, Gemini CLI, OpenCode and DeepSeek Harness (through rsc's machine
288
+ bridge, see [targets](#multi-target)). Codex asks you to inspect
266
289
  and trust the project hook once with `/hooks`; until then `doctor` reports that
267
290
  trust is still required.
268
291
  - **Assisted:** Cursor desktop. Its start hook is fire-and-forget, so rsc also
@@ -489,7 +512,7 @@ What it will not do, by design:
489
512
  | Run where it has no business | No `origin`, no knowledge folders, a rebase in progress, a cloud agent: it stays quiet. |
490
513
 
491
514
  It is wired into the assistant's own turn hooks (end of turn, new message) in Claude Code, Codex,
492
- Gemini, Cursor and OpenCode (tested end to end in Claude Code, Codex and OpenCode). The network part runs in the background, so a turn never waits for
515
+ Gemini, Cursor, OpenCode and DeepSeek Harness (tested end to end in Claude Code, Codex and OpenCode). The network part runs in the background, so a turn never waits for
493
516
  it. It is kept apart from the [local session memory](#new-sessions-pick-up-the-latest-local-work),
494
517
  which promises never to touch the network. The first turn says it is on. To turn it off for the
495
518
  project:
@@ -550,7 +573,7 @@ A new install asks whether you want this (yes by default). To turn it off later,
550
573
  `.rsc.json` like the other switches. The update can leave harness files changed in git; rsc never
551
574
  commits them for you.
552
575
 
553
- Where it runs on its own: **Claude Code, Codex, Gemini CLI, Cursor and OpenCode**, from a
576
+ Where it runs on its own: **Claude Code, Codex, Gemini CLI, Cursor, OpenCode and DeepSeek Harness**, from a
554
577
  session-start hook. Every other assistant has no such hook, so the always-on instructions ask the
555
578
  agent to run `node .rsc/auto-update.mjs` on its first turn — same rules, but it depends on the agent
556
579
  doing it (and some assistants ask your permission before running a command). Codex asks you to
@@ -772,14 +795,23 @@ duplicated. The wizard asks which ones; `--target a,b` does it non-interactively
772
795
  | `roo` | `.roo/rsc/<id>/` → symlink | rule in `.roo/rules/rsc-suggest.md` |
773
796
  | `amp` | `.amp/rsc/<id>/` → symlink | block in `AGENTS.md` |
774
797
  | `opencode` | `.opencode/rsc/<id>/` → symlink | block in `AGENTS.md` |
798
+ | `deepseek` | `.dsh/skills/<id>/` → symlink (DeepSeek Harness discovers it natively) | block in `AGENTS.md` |
775
799
  | `jules` | `.jules/rsc/<id>/` → symlink | block in `AGENTS.md` |
776
800
  | `junie` | `.junie/rsc/<id>/` → symlink | block in `.junie/guidelines.md` |
777
801
  | `kiro` | `.kiro/rsc/<id>/` → symlink | doc in `.kiro/steering/rsc-suggest.md` |
778
802
  | `aider` | `.aider/rsc/<id>/` → symlink | block in `CONVENTIONS.md` |
779
803
 
780
- > `codex`, `zed`, `amp`, `opencode` and `jules` all share the one root
804
+ > `codex`, `zed`, `amp`, `opencode`, `deepseek` and `jules` all share the one root
781
805
  > `AGENTS.md`; the block is idempotent, so picking several writes it once.
782
806
 
807
+ > **DeepSeek Harness** (`dsh`) has no project-level hook config: its plugins are per machine. So
808
+ > the `deepseek` target writes the project hooks to `.dsh/hooks.json` (Codex format, untracked) and,
809
+ > once per machine, adds one marked block to `~/.dsh/cordis.patch.yml` (or `$DSH_HOME`) that mounts
810
+ > dsh's Codex-hooks bridge on rsc's dispatcher in `~/.dsh/rsc/`. The dispatcher runs the hooks of
811
+ > whichever project the session is in, so `dsh web` serving several workspaces works too, and a
812
+ > project without rsc runs nothing. Restart dsh once after the first install. Delete the block to
813
+ > turn it off.
814
+
783
815
  The richer surfaces are intentionally narrower than skill support:
784
816
 
785
817
  | Targets | Stack agents | Native commands | Local session continuation |
@@ -788,6 +820,7 @@ The richer surfaces are intentionally narrower than skill support:
788
820
  | Codex | yes | no separate project-command surface | full after `/hooks` trust |
789
821
  | Cursor desktop | yes | yes | assisted |
790
822
  | Gemini CLI, OpenCode | yes | yes | full |
823
+ | DeepSeek Harness | unsupported | unsupported | full (through the machine bridge) |
791
824
  | GitHub Copilot | yes | yes | unsupported |
792
825
  | Junie, Kiro | yes | unsupported | unsupported |
793
826
  | Windsurf, Cline, Roo | unsupported | yes | unsupported |
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.0.6",
2
+ "version": "3.0.7",
3
3
  "counts": {
4
4
  "skills": 270,
5
5
  "agents": 33,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "3.0.6",
3
+ "version": "3.0.7",
4
4
  "description": "A guided harness builder that turns your goal into a minimal, verified AI-agent setup using strict best practices.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,6 +46,8 @@
46
46
  "claude-code",
47
47
  "cursor",
48
48
  "codex",
49
+ "deepseek",
50
+ "deepseek-harness",
49
51
  "gemini",
50
52
  "antigravity",
51
53
  "cli",
@@ -25,6 +25,7 @@ import {
25
25
  import { wireUpdate, unwireUpdate, updateManagedPaths, updateArtifactsPresent } from '../targets/update-wiring.js';
26
26
  import { wireKnowledge, unwireKnowledge, knowledgeManagedPaths, knowledgeArtifactsPresent } from '../targets/knowledge-wiring.js';
27
27
  import { wireGitPermissions, unwireGitPermissions, gitPermissionsPath } from '../targets/git-permissions.js';
28
+ import { wireDeepseekBridge } from '../targets/deepseek-bridge.js';
28
29
 
29
30
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
30
31
  const CLI_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version;
@@ -320,6 +321,8 @@ export async function applyInstall({ skillIds = [], agentIds = [], target, home,
320
321
  // the memory, so the memory's policy does not decide it: wired wherever the assistant can run it.
321
322
  const knowledgeResult = wireKnowledge(target, cwd);
322
323
  state.knowledge = { mode: knowledgeResult.mode, reason: knowledgeResult.reason || null, paths: knowledgeResult.paths };
324
+ // DeepSeek Harness only reads hooks through a machine-level bridge (targets/deepseek-bridge.js).
325
+ if (target === 'deepseek') state.deepseekBridge = wireDeepseekBridge();
323
326
  if (gitPermissions === true) state.gitPermissions = wireGitPermissions(target, cwd);
324
327
  else if (gitPermissions === false) { unwireGitPermissions(target, cwd); state.gitPermissions = { mode: 'off' }; }
325
328
  const context7OptOut = join(cwd, '.rsc', '.no-context7');
package/scripts/rsc.js CHANGED
@@ -450,6 +450,7 @@ function printNextSteps(targets, ids) {
450
450
  say(' Browse the catalog / get picks: npx @ericrisco/rsc consult "whatever you need"');
451
451
  say('────────────────────────────────────────────────────────');
452
452
  if (targets.includes('codex')) say(' Codex: review and trust the project lifecycle hook once with `/hooks`; until then memory is reported as requiring trust.');
453
+ if (targets.includes('deepseek')) say(' DeepSeek Harness: hooks run through rsc\'s bridge in ~/.dsh/cordis.patch.yml (one block, every profile); restart dsh once to load it.');
453
454
  if (targets.includes('cursor')) say(' Cursor: startup memory is assisted because its sessionStart hook is fire-and-forget; the installed always-on rule performs the read-before-action fallback.');
454
455
  printAgentHandoff();
455
456
  }
@@ -0,0 +1,86 @@
1
+ // DeepSeek Harness (dsh) has no project-level hook or plugin config: plugins are per machine, and
2
+ // its Codex-hooks bridge reads one file named at boot. So the deepseek target needs one machine-level
3
+ // step besides the project files: put rsc's bridge in $DSH_HOME/rsc/ and add one insert to the
4
+ // home patch layer ($DSH_HOME/cordis.patch.yml, applied over every profile). Idempotent, and it only
5
+ // ever touches the block between its own markers. It is never removed on uninstall: other projects on
6
+ // the machine may use it, and with no `.dsh/hooks.json` in a project it does nothing at all.
7
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
8
+ import { dirname, join } from 'node:path';
9
+ import { homedir } from 'node:os';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const HERE = dirname(fileURLToPath(import.meta.url));
13
+ const BEGIN = '# rsc-deepseek-bridge:begin';
14
+ const END = '# rsc-deepseek-bridge:end';
15
+ // The points rsc uses. PreToolUse is left out: nothing of rsc's runs there yet, and every tool call
16
+ // would pay a process spawn for nothing.
17
+ const EVENTS = ['SessionStart', 'UserPromptSubmit', 'PostToolUse', 'Stop'];
18
+
19
+ export function dshHome(env = process.env) {
20
+ const value = (env.DSH_HOME || '').trim();
21
+ return value || join(homedir(), '.dsh');
22
+ }
23
+
24
+ export function deepseekBridgePaths(env = process.env) {
25
+ const home = dshHome(env);
26
+ return {
27
+ home,
28
+ bridge: join(home, 'rsc', 'bridge.mjs'),
29
+ hooks: join(home, 'rsc', 'hooks.json'),
30
+ patch: join(home, 'cordis.patch.yml'),
31
+ };
32
+ }
33
+
34
+ const quote = (value) => `'${String(value).replaceAll("'", "''")}'`;
35
+
36
+ function block(hooksPath) {
37
+ return [
38
+ `${BEGIN} — rsc hooks for DeepSeek Harness (delete this block to turn them off)`,
39
+ '- insert:',
40
+ ' - id: rsc-hooks',
41
+ " name: '@deepseek-ai/dsh-hooks-codex'",
42
+ ' config:',
43
+ ` configPath: ${quote(hooksPath)}`,
44
+ END,
45
+ ].join('\n');
46
+ }
47
+
48
+ /** Where the block goes, or null when the file is in a shape we must not guess at. */
49
+ export function patchWithBlock(body, hooksPath) {
50
+ const ours = block(hooksPath);
51
+ const start = body.indexOf(BEGIN);
52
+ const stop = body.indexOf(END);
53
+ if (start !== -1 && stop > start) return body.slice(0, start) + ours + body.slice(stop + END.length);
54
+ const lines = body.split('\n');
55
+ const content = lines.filter((line) => line.trim() && !line.trim().startsWith('#'));
56
+ if (content.length === 1 && content[0].trim() === '[]') {
57
+ const kept = lines.filter((line) => line.trim() !== '[]').join('\n').replace(/\n*$/, '');
58
+ return `${kept ? `${kept}\n` : ''}${ours}\n`;
59
+ }
60
+ if (!content.length) return `${body.replace(/\n*$/, '')}${body.trim() ? '\n' : ''}${ours}\n`;
61
+ if (content[0].startsWith('- ')) return `${body.replace(/\n*$/, '')}\n${ours}\n`;
62
+ return null;
63
+ }
64
+
65
+ export function wireDeepseekBridge(env = process.env) {
66
+ const paths = deepseekBridgePaths(env);
67
+ mkdirSync(dirname(paths.bridge), { recursive: true });
68
+ copyFileSync(join(HERE, 'deepseek-bridge.mjs'), paths.bridge);
69
+ const hooks = {};
70
+ for (const event of EVENTS) {
71
+ hooks[event] = [{ hooks: [{ type: 'command', command: `node "${paths.bridge}" ${event}`, timeout: 30 }] }];
72
+ }
73
+ writeFileSync(paths.hooks, `${JSON.stringify({ hooks }, null, 2)}\n`);
74
+ const body = existsSync(paths.patch) ? readFileSync(paths.patch, 'utf8') : '';
75
+ const next = patchWithBlock(body, paths.hooks);
76
+ if (next === null) return { mode: 'manual', reason: 'patch-shape-unknown', paths: [paths.bridge, paths.hooks] };
77
+ if (next !== body) writeFileSync(paths.patch, next);
78
+ return { mode: 'wired', paths: [paths.bridge, paths.hooks, paths.patch] };
79
+ }
80
+
81
+ export function inspectDeepseekBridge(env = process.env) {
82
+ const paths = deepseekBridgePaths(env);
83
+ const patched = existsSync(paths.patch) && readFileSync(paths.patch, 'utf8').includes(BEGIN);
84
+ const ready = patched && existsSync(paths.bridge) && existsSync(paths.hooks);
85
+ return { status: ready ? 'ready' : 'missing', ...paths };
86
+ }
@@ -0,0 +1,90 @@
1
+ // rsc-deepseek-bridge:managed
2
+ // DeepSeek Harness runs Codex-format hooks only from ONE file named at boot, and a boot is per
3
+ // machine, not per project (`dsh web` serves every workspace from one process). So the machine gets
4
+ // one hooks file whose every entry calls this script, and this script runs the hooks of whichever
5
+ // project the session is in: the nearest ancestor of the session's cwd holding `.dsh/hooks.json`.
6
+ // No project there → nothing runs, nothing is printed.
7
+ //
8
+ // Output is folded back into the Codex shape the bridge reads. The bridge drops `systemMessage`
9
+ // (not surfaced yet), so a message for the person travels as model context with an instruction to
10
+ // relay it. Usage: node bridge.mjs <SessionStart|UserPromptSubmit|PostToolUse|Stop> (payload on stdin)
11
+ import { existsSync, readFileSync } from 'node:fs';
12
+ import { dirname, join, resolve } from 'node:path';
13
+ import { spawnSync } from 'node:child_process';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const CONTEXT_EVENTS = new Set(['SessionStart', 'UserPromptSubmit', 'PostToolUse']);
17
+
18
+ export function projectOf(dir) {
19
+ for (let current = resolve(dir); ; current = dirname(current)) {
20
+ if (existsSync(join(current, '.dsh', 'hooks.json'))) return current;
21
+ if (dirname(current) === current) return null;
22
+ }
23
+ }
24
+
25
+ // The hooks rsc writes are plain argv (`node ".rsc/x.mjs" args`), so no shell is needed — and none is
26
+ // used, which also keeps Windows from flashing a console (#293). Quotes group, nothing else is special.
27
+ export function argv(command) {
28
+ const out = [];
29
+ for (const m of command.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g)) out.push(m[1] ?? m[2] ?? m[3]);
30
+ if (out[0] === 'node') out[0] = process.execPath;
31
+ return out;
32
+ }
33
+
34
+ function matches(matcher, subject) {
35
+ if (!matcher) return true;
36
+ try { return new RegExp(`^(?:${matcher})$`).test(subject); } catch { return false; }
37
+ }
38
+
39
+ export function runBridge(event, raw = '', env = process.env) {
40
+ let payload = {};
41
+ try { payload = raw.trim() ? JSON.parse(raw) : {}; } catch { payload = {}; }
42
+ const root = projectOf(payload.cwd || process.cwd());
43
+ if (!root) return {};
44
+ let config;
45
+ try { config = JSON.parse(readFileSync(join(root, '.dsh', 'hooks.json'), 'utf8')); } catch { return {}; }
46
+ const groups = Array.isArray(config?.hooks?.[event]) ? config.hooks[event] : [];
47
+ const context = [];
48
+ let deny = null;
49
+ for (const group of groups) {
50
+ if (!matches(group?.matcher, String(payload.tool_name || ''))) continue;
51
+ for (const hook of Array.isArray(group?.hooks) ? group.hooks : []) {
52
+ if (typeof hook?.command !== 'string') continue;
53
+ const seconds = typeof hook.timeout === 'number' ? hook.timeout : 10;
54
+ const [file, ...args] = argv(hook.command);
55
+ if (!file) continue;
56
+ const result = spawnSync(file, args, {
57
+ cwd: root, input: raw, encoding: 'utf8', timeout: seconds * 1000, windowsHide: true, env,
58
+ });
59
+ if (result.status === 2) { deny = (result.stderr || '').trim() || 'blocked by an rsc hook'; continue; }
60
+ if (result.status !== 0) continue;
61
+ const out = (result.stdout || '').trim();
62
+ if (!out) continue;
63
+ let parsed = null;
64
+ try { parsed = JSON.parse(out); } catch { parsed = null; }
65
+ if (!parsed || typeof parsed !== 'object') { context.push(out); continue; }
66
+ const specific = parsed.hookSpecificOutput || {};
67
+ if (parsed.decision === 'block' || parsed.decision === 'deny' || specific.permissionDecision === 'deny') {
68
+ deny = parsed.reason || specific.permissionDecisionReason || 'blocked by an rsc hook';
69
+ }
70
+ if (typeof specific.additionalContext === 'string' && specific.additionalContext) context.push(specific.additionalContext);
71
+ if (typeof parsed.systemMessage === 'string' && parsed.systemMessage) {
72
+ context.push(`Tell the person this in one line, in their language:\n${parsed.systemMessage}`);
73
+ }
74
+ }
75
+ }
76
+ const out = {};
77
+ if (deny) { out.decision = 'block'; out.reason = deny; }
78
+ if (context.length && CONTEXT_EVENTS.has(event)) {
79
+ out.hookSpecificOutput = { hookEventName: event, additionalContext: context.join('\n\n') };
80
+ }
81
+ return out;
82
+ }
83
+
84
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
85
+ let raw = '';
86
+ try { raw = readFileSync(0, 'utf8'); } catch { raw = ''; }
87
+ let out = {};
88
+ try { out = runBridge(String(process.argv[2] || ''), raw); } catch { out = {}; }
89
+ if (Object.keys(out).length) process.stdout.write(`${JSON.stringify(out)}\n`);
90
+ }
package/targets/index.js CHANGED
@@ -44,6 +44,8 @@ const SPEC = {
44
44
  // AGENTS.md family — all read the same root AGENTS.md
45
45
  codex: { root: '.codex/rsc', hook: 'AGENTS.md', adapter: 'md' },
46
46
  opencode: { root: '.opencode/rsc', hook: 'AGENTS.md', adapter: 'md' },
47
+ // DeepSeek Harness discovers <root>/.dsh/skills/<name>/SKILL.md natively.
48
+ deepseek: { root: '.dsh/skills', hook: 'AGENTS.md', adapter: 'md' },
47
49
  amp: { root: '.amp/rsc', hook: 'AGENTS.md', adapter: 'md' },
48
50
  jules: { root: '.jules/rsc', hook: 'AGENTS.md', adapter: 'md' },
49
51
  zed: { root: '.zed/rsc', hook: 'AGENTS.md', adapter: 'md' },
@@ -78,6 +80,7 @@ export const TARGETS = [
78
80
  { id: 'roo', label: 'Roo Code', hint: '.roo/rules/' },
79
81
  { id: 'amp', label: 'Amp', hint: 'AGENTS.md' },
80
82
  { id: 'opencode', label: 'opencode', hint: 'AGENTS.md' },
83
+ { id: 'deepseek', label: 'DeepSeek Harness', hint: '.dsh/skills/' },
81
84
  { id: 'jules', label: 'Jules', hint: 'AGENTS.md' },
82
85
  { id: 'junie', label: 'JetBrains Junie', hint: '.junie/guidelines.md' },
83
86
  { id: 'kiro', label: 'Kiro', hint: '.kiro/steering/' },
@@ -118,6 +121,7 @@ export function detectTarget(cwd = process.cwd()) {
118
121
  if (has('.kiro')) return 'kiro';
119
122
  if (has('.zed')) return 'zed';
120
123
  if (has('.opencode')) return 'opencode';
124
+ if (has('.dsh')) return 'deepseek';
121
125
  if (has('.amp')) return 'amp';
122
126
  if (has('.jules')) return 'jules';
123
127
  if (has('.antigravity')) return 'antigravity';
@@ -18,6 +18,7 @@ const PLUGIN_MARKER = 'rsc-knowledge:managed';
18
18
  const CONFIG = Object.freeze({
19
19
  claude: '.claude/settings.local.json',
20
20
  codex: '.codex/hooks.json',
21
+ deepseek: '.dsh/hooks.json',
21
22
  cursor: '.cursor/hooks.json',
22
23
  gemini: '.gemini/settings.json',
23
24
  opencode: '.opencode/plugins/rsc-knowledge.js',
@@ -26,6 +27,7 @@ const CONFIG = Object.freeze({
26
27
  const EVENTS = Object.freeze({
27
28
  claude: [['UserPromptSubmit', 'request'], ['Stop', 'turn']],
28
29
  codex: [['UserPromptSubmit', 'request'], ['Stop', 'turn']],
30
+ deepseek: [['UserPromptSubmit', 'request'], ['Stop', 'turn']],
29
31
  gemini: [['BeforeAgent', 'request'], ['AfterAgent', 'turn']],
30
32
  cursor: [['beforeSubmitPrompt', 'request'], ['afterAgentResponse', 'turn']],
31
33
  });
package/targets/memory.js CHANGED
@@ -13,6 +13,7 @@ const PLUGIN_MARKER = 'rsc-memory:managed';
13
13
  export const MEMORY_TARGETS = Object.freeze({
14
14
  claude: 'full',
15
15
  codex: 'full',
16
+ deepseek: 'full',
16
17
  // Cursor documents sessionStart as fire-and-forget. Its best-effort hook is useful,
17
18
  // but only the always-on rule can make the read-before-action obligation explicit.
18
19
  cursor: 'assisted',
@@ -23,6 +24,7 @@ export const MEMORY_TARGETS = Object.freeze({
23
24
  const CONFIG = Object.freeze({
24
25
  claude: '.claude/settings.local.json',
25
26
  codex: '.codex/hooks.json',
27
+ deepseek: '.dsh/hooks.json',
26
28
  cursor: '.cursor/hooks.json',
27
29
  gemini: '.gemini/settings.json',
28
30
  opencode: '.opencode/plugins/rsc-memory.js',
@@ -99,6 +101,11 @@ const nestedEvents = {
99
101
  ['PostToolUse', 'edit', 'apply_patch|Edit|Write'], ['PostToolUse', 'boundary', 'Bash'],
100
102
  ['Stop', 'turn'], ['PreCompact', 'compact'], ['SessionEnd', 'end'],
101
103
  ],
104
+ // DeepSeek Harness runs Codex-format hooks through rsc's bridge: only these five points exist there.
105
+ deepseek: [
106
+ ['SessionStart', 'start'], ['UserPromptSubmit', 'request'],
107
+ ['PostToolUse', 'edit', 'edit|write|str_replace_editor'], ['PostToolUse', 'boundary', 'bash'], ['Stop', 'turn'],
108
+ ],
102
109
  gemini: [
103
110
  ['SessionStart', 'start'], ['BeforeAgent', 'request'], ['AfterTool', 'edit', 'write_file|replace'],
104
111
  ['AfterTool', 'boundary', 'run_shell_command'], ['AfterAgent', 'turn'], ['PreCompress', 'compact'], ['SessionEnd', 'end'],
@@ -4,7 +4,7 @@ import { existsSync, readFileSync, realpathSync } from 'node:fs';
4
4
  import { resolve, join, dirname } from 'node:path';
5
5
  import { capture, otherActiveSessions, resume } from './session-memory-core.mjs';
6
6
 
7
- const LOCAL_TARGETS = new Set(['claude', 'codex', 'cursor', 'gemini', 'opencode']);
7
+ const LOCAL_TARGETS = new Set(['claude', 'codex', 'cursor', 'deepseek', 'gemini', 'opencode']);
8
8
 
9
9
  function projectSettings(cwd) {
10
10
  try {
@@ -19,12 +19,13 @@ const PLUGIN_MARKER = 'rsc-update:managed';
19
19
 
20
20
  const CONFIG = Object.freeze({
21
21
  codex: '.codex/hooks.json',
22
+ deepseek: '.dsh/hooks.json',
22
23
  cursor: '.cursor/hooks.json',
23
24
  gemini: '.gemini/settings.json',
24
25
  opencode: '.opencode/plugins/rsc-update.js',
25
26
  });
26
27
 
27
- const EVENT = Object.freeze({ codex: 'SessionStart', gemini: 'SessionStart', cursor: 'sessionStart' });
28
+ const EVENT = Object.freeze({ codex: 'SessionStart', deepseek: 'SessionStart', gemini: 'SessionStart', cursor: 'sessionStart' });
28
29
 
29
30
  /** Assistants where the update runs on its own. Every other one depends on the agent running it. */
30
31
  export const UPDATE_HOOK_TARGETS = Object.freeze(['claude', ...Object.keys(CONFIG)].sort());