@vibe-forge/workspace-assets 3.2.2-alpha.3 → 3.3.0-rc.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__/bundle.spec.ts +76 -1
- package/package.json +5 -5
- package/src/bundle-internal.ts +44 -6
- package/src/configured-skills.ts +1 -0
package/__tests__/bundle.spec.ts
CHANGED
|
@@ -22,7 +22,7 @@ vi.mock('@vibe-forge/utils/skills-cli', async () => {
|
|
|
22
22
|
}
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { resolveWorkspaceAssetBundle } from '#~/index.js'
|
|
26
26
|
|
|
27
27
|
import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
|
|
28
28
|
|
|
@@ -93,6 +93,47 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
93
93
|
]))
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
+
it('skips invalid plugin MCP files and keeps resolving other plugin assets', async () => {
|
|
97
|
+
const workspace = await createWorkspace()
|
|
98
|
+
const invalidMcpPath = join(
|
|
99
|
+
workspace,
|
|
100
|
+
'node_modules/@vibe-forge/plugin-demo/mcp/broken.yaml'
|
|
101
|
+
)
|
|
102
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await installPluginPackage(workspace, '@vibe-forge/plugin-demo', {
|
|
106
|
+
'package.json': JSON.stringify(
|
|
107
|
+
{
|
|
108
|
+
name: '@vibe-forge/plugin-demo',
|
|
109
|
+
version: '1.0.0'
|
|
110
|
+
},
|
|
111
|
+
null,
|
|
112
|
+
2
|
|
113
|
+
),
|
|
114
|
+
'mcp/browser.json': JSON.stringify({ command: 'npx', args: ['browser-server'] }, null, 2),
|
|
115
|
+
'mcp/broken.yaml': 'command: npx\nargs: [broken\n'
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
119
|
+
cwd: workspace,
|
|
120
|
+
configs: [{
|
|
121
|
+
plugins: [
|
|
122
|
+
{ id: 'demo', scope: 'demo' }
|
|
123
|
+
]
|
|
124
|
+
}, undefined],
|
|
125
|
+
useDefaultVibeForgeMcpServer: false
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
expect(Object.keys(bundle.mcpServers)).toEqual(['demo/browser'])
|
|
129
|
+
expect(warnSpy.mock.calls.map(([message]) => String(message))).toEqual(expect.arrayContaining([
|
|
130
|
+
expect.stringContaining(`Ignoring invalid mcpServer asset "${invalidMcpPath}"`)
|
|
131
|
+
]))
|
|
132
|
+
} finally {
|
|
133
|
+
warnSpy.mockRestore()
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
96
137
|
it('loads workspace assets from the env-configured ai base dir', async () => {
|
|
97
138
|
const workspace = await createWorkspace()
|
|
98
139
|
const previousBaseDir = process.env.__VF_PROJECT_AI_BASE_DIR__
|
|
@@ -187,6 +228,40 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
187
228
|
}))
|
|
188
229
|
})
|
|
189
230
|
|
|
231
|
+
it('skips invalid home-bridged skill frontmatter and keeps resolving other skills', async () => {
|
|
232
|
+
const workspace = await createWorkspace()
|
|
233
|
+
const realHome = process.env.__VF_PROJECT_REAL_HOME__
|
|
234
|
+
const invalidSkillPath = join(realHome!, '.agents/skills/broken/SKILL.md')
|
|
235
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await writeDocument(
|
|
239
|
+
invalidSkillPath,
|
|
240
|
+
'---\ndescription: Use minimal writing principles: understand the reader\n---\nBroken skill body'
|
|
241
|
+
)
|
|
242
|
+
await writeDocument(
|
|
243
|
+
join(realHome!, '.agents/skills/research/SKILL.md'),
|
|
244
|
+
'---\ndescription: Valid skill\n---\nValid skill body'
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
248
|
+
cwd: workspace,
|
|
249
|
+
configs: [undefined, undefined],
|
|
250
|
+
useDefaultVibeForgeMcpServer: false
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
expect(bundle.skills.map(asset => asset.displayName)).toEqual(['research'])
|
|
254
|
+
expect(warnSpy.mock.calls.map(([message]) => String(message))).toEqual(expect.arrayContaining([
|
|
255
|
+
expect.stringContaining(`Ignoring invalid skill asset "${invalidSkillPath}"`)
|
|
256
|
+
]))
|
|
257
|
+
expect(warnSpy.mock.calls.map(([message]) => String(message))).toEqual(expect.arrayContaining([
|
|
258
|
+
expect.stringContaining('quote plain strings containing ": "')
|
|
259
|
+
]))
|
|
260
|
+
} finally {
|
|
261
|
+
warnSpy.mockRestore()
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
|
|
190
265
|
it('can disable the home skill bridge entirely', async () => {
|
|
191
266
|
const workspace = await createWorkspace()
|
|
192
267
|
const realHome = process.env.__VF_PROJECT_REAL_HOME__
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-forge/workspace-assets",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0-rc.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/config": "3.
|
|
33
|
-
"@vibe-forge/
|
|
34
|
-
"@vibe-forge/
|
|
35
|
-
"@vibe-forge/
|
|
32
|
+
"@vibe-forge/config": "3.3.0-rc.0",
|
|
33
|
+
"@vibe-forge/utils": "3.3.0-rc.0",
|
|
34
|
+
"@vibe-forge/types": "3.3.0-rc.0",
|
|
35
|
+
"@vibe-forge/definition-core": "3.3.0-rc.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/js-yaml": "^4.0.9"
|
package/src/bundle-internal.ts
CHANGED
|
@@ -159,6 +159,20 @@ const warnInvalidHomeSkillRoot = (root: string) => {
|
|
|
159
159
|
)
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
const formatErrorMessage = (error: unknown) => (
|
|
163
|
+
(error instanceof Error ? error.message : String(error))
|
|
164
|
+
.split(/\r?\n/u)[0]
|
|
165
|
+
?.trim() ?? 'Unknown error'
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const warnInvalidWorkspaceAsset = (kind: WorkspaceAssetKind, path: string, error: unknown) => {
|
|
169
|
+
console.warn(
|
|
170
|
+
`[vibe-forge] Ignoring invalid ${kind} asset "${path}". ` +
|
|
171
|
+
`${formatErrorMessage(error)}. ` +
|
|
172
|
+
'Check the asset frontmatter or structured config syntax; quote plain strings containing ": ".'
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
162
176
|
const resolveHomeBridgeConfig = (configs: [Config?, Config?]) => {
|
|
163
177
|
const [config, userConfig] = configs
|
|
164
178
|
const projectHomeBridge = resolveSkillsHomeBridge(config)
|
|
@@ -221,6 +235,19 @@ const parseFrontmatterDocument = async <TDefinition extends object>(
|
|
|
221
235
|
}
|
|
222
236
|
}
|
|
223
237
|
|
|
238
|
+
const parseOptionalDocument = async <TDefinition extends object>(
|
|
239
|
+
kind: DocumentAssetKind,
|
|
240
|
+
path: string,
|
|
241
|
+
parser: (path: string) => Promise<Definition<TDefinition>>
|
|
242
|
+
) => {
|
|
243
|
+
try {
|
|
244
|
+
return await parser(path)
|
|
245
|
+
} catch (error) {
|
|
246
|
+
warnInvalidWorkspaceAsset(kind, path, error)
|
|
247
|
+
return undefined
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
224
251
|
const parseEntityMarkdownDocument = async (path: string): Promise<Definition<Entity>> => {
|
|
225
252
|
const definition = await parseFrontmatterDocument<Entity>(path)
|
|
226
253
|
|
|
@@ -256,6 +283,15 @@ const parseStructuredMcpFile = async (path: string) => {
|
|
|
256
283
|
: JSON.parse(raw)
|
|
257
284
|
}
|
|
258
285
|
|
|
286
|
+
const parseOptionalStructuredMcpFile = async (path: string) => {
|
|
287
|
+
try {
|
|
288
|
+
return await parseStructuredMcpFile(path)
|
|
289
|
+
} catch (error) {
|
|
290
|
+
warnInvalidWorkspaceAsset('mcpServer', path, error)
|
|
291
|
+
return undefined
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
259
295
|
const createDocumentAsset = <
|
|
260
296
|
TKind extends DocumentAssetKind,
|
|
261
297
|
TDefinition extends { path: string; attributes: { name?: string } },
|
|
@@ -659,10 +695,12 @@ export async function collectWorkspaceAssets(params: {
|
|
|
659
695
|
resolvedBy?: string
|
|
660
696
|
) => {
|
|
661
697
|
const definitions = await Promise.all(paths.map(path => (
|
|
662
|
-
parser != null ? parser
|
|
698
|
+
parseOptionalDocument(kind, path, parser != null ? parser : parseFrontmatterDocument)
|
|
663
699
|
)))
|
|
664
|
-
const createdAssets = definitions.
|
|
665
|
-
|
|
700
|
+
const createdAssets = definitions.flatMap((definition) => {
|
|
701
|
+
if (definition == null) return []
|
|
702
|
+
|
|
703
|
+
return [createDocumentAsset({
|
|
666
704
|
cwd: params.cwd,
|
|
667
705
|
kind,
|
|
668
706
|
definition,
|
|
@@ -670,8 +708,8 @@ export async function collectWorkspaceAssets(params: {
|
|
|
670
708
|
scope: instance?.scope,
|
|
671
709
|
instance,
|
|
672
710
|
resolvedBy
|
|
673
|
-
})
|
|
674
|
-
)
|
|
711
|
+
})]
|
|
712
|
+
})
|
|
675
713
|
|
|
676
714
|
if (kind === 'skill') {
|
|
677
715
|
skillAssets.push(...createdAssets as SkillAsset[])
|
|
@@ -770,7 +808,7 @@ export async function collectWorkspaceAssets(params: {
|
|
|
770
808
|
const instance = flattenedPluginInstances[index]
|
|
771
809
|
const scan = pluginScans[index]
|
|
772
810
|
for (const path of scan.mcpPaths) {
|
|
773
|
-
const parsed = await
|
|
811
|
+
const parsed = await parseOptionalStructuredMcpFile(path)
|
|
774
812
|
if (!isRecord(parsed)) continue
|
|
775
813
|
const fileName = basename(path, extname(path))
|
|
776
814
|
const name = typeof parsed.name === 'string' && parsed.name.trim() !== ''
|
package/src/configured-skills.ts
CHANGED