@danieljvdm/dev-kit 0.6.0 → 0.7.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 (49) hide show
  1. package/README.md +123 -56
  2. package/dev-kit.example.jsonc +7 -3
  3. package/package.json +19 -16
  4. package/schema/dev-kit.schema.json +38 -0
  5. package/skill-sources.jsonc +8 -12
  6. package/skill-sources.lock.json +3 -9
  7. package/skills/dev-kit/SKILL.md +52 -17
  8. package/skills/effect-ts/agents/openai.yaml +0 -1
  9. package/skills/effect-ts/references/audit-services.md +11 -11
  10. package/skills/effect-ts/references/guide-effect.md +56 -69
  11. package/skills/effect-ts/references/guide-error-handling.md +64 -73
  12. package/skills/effect-ts/references/guide-layers.md +187 -215
  13. package/skills/effect-ts/references/guide-observability.md +91 -116
  14. package/skills/effect-ts/references/guide-retries.md +32 -44
  15. package/skills/effect-ts/references/guide-schedule.md +26 -40
  16. package/skills/effect-ts/references/guide-schema.md +50 -57
  17. package/skills/effect-ts/references/guide-sql.md +47 -50
  18. package/skills/effect-ts/references/guide-testing.md +96 -98
  19. package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +7 -7
  20. package/skills/effect-ts/references/version-and-source.md +0 -1
  21. package/src/bin/dev-kit.ts +61 -28
  22. package/src/catalog-manager.ts +86 -34
  23. package/src/catalog.ts +71 -33
  24. package/src/cli-ui.ts +20 -16
  25. package/src/effect-source.ts +49 -19
  26. package/src/effect-tsgo.ts +66 -35
  27. package/src/gitignore.ts +19 -6
  28. package/src/index.ts +6 -0
  29. package/src/manifest.ts +38 -3
  30. package/src/node-symbolic-link.ts +3 -0
  31. package/src/oxlint-plugin-effect.js +3 -0
  32. package/src/oxlint-plugin-style.d.ts +8 -0
  33. package/src/oxlint-plugin-style.js +8 -0
  34. package/src/oxlint.js +14 -0
  35. package/src/oxlint.ts +14 -0
  36. package/src/package-skill-source.ts +189 -52
  37. package/src/path-digest.ts +31 -11
  38. package/src/project-package.ts +44 -19
  39. package/src/project-process-lock.ts +19 -12
  40. package/src/project-state.ts +11 -0
  41. package/src/skill-manager.ts +134 -55
  42. package/src/skill-selector.ts +8 -2
  43. package/src/source-manifest.ts +2 -6
  44. package/src/sync.ts +371 -103
  45. package/src/vendor.ts +112 -42
  46. package/src/vite-plus-hooks.ts +174 -0
  47. package/src/vite-plus-quality.ts +49 -0
  48. package/templates/vite-plus/github-actions-check.yml +44 -0
  49. package/templates/vite-plus/vite.config.ts +22 -0
@@ -44,17 +44,17 @@ Enumerate source and test files, then find:
44
44
 
45
45
  Record one row per discovered service or candidate:
46
46
 
47
- | Field | Question |
48
- | --- | --- |
49
- | Owner | Which module owns the capability's meaning? |
50
- | Contract | Where are its interface and tag? |
51
- | Construction | Does construction yield every runtime dependency? |
52
- | Production | Who owns the concrete implementation and Layer choice? |
53
- | Tests | Does it have an intentional and honest substitute strategy? |
54
- | Consumers | Are capabilities yielded or drilled as values? |
55
- | Requirements | Do requirements remain visible to the composition root? |
56
- | Type boundary | Who owns decoding, narrowing, and error translation? |
57
- | Verdict | Keep, deepen, relocate, merge, remove, or create? |
47
+ | Field | Question |
48
+ | ------------- | ----------------------------------------------------------- |
49
+ | Owner | Which module owns the capability's meaning? |
50
+ | Contract | Where are its interface and tag? |
51
+ | Construction | Does construction yield every runtime dependency? |
52
+ | Production | Who owns the concrete implementation and Layer choice? |
53
+ | Tests | Does it have an intentional and honest substitute strategy? |
54
+ | Consumers | Are capabilities yielded or drilled as values? |
55
+ | Requirements | Do requirements remain visible to the composition root? |
56
+ | Type boundary | Who owns decoding, narrowing, and error translation? |
57
+ | Verdict | Keep, deepen, relocate, merge, remove, or create? |
58
58
 
59
59
  Build a companion type-safety inventory using
60
60
  [`guide-type-safety-and-boundaries.md`](guide-type-safety-and-boundaries.md).
@@ -47,11 +47,11 @@ The dominant usage pattern is:
47
47
  For reusable effectful operations, prefer `Effect.fn`.
48
48
 
49
49
  ```ts
50
- import { Effect } from "effect"
50
+ import { Effect } from "effect";
51
51
 
52
- const loadUser = Effect.fn("loadUser")(function*(userId: string) {
53
- return { id: userId, name: "Ada" }
54
- })
52
+ const loadUser = Effect.fn("loadUser")(function* (userId: string) {
53
+ return { id: userId, name: "Ada" };
54
+ });
55
55
  ```
56
56
 
57
57
  Use `Effect.fn` when:
@@ -73,12 +73,12 @@ Repo examples:
73
73
  Use `Effect.gen` for orchestration and sequential workflows, especially when there are multiple `yield*` steps.
74
74
 
75
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
- })
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
82
  ```
83
83
 
84
84
  Use `Effect.gen` when:
@@ -103,15 +103,15 @@ Use this rule:
103
103
  Good split:
104
104
 
105
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
- })
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
115
  ```
116
116
 
117
117
  ## `Effect.fnUntraced` Is An Escape Hatch
@@ -127,17 +127,17 @@ Use it only when:
127
127
  If the only goal is to avoid an explicit named span, prefer:
128
128
 
129
129
  ```ts
130
- const normalizeUser = Effect.fn(function*(input: string) {
131
- return input.trim().toLowerCase()
132
- })
130
+ const normalizeUser = Effect.fn(function* (input: string) {
131
+ return input.trim().toLowerCase();
132
+ });
133
133
  ```
134
134
 
135
135
  Instead of:
136
136
 
137
137
  ```ts
138
- const normalizeUser = Effect.fnUntraced(function*(input: string) {
139
- return input.trim().toLowerCase()
140
- })
138
+ const normalizeUser = Effect.fnUntraced(function* (input: string) {
139
+ return input.trim().toLowerCase();
140
+ });
141
141
  ```
142
142
 
143
143
  ## Constructor Functions
@@ -149,7 +149,7 @@ The repo uses constructor functions very deliberately.
149
149
  Use for pure successful values.
150
150
 
151
151
  ```ts
152
- const ok = Effect.succeed(42)
152
+ const ok = Effect.succeed(42);
153
153
  ```
154
154
 
155
155
  ### `Effect.fail`
@@ -157,7 +157,7 @@ const ok = Effect.succeed(42)
157
157
  Use for expected typed failures.
158
158
 
159
159
  ```ts
160
- const notFound = Effect.fail(UserNotFound.make({ userId: "u_123" }))
160
+ const notFound = Effect.fail(UserNotFound.make({ userId: "u_123" }));
161
161
  ```
162
162
 
163
163
  ### `Effect.sync`
@@ -165,7 +165,7 @@ const notFound = Effect.fail(UserNotFound.make({ userId: "u_123" }))
165
165
  Use for synchronous side effects or pure synchronous construction that should live inside `Effect`.
166
166
 
167
167
  ```ts
168
- const buildConfig = Effect.sync(() => ({ retries: 3 }))
168
+ const buildConfig = Effect.sync(() => ({ retries: 3 }));
169
169
  ```
170
170
 
171
171
  ### `Effect.try`
@@ -173,17 +173,17 @@ const buildConfig = Effect.sync(() => ({ retries: 3 }))
173
173
  Use for synchronous code that may throw.
174
174
 
175
175
  ```ts
176
- import { Effect, Schema } from "effect"
176
+ import { Effect, Schema } from "effect";
177
177
 
178
178
  class ParseError extends Schema.TaggedErrorClass<ParseError>()("ParseError", {
179
- cause: Schema.Defect()
179
+ cause: Schema.Defect(),
180
180
  }) {}
181
181
 
182
182
  const parseJson = (input: string) =>
183
183
  Effect.try({
184
184
  try: () => JSON.parse(input),
185
- catch: (cause) => ParseError.make({ cause })
186
- })
185
+ catch: (cause) => ParseError.make({ cause }),
186
+ });
187
187
  ```
188
188
 
189
189
  ### `Effect.tryPromise`
@@ -194,17 +194,17 @@ opaque cause when it is diagnostically useful. Compose Effect-native APIs
194
194
  directly instead of converting them through Promise.
195
195
 
196
196
  ```ts
197
- import { Effect, Schema } from "effect"
197
+ import { Effect, Schema } from "effect";
198
198
 
199
199
  class FetchError extends Schema.TaggedErrorClass<FetchError>()("FetchError", {
200
- cause: Schema.Defect()
200
+ cause: Schema.Defect(),
201
201
  }) {}
202
202
 
203
203
  const fetchText = (url: string) =>
204
204
  Effect.tryPromise({
205
205
  try: () => fetch(url).then((response) => response.text()),
206
- catch: (cause) => FetchError.make({ cause })
207
- })
206
+ catch: (cause) => FetchError.make({ cause }),
207
+ });
208
208
  ```
209
209
 
210
210
  Preferred rule:
@@ -224,9 +224,7 @@ The repo uses `map`, `flatMap`, and `tap` constantly for small local transformat
224
224
  Use to transform successful values.
225
225
 
226
226
  ```ts
227
- const userName = loadUser("u_123").pipe(
228
- Effect.map((user) => user.name)
229
- )
227
+ const userName = loadUser("u_123").pipe(Effect.map((user) => user.name));
230
228
  ```
231
229
 
232
230
  ### `Effect.flatMap`
@@ -234,9 +232,7 @@ const userName = loadUser("u_123").pipe(
234
232
  Use when the next step returns another `Effect`.
235
233
 
236
234
  ```ts
237
- const result = loadUser("u_123").pipe(
238
- Effect.flatMap((user) => saveAudit(user.id))
239
- )
235
+ const result = loadUser("u_123").pipe(Effect.flatMap((user) => saveAudit(user.id)));
240
236
  ```
241
237
 
242
238
  ### `Effect.tap`
@@ -245,8 +241,8 @@ Use for side effects that should preserve the main value.
245
241
 
246
242
  ```ts
247
243
  const result = loadUser("u_123").pipe(
248
- Effect.tap((user) => Effect.logDebug("loaded user", { userId: user.id }))
249
- )
244
+ Effect.tap((user) => Effect.logDebug("loaded user", { userId: user.id })),
245
+ );
250
246
  ```
251
247
 
252
248
  Preferred rule:
@@ -264,27 +260,23 @@ Repo style is:
264
260
  ### Access services in implementations
265
261
 
266
262
  ```ts
267
- const loadUser = Effect.fn("loadUser")(function*(userId: string) {
268
- const repo = yield* UserRepo
269
- return yield* repo.getById(userId)
270
- })
263
+ const loadUser = Effect.fn("loadUser")(function* (userId: string) {
264
+ const repo = yield* UserRepo;
265
+ return yield* repo.getById(userId);
266
+ });
271
267
  ```
272
268
 
273
269
  or:
274
270
 
275
271
  ```ts
276
272
  const loadUser = (userId: string) =>
277
- Effect.service(UserRepo).pipe(
278
- Effect.flatMap((repo) => repo.getById(userId))
279
- )
273
+ Effect.service(UserRepo).pipe(Effect.flatMap((repo) => repo.getById(userId)));
280
274
  ```
281
275
 
282
276
  ### Provide at the edge
283
277
 
284
278
  ```ts
285
- const program = loadUser("u_123").pipe(
286
- Effect.provide(AppLayer)
287
- )
279
+ const program = loadUser("u_123").pipe(Effect.provide(AppLayer));
288
280
  ```
289
281
 
290
282
  Use `provideService` and `provideServiceEffect` for targeted overrides, especially in tests or framework boundaries.
@@ -309,9 +301,7 @@ Common repo patterns:
309
301
  Use for targeted typed recovery.
310
302
 
311
303
  ```ts
312
- const safe = loadUser("u_123").pipe(
313
- Effect.catchTag("UserNotFound", () => Effect.succeed(null))
314
- )
304
+ const safe = loadUser("u_123").pipe(Effect.catchTag("UserNotFound", () => Effect.succeed(null)));
315
305
  ```
316
306
 
317
307
  ### `Effect.match`
@@ -322,9 +312,9 @@ Use when the caller wants a value either way.
322
312
  const result = loadUser("u_123").pipe(
323
313
  Effect.match({
324
314
  onFailure: () => null,
325
- onSuccess: (user) => user
326
- })
327
- )
315
+ onSuccess: (user) => user,
316
+ }),
317
+ );
328
318
  ```
329
319
 
330
320
  For deeper guidance, see `./references/guide-error-handling.md`.
@@ -338,10 +328,7 @@ One of the strongest repo patterns is explicit resource ownership.
338
328
  Use for resources that must be cleaned up.
339
329
 
340
330
  ```ts
341
- const connection = Effect.acquireRelease(
342
- openConnection,
343
- (conn) => closeConnection(conn)
344
- )
331
+ const connection = Effect.acquireRelease(openConnection, (conn) => closeConnection(conn));
345
332
  ```
346
333
 
347
334
  ### `Effect.scoped`
@@ -350,11 +337,11 @@ Use when a workflow consumes scoped resources and should tie cleanup to scope li
350
337
 
351
338
  ```ts
352
339
  const program = Effect.scoped(
353
- Effect.gen(function*() {
354
- const conn = yield* connection
355
- return yield* conn.query("select 1")
356
- })
357
- )
340
+ Effect.gen(function* () {
341
+ const conn = yield* connection;
342
+ return yield* conn.query("select 1");
343
+ }),
344
+ );
358
345
  ```
359
346
 
360
347
  Repo examples:
@@ -59,34 +59,34 @@ Repo references:
59
59
  Example:
60
60
 
61
61
  ```ts
62
- import { Effect, Schema } from "effect"
62
+ import { Effect, Schema } from "effect";
63
63
 
64
- class InvalidPayload extends Schema.TaggedErrorClass<InvalidPayload>()(
65
- "InvalidPayload",
66
- {
67
- field: Schema.String,
68
- reason: Schema.String
69
- }
70
- ) {}
64
+ class InvalidPayload extends Schema.TaggedErrorClass<InvalidPayload>()("InvalidPayload", {
65
+ field: Schema.String,
66
+ reason: Schema.String,
67
+ }) {}
71
68
 
72
69
  const validate = Effect.fail(
73
70
  InvalidPayload.make({
74
71
  field: "email",
75
- reason: "missing"
76
- })
77
- )
72
+ reason: "missing",
73
+ }),
74
+ );
78
75
  ```
79
76
 
80
77
  Expected application and service failures belong in the typed error channel.
81
78
  In generators, use an explicit control-flow exit when failing:
82
79
 
83
80
  ```ts
84
- return yield* Effect.fail(
85
- InvalidPayload.make({
86
- field: "email",
87
- reason: "missing"
88
- })
89
- )
81
+ return (
82
+ yield *
83
+ Effect.fail(
84
+ InvalidPayload.make({
85
+ field: "email",
86
+ reason: "missing",
87
+ }),
88
+ )
89
+ );
90
90
  ```
91
91
 
92
92
  Use this when:
@@ -128,18 +128,17 @@ Repo reference:
128
128
  Example:
129
129
 
130
130
  ```ts
131
- import { Data, Effect } from "effect"
131
+ import { Data, Effect } from "effect";
132
132
 
133
133
  class UserNotFound extends Data.TaggedError("UserNotFound")<{
134
- readonly userId: string
134
+ readonly userId: string;
135
135
  }> {}
136
136
 
137
- const loadUser = (userId: string) =>
138
- Effect.fail(new UserNotFound({ userId }))
137
+ const loadUser = (userId: string) => Effect.fail(new UserNotFound({ userId }));
139
138
 
140
- const program = Effect.gen(function*() {
141
- yield* loadUser("u_123")
142
- })
139
+ const program = Effect.gen(function* () {
140
+ yield* loadUser("u_123");
141
+ });
143
142
  ```
144
143
 
145
144
  ## When To Prefer `Data.TaggedError` vs `Schema.TaggedErrorClass`
@@ -173,14 +172,14 @@ Repo references:
173
172
  Example:
174
173
 
175
174
  ```ts
176
- import { Effect, Schema } from "effect"
175
+ import { Effect, Schema } from "effect";
177
176
 
178
177
  const UserPayload = Schema.Struct({
179
178
  id: Schema.String,
180
- email: Schema.String
181
- })
179
+ email: Schema.String,
180
+ });
182
181
 
183
- const decodeUser = Schema.decodeUnknownEffect(UserPayload)
182
+ const decodeUser = Schema.decodeUnknownEffect(UserPayload);
184
183
  ```
185
184
 
186
185
  This gives you:
@@ -195,23 +194,23 @@ For application code, it is often better to convert `SchemaError` into a domain
195
194
  Example:
196
195
 
197
196
  ```ts
198
- import { Data, Effect, Schema } from "effect"
197
+ import { Data, Effect, Schema } from "effect";
199
198
 
200
199
  class InvalidRequestBody extends Data.TaggedError("InvalidRequestBody")<{
201
- readonly message: string
200
+ readonly message: string;
202
201
  }> {}
203
202
 
204
203
  const UserPayload = Schema.Struct({
205
204
  id: Schema.String,
206
- email: Schema.String
207
- })
205
+ email: Schema.String,
206
+ });
208
207
 
209
208
  const decodeUser = (input: unknown) =>
210
209
  Schema.decodeUnknownEffect(UserPayload)(input).pipe(
211
210
  Effect.catchTag("SchemaError", (error) =>
212
- Effect.fail(new InvalidRequestBody({ message: error.message }))
213
- )
214
- )
211
+ Effect.fail(new InvalidRequestBody({ message: error.message })),
212
+ ),
213
+ );
215
214
  ```
216
215
 
217
216
  Why:
@@ -262,38 +261,32 @@ Prefer using:
262
261
  Example:
263
262
 
264
263
  ```ts
265
- import { Effect, Schema } from "effect"
264
+ import { Effect, Schema } from "effect";
266
265
 
267
- class TodoStorageError extends Schema.TaggedErrorClass<TodoStorageError>()(
268
- "TodoStorageError",
269
- {
270
- operation: Schema.String,
271
- cause: Schema.Defect()
272
- }
273
- ) {}
266
+ class TodoStorageError extends Schema.TaggedErrorClass<TodoStorageError>()("TodoStorageError", {
267
+ operation: Schema.String,
268
+ cause: Schema.Defect(),
269
+ }) {}
274
270
 
275
271
  const makeStorageError = (operation: string) => (cause: unknown) =>
276
272
  TodoStorageError.make({
277
273
  operation,
278
- cause
279
- })
274
+ cause,
275
+ });
280
276
 
281
277
  const loadTodo = (id: number) =>
282
278
  Effect.try({
283
279
  try: () => someLibraryCall(id),
284
- catch: makeStorageError("loadTodo")
285
- })
280
+ catch: makeStorageError("loadTodo"),
281
+ });
286
282
  ```
287
283
 
288
284
  When stack preservation matters in the encoded schema, prefer:
289
285
 
290
286
  ```ts
291
- class WorkerFailure extends Schema.TaggedErrorClass<WorkerFailure>()(
292
- "WorkerFailure",
293
- {
294
- cause: Schema.Defect({ includeStack: true })
295
- }
296
- ) {}
287
+ class WorkerFailure extends Schema.TaggedErrorClass<WorkerFailure>()("WorkerFailure", {
288
+ cause: Schema.Defect({ includeStack: true }),
289
+ }) {}
297
290
  ```
298
291
 
299
292
  ### Why This Is Preferred
@@ -332,8 +325,8 @@ Bad:
332
325
  const loadTodo = (id: number) =>
333
326
  Effect.try({
334
327
  try: () => someLibraryCall(id),
335
- catch: (cause) => cause as Error
336
- })
328
+ catch: (cause) => cause as Error,
329
+ });
337
330
  ```
338
331
 
339
332
  Why this is bad:
@@ -373,10 +366,8 @@ Example:
373
366
 
374
367
  ```ts
375
368
  const recovered = program.pipe(
376
- Effect.catchTag("UserNotFound", (error) =>
377
- Effect.succeed({ id: error.userId, guest: true })
378
- )
379
- )
369
+ Effect.catchTag("UserNotFound", (error) => Effect.succeed({ id: error.userId, guest: true })),
370
+ );
380
371
  ```
381
372
 
382
373
  ### Handle several tagged errors with `Effect.catchTags`
@@ -387,9 +378,9 @@ Use `catchTags` when multiple domain errors should be handled together.
387
378
  const recovered = program.pipe(
388
379
  Effect.catchTags({
389
380
  UserNotFound: () => Effect.succeed(null),
390
- InvalidPayload: (error) => Effect.succeed({ error: error.reason })
391
- })
392
- )
381
+ InvalidPayload: (error) => Effect.succeed({ error: error.reason }),
382
+ }),
383
+ );
393
384
  ```
394
385
 
395
386
  ### Handle predicate-based subsets with `Effect.catchIf`
@@ -404,9 +395,9 @@ Use `match` when you want to fully fold the typed error channel into a success v
404
395
  const outcome = program.pipe(
405
396
  Effect.match({
406
397
  onFailure: (error) => ({ ok: false as const, error }),
407
- onSuccess: (value) => ({ ok: true as const, value })
408
- })
409
- )
398
+ onSuccess: (value) => ({ ok: true as const, value }),
399
+ }),
400
+ );
410
401
  ```
411
402
 
412
403
  ## Handling Defects
@@ -439,17 +430,17 @@ Use defects for:
439
430
  Use `sandbox` to expose `Cause<E>` in the error channel.
440
431
 
441
432
  ```ts
442
- import { Cause, Effect } from "effect"
433
+ import { Cause, Effect } from "effect";
443
434
 
444
435
  const diagnosed = program.pipe(
445
436
  Effect.sandbox,
446
437
  Effect.catchCause((cause) => {
447
438
  if (Cause.hasDies(cause)) {
448
- return Effect.succeed("defect")
439
+ return Effect.succeed("defect");
449
440
  }
450
- return Effect.failCause(cause)
451
- })
452
- )
441
+ return Effect.failCause(cause);
442
+ }),
443
+ );
453
444
  ```
454
445
 
455
446
  Use `matchCause` or `matchCauseEffect` when you need to distinguish:
@@ -503,11 +494,11 @@ Interrupts signal that the fiber should stop. They should not usually be transla
503
494
  If interrupted work needs special cleanup, use `onInterrupt`.
504
495
 
505
496
  ```ts
506
- import { Console, Effect } from "effect"
497
+ import { Console, Effect } from "effect";
507
498
 
508
499
  const program = longRunningTask.pipe(
509
- Effect.onInterrupt(() => Console.log("cleaning up after interrupt"))
510
- )
500
+ Effect.onInterrupt(() => Console.log("cleaning up after interrupt")),
501
+ );
511
502
  ```
512
503
 
513
504
  ### Use `Cause` inspection when interrupts must be distinguished