@vibe-forge/workspace-assets 2.0.3 → 2.0.4-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.
- package/__tests__/__snapshots__/workspace-assets-rich.snapshot.json +1 -1
- package/__tests__/adapter-asset-plan.spec.ts +67 -65
- package/__tests__/bundle.spec.ts +208 -138
- package/__tests__/prompt-builders.spec.ts +2 -6
- package/__tests__/skill-dependencies-cli.spec.ts +234 -0
- package/package.json +2 -2
- package/src/bundle-internal.ts +18 -3
- package/src/bundle.ts +2 -0
- package/src/configured-skills.ts +85 -0
- package/src/prompt-selection.ts +2 -2
- package/src/selection-internal.ts +2 -2
- package/src/skill-dependencies.ts +17 -96
- package/src/skills-cli-dependency-helpers.ts +94 -0
- package/src/skills-cli-dependency.ts +104 -0
- package/src/task-tool-guidance.ts +2 -4
- package/__tests__/skill-dependencies.spec.ts +0 -80
- package/src/skill-registry.ts +0 -345
- package/vibe-forge-workspace-assets-2.0.2.tgz +0 -0
|
@@ -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.\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
|
|
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 }` only when a task status is `running` and you need to give it another instruction without starting a replacement task.\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.\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,8 +1,24 @@
|
|
|
1
|
-
/* eslint-disable max-lines -- adapter asset plan scenarios share setup helpers and assertions */
|
|
1
|
+
/* eslint-disable import/first, 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
|
+
|
|
6
22
|
import { buildAdapterAssetPlan, resolvePromptAssetSelection, resolveWorkspaceAssetBundle } from '#~/index.js'
|
|
7
23
|
|
|
8
24
|
import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
|
|
@@ -264,76 +280,62 @@ describe('buildAdapterAssetPlan', () => {
|
|
|
264
280
|
]))
|
|
265
281
|
})
|
|
266
282
|
|
|
267
|
-
it('keeps explicit
|
|
283
|
+
it('keeps explicit skills CLI dependencies ahead of preselected home-bridged skills in overlays', async () => {
|
|
268
284
|
const workspace = await createWorkspace()
|
|
269
285
|
const realHome = process.env.__VF_PROJECT_REAL_HOME__
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
}))
|
|
286
|
+
const tempInstallDir = join(workspace, '.tmp-install-skills-cli-foo')
|
|
287
|
+
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'foo')
|
|
288
|
+
await writeDocument(
|
|
289
|
+
join(installedSkillDir, 'SKILL.md'),
|
|
290
|
+
'---\nname: foo\ndescription: Registry foo\n---\nUse the registry definition.\n'
|
|
291
|
+
)
|
|
292
|
+
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
293
|
+
tempDir: tempInstallDir,
|
|
294
|
+
installedSkill: {
|
|
295
|
+
dirName: 'foo',
|
|
296
|
+
name: 'foo',
|
|
297
|
+
sourcePath: installedSkillDir
|
|
288
298
|
}
|
|
289
|
-
return new Response('not found', { status: 404 })
|
|
290
299
|
})
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const plan = await buildAdapterAssetPlan({
|
|
321
|
-
adapter: 'opencode',
|
|
322
|
-
bundle,
|
|
323
|
-
options: {
|
|
324
|
-
skills: {
|
|
325
|
-
include: ['foo', 'app-builder']
|
|
326
|
-
}
|
|
300
|
+
|
|
301
|
+
await writeDocument(
|
|
302
|
+
join(realHome!, '.agents/skills/foo/SKILL.md'),
|
|
303
|
+
'---\ndescription: Home foo\n---\nUse the home definition.\n'
|
|
304
|
+
)
|
|
305
|
+
await writeDocument(
|
|
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']
|
|
327
329
|
}
|
|
328
|
-
}
|
|
330
|
+
}
|
|
331
|
+
})
|
|
329
332
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
333
|
+
const fooOverlays = plan.overlays.filter(entry => entry.kind === 'skill' && entry.targetPath === 'skills/foo')
|
|
334
|
+
expect(fooOverlays).toHaveLength(1)
|
|
335
|
+
expect(fooOverlays[0]?.sourcePath).toContain(
|
|
336
|
+
'/.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/foo'
|
|
337
|
+
)
|
|
338
|
+
expect(fooOverlays[0]?.sourcePath).not.toBe(join(realHome!, '.agents/skills/foo'))
|
|
337
339
|
})
|
|
338
340
|
|
|
339
341
|
it('prunes excluded skill dependency subtrees from selected native overlays', async () => {
|
package/__tests__/bundle.spec.ts
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
|
-
/* eslint-disable max-lines -- bundle coverage keeps related fixture scenarios in one file */
|
|
1
|
+
/* eslint-disable import/first, 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
|
+
|
|
8
24
|
import { buildAdapterAssetPlan, resolveWorkspaceAssetBundle } from '#~/index.js'
|
|
9
25
|
|
|
10
26
|
import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
|
|
11
27
|
|
|
12
28
|
afterEach(() => {
|
|
29
|
+
vi.clearAllMocks()
|
|
13
30
|
vi.unstubAllGlobals()
|
|
14
31
|
})
|
|
15
32
|
|
|
@@ -369,31 +386,23 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
369
386
|
}))
|
|
370
387
|
})
|
|
371
388
|
|
|
372
|
-
it('installs selected missing skill dependencies from
|
|
389
|
+
it('installs selected missing skill dependencies from the skills CLI cache', async () => {
|
|
373
390
|
const workspace = await createWorkspace()
|
|
374
391
|
const realHome = process.env.__VF_PROJECT_REAL_HOME__
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
}))
|
|
392
|
+
const tempInstallDir = join(workspace, '.tmp-install-skills-cli')
|
|
393
|
+
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'frontend-design')
|
|
394
|
+
await writeDocument(
|
|
395
|
+
join(installedSkillDir, 'SKILL.md'),
|
|
396
|
+
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse strong visual hierarchy.\n'
|
|
397
|
+
)
|
|
398
|
+
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
399
|
+
tempDir: tempInstallDir,
|
|
400
|
+
installedSkill: {
|
|
401
|
+
dirName: 'frontend-design',
|
|
402
|
+
name: 'frontend-design',
|
|
403
|
+
sourcePath: installedSkillDir
|
|
393
404
|
}
|
|
394
|
-
return new Response('not found', { status: 404 })
|
|
395
405
|
})
|
|
396
|
-
vi.stubGlobal('fetch', fetchMock)
|
|
397
406
|
|
|
398
407
|
await writeDocument(
|
|
399
408
|
join(realHome!, '.agents/skills/frontend-design/SKILL.md'),
|
|
@@ -414,11 +423,7 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
414
423
|
|
|
415
424
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
416
425
|
cwd: workspace,
|
|
417
|
-
configs: [
|
|
418
|
-
skills: {
|
|
419
|
-
registry: 'https://registry.example.test'
|
|
420
|
-
}
|
|
421
|
-
}, undefined],
|
|
426
|
+
configs: [undefined, undefined],
|
|
422
427
|
useDefaultVibeForgeMcpServer: false
|
|
423
428
|
})
|
|
424
429
|
|
|
@@ -427,7 +432,6 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
427
432
|
resolvedBy: 'home-bridge',
|
|
428
433
|
sourcePath: join(realHome!, '.agents/skills/frontend-design/SKILL.md')
|
|
429
434
|
}))
|
|
430
|
-
expect(fetchMock).not.toHaveBeenCalled()
|
|
431
435
|
|
|
432
436
|
await buildAdapterAssetPlan({
|
|
433
437
|
adapter: 'opencode',
|
|
@@ -441,13 +445,122 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
441
445
|
|
|
442
446
|
const dependency = bundle.skills.find(asset => asset.name === 'frontend-design')
|
|
443
447
|
expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['app-builder', 'frontend-design'])
|
|
444
|
-
expect(dependency?.sourcePath).toContain(
|
|
448
|
+
expect(dependency?.sourcePath).toContain(
|
|
449
|
+
'/.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/frontend-design/'
|
|
450
|
+
)
|
|
445
451
|
expect(bundle.skills.find(asset => (
|
|
446
452
|
asset.name === 'frontend-design' && asset.resolvedBy === 'home-bridge'
|
|
447
453
|
))).toBeUndefined()
|
|
448
|
-
expect(
|
|
449
|
-
|
|
450
|
-
|
|
454
|
+
expect(skillsCliMocks.findSkillsCli).not.toHaveBeenCalled()
|
|
455
|
+
expect(skillsCliMocks.installSkillsCliSkillToTemp).toHaveBeenCalledWith({
|
|
456
|
+
config: undefined,
|
|
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.'
|
|
451
564
|
)
|
|
452
565
|
})
|
|
453
566
|
|
|
@@ -455,28 +568,20 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
455
568
|
const primary = await createWorkspace()
|
|
456
569
|
const worktree = await createWorkspace()
|
|
457
570
|
const previousPrimaryWorkspace = process.env.__VF_PROJECT_PRIMARY_WORKSPACE_FOLDER__
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
}))
|
|
571
|
+
const tempInstallDir = join(worktree, '.tmp-install-skills-cli')
|
|
572
|
+
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'frontend-design')
|
|
573
|
+
await writeDocument(
|
|
574
|
+
join(installedSkillDir, 'SKILL.md'),
|
|
575
|
+
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse primary cache.\n'
|
|
576
|
+
)
|
|
577
|
+
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
578
|
+
tempDir: tempInstallDir,
|
|
579
|
+
installedSkill: {
|
|
580
|
+
dirName: 'frontend-design',
|
|
581
|
+
name: 'frontend-design',
|
|
582
|
+
sourcePath: installedSkillDir
|
|
476
583
|
}
|
|
477
|
-
return new Response('not found', { status: 404 })
|
|
478
584
|
})
|
|
479
|
-
vi.stubGlobal('fetch', fetchMock)
|
|
480
585
|
|
|
481
586
|
try {
|
|
482
587
|
process.env.__VF_PROJECT_PRIMARY_WORKSPACE_FOLDER__ = primary
|
|
@@ -487,7 +592,7 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
487
592
|
'name: app-builder',
|
|
488
593
|
'description: Build apps',
|
|
489
594
|
'dependencies:',
|
|
490
|
-
' - frontend-design',
|
|
595
|
+
' - anthropics/skills@frontend-design',
|
|
491
596
|
'---',
|
|
492
597
|
'Build the app.'
|
|
493
598
|
].join('\n')
|
|
@@ -495,11 +600,7 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
495
600
|
|
|
496
601
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
497
602
|
cwd: worktree,
|
|
498
|
-
configs: [
|
|
499
|
-
skills: {
|
|
500
|
-
registry: 'https://registry.example.test'
|
|
501
|
-
}
|
|
502
|
-
}, undefined],
|
|
603
|
+
configs: [undefined, undefined],
|
|
503
604
|
useDefaultVibeForgeMcpServer: false
|
|
504
605
|
})
|
|
505
606
|
|
|
@@ -516,7 +617,7 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
516
617
|
const dependency = bundle.skills.find(asset => asset.name === 'frontend-design')
|
|
517
618
|
expect(dependency?.sourcePath).toContain(join(
|
|
518
619
|
primary,
|
|
519
|
-
'.ai/caches/skill-dependencies/
|
|
620
|
+
'.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/frontend-design/'
|
|
520
621
|
))
|
|
521
622
|
expect(dependency?.sourcePath).not.toContain(join(worktree, '.ai/caches'))
|
|
522
623
|
} finally {
|
|
@@ -530,12 +631,10 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
530
631
|
|
|
531
632
|
it('reuses complete skill dependency caches without deleting or downloading them again', async () => {
|
|
532
633
|
const workspace = await createWorkspace()
|
|
533
|
-
const fetchMock = vi.fn(async () => new Response('not found', { status: 404 }))
|
|
534
|
-
vi.stubGlobal('fetch', fetchMock)
|
|
535
634
|
|
|
536
635
|
const cachedSkillPath = join(
|
|
537
636
|
workspace,
|
|
538
|
-
'.ai/caches/skill-dependencies/
|
|
637
|
+
'.ai/caches/skill-dependencies/skills-cli/skills/latest/default/anthropics/skills/latest/frontend-design/SKILL.md'
|
|
539
638
|
)
|
|
540
639
|
await writeDocument(
|
|
541
640
|
cachedSkillPath,
|
|
@@ -556,11 +655,7 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
556
655
|
|
|
557
656
|
const bundle = await resolveWorkspaceAssetBundle({
|
|
558
657
|
cwd: workspace,
|
|
559
|
-
configs: [
|
|
560
|
-
skills: {
|
|
561
|
-
registry: 'https://registry.example.test'
|
|
562
|
-
}
|
|
563
|
-
}, undefined],
|
|
658
|
+
configs: [undefined, undefined],
|
|
564
659
|
useDefaultVibeForgeMcpServer: false
|
|
565
660
|
})
|
|
566
661
|
|
|
@@ -574,89 +669,64 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
574
669
|
}
|
|
575
670
|
})
|
|
576
671
|
|
|
577
|
-
expect(
|
|
672
|
+
expect(skillsCliMocks.findSkillsCli).not.toHaveBeenCalled()
|
|
673
|
+
expect(skillsCliMocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
|
|
578
674
|
expect(await readFile(cachedSkillPath, 'utf8')).toContain('Use the cached copy.')
|
|
579
675
|
expect(bundle.skills.map(asset => asset.name).sort()).toEqual(['app-builder', 'frontend-design'])
|
|
580
676
|
})
|
|
581
677
|
|
|
582
|
-
it('
|
|
678
|
+
it('parses registry/source/version dependency specs and forwards them to the skills CLI installer', async () => {
|
|
583
679
|
const workspace = await createWorkspace()
|
|
584
|
-
const
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
}))
|
|
680
|
+
const tempInstallDir = join(workspace, '.tmp-install-skills-cli')
|
|
681
|
+
const installedSkillDir = join(tempInstallDir, '.agents', 'skills', 'frontend-design')
|
|
682
|
+
await writeDocument(
|
|
683
|
+
join(installedSkillDir, 'SKILL.md'),
|
|
684
|
+
'---\nname: frontend-design\ndescription: UI design guidance\n---\nUse internal design tokens.\n'
|
|
685
|
+
)
|
|
686
|
+
skillsCliMocks.installSkillsCliSkillToTemp.mockResolvedValue({
|
|
687
|
+
tempDir: tempInstallDir,
|
|
688
|
+
installedSkill: {
|
|
689
|
+
dirName: 'frontend-design',
|
|
690
|
+
name: 'frontend-design',
|
|
691
|
+
sourcePath: installedSkillDir
|
|
603
692
|
}
|
|
604
|
-
return new Response('not found', { status: 404 })
|
|
605
693
|
})
|
|
606
|
-
vi.stubGlobal('fetch', fetchMock)
|
|
607
694
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
].join('\n')
|
|
621
|
-
)
|
|
695
|
+
await writeDocument(
|
|
696
|
+
join(workspace, '.ai/skills/app-builder/SKILL.md'),
|
|
697
|
+
[
|
|
698
|
+
'---',
|
|
699
|
+
'name: app-builder',
|
|
700
|
+
'description: Build apps',
|
|
701
|
+
'dependencies:',
|
|
702
|
+
' - https://registry.example.com@example-source/default/public@frontend-design@1.0.3',
|
|
703
|
+
'---',
|
|
704
|
+
'Build the app.'
|
|
705
|
+
].join('\n')
|
|
706
|
+
)
|
|
622
707
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
url: 'https://private-registry.example.test'
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}, undefined],
|
|
632
|
-
useDefaultVibeForgeMcpServer: false
|
|
633
|
-
})
|
|
708
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
709
|
+
cwd: workspace,
|
|
710
|
+
configs: [undefined, undefined],
|
|
711
|
+
useDefaultVibeForgeMcpServer: false
|
|
712
|
+
})
|
|
634
713
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
714
|
+
await buildAdapterAssetPlan({
|
|
715
|
+
adapter: 'opencode',
|
|
716
|
+
bundle,
|
|
717
|
+
options: {
|
|
718
|
+
skills: {
|
|
719
|
+
include: ['app-builder']
|
|
642
720
|
}
|
|
643
|
-
})
|
|
644
|
-
|
|
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
721
|
}
|
|
659
|
-
}
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
expect(skillsCliMocks.installSkillsCliSkillToTemp).toHaveBeenCalledWith({
|
|
725
|
+
registry: 'https://registry.example.com',
|
|
726
|
+
skill: 'frontend-design',
|
|
727
|
+
source: 'example-source/default/public',
|
|
728
|
+
version: '1.0.3'
|
|
729
|
+
})
|
|
660
730
|
})
|
|
661
731
|
|
|
662
732
|
it('loads workspace entities from the env-configured entities dir', async () => {
|
|
@@ -182,11 +182,9 @@ describe('workspace asset prompt builders', () => {
|
|
|
182
182
|
expect(prompt).toContain('`logLimit`')
|
|
183
183
|
expect(prompt).toContain('`logOrder`')
|
|
184
184
|
expect(prompt).toContain('`VibeForge.SendTaskMessage`')
|
|
185
|
-
expect(prompt).toContain('`{ taskId, message
|
|
186
|
-
expect(prompt).toContain('Choose `mode: "direct"`')
|
|
187
|
-
expect(prompt).toContain('Choose `mode: "steer"`')
|
|
185
|
+
expect(prompt).toContain('`{ taskId, message }`')
|
|
188
186
|
expect(prompt).toContain('`VibeForge.SubmitTaskInput`')
|
|
189
|
-
expect(prompt).toContain('Do not use it for ordinary follow-up instructions
|
|
187
|
+
expect(prompt).toContain('Do not use it for ordinary follow-up instructions')
|
|
190
188
|
expect(prompt).toContain('If a task is `completed` or `failed`')
|
|
191
189
|
expect(prompt).toContain('`wait`')
|
|
192
190
|
expect(prompt).not.toContain('run-tasks')
|
|
@@ -215,8 +213,6 @@ describe('workspace asset prompt builders', () => {
|
|
|
215
213
|
expect(prompt).toContain('`VibeForge.ListTasks`')
|
|
216
214
|
expect(prompt).toContain('`VibeForge.SendTaskMessage`')
|
|
217
215
|
expect(prompt).toContain('`VibeForge.SubmitTaskInput`')
|
|
218
|
-
expect(prompt).toContain('Choose `mode: "direct"`')
|
|
219
|
-
expect(prompt).toContain('Choose `mode: "steer"`')
|
|
220
216
|
expect(prompt).toContain('Do not directly edit files inside a registered workspace')
|
|
221
217
|
})
|
|
222
218
|
|