npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zhchxiao123/dsh-devflow-filesystem

v0.3.0

Published

Filesystem Service Provider for the ctx.devflow task-card capability seam

Readme

@zhchxiao123/dsh-devflow-filesystem

English | 中文

Filesystem Service Provider for the ctx.devflow seam. Cards live under <root>/tasks/<id>/ — a directory named <seq>-<slug>, stable from creation — as a card.md (YAML frontmatter projection plus Markdown body) and an append-only journal.jsonl, the authoritative history. One store instance serves any number of roots: every operation resolves its explicit root argument through one defaulting step (an omitted root is the configured default, a given one resolves absolute), per-card serialization keys on root + id, and each returned card names its root.

Read behavior

list scans <root>/tasks (a missing root lists empty; entries that are not card directories are skipped), read loads one card. Every load replays journal.jsonl through the Definition's fold: an invalid JSON line, a malformed entry, or a broken stream fails the read with an error naming the file and line — a card is never silently skipped. The card.md frontmatter requires title; its stage/stageRevision fields are projections, warned and overridden by the journal on drift. A card missing its journal fails loudly; a lost card.md degrades to a warned journal-only view (the title is frontmatter-owned and irrecoverable) and is rematerialized by the next committed transition.

Write behavior

create serializes in-process and allocates the next sequence number past every active and archived card, reserving tasks/<seq>-<slug>/ with an exclusive (non-recursive) mkdir; losing that reservation to another process rescans for a fresh number, and five straight losses resolve the stable exists rejection. The journal's first created entry is the only commit point — a failed write fails the whole creation — after which the card.md projection (frontmatter title, draft, revision 1, plus the Markdown body) is written (failure only warns, the standard projection degradation) and devflow/card-created is emitted. An omitted slug derives from the title (lowercase alphanumeric runs joined by dashes, bounded, falling back to card).

A requested parent is validated before any directory is reserved, against the same root: a card the root's tasks/ does not hold rejects unknown-parent unless the root's archive holds it (parent-settled, as does a parent already done), and a parent that carries a parent of its own rejects nested-parent — the breakdown is one level deep. Validation and commit both run under the parent's in-process card chain, so one provider instance cannot interleave a creation with a move deciding on the current children. The accepted edge is written into the created entry and projected as the frontmatter's parent:; list narrows to one parent's children through filter.parent.

Every journal mutation after creation — transition, artifact registration, and stale-claim eviction — replays the journal and appends its next entry under the card's commit.lock, taken with O_EXCL. Transition and artifact commits hold the lock for that journal re-read and append; stale takeover additionally re-reads and replaces claim.json before releasing it. Transition gates run before the lock, and a permitting gate decision's approvedBy/checks land in the committed entry's gate. A store-written artifact registration (kind + content) validates its kind against the slug grammar (invalid-kind otherwise), then writes tasks/<id>/artifacts/<rev>-<kind>.md atomically (temp file + rename) before taking the lock — so a registration that loses the revision re-check or the lock budget leaves only a file no journal entry references: invisible to reads, harmless, and overwritten by a same-revision retry. Temp-file replacements retry transient EBUSY/EPERM conflicts within a fixed internal budget, covering Windows readers that briefly hold the destination path. A revision-dependent writer that finds the card moved resolves revision-mismatch; transition and artifact writers that exhaust the lock budget resolve write-contended without appending. After a transition commits, the frontmatter projection is rewritten atomically (temp file + rename, preserving unrelated fields and the body; failure only warns) and devflow/stage-changed is emitted.

claim creates claim.json with O_EXCL: a second claim resolves with the current holder, heartbeat() refreshes the liveness mark, and release() removes the file idempotently. A stale takeover journals claim-expired and replaces the lease inside the same commit lock, so concurrent takeover attempts grant at most one holder; lock contention leaves the observed holder in place. The lease assigns work, while commit.lock protects journal structure. archiveDone renames each archivable done card's directory whole into archive/<YYYY-MM>/<id>/ — the month of its last journal entry, falling back to the current month when that entry's at carries no YYYY-MM prefix — so the journal and artifacts stay intact while list (which scans only tasks/) no longer reports the card. A decomposed requirement moves as one family: a done child whose parent is still on the board stays with it, and once the parent is done the whole family lands in the parent's month bucket. A child that outlived its parent's archiving keeps its own month.

Config

- id: devflow
  name: '@zhchxiao123/dsh-devflow-filesystem'
  config:
    root: .devflow

| Key | Default | Meaning | |---|---|---| | root | .devflow | Default devflow root, used by operations whose caller derives no root of its own; a relative path resolves against the process cwd. |

Model Experience

Indirectly, through the model-facing tools in dsh-tool-devflow: the store backend registers no prompt or schema.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

  • An abandoned commit lock fails closed — mtime alone cannot prove ownership: a stale checker can otherwise delete a successor's newly acquired lock. If a process dies while holding commit.lock, writes resolve as contended until an operator verifies no writer is active and removes that card's lock file.
  • Stale takeover is audit-first across two filesclaim-expired commits before claim.json is replaced. A process crash between those writes leaves the old lease beside a truthful eviction record; a later takeover can retry and append the next revision, but the two files cannot be replaced atomically as one filesystem operation.
  • Takeover trusts the local clock — staleness compares the lease heartbeat with Date.now(), so severe clock skew across machines sharing one workspace can evict a live holder early or late.
  • Cross-process simultaneous creation can share a sequence number — the exclusive mkdir guards the full <seq>-<slug> directory name, so two processes creating with different slugs in the same instant can both keep one number; ids stay unique, and in-process creators are serialized.
  • No change watching — reads are on-demand; a card moved by another process is seen at the next read, not pushed.