Files
deepseek-harness/docs/rfc/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.md
T
Tianyi Cui e6fad266a6 docs(rfc): define and enforce a uniform RFC format; adopt it across the corpus
Define the in-file RFC contract in docs/rfc/README.md § The file format:
the header block (`# RFC: <title>` plus a dateless Status enum
cross-checked against the lifecycle folder), the per-lifecycle body
skeleton (a Problem opener everywhere; Proposal/Alternatives considered/
Acceptance criteria/Risks in proposed/; present-tense Decision/
Consequences with proposal-era headings banned in implemented/; the
frozen proposal shape in rejected/), and a mandatory Alternatives
considered section with a date-fenced grandfather comment for pre-format
RFCs whose alternatives are not reconstructible from the record.

Enforce it with a new doc-sync gate, scripts/verify-rfc-format.ts, and
normalize all 112 RFCs to it: ~15 Status-line spellings collapse to the
enum, 29 Context openers become Problem, the 39 legacy-format XXX debt
markers are resolved and banned from reappearing, proposal-era sections
in implemented RFCs are rewritten to shipped reality (including the
web/fs/subagent seam RFCs' migration plans and test checklists, closing
the doc-tiers deferred-work item on the web seam), every RFC gains an
Alternatives considered section or the grandfather comment, and the
bilingual pair is re-mirrored and re-recorded.

Move the generated index tables out of README.md into a fully generated
docs/rfc/INDEX.md — gen-rfc-index now writes the whole file, and
verify-rfc-classification checks its freshness and rejects index-shaped
rows in the curated README — which makes room for the format contract to
live in the README front door instead of a separate FORMAT.md.

The decision record, and the first RFC written in the new format, is
docs/rfc/implemented/process/2026-07-05-uniform-rfc-format.md.
2026-07-05 22:58:25 +08:00

4.0 KiB

RFC: Add direct directory listing to the filesystem seam

Status: implemented

Problem

@deepseek-ai/dsh-fs is the provider seam for filesystem access, with local and future non-local backends behind the same ctx.fs contract. Before this change it could resolve paths, stat targets, read text, stream text, write text, and edit text. That was enough for model-facing file tools, but not for non-model-facing consumers that need to enumerate directories without importing node:fs.

The immediate pressure came from skill loading: reading an individual SKILL.md can already go through ctx.get('fs'), but discovering which skill roots contain <name>/SKILL.md or <name>.md still needs directory enumeration. Adding directory listing only in dsh-skill would either keep a direct Node dependency there or invent a one-off local helper outside the filesystem provider stack.

This branch deliberately lands the provider capability first and does not add a model-facing ls/list tool or change skill discovery. The follow-up consumer can validate UX and prompt shape separately, while this PR establishes the backend seam and local implementation.

Decision

Add FileSystem.listDir(target, signal?) to @deepseek-ai/dsh-fs.

listDir lists one directory level only. It returns direct children in stable name order and includes:

  • name: the child basename.
  • type: file, directory, or other.
  • target: the resolved child FsTarget.
  • version: cheap metadata when available.
  • size: regular-file size when available.

It never reads file contents. Recursive traversal, globbing, pagination, search, file watching, and model-facing rendering are intentionally out of scope.

The local backend implements this through readdir({ withFileTypes: true }), resolveLocalTarget, and metadata stat/realpath probes. The result order is deterministic (name.localeCompare) to keep prompt/listing output stable for future consumers and improve prefix-cache reuse.

Broken or disappeared children may be represented as type: 'other' without version/size; they do not abort the whole listing. Permission or backend I/O failures while listing the directory or resolving/probing child metadata fail the whole listing with structured FsError codes:

  • FS_NOT_FOUND for missing targets.
  • FS_NOT_DIRECTORY for existing non-directory targets.
  • FS_PERMISSION_DENIED for permission failures.
  • FS_IO_ERROR for other backend I/O failures.
  • FS_ABORTED for aborted calls.

Alternatives considered

Add a model-facing list tool now. Rejected for this PR. The immediate request is the provider seam, and the user explicitly asked not to change skill loading or other upper layers in this branch. A model-facing tool needs prompt/schema/rendering decisions that should be reviewed separately.

Keep directory enumeration in each consumer. Rejected. That would bind product packages such as dsh-skill to Node/local filesystem behavior and bypass policy/remote/sandboxed backends.

Make listDir recursive or glob-shaped. Rejected for now. Skill-root discovery only needs direct children, and a simple direct listing is the smallest backend contract future consumers can safely compose.

Skip children that fail metadata resolution. Rejected. The API promises resolved child targets, so permission/IO failures while resolving a child are contract failures. Broken or disappeared children are the exception because they can still be represented without claiming a live resolved file.

Consequences

Every filesystem backend must now implement one additional provider primitive. That is deliberate foundation work while the harness is still unreleased, but it does mean future sandboxed/remote backends need to define equivalent direct-child listing behavior.

The capability remains provider-facing. Until a consumer lands, ACP/model sessions will still need existing tools such as bash for directory listing. The absence of a model-facing listdir tool is expected, not a wiring failure.