JSONL Output Format
When you run phpnomad index, the indexer writes a .phpnomad/ directory at the project root containing 16 files. One file is plain JSON. The other 15 are JSONL, meaning one JSON object per line.
This page is the reference for every file in that directory, including exact JSON shapes, the dependency graph edge types, and practical querying patterns.
Why JSONL
The index is designed to be consumed by AI agents and developer tools, not by humans staring at a web UI. JSONL was chosen over a single large JSON file for three reasons.
Grep-friendly. Each line is a self-contained JSON object. You can grep for a class name and get back just the lines that mention it, without parsing the entire file. This matters when the index contains thousands of classes.
Token-efficient. An AI agent that needs to understand one controller does not need to read the entire index. It greps for that controller's FQCN and gets back one line. The cost in tokens is proportional to what is actually queried, not what exists in the project.
Proportional cost. A project with 50 classes produces a small index. A project with 2,000 classes produces a larger one. But a single query against either project returns roughly the same amount of data.
Directory structure
After indexing, your project looks like this:
your-project/
.phpnomad/
meta.json
classes.jsonl
initializers.jsonl
applications.jsonl
controllers.jsonl
commands.jsonl
dependencies.jsonl
tables.jsonl
events.jsonl
graphql-types.jsonl
facades.jsonl
task-handlers.jsonl
mutations.jsonl
dependency-map.jsonl
dependents-map.jsonl
orphans.jsonl
phpnomad-cli.md
The phpnomad-cli.md file is an auto-generated cheat sheet that summarizes the index and common queries. The 16 data files are described below.
File reference
meta.json
The only non-JSONL file. A single JSON object with summary counts for the entire index.
{
"projectPath": "/home/user/projects/my-app",
"indexedAt": "2026-04-13T14:22:07-05:00",
"counts": {
"classes": 1019,
"applications": 1,
"initializers": 24,
"bindings": 187,
"controllers": 42,
"listeners": 18,
"resolvedControllers": 42,
"resolvedCommands": 7,
"dependencyTrees": 187,
"resolvedTables": 15,
"resolvedEvents": 12,
"resolvedGraphQLTypes": 0,
"resolvedFacades": 3,
"resolvedTaskHandlers": 5,
"resolvedMutations": 4,
"dependencyMapNodes": 843,
"dependentsMapNodes": 671,
"orphans": 23
}
}
Use this file to get a quick overview of the project's size and composition without reading any JSONL files.
classes.jsonl
Every PHP class discovered in the project. One line per class.
Key fields: fqcn, file, line, implements, traits, constructorParams, isAbstract, parentClass, description
{"fqcn":"App\\Services\\PayoutService","file":"lib/Services/PayoutService.php","line":12,"implements":["App\\Contracts\\PayoutServiceInterface"],"traits":[],"constructorParams":[{"name":"datastore","type":"App\\Datastores\\Interfaces\\PayoutDatastoreInterface","isBuiltin":false}],"isAbstract":false,"parentClass":null,"description":"Handles payout calculations and distribution."}
{"fqcn":"App\\Models\\Payout","file":"lib/Models/Payout.php","line":8,"implements":["PHPNomad\\Datamodel\\Interfaces\\DataModel"],"traits":["PHPNomad\\Datamodel\\Traits\\WithDataModel"],"constructorParams":[],"isAbstract":false,"parentClass":null,"description":null}
Each constructorParams entry contains name, type (the FQCN of the type hint), and isBuiltin (true for scalar types like string, int, bool).
initializers.jsonl
Each initializer in the project, with its bindings, controllers, listeners, commands, and other contributions.
Key fields: fqcn, file, isVendor, implementedInterfaces, classDefinitions, controllers, listeners, eventBindings, commands, mutations, taskHandlers, typeDefinitions, updates, facades, hasLoadCondition, isLoadable
{"fqcn":"App\\Initializers\\PayoutInitializer","file":"lib/Initializers/PayoutInitializer.php","isVendor":false,"implementedInterfaces":["PHPNomad\\Di\\Interfaces\\HasClassDefinitions","PHPNomad\\Rest\\Interfaces\\HasControllers","PHPNomad\\Event\\Interfaces\\HasListeners"],"classDefinitions":[{"concrete":"App\\Services\\PayoutService","abstracts":["App\\Contracts\\PayoutServiceInterface"],"source":"App\\Initializers\\PayoutInitializer","sourceFile":"lib/Initializers/PayoutInitializer.php","bindingType":"declarative"}],"controllers":["App\\Controllers\\GetPayoutsController","App\\Controllers\\CreatePayoutController"],"listeners":{"App\\Events\\SaleTriggered":["App\\Listeners\\CalculatePayoutListener"]},"eventBindings":[],"commands":[],"mutations":{},"taskHandlers":{},"typeDefinitions":[],"updates":[],"facades":[],"hasLoadCondition":false,"isLoadable":true}
The listeners field is a map from event FQCN to an array of handler FQCNs. The classDefinitions field contains the DI bindings this initializer registers.
applications.jsonl
Application classes that define the boot sequence. Most projects have exactly one.
Key fields: fqcn, file, preBootstrapBindings, bootstrapperCalls, postBootstrapBindings
{"fqcn":"App\\Application","file":"lib/Application.php","preBootstrapBindings":[{"concrete":"App\\Strategies\\MySqlQueryStrategy","abstracts":["PHPNomad\\Database\\Interfaces\\QueryStrategy"],"source":"App\\Application","sourceFile":"lib/Application.php","bindingType":"declarative"}],"bootstrapperCalls":[{"method":"use","line":34,"initializers":[{"fqcn":"App\\Initializers\\PayoutInitializer","isDynamic":false}]},{"method":"use","line":35,"initializers":[{"fqcn":"App\\Initializers\\ReportInitializer","isDynamic":false}]}],"postBootstrapBindings":[]}
The bootstrapperCalls array preserves the order that initializers are loaded. Each call records the method name, line number, and the initializer references passed to it.
controllers.jsonl
Resolved REST controllers with their endpoints, HTTP methods, and capabilities.
Key fields: fqcn, file, endpoint, endpointTail, method, usesEndpointBase, hasMiddleware, hasValidations, hasInterceptors
{"fqcn":"App\\Controllers\\GetPayoutsController","file":"lib/Controllers/GetPayoutsController.php","endpoint":"/api/v1/payouts","endpointTail":null,"method":"GET","usesEndpointBase":false,"hasMiddleware":true,"hasValidations":true,"hasInterceptors":false}
{"fqcn":"App\\Controllers\\CreatePayoutController","file":"lib/Controllers/CreatePayoutController.php","endpoint":null,"endpointTail":"/create","method":"POST","usesEndpointBase":true,"hasMiddleware":true,"hasValidations":true,"hasInterceptors":true}
When usesEndpointBase is true and endpoint is null, the controller derives its path from a shared base. The endpointTail is appended to that base.
commands.jsonl
CLI commands registered through initializers.
Key fields: fqcn, file, signature, description
{"fqcn":"App\\Commands\\ProcessPayoutsCommand","file":"lib/Commands/ProcessPayoutsCommand.php","signature":"payouts:process {--dry-run}","description":"Process all pending payouts"}
dependencies.jsonl
Recursive dependency trees for each DI binding. Shows how interfaces resolve to concrete classes and what those concrete classes depend on in turn.
Key fields: abstract, concrete, source, resolutionType, dependencies
{"abstract":"App\\Contracts\\PayoutServiceInterface","concrete":"App\\Services\\PayoutService","source":"App\\Initializers\\PayoutInitializer","resolutionType":"bound","dependencies":[{"abstract":"App\\Datastores\\Interfaces\\PayoutDatastoreInterface","concrete":"App\\Datastores\\PayoutDatastore","source":"App\\Initializers\\PayoutInitializer","resolutionType":"bound","dependencies":[]}]}
The resolutionType field is bound when the interface has an explicit DI binding, or unresolved when no binding was found.
tables.jsonl
Database table definitions with their column schemas.
Key fields: fqcn, file, tableName, columns
{"fqcn":"App\\Tables\\PayoutsTable","file":"lib/Tables/PayoutsTable.php","tableName":"payouts","columns":[{"name":"id","type":"BIGINT","typeArgs":null,"factory":"PrimaryKeyFactory","attributes":["UNSIGNED","NOT NULL","AUTO_INCREMENT"]},{"name":"userId","type":"BIGINT","typeArgs":null,"factory":null,"attributes":["UNSIGNED","NOT NULL"]},{"name":"amount","type":"DECIMAL","typeArgs":[10,2],"factory":null,"attributes":["NOT NULL"]},{"name":"status","type":"VARCHAR","typeArgs":[50],"factory":null,"attributes":["NOT NULL","DEFAULT 'pending'"]},{"name":"createdAt","type":"TIMESTAMP","typeArgs":null,"factory":"DateCreatedFactory","attributes":["NOT NULL","DEFAULT CURRENT_TIMESTAMP"]}]}
Each column object includes name, type, optional typeArgs (for types like VARCHAR(255) or DECIMAL(10,2)), an optional factory name, and an attributes array with constraints.
events.jsonl
Event classes with their event IDs and payload properties.
Key fields: fqcn, file, eventId, properties
{"fqcn":"App\\Events\\SaleTriggered","file":"lib/Events/SaleTriggered.php","eventId":"sale_triggered","properties":[{"name":"saleId","type":"int"},{"name":"amount","type":"float"},{"name":"collaboratorId","type":"int"}]}
The eventId is the string identifier used in event dispatching. The properties array lists the payload fields with their types.
graphql-types.jsonl
GraphQL type definitions with their SDL and resolver mappings.
Key fields: fqcn, file, sdl, resolvers
{"fqcn":"App\\GraphQL\\Types\\PayoutType","file":"lib/GraphQL/Types/PayoutType.php","sdl":"type Payout {\n id: ID!\n amount: Float!\n status: String!\n createdAt: String!\n}","resolvers":{"amount":{"App\\GraphQL\\Resolvers\\PayoutAmountResolver":true}}}
The sdl field contains the raw GraphQL schema definition. The resolvers field maps field names to their resolver FQCNs.
facades.jsonl
Facade classes and the interfaces they proxy.
Key fields: fqcn, file, proxiedInterface
{"fqcn":"App\\Facades\\Transactions","file":"lib/Facades/Transactions.php","proxiedInterface":"App\\Contracts\\TransactionServiceInterface"}
task-handlers.jsonl
Task handler mappings that connect task classes to their handler implementations.
Key fields: handlerFqcn, handlerFile, taskClass, taskId, taskFile
{"handlerFqcn":"App\\TaskHandlers\\ProcessPayoutHandler","handlerFile":"lib/TaskHandlers/ProcessPayoutHandler.php","taskClass":"App\\Tasks\\ProcessPayout","taskId":"process_payout","taskFile":"lib/Tasks/ProcessPayout.php"}
mutations.jsonl
Mutation handlers with their registered actions and adapter information.
Key fields: fqcn, file, actions, usesAdapter, adapterClass
{"fqcn":"App\\Mutations\\PayoutMutator","file":"lib/Mutations/PayoutMutator.php","actions":["create","update"],"usesAdapter":true,"adapterClass":"App\\Adapters\\PayoutMutationAdapter"}
dependency-map.jsonl
The forward dependency graph. For each class, lists everything it depends on as outbound edges.
Key fields: fqcn, file, edges (array of {type, target, via?})
{"fqcn":"App\\Services\\PayoutService","file":"lib/Services/PayoutService.php","edges":[{"type":"injects","target":"App\\Datastores\\Interfaces\\PayoutDatastoreInterface"},{"type":"implements","target":"App\\Contracts\\PayoutServiceInterface"}]}
This file answers the question: "What does class X depend on?"
dependents-map.jsonl
The reverse dependency graph. For each class or interface, lists everything that depends on it as inbound edges.
Key fields: fqcn, file, edges (array of {type, source, via?})
{"fqcn":"App\\Contracts\\PayoutServiceInterface","file":null,"edges":[{"type":"implemented-by","source":"App\\Services\\PayoutService"},{"type":"injected-by","source":"App\\Controllers\\GetPayoutsController"},{"type":"injected-by","source":"App\\Controllers\\CreatePayoutController"}]}
This file answers the question: "What depends on class X?"
Note that the file field is null for interfaces and classes that exist only in vendor packages.
orphans.jsonl
Classes that have no edges in either direction. They neither depend on anything in the dependency graph nor are depended upon by anything else.
Key fields: fqcn, file
{"fqcn":"App\\Helpers\\LegacyFormatter","file":"lib/Helpers/LegacyFormatter.php"}
Orphans are candidates for removal. They may be dead code, or they may be used through mechanisms the indexer does not track (like reflection or dynamic instantiation).
Dependency graph edge types
The dependency graph uses 9 edge types for the forward direction (dependency-map.jsonl) and 9 corresponding inverted types for the reverse direction (dependents-map.jsonl).
Forward edges (dependency-map)
| Edge type | Meaning | Example |
|---|---|---|
injects |
Constructor parameter type hint | PayoutService injects PayoutDatastoreInterface |
implements |
Interface implementation | PayoutService implements PayoutServiceInterface |
extends |
Class inheritance | AdminPayoutService extends PayoutService |
uses-trait |
Trait usage | PayoutDatastore uses-trait WithDatastoreDecorator |
listens-to |
Event listener registration | CalculatePayoutListener listens-to SaleTriggered |
handles-task |
Task handler registration | ProcessPayoutHandler handles-task ProcessPayout |
proxies |
Facade proxy | Transactions proxies TransactionServiceInterface |
resolves-to |
DI binding resolution | PayoutServiceInterface resolves-to PayoutService |
mutates-via |
Mutation adapter | PayoutMutator mutates-via PayoutMutationAdapter |
Inverted edges (dependents-map)
| Edge type | Meaning | Forward counterpart |
|---|---|---|
injected-by |
Something injects this type | injects |
implemented-by |
A class implements this interface | implements |
extended-by |
A class extends this class | extends |
trait-used-by |
A class uses this trait | uses-trait |
listened-by |
A listener is registered for this event | listens-to |
task-handled-by |
A handler is registered for this task | handles-task |
proxied-by |
A facade proxies this interface | proxies |
resolved-from |
A concrete resolves from this abstract | resolves-to |
adapter-for |
An adapter is used by this mutator | mutates-via |
The via field
Some edges include an optional via field that records where the relationship is established. For resolves-to edges, the via field contains the FQCN of the initializer or application that registered the binding.
{"type":"resolves-to","target":"App\\Services\\PayoutService","via":"App\\Initializers\\PayoutInitializer"}
Querying patterns
Because each file is JSONL, you can query the index with standard Unix tools. No special tooling required.
Find everything that depends on an interface
grep "PayoutDatastoreInterface" .phpnomad/dependents-map.jsonl
This returns a single JSON line listing every class that injects, implements, or otherwise references the interface.
Find what a class depends on
grep "PayoutService" .phpnomad/dependency-map.jsonl
Returns the class's outbound edges, showing all its constructor dependencies, implemented interfaces, traits, and other relationships.
List all unreferenced classes
cat .phpnomad/orphans.jsonl | jq '.fqcn'
Returns the FQCN of every class with no relationships in the dependency graph.
Find all GET endpoints
grep '"method":"GET"' .phpnomad/controllers.jsonl
Find listeners for a specific event
grep "SaleTriggered" .phpnomad/initializers.jsonl
Find a table's column schema
grep '"tableName":"payouts"' .phpnomad/tables.jsonl
Find what interface a facade proxies
grep "Transactions" .phpnomad/facades.jsonl
Pipe into jq for structured output
grep "PayoutService" .phpnomad/dependency-map.jsonl | jq '.edges[] | select(.type == "injects") | .target'
This extracts just the constructor dependencies for PayoutService.
Token efficiency
The index is designed for AI agent consumption, where every token costs money and time. Here are real measurements from a project with 1,019 classes.
| Query | Index size | Raw source size | Savings |
|---|---|---|---|
| Reverse dependency lookup on EventStrategy | 7 KB | 394 KB | 54x |
| Full boot sequence | 10 KB | 315 KB | 32x |
| All task handlers | 0.3 KB | 37 KB | 109x |
The savings come from two factors. First, the index stores only the structural information that tools and agents need, not method bodies, comments, or whitespace. Second, JSONL lets you query a single line instead of reading the entire file.
For an AI agent that needs to understand how an interface is used across a project, grepping dependents-map.jsonl returns one line with all the information. The alternative is reading dozens of source files to manually trace imports and constructor parameters.
Next steps
- New to the indexer? Read the introduction for how the indexing pipeline works.
- Want to run the indexer? See the commands reference for
phpnomad index. - Looking for the CLI overview? Check the CLI package introduction.