Skip to content

Releases

v1.1.0 — stop the ledger being able to lie, and add merge

Choose a tag to compare

@liyixuan201211 liyixuan201211 released this 13 Sep 14:56

A correctness release. A tool whose only job is to be trusted should be judged by how it fails, so this version is mostly defect fixes. Each one is a case where the ledger could have told you something plausible and untrue.

Fixed

Defect What it could have told you
An empty or whitespace-only log produced a universal fingerprint (sha256("")). That two unrelated failures had an identical failure signature — a false block. signature now returns null when there is nothing worth hashing.
verify --still-fails on an entry with no anchors flipped decay from none to anchored. ([] is truthy in JavaScript.) That an entry which blocks forever was properly anchored — after which status stopped warning about it.
--unanchored together with --anchor was accepted. That anchors were protecting you, when they were stored but never checked. Now refused.
A directory anchor covering zero files was accepted. That a claim could expire, when its manifest can never change. Now refused.
Directory hashing fell back to a filesystem walk when git reported zero files. Anchors that thrashed on gitignored build output. An empty answer from git is an answer.
An unreadable --log path was silently dropped. A downgrade from signature matching to command matching — a typo could turn a BLOCKED into a CLEAR. Now a usage error: a safety check must not fail open.
A non-integer --exit was silently discarded. An entry vaguer than the one you asked for. Now rejected.
--force discarded earlier notes, and the duplicate message promised "a separate entry". It replaces the entry with the same content-derived id, keeping its history. The message now says so, and notes survive.

Added

  • deadend merge <ledger...> — union other ledgers into this one (or - for stdin). This turns the set-union property that the content-derived identity scheme was designed around from a claim in the README into an actual command. When both sides know an id, the later observation wins on status and anchors, while histories and notes are unioned so neither side loses one.

    $ deadend merge ../other-clone/.deadend/ledger.jsonl
      added      1
      updated    1
      unchanged  0
      total      2
    
  • status names the entries that need attention — no anchors, or every anchor deleted — instead of only counting them. --json exposes the same list, so the ledger's own health can gate CI.

  • verify says so when a re-confirmation could not re-pin anything, rather than claiming the entry is authoritative again.

  • A paste-able CI recipe in the README.

Verification

  • 74 tests (16 new), one per defect above
  • tsc --noEmit clean over the JSDoc types
  • CI green on Node 20, 22 and 24, plus the installable job that packs the tarball, installs it into a real consumer node_modules and runs the bin
  • examples/demo.sh extended to show the new attention list

Requires Node >= 20. Plain ESM JavaScript: no build step, no install-time scripts.

v1.0.0 — a refutation ledger whose entries expire by content

Choose a tag to compare

@liyixuan201211 liyixuan201211 released this 13 Sep 14:42

Remember what did not work — and expire it automatically when the code it depended on changes.

Agents do not remember failures. Every new session meets the same broken build with the same optimism and pays the same cost again. The obvious fix — a list of things that failed — is also wrong: a note from six months ago, written against code that has since changed, will confidently block a fix that now works.

The hard part of negative knowledge is not recording it. It is invalidating it.

The idea: anchors

Every entry carries the content hashes of the files whose change would falsify it.

Anchors Meaning check exit
all unchanged authoritative — it still fails 3
any changed / deleted suspect — re-test it 4
nothing known clear 0
record refused no anchors, missing anchor, or duplicate 5

Validity is judged by content, not by a clock. No MAX_AGE_DAYS to calibrate, and nothing to remember to expire — because wall-clock expiry is wrong in both directions: too slow (a dependency bump that invalidates a dead end this morning does not clear it for 60 days) and too fast (a dead end about a frozen dependency vanishes on day 61 for no reason).

record refuses to create an entry with no anchors, and suggests anchors that actually exist in your repository. A claim that can never expire is indistinguishable from a bug — so --unanchored exists, but it is loud and it is counted in deadend status.

Install

dsh plugin --profile web add github:liyixuan201211/dsh-deadend

No install needed to try it:

npx --yes github:liyixuan201211/dsh-deadend --help

In this release

  • check / record / verify / list / show / status / gc / init
  • Content-addressed decay: validity tracked by anchor hashes, never by age
  • Deterministic failure-signature normalisation — line and column refs, paths, timestamps, UUIDs, digests, durations and ANSI codes erased — so the same breakage is recognised across machines and reworded commands
  • Layered matching: failure signature and exact command are decisive; a shared command family is a hint that never blocks (npm install sharp failing says nothing about npm install left-pad)
  • In-repo, append-only event log at .deadend/ledger.jsonl — reviewable in a PR, committed with the code, usable as a CI gate
  • Content-derived entry identity, so two clones that record the same refutation produce the same id and ledgers merge as a set union
  • A DSH skill (skills/deadend/) plus reference/decay.md and reference/schema.md
  • An intentionally empty cordis patch: this plugin adds nothing to the boot graph

Verification

  • 58 tests, tsc --noEmit clean (JSDoc types, checkJs)
  • CI green on Node 20, 22 and 24
  • An installable CI job packs the tarball, installs it into a real consumer's node_modules, and runs the bin — regression guard for an entry point that cannot be a .ts file, because Node refuses to strip types under node_modules
  • An end-to-end demo (examples/demo.sh) that walks blocked → suspect → re-confirmed → retired

Requirements

Node >= 20. Plain ESM JavaScript: no build step, no install-time scripts.

Honest positioning

Recording failures is not a new idea, and this release does not claim it is. The closest prior art is dead-end-registry; there is also academic work on failure-aware shared memory (Negative Knowledge, ICML 2026 AI4Research workshop). The contribution here is the invalidation model: content-hash expiry instead of wall-clock age, refusing undecayable claims by default, and an exit-code interface that composes with any shell or CI. Full comparison in the README.