@danieljvdm/dev-kit 0.2.0 → 0.2.3
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 +39 -11
- package/dev-kit.example.jsonc +1 -1
- package/package.json +3 -3
- package/schema/dev-kit.schema.json +2 -2
- package/schema/skill-sources.schema.json +2 -2
- package/skills/dev-kit/SKILL.md +30 -10
- 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,37 @@ 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, 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:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bun x dev-kit search cloudflare
|
|
46
|
+
bun x dev-kit info workers-best-practices
|
|
47
|
+
bun x dev-kit add workers-best-practices wrangler
|
|
29
48
|
```
|
|
30
49
|
|
|
31
50
|
`add` updates `dev-kit.jsonc` and applies the selection immediately. The
|
|
@@ -49,8 +68,8 @@ or use `--no-apply` to edit the manifest without syncing yet.
|
|
|
49
68
|
For review-first workflows, edit the manifest or pass `--no-apply`, then:
|
|
50
69
|
|
|
51
70
|
```bash
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
bun x dev-kit plan
|
|
72
|
+
bun x dev-kit apply
|
|
54
73
|
```
|
|
55
74
|
|
|
56
75
|
Commit the generated `dev-kit.lock.json`, then use locked mode in your package
|
|
@@ -135,7 +154,13 @@ tool versions. A project-local process lock also prevents concurrent applies.
|
|
|
135
154
|
```jsonc
|
|
136
155
|
{
|
|
137
156
|
"$schema": "./node_modules/@danieljvdm/dev-kit/schema/dev-kit.schema.json",
|
|
138
|
-
"include": [
|
|
157
|
+
"include": [
|
|
158
|
+
"dev-kit",
|
|
159
|
+
"effect",
|
|
160
|
+
"workers-best-practices",
|
|
161
|
+
"wrangler",
|
|
162
|
+
"serve-sim"
|
|
163
|
+
],
|
|
139
164
|
"exclude": ["animation-vocabulary"],
|
|
140
165
|
"targets": {
|
|
141
166
|
"agents": { "enabled": true, "mode": "copy" },
|
|
@@ -147,8 +172,11 @@ tool versions. A project-local process lock also prevents concurrent applies.
|
|
|
147
172
|
|
|
148
173
|
- `dev-kit` installs guidance for operating the toolkit itself.
|
|
149
174
|
- `effect` expands to the consolidated `effect-ts` skill.
|
|
150
|
-
-
|
|
151
|
-
|
|
175
|
+
- Prefer individual external skills such as `workers-best-practices` and
|
|
176
|
+
`wrangler`, selected after scanning the project for relevant technologies.
|
|
177
|
+
- `serve-sim` selects the approved Evan Bacon simulator skill directly.
|
|
178
|
+
- An approved source ID is broad shorthand that selects every skill from that
|
|
179
|
+
source. Use it only when the scan confirms that every member applies.
|
|
152
180
|
|
|
153
181
|
Dev Kit reserves `.repos/<source-id>` for project-local source checkouts. Run
|
|
154
182
|
`dev-kit gitignore` to add `.repos/` and `.dev-kit/` to the project ignore file.
|
|
@@ -196,7 +224,7 @@ Pin the compatible packages in the consuming project:
|
|
|
196
224
|
```jsonc
|
|
197
225
|
{
|
|
198
226
|
"devDependencies": {
|
|
199
|
-
"@danieljvdm/dev-kit": "
|
|
227
|
+
"@danieljvdm/dev-kit": "^0.2.0",
|
|
200
228
|
"@effect/tsgo": "0.24.3",
|
|
201
229
|
"typescript": "7.0.2"
|
|
202
230
|
}
|
package/dev-kit.example.jsonc
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danieljvdm/dev-kit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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": {
|
|
@@ -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",
|
|
@@ -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",
|
|
@@ -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",
|
|
@@ -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",
|
|
@@ -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,26 +21,46 @@ stores explicit skill names and exact commit/content digests.
|
|
|
21
21
|
|
|
22
22
|
## Apply loop
|
|
23
23
|
|
|
24
|
-
1. Establish the Git root. Read
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
30
48
|
the project overrides their defaults. Planning is read-only; inspect every
|
|
31
49
|
create, update, remove, adoption, and conflict before proceeding. Finish
|
|
32
50
|
when the plan contains only intended actions and understood conflicts.
|
|
33
|
-
|
|
51
|
+
6. Resolve conflicts, then run `dev-kit apply`. Commit the manifest and
|
|
34
52
|
regenerated `dev-kit.lock.json`; keep `.dev-kit/` local. Finish when a second
|
|
35
53
|
plan reports only unchanged resources and setup tasks.
|
|
36
|
-
|
|
54
|
+
7. Use `dev-kit apply --locked` in CI and the package lifecycle. Finish when a
|
|
37
55
|
clean install converges from the committed manifest and lock.
|
|
38
56
|
|
|
39
57
|
## Manifest
|
|
40
58
|
|
|
41
59
|
Use skill names or family names in `include`; subtract selections with
|
|
42
|
-
`exclude`.
|
|
43
|
-
|
|
60
|
+
`exclude`. Built-in families such as `effect` are intentional bundles. An
|
|
61
|
+
external source ID is also a family, but expands to every approved skill from
|
|
62
|
+
that source, so prefer individually relevant external skills. Include this
|
|
63
|
+
skill as `dev-kit` when project agents should carry the toolkit procedure.
|
|
44
64
|
|
|
45
65
|
```jsonc
|
|
46
66
|
{
|
|
@@ -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);
|