# Changelog

English | [简体中文](CHANGELOG.zh-CN.md)

All notable changes to this project are documented here. Versions follow [SemVer](https://semver.org/).

## [1.1.0] — 2026-11-05

### Upgrade notes
- **HTTP MCP entries require `"type": "http"`** (or `"streamable-http"`). A URL without a type, and unsupported `sse`/`ws` transports, are skipped with a warning. Migrate remote entries before upgrading.
- **Project MCP credentials and helpers require explicit trust.** `mcpTrustProjectConfig` defaults to `false`: project `headersHelper` commands do not run and credential-shaped `${VAR}` references stay literal. Set it to `true` only for a project you trust; project helpers still receive a scrubbed environment.
- **Restart DSH after updating.** MCP and project hooks are loaded from the launch workspace at startup. This release does not add per-session MCP discovery or native-realm integration.
- **Re-import a Claude session if an earlier import is missing from DSH.** The corrected DSH directory key is used for new imports; existing logs in old directories are not moved or deleted automatically.

### Added
- **Claude Code location configuration** ([#6](https://github.com/biedongbin/dsh-claude-compat/pull/6)): honor `CLAUDE_CONFIG_DIR`, plugin cache/seed/inline directories and `CLAUDE_CODE_PROJECT_DIR_NAME`, with explicit config taking priority. Relocated seeds and whole-plugin inline overrides are supported.
- **Hook handler fields and lifecycle coverage** ([#7](https://github.com/biedongbin/dsh-claude-compat/pull/7), [#8](https://github.com/biedongbin/dsh-claude-compat/pull/8)): bridge 26 of 33 events with `command`, `http`, `mcp_tool`, `prompt` and `agent` handlers; support conditional execution, per-hook timeouts, asynchronous hooks, failure blocking and `asyncRewake`. The README lists event-specific limits and unsupported events.
- **Additional MCP sources and policy** ([#9](https://github.com/biedongbin/dsh-claude-compat/pull/9)): read managed, local, project, user and opt-in plugin definitions; apply source precedence, disabled lists, allow/deny policy, environment references, per-call timeouts and mount-time `headersHelper` output. Invalid managed policy fails closed.

### Fixed
- **Claude session discovery for punctuation, spaces, non-ASCII and long paths** ([#11](https://github.com/biedongbin/dsh-claude-compat/issues/11)). Reuse the existing Claude directory encoder, including its full-path hash, and preserve custom Claude configuration roots for explicit list/import commands.
- **DSH session import directories** ([#12](https://github.com/biedongbin/dsh-claude-compat/issues/12)). Import and imported-status detection share DSH's native key: collapsed separators, UTF-16 `~XXXX` escaping, root handling and a 251-character slug. Real CLI regressions cover both discovery and the resulting compressed session file.
- **Hook runtime contracts.** Use native tool names and single-call MCP execution with the viewing Agent scope; observe model switches on `agent/request`; update FileChanged watchers on cwd changes even without a CwdChanged hook; preserve failure-blocking PermissionRequest decisions.
- **Hook output and lifecycle handling.** Decode split UTF-8 characters correctly, bound output and verifier cleanup, retain guards in verifier descendants, and report ignored or malformed results. SessionStart uses `agent/created`, payloads carry the session id, and injected context does not trigger user-prompt hooks.
- **MCP source isolation and diagnostics.** Skip malformed entries individually, validate managed definitions before policy exemptions, constrain plugin file references, expand plugin roots once, and protect project helper credentials. Plugin-manager malformed JSON and large timeout values now have explicit handling.

### Verification and remaining limits
- Release verification targets DSH 0.2.1-rc.5 with Desktop Node 24.21.0. Node 22 has a known URL-normalization property-test difference; do not treat that runtime as fully verified.
- With a custom `userClaudeDir`, use explicit `/cc-resume list` and `/cc-resume import <id>` commands. The default interactive picker's later skill handoff does not yet carry that directory.
- Complete AgentLoop contract tests require the optional matching testkit. See [release preparation notes](docs/releases/1.1.0.md) for the verification scope and manual publication steps.

## [1.0.1] — 2026-11-04

### Fixed
- **Project `.claude/skills` and `.claude/rules` were silently skipped when the project was not a git repo** ([#10](https://github.com/biedongbin/dsh-claude-compat/issues/10)). `findProjectRoot`/`findProjectRootSync` walked to the filesystem root, found no `.git`, and returned `undefined`; both call sites (`list()` for skills, `buildRulesText()` for rules) guard on `undefined` and skip without a word. Root discovery now falls back to the session cwd when no `projectRootMarkers` entry is found, which is DSH's own skill-filesystem rule ("nearest ancestor containing `.git`; without one, the current cwd is used") and matches Claude Code, which reads `.claude/` from any directory. The same fallback applies to project hooks (`loadHookSettings`) and project `.mcp.json`. Covered by `test/v100.test.js`.

## [1.0.0] — 2026-10-30

### Added
- **Published to the DSH plugin market.** The repo now hosts a market index — `site/index.json`, deployed to GitHub Pages by `.github/workflows/market.yml`. Add `https://biedongbin.github.io/dsh-claude-compat/index.json` as an index source in the 插件市场「发现」 tab to browse and install every release. `scripts/update-market-index.mjs` regenerates the index from the npm registry (non-deprecated versions declaring `dsh.bundle`, matching the official generator's rule); versions already listed are preserved across reruns, so run it after `npm publish` and commit to pick up the new release. 0.9.2 was packed but never pushed to npm — the registry jumps from 0.9.1 straight to 1.0.0.

## [0.9.2] — 2026-10-28

### Added
- **Native DSH slash commands for the manager surface** (`/cc-resume`, `/cc-plugin`, `/cc-export`, `/reload-cc-plugins`, `/reload-skills`). DSH's `@deepseek-ai/dsh-commands` service (mounted in the shipped Web host) lets a plugin register host-executed human commands: the handler runs in the DSH process when the user submits the slash line, and the result text is rendered by the UI directly — command discovery, execution, and output add **no model tokens** and never enter model history (each run is logged as a log-only `command/run`+`command/done` pair). Handlers spawn this package's own CLIs (`scripts/cc-resume.mjs`, `scripts/cc-export.mjs`) with the **session** cwd pinned via `--cwd` (the host process cwd is not the workspace), `/cc-plugin` reuses `runPluginCommand`, and the reload commands call the skill registry's `control.invalidate()` directly. Registration goes through the runtime guard `ctx.inject(['commands'], …)` — the exact pattern of the built-in `dsh-plan-mode` — so hosts without the service (UI-less spines, ACP automation, older builds) silently keep the skill path. Covered by `test/v092.test.js`.

### Changed
- **The five manager skills left the user's slash menu** (`userInvocable: false`, still `modelInvocable: true`): the human entry point is now the native command, so the menu shows one entry per surface instead of a command + a model-mediated skill; the model keeps the loadable-instructions fallback.

## [0.9.1] — 2026-10-28

### Fixed
- **Manager skills were listed but could never load (`skill "cc-plugin" is unknown or no longer available`)** — affected all five inline skills (`/cc-plugin`, `/reload-cc-plugins`, `/reload-skills`, `/cc-resume`, `/cc-export`). The DSH registry loads every definition exclusively through `provider.get(candidate)`; a candidate-carried `get()` closure is never called. These skills carried their bodies in exactly such a closure, and their locator held `import.meta.url` (a `file://` URL) that `provider.get()` then tried to read from disk — `readTextSafe(file://…)` returns `undefined`, so the registry reported the skill as unknown. Two latent defects died with the closure: it returned `{ body }` where `validateDefinition` requires `{ content }`, and `/reload-cc-plugins`' cache drop (`control.invalidate()`) lived inside it, so reload never actually reloaded. Bodies and the invalidate flag now travel in the locator (`{ kind: 'inline', body, invalidate }`); `provider.get()` serves inline candidates directly and fires `invalidate()` at load time — loading the reload skill IS the reload. Resolves the 0.7.0 "known issue": the root cause was this plugin's dead closure, not DSH runtime layering. Covered by `test/v060.test.js` and a real-`SkillRegistry` load assertion in `test/self-check.test.js`.
- **`/cc-plugin` HELP copy said `/plugin` throughout** while the skill is invoked as `/cc-plugin`; now consistent, and the Execution section explicitly tells the model to run `claude plugin <args>` via bash (`runPluginCommand` has no DSH tool mount, so the body is the only execution path).

### Tooling
- **npm tarball ships only the runtime scripts.** `files` previously globbed the whole `scripts/` directory, publishing maintainer-only tooling (`deploy-local.sh` — checkout→profile deploy with a macOS app-path hint, meaningless elsewhere; `gen-downloads-chart.mjs`) alongside the four scripts the installed plugin actually needs (`cc-resume.mjs`, `cc-export.mjs`, `zstd-compat.mjs`, `dsh-restart.sh` for the `bin`). Both dev scripts stay in the repo; only the published package slims down.

## [0.9.0] — 2026-10-28

### Added
- **`skillAliases`: make Chinese (and any non-ASCII) skill names usable.** DSH fixes the skill-name grammar to kebab-case (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, defined in `@deepseek-ai/dsh-skill` and checked by the registry before `get()`), so a Chinese name emitted by the plugin still fails one layer down with `return void 0` — until now the only option was to drop it. Map it to a legal slug instead: `skillAliases: { "三脑": "san-nao" }`; the original name stays at the front of the description (`三脑 — …`, disable with `aliasDescriptionPrefix: false`). The map applies to skills, commands, agents and plugin skills alike. A legal name always beats an alias, and aliased entries keep their provider rank, so collisions with DSH-native or `~/.agents` skills dedupe exactly as before. Covered by `test/v090.test.js`.

### Fixed
- **Non-kebab skills were dropped silently.** `skills`, `commands`, `agents` and plugin commands all did a bare `continue`; the only way to learn what was missing was to read the source. Each file now warns once per process with a paste-ready `skillAliases` example.

### Tooling
- **`scripts/deploy-local.sh`**: copy this checkout's `src/` into an installed DSH profile (`~/.dsh/profiles/<profile>/node_modules/dsh-claude-compat`) with a timestamped backup. DSH imports the installed copy at process start, so editing a checkout otherwise changes nothing. (`bash scripts/deploy-local.sh --help`.) Contributed in PR #3.

_Contributed by @liujunctz123-jpg (PR #3)._

## [0.8.4] — 2026-10-18

### Fixed
- **New sessions failed to load on dsh 0.2.0 (`format v4 message requires a producer-owned source kind`)**: session format v4 admission (`session-format-v3-to-v4/src/message-sources.ts`) rejects the retired V3 wrapper `source.kind: 'plugin'` — a third-party producer must declare its own kind. All three injection sites (rules in `index.js`, hook-context in `hooks.js` ×2) still emitted the wrapper, so every session in a project with `.claude/rules/*.md` failed admission at load. Injection now emits `source.kind: 'plugin:dsh-claude-compat'` with the `plugin` field dropped — the exact kind dsh's own `producerKind()` derives for us (not in `RENAMED_PRODUCERS`/`RELEASED_SAME_NAME_PRODUCERS` → `plugin:${plugin}`), so 0.8.3 logs migrated by dsh and messages written today converge on one value. Duplicate-injection detection compares the shared `CLAUDE_COMPAT_SOURCE_KIND` constant. Regression covered by `test/v080.test.js` and verified against the real admission predicate in `test-evidence/verify-084-v4-admission.mjs`. Issue #2.

## [0.8.3] — 2026-09-30

### Fixed
- **Rules dedup scan used the wrong event lookup**: the pre-step hook took one `snapshotEvents()` snapshot and indexed it by surface seq (`events[seq]`). Snapshot indices and session seqs are not guaranteed to align (dsh has `firstLiveSeq`/offset semantics), so the duplicate-injection check could silently miss. Now uses `session.eventAt(seq)` — the exact-lookup API dsh core itself uses for surface scans — with an optional `session.events?.[seq]` fallback for pre-0.1.2 hosts. Based on PR #1 by @SvenKunkka.

## [0.8.2] — 2026-09-30

### Fixed
- **Session archives unreadable after dsh upgrade (`cannot safely transform unclassified message source`)**: injected messages carried a custom `source.kind: 'claude-compat'`, which is not in dsh's migration whitelist (`SOURCE_KINDS`), so any session log containing one failed v0→v3 migration fail-closed and could not load. Injection sources (rules in `index.js`, hook-context in `hooks.js`) now use the whitelisted `kind: 'plugin'` with `plugin: 'dsh-claude-compat'` — the same shape dsh core plugins (`@deepseek-ai/dsh-system-prompt`, `user-approval`) already write. Duplicate-injection detection updated to match the new identity.

## [0.8.1] — 2026-09-08

### Fixed
- **Resume crash (`Cannot read properties of undefined (reading '7')`)**: the rules `agent/pre-step` hook indexed `agent.session.events[seq]`, but dsh 0.1.2-rc.1's `Session` has no `events` property — the public API is `snapshotEvents()`. On a resumed session the surface is non-empty, so the first `.some()` callback threw and the turn failed with `code UNKNOWN`. The hook now takes one `snapshotEvents()` snapshot outside the loop and indexes that. New sessions were unaffected (empty surface short-circuits).

## [0.8.0] — 2026-08-19

### Added
- **Session lifecycle hooks**: `SessionStart` → `agent/session-start`, `SessionEnd` → `agent/disposed`. Fire-and-forget (best-effort, non-zero exit logged, never blocks the loop). Payload carries `session_id` + `cwd`.
- **`/cc-export`**: export DSH-native skills (`.dsh/skills`) into Claude Code `.claude/skills/<name>/SKILL.md` with frontmatter preserved; multiline values emit as YAML literal blocks. `list` / `export [--overwrite] [--target]`.

### Fixed
- **Session hook agent extraction**: DSH agent events dispatch `(carrier, name, payload)` and pass the agent on the **first argument** (`carrier.agent`), not the payload — verified in an isolated sandbox. Listeners now read `carrier?.agent ?? payload?.agent` (defensive against agent-loop's fused-payload path). Previously `session_id` was always `'unknown'`.

## [0.7.0] — 2026-08-18

### Added
- **`/cc-resume` — Claude Code session resume**: list conversations stored under `~/.claude/projects/<munged-cwd>/*.jsonl` (id, date, message count, first-user-text preview, imported marker) and import any session into DSH as a native event-sourced log (`~/.dsh/sessions/<munged>/session-cc-<id>/session.jsonl.zstd`). Imported sessions carry full user/assistant/tool history, a `cc: <preview>` title, and open/resume like any DSH session.
- Translation rules: main thread only (Claude sidechains skipped); thinking blocks dropped (DSH reasoning is stream-chunk based); tool_use/tool_result paired into DSH `tool/call`/`tool/result` events; `--limit-turns N` imports just the tail of huge sessions; idempotent re-import.
- **zstd frame layout fix**: the session header line is compressed as its own first zstd frame (DSH's `assertZstdHeaderFrame` requires it), events follow in a second frame — single-frame compression made DSH fail to parse imported logs.

- `scripts/cc-resume.mjs` is also a standalone CLI (`list` / `import <sessionId>`); requires the `zstd` binary on PATH.

### Known issues
- The `skill` tool may fail to resolve plugin-provided skills (`unknown or no longer available`) in agent-scoped runtime layers even though the catalog lists them — a DSH runtime layering behavior. `/cc-resume` degrades gracefully: run `scripts/cc-resume.mjs` directly (the skill body only wraps the CLI). **Resolved in 0.9.1 — root cause was this plugin's never-called inline closure, not DSH.**

## [0.6.2] — 2026-08-18

### Changed
- **Renamed built-in commands**: `/plugin` → `/cc-plugin`, `/reload-plugins` → `/reload-cc-plugins`. The generic `plugin` name was likely to collide with DSH-native or third-party skills; the `cc-` prefix marks Claude Code bridge commands unambiguously. `/reload-skills` kept as an alias.

### Added
- **`scripts/dsh-restart.sh`** — portable, safe DSH Web restart with idempotent terminal-bash prompt patch:
  - Zero hardcoded user paths: discovers `dsh` via PATH, nvm, or npx cache (`~/.npm/_npx/*/node_modules/.bin/dsh`).
  - Patches **every** installed copy of `dsh-terminal-bash` found on the machine.
  - Cross-platform `sed` (GNU/BSD), SIGKILL fallback, startup-crash detection, `--no-patch` mode, `DSH_RESTART_PORT` override.
  - Shipped in the npm tarball (`files`) and exposed as bin alias `dsh-claude-compat-restart`.
- README: built-in commands section, `enablePluginManager`/`pluginManagerRank` config entries, troubleshooting section (both languages).

## [0.6.1] — 2026-08-18

### Added
- **`/reload-plugins` (now `/reload-cc-plugins`) and `/reload-skills`** — hot catalog reload. Loading the reload skill definition IS the reload: `get()` calls the provider control's `invalidate()`, bumping the registry revision, dropping the collect cache, and broadcasting `skills/change` so the Web GUI skill picker refreshes **in the current session** — no restart, no new session needed. Plugin-shipped MCP servers still require a process restart.

## [0.6.0] — 2026-08-18

### Added
- **`/plugin` (now `/cc-plugin`) — full Claude Code plugin & marketplace management** as a user-invocable DSH skill:
  - Subcommands mirroring `claude plugin`: `list`, `install <name>[@marketplace]` (incl. one-shot `/cc-plugin name@market`), `uninstall`, `enable`, `disable`, `update [name]`, `search <term>`, `marketplace list|add|remove|update`.
  - Execution engine: **claude CLI first** (`claude plugin ...` when on PATH), **self-implemented fallback** (direct JSON + git manipulation) when unavailable — every mutated file gets a timestamped backup.
  - All state stays in Claude-native locations (`~/.claude/plugins/installed_plugins.json`, `known_marketplaces.json`, `~/.claude/settings.json` `enabledPlugins`) so Claude Code and DSH read the same truth; newly installed plugin content surfaces via the existing discovery bridge.
  - Hermetic test suite for the manager (`test/v060.test.js`); real-CLI lifecycle (install → disable → enable → uninstall) verified end-to-end against `claude` 2.1.220.

## [0.5.1] — 2026-08-17

### Fixed
- **MCP mount crash with non-promise plugin returns**: `ctx.plugin()` returns a Fiber (PromiseLike) in real cordis, but test/embedding environments may return plain objects. The `.catch` call is now guarded with a `typeof fiber.catch === 'function'` check instead of assumed. Also bumped the version to bust the pnpm tarball cache (same-name `.tgz` updates keep stale integrity checksums).

## [0.5.0] — 2026-08-17

### Added
- **Plugin marketplace support**: reads `~/.claude/plugins/installed_plugins.json` (v2 registry) and surfaces every installed plugin's skills, commands, and agents — manifest-driven (`.claude-plugin/plugin.json`) with a directory-scan fallback for manifest-less plugins. Content lands at rank `750` (the long tail: project `.claude`, DSH-native, and `~/.claude` all win same-name collisions).
- **Plugin MCP mounting (opt-in)**: `mcpServers` paths in plugin manifests translate to `dsh-mcp-client` instances when `enablePluginMcp: true`; `${CLAUDE_PLUGIN_ROOT}` is substituted with the install path.

## [0.4.0] — 2026-08-16

### Added
- **`.mcp.json` mounting**: project-root MCP server definitions translate to `dsh-mcp-client` plugin instances at DSH startup — `command` → stdio, `url` → streamable-http. Malformed entries or a missing client package degrade to a warning, never a crash. Read once per process from the launch workspace; restart to pick up edits.
- **Claude Code hooks subset**: `PreToolUse` → `tools/pre-execute` (exit 2 = deny), `PostToolUse` → `tools/post-execute` (exit 2 = block), `UserPromptSubmit` → `agent/pre-step`. Matchers support exact names, `*` wildcards, `|` alternation; commands run via `/bin/sh -c` with a Claude-style JSON payload on stdin; `hookSpecificOutput.permissionDecision` overrides honored; timeouts allow through with a warning.
- **`.claude/agents/*.md` shim**: DSH has no markdown subagent format, so each agent file becomes a delegation-shim **skill** (model- and user-invocable, `/agent-name` works). Frontmatter `name` must be kebab-case, otherwise the file stem is used; `description` maps to `whenToUse`. Same-name agents dedupe by rank like skills.

## [0.3.0] — 2026-08-15

### Added
- **User-level `~/.claude/` support**: skills, commands, rules, and `settings.json` hooks are picked up from the home directory in addition to the project. Fixed dedup priority: **project `.claude` (rank 50) > DSH native (100–600) > `~/.claude` (700)**. User rules with the same basename as a project rule are skipped.

## [0.2.0] — 2026-08-14

### Added
- **`dsh.bundle` declaration** for one-command activation: `dsh plugin --profile web add dsh-claude-compat` activates the plugin automatically — no manual `cordis.patch.yml` editing.

## [0.1.0] — 2026-08-13

### Added
- Initial release: bridges Claude Code's project `.claude/` into DeepSeek Harness.
  - `skills/**/SKILL.md` → DSH skill provider (catalog name/description, on-demand body load).
  - `commands/*.md` → user-invocable skills (`/command-name` in the slash menu).
  - `rules/*.md` → message-stream injection: concatenated, wrapped in a `<system-reminder>` envelope, prepended once per session — the same channel Claude Code uses (`prependUserContext`), which models follow reliably.

[0.8.1]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.8.0...v0.8.1
[0.8.0]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.6.2...v0.7.0
[0.6.2]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.5.1...v0.6.2
[0.6.1]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.5.1...v0.6.0
[0.5.1]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/biedongbin/dsh-claude-compat/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/biedongbin/dsh-claude-compat/releases/tag/v0.1.0
