The dependency notes formats
Depos stores dependency notes as Markdown. npm files use dependency-notes/1; the ecosystem-scoped Cargo, Maven, Gradle, Mix, Gleam, Zig, Python, Ruby, Composer, Go, NuGet, vcpkg and Conan files use dependency-notes/2. VS Code and JetBrains support every listed ecosystem. Visual Studio 2022+ currently supports NuGet and reads and writes the identical dependency-notes/2 file. See Versioning.
The notes file
The path identifies the ecosystem:
- npm:
.depos/DEPENDENCY-NOTES.md, besidepackage.json; - Cargo:
.depos/cargo/DEPENDENCY-NOTES.md, besideCargo.toml; - Maven:
.depos/maven/DEPENDENCY-NOTES.md, besidepom.xml; - Gradle:
.depos/gradle/DEPENDENCY-NOTES.md, besidebuild.gradleorbuild.gradle.kts. - Mix:
.depos/mix/DEPENDENCY-NOTES.md, besidemix.exs. - Gleam:
.depos/gleam/DEPENDENCY-NOTES.md, besidegleam.toml. - Zig:
.depos/zig/DEPENDENCY-NOTES.md, besidebuild.zig.zon. - Python:
.depos/python/DEPENDENCY-NOTES.md, besidepyproject.tomlor a requirements manifest. Files belowrequirements/are owned by the directory above it. - Ruby:
.depos/ruby/DEPENDENCY-NOTES.md, besideGemfile,gems.rbor a*.gemspecfile. - NuGet:
.depos/nuget/DEPENDENCY-NOTES.md, besideDirectory.Packages.propsor a .NET project file. A project owned by central package management uses the closestDirectory.Packages.propsdirectory. - Go:
.depos/go/DEPENDENCY-NOTES.md, besidego.mod. - vcpkg:
.depos/vcpkg/DEPENDENCY-NOTES.md, besidevcpkg.json. - Conan:
.depos/conan/DEPENDENCY-NOTES.md, besideconanfile.pyorconanfile.txt. - Composer:
.depos/composer/DEPENDENCY-NOTES.md, besidecomposer.json.
A manifest without its own notes file uses the closest ancestor notes file for the same ecosystem, up to the workspace root. Different ecosystems never share notes.
A repository that started before Pacmon became Depos keeps these files under .pacmon/ instead of .depos/, in the same layout. A reader looks in .depos/ first, then in .pacmon/, at each directory level; where both hold a file for the same ecosystem at one level, the .pacmon/ file is ignored. A repository keeps its notes in one of the two: a new file goes to .depos/ when the repository has anything under .depos/ (a notes file or AGENT-RULES.md), else to .pacmon/ when it has anything there, else to .depos/. A repository moves from .pacmon/ to .depos/ all at once, after everyone working in it reads .depos/.
The file is UTF-8. Its line ending is CRLF if the file contains one CRLF, otherwise LF. A byte order mark at the start is accepted; a tool that rewrites the file drops it.
The file has these parts, in this order: frontmatter, header comment, title, introduction, sections.
---
format: dependency-notes/1
lang: en
---
<!-- Each "## name" below is a package from package.json. The text right under the
heading is written by people. "### Agent notes" and everything below it is written
by AI agents — rules in .depos/AGENT-RULES.md. -->
# Dependency Notes
Repository-wide rules, free text.
## <package name>
Human layer.
### Agent notes
- key: value
The frontmatter, the header comment and the title have fixed content, given below. A tool that formats the file rewrites them. The introduction is written by people. A section has layers: one is written by people, one by AI agents.
Cargo, Maven, Gradle, Mix, Gleam, Zig, Python, Ruby, Composer, Go, NuGet, vcpkg and Conan files use v2 frontmatter:
---
format: dependency-notes/2
ecosystem: cargo
lang: en
---
A fenced code block starts and ends with a line of three or more backticks or tildes, indented by at most three spaces. Inside a fenced code block, a line is never a heading and never a field.
Frontmatter
The frontmatter is present when the first line of the file is ---. It ends at the next line that is ---. Each line between them is key: value. A key is a letter followed by letters, digits, _ or -. A key with an empty value is not read.
Version 1 defines format and lang. Version 2 also requires ecosystem.
format names the version of these rules the file follows. Supported values are dependency-notes/1 and dependency-notes/2. When the key is missing, the file is read as v1.
ecosystem is required in v2 and is cargo, maven, gradle, mix, gleam, zig, python, ruby, composer, go, nuget, vcpkg or conan. It must agree with the notes path.
lang names the language the values are written in. Keys are always English. When the key is missing, the language is en.
Any other key is kept where it is and is not read.
Header comment
The header comment is the first HTML comment at the top of the file, after the frontmatter if there is one. Its text is fixed: v1 names package.json; v2 names the ecosystem dependency manifest selected by ecosystem. It says who writes where and points AI agents to the AGENT-RULES.md of the file's own notes directory: .depos/AGENT-RULES.md, or .pacmon/AGENT-RULES.md for a file under .pacmon/. A file's own commentary goes in the introduction, not in the header comment.
Title
The title is the only level-1 heading in the file. Its text is # Dependency Notes. A second level-1 heading is a mistake. A level-1 heading that names a dependency is a section at the wrong level.
Introduction
The introduction is everything between the title and the first section. It holds the repository's own rules about dependencies. It is free text and is never parsed. It may contain headings of level 3 or deeper.
Sections
A section starts with a level-2 heading: ## <name>. Level-2 headings are reserved for dependency note keys everywhere in the file.
A direct dependency is identified statically from its manifest. Its note key is the name its section carries:
- npm: a key under
dependencies,devDependencies,peerDependencies, oroptionalDependencies. The note key is that key, including@scope/. - Cargo: a key in a dependency, dev-dependency, build-dependency, or target-specific dependency table, not in
[workspace.dependencies]. The note key is the local key; for a renamed dependency, that is the key and not itspackage. - Maven: a dependency under the project's or a profile's
dependencies, not under dependency management or a plugin. The note key isgroupId:artifactId. - Gradle: a static external module coordinate or a
libs.*catalog alias used in adependenciesblock, not a plugin, constraint, project dependency, file dependency, or catalog bundle. The note key isgroup:namewithout the version, or the alias as written, such aslibs.junit.jupiter. - Mix: the first application atom in a literal dependency tuple inside
def/defp depsor an inlinedeps: [...]list. Hex, Git, path and umbrella tuples share this rule;{:phoenix, "~> 1.8"}has the note keyphoenix. Dynamically assembled lists are not evaluated. - Zig: a direct field of the top-level
.dependenciesstruct inbuild.zig.zon. URL/hash, path and lazy dependencies share this rule;.known_folders = .{ ... }has the note keyknown_folders. Escaped identifiers are decoded.build.zig, system libraries and transitive dependencies are not evaluated. - Python: a named dependency in
pyproject.tomlproject metadata, optional dependencies, dependency groups, build requirements, Poetry dependency tables/groups, or uv legacy development dependencies; or a named PEP 508 requirement in a supported pip requirements file. Includes, constraints, options, source metadata, unnamed paths and lock files are ignored. Note keys are normalized to lowercase, with each run of.,_, or-replaced by-. - Ruby: a receiverless
gemcall with a literal name inGemfileorgems.rb, or a literaladd_dependency,add_runtime_dependencyoradd_development_dependencycall on the activeGem::Specificationreceiver in*.gemspec. Literal Gemfile group and platform scopes are retained. Ruby code, dynamic names, method/class/module bodies,eval_gemfile,Gemfile.lockand transitive gems are ignored. The note key is the case-sensitive gem name as written. - NuGet: a literal
PackageReference Includeitem in a.csproj,.fsproj, or.vbproj; aPackageVersion Include/UpdateorGlobalPackageReference Includeitem inDirectory.Packages.props. Imports, MSBuild expressions,Update/Removeproject items, project/framework references, package downloads and transitive packages are ignored. The note key is the package ID as written. - Go: a module path in a
requiredirective ofgo.mod, single-line or in a block, including requirements marked// indirect;require github.com/jackc/pgx/v5 v5.7.1has the note keygithub.com/jackc/pgx/v5. Atooldirective names a package; it belongs to the required module whose path is its longest prefix, and a tool with no such module is keyed by its own path.replace,exclude,retractandgo.workare not dependencies. - vcpkg: a string or object entry in the root
dependenciesarray or afeatures.<name>.dependenciesarray ofvcpkg.json. The note key is the lower-cased port name.host: truechanges the scope, not the key. Overrides, baselines, configuration files and requested features are not separate dependencies. - Conan: a package reference in a supported dependency section of
conanfile.txt, a literal class-field value inconanfile.py, or the literal first argument to a supportedself.*requirement call. The note key is the lower-cased package name before the first/. Python is never executed; dynamic expressions and lockfiles are ignored. - Composer: a package or platform requirement in the root
requireorrequire-devobject ofcomposer.json, includingphp,php-*,hhvm,ext-*,lib-*,composer,composer-plugin-apiandcomposer-runtime-api.provide,replace,conflict,suggest, repositories,composer.lockand transitive packages are ignored. The note key is the lower-cased package or platform name.
A v1/npm section matches after trimming, stripping one pair of surrounding quotes or backticks, and lower-casing. Python applies distribution-name normalization after the same wrapper removal. NuGet, vcpkg, Conan and Composer lower-case after wrapper removal. Cargo, Maven, Gradle, Mix, Gleam, Zig, Ruby and Go v2 keys preserve case.
Each dependency has at most one section. Two sections with the same name are a mistake; the file does not say which one is wrong. Until it is fixed, tools read the first one in the file.
Sections are sorted by name. The sort compares the matched names character by character, so @scope/x comes before a.
A section whose name matches no dependency is an orphan. An orphan whose agent layer has status: removed … is kept on purpose. status: removed … on a dependency still present in its manifest is a mistake.
A section with an empty body does not count as a note.
Layers
The body of a section has up to two layers. The reserved heading ### Agent notes splits it. The heading is matched after trimming and without regard to case.
- The human layer starts on the line after
## <name>. It ends before### Agent notes, or at the end of the section when there is no such heading. - The agent layer starts on the line after
### Agent notesand runs to the end of the section.
The first ### Agent notes in a section is the one that counts. Inside a section, ### Agent notes is the only allowed heading. Any other heading of level 3 or deeper is a mistake.
Human layer
The human layer is free text written by people. Tools do not parse it. They display it, and they show its first non-empty line as the summary of the section. A leading - or * is dropped from the summary. Tools and AI agents do not change this layer.
Agent layer
The agent layer is written by AI agents. It holds field lines. It may also hold prose.
A field line is a list item of the form - key: value. The exact pattern is ^\s*[-*]\s+([a-z][a-z0-9_-]*)\s*:\s*(.*)$, matched without regard to case. The key is read lower-cased. The value is read trimmed. A list item whose value starts with // is a bare URL, such as - https://…, not a field. Any line that does not match the pattern is prose and is not read.
A key must be one of the fields listed below. An unknown key is a mistake. A key may appear on several lines. When one value is needed, the first line is used. A value that is empty, or that consists only of dashes, is a mistake.
Fields
The first six fields are the core fields. They are filled whenever there is something true to say. The other fields are added when they are true and not obvious.
purpose— the package's role in this project: what it is and why this repository uses it. One sentence.usage— where and how the package is wired in: entry points, wrapper module, config, and the rule for using it.constraint— something that must not change: a pin, a forbidden upgrade, a coordination requirement. The rule and its reason.verify— a command or a flow that shows the package still works.log— one dated event per line: added, upgrade attempted, proposal rejected. With a commit, PR or advisory id.verified— the installed version the notes were last checked against. The value starts with a version number; a leadingvis allowed.risk— a judgment about what the package costs or endangers: security exposure, native binary, licence obligation, maintenance state.runtime— where the package executes. One ofserver,client,build,dev,deploy.exposure— whether the package handles untrusted input. One ofuntrusted-input,internal.bump-with— packages that must be upgraded together with this one.remove-when— the condition under which the package should be removed.alternatives— real decisions about replacements, dated, with a verdict.owner— who to ask: a team, a person, a channel.status— whether the package is still here. One ofdead,removal-planned, orremovedfollowed by a year and month asYYYY-MMand, usually, a reason. A missingstatusmeans the package is active.links— changelog, docs, upstream issue, registry page. The specific ones.note— anything that fits no other field. One thought per line.
runtime and exposure may hold several values, separated by ,, | or /. The values of runtime, exposure and status are matched without regard to case.
Validity
A file is valid when none of the following is true. Each of them is a mistake with a fix. The human layer is never checked.
- The file has no frontmatter.
formatis present and is neitherdependency-notes/1nordependency-notes/2.- A v2 file has no valid
ecosystem, or it disagrees with the file path. - The file has no level-1 heading, or its level-1 heading is not
# Dependency Notes, or it has a second level-1 heading. - A dependency name is a heading of a level other than 2, or is written
##name, without the space. - A section contains a heading of level 3 or deeper other than
### Agent notes. - Two sections have the same name.
- A field line has a key that is not a field, or a value that is empty or only dashes.
runtime,exposure,statusorverifiedhas a value of the wrong shape.status: removed …appears in the section of a package that is still in its manifest.
An orphan is not a mistake. It may be a typo, or a package that left its manifest without its notes.
Canonical form
A file in canonical form has these parts, in this order:
- The frontmatter as written, with required version keys appended when their values are known. A new v1 file gets
formatandlang; a new v2 file also getsecosystem. - One blank line, then the header comment.
- One blank line, then
# Dependency Notes. - If the file has an introduction: one blank line, then the introduction with its outer blank lines removed.
- For each section, in name order: one blank line, then
## <name>with the name as written, then one blank line, then the body with its outer blank lines removed. The inside of the body is kept as it is. - One line ending at the end of the file.
Duplicate sections keep their relative order. A tool that rewrites a file into canonical form changes nothing else.
A tool that adds a section puts it at its sorted position when the file is sorted. Otherwise it appends the section at the end.
The organization layer
Status: proposal. VS Code, JetBrains and Visual Studio implement it as a preview.
A web app such as Depos (depos.dev) can hold two kinds of data for an organization: notes, one note per package shared by all its repositories, and an allowlist. This data lives in the web app. It is never written into a notes file, and it does not change this format. A file that is valid without it stays valid and means the same.
Two modes
A tool works in one of two modes for each workspace folder.
- File mode. The user is not signed in to the web app, or the folder's repository is not in one of the user's organizations. The tool works as the rest of this document describes. It makes no network request.
- Org mode. The user is signed in, and the folder's repository is in one of the user's organizations. The notes of that organization in the web app are the notes. The notes files of the folder are read-only.
Matching
The repository is found by its GitHub remote, as owner/name. A package matches org data by its ecosystem and its name, normalized the same way as when a dependency is matched to a section.
Notes in the web app
An organization has at most one note per package in the web app. All repositories of the organization share it.
Such a note has the same two layers as a section: a human layer and agent fields. Its fields are the fields of Fields, with the same rules for their values.
In org mode:
- The tool shows the web app's note of a package where it would show the section: the hover, the note editor, the end-of-line preview, coverage.
- Only an admin of the organization changes a note in the web app. The tool sends the change to the web app. For every other member, it is read-only.
- The tool does not change the notes files. It does not show their sections as notes. A notes file still exists in the repository for people in file mode.
- The human layer of a note from the web app is shown the same way as the human layer of a section. HTML in it is shown as text, never rendered.
From notes files to the web app
The web app reads the notes files of the organization's repositories. A section of a package that has no note in the web app yet is a candidate for one.
- A package with sections in one repository, or with the same sections in several repositories, has one candidate.
- A package with different sections in several repositories is a conflict. An admin chooses one of them, or writes the note from them.
A section written later in file mode becomes a candidate again. It does not change an existing note in the web app.
Allowlist results
Each ecosystem has a policy mode: Off, Audit or Enforce. In an ecosystem set to Audit or Enforce, each package is Allowed, Denied or Not listed in each repository.
- The web app decides each result. A tool applies no allowlist rules of its own. It receives the decided packages of the folder's repository and treats every other package of that ecosystem as Not listed. A decision whose expiry has passed is Not listed.
- A tool marks Denied and Not listed packages at their manifest line. In Audit this is a warning. In Enforce it is an error.
- An ecosystem set to Off shows nothing.
Local copy
A tool keeps a copy of the notes and the allowlist results on the user's machine. It asks the web app only for what changed since that copy. It uses the copy while the web app cannot be reached, and shows when the copy was received. Signing out removes the copy.
Agents
In org mode, a tool can give AI agents the notes of the web app through an MCP server. The web app serves one MCP server per repository. Where the editor has a way to add MCP servers, the tool registers it for the editor's agents, with the user's sign-in. Agents outside an editor, such as a command-line agent or CI, connect to the same server with an MCP key of the organization. An admin adds the key in the web app. It can read, or read and write. A tool whose editor has no way to add an MCP server can give the user the setup for the key instead: the server's address, with the key read from the environment or asked for by the editor, never the key itself.
- An agent that has these tools reads notes through them. It does not read or write the notes files of the folder.
- An agent acts with the rights of the signed-in user, or of the admin who added the key. Only an admin's agent can write, and with a key only when the key can write.
- An agent writes agent fields only. It never changes the human layer.
- An agent without these tools works as in file mode. In org mode the notes files can be older than the notes in the web app.
The tools:
| Tool | Input | Answer |
|---|---|---|
get_dependency_note | ecosystem, package name | The note of the package for this repository, or nothing. |
list_dependency_notes | ecosystem (optional) | The notes for this repository. |
check_package | ecosystem, package name | The package's allowlist result in the web app: Allowed, Denied, Not listed, or Not audited. |
set_agent_fields | ecosystem, package name, fields | Replaces the agent fields of the note. Adds the note when there is none. Admins only. |
AGENT-RULES.md tells agents to use these tools when they have them.
Versioning
format is the contract between the file and its reader. The number is a major version alone. It changes when a file that is valid under version n would be invalid under version n + 1. It changes for no other reason. Additions are recorded under Changes and do not change the number. A reader that meets a version it does not know reports it.
Changes
dependency-notes/2adds ecosystem-scoped Cargo, Maven, Gradle, Mix, Gleam, Zig, Python, Ruby, Composer, Go, NuGet, vcpkg and Conan notes while leaving npm v1 files in place.
Example
A complete repository is in example-repo: a package.json and the .depos/DEPENDENCY-NOTES.md that documents it. Other ecosystem files differ in their frontmatter, header comments, and note keys. One section:
## express
HTTP API layer. Do not upgrade to v5 (SEC-1301).
### Agent notes
- purpose: HTTP framework; serves the public REST API
- constraint: stay on ^4 — v5 changes router path matching
- verified: 4.19.2