@danieljvdm/dev-kit 0.18.0 → 1.0.1

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 (45) hide show
  1. package/README.md +148 -659
  2. package/package.json +8 -62
  3. package/skill-sources.jsonc +1 -0
  4. package/skill-sources.lock.json +5 -1
  5. package/skills/dev-kit/SKILL.md +42 -212
  6. package/skills/dev-kit/agents/openai.yaml +2 -2
  7. package/skills/dev-kit/references/cloudflare-worker-api.md +37 -0
  8. package/skills/dev-kit/references/default-typescript-repository.md +43 -0
  9. package/skills/dev-kit/references/legacy-eject.md +46 -0
  10. package/skills/dev-kit/references/repository-setup.md +53 -0
  11. package/skills/dev-kit/references/skills.md +35 -0
  12. package/src/bin/dev-kit.ts +142 -127
  13. package/src/eject.ts +715 -0
  14. package/src/legacy-project.ts +67 -0
  15. package/src/oxfmt.ts +1 -4
  16. package/src/oxlint.ts +5 -10
  17. package/src/path-digest.ts +60 -0
  18. package/src/project-skills.ts +722 -0
  19. package/src/tool-metadata.ts +0 -2
  20. package/src/vendor.ts +0 -5
  21. package/src/vite-plus.ts +1 -3
  22. package/dev-kit.example.jsonc +0 -22
  23. package/schema/dev-kit.schema.json +0 -218
  24. package/schema/skill-sources.schema.json +0 -83
  25. package/scripts/sync-anti-slop-runtime.mjs +0 -19
  26. package/src/index.ts +0 -125
  27. package/src/manifest.ts +0 -224
  28. package/src/oxfmt.js +0 -23
  29. package/src/oxlint-plugin-anti-slop/runtime.d.ts +0 -22
  30. package/src/oxlint-plugin-effect.d.ts +0 -19
  31. package/src/oxlint-plugin-style.d.ts +0 -8
  32. package/src/oxlint.js +0 -113
  33. package/src/project-state.ts +0 -122
  34. package/src/scaffold.ts +0 -79
  35. package/src/skill-manager.ts +0 -527
  36. package/src/sync.ts +0 -1935
  37. package/src/tool-ignore-patterns.js +0 -9
  38. package/src/vite-plus-dependency.ts +0 -69
  39. package/src/vite-plus-hooks.ts +0 -175
  40. package/src/vite-plus-workflow.ts +0 -82
  41. package/src/vite-plus.js +0 -88
  42. package/src/worktrunk-config.ts +0 -88
  43. package/templates/AGENTS.md +0 -11
  44. package/templates/vite-plus/github-actions-check.yml +0 -51
  45. package/templates/worktrunk/wt.toml +0 -27
@@ -1,527 +0,0 @@
1
- import { Effect, FileSystem, Path, Schema } from "effect";
2
- import { Prompt } from "effect/unstable/cli";
3
- import { applyEdits, modify, parse as parseJsonc, type ParseError } from "jsonc-parser";
4
-
5
- import { loadSkillCatalog } from "./catalog.ts";
6
- import { isInteractiveTerminal, printDetail, printLine, printStatus } from "./cli-ui.ts";
7
- import { patchProjectGitignore } from "./gitignore.ts";
8
- import { DevKitManifestSchema } from "./manifest.ts";
9
- import { observeSymbolicLink } from "./node-symbolic-link.ts";
10
- import { runProjectSkillPlan } from "./sync.ts";
11
-
12
- class SkillManagerError extends Schema.TaggedError<SkillManagerError>()("SkillManagerError", {
13
- message: Schema.String,
14
- }) {}
15
-
16
- type ManagerOptions = {
17
- readonly projectDir?: string;
18
- readonly manifestPath?: string;
19
- readonly apply?: boolean;
20
- };
21
-
22
- type ManagerSyncOptions = {
23
- projectDir?: string;
24
- manifestPath?: string;
25
- };
26
-
27
- const packageRoot = Effect.fn("skillManagerPackageRoot")(function* () {
28
- const path = yield* Path.Path;
29
-
30
- return path.resolve(path.dirname(yield* path.fromFileUrl(new URL(import.meta.url))), "..");
31
- });
32
-
33
- const resolvePaths = Effect.fn("resolveSkillManagerPaths")(function* (options: ManagerOptions) {
34
- const fs = yield* FileSystem.FileSystem;
35
- const path = yield* Path.Path;
36
- const projectDir = path.resolve(options.projectDir ?? ".");
37
- const candidate = options.manifestPath ?? "dev-kit.jsonc";
38
-
39
- if (candidate.length === 0 || path.isAbsolute(candidate)) {
40
- return yield* SkillManagerError.make({
41
- message: "--manifest must be a non-empty project-relative path",
42
- });
43
- }
44
- const manifestPath = path.resolve(projectDir, candidate);
45
- const relative = path.relative(projectDir, manifestPath);
46
-
47
- if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
48
- return yield* SkillManagerError.make({
49
- message: "--manifest must resolve inside the project",
50
- });
51
- }
52
- let ancestor = projectDir;
53
-
54
- for (const segment of relative.split(path.sep).slice(0, -1)) {
55
- ancestor = path.join(ancestor, segment);
56
- if ((yield* observeSymbolicLink(ancestor)).kind === "symlink") {
57
- return yield* SkillManagerError.make({
58
- message: `manifest ancestor is a symlink: ${path.relative(projectDir, ancestor)}`,
59
- });
60
- }
61
- }
62
- const destination = yield* observeSymbolicLink(manifestPath);
63
-
64
- if (destination.kind === "symlink") {
65
- return yield* SkillManagerError.make({ message: `manifest is a symlink: ${relative}` });
66
- }
67
- if (destination.kind === "not-symlink" && (yield* fs.stat(manifestPath)).type !== "File") {
68
- return yield* SkillManagerError.make({
69
- message: `manifest is not a regular file: ${relative}`,
70
- });
71
- }
72
-
73
- return {
74
- projectDir,
75
- manifestPath,
76
- };
77
- });
78
-
79
- const renderDefaultManifest = (projectDir: string, manifestPath: string, path: Path.Path) => {
80
- const rawSchemaPath = path.relative(
81
- path.dirname(manifestPath),
82
- path.join(
83
- projectDir,
84
- "node_modules",
85
- "@danieljvdm",
86
- "dev-kit",
87
- "schema",
88
- "dev-kit.schema.json",
89
- ),
90
- );
91
- const portableSchemaPath =
92
- path.sep === "/" ? rawSchemaPath : rawSchemaPath.split(path.sep).join("/");
93
- const schemaPath = portableSchemaPath.startsWith(".")
94
- ? portableSchemaPath
95
- : `./${portableSchemaPath}`;
96
-
97
- return `${JSON.stringify(
98
- {
99
- $schema: schemaPath,
100
- include: [],
101
- targets: { agents: { enabled: true, mode: "copy" } },
102
- },
103
- null,
104
- 2,
105
- )}\n`;
106
- };
107
-
108
- const createDefaultManifest = Effect.fn("createDefaultSkillManifest")(function* (paths: {
109
- readonly projectDir: string;
110
- readonly manifestPath: string;
111
- }) {
112
- const fs = yield* FileSystem.FileSystem;
113
- const path = yield* Path.Path;
114
-
115
- yield* fs.makeDirectory(path.dirname(paths.manifestPath), { recursive: true });
116
- const staged = yield* fs.makeTempFileScoped({
117
- directory: path.dirname(paths.manifestPath),
118
- prefix: ".dev-kit-init-",
119
- });
120
-
121
- yield* fs.writeFileString(
122
- staged,
123
- renderDefaultManifest(paths.projectDir, paths.manifestPath, path),
124
- );
125
- yield* fs.rename(staged, paths.manifestPath);
126
- yield* patchProjectGitignore({ projectDir: paths.projectDir });
127
- });
128
-
129
- const readManifest = Effect.fn("readManagedSkillManifest")(function* (
130
- options: ManagerOptions,
131
- create = false,
132
- ) {
133
- const fs = yield* FileSystem.FileSystem;
134
- const paths = yield* resolvePaths(options);
135
-
136
- if (!(yield* fs.exists(paths.manifestPath))) {
137
- if (!create) {
138
- return yield* SkillManagerError.make({
139
- message: "dev-kit.jsonc not found. Run `dev-kit init` first.",
140
- });
141
- }
142
- yield* createDefaultManifest(paths);
143
- }
144
- const raw = yield* fs.readFileString(paths.manifestPath);
145
- const errors: Array<ParseError> = [];
146
- const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
147
-
148
- if (errors.length > 0) {
149
- return yield* SkillManagerError.make({ message: `could not parse ${paths.manifestPath}` });
150
- }
151
- const manifest = yield* Schema.decodeUnknownEffect(DevKitManifestSchema, {
152
- onExcessProperty: "error",
153
- })(parsed).pipe(Effect.mapError((error) => SkillManagerError.make({ message: error.message })));
154
-
155
- return { ...paths, manifest, raw };
156
- });
157
-
158
- const ManifestSelectionSchema = Schema.Struct({
159
- include: Schema.optional(Schema.Array(Schema.String)),
160
- exclude: Schema.optional(Schema.Array(Schema.String)),
161
- });
162
-
163
- const writeArray = Effect.fn("writeManifestArray")(function* (
164
- manifestPath: string,
165
- raw: string,
166
- property: "include" | "exclude",
167
- values: ReadonlyArray<string>,
168
- ) {
169
- const fs = yield* FileSystem.FileSystem;
170
- const parsed = yield* Schema.decodeUnknownEffect(ManifestSelectionSchema)(parseJsonc(raw)).pipe(
171
- Effect.mapError((error) => SkillManagerError.make({ message: error.message })),
172
- );
173
- const current = parsed[property];
174
-
175
- if (current === undefined) {
176
- if (values.length === 0) return;
177
- const edits = modify(raw, [property], [...values], {
178
- formattingOptions: { insertSpaces: true, tabSize: 2 },
179
- });
180
-
181
- yield* fs.writeFileString(manifestPath, applyEdits(raw, edits));
182
-
183
- return;
184
- }
185
- let next = raw;
186
- const retained = [...current];
187
-
188
- for (let index = current.length - 1; index >= 0; index -= 1) {
189
- const currentValue = current[index];
190
-
191
- if (currentValue !== undefined && !values.includes(currentValue)) {
192
- next = applyEdits(
193
- next,
194
- modify(next, [property, index], undefined, {
195
- formattingOptions: { insertSpaces: true, tabSize: 2 },
196
- }),
197
- );
198
- retained.splice(index, 1);
199
- }
200
- }
201
- for (const value of values) {
202
- if (retained.includes(value)) continue;
203
- next = applyEdits(
204
- next,
205
- modify(next, [property, retained.length], value, {
206
- formattingOptions: { insertSpaces: true, tabSize: 2 },
207
- isArrayInsertion: true,
208
- }),
209
- );
210
- retained.push(value);
211
- }
212
- if (next !== raw) yield* fs.writeFileString(manifestPath, next);
213
- });
214
-
215
- const selectedNames = (
216
- include: ReadonlyArray<string>,
217
- exclude: ReadonlyArray<string>,
218
- families: Readonly<Record<string, ReadonlyArray<string>>>,
219
- ) => {
220
- const selected = new Set<string>();
221
-
222
- for (const name of include) {
223
- for (const skill of families[name] ?? [name]) selected.add(skill);
224
- }
225
- for (const name of exclude) {
226
- for (const skill of families[name] ?? [name]) selected.delete(skill);
227
- }
228
-
229
- return selected;
230
- };
231
-
232
- const displayValue = (value: string): string =>
233
- [...value]
234
- .map((character) => {
235
- const code = character.charCodeAt(0);
236
-
237
- return code <= 31 || (code >= 127 && code <= 159) ? " " : character;
238
- })
239
- .join("")
240
- .replace(/\s+/g, " ")
241
- .trim();
242
-
243
- const summary = (description: string, defaultDescription: string): string => {
244
- const text = displayValue(description || defaultDescription);
245
- const firstSentence = text.match(/^.*?[.!?](?:\s|$)/)?.[0]?.trim() ?? text;
246
-
247
- return firstSentence.length > 96 ? `${firstSentence.slice(0, 93).trimEnd()}…` : firstSentence;
248
- };
249
-
250
- const applyIfRequested = (options: ManagerOptions) => {
251
- if (options.apply === false) {
252
- return printStatus("success", "Manifest updated", "run dev-kit sync to apply");
253
- }
254
- const syncOptions: ManagerSyncOptions = {};
255
-
256
- if (options.projectDir !== undefined) syncOptions.projectDir = options.projectDir;
257
- if (options.manifestPath !== undefined) syncOptions.manifestPath = options.manifestPath;
258
-
259
- return runProjectSkillPlan(syncOptions);
260
- };
261
-
262
- export const initProject = Effect.fn("initDevKitProject")(function* (options: ManagerOptions) {
263
- const fs = yield* FileSystem.FileSystem;
264
- const paths = yield* resolvePaths(options);
265
-
266
- if (yield* fs.exists(paths.manifestPath)) {
267
- yield* printStatus("info", "Already initialized", paths.manifestPath);
268
-
269
- return;
270
- }
271
- yield* createDefaultManifest(paths);
272
- yield* printStatus("success", "Created dev-kit.jsonc");
273
- yield* printDetail("Add a skill with: dev-kit add <name>");
274
- });
275
-
276
- export const addSkills = Effect.fn("addManagedSkills")(function* (
277
- names: ReadonlyArray<string>,
278
- options: ManagerOptions,
279
- ) {
280
- const current = yield* readManifest(options, true);
281
- const catalog = yield* loadSkillCatalog(yield* packageRoot(), current.projectDir);
282
- const known = new Set([
283
- ...catalog.skills.map((skill) => skill.selector),
284
- ...Object.keys(catalog.families),
285
- ]);
286
- const unknown = names.filter((name) => !known.has(name));
287
-
288
- if (unknown.length > 0) {
289
- return yield* SkillManagerError.make({
290
- message: `unknown skill${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}. Try \`dev-kit search ${unknown[0]}\`.`,
291
- });
292
- }
293
- const sourceFamilies = catalog.lock?.sources ?? [];
294
-
295
- for (const source of sourceFamilies) {
296
- if (!names.includes(source.id)) continue;
297
- yield* printStatus(
298
- "info",
299
- `Source family ${source.id} selects all ${source.skills.length} approved skills`,
300
- );
301
- yield* printDetail(
302
- `Prefer individual skill names unless every skill applies. Inspect with: dev-kit search ${source.id}`,
303
- );
304
- }
305
- const include = [...new Set([...current.manifest.include, ...names])];
306
- const exclude = (current.manifest.exclude ?? []).filter((name) => !names.includes(name));
307
-
308
- yield* writeArray(current.manifestPath, current.raw, "include", include);
309
- const reread = yield* FileSystem.FileSystem;
310
-
311
- yield* writeArray(
312
- current.manifestPath,
313
- yield* reread.readFileString(current.manifestPath),
314
- "exclude",
315
- exclude,
316
- );
317
- yield* applyIfRequested(options);
318
- });
319
-
320
- export const removeSkills = Effect.fn("removeManagedSkills")(function* (
321
- names: ReadonlyArray<string>,
322
- options: ManagerOptions,
323
- ) {
324
- const current = yield* readManifest(options);
325
- const catalog = yield* loadSkillCatalog(yield* packageRoot(), current.projectDir);
326
- const before = selectedNames(
327
- current.manifest.include,
328
- current.manifest.exclude ?? [],
329
- catalog.families,
330
- );
331
- const absent = names.filter(
332
- (name) => !before.has(name) && !current.manifest.include.includes(name),
333
- );
334
-
335
- if (absent.length > 0) {
336
- return yield* SkillManagerError.make({ message: `not selected: ${absent.join(", ")}` });
337
- }
338
- const include = current.manifest.include.filter((name) => !names.includes(name));
339
- const excluded = new Set(current.manifest.exclude ?? []);
340
-
341
- for (const name of names) {
342
- if (before.has(name) && !current.manifest.include.includes(name)) excluded.add(name);
343
- else excluded.delete(name);
344
- }
345
- yield* writeArray(current.manifestPath, current.raw, "include", include);
346
- const fs = yield* FileSystem.FileSystem;
347
-
348
- yield* writeArray(
349
- current.manifestPath,
350
- yield* fs.readFileString(current.manifestPath),
351
- "exclude",
352
- [...excluded].sort(),
353
- );
354
- yield* applyIfRequested(options);
355
- });
356
-
357
- export const listSkills = Effect.fn("listManagedSkills")(function* (
358
- options: ManagerOptions & { readonly all?: boolean; readonly query?: string },
359
- ) {
360
- const fs = yield* FileSystem.FileSystem;
361
- const paths = yield* resolvePaths(options);
362
- const catalog = yield* loadSkillCatalog(yield* packageRoot(), paths.projectDir);
363
- const manifest = (yield* fs.exists(paths.manifestPath))
364
- ? (yield* readManifest(options)).manifest
365
- : { include: [], exclude: [] };
366
- const selected = selectedNames(manifest.include, manifest.exclude ?? [], catalog.families);
367
- const query = options.query?.toLowerCase();
368
- const visible = catalog.skills.filter(
369
- (skill) =>
370
- (options.all || selected.has(skill.selector)) &&
371
- (!query ||
372
- `${skill.selector} ${skill.description} ${skill.source}`.toLowerCase().includes(query)),
373
- );
374
- const catalogSelectors = new Set(catalog.skills.map((skill) => skill.selector));
375
- const unavailable = [...selected].filter(
376
- (selector) =>
377
- !catalogSelectors.has(selector) && (!query || selector.toLowerCase().includes(query)),
378
- );
379
-
380
- if (visible.length === 0 && unavailable.length === 0) {
381
- yield* printStatus("info", query ? "No matching skills" : "No skills selected");
382
- if (!query && !options.all) yield* printDetail("Browse with: dev-kit list --all");
383
-
384
- return;
385
- }
386
- for (const skill of visible) {
387
- const marker = selected.has(skill.selector) ? "✓" : " ";
388
- const origin = skill.bundled ? "built in" : skill.source;
389
- const provenance = skill.package
390
- ? ` [installed ${displayValue(skill.package.version)}]`
391
- : skill.bundled
392
- ? ""
393
- : ` [${skill.source}]`;
394
-
395
- yield* printLine(
396
- `${marker} ${skill.selector}${provenance} ${summary(skill.description, origin)}`,
397
- );
398
- }
399
- for (const selector of unavailable) {
400
- yield* printLine(
401
- `! ${selector} [unavailable] install or repair the selected direct dependency`,
402
- );
403
- }
404
- yield* printLine();
405
- yield* printLine(`${selected.size} selected · ${catalog.skills.length} available`);
406
- });
407
-
408
- export const showSkill = Effect.fn("showCatalogSkill")(function* (
409
- name: string,
410
- options: ManagerOptions,
411
- ) {
412
- const paths = yield* resolvePaths(options);
413
- const catalog = yield* loadSkillCatalog(yield* packageRoot(), paths.projectDir);
414
- const skill = catalog.skills.find((candidate) => candidate.selector === name);
415
-
416
- if (!skill) return yield* SkillManagerError.make({ message: `unknown skill: ${name}` });
417
- yield* printLine(skill.selector);
418
- if (skill.description) yield* printLine(displayValue(skill.description));
419
- if (skill.package) {
420
- yield* printLine(`Source: installed package`);
421
- yield* printLine(`Package: ${skill.package.name}`);
422
- yield* printLine(`Version: ${displayValue(skill.package.version)}`);
423
-
424
- return;
425
- }
426
- yield* printLine(`Source: ${skill.bundled ? "dev-kit (built in)" : skill.source}`);
427
- if (!skill.bundled) {
428
- const source = catalog.lock?.sources.find((candidate) => candidate.id === skill.source);
429
-
430
- if (source) {
431
- yield* printLine(`Repository: ${source.repository}`);
432
- yield* printLine(`Approved commit: ${source.resolved}`);
433
-
434
- return;
435
- }
436
- }
437
- });
438
-
439
- export const showDashboard = Effect.fn("showSkillDashboard")(function* (options: ManagerOptions) {
440
- yield* printLine("dev-kit skills");
441
- yield* printLine();
442
- yield* listSkills({ ...options, all: false });
443
- yield* printLine("Add dev-kit add <skill>");
444
- yield* printLine("Browse dev-kit list --all");
445
- yield* printLine("Find dev-kit search <words>");
446
- yield* printLine("Remove dev-kit remove <skill>");
447
- });
448
-
449
- export const chooseSkillsToAdd = Effect.fn("chooseSkillsToAdd")(function* (
450
- options: ManagerOptions,
451
- ) {
452
- if (!(yield* isInteractiveTerminal)) {
453
- return yield* SkillManagerError.make({
454
- message: "pass one or more skill names, or run this command in a terminal",
455
- });
456
- }
457
- const current = yield* readManifest(options, true);
458
- const catalog = yield* loadSkillCatalog(yield* packageRoot(), current.projectDir);
459
- const selected = selectedNames(
460
- current.manifest.include,
461
- current.manifest.exclude ?? [],
462
- catalog.families,
463
- );
464
- const available = catalog.skills.filter((skill) => !selected.has(skill.selector));
465
-
466
- if (available.length === 0) {
467
- yield* printStatus("success", "All available skills are selected");
468
-
469
- return;
470
- }
471
- const names = yield* Prompt.multiSelect({
472
- message: "Choose skills to add",
473
- choices: available.map((skill) => ({
474
- title: skill.selector,
475
- value: skill.selector,
476
- description: summary(skill.description, skill.source),
477
- })),
478
- min: 1,
479
- });
480
-
481
- yield* addSkills(names, options);
482
- });
483
-
484
- export const chooseSkillsToRemove = Effect.fn("chooseSkillsToRemove")(function* (
485
- options: ManagerOptions,
486
- ) {
487
- if (!(yield* isInteractiveTerminal)) {
488
- return yield* SkillManagerError.make({
489
- message: "pass one or more skill names, or run this command in a terminal",
490
- });
491
- }
492
- const current = yield* readManifest(options);
493
- const catalog = yield* loadSkillCatalog(yield* packageRoot(), current.projectDir);
494
- const selected = selectedNames(
495
- current.manifest.include,
496
- current.manifest.exclude ?? [],
497
- catalog.families,
498
- );
499
-
500
- if (selected.size === 0) {
501
- yield* printStatus("info", "No skills selected");
502
-
503
- return;
504
- }
505
- const names = yield* Prompt.multiSelect({
506
- message: "Choose skills to remove",
507
- choices: [
508
- ...catalog.skills
509
- .filter((skill) => selected.has(skill.selector))
510
- .map((skill) => ({
511
- title: skill.selector,
512
- value: skill.selector,
513
- description: summary(skill.description, skill.source),
514
- })),
515
- ...[...selected]
516
- .filter((selector) => !catalog.skills.some((skill) => skill.selector === selector))
517
- .map((selector) => ({
518
- title: selector,
519
- value: selector,
520
- description: "Selected but currently unavailable",
521
- })),
522
- ],
523
- min: 1,
524
- });
525
-
526
- yield* removeSkills(names, options);
527
- });