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