# dsh-task-guard [![Tag](https://img.shields.io/github/v/tag/MCviseron/dsh-task-guard?label=tag)](https://github.com/MCviseron/dsh-task-guard/tags) [![CI](https://img.shields.io/github/actions/workflow/status/MCviseron/dsh-task-guard/ci.yml?branch=main&label=CI)](https://github.com/MCviseron/dsh-task-guard/actions) [![License](https://img.shields.io/github/license/MCviseron/dsh-task-guard)](./LICENSE) A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) plugin that keeps a `todo_write` plan moving with the agent's work. It fixes the common failure where the model completes one stage, forgets to mark the next stage `in_progress`, and eventually stops updating the todo list at all. > GitHub: [MCviseron/dsh-task-guard](https://github.com/MCviseron/dsh-task-guard) · npm package: `dsh-task-guard` English | [简体中文](./README.md) ## Contents - [Why](#why) - [What it does](#what-it-does) - [Install](#install) - [Configuration](#configuration) - [What the model sees](#what-the-model-sees) - [Compatibility](#compatibility) - [Verification](#verification) - [Development](#development) - [Known limits](#known-limits) - [License](#license) ## Why DSH's `todo_write` is a **whole-list replacement**. After finishing stage A, a model often sends: ```json [ { "content": "Stage A", "status": "completed" }, { "content": "Stage B", "status": "pending" } ] ``` The built-in tool only enforces "at most one `in_progress`"; it does not enforce "while work remains, at least one task is `in_progress`". The write is accepted silently, the UI shows no active task, and the model may never write the list again. `dsh-task-guard` turns that implicit contract into a **collapsible plugin notice**, and re-anchors a stale plan after a few tool calls. ## What it does The plugin **does not modify the system prompt** and has no resident prompt section. It only acts at two observable moments: 1. **Stage-boundary notice** When a `todo_write` list still has `pending` items but no `in_progress` item, the plugin appends a notice after the tool result, asking the model to mark the next stage `in_progress` (or explicitly finish the list). By default, the write itself is **not** turned into an error. 2. **Stale-plan notice** After `staleAfterCalls` successful non-`todo_write` tool calls without a todo update, and while unfinished items remain, the plugin appends a notice to the following tool result. It can repeat every `repeatEveryCalls` calls. At most one notice is added per step, even for parallel calls. Notices use DSH's official `additionalContexts` channel with `source.kind = 'plugin:task-guard'` and `form = 'notice'` (the session-format-v4 producer-owned source kind). They are collapsible and consume no resident prompt space. ### What it does not do - It does not register a new tool or replace `todo_write`. - It does not rewrite the model's todo list (tool results cannot reliably prove a stage is complete). - It does not access the network, read credentials, or write to user directories. - It does not modify the system prompt or inject system messages. If you want harder enforcement, enable `rejectMissingActive: true` or `blockAfterCalls: N` explicitly. ## Install ### Local link ```powershell dsh plugin --profile desktop add "link:" ``` `dsh plugin` writes the package into the profile's `dependencies` and `dsh.profile.bundles`; the package's `cordis.patch.yml` inserts `id: task-guard`. **Restart DSH** after installing (the bundle list is read at startup). Manual profile setup: 1. Add to `package.json` `dependencies`: ```json "dsh-task-guard": "link:" ``` On Windows, for example: `link:D:/Program/DSH-Plugin-Class/dsh-task-fix`. 2. Add `"dsh-task-guard"` to `dsh.profile.bundles`. 3. Run `pnpm install` in the profile directory, then restart DSH. ### From npm (after publishing) ```powershell dsh plugin --profile desktop add dsh-task-guard ``` > The npm package has not been published yet. See [docs/release.md](./docs/release.md) for the first publish, account-suspension recovery, and Trusted Publisher setup. The command above becomes available after publishing. ## Configuration All fields are optional. Invalid values fail at plugin load instead of silently degrading. | Field | Default | Meaning | |---|---|---| | `enabled` | `true` | Master switch. | | `locale` | `zh` | Language of model-facing text: `zh` / `en`. | | `staleAfterCalls` | `5` | Successful non-`todo_write` tool calls before the first stale notice. | | `repeatEveryCalls` | `4` | Calls between later notices; `0` sends the first notice only. | | `blockAfterCalls` | `0` | Hard-stop the next tool call after this many stale calls; `0` disables blocking. Must be greater than `staleAfterCalls`. | | `rejectMissingActive` | `false` | When `true`, reject a `todo_write` that leaves pending work with no `in_progress` task; default only sends a notice. | | `sourceKind` | `auto` | Notice source kind: `auto` reads `session.header.version`; force `v3` / `v4` if needed. | | `include` | `[]` | Only these tool names advance the counter (`*` wildcard supported); empty means all. | | `exclude` | `todo_write`, `ask_user_question` | Tool names that never advance the counter. | | `maxListItems` | `6` | Maximum remaining items quoted in a notice. | Example: ```yaml - id: task-guard name: dsh-task-guard config: enabled: true locale: en staleAfterCalls: 5 repeatEveryCalls: 4 blockAfterCalls: 0 rejectMissingActive: false sourceKind: auto maxListItems: 6 ``` ### Recommended modes - **Conservative (default):** immediate stage-boundary notice + a stale notice every 4 calls; never blocks a tool. - **Strong enforcement:** `blockAfterCalls: 12`. After 12 successful calls without a todo update, the next tool call is denied with a clear "call `todo_write` first" error; it recovers after the update. - **Strict validation:** `rejectMissingActive: true`. Every `todo_write` that leaves pending work with no active task is rejected. This can interrupt a legitimate plan write, so it is off by default. - **Notify once:** `repeatEveryCalls: 0`. ## What the model sees Stage-boundary notice: ```text [Todo sync] A task was marked completed, but 2 task(s) remain pending and no task is in_progress. Mark the next task in_progress before continuing, or clear the list / mark everything completed if this work is finished. ``` Stale-plan notice: ```text [Todo sync] 6 tool calls have run since the last todo_write. In progress: Stage B Remaining: [~] Stage B [ ] Stage C If the current phase is not finished yet, ignore this reminder. The moment it is finished, call todo_write: mark it completed and the next task in_progress (never leave pending work with no active task). ``` Strict-mode tool error: ```text todo_write rejected: 2 task(s) are still pending but no task is in_progress. While work remains, keep exactly one task in_progress. Resend the COMPLETE list with the next task you are about to work on marked in_progress, and finished tasks marked completed. ``` ## Compatibility | Item | Status | |---|---| | DSH `0.2.0-rc.2` / session format v4 | ✅ Historical live verification | | DSH `0.2.1-rc.2` / session format v4 | ✅ Current desktop; package-type audit + live smoke (both notice paths) | | DSH `0.1.5-rc.2` / format v3 | ✅ `sourceKind: auto` selects the v3 source | | Node.js | `^22.19.0 || >=24` | | Runtime dependencies | None (Node built-ins only) | `sourceKind: auto` (default) reads `session.header.version`: `< 4` uses the v3 shape, `>= 4` uses v4. You can force `v3` / `v4`. v3: ```js { kind: 'plugin', plugin: 'task-guard', form: 'notice', summary: '...' } ``` v4: ```js { kind: 'plugin:task-guard', form: 'notice', summary: '...' } ``` Using the old shape on v4 raises: ```text format v4 message requires a producer-owned source kind ``` See [docs/compatibility.md](./docs/compatibility.md) for the full audit. ## Verification ```powershell cd pnpm check ``` - `typecheck` + `build` pass; - 18 tests pass (real Cordis context, hook waterfalls, v3/v4 source kind, patch/config consistency); - live tests: - a `todo_write` with only `pending` succeeds and receives a stage-boundary notice; - after switching to `in_progress`, five tool calls produce a stale-plan notice; - neither run raises `format v4` errors; - in a real three-stage task, both notice types drove the next-stage update. See [VERIFICATION.md](./VERIFICATION.md) for details. ## Development ```powershell pnpm install pnpm typecheck pnpm build pnpm test pnpm check ``` Project layout: ```text src/index.ts plugin entry, hooks, config, notice construction test/ node:test + real Cordis context tests cordis.patch.yml bundle defaults build.mjs esbuild build for lib/index.js .github/workflows/ CI ``` - No runtime dependencies; `lib/` is generated by `pnpm build` / `prepare` and is not committed. - When changing a hook or the source kind, update `CHANGELOG.md` and record verification in `VERIFICATION.md`. ## Known limits - Tool results cannot prove a stage is complete; `blockAfterCalls` is a heuristic fallback and is off by default. - If the model never calls `todo_write`, the plugin can only remind or block; it cannot invent completion state. - Notices are model-visible context and stay in session history; low thresholds cost more tokens. - The official projection clears the todo list at `turn/start`; a plan spanning turns must be rewritten by the model. - This release targets session format v4; v3 runtimes need a separate compatibility build. ## Contributing and security - [CONTRIBUTING.md](./CONTRIBUTING.md) - [SECURITY.md](./SECURITY.md) - [docs/release.md](./docs/release.md) (maintainer release process) - [CHANGELOG.md](./CHANGELOG.md) - [LICENSE](./LICENSE) (MIT)