depcurfew 1.3.0 → 1.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.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Readable, Writable } from "node:stream";
2
- type ToolId = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'deno' | 'uv' | 'pip' | 'poetry' | 'pdm' | 'pipenv' | 'renovate' | 'dependabot';
2
+ type ToolId = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'deno' | 'uv' | 'pip' | 'poetry' | 'pdm' | 'pipenv' | 'composer' | 'renovate' | 'dependabot';
3
3
  type ToolKind = 'package-manager' | 'update-bot';
4
4
  /** The registry a package manager installs from; package names in the policy are compared per ecosystem. */
5
- type Ecosystem = 'npm' | 'pypi';
5
+ type Ecosystem = 'npm' | 'pypi' | 'packagist';
6
6
  interface Evidence$1<T> {
7
7
  value: T;
8
8
  /** URL of official documentation or changelog. */
@@ -250,9 +250,19 @@ type OverrideValues = 'delay' | 'any' | readonly string[] | {
250
250
  interface OverrideSwitch {
251
251
  /** The option as the scanner resolves it (--name) or the variable, compared without regard to case. */
252
252
  name: string;
253
- /** The release-age delay, or the rule on dependency install scripts. */
254
- weakens: 'delay' | 'scripts';
253
+ /**
254
+ * The release-age delay, the rule on dependency install scripts, or a protection of the package manager that the
255
+ * policy does not manage (Composer's blocking of versions flagged as malware), which always counts (decision D-133).
256
+ */
257
+ weakens: 'delay' | 'scripts' | 'protection';
255
258
  values: OverrideValues;
259
+ /** protection: what it turns off, for messages ("turns off the blocking of versions flagged as malware"). */
260
+ effect?: string;
261
+ /**
262
+ * protection: what that protection does, as a clause of its own, when it covers less than every package the command
263
+ * fetches ("secure-http lets Composer download only over HTTPS, …"; decision D-142).
264
+ */
265
+ protects?: string;
256
266
  }
257
267
  /** A tool that environment facts refer to: Node.js, Python or a package manager. */
258
268
  type RuntimeOrTool = 'node' | 'python' | ToolId;
@@ -386,6 +396,71 @@ interface ProjectStatusKnowledge {
386
396
  /** Installers may warn about the project. */
387
397
  warned: readonly string[];
388
398
  }
399
+ /** The batch query of the OSV.dev API (decision D-129): where to send it, how its JSON is spelled, and its limit. */
400
+ interface OsvApiKnowledge {
401
+ /** Base URL of the API. */
402
+ url: string;
403
+ queryBatchPath: string;
404
+ /** GET: one record by id ({id}). */
405
+ vulnerabilityPath: string;
406
+ /** The web page of a record ({id}). */
407
+ recordPage: string;
408
+ queriesField: string;
409
+ resultsField: string;
410
+ vulnsField: string;
411
+ idField: string;
412
+ /** A result with more pages carries this token; the next request sends it back as pageTokenField of that query. */
413
+ nextPageTokenField: string;
414
+ pageTokenField: string;
415
+ /** Most queries in one batch request. */
416
+ maxQueriesPerBatch: number;
417
+ }
418
+ /** When a batch result is paged: more vulnerabilities than these for one query, or for the whole batch. */
419
+ interface OsvPagingKnowledge {
420
+ perQuery: number;
421
+ perBatch: number;
422
+ }
423
+ /** What OSV.dev promises about its service: no rate limit (null), how fresh the data is, the largest response over HTTP/1.1. */
424
+ interface OsvServiceKnowledge {
425
+ rateLimit: number | null;
426
+ freshnessMinutes: number;
427
+ freshnessShare: number;
428
+ responseLimitBytes: number;
429
+ }
430
+ /** Withdrawn records: the field that marks them, and which responses still carry them. */
431
+ interface OsvWithdrawnKnowledge {
432
+ field: string;
433
+ inQueryResponses: boolean;
434
+ inRecordResponses: boolean;
435
+ }
436
+ /** The records of known malicious packages in OSV.dev: their id prefix and where they come from. */
437
+ interface OsvMaliciousKnowledge {
438
+ idPrefix: string;
439
+ database: string;
440
+ license: string;
441
+ }
442
+ /** The characters a record id may have (a regular expression for the whole id), from the OSV schema (decision D-135). */
443
+ interface OsvIdKnowledge {
444
+ pattern: string;
445
+ }
446
+ /** OSV.dev, asked about known malicious package versions with --osv (decision D-129). */
447
+ interface OsvKnowledge {
448
+ api: Fact<OsvApiKnowledge>;
449
+ paging: Fact<OsvPagingKnowledge>;
450
+ ids: Fact<OsvIdKnowledge>;
451
+ service: Fact<OsvServiceKnowledge>;
452
+ withdrawn: Fact<OsvWithdrawnKnowledge>;
453
+ malicious: Fact<OsvMaliciousKnowledge>;
454
+ /** The OSV ecosystem name of each ecosystem of depcurfew (npm, pypi, ...). */
455
+ ecosystems: Fact<Readonly<Record<string, string>>>;
456
+ /** How the API compares the version of a query with the versions of a record, by depcurfew's ecosystem (decision D-142). */
457
+ versionMatching: Fact<Readonly<Record<string, OsvVersionMatchingKnowledge>>>;
458
+ }
459
+ /** What OSV.dev leaves out of a version before it compares it. */
460
+ interface OsvVersionMatchingKnowledge {
461
+ /** A leading character left out of both versions (Packagist: v and V). */
462
+ ignoredPrefixes: readonly string[];
463
+ }
389
464
  /** Claude Code hooks that run before a tool call and can block it (used by depcurfew check --hook claude-code). */
390
465
  interface ClaudeCodeHooksKnowledge {
391
466
  /** Project settings file that can be committed. */
@@ -775,6 +850,36 @@ interface RuntimeImageKnowledge {
775
850
  interface MovingTagsKnowledge {
776
851
  tags: Readonly<Record<string, string>>;
777
852
  }
853
+ /**
854
+ * An npm package with install scripts whose effect is known (decision D-127), read in its source at the releases in
855
+ * `versions`: `not-needed` when the package works without the script, `project-task` when the script does a task of
856
+ * the project (installing git hooks) that the project can run in its own lifecycle script (`projectScript`).
857
+ */
858
+ interface InstallScriptPackageKnowledge {
859
+ name: string;
860
+ /** The releases the source was read at. */
861
+ versions: readonly string[];
862
+ /** The install script, as the package declares it or as npm adds it, for messages. */
863
+ script: string;
864
+ kind: 'not-needed' | 'project-task';
865
+ /** What the script does, as a clause for messages ("it runs lefthook install, which installs the git hooks"). */
866
+ does: string;
867
+ /** What changes when the script does not run, as a clause for messages ("nothing changes"). */
868
+ withoutScript: string;
869
+ /**
870
+ * Without its install script the package can take its binary only from an optional dependency for the platform (the
871
+ * script fetches it otherwise), so a denied one fails where optional dependencies are left out.
872
+ */
873
+ needsOptionalDependencies: boolean;
874
+ /**
875
+ * project-task: how the project does the same task without the install script, as the package documents it for
876
+ * projects that do not use that script: a command to run after each clone of the repository (decision D-135).
877
+ */
878
+ projectStep?: {
879
+ command: string;
880
+ after: 'clone';
881
+ };
882
+ }
778
883
  interface EnvironmentKnowledge {
779
884
  corepack: {
780
885
  packageManagerField: Fact<PackageManagerFieldKnowledge>;
@@ -792,7 +897,18 @@ interface EnvironmentKnowledge {
792
897
  command: string;
793
898
  npmOnlyWhenNamed: boolean;
794
899
  }>;
900
+ /**
901
+ * Installing Corepack where Node.js does not come with it (decision D-128): the npm package and the command; then
902
+ * corepack enable puts the shims on the path.
903
+ */
904
+ manualInstall: Fact<{
905
+ package: string;
906
+ command: string;
907
+ enable: string;
908
+ }>;
795
909
  };
910
+ /** npm packages whose install scripts are known to be unneeded or to do a task of the project (decision D-127). */
911
+ installScriptPackages: Readonly<Record<string, Fact<InstallScriptPackageKnowledge>>>;
796
912
  node: {
797
913
  /** The npm each Node.js release bundles, per release line. */
798
914
  bundledNpm: Fact<readonly BundledVersionsLine[]>;
@@ -853,6 +969,10 @@ interface EnvironmentKnowledge {
853
969
  };
854
970
  githubActions: {
855
971
  workflows: Fact<WorkflowsKnowledge>;
972
+ /** The ids GitHub accepts as keys of jobs, as a regular expression (decision D-135). */
973
+ jobId: Fact<{
974
+ pattern: string;
975
+ }>;
856
976
  setupNode: Fact<SetupActionKnowledge>;
857
977
  /** The versions node-version accepts besides semver ranges. */
858
978
  setupNodeVersions: Fact<NodeVersionNamesKnowledge>;
@@ -871,6 +991,10 @@ interface EnvironmentKnowledge {
871
991
  key: string;
872
992
  levels: readonly ('workflow' | 'job' | 'step')[];
873
993
  }>;
994
+ /** Whether workflows may use YAML anchors and aliases, which GitHub expands (decision D-141). */
995
+ yamlAliases: Fact<{
996
+ accepted: boolean;
997
+ }>;
874
998
  pnpmActionSetup: Fact<SetupActionKnowledge>;
875
999
  pnpmSetup: Fact<SetupActionKnowledge>;
876
1000
  setupBun: Fact<SetupActionKnowledge>;
@@ -943,6 +1067,8 @@ interface EnvironmentKnowledge {
943
1067
  extensions: readonly string[];
944
1068
  }>;
945
1069
  };
1070
+ /** OSV.dev, for --osv (decision D-129). */
1071
+ osv: OsvKnowledge;
946
1072
  python: {
947
1073
  /** How Python package names are compared: lowercased, with every run of these characters replaced by `replacement`. */
948
1074
  nameNormalization: Fact<{
@@ -969,7 +1095,7 @@ interface EnvironmentKnowledge {
969
1095
  }>;
970
1096
  };
971
1097
  }
972
- type PackageManagerId = Extract<ToolId, 'npm' | 'pnpm' | 'yarn' | 'bun' | 'deno' | 'uv' | 'pip' | 'poetry' | 'pdm' | 'pipenv'>;
1098
+ type PackageManagerId = Extract<ToolId, 'npm' | 'pnpm' | 'yarn' | 'bun' | 'deno' | 'uv' | 'pip' | 'poetry' | 'pdm' | 'pipenv' | 'composer'>;
973
1099
  type BotId = Extract<ToolId, 'renovate' | 'dependabot'>;
974
1100
  /** Why a package manager is considered in use. Ordered from the strongest signal. */
975
1101
  type EvidenceKind = 'lockfile' | 'packageManager' | 'devEngines' | 'config' | 'ci' | 'default';
@@ -1032,11 +1158,22 @@ interface ToolCommand {
1032
1158
  lockfile?: true;
1033
1159
  /** npm runs it with dependency install scripts off (--ignore-scripts, npm_config_ignore_scripts=true; decision D-115). */
1034
1160
  scriptsOff?: true;
1161
+ /**
1162
+ * npm install: its command line and variables leave out optional dependencies (--omit=optional, --no-optional,
1163
+ * npm_config_omit=optional) or keep them whatever .npmrc says (--include=optional); absent when they do not say
1164
+ * (decision D-127).
1165
+ */
1166
+ optionalDependencies?: 'omitted' | 'kept';
1035
1167
  /** pip install: the release-age delay it gets, as written (--uploaded-prior-to, PIP_UPLOADED_PRIOR_TO; decision D-115). */
1036
1168
  delay?: string;
1037
1169
  /** pip install: PIP_CONFIG_FILE names a file that may set the delay, or the value is a variable depcurfew does not read. */
1038
1170
  delayUnknown?: true;
1039
1171
  line: number;
1172
+ /**
1173
+ * A command that installs or runs Corepack comes before it in the same job (an earlier run step, or earlier in its
1174
+ * own) or Dockerfile stage (an earlier RUN, or earlier in its own), also on the same line (decision D-141).
1175
+ */
1176
+ afterCorepack?: true;
1040
1177
  /**
1041
1178
  * github-actions: the working directory of the run step as written (working-directory of the step, or
1042
1179
  * defaults.run.working-directory of the job or the workflow), without ./ and a trailing /, joined with the literal
@@ -1061,6 +1198,8 @@ interface WorkflowJob {
1061
1198
  * release-age delay of its pip installs cannot be told (decision D-115).
1062
1199
  */
1063
1200
  pipConfig?: true;
1201
+ /** The line of the first run step command that installs Corepack (npm install -g corepack) or runs it (decision D-128). */
1202
+ corepack?: number;
1064
1203
  }
1065
1204
  /** The official runtime image a build stage starts from. */
1066
1205
  interface StageRuntime {
@@ -1086,6 +1225,8 @@ interface DockerStage {
1086
1225
  commands: ToolCommand[];
1087
1226
  /** A RUN may change pip's configuration (pip config set, a pip.conf it writes), as for jobs (decision D-115). */
1088
1227
  pipConfig?: true;
1228
+ /** The line of the first RUN command that installs Corepack (npm install -g corepack) or runs it (decision D-128). */
1229
+ corepack?: number;
1089
1230
  /**
1090
1231
  * The source paths of the stage's COPY and ADD instructions that copy from the build context (not --from), as
1091
1232
  * written; absent when there are none. A root Dockerfile whose stage copies only files of one directory listed in
@@ -1129,6 +1270,13 @@ interface DetectionWarning {
1129
1270
  }
1130
1271
  interface Detection {
1131
1272
  packageManagers: PackageManagerInfo[];
1273
+ /**
1274
+ * Detected with the directories listed in projects (decision D-137): package managers only CI setup steps of the root
1275
+ * show, while the root holds no manifest of their registry, or every install with them in its workflows runs in a
1276
+ * listed directory and no Dockerfile of the root installs with them. The steps set them up for the projects, so they
1277
+ * are no package managers of the root; the versions the steps install are still checked. Absent when there are none.
1278
+ */
1279
+ ciForProjects?: PackageManagerInfo[];
1132
1280
  node: {
1133
1281
  versions: VersionDeclaration[];
1134
1282
  };
@@ -1154,10 +1302,13 @@ interface UserEnvironment {
1154
1302
  /** Whether a directory exists; replaceable in tests. */
1155
1303
  isDirectory?: (path: string) => boolean;
1156
1304
  }
1305
+ /** fetch as depcurfew calls it: a GET of a registry or index, or the POST of a batch query to OSV.dev (--osv, decision D-129). */
1157
1306
  type FetchLike = (url: string, init: {
1158
1307
  headers: Record<string, string>;
1159
1308
  signal: AbortSignal;
1160
1309
  redirect?: 'manual';
1310
+ method?: 'POST';
1311
+ body?: string;
1161
1312
  }) => Promise<Response>;
1162
1313
  /**
1163
1314
  * Lookups shared by the checks of one run, by registry or index and name: a command checked against two policies
@@ -1178,9 +1329,9 @@ interface McpLimits {
1178
1329
  /** How long calls may still run after stdin ends before they are stopped. */
1179
1330
  graceMs: number;
1180
1331
  }
1181
- declare const EXIT_OK = 0;
1182
- declare const EXIT_FAILURE = 1;
1183
- declare const EXIT_USAGE = 2;
1332
+ export declare const EXIT_OK = 0;
1333
+ export declare const EXIT_FAILURE = 1;
1334
+ export declare const EXIT_USAGE = 2;
1184
1335
  interface CliIo {
1185
1336
  stdout(text: string): void;
1186
1337
  stderr(text: string): void;
@@ -1243,8 +1394,8 @@ type DurationResult = {
1243
1394
  message: string;
1244
1395
  };
1245
1396
  /** Writes minutes with the largest unit that represents them exactly, e.g. 2160 → "36h". */
1246
- declare function formatDuration(minutes: number): string;
1247
- declare function parseDuration(input: string): DurationResult;
1397
+ export declare function formatDuration(minutes: number): string;
1398
+ export declare function parseDuration(input: string): DurationResult;
1248
1399
  interface Issue {
1249
1400
  /** Path to the field, e.g. `cooldown.exclude[2]`; `(root)` for the document itself. */
1250
1401
  path: string;
@@ -1293,12 +1444,6 @@ type Output<S> = S extends Shape<unknown, infer Out> ? Out : never;
1293
1444
  declare const FAIL_ON_LEVELS: readonly ["error", "warning"];
1294
1445
  declare const policyShape: Shape<{
1295
1446
  version: 1;
1296
- $schema?: string;
1297
- cooldown?: {
1298
- minimum: string;
1299
- exclude?: readonly string[];
1300
- securityUpdates?: "bypass" | "delay";
1301
- };
1302
1447
  installScripts?: {
1303
1448
  default: "allow" | "deny";
1304
1449
  allow?: readonly string[];
@@ -1310,6 +1455,12 @@ declare const policyShape: Shape<{
1310
1455
  file?: "allow" | "deny";
1311
1456
  directory?: "allow" | "deny";
1312
1457
  };
1458
+ $schema?: string;
1459
+ cooldown?: {
1460
+ minimum: string;
1461
+ exclude?: readonly string[];
1462
+ securityUpdates?: "bypass" | "delay";
1463
+ };
1313
1464
  targets?: "auto" | readonly ToolId[];
1314
1465
  projects?: readonly string[];
1315
1466
  doctor?: {
@@ -1319,12 +1470,6 @@ declare const policyShape: Shape<{
1319
1470
  version: 1;
1320
1471
  targets: "auto" | readonly ToolId[];
1321
1472
  projects: readonly string[];
1322
- $schema?: string;
1323
- cooldown?: {
1324
- minimum: Duration;
1325
- exclude: readonly string[];
1326
- securityUpdates: "bypass" | "delay";
1327
- };
1328
1473
  installScripts?: {
1329
1474
  default: "allow" | "deny";
1330
1475
  allow: readonly string[];
@@ -1336,6 +1481,12 @@ declare const policyShape: Shape<{
1336
1481
  file?: "allow" | "deny";
1337
1482
  directory?: "allow" | "deny";
1338
1483
  };
1484
+ $schema?: string;
1485
+ cooldown?: {
1486
+ minimum: Duration;
1487
+ exclude: readonly string[];
1488
+ securityUpdates: "bypass" | "delay";
1489
+ };
1339
1490
  doctor?: {
1340
1491
  failOn?: "error" | "warning";
1341
1492
  };
@@ -1344,6 +1495,159 @@ declare const policyShape: Shape<{
1344
1495
  type Policy = Input<typeof policyShape>;
1345
1496
  /** The policy after validation: defaults filled in, durations parsed. */
1346
1497
  type ResolvedPolicy = Output<typeof policyShape>;
1498
+ interface SimilarName {
1499
+ /** The popular package the name resembles. */
1500
+ match: string;
1501
+ /** edit: a letter or two differ; separators: only -, _ or . differ. */
1502
+ reason: 'edit' | 'separators';
1503
+ }
1504
+ export declare class PopularNames {
1505
+ #private;
1506
+ constructor(names: readonly string[]);
1507
+ has(name: string): boolean;
1508
+ /** The popular package `name` resembles, or undefined (also when `name` is itself popular). */
1509
+ similar(name: string): SimilarName | undefined;
1510
+ }
1511
+ type SpecKind = 'registry' | 'git' | 'remoteTarball' | 'file' | 'directory' | 'workspace' | 'other';
1512
+ type FindingCode = 'invalid-spec' | 'forbidden-source' | 'unchecked-source' | 'unverified' | 'not-found' | 'no-version' | 'too-young' | 'install-scripts' | 'similar-name' | 'deprecated' | 'yanked' | 'archived' | 'quarantined' | 'insecure-registry' |
1513
+ /** --osv: OSV.dev has a record of the OpenSSF malicious-packages database for the version (decision D-129). */
1514
+ 'known-malicious';
1515
+ interface CheckFinding {
1516
+ level: 'error' | 'warning';
1517
+ code: FindingCode;
1518
+ message: string;
1519
+ /** What to do about it. */
1520
+ fix: string;
1521
+ }
1522
+ interface PackageReport {
1523
+ spec: string;
1524
+ kind: SpecKind | 'invalid';
1525
+ name?: string;
1526
+ version?: string;
1527
+ publishedAt?: string;
1528
+ latest?: {
1529
+ version: string;
1530
+ publishedAt?: string;
1531
+ };
1532
+ /** npm: the dist-tag the version was picked by, when it is not the default one (--tag, npm_config_tag, tag in .npmrc). */
1533
+ tag?: string;
1534
+ /** Python: pre-releases allowed or forbidden by the command. */
1535
+ prereleases?: 'allow' | 'deny';
1536
+ /** Python: the command builds the package from source (true) or installs wheels only (false). */
1537
+ buildFromSource?: boolean;
1538
+ findings: CheckFinding[];
1539
+ }
1540
+ /** A spec with what the install command changes about the version or the build. */
1541
+ interface CheckRequest {
1542
+ spec: string;
1543
+ /** The package manager of the command, when a command names one (the hook): its own registry settings apply. */
1544
+ tool?: ToolId;
1545
+ /** npm: the dist-tag npm prefers for a name without a version or with a range; the registry's default when left out. */
1546
+ tag?: string;
1547
+ /** The package manager is not npm, so tag= in .npmrc (CheckOptions.defaultTag) does not choose its version. */
1548
+ ignoresConfigTag?: boolean;
1549
+ /** Python: pre-releases allowed or forbidden; PEP 440's default when left out. */
1550
+ prereleases?: 'allow' | 'deny';
1551
+ /** Python: the package is built from source even when it has wheels (true), or only wheels are used (false). */
1552
+ buildFromSource?: boolean;
1553
+ /**
1554
+ * npm: the command line has an option that may set workspaces (--no-workspaces, --workspaces=false), with which npm
1555
+ * reads the .npmrc of a workspace package as its project configuration (decision D-124).
1556
+ */
1557
+ workspacesOption?: boolean;
1558
+ /**
1559
+ * Composer (decision D-133): the command does not read the composer.json of the directory it runs in (create-project,
1560
+ * global), so the repositories declared there do not apply to it.
1561
+ */
1562
+ readsManifest?: false;
1563
+ /**
1564
+ * Composer: composer update names the package without a constraint, so the constraint the root composer.json
1565
+ * requires applies; a package that composer.json does not require is not checked.
1566
+ */
1567
+ fromManifest?: true;
1568
+ /**
1569
+ * Composer (decision D-136): the command word as written and the command of Composer it stands for, when it is not
1570
+ * that command's exact name (an alias, an abbreviation, another case), so that the scripts of the composer.json the
1571
+ * command runs with, which Composer then registers as commands, may make it stand for a script or for nothing.
1572
+ */
1573
+ composerCommand?: {
1574
+ word: string;
1575
+ command: string;
1576
+ };
1577
+ /**
1578
+ * Composer: picks the version at the minimum stability stable, without the minimum-stability and prefer-stable of
1579
+ * the root composer.json, for a file of a parent directory that Composer may not use (decision D-142).
1580
+ */
1581
+ rootStability?: false;
1582
+ }
1583
+ interface CheckOptions {
1584
+ policy: ResolvedPolicy;
1585
+ fetch: FetchLike;
1586
+ now: Date;
1587
+ registry: string;
1588
+ popular: PopularNames;
1589
+ /** Registries the project assigns to scopes (@scope:registry in .npmrc); those packages are not looked up. */
1590
+ scopeRegistries?: ReadonlyMap<string, string>;
1591
+ /** The Python package index pypi: packages are looked up in; PyPI when not given. */
1592
+ index?: string;
1593
+ /** Popular Python projects, by normalized name, for look-alike names. */
1594
+ popularPython?: PopularNames;
1595
+ /** npm: the dist-tag npm prefers when a request names none (tag in .npmrc); the registry's default when left out. */
1596
+ defaultTag?: string;
1597
+ /**
1598
+ * Whether a finding about install scripts quotes them (the default) or names them only, for the hook: their body is
1599
+ * written by the package's publisher and should not reach the model as part of depcurfew's reason.
1600
+ */
1601
+ scriptBodies?: boolean;
1602
+ /** Stops the lookups: checkSpecs then rejects with the signal's reason (a cancelled call of depcurfew mcp). */
1603
+ signal?: AbortSignal;
1604
+ /** Lookups shared with other checks of the same run (the two policies of the hook), by registry and name. */
1605
+ lookups?: LookupCache;
1606
+ /**
1607
+ * npm packages not to look up: their scope's registry is set in a file depcurfew does not take (decision D-113), so
1608
+ * the name may be private and the public registry may hold a look-alike. The package is reported as unverified.
1609
+ */
1610
+ skipLookup?: (name: string, tool: ToolId | undefined, request?: Omit<CheckRequest, 'spec'>) => boolean;
1611
+ /**
1612
+ * Composer: minimum-stability and prefer-stable of the root composer.json (decision D-136), for the requests that read
1613
+ * it; without them, the defaults (stable, prefer-stable off).
1614
+ */
1615
+ composerStability?: {
1616
+ minimum?: string;
1617
+ preferStable: boolean;
1618
+ file: string;
1619
+ };
1620
+ }
1621
+ /** A package version to look up; `ecosystem` is depcurfew's (npm, pypi, packagist), whose names the policy writes without a prefix, with pypi: and with composer:. */
1622
+ interface OsvPackage {
1623
+ ecosystem: string;
1624
+ name: string;
1625
+ version: string;
1626
+ }
1627
+ /** What OSV.dev says about one version: the ids of its MAL- records (none: no known malware), or why it was not checked. */
1628
+ type OsvVerdict = {
1629
+ status: 'checked';
1630
+ malicious: string[];
1631
+ } | {
1632
+ status: 'unchecked';
1633
+ reason: string;
1634
+ };
1635
+ interface OsvOptions {
1636
+ fetch: FetchLike;
1637
+ signal?: AbortSignal | undefined;
1638
+ /** Per request; OSV_TIMEOUT_MS when left out. */
1639
+ timeoutMs?: number;
1640
+ /** Largest answer read; the response limit of OSV.dev over HTTP/1.1 when left out. */
1641
+ maxBytes?: number;
1642
+ /** Most distinct versions looked up in one run; the others stay unchecked. MAX_OSV_PACKAGES when left out. */
1643
+ limit?: number;
1644
+ }
1645
+ /**
1646
+ * What OSV.dev says about each package version, in the order given. Each distinct version is asked about once; the
1647
+ * batches go one after the other, and once one fails the rest is not asked (OSV.dev is out of reach, or answers
1648
+ * wrong), so every version left is unchecked with that reason. When `options.signal` aborts, the promise rejects.
1649
+ */
1650
+ export declare function queryOsv(packages: readonly OsvPackage[], options: OsvOptions): Promise<OsvVerdict[]>;
1347
1651
  /** The SARIF levels: an error fails `doctor`, warnings and notes do not. */
1348
1652
  type Level = 'error' | 'warning' | 'note';
1349
1653
  interface Location {
@@ -1406,7 +1710,18 @@ interface RuleContext {
1406
1710
  projects?: readonly ProjectView[];
1407
1711
  /** How apply plans this directory, for the rules that compare with that plan (config-drift). */
1408
1712
  plan?: PlanOptions;
1713
+ /** doctor --osv (decision D-129): absent without it; see OsvDoctor. */
1714
+ osv?: OsvDoctor;
1409
1715
  }
1716
+ /**
1717
+ * What known-malicious gets with --osv: a list to fill with the locked versions (the first run, before OSV.dev is
1718
+ * asked), or the answers of OSV.dev by osvKey (the second run, which reports).
1719
+ */
1720
+ type OsvDoctor = {
1721
+ collect: OsvPackage[];
1722
+ } | {
1723
+ answers: ReadonlyMap<string, OsvVerdict>;
1724
+ };
1410
1725
  /** A directory listed in projects, as the rules at the repository root see it. */
1411
1726
  interface ProjectView {
1412
1727
  /** Relative to the repository root, as listed. */
@@ -1452,6 +1767,8 @@ interface CheckedTool {
1452
1767
  versionFrom?: string;
1453
1768
  /** A bot configuration in a directory listed in projects, which the bot does not read (it reads the root's, decision D-109). */
1454
1769
  unread?: true;
1770
+ /** A package manager that a CI setup step of the root sets up for the directories listed in projects (decisions D-137 and D-141). */
1771
+ setUpInCi?: true;
1455
1772
  }
1456
1773
  /** One check of the list of what doctor checked: a rule and what came of it. */
1457
1774
  interface RuleResult {
@@ -1501,7 +1818,7 @@ interface RootPlaces {
1501
1818
  stages: readonly number[];
1502
1819
  }[];
1503
1820
  }
1504
- declare const APPLY_WARNINGS: {
1821
+ export declare const APPLY_WARNINGS: {
1505
1822
  readonly 'nothing-to-configure': "depcurfew found no supported package manager or bot, so nothing is configured.";
1506
1823
  readonly 'environment-overrides': "apply --user: an environment variable makes a tool read another user file or none, or overrides a value apply writes there.";
1507
1824
  readonly 'depcurfew-outdated': "The project installs a newer depcurfew than the one running, which may not know settings that version writes.";
@@ -1523,12 +1840,15 @@ declare const APPLY_WARNINGS: {
1523
1840
  readonly 'cooldown-environment-only': "pip reads no project configuration, so the release-age delay has to be set in the environment where pip runs.";
1524
1841
  readonly 'exclude-pattern-unsupported': "A cooldown exception uses a wildcard the tool does not support, so it is not written.";
1525
1842
  readonly 'exclude-unsupported': "The tool has no setting for cooldown exceptions, so the release-age delay applies to every package.";
1843
+ readonly 'exclude-not-written': "The setting that holds the cooldown exceptions (Composer's config.policy.cooldown.ignore) has a value of a type the tool does not accept there, so apply leaves it as it is and does not add the exceptions; the release-age delay is written.";
1844
+ readonly 'exclude-entry-outside-policy': "A cooldown exception in a file without depcurfew's markers (Composer's config.policy.cooldown.ignore) exempts a package that cooldown.exclude does not; apply removes only the entries it wrote, recognised by their reason, and leaves this one.";
1526
1845
  readonly 'allow-pattern-unsupported': "An installScripts.allow entry is a pattern, but the tool takes package names only, so it is not written.";
1527
1846
  readonly 'deny-pattern-unsupported': "An installScripts.deny entry is a pattern, but the tool records denials by package name only, so it is not written.";
1528
1847
  readonly 'allow-all-unsupported': "The tool has no setting that allows every install script, so installScripts \"allow\" is not written.";
1529
- readonly 'allow-all-replaced': "A setting that allowed every install script (with any value the tool reads as true, also one that a YAML merge key brings in, or pnpm 10's dangerously-allow-all-builds in .npmrc) was set to false, because the policy denies install scripts.";
1848
+ readonly 'allow-all-replaced': "A setting that allowed every install script (with any value the tool reads as true, also one that a YAML merge key brings in, or pnpm 10's dangerously-allow-all-builds in .npmrc) was set to false, or replaced with the policy's allow-list where the setting is the list itself (Deno's allowScripts, Composer's allow-plugins, which then may be {}), because the policy denies install scripts.";
1530
1849
  readonly 'allow-list-entry-outside-policy': "An install-script allow-list in the repository (with apply --user, in a user file) lets a package run scripts the policy does not allow; apply does not remove it.";
1531
1850
  readonly 'deny-conflicts-with-file': "An entry in a tool's own list lets a package of installScripts.deny run its install scripts, or blocks a package of installScripts.allow; apply does not change entries it did not write.";
1851
+ readonly 'deny-needs-optional-dependencies': "A package of installScripts.deny takes its binary from an optional dependency (esbuild), but npm leaves optional dependencies out (omit=optional or optional=false in .npmrc, --omit=optional or --no-optional on an install of a CI job or Dockerfile, NPM_CONFIG_OMIT=optional), so the package cannot find its binary after that install.";
1532
1852
  readonly 'unexpected-value': "A setting apply adds to has a value of a type the tool does not accept there, so apply leaves it as it is and writes nothing into it.";
1533
1853
  readonly 'built-in-list-replaced': "Bun's built-in list of trusted packages is replaced by the policy's allow-list.";
1534
1854
  readonly 'scripts-not-enforceable': "The pinned (with apply --user, the installed) npm or pnpm, or another npm the repository runs (one a declared Node.js comes with, or a CI job or Dockerfile installs), runs every install script and has no allow-list, so \"deny\" cannot be enforced; also given for an npm the repository runs whose version depcurfew cannot tell, which counts as one that runs them.";
@@ -1543,16 +1863,17 @@ declare const APPLY_WARNINGS: {
1543
1863
  readonly 'build-switch-install-only': "Poetry applies its build switch when installing; poetry lock may still build source distributions.";
1544
1864
  readonly 'build-switch-environment-only': "The tool has no project setting that forbids building from source, so it has to be set in the environment where the tool runs.";
1545
1865
  readonly 'requirements-file-missing': "There is no requirements file to write pip options into.";
1546
- readonly 'manifest-missing': "A setting belongs in a project manifest (package.json, deno.json, pyproject.toml, Pipfile) that does not exist, and apply does not create manifests.";
1866
+ readonly 'manifest-missing': "A setting belongs in a project manifest (package.json, deno.json, pyproject.toml, Pipfile, composer.json) that does not exist, and apply does not create manifests.";
1547
1867
  readonly 'lockfile-out-of-date': "The lockfile records, or is checked against, a setting apply changed, so it has to be locked again.";
1548
1868
  readonly 'file-ignored-by-git': "A file apply writes matches a .gitignore rule, so the change may never reach the repository.";
1549
1869
  readonly 'bot-config-missing': "A bot has no configuration file, and depcurfew does not create one.";
1550
1870
  readonly 'bot-config-not-edited': "A bot configuration file is in a place or format depcurfew does not edit; a snippet shows what to add.";
1551
1871
  readonly 'bot-entries-missing': "The Dependabot configuration has no entries for the package managers in use.";
1552
- readonly 'bot-rule-shortens-delay': "A Renovate packageRules entry apply does not manage gives every package of a registry, or some update types, a shorter release-age delay than the one apply writes; Renovate uses it, and apply does not change it.";
1872
+ readonly 'bot-config-unreadable': "The Dependabot configuration uses a YAML alias, which Dependabot does not accept, so it reads no configuration from the file (the cooldown apply writes included), and depcurfew does not read the delays the file sets.";
1873
+ readonly 'bot-rule-shortens-delay': "A Renovate packageRules entry apply does not manage gives every package of a registry, Renovate manager or category in use, or the actions of the workflows, or some update types of them, a shorter release-age delay than the one apply writes; Renovate uses it, and apply does not change it.";
1553
1874
  readonly 'security-delay-unsupported': "Dependabot cannot delay security updates, so securityUpdates \"delay\" is not applied.";
1554
1875
  };
1555
- declare const APPLY_ERRORS: {
1876
+ export declare const APPLY_ERRORS: {
1556
1877
  readonly 'cannot-edit': "A file cannot be read or edited safely (invalid syntax, a symlink, a style depcurfew does not edit), so nothing is written.";
1557
1878
  readonly 'cannot-write': "A file cannot be written (no permission, a read-only file system, a full disk, a directory or symlink in its place); the files already written are put back, so nothing changes.";
1558
1879
  readonly 'policy-missing': "The directory has no depcurfew.json.";
@@ -1589,7 +1910,7 @@ interface ApplyWarning {
1589
1910
  fix?: string;
1590
1911
  }
1591
1912
  /** A warning as one line of text, as ApplyReport.warnings has it. */
1592
- declare function warningText(warning: ApplyWarning): string;
1913
+ export declare function warningText(warning: ApplyWarning): string;
1593
1914
  interface FileChange {
1594
1915
  /** Repository-relative path with `/` separators. */
1595
1916
  file: string;
@@ -1631,6 +1952,8 @@ interface EditFailure {
1631
1952
  tool: ToolId;
1632
1953
  file: string;
1633
1954
  message: string;
1955
+ /** What to do, when the generic fix of cannot-edit does not say it. */
1956
+ fix?: string;
1634
1957
  }
1635
1958
  /** Why a directory listed in projects cannot be used; nothing is written and doctor reports it (invalid-project). */
1636
1959
  interface ProjectProblem {
@@ -1696,12 +2019,12 @@ interface PlanOptions {
1696
2019
  root?: RootPlaces;
1697
2020
  }
1698
2021
  /** The bots' delay: the policy's, or longer if a package manager rounded its delay up further. */
1699
- declare function botDuration(policy: Duration, managerMinutes: readonly number[]): Duration;
2022
+ export declare function botDuration(policy: Duration, managerMinutes: readonly number[]): Duration;
1700
2023
  /**
1701
2024
  * Computes every change of one directory without writing anything. With projects in the policy, `options` says which
1702
2025
  * part of the repository this directory is (planRepository plans them all).
1703
2026
  */
1704
- declare function planApply(root: string, policy: ResolvedPolicy, detection: Detection, tools?: readonly ToolKnowledge[], options?: PlanOptions): ApplyReport;
2027
+ export declare function planApply(root: string, policy: ResolvedPolicy, detection: Detection, tools?: readonly ToolKnowledge[], options?: PlanOptions): ApplyReport;
1705
2028
  /**
1706
2029
  * Plans the whole repository: the root as planApply does, and each directory listed in projects as a project of its
1707
2030
  * own with the same policy (decision D-100). Bots are configured only from the root, with a delay at least as long
@@ -1709,8 +2032,8 @@ declare function planApply(root: string, policy: ResolvedPolicy, detection: Dete
1709
2032
  * used is an error (`problems`), so that nothing is written; the other directories are still planned, so one run
1710
2033
  * reports everything.
1711
2034
  */
1712
- declare function planRepository(root: string, policy: ResolvedPolicy, tools?: readonly ToolKnowledge[], rootDetection?: Detection): ApplyReport;
1713
- declare class PolicyError extends Error {
2035
+ export declare function planRepository(root: string, policy: ResolvedPolicy, tools?: readonly ToolKnowledge[], rootDetection?: Detection): ApplyReport;
2036
+ export declare class PolicyError extends Error {
1714
2037
  readonly issues: string[];
1715
2038
  readonly code: ApplyErrorCode;
1716
2039
  /** What to do, when the message does not say it. */
@@ -1721,12 +2044,12 @@ declare class PolicyError extends Error {
1721
2044
  /** The message and what to do, as one line of text. */
1722
2045
  get text(): string;
1723
2046
  }
1724
- declare function loadPolicy(root: string): ResolvedPolicy;
2047
+ export declare function loadPolicy(root: string): ResolvedPolicy;
1725
2048
  /**
1726
2049
  * A file could not be written (code cannot-write). The files apply had already replaced were put back, so the
1727
2050
  * repository is as it was, unless `unrestored` lists files. `report` is the plan that was being written.
1728
2051
  */
1729
- declare class ApplyWriteError extends Error {
2052
+ export declare class ApplyWriteError extends Error {
1730
2053
  readonly code: "cannot-write";
1731
2054
  /** The file that could not be written, relative to the repository. */
1732
2055
  readonly file: string;
@@ -1749,104 +2072,16 @@ declare class ApplyWriteError extends Error {
1749
2072
  * could not be edited, nothing is written, so a run either applies the whole policy or leaves the repository as it was.
1750
2073
  * A file that cannot be written throws ApplyWriteError after the files already written are put back.
1751
2074
  */
1752
- declare function applyProject(root: string, options?: {
2075
+ export declare function applyProject(root: string, options?: {
1753
2076
  write?: boolean;
1754
2077
  beforeWrite?: () => void;
1755
2078
  }): ApplyReport;
1756
- interface SimilarName {
1757
- /** The popular package the name resembles. */
1758
- match: string;
1759
- /** edit: a letter or two differ; separators: only -, _ or . differ. */
1760
- reason: 'edit' | 'separators';
1761
- }
1762
- declare class PopularNames {
1763
- #private;
1764
- constructor(names: readonly string[]);
1765
- has(name: string): boolean;
1766
- /** The popular package `name` resembles, or undefined (also when `name` is itself popular). */
1767
- similar(name: string): SimilarName | undefined;
1768
- }
1769
- type SpecKind = 'registry' | 'git' | 'remoteTarball' | 'file' | 'directory' | 'workspace' | 'other';
1770
- type FindingCode = 'invalid-spec' | 'forbidden-source' | 'unchecked-source' | 'unverified' | 'not-found' | 'no-version' | 'too-young' | 'install-scripts' | 'similar-name' | 'deprecated' | 'yanked' | 'archived' | 'quarantined' | 'insecure-registry';
1771
- interface CheckFinding {
1772
- level: 'error' | 'warning';
1773
- code: FindingCode;
1774
- message: string;
1775
- /** What to do about it. */
1776
- fix: string;
1777
- }
1778
- interface PackageReport {
1779
- spec: string;
1780
- kind: SpecKind | 'invalid';
1781
- name?: string;
1782
- version?: string;
1783
- publishedAt?: string;
1784
- latest?: {
1785
- version: string;
1786
- publishedAt?: string;
1787
- };
1788
- /** npm: the dist-tag the version was picked by, when it is not the default one (--tag, npm_config_tag, tag in .npmrc). */
1789
- tag?: string;
1790
- /** Python: pre-releases allowed or forbidden by the command. */
1791
- prereleases?: 'allow' | 'deny';
1792
- /** Python: the command builds the package from source (true) or installs wheels only (false). */
1793
- buildFromSource?: boolean;
1794
- findings: CheckFinding[];
1795
- }
1796
- /** A spec with what the install command changes about the version or the build. */
1797
- interface CheckRequest {
1798
- spec: string;
1799
- /** The package manager of the command, when a command names one (the hook): its own registry settings apply. */
1800
- tool?: ToolId;
1801
- /** npm: the dist-tag npm prefers for a name without a version or with a range; the registry's default when left out. */
1802
- tag?: string;
1803
- /** The package manager is not npm, so tag= in .npmrc (CheckOptions.defaultTag) does not choose its version. */
1804
- ignoresConfigTag?: boolean;
1805
- /** Python: pre-releases allowed or forbidden; PEP 440's default when left out. */
1806
- prereleases?: 'allow' | 'deny';
1807
- /** Python: the package is built from source even when it has wheels (true), or only wheels are used (false). */
1808
- buildFromSource?: boolean;
1809
- /**
1810
- * npm: the command line has an option that may set workspaces (--no-workspaces, --workspaces=false), with which npm
1811
- * reads the .npmrc of a workspace package as its project configuration (decision D-124).
1812
- */
1813
- workspacesOption?: boolean;
1814
- }
1815
- interface CheckOptions {
1816
- policy: ResolvedPolicy;
1817
- fetch: FetchLike;
1818
- now: Date;
1819
- registry: string;
1820
- popular: PopularNames;
1821
- /** Registries the project assigns to scopes (@scope:registry in .npmrc); those packages are not looked up. */
1822
- scopeRegistries?: ReadonlyMap<string, string>;
1823
- /** The Python package index pypi: packages are looked up in; PyPI when not given. */
1824
- index?: string;
1825
- /** Popular Python projects, by normalized name, for look-alike names. */
1826
- popularPython?: PopularNames;
1827
- /** npm: the dist-tag npm prefers when a request names none (tag in .npmrc); the registry's default when left out. */
1828
- defaultTag?: string;
1829
- /**
1830
- * Whether a finding about install scripts quotes them (the default) or names them only, for the hook: their body is
1831
- * written by the package's publisher and should not reach the model as part of depcurfew's reason.
1832
- */
1833
- scriptBodies?: boolean;
1834
- /** Stops the lookups: checkSpecs then rejects with the signal's reason (a cancelled call of depcurfew mcp). */
1835
- signal?: AbortSignal;
1836
- /** Lookups shared with other checks of the same run (the two policies of the hook), by registry and name. */
1837
- lookups?: LookupCache;
1838
- /**
1839
- * npm packages not to look up: their scope's registry is set in a file depcurfew does not take (decision D-113), so
1840
- * the name may be private and the public registry may hold a look-alike. The package is reported as unverified.
1841
- */
1842
- skipLookup?: (name: string, tool: ToolId | undefined, request?: Omit<CheckRequest, 'spec'>) => boolean;
1843
- }
1844
- declare function checkSpec(request: string | CheckRequest, options: CheckOptions): Promise<PackageReport>;
2079
+ export declare function checkSpec(request: string | CheckRequest, options: CheckOptions): Promise<PackageReport>;
1845
2080
  /**
1846
2081
  * Checks the specs a few at a time; the reports keep the order of the specs. When `options.signal` aborts, no further
1847
2082
  * spec is looked up and the promise rejects with the signal's reason.
1848
2083
  */
1849
- declare function checkSpecs(specs: readonly (string | CheckRequest)[], options: CheckOptions): Promise<PackageReport[]>;
2084
+ export declare function checkSpecs(specs: readonly (string | CheckRequest)[], options: CheckOptions): Promise<PackageReport[]>;
1850
2085
  interface Paint {
1851
2086
  red(text: string): string;
1852
2087
  yellow(text: string): string;
@@ -1866,11 +2101,16 @@ interface CheckDependencies {
1866
2101
  projectDir?: () => string | undefined;
1867
2102
  /** --user: platform, home directory and environment variables (tests replace them). */
1868
2103
  userEnvironment?: () => UserEnvironment;
2104
+ /**
2105
+ * The platform the hook runs on: on Windows a POSIX shell (Git Bash) hands its variables to programs that do not tell
2106
+ * their names apart by case (decision D-143); tests replace it.
2107
+ */
2108
+ platform?: () => NodeJS.Platform;
1869
2109
  /** depcurfew mcp: the streams the server talks over, its environment and signals (tests replace them). */
1870
2110
  mcpProcess?: () => McpProcess;
1871
2111
  }
1872
- declare function run(argv: readonly string[], output: CliIo, dependencies?: CheckDependencies): Promise<number>;
1873
- declare function getVersion(): string;
2112
+ export declare function run(argv: readonly string[], output: CliIo, dependencies?: CheckDependencies): Promise<number>;
2113
+ export declare function getVersion(): string;
1874
2114
  interface DiffOptions {
1875
2115
  /** Shows the values of secrets as <redacted> in every line (redactSecrets), for a diff that people or logs see. */
1876
2116
  redact?: boolean;
@@ -1881,7 +2121,7 @@ interface DiffOptions {
1881
2121
  * and forge a header or a hunk. A file with an absolute path (a user file outside the home directory) is named as it
1882
2122
  * is, without the a/ and b/ of repository paths.
1883
2123
  */
1884
- declare function unifiedDiff(file: string, before: string | null, after: string, context?: number, options?: DiffOptions): string;
2124
+ export declare function unifiedDiff(file: string, before: string | null, after: string, context?: number, options?: DiffOptions): string;
1885
2125
  /**
1886
2126
  * Where a command runs, relative to the directory the command line starts in: the directories it changes to, in order
1887
2127
  * (an absolute one starts over). `localPrefix`: npm --prefix names npm's local prefix, whose .npmrc npm reads without
@@ -1993,6 +2233,15 @@ interface InstallRequest {
1993
2233
  buildFromSource?: boolean;
1994
2234
  /** Where the install runs, when the command changes directory before it or moves it with an option (decision D-118). */
1995
2235
  directory?: CommandDirectory;
2236
+ /** Composer: the command does not read the composer.json of the directory it runs in (create-project, global; decision D-133). */
2237
+ readsManifest?: false;
2238
+ /** Composer: composer update names the package without a constraint, so the one of the root composer.json applies. */
2239
+ fromManifest?: true;
2240
+ /** Composer: the command word, when the scripts of composer.json may change the command it stands for (decision D-136). */
2241
+ composerCommand?: {
2242
+ word: string;
2243
+ command: string;
2244
+ };
1996
2245
  }
1997
2246
  /** An option or variable of a command that weakens the release-age delay or the rule on install scripts (installOverrides). */
1998
2247
  interface Override {
@@ -2002,10 +2251,16 @@ interface Override {
2002
2251
  /** As written: --min-release-age=0, npm_config_ignore_scripts=false. */
2003
2252
  written: string;
2004
2253
  weakens: OverrideSwitch['weakens'];
2254
+ /** protection: what it turns off (OverrideSwitch.effect). */
2255
+ effect?: string;
2256
+ /** protection: what that protection does, when it covers less than every package the command fetches (OverrideSwitch.protects). */
2257
+ protects?: string;
2005
2258
  /** The delay it sets, to compare with the policy; left out when any value weakens it. */
2006
2259
  delay?: string;
2007
2260
  /** The shell expands its value. */
2008
2261
  expanded?: boolean;
2262
+ /** A shell builtin or another form sets it, whose value depcurfew does not read (decision D-143); written is the name. */
2263
+ unread?: true;
2009
2264
  /** Where its command runs, when that is not where the command line starts (decision D-118). */
2010
2265
  directory?: CommandDirectory;
2011
2266
  }
@@ -2025,16 +2280,21 @@ interface CommandScan {
2025
2280
  interface ScanOptions {
2026
2281
  /** The shell that runs the line: a POSIX shell (the default; also Git Bash) or PowerShell. */
2027
2282
  shell?: 'posix' | 'powershell';
2283
+ /**
2284
+ * The line runs on Windows (by default where depcurfew runs), whose programs do not tell the names of environment
2285
+ * variables apart by case, so a POSIX shell's Composer=… sets COMPOSER there too (decision D-143).
2286
+ */
2287
+ windows?: boolean;
2028
2288
  }
2029
2289
  /** The packages a command line installs or downloads to run with npm, pnpm, Yarn, Bun, Deno, pip, uv, Poetry, PDM, Pipenv or pipx. */
2030
- declare function scanCommandLine(line: string, options?: ScanOptions): CommandScan;
2031
- /**
2032
- * Describes the repository at `root`. Never executes anything from the repository and never leaves it;
2033
- * problems with individual files become warnings.
2034
- */
2035
- declare function detectProject(root: string, tools?: readonly ToolKnowledge[], environment?: EnvironmentKnowledge): Detection;
2290
+ export declare function scanCommandLine(line: string, options?: ScanOptions): CommandScan;
2291
+ interface DetectOptions {
2292
+ /** The directories listed in projects in depcurfew.json, relative to the root (decision D-137). */
2293
+ projects?: readonly string[];
2294
+ }
2295
+ export declare function detectProject(root: string, tools?: readonly ToolKnowledge[], environment?: EnvironmentKnowledge, options?: DetectOptions): Detection;
2036
2296
  /** Every doctor rule, in the order they are documented. unreadable-config runs last, after every other rule has read its files. */
2037
- declare const RULES: readonly Rule[];
2297
+ export declare const RULES: readonly Rule[];
2038
2298
  type FailOn = (typeof FAIL_ON_LEVELS)[number];
2039
2299
  /** Where the setting came from: the command line (or the Action input), doctor.failOn of the policy, or neither. */
2040
2300
  type FailOnFrom = 'option' | 'policy' | 'default';
@@ -2046,12 +2306,12 @@ interface FailOnSetting {
2046
2306
  /** from policy: the policy file as messages show it, e.g. depcurfew.json. */
2047
2307
  file?: string;
2048
2308
  }
2049
- declare function countLevels(findings: readonly Finding[]): Record<'error' | 'warning' | 'note', number>;
2309
+ export declare function countLevels(findings: readonly Finding[]): Record<'error' | 'warning' | 'note', number>;
2050
2310
  /**
2051
2311
  * `failOn`: which findings fail doctor and where that was set (decision D-112); when warnings make it fail, a line under
2052
2312
  * the counts says why, with how to fail on errors only. `strict` alone (before D-112) adds STRICT_NOTE.
2053
2313
  */
2054
- declare function renderText(report: DoctorReport, rules: readonly Rule[], colors?: Paint, options?: {
2314
+ export declare function renderText(report: DoctorReport, rules: readonly Rule[], colors?: Paint, options?: {
2055
2315
  strict?: boolean;
2056
2316
  failOn?: FailOnSetting;
2057
2317
  }): string;
@@ -2063,7 +2323,7 @@ interface SarifError {
2063
2323
  message: string;
2064
2324
  fix?: string;
2065
2325
  }
2066
- declare function toSarif(report: DoctorReport, rules: readonly Rule[], version: string, readLine?: LineReader, errors?: readonly SarifError[]): object;
2326
+ export declare function toSarif(report: DoctorReport, rules: readonly Rule[], version: string, readLine?: LineReader, errors?: readonly SarifError[]): object;
2067
2327
  /**
2068
2328
  * Runs the rules on a repository. When depcurfew.json exists but is invalid, it throws PolicyError, or with
2069
2329
  * invalidPolicy: 'report' runs the rules that need no policy and reports the problems as an invalid-policy finding.
@@ -2071,12 +2331,18 @@ declare function toSarif(report: DoctorReport, rules: readonly Rule[], version:
2071
2331
  * policy, the rules also run on every listed directory with the same policy, and the report covers them all, with
2072
2332
  * paths relative to the root (decision D-100).
2073
2333
  */
2074
- declare function runDoctor(root: string, options?: {
2334
+ export declare function runDoctor(root: string, options?: {
2075
2335
  rules?: readonly Rule[];
2076
2336
  tools?: readonly ToolKnowledge[];
2077
2337
  invalidPolicy?: 'throw' | 'report';
2078
2338
  policy?: ResolvedPolicy | null;
2339
+ osv?: OsvDoctor;
2079
2340
  }): DoctorReport;
2341
+ /**
2342
+ * runDoctor with known-malicious: the locked versions are looked up in OSV.dev first. Throws PolicyError as runDoctor
2343
+ * does for an invalid depcurfew.json, unless asked to report it.
2344
+ */
2345
+ export declare function runDoctorWithOsv(root: string, options: Omit<Parameters<typeof runDoctor>[1] & object, 'osv'>, osv: OsvOptions): Promise<DoctorReport>;
2080
2346
  type NumericDelayUnit = Exclude<DelayUnit, 'duration-string'>;
2081
2347
  declare const DURATION_STRING_UNITS: readonly ["minutes", "hours", "days", "weeks"];
2082
2348
  type DurationStringUnit = (typeof DURATION_STRING_UNITS)[number];
@@ -2097,19 +2363,19 @@ type CooldownConversion = {
2097
2363
  reason: string;
2098
2364
  fix: string;
2099
2365
  };
2100
- declare function convertMinutes(minutes: number, unit: NumericDelayUnit): Converted<number>;
2366
+ export declare function convertMinutes(minutes: number, unit: NumericDelayUnit): Converted<number>;
2101
2367
  /** Writes minutes as a duration string such as `36 hours` or `3 days`. */
2102
- declare function toDurationString(minutes: number, units: readonly DurationStringUnit[]): Converted<string>;
2368
+ export declare function toDurationString(minutes: number, units: readonly DurationStringUnit[]): Converted<string>;
2103
2369
  /**
2104
2370
  * Converts the policy's minimum release age into the value for one tool. When official sources disagree on the
2105
2371
  * unit, the smallest unit wins: its larger number is long enough under every reading.
2106
2372
  */
2107
- declare function convertCooldown(tool: ToolKnowledge, duration: Duration): CooldownConversion;
2108
- declare const POLICY_FILE = "depcurfew.json";
2373
+ export declare function convertCooldown(tool: ToolKnowledge, duration: Duration): CooldownConversion;
2374
+ export declare const POLICY_FILE = "depcurfew.json";
2109
2375
  /** Where the published package serves schema.json; init writes it into `$schema`. */
2110
- declare const SCHEMA_URL = "https://unpkg.com/depcurfew/schema.json";
2376
+ export declare const SCHEMA_URL = "https://unpkg.com/depcurfew/schema.json";
2111
2377
  /** What `depcurfew init` writes. Every section is explicit, so the file shows what can be configured. */
2112
- declare const DEFAULT_POLICY: {
2378
+ export declare const DEFAULT_POLICY: {
2113
2379
  readonly $schema: "https://unpkg.com/depcurfew/schema.json";
2114
2380
  readonly version: 1;
2115
2381
  readonly cooldown: {
@@ -2133,7 +2399,7 @@ declare const DEFAULT_POLICY: {
2133
2399
  readonly failOn: "warning";
2134
2400
  };
2135
2401
  };
2136
- declare function renderPolicy(policy: Policy): string;
2402
+ export declare function renderPolicy(policy: Policy): string;
2137
2403
  type PolicyIssue = Issue;
2138
2404
  type PolicyResult = {
2139
2405
  ok: true;
@@ -2143,8 +2409,8 @@ type PolicyResult = {
2143
2409
  issues: PolicyIssue[];
2144
2410
  };
2145
2411
  /** Validates a parsed policy document and fills in defaults. Reports every problem, each with the path to its field. */
2146
- declare function validatePolicy(document: unknown, tools?: readonly ToolKnowledge[]): PolicyResult;
2412
+ export declare function validatePolicy(document: unknown, tools?: readonly ToolKnowledge[]): PolicyResult;
2147
2413
  /** Parses and validates the text of a policy file. */
2148
- declare function parsePolicy(source: string, tools?: readonly ToolKnowledge[]): PolicyResult;
2149
- declare function formatIssue(issue: PolicyIssue): string;
2150
- export { APPLY_ERRORS, APPLY_WARNINGS, type ApplyErrorCode, type ApplyProblem, type ApplyReport, type ApplyWarning, type ApplyWarningCode, ApplyWriteError, type CheckFinding, type CheckOptions, type CliIo, type CommandDirectory, type CommandScan, type Converted, type CooldownConversion, DEFAULT_POLICY, type Detection, type DetectionWarning, type DoctorReport, type Duration, type DurationResult, type DurationStringUnit, EXIT_FAILURE, EXIT_OK, EXIT_USAGE, type FileChange, type Finding, type FindingCode, type InstallRequest, type KnownDirectory, type Level, type Location, type Notice, type NumericDelayUnit, POLICY_FILE, type PackageManagerInfo, type PackageReport, type PlanOptions, type Policy, PolicyError, type PolicyIssue, type PolicyResult, PopularNames, type ProjectProblem, RULES, type ResolvedPolicy, type Rule, SCHEMA_URL, type ScanOptions, type Snippet, type VersionDeclaration, applyProject, botDuration, checkSpec, checkSpecs, convertCooldown, convertMinutes, countLevels, detectProject, formatDuration, formatIssue, getVersion, loadPolicy, parseDuration, parsePolicy, planApply, planRepository, renderPolicy, renderText, run, runDoctor, scanCommandLine, toDurationString, toSarif, unifiedDiff, validatePolicy, warningText };
2414
+ export declare function parsePolicy(source: string, tools?: readonly ToolKnowledge[]): PolicyResult;
2415
+ export declare function formatIssue(issue: PolicyIssue): string;
2416
+ export type { ApplyErrorCode, ApplyProblem, ApplyReport, ApplyWarning, ApplyWarningCode, CheckFinding, CheckOptions, CliIo, CommandDirectory, CommandScan, Converted, CooldownConversion, DetectOptions, Detection, DetectionWarning, DoctorReport, Duration, DurationResult, DurationStringUnit, FileChange, Finding, FindingCode, InstallRequest, KnownDirectory, Level, Location, Notice, NumericDelayUnit, OsvOptions, OsvPackage, OsvVerdict, PackageManagerInfo, PackageReport, PlanOptions, Policy, PolicyIssue, PolicyResult, ProjectProblem, ResolvedPolicy, Rule, ScanOptions, Snippet, VersionDeclaration };