Skip to content

Commit 976ec42

Browse files
hyperpolymathclaude
andcommitted
docs(spec): rename affex-spec.md to .adoc and convert to AsciiDoc
Estate rule: docs/ files use .adoc extension; .md is reserved for files GitHub community-health rules special-case by name (CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, CHANGELOG.md, README.md). General specs must be .adoc. This commit: - git mv docs/specs/affex-spec.md -> docs/specs/affex-spec.adoc - Converts Markdown headings (#, ##, ###, ####) to AsciiDoc (=, ==, ===, ====) - Converts Markdown pipe tables to AsciiDoc [cols=...] |=== blocks - Converts code fences to AsciiDoc [source,json] ---- listing blocks - Converts emphasis (bold, italic) to AsciiDoc (* and _ markers) - Adds standard AffineScript-spec preamble (SPDX, :toc:, :source-highlighter:) - Updates internal §9 reference to point at the new .adoc filename Refs #84. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 4487fed commit 976ec42

1 file changed

Lines changed: 112 additions & 112 deletions

File tree

Lines changed: 112 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,68 @@
1-
# `.affex` — AffineScript Face-Interop Manifest Specification v0.1
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
= `.affex` — AffineScript Face-Interop Manifest Specification v0.1
3+
:toc: macro
4+
:toclevels: 2
5+
:source-highlighter: rouge
26

3-
**Status**: Draft · Linked to requirements-target issue #84
7+
*Status*: Draft · Linked to requirements-target issue #84
48

5-
---
9+
toc::[]
610

7-
## 1. Problem
11+
== 1. Problem
812

9-
AffineScript supports multiple *faces* — surface syntaxes that all lower to the
13+
AffineScript supports multiple _faces_ — surface syntaxes that all lower to the
1014
same canonical AST. A programmer fluent in `canonical` reading a file written in
1115
`lucid` or `cafe` cannot tell what is going on, even though the semantics are
1216
identical.
1317

14-
`.affex` is a **tooling-only, per-package manifest** that captures
18+
`.affex` is a *tooling-only, per-package manifest* that captures
1519
face-specific renderings of every top-level declaration, so readers can work in
1620
their preferred face without leaving their `.affine` files.
1721

18-
---
19-
20-
## 2. Design decision: Shape 3 (face-interop manifest)
22+
== 2. Design decision: Shape 3 (face-interop manifest)
2123

2224
The four speculative shapes from the issue were:
2325

24-
| # | Shape | Chosen? |
25-
|---|-------|---------|
26-
| 1 | Rosetta-stone side-by-side listing | Derived output only |
27-
| 2 | Canonical-equivalence annotations | Not chosen |
28-
| **3** | **Face-interop manifest** | **Primary form** |
29-
| 4 | Mixed hand-written + auto | Supported (override blocks) |
26+
[cols="1,3,2", options="header"]
27+
|===
28+
| # | Shape | Chosen?
29+
| 1 | Rosetta-stone side-by-side listing | Derived output only
30+
| 2 | Canonical-equivalence annotations | Not chosen
31+
| *3* | *Face-interop manifest* | *Primary form*
32+
| 4 | Mixed hand-written + auto | Supported (override blocks)
33+
|===
3034

31-
**Why Shape 3**: the compiler already holds the canonical↔face bijection
35+
*Why Shape 3*: the compiler already holds the canonical↔face bijection
3236
through the lowering pass. A manifest that captures this mapping once per
3337
package lets all tooling (LSP, CLI renderer, web viewer) derive any face view
3438
on demand — without storing N copies of source. Shape 1 (side-by-side listing)
35-
is a *derived view* generated from Shape 3 + the source, not a stored artifact.
36-
37-
---
39+
is a _derived view_ generated from Shape 3 + the source, not a stored artifact.
3840

39-
## 3. File extension policy
41+
== 3. File extension policy
4042

41-
`.affex` files are **tooling artifacts only**. The compiler (`check`, `compile`,
43+
`.affex` files are *tooling artifacts only*. The compiler (`check`, `compile`,
4244
`eval`) does not parse or consume them. They are generated by the CLI and
4345
consumed by the LSP and renderer.
4446

45-
Scope: **one `.affex` file per package**, placed at the package root alongside
47+
Scope: *one `.affex` file per package*, placed at the package root alongside
4648
`dune` and the package's `.affine` source files.
4749

48-
```
50+
----
4951
src/
5052
dune
5153
pixi.affine
5254
renderer.affine
53-
pixi.affex ← generated manifest for this package
54-
```
55-
56-
---
55+
pixi.affex <- generated manifest for this package
56+
----
5757

58-
## 4. File format
58+
== 4. File format
5959

6060
`.affex` files are UTF-8 JSON with schema version `"affex/1"`.
6161

62-
### 4.1 Top-level structure
62+
=== 4.1 Top-level structure
6363

64-
```json
64+
[source,json]
65+
----
6566
{
6667
"affex": "1",
6768
"package": "affinescript-pixijs",
@@ -70,20 +71,23 @@ src/
7071
"faces": ["canonical", "lucid", "cafe"],
7172
"mappings": [ ... ]
7273
}
73-
```
74-
75-
| Field | Type | Description |
76-
|-------|------|-------------|
77-
| `affex` | `"1"` | Schema version. Must be `"1"` for this spec. |
78-
| `package` | string | Package name (matches `dune` library name). |
79-
| `generated_at` | ISO 8601 | Timestamp of last generation run. |
80-
| `source_hash` | string | SHA-256 of all `.affine` source files combined, to detect staleness. |
81-
| `faces` | string[] | Faces present in this manifest. Always includes `"canonical"`. |
82-
| `mappings` | object[] | One entry per top-level declaration. See §4.2. |
83-
84-
### 4.2 Mapping entry
85-
86-
```json
74+
----
75+
76+
[cols="1,1,3", options="header"]
77+
|===
78+
| Field | Type | Description
79+
| `affex` | `"1"` | Schema version. Must be `"1"` for this spec.
80+
| `package` | string | Package name (matches `dune` library name).
81+
| `generated_at` | ISO 8601 | Timestamp of last generation run.
82+
| `source_hash` | string | SHA-256 of all `.affine` source files combined, to detect staleness.
83+
| `faces` | string[] | Faces present in this manifest. Always includes `"canonical"`.
84+
| `mappings` | object[] | One entry per top-level declaration. See §4.2.
85+
|===
86+
87+
=== 4.2 Mapping entry
88+
89+
[source,json]
90+
----
8791
{
8892
"id": "init_pixi",
8993
"kind": "fn",
@@ -107,29 +111,32 @@ src/
107111
}
108112
}
109113
}
110-
```
111-
112-
| Field | Type | Description |
113-
|-------|------|-------------|
114-
| `id` | string | Canonical name of the declaration. |
115-
| `kind` | `"fn"` \| `"type"` \| `"const"` \| `"effect"` \| `"trait"` \| `"impl"` | Declaration kind. |
116-
| `source_file` | string | Relative path to the `.affine` file. |
117-
| `canonical_span` | `{start: [line, col], end: [line, col]}` | Source location of the canonical declaration. |
118-
| `faces.<name>.head` | string | Face-specific rendering of the declaration *signature* (no body). |
119-
| `faces.<name>.body_digest` | string | SHA-256 of the canonical body. Identical across faces — a mismatch signals the manifest is stale. |
120-
| `faces.<name>.override` | bool | `true` if the face rendering was hand-written rather than auto-generated. |
121-
| `faces.<name>.override_source` | string? | Pointer to the override block (§5) when `override` is `true`. |
122-
123-
**`head` only, not `body`**: the manifest stores *signatures*, not bodies. Full
114+
----
115+
116+
[cols="1,1,3", options="header"]
117+
|===
118+
| Field | Type | Description
119+
| `id` | string | Canonical name of the declaration.
120+
| `kind` | `"fn"` \| `"type"` \| `"const"` \| `"effect"` \| `"trait"` \| `"impl"` | Declaration kind.
121+
| `source_file` | string | Relative path to the `.affine` file.
122+
| `canonical_span` | `{start: [line, col], end: [line, col]}` | Source location of the canonical declaration.
123+
| `faces.<name>.head` | string | Face-specific rendering of the declaration _signature_ (no body).
124+
| `faces.<name>.body_digest` | string | SHA-256 of the canonical body. Identical across faces — a mismatch signals the manifest is stale.
125+
| `faces.<name>.override` | bool | `true` if the face rendering was hand-written rather than auto-generated.
126+
| `faces.<name>.override_source` | string? | Pointer to the override block (§4.3) when `override` is `true`.
127+
|===
128+
129+
*`head` only, not `body`*: the manifest stores _signatures_, not bodies. Full
124130
body rendering is done on demand by the renderer using the compiler's face
125131
lowering pass. This keeps the manifest compact and avoids duplicating source.
126132

127-
### 4.3 Override blocks
133+
=== 4.3 Override blocks
128134

129135
When the auto-generated `head` for a face is wrong or unnatural, a developer
130136
can add a hand-written override in a top-level `"overrides"` section:
131137

132-
```json
138+
[source,json]
139+
----
133140
{
134141
"affex": "1",
135142
...
@@ -140,51 +147,50 @@ can add a hand-written override in a top-level `"overrides"` section:
140147
},
141148
"mappings": [ ... ]
142149
}
143-
```
150+
----
144151

145152
The generator preserves existing `overrides` on regeneration and merges them
146153
with newly auto-generated entries.
147154

148-
---
155+
== 5. Tooling
149156

150-
## 5. Tooling
157+
=== 5.1 CLI commands
151158

152-
### 5.1 CLI commands
159+
==== Generate
153160

154-
#### Generate
155-
156-
```
161+
----
157162
affinescript affex generate [<package_dir>]
158-
```
163+
----
159164

160165
Reads all `.affine` files in `<package_dir>` (default: current directory),
161166
runs the face detection and lowering passes, and writes
162167
`<package_dir>/<package_name>.affex`. Existing override blocks are preserved.
163168

164169
Options:
165-
- `--faces canonical,lucid,cafe` — limit faces to generate (default: all installed faces)
166-
- `--force` — regenerate even if source hash is unchanged
167170

168-
#### Render
171+
* `--faces canonical,lucid,cafe` — limit faces to generate (default: all installed faces)
172+
* `--force` — regenerate even if source hash is unchanged
173+
174+
==== Render
169175

170-
```
176+
----
171177
affinescript affex render --face <face> <file.affine>
172-
```
178+
----
173179

174180
Reads the package's `.affex` manifest, then renders `<file.affine>` in
175181
`<face>` to stdout. If no manifest exists, falls back to running the face
176182
lowering pass directly (slower).
177183

178-
#### Diff
184+
==== Diff
179185

180-
```
186+
----
181187
affinescript affex diff --face <face_a> --face <face_b> <file.affine>
182-
```
188+
----
183189

184190
Renders `<file.affine>` in both faces side-by-side (Shape 1 output) using the
185191
manifest as the source of truth.
186192

187-
### 5.2 LSP integration
193+
=== 5.2 LSP integration
188194

189195
The LSP reads the nearest `.affex` manifest when opening a `.affine` file.
190196
When the user configures a preferred face (e.g. `"affinescript.face": "lucid"`
@@ -193,53 +199,47 @@ render declaration signatures in that face rather than the source face.
193199

194200
The LSP does not rewrite file contents — it only affects rendered metadata.
195201

196-
---
197-
198-
## 6. Generation strategy
202+
== 6. Generation strategy
199203

200-
| Scenario | Approach |
201-
|----------|----------|
202-
| Clean package with no `.affex` | `affex generate` creates one from scratch |
203-
| Source changed since last generate | `source_hash` mismatch; re-run `affex generate` |
204-
| Override present for a declaration | Generator merges override, marks `"override": true` |
205-
| New declaration added | Generator appends mapping; existing entries untouched |
206-
| Declaration removed | Generator removes stale mapping entry |
204+
[cols="1,2", options="header"]
205+
|===
206+
| Scenario | Approach
207+
| Clean package with no `.affex` | `affex generate` creates one from scratch
208+
| Source changed since last generate | `source_hash` mismatch; re-run `affex generate`
209+
| Override present for a declaration | Generator merges override, marks `"override": true`
210+
| New declaration added | Generator appends mapping; existing entries untouched
211+
| Declaration removed | Generator removes stale mapping entry
212+
|===
207213

208-
**CI recommendation**: add `affinescript affex generate --check` (exits non-zero
214+
*CI recommendation*: add `affinescript affex generate --check` (exits non-zero
209215
if manifest is stale) to CI for estates that adopt multi-face conventions.
210216

211-
---
217+
== 7. What this is NOT
212218

213-
## 7. What this is NOT
214-
215-
- Not a compiler concern. The compiler lowers all faces independently.
216-
- Not an AffineScript↔non-AffineScript FFI layer. See `extern fn` for that.
217-
- Not required for single-face estates. A project using only `canonical` has no
219+
* Not a compiler concern. The compiler lowers all faces independently.
220+
* Not an AffineScript↔non-AffineScript FFI layer. See `extern fn` for that.
221+
* Not required for single-face estates. A project using only `canonical` has no
218222
need for a `.affex` file.
219223

220-
---
221-
222-
## 8. Open questions (pre-implementation)
223-
224-
- [ ] Should `affinescript affex generate` be a separate binary or a subcommand of the main `affinescript` CLI?
225-
- [ ] Face names in the manifest should match exactly the values in `lib/face.ml`'s `face` type. Confirm the canonical string identifiers: `canonical`, `python`, `js`, `pseudocode`, `lucid`, `cafe`.
226-
- [ ] Should `head` rendering use the compiler's pretty-printer or store the raw source span? Raw source span is simpler and always correct; pretty-printed form is more useful when the source face differs from the reader face.
227-
- [ ] Staleness detection: `source_hash` over all `.affine` files in the package is coarse. Consider per-file hashes to support incremental regeneration.
228-
- [ ] Override syntax: embedding overrides inside the JSON file is workable but verbose. An alternative is a sidecar `.affex.overrides` file.
224+
== 8. Open questions (pre-implementation)
229225

230-
---
226+
* [ ] Should `affinescript affex generate` be a separate binary or a subcommand of the main `affinescript` CLI?
227+
* [ ] Face names in the manifest should match exactly the values in `lib/face.ml`'s `face` type. Confirm the canonical string identifiers: `canonical`, `python`, `js`, `pseudocode`, `lucid`, `cafe`.
228+
* [ ] Should `head` rendering use the compiler's pretty-printer or store the raw source span? Raw source span is simpler and always correct; pretty-printed form is more useful when the source face differs from the reader face.
229+
* [ ] Staleness detection: `source_hash` over all `.affine` files in the package is coarse. Consider per-file hashes to support incremental regeneration.
230+
* [ ] Override syntax: embedding overrides inside the JSON file is workable but verbose. An alternative is a sidecar `.affex.overrides` file.
231231

232-
## 9. Satisfaction criteria for closing #84
232+
== 9. Satisfaction criteria for closing #84
233233

234234
This issue closes when Claude and the user explicitly agree, in a recorded
235235
exchange on this issue, that the following are satisfied:
236236

237-
1. This spec (or a revised version) is merged into `docs/specs/affex-spec.md`.
238-
2. The file format schema (§4) is agreed to be correct and complete.
239-
3. The tooling surface (§5) matches the CLI and LSP integration plan.
240-
4. At least one of the open questions in §8 has a recorded resolution (or is
241-
explicitly deferred with a rationale).
237+
. This spec (or a revised version) is merged into `docs/specs/affex-spec.adoc`.
238+
. The file format schema (§4) is agreed to be correct and complete.
239+
. The tooling surface (§5) matches the CLI and LSP integration plan.
240+
. At least one of the open questions in §8 has a recorded resolution (or is
241+
explicitly deferred with a rationale).
242242

243-
*The presence of a PR, a draft, or a partial implementation does not satisfy
243+
_The presence of a PR, a draft, or a partial implementation does not satisfy
244244
these criteria alone — explicit mutual agreement in a comment on issue #84 is
245-
required.*
245+
required._

0 commit comments

Comments
 (0)