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

@principled/cairn

v5.0.0

Published

Cairn — decision traceability framework. Trace every decision back to what drives it.

Readme

Cairn — Goals, Values, and Principles

A decision traceability framework. Define your goals, values, and principles in YAML. Trace every decision back to what drives it. Link decisions to the code, documents, and artifacts they produce.

Install

npm install -g @principled/cairn

Note: gvp is available as an alias for cairn.

Quick Start

# Before authoring anything: check whether the guidance already exists.
# Cairn registers every library it resolves, so you can inherit instead of re-deriving.
cairn libs search "simplicity"

# Initialize a GVP library
mkdir -p .gvp/library
cat > .gvp/library/project.yaml << 'EOF'
meta:
  name: my-project
  scope: project

goals:
  - id: G1
    name: Recoverable state
    statement: >
      A user who loses their machine can rebuild every byte of their data
      from the files we wrote to their disk.
    tags: []
    maps_to: []

values:
  - id: V1
    name: Simplicity
    statement: Complexity must earn its place.
    tags: []
    maps_to: []

decisions:
  - id: D1
    name: Persist state as an append-only event log
    disposition: accepted
    rationale: >
      "Anything with a mutable schema needs a migration story the first time
      the schema changes. An append-only log only ever needs a reader for the
      older shape, which is a test I can write today."
    tags: []
    maps_to: [my-project:G1, my-project:V1]
    considered:
      sqlite:
        rationale: "Queryable and transactional, but adds a native dependency and a schema to migrate on every change."
        would_have_served: [my-project:G1]
        conflicts_with: [my-project:V1]
      mutable_json_blob:
        rationale: "Fewest moving parts, but a partial write destroys the whole history rather than the last record."
        would_have_served: [my-project:V1]
        conflicts_with: [my-project:G1]
    refs:
      - file: src/store.ts
        identifier: appendEvent
        role: implements
EOF

# Validate
cairn validate

# Export
cairn export --format json
cairn export --format markdown

Note what the example models: a goal stating a checkable outcome rather than "software that works"; a decision whose rationale is a verbatim quote; and considered alternatives that name what each one would have served and what it conflicts with. A decision without its rejected alternatives records the choice but loses the reasoning — which is the part you need when revisiting it.

Commands

| Command | Description | |---------|-------------| | cairn validate | Validate the GVP library | | cairn validate --coverage | Include coverage checks (orphan identifiers, decisions without refs) | | cairn validate --scope staged | Scope validation to staged git changes | | cairn validate --include-inherited | Show diagnostics on inherited elements (default: counted, not listed) | | cairn export --format <fmt> | Export catalog to json, csv, markdown, or dot | | cairn add <category> <name> | Add a new element with auto-assigned ID | | cairn edit <element> --field key=value | Modify an existing element | | cairn review | Find stale elements needing review | | cairn review <element> | Review a specific element | | cairn inspect <element> | View element details | | cairn inspect <element> --trace | Trace element to its goals and values | | cairn inspect --ref file::identifier --trace | "Why does this code exist?" | | cairn query --category decision | Filter elements by category, tag, status | | cairn query --refs-file src/foo.ts | Find elements referencing a file | | cairn query --list documents | List the documents in the resolved library, with element counts | | cairn diff <commitA> <commitB> | Trace code changes back to decisions | | cairn analyze | Detect unmapped relationships via similarity | | cairn skill install | Install the bundled agent skill into ~/.claude/skills/cairn/ | | cairn skill path | Print the bundled skill's directory, for symlinking by hand | | cairn skill status | Compare the installed skill against the bundled one |

Global Options

| Flag | Description | |------|-------------| | --config <path> | Load specific config file | | --no-config | Skip all config files | | -c key=value | Inline config override. Repeat the flag for more than one: -c strict=true -c source=@local. | | --strict | Promote warnings to errors | | -v / -vv / -vvv | Verbose output |

Global options may be written before the subcommand or after it — cairn -c strict=true query and cairn query --strict are both accepted. Where a subcommand defines the same short flag, the subcommand wins in the trailing position: cairn -c strict=true query -c decision sets the config override strict=true and filters to the decision category, and -c after query is always --category.

Validation Codes

Errors

| Code | Name | Description | |------|------|-------------| | E001 | BROKEN_REFERENCE | maps_to target not found | | E002 | DUPLICATE_ELEMENT_ID | Duplicate element ID within a document | | E003 | BROKEN_INHERITANCE | Inherited document not found | | E004 | SCHEMA_VALIDATION | Element fails schema validation | | E005 | DUPLICATE_STEP_ID | Duplicate step ID within a procedure | | E006 | DUPLICATE_DOCUMENT_NAME | Two documents in one library share a meta.name |

Warnings

| Code | Name | Description | |------|------|-------------| | W001 | EMPTY_MAPS_TO | Non-root active element has no maps_to | | W002 | EMPTY_DOCUMENT | Document has no active elements | | W003 | MAPPING_RULES_VIOLATION | Element doesn't satisfy category mapping rules | | W004 | ORPHAN_ELEMENT | Isolated element (no incoming or outgoing edges) | | W005 | SELF_DOCUMENT_MAPPING | Element maps only within its own document | | W006 | STALE_ELEMENT | Element has unreviewed updates | | W007 | UNDEFINED_TAG | Element uses tag not in definitions | | W008 | DUPLICATE_CATEGORY_DEF | Duplicate category definition within library siblings | | W009 | ID_SEQUENCE_GAP | Gap in element ID sequence | | W010 | REF_FILE_MISSING | Ref points to nonexistent file | | W011 | REF_IDENTIFIER_MISSING | Ref identifier not found in file | | W012 | ORPHAN_IDENTIFIER | Identifier not referenced by any element (coverage pass) | | W013 | DECISION_NO_REFS | Decision has no refs (coverage pass) | | W014 | NO_ROOT_TRACE | Element cannot trace to any root element transitively | | W015 | AUTO_ASSIGNED_STEP_ID | Procedure has steps without explicit IDs; auto-numbered at load time | | W016 | UNRECOGNIZED_YAML_KEY | Top-level YAML key is neither meta nor a known category yaml_key | | W017 | NO_VALUE_TRACE | Non-root element does not trace to any value transitively (soft anchor) | | W018 | ROOT_NO_DECISION | Actionable root (requires_decision) has no Decision tracing to it (coverage pass) |

Config

Config files are discovered in this order (closer scope wins):

  1. /etc/gvp/config.yaml (system)
  2. ~/.config/gvp/config.yaml (global)
  3. .gvp/config.yaml (project)
  4. .gvp.yaml (local, gitignored)

Environment variables: GVP_CONFIG_SYSTEM, GVP_CONFIG_GLOBAL, GVP_CONFIG_PROJECT, GVP_CONFIG_LOCAL

# .gvp/config.yaml
user:
  name: "Your Name"
  email: "[email protected]"

strict: false
suppress_diagnostics: []
default_timezone: "America/New_York"

# Scope diagnostic DISPLAY by source (see below). Inherited elements are
# someone else's to fix; by default their diagnostics are counted, not listed.
diagnostics:
  inherited: count        # show | count | hide
  by_source:
    "@github:shitchell/gvp-docs": count

priority:
  elements: ancestor      # ancestor-wins for elements
  definitions: descendant  # descendant-wins for definitions

# Coverage settings (patterns use glob syntax via minimatch: *, **, ?)
coverage:
  exclude:
    - "README.md"
    - ".gvp/**"
    - "**/*.test.ts"
    - "docs/**"

Diagnostics you cannot fix

Inheriting a library means inheriting its diagnostics. They are the upstream author's defects, unfixable from your repo, and they bury your own signal -- 49 of this repo's 148 warnings landed on upstream elements.

diagnostics scopes display by source, in three states:

  • show -- print every diagnostic individually
  • count -- withhold the lines, print a one-line roll-up (default for inherited sources)
  • hide -- withhold the lines and the counts, print a bare N sources fully hidden trace
$ cairn validate
WARN  W005  gvp:D4   Element gvp:D4 maps only to elements within its own document
...
  49 further warnings from @github:shitchell/[email protected] (W005 ×24, W003 ×23, W017 ×2)
  → --include-inherited to show

There is deliberately no state that renders nothing. gvp:P9 says "hide what is not actively needed, but never lose it" -- so even hide confesses that it is hiding something. That is the difference between this and suppress_diagnostics, which leaves no trace at all.

The two are orthogonal, not redundant: suppress_diagnostics answers "I don't care about this kind of finding"; diagnostics.by_source answers "this isn't my code." They compose as per-code x per-source. Errors are never scoped -- an invalid catalog is your problem whoever authored it.

See docs/reference/validation.md for the full rules.

Built-in Categories

| Category | Prefix | Root | Primary Field | |----------|--------|------|---------------| | goal | G | yes | statement | | value | V | yes | statement | | constraint | C | yes | impact | | user_requirement | U | yes | statement | | exclusion | X | yes | statement | | principle | P | no | statement | | rule | R | no | statement | | heuristic | H | no | statement | | decision | D | no | rationale | | milestone | M | no | description | | procedure | S | no | description |

Root categories (goal, value, constraint, user_requirement, exclusion) need no upward mapping. A user_requirement is a stakeholder-decreed mandate asserted as input — not derivable from any value (that is what makes it a requirement and not a rule). An exclusion is a self-imposed out-of-scope boundary — the negative space of goals. Every non-root element anchors to any non-value root and should trace to a value (enforced softly by W017). Decisions carry a disposition (accepted | declined | deferred); a declined/deferred decision's rationale is the record, so it is exempt from the decision-needs-refs check (W013).

Cross-Repo Inheritance

GVP libraries can inherit from other libraries hosted in git repositories. Documents reference external sources in meta.inherits:

meta:
  name: my-project
  inherits:
    - source: "@github:company/[email protected]"
      as: org
    - source: "@azure:myorg/myproject/[email protected]"
      as: shared

Supported providers:

| Provider | Format | Resolves to | |----------|--------|-------------| | GitHub | @github:user/repo@tag | https://github.com/user/repo.git | | Azure DevOps | @azure:org/project/repo@tag | https://dev.azure.com/org/project/_git/repo | | GitLab | @gitlab:user/repo@tag | https://gitlab.com/user/repo.git | | Bitbucket | @bitbucket:user/repo@tag | https://bitbucket.org/user/repo.git |

The commit-ish must be an immutable reference (tag or SHA) — branches are not allowed. Sources are cached at ~/.cache/cairn/sources/ and only cloned once.

Elements resolve by the library short address [<alias>:]<meta.name>:<id> — the as: alias selects the inherited library, and meta.name (not the file path) selects the document, so references survive upstream file reorganization. A bare <meta.name>:<id> resolves across all libraries (preferring the local one on a name collision); qualify with the alias when it is ambiguous. meta.name must be unique within each library (enforced by E006).

decisions:
  - id: D1
    name: Follow org coding standards
    maps_to: [org:values:V1, my-project:G1]   # org:<meta.name>:<id>

What's in the library I'm actually using?

cairn query --list <type> enumerates a kind of thing in the resolved catalog — the library this project actually builds, root plus everything it inherits. Today the one type is documents:

cairn query --list documents                  # name, count, source
cairn query --list documents --format json    # stable schema for agents

Each row carries name, document_path, source, description (from meta.description), scope, element_counts (keyed by category, e.g. principles) and element_total. It is the index to read at the start of a session to decide which documents bear on the task.

--list selects what to enumerate; --format still selects how to render it, so the two compose. The element filters (--category, --tag, --status, --include-deprecated) change the counts; --document changes which rows appear. A document with no matching elements is still listed, at zero — it is never silently dropped.

query --format json also tags every element with _document, _documentPath and _source, which are exactly the row key above, so element output and the document listing join on equality.

This is not the same question as cairn libs list below. --list documents reads the catalog this invocation resolved; libs reads the machine-wide record of every library cairn has ever seen, on any project.

Library registry

Cairn records every library it resolves into ~/.gvp/registry/ (override with GVP_REGISTRY_ROOT), so a new project can discover guiding elements that already exist instead of re-deriving them.

cairn libs list                  # everything cairn has seen
cairn libs search "flex point"   # across every known library
cairn libs show personal         # detail + which projects have used it
cairn libs forget <source>:<doc> # drop one entry
cairn libs prune [--remote]      # drop entries whose document is gone

search matches element names and each category's primary field — resolved from the schema, not hard-coded — so a decision's rationale and a constraint's impact are searchable, not just statement. It never reaches the network unless you pass --fetch, and it names everything it skipped rather than returning a quietly incomplete answer.

show and forget refuse to guess: meta.name is not unique across libraries, so an ambiguous name lists its candidates and exits non-zero rather than picking one. All three commands take --json.

Recording is on by default. There is one switch, registry.enabled, and three ways to reach it:

registry.enabled: false      # in any config layer — the whole invocation
--no-registry                # one invocation
meta.registry.enabled: false # in a library document — that library only

The third is for a library that should never be indexed — a scratch copy made to reproduce a bug, a throwaway experiment — and it holds however cairn is pointed at the library (--store, --library, or discovery from the current directory):

meta:
  name: scratch-repro
  registry:
    enabled: false

It governs the library that declares it and nothing else. A project that inherits an opted-out library still records its own documents: an upstream library has no say over its consumer's registry. For the same reason it can only suppress — enabled: true will not re-enable recording that a config layer or --no-registry turned off.

Recording never changes a command's exit code or output; on failure it warns once to stderr and carries on.

Recording happens when a command builds the catalog, so cairn init — which creates a library rather than loading one — does not itself register the project. The first validate, query, or export does.

The registry is safe to delete — but rebuilding it is lossy. Nothing breaks if you rm -rf ~/.gvp/registry, and cairn will not complain. But it does not rebuild itself: each library reappears only when cairn next resolves it, so a library you have not touched since deleting is simply absent until you next work in a project that uses it.

The agent skill

Cairn ships the skill that teaches an AI agent how to use it — element-ID syntax, citation conventions, the import/patch workflow, cross-repo inheritance, and the review checklist. It travels inside the npm package, so it versions in lockstep with the tool instead of drifting from the schema.

cairn skill install                 # into ~/.claude/skills/cairn/
cairn skill install --dest .claude/skills/cairn   # project-local instead
cairn skill path                    # where the bundled copy lives
cairn skill status                  # installed version vs. bundled version

Nothing is installed automatically. There is no postinstall hook — it would not run under --ignore-scripts anyway, and writing into your skills directory because you installed a CLI is not a decision cairn gets to make for you.

install writes a .cairn-skill.json manifest beside the files recording the source version and a checksum per file, which lets it tell three situations apart:

| state | what happens | |-------|--------------| | nothing installed | installs, after confirming the destination | | installed, untouched | replaces it — this is an update | | installed, you edited it | refuses, even interactively |

To overwrite edits, pass --force; the current contents are copied to <dest>/.backups/<timestamp>/ first. --force and -y, --yes are separate on purpose: --yes only skips the prompt, so a CI job that wants no prompt cannot silently destroy your local edits. Files you add beside the skill are never touched or removed.

Without a TTY, install refuses and tells you to pass --yes. path and status take --json, as does install. status never touches the network — it compares what is on disk with what this copy of cairn ships, and answers only when asked:

$ cairn skill status
Installed from 3.1.0, current is 3.2.0 — run `cairn skill install` to update.

If you would rather not have cairn write anything, symlink it:

ln -s "$(cairn skill path)" .claude/skills/cairn

Refs — Linking Decisions to Artifacts

Any element can have refs linking it to external files:

refs:
  - file: src/catalog/catalog.ts
    identifier: Catalog
    role: implements  # defines | implements | uses | extends

The refs system is domain-agnostic — it works with any file type that has a registered parser (TypeScript, Markdown, YAML built-in).

Documentation