Packs & distribution

A pack is a bundle — one contiguous block in your config (its signs, rules, and settings) plus the scripts in rules/<bundle>/. There’s no separate pack format: any repo with a signposts.yml can be installed from, including your day-to-day project.

A pack is a bundle

A bundle is self-contained — copying it is copying its block plus its rules/<bundle>/ folder, nothing scattered:

neon:
  title: Neon
  summary: Guards for Neon/Postgres usage.
  signposts:
    - type: sign
      id: migrations
      at: [touch]
      on: ["src/db/migrations/**"]
      text: "Migrations are append-only."
    - type: rule
      id: no-raw-pool
      at: [write, commit]
      use: neon/no-raw-pool          # + rules/neon/no-raw-pool.mjs
      on: "src/**/*.ts"
  settings:
    permissions:
      deny: ["Read(.env.keys)"]

A bundle owns rules/<bundle>/** — that ownership convention is the manifest, so there’s nothing extra to write and nothing to drift. (This mirrors how Claude Code plugins are just a repo you point at.)

Author in place — never extract

The trap with “packaging” is that it pulls your rules away from where you use them, and then they’re hard to edit. So don’t. You build your neon rules in rules/neon/ in your actual project, and share by letting others point at your repo. The rules never leave the place you author them.

Installing — cherry-pick from any repo

/signposts install points at a repo, diffs it against yours, and lets you pick what to pull — by namespace or by entry. See the skill. The deterministic form, when you already know what you want:

npx signposts install github:you/your-project           # browse what's there
npx signposts install github:you/your-project  neon     # take the neon namespace
npx signposts install ../my-hub  neon                   # …or from a sibling folder on disk

It copies rules/neon/ and merges the neon bundle’s signposts: list — the whole pack, in one move. Comments in your signposts.yml are preserved (the merge edits the document in place, it doesn’t re-serialise from scratch). To remove a pack later: npx signposts uninstall --pack neon — it drops the namespace’s yaml groups, its rules/neon/, and any permissions it added, leaving your own config untouched.

Install reads the current layout only. An older repo (flat signs:/rules: lists, or the legacy advisory: key) is refused with a clear message — open it with /signposts install (the skill) and cherry-pick by hand. No silent half-install.

The hub: your own repo as the spine

You don’t need a public anything. Keep a personal rules repo (private is fine) as your hub: reflect finds a rule in a project → propagate sends it to the hub → install pulls it into your other projects. Trusted teammates can install from the hub too.

Three install doors

The same pack installs any of three ways — pick by how much ceremony you want.

Local path (private / quick) Git (default) npm-scoped (at scale)
Source ../my-hub · /abs/path github:you/pack#v1, or private over SSH @signposts/neon · @acme/guardrails
Publish step none — install straight from disk just git push (tag to pin) npm publish
Versioning whatever’s on disk tags / refs semver (@^2)
Best for private repos · one cleaned-up hub folder you install everywhere from you · personal · shared official packs · companies · access control

Same cherry-pick semantics for all three — only the fetch differs. The local door fits the common case: most rule-bearing repos are private, and the natural workflow is “tidy one local hub, install from there”. Official packs live under @signposts/*; a company publishes under its own (@acme/*); you just push (or point at) a repo.

A pack can carry host permissions

Some guardrails aren’t enforceable by the engine — denying a read, or an MCP tool call, is the host’s job (Claude Code’s .claude/settings.json), not ours. A bundle carries those in its own settings: key, so you don’t hand-edit settings after every install:

bundles:
  neon:
    settings:
      permissions:
        deny:  ["Read(./.env.keys)"]   # the host blocks the read; Signposts just delivers the entry
        allow: []                       # optional — surfaced for you, never auto-applied (it widens autonomy)

On install the deny entries merge into .claude/settings.json; on uninstall, only the ones nothing else still needs are removed — never a hand-written entry.

Refresh keeps your local tweaks — the from: pin

A vendored bundle carries a from: pin recording where it came from and at what version. It’s the whole ledger — no separate manifest — and it’s what makes the bundle editable in place: editing the vendored copy is your local override, and the pin makes that drift visible.

bundles:
  mystyle:
    from: github:you/signposts-mystyle#v1     # provenance — the base refresh diffs against
    rules:
      - { id: house-style, use: mystyle/house-style, ignore: ["src/legacy/**"] }   # ← a deliberate local edit

/signposts refresh (agent-driven — see the skill) does a semantic three-way against the pin: “upstream fixed this regex” merges cleanly under “you deliberately narrowed this rule’s scope”; only upstream changed → take theirs; only you changed → keep + flag yours; both diverged → surface it. A conflicting script drops a <file>.upstream sidecar beside yours — never conflict markers in a live rule. A local improvement worth sharing gets offered back via propagate. That’s what makes “install, then tune it locally” safe to combine with “stay current”.

Lifecycle is agent-driven. install · refresh · propagate · uninstall are flows of the /signposts skill: an agent reads the bundle block and orchestrates the deterministic primitives (source resolution, comment-preserving edits, the three-way merge). For one release the npx signposts install|refresh|uninstall commands still run, behind a pointer to the skill.

core is a bundle too. The baked-in scripts and starter rules ship as the core bundle — same mechanism as neon or your own. Nothing is special; it’s just the bundle that comes pre-installed.