@syv-ai/rulecast 0.2.0 → 0.4.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 +134 -10
- package/dist/{chunk-ZEFEQKG5.js → chunk-DQUCE2IK.js} +230 -69
- package/dist/{chunk-C6C5EER7.js → chunk-IKONOTME.js} +238 -130
- package/dist/{chunk-RODZRI7B.js → chunk-WUFU4LW2.js} +792 -319
- package/dist/cli.js +803 -102
- package/dist/index.d.ts +111 -4
- package/dist/index.js +37 -5
- package/dist/internal.d.ts +13 -10
- package/dist/internal.js +2 -2
- package/dist/{types-2oz2AlwA.d.ts → types-Dcra5LXR.d.ts} +200 -3
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,125 @@
|
|
|
1
|
-
import { A as Adapter,
|
|
2
|
-
export {
|
|
1
|
+
import { A as Adapter, e as Delivery, g as DetectorRun, h as DetectorRuleInput, M as Match, i as Detector, j as AnyDetector, k as AdapterInput, E as Event } from './types-Dcra5LXR.js';
|
|
2
|
+
export { l as AdapterInstall, m as Cache, n as ChangeSet, b as CheckResult, o as CompiledDetector, a as CompiledRule, p as ContentSource, q as DeliveredReference, s as DetectorCheck, t as DetectorEvent, D as DetectorRegistry, u as DetectorResult, d as DetectorRule, c as DetectorSettings, v as DetectorWarm, w as EventKind, F as Finding, I as InstallScope, x as LLM_PROVIDERS, y as LlmFinding, f as LlmProvider, L as LlmProviderName, z as LlmRequest, B as LlmSettings, G as LlmUnavailableError, O as Omitted, H as ReferenceMode, J as ReferenceSpec, K as RenderOptions, N as ResolvedReference, P as RuleExample, Q as RuleExamples, T as RuleScope, U as Severity, S as Stage, V as TouchRule, W as WriteIntent, X as createRegistry, Y as defaultDetectorSettings, Z as emptyDelivery, _ as isDetectorRule, r as renderAgentText } from './types-Dcra5LXR.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
declare const claudeCodeAdapter: Adapter;
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* How much of a rule a repository already owes, per rule and per file.
|
|
9
|
+
*
|
|
10
|
+
* Counts, never rates. One codebase went from 61 violations in 80 files to 57 in 194 over eight
|
|
11
|
+
* months: the rate fell from 76% to 29% and the count did not move, because new code complied and
|
|
12
|
+
* the old violations were only diluted. A team watching the rate would have believed it was fixing
|
|
13
|
+
* the problem. So this reports the stock and leaves the division to nobody.
|
|
14
|
+
*/
|
|
15
|
+
interface BacklogSummary {
|
|
16
|
+
/** Violations descending, then rule ascending. */
|
|
17
|
+
rules: {
|
|
18
|
+
rule: string;
|
|
19
|
+
violations: number;
|
|
20
|
+
files: number;
|
|
21
|
+
}[];
|
|
22
|
+
/** Violations descending, then file ascending. */
|
|
23
|
+
files: {
|
|
24
|
+
file: string;
|
|
25
|
+
violations: number;
|
|
26
|
+
}[];
|
|
27
|
+
totalViolations: number;
|
|
28
|
+
/** Distinct files with at least one violation — not the sum of the per-rule file counts. */
|
|
29
|
+
totalFiles: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Both halves of a delivery count.
|
|
33
|
+
*
|
|
34
|
+
* `--all-files` has no baseline, so every violation arrives as a finding. `--from-ref` classifies
|
|
35
|
+
* against the merge base, and there most of the backlog is in `preexistingSummary` — which is
|
|
36
|
+
* exactly the set this exists to give a number to.
|
|
37
|
+
*/
|
|
38
|
+
declare function summarise(delivery: Delivery): BacklogSummary;
|
|
39
|
+
declare function renderBacklog(summary: BacklogSummary, options: {
|
|
40
|
+
topFiles: number;
|
|
41
|
+
note?: boolean;
|
|
42
|
+
}): string;
|
|
43
|
+
|
|
7
44
|
/** Every agent adapter rulecast ships. */
|
|
8
45
|
declare const ADAPTERS: readonly Adapter[];
|
|
9
46
|
declare function adapterByName(name: string): Adapter | undefined;
|
|
10
47
|
|
|
48
|
+
/**
|
|
49
|
+
* A path an external tool reported, made repo-relative with forward slashes.
|
|
50
|
+
*
|
|
51
|
+
* Two things a tool does that naive relativisation gets wrong. SARIF requires `uri` to be
|
|
52
|
+
* percent-encoded, so a `file://` URI has to be decoded rather than sliced. And resolving a path —
|
|
53
|
+
* Python's `Path.resolve()`, eslint — follows symlinks, so a project under a symlinked root, which
|
|
54
|
+
* is every macOS temp directory and plenty of real checkouts, comes back as a realpath that does
|
|
55
|
+
* not sit under `cwd`. Either way the finding would be attributed to a path no rule selected and
|
|
56
|
+
* silently dropped, so the realpath of the root is tried before giving up.
|
|
57
|
+
*
|
|
58
|
+
* `command` and `linter` each carried a byte-identical copy of the root-and-realpath half of this.
|
|
59
|
+
*
|
|
60
|
+
* A factory, because the realpath is a filesystem call: a tool that reports thousands of findings
|
|
61
|
+
* resolves its root once, not once per finding.
|
|
62
|
+
*/
|
|
63
|
+
declare function repoRelativeTo(cwd: string): (file: string) => string;
|
|
64
|
+
/** One path; for many from the same tool, take `repoRelativeTo(cwd)` once. */
|
|
65
|
+
declare function repoRelative(file: string, cwd: string): string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether this error is the clock rather than the rule.
|
|
69
|
+
*
|
|
70
|
+
* It decides which of two very different things a detector reports. Rethrowing lets the core record
|
|
71
|
+
* a timeout, which is logged and tried again at the next verify; swallowing it into
|
|
72
|
+
* `DetectorResult.errors` is a rule error, which disables the rule for the whole session (§14).
|
|
73
|
+
*
|
|
74
|
+
* The signal alone cannot tell the two apart. Its abort timer only runs when the event loop is free,
|
|
75
|
+
* and synchronous work — `ast-grep` parses in native code that no timeout interrupts — is what keeps
|
|
76
|
+
* it busy. So the wall clock is the authority, and every detector that catches its own errors has to
|
|
77
|
+
* ask this rather than `signal.aborted`.
|
|
78
|
+
*/
|
|
79
|
+
declare function pastDeadline(error: unknown, input: Pick<DetectorRun<unknown>, "signal" | "deadlineAt">): boolean;
|
|
11
80
|
/** Turns a per-rule function into a detector run; an error in one rule does not affect the others. */
|
|
12
81
|
declare function perRule<Config>(detect: (rule: DetectorRuleInput<Config>, input: DetectorRun<Config>) => Promise<Match[]>): Detector<Config>["run"];
|
|
82
|
+
/**
|
|
83
|
+
* Reads each file at most once for the life of the returned function, for a detector that hands
|
|
84
|
+
* paths to another program and reads the files only to quote them (`{{text}}`, a prompt).
|
|
85
|
+
*
|
|
86
|
+
* Not for a detector that declares `guards`: those read only through `DetectorRun.read`, which is
|
|
87
|
+
* what lets them judge content the agent proposed and that is not on disk (docs/conventions.md).
|
|
88
|
+
* `linter` and `llm` each carried a byte-identical copy of this.
|
|
89
|
+
*/
|
|
90
|
+
declare function sourceReader(cwd: string): (file: string) => Promise<string | null>;
|
|
91
|
+
|
|
92
|
+
declare function lineStarts(text: string): number[];
|
|
93
|
+
/** 1-based line and column of a string offset. */
|
|
94
|
+
declare function positionAt(starts: number[], offset: number): {
|
|
95
|
+
line: number;
|
|
96
|
+
column: number;
|
|
97
|
+
};
|
|
98
|
+
/** String offset of a 1-based line and column, clamped to a file of `length` characters. */
|
|
99
|
+
declare function offsetAt(starts: number[], line: number, column: number, length: number): number;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Runs a checker and returns what it printed.
|
|
103
|
+
*
|
|
104
|
+
* A non-zero exit is not a failure: a linter or a command rule exits non-zero precisely when it
|
|
105
|
+
* found something, and the output is the answer. What is a failure is a binary that is not there —
|
|
106
|
+
* reported as `notFound`, which the caller words for its own user — and an error that carries no
|
|
107
|
+
* output at all, such as a permission failure. An abort is rethrown untouched, so the caller can tell the clock from
|
|
108
|
+
* the rule (`pastDeadline`).
|
|
109
|
+
*
|
|
110
|
+
* `command` and `linter` each carried a copy of this, `maxBuffer` and comment included. `llm`'s CLI
|
|
111
|
+
* providers do not use it: they write a prompt to stdin, keep stderr and the exit code, and a
|
|
112
|
+
* missing binary is a whole-run `LlmUnavailableError` there rather than a rule error.
|
|
113
|
+
*/
|
|
114
|
+
declare function runTool(command: string, args: readonly string[], options: {
|
|
115
|
+
cwd: string;
|
|
116
|
+
signal: AbortSignal;
|
|
117
|
+
notFound: string;
|
|
118
|
+
stdin?: string;
|
|
119
|
+
}): Promise<string>;
|
|
13
120
|
|
|
14
121
|
/** The running rulecast version. test/core/version.test.ts keeps it equal to package.json. */
|
|
15
|
-
declare const VERSION = "0.
|
|
122
|
+
declare const VERSION = "0.4.0";
|
|
16
123
|
|
|
17
124
|
declare const builtinDetectors: readonly AnyDetector[];
|
|
18
125
|
|
|
@@ -71,4 +178,4 @@ interface DetectorFixture {
|
|
|
71
178
|
*/
|
|
72
179
|
declare function detectorContract(detector: AnyDetector, fixture: DetectorFixture): ContractCase[];
|
|
73
180
|
|
|
74
|
-
export { ADAPTERS, Adapter, type AdapterFixture, AdapterInput, AnyDetector, type ContractCase, Delivery, Detector, type DetectorFixture, DetectorRuleInput, DetectorRun, Event, Match, VERSION, adapterByName, adapterContract, builtinDetectors, claudeCodeAdapter, detectorContract, perRule };
|
|
181
|
+
export { ADAPTERS, Adapter, type AdapterFixture, AdapterInput, AnyDetector, type BacklogSummary, type ContractCase, Delivery, Detector, type DetectorFixture, DetectorRuleInput, DetectorRun, Event, Match, VERSION, adapterByName, adapterContract, builtinDetectors, claudeCodeAdapter, detectorContract, lineStarts, offsetAt, pastDeadline, perRule, positionAt, renderBacklog, repoRelative, repoRelativeTo, runTool, sourceReader, summarise };
|
package/dist/index.js
CHANGED
|
@@ -3,18 +3,29 @@ import {
|
|
|
3
3
|
adapterByName,
|
|
4
4
|
builtinDetectors,
|
|
5
5
|
claudeCodeAdapter,
|
|
6
|
-
createRegistry
|
|
7
|
-
|
|
6
|
+
createRegistry,
|
|
7
|
+
renderBacklog,
|
|
8
|
+
repoRelative,
|
|
9
|
+
repoRelativeTo,
|
|
10
|
+
runTool,
|
|
11
|
+
summarise
|
|
12
|
+
} from "./chunk-IKONOTME.js";
|
|
8
13
|
import {
|
|
9
14
|
LlmUnavailableError,
|
|
10
15
|
VERSION,
|
|
11
16
|
defaultDetectorSettings,
|
|
12
17
|
emptyDelivery,
|
|
13
18
|
isDetectorRule,
|
|
19
|
+
lineStarts,
|
|
14
20
|
memoryCache,
|
|
21
|
+
offsetAt,
|
|
22
|
+
pastDeadline,
|
|
15
23
|
perRule,
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
positionAt,
|
|
25
|
+
readSourceFile,
|
|
26
|
+
renderAgentText,
|
|
27
|
+
sourceReader
|
|
28
|
+
} from "./chunk-DQUCE2IK.js";
|
|
18
29
|
|
|
19
30
|
// src/testing/adapter-contract.ts
|
|
20
31
|
import assert from "assert/strict";
|
|
@@ -118,6 +129,16 @@ function adapterContract(adapter, fixture) {
|
|
|
118
129
|
const twice = install.merge(structuredClone(once.settings), command, 5e3);
|
|
119
130
|
assert.deepEqual(twice.settings, once.settings, "merging twice must change nothing");
|
|
120
131
|
assert.deepEqual(twice.added, [], "merging twice must add nothing");
|
|
132
|
+
if (twice.updated !== void 0) assert.deepEqual(twice.updated, [], "merging twice must update nothing");
|
|
133
|
+
const upgraded = install.merge(structuredClone(once.settings), install.command(true), 5e3);
|
|
134
|
+
assert.deepEqual(upgraded.added, [], "merging another command over installed hooks must add nothing");
|
|
135
|
+
if (upgraded.updated !== void 0) {
|
|
136
|
+
assert.deepEqual(
|
|
137
|
+
[...upgraded.updated].sort(),
|
|
138
|
+
[...once.added].sort(),
|
|
139
|
+
"merging another command must report every hook it rewrote"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
121
142
|
const removed = install.remove(structuredClone(once.settings));
|
|
122
143
|
assert.deepEqual(removed.settings, original, "remove must return the settings merge was given");
|
|
123
144
|
assert.deepEqual([...removed.removed].sort(), [...once.added].sort(), "remove must name what merge added");
|
|
@@ -297,5 +318,16 @@ export {
|
|
|
297
318
|
detectorContract,
|
|
298
319
|
emptyDelivery,
|
|
299
320
|
isDetectorRule,
|
|
300
|
-
|
|
321
|
+
lineStarts,
|
|
322
|
+
offsetAt,
|
|
323
|
+
pastDeadline,
|
|
324
|
+
perRule,
|
|
325
|
+
positionAt,
|
|
326
|
+
renderAgentText,
|
|
327
|
+
renderBacklog,
|
|
328
|
+
repoRelative,
|
|
329
|
+
repoRelativeTo,
|
|
330
|
+
runTool,
|
|
331
|
+
sourceReader,
|
|
332
|
+
summarise
|
|
301
333
|
};
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { D as DetectorRegistry, C as Config, a as CompiledRule, b as CheckResult, c as DetectorSettings, d as DetectorRule, M as Match, E as Event, e as Delivery, L as LlmProviderName, f as LlmProvider } from './types-Dcra5LXR.js';
|
|
2
|
+
export { R as RuleEntry, S as Stage, r as renderAgentText } from './types-Dcra5LXR.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type Checkout = {
|
|
@@ -47,7 +47,7 @@ interface CompileOptions {
|
|
|
47
47
|
/** The project config and the manifests of its pinned repos → ready rules plus diagnostics (spec §5). */
|
|
48
48
|
declare function compile(options: CompileOptions): Promise<CompiledProject>;
|
|
49
49
|
/** A manifest on its own (validate, the init catalog): references resolve against `dir`. */
|
|
50
|
-
declare function compileManifest(dir: string, registry: DetectorRegistry): Promise<{
|
|
50
|
+
declare function compileManifest(dir: string, registry: DetectorRegistry, file?: string): Promise<{
|
|
51
51
|
rules: CompiledRule[];
|
|
52
52
|
diagnostics: Diagnostic[];
|
|
53
53
|
}>;
|
|
@@ -55,11 +55,6 @@ declare function compileManifest(dir: string, registry: DetectorRegistry): Promi
|
|
|
55
55
|
declare const CONFIG_FILE = ".rulecast-config.yaml";
|
|
56
56
|
declare const MANIFEST_FILE = ".rulecast-rules.yaml";
|
|
57
57
|
|
|
58
|
-
interface RenderOptions {
|
|
59
|
-
maxMatchesPerRule: number;
|
|
60
|
-
}
|
|
61
|
-
declare function renderAgentText(delivery: Delivery, options: RenderOptions): string;
|
|
62
|
-
|
|
63
58
|
interface CheckOptions {
|
|
64
59
|
project: CompiledProject;
|
|
65
60
|
registry: DetectorRegistry;
|
|
@@ -89,6 +84,12 @@ declare function cacheHome(env: Env): string;
|
|
|
89
84
|
*/
|
|
90
85
|
declare function projectStateDir(home: string, root: string): string;
|
|
91
86
|
|
|
87
|
+
interface IgnoredFinding {
|
|
88
|
+
rule: DetectorRule;
|
|
89
|
+
match: Match;
|
|
90
|
+
reason: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
92
93
|
interface PipelineOptions {
|
|
93
94
|
/** Compiled by the caller: hooks never fetch rule repos, the CLI does (spec §4). */
|
|
94
95
|
project: CompiledProject;
|
|
@@ -100,7 +101,7 @@ interface PipelineOptions {
|
|
|
100
101
|
maxContextChars: number | null;
|
|
101
102
|
/** Recently accessed files the agent re-attaches after compaction (adapter.restoredFiles); reset re-delivers their touch context. */
|
|
102
103
|
restoredFiles?: number;
|
|
103
|
-
/** Detector kinds to skip entirely
|
|
104
|
+
/** Detector kinds to skip entirely: the metered ones, for a staged run or --no-llm (commands/run.ts). */
|
|
104
105
|
skipDetectorKinds?: ReadonlySet<string>;
|
|
105
106
|
/** Run only these rules (rulecast run RULE_ID); touch rules are unaffected. */
|
|
106
107
|
onlyRules?: ReadonlySet<string>;
|
|
@@ -115,6 +116,8 @@ interface PipelineResult {
|
|
|
115
116
|
failed: boolean;
|
|
116
117
|
/** Detector kinds whose results were dropped at the edit deadline (§13). */
|
|
117
118
|
deadlineMissed: string[];
|
|
119
|
+
/** Findings a `rulecast-ignore` comment dropped (core/suppress.ts); absent when nothing was detected. */
|
|
120
|
+
ignored?: IgnoredFinding[];
|
|
118
121
|
}
|
|
119
122
|
declare function runPipeline(options: PipelineOptions): Promise<PipelineResult>;
|
|
120
123
|
|
|
@@ -133,4 +136,4 @@ declare function resolveModel(model: string, provider: LlmProviderName): string
|
|
|
133
136
|
*/
|
|
134
137
|
declare function providerByName(name: LlmProviderName): LlmProvider;
|
|
135
138
|
|
|
136
|
-
export { CONFIG_FILE, type Checkout, type CompileOptions, type CompiledProject, Config, type Diagnostic, type Env, type KindCheckResult, MANIFEST_FILE, MODEL_ALIASES, type PipelineOptions, type PipelineResult, type RepoProvider, cacheHome, cachedRepos, checkDetectors, checkableKinds, compile, compileManifest, fetchingRepos, fixedRepo, projectStateDir, providerByName,
|
|
139
|
+
export { CONFIG_FILE, type Checkout, type CompileOptions, type CompiledProject, Config, type Diagnostic, type Env, type KindCheckResult, MANIFEST_FILE, MODEL_ALIASES, type PipelineOptions, type PipelineResult, type RepoProvider, cacheHome, cachedRepos, checkDetectors, checkableKinds, compile, compileManifest, fetchingRepos, fixedRepo, projectStateDir, providerByName, resolveModel, runPipeline };
|
package/dist/internal.js
CHANGED
|
@@ -11,13 +11,13 @@ import {
|
|
|
11
11
|
fixedRepo,
|
|
12
12
|
projectStateDir,
|
|
13
13
|
runPipeline
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WUFU4LW2.js";
|
|
15
15
|
import {
|
|
16
16
|
MODEL_ALIASES,
|
|
17
17
|
providerByName,
|
|
18
18
|
renderAgentText,
|
|
19
19
|
resolveModel
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-DQUCE2IK.js";
|
|
21
21
|
export {
|
|
22
22
|
CONFIG_FILE,
|
|
23
23
|
MANIFEST_FILE,
|
|
@@ -19,6 +19,19 @@ interface WriteIntent {
|
|
|
19
19
|
all: boolean;
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Where a run reads the current content of a file from. The working tree for every hook; the index
|
|
24
|
+
* for `rulecast run` on staged files, so a commit is judged as it will be committed; a commit for
|
|
25
|
+
* `--to-ref`, so a push is judged as it will be pushed.
|
|
26
|
+
*/
|
|
27
|
+
type ContentSource = {
|
|
28
|
+
kind: "worktree";
|
|
29
|
+
} | {
|
|
30
|
+
kind: "index";
|
|
31
|
+
} | {
|
|
32
|
+
kind: "commit";
|
|
33
|
+
ref: string;
|
|
34
|
+
};
|
|
22
35
|
interface Event {
|
|
23
36
|
kind: EventKind;
|
|
24
37
|
/** Repo-relative paths (adapters may give absolute ones; the hook command converts them). Empty for prompt and reset. */
|
|
@@ -29,6 +42,8 @@ interface Event {
|
|
|
29
42
|
completeRead?: boolean;
|
|
30
43
|
/** verify from the CLI: the commit the baseline is read from (the merge base for --from-ref). */
|
|
31
44
|
baseCommit?: string;
|
|
45
|
+
/** verify from the CLI: where current content is read from. Absent: the working tree. */
|
|
46
|
+
content?: ContentSource;
|
|
32
47
|
session?: {
|
|
33
48
|
id: string;
|
|
34
49
|
agentId?: string;
|
|
@@ -88,6 +103,12 @@ interface DetectorRun<Config> {
|
|
|
88
103
|
* is not on disk and never will be if the write is refused.
|
|
89
104
|
*/
|
|
90
105
|
read(file: string): Promise<string | null>;
|
|
106
|
+
/**
|
|
107
|
+
* False when the files are not as they are on disk — a staged run reads the index, `--to-ref` a
|
|
108
|
+
* commit — and this detector declared `takesContent`: it must read through `read` and hand the
|
|
109
|
+
* text to its tool under the file's path, never let the tool open the path. Absent: true.
|
|
110
|
+
*/
|
|
111
|
+
fromDisk?: boolean;
|
|
91
112
|
/** File absent = no baseline, the whole file is new. */
|
|
92
113
|
changes: ReadonlyMap<string, ChangeSet>;
|
|
93
114
|
cache: Cache;
|
|
@@ -158,6 +179,40 @@ interface Detector<Config> {
|
|
|
158
179
|
* not on disk, so ruff, eslint, ast-grep's CLI or a `command` script would judge the old one.
|
|
159
180
|
*/
|
|
160
181
|
guards?: boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Each file costs money or a third party sees it (§6, Consent). The core never preselects such a
|
|
184
|
+
* rule in `init`, never runs it as a side effect — no fingerprint run on `touch`, no `doctor` dry
|
|
185
|
+
* run, no `rulecast test` without a rule id — and budgets the files it is given per event.
|
|
186
|
+
*
|
|
187
|
+
* The core asks this rather than a kind name, the way it asks `guards`, so a second metered
|
|
188
|
+
* detector needs no edit to the core.
|
|
189
|
+
*/
|
|
190
|
+
metered?: boolean;
|
|
191
|
+
/** For `init`'s consent line: what one rule of this kind costs and where the file goes. */
|
|
192
|
+
cost?(config: Config): string;
|
|
193
|
+
/**
|
|
194
|
+
* Most files this detector is given in one verify; the most recently edited are kept (§6).
|
|
195
|
+
* `setting` names the config key that raises it, because a warning that does not name its lever
|
|
196
|
+
* leaves nobody knowing what to do.
|
|
197
|
+
*/
|
|
198
|
+
fileBudget?(settings: DetectorSettings): {
|
|
199
|
+
max: number;
|
|
200
|
+
setting: string;
|
|
201
|
+
};
|
|
202
|
+
/** Added to a verify timeout's warning, when this kind can legitimately need far longer. */
|
|
203
|
+
timeoutHint?: string;
|
|
204
|
+
/**
|
|
205
|
+
* A match is the whole file rather than a range in it, so any write to a matching file is
|
|
206
|
+
* evidence for `refuse_write`, not only a write that inserts the matched text.
|
|
207
|
+
*/
|
|
208
|
+
wholeFile?: boolean;
|
|
209
|
+
/**
|
|
210
|
+
* This rule's tool can be handed a file's content under its real path (ruff `--stdin-filename`,
|
|
211
|
+
* a model prompt). When the content is not the working tree, such a rule is run with
|
|
212
|
+
* `fromDisk: false`; one that cannot is given a scratch copy instead, which configuration keyed
|
|
213
|
+
* on the file's path no longer matches (spec §12).
|
|
214
|
+
*/
|
|
215
|
+
takesContent?(config: Config): boolean;
|
|
161
216
|
run(input: DetectorRun<Config>): Promise<DetectorResult>;
|
|
162
217
|
/** Optional: build expensive caches ahead of events (rulecast warm, §13). */
|
|
163
218
|
warm?(input: DetectorWarm<Config>): Promise<void>;
|
|
@@ -212,6 +267,11 @@ interface Delivery {
|
|
|
212
267
|
omitted: Omitted;
|
|
213
268
|
/** Absolute path of the untrimmed delivery, written when even the floor did not fit; null when it did. */
|
|
214
269
|
overflowPath: string | null;
|
|
270
|
+
/**
|
|
271
|
+
* For the user, not the agent (spec §9, Oversight): the config changed this session, ignores were
|
|
272
|
+
* added. Attached after the budget is spent and never part of agent context under a hook adapter.
|
|
273
|
+
*/
|
|
274
|
+
notices?: string[];
|
|
215
275
|
}
|
|
216
276
|
interface AdapterInput {
|
|
217
277
|
/** The agent's directory; the project root is found from it. */
|
|
@@ -232,9 +292,14 @@ interface AdapterInstall {
|
|
|
232
292
|
}[];
|
|
233
293
|
/** The hook command; local: rulecast is installed in the project's node_modules. */
|
|
234
294
|
command(local: boolean): string;
|
|
295
|
+
/**
|
|
296
|
+
* Adds the hooks. `updated` (optional, for adapters that can tell): the adapter's own hooks whose
|
|
297
|
+
* command differed from `command` and were rewritten — an older install upgraded in place.
|
|
298
|
+
*/
|
|
235
299
|
merge(settings: unknown, command: string, verifyMs: number): {
|
|
236
300
|
settings: unknown;
|
|
237
301
|
added: string[];
|
|
302
|
+
updated?: string[];
|
|
238
303
|
};
|
|
239
304
|
remove(settings: unknown): {
|
|
240
305
|
settings: unknown;
|
|
@@ -264,6 +329,30 @@ declare function emptyDelivery(): Delivery;
|
|
|
264
329
|
|
|
265
330
|
declare const STAGES: readonly ["touch", "edit", "verify"];
|
|
266
331
|
type Stage = (typeof STAGES)[number];
|
|
332
|
+
declare const RULE_SCOPES: readonly ["instance", "container"];
|
|
333
|
+
type RuleScope = (typeof RULE_SCOPES)[number];
|
|
334
|
+
/**
|
|
335
|
+
* One inline example: a file the rule would see, and what is in it.
|
|
336
|
+
*
|
|
337
|
+
* `path` is required and has no useful default — `files`, `exclude`, the type tags and `ast-grep`'s
|
|
338
|
+
* language selection are all functions of it, so an example without one would be testing a
|
|
339
|
+
* different rule than the one that will run.
|
|
340
|
+
*/
|
|
341
|
+
declare const exampleSchema: z.ZodObject<{
|
|
342
|
+
path: z.ZodString;
|
|
343
|
+
code: z.ZodString;
|
|
344
|
+
}, "strict", z.ZodTypeAny, {
|
|
345
|
+
code: string;
|
|
346
|
+
path: string;
|
|
347
|
+
}, {
|
|
348
|
+
code: string;
|
|
349
|
+
path: string;
|
|
350
|
+
}>;
|
|
351
|
+
type RuleExample = z.infer<typeof exampleSchema>;
|
|
352
|
+
type RuleExamples = {
|
|
353
|
+
good: RuleExample[];
|
|
354
|
+
bad: RuleExample[];
|
|
355
|
+
};
|
|
267
356
|
/** A config entry selecting a rule from a rule repo; every key but id overrides the manifest rule's. */
|
|
268
357
|
declare const overrideSchema: z.ZodObject<{
|
|
269
358
|
alias: z.ZodOptional<z.ZodString>;
|
|
@@ -277,7 +366,15 @@ declare const overrideSchema: z.ZodObject<{
|
|
|
277
366
|
stages: z.ZodOptional<z.ZodArray<z.ZodEnum<["touch", "edit", "verify"]>, "atleastone">>;
|
|
278
367
|
minimum_rulecast_version: z.ZodOptional<z.ZodString>;
|
|
279
368
|
severity: z.ZodOptional<z.ZodEnum<["error", "warning"]>>;
|
|
369
|
+
/**
|
|
370
|
+
* What the convention belongs to (§8). `instance`: the token the detector matched, classified by
|
|
371
|
+
* whether the agent's edit touched it. `container`: the node the match spans, classified by
|
|
372
|
+
* whether that node already violated the rule at the baseline.
|
|
373
|
+
*/
|
|
374
|
+
scope: z.ZodOptional<z.ZodEnum<["instance", "container"]>>;
|
|
280
375
|
refuse_write: z.ZodOptional<z.ZodBoolean>;
|
|
376
|
+
/** false: compiled and listed, selected for nothing. The way to switch a catalog rule off without deleting it. */
|
|
377
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
281
378
|
detect: z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>>;
|
|
282
379
|
message: z.ZodOptional<z.ZodString>;
|
|
283
380
|
context: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodObject<{
|
|
@@ -290,11 +387,52 @@ declare const overrideSchema: z.ZodObject<{
|
|
|
290
387
|
path: string;
|
|
291
388
|
mode?: "inject" | "read" | undefined;
|
|
292
389
|
}>]>, "many">>;
|
|
390
|
+
/** Code the rule must flag (`bad`) and must not (`good`), run by `rulecast test`. */
|
|
391
|
+
examples: z.ZodOptional<z.ZodObject<{
|
|
392
|
+
good: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
393
|
+
path: z.ZodString;
|
|
394
|
+
code: z.ZodString;
|
|
395
|
+
}, "strict", z.ZodTypeAny, {
|
|
396
|
+
code: string;
|
|
397
|
+
path: string;
|
|
398
|
+
}, {
|
|
399
|
+
code: string;
|
|
400
|
+
path: string;
|
|
401
|
+
}>, "many">>;
|
|
402
|
+
bad: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
403
|
+
path: z.ZodString;
|
|
404
|
+
code: z.ZodString;
|
|
405
|
+
}, "strict", z.ZodTypeAny, {
|
|
406
|
+
code: string;
|
|
407
|
+
path: string;
|
|
408
|
+
}, {
|
|
409
|
+
code: string;
|
|
410
|
+
path: string;
|
|
411
|
+
}>, "many">>;
|
|
412
|
+
}, "strict", z.ZodTypeAny, {
|
|
413
|
+
good: {
|
|
414
|
+
code: string;
|
|
415
|
+
path: string;
|
|
416
|
+
}[];
|
|
417
|
+
bad: {
|
|
418
|
+
code: string;
|
|
419
|
+
path: string;
|
|
420
|
+
}[];
|
|
421
|
+
}, {
|
|
422
|
+
good?: {
|
|
423
|
+
code: string;
|
|
424
|
+
path: string;
|
|
425
|
+
}[] | undefined;
|
|
426
|
+
bad?: {
|
|
427
|
+
code: string;
|
|
428
|
+
path: string;
|
|
429
|
+
}[] | undefined;
|
|
430
|
+
}>>;
|
|
293
431
|
id: z.ZodString;
|
|
294
432
|
}, "strict", z.ZodTypeAny, {
|
|
295
433
|
id: string;
|
|
296
|
-
name?: string | undefined;
|
|
297
434
|
message?: string | undefined;
|
|
435
|
+
name?: string | undefined;
|
|
298
436
|
alias?: string | undefined;
|
|
299
437
|
description?: string | undefined;
|
|
300
438
|
files?: string | undefined;
|
|
@@ -305,16 +443,28 @@ declare const overrideSchema: z.ZodObject<{
|
|
|
305
443
|
stages?: ["touch" | "edit" | "verify", ...("touch" | "edit" | "verify")[]] | undefined;
|
|
306
444
|
minimum_rulecast_version?: string | undefined;
|
|
307
445
|
severity?: "error" | "warning" | undefined;
|
|
446
|
+
scope?: "instance" | "container" | undefined;
|
|
308
447
|
refuse_write?: boolean | undefined;
|
|
448
|
+
enabled?: boolean | undefined;
|
|
309
449
|
detect?: Record<string, unknown> | undefined;
|
|
310
450
|
context?: (string | {
|
|
311
451
|
path: string;
|
|
312
452
|
mode?: "inject" | "read" | undefined;
|
|
313
453
|
})[] | undefined;
|
|
454
|
+
examples?: {
|
|
455
|
+
good: {
|
|
456
|
+
code: string;
|
|
457
|
+
path: string;
|
|
458
|
+
}[];
|
|
459
|
+
bad: {
|
|
460
|
+
code: string;
|
|
461
|
+
path: string;
|
|
462
|
+
}[];
|
|
463
|
+
} | undefined;
|
|
314
464
|
}, {
|
|
315
465
|
id: string;
|
|
316
|
-
name?: string | undefined;
|
|
317
466
|
message?: string | undefined;
|
|
467
|
+
name?: string | undefined;
|
|
318
468
|
alias?: string | undefined;
|
|
319
469
|
description?: string | undefined;
|
|
320
470
|
files?: string | undefined;
|
|
@@ -325,12 +475,24 @@ declare const overrideSchema: z.ZodObject<{
|
|
|
325
475
|
stages?: ["touch" | "edit" | "verify", ...("touch" | "edit" | "verify")[]] | undefined;
|
|
326
476
|
minimum_rulecast_version?: string | undefined;
|
|
327
477
|
severity?: "error" | "warning" | undefined;
|
|
478
|
+
scope?: "instance" | "container" | undefined;
|
|
328
479
|
refuse_write?: boolean | undefined;
|
|
480
|
+
enabled?: boolean | undefined;
|
|
329
481
|
detect?: Record<string, unknown> | undefined;
|
|
330
482
|
context?: (string | {
|
|
331
483
|
path: string;
|
|
332
484
|
mode?: "inject" | "read" | undefined;
|
|
333
485
|
})[] | undefined;
|
|
486
|
+
examples?: {
|
|
487
|
+
good?: {
|
|
488
|
+
code: string;
|
|
489
|
+
path: string;
|
|
490
|
+
}[] | undefined;
|
|
491
|
+
bad?: {
|
|
492
|
+
code: string;
|
|
493
|
+
path: string;
|
|
494
|
+
}[] | undefined;
|
|
495
|
+
} | undefined;
|
|
334
496
|
}>;
|
|
335
497
|
type RuleEntry = z.infer<typeof overrideSchema>;
|
|
336
498
|
declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
@@ -570,13 +732,31 @@ interface CompiledRule {
|
|
|
570
732
|
/** "local", or the rule repo label ("syv-ai/rulecast@v0.2.0"). */
|
|
571
733
|
source: string;
|
|
572
734
|
severity: Severity;
|
|
735
|
+
/**
|
|
736
|
+
* How a finding of this rule is classified against the baseline (spec §8). `instance` — the
|
|
737
|
+
* default, and every rule before 0.2 — is new when the match touches a changed line. `container`
|
|
738
|
+
* is new when the node the match spans was not already violating the rule at the baseline.
|
|
739
|
+
*/
|
|
740
|
+
scope: RuleScope;
|
|
573
741
|
/** Refuse the agent's write when this rule fires on what it is writing (spec §9, Guard). */
|
|
574
742
|
refuseWrite: boolean;
|
|
743
|
+
/** false: switched off with `enabled: false`; never selected for an event (spec §4). */
|
|
744
|
+
enabled: boolean;
|
|
575
745
|
stages: Stage[];
|
|
746
|
+
/** The rule's own `files` and `exclude`, as written, for `rulecast list`; `matches` is what decides. */
|
|
747
|
+
patterns: {
|
|
748
|
+
files: string;
|
|
749
|
+
exclude: string;
|
|
750
|
+
};
|
|
576
751
|
matches(file: string): boolean;
|
|
577
752
|
detector: CompiledDetector | null;
|
|
578
753
|
message: string | null;
|
|
579
754
|
context: ReferenceSpec[];
|
|
755
|
+
/**
|
|
756
|
+
* Inline good/bad examples, run by `rulecast test`. `null` when the rule declared none, which
|
|
757
|
+
* `validate` and `test` report differently from a rule that declared an empty set.
|
|
758
|
+
*/
|
|
759
|
+
examples: RuleExamples | null;
|
|
580
760
|
}
|
|
581
761
|
/**
|
|
582
762
|
* A rule that runs a detector. compileRule enforces the split — a rule with `detect` must also
|
|
@@ -594,6 +774,23 @@ type TouchRule = CompiledRule & {
|
|
|
594
774
|
/** Whether this rule runs a detector, as a type guard so the narrowing survives a filter. */
|
|
595
775
|
declare function isDetectorRule(rule: CompiledRule): rule is DetectorRule;
|
|
596
776
|
|
|
777
|
+
interface RenderOptions {
|
|
778
|
+
maxMatchesPerRule: number;
|
|
779
|
+
/**
|
|
780
|
+
* Print `delivery.notices` (default true). A hook adapter passes false and shows them to the user
|
|
781
|
+
* itself; `--format agent` serves agents without hooks, where nobody else would see them. Notices
|
|
782
|
+
* are attached after the budget is spent, so they are never priced: no budgeted delivery has any.
|
|
783
|
+
*/
|
|
784
|
+
notices?: boolean;
|
|
785
|
+
/**
|
|
786
|
+
* Print `delivery.warnings` (default true). Rule and detector problems are the human's to fix; a
|
|
787
|
+
* hook adapter that can reach the user passes false and shows them there. Not printing them only
|
|
788
|
+
* ever spends less than the budget reserved for them.
|
|
789
|
+
*/
|
|
790
|
+
warnings?: boolean;
|
|
791
|
+
}
|
|
792
|
+
declare function renderAgentText(delivery: Delivery, options: RenderOptions): string;
|
|
793
|
+
|
|
597
794
|
/** What the model is asked to answer with (spec §6). */
|
|
598
795
|
declare const responseSchema: z.ZodObject<{
|
|
599
796
|
findings: z.ZodArray<z.ZodObject<{
|
|
@@ -669,4 +866,4 @@ interface LlmProvider {
|
|
|
669
866
|
}): Promise<LlmAvailability>;
|
|
670
867
|
}
|
|
671
868
|
|
|
672
|
-
export { type Adapter as A,
|
|
869
|
+
export { type Adapter as A, type LlmSettings as B, type Config as C, type DetectorRegistry as D, type Event as E, type Finding as F, LlmUnavailableError as G, type ReferenceMode as H, type InstallScope as I, type ReferenceSpec as J, type RenderOptions as K, type LlmProviderName as L, type Match as M, type ResolvedReference as N, type Omitted as O, type RuleExample as P, type RuleExamples as Q, type RuleEntry as R, type Stage as S, type RuleScope as T, type Severity as U, type TouchRule as V, type WriteIntent as W, createRegistry as X, defaultDetectorSettings as Y, emptyDelivery as Z, isDetectorRule as _, type CompiledRule as a, type CheckResult as b, type DetectorSettings as c, type DetectorRule as d, type Delivery as e, type LlmProvider as f, type DetectorRun as g, type DetectorRuleInput as h, type Detector as i, type AnyDetector as j, type AdapterInput as k, type AdapterInstall as l, type Cache as m, type ChangeSet as n, type CompiledDetector as o, type ContentSource as p, type DeliveredReference as q, renderAgentText as r, type DetectorCheck as s, type DetectorEvent as t, type DetectorResult as u, type DetectorWarm as v, type EventKind as w, LLM_PROVIDERS as x, type LlmFinding as y, type LlmRequest as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syv-ai/rulecast",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Deliver project conventions to coding agents at the moment they matter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"./internal": {
|
|
36
36
|
"types": "./dist/internal.d.ts",
|
|
37
37
|
"import": "./dist/internal.js"
|
|
38
|
-
}
|
|
38
|
+
},
|
|
39
|
+
"./cli": "./dist/cli.js"
|
|
39
40
|
},
|
|
40
41
|
"files": [
|
|
41
42
|
"dist"
|