depcurfew v1.6.1
On this page

The policy file

Every section is optional: a section that is absent means depcurfew does not manage that area. Durations are converted to each tool's own unit and always rounded up, so a tool never waits less than the policy says. Where a tool cannot express part of the policy, depcurfew says so instead of skipping it silently.

FieldValuesDefaultMeaning
versionVersion of the policy format.
cooldownRelease-age delay (cooldown): freshly published versions wait before they are used.
cooldown.minimumMinimum age of a release before it is installed or proposed: a whole number followed by m (minutes), h (hours), d (days) or w (weeks). Converted to each tool's unit, always rounding up.
cooldown.exclude[]Packages that skip the release-age delay, e.g. your own scope.
cooldown.securityUpdates"bypass", "delay""bypass"Whether security updates skip the delay ("bypass") or wait like other updates ("delay"), where the tool can tell them apart.
installScriptsCode that dependencies run when they are installed: lifecycle scripts in npm, building from source in Python, plugins in Composer.
installScripts.default"allow", "deny"Whether dependency lifecycle scripts (preinstall, install, postinstall) run by default; for Python, whether packages may be built from source; for Composer, whether plugins may run.
installScripts.allow[]Packages whose lifecycle scripts may run when the default is "deny"; Python packages listed here are always built from source; Composer packages listed here are the plugins allow-plugins lets run.
installScripts.deny[]Packages you reviewed that work without their lifecycle scripts, so the scripts stay blocked on purpose and doctor does not report them, for example "fsevents", which ships a prebuilt binary. depcurfew init fills it with the packages of the lockfile whose scripts depcurfew knows to be unneeded (esbuild, fsevents) or to do only a task of the project (lefthook). A package cannot be in both lists, also not through two patterns that match the same name. Python packages listed here must not be built from source: doctor reports a tool that would still build one. Composer packages listed here are plugins that allow-plugins disallows.
sourcesWhere dependencies may come from besides the registry. An absent field is not managed.
sources.git"allow", "deny"Dependencies installed from git repositories.
sources.remoteTarball"allow", "deny"Dependencies installed from tarball URLs outside the registry.
sources.file"allow", "deny"Dependencies installed from local tarball files.
sources.directory"allow", "deny"Dependencies installed from local directories.
targets"auto"Tools to configure: "auto" detects them in the repository, or list them explicitly.
projects[]Directories of independent projects in this repository, each with its own manifest and lockfile (e.g. frontend and backend). depcurfew configures each one with this policy as a project of its own, and configures Renovate and Dependabot once, from the repository root, for all of them. The repository root is always configured as well. A project must not be inside another one, must not be a workspace package of the root and must not have a depcurfew.json of its own.
doctorHow depcurfew doctor and the GitHub Action treat findings. Without this section only errors fail, as before; depcurfew init writes "failOn": "warning".
doctor.failOn"error", "warning"Which findings make depcurfew doctor exit with 1 and fail the GitHub Action: "error" (only errors, also when left out) or "warning" (warnings too). Notes never fail. doctor --fail-on and the input fail-on of the Action override it.

doctor.failOn decides which findings make depcurfew doctor exit with 1 and fail the GitHub Action: "error" only errors, "warning" warnings too; notes, which say what doctor could not check, never fail. depcurfew init writes "warning", so a new repository stays free of warnings. A policy without the doctor section fails on errors only, as before, so an existing depcurfew.json keeps its exit codes. doctor --fail-on error or --fail-on warning and the input fail-on of the Action override it for one run; an invalid depcurfew.json counts as "error" (it fails doctor anyway, as invalid-policy). The user policy takes the same section for doctor --user.

Several projects in one repository

A repository can hold independent projects side by side, each in its own directory with its own manifest and lockfile, often with nothing in the root but the bot configuration:

.github/dependabot.yml   updates entries for /frontend and /backend
depcurfew.json           "projects": ["frontend", "backend"]
frontend/                package.json, package-lock.json
backend/                 pyproject.toml, uv.lock

List those directories under projects, relative to the repository root with / between directories (no patterns, no .., no / at either end). apply, doctor and explain then treat each listed directory as a project of its own with the policy of the root: its own .npmrc, lockfiles and workspaces, as if the command ran there. The root is configured as before. Renovate and Dependabot are configured once, from the repository root, where they read their configuration, and wait at least as long as the package manager of any project (in the example, npm rounds 36h up to 2 days, so every Dependabot entry gets default-days: 2). Every path in the output is relative to the root (frontend/.npmrc), and all files of all projects are written or none. A renovate.json or .github/dependabot.yml inside a project is never read by the bot, so doctor gives a note at it instead of comparing its delay (and says so when the root has no configuration of that bot at all), and "What was checked" marks it as not read; a project that the Dependabot configuration of the root leaves out while it updates other projects gets a warning.

Detection of package managers reads only the root of the repository, the packages of its workspaces and the directories listed under projects, so a project elsewhere, for example frontend/ beside a uv project in the root, is not configured or checked until it is listed in projects: apply writes nothing for it, and doctor reports nothing about it, its CI jobs or its Dockerfile. init proposes direct subdirectories for projects only when the root has no manifest or lockfile.

What the root gives a project, for the npm it runs: the CI jobs of the root that install in its directory (working-directory: frontend, or a literal cd frontend before the install in the same shell); the .nvmrc, .node-version and .tool-versions of the root (nvm, nodenv and asdf use those files in subdirectories too), each unless the project has a file of the same kind, because each tool reads only its own file: with frontend/.nvmrc, asdf still uses the root .tool-versions in frontend; and the stages of a root Dockerfile that copy files only from that project (COPY frontend/package.json frontend/package-lock.json ./). Copy sources are read as Docker reads them, from the root of the build context with . and .. resolved (COPY frontend/../ . copies the whole context, /frontend/x is frontend/x). A stage that copies . or files of several directories is the root's, because it is not clearly a build of one project, except an install after a literal cd frontend (RUN cd frontend && npm ci), which is the project's. A root CI job or Dockerfile stage that installs another npm (npm install -g npm@<version>) before installing in a project is checked with the settings of that project. Volta, devEngines and engines in the root package.json do not count for a project, which has a package.json of its own. explain lists, for each project, the root version files and root Dockerfile stages it uses (fromRoot in --json). A version a setup step of a root CI job installs (astral-sh/setup-uv with version: 0.13.2) is the project's too when every run step of that job works in its directory (working-directory: backend or defaults.run.working-directory), so apply configures the project for it.

A package manager that the root shows only through a CI setup step (such as pnpm/action-setup or oven-sh/setup-bun in a workflow of the root) is set up there for the projects, not used by the root, when the root has no manifest of any tool of its registry (for pnpm, Bun and Deno no package.json, package.json5, package.yaml, deno.json or deno.jsonc; for uv and Poetry no pyproject.toml, requirements.txt or Pipfile), or when every install with it in the root's workflows runs in a listed directory (by working-directory or a literal cd) and no Dockerfile of the root installs with it. Then apply writes nothing for it in the root, doctor does not list it among the tools of the root or compare Renovate and Dependabot with it, and explain lists it under "Package managers set up in CI for projects" (ciForProjects in --json); manager-version-too-old still checks the version the step installs. When the root has none of its manifests and no project uses it either, apply names it in tool-only-in-ci as before. Without projects such a step counts for the root, as before.

An npm depcurfew cannot tell (a Node.js version newer than the releases it knows, an image pinned only by digest, a version it cannot read) counts as one that runs every dependency install script: apply warns with scripts-not-enforceable and doctor reports packages with install scripts as errors, so an unknown npm never passes for one that blocks them. The npm of every Node.js release down to 0.6.3 is known from nodejs.org/dist/index.json; every release before 18.0.0 comes with npm 8 or older.

depcurfew init in a root without a manifest or lockfile lists the direct subdirectories that have one, except one with a depcurfew.json of its own, which it names and leaves out. init also leaves out a git submodule or another repository of its own (a directory with its own .git file or directory). A listed directory that does not exist, is a file, is reached through a symlink, is a separate git repository or lies in one, has a depcurfew.json of its own or overlaps a workspace package of the root is an error (project-not-found, project-not-a-directory, project-symlink, project-is-repository, project-has-policy, project-in-workspace) at the line of its entry in depcurfew.json: apply writes nothing, explain shows it and exits with 1, and doctor reports it as invalid-project. With a list of targets, each listed package manager is configured only in the directories that use it. check and the Claude Code hook find the depcurfew.json of the root from a project directory, as from any subdirectory of the repository.

JSON Schema

schema.json is the JSON Schema of depcurfew.json, the file the npm package ships. schema/v1.json is the same file at an address that stays with version 1 of the policy format.