@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 +34 -4
- package/package.json +18 -4
- package/schema/dev-kit.schema.json +1 -1
- package/schema/skill-sources.schema.json +1 -1
- package/skills/dev-kit/SKILL.md +53 -15
- package/src/catalog-manager.ts +4 -1
- package/src/gitignore.ts +1 -1
- package/src/node-symbolic-link.ts +1 -1
- package/src/oxlint.ts +35 -0
- package/src/path-digest.ts +1 -1
- package/src/skill-manager.ts +2 -1
- package/src/sync.ts +0 -1
- package/src/vendor.ts +20 -5
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,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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.
|
|
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/
|
|
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/
|
|
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/
|
|
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",
|
package/skills/dev-kit/SKILL.md
CHANGED
|
@@ -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,
|
|
25
|
-
|
|
26
|
-
configuration,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
150
|
-
|
|
151
|
-
exposes
|
|
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.
|
package/src/catalog-manager.ts
CHANGED
|
@@ -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";
|
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;
|
package/src/path-digest.ts
CHANGED
package/src/skill-manager.ts
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
const name =
|
|
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
|
-
|
|
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"]);
|