Repository navigation
Releases
Releases · KSF1216/chinese-script-policy
v1.4.0
1.4.0 — ported to the DSH 0.1.7 settings API
Requires DSH >= 0.1.7. DSH 0.1.7 removed
ctx.settings.installSection()and the client-side
ctx.settingsScope. A plugin still using them stays pending on the client, and the browser's
boot audit THROWS on any parked client entry - which takes the whole Web GUI down (on the host it is
only a warning, but the write guard then never runs, silently). This release ports the plugin.
Fixed
- Host half now declares a schemastery
Configwhose six fields are all.volatile(); values live in
the active profile'scordis.patch.yml.scriptstill accepts the old boolean spelling, so a profile
written by 1.0-1.1 keeps loading. - Browser half now uses
ctx.configForms(namespace = profile entry id) and registers into the Plugins
page'splugins.itemlist slot. Saving is ONE atomicmutatecarrying only the changed fields and the
revision the card read, so a concurrent edit is refused instead of half-applied. package.jsondeclarespeerDependencies: { "@deepseek-ai/schemastery": "~3.18.4" }: a bundle
installed by directory link (dsh plugin add <path>) resolves the harness's own copy through that
declaration.
For maintainers
- New
npm run test:boot: boots a throwaway DSH instance (its own$DSH_HOMEand port; the user's
~/.dshis untouched) and checks that no host entry is parked or failed, thatsettings/describe
really serves this namespace, and that the served client bundle evaluates with our module registered. test:plugingained two guards: every name in the clientinjectlist must exist in a pinned set of
this DSH's client services (that guard is this bug), and the volatile contract (schema defaults equal
the guard's defaults, the legacy boolean still validates, undeclared values are refused).
1.3.1 — write-time guard for Windows script file types
1.3.1 — write-time guard for Windows script file types
This release is "everything since 1.2.0". It was prepared as
1.3.0, but that version number was
never published (the registry stayed on 1.2.0), so1.3.1carries it instead and1.3.0is
permanently retired.
Added
- A fourth switch on the settings card: "Windows script file types" (
fileTypes:off/warn/block, defaultwarn).
This is not a Chinese axis — the switches above look at the Chinese inside the content; this one looks at
the type of file being written. The rule is deliberately narrow: a.ps1/.psm1whose content holds
any non-ASCII byte is blocked (it really does break: every writer here writes "UTF-8 without a BOM", while
Windows PowerShell 5.1 reads.ps1as ANSI — the measured error isThe string is missing the terminator);
a.cmd/.batholding non-ASCII bytes, or LF but no CRLF, only warns (measured: it still runs, but on
another machine it turns into mojibake andgotojumps to the wrong label). It looks at writes only and never
scans existing files (someone else's UTF-16 or BOM-prefixed script is untouched), it is fail-open, and pure
ASCII never triggers it;blockonly ever blocks.ps1, and.cmdalways only warns. SKILL.mdgained a "file-type traps (Windows)" section: the single source for the rule (two tables plus three
byte-level checks, why it is not "write first, convert later", and why a BOM is not the answer).
references/encoding.mdgained the cause and the measurements: a five-encoding comparison of the same script
(only "UTF-8 without a BOM" breaks — even the UTF-16 that PowerShell ISE writes by default runs), and why a
bare-LF.cmdmakesgotoandset /pmisbehave.- Two layers of protection on the settings card (
lib/client.js):apply()is wrapped in try/catch, so when a
host service is broken only that one card disappears instead of the error travelling up (measured on another
DSH plugin: the whole plugin tree failed to load); and the card is wrapped in an error boundary that prints
"this settings card failed to load" in place, together with the underlying message, instead of leaving a blank
(a blank looks exactly like "this plugin has no such page"). The message goes to bothctx.logger.warnand
console.error— failures always speak up. - No source renames for users: the
hooks.jsonhook protocol and the CLI flags are unchanged; the only thing
renamed in this release is on the maintainer side (this project's card guardtools\docs.mjs→tools\cards.mjs,
see below). User-facing commands and APIs are unaffected.
Fixed
- The comments in
scripts/build-codepage.ps1held 15 non-ASCII bytes (five common Traditional characters), so
the script was violating the very rule it demonstrates. They are now Unicode code points (pure ASCII), and
test:codepageverifies that those five characters really are not in cp20936. - A hole in the shipped-file scan is closed: it used to walk only the
fileswhitelist inpackage.json, while
package.jsonitself ships without being in its own whitelist — so the file that is read on every install had
never been scanned (measured: its description held a string equal to a local folder name, and that is how it
leaked). It now scans every text file in the repo (excludingnode_modules/.git/.ship-deny.txt), and a
regression test — "leak scan covers package.json" — pins that hole shut. The npm description had that one sentence
changed toan LLM output guardat the same time (252 / 255 characters, same meaning). - The shipped list was missing two files that
npm runneeds (caught the first timetest:tarballwas ever run,
2026-09-20):cantonese-allow.json(the Cantonese axis oftest:reporeads it; not shipped → 135 false FAILs) and
tools\cards.mjs(the entry point oftest:cards; not shipped →MODULE_NOT_FOUND). Both are the
"always fine in the checkout, broken in what the user receives" kind; both ship now (58 → 61 files: those two
plus the newscripts\tarball-selftest.mjs), and each has its own guard:scripts\tarball-selftest.mjs(which
really unpacks the tarball and runsnpm testinside it) plus a static check intest:api(everynpm runentry
point must be in thefileswhitelist).
Tests
test:apiwent from 61 to 81 checks:- "the repo's own byte rules" — walk the repo (skipping
node_modules/.git), read the raw bytes and
confirm every.ps1is pure ASCII and every.cmd/.batis pure ASCII with CRLF, after first proving that
both detectors really can go red (reading the files as text hides the difference, which is why this one has to
work at the byte level). It caught a real violation the first time it ran (thebuild-codepage.ps1above) —
not a synthetic case. - Three regression tests:
package.jsonmust be inside the leak scan's coverage; the npm-description example in
PUBLISHING.mdmust be character-for-character identical to thedescriptioninpackage.json(measured:
that example was missing its last sentence, and nothing could tell at the time); and everynpm runentry point
must be in the whitelist (that is the two missing files above). - Anything the scan skips must also be gitignored: the scan and git are two separate protections, and when each
assumes the other will catch it, that is exactly when it goes wrong (measured:.board/held absolute paths, the
scan skipped it, and it was not ignored → onegit add -Awould have leaked it). - Counts written in prose are machine-checked (11 rows): the expected values are computed from the data
files (never copied from the prose — a copy would satisfy itself and never go red), they are checked
file by file (49,257…3,083each carry their own list), and only numbers large enough to be worth
searching for are pinned (the small ones became "shape of the data" assertions). Both ways of breaking it were
measured: changingREADME.md's2,637to2,636→ red; cutting one phrase fromjapanese-only.json
(123→122) → red, listing all five files. It also caught three mistakes in the hand-written mapping table
itself.
- "the repo's own byte rules" — walk the repo (skipping
- New
npm run test:tarball(scripts\tarball-selftest.mjs): pack → unpack → runnpm testagain inside the
unpacked package, and assert that no maintainer file is in the tarball (PUBLISHING.md, the cards,
.ship-deny.txt,tools\cards.config.mjs). It caught something genuinely broken the first time it ran. It is
deliberately not part ofnpm test(it packs and unpacks, about 15 seconds) but sits in the pre-publish checklist
(§1). test:plugin's settings card went from 24 to 34 checks:- 14 behavioural cases for
inspectFileType(pure ASCII passes,.md/readdoes not trigger,.cmdwith
blockstill only warns, CRLF passes while bare LF triggers), 3 mount cases on a real cordis waterfall, and the
card's fourth radio group plus its collapsed header line followingfileTypes. - Both layers have their own tests:
apply()must not let an error escape when a host service is broken, and
it must speak up; when the card render throws, the boundary must draw the reason and the underlying message.
The test side gained a mini renderer (one that calls function / class components and runs boundaries in
React's order) plusReact.Component, and it collects the expected log lines into an array before asserting
(stderr0 lines). Both layers were verified by breaking them on purpose: removing the boundary → red;
makingapply's catch rethrow → red. - Both routes for the header icon are pinned: when the host provides
@deepseek-ai/dsh-client-ui-primitives(a virtual module, supplied by the web frontend's bundle, like
react) it draws the icon, and when it does not it draws the text▾.
- 14 behavioural cases for
Documentation
- The README is now English-first, with the Chinese version in its own file (2026-09-26):
README.mdwas
rewritten in English (section for section, the same content as the original Chinese, keeping the headline numbers
2,637/3,083/367/123), and that is the file the npm package page and the GitHub front page show;
the original Chinese text moved toREADME.zh.md(which is listed in thefileswhitelist now too. It always
shipped anyway: npm always adds a rootREADME*, so that line only writes the existing fact down — the package
page itself still rendersREADME.mdonly), and both files gained a link row at the top pointing at each other;
the pack list therefore went from 61 to 62 files.cantonese-allow.jsongained one entry for
**/README.zh.md(the Chinese version quotes Cantonese examples the same way). The lines that deliberately show
Simplified or Japanese examples still carrysimplified-example/check-ok— without them the three axes of
test:repogo red. README.md's write-guard section gained these two file-type rules and their defaults;
references/integration.mdgained thefileTypesconfiguration, the three deliberate design choices, and
two gaps that are still open (thehooks.jsonroute does not have this rule, and the CLI has no
--file-types).- Maintainer side: this project's card guard was renamed (
tools\docs.mjs→tools\cards.mjs,
docs.config.mjs→cards.config.mjs,test:docs→test:cards, the old names still work);VERIFICATION.md
(round-by-round evidence) andinstallDocs(the red light for install docs and fallback) were added.
None of that ships — the only shipped file that changed along with it is `scripts\api-selft...
1.2.0 — 輸出把關、執行時編碼風險、出貨守門
1.2.0 — 輸出把關、執行時編碼風險、出貨守門
新增
examples/llm-proxy/llm-guard-proxy.mjs:本機 LLM 的輸出守衛代理(零依賴,
npm scriptllm-guard-proxy)。llama-server 沒有外掛機制,--logit-bias只是降低機率、
表達不了詞組,--grammar對散文不實用,所以機械層放在伺服器外面:代理整個 origin
(內建網頁照用)、非串流、只讀生成端點的 JSON、帶tool_calls的回應絕不改寫
(所以不要指給 agent harness),乾淨的回應逐位元組通過,轉不動的殘留在 log 裡回報。
用的是同一份guardInspect/toTraditional。--console-hazard:第三條軸(執行時編碼風險)。找「會印到 console 的行」裡
目標 codepage 編不出來的字元,計入 exit code。檔案可以是正確繁體卻在執行時崩潰——
實測案例是一個 ComfyUI 節點 print 含簡體字的訊息,Windows 主控台 cp950 丟
UnicodeEncodeError,節點掛掉、連 traceback 都寫不進日誌(症狀看起來像「卡住」)。
只綁print/echo/logging/logger/log/console/raise(註解與變數名不算),
並在輸出明說這是啟發式判斷。
--codepage內建 950/936/932/1252/20936;54936(GB18030)與 65001(UTF-8)
編得下全部 Unicode,永不回報。⚠️ 命名陷阱:Windows 把 936 叫gb2312,但它其實是 GBK
(連繁體都編得出來);嚴格 GB2312-80 是 20936,它反過來編不出體 軟 淨 麵 裡。- 設定卡的標題列改成跟著設定變(原本寫死「簡體專有字」,切到「要求簡體」時與同卡的
單選標籤自相矛盾——折疊起來時那是唯一看得到的字)。
修正
- 出貨檔案不再含本機資訊:新增出貨掃描守門(在
test:api內),照package.json的
files白名單逐一掃。通用樣式隨套件出貨;只在本機成立的名稱放在不進版控的
.ship-deny.txt——把名字寫進會出貨的檔案裡就是把它公開,哪怕只是為了禁止它。
測試
test:codepage(41 項)釘住 codepage 涵蓋表的量測數字;test:proxy(30 項)用真的
upstream + 真的監聽中的代理測邊界;test:api58 → 61 項;--console-hazard的 8 個
CLI 案例進selftest-cases.json。
移除
chinese-script-policy@1.1.0與1.1.1自 npm 移除(都在發布後 72 小時內,依官方政策可
unpublish);1.0.0更早已移除。三個版號永久保留給已下載過的安裝來源,不會再被使用。
移除的理由是「registry 上只留最新版」,不是外洩:兩份舊 tarball 都經掃描確認不含任何
本機資訊(各 52 檔、沒有.ship-deny.txt、私人名稱命中 0)。
registry 上現在只有1.2.0(dist-tags.latest不變)。
資料
scripts/codepage-repertoire.json(182 KB)由npm run build:codepage以 .NET 產生
(Node 沒有這些編碼器)。encoder 與 decoder 的 fallback 必須是ExceptionFallback,
否則 .NET 會把編不出來的字換成?,得到「全部都編得出來」的假通過。