opencode-effect-enforcer 0.2.5 → 0.2.6

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 (49) hide show
  1. package/README.md +42 -140
  2. package/docs/effect-4.0.0-rc.116-changelog.md +2654 -0
  3. package/docs/effect-4.0.0-rc.116.md +102 -0
  4. package/guidance/effect-first-development.md +23 -14
  5. package/guidance/progressive-disclosure-guidance.md +3 -3
  6. package/package.json +2 -2
  7. package/patterns/avoid-direct-tag-checks.md +1 -1
  8. package/patterns/avoid-process-env.md +4 -4
  9. package/patterns/context-tag-extends.md +4 -4
  10. package/patterns/prefer-redacted-config.md +10 -10
  11. package/patterns/require-effect-concurrency.md +1 -1
  12. package/skills/effect-ai-chat/SKILL.md +2 -2
  13. package/skills/effect-ai-language-model/SKILL.md +36 -4
  14. package/skills/effect-ai-prompt/SKILL.md +1 -1
  15. package/skills/effect-ai-provider/SKILL.md +23 -12
  16. package/skills/effect-ai-tool/SKILL.md +13 -0
  17. package/skills/effect-atom-rpc/SKILL.md +7 -1
  18. package/skills/effect-atom-state/SKILL.md +8 -2
  19. package/skills/effect-cache/SKILL.md +10 -1
  20. package/skills/effect-cli/SKILL.md +105 -94
  21. package/skills/effect-command-executor/SKILL.md +7 -1
  22. package/skills/effect-config/SKILL.md +67 -44
  23. package/skills/effect-domain-modeling/SKILL.md +3 -3
  24. package/skills/effect-error-handling/SKILL.md +2 -2
  25. package/skills/effect-fiber/SKILL.md +2 -2
  26. package/skills/effect-filesystem/SKILL.md +34 -4
  27. package/skills/effect-http-api/SKILL.md +17 -2
  28. package/skills/effect-http-client/SKILL.md +11 -2
  29. package/skills/effect-http-server/SKILL.md +28 -11
  30. package/skills/effect-layer-design/SKILL.md +6 -2
  31. package/skills/effect-mcp-server/SKILL.md +21 -4
  32. package/skills/effect-observability/SKILL.md +2 -2
  33. package/skills/effect-optics/SKILL.md +1 -1
  34. package/skills/effect-parallelization/SKILL.md +2 -2
  35. package/skills/effect-pattern-matching/SKILL.md +1 -1
  36. package/skills/effect-platform-abstraction/SKILL.md +3 -3
  37. package/skills/effect-rpc-api/SKILL.md +3 -3
  38. package/skills/effect-rpc-client/SKILL.md +14 -13
  39. package/skills/effect-rpc-cluster/SKILL.md +15 -12
  40. package/skills/effect-rpc-server/SKILL.md +11 -13
  41. package/skills/effect-scheduling/SKILL.md +7 -0
  42. package/skills/effect-schema-composition/SKILL.md +12 -4
  43. package/skills/effect-schema-v4/SKILL.md +75 -20
  44. package/skills/effect-scope/SKILL.md +4 -4
  45. package/skills/effect-socket/SKILL.md +161 -658
  46. package/skills/effect-sql/SKILL.md +50 -12
  47. package/skills/effect-stream/SKILL.md +19 -24
  48. package/skills/effect-testing/SKILL.md +35 -22
  49. package/skills/effect-workflow/SKILL.md +13 -2
@@ -7,7 +7,7 @@ description: Implement reactive state management with Effect Atom for React appl
7
7
 
8
8
  Effect Atom is a reactive state management library for Effect that seamlessly integrates with React.
9
9
 
10
- At rc.112, `@effect/atom-react` supports React `>=19.0.0 <20.0.0` (the peer range
10
+ `@effect/atom-react` supports React `>=19.0.0 <20.0.0` (the peer range
11
11
  was relaxed). This does not add React 18 support. Keep the adapter aligned with
12
12
  the Effect release; core atoms still live in `effect/unstable/reactivity`, and
13
13
  React bindings live in `@effect/atom-react` (`packages/atom/react` upstream).
@@ -27,6 +27,12 @@ Reference this for:
27
27
 
28
28
  ### Atoms as References
29
29
 
30
+ `Reactivity.Reactivity` is the branded service interface. Custom implementations
31
+ include its exported `[TypeId]: TypeId`; prefer the supplied constructor/layer.
32
+ Registry dehydration skips unencodable values while preserving other atoms.
33
+ `Atom.withFallback` forwards writes to the primary atom. Keep encoding contracts
34
+ explicit for state that must survive SSR dehydration.
35
+
30
36
  Atoms work **by reference** - they are stable containers for reactive state:
31
37
 
32
38
  ```typescript
@@ -301,7 +307,7 @@ export const notifications = Atom.make(
301
307
  Stream.fromEventListener(window, 'notification').pipe(
302
308
  Stream.map(parseNotification),
303
309
  Stream.filter(isValid),
304
- Stream.scan([], (acc, n) => [...acc, n].slice(-10))
310
+ Stream.scan(() => [], (acc, n) => [...acc, n].slice(-10))
305
311
  )
306
312
  );
307
313
  ```
@@ -7,6 +7,15 @@ You are an Effect TypeScript expert specializing in in-memory caching with `Cach
7
7
 
8
8
  ## Effect Source Reference
9
9
 
10
+ `Effect.cachedWithTTL` accepts either a fixed Duration input or an Exit-dependent
11
+ TTL function, so success and failure retention can differ. Allocate the cache
12
+ once in its owning layer. A zero TTL is explicit, not omission.
13
+ For `LayerMap.Service({ preload: true, ... })`, preserve acquisition errors on
14
+ the yielded service and `get` / `contextEffect` / `contextEffectOption`: an entry
15
+ can fail when reacquired after successful preloading. Invalidating an active
16
+ RcMap/LayerMap entry releases it after its last borrower closes; a replacement
17
+ entry remains independently owned.
18
+
10
19
  The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
11
20
  Browse and read files there directly to look up APIs, types, and implementations.
12
21
 
@@ -363,7 +372,7 @@ The full method surface (`get`, `getOption`, `getSuccess`, `set`, `has`, `invali
363
372
 
364
373
  ## 9. Services in Lookups (requireServicesAt)
365
374
 
366
- ### Retain an existing keyed resource (rc.112)
375
+ ### Retain an existing keyed resource
367
376
 
368
377
  `RcMap.getOption(map, key)` atomically retains a cached entry for the caller's
369
378
  `Scope` before awaiting it. It returns `Option.none()` if the entry is missing or
@@ -7,6 +7,17 @@ description: Build type-safe CLI applications using Effect CLI module for argume
7
7
 
8
8
  Build type-safe command-line applications with typed arguments, flags, subcommands, and dependency injection.
9
9
 
10
+ Scalar/control constructors are PascalCase; combinators and factories
11
+ such as `Command.make`, `Prompt.succeed`, and `Flag.withDefault` retain their names.
12
+ `Primitive.Choice` differs from `Param` / `Flag` / `Argument.Literals`.
13
+ Sentinels are `Never`; numeric constructors are `Int` / `Finite`, while prompts
14
+ use `Prompt.Int` / `Prompt.Number`. Global flags use `GlobalFlag.Action` / `Setting`.
15
+ Public primitive/completion tags are `Int`, `Finite`, and (for primitives) `Never`.
16
+
17
+ `Prompt.Select` / `MultiSelect` may omit `message`; `AutoComplete` requires it.
18
+ `KeyValuePair` preserves `=` inside values. YAML config parsing rejects plain
19
+ scalars containing colon-whitespace or a trailing colon: quote those values.
20
+
10
21
  ## Import Pattern
11
22
 
12
23
  ```typescript
@@ -21,33 +32,33 @@ import { NodeRuntime, NodeServices } from '@effect/platform-node';
21
32
 
22
33
  ## Positional Arguments (Argument)
23
34
 
24
- Positional arguments are parsed in order. `Argument.boolean` intentionally does not exist — use `Flag.boolean` or `Argument.choice("name", ["true", "false"])` instead.
35
+ Positional arguments are parsed in order. Use `Flag.Boolean` for toggles or `Argument.Literals("name", ["true", "false"])` for positional boolean spellings.
25
36
 
26
37
  ### Constructors
27
38
 
28
39
  ```typescript
29
40
  import { Argument } from 'effect/unstable/cli';
30
41
 
31
- Argument.string('name'); // string
32
- Argument.integer('count'); // number (integer)
33
- Argument.float('ratio'); // number (float)
34
- Argument.date('deadline'); // Date
35
- Argument.file('input'); // file path (string)
36
- Argument.file('input', { mustExist: true }); // file path that must exist
37
- Argument.directory('dir'); // directory path (string)
38
- Argument.directory('dir', { mustExist: true }); // directory that must exist
39
- Argument.path('target'); // any path (string)
40
- Argument.choice('env', ['dev', 'staging', 'prod']); // constrained string union
41
- Argument.choiceWithValue('level', [
42
+ Argument.String('name'); // string
43
+ Argument.Int('count'); // number (integer)
44
+ Argument.Finite('ratio'); // finite number
45
+ Argument.Date('deadline'); // Date
46
+ Argument.File('input'); // file path (string)
47
+ Argument.File('input', { mustExist: true }); // file path that must exist
48
+ Argument.Directory('dir'); // directory path (string)
49
+ Argument.Directory('dir', { mustExist: true }); // directory that must exist
50
+ Argument.Path('target'); // any path (string)
51
+ Argument.Literals('env', ['dev', 'staging', 'prod']); // constrained string union
52
+ Argument.ChoiceWithValue('level', [
42
53
  // choice with mapped values
43
54
  ['debug', 0],
44
55
  ['info', 1],
45
56
  ['error', 3]
46
57
  ]);
47
- Argument.redacted('secret'); // Redacted<string>
48
- Argument.fileText('config'); // reads file content as string
49
- Argument.fileParse('config'); // reads and parses file (auto-detects format)
50
- Argument.fileSchema('config', MySchema); // reads and validates file via Schema
58
+ Argument.Redacted('secret'); // Redacted<string>
59
+ Argument.FileText('config'); // reads file content as string
60
+ Argument.FileParse('config'); // reads and parses file (auto-detects format)
61
+ Argument.FileSchema('config', MySchema); // reads and validates file via Schema
51
62
  ```
52
63
 
53
64
  ### Combinators
@@ -56,49 +67,49 @@ Argument.fileSchema('config', MySchema); // reads and validates file via Schema
56
67
  import { Argument } from 'effect/unstable/cli';
57
68
 
58
69
  // Description for help text
59
- Argument.string('file').pipe(Argument.withDescription('Input file'));
70
+ Argument.String('file').pipe(Argument.withDescription('Input file'));
60
71
 
61
72
  // Default value
62
- Argument.integer('port').pipe(Argument.withDefault(8080));
73
+ Argument.Int('port').pipe(Argument.withDefault(8080));
63
74
 
64
75
  // Optional (returns Option<T>)
65
- Argument.string('config').pipe(Argument.optional);
76
+ Argument.String('config').pipe(Argument.optional);
66
77
 
67
78
  // Variadic (returns ReadonlyArray<T>)
68
- Argument.string('files').pipe(Argument.variadic);
69
- Argument.string('files').pipe(Argument.variadic({ min: 1 }));
70
- Argument.string('files').pipe(Argument.variadic({ min: 1, max: 5 }));
79
+ Argument.String('files').pipe(Argument.variadic);
80
+ Argument.String('files').pipe(Argument.variadic({ min: 1 }));
81
+ Argument.String('files').pipe(Argument.variadic({ min: 1, max: 5 }));
71
82
 
72
83
  // Direct variadic form is also supported
73
- Argument.variadic(Argument.string('files'));
74
- Argument.variadic(Argument.string('files'), { min: 1 });
84
+ Argument.variadic(Argument.String('files'));
85
+ Argument.variadic(Argument.String('files'), { min: 1 });
75
86
 
76
87
  // Cardinality shortcuts
77
- Argument.string('files').pipe(Argument.atLeast(1));
78
- Argument.string('files').pipe(Argument.atMost(5));
79
- Argument.string('files').pipe(Argument.between(1, 5));
88
+ Argument.String('files').pipe(Argument.atLeast(1));
89
+ Argument.String('files').pipe(Argument.atMost(5));
90
+ Argument.String('files').pipe(Argument.between(1, 5));
80
91
 
81
92
  // Transform
82
- Argument.integer('port').pipe(Argument.map((p) => `http://localhost:${p}`));
93
+ Argument.Int('port').pipe(Argument.map((p) => `http://localhost:${p}`));
83
94
 
84
95
  // Validate with Schema
85
- Argument.string('input').pipe(Argument.withSchema(Schema.NonEmptyString));
96
+ Argument.String('input').pipe(Argument.withSchema(Schema.NonEmptyString));
86
97
 
87
98
  // Fallback from env config
88
- Argument.string('repo').pipe(
89
- Argument.withFallbackConfig(Config.string('REPOSITORY'))
99
+ Argument.String('repo').pipe(
100
+ Argument.withFallbackConfig(Config.String('REPOSITORY'))
90
101
  );
91
102
 
92
103
  // Fallback interactive prompt
93
- Argument.string('name').pipe(
94
- Argument.withFallbackPrompt(Prompt.text({ message: 'Name' }))
104
+ Argument.String('name').pipe(
105
+ Argument.withFallbackPrompt(Prompt.String({ message: 'Name' }))
95
106
  );
96
107
 
97
108
  // Custom metavar for help text
98
- Argument.integer('port').pipe(Argument.withMetavar('PORT'));
109
+ Argument.Int('port').pipe(Argument.withMetavar('PORT'));
99
110
 
100
111
  // Filter with error message
101
- Argument.integer('count').pipe(
112
+ Argument.Int('count').pipe(
102
113
  Argument.filter(
103
114
  (n) => n > 0,
104
115
  (n) => `Expected positive, got ${n}`
@@ -115,27 +126,27 @@ Flags are named options with `--name` or `-alias` syntax.
115
126
  ```typescript
116
127
  import { Flag } from 'effect/unstable/cli';
117
128
 
118
- Flag.boolean('verbose'); // required: --verbose / --no-verbose; omission fails
119
- Flag.string('config'); // --config value
120
- Flag.integer('port'); // --port 8080
121
- Flag.float('rate'); // --rate 3.14
122
- Flag.date('since'); // --since 2024-01-01
123
- Flag.file('input'); // --input file.txt
124
- Flag.file('input', { mustExist: true }); // file must exist
125
- Flag.directory('output'); // --output ./dist
126
- Flag.path('config-path'); // --config-path /etc/app
127
- Flag.choice('env', ['dev', 'staging', 'prod']); // --env dev
128
- Flag.choiceWithValue('log-level', [
129
+ Flag.Boolean('verbose'); // required: --verbose / --no-verbose; omission fails
130
+ Flag.String('config'); // --config value
131
+ Flag.Int('port'); // --port 8080
132
+ Flag.Finite('rate'); // --rate 3.14
133
+ Flag.Date('since'); // --since 2024-01-01
134
+ Flag.File('input'); // --input file.txt
135
+ Flag.File('input', { mustExist: true }); // file must exist
136
+ Flag.Directory('output'); // --output ./dist
137
+ Flag.Path('config-path'); // --config-path /etc/app
138
+ Flag.Literals('env', ['dev', 'staging', 'prod']); // --env dev
139
+ Flag.ChoiceWithValue('log-level', [
129
140
  // choice with mapped values
130
141
  ['debug', 'Debug' as const],
131
142
  ['info', 'Info' as const],
132
143
  ['error', 'Error' as const]
133
144
  ]);
134
- Flag.redacted('password'); // Redacted<string>
135
- Flag.fileText('config-file'); // reads file content
136
- Flag.fileParse('config'); // reads and parses file (auto-detects format)
137
- Flag.fileSchema('config', MySchema); // reads and validates via Schema
138
- Flag.keyValuePair('env'); // --env FOO=bar → Record<string, string>
145
+ Flag.Redacted('password'); // Redacted<string>
146
+ Flag.FileText('config-file'); // reads file content
147
+ Flag.FileParse('config'); // reads and parses file (auto-detects format)
148
+ Flag.FileSchema('config', MySchema); // reads and validates via Schema
149
+ Flag.KeyValuePair('env'); // --env FOO=bar → Record<string, string>
139
150
  ```
140
151
 
141
152
  ### Combinators
@@ -144,42 +155,42 @@ Flag.keyValuePair('env'); // --env FOO=bar → Record<string, string>
144
155
  import { Flag } from 'effect/unstable/cli';
145
156
 
146
157
  // Alias
147
- Flag.boolean('verbose').pipe(
158
+ Flag.Boolean('verbose').pipe(
148
159
  Flag.withAlias('v'),
149
160
  Flag.withDefault(false)
150
161
  ); // switch semantics: omission is false
151
162
 
152
163
  // Hidden from help, completions, and typo suggestions, but still parsed
153
- Flag.boolean('experimental-foo').pipe(
164
+ Flag.Boolean('experimental-foo').pipe(
154
165
  Flag.withHidden,
155
166
  Flag.withDefault(false)
156
167
  );
157
168
 
158
169
  // Description
159
- Flag.string('config').pipe(Flag.withDescription('Path to config file'));
170
+ Flag.String('config').pipe(Flag.withDescription('Path to config file'));
160
171
 
161
172
  // Default value (makes flag optional with fallback)
162
- Flag.integer('port').pipe(Flag.withDefault(3000));
173
+ Flag.Int('port').pipe(Flag.withDefault(3000));
163
174
 
164
175
  // Optional (returns Option<T>)
165
- Flag.string('token').pipe(Flag.optional);
176
+ Flag.String('token').pipe(Flag.optional);
166
177
 
167
178
  // Custom metavar for help
168
- Flag.string('db-url').pipe(Flag.withMetavar('URL')); // --db-url URL
179
+ Flag.String('db-url').pipe(Flag.withMetavar('URL')); // --db-url URL
169
180
 
170
181
  // Repetition
171
- Flag.string('tag').pipe(Flag.atLeast(1)); // --tag a --tag b
172
- Flag.string('warning').pipe(Flag.atMost(3));
173
- Flag.string('host').pipe(Flag.between(1, 3));
182
+ Flag.String('tag').pipe(Flag.atLeast(1)); // --tag a --tag b
183
+ Flag.String('warning').pipe(Flag.atMost(3));
184
+ Flag.String('host').pipe(Flag.between(1, 3));
174
185
 
175
186
  // Transform
176
- Flag.integer('port').pipe(Flag.map((p) => `http://localhost:${p}`));
187
+ Flag.Int('port').pipe(Flag.map((p) => `http://localhost:${p}`));
177
188
 
178
189
  // Validate with Schema
179
- Flag.string('email').pipe(Flag.withSchema(EmailSchema));
190
+ Flag.String('email').pipe(Flag.withSchema(Email));
180
191
 
181
192
  // Filter
182
- Flag.integer('port').pipe(
193
+ Flag.Int('port').pipe(
183
194
  Flag.filter(
184
195
  (p) => p >= 1 && p <= 65535,
185
196
  (p) => `Port ${p} out of range`
@@ -187,13 +198,13 @@ Flag.integer('port').pipe(
187
198
  );
188
199
 
189
200
  // Fallback from env config
190
- Flag.boolean('verbose').pipe(
191
- Flag.withFallbackConfig(Config.boolean('VERBOSE'))
201
+ Flag.Boolean('verbose').pipe(
202
+ Flag.withFallbackConfig(Config.Boolean('VERBOSE'))
192
203
  );
193
204
 
194
205
  // Fallback interactive prompt
195
- Flag.string('name').pipe(
196
- Flag.withFallbackPrompt(Prompt.text({ message: 'Name' }))
206
+ Flag.String('name').pipe(
207
+ Flag.withFallbackPrompt(Prompt.String({ message: 'Name' }))
197
208
  );
198
209
  ```
199
210
 
@@ -208,11 +219,11 @@ Bare boolean flags are required. `--verbose` produces `true`, `--no-verbose` pro
208
219
  import { Effect } from 'effect';
209
220
  import { Prompt } from 'effect/unstable/cli';
210
221
 
211
- Prompt.integer({ message: 'Count', default: 42 });
212
- Prompt.file({ message: 'Pick file', default: '/workspace/config.json' });
222
+ Prompt.Int({ message: 'Count', default: 42 });
223
+ Prompt.File({ message: 'Pick file', default: '/workspace/config.json' });
213
224
 
214
225
  // A local override merges over the context theme.
215
- const name = Prompt.text({ message: 'Name', theme: { prefix: '>' } });
226
+ const name = Prompt.String({ message: 'Name', theme: { prefix: '>' } });
216
227
 
217
228
  // Provide once to theme all prompts in a command or application.
218
229
  const themed = name.pipe(
@@ -220,9 +231,9 @@ const themed = name.pipe(
220
231
  );
221
232
  ```
222
233
 
223
- Integer prompt defaults are editable and Enter submits the default if unchanged. `Prompt.file` resolves/selects the default as the initial path.
234
+ Integer prompt defaults are editable and Enter submits the default if unchanged. `Prompt.File` resolves/selects the default as the initial path.
224
235
 
225
- In rc.112, per-prompt `prefix` is replaced by `theme?: Partial<Prompt.Theme>`.
236
+ Per-prompt appearance is configured with `theme?: Partial<Prompt.Theme>`.
226
237
  `Prompt.Theme` is a context reference with platform defaults; `Prompt.makeTheme`
227
238
  builds a complete theme. Local fields override the context theme. Theme fields
228
239
  include prompt symbols, `passwordMask`, and ANSI color values. The default
@@ -249,19 +260,19 @@ const version = Command.make('version');
249
260
 
250
261
  // Command with config (no handler yet)
251
262
  const deploy = Command.make('deploy', {
252
- env: Flag.string('env'),
253
- force: Flag.boolean('force').pipe(Flag.withDefault(false)),
254
- files: Argument.string('files').pipe(Argument.variadic)
263
+ env: Flag.String('env'),
264
+ force: Flag.Boolean('force').pipe(Flag.withDefault(false)),
265
+ files: Argument.String('files').pipe(Argument.variadic)
255
266
  });
256
267
 
257
268
  // Command with config and inline handler
258
269
  const greet = Command.make(
259
270
  'greet',
260
271
  {
261
- name: Argument.string('name').pipe(
272
+ name: Argument.String('name').pipe(
262
273
  Argument.withDescription('Person to greet')
263
274
  ),
264
- times: Flag.integer('times').pipe(Flag.withDefault(1))
275
+ times: Flag.Int('times').pipe(Flag.withDefault(1))
265
276
  },
266
277
  Effect.fn(function* ({ name, times }) {
267
278
  for (let i = 0; i < times; i++) {
@@ -279,8 +290,8 @@ Handlers use `Effect.fn` with a generator that destructures the config:
279
290
  const cmd = Command.make(
280
291
  'deploy',
281
292
  {
282
- env: Flag.choice('env', ['dev', 'staging', 'prod']),
283
- dryRun: Flag.boolean('dry-run').pipe(Flag.withDefault(false))
293
+ env: Flag.Literals('env', ['dev', 'staging', 'prod']),
294
+ dryRun: Flag.Boolean('dry-run').pipe(Flag.withDefault(false))
284
295
  },
285
296
  Effect.fn(function* ({ env, dryRun }) {
286
297
  if (dryRun) {
@@ -296,7 +307,7 @@ Alternatively, add a handler later with `Command.withHandler`:
296
307
 
297
308
  ```typescript
298
309
  const cmd = Command.make('greet', {
299
- name: Flag.string('name')
310
+ name: Flag.String('name')
300
311
  }).pipe(Command.withHandler(({ name }) => Console.log(`Hello, ${name}!`)));
301
312
  ```
302
313
 
@@ -326,12 +337,12 @@ Config objects can be nested for organization:
326
337
 
327
338
  ```typescript
328
339
  const deploy = Command.make('deploy', {
329
- environment: Flag.string('env'),
340
+ environment: Flag.String('env'),
330
341
  server: {
331
- host: Flag.string('host').pipe(Flag.withDefault('localhost')),
332
- port: Flag.integer('port').pipe(Flag.withDefault(3000))
342
+ host: Flag.String('host').pipe(Flag.withDefault('localhost')),
343
+ port: Flag.Int('port').pipe(Flag.withDefault(3000))
333
344
  },
334
- files: Argument.string('files').pipe(Argument.variadic)
345
+ files: Argument.String('files').pipe(Argument.variadic)
335
346
  });
336
347
  // Handler receives: { environment: string, server: { host: string, port: number }, files: ReadonlyArray<string> }
337
348
  ```
@@ -354,7 +365,7 @@ const init = Command.make(
354
365
  const build = Command.make(
355
366
  'build',
356
367
  {
357
- target: Flag.choice('target', ['web', 'node'])
368
+ target: Flag.Literals('target', ['web', 'node'])
358
369
  },
359
370
  Effect.fn(function* ({ target }) {
360
371
  yield* Console.log(`Building for ${target}`);
@@ -377,11 +388,11 @@ Use `Command.withSharedFlags` to define flags on a parent that are available to
377
388
  ```typescript
378
389
  const tasks = Command.make('tasks').pipe(
379
390
  Command.withSharedFlags({
380
- workspace: Flag.string('workspace').pipe(
391
+ workspace: Flag.String('workspace').pipe(
381
392
  Flag.withAlias('w'),
382
393
  Flag.withDefault('personal')
383
394
  ),
384
- verbose: Flag.boolean('verbose').pipe(
395
+ verbose: Flag.Boolean('verbose').pipe(
385
396
  Flag.withAlias('v'),
386
397
  Flag.withDefault(false)
387
398
  )
@@ -391,8 +402,8 @@ const tasks = Command.make('tasks').pipe(
391
402
  const create = Command.make(
392
403
  'create',
393
404
  {
394
- title: Argument.string('title'),
395
- priority: Flag.choice('priority', ['low', 'normal', 'high']).pipe(
405
+ title: Argument.String('title'),
406
+ priority: Flag.Literals('priority', ['low', 'normal', 'high']).pipe(
396
407
  Flag.withDefault('normal')
397
408
  )
398
409
  },
@@ -419,10 +430,10 @@ const create = Command.make(
419
430
  const list = Command.make(
420
431
  'list',
421
432
  {
422
- status: Flag.choice('status', ['open', 'done', 'all']).pipe(
433
+ status: Flag.Literals('status', ['open', 'done', 'all']).pipe(
423
434
  Flag.withDefault('open')
424
435
  ),
425
- json: Flag.boolean('json').pipe(Flag.withDefault(false))
436
+ json: Flag.Boolean('json').pipe(Flag.withDefault(false))
426
437
  },
427
438
  Effect.fn(function* ({ status, json }) {
428
439
  const root = yield* tasks;
@@ -467,7 +478,7 @@ app.pipe(
467
478
  const deploy = Command.make(
468
479
  'deploy',
469
480
  {
470
- env: Flag.string('env')
481
+ env: Flag.String('env')
471
482
  },
472
483
  Effect.fn(function* ({ env }) {
473
484
  const fs = yield* FileSystem.FileSystem;
@@ -503,7 +514,7 @@ import { Command, Flag } from 'effect/unstable/cli';
503
514
  const myCommand = Command.make(
504
515
  'myapp',
505
516
  {
506
- name: Flag.string('name')
517
+ name: Flag.String('name')
507
518
  },
508
519
  Effect.fn(function* ({ name }) {
509
520
  yield* Console.log(`Hello, ${name}!`);
@@ -533,7 +544,7 @@ const run = Command.runWith(myCommand, { version: '1.0.0' });
533
544
 
534
545
  ## Key Patterns
535
546
 
536
- 1. **`Argument` = positional, `Flag` = named** — No `Argument.boolean`; use `Flag.boolean` for toggles, and add `Flag.withDefault(false)` when omission should mean `false`
547
+ 1. **`Argument` = positional, `Flag` = named** — Use `Flag.Boolean` for toggles, and add `Flag.withDefault(false)` when omission should mean `false`
537
548
  2. **Handlers use `Effect.fn`** — `Effect.fn(function*({ ...config }) { ... })`
538
549
  3. **Parent access via yield** — `const root = yield* parentCommand` inside subcommand handlers
539
550
  4. **Shared flags** — `Command.withSharedFlags` on parent; only flags allowed (no arguments)
@@ -7,6 +7,12 @@ description: Spawn and manage child processes using Effect's ChildProcess module
7
7
 
8
8
  ## Overview
9
9
 
10
+ Node process-group cleanup waits for the leader and descendants. Without
11
+ `forceKillAfter`, cleanup waits up to one second without escalating. With it,
12
+ the group receives SIGKILL at the deadline, then a final bounded wait. Native
13
+ timers drive escalation even under TestClock. `exitCode` / `isRunning` describe
14
+ the leader, not all descendants; do not use virtual time alone to prove OS cleanup.
15
+
10
16
  The `ChildProcess` module provides type-safe, composable process execution with automatic resource cleanup via `Scope`. Commands are AST values — built first with `make` and `pipeTo`, then executed via the `ChildProcessSpawner` service.
11
17
 
12
18
  **When to use this skill:**
@@ -131,7 +137,7 @@ const cmd = ChildProcess.make('node', ['script.js'], {
131
137
 
132
138
  `extendEnv` defaults to `false`. If `env` is supplied without `extendEnv: true`, it replaces the inherited child environment rather than merging with it.
133
139
 
134
- The `fd3`/`fd4` names above configure child-process stdio channels. They are unrelated to the `FileSystem.File.Descriptor` type removed in beta.103; process handles still expose `getInputFd(number)` and `getOutputFd(number)` for configured additional descriptors.
140
+ The `fd3`/`fd4` names configure child-process stdio channels. Process handles expose `getInputFd(number)` and `getOutputFd(number)` for configured additional descriptors; use FileSystem's scoped handle operations for files.
135
141
 
136
142
  ### Combinators
137
143