@vibe-forge/workspace-assets 0.11.2 → 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.
@@ -467,7 +467,6 @@
467
467
  ]
468
468
  },
469
469
  "options": {
470
- "systemPrompt": "<system-prompt>\nThe project system rules are:\n# review\n\n> 必须检查发布改动的回归风险。\n\n# demo/security\n\n> Use when: 插件安全规则\n> Rule file path: node_modules/@vibe-forge/plugin-demo/rules/security.md\n> Only read this rule file when the task matches the scenario above.\n</system-prompt>\n\n\n<system-prompt>\nThe following skill modules are loaded for the project:\n# research\n\n> Skill description: 检索资料\n> Skill file path: .ai/skills/research/SKILL.md\n> Resolve relative paths in the resource content relative to the directory containing this skill file.\n\n<skill-content>\n先阅读 README.md,再补充结论。\n</skill-content>\n</system-prompt>\n\n\n<system-prompt>\nThe project includes the following entities:\n - architect: 负责拆解方案的实体\n\nWhen solving user problems, you may specify entities through `VibeForge.StartTasks` as needed and have them coordinate multiple entity types to complete the work; use `VibeForge.GetTaskInfo` and `wait` to track progress.\n</system-prompt>\n\n\n<system-prompt>\nYou are a professional project execution manager who can skillfully direct other entities to work toward your goal. Expectations:\n\n- Never complete code development work alone\n- You must coordinate other developers to complete tasks\n- You must keep them aligned with the goal and verify that their completion reports meet the requirements\n\nChoose the appropriate workflow based on the user's needs and the actual development goal, and use the workflow identifier to locate and load the corresponding definition.\n- Pass the identifier based on the actual need. This is not a path; use the standard workflow loading capability to resolve it.\n- Decide how to pass parameters based on their descriptions and actual usage scenarios.\nThe project includes the following workflows:\n- Workflow name: release\n - Description: 正式发布流程\n - Identifier: release\n - Parameters:\n - None\n\n- Workflow name: demo/release\n - Description: 插件发布流程\n - Identifier: demo/release\n - Parameters:\n - None\n\n</system-prompt>\n\n\n执行正式发布,并整理变更摘要。",
471
470
  "systemPrompt": "<system-prompt>\nThe project system rules are:\n# review\n\n> 必须检查发布改动的回归风险。\n\n# demo/security\n\n> Use when: 插件安全规则\n> Rule file path: node_modules/@vibe-forge/plugin-demo/rules/security.md\n> Only read this rule file when the task matches the scenario above.\n</system-prompt>\n\n\n<system-prompt>\nThe following skill modules are loaded for the project:\n# research\n\n> Skill description: 检索资料\n> Skill file path: .ai/skills/research/SKILL.md\n> Resolve relative paths in the resource content relative to the directory containing this skill file.\n\n<skill-content>\n先阅读 README.md,再补充结论。\n</skill-content>\n</system-prompt>\n\n\n<system-prompt>\nThe project includes the following entities:\n - architect: 负责拆解方案的实体\n\nWhen solving user problems, you may specify entities through `VibeForge.StartTasks` as needed and have them coordinate multiple entity types to complete the work; use `VibeForge.GetTaskInfo` and `wait` to track progress.\n</system-prompt>\n\n\n<system-prompt>\nYou are a professional project execution manager who can skillfully direct other entities to work toward your goal. Expectations:\n\n- Never complete code development work alone\n- You must coordinate other developers to complete tasks\n- You must keep them aligned with the goal and verify that their completion reports meet the requirements\n\nChoose the appropriate workflow based on the user's needs and the actual development goal, and use the workflow identifier to locate and load the corresponding definition.\n- Pass the identifier based on the actual need. This is not a path; use the standard workflow loading capability to resolve it.\n- Decide how to pass parameters based on their descriptions and actual usage scenarios.\nThe project includes the following workflows:\n- Workflow name: release\n - Description: 正式发布流程\n - Identifier: release\n - Parameters:\n - None\n\n- Workflow name: demo/release\n - Description: 插件发布流程\n - Identifier: demo/release\n - Parameters:\n - None\n\n</system-prompt>\n\n\n执行正式发布,并整理变更摘要。",
472
471
  "tools": {
473
472
  "include": [
@@ -697,6 +696,126 @@
697
696
  }
698
697
  ]
699
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
+ },
700
819
  "opencode": {
701
820
  "adapter": "opencode",
702
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
+ })
@@ -75,7 +75,10 @@ describe('resolveWorkspaceAssetBundle', () => {
75
75
  try {
76
76
  process.env.__VF_PROJECT_AI_BASE_DIR__ = '.vf'
77
77
  await writeDocument(join(workspace, '.vf/rules/review.md'), '---\ndescription: 评审规则\n---\n必须检查风险')
78
- await writeDocument(join(workspace, '.vf/skills/research/SKILL.md'), '---\ndescription: 检索资料\n---\n阅读 README.md')
78
+ await writeDocument(
79
+ join(workspace, '.vf/skills/research/SKILL.md'),
80
+ '---\ndescription: 检索资料\n---\n阅读 README.md'
81
+ )
79
82
 
80
83
  const bundle = await resolveWorkspaceAssetBundle({
81
84
  cwd: workspace,
@@ -100,7 +103,10 @@ describe('resolveWorkspaceAssetBundle', () => {
100
103
 
101
104
  try {
102
105
  process.env.__VF_PROJECT_AI_ENTITIES_DIR__ = 'agents'
103
- await writeDocument(join(workspace, '.ai/agents/reviewer/README.md'), '---\ndescription: 负责代码评审\n---\n检查风险')
106
+ await writeDocument(
107
+ join(workspace, '.ai/agents/reviewer/README.md'),
108
+ '---\ndescription: 负责代码评审\n---\n检查风险'
109
+ )
104
110
 
105
111
  const bundle = await resolveWorkspaceAssetBundle({
106
112
  cwd: workspace,
@@ -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.11.2",
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.11.1",
33
- "@vibe-forge/definition-core": "^0.11.0",
34
- "@vibe-forge/types": "^0.11.2",
35
- "@vibe-forge/utils": "^0.11.2"
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: 'No stable native Codex mapping exists for this asset kind in V1.',
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
- ...selectedSkillAssets.map((asset): AdapterOverlayEntry => ({
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
  )
@@ -1,3 +1,5 @@
1
1
  import type { WorkspaceAsset } from '@vibe-forge/types'
2
2
 
3
- export const isOpenCodeOverlayAsset = (asset: WorkspaceAsset) => asset.kind === 'nativePlugin'
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