Repository navigation
Releases: Dantwz7/dsh-doc-router
Release list
v0.6.3
A stdout-contract fix. No routing verdict changed.
pymupdf 2.0 pulls in pymupdf-layout and an ONNX runtime, and those print banners and progress lines to stdout. The plugin spawns lib/docprobe.py and parses its stdout as exactly one JSON object, so that noise made every conversion fail with:
Unexpected token '=', "=== Docume"... is not valid JSON
main() now saves the real stdout, points sys.stdout at stderr for the whole run, and writes its own payload through the saved handle. Third-party chatter can no longer reach the channel the plugin parses.
Changed
lib/docprobe.pyisolates library stdout: the plugin's JSON goes to the real stdout, everything else goes to stderr.
Verified against pymupdf 2.0 in an isolated venv
npm test— 102/102 passnpm run fixtures— self-check passes, every fixture routes as expectedroute,markdown, missing-file and--pagespaths all emit clean JSON- Layout detection still reports
columns: "multi-column",column_estimate: 2
Install: npm install dsh-doc-router@0.6.3
v0.6.2
An install-command fix. No code changed and no verdict changed.
pymupdf4llm 2.0 is an empty placeholder. Upstream moved the 4llm code into pymupdf 2.0 itself, and the shim exists only so that upgrading from pymupdf 1.x does not break pip. So pip install pymupdf4llm now installs nothing usable: import pymupdf fails, layout detection silently degrades to "format known, layout unknown", and the CI fixture step fails outright.
Every install hint now reads pip install -U pymupdf pymupdf4llm. Both names are needed because pymupdf 2.0 requires Python 3.11-3.15 while this plugin still supports 3.9: on 3.11+ the pair resolves to pymupdf 2.0 (which ships 4llm) plus the empty shim, and on 3.9/3.10 it resolves to pymupdf 1.x plus the real pymupdf4llm 1.x. One command covers both eras.
Note that pymupdf 2.0 also pulls pymupdf-layout, onnxruntime and numpy, so a fresh install is considerably larger than it used to be.
Changed
README.md,README.zh.md,CONTRIBUTING.md,.github/workflows/ci.yml,tests/fixtures/generate.pyand the runtimePYMUPDF_HINTinlib/docprobe.pyall give the two-package install command.
Install: npm install dsh-doc-router@0.6.2
v0.6.1
A compatibility-range fix. No code changed and no verdict changed.
engines.dsh and the @deepseek-ai/dsh-tools peer declared >=0.2.0-rc.1 <0.3.0-0. Under node-semver a prerelease version satisfies a range only when some comparator carries a prerelease and shares its [major, minor, patch] tuple. That range therefore accepts 0.2.1 while silently excluding every prerelease of 0.2.1 - including 0.2.1-rc.3, the DSH version this plugin was developed and verified on. Both fields now enumerate the tested line:
>=0.2.0-rc.1 <0.3.0-0 || >=0.2.1-rc.1 <0.3.0-0
0.2.2-rc.x stays excluded until it is tested. The range is a claim about what was run, not a switch, so it is widened only as far as the evidence goes.
Changed
engines.dshand the@deepseek-ai/dsh-toolspeer now accept the0.2.1prerelease line.CONTRIBUTING.mdsection 5 records the prerelease-tuple rule, so the next peer range is not written as a single open-ended range.
Install: npm install dsh-doc-router@0.6.1
v0.6.0
[0.6.0] — 2026-10-04
A contract-and-coverage release. Routing verdicts are unchanged — the full
71-paper corpus re-runs to the same per-file table — but the places where the
plugin could quietly paper over a broken contract now fail loudly, and the format
branches that had no fixture are covered.
Added
scripts/check-readme-facts.mjs, wired intoverify:docsand CI. The
READMEs' per-fixture verdict table was the one claimcheck-doc-links.mjsnever
checked: renaming a fixture or moving a column estimate updated one side and
silently rotted the other. The script parsesEXPECTEDout of
tests/fixtures/generate.pywith Node only (thedocsCI job installs no
Python) and asserts, for both READMEs, that every named fixture exists in
EXPECTED, thatest N/估 N 栏equals itscolumn_estimate, and that the
pipeline cell names exactly itsrecommendtokens. It found a real gap on its
first run:sample.txt,sample.csv,sample.jsonandsample.htmlwere
fixtures but had never been listed inEXPECTED.scripts/validate-routing.py, the corpus validator the READMEs referenced but
the repository never contained. Both of them told readers to run
_verify/validate_routing.py— a maintainer-only path that was never committed —
so the "re-runnable in one command" claim could not be followed from a clone. The
script now ships atscripts/validate-routing.py, takes the corpus root as an
argument (the papers cannot be redistributed), imports the samelib/docprobe.py
the tool uses, and is read-only on the corpus. Run against the recorded 71-PDF
library it reproduces the manifest file for file: 71 files, 0 diffs.- Nineteen fixtures for format branches that had no coverage, generated
targetedly rather than by re-runninggenerate.py(a full run rewrites every
binary, because PyMuPDF stamps each PDF with a random/IDand ZIP entries
carry timestamps):sample.tsv/sample.xml/sample.ipynb(the
_TEXT_EXTmappings),sample.md/sample-noext(plain text), the
UTF-16/UTF-32 pairs with and without a BOM,sample-empty.txt,
nul-bytes.bin/random-binary.bin(the control-character guard),
magic-jpeg.jpg/magic-gif.gif/magic-webp.webp,sample.odt/
sample.epub(the two_sniff_zipbranches that had never executed, not
even in CI) andsample.mp3. - UTF-16 and UTF-32 plain text are now recognised. The sniffer only ever tried
UTF-8, so a UTF-16 file fell intounknown— whose advice ("try it, then use
human judgement") is actively misleading for a file that is simply text.
sniff_formatnow checks the UTF-8/UTF-16/UTF-32 BOMs and, with none, tries
utf-8 → utf-16-le → utf-16-be. The order is load-bearing: a UTF-32LE BOM
(FF FE 00 00) begins with the UTF-16LE BOM (FF FE).
Changed
doc_routethrows when the probe returns norecommend, instead of guessing.
It used to fall back to['markitdown'], which dressed a broken contract up as a
normal result — in the one direction the asymmetric bet forbids. The error names
the probe script so the failure is diagnosable, and a test asserts that nothing
is returned in that case.pdf_markdown's non-PDF refusal followsnoteLanguage. The probe has always
produced a Chinese and an English note, but this one message was hardcoded
English, so azhsession got a mixed-language result.- The
doc-routingskill body followsnoteLanguage. The most important text
the model reads was a fixed Chinese template whilenoteLanguagedefaulted to
en. Description,whenToUseand content are now each available in both
languages and selected together, so an English session no longer receives a
Chinese skill. The English body is asserted to contain no CJK. - CI's
integrationjob now runs on Windows as well as Linux. The unit job
already covered Windows, but it uses local stubs and never starts Python —
interpreter resolution, non-ASCII paths and backslash separators had zero CI
coverage on the platform the plugin is developed on. The interpreter is resolved
withsys.executable, because Windows has nopython3and a barepythoncan
be the Microsoft Store alias.
Fixed
tests/manual/real-loader.mjsno longer resolves nothing when
DSH_ASAR_DSH_ROOTis set without a trailing slash.createRequirereads such
a path as a file and looks fornode_modulesone level up, where
@deepseek-ai/dsh-toolsdoes not exist; every lookup came back empty and the only
symptom was anERR_MODULE_NOT_FOUNDfor the plugin's own import. Both spellings
now work, so the override documented intests/manual/README.mdis safe to use
verbatim.
Notes
- The UTF-16/UTF-32 detection is a heuristic, not a proof. A binary that
happens to be valid UTF-16 and carries few control characters can still slip
through;nul-bytes.binpins the guard's direct case andrandom-binary.binthe
other. Both directions are asserted, not every possible input. - Still unverified: the CI matrix needs a push to GitHub to be confirmed —
locally only the YAML and the Windows$env:GITHUB_ENVstep were exercised. As
before, how npm renders> [!NOTE]alerts and<details>remains unknown
(npmjs.com returns HTTP 403), and the npm package page's handling of relative
tarball links is unverified.