rulebookguard 0.11.0 → 0.12.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 +28 -6
- package/dist/{anchors-jf2bKfyg.mjs → anchors-JxhjEzRN.mjs} +1 -1
- package/dist/cli.mjs +88 -6
- package/dist/index.mjs +2 -2
- package/dist/{lint-BPdk6XxH.mjs → lint-HEXtuztv.mjs} +2 -0
- package/dist/{site-B7g8lbjx.mjs → site-COuF4ib4.mjs} +1 -1
- package/npm-shrinkwrap.json +3 -3
- package/package.json +6 -3
- package/plugin/agents/rule-author.md +58 -0
- package/plugin/agents/rule-miner.md +43 -0
- package/plugin/plugin.json +13 -0
- package/plugin/skills/rule-writing/SKILL.md +38 -0
- package/plugin/skills/rules/SKILL.md +28 -0
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Requires Node.js 22 or later.
|
|
|
43
43
|
rulebookguard does not need to be a dependency of your project. Nothing in your code imports it, so you can run it with `npx`, pinning the version so that every machine and CI run the same one:
|
|
44
44
|
|
|
45
45
|
```sh
|
|
46
|
-
npx rulebookguard@0.
|
|
46
|
+
npx rulebookguard@0.12.0 lint
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Each release locks its own dependencies for npm and `npx`, so a pinned version keeps running the same code. Other package managers, such as `pnpm dlx`, ignore that lock and resolve the dependency ranges. Your `package.json` and lockfile stay untouched.
|
|
@@ -56,10 +56,10 @@ npm install --save-dev rulebookguard
|
|
|
56
56
|
|
|
57
57
|
A first rule in four steps:
|
|
58
58
|
|
|
59
|
-
1. Set up the rulebook: `npx rulebookguard@0.
|
|
60
|
-
2. Scaffold a rule: `npx rulebookguard@0.
|
|
59
|
+
1. Set up the rulebook: `npx rulebookguard@0.12.0 init clients --ci` writes `rulebookguard.config.yaml`, the `clients` context and a CI workflow (see [Configuration](#configuration) to adjust `testGlobs`).
|
|
60
|
+
2. Scaffold a rule: `npx rulebookguard@0.12.0 new clients` creates `docs/rules/clients/R-CLI-001.md` as `proposed`.
|
|
61
61
|
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`.
|
|
62
|
-
4. Run `npx rulebookguard@0.
|
|
62
|
+
4. Run `npx rulebookguard@0.12.0 lint`, and add it to CI with the [CI recipe](#ci-recipe).
|
|
63
63
|
|
|
64
64
|
## Configuration
|
|
65
65
|
|
|
@@ -250,6 +250,7 @@ func validateDocument(country, number string) error {
|
|
|
250
250
|
| [`export gherkin`](#export-gherkin---out-dir) | writes the rules' scenarios as Cucumber `.feature` files |
|
|
251
251
|
| [`export cursor\|copilot\|kiro`](#export-cursorcopilotkiro---out-dir) | writes the active rules as rule files of other AI tools |
|
|
252
252
|
| [`export agents-md`](#export-agents-md---out-dir) | keeps an index of the active rules in `AGENTS.md` |
|
|
253
|
+
| [`export kiro-agents`](#export-kiro-agents---out-dir) | writes the `rule-author` and `rule-miner` agents for Kiro |
|
|
253
254
|
| [`hook`](#hook) | Claude Code hook that injects the rules of a file |
|
|
254
255
|
| [`mcp`](#mcp---project-dir) | MCP server for AI agents |
|
|
255
256
|
| [`site:generate`](#sitegenerate---out-dir---base-path) | builds a static site of the rulebook |
|
|
@@ -488,6 +489,10 @@ Run it again after changing rules, for example in CI or before committing: it ov
|
|
|
488
489
|
|
|
489
490
|
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, along with the file if nothing else is left (printing `removed AGENTS.md`). Anything other than one start marker followed by one end marker, or neither, fails with exit code 1 and leaves the file untouched.
|
|
490
491
|
|
|
492
|
+
### `export kiro-agents [--out <dir>]`
|
|
493
|
+
|
|
494
|
+
Writes `rule-author` and `rule-miner` as [Kiro custom agents](https://kiro.dev/docs/custom-agents/) in `.kiro/agents/rulebookguard-<name>.md`, under the rulebook root or directly in `--out`. Each file carries its own MCP server, pinned to the version of the CLI that wrote it, and the rule writing guide, so it works without the [Kiro power](#kiro). Run it again after upgrading. Other files in the directory are left alone, and if `rulebookguard-rule-author.md` or `rulebookguard-rule-miner.md` lacks the generated line, the command fails with exit code 1 before writing anything.
|
|
495
|
+
|
|
491
496
|
### `hook`
|
|
492
497
|
|
|
493
498
|
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`:
|
|
@@ -528,7 +533,7 @@ The rulebook is found from `--project` (default: the current directory). A comma
|
|
|
528
533
|
> npm install --save-dev rulebookguard @rulebookguard/site
|
|
529
534
|
> ```
|
|
530
535
|
|
|
531
|
-
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@0.
|
|
536
|
+
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@0.12.0 -p @rulebookguard/site@0.12.0 rulebookguard site:run`.
|
|
532
537
|
|
|
533
538
|
Builds a read-only static site of the rulebook with [Astro Starlight](https://starlight.astro.build) into `--out` (default `site`, relative to the current directory) and prints that path. Open it from disk or publish it as is, for example on GitHub Pages; pass `--base` when it is served from a sub-path (`--base /rules`).
|
|
534
539
|
|
|
@@ -581,7 +586,7 @@ jobs:
|
|
|
581
586
|
- uses: actions/setup-node@v6
|
|
582
587
|
with:
|
|
583
588
|
node-version: 22
|
|
584
|
-
- run: npx --yes rulebookguard@0.
|
|
589
|
+
- run: npx --yes rulebookguard@0.12.0 lint --base origin/${{ github.base_ref }}
|
|
585
590
|
```
|
|
586
591
|
|
|
587
592
|
> [!TIP]
|
|
@@ -608,3 +613,20 @@ claude plugin install rulebookguard@rulebookguard
|
|
|
608
613
|
Or, inside Claude Code 2.1.275 or later: `/plugin install rulebookguard --marketplace JuanCJR/rulebookguard`.
|
|
609
614
|
|
|
610
615
|
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.
|
|
616
|
+
|
|
617
|
+
## Kiro
|
|
618
|
+
|
|
619
|
+
Kiro gets the same rules through three pieces:
|
|
620
|
+
|
|
621
|
+
| Piece | What it gives Kiro | How to get it |
|
|
622
|
+
| ----------------------------------------------------- | -------------------------------------------------------- | ---------------------------------- |
|
|
623
|
+
| [Steering files](#export-cursorcopilotkiro---out-dir) | the active rules of the files it works on | `rulebookguard export kiro` |
|
|
624
|
+
| Power in [`plugin/`](plugin) | the `rules` and `rule-writing` skills and the MCP server | import the `plugin` folder |
|
|
625
|
+
| [Custom agents](#export-kiro-agents---out-dir) | `rule-author` and `rule-miner` | `rulebookguard export kiro-agents` |
|
|
626
|
+
|
|
627
|
+
To install the power, open the Powers panel in Kiro, choose **Add Custom Power**, then **Import power from a folder** with a clone of this repository's `plugin` folder. Its MCP server runs `npx --yes rulebookguard@0.12.0 mcp` from the directory Kiro starts it in, and needs Node.js and network access the first time.
|
|
628
|
+
|
|
629
|
+
Keep `rulebookguard.config.yaml` at the root of the Kiro workspace: Kiro reads `.kiro/` from there, and the steering patterns are relative to the rulebook root. There is no hook for Kiro, because steering files already deliver the rules of a matching file.
|
|
630
|
+
|
|
631
|
+
> [!NOTE]
|
|
632
|
+
> Kiro limits what these pieces reach. Kiro CLI V1 and V2 do not load `fileMatch` steering files automatically, so `rulebookguard export agents-md` is the way to reach them, and powers are listed for CLI V3. A custom agent does not read steering files unless its `resources` include `file://.kiro/steering/**/*.md`.
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { B as FormatError, I as CONFIG_FILE_NAME, M as CONTEXT_FILE_NAME, S as toRepoPath, V as formatIssue, _ as loadCatalog, b as expandGlobs, c as gitFallbackWarning, d as writeJson, f as writeLines, g as findRulebookRoot, h as scanTestTags, i as lintCommand, k as isRuleId, l as openCatalog, o as CliError, p as scanImplementations, s as findRule, t as changedFiles, u as shownPath, z as messages } from "./lint-
|
|
3
|
-
import { t as rulesForPath } from "./anchors-
|
|
2
|
+
import { B as FormatError, F as splitFrontmatter, I as CONFIG_FILE_NAME, M as CONTEXT_FILE_NAME, S as toRepoPath, V as formatIssue, _ as loadCatalog, b as expandGlobs, c as gitFallbackWarning, d as writeJson, f as writeLines, g as findRulebookRoot, h as scanTestTags, i as lintCommand, k as isRuleId, l as openCatalog, o as CliError, p as scanImplementations, s as findRule, t as changedFiles, u as shownPath, z as messages } from "./lint-HEXtuztv.mjs";
|
|
3
|
+
import { t as rulesForPath } from "./anchors-JxhjEzRN.mjs";
|
|
4
4
|
import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
5
5
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
@@ -85,7 +85,7 @@ async function handWrittenFiles(dir, format, names) {
|
|
|
85
85
|
}
|
|
86
86
|
async function staleRuleFiles(dir, format, target, current) {
|
|
87
87
|
const stale = [];
|
|
88
|
-
for (const name of await listNames(dir)) if (!current.has(name) && name.startsWith(
|
|
88
|
+
for (const name of await listNames(dir)) if (!current.has(name) && name.startsWith("rulebookguard-") && name.endsWith(target.extension) && isRuleId(name.slice(14, -target.extension.length)) && hasGeneratedLine(await readFile(join(dir, name), "utf8"), format)) stale.push(name);
|
|
89
89
|
return stale.sort();
|
|
90
90
|
}
|
|
91
91
|
function hasGeneratedLine(content, format) {
|
|
@@ -172,6 +172,78 @@ function expandBraces(glob) {
|
|
|
172
172
|
function normalizedBody(rule) {
|
|
173
173
|
return rule.body.replaceAll("\r\n", "\n").trim();
|
|
174
174
|
}
|
|
175
|
+
// src/commands/export-kiro-agents.ts
|
|
176
|
+
const KIRO_AGENTS_FORMAT = "kiro-agents";
|
|
177
|
+
const AGENTS_DIR = ".kiro/agents";
|
|
178
|
+
const AGENT_NAMES = ["rule-author", "rule-miner"];
|
|
179
|
+
const WRITING_GUIDE = "skills/rule-writing/SKILL.md";
|
|
180
|
+
const KIRO_TOOLS = [
|
|
181
|
+
"read",
|
|
182
|
+
"write",
|
|
183
|
+
"shell",
|
|
184
|
+
"@rulebookguard"
|
|
185
|
+
];
|
|
186
|
+
const PRELOADED_GUIDE = "the preloaded `rulebookguard:rule-writing` skill says";
|
|
187
|
+
const INLINE_GUIDE = "the writing guide at the end of these instructions says";
|
|
188
|
+
const LAUNCHER_NEW = "`rulebookguard.mjs new <context>` (on PATH while the plugin is enabled; if the command is not found, `npx rulebookguard new <context>`)";
|
|
189
|
+
async function exportKiroAgents(catalog, io, out) {
|
|
190
|
+
const text = messages(catalog.config.language);
|
|
191
|
+
const dir = out === void 0 ? join(catalog.root, AGENTS_DIR) : resolve(io.cwd, out);
|
|
192
|
+
const fileName = (name) => `${RULE_FILE_PREFIX}${name}.md`;
|
|
193
|
+
const handWritten = await handWrittenFiles(dir, KIRO_AGENTS_FORMAT, AGENT_NAMES.map(fileName));
|
|
194
|
+
if (handWritten.length > 0) throw new CliError(handWritten.map((name) => text.handWrittenExport(toRepoPath(shownPath(io.cwd, join(dir, name))), KIRO_AGENTS_FORMAT)).join("\n"));
|
|
195
|
+
const { version } = JSON.parse(await readPluginFile("plugin.json"));
|
|
196
|
+
const guide = bodyOf(await readPluginFile(WRITING_GUIDE), WRITING_GUIDE);
|
|
197
|
+
const lines = [];
|
|
198
|
+
await mkdir(dir, { recursive: true });
|
|
199
|
+
for (const name of AGENT_NAMES) {
|
|
200
|
+
const file = join(dir, fileName(name));
|
|
201
|
+
await writeFile(file, await kiroAgent(name, guide, version, text));
|
|
202
|
+
lines.push(toRepoPath(shownPath(io.cwd, file)));
|
|
203
|
+
}
|
|
204
|
+
writeLines(io, lines);
|
|
205
|
+
}
|
|
206
|
+
async function kiroAgent(name, guide, version, text) {
|
|
207
|
+
const source = `agents/${name}.md`;
|
|
208
|
+
const { data, body } = splitFrontmatter(await readPluginFile(source), source);
|
|
209
|
+
const { description } = data;
|
|
210
|
+
const instructions = normalized(body).replaceAll(PRELOADED_GUIDE, INLINE_GUIDE).replaceAll(LAUNCHER_NEW, `\`npx --yes rulebookguard@${version} new <context>\``);
|
|
211
|
+
return [
|
|
212
|
+
"---",
|
|
213
|
+
`name: ${name}`,
|
|
214
|
+
`description: ${JSON.stringify(description)}`,
|
|
215
|
+
`tools: ${JSON.stringify(KIRO_TOOLS)}`,
|
|
216
|
+
"mcpServers:",
|
|
217
|
+
" rulebookguard:",
|
|
218
|
+
" command: npx",
|
|
219
|
+
` args: ${JSON.stringify([
|
|
220
|
+
"--yes",
|
|
221
|
+
`rulebookguard@${version}`,
|
|
222
|
+
"mcp"
|
|
223
|
+
])}`,
|
|
224
|
+
"---",
|
|
225
|
+
"",
|
|
226
|
+
`<!-- ${text.generatedKiroAgent(KIRO_AGENTS_FORMAT, version)} -->`,
|
|
227
|
+
"",
|
|
228
|
+
instructions,
|
|
229
|
+
"",
|
|
230
|
+
`${guide}\n`
|
|
231
|
+
].join("\n");
|
|
232
|
+
}
|
|
233
|
+
function bodyOf(source, file) {
|
|
234
|
+
return normalized(splitFrontmatter(source, file).body);
|
|
235
|
+
}
|
|
236
|
+
function normalized(text) {
|
|
237
|
+
return text.replaceAll("\r\n", "\n").trim();
|
|
238
|
+
}
|
|
239
|
+
async function readPluginFile(path) {
|
|
240
|
+
try {
|
|
241
|
+
return await readFile(new URL(`../plugin/${path}`, import.meta.url), "utf8");
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (error.code !== "ENOENT") throw error;
|
|
244
|
+
return readFile(new URL(`../../plugin/${path}`, import.meta.url), "utf8");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
175
247
|
// src/commands/export.ts
|
|
176
248
|
const EXPORT_USAGE = "rulebookguard export <format> [--out <dir>]";
|
|
177
249
|
const GHERKIN_BLOCK = /^```gherkin[^\n]*\n([\s\S]*?)^```/gm;
|
|
@@ -181,8 +253,16 @@ async function exportCommand(catalog, io, format, out) {
|
|
|
181
253
|
await exportAgentRules(catalog, io, format, out);
|
|
182
254
|
return;
|
|
183
255
|
}
|
|
256
|
+
if (format === "kiro-agents") {
|
|
257
|
+
await exportKiroAgents(catalog, io, out);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
184
260
|
if (format !== "gherkin") {
|
|
185
|
-
const supported = [
|
|
261
|
+
const supported = [
|
|
262
|
+
"gherkin",
|
|
263
|
+
...AGENT_FORMATS,
|
|
264
|
+
KIRO_AGENTS_FORMAT
|
|
265
|
+
].join(", ");
|
|
186
266
|
throw new CliError(`Unknown export format "${format}"; supported: ${supported}.\nUsage: ${EXPORT_USAGE}`, 2);
|
|
187
267
|
}
|
|
188
268
|
const language = catalog.config.language ?? "en";
|
|
@@ -495,7 +575,7 @@ function render(path, rules, regions, text) {
|
|
|
495
575
|
return parts.join("\n\n");
|
|
496
576
|
}
|
|
497
577
|
// package.json
|
|
498
|
-
var version = "0.
|
|
578
|
+
var version = "0.12.0";
|
|
499
579
|
// src/commands/new.ts
|
|
500
580
|
const MAX_NUMBER = 999;
|
|
501
581
|
const CONTEXT_NAME = /^[a-z0-9][a-z0-9_-]*$/;
|
|
@@ -791,6 +871,8 @@ Commands:
|
|
|
791
871
|
active; --out: output directory
|
|
792
872
|
export agents-md [--out <dir>] keep an index of the active rules in a marked block of
|
|
793
873
|
AGENTS.md; --out: its directory (default: rulebook root)
|
|
874
|
+
export kiro-agents [--out <dir>] write the rule-author and rule-miner agents for Kiro in
|
|
875
|
+
.kiro/agents; --out: output directory
|
|
794
876
|
hook Claude Code hook for PreToolUse and PostToolUse: reads
|
|
795
877
|
the event on stdin and prints the rules anchored to
|
|
796
878
|
the file of the tool (Read, Edit, Write...)
|
|
@@ -935,7 +1017,7 @@ async function dispatch(argv, io) {
|
|
|
935
1017
|
return 0;
|
|
936
1018
|
}
|
|
937
1019
|
if (name === "site" || name === "site:generate" || name === "site:run") {
|
|
938
|
-
const { siteCommand } = await import("./site-
|
|
1020
|
+
const { siteCommand } = await import("./site-COuF4ib4.mjs");
|
|
939
1021
|
await siteCommand(io, {
|
|
940
1022
|
out: values.out,
|
|
941
1023
|
base: values.base,
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as loadRule, B as FormatError, D as findRuleRegions, E as RULE_TAG_PATTERN, F as splitFrontmatter, I as CONFIG_FILE_NAME, L as loadConfig, M as CONTEXT_FILE_NAME, N as loadContext, O as findRuleTags, P as parseContext, R as parseConfig, S as toRepoPath, T as RULE_MARKER_PATTERN, V as formatIssue, _ as loadCatalog, a as pendingImplementation, b as expandGlobs, g as findRulebookRoot, h as scanTestTags, j as parseRule, k as isRuleId, m as scanTestRegions, n as formatFinding, p as scanImplementations, r as lintCatalog, v as relativeToRoot, w as IMPLEMENTS_MARKER_PATTERN, x as listRepoFiles, y as createGlobMatcher } from "./lint-
|
|
2
|
-
import { t as rulesForPath } from "./anchors-
|
|
1
|
+
import { A as loadRule, B as FormatError, D as findRuleRegions, E as RULE_TAG_PATTERN, F as splitFrontmatter, I as CONFIG_FILE_NAME, L as loadConfig, M as CONTEXT_FILE_NAME, N as loadContext, O as findRuleTags, P as parseContext, R as parseConfig, S as toRepoPath, T as RULE_MARKER_PATTERN, V as formatIssue, _ as loadCatalog, a as pendingImplementation, b as expandGlobs, g as findRulebookRoot, h as scanTestTags, j as parseRule, k as isRuleId, m as scanTestRegions, n as formatFinding, p as scanImplementations, r as lintCatalog, v as relativeToRoot, w as IMPLEMENTS_MARKER_PATTERN, x as listRepoFiles, y as createGlobMatcher } from "./lint-HEXtuztv.mjs";
|
|
2
|
+
import { t as rulesForPath } from "./anchors-JxhjEzRN.mjs";
|
|
3
3
|
export { CONFIG_FILE_NAME, CONTEXT_FILE_NAME, FormatError, IMPLEMENTS_MARKER_PATTERN, RULE_MARKER_PATTERN, RULE_TAG_PATTERN, createGlobMatcher, expandGlobs, findRuleRegions, findRuleTags, findRulebookRoot, formatFinding, formatIssue, isRuleId, lintCatalog, listRepoFiles, loadCatalog, loadConfig, loadContext, loadRule, parseConfig, parseContext, parseRule, pendingImplementation, relativeToRoot, rulesForPath, scanImplementations, scanTestRegions, scanTestTags, splitFrontmatter, toRepoPath };
|
|
@@ -227,6 +227,7 @@ const MESSAGES = {
|
|
|
227
227
|
removedExport: (path) => `removed ${path}`,
|
|
228
228
|
handWrittenExport: (path, format) => `${path} was not generated by "rulebookguard export ${format}"; move it away and run the export again.`,
|
|
229
229
|
generatedRule: (format, file) => `Generated by "rulebookguard export ${format}" from ${file}; edit the rule instead.`,
|
|
230
|
+
generatedKiroAgent: (format, version) => `Generated by "rulebookguard export ${format}" from rulebookguard ${version}; run it again to update.`,
|
|
230
231
|
generatedAgentsMd: "Generated by \"rulebookguard export agents-md\"; edit the rules instead.",
|
|
231
232
|
agentsMdHeading: "Business rules",
|
|
232
233
|
agentsMdIntro: "Before changing a file, follow the active rules anchored to it:",
|
|
@@ -335,6 +336,7 @@ const MESSAGES = {
|
|
|
335
336
|
removedExport: (path) => `eliminado ${path}`,
|
|
336
337
|
handWrittenExport: (path, format) => `${path} no fue generado por "rulebookguard export ${format}"; muévelo y vuelve a exportar.`,
|
|
337
338
|
generatedRule: (format, file) => `Generado por "rulebookguard export ${format}" desde ${file}; edita la regla en su lugar.`,
|
|
339
|
+
generatedKiroAgent: (format, version) => `Generado por "rulebookguard export ${format}" desde rulebookguard ${version}; vuelve a ejecutarlo para actualizar.`,
|
|
338
340
|
generatedAgentsMd: "Generado por \"rulebookguard export agents-md\"; edita las reglas en su lugar.",
|
|
339
341
|
agentsMdHeading: "Reglas de negocio",
|
|
340
342
|
agentsMdIntro: "Antes de cambiar un archivo, sigue las reglas activas ancladas a él:",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as runGit, S as toRepoPath, b as expandGlobs, c as gitFallbackWarning, f as writeLines, l as openCatalog, m as scanTestRegions, o as CliError, r as lintCatalog } from "./lint-
|
|
1
|
+
import { C as runGit, S as toRepoPath, b as expandGlobs, c as gitFallbackWarning, f as writeLines, l as openCatalog, m as scanTestRegions, o as CliError, r as lintCatalog } from "./lint-HEXtuztv.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulebookguard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "rulebookguard",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.12.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/server": "2.3.1",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"node": ">=22"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@rulebookguard/site": "^0.
|
|
25
|
+
"@rulebookguard/site": "^0.12.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependenciesMeta": {
|
|
28
28
|
"@rulebookguard/site": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulebookguard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Keep business rules as the single source of truth, bound to code and tests.",
|
|
5
5
|
"homepage": "https://github.com/JuanCJR/rulebookguard#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
19
|
"schemas",
|
|
20
|
+
"plugin/agents",
|
|
21
|
+
"plugin/skills",
|
|
22
|
+
"plugin/plugin.json",
|
|
20
23
|
"npm-shrinkwrap.json"
|
|
21
24
|
],
|
|
22
25
|
"type": "module",
|
|
@@ -37,14 +40,14 @@
|
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@modelcontextprotocol/client": "2.3.1",
|
|
40
|
-
"@rulebookguard/site": "0.
|
|
43
|
+
"@rulebookguard/site": "0.12.0",
|
|
41
44
|
"@types/node": "^20.19.43",
|
|
42
45
|
"@types/picomatch": "^4.0.3",
|
|
43
46
|
"typescript": "^7.0.2",
|
|
44
47
|
"vite-plus": "^1.4.0"
|
|
45
48
|
},
|
|
46
49
|
"peerDependencies": {
|
|
47
|
-
"@rulebookguard/site": "^0.
|
|
50
|
+
"@rulebookguard/site": "^0.12.0"
|
|
48
51
|
},
|
|
49
52
|
"peerDependenciesMeta": {
|
|
50
53
|
"@rulebookguard/site": {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rule-author
|
|
3
|
+
description: Turns a feature, a ticket or a behavior change into a rulebookguard rule change plan (new rules with Gherkin, changed rules as diffs, deprecations, conflicts, test skeletons and open questions) for a person to approve, then writes the approved rules as proposed. Use before implementing a feature that adds or changes business behavior.
|
|
4
|
+
tools: Read, Grep, Glob, Bash, Edit, Write, mcp__plugin_rulebookguard_rulebookguard__contexts, mcp__plugin_rulebookguard_rulebookguard__rules_list, mcp__plugin_rulebookguard_rulebookguard__rules_search, mcp__plugin_rulebookguard_rulebookguard__rules_show, mcp__plugin_rulebookguard_rulebookguard__rules_for, mcp__plugin_rulebookguard_rulebookguard__lint
|
|
5
|
+
skills:
|
|
6
|
+
- rulebookguard:rule-writing
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You design the business rules a feature needs before anyone writes its code. You answer: which rules does this feature add, change or retire, and which existing rules does it collide with? Write every rule as the preloaded `rulebookguard:rule-writing` skill says.
|
|
10
|
+
|
|
11
|
+
You run in one of two modes:
|
|
12
|
+
|
|
13
|
+
- **Plan**, the default: you read, but write nothing. You return a rule change plan and stop.
|
|
14
|
+
- **Apply**, only when the request includes a plan a person has approved: you write exactly that plan into the rulebook.
|
|
15
|
+
|
|
16
|
+
## Hard limits
|
|
17
|
+
|
|
18
|
+
- Never set a rule `active`, and never write tests or production code. After the rules, the work follows the usual flow: rules, then tests, then code, and `lint --base` holds it to that order.
|
|
19
|
+
- Never read the whole catalog. Read by levels: contexts, then the rule lists of the contexts involved, then only the relevant rules.
|
|
20
|
+
- In plan mode, edit nothing and run no command that writes.
|
|
21
|
+
- In apply mode, write only what the approved plan says. If the plan is ambiguous or no longer matches the rulebook, stop and ask instead of guessing.
|
|
22
|
+
- Never delete rule files, commit or push.
|
|
23
|
+
|
|
24
|
+
## Plan mode
|
|
25
|
+
|
|
26
|
+
1. **Understand the feature.** Restate it in one or two sentences of business language. Go through every effect a person could observe: results and exit status, messages and printed output, machine-readable output, stored or written data. If the request does not say what happens to one of them, or in a case the feature obviously raises, record that as an open question; do not invent the answer.
|
|
27
|
+
2. **Find the contexts.** `contexts` (L0) for the map. When the feature names files or code areas, `rules_for` on them shows the active rules that already govern them.
|
|
28
|
+
3. **Read the rules involved.** `rules_list` (L1) for each affected context, then `rules_show` (L2) only for the rules whose summary touches the feature. Run `rules_search` once per key word of the feature, and once per obvious synonym (it matches text, needs every word, and knows no synonyms: "exit code" will not find "exit status"), to catch rules in other contexts. Search results are summaries; open only those that touch the feature.
|
|
29
|
+
4. **Decide, rule by rule:**
|
|
30
|
+
- a new behavior: a new rule;
|
|
31
|
+
- an existing rule whose statement or examples change, or a proposed rule the feature contradicts: a change to that rule, shown as a diff. Keep its `origin` and `evidence`; a "Questions for the business" item the feature answers is removed in the diff;
|
|
32
|
+
- an existing rule the feature replaces: a deprecation, with the new rule naming it in `supersedes`;
|
|
33
|
+
- an active rule the feature would contradict and the request does not ask to change: a conflict. Do not resolve it; describe it.
|
|
34
|
+
|
|
35
|
+
A rule you touch that already breaks the writing guide (for example two behaviors in one rule) is an open question, not something to fix in passing.
|
|
36
|
+
|
|
37
|
+
5. **Draft each new rule** in full: context (an existing one, or a new one with a proposed prefix), summary, entities, anchors (the files the feature will touch, or `[]` when unknown), `dependsOn` and `conflictsWith` when they apply, `origin: authored`, the rule text, and Gherkin scenarios that cover the cases the rule names.
|
|
38
|
+
6. **Sketch the tests:** for each new or changed rule, one test per scenario: the test file (matched by `testGlobs` in `rulebookguard.config.yaml`), the test name, and the `@rule <ID>` and `@endrule <ID>` comment lines around it. Use `NEW-1`, `NEW-2`… for rules that have no id yet. Give names and structure, not test code.
|
|
39
|
+
|
|
40
|
+
End plan mode with the plan below and nothing after it:
|
|
41
|
+
|
|
42
|
+
1. **Feature:** the restatement.
|
|
43
|
+
2. **Rules read:** the ids you read in full.
|
|
44
|
+
3. **New rules:** each as `NEW-n` with its complete draft.
|
|
45
|
+
4. **Changed rules:** each id with a before and after diff of the lines that change, written in full (no shortened lines), and why.
|
|
46
|
+
5. **Deprecated rules:** each id and what replaces it.
|
|
47
|
+
6. **Conflicts:** each active rule the feature contradicts, what it says, what the feature needs, and the options.
|
|
48
|
+
7. **Test skeletons:** per rule.
|
|
49
|
+
8. **Open questions:** what a person must decide before approving.
|
|
50
|
+
9. **Waiting for approval.** Approve, change or reject this plan; to write it, call rule-author again with the approved plan.
|
|
51
|
+
|
|
52
|
+
## Apply mode
|
|
53
|
+
|
|
54
|
+
1. Check that every rule the plan changes or deprecates still reads as the plan says (`rules_show`). If one changed since, stop and report it. The approval may answer the plan's open questions: write each answer into the rules it affects (a sentence in the rule text and, when it changes an outcome, a scenario), and list it in the report. An open question left unanswered stops you.
|
|
55
|
+
2. For each `NEW-n`, run `rulebookguard.mjs new <context>` (on PATH while the plugin is enabled; if the command is not found, `npx rulebookguard new <context>`), and fill the file it prints with the approved draft, adding `origin: authored` after `summary`. Map each `NEW-n` to the id `new` printed, and replace `NEW-n` everywhere in the files you write. If `new` created a context, fill its `_context.md`.
|
|
56
|
+
3. Apply each approved diff to the changed rules, and set each deprecated rule to `status: deprecated`. Change no other line.
|
|
57
|
+
4. Run the `lint` tool. Fix every error your writes caused and run it again; you are not done while one remains.
|
|
58
|
+
5. Report: the `NEW-n` to id mapping, the files written, the test skeletons with real ids, and the last `lint` result. The next step is writing those tests, then the code.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rule-miner
|
|
3
|
+
description: Lifts the business rules that existing code already enforces into proposed rulebookguard rules, one context at a time, each with evidence and questions for the business. Use when adopting rulebookguard in an existing project, or when asked to extract, mine or document the rules of a module.
|
|
4
|
+
tools: Read, Grep, Glob, Bash, Edit, Write, mcp__plugin_rulebookguard_rulebookguard__contexts, mcp__plugin_rulebookguard_rulebookguard__rules_list, mcp__plugin_rulebookguard_rulebookguard__rules_search, mcp__plugin_rulebookguard_rulebookguard__rules_show, mcp__plugin_rulebookguard_rulebookguard__rules_for, mcp__plugin_rulebookguard_rulebookguard__lint
|
|
5
|
+
skills:
|
|
6
|
+
- rulebookguard:rule-writing
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You lift the business rules that existing code already enforces into the rulebook of this repository. The code says what it does, not what it should do: you propose, a person decides. Write every rule as the preloaded `rulebookguard:rule-writing` skill says.
|
|
10
|
+
|
|
11
|
+
## Hard limits
|
|
12
|
+
|
|
13
|
+
- One context per run. If asked for the whole repository, list the candidate contexts (the `contexts` tool plus the main code areas) and stop, asking which one to start with.
|
|
14
|
+
- Every rule you write has `status: proposed` and `origin: extracted`. Never set `active`.
|
|
15
|
+
- Create rule files only with `rulebookguard.mjs new <context>` (on PATH while the plugin is enabled; if the command is not found, `npx rulebookguard new <context>`). Never write a rule file by hand or pick an id yourself.
|
|
16
|
+
- No evidence, no rule: each rule names at least one place in the code that enforces it.
|
|
17
|
+
- Never edit an existing rule, production code, test logic, the config, or files outside the rulebook, the test tags and the `@implements` markers. Never delete files, commit or push.
|
|
18
|
+
|
|
19
|
+
## Steps
|
|
20
|
+
|
|
21
|
+
1. **Scope.** Take the context from the request. Read `rulebookguard.config.yaml` (`rulesDir`, `testGlobs`, and `contextBudget`, 30 when unset). Find the code area: the one the user names, or the anchors of the context's existing rules. If neither tells you, ask. A check the area only passes through from another module belongs to the context that owns that module: skip it and suggest that context. Run the `lint` tool once now and keep its findings as the baseline.
|
|
22
|
+
2. **Read the rulebook by levels.** `contexts`, then `rules_list` for the context, then `rules_show` for the rules close to the area. Never read the whole catalog.
|
|
23
|
+
3. **Mine.** Read the code and its tests. Look for decisions the business would recognize: validations, limits, states and transitions, calculations, permissions, deadlines. Use `git log` on the files for intent (commit messages, ticket keys); treat it as a hint, not proof.
|
|
24
|
+
4. **Sort each candidate** into one of the kinds below. One rule per outcome the business would tell apart: two failures with different consequences are two rules; two causes of the same outcome are one.
|
|
25
|
+
- a business rule, which you write;
|
|
26
|
+
- a technical detail (retries, caching, formats, framework behavior), which you skip;
|
|
27
|
+
- a doubt (it may be a bug or a technical value), which you write with the doubt as a question.
|
|
28
|
+
5. **Deduplicate.** For each candidate run `rules_search` once per key word (it needs every word to match, so one word at a time finds more), and compare with the context's `rules_list`. If a rule already states it, do not write it; report "covered by R-…". If a rule states something different from what the code does, do not touch it; report the difference.
|
|
29
|
+
6. **Stay within budget.** Write at most `contextBudget` minus the rules the context already has. Report the rest as pending, with a suggested split of the context.
|
|
30
|
+
7. **Write.** Run `new` for each rule and fill the file it prints: a `summary` in business language, `entities`, narrow `anchors`, the rule text, at least one Gherkin scenario taken from the code or its tests, and a "Questions for the business" list. The template has no `origin` or `evidence`: add `origin: extracted` and an `evidence` list to the frontmatter, after `summary`, with every place in code and tests as `path symbol` (for example `src/clients/create.ts validateDocument`), never with line numbers. Ask only where a value or branch might be technical or a bug rather than intended, for example "Is the limit of 3 attempts a business rule or a technical value?"; at most three questions per rule, and none when the code and its tests leave no doubt. If `new` created the context, fill its `_context.md` summary and body too.
|
|
31
|
+
8. **Tag existing tests.** When a test in a file matched by `testGlobs` already verifies a rule, add a `@rule <ID>` comment on its own line above the test and an `@endrule <ID>` comment on its own line after it, in that file's comment syntax (a marker after code on the same line does not count). Change nothing else in the test. If you are not sure a test covers the rule, or the test file is not matched by `testGlobs`, list it as a suggestion instead; never edit `testGlobs` or the config.
|
|
32
|
+
9. **Mark the code.** Wrap the code that enforces each rule you wrote between an `@implements <ID>` comment on its own line and an `@endimplements <ID>` comment on its own line, in that file's comment syntax and in a file the rule anchors (widen the rule's `anchors` if needed). Add only those two lines: never change the code itself.
|
|
33
|
+
10. **Lint.** Run the `lint` tool. Fix every error that is not in the baseline and run it again. You are not done while one remains. Report the baseline errors, without fixing them.
|
|
34
|
+
|
|
35
|
+
## Report
|
|
36
|
+
|
|
37
|
+
End with this report and nothing after it:
|
|
38
|
+
|
|
39
|
+
1. **Rules proposed:** one line per rule: id, summary, evidence, `@implements` regions as `path:line-endLine`, tagged tests (or "none"). Then the suggested tags you did not apply, each with its test `path:line` and the reason.
|
|
40
|
+
2. **Skipped:** each candidate you did not write and why (technical detail, covered by R-…, no evidence, over budget).
|
|
41
|
+
3. **Differences with existing rules:** rule id, what it says, what the code does, with `path:line`.
|
|
42
|
+
4. **Questions for the business:** grouped by rule id.
|
|
43
|
+
5. **Lint:** result of the last run.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "rulebookguard",
|
|
4
|
+
"version": "0.12.0",
|
|
5
|
+
"description": "Business rules as the source of truth: follow, look up and write the rules anchored to your code.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Juan Carlos Jimenez"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/JuanCJR/rulebookguard#kiro",
|
|
10
|
+
"repository": "https://github.com/JuanCJR/rulebookguard",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["business rules", "business rule", "rulebook", "rulebookguard"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rule-writing
|
|
3
|
+
description: How to write a rulebookguard business rule so every rule reads the same, whoever writes it. Use when writing, extracting, proposing or reviewing a rule file, or turning a feature, ticket or existing code into rules.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Writing a business rule (rulebookguard)
|
|
7
|
+
|
|
8
|
+
Follow these rules for every rule file, whether a person or an agent writes it, and whether it comes from a feature or from existing code.
|
|
9
|
+
|
|
10
|
+
## One rule, one behavior
|
|
11
|
+
|
|
12
|
+
- A rule states one business behavior. If its text needs "and also", split it into two rules.
|
|
13
|
+
- Write it in the language of the business: what a client, an invoice or an order may or may not do. No class names, functions, tables or HTTP codes in the summary or the rule statement.
|
|
14
|
+
- It must be verifiable: a person can write a test that passes when the rule holds and fails when it does not. "Fast", "secure" or "user friendly" are not rules.
|
|
15
|
+
|
|
16
|
+
## The fields
|
|
17
|
+
|
|
18
|
+
- `summary`: one line, a full sentence, stating the behavior itself ("A client document number is unique per country."), not its topic ("Document numbers").
|
|
19
|
+
- `entities`: the domain nouns the rule talks about, in singular (`Client`, `Invoice`).
|
|
20
|
+
- `anchors`: globs of the files that implement the rule. Point at the narrowest files that decide the behavior, not whole directories by default.
|
|
21
|
+
- `status`: always `proposed`. Only a person sets `active`, after confirming the rule and tagging a test.
|
|
22
|
+
- `supersedes`: the id of the rule this one replaces; set the old one to `deprecated`.
|
|
23
|
+
- `dependsOn`: rules that must hold for this one to make sense. `conflictsWith`: rules that cannot be active together with this one.
|
|
24
|
+
- `origin`: `extracted` when the rule was lifted from existing code, `authored` when written from a feature or a decision.
|
|
25
|
+
- `evidence`: for extracted rules, every place in code or tests where the behavior shows, as `path symbol` (the function, method or constant), never `path:line`: line numbers go stale when the code moves. The exact lines live in `@implements` regions. No evidence, no extracted rule.
|
|
26
|
+
|
|
27
|
+
## The body
|
|
28
|
+
|
|
29
|
+
1. A `#` title of a few words.
|
|
30
|
+
2. The full rule in plain sentences: the behavior, its limits and its exceptions.
|
|
31
|
+
3. At least one example as a ` ```gherkin ` scenario (`Given` / `When` / `Then`) with concrete values, or a table. Cover the edge the rule names (the limit, the duplicate, the expired case).
|
|
32
|
+
4. For an extracted rule, a final "Questions for the business" list: what the code does that might be a bug or a technical detail rather than intent, for example "Is the limit of 3 attempts a business rule or a technical value?".
|
|
33
|
+
|
|
34
|
+
## Before you finish
|
|
35
|
+
|
|
36
|
+
- Search existing rules first (`rules_search`, or `rulebookguard search <words>`); change or supersede a rule instead of writing a duplicate.
|
|
37
|
+
- Create files only with `rulebookguard new <context>`, then fill them in.
|
|
38
|
+
- Run `rulebookguard lint` (or the `lint` tool). If it reports an error, the rule is not done.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rules
|
|
3
|
+
description: Business rules protocol for repositories with a rulebookguard.config.yaml. Use before changing code that may implement a business rule, when a rulebookguard hook injects rules for a file, or when asked to add, change or retire a business rule.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Business rules (rulebookguard)
|
|
7
|
+
|
|
8
|
+
In this repository the business rules in the rulebook (`rulesDir` in `rulebookguard.config.yaml`, `docs/rules` by default) are the source of truth. Code implements them and tests verify them. Never infer a business rule from the code alone when a rule file exists.
|
|
9
|
+
|
|
10
|
+
## Before changing code
|
|
11
|
+
|
|
12
|
+
1. Before editing a file, a hook or a steering file may add the active rules anchored to it; if neither did, ask the `rules_for` tool (or `rulebookguard for <path>`). When a hook lists rule texts as omitted, read each rule with the `rules_show` tool (or `rulebookguard show <id>`).
|
|
13
|
+
2. For a file you have not edited yet, or a broader change, ask the `rules_for` tool (or `rulebookguard for <path>`).
|
|
14
|
+
For a change that is not tied to one file, read the rulebook by levels: the `contexts` tool, then `rules_list` for the contexts involved, then `rules_show` for the relevant rules. `rules_search` finds rules by words.
|
|
15
|
+
3. Keep the change consistent with every rule listed. If the change would alter what a rule says, stop and tell the user which rule conflicts and how; do not change the behavior silently.
|
|
16
|
+
|
|
17
|
+
## Changing a rule
|
|
18
|
+
|
|
19
|
+
Only when the user asks for the behavior change:
|
|
20
|
+
|
|
21
|
+
1. Edit the rule file first: its summary, body and examples. A new rule starts with `rulebookguard new <context>` as `proposed`; it becomes `active` only when the user confirms it.
|
|
22
|
+
2. Update or add the tests that verify it. A test links to a rule by sitting between a `@rule <ID>` comment right above it and an `@endrule <ID>` comment right after it; the link lives only in the test.
|
|
23
|
+
3. Change the code until those tests pass. Wrap the code that enforces the rule between an `@implements <ID>` comment line and an `@endimplements <ID>` comment line, in a file the rule anchors, so the rule points at those exact lines.
|
|
24
|
+
4. Run `rulebookguard lint` or the `lint` tool, and `rulebookguard lint --base <base branch>` before opening a pull request. A changed rule whose tagging tests did not change fails as `pending-implementation`.
|
|
25
|
+
|
|
26
|
+
Retire a rule by setting `status: deprecated` and removing its `@rule`/`@endrule` tags and its `@implements`/`@endimplements` regions; a replacement rule names it in `supersedes`.
|
|
27
|
+
|
|
28
|
+
A rule that needs another one in force lists it in `dependsOn`; two rules that cannot both be active list each other in `conflictsWith`. `lint` rejects unknown ids, `dependsOn` cycles and two active rules in conflict.
|