@vibe-forge/workspace-assets 3.2.2-alpha.2 → 3.2.2-alpha.3
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__/bundle.spec.ts +70 -2
- package/__tests__/prompt-selection.spec.ts +42 -0
- package/package.json +4 -4
- package/src/bundle-internal.ts +24 -2
- package/src/configured-skills.ts +222 -21
package/__tests__/bundle.spec.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
7
7
|
const skillsCliMocks = vi.hoisted(() => ({
|
|
8
8
|
findSkillsCli: vi.fn(),
|
|
9
9
|
installSkillsCliRefToTemp: vi.fn(),
|
|
10
|
-
installSkillsCliSkillToTemp: vi.fn()
|
|
10
|
+
installSkillsCliSkillToTemp: vi.fn(),
|
|
11
|
+
installSkillsCliSourceToTemp: vi.fn()
|
|
11
12
|
}))
|
|
12
13
|
|
|
13
14
|
vi.mock('@vibe-forge/utils/skills-cli', async () => {
|
|
@@ -16,7 +17,8 @@ vi.mock('@vibe-forge/utils/skills-cli', async () => {
|
|
|
16
17
|
...actual,
|
|
17
18
|
findSkillsCli: skillsCliMocks.findSkillsCli,
|
|
18
19
|
installSkillsCliRefToTemp: skillsCliMocks.installSkillsCliRefToTemp,
|
|
19
|
-
installSkillsCliSkillToTemp: skillsCliMocks.installSkillsCliSkillToTemp
|
|
20
|
+
installSkillsCliSkillToTemp: skillsCliMocks.installSkillsCliSkillToTemp,
|
|
21
|
+
installSkillsCliSourceToTemp: skillsCliMocks.installSkillsCliSourceToTemp
|
|
20
22
|
}
|
|
21
23
|
})
|
|
22
24
|
|
|
@@ -410,6 +412,72 @@ describe('resolveWorkspaceAssetBundle', () => {
|
|
|
410
412
|
expect(warn).toHaveBeenCalledWith(expect.stringContaining('Declared skills are not installed: internal-review'))
|
|
411
413
|
})
|
|
412
414
|
|
|
415
|
+
it('warns when a configured skill source collection is missing', async () => {
|
|
416
|
+
const workspace = await createWorkspace()
|
|
417
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
418
|
+
|
|
419
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
420
|
+
cwd: workspace,
|
|
421
|
+
configs: [{
|
|
422
|
+
skills: [
|
|
423
|
+
{
|
|
424
|
+
include: ['*'],
|
|
425
|
+
source: 'example-source/default/public'
|
|
426
|
+
}
|
|
427
|
+
]
|
|
428
|
+
}, undefined],
|
|
429
|
+
warnMissingConfiguredSkills: true,
|
|
430
|
+
useDefaultVibeForgeMcpServer: false
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
expect(bundle.skills.map(asset => asset.name)).toEqual([])
|
|
434
|
+
expect(skillsCliMocks.installSkillsCliSourceToTemp).not.toHaveBeenCalled()
|
|
435
|
+
expect(warn).toHaveBeenCalledWith(
|
|
436
|
+
expect.stringContaining('Declared skill sources are not installed: example-source/default/public')
|
|
437
|
+
)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it('does not warn for installed configured skill source collections', async () => {
|
|
441
|
+
const workspace = await createWorkspace()
|
|
442
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
|
443
|
+
|
|
444
|
+
await writeDocument(
|
|
445
|
+
join(workspace, '.ai/skills/source-review/SKILL.md'),
|
|
446
|
+
'---\nname: source-review\ndescription: Existing skill\n---\nExisting content.\n'
|
|
447
|
+
)
|
|
448
|
+
await writeDocument(
|
|
449
|
+
join(workspace, '.ai/skills.lock.yaml'),
|
|
450
|
+
[
|
|
451
|
+
'version: 1',
|
|
452
|
+
'skills:',
|
|
453
|
+
' source-review:',
|
|
454
|
+
' name: source-review',
|
|
455
|
+
' requested: true',
|
|
456
|
+
' installPath: .ai/skills/source-review',
|
|
457
|
+
' source: example-source/default/public',
|
|
458
|
+
' hash: sha256:test',
|
|
459
|
+
' installedAt: "2026-05-14T00:00:00.000Z"'
|
|
460
|
+
].join('\n')
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
464
|
+
cwd: workspace,
|
|
465
|
+
configs: [{
|
|
466
|
+
skills: [
|
|
467
|
+
{
|
|
468
|
+
include: ['*'],
|
|
469
|
+
source: 'example-source/default/public'
|
|
470
|
+
}
|
|
471
|
+
]
|
|
472
|
+
}, undefined],
|
|
473
|
+
warnMissingConfiguredSkills: true,
|
|
474
|
+
useDefaultVibeForgeMcpServer: false
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
expect(bundle.skills.map(asset => asset.name)).toContain('source-review')
|
|
478
|
+
expect(warn).not.toHaveBeenCalled()
|
|
479
|
+
})
|
|
480
|
+
|
|
413
481
|
it('loads installed configured skills without attempting runtime updates', async () => {
|
|
414
482
|
const workspace = await createWorkspace()
|
|
415
483
|
await writeDocument(
|
|
@@ -2,11 +2,53 @@ import { join } from 'node:path'
|
|
|
2
2
|
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
4
|
|
|
5
|
+
import { writeProjectSkillsLockfile } from '@vibe-forge/utils'
|
|
6
|
+
|
|
5
7
|
import { resolvePromptAssetSelection, resolveWorkspaceAssetBundle } from '#~/index.js'
|
|
6
8
|
|
|
7
9
|
import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
|
|
8
10
|
|
|
9
11
|
describe('resolvePromptAssetSelection', () => {
|
|
12
|
+
it('loads nested project skills through the project skills lockfile', async () => {
|
|
13
|
+
const workspace = await createWorkspace()
|
|
14
|
+
const skillPath = join(workspace, '.ai/skills/.extends/base-skills/larksuite-cli/lark-doc/SKILL.md')
|
|
15
|
+
await writeDocument(
|
|
16
|
+
skillPath,
|
|
17
|
+
[
|
|
18
|
+
'---',
|
|
19
|
+
'name: lark-doc',
|
|
20
|
+
'description: Lark docs',
|
|
21
|
+
'---',
|
|
22
|
+
'Read docs.'
|
|
23
|
+
].join('\n')
|
|
24
|
+
)
|
|
25
|
+
await writeProjectSkillsLockfile(workspace, {
|
|
26
|
+
version: 1,
|
|
27
|
+
skills: {
|
|
28
|
+
'lark-doc': {
|
|
29
|
+
hash: 'sha256:test',
|
|
30
|
+
installedAt: '2026-01-01T00:00:00.000Z',
|
|
31
|
+
installPath: '.ai/skills/.extends/base-skills/larksuite-cli/lark-doc',
|
|
32
|
+
name: 'lark-doc',
|
|
33
|
+
requested: true,
|
|
34
|
+
source: 'larksuite/cli'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
40
|
+
cwd: workspace,
|
|
41
|
+
useDefaultVibeForgeMcpServer: false
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
expect(bundle.skills).toEqual(expect.arrayContaining([
|
|
45
|
+
expect.objectContaining({
|
|
46
|
+
name: 'lark-doc',
|
|
47
|
+
sourcePath: skillPath
|
|
48
|
+
})
|
|
49
|
+
]))
|
|
50
|
+
})
|
|
51
|
+
|
|
10
52
|
it('embeds only alwaysApply rules and keeps optional rules as summaries', async () => {
|
|
11
53
|
const workspace = await createWorkspace()
|
|
12
54
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-forge/workspace-assets",
|
|
3
|
-
"version": "3.2.2-alpha.
|
|
3
|
+
"version": "3.2.2-alpha.3",
|
|
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": "3.2.3-alpha.
|
|
33
|
-
"@vibe-forge/definition-core": "3.2.0",
|
|
32
|
+
"@vibe-forge/config": "3.2.3-alpha.3",
|
|
34
33
|
"@vibe-forge/types": "3.2.3-alpha.1",
|
|
35
|
-
"@vibe-forge/
|
|
34
|
+
"@vibe-forge/definition-core": "3.2.0",
|
|
35
|
+
"@vibe-forge/utils": "3.2.3-alpha.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/js-yaml": "^4.0.9"
|
package/src/bundle-internal.ts
CHANGED
|
@@ -377,9 +377,18 @@ const createOpenCodeOverlayAsset = <TKind extends OpenCodeOverlayKind>(params: {
|
|
|
377
377
|
const scanWorkspaceDocuments = async (cwd: string) => {
|
|
378
378
|
const aiBaseDir = resolveProjectAiBaseDir(cwd, process.env)
|
|
379
379
|
const entitiesDir = resolveProjectAiEntitiesDir(cwd, process.env)
|
|
380
|
-
const [
|
|
380
|
+
const [
|
|
381
|
+
rulePaths,
|
|
382
|
+
directSkillPaths,
|
|
383
|
+
lockedSkillPaths,
|
|
384
|
+
specPaths,
|
|
385
|
+
entityDocPaths,
|
|
386
|
+
entityJsonPaths,
|
|
387
|
+
mcpPaths
|
|
388
|
+
] = await Promise.all([
|
|
381
389
|
glob(['rules/*.md'], { cwd: aiBaseDir, absolute: true }),
|
|
382
390
|
glob(['skills/*/SKILL.md'], { cwd: aiBaseDir, absolute: true }),
|
|
391
|
+
scanProjectSkillLockfileDocuments(cwd),
|
|
383
392
|
glob(['specs/*.md', 'specs/*/index.md'], { cwd: aiBaseDir, absolute: true }),
|
|
384
393
|
glob(['*.md', '*/README.md'], { cwd: entitiesDir, absolute: true }),
|
|
385
394
|
glob(['*/index.json'], { cwd: entitiesDir, absolute: true }),
|
|
@@ -388,7 +397,7 @@ const scanWorkspaceDocuments = async (cwd: string) => {
|
|
|
388
397
|
|
|
389
398
|
return {
|
|
390
399
|
rulePaths,
|
|
391
|
-
skillPaths,
|
|
400
|
+
skillPaths: Array.from(new Set([...directSkillPaths, ...lockedSkillPaths])),
|
|
392
401
|
specPaths,
|
|
393
402
|
entityDocPaths,
|
|
394
403
|
entityJsonPaths,
|
|
@@ -451,6 +460,19 @@ const pathExists = async (path: string) => {
|
|
|
451
460
|
}
|
|
452
461
|
}
|
|
453
462
|
|
|
463
|
+
const scanProjectSkillLockfileDocuments = async (cwd: string) => {
|
|
464
|
+
const lockfile = await readProjectSkillsLockfile(cwd)
|
|
465
|
+
const skillPaths: string[] = []
|
|
466
|
+
for (const entry of Object.values(lockfile.skills ?? {})) {
|
|
467
|
+
const skillPath = resolve(cwd, entry.installPath, 'SKILL.md')
|
|
468
|
+
if (await pathExists(skillPath)) {
|
|
469
|
+
skillPaths.push(skillPath)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return skillPaths
|
|
474
|
+
}
|
|
475
|
+
|
|
454
476
|
const scanPluginDependencySkillDocuments = async (
|
|
455
477
|
cwd: string,
|
|
456
478
|
instances: ResolvedPluginInstance[]
|
package/src/configured-skills.ts
CHANGED
|
@@ -1,23 +1,68 @@
|
|
|
1
1
|
import { access } from 'node:fs/promises'
|
|
2
|
+
import { isAbsolute, join, resolve as resolvePath } from 'node:path'
|
|
2
3
|
import process from 'node:process'
|
|
3
4
|
|
|
4
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
Config,
|
|
7
|
+
ConfiguredSkillCollectionConfig,
|
|
8
|
+
ConfiguredSkillIncludeConfig,
|
|
9
|
+
ConfiguredSkillInstallConfig
|
|
10
|
+
} from '@vibe-forge/types'
|
|
5
11
|
import {
|
|
12
|
+
isConfiguredSkillCollectionInstall,
|
|
13
|
+
isWildcardSkillInclude,
|
|
6
14
|
normalizeProjectSkillInstall,
|
|
15
|
+
readProjectSkillsLockfile,
|
|
7
16
|
resolveConfiguredSkillInstalls as resolveDeclaredConfiguredSkillInstalls,
|
|
8
|
-
resolveProjectAiPath
|
|
17
|
+
resolveProjectAiPath,
|
|
18
|
+
toSkillSlug
|
|
9
19
|
} from '@vibe-forge/utils'
|
|
10
|
-
import type { NormalizedProjectSkillInstall } from '@vibe-forge/utils'
|
|
20
|
+
import type { NormalizedProjectSkillInstall, ProjectSkillLockEntry } from '@vibe-forge/utils'
|
|
21
|
+
|
|
22
|
+
interface NormalizedConfiguredSkillCollection {
|
|
23
|
+
include?: ConfiguredSkillCollectionConfig['include']
|
|
24
|
+
registry?: string
|
|
25
|
+
source: string
|
|
26
|
+
version?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface MissingConfiguredProjectSkillCollection extends NormalizedConfiguredSkillCollection {
|
|
30
|
+
missingTargets?: string[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const normalizeNonEmptyString = (value: unknown) => (
|
|
34
|
+
typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const resolveConfiguredProjectSkillDeclarations = (configs: [Config?, Config?]) => [
|
|
38
|
+
...resolveDeclaredConfiguredSkillInstalls(configs[0]?.skills),
|
|
39
|
+
...resolveDeclaredConfiguredSkillInstalls(configs[1]?.skills)
|
|
40
|
+
]
|
|
11
41
|
|
|
12
42
|
export const resolveConfiguredProjectSkillInstalls = (configs: [Config?, Config?]) => (
|
|
13
|
-
|
|
14
|
-
...resolveDeclaredConfiguredSkillInstalls(configs[0]?.skills),
|
|
15
|
-
...resolveDeclaredConfiguredSkillInstalls(configs[1]?.skills)
|
|
16
|
-
]
|
|
43
|
+
resolveConfiguredProjectSkillDeclarations(configs)
|
|
17
44
|
.map((item) => normalizeProjectSkillInstall(item as string | ConfiguredSkillInstallConfig))
|
|
18
45
|
.filter((item): item is NormalizedProjectSkillInstall => item != null)
|
|
19
46
|
)
|
|
20
47
|
|
|
48
|
+
export const resolveConfiguredProjectSkillCollections = (configs: [Config?, Config?]) => (
|
|
49
|
+
resolveConfiguredProjectSkillDeclarations(configs)
|
|
50
|
+
.map((item) => {
|
|
51
|
+
if (!isConfiguredSkillCollectionInstall(item)) return undefined
|
|
52
|
+
const source = normalizeNonEmptyString(item.source)
|
|
53
|
+
if (source == null) return undefined
|
|
54
|
+
const registry = normalizeNonEmptyString(item.registry)
|
|
55
|
+
const version = normalizeNonEmptyString(item.version)
|
|
56
|
+
return {
|
|
57
|
+
...(item.include == null ? {} : { include: item.include }),
|
|
58
|
+
...(registry == null ? {} : { registry }),
|
|
59
|
+
source,
|
|
60
|
+
...(version == null ? {} : { version })
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
.filter((item): item is NormalizedConfiguredSkillCollection => item != null)
|
|
64
|
+
)
|
|
65
|
+
|
|
21
66
|
const pathExists = async (targetPath: string) => {
|
|
22
67
|
try {
|
|
23
68
|
await access(targetPath)
|
|
@@ -27,6 +72,77 @@ const pathExists = async (targetPath: string) => {
|
|
|
27
72
|
}
|
|
28
73
|
}
|
|
29
74
|
|
|
75
|
+
const isWildcardCollection = (collection: NormalizedConfiguredSkillCollection) => (
|
|
76
|
+
collection.include == null ||
|
|
77
|
+
collection.include.length === 0 ||
|
|
78
|
+
collection.include.some(isWildcardSkillInclude)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const normalizeCollectionTarget = (
|
|
82
|
+
include: ConfiguredSkillIncludeConfig
|
|
83
|
+
) => {
|
|
84
|
+
if (isWildcardSkillInclude(include)) return undefined
|
|
85
|
+
|
|
86
|
+
const name = normalizeNonEmptyString(typeof include === 'string' ? include : include.name)
|
|
87
|
+
if (name == null) return undefined
|
|
88
|
+
const rename = typeof include === 'string' ? undefined : normalizeNonEmptyString(include.rename)
|
|
89
|
+
const targetName = rename ?? name
|
|
90
|
+
const targetDirName = toSkillSlug(targetName)
|
|
91
|
+
if (targetDirName === '') return undefined
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
targetDirName,
|
|
95
|
+
targetName
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const lockEntryMatchesCollection = (
|
|
100
|
+
entry: ProjectSkillLockEntry,
|
|
101
|
+
collection: NormalizedConfiguredSkillCollection
|
|
102
|
+
) => (
|
|
103
|
+
entry.source === collection.source &&
|
|
104
|
+
(collection.registry == null || entry.registry === collection.registry) &&
|
|
105
|
+
(collection.version == null || entry.version === collection.version)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const resolveLockEntrySkillPath = (workspaceFolder: string, entry: ProjectSkillLockEntry) => {
|
|
109
|
+
const installDir = isAbsolute(entry.installPath)
|
|
110
|
+
? entry.installPath
|
|
111
|
+
: resolvePath(workspaceFolder, entry.installPath)
|
|
112
|
+
return join(installDir, 'SKILL.md')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const hasInstalledCollectionLockEntry = async (params: {
|
|
116
|
+
collection: NormalizedConfiguredSkillCollection
|
|
117
|
+
entries: Record<string, ProjectSkillLockEntry>
|
|
118
|
+
workspaceFolder: string
|
|
119
|
+
}) => {
|
|
120
|
+
for (const entry of Object.values(params.entries)) {
|
|
121
|
+
if (!lockEntryMatchesCollection(entry, params.collection)) continue
|
|
122
|
+
if (await pathExists(resolveLockEntrySkillPath(params.workspaceFolder, entry))) return true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const hasInstalledSkillTarget = async (params: {
|
|
129
|
+
entries: Record<string, ProjectSkillLockEntry>
|
|
130
|
+
targetDirName: string
|
|
131
|
+
workspaceFolder: string
|
|
132
|
+
}) => {
|
|
133
|
+
const entry = params.entries[params.targetDirName]
|
|
134
|
+
if (entry != null && await pathExists(resolveLockEntrySkillPath(params.workspaceFolder, entry))) return true
|
|
135
|
+
|
|
136
|
+
const skillPath = resolveProjectAiPath(
|
|
137
|
+
params.workspaceFolder,
|
|
138
|
+
process.env,
|
|
139
|
+
'skills',
|
|
140
|
+
params.targetDirName,
|
|
141
|
+
'SKILL.md'
|
|
142
|
+
)
|
|
143
|
+
return pathExists(skillPath)
|
|
144
|
+
}
|
|
145
|
+
|
|
30
146
|
export const ensureUniqueConfiguredSkillTargets = (skills: NormalizedProjectSkillInstall[]) => {
|
|
31
147
|
const seen = new Map<string, string>()
|
|
32
148
|
|
|
@@ -52,32 +168,117 @@ export const findMissingConfiguredProjectSkills = async (params: {
|
|
|
52
168
|
|
|
53
169
|
ensureUniqueConfiguredSkillTargets(installs)
|
|
54
170
|
|
|
171
|
+
const lockfile = await readProjectSkillsLockfile(params.workspaceFolder)
|
|
55
172
|
const missing: NormalizedProjectSkillInstall[] = []
|
|
56
173
|
for (const skill of installs) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
|
|
174
|
+
if (
|
|
175
|
+
!await hasInstalledSkillTarget({
|
|
176
|
+
entries: lockfile.skills ?? {},
|
|
177
|
+
targetDirName: skill.targetDirName,
|
|
178
|
+
workspaceFolder: params.workspaceFolder
|
|
179
|
+
})
|
|
180
|
+
) {
|
|
181
|
+
missing.push(skill)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return missing
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export const findMissingConfiguredProjectSkillCollections = async (params: {
|
|
189
|
+
configs: [Config?, Config?]
|
|
190
|
+
workspaceFolder: string
|
|
191
|
+
}) => {
|
|
192
|
+
const collections = resolveConfiguredProjectSkillCollections(params.configs)
|
|
193
|
+
if (collections.length === 0) return []
|
|
194
|
+
|
|
195
|
+
const lockfile = await readProjectSkillsLockfile(params.workspaceFolder)
|
|
196
|
+
const missing: MissingConfiguredProjectSkillCollection[] = []
|
|
197
|
+
const seen = new Set<string>()
|
|
198
|
+
|
|
199
|
+
const addMissing = (collection: MissingConfiguredProjectSkillCollection) => {
|
|
200
|
+
const key = [
|
|
201
|
+
collection.source,
|
|
202
|
+
collection.registry ?? '',
|
|
203
|
+
collection.version ?? '',
|
|
204
|
+
collection.missingTargets?.join('|') ?? '*'
|
|
205
|
+
].join('\0')
|
|
206
|
+
if (seen.has(key)) return
|
|
207
|
+
seen.add(key)
|
|
208
|
+
missing.push(collection)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const collection of collections) {
|
|
212
|
+
if (isWildcardCollection(collection)) {
|
|
213
|
+
if (
|
|
214
|
+
!await hasInstalledCollectionLockEntry({
|
|
215
|
+
collection,
|
|
216
|
+
entries: lockfile.skills ?? {},
|
|
217
|
+
workspaceFolder: params.workspaceFolder
|
|
218
|
+
})
|
|
219
|
+
) {
|
|
220
|
+
addMissing(collection)
|
|
221
|
+
}
|
|
222
|
+
continue
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const missingTargets: string[] = []
|
|
226
|
+
for (const include of collection.include ?? []) {
|
|
227
|
+
const target = normalizeCollectionTarget(include)
|
|
228
|
+
if (target == null) continue
|
|
229
|
+
if (
|
|
230
|
+
!await hasInstalledSkillTarget({
|
|
231
|
+
entries: lockfile.skills ?? {},
|
|
232
|
+
targetDirName: target.targetDirName,
|
|
233
|
+
workspaceFolder: params.workspaceFolder
|
|
234
|
+
})
|
|
235
|
+
) {
|
|
236
|
+
missingTargets.push(target.targetName)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (missingTargets.length > 0) {
|
|
241
|
+
addMissing({
|
|
242
|
+
...collection,
|
|
243
|
+
missingTargets
|
|
244
|
+
})
|
|
245
|
+
}
|
|
65
246
|
}
|
|
66
247
|
|
|
67
248
|
return missing
|
|
68
249
|
}
|
|
69
250
|
|
|
251
|
+
const formatMissingCollectionLabel = (collection: MissingConfiguredProjectSkillCollection) => {
|
|
252
|
+
const sourceLabel = collection.version == null
|
|
253
|
+
? collection.source
|
|
254
|
+
: `${collection.source}@${collection.version}`
|
|
255
|
+
if (collection.missingTargets == null || collection.missingTargets.length === 0) return sourceLabel
|
|
256
|
+
return `${sourceLabel} (${collection.missingTargets.join(', ')})`
|
|
257
|
+
}
|
|
258
|
+
|
|
70
259
|
export const warnMissingConfiguredProjectSkills = async (params: {
|
|
71
260
|
configs: [Config?, Config?]
|
|
72
261
|
workspaceFolder: string
|
|
73
262
|
}) => {
|
|
74
263
|
const missing = await findMissingConfiguredProjectSkills(params)
|
|
75
|
-
|
|
264
|
+
const missingCollections = await findMissingConfiguredProjectSkillCollections(params)
|
|
265
|
+
if (missing.length === 0 && missingCollections.length === 0) return []
|
|
266
|
+
|
|
267
|
+
if (missing.length > 0) {
|
|
268
|
+
const names = missing.map(skill => skill.targetName).join(', ')
|
|
269
|
+
console.warn(
|
|
270
|
+
`[vibe-forge] Declared skills are not installed: ${names}. ` +
|
|
271
|
+
'Run `vf skills install` to install all declared skills, or `vf skills install <name>` for one skill.'
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (missingCollections.length > 0) {
|
|
276
|
+
const sources = missingCollections.map(formatMissingCollectionLabel).join(', ')
|
|
277
|
+
console.warn(
|
|
278
|
+
`[vibe-forge] Declared skill sources are not installed: ${sources}. ` +
|
|
279
|
+
'Run `vf skills install` to install all declared skills.'
|
|
280
|
+
)
|
|
281
|
+
}
|
|
76
282
|
|
|
77
|
-
const names = missing.map(skill => skill.targetName).join(', ')
|
|
78
|
-
console.warn(
|
|
79
|
-
`[vibe-forge] Declared skills are not installed: ${names}. ` +
|
|
80
|
-
'Run `vf skills install` to install all declared skills, or `vf skills install <name>` for one skill.'
|
|
81
|
-
)
|
|
82
283
|
return missing
|
|
83
284
|
}
|