rulebookguard 0.9.0 → 0.11.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 +31 -9
- package/dist/{anchors-DQwbqb7w.mjs → anchors-jf2bKfyg.mjs} +1 -1
- package/dist/cli.mjs +54 -10
- package/dist/index.d.mts +9 -1
- package/dist/index.mjs +2 -2
- package/dist/{lint-CaJnNLmU.mjs → lint-BPdk6XxH.mjs} +12 -9
- package/dist/{site-BTCwdt_H.mjs → site-B7g8lbjx.mjs} +2 -2
- package/npm-shrinkwrap.json +148 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -40,11 +40,15 @@ docs/rules/clients/R-CLI-001.md ── anchors ──▶ src/clients/**/*.ts
|
|
|
40
40
|
|
|
41
41
|
Requires Node.js 22 or later.
|
|
42
42
|
|
|
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
|
+
|
|
43
45
|
```sh
|
|
44
|
-
npx rulebookguard lint
|
|
46
|
+
npx rulebookguard@0.11.0 lint
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
|
|
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.
|
|
50
|
+
|
|
51
|
+
If you prefer to keep the version in your lockfile, and renovate it with the rest of your dependencies, add it as a dev dependency instead:
|
|
48
52
|
|
|
49
53
|
```sh
|
|
50
54
|
npm install --save-dev rulebookguard
|
|
@@ -52,10 +56,10 @@ npm install --save-dev rulebookguard
|
|
|
52
56
|
|
|
53
57
|
A first rule in four steps:
|
|
54
58
|
|
|
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`.
|
|
59
|
+
1. Set up the rulebook: `npx rulebookguard@0.11.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.11.0 new clients` creates `docs/rules/clients/R-CLI-001.md` as `proposed`.
|
|
57
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`.
|
|
58
|
-
4. Run `npx rulebookguard lint`, and add it to CI with the [CI recipe](#ci-recipe).
|
|
62
|
+
4. Run `npx rulebookguard@0.11.0 lint`, and add it to CI with the [CI recipe](#ci-recipe).
|
|
59
63
|
|
|
60
64
|
## Configuration
|
|
61
65
|
|
|
@@ -239,6 +243,7 @@ func validateDocument(country, number string) error {
|
|
|
239
243
|
| [`show <id>`](#show-id-l2) | the full rule (L2) |
|
|
240
244
|
| [`search <text>`](#search-text---context-c---json) | rules holding every word of a text |
|
|
241
245
|
| [`for <path>`](#for-path) | the active rules anchored to a file |
|
|
246
|
+
| [`for --changed <ref>`](#for---changed-ref) | the active rules anchored to the files changed since a ref |
|
|
242
247
|
| [`impact <id>`](#impact-id) | the files and tests a rule touches |
|
|
243
248
|
| [`new <context>`](#new-context---prefix-p) | scaffolds the next rule of a context |
|
|
244
249
|
| [`lint`](#lint---base-ref---json) | checks the whole catalog, and optionally changes since a ref |
|
|
@@ -326,6 +331,23 @@ R-CLI-001 (clients): A client document number is unique per country. (lines 12-3
|
|
|
326
331
|
|
|
327
332
|
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.
|
|
328
333
|
|
|
334
|
+
### `for --changed <ref>`
|
|
335
|
+
|
|
336
|
+
Active rules anchored to the files changed since a git ref, each with those files. This is what a reviewer needs to know about a pull request: which rules it touches, even when no rule file changed.
|
|
337
|
+
|
|
338
|
+
```sh
|
|
339
|
+
$ rulebookguard for --changed origin/main
|
|
340
|
+
R-BIL-001 (billing): Every new client gets a zero-balance account.
|
|
341
|
+
src/clients/create.ts
|
|
342
|
+
R-CLI-001 (clients): A client document number is unique per country.
|
|
343
|
+
src/clients/create.ts
|
|
344
|
+
src/clients/update.ts
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
It looks at the difference between the ref and the working tree, including deleted files and untracked ones that are not ignored, as `lint --base` does. A renamed file counts under both paths, so a file moved away from its anchors still reports their rules. On a branch that is behind the ref, the files changed on the ref since the branch point count too, so in CI compare against the base of the pull request on the merge commit, which is what a pull request checkout gives you.
|
|
348
|
+
|
|
349
|
+
`--json` prints `{ base, files, rules }`, where `files` lists every changed file and each rule carries its own `files`. Prints `No active rules anchored to the files changed since <ref>.` when there are none. Exits 1 for an unknown ref or outside a git work tree, and 2 when given both a path and `--changed`.
|
|
350
|
+
|
|
329
351
|
### `impact <id>`
|
|
330
352
|
|
|
331
353
|
Files matched by a rule's anchors and the tests tagging it.
|
|
@@ -464,7 +486,7 @@ Run it again after changing rules, for example in CI or before committing: it ov
|
|
|
464
486
|
|
|
465
487
|
### `export agents-md [--out <dir>]`
|
|
466
488
|
|
|
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.
|
|
489
|
+
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.
|
|
468
490
|
|
|
469
491
|
### `hook`
|
|
470
492
|
|
|
@@ -506,7 +528,7 @@ The rulebook is found from `--project` (default: the current directory). A comma
|
|
|
506
528
|
> npm install --save-dev rulebookguard @rulebookguard/site
|
|
507
529
|
> ```
|
|
508
530
|
|
|
509
|
-
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`.
|
|
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.11.0 -p @rulebookguard/site@0.11.0 rulebookguard site:run`.
|
|
510
532
|
|
|
511
533
|
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`).
|
|
512
534
|
|
|
@@ -559,11 +581,11 @@ jobs:
|
|
|
559
581
|
- uses: actions/setup-node@v6
|
|
560
582
|
with:
|
|
561
583
|
node-version: 22
|
|
562
|
-
- run: npx --yes rulebookguard@0.
|
|
584
|
+
- run: npx --yes rulebookguard@0.11.0 lint --base origin/${{ github.base_ref }}
|
|
563
585
|
```
|
|
564
586
|
|
|
565
587
|
> [!TIP]
|
|
566
|
-
>
|
|
588
|
+
> Keep the version pinned, or add rulebookguard as a dev dependency and run it from the lockfile, so a new release never changes CI results by itself.
|
|
567
589
|
|
|
568
590
|
## Claude Code plugin
|
|
569
591
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { B as
|
|
3
|
-
import { t as rulesForPath } from "./anchors-
|
|
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-BPdk6XxH.mjs";
|
|
3
|
+
import { t as rulesForPath } from "./anchors-jf2bKfyg.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";
|
|
@@ -115,9 +115,18 @@ async function writeAgentsMd(catalog, io, rules, dir) {
|
|
|
115
115
|
if (rules.length === 0 && !hasBlock) return [text.noActiveRules];
|
|
116
116
|
const before = hasBlock ? existing.slice(0, start) : existing;
|
|
117
117
|
const after = hasBlock ? existing.slice(end + 26) : "";
|
|
118
|
+
if (rules.length === 0) {
|
|
119
|
+
const rest = withoutBlock(before, after);
|
|
120
|
+
if (rest === "") {
|
|
121
|
+
await rm(file);
|
|
122
|
+
return [text.noActiveRules, text.removedExport(shown)];
|
|
123
|
+
}
|
|
124
|
+
await writeFile(file, rest);
|
|
125
|
+
return [text.noActiveRules, shown];
|
|
126
|
+
}
|
|
118
127
|
await mkdir(dir, { recursive: true });
|
|
119
|
-
await writeFile(file,
|
|
120
|
-
return
|
|
128
|
+
await writeFile(file, withBlock(before, after, agentsBlock(rules, text), hasBlock));
|
|
129
|
+
return [shown];
|
|
121
130
|
}
|
|
122
131
|
function withBlock(before, after, block, hasBlock) {
|
|
123
132
|
if (hasBlock) return `${before}${block}${after}`;
|
|
@@ -325,6 +334,33 @@ async function forCommand(catalog, io, input, json) {
|
|
|
325
334
|
}
|
|
326
335
|
writeLines(io, rules.map((rule) => `${rule.id} (${rule.context}): ${rule.summary}${rule.regions.length === 0 ? "" : ` (${text.lines(formatSpans(rule.regions))})`}`));
|
|
327
336
|
}
|
|
337
|
+
async function forChangedCommand(catalog, io, base, json) {
|
|
338
|
+
const text = messages(catalog.config.language);
|
|
339
|
+
const { files } = await changedFiles(catalog.root, base, text, "for --changed", { bothRenamePaths: true });
|
|
340
|
+
const touched = /* @__PURE__ */ new Map();
|
|
341
|
+
for (const file of files) for (const rule of rulesForPath(catalog.rules, file)) touched.set(rule.id, [...touched.get(rule.id) ?? [], file]);
|
|
342
|
+
const rules = sortById(catalog.rules.filter((rule) => rule.status === "active")).flatMap((rule) => {
|
|
343
|
+
const ruleFiles = touched.get(rule.id);
|
|
344
|
+
return ruleFiles === void 0 ? [] : [{
|
|
345
|
+
...toSummary(rule),
|
|
346
|
+
file: rule.file,
|
|
347
|
+
files: ruleFiles
|
|
348
|
+
}];
|
|
349
|
+
});
|
|
350
|
+
if (json) {
|
|
351
|
+
writeJson(io, {
|
|
352
|
+
base,
|
|
353
|
+
files,
|
|
354
|
+
rules
|
|
355
|
+
});
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (rules.length === 0) {
|
|
359
|
+
writeLines(io, [text.noActiveRulesForChanged(base)]);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
writeLines(io, rules.flatMap((rule) => [`${rule.id} (${rule.context}): ${rule.summary}`, ...indent(rule.files, text.none)]));
|
|
363
|
+
}
|
|
328
364
|
async function regionsIn(root, path) {
|
|
329
365
|
const found = (await scanImplementations(root, [path])).regions;
|
|
330
366
|
return (id) => (found.get(id) ?? []).map(({ line, endLine }) => ({
|
|
@@ -459,7 +495,7 @@ function render(path, rules, regions, text) {
|
|
|
459
495
|
return parts.join("\n\n");
|
|
460
496
|
}
|
|
461
497
|
// package.json
|
|
462
|
-
var version = "0.
|
|
498
|
+
var version = "0.11.0";
|
|
463
499
|
// src/commands/new.ts
|
|
464
500
|
const MAX_NUMBER = 999;
|
|
465
501
|
const CONTEXT_NAME = /^[a-z0-9][a-z0-9_-]*$/;
|
|
@@ -738,6 +774,8 @@ Commands:
|
|
|
738
774
|
search <text> [--context <c>] [--json] rules whose id, summary, entities or text hold every
|
|
739
775
|
word of <text>, ignoring case
|
|
740
776
|
for <path> [--json] active rules anchored to a path
|
|
777
|
+
for --changed <ref> [--json] active rules anchored to the files changed since <ref>,
|
|
778
|
+
each with those files
|
|
741
779
|
impact <id> [--json] files matched by a rule's anchors and tests tagging it
|
|
742
780
|
new <context> [--prefix <P>] scaffold the next free rule id as a proposed rule;
|
|
743
781
|
creates the context first when it does not exist,
|
|
@@ -809,9 +847,13 @@ const COMMANDS = {
|
|
|
809
847
|
}
|
|
810
848
|
},
|
|
811
849
|
for: {
|
|
812
|
-
usage: "rulebookguard for <path> [--json]",
|
|
850
|
+
usage: "rulebookguard for <path>|--changed <ref> [--json]",
|
|
813
851
|
argument: "path",
|
|
814
|
-
|
|
852
|
+
argumentUnless: "changed",
|
|
853
|
+
options: {
|
|
854
|
+
...JSON_OPTION,
|
|
855
|
+
changed: { type: "string" }
|
|
856
|
+
}
|
|
815
857
|
},
|
|
816
858
|
impact: {
|
|
817
859
|
usage: "rulebookguard impact <id> [--json]",
|
|
@@ -893,7 +935,7 @@ async function dispatch(argv, io) {
|
|
|
893
935
|
return 0;
|
|
894
936
|
}
|
|
895
937
|
if (name === "site" || name === "site:generate" || name === "site:run") {
|
|
896
|
-
const { siteCommand } = await import("./site-
|
|
938
|
+
const { siteCommand } = await import("./site-B7g8lbjx.mjs");
|
|
897
939
|
await siteCommand(io, {
|
|
898
940
|
out: values.out,
|
|
899
941
|
base: values.base,
|
|
@@ -937,7 +979,8 @@ async function dispatch(argv, io) {
|
|
|
937
979
|
});
|
|
938
980
|
break;
|
|
939
981
|
case "for":
|
|
940
|
-
await forCommand(catalog, io, argument, json);
|
|
982
|
+
if (values.changed === void 0) await forCommand(catalog, io, argument, json);
|
|
983
|
+
else await forChangedCommand(catalog, io, values.changed, json);
|
|
941
984
|
break;
|
|
942
985
|
case "impact":
|
|
943
986
|
await impactCommand(catalog, io, argument, json);
|
|
@@ -971,7 +1014,8 @@ function parse(spec, args) {
|
|
|
971
1014
|
} catch (error) {
|
|
972
1015
|
throw new CliError(`${error.message}\nUsage: ${spec.usage}`, 2);
|
|
973
1016
|
}
|
|
974
|
-
const
|
|
1017
|
+
const replaced = spec.argumentUnless !== void 0 && parsed.values[spec.argumentUnless] !== void 0;
|
|
1018
|
+
const expected = spec.argument === void 0 || replaced ? 0 : 1;
|
|
975
1019
|
if (parsed.positionals.length < expected) throw new CliError(`Missing argument <${spec.argument}>. Usage: ${spec.usage}`, 2);
|
|
976
1020
|
if (parsed.positionals.length > expected) throw new CliError(`Unexpected argument "${parsed.positionals[expected]}". Usage: ${spec.usage}`, 2);
|
|
977
1021
|
return {
|
package/dist/index.d.mts
CHANGED
|
@@ -179,6 +179,14 @@ interface ForJson {
|
|
|
179
179
|
regions: LineSpanJson[];
|
|
180
180
|
})[];
|
|
181
181
|
}
|
|
182
|
+
interface ForChangedJson {
|
|
183
|
+
base: string;
|
|
184
|
+
files: string[];
|
|
185
|
+
rules: (RuleSummaryJson & {
|
|
186
|
+
file: string;
|
|
187
|
+
files: string[];
|
|
188
|
+
})[];
|
|
189
|
+
}
|
|
182
190
|
interface LineSpanJson {
|
|
183
191
|
line: number;
|
|
184
192
|
endLine: number;
|
|
@@ -212,4 +220,4 @@ interface LintOptions {
|
|
|
212
220
|
export declare function lintCatalog(catalog: Catalog, scan?: TagScan, onGitFallback?: GitFallbackListener): Promise<LintFinding[]>;
|
|
213
221
|
export declare function pendingImplementation(catalog: Catalog, scan: TagScan, base: string, changed: string[], lines: Map<string, LineSpan[]>): LintFinding[];
|
|
214
222
|
export declare function formatFinding(finding: LintFinding): string;
|
|
215
|
-
export type { Catalog, CatalogIssue, CatalogIssueCode, ContextDoc, ContextFrontmatter, ContextsJson, ForJson, FormatIssue, Frontmatter, GitFallbackListener, ImpactJson, ImplementationScan, ImplementsTagProblemCode, LintFinding, LintJson, LintOptions, LintSeverity, ListJson, ParseOptions, RegionIndex, RegionKind, RuleDoc, RuleFrontmatter, RuleLanguage, RuleRegion, RuleRegions, RuleStatus, RuleSummaryJson, RuleTag, RuleTagProblem, RuleTagProblemCode, RulebookConfig, ShowJson, TagIndex, TagLocation, TagProblem, TagRegion, TagScan };
|
|
223
|
+
export type { Catalog, CatalogIssue, CatalogIssueCode, ContextDoc, ContextFrontmatter, ContextsJson, ForChangedJson, ForJson, FormatIssue, Frontmatter, GitFallbackListener, ImpactJson, ImplementationScan, ImplementsTagProblemCode, LintFinding, LintJson, LintOptions, LintSeverity, ListJson, ParseOptions, RegionIndex, RegionKind, RuleDoc, RuleFrontmatter, RuleLanguage, RuleRegion, RuleRegions, RuleStatus, RuleSummaryJson, RuleTag, RuleTagProblem, RuleTagProblemCode, RulebookConfig, ShowJson, TagIndex, TagLocation, TagProblem, TagRegion, TagScan };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as
|
|
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-BPdk6XxH.mjs";
|
|
2
|
+
import { t as rulesForPath } from "./anchors-jf2bKfyg.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 };
|
|
@@ -187,6 +187,7 @@ const MESSAGES = {
|
|
|
187
187
|
noContexts: "No contexts found. Create one with \"rulebookguard new <context>\".",
|
|
188
188
|
noRules: "No rules found.",
|
|
189
189
|
noActiveRulesFor: (path) => `No active rules anchored to ${path}.`,
|
|
190
|
+
noActiveRulesForChanged: (ref) => `No active rules anchored to the files changed since ${ref}.`,
|
|
190
191
|
lines: (spans) => `lines ${spans}`,
|
|
191
192
|
filesMatched: (count) => `Files matched by anchors (${count}):`,
|
|
192
193
|
testsTagging: (id, count) => `Tests tagging ${id} (${count}):`,
|
|
@@ -259,7 +260,7 @@ const MESSAGES = {
|
|
|
259
260
|
contextOverBudgetHint: "split the context into smaller ones, or raise contextBudget in rulebookguard.config.yaml",
|
|
260
261
|
pendingImplementationHint: (id) => `update or add a test inside an "@rule ${id}" … "@endrule ${id}" region that covers the changed rule`,
|
|
261
262
|
lintSummary: (errors, warnings) => `${errors} ${errors === 1 ? "error" : "errors"}, ${warnings} ${warnings === 1 ? "warning" : "warnings"}`,
|
|
262
|
-
needsWorkTree: (root) =>
|
|
263
|
+
needsWorkTree: (command, root) => `${command} needs a git work tree, but ${root} is not inside one.`,
|
|
263
264
|
unknownRef: (ref) => `Unknown git ref "${ref}".`,
|
|
264
265
|
cannotListChanged: (ref, reason) => `Cannot list the files changed since "${ref}": ${reason}`
|
|
265
266
|
},
|
|
@@ -294,6 +295,7 @@ const MESSAGES = {
|
|
|
294
295
|
noContexts: "No se encontraron contextos. Crea uno con \"rulebookguard new <contexto>\".",
|
|
295
296
|
noRules: "No se encontraron reglas.",
|
|
296
297
|
noActiveRulesFor: (path) => `No hay reglas activas ancladas a ${path}.`,
|
|
298
|
+
noActiveRulesForChanged: (ref) => `No hay reglas activas ancladas a los archivos cambiados desde ${ref}.`,
|
|
297
299
|
lines: (spans) => `líneas ${spans}`,
|
|
298
300
|
filesMatched: (count) => `Archivos que cubren los anchors (${count}):`,
|
|
299
301
|
testsTagging: (id, count) => `Tests que etiquetan ${id} (${count}):`,
|
|
@@ -366,7 +368,7 @@ const MESSAGES = {
|
|
|
366
368
|
contextOverBudgetHint: "divide el contexto en otros más chicos o sube contextBudget en rulebookguard.config.yaml",
|
|
367
369
|
pendingImplementationHint: (id) => `actualiza o agrega un test dentro de una región "@rule ${id}" … "@endrule ${id}" que cubra el cambio de la regla`,
|
|
368
370
|
lintSummary: (errors, warnings) => `${errors} ${errors === 1 ? "error" : "errores"}, ${warnings} ${warnings === 1 ? "aviso" : "avisos"}`,
|
|
369
|
-
needsWorkTree: (root) =>
|
|
371
|
+
needsWorkTree: (command, root) => `${command} necesita un work tree de git, pero ${root} no está en uno.`,
|
|
370
372
|
unknownRef: (ref) => `La referencia de git "${ref}" no existe.`,
|
|
371
373
|
cannotListChanged: (ref, reason) => `No se pudieron listar los archivos cambiados desde "${ref}": ${reason}`
|
|
372
374
|
}
|
|
@@ -613,10 +615,11 @@ async function isCommitRef(cwd, ref) {
|
|
|
613
615
|
return false;
|
|
614
616
|
}
|
|
615
617
|
}
|
|
616
|
-
async function changedSince(cwd, ref) {
|
|
618
|
+
async function changedSince(cwd, ref, { bothRenamePaths = false } = {}) {
|
|
617
619
|
const changed = await gitPaths(cwd, "diff", [
|
|
618
620
|
"--name-only",
|
|
619
621
|
"--relative",
|
|
622
|
+
...bothRenamePaths ? ["--no-renames"] : [],
|
|
620
623
|
ref,
|
|
621
624
|
"--"
|
|
622
625
|
]);
|
|
@@ -1001,7 +1004,7 @@ function writeLines(io, lines) {
|
|
|
1001
1004
|
}
|
|
1002
1005
|
function shownPath(cwd, file) {
|
|
1003
1006
|
const fromCwd = relative(cwd, file);
|
|
1004
|
-
return fromCwd.startsWith("..") || isAbsolute(fromCwd) ? file : fromCwd;
|
|
1007
|
+
return fromCwd.startsWith("..") || isAbsolute(fromCwd) ? file : toRepoPath(fromCwd);
|
|
1005
1008
|
}
|
|
1006
1009
|
function gitFallbackWarning(io, language) {
|
|
1007
1010
|
const text = messages(language);
|
|
@@ -1035,7 +1038,7 @@ async function lintCommand(io, options) {
|
|
|
1035
1038
|
}, messages("en"))), options.json, messages("en"));
|
|
1036
1039
|
}
|
|
1037
1040
|
const text = messages(catalog.config.language);
|
|
1038
|
-
const changed = options.base === void 0 ? void 0 : await changedFiles(catalog.root, options.base, text);
|
|
1041
|
+
const changed = options.base === void 0 ? void 0 : await changedFiles(catalog.root, options.base, text, "lint --base");
|
|
1039
1042
|
const warn = gitFallbackWarning(io, catalog.config.language);
|
|
1040
1043
|
const scan = await scanTestRegions(catalog.root, catalog.config.testGlobs, warn);
|
|
1041
1044
|
const findings = await lintCatalog(catalog, scan, warn);
|
|
@@ -1201,11 +1204,11 @@ async function testLineChanges(catalog, scan, base, changed, text) {
|
|
|
1201
1204
|
}
|
|
1202
1205
|
return lines;
|
|
1203
1206
|
}
|
|
1204
|
-
async function changedFiles(root, base, text) {
|
|
1205
|
-
if (!await isInsideWorkTree(root)) throw new CliError(text.needsWorkTree(root));
|
|
1207
|
+
async function changedFiles(root, base, text, command, options) {
|
|
1208
|
+
if (!await isInsideWorkTree(root)) throw new CliError(text.needsWorkTree(command, root));
|
|
1206
1209
|
if (!await isCommitRef(root, base)) throw new CliError(text.unknownRef(base));
|
|
1207
1210
|
try {
|
|
1208
|
-
return await changedSince(root, base);
|
|
1211
|
+
return await changedSince(root, base, options);
|
|
1209
1212
|
} catch (error) {
|
|
1210
1213
|
throw new CliError(text.cannotListChanged(base, gitFailureReason(error)));
|
|
1211
1214
|
}
|
|
@@ -1229,4 +1232,4 @@ function sortFindings(findings) {
|
|
|
1229
1232
|
function compare(a, b) {
|
|
1230
1233
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
1231
1234
|
}
|
|
1232
|
-
export {
|
|
1235
|
+
export { loadRule as A, FormatError as B, runGit as C, findRuleRegions as D, RULE_TAG_PATTERN as E, splitFrontmatter as F, CONFIG_FILE_NAME as I, loadConfig as L, CONTEXT_FILE_NAME as M, loadContext as N, findRuleTags as O, parseContext as P, parseConfig as R, toRepoPath as S, RULE_MARKER_PATTERN as T, formatIssue as V, loadCatalog as _, pendingImplementation as a, expandGlobs as b, gitFallbackWarning as c, writeJson as d, writeLines as f, findRulebookRoot as g, scanTestTags as h, lintCommand as i, parseRule as j, isRuleId as k, openCatalog as l, scanTestRegions as m, formatFinding as n, CliError as o, scanImplementations as p, lintCatalog as r, findRule as s, changedFiles as t, shownPath as u, relativeToRoot as v, IMPLEMENTS_MARKER_PATTERN as w, listRepoFiles as x, createGlobMatcher as y, messages as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-BPdk6XxH.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";
|
|
@@ -417,7 +417,7 @@ async function siteCommand(io, options, until = untilInterrupted()) {
|
|
|
417
417
|
const catalog = await openCatalog(io);
|
|
418
418
|
const out = resolve(io.cwd, options.out ?? "site");
|
|
419
419
|
const fromCwd = relative(io.cwd, out);
|
|
420
|
-
const shown = fromCwd === "" ? "." : fromCwd.startsWith("..") || isAbsolute(fromCwd) ? out : fromCwd;
|
|
420
|
+
const shown = fromCwd === "" ? "." : fromCwd.startsWith("..") || isAbsolute(fromCwd) ? out : toRepoPath(fromCwd);
|
|
421
421
|
await assertReplaceable(out, shown);
|
|
422
422
|
const site = await importSitePackage([catalog.root, dirname(fileURLToPath(import.meta.url))]);
|
|
423
423
|
const language = catalog.config.language ?? "en";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rulebookguard",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"": {
|
|
8
|
+
"name": "rulebookguard",
|
|
9
|
+
"version": "0.11.0",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@modelcontextprotocol/server": "2.3.1",
|
|
13
|
+
"ajv": "8.20.0",
|
|
14
|
+
"picomatch": "4.0.7",
|
|
15
|
+
"yaml": "2.9.1",
|
|
16
|
+
"zod": "4.6.7"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"rulebookguard": "dist/cli.mjs"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@rulebookguard/site": "^0.11.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependenciesMeta": {
|
|
28
|
+
"@rulebookguard/site": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"node_modules/@modelcontextprotocol/core": {
|
|
34
|
+
"version": "2.3.1",
|
|
35
|
+
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.3.1.tgz",
|
|
36
|
+
"integrity": "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==",
|
|
37
|
+
"license": "Apache-2.0",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"zod": "^4.2.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"node_modules/@modelcontextprotocol/server": {
|
|
46
|
+
"version": "2.3.1",
|
|
47
|
+
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz",
|
|
48
|
+
"integrity": "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==",
|
|
49
|
+
"license": "Apache-2.0",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@modelcontextprotocol/core": "2.3.1",
|
|
52
|
+
"zod": "^4.2.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"node_modules/ajv": {
|
|
59
|
+
"version": "8.20.0",
|
|
60
|
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
|
61
|
+
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"fast-deep-equal": "^3.1.3",
|
|
65
|
+
"fast-uri": "^3.0.1",
|
|
66
|
+
"json-schema-traverse": "^1.0.0",
|
|
67
|
+
"require-from-string": "^2.0.2"
|
|
68
|
+
},
|
|
69
|
+
"funding": {
|
|
70
|
+
"type": "github",
|
|
71
|
+
"url": "https://github.com/sponsors/epoberezkin"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"node_modules/fast-deep-equal": {
|
|
75
|
+
"version": "3.1.3",
|
|
76
|
+
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
77
|
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
|
78
|
+
"license": "MIT"
|
|
79
|
+
},
|
|
80
|
+
"node_modules/fast-uri": {
|
|
81
|
+
"version": "3.1.9",
|
|
82
|
+
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz",
|
|
83
|
+
"integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==",
|
|
84
|
+
"funding": [
|
|
85
|
+
{
|
|
86
|
+
"type": "github",
|
|
87
|
+
"url": "https://github.com/sponsors/fastify"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"type": "opencollective",
|
|
91
|
+
"url": "https://opencollective.com/fastify"
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"license": "BSD-3-Clause"
|
|
95
|
+
},
|
|
96
|
+
"node_modules/json-schema-traverse": {
|
|
97
|
+
"version": "1.0.0",
|
|
98
|
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
|
99
|
+
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
|
100
|
+
"license": "MIT"
|
|
101
|
+
},
|
|
102
|
+
"node_modules/picomatch": {
|
|
103
|
+
"version": "4.0.7",
|
|
104
|
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
|
|
105
|
+
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
|
|
106
|
+
"license": "MIT",
|
|
107
|
+
"engines": {
|
|
108
|
+
"node": ">=12"
|
|
109
|
+
},
|
|
110
|
+
"funding": {
|
|
111
|
+
"url": "https://github.com/sponsors/jonschlinkert"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"node_modules/require-from-string": {
|
|
115
|
+
"version": "2.0.2",
|
|
116
|
+
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
|
117
|
+
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
|
118
|
+
"license": "MIT",
|
|
119
|
+
"engines": {
|
|
120
|
+
"node": ">=0.10.0"
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"node_modules/yaml": {
|
|
124
|
+
"version": "2.9.1",
|
|
125
|
+
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz",
|
|
126
|
+
"integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==",
|
|
127
|
+
"license": "ISC",
|
|
128
|
+
"bin": {
|
|
129
|
+
"yaml": "bin.mjs"
|
|
130
|
+
},
|
|
131
|
+
"engines": {
|
|
132
|
+
"node": ">= 14.6"
|
|
133
|
+
},
|
|
134
|
+
"funding": {
|
|
135
|
+
"url": "https://github.com/sponsors/eemeli"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"node_modules/zod": {
|
|
139
|
+
"version": "4.6.7",
|
|
140
|
+
"resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz",
|
|
141
|
+
"integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==",
|
|
142
|
+
"license": "MIT",
|
|
143
|
+
"funding": {
|
|
144
|
+
"url": "https://github.com/sponsors/colinhacks"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulebookguard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
|
-
"schemas"
|
|
19
|
+
"schemas",
|
|
20
|
+
"npm-shrinkwrap.json"
|
|
20
21
|
],
|
|
21
22
|
"type": "module",
|
|
22
23
|
"exports": {
|
|
@@ -36,14 +37,14 @@
|
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@modelcontextprotocol/client": "2.3.1",
|
|
39
|
-
"@rulebookguard/site": "0.
|
|
40
|
+
"@rulebookguard/site": "0.11.0",
|
|
40
41
|
"@types/node": "^20.19.43",
|
|
41
42
|
"@types/picomatch": "^4.0.3",
|
|
42
43
|
"typescript": "^7.0.2",
|
|
43
44
|
"vite-plus": "^1.4.0"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
|
-
"@rulebookguard/site": "^0.
|
|
47
|
+
"@rulebookguard/site": "^0.11.0"
|
|
47
48
|
},
|
|
48
49
|
"peerDependenciesMeta": {
|
|
49
50
|
"@rulebookguard/site": {
|