package.json — "lpm" key
Reference for the LPM CLI-specific configuration block inside package.json.
LPM CLI reads its project-level configuration from a "lpm" block inside the project's package.json. Settings here are committed alongside dependencies, so the whole team picks them up automatically.
Every package.json read by LPM CLI has a 16 MiB limit enforced before JSON parsing. An oversized root or workspace-member manifest is a path-specific error, not a missing manifest or an empty configuration, and install fails before dependency resolution or registry access. See local configuration size limits for the complete scope and exclusions.
Project-only. Every key in this block — scriptPolicy, trustedDependencies, minimumReleaseAge, minimumReleaseAgePolicy, scripts.autoBuild, linker, the rest — applies to lpm install (and other commands that run in the project context). lpm install -g does not read this file: globals synthesize their own package.json and use the equivalent CLI flags plus ~/.lpm/config.toml for the user-config tier. Global trust lives in a separate file at ~/.lpm/global/trusted-dependencies.json.
Security-sensitive project keys such as scriptPolicy, minimumReleaseAge, and minimumReleaseAgePolicy are treated as project proposals, not machine authority. If a repo asks for a weaker posture than this machine currently allows, install or rebuild fails and points you at lpm security unlock or a persistent lpm config change.
A few related fields live at the top level of package.json (not inside "lpm") — workspaces, engines, scripts, bin, and overrides / resolutions. Those follow npm/pnpm/yarn conventions and are listed at the bottom.
{
"name": "my-app",
"version": "1.0.0",
"engines": { "node": ">=22.0.0", "lpm": ">=0.32.0" },
"workspaces": ["packages/*", "apps/*"],
"lpm": {
"linker": "isolated",
"strictDeps": "warn",
"strictPeerDependencies": false,
"catalogMode": "manual",
"cleanupUnusedCatalogs": false,
"scriptPolicy": "deny",
"triageAdvisor": "none",
"trustedDependencies": ["esbuild", "sharp"],
"minimumReleaseAge": 86400,
"minimumReleaseAgePolicy": "direct",
"minimumReleaseAgeExclude": ["react", "@scope/pkg"],
"engineStrict": true,
"overrides": {
"lodash": "^4.17.21",
"react@<18": "18.0.0"
},
"patchedDependencies": {
"lodash@4.17.21": {
"path": "patches/lodash@4.17.21.patch",
"originalIntegrity": "sha512-..."
}
},
"scripts": {
"autoBuild": false,
"denyAll": false,
"trustedScopes": ["@myorg/*"],
"passEnv": ["GITHUB_TOKEN"],
"readProject": "narrow",
"sandboxLimits": {
"RLIMIT_AS": 4294967296,
"RLIMIT_NPROC": 64,
"RLIMIT_NOFILE": 1024,
"RLIMIT_CPU": 60
},
"sandboxWriteDirs": ["build/", "dist/"]
}
}
}Top-level keys under "lpm"
linker
{ "lpm": { "linker": "isolated" } }node_modules materialization style.
| Value | Meaning |
|---|---|
"isolated" | pnpm-style strict layout — each package sees only its declared deps |
"hoisted" | LPM CLI v2 hoisted virtual-store layout — root direct deps are surfaced at project node_modules, with package-local dependency links inside shared link entries |
Default: "hoisted" for single-package projects. LPM CLI auto-detects workspace roots (a package.json > workspaces glob or a pnpm-workspace.yaml) and defaults those projects to "isolated". If a default-hoisted install detects incompatible peer requirements, LPM CLI switches that project to "isolated" for the install and records the decision in lpm.lock for warm installs. v2 hoisted mode does not intentionally flatten every transitive dependency to the project root.
CLI flag: lpm install --linker <isolated\|hoisted> overrides per-invocation. Precedence: --linker flag > ~/.lpm/config.toml > linker > LPM_LINKER > this package.json value > built-in default ("hoisted", or "isolated" if a workspace is detected). The peer-conflict auto-switch only applies at the built-in default tier, so any explicit value here opts out. Unknown values fail loudly at install time — there is no silent fallback.
strictDeps
{ "lpm": { "strictDeps": "warn" } }How strictly LPM CLI enforces declared dependencies (no implicit access to phantom deps).
| Value | Behavior |
|---|---|
"strict" | Hard error on undeclared imports |
"warn" | Print a warning, continue |
"loose" | Permit silently |
strictPeerDependencies
{ "lpm": { "strictPeerDependencies": true } }Whether peer-dependency diagnostics fail the install. Default false: missing required peers, peer version mismatches, and cross-consumer peer conflicts print warnings and the install continues. Set to true when those diagnostics should be hard failures, especially in CI. Optional peers that are absent still do not fail.
Precedence: lpm install --strict-peer-dependencies / --no-strict-peer-dependencies > package.json > lpm.strictPeerDependencies > ~/.lpm/config.toml > strict-peer-dependencies > false.
catalogMode
{ "lpm": { "catalogMode": "prefer" } }How lpm install <pkg> saves dependencies that match the root default catalog.
| Value | Behavior |
|---|---|
"manual" (default) | Keep normal raw save specs such as "^4.3.6" or the explicit range you typed. |
"prefer" | Save "catalog:" when the root default catalog entry satisfies the resolved version; warn and save the direct spec when it does not. |
"strict" | Save "catalog:" when the root default catalog entry satisfies the resolved version; fail before committing package.json on mismatch or missing default-catalog entry. |
This affects lpm install <pkg> save policy only. Hand-written "catalog:" and "catalog:<name>" dependencies still resolve through the root catalog set, and lpm install --catalog[=<name>] <pkg> can force a catalog save for one invocation.
cleanupUnusedCatalogs
{ "lpm": { "cleanupUnusedCatalogs": true } }Prune root catalog entries that no root or workspace-member manifest references after a successful lpm install.
Default: false. When enabled, LPM CLI scans dependencies, devDependencies, optionalDependencies, and peerDependencies for "catalog:" and "catalog:<name>" references, then removes unreferenced entries from root package.json > catalogs. In pnpm-style workspaces, pnpm-workspace.yaml > cleanupUnusedCatalogs: true enables the same policy for pnpm-workspace.yaml > catalog / catalogs; an explicit package.json > lpm.cleanupUnusedCatalogs value wins when both are present.
scriptPolicy
{ "lpm": { "scriptPolicy": "deny" } }Dependency lifecycle-script gate for lpm install and lpm rebuild. Bare root-project lifecycle scripts are separate; see lpm install for the full semantics.
| Value | Behavior |
|---|---|
"deny" (default) | Dependency scripts blocked. lpm approve-scripts required per package. |
"allow" | Run every dependency lifecycle script during install. |
"triage" | Tiered gate: greens auto-run in the sandbox; ambers and reds need manual review (or the optional LLM advisor). |
Precedence: CLI flag > package.json > lpm > scriptPolicy > ~/.lpm/config.toml > script-policy > default (deny).
If this project value weakens the current approved machine floor, install or rebuild fails with error_code: "security_approval_required" instead of silently lowering the machine posture.
triageAdvisor
{ "lpm": { "triageAdvisor": "claude-cli" } }Optional LLM advisor for the triage gate. Active only when scriptPolicy: "triage".
| Value | Provider |
|---|---|
"none" (default) | No advisor — portable layers 1-4 only. |
"claude-cli" | Claude CLI. |
"codex" | OpenAI Codex CLI. |
"ollama" | Ollama-served local model. |
Approvals are ephemeral — never written to package.json or any other file. A second lpm install invokes the advisor again. Use lpm approve-scripts (which writes to trustedDependencies) for durable trust.
Precedence: package.json > lpm > triageAdvisor > ~/.lpm/config.toml > triage-advisor > default ("none").
trustedDependencies
{ "lpm": { "trustedDependencies": ["esbuild", "sharp"] } }Allowlist of dependency packages permitted to run lifecycle scripts under scriptPolicy: "deny". Two accepted shapes:
Legacy array form — names only:
{ "lpm": { "trustedDependencies": ["esbuild", "sharp"] } }Rich map form — bound to integrity + script hash, so any change re-opens the package for review:
{
"lpm": {
"trustedDependencies": {
"esbuild@0.25.1": {
"integrity": "sha512-...",
"scriptHash": "sha256-..."
}
}
}
}Manage with lpm trust diff and lpm approve-scripts.
Only
package.json > lpm > trustedDependenciesis read. A top-level"trustedDependencies"field (outside the"lpm"block) is not consulted — convert legacy top-level entries by moving them under"lpm".
minimumReleaseAge
{ "lpm": { "minimumReleaseAge": 86400 } }Cooldown in seconds before a newly published dependency version is installable. Default 86400 (24h). By default, project installs apply this to packages declared by the project or passed to lpm install <pkg>; transitive dependencies of an allowed direct package are not separately halted. Set minimumReleaseAgePolicy: "strict" to apply the same cooldown to transitive dependencies and lockfile replays. For ranges on checked packages, the resolver skips too-new candidates and can pick an older satisfying version; latest fallbacks never exceed the registry's current dist-tags.latest target in SemVer order, and exact pins to too-new versions fail. Bypass per-invocation with lpm install --allow-new, override with lpm install --min-release-age=<DUR>, or exempt exact package names with minimumReleaseAgeExclude.
Precedence: CLI > package.json > lpm.minimumReleaseAge > ~/.lpm/config.toml > minimum-release-age-secs > 24h default.
If the repo lowers the cooldown below the current approved machine floor, install fails with security_approval_required and points you at a temporary lpm security unlock or a persistent machine-level config change.
minimumReleaseAgePolicy
{ "lpm": { "minimumReleaseAgePolicy": "strict" } }Scope for the minimumReleaseAge cooldown.
| Value | Behavior |
|---|---|
"direct" (default) | Check direct/root dependencies only. This keeps normal installs fast and avoids downgrading a mature direct package because one of its children was published recently. |
"strict" | Check direct and transitive dependencies. Lockfile replays are revalidated from persisted registry-published-at timestamps when available. |
Precedence: package.json > lpm.minimumReleaseAgePolicy > ~/.lpm/config.toml > release-age-policy > default ("direct").
If the repo sets a looser policy than the current approved machine posture, install fails with security_approval_required. A machine with force-security-floor = true suppresses a project-level downgrade back to the stricter policy instead.
minimumReleaseAgeExclude
{ "lpm": { "minimumReleaseAgeExclude": ["react", "@scope/pkg"] } }Exact package names exempted from the minimum-release-age resolver and install halt. Alias dependencies match by their canonical target name, not the local alias key. CLI, package, and global exclude lists merge in that order, with duplicates removed. On project installs, the exclude is evaluated inside the direct/root dependency scope.
Unsupported in v1: globs such as @scope/*, version selectors such as react@19.0.0, and protocol specifiers such as npm:react.
engineStrict
{ "lpm": { "engineStrict": true } }Whether workspace-root engines.lpm / engines.node and, during installs, selected dependencies' engines.node constraints are enforced. Default true — workspace-root mismatches abort install / rebuild / add, and required dependency mismatches abort installation. Packages reachable only through optional dependency edges are skipped when their Node constraint is incompatible.
Precedence: lpm install --no-engine-strict (CLI) > package.json > lpm.engineStrict > ~/.lpm/config.toml > engine-strict > true default.
Set to false per-project to fall back to warning-only behavior. Incompatible dependencies remain installed and the unsatisfied constraints print warnings on stderr (suppressed under --json).
overrides
{
"lpm": {
"overrides": {
"lodash": "^4.17.21",
"react@<18": "18.0.0",
"baz>foo@1": "2.0.0"
}
}
}LPM CLI-native dependency overrides — same intent as pnpm's pnpm.overrides and npm's top-level overrides. Selectors:
| Selector | Matches |
|---|---|
"foo" | Every instance of foo |
"foo@<1.0.0" | Instances whose natural version satisfies the range |
"baz>foo" / "baz>foo@1" | Instances reached through baz |
On conflict with the top-level overrides / resolutions fields, lpm.overrides wins. Multi-segment paths (a>b>c) are rejected at parse time.
patchedDependencies
{
"lpm": {
"patchedDependencies": {
"lodash@4.17.21": {
"path": "patches/lodash@4.17.21.patch",
"originalIntegrity": "sha512-..."
}
}
}
}Local-only patches applied after install, patch-package-style. Selector format is "<name>@<exact-version>". Generated and registered automatically by lpm patch and lpm patch-commit — usually you don't edit this block by hand. The path field is relative to the directory of the package.json that declares it.
lpm.lock > [patches] records the patch file SHA-256 for each entry. On every install, LPM CLI verifies both the patch checksum and the store entry's originalIntegrity. Drift is a hard error.
peerDependencyRules
{
"lpm": {
"peerDependencyRules": {
"ignoreMissing": ["@types/react", "fsevents"],
"allowedVersions": {
"react": "16 || 17 || 18",
"card>react": "17",
"@scope/foo@^2>react": "17",
"typescript": "5"
},
"allowAny": ["@babel/*"]
}
}
}Tunes the post-resolution peer-dependency warning loop. Three independent sub-keys; each addresses a different complaint:
| Sub-key | Effect | Key shape |
|---|---|---|
ignoreMissing | Suppress missing-peer warnings for the listed names. Use when a peer is intentionally optional (something else installs it; your code handles absence). | Names + glob patterns (*, @scope/*, *-suffix, etc.) |
allowedVersions | Widen the accepted range. When the resolved peer's version doesn't satisfy the package's declared range, this widened range is tested as a fallback. If either matches, no warning fires. | Structured selectors (see below) |
allowAny | Accept any version when the peer is in the tree. Suppresses version-mismatch warnings for matched names. Does not suppress missing-peer warnings (combine with ignoreMissing for that). | Names + glob patterns |
allowedVersions selector grammar
The same grammar as lpm.overrides. The selector decides which (consumer, peer) pair the rule applies to:
| Selector | Matches |
|---|---|
"react" | any peer named react, regardless of consumer |
"@scope/foo" | scoped peer, any consumer |
"foo>react" | react peer of foo (any version of foo) |
"foo@^2>react" | react peer of foo whose installed version satisfies ^2 |
"@scope/foo@^2>react" | scoped parent + version range |
Multi-segment paths ("a>b>c") and standalone version qualifiers on a bare peer name ("foo@2" without >) are rejected at install time. Glob patterns are not accepted in allowedVersions — use the structured selector grammar above.
Same shape as pnpm's pnpm.peerDependencyRules — lpm migrate translates the pnpm side verbatim, validating selector keys with the same parser the resolver uses at install time. Hand-authored typos in lpm.peerDependencyRules.allowedVersions (bad selector, unparseable widened range) abort the install with a named error — same fail-closed posture as lpm.overrides. Drift between the pnpm and LPM CLI sides post-migration is surfaced as the pnpm_peer_rules_drift code on lpm doctor --json.
The lpm.scripts block
Configuration for dependency lifecycle-script execution under lpm rebuild and the auto-build flow. Most fields control the sandbox and capability surface granted to scripts.
{
"lpm": {
"scripts": {
"autoBuild": false,
"denyAll": false,
"trustedScopes": ["@myorg/*"],
"passEnv": ["GITHUB_TOKEN"],
"readProject": "narrow",
"sandboxLimits": { "RLIMIT_AS": 4294967296 },
"sandboxWriteDirs": ["build/"],
"sandboxReadAllow": [".env"]
}
}
}| Field | Type | Effect |
|---|---|---|
autoBuild | bool | If true, lpm install auto-runs lpm rebuild for trusted packages. Equivalent to lpm install --auto-build; if a trusted dependency lifecycle script fails, install exits non-zero. |
denyAll | bool | Refuse to run any dependency lifecycle scripts, ever — even trusted ones. Hard kill switch. |
trustedScopes | string[] | Scope globs (e.g. "@myorg/*") whose packages are auto-trusted for script execution |
passEnv | string[] | Extra env vars to forward into scripts. Each entry widens the capability surface — triggers user review. |
readProject | "narrow" | "full" | Filesystem read scope for scripts. "full" widens the capability and triggers review. |
sandboxLimits | object | Per-resource limits. Keys: RLIMIT_AS, RLIMIT_NPROC, RLIMIT_NOFILE, RLIMIT_CPU. Values: non-negative integer. Any requested limit outside the approved baseline triggers review. Raw user-configured [sandbox.limits] ceilings are not authority on their own. |
sandboxWriteDirs | string[] | Extra directories the sandbox lets scripts write to. Relative entries stay inside the canonical project root. Absolute entries outside it require a covering max-sandbox-write-roots entry. Existing paths and the nearest existing ancestor of missing paths are resolved before use; symlink, junction, and reparse-point traversal below the authorized root is rejected. The dangerous-root denylist still has final veto. |
sandboxReadAllow | string[] | Project-relative paths explicitly authorized for lifecycle-script reads despite matching the secret-file deny list. Use when a build genuinely needs a normally blocked file (.env for prisma generate, a project-rooted CA bundle, etc.). Entries must canonicalize inside project_dir; traversal escapes (..) and absolute paths outside the project are rejected. Unioned with per-user ~/.lpm/config.toml > script-read-allow. Allowlisted secrets remain readable; if every matching secret is allowlisted, Linux skips the namespace overlay. |
The project root itself may be reached through a symlink; LPM CLI resolves it once and evaluates containment against its canonical target. Links or reparse points inside the project or an explicitly authorized write root are not accepted, including when the requested leaf does not exist yet.
Widening any of passEnv, readProject, or sandboxLimits beyond the approved baseline requires explicit approval through lpm approve-scripts — the system does not silently grant elevated capabilities from raw repo or user config edits.
Related top-level fields
These live at the top level of package.json, not inside "lpm":
workspaces
{ "workspaces": ["packages/*", "apps/*"] }Or the object form:
{ "workspaces": { "packages": ["packages/*"] } }Both shapes accepted. See Workspaces.
engines
{ "engines": { "node": ">=22.0.0", "lpm": ">=0.32.0" } }Runtime / CLI version constraints. LPM CLI enforces both engines.node (against the effective Node version) and engines.lpm (against the running CLI version) on the workspace root by default. It also enforces engines.node from selected dependency versions, persisting those ranges in lpm.lock for warm, frozen, and offline replay. Required mismatches abort; optional-only incompatible packages are skipped. lpm.json > runtime.node takes precedence over the root engines.node as a pin source for runtime auto-install; every Node enforcement check still runs against the resolved Node version.
Other keys (engines.npm, engines.pnpm, engines.yarn, engines.bun) are recognized and surfaced as a one-line stderr warning that LPM CLI doesn't enforce them. Use engines.lpm for the LPM CLI version, and use lpm.json > runtime.bun when scripts need a managed Bun binary on PATH.
Opt out via lpm install --no-engine-strict, package.json > lpm > engineStrict = false, or ~/.lpm/config.toml > engine-strict = false.
overrides / resolutions
npm-style overrides and yarn-style resolutions are read for compatibility. lpm.overrides (above) takes precedence when there's a conflict.
scripts, bin, dependencies, devDependencies, peerDependencies, optionalDependencies
Standard npm fields — LPM CLI reads them as you'd expect.
catalogs
{
"catalogs": {
"default": { "react": "^18.2.0", "react-dom": "^18.2.0" },
"testing": { "jest": "^29.0.0", "vitest": "^1.0.0" }
}
}Centralized version catalogs for monorepos. Members reference catalog versions with "catalog:" or "catalog:<name>".
LPM CLI reads catalogs from root package.json > catalogs and from pnpm-style pnpm-workspace.yaml:
packages:
- "packages/*"
cleanupUnusedCatalogs: true
catalog:
react: ^18.2.0
catalogs:
testing:
vitest: ^1.0.0pnpm-workspace.yaml > catalog becomes LPM CLI's default catalog. pnpm-workspace.yaml > catalogs becomes the named catalog map. When both files define the same catalog package entry, package.json > catalogs wins and LPM CLI keeps the pnpm-workspace entry only for non-conflicting packages.
Catalog entry values must be concrete package ranges. A catalog entry cannot point at another catalog entry with "catalog:" or "catalog:<name>"; LPM CLI rejects recursive catalog definitions during install.
Set package.json > lpm > catalogMode to control whether lpm install <pkg> writes matching default-catalog entries back as "catalog:". Set cleanupUnusedCatalogs to prune unused catalog entries after successful installs.
See also
lpm.json— separate file for dev-server, task-runner, and publish configlpm.toml— project-level CLI defaults (save policy, etc.)~/.lpm/config.toml— user-level CLI defaults- Save policy — how install ranges are written
- Workspaces — filter grammar and topology