@danieljvdm/dev-kit 0.2.0 → 0.2.2
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 +31 -11
- package/dev-kit.example.jsonc +1 -1
- package/package.json +2 -2
- package/schema/dev-kit.schema.json +1 -1
- package/schema/skill-sources.schema.json +1 -1
- package/skills/dev-kit/SKILL.md +13 -7
- package/src/project-process-lock.ts +1 -1
- package/src/skill-manager.ts +85 -14
- package/src/sync.ts +27 -3
- package/src/vendor.ts +2 -0
package/README.md
CHANGED
|
@@ -14,18 +14,29 @@ collection of unrelated postinstall scripts. It can:
|
|
|
14
14
|
|
|
15
15
|
## Quick start
|
|
16
16
|
|
|
17
|
-
Install Dev Kit
|
|
17
|
+
Install the published Dev Kit package:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
bun add -d
|
|
20
|
+
bun add -d @danieljvdm/dev-kit
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Initialize the project, browse the approved catalog, and add skills:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
bun x dev-kit init
|
|
27
|
+
bun x dev-kit list --all
|
|
28
|
+
bun x dev-kit add dev-kit effect
|
|
29
|
+
```
|
|
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:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bun x dev-kit search cloudflare
|
|
38
|
+
bun x dev-kit info workers-best-practices
|
|
39
|
+
bun x dev-kit add workers-best-practices wrangler
|
|
29
40
|
```
|
|
30
41
|
|
|
31
42
|
`add` updates `dev-kit.jsonc` and applies the selection immediately. The
|
|
@@ -49,8 +60,8 @@ or use `--no-apply` to edit the manifest without syncing yet.
|
|
|
49
60
|
For review-first workflows, edit the manifest or pass `--no-apply`, then:
|
|
50
61
|
|
|
51
62
|
```bash
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
bun x dev-kit plan
|
|
64
|
+
bun x dev-kit apply
|
|
54
65
|
```
|
|
55
66
|
|
|
56
67
|
Commit the generated `dev-kit.lock.json`, then use locked mode in your package
|
|
@@ -135,7 +146,13 @@ tool versions. A project-local process lock also prevents concurrent applies.
|
|
|
135
146
|
```jsonc
|
|
136
147
|
{
|
|
137
148
|
"$schema": "./node_modules/@danieljvdm/dev-kit/schema/dev-kit.schema.json",
|
|
138
|
-
"include": [
|
|
149
|
+
"include": [
|
|
150
|
+
"dev-kit",
|
|
151
|
+
"effect",
|
|
152
|
+
"workers-best-practices",
|
|
153
|
+
"wrangler",
|
|
154
|
+
"serve-sim"
|
|
155
|
+
],
|
|
139
156
|
"exclude": ["animation-vocabulary"],
|
|
140
157
|
"targets": {
|
|
141
158
|
"agents": { "enabled": true, "mode": "copy" },
|
|
@@ -147,8 +164,11 @@ tool versions. A project-local process lock also prevents concurrent applies.
|
|
|
147
164
|
|
|
148
165
|
- `dev-kit` installs guidance for operating the toolkit itself.
|
|
149
166
|
- `effect` expands to the consolidated `effect-ts` skill.
|
|
150
|
-
-
|
|
151
|
-
|
|
167
|
+
- Prefer individual external skills such as `workers-best-practices` and
|
|
168
|
+
`wrangler`, selected after scanning the project for relevant technologies.
|
|
169
|
+
- `serve-sim` selects the approved Evan Bacon simulator skill directly.
|
|
170
|
+
- An approved source ID is broad shorthand that selects every skill from that
|
|
171
|
+
source. Use it only when the scan confirms that every member applies.
|
|
152
172
|
|
|
153
173
|
Dev Kit reserves `.repos/<source-id>` for project-local source checkouts. Run
|
|
154
174
|
`dev-kit gitignore` to add `.repos/` and `.dev-kit/` to the project ignore file.
|
|
@@ -196,7 +216,7 @@ Pin the compatible packages in the consuming project:
|
|
|
196
216
|
```jsonc
|
|
197
217
|
{
|
|
198
218
|
"devDependencies": {
|
|
199
|
-
"@danieljvdm/dev-kit": "
|
|
219
|
+
"@danieljvdm/dev-kit": "^0.2.0",
|
|
200
220
|
"@effect/tsgo": "0.24.3",
|
|
201
221
|
"typescript": "7.0.2"
|
|
202
222
|
}
|
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.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Declarative project development toolkit with portable agent skills.",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"prepare": "./bin/dev-kit.mjs apply --locked",
|
|
35
35
|
"dev-kit": "./bin/dev-kit.mjs",
|
|
36
36
|
"changeset": "changeset",
|
|
37
|
-
"version-packages": "changeset version",
|
|
37
|
+
"version-packages": "changeset version && ./bin/dev-kit.mjs apply",
|
|
38
38
|
"release": "changeset publish",
|
|
39
39
|
"check": "tsc --noEmit",
|
|
40
40
|
"plan": "./bin/dev-kit.mjs plan",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "string"
|
|
11
11
|
},
|
|
12
12
|
"include": {
|
|
13
|
-
"description": "Skill names or family names to sync.
|
|
13
|
+
"description": "Skill names or family names to sync. Prefer individually relevant external skills; an external source id selects every approved skill from that source.",
|
|
14
14
|
"type": "array",
|
|
15
15
|
"items": {
|
|
16
16
|
"type": "string",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"additionalProperties": false,
|
|
24
24
|
"properties": {
|
|
25
25
|
"id": {
|
|
26
|
-
"description": "Unique source id. It also becomes
|
|
26
|
+
"description": "Unique source id. It also becomes broad family shorthand that selects every approved source skill in project manifests.",
|
|
27
27
|
"type": "string",
|
|
28
28
|
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
|
|
29
29
|
},
|
package/skills/dev-kit/SKILL.md
CHANGED
|
@@ -21,11 +21,15 @@ stores explicit skill names and exact commit/content digests.
|
|
|
21
21
|
|
|
22
22
|
## Apply loop
|
|
23
23
|
|
|
24
|
-
1. Establish the Git root. Read `dev-kit.jsonc`,
|
|
25
|
-
present,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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.
|
|
29
33
|
3. Run `dev-kit plan`. Use `--manifest`, `--project-dir`, or `--lockfile` when
|
|
30
34
|
the project overrides their defaults. Planning is read-only; inspect every
|
|
31
35
|
create, update, remove, adoption, and conflict before proceeding. Finish
|
|
@@ -39,8 +43,10 @@ stores explicit skill names and exact commit/content digests.
|
|
|
39
43
|
## Manifest
|
|
40
44
|
|
|
41
45
|
Use skill names or family names in `include`; subtract selections with
|
|
42
|
-
`exclude`.
|
|
43
|
-
|
|
46
|
+
`exclude`. Built-in families such as `effect` are intentional bundles. An
|
|
47
|
+
external source ID is also a family, but expands to every approved skill from
|
|
48
|
+
that source, so prefer individually relevant external skills. Include this
|
|
49
|
+
skill as `dev-kit` when project agents should carry the toolkit procedure.
|
|
44
50
|
|
|
45
51
|
```jsonc
|
|
46
52
|
{
|
|
@@ -9,7 +9,7 @@ export class ProjectAlreadyLockedError extends Schema.TaggedErrorClass<ProjectAl
|
|
|
9
9
|
{ path: Schema.String },
|
|
10
10
|
) {
|
|
11
11
|
override get message() {
|
|
12
|
-
return `another dev-kit
|
|
12
|
+
return `another dev-kit operation may be active (${this.path}); verify the owner before removing a stale lock`;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
package/src/skill-manager.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Prompt } from "effect/unstable/cli";
|
|
|
5
5
|
import { loadSkillCatalog } from "./catalog.ts";
|
|
6
6
|
import { isInteractiveTerminal, printDetail, printLine, printStatus } from "./cli-ui.ts";
|
|
7
7
|
import { DevKitManifestSchema } from "./manifest.ts";
|
|
8
|
+
import { observeSymbolicLink } from "./node-symbolic-link.ts";
|
|
8
9
|
import { runProjectSkillPlan } from "./sync.ts";
|
|
9
10
|
import { patchProjectGitignore } from "./gitignore.ts";
|
|
10
11
|
|
|
@@ -27,22 +28,83 @@ const packageRoot = Effect.fn("skillManagerPackageRoot")(function* () {
|
|
|
27
28
|
const resolvePaths = Effect.fn("resolveSkillManagerPaths")(function* (
|
|
28
29
|
options: ManagerOptions,
|
|
29
30
|
) {
|
|
31
|
+
const fs = yield* FileSystem.FileSystem;
|
|
30
32
|
const path = yield* Path.Path;
|
|
31
33
|
const projectDir = path.resolve(options.projectDir ?? ".");
|
|
34
|
+
const candidate = options.manifestPath ?? "dev-kit.jsonc";
|
|
35
|
+
if (candidate.length === 0 || path.isAbsolute(candidate)) {
|
|
36
|
+
return yield* new SkillManagerError({
|
|
37
|
+
message: "--manifest must be a non-empty project-relative path",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const manifestPath = path.resolve(projectDir, candidate);
|
|
41
|
+
const relative = path.relative(projectDir, manifestPath);
|
|
42
|
+
if (
|
|
43
|
+
relative === ".." ||
|
|
44
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
45
|
+
path.isAbsolute(relative)
|
|
46
|
+
) {
|
|
47
|
+
return yield* new SkillManagerError({
|
|
48
|
+
message: "--manifest must resolve inside the project",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
let ancestor = projectDir;
|
|
52
|
+
for (const segment of relative.split(path.sep).slice(0, -1)) {
|
|
53
|
+
ancestor = path.join(ancestor, segment);
|
|
54
|
+
if ((yield* observeSymbolicLink(ancestor)).kind === "symlink") {
|
|
55
|
+
return yield* new SkillManagerError({
|
|
56
|
+
message: `manifest ancestor is a symlink: ${path.relative(projectDir, ancestor)}`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const destination = yield* observeSymbolicLink(manifestPath);
|
|
61
|
+
if (destination.kind === "symlink") {
|
|
62
|
+
return yield* new SkillManagerError({ message: `manifest is a symlink: ${relative}` });
|
|
63
|
+
}
|
|
64
|
+
if (destination.kind === "not-symlink" && (yield* fs.stat(manifestPath)).type !== "File") {
|
|
65
|
+
return yield* new SkillManagerError({ message: `manifest is not a regular file: ${relative}` });
|
|
66
|
+
}
|
|
32
67
|
return {
|
|
33
68
|
projectDir,
|
|
34
|
-
manifestPath
|
|
69
|
+
manifestPath,
|
|
35
70
|
};
|
|
36
71
|
});
|
|
37
72
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
73
|
+
const renderDefaultManifest = (projectDir: string, manifestPath: string, path: Path.Path) => {
|
|
74
|
+
const rawSchemaPath = path.relative(
|
|
75
|
+
path.dirname(manifestPath),
|
|
76
|
+
path.join(projectDir, "node_modules", "@danieljvdm", "dev-kit", "schema", "dev-kit.schema.json"),
|
|
77
|
+
);
|
|
78
|
+
const portableSchemaPath = path.sep === "/"
|
|
79
|
+
? rawSchemaPath
|
|
80
|
+
: rawSchemaPath.split(path.sep).join("/");
|
|
81
|
+
const schemaPath = portableSchemaPath.startsWith(".")
|
|
82
|
+
? portableSchemaPath
|
|
83
|
+
: `./${portableSchemaPath}`;
|
|
84
|
+
return `${JSON.stringify({
|
|
85
|
+
$schema: schemaPath,
|
|
86
|
+
include: [],
|
|
87
|
+
targets: { agents: { enabled: true, mode: "copy" } },
|
|
88
|
+
}, null, 2)}\n`;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const createDefaultManifest = Effect.fn("createDefaultSkillManifest")(function* (
|
|
92
|
+
paths: { readonly projectDir: string; readonly manifestPath: string },
|
|
93
|
+
) {
|
|
94
|
+
const fs = yield* FileSystem.FileSystem;
|
|
95
|
+
const path = yield* Path.Path;
|
|
96
|
+
yield* fs.makeDirectory(path.dirname(paths.manifestPath), { recursive: true });
|
|
97
|
+
const staged = yield* fs.makeTempFileScoped({
|
|
98
|
+
directory: path.dirname(paths.manifestPath),
|
|
99
|
+
prefix: ".dev-kit-init-",
|
|
100
|
+
});
|
|
101
|
+
yield* fs.writeFileString(
|
|
102
|
+
staged,
|
|
103
|
+
renderDefaultManifest(paths.projectDir, paths.manifestPath, path),
|
|
104
|
+
);
|
|
105
|
+
yield* fs.rename(staged, paths.manifestPath);
|
|
106
|
+
yield* patchProjectGitignore({ projectDir: paths.projectDir });
|
|
107
|
+
});
|
|
46
108
|
|
|
47
109
|
const readManifest = Effect.fn("readManagedSkillManifest")(function* (
|
|
48
110
|
options: ManagerOptions,
|
|
@@ -56,8 +118,7 @@ const readManifest = Effect.fn("readManagedSkillManifest")(function* (
|
|
|
56
118
|
message: "dev-kit.jsonc not found. Run `dev-kit init` first.",
|
|
57
119
|
});
|
|
58
120
|
}
|
|
59
|
-
yield*
|
|
60
|
-
yield* patchProjectGitignore({ projectDir: paths.projectDir });
|
|
121
|
+
yield* createDefaultManifest(paths);
|
|
61
122
|
}
|
|
62
123
|
const raw = yield* fs.readFileString(paths.manifestPath);
|
|
63
124
|
const errors: Array<ParseError> = [];
|
|
@@ -147,8 +208,7 @@ export const initProject = Effect.fn("initDevKitProject")(function* (options: Ma
|
|
|
147
208
|
yield* printStatus("info", "Already initialized", paths.manifestPath);
|
|
148
209
|
return;
|
|
149
210
|
}
|
|
150
|
-
yield*
|
|
151
|
-
yield* patchProjectGitignore({ projectDir: paths.projectDir });
|
|
211
|
+
yield* createDefaultManifest(paths);
|
|
152
212
|
yield* printStatus("success", "Created dev-kit.jsonc");
|
|
153
213
|
yield* printDetail("Add a skill with: dev-kit add <name>");
|
|
154
214
|
});
|
|
@@ -166,6 +226,16 @@ export const addSkills = Effect.fn("addManagedSkills")(function* (
|
|
|
166
226
|
message: `unknown skill${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}. Try \`dev-kit search ${unknown[0]}\`.`,
|
|
167
227
|
});
|
|
168
228
|
}
|
|
229
|
+
for (const source of catalog.lock?.sources ?? []) {
|
|
230
|
+
if (!names.includes(source.id)) continue;
|
|
231
|
+
yield* printStatus(
|
|
232
|
+
"info",
|
|
233
|
+
`Source family ${source.id} selects all ${source.skills.length} approved skills`,
|
|
234
|
+
);
|
|
235
|
+
yield* printDetail(
|
|
236
|
+
`Prefer individual skill names unless every skill applies. Inspect with: dev-kit search ${source.id}`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
169
239
|
const include = [...new Set([...current.manifest.include, ...names])];
|
|
170
240
|
const exclude = (current.manifest.exclude ?? []).filter((name) => !names.includes(name));
|
|
171
241
|
yield* writeArray(current.manifestPath, current.raw, "include", include);
|
|
@@ -234,7 +304,8 @@ export const listSkills = Effect.fn("listManagedSkills")(function* (
|
|
|
234
304
|
for (const skill of visible) {
|
|
235
305
|
const marker = selected.has(skill.name) ? "✓" : " ";
|
|
236
306
|
const origin = skill.bundled ? "built in" : skill.source;
|
|
237
|
-
|
|
307
|
+
const provenance = skill.bundled ? "" : ` [${skill.source}]`;
|
|
308
|
+
yield* printLine(`${marker} ${skill.name}${provenance} ${summary(skill.description, origin)}`);
|
|
238
309
|
}
|
|
239
310
|
yield* printLine();
|
|
240
311
|
yield* printLine(`${selected.size} selected · ${catalog.skills.length} approved`);
|
package/src/sync.ts
CHANGED
|
@@ -786,7 +786,13 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
|
|
|
786
786
|
yield* fs.writeFileString(stagedLock, canonicalLock(plan.nextLock));
|
|
787
787
|
yield* fs.writeFileString(stagedState, canonicalState(plan.nextState));
|
|
788
788
|
|
|
789
|
-
type Replacement = {
|
|
789
|
+
type Replacement = {
|
|
790
|
+
readonly destination: string;
|
|
791
|
+
readonly backup: string;
|
|
792
|
+
readonly expected?: ObservedPath;
|
|
793
|
+
readonly path: string;
|
|
794
|
+
readonly staged?: string;
|
|
795
|
+
};
|
|
790
796
|
const replacements: Array<Replacement> = [];
|
|
791
797
|
let replacementIndex = 0;
|
|
792
798
|
for (const action of mutating) {
|
|
@@ -801,12 +807,24 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
|
|
|
801
807
|
replacements.push({
|
|
802
808
|
destination: action.action === "remove" ? action.destination : action.desired.destination,
|
|
803
809
|
backup: path.join(backupDir, String(replacementIndex++)),
|
|
810
|
+
expected: action.observed,
|
|
811
|
+
path: action.action === "remove" ? action.previous.path : action.desired.path,
|
|
804
812
|
...(staged === undefined ? {} : { staged }),
|
|
805
813
|
});
|
|
806
814
|
}
|
|
807
815
|
replacements.push(
|
|
808
|
-
{
|
|
809
|
-
|
|
816
|
+
{
|
|
817
|
+
destination: plan.lockfilePath,
|
|
818
|
+
backup: path.join(backupDir, "lock"),
|
|
819
|
+
path: plan.lockfilePath,
|
|
820
|
+
staged: stagedLock,
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
destination: plan.statePath,
|
|
824
|
+
backup: path.join(backupDir, "state"),
|
|
825
|
+
path: plan.statePath,
|
|
826
|
+
staged: stagedState,
|
|
827
|
+
},
|
|
810
828
|
);
|
|
811
829
|
|
|
812
830
|
const installed: Array<string> = [];
|
|
@@ -824,6 +842,12 @@ const applyPlannedSkillChanges = Effect.fn("applyPlannedSkillChanges")(function*
|
|
|
824
842
|
const apply = Effect.gen(function* () {
|
|
825
843
|
for (const replacement of replacements) {
|
|
826
844
|
const observed = yield* observePath(replacement.destination);
|
|
845
|
+
if (
|
|
846
|
+
replacement.expected !== undefined &&
|
|
847
|
+
!observationsEqual(observed, replacement.expected)
|
|
848
|
+
) {
|
|
849
|
+
return yield* new ApplyRaceError({ path: replacement.path });
|
|
850
|
+
}
|
|
827
851
|
if (observed.kind !== "missing") {
|
|
828
852
|
yield* fs.makeDirectory(path.dirname(replacement.backup), { recursive: true });
|
|
829
853
|
yield* fs.rename(replacement.destination, replacement.backup);
|
package/src/vendor.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { ChildProcess } from "effect/unstable/process";
|
|
|
4
4
|
|
|
5
5
|
import { printStatus, withSpinner } from "./cli-ui.ts";
|
|
6
6
|
import { observePath, type Digest } from "./path-digest.ts";
|
|
7
|
+
import { acquireProjectProcessLock } from "./project-process-lock.ts";
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
SkillSourcesLockSchema,
|
|
@@ -754,6 +755,7 @@ export const refreshSkillCatalog = Effect.fn("refreshSkillCatalog")(function* (
|
|
|
754
755
|
: Effect.fail(error),
|
|
755
756
|
),
|
|
756
757
|
);
|
|
758
|
+
yield* acquireProjectProcessLock(repoDir);
|
|
757
759
|
const sourcesPath = path.resolve(repoDir, options.sourcesPath ?? DEFAULT_SOURCES_PATH);
|
|
758
760
|
const lockfilePath = path.resolve(repoDir, options.lockfilePath ?? DEFAULT_LOCKFILE_PATH);
|
|
759
761
|
const manifest = yield* readJsonc(sourcesPath, SkillSourcesManifestSchema);
|