Skip to content

Commit f79ec1f

Browse files
cristianocclaude
andauthored
Semantic diff: write and draw changes as semantic objects (#8908)
Draw each record's forms Before and After on the Semantic Diff page Why: Reviewers reconstruct a change's effect on compiler objects (trees, Lambda, JavaScript, diagnostics, typing rules) from its code diff. A record states that effect in each object's own form, Before and After, and the review page draws it, so a reviewer reads the change at the level of the object it changes. Signed-off-by: Cristiano Calcagno <cristianoc@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 58da277 commit f79ec1f

6 files changed

Lines changed: 2102 additions & 1210 deletions

File tree

‎.claude/skills/records/SKILL.md‎

Lines changed: 143 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,162 @@
11
---
22
name: records
3-
description: Commit records and review pages for the ReScript compiler repository. Use when committing a change, when an agent reports its work, and when someone asks to review a commit, a pull request or a branch. Every commit message is a structured record that answers the questions ReScript reviewers ask; review pages are rendered from records with scripts/commitRecords.js, never written by hand.
3+
description: Commit records and Semantic Diff pages for the ReScript compiler repository. Use when committing a change, when an agent reports its work, and when someone asks to review a commit, a pull request or a branch. A commit message describes what the commit changes in the form suited to it (trees, code, diagnostics, typing rules, clause matrices, representations) Before and After; scripts/commitRecords.js renders the messages as a Semantic Diff page, one card per commit drawing its message.
44
---
55

6-
# Commit records and review pages
6+
# Commit records
77

8-
A **record** is a commit message whose body has named prose sections and
9-
whose last block is git trailers. Its fields are the questions reviewers of
10-
this repository ask on pull requests, answered before they are asked. A
11-
review page is rendered from records: one card per commit, and for a range a
12-
summary, a changelog draft and the files more than one commit changes.
8+
A record is a commit message that describes what the commit changes, each
9+
change in the form suited to what it changes: a parse tree or typed tree as
10+
the tree before and after, Lambda and generated JavaScript as code before
11+
and after, a diagnostic as its text, a typing rule as an inference rule,
12+
the match compiler's split as a clause matrix, a runtime representation as
13+
a mapping. `scripts/commitRecords.js` renders records as a Semantic Diff
14+
page, one card per commit, drawing each form in its own way, Before and
15+
After side by side.
1316

14-
## The record
17+
## The message
1518

1619
```
17-
<subject: present tense, what the compiler or tool now does>
18-
19-
<one paragraph: the change at the level of the language or the tool>
20-
21-
Example (rescript):
22-
<the ReScript source that shows the change, at most 15 lines>
23-
24-
Before (js):
25-
<generated JavaScript before, for that source>
26-
27-
After (js):
28-
<generated JavaScript after>
29-
30-
Source: <how the Before/After JavaScript was produced: compiler and revision,
31-
or the snapshot file in the diff>
32-
20+
<subject: what the compiler or tool does, present tense>
21+
22+
Why:
23+
<the problem the change solves>
24+
25+
AST modified (raw extensions):
26+
Example:
27+
let f = %raw(`(a) => a`)
28+
Before (parse):
29+
structure_item
30+
Pstr_value Nonrec
31+
<def>
32+
pattern
33+
Ppat_var "f"
34+
expression
35+
Pexp_extension "raw"
36+
After (parse):
37+
structure_item
38+
Pstr_value Nonrec
39+
<def>
40+
pattern
41+
Ppat_var "f"
42+
expression
43+
Pexp_raw "raw"
44+
Raw_template "(a) => a"
45+
46+
Review: <a question a reviewer asks, and its answer>
3347
Breaking: <what existing code or tooling stops working, and the migration>
3448
35-
Review: <a question a reviewer raised or will raise, and its answer; repeat>
36-
37-
Area: syntax printer frontend typechecker lambda codegen runtime rewatch
38-
analysis reanalyze gentype tools playground scripts ci docs tests
39-
Output: unchanged | changed <which programs>, <evidence>
40-
Formatting: unchanged | changed <which source shapes>
41-
Formats: none | <bumped magic numbers: ast cmi cmt cmj>
42-
PPX: unchanged | <effect on the frozen v0 PPX wire>
43-
Editor: unchanged | <effect on completion, hover, hints; fixtures>
44-
Tests: +added/path ~updated/path -removed/path | none, because <reason>
45-
Measured: <quantity before -> after, and what produced it>; ...
46-
PR: #<number>
47-
Signed-off-by: <Name> <email>
49+
Signed-off-by: Name <email>
4850
```
4951

50-
- **Prose sections** are a line `Name:` or `Name (lang):` followed by text or
51-
a block indented by 4 spaces. `lang` is `rescript`, `js`, `ts`, `sh`,
52-
`rust`, `ocaml` or `text`; the renderer highlights ReScript and JavaScript.
53-
`Before` and `After` sections of one language render side by side with
54-
changed lines marked. Use `Before (rescript)` / `After (rescript)` for a
55-
formatter change and `Diagnostic (text)` for compiler output. Any other
56-
name is allowed and renders as prose or code.
57-
- **The final block is git trailers**: `Key: value` lines, continuation lines
58-
indented by one space, nothing after them. `git interpret-trailers --parse`
59-
must read every key. `Signed-off-by` (DCO) is required on every commit.
60-
- **No changelog in the record**: the `changes/<name>.<category>.md`
61-
fragment the commit adds is the single source of truth for its changelog
62-
category and entry (see `changes/README.md`). The record repeats neither;
63-
the review page reads the fragment from the diff.
64-
- **Required keys**: `Area`, `Output`, `Tests`. `Formatting`, `Formats`,
65-
`PPX` and `Editor` are required whenever the change touches the syntax
66-
library, the parsetree, the typed tree or Lambda. Omit the others when they
67-
do not apply.
68-
- **Wording** follows the writing rules: present facts, positive statements,
69-
claims as strong as their evidence, every number labelled by what produced
70-
it. A JavaScript `Before`/`After` pair says where it came from in a
71-
`Source:` paragraph (compiler run, or a snapshot file in the diff).
72-
- **Lists in trailers**: `Measured` separates items with `; `
73-
(outside parentheses); each item renders on its own line. `Output` and the
74-
other state trailers start with their state word (`unchanged`, `changed`,
75-
`none`), followed by the detail.
76-
77-
## What each field states
78-
79-
| Field | What the record states |
52+
- The subject and `Signed-off-by` are required. The message ends with its
53+
trailers, git's `Key: value` lines (`Co-Authored-By:`, `Fixes:` or any
54+
other one-word key), `Signed-off-by` last; the page lists the
55+
`Co-Authored-By` names and draws no other trailer. Every section is
56+
optional.
57+
- A section starts at a line `Why:`, `Review:`, `Breaking:`, an object
58+
block's heading, an `Example:` alone on its line, or a `Before (<form>):`
59+
or `After (<form>):` heading, and holds the text after its colon and the
60+
block indented by 4 spaces or more under it; a blank line ends a section
61+
that holds only text. `Review:` repeats, one question each. Every other
62+
line is prose, and a block indented by 4 spaces outside a section is
63+
ReScript code. Tabs count as 4 columns.
64+
- An **object block** is a heading `<Kind> added|modified|removed (<object>):`,
65+
the kind one capitalised word naming the object, then an optional
66+
`Example:` with a ReScript program indented by 4 spaces, then the
67+
object's forms, each version a `Before (<form>):` or `After (<form>):`
68+
heading with its content indented by 4 spaces. Text after the colon of a
69+
`Before`/`After` heading captions the block under it, or is the version
70+
itself when no block follows (`After (js): return x;`). A heading that
71+
leaves out the object's parentheses is drawn as its action and kind.
72+
- A block draws the versions it has: a form with both versions side by
73+
side, or once, with the captions, when the two are equal; a form with
74+
one version across the block under its version's label. Trees, code,
75+
text, matrices and mappings are drawn on aligned rows, a line common to
76+
both versions facing itself and the changed lines shaded; typing rules
77+
and rewrites are drawn as two figures side by side, the changed parts
78+
shaded: premises, conclusions, rule names and formulas, and each term of
79+
a rewrite against the term at its position.
80+
- The changelog entry is the `changes/<name>.<category>.md` fragment the
81+
commit adds (`changes/README.md`).
82+
83+
## Kinds
84+
85+
The table lists the common kinds.
86+
87+
| Kind | Object |
8088
| --- | --- |
81-
| `Area` | The layers the change touches. They decide who reviews it and which suites apply; a parsetree or typed-tree change also reaches editor analysis. |
82-
| `Output` + `Before`/`After (js)` | The generated JavaScript for the example before and after, and which existing programs compile differently. `unchanged` holds when no committed generated JS changes, or when a rebuild of the test suites is byte-identical. |
83-
| `Formatting` + `Before`/`After (rescript)` | Whether `rescript format` rewrites existing code, and whether formatted output parses back to the same tree with its comments. |
84-
| `Formats` | The magic numbers bumped in `compiler/ext/config.ml`. Any change to the marshaled parsetree, typed tree (`.cmi`, `.cmt`) or `.cmj` layout, constructor order included, bumps the matching number. |
85-
| `PPX` | The effect on the frozen v0 PPX wire: `parsetree0.ml` is unchanged, and a change to the v0 mapping comes with an ast-mapping fixture. |
86-
| `Editor` | Completion, hover and inlay hints on new or changed nodes, and the analysis fixtures that cover them. |
87-
| `Tests` | The tests added, changed or removed, each observing the effect the record claims, so that it fails without the change. `none, because …` gives the reason when no test changes. |
88-
| `Measured` | Performance before and after, with the input and the command that produced each number. |
89-
| `Breaking` | What existing code or tooling stops working, and the migration. |
90-
| `Review` | Questions a reviewer raised or will raise, with their answers. |
91-
| `PR` | The pull request; the renderer links it. |
92-
| `Signed-off-by` | The DCO sign-off, with the commit author's name and email, as the last trailer. |
89+
| `AST` | a parsetree node |
90+
| `Typedtree` | a typed tree node |
91+
| `Typing` | a typing rule |
92+
| `Matching` | the pattern match compiler's split |
93+
| `Lambda` | a Lambda term or pass |
94+
| `JS` | generated JavaScript |
95+
| `Diagnostic` | an error or warning |
96+
| `Format` | the formatter's output |
97+
| `Representation` | how a value is represented in JavaScript |
98+
99+
## Forms
100+
101+
| Form | Content | Drawn as |
102+
| --- | --- | --- |
103+
| `parse` | the parse tree | tree |
104+
| `expanded` | the parse tree after the built-in PPX | tree |
105+
| `typed` | the typed tree | tree |
106+
| `lambda` | Lambda | highlighted code |
107+
| `js` | the generated JavaScript | highlighted code |
108+
| `rescript` | ReScript source, such as the formatter's output | highlighted code |
109+
| `diagnostic` | what the compiler reports | text |
110+
| `text` | any text | text |
111+
| `rule` | inference rules and formulas | inference rule |
112+
| `matrix` | a clause matrix | matrix |
113+
| `representation` | how ReScript values map to JavaScript | mapping |
114+
115+
- A tree is indented lines, two spaces a level, as the compiler prints it:
116+
`bsc -bs-no-builtin-ppx -only-parse -dparsetree file.res` prints the
117+
parse tree, `bsc -only-parse -dparsetree` the expanded one, and
118+
`bsc -dtypedtree` the typed tree.
119+
- Code is highlighted in its language; `bsc -drawlambda` prints the Lambda.
120+
Code can be a rewrite: each `⇝` separates the code before it from the
121+
code after it (`lhs ⇝ rhs` or a chain `a ⇝ b ⇝ c` on one line, or `⇝`
122+
alone on a line), and `label: formula` lines after the last term are its
123+
side conditions.
124+
- A `rule` group is premises, a bar of `─` with the rule's name in
125+
parentheses, and the conclusion; any other group is formulas, one a
126+
line. Groups are separated by blank lines, side-by-side formulas by three
127+
or more spaces. One-letter names, with their primes, are italic
128+
variables, `_x`, `_{…}` and `^{…}` sub- and superscripts, and `code` in
129+
backticks is ReScript.
130+
- A `matrix` is one row a line, columns three or more spaces apart, each
131+
cell a pattern; `label: text` lines are notes under it.
132+
- A `representation` is `ReScript ↦ JavaScript` lines; `label: text` lines
133+
are notes among them.
134+
- A form of another name is drawn as text.
135+
136+
## When to add a block
137+
138+
A commit gets one object block for each object it changes, with the
139+
smallest example that shows the change. A refactor, a test, a script or a
140+
review fix is a subject and a line or two of prose.
93141

94142
## Rendering
95143

96144
```sh
97-
node scripts/commitRecords.js --out review.html <rev>...
98-
node scripts/commitRecords.js --out review.html <base>..<branch>...
99-
node scripts/commitRecords.js --fragment --out part.html <rev>...
100-
node scripts/commitRecords.js --out review.html --pr 1234 --title "<PR title>" <base>..<branch>
145+
make semantic-diff # master..HEAD to _build/semantic-diff.html, opened
146+
node scripts/commitRecords.js --out page.html [--pr <number>] [--title <text>] \
147+
<rev | base..tip>...
101148
```
102149

103-
- `--pr` and `--title` head the page with the pull request's number (linked)
104-
and title; without them the page is headed "Semantic Diff".
105-
106-
- A revision renders one card. A range renders a summary row, a changelog
107-
draft grouped by category, its commits as cards, and the files more than
108-
one commit (or more than one range) changes. The changelog category, the
109-
entries and the draft come from the `changes/` fragments in the diff: a
110-
card shows the fragments its commit adds or changes, and a range's draft
111-
shows the fragments the range leaves, in their last version.
112-
- Each card checks the record against its diff: a `changes/` fragment that
113-
`yarn changelog:check` rejects (such as one still carrying the placeholder
114-
PR link), an edit to `CHANGELOG.md`, modified generated `.mjs`, `.cjs` and `.res.js` files under
115-
`Output: unchanged` (new ones from new tests are counted, not flagged), a
116-
magic number bumped in `compiler/ext/config.ml` that
117-
`Formats` does not name, a parsetree change with no ast-mapping fixture, a
118-
missing `Tests` trailer, `Tests: none` on a diff that changes tests. A failed
119-
check is shown on the card. The DCO app checks `Signed-off-by`.
120-
- The page is one self-contained HTML file: no script, no external data
121-
(fonts fall back to system faces), light and dark themes, phone width.
122-
`--fragment` omits the document skeleton for embedding.
123-
- A note on `refs/notes/records` replaces the commit message's record. Use it
124-
for commits that exist already (history, a squash merge) and for review
125-
outcomes added later; a new commit carries its record in its message.
126-
127-
## When to use
128-
129-
- Every commit an agent makes carries a record in its message.
130-
- An agent's report lists its commits and the rendered page; the records are
131-
the description of the work. A pull request description is a rendering of
132-
its range.
133-
- Before review or merge: render `master..<branch>` and hand over the page.
134-
- On GitHub, `.github/workflows/semantic_diff.yml` renders each pull
135-
request's range when it is opened or pushed, and comments the page's link
136-
(`Semantic diff: https://cdn.rescript-lang.org/semantic-diff/pr-<N>/…`).
150+
- `--pr` and `--title` head the page with the pull request.
151+
`.github/workflows/semantic_diff.yml` renders each pull request's range
152+
and comments the page's link.
153+
- In prose, `#1234` and full pull request and issue URLs link as `#1234`,
154+
and the 9-character short sha of a commit on the page links to its card.
155+
156+
## What the page shows
157+
158+
The page is the heading, then a card per commit. A card shows the commit's
159+
short sha, author and date, the pull request a `(#1234)` at the end of the
160+
subject names, and the message drawn: the subject, the prose, each object
161+
block with its example and forms, the `Breaking:` sections as banners, the
162+
`Review:` sections as a list, and the co-authors.

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ relevant guidance from each.
7777
- Build must pass before committing
7878
- Write each commit message as a record in the format of
7979
[`.claude/skills/records/SKILL.md`](.claude/skills/records/SKILL.md), and
80-
render review pages from records with `node scripts/commitRecords.js`
80+
render review pages from records with `make semantic-diff`
8181

8282
### Stacked pull requests
8383

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Commit records describe each changed compiler object in its own form (parse and typed trees, Lambda and JavaScript, diagnostics, typing rules, clause matrices, representations) Before and After, and the semantic diff page draws each form in its own way, Before and After side by side.

0 commit comments

Comments
 (0)