Skip to content
docsv0.44.3

ImplementHandler

Write the body of a class that `make` already scaffolded — through a gate, not around it. ── WHY THE UNIT IS A CLASS AND NOT A FILE PATH ────────────────────────────────────────────────── A generic file write is the ungovernable extreme: its target is a path nobody's request names, the intent contract has nothing to compare, and its blast radius is the filesystem. A CLASS in a plugin this app scaffolded is the governable unit: the target is a name the human's request can carry (the intent contract applies, ADR-0044), and the path is DERIVED — searching only inside `src/Plugins/<plugin>/` — so escaping the tree is impossible by construction, not by discipline. ── LANDING IS A POSTCONDITION, NOT A HOPE ─────────────────────────────────────────────────────── Measured on three real sessions (long-session-three-arms.tsv): everything the catalogue cannot express, the agent fakes with structure that LOOKS like the task. Raising the ceiling without a gate would trade unfilled shells for broken files. So the content verifies BEFORE it lands — syntax (`php -l` on a temp copy), strict types, the class it claims, the namespace its location dictates — and on any failure the original survives byte for byte and the diagnostic travels back, which is what a model corrects from. ── STATIC CONFORMANCE, MEASURED BEFORE IT WAS ADDED ───────────────────────────────────────────── The first real run (implement-first-landing.tsv) landed clean four times and still fataled three: invented collaborators and a narrowed interface signature — linkage defects `php -l` cannot see. Replayed against PHPStan level 0 with the app's autoloader, BOTH classes of failure were named surgically in ~1s and the good landing passed in 0.8s. So when the app ships an analyzer, the gate uses it: the candidate is analyzed IN PLACE — context needs the app's autoloader — and on any finding the original is restored byte for byte. Level 0 on purpose: this gate rules on linkage, not style; the app's own level belongs to its suite. ── THE BEHAVIORAL JUDGE: THE CLASS'S OWN TEST, INSIDE THE GATE ────────────────────────────────── Measured on a real session: a service landed conformant — syntax, interface, namespace clean — while its solicitar() SIMULATED persistence in a comment. Linkage had a judge and the app suite had one; behavior had none. The judge is never an LLM reading code (the substitute certificate P-0001 took seven generations to kill) and never the agent grading itself (the prior that faked the behavior would grade its fake): it is the test that already declares what the class must DO — `tests/Plugins/<plugin>/<Class>Test.php` — executed as one more landing postcondition, red restoring byte for byte. Without a test the landing still lands — Q-P19-R measured that OBLIGATING a criterion made everything worse — but the result SAYS the behavior went unjudged: a silent gap reads as covered, and that is how a simulated persistence ships wearing green. Running the class's test EXECUTES the written code, in a subprocess — the same risk class as the `test` operation, accepted for the same reason and said here. Fuller isolation is ADR-0045's. ── THE CAP: BOUNDED INPUT AFTER TRANSPORT DECODING ───────────────────────────────────────────── Historical truncated tool calls led to a 4,000-byte ceiling. That cannot repair a transport which has already decoded the arguments: greenhouse 0763 received a complete 7,854-byte body which the ceiling refused before any code judge could inspect it. Bound accepted content at 8 KiB, measured in bytes, and let the SAME syntax, linkage and behavior gates decide whether a complete body can land. Transport truncation remains a gateway failure, not a size diagnosis. Larger bodies use `mode=start` and `mode=append`, each within the same byte ceiling. These write unverified staging only; `mode=finish` judges the whole assembly through the single-shot gate. A partial file never claims verification or replaces the live scaffold.

ImplementHandler::__construct()

public function __construct(Milpa\DevTools\Support\RootResolver $roots = new RootResolver(), ?string $analyzer = null, ?string $behaviorRunner = null, Milpa\DevTools\Operations\ConstructionProbe $construction = new ConstructionProbe()):

Parameters

Parameters of __construct()
NameTypeDescription
$rootsMilpa\DevTools\Support\RootResolver
$analyzer(string | null)the static-analysis command, or `null` to derive it from the app (`vendor/bin/phpstan`, when present). Tests inject a seam here — a gate only verifiable against a running binary is a gate nobody verifies in practice
$behaviorRunner(string | null)the command that runs one test file, or `null` to derive it (`vendor/bin/phpunit`, when present) — same seam, same reason
$constructionConstructionProbeasks the booted house to build a routed class — the request's own act (greenhouse decisions/0541)

ImplementHandler::handle()

public function handle(array $input): array

Land the complete body of one scaffolded class — or refuse with the diagnostic, original intact.

Parameters

Parameters of handle()
NameTypeDescription
$inputarray<string, mixed>

ImplementHandler::edit()

public function edit(array $input): array

Apply bounded exact pairs to a current source file and use the same landing judge. The input limit counts the transmitted pairs, not the already-installed bytes. No public input flag disables handle()'s inline ceiling, and existing multipart staging is untouched. Recorded proposals retain their host-specific protocol; this entry reads the current file.

Parameters

Parameters of edit()
NameTypeDescription
$inputarray<string, mixed>

ImplementHandler::scaffold()

public static function scaffold(string $root, string $plugin, string $class): ?string

The file `implement` and `edit` land a class on, or `null` when no scaffold declares it. The path is DERIVED, never received: the class is searched inside the plugin's own trees only — its sources, then its tests. A judge is a class too, and the TDD flow depends on it landing through the SAME gate before the body it will judge. Public so a host can know, before it asks a person to confirm a call, that the call has nothing to land on.

Parameters

Parameters of scaffold()
NameTypeDescription
$rootstring
$pluginstring
$classstring

ImplementHandler::unscaffolded()

public static function unscaffolded(string $plugin, string $class, string $act): string

What a caller is told when nothing declares the class: `make` is the only door that creates one.

Parameters

Parameters of unscaffolded()
NameTypeDescription
$pluginstring
$classstring
$actstring