@pho9ubenaa/siro 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/dist/index.d.mts CHANGED
@@ -1,18 +1,22 @@
1
1
  import * as vb from "valibot";
2
2
 
3
+ //#region src/domain/entities/config-value.d.ts
4
+ declare const CODEC_KINDS: readonly ["json", "npmrc", "toml", "yaml"];
5
+ type CodecKind = (typeof CODEC_KINDS)[number];
6
+ /** Scalar value that can be written back to a config file. */
7
+ type ConfigValue = string | number | boolean;
8
+ /** Parsed keys are unvalidated; each rule narrows the values it reads. */
9
+ interface ParsedConfig {
10
+ readonly [key: string]: unknown;
11
+ }
12
+ /** A (possibly nested) key path, guaranteed to have at least one segment. */
13
+ type KeyPath = readonly [string, ...string[]];
14
+ /** Parser values may include nested arrays and TOML dates. */
15
+ type ConfigReadValue = unknown;
16
+ /** Read an own key path; absent or non-mapping parents yield undefined. */
17
+ declare const getByPath: (config: ParsedConfig, keyPath: KeyPath) => ConfigReadValue;
18
+ //#endregion
3
19
  //#region src/shared/paths.d.ts
4
- /**
5
- * Branded path types. Surface the distinction between
6
- * - `AbsPath`: absolute filesystem paths handed to the FS boundary
7
- * (`FileSystem.readText` etc.), and
8
- * - `RelPath`: paths relative to a repo root, accepted by
9
- * `RepoContext` and resolved via `resolveIn`.
10
- *
11
- * The brand is a structural tag with no runtime footprint — the cast in
12
- * `asAbsPath` / `asRelPath` is the only place either type is minted, so
13
- * passing a raw `string` to an FS or repo API now requires the caller to
14
- * declare intent rather than silently mixing the two flavours.
15
- */
16
20
  declare const AbsPathBrand: unique symbol;
17
21
  declare const RelPathBrand: unique symbol;
18
22
  type AbsPath = string & {
@@ -21,10 +25,8 @@ type AbsPath = string & {
21
25
  type RelPath = string & {
22
26
  readonly [RelPathBrand]: true;
23
27
  };
24
- /** Tag a string as absolute. The caller vouches for the shape. */
25
- declare const asAbsPath: (path: string) => AbsPath;
26
- /** Tag a string as repo-root-relative. The caller vouches for the shape. */
27
- declare const asRelPath: (path: string) => RelPath;
28
+ declare const asAbsPath: (value: string) => AbsPath;
29
+ declare const asRelPath: (value: string) => RelPath;
28
30
  //#endregion
29
31
  //#region src/domain/ports/file-system.d.ts
30
32
  /** IO boundary: swap in memfs (or any other backend) for tests. */
@@ -33,53 +35,6 @@ interface FileSystem {
33
35
  exists: (path: AbsPath) => boolean;
34
36
  }
35
37
  //#endregion
36
- //#region src/adapters/node-file-system.d.ts
37
- declare const nodeFileSystem: FileSystem;
38
- //#endregion
39
- //#region src/domain/ports/io.d.ts
40
- /** Output sink for commands; injectable so tests can drive a string buffer. */
41
- interface IO {
42
- stdout: (line: string) => void;
43
- stderr: (line: string) => void;
44
- }
45
- //#endregion
46
- //#region src/adapters/node-io.d.ts
47
- declare const nodeIO: IO;
48
- //#endregion
49
- //#region src/domain/entities/config-value.d.ts
50
- type CodecKind = 'npmrc' | 'yaml' | 'toml' | 'json';
51
- /** Scalar value that can be written back to a config file. */
52
- type ConfigValue = string | number | boolean;
53
- /** Scalar value as it appears when reading (codecs may produce `null`). */
54
- type ConfigScalar = ConfigValue | null;
55
- /** Recursive, structurally typed view of a parsed config file. */
56
- interface ParsedConfig {
57
- readonly [key: string]: ConfigScalar | readonly ConfigScalar[] | ParsedConfig;
58
- }
59
- /** A (possibly nested) key path, guaranteed to have at least one segment. */
60
- type KeyPath = readonly [string, ...string[]];
61
- /** A single key to set. */
62
- interface KeyAssignment {
63
- readonly keyPath: KeyPath;
64
- readonly value: ConfigValue;
65
- }
66
- /**
67
- * Value a rule's `check` saw at the target key, as returned by `getByPath`.
68
- * Runtime caveat: YAML/TOML parsers can yield host values (e.g. `Date`)
69
- * nested inside `ParsedConfig` even though the type does not name them —
70
- * compare timestamps by `valueOf()`, not by type narrowing on this union.
71
- */
72
- type ConfigReadValue = ConfigScalar | readonly ConfigScalar[] | ParsedConfig | undefined;
73
- /**
74
- * Look up a nested value by key path; `undefined` if any segment is missing.
75
- *
76
- * Part of the public surface (re-exported from `src/index.ts`): embedders
77
- * writing custom rules receive the same `config: ParsedConfig` shape that
78
- * built-in rules see, so they need the canonical traversal helper rather
79
- * than re-implementing `null`-vs-missing semantics per rule.
80
- */
81
- declare const getByPath: (config: ParsedConfig, keyPath: KeyPath) => ConfigScalar | readonly ConfigScalar[] | ParsedConfig | undefined;
82
- //#endregion
83
38
  //#region src/domain/entities/pms.d.ts
84
39
  /**
85
40
  * Single source of truth for package managers and severities.
@@ -93,134 +48,108 @@ type Severity = (typeof SEVERITIES)[number];
93
48
  declare const isPM: (value: string) => value is PM;
94
49
  declare const isSeverity: (value: string) => value is Severity;
95
50
  //#endregion
51
+ //#region src/domain/entities/project-type.d.ts
52
+ declare const PROJECT_TYPES: readonly ["application", "package"];
53
+ type ProjectType = (typeof PROJECT_TYPES)[number];
54
+ //#endregion
96
55
  //#region src/domain/schemas/package-json.d.ts
97
56
  declare const PackageJsonSchema: vb.LooseObjectSchema<{
98
- readonly files: vb.SchemaWithFallback<vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>, undefined>;
99
- readonly name: vb.SchemaWithFallback<vb.OptionalSchema<vb.StringSchema<undefined>, undefined>, undefined>;
100
- readonly packageManager: vb.SchemaWithFallback<vb.OptionalSchema<vb.StringSchema<undefined>, undefined>, undefined>;
101
- readonly private: vb.OptionalSchema<vb.SchemaWithFallback<vb.BooleanSchema<undefined>, true>, undefined>;
102
- readonly publishConfig: vb.SchemaWithFallback<vb.OptionalSchema<vb.LooseObjectSchema<{
103
- readonly access: vb.SchemaWithFallback<vb.OptionalSchema<vb.UnionSchema<[vb.LiteralSchema<"public", undefined>, vb.LiteralSchema<"restricted", undefined>], undefined>, undefined>, undefined>;
104
- }, undefined>, undefined>, undefined>;
105
- readonly trustedDependencies: vb.SchemaWithFallback<vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>, undefined>;
57
+ readonly files: vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>;
58
+ readonly name: vb.OptionalSchema<vb.StringSchema<undefined>, undefined>;
59
+ readonly packageManager: vb.OptionalSchema<vb.StringSchema<undefined>, undefined>;
60
+ readonly private: vb.OptionalSchema<vb.BooleanSchema<undefined>, undefined>;
61
+ readonly publishConfig: vb.OptionalSchema<vb.LooseObjectSchema<{
62
+ readonly access: vb.OptionalSchema<vb.PicklistSchema<["public", "restricted", "private"], undefined>, undefined>;
63
+ }, undefined>, undefined>;
64
+ readonly trustedDependencies: vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>;
106
65
  }, undefined>;
107
66
  type PackageJson = vb.InferOutput<typeof PackageJsonSchema>;
108
67
  //#endregion
109
68
  //#region src/domain/ports/repo-context.d.ts
110
- /** Read-only view of a repository, passed to every rule's `check` and `fix`. */
69
+ /** Read-only view of a repository, passed to every rule's `check`. */
111
70
  interface RepoContext {
112
71
  readonly root: AbsPath;
113
72
  exists: (relPath: RelPath) => boolean;
114
73
  readText: (relPath: RelPath) => string | undefined;
115
74
  readonly packageJson: PackageJson | undefined;
75
+ readonly projectType?: ProjectType;
76
+ }
77
+ /** Settings read during one evaluation share its parser and cache. */
78
+ interface RuleContext extends RepoContext {
79
+ readConfig: (file: ConfigFileRef) => ParsedConfig;
116
80
  }
117
81
  //#endregion
118
82
  //#region src/domain/entities/rule.d.ts
119
- /**
120
- * What a rule binding points at on disk: a parsed config file or an existence
121
- * check (e.g. "a lockfile is committed"). `path` is a branded `RelPath` minted
122
- * once at the ref's definition (CONFIG_FILES), so the readText boundary is
123
- * type-checked and a stray absolute path can't slip in as a string.
124
- */
125
- type ConfigFileRef = {
83
+ interface ConfigFileRef {
126
84
  readonly kind: CodecKind;
127
85
  readonly path: RelPath;
86
+ }
87
+ interface SetKeyOperation {
88
+ readonly op: 'setKey';
89
+ readonly file: ConfigFileRef;
90
+ readonly keyPath: KeyPath;
91
+ readonly value: ConfigValue;
92
+ }
93
+ /** A check chooses one remedy for the state it observed. siro does not apply it. */
94
+ type Remediation = {
95
+ readonly kind: 'automatic';
96
+ readonly operations: readonly [SetKeyOperation, ...SetKeyOperation[]];
97
+ readonly steps?: never;
128
98
  } | {
129
- readonly kind: 'fileGlob';
130
- readonly path: RelPath;
99
+ readonly kind: 'manual';
100
+ readonly steps: readonly [string, ...string[]];
101
+ readonly operations?: never;
131
102
  };
132
- /** Result of evaluating a single rule binding against a repo. */
133
103
  type CheckStatus = {
134
104
  readonly state: 'ok';
105
+ } | {
106
+ readonly state: 'na';
135
107
  } | {
136
108
  readonly state: 'violation';
137
- readonly message: string;
109
+ readonly message: string; /** Override the primary file when the violation concerns another input. */
110
+ readonly file?: RelPath;
138
111
  readonly expected?: ConfigValue;
139
- readonly actual?: ConfigReadValue;
140
- /**
141
- * Dynamic severity for this specific violation, overriding both
142
- * {@link AutoRuleBinding.severity} and {@link Rule.severity}. Lets a
143
- * `check` distinguish "unset but PM-default safe" (advisory) from
144
- * "explicitly weakened" (full severity). A user `rules` override in
145
- * config still wins (see `applyConfig`).
146
- */
112
+ readonly actual?: ConfigReadValue; /** User configuration takes precedence over this per-result severity. */
147
113
  readonly severity?: Severity;
148
- /**
149
- * Manual remediation steps surfaced verbatim on the finding. Lets a
150
- * `check` signal "the setKey fix ops cannot resolve this state; the
151
- * user has to intervene" — e.g. when an explicit user override would
152
- * have to be deleted first. When non-empty, `Finding.fix` is emitted
153
- * empty so an external fixer never writes ops the user's existing
154
- * config would defeat.
155
- */
156
- readonly manualSteps?: readonly string[];
157
- } | {
158
- readonly state: 'na';
159
- };
160
- /** A remediation step produced by a rule's `fix`. */
161
- type FixOp = {
162
- readonly op: 'setKey';
163
- readonly file: ConfigFileRef;
164
- readonly keyPath: KeyPath;
165
- readonly value: ConfigValue;
166
- } | {
167
- readonly op: 'ensureFileTracked';
168
- readonly file: ConfigFileRef;
169
- readonly message: string;
170
- } | {
171
- readonly op: 'note';
172
- readonly message: string;
173
- readonly file?: ConfigFileRef;
114
+ readonly remediation?: Remediation;
174
115
  };
175
- /**
176
- * - `auto`: `fix` produces setKey ops an external fixer can apply
177
- * mechanically (surfaced via `Finding.fix`; see docs/json-output.md).
178
- * - `advisory`: `fix` only produces notes/ensureFileTracked hints.
179
- */
180
- type FixKind = 'auto' | 'advisory';
181
- type SetKeyOp = Extract<FixOp, {
182
- op: 'setKey';
183
- }>;
184
- type AdvisoryOp = Extract<FixOp, {
185
- op: 'note' | 'ensureFileTracked';
186
- }>;
187
- interface AutoRuleBinding {
188
- readonly file: ConfigFileRef;
189
- readonly fixKind: 'auto';
190
- /** Official package-manager doc URL for the setting this binding writes. */
191
- readonly docs?: string;
192
- /**
193
- * Per-binding severity. When set, it shadows {@link Rule.severity} for this
194
- * PM only — used when a package manager's safe default already mitigates
195
- * the threat (so the binding is informational) while another PM with the
196
- * same rule still warrants the rule-wide level. A user config `rules`
197
- * override always wins over both.
198
- */
199
- readonly severity?: Severity;
200
- check: (ctx: RepoContext, config: ParsedConfig) => CheckStatus;
201
- fix: (ctx: RepoContext) => readonly SetKeyOp[];
116
+ /** Display-only package-manager version metadata. */
117
+ interface VersionNote {
118
+ readonly configAvailableSince?: string;
119
+ readonly defaultSafeSince?: string;
120
+ readonly note?: string;
202
121
  }
203
- interface AdvisoryRuleBinding {
204
- readonly file: ConfigFileRef;
205
- readonly fixKind: 'advisory';
206
- /** Official package-manager doc URL for the setting this binding describes. */
122
+ interface RuleBinding {
123
+ readonly file?: ConfigFileRef;
207
124
  readonly docs?: string;
208
- /** See {@link AutoRuleBinding.severity}. */
209
125
  readonly severity?: Severity;
210
- check: (ctx: RepoContext, config: ParsedConfig) => CheckStatus;
211
- fix: (ctx: RepoContext) => readonly AdvisoryOp[];
126
+ readonly versionNote?: VersionNote;
127
+ check: (ctx: RuleContext, config: ParsedConfig) => CheckStatus;
212
128
  }
213
- type RuleBinding = AutoRuleBinding | AdvisoryRuleBinding;
214
129
  /** A package-manager-agnostic security intent, realized per PM via `bindings`. */
215
- interface Rule {
216
- readonly id: string;
130
+ interface Rule<Id extends string = string> {
131
+ readonly id: Id;
217
132
  readonly title: string;
218
133
  readonly description: string;
219
134
  readonly severity: Severity;
220
135
  readonly docs?: string;
136
+ /** Project types this rule applies to; omission means both types. */
137
+ readonly projectTypes?: readonly ProjectType[];
221
138
  /** Absence of a PM key means the rule does not apply (N/A) to that PM. */
222
139
  readonly bindings: Partial<Record<PM, RuleBinding>>;
223
140
  }
141
+ declare const defineRule: <const Id extends string>(rule: Rule<Id>) => Rule<Id>;
142
+ //#endregion
143
+ //#region src/domain/builtin-rules.d.ts
144
+ declare const rules: readonly [Rule<"advisory-check">, Rule<"approved-git-repos">, Rule<"audit-suppression">, Rule<"block-auto-install">, Rule<"block-exotic-subdeps">, Rule<"bun-security-scanner">, Rule<"checksum-verification">, Rule<"commit-lockfile">, Rule<"dependency-overrides">, Rule<"disable-lifecycle-scripts">, Rule<"enforce-strict-ssl">, Rule<"files-field">, Rule<"frozen-lockfile">, Rule<"frozen-store">, Rule<"hardened-mode">, Rule<"minimum-release-age">, Rule<"named-registries">, Rule<"paranoid-mode">, Rule<"patched-dependencies">, Rule<"pin-exact-versions">, Rule<"provenance">, Rule<"publish-access">, Rule<"store-server">, Rule<"strict-allow-scripts">, Rule<"strict-release-age">, Rule<"strict-store-integrity">, Rule<"trust-policy">];
145
+ type BuiltinRuleId = (typeof rules)[number]['id'];
146
+ //#endregion
147
+ //#region src/domain/ports/io.d.ts
148
+ /** Output sink for commands; injectable so tests can drive a string buffer. */
149
+ interface IO {
150
+ stdout: (line: string) => void;
151
+ stderr: (line: string) => void;
152
+ }
224
153
  //#endregion
225
154
  //#region src/domain/entities/lint-result.d.ts
226
155
  interface Finding {
@@ -229,22 +158,7 @@ interface Finding {
229
158
  readonly severity: Severity;
230
159
  readonly message: string;
231
160
  readonly file?: string;
232
- readonly fixable: boolean;
233
- /**
234
- * Machine-readable remediation: the binding's fix ops, verbatim. Empty
235
- * when `manualSteps` is present (writing the ops would be defeated by the
236
- * user state that produced the steps). Advisory bindings contribute their
237
- * `note` / `ensureFileTracked` ops here so external fixers can surface
238
- * them. Part of the json output contract — see docs/json-output.md.
239
- */
240
- readonly fix: readonly FixOp[];
241
- /** Manual remediation steps, verbatim from the check (see CheckStatus). */
242
- readonly manualSteps?: readonly string[];
243
- /**
244
- * Pinned to {@link ConfigValue} (no `null`) so it stays in lockstep with
245
- * `CheckStatus.violation.expected`. Widening to ConfigScalar would have
246
- * advertised a `null` outcome that no current rule can produce.
247
- */
161
+ readonly remediation?: Remediation;
248
162
  readonly expected?: ConfigValue;
249
163
  readonly actual?: ConfigReadValue;
250
164
  /** Official PM doc anchor for the setting this finding is about, if any. */
@@ -256,32 +170,54 @@ interface LintResult {
256
170
  }
257
171
  //#endregion
258
172
  //#region src/domain/ports/reporter.d.ts
259
- /**
260
- * Renderer from `LintResult` to user-facing output.
261
- *
262
- * Reporters never touch the filesystem and never spawn processes — every
263
- * output byte flows through the injected `IO` port. They MAY however
264
- * consult `process.env` for environmental shape (TTY colour support,
265
- * NO_COLOR / FORCE_COLOR, CI detection) because that information has no
266
- * `IO`-port equivalent and treating it as I/O would force the application
267
- * layer to thread a snapshot through every call site for a value the
268
- * adapter can read in a single line.
269
- *
270
- * Implementations live in the adapter layer (src/adapters/reporters/),
271
- * so the env access stays out of the domain.
272
- */
273
173
  interface Reporter<Name extends string = string> {
274
174
  readonly name: Name;
275
175
  format: (result: LintResult, io: IO) => void;
276
176
  }
177
+ //#endregion
178
+ //#region src/domain/entities/siro-config.d.ts
179
+ /** Per-rule setting. `'off'` disables; a Severity overrides the default level. */
180
+ type RuleSetting = Severity | 'off';
277
181
  /**
278
- * Structural guard for an embedder-supplied reporter. The `Reporter` contract
279
- * is compile-time only (`defineConfig` / typed options), so a hand-written JS
280
- * config or a dynamically-built object can still pass a malformed value; this
281
- * lets each boundary reject it with its own error type before `format` is
282
- * called on something that isn't a function.
182
+ * User-facing config returned by `defineConfig` in `siro.config.{ts,mjs,js}`.
183
+ *
184
+ * defineConfig({
185
+ * pms: ['npm', 'pnpm'], // restrict detection
186
+ * rules: { provenance: 'off' }, // disable / override severity
187
+ * customRules: [myRule], // extend
188
+ * reporters: [mySarifReporter], // extend
189
+ * })
283
190
  */
284
- declare const isReporterShape: (value: unknown) => value is Reporter;
191
+ interface SiroConfig {
192
+ readonly pms?: readonly PM[];
193
+ readonly projectType?: ProjectType;
194
+ readonly rules?: Readonly<Partial<Record<BuiltinRuleId | (string & Record<never, never>), RuleSetting>>>;
195
+ readonly customRules?: readonly Rule[];
196
+ readonly reporters?: readonly Reporter[];
197
+ }
198
+ /** Identity helper for type-checked config files. */
199
+ declare const defineConfig: (config: SiroConfig) => SiroConfig;
200
+ //#endregion
201
+ //#region src/application/lint.d.ts
202
+ interface LintOptions {
203
+ readonly cwd: AbsPath;
204
+ readonly fs?: FileSystem;
205
+ readonly pm?: PM;
206
+ readonly projectType?: ProjectType;
207
+ /** Explicit configuration; the library never imports files from the target repository. */
208
+ readonly config?: SiroConfig;
209
+ }
210
+ declare const lint: (options: LintOptions) => LintResult;
211
+ //#endregion
212
+ //#region src/adapters/config-loader.d.ts
213
+ /** Load the first matching config; executable imports always use the real filesystem. */
214
+ declare const loadConfig: (cwd: AbsPath, nodeVersion?: string) => Promise<SiroConfig | undefined>;
215
+ //#endregion
216
+ //#region src/adapters/node-file-system.d.ts
217
+ declare const nodeFileSystem: FileSystem;
218
+ //#endregion
219
+ //#region src/adapters/node-io.d.ts
220
+ declare const nodeIO: IO;
285
221
  //#endregion
286
222
  //#region src/adapters/reporters/github.d.ts
287
223
  /** Emit GitHub Actions workflow commands (annotations on PRs). */
@@ -299,60 +235,17 @@ declare const BUILTINS: readonly [Reporter<"pretty">, Reporter<"json">, Reporter
299
235
  declare const BUILTIN_REPORTER_NAMES: readonly BuiltinReporterName[];
300
236
  /** Literal union of every built-in reporter name. */
301
237
  type BuiltinReporterName = (typeof BUILTINS)[number]['name'];
302
- /** Reporter lookup table — pass an explicit value to keep calls hermetic. */
303
- interface ReporterRegistry {
304
- get: (name: string) => Reporter | undefined;
305
- list: () => readonly string[];
306
- }
307
- /** Build a registry from the builtins plus any extras (later wins on collision). */
308
- declare const createRegistry: (extras?: readonly Reporter[]) => ReporterRegistry;
309
238
  //#endregion
310
239
  //#region src/application/commands/lint.d.ts
311
- interface LintOptions {
312
- /**
313
- * Absolute repo root. Callers are responsible for resolving any relative
314
- * path before branding it; siro's CLI does this in `cli.ts`. Keeping the
315
- * brand here lets the application layer stay free of `node:path`.
316
- */
317
- cwd: AbsPath;
318
- /** Restrict to a single PM; otherwise PMs are auto-detected. */
319
- pm?: PM;
320
- /** Reporter name or instance; defaults to `pretty`. */
321
- reporter?: string | Reporter;
322
- /** Extra reporters to make available by name (merged with the user config). */
323
- reporters?: readonly Reporter[];
324
- /**
325
- * Programmatic custom rules to evaluate alongside the builtins and any
326
- * `customRules` from the user config. Mirrors `SiroConfig.customRules`
327
- * but is supplied at the call site, so embedders can compose rulesets
328
- * without writing a config file. Ids must be unique across builtins,
329
- * config-supplied custom rules, and this list — collisions throw a
330
- * `ConfigError` (exit 2), matching how `loadConfig` rejects them.
331
- */
332
- customRules?: readonly Rule[];
333
- /** Show (and fail on) findings at or above this severity. */
334
- severity?: Severity;
335
- /**
336
- * Inject a non-default FS (e.g. memfs in tests). Caveat: `siro.config.{ts,mjs,js}`
337
- * is imported from the REAL disk — only the config's existence is probed
338
- * through this FS. A config that lives solely in an injected FS won't load.
339
- */
340
- fs?: FileSystem;
240
+ interface LintCommandOptions extends LintOptions {
241
+ readonly reporter?: string | Reporter;
242
+ readonly severity?: Severity;
341
243
  }
342
- /** `siro lint`: detect PMs, evaluate rules, report findings. */
343
- declare const lintCommand: (options: LintOptions, io: IO) => Promise<number>;
244
+ /** Evaluate and report. Executable config loading belongs to the CLI adapter. */
245
+ declare const lintCommand: (options: LintCommandOptions, io: IO) => Promise<number>;
344
246
  //#endregion
345
247
  //#region src/domain/entities/config-files.d.ts
346
- /**
347
- * Canonical {@link ConfigFileRef}s for every package-manager config file siro
348
- * knows how to read. Centralizing them keeps each rule module from hardcoding
349
- * `kind` / `path` pairs, so renaming a file (or fixing a typo in its path) is a
350
- * single-line change. This is also the one place `RelPath` is minted for a
351
- * config ref — every downstream binding inherits the brand, so no rule needs
352
- * an ad-hoc `asRelPath(ref.path)` cast at the FS boundary.
353
- *
354
- * Add a new entry here when introducing support for a new package manager.
355
- */
248
+ /** Known configuration locations and their parsers. */
356
249
  declare const CONFIG_FILES: {
357
250
  readonly aubeWorkspace: {
358
251
  readonly kind: "yaml";
@@ -384,137 +277,19 @@ declare const CONFIG_FILES: {
384
277
  };
385
278
  };
386
279
  //#endregion
387
- //#region src/domain/entities/signals.d.ts
388
- /**
389
- * Filenames that identify a package manager. `lockfiles[0]` is the
390
- * canonical/preferred one (used by lint messages and error
391
- * messages); the rest are legacy/alternative forms the PM writes itself.
392
- *
393
- * `lockfiles` and `configs` are **detection evidence** — their presence means
394
- * the PM is in use. `reusesLockfiles` is NOT evidence: it lists other PMs'
395
- * lockfile shapes that this PM will reuse rather than writing its own (aube),
396
- * so `commit-lockfile` accepts them but `detectPMs` ignores them — otherwise a
397
- * plain pnpm/npm repo would false-positive as aube. Keeping the two lists
398
- * separate is what lets detection stay a simple "any signal present?" check.
399
- */
400
- interface PMSignals {
401
- readonly lockfiles: readonly [string, ...string[]];
402
- readonly configs: readonly string[];
403
- /** Other PMs' lockfiles this PM reuses; satisfies commit-lockfile, not detection. */
404
- readonly reusesLockfiles?: readonly string[];
405
- }
406
- declare const PM_SIGNALS: {
407
- readonly aube: {
408
- readonly configs: readonly [RelPath];
409
- readonly lockfiles: readonly ["aube-lock.yaml"];
410
- readonly reusesLockfiles: readonly ["pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "bun.lock", "bun.lockb", "deno.lock"];
411
- };
412
- readonly bun: {
413
- readonly configs: readonly [RelPath];
414
- readonly lockfiles: readonly ["bun.lock", "bun.lockb"];
415
- };
416
- readonly deno: {
417
- readonly configs: readonly [RelPath];
418
- readonly lockfiles: readonly ["deno.lock"];
419
- };
420
- readonly npm: {
421
- readonly configs: readonly [];
422
- readonly lockfiles: readonly ["package-lock.json", "npm-shrinkwrap.json"];
423
- };
424
- readonly pnpm: {
425
- readonly configs: readonly [RelPath];
426
- readonly lockfiles: readonly ["pnpm-lock.yaml"];
427
- };
428
- readonly yarn: {
429
- readonly configs: readonly [RelPath];
430
- readonly lockfiles: readonly ["yarn.lock"];
431
- };
432
- };
433
- //#endregion
434
- //#region src/domain/entities/rule-id.d.ts
435
- /**
436
- * Closed set of built-in rule IDs. Declared in `domain/` so SiroConfig can
437
- * type `rules` keys without depending on the engine.
438
- */
439
- declare const BUILTIN_RULE_IDS: readonly ["advisory-check", "approved-git-repos", "audit-suppression", "block-auto-install", "block-exotic-subdeps", "bun-security-scanner", "checksum-verification", "commit-lockfile", "dependency-overrides", "disable-lifecycle-scripts", "enforce-strict-ssl", "files-field", "frozen-lockfile", "frozen-store", "hardened-mode", "minimum-release-age", "named-registries", "paranoid-mode", "patched-dependencies", "pin-exact-versions", "provenance", "publish-access", "store-server", "strict-allow-scripts", "strict-release-age", "strict-store-integrity", "trust-policy"];
440
- type BuiltinRuleId = (typeof BUILTIN_RULE_IDS)[number];
441
- //#endregion
442
- //#region src/domain/entities/siro-config.d.ts
443
- /** Per-rule setting. `'off'` disables; a Severity overrides the default level. */
444
- type RuleSetting = Severity | 'off';
445
- /**
446
- * User-facing config returned by `defineConfig` in `siro.config.{ts,mjs,js}`.
447
- *
448
- * defineConfig({
449
- * pms: ['npm', 'pnpm'], // restrict detection
450
- * rules: { provenance: 'off' }, // disable / override severity
451
- * customRules: [myRule], // extend
452
- * reporters: [mySarifReporter], // extend
453
- * })
454
- */
455
- interface SiroConfig {
456
- readonly pms?: readonly PM[];
457
- readonly rules?: Readonly<Partial<Record<BuiltinRuleId | (string & Record<never, never>), RuleSetting>>>;
458
- readonly customRules?: readonly Rule[];
459
- readonly reporters?: readonly Reporter[];
460
- }
461
- /** Identity helper for type-checked config files. */
462
- declare const defineConfig: (config: SiroConfig) => SiroConfig;
463
- //#endregion
464
- //#region src/domain/ports/config-codec.d.ts
465
- /** Reads a single config-file format into siro's structural view. */
466
- interface ConfigCodec {
467
- parse: (text: string) => ParsedConfig;
468
- }
469
- /** Resolve the codec for a given file kind. Total over `CodecKind`. */
470
- type CodecFor = (kind: CodecKind) => ConfigCodec;
471
- //#endregion
472
280
  //#region src/domain/rules/builders/require-config-key.d.ts
473
- /**
474
- * Display-only metadata for a binding. siro itself is intentionally
475
- * version-agnostic (we never branch on the user's actual PM version) — these
476
- * fields exist so the rendered message can still tell the reader *when* a key
477
- * appeared and *when* the PM started shipping a safe default. They MUST NOT
478
- * be consulted by any code path that decides severity or applicability.
479
- */
480
- interface VersionNote {
481
- /** PM version that first shipped this config key (e.g. `'pnpm 10.16.0'`). */
482
- readonly configAvailableSince?: string;
483
- /** PM version whose built-in default first satisfies the rule (e.g. `'pnpm 11.0.0 (1440 minutes)'`). */
484
- readonly defaultSafeSince?: string;
485
- /** Free-form trailing note rendered last (e.g. `'replaces auto-install-peers'`). */
486
- readonly note?: string;
487
- }
488
281
  interface RequireConfigKeySpec {
489
282
  readonly file: ConfigFileRef;
490
283
  readonly keyPath: KeyPath;
491
- /** Value `fix` will write and (when no `accept` predicate is given) the value `check` expects. */
284
+ /** Expected value and proposed replacement; `accept` may allow other values. */
492
285
  readonly value: ConfigValue;
493
286
  readonly message: string;
494
287
  readonly docs?: string;
495
288
  readonly severity?: Severity;
496
289
  accept?: (actual: unknown) => boolean;
497
290
  /**
498
- * Extra key writes appended after the auto-generated one (e.g. clearing
499
- * save-prefix alongside save-exact on the same .npmrc). The target file
500
- * is implicitly `spec.file` — extras can't redirect to a different file
501
- * because `check` would then never validate them, leaving the fix ops
502
- * and `lint` permanently out of step. A rule that legitimately needs to write
503
- * across multiple files belongs in a hand-written binding (see
504
- * `disable-lifecycle-scripts` → `overrideBindings`).
505
- */
506
- readonly extraFix?: readonly KeyAssignment[];
507
- /**
508
- * PM-documented default for this key. When the user has not set the key
509
- * AND this default would satisfy `accept`/`value`, the finding is
510
- * downgraded to {@link defaultSatisfiedSeverity} (default `'info'`) — the
511
- * threat is mitigated by the PM but explicit pinning is still recommended.
512
- * - unset: no PM-default protection (legacy behaviour).
513
- *
514
- * A CONDITIONAL default (e.g. CI-only: pnpm `frozenLockfile`, aube
515
- * `preferFrozenLockfile`) may use this field, but the binding's `message`
516
- * MUST name the condition — the downgrade then reads "covered where it
517
- * matters most", not "covered unconditionally".
291
+ * An omitted value that is safe across every supported version and target
292
+ * environment emits info. Version-dependent defaults retain full severity.
518
293
  */
519
294
  readonly documentedDefault?: ConfigValue;
520
295
  /**
@@ -524,82 +299,36 @@ interface RequireConfigKeySpec {
524
299
  readonly defaultSatisfiedSeverity?: Severity | 'off';
525
300
  readonly versionNote?: VersionNote;
526
301
  }
527
- interface RequireConfigKeyOptions {
528
- readonly id: string;
302
+ interface RequireConfigKeyOptions<Id extends string = string> {
303
+ readonly id: Id;
529
304
  readonly title: string;
530
305
  readonly description: string;
531
306
  readonly severity: Severity;
532
307
  readonly docs?: string;
308
+ readonly projectTypes?: Rule['projectTypes'];
533
309
  /** Bindings keyed by PM. PMs absent from this map are treated as N/A. */
534
310
  readonly bindings: Partial<Record<PM, RequireConfigKeySpec>>;
535
311
  /** Return false to short-circuit `check` as N/A (e.g. private packages). */
536
312
  applies?: (ctx: RepoContext) => boolean;
537
313
  }
538
- /**
539
- * Replace one or more bindings on an existing rule, returning a fresh Rule
540
- * object. Used by rules whose shape outgrows {@link requireConfigKey} for a
541
- * subset of PMs — they build the simple slots via the builder, then splice
542
- * the hand-written bindings in via this helper rather than rebuilding from
543
- * scratch or mutating the source rule.
544
- */
545
- declare const overrideBindings: (rule: Rule, overrides: Partial<Rule["bindings"]>) => Rule;
546
- declare const renderVersionNoteMessage: (message: string, versionNote: VersionNote | undefined) => string;
314
+ declare const overrideBindings: <Id extends string>(rule: Rule<Id>, overrides: Partial<Rule["bindings"]>) => Rule<Id>;
547
315
  /** Build a Rule from a per-PM table of {file, keyPath, value, message}. */
548
- declare const requireConfigKey: (options: RequireConfigKeyOptions) => Rule;
549
- //#endregion
550
- //#region src/domain/services/detect-pms.d.ts
551
- declare const detectPMs: (ctx: RepoContext) => PM[];
316
+ declare const requireConfigKey: <const Id extends string>(options: RequireConfigKeyOptions<Id>) => Rule<Id>;
552
317
  //#endregion
553
- //#region src/domain/services/filter.d.ts
554
- /** Keep only findings at or above `threshold`, recomputing the summary. */
555
- declare const filterBySeverity: (result: LintResult, threshold: Severity) => LintResult;
556
- /**
557
- * Exit code for a lint run. Non-zero when any finding meets `threshold`
558
- * (default: only `error` fails the run).
559
- */
560
- declare const exitCodeForLint: (result: LintResult, threshold?: Severity) => number;
561
- //#endregion
562
- //#region src/shared/siro-error.d.ts
563
- /**
564
- * Structured errors used across siro. The CLI maps them to exit codes in one
565
- * place so individual commands never have to think about exit semantics.
566
- * The exit-code table lives in docs/configuration.md S"Exit codes" and
567
- * src/cli/help.ts (HELP_LINT) -- not restated here.
568
- *
569
- * One mapping note that belongs at this altitude: filesystem errno errors
570
- * are routed to 2 by SHAPE (numeric `errno`) in cli run(), so an
571
- * errno-bearing throw from a user extension also lands on 2 rather than the
572
- * 70 crash path -- same remedy (fix the environment) either way.
573
- */
318
+ //#region src/shared/errors.d.ts
574
319
  declare class SiroError extends Error {
575
- /** Recommended process exit code for this error. */
576
320
  readonly exitCode: number;
577
321
  constructor(message: string, exitCode: number);
578
322
  }
579
- //#endregion
580
- //#region src/shared/config-error.d.ts
581
323
  declare class ConfigError extends SiroError {
582
324
  constructor(message: string);
583
325
  }
584
- //#endregion
585
- //#region src/shared/usage-error.d.ts
586
326
  declare class UsageError extends SiroError {
587
327
  constructor(message: string);
588
328
  }
589
329
  //#endregion
590
- //#region src/shared/errors.d.ts
591
- /**
592
- * Run `fn` and wrap any non-`ConfigError` failure as a `ConfigError` prefixed
593
- * with `filePath`. Codec `parse` calls hit raw libraries that
594
- * throw bare `Error`s; without this wrap the CLI would surface those as
595
- * exit-1 internal errors and break CI that branches on exit codes. A
596
- * `ConfigError` that bubbles up from a nested call is re-thrown unchanged so
597
- * the original `path: message` framing is preserved.
598
- */
599
- declare const wrapCodecError: <TResult>(filePath: string, fn: () => TResult) => TResult;
600
- //#endregion
601
330
  //#region src/version.d.ts
602
- declare const version = "0.2.0";
331
+ declare const version: string;
603
332
  //#endregion
604
- export { type AbsPath, type AdvisoryRuleBinding, type AutoRuleBinding, BUILTIN_REPORTER_NAMES, type BuiltinReporterName, CONFIG_FILES, type CheckStatus, type CodecFor, type ConfigCodec, ConfigError, type ConfigFileRef, type ConfigReadValue, type ConfigScalar, type ConfigValue, type FileSystem, type Finding, type FixKind, type FixOp, type IO, type KeyAssignment, type KeyPath, type LintOptions, type LintResult, type PM, PMS, type PMSignals, PM_SIGNALS, type PackageJson, type ParsedConfig, type RelPath, type RepoContext, type Reporter, type ReporterRegistry, type RequireConfigKeyOptions, type Rule, type RuleBinding, type RuleSetting, SEVERITIES, type Severity, type SiroConfig, SiroError, UsageError, type VersionNote, asAbsPath, asRelPath, createRegistry, defineConfig, detectPMs, exitCodeForLint, filterBySeverity, getByPath, githubReporter, isPM, isReporterShape, isSeverity, jsonReporter, lintCommand, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, renderVersionNoteMessage, requireConfigKey, version, wrapCodecError };
333
+ export { type AbsPath, BUILTIN_REPORTER_NAMES, type BuiltinReporterName, CONFIG_FILES, type CheckStatus, ConfigError, type ConfigFileRef, type ConfigReadValue, type ConfigValue, type FileSystem, type Finding, type IO, type KeyPath, type LintCommandOptions, type LintOptions, type LintResult, type PM, PMS, PROJECT_TYPES, type ParsedConfig, type ProjectType, type RelPath, type Remediation, type RepoContext, type Reporter, type RequireConfigKeyOptions, type Rule, type RuleBinding, type RuleContext, type RuleSetting, SEVERITIES, type SetKeyOperation, type Severity, type SiroConfig, SiroError, UsageError, type VersionNote, asAbsPath, asRelPath, defineConfig, defineRule, getByPath, githubReporter, isPM, isSeverity, jsonReporter, lint, lintCommand, loadConfig, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, requireConfigKey, version };
605
334
  //# sourceMappingURL=index.d.mts.map