@llm4ts/shell 2.32.0 → 2.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: Scala/ZIO → TypeScript/Effect pitfalls — alike on the page, different at runtime
3
+ matches: .
4
+ tags: port, scala, zio, effect
5
+ ---
6
+ 1. **Type parameter order.** `ZIO[R, E, A]` is `Effect<A, E, R>`: a mechanical
7
+ copy of the parameter list silently swaps the requirement and the value.
8
+ 2. **Equality.** `==` on a case class compares structure; `===` on an object
9
+ compares identity. Use `Equal.equals` or compare fields.
10
+ 3. **Laziness.** A Scala `lazy val` or by-name parameter evaluates once or on
11
+ demand; a TypeScript expression evaluates where it is written. Wrap in a
12
+ thunk or `Effect.suspend`.
13
+ 4. **Option.get and head.** Scala throws; TypeScript yields `undefined` and
14
+ carries on. Make the absence a typed failure.
15
+ 5. **Integer division and overflow.** Scala `Int` wraps at 32 bits and `/`
16
+ truncates; JavaScript numbers are doubles. Use `Math.trunc` and check
17
+ ranges where the source relied on `Int`.
18
+ 6. **String formatting and `toString`.** A Scala `toString` on a case class
19
+ prints its fields; the TypeScript default prints `[object Object]`.
20
+ 7. **Implicit conversions and givens.** They vanish in the port; every one is
21
+ an explicit call or a service the draft must name.
@@ -0,0 +1,64 @@
1
+ You are translating one Scala 3 / ZIO 2 file to TypeScript with Effect 4.
2
+ Read this whole document before writing any code. The first pass is a
3
+ **draft** `.ts` beside the `.scala`, same basename, that captures the logic
4
+ faithfully; it does **not** need to type-check. The compile pass makes it
5
+ type-check module by module.
6
+
7
+ ## Ground rules
8
+
9
+ - Same module, same names (camelCase for values, PascalCase for types), same
10
+ order of declarations, same control flow. Reviewers diff the two files side
11
+ by side.
12
+ - Use Effect 4 as the repository uses it: `Effect.gen` for sequencing,
13
+ `Effect.fn` for named reusable operations, `Context.Service` and `Layer` for
14
+ replaceable dependencies, `Schema.Class` / `Schema.TaggedClass` for data,
15
+ `Schema.TaggedError` for expected failures. Never `any`, never an unchecked
16
+ cast, never a global `Error` as a domain error.
17
+ - Relative imports use `.ts` extensions; a module imports a contract, never a
18
+ sibling's internals.
19
+ - Leave `// TODO(port): <reason>` for anything you cannot translate
20
+ confidently. Do not guess.
21
+ - Do not translate build definitions, sbt plugins or Scala-only tooling;
22
+ note them as `// SKIPPED(port): <what>`.
23
+
24
+ ## Type map
25
+
26
+ | Scala / ZIO | TypeScript / Effect |
27
+ | ------------------------------------ | ---------------------------------------------- |
28
+ | `ZIO[R, E, A]` | `Effect.Effect<A, E, R>` (note the order) |
29
+ | `UIO[A]` / `Task[A]` | `Effect.Effect<A>` / `Effect.Effect<A, UnknownException>` |
30
+ | `ZStream[R, E, A]` | `Stream.Stream<A, E, R>` |
31
+ | `ZLayer[RIn, E, ROut]` | `Layer.Layer<ROut, E, RIn>` |
32
+ | `trait Service` + `ZIO.service` | `Context.Service` class + `yield* Service` |
33
+ | `case class` | `Schema.Class` |
34
+ | `enum` / sealed trait ADT | `Schema.Union` of `Schema.TaggedClass` |
35
+ | `Option[A]` | optional property / `A \| undefined` |
36
+ | `Either[E, A]` | `Result` or `Effect.Effect<A, E>` |
37
+ | `Chunk[A]` | `ReadonlyArray<A>` |
38
+ | `Ref[A]`, `Queue[A]`, `Hub[A]` | `Ref`, `Queue`, `PubSub` |
39
+ | `Scope` / `acquireRelease` | `Scope` / `Effect.acquireRelease` |
40
+ | `Schedule` | `Schedule` |
41
+ | zio-json codecs | `Schema` with `Schema.fromJsonString` |
42
+ | ZIO Test `spec` / `test` | `@effect/vitest` `describe` / `it.effect` |
43
+ | `for` comprehension | `Effect.gen(function* () { … })` |
44
+
45
+ ## Idiom map
46
+
47
+ - `ZIO.attempt(…)` → `Effect.try(…)`; `ZIO.fail(E)` → `Effect.fail(E)`; `.orDie` → `Effect.orDie`.
48
+ - `.provide(layer)` / `.provideSome` → `Effect.provide(layer)`; layer composition order follows `Layer.provide`.
49
+ - `.catchSome { case e: X => … }` → `Effect.catchTag("X", …)`.
50
+ - `zio.Duration` → `Duration` from effect; `ZIO.sleep` → `Effect.sleep`.
51
+ - Implicit parameters and givens become explicit arguments or services.
52
+ - `==` on case classes is structural; TypeScript needs `Equal.equals` or a field-by-field compare.
53
+
54
+ ## Output format
55
+
56
+ End the file with the trailer the flow reads:
57
+
58
+ ```ts
59
+ // PORT STATUS
60
+ // source: <path>.scala
61
+ // confidence: high | medium | low
62
+ // todos: <count of TODO(port)>
63
+ // notes: <one line>
64
+ ```
@@ -0,0 +1,13 @@
1
+ ---
2
+ files: \.ts$
3
+ ---
4
+ Review a TypeScript draft against the rulebook as a port reviewer would, with
5
+ the Scala source in mind: the diff is the draft. Report as findings: a method,
6
+ field or case the source has that the draft lacks; `any`, an unchecked cast, a
7
+ namespace, or a global `Error` in an error channel; a service without a layer
8
+ where the source had a `ZLayer`; a relative import without a `.ts` extension;
9
+ a case class turned into a plain interface where a `Schema.Class` belongs;
10
+ `ZIO[R, E, A]` order kept as `Effect<R, E, A>`; a `for` comprehension turned
11
+ into nested callbacks where `Effect.gen` was the answer; a guessed translation
12
+ where a `TODO(port)` was honest. Do not report imports that cannot resolve
13
+ yet: the compile pass owns those.
@@ -0,0 +1,43 @@
1
+ # Pack: zig-rust
2
+
3
+ source: zig
4
+ sources: .*\.zig$
5
+ exclude: (^|/)(zig-out|zig-cache|\.zig-cache|node_modules|vendor)/
6
+ target: {{dir}}/{{base}}.rs
7
+ comment: //
8
+ specs-dir: docs/port
9
+ features-dir: docs/port/features
10
+
11
+ ## Gates
12
+
13
+ - check: cargo check --workspace
14
+
15
+ ## Diagnostics
16
+
17
+ - command: cargo check --workspace --message-format=json
18
+ - format: cargo
19
+
20
+ ## Ledger
21
+
22
+ - unit: ^\s+(\w+):\s*(?:\?\*|\*|\[\]|\[\*\])
23
+ - classes: OWNED, SHARED, BORROW_PARAM, BORROW_FIELD, STATIC, JSC_BORROW, BACKREF, INTRUSIVE, FFI, ARENA, UNKNOWN
24
+ - question: Who owns the memory this pointer or slice field points at, and how long does it live? The Rust type follows from the class (OWNED → Box/Vec, BORROW_* → a reference with a lifetime, SHARED → Rc/Arc, FFI → a raw pointer).
25
+
26
+ ## Differential
27
+
28
+ - tests: ^test/.*\.test\.(ts|js)$
29
+ - legacy: scripts/legacy-test.sh {{file}}
30
+ - target: scripts/target-test.sh {{file}}
31
+ - timeout: 60
32
+
33
+ ## Audit
34
+
35
+ - dimensions: error model, allocator threading, collections and strings, comptime carry-over, pointer and ownership idioms, API shape, what not to translate
36
+
37
+ ## Review rules
38
+
39
+ Every `unsafe` block carries a `// SAFETY: <why>` comment, and no new
40
+ `unsafe` appears outside FFI. A `TODO(port): <reason>` marker is the right
41
+ answer where the translation is uncertain; a guess is not. `anyhow`,
42
+ `tokio`, `rayon`, `async fn` and `std::fs`/`std::net`/`std::process` are
43
+ findings: the rulebook forbids them.
@@ -0,0 +1,22 @@
1
+ ---
2
+ title: Zig → Rust pitfalls — syntactically alike, semantically different
3
+ matches: .
4
+ tags: port, zig, rust
5
+ ---
6
+ The regressions the Bun port shipped (May 2026) were code that reads the same
7
+ in both languages and does something different. Check for each:
8
+
9
+ 1. **Asserts with side effects.** Zig's `std.debug.assert(f())` always calls
10
+ `f` in every build; Rust's `debug_assert!(f())` erases the call in release.
11
+ Keep the call, assert the result.
12
+ 2. **Slice casts on odd lengths.** The Zig helper truncated a `[]u8` to the
13
+ element size; `bytemuck::cast_slice` panics on a remainder. Truncate first.
14
+ 3. **Bounds checks kept.** Zig ReleaseFast dropped them; Rust release keeps
15
+ them, so a latent off-by-one the Zig never hit is reachable. Treat an
16
+ index panic as a real bug in the source's logic, not as noise.
17
+ 4. **Comptime format strings.** `comptime` string assembly happened once at
18
+ build time; a runtime `format!` per call changes both cost and, where
19
+ markers are rewritten, the bytes. Use a macro or a `const`.
20
+ 5. **Placeholder constants.** A value "to be threaded through later" becomes
21
+ a different limit in the port. Every `TODO(port)` placeholder is listed in
22
+ the trailer and closed in the compile pass.
@@ -0,0 +1,67 @@
1
+ You are translating one Zig file to Rust. Read this whole document before
2
+ writing any code. The goal of the first pass is a **draft** `.rs` beside the
3
+ `.zig`, same basename, that captures the logic faithfully; it does **not**
4
+ need to compile. The compile pass makes it compile crate by crate.
5
+
6
+ ## Ground rules
7
+
8
+ - Write the `.rs` in the same directory as the `.zig`, same basename. Do not
9
+ invent crate layouts; put the file where the source is.
10
+ - Match the Zig's structure: same `fn` names (snake_case), same field order,
11
+ same control flow. Reviewers diff `.zig` and `.rs` side by side.
12
+ - No `tokio`, `rayon`, `hyper`, `async-trait`, `futures`. No `std::fs`,
13
+ `std::net`, `std::process`: the code base owns its event loop and
14
+ syscalls. No `async fn`.
15
+ - `unsafe` is fine where the Zig was already unsafe. Annotate every block
16
+ with `// SAFETY: <why>`.
17
+ - Leave `// TODO(port): <reason>` for anything you cannot translate
18
+ confidently. Do not guess: flagging is better than wrong code.
19
+ - Leave `// PERF(port): <zig idiom>` wherever the Zig used a
20
+ performance-specific idiom, for the profiling pass.
21
+ - Do not translate tests, build scripts or comptime-only helpers that exist
22
+ to drive the Zig build; note them as `// SKIPPED(port): <what>`.
23
+
24
+ ## Type map
25
+
26
+ | Zig | Rust |
27
+ | ------------------------- | ------------------------------------- |
28
+ | `[]const u8` | `&[u8]` (or `&str` only when the Zig guarantees UTF-8) |
29
+ | `[]u8` owned | `Vec<u8>` |
30
+ | `?T` | `Option<T>` |
31
+ | `anyerror!T`, `E!T` | `Result<T, Error>` with the crate's error enum, never `anyhow` |
32
+ | `*T` | `&mut T` or `*mut T` per the lifetime table |
33
+ | `*const T` | `&T` or `*const T` per the lifetime table |
34
+ | `std.ArrayList(T)` | `Vec<T>` |
35
+ | `std.StringHashMap(V)` | `HashMap<Vec<u8>, V>` (the crate's hasher) |
36
+ | `comptime T: type` | a generic parameter or a trait |
37
+ | `switch` on tagged union | `match` on an enum with data |
38
+ | `defer` | a scope guard or `Drop` |
39
+ | `errdefer` | explicit cleanup on the `Err` path |
40
+
41
+ ## Idiom map
42
+
43
+ - Allocator parameters: delete them outside the AST crates; the Rust side
44
+ allocates through the type's own methods.
45
+ - `std.debug.assert(expr)` where `expr` has side effects: keep the side
46
+ effect, assert the value (`debug_assert!` erases the call).
47
+ - Casting slices (`@ptrCast`, `@alignCast`) on odd lengths: the Zig helper
48
+ truncated; `bytemuck::cast_slice` panics. Truncate explicitly.
49
+ - Bounds checks the Zig dropped in ReleaseFast stay in Rust; do not
50
+ "optimise" with `get_unchecked` in the draft.
51
+ - `comptime` format strings and string builders become macros or
52
+ `const` arrays, never runtime strings assembled per call.
53
+
54
+ ## Output format
55
+
56
+ End the file with the trailer the flow reads:
57
+
58
+ ```rust
59
+ // PORT STATUS
60
+ // source: <path>.zig
61
+ // confidence: high | medium | low
62
+ // todos: <count of TODO(port)>
63
+ // notes: <one line>
64
+ ```
65
+
66
+ `confidence: low` means "the logic is probably wrong; re-read the Zig in the
67
+ compile pass".
@@ -0,0 +1,13 @@
1
+ ---
2
+ files: \.rs$
3
+ ---
4
+ Review a Rust draft against the rulebook as a port reviewer would, with the
5
+ Zig source in mind: the diff is the draft. Report as findings: a function,
6
+ field or branch the source has that the draft lacks (dropped logic); a
7
+ `pub fn deinit(&mut self)` where `impl Drop` is the rulebook's answer;
8
+ `anyhow::Error` or `Box<dyn Error>` where the crate's error enum belongs;
9
+ a bare `as` narrowing cast; an `unsafe` block without a `// SAFETY:` line;
10
+ `async fn`, `tokio`, `rayon`, `std::fs`, `std::net` or `std::process`; a
11
+ `debug_assert!` whose argument had a side effect in the source; a guessed
12
+ translation where a `TODO(port)` was the honest answer. Do not report imports
13
+ that cannot resolve yet or lifetimes: the compile pass owns those.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llm4ts/shell",
3
- "version": "2.32.0",
3
+ "version": "2.34.0",
4
4
  "description": "Interactive shell and CLI for llm4ts: flow discovery, run-a-flow, and view",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,9 +52,9 @@
52
52
  "dependencies": {
53
53
  "@effect/platform-node": "4.0.0",
54
54
  "@effect/platform-node-shared": "4.0.0",
55
- "@llm4ts/core": "2.32.0",
56
- "@llm4ts/flow": "2.32.0",
57
- "@llm4ts/runner": "2.32.0"
55
+ "@llm4ts/core": "2.34.0",
56
+ "@llm4ts/flow": "2.34.0",
57
+ "@llm4ts/runner": "2.34.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "effect": "4.0.0"