voku / agent-map
Compact PHP repository symbol maps for coding-agent navigation.
Fund package maintenance!
Requires
- php: >=8.2
- composer-runtime-api: ^2.2
- helgesverre/toon: ^3.1
- nikic/php-parser: ^5.0
- voku/agent-graph: ^0.2.0
- voku/simple-php-code-parser: ^0.22
- voku/stop-words: ^2.0
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^11.0
Suggests
- phpstan/phpstan: Enables PHPStan-backed semantic relation analysis; without it agent-map builds structural-only maps.
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 0.11.6
- 0.11.5
- 0.11.4
- 0.11.3
- 0.11.2
- 0.11.1
- 0.11.0
- 0.10.0
- 0.9.0
- 0.8.8
- 0.8.7
- 0.8.6
- 0.8.5
- 0.8.4
- 0.8.3
- 0.8.2
- 0.8.1
- 0.8.0
- 0.7.0
- 0.6.0
- 0.5.0
- 0.4.1
- 0.4.0
- 0.3.0
- 0.2.1
- 0.2.0
- 0.1.1
- 0.1.0
- dev-experiment/definition-capability-report
- dev-feature/polyglot-scip-definition
- dev-experiment/polyglot-evidence-providers
- dev-claude/agent-ui-dogfood-continuation-k6288f
- dev-release/0.11.4-final
- dev-release/0.11.4
- dev-fix/79-declare-php-parser-floor
- dev-fix/76-search-refresh-duplicate-precedence
- dev-claude/agent-ui-search-visualization-bjf47x
- dev-pre-1.0/61-local-semantic-bounds
- dev-dogfood/graph-scale-70mb
- dev-perf/graph-generation-marker
- dev-fix/graph-open-integrity
- dev-dogfood/graph-stage2-70mb-synthetic
- dev-release/use-agent-graph-0.2.0
- dev-bench/large-graph-decision-gate
- dev-benchmark/graph-index-decision-gate
- dev-refactor/use-agent-graph-relations
- dev-refactor/use-agent-graph-sqlite-runtime
- dev-fix/phpstan-refresh-cache-scope
- dev-release/0.9.0-marker
- dev-claude/agent-map-0.9-roadmap-0zngyp
- dev-chatgpt-parameter-rename-plan
- dev-claude/agent-map-navigation-experiment-a7wmvl
- dev-codex/generate-project-specific-l1-experiment-prompt-9jjpjt
- dev-codex/generate-project-specific-l1-experiment-prompt
- dev-docs/exact-agent-navigation
- dev-docs/agents-owner-boundary
- dev-chore/harden-release-tag-workflow
- dev-release/0.8.8
- dev-codex/adapt-code-from-php-rector-project
- dev-feature/property-removal-plan
- dev-codex/adapt-php-rector-code-for-agents
- dev-release/0.8.5
- dev-codex/copy-and-adapt-code-from-php-rector
- dev-feature/property-rename-plan
- dev-feature/property-rename-evidence
- dev-codex/implement-rename/refactoring-in-agent-map-and-agent-loop
- dev-codex/implement-rename/refactoring-in-agent-map-and-agent-loop-thu7uk
- dev-dogfood/rename-plan-actions
- dev-feature/governed-class-rename
- dev-feature/function-rename-plan
- dev-feature/rector-method-rename-hardening
- dev-feature/governed-rename-family
- dev-codex/create-roadmap-for-agent-map-improvements
- dev-feature/method-rename-plan
- dev-feature/class-rename-plan
- dev-fix/analysis-fingerprint-phpstan-reference
- dev-fix/dogfood-replay-backend-integrity
- dev-claude/agent-map-issue-25-gf4kni
- dev-dogfood/map-a-frozen-replays
- dev-release/0.8.2
- dev-agent/security-explicit-analysis-backend
- dev-agent/optional-phpstan-semantic-capability
- dev-agent/token-diet-20260815
- dev-agent/release-0.8.1
- dev-agent/release-0.8.1-notes
- dev-agent/map-readiness-boundary
- dev-release/0.8.0
- dev-agent/map-root-ownership
- dev-renovate-integration
- dev-agent/issue-12-symbol-less-search
- dev-agent/fix-positional-help-output
- dev-agent/issue-8-artifact-path-contract
- dev-agent/fix-artifact-path-bases
- dev-breaking/default-agent-loop-paths
- dev-release/0.7.0
- dev-agent/temporal-evolution
- dev-feat/php-architecture-regions
- dev-feat/php-architecture-discovery
- dev-feat/toon-map-store
This package is auto-updated.
Last update: 2026-09-12 04:02:25 UTC
README
Deterministic PHP repository maps for coding-agent context selection.
agent-map always records structural repository facts and enriches them when the PHPStan capability is installed:
voku/simple-php-code-parserrecords physical declarations and source ranges;- optional PHPStan 2.2 resolves PHPDoc types, generics, call targets, inheritance, and semantic relationships.
The results are reconciled into one map that can answer focused questions such as:
vendor/bin/agent-map discover vendor/bin/agent-map callers 'App\Service\UserService::save' vendor/bin/agent-map callees 'App\Service\UserService::save' vendor/bin/agent-map context 'App\Service\UserService::save' --format=toon
The important output is not a grand graph for admiring in meetings. It is a bounded, source-backed edit context that agent-loop and agent-recall-compiler can use without asking an LLM to rediscover the repository first.
Boundaries
agent-map owns:
repository analysis
→ reconciled symbols, types, and relations
→ deterministic queries
→ EditContextPlan
It does not:
- call an LLM;
- write the final implementation prompt;
- modify source code;
- execute tests;
- store durable project learning.
Those responsibilities belong to the surrounding agent-* packages.
Stability policy classifies every public surface - stable, supported-conditional, experimental, diagnostic or subtraction candidate - and states what 1.0 freezes. Read it before depending on a command.
When to use it
- The PHP identity to change is already known and you want its exact location, contracts, callers and dependencies without reading whole files.
- A change is mechanical - a rename, a removal, a namespace move - and you want exact byte-range edits with preconditions instead of a text substitution that half-works.
- A question is about PHP structure: what calls this, what does this depend on, what breaks.
When not to use it
- The answer is a literal string, a config key, a template, or a file name. That is a text-search
shape, and
grepwins; the map has nothing to add and costs a build. - The repository has no map yet and the task touches one obvious file. Building a map to edit one known line is the expensive path.
- The question is about intent, design or history rather than structure. Map reports what the source says, not what it should have said.
Silence from a scoped query is scoped silence. "The map has no callers for this" is not "this has no callers" - a structural-only map has no call edges at all, and every surface says so rather than implying absence.
Requirements
- PHP 8.2 or newer
- Composer
- PHPStan 2.2 only when PHPStan-backed semantic enrichment is required
Installation
composer require --dev voku/agent-map
Install PHPStan explicitly when semantic enrichment is wanted:
composer require --dev phpstan/phpstan:^2.2
Without PHPStan, map builds remain available with backend identity simple-php-code-parser+structural-only. When PHPStan is installed, the default backend remains simple-php-code-parser+phpstan. A selected PHPStan backend never falls back after an execution or configuration failure.
Build a map
JSON remains the default interoperable storage format:
vendor/bin/agent-map build \ --root=. \ --paths=src,tests \ --out=.agent-map/php-symbols.json
TOON is an optional compact serialization of the same model:
vendor/bin/agent-map build \ --root=. \ --paths=src,tests \ --out=.agent-map/php-symbols.toon \ --format=toon
There is one analysis path and one map model. JSON and TOON are serializers, not competing architectures.
Build options
--root: repository root, default current directory;--paths: comma-separated PHP files or directories, default.;--out: map file, default.agent-map/php-symbols.json;--format:jsonortoon, defaultjson;--phpstan-config: explicit PHPStan configuration when the PHPStan backend is available;--phpstan-memory-limit: explicit positive PHPStan memory limit, for example512Mor2G;--scan: comma-separated directories that only have to resolve symbols and are never indexed;--merge: patch the existing--outmap instead of replacing it;--exclude: repeatable PHP regular expression applied to normalized paths.
Keep --paths on directories when you can. PHPStan turns its result cache off as soon as it is
handed individual files, so a file-list scope re-analyses everything on every build, while a
directory scope makes an unchanged rebuild close to free. --exclude stays exact without losing
that cache: agent-map derives PHPStan excludePaths from the files the map excludes and keeps
PHPStan on the original directory scope.
Use --scan when the analysed scope references classes that live outside it. Without it PHPStan
cannot resolve those types and reports Class X was not found ... discovering symbols is probably not configured properly, which silently costs call edges:
vendor/bin/agent-map build --paths=src --scan=lib,vendor/acme
Configuration discovery uses:
--phpstan-config;phpstan.neon;phpstan.neon.dist;- a generated level-0 configuration.
Project PHPStan findings are stored as diagnostics when the semantic export itself succeeds. Parse failures, internal PHPStan failures, or a missing semantic export fail the build.
What the map contains
Files
- repository-relative path;
- SHA-256 source hash;
- namespace;
- structural and semantic status.
Symbols
- classes, interfaces, traits, enums, functions, and methods;
- exact declaration ranges;
- inheritance, interfaces, traits, and attributes;
- native, PHPDoc, and PHPStan-resolved parameter and return types;
- PHPStan template types and resolved generic ancestors;
- reconciliation state.
For example:
native return: Entity|null
PHPDoc return: T|null
resolved return: User|null
Generics are regular PHPStan types. There is no separate ceremonial generic subsystem.
Relations
definesdeclares_methodextendsimplementsuses_traitoverridescallsinstantiatesreferences_type
Relations record source locations and one of these resolution states:
structural_onlyphpstan_resolvedmultiple_targetsdynamic
Dynamic facts stay visible, but they are never promoted into imaginary certainty.
Reconciliation
Comparable parser and PHPStan facts are classified as:
confirmedsemantic_enrichmentstructural_onlyphpstan_onlyconflict
Conflicted symbols cannot be used as edit targets.
Commands
All read commands accept either a JSON or TOON index. The input format is detected from the file extension, while --format controls command output.
Locate symbols
vendor/bin/agent-map query UserService vendor/bin/agent-map file src/Service/UserService.php vendor/bin/agent-map related UserService
Inspect dependencies
vendor/bin/agent-map callers 'App\Service\UserService::save' vendor/bin/agent-map callees 'App\Service\UserService::save'
Method edit targets are exact:
Foo::bar
App\Foo::bar
\App\Foo::bar
A short class name that matches multiple methods fails and lists the fully qualified candidates. Editing the wrong Foo faster was not a requested feature.
Search when the target is not yet known
When the task names no PHP identity, ranked hybrid search turns prose into seeds. It is a seed generator, not a location oracle - the exact commands above remain the way to confirm an identity.
vendor/bin/agent-map search-index build --index=.agent-map/php-symbols.json
vendor/bin/agent-map search 'why are trailing commas dropped' --limit=8
vendor/bin/agent-map search-index doctor
The index is derived state, not a second source of truth: search-index build refuses to run against
a stale map, refresh re-chunks only what moved, and doctor reports drift between the map snapshot
and the stored index. --semantic adds embedding-backed ranking when a corpus provider is available;
without it the ranking stays lexical.
Search is conditional: it needs a SQLite build with FTS5 and a configured search database, and it reports that plainly instead of returning an empty result that looks like an answer. Literal strings, configuration, templates and file-name questions stay native text-search shapes - see ADR 0001.
Discover architecture
vendor/bin/agent-map discover
vendor/bin/agent-map impact 'App\Service\UserService::save' --depth=3
discover derives evidence-backed repository orientation without requiring a search query. It reports entrypoint candidates, call hubs, orchestrators, type hubs, relation quality, and coupling across namespaces, directories, and files.
Namespaces are deliberately not the only architecture signal. PHP allows projects without namespaces, so path and file coupling remain available for flat and legacy codebases.
impact performs a bounded, cycle-safe reverse traversal and preserves relation evidence, path nodes, truncation, and dynamic / multiple_targets uncertainty instead of collapsing them into an opaque score.
Both are experimental: they produce real output, but no consumer and no replay has yet measured that the output is worth its prompt cost.
See Architecture discovery for the complete command, semantics, legacy-PHP, freshness, and library-API documentation.
Generate edit context
vendor/bin/agent-map context 'App\Service\UserService::save' \
--index=.agent-map/php-symbols.json \
--context-budget=60000 \
--max-files=20 \
--max-callers=10 \
--max-callees=10 \
--max-tests=10 \
--format=toon
The resulting EditContextPlan contains:
- the primary method;
- implemented or overridden contracts;
- direct callers that may need adaptation;
- tests calling the target or its direct callers;
- direct callees;
- referenced type definitions;
- exact source slices and SHA-256 evidence;
- dynamic or conflicting blind spots;
- candidates omitted by the configured budget;
- a deterministic map digest.
The default traversal is intentionally one hop. Context selection is deterministic and methods are never truncated halfway through.
Plan safe PHP renames
Renaming a declaration with sed is how a repository acquires a half-renamed identity. The governed
rename family resolves one explicitly requested target and publishes exact, hash-bound byte edits
instead:
vendor/bin/agent-map class-rename-plan 'App\Service\OldName' NewName --format=json vendor/bin/agent-map rename-plan 'App\Service\UserService::save' store --format=json vendor/bin/agent-map parameter-rename-plan 'App\Service\UserService::save' '$old' '$new' --format=json vendor/bin/agent-map property-rename-plan 'App\Service\UserService::$old' '$new' --format=json vendor/bin/agent-map class-constant-rename-plan 'App\Service\UserService::OLD' NEW --format=json vendor/bin/agent-map function-rename-plan 'App\old_helper' new_helper --format=json
Every plan in the family shares the same shape: a versioned contract type, a safe /
review_required / blocked status, provenance (map digest, effective backend, analysis
fingerprint), exact edits, blind spots, stale evidence, blockers, and an explicit not_observable
boundary. A blocked plan publishes no edits.
Which map a contract needs differs. Static class-name tokens are name-resolvable, so class renaming, class-constant renaming and class moves work on a structural-only map. Method, parameter, property and function renaming, and every removal contract, need semantic evidence and therefore a PHPStan-backed map. Ask the registry rather than guessing - it covers all three families, and routing and discovery read the same list, so an advertised contract is always routable:
vendor/bin/agent-map plan-capabilities --format=json
See class rename and method rename for the full evidence, status and mutation-host validation semantics.
Plan a class namespace move
Moving a class is not a rename: the file has to land where the autoloader expects the new identity, and every reference that resolved through the old namespace changes meaning.
vendor/bin/agent-map class-move-plan 'App\Legacy\UserService' 'App\Service\UserService' --format=json
The destination path is derived from the project's declared Composer PSR-4 mappings and the manifest
identity is recorded as evidence; composer.json itself is never rewritten. The plan publishes the
namespace declaration edit, the affected imports and references, and one preconditioned file move.
References that resolved through the enclosing namespace are pinned to fully qualified names and reported for review rather than by synthesizing new imports. Ambiguous autoload layouts, destination collisions, grouped imports of the moved class, multi-symbol or multi-namespace files and namespaced function fallbacks fail closed.
See class move for the complete contract.
Plan safe PHP removals
Avoid line-oriented sed edits when deleting PHP declarations. A PHPStan-backed map can produce a
whole-node, hash-guarded deletion for an unused private method:
vendor/bin/agent-map method-removal-plan 'App\Worker::obsolete' --format=json vendor/bin/agent-map property-removal-plan 'App\Worker::$obsolete' --format=json vendor/bin/agent-map class-constant-removal-plan 'App\Worker::OBSOLETE' --format=json
The plan includes the exact byte range and expected source, including associated PHPDoc and attributes, but remains read-only. Observed calls, public/protected contracts, stale files, conflicting parser evidence, traits, magic methods/dispatch, unresolved class-string static calls anywhere in indexed source, and unsafe same-line source fail closed. Typed dynamic dispatch and method attributes are surfaced for review rather than promoted to certainty.
The same read-only contract now covers unused private properties and class constants. Class-constant
plans adapt Rector's RemoveUnusedPrivateClassConstantRector: only a single private declaration can
be deleted, every indexed PHP file is AST-scanned for static fetches, and the plan includes the whole
declaration (PHPDoc and attributes included) rather than asking an agent to splice lines with sed.
Stale files and observed fetches fail closed. Attributes and PHPDoc require review. Reflection,
constant(), dynamic constant names, inherited or late-static lookup, and source outside the indexed
map are not observable; the plan lists them as explicit boundaries instead of proving them absent.
Keep a map current
A full semantic build of a large repository costs minutes. A structural-only refresh re-analyses
only files whose hash moved, drops deleted ones, and patches the result into the existing map. A
PHPStan-backed refresh instead rebuilds its complete stored semantic scope through the structural
cache and lets PHPStan's dependency-aware result cache select changed files and semantic dependents:
vendor/bin/agent-map refresh --root=. --index=.agent-map/php-symbols.json
It reports Index is up to date and skips the analysis entirely when nothing changed. Without an
explicit --paths, new files are looked for in the directories the map already covers.
An incremental build refuses to mix semantic backends. If PHPStan availability changed since the existing map was built, run a full build so every carried file and relation has one backend identity.
The map records its PHPStan paths, exclude rules, and scan directories in the analysis fingerprint.
A normal PHPStan refresh reuses that recorded scope even when the caller omits flags; an explicit
scope, PHPStan configuration, or composer.lock change triggers a full semantic refresh. This
keeps call edges into a changed declaration exact without trusting source hashes alone.
Repository status
vendor/bin/agent-map stale vendor/bin/agent-map changed --base=main vendor/bin/agent-map summary vendor/bin/agent-map stats
stale compares current SHA-256 hashes with the map. context refuses to materialize source from a stale map.
Output formats
Read commands support:
text
json
markdown
toon
text and markdown are human projections. json and toon are the machine boundary and are two
serializers of one model, never two semantic implementations. Governed plans therefore emit text,
json and toon and deliberately not markdown: a plan is consumed by a mutation host, not pasted
into a report.
Plan status semantics
Every governed plan - rename, removal, move - reports exactly one status, and a host must branch on it before doing anything:
| status | meaning | edits and moves | exit code |
|---|---|---|---|
safe |
Every consequence agent-map can observe maps to an exact byte range. | published | 0 |
review_required |
The exact edits are published, and bounded evidence remains that PHP source alone cannot settle - listed in blind_spots. |
published | 0 |
blocked |
The plan cannot be proven. | none | 1 |
A blocked plan never publishes apparently applicable edits. That is the single rule the whole family is built around: a partial mutation is worse than no mutation.
Alongside the status, every plan carries:
provenance- map digest, effective backend, analysis fingerprint;stale_evidence- source that moved since the map was built, kept machine-distinct from semantic blockers because the recovery differs (refresh the map, versus rethink the change);blockers- why the plan is not safe;not_observable- what the contract structurally cannot see, stated rather than implied.
Every edit carries the pre-edit source SHA-256 and an exact byte range; every move carries the same hash and requires the destination to be absent. Validate the complete precondition set against one pre-edit snapshot before applying anything.
Library API
The CLI is an inspection layer. Other agent-* packages should compose PHP objects directly:
use voku\AgentMap\Context\EditContextPlanner; use voku\AgentMap\Index\IndexReader; $map = (new IndexReader())->read('.agent-map/php-symbols.json'); $plan = (new EditContextPlanner())->plan( map: $map, target: 'App\\Service\\UserService::save', );
agent-loop should not shell out to agent-map and scrape formatted text. Humans have invented enough avoidable protocols already.
The supported consumer boundary is:
Index\IndexReader/Index\AgentMapIndexfor map reads and exact identity resolution;Context\EditContextPlannerfor bounded edit context;- the planners under
Rename\,Removal\andMove\, all returning aPlan\GovernedPlan; Plan\PlanCapabilityviaagent-map plan-capabilitiesto discover which contracts this version proves and which map backend each needs;Cli\CliApplicationwhen a host genuinely needs to embed the command line.
Files below .agent-map/ are package-owned state, not an interface. A consumer that reads them, or
parses CLI text, is depending on something that is free to change in a patch release.
Makefile integration & PackageResources
The package includes a ready-to-use Makefile helper at resources/make/agent-map.mk. You can include it directly in your project's Makefile:
-include vendor/voku/agent-map/resources/make/agent-map.mk
PHP tools and host integrations can resolve that resource path programmatically via voku\AgentMap\PackageResources:
use voku\AgentMap\PackageResources; $makeIncludePath = PackageResources::makeInclude(); // returns /path/to/vendor/voku/agent-map/resources/make/agent-map.mk
Generated files
Recommended .gitignore entry:
.agent-map/
Commit a map only when a repository explicitly wants a versioned snapshot.
Evidence
Does bounded Map navigation reduce LLM reading? replays
three already-solved PHP issues against a grep/read baseline, the projection from pinned agent-loop
revision 3b7190d, and agent-map's existing exact surfaces, and records where each one helps, where it
costs more than it returns, and which capabilities nothing consumes. The harness is in tools/dogfood/.
Those per-capability verdicts feed the stability policy, which is where a capability's tier and its 1.0 direction are recorded.
Development
composer install composer ci
CI validates Composer metadata, PHPUnit, and PHPStan on supported PHP versions.