@vibe-forge/workspace-assets 0.10.0 → 0.10.1-alpha.1

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.
@@ -241,6 +241,90 @@ describe('resolvePromptAssetSelection', () => {
241
241
  expect(options.systemPrompt).not.toContain('先读 README.md')
242
242
  })
243
243
 
244
+ it('omits route-only project skills when the adapter provides native skill loading', async () => {
245
+ const workspace = await createWorkspace()
246
+
247
+ await writeDocument(
248
+ join(workspace, '.ai/skills/research/SKILL.md'),
249
+ [
250
+ '---',
251
+ 'description: 检索项目信息',
252
+ '---',
253
+ '先读 README.md'
254
+ ].join('\n')
255
+ )
256
+
257
+ const bundle = await resolveWorkspaceAssetBundle({
258
+ cwd: workspace,
259
+ useDefaultVibeForgeMcpServer: false
260
+ })
261
+ const [data, options] = await resolvePromptAssetSelection({
262
+ bundle,
263
+ adapter: 'claude-code',
264
+ type: undefined
265
+ })
266
+
267
+ expect(data.targetSkills).toEqual([])
268
+ expect(options.systemPrompt).not.toContain('<skills>')
269
+ expect(options.systemPrompt).not.toContain('# research')
270
+ expect(options.systemPrompt).not.toContain('Skill file path: .ai/skills/research/SKILL.md')
271
+ expect(options.promptAssetIds).not.toContain(bundle.skills[0]?.id)
272
+ })
273
+
274
+ it('keeps explicitly referenced skills embedded for adapters with native project skills', async () => {
275
+ const workspace = await createWorkspace()
276
+
277
+ await writeDocument(
278
+ join(workspace, '.ai/skills/research/SKILL.md'),
279
+ [
280
+ '---',
281
+ 'description: 检索项目信息',
282
+ '---',
283
+ '先读 README.md'
284
+ ].join('\n')
285
+ )
286
+ await writeDocument(
287
+ join(workspace, '.ai/skills/review/SKILL.md'),
288
+ [
289
+ '---',
290
+ 'description: 评审代码改动',
291
+ '---',
292
+ '检查回归风险'
293
+ ].join('\n')
294
+ )
295
+ await writeDocument(
296
+ join(workspace, '.ai/specs/release/index.md'),
297
+ [
298
+ '---',
299
+ 'description: 发布流程',
300
+ 'skills:',
301
+ ' - research',
302
+ '---',
303
+ '执行发布'
304
+ ].join('\n')
305
+ )
306
+
307
+ const bundle = await resolveWorkspaceAssetBundle({
308
+ cwd: workspace,
309
+ useDefaultVibeForgeMcpServer: false
310
+ })
311
+ const [data, options] = await resolvePromptAssetSelection({
312
+ bundle,
313
+ adapter: 'claude-code',
314
+ type: 'spec',
315
+ name: 'release'
316
+ })
317
+
318
+ expect(data.targetSkills.map(skill => skill.resolvedName ?? skill.attributes.name)).toEqual(['research'])
319
+ expect(options.systemPrompt).toContain('The following skill modules are loaded for the project')
320
+ expect(options.systemPrompt).toContain('# research')
321
+ expect(options.systemPrompt).toContain('<skill-content>')
322
+ expect(options.systemPrompt).toContain('先读 README.md')
323
+ expect(options.systemPrompt).not.toContain('<skills>')
324
+ expect(options.systemPrompt).not.toContain('# review')
325
+ expect(options.systemPrompt).not.toContain('Skill file path: .ai/skills/review/SKILL.md')
326
+ })
327
+
244
328
  it('keeps spec route guidance without default identity in normal mode', async () => {
245
329
  const workspace = await createWorkspace()
246
330
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-forge/workspace-assets",
3
- "version": "0.10.0",
3
+ "version": "0.10.1-alpha.1",
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/definition-core": "^0.10.0",
33
32
  "@vibe-forge/config": "^0.10.0",
34
- "@vibe-forge/types": "^0.10.0",
35
- "@vibe-forge/utils": "^0.10.0"
33
+ "@vibe-forge/types": "^0.10.1",
34
+ "@vibe-forge/utils": "^0.10.1",
35
+ "@vibe-forge/definition-core": "^0.10.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/js-yaml": "^4.0.9"
@@ -10,6 +10,7 @@ import type {
10
10
  WorkspaceSkillSelection
11
11
  } from '@vibe-forge/types'
12
12
 
13
+ import { resolveNativeSkillDiagnosticReason, supportsNativeProjectSkills } from './adapter-capabilities'
13
14
  import { resolveSelectedMcpNames, resolveSelectedSkillAssets } from './selection-internal'
14
15
 
15
16
  export function buildAdapterAssetPlan(params: {
@@ -83,13 +84,13 @@ export function buildAdapterAssetPlan(params: {
83
84
  })
84
85
 
85
86
  const selectedSkillAssets = resolveSelectedSkillAssets(params.bundle.skills, params.options.skills)
86
- if (params.adapter === 'opencode') {
87
+ if (supportsNativeProjectSkills(params.adapter)) {
87
88
  selectedSkillAssets.forEach((asset) => {
88
89
  diagnostics.push({
89
90
  assetId: asset.id,
90
91
  adapter: params.adapter,
91
92
  status: 'native',
92
- reason: 'Mirrored into OPENCODE_CONFIG_DIR as a native skill.',
93
+ reason: resolveNativeSkillDiagnosticReason(params.adapter),
93
94
  packageId: asset.packageId,
94
95
  scope: asset.scope,
95
96
  instancePath: asset.instancePath,
@@ -98,6 +99,8 @@ export function buildAdapterAssetPlan(params: {
98
99
  taskOverlaySource: asset.taskOverlaySource
99
100
  })
100
101
  })
102
+ }
103
+ if (params.adapter === 'opencode') {
101
104
  params.bundle.opencodeOverlayAssets.forEach((asset) => {
102
105
  diagnostics.push({
103
106
  assetId: asset.id,
@@ -0,0 +1,12 @@
1
+ import type { WorkspaceAssetAdapter } from '@vibe-forge/types'
2
+
3
+ const NATIVE_SKILL_ADAPTERS = new Set<WorkspaceAssetAdapter>(['claude-code', 'opencode'])
4
+
5
+ export const supportsNativeProjectSkills = (adapter?: string): adapter is WorkspaceAssetAdapter =>
6
+ adapter != null && NATIVE_SKILL_ADAPTERS.has(adapter as WorkspaceAssetAdapter)
7
+
8
+ export const resolveNativeSkillDiagnosticReason = (adapter: WorkspaceAssetAdapter) => (
9
+ adapter === 'claude-code'
10
+ ? 'Synced into the Claude mock home as a native skill.'
11
+ : 'Mirrored into OPENCODE_CONFIG_DIR as a native skill.'
12
+ )
@@ -11,6 +11,7 @@ import type {
11
11
  WorkspaceSkillSelection
12
12
  } from '@vibe-forge/types'
13
13
 
14
+ import { supportsNativeProjectSkills } from './adapter-capabilities'
14
15
  import { resolveWorkspaceAssetBundle } from './bundle'
15
16
  import {
16
17
  generateEntitiesRoutePrompt,
@@ -35,6 +36,7 @@ export async function resolvePromptAssetSelection(params: {
35
36
  bundle: WorkspaceAssetBundle
36
37
  type: 'spec' | 'entity' | undefined
37
38
  name?: string
39
+ adapter?: string
38
40
  input?: {
39
41
  skills?: WorkspaceSkillSelection
40
42
  }
@@ -83,10 +85,11 @@ export async function resolvePromptAssetSelection(params: {
83
85
  }
84
86
 
85
87
  const selectedSkillAssets = resolveSelectedSkillAssets(effectiveBundle.skills, params.input?.skills)
88
+ const useNativeProjectSkills = supportsNativeProjectSkills(params.adapter)
86
89
  const promptAssetIds = new Set<string>([
87
90
  ...effectiveBundle.rules.map(asset => asset.id),
88
91
  ...effectiveBundle.specs.map(asset => asset.id),
89
- ...selectedSkillAssets.map(asset => asset.id),
92
+ ...(useNativeProjectSkills ? [] : selectedSkillAssets.map(asset => asset.id)),
90
93
  ...(params.type !== 'entity' ? effectiveBundle.entities.map(asset => asset.id) : [])
91
94
  ])
92
95
  const ruleDefinitions = new Map<string, Definition<Rule>>(
@@ -164,10 +167,12 @@ export async function resolvePromptAssetSelection(params: {
164
167
  generateRulesPrompt(effectiveBundle.cwd, rules),
165
168
  generateSkillsPrompt(effectiveBundle.cwd, targetSkills),
166
169
  generateEntitiesRoutePrompt(entities),
167
- generateSkillsRoutePrompt(effectiveBundle.cwd, routedSkills),
170
+ useNativeProjectSkills ? '' : generateSkillsRoutePrompt(effectiveBundle.cwd, routedSkills),
168
171
  generateSpecRoutePrompt(specs, { active: params.type === 'spec' }),
169
172
  targetBody
170
- ].join('\n\n')
173
+ ]
174
+ .filter(section => section !== '')
175
+ .join('\n\n')
171
176
 
172
177
  if (targetToolsFilter != null) {
173
178
  options.tools = targetToolsFilter