Every packages/*/* README now carries a canonical '## Known Limitations and
Deferred Work' section: condensed, evidence-backed bullets for consumer-visible
gaps (unimplemented features, platform caveats, MVP cuts) and consciously
postponed work (TODO/FIXME/XXX markers, RFC deferrals still open). The ten
pre-existing ad-hoc variants ('What is NOT here (TODO)', 'Deferred',
'Limitations (MVP)', 'Known limitations (tracked TODOs)', ...) are normalized
into the canonical heading.
A new doc-sync gate, scripts/verify-readme-limitations.ts, enforces the shape:
exactly one limitations-like heading per package README, byte-equal to the
canonical h2, with at least one bullet; near-miss headings fail so variants
cannot creep back. Packages with genuinely nothing to declare (dsh-brand,
dsh-timeout, dsh-subagent-mock, dsh-app-boot) are whitelisted in the script and
must NOT carry the section; whitelist entries are validated against the scanned
package set so a rename fails loud.
Wired into the doc-sync chain (package.json) and the run-gates doc-sync leaf
set; the standing rule lands in packages/AGENTS.md and the adding-a-package
cookbook; decision record in
docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md
(RFC index regenerated).
Also fixes two stale '(deferred)' markers claiming dsh-compact-basic is
unimplemented (the dsh-compact seam README's package table and the seam's
module doc comment).
@deepseek-ai/dsh-fs-local
The local-filesystem implementation of the ctx.fs provider seam (@deepseek-ai/dsh-fs). Backs the seven FileSystem primitives with the host filesystem; loading it as a plugin populates ctx.fs.
import { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'
await ctx.plugin(LocalFileSystem, { cwd: process.cwd() })
// ctx.fs is now the local backend; load @deepseek-ai/dsh-fs-policy for the
// freshness policy gate and @deepseek-ai/dsh-tool-fs to expose read/write/edit.
Behavior
resolve(path, opts?)— a relativepathresolves againstopts.cwdwhen the caller supplies one (the model-facing tools pass the calling agent's session cwd — see the per-session cwd RFC), elseconfig.cwd(defaultprocess.cwd()); an absolutepathignores both. ThetargetKeyis the file'srealpath, so two input paths reaching the same file through symlinks share one identity, and writes/edits land on the link target (preserving the link). A not-yet-existing path uses the realpathed parent directory plus basename when the parent exists; only an unresolvable parent falls back to the absolute path.displayPathis the absolute (un-resolved) path.stat— returnsFsInfo(version=mtimeMs:size,typeoffile/directory/other, bytesize) orundefinedwhen the target is absent.readText/streamText— UTF-8 only.readTextreads the whole file;streamTextstreams it in chunks (cross-chunk decoding) so a huge file never has to be held whole in memory. Both reject invalid UTF-8 and NUL-byte binary samples (FS_NOT_TEXT) and non-regular targets. Thereadtool (@deepseek-ai/dsh-tool-fs) decides which to call by size and owns the line windowing.listDir— lists one directory level in stablename.localeCompare()order. Each entry carries the child basename, type, resolved child target (displayPathunder the listed directory,targetKeyas the realpath identity), and cheap stat metadata (version, plussizefor regular files). It never opens or decodes file contents. Missing targets reportFS_NOT_FOUND, file/special-file targets reportFS_NOT_DIRECTORY, aborted calls reportFS_ABORTED, permission failures reportFS_PERMISSION_DENIED, and other listing or child metadata I/O failures reportFS_IO_ERROR. Broken/disappeared children are returned asotherwithout metadata, but permission/IO failures while resolving a child fail the whole listing with a structuredFsError.writeText— atomic: writes to a temp file opened exclusively (wx,0o600) inside a randomly-named private staging dir (0o700) next to the target, fsyncs, then renames over the target. An existing file's mode is preserved, while new files default to0o600. Theexpectedguard is OPTIONAL: omitting it unconditionally creates-or-overwrites;createIfAbsentcreates a missing target and rejects an existing one (FS_NOT_OBSERVED);replaceIfVersionreplaces only at the observed version (a missing target or mismatch isFS_STALE_VERSION).editText— atomic literal read-modify-write over the same primitive, serialized per target by a mutation lock. Theexpectedguard is OPTIONAL: when supplied it verifies the version BEFORE literal matching (a stale edit reportsFS_STALE_VERSION, neverFS_EDIT_NOT_FOUND/FS_AMBIGUOUS_EDITagainst newer content); omitting it edits the current content unconditionally. A missing target reportsFS_STALE_VERSIONeither way. LF-normalizes for matching, restores the file's dominant CRLF/LF style, and rejects emptyoldString/ zero matches (FS_EDIT_NOT_FOUND) or ambiguous multi-matches withoutreplace_all(FS_AMBIGUOUS_EDIT).
The raw I/O lives in src/fsio.ts (Cordis-free, independently unit-tested); src/index.ts is the thin service wiring.
Known Limitations and Deferred Work
config.cwdis not a sandbox — it is a resolution default, not containment: absolute paths and..escape it. Enforce containment with a stricterctx.fsbackend or a permission plugin on thetools/executewaterfall (capability-seam RFC).- An overwrite reads the whole prior file into memory — solely as the UI diff basis; bounding that pre-read above a size threshold is deferred (
TODO(overwrite-diff-bound)). - Version tokens are
mtimeMs:size— an external change that preserves both within the filesystem's timestamp granularity defeats the stale guard. editTextholds the whole file (plus the edited copy) in memory — streaming exists only on the read path.- Binary detection is asymmetric — reads NUL-sample only the first 8192 bytes while edits scan the whole buffer, so a file with a late NUL reads fine but rejects edits.
- The per-target mutation lock is in-process only — a writer in another process is caught only by the optional version guard, never serialized.