rulebookguard 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -81
- package/dist/{anchors-BFJCm1NU.mjs → anchors-C2j9c5MI.mjs} +1 -1
- package/dist/cli.mjs +99 -36
- package/dist/index.d.mts +43 -8
- package/dist/index.mjs +3 -3
- package/dist/lint-DfibQDW3.mjs +1042 -0
- package/dist/{site-BiiOPVEe.mjs → site-DmY5YVte.mjs} +5 -5
- package/package.json +3 -3
- package/dist/lint-C1yUeLOq.mjs +0 -755
package/README.md
CHANGED
|
@@ -1,17 +1,42 @@
|
|
|
1
|
+
<!-- prettier-ignore -->
|
|
2
|
+
<div align="center">
|
|
3
|
+
|
|
1
4
|
# rulebookguard
|
|
2
5
|
|
|
6
|
+
[](https://www.npmjs.com/package/rulebookguard)
|
|
7
|
+
[](https://github.com/JuanCJR/rulebookguard/actions/workflows/ci.yml)
|
|
8
|
+

|
|
9
|
+
[](https://www.typescriptlang.org)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
|
|
3
12
|
Keep business rules as the single source of truth, readable by humans and AI, and bound to the code and tests that implement them.
|
|
4
13
|
|
|
5
|
-
|
|
14
|
+
[Overview](#overview) • [Get started](#getting-started) • [Writing rules](#writing-rules) • [Commands](#commands) • [CI recipe](#ci-recipe) • [Claude Code plugin](#claude-code-plugin)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
Business rules usually end up scattered across code, tickets and people's memory. Neither a developer's memory nor an AI model's reading of the code is deterministic.
|
|
21
|
+
|
|
22
|
+
`rulebookguard` keeps each rule in its own small Markdown file, links it to the code it governs (**anchors**) and to the tests that prove it (**`@rule` … `@endrule` regions**), and fails CI when they drift apart. It is language-agnostic: rules are Markdown, and tests in any language link to them through a comment.
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
docs/rules/clients/R-CLI-001.md ── anchors ──▶ src/clients/**/*.ts
|
|
26
|
+
▲
|
|
27
|
+
└── @rule R-CLI-001 … @endrule R-CLI-001 in src/clients/create.test.ts
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Features
|
|
6
31
|
|
|
7
32
|
- **Atomic rules.** One file per rule, with YAML frontmatter validated against a versioned JSON Schema.
|
|
8
33
|
- **Progressive disclosure.** Contexts map (L0), `id + summary` per context (L1), the full rule (L2), so neither people nor AI models have to read the whole catalog.
|
|
9
34
|
- **Inverse index.** Given a file path, list the active rules anchored to it. Deterministic glob matching, no embeddings.
|
|
10
|
-
- **Linter.** Untested rules, tags pointing at missing or deprecated rules, dead anchors, invalid files, and rules changed without a
|
|
35
|
+
- **Linter.** Untested rules, tags pointing at missing or deprecated rules, dead anchors, invalid files, and rules changed without a change in their tests.
|
|
36
|
+
- **AI-ready.** A Claude Code hook, an MCP server and a skill hand the rules of a file to an agent before it edits it.
|
|
37
|
+
- **Static site.** A read-only, searchable view of the rulebook for the whole team.
|
|
11
38
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## Install
|
|
39
|
+
## Getting started
|
|
15
40
|
|
|
16
41
|
Requires Node.js 22 or later.
|
|
17
42
|
|
|
@@ -25,6 +50,13 @@ Or add it to a project:
|
|
|
25
50
|
npm install --save-dev rulebookguard
|
|
26
51
|
```
|
|
27
52
|
|
|
53
|
+
A first rule in four steps:
|
|
54
|
+
|
|
55
|
+
1. Create `rulebookguard.config.yaml` at the repository root (see [Configuration](#configuration)).
|
|
56
|
+
2. Scaffold a rule: `npx rulebookguard new clients` creates the context and `docs/rules/clients/R-CLI-001.md` as `proposed`.
|
|
57
|
+
3. Write the rule, add its `anchors`, set `status: active`, and wrap the test that proves it with `@rule R-CLI-001` and `@endrule R-CLI-001`.
|
|
58
|
+
4. Run `npx rulebookguard lint`, and add it to CI with the [CI recipe](#ci-recipe).
|
|
59
|
+
|
|
28
60
|
## Configuration
|
|
29
61
|
|
|
30
62
|
Put `rulebookguard.config.yaml` at the repository root. Commands find it by walking up from the current directory, and every path in the catalog is relative to the directory that holds it.
|
|
@@ -39,13 +71,23 @@ contextBudget: 30 # default: 30; lint warns when a context holds more rules
|
|
|
39
71
|
language: es # default: en; language of the text that rulebookguard generates
|
|
40
72
|
```
|
|
41
73
|
|
|
42
|
-
`schema` and `testGlobs` are required (`testGlobs: []` is valid but finds no tags). Unknown fields are rejected.
|
|
74
|
+
`schema` and `testGlobs` are required (`testGlobs: []` is valid but finds no tags). Unknown fields are rejected. The JSON Schemas ship with the package under `rulebookguard/schemas/v1/`.
|
|
75
|
+
|
|
76
|
+
`language` (`en` or `es`) sets the language of the text rulebookguard writes for you: the template of `new`, the hook context, the site, and every message printed once the config is loaded (lint findings, errors, warnings and headers, also the `message` of `--json` output). Lint codes, JSON keys and values such as `active` stay in English, and so do `--help`, argument errors and the findings of an invalid config file, which come before the config is read. Rule content is free text and is never checked for language, so rules can be written in any language whatever this setting says.
|
|
77
|
+
|
|
78
|
+
> [!NOTE]
|
|
79
|
+
> `language` needs rulebookguard 0.1.1 or later; older versions reject it as an unknown field.
|
|
43
80
|
|
|
44
|
-
|
|
81
|
+
<details>
|
|
82
|
+
<summary>How files are listed</summary>
|
|
45
83
|
|
|
46
84
|
Inside a git work tree, files are listed with `git ls-files --cached --others --exclude-standard`, so anything in `.gitignore` (`dist/`, `coverage/`, caches) is never matched by anchors or test globs; files inside submodules are listed too. A submodule path that is a symlink, or that resolves outside the rulebook root, is skipped. A test file that is a symlink is scanned for `@rule` tags only when its target lies inside the rulebook root. If one submodule cannot be listed, commands print `warning: could not list files of submodule "<path>" (<reason>); skipping it.` and keep the rest of the git listing. Outside git, or when the rulebook root itself is git-ignored, the whole tree is walked, skipping only `.git` and `node_modules`. If git fails (for example, git is missing from `PATH` or the repository has dubious ownership), commands print one warning on stderr and walk the tree instead.
|
|
47
85
|
|
|
48
|
-
|
|
86
|
+
</details>
|
|
87
|
+
|
|
88
|
+
## Writing rules
|
|
89
|
+
|
|
90
|
+
### Catalog layout
|
|
49
91
|
|
|
50
92
|
Rules are grouped by context, one directory per context:
|
|
51
93
|
|
|
@@ -74,7 +116,7 @@ The clients context owns customer identity and contact data.
|
|
|
74
116
|
|
|
75
117
|
The `context` field must match the directory name, and every rule id in the directory must start with `R-<prefix>-`.
|
|
76
118
|
|
|
77
|
-
|
|
119
|
+
### Rule file format
|
|
78
120
|
|
|
79
121
|
````markdown
|
|
80
122
|
---
|
|
@@ -114,15 +156,14 @@ Scenario: Duplicate document in the same country
|
|
|
114
156
|
|
|
115
157
|
The Markdown body holds the full rule and its examples (Gherkin scenarios or tables). Rules do not list their tests: the link lives only in the tests, through `@rule` tags.
|
|
116
158
|
|
|
117
|
-
|
|
159
|
+
### Linking tests with `@rule` … `@endrule`
|
|
118
160
|
|
|
119
|
-
A test links to a rule with `@rule <ID>`
|
|
161
|
+
A test links to a rule with a region: `@rule <ID>` opens it and `@endrule <ID>` closes it, in a file matched by `testGlobs`. Both lines belong to the region. Regions may nest (a suite tagged with one rule holding tests tagged with others) as long as they close in order. One line can carry several markers.
|
|
120
162
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
```
|
|
163
|
+
> [!IMPORTANT]
|
|
164
|
+
> Every `@rule` needs its `@endrule`, otherwise `lint` reports `unclosed-rule-tag`; an `@endrule` with no open `@rule` of the same id is reported as `unmatched-endrule-tag`. The region tells `lint --base` which lines belong to the rule: a changed rule needs a change inside one of its regions, not anywhere in the test file.
|
|
124
165
|
|
|
125
|
-
To mention a rule in a test file without linking to it, write the id without the `@rule` prefix.
|
|
166
|
+
A comment is the usual place, but the markers are matched as text, so they also count inside a test name or a string. To mention a rule in a test file without linking to it, write the id without the `@rule` prefix.
|
|
126
167
|
|
|
127
168
|
TypeScript or JavaScript:
|
|
128
169
|
|
|
@@ -131,6 +172,7 @@ TypeScript or JavaScript:
|
|
|
131
172
|
it('rejects a duplicate document in the same country', () => {
|
|
132
173
|
/* ... */
|
|
133
174
|
});
|
|
175
|
+
// @endrule R-CLI-001
|
|
134
176
|
```
|
|
135
177
|
|
|
136
178
|
Go:
|
|
@@ -138,6 +180,7 @@ Go:
|
|
|
138
180
|
```go
|
|
139
181
|
// @rule R-CLI-001 @rule R-BIL-001
|
|
140
182
|
func TestCreateClient(t *testing.T) {}
|
|
183
|
+
// @endrule R-BIL-001 @endrule R-CLI-001
|
|
141
184
|
```
|
|
142
185
|
|
|
143
186
|
Python:
|
|
@@ -146,6 +189,7 @@ Python:
|
|
|
146
189
|
# @rule R-CLI-001
|
|
147
190
|
def test_duplicate_document():
|
|
148
191
|
...
|
|
192
|
+
# @endrule R-CLI-001
|
|
149
193
|
```
|
|
150
194
|
|
|
151
195
|
Java or Kotlin:
|
|
@@ -154,6 +198,7 @@ Java or Kotlin:
|
|
|
154
198
|
/** @rule R-CLI-001 */
|
|
155
199
|
@Test
|
|
156
200
|
void rejectsDuplicateDocument() {}
|
|
201
|
+
/** @endrule R-CLI-001 */
|
|
157
202
|
```
|
|
158
203
|
|
|
159
204
|
SQL:
|
|
@@ -161,12 +206,27 @@ SQL:
|
|
|
161
206
|
```sql
|
|
162
207
|
-- @rule R-BIL-002
|
|
163
208
|
SELECT count(*) FROM invoices WHERE number IS NULL;
|
|
209
|
+
-- @endrule R-BIL-002
|
|
164
210
|
```
|
|
165
211
|
|
|
166
212
|
## Commands
|
|
167
213
|
|
|
168
214
|
`rulebookguard --help` prints the command list. Exit codes: `0` success, `1` failure (unknown id or context, missing config, lint errors, git errors), `2` usage error (unknown command or option, missing or extra argument).
|
|
169
215
|
|
|
216
|
+
| Command | What it does |
|
|
217
|
+
| ------------------------------------------------------ | ------------------------------------------------------------ |
|
|
218
|
+
| [`contexts`](#contexts-l0) | the contexts map (L0) |
|
|
219
|
+
| [`list`](#list---context-c---status-s-l1) | rule ids and summaries (L1) |
|
|
220
|
+
| [`show <id>`](#show-id-l2) | the full rule (L2) |
|
|
221
|
+
| [`for <path>`](#for-path) | the active rules anchored to a file |
|
|
222
|
+
| [`impact <id>`](#impact-id) | the files and tests a rule touches |
|
|
223
|
+
| [`new <context>`](#new-context---prefix-p) | scaffolds the next rule of a context |
|
|
224
|
+
| [`lint`](#lint---base-ref---json) | checks the whole catalog, and optionally changes since a ref |
|
|
225
|
+
| [`hook`](#hook) | Claude Code hook that injects the rules of a file |
|
|
226
|
+
| [`mcp`](#mcp---project-dir) | MCP server for AI agents |
|
|
227
|
+
| [`site:generate`](#sitegenerate---out-dir---base-path) | builds a static site of the rulebook |
|
|
228
|
+
| [`site:run`](#siterun---out-dir---base-path---port-n) | builds the site and serves it locally |
|
|
229
|
+
|
|
170
230
|
The read commands (`contexts`, `list`, `show`, `for`, `impact`) accept `--json` for machine consumers. When the catalog has problems they still run, and print one warning on stderr pointing at `lint`.
|
|
171
231
|
|
|
172
232
|
### `contexts` (L0)
|
|
@@ -184,7 +244,7 @@ $ rulebookguard list --context clients --status active
|
|
|
184
244
|
R-CLI-001 [active] A client document number is unique per country.
|
|
185
245
|
```
|
|
186
246
|
|
|
187
|
-
Exits 1 for an unknown context and 2 for an invalid status.
|
|
247
|
+
Prints `No rules found.` when no rule matches, and `contexts` says so when the catalog has no context yet. Exits 1 for an unknown context and 2 for an invalid status.
|
|
188
248
|
|
|
189
249
|
### `show <id>` (L2)
|
|
190
250
|
|
|
@@ -194,7 +254,9 @@ R-CLI-001: A client document number is unique per country.
|
|
|
194
254
|
context: clients
|
|
195
255
|
status: active
|
|
196
256
|
entities: Client
|
|
197
|
-
anchors:
|
|
257
|
+
anchors:
|
|
258
|
+
src/clients/**/*.ts
|
|
259
|
+
src/api/clients.controller.ts
|
|
198
260
|
supersedes: R-CLI-000
|
|
199
261
|
file: docs/rules/clients/R-CLI-001.md
|
|
200
262
|
|
|
@@ -231,7 +293,7 @@ Tests tagging R-CLI-001 (1):
|
|
|
231
293
|
|
|
232
294
|
Exits 1 for an unknown id.
|
|
233
295
|
|
|
234
|
-
### `new <context
|
|
296
|
+
### `new <context> [--prefix <P>]`
|
|
235
297
|
|
|
236
298
|
Scaffolds the next free id of a context as a `proposed` rule and prints its path. Ids are never reused, and an existing file is never overwritten. The placeholder text of the new rule follows `language` in the config.
|
|
237
299
|
|
|
@@ -240,11 +302,63 @@ $ rulebookguard new clients
|
|
|
240
302
|
docs/rules/clients/R-CLI-004.md
|
|
241
303
|
```
|
|
242
304
|
|
|
243
|
-
|
|
305
|
+
When the context does not exist yet, `new` creates its `_context.md` first and prints both paths. The prefix defaults to the first three letters and digits of the name, upper-cased; `--prefix` sets another one.
|
|
306
|
+
|
|
307
|
+
```sh
|
|
308
|
+
$ rulebookguard new inventory
|
|
309
|
+
docs/rules/inventory/_context.md
|
|
310
|
+
docs/rules/inventory/R-INV-001.md
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Exits 1, writing nothing, when the context has no free id left, when the name is not lowercase letters, digits, `-` or `_`, when the prefix is invalid or already used by another context, or when `--prefix` differs from the prefix of an existing context.
|
|
314
|
+
|
|
315
|
+
### `lint [--base <ref>] [--json]`
|
|
316
|
+
|
|
317
|
+
Checks the whole catalog. Each finding is printed as `<severity> <code> <file>[:line] <message>`, with a `hint:` line under it that says how to fix it, followed by a count.
|
|
318
|
+
|
|
319
|
+
| Severity | Code | Meaning |
|
|
320
|
+
| -------- | ------------------------ | ------------------------------------------------------------------ |
|
|
321
|
+
| error | `untested-rule` | an active rule has no test tagging it |
|
|
322
|
+
| error | `unknown-rule-tag` | a test tags a rule id that does not exist |
|
|
323
|
+
| error | `deprecated-rule-tag` | a test tags a deprecated rule |
|
|
324
|
+
| error | `unclosed-rule-tag` | a `@rule` has no matching `@endrule` |
|
|
325
|
+
| error | `unmatched-endrule-tag` | an `@endrule` has no open `@rule` of the same id |
|
|
326
|
+
| error | `unmatched-anchor` | an anchor glob matches no file |
|
|
327
|
+
| error | `schema` | a config, context or rule file breaks the JSON Schema |
|
|
328
|
+
| error | `duplicate-id` | two rule files share an id |
|
|
329
|
+
| error | `file-name-mismatch` | a rule file is not named `<id>.md` |
|
|
330
|
+
| error | `id-prefix-mismatch` | a rule id does not use its context prefix |
|
|
331
|
+
| error | `context-mismatch` | a `context` field does not match its directory |
|
|
332
|
+
| error | `missing-context-file` | a context directory has no `_context.md` |
|
|
333
|
+
| error | `missing-rules-dir` | `rulesDir` does not exist |
|
|
334
|
+
| error | `pending-implementation` | with `--base`: an active rule changed, but none of its regions did |
|
|
335
|
+
| warning | `context-over-budget` | a context holds more rules than `contextBudget` |
|
|
336
|
+
|
|
337
|
+
```sh
|
|
338
|
+
$ rulebookguard lint
|
|
339
|
+
error untested-rule docs/rules/billing/R-BIL-002.md active rule R-BIL-002 has no test tagging it
|
|
340
|
+
hint: wrap a test in a file matched by testGlobs with "@rule R-BIL-002" and "@endrule R-BIL-002", or set the rule's status to proposed
|
|
341
|
+
warning context-over-budget docs/rules/clients/_context.md context "clients" has 31 rules, more than the contextBudget of 30
|
|
342
|
+
hint: split the context into smaller ones, or raise contextBudget in rulebookguard.config.yaml
|
|
343
|
+
1 error, 1 warning
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Exits 1 when there is any error, and 0 when there are only warnings or nothing at all (`No issues found.`). An invalid `rulebookguard.config.yaml` is reported as `schema` findings too; tags of a rule file that fails the schema are not reported again as `unknown-rule-tag`.
|
|
347
|
+
|
|
348
|
+
With `--base <ref>`, lint also compares `<ref>` with the working tree (`git diff --name-only <ref>` plus untracked files, so uncommitted work counts). A rule whose file changed must come with a change inside at least one of its `@rule` … `@endrule` regions (`git diff -U0 <ref>`; a test file that is still untracked counts whole, and a deletion counts when it falls strictly inside the region), otherwise it is reported as `pending-implementation`. A change elsewhere in the same test file, such as another test or a blank line between regions, does not count; an active rule that no test tags at all is reported only as `untested-rule`. Only rules whose status is `active` after the change are checked: new active rules are checked, `proposed` and `deprecated` rules are not. Outside a git work tree, with a ref git cannot resolve, or when git fails while listing the changes, lint fails with exit code 1.
|
|
349
|
+
|
|
350
|
+
```sh
|
|
351
|
+
$ rulebookguard lint --base origin/main
|
|
352
|
+
error pending-implementation docs/rules/clients/R-CLI-001.md rule R-CLI-001 changed since origin/main but none of the tests tagging it changed
|
|
353
|
+
hint: update or add a test inside an "@rule R-CLI-001" … "@endrule R-CLI-001" region that covers the changed rule
|
|
354
|
+
1 error, 0 warnings
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
`--json` prints `{ "findings": [{ "severity", "code", "file", "line"?, "message", "hint" }], "errors", "warnings" }`.
|
|
244
358
|
|
|
245
359
|
### `hook`
|
|
246
360
|
|
|
247
|
-
A Claude Code hook for `PreToolUse` and `PostToolUse` events of tools that take a `file_path` (the Claude Code plugin
|
|
361
|
+
A Claude Code hook for `PreToolUse` and `PostToolUse` events of tools that take a `file_path` (the [Claude Code plugin](#claude-code-plugin) wires it up). It reads the hook event as JSON on stdin, takes `tool_input.file_path` (relative paths resolve against the event's `cwd`), finds `rulebookguard.config.yaml` by walking up from that file, and prints the active rules anchored to it as additional context for Claude, under the same `hookEventName`:
|
|
248
362
|
|
|
249
363
|
```sh
|
|
250
364
|
$ echo '{"hook_event_name":"PostToolUse","tool_name":"Read","tool_input":{"file_path":"/repo/src/billing/invoice.go"}}' | rulebookguard hook
|
|
@@ -253,7 +367,10 @@ $ echo '{"hook_event_name":"PostToolUse","tool_name":"Read","tool_input":{"file_
|
|
|
253
367
|
|
|
254
368
|
Claude Code adds the context next to the tool result, so a `PostToolUse` hook on `Read` delivers the rules together with the file contents, before Claude writes a change (Claude Code requires reading a file before editing it).
|
|
255
369
|
|
|
256
|
-
Each rule comes with its id, context, summary, rule file and full text; when the texts add up to more than 10,000 characters only the summaries are listed, with a pointer to `rulebookguard show <id>`. The header follows `language` in the config.
|
|
370
|
+
Each rule comes with its id, context, summary, rule file and full text; when the texts add up to more than 10,000 characters only the summaries are listed, with a pointer to `rulebookguard show <id>`. The header follows `language` in the config.
|
|
371
|
+
|
|
372
|
+
> [!NOTE]
|
|
373
|
+
> The hook never blocks a tool and never answers the permission prompt: with no config, a path outside the rulebook root, no active rule, another event, unreadable input or any error, it prints nothing and exits 0.
|
|
257
374
|
|
|
258
375
|
### `mcp [--project <dir>]`
|
|
259
376
|
|
|
@@ -268,11 +385,12 @@ The rulebook is found from `--project` (default: the current directory). A comma
|
|
|
268
385
|
|
|
269
386
|
### `site:generate [--out <dir>] [--base <path>]`
|
|
270
387
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
388
|
+
> [!IMPORTANT]
|
|
389
|
+
> The site commands build with Astro, which lives in a separate package so that installing the linter does not download it. Install it next to `rulebookguard` (it requires Node.js 22.23.4 or later):
|
|
390
|
+
>
|
|
391
|
+
> ```sh
|
|
392
|
+
> npm install --save-dev rulebookguard @rulebookguard/site
|
|
393
|
+
> ```
|
|
276
394
|
|
|
277
395
|
Without it, the site commands fail, exit 1, with `The site commands need the @rulebookguard/site package. Install it next to rulebookguard: npm install --save-dev @rulebookguard/site`. One-off, without installing: `npx -p rulebookguard -p @rulebookguard/site rulebookguard site:run`.
|
|
278
396
|
|
|
@@ -304,46 +422,10 @@ The site has:
|
|
|
304
422
|
- **Health:** the `lint` findings when the site was built.
|
|
305
423
|
- Full-text search (Pagefind).
|
|
306
424
|
|
|
307
|
-
Generated text and the interface follow `language` in the config. The site is a view: rules are still edited in their files, through pull requests.
|
|
308
|
-
|
|
309
|
-
### `lint [--base <ref>] [--json]`
|
|
310
|
-
|
|
311
|
-
Checks the whole catalog. Each finding is printed as `<severity> <code> <file>[:line] <message>`, followed by a count.
|
|
312
|
-
|
|
313
|
-
| Severity | Code | Meaning |
|
|
314
|
-
| -------- | ------------------------ | ---------------------------------------------------------------- |
|
|
315
|
-
| error | `untested-rule` | an active rule has no test tagging it |
|
|
316
|
-
| error | `unknown-rule-tag` | a test tags a rule id that does not exist |
|
|
317
|
-
| error | `deprecated-rule-tag` | a test tags a deprecated rule |
|
|
318
|
-
| error | `unmatched-anchor` | an anchor glob matches no file |
|
|
319
|
-
| error | `schema` | a config, context or rule file breaks the JSON Schema |
|
|
320
|
-
| error | `duplicate-id` | two rule files share an id |
|
|
321
|
-
| error | `file-name-mismatch` | a rule file is not named `<id>.md` |
|
|
322
|
-
| error | `id-prefix-mismatch` | a rule id does not use its context prefix |
|
|
323
|
-
| error | `context-mismatch` | a `context` field does not match its directory |
|
|
324
|
-
| error | `missing-context-file` | a context directory has no `_context.md` |
|
|
325
|
-
| error | `missing-rules-dir` | `rulesDir` does not exist |
|
|
326
|
-
| error | `pending-implementation` | with `--base`: an active rule changed, but none of its tests did |
|
|
327
|
-
| warning | `context-over-budget` | a context holds more rules than `contextBudget` |
|
|
328
|
-
|
|
329
|
-
```sh
|
|
330
|
-
$ rulebookguard lint
|
|
331
|
-
error untested-rule docs/rules/billing/R-BIL-002.md active rule R-BIL-002 has no test tagging it
|
|
332
|
-
warning context-over-budget docs/rules/clients/_context.md context "clients" has 31 rules, more than the contextBudget of 30
|
|
333
|
-
1 error, 1 warning
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
Exits 1 when there is any error, and 0 when there are only warnings or nothing at all (`No issues found.`). An invalid `rulebookguard.config.yaml` is reported as `schema` findings too; tags of a rule file that fails the schema are not reported again as `unknown-rule-tag`.
|
|
337
|
-
|
|
338
|
-
With `--base <ref>`, lint also compares `<ref>` with the working tree (`git diff --name-only <ref>` plus untracked files, so uncommitted work counts). A rule whose file changed must come with a change in at least one test that tags it, otherwise it is reported as `pending-implementation`; an active rule that no test tags at all is reported only as `untested-rule`. Only rules whose status is `active` after the change are checked: new active rules are checked, `proposed` and `deprecated` rules are not. Outside a git work tree, with a ref git cannot resolve, or when git fails while listing the changes, lint fails with exit code 1.
|
|
425
|
+
Generated text and the interface follow `language` in the config. The site is a view: rules are still edited in their files, through pull requests.
|
|
339
426
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
error pending-implementation docs/rules/clients/R-CLI-001.md rule R-CLI-001 changed since origin/main but none of the tests tagging it changed
|
|
343
|
-
1 error, 0 warnings
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
`--json` prints `{ "findings": [{ "severity", "code", "file", "line"?, "message" }], "errors", "warnings" }`.
|
|
427
|
+
> [!WARNING]
|
|
428
|
+
> `site` replaces `--out` only when it is empty or was written by an earlier `site`; any other non-empty directory makes it fail, exit 1, without touching it. Add the output directory to `.gitignore` unless you mean to commit it.
|
|
347
429
|
|
|
348
430
|
## CI recipe
|
|
349
431
|
|
|
@@ -363,7 +445,8 @@ jobs:
|
|
|
363
445
|
- run: npx --yes rulebookguard@0.1.0 lint --base origin/main
|
|
364
446
|
```
|
|
365
447
|
|
|
366
|
-
|
|
448
|
+
> [!TIP]
|
|
449
|
+
> Pin the version, or add rulebookguard as a dev dependency and run it from the lockfile, so a new release never changes CI results by itself.
|
|
367
450
|
|
|
368
451
|
## Claude Code plugin
|
|
369
452
|
|
|
@@ -383,18 +466,3 @@ claude plugin install rulebookguard@rulebookguard
|
|
|
383
466
|
Or, inside Claude Code 2.1.275 or later: `/plugin install rulebookguard --marketplace JuanCJR/rulebookguard`.
|
|
384
467
|
|
|
385
468
|
The plugin runs the project's own rulebookguard (`node_modules/rulebookguard`, in the project directory or a parent) when its version is at least the plugin's; otherwise it runs `npx rulebookguard@<plugin version>`, which needs Node.js and network access the first time. The hooks never block a tool: any failure, including a timeout, lets it go ahead without rules.
|
|
386
|
-
|
|
387
|
-
## Releasing
|
|
388
|
-
|
|
389
|
-
Releases are published to npm from GitHub Actions with [trusted publishing](https://docs.npmjs.com/trusted-publishers), so no npm token is stored and every version carries a provenance attestation. To release, bump `version` in `package.json` and in `plugin/.claude-plugin/plugin.json` (a test keeps them equal), merge it to `main`, then push a matching tag:
|
|
390
|
-
|
|
391
|
-
```sh
|
|
392
|
-
git tag v0.1.0
|
|
393
|
-
git push origin v0.1.0
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
The `Release` workflow fails without publishing when the tag does not match the package version, or when `vp check`, `vp test` or `vp pack` fails.
|
|
397
|
-
|
|
398
|
-
## License
|
|
399
|
-
|
|
400
|
-
MIT
|