The core scripts
A rule names a script with use: and carries its config inline. Signposts ships a handful of core scripts that cover the ground; pick the one that matches what you’re stopping.
Everything is a script — no special cases, nothing auto-magic. use: is always a path: a core one (core/<name>) or your own (<namespace>/<name>).
Pick by what you’re stopping
| You want to stop… | use: |
|---|---|
A banned code shape in TS/TSX (e.g. ?? new Date()) |
core/ast-grep |
| A file that’s missing a required companion (e.g. its test) | core/sibling-exists |
| An exported symbol never referenced in its sibling test | core/symbols-in-sibling |
A structured file breaking a shape rule (e.g. package.json) |
core/json-invariant |
| A banned word or pattern in text/prose | core/text-ban |
| A dangerous shell command | core/command-guard |
| Any hand-edit of a protected path (generated, vendored) | core/protected-path |
| An import crossing an architecture boundary (layers & fences) | core/depcruise |
| A whole-project tool failing (import graph, coverage, types) | core/tool-gate |
Two families
Native (in-process)
All but the last. Signposts owns the logic; the config is in your signposts.yml. They run in-process, so they fire pre-emptively on edit and at commit. (This is why ast-grep is a core script, not a tool-gate — it can block before the write lands.)
Tool-delegated
core/tool-gate only. The tool owns its config file (e.g. dependency-cruiser, tsc); Signposts just runs it and blocks on failure. Commit/push only — you can’t run a whole-project build on every keystroke.
On at:. Native scripts default to at: [write, commit] — leave it off. A whole-graph check (tool-gate, depcruise) sets at: [commit] (too heavy to run pre-emptively); an end-of-turn check sets at: [turn]. See the moments. Rules live in their bundle’s block.