Pacmon

Dependency notes for PHP

composer.json is JSON, and JSON has no comments. A require entry names a package and the versions a project accepts, and that is all: not why the package is there, why its constraint stops below the next major, or what to run after composer update moves it.

Pacmon keeps a note for each package in .pacmon/composer/DEPENDENCY-NOTES.md, beside composer.json, and shows it on the package's line. Platform requirements get notes too, so the reason a project needs ext-intl or a given php version sits beside the requirement.

What Pacmon reads

Pacmon reads package and platform requirements in the root require and require-dev objects of composer.json, including php, php-*, hhvm, ext-*, lib-* and Composer API packages. Names are matched case-insensitively. provide, replace, conflict, suggest, composer.lock and transitive packages are not dependencies.

Pacmon never runs PHP or Composer, so neither has to be installed.

Example

{
  "require": {
    "php": "^8.3",
    "ext-intl": "*",
    "guzzlehttp/guzzle": "^7.9",
    "monolog/monolog": "^3.8"
  },
  "require-dev": {
    "phpunit/phpunit": "^11.5"
  }
}

.pacmon/composer/DEPENDENCY-NOTES.md:

## guzzlehttp/guzzle

HTTP client for the payment provider. Call it through src/Payments/Client.php.

### Agent notes

- purpose: HTTP client for the payment provider and the shipping-rate APIs
- usage: only through src/Payments/Client.php, which sets the timeouts and retries
- verify: `vendor/bin/phpunit --testsuite payments`
- verified: 7.9.2

The heading is the package or platform name as require writes it, in lower case: "guzzlehttp/guzzle" is ## guzzlehttp/guzzle, "ext-intl" is ## ext-intl, and the PHP version itself is ## php.

Comments in composer.json

Composer refuses a // or /* */ comment anywhere in composer.json: every command stops with Comments are not allowed. The place Composer documents for comments is a top-level _comment key, a string or an array of strings. It sits away from the packages it is about, and it outlives them: composer remove leaves it behind. Comments in composer.json goes through each workaround and what Composer does with it.

Pinning a version

composer require monolog/monolog writes a caret constraint on the newest version, such as "^3.12", which accepts any later 3.x release. A version on its own, "3.8.0", means that version only, and composer validate warns that such an exact constraint should be avoided for a package that follows semantic versioning.

composer.lock records the exact version of every package, and composer install installs exactly that. composer update monolog/monolog moves only the packages it names. Why a constraint stops where it does goes in the package's note, as constraint:.

For AI coding agents

An agent reads a package's section before it adds, upgrades or removes the package. usage: says where the code calls it, and verify: which tests to run after the version moves. See the rules agents follow.