D3: the F1 hook_json family engine — claude_code, codex_cli, kimi_code, devin folded to engine + config - #97
Merged
Conversation
Extend the vendor config schema with the dialect-families.md §3.1 renderer data D2 deferred: verdict word tables, degradation-note strings (verbatim from the adapters, so wire output stays byte-identical), reason defaults, note style, hookEventName echo source, context-event lists, and the hook_entry bare flag. Claims gain accept_names, accept_when_all and reject_markers_unless_probe, replacing three notes-only caveats with checkable data. Recount derives the new fields: devin's PermissionRequest gate is replayed from the frozen pre_tool payload (the W37 _EXTRA_GATE_NAMES generalization), bare is classified from the fixture's hook_config shape, and the word/note tables are pinned in tables.py where every string is exercised by the golden fixture replay. Entries regenerated via tools/recount_vendor_config.py --write; only the four fold-candidate vendors change. Signed-off-by: Claude <noreply@anthropic.com>
…ok_json engine The F1 engine (dialect-families.md D3): adapters/_hook_json.py executes a vendor's data/vendors entry -- marker claims, ordered field-fallback parse, G1/G2 verdict renderers, shared hookSpecificOutput context and transform bodies -- with adapters/_hook_entry.py rendering hook_config (and kimi's TOML) and adapters/_family.py binding one entry behind the unchanged adapters.get() surface. The four modules are deleted; their golden wire fixtures pass byte-for-byte unchanged, which was the acceptance gate. vscode_copilot stays a dialect module by design: its three-path claims() and memory-tool parse branching are payload-content branching that §3.1's config/code line keeps as code; recorded as the answered dual-casing [h] in the design doc. bundle() gains an engine+config composition for config-driven agents (contract + probes/windows where the entry needs them + engine + inlined VENDOR literal + runtime) and still splices the other eight legacy-style. looks_like_claude_code moves to adapters/_probes.py, shared with gemini_cli. Recount reads fields/wire_events off the bound entry for config-driven vendors (the config IS parse() there; behaviour stays held by the fixtures and per-adapter suites). New schema fields carry mutation tests and behavioural replay tests; measured bundle sizes are published next to the design's §4 estimates. Signed-off-by: Claude <noreply@anthropic.com>
open-coder-ai
marked this pull request as ready for review
September 1, 2026 11:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Four of the five F1
hook_jsonadapters (claude_code,codex_cli,kimi_code,devin) are re-expressed as one shared engine driven by theirdata/vendors/<agent>.jsonentries (docs/design/dialect-families.mdD3); their hand-written modules are deleted.vscode_copilotdeliberately stays a dialect module (see Notes). The golden wire fixture suite passes byte-for-byte with zero changes undertests/fixtures/golden/or totests/test_golden_fixtures.py— that empty diff is the acceptance gate for the whole fold.What lands:
adapters/_hook_json.py— the family engine: markerclaims(), ordered field-fallbackparse(), G1/G2 verdict renderers plus the sharedhookSpecificOutputcontext/transform bodies, every function taking the vendor entry as data.adapters/_hook_entry.pyrendershook_config()(hooks-map / bare-map / flat-list wrappers, windows-wrapper extras, kimi's TOML emitter).adapters/_family.pybinds one entry behind the unchangedadapters.get()surface (all module attributes tests rely on —EVENT_MAP,DECISION_VOCABULARY,BLOCKING_EVENTS,render_config, … — are derived from the entry).adapters/_probes.pyholdslooks_like_claude_code, now shared by the engine andgemini_cli.Schema + entries gain the §3.1 renderer data D2 deferred: verdict
words,degrade_notes(verbatim strings from the deleted adapters, so the wire cannot move),reason_defaults,note_style,echo, context-event lists — plus three claims fields that turn notes-only caveats into checked data (accept_names,accept_when_all,reject_markers_unless_probe) andhook_entry.bare. All entries regenerated viatools/recount_vendor_config.py --write, never edited by hand; devin'sPermissionRequestgate is now recounted explicitly by replaying the frozen pre_tool payload with the event name swapped (the generalization W37's report suggested).bundle()composes engine + inlinedVENDORliteral for config-driven agents and still splices the other eight legacy-style. Bundle line counts, measured at this PR's base vs. this PR (the design's §4 [h] asks for real numbers; estimates were +20–30%):Slightly above the §4 estimate; the numbers and the payoff side (624 deleted adapter lines → ~390 shared engine lines + data) are recorded in the design doc's §7.
Claim check
test_config_gates_are_a_subset_of_matrix_block_capable_events, and devin's addedPermissionRequestgate maps to canonicalpre_tool, already block-capable in its rowMATRIXrow carries averifiedrecord — no matrix rows changedtests/fixtures/golden/<agent>.json, the D1-frozen capture of the real adaptersChecks
pytest -qpasses — 1349 passed, 4 skipped (baseline was 1320/4; +29 tests, none removed)ruff check .andruff format --check .pass (--select Ia no-op)check_stdlib_only.pyand the AST check both green)git commit -s)Also run locally:
examples/generate.py --check(13 pages unchanged),docs/assets/gen_brand_assets.py --check, CLI smoke (--version,agents,matrix,doctor), and the 12-agent × 4-outcome bundler subprocess replay.Notes for the reviewer
vscode_copilotis not foldable inside the design's §3.1 config/code line: itsclaims()accepts by three disjoint paths (envelope marker; lowercase-first names with their own conditional reject list; a memory-tool call carrying no event name at all), andparse()branches ontool ∈ MEMORY_TOOLS ∧ command ∈ MEMORY_WRITE_COMMANDSto select a second field-chain set — branching on payload content, which §3.1 says is dialect code. Encoding that as config means either a rule interpreter (the "worse programming language" §3.1 refuses) or a named accept-probe, i.e. widening thereject_probescrack to the accept side — a design decision I'm surfacing rather than making. The §7 dual-casing [h] is answered accordingly in the design doc.additionalProperties: falseand covered by new mutation tests (a typo'd word/note key fails loud). Mutation checks were run beyond the schema: a mutated note string fails the golden replay; a flippedhonours_transformvisibly changes the wire.fields/wire_eventsare read off the bound entry (the entry isparse()there — the engine executes it, so a second derivation source no longer exists); gates are still replayed from the frozen fixtures, and behaviour is held by the golden suite plus the untouched per-adapter suites. The word/note tables live intools/recount/tables.pywith the claims tables, every string exercised by the fixture replay on every run.dispatch.handle, untested before and after) behaviour notes:respond()with a rawWARNat a G2 gate is now uniformly silent (the old claude_code emitted a barehookEventNamebody; codex/kimi already returned""), andtest_function_local_imports_are_left_alonesamplesvscode_copilotnow that the engine bundles legitimately contain no function-local imports.