Repository navigation
How It Works
rosetta rests on one idea: a prompt's meaning lives in a small structured record, and the packet a model reads and the document a person reads are two renderings of that record. Because either rendering can be edited directly, the tool cannot trust a timestamp or a diff; it keeps a lock file with the SHA-256 hash of each rendering as it stood at the last successful sync, and it decides everything from a comparison with those two hashes.
For an artifact called <name>, a directory holds four files that the tool manages, and a fifth that belongs to the user.
| File | Role | Who writes it |
|---|---|---|
<name>.fields.json |
The canonical record: a schema_ver and a fields object |
The user at the start; sync afterward |
<name>.cpn |
The compact packet: SCHEMA_VER= first, then KEY=VALUE lines in sorted order, LF endings, one trailing newline |
Sync |
<name>.md |
The human view: a generated block, then a hand-written notes region | Sync for the block; the user for the notes |
<name>.rosetta.lock |
The SHA-256 hashes of the .md and .cpn at the last sync |
Sync |
<name>.schema.json |
A JSON Schema for validation | The user |
The Markdown block carries three things: a ROSETTA:FIELDS comment holding the fields as JSON, a ROSETTA:SCHEMA_VER comment, and one bullet per field. The comment is what sync reads when the Markdown changes, so the bullets exist for the human reader and the comment exists for the machine.
flowchart TD
A["rosetta sync name"] --> B{"Lock file holds hashes"}
B -- "No" --> C["bootstrap: write .cpn and .md from fields.json, write lock"]
B -- "Yes" --> D{"Views that differ from the lock"}
D -- "Both" --> E["CONFLICT: list differing keys, exit 1, write nothing"]
D -- "Packet only" --> F["machine-to-human: parse packet, save fields.json, regenerate .md, keep notes"]
D -- "Markdown only" --> G["human-to-machine: read FIELDS comment, save fields.json, regenerate .cpn"]
D -- "Neither" --> H["no-op"]
The fields file is read at the start of every run and written during the two one-way paths, yet it takes no part in the decision. A change to fields.json alone therefore matches the "Neither" branch, and Limits and Next Steps treats that as the principal limit.
| Module | Role |
|---|---|
fields.py |
FieldsStore: load and save the JSON record with sorted keys and LF endings |
cpn.py |
serialize_packet and parse_packet; floats are formatted to two decimals, and every parsed value is a string |
human_doc.py |
Render the Markdown view; extract the fields comment, the schema comment, and the notes; raise MalformedRosettaDoc when a marker is missing |
hashing.py and lockfile.py
|
SHA-256 of a text, and the two-hash lock file |
sync.py |
The decision above, plus SyncConflict, which carries the sorted list of differing keys |
schema.py |
Draft 7 validation, with numeric strings coerced to the type the schema declares |
loader.py |
load_prompt: serve the packet if it parses and validates, otherwise the Markdown as a full replacement, otherwise raise PacketUnavailable
|
cli.py |
sync, compile (an alias), validate, and expand
|
The example artifact rosetta_build holds three fields: CONFLICT_POLICY, DECISION_COUNT, and FIELD_VALUE_TYPES. Its packet begins SCHEMA_VER=1.0 and contains the line DECISION_COUNT=3.
- A user changes that line to
DECISION_COUNT=4and runsrosetta sync rosetta_build --dir <folder>. - The lock file holds hashes for both views. The packet's hash no longer matches, and the Markdown's hash still does, so the branch is "Packet only".
-
parse_packetreads the packet and returns the schema version and a fields dictionary in which every value is a string, soDECISION_COUNTis now"4". - The fields store is saved with that dictionary, which is why
rosetta_build.fields.jsonchanges from the integer3to the string"4". -
extract_noteslifts the hand-written notes out of the existing Markdown,render_human_docrebuilds the generated block from the new fields, and the notes go back in unchanged. A comparison of the Markdown before and after shows two changes, theROSETTA:FIELDScomment and theDECISION_COUNTbullet, plus two blank lines around the notes region that the renderer normalizes; the notes text itself is identical. - The lock is rewritten with the hash of the new Markdown and the hash of the packet as the user left it, and the command prints
sync: machine-to-human.
rosetta validate then reads the fields file, finds "4" where the schema expects an integer, coerces it, checks the minimum of 0, and prints valid. Every step above was reproduced from the command line for this wiki.
The loader is the reason the Markdown exists as more than documentation. A consumer calls load_prompt(name, directory, schema); the packet is returned verbatim when it parses and passes the schema, and the Markdown is returned in its place when the packet is missing, malformed, or invalid. The Markdown is never appended to the packet. The tests cover four cases: a valid packet, a missing packet, a packet that fails the schema, and both files missing, which raises PacketUnavailable. A packet that does not parse, for example one without a SCHEMA_VER line, takes the same fallback in the code, although no test pins that case.
Designed and authored by Daniel Boles; implemented with coding agents. Licence: Apache-2.0.