# CLAUDE.md

Guidance for Claude Code when working in this repository. Read this first; it answers "what is this project?", "how do I build and test it?", and "how should I turn a Jira ticket into a code change?".

## What this project is

**p4php** is a PHP C extension (written in C++) that wraps the **Perforce C++ API**, giving PHP applications direct access to a Perforce server. It supports **PHP 7.x and PHP 8.x**.

This is a **C extension**, not a PHP library. Do not propose pure-PHP refactors — PHP-visible behavior is implemented in `.cc` / `.h` files and registered into the Zend engine.

- Module entry point: `perforce.cc`
- Main class implementation: `php_p4.cc` / `php_p4.h`
- Build system: autoconf (`config.m4` for Unix, `config.w32` for Windows)
- Tests: PHPT format under `tests/`
- License: BSD-2-Clause (see `LICENSE.txt`)

## Source map

Top-level layout:

| Path | Role |
|---|---|
| `perforce.cc` | Extension module init, `MINFO`, function table |
| `php_p4.cc` / `php_p4.h` | The `P4` class — methods, magic getters/setters, function entries |
| `php_clientapi.cc` / `.h` | Wrapper around Perforce `ClientApi` |
| `php_clientuser.cc` / `.h` | Wrapper around Perforce `ClientUser` (callbacks from server) |
| `php_p4_map.cc` | `P4_Map` class (Perforce `MapApi` wrapper) |
| `php_p4_exception.cc` | Exception class — reuse this; don't invent new error paths |
| `php_p4_result.cc` | Result-object plumbing |
| `specmgr.cc` | Spec manager (form parsing/serialization) |
| `tests/` | PHPT tests (`001.phpt` … `032.phpt`); requires `tests/php.ini` |
| `sanity_test/` | Per-OS Docker test envs (Centos7, Rocky 8/9/10, Ubuntu 16/18/20) |
| `docker/linux`, `docker/windows` | Build containers |
| `config.m4` / `config.w32` | Build config; key flag is `--with-perforce=<p4api-dir>` |
| `LICENSE.txt`, `CODE_OF_CONDUCT.md`, `mend.config`, `.p4ignore` | Externally managed — do not edit without explicit ask |

## Build

The P4 C++ API headers must be available at `<p4api>/include/p4/clientapi.h`.

**Unix:**
```
phpize
./configure --with-perforce=/path/to/p4api
make
```

Optional flags exposed by `config.m4` include OpenSSL location overrides — check `config.m4` before adding new ones.

**Windows:** use `config.w32` with `phpize` from the PHP SDK.

**Clean rebuild:** `phpize --clean && phpize && ./configure --with-perforce=... && make`.

## Test

Tests are PHPT (PHP's native test format). Each file has `--TEST--`, `--FILE--`, `--EXPECT--` (or `--EXPECTF--`) sections.

```
make test
# or directly:
php -n -c tests/php.ini run-tests.php tests/*.phpt
```

Every new public method or behavior change needs a matching numbered `.phpt` test. Pick the next free number after the highest existing one (currently `032.phpt`).

When a test fails, the runner writes `tests/<name>.diff`, `.out`, `.exp`, and `.log` files alongside the `.phpt`. Read the `.diff` first — it shows expected vs actual. These artifacts are listed in `.p4ignore` so they don't pollute the depot.

## Debugging a crashing extension

The PHP↔C boundary makes most bugs show up as segfaults, leaks, or refcount errors rather than PHP exceptions. Useful entry points:

- **Reproduce under gdb:** `gdb --args php -n -c tests/php.ini -d extension=$(pwd)/modules/perforce.so script.php`. Run the failing case, then `bt full` on crash.
- **Disable Zend's pool allocator** (so memory bugs surface earlier and tools see real allocations): `USE_ZEND_ALLOC=0 php …`
- **Valgrind:** `USE_ZEND_ALLOC=0 valgrind --leak-check=full --suppressions=$(php-config --prefix)/share/php/valgrind.supp php -n -c tests/php.ini script.php`
- **Refcount sanity:** build PHP with `--enable-debug` to get the Zend MM debug output and refcount asserts.

If the user reports a crash, ask which PHP version (7.x vs 8.x) and which P4 C++ API version it reproduces against — the cross-product matters.

## ABI and API stability

p4php is loaded as a shared object into PHP processes that may also load other extensions. Treat the PHP-visible surface as a public API:

- **Don't change the signature** of an existing `PHP_METHOD` (parameter count, types, by-ref vs by-val) without a deprecation cycle. Add a new method instead.
- **Don't rename** a public method or class constant. Aliases are cheaper than breakage.
- **Adding** a new method, class, or constant is fine and does not break ABI.
- **Internal C++ symbols** (helpers, statics, file-local classes) are not public — refactor freely.
- The Perforce C++ API itself has its own version skew; if a change requires a newer P4 API, call it out in the commit and bump the documented minimum in `config.m4` comments.

## Conventions and gotchas

**PHP 7 + 8 compatibility.** Code must compile on both. Watch for:
- `ZEND_PARSE_PARAMETERS_*` macros (preferred) vs older `zend_parse_parameters`
- `zval` API differences and reference-counting changes
- `zend_string` vs raw `char*` lifetime rules

**Memory across the PHP↔C boundary.** Use Zend allocators (`emalloc`, `efree`, `zend_string_init`, `zend_string_release`). Do not mix C++ `new`/`delete` with objects handed to PHP — they'll be freed by the engine.

**Errors and exceptions.** PHP-visible errors raised from C++ go through `php_p4_exception.cc`. Reuse it.

**Registering new PHP-visible methods.** Add the implementation as a `PHP_METHOD(P4, ...)` (or class equivalent), then register it in the class function table in `php_p4.cc`. Argument info (`ZEND_BEGIN_ARG_INFO_*`) goes alongside.

**Don't touch without asking.** `LICENSE.txt`, `mend.config`, `CODE_OF_CONDUCT.md`, `.p4ignore` — these are externally managed.

**Style.** No formal lint/format config exists in the repo. Match the surrounding file's brace style, indentation (tabs vs spaces), and naming (`php_p4_*` for PHP-side glue, `clientapi`/`clientuser` mirror the Perforce API names).

## Cross-distro build matrix

`sanity_test/` is the canonical matrix — each subdirectory is a Dockerfile that builds and tests p4php against a target OS / PHP combination. When a fix is OS- or libc-specific, reproduce it in the matching `sanity_test/<distro>/` image rather than only on the developer machine.

| Directory | Target |
|---|---|
| `sanity_test/Centos7` | CentOS 7 (older glibc; compatibility floor) |
| `sanity_test/RockyLinux8` / `RockyLinux9` / `RockyLinux10` | RHEL-family modern |
| `sanity_test/Ubuntu16` / `Ubuntu18` / `Ubuntu20` | Debian-family LTS range |

`docker/linux` and `docker/windows` are the build (not test) containers used to produce release artifacts.

## Working from a Jira ticket

When given a Jira description, follow this loop:

1. **Restate the ask** in one sentence so we agree on scope.
2. **Classify**: new method on `P4` / new class / bug fix / build or CI change / test-only change. Each lands in a different place — see the source map.
3. **Locate the file(s)** using the source map. Read the relevant file(s) before proposing edits.
4. **Propose the diff** in plain English (or as a small snippet) *before* editing — especially for anything touching the function table, exception flow, or refcounting.
5. **Implement** the C++ change.
6. **Add or update a PHPT test** under `tests/` (next free number, e.g. `033.phpt`). The test must pass with the new behavior and would fail without it.
7. **Build and run tests** locally (`make && make test`). If the environment doesn't have the P4 C++ API or PHP dev headers, say so explicitly — do not claim success based on "it should work."
8. **Summarize** what changed, what file(s) were touched, and what the new test covers.

### Definition of done

A change is done when **all** of these are true:

- [ ] C++ change compiles cleanly on PHP 7 and PHP 8 (no new warnings)
- [ ] A new or updated `.phpt` test exists, **passes with the change**, and **fails without it**
- [ ] `make test` reports no regressions in the rest of the suite
- [ ] No edits to externally-managed files (`LICENSE.txt`, `mend.config`, `CODE_OF_CONDUCT.md`, `.p4ignore`) unless the user asked for them
- [ ] No public-method signature changes or renames without a deprecation note (see ABI rules above)
- [ ] Summary post-message lists files touched, the new test name, and build/test outcome — including an explicit "could not build/test in this environment" if that's the case

## Suggested Claude Code setup beyond this file

The items below are recommendations to layer onto this `CLAUDE.md` if you want a fuller workflow. They are not required for `CLAUDE.md` to be useful.

### `.claude/commands/` — project slash commands

- **`/build [p4api-path]`** → runs `phpize && ./configure --with-perforce=$ARGUMENTS && make`, surfaces errors verbatim, stops on first failure.
- **`/test`** → runs the PHPT suite, summarizes PASS/FAIL with the names of any failures.
- **`/jira <pasted description>`** → prompt template that restates the ticket, maps it to files in the source map above, proposes a diff before editing, implements, adds a `.phpt`, then builds and tests.

Each is a markdown file in `.claude/commands/`. The body is appended to your prompt; `$ARGUMENTS` carries any text after the slash.

### `.claude/settings.json` — permissions and hooks

Keep prompts low for safe, repeated commands; keep guardrails on a small set of files.

`permissions.allow` (representative list):
- `Bash(ls:*)`, `Bash(find:*)`, `Bash(grep:*)`, `Bash(rg:*)`, `Bash(wc:*)`, `Bash(file:*)`
- `Bash(git status)`, `Bash(git diff:*)`, `Bash(git log:*)`, `Bash(git show:*)`, `Bash(git blame:*)`
- `Bash(phpize)`, `Bash(phpize --clean)`
- `Bash(./configure:*)`, `Bash(make)`, `Bash(make test)`, `Bash(make clean)`
- `Bash(php --version)`, `Bash(php -i)`, `Bash(php -m)`
- `Bash(php -n -c tests/php.ini:*)`

`permissions.deny`:
- `Bash(rm -rf:*)`
- `Write(./LICENSE.txt)`, `Write(./mend.config)`

`hooks`:
- `PreToolUse` matching `Edit|Write` on `LICENSE.txt|mend.config|.p4ignore` → block with a message asking for explicit confirmation.
- `Stop` → terminal bell / notification so you know when a long build or test run finishes.

## Out of scope (today)

- No Atlassian Rovo MCP / `/jira <KEY>` auto-fetch — Jira descriptions are pasted manually.
- No CI workflow files (`.github/` does not exist).
- No code-style or linter config (`.editorconfig`, clang-format) — none exists today; add separately if desired.
