{"_id":"@dennisrongo/dsh-hooks","_rev":"3-cc66a716dd1178ca75910855c8136550","name":"@dennisrongo/dsh-hooks","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@dennisrongo/dsh-hooks","version":"0.1.0","author":{"name":"Dennis Rongo"},"license":"MIT","_id":"@dennisrongo/dsh-hooks@0.1.0","maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-hooks#readme","bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"06681bb94b46ec959cb215269e583b736cae6a57","tarball":"https://registry.npmjs.org/@dennisrongo/dsh-hooks/-/dsh-hooks-0.1.0.tgz","fileCount":5,"integrity":"sha512-A23F+JS1T2YP6HWglOKuCx706AgzYu/Vqpe/AyS5AduMMSOEXRWoTtGeenZ5JJ2MT/PNjBRWfqCR7LqEx/p+xw==","signatures":[{"sig":"MEUCIEFw3s9nlH9pqjmDC8nke76sXymNteSQNxuqGQkC22skAiEAiVexIUATtzWxN53wt48+n5pJiJr0rOrTww2QujcJ6TA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dennisrongo%2fdsh-hooks@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":51087},"main":"lib/index.js","type":"module","exports":{".":{"default":"./lib/index.js"},"./typert":{"default":"./lib/typert.host.js"},"./package.json":"./package.json"},"gitHead":"f3f56e3e0d834f8bf484ed327bda5e15afe71e5a","scripts":{"test":"node build/build.mjs && node test/smoke.mjs","build":"node build/build.mjs","typecheck":"tsc --noEmit"},"_npmUser":{"name":"dennisrongo","email":"dennis@menacestudio.com"},"repository":{"url":"git+https://github.com/dennisrongo/dsh-plugins.git","type":"git","directory":"plugins/dsh-hooks"},"_npmVersion":"10.9.8","description":"Claude Code-compatible hook lifecycle for DeepSeek Harness (dsh) — run shell commands at PreToolUse, PostToolUse, UserPromptSubmit, SessionStart/End, Stop, SubagentStop and Notification","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3"},"_hasShrinkwrap":false,"devDependencies":{"esbuild":"^0.24.0","typescript":"^5.6.0","@types/node":"^22.20.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-llm":"^0.1.1-rc.2","@deepseek-ai/dsh-agent":"^0.1.1-rc.2","@deepseek-ai/dsh-tools":"^0.1.1-rc.2","@deepseek-ai/dsh-session":"^0.1.1-rc.2","@deepseek-ai/schemastery":"^3.18.1","@deepseek-ai/dsh-settings":"^0.1.1-rc.2","@deepseek-ai/dsh-workspace":"^0.1.1-rc.2","@deepseek-ai/dsh-subprocess":"^0.1.1-rc.2","@deepseek-ai/dsh-typert-protocol":"^0.1.1-rc.2"},"_npmOperationalInternal":{"tmp":"tmp/dsh-hooks_0.1.0_1788036364227_0.69857503852441","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dennisrongo/dsh-hooks","version":"0.1.1","author":{"name":"Dennis Rongo"},"license":"MIT","_id":"@dennisrongo/dsh-hooks@0.1.1","maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-hooks#readme","bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"03d78e9430eabfcc90aa77c078672e45fbf10d00","tarball":"https://registry.npmjs.org/@dennisrongo/dsh-hooks/-/dsh-hooks-0.1.1.tgz","fileCount":5,"integrity":"sha512-0coZBxs6alXAEoquqqquvW8MLEszcZcV8wwNidAz9D2OQl0dSA9m2jaVIIcoCerVhiP3UuuK+ITy7LGroOjK6Q==","signatures":[{"sig":"MEUCIBHgYaPsOuOfYyGuvE0wn17+qp+tbvC2ckflsRPugjmKAiEA5ep0Otet5jVWO6UIbOq+nY1iEE5WDo9ZxtBcHvzdptw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIBo9Hy3iqUqD5Ix+RUGjM1J/IluECWEqf39cphaLEu7oAiAuxpyxqJUdqGr5spnPX6fVb1JYEWGhuYg0kLdEPYg9Sw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dennisrongo%2fdsh-hooks@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":51256},"main":"lib/index.js","type":"module","exports":{".":{"default":"./lib/index.js"},"./typert":{"default":"./lib/typert.host.js"},"./package.json":"./package.json"},"gitHead":"66d0f3f76a02a0d875cd49c30e3a4131c8af283c","scripts":{"test":"node build/build.mjs && node test/smoke.mjs","build":"node build/build.mjs","typecheck":"tsc --noEmit"},"_npmUser":{"name":"dennisrongo","email":"dennis@menacestudio.com"},"repository":{"url":"git+https://github.com/dennisrongo/dsh-plugins.git","type":"git","directory":"plugins/dsh-hooks"},"_npmVersion":"10.9.8","description":"Claude Code-compatible hook lifecycle for DeepSeek Harness (dsh) — run shell commands at PreToolUse, PostToolUse, UserPromptSubmit, SessionStart/End, Stop, SubagentStop and Notification","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3"},"_hasShrinkwrap":false,"devDependencies":{"esbuild":"^0.24.0","typescript":"^5.6.0","@types/node":"^22.20.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-llm":"^0.1.1-rc.2","@deepseek-ai/dsh-agent":"^0.1.1-rc.2","@deepseek-ai/dsh-tools":"^0.1.1-rc.2","@deepseek-ai/dsh-session":"^0.1.1-rc.2","@deepseek-ai/schemastery":"^3.18.1","@deepseek-ai/dsh-settings":"^0.1.1-rc.2","@deepseek-ai/dsh-workspace":"^0.1.1-rc.2","@deepseek-ai/dsh-subprocess":"^0.1.1-rc.2","@deepseek-ai/dsh-typert-protocol":"^0.1.1-rc.2"},"_npmOperationalInternal":{"tmp":"tmp/dsh-hooks_0.1.1_1788534906343_0.19564216094005848","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"_id":"@dennisrongo/dsh-hooks@0.2.0","dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime"],"platform":"web","immediately":true}},"bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"dist":{"shasum":"096f4420f0f65467e05d2f7d0d8b4ab7011c4fdf","tarball":"https://registry.npmjs.org/@dennisrongo/dsh-hooks/-/dsh-hooks-0.2.0.tgz","fileCount":6,"integrity":"sha512-XEW3XkBZq+/5FWrlIAflgZBG3sRqZtIat3hSxfrIJsyL22o3/6rdHu0qAIHYeM6QHjdU+/Akju5EiotLLpIjPg==","signatures":[{"sig":"MEUCIQCxCmEeIlDBMQ9vXKq+16YYLzWEzihuJ7Ggxf2pFhVHKQIgFdtV5pFU5+LHtGC81JsIRNgzzqyc9W47MLEne9iE/Us=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCVx5Gd3HQ0FqSWCkPWEF1EjI8mdwIZo1uV2WmKUwDnEwIgOXodwzR4AVF55/or77mij0a70IWXywrWTULTbAHkweg="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dennisrongo%2fdsh-hooks@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":422884},"main":"lib/index.js","name":"@dennisrongo/dsh-hooks","type":"module","author":{"url":"https://www.youtube.com/@codingmenace","name":"Dennis Rongo"},"exports":{".":{"default":"./lib/index.js"},"./client":{"default":"./lib/client.js"},"./typert":{"default":"./lib/typert.host.js"},"./package.json":"./package.json"},"gitHead":"e3786cf6b1cf7a267fb5d1b97186cbc3aa4124d3","license":"MIT","scripts":{"test":"node build/build.mjs && node test/smoke.mjs","build":"node build/build.mjs","typecheck":"tsc --noEmit"},"version":"0.2.0","_npmUser":{"name":"dennisrongo","email":"dennis@menacestudio.com"},"homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-hooks#readme","keywords":["ai-coding-agent","claude-code-alternative","deepseek-harness","dsh","dsh-plugin"],"repository":{"url":"git+https://github.com/dennisrongo/dsh-plugins.git","type":"git","directory":"plugins/dsh-hooks"},"_npmVersion":"10.9.8","description":"Claude Code-compatible hook lifecycle for DeepSeek Harness (dsh) — run shell commands at PreToolUse, PostToolUse, UserPromptSubmit, SessionStart/End, Stop, SubagentStop and Notification, plus contextual skill hints beside the composer","directories":{},"maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.4.3"},"_hasShrinkwrap":false,"devDependencies":{"react":"^18.3.0","esbuild":"^0.24.0","typescript":"^5.6.0","@types/node":"^22.20.1","@types/react":"^18.3.0"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-llm":"^0.1.1-rc.2","@deepseek-ai/dsh-agent":"^0.1.1-rc.2","@deepseek-ai/dsh-tools":"^0.1.1-rc.2","@deepseek-ai/dsh-session":"^0.1.1-rc.2","@deepseek-ai/schemastery":"^3.18.1","@deepseek-ai/dsh-settings":"^0.1.1-rc.2","@deepseek-ai/dsh-workspace":"^0.1.1-rc.2","@deepseek-ai/dsh-subprocess":"^0.1.1-rc.2","@deepseek-ai/dsh-typert-protocol":"^0.1.1-rc.2"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-hooks_0.2.0_1789592865979_0.439907035814767"}}},"time":{"created":"2026-08-29T20:46:03.974Z","modified":"2026-09-16T21:07:46.393Z","0.1.0":"2026-08-29T20:46:04.374Z","0.1.1":"2026-09-04T15:15:06.425Z","0.2.0":"2026-09-16T21:07:46.074Z"},"bugs":{"url":"https://github.com/dennisrongo/dsh-plugins/issues"},"author":{"url":"https://www.youtube.com/@codingmenace","name":"Dennis Rongo"},"license":"MIT","homepage":"https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-hooks#readme","repository":{"url":"git+https://github.com/dennisrongo/dsh-plugins.git","type":"git","directory":"plugins/dsh-hooks"},"description":"Claude Code-compatible hook lifecycle for DeepSeek Harness (dsh) — run shell commands at PreToolUse, PostToolUse, UserPromptSubmit, SessionStart/End, Stop, SubagentStop and Notification, plus contextual skill hints beside the composer","maintainers":[{"name":"dennisrongo","email":"dennis@menacestudio.com"}],"readme":"# dsh-hooks\n\n**npm:** [`@dennisrongo/dsh-hooks`](https://www.npmjs.com/package/@dennisrongo/dsh-hooks)\n\nA Claude Code-compatible hook lifecycle for [dsh](https://github.com/deepseek-ai/deepseek-harness). Attach a shell command to a lifecycle point and it runs there — to block a tool call, feed the model context, format a file after an edit, or notify you when the agent stops. It also reads those same signals to suggest a skill beside the composer — see [Skill hints](#skill-hints).\n\ndsh already had the lifecycle. `tools/pre-execute` is a waterfall returning `allow | deny | ask`, `tools/post-execute` can block a settled result or attach model-facing context, `agent/pre-step` can reject or rewrite the messages entering a step, and `agent/turn-stopping` can steer an agent back into work. What it had no way to do was attach a **command** to any of that from configuration. This plugin is that runner and nothing else: it owns no policy, and with an empty config it is inert.\n\n## The eight events\n\n| Config name | dsh event | What a hook can do |\n|---|---|---|\n| `PreToolUse` | `tools/pre-execute` | **allow / deny / ask** before the tool runs |\n| `PostToolUse` | `tools/post-execute` | **block** with feedback the model reads, or attach context |\n| `UserPromptSubmit` | `agent/pre-step` | **reject** the step, or attach context to the prompt |\n| `SessionStart` | `agent/session-start` | attach context (`source` is `startup`/`resume`/`clear`/`compact`) |\n| `SessionEnd` | `agent/disposed` | effect only |\n| `Stop` | `agent/turn-stopping` | **continue** — steer the agent into another step |\n| `SubagentStop` | `subagent/end` | effect only |\n| `Notification` | `approval/request` | effect only |\n\n`UserPromptSubmit` is gated to steps that actually claimed a user-sourced message, because `agent/pre-step` fires on *every* step — without the gate a prompt hook would run once per tool round-trip.\n\n## Configuration\n\nTwo layers, and they are **additive**: every matching hook from both documents runs. A checked-out repository cannot silently disable your global guard by declaring an empty list.\n\n**User layer** — the `dsh-hooks` settings namespace, so it lives in `$DSH_HOME/settings.yaml`, is schema-validated, and reloads live when you edit it:\n\n```yaml\ndsh-hooks:\n  enabled: true\n  hooks:\n    PreToolUse:\n      - matcher: bash\n        hooks:\n          - type: command\n            command: node ~/.dsh/hooks/no-force-push.mjs\n            timeout: 10\n            failClosed: true\n    PostToolUse:\n      - matcher: str_replace_editor|create_file\n        hooks:\n          - type: command\n            command: pnpm exec prettier --write \"$DSH_PROJECT_DIR\"\n```\n\n**Project layer** — `<workspace>/.dsh/hooks.json`, same shape, committed with the repo:\n\n```json\n{\n  \"PostToolUse\": [\n    { \"matcher\": \"str_replace_editor\", \"hooks\": [{ \"type\": \"command\", \"command\": \"pnpm run lint --fix\" }] }\n  ]\n}\n```\n\nRead on every dispatch and cached against the file's `mtime+size`, so an edit takes effect with no restart and no watcher held open per workspace. Set `projectHooks: false` to trust only your own settings.\n\n`matcher` is a **regular expression** over the tool name — `bash|str_replace_editor` works. Absent, empty and `*` all mean every tool. An invalid pattern matches **nothing** and warns once: one typo must not turn into a hook that fires on every call in the session.\n\n## The command protocol\n\nIdentical to Claude Code, so an existing hook script runs here unchanged.\n\n- The JSON payload arrives on **stdin** (`hook_event_name`, `session_id`, `cwd`, `workspace_id`, `tool_name`, `tool_input`, `tool_response`, `prompt`, `source`, `stop_hook_active`).\n- **Exit 0** — success. stdout is parsed as JSON when it is JSON; anything else is ordinary log output.\n- **Exit 2** — blocking. stderr becomes the reason handed to the model.\n- **Any other code** — non-blocking error. Recorded and logged.\n\nStructured stdout:\n\n```json\n{\n  \"hookSpecificOutput\": {\n    \"permissionDecision\": \"deny\",\n    \"permissionDecisionReason\": \"force-push to main is not allowed\",\n    \"additionalContext\": \"the repo uses squash merges\"\n  }\n}\n```\n\n`decision: \"block\" | \"approve\"` is honoured too; where the two fields disagree the **more restrictive** one wins, because a hook that says `deny` in one field and `approve` in another has a bug and denying is the safe reading of a buggy security hook.\n\nEvery hook is handed `DSH_PROJECT_DIR`, `DSH_SESSION_ID` and `DSH_HOOK_EVENT` — plus `CLAUDE_PROJECT_DIR` as the same value, so a ported script finds its project without edits. These names are supplied explicitly because the subprocess seam scrubs *all* `DSH_*` and credential-shaped variables from a child's ambient environment; the spec's explicit `env` is the documented opt-in.\n\n## Two things Claude Code does that dsh cannot\n\nBoth are **reported, never silently dropped** — a hook that believes it did something it did not is worse than one that cannot do it at all.\n\n- **Rewriting tool arguments.** `PreToolDecision` is `allow | deny | ask`; the tool registry's own documentation says input rewriting is excluded because arguments are already logged and presented. A hook returning `updatedInput` gets a warning naming the alternative: deny the call.\n- **Blocking session teardown.** `agent/disposed` is emit-mode, so `SessionEnd` runs for effect and cannot object.\n\nOne timing caveat: `agent/session-start` is emit-mode too, so cordis does not await the listener and a slow `SessionStart` hook races the first model request. Its `additionalContext` goes in through `agent.inject()`, which a running driver claims at its nearest step boundary — so the context lands a step later rather than being lost.\n\n## Failure is fail-open by default\n\n`tools/pre-execute` is awaited by the tool registry before **every** dispatch. A hook that hangs would stall the session, so:\n\n- the deadline is owned by the runner, never delegated to the hook (default 60s, `timeout` per entry, 600s ceiling);\n- expiry escalates through the seam's tree-scoped `terminate`, so a helper process cannot outlive it;\n- a crash, a timeout, or an unparseable reply is **not** a denial unless the entry sets `failClosed: true`.\n\nThat default is deliberate. One broken hook bricking every tool call is the failure mode that makes people delete their hooks entirely. A hook that exists to *enforce* something sets `failClosed` and accepts that breaking it stops the work — the right trade for a security gate and the wrong one for a formatter.\n\n`Stop` gets a second guard. The protocol's own loop-breaker is `stop_hook_active`, which this plugin passes faithfully; the cap of **five consecutive continuations** exists for the hook that ignores it, because `{\"decision\":\"block\"}` on `Stop` is otherwise an infinite loop that burns tokens until you notice.\n\n## Skill hints\n\nThe same lifecycle signals the hooks run on also describe what you are doing, and this plugin uses them for a second thing: small chips in the composer's tool row, beside the `+` and access-mode controls, naming a **skill you could run next**. Clicking a chip runs it: the composer receives `/skill-name` followed by an **intent** that says what the skill is for — `/code-review review the changes from the last turn: Greeter.cs, Calculator.cs`, or `/diagnose` followed by your own words when your prompt described something broken — and submits. The chip then disappears. `×` dismisses it for the rest of the session without running anything.\n\nChips appear **only while the composer is idle**. Nothing shows while a turn is running. Project chips (.NET, Next.js, …) show only on a fresh session, before your first prompt; once work has started, the turn rules take over.\n\nEvery chip names a skill that is **actually installed and user-invocable in this deployment**. Rules carry patterns, not names; the pattern is resolved against `ctx.skills.list()` for your agent's scope, so no installed match means no chip. A skill you have already used this session is never suggested again.\n\n| Rule id | Fires when | Suggests |\n|---|---|---|\n| `prompt:phrase` | your prompt contains a phrase a skill quotes in its own description (`\"review my code\"`, `\"debug this\"`) | that skill |\n| `prompt:bug` | your prompt mentions a bug / crash / failure and no phrase matched | a diagnosis skill |\n| `prompt:plan` | your prompt is about planning, design or architecture | a planning skill |\n| `turn:feature-done` | the turn made ≥ 3 source edits, its last tool call succeeded, and it loaded no skill | a code-review skill, then a tests skill |\n| `turn:tests-missing` | ≥ 3 source edits and nothing ran a test command | a tests skill |\n| `turn:ready-to-ship` | the turn ran `git add`/`git commit`, or the work was already reviewed | a commit-message skill, then a PR skill |\n| `project:dotnet` | a `.csproj` / `.sln` / `.slnx` / `global.json` in the workspace | a .NET skill |\n| `project:nextjs` | `package.json` depends on `next` | a Next.js skill |\n| `project:tauri` | a Tauri config or `@tauri-apps/*` dependency | a Tauri skill |\n| `project:remotion` | `package.json` depends on `remotion` | a Remotion skill |\n| `project:shadcn` | `components.json` whose `$schema` names shadcn | a shadcn skill |\n| `session:long` | 25 or more prompts in the session | a hand-off skill |\n\nTurn rules outrank project rules, so a chip about what just happened beats a standing fact about the workspace. The workspace scan reads the top level plus one level of subdirectories (skipping `node_modules`, `bin`, `obj`, `dist` and friends, bounded to 400 entries) and is cached for 30 seconds — a `.csproj` at `src/Web/Web.csproj` is found, which a root-only scan would miss.\n\nThe rules are deliberately cheap regexes and quoted-phrase matches, not a classifier. A false positive costs you one ignorable chip, so paying model latency to sharpen it would be the wrong trade — and a rule you can predict is one you can turn off:\n\n```yaml\ndsh-hooks:\n  hints:\n    enabled: true          # master switch for the strip and the engine\n    max: 3                 # 1..6 chips at once\n    disableRules:          # silence rules by id\n      - session:long\n      - prompt:plan\n```\n\n`enabled: false` leaves the hook runner completely untouched. There is **no project layer for hints** — `.dsh/hints.json` is not a thing yet, and neither are user-defined rules; both are on the list, not in the box.\n\nThe strip renders nothing at all when there are no hints, so it costs no vertical space in a session it has nothing to say about.\n\n## Endpoints\n\n`POST /api/dshHooks/describe` → `{ enabled, shell, userOrigin?, projectOrigin?, hooks: [...] }` — every hook in force across both layers, with the document each came from. Takes `{ workspaceId? }`.\n\n`POST /api/dshHooks/recent` → `{ runs: [...] }` — the last 200 settled runs with exit codes, durations, stdout/stderr tails and parsed output. Takes `{ limit? }`.\n\n`POST /api/dshHooks/hints` → `{ hints: [...], token }` — the chips computed for one session. Takes `{ sessionId }`.\n\n`POST /api/dshHooks/hintsToken` → `{ token }` — an in-memory counter that moves **only** when the computed list actually changes. This is what the strip polls (every 1.5s while the tab is visible), so it does no work at all; an idle session polls a constant and never refetches. Takes `{ sessionId }`.\n\n`POST /api/dshHooks/dismissHint` → `{ ok, token }` — silence one hint id for the session. Takes `{ sessionId, id }`.\n\nAn unknown session is an empty list and token `0` on all three, never an error. Every endpoint takes a single parameter named `request`.\n\n## Install\n\n```bash\ndsh plugin add @dennisrongo/dsh-hooks\n```\n\nThen restart the profile — the Typert loader caches its per-package verdict for the process lifetime, so a newly added service is not picked up by a refresh.\n\n## Requires\n\n`ctx.tools` and `ctx.subprocess`, both composed by `@deepseek-ai/dsh-web-app` by default. `ctx.settings`, `ctx.workspaceRegistry` and `ctx.skills` are used when present and are **not** injected: a deployment composing none of them still gets project-layer hooks and a working cwd-derived payload, rather than a service that never becomes injectable. With no skill registry composed, the hint strip simply stays empty.\n\n---\n\n## About the author\n\nPlugins and walkthroughs on [YouTube @codingmenace](https://www.youtube.com/@codingmenace) —\nI build AI coding tools in public, including DeepSeek Harness itself. More at\n[dennisrongo.com](https://dennisrongo.com/).\n","readmeFilename":"README.md","keywords":["ai-coding-agent","claude-code-alternative","deepseek-harness","dsh","dsh-plugin"]}