Standalone DeepSeek Harness plugin repository (dsh-permission-rules). Development follows the dsh-plugin-guide skill and the official plugin contract; this file records repo-local decisions.
src/index.ts— function-plugin contract (name/inject/Config/apply; NO default export — the Loader unwrapsexports.default ?? exports). Injectscommands+tools.src/config.ts— Schemastery schema + explicitresolveConfig(no hidden?? defaultinrun()paths); closed enums and boolean flags are validated at resolution so plain-JS mounts fail loud too.src/glob.ts— strict glob→RegExp compiler + the backtracking guards:maxGlobStarscaps unbounded star expansions, and regex-mode patterns reject nested unbounded quantifiers and quantified overlapping literal alternations. Bad patterns throw at compile time (load), never silently match nothing.src/rules.ts— the pure core: YAML document validation, pattern compilation (incl.!patternnegation), first-match evaluation, theagentsidentity dimension (main/subagent/preset:<name>selectors against session-header candidates; unknown identity never matches — fail closed), nested path-candidate extraction,when/absentdimensions, chain merging (compileRulesChain), shadow detection (findUnreachableRules). No fs/clock/process state.src/prose.ts—/rulesoutput vocabulary in five languages (en/zhreference,es/pt/hicommunity) +describeRuledimension tokens (incl. thesrcsource-attribution token). Rulereasons are never translated.src/events.ts—permissionRules/decisionSessionEventMap member (declaration merging, incl. theoutcomefield) +AuditAppend, the append surface that requests the envelope'signorable: truemarker.src/runtime.ts—tools/pre-executelistener, per-cwd rule-chain loading (project chain by cwd /searchUpwalk → fallback → empty),permissionRules/decisionaudit,/rulescommand (list | reload | decisions [n] | test [flags incl. --platform]), Chokidar watch (LRU cache with resolved/case-folded keys, watcher reconciliation, timer cleanup, candidate watches on expected-but-absent rule files so mid-session creation is adopted). Registers itself asctx.permissionRulesRuntime.test/— vitest; realContext+ realSession/Commands/ApprovalServicefrom the0.1.1-rc.2peers; chokidar mocked with a fake EventEmitter; the dsh-auto-review integration uses its tarball with a scripted reviewer mock.docs/rules-format.md(+.en.md) — the rule file schema and the 5-rule security baseline;docs/rules-format.schema.jsonis the machine-readable schema for editor completion.scripts/repair-session-logs.mjs— one-off repair for session logs written before the marker: rewrites targeted audit rows to carryignorable: true(frame-preserving zstd rewrite, backups,scan/repair/--dry-runmodes), or removes them outright instripmode for harness lines that refuse marked plugin events (the0.1.2-alphaline).scripts/check-readme-sync.mjs— five-language README sync gate (section structure, config-table keys,/rulescommand docs); wired into CI..github/ISSUE_TEMPLATE/*+.github/PULL_REQUEST_TEMPLATE.md+SECURITY.md— structured issue forms (bug/feature), PR checklist, and the private vulnerability-reporting policy.
- Waterfall listener (
tools/pre-execute) always callsnext()unless it claims the call withdeny/ask. Anallowhit is NEVER short-circuited. Underenforce: false(dry-run) even deny/ask hits delegate — the record keeps the would-be action withdryRun: trueplus the real downstreamoutcome. - Model-visible ⟺ logged: the only model-visible plugin content is the deny/ask reason materialized by the tools registry into the tool result; the
permissionRules/decisionaudit event carries the samecallIdand reason for reconstruction, and itsoutcomerecords the FINAL pre-execute decision (an allow hit followed by a downstream deny is logged as denied). - Log-only audit:
permissionRules/decisionis never injected into the model context, and is appended with{ ignorable: true }via theAuditAppendsurface. The0.1.0-rc.8+ line stamps the marker; the0.1.0-rc.1–rc.7and0.1.1-rc.1–rc.7lines silently drop it, and the0.1.2-alphaline refuses plugin events on read even when marked — so the runtime detects unsafe hosts BEFORE the first append (peer-version pre-check, then a probe of the appended envelope's return value) and disables session-log audit with a one-time warning —allowUnmarkedAudit: trueopts back in, andscripts/repair-session-logs.mjsrepairs already-polluted logs (itsstripmode removes audit rows where the marker cannot help). - Loud misconfiguration: invalid YAML, unknown fields/actions, bad globs/regexes, backtracking-prone patterns, and rule counts over
maxRulesfail the load (badFilePolicychooses fail vs ignore-with-warning). Deployment-level files (absoluterulesFile,fallbackPath) fail the mount.searchUp+ absoluterulesFilefailsresolveConfig. - Backtracking bounds: a compiled glob's degree equals its star count —
maxGlobStars(default 2) caps it exactly; regex mode rejects nested unbounded quantifiers and quantified overlapping literal alternations, while independent quantifier chains stay allowed (documented escape hatch). - Agent identity is derived only from the session header (
origin: 'subagent',agentPreset):main/subagent/preset:<name>candidates. Unknown identity (agentless or header-less) yields no candidates, soagents-scoped rules fail closed and never match an unidentified caller. - Watch failures warn only: a bad HMR reload keeps the previous rules and never crashes the process.
- Expected-but-absent rule files (project file not in effect, deleted fallback) are watched through their deepest existing ancestor directory — chokidar cannot reliably watch a missing path whose parent is also missing — so a file created mid-session is adopted without a manual reload. Under
searchUponly the immediate cwd-level candidate is watched; deeper ancestors need/rules reload. The per-workspace cache key isresolve(cwd)case-folded on Windows, so differently-spelled paths to one workspace share one entry and one watcher set. - No reviewer subagents, no model calls, no OS-sandbox changes —
askends at the official approval seam; the answerer role belongs todsh-auto-review.
- Five-language READMEs (
README.md,README.zh.md,README.es.md,README.pt.md,README.hi.md) — keep all five in sync; the English file is the source of truth.scripts/check-readme-sync.mjs(CI) enforces section structure, config-table keys, and/rulescommand docs. docs/rules-format.mdis the Chinese reference for the rule vocabulary;docs/rules-format.en.mdis its English twin — update both together, plusdocs/rules-format.schema.jsonwhenever the vocabulary changes.- When the repo is published on GitHub, set topics
dsh,dsh-plugin,deepseek-harness,deepseek,cordis,permission,approval,ai-safety(the ecosystem's visibility channel is thedsh-plugintopic; see dsh-plugin-guide §9). - License is Apache-2.0 (
LICENSE+ the package.jsonlicensefield). - Community engineering: GitHub Discussions enabled (welcome post in Announcements);
mainbranch protection requires all sixgatesmatrix check statuses (osubuntu-latest/macos-latest/windows-latest× node22/24, strict off), allows force pushes, and does NOT require PR reviews — maintainer direct pushes stay available, PRs with a red CI cannot merge. The About homepage points at the npm package page and topics mirrorpackage.jsonkeywords.
typescript + tsdown are regular dependencies on purpose: pnpm does not install devDependencies of git-hosted packages, and the git channel's prepare must build with production dependencies alone. scripts/prepare.mjs is the single build entry; it runs tsdown FIRST, then tsc declarations into lib/types — tsdown's clean: true wipes lib/, so the reverse order would delete the fresh declarations.
The repo's pnpm-workspace.yaml declares allowBuilds: { esbuild: true }: pnpm's isolated prepare env for git-hosted packages reads the dependency's shipped workspace file, and without that entry both local installs and git installs fail with ERR_PNPM_IGNORED_BUILDS on esbuild's (harmless platform-binary validation) postinstall. The package.json pnpm field is NOT usable for this — pnpm 11 ignores it. Git users still need the single allowBuilds key for dsh-permission-rules itself, which the dsh CLI prints verbatim.
pnpm run typecheck && pnpm run lint && pnpm test && pnpm run test:coverage && pnpm run build && pnpm pack && node scripts/check-readme-sync.mjs.
test/integration.spec.ts imports dsh-auto-review from vendor/dsh-auto-review-0.6.0.tgz — a COMMITTED build artifact of the sibling repo (regenerate with pnpm --dir ../dsh-auto-review pack --pack-destination <this repo>/vendor). It must stay in the tree because the git install channel's isolated prepare installs devDependencies and would fail on a missing file: target. The shipped tarball carries runtime JS without .d.ts, so tsconfig.test.json maps the package name onto test/auto-review.d.ts for types while runtime resolution loads the real bundle.