@chaggle/dsh-powershell-check
A native DeepSeek Harness plugin that gates every pwsh tool call against the PowerShell pitfalls documented in the blog post PowerShell 实战踩坑大全 (GBK console mojibake, $var: parsing, PS 5.1 ternary, double-quoted variable expansion, JS escaping, Start-Process quoting and sandbox traps, -FeatureName arrays, DISM verbs, RestoreHealth source versions, CDN downloads, npm.cmd suffix, &&/|| chains), and bundles the powershell-check skill.
Installable as a profile plugin: dsh plugin --profile <name> add @chaggle/dsh-powershell-check (or mount the row directly — see [Install](#install)).
Features
- Automatic gate — subscribes to the official
tools/pre-executeinterception point; everypwshcall is statically checked before execution. Blocking violations (R2–R11) return adenywhose reason IS the fix guidance; the advisory R1 passes through.warnmode only logs. - Bundled skill — exposes
powershell-checkthroughctx.skills.registerProvider(thedsh-skill-badgepattern), visible and loadable in every session catalog. - Bilingual — rule text, CLI output (
--lang en|zh), deny reasons, and docs ship in English and Simplified Chinese. - Single rule source — the rules engine (
src/checker.ts) is shared by the gate and the CLI; update once, both follow. - Self-test —
--selftestruns a 51-case positive/negative battery (R1–R17, AI-generated-script focused).
Install
As a profile plugin (recommended)
The package declares dsh.bundle.patch and ships its own cordis.patch.yml, so it is installable per profile:
dsh plugin --profile <name> add @chaggle/dsh-powershell-checkor add the row to your profile layer $DSH_HOME/profiles/<name>/cordis.patch.yml:
- insert:
- id: dsh-powershell-check
name: @chaggle/dsh-powershell-check
config:
mode: deny # deny | warn
lang: zh # zh | enIf the harness was launched from a checkout, either publish the package or point the row at a local clone:
git clone https://github.com/chaggle/dsh-powershell-check.git
# then junction/symlink it into $DSH_HOME/profiles/node_modules/@chaggle/dsh-powershell-checkUser patch layers are watched: the change hot-applies to a running dsh web instance (transactional HMR) without a restart.
Skill only (no gate)
The repository root is a skill bundle (SKILL.md + scripts/); clone it into any skill root:
git clone https://github.com/chaggle/dsh-powershell-check.git "$HOME/.dsh/skills/powershell-check"Configuration
| Key | Default | Meaning |
|---|---|---|
mode | deny | deny blocks pwsh calls with blocking violations; warn logs and allows |
lang | zh | Language of deny reasons and warn logs: zh or en |
analyzer | builtin | builtin: bundled R1–R17 rules only. psscriptanalyzer: additionally deep-checks every pwsh command with the official PSScriptAnalyzer (install the module on the host; degrades to builtin when missing). Error/ParseError findings deny, warnings are logged |
CLI
node scripts/check-pwsh.mjs -- "command text" [--lang en]
Get-Content fix.ps1 -Raw | node scripts/check-pwsh.mjs - [--lang en]
node scripts/check-pwsh.mjs --selftestExit codes: 0 = PASS, 1 = FAIL (violations listed with fixes), 2 = usage error.
Rules
| Rule | Level | Detects | Blog section | | --- | --- | --- | --- | | R1 | advisory | wsl/Windows feature queries without chcp 65001 | §1-1-3 | | R2 | blocking | $var: parsed as drive-qualified syntax | §1-1 | | R3 | blocking | ) ? ... ternary shape (PS 5.1) | §1-2 | | R4 | blocking | $WORD followed by ./` inside double quotes (path trap) | §1-1-1 | | R5 | blocking | Start-Process wrapping an external command | §1-2-1 | | R6 | blocking | -FeatureName A, B array form | §1-1-1-1 | | R7 | blocking | /Dismount-Image verb | §1-1-1-2 | | R8 | blocking | RestoreHealth with a newer source | §1-1-1-3 | | R9 | blocking | curl -L combined with -C - | §1-2-4 | | R10 | blocking | bare npm/npx/pnpm without the .cmd suffix | §1-2-2 | | R11 | blocking | && / || chains (PS 5.1) | §1-3 | | R12 | advisory | ConvertTo-Json without -Depth (default 2 truncates nested data) | §5-1 | | R13 | advisory | $_ inside a foreach ($x in ...) loop | §5-2 | | R14 | blocking | single = as a comparison inside if/while | §1-4 | | R15 | advisory | PS 7+ only syntax (-AsHashtable/-Parallel/-AsByteStream/??/?.) | §1-5 | | R16 | advisory | cmd-style commands / %VAR% env syntax | §1-2-3 | | R17 | advisory | Write-Host` output bypasses the pipeline | §1-1-4 |
PSScriptAnalyzer deep check
Set analyzer: psscriptanalyzer to add the official PowerShell static analyzer on top of the builtin rules. The plugin runs Invoke-ScriptAnalyzer against the command text (as a temp .ps1) through the same ctx.shell seam the harness hooks bridges use; the probe raises the process-scope execution policy to Bypass first (the harness starts pwsh under Restricted). Findings with severity Error/ParseError deny the call with the analyzer messages; warnings are logged and allowed.
# one-time, on the host (PowerShell 5.1 or 7):
Install-Module PSScriptAnalyzer -Scope CurrentUser -ForceTrade-offs: each deep check spawns an analyzer pass (module load ~1–3 s), so enable it only when the extra coverage is worth the latency; when the module is absent the plugin logs once and falls back to the builtin rules. Empirical notes from this project: PSSA does not flag &&/|| on a 5.1 host (the builtin R11 covers that) and serializes Severity as a numeric enum (the parser handles both forms).
How it works (official extension points)
The harness extension surface is its typed interception points: a "native hook" is an ordinary Cordis plugin subscribing to canonical lifecycle events and returning typed decisions — no external hooks bridge, no hook/* log, no subprocess boundary. This plugin uses two entries:
1. ctx.on(tools/pre-execute, (exec, next) => PreToolDecision) — the pre-execution waterfall gate; 2. ctx.skills.registerProvider(...) — contributes the bundled skill to the registry.
Model Experience
Request context and condition
#### What the model sees
Nothing is injected into prompts by this plugin. The skill is exposed through the standard session skill catalog (powershell-check, description above) and can be loaded with the skill tool. When the gate denies a pwsh call, the model sees the deny reason in the tool error result — that reason is the fix guidance generated by formatHits.
#### Token effect
Zero direct token effect outside tool-error feedback: no prompt text is added or rewritten. The deny reason replaces a would-be tool result with a bounded error payload.
#### KV Cache effect
The plugin publishes no system-reminder or catalog text of its own; the skill description rides the session catalog produced by the skill consumer, so prompt-prefix reuse is unaffected. Deny reasons are per-call error results and do not change the request prefix.
Known Limitations and Deferred Work
- Static heuristics — rules are pattern-based; R1 is advisory by design and R4 may flag intended variable expansion (the fix text says when to ignore).
- Live reload — code changes require the running harness to re-import the plugin (user-patch rows hot-reload; the module cache reloads on row replacement).
- Rule count — R10/R11 cover the ExecutionPolicy and
&&/||traps; new pitfalls should be added tosrc/checker.tswith selftest cases, then mirrored in the blog post.
License
MIT — see [LICENSE](LICENSE).