@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
@@ -46,8 +46,8 @@ repository explicitly owns that workflow.
46
46
  Preferred imports for Effect tests:
47
47
 
48
48
  ```ts
49
- import { assert, describe, it, layer } from "@effect/vitest"
50
- import { Effect } from "effect"
49
+ import { assert, describe, it, layer } from "@effect/vitest";
50
+ import { Effect } from "effect";
51
51
  ```
52
52
 
53
53
  `@effect/vitest` re-exports Vitest, so it is the normal entrypoint for test APIs in an Effect codebase.
@@ -74,15 +74,15 @@ This comes directly from the internal implementation.
74
74
  Example:
75
75
 
76
76
  ```ts
77
- import { assert, it } from "@effect/vitest"
78
- import { Effect } from "effect"
77
+ import { assert, it } from "@effect/vitest";
78
+ import { Effect } from "effect";
79
79
 
80
80
  it.effect("loads a user", () =>
81
- Effect.gen(function*() {
82
- yield* Effect.void
83
- assert.isTrue(true)
84
- })
85
- )
81
+ Effect.gen(function* () {
82
+ yield* Effect.void;
83
+ assert.isTrue(true);
84
+ }),
85
+ );
86
86
  ```
87
87
 
88
88
  Use `it.effect` when:
@@ -100,10 +100,10 @@ Example:
100
100
 
101
101
  ```ts
102
102
  it.live("uses live services", () =>
103
- Effect.gen(function*() {
104
- yield* Effect.void
105
- })
106
- )
103
+ Effect.gen(function* () {
104
+ yield* Effect.void;
105
+ }),
106
+ );
107
107
  ```
108
108
 
109
109
  Use `it.live` when:
@@ -123,11 +123,11 @@ Preferred pattern:
123
123
 
124
124
  ```ts
125
125
  it.effect("does something", () =>
126
- Effect.gen(function*() {
127
- const value = yield* someEffect
128
- assert.strictEqual(value, 1)
129
- })
130
- )
126
+ Effect.gen(function* () {
127
+ const value = yield* someEffect;
128
+ assert.strictEqual(value, 1);
129
+ }),
130
+ );
131
131
  ```
132
132
 
133
133
  Prefer `Effect.gen` inside `it.effect` and `it.live` for readability.
@@ -147,9 +147,9 @@ The canonical source also uses `expect` in some tests, but for skill guidance pr
147
147
  Examples:
148
148
 
149
149
  ```ts
150
- assert.isTrue(value === 1)
151
- assert.strictEqual(a + b, b + a)
152
- assert.include(text, substring)
150
+ assert.isTrue(value === 1);
151
+ assert.strictEqual(a + b, b + a);
152
+ assert.include(text, substring);
153
153
  ```
154
154
 
155
155
  ## Test Context
@@ -160,12 +160,12 @@ Example:
160
160
 
161
161
  ```ts
162
162
  it.effect("uses context", (ctx) =>
163
- Effect.gen(function*() {
163
+ Effect.gen(function* () {
164
164
  ctx.onTestFailed(() => {
165
165
  // cleanup or diagnostics
166
- })
167
- })
168
- )
166
+ });
167
+ }),
168
+ );
169
169
  ```
170
170
 
171
171
  Use this when you need:
@@ -201,12 +201,12 @@ Use `it.prop` for non-Effect property tests.
201
201
  Example:
202
202
 
203
203
  ```ts
204
- import { it } from "@effect/vitest"
205
- import { FastCheck } from "effect/testing"
204
+ import { it } from "@effect/vitest";
205
+ import { FastCheck } from "effect/testing";
206
206
 
207
- const realNumber = FastCheck.float({ noNaN: true, noDefaultInfinity: true })
207
+ const realNumber = FastCheck.float({ noNaN: true, noDefaultInfinity: true });
208
208
 
209
- it.prop("symmetry", [realNumber, FastCheck.integer()], ([a, b]) => a + b === b + a)
209
+ it.prop("symmetry", [realNumber, FastCheck.integer()], ([a, b]) => a + b === b + a);
210
210
  ```
211
211
 
212
212
  Important limitation from the internal implementation:
@@ -225,17 +225,17 @@ This is the more powerful property-testing mode for Effect code.
225
225
  Example:
226
226
 
227
227
  ```ts
228
- import { assert, it } from "@effect/vitest"
229
- import { Effect } from "effect"
230
- import { FastCheck } from "effect/testing"
228
+ import { assert, it } from "@effect/vitest";
229
+ import { Effect } from "effect";
230
+ import { FastCheck } from "effect/testing";
231
231
 
232
- const realNumber = FastCheck.float({ noNaN: true, noDefaultInfinity: true })
232
+ const realNumber = FastCheck.float({ noNaN: true, noDefaultInfinity: true });
233
233
 
234
234
  it.effect.prop("symmetry", [realNumber, FastCheck.integer()], ([a, b]) =>
235
- Effect.gen(function*() {
236
- assert.strictEqual(a + b, b + a)
237
- })
238
- )
235
+ Effect.gen(function* () {
236
+ assert.strictEqual(a + b, b + a);
237
+ }),
238
+ );
239
239
  ```
240
240
 
241
241
  Unlike top-level `it.prop`, `it.effect.prop` does support `Schema` inputs by converting them with `Schema.toArbitrary`.
@@ -260,23 +260,23 @@ This is one of the most important `@effect/vitest` features.
260
260
  Example:
261
261
 
262
262
  ```ts
263
- import { describe, it, layer } from "@effect/vitest"
264
- import { Context, Effect, Layer } from "effect"
263
+ import { describe, it, layer } from "@effect/vitest";
264
+ import { Context, Effect, Layer } from "effect";
265
265
 
266
266
  class Foo extends Context.Service<Foo, string>()("Foo") {
267
- static readonly layer = Layer.succeed(Foo)("foo")
267
+ static readonly layer = Layer.succeed(Foo)("foo");
268
268
  }
269
269
 
270
270
  describe("foo", () => {
271
271
  layer(Foo.layer)((it) => {
272
272
  it.effect("gets foo", () =>
273
- Effect.gen(function*() {
274
- const foo = yield* Foo
275
- return foo
276
- })
277
- )
278
- })
279
- })
273
+ Effect.gen(function* () {
274
+ const foo = yield* Foo;
275
+ return foo;
276
+ }),
277
+ );
278
+ });
279
+ });
280
280
  ```
281
281
 
282
282
  What it does internally:
@@ -314,14 +314,12 @@ If multiple tests use the same layer, do not write tests like this:
314
314
 
315
315
  ```ts
316
316
  it.effect("creates and lists todos", () =>
317
- Effect.gen(function*() {
318
- const service = yield* TodoService
319
- yield* service.create("write tests")
320
- yield* service.create("ship feature")
321
- }).pipe(
322
- Effect.provide(TodoService.inMemoryLayer)
323
- )
324
- )
317
+ Effect.gen(function* () {
318
+ const service = yield* TodoService;
319
+ yield* service.create("write tests");
320
+ yield* service.create("ship feature");
321
+ }).pipe(Effect.provide(TodoService.inMemoryLayer)),
322
+ );
325
323
  ```
326
324
 
327
325
  Why this is the wrong pattern:
@@ -338,23 +336,23 @@ Prefer:
338
336
  describe("TodoService", () => {
339
337
  layer(TodoService.inMemoryLayer)((it) => {
340
338
  it.effect("creates and lists todos", () =>
341
- Effect.gen(function*() {
342
- const service = yield* TodoService
343
- yield* service.create("write tests")
344
- yield* service.create("ship feature")
345
- })
346
- )
339
+ Effect.gen(function* () {
340
+ const service = yield* TodoService;
341
+ yield* service.create("write tests");
342
+ yield* service.create("ship feature");
343
+ }),
344
+ );
347
345
 
348
346
  it.effect("updates completion and deletes todos", () =>
349
- Effect.gen(function*() {
350
- const service = yield* TodoService
351
- const todo = yield* service.create("close issue")
352
- yield* service.setCompleted(todo.id, true)
353
- yield* service.remove(todo.id)
354
- })
355
- )
356
- })
357
- })
347
+ Effect.gen(function* () {
348
+ const service = yield* TodoService;
349
+ const todo = yield* service.create("close issue");
350
+ yield* service.setCompleted(todo.id, true);
351
+ yield* service.remove(todo.id);
352
+ }),
353
+ );
354
+ });
355
+ });
358
356
  ```
359
357
 
360
358
  Rule:
@@ -381,14 +379,14 @@ Example:
381
379
  layer(Foo.layer)((it) => {
382
380
  it.layer(Bar.layer)("nested", (it) => {
383
381
  it.effect("gets both", () =>
384
- Effect.gen(function*() {
385
- const foo = yield* Foo
386
- const bar = yield* Bar
387
- return [foo, bar]
388
- })
389
- )
390
- })
391
- })
382
+ Effect.gen(function* () {
383
+ const foo = yield* Foo;
384
+ const bar = yield* Bar;
385
+ return [foo, bar];
386
+ }),
387
+ );
388
+ });
389
+ });
392
390
  ```
393
391
 
394
392
  Important behavior from the implementation:
@@ -476,11 +474,11 @@ It is not needed for ordinary test structure.
476
474
 
477
475
  ```ts
478
476
  it.effect("does work", () =>
479
- Effect.gen(function*() {
480
- const value = yield* Effect.succeed(1)
481
- assert.strictEqual(value, 1)
482
- })
483
- )
477
+ Effect.gen(function* () {
478
+ const value = yield* Effect.succeed(1);
479
+ assert.strictEqual(value, 1);
480
+ }),
481
+ );
484
482
  ```
485
483
 
486
484
  ### Pattern: shared layer for a test group
@@ -488,33 +486,33 @@ it.effect("does work", () =>
488
486
  ```ts
489
487
  layer(AppLayer)("app", (it) => {
490
488
  it.effect("uses app services", () =>
491
- Effect.gen(function*() {
492
- yield* Effect.void
493
- })
494
- )
495
- })
489
+ Effect.gen(function* () {
490
+ yield* Effect.void;
491
+ }),
492
+ );
493
+ });
496
494
  ```
497
495
 
498
496
  ### Pattern: property test with Effect
499
497
 
500
498
  ```ts
501
499
  it.effect.prop("law", [FastCheck.integer()], ([n]) =>
502
- Effect.gen(function*() {
503
- assert.strictEqual(n + 0, n)
504
- })
505
- )
500
+ Effect.gen(function* () {
501
+ assert.strictEqual(n + 0, n);
502
+ }),
503
+ );
506
504
  ```
507
505
 
508
506
  ### Pattern: use `TestClock`
509
507
 
510
508
  ```ts
511
509
  it.effect("uses TestClock", () =>
512
- Effect.gen(function*() {
513
- const fiber = yield* Effect.forkChild(Effect.sleep("1 second"))
514
- yield* TestClock.adjust("1 second")
515
- yield* Fiber.join(fiber)
516
- })
517
- )
510
+ Effect.gen(function* () {
511
+ const fiber = yield* Effect.forkChild(Effect.sleep("1 second"));
512
+ yield* TestClock.adjust("1 second");
513
+ yield* Fiber.join(fiber);
514
+ }),
515
+ );
518
516
  ```
519
517
 
520
518
  ## Anti-Patterns
@@ -49,13 +49,13 @@ domain policy.
49
49
 
50
50
  Choose the Schema adapter that matches the caller's control flow:
51
51
 
52
- | Intent | Adapter |
53
- | --- | --- |
54
- | Boolean type guard | `Schema.is(Model)` |
55
- | Optional tolerant decode | `Schema.decodeUnknownOption(Model)` |
56
- | Typed Effect failure | `Schema.decodeUnknownEffect(Model)` |
57
- | JSON string decode | `Schema.fromJsonString(Model)` |
58
- | Any JSON-compatible value | `Schema.Json` |
52
+ | Intent | Adapter |
53
+ | ------------------------- | ----------------------------------- |
54
+ | Boolean type guard | `Schema.is(Model)` |
55
+ | Optional tolerant decode | `Schema.decodeUnknownOption(Model)` |
56
+ | Typed Effect failure | `Schema.decodeUnknownEffect(Model)` |
57
+ | JSON string decode | `Schema.fromJsonString(Model)` |
58
+ | Any JSON-compatible value | `Schema.Json` |
59
59
 
60
60
  Define small schemas for provider responses, SDK payloads, persisted data, and
61
61
  other structured external values. Decode once in the adapter and return the
@@ -84,4 +84,3 @@ Before completing version-sensitive work:
84
84
  - every installed Effect v4 package resolves to the same beta
85
85
  - no source path is assumed solely because it appears in this skill
86
86
  - current-main guidance is not presented as installed-version behavior
87
-
@@ -1,23 +1,17 @@
1
1
  import { NodeRuntime, NodeServices } from "@effect/platform-node";
2
- import { Argument, CliError, Command as CliCommand, Flag } from "effect/unstable/cli";
3
2
  import { Effect, Result } from "effect";
3
+ import { Argument, CliError, Command as CliCommand, Flag } from "effect/unstable/cli";
4
4
 
5
- import { printError } from "../cli-ui.ts";
6
- import { patchEffectTsgo } from "../effect-tsgo.ts";
7
- import { syncEffectSource } from "../effect-source.ts";
8
- import { patchProjectGitignore } from "../gitignore.ts";
9
- import {
10
- DEFAULT_MANIFEST,
11
- runProjectSkillPlan,
12
- } from "../sync.ts";
13
- import { DEV_KIT_VERSION } from "../tool-metadata.ts";
14
- import { refreshSkillCatalog } from "../vendor.ts";
15
5
  import {
16
6
  addCatalogSource,
17
7
  listCatalogSources,
18
8
  removeCatalogEntry,
19
9
  showCatalogSource,
20
10
  } from "../catalog-manager.ts";
11
+ import { printError } from "../cli-ui.ts";
12
+ import { syncEffectSource } from "../effect-source.ts";
13
+ import { patchEffectTsgo } from "../effect-tsgo.ts";
14
+ import { patchProjectGitignore } from "../gitignore.ts";
21
15
  import {
22
16
  addSkills,
23
17
  chooseSkillsToAdd,
@@ -28,6 +22,9 @@ import {
28
22
  showDashboard,
29
23
  showSkill,
30
24
  } from "../skill-manager.ts";
25
+ import { DEFAULT_MANIFEST, runProjectSkillPlan } from "../sync.ts";
26
+ import { DEV_KIT_VERSION } from "../tool-metadata.ts";
27
+ import { refreshSkillCatalog } from "../vendor.ts";
31
28
 
32
29
  const projectFlags = {
33
30
  manifest: Flag.string("manifest").pipe(
@@ -80,8 +77,7 @@ const listCommand = CliCommand.make(
80
77
  all: Flag.boolean("all").pipe(Flag.withDescription("Include unselected skills.")),
81
78
  ...projectFlags,
82
79
  },
83
- ({ all, manifest, projectDir }) =>
84
- listSkills({ all, manifestPath: manifest, projectDir }),
80
+ ({ all, manifest, projectDir }) => listSkills({ all, manifestPath: manifest, projectDir }),
85
81
  ).pipe(CliCommand.withDescription("List selected skills; use --all to browse the catalog."));
86
82
 
87
83
  const searchCommand = CliCommand.make(
@@ -113,7 +109,9 @@ const planCommand = CliCommand.make(
113
109
  manifestPath: manifest,
114
110
  projectDir,
115
111
  }),
116
- ).pipe(CliCommand.withDescription("Plan ownership-safe project skill changes without writing files."));
112
+ ).pipe(
113
+ CliCommand.withDescription("Plan ownership-safe project skill changes without writing files."),
114
+ );
117
115
 
118
116
  const applyCommand = CliCommand.make(
119
117
  "apply",
@@ -130,7 +128,9 @@ const applyCommand = CliCommand.make(
130
128
  manifestPath: manifest,
131
129
  projectDir,
132
130
  }),
133
- ).pipe(CliCommand.withDescription("Apply ownership-safe project skill changes and update the lock."));
131
+ ).pipe(
132
+ CliCommand.withDescription("Apply ownership-safe project skill changes and update the lock."),
133
+ );
134
134
 
135
135
  const syncCommand = CliCommand.make(
136
136
  "sync",
@@ -166,9 +166,7 @@ const gitignoreCommand = CliCommand.make(
166
166
  },
167
167
  ({ dryRun, projectDir }) => patchProjectGitignore({ dryRun, projectDir }),
168
168
  ).pipe(
169
- CliCommand.withDescription(
170
- "Idempotently add .repos/ and .dev-kit/ to the project .gitignore.",
171
- ),
169
+ CliCommand.withDescription("Idempotently add .repos/ and .dev-kit/ to the project .gitignore."),
172
170
  );
173
171
 
174
172
  const tsgoPatchCommand = CliCommand.make(
@@ -177,9 +175,7 @@ const tsgoPatchCommand = CliCommand.make(
177
175
  dryRun: Flag.boolean("dry-run"),
178
176
  force: Flag.boolean("force"),
179
177
  projectDir: Flag.string("project-dir").pipe(Flag.withDefault(".")),
180
- typescriptPackage: Flag.string("typescript-package").pipe(
181
- Flag.withDefault("typescript"),
182
- ),
178
+ typescriptPackage: Flag.string("typescript-package").pipe(Flag.withDefault("typescript")),
183
179
  },
184
180
  ({ dryRun, force, projectDir, typescriptPackage }) =>
185
181
  patchEffectTsgo({ dryRun, force, projectDir, typescriptPackage }),
@@ -247,9 +243,18 @@ const catalogAddCommand = CliCommand.make(
247
243
  Flag.withDescription("Approve every skill discovered in this snapshot."),
248
244
  ),
249
245
  dryRun: Flag.boolean("dry-run").pipe(Flag.withDescription("Inspect without writing.")),
250
- id: Flag.string("id").pipe(Flag.withDefault(""), Flag.withDescription("Override the inferred source id.")),
251
- license: Flag.string("license").pipe(Flag.withDefault(""), Flag.withDescription("Repository-relative license path.")),
252
- ref: Flag.string("ref").pipe(Flag.withDefault(""), Flag.withDescription("Branch, tag, or commit to track.")),
246
+ id: Flag.string("id").pipe(
247
+ Flag.withDefault(""),
248
+ Flag.withDescription("Override the inferred source id."),
249
+ ),
250
+ license: Flag.string("license").pipe(
251
+ Flag.withDefault(""),
252
+ Flag.withDescription("Repository-relative license path."),
253
+ ),
254
+ ref: Flag.string("ref").pipe(
255
+ Flag.withDefault(""),
256
+ Flag.withDescription("Branch, tag, or commit to track."),
257
+ ),
253
258
  skill: Flag.string("skill").pipe(
254
259
  Flag.atLeast(0),
255
260
  Flag.withDescription("Approve one skill; repeat for several."),
@@ -264,7 +269,20 @@ const catalogAddCommand = CliCommand.make(
264
269
  ),
265
270
  ...catalogFilesFlags,
266
271
  },
267
- ({ repository, all, dryRun, id, license, ref, skill, skillsPath, stripFrontmatter, lockfile, repoDir, sources }) =>
272
+ ({
273
+ repository,
274
+ all,
275
+ dryRun,
276
+ id,
277
+ license,
278
+ ref,
279
+ skill,
280
+ skillsPath,
281
+ stripFrontmatter,
282
+ lockfile,
283
+ repoDir,
284
+ sources,
285
+ }) =>
268
286
  addCatalogSource({
269
287
  repository,
270
288
  all,
@@ -304,7 +322,13 @@ const catalogRemoveCommand = CliCommand.make(
304
322
  ...catalogFilesFlags,
305
323
  },
306
324
  ({ entry, dryRun, yes, lockfile, repoDir, sources }) =>
307
- removeCatalogEntry(entry, { dryRun, yes, lockfilePath: lockfile, repoDir, sourcesPath: sources }),
325
+ removeCatalogEntry(entry, {
326
+ dryRun,
327
+ yes,
328
+ lockfilePath: lockfile,
329
+ repoDir,
330
+ sourcesPath: sources,
331
+ }),
308
332
  ).pipe(CliCommand.withDescription("Revoke an approved source or individual skill."));
309
333
 
310
334
  const catalogRefreshCommand = CliCommand.make(
@@ -316,7 +340,9 @@ const catalogRefreshCommand = CliCommand.make(
316
340
  },
317
341
  ({ dryRun, locked, lockfile, repoDir, sources }) =>
318
342
  refreshSkillCatalog({ dryRun, locked, lockfilePath: lockfile, repoDir, sourcesPath: sources }),
319
- ).pipe(CliCommand.withDescription("Approve the current upstream refs as an exact catalog snapshot."));
343
+ ).pipe(
344
+ CliCommand.withDescription("Approve the current upstream refs as an exact catalog snapshot."),
345
+ );
320
346
 
321
347
  const catalogVerifyCommand = CliCommand.make(
322
348
  "verify",
@@ -349,7 +375,14 @@ const command = CliCommand.make("dev-kit", projectFlags, ({ manifest, projectDir
349
375
  { group: "Project", commands: [statusCommand, syncCommand] },
350
376
  {
351
377
  group: "Advanced",
352
- commands: [planCommand, applyCommand, gitignoreCommand, effectCommand, tsgoCommand, catalogCommand],
378
+ commands: [
379
+ planCommand,
380
+ applyCommand,
381
+ gitignoreCommand,
382
+ effectCommand,
383
+ tsgoCommand,
384
+ catalogCommand,
385
+ ],
353
386
  },
354
387
  ] as const),
355
388
  );