feat(mapper): add simplicio.mechanical-edit/v1 anchor producer (closes #110)#116
Merged
Merged
Conversation
Closes #110. Adopts the canonical cross-Simplicio mechanical edit contract pinned in simplicio-runtime#69. The mapper is now the deterministic producer of the edit-anchor context an LLM planner needs to plan compact JSON edits without rewriting whole files. Adds `simplicio_mapper.mechanical_edit` exposing: - `snapshot_hash(text)` — sha256 of the full file content. - `range_hash(text, start, end)` — sha256 of the 1-indexed inclusive line slice. Raises ValueError on out-of-bounds. - `is_binary(path)` — heuristic NUL-byte / UTF-8 decode probe over the first 8 KiB. - `extract_file_entry(root, path, ranges)` — builds the per-file dict with language detection (reusing `mapper._language_for`), full snapshot hash, and a `selected_ranges[]` list with `start_line`, `end_line`, `before_hash`, and `must_contain` (first + last line of the range, truncated to 120 chars). Files longer than `COMPACT_LINE_THRESHOLD` (2000 lines) omit `must_contain` so the envelope stays compact while still anchoring via the hash. - `build_context(root, selections)` — groups selections by path, builds per-file entries, and emits the full envelope with `schema="simplicio.mechanical-edit/v1"`, an overall `context_hash` over all snapshot + range hashes, and the canonical `mapper_schema` pointer to `simplicio.mapper-index/v1`. Missing files raise `FileNotFoundError` and binary files raise `ValueError` — the mapper never silently emits an ambiguous anchor. Tests (`tests/python/test_mechanical_edit.py`, 12 cases) cover stable range hashing, snapshot drift, anchor drift detection, missing-file refusal, binary-file refusal, large-file compact mode, multi-language fixture parity (TS / Python / JSON / Markdown), and deterministic context-hash agreement on repeated runs. Fixtures live under `tests/fixtures/mech-edit-host/` and ship with the test suite so downstream consumers can copy them. Documents the contract in `SIMPLICIO_INTEGRATION.md` next to the existing runtime contract section. https://claude.ai/code/session_01JdmemqddwFnvbceWyuDE8m
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new mapper-side producer module for the canonical simplicio.mechanical-edit/v1 contract, including deterministic snapshot/range hashing, file selection context building, and supporting fixtures/docs so downstream tooling can plan anchored edits without rewriting whole files.
Changes:
- Introduces
simplicio_mapper/mechanical_edit.pywith hashing helpers, binary refusal, and context envelope builder. - Adds a dedicated Python test suite plus multi-language fixtures to validate determinism, drift detection, and compact-mode behavior.
- Documents the mechanical-edit producer contract and usage in
SIMPLICIO_INTEGRATION.md.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
simplicio_mapper/mechanical_edit.py |
New producer helpers for simplicio.mechanical-edit/v1 context envelopes and anchor hashing. |
tests/python/test_mechanical_edit.py |
New unit tests covering hashing stability, drift detection, refusal modes, compact mode, and determinism. |
tests/fixtures/mech-edit-host/sample.ts |
TypeScript fixture for multi-language hashing/anchor tests. |
tests/fixtures/mech-edit-host/sample.py |
Python fixture for multi-language hashing/anchor tests. |
tests/fixtures/mech-edit-host/sample.json |
JSON fixture for multi-language hashing/anchor tests. |
tests/fixtures/mech-edit-host/sample.md |
Markdown fixture for multi-language hashing/anchor tests. |
tests/fixtures/mech-edit-host/binary.bin |
Binary fixture to validate binary refusal behavior. |
SIMPLICIO_INTEGRATION.md |
Documentation describing the mechanical-edit producer contract, usage, and guarantees. |
Comment on lines
+99
to
+100
| def _absolute(root: str, path: str) -> str: | ||
| return path if os.path.isabs(path) else os.path.join(root, path) |
Comment on lines
+105
to
+125
| abs_path = _absolute(root, path) | ||
| if not os.path.exists(abs_path): | ||
| raise FileNotFoundError(f"file not found: {path}") | ||
| if is_binary(abs_path): | ||
| raise ValueError(f"binary file refused: {path}") | ||
| text = _read_safe(abs_path) | ||
| compact = len(text.splitlines()) > COMPACT_LINE_THRESHOLD | ||
| selected = [] | ||
| for start, end in ranges: | ||
| selected.append({ | ||
| "start_line": start, | ||
| "end_line": end, | ||
| "before_hash": range_hash(text, start, end), | ||
| "must_contain": _must_contain(text, start, end, compact), | ||
| }) | ||
| return { | ||
| "path": path.replace(os.sep, "/"), | ||
| "language": _language_for(abs_path), | ||
| "snapshot_hash": snapshot_hash(text), | ||
| "selected_ranges": selected, | ||
| } |
| with tempfile.TemporaryDirectory() as tmp: | ||
| root = Path(tmp) | ||
| big = root / "huge.py" | ||
| big.write_text("\n".join(f"line_{i}" for i in range(COMPACT_LINE_THRESHOLD + 50))) |
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.
Resumo
Fecha #110. Implementa o lado do mapper como produtor do contrato
simplicio.mechanical-edit/v1(canônico em simplicio-runtime#69).Mudanças
simplicio_mapper/mechanical_edit.py(novo):snapshot_hash(text),range_hash(text, s, e),is_binary(path),extract_file_entry(root, path, ranges),build_context(root, selections).MECHANICAL_EDIT_SCHEMA,MECHANICAL_EDIT_RESULT_SCHEMA,MAPPER_INDEX_SCHEMA.FileNotFoundError) e binary file (ValueError) — nunca emite âncora ambígua.COMPACT_LINE_THRESHOLD=2000— arquivos grandes mantêmbefore_hashmas omitemmust_contain.tests/python/test_mechanical_edit.py— 12 testes cobrindo: stable range hashing, snapshot drift, anchor drift, missing/binary refusal, large-file compact mode, multi-language (TS/Py/JSON/MD), deterministiccontext_hash.tests/fixtures/mech-edit-host/—sample.ts,sample.py,sample.json,sample.md,binary.bin.SIMPLICIO_INTEGRATION.md— nova seção "Mechanical Edit Contract" documentando producer + exemplo + garantias.Safety rule
Esquema canônico, sem variação repo-local — campos novos em v1 são aditivos; rename/remove exige v2 + ADR.
Validação
unittest tests.python.test_mechanical_editruff checknpm run lintRefs #110, simplicio-runtime#69.
https://claude.ai/code/session_01JdmemqddwFnvbceWyuDE8m
Generated by Claude Code