@orkestrel/scaffold 0.0.67 → 0.0.68

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.
Files changed (74) hide show
  1. package/dist/bin/main.js +67 -44
  2. package/dist/bin/main.js.map +1 -1
  3. package/dist/host/agents/templates/brief.md +9 -0
  4. package/dist/host/claude/agents/orkestrel.md +4 -4
  5. package/dist/host/claude/rules/names.md +15 -0
  6. package/dist/host/claude/rules/tests.md +33 -4
  7. package/dist/host/claude/rules/workspace.md +14 -2
  8. package/dist/host/dotfiles/prettierignore +3 -0
  9. package/dist/host/guides/README.md +65 -0
  10. package/dist/host/guides/abort.md +169 -0
  11. package/dist/host/guides/agent.md +1509 -0
  12. package/dist/host/guides/brief.md +1266 -0
  13. package/dist/host/guides/browser.md +2200 -0
  14. package/dist/host/guides/budget.md +196 -0
  15. package/dist/host/guides/codec.md +519 -0
  16. package/dist/host/guides/console.md +785 -0
  17. package/dist/host/guides/contract.md +1193 -0
  18. package/dist/host/guides/csv.md +541 -0
  19. package/dist/host/guides/database.md +2518 -0
  20. package/dist/host/guides/emitter.md +233 -0
  21. package/dist/host/guides/form.md +1791 -0
  22. package/dist/host/guides/html.md +717 -0
  23. package/dist/host/guides/indexeddb.md +505 -0
  24. package/dist/host/guides/interpret.md +1029 -0
  25. package/dist/host/guides/lsp.md +515 -0
  26. package/dist/host/guides/markdown.md +964 -0
  27. package/dist/host/guides/mcp.md +5554 -0
  28. package/dist/host/guides/middleware.md +927 -0
  29. package/dist/host/guides/msg.md +440 -0
  30. package/dist/host/guides/ndjson.md +120 -0
  31. package/dist/host/guides/ollama.md +380 -0
  32. package/dist/host/guides/pool.md +280 -0
  33. package/dist/host/guides/probe.md +1210 -0
  34. package/dist/host/guides/process.md +1620 -0
  35. package/dist/host/guides/program.md +1110 -0
  36. package/dist/host/guides/qualifier.md +854 -0
  37. package/dist/host/guides/queue.md +370 -0
  38. package/dist/host/guides/rater.md +330 -0
  39. package/dist/host/guides/reason.md +1122 -0
  40. package/dist/host/guides/relation.md +373 -0
  41. package/dist/host/guides/router.md +753 -0
  42. package/dist/host/guides/scaffold.md +192 -31
  43. package/dist/host/guides/sea.md +383 -0
  44. package/dist/host/guides/server.md +752 -0
  45. package/dist/host/guides/sqlite.md +330 -0
  46. package/dist/host/guides/sse.md +187 -0
  47. package/dist/host/guides/supervisor.md +4890 -0
  48. package/dist/host/guides/table.md +1556 -0
  49. package/dist/host/guides/template.md +280 -0
  50. package/dist/host/guides/terminal.md +1145 -0
  51. package/dist/host/guides/test.md +2969 -0
  52. package/dist/host/guides/timeout.md +252 -0
  53. package/dist/host/guides/tool.md +311 -0
  54. package/dist/host/guides/toolbox.md +1038 -0
  55. package/dist/host/guides/websocket.md +282 -0
  56. package/dist/host/guides/worker.md +615 -0
  57. package/dist/host/guides/workflow.md +1507 -0
  58. package/dist/host/guides/workspace.md +595 -0
  59. package/dist/host/manifest.json +1218 -10
  60. package/dist/host/tests/policy.test.ts +279 -2
  61. package/dist/host/tests/setupPolicy.ts +437 -6
  62. package/dist/src/core/index.cjs +38 -16
  63. package/dist/src/core/index.cjs.map +1 -1
  64. package/dist/src/core/index.d.cts +33 -9
  65. package/dist/src/core/index.d.ts +33 -9
  66. package/dist/src/core/index.js +37 -17
  67. package/dist/src/core/index.js.map +1 -1
  68. package/dist/src/server/index.cjs +1750 -1567
  69. package/dist/src/server/index.cjs.map +1 -1
  70. package/dist/src/server/index.d.cts +106 -24
  71. package/dist/src/server/index.d.ts +106 -24
  72. package/dist/src/server/index.js +1751 -1570
  73. package/dist/src/server/index.js.map +1 -1
  74. package/package.json +3 -3
@@ -0,0 +1,233 @@
1
+ # Emitter
2
+
3
+ > The foundational observable primitive: a typed, synchronous event emitter that a
4
+ > stateful entity owns as a `#emitter` field and exposes through a `readonly emitter`
5
+ > property, fanning each event out to its listeners in the current tick and isolating a
6
+ > throwing listener from its siblings.
7
+
8
+ A queue, a database table, an agent — anything with lifecycle transitions or observable
9
+ operations takes one, and its consumers subscribe through `entity.emitter.on(...)`.
10
+ Composition, never inheritance: an entity threads its event map and an optional error
11
+ handler into the emitter and otherwise forgets it exists. It is deliberately small. There
12
+ is no scheduler, no listener cap, no `max`-listeners warning, and no `console` output;
13
+ `emit` fires listeners in registration order, and `on` returns `void`, not an
14
+ `Unsubscribe`. A throwing listener routes to the optional `error` handler instead of being
15
+ rethrown, and with no handler its throw is swallowed silently. Source:
16
+ [`src/core`](../src/core). Surfaced through the `@src/core` barrel.
17
+
18
+ ## Surface
19
+
20
+ Create a standalone emitter, subscribe, and fire events synchronously:
21
+
22
+ ```ts
23
+ import { createEmitter } from '@orkestrel/emitter'
24
+
25
+ // The event map names each event and the argument tuple its listeners receive.
26
+ // Declare it as a `type` alias, never as `interface … extends EventMap`: a
27
+ // type-literal satisfies the `EventMap` constraint structurally without
28
+ // inheriting its index signature, so each event keeps a precise tuple and
29
+ // `on`-hook literals stay exactly typed.
30
+ type ClockEventMap = {
31
+ tick: readonly [at: number]
32
+ done: readonly []
33
+ }
34
+
35
+ const clock = createEmitter<ClockEventMap>({
36
+ on: { done: () => stop() }, // initial listeners wired at construction
37
+ error: (error, event) => logger.warn(`listener for "${event}" threw`, error),
38
+ })
39
+
40
+ clock.on('tick', (at) => render(at)) // `at` is typed `number` from the map
41
+ clock.emit('tick', Date.now()) // synchronous — every `tick` listener runs now
42
+ clock.destroy() // teardown — drops every listener, flips `destroyed`
43
+ ```
44
+
45
+ The reserved `on` option wires initial listeners at construction; the optional `error` handler receives any listener's throw as `(error, event)` so `emit` never has to rethrow. Event names are single present-tense verbs or nouns.
46
+
47
+ ### Factories
48
+
49
+ | API | Kind | Summary |
50
+ | --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | `createEmitter` | function | Creates a typed synchronous event emitter and returns it as an `EmitterInterface<TMap>`, wiring the initial `on` hooks and the `error` handler its options carry. |
52
+
53
+ ### Helpers
54
+
55
+ | API | Kind | Summary |
56
+ | ------------- | -------- | ---------------------------------------------------------------------------- |
57
+ | `extractKeys` | function | Extracts the own enumerable keys of a mapped object, typed as its key union. |
58
+
59
+ ### Classes
60
+
61
+ | API | Kind | Summary |
62
+ | --------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `Emitter` | class | Implements `EmitterInterface` over one listener `Set` per event, so every public method is precisely typed with no assertion. A stateful entity owns one as a `#emitter` field and exposes it through `readonly emitter`; it never inherits from it. |
64
+
65
+ ### Types
66
+
67
+ A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional member and `plus` introducing its call-signature members, and a type alias's own type literal with a union's arms escaped as `\|`.
68
+
69
+ | Type | Kind | Shape | Summary |
70
+ | --------------------- | --------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
71
+ | `EventMap` | type | `Record<string, readonly unknown[]>` | Maps each event name to the argument tuple its listeners receive. |
72
+ | `EmitterHandler` | type | `(...args: TArgs) => void` | Represents a listener for one event's argument tuple. |
73
+ | `EmitterErrorHandler` | type | `(error: unknown, event: string) => void` | Represents the emitter's own listener-error handler — the `error` option, invoked when a listener throws during `emit`, with the caught error and the stringified event name. |
74
+ | `EmitterHooks` | type | `{ readonly [K in keyof TMap]?: EmitterHandler<TMap[K]> }` | Declares the initial event listeners for an emitter — the reserved `on` option: a partial map of event name to its handler, wired at construction. |
75
+ | `EmitterOptions` | interface | `{ on?, error? }` | Configures `createEmitter` and the `Emitter` constructor. |
76
+ | `EmitterInterface` | interface | `{ destroyed } plus on, once, off, emit, count, clear, destroy` | Represents the contract a consumer of an emitter holds: the `destroyed` reading, the `on` / `once` / `off` registration trio, the synchronous `emit`, and the `count` / `clear` / `destroy` set that reports on and releases listeners. |
77
+
78
+ The `destroyed` boolean is a `readonly` data member of `EmitterInterface` (a preceding Surface row) — its call-signature methods are documented under [Methods](#methods).
79
+
80
+ ## Methods
81
+
82
+ The public methods of `EmitterInterface` — every call-signature member listed (its `readonly` data member `destroyed` stays a Surface row). `Emitter` implements the interface exactly, so this doubles as the class's instance-method surface.
83
+
84
+ #### `EmitterInterface`
85
+
86
+ `on` / `once` / `off` register and unregister listeners; `emit` fires them synchronously; `count` / `clear` are the batch pair (all events, or one); `destroy` is the teardown.
87
+
88
+ | Method | Returns | Summary |
89
+ | --------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
90
+ | `on` | `void` | Registers a listener for an event. Does nothing after `destroy()`. |
91
+ | `once` | `void` | Registers a listener that removes itself after its first call. Does nothing after `destroy()`. |
92
+ | `off` | `void` | Removes a listener registered for an event by its original handler, including one registered through `once`. |
93
+ | `emit` | `void` | Invokes an event's listeners synchronously, in registration order, isolating a throw. Does nothing after `destroy()`. |
94
+ | `count` | `number` | Returns the live listener count, for one event or across every event. |
95
+ | `clear` | `void` | Drops registered listeners, for one event or every event, leaving the emitter usable and `destroyed` unchanged. |
96
+ | `destroy` | `void` | Tears down the emitter: drops every listener and sets `destroyed` to `true`. Idempotent. |
97
+
98
+ ## Contract
99
+
100
+ These invariants hold across `src/core` ↔ `emitter.md`:
101
+
102
+ 1. **DOC ↔ SOURCE bijection.** Every `function` / `class` / `interface` / `type` row in the `## Surface` tables is a real export of the emitter source, and every export appears as a Surface row — exhaustive, both directions.
103
+ 2. **Synchronous, ordered.** `emit` invokes listeners in registration order, in the current tick — no microtask, no scheduler. A listener registered during an `emit` is not invoked for that same `emit` (the listener set is snapshotted before the loop).
104
+ 3. **Listener isolation routes errors.** A throwing listener never stops its siblings: every listener runs, and a throw is routed to the emitter's OWN `error` handler (`EmitterOptions.error`, surfaced as `(error, event)`) — `emit` NEVER rethrows. EVERY throwing listener surfaces (not only the first); with no `error` handler, a throw is swallowed silently. The `error` handler runs in its own try/catch, so a throwing handler is swallowed too (anti-recursion).
105
+ 4. **Composition, not inheritance.** Entities own an `Emitter` as `#emitter` and expose `readonly emitter`; they never extend it. There is no delegation boilerplate, no `Omit` hacks.
106
+ 5. **Destroyed → no-op.** After `destroy()`, `on` / `once` / `emit` do nothing and `destroyed` is `true`; `destroy()` is idempotent. `clear()` resets listeners without destroying the emitter (`destroyed` stays `false`).
107
+ 6. **`once` / `off` correlate.** A `once` listener is wrapped so it removes itself after firing; `off` called with the original handler removes that wrapper, so callers never juggle the wrapper themselves.
108
+ 7. **DOC ↔ SOURCE method bijection.** The `## Methods` table lists exactly `EmitterInterface`'s public methods — exhaustive, both directions — and `Emitter` exposes the same public methods, no more.
109
+
110
+ Deliberately out of scope, to keep the surface small: a listener-count cap or `max` warning, and any `console` output. Asynchronous emit, wildcard events, and an `Unsubscribe` return from `on` are additive and would leave the preceding surface unchanged.
111
+
112
+ ## Patterns
113
+
114
+ ### Standalone emitter
115
+
116
+ Create an emitter with no owning entity, subscribe, and fire its events:
117
+
118
+ ```ts
119
+ import { createEmitter } from '@orkestrel/emitter'
120
+
121
+ type DownloadEventMap = {
122
+ chunk: readonly [bytes: number]
123
+ done: readonly []
124
+ }
125
+
126
+ const emitter = createEmitter<DownloadEventMap>()
127
+ emitter.on('chunk', (bytes) => accumulate(bytes))
128
+ emitter.once('done', () => finish())
129
+ emitter.emit('chunk', 1024)
130
+ emitter.emit('done')
131
+ ```
132
+
133
+ ### Own an emitter
134
+
135
+ This is the dominant use, and the shape every observable entity in the codebase follows. The entity owns an `Emitter` as `#emitter`, exposes it through `readonly emitter`, and threads the caller's `on` (and optional `error`) options straight into the constructor — so the owner's options surface mirrors the emitter's without re-deriving it. It emits internally and tears the emitter down last in its own `destroy()`. No inheritance, no delegation boilerplate.
136
+
137
+ ```ts
138
+ import {
139
+ Emitter,
140
+ type EmitterErrorHandler,
141
+ type EmitterHooks,
142
+ type EmitterInterface,
143
+ } from '@orkestrel/emitter'
144
+
145
+ type CounterEventMap = {
146
+ tick: readonly [count: number]
147
+ done: readonly []
148
+ }
149
+
150
+ interface CounterOptions {
151
+ readonly on?: EmitterHooks<CounterEventMap> // initial listeners
152
+ readonly error?: EmitterErrorHandler // routes a listener throw, never rethrows
153
+ }
154
+
155
+ interface CounterInterface {
156
+ readonly emitter: EmitterInterface<CounterEventMap>
157
+ increment(): void
158
+ destroy(): void
159
+ }
160
+
161
+ class Counter implements CounterInterface {
162
+ #count = 0
163
+ #emitter: Emitter<CounterEventMap>
164
+
165
+ constructor(options?: CounterOptions) {
166
+ // Forward both options into the owned emitter — the entity adds no logic of its own.
167
+ this.#emitter = new Emitter({ on: options?.on, error: options?.error })
168
+ }
169
+
170
+ get emitter(): EmitterInterface<CounterEventMap> {
171
+ return this.#emitter
172
+ }
173
+
174
+ increment(): void {
175
+ this.#count += 1
176
+ this.#emitter.emit('tick', this.#count) // synchronous fan-out to subscribers
177
+ }
178
+
179
+ destroy(): void {
180
+ this.#emitter.emit('done') // final event, while listeners are still attached…
181
+ this.#emitter.destroy() // …then release them last, on teardown
182
+ }
183
+ }
184
+
185
+ const counter = new Counter({ on: { done: () => cleanup() } })
186
+ counter.emitter.on('tick', (count) => render(count))
187
+ ```
188
+
189
+ ### Manage listeners
190
+
191
+ `off` removes a specific listener, `count` reports how many are live (per-event or total), and `clear` drops listeners (per-event or all) without destroying the emitter:
192
+
193
+ ```ts
194
+ import { createEmitter } from '@orkestrel/emitter'
195
+
196
+ type FeedEventMap = {
197
+ post: readonly [id: string]
198
+ }
199
+
200
+ const feed = createEmitter<FeedEventMap>()
201
+ const onPost = (id: string) => log(id)
202
+
203
+ feed.on('post', onPost)
204
+ feed.count('post') // 1
205
+ feed.count() // 1 — total across all events
206
+
207
+ feed.off('post', onPost)
208
+ feed.count('post') // 0
209
+
210
+ feed.on('post', onPost)
211
+ feed.clear('post') // drop only `post` listeners
212
+ feed.clear() // drop everything; `feed.destroyed` stays false
213
+ ```
214
+
215
+ ### Practices
216
+
217
+ - **Own, never inherit** — store an `Emitter` as `#emitter`, expose `readonly emitter`. No subclassing, no delegation boilerplate.
218
+ - **Empty tuples for pure signals** — an event with no payload is `readonly []`; emit it with no extra args.
219
+ - **Wire initial listeners through `on`** — the reserved options key; the constructor registers them up front.
220
+ - **Destroy last** — call `this.#emitter.destroy()` at the end of the entity's own `destroy()`, after any final events.
221
+ - **Route listener errors to the `error` handler** — `emit` never rethrows; supply `EmitterOptions.error` to receive a listener's throw (as `(error, event)`), or it is swallowed silently.
222
+
223
+ ## Tests
224
+
225
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/core` bijection (value + type exports), the `EmitterInterface` ↔ `Emitter` method bijection, and the equality gate: every `Summary` cell against its declaration's description paragraph, the titled `Standalone emitter` fence against the `@example` block of that title (pinned so the titled pair cannot be retired silently), and the README pitch against this guide's tagline. It also runs the Manage listeners fence and asserts the values its comments claim.
226
+ - [`tests/src/core/Emitter.test.ts`](../tests/src/core/Emitter.test.ts) — `on` / `emit` (typed args, registration order), `once` (fires once, auto-removes), `off` (by original handler, including a `once` wrapper), `count` / `clear` (total and per-event), `destroy` (clears, flips `destroyed`, then no-ops), initial `on` hooks, listener isolation (a throwing listener does not stop siblings; the throw routes to the `error` handler, never rethrown; every throwing listener surfaces; a throwing `error` handler is swallowed), and empty-tuple signals.
227
+ - [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — `createEmitter` returns a working `EmitterInterface` and honors initial `on` hooks.
228
+ - [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — `extractKeys` returns the typed own-enumerable-key union, including the empty-object case and an object whose prototype carries an enumerable key.
229
+
230
+ ## See also
231
+
232
+ - [`AGENTS.md`](../AGENTS.md) — the repository's coding and orchestration authority.
233
+ - [`README.md`](README.md) — the guides index.