rulebookguard 0.7.0 → 0.9.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 CHANGED
@@ -52,8 +52,8 @@ npm install --save-dev rulebookguard
52
52
 
53
53
  A first rule in four steps:
54
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`.
55
+ 1. Set up the rulebook: `npx rulebookguard init clients --ci` writes `rulebookguard.config.yaml`, the `clients` context and a CI workflow (see [Configuration](#configuration) to adjust `testGlobs`).
56
+ 2. Scaffold a rule: `npx rulebookguard new clients` creates `docs/rules/clients/R-CLI-001.md` as `proposed`.
57
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
58
  4. Run `npx rulebookguard lint`, and add it to CI with the [CI recipe](#ci-recipe).
59
59
 
@@ -144,15 +144,19 @@ Scenario: Duplicate document in the same country
144
144
  ```
145
145
  ````
146
146
 
147
- | Field | Required | Meaning |
148
- | ------------ | -------- | ---------------------------------------------------------------- |
149
- | `id` | yes | `R-<PREFIX>-<NNN>`, three digits, unique, equal to the file name |
150
- | `context` | yes | the context directory the rule lives in |
151
- | `entities` | yes | domain entities the rule talks about |
152
- | `status` | yes | `proposed`, `active` or `deprecated` |
153
- | `supersedes` | no | id of the rule this one replaces |
154
- | `anchors` | yes | repo-relative globs of the code that implements the rule |
155
- | `summary` | yes | one line; this is what `list` shows |
147
+ | Field | Required | Meaning |
148
+ | --------------- | -------- | ------------------------------------------------------------------------------ |
149
+ | `id` | yes | `R-<PREFIX>-<NNN>`, three digits, unique, equal to the file name |
150
+ | `context` | yes | the context directory the rule lives in |
151
+ | `entities` | yes | domain entities the rule talks about |
152
+ | `status` | yes | `proposed`, `active` or `deprecated` |
153
+ | `supersedes` | no | id of the rule this one replaces |
154
+ | `dependsOn` | no | ids of the rules this one needs in force; no cycles |
155
+ | `conflictsWith` | no | ids of the rules that cannot be active together with this one |
156
+ | `origin` | no | `authored` (written from intent) or `extracted` (lifted from code) |
157
+ | `evidence` | no | where the rule shows in code or tests, for example `src/x.ts validateDocument` |
158
+ | `anchors` | yes | repo-relative globs of the code that implements the rule |
159
+ | `summary` | yes | one line; this is what `list` shows |
156
160
 
157
161
  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.
158
162
 
@@ -163,7 +167,7 @@ A test links to a rule with a region: `@rule <ID>` opens it and `@endrule <ID>`
163
167
  > [!IMPORTANT]
164
168
  > 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.
165
169
 
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.
170
+ Markers count only on a line that starts with a comment: `//`, `/*`, `*`, `#`, `--`, `;`, `<!--` or `%`, after any indentation. A marker in a test name, a string or a comment after code on the same line is not a link, so test files may quote `@rule` freely. Inside a `/* … */` block, start each marker line with `*`, as in the Java example below; a bare inner line is not taken as a comment.
167
171
 
168
172
  TypeScript or JavaScript:
169
173
 
@@ -209,25 +213,59 @@ SELECT count(*) FROM invoices WHERE number IS NULL;
209
213
  -- @endrule R-BIL-002
210
214
  ```
211
215
 
216
+ ### Marking the code that enforces a rule with `@implements` … `@endimplements`
217
+
218
+ Anchors say which files a rule governs; an `@implements <ID>` … `@endimplements <ID>` region says which lines enforce it, so the link moves with the code instead of pointing at a line number. Regions are optional and follow the same comment rule, nesting and pairing as `@rule` regions. Only files matched by some rule's `anchors` are scanned, and a region must sit in a file its own rule anchors.
219
+
220
+ ```go
221
+ // @implements R-CLI-001
222
+ func validateDocument(country, number string) error {
223
+ // ...
224
+ }
225
+ // @endimplements R-CLI-001
226
+ ```
227
+
228
+ `lint` reports `unclosed-implements-tag`, `unmatched-endimplements-tag`, `unknown-implements-tag`, `deprecated-implements-tag` and `implements-outside-anchors` (see the table under `lint`).
229
+
212
230
  ## Commands
213
231
 
214
232
  `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).
215
233
 
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
-
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`.
234
+ | Command | What it does |
235
+ | --------------------------------------------------------------------- | ------------------------------------------------------------ |
236
+ | [`init <context>`](#init-context---prefix-p---language-enes---ci) | sets up the config, a first context and optionally CI |
237
+ | [`contexts`](#contexts-l0) | the contexts map (L0) |
238
+ | [`list`](#list---context-c---status-s-l1) | rule ids and summaries (L1) |
239
+ | [`show <id>`](#show-id-l2) | the full rule (L2) |
240
+ | [`search <text>`](#search-text---context-c---json) | rules holding every word of a text |
241
+ | [`for <path>`](#for-path) | the active rules anchored to a file |
242
+ | [`impact <id>`](#impact-id) | the files and tests a rule touches |
243
+ | [`new <context>`](#new-context---prefix-p) | scaffolds the next rule of a context |
244
+ | [`lint`](#lint---base-ref---json) | checks the whole catalog, and optionally changes since a ref |
245
+ | [`export gherkin`](#export-gherkin---out-dir) | writes the rules' scenarios as Cucumber `.feature` files |
246
+ | [`export cursor\|copilot\|kiro`](#export-cursorcopilotkiro---out-dir) | writes the active rules as rule files of other AI tools |
247
+ | [`export agents-md`](#export-agents-md---out-dir) | keeps an index of the active rules in `AGENTS.md` |
248
+ | [`hook`](#hook) | Claude Code hook that injects the rules of a file |
249
+ | [`mcp`](#mcp---project-dir) | MCP server for AI agents |
250
+ | [`site:generate`](#sitegenerate---out-dir---base-path) | builds a static site of the rulebook |
251
+ | [`site:run`](#siterun---out-dir---base-path---port-n) | builds the site and serves it locally |
252
+
253
+ The read commands (`contexts`, `list`, `show`, `search`, `for`, `impact`) accept `--json` for machine consumers. When the catalog has problems they still run, and print one warning on stderr pointing at `lint`.
254
+
255
+ ### `init <context> [--prefix <P>] [--language <en|es>] [--ci]`
256
+
257
+ Sets up a rulebook in the current directory and prints the paths it wrote: `rulebookguard.config.yaml`, then the context, the same one `new` would create (`--prefix` works the same way).
258
+
259
+ ```sh
260
+ $ rulebookguard init clients --ci
261
+ rulebookguard.config.yaml
262
+ docs/rules/clients/_context.md
263
+ .github/workflows/rulebookguard.yml
264
+ ```
265
+
266
+ The config uses `rulesDir: docs/rules` and these `testGlobs`, which you can narrow afterwards: `**/*.test.*`, `**/*.spec.*`, `**/test_*.py` and `**/*_test.go`. `--language` writes `language` into the config, so the context template and later messages follow it. `--ci` also writes the GitHub Actions workflow of the [CI recipe](#ci-recipe), pinned to the running version.
267
+
268
+ Exits 1, writing nothing, when `rulebookguard.config.yaml` (or, with `--ci`, the workflow) already exists in the current directory, or when the context name or prefix is invalid. Exits 2 for a `--language` other than `en` or `es`.
231
269
 
232
270
  ### `contexts` (L0)
233
271
 
@@ -264,7 +302,18 @@ file: docs/rules/clients/R-CLI-001.md
264
302
  ...
265
303
  ```
266
304
 
267
- Exits 1 for an unknown id.
305
+ `supersedes`, `dependsOn`, `conflictsWith`, `origin` and `evidence` lines appear only when the rule sets them; `--json` always has `supersedes` and `origin` (or `null`), and `dependsOn`, `conflictsWith` and `evidence` (or `[]`). An `implementations:` section lists the [`@implements` regions](#marking-the-code-that-enforces-a-rule-with-implements--endimplements) of the rule as `file:line-endLine`, read from the files its anchors match; `--json` has them as `implementations: [{ file, line, endLine }]` (or `[]`). Exits 1 for an unknown id.
306
+
307
+ ### `search <text> [--context <c>] [--json]`
308
+
309
+ Rules in any status whose id, summary, entities or text hold every word of `<text>`, ignoring case. Plain substring search, no index or embeddings: enough to find an existing rule before writing a new one. `--context` narrows it to one context. Rows and `--json` (`{ "rules": [...] }`) look like `list`; no match prints `No rules found.`
310
+
311
+ ```sh
312
+ $ rulebookguard search "document number"
313
+ R-CLI-001 [active] A client document number is unique per country.
314
+ ```
315
+
316
+ Exits 2 for an empty text and 1 for an unknown context.
268
317
 
269
318
  ### `for <path>`
270
319
 
@@ -272,10 +321,10 @@ Active rules whose anchors match a path, relative to the current directory. This
272
321
 
273
322
  ```sh
274
323
  $ rulebookguard for src/clients/create.ts
275
- R-CLI-001 (clients): A client document number is unique per country.
324
+ R-CLI-001 (clients): A client document number is unique per country. (lines 12-30)
276
325
  ```
277
326
 
278
- Exits 1 when the path is outside the rulebook root.
327
+ When the file holds `@implements` regions of a rule, its line ends with their line ranges; `--json` rows carry them as `regions: [{ line, endLine }]` (or `[]`). Exits 1 when the path is outside the rulebook root.
279
328
 
280
329
  ### `impact <id>`
281
330
 
@@ -316,23 +365,31 @@ Exits 1, writing nothing, when the context has no free id left, when the name is
316
365
 
317
366
  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
367
 
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` |
368
+ | Severity | Code | Meaning |
369
+ | -------- | ----------------------------- | ------------------------------------------------------------------ |
370
+ | error | `untested-rule` | an active rule has no test tagging it |
371
+ | error | `unknown-rule-tag` | a test tags a rule id that does not exist |
372
+ | error | `deprecated-rule-tag` | a test tags a deprecated rule |
373
+ | error | `unclosed-rule-tag` | a `@rule` has no matching `@endrule` |
374
+ | error | `unmatched-endrule-tag` | an `@endrule` has no open `@rule` of the same id |
375
+ | error | `unclosed-implements-tag` | an `@implements` has no matching `@endimplements` |
376
+ | error | `unmatched-endimplements-tag` | an `@endimplements` has no open `@implements` of the same id |
377
+ | error | `unknown-implements-tag` | code is marked as implementing a rule id that does not exist |
378
+ | error | `deprecated-implements-tag` | code is marked as implementing a deprecated rule |
379
+ | error | `implements-outside-anchors` | an `@implements` region sits in a file its rule does not anchor |
380
+ | error | `unmatched-anchor` | an anchor glob matches no file |
381
+ | error | `schema` | a config, context or rule file breaks the JSON Schema |
382
+ | error | `duplicate-id` | two rule files share an id |
383
+ | error | `file-name-mismatch` | a rule file is not named `<id>.md` |
384
+ | error | `id-prefix-mismatch` | a rule id does not use its context prefix |
385
+ | error | `context-mismatch` | a `context` field does not match its directory |
386
+ | error | `missing-context-file` | a context directory has no `_context.md` |
387
+ | error | `missing-rules-dir` | `rulesDir` does not exist |
388
+ | error | `pending-implementation` | with `--base`: an active rule changed, but none of its regions did |
389
+ | error | `invalid-rule-reference` | `dependsOn` or `conflictsWith` names an unknown rule or itself |
390
+ | error | `dependency-cycle` | `dependsOn` links form a cycle (reported once, at its lowest id) |
391
+ | error | `active-rule-conflict` | two active rules where one lists the other in `conflictsWith` |
392
+ | warning | `context-over-budget` | a context holds more rules than `contextBudget` |
336
393
 
337
394
  ```sh
338
395
  $ rulebookguard lint
@@ -356,6 +413,59 @@ error pending-implementation docs/rules/clients/R-CLI-001.md rule R-CLI-001 chan
356
413
 
357
414
  `--json` prints `{ "findings": [{ "severity", "code", "file", "line"?, "message", "hint" }], "errors", "warnings" }`.
358
415
 
416
+ ### `export gherkin [--out <dir>]`
417
+
418
+ Writes the ` ```gherkin ` blocks of every rule that is not deprecated as `<out>/<context>/<id>.feature`, relative to the current directory (`--out` defaults to `features/rules`). Each file is tagged with the rule id, so Cucumber runs or filters a rule's scenarios with `--tags @R-CLI-001`. Blocks hold scenarios; their `# language:` lines are dropped, and with `language: es` the file starts with `# language: es` and uses `Característica:`.
419
+
420
+ ```sh
421
+ $ rulebookguard export gherkin
422
+ features/rules/clients/R-CLI-001.feature
423
+ $ cat features/rules/clients/R-CLI-001.feature
424
+ # Generated by "rulebookguard export gherkin" from docs/rules/clients/R-CLI-001.md; edit the rule instead.
425
+ @R-CLI-001
426
+ Feature: R-CLI-001 A client document number is unique per country.
427
+
428
+ Scenario: Duplicate document in the same country
429
+ Given a client with document "00123" in "VE"
430
+ When another client is created with document "123" in "VE"
431
+ Then the creation is rejected with "document already registered"
432
+ ```
433
+
434
+ Generated files are overwritten; nothing else in the output directory is touched, so a feature of a rule that was deprecated or lost its scenarios stays until you delete it. Prints `No rule has Gherkin scenarios.` when there is nothing to write. The tags are Cucumber tags, not `@rule` regions: link the step definitions to the rule with `@rule` … `@endrule` as with any test.
435
+
436
+ ### `export cursor|copilot|kiro [--out <dir>]`
437
+
438
+ Writes every active rule with anchors as one file that the tool attaches when it works on a matching path, so agents other than Claude Code get the same rules. The files go under the rulebook root by default, or directly in `--out`, relative to the current directory:
439
+
440
+ | Format | File | Scoped with |
441
+ | --------- | --------------------------------------------------------- | --------------------------------------------------- |
442
+ | `cursor` | `.cursor/rules/rulebookguard-<id>.mdc` | `globs`, comma-separated, with `alwaysApply: false` |
443
+ | `copilot` | `.github/instructions/rulebookguard-<id>.instructions.md` | `applyTo`, comma-separated |
444
+ | `kiro` | `.kiro/steering/rulebookguard-<id>.md` | `inclusion: fileMatch` and `fileMatchPattern` |
445
+
446
+ ```sh
447
+ $ rulebookguard export kiro
448
+ .kiro/steering/rulebookguard-R-CLI-001.md
449
+ $ cat .kiro/steering/rulebookguard-R-CLI-001.md
450
+ ---
451
+ inclusion: fileMatch
452
+ fileMatchPattern: "src/clients/**/*.go"
453
+ ---
454
+
455
+ <!-- Generated by "rulebookguard export kiro" from docs/rules/clients/R-CLI-001.md; edit the rule instead. -->
456
+
457
+ **R-CLI-001:** A client document number is unique per country.
458
+
459
+ # Unique document number
460
+ …
461
+ ```
462
+
463
+ Run it again after changing rules, for example in CI or before committing: it overwrites the generated files and removes, printing `removed <path>`, those of rules that are no longer active. Only a file named after a rule id, with the generated line of that format right after its frontmatter, is removed. If the file of an active rule lacks that line, it was not generated, so the command fails with exit code 1 before writing or removing anything. An active rule without anchors has no path to attach to, so it is skipped with a warning on stderr. Prints `No active rule to export.` when there is nothing to write, or `No active rule with anchors to export.` when no active rule has anchors. Brace anchors are expanded into one pattern per option; an anchor with a literal comma in a file name cannot be written in the comma-separated formats.
464
+
465
+ ### `export agents-md [--out <dir>]`
466
+
467
+ Keeps an index of the active rules in `AGENTS.md`, at the rulebook root or in `--out`. `AGENTS.md` has no path scoping and is always read, so it holds each rule's id, anchors (or `no anchors`), summary and rule file, not the full text. The index lives between `<!-- rulebookguard:start -->` and `<!-- rulebookguard:end -->`: the rest of the file is kept, the block is appended the first time and replaced after that, and it is removed when no rule is active. Anything other than one start marker followed by one end marker, or neither, fails with exit code 1 and leaves the file untouched.
468
+
359
469
  ### `hook`
360
470
 
361
471
  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`:
@@ -367,21 +477,25 @@ $ echo '{"hook_event_name":"PostToolUse","tool_name":"Read","tool_input":{"file_
367
477
 
368
478
  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).
369
479
 
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.
480
+ Each rule comes with its id, context, summary, rule file, the lines of its `@implements` regions in that file (when there are any) 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
481
 
372
482
  > [!NOTE]
373
483
  > 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.
374
484
 
375
485
  ### `mcp [--project <dir>]`
376
486
 
377
- An [MCP](https://modelcontextprotocol.io) server over stdio for AI agents, with two read-only tools:
487
+ An [MCP](https://modelcontextprotocol.io) server over stdio for AI agents, with read-only tools:
378
488
 
379
- | Tool | Input | Answers |
380
- | ------------ | ------------------------------------------------------ | --------------------------------------------- |
381
- | `rules_for` | `path`: absolute, or relative to the project directory | what `rulebookguard for <path> --json` prints |
382
- | `rules_show` | `id`: a rule id | what `rulebookguard show <id> --json` prints |
489
+ | Tool | Input | Answers |
490
+ | -------------- | ------------------------------------------------------ | ------------------------------------------------------------- |
491
+ | `rules_for` | `path`: absolute, or relative to the project directory | what `rulebookguard for <path> --json` prints |
492
+ | `rules_show` | `id`: a rule id | what `rulebookguard show <id> --json` prints |
493
+ | `contexts` | none | what `rulebookguard contexts --json` prints |
494
+ | `rules_list` | `context`?, `status`? | what `rulebookguard list --json` prints with those options |
495
+ | `rules_search` | `text`, `context`? | what `rulebookguard search <text> --json` prints |
496
+ | `lint` | `base`?: a git ref | what `rulebookguard lint --json` prints, with `--base` if set |
383
497
 
384
- The rulebook is found from `--project` (default: the current directory). A command that would fail answers a tool error carrying the same message the CLI prints, for example `Unknown rule id "R-NOPE-001". Run "rulebookguard list" to list them.`
498
+ The rulebook is found from `--project` (default: the current directory). A command that would fail answers a tool error carrying the same message the CLI prints, for example `Unknown rule id "R-NOPE-001". Run "rulebookguard list" to list them.` Lint findings are data, not a failure: `lint` answers its JSON report even when it has errors, and fails only when lint cannot run.
385
499
 
386
500
  ### `site:generate [--out <dir>] [--base <path>]`
387
501
 
@@ -418,7 +532,7 @@ The site has:
418
532
  - **Home:** the contexts map, with how many rules of each status every context holds.
419
533
  - **A page per context** with its description and rules.
420
534
  - **All rules:** one table filterable by text, context and status.
421
- - **A page per rule:** summary, status, entities, rule file, the supersedes chain both ways, the rule text, its anchors and the files they match, the tests tagging it, and the git history of the rule file.
535
+ - **A page per rule:** summary, status, entities, rule file, the supersedes chain both ways, the origin (an `extracted` rule still `proposed` is flagged as not validated by the business yet) and evidence, the rule text, its anchors and the files they match, the tests tagging it, and the git history of the rule file.
422
536
  - **Health:** the `lint` findings when the site was built.
423
537
  - Full-text search (Pagefind).
424
538
 
@@ -429,7 +543,7 @@ Generated text and the interface follow `language` in the config. The site is a
429
543
 
430
544
  ## CI recipe
431
545
 
432
- Run the linter on every pull request, comparing against the main branch. The full history is needed so that `origin/main` exists.
546
+ Run the linter on every pull request, comparing against the branch it targets. The full history is needed so that the base branch exists. `rulebookguard init --ci` writes this file as `.github/workflows/rulebookguard.yml`.
433
547
 
434
548
  ```yaml
435
549
  name: rules
@@ -442,7 +556,10 @@ jobs:
442
556
  - uses: actions/checkout@v7.0.1
443
557
  with:
444
558
  fetch-depth: 0
445
- - run: npx --yes rulebookguard@0.1.0 lint --base origin/main
559
+ - uses: actions/setup-node@v6
560
+ with:
561
+ node-version: 22
562
+ - run: npx --yes rulebookguard@0.9.0 lint --base origin/${{ github.base_ref }}
446
563
  ```
447
564
 
448
565
  > [!TIP]
@@ -453,8 +570,11 @@ jobs:
453
570
  The plugin in [`plugin/`](plugin) connects the rulebook to Claude Code:
454
571
 
455
572
  - **Hooks:** after every `Read` and before every `Write`, it runs `rulebookguard hook`, so Claude gets the active rules anchored to a file together with its contents, before it writes a change. A `Write` of a new file, which needs no `Read`, gets them right after the file is created.
456
- - **MCP server:** the `rules_for` and `rules_show` tools from `rulebookguard mcp`, for rules of files Claude has not opened yet.
457
- - **Skill** `rulebookguard:rules`: the protocol, that is, follow the rules, and change a rule (then its tests, then the code) only when the user asks for the behavior change.
573
+ - **MCP server:** the tools of `rulebookguard mcp` (`rules_for`, `rules_show`, `contexts`, `rules_list`, `rules_search`, `lint`), for rules of files Claude has not opened yet and for working on the rulebook itself.
574
+ - **Skill** `rulebookguard:rules`: the protocol, that is, follow the rules, and change a rule (then its tests, then the code, marked with `@implements`) only when the user asks for the behavior change.
575
+ - **Skill** `rulebookguard:rule-writing`: the shared writing guide, so a rule reads the same whoever writes it: one verifiable behavior, a one-line summary in business language, Gherkin examples, and `origin` and `evidence` for rules lifted from code.
576
+ - **Agent** `rulebookguard:rule-miner`: lifts the business rules existing code already enforces, one context at a time. It writes them as `proposed` rules with `origin: extracted`, evidence as `path symbol` and questions for the business, through `rulebookguard new`, and marks the code that enforces each rule with `@implements` … `@endimplements`. It checks existing rules first to avoid duplicates, stays within `contextBudget`, wraps tests that already verify a rule in `@rule` … `@endrule`, and finishes only when `lint` has no errors of its own. It never activates a rule or edits existing rules, and adds only marker comments to code: a person reviews the diff and activates what holds. Ask for it by name, for example "use rule-miner on the clients context".
577
+ - **Agent** `rulebookguard:rule-author`: designs the rules a feature needs before its code. By default it writes nothing: it reads the rulebook by levels and returns a rule change plan with new rules and their Gherkin, changed rules as diffs, deprecations, conflicts with active rules, test skeletons and open questions, then stops for a person to approve. Called again with the approved plan, it writes the new rules as `proposed` through `rulebookguard new`, applies the approved diffs and finishes when `lint` has no errors of its own. Tests and code come after, as the `rules` skill says. Ask for it by name, for example "use rule-author for this feature: …".
458
578
 
459
579
  Install it from this repository's marketplace:
460
580
 
@@ -465,4 +585,4 @@ claude plugin install rulebookguard@rulebookguard
465
585
 
466
586
  Or, inside Claude Code 2.1.275 or later: `/plugin install rulebookguard --marketplace JuanCJR/rulebookguard`.
467
587
 
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.
588
+ 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. In a checkout of rulebookguard itself it runs the checkout's own built CLI (`dist/cli.mjs`, from `vp pack`), whatever its version. The hooks never block a tool: any failure, including a timeout, lets it go ahead without rules.
@@ -1,4 +1,4 @@
1
- import { g as createGlobMatcher, y as toRepoPath } from "./lint-DfibQDW3.mjs";
1
+ import { v as createGlobMatcher, x as toRepoPath } from "./lint-CaJnNLmU.mjs";
2
2
  // src/catalog/anchors.ts
3
3
  function rulesForPath(rules, path) {
4
4
  const target = toRepoPath(path);