@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.
@@ -1,9 +1,7 @@
1
1
  /* eslint-disable import/first -- hoisted vitest mocks must be declared before importing the bundle entrypoint */
2
- import { access, mkdir, mkdtemp, rm, utimes, writeFile } from 'node:fs/promises'
3
- import os from 'node:os'
4
- import path, { join } from 'node:path'
2
+ import { join } from 'node:path'
5
3
 
6
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
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('skills CLI dependency resolution', () => {
29
- let installWorkspace: string
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
- afterEach(async () => {
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('Skill dependency automatic downloads are disabled; cache not found')
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('blocks bare-name dependency searches when auto downloads are disabled', async () => {
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/app-builder/SKILL.md'),
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('parses registry and version from dependency specs', async () => {
108
+ it('loads plugin dependencies from .ai/skills/.plugins through the lockfile', async () => {
252
109
  const workspace = await createWorkspace()
253
- const installedSkillDir = join(installWorkspace, '.agents', 'skills', 'frontend-design')
254
- await mkdir(installedSkillDir, { recursive: true })
255
- await writeFile(
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: app-builder',
274
- 'description: Build apps',
114
+ 'name: review-helper',
115
+ 'description: Review helper',
275
116
  'dependencies:',
276
- ' - https://registry.example.com@example-source/default/public@frontend-design@1.0.3',
117
+ ' - shared-runtime',
277
118
  '---',
278
- 'Build the app.'
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
- 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'
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/lynx-miniapp/SKILL.md'),
127
+ join(workspace, '.ai/skills.lock.yaml'),
326
128
  [
327
- '---',
328
- 'name: lynx-miniapp',
329
- 'description: lynx 调试使用',
330
- 'dependencies:',
331
- ' - https://registry.example.com@example-source/lynx/skills@lynx-cat@latest',
332
- '---',
333
- '这是一个测试的 lynx 调试技能'
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: [undefined, undefined],
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: ['lynx-miniapp']
161
+ include: ['review/review-helper']
356
162
  }
357
163
  }
358
164
  })
359
165
 
360
- expect(mocks.installSkillsCliSkillToTemp).toHaveBeenCalledWith({
361
- registry: 'https://registry.example.com',
362
- skill: 'lynx-cat',
363
- source: 'example-source/lynx/skills',
364
- version: 'latest'
365
- })
366
- await expect(pathExists(lockDir)).resolves.toBe(false)
367
- expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['lynx-cat', 'lynx-miniapp'])
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/types": "3.2.0",
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"
@@ -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
- isLegacySkillsConfig,
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 = isLegacySkillsConfig(config?.skills) ? config.skills.homeBridge : undefined
163
- const userHomeBridge = isLegacySkillsConfig(userConfig?.skills) ? userConfig.skills.homeBridge : undefined
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)
@@ -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'
@@ -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
- 'When solving user problems, you may start child runtime sessions with `vf run --input-format stream-json --output-format stream-json` and a `session.start` protocol envelope as needed; use `session.status`, `session.events`, and `wait` to track progress.\n' +
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
- : assets
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
- : bundle.skills
544
+ : rootAssets
538
545
  const excluded = new Set(
539
546
  resolveNamedAssets(bundle.skills, selection?.exclude).map(asset => asset.id)
540
547
  )