@syv-ai/rulecast 0.3.0 → 0.5.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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as Adapter, D as Delivery, a as DetectorRuleInput, b as DetectorRun, M as Match, c as Detector, d as AnyDetector, e as AdapterInput, E as Event } from './types-C8b7hrnG.js';
2
- export { f as AdapterInstall, C as Cache, g as ChangeSet, h as CheckResult, i as CompiledDetector, j as CompiledRule, k as DeliveredReference, l as DetectorCheck, m as DetectorEvent, n as DetectorRegistry, o as DetectorResult, p as DetectorRule, q as DetectorSettings, r as DetectorWarm, s as EventKind, F as Finding, I as InstallScope, L as LLM_PROVIDERS, t as LlmFinding, u as LlmProvider, v as LlmProviderName, w as LlmRequest, x as LlmSettings, y as LlmUnavailableError, O as Omitted, R as ReferenceMode, z as ResolvedReference, B as RuleScope, S as Severity, T as TouchRule, W as WriteIntent, G as createRegistry, H as defaultDetectorSettings, J as emptyDelivery, K as isDetectorRule } from './types-C8b7hrnG.js';
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-D5y_IokF.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-D5y_IokF.js';
3
3
  import 'zod';
4
4
 
5
5
  declare const claudeCodeAdapter: Adapter;
@@ -38,17 +38,88 @@ interface BacklogSummary {
38
38
  declare function summarise(delivery: Delivery): BacklogSummary;
39
39
  declare function renderBacklog(summary: BacklogSummary, options: {
40
40
  topFiles: number;
41
+ note?: boolean;
41
42
  }): string;
42
43
 
43
44
  /** Every agent adapter rulecast ships. */
44
45
  declare const ADAPTERS: readonly Adapter[];
45
46
  declare function adapterByName(name: string): Adapter | undefined;
46
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;
47
80
  /** Turns a per-rule function into a detector run; an error in one rule does not affect the others. */
48
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>;
49
120
 
50
121
  /** The running rulecast version. test/core/version.test.ts keeps it equal to package.json. */
51
- declare const VERSION = "0.3.0";
122
+ declare const VERSION = "0.5.0";
52
123
 
53
124
  declare const builtinDetectors: readonly AnyDetector[];
54
125
 
@@ -107,4 +178,4 @@ interface DetectorFixture {
107
178
  */
108
179
  declare function detectorContract(detector: AnyDetector, fixture: DetectorFixture): ContractCase[];
109
180
 
110
- 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, perRule, renderBacklog, summarise };
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
@@ -5,18 +5,27 @@ import {
5
5
  claudeCodeAdapter,
6
6
  createRegistry,
7
7
  renderBacklog,
8
+ repoRelative,
9
+ repoRelativeTo,
10
+ runTool,
8
11
  summarise
9
- } from "./chunk-KOUHBT7F.js";
12
+ } from "./chunk-MB4F7ZXC.js";
10
13
  import {
11
14
  LlmUnavailableError,
12
15
  VERSION,
13
16
  defaultDetectorSettings,
14
17
  emptyDelivery,
15
18
  isDetectorRule,
19
+ lineStarts,
16
20
  memoryCache,
21
+ offsetAt,
22
+ pastDeadline,
17
23
  perRule,
18
- readSourceFile
19
- } from "./chunk-HZUATJ53.js";
24
+ positionAt,
25
+ readSourceFile,
26
+ renderAgentText,
27
+ sourceReader
28
+ } from "./chunk-4XS34SYW.js";
20
29
 
21
30
  // src/testing/adapter-contract.ts
22
31
  import assert from "assert/strict";
@@ -70,7 +79,7 @@ function adapterContract(adapter, fixture) {
70
79
  if (!event) continue;
71
80
  assert.ok(event.cwd.length > 0, `${name}: event.cwd must not be empty`);
72
81
  for (const file of event.files) assert.equal(typeof file, "string", `${name}: files must be strings`);
73
- if (event.kind === "prompt" || event.kind === "reset") {
82
+ if (["prompt", "reset", "start", "shell-before", "shell-after"].includes(event.kind)) {
74
83
  assert.deepEqual(event.files, [], `${name}: ${event.kind} carries no files`);
75
84
  }
76
85
  }
@@ -120,6 +129,16 @@ function adapterContract(adapter, fixture) {
120
129
  const twice = install.merge(structuredClone(once.settings), command, 5e3);
121
130
  assert.deepEqual(twice.settings, once.settings, "merging twice must change nothing");
122
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
+ }
123
142
  const removed = install.remove(structuredClone(once.settings));
124
143
  assert.deepEqual(removed.settings, original, "remove must return the settings merge was given");
125
144
  assert.deepEqual([...removed.removed].sort(), [...once.added].sort(), "remove must name what merge added");
@@ -299,7 +318,16 @@ export {
299
318
  detectorContract,
300
319
  emptyDelivery,
301
320
  isDetectorRule,
321
+ lineStarts,
322
+ offsetAt,
323
+ pastDeadline,
302
324
  perRule,
325
+ positionAt,
326
+ renderAgentText,
303
327
  renderBacklog,
328
+ repoRelative,
329
+ repoRelativeTo,
330
+ runTool,
331
+ sourceReader,
304
332
  summarise
305
333
  };
@@ -1,5 +1,5 @@
1
- import { n as DetectorRegistry, N as Config, j as CompiledRule, D as Delivery, h as CheckResult, q as DetectorSettings, E as Event, v as LlmProviderName, u as LlmProvider } from './types-C8b7hrnG.js';
2
- export { P as RuleEntry, Q as Stage } from './types-C8b7hrnG.js';
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-D5y_IokF.js';
2
+ export { R as RuleEntry, S as Stage, r as renderAgentText } from './types-D5y_IokF.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 (run --no-llm). */
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, renderAgentText, resolveModel, runPipeline };
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-7KUPYT35.js";
14
+ } from "./chunk-Y3FXJRZB.js";
15
15
  import {
16
16
  MODEL_ALIASES,
17
17
  providerByName,
18
18
  renderAgentText,
19
19
  resolveModel
20
- } from "./chunk-HZUATJ53.js";
20
+ } from "./chunk-4XS34SYW.js";
21
21
  export {
22
22
  CONFIG_FILE,
23
23
  MANIFEST_FILE,
@@ -1,6 +1,10 @@
1
1
  import { ZodType, ZodTypeDef, z } from 'zod';
2
2
 
3
- type EventKind = "touch" | "edit" | "verify" | "prompt" | "reset" | "guard";
3
+ /**
4
+ * `start`: the session began or resumed. `shell-before` / `shell-after`: around one call of the
5
+ * agent's shell tool, whose writes rulecast finds by comparing the working tree (session/tree.ts).
6
+ */
7
+ type EventKind = "touch" | "edit" | "verify" | "prompt" | "reset" | "guard" | "start" | "shell-before" | "shell-after";
4
8
  type DetectorEvent = "edit" | "verify";
5
9
  type Severity = "error" | "warning";
6
10
  type ReferenceMode = "inject" | "read";
@@ -19,16 +23,35 @@ interface WriteIntent {
19
23
  all: boolean;
20
24
  };
21
25
  }
26
+ /**
27
+ * Where a run reads the current content of a file from. The working tree for every hook; the index
28
+ * for `rulecast run` on staged files, so a commit is judged as it will be committed; a commit for
29
+ * `--to-ref`, so a push is judged as it will be pushed.
30
+ */
31
+ type ContentSource = {
32
+ kind: "worktree";
33
+ } | {
34
+ kind: "index";
35
+ } | {
36
+ kind: "commit";
37
+ ref: string;
38
+ };
22
39
  interface Event {
23
40
  kind: EventKind;
24
- /** Repo-relative paths (adapters may give absolute ones; the hook command converts them). Empty for prompt and reset. */
41
+ /** Repo-relative paths (adapters may give absolute ones; the hook command converts them). Empty for prompt, reset, start and shell events. */
25
42
  files: string[];
43
+ /** shell events: pairs a call's before and after states when the agent's payloads carry an id. */
44
+ toolUseId?: string;
45
+ /** shell-after: the command exited non-zero. It may still have written files; an adapter may answer it differently. */
46
+ failed?: boolean;
26
47
  /** guard only: what the agent is about to write to `files[0]`. */
27
48
  intent?: WriteIntent;
28
49
  /** touch from a read: the whole file was read. */
29
50
  completeRead?: boolean;
30
51
  /** verify from the CLI: the commit the baseline is read from (the merge base for --from-ref). */
31
52
  baseCommit?: string;
53
+ /** verify from the CLI: where current content is read from. Absent: the working tree. */
54
+ content?: ContentSource;
32
55
  session?: {
33
56
  id: string;
34
57
  agentId?: string;
@@ -88,6 +111,12 @@ interface DetectorRun<Config> {
88
111
  * is not on disk and never will be if the write is refused.
89
112
  */
90
113
  read(file: string): Promise<string | null>;
114
+ /**
115
+ * False when the files are not as they are on disk — a staged run reads the index, `--to-ref` a
116
+ * commit — and this detector declared `takesContent`: it must read through `read` and hand the
117
+ * text to its tool under the file's path, never let the tool open the path. Absent: true.
118
+ */
119
+ fromDisk?: boolean;
91
120
  /** File absent = no baseline, the whole file is new. */
92
121
  changes: ReadonlyMap<string, ChangeSet>;
93
122
  cache: Cache;
@@ -158,6 +187,40 @@ interface Detector<Config> {
158
187
  * not on disk, so ruff, eslint, ast-grep's CLI or a `command` script would judge the old one.
159
188
  */
160
189
  guards?: boolean;
190
+ /**
191
+ * Each file costs money or a third party sees it (§6, Consent). The core never preselects such a
192
+ * rule in `init`, never runs it as a side effect — no fingerprint run on `touch`, no `doctor` dry
193
+ * run, no `rulecast test` without a rule id — and budgets the files it is given per event.
194
+ *
195
+ * The core asks this rather than a kind name, the way it asks `guards`, so a second metered
196
+ * detector needs no edit to the core.
197
+ */
198
+ metered?: boolean;
199
+ /** For `init`'s consent line: what one rule of this kind costs and where the file goes. */
200
+ cost?(config: Config): string;
201
+ /**
202
+ * Most files this detector is given in one verify; the most recently edited are kept (§6).
203
+ * `setting` names the config key that raises it, because a warning that does not name its lever
204
+ * leaves nobody knowing what to do.
205
+ */
206
+ fileBudget?(settings: DetectorSettings): {
207
+ max: number;
208
+ setting: string;
209
+ };
210
+ /** Added to a verify timeout's warning, when this kind can legitimately need far longer. */
211
+ timeoutHint?: string;
212
+ /**
213
+ * A match is the whole file rather than a range in it, so any write to a matching file is
214
+ * evidence for `refuse_write`, not only a write that inserts the matched text.
215
+ */
216
+ wholeFile?: boolean;
217
+ /**
218
+ * This rule's tool can be handed a file's content under its real path (ruff `--stdin-filename`,
219
+ * a model prompt). When the content is not the working tree, such a rule is run with
220
+ * `fromDisk: false`; one that cannot is given a scratch copy instead, which configuration keyed
221
+ * on the file's path no longer matches (spec §12).
222
+ */
223
+ takesContent?(config: Config): boolean;
161
224
  run(input: DetectorRun<Config>): Promise<DetectorResult>;
162
225
  /** Optional: build expensive caches ahead of events (rulecast warm, §13). */
163
226
  warm?(input: DetectorWarm<Config>): Promise<void>;
@@ -191,6 +254,7 @@ interface Omitted {
191
254
  rule: string;
192
255
  count: number;
193
256
  files: number;
257
+ swept?: true;
194
258
  }[];
195
259
  /** Rules dropped whole, with their findings: only when the floor itself did not fit. */
196
260
  rules: number;
@@ -212,6 +276,28 @@ interface Delivery {
212
276
  omitted: Omitted;
213
277
  /** Absolute path of the untrimmed delivery, written when even the floor did not fit; null when it did. */
214
278
  overflowPath: string | null;
279
+ /** The findings are in files the agent's shell command changed: the title says so. Absent: an edit tool. */
280
+ via?: "shell";
281
+ /**
282
+ * Files with findings that changed outside the agent's tool calls (spec §9, Shell edits): printed
283
+ * under their own heading, after the agent's own, and never blocking. Set at a stop.
284
+ */
285
+ swept?: string[];
286
+ /**
287
+ * Findings of `refuse_write` rules in files the agent changed with its shell (spec §9, Shell
288
+ * edits): the write could not be refused before it happened, so the agent is told to revert it.
289
+ * `dirtyAtStart`: the file had the user's uncommitted changes, so reverting the whole file is wrong.
290
+ */
291
+ refused?: {
292
+ file: string;
293
+ rule: string;
294
+ dirtyAtStart: boolean;
295
+ }[];
296
+ /**
297
+ * For the user, not the agent (spec §9, Oversight): the config changed this session, ignores were
298
+ * added. Attached after the budget is spent and never part of agent context under a hook adapter.
299
+ */
300
+ notices?: string[];
215
301
  }
216
302
  interface AdapterInput {
217
303
  /** The agent's directory; the project root is found from it. */
@@ -232,9 +318,14 @@ interface AdapterInstall {
232
318
  }[];
233
319
  /** The hook command; local: rulecast is installed in the project's node_modules. */
234
320
  command(local: boolean): string;
321
+ /**
322
+ * Adds the hooks. `updated` (optional, for adapters that can tell): the adapter's own hooks whose
323
+ * command differed from `command` and were rewritten — an older install upgraded in place.
324
+ */
235
325
  merge(settings: unknown, command: string, verifyMs: number): {
236
326
  settings: unknown;
237
327
  added: string[];
328
+ updated?: string[];
238
329
  };
239
330
  remove(settings: unknown): {
240
331
  settings: unknown;
@@ -308,6 +399,8 @@ declare const overrideSchema: z.ZodObject<{
308
399
  */
309
400
  scope: z.ZodOptional<z.ZodEnum<["instance", "container"]>>;
310
401
  refuse_write: z.ZodOptional<z.ZodBoolean>;
402
+ /** false: compiled and listed, selected for nothing. The way to switch a catalog rule off without deleting it. */
403
+ enabled: z.ZodOptional<z.ZodBoolean>;
311
404
  detect: z.ZodOptional<z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodUnknown>, Record<string, unknown>, Record<string, unknown>>>;
312
405
  message: z.ZodOptional<z.ZodString>;
313
406
  context: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodObject<{
@@ -364,8 +457,8 @@ declare const overrideSchema: z.ZodObject<{
364
457
  id: z.ZodString;
365
458
  }, "strict", z.ZodTypeAny, {
366
459
  id: string;
367
- name?: string | undefined;
368
460
  message?: string | undefined;
461
+ name?: string | undefined;
369
462
  alias?: string | undefined;
370
463
  description?: string | undefined;
371
464
  files?: string | undefined;
@@ -378,6 +471,7 @@ declare const overrideSchema: z.ZodObject<{
378
471
  severity?: "error" | "warning" | undefined;
379
472
  scope?: "instance" | "container" | undefined;
380
473
  refuse_write?: boolean | undefined;
474
+ enabled?: boolean | undefined;
381
475
  detect?: Record<string, unknown> | undefined;
382
476
  context?: (string | {
383
477
  path: string;
@@ -395,8 +489,8 @@ declare const overrideSchema: z.ZodObject<{
395
489
  } | undefined;
396
490
  }, {
397
491
  id: string;
398
- name?: string | undefined;
399
492
  message?: string | undefined;
493
+ name?: string | undefined;
400
494
  alias?: string | undefined;
401
495
  description?: string | undefined;
402
496
  files?: string | undefined;
@@ -409,6 +503,7 @@ declare const overrideSchema: z.ZodObject<{
409
503
  severity?: "error" | "warning" | undefined;
410
504
  scope?: "instance" | "container" | undefined;
411
505
  refuse_write?: boolean | undefined;
506
+ enabled?: boolean | undefined;
412
507
  detect?: Record<string, unknown> | undefined;
413
508
  context?: (string | {
414
509
  path: string;
@@ -671,7 +766,14 @@ interface CompiledRule {
671
766
  scope: RuleScope;
672
767
  /** Refuse the agent's write when this rule fires on what it is writing (spec §9, Guard). */
673
768
  refuseWrite: boolean;
769
+ /** false: switched off with `enabled: false`; never selected for an event (spec §4). */
770
+ enabled: boolean;
674
771
  stages: Stage[];
772
+ /** The rule's own `files` and `exclude`, as written, for `rulecast list`; `matches` is what decides. */
773
+ patterns: {
774
+ files: string;
775
+ exclude: string;
776
+ };
675
777
  matches(file: string): boolean;
676
778
  detector: CompiledDetector | null;
677
779
  message: string | null;
@@ -698,6 +800,23 @@ type TouchRule = CompiledRule & {
698
800
  /** Whether this rule runs a detector, as a type guard so the narrowing survives a filter. */
699
801
  declare function isDetectorRule(rule: CompiledRule): rule is DetectorRule;
700
802
 
803
+ interface RenderOptions {
804
+ maxMatchesPerRule: number;
805
+ /**
806
+ * Print `delivery.notices` (default true). A hook adapter passes false and shows them to the user
807
+ * itself; `--format agent` serves agents without hooks, where nobody else would see them. Notices
808
+ * are attached after the budget is spent, so they are never priced: no budgeted delivery has any.
809
+ */
810
+ notices?: boolean;
811
+ /**
812
+ * Print `delivery.warnings` (default true). Rule and detector problems are the human's to fix; a
813
+ * hook adapter that can reach the user passes false and shows them there. Not printing them only
814
+ * ever spends less than the budget reserved for them.
815
+ */
816
+ warnings?: boolean;
817
+ }
818
+ declare function renderAgentText(delivery: Delivery, options: RenderOptions): string;
819
+
701
820
  /** What the model is asked to answer with (spec §6). */
702
821
  declare const responseSchema: z.ZodObject<{
703
822
  findings: z.ZodArray<z.ZodObject<{
@@ -773,4 +892,4 @@ interface LlmProvider {
773
892
  }): Promise<LlmAvailability>;
774
893
  }
775
894
 
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 };
895
+ 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.0",
3
+ "version": "0.5.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"