@danieljvdm/dev-kit 0.3.3 → 0.5.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 +138 -33
- package/dev-kit.example.jsonc +1 -0
- package/package.json +19 -1
- package/schema/dev-kit.schema.json +18 -4
- package/skills/dev-kit/SKILL.md +34 -11
- package/src/bin/dev-kit.ts +5 -5
- package/src/catalog.ts +72 -15
- package/src/index.ts +8 -0
- package/src/manifest.ts +16 -2
- package/src/oxfmt.js +18 -0
- package/src/oxfmt.ts +22 -0
- package/src/oxlint-plugin-effect.d.ts +17 -0
- package/src/oxlint-plugin-effect.js +166 -0
- package/src/oxlint.js +6 -0
- package/src/oxlint.ts +11 -4
- package/src/package-skill-source.ts +272 -0
- package/src/project-state.ts +33 -8
- package/src/skill-manager.ts +66 -30
- package/src/skill-selector.ts +43 -0
- package/src/sync.ts +175 -38
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ Install the published Dev Kit package:
|
|
|
20
20
|
bun add -d @danieljvdm/dev-kit
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Initialize the project, browse
|
|
23
|
+
Initialize the project, browse available built-in, approved Git, and installed
|
|
24
|
+
package skills, then add the ones you want:
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
27
|
bun x dev-kit init
|
|
@@ -107,7 +108,7 @@ the root package itself.
|
|
|
107
108
|
| `dev-kit remove <skill...>` | Deselect and uninstall skills safely. |
|
|
108
109
|
| `dev-kit list [--all]` | List selected skills or browse the catalog. |
|
|
109
110
|
| `dev-kit search <words...>` | Search names and descriptions. |
|
|
110
|
-
| `dev-kit info <skill>` | Show description
|
|
111
|
+
| `dev-kit info <skill>` | Show description and Git or installed-package provenance. |
|
|
111
112
|
| `dev-kit status` | Check whether the project matches its selection. |
|
|
112
113
|
| `dev-kit sync` | Apply the current manifest. |
|
|
113
114
|
| `dev-kit plan` | Preview project changes without writing files. |
|
|
@@ -149,7 +150,8 @@ tool versions. A project-local process lock also prevents concurrent applies.
|
|
|
149
150
|
|
|
150
151
|
## Manifest
|
|
151
152
|
|
|
152
|
-
`include` accepts
|
|
153
|
+
`include` accepts static skill names, skill families, and explicit
|
|
154
|
+
`<package>#<skill>` selectors:
|
|
153
155
|
|
|
154
156
|
```jsonc
|
|
155
157
|
{
|
|
@@ -159,9 +161,13 @@ tool versions. A project-local process lock also prevents concurrent applies.
|
|
|
159
161
|
"effect",
|
|
160
162
|
"workers-best-practices",
|
|
161
163
|
"wrangler",
|
|
162
|
-
"serve-sim"
|
|
164
|
+
"serve-sim",
|
|
165
|
+
"@tanstack/ai#ai-core"
|
|
163
166
|
],
|
|
164
167
|
"exclude": ["animation-vocabulary"],
|
|
168
|
+
"setup": {
|
|
169
|
+
"claudeInstructions": { "enabled": true }
|
|
170
|
+
},
|
|
165
171
|
"targets": {
|
|
166
172
|
"agents": { "enabled": true, "mode": "copy" },
|
|
167
173
|
"claude": { "enabled": true, "mode": "symlink" },
|
|
@@ -175,6 +181,8 @@ tool versions. A project-local process lock also prevents concurrent applies.
|
|
|
175
181
|
- Prefer individual external skills such as `workers-best-practices` and
|
|
176
182
|
`wrangler`, selected after scanning the project for relevant technologies.
|
|
177
183
|
- `serve-sim` selects the approved Evan Bacon simulator skill directly.
|
|
184
|
+
- `@tanstack/ai#ai-core` explicitly selects a skill discovered in that direct
|
|
185
|
+
project dependency; discovery alone never selects it.
|
|
178
186
|
- An approved source ID is broad shorthand that selects every skill from that
|
|
179
187
|
source. Use it only when the scan confirms that every member applies.
|
|
180
188
|
|
|
@@ -183,6 +191,25 @@ Dev Kit reserves `.repos/<source-id>` for project-local source checkouts. Run
|
|
|
183
191
|
The patch is idempotent, preserves existing lines, and refuses symlinked
|
|
184
192
|
`.gitignore` files.
|
|
185
193
|
|
|
194
|
+
## Claude instructions
|
|
195
|
+
|
|
196
|
+
Enable a portable Claude Code instruction bridge in the manifest:
|
|
197
|
+
|
|
198
|
+
```jsonc
|
|
199
|
+
{
|
|
200
|
+
"include": [],
|
|
201
|
+
"setup": {
|
|
202
|
+
"claudeInstructions": { "enabled": true }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`dev-kit apply` requires a project-root `AGENTS.md`, then manages
|
|
208
|
+
`CLAUDE.md` as the relative symlink `CLAUDE.md → AGENTS.md`. The link is
|
|
209
|
+
recorded in the lockfile and local ownership state. Dev Kit refuses to replace
|
|
210
|
+
an unowned `CLAUDE.md` and removes the link when the task is disabled only if
|
|
211
|
+
the owned link is unchanged.
|
|
212
|
+
|
|
186
213
|
## Effect source checkout
|
|
187
214
|
|
|
188
215
|
Enable a local checkout of the exact installed Effect release in the manifest:
|
|
@@ -248,10 +275,65 @@ troubleshooting the task directly.
|
|
|
248
275
|
Package and tsconfig edits remain explicit until Dev Kit can safely own parts
|
|
249
276
|
of shared JSONC files.
|
|
250
277
|
|
|
251
|
-
##
|
|
278
|
+
## Installed package skills
|
|
279
|
+
|
|
280
|
+
Dev Kit generically discovers agent skills bundled by the project's installed
|
|
281
|
+
JavaScript packages. It reads the project's direct dependencies, checks the
|
|
282
|
+
package's Intent v1 discovery metadata (or Intent's repository-metadata
|
|
283
|
+
fallback), then looks for the layout
|
|
284
|
+
`node_modules/<package>/skills/<skill>/SKILL.md`.
|
|
285
|
+
TanStack is one publisher of this layout; no TanStack package names or skill
|
|
286
|
+
paths are hard-coded into Dev Kit.
|
|
287
|
+
|
|
288
|
+
Discovery is browse-only. These commands show an installed package skill but do
|
|
289
|
+
not select, copy, symlink, lock, or otherwise install it:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
bun x dev-kit list --all
|
|
293
|
+
bun x dev-kit search tanstack
|
|
294
|
+
bun x dev-kit info @tanstack/ai#ai-core
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Selection is explicit and package-qualified:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
bun x dev-kit add @tanstack/ai#ai-core
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
That writes `@tanstack/ai#ai-core` to `dev-kit.jsonc` and, unless
|
|
304
|
+
`--no-apply` is passed, installs it through the normal ownership-safe sync
|
|
305
|
+
path. The qualifier prevents ambiguity when two dependencies publish the same
|
|
306
|
+
skill name. Two selected skills that would both write the same destination are
|
|
307
|
+
rejected before any output is changed.
|
|
308
|
+
|
|
309
|
+
The initial compatibility boundary is intentionally small and deterministic:
|
|
252
310
|
|
|
253
|
-
|
|
254
|
-
|
|
311
|
+
- only packages named in the root project's `dependencies`,
|
|
312
|
+
`devDependencies`, `optionalDependencies`, or `peerDependencies` are
|
|
313
|
+
scanned;
|
|
314
|
+
- package code is never imported or executed;
|
|
315
|
+
- npm-style and pnpm/workspace symlinks under `node_modules` are supported;
|
|
316
|
+
- Yarn Plug'n'Play and transitive dependency traversal are not scanned; and
|
|
317
|
+
- immediate `skills/<name>/SKILL.md` roots are listed. Nested topic skills and
|
|
318
|
+
references remain part of that root and are copied with it.
|
|
319
|
+
|
|
320
|
+
The last rule adapts Intent's routed, nested skill trees to the immediate folder
|
|
321
|
+
and frontmatter-name invariants expected by Agent Skills targets. Dev Kit does
|
|
322
|
+
not rewrite nested names or ask Intent to manage agent configuration.
|
|
323
|
+
|
|
324
|
+
The project `dev-kit.lock.json` records the selected package name, installed
|
|
325
|
+
version, skill name, and content digest. `apply --locked` therefore rejects
|
|
326
|
+
package-version or skill-content drift. Dev Kit never downloads a missing
|
|
327
|
+
package or substitutes a registry version.
|
|
328
|
+
|
|
329
|
+
See TanStack's
|
|
330
|
+
[Agent Skills documentation](https://tanstack.com/ai/latest/docs/getting-started/agent-skills)
|
|
331
|
+
for a real package suite that uses this convention.
|
|
332
|
+
|
|
333
|
+
## Approved external Git skills
|
|
334
|
+
|
|
335
|
+
This repository remains an opinionated catalog for Git-hosted skills.
|
|
336
|
+
`skill-sources.jsonc` contains only reviewed Git sources:
|
|
255
337
|
|
|
256
338
|
```jsonc
|
|
257
339
|
{
|
|
@@ -276,49 +358,41 @@ bun run catalog:refresh
|
|
|
276
358
|
bun run catalog:check
|
|
277
359
|
```
|
|
278
360
|
|
|
279
|
-
Adding a source does not require editing JSONC:
|
|
361
|
+
Adding a Git source does not require editing JSONC:
|
|
280
362
|
|
|
281
363
|
```bash
|
|
282
|
-
# Opens a skill picker in a terminal
|
|
283
364
|
dev-kit catalog add https://github.com/owner/repository
|
|
284
|
-
|
|
285
|
-
# Explicit and automation-friendly
|
|
286
365
|
dev-kit catalog add https://github.com/owner/repository \
|
|
287
366
|
--skill one --skill two
|
|
288
367
|
dev-kit catalog add https://github.com/owner/repository --all
|
|
289
368
|
```
|
|
290
369
|
|
|
291
|
-
GitHub tree URLs are accepted
|
|
292
|
-
`https://github.com/owner/repository/tree/main/skills` supplies the repository,
|
|
293
|
-
ref, and skills path together. `--all` expands to the skills discovered at that
|
|
370
|
+
GitHub tree URLs are accepted. `--all` expands to the skills found at that
|
|
294
371
|
exact snapshot; it never writes a wildcard that could silently approve a future
|
|
295
|
-
upstream addition.
|
|
372
|
+
upstream addition. Catalog refresh resolves refs to exact commits, validates
|
|
373
|
+
names and paths, rejects symlinks and collisions, extracts descriptions, and
|
|
374
|
+
updates `skill-sources.lock.json`.
|
|
296
375
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
376
|
+
When a project selects one of these Git-backed skills, Dev Kit fetches the
|
|
377
|
+
approved commit into the ignored `.dev-kit/cache` and installs it through the
|
|
378
|
+
same ownership-safe sync path. Only a reviewed catalog refresh changes the
|
|
379
|
+
approved Git content.
|
|
300
380
|
|
|
301
|
-
|
|
302
|
-
symlinks and collisions, extracts short descriptions, and updates
|
|
303
|
-
`skill-sources.lock.json`. It does not copy upstream skill trees into this
|
|
304
|
-
repository.
|
|
381
|
+
## Oxlint and Oxfmt configurations
|
|
305
382
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
installs never float to a newer upstream commit; only a reviewed catalog refresh
|
|
310
|
-
changes what is approved.
|
|
311
|
-
|
|
312
|
-
## Oxlint preset for Vite+
|
|
313
|
-
|
|
314
|
-
Dev Kit exports a typed, composable set of high-signal Oxlint rules for Vite+
|
|
315
|
-
projects:
|
|
383
|
+
Dev Kit exports one typed Oxlint ruleset and one typed Oxfmt configuration for
|
|
384
|
+
both standalone Oxc projects and Vite+ projects. A Vite+ project composes them
|
|
385
|
+
in `vite.config.ts`:
|
|
316
386
|
|
|
317
387
|
```ts
|
|
318
388
|
import { recommendedOxlintConfig } from "@danieljvdm/dev-kit/oxlint";
|
|
389
|
+
import { recommendedOxfmtConfig } from "@danieljvdm/dev-kit/oxfmt";
|
|
319
390
|
import { defineConfig } from "vite-plus";
|
|
320
391
|
|
|
321
392
|
export default defineConfig({
|
|
393
|
+
fmt: {
|
|
394
|
+
...recommendedOxfmtConfig,
|
|
395
|
+
},
|
|
322
396
|
lint: {
|
|
323
397
|
extends: [recommendedOxlintConfig],
|
|
324
398
|
rules: {
|
|
@@ -329,7 +403,38 @@ export default defineConfig({
|
|
|
329
403
|
```
|
|
330
404
|
|
|
331
405
|
Use `lint.extends` rather than a shallow object spread so Vite+ composes the
|
|
332
|
-
nested plugin and rule configuration correctly.
|
|
406
|
+
nested plugin and rule configuration correctly. Oxfmt has no `extends`, so
|
|
407
|
+
spread its configuration before project-local formatter options.
|
|
408
|
+
|
|
409
|
+
Standalone projects import the same objects from their native config files:
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
// oxlint.config.ts
|
|
413
|
+
import { recommendedOxlintConfig } from "@danieljvdm/dev-kit/oxlint";
|
|
414
|
+
import { defineConfig } from "oxlint";
|
|
415
|
+
|
|
416
|
+
export default defineConfig({
|
|
417
|
+
extends: [recommendedOxlintConfig],
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
// oxfmt.config.ts
|
|
423
|
+
import { recommendedOxfmtConfig } from "@danieljvdm/dev-kit/oxfmt";
|
|
424
|
+
import { defineConfig } from "oxfmt";
|
|
425
|
+
|
|
426
|
+
export default defineConfig({
|
|
427
|
+
...recommendedOxfmtConfig,
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
The Oxlint preset also registers the shared `effect` JavaScript plugin. Effect
|
|
432
|
+
projects opt into its rules in path-specific overrides, for example
|
|
433
|
+
`effect/no-effect-run`, `effect/no-unsafe-promise`, and
|
|
434
|
+
`effect/no-untyped-throw`. The package exports the plugin directly from
|
|
435
|
+
`@danieljvdm/dev-kit/oxlint-plugin-effect` for configurations that do not
|
|
436
|
+
extend the recommended preset. Strict workflow, Atom, and boundary rules remain
|
|
437
|
+
consumer-scoped because application and host boundaries differ by repository.
|
|
333
438
|
|
|
334
439
|
## Development
|
|
335
440
|
|
package/dev-kit.example.jsonc
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danieljvdm/dev-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Declarative project development toolkit with portable agent skills.",
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
"./oxlint": {
|
|
24
24
|
"types": "./src/oxlint.ts",
|
|
25
25
|
"default": "./src/oxlint.js"
|
|
26
|
+
},
|
|
27
|
+
"./oxfmt": {
|
|
28
|
+
"types": "./src/oxfmt.ts",
|
|
29
|
+
"default": "./src/oxfmt.js"
|
|
30
|
+
},
|
|
31
|
+
"./oxlint-plugin-effect": {
|
|
32
|
+
"types": "./src/oxlint-plugin-effect.d.ts",
|
|
33
|
+
"default": "./src/oxlint-plugin-effect.js"
|
|
26
34
|
}
|
|
27
35
|
},
|
|
28
36
|
"files": [
|
|
@@ -64,14 +72,24 @@
|
|
|
64
72
|
"@effect/tsgo": "0.24.3",
|
|
65
73
|
"@effect/vitest": "4.0.0-beta.102",
|
|
66
74
|
"@types/node": "25.9.1",
|
|
75
|
+
"oxfmt": "0.60.0",
|
|
76
|
+
"oxlint": "1.75.0",
|
|
67
77
|
"typescript": "7.0.2",
|
|
68
78
|
"vite-plus": "0.2.6",
|
|
69
79
|
"vitest": "4.1.10"
|
|
70
80
|
},
|
|
71
81
|
"peerDependencies": {
|
|
82
|
+
"oxfmt": ">=0.60.0 <0.61.0",
|
|
83
|
+
"oxlint": ">=1.75.0 <2.0.0",
|
|
72
84
|
"vite-plus": ">=0.2.6 <0.3.0"
|
|
73
85
|
},
|
|
74
86
|
"peerDependenciesMeta": {
|
|
87
|
+
"oxfmt": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"oxlint": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
75
93
|
"vite-plus": {
|
|
76
94
|
"optional": true
|
|
77
95
|
}
|
|
@@ -10,21 +10,21 @@
|
|
|
10
10
|
"type": "string"
|
|
11
11
|
},
|
|
12
12
|
"include": {
|
|
13
|
-
"description": "
|
|
13
|
+
"description": "Static skill names, family names, or exact <package>#<skill> selectors to sync. Installed package skills are discovered for browsing but selected only when explicitly included.",
|
|
14
14
|
"type": "array",
|
|
15
15
|
"items": {
|
|
16
16
|
"type": "string",
|
|
17
|
-
"pattern": "^[a-z0-9]+(-[a-z0-9]+)
|
|
17
|
+
"pattern": "^(?:[a-z0-9]+(?:-[a-z0-9]+)*|(?:[a-z0-9][a-z0-9._-]*|@[a-z0-9][a-z0-9._-]*/[a-z0-9][a-z0-9._-]*)#[a-z0-9]+(?:-[a-z0-9]+)*)$"
|
|
18
18
|
},
|
|
19
19
|
"uniqueItems": true,
|
|
20
20
|
"minItems": 0
|
|
21
21
|
},
|
|
22
22
|
"exclude": {
|
|
23
|
-
"description": "
|
|
23
|
+
"description": "Static skill names, family names, or exact <package>#<skill> selectors to remove after expanding includes.",
|
|
24
24
|
"type": "array",
|
|
25
25
|
"items": {
|
|
26
26
|
"type": "string",
|
|
27
|
-
"pattern": "^[a-z0-9]+(-[a-z0-9]+)
|
|
27
|
+
"pattern": "^(?:[a-z0-9]+(?:-[a-z0-9]+)*|(?:[a-z0-9][a-z0-9._-]*|@[a-z0-9][a-z0-9._-]*/[a-z0-9][a-z0-9._-]*)#[a-z0-9]+(?:-[a-z0-9]+)*)$"
|
|
28
28
|
},
|
|
29
29
|
"uniqueItems": true,
|
|
30
30
|
"default": []
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
"type": "object",
|
|
35
35
|
"additionalProperties": false,
|
|
36
36
|
"properties": {
|
|
37
|
+
"claudeInstructions": {
|
|
38
|
+
"$ref": "#/$defs/claudeInstructionsSetup"
|
|
39
|
+
},
|
|
37
40
|
"effectSource": {
|
|
38
41
|
"$ref": "#/$defs/effectSourceSetup"
|
|
39
42
|
},
|
|
@@ -58,6 +61,17 @@
|
|
|
58
61
|
},
|
|
59
62
|
"required": ["include"],
|
|
60
63
|
"$defs": {
|
|
64
|
+
"claudeInstructionsSetup": {
|
|
65
|
+
"description": "Manage CLAUDE.md as a relative symlink to the project-root AGENTS.md file.",
|
|
66
|
+
"type": "object",
|
|
67
|
+
"additionalProperties": false,
|
|
68
|
+
"properties": {
|
|
69
|
+
"enabled": {
|
|
70
|
+
"type": "boolean",
|
|
71
|
+
"default": false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
61
75
|
"effectSourceSetup": {
|
|
62
76
|
"type": "object",
|
|
63
77
|
"additionalProperties": false,
|
package/skills/dev-kit/SKILL.md
CHANGED
|
@@ -67,6 +67,9 @@ skill as `dev-kit` when project agents should carry the toolkit procedure.
|
|
|
67
67
|
"$schema": "./node_modules/@danieljvdm/dev-kit/schema/dev-kit.schema.json",
|
|
68
68
|
"include": ["dev-kit", "effect"],
|
|
69
69
|
"exclude": [],
|
|
70
|
+
"setup": {
|
|
71
|
+
"claudeInstructions": { "enabled": true }
|
|
72
|
+
},
|
|
70
73
|
"targets": {
|
|
71
74
|
"agents": { "enabled": true, "mode": "copy" },
|
|
72
75
|
"claude": { "enabled": true, "mode": "symlink" },
|
|
@@ -80,6 +83,12 @@ use symlinks for additional harness discovery paths. Keep every target path
|
|
|
80
83
|
project-relative and separate from the manifest, lock, state, and process-lock
|
|
81
84
|
paths.
|
|
82
85
|
|
|
86
|
+
Enable `setup.claudeInstructions` when Claude Code should consume the same
|
|
87
|
+
project-root instructions as Codex. It manages `CLAUDE.md` as a relative
|
|
88
|
+
symlink to an existing `AGENTS.md`. Preserve any conflicting `CLAUDE.md`; when
|
|
89
|
+
disabled, dev-kit removes only an unchanged link recorded in local ownership
|
|
90
|
+
state.
|
|
91
|
+
|
|
83
92
|
## Ownership and conflicts
|
|
84
93
|
|
|
85
94
|
Dev-kit adopts an existing destination only when its digest exactly matches a
|
|
@@ -156,16 +165,19 @@ dependencies; `dev-kit apply` patches once and then converges.
|
|
|
156
165
|
Use `dev-kit tsgo patch --dry-run` for focused diagnosis. Use `--force` only
|
|
157
166
|
after the user accepts a potentially commit-incompatible TypeScript binary.
|
|
158
167
|
|
|
159
|
-
## Oxlint
|
|
168
|
+
## Oxlint and Oxfmt configurations
|
|
160
169
|
|
|
161
|
-
|
|
162
|
-
project-specific lint configuration:
|
|
170
|
+
Use Dev Kit's canonical Oxlint and Oxfmt objects in Vite+ projects:
|
|
163
171
|
|
|
164
172
|
```ts
|
|
165
173
|
import { recommendedOxlintConfig } from "@danieljvdm/dev-kit/oxlint";
|
|
174
|
+
import { recommendedOxfmtConfig } from "@danieljvdm/dev-kit/oxfmt";
|
|
166
175
|
import { defineConfig } from "vite-plus";
|
|
167
176
|
|
|
168
177
|
export default defineConfig({
|
|
178
|
+
fmt: {
|
|
179
|
+
...recommendedOxfmtConfig,
|
|
180
|
+
},
|
|
169
181
|
lint: {
|
|
170
182
|
extends: [recommendedOxlintConfig],
|
|
171
183
|
rules: {
|
|
@@ -176,14 +188,25 @@ export default defineConfig({
|
|
|
176
188
|
```
|
|
177
189
|
|
|
178
190
|
Use `lint.extends` instead of spreading the object so Vite+ composes nested
|
|
179
|
-
rule maps correctly.
|
|
180
|
-
|
|
191
|
+
rule maps correctly. Oxfmt has no inheritance mechanism, so spread its object
|
|
192
|
+
before project-local options. Standalone `oxlint.config.ts` uses the same
|
|
193
|
+
`extends: [recommendedOxlintConfig]`; standalone `oxfmt.config.ts` spreads the
|
|
194
|
+
same `recommendedOxfmtConfig`.
|
|
195
|
+
|
|
196
|
+
The Oxlint preset registers Dev Kit's shared Effect plugin as `effect`, but
|
|
197
|
+
does not enable its scope-sensitive rules globally. Effect projects should
|
|
198
|
+
enable rules such as `effect/no-effect-run`, `effect/no-unsafe-promise`, and
|
|
199
|
+
`effect/no-untyped-throw` only in Effect-owned code, with explicit exceptions
|
|
200
|
+
for tests and host boundaries. The stricter `effect/no-async-workflow`,
|
|
201
|
+
`effect/no-promise-atom-mode`, and `effect/no-sync-boundary-decode` rules also
|
|
202
|
+
need consumer-owned scopes. Keep repository-specific paths and platform rules
|
|
203
|
+
in the consuming project.
|
|
181
204
|
|
|
182
205
|
## Current boundary
|
|
183
206
|
|
|
184
|
-
Manage skill outputs, the `setup.
|
|
185
|
-
`setup.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
them.
|
|
207
|
+
Manage skill outputs, the `setup.claudeInstructions` link, the
|
|
208
|
+
`setup.effectSource` checkout, and the explicit `setup.effectTsgo` task. Edit
|
|
209
|
+
shared `package.json` and `tsconfig.json`
|
|
210
|
+
contributions deliberately. The Oxlint and Oxfmt configurations are composable
|
|
211
|
+
package exports, not manifest-managed outputs. Treat broader setup tasks as
|
|
212
|
+
future manifest capabilities until the installed CLI exposes them.
|
package/src/bin/dev-kit.ts
CHANGED
|
@@ -57,7 +57,7 @@ const addCommand = CliCommand.make(
|
|
|
57
57
|
skills.length === 0
|
|
58
58
|
? chooseSkillsToAdd({ apply: !noApply, manifestPath: manifest, projectDir })
|
|
59
59
|
: addSkills(skills, { apply: !noApply, manifestPath: manifest, projectDir }),
|
|
60
|
-
).pipe(CliCommand.withDescription("Select and install one or more
|
|
60
|
+
).pipe(CliCommand.withDescription("Select and install one or more available skills."));
|
|
61
61
|
|
|
62
62
|
const removeCommand = CliCommand.make(
|
|
63
63
|
"remove",
|
|
@@ -89,13 +89,13 @@ const searchCommand = CliCommand.make(
|
|
|
89
89
|
{ query: Argument.string("query").pipe(Argument.variadic({ min: 1 })), ...projectFlags },
|
|
90
90
|
({ query, manifest, projectDir }) =>
|
|
91
91
|
listSkills({ all: true, query: query.join(" "), manifestPath: manifest, projectDir }),
|
|
92
|
-
).pipe(CliCommand.withDescription("Search
|
|
92
|
+
).pipe(CliCommand.withDescription("Search available skill names and descriptions."));
|
|
93
93
|
|
|
94
94
|
const infoCommand = CliCommand.make(
|
|
95
95
|
"info",
|
|
96
|
-
{ skill: Argument.string("skill") },
|
|
97
|
-
({ skill }) => showSkill(skill),
|
|
98
|
-
).pipe(CliCommand.withDescription("Show provenance and details for an
|
|
96
|
+
{ skill: Argument.string("skill"), ...projectFlags },
|
|
97
|
+
({ skill, manifest, projectDir }) => showSkill(skill, { manifestPath: manifest, projectDir }),
|
|
98
|
+
).pipe(CliCommand.withDescription("Show provenance and details for an available skill."));
|
|
99
99
|
|
|
100
100
|
const planCommand = CliCommand.make(
|
|
101
101
|
"plan",
|
package/src/catalog.ts
CHANGED
|
@@ -2,7 +2,11 @@ import { parse as parseJsonc, type ParseError } from "jsonc-parser";
|
|
|
2
2
|
import { Effect, FileSystem, Path, Schema, Stream } from "effect";
|
|
3
3
|
import { ChildProcess } from "effect/unstable/process";
|
|
4
4
|
|
|
5
|
-
import { observePath } from "./path-digest.ts";
|
|
5
|
+
import { observePath, type Digest } from "./path-digest.ts";
|
|
6
|
+
import {
|
|
7
|
+
discoverPackageSkills,
|
|
8
|
+
resolvePackageSkillSelector,
|
|
9
|
+
} from "./package-skill-source.ts";
|
|
6
10
|
import {
|
|
7
11
|
SkillSourcesLockSchema,
|
|
8
12
|
type LockedSkillSource,
|
|
@@ -11,9 +15,14 @@ import {
|
|
|
11
15
|
|
|
12
16
|
export type CatalogSkill = {
|
|
13
17
|
readonly name: string;
|
|
18
|
+
readonly selector: string;
|
|
14
19
|
readonly description: string;
|
|
15
20
|
readonly source: string;
|
|
16
21
|
readonly bundled: boolean;
|
|
22
|
+
readonly package?: {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly version: string;
|
|
25
|
+
};
|
|
17
26
|
};
|
|
18
27
|
|
|
19
28
|
export type SkillCatalog = {
|
|
@@ -24,11 +33,19 @@ export type SkillCatalog = {
|
|
|
24
33
|
|
|
25
34
|
export type ResolvedSkillSource = {
|
|
26
35
|
readonly path: string;
|
|
27
|
-
readonly
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
readonly linkPath?: string;
|
|
37
|
+
readonly catalog?:
|
|
38
|
+
| {
|
|
39
|
+
readonly source: string;
|
|
40
|
+
readonly repository: string;
|
|
41
|
+
readonly resolved: string;
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
readonly package: string;
|
|
45
|
+
readonly version: string;
|
|
46
|
+
readonly skill: string;
|
|
47
|
+
readonly digest: Digest;
|
|
48
|
+
};
|
|
32
49
|
};
|
|
33
50
|
|
|
34
51
|
class CatalogError extends Schema.TaggedErrorClass<CatalogError>()("CatalogError", {
|
|
@@ -87,6 +104,7 @@ const readDescription = Effect.fn("readSkillDescription")(function* (skillPath:
|
|
|
87
104
|
|
|
88
105
|
export const loadSkillCatalog = Effect.fn("loadSkillCatalog")(function* (
|
|
89
106
|
packageRoot: string,
|
|
107
|
+
projectDir: string,
|
|
90
108
|
) {
|
|
91
109
|
const fs = yield* FileSystem.FileSystem;
|
|
92
110
|
const path = yield* Path.Path;
|
|
@@ -98,6 +116,7 @@ export const loadSkillCatalog = Effect.fn("loadSkillCatalog")(function* (
|
|
|
98
116
|
if ((yield* fs.exists(path.join(skillPath, "SKILL.md")))) {
|
|
99
117
|
skills.push({
|
|
100
118
|
name,
|
|
119
|
+
selector: name,
|
|
101
120
|
description: yield* readDescription(skillPath),
|
|
102
121
|
source: "built-in",
|
|
103
122
|
bundled: true,
|
|
@@ -110,28 +129,49 @@ export const loadSkillCatalog = Effect.fn("loadSkillCatalog")(function* (
|
|
|
110
129
|
for (const name of source.skills) {
|
|
111
130
|
skills.push({
|
|
112
131
|
name,
|
|
132
|
+
selector: name,
|
|
113
133
|
description: source.descriptions?.[name] ?? "",
|
|
114
134
|
source: source.id,
|
|
115
135
|
bundled: false,
|
|
116
136
|
});
|
|
117
137
|
}
|
|
118
138
|
}
|
|
139
|
+
const discovery = yield* discoverPackageSkills(projectDir);
|
|
140
|
+
for (const candidate of discovery.candidates) {
|
|
141
|
+
skills.push({
|
|
142
|
+
name: candidate.name,
|
|
143
|
+
selector: candidate.selector,
|
|
144
|
+
description: candidate.description,
|
|
145
|
+
source: candidate.package,
|
|
146
|
+
bundled: false,
|
|
147
|
+
package: { name: candidate.package, version: candidate.version },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
119
150
|
const duplicates = skills.filter(
|
|
120
|
-
(skill, index) => skills.findIndex((candidate) => candidate.
|
|
151
|
+
(skill, index) => skills.findIndex((candidate) => candidate.selector === skill.selector) !== index,
|
|
121
152
|
);
|
|
122
153
|
if (duplicates.length > 0) {
|
|
123
154
|
return yield* new CatalogError({
|
|
124
|
-
message: `duplicate catalog skill: ${duplicates[0]?.
|
|
155
|
+
message: `duplicate catalog skill selector: ${duplicates[0]?.selector ?? "unknown"}`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const externalFamilies = (lock?.sources ?? []).map((source) =>
|
|
159
|
+
[source.id, source.skills] as const
|
|
160
|
+
);
|
|
161
|
+
const duplicateFamily = externalFamilies.find(
|
|
162
|
+
([id], index) => externalFamilies.findIndex(([candidate]) => candidate === id) !== index,
|
|
163
|
+
);
|
|
164
|
+
if (duplicateFamily !== undefined) {
|
|
165
|
+
return yield* new CatalogError({
|
|
166
|
+
message: `duplicate catalog family: ${duplicateFamily[0]}`,
|
|
125
167
|
});
|
|
126
168
|
}
|
|
127
169
|
const families: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
128
170
|
effect: ["effect-ts"],
|
|
129
|
-
...Object.fromEntries(
|
|
130
|
-
(lock?.sources ?? []).map((source) => [source.id, source.skills]),
|
|
131
|
-
),
|
|
171
|
+
...Object.fromEntries(externalFamilies),
|
|
132
172
|
};
|
|
133
173
|
return {
|
|
134
|
-
skills: skills.sort((left, right) => left.
|
|
174
|
+
skills: skills.sort((left, right) => left.selector.localeCompare(right.selector)),
|
|
135
175
|
families,
|
|
136
176
|
...(lock ? { lock } : {}),
|
|
137
177
|
} satisfies SkillCatalog;
|
|
@@ -224,15 +264,15 @@ const materializeSource = Effect.fn("materializeCatalogSource")(function* (
|
|
|
224
264
|
export const resolveSkillSources = Effect.fn("resolveSkillSources")(function* (
|
|
225
265
|
packageRoot: string,
|
|
226
266
|
projectDir: string,
|
|
267
|
+
catalog: SkillCatalog,
|
|
227
268
|
selected: ReadonlyArray<string>,
|
|
228
269
|
cache = true,
|
|
229
270
|
) {
|
|
230
271
|
const path = yield* Path.Path;
|
|
231
|
-
const catalog = yield* loadSkillCatalog(packageRoot);
|
|
232
272
|
const sources = new Map<string, ResolvedSkillSource>();
|
|
233
273
|
for (const skill of catalog.skills.filter((skill) => skill.bundled)) {
|
|
234
|
-
if (selected.includes(skill.
|
|
235
|
-
sources.set(skill.
|
|
274
|
+
if (selected.includes(skill.selector)) {
|
|
275
|
+
sources.set(skill.selector, { path: path.join(packageRoot, "skills", skill.name) });
|
|
236
276
|
}
|
|
237
277
|
}
|
|
238
278
|
for (const source of catalog.lock?.sources ?? []) {
|
|
@@ -242,5 +282,22 @@ export const resolveSkillSources = Effect.fn("resolveSkillSources")(function* (
|
|
|
242
282
|
sources.set(name, sourcePath);
|
|
243
283
|
}
|
|
244
284
|
}
|
|
285
|
+
for (const selector of selected.filter((value) => value.includes("#"))) {
|
|
286
|
+
const resolved = yield* resolvePackageSkillSelector(projectDir, selector);
|
|
287
|
+
const observation = yield* observePath(resolved.path);
|
|
288
|
+
if (observation.kind !== "directory") {
|
|
289
|
+
return yield* new CatalogError({ message: `package skill is missing: ${selector}` });
|
|
290
|
+
}
|
|
291
|
+
sources.set(selector, {
|
|
292
|
+
path: resolved.path,
|
|
293
|
+
linkPath: resolved.linkPath,
|
|
294
|
+
catalog: {
|
|
295
|
+
package: resolved.package,
|
|
296
|
+
version: resolved.version,
|
|
297
|
+
skill: resolved.name,
|
|
298
|
+
digest: observation.digest,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
245
302
|
return sources;
|
|
246
303
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export {
|
|
2
|
+
type ClaudeInstructionsSetup,
|
|
3
|
+
ClaudeInstructionsSetupSchema,
|
|
2
4
|
type DevKitManifest,
|
|
3
5
|
DevKitManifestSchema,
|
|
4
6
|
type EffectSourceSetup,
|
|
@@ -58,15 +60,21 @@ export {
|
|
|
58
60
|
} from "./sync.ts";
|
|
59
61
|
export {
|
|
60
62
|
AppliedStateSchema,
|
|
63
|
+
CatalogProvenanceSchema,
|
|
61
64
|
DevKitLockSchema,
|
|
62
65
|
EffectSourceLockSchema,
|
|
63
66
|
EffectTsgoLockSchema,
|
|
67
|
+
ManagedInstructionOutputSchema,
|
|
68
|
+
ManagedOutputSchema,
|
|
64
69
|
ManagedSkillOutputSchema,
|
|
65
70
|
OwnershipReceiptSchema,
|
|
66
71
|
type AppliedState,
|
|
72
|
+
type CatalogProvenance,
|
|
67
73
|
type DevKitLock,
|
|
68
74
|
type EffectSourceLock,
|
|
69
75
|
type EffectTsgoLock,
|
|
76
|
+
type ManagedInstructionOutput,
|
|
77
|
+
type ManagedOutput,
|
|
70
78
|
type ManagedSkillOutput,
|
|
71
79
|
type OwnershipReceipt,
|
|
72
80
|
} from "./project-state.ts";
|