# Files LPM CLI creates (/docs/project-files)



LPM CLI follows one naming rule in a project:

* Files and directories named `lpm.*` or `lpm-*` at the project root are team files. Commit them.
* The `.lpm/` directory holds machine-local state. It keeps itself out of Git, so it needs no `.gitignore` entry.

Data that belongs to you rather than to a project, such as your login and local env secrets, lives in the [LPM home](#the-lpm-home), outside the project.

```text
my-app/
├── package.json            commit
├── lpm.lock                commit
├── lpm.json                commit
├── lpm.toml                commit
├── lpm-skills/             commit
├── lpm-changes/            commit
├── lpm-sources.json        commit
├── lpm-agent-skills.json   commit
├── patches/                commit
├── .env.example            commit
└── .lpm/                   local, ignores itself
```

Each file appears only after you or a command creates it.

## Files to commit [#files-to-commit]

| Path                                                     | Created by                                                                                                                                                   | Purpose                                                                                                                                                                                                                                    |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `package.json`                                           | You, [`lpm init`](/docs/packages/init), and commands that change dependencies or trust, such as `lpm install`, `lpm approve-scripts`, and `lpm patch-commit` | Dependencies, scripts, and the [`"lpm"` block](/docs/reference/package-json-lpm) of shared package policy.                                                                                                                                 |
| `lpm.lock`                                               | `lpm install`                                                                                                                                                | The resolved dependency graph. A fresh clone installs from it, including a project with overrides or patches, and offline installs can use it too. See [Lockfile](/docs/packages/lockfile).                                                |
| `lpm.json`                                               | You, `lpm init --npm`, and `lpm env`, which adds the `vault` ID                                                                                              | Runtimes, tasks, services, env files and schema, publish targets, and the project's env ID. The ID is not a secret. See [`lpm.json`](/docs/reference/lpm-json).                                                                            |
| `lpm.toml`                                               | You, and `lpm install` when you allow a suspicious package name at its prompt                                                                                | Team CLI defaults and reviewable policy exceptions, such as `policy.typosquat.allow` entries. See [`lpm.toml`](/docs/reference/lpm-toml).                                                                                                  |
| `patches/`                                               | [`lpm patch-commit`](/docs/packages/patch)                                                                                                                   | Patch files that `package.json > lpm.patchedDependencies` names.                                                                                                                                                                           |
| `lpm-skills/`                                            | You                                                                                                                                                          | Agent skills your package publishes. [`lpm publish`](/docs/packages/publish) ships them at `lpm-skills/` in the tarball. `lpm skills validate` checks them with the rules LPM.dev Registry applies at upload.                              |
| `lpm-changes/`                                           | You                                                                                                                                                          | [Release](/docs/packages/release) change files at the workspace root. `lpm release apply` removes the entries it applied after it writes the new versions.                                                                                 |
| `lpm-sources.json`                                       | [`lpm add`](/docs/packages/add)                                                                                                                              | What `lpm add` delivered: each source package and version, the files it created or replaced with their digests, the directories it created, and the dependencies it added. [`lpm remove`](/docs/packages/remove) reads it on any checkout. |
| Files that `lpm add` copied                              | `lpm add`                                                                                                                                                    | Source code delivered into your project. It is your code now.                                                                                                                                                                              |
| `lpm-agent-skills.json`                                  | `lpm skills add --project`                                                                                                                                   | The project's managed agent skills and the revision of each. `lpm install` restores what a checkout lacks. See [Skills](/docs/packages/skills).                                                                                            |
| `.env.example`                                           | `lpm env example`                                                                                                                                            | Variable names, descriptions, and defaults from `envSchema`, without values. LPM CLI creates it with your usual file permissions.                                                                                                          |
| `Packages/LPMDependencies/` and the `.xcodeproj` changes | `lpm install` in an Xcode project                                                                                                                            | The wrapper package that exports your Swift dependencies and the project's link to it. Commit them with the shared `Package.resolved` files. See [Swift](/docs/guides/using-with-swift#xcode-projects-and-workspaces).                     |

Agent folders such as `.claude/skills/<name>` link to skill content in `.lpm/managed-skills/`, or hold a copy of it. `lpm install` restores the content and agent folders that a checkout lacks. It warns about a skill that it can't restore and points to `lpm skills update`. Where a repository commits such a link and Git checks it out as a plain file, as on Windows without link support, `lpm install` replaces that file.

### Records from earlier releases [#records-from-earlier-releases]

Earlier releases kept some of these records in `.lpm/`, where Git now never sees them:

| Earlier location                       | Now                     | What LPM CLI does                                                                                                                                                   |
| -------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.lpm/added-sources.json`              | `lpm-sources.json`      | Reads it, and replaces it with `lpm-sources.json` the next time it records a change. Packages that a teammate's committed `lpm-sources.json` lacks join it.         |
| `.lpm/managed-skills/skills.lock.json` | `lpm-agent-skills.json` | Reads it, and replaces it with `lpm-agent-skills.json` the next time it records a change. Skills that a teammate's committed `lpm-agent-skills.json` lacks join it. |
| `.lpm/changes/`                        | `lpm-changes/`          | `lpm release plan` and `lpm release apply` stop until you move the files.                                                                                           |
| Authored skills in `.lpm/skills/`      | `lpm-skills/`           | `lpm skills validate` and `lpm publish` fail until you move them.                                                                                                   |

## What stays local: `.lpm/` [#what-stays-local-lpm]

Every command that writes project state keeps a `.lpm/.gitignore` file that holds `*`. Git then ignores everything in `.lpm/`, wherever the project sits in a repository. LPM CLI does not edit your project's `.gitignore` for it. A [deploy output](/docs/packages/workspaces#lpm-deploy) is the exception: its `.lpm/` holds the output's dependencies, which its `.gitignore` lets Git commit, along with the `.gitignore` itself.

LPM CLI keeps `.lpm/` owner-only on Unix and refuses a symbolic link in its place. It creates the directory that way, removes group and other access from one that has it, such as one an earlier version created, and on macOS removes the access list entries that the project directory passes on. Another user's `.lpm/` stays as it is. If `.lpm/.gitignore` stops ignoring everything, for example because a negation follows `*`, LPM CLI replaces it. It leaves a read-only or linked file alone.

| In `.lpm/`                                                                                            | Contents                                                                                          | Why it stays local                                                                                                                                             |
| ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `install-hash`, `build-state.json`, `trust-snapshot.json`, `overrides-state.json`, `patch-state.json` | Install checkpoints, blocked lifecycle scripts, and the trust and patch state of the last install | Installs rebuild them. A committed copy can hide the warnings that they exist to raise.                                                                        |
| `wrappers/`, `hoisted/`                                                                               | Package directories that some `node_modules` layouts keep here                                    | Installs recreate them.                                                                                                                                        |
| `skills/<owner.package>/`                                                                             | Skills of installed `@lpm.dev` packages                                                           | `lpm install` restores them.                                                                                                                                   |
| `managed-skills/`                                                                                     | Content of the project's managed agent skills                                                     | `lpm install` restores it from `lpm-agent-skills.json`.                                                                                                        |
| `added-source-backups/`                                                                               | Copies of the files that `lpm add` replaced                                                       | They belong to the machine that ran `lpm add`. On another checkout, `lpm remove` keeps such a file and tells you to restore the original from version control. |
| `certs/`                                                                                              | The project's development certificate and its private key, `key.pem`                              | A private key never belongs in a repository. See [`lpm cert`](/docs/infra/cert).                                                                               |
| `inspector.db` and its `-wal`, `-shm`, and `-lock` files                                              | [Tunnel](/docs/infra/tunnel) captures, including raw request headers and bodies                   | Captures can hold tokens, cookies, and personal data.                                                                                                          |
| `env-sync.json`                                                                                       | This checkout's env sync state: cloud versions, account bindings, and revision floors             | Each checkout keeps its own. A committed copy would bind teammates' clones to your account.                                                                    |
| `migrate-backup/`                                                                                     | Originals of the files that [`lpm migrate`](/docs/packages/migrate) changed                       | `lpm migrate --rollback` uses them. They can include an `.npmrc` with a token.                                                                                 |
| `xcode-backups/`                                                                                      | `project.pbxproj` as it was before the last Xcode project edit by LPM CLI                         | For recovery on this machine.                                                                                                                                  |
| `release-apply/`, rollback and restore staging directories                                            | Recovery state of interrupted releases, workspace installs, and task cache restores               | It only applies to this machine.                                                                                                                               |
| `graph.html`                                                                                          | The view that `lpm graph --format html` writes                                                    | Regenerated on demand.                                                                                                                                         |
| Lock files                                                                                            | Locks that serialize installs, publishes, migrations, and `lpm.json` updates                      | They coordinate processes on this machine.                                                                                                                     |

### If Git already tracks files in `.lpm/` [#if-git-already-tracks-files-in-lpm]

Files committed before `.lpm/` ignored itself stay tracked, because ignore rules don't untrack files. [`lpm doctor --all`](/docs/infra/doctor#project-state) reports them as `project_state_tracked`, names up to three, and gives a command that untracks them from any directory. Git can track the directory under a spelling that the file system takes for `.lpm/`, such as `.LPM/` on a case-insensitive file system, or the name with a zero-width mark in it on HFS+ or in a Linux casefold directory; the check counts those files, and the command uses Git's spelling. A repository of `.lpm/`'s own, such as a clone or a submodule there, is checked too, with its own command. In a [deploy output](/docs/packages/workspaces#lpm-deploy), whose `.lpm/` commits its dependencies, the check leaves out `.lpm/.gitignore`, `.lpm/store/`, and `.lpm/deploy-workspace/`, and the command names only the other files; run that command instead of the one below. Elsewhere, untrack them once and commit:

```bash
git rm -r --cached .lpm
git commit -m "Stop tracking .lpm"
```

The files stay on disk, and `.lpm/` keeps them out of later commits. Until you untrack them, [`lpm cert`](/docs/infra/cert) refuses to write the project's private key into `.lpm/certs/` while Git tracks a file there, in the project's repository or in one at `.lpm/` or `.lpm/certs/`, or while Git can't tell whether it does, as when Git can't be run in a project inside a repository. Env sync commands refuse a `.lpm/env-sync.json` that Git tracks, and warn when Git can't tell; see [env sync state](/docs/reference/lpm-json#env-sync-state).

### `.gitignore` entries from earlier releases [#gitignore-entries-from-earlier-releases]

Earlier releases appended blocks such as this one to the project's `.gitignore`:

```text title=".gitignore"
# LPM wrapper tree (auto-generated)
.lpm/wrappers/
```

`lpm install` removes the wrapper, hoisted-state, and Agent Skills blocks when they appear exactly as LPM CLI wrote them. It deletes `.gitignore` when nothing else is left in it. A `.lpm/` rule that you wrote yourself stays; it is harmless but no longer needed.

### Lifecycle scripts and publishing [#lifecycle-scripts-and-publishing]

Sandboxed dependency lifecycle scripts can read the package directories in `.lpm/`, `wrappers/` and `hoisted/nested/`, and write only their own package's directory there. With the default `readProject: "narrow"`, those are also the only parts of `.lpm/` that they can read. `package.json > lpm.scripts.sandboxWriteDirs` rejects entries inside `.lpm` or above the project, on Linux scripts don't run when a directory they can write, or a mount below one, shows `.lpm`, the LPM home or the repository's git directory under another path, and a project inside `~/.cache`, `~/.npm`, or `~/.node-gyp`, which scripts can write, can't run dependency scripts in the sandbox. See [the `lpm.scripts` block](/docs/reference/package-json-lpm#the-lpmscripts-block).

`lpm publish` never packs `.lpm/` at any depth, even when `files` or an entrypoint names it; an entrypoint that reaches it by another spelling, such as the NTFS short name `LPM~1`, stops the publish. It also leaves out the root `lpm.lock`, `lpm.lockb`, `lpm-changes/`, `lpm-sources.json`, and `lpm-agent-skills.json`. A published `lpm.json` keeps its other fields but drops `vault` and `vaultSync`. See [`lpm publish`](/docs/packages/publish).

## Secret files in a project [#secret-files-in-a-project]

Some commands write real secrets into project files. Before a secret reaches disk, LPM CLI makes sure that Git ignores the file.

| Command                                                             | File                                                      | Ignore rule goes to | When the file can't be kept out of Git                                         |
| ------------------------------------------------------------------- | --------------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------ |
| `lpm env export <file>`                                             | The exported values                                       | `.gitignore`        | The command stops and writes nothing.                                          |
| `lpm env export --ci <file>`                                        | The resolved CI values                                    | `.git/info/exclude` | The command stops and writes nothing.                                          |
| `lpm env pull --oidc --output=<file>`                               | The pulled values                                         | `.git/info/exclude` | The command stops and writes nothing.                                          |
| [`lpm setup local`](/docs/infra/setup#create-a-local-project-token) | `.npmrc` with a project token                             | `.gitignore`        | The command stops before it changes the token.                                 |
| [`lpm setup ci npmrc`](/docs/infra/setup)                           | `.npmrc` with a CI token                                  | `.git/info/exclude` | The command stops before it changes the token.                                 |
| [`lpm dev`](/docs/dev/dev)                                          | `.env`, copied from `.env.example` when `.env` is missing | `.gitignore`        | A warning; the copy is written. When Git tracks `.env`, a warning and no copy. |
| `lpm env import <file>`, `lpm env init`                             | The env files they read                                   | `.gitignore`        | A warning; the import is done.                                                 |

For a file inside the project, or elsewhere in a Git work tree, LPM CLI does this:

1. It refuses a destination that Git tracks, including one that Git tracks under a spelling the file system takes for the same file, such as `.ENV` for `.env` on a case-insensitive file system, `ſ` for `s` on APFS, the name with a zero-width mark in it on HFS+ or in a Linux casefold directory, or on Windows an NTFS short name such as `ENV~1` or a name that ends in a dot or a space, such as `.env.`. It asks every repository whose index can list the file: the one the project is in, one cloned or added as a submodule in a directory on the way to the file, and one around either of those, whose index still lists the files it had before a repository was made inside it. A link at the destination counts as the link, which the write replaces, not as the file it leads to. It names a command that untracks the file from any directory, one for each repository that tracks it, such as `git -C <directory> rm --cached -- .ENV`. `import` and `init` only read their files, so a tracked source file is a warning, and they keep the file out of Git where it is once links are followed.
2. If Git doesn't already ignore the file, it appends an anchored rule for that exact path, such as `/.env.production`, and checks that Git now ignores the file. When the path names a directory by another spelling, such as `INFRA/` for `infra/`, a rule for the name the directory is stored under joins it. The rule goes to the project's `.gitignore`, or for a file in another work tree, such as a repository cloned inside the project or a monorepo's root outside it, to the `.gitignore` at the root of that work tree. Commands for CI use the repository's `.git/info/exclude`, which leaves the work tree clean. Another repository whose index lists files in that directory, comparing names as its `core.ignorecase` setting says, looks inside it, so LPM CLI checks that it ignores the file too. That repository reads the work tree's `.gitignore` on its way in, but not the work tree's `.git/info/exclude`, so commands for CI add the rule to its own `.git/info/exclude`. A repository that records the directory as a submodule never looks inside it, and needs no rule.
3. It writes the file atomically with owner-only permissions: `0600` on Unix, without access list entries that the directory would pass on on macOS, and a protected access list on Windows. In the project, it refuses a parent directory that is a link or doesn't exist, under a nested repository too. Outside the project, it follows links and keeps the file they lead to out of Git.

Outside a Git repository, LPM CLI still adds the `.gitignore` rule, so the file stays ignored after you create a repository. A rule that it can't write there is a warning. When Git can't be run at all, LPM CLI writes the file with a warning that it couldn't confirm that Git ignores it; commands that use `.gitignore` still add the rule, under a repository cloned inside the project to the `.gitignore` beside its `.git`. Commands that only warn when Git can't answer, such as `lpm dev`, add the rule the same way. `lpm setup local` and `lpm setup ci npmrc` stop instead when the project is inside a repository, as they can't tell whether Git tracks the `.npmrc` that will hold a token. A file in no Git work tree, outside the project, is written owner-only without changes to any ignore file. When Git can't tell whether a work tree holds such a file, for example because of a `.git` file that points nowhere, the commands that stop above stop, and the others write it with a warning. JSON output lists warnings in `warnings`.

## Which directory is the project [#which-directory-is-the-project]

Commands that keep project state, such as `lpm env`, `lpm dev`, `lpm tunnel`, and `lpm cert`, act on the nearest directory at or above the current one that has a `package.json` or an `lpm.json`. The search stops before your home directory, also where `HOME` names it through a link. Package commands such as `lpm add`, `lpm remove`, and `lpm publish` use the nearest `package.json`, stopping there too.

LPM CLI prints the directory that it chose when it isn't the current one. File paths that you type, such as `lpm env export local.env`, `lpm env generate --out-dir src/env`, `lpm add <package> --path widgets`, `lpm publish --provenance-file bundle.sigstore`, or `lpm skills add ./team-skills`, stay relative to the directory where you ran the command. `lpm release publish --provenance-file` is the exception: each released package has its own bundle, so a relative path names a file in each package's directory. Paths in `package.json` and `lpm.json` are relative to the project.

Without a project around it, a command uses the current directory. Commands that write project state refuse a directory whose `.lpm` would be the LPM home, such as your home directory. A project that is your home directory, as `HOME=/app` with the app in `/app` makes it, needs `LPM_HOME` set to a directory outside the project. Account actions that use no project, such as `lpm env pair`, `unpair`, `ls-remote`, and `rotate-sharing-key`, run in any directory.

## The LPM home [#the-lpm-home]

Data that isn't part of any project lives in the LPM home: `~/.lpm` by default, `%USERPROFILE%\.lpm` on Windows, or the directory that [`LPM_HOME`](/docs/reference/env-vars#filesystem-layout) names. Keep it out of repositories.

Every command gives the LPM home a `.gitignore` that ignores everything in it. When a repository holds the home, as `LPM_HOME="$PWD/.lpm-ci"` does in CI, the command does so before it writes anything there, creating the home with that `.gitignore` if it doesn't exist yet. A `.gitignore` already there stays as it is, and a repository's root gets none, as it would hide the whole repository. Inside a repository, a command stops when Git would commit what LPM CLI adds to the home: give the home a `.gitignore` that ignores everything in it, or point `LPM_HOME` at a directory of its own. A home that is a repository's root, without a `.gitignore` of its own that ignores everything as described next, can only be moved. [`lpm logout`](/docs/infra/authentication#logout-and-revocation), `lpm cert status`, and [`lpm cert uninstall`](/docs/infra/cert#remove-ca-trust) still run in such a home, with the same message as a warning, so you can remove the login and certificate trust it already holds. A `.gitignore` that ignores everything and then takes back only `config.toml` and the `.gitignore` itself, as a dotfiles repository that commits them does, is enough; one that takes back anything else isn't, whatever a repository's own exclude file says. This holds wherever a linked `LPM_HOME` leads. [`lpm publish`](/docs/packages/publish#package-contents-and-validation) and [`lpm deploy`](/docs/packages/workspaces#lpm-deploy) leave the LPM home out of a package that holds it, bundled dependencies included. The package store and the task cache keep their directories to their owner (`0700`), on macOS without the access list entries a directory above would pass on, and a task cache entry that another account could write isn't restored. Give the LPM home a directory of its own.

| In the LPM home                                           | Contents                                                                                                                                      |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `config.toml`                                             | [User defaults](/docs/reference/config-toml).                                                                                                 |
| `store/`, `cache/`                                        | The [package store](/docs/packages/content-addressable-store) and [caches](/docs/packages/cache).                                             |
| `global/`, `bin/`                                         | Globally installed packages and their commands.                                                                                               |
| `.credentials` and its key files                          | Login state, when the OS credential store can't hold it. See [Authentication](/docs/infra/authentication#credential-storage).                 |
| `vaults/`, `.vault-fallback-key`                          | Local env secrets on Linux and Windows. See [Local secrets](/docs/dev/lpm-vault#storage-and-recovery).                                        |
| `certs/`                                                  | The local root certificate authority, including its private key.                                                                              |
| `security/`                                               | Security approvals.                                                                                                                           |
| `cert-projects.json`, `known-projects.json`, `ports.toml` | Records keyed by project path: projects with development certificates, projects whose packages the store keeps, and per-project port choices. |

An LPM home other than `~/.lpm` keeps its own entries in the OS credential store, so it has its own login, local env secrets, and approvals. Switching `LPM_HOME` doesn't carry them over. [`lpm doctor`](/docs/infra/doctor#auth) warns with `lpm_home_default_state_unused` when `~/.lpm` holds login or env files that the selected home doesn't use.

Project env values can't set `LPM_HOME` for the scripts that LPM CLI starts: it drops the variable from `.env` files and stored env values. [`lpm self-update`](/docs/infra/self-update) keeps its state in `.lpm` in your OS account's home directory, whatever `LPM_HOME` names.

## See also [#see-also]

* [Project setup](/docs/project-setup) — what `package.json`, `lpm.json`, and `lpm.toml` own
* [`lpm doctor`](/docs/infra/doctor) — project state and storage checks
* [`lpm env`](/docs/dev/env) — env files, exports, and sync state
* [Environment variables](/docs/reference/env-vars#filesystem-layout) — `LPM_HOME`
