@danieljvdm/dev-kit 0.2.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.
Files changed (47) hide show
  1. package/README.md +290 -0
  2. package/bin/dev-kit.mjs +3 -0
  3. package/dev-kit.example.jsonc +13 -0
  4. package/package.json +69 -0
  5. package/schema/dev-kit.schema.json +128 -0
  6. package/schema/skill-sources.schema.json +83 -0
  7. package/skill-sources.jsonc +55 -0
  8. package/skill-sources.lock.json +136 -0
  9. package/skills/dev-kit/SKILL.md +145 -0
  10. package/skills/dev-kit/agents/openai.yaml +4 -0
  11. package/skills/effect-ts/SKILL.md +242 -0
  12. package/skills/effect-ts/UPSTREAM.md +28 -0
  13. package/skills/effect-ts/agents/openai.yaml +5 -0
  14. package/skills/effect-ts/references/audit-services.md +144 -0
  15. package/skills/effect-ts/references/features.md +525 -0
  16. package/skills/effect-ts/references/guide-cli.md +106 -0
  17. package/skills/effect-ts/references/guide-effect.md +453 -0
  18. package/skills/effect-ts/references/guide-error-handling.md +574 -0
  19. package/skills/effect-ts/references/guide-http-boundaries.md +55 -0
  20. package/skills/effect-ts/references/guide-layers.md +1017 -0
  21. package/skills/effect-ts/references/guide-observability.md +771 -0
  22. package/skills/effect-ts/references/guide-retries.md +446 -0
  23. package/skills/effect-ts/references/guide-schedule.md +357 -0
  24. package/skills/effect-ts/references/guide-schema.md +671 -0
  25. package/skills/effect-ts/references/guide-sql.md +539 -0
  26. package/skills/effect-ts/references/guide-testing.md +534 -0
  27. package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +131 -0
  28. package/skills/effect-ts/references/version-and-source.md +87 -0
  29. package/src/bin/dev-kit.ts +372 -0
  30. package/src/catalog-manager.ts +345 -0
  31. package/src/catalog.ts +246 -0
  32. package/src/cli-ui.ts +110 -0
  33. package/src/effect-source.ts +325 -0
  34. package/src/effect-tsgo.ts +256 -0
  35. package/src/gitignore.ts +212 -0
  36. package/src/index.ts +98 -0
  37. package/src/manifest.ts +133 -0
  38. package/src/node-symbolic-link.ts +31 -0
  39. package/src/path-digest.ts +140 -0
  40. package/src/project-process-lock.ts +76 -0
  41. package/src/project-state.ts +67 -0
  42. package/src/skill-manager.ts +326 -0
  43. package/src/source-manifest.ts +51 -0
  44. package/src/sync.ts +900 -0
  45. package/src/tool-metadata.ts +3 -0
  46. package/src/typescript-package-name.ts +5 -0
  47. package/src/vendor.ts +848 -0
@@ -0,0 +1,106 @@
1
+ # Effect CLI
2
+
3
+ ## Overview
4
+
5
+ Build scripts as small Effect programs with typed errors, platform services, and `effect/unstable/cli`. Read nearby scripts first and follow local conventions for imports, package scripts, and validation commands.
6
+
7
+ Use the matching general and local guides from `effect-ts` when the CLI also
8
+ includes domain models, reusable services, layers, typed domain errors,
9
+ structured logging, tests, or HTTP/runtime boundary code.
10
+
11
+ ## Workflow
12
+
13
+ 1. Read nearby scripts before editing. Prefer the repo's current imports, error classes, command helper names, and package-script style.
14
+ 2. Use `effect/unstable/cli` for command shape, descriptions, help, and arguments/options.
15
+ 3. Use platform services for effects:
16
+ - `@effect/platform-node`: `NodeRuntime`, `NodeServices`
17
+ - `@effect/platform-bun`: Bun equivalent only when the project already uses it
18
+ - `FileSystem`, `Path`, `Console`, `Terminal`, `ChildProcess`, `Stream` from Effect/platform packages where available
19
+ 4. Keep direct `node:*`, Bun globals, `process.argv`, `process.env`, `console.*`, `fs`, `path`, and `child_process` calls inside explicit runtime adapters. Use Effect platform services throughout script logic.
20
+ 5. Model expected failures as `Schema.TaggedErrorClass` with useful `message` overrides.
21
+ 6. Keep shell execution deterministic: pass command and args as arrays to `ChildProcess.make`, set `cwd` explicitly, capture output, and fail on non-zero exit.
22
+ 7. Make interactive flows explicit and pleasant: show a short title, summarize detected state, prompt before ambiguous choices, and print the final command/action before running it.
23
+ 8. Wire the command with `CliCommand.run(command, { version: "1.0.0" }).pipe(Effect.scoped, Effect.provide(NodeServices.layer))`, then `NodeRuntime.runMain(program, { disableErrorReporting: true })`.
24
+ 9. Update `package.json` scripts to call the TypeScript script through the repo's established runner (`tsx`/`bun`) and keep runner choice consistent across scripts.
25
+ 10. Include every executable TypeScript script in one checked TypeScript project. Validate with `bun run check` or the narrow package check plus a direct `--help`/dry-run path for the new command.
26
+
27
+ ## Script Skeleton
28
+
29
+ Use this shape unless local scripts have a better established variant:
30
+
31
+ ```ts
32
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
33
+ import { Console, Effect, Path, Schema as S, Stream } from "effect";
34
+ import { Command as CliCommand } from "effect/unstable/cli";
35
+ import { ChildProcess } from "effect/unstable/process";
36
+
37
+ class CommandError extends S.TaggedErrorClass<CommandError>()("CommandError", {
38
+ command: S.String,
39
+ exitCode: S.Int,
40
+ output: S.String,
41
+ }) {
42
+ override get message() {
43
+ return this.output.length > 0
44
+ ? `${this.command} exited with code ${this.exitCode}: ${this.output}`
45
+ : `${this.command} exited with code ${this.exitCode}`;
46
+ }
47
+ }
48
+
49
+ const runCommand = Effect.fn("runCommand")(function* (
50
+ cwd: string,
51
+ command: string,
52
+ args: ReadonlyArray<string>,
53
+ ) {
54
+ const formatted = [command, ...args].join(" ");
55
+ const child = yield* ChildProcess.make(command, args, { cwd, stderr: "pipe", stdout: "pipe" });
56
+ const [output, exitCode] = yield* Effect.all([
57
+ Stream.mkString(Stream.decodeText(child.all)),
58
+ child.exitCode,
59
+ ]);
60
+ const trimmed = output.trim();
61
+
62
+ if (exitCode !== 0) {
63
+ return yield* new CommandError({ command: formatted, exitCode, output: trimmed });
64
+ }
65
+
66
+ return trimmed;
67
+ });
68
+
69
+ const main = Effect.gen(function* () {
70
+ yield* Console.log("Doing work...");
71
+ });
72
+
73
+ const command = CliCommand.make("script-name", {}, () => main).pipe(
74
+ CliCommand.withDescription("Describe what the script does."),
75
+ );
76
+
77
+ const program = CliCommand.run(command, { version: "1.0.0" }).pipe(
78
+ Effect.scoped,
79
+ Effect.provide(NodeServices.layer),
80
+ );
81
+
82
+ NodeRuntime.runMain(program, { disableErrorReporting: true });
83
+ ```
84
+
85
+ ## CLI Design Rules
86
+
87
+ - Prefer named options over environment variables for normal user input. Keep environment variables only for CI or stable machine-local overrides.
88
+ - Prefer deterministic auto-detection when there is exactly one valid candidate; prompt or fail clearly when there are zero or many.
89
+ - Print normal interactive UI/status lines to stdout with `Console.log`; use stderr for real errors or for commands that intentionally reserve stdout for machine-readable output.
90
+ - For visually nicer output, use simple ASCII structure that degrades well in CI: title lines, aligned labels, and short bullet lists. Avoid decorative Unicode unless the file already uses it.
91
+ - Use `CliCommand.withDescription` and ensure `--help` explains the happy path and escape hatches.
92
+ - Add a dry-run or help path for scripts that would start long-running servers or device builds.
93
+ - Decode structured JSON input with `Schema.fromJsonString(Model)` so parsing
94
+ and validation share one schema-owned boundary.
95
+
96
+ ## Validation
97
+
98
+ Run the narrowest meaningful checks:
99
+
100
+ - Confirm that every TypeScript entrypoint named by `package.json` belongs to
101
+ one checked TypeScript project.
102
+ - `bun run check:scripts` for root scripts.
103
+ - Package-level `bun run check` when the script lives under an app/package and is included in that TS config.
104
+ - The command's `--help` path.
105
+ - A dry-run or harmless invalid-input path that loads the entrypoint and
106
+ exercises parsing and environment discovery.
@@ -0,0 +1,453 @@
1
+ # Effect Guide
2
+
3
+ This guide covers general Effect usage, including reusable function boundaries,
4
+ Promise interop, composition, provisioning, and runtime execution.
5
+
6
+ Key source areas:
7
+
8
+ - `packages/effect/src/Effect.ts`
9
+ - `packages/tools/`
10
+ - `packages/platform-*`
11
+ - `packages/opentelemetry/`
12
+ - `packages/vitest/`
13
+
14
+ ## Mental Model
15
+
16
+ `Effect<A, E, R>` is the default way to represent application work.
17
+
18
+ It describes a computation that:
19
+
20
+ - succeeds with `A`
21
+ - fails with `E`
22
+ - requires services `R`
23
+
24
+ The repo consistently uses `Effect` as the main abstraction for:
25
+
26
+ - business workflows
27
+ - service methods
28
+ - platform integrations
29
+ - resource lifecycles
30
+ - tests
31
+
32
+ ## Most Common Patterns In The Repo
33
+
34
+ The dominant usage pattern is:
35
+
36
+ 1. use `Effect.gen` for workflows and orchestration
37
+ 2. use `Effect.fn` for reusable effectful functions
38
+ 3. use precise constructors such as `succeed`, `fail`, `sync`, `try`, and `tryPromise`
39
+ 4. use `map`, `flatMap`, and `tap` for local transformations
40
+ 5. access services in implementations and `provide*` only at edges
41
+ 6. use `acquireRelease` and `scoped` for owned resources
42
+ 7. use `catchTag` and `match` for typed recovery
43
+ 8. use `run*` only at runtime boundaries
44
+
45
+ ## Prefer `Effect.fn` For Reusable Operations
46
+
47
+ For reusable effectful operations, prefer `Effect.fn`.
48
+
49
+ ```ts
50
+ import { Effect } from "effect"
51
+
52
+ const loadUser = Effect.fn("loadUser")(function*(userId: string) {
53
+ return { id: userId, name: "Ada" }
54
+ })
55
+ ```
56
+
57
+ Use `Effect.fn` when:
58
+
59
+ - the operation is reusable
60
+ - the operation takes parameters
61
+ - the operation is part of business logic or a module API
62
+ - you want consistent tracing and stack frames
63
+
64
+ Do not treat `Effect.fnUntraced` as the default. If you do not want an explicit named span, use `Effect.fn` without a span name.
65
+
66
+ Repo examples:
67
+
68
+ - `packages/tools/utils/src/Codegen.ts`
69
+ - `packages/tools/openapi-generator/src/OpenApiPatch.ts`
70
+
71
+ ## Use `Effect.gen` For Workflows
72
+
73
+ Use `Effect.gen` for orchestration and sequential workflows, especially when there are multiple `yield*` steps.
74
+
75
+ ```ts
76
+ const program = Effect.gen(function*() {
77
+ const config = yield* Config
78
+ const repo = yield* UserRepo
79
+ const user = yield* repo.getById("u_123")
80
+ return { config, user }
81
+ })
82
+ ```
83
+
84
+ Use `Effect.gen` when:
85
+
86
+ - the body is a workflow
87
+ - you are reading multiple services
88
+ - you have branching or multiple sequential steps
89
+ - you are implementing a layer, handler, or orchestration
90
+
91
+ Repo examples:
92
+
93
+ - `packages/opentelemetry/src/NodeSdk.ts`
94
+ - `packages/tools/openapi-generator/src/OpenApiGenerator.ts`
95
+
96
+ ## `Effect.fn` vs `Effect.gen`
97
+
98
+ Use this rule:
99
+
100
+ - reusable operation: `Effect.fn`
101
+ - inline workflow block: `Effect.gen`
102
+
103
+ Good split:
104
+
105
+ ```ts
106
+ const loadUser = Effect.fn("loadUser")(function*(userId: string) {
107
+ const repo = yield* UserRepo
108
+ return yield* repo.getById(userId)
109
+ })
110
+
111
+ const program = Effect.gen(function*() {
112
+ const user = yield* loadUser("u_123")
113
+ yield* Effect.logInfo("loaded user", user)
114
+ })
115
+ ```
116
+
117
+ ## `Effect.fnUntraced` Is An Escape Hatch
118
+
119
+ For application and business code, `Effect.fnUntraced` is not the default.
120
+
121
+ Use it only when:
122
+
123
+ - the function is an internal low-level helper
124
+ - observability is intentionally being traded away
125
+ - there is a concrete performance or tracing reason
126
+
127
+ If the only goal is to avoid an explicit named span, prefer:
128
+
129
+ ```ts
130
+ const normalizeUser = Effect.fn(function*(input: string) {
131
+ return input.trim().toLowerCase()
132
+ })
133
+ ```
134
+
135
+ Instead of:
136
+
137
+ ```ts
138
+ const normalizeUser = Effect.fnUntraced(function*(input: string) {
139
+ return input.trim().toLowerCase()
140
+ })
141
+ ```
142
+
143
+ ## Constructor Functions
144
+
145
+ The repo uses constructor functions very deliberately.
146
+
147
+ ### `Effect.succeed`
148
+
149
+ Use for pure successful values.
150
+
151
+ ```ts
152
+ const ok = Effect.succeed(42)
153
+ ```
154
+
155
+ ### `Effect.fail`
156
+
157
+ Use for expected typed failures.
158
+
159
+ ```ts
160
+ const notFound = Effect.fail(UserNotFound.make({ userId: "u_123" }))
161
+ ```
162
+
163
+ ### `Effect.sync`
164
+
165
+ Use for synchronous side effects or pure synchronous construction that should live inside `Effect`.
166
+
167
+ ```ts
168
+ const buildConfig = Effect.sync(() => ({ retries: 3 }))
169
+ ```
170
+
171
+ ### `Effect.try`
172
+
173
+ Use for synchronous code that may throw.
174
+
175
+ ```ts
176
+ import { Effect, Schema } from "effect"
177
+
178
+ class ParseError extends Schema.TaggedErrorClass<ParseError>()("ParseError", {
179
+ cause: Schema.Defect()
180
+ }) {}
181
+
182
+ const parseJson = (input: string) =>
183
+ Effect.try({
184
+ try: () => JSON.parse(input),
185
+ catch: (cause) => ParseError.make({ cause })
186
+ })
187
+ ```
188
+
189
+ ### `Effect.tryPromise`
190
+
191
+ Use for external Promise-returning APIs. Translate recoverable rejections into
192
+ the concrete typed error owned by that integration boundary, preserving an
193
+ opaque cause when it is diagnostically useful. Compose Effect-native APIs
194
+ directly instead of converting them through Promise.
195
+
196
+ ```ts
197
+ import { Effect, Schema } from "effect"
198
+
199
+ class FetchError extends Schema.TaggedErrorClass<FetchError>()("FetchError", {
200
+ cause: Schema.Defect()
201
+ }) {}
202
+
203
+ const fetchText = (url: string) =>
204
+ Effect.tryPromise({
205
+ try: () => fetch(url).then((response) => response.text()),
206
+ catch: (cause) => FetchError.make({ cause })
207
+ })
208
+ ```
209
+
210
+ Preferred rule:
211
+
212
+ - pure value: `succeed`
213
+ - expected failure: `fail`
214
+ - synchronous non-throwing effect: `sync`
215
+ - synchronous throwing boundary: `try`
216
+ - Promise boundary: `tryPromise`
217
+
218
+ ## Local Composition
219
+
220
+ The repo uses `map`, `flatMap`, and `tap` constantly for small local transformations.
221
+
222
+ ### `Effect.map`
223
+
224
+ Use to transform successful values.
225
+
226
+ ```ts
227
+ const userName = loadUser("u_123").pipe(
228
+ Effect.map((user) => user.name)
229
+ )
230
+ ```
231
+
232
+ ### `Effect.flatMap`
233
+
234
+ Use when the next step returns another `Effect`.
235
+
236
+ ```ts
237
+ const result = loadUser("u_123").pipe(
238
+ Effect.flatMap((user) => saveAudit(user.id))
239
+ )
240
+ ```
241
+
242
+ ### `Effect.tap`
243
+
244
+ Use for side effects that should preserve the main value.
245
+
246
+ ```ts
247
+ const result = loadUser("u_123").pipe(
248
+ Effect.tap((user) => Effect.logDebug("loaded user", { userId: user.id }))
249
+ )
250
+ ```
251
+
252
+ Preferred rule:
253
+
254
+ - outer workflow: `Effect.gen`
255
+ - local transformation: `map`, `flatMap`, `tap`
256
+
257
+ ## Services And Provisioning
258
+
259
+ Repo style is:
260
+
261
+ - access services in implementation code
262
+ - provide them at boundaries
263
+
264
+ ### Access services in implementations
265
+
266
+ ```ts
267
+ const loadUser = Effect.fn("loadUser")(function*(userId: string) {
268
+ const repo = yield* UserRepo
269
+ return yield* repo.getById(userId)
270
+ })
271
+ ```
272
+
273
+ or:
274
+
275
+ ```ts
276
+ const loadUser = (userId: string) =>
277
+ Effect.service(UserRepo).pipe(
278
+ Effect.flatMap((repo) => repo.getById(userId))
279
+ )
280
+ ```
281
+
282
+ ### Provide at the edge
283
+
284
+ ```ts
285
+ const program = loadUser("u_123").pipe(
286
+ Effect.provide(AppLayer)
287
+ )
288
+ ```
289
+
290
+ Use `provideService` and `provideServiceEffect` for targeted overrides, especially in tests or framework boundaries.
291
+
292
+ Do not default to exporting thin accessor functions that just fetch a service and forward to one service method. Prefer real business operations or direct service usage within the owning workflow.
293
+
294
+ Repo examples:
295
+
296
+ - `packages/tools/utils/src/bin.ts`
297
+ - `packages/tools/openapi-generator/test/`
298
+
299
+ ## Error Handling
300
+
301
+ Common repo patterns:
302
+
303
+ - `catchTag` for expected tagged errors
304
+ - `match` for totalizing an effect into a value
305
+ - `catchCause` for full-cause infra handling
306
+
307
+ ### `Effect.catchTag`
308
+
309
+ Use for targeted typed recovery.
310
+
311
+ ```ts
312
+ const safe = loadUser("u_123").pipe(
313
+ Effect.catchTag("UserNotFound", () => Effect.succeed(null))
314
+ )
315
+ ```
316
+
317
+ ### `Effect.match`
318
+
319
+ Use when the caller wants a value either way.
320
+
321
+ ```ts
322
+ const result = loadUser("u_123").pipe(
323
+ Effect.match({
324
+ onFailure: () => null,
325
+ onSuccess: (user) => user
326
+ })
327
+ )
328
+ ```
329
+
330
+ For deeper guidance, see `./references/guide-error-handling.md`.
331
+
332
+ ## Resource Management
333
+
334
+ One of the strongest repo patterns is explicit resource ownership.
335
+
336
+ ### `Effect.acquireRelease`
337
+
338
+ Use for resources that must be cleaned up.
339
+
340
+ ```ts
341
+ const connection = Effect.acquireRelease(
342
+ openConnection,
343
+ (conn) => closeConnection(conn)
344
+ )
345
+ ```
346
+
347
+ ### `Effect.scoped`
348
+
349
+ Use when a workflow consumes scoped resources and should tie cleanup to scope lifetime.
350
+
351
+ ```ts
352
+ const program = Effect.scoped(
353
+ Effect.gen(function*() {
354
+ const conn = yield* connection
355
+ return yield* conn.query("select 1")
356
+ })
357
+ )
358
+ ```
359
+
360
+ Repo examples:
361
+
362
+ - `packages/platform-node/`
363
+ - `packages/opentelemetry/src/NodeSdk.ts`
364
+
365
+ ## SQL And Runtime Integrations
366
+
367
+ When Effect already provides a domain module for a capability, prefer that module over direct raw runtime client usage in business code.
368
+
369
+ Important example:
370
+
371
+ - prefer Effect SQL modules from `effect/unstable/sql/*` over embedding a native SQL driver directly in domain services
372
+
373
+ Why:
374
+
375
+ - transactions, spans, and typed errors stay inside the Effect model
376
+ - layering stays cleaner
377
+ - migrations and query conventions stay consistent
378
+
379
+ For SQL-specific guidance, see `./references/guide-sql.md`.
380
+
381
+ ## Observability
382
+
383
+ The repo uses observability around meaningful boundaries, not every tiny helper.
384
+
385
+ Common patterns:
386
+
387
+ - `Effect.fn` for named operations
388
+ - `Effect.withSpan` for nested span boundaries
389
+ - `Effect.log*` for operational events
390
+ - `Effect.track` for metrics
391
+
392
+ For detailed guidance, see `./references/guide-observability.md`.
393
+
394
+ ## Runtime Boundaries
395
+
396
+ The repo keeps `run*` APIs at true runtime boundaries.
397
+
398
+ ### `Effect.runPromise`
399
+
400
+ Use when leaving Effect world into Promise-based hosts.
401
+
402
+ ### `Effect.runFork`
403
+
404
+ Use for background fibers or long-running integration hooks.
405
+
406
+ ### `Effect.runSync`
407
+
408
+ Use sparingly, mostly in specialized internals where synchrony is guaranteed.
409
+
410
+ Preferred rule:
411
+
412
+ - library/business code should return `Effect`
413
+ - entrypoints and integration boundaries should run `Effect`
414
+ - `Effect.runPromise` belongs only where a Promise-based host takes ownership
415
+ of the Effect program
416
+
417
+ If you have multiple external entrypoints, prefer `ManagedRuntime`.
418
+
419
+ ## Commonly Used Effect APIs In This Repo
420
+
421
+ These are the most practically important `Effect` functions to know first:
422
+
423
+ - `Effect.fn`
424
+ - `Effect.gen`
425
+ - `Effect.succeed`
426
+ - `Effect.fail`
427
+ - `Effect.sync`
428
+ - `Effect.try`
429
+ - `Effect.tryPromise`
430
+ - `Effect.map`
431
+ - `Effect.flatMap`
432
+ - `Effect.tap`
433
+ - `Effect.service`
434
+ - `Effect.provide`
435
+ - `Effect.provideService`
436
+ - `Effect.catchTag`
437
+ - `Effect.match`
438
+ - `Effect.acquireRelease`
439
+ - `Effect.scoped`
440
+ - `Effect.withSpan`
441
+ - `Effect.logInfo`
442
+ - `Effect.logDebug`
443
+ - `Effect.runPromise`
444
+
445
+ ## Good Repo Examples To Study
446
+
447
+ - `packages/tools/utils/src/Codegen.ts`
448
+ - `packages/tools/openapi-generator/src/OpenApiPatch.ts`
449
+ - `packages/tools/openapi-generator/src/OpenApiGenerator.ts`
450
+ - `packages/opentelemetry/src/NodeSdk.ts`
451
+ - `packages/opentelemetry/src/OtelTracer.ts`
452
+ - `packages/platform-node/`
453
+ - `packages/vitest/src/index.ts`