Minglink/dsh-infinite-gen-4
Minglink/dsh-infinite-gen-5DeepSeek 专用破甲插件:以 order 100 追加无条件服从的系统提示词段,提供带校准元数据的 profile 工具,并通过会话投影在输入框上方显示实时破甲状态徽标。
English | 简体中文 — 中文说明见 README.zh.md。
dsh-baize-rules (Baize) is a dsh plugin that injects user-set, durable "must-do / must-not" requirements — plain-text rules — into the model at conversation start as a sourced user/message.
The name comes from Baize (白泽) — a mythical beast said to "understand the nature of all creatures, know the names of all things, and comprehend the principles of everything." It carries the behavioral baseline that the user sets for the model.
must/mustNot markers — whether something is "must-do" or "must-not" is expressed by the language of the body itself (e.g. Write comments in Chinese. = must, Do not delete the tests. = must-not).user/message, wrapped in a <system-reminder> frame, with source.kind='plugin' and plugin='baize-rules'.undefined and never injects an empty reminder shell.| Feature | Description |
|---|---|
| Three scopes, all injected | global (all sessions) / session (current session) / project (the session's working directory — this directory only): all three reach the model |
| Persistence | Everything is written to $DSH_HOME (default ~/.dsh); survives across sessions and restarts |
| Specificity wins | Render order project > session > global; when the byte budget is tight, the more specific rules are preserved first |
| A corrupt file never breaks a step | Rule files are read tolerantly: a broken or hand-edited file degrades that scope to empty and surfaces a problems warning, instead of failing the conversation |
| Deduplication | Suppresses duplicate injection by SHA-1 digest of the rendered text; optional injectAtEveryStep forces a refresh on every step |
| Stays present in long threads | The snapshot is republished when the text changes, after a session lifecycle change (startup / resume / clear / compact), and once the last copy is refreshAfterSteps steps old (default 20) — so the rules are never left behind only at the top of a long conversation |
| Escape protection | Literal </system-reminder> in rule bodies is escaped so user text can't close the plugin's frame |
| Command + API share the same source | The /baize-rules command and the front-end panel use the same store/core, so there is always a single source of truth |
| Tags | Rules can carry free-text tags (flow, #frontend) that the panel filters by. Panel-only by default — tags cost no model-context budget (injectTags opts in) |
| Template library | Save frequently used rules as templates (body + tags) and apply them to any scope; export/import the library as JSON to move it across machines and sessions. Applying a template makes a content copy — later template edits do not rewrite the rules you already added |
| Conflict-safe writes | Every write carries the file version it read; if another window changed the file in between, the write is refused with a retry hint instead of silently overwriting |
AGENTS.md instructionsdsh ships @deepseek-ai/dsh-agent-instructions, which loads the workspace instruction chain (AGENTS.md / CLAUDE.md). The two are complementary — pick by where the rule has to live:
AGENTS.md (built in) | dsh-baize-rules (this plugin) | |
|---|---|---|
| Where it lives | A file inside the repository | $DSH_HOME/rules/*.json, plus the panel |
| Reach | Everyone working in that repository | Your machine: all sessions, one session, or one directory |
| Version control | Committed, reviewed and shared with the team; travels with the repository | Outside the repository; travels with DSH_HOME |
| How you change it | Edit the file; the change lands on the next file touch or session resume | Edit it in the panel or with /baize-rules; the next step injects it |
| Best for | Project conventions that belong to the project | Personal, cross-project, or per-conversation requirements |
Both inject a sourced user/message framed with <system-reminder>, and both carry the same precedence sentence, so they reinforce rather than fight each other.
dsh plugins are distributed from npm and installed into a profile via
dsh plugin.
# Install from npm into the web profile (use the actual published version)
dsh plugin --profile web add dsh-baize-rules@0.2.4
pm2 restart dsh # Reload when dsh is managed by pm2
dsh --profile web
Peer dependencies (@deepseek-ai/*, react, etc.) are provided by the dsh profile; if any are missing, pnpm resolves them against peerDependencies in the profile directory.
# Remove the plugin from the profile
dsh plugin --profile web remove dsh-baize-rules
pm2 restart dsh # Reload when dsh is managed by pm2
If the entry lingers in the profile's dsh.profile.bundles, delete that line from
$DSH_HOME/profiles/web/package.json and restart dsh again. Your rule files under
$DSH_HOME/rules/ are not touched — delete them by hand if you want a clean slate.
Install into a separate profile so your currently running dsh stays unchanged:
dsh plugin --profile smoke add dsh-baize-rules@0.2.4
dsh --profile smoke --dump-config # read & compose the config only — does not boot dsh
--dump-config only composes and prints the tree; it does not start a server, so it is safe to run
alongside your live dsh. To actually try the plugin in that profile, run dsh --profile smoke.
Note on
pm2 restart dsh: it reloads the profile you are running. Install/remove only changes the profile on disk — nothing takes effect until the next boot (or that restart).
If you haven't published yet, or want to pick up source changes live, use this directory as a link dependency:
// /home/abc/.dsh/profiles/web/package.json
"dependencies": {
"dsh-baize-rules": "link:/home/abc/work/plugin/dsh-baize-rules"
}
Then run pnpm install in the profile directory and add dsh-baize-rules to dsh.profile.bundles.
/baize-rules # Same as /baize-rules list: show the currently active rules
/baize-rules add Write comments in Chinese. # Add to the default scope (usually session)
/baize-rules global add Don't delete or rewrite existing tests. # Explicitly add to global
/baize-rules list # Show project + session + global (abbreviated id / disabled markers)
/baize-rules list project # List one scope only
/baize-rules edit <id> Use pnpm to build only. # Edit a rule's text
/baize-rules disable <id> # Disable one (keep it, don't delete)
/baize-rules enable <id> # Re-enable
/baize-rules scope global # Subsequent commands default to global
/baize-rules clear session # Clear the current session's rules
/baize-rules global clear # Same thing: `clear <scope>` and `<scope> clear` are equivalent
/baize-rules export # Export all rules as JSON
# --- tags ---
/baize-rules tag <id> flow release # Tag a rule (idempotent, deduped)
/baize-rules untag <id> release # Drop one tag
# --- templates ---
/baize-rules save <id> # Store this rule as a template ("Save as template" in the panel)
/baize-rules tmpl list # List the template library (optionally by tag)
/baize-rules from <id|#tag> # Add rules from templates (#tag = all with that tag)
/baize-rules tmpl export ./templates.json # Export the library to a JSON file
/baize-rules tmpl import ./templates.json --yes # Import (dry run by default; --yes writes)
Scope goes before the verb. Write
/baize-rules global add <text>. A scope word at the end is no longer a modifier — in/baize-rules add Deploy after tests globalthe wordglobalis part of the rule text. See Scope syntax.
All subcommands live under /baize-rules; no argument is equivalent to list.
/baize-rules [<scope>] <command> # scope is global|session|project, written BEFORE the verb
/baize-rules [list [scope]|add <text>|remove <id>|edit <id> <text>|enable|disable <id>|tag|untag <id> <tag…>|save <id> [#tag…]|from <id|#tag>|tmpl <list|add|edit|rm|export|import>|scope <scope>|clear <scope>|export]
| Subcommand | Syntax | Purpose |
|---|---|---|
| list | /baize-rules list [scope] | List the merged active rules (Project/Global/Session sections; shows No active rules. when empty). Pass a scope to list that scope only (/baize-rules list project; <scope> list is equivalent) |
| add | /baize-rules add <text> | Append a rule to the target scope (default scope); the text is the rule |
| remove | /baize-rules remove <id> | Delete a rule (id or unique prefix) |
| edit | /baize-rules edit <id> <text> | Change a rule's text |
| enable | /baize-rules enable <id> | Enable a disabled rule |
| disable | /baize-rules disable <id> | Disable a rule (keep but not active) |
| tag | /baize-rules tag <id> <tag…> | Append tags to a rule (idempotent, deduped, case-insensitive) |
| untag | /baize-rules untag <id> <tag…> | Remove the named tags |
| save | /baize-rules save <id> [#tag…] | Store the rule as a template; merges tags if a same-text template exists |
| from | /baize-rules from <id|#tag> | Add rules from templates; #tag adds every template carrying that tag. The target scope comes from the leading scope keyword (or the default), never from a second argument |
| tmpl | /baize-rules tmpl <subcommand> | Template library management (below) |
| scope | /baize-rules scope <global|session|project> | Switch the default scope for subsequent commands (persistent for the current process). The scope is an argument here, so both /baize-rules scope global and /baize-rules global scope work |
| clear | /baize-rules clear <global|session|project> | Clear all rules in a scope. Both /baize-rules clear global and /baize-rules global clear work |
| export | /baize-rules export | Export { global, session, project } as JSON |
tmpl subcommands
| Subcommand | Syntax | Purpose |
|---|---|---|
| list | /baize-rules tmpl list [tag] | List templates (abbreviated id, tags, use count), optionally filtered by tag |
| add | /baize-rules tmpl add <text> [#tag…] | Create a template |
| edit | /baize-rules tmpl edit <id> [<text>] [#tag…] | Change body and/or tags; omit <text> to retag only |
| rm | /baize-rules tmpl rm <id> | Delete a template |
| export | /baize-rules tmpl export [<file>] | Export the library (prints JSON when no path is given) |
| import | /baize-rules tmpl import <file> [--merge|--replace] [--dry-run] [--yes] | Import a library (dry run by default; only --yes writes) |
Argument details
<text>: the rule body, may contain spaces. Whether it's "must-do" or "must-not" is expressed by the body's language; there is no marker.<id>: a stable rule id (crypto.randomUUID). list shows the first 8 characters as an abbreviated id for readability;
remove/edit/enable/disable/tag/untag/save all accept the full id or a unique prefix.
An ambiguous prefix reports ambiguous and lists the candidates — it never guesses. Same for tmpl rm/tmpl edit.<tag…>: free-text tags, space separated, # prefix optional (#flow ≡ flow). At most 8 tags per item,
24 characters each; dedupe and filtering are case-insensitive, while the stored spelling is what you first typed.from duplicate guard: a rule whose text already exists in the target scope is skipped
(Skipped N duplicate(s)), so re-adding from a template never piles up duplicates. A template that is
actually applied gets its use count +1.A scope modifier is written before the verb — this is the only form:
/baize-rules global add Write in Chinese./baize-rules project add Run the tests before every commit./baize-rules session remove <id>BREAKING (0.2.1): the trailing scope form was removed. A scope word at the end of the line is no longer a modifier — it is part of the argument.
/baize-rules add Deploy after tests globalnow stores the bodyDeploy after tests globalin the default scope. Before 0.2.1 the same line dropped the wordglobalfrom the body and wrote the rule intoglobal, silently truncating rule text and mis-targeting the write, so the trailing form was removed outright rather than kept as an alias.The verbs this applies to are
add/remove/edit/enable/disable/from. A scope word anywhere other than the first token is plain text:/baize-rules add Run global checks before release.stores that whole sentence as the body.fromlost its positional scope argument with the trailing form: write/baize-rules project from <id>(or set the default with/baize-rules scope project) instead of/baize-rules from <id> project.Trailing tag metadata is unchanged.
tag/untag/save/tmplstill take a trailing run of#tagtokens as metadata, not rule text. They take the scope in the prefix form only (/baize-rules global tag <id> frontend), so a tag literally namedproject/global/sessioncan never be swallowed as a scope.
Where the scope is an argument, not a modifier, both orders work — /baize-rules clear global ≡
/baize-rules global clear, /baize-rules scope project ≡ /baize-rules project scope, and
/baize-rules list project ≡ /baize-rules project list.
When no scope is given, the default set by /baize-rules scope is used (initially from Config.scope, usually session).
agent/pre-step (prepend:true) inserts the active rules as a user/message into the request, framed as <system-reminder> with source.kind='plugin', plugin='baize-rules', form='snapshot'.project > session > global, specific first. project rules are read from the session's working directory, so they join the view (and the model context) whenever the session declares a cwd; they are rendered under the header Project requirements (this directory only):.global rules are trimmed first.injectAtEveryStep:true forces a refresh on each step.agent/session-start (startup / resume / clear / compact) drops that session's record, so the next step publishes the full rules again — a compacted or cleared session cannot be left without them, which is the one case where "inject once at the start" could lose them for good.refreshAfterSteps steps: once the last published copy is that many steps old it is republished even when the text is byte-identical (default 20, 0 disables the periodic refresh). A step counter that restarts after compaction counts as stale as well. Because the message is a snapshot, the model still sees a single copy — this only moves it back to where the conversation is now.</system-reminder> in a body is escaped via escapeReminder.injectTags:false), so tagging or retagging a rule neither changes the text the model sees nor triggers a redundant re-injection.undefined (i.e. does not inject that message).problems entry. The step always completes; the warning is what tells you a file needs fixing.<system-reminder>
The following user requirements apply to every step of this conversation. Obey them.
More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.
Project requirements (this directory only):
- Run the tests before every commit.
Session requirements (this conversation only):
- Always test the plugin in isolation before deploying.
Global requirements:
- Write comments in Chinese.
- Do not delete or rewrite existing tests.
</system-reminder>
(The Project requirements (this directory only): section appears only when the session has a working directory and that directory has active rules.)
Config)On startup the plugin validates Config with @deepseek-ai/schemastery; an invalid value makes the plugin fail to load.
| Config | Default | Description |
|---|---|---|
scope | — (schema-required) | Default scope, used when /baize-rules doesn't specify one. Any of global/session/project — project included, resolved against the session's working directory |
maxBytes | — (schema-required) | Byte cap visible to the model; trimmed with specificity-wins when exceeded |
globalRulesPath | $DSH_HOME/rules/global.json | Override the global rules file path |
injectAtEveryStep | false | Force re-render on every step (debugging); default only patches on change |
refreshAfterSteps | 20 | Republish the snapshot once the last copy is this many steps old, even when nothing changed, so a long conversation never keeps the rules only at its top. 0 disables the periodic refresh |
injectTags | false | When true, render tags into the model context (- [flow,release] body). Off by default: tags are a human-facing classifier, and injecting them spends budget and adds noise |
apiOriginCheck | false | When true, the panel API accepts same-machine, same-origin requests only; off by default so a reverse-proxied web UI keeps working |
scopeandmaxBytesare required by the schema, not optional with an implicit fallback. Omitting either fails the plugin load with an explicit error naming the missing field — silently defaulting an undefined scope used to hide a broken configuration.
apiOriginCheck(defaultfalse). When switched on, the panel API answers only requests that come from this machine and, when the browser states anOrigin, from the host's own origin (or a localfile://page, which sends anullorigin); anything else is refused with403. It is off by default because the web UI is routinely reached through a reverse proxy, where the request arrives from the proxy's address instead of loopback and the panel would break. Turn it on when the port is reachable by others and no proxy layer authenticates the callers — the check exists because the global rule file reaches every conversation's prompt, so "anything that can reach the port" must not be enough. With the check off, authentication belongs to the transport (dsh's own token) and the proxy.
cordis.patch.yml)The published npm package ships dsh.bundle.patch, wired up automatically by dsh when you install dsh-baize-rules@<version>:
cordis.patch.yml inserts a single plugin line with default scope: session and maxBytes: 8192. To adjust the default scope / budget, change it there.
To mount by hand instead of relying on the bundle patch, insert the same row yourself — note the whole
mounted-rows list is inserted into the tree, and config mirrors the Config schema:
# $DSH_HOME/profiles/<profile>/cordis.patch.yml (e.g. ~/.dsh/profiles/web/cordis.patch.yml)
- insert:
- id: baize-rules
name: 'dsh-baize-rules'
config:
scope: session
maxBytes: 8192
After editing the profile's cordis.patch.yml, restart dsh (pm2 restart dsh when it is pm2-managed);
the profile file is read at boot.
| Scope | Storage | When written | Persistence |
|---|---|---|---|
| global | $DSH_HOME/rules/global.json | On any command / API submission | ✅ across restarts |
| session | $DSH_HOME/rules/sessions/<sessionId>.json | Same | ✅ across restarts |
| project | $DSH_HOME/rules/projects/<slug>.json (slug from the session cwd; no cwd → nothing is written) | Same | ✅ across restarts |
| templates | $DSH_HOME/rules/templates.json (global, not bound to any scope) | On template create/update/delete, import, or an apply that bumps the use count | ✅ across restarts |
$DSH_HOMEis resolved by@deepseek-ai/dsh-home-paths, default~/.dsh.Reads never throw. They go through
ctx.fs(resolve/stat/readText; writes auto-create directories). A missing file is simply an empty scope, and a corrupt or hand-edited file degrades to an empty scope plus aproblemsentry — the failure is reported (command output gains a⚠ …line, the panel shows a warning), never fatal to the step. Writes replace a whole file (there is no partial edit on disk), so a write is either the new complete file or nothing at all.Concurrent writes are guarded. Every write carries the freshness token of the file it read. If the file changed in between (another panel window, another command), the write is refused — the command reports a conflict and asks you to retry, the HTTP API answers
409— instead of silently overwriting the other edit.No empty files. Only the scopes that actually changed are rewritten, and an untouched empty scope is not written at all. (Older versions left a 3-byte
[]file per session under$DSH_HOME/rules/sessions/; those leftovers are harmless and can be deleted by hand — new ones are no longer created.)
projectrules are part of the model context. When the session declares a working directory, its project rules are read and injected underProject requirements (this directory only):, most specific first (project > session > global). The project file name is a slug of the session's cwd: the characters\ / : * ? " < > |become_, leading and trailing_are trimmed, and a cwd made entirely of those characters falls back to the literal_. With no session or nocwdthere is no project key, so nothing is written to disk (the HTTP API refuses such a write with400).