@panagos/jojoscript 0.9.3 → 1.0.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/README.md CHANGED
@@ -1,13 +1,12 @@
1
- # JojoScript 0.9.3
1
+ # JojoScript 1.0.0
2
2
 
3
- JojoScript is a small JavaScript-compatible language and runtime for
4
- expressive, composable pipelines. It adds lightweight syntax for declarations,
5
- functions, pattern matching, type annotations, and pipelines while preserving
6
- ordinary JavaScript. Its compiler uses a lightweight lexer and source
7
- transformations rather than a full AST, keeping the toolchain compact. The
8
- project also provides lazy and async collection operators, checkpointing,
9
- retries, metrics, pipeline graphing, CLI workflows, effect handlers, MCP
10
- support, and VS Code editor support.
3
+ JojoScript is a lightweight language and runtime for data pipelines and agent
4
+ workflows, built on JavaScript.
5
+
6
+ It combines expressive pipeline syntax, lazy data processing, resumable
7
+ execution and effect handling with MCP tool serving and A2A agent
8
+ communication. It compiles to ordinary JavaScript and keeps the familiar
9
+ JavaScript ecosystem accessible, without requiring a heavyweight framework.
11
10
 
12
11
  See [CHANGELOG.md](CHANGELOG.md) for release history and
13
12
  [example/](example/) for a complete runnable application that exercises
@@ -23,8 +22,17 @@ npm install @panagos/jojoscript
23
22
 
24
23
  ```bash
25
24
  jojo build <file.jojo|directory> [--out <directory>] [--watch]
26
- jojo run <file.jojo> [--out <directory>]
27
- jojo graph <file.jojo> [--format text|dot]
25
+ jojo run <file.jojo> [--out <directory>] [--deterministic [--seed <n>]]
26
+ jojo graph <file.jojo> [--format text|dot] [--pipeline <name>]
27
+ jojo explain <file.jojo> [--format text|json] [--pipeline <name>]
28
+ jojo plan <file.jojo> [--format json] [--pipeline <name>]
29
+ jojo generate <plan.json>
30
+ jojo check <file.jojo|plan.json> [--format text|json] [--machine]
31
+ jojo describe <file.jojo> [--pipeline <name>]
32
+ jojo simulate <file.jojo> [--input <records>] [--format text|json] [--pipeline <name>]
33
+ jojo why <file.jojo> [--format text|json] [--pipeline <name>]
34
+ jojo format <file.jojo|directory> [--check]
35
+ jojo parse <file.jojo> [--json]
28
36
  jojo profile <file.jojo>
29
37
  jojo resume <file.jojo> [--out <directory>]
30
38
  jojo replay <file.jojo> --from <checkpoint> [--out <directory>]
@@ -36,7 +44,17 @@ jojo --help
36
44
  - `jojo build src` compiles every `.jojo` file under `src` to `src/dist/*.js` (or `--out <dir>`).
37
45
  - `jojo build src --watch` rebuilds whenever a `.jojo` file under `src` changes.
38
46
  - `jojo run app.jojo` compiles a single file and immediately runs it with Node — useful for a fast dev loop.
47
+ - `jojo run app.jojo --deterministic --seed 42` runs the program in [deterministic execution mode](#deterministic-execution-mode): seeded `random()`, a fixed clock, sequential `parallel()`, and deterministic effect handlers only.
39
48
  - `jojo graph app.jojo` prints a static pipeline graph; use `--format dot` for Graphviz output.
49
+ - `jojo explain app.jojo` describes what each pipeline means (stages, effects, properties) without running it; use `--format json` for tools and LLMs. See [Explaining pipelines](#explaining-pipelines).
50
+ - `jojo plan app.jojo` prints a canonical JSON execution plan, and `jojo generate plan.json` turns a plan back into validated JojoScript. See [Machine-readable execution plans](#machine-readable-execution-plans).
51
+ - `graph`, `explain`, and `plan` accept `--pipeline <name>` to select one pipeline by its (inferred or [declared](#named-pipelines)) name.
52
+ - `jojo check app.jojo` (or `plan.json`) reports structured diagnostics with codes, lines, and suggestions, and exits 1 on errors; `--machine` prints JSON. See [Checking programs](#checking-programs).
53
+ - `jojo describe app.jojo` prints one JSON document describing the pipelines, effects, functions, and types in a file. See [Describing programs](#describing-programs).
54
+ - `jojo simulate app.jojo --input 1000000` estimates item counts, effect calls, and risks for a given input size without running anything. See [Simulating pipelines](#simulating-pipelines).
55
+ - `jojo why app.jojo` answers questions such as "why is this pipeline not deterministic?" in plain language. See [Asking why](#asking-why).
56
+ - `jojo format src` reformats `.jojo` files in place; `--check` lists unformatted files and exits 1. See [Formatting](#formatting).
57
+ - `jojo parse app.jojo --json` lists every syntax construct (imports, effects, types, functions, pipelines, handle blocks, matches) with its line. See [Parsing programs](#parsing-programs).
40
58
  - `jojo profile app.jojo` executes a pipeline and prints per-node item counts, duration, and throughput.
41
59
  - `jojo resume app.jojo` reuses completed file-backed checkpoints; `jojo replay app.jojo --from name` starts from a selected checkpoint.
42
60
  - `jojo test app.jojo` compiles the file and runs its co-located `<name>.test.js` test when present.
@@ -137,6 +155,69 @@ async fn loadUsers() {
137
155
  }
138
156
  ```
139
157
 
158
+ ### Pure and effectful functions
159
+
160
+ `pure` and `effectful` declare a function's role. They compile away to an
161
+ ordinary function, and may follow `export` or surround `async`:
162
+
163
+ ```jojo
164
+ effect storeUser(user)
165
+
166
+ export pure fn normalizeUser(user) {
167
+ return { ...user, email: user.email.trim().toLowerCase() }
168
+ }
169
+
170
+ effectful fn saveUser(user) {
171
+ return storeUser(user)
172
+ }
173
+ ```
174
+
175
+ The compiler rejects a `pure fn` that uses a declared `effect` or an
176
+ `effectful fn`, whether it calls it or passes it along (`map(saveUser)`):
177
+
178
+ ```
179
+ pure fn "normalizeUser" uses effectful function "saveUser" (line 9)
180
+ ```
181
+
182
+ The check is static and name-based. Strings, comments, property access
183
+ (`user.saveUser`), and recursion are ignored, and calls to unannotated
184
+ functions are allowed. `jojo explain` reports a stage that uses an
185
+ `effectful fn` as `effectful` and the pipeline as non-deterministic, exactly as
186
+ it does for declared effects. See
187
+ [example/src/application/purity-example.jojo](example/src/application/purity-example.jojo).
188
+
189
+ ### Function contracts
190
+
191
+ A block function can state what it expects and what it guarantees. Each
192
+ clause goes on its own line between the parameter list and the opening `{`:
193
+
194
+ ```jojo
195
+ fn applyDiscount(price, percent)
196
+ requires price >= 0
197
+ requires percent >= 0 && percent <= 100
198
+ ensures result >= 0 && result <= price
199
+ {
200
+ return price - price * percent / 100
201
+ }
202
+ ```
203
+
204
+ `requires` clauses are checked on entry, in order, before the body runs.
205
+ `ensures` clauses are checked after it, with the return value available as
206
+ `result` (async functions are awaited first). The body runs inside an arrow
207
+ function, so `this` and `arguments` keep referring to the declared function.
208
+ A broken clause throws `ContractError`:
209
+
210
+ ```
211
+ ContractError: Contract violated: applyDiscount requires percent >= 0 && percent <= 100
212
+ ```
213
+
214
+ The error carries `functionName`, `kind` (`"requires"` or `"ensures"`), and
215
+ `condition`, and is non-retryable, so `retry()` never repeats a call that
216
+ broke its contract. Contract clauses keep the source line count. Contracts
217
+ apply to block functions only, and a function using `ensures` should not
218
+ have a parameter named `result`. See
219
+ [example/src/application/contracts-example.jojo](example/src/application/contracts-example.jojo).
220
+
140
221
  ### Optional type annotations
141
222
 
142
223
  Parameters and return values can be annotated. Annotations are **never
@@ -198,8 +279,77 @@ category := match true {
198
279
  }
199
280
  ```
200
281
 
201
- `match` is a value-equality construct, not structural pattern matching:
202
- patterns are plain expressions compared with `===`.
282
+ #### Structured patterns
283
+
284
+ Object patterns match on the shape of a value. Arms can be separated by
285
+ newlines instead of commas:
286
+
287
+ ```jojo
288
+ message := match result {
289
+ { status: "ok", value } => value
290
+ { status: "error", error: { code: 404 } } => "not found"
291
+ { status: "error", error } => handle(error)
292
+ }
293
+ ```
294
+
295
+ Inside an object pattern:
296
+
297
+ - a literal or expression (`status: "ok"`, `code: Status.OK`) is compared with `===`;
298
+ - a bare name (`value`) binds that field, and the field must be present;
299
+ - `key: name` binds the field under another name, and `key: { ... }` matches a nested object;
300
+ - quoted keys (`"content-type": "json"`) are supported.
301
+
302
+ A match with at least one object pattern may omit the `_` arm. If no arm
303
+ matches, it throws `TypeError: No match arm matched the value`, so an
304
+ unexpected shape fails loudly instead of returning `undefined`. A match whose
305
+ patterns are all plain expressions still requires a final `_` arm and
306
+ compiles exactly as before. See
307
+ [example/src/application/structured-match-example.jojo](example/src/application/structured-match-example.jojo).
308
+
309
+ ### Tagged unions
310
+
311
+ `type` declares a tagged union: a value that is exactly one of a fixed set of
312
+ variants. Each variant has a constructor:
313
+
314
+ ```jojo
315
+ type PaymentResult =
316
+ | Paid(amount)
317
+ | Declined(reason)
318
+ | Pending
319
+ ```
320
+
321
+ becomes:
322
+
323
+ ```js
324
+ const PaymentResult = defineType("PaymentResult", { Paid: ["amount"], Declined: ["reason"], Pending: [] }); const { Paid, Declined, Pending } = PaymentResult
325
+ ```
326
+
327
+ `Paid(40)` returns the frozen plain object `{ tag: "Paid", amount: 40 }`, so
328
+ values serialize to JSON unchanged. Constructors reject the wrong number of
329
+ arguments (`Paid expects 1 argument (amount), got 0`), and fieldless variants
330
+ are called with no arguments (`Pending()`). `export type` exports the type and
331
+ all of its constructors. Variant names must start with an uppercase letter,
332
+ variants and fields must be unique, and no field may be named `tag`.
333
+
334
+ Constructor patterns match a variant by its tag and bind its fields by
335
+ position, so they also match values decoded from JSON:
336
+
337
+ ```jojo
338
+ message := match result {
339
+ Paid(amount) => `paid ${amount}`
340
+ Declined("insufficient funds") => "ask for another card"
341
+ Declined(reason) => `declined: ${reason}`
342
+ Pending() => "waiting for the bank"
343
+ }
344
+ ```
345
+
346
+ Pattern arguments can be literals, `_`, bindings, or nested object and
347
+ constructor patterns. Like object patterns, a match over constructor patterns
348
+ may omit `_` and throws `TypeError` when nothing matches. A capitalized call
349
+ such as `Ok(v)` inside a `match` is always a constructor pattern. Avoid naming
350
+ a variant `Error`, since its constructor would shadow JavaScript's `Error` in
351
+ that module. See
352
+ [example/src/application/tagged-unions-example.jojo](example/src/application/tagged-unions-example.jojo).
203
353
 
204
354
  ### Pipelines
205
355
 
@@ -222,6 +372,52 @@ A stage can have no arguments (`users |> unique`) or arguments
222
372
  inputs, including `await` expressions and pipelines inside `return`
223
373
  statements, function bodies, and object literals.
224
374
 
375
+ #### Named pipelines
376
+
377
+ A `pipeline` block declares a named, reusable pipeline. Its first line names
378
+ the source, and the remaining lines are stages:
379
+
380
+ ```jojo
381
+ export pipeline enrichOrders {
382
+ source orders
383
+
384
+ |> filter(order => order.active)
385
+ |> map(addVat)
386
+ |> checkpoint("enriched")
387
+ |> toArrayAsync
388
+ |> await
389
+ }
390
+ ```
391
+
392
+ It compiles to a function. An identifier source becomes its single parameter
393
+ (`enrichOrders(orders)`); any other source expression gives a function with
394
+ no parameters. A pipeline with a `|> await` stage (or an awaited source) becomes an
395
+ `async` function; an `await` inside a stage callback does not. Also,
396
+ blank lines inside the block are allowed. Tools see the pipeline under its
397
+ declared name, so `jojo explain app.jojo --pipeline enrichOrders` (and the same
398
+ option on `graph` and `plan`) selects it directly. See
399
+ [example/src/application/named-pipeline-example.jojo](example/src/application/named-pipeline-example.jojo).
400
+
401
+ #### Stage IDs
402
+
403
+ A stage can be labelled with a trailing `@id` (letters, digits, `_`, and `-`,
404
+ starting with a letter or `_`):
405
+
406
+ ```jojo
407
+ saved := orders
408
+ |> filter(order => order.paid) @paid-only
409
+ |> map(assignCarrier) @pick-carrier
410
+ |> checkpoint("prepared")
411
+ ```
412
+
413
+ The label is removed from the compiled code. It becomes the stage's node id in
414
+ runtime metadata (checkpoint, inspect, parallel, batch, and `jojo profile`),
415
+ in `jojo graph`, in `jojo explain` (`id` and `@pick-carrier` in the text), and
416
+ in `jojo plan` (an `id` field that `jojo generate` writes back). Tools and
417
+ LLMs can therefore refer to "the `@pick-carrier` stage" instead of a position
418
+ in the source. IDs must be unique within a pipeline. They are not part of the
419
+ pipeline's hash, so renaming a label never invalidates saved checkpoints.
420
+
225
421
  #### Placeholder argument
226
422
 
227
423
  By default the piped value is inserted as the stage's first argument. The
@@ -326,6 +522,381 @@ auto-imported):
326
522
  See [ITERATOR_PROPOSAL.md](ITERATOR_PROPOSAL.md) and [ITERATORS.md](ITERATORS.md)
327
523
  for the full laziness/memory model.
328
524
 
525
+ ## Explaining pipelines
526
+
527
+ `jojo explain` lets a person, a tool, or an LLM ask JojoScript what a program
528
+ means instead of inferring it from source. It is static: nothing is executed.
529
+
530
+ ```jojo
531
+ effect enrichOrder(order)
532
+
533
+ export async fn enrichPaidOrders(orders) {
534
+ return handle {
535
+ enrichOrder: order => ({ ...order, region: "EU" })
536
+ } {
537
+ return orders
538
+ |> filter(order => order.status == "paid")
539
+ |> parallel(4)
540
+ |> map(enrichOrder)
541
+ |> checkpoint("enriched-orders")
542
+ |> toArrayAsync
543
+ |> await
544
+ }
545
+ }
546
+ ```
547
+
548
+ ```
549
+ $ jojo explain orders.jojo
550
+ PIPELINE enrichPaidOrders
551
+
552
+ STAGES
553
+ 1 source orders
554
+ 2 filter pure
555
+ 3 parallel concurrency=4
556
+ 4 map effectful: enrichOrder
557
+ 5 checkpoint enriched-orders
558
+ 6 toArrayAsync pure
559
+ 7 await control
560
+
561
+ EFFECTS
562
+ enrichOrder (handler declared)
563
+
564
+ PROPERTIES
565
+ lazy: true
566
+ parallel: true
567
+ resumable: true
568
+ deterministic: false
569
+ ```
570
+
571
+ `jojo explain orders.jojo --format json` prints the same information as an
572
+ array of `{ name, id, stages, effects, properties }` objects, and
573
+ `explainPipelines(source)` / `renderExplanationText(explanation)` are exported
574
+ from `@panagos/jojoscript` for programmatic use.
575
+
576
+ - **name** is inferred: the variable or property a pipeline is assigned to
577
+ (`paid := orders |> ...`), the `fn` name of an expression function, or the
578
+ enclosing named function of a `return` pipeline; otherwise `pipeline-N`.
579
+ - **stages** list each stage's type, raw `args`, referenced `effects`, and a
580
+ `detail`: the source expression, `concurrency=N` for `parallel`, `size=N` for
581
+ `batch`, the checkpoint name, `control` for `await`/`catch`/`orElse`/`pass`,
582
+ or `pure`/`effectful: <effects>`.
583
+ - **effects** are the declared effects the pipeline references, each with the
584
+ same best-effort `staticallyHandled` signal as `jojo graph`.
585
+ - **properties**: `lazy` (no `sort`/`sortBy`/`groupBy`/`partition`
586
+ materialization boundary), `parallel` (has a `parallel` stage), `resumable`
587
+ (has a `checkpoint` stage), `deterministic` (references no declared effect).
588
+
589
+ Purity and determinism only reflect references to declared `effect`s and
590
+ `effectful fn`s (see [Pure and effectful functions](#pure-and-effectful-functions)): a stage
591
+ calling ordinary JavaScript such as `Math.random()` or `fetch()` directly is
592
+ still reported as `pure`. Route external work through effects to make it
593
+ visible. See
594
+ [example/src/application/explain-example.jojo](example/src/application/explain-example.jojo).
595
+
596
+ ## Machine-readable execution plans
597
+
598
+ `jojo plan` gives every pipeline a stable, simple JSON representation, and
599
+ `jojo generate` turns such a plan back into JojoScript. A tool or an LLM can
600
+ read a program as data, or write a plan and have JojoScript validate it:
601
+
602
+ ```
603
+ Jojo source -> jojo plan -> JSON plan -> LLM
604
+ LLM -> JSON plan -> jojo generate -> validated Jojo program
605
+ ```
606
+
607
+ ```jojo
608
+ export fn topCustomers(customers) {
609
+ return customers
610
+ |> filter(customer => customer.active)
611
+ |> sortBy(customer => -customer.spend)
612
+ |> take(2)
613
+ |> map(customer => customer.name)
614
+ |> toArray
615
+ }
616
+ ```
617
+
618
+ ```
619
+ $ jojo plan customers.jojo --format=json
620
+ {
621
+ "version": 1,
622
+ "pipelines": [
623
+ {
624
+ "pipeline": "topCustomers",
625
+ "id": "…",
626
+ "stages": [
627
+ { "type": "source", "expression": "customers" },
628
+ { "type": "filter", "expression": "customer => customer.active" },
629
+ { "type": "sortBy", "expression": "customer => -customer.spend" },
630
+ { "type": "take", "args": ["2"] },
631
+ { "type": "map", "expression": "customer => customer.name" },
632
+ { "type": "toArray" }
633
+ ]
634
+ }
635
+ ]
636
+ }
637
+ ```
638
+
639
+ (Printed with two-space indentation; condensed here.)
640
+
641
+ The plan format, version 1:
642
+
643
+ | Field | Meaning |
644
+ |---|---|
645
+ | `version` | Always `1` for this format |
646
+ | `pipelines[].pipeline` | Inferred name (same rules as `jojo explain`), or `null` for an unnamed pipeline |
647
+ | `pipelines[].id` | The pipeline's deterministic hash (informational, ignored by `generate`) |
648
+ | `pipelines[].stages[0]` | `{ "type": "source", "expression": "..." }` |
649
+ | `parallel` / `batch` | `concurrency` / `size` as a positive integer |
650
+ | `checkpoint` | `name` as a string |
651
+ | `map`, `filter`, `flatMap`, `tap`, `mapAsync`, `filterAsync`, `find`, `some`, `every`, `sortBy`, `distinctBy`, `groupBy`, `partition`, `catch` | `expression`: the single function argument as source text |
652
+ | any other stage | `args`: argument source strings, omitted when there are none |
653
+ | `.prop` / `.method()` | `{ "type": ".length" }` reads a property; `{ "type": ".trim", "args": [] }` calls it |
654
+ | `id` (any stage after the source) | Optional [stage ID](#stage-ids) such as `"pick-carrier"`, unique within the pipeline |
655
+
656
+ A stage whose setting is not a literal (`parallel(limit)`) keeps it in `args`
657
+ instead, so a plan is always lossless. Arguments stay source text: the plan
658
+ describes computation without evaluating it.
659
+
660
+ `jojo generate plan.json` (or `planToJojo(plan)`) emits one
661
+ `name := source |> ...` statement per pipeline. It rejects unknown fields,
662
+ wrong types, misplaced `source` stages, and duplicate names with the exact path
663
+ of the problem (for example
664
+ `Invalid plan: pipelines[0].stages[2].concurrency must be a positive integer`),
665
+ then compiles the program and checks that it parses as JavaScript, without
666
+ running it. The generated program contains only the pipelines: their source
667
+ variables, custom stage functions, and effects must be supplied by
668
+ surrounding code. Validation is not a sandbox: expressions are code, so
669
+ review a generated program as you would any code you did not write.
670
+
671
+ `planPipelines(source)` and `planToJojo(plan)` are exported from
672
+ `@panagos/jojoscript`. See
673
+ [example/src/application/plan-example.jojo](example/src/application/plan-example.jojo).
674
+
675
+ ## Checking programs
676
+
677
+ `jojo check` validates a program, or a JSON plan, without running it. It is
678
+ designed for LLM-generated code: every problem is a structured diagnostic with
679
+ a stable code, a line, and a concrete suggestion, instead of a raw stack trace.
680
+
681
+ ```
682
+ $ jojo check generated.jojo
683
+ ERROR E301
684
+ line 8
685
+ Call divide(1, 0) violates "requires b != 0"
686
+ suggestion: Pass arguments to "divide" that satisfy b != 0
687
+
688
+ 1 error, 0 warnings
689
+ ```
690
+
691
+ `--machine` (or `--format json`) prints the same result as JSON:
692
+
693
+ ```json
694
+ {
695
+ "valid": false,
696
+ "errors": [
697
+ {
698
+ "code": "E301",
699
+ "severity": "error",
700
+ "line": 8,
701
+ "message": "Call divide(1, 0) violates \"requires b != 0\"",
702
+ "suggestion": "Pass arguments to \"divide\" that satisfy b != 0"
703
+ }
704
+ ],
705
+ "warnings": []
706
+ }
707
+ ```
708
+
709
+ The command exits with code 1 when there is at least one error, so a
710
+ generate-check-fix loop can rely on the exit code.
711
+
712
+ | Code | Severity | Meaning |
713
+ |---|---|---|
714
+ | `E100` | error | The compiler rejects the program (with its line when known) |
715
+ | `E101` | error | The compiled JavaScript does not parse (checked by Node's own parser) |
716
+ | `W102` | warning | A declared effect is used but no `handle { ... }` in the file handles it; a caller may still install one |
717
+ | `E201` | error | A `pure fn` uses a declared effect or an `effectful fn` |
718
+ | `E301` | error | A call with literal arguments breaks a `requires` clause |
719
+ | `E401` | error | A `match` over a declared tagged union misses a variant and has no `_` arm |
720
+ | `E402` | error | A constructor pattern has a different number of fields than its variant |
721
+ | `E700` | error | `jojo check plan.json`: the plan is invalid (message names the exact field) |
722
+
723
+ Contract clauses are only evaluated statically when they use nothing but the
724
+ function's parameters, literals, and operators, so `jojo check` never runs
725
+ program code. `checkSource(source)`, `checkPlan(plan)`, and
726
+ `renderCheckText(result)` are exported from `@panagos/jojoscript`. See
727
+ [example/src/application/check-example.jojo](example/src/application/check-example.jojo).
728
+
729
+ ## Describing programs
730
+
731
+ `jojo describe` prints one self-describing JSON document for a file, so a tool
732
+ or an LLM can learn what a program contains without reading its source:
733
+
734
+ ```json
735
+ {
736
+ "language": "jojo",
737
+ "version": "1",
738
+ "pipelines": [
739
+ {
740
+ "name": "openInvoices",
741
+ "inputs": ["records"],
742
+ "effects": [],
743
+ "stages": [
744
+ { "id": "unpaid", "operation": "filter", "expression": "record => !record.paid" },
745
+ { "id": "classify", "operation": "map", "expression": "toInvoice" },
746
+ { "id": "node-3", "operation": "checkpoint", "name": "open-invoices" }
747
+ ],
748
+ "properties": { "lazy": true, "parallel": false, "resumable": true, "deterministic": true }
749
+ }
750
+ ],
751
+ "effects": [{ "name": "storeInvoice", "params": ["invoice"], "handled": true }],
752
+ "functions": [
753
+ { "name": "toInvoice", "line": 12, "purity": "pure", "requires": ["record.amount >= 0"], "ensures": [] }
754
+ ],
755
+ "types": [
756
+ { "name": "Invoice", "variants": [{ "name": "Open", "fields": ["id", "amount"] }, { "name": "Settled", "fields": ["id"] }] }
757
+ ]
758
+ }
759
+ ```
760
+
761
+ - `pipelines` uses the same names, effects, and properties as `jojo explain`,
762
+ and the same stage fields as `jojo plan`. A pipeline whose source is an
763
+ identifier lists it in `inputs`. A stage's `id` is its
764
+ [stage ID](#stage-ids), or the runtime node id (`node-3`) used by
765
+ `jojo graph` and `jojo profile`.
766
+ - `effects` lists declared effects and whether a handler is statically
767
+ visible in the file.
768
+ - `functions` lists functions that declare a purity or contracts.
769
+ - `types` lists tagged unions with their variants and fields.
770
+
771
+ `--pipeline <name>` limits `pipelines` to one pipeline. `describeSource(source)`
772
+ is exported from `@panagos/jojoscript`. See
773
+ [example/src/application/describe-example.jojo](example/src/application/describe-example.jojo).
774
+
775
+ ## Parsing programs
776
+
777
+ `jojo parse <file.jojo> --json` prints the syntax-level view of a file: every
778
+ JojoScript construct the compiler recognizes, with its line, ordered by line.
779
+ Where `jojo describe` summarizes what a program means, `jojo parse` lists what
780
+ it contains, so a tool or an LLM can locate and edit constructs precisely.
781
+
782
+ ```json
783
+ {
784
+ "language": "jojo",
785
+ "version": "1",
786
+ "nodes": [
787
+ { "kind": "type", "line": 6, "name": "Decision", "exported": true,
788
+ "variants": [{ "name": "Approved", "fields": ["id"] }, { "name": "Escalated", "fields": ["id", "team"] }] },
789
+ { "kind": "function", "line": 10, "name": "decide", "params": ["refund"], "exported": false,
790
+ "async": false, "purity": null, "body": "expression", "requires": [], "ensures": [] },
791
+ { "kind": "match", "line": 13, "patterns": ["Approved(id)", "Escalated(id, team)"] },
792
+ { "kind": "pipeline", "line": 20, "name": "routeRefunds", "declared": false, "source": "refunds",
793
+ "stages": [{ "type": "map", "args": ["decide"], "id": null }] }
794
+ ]
795
+ }
796
+ ```
797
+
798
+ | Kind | Fields |
799
+ |---|---|
800
+ | `import` | `from`, `names` |
801
+ | `effect` | `name`, `params` |
802
+ | `type` | `name`, `exported`, `variants` (`name`, `fields`) |
803
+ | `function` | `name`, `params` (as written, including defaults), `exported`, `async`, `purity`, `body` (`"block"` or `"expression"`), `requires`, `ensures` |
804
+ | `pipeline` | `name`, `declared` (a `pipeline` block), `source`, `stages` (`type`, `args`, `id`) |
805
+ | `handle` | `handlers` |
806
+ | `match` | `patterns` |
807
+
808
+ JojoScript has no AST by design: nodes come from the same finders the compiler
809
+ uses, so `parse` never disagrees with what compiles. Invalid syntax is
810
+ reported as a single `jojo:` error line. `parseSource(source)` is exported
811
+ from `@panagos/jojoscript`. See
812
+ [example/src/application/parse-example.jojo](example/src/application/parse-example.jojo).
813
+
814
+ ## Simulating pipelines
815
+
816
+ `jojo simulate` answers "what would this pipeline do with N records?" before
817
+ anything runs. No stage or effect is executed:
818
+
819
+ ```
820
+ $ jojo simulate prices.jojo --input 250000 --pipeline pricedProducts
821
+ SIMULATION pricedProducts
822
+
823
+ INPUT
824
+ 250,000 records
825
+
826
+ OPERATIONS
827
+ 2 filter 250,000 items in
828
+ 3 parallel ≤250,000 items in, 16 workers
829
+ 4 map ≤250,000 items in, ≤250,000 calls to fetchPrice
830
+ 5 checkpoint ≤250,000 items in, saved as "priced"
831
+
832
+ POTENTIAL PROBLEMS
833
+ ⚠ stage 4: up to 250,000 calls to effect "fetchPrice"
834
+ ⚠ stage 4: effect "fetchPrice" runs before checkpoint "priced": items processed before a failure are fetched again on retry (at-least-once)
835
+ ```
836
+
837
+ Stages are numbered as in `jojo explain`, where the source is stage 1.
838
+ Item counts flow through the stages as upper bounds (`≤` once a filtering
839
+ stage such as `filter`, `unique`, or `distinctBy` has run). `take`, `skip`,
840
+ `chunk`, `batch`, and `window` change them exactly; `flatMap` and `groupBy`
841
+ make them unknown; `count`, `first`, `find`, `some`, `every`, and `reduce`
842
+ produce one result. A stage that uses a declared effect or an `effectful fn`
843
+ makes one call per item it receives.
844
+
845
+ Potential problems reported:
846
+
847
+ - 1,000 or more calls to one effect;
848
+ - an effect that runs before a checkpoint, and so may run again for items
849
+ processed before a failure (see
850
+ [Effects and checkpoint/resume](#effects-and-checkpointresume));
851
+ - a pipeline with effects but no checkpoint at all;
852
+ - `sort`, `sortBy`, `groupBy`, or `partition` holding 10,000 or more items in
853
+ memory.
854
+
855
+ The input defaults to 1,000 records. `--format json` prints the simulation as
856
+ data, and `--pipeline <name>` selects one pipeline. `simulatePipelines(source,
857
+ { input })` and `renderSimulationText(simulation)` are exported from
858
+ `@panagos/jojoscript`. See
859
+ [example/src/application/simulate-example.jojo](example/src/application/simulate-example.jojo).
860
+
861
+ ## Asking why
862
+
863
+ `jojo why` turns the static analysis into plain-language answers, numbered
864
+ like `jojo explain` (the source is stage 1):
865
+
866
+ ```
867
+ $ jojo why onboarding.jojo --pipeline verifiedCustomers
868
+ WHY verifiedCustomers
869
+
870
+ Why is this pipeline not deterministic?
871
+ → Stage 3 (mapAsync) uses effect verifyEmail; stage 5 (mapAsync) uses effectful function sendWelcome.
872
+
873
+ Why is this pipeline lazy?
874
+ → Every stage passes items on one at a time, so no stage waits for the complete input.
875
+
876
+ Why is this pipeline resumable?
877
+ → Checkpoint "verified" (stage 4) saves its items once they all complete; a rerun restores them instead of repeating stages 2-3.
878
+
879
+ Why can't stage 3 be moved before stage 2?
880
+ → Stage 3 (mapAsync) uses effect verifyEmail and stage 2 (filter) decides which items reach it: moving it earlier would also run it for items that stage 2 drops.
881
+
882
+ Why does checkpoint "verified" not guarantee replay safety?
883
+ → Effect verifyEmail runs before it (stage 3): if a run fails before "verified" completes, it runs again on retry (at-least-once, not exactly-once).
884
+
885
+ Why can stage 5 repeat after a failure?
886
+ → Stage 5 (mapAsync) uses effectful function sendWelcome after the last checkpoint "verified": a rerun restores the checkpoint and runs it again for every restored item.
887
+ ```
888
+
889
+ Every pipeline gets answers about determinism, laziness, and resumability.
890
+ Additional questions appear when they apply: an effectful stage behind a
891
+ filtering stage (`filter`, `filterAsync`, `unique`, `distinctBy`, `take`,
892
+ `skip`) cannot be moved before it, a `parallel` stage explains what it
893
+ parallelizes, and checkpoints explain which effects can run again on retry.
894
+ Answers only reflect declared effects and `effectful fn`s. `--format json`
895
+ prints `[{ name, answers: [{ question, answer }] }]`, and
896
+ `whyPipelines(source)` / `renderWhyText(why)` are exported from
897
+ `@panagos/jojoscript`. See
898
+ [example/src/application/why-example.jojo](example/src/application/why-example.jojo).
899
+
329
900
  ## Effect Handlers
330
901
 
331
902
  > This is a deliberately simplified effect-handler model, not full
@@ -592,6 +1163,51 @@ declared itself `deterministic`, and the declaring effect's `sideEffecting`/
592
1163
  `replayable` metadata (from `effect name(params)`'s options, or defaults —
593
1164
  `sideEffecting: true`, `replayable: false`).
594
1165
 
1166
+ ### Effect authorization
1167
+
1168
+ Applications can place a capability policy around a `handle` block with
1169
+ `withAuthorization(policy, fn)`. The policy receives the declared effect
1170
+ name, a shallow copy of its arguments, declared effect metadata, and the
1171
+ current execution context. It must explicitly return `true` or
1172
+ `{ allowed: true }` for the handler to run.
1173
+
1174
+ ```js
1175
+ import { withAuthorization } from "@panagos/jojoscript/runtime"
1176
+
1177
+ const result = await withAuthorization(
1178
+ invocation => invocation.name === "readTender"
1179
+ ? true
1180
+ : { allowed: false, reason: "Capability is not granted" },
1181
+ () => withHandlers({
1182
+ readTender: id => ({ id }),
1183
+ approveOffer: offer => approve(offer)
1184
+ }, () => program())
1185
+ )
1186
+ ```
1187
+
1188
+ Authorization is opt-in; programs that do not use `withAuthorization` retain
1189
+ their existing unrestricted effect behavior. When enabled, every active
1190
+ authorization scope must allow each resolved effect invocation before its
1191
+ handler starts. This means an inner `handle` block cannot bypass an outer
1192
+ policy. A missing, malformed, denied, or failed policy decision throws
1193
+ `EffectAuthorizationError`; denial is non-retryable, so `retry()` does not
1194
+ repeat it. An unhandled effect still throws `UnhandledEffectError` before any
1195
+ authorization policy runs.
1196
+
1197
+ Authorization is evaluated before normal handlers, recording wrappers, and
1198
+ replay wrappers. Replay therefore does not bypass a policy, and a checkpoint
1199
+ resume only avoids authorization when it restores a completed checkpoint
1200
+ without invoking an upstream effect at all. Policies do not record arguments
1201
+ or decisions automatically, and this mechanism is not a sandbox: ordinary
1202
+ JavaScript and operations that do not pass through a declared effect are
1203
+ outside its enforcement boundary. Do not derive permission from model or
1204
+ remote-agent output; make policy decisions in trusted application code.
1205
+
1206
+ See
1207
+ [example/src/effects/effect-authorization.jojo](example/src/effects/effect-authorization.jojo)
1208
+ for a procurement workflow that permits reads and supplier lookup while
1209
+ blocking invitations and offer approval before their handlers run.
1210
+
595
1211
  ### Effects and checkpoint/resume
596
1212
 
597
1213
  Consider:
@@ -629,6 +1245,13 @@ actually safe. See
629
1245
  [example/src/effects/checkpoint-resume.jojo](example/src/effects/checkpoint-resume.jojo)
630
1246
  and `test/effects-checkpoint.test.js` for both cases exercised end to end.
631
1247
 
1248
+ File-backed checkpoint records are validated when they are loaded: their
1249
+ pipeline ID and checkpoint ID must match the requested checkpoint, `items`
1250
+ must be an array, and a present `itemCount` must match the saved item count.
1251
+ Malformed records fail clearly rather than resuming with untrusted data. See
1252
+ [example/src/effects/checkpoint-integrity.jojo](example/src/effects/checkpoint-integrity.jojo)
1253
+ for a compiled pipeline test of this behavior.
1254
+
632
1255
  ### Record/replay
633
1256
 
634
1257
  A handler map can be wrapped to record every call (arguments + result) to
@@ -653,6 +1276,44 @@ interface (`record`/`replayNext`), mirroring `CheckpointStore` — an
653
1276
  extension point for a future backend, not a full production-grade
654
1277
  persistence system.
655
1278
 
1279
+ ### Deterministic execution mode
1280
+
1281
+ `now()` and `random()` are built-in capabilities, like `parallel()`. Outside
1282
+ any handler they behave like `Date.now()` and `Math.random()`, and a
1283
+ `handle { TIME: ..., RANDOM: ... }` block can replace them:
1284
+
1285
+ ```jojo
1286
+ import { now, random } from "@panagos/jojoscript/runtime"
1287
+
1288
+ order := { id: Math.round(random() * 1000), createdAt: new Date(now()) }
1289
+ ```
1290
+
1291
+ `deterministic(fn, { seed, startTime })` runs `fn` so that the same inputs
1292
+ always produce the same outputs:
1293
+
1294
+ - `random()` is a seeded generator (`seed` defaults to `1`);
1295
+ - `now()` starts at `startTime` (default `0`) and advances 1ms per read;
1296
+ - `parallel()` stages process one item at a time, in order;
1297
+ - every declared effect must resolve to a handler declared deterministic
1298
+ (`{ handle, deterministic: true }`, or `replayHandlers(...)`); any other
1299
+ handler throws a non-retryable `NondeterministicEffectError` before it runs;
1300
+ - checkpoints record `now()` as their `createdAt`, so reruns write identical
1301
+ checkpoints.
1302
+
1303
+ ```js
1304
+ import { deterministic } from "@panagos/jojoscript/runtime"
1305
+
1306
+ const first = await deterministic(() => generateOrders(3), { seed: 2026 })
1307
+ const second = await deterministic(() => generateOrders(3), { seed: 2026 })
1308
+ // first and second are deep-equal
1309
+ ```
1310
+
1311
+ `jojo run app.jojo --deterministic [--seed <n>]` runs a whole program, top-level
1312
+ code included, in this mode. Determinism covers JojoScript's capabilities:
1313
+ direct calls to `Date.now()`, `Math.random()`, or I/O outside declared effects
1314
+ are not intercepted. See
1315
+ [example/src/application/deterministic-example.jojo](example/src/application/deterministic-example.jojo).
1316
+
656
1317
  ### JavaScript interoperability
657
1318
 
658
1319
  A handler is just a normal JavaScript function — sync or async — and can
@@ -739,6 +1400,38 @@ never through `|>`) and also uses pipeline syntax with overlapping names,
739
1400
  the compiler merges the auto-imported names into the existing import
740
1401
  instead of emitting a second, colliding import declaration.
741
1402
 
1403
+ ### Deterministic serialization
1404
+
1405
+ `toJson(value, { indent })` and `fromJson(text)` give JojoScript values one
1406
+ canonical JSON form:
1407
+
1408
+ ```jojo
1409
+ import { toJson, fromJson } from "@panagos/jojoscript/runtime"
1410
+
1411
+ snapshot := toJson({ name: "John", id: 42, placedAt: new Date(0) })
1412
+ // {"id":42,"name":"John","placedAt":{"$type":"Date","value":"1970-01-01T00:00:00.000Z"}}
1413
+
1414
+ order := fromJson(snapshot) // order.placedAt is a Date again
1415
+ ```
1416
+
1417
+ - Object keys are sorted recursively, so equal values always serialize to
1418
+ identical text, whatever order their properties were created in.
1419
+ - Values plain JSON would lose or change are written as `{ "$type": ... }`
1420
+ objects: `Date`, `Map`, `Set`, `BigInt`, `undefined`, `NaN`, `Infinity`,
1421
+ `-Infinity`, `-0`, and `Error` (name, message, own fields such as a
1422
+ `ContractError`'s `kind`, and `cause`). `fromJson` restores each exactly.
1423
+ - An ordinary object that has its own `$type` key is wrapped, so tags are
1424
+ never ambiguous. Tagged union values are plain objects and serialize as
1425
+ they are. Objects with a `toJSON` method serialize its result.
1426
+ - Functions, symbols, and circular structures throw a `TypeError`.
1427
+
1428
+ `FileCheckpointStore` writes and reads checkpoints in this format, so
1429
+ checkpointed dates, maps, and sets resume as themselves. Checkpoint files
1430
+ written as plain JSON before this change still load. Pipelines and execution
1431
+ plans have their own canonical form: the [`jojo plan`](#machine-readable-execution-plans)
1432
+ JSON. See
1433
+ [example/src/application/serialization-example.jojo](example/src/application/serialization-example.jojo).
1434
+
742
1435
  ### MCP support
743
1436
 
744
1437
  JojoScript includes a small MCP-style JSON-RPC runtime for Node.js. It does
@@ -749,18 +1442,121 @@ JavaScript functions.
749
1442
  import { createMcpServer, runMcpStdioServer } from "jojoscript/runtime"
750
1443
 
751
1444
  const server = createMcpServer({
1445
+ name: "example-server",
1446
+ version: "1.0.0",
752
1447
  tools: {
753
- echo: args => ({ content: [{ type: "text", text: args.value }] })
1448
+ echo: {
1449
+ description: "Returns the supplied value.",
1450
+ inputSchema: {
1451
+ type: "object",
1452
+ properties: {
1453
+ value: { type: "string" }
1454
+ },
1455
+ required: ["value"],
1456
+ additionalProperties: false
1457
+ },
1458
+ handler: args => ({
1459
+ content: [{ type: "text", text: args.value }]
1460
+ })
1461
+ }
754
1462
  }
755
1463
  })
756
1464
 
757
1465
  await runMcpStdioServer(server)
758
1466
  ```
759
1467
 
760
- The server accepts `tools/call` requests and returns JSON-RPC responses on
761
- standard output. It is intentionally minimal: supporting tool registration,
762
- request dispatch, structured errors, and the stdin/stdout transport without any
763
- external NPM packages.
1468
+ The server supports the `initialize` handshake (protocol version
1469
+ `2025-06-18`), `notifications/initialized`, `ping`, `tools/list`, and
1470
+ `tools/call` over newline-delimited JSON-RPC on standard input/output. Clients
1471
+ must initialize before making requests. Tools declare an `inputSchema`; calls
1472
+ that do not conform are rejected before the handler runs. Handlers return a
1473
+ `CallToolResult` object with a `content` array and may return
1474
+ `structuredContent` when their tool declares an `outputSchema`. Thrown handler
1475
+ errors are returned as `CallToolResult` values with `isError: true`.
1476
+
1477
+ It is intentionally minimal: supporting tool registration, schema validation,
1478
+ request dispatch, structured errors, and the stdin/stdout transport without
1479
+ external NPM packages. It remains an MCP-style runtime until its supported
1480
+ protocol surface has been verified against MCP conformance tests and real
1481
+ clients.
1482
+
1483
+ ### A2A client
1484
+
1485
+ JojoScript provides a dependency-free A2A 1.0 JSON-RPC client. It discovers a
1486
+ remote agent through `/.well-known/agent-card.json`, selects its advertised
1487
+ JSON-RPC interface, sends messages, and retrieves/polls task results.
1488
+
1489
+ ```js
1490
+ import { createA2AClient } from "jojoscript/runtime"
1491
+
1492
+ const agent = createA2AClient("https://agent.example")
1493
+ await agent.discover()
1494
+
1495
+ const task = await agent.sendMessage({
1496
+ role: "ROLE_USER",
1497
+ parts: [{ kind: "data", data: { record: { id: "item-1" } } }],
1498
+ metadata: { requestId: "request-1" }
1499
+ })
1500
+ const result = await agent.waitForTask(task, {
1501
+ pollIntervalMs: 500,
1502
+ maxPollIntervalMs: 5000,
1503
+ timeoutMs: 30000
1504
+ })
1505
+ ```
1506
+
1507
+ `sendMessage`, `getTask`, and `cancelTask` map to the A2A `SendMessage`,
1508
+ `GetTask`, and `CancelTask` operations. Messages, task IDs, context IDs,
1509
+ metadata, typed parts, and artifacts are returned unchanged. `waitForTask`
1510
+ accepts either a task ID or an in-progress task, then returns the final task
1511
+ for every terminal state—including failed, rejected, canceled,
1512
+ input-required, and authentication-required tasks—so callers must inspect the
1513
+ state before treating it as successful.
1514
+
1515
+ Waiting uses a five-minute default timeout, configurable polling and bounded
1516
+ backoff (`pollIntervalMs`, `maxPollIntervalMs`, and `backoffMultiplier`), and
1517
+ an optional `AbortSignal`. It rejects with an `A2AError` whose `code` clearly
1518
+ identifies timeouts, cancellation, network/HTTP failures, invalid discovery or
1519
+ JSON-RPC responses, and remote-agent errors. Pass `bearerToken`, custom
1520
+ headers, or an alternate `fetch` function in the client options. Discovery
1521
+ validates advertised JSON-RPC endpoints and only permits credential-free HTTP
1522
+ or HTTPS URLs.
1523
+
1524
+ JojoScript intentionally provides a lightweight A2A client rather than an
1525
+ A2A SDK or agent server. Its protocol behavior is covered by local fixtures;
1526
+ real-agent interoperability remains a separate integration concern.
1527
+
1528
+ ### Resumable MCP-to-A2A enrichment
1529
+
1530
+ [example/src/application/mcp-a2a-resumable-enrichment.jojo](example/src/application/mcp-a2a-resumable-enrichment.jojo)
1531
+ combines the MCP server, A2A client, asynchronous pipelines, and checkpoints
1532
+ in one tool. `enrich_records` accepts a batch of `{ id, title }` records,
1533
+ delegates enrichment to an A2A agent, checkpoints a completed batch, and
1534
+ returns both text content and structured records to the MCP client.
1535
+
1536
+ ```js
1537
+ import {
1538
+ FileCheckpointStore,
1539
+ runMcpStdioServer
1540
+ } from "jojoscript/runtime"
1541
+ import { createEnrichmentServer } from "./application/mcp-a2a-resumable-enrichment.js"
1542
+
1543
+ const server = createEnrichmentServer({
1544
+ agentUrl: process.env.A2A_AGENT_URL,
1545
+ checkpointStore: new FileCheckpointStore(".jojo-checkpoints")
1546
+ })
1547
+
1548
+ await runMcpStdioServer(server)
1549
+ ```
1550
+
1551
+ Use a stable `jobId` for retries of the same batch. The checkpoint is saved
1552
+ only after every record has been enriched, so retrying a completed batch uses
1553
+ the saved records without calling the A2A agent again. This is **at-least-once
1554
+ delegation**, not exactly-once: if an A2A agent completes a record and the
1555
+ process fails before the enclosing batch checkpoint commits, the retry invokes
1556
+ that agent again. Remote agents should therefore use idempotency keys derived
1557
+ from the record and job ID when duplicate work is unsafe. The matching
1558
+ integration test demonstrates both the duplicate-before-commit case and the
1559
+ no-repeat-after-commit case.
764
1560
 
765
1561
  ## Example
766
1562
 
@@ -777,6 +1573,10 @@ running the same program with multiple handlers, deterministic testing,
777
1573
  `parallel()` as an effect, effect visibility in the execution plan, and the
778
1574
  checkpoint/resume interaction.
779
1575
 
1576
+ [example/src/application/a2a-enrichment.jojo](example/src/application/a2a-enrichment.jojo)
1577
+ demonstrates enriching a record through A2A discovery, message dispatch, and
1578
+ task polling against a local test agent.
1579
+
780
1580
  ```bash
781
1581
  cd example
782
1582
  npm install
@@ -804,10 +1604,36 @@ const total = users
804
1604
  boundaries because they need the complete input. See
805
1605
  [ITERATORS.md](ITERATORS.md) for the complete proposal and memory model.
806
1606
 
1607
+ ## Formatting
1608
+
1609
+ `jojo format <file.jojo|directory>` reformats files in place and prints each
1610
+ file it changed. `--check` changes nothing: it lists the files that need
1611
+ formatting and exits with code 1 if there are any, which suits CI and
1612
+ generate-format-check loops.
1613
+
1614
+ The formatter is line-based, like the compiler: it normalizes indentation and
1615
+ whitespace but never rewrites expressions.
1616
+
1617
+ - A line that opens brackets indents their contents one level, however many
1618
+ it opens (`posts.reduce((groups, post) => {`).
1619
+ - `|>` stages, `requires` / `ensures` clauses, `| Variant` lines, ternary
1620
+ `?` / `:` lines, leading `.method()` chains, and the line after a trailing
1621
+ `:=` or `=>` are indented one extra level. Stages inside a
1622
+ `pipeline name { ... }` block line up with its `source` line.
1623
+ - Trailing whitespace is removed, Windows line endings become `\n`, runs of
1624
+ blank lines collapse to one, and the file ends with a single newline.
1625
+ - Block comments and template literal text are left untouched.
1626
+
1627
+ `formatSource(source, { indent })` is exported from `@panagos/jojoscript`, and
1628
+ the VS Code extension's `Format Document` uses the same formatter. Every file
1629
+ under `example/src` is kept formatted by a test.
1630
+
807
1631
  ## Editor support
808
1632
 
809
1633
  See [editors/vscode](editors/vscode) for a VS Code extension providing syntax
810
- highlighting and a `Format Document` provider for `.jojo` files. It is not
1634
+ highlighting for all JojoScript syntax (pipelines, stage IDs, named
1635
+ pipelines, `match` patterns, tagged unions, effects, purity, and contracts)
1636
+ and a `Format Document` provider for `.jojo` files. It is not
811
1637
  published to the marketplace; package it with `vsce package` and install the
812
1638
  resulting `.vsix` locally.
813
1639
 
@@ -815,8 +1641,6 @@ resulting `.vsix` locally.
815
1641
 
816
1642
  - **No source maps.** Stack traces from compiled `dist/*.js` point at the
817
1643
  generated JavaScript, not the original `.jojo` source.
818
- - **No `jojo fmt` CLI command.** Formatting is only available through the
819
- VS Code extension's `Format Document` command, not the `jojo` CLI.
820
1644
  - **No regex-literal awareness in the lexer.** A regex containing `{`, `}`,
821
1645
  `(`, or `)` can confuse brace/paren-depth-sensitive transforms (pipelines,
822
1646
  `match`, destructuring, type annotations). Prefer `new RegExp(...)` in code
@@ -845,8 +1669,7 @@ resulting `.vsix` locally.
845
1669
  actually consumed.** Constructing a lazy pipeline inside a `handle` block
846
1670
  without consuming it there (no `toArray`/`toArrayAsync`/`reduce`/...) means
847
1671
  effects pulled later, outside the block, won't see its handlers.
848
- - **Only `PARALLEL` is a built-in effect today.** The other capabilities
849
- mentioned as a long-term direction (`IO`, `HTTP`, `FILE`, `DATABASE`,
850
- `SLEEP`, `TIME`, `RANDOM`, `CHECKPOINT`) are not implemented — user-defined
1672
+ - **`PARALLEL`, `TIME`, and `RANDOM` are the only built-in effects.** The
1673
+ other capabilities mentioned as a long-term direction (`IO`, `HTTP`, `FILE`,
1674
+ `DATABASE`, `SLEEP`, `CHECKPOINT`) are not implemented; user-defined
851
1675
  `effect` declarations cover the same need today.
852
-