Skip to content

Releases

v1.4.0

Choose a tag to compare

@KSF1216 KSF1216 released this 28 Sep 14:14

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 Config whose six fields are all .volatile(); values live in
    the active profile's cordis.patch.yml. script still 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's plugins.item list slot. Saving is ONE atomic mutate carrying only the changed fields and the
    revision the card read, so a concurrent edit is refused instead of half-applied.
  • package.json declares peerDependencies: { "@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_HOME and port; the user's
    ~/.dsh is untouched) and checks that no host entry is parked or failed, that settings/describe
    really serves this namespace, and that the served client bundle evaluates with our module registered.
  • test:plugin gained two guards: every name in the client inject list 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

Choose a tag to compare

@KSF1216 KSF1216 released this 26 Sep 03:58

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), so 1.3.1 carries it instead and 1.3.0 is
permanently retired.

Added

  • A fourth switch on the settings card: "Windows script file types" (fileTypes: off / warn / block, default warn).
    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 / .psm1 whose 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 .ps1 as ANSI — the measured error is The string is missing the terminator);
    a .cmd / .bat holding non-ASCII bytes, or LF but no CRLF, only warns (measured: it still runs, but on
    another machine it turns into mojibake and goto jumps 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; block only ever blocks .ps1, and .cmd always only warns.
  • SKILL.md gained 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.md gained 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 .cmd makes goto and set /p misbehave.
  • 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 both ctx.logger.warn and
    console.error — failures always speak up.
  • No source renames for users: the hooks.json hook protocol and the CLI flags are unchanged; the only thing
    renamed in this release is on the maintainer side (this project's card guard tools\docs.mjs → tools\cards.mjs,
    see below). User-facing commands and APIs are unaffected.

Fixed

  • The comments in scripts/build-codepage.ps1 held 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:codepage verifies that those five characters really are not in cp20936.
  • A hole in the shipped-file scan is closed: it used to walk only the files whitelist in package.json, while
    package.json itself 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 (excluding node_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 to an LLM output guard at the same time (252 / 255 characters, same meaning).
  • The shipped list was missing two files that npm run needs (caught the first time test:tarball was ever run,
    2026-09-20): cantonese-allow.json (the Cantonese axis of test:repo reads it; not shipped → 135 false FAILs) and
    tools\cards.mjs (the entry point of test: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 new scripts\tarball-selftest.mjs), and each has its own guard: scripts\tarball-selftest.mjs (which
    really unpacks the tarball and runs npm test inside it) plus a static check in test:api (every npm run entry
    point must be in the files whitelist).

Tests

  • test:api went 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 .ps1 is pure ASCII and every .cmd / .bat is 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 (the build-codepage.ps1 above) —
      not a synthetic case.
    • Three regression tests: package.json must be inside the leak scan's coverage; the npm-description example in
      PUBLISHING.md must be character-for-character identical to the description in package.json (measured:
      that example was missing its last sentence, and nothing could tell at the time); and every npm run entry 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 → one git add -A would 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,083 each 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: changing README.md's 2,637 to 2,636 → red; cutting one phrase from japanese-only.json
      (123 → 122) → red, listing all five files. It also caught three mistakes in the hand-written mapping table
      itself
      .
  • New npm run test:tarball (scripts\tarball-selftest.mjs): pack → unpack → run npm test again 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 of npm 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 / read does not trigger, .cmd with
      block still 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 following fileTypes.
    • 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) plus React.Component, and it collects the expected log lines into an array before asserting
      (stderr 0 lines). Both layers were verified by breaking them on purpose: removing the boundary → red;
      making apply'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 ▾.

Documentation

  • The README is now English-first, with the Chinese version in its own file (2026-09-26): README.md was
    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 to README.zh.md (which is listed in the files whitelist now too. It always
    shipped anyway
    : npm always adds a root README*, so that line only writes the existing fact down — the package
    page itself still renders README.md only), 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.json gained 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 carry simplified-example / check-ok — without them the three axes of
    test:repo go red.
  • README.md's write-guard section gained these two file-type rules and their defaults;
    references/integration.md gained the fileTypes configuration, the three deliberate design choices, and
    two gaps that are still open (the hooks.json route 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) and installDocs (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...
Read more

1.2.0 — 輸出把關、執行時編碼風險、出貨守門

Choose a tag to compare

@KSF1216 KSF1216 released this 19 Sep 07:15

1.2.0 — 輸出把關、執行時編碼風險、出貨守門

新增

  • examples/llm-proxy/llm-guard-proxy.mjs:本機 LLM 的輸出守衛代理(零依賴,
    npm script llm-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:api 58 → 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 會把編不出來的字換成 ?,得到「全部都編得出來」的假通過。