Captures the framework architecture, the docker-based PHP/Composer runtime, the components-outer game subtree convention, the PatternParser-and-named-groups gotcha, the workflow conventions that emerged during the rename and ProjectZomboid build-out, and the synthetic-only fixture rule for committed test data. Adds .claude/ and .claude.local.md to .gitignore so Claude session state and personal overrides don't ride along.
7.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
indifferentketchup/codex — a generic PHP log parsing and analysis framework, plus per-game subclasses that adapt the framework to specific games' log formats. PHP >=8.4, MIT license. Forked from aternos/codex; namespace was renamed in-tree (Aternos\Codex → IndifferentKetchup\Codex) — only the LICENSE retains the original Aternos GmbH copyright line, which must remain byte-for-byte (MIT requires it).
Local environment
PHP and Composer are not installed on the host. All Composer/PHPUnit invocations go through the official composer:latest Docker image (currently PHP 8.5, satisfies the >=8.4 floor):
docker run --rm -v "$(pwd):/app" -w /app -u "$(id -u):$(id -g)" composer:latest <subcommand>
Use $(pwd) or an absolute path — bare $PWD has misfired here, mounting nothing and silently no-op'ing the run.
Common commands
- All tests:
composer test(=phpunit test/testspercomposer.json) - One test file or method (wrap in the same docker invocation):
docker run --rm -v "$(pwd):/app" -w /app -u "$(id -u):$(id -g)" composer:latest vendor/bin/phpunit --filter=testFooBar test/tests/path/to/SomeTest.php - Refresh autoloader after editing
composer.json:composer dump-autoload - After cloning:
composer install(writesvendor/, gitignored)
Framework architecture
LogFile (Path|String|Stream)
│
▼
Log ── extends AnalysableLog ── implements DetectableLogInterface
│ │ │
│ │ └─ static getDetectors(): Detector[]
│ └─ static getDefaultAnalyser(): Analyser
├─ static getDefaultParser(): Parser
│
▼ Log->parse()
Entry[] of Line[] (each Entry has level, time, prefix, lines)
│
▼ Log->analyse()
Analysis of Insight[]
└── Information (label + value) or
Problem (with attached Solution[])
Detectiveranks candidate Log subclasses by running each candidate'sgetDetectors()and picking the highest-scoring result (bool|float). It receives aLogFile, returns a constructedLogsubclass.PatternParseris regex-driven. Lines that don't match the LINE regex append to the previousEntry— this is the mechanism that handles multi-line records like Java stack traces under an ERROR header.PatternAnalyserwalks entries, runs each registered insight class's staticgetPatterns()against entry text viapreg_match_all, and emits coalesced insights (equal insights bump a counter instead of duplicating).- Detectors available out of the box:
SinglePatternDetector,WeightedSinglePatternDetector,LinePatternDetector(returns match ratio),MultiPatternDetector(AND), and the path-basedFilenameDetector(usesLogFileInterface::getPath(), returnsfalsewhen no path is available).
Game subtrees
Layout is components-outer with game suffix, not games-outer:
src/<Component>/<Game>/... e.g. src/Log/ProjectZomboid/ProjectZomboidServerLog.php
src/Pattern/<Game>/<Type>Pattern.php (regex string constants; not a framework abstraction)
test/tests/Games/<Game>/...
test/src/Games/<Game>/fixtures/<type>-minimal.txt (synthetic fixtures only)
Scaffolded games: Minecraft, Hytale, SevenDaysToDie (stubs only — empty .gitkeeps plus a TODO <Game>Detective extending base Detective). ProjectZomboid is fully implemented (11 log subclasses, 11 pattern classes, detective wired with all 11, synthetic fixtures, dispatch tests).
src/Pattern/ is not a framework abstraction — patterns are plain string class constants. Each <Type>Pattern typically holds a LINE constant for the parser plus named-group extractor constants (FIELDS, COMBAT, MOD_LOAD, etc.) for analysers.
ProjectZomboid specifics
- Two abstract bases:
ProjectZomboidLog(TIME_FORMAT = 'd-m-y H:i:s.v', UTC default,makePatternParser()helper) andProjectZomboidEventLog(marker for the ten single-line logs;ProjectZomboidServerLogextends the parent directly because it permits multi-line entries). ProjectZomboidDetective::__construct()pre-registers all 11 log classes — instantiate it and callsetLogFile(...)->detect().
Standard test template for a Log subclass
At minimum: (1) entry count after parse() matches the synthetic fixture's line count, (2) one or more named-group FIELDS regexes from the <Type>Pattern class extract correctly from a representative line, (3) Detective handed the fixture path returns an instance of this Log class. Use #[DataProvider] when the same shape repeats per file.
Pitfalls
PatternParseris incompatible with named regex groups. PHP'spreg_matchreturns named groups plus their numeric duplicates in the same array;PatternParser's foreach iterates both and throws on the string-key entries. Convention:LINEregexes (used by the parser) use unnamed groups with field order documented in the Pattern class's docblock. Named groups are fine inside extractor regexes invoked from analysers, sincePatternAnalyserhands the whole match array toInsight::setMatches.- PHPUnit 12 requires the
#[DataProvider('methodName')]attribute. The legacy@dataProviderannotation silently passes zero args and fails withArgumentCountError. Level::fromString()defaults toLevel::INFOfor unknown tokens. Project Zomboid log levels map:LOG/INFO→ INFO;WARN→ WARNING;ERROR→ ERROR.PatternParsermatches array must declare a match-type for every capture group in the regex (TIME,LEVEL, orPREFIX); otherwise the parser throws on the unmapped index. Use non-capturing groups(?:...)for fields you want to skip.
Workflow conventions
- One commit per concrete log type when adding game support: pattern class + log subclass + synthetic fixture + test in a single commit, run
composer test, then move on.<Game>Detective::__construct()wiring goes in its own follow-up commit once all log types are present. - Out-of-scope cleanup goes in its own commit. Tempting workflow/lint fixes (e.g. deprecated CI syntax, comment hygiene) noticed mid-feature should not be folded in — separate commit or follow-up PR.
- Pre-destructive checkpoint pattern. Before bulk renames/moves:
git commit --allow-empty -m "pre-X checkpoint"as a revert anchor. Skip the empty slot if it produces no diff at the end of a plan.
Privacy / fixture rules
Logs.zipat the repo root contains real production server data (Steam IDs, player names, world coordinates). It is gitignored.- Extract for reference:
unzip -q Logs.zip -d .scratch/pz/. Real logs then live under.scratch/pz/Logs/(gitignored). Use only as format reference. Do not paste raw Steam IDs, player names, or coordinates into chat output, commit messages, or any committed file. - All fixtures committed under
test/src/Games/<Game>/fixtures/must be synthetic, hand-crafted from the observed format with placeholder identifiers:76561198000000001/2/3for Steam IDs,Player1/Player2/AdminUserfor names, generic coords (1000-1100, 2000-2200, 0).