@vibe-forge/workspace-assets 3.2.0 → 3.2.2-alpha.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/__tests__/__snapshots__/workspace-assets-rich.snapshot.json +1 -1
- package/__tests__/adapter-asset-plan.spec.ts +0 -58
- package/__tests__/bundle.spec.ts +0 -238
- package/__tests__/prompt-builders.spec.ts +30 -3
- package/__tests__/skill-dependencies-cli.spec.ts +59 -252
- package/package.json +4 -4
- package/src/bundle-internal.ts +51 -4
- package/src/configured-skills.ts +5 -1
- package/src/plugin-skill-dependencies.ts +1 -0
- package/src/prompt-builders.ts +4 -2
- package/src/selection-internal.ts +9 -2
- package/src/skill-dependencies.ts +10 -108
- package/src/task-tool-guidance.ts +19 -6
- package/src/workspace-prompt.ts +4 -2
- package/src/skills-cli-dependency-helpers.ts +0 -94
- package/src/skills-cli-dependency.ts +0 -125
|
@@ -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,17 +57,15 @@ 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('uses project-materialized dependencies from .ai/skills', async () => {
|
|
161
68
|
const workspace = await createWorkspace()
|
|
162
|
-
|
|
163
69
|
await writeDocument(
|
|
164
70
|
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
165
71
|
[
|
|
@@ -172,67 +78,17 @@ describe('skills CLI dependency resolution', () => {
|
|
|
172
78
|
'Build the app.'
|
|
173
79
|
].join('\n')
|
|
174
80
|
)
|
|
175
|
-
|
|
176
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
177
|
-
cwd: workspace,
|
|
178
|
-
configs: [{
|
|
179
|
-
skills: {
|
|
180
|
-
autoDownloadDependencies: false
|
|
181
|
-
}
|
|
182
|
-
}, undefined],
|
|
183
|
-
useDefaultVibeForgeMcpServer: false
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
await expect(buildAdapterAssetPlan({
|
|
187
|
-
adapter: 'opencode',
|
|
188
|
-
bundle,
|
|
189
|
-
options: {
|
|
190
|
-
skills: {
|
|
191
|
-
include: ['app-builder']
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
})).rejects.toThrow(
|
|
195
|
-
'Skill dependency automatic downloads are disabled; cannot resolve frontend-design without a source'
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
expect(mocks.findSkillsCli).not.toHaveBeenCalled()
|
|
199
|
-
expect(mocks.installSkillsCliRefToTemp).not.toHaveBeenCalled()
|
|
200
|
-
expect(mocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('reuses source-qualified dependency caches when auto downloads are disabled', async () => {
|
|
204
|
-
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
81
|
await writeDocument(
|
|
213
|
-
join(workspace, '.ai/skills/
|
|
214
|
-
|
|
215
|
-
'---',
|
|
216
|
-
'name: app-builder',
|
|
217
|
-
'description: Build apps',
|
|
218
|
-
'dependencies:',
|
|
219
|
-
' - anthropics/skills@frontend-design',
|
|
220
|
-
'---',
|
|
221
|
-
'Build the app.'
|
|
222
|
-
].join('\n')
|
|
82
|
+
join(workspace, '.ai/skills/frontend-design/SKILL.md'),
|
|
83
|
+
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse strong visual hierarchy.\n'
|
|
223
84
|
)
|
|
224
85
|
|
|
225
86
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
226
87
|
cwd: workspace,
|
|
227
|
-
configs: [
|
|
228
|
-
skills: {
|
|
229
|
-
autoDownloadDependencies: false
|
|
230
|
-
}
|
|
231
|
-
}, undefined],
|
|
88
|
+
configs: [undefined, undefined],
|
|
232
89
|
useDefaultVibeForgeMcpServer: false
|
|
233
90
|
})
|
|
234
|
-
|
|
235
|
-
await buildAdapterAssetPlan({
|
|
91
|
+
const plan = await buildAdapterAssetPlan({
|
|
236
92
|
adapter: 'opencode',
|
|
237
93
|
bundle,
|
|
238
94
|
options: {
|
|
@@ -242,128 +98,79 @@ describe('skills CLI dependency resolution', () => {
|
|
|
242
98
|
}
|
|
243
99
|
})
|
|
244
100
|
|
|
101
|
+
expect(plan.overlays.filter(entry => entry.kind === 'skill').map(entry => entry.targetPath).sort()).toEqual([
|
|
102
|
+
'skills/app-builder',
|
|
103
|
+
'skills/frontend-design'
|
|
104
|
+
])
|
|
245
105
|
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
106
|
})
|
|
250
107
|
|
|
251
|
-
it('
|
|
108
|
+
it('loads plugin dependencies from .ai/skills/.plugins through the lockfile', async () => {
|
|
252
109
|
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
|
-
[
|
|
110
|
+
await installPluginPackage(workspace, '@vibe-forge/plugin-review', {
|
|
111
|
+
'package.json': JSON.stringify({ name: '@vibe-forge/plugin-review', version: '1.0.0' }, null, 2),
|
|
112
|
+
'skills/review-helper/SKILL.md': [
|
|
272
113
|
'---',
|
|
273
|
-
'name:
|
|
274
|
-
'description:
|
|
114
|
+
'name: review-helper',
|
|
115
|
+
'description: Review helper',
|
|
275
116
|
'dependencies:',
|
|
276
|
-
' -
|
|
117
|
+
' - shared-runtime',
|
|
277
118
|
'---',
|
|
278
|
-
'
|
|
119
|
+
'Review code.'
|
|
279
120
|
].join('\n')
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
283
|
-
cwd: workspace,
|
|
284
|
-
configs: [undefined, undefined],
|
|
285
|
-
useDefaultVibeForgeMcpServer: false
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
await buildAdapterAssetPlan({
|
|
289
|
-
adapter: 'opencode',
|
|
290
|
-
bundle,
|
|
291
|
-
options: {
|
|
292
|
-
skills: {
|
|
293
|
-
include: ['app-builder']
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
121
|
})
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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'
|
|
122
|
+
await writeDocument(
|
|
123
|
+
join(workspace, '.ai/skills/.plugins/review/shared-runtime/SKILL.md'),
|
|
124
|
+
'---\nname: shared-runtime\ndescription: Shared runtime\n---\nShared plugin dependency.\n'
|
|
313
125
|
)
|
|
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
126
|
await writeDocument(
|
|
325
|
-
join(workspace, '.ai/skills
|
|
127
|
+
join(workspace, '.ai/skills.lock.yaml'),
|
|
326
128
|
[
|
|
327
|
-
'
|
|
328
|
-
'
|
|
329
|
-
'
|
|
330
|
-
'
|
|
331
|
-
'
|
|
332
|
-
'
|
|
333
|
-
'
|
|
129
|
+
'version: 1',
|
|
130
|
+
'pluginSkills:',
|
|
131
|
+
' review/shared-runtime:',
|
|
132
|
+
' name: shared-runtime',
|
|
133
|
+
' requested: false',
|
|
134
|
+
' pluginInstance: review',
|
|
135
|
+
' pluginInstancePath: "0"',
|
|
136
|
+
' installPath: .ai/skills/.plugins/review/shared-runtime',
|
|
137
|
+
' dependencyOf:',
|
|
138
|
+
' - plugin:review/review-helper',
|
|
139
|
+
' source: vendor/shared-skills',
|
|
140
|
+
' version: 1.0.0',
|
|
141
|
+
' hash: sha256:test',
|
|
142
|
+
' installedAt: "2026-05-13T00:00:00.000Z"'
|
|
334
143
|
].join('\n')
|
|
335
144
|
)
|
|
336
145
|
|
|
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
146
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
345
147
|
cwd: workspace,
|
|
346
|
-
configs: [
|
|
148
|
+
configs: [{
|
|
149
|
+
plugins: [{
|
|
150
|
+
id: '@vibe-forge/plugin-review',
|
|
151
|
+
scope: 'review'
|
|
152
|
+
}]
|
|
153
|
+
}, undefined],
|
|
347
154
|
useDefaultVibeForgeMcpServer: false
|
|
348
155
|
})
|
|
349
|
-
|
|
350
|
-
await buildAdapterAssetPlan({
|
|
156
|
+
const plan = await buildAdapterAssetPlan({
|
|
351
157
|
adapter: 'opencode',
|
|
352
158
|
bundle,
|
|
353
159
|
options: {
|
|
354
160
|
skills: {
|
|
355
|
-
include: ['
|
|
161
|
+
include: ['review/review-helper']
|
|
356
162
|
}
|
|
357
163
|
}
|
|
358
164
|
})
|
|
359
165
|
|
|
360
|
-
expect(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
166
|
+
expect(bundle.skills.map(asset => asset.displayName).sort()).toEqual([
|
|
167
|
+
'review/review-helper',
|
|
168
|
+
'review/shared-runtime'
|
|
169
|
+
])
|
|
170
|
+
expect(plan.overlays.filter(entry => entry.kind === 'skill').map(entry => entry.targetPath).sort()).toEqual([
|
|
171
|
+
'skills/review__review-helper',
|
|
172
|
+
'skills/review__shared-runtime'
|
|
173
|
+
])
|
|
174
|
+
expect(mocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
368
175
|
})
|
|
369
176
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-forge/workspace-assets",
|
|
3
|
-
"version": "3.2.0",
|
|
3
|
+
"version": "3.2.2-alpha.0",
|
|
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/
|
|
32
|
+
"@vibe-forge/config": "3.2.3-alpha.0",
|
|
33
|
+
"@vibe-forge/types": "3.2.3-alpha.0",
|
|
33
34
|
"@vibe-forge/definition-core": "3.2.0",
|
|
34
|
-
"@vibe-forge/utils": "3.2.0"
|
|
35
|
-
"@vibe-forge/config": "3.2.0"
|
|
35
|
+
"@vibe-forge/utils": "3.2.3-alpha.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,7 +10,8 @@ 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
|
+
isObjectSkillsConfig,
|
|
14
|
+
readProjectSkillsLockfile,
|
|
14
15
|
resolveProjectAiBaseDir,
|
|
15
16
|
resolveProjectAiEntitiesDir,
|
|
16
17
|
resolveRelativePath
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
} from '@vibe-forge/definition-core'
|
|
36
37
|
import { ensureConfiguredProjectSkills } 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'>
|
|
@@ -159,8 +161,8 @@ const warnInvalidHomeSkillRoot = (root: string) => {
|
|
|
159
161
|
|
|
160
162
|
const resolveHomeBridgeConfig = (configs: [Config?, Config?]) => {
|
|
161
163
|
const [config, userConfig] = configs
|
|
162
|
-
const projectHomeBridge =
|
|
163
|
-
const userHomeBridge =
|
|
164
|
+
const projectHomeBridge = isObjectSkillsConfig(config?.skills) ? config.skills.homeBridge : undefined
|
|
165
|
+
const userHomeBridge = isObjectSkillsConfig(userConfig?.skills) ? userConfig.skills.homeBridge : undefined
|
|
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',
|
|
@@ -578,6 +614,7 @@ export async function collectWorkspaceAssets(params: {
|
|
|
578
614
|
])
|
|
579
615
|
const flattenedPluginInstances = flattenPluginInstances(pluginInstances)
|
|
580
616
|
const pluginScans = await Promise.all(flattenedPluginInstances.map(instance => scanInstanceDocuments(instance)))
|
|
617
|
+
const pluginDependencySkillDocs = await scanPluginDependencySkillDocuments(params.cwd, flattenedPluginInstances)
|
|
581
618
|
const pluginOverlayScans = await Promise.all(
|
|
582
619
|
flattenedPluginInstances.map(instance => scanInstanceOpenCodeOverlays(instance))
|
|
583
620
|
)
|
|
@@ -631,6 +668,16 @@ export async function collectWorkspaceAssets(params: {
|
|
|
631
668
|
await pushDocumentAssets('entity', scan.entityDocPaths, 'plugin', instance, parseEntityMarkdownDocument)
|
|
632
669
|
await pushDocumentAssets('entity', scan.entityJsonPaths, 'plugin', instance, parseEntityIndexJson)
|
|
633
670
|
}
|
|
671
|
+
for (const entry of pluginDependencySkillDocs) {
|
|
672
|
+
await pushDocumentAssets(
|
|
673
|
+
'skill',
|
|
674
|
+
[entry.path],
|
|
675
|
+
'plugin',
|
|
676
|
+
entry.instance,
|
|
677
|
+
undefined,
|
|
678
|
+
PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY
|
|
679
|
+
)
|
|
680
|
+
}
|
|
634
681
|
await pushDocumentAssets('skill', homeSkillPaths, 'workspace', undefined, undefined, HOME_BRIDGE_RESOLVED_BY)
|
|
635
682
|
|
|
636
683
|
const skills = mergeSkillAssets(skillAssets)
|
package/src/configured-skills.ts
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
installProjectSkill,
|
|
7
7
|
normalizeProjectSkillInstall,
|
|
8
8
|
resolveConfiguredSkillInstalls as resolveDeclaredConfiguredSkillInstalls,
|
|
9
|
-
resolveProjectAiPath
|
|
9
|
+
resolveProjectAiPath,
|
|
10
|
+
resolveSkillsRegistry
|
|
10
11
|
} from '@vibe-forge/utils'
|
|
11
12
|
import type { NormalizedProjectSkillInstall } from '@vibe-forge/utils'
|
|
12
13
|
|
|
@@ -47,6 +48,8 @@ export const ensureConfiguredProjectSkills = async (params: {
|
|
|
47
48
|
updateInstalledSkills?: boolean
|
|
48
49
|
workspaceFolder: string
|
|
49
50
|
}) => {
|
|
51
|
+
const defaultRegistry = resolveSkillsRegistry(params.configs[1]?.skills) ??
|
|
52
|
+
resolveSkillsRegistry(params.configs[0]?.skills)
|
|
50
53
|
const installs = resolveConfiguredSkillInstalls(params.configs)
|
|
51
54
|
if (installs.length === 0) {
|
|
52
55
|
return []
|
|
@@ -75,6 +78,7 @@ export const ensureConfiguredProjectSkills = async (params: {
|
|
|
75
78
|
ensured.push(
|
|
76
79
|
await installProjectSkill({
|
|
77
80
|
force: true,
|
|
81
|
+
registry: defaultRegistry,
|
|
78
82
|
skill,
|
|
79
83
|
workspaceFolder: params.workspaceFolder
|
|
80
84
|
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY = 'plugin-skill-dependency-lock'
|
package/src/prompt-builders.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import type { Definition, Entity, Rule, Skill, Spec } from '@vibe-forge/types'
|
|
8
8
|
import { resolvePromptPath } from '@vibe-forge/utils'
|
|
9
9
|
|
|
10
|
-
import { buildManagedTaskToolGuidance } from './task-tool-guidance'
|
|
10
|
+
import { buildManagedTaskToolGuidance, resolveRuntimeProtocolCliCommand } from './task-tool-guidance'
|
|
11
11
|
|
|
12
12
|
const toMarkdownBlockquote = (content: string) => (
|
|
13
13
|
content
|
|
@@ -168,6 +168,8 @@ export const generateSpecRoutePrompt = (
|
|
|
168
168
|
|
|
169
169
|
export const generateEntitiesRoutePrompt = (entities: Definition<Entity>[]) => {
|
|
170
170
|
const taskToolGuidance = buildManagedTaskToolGuidance()
|
|
171
|
+
const runtimeProtocolCommand =
|
|
172
|
+
`${resolveRuntimeProtocolCliCommand()} --input-format stream-json --output-format stream-json`
|
|
171
173
|
return (
|
|
172
174
|
'<system-prompt>\n' +
|
|
173
175
|
'The project includes the following entities:\n' +
|
|
@@ -181,7 +183,7 @@ export const generateEntitiesRoutePrompt = (entities: Definition<Entity>[]) => {
|
|
|
181
183
|
})
|
|
182
184
|
.join('')
|
|
183
185
|
}\n` +
|
|
184
|
-
|
|
186
|
+
`When solving user problems, you may start child runtime sessions with \`${runtimeProtocolCommand}\` and a \`session.start\` protocol envelope as needed; use \`session.status\`, \`session.events\`, and \`wait\` to track progress.\n` +
|
|
185
187
|
`${taskToolGuidance}\n` +
|
|
186
188
|
'</system-prompt>\n'
|
|
187
189
|
)
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
isRemoteRuleReference,
|
|
27
27
|
parseScopedReference
|
|
28
28
|
} from '@vibe-forge/definition-core'
|
|
29
|
+
import { PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY } from './plugin-skill-dependencies'
|
|
29
30
|
import { expandSkillAssetDependencies, expandSkillAssetDependenciesWithRemoteResolution } from './skill-dependencies'
|
|
30
31
|
|
|
31
32
|
type DocumentAssetKind = Extract<WorkspaceAssetKind, 'rule' | 'spec' | 'entity' | 'skill'>
|
|
@@ -359,6 +360,10 @@ const mergeEntityDefinitions = (
|
|
|
359
360
|
|
|
360
361
|
const uniqueAssetIds = (values: string[]) => toUniqueValues(values, value => value)
|
|
361
362
|
|
|
363
|
+
const isPluginSkillDependencyAsset = (asset: Extract<WorkspaceAsset, { kind: 'skill' }>) => (
|
|
364
|
+
asset.resolvedBy === PLUGIN_SKILL_DEPENDENCY_RESOLVED_BY
|
|
365
|
+
)
|
|
366
|
+
|
|
362
367
|
const formatEntityCycle = (stack: EntityAsset[], asset: EntityAsset) => (
|
|
363
368
|
[...stack.slice(stack.findIndex(item => item.id === asset.id)), asset]
|
|
364
369
|
.map(item => item.displayName)
|
|
@@ -517,9 +522,10 @@ export const resolveSelectedSkillAssets = (
|
|
|
517
522
|
): Array<Extract<WorkspaceAsset, { kind: 'skill' }>> => {
|
|
518
523
|
if (selection == null) return assets
|
|
519
524
|
|
|
525
|
+
const rootAssets = assets.filter(asset => !isPluginSkillDependencyAsset(asset))
|
|
520
526
|
const included = selection.include != null && selection.include.length > 0
|
|
521
527
|
? resolveNamedAssets(assets, selection.include)
|
|
522
|
-
:
|
|
528
|
+
: rootAssets
|
|
523
529
|
const excluded = new Set(
|
|
524
530
|
resolveNamedAssets(assets, selection.exclude).map(asset => asset.id)
|
|
525
531
|
)
|
|
@@ -532,9 +538,10 @@ export const resolveSelectedSkillAssetsWithDependencies = async (
|
|
|
532
538
|
bundle: WorkspaceAssetBundle,
|
|
533
539
|
selection?: WorkspaceSkillSelection
|
|
534
540
|
): Promise<Array<Extract<WorkspaceAsset, { kind: 'skill' }>>> => {
|
|
541
|
+
const rootAssets = bundle.skills.filter(asset => !isPluginSkillDependencyAsset(asset))
|
|
535
542
|
const included = selection?.include != null && selection.include.length > 0
|
|
536
543
|
? resolveNamedAssets(bundle.skills, selection.include)
|
|
537
|
-
:
|
|
544
|
+
: rootAssets
|
|
538
545
|
const excluded = new Set(
|
|
539
546
|
resolveNamedAssets(bundle.skills, selection?.exclude).map(asset => asset.id)
|
|
540
547
|
)
|