@vibe-forge/workspace-assets 0.11.1 → 1.0.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 +120 -0
- package/__tests__/adapter-asset-plan.spec.ts +236 -0
- package/__tests__/adapter-capabilities.spec.ts +14 -0
- package/__tests__/bundle.spec.ts +57 -0
- package/__tests__/workspace-assets.snapshot.spec.ts +1 -1
- package/package.json +5 -5
- package/src/adapter-asset-plan.ts +44 -9
- package/src/adapter-capabilities.ts +7 -1
- package/src/bundle-internal.ts +10 -8
- package/src/internal-types.ts +3 -1
- package/src/selection-internal.ts +5 -2
|
@@ -696,6 +696,126 @@
|
|
|
696
696
|
}
|
|
697
697
|
]
|
|
698
698
|
},
|
|
699
|
+
"gemini": {
|
|
700
|
+
"adapter": "gemini",
|
|
701
|
+
"mcpServers": {
|
|
702
|
+
"docs": {
|
|
703
|
+
"args": [
|
|
704
|
+
"docs-server"
|
|
705
|
+
],
|
|
706
|
+
"command": "npx"
|
|
707
|
+
}
|
|
708
|
+
},
|
|
709
|
+
"overlays": [],
|
|
710
|
+
"diagnostics": [
|
|
711
|
+
{
|
|
712
|
+
"assetId": "agent:plugin:1:demo/release-helper:<workspace>/node_modules/@vibe-forge/plugin-demo/opencode/agents/release-helper.md",
|
|
713
|
+
"adapter": "gemini",
|
|
714
|
+
"status": "skipped",
|
|
715
|
+
"reason": "No stable native Gemini mapping exists for this asset kind in V1.",
|
|
716
|
+
"scope": "demo",
|
|
717
|
+
"packageId": "@vibe-forge/plugin-demo"
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
"assetId": "command:plugin:1:demo/review:<workspace>/node_modules/@vibe-forge/plugin-demo/opencode/commands/review.md",
|
|
721
|
+
"adapter": "gemini",
|
|
722
|
+
"status": "skipped",
|
|
723
|
+
"reason": "No stable native Gemini mapping exists for this asset kind in V1.",
|
|
724
|
+
"scope": "demo",
|
|
725
|
+
"packageId": "@vibe-forge/plugin-demo"
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
"assetId": "entity:workspace:workspace:architect:<workspace>/.ai/entities/architect/README.md",
|
|
729
|
+
"adapter": "gemini",
|
|
730
|
+
"status": "prompt",
|
|
731
|
+
"reason": "Mapped into the generated system prompt."
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
"assetId": "hookPlugin:plugin:0:logger:<workspace>/node_modules/@vibe-forge/plugin-logger",
|
|
735
|
+
"adapter": "gemini",
|
|
736
|
+
"status": "native",
|
|
737
|
+
"reason": "Mapped into the Gemini native hooks bridge.",
|
|
738
|
+
"packageId": "@vibe-forge/plugin-logger"
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
"assetId": "hookPlugin:plugin:1:demo/demo:<workspace>/node_modules/@vibe-forge/plugin-demo",
|
|
742
|
+
"adapter": "gemini",
|
|
743
|
+
"status": "native",
|
|
744
|
+
"reason": "Mapped into the Gemini native hooks bridge.",
|
|
745
|
+
"scope": "demo",
|
|
746
|
+
"packageId": "@vibe-forge/plugin-demo"
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
"assetId": "hookPlugin:plugin:2:telemetry:<workspace>/node_modules/@vibe-forge/plugin-telemetry",
|
|
750
|
+
"adapter": "gemini",
|
|
751
|
+
"status": "native",
|
|
752
|
+
"reason": "Mapped into the Gemini native hooks bridge.",
|
|
753
|
+
"packageId": "@vibe-forge/plugin-telemetry"
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
"assetId": "mcpServer:workspace:workspace:docs:<workspace>/.ai.config.json",
|
|
757
|
+
"adapter": "gemini",
|
|
758
|
+
"status": "translated",
|
|
759
|
+
"reason": "Translated into adapter-specific MCP configuration."
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
"assetId": "mode:plugin:1:demo/strict:<workspace>/node_modules/@vibe-forge/plugin-demo/opencode/modes/strict.md",
|
|
763
|
+
"adapter": "gemini",
|
|
764
|
+
"status": "skipped",
|
|
765
|
+
"reason": "No stable native Gemini mapping exists for this asset kind in V1.",
|
|
766
|
+
"scope": "demo",
|
|
767
|
+
"packageId": "@vibe-forge/plugin-demo"
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
"assetId": "nativePlugin:plugin:1:demo/demo-plugin:<workspace>/node_modules/@vibe-forge/plugin-demo/opencode/plugins/demo-plugin.js",
|
|
771
|
+
"adapter": "gemini",
|
|
772
|
+
"status": "skipped",
|
|
773
|
+
"reason": "No stable native Gemini mapping exists for this asset kind in V1.",
|
|
774
|
+
"scope": "demo",
|
|
775
|
+
"packageId": "@vibe-forge/plugin-demo"
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
"assetId": "rule:plugin:1:demo/security:<workspace>/node_modules/@vibe-forge/plugin-demo/rules/security.md",
|
|
779
|
+
"adapter": "gemini",
|
|
780
|
+
"status": "prompt",
|
|
781
|
+
"reason": "Mapped into the generated system prompt.",
|
|
782
|
+
"scope": "demo",
|
|
783
|
+
"packageId": "@vibe-forge/plugin-demo"
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
"assetId": "rule:workspace:workspace:review:<workspace>/.ai/rules/review.md",
|
|
787
|
+
"adapter": "gemini",
|
|
788
|
+
"status": "prompt",
|
|
789
|
+
"reason": "Mapped into the generated system prompt."
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
"assetId": "skill:workspace:workspace:research:<workspace>/.ai/skills/research/SKILL.md",
|
|
793
|
+
"adapter": "gemini",
|
|
794
|
+
"status": "native",
|
|
795
|
+
"reason": "Symlinked into GEMINI_CLI_HOME as a native Gemini skill."
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
"assetId": "skill:workspace:workspace:research:<workspace>/.ai/skills/research/SKILL.md",
|
|
799
|
+
"adapter": "gemini",
|
|
800
|
+
"status": "prompt",
|
|
801
|
+
"reason": "Mapped into the generated system prompt."
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
"assetId": "spec:plugin:1:demo/release:<workspace>/node_modules/@vibe-forge/plugin-demo/specs/release/index.md",
|
|
805
|
+
"adapter": "gemini",
|
|
806
|
+
"status": "prompt",
|
|
807
|
+
"reason": "Mapped into the generated system prompt.",
|
|
808
|
+
"scope": "demo",
|
|
809
|
+
"packageId": "@vibe-forge/plugin-demo"
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
"assetId": "spec:workspace:workspace:release:<workspace>/.ai/specs/release/index.md",
|
|
813
|
+
"adapter": "gemini",
|
|
814
|
+
"status": "prompt",
|
|
815
|
+
"reason": "Mapped into the generated system prompt."
|
|
816
|
+
}
|
|
817
|
+
]
|
|
818
|
+
},
|
|
699
819
|
"opencode": {
|
|
700
820
|
"adapter": "opencode",
|
|
701
821
|
"mcpServers": {
|
|
@@ -179,4 +179,240 @@ describe('buildAdapterAssetPlan', () => {
|
|
|
179
179
|
})
|
|
180
180
|
]))
|
|
181
181
|
})
|
|
182
|
+
|
|
183
|
+
it('builds copilot native skill overlays and translated runtime diagnostics', async () => {
|
|
184
|
+
const workspace = await createWorkspace()
|
|
185
|
+
|
|
186
|
+
await installPluginPackage(workspace, '@vibe-forge/plugin-logger', {
|
|
187
|
+
'package.json': JSON.stringify(
|
|
188
|
+
{
|
|
189
|
+
name: '@vibe-forge/plugin-logger',
|
|
190
|
+
version: '1.0.0'
|
|
191
|
+
},
|
|
192
|
+
null,
|
|
193
|
+
2
|
|
194
|
+
),
|
|
195
|
+
'hooks.js': 'module.exports = {}\n'
|
|
196
|
+
})
|
|
197
|
+
await installPluginPackage(workspace, '@vibe-forge/plugin-demo', {
|
|
198
|
+
'package.json': JSON.stringify(
|
|
199
|
+
{
|
|
200
|
+
name: '@vibe-forge/plugin-demo',
|
|
201
|
+
version: '1.0.0'
|
|
202
|
+
},
|
|
203
|
+
null,
|
|
204
|
+
2
|
|
205
|
+
),
|
|
206
|
+
'opencode/commands/review.md': '# review\n'
|
|
207
|
+
})
|
|
208
|
+
await writeDocument(
|
|
209
|
+
join(workspace, '.ai/skills/research/SKILL.md'),
|
|
210
|
+
'---\ndescription: 检索资料\n---\n阅读 README.md'
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
214
|
+
cwd: workspace,
|
|
215
|
+
configs: [{
|
|
216
|
+
plugins: [
|
|
217
|
+
{ id: 'logger' },
|
|
218
|
+
{ id: 'demo', scope: 'demo' }
|
|
219
|
+
],
|
|
220
|
+
mcpServers: {
|
|
221
|
+
docs: {
|
|
222
|
+
command: 'npx',
|
|
223
|
+
args: ['docs-server']
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}, undefined],
|
|
227
|
+
useDefaultVibeForgeMcpServer: false
|
|
228
|
+
})
|
|
229
|
+
const researchSkillId = bundle.skills.find(asset => asset.name === 'research')?.id
|
|
230
|
+
const loggerHookPluginId = bundle.hookPlugins.find(asset => asset.packageId === '@vibe-forge/plugin-logger')?.id
|
|
231
|
+
const demoCommandId = bundle.opencodeOverlayAssets.find(asset => asset.kind === 'command')?.id
|
|
232
|
+
const docsMcpId = bundle.mcpServers.docs?.id
|
|
233
|
+
expect(researchSkillId).toBeDefined()
|
|
234
|
+
expect(loggerHookPluginId).toBeDefined()
|
|
235
|
+
expect(demoCommandId).toBeDefined()
|
|
236
|
+
expect(docsMcpId).toBeDefined()
|
|
237
|
+
|
|
238
|
+
const [, resolvedOptions] = await resolvePromptAssetSelection({
|
|
239
|
+
bundle,
|
|
240
|
+
type: undefined,
|
|
241
|
+
name: undefined,
|
|
242
|
+
adapter: 'copilot',
|
|
243
|
+
input: {
|
|
244
|
+
skills: {
|
|
245
|
+
include: ['research']
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
const plan = buildAdapterAssetPlan({
|
|
250
|
+
adapter: 'copilot',
|
|
251
|
+
bundle,
|
|
252
|
+
options: {
|
|
253
|
+
promptAssetIds: resolvedOptions.promptAssetIds,
|
|
254
|
+
mcpServers: resolvedOptions.mcpServers,
|
|
255
|
+
skills: {
|
|
256
|
+
include: ['research']
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
expect(plan.mcpServers).toHaveProperty('docs')
|
|
262
|
+
expect(plan.overlays).toEqual([
|
|
263
|
+
expect.objectContaining({
|
|
264
|
+
assetId: researchSkillId,
|
|
265
|
+
kind: 'skill',
|
|
266
|
+
targetPath: 'skills/research'
|
|
267
|
+
})
|
|
268
|
+
])
|
|
269
|
+
expect(plan.diagnostics).toEqual(expect.arrayContaining([
|
|
270
|
+
expect.objectContaining({
|
|
271
|
+
assetId: researchSkillId,
|
|
272
|
+
adapter: 'copilot',
|
|
273
|
+
status: 'native'
|
|
274
|
+
}),
|
|
275
|
+
expect.objectContaining({
|
|
276
|
+
assetId: loggerHookPluginId,
|
|
277
|
+
adapter: 'copilot',
|
|
278
|
+
status: 'translated'
|
|
279
|
+
}),
|
|
280
|
+
expect.objectContaining({
|
|
281
|
+
assetId: docsMcpId,
|
|
282
|
+
adapter: 'copilot',
|
|
283
|
+
status: 'translated'
|
|
284
|
+
}),
|
|
285
|
+
expect.objectContaining({
|
|
286
|
+
assetId: demoCommandId,
|
|
287
|
+
adapter: 'copilot',
|
|
288
|
+
status: 'skipped'
|
|
289
|
+
})
|
|
290
|
+
]))
|
|
291
|
+
expect(plan.diagnostics).not.toEqual(expect.arrayContaining([
|
|
292
|
+
expect.objectContaining({
|
|
293
|
+
assetId: researchSkillId,
|
|
294
|
+
adapter: 'copilot',
|
|
295
|
+
status: 'prompt'
|
|
296
|
+
})
|
|
297
|
+
]))
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('builds kimi overlays for native skills and native hooks', async () => {
|
|
301
|
+
const workspace = await createWorkspace()
|
|
302
|
+
|
|
303
|
+
await installPluginPackage(workspace, '@vibe-forge/plugin-logger', {
|
|
304
|
+
'package.json': JSON.stringify(
|
|
305
|
+
{
|
|
306
|
+
name: '@vibe-forge/plugin-logger',
|
|
307
|
+
version: '1.0.0'
|
|
308
|
+
},
|
|
309
|
+
null,
|
|
310
|
+
2
|
|
311
|
+
),
|
|
312
|
+
'hooks.js': 'module.exports = {}\n'
|
|
313
|
+
})
|
|
314
|
+
await installPluginPackage(workspace, '@vibe-forge/plugin-demo', {
|
|
315
|
+
'package.json': JSON.stringify(
|
|
316
|
+
{
|
|
317
|
+
name: '@vibe-forge/plugin-demo',
|
|
318
|
+
version: '1.0.0'
|
|
319
|
+
},
|
|
320
|
+
null,
|
|
321
|
+
2
|
|
322
|
+
),
|
|
323
|
+
'opencode/commands/review.md': '# review\n'
|
|
324
|
+
})
|
|
325
|
+
await writeDocument(
|
|
326
|
+
join(workspace, '.ai/skills/research/SKILL.md'),
|
|
327
|
+
'---\ndescription: 检索资料\n---\n阅读 README.md'
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
331
|
+
cwd: workspace,
|
|
332
|
+
configs: [{
|
|
333
|
+
plugins: [
|
|
334
|
+
{ id: 'logger' },
|
|
335
|
+
{ id: 'demo', scope: 'demo' }
|
|
336
|
+
],
|
|
337
|
+
mcpServers: {
|
|
338
|
+
docs: {
|
|
339
|
+
command: 'npx',
|
|
340
|
+
args: ['docs-server']
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}, undefined],
|
|
344
|
+
useDefaultVibeForgeMcpServer: false
|
|
345
|
+
})
|
|
346
|
+
const loggerHookPluginId = bundle.hookPlugins.find(asset => asset.packageId === '@vibe-forge/plugin-logger')?.id
|
|
347
|
+
const demoCommandId = bundle.opencodeOverlayAssets.find(asset => asset.kind === 'command')?.id
|
|
348
|
+
|
|
349
|
+
const plan = buildAdapterAssetPlan({
|
|
350
|
+
adapter: 'kimi',
|
|
351
|
+
bundle,
|
|
352
|
+
options: {
|
|
353
|
+
skills: {
|
|
354
|
+
include: ['research']
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
expect(plan.overlays).toEqual(expect.arrayContaining([
|
|
360
|
+
expect.objectContaining({
|
|
361
|
+
kind: 'skill',
|
|
362
|
+
targetPath: 'research'
|
|
363
|
+
})
|
|
364
|
+
]))
|
|
365
|
+
expect(plan.diagnostics).toEqual(expect.arrayContaining([
|
|
366
|
+
expect.objectContaining({
|
|
367
|
+
adapter: 'kimi',
|
|
368
|
+
assetId: loggerHookPluginId,
|
|
369
|
+
status: 'native'
|
|
370
|
+
}),
|
|
371
|
+
expect.objectContaining({
|
|
372
|
+
adapter: 'kimi',
|
|
373
|
+
assetId: demoCommandId,
|
|
374
|
+
status: 'skipped'
|
|
375
|
+
})
|
|
376
|
+
]))
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('marks Gemini hook plugins as native bridge assets', async () => {
|
|
380
|
+
const workspace = await createWorkspace()
|
|
381
|
+
|
|
382
|
+
await installPluginPackage(workspace, '@vibe-forge/plugin-logger', {
|
|
383
|
+
'package.json': JSON.stringify(
|
|
384
|
+
{
|
|
385
|
+
name: '@vibe-forge/plugin-logger',
|
|
386
|
+
version: '1.0.0'
|
|
387
|
+
},
|
|
388
|
+
null,
|
|
389
|
+
2
|
|
390
|
+
),
|
|
391
|
+
'hooks.js': 'module.exports = {}\n'
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
395
|
+
cwd: workspace,
|
|
396
|
+
configs: [{
|
|
397
|
+
plugins: [{ id: 'logger' }]
|
|
398
|
+
}, undefined],
|
|
399
|
+
useDefaultVibeForgeMcpServer: false
|
|
400
|
+
})
|
|
401
|
+
const loggerHookPluginId = bundle.hookPlugins.find(asset => asset.packageId === '@vibe-forge/plugin-logger')?.id
|
|
402
|
+
|
|
403
|
+
const plan = buildAdapterAssetPlan({
|
|
404
|
+
adapter: 'gemini',
|
|
405
|
+
bundle,
|
|
406
|
+
options: {}
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
expect(plan.diagnostics).toEqual(expect.arrayContaining([
|
|
410
|
+
expect.objectContaining({
|
|
411
|
+
adapter: 'gemini',
|
|
412
|
+
assetId: loggerHookPluginId,
|
|
413
|
+
status: 'native',
|
|
414
|
+
reason: 'Mapped into the Gemini native hooks bridge.'
|
|
415
|
+
})
|
|
416
|
+
]))
|
|
417
|
+
})
|
|
182
418
|
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { resolveNativeSkillDiagnosticReason, supportsNativeProjectSkills } from '#~/adapter-capabilities.js'
|
|
4
|
+
|
|
5
|
+
describe('adapter native skill capabilities', () => {
|
|
6
|
+
it('treats gemini as a native skill adapter', () => {
|
|
7
|
+
expect(supportsNativeProjectSkills('gemini')).toBe(true)
|
|
8
|
+
expect(resolveNativeSkillDiagnosticReason('gemini')).toContain('Gemini')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('does not treat codex as a native skill adapter', () => {
|
|
12
|
+
expect(supportsNativeProjectSkills('codex')).toBe(false)
|
|
13
|
+
})
|
|
14
|
+
})
|
package/__tests__/bundle.spec.ts
CHANGED
|
@@ -68,6 +68,63 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
68
68
|
]))
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
it('loads workspace assets from the env-configured ai base dir', async () => {
|
|
72
|
+
const workspace = await createWorkspace()
|
|
73
|
+
const previousBaseDir = process.env.__VF_PROJECT_AI_BASE_DIR__
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
process.env.__VF_PROJECT_AI_BASE_DIR__ = '.vf'
|
|
77
|
+
await writeDocument(join(workspace, '.vf/rules/review.md'), '---\ndescription: 评审规则\n---\n必须检查风险')
|
|
78
|
+
await writeDocument(
|
|
79
|
+
join(workspace, '.vf/skills/research/SKILL.md'),
|
|
80
|
+
'---\ndescription: 检索资料\n---\n阅读 README.md'
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
84
|
+
cwd: workspace,
|
|
85
|
+
configs: [undefined, undefined],
|
|
86
|
+
useDefaultVibeForgeMcpServer: false
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect(bundle.rules.map(asset => asset.displayName)).toEqual(['review'])
|
|
90
|
+
expect(bundle.skills.map(asset => asset.displayName)).toEqual(['research'])
|
|
91
|
+
} finally {
|
|
92
|
+
if (previousBaseDir == null) {
|
|
93
|
+
delete process.env.__VF_PROJECT_AI_BASE_DIR__
|
|
94
|
+
} else {
|
|
95
|
+
process.env.__VF_PROJECT_AI_BASE_DIR__ = previousBaseDir
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('loads workspace entities from the env-configured entities dir', async () => {
|
|
101
|
+
const workspace = await createWorkspace()
|
|
102
|
+
const previousEntitiesDir = process.env.__VF_PROJECT_AI_ENTITIES_DIR__
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
process.env.__VF_PROJECT_AI_ENTITIES_DIR__ = 'agents'
|
|
106
|
+
await writeDocument(
|
|
107
|
+
join(workspace, '.ai/agents/reviewer/README.md'),
|
|
108
|
+
'---\ndescription: 负责代码评审\n---\n检查风险'
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
112
|
+
cwd: workspace,
|
|
113
|
+
configs: [undefined, undefined],
|
|
114
|
+
useDefaultVibeForgeMcpServer: false
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
expect(bundle.entities.map(asset => asset.displayName)).toEqual(['reviewer'])
|
|
118
|
+
expect(bundle.entities[0]?.sourcePath).toContain('/.ai/agents/reviewer/README.md')
|
|
119
|
+
} finally {
|
|
120
|
+
if (previousEntitiesDir == null) {
|
|
121
|
+
delete process.env.__VF_PROJECT_AI_ENTITIES_DIR__
|
|
122
|
+
} else {
|
|
123
|
+
process.env.__VF_PROJECT_AI_ENTITIES_DIR__ = previousEntitiesDir
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
71
128
|
it('auto-loads managed Claude plugins from .ai/plugins as directory plugins', async () => {
|
|
72
129
|
const workspace = await createWorkspace()
|
|
73
130
|
|
|
@@ -188,7 +188,7 @@ describe('workspace assets snapshots', () => {
|
|
|
188
188
|
}
|
|
189
189
|
})
|
|
190
190
|
|
|
191
|
-
const adapters = ['claude-code', 'codex', 'opencode'] as const
|
|
191
|
+
const adapters = ['claude-code', 'codex', 'gemini', 'opencode'] as const
|
|
192
192
|
const plans = adapters.map(adapter => (
|
|
193
193
|
buildAdapterAssetPlan({
|
|
194
194
|
adapter,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-forge/workspace-assets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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": "^0.
|
|
33
|
-
"@vibe-forge/definition-core": "^0.
|
|
34
|
-
"@vibe-forge/types": "^0.
|
|
35
|
-
"@vibe-forge/utils": "^0.
|
|
32
|
+
"@vibe-forge/config": "^1.0.0",
|
|
33
|
+
"@vibe-forge/definition-core": "^1.0.0",
|
|
34
|
+
"@vibe-forge/types": "^1.0.0",
|
|
35
|
+
"@vibe-forge/utils": "^1.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/js-yaml": "^4.0.9"
|
|
@@ -68,11 +68,17 @@ export function buildAdapterAssetPlan(params: {
|
|
|
68
68
|
diagnostics.push({
|
|
69
69
|
assetId: asset.id,
|
|
70
70
|
adapter: params.adapter,
|
|
71
|
-
status: 'native',
|
|
71
|
+
status: params.adapter === 'copilot' ? 'translated' : 'native',
|
|
72
72
|
reason: params.adapter === 'claude-code'
|
|
73
73
|
? 'Mapped into the Claude Code native hooks bridge.'
|
|
74
74
|
: params.adapter === 'codex'
|
|
75
75
|
? 'Mapped into the Codex native hooks bridge.'
|
|
76
|
+
: params.adapter === 'gemini'
|
|
77
|
+
? 'Mapped into the Gemini native hooks bridge.'
|
|
78
|
+
: params.adapter === 'copilot'
|
|
79
|
+
? 'Handled by the Vibe Forge task hook bridge.'
|
|
80
|
+
: params.adapter === 'kimi'
|
|
81
|
+
? 'Mapped into the Kimi native hooks bridge.'
|
|
76
82
|
: 'Mapped into the OpenCode native hooks bridge.',
|
|
77
83
|
packageId: asset.packageId,
|
|
78
84
|
scope: asset.scope,
|
|
@@ -115,13 +121,32 @@ export function buildAdapterAssetPlan(params: {
|
|
|
115
121
|
taskOverlaySource: asset.taskOverlaySource
|
|
116
122
|
})
|
|
117
123
|
})
|
|
118
|
-
} else if (params.adapter === 'codex') {
|
|
124
|
+
} else if (params.adapter === 'codex' || params.adapter === 'copilot' || params.adapter === 'kimi') {
|
|
119
125
|
params.bundle.opencodeOverlayAssets.forEach((asset) => {
|
|
120
126
|
diagnostics.push({
|
|
121
127
|
assetId: asset.id,
|
|
122
128
|
adapter: params.adapter,
|
|
123
129
|
status: 'skipped',
|
|
124
|
-
reason:
|
|
130
|
+
reason: params.adapter === 'codex'
|
|
131
|
+
? 'No stable native Codex mapping exists for this asset kind in V1.'
|
|
132
|
+
: params.adapter === 'copilot'
|
|
133
|
+
? 'No stable native Copilot mapping exists for this asset kind in V1.'
|
|
134
|
+
: 'No stable native Kimi mapping exists for this asset kind in V1.',
|
|
135
|
+
packageId: asset.packageId,
|
|
136
|
+
scope: asset.scope,
|
|
137
|
+
instancePath: asset.instancePath,
|
|
138
|
+
origin: asset.origin,
|
|
139
|
+
resolvedBy: asset.resolvedBy,
|
|
140
|
+
taskOverlaySource: asset.taskOverlaySource
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
} else if (params.adapter === 'gemini') {
|
|
144
|
+
params.bundle.opencodeOverlayAssets.forEach((asset) => {
|
|
145
|
+
diagnostics.push({
|
|
146
|
+
assetId: asset.id,
|
|
147
|
+
adapter: params.adapter,
|
|
148
|
+
status: 'skipped',
|
|
149
|
+
reason: 'No stable native Gemini mapping exists for this asset kind in V1.',
|
|
125
150
|
packageId: asset.packageId,
|
|
126
151
|
scope: asset.scope,
|
|
127
152
|
instancePath: asset.instancePath,
|
|
@@ -132,14 +157,15 @@ export function buildAdapterAssetPlan(params: {
|
|
|
132
157
|
})
|
|
133
158
|
}
|
|
134
159
|
|
|
160
|
+
const selectedSkillOverlays = selectedSkillAssets.map((asset): AdapterOverlayEntry => ({
|
|
161
|
+
assetId: asset.id,
|
|
162
|
+
kind: 'skill',
|
|
163
|
+
sourcePath: dirname(asset.sourcePath),
|
|
164
|
+
targetPath: `skills/${asset.displayName.replaceAll('/', '__')}`
|
|
165
|
+
}))
|
|
135
166
|
const overlays: AdapterOverlayEntry[] = params.adapter === 'opencode'
|
|
136
167
|
? [
|
|
137
|
-
...
|
|
138
|
-
assetId: asset.id,
|
|
139
|
-
kind: 'skill',
|
|
140
|
-
sourcePath: dirname(asset.sourcePath),
|
|
141
|
-
targetPath: `skills/${asset.displayName.replaceAll('/', '__')}`
|
|
142
|
-
})),
|
|
168
|
+
...selectedSkillOverlays,
|
|
143
169
|
...params.bundle.opencodeOverlayAssets.map((asset): AdapterOverlayEntry => ({
|
|
144
170
|
assetId: asset.id,
|
|
145
171
|
kind: asset.kind,
|
|
@@ -147,6 +173,15 @@ export function buildAdapterAssetPlan(params: {
|
|
|
147
173
|
targetPath: asset.payload.targetSubpath
|
|
148
174
|
}))
|
|
149
175
|
]
|
|
176
|
+
: params.adapter === 'copilot'
|
|
177
|
+
? selectedSkillOverlays
|
|
178
|
+
: params.adapter === 'kimi'
|
|
179
|
+
? selectedSkillAssets.map((asset): AdapterOverlayEntry => ({
|
|
180
|
+
assetId: asset.id,
|
|
181
|
+
kind: 'skill',
|
|
182
|
+
sourcePath: dirname(asset.sourcePath),
|
|
183
|
+
targetPath: asset.displayName.replaceAll('/', '__')
|
|
184
|
+
}))
|
|
150
185
|
: []
|
|
151
186
|
|
|
152
187
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WorkspaceAssetAdapter } from '@vibe-forge/types'
|
|
2
2
|
|
|
3
|
-
const NATIVE_SKILL_ADAPTERS = new Set<WorkspaceAssetAdapter>(['claude-code', 'opencode'])
|
|
3
|
+
const NATIVE_SKILL_ADAPTERS = new Set<WorkspaceAssetAdapter>(['claude-code', 'copilot', 'gemini', 'kimi', 'opencode'])
|
|
4
4
|
|
|
5
5
|
export const supportsNativeProjectSkills = (adapter?: string): adapter is WorkspaceAssetAdapter =>
|
|
6
6
|
adapter != null && NATIVE_SKILL_ADAPTERS.has(adapter as WorkspaceAssetAdapter)
|
|
@@ -8,5 +8,11 @@ export const supportsNativeProjectSkills = (adapter?: string): adapter is Worksp
|
|
|
8
8
|
export const resolveNativeSkillDiagnosticReason = (adapter: WorkspaceAssetAdapter) => (
|
|
9
9
|
adapter === 'claude-code'
|
|
10
10
|
? 'Synced into the Claude mock home as a native skill.'
|
|
11
|
+
: adapter === 'copilot'
|
|
12
|
+
? 'Staged for Copilot CLI native skill discovery.'
|
|
13
|
+
: adapter === 'gemini'
|
|
14
|
+
? 'Symlinked into GEMINI_CLI_HOME as a native Gemini skill.'
|
|
15
|
+
: adapter === 'kimi'
|
|
16
|
+
? 'Staged into a Kimi --skills-dir directory as a native skill.'
|
|
11
17
|
: 'Mirrored into OPENCODE_CONFIG_DIR as a native skill.'
|
|
12
18
|
)
|
package/src/bundle-internal.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveDefaultVibeForgeMcpServerConfig
|
|
10
10
|
} from '@vibe-forge/config'
|
|
11
11
|
import type { Config, Definition, Entity, PluginConfig, WorkspaceAsset, WorkspaceAssetKind } from '@vibe-forge/types'
|
|
12
|
-
import { resolveRelativePath } from '@vibe-forge/utils'
|
|
12
|
+
import { resolveProjectAiBaseDir, resolveProjectAiEntitiesDir, resolveRelativePath } from '@vibe-forge/utils'
|
|
13
13
|
import { listManagedPluginInstalls, toManagedPluginConfig } from '@vibe-forge/utils/managed-plugin'
|
|
14
14
|
import {
|
|
15
15
|
flattenPluginInstances,
|
|
@@ -217,13 +217,15 @@ const createOpenCodeOverlayAsset = <TKind extends OpenCodeOverlayKind>(params: {
|
|
|
217
217
|
} as OpenCodeOverlayAsset<TKind>)
|
|
218
218
|
|
|
219
219
|
const scanWorkspaceDocuments = async (cwd: string) => {
|
|
220
|
+
const aiBaseDir = resolveProjectAiBaseDir(cwd, process.env)
|
|
221
|
+
const entitiesDir = resolveProjectAiEntitiesDir(cwd, process.env)
|
|
220
222
|
const [rulePaths, skillPaths, specPaths, entityDocPaths, entityJsonPaths, mcpPaths] = await Promise.all([
|
|
221
|
-
glob(['
|
|
222
|
-
glob(['
|
|
223
|
-
glob(['
|
|
224
|
-
glob(['
|
|
225
|
-
glob(['
|
|
226
|
-
glob(['
|
|
223
|
+
glob(['rules/*.md'], { cwd: aiBaseDir, absolute: true }),
|
|
224
|
+
glob(['skills/*/SKILL.md'], { cwd: aiBaseDir, absolute: true }),
|
|
225
|
+
glob(['specs/*.md', 'specs/*/index.md'], { cwd: aiBaseDir, absolute: true }),
|
|
226
|
+
glob(['*.md', '*/README.md'], { cwd: entitiesDir, absolute: true }),
|
|
227
|
+
glob(['*/index.json'], { cwd: entitiesDir, absolute: true }),
|
|
228
|
+
glob(['mcp/*.json', 'mcp/*.yaml', 'mcp/*.yml'], { cwd: aiBaseDir, absolute: true })
|
|
227
229
|
])
|
|
228
230
|
|
|
229
231
|
return {
|
|
@@ -441,7 +443,7 @@ export async function collectWorkspaceAssets(params: {
|
|
|
441
443
|
name: DEFAULT_VIBE_FORGE_MCP_SERVER_NAME,
|
|
442
444
|
config: defaultVibeForgeMcpServer,
|
|
443
445
|
origin: 'workspace',
|
|
444
|
-
sourcePath:
|
|
446
|
+
sourcePath: resolveProjectAiBaseDir(params.cwd, process.env)
|
|
445
447
|
}))
|
|
446
448
|
}
|
|
447
449
|
}
|
package/src/internal-types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { WorkspaceAsset } from '@vibe-forge/types'
|
|
2
2
|
|
|
3
|
-
export const isOpenCodeOverlayAsset = (
|
|
3
|
+
export const isOpenCodeOverlayAsset = (
|
|
4
|
+
asset: WorkspaceAsset
|
|
5
|
+
): asset is Extract<WorkspaceAsset, { kind: 'nativePlugin' }> => asset.kind === 'nativePlugin'
|
|
@@ -139,7 +139,10 @@ export const resolveRuleSelection = async (
|
|
|
139
139
|
bundle: WorkspaceAssetBundle,
|
|
140
140
|
refs: RuleReference[] | string[] | undefined,
|
|
141
141
|
currentInstancePath?: string
|
|
142
|
-
)
|
|
142
|
+
): Promise<{
|
|
143
|
+
assets: Array<Extract<WorkspaceAsset, { kind: 'rule' }>>
|
|
144
|
+
remoteDefinitions: Definition<Rule>[]
|
|
145
|
+
}> => {
|
|
143
146
|
const assets: Array<Extract<WorkspaceAsset, { kind: 'rule' }>> = []
|
|
144
147
|
const remoteDefinitions: Definition<Rule>[] = []
|
|
145
148
|
const seen = new Set<string>()
|
|
@@ -199,7 +202,7 @@ export const resolveExcludedSkillRefs = (selection: string[] | SkillSelection |
|
|
|
199
202
|
export const resolveSelectedSkillAssets = (
|
|
200
203
|
assets: Array<Extract<WorkspaceAsset, { kind: 'skill' }>>,
|
|
201
204
|
selection?: WorkspaceSkillSelection
|
|
202
|
-
) => {
|
|
205
|
+
): Array<Extract<WorkspaceAsset, { kind: 'skill' }>> => {
|
|
203
206
|
if (selection == null) return assets
|
|
204
207
|
|
|
205
208
|
const included = selection.include != null && selection.include.length > 0
|