README.md

README.md is a file in Content Fingerprint. 101 lines of code and 0 definitions.

<!-- Auto-generated 2026-10-04T16:25Z v8 -->

# @govlab/content-fingerprint

<!-- concern:overview -->

## Purpose

A dependency-free leaf that answers one question deterministically: have a generator's inputs changed since it last ran? It content-hashes a set of files (SHA-256 of bytes, order-independent), folds those hashes into a composite fingerprint, and persists a per-key index so a generator can skip the units whose inputs are unchanged and regenerate only those that moved.
<!-- /concern:overview -->

<!-- concern:use -->

## When to use

- A code or doc generator that should skip regenerating an output whose precise inputs have not changed since the last run.
- Per-unit selective regeneration: hash each unit's own inputs plus a shared-input hash, so a shared change invalidates every unit while a single-unit change invalidates only that unit.
- Any build step wanting a deterministic, content-based (not mtime-based) up-to-date check with an ephemeral on-disk index.

## When NOT to use

- Cryptographic integrity or tamper-evidence. The package is a change-detection fingerprint, not a security primitive.
- Detecting change in a live, in-memory object graph. The package hashes files on disk, so an in-memory hash cache fits better.
- A hybrid generated-and-authored output whose file must never be overwritten. The index says what changed, and preserving authored content stays with the caller.

<!-- /concern:use -->

<!-- concern:charts -->

## Architecture charts

The structure, logical-flow and dependency diagrams derived from the source AST live in [_code.info.generated/mermaid-charts.generated.md](./_code.info.generated/mermaid-charts.generated.md).
<!-- /concern:charts -->

<!-- concern:install -->

## Install

The package is private and resolves as `@govlab/content-fingerprint`. Build it with `npm run build`, then import from the barrel.

## Quick start

```js EXAMPLE: Skip a unit whose inputs are unchanged
import {
    collectFiles,
    fingerprint,
    fingerprintOf,
    createFingerprintIndex,
    cacheFile,
} from "@govlab/content-fingerprint";
import { existsSync } from "node:fs";

const index = createFingerprintIndex({ file: cacheFile("module-docs"), force: process.env.GOVLAB_NO_CACHE === "1" });
const shared = fingerprint([canonicalIndexPath, allRulesPath, govlabConfigPath]);
for (const unit of units) {
    const own = fingerprint([...collectFiles(unit.dir, { include: (n) => n.endsWith(".ts") }), unit.manifest]);
    const key = fingerprintOf([own, shared, GENERATOR_SOURCE]);
    if (index.unchanged(unit.id, key) && existsSync(unit.output)) {
        continue;
    }
    render(unit);
    index.update(unit.id, key);
}
index.flush();
```

<!-- /concern:install -->

<!-- concern:api -->

## API

- `const ABSENT_SENTINEL: "absent"`
- `function cacheFile(name: string): string` — resolves an ephemeral index path under the consumer's node_modules/.cache/govlab/fingerprint/.
- `function collectFiles(root: string, options?: CollectOptions): string[]` — deterministic sorted file list under a root, skipping the directories the caller names, with an optional name filter. An unreadable root yields an empty list.
- `interface CollectOptions`
- `function createFingerprintIndex(options: FingerprintIndexOptions): FingerprintIndex` — opens a per-key on-disk index exposing `unchanged`, `update` and `flush`, where `force` makes every key report changed.
- `function digestOf(body: string): string` — SHA-256 hex of one string's exact bytes, with no separator added, so a reader can recompute it from the served body.
- `function fingerprint(files: readonly string[]): string` — the order-independent composite hash of a file set (path and content per file). A missing file hashes to the sentinel.
- `interface FingerprintIndex`
- `interface FingerprintIndexOptions`
- `function fingerprintOf(parts: readonly string[]): string` — the order-sensitive composite of already-computed hash strings, which folds the own, shared and generator-source hashes into one key.
- `function hashFile(file: string): string` — SHA-256 hex of a file's bytes, or the `absent` sentinel if the file does not exist. Any other read failure throws.

<!-- /concern:api -->

<!-- concern:config -->

## Configuration

No constructor options beyond the index file path and a `force` flag. Scalar knobs (hash algorithm, separators, index indent, default ignored directories, cache path segments) are package constants. The caller injects the cache file location through `cacheFile(name)`, which resolves the index under the consumer's `node_modules/.cache` (a govlab/fingerprint subfolder created on first write).
<!-- /concern:config -->

<!-- concern:deps -->

## Dependencies

- `@govlab/canonical-write`
- `@ssot/paths`

<!-- /concern:deps -->

<!-- concern:ai-context -->

## AI context

- The fingerprint is content-based (SHA-256 of file bytes), never mtime-based, so it is deterministic across machines and reruns.
- `fingerprint(files)` is order-independent (it sorts), while `fingerprintOf(parts)` is order-sensitive. The latter composes an own-hash, a shared-hash and the generator's own source hash into one key.
- The index lives under `node_modules/.cache` and is ephemeral: a fresh checkout has no index, so every unit regenerates and the result is authoritative.
- A correct per-unit key folds in every shared input (a change there must invalidate all units) and the generator's own source (a generator change must invalidate its outputs).
- A pure leaf: zero `@govlab/*` sibling dependencies.

<!-- /concern:ai-context -->

<!-- concern:domains -->

## Domains

This package serves these software domains, which `_manifest.json` declares in `domains` from the two-tier software-domain vocabulary (`meta → sub`):

- **data** — caching
- **developer-tooling** — build-tooling

<!-- /concern:domains -->

<!-- concern:quality-governance -->

## Quality governance

The canonical quality catalog resolves the quality concepts that govern this package. `_manifest.json` declares them in `governedBy`, and a lint package derives them from the concepts its own rules enforce. Each maps to the custom lint rules that enforce it:

- **immutability** — _best-practice_
- **separation-of-concerns** — _complexity_
- **type-safety** — _correctness_

<!-- /concern:quality-governance -->

<!-- concern:disposal -->

## Disposal

- Remove `govlab.root/govlab.utils/content-fingerprint/`.
- Drop `"@govlab/content-fingerprint": "*"` from the `govlab.quality`, `govlab.patterns`, and `govlab.docs` package manifests.
- Remove every `import … from "@govlab/content-fingerprint"` and inline or drop the skip check where still needed.

<!-- /concern:disposal -->

<!-- concern:metrics -->

---

stable · 11 exports · 2 deps · 0 principles · 3 concepts
<!-- /concern:metrics -->