@vibe-forge/workspace-assets 3.2.1 → 3.2.2-alpha.1
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/__tests__/adapter-asset-plan.spec.ts +0 -58
- package/__tests__/bundle.spec.ts +10 -303
- package/__tests__/skill-dependencies-cli.spec.ts +72 -234
- package/package.json +5 -5
- package/src/bundle-internal.ts +63 -10
- package/src/bundle.ts +1 -0
- package/src/configured-skills.ts +24 -30
- package/src/plugin-skill-dependencies.ts +1 -0
- package/src/prompt-selection.ts +3 -3
- package/src/selection-internal.ts +45 -5
- package/src/skill-dependencies.ts +10 -108
- package/src/skills-cli-dependency-helpers.ts +0 -94
- package/src/skills-cli-dependency.ts +0 -133
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/* eslint-disable import/first -- hoisted vitest mocks must be declared before importing the bundle entrypoint */
|
|
2
|
-
import {
|
|
3
|
-
import os from 'node:os'
|
|
4
|
-
import path, { join } from 'node:path'
|
|
2
|
+
import { join } from 'node:path'
|
|
5
3
|
|
|
6
|
-
import {
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
7
5
|
|
|
8
6
|
const mocks = vi.hoisted(() => ({
|
|
9
7
|
findSkillsCli: vi.fn(),
|
|
@@ -23,54 +21,15 @@ vi.mock('@vibe-forge/utils/skills-cli', async () => {
|
|
|
23
21
|
|
|
24
22
|
import { buildAdapterAssetPlan, resolveWorkspaceAssetBundle } from '#~/index.js'
|
|
25
23
|
|
|
26
|
-
import { createWorkspace, writeDocument } from './test-helpers'
|
|
24
|
+
import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
|
|
27
25
|
|
|
28
|
-
describe('
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const pathExists = async (targetPath: string) => {
|
|
32
|
-
try {
|
|
33
|
-
await access(targetPath)
|
|
34
|
-
return true
|
|
35
|
-
} catch {
|
|
36
|
-
return false
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
beforeEach(async () => {
|
|
41
|
-
installWorkspace = await mkdtemp(path.join(os.tmpdir(), 'vf-skills-cli-dependency-'))
|
|
26
|
+
describe('materialized skill dependency resolution', () => {
|
|
27
|
+
beforeEach(() => {
|
|
42
28
|
vi.clearAllMocks()
|
|
43
29
|
})
|
|
44
30
|
|
|
45
|
-
|
|
46
|
-
await rm(installWorkspace, { recursive: true, force: true })
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('installs missing bare-name dependencies through skills CLI by default', async () => {
|
|
31
|
+
it('fails missing dependencies without downloading during runtime preparation', async () => {
|
|
50
32
|
const workspace = await createWorkspace()
|
|
51
|
-
const installedSkillDir = join(installWorkspace, '.agents', 'skills', 'frontend-design')
|
|
52
|
-
await mkdir(installedSkillDir, { recursive: true })
|
|
53
|
-
await writeFile(
|
|
54
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
55
|
-
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse strong visual hierarchy.\n'
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
mocks.findSkillsCli.mockResolvedValue([
|
|
59
|
-
{
|
|
60
|
-
installRef: 'anthropics/skills@frontend-design',
|
|
61
|
-
source: 'anthropics/skills',
|
|
62
|
-
skill: 'frontend-design'
|
|
63
|
-
}
|
|
64
|
-
])
|
|
65
|
-
mocks.installSkillsCliRefToTemp.mockResolvedValue({
|
|
66
|
-
tempDir: installWorkspace,
|
|
67
|
-
installedSkill: {
|
|
68
|
-
dirName: 'frontend-design',
|
|
69
|
-
name: 'frontend-design',
|
|
70
|
-
sourcePath: installedSkillDir
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
|
|
74
33
|
await writeDocument(
|
|
75
34
|
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
76
35
|
[
|
|
@@ -90,57 +49,6 @@ describe('skills CLI dependency resolution', () => {
|
|
|
90
49
|
useDefaultVibeForgeMcpServer: false
|
|
91
50
|
})
|
|
92
51
|
|
|
93
|
-
await buildAdapterAssetPlan({
|
|
94
|
-
adapter: 'opencode',
|
|
95
|
-
bundle,
|
|
96
|
-
options: {
|
|
97
|
-
skills: {
|
|
98
|
-
include: ['app-builder']
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
const dependency = bundle.skills.find(asset => asset.name === 'frontend-design')
|
|
104
|
-
expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['app-builder', 'frontend-design'])
|
|
105
|
-
expect(dependency?.sourcePath).toContain(
|
|
106
|
-
'/.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/frontend-design/'
|
|
107
|
-
)
|
|
108
|
-
expect(mocks.findSkillsCli).toHaveBeenCalledWith({
|
|
109
|
-
query: 'frontend-design'
|
|
110
|
-
})
|
|
111
|
-
expect(mocks.installSkillsCliRefToTemp).toHaveBeenCalledWith({
|
|
112
|
-
installRef: 'anthropics/skills@frontend-design'
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('blocks missing dependency installs when auto downloads are disabled', async () => {
|
|
117
|
-
const workspace = await createWorkspace()
|
|
118
|
-
|
|
119
|
-
await writeDocument(
|
|
120
|
-
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
121
|
-
[
|
|
122
|
-
'---',
|
|
123
|
-
'name: app-builder',
|
|
124
|
-
'description: Build apps',
|
|
125
|
-
'dependencies:',
|
|
126
|
-
' - name: frontend-design',
|
|
127
|
-
' source: anthropics/skills',
|
|
128
|
-
' registry: https://dependency-registry.example.test',
|
|
129
|
-
'---',
|
|
130
|
-
'Build the app.'
|
|
131
|
-
].join('\n')
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
135
|
-
cwd: workspace,
|
|
136
|
-
configs: [{
|
|
137
|
-
skills: {
|
|
138
|
-
autoDownloadDependencies: false
|
|
139
|
-
}
|
|
140
|
-
}, undefined],
|
|
141
|
-
useDefaultVibeForgeMcpServer: false
|
|
142
|
-
})
|
|
143
|
-
|
|
144
52
|
await expect(buildAdapterAssetPlan({
|
|
145
53
|
adapter: 'opencode',
|
|
146
54
|
bundle,
|
|
@@ -149,36 +57,25 @@ describe('skills CLI dependency resolution', () => {
|
|
|
149
57
|
include: ['app-builder']
|
|
150
58
|
}
|
|
151
59
|
}
|
|
152
|
-
})).rejects.toThrow('
|
|
60
|
+
})).rejects.toThrow('Run vf skills install or vf skills update')
|
|
153
61
|
|
|
154
62
|
expect(mocks.findSkillsCli).not.toHaveBeenCalled()
|
|
155
63
|
expect(mocks.installSkillsCliRefToTemp).not.toHaveBeenCalled()
|
|
156
64
|
expect(mocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
157
|
-
expect(bundle.skills.map(asset => asset.name)).toEqual(['app-builder'])
|
|
158
65
|
})
|
|
159
66
|
|
|
160
|
-
it('
|
|
67
|
+
it('prompts install command when an explicitly selected configured skill is missing', async () => {
|
|
161
68
|
const workspace = await createWorkspace()
|
|
162
|
-
|
|
163
|
-
await writeDocument(
|
|
164
|
-
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
165
|
-
[
|
|
166
|
-
'---',
|
|
167
|
-
'name: app-builder',
|
|
168
|
-
'description: Build apps',
|
|
169
|
-
'dependencies:',
|
|
170
|
-
' - frontend-design',
|
|
171
|
-
'---',
|
|
172
|
-
'Build the app.'
|
|
173
|
-
].join('\n')
|
|
174
|
-
)
|
|
175
|
-
|
|
176
69
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
177
70
|
cwd: workspace,
|
|
178
71
|
configs: [{
|
|
179
|
-
skills:
|
|
180
|
-
|
|
181
|
-
|
|
72
|
+
skills: [
|
|
73
|
+
{
|
|
74
|
+
name: 'design-review',
|
|
75
|
+
source: 'example-source/default/public',
|
|
76
|
+
rename: 'internal-review'
|
|
77
|
+
}
|
|
78
|
+
]
|
|
182
79
|
}, undefined],
|
|
183
80
|
useDefaultVibeForgeMcpServer: false
|
|
184
81
|
})
|
|
@@ -188,27 +85,18 @@ describe('skills CLI dependency resolution', () => {
|
|
|
188
85
|
bundle,
|
|
189
86
|
options: {
|
|
190
87
|
skills: {
|
|
191
|
-
include: ['
|
|
88
|
+
include: ['internal-review']
|
|
192
89
|
}
|
|
193
90
|
}
|
|
194
|
-
})).rejects.toThrow(
|
|
195
|
-
'Skill dependency automatic downloads are disabled; cannot resolve frontend-design without a source'
|
|
196
|
-
)
|
|
91
|
+
})).rejects.toThrow('Run `vf skills install internal-review` or `vf skills install`')
|
|
197
92
|
|
|
198
93
|
expect(mocks.findSkillsCli).not.toHaveBeenCalled()
|
|
199
94
|
expect(mocks.installSkillsCliRefToTemp).not.toHaveBeenCalled()
|
|
200
95
|
expect(mocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
201
96
|
})
|
|
202
97
|
|
|
203
|
-
it('
|
|
98
|
+
it('uses project-materialized dependencies from .ai/skills', async () => {
|
|
204
99
|
const workspace = await createWorkspace()
|
|
205
|
-
await writeDocument(
|
|
206
|
-
join(
|
|
207
|
-
workspace,
|
|
208
|
-
'.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/frontend-design/SKILL.md'
|
|
209
|
-
),
|
|
210
|
-
'---\nname: frontend-design\ndescription: Cached UI guidance\n---\nUse the cached dependency.\n'
|
|
211
|
-
)
|
|
212
100
|
await writeDocument(
|
|
213
101
|
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
214
102
|
[
|
|
@@ -216,23 +104,22 @@ describe('skills CLI dependency resolution', () => {
|
|
|
216
104
|
'name: app-builder',
|
|
217
105
|
'description: Build apps',
|
|
218
106
|
'dependencies:',
|
|
219
|
-
' -
|
|
107
|
+
' - frontend-design',
|
|
220
108
|
'---',
|
|
221
109
|
'Build the app.'
|
|
222
110
|
].join('\n')
|
|
223
111
|
)
|
|
112
|
+
await writeDocument(
|
|
113
|
+
join(workspace, '.ai/skills/frontend-design/SKILL.md'),
|
|
114
|
+
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse strong visual hierarchy.\n'
|
|
115
|
+
)
|
|
224
116
|
|
|
225
117
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
226
118
|
cwd: workspace,
|
|
227
|
-
configs: [
|
|
228
|
-
skills: {
|
|
229
|
-
autoDownloadDependencies: false
|
|
230
|
-
}
|
|
231
|
-
}, undefined],
|
|
119
|
+
configs: [undefined, undefined],
|
|
232
120
|
useDefaultVibeForgeMcpServer: false
|
|
233
121
|
})
|
|
234
|
-
|
|
235
|
-
await buildAdapterAssetPlan({
|
|
122
|
+
const plan = await buildAdapterAssetPlan({
|
|
236
123
|
adapter: 'opencode',
|
|
237
124
|
bundle,
|
|
238
125
|
options: {
|
|
@@ -242,128 +129,79 @@ describe('skills CLI dependency resolution', () => {
|
|
|
242
129
|
}
|
|
243
130
|
})
|
|
244
131
|
|
|
132
|
+
expect(plan.overlays.filter(entry => entry.kind === 'skill').map(entry => entry.targetPath).sort()).toEqual([
|
|
133
|
+
'skills/app-builder',
|
|
134
|
+
'skills/frontend-design'
|
|
135
|
+
])
|
|
245
136
|
expect(mocks.findSkillsCli).not.toHaveBeenCalled()
|
|
246
|
-
expect(mocks.installSkillsCliRefToTemp).not.toHaveBeenCalled()
|
|
247
|
-
expect(mocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
248
|
-
expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['app-builder', 'frontend-design'])
|
|
249
137
|
})
|
|
250
138
|
|
|
251
|
-
it('
|
|
139
|
+
it('loads plugin dependencies from .ai/skills/.plugins through the lockfile', async () => {
|
|
252
140
|
const workspace = await createWorkspace()
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
257
|
-
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse internal design system.\n'
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
mocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
261
|
-
tempDir: installWorkspace,
|
|
262
|
-
installedSkill: {
|
|
263
|
-
dirName: 'frontend-design',
|
|
264
|
-
name: 'frontend-design',
|
|
265
|
-
sourcePath: installedSkillDir
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
await writeDocument(
|
|
270
|
-
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
271
|
-
[
|
|
141
|
+
await installPluginPackage(workspace, '@vibe-forge/plugin-review', {
|
|
142
|
+
'package.json': JSON.stringify({ name: '@vibe-forge/plugin-review', version: '1.0.0' }, null, 2),
|
|
143
|
+
'skills/review-helper/SKILL.md': [
|
|
272
144
|
'---',
|
|
273
|
-
'name:
|
|
274
|
-
'description:
|
|
145
|
+
'name: review-helper',
|
|
146
|
+
'description: Review helper',
|
|
275
147
|
'dependencies:',
|
|
276
|
-
' -
|
|
148
|
+
' - shared-runtime',
|
|
277
149
|
'---',
|
|
278
|
-
'
|
|
150
|
+
'Review code.'
|
|
279
151
|
].join('\n')
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
283
|
-
cwd: workspace,
|
|
284
|
-
configs: [undefined, undefined],
|
|
285
|
-
useDefaultVibeForgeMcpServer: false
|
|
286
152
|
})
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
bundle,
|
|
291
|
-
options: {
|
|
292
|
-
skills: {
|
|
293
|
-
include: ['app-builder']
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
expect(mocks.installSkillsCliSkillToTemp).toHaveBeenCalledWith({
|
|
299
|
-
registry: 'https://registry.example.com',
|
|
300
|
-
skill: 'frontend-design',
|
|
301
|
-
source: 'example-source/default/public',
|
|
302
|
-
version: '1.0.3'
|
|
303
|
-
})
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
it('clears stale dependency install locks before retrying a direct startup install', async () => {
|
|
307
|
-
const workspace = await createWorkspace()
|
|
308
|
-
const installedSkillDir = join(installWorkspace, '.agents', 'skills', 'lynx-cat')
|
|
309
|
-
await mkdir(installedSkillDir, { recursive: true })
|
|
310
|
-
await writeFile(
|
|
311
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
312
|
-
'---\nname: lynx-cat\ndescription: Lynx helper\n---\nDebug Lynx apps.\n'
|
|
153
|
+
await writeDocument(
|
|
154
|
+
join(workspace, '.ai/skills/.plugins/review/shared-runtime/SKILL.md'),
|
|
155
|
+
'---\nname: shared-runtime\ndescription: Shared runtime\n---\nShared plugin dependency.\n'
|
|
313
156
|
)
|
|
314
|
-
|
|
315
|
-
mocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
316
|
-
tempDir: installWorkspace,
|
|
317
|
-
installedSkill: {
|
|
318
|
-
dirName: 'lynx-cat',
|
|
319
|
-
name: 'lynx-cat',
|
|
320
|
-
sourcePath: installedSkillDir
|
|
321
|
-
}
|
|
322
|
-
})
|
|
323
|
-
|
|
324
157
|
await writeDocument(
|
|
325
|
-
join(workspace, '.ai/skills
|
|
158
|
+
join(workspace, '.ai/skills.lock.yaml'),
|
|
326
159
|
[
|
|
327
|
-
'
|
|
328
|
-
'
|
|
329
|
-
'
|
|
330
|
-
'
|
|
331
|
-
'
|
|
332
|
-
'
|
|
333
|
-
'
|
|
160
|
+
'version: 1',
|
|
161
|
+
'pluginSkills:',
|
|
162
|
+
' review/shared-runtime:',
|
|
163
|
+
' name: shared-runtime',
|
|
164
|
+
' requested: false',
|
|
165
|
+
' pluginInstance: review',
|
|
166
|
+
' pluginInstancePath: "0"',
|
|
167
|
+
' installPath: .ai/skills/.plugins/review/shared-runtime',
|
|
168
|
+
' dependencyOf:',
|
|
169
|
+
' - plugin:review/review-helper',
|
|
170
|
+
' source: vendor/shared-skills',
|
|
171
|
+
' version: 1.0.0',
|
|
172
|
+
' hash: sha256:test',
|
|
173
|
+
' installedAt: "2026-05-13T00:00:00.000Z"'
|
|
334
174
|
].join('\n')
|
|
335
175
|
)
|
|
336
176
|
|
|
337
|
-
const lockDir = join(
|
|
338
|
-
workspace,
|
|
339
|
-
'.ai/caches/skill-dependencies/skills-cli/skills/latest/https-registry.example.com/example-source/lynx/skills/latest/lynx-cat.lock'
|
|
340
|
-
)
|
|
341
|
-
await mkdir(lockDir, { recursive: true })
|
|
342
|
-
await utimes(lockDir, new Date(Date.now() - 120_000), new Date(Date.now() - 120_000))
|
|
343
|
-
|
|
344
177
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
345
178
|
cwd: workspace,
|
|
346
|
-
configs: [
|
|
179
|
+
configs: [{
|
|
180
|
+
plugins: [{
|
|
181
|
+
id: '@vibe-forge/plugin-review',
|
|
182
|
+
scope: 'review'
|
|
183
|
+
}]
|
|
184
|
+
}, undefined],
|
|
347
185
|
useDefaultVibeForgeMcpServer: false
|
|
348
186
|
})
|
|
349
|
-
|
|
350
|
-
await buildAdapterAssetPlan({
|
|
187
|
+
const plan = await buildAdapterAssetPlan({
|
|
351
188
|
adapter: 'opencode',
|
|
352
189
|
bundle,
|
|
353
190
|
options: {
|
|
354
191
|
skills: {
|
|
355
|
-
include: ['
|
|
192
|
+
include: ['review/review-helper']
|
|
356
193
|
}
|
|
357
194
|
}
|
|
358
195
|
})
|
|
359
196
|
|
|
360
|
-
expect(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
197
|
+
expect(bundle.skills.map(asset => asset.displayName).sort()).toEqual([
|
|
198
|
+
'review/review-helper',
|
|
199
|
+
'review/shared-runtime'
|
|
200
|
+
])
|
|
201
|
+
expect(plan.overlays.filter(entry => entry.kind === 'skill').map(entry => entry.targetPath).sort()).toEqual([
|
|
202
|
+
'skills/review__review-helper',
|
|
203
|
+
'skills/review__shared-runtime'
|
|
204
|
+
])
|
|
205
|
+
expect(mocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
368
206
|
})
|
|
369
207
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-forge/workspace-assets",
|
|
3
|
-
"version": "3.2.1",
|
|
3
|
+
"version": "3.2.2-alpha.1",
|
|
4
4
|
"description": "Workspace asset resolution and adapter asset planning for Vibe Forge",
|
|
5
5
|
"imports": {
|
|
6
6
|
"#~/*.js": {
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"fast-glob": "^3.3.3",
|
|
30
30
|
"front-matter": "^4.0.2",
|
|
31
31
|
"js-yaml": "^4.1.1",
|
|
32
|
-
"@vibe-forge/
|
|
33
|
-
"@vibe-forge/
|
|
34
|
-
"@vibe-forge/types": "3.2.
|
|
35
|
-
"@vibe-forge/
|
|
32
|
+
"@vibe-forge/config": "3.2.3-alpha.1",
|
|
33
|
+
"@vibe-forge/utils": "3.2.3-alpha.1",
|
|
34
|
+
"@vibe-forge/types": "3.2.3-alpha.1",
|
|
35
|
+
"@vibe-forge/definition-core": "3.2.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/js-yaml": "^4.0.9"
|
package/src/bundle-internal.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
1
|
+
import { access, readFile } from 'node:fs/promises'
|
|
2
2
|
import { basename, dirname, extname, isAbsolute, resolve } from 'node:path'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
|
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
} from '@vibe-forge/config'
|
|
11
11
|
import type { Config, Definition, Entity, PluginConfig, WorkspaceAsset, WorkspaceAssetKind } from '@vibe-forge/types'
|
|
12
12
|
import {
|
|
13
|
-
|
|
13
|
+
readProjectSkillsLockfile,
|
|
14
14
|
resolveProjectAiBaseDir,
|
|
15
15
|
resolveProjectAiEntitiesDir,
|
|
16
|
-
resolveRelativePath
|
|
16
|
+
resolveRelativePath,
|
|
17
|
+
resolveSkillsHomeBridge
|
|
17
18
|
} from '@vibe-forge/utils'
|
|
18
19
|
import { listManagedPluginInstalls, toManagedPluginConfig } from '@vibe-forge/utils/managed-plugin'
|
|
19
20
|
import {
|
|
@@ -33,8 +34,9 @@ import {
|
|
|
33
34
|
resolveSkillIdentifier,
|
|
34
35
|
resolveSpecIdentifier
|
|
35
36
|
} from '@vibe-forge/definition-core'
|
|
36
|
-
import {
|
|
37
|
+
import { warnMissingConfiguredProjectSkills } from './configured-skills'
|
|
37
38
|
import { HOME_BRIDGE_RESOLVED_BY } from './home-bridge'
|
|
39
|
+
import { PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY } from './plugin-skill-dependencies'
|
|
38
40
|
import { resolveConfiguredWorkspaceAssets } from './workspaces'
|
|
39
41
|
|
|
40
42
|
type DocumentAssetKind = Extract<WorkspaceAssetKind, 'rule' | 'spec' | 'entity' | 'skill'>
|
|
@@ -152,15 +154,15 @@ const resolveRealHomeDir = (env: NodeJS.ProcessEnv) => {
|
|
|
152
154
|
|
|
153
155
|
const warnInvalidHomeSkillRoot = (root: string) => {
|
|
154
156
|
console.warn(
|
|
155
|
-
`[vibe-forge] Ignoring invalid
|
|
157
|
+
`[vibe-forge] Ignoring invalid skillsMeta.homeBridge root "${root}". ` +
|
|
156
158
|
'Use an absolute path or a path starting with "~".'
|
|
157
159
|
)
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
const resolveHomeBridgeConfig = (configs: [Config?, Config?]) => {
|
|
161
163
|
const [config, userConfig] = configs
|
|
162
|
-
const projectHomeBridge =
|
|
163
|
-
const userHomeBridge =
|
|
164
|
+
const projectHomeBridge = resolveSkillsHomeBridge(config)
|
|
165
|
+
const userHomeBridge = resolveSkillsHomeBridge(userConfig)
|
|
164
166
|
|
|
165
167
|
return {
|
|
166
168
|
enabled: userHomeBridge?.enabled ?? projectHomeBridge?.enabled ?? true,
|
|
@@ -440,6 +442,40 @@ const scanInstanceDocuments = async (instance: ResolvedPluginInstance) => {
|
|
|
440
442
|
}
|
|
441
443
|
}
|
|
442
444
|
|
|
445
|
+
const pathExists = async (path: string) => {
|
|
446
|
+
try {
|
|
447
|
+
await access(path)
|
|
448
|
+
return true
|
|
449
|
+
} catch {
|
|
450
|
+
return false
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const scanPluginDependencySkillDocuments = async (
|
|
455
|
+
cwd: string,
|
|
456
|
+
instances: ResolvedPluginInstance[]
|
|
457
|
+
) => {
|
|
458
|
+
const lockfile = await readProjectSkillsLockfile(cwd)
|
|
459
|
+
const documents: Array<{ instance: ResolvedPluginInstance; path: string }> = []
|
|
460
|
+
for (const entry of Object.values(lockfile.pluginSkills ?? {})) {
|
|
461
|
+
const instance = instances.find(candidate => (
|
|
462
|
+
candidate.instancePath === entry.pluginInstancePath ||
|
|
463
|
+
candidate.scope === entry.pluginInstance
|
|
464
|
+
))
|
|
465
|
+
if (instance == null) continue
|
|
466
|
+
|
|
467
|
+
const skillPath = resolve(cwd, entry.installPath, 'SKILL.md')
|
|
468
|
+
if (await pathExists(skillPath)) {
|
|
469
|
+
documents.push({
|
|
470
|
+
instance,
|
|
471
|
+
path: skillPath
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return documents
|
|
477
|
+
}
|
|
478
|
+
|
|
443
479
|
const toOpenCodeOverlayEntries = (
|
|
444
480
|
kind: OpenCodeOverlayKind,
|
|
445
481
|
targetDir: 'agents' | 'commands' | 'modes' | 'plugins',
|
|
@@ -535,6 +571,7 @@ export async function collectWorkspaceAssets(params: {
|
|
|
535
571
|
syncConfiguredSkills?: boolean
|
|
536
572
|
updateConfiguredSkills?: boolean
|
|
537
573
|
useDefaultVibeForgeMcpServer?: boolean
|
|
574
|
+
warnMissingConfiguredSkills?: boolean
|
|
538
575
|
}): Promise<{
|
|
539
576
|
assets: WorkspaceAsset[]
|
|
540
577
|
configs: [Config?, Config?]
|
|
@@ -552,10 +589,15 @@ export async function collectWorkspaceAssets(params: {
|
|
|
552
589
|
workspaces: Array<Extract<WorkspaceAsset, { kind: 'workspace' }>>
|
|
553
590
|
}> {
|
|
554
591
|
const [config, userConfig] = params.configs ?? await loadWorkspaceConfig(params.cwd)
|
|
555
|
-
if (params.syncConfiguredSkills === true) {
|
|
556
|
-
|
|
592
|
+
if (params.syncConfiguredSkills === true || params.updateConfiguredSkills === true) {
|
|
593
|
+
console.warn(
|
|
594
|
+
'[vibe-forge] Runtime skill install/update is disabled. ' +
|
|
595
|
+
'Run `vf skills install` or `vf skills update` before starting the session.'
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
if (params.warnMissingConfiguredSkills === true) {
|
|
599
|
+
await warnMissingConfiguredProjectSkills({
|
|
557
600
|
configs: [config, userConfig],
|
|
558
|
-
updateInstalledSkills: params.updateConfiguredSkills,
|
|
559
601
|
workspaceFolder: params.cwd
|
|
560
602
|
})
|
|
561
603
|
}
|
|
@@ -578,6 +620,7 @@ export async function collectWorkspaceAssets(params: {
|
|
|
578
620
|
])
|
|
579
621
|
const flattenedPluginInstances = flattenPluginInstances(pluginInstances)
|
|
580
622
|
const pluginScans = await Promise.all(flattenedPluginInstances.map(instance => scanInstanceDocuments(instance)))
|
|
623
|
+
const pluginDependencySkillDocs = await scanPluginDependencySkillDocuments(params.cwd, flattenedPluginInstances)
|
|
581
624
|
const pluginOverlayScans = await Promise.all(
|
|
582
625
|
flattenedPluginInstances.map(instance => scanInstanceOpenCodeOverlays(instance))
|
|
583
626
|
)
|
|
@@ -631,6 +674,16 @@ export async function collectWorkspaceAssets(params: {
|
|
|
631
674
|
await pushDocumentAssets('entity', scan.entityDocPaths, 'plugin', instance, parseEntityMarkdownDocument)
|
|
632
675
|
await pushDocumentAssets('entity', scan.entityJsonPaths, 'plugin', instance, parseEntityIndexJson)
|
|
633
676
|
}
|
|
677
|
+
for (const entry of pluginDependencySkillDocs) {
|
|
678
|
+
await pushDocumentAssets(
|
|
679
|
+
'skill',
|
|
680
|
+
[entry.path],
|
|
681
|
+
'plugin',
|
|
682
|
+
entry.instance,
|
|
683
|
+
undefined,
|
|
684
|
+
PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY
|
|
685
|
+
)
|
|
686
|
+
}
|
|
634
687
|
await pushDocumentAssets('skill', homeSkillPaths, 'workspace', undefined, undefined, HOME_BRIDGE_RESOLVED_BY)
|
|
635
688
|
|
|
636
689
|
const skills = mergeSkillAssets(skillAssets)
|
package/src/bundle.ts
CHANGED
|
@@ -11,6 +11,7 @@ export async function resolveWorkspaceAssetBundle(params: {
|
|
|
11
11
|
syncConfiguredSkills?: boolean
|
|
12
12
|
updateConfiguredSkills?: boolean
|
|
13
13
|
useDefaultVibeForgeMcpServer?: boolean
|
|
14
|
+
warnMissingConfiguredSkills?: boolean
|
|
14
15
|
}): Promise<WorkspaceAssetBundle> {
|
|
15
16
|
const collected = await collectWorkspaceAssets(params)
|
|
16
17
|
|
package/src/configured-skills.ts
CHANGED
|
@@ -3,15 +3,13 @@ import process from 'node:process'
|
|
|
3
3
|
|
|
4
4
|
import type { Config, ConfiguredSkillInstallConfig } from '@vibe-forge/types'
|
|
5
5
|
import {
|
|
6
|
-
installProjectSkill,
|
|
7
6
|
normalizeProjectSkillInstall,
|
|
8
7
|
resolveConfiguredSkillInstalls as resolveDeclaredConfiguredSkillInstalls,
|
|
9
|
-
resolveProjectAiPath
|
|
10
|
-
resolveSkillsRegistry
|
|
8
|
+
resolveProjectAiPath
|
|
11
9
|
} from '@vibe-forge/utils'
|
|
12
10
|
import type { NormalizedProjectSkillInstall } from '@vibe-forge/utils'
|
|
13
11
|
|
|
14
|
-
const
|
|
12
|
+
export const resolveConfiguredProjectSkillInstalls = (configs: [Config?, Config?]) => (
|
|
15
13
|
[
|
|
16
14
|
...resolveDeclaredConfiguredSkillInstalls(configs[0]?.skills),
|
|
17
15
|
...resolveDeclaredConfiguredSkillInstalls(configs[1]?.skills)
|
|
@@ -29,7 +27,7 @@ const pathExists = async (targetPath: string) => {
|
|
|
29
27
|
}
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
const
|
|
30
|
+
export const ensureUniqueConfiguredSkillTargets = (skills: NormalizedProjectSkillInstall[]) => {
|
|
33
31
|
const seen = new Map<string, string>()
|
|
34
32
|
|
|
35
33
|
for (const skill of skills) {
|
|
@@ -43,22 +41,18 @@ const ensureUniqueTargets = (skills: NormalizedProjectSkillInstall[]) => {
|
|
|
43
41
|
}
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
export const
|
|
44
|
+
export const findMissingConfiguredProjectSkills = async (params: {
|
|
47
45
|
configs: [Config?, Config?]
|
|
48
|
-
updateInstalledSkills?: boolean
|
|
49
46
|
workspaceFolder: string
|
|
50
47
|
}) => {
|
|
51
|
-
const
|
|
52
|
-
resolveSkillsRegistry(params.configs[0]?.skills)
|
|
53
|
-
const installs = resolveConfiguredSkillInstalls(params.configs)
|
|
48
|
+
const installs = resolveConfiguredProjectSkillInstalls(params.configs)
|
|
54
49
|
if (installs.length === 0) {
|
|
55
50
|
return []
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const ensured: Array<{ dirName: string; skillPath: string }> = []
|
|
53
|
+
ensureUniqueConfiguredSkillTargets(installs)
|
|
61
54
|
|
|
55
|
+
const missing: NormalizedProjectSkillInstall[] = []
|
|
62
56
|
for (const skill of installs) {
|
|
63
57
|
const skillPath = resolveProjectAiPath(
|
|
64
58
|
params.workspaceFolder,
|
|
@@ -67,23 +61,23 @@ export const ensureConfiguredProjectSkills = async (params: {
|
|
|
67
61
|
skill.targetDirName,
|
|
68
62
|
'SKILL.md'
|
|
69
63
|
)
|
|
70
|
-
if (
|
|
71
|
-
ensured.push({
|
|
72
|
-
dirName: skill.targetDirName,
|
|
73
|
-
skillPath
|
|
74
|
-
})
|
|
75
|
-
continue
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
ensured.push(
|
|
79
|
-
await installProjectSkill({
|
|
80
|
-
force: true,
|
|
81
|
-
registry: defaultRegistry,
|
|
82
|
-
skill,
|
|
83
|
-
workspaceFolder: params.workspaceFolder
|
|
84
|
-
})
|
|
85
|
-
)
|
|
64
|
+
if (!await pathExists(skillPath)) missing.push(skill)
|
|
86
65
|
}
|
|
87
66
|
|
|
88
|
-
return
|
|
67
|
+
return missing
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const warnMissingConfiguredProjectSkills = async (params: {
|
|
71
|
+
configs: [Config?, Config?]
|
|
72
|
+
workspaceFolder: string
|
|
73
|
+
}) => {
|
|
74
|
+
const missing = await findMissingConfiguredProjectSkills(params)
|
|
75
|
+
if (missing.length === 0) return []
|
|
76
|
+
|
|
77
|
+
const names = missing.map(skill => skill.targetName).join(', ')
|
|
78
|
+
console.warn(
|
|
79
|
+
`[vibe-forge] Declared skills are not installed: ${names}. ` +
|
|
80
|
+
'Run `vf skills install` to install all declared skills, or `vf skills install <name>` for one skill.'
|
|
81
|
+
)
|
|
82
|
+
return missing
|
|
89
83
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY = 'plugin-skill-dependency-lock'
|