@syv-ai/rulecast 0.1.1 → 0.3.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 +49 -2
- package/dist/chunk-7KUPYT35.js +1977 -0
- package/dist/chunk-HZUATJ53.js +947 -0
- package/dist/chunk-KOUHBT7F.js +1361 -0
- package/dist/cli.js +326 -42
- package/dist/index.d.ts +33 -763
- package/dist/index.js +15 -37
- package/dist/internal.d.ts +136 -0
- package/dist/internal.js +38 -0
- package/dist/types-C8b7hrnG.d.ts +776 -0
- package/package.json +8 -2
- package/dist/chunk-3HRTZULM.js +0 -3780
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
import { ZodType, ZodTypeDef, z } from 'zod';
|
|
2
|
+
|
|
3
|
+
type EventKind = "touch" | "edit" | "verify" | "prompt" | "reset" | "guard";
|
|
4
|
+
type DetectorEvent = "edit" | "verify";
|
|
5
|
+
type Severity = "error" | "warning";
|
|
6
|
+
type ReferenceMode = "inject" | "read";
|
|
7
|
+
/**
|
|
8
|
+
* What a `guard` event's write would do, as the agent's tool call describes it and before anything
|
|
9
|
+
* has touched the disk. The adapter reports the intent; the pipeline turns it into the file's
|
|
10
|
+
* proposed content, because only the pipeline may read the file it will be applied to.
|
|
11
|
+
*/
|
|
12
|
+
interface WriteIntent {
|
|
13
|
+
/** The whole file, for a tool that supplies it (Write).*/
|
|
14
|
+
content?: string;
|
|
15
|
+
/** A replacement inside the current file (Edit). */
|
|
16
|
+
edit?: {
|
|
17
|
+
find: string;
|
|
18
|
+
replace: string;
|
|
19
|
+
all: boolean;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
interface Event {
|
|
23
|
+
kind: EventKind;
|
|
24
|
+
/** Repo-relative paths (adapters may give absolute ones; the hook command converts them). Empty for prompt and reset. */
|
|
25
|
+
files: string[];
|
|
26
|
+
/** guard only: what the agent is about to write to `files[0]`. */
|
|
27
|
+
intent?: WriteIntent;
|
|
28
|
+
/** touch from a read: the whole file was read. */
|
|
29
|
+
completeRead?: boolean;
|
|
30
|
+
/** verify from the CLI: the commit the baseline is read from (the merge base for --from-ref). */
|
|
31
|
+
baseCommit?: string;
|
|
32
|
+
session?: {
|
|
33
|
+
id: string;
|
|
34
|
+
agentId?: string;
|
|
35
|
+
};
|
|
36
|
+
cwd: string;
|
|
37
|
+
}
|
|
38
|
+
interface Match {
|
|
39
|
+
file: string;
|
|
40
|
+
/** 1-based. */
|
|
41
|
+
line: number;
|
|
42
|
+
endLine: number;
|
|
43
|
+
column: number;
|
|
44
|
+
text: string;
|
|
45
|
+
/** Exactly the names the detector declared for the rule. */
|
|
46
|
+
captures: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
interface ChangeSet {
|
|
49
|
+
/** 1-based inclusive ranges in the current file. */
|
|
50
|
+
changedLines: [start: number, end: number][];
|
|
51
|
+
}
|
|
52
|
+
interface Cache {
|
|
53
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
54
|
+
set(key: string, value: unknown): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
interface ResolvedReference {
|
|
57
|
+
/** "conventions/api-access.md#frontend-data-flow" */
|
|
58
|
+
ref: string;
|
|
59
|
+
content: string;
|
|
60
|
+
}
|
|
61
|
+
declare const LLM_PROVIDERS: readonly ["claude-code", "opencode", "anthropic", "openai-compatible"];
|
|
62
|
+
type LlmProviderName = (typeof LLM_PROVIDERS)[number];
|
|
63
|
+
/** Project-level `llm` settings from .rulecast-config.yaml (spec §6, §12). */
|
|
64
|
+
interface LlmSettings {
|
|
65
|
+
provider: LlmProviderName;
|
|
66
|
+
/** Overrides the provider's own endpoint; the only way to reach Azure OpenAI or Ollama. */
|
|
67
|
+
baseUrl: string | null;
|
|
68
|
+
apiKeyEnv: string;
|
|
69
|
+
maxFilesPerVerify: number;
|
|
70
|
+
}
|
|
71
|
+
/** Project settings the core passes to every detector run; only `llm` reads them today. */
|
|
72
|
+
interface DetectorSettings {
|
|
73
|
+
llm: LlmSettings;
|
|
74
|
+
}
|
|
75
|
+
declare function defaultDetectorSettings(): DetectorSettings;
|
|
76
|
+
interface DetectorRuleInput<Config> {
|
|
77
|
+
id: string;
|
|
78
|
+
config: Config;
|
|
79
|
+
files: string[];
|
|
80
|
+
context: ResolvedReference[];
|
|
81
|
+
}
|
|
82
|
+
interface DetectorRun<Config> {
|
|
83
|
+
event: DetectorEvent;
|
|
84
|
+
rules: DetectorRuleInput<Config>[];
|
|
85
|
+
/**
|
|
86
|
+
* The file's content, null when it no longer exists. A detector that can read a file itself must
|
|
87
|
+
* use this instead: before a write, the content that matters is the one the agent proposed, which
|
|
88
|
+
* is not on disk and never will be if the write is refused.
|
|
89
|
+
*/
|
|
90
|
+
read(file: string): Promise<string | null>;
|
|
91
|
+
/** File absent = no baseline, the whole file is new. */
|
|
92
|
+
changes: ReadonlyMap<string, ChangeSet>;
|
|
93
|
+
cache: Cache;
|
|
94
|
+
/** Project settings from .rulecast-config.yaml; a detector that needs none ignores them. */
|
|
95
|
+
settings: DetectorSettings;
|
|
96
|
+
cwd: string;
|
|
97
|
+
signal: AbortSignal;
|
|
98
|
+
/**
|
|
99
|
+
* `Date.now()` after which this run's results are discarded (§13). A detector whose work is
|
|
100
|
+
* synchronous must bound itself against this: `signal` cannot help it, because the timer that
|
|
101
|
+
* fires it only runs when the event loop is free, and synchronous work is what keeps it busy.
|
|
102
|
+
*/
|
|
103
|
+
deadlineAt: number;
|
|
104
|
+
}
|
|
105
|
+
interface DetectorResult {
|
|
106
|
+
findings: {
|
|
107
|
+
rule: string;
|
|
108
|
+
match: Match;
|
|
109
|
+
}[];
|
|
110
|
+
/** rule null = the whole run failed. */
|
|
111
|
+
errors: {
|
|
112
|
+
rule: string | null;
|
|
113
|
+
message: string;
|
|
114
|
+
}[];
|
|
115
|
+
}
|
|
116
|
+
interface DetectorWarm<Config> {
|
|
117
|
+
/** Every rule of this detector kind in the project. */
|
|
118
|
+
rules: {
|
|
119
|
+
id: string;
|
|
120
|
+
config: Config;
|
|
121
|
+
}[];
|
|
122
|
+
cache: Cache;
|
|
123
|
+
cwd: string;
|
|
124
|
+
signal: AbortSignal;
|
|
125
|
+
}
|
|
126
|
+
interface DetectorCheck<Config> {
|
|
127
|
+
/** Every rule of this detector kind in the project. */
|
|
128
|
+
rules: {
|
|
129
|
+
id: string;
|
|
130
|
+
config: Config;
|
|
131
|
+
}[];
|
|
132
|
+
/** Project settings from .rulecast-config.yaml; the same value a run gets. */
|
|
133
|
+
settings: DetectorSettings;
|
|
134
|
+
/** The process environment, so a check can look for credentials without reaching for process.env. */
|
|
135
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
136
|
+
cwd: string;
|
|
137
|
+
signal: AbortSignal;
|
|
138
|
+
}
|
|
139
|
+
/** One thing `rulecast doctor` asked a detector about, and the answer (spec §5). */
|
|
140
|
+
interface CheckResult {
|
|
141
|
+
/** What was checked, as a person would name it: "ruff", "ast-grep", `model "haiku"`. */
|
|
142
|
+
what: string;
|
|
143
|
+
level: "ok" | "warning" | "error";
|
|
144
|
+
/** Why: a resolved path when it worked, the reason when it did not. */
|
|
145
|
+
detail: string;
|
|
146
|
+
/** The rules this result decides the fate of; empty when it is about the kind as a whole. */
|
|
147
|
+
rules: string[];
|
|
148
|
+
}
|
|
149
|
+
interface Detector<Config> {
|
|
150
|
+
kind: string;
|
|
151
|
+
/** Input is unknown: schemas may apply defaults and refinements. */
|
|
152
|
+
schema: ZodType<Config, ZodTypeDef, unknown>;
|
|
153
|
+
captures(config: Config): string[];
|
|
154
|
+
events(config: Config): DetectorEvent[];
|
|
155
|
+
/**
|
|
156
|
+
* Whether this detector reads only through `read`, and so can judge a write before it happens
|
|
157
|
+
* (`refuse_write`). False for anything that hands a path to another program: the proposed file is
|
|
158
|
+
* not on disk, so ruff, eslint, ast-grep's CLI or a `command` script would judge the old one.
|
|
159
|
+
*/
|
|
160
|
+
guards?: boolean;
|
|
161
|
+
run(input: DetectorRun<Config>): Promise<DetectorResult>;
|
|
162
|
+
/** Optional: build expensive caches ahead of events (rulecast warm, §13). */
|
|
163
|
+
warm?(input: DetectorWarm<Config>): Promise<void>;
|
|
164
|
+
/** Optional: report what this detector's rules need from the environment (rulecast doctor, §5). */
|
|
165
|
+
check?(input: DetectorCheck<Config>): Promise<CheckResult[]>;
|
|
166
|
+
}
|
|
167
|
+
interface Finding {
|
|
168
|
+
rule: string;
|
|
169
|
+
severity: Severity;
|
|
170
|
+
status: "new" | "preexisting";
|
|
171
|
+
file: string;
|
|
172
|
+
line: number;
|
|
173
|
+
column: number;
|
|
174
|
+
message: string;
|
|
175
|
+
count: number;
|
|
176
|
+
/** The detector's captures and the matched text, for a grouped rendering's per-site line. */
|
|
177
|
+
captures?: Record<string, string>;
|
|
178
|
+
}
|
|
179
|
+
interface DeliveredReference {
|
|
180
|
+
ref: string;
|
|
181
|
+
state: "full" | "pointer" | "read" | "missing";
|
|
182
|
+
content?: string;
|
|
183
|
+
reason?: "mode" | "budget" | "tooLarge";
|
|
184
|
+
/** State "read" of a reference from a rule repo: the absolute path of the file to read. */
|
|
185
|
+
location?: string;
|
|
186
|
+
}
|
|
187
|
+
/** What the context budget left out. A renderer says how much is missing; it never recomputes it. */
|
|
188
|
+
interface Omitted {
|
|
189
|
+
/** Findings of a rule that is still delivered, and how many files they were in. */
|
|
190
|
+
findings: {
|
|
191
|
+
rule: string;
|
|
192
|
+
count: number;
|
|
193
|
+
files: number;
|
|
194
|
+
}[];
|
|
195
|
+
/** Rules dropped whole, with their findings: only when the floor itself did not fit. */
|
|
196
|
+
rules: number;
|
|
197
|
+
preexisting: number;
|
|
198
|
+
}
|
|
199
|
+
interface Delivery {
|
|
200
|
+
findings: Finding[];
|
|
201
|
+
preexistingSummary: {
|
|
202
|
+
rule: string;
|
|
203
|
+
file: string;
|
|
204
|
+
count: number;
|
|
205
|
+
}[];
|
|
206
|
+
references: DeliveredReference[];
|
|
207
|
+
touches: string[];
|
|
208
|
+
stop: "block" | "allow" | "capReached" | null;
|
|
209
|
+
warnings: string[];
|
|
210
|
+
/** `message` template per rule with findings: the skeleton a grouped rendering prints once. */
|
|
211
|
+
templates: Record<string, string>;
|
|
212
|
+
omitted: Omitted;
|
|
213
|
+
/** Absolute path of the untrimmed delivery, written when even the floor did not fit; null when it did. */
|
|
214
|
+
overflowPath: string | null;
|
|
215
|
+
}
|
|
216
|
+
interface AdapterInput {
|
|
217
|
+
/** The agent's directory; the project root is found from it. */
|
|
218
|
+
cwd: string;
|
|
219
|
+
/** null: nothing to run for this input. */
|
|
220
|
+
event: Event | null;
|
|
221
|
+
/** Start detector warm-up (§13). */
|
|
222
|
+
warmup: boolean;
|
|
223
|
+
}
|
|
224
|
+
type InstallScope = "shared" | "personal";
|
|
225
|
+
interface AdapterInstall {
|
|
226
|
+
/** Paths whose presence means the project uses this agent (init); a trailing "/" means a directory. */
|
|
227
|
+
markers: string[];
|
|
228
|
+
/** Settings files, repo-relative, that hooks can be written to. */
|
|
229
|
+
scopes: {
|
|
230
|
+
scope: InstallScope;
|
|
231
|
+
file: string;
|
|
232
|
+
}[];
|
|
233
|
+
/** The hook command; local: rulecast is installed in the project's node_modules. */
|
|
234
|
+
command(local: boolean): string;
|
|
235
|
+
merge(settings: unknown, command: string, verifyMs: number): {
|
|
236
|
+
settings: unknown;
|
|
237
|
+
added: string[];
|
|
238
|
+
};
|
|
239
|
+
remove(settings: unknown): {
|
|
240
|
+
settings: unknown;
|
|
241
|
+
removed: string[];
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
interface Adapter {
|
|
245
|
+
name: string;
|
|
246
|
+
/** Shown to people: "Claude Code". */
|
|
247
|
+
label: string;
|
|
248
|
+
/** Budget handed to commit (§9); null = unlimited. */
|
|
249
|
+
maxContextChars: number | null;
|
|
250
|
+
/** Recently read or edited files the agent re-attaches to its context after compaction; reset re-delivers their touch context (§9). 0 = none. */
|
|
251
|
+
restoredFiles: number;
|
|
252
|
+
/** null: not an input this adapter handles. */
|
|
253
|
+
parse(input: unknown): AdapterInput | null;
|
|
254
|
+
format(delivery: Delivery, event: Event, options: {
|
|
255
|
+
maxMatchesPerRule: number;
|
|
256
|
+
}): {
|
|
257
|
+
stdout: string;
|
|
258
|
+
exitCode: number;
|
|
259
|
+
};
|
|
260
|
+
/** null: the agent has no hooks to install. */
|
|
261
|
+
install: AdapterInstall | null;
|
|
262
|
+
}
|
|
263
|
+
declare function emptyDelivery(): Delivery;
|
|
264
|
+
|
|
265
|
+
declare const STAGES: readonly ["touch", "edit", "verify"];
|
|
266
|
+
type Stage = (typeof STAGES)[number];
|
|
267
|
+
declare const RULE_SCOPES: readonly ["instance", "container"];
|
|
268
|
+
type RuleScope = (typeof RULE_SCOPES)[number];
|
|
269
|
+
/**
|
|
270
|
+
* One inline example: a file the rule would see, and what is in it.
|
|
271
|
+
*
|
|
272
|
+
* `path` is required and has no useful default — `files`, `exclude`, the type tags and `ast-grep`'s
|
|
273
|
+
* language selection are all functions of it, so an example without one would be testing a
|
|
274
|
+
* different rule than the one that will run.
|
|
275
|
+
*/
|
|
276
|
+
declare const exampleSchema: z.ZodObject<{
|
|
277
|
+
path: z.ZodString;
|
|
278
|
+
code: z.ZodString;
|
|
279
|
+
}, "strict", z.ZodTypeAny, {
|
|
280
|
+
code: string;
|
|
281
|
+
path: string;
|
|
282
|
+
}, {
|
|
283
|
+
code: string;
|
|
284
|
+
path: string;
|
|
285
|
+
}>;
|
|
286
|
+
type RuleExample = z.infer<typeof exampleSchema>;
|
|
287
|
+
type RuleExamples = {
|
|
288
|
+
good: RuleExample[];
|
|
289
|
+
bad: RuleExample[];
|
|
290
|
+
};
|
|
291
|
+
/** A config entry selecting a rule from a rule repo; every key but id overrides the manifest rule's. */
|
|
292
|
+
declare const overrideSchema: z.ZodObject<{
|
|
293
|
+
alias: z.ZodOptional<z.ZodString>;
|
|
294
|
+
name: z.ZodOptional<z.ZodString>;
|
|
295
|
+
description: z.ZodOptional<z.ZodString>;
|
|
296
|
+
files: z.ZodOptional<z.ZodString>;
|
|
297
|
+
exclude: z.ZodOptional<z.ZodString>;
|
|
298
|
+
types: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
299
|
+
types_or: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
300
|
+
exclude_types: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
301
|
+
stages: z.ZodOptional<z.ZodArray<z.ZodEnum<["touch", "edit", "verify"]>, "atleastone">>;
|
|
302
|
+
minimum_rulecast_version: z.ZodOptional<z.ZodString>;
|
|
303
|
+
severity: z.ZodOptional<z.ZodEnum<["error", "warning"]>>;
|
|
304
|
+
/**
|
|
305
|
+
* What the convention belongs to (§8). `instance`: the token the detector matched, classified by
|
|
306
|
+
* whether the agent's edit touched it. `container`: the node the match spans, classified by
|
|
307
|
+
* whether that node already violated the rule at the baseline.
|
|
308
|
+
*/
|
|
309
|
+
scope: z.ZodOptional<z.ZodEnum<["instance", "container"]>>;
|
|
310
|
+
refuse_write: z.ZodOptional<z.ZodBoolean>;
|
|
311
|
+
detect: z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>>;
|
|
312
|
+
message: z.ZodOptional<z.ZodString>;
|
|
313
|
+
context: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodObject<{
|
|
314
|
+
path: z.ZodString;
|
|
315
|
+
mode: z.ZodOptional<z.ZodEnum<["inject", "read"]>>;
|
|
316
|
+
}, "strict", z.ZodTypeAny, {
|
|
317
|
+
path: string;
|
|
318
|
+
mode?: "inject" | "read" | undefined;
|
|
319
|
+
}, {
|
|
320
|
+
path: string;
|
|
321
|
+
mode?: "inject" | "read" | undefined;
|
|
322
|
+
}>]>, "many">>;
|
|
323
|
+
/** Code the rule must flag (`bad`) and must not (`good`), run by `rulecast test`. */
|
|
324
|
+
examples: z.ZodOptional<z.ZodObject<{
|
|
325
|
+
good: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
326
|
+
path: z.ZodString;
|
|
327
|
+
code: z.ZodString;
|
|
328
|
+
}, "strict", z.ZodTypeAny, {
|
|
329
|
+
code: string;
|
|
330
|
+
path: string;
|
|
331
|
+
}, {
|
|
332
|
+
code: string;
|
|
333
|
+
path: string;
|
|
334
|
+
}>, "many">>;
|
|
335
|
+
bad: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
336
|
+
path: z.ZodString;
|
|
337
|
+
code: z.ZodString;
|
|
338
|
+
}, "strict", z.ZodTypeAny, {
|
|
339
|
+
code: string;
|
|
340
|
+
path: string;
|
|
341
|
+
}, {
|
|
342
|
+
code: string;
|
|
343
|
+
path: string;
|
|
344
|
+
}>, "many">>;
|
|
345
|
+
}, "strict", z.ZodTypeAny, {
|
|
346
|
+
good: {
|
|
347
|
+
code: string;
|
|
348
|
+
path: string;
|
|
349
|
+
}[];
|
|
350
|
+
bad: {
|
|
351
|
+
code: string;
|
|
352
|
+
path: string;
|
|
353
|
+
}[];
|
|
354
|
+
}, {
|
|
355
|
+
good?: {
|
|
356
|
+
code: string;
|
|
357
|
+
path: string;
|
|
358
|
+
}[] | undefined;
|
|
359
|
+
bad?: {
|
|
360
|
+
code: string;
|
|
361
|
+
path: string;
|
|
362
|
+
}[] | undefined;
|
|
363
|
+
}>>;
|
|
364
|
+
id: z.ZodString;
|
|
365
|
+
}, "strict", z.ZodTypeAny, {
|
|
366
|
+
id: string;
|
|
367
|
+
name?: string | undefined;
|
|
368
|
+
message?: string | undefined;
|
|
369
|
+
alias?: string | undefined;
|
|
370
|
+
description?: string | undefined;
|
|
371
|
+
files?: string | undefined;
|
|
372
|
+
exclude?: string | undefined;
|
|
373
|
+
types?: string[] | undefined;
|
|
374
|
+
types_or?: string[] | undefined;
|
|
375
|
+
exclude_types?: string[] | undefined;
|
|
376
|
+
stages?: ["touch" | "edit" | "verify", ...("touch" | "edit" | "verify")[]] | undefined;
|
|
377
|
+
minimum_rulecast_version?: string | undefined;
|
|
378
|
+
severity?: "error" | "warning" | undefined;
|
|
379
|
+
scope?: "instance" | "container" | undefined;
|
|
380
|
+
refuse_write?: boolean | undefined;
|
|
381
|
+
detect?: Record<string, unknown> | undefined;
|
|
382
|
+
context?: (string | {
|
|
383
|
+
path: string;
|
|
384
|
+
mode?: "inject" | "read" | undefined;
|
|
385
|
+
})[] | undefined;
|
|
386
|
+
examples?: {
|
|
387
|
+
good: {
|
|
388
|
+
code: string;
|
|
389
|
+
path: string;
|
|
390
|
+
}[];
|
|
391
|
+
bad: {
|
|
392
|
+
code: string;
|
|
393
|
+
path: string;
|
|
394
|
+
}[];
|
|
395
|
+
} | undefined;
|
|
396
|
+
}, {
|
|
397
|
+
id: string;
|
|
398
|
+
name?: string | undefined;
|
|
399
|
+
message?: string | undefined;
|
|
400
|
+
alias?: string | undefined;
|
|
401
|
+
description?: string | undefined;
|
|
402
|
+
files?: string | undefined;
|
|
403
|
+
exclude?: string | undefined;
|
|
404
|
+
types?: string[] | undefined;
|
|
405
|
+
types_or?: string[] | undefined;
|
|
406
|
+
exclude_types?: string[] | undefined;
|
|
407
|
+
stages?: ["touch" | "edit" | "verify", ...("touch" | "edit" | "verify")[]] | undefined;
|
|
408
|
+
minimum_rulecast_version?: string | undefined;
|
|
409
|
+
severity?: "error" | "warning" | undefined;
|
|
410
|
+
scope?: "instance" | "container" | undefined;
|
|
411
|
+
refuse_write?: boolean | undefined;
|
|
412
|
+
detect?: Record<string, unknown> | undefined;
|
|
413
|
+
context?: (string | {
|
|
414
|
+
path: string;
|
|
415
|
+
mode?: "inject" | "read" | undefined;
|
|
416
|
+
})[] | undefined;
|
|
417
|
+
examples?: {
|
|
418
|
+
good?: {
|
|
419
|
+
code: string;
|
|
420
|
+
path: string;
|
|
421
|
+
}[] | undefined;
|
|
422
|
+
bad?: {
|
|
423
|
+
code: string;
|
|
424
|
+
path: string;
|
|
425
|
+
}[] | undefined;
|
|
426
|
+
} | undefined;
|
|
427
|
+
}>;
|
|
428
|
+
type RuleEntry = z.infer<typeof overrideSchema>;
|
|
429
|
+
declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
430
|
+
repos: z.ZodArray<z.ZodObject<{
|
|
431
|
+
repo: z.ZodString;
|
|
432
|
+
rev: z.ZodOptional<z.ZodString>;
|
|
433
|
+
/** Parsed rule by rule in compile, so one bad rule does not reject the config. */
|
|
434
|
+
rules: z.ZodArray<z.ZodUnknown, "many">;
|
|
435
|
+
}, "strict", z.ZodTypeAny, {
|
|
436
|
+
rules: unknown[];
|
|
437
|
+
repo: string;
|
|
438
|
+
rev?: string | undefined;
|
|
439
|
+
}, {
|
|
440
|
+
rules: unknown[];
|
|
441
|
+
repo: string;
|
|
442
|
+
rev?: string | undefined;
|
|
443
|
+
}>, "many">;
|
|
444
|
+
minimum_rulecast_version: z.ZodOptional<z.ZodString>;
|
|
445
|
+
files: z.ZodDefault<z.ZodString>;
|
|
446
|
+
exclude: z.ZodDefault<z.ZodString>;
|
|
447
|
+
default_stages: z.ZodOptional<z.ZodArray<z.ZodEnum<["touch", "edit", "verify"]>, "atleastone">>;
|
|
448
|
+
context: z.ZodDefault<z.ZodObject<{
|
|
449
|
+
mode: z.ZodDefault<z.ZodEnum<["inject", "read"]>>;
|
|
450
|
+
max_bytes: z.ZodDefault<z.ZodNumber>;
|
|
451
|
+
}, "strict", z.ZodTypeAny, {
|
|
452
|
+
mode: "inject" | "read";
|
|
453
|
+
max_bytes: number;
|
|
454
|
+
}, {
|
|
455
|
+
mode?: "inject" | "read" | undefined;
|
|
456
|
+
max_bytes?: number | undefined;
|
|
457
|
+
}>>;
|
|
458
|
+
max_matches_per_rule: z.ZodDefault<z.ZodNumber>;
|
|
459
|
+
/**
|
|
460
|
+
* Files above this many bytes are skipped by the in-process detectors on edit and guard, where
|
|
461
|
+
* an agent is waiting. `ast-grep` parses in native code, which the vm timeout that bounds
|
|
462
|
+
* `regex` cannot interrupt: a 2.6 MB TypeScript file measured 762 ms, and the cost is linear,
|
|
463
|
+
* so 26 MB would be 7.6 s of a blocked write. verify has seconds to spend and always runs.
|
|
464
|
+
*/
|
|
465
|
+
max_file_bytes: z.ZodDefault<z.ZodNumber>;
|
|
466
|
+
timeouts: z.ZodDefault<z.ZodObject<{
|
|
467
|
+
edit_deadline_ms: z.ZodDefault<z.ZodNumber>;
|
|
468
|
+
verify_ms: z.ZodDefault<z.ZodNumber>;
|
|
469
|
+
}, "strict", z.ZodTypeAny, {
|
|
470
|
+
edit_deadline_ms: number;
|
|
471
|
+
verify_ms: number;
|
|
472
|
+
}, {
|
|
473
|
+
edit_deadline_ms?: number | undefined;
|
|
474
|
+
verify_ms?: number | undefined;
|
|
475
|
+
}>>;
|
|
476
|
+
stop_gate: z.ZodDefault<z.ZodObject<{
|
|
477
|
+
max_blocks: z.ZodDefault<z.ZodNumber>;
|
|
478
|
+
}, "strict", z.ZodTypeAny, {
|
|
479
|
+
max_blocks: number;
|
|
480
|
+
}, {
|
|
481
|
+
max_blocks?: number | undefined;
|
|
482
|
+
}>>;
|
|
483
|
+
refuse_gate: z.ZodDefault<z.ZodObject<{
|
|
484
|
+
max_refusals: z.ZodDefault<z.ZodNumber>;
|
|
485
|
+
}, "strict", z.ZodTypeAny, {
|
|
486
|
+
max_refusals: number;
|
|
487
|
+
}, {
|
|
488
|
+
max_refusals?: number | undefined;
|
|
489
|
+
}>>;
|
|
490
|
+
llm: z.ZodDefault<z.ZodObject<{
|
|
491
|
+
provider: z.ZodDefault<z.ZodEnum<["claude-code", "opencode", "anthropic", "openai-compatible"]>>;
|
|
492
|
+
base_url: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
493
|
+
api_key_env: z.ZodDefault<z.ZodString>;
|
|
494
|
+
max_files_per_verify: z.ZodDefault<z.ZodNumber>;
|
|
495
|
+
}, "strict", z.ZodTypeAny, {
|
|
496
|
+
provider: "claude-code" | "opencode" | "anthropic" | "openai-compatible";
|
|
497
|
+
base_url: string | null;
|
|
498
|
+
api_key_env: string;
|
|
499
|
+
max_files_per_verify: number;
|
|
500
|
+
}, {
|
|
501
|
+
provider?: "claude-code" | "opencode" | "anthropic" | "openai-compatible" | undefined;
|
|
502
|
+
base_url?: string | null | undefined;
|
|
503
|
+
api_key_env?: string | undefined;
|
|
504
|
+
max_files_per_verify?: number | undefined;
|
|
505
|
+
}>>;
|
|
506
|
+
}, "strict", z.ZodTypeAny, {
|
|
507
|
+
llm: {
|
|
508
|
+
provider: "claude-code" | "opencode" | "anthropic" | "openai-compatible";
|
|
509
|
+
base_url: string | null;
|
|
510
|
+
api_key_env: string;
|
|
511
|
+
max_files_per_verify: number;
|
|
512
|
+
};
|
|
513
|
+
repos: {
|
|
514
|
+
rules: unknown[];
|
|
515
|
+
repo: string;
|
|
516
|
+
rev?: string | undefined;
|
|
517
|
+
}[];
|
|
518
|
+
files: string;
|
|
519
|
+
exclude: string;
|
|
520
|
+
context: {
|
|
521
|
+
mode: "inject" | "read";
|
|
522
|
+
max_bytes: number;
|
|
523
|
+
};
|
|
524
|
+
max_matches_per_rule: number;
|
|
525
|
+
max_file_bytes: number;
|
|
526
|
+
timeouts: {
|
|
527
|
+
edit_deadline_ms: number;
|
|
528
|
+
verify_ms: number;
|
|
529
|
+
};
|
|
530
|
+
stop_gate: {
|
|
531
|
+
max_blocks: number;
|
|
532
|
+
};
|
|
533
|
+
refuse_gate: {
|
|
534
|
+
max_refusals: number;
|
|
535
|
+
};
|
|
536
|
+
minimum_rulecast_version?: string | undefined;
|
|
537
|
+
default_stages?: ["touch" | "edit" | "verify", ...("touch" | "edit" | "verify")[]] | undefined;
|
|
538
|
+
}, {
|
|
539
|
+
repos: {
|
|
540
|
+
rules: unknown[];
|
|
541
|
+
repo: string;
|
|
542
|
+
rev?: string | undefined;
|
|
543
|
+
}[];
|
|
544
|
+
llm?: {
|
|
545
|
+
provider?: "claude-code" | "opencode" | "anthropic" | "openai-compatible" | undefined;
|
|
546
|
+
base_url?: string | null | undefined;
|
|
547
|
+
api_key_env?: string | undefined;
|
|
548
|
+
max_files_per_verify?: number | undefined;
|
|
549
|
+
} | undefined;
|
|
550
|
+
files?: string | undefined;
|
|
551
|
+
exclude?: string | undefined;
|
|
552
|
+
minimum_rulecast_version?: string | undefined;
|
|
553
|
+
context?: {
|
|
554
|
+
mode?: "inject" | "read" | undefined;
|
|
555
|
+
max_bytes?: number | undefined;
|
|
556
|
+
} | undefined;
|
|
557
|
+
default_stages?: ["touch" | "edit" | "verify", ...("touch" | "edit" | "verify")[]] | undefined;
|
|
558
|
+
max_matches_per_rule?: number | undefined;
|
|
559
|
+
max_file_bytes?: number | undefined;
|
|
560
|
+
timeouts?: {
|
|
561
|
+
edit_deadline_ms?: number | undefined;
|
|
562
|
+
verify_ms?: number | undefined;
|
|
563
|
+
} | undefined;
|
|
564
|
+
stop_gate?: {
|
|
565
|
+
max_blocks?: number | undefined;
|
|
566
|
+
} | undefined;
|
|
567
|
+
refuse_gate?: {
|
|
568
|
+
max_refusals?: number | undefined;
|
|
569
|
+
} | undefined;
|
|
570
|
+
}>, {
|
|
571
|
+
repos: {
|
|
572
|
+
rules: unknown[];
|
|
573
|
+
repo: string;
|
|
574
|
+
rev?: string | undefined;
|
|
575
|
+
}[];
|
|
576
|
+
minimumRulecastVersion: string | null;
|
|
577
|
+
files: string;
|
|
578
|
+
exclude: string;
|
|
579
|
+
defaultStages: ["touch" | "edit" | "verify", ...("touch" | "edit" | "verify")[]] | null;
|
|
580
|
+
context: {
|
|
581
|
+
mode: "inject" | "read";
|
|
582
|
+
maxBytes: number;
|
|
583
|
+
};
|
|
584
|
+
maxMatchesPerRule: number;
|
|
585
|
+
maxFileBytes: number;
|
|
586
|
+
timeouts: {
|
|
587
|
+
editDeadlineMs: number;
|
|
588
|
+
verifyMs: number;
|
|
589
|
+
};
|
|
590
|
+
stopGate: {
|
|
591
|
+
maxBlocks: number;
|
|
592
|
+
};
|
|
593
|
+
refuseGate: {
|
|
594
|
+
maxRefusals: number;
|
|
595
|
+
};
|
|
596
|
+
llm: {
|
|
597
|
+
provider: "claude-code" | "opencode" | "anthropic" | "openai-compatible";
|
|
598
|
+
baseUrl: string | null;
|
|
599
|
+
apiKeyEnv: string;
|
|
600
|
+
maxFilesPerVerify: number;
|
|
601
|
+
};
|
|
602
|
+
}, {
|
|
603
|
+
repos: {
|
|
604
|
+
rules: unknown[];
|
|
605
|
+
repo: string;
|
|
606
|
+
rev?: string | undefined;
|
|
607
|
+
}[];
|
|
608
|
+
llm?: {
|
|
609
|
+
provider?: "claude-code" | "opencode" | "anthropic" | "openai-compatible" | undefined;
|
|
610
|
+
base_url?: string | null | undefined;
|
|
611
|
+
api_key_env?: string | undefined;
|
|
612
|
+
max_files_per_verify?: number | undefined;
|
|
613
|
+
} | undefined;
|
|
614
|
+
files?: string | undefined;
|
|
615
|
+
exclude?: string | undefined;
|
|
616
|
+
minimum_rulecast_version?: string | undefined;
|
|
617
|
+
context?: {
|
|
618
|
+
mode?: "inject" | "read" | undefined;
|
|
619
|
+
max_bytes?: number | undefined;
|
|
620
|
+
} | undefined;
|
|
621
|
+
default_stages?: ["touch" | "edit" | "verify", ...("touch" | "edit" | "verify")[]] | undefined;
|
|
622
|
+
max_matches_per_rule?: number | undefined;
|
|
623
|
+
max_file_bytes?: number | undefined;
|
|
624
|
+
timeouts?: {
|
|
625
|
+
edit_deadline_ms?: number | undefined;
|
|
626
|
+
verify_ms?: number | undefined;
|
|
627
|
+
} | undefined;
|
|
628
|
+
stop_gate?: {
|
|
629
|
+
max_blocks?: number | undefined;
|
|
630
|
+
} | undefined;
|
|
631
|
+
refuse_gate?: {
|
|
632
|
+
max_refusals?: number | undefined;
|
|
633
|
+
} | undefined;
|
|
634
|
+
}>;
|
|
635
|
+
type Config = z.output<typeof configSchema>;
|
|
636
|
+
|
|
637
|
+
type AnyDetector = Detector<any>;
|
|
638
|
+
interface DetectorRegistry {
|
|
639
|
+
get(kind: string): AnyDetector | undefined;
|
|
640
|
+
kinds(): string[];
|
|
641
|
+
}
|
|
642
|
+
declare function createRegistry(detectors: AnyDetector[]): DetectorRegistry;
|
|
643
|
+
|
|
644
|
+
interface ReferenceSpec {
|
|
645
|
+
/** Identity and display. Project: "docs/api.md#errors". Rule repo: "syv-ai/rulecast@v0.2.0:docs/api.md#errors". */
|
|
646
|
+
ref: string;
|
|
647
|
+
/** Project references: repo-relative with forward slashes. Rule repo references: absolute path in the cache. */
|
|
648
|
+
path: string;
|
|
649
|
+
anchor: string | null;
|
|
650
|
+
mode: ReferenceMode;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
interface CompiledDetector {
|
|
654
|
+
kind: string;
|
|
655
|
+
config: unknown;
|
|
656
|
+
captures: string[];
|
|
657
|
+
}
|
|
658
|
+
interface CompiledRule {
|
|
659
|
+
/** alias when set, else id: the name findings, dedupe and session state use. */
|
|
660
|
+
id: string;
|
|
661
|
+
name: string;
|
|
662
|
+
description: string | null;
|
|
663
|
+
/** "local", or the rule repo label ("syv-ai/rulecast@v0.2.0"). */
|
|
664
|
+
source: string;
|
|
665
|
+
severity: Severity;
|
|
666
|
+
/**
|
|
667
|
+
* How a finding of this rule is classified against the baseline (spec §8). `instance` — the
|
|
668
|
+
* default, and every rule before 0.2 — is new when the match touches a changed line. `container`
|
|
669
|
+
* is new when the node the match spans was not already violating the rule at the baseline.
|
|
670
|
+
*/
|
|
671
|
+
scope: RuleScope;
|
|
672
|
+
/** Refuse the agent's write when this rule fires on what it is writing (spec §9, Guard). */
|
|
673
|
+
refuseWrite: boolean;
|
|
674
|
+
stages: Stage[];
|
|
675
|
+
matches(file: string): boolean;
|
|
676
|
+
detector: CompiledDetector | null;
|
|
677
|
+
message: string | null;
|
|
678
|
+
context: ReferenceSpec[];
|
|
679
|
+
/**
|
|
680
|
+
* Inline good/bad examples, run by `rulecast test`. `null` when the rule declared none, which
|
|
681
|
+
* `validate` and `test` report differently from a rule that declared an empty set.
|
|
682
|
+
*/
|
|
683
|
+
examples: RuleExamples | null;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* A rule that runs a detector. compileRule enforces the split — a rule with `detect` must also
|
|
687
|
+
* have a `message` and an edit or verify stage; one without must have neither and must have
|
|
688
|
+
* `context` — so selection can narrow to this and consumers stop asserting what it already knows.
|
|
689
|
+
*/
|
|
690
|
+
type DetectorRule = CompiledRule & {
|
|
691
|
+
detector: CompiledDetector;
|
|
692
|
+
message: string;
|
|
693
|
+
};
|
|
694
|
+
/** A rule with no detector: it delivers its `context` when a matching file is touched. */
|
|
695
|
+
type TouchRule = CompiledRule & {
|
|
696
|
+
detector: null;
|
|
697
|
+
};
|
|
698
|
+
/** Whether this rule runs a detector, as a type guard so the narrowing survives a filter. */
|
|
699
|
+
declare function isDetectorRule(rule: CompiledRule): rule is DetectorRule;
|
|
700
|
+
|
|
701
|
+
/** What the model is asked to answer with (spec §6). */
|
|
702
|
+
declare const responseSchema: z.ZodObject<{
|
|
703
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
704
|
+
rule: z.ZodString;
|
|
705
|
+
line: z.ZodNumber;
|
|
706
|
+
/** The model's quote of the line. Asked for because quoting sharpens the line number; never rendered. */
|
|
707
|
+
text: z.ZodOptional<z.ZodString>;
|
|
708
|
+
reason: z.ZodString;
|
|
709
|
+
}, "strip", z.ZodTypeAny, {
|
|
710
|
+
rule: string;
|
|
711
|
+
line: number;
|
|
712
|
+
reason: string;
|
|
713
|
+
text?: string | undefined;
|
|
714
|
+
}, {
|
|
715
|
+
rule: string;
|
|
716
|
+
line: number;
|
|
717
|
+
reason: string;
|
|
718
|
+
text?: string | undefined;
|
|
719
|
+
}>, "many">;
|
|
720
|
+
}, "strip", z.ZodTypeAny, {
|
|
721
|
+
findings: {
|
|
722
|
+
rule: string;
|
|
723
|
+
line: number;
|
|
724
|
+
reason: string;
|
|
725
|
+
text?: string | undefined;
|
|
726
|
+
}[];
|
|
727
|
+
}, {
|
|
728
|
+
findings: {
|
|
729
|
+
rule: string;
|
|
730
|
+
line: number;
|
|
731
|
+
reason: string;
|
|
732
|
+
text?: string | undefined;
|
|
733
|
+
}[];
|
|
734
|
+
}>;
|
|
735
|
+
type LlmFinding = z.infer<typeof responseSchema>["findings"][number];
|
|
736
|
+
interface LlmRequest {
|
|
737
|
+
/** The provider's own model name, already resolved from the rule's alias. */
|
|
738
|
+
model: string;
|
|
739
|
+
/** The whole prompt: rules, grounding and the marked-up file. */
|
|
740
|
+
prompt: string;
|
|
741
|
+
settings: LlmSettings;
|
|
742
|
+
env: NodeJS.ProcessEnv;
|
|
743
|
+
cwd: string;
|
|
744
|
+
signal: AbortSignal;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* The backend itself is unusable — no binary, no credentials. Spec §14 makes this a failure of the
|
|
748
|
+
* whole run (every llm rule disabled, one warning), not of one rule. Every other throw is §14's
|
|
749
|
+
* "malformed LLM output": an error for the rules in that call only.
|
|
750
|
+
*/
|
|
751
|
+
declare class LlmUnavailableError extends Error {
|
|
752
|
+
}
|
|
753
|
+
/** What `rulecast doctor` learns about a backend without calling it (spec §5). */
|
|
754
|
+
interface LlmAvailability {
|
|
755
|
+
ok: boolean;
|
|
756
|
+
/** The resolved binary or the endpoint when ok; why not when it is not. */
|
|
757
|
+
detail: string;
|
|
758
|
+
}
|
|
759
|
+
interface LlmProvider {
|
|
760
|
+
name: string;
|
|
761
|
+
ask(request: LlmRequest): Promise<LlmFinding[]>;
|
|
762
|
+
/**
|
|
763
|
+
* Can this backend be reached at all — the binary exists, or the key is set. Environmental, so
|
|
764
|
+
* it never calls the model: a false answer here is the same situation ask() would report as
|
|
765
|
+
* LlmUnavailableError, found before anything is spent.
|
|
766
|
+
*/
|
|
767
|
+
available(input: {
|
|
768
|
+
settings: LlmSettings;
|
|
769
|
+
env: NodeJS.ProcessEnv;
|
|
770
|
+
cwd: string;
|
|
771
|
+
/** Give up when doctor's deadline passes: a PATH lookup can block on a stalled mount. */
|
|
772
|
+
signal: AbortSignal;
|
|
773
|
+
}): Promise<LlmAvailability>;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
export { type Adapter as A, type RuleScope as B, type Cache as C, type Delivery as D, type Event as E, type Finding as F, createRegistry as G, defaultDetectorSettings as H, type InstallScope as I, emptyDelivery as J, isDetectorRule as K, LLM_PROVIDERS as L, type Match as M, type Config as N, type Omitted as O, type RuleEntry as P, type Stage as Q, type ReferenceMode as R, type Severity as S, type TouchRule as T, type WriteIntent as W, type DetectorRuleInput as a, type DetectorRun as b, type Detector as c, type AnyDetector as d, type AdapterInput as e, type AdapterInstall as f, type ChangeSet as g, type CheckResult as h, type CompiledDetector as i, type CompiledRule as j, type DeliveredReference as k, type DetectorCheck as l, type DetectorEvent as m, type DetectorRegistry as n, type DetectorResult as o, type DetectorRule as p, type DetectorSettings as q, type DetectorWarm as r, type EventKind as s, type LlmFinding as t, type LlmProvider as u, type LlmProviderName as v, type LlmRequest as w, type LlmSettings as x, LlmUnavailableError as y, type ResolvedReference as z };
|