@vibe-forge/workspace-assets 3.4.0-rc.2 → 3.4.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.
@@ -23,6 +23,7 @@ vi.mock('@vibe-forge/utils/skills-cli', async () => {
23
23
  })
24
24
 
25
25
  import { resolveWorkspaceAssetBundle } from '#~/index.js'
26
+ import { resolveProjectSkillsStorePath, writeProjectSkillsLockfile } from '@vibe-forge/utils'
26
27
 
27
28
  import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
28
29
 
@@ -579,6 +580,57 @@ describe('resolveWorkspaceAssetBundle', () => {
579
580
  expect(skillsCliMocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
580
581
  })
581
582
 
583
+ it('loads lockfile-managed configured skills from the real home project store', async () => {
584
+ const workspace = await createWorkspace()
585
+ const storeHash = 'sha256-home-store'
586
+ const installDir = resolveProjectSkillsStorePath(
587
+ {
588
+ storeHash,
589
+ workspaceFolder: workspace
590
+ },
591
+ 'skills',
592
+ 'internal-review'
593
+ )
594
+ await writeDocument(
595
+ join(installDir, 'SKILL.md'),
596
+ '---\nname: internal-review\ndescription: Existing skill\n---\nExisting content.\n'
597
+ )
598
+ await writeProjectSkillsLockfile(workspace, {
599
+ version: 1,
600
+ storeHash,
601
+ skills: {
602
+ 'internal-review': {
603
+ hash: 'sha256:test',
604
+ installedAt: '2026-05-19T00:00:00.000Z',
605
+ installPath: 'skills/internal-review',
606
+ name: 'internal-review',
607
+ requested: true,
608
+ source: 'example-source/default/public'
609
+ }
610
+ }
611
+ })
612
+
613
+ const bundle = await resolveWorkspaceAssetBundle({
614
+ cwd: workspace,
615
+ configs: [{
616
+ skills: [
617
+ {
618
+ name: 'design-review',
619
+ source: 'example-source/default/public',
620
+ rename: 'internal-review'
621
+ }
622
+ ]
623
+ }, undefined],
624
+ warnMissingConfiguredSkills: true,
625
+ useDefaultVibeForgeMcpServer: false
626
+ })
627
+
628
+ expect(bundle.skills.find(asset => asset.name === 'internal-review')).toEqual(expect.objectContaining({
629
+ sourcePath: join(installDir, 'SKILL.md')
630
+ }))
631
+ expect(skillsCliMocks.installSkillsCliSkillToTemp).not.toHaveBeenCalled()
632
+ })
633
+
582
634
  it('loads workspace entities from the env-configured entities dir', async () => {
583
635
  const workspace = await createWorkspace()
584
636
  const previousEntitiesDir = process.env.__VF_PROJECT_AI_ENTITIES_DIR__
@@ -19,6 +19,8 @@ vi.mock('@vibe-forge/utils/skills-cli', async () => {
19
19
  }
20
20
  })
21
21
 
22
+ import { resolveProjectSkillsStorePath } from '@vibe-forge/utils'
23
+
22
24
  import { buildAdapterAssetPlan, resolveWorkspaceAssetBundle } from '#~/index.js'
23
25
 
24
26
  import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
@@ -136,8 +138,9 @@ describe('materialized skill dependency resolution', () => {
136
138
  expect(mocks.findSkillsCli).not.toHaveBeenCalled()
137
139
  })
138
140
 
139
- it('loads plugin dependencies from .ai/skills/.plugins through the lockfile', async () => {
141
+ it('loads plugin dependencies from the home project store through the lockfile', async () => {
140
142
  const workspace = await createWorkspace()
143
+ const storeHash = 'sha256-store'
141
144
  await installPluginPackage(workspace, '@vibe-forge/plugin-review', {
142
145
  'package.json': JSON.stringify({ name: '@vibe-forge/plugin-review', version: '1.0.0' }, null, 2),
143
146
  'skills/review-helper/SKILL.md': [
@@ -151,20 +154,27 @@ describe('materialized skill dependency resolution', () => {
151
154
  ].join('\n')
152
155
  })
153
156
  await writeDocument(
154
- join(workspace, '.ai/skills/.plugins/review/shared-runtime/SKILL.md'),
157
+ join(
158
+ resolveProjectSkillsStorePath({
159
+ storeHash,
160
+ workspaceFolder: workspace
161
+ }, 'skills', '.plugins', 'review', 'shared-runtime'),
162
+ 'SKILL.md'
163
+ ),
155
164
  '---\nname: shared-runtime\ndescription: Shared runtime\n---\nShared plugin dependency.\n'
156
165
  )
157
166
  await writeDocument(
158
167
  join(workspace, '.ai/skills.lock.yaml'),
159
168
  [
160
169
  'version: 1',
170
+ `storeHash: ${storeHash}`,
161
171
  'pluginSkills:',
162
172
  ' review/shared-runtime:',
163
173
  ' name: shared-runtime',
164
174
  ' requested: false',
165
175
  ' pluginInstance: review',
166
176
  ' pluginInstancePath: "0"',
167
- ' installPath: .ai/skills/.plugins/review/shared-runtime',
177
+ ' installPath: skills/.plugins/review/shared-runtime',
168
178
  ' dependencyOf:',
169
179
  ' - plugin:review/review-helper',
170
180
  ' source: vendor/shared-skills',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-forge/workspace-assets",
3
- "version": "3.4.0-rc.2",
3
+ "version": "3.4.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": "3.4.0-rc.2",
33
- "@vibe-forge/definition-core": "3.4.0-rc.2",
34
- "@vibe-forge/types": "3.4.0-rc.2",
35
- "@vibe-forge/utils": "3.4.0-rc.2"
32
+ "@vibe-forge/config": "3.4.0",
33
+ "@vibe-forge/definition-core": "3.4.0",
34
+ "@vibe-forge/types": "3.4.0",
35
+ "@vibe-forge/utils": "3.4.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/js-yaml": "^4.0.9"
@@ -13,6 +13,7 @@ import {
13
13
  readProjectSkillsLockfile,
14
14
  resolveProjectAiBaseDir,
15
15
  resolveProjectAiEntitiesDir,
16
+ resolveProjectSkillInstallDir,
16
17
  resolveRelativePath,
17
18
  resolveSkillsHomeBridge
18
19
  } from '@vibe-forge/utils'
@@ -500,7 +501,14 @@ const scanProjectSkillLockfileDocuments = async (cwd: string) => {
500
501
  const lockfile = await readProjectSkillsLockfile(cwd)
501
502
  const skillPaths: string[] = []
502
503
  for (const entry of Object.values(lockfile.skills ?? {})) {
503
- const skillPath = resolve(cwd, entry.installPath, 'SKILL.md')
504
+ const skillPath = resolve(
505
+ resolveProjectSkillInstallDir({
506
+ installPath: entry.installPath,
507
+ storeHash: entry.storeHash ?? lockfile.storeHash,
508
+ workspaceFolder: cwd
509
+ }),
510
+ 'SKILL.md'
511
+ )
504
512
  if (await pathExists(skillPath)) {
505
513
  skillPaths.push(skillPath)
506
514
  }
@@ -522,7 +530,14 @@ const scanPluginDependencySkillDocuments = async (
522
530
  ))
523
531
  if (instance == null) continue
524
532
 
525
- const skillPath = resolve(cwd, entry.installPath, 'SKILL.md')
533
+ const skillPath = resolve(
534
+ resolveProjectSkillInstallDir({
535
+ installPath: entry.installPath,
536
+ storeHash: entry.storeHash ?? lockfile.storeHash,
537
+ workspaceFolder: cwd
538
+ }),
539
+ 'SKILL.md'
540
+ )
526
541
  if (await pathExists(skillPath)) {
527
542
  documents.push({
528
543
  instance,
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable max-lines -- configured skill resolution keeps matching, warnings, and CLI sync policy together. */
2
2
  import { access } from 'node:fs/promises'
3
- import { isAbsolute, join, resolve as resolvePath } from 'node:path'
3
+ import { join } from 'node:path'
4
4
  import process from 'node:process'
5
5
 
6
6
  import type {
@@ -16,9 +16,10 @@ import {
16
16
  readProjectSkillsLockfile,
17
17
  resolveConfiguredSkillInstalls as resolveDeclaredConfiguredSkillInstalls,
18
18
  resolveProjectAiPath,
19
+ resolveProjectSkillInstallDir,
19
20
  toSkillSlug
20
21
  } from '@vibe-forge/utils'
21
- import type { NormalizedProjectSkillInstall, ProjectSkillLockEntry } from '@vibe-forge/utils'
22
+ import type { NormalizedProjectSkillInstall, ProjectSkillLockEntry, ProjectSkillsLockfile } from '@vibe-forge/utils'
22
23
 
23
24
  interface NormalizedConfiguredSkillCollection {
24
25
  include?: ConfiguredSkillCollectionConfig['include']
@@ -106,33 +107,42 @@ const lockEntryMatchesCollection = (
106
107
  (collection.version == null || entry.version === collection.version)
107
108
  )
108
109
 
109
- const resolveLockEntrySkillPath = (workspaceFolder: string, entry: ProjectSkillLockEntry) => {
110
- const installDir = isAbsolute(entry.installPath)
111
- ? entry.installPath
112
- : resolvePath(workspaceFolder, entry.installPath)
113
- return join(installDir, 'SKILL.md')
114
- }
110
+ const resolveLockEntrySkillPath = (
111
+ workspaceFolder: string,
112
+ lockfile: Pick<ProjectSkillsLockfile, 'storeHash'>,
113
+ entry: ProjectSkillLockEntry
114
+ ) =>
115
+ join(
116
+ resolveProjectSkillInstallDir({
117
+ installPath: entry.installPath,
118
+ storeHash: entry.storeHash ?? lockfile.storeHash,
119
+ workspaceFolder
120
+ }),
121
+ 'SKILL.md'
122
+ )
115
123
 
116
124
  const hasInstalledCollectionLockEntry = async (params: {
117
125
  collection: NormalizedConfiguredSkillCollection
118
- entries: Record<string, ProjectSkillLockEntry>
126
+ lockfile: ProjectSkillsLockfile
119
127
  workspaceFolder: string
120
128
  }) => {
121
- for (const entry of Object.values(params.entries)) {
129
+ for (const entry of Object.values(params.lockfile.skills ?? {})) {
122
130
  if (!lockEntryMatchesCollection(entry, params.collection)) continue
123
- if (await pathExists(resolveLockEntrySkillPath(params.workspaceFolder, entry))) return true
131
+ if (await pathExists(resolveLockEntrySkillPath(params.workspaceFolder, params.lockfile, entry))) return true
124
132
  }
125
133
 
126
134
  return false
127
135
  }
128
136
 
129
137
  const hasInstalledSkillTarget = async (params: {
130
- entries: Record<string, ProjectSkillLockEntry>
138
+ lockfile: ProjectSkillsLockfile
131
139
  targetDirName: string
132
140
  workspaceFolder: string
133
141
  }) => {
134
- const entry = params.entries[params.targetDirName]
135
- if (entry != null && await pathExists(resolveLockEntrySkillPath(params.workspaceFolder, entry))) return true
142
+ const entry = params.lockfile.skills?.[params.targetDirName]
143
+ if (entry != null && await pathExists(resolveLockEntrySkillPath(params.workspaceFolder, params.lockfile, entry))) {
144
+ return true
145
+ }
136
146
 
137
147
  const skillPath = resolveProjectAiPath(
138
148
  params.workspaceFolder,
@@ -174,7 +184,7 @@ export const findMissingConfiguredProjectSkills = async (params: {
174
184
  for (const skill of installs) {
175
185
  if (
176
186
  !await hasInstalledSkillTarget({
177
- entries: lockfile.skills ?? {},
187
+ lockfile,
178
188
  targetDirName: skill.targetDirName,
179
189
  workspaceFolder: params.workspaceFolder
180
190
  })
@@ -214,7 +224,7 @@ export const findMissingConfiguredProjectSkillCollections = async (params: {
214
224
  if (
215
225
  !await hasInstalledCollectionLockEntry({
216
226
  collection,
217
- entries: lockfile.skills ?? {},
227
+ lockfile,
218
228
  workspaceFolder: params.workspaceFolder
219
229
  })
220
230
  ) {
@@ -229,7 +239,7 @@ export const findMissingConfiguredProjectSkillCollections = async (params: {
229
239
  if (target == null) continue
230
240
  if (
231
241
  !await hasInstalledSkillTarget({
232
- entries: lockfile.skills ?? {},
242
+ lockfile,
233
243
  targetDirName: target.targetDirName,
234
244
  workspaceFolder: params.workspaceFolder
235
245
  })