Issue #928: Official package yaml, also a Rust crate: a serde_yaml 0.9 drop-in layer over a native API (zero-copy, spans, format-preserving edits, limits)

Open and answer this issue on GitHub.

ready, wip · opened 2026-10-28 · updated 2026-10-31 · 11 comments

The owner’s direction (2026-10-28)

An official package yaml (ADR-338 D2, in packages/yaml/) that Nikaia programs use, and that is published as a crate Rust programs use on its own, in two layers:

  1. Compatibility layer: a drop-in for the archived serde_yaml 0.9. Switching is one line in Cargo.toml:
    serde_yaml = { package = "nikaia-yaml", version = "0.1" }

    Surface: from_str / from_slice / from_reader, to_string / to_writer, Value, Mapping, Number, Error with a position, and the serde Deserializer / Serializer impls. Behaviour matters more than the API; serde_yaml’s defaults are kept, or existing configs break silently:

    • YAML 1.2 core schema: yes / no / on are strings, not booleans (the “Norway problem”).
    • Merge keys << are not resolved by themselves, only on request.
    • Tags map to enum variants.
    • A duplicate key is an error.

    Every deviation goes on a migration page. Better error messages may differ.

  2. Native API: what nobody fills well today.
    • Zero-copy Value<'a> pointing into the input, and deserializing &'a str without allocating (serde_yaml cannot).
    • Pull / event parser with spans, for linters, language servers, validating large Kubernetes manifests.
    • Format-preserving editing (comments, order, indentation kept on change), like toml_edit. Missing in Rust; ruamel.yaml fills it poorly in Python. Tools such as Renovate, Helm helpers, CI bots need it: probably the strongest selling point.
    • Safety limits as API: alias expansion (billion laughs), depth, size; on by default.

Later, the same principle for Python (drop-in for PyYAML safe_load / safe_dump; no unsafe load; a ruamel-like round-trip API) and JS (parse / stringify as the yaml package, plus a document model that keeps comments).

Benchmark shows both layers: “drop-in, x times faster than serde_yaml” and “native API, another y times faster” (counted with callgrind, CLAUDE.md “Measuring performance”, against tuned Rust: serde_yaml 0.9, serde_yml, saphyr / yaml-rust2).

What the repository says today

Open questions for the decision (ADR to write)

  1. How Rust sees a Nikaia package. A Rust user needs a Rust-idiomatic API: pub fn from_str<'de, T: Deserialize<'de>>, impl serde::Deserializer<'de> for &mut Deserializer, impl Serialize for Value, Value<'a> with a lifetime, std::io::Read / Write. Nikaia cannot implement a foreign Rust trait today, and the lowered names are not a stable public API. Options:
    • (a) the core (scanner, parser, events, document model, limits) in Nikaia, lowered; a thin hand-written Rust facade crate that implements the serde traits over it;
    • (b) the language learns to implement a Rust trait for a Nikaia type and to declare the Rust-facing signature (generic over T: Deserialize<'de>), so the facade is Nikaia too — larger, and a language change;
    • (c) the whole crate in Rust — contradicts “Nikaia shows what it can do”. Recommendation to discuss: (a) now, with each piece of the facade that (b) could replace filed as a finding.
  2. Version of the crate vs. the package. The serde_yaml layer’s semver (0.1, promising serde_yaml 0.9’s behaviour) and the Nikaia package’s version: one version or two?
  3. Python / JS bindings: in this repository (packages/yaml/bindings/?) or separate; built on the same Rust crate (PyO3, napi / wasm).
  4. Conformance: the yaml-test-suite as the gate for the parser, and serde_yaml 0.9’s own tests (ported) as the gate for the compatibility layer.

To build

Decided: the owner’s comments below (2026-10-28: option (a), one crate one version, licences; 2026-10-29: features serde/tree/stream, streaming via winnow-grammar) and ADR-yaml-1 (docs/specification/adr/adr-yaml-1.md). Built by the lane scripts/drive.nika -- docs/briefs/yaml (briefs in docs/briefs/yaml/, order in its batch.txt).

  1. ADR: ADR-yaml-1 (package layout, native API, core schema, limits, tests, later stages).
  2. Core grammar over ref Bytes into the zero-copy tree with spans (ADR-yaml-1 D2-D5), the package’s own tests:
    • 2a skeleton, documents, comments, block and flow collections, plain and quoted scalars, core schema, errors with line/column (y1-tree-core.md, 642eedf; 0x/0o and \x \u \U left for 2c);
    • 2c escapes checked and decoded once in the grammar, 0x/0o converted for their shape (ADR-yaml-1 D5, D11; owner 2026-10-30): scalar::from (#1003 steps 1-2, 52effec, 1443b82), grammar actions’ claims proved (201f088), 0x/0o (e68dc72), escapes in the grammar (y5b, b5157023), #639 loop ranges (y7a, a7f3843f, closes on promote), #1013 point 1 (bdbe8a7a; point 2 waits on the owner); left, in this order:
      • y7b-1009-let-reads.md (#1009 and three neighbours, 65d86e57; closes on promote);
      • y4c-1003-finish.md (done, 4e30179a, 28a0c1a2, closes #1003 on promote): json’s hex4 promises its range with an assert (ADR-359 D3), scalar::from(cp), then dfe7bac6 (an integer as scalar refused, NK1241), Fixes #1003;
      • y7c-match-join.md (done, cf5bb6f1, closes #1021 on promote; its Found filed as #1022, decision, nothing needs it): #1021 (found by y7b: a match arm’s assignment to a let mut is not joined as after an if, ADR-361 D9); a defect, not needed by json/yaml, built here since it touches only the prover;
      • later, not needed by json: the untyped let d = ds[k] read (spike probes b_acc_ret, c_acc_for);
    • 2b (after 2c): block scalars (y2 step 1, 9d92e474); anchors, tags, aliases were blocked on #1026 (found by y2), y8-1026-grammar-label-struct.md (done, 555fe6e9, closes #1026 on promote); its Found filed as #1027 (a label on a one-element group, n:(P), reaches rustc; nothing in the package needs it): y9-1027-group-label.md (done, 9293d338, closes #1027 on promote); its Found filed as #1030 (a label on a literal returned or stored as String reaches rustc; nothing in the package needs it): y10-1030-literal-label.md (done, 24751a95, closes #1030 on promote), then y2c-properties.md (y2 step 2 from the draft on wip/y2-properties, ea068fa4; run 1 blocked on two compiler defects, filed as #1032 (a call in a grammar guard is no call site for the text tiers, NK1185) and #1033 (a guard cannot read a text(..) label that binds names, rustc): y11-1032-guard-call-tiers.md (297eefc8), y12-1033-guard-text-label.md (6e843b68), both close on promote; the draft then checks but its tests fail in rustc on #1037 (a dependency’s struct with three view fields named ref yaml::Node gets one lifetime, ADR-353 D7): y13-1037-dependency-places.md (262d5fa6, closes #1037 on promote); y2c run 2 then built the step (11/11 package tests, on wip/y2c-properties, b223f972) but the fmt scan is red on #1015 (nikaia fmt breaks a labelled guard on a long rule line into text that does not parse): y15-1015-fmt-guard-label.md (2e553fe5, closes #1015 on promote), then y2c-properties.md run 3 (done, 95a690fe: anchors, tags, aliases); y12’s Found, a guard reading a name bound inside text(..) (NK1117), #1013 step 4: y14-1013-guard-inner-label.md (03d43063, closes #1013 on promote); its Found filed as #1039 (an unbound text(d:P+) or a name below a repeat read by an action reaches rustc; nothing in the package needs it): y16-1039-unbound-text-names.md (085bb858, closes #1039 on promote)); y17-1041-entry-takes-a-parameter.md (7a91e7d9, closes #1041 on promote), then explicit keys, directives, Limits for depth and size (y2b-keys-directives-limits.md) (c8d95984; 14 package tests); y2b’s Found filed as #1049 (a sequence inside peek(..)/not(..) passes check, fails at run), #1050 (a guard handing an optional label to a by-value Node? parameter, rustc), #1051 (nikaia fmt breaks before a postfix *); the package needs none of them (it uses peek(Dash) and t: ref Node?): y18-1049-1050-grammar-builtins-and-guard.md (0c9f96a4, f0ae806e, close #1049, #1050 on promote); y18’s Found filed as #1056 (the plain-call NK2105 help says t.clone() for an optional; nothing needs it): y20-optional-copy-help.md (e08ba49c, closes #1056 on promote); #1051 waits for a later batch. y19-1002-hex-digit.md (4dbc9685: the package’s own Hex guard replaced by the built-in hex_digit, #1002 built in 705a5db2; two error offsets of a bad \x/\u escape now point at the offending character, accepted by the architect: “where reading stopped”).
  3. #1000 (a struct or enum built from views of a ref parameter), which steps 4-5 need: y3-1000-views-from-ref.md (d715e425, closes #1000 on promote; it also carries the lowered/project_members.rs drift fix that kept CI’s sysroot tests red from 263d13d0). Its Found (an owned field moved out through an index, ns[0].raw, reaches rustc) is commented on #927; nothing needs it.
  4. Events as a walk of the tree in the yaml-test-suite’s vocabulary, with spans (D7; the tree keeps Node.flow and documents gives Document with its ---/... markers, D7’s paragraph of 2026-10-31): blocked on #1063 (found by the architect probing this step: a view field of a lent struct, n.anchor of a ref Node, stored in a new struct is refused NK2106 and cascades into NK1106 across the grammar; load needs it too). y22-1063-view-field-copy.md (ac156203, closes #1063 on promote); y21-events.md run 1 built Node.flow and documents (2a303980) and stopped on the tree dropping a scalar’s text and style when it resolves to Null/Bool/Int/Float (~ = null, !!int "1" loses its quotes); decided by the architect (ADR-yaml-1 D7, last paragraph: Node.raw, Node.style for every scalar node). y23-scalar-written.md (ff39fc6f: Node.raw, Node.style). y21-events.md run 2 drafted events (branch wip/y21-events, 0433d662) and stopped on three view-checker defects, filed #1070 (a by-value field-less enum beside a view taken for a view source, NK2302), #1071 (a list of views handed back refused NK2302 as a literal, started non-empty, or grown with extend), #1072 (a view field off a match payload of a lent struct, NK1106). y24-1070-by-value-enum-not-a-view.md (05c98e45), y25-1071-list-of-views-literal-extend.md (79ee176c), y26-1072-payload-view-field.md (4b1b9fba), each closes its issue on promote. y21-events.md run 3 finished the draft (wip/y21-events, 1c1199d6: Event.value a view, Event.text the owned text of a Value::Str) and stopped on #1085 (a list or struct of views handed back through an if/match is refused NK2302; the architect checked the rest of the draft builds and its events test runs). y28-1085-views-back-through-if-match.md (91b72357, closes #1085 on promote), y21-events.md run 4 (0a55a3ce: Event, events, event_line; tags written as the tree keeps them, resolving them later). y27-load.md run 1 built load (load(input; limits), aliases expanded per document within the alias limit (checked before copying), an unknown or recursive alias an error, a duplicate key an error naming both positions (keys by kind and value: a="a", 16=0x10), << an ordinary key; D6, D8) on local branch wip/y27-load (eef3a2e7) and stopped on #1089 (a local bound by m.get(k) ?? <jump> / match m.get(k) { null => <jump> x => x } is not lent; a match on its field lowers without &, rustc E0507). y29-1089-match-on-a-get-bound-local.md (445f6064, closes #1089 on promote); its Found filed as #1092 (a by-value parameter whose field a match moves out is inferred lent, rustc E0507; nothing needs it). y27-load.md run 2 (30383486: load). y31-merge.md run 1 (merge(node, input), the << key on request; ADR-yaml-1 D6, paragraph of 2026-10-31) stopped on #1094 (a call result built from a by-value view-holding argument is charged to a ref argument beside it: NK2302 on a struct-literal store, rustc “missing lifetime” through a method). Batch of 2026-10-31 (twelfth): y32-1094-result-from-struct-argument.md (Fixes #1094), y31-merge.md run 2, y30-1092-match-moves-owned-field.md (Fixes #1092). Next: tag resolution for events.
  5. yaml-test-suite: the data branch vendored byte for byte with its License and source commit, a test program over every case, a pass list that only grows (D9). Waits for the owner: the data branch (6ad3d2c, generated from main 45db50ae = v2022-01-17) carries no licence file of its own; main’s License (MIT, Ingy döt Net) is byte-identical on main and data-flat.
  6. Writer (to_string) matching serde_yaml 0.9’s output where a program could depend on it.
  7. Format-preserving document model: comments and every token’s span in the tree, an ADR for the edit model.
  8. Rust facade crate nikaia-yaml (serde_yaml 0.9 surface and behaviour, ported tests with their licence, migration page): waits on #978.
  9. Feature stream: waits on #978 and the lowering writing winnow-grammar’s parse_<rule>_reader, #[stream], #[stream(each)].
  10. Benchmark in benches/yaml/ (serde_yaml 0.9 vs. drop-in vs. native API, instructions counted).
  11. Python and JS layers (separate issues once 8 stands).

Found on the way (y21 run 2, 2026-10-31): #1070, #1071, #1072 (above). y21 run 3: #1085 (above). y27 run 1: #1089 (above). y29: #1092 (above). y31: #1094 (above). Found on the way (y4b/y5/y6, 2026-10-30): #1009 (above), #1010, #1013 (above), #1014 (lowering cache key misses an inferred sync), #1015 (nikaia fmt breaks if(cond, "label") rule lines), #1016 (a ref Vec[T] result into a ref Vec[T] parameter); y7b (2026-10-31): #1021 (above); y7c: #1022 (what a match guard gives its arm, decision); y2 (2026-10-31): #1026 (above); y8: #1027 (above); y9: #1030 (above); y2c: #1032, #1033 (above); y12: #1013 step 4 (above); architect probing y2c: #1037 (above), #1038 (a dependency’s view field returned beside an owned text, NK1104; nothing needs it yet); y14: #1039 (above); architect probing y2b: #1041 (above), #1040 (a return in a grammar action reaches rustc; decision, nothing needs it); y2b: #1049, #1050, #1051 (above); none else blocks the package beyond those named in 2c.

Found earlier: #999 (a tuple result mixing String with another type refuses views; the package uses a struct), #1000 (above), #1001 (a group with branches of different types reaches rustc), #1002 (hex_digit undefined; the package checks digits with a guard instead, by decision), #1003 (scalar::from, decided 2026-10-30; steps 1-2 on develop, step 3 in y4c), #1004 (a temporary Bytes(..) entry argument). Only #1003 blocks the package (2c); #1000 blocks step 4; #1001, #1002, #1004 are compiler bugs the package does not need and come after y3.

Every gap found on the way in the compiler, lowering or std is a finding fixed in the compiler (CLAUDE.md), not routed around in the package.


Generated by Claude Code

Comments

keywan-ghadami, 2026-10-28

Owner’s decisions (2026-10-28)

  1. Option (a): core in Nikaia, a thin hand-written Rust layer for serde and the Rust-facing signatures; each piece of that layer the language could take over later (Rust trait impls, …) is a finding.
  2. One crate, one version. packages/yaml/ is the Rust crate nikaia-yaml: lowering the package fills the crate’s src/ with the generated .rs files, and the hand-written Rust files lie beside them. The Nikaia package and the crate carry the same version. Reason: when Rust trait support arrives, the hand-written files move into Nikaia inside the same package, with no migration between two crates.
  3. Python and JS build on that Rust crate. They need not be driven by the Nikaia compiler yet: generating the crate and building the targets with Cargo (PyO3, napi/wasm) is fine for now.
  4. Licences are checked before anything is taken in; the owner answers before any test data or ported test enters the repository (findings below). Label stays decision until then.

Can one crate hold both? The precedent exists

crates/nikaia-std is that shape today: hand-written .rs modules next to .nika sources whose lowered .rs files (src/tools/lowered/*.rs, src/text.rs, …) are committed and written by nikaia lower-std; one Cargo.toml, one version. What is missing for a package:

Licence check (done 2026-10-28, from the upstream files)

Source Licence Compatible with our Apache-2.0 What taking it in requires
serde_yaml 0.9.34 (dtolnay/serde-yaml): API shape, behaviour, tests MIT OR Apache-2.0 (Cargo.toml, LICENSE-MIT, LICENSE-APACHE) yes ported tests keep the copyright/permission notice (author: David Tolnay; LICENSE-MIT has no copyright line of its own) and say they were changed (Apache 4(b)); a LICENSE-serde_yaml / NOTICE entry beside them
yaml-test-suite (yaml/yaml-test-suite) MIT, “Copyright (c) 2016-2020 Ingy döt Net” yes ship its License file with the data
… its “Spec Example” cases, copied from the YAML 1.2 spec the spec says “This document may be freely copied, provided it is not modified” (yaml-spec 1.2.2) yes if the files are taken unchanged vendor the data files byte for byte; our tests read them, never rewrite them
libyaml / unsafe-libyaml (serde_yaml’s parser) MIT yes only a reference for behaviour; no code is taken

Not checked: the data-* release branches of the yaml-test-suite (GitHub’s API did not answer from here); their licence is to be confirmed before one of them is the vendored form.


Generated by Claude Code

keywan-ghadami, 2026-10-28

What the language must have first (probes, 2026-10-28, target/fast/nikaia on develop)

Need State Blocks
Zero-copy tree: parse(input) -> Value whose strings are views works: lowers to enum Value<'a> { Str(&'a str), .. }, fn parse(input: &str) -> Value<'_> nothing
Pull / event parser: a struct holding the input and a position, next returning views broken: checker accepts, rustc refuses (lifetime; move while borrowed); a ref String field refuses a text read at run time (NK1106) while NK1261 calls ref redundant. Filed as #929 the event parser, the editing model
Cutting text at positions: text::Pos, text::Cursor, before / after / between, find(…; from:) ADR-374 and ADR-320 accepted, not built (#453); json works around it by copying the text into a Vec[scalar] a zero-copy scanner that is not rewritten later
Streaming input exists: io::read_to_string(), stdin line by line and in chunks (ADR-019), fs::map. serde_yaml 0.9’s from_reader reads everything first, so the drop-in needs nothing more. An incremental parser over chunks is the package’s design, not a language gap nothing for the start
A type as a parameter, T::kind, building a value from fields (#859) not built only the typed yaml::read[T] / write[T] on the Nikaia side; the Rust layer uses serde
Lowering a package into its own crate (as lower-std does for std), one version for nikaia.toml and Cargo.toml not built publishing, not the core
Bindings nikaia bind python / node exist (ADR-284 D26: one C library, ctypes / N-API over it): an alternative to PyO3 / napi for step 8 nothing

Order proposed: #929 and the text positions (ADR-374 / #453) before the parser core; the ADR can be written now; #859 and the package lowering run alongside.


Generated by Claude Code

keywan-ghadami, 2026-10-28

Licences: decided (owner, 2026-10-28)

The owner accepts the licence check above. Test data and ported tests may enter the repository on these terms, with no further approval:

With decisions 1-4 above and this one, nothing here waits on the owner any more: label ready. Before the parser core, two language steps: #929 step 3 (does not block the parser) and the text positions (ADR-374 / #453).


Generated by Claude Code

keywan-ghadami, 2026-10-29

Entscheidungen vom 2026-10-29 zur Gestalt des Pakets:


Generated by Claude Code

keywan-ghadami, 2026-10-29

Streaming über winnow-grammar, entschieden am 2026-10-29 (Owner, nach dem Review des Upstream-Agenten):

  1. Stufe 0, zuerst: ein Einstieg über einen Reader auf Basis von #[frame].
    • Block lesen, bis zur letzten Grenze mit dem heutigen Parser parsen, Rest behalten.
    • Längengrenze ist die maximale Frame-Länge.
    • Paralleles Lesen und Parsen (D) gehört dazu.
    • Prüfstein: Die Frame-Prüfung soll Grammatiken annehmen, die Einrückung über Regelwerte tragen (ADR 26). Erster Testfall ist ein YAML-Strom mit --- am Zeilenanfang.
  2. Messung: Was kostet Partial auf dem vollständigen Pfad? Kostet es nichts, gibt es nur einen Codepfad. Sonst eine Deklaration, die unabhängig von input bytes; ist.
  3. A1: unvollständige Eingabe pro Einstieg (parse_<rule>_reader, Form prefix ELEM* suffix). Getestet wird jede Eingabe, geteilt an jedem Byte-Offset und in 1-Byte-Häppchen.
  4. Nächster geplanter Schritt: eine markierte Wiederholung an einem statischen Pfad eine Ebene tiefer. Fälle: eine Sequenz auf oberster Ebene in YAML, {"items": [...]} in JSON, ein XML-Wurzelelement mit vielen Kindern.
  5. Abgelehnt bzw. zurückgestellt:
    • Volles A2 (verdinglichter Stapel) ist zurückgestellt.
    • B (Block-Puffer mit gehaltenen Views) ist abgelehnt. Nikaia löst Lebensdauern über Kopie oder intern.
    • C (Fassung, die nur erkennt) ist zurückgestellt, bis eine Messung dafür spricht.

Alle Punkte sind allgemein formuliert, YAML ist jeweils nur der erste Testfall. Das YAML-Paket beginnt mit vollständiger Eingabe und fs::map und bekommt stream, sobald Stufe 0 da ist.


Generated by Claude Code

keywan-ghadami, 2026-10-30

Die Engine steht jetzt auf winnow-grammar 3b2dd3d (4853408, 0.0.1304). Upstream hat Stufe 0 und den statischen Pfad gebaut (dort ADR 27):

Nikaia schreibt #[stream] noch nicht. Das kommt mit dem Feature stream des Pakets.


Generated by Claude Code

keywan-ghadami, 2026-10-30

Licence of the yaml-test-suite data branch (checked 2026-10-30)

Question for the owner: may data be vendored byte for byte into packages/yaml/tests/suite/ with main’s License beside it and a file naming both commits (the data is the copyright holder’s own generated form of the MIT-licensed main)? Until then #928 step 5 waits; steps 2-4 use the package’s own tests.

ADR-yaml-1 is on develop; the lane’s first batch is docs/briefs/yaml/batch.txt.


Generated by Claude Code

keywan-ghadami, 2026-10-30

Owner, 2026-10-30: yaml-test-suite licence answered, and a rule. The data branch may be vendored, with main’s MIT License beside it. It must be vendored byte for byte and is never changed: no case edited, reformatted, renamed, re-encoded, trimmed or deleted, not even a failing one; no formatter or whitespace fix touches the directory. Failing cases go into the pass list beside it. An update is a fresh byte-for-byte copy of a newer upstream commit. Recorded in ADR-yaml-1 D9 (76cccc1). The vendoring step also adds a .gitattributes with -text for packages/yaml/tests/suite/**, so git never converts line endings.


Generated by Claude Code

keywan-ghadami, 2026-10-30

Owner’s decisions, 2026-10-30, recorded in ADR-yaml-1 (5914134): (1) no generic radix parse in std for now; numbers of a known shape are converted for that shape, checking nothing per digit (hex digit (c & 15) + 9 * (c >> 6), 0x/0o overflow by the count of significant digits, decimal stays text::parse_i64), D5. (2) scalar::from(n) with a precondition, n as scalar refused (#1003). (3) Escapes are checked (digits, range) and decoded once, in the grammar, so an invalid escape is a parse error with line and column and the guard’s fact proves scalar::from’s precondition; only a double-quoted scalar with an escape or line break gets text of its own, D4/D11. Next lane batch: y4 (#1003), y5 (escapes and radix, starting with a probe whether the prover carries a guard’s fact into the action), y2. To build updated.


Generated by Claude Code

keywan-ghadami, 2026-10-30

Owner’s decisions, 2026-10-30 (recorded by the architect):

  1. #639: unrolling as invariant is decided. For a loop with a constant trip count of at most 16 turns the prover proposes per-turn interval candidates, each checked like any candidate. Recorded as ADR-361 D12; #639 is ready with a To build; Part III 14.2 comes with the build (y7a-639-loop-ranges.md). Built together with #1009 and the untyped-let read (y7b). It frees json’s \u and so #1003 step 3 (y4c).
  2. Bindings inside text(..) are supported (s:text(parts:Part*): the raw view and the inner bindings in one pass). This settles #1013 point 1 as “support”; the yaml escapes use it instead of peek(text(..)), so nothing is parsed twice. Part II 10 text comes with the build (y6-1013-text-bindings.md). The peek binding refusal in winnow-grammar is reported upstream by the main session and does not block.
  3. Scalar.value is a private field, read through text(). ADR-282 D22 stays as it is, and ADR-yaml-1 D4 is amended. Whether the value is a view or owned stays internal (y5b-escapes.md).

Lane batch (docs/briefs/yaml/batch.txt): y6-1013-text-bindings, y5b-escapes, y7a-639-loop-ranges. After them: y7b-1009-let-reads, y4c-1003-finish, y2-tree-properties, y3-1000-views-from-ref (To build above).


Generated by Claude Code

keywan-ghadami, 2026-10-31

95a690fe (anchors, tags and aliases, y2 step 2) is part of #928; its message reads Fixes none by a slip of the report (builder rule fixed in .claude/agents/builder.md).


Generated by Claude Code

Back to Open Issues