@danieljvdm/dev-kit 0.4.0 → 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 +99 -30
- package/dev-kit.example.jsonc +1 -0
- package/package.json +1 -1
- package/schema/dev-kit.schema.json +18 -4
- package/skills/dev-kit/SKILL.md +12 -2
- 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/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.
|
|
252
308
|
|
|
253
|
-
|
|
254
|
-
|
|
309
|
+
The initial compatibility boundary is intentionally small and deterministic:
|
|
310
|
+
|
|
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,38 +358,25 @@ 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.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
`skill-sources.lock.json`. It does not copy upstream skill trees into this
|
|
304
|
-
repository.
|
|
305
|
-
|
|
306
|
-
When a consuming project selects an external skill, Dev Kit fetches that exact
|
|
307
|
-
approved commit into the ignored `.dev-kit/cache`, applies declared compatibility
|
|
308
|
-
transforms, and installs the result through the ownership-safe sync path. Normal
|
|
309
|
-
installs never float to a newer upstream commit; only a reviewed catalog refresh
|
|
310
|
-
changes what is approved.
|
|
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`.
|
|
375
|
+
|
|
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.
|
|
311
380
|
|
|
312
381
|
## Oxlint and Oxfmt configurations
|
|
313
382
|
|
package/dev-kit.example.jsonc
CHANGED
package/package.json
CHANGED
|
@@ -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
|
|
@@ -195,8 +204,9 @@ in the consuming project.
|
|
|
195
204
|
|
|
196
205
|
## Current boundary
|
|
197
206
|
|
|
198
|
-
Manage skill outputs, the `setup.
|
|
199
|
-
`setup.
|
|
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`
|
|
200
210
|
contributions deliberately. The Oxlint and Oxfmt configurations are composable
|
|
201
211
|
package exports, not manifest-managed outputs. Treat broader setup tasks as
|
|
202
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";
|
package/src/manifest.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
|
|
3
|
+
import { SKILL_SELECTOR_PATTERN } from "./skill-selector.ts";
|
|
3
4
|
import { TYPESCRIPT_PACKAGE_NAME_PATTERN } from "./typescript-package-name.ts";
|
|
4
5
|
|
|
5
6
|
export type HarnessTarget = "agents" | "claude" | "opencode";
|
|
@@ -36,12 +37,19 @@ export const EffectSourceSetupSchema = Schema.Struct({
|
|
|
36
37
|
|
|
37
38
|
export type EffectSourceSetup = typeof EffectSourceSetupSchema.Type;
|
|
38
39
|
|
|
40
|
+
export const ClaudeInstructionsSetupSchema = Schema.Struct({
|
|
41
|
+
enabled: Schema.optional(Schema.Boolean),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export type ClaudeInstructionsSetup = typeof ClaudeInstructionsSetupSchema.Type;
|
|
45
|
+
|
|
39
46
|
export const DevKitManifestSchema = Schema.Struct({
|
|
40
47
|
$schema: Schema.optional(Schema.String),
|
|
41
|
-
include: Schema.Array(Schema.String),
|
|
42
|
-
exclude: Schema.optional(Schema.Array(Schema.String)),
|
|
48
|
+
include: Schema.Array(Schema.String.check(Schema.isPattern(SKILL_SELECTOR_PATTERN))),
|
|
49
|
+
exclude: Schema.optional(Schema.Array(Schema.String.check(Schema.isPattern(SKILL_SELECTOR_PATTERN)))),
|
|
43
50
|
setup: Schema.optional(
|
|
44
51
|
Schema.Struct({
|
|
52
|
+
claudeInstructions: Schema.optional(ClaudeInstructionsSetupSchema),
|
|
45
53
|
effectSource: Schema.optional(EffectSourceSetupSchema),
|
|
46
54
|
effectTsgo: Schema.optional(EffectTsgoSetupSchema),
|
|
47
55
|
}),
|
|
@@ -67,6 +75,9 @@ export type NormalizedManifest = {
|
|
|
67
75
|
readonly include: ReadonlyArray<string>;
|
|
68
76
|
readonly exclude: ReadonlyArray<string>;
|
|
69
77
|
readonly setup: {
|
|
78
|
+
readonly claudeInstructions: {
|
|
79
|
+
readonly enabled: boolean;
|
|
80
|
+
};
|
|
70
81
|
readonly effectSource: {
|
|
71
82
|
readonly enabled: boolean;
|
|
72
83
|
readonly packageName: string;
|
|
@@ -114,6 +125,9 @@ export const normalizeManifest = (manifest: DevKitManifest): NormalizedManifest
|
|
|
114
125
|
exclude: manifest.exclude ?? [],
|
|
115
126
|
include: manifest.include,
|
|
116
127
|
setup: {
|
|
128
|
+
claudeInstructions: {
|
|
129
|
+
enabled: manifest.setup?.claudeInstructions?.enabled ?? false,
|
|
130
|
+
},
|
|
117
131
|
effectSource: {
|
|
118
132
|
enabled: manifest.setup?.effectSource?.enabled ?? false,
|
|
119
133
|
packageName: manifest.setup?.effectSource?.packageName ?? "effect",
|