@vibe-forge/workspace-assets 2.0.1 → 2.0.2
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 +1 -1
- package/__tests__/adapter-asset-plan.spec.ts +65 -67
- package/__tests__/bundle.spec.ts +138 -213
- package/__tests__/prompt-builders.spec.ts +39 -0
- package/package.json +4 -4
- package/src/bundle-internal.ts +3 -18
- package/src/bundle.ts +0 -2
- package/src/prompt-builders.ts +4 -0
- package/src/prompt-selection.ts +2 -2
- package/src/selection-internal.ts +2 -2
- package/src/skill-dependencies.ts +16 -16
- package/src/skill-registry.ts +329 -0
- package/src/task-tool-guidance.ts +15 -0
- package/src/workspace-prompt.ts +4 -0
- package/vibe-forge-workspace-assets-2.0.2.tgz +0 -0
- package/__tests__/skill-dependencies-cli.spec.ts +0 -175
- package/src/configured-skills.ts +0 -99
- package/src/skills-cli-dependency.ts +0 -208
|
@@ -467,7 +467,7 @@
|
|
|
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执行正式发布,并整理变更摘要。",
|
|
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.\nTask tool guide:\n- Use `VibeForge.StartTasks` to start a new child task when the work should run in a separate entity or workspace, or when it needs to continue independently from the current turn.\n- After starting a task, use `VibeForge.GetTaskInfo` with `{ taskId }` to inspect one task. It is also the right tool when a task seems stalled, failed, or might be waiting for input.\n- By default, `GetTaskInfo` returns the 10 most recent log entries in descending order, so newer entries appear earlier in the `logs` array. Pass `logLimit` to inspect a different number of recent logs, and set `logOrder` to `\"asc\"` when you want the selected log window in oldest-to-newest order.\n- Use `VibeForge.ListTasks` with the same `logLimit` and `logOrder` fields when you need to find a taskId or inspect multiple tasks at once.\n- Use `VibeForge.SendTaskMessage` with `{ taskId, message, mode }` when you want to continue the same task without starting a replacement task.\n- Choose `mode: \"direct\"` when the task is `running` and the new instruction should be delivered immediately into the current task.\n- Choose `mode: \"steer\"` when the task should finish its current run naturally first, and the follow-up should be queued for the same task afterward.\n- Use `VibeForge.SubmitTaskInput` only when `VibeForge.GetTaskInfo` or `VibeForge.ListTasks` shows `pendingInput` or `pendingInteraction`, or the task status is `waiting_input`. Do not use it for ordinary follow-up instructions or queued steer turns.\n- If a task is `completed` or `failed`, start a new task instead of trying to continue the old one.\n- When a task is still making progress, use `wait` between checks instead of repeatedly restarting it.\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
471
|
"tools": {
|
|
472
472
|
"include": [
|
|
473
473
|
"Edit",
|
|
@@ -1,24 +1,8 @@
|
|
|
1
|
-
/* eslint-disable
|
|
1
|
+
/* eslint-disable max-lines -- adapter asset plan scenarios share setup helpers and assertions */
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { describe, expect, it, vi } from 'vitest'
|
|
5
5
|
|
|
6
|
-
const skillsCliMocks = vi.hoisted(() => ({
|
|
7
|
-
findSkillsCli: vi.fn(),
|
|
8
|
-
installSkillsCliRefToTemp: vi.fn(),
|
|
9
|
-
installSkillsCliSkillToTemp: vi.fn()
|
|
10
|
-
}))
|
|
11
|
-
|
|
12
|
-
vi.mock('@vibe-forge/utils/skills-cli', async () => {
|
|
13
|
-
const actual = await vi.importActual<typeof import('@vibe-forge/utils/skills-cli')>('@vibe-forge/utils/skills-cli')
|
|
14
|
-
return {
|
|
15
|
-
...actual,
|
|
16
|
-
findSkillsCli: skillsCliMocks.findSkillsCli,
|
|
17
|
-
installSkillsCliRefToTemp: skillsCliMocks.installSkillsCliRefToTemp,
|
|
18
|
-
installSkillsCliSkillToTemp: skillsCliMocks.installSkillsCliSkillToTemp
|
|
19
|
-
}
|
|
20
|
-
})
|
|
21
|
-
|
|
22
6
|
import { buildAdapterAssetPlan, resolvePromptAssetSelection, resolveWorkspaceAssetBundle } from '#~/index.js'
|
|
23
7
|
|
|
24
8
|
import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
|
|
@@ -280,62 +264,76 @@ describe('buildAdapterAssetPlan', () => {
|
|
|
280
264
|
]))
|
|
281
265
|
})
|
|
282
266
|
|
|
283
|
-
it('keeps explicit
|
|
267
|
+
it('keeps explicit registry dependencies ahead of preselected home-bridged skills in overlays', async () => {
|
|
284
268
|
const workspace = await createWorkspace()
|
|
285
269
|
const realHome = process.env.__VF_PROJECT_REAL_HOME__
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
name: 'foo',
|
|
297
|
-
sourcePath: installedSkillDir
|
|
270
|
+
const fetchMock = vi.fn(async (url: string) => {
|
|
271
|
+
if (url === 'https://registry.example.test/api/search?q=foo&limit=10') {
|
|
272
|
+
return new Response(JSON.stringify({
|
|
273
|
+
skills: [{
|
|
274
|
+
id: 'anthropics/skills/foo',
|
|
275
|
+
skillId: 'foo',
|
|
276
|
+
name: 'foo',
|
|
277
|
+
source: 'anthropics/skills'
|
|
278
|
+
}]
|
|
279
|
+
}))
|
|
298
280
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
307
|
-
[
|
|
308
|
-
'---',
|
|
309
|
-
'name: app-builder',
|
|
310
|
-
'description: Build apps',
|
|
311
|
-
'dependencies:',
|
|
312
|
-
' - anthropics/skills@foo',
|
|
313
|
-
'---',
|
|
314
|
-
'Build the app.'
|
|
315
|
-
].join('\n')
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
319
|
-
cwd: workspace,
|
|
320
|
-
configs: [undefined, undefined],
|
|
321
|
-
useDefaultVibeForgeMcpServer: false
|
|
322
|
-
})
|
|
323
|
-
const plan = await buildAdapterAssetPlan({
|
|
324
|
-
adapter: 'opencode',
|
|
325
|
-
bundle,
|
|
326
|
-
options: {
|
|
327
|
-
skills: {
|
|
328
|
-
include: ['foo', 'app-builder']
|
|
329
|
-
}
|
|
281
|
+
if (url === 'https://registry.example.test/api/download/anthropics/skills/foo') {
|
|
282
|
+
return new Response(JSON.stringify({
|
|
283
|
+
files: [{
|
|
284
|
+
path: 'SKILL.md',
|
|
285
|
+
contents: '---\nname: foo\ndescription: Registry foo\n---\nUse the registry definition.\n'
|
|
286
|
+
}]
|
|
287
|
+
}))
|
|
330
288
|
}
|
|
289
|
+
return new Response('not found', { status: 404 })
|
|
331
290
|
})
|
|
291
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
await writeDocument(
|
|
295
|
+
join(realHome!, '.agents/skills/foo/SKILL.md'),
|
|
296
|
+
'---\ndescription: Home foo\n---\nUse the home definition.\n'
|
|
297
|
+
)
|
|
298
|
+
await writeDocument(
|
|
299
|
+
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
300
|
+
[
|
|
301
|
+
'---',
|
|
302
|
+
'name: app-builder',
|
|
303
|
+
'description: Build apps',
|
|
304
|
+
'dependencies:',
|
|
305
|
+
' - anthropics/skills@foo',
|
|
306
|
+
'---',
|
|
307
|
+
'Build the app.'
|
|
308
|
+
].join('\n')
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
312
|
+
cwd: workspace,
|
|
313
|
+
configs: [{
|
|
314
|
+
skills: {
|
|
315
|
+
registry: 'https://registry.example.test'
|
|
316
|
+
}
|
|
317
|
+
}, undefined],
|
|
318
|
+
useDefaultVibeForgeMcpServer: false
|
|
319
|
+
})
|
|
320
|
+
const plan = await buildAdapterAssetPlan({
|
|
321
|
+
adapter: 'opencode',
|
|
322
|
+
bundle,
|
|
323
|
+
options: {
|
|
324
|
+
skills: {
|
|
325
|
+
include: ['foo', 'app-builder']
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
})
|
|
332
329
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
'
|
|
337
|
-
|
|
338
|
-
|
|
330
|
+
const fooOverlays = plan.overlays.filter(entry => entry.kind === 'skill' && entry.targetPath === 'skills/foo')
|
|
331
|
+
expect(fooOverlays).toHaveLength(1)
|
|
332
|
+
expect(fooOverlays[0]?.sourcePath).toContain('/.ai/caches/skill-dependencies/registry.example.test/')
|
|
333
|
+
expect(fooOverlays[0]?.sourcePath).not.toBe(join(realHome!, '.agents/skills/foo'))
|
|
334
|
+
} finally {
|
|
335
|
+
vi.unstubAllGlobals()
|
|
336
|
+
}
|
|
339
337
|
})
|
|
340
338
|
|
|
341
339
|
it('prunes excluded skill dependency subtrees from selected native overlays', async () => {
|
package/__tests__/bundle.spec.ts
CHANGED
|
@@ -1,32 +1,15 @@
|
|
|
1
|
-
/* eslint-disable
|
|
1
|
+
/* eslint-disable max-lines -- bundle coverage keeps related fixture scenarios in one file */
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
|
|
5
5
|
import { readFile } from 'node:fs/promises'
|
|
6
6
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
7
7
|
|
|
8
|
-
const skillsCliMocks = vi.hoisted(() => ({
|
|
9
|
-
findSkillsCli: vi.fn(),
|
|
10
|
-
installSkillsCliRefToTemp: vi.fn(),
|
|
11
|
-
installSkillsCliSkillToTemp: vi.fn()
|
|
12
|
-
}))
|
|
13
|
-
|
|
14
|
-
vi.mock('@vibe-forge/utils/skills-cli', async () => {
|
|
15
|
-
const actual = await vi.importActual<typeof import('@vibe-forge/utils/skills-cli')>('@vibe-forge/utils/skills-cli')
|
|
16
|
-
return {
|
|
17
|
-
...actual,
|
|
18
|
-
findSkillsCli: skillsCliMocks.findSkillsCli,
|
|
19
|
-
installSkillsCliRefToTemp: skillsCliMocks.installSkillsCliRefToTemp,
|
|
20
|
-
installSkillsCliSkillToTemp: skillsCliMocks.installSkillsCliSkillToTemp
|
|
21
|
-
}
|
|
22
|
-
})
|
|
23
|
-
|
|
24
8
|
import { buildAdapterAssetPlan, resolveWorkspaceAssetBundle } from '#~/index.js'
|
|
25
9
|
|
|
26
10
|
import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
|
|
27
11
|
|
|
28
12
|
afterEach(() => {
|
|
29
|
-
vi.clearAllMocks()
|
|
30
13
|
vi.unstubAllGlobals()
|
|
31
14
|
})
|
|
32
15
|
|
|
@@ -386,23 +369,31 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
386
369
|
}))
|
|
387
370
|
})
|
|
388
371
|
|
|
389
|
-
it('installs selected missing skill dependencies from
|
|
372
|
+
it('installs selected missing skill dependencies from an API-compatible registry cache', async () => {
|
|
390
373
|
const workspace = await createWorkspace()
|
|
391
374
|
const realHome = process.env.__VF_PROJECT_REAL_HOME__
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
375
|
+
const fetchMock = vi.fn(async (url: string) => {
|
|
376
|
+
if (url === 'https://registry.example.test/api/search?q=frontend-design&limit=10') {
|
|
377
|
+
return new Response(JSON.stringify({
|
|
378
|
+
skills: [{
|
|
379
|
+
id: 'anthropics/skills/frontend-design',
|
|
380
|
+
skillId: 'frontend-design',
|
|
381
|
+
name: 'frontend-design',
|
|
382
|
+
source: 'anthropics/skills'
|
|
383
|
+
}]
|
|
384
|
+
}))
|
|
385
|
+
}
|
|
386
|
+
if (url === 'https://registry.example.test/api/download/anthropics/skills/frontend-design') {
|
|
387
|
+
return new Response(JSON.stringify({
|
|
388
|
+
files: [{
|
|
389
|
+
path: 'SKILL.md',
|
|
390
|
+
contents: '---\nname: frontend-design\ndescription: UI design guidance\n---\nUse strong visual hierarchy.\n'
|
|
391
|
+
}]
|
|
392
|
+
}))
|
|
404
393
|
}
|
|
394
|
+
return new Response('not found', { status: 404 })
|
|
405
395
|
})
|
|
396
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
406
397
|
|
|
407
398
|
await writeDocument(
|
|
408
399
|
join(realHome!, '.agents/skills/frontend-design/SKILL.md'),
|
|
@@ -423,7 +414,11 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
423
414
|
|
|
424
415
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
425
416
|
cwd: workspace,
|
|
426
|
-
configs: [
|
|
417
|
+
configs: [{
|
|
418
|
+
skills: {
|
|
419
|
+
registry: 'https://registry.example.test'
|
|
420
|
+
}
|
|
421
|
+
}, undefined],
|
|
427
422
|
useDefaultVibeForgeMcpServer: false
|
|
428
423
|
})
|
|
429
424
|
|
|
@@ -432,6 +427,7 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
432
427
|
resolvedBy: 'home-bridge',
|
|
433
428
|
sourcePath: join(realHome!, '.agents/skills/frontend-design/SKILL.md')
|
|
434
429
|
}))
|
|
430
|
+
expect(fetchMock).not.toHaveBeenCalled()
|
|
435
431
|
|
|
436
432
|
await buildAdapterAssetPlan({
|
|
437
433
|
adapter: 'opencode',
|
|
@@ -445,122 +441,13 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
445
441
|
|
|
446
442
|
const dependency = bundle.skills.find(asset => asset.name === 'frontend-design')
|
|
447
443
|
expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['app-builder', 'frontend-design'])
|
|
448
|
-
expect(dependency?.sourcePath).toContain(
|
|
449
|
-
'/.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/frontend-design/'
|
|
450
|
-
)
|
|
444
|
+
expect(dependency?.sourcePath).toContain('/.ai/caches/skill-dependencies/registry.example.test/')
|
|
451
445
|
expect(bundle.skills.find(asset => (
|
|
452
446
|
asset.name === 'frontend-design' && asset.resolvedBy === 'home-bridge'
|
|
453
447
|
))).toBeUndefined()
|
|
454
|
-
expect(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
skill: 'frontend-design',
|
|
458
|
-
source: 'anthropics/skills'
|
|
459
|
-
})
|
|
460
|
-
})
|
|
461
|
-
|
|
462
|
-
it('installs configured project skills before bundle resolution and rewrites renamed skill names', async () => {
|
|
463
|
-
const workspace = await createWorkspace()
|
|
464
|
-
const tempInstallDir = join(workspace, '.tmp-configured-install')
|
|
465
|
-
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'design-review')
|
|
466
|
-
await writeDocument(
|
|
467
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
468
|
-
'---\nname: design-review\ndescription: Review design work\n---\nReview the UI implementation.\n'
|
|
469
|
-
)
|
|
470
|
-
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
471
|
-
tempDir: tempInstallDir,
|
|
472
|
-
installedSkill: {
|
|
473
|
-
dirName: 'design-review',
|
|
474
|
-
name: 'design-review',
|
|
475
|
-
sourcePath: installedSkillDir
|
|
476
|
-
}
|
|
477
|
-
})
|
|
478
|
-
|
|
479
|
-
const bundle = await resolveWorkspaceAssetBundle({
|
|
480
|
-
cwd: workspace,
|
|
481
|
-
configs: [{
|
|
482
|
-
skills: [
|
|
483
|
-
{
|
|
484
|
-
name: 'design-review',
|
|
485
|
-
source: 'example-source/default/public',
|
|
486
|
-
rename: 'internal-review'
|
|
487
|
-
}
|
|
488
|
-
]
|
|
489
|
-
}, undefined],
|
|
490
|
-
syncConfiguredSkills: true,
|
|
491
|
-
useDefaultVibeForgeMcpServer: false
|
|
492
|
-
})
|
|
493
|
-
|
|
494
|
-
expect(bundle.skills.map(asset => asset.name)).toContain('internal-review')
|
|
495
|
-
expect(skillsCliMocks.installSkillsCliSkillToTemp).toHaveBeenCalledWith({
|
|
496
|
-
config: undefined,
|
|
497
|
-
skill: 'design-review',
|
|
498
|
-
source: 'example-source/default/public'
|
|
499
|
-
})
|
|
500
|
-
await expect(readFile(join(workspace, '.ai/skills/internal-review/SKILL.md'), 'utf8')).resolves.toContain(
|
|
501
|
-
'name: internal-review'
|
|
502
|
-
)
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
it('skips configured skill reinstalls unless updateConfiguredSkills is enabled', async () => {
|
|
506
|
-
const workspace = await createWorkspace()
|
|
507
|
-
await writeDocument(
|
|
508
|
-
join(workspace, '.ai/skills/internal-review/SKILL.md'),
|
|
509
|
-
'---\nname: internal-review\ndescription: Existing skill\n---\nExisting content.\n'
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
const skippedBundle = await resolveWorkspaceAssetBundle({
|
|
513
|
-
cwd: workspace,
|
|
514
|
-
configs: [{
|
|
515
|
-
skills: [
|
|
516
|
-
{
|
|
517
|
-
name: 'design-review',
|
|
518
|
-
source: 'example-source/default/public',
|
|
519
|
-
rename: 'internal-review'
|
|
520
|
-
}
|
|
521
|
-
]
|
|
522
|
-
}, undefined],
|
|
523
|
-
syncConfiguredSkills: true,
|
|
524
|
-
useDefaultVibeForgeMcpServer: false
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
expect(skippedBundle.skills.map(asset => asset.name)).toContain('internal-review')
|
|
528
|
-
expect(skillsCliMocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
529
|
-
|
|
530
|
-
const tempInstallDir = join(workspace, '.tmp-configured-update')
|
|
531
|
-
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'design-review')
|
|
532
|
-
await writeDocument(
|
|
533
|
-
join(installedSkillDir, 'SKILL.md'),
|
|
534
|
-
'---\nname: design-review\ndescription: Updated skill\n---\nUpdated content.\n'
|
|
535
|
-
)
|
|
536
|
-
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValueOnce({
|
|
537
|
-
tempDir: tempInstallDir,
|
|
538
|
-
installedSkill: {
|
|
539
|
-
dirName: 'design-review',
|
|
540
|
-
name: 'design-review',
|
|
541
|
-
sourcePath: installedSkillDir
|
|
542
|
-
}
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
await resolveWorkspaceAssetBundle({
|
|
546
|
-
cwd: workspace,
|
|
547
|
-
configs: [{
|
|
548
|
-
skills: [
|
|
549
|
-
{
|
|
550
|
-
name: 'design-review',
|
|
551
|
-
source: 'example-source/default/public',
|
|
552
|
-
rename: 'internal-review'
|
|
553
|
-
}
|
|
554
|
-
]
|
|
555
|
-
}, undefined],
|
|
556
|
-
syncConfiguredSkills: true,
|
|
557
|
-
updateConfiguredSkills: true,
|
|
558
|
-
useDefaultVibeForgeMcpServer: false
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
expect(skillsCliMocks.installSkillsCliSkillToTemp).toHaveBeenCalledTimes(1)
|
|
562
|
-
await expect(readFile(join(workspace, '.ai/skills/internal-review/SKILL.md'), 'utf8')).resolves.toContain(
|
|
563
|
-
'Updated content.'
|
|
448
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
449
|
+
'https://registry.example.test/api/download/anthropics/skills/frontend-design',
|
|
450
|
+
expect.any(Object)
|
|
564
451
|
)
|
|
565
452
|
})
|
|
566
453
|
|
|
@@ -568,20 +455,28 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
568
455
|
const primary = await createWorkspace()
|
|
569
456
|
const worktree = await createWorkspace()
|
|
570
457
|
const previousPrimaryWorkspace = process.env.__VF_PROJECT_PRIMARY_WORKSPACE_FOLDER__
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
name: 'frontend-design',
|
|
582
|
-
sourcePath: installedSkillDir
|
|
458
|
+
const fetchMock = vi.fn(async (url: string) => {
|
|
459
|
+
if (url === 'https://registry.example.test/api/search?q=frontend-design&limit=10') {
|
|
460
|
+
return new Response(JSON.stringify({
|
|
461
|
+
skills: [{
|
|
462
|
+
id: 'anthropics/skills/frontend-design',
|
|
463
|
+
skillId: 'frontend-design',
|
|
464
|
+
name: 'frontend-design',
|
|
465
|
+
source: 'anthropics/skills'
|
|
466
|
+
}]
|
|
467
|
+
}))
|
|
583
468
|
}
|
|
469
|
+
if (url === 'https://registry.example.test/api/download/anthropics/skills/frontend-design') {
|
|
470
|
+
return new Response(JSON.stringify({
|
|
471
|
+
files: [{
|
|
472
|
+
path: 'SKILL.md',
|
|
473
|
+
contents: '---\nname: frontend-design\ndescription: UI design guidance\n---\nUse primary cache.\n'
|
|
474
|
+
}]
|
|
475
|
+
}))
|
|
476
|
+
}
|
|
477
|
+
return new Response('not found', { status: 404 })
|
|
584
478
|
})
|
|
479
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
585
480
|
|
|
586
481
|
try {
|
|
587
482
|
process.env.__VF_PROJECT_PRIMARY_WORKSPACE_FOLDER__ = primary
|
|
@@ -592,7 +487,7 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
592
487
|
'name: app-builder',
|
|
593
488
|
'description: Build apps',
|
|
594
489
|
'dependencies:',
|
|
595
|
-
' -
|
|
490
|
+
' - frontend-design',
|
|
596
491
|
'---',
|
|
597
492
|
'Build the app.'
|
|
598
493
|
].join('\n')
|
|
@@ -600,7 +495,11 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
600
495
|
|
|
601
496
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
602
497
|
cwd: worktree,
|
|
603
|
-
configs: [
|
|
498
|
+
configs: [{
|
|
499
|
+
skills: {
|
|
500
|
+
registry: 'https://registry.example.test'
|
|
501
|
+
}
|
|
502
|
+
}, undefined],
|
|
604
503
|
useDefaultVibeForgeMcpServer: false
|
|
605
504
|
})
|
|
606
505
|
|
|
@@ -617,7 +516,7 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
617
516
|
const dependency = bundle.skills.find(asset => asset.name === 'frontend-design')
|
|
618
517
|
expect(dependency?.sourcePath).toContain(join(
|
|
619
518
|
primary,
|
|
620
|
-
'.ai/caches/skill-dependencies/
|
|
519
|
+
'.ai/caches/skill-dependencies/registry.example.test/'
|
|
621
520
|
))
|
|
622
521
|
expect(dependency?.sourcePath).not.toContain(join(worktree, '.ai/caches'))
|
|
623
522
|
} finally {
|
|
@@ -631,10 +530,12 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
631
530
|
|
|
632
531
|
it('reuses complete skill dependency caches without deleting or downloading them again', async () => {
|
|
633
532
|
const workspace = await createWorkspace()
|
|
533
|
+
const fetchMock = vi.fn(async () => new Response('not found', { status: 404 }))
|
|
534
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
634
535
|
|
|
635
536
|
const cachedSkillPath = join(
|
|
636
537
|
workspace,
|
|
637
|
-
'.ai/caches/skill-dependencies/
|
|
538
|
+
'.ai/caches/skill-dependencies/registry.example.test/anthropics/skills/frontend-design/SKILL.md'
|
|
638
539
|
)
|
|
639
540
|
await writeDocument(
|
|
640
541
|
cachedSkillPath,
|
|
@@ -655,7 +556,11 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
655
556
|
|
|
656
557
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
657
558
|
cwd: workspace,
|
|
658
|
-
configs: [
|
|
559
|
+
configs: [{
|
|
560
|
+
skills: {
|
|
561
|
+
registry: 'https://registry.example.test'
|
|
562
|
+
}
|
|
563
|
+
}, undefined],
|
|
659
564
|
useDefaultVibeForgeMcpServer: false
|
|
660
565
|
})
|
|
661
566
|
|
|
@@ -669,69 +574,89 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
669
574
|
}
|
|
670
575
|
})
|
|
671
576
|
|
|
672
|
-
expect(
|
|
673
|
-
expect(skillsCliMocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
577
|
+
expect(fetchMock).not.toHaveBeenCalled()
|
|
674
578
|
expect(await readFile(cachedSkillPath, 'utf8')).toContain('Use the cached copy.')
|
|
675
579
|
expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['app-builder', 'frontend-design'])
|
|
676
580
|
})
|
|
677
581
|
|
|
678
|
-
it('
|
|
582
|
+
it('keeps configured registry url search and download endpoints together when env overrides exist', async () => {
|
|
679
583
|
const workspace = await createWorkspace()
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
sourcePath: installedSkillDir
|
|
584
|
+
const previousDownloadUrl = process.env.SKILLS_DOWNLOAD_URL
|
|
585
|
+
const fetchMock = vi.fn(async (url: string) => {
|
|
586
|
+
if (url === 'https://private-registry.example.test/api/search?q=frontend-design&limit=10') {
|
|
587
|
+
return new Response(JSON.stringify({
|
|
588
|
+
skills: [{
|
|
589
|
+
id: 'anthropics/skills/frontend-design',
|
|
590
|
+
skillId: 'frontend-design',
|
|
591
|
+
name: 'frontend-design',
|
|
592
|
+
source: 'anthropics/skills'
|
|
593
|
+
}]
|
|
594
|
+
}))
|
|
692
595
|
}
|
|
596
|
+
if (url === 'https://private-registry.example.test/api/download/anthropics/skills/frontend-design') {
|
|
597
|
+
return new Response(JSON.stringify({
|
|
598
|
+
files: [{
|
|
599
|
+
path: 'SKILL.md',
|
|
600
|
+
contents: '---\nname: frontend-design\ndescription: UI design guidance\n---\nUse strong visual hierarchy.\n'
|
|
601
|
+
}]
|
|
602
|
+
}))
|
|
603
|
+
}
|
|
604
|
+
return new Response('not found', { status: 404 })
|
|
693
605
|
})
|
|
606
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
694
607
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
'
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
608
|
+
try {
|
|
609
|
+
process.env.SKILLS_DOWNLOAD_URL = 'https://env-download.example.test'
|
|
610
|
+
await writeDocument(
|
|
611
|
+
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
612
|
+
[
|
|
613
|
+
'---',
|
|
614
|
+
'name: app-builder',
|
|
615
|
+
'description: Build apps',
|
|
616
|
+
'dependencies:',
|
|
617
|
+
' - frontend-design',
|
|
618
|
+
'---',
|
|
619
|
+
'Build the app.'
|
|
620
|
+
].join('\n')
|
|
621
|
+
)
|
|
707
622
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
623
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
624
|
+
cwd: workspace,
|
|
625
|
+
configs: [{
|
|
626
|
+
skills: {
|
|
627
|
+
registry: {
|
|
628
|
+
url: 'https://private-registry.example.test'
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}, undefined],
|
|
632
|
+
useDefaultVibeForgeMcpServer: false
|
|
633
|
+
})
|
|
717
634
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
635
|
+
await buildAdapterAssetPlan({
|
|
636
|
+
adapter: 'opencode',
|
|
637
|
+
bundle,
|
|
638
|
+
options: {
|
|
639
|
+
skills: {
|
|
640
|
+
include: ['app-builder']
|
|
641
|
+
}
|
|
724
642
|
}
|
|
725
|
-
}
|
|
726
|
-
})
|
|
643
|
+
})
|
|
727
644
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
645
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
646
|
+
'https://private-registry.example.test/api/download/anthropics/skills/frontend-design',
|
|
647
|
+
expect.any(Object)
|
|
648
|
+
)
|
|
649
|
+
expect(fetchMock).not.toHaveBeenCalledWith(
|
|
650
|
+
'https://env-download.example.test/api/download/anthropics/skills/frontend-design',
|
|
651
|
+
expect.any(Object)
|
|
652
|
+
)
|
|
653
|
+
} finally {
|
|
654
|
+
if (previousDownloadUrl == null) {
|
|
655
|
+
delete process.env.SKILLS_DOWNLOAD_URL
|
|
656
|
+
} else {
|
|
657
|
+
process.env.SKILLS_DOWNLOAD_URL = previousDownloadUrl
|
|
658
|
+
}
|
|
659
|
+
}
|
|
735
660
|
})
|
|
736
661
|
|
|
737
662
|
it('loads workspace entities from the env-configured entities dir', async () => {
|