@danieljvdm/dev-kit 0.2.2 → 0.3.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.
package/README.md CHANGED
@@ -28,10 +28,18 @@ bun x dev-kit list --all
28
28
  bun x dev-kit add dev-kit effect
29
29
  ```
30
30
 
31
- Before adding external skills, scan the repository's agent instructions,
32
- package and lock files, framework or tool configuration, and CI workflows for
33
- the capabilities it actually uses. Search and inspect candidates using those
34
- signals, then add the matching skills individually:
31
+ Before adding external skills, have the agent inspect repository instructions,
32
+ workspace dependencies, framework and tool configuration, representative
33
+ source boundaries, and CI workflows. It should compare that concrete capability
34
+ inventory with catalog descriptions and select the narrowest useful set. Broad
35
+ umbrella skills and source families belong only when their full breadth is
36
+ intentional; explicit creative or advisory requests remain valid even without a
37
+ mechanical dependency signal. Treat lazy reference folders inside one skill as
38
+ progressive-disclosure content, not as separately triggered skills; a repository
39
+ using several covered products may reasonably select that umbrella while still
40
+ excluding unrelated top-level skills.
41
+
42
+ Search and inspect candidates, then add the matching skills individually:
35
43
 
36
44
  ```bash
37
45
  bun x dev-kit search cloudflare
@@ -301,6 +309,28 @@ transforms, and installs the result through the ownership-safe sync path. Normal
301
309
  installs never float to a newer upstream commit; only a reviewed catalog refresh
302
310
  changes what is approved.
303
311
 
312
+ ## Oxlint preset for Vite+
313
+
314
+ Dev Kit exports a typed, composable set of high-signal Oxlint rules for Vite+
315
+ projects:
316
+
317
+ ```ts
318
+ import { recommendedOxlintConfig } from "@danieljvdm/dev-kit/oxlint";
319
+ import { defineConfig } from "vite-plus";
320
+
321
+ export default defineConfig({
322
+ lint: {
323
+ extends: [recommendedOxlintConfig],
324
+ rules: {
325
+ // Add repository-specific rules here.
326
+ },
327
+ },
328
+ });
329
+ ```
330
+
331
+ Use `lint.extends` rather than a shallow object spread so Vite+ composes the
332
+ nested plugin and rule configuration correctly.
333
+
304
334
  ## Development
305
335
 
306
336
  ```bash
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@danieljvdm/dev-kit",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Declarative project development toolkit with portable agent skills.",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/danieljvdm/agent-skills.git"
9
+ "url": "git+https://github.com/danieljvdm/dev-kit.git"
10
10
  },
11
11
  "license": "MIT",
12
12
  "bin": {
@@ -19,7 +19,11 @@
19
19
  },
20
20
  "./schema": "./schema/dev-kit.schema.json",
21
21
  "./schema/dev-kit.schema.json": "./schema/dev-kit.schema.json",
22
- "./schema/skill-sources.schema.json": "./schema/skill-sources.schema.json"
22
+ "./schema/skill-sources.schema.json": "./schema/skill-sources.schema.json",
23
+ "./oxlint": {
24
+ "types": "./src/oxlint.ts",
25
+ "default": "./src/oxlint.ts"
26
+ }
23
27
  },
24
28
  "files": [
25
29
  "dev-kit.example.jsonc",
@@ -36,7 +40,8 @@
36
40
  "changeset": "changeset",
37
41
  "version-packages": "changeset version && ./bin/dev-kit.mjs apply",
38
42
  "release": "changeset publish",
39
- "check": "tsc --noEmit",
43
+ "check": "tsc --noEmit && vp lint",
44
+ "lint": "vp lint",
40
45
  "plan": "./bin/dev-kit.mjs plan",
41
46
  "apply": "./bin/dev-kit.mjs apply",
42
47
  "gitignore": "./bin/dev-kit.mjs gitignore",
@@ -60,8 +65,17 @@
60
65
  "@effect/vitest": "4.0.0-beta.102",
61
66
  "@types/node": "25.9.1",
62
67
  "typescript": "7.0.2",
68
+ "vite-plus": "0.2.6",
63
69
  "vitest": "4.1.10"
64
70
  },
71
+ "peerDependencies": {
72
+ "vite-plus": ">=0.2.6 <0.3.0"
73
+ },
74
+ "peerDependenciesMeta": {
75
+ "vite-plus": {
76
+ "optional": true
77
+ }
78
+ },
65
79
  "engines": {
66
80
  "node": ">=22.12.0"
67
81
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://raw.githubusercontent.com/danieljvdm/agent-skills/main/schema/dev-kit.schema.json",
3
+ "$id": "https://raw.githubusercontent.com/danieljvdm/dev-kit/main/schema/dev-kit.schema.json",
4
4
  "title": "Dev Kit Manifest",
5
5
  "description": "Project-local desired state for portable skills and explicit dev-kit setup tasks.",
6
6
  "type": "object",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://raw.githubusercontent.com/danieljvdm/agent-skills/main/schema/skill-sources.schema.json",
3
+ "$id": "https://raw.githubusercontent.com/danieljvdm/dev-kit/main/schema/skill-sources.schema.json",
4
4
  "title": "External Agent Skill Sources",
5
5
  "description": "Repository-level sources approved for the skill catalog.",
6
6
  "type": "object",
@@ -21,23 +21,37 @@ stores explicit skill names and exact commit/content digests.
21
21
 
22
22
  ## Apply loop
23
23
 
24
- 1. Establish the Git root. Read project agent instructions, `dev-kit.jsonc`,
25
- `dev-kit.lock.json` when present, package and lock files, framework or tool
26
- configuration, CI workflows, and the configured target paths. Derive the
27
- capabilities the project actually uses from those signals.
28
- 2. Use `dev-kit search <terms>` and `dev-kit info <skill>` to inspect matching
29
- catalog entries. Prefer individual external skills; select an external
30
- source family only when every approved skill from it applies. Update
31
- `dev-kit.jsonc`, preserve JSONC comments, and validate against the package
32
- schema. Finish with each desired resource represented once.
33
- 3. Run `dev-kit plan`. Use `--manifest`, `--project-dir`, or `--lockfile` when
24
+ 1. Establish the Git root. Read project agent instructions, the current
25
+ manifest and lock, package and workspace manifests, framework and tool
26
+ configuration, representative source boundaries, and CI workflows. Build a
27
+ concrete inventory of the platforms, frameworks, tools, and workflows the
28
+ repository actually uses; do not infer capabilities from a product or
29
+ company name alone.
30
+ 2. Run `dev-kit list --all`, then use `dev-kit search <terms>` and `dev-kit info
31
+ <skill>` for each capability in the inventory. Compare every candidate's
32
+ trigger description with concrete repository evidence. Keep explicitly
33
+ requested creative or advisory skills even when they have no mechanical
34
+ dependency signal.
35
+ 3. Choose the narrowest useful set. Prefer focused external skills over a
36
+ generic umbrella when they cover the repository's work. Select an umbrella
37
+ or external source family only when its full breadth is intentionally useful;
38
+ never select one merely because one member or product matches. Explain any
39
+ uncertain inclusion before applying it. Distinguish separately triggered
40
+ skills from lazy `references/` bundled inside one skill: unused reference
41
+ folders cost repository space but are not loaded into agent context unless
42
+ the skill routes to them. A multi-product repository can therefore justify
43
+ an umbrella while still excluding unrelated top-level skills.
44
+ 4. Update `dev-kit.jsonc`. Preserve JSONC comments and validate against the
45
+ package schema. Finish with each desired resource represented once and every
46
+ external selection supported by repository evidence or an explicit request.
47
+ 5. Run `dev-kit plan`. Use `--manifest`, `--project-dir`, or `--lockfile` when
34
48
  the project overrides their defaults. Planning is read-only; inspect every
35
49
  create, update, remove, adoption, and conflict before proceeding. Finish
36
50
  when the plan contains only intended actions and understood conflicts.
37
- 4. Resolve conflicts, then run `dev-kit apply`. Commit the manifest and
51
+ 6. Resolve conflicts, then run `dev-kit apply`. Commit the manifest and
38
52
  regenerated `dev-kit.lock.json`; keep `.dev-kit/` local. Finish when a second
39
53
  plan reports only unchanged resources and setup tasks.
40
- 5. Use `dev-kit apply --locked` in CI and the package lifecycle. Finish when a
54
+ 7. Use `dev-kit apply --locked` in CI and the package lifecycle. Finish when a
41
55
  clean install converges from the committed manifest and lock.
42
56
 
43
57
  ## Manifest
@@ -142,10 +156,34 @@ dependencies; `dev-kit apply` patches once and then converges.
142
156
  Use `dev-kit tsgo patch --dry-run` for focused diagnosis. Use `--force` only
143
157
  after the user accepts a potentially commit-incompatible TypeScript binary.
144
158
 
159
+ ## Oxlint preset for Vite+
160
+
161
+ For Vite+ projects, compose the package's typed recommended Oxlint preset with
162
+ project-specific lint configuration:
163
+
164
+ ```ts
165
+ import { recommendedOxlintConfig } from "@danieljvdm/dev-kit/oxlint";
166
+ import { defineConfig } from "vite-plus";
167
+
168
+ export default defineConfig({
169
+ lint: {
170
+ extends: [recommendedOxlintConfig],
171
+ rules: {
172
+ // Project-specific rules apply after the shared preset.
173
+ },
174
+ },
175
+ });
176
+ ```
177
+
178
+ Use `lint.extends` instead of spreading the object so Vite+ composes nested
179
+ rule maps correctly. Keep repository-specific path conventions and
180
+ platform-specific accessibility rules in the consuming project.
181
+
145
182
  ## Current boundary
146
183
 
147
184
  Manage skill outputs, the `setup.effectSource` checkout, and the explicit
148
185
  `setup.effectTsgo` task. Edit shared `package.json` and `tsconfig.json`
149
- contributions deliberately. Treat named bundles, Oxlint/Oxfmt presets, and
150
- broader setup tasks as future manifest capabilities until the installed CLI
151
- exposes them.
186
+ contributions deliberately. The Oxlint preset is a composable package export,
187
+ not a manifest-managed output. Treat named bundles, Oxfmt presets, and broader
188
+ setup tasks as future manifest capabilities until the installed CLI exposes
189
+ them.
@@ -216,7 +216,10 @@ export const addCatalogSource = Effect.fn("addCatalogSource")(function* (
216
216
  const existingIndex = byId >= 0 ? byId : byRepository;
217
217
  let next = state.sources.raw;
218
218
  if (existingIndex >= 0) {
219
- const existing = sources[existingIndex]!;
219
+ const existing = sources[existingIndex];
220
+ if (existing === undefined) {
221
+ return yield* new CatalogManagerError({ message: "catalog source index is out of bounds" });
222
+ }
220
223
  const approved = state.lock?.value.sources.find((source) => source.id === existing.id)?.skills ?? [];
221
224
  const currentInclude = existing.include.includes("*") ? approved : existing.include;
222
225
  const include = [...new Set([...currentInclude, ...selection.include])].sort();
package/src/gitignore.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Cause, Effect, FileSystem, Path, PlatformError, Schema } from "effect";
1
+ import { Cause, Effect, FileSystem, Path, type PlatformError, Schema } from "effect";
2
2
 
3
3
  import { printStatus } from "./cli-ui.ts";
4
4
  import { observeSymbolicLink } from "./node-symbolic-link.ts";
@@ -1,4 +1,4 @@
1
- import { Effect, FileSystem, PlatformError } from "effect";
1
+ import { Effect, FileSystem, type PlatformError } from "effect";
2
2
 
3
3
  export type SymbolicLinkObservation =
4
4
  | { readonly kind: "missing" | "not-symlink" }
package/src/oxlint.ts ADDED
@@ -0,0 +1,35 @@
1
+ import type { OxlintConfig } from "vite-plus/lint";
2
+
3
+ /**
4
+ * High-signal Oxlint defaults for TypeScript projects using Vite+.
5
+ *
6
+ * Extend this object from `lint.extends` so project-local plugins, rules, and
7
+ * overrides compose without losing nested configuration.
8
+ */
9
+ export const recommendedOxlintConfig = {
10
+ options: {
11
+ typeAware: true,
12
+ },
13
+ plugins: ["import", "react", "vitest"],
14
+ rules: {
15
+ eqeqeq: "error",
16
+ "import/default": "off",
17
+ "import/namespace": "off",
18
+ "import/no-cycle": "error",
19
+ "import/no-duplicates": "error",
20
+ "import/no-self-import": "error",
21
+ "react/exhaustive-deps": "error",
22
+ "react/rules-of-hooks": "error",
23
+ "typescript/consistent-type-imports": "error",
24
+ "typescript/no-explicit-any": "error",
25
+ "typescript/no-non-null-assertion": "error",
26
+ "typescript/switch-exhaustiveness-check": "error",
27
+ "unicorn/prefer-node-protocol": "error",
28
+ "vitest/no-focused-tests": "error",
29
+ "vitest/no-identical-title": "error",
30
+ "vitest/no-standalone-expect": "off",
31
+ "vitest/valid-expect": "error",
32
+ },
33
+ } satisfies OxlintConfig;
34
+
35
+ export type RecommendedOxlintConfig = typeof recommendedOxlintConfig;
@@ -1,4 +1,4 @@
1
- import { Crypto, Effect, Encoding, FileSystem, Path, PlatformError, Schema } from "effect";
1
+ import { Crypto, Effect, Encoding, FileSystem, Path, type PlatformError, Schema } from "effect";
2
2
 
3
3
  import { observeSymbolicLink } from "./node-symbolic-link.ts";
4
4
 
@@ -154,7 +154,8 @@ const writeArray = Effect.fn("writeManifestArray")(function* (
154
154
  let next = raw;
155
155
  const retained = [...current];
156
156
  for (let index = current.length - 1; index >= 0; index -= 1) {
157
- if (!values.includes(current[index]!)) {
157
+ const currentValue = current[index];
158
+ if (currentValue !== undefined && !values.includes(currentValue)) {
158
159
  next = applyEdits(next, modify(next, [property, index], undefined, {
159
160
  formattingOptions: { insertSpaces: true, tabSize: 2 },
160
161
  }));
package/src/sync.ts CHANGED
@@ -284,7 +284,6 @@ const resolveManagedPath = Effect.fn("resolveManagedPath")(function* (
284
284
  projectDir: string,
285
285
  candidate: string,
286
286
  ) {
287
- const fs = yield* FileSystem.FileSystem;
288
287
  const path = yield* Path.Path;
289
288
  if (candidate.length === 0 || path.isAbsolute(candidate)) {
290
289
  return yield* new UnsafeManagedPathError({ path: candidate, reason: "must be a non-empty project-relative path" });
package/src/vendor.ts CHANGED
@@ -109,6 +109,14 @@ const inferSourceId = (repository: string): string => {
109
109
  return tail.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
110
110
  };
111
111
 
112
+ const containsControlCharacter = (value: string): boolean => {
113
+ for (const character of value) {
114
+ const code = character.charCodeAt(0);
115
+ if (code <= 0x1f || code === 0x7f) return true;
116
+ }
117
+ return false;
118
+ };
119
+
112
120
  const normalizeRepositoryLocator = (
113
121
  repository: string,
114
122
  ): Effect.Effect<{
@@ -116,7 +124,7 @@ const normalizeRepositoryLocator = (
116
124
  readonly ref?: string;
117
125
  readonly skillsPath?: string;
118
126
  }, InvalidSourceError> => {
119
- if (/[\u0000-\u001f\u007f]/.test(repository)) {
127
+ if (containsControlCharacter(repository)) {
120
128
  return Effect.fail(new InvalidSourceError({ source: repository, reason: "repository contains control characters" }));
121
129
  }
122
130
  try {
@@ -126,9 +134,9 @@ const normalizeRepositoryLocator = (
126
134
  }
127
135
  if (url.hostname.toLowerCase() !== "github.com") return Effect.succeed({ repository });
128
136
  const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
129
- if (segments.length < 2) return Effect.succeed({ repository });
130
- const owner = segments[0]!;
131
- const name = segments[1]!.replace(/\.git$/i, "");
137
+ const [owner, rawName] = segments;
138
+ if (owner === undefined || rawName === undefined) return Effect.succeed({ repository });
139
+ const name = rawName.replace(/\.git$/i, "");
132
140
  const normalized = `https://github.com/${owner}/${name}.git`;
133
141
  if (segments[2] === "tree" && segments[3]) {
134
142
  return Effect.succeed({
@@ -389,13 +397,20 @@ const prepareSource = Effect.fn("prepareSkillSource")(function* (
389
397
  yield* fs.makeDirectory(checkoutDir, { recursive: true });
390
398
  yield* runCommand(checkoutDir, "git", ["init", "--quiet"]);
391
399
  yield* runCommand(checkoutDir, "git", ["remote", "add", "origin", source.repository]);
400
+ const fetchRef = useLock ? lockedSource?.resolved : source.ref;
401
+ if (fetchRef === undefined) {
402
+ return yield* new InvalidSourceError({
403
+ source: source.id,
404
+ reason: "no matching lockfile entry; run catalog refresh without --locked first",
405
+ });
406
+ }
392
407
  yield* runCommand(checkoutDir, "git", [
393
408
  "fetch",
394
409
  "--quiet",
395
410
  "--depth",
396
411
  "1",
397
412
  "origin",
398
- useLock ? lockedSource!.resolved : source.ref,
413
+ fetchRef,
399
414
  ]);
400
415
  yield* runCommand(checkoutDir, "git", ["checkout", "--quiet", "--detach", "FETCH_HEAD"]);
401
416
  const resolved = yield* runCommand(checkoutDir, "git", ["rev-parse", "HEAD"]);