package.json — "lpm" key
Reference for the LPM-specific configuration block inside package.json.
LPM 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.
Project-only. Every key in this block — scriptPolicy, trustedDependencies, minimumReleaseAge, 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 and minimumReleaseAge 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,
"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" | npm-style flat layout — phantom-deps accessible |
Default: "hoisted" for single-package projects. LPM 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 switches that project to "isolated" for the install and records the decision in lpm.lock for warm installs.
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 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 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" } }Lifecycle-script gate for lpm install and lpm rebuild. See lpm install for the full semantics.
| Value | Behavior |
|---|---|
"deny" (default) | Scripts blocked. lpm approve-scripts required per package. |
"allow" | Run every lifecycle script during install. Matches npm/pnpm/bun default. |
"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 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 version is installable. Default 86400 (24h). Bypass per-invocation with lpm install --allow-new or override with lpm install --min-release-age=<DUR>.
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.
engineStrict
{ "lpm": { "engineStrict": true } }Whether engines.lpm and engines.node constraints from the workspace root package.json are enforced. Default true — install / rebuild / add abort when the running CLI version doesn't satisfy engines.lpm, or the effective Node version doesn't satisfy engines.node.
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 npm's behavior (read but don't enforce). The unsatisfied constraints still print as 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-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.
The patch engine verifies the store entry's integrity matches originalIntegrity on every install. 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 sides post-migration is surfaced as the pnpm_peer_rules_drift code on lpm doctor --json.
The lpm.scripts block
Configuration for 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 lifecycle script fails, install exits non-zero. |
denyAll | bool | Refuse to run any 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 inside the project the sandbox lets scripts write to (default sandbox blocks writes outside the package's own dir) |
sandboxReadAllow | string[] | Project-relative paths to exempt from the secret-file deny list. Use when a lifecycle script genuinely needs to read 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. |
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 enforces both engines.node (against the effective Node version) and engines.lpm (against the running CLI version) by default — install / rebuild / add abort on mismatch. lpm.json > runtime.node takes precedence over engines.node as a pin source for runtime auto-install; the 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 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 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 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'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 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 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