Skip to main content

phpkg

A code knowledge graph for large legacy PHP codebases — CakePHP 2/3 and Laravel — served to coding agents over MCP.

Legacy PHP defeats both of an AI agent's usual moves. The files are too big to read, and the symbol names are too repetitive to grep. In the codebase this was built against:

File Lines Tokens to read it get_file method index Saving
OrderRepository.php 34,053 ~447,000 4,249 99.0%
PaymentsController.php 30,267 ~399,500
UsersController.php 18,395 ~212,700 10,427 95.1%

Reading one of those files can consume an entire context window. Meanwhile 279 methods in that codebase are named index, 230 add, 211 edit — so grep "function index" returns 279 hits with no way to rank them.

phpkg parses the codebase once with tree-sitter, resolves it into a graph (symbols, callers, routes, model associations, DB-table access), and serves 36 tools over MCP so your agent asks precise questions instead of reading files.

get_symbol("applyScheme")     → file + exact start_line/end_line  (~200 tokens)
get_callers("applyScheme")    → real CALLS_METHOD edges, not text matches
table_consumers("orders")     → every reader/writer, across every stack
blast_radius("applyScheme")   → what breaks if you change it

Install

Claude Code (plugin — MCP + skill + hooks in one)

/plugin marketplace add ArcXzost/phpkg
/plugin install phpkg@phpkg

Then, in your PHP project:

/phpkg-setup

That detects your stacks, builds the graph, and verifies it. No Python setup — the plugin bootstraps its own environment via uv.

Any other agent (opencode, Cursor, Zed, Codex…)

uv tool install phpkg          # or: pipx install phpkg
phpkg init && phpkg index      # in your PHP project
phpkg mcp-config opencode --write

mcp-config supports claude-code, opencode, cursor, zed, codex and generic. It merges into an existing config rather than overwriting it.


What it indexes

Both stacks, one graph. Many legacy PHP estates run a monolith and a newer framework over the same database. phpkg joins them on physical table nodes, so you can ask what no single-stack tool can answer: if I change this repository, what on the other side of the codebase reads the tables it writes?

CakePHP 2/3 Controllers, actions, models, behaviors, components, helpers, templates, routes, shells
Laravel Controllers, Eloquent models, repositories, services, providers, routes, form requests, observers, container bindings
Shared DbTable nodes — the join between stacks

Convention-driven CakePHP and declaration-driven Laravel get separate resolvers, because they establish structure in opposite directions.

Commands

phpkg init          # detect stacks, write phpkg.yaml
phpkg index         # build the graph
phpkg watch         # incremental reindex on file changes
phpkg serve         # start the MCP server
phpkg status        # generation, index age, node/edge counts
phpkg summarize     # plain-English summaries (see below)
phpkg mcp-config    # emit MCP config for a given agent

Reindexing while the server is live

Kuzu takes an exclusive cross-process lock even read-only, so "server running" and "indexer running" cannot share one database file. phpkg writes each index run to a new generation and atomically flips a marker; the live server reopens on its next tool call. phpkg index is safe to run at any time, and phpkg watch is safe to leave running.

Semantic summaries (optional, no API key)

The graph knows where a method is and what calls it — never what it does. phpkg summarize fills that in, and your agent writes the prose, so there's no API key and no per-run cost:

phpkg summarize next <module> -o batch.json   # methods + source out
#   agent writes {id: sentence} into answers.json
phpkg summarize apply answers.json            # summaries in

Summaries survive reindexing for unchanged methods, so a re-run only costs effort for code that actually changed. Read them back with describe_symbol / describe_module. The module-level links (cross-module calls, shared tables) are derived mechanically from existing edges — those cost nothing and work immediately.

Configuration

phpkg init writes phpkg.yaml. Detection reads composer.json constraints and layout markers; it never infers a CakePHP major version from a directory name, because the wrong extractor produces a confidently empty graph rather than an error. If detection reports ambiguous, set stack: yourself.

repos:
  - name: legacy-monolith
    root: .
    stack: cake2          # cake2 | cake3 | laravel
    app_path: app
    exclude: [app/Vendor, vendor, app/tmp]
  - name: api
    root: app/laravel     # nested apps are normal
    stack: laravel
    app_path: app
    psr4: { "App\\": "app/" }

When not to use it

Grep is still the right tool for string literals, config values, comments, migrations and template bodies — anything that isn't a PHP symbol. phpkg replaces grep for structure, not for text.

Requirements

Python 3.10–3.13 (Kuzu and tree-sitter have no 3.14 wheels yet). No PHP runtime needed — parsing is static.

Releasing

Publishing runs on PyPI Trusted Publishing (OIDC) — no API token is stored anywhere. Tag and push:

git tag v0.1.0 && git push origin v0.1.0

CI runs the test matrix plus a clean-install job that builds the wheel, installs it with freshly resolved dependencies, and indexes a real project. That job exists because the dev tree pins dependencies via uv.lock and therefore cannot catch a break introduced by a newer release of one — which is exactly how xxhash 4.0 once produced a silently empty graph.

Status & contributing

Beta. Built against a ~60k-node, ~100k-edge production codebase; broader framework-version coverage is exactly where outside testing helps most. Issues and PRs welcome — especially CakePHP 3/4 and non-standard Laravel layouts.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

phpkg-0.1.0.tar.gz (326.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

phpkg-0.1.0-py3-none-any.whl (159.9 kB view details)

Uploaded Python 3

File details

Details for the file phpkg-0.1.0.tar.gz.

File metadata

  • Download URL: phpkg-0.1.0.tar.gz
  • Upload date:
  • Size: 326.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for phpkg-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5df77bafe8831062a64c2d211ef094e0297db826cd4914bdeda6e6fee99acb13
MD5 15111cf312f9608b4f2a72355ae861b8
BLAKE2b-256 cd50934d0b45b743a6e8dc1999bdab9545aed147b6765ef951e4904d256308d6

See more details on using hashes here.

Provenance

The following attestation bundles were made for phpkg-0.1.0.tar.gz:

Publisher: publish.yml on ArcXzost/phpkg

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file phpkg-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: phpkg-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 159.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for phpkg-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 69fc7f50f142a34874785a823c979d7c8cc86b8c66cda406f4d5f6000acd753b
MD5 9c51b9a07d797d7b707f7cc44cca8423
BLAKE2b-256 0e5f89f3f60318a61fe879cb1145913af1bd1ef59f84dab4483d62c24555fc67

See more details on using hashes here.

Provenance

The following attestation bundles were made for phpkg-0.1.0-py3-none-any.whl:

Publisher: publish.yml on ArcXzost/phpkg

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page