|
2 | 2 |
|
3 | 3 | All notable changes to this repository are documented in this file. |
4 | 4 |
|
| 5 | +## [3.6.2] — 2026-05-07 |
| 6 | + |
| 7 | +A stability and parity release. Closes 27 categories of cross-engine |
| 8 | +divergence between `synx-core` (Rust) and `@aperturesyndicate/synx-format` |
| 9 | +(TypeScript), plus three new utility markers. **No surface syntax change** |
| 10 | +to existing markers — same `.synx` file produces the same JSON in every |
| 11 | +binding now. |
| 12 | + |
| 13 | +### Added — three new markers (active mode) |
| 14 | + |
| 15 | +- **`:replace:FROM:TO`** — literal substring replacement on a string value. |
| 16 | + `TO` defaults to `""` (deletion). `FROM`/`TO` cannot themselves contain |
| 17 | + `:` (chain delimiter); use `{interpolation}` for those cases. |
| 18 | + ```synx |
| 19 | + !active |
| 20 | + greeting:replace:l:L Hello there # → "HeLLo there" |
| 21 | + ``` |
| 22 | +- **`:sort`** / **`:sort:desc`** — sort an array. Numeric items compare |
| 23 | + numerically, otherwise lexicographic. Default direction is ascending. |
| 24 | + ```synx |
| 25 | + !active |
| 26 | + ranked:sort:desc |
| 27 | + - 5 |
| 28 | + - 1 |
| 29 | + - 3 |
| 30 | + # → [5, 3, 1] |
| 31 | + ``` |
| 32 | +- **`:sum`** — sum the numeric items of an array (non-numeric items are |
| 33 | + ignored). Returns an integer when all summands are integers, otherwise |
| 34 | + a float. |
| 35 | + ```synx |
| 36 | + !active |
| 37 | + total:sum |
| 38 | + - 19.99 |
| 39 | + - 29.99 |
| 40 | + - 5.50 |
| 41 | + # → 55.48 |
| 42 | + ``` |
| 43 | + |
| 44 | +### Fixed — cross-engine parity (CRITICAL) |
| 45 | + |
| 46 | +- **`Synx.parse()` no longer silently switches engines at 5 KB.** The pure-TS |
| 47 | + parser is now used unconditionally. The native binding remains available |
| 48 | + via `bindings/node` for callers that explicitly opt in. Previously, the |
| 49 | + same file produced *different trees* depending on its size. |
| 50 | +- **`Synx.parseTool()` rewritten to spec §9.** Sorts top-level keys |
| 51 | + lexicographically, takes the first as the tool name, returns |
| 52 | + `{ tool, params }` (call mode) or `{ tools: [...] }` (schema mode). |
| 53 | + Previously returned empty `{ tool: "", params: {} }`. |
| 54 | +- **`Value::Secret` is now properly redacted in JSON output.** `synx-core`'s |
| 55 | + `write_json` emits `"[SECRET]"` for the `Value::Secret` variant; previously |
| 56 | + the raw secret string was leaked. **Security-relevant**. |
| 57 | +- **`.synxb` binary format is now cross-language compatible.** JS |
| 58 | + `Synx.compile`/`decompile` now use the same wire format as |
| 59 | + `synx-core::binary` (header + raw-deflate payload via Node `zlib`). |
| 60 | +- **List items with sub-keys parse identically in both engines.** Rust now |
| 61 | + supports the README pattern `- name Sword\n damage 50` as an object, |
| 62 | + matching the long-standing JS behaviour. Previously Rust silently |
| 63 | + hoisted `damage` into the parent map. |
| 64 | +- **`!tool` and `!schema` directives now parsed by JS parser.** Previously |
| 65 | + ignored — flags on `SynxParseResult` were never set. |
| 66 | + |
| 67 | +### Fixed — semantic alignment (MAJOR) |
| 68 | + |
| 69 | +- **`pattern` constraint is now enforced in Rust** (`regex` crate added). |
| 70 | +- **Constraint validation runs once, not twice.** Eliminates Rust error |
| 71 | + messages like `value 52 exceeds max 10` (where 52 was the byte length |
| 72 | + of the previous error string). |
| 73 | +- **`[constraints]` parser is now bracket-balanced.** Patterns containing |
| 74 | + `]` such as `[pattern:^[A-Z]{2}$]` parse correctly in both engines. |
| 75 | +- **`:env` casts booleans/`null` from the environment in JS.** `DEBUG=true` |
| 76 | + now yields `true` (boolean), not `"true"` (string). |
| 77 | +- **`:calc` supports dot-paths in JS** (`base.hp * 5`). Was Rust-only. |
| 78 | +- **`:i18n:count_field` plural forms work in JS** (CLDR-inspired: |
| 79 | + `one/few/many/other`/`zero`/`two`). |
| 80 | +- **`:watch:nested.field` resolves dot-paths** in Rust for both JSON and |
| 81 | + SYNX inputs (real `serde_json` parse, real SYNX parse — not substring |
| 82 | + search). |
| 83 | +- **`:import` marker** is now handled in JS (was Rust-only). |
| 84 | +- **`:include` / `:import` markers register an alias** in the includes map |
| 85 | + so `{leaf:db}` interpolation works for the README pattern. |
| 86 | +- **`:inherit base` with children** — both engines now treat the value |
| 87 | + after `:inherit` as the parent name and open a group for the child |
| 88 | + block. Previously the README-documented pattern silently broke. |
| 89 | +- **`:format:%e`** is implemented in Rust. |
| 90 | +- **`:prompt` emits keys in sorted order** in both engines (deterministic |
| 91 | + for LLM caching). |
| 92 | +- **`Synx.toJSON()` sorts keys lexicographically** (canonical JSON per |
| 93 | + spec §10). |
| 94 | +- **`__proto__` / `constructor` / `prototype` keys are blocked** in both |
| 95 | + engines for safe consumption from JavaScript downstream code. |
| 96 | +- **`synx parse` auto-enables `--active`** when the file declares |
| 97 | + `!active` or `#!mode:active`. Removes a long-standing UX papercut. |
| 98 | + |
| 99 | +### Fixed — security (MAJOR) |
| 100 | + |
| 101 | +- **Path-jail blocks Linux-style `/foo` and Windows-rooted `\foo`.** |
| 102 | + Previously, on Windows, `/etc/passwd` was not classified as absolute |
| 103 | + by `Path::is_absolute()` and could escape the base directory in some |
| 104 | + edge cases. |
| 105 | +- **Error messages are OS-portable.** ENOENT / "system cannot find" both |
| 106 | + emit `file not found: <path>`; absolute/rooted blocks emit identical |
| 107 | + text on Linux and Windows. |
| 108 | +- **JS parser enforces the §3 limits** (16 MiB input, 128 nesting depth, |
| 109 | + 1 MiB multiline block, 1 M list items, 4 K includes, 4 K enum parts, |
| 110 | + 512 marker chain segments). Previously only the Rust core had them. |
| 111 | + |
| 112 | +### Fixed — cosmetic |
| 113 | + |
| 114 | +- **Cycle-alias error messages are now stable.** Both keys in an |
| 115 | + `a → b → a` cycle produce the same message regardless of HashMap |
| 116 | + iteration order. |
| 117 | +- **`(unknown_hint)value`** in JS no longer wraps the value back with |
| 118 | + the unknown hint; falls through to plain cast (matches Rust |
| 119 | + `cast_typed` fallback). A leading `(`-prefixed value with non-identifier |
| 120 | + contents (e.g. `(10 + 20) / 2`) is preserved as a literal string. |
| 121 | + |
5 | 122 | ## [3.6.1] — 2026-04-09 |
6 | 123 |
|
7 | 124 | ### Added |
|
0 commit comments