@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,87 @@
1
+ # Effect Version And Source Strategy
2
+
3
+ Use this guide before installing Effect packages or relying on exact API names,
4
+ signatures, exports, or source paths.
5
+
6
+ ## Review Baseline
7
+
8
+ This skill was last reviewed on 2026-07-29 against:
9
+
10
+ - `effect@4.0.0-beta.102`
11
+ - matching `4.0.0-beta.102` releases of the v4 `@effect/*` packages
12
+ - canonical source from `Effect-TS/effect`
13
+ - official Effect skill commit
14
+ `a8b6bb40d1d4d550b49c0ff7a624b5e6da500a24`
15
+
16
+ The beta is intentionally volatile. Review metadata records what was checked;
17
+ it does not override the target repository's installed version.
18
+
19
+ ## Determine The Target Version
20
+
21
+ Before editing:
22
+
23
+ 1. Read the target repository's package manifest and lockfile.
24
+ 2. Identify the resolved `effect` version and every installed `@effect/*`
25
+ version.
26
+ 3. Determine whether the repository is on Effect v3 or the v4 beta.
27
+ 4. Keep guidance and source research on that same release line.
28
+
29
+ Do not silently upgrade an existing repository. If its Effect packages are
30
+ misaligned, report the mismatch and treat version alignment as a separate
31
+ change.
32
+
33
+ ## Installing Effect v4
34
+
35
+ For a new Effect v4 installation:
36
+
37
+ 1. Resolve the current `effect@beta` version from the package registry.
38
+ 2. Install `effect` at that exact version.
39
+ 3. Install only the runtime and integration packages the task requires.
40
+ 4. Pin every v4 `@effect/*` package to the same exact beta version.
41
+
42
+ `effect@latest` is the stable v3 release line until Effect v4 leaves beta.
43
+ Do not use generic v3 packages such as `@effect/platform` with Effect v4.
44
+ Effect v4 places many capabilities under `effect/unstable/*`; install separate
45
+ packages only for runtime-, provider-, driver-, or tool-specific integrations.
46
+
47
+ ## Source Research
48
+
49
+ Use this order:
50
+
51
+ 1. Search the installed package declarations and source in the target
52
+ repository.
53
+ 2. Check the target repository for established usage of the same API.
54
+ 3. Inspect the canonical `Effect-TS/effect` source at the matching tag or
55
+ commit.
56
+ 4. Use current `main` only when intentionally researching a future upgrade.
57
+
58
+ A project-local checkout at canonical `Effect-TS/effect` source may be used when the repository
59
+ already provides one, but it is optional. Never pause ordinary Effect work to
60
+ force the user to add a subtree, submodule, or clone.
61
+
62
+ ## V4 Migration Checks
63
+
64
+ When adapting older examples, verify them against the installed v4 release.
65
+ Common v4 changes include:
66
+
67
+ - define services with `Context.Service`, not the removed `Context.Tag`,
68
+ `Context.GenericTag`, `Effect.Tag`, or `Effect.Service`
69
+ - prefer yielding the service key directly
70
+ - use `Effect.catch`, `Effect.catchCause`, and `Effect.catchFilter` rather than
71
+ the renamed v3 `catchAll`, `catchAllCause`, and `catchSome`
72
+ - use `Context` plus `Effect.runForkWith` rather than the removed generic
73
+ `Runtime<R>` and `Effect.runtime`
74
+ - import consolidated capabilities from `effect/unstable/*` rather than
75
+ obsolete standalone packages
76
+ - verify Schema class, union, decoding, and transformation signatures because
77
+ they changed throughout the v4 beta
78
+
79
+ ## Completion Check
80
+
81
+ Before completing version-sensitive work:
82
+
83
+ - every code example uses APIs present in the target's resolved packages
84
+ - every installed Effect v4 package resolves to the same beta
85
+ - no source path is assumed solely because it appears in this skill
86
+ - current-main guidance is not presented as installed-version behavior
87
+
@@ -0,0 +1,372 @@
1
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
2
+ import { Argument, CliError, Command as CliCommand, Flag } from "effect/unstable/cli";
3
+ import { Effect, Result } from "effect";
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
+ import {
16
+ addCatalogSource,
17
+ listCatalogSources,
18
+ removeCatalogEntry,
19
+ showCatalogSource,
20
+ } from "../catalog-manager.ts";
21
+ import {
22
+ addSkills,
23
+ chooseSkillsToAdd,
24
+ chooseSkillsToRemove,
25
+ initProject,
26
+ listSkills,
27
+ removeSkills,
28
+ showDashboard,
29
+ showSkill,
30
+ } from "../skill-manager.ts";
31
+
32
+ const projectFlags = {
33
+ manifest: Flag.string("manifest").pipe(
34
+ Flag.withDefault(DEFAULT_MANIFEST),
35
+ Flag.withDescription("Project-relative manifest path."),
36
+ ),
37
+ projectDir: Flag.string("project-dir").pipe(
38
+ Flag.withDefault("."),
39
+ Flag.withDescription("Project directory (defaults to the current directory)."),
40
+ ),
41
+ };
42
+
43
+ const initCommand = CliCommand.make("init", projectFlags, ({ manifest, projectDir }) =>
44
+ initProject({ manifestPath: manifest, projectDir }),
45
+ ).pipe(CliCommand.withDescription("Initialize skill management in this project."));
46
+
47
+ const addCommand = CliCommand.make(
48
+ "add",
49
+ {
50
+ skills: Argument.string("skills").pipe(Argument.variadic()),
51
+ noApply: Flag.boolean("no-apply").pipe(
52
+ Flag.withDescription("Update the manifest without installing yet."),
53
+ ),
54
+ ...projectFlags,
55
+ },
56
+ ({ skills, noApply, manifest, projectDir }) =>
57
+ skills.length === 0
58
+ ? chooseSkillsToAdd({ apply: !noApply, manifestPath: manifest, projectDir })
59
+ : addSkills(skills, { apply: !noApply, manifestPath: manifest, projectDir }),
60
+ ).pipe(CliCommand.withDescription("Select and install one or more approved skills."));
61
+
62
+ const removeCommand = CliCommand.make(
63
+ "remove",
64
+ {
65
+ skills: Argument.string("skills").pipe(Argument.variadic()),
66
+ noApply: Flag.boolean("no-apply").pipe(
67
+ Flag.withDescription("Update the manifest without uninstalling yet."),
68
+ ),
69
+ ...projectFlags,
70
+ },
71
+ ({ skills, noApply, manifest, projectDir }) =>
72
+ skills.length === 0
73
+ ? chooseSkillsToRemove({ apply: !noApply, manifestPath: manifest, projectDir })
74
+ : removeSkills(skills, { apply: !noApply, manifestPath: manifest, projectDir }),
75
+ ).pipe(CliCommand.withDescription("Deselect and uninstall one or more skills."));
76
+
77
+ const listCommand = CliCommand.make(
78
+ "list",
79
+ {
80
+ all: Flag.boolean("all").pipe(Flag.withDescription("Include unselected skills.")),
81
+ ...projectFlags,
82
+ },
83
+ ({ all, manifest, projectDir }) =>
84
+ listSkills({ all, manifestPath: manifest, projectDir }),
85
+ ).pipe(CliCommand.withDescription("List selected skills; use --all to browse the catalog."));
86
+
87
+ const searchCommand = CliCommand.make(
88
+ "search",
89
+ { query: Argument.string("query").pipe(Argument.variadic({ min: 1 })), ...projectFlags },
90
+ ({ query, manifest, projectDir }) =>
91
+ listSkills({ all: true, query: query.join(" "), manifestPath: manifest, projectDir }),
92
+ ).pipe(CliCommand.withDescription("Search approved skill names and descriptions."));
93
+
94
+ const infoCommand = CliCommand.make(
95
+ "info",
96
+ { skill: Argument.string("skill") },
97
+ ({ skill }) => showSkill(skill),
98
+ ).pipe(CliCommand.withDescription("Show provenance and details for an approved skill."));
99
+
100
+ const planCommand = CliCommand.make(
101
+ "plan",
102
+ {
103
+ locked: Flag.boolean("locked"),
104
+ lockfile: Flag.string("lockfile").pipe(Flag.withDefault("dev-kit.lock.json")),
105
+ manifest: Flag.string("manifest").pipe(Flag.withDefault(DEFAULT_MANIFEST)),
106
+ projectDir: Flag.string("project-dir").pipe(Flag.withDefault(".")),
107
+ },
108
+ ({ locked, lockfile, manifest, projectDir }) =>
109
+ runProjectSkillPlan({
110
+ dryRun: true,
111
+ locked,
112
+ lockfilePath: lockfile,
113
+ manifestPath: manifest,
114
+ projectDir,
115
+ }),
116
+ ).pipe(CliCommand.withDescription("Plan ownership-safe project skill changes without writing files."));
117
+
118
+ const applyCommand = CliCommand.make(
119
+ "apply",
120
+ {
121
+ locked: Flag.boolean("locked"),
122
+ lockfile: Flag.string("lockfile").pipe(Flag.withDefault("dev-kit.lock.json")),
123
+ manifest: Flag.string("manifest").pipe(Flag.withDefault(DEFAULT_MANIFEST)),
124
+ projectDir: Flag.string("project-dir").pipe(Flag.withDefault(".")),
125
+ },
126
+ ({ locked, lockfile, manifest, projectDir }) =>
127
+ runProjectSkillPlan({
128
+ locked,
129
+ lockfilePath: lockfile,
130
+ manifestPath: manifest,
131
+ projectDir,
132
+ }),
133
+ ).pipe(CliCommand.withDescription("Apply ownership-safe project skill changes and update the lock."));
134
+
135
+ const syncCommand = CliCommand.make(
136
+ "sync",
137
+ {
138
+ locked: Flag.boolean("locked"),
139
+ lockfile: Flag.string("lockfile").pipe(Flag.withDefault("dev-kit.lock.json")),
140
+ ...projectFlags,
141
+ },
142
+ ({ locked, lockfile, manifest, projectDir }) =>
143
+ runProjectSkillPlan({ locked, lockfilePath: lockfile, manifestPath: manifest, projectDir }),
144
+ ).pipe(CliCommand.withDescription("Install the skills selected in dev-kit.jsonc."));
145
+
146
+ const statusCommand = CliCommand.make(
147
+ "status",
148
+ {
149
+ lockfile: Flag.string("lockfile").pipe(Flag.withDefault("dev-kit.lock.json")),
150
+ ...projectFlags,
151
+ },
152
+ ({ lockfile, manifest, projectDir }) =>
153
+ runProjectSkillPlan({
154
+ dryRun: true,
155
+ lockfilePath: lockfile,
156
+ manifestPath: manifest,
157
+ projectDir,
158
+ }),
159
+ ).pipe(CliCommand.withDescription("Check whether selected skills match the project."));
160
+
161
+ const gitignoreCommand = CliCommand.make(
162
+ "gitignore",
163
+ {
164
+ dryRun: Flag.boolean("dry-run"),
165
+ projectDir: Flag.string("project-dir").pipe(Flag.withDefault(".")),
166
+ },
167
+ ({ dryRun, projectDir }) => patchProjectGitignore({ dryRun, projectDir }),
168
+ ).pipe(
169
+ CliCommand.withDescription(
170
+ "Idempotently add .repos/ and .dev-kit/ to the project .gitignore.",
171
+ ),
172
+ );
173
+
174
+ const tsgoPatchCommand = CliCommand.make(
175
+ "patch",
176
+ {
177
+ dryRun: Flag.boolean("dry-run"),
178
+ force: Flag.boolean("force"),
179
+ projectDir: Flag.string("project-dir").pipe(Flag.withDefault(".")),
180
+ typescriptPackage: Flag.string("typescript-package").pipe(
181
+ Flag.withDefault("typescript"),
182
+ ),
183
+ },
184
+ ({ dryRun, force, projectDir, typescriptPackage }) =>
185
+ patchEffectTsgo({ dryRun, force, projectDir, typescriptPackage }),
186
+ ).pipe(
187
+ CliCommand.withDescription(
188
+ "Patch the project-local native TypeScript compiler with the pinned Effect language service.",
189
+ ),
190
+ );
191
+
192
+ const tsgoCommand = CliCommand.make("tsgo").pipe(
193
+ CliCommand.withDescription("Manage the pinned Effect TypeScript-Go integration."),
194
+ CliCommand.withSubcommands([tsgoPatchCommand] as const),
195
+ );
196
+
197
+ const effectSyncCommand = CliCommand.make(
198
+ "sync",
199
+ {
200
+ dryRun: Flag.boolean("dry-run"),
201
+ packageName: Flag.string("package").pipe(Flag.withDefault("effect")),
202
+ path: Flag.string("path").pipe(Flag.withDefault(".repos/effect")),
203
+ projectDir: Flag.string("project-dir").pipe(Flag.withDefault(".")),
204
+ repository: Flag.string("repository").pipe(
205
+ Flag.withDefault("https://github.com/Effect-TS/effect.git"),
206
+ ),
207
+ },
208
+ ({ dryRun, packageName, path, projectDir, repository }) =>
209
+ syncEffectSource({
210
+ dryRun,
211
+ packageName,
212
+ path,
213
+ projectDir,
214
+ repository,
215
+ }),
216
+ ).pipe(
217
+ CliCommand.withDescription(
218
+ "Sync a detached Effect source checkout to the installed package version.",
219
+ ),
220
+ );
221
+
222
+ const effectCommand = CliCommand.make("effect").pipe(
223
+ CliCommand.withDescription("Manage the version-matched Effect source checkout."),
224
+ CliCommand.withSubcommands([effectSyncCommand] as const),
225
+ );
226
+
227
+ const catalogFilesFlags = {
228
+ lockfile: Flag.string("lockfile").pipe(
229
+ Flag.withDefault("skill-sources.lock.json"),
230
+ Flag.withDescription("Approved catalog snapshot path."),
231
+ ),
232
+ repoDir: Flag.string("repo-dir").pipe(
233
+ Flag.withDefault("."),
234
+ Flag.withDescription("Catalog repository directory."),
235
+ ),
236
+ sources: Flag.string("sources").pipe(
237
+ Flag.withDefault("skill-sources.jsonc"),
238
+ Flag.withDescription("Authored source manifest path."),
239
+ ),
240
+ };
241
+
242
+ const catalogAddCommand = CliCommand.make(
243
+ "add",
244
+ {
245
+ repository: Argument.string("repository"),
246
+ all: Flag.boolean("all").pipe(
247
+ Flag.withDescription("Approve every skill discovered in this snapshot."),
248
+ ),
249
+ 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.")),
253
+ skill: Flag.string("skill").pipe(
254
+ Flag.atLeast(0),
255
+ Flag.withDescription("Approve one skill; repeat for several."),
256
+ ),
257
+ skillsPath: Flag.string("skills-path").pipe(
258
+ Flag.withDefault(""),
259
+ Flag.withDescription("Repository-relative directory containing skills."),
260
+ ),
261
+ stripFrontmatter: Flag.string("strip-frontmatter").pipe(
262
+ Flag.atLeast(0),
263
+ Flag.withDescription("Remove an upstream frontmatter key; repeat for several."),
264
+ ),
265
+ ...catalogFilesFlags,
266
+ },
267
+ ({ repository, all, dryRun, id, license, ref, skill, skillsPath, stripFrontmatter, lockfile, repoDir, sources }) =>
268
+ addCatalogSource({
269
+ repository,
270
+ all,
271
+ dryRun,
272
+ skills: skill,
273
+ stripFrontmatter,
274
+ lockfilePath: lockfile,
275
+ repoDir,
276
+ sourcesPath: sources,
277
+ ...(id ? { id } : {}),
278
+ ...(license ? { licensePath: license } : {}),
279
+ ...(ref ? { ref } : {}),
280
+ ...(skillsPath ? { skillsPath } : {}),
281
+ }),
282
+ ).pipe(CliCommand.withDescription("Inspect a repository and approve selected skills."));
283
+
284
+ const catalogListCommand = CliCommand.make(
285
+ "list",
286
+ catalogFilesFlags,
287
+ ({ lockfile, repoDir, sources }) =>
288
+ listCatalogSources({ lockfilePath: lockfile, repoDir, sourcesPath: sources }),
289
+ ).pipe(CliCommand.withDescription("List approved upstream repositories."));
290
+
291
+ const catalogInfoCommand = CliCommand.make(
292
+ "info",
293
+ { source: Argument.string("source"), ...catalogFilesFlags },
294
+ ({ source, lockfile, repoDir, sources }) =>
295
+ showCatalogSource(source, { lockfilePath: lockfile, repoDir, sourcesPath: sources }),
296
+ ).pipe(CliCommand.withDescription("Show an approved source and its skills."));
297
+
298
+ const catalogRemoveCommand = CliCommand.make(
299
+ "remove",
300
+ {
301
+ entry: Argument.string("source-or-skill"),
302
+ dryRun: Flag.boolean("dry-run").pipe(Flag.withDescription("Preview without writing.")),
303
+ yes: Flag.boolean("yes").pipe(Flag.withDescription("Confirm catalog revocation.")),
304
+ ...catalogFilesFlags,
305
+ },
306
+ ({ entry, dryRun, yes, lockfile, repoDir, sources }) =>
307
+ removeCatalogEntry(entry, { dryRun, yes, lockfilePath: lockfile, repoDir, sourcesPath: sources }),
308
+ ).pipe(CliCommand.withDescription("Revoke an approved source or individual skill."));
309
+
310
+ const catalogRefreshCommand = CliCommand.make(
311
+ "refresh",
312
+ {
313
+ dryRun: Flag.boolean("dry-run").pipe(Flag.withDescription("Preview without writing.")),
314
+ locked: Flag.boolean("locked").pipe(Flag.withHidden),
315
+ ...catalogFilesFlags,
316
+ },
317
+ ({ dryRun, locked, lockfile, repoDir, sources }) =>
318
+ refreshSkillCatalog({ dryRun, locked, lockfilePath: lockfile, repoDir, sourcesPath: sources }),
319
+ ).pipe(CliCommand.withDescription("Approve the current upstream refs as an exact catalog snapshot."));
320
+
321
+ const catalogVerifyCommand = CliCommand.make(
322
+ "verify",
323
+ catalogFilesFlags,
324
+ ({ lockfile, repoDir, sources }) =>
325
+ refreshSkillCatalog({ locked: true, lockfilePath: lockfile, repoDir, sourcesPath: sources }),
326
+ ).pipe(CliCommand.withDescription("Verify the committed catalog without advancing refs."));
327
+
328
+ const catalogCommand = CliCommand.make("catalog").pipe(
329
+ CliCommand.withDescription("Maintain the approved upstream catalog."),
330
+ CliCommand.withSubcommands([
331
+ catalogAddCommand,
332
+ catalogRemoveCommand,
333
+ catalogListCommand,
334
+ catalogInfoCommand,
335
+ catalogRefreshCommand,
336
+ catalogVerifyCommand,
337
+ ] as const),
338
+ );
339
+
340
+ const command = CliCommand.make("dev-kit", projectFlags, ({ manifest, projectDir }) =>
341
+ showDashboard({ manifestPath: manifest, projectDir }),
342
+ ).pipe(
343
+ CliCommand.withDescription("Your approved skill catalog for coding agents."),
344
+ CliCommand.withSubcommands([
345
+ {
346
+ group: "Skills",
347
+ commands: [initCommand, addCommand, removeCommand, listCommand, searchCommand, infoCommand],
348
+ },
349
+ { group: "Project", commands: [statusCommand, syncCommand] },
350
+ {
351
+ group: "Advanced",
352
+ commands: [planCommand, applyCommand, gitignoreCommand, effectCommand, tsgoCommand, catalogCommand],
353
+ },
354
+ ] as const),
355
+ );
356
+
357
+ const program = CliCommand.run(command, { version: DEV_KIT_VERSION }).pipe(
358
+ Effect.catchFilter(
359
+ (error) =>
360
+ CliError.isCliError(error) && error._tag === "ShowHelp"
361
+ ? Result.fail(error)
362
+ : Result.succeed(error),
363
+ (error) =>
364
+ printError(error instanceof Error ? error.message : String(error)).pipe(
365
+ Effect.andThen(Effect.fail(error)),
366
+ ),
367
+ ),
368
+ Effect.scoped,
369
+ Effect.provide(NodeServices.layer),
370
+ );
371
+
372
+ NodeRuntime.runMain(program, { disableErrorReporting: true });