DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Baize Rules — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins

dsh-baize-rules

Baize Rules

User-set session/global must-do and must-not requirements injected at conversation start (Baize).

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add dsh-baize-rules@0.2.2
READMECompatibilityVersions

Compatibility and provenance

Baize Rules is published as dsh-baize-rules and currently resolves to version 0.2.2. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
npm
Registry updated
9/20/2026

Versions

0.2.2stable
9/20/2026
0.2.1stable
9/20/2026
0.2.0stable
9/15/2026
Show 5 more versionsCollapse versions
0.1.6stable
9/14/2026
0.1.5stable
9/11/2026
0.1.4stable
9/7/2026
0.1.3stable
9/4/2026
0.1.2stable
9/1/2026

Related plugins

Loading related plugins…

Latest
0.2.2
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
343.6 kB
Files
29
Surface
web
License
MIT
Source
npm
GitHub
★ 2
Weekly downloads
0
Last push
9/20/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in memory-context.

Memsearch Dsh@zilliz/memsearch-dshMemSearch plugin for DeepSeek Harness: shared markdown memory across agents, with capture, pre-step context injection, memory-recall skill, and a skill-candidate review panel.Reme@agentscope-ai/remeReMe client and memory integrations for TypeScript agentsStratagate Dshstratagate-dshRecent conversations stay vivid. Older ones fade into summaries, not oblivion. StrataGate gives DeepSeek Harness six-layer, time-decaying memory, while lasting events and relationships settle into a knowledge graph. Bring your memories from other AIs withMeow Memorymeow-memoryCross-session project memory for DeepSeek Harness: seven-layer SQLite memory, first-turn snapshot injection, per-message keyword hits, memory_remember/search/project tools, automatic reflection with reflection-fold UI, and idle-triggered dream consolidati

README

dsh-baize-rules

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.

The rules panel in the dsh web UI — the 规则 / 模板 panes, scope tabs (conversation / project / global), tag-chip filtering, per-row enable / save-as-template actions, and the active rules listed

  • Rules are plain text with no 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).
  • Injection happens at the start of a conversation: the currently active rules are injected into the model request as a persistent user/message, wrapped in a <system-reminder> frame, with source.kind='plugin' and plugin='baize-rules'.
  • No rules → no injection; if the byte budget shrinks such that all rules are cut, it returns undefined and never injects an empty reminder shell.

Features

FeatureDescription
Three scopes, all injectedglobal (all sessions) / session (current session) / project (the session's working directory — this directory only): all three reach the model
PersistenceEverything is written to $DSH_HOME (default ~/.dsh); survives across sessions and restarts
Specificity winsRender order project > session > global; when the byte budget is tight, the more specific rules are preserved first
A corrupt file never breaks a stepRule 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
DeduplicationSuppresses duplicate injection by SHA-1 digest of the rendered text; optional injectAtEveryStep forces a refresh on every step
Stays present in long threadsThe 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 protectionLiteral </system-reminder> in rule bodies is escaped so user text can't close the plugin's frame
Command + API share the same sourceThe /baize-rules command and the front-end panel use the same store/core, so there is always a single source of truth
TagsRules 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 librarySave 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 writesEvery 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

How this compares to AGENTS.md instructions

dsh 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 livesA file inside the repository$DSH_HOME/rules/*.json, plus the panel
ReachEveryone working in that repositoryYour machine: all sessions, one session, or one directory
Version controlCommitted, reviewed and shared with the team; travels with the repositoryOutside the repository; travels with DSH_HOME
How you change itEdit the file; the change lands on the next file touch or session resumeEdit it in the panel or with /baize-rules; the next step injects it
Best forProject conventions that belong to the projectPersonal, 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.


Installation

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.2
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.

Uninstall

# 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.

Try it without touching your running setup

Install into a separate profile so your currently running dsh stays unchanged:

dsh plugin --profile smoke add dsh-baize-rules@0.2.2
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).

Local development (link)

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.


Quick Start

/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 global the word global is part of the rule text. See Scope syntax.


Commands

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]

/baize-rules in the slash-command menu, described as "查看/增删改 会话或全局的 必须/禁止 要求,并管理可复用的规则模板"

SubcommandSyntaxPurpose
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 work

tmpl subcommands

SubcommandSyntaxPurpose
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.

Scope syntax

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 global now stores the body Deploy after tests global in the default scope. Before 0.2.1 the same line dropped the word global from the body and wrote the rule into global, 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. from lost 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/tmpl still take a trailing run of #tag tokens as metadata, not rule text. They take the scope in the prefix form only (/baize-rules global tag <id> frontend), so a tag literally named project/global/session can 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).


Injection behavior (how the model context changes)

  • Conversation-start baseline: at the start of a 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'.
  • All three scopes are injected: 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):.
  • Specificity wins: when the budget is tight the broader global rules are trimmed first.
  • Deduplication: a SHA-1 digest is computed over the rendered text; unchanged rules aren't re-injected. injectAtEveryStep:true forces a refresh on each step.
  • A fresh copy after a session lifecycle change: 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.
  • A fresh copy every 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.
  • Escape: a literal </system-reminder> in a body is escaped via escapeReminder.
  • Tags stay out of the model: tags are not injected by default (injectTags:false), so tagging or retagging a rule neither changes the text the model sees nor triggers a redundant re-injection.
  • Empty / fully trimmed: when there are no rules, or the budget cuts all of them, it returns undefined (i.e. does not inject that message).
  • A corrupt file never blocks the step: every read degrades instead of throwing — a broken file makes that scope read as empty and adds a problems entry. The step always completes; the warning is what tells you a file needs fixing.

What the model actually sees

<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.)


Configuration (Config)

On startup the plugin validates Config with @deepseek-ai/schemastery; an invalid value makes the plugin fail to load.

ConfigDefaultDescription
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.jsonOverride the global rules file path
injectAtEveryStepfalseForce re-render on every step (debugging); default only patches on change
refreshAfterSteps20Republish 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
injectTagsfalseWhen 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
apiOriginChecktruePanel API accepts same-machine, same-origin requests only; disable when a reverse proxy fronts the web UI

scope and maxBytes are 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 (default true). The panel API answers only requests that come from this machine and, when the browser states an Origin, from the host's own origin (or a local file:// page, which sends a null origin). Anything else is refused with 403. The check exists because the global rule file reaches every conversation's prompt, so "anything that can reach the port" must not be enough. Turn it off when the web UI is reached through a reverse proxy or from another host — such a request either arrives from a non-loopback address or carries an Origin that is not the host's own, and would be refused. With the check disabled, authentication belongs to the proxy layer.

Mount metadata (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.


Data location

ScopeStorageWhen writtenPersistence
global$DSH_HOME/rules/global.jsonOn any command / API submission✅ across restarts
session$DSH_HOME/rules/sessions/<sessionId>.jsonSame✅ 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_HOME is 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 a problems entry — 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.)

project rules are part of the model context. When the session declares a working directory, its project rules are read and injected under Project 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 no cwd there is no project key, so nothing is written to disk (the HTTP API refuses such a write with 400).


Client panel (optional)

The published package also exposes a dsh web client panel (lib/client.js; see the ./client entry in package.json exports), talking to the same store/core as the command through the host HTTP API /baize-rules.api.

Where the panel lives

The panel registers itself into two dsh client slots (ctx.slots.inject('sidebar.footer.action') and ctx.slots.inject('conversation.view')), so there are three ways in and they all show the same data:

  • Sidebar footer → 「规则」 button — always available, opens (or focuses) the panel.
  • In-session 「规则」 tab — when a conversation is open, the panel is a tab in that conversation's view ring.
  • New-chat page — with no session yet there is no tab ring, so the button opens a full-viewport overlay instead.

The panel has two panes:

  • Rules: scope switch (conversation / project / global), tag-chip filtering, and per-row Edit / Save as template / Remove. Each row shows whether the rule is enabled or disabled and toggles it in one click (no need to fall back to /baize-rules disable). New rules are created through the structured rule.add op, so the text you typed is never re-parsed as a command line and can never lose a trailing word to a scope keyword; when a submission fails the input is left as typed so nothing has to be retyped. Warnings returned in problems are shown in the panel, and the UI is keyboard- and screen-reader-accessible (tab role/aria state, modal-dialog semantics with focus management, and aria-pressed on the tag chips).
  • Templates: create/edit (body + tags)/delete, Add to rules (pick a scope and apply), Export (download JSON) and Import (choose a file → dry-run preview → confirm).

Templates are content copies. A rule row's Save as template button (「存为模板」 — this is also referred to as 「加入模板」, "add to template"; it is the same button) stores a snapshot of that rule's body

  • tags in the template library. Adding a template to a scope creates a new rule with that content: the two are independent afterwards, so later edits to the template do not rewrite the rules you already added, and editing a rule does not change the template it came from.

A round trip through the panel: rule row Save as template → switch to the Templates pane and edit the body and tags there → back on the Rules pane open From template and tick the entries you want → choose the scope and add them.

Scope availability: the panel only offers session/project when it is attached to a conversation — opened from the new-chat page (no session yet) those two buttons are disabled with a hint and only global rules can be edited, which avoids the old behaviour where a rule looked added and then vanished.

The project directory does not come from the panel: the host resolves the session's cwd itself (resolveProject), so project rules are available whenever you are in a conversation.

API:

  • GET /baize-rules.api?sessionId=…&project=… → { global, session, project, templates, problems }

  • POST /baize-rules.api, body { sessionId, project, op, … } → { ok, text, view, templates, problems, … }

    • op: 'raw' (the default, and what older panels send) { raw, scope } — one command line through the same core as /baize-rules
    • rules: rule.add { scope, text }, rule.update, rule.setTags, rule.setEnabled { scope, ruleId, enabled }, rule.saveAsTemplate, rule.addFromTemplates
    • templates: template.create, template.update, template.delete, template.export, template.import
    • every op returns the post-change view, templates and problems, so one call refreshes both panes and surfaces any unreadable store
  • Failure semantics (the response body always carries text for a refusal, except 500 which carries { error }):

    StatusMeaning
    400The request was refused: malformed JSON body, an empty raw, a missing ruleId/text, or an edit to a scope this request has no storage key for (no session, no cwd)
    403Origin refused — the request is not from this machine (a non-loopback source), or the browser states an Origin that is neither the host's own nor a local page (cross-site). Behind a reverse proxy or when the UI is reached from another host, set apiOriginCheck: false and let the proxy authenticate
    405Method other than GET/POST
    409Concurrency conflict — the file changed since it was read, so nothing was written; reload and retry
    413Request body larger than 1 MiB
    500

Module structure

src/rules.ts      Pure logic: Rule/RuleTemplate models + tag normalization + render/<system-reminder>/byte budget(specificity-wins)/digest/escapeReminder
src/core.ts       Pure logic: parseCommand/runCommand/scope resolution/CRUD/template library ops/template import-export (zero deps, unit-testable without dsh)
src/store.ts      Pure logic: global/session/project rule files + templates.json persistence; tolerant reads (empty scope + `problems`) and version-guarded writes (ctx.fs + dshHomePath)
src/command.ts    Thin dsh adapter: feed view/templates/defaultScope → core, persist nextView/nextTemplates; file IO for tmpl export|import; appends `⚠ …` warnings
src/index.ts      apply: agent/pre-step injection + /baize-rules command registration + API mount (inject: agents/commands/fs/webServer/sessions)
src/api.ts        Host HTTP API: GET + POST(op dispatch) /baize-rules.api (origin check, 1 MiB body cap, 409 on a lost write race) for the front-end panel
src/invariant.ts  dsh-invariants contract companion (name/inject/apply)
scripts/dev-render.ts  Loop 0 demo
test/*.spec.ts    rules/core/composition/regression tests
cordis.patch.yml  Mount metadata (inserts the baize-rules plugin line + default config)

Public entry points (see package.json exports): . (index), ./invariant, ./client, ./src/*.


Development & instant feedback

pnpm dev:render            # Print the <system-reminder> text the model will actually see (supports budget args)
pnpm test                  # Run unit + REAL-composition tests, then the client smoke test
pnpm test:watch            # Re-run on save
pnpm test:client           # Just the client-panel smoke test (node test/client.smoke.mjs)
pnpm build                 # tsc -p tsconfig.build.json → lib/
pnpm typecheck             # tsc --noEmit
pnpm check:exports         # Validate every path promised by package.json `exports`

Change the pure functions in src/rules.ts (rendering) or src/core.ts (command decisions), then re-run pnpm dev:render to see the change — the fastest feedback loop (sub-second, without touching dsh).


Publishing

Releases are driven from a v* tag, but the publish itself is a local command. The publish job in .github/workflows/ci.yml requires the NPM_TOKEN secret, which is not configured in this repository — so it fails with npm error code ENEEDAUTH on every tag (verified on v0.1.3, v0.1.4 and v0.1.5). Until that secret is set, npm publish --access public is the step that actually ships a version.

# 1. Bump the version: update `version` in package.json + the install example in both READMEs
# 2. Verify locally
pnpm build && pnpm typecheck && pnpm test
# 3. Commit, tag, and push both (the tag run still gives you the CI build+test gate)
git add -A && git commit -m "release: vX.Y.Z"
git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin main && git push origin vX.Y.Z
# 4. Publish — this is the step that actually ships
npm publish --access public

The publish job runs on v* tags and needs the test job to pass; it will keep reporting failure until NPM_TOKEN is added to the repository secrets.

Credentials. npm publish authenticates with your own npm credentials — either the ones already in ~/.npmrc or ones supplied through environment variables. The repository-root .npmrc is listed in .gitignore and must stay there: never commit a token, and never paste one into an issue, a README, or a CI log.


Changelog

See CHANGELOG.md.

License

MIT

/baize-rules global scope
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 exportExport { global, session, project } as JSON
Unexpected server-side failure, reported as { error }