@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.
- package/flows/lib/port.js +147 -0
- package/flows/port-compile.js +139 -0
- package/flows/port-files.js +173 -0
- package/flows/port-guide.js +245 -0
- package/flows/port-ledger.js +180 -0
- package/flows/port-tests.js +177 -0
- package/kits/port/README.md +32 -0
- package/kits/port/packs/scala-ts/pack.md +36 -0
- package/kits/port/packs/scala-ts/patterns/pitfalls-scala-ts.md +21 -0
- package/kits/port/packs/scala-ts/prompts/porting.md +64 -0
- package/kits/port/packs/scala-ts/reviewers/effect-fidelity.md +13 -0
- package/kits/port/packs/zig-rust/pack.md +43 -0
- package/kits/port/packs/zig-rust/patterns/pitfalls-zig-rust.md +22 -0
- package/kits/port/packs/zig-rust/prompts/porting.md +67 -0
- package/kits/port/packs/zig-rust/reviewers/port-fidelity.md +13 -0
- package/package.json +4 -4
|
@@ -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.
|
|
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.
|
|
56
|
-
"@llm4ts/flow": "2.
|
|
57
|
-
"@llm4ts/runner": "2.
|
|
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"
|