@vibe-forge/workspace-assets 0.9.0 → 0.9.1-alpha.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.
@@ -4,129 +4,19 @@ import { describe, expect, it } from 'vitest'
4
4
 
5
5
  import { resolvePromptAssetSelection, resolveWorkspaceAssetBundle } from '#~/index.js'
6
6
 
7
- import { createWorkspace, installPluginPackage, writeDocument } from './test-helpers'
7
+ import { createWorkspace, writeDocument } from './test-helpers'
8
8
 
9
9
  describe('resolvePromptAssetSelection', () => {
10
- it('embeds only alwaysApply rules and keeps optional rules as summaries', async () => {
10
+ it('prefers project prompt assets over plugin assets with the same identifier', async () => {
11
11
  const workspace = await createWorkspace()
12
12
 
13
- await writeDocument(
14
- join(workspace, '.ai/rules/base.md'),
15
- [
16
- '---',
17
- 'alwaysApply: true',
18
- 'description: 基础约束',
19
- '---',
20
- '始终检查公共边界。'
21
- ].join('\n')
22
- )
23
- await writeDocument(
24
- join(workspace, '.ai/rules/optional.md'),
25
- [
26
- '---',
27
- 'alwaysApply: false',
28
- 'description: 按需参考规则',
29
- '---',
30
- '只有在特定场景才需要展开。'
31
- ].join('\n')
32
- )
33
-
34
- const bundle = await resolveWorkspaceAssetBundle({
35
- cwd: workspace,
36
- useDefaultVibeForgeMcpServer: false
37
- })
38
- const [, options] = await resolvePromptAssetSelection({
39
- bundle,
40
- type: undefined
41
- })
42
-
43
- expect(options.systemPrompt).toContain('# base')
44
- expect(options.systemPrompt).toContain('> 始终检查公共边界。')
45
- expect(options.systemPrompt).toContain('# optional')
46
- expect(options.systemPrompt).toContain('> 适用场景:按需参考规则')
47
- expect(options.systemPrompt).toContain('> 规则文件路径:.ai/rules/optional.md')
48
- expect(options.systemPrompt).toContain('> 仅在任务满足上述场景时,再阅读该规则文件。')
49
- expect(options.systemPrompt).not.toContain('> 只有在特定场景才需要展开。')
50
- })
51
-
52
- it('selects local assets by short name and scoped plugin assets explicitly', async () => {
53
- const workspace = await createWorkspace()
54
-
55
- await installPluginPackage(workspace, '@vibe-forge/plugin-demo', {
56
- 'package.json': JSON.stringify(
57
- {
58
- name: '@vibe-forge/plugin-demo',
59
- version: '1.0.0'
60
- },
61
- null,
62
- 2
63
- ),
64
- 'specs/release/index.md': '---\ndescription: 插件发布流程\n---\n执行插件发布'
65
- })
66
13
  await writeDocument(
67
14
  join(workspace, '.ai/specs/release.md'),
68
15
  '---\ndescription: 项目发布流程\n---\n执行项目发布'
69
16
  )
70
-
71
- const bundle = await resolveWorkspaceAssetBundle({
72
- cwd: workspace,
73
- configs: [{
74
- plugins: [
75
- { id: 'demo', scope: 'demo' }
76
- ]
77
- }, undefined],
78
- useDefaultVibeForgeMcpServer: false
79
- })
80
- const [localData, localOptions] = await resolvePromptAssetSelection({
81
- bundle,
82
- type: 'spec',
83
- name: 'release'
84
- })
85
- const [pluginData, pluginOptions] = await resolvePromptAssetSelection({
86
- bundle,
87
- type: 'spec',
88
- name: 'demo/release'
89
- })
90
- const localSpecId = bundle.specs.find(asset => asset.origin === 'workspace' && asset.name === 'release')?.id
91
- const pluginSpecId = bundle.specs.find(asset => asset.origin === 'plugin' && asset.displayName === 'demo/release')
92
- ?.id
93
-
94
- expect(localData.targetBody).toContain('执行项目发布')
95
- expect(localData.targetBody).not.toContain('执行插件发布')
96
- expect(localOptions.systemPrompt).toContain('项目发布流程')
97
- expect(localOptions.systemPrompt).toContain('demo/release')
98
- expect(localOptions.promptAssetIds).toEqual(expect.arrayContaining([localSpecId]))
99
-
100
- expect(pluginData.targetBody).toContain('执行插件发布')
101
- expect(pluginOptions.systemPrompt).toContain('插件发布流程')
102
- expect(pluginOptions.promptAssetIds).toEqual(expect.arrayContaining([pluginSpecId]))
103
- })
104
-
105
- it('formats rules as markdown headings and blockquotes', async () => {
106
- const workspace = await createWorkspace()
107
-
108
- await writeDocument(
109
- join(workspace, '.ai/rules/required.md'),
110
- [
111
- '---',
112
- 'description: 必须执行的规则',
113
- 'alwaysApply: true',
114
- '---',
115
- '# 标题',
116
- '',
117
- '正文第一行',
118
- '正文第二行'
119
- ].join('\n')
120
- )
121
17
  await writeDocument(
122
- join(workspace, '.ai/rules/summary-only.md'),
123
- [
124
- '---',
125
- 'description: 只展示摘要',
126
- 'alwaysApply: false',
127
- '---',
128
- '不应该出现在引用正文里'
129
- ].join('\n')
18
+ join(workspace, '.ai/plugins/demo/specs/release/index.md'),
19
+ '---\ndescription: 插件发布流程\n---\n执行插件发布'
130
20
  )
131
21
 
132
22
  const bundle = await resolveWorkspaceAssetBundle({
@@ -134,255 +24,21 @@ describe('resolvePromptAssetSelection', () => {
134
24
  configs: [undefined, undefined],
135
25
  useDefaultVibeForgeMcpServer: false
136
26
  })
137
- const [, resolvedOptions] = await resolvePromptAssetSelection({
138
- bundle,
139
- type: undefined
140
- })
141
-
142
- expect(resolvedOptions.systemPrompt).toContain('# required')
143
- expect(resolvedOptions.systemPrompt).toContain('> # 标题')
144
- expect(resolvedOptions.systemPrompt).toContain('> 正文第一行')
145
- expect(resolvedOptions.systemPrompt).toContain('> 正文第二行')
146
- expect(resolvedOptions.systemPrompt).toContain('# summary-only')
147
- expect(resolvedOptions.systemPrompt).toContain('> 适用场景:只展示摘要')
148
- expect(resolvedOptions.systemPrompt).toContain('> 规则文件路径:.ai/rules/summary-only.md')
149
- expect(resolvedOptions.systemPrompt).not.toContain('> 不应该出现在引用正文里')
150
- expect(resolvedOptions.systemPrompt).not.toContain('--------------------')
151
- })
152
-
153
- it('keeps skills as route-only guidance unless the target spec references them', async () => {
154
- const workspace = await createWorkspace()
155
-
156
- await writeDocument(
157
- join(workspace, '.ai/skills/research/SKILL.md'),
158
- [
159
- '---',
160
- 'description: 检索项目信息',
161
- '---',
162
- '先读 README.md'
163
- ].join('\n')
164
- )
165
- await writeDocument(
166
- join(workspace, '.ai/skills/review/SKILL.md'),
167
- [
168
- '---',
169
- 'description: 评审代码改动',
170
- '---',
171
- '检查回归风险'
172
- ].join('\n')
173
- )
174
- await writeDocument(
175
- join(workspace, '.ai/specs/release/index.md'),
176
- [
177
- '---',
178
- 'description: 发布流程',
179
- 'skills:',
180
- ' - research',
181
- '---',
182
- '执行发布'
183
- ].join('\n')
184
- )
185
-
186
- const bundle = await resolveWorkspaceAssetBundle({
187
- cwd: workspace,
188
- useDefaultVibeForgeMcpServer: false
189
- })
190
- const [data, options] = await resolvePromptAssetSelection({
191
- bundle,
192
- type: 'spec',
193
- name: 'release'
194
- })
195
-
196
- expect(data.targetSkills.map(skill => skill.resolvedName ?? skill.attributes.name)).toEqual(['research'])
197
- expect(options.systemPrompt).toContain('项目已加载如下技能模块')
198
- expect(options.systemPrompt).toContain('# research')
199
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/research/SKILL.md')
200
- expect(options.systemPrompt).toContain('<skill-content>')
201
- expect(options.systemPrompt).toContain('先读 README.md')
202
- expect(options.systemPrompt).toContain('# review')
203
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/review/SKILL.md')
204
- expect(options.systemPrompt).toContain('> 默认无需预先加载正文;仅在任务明确需要该技能时,再读取对应技能文件。')
205
- expect(options.systemPrompt).not.toContain('<skill-content>\n检查回归风险\n</skill-content>')
206
- })
207
-
208
- it('keeps skills as route-only guidance in normal mode', async () => {
209
- const workspace = await createWorkspace()
210
-
211
- await writeDocument(
212
- join(workspace, '.ai/skills/research/SKILL.md'),
213
- [
214
- '---',
215
- 'description: 检索项目信息',
216
- '---',
217
- '先读 README.md'
218
- ].join('\n')
219
- )
220
-
221
- const bundle = await resolveWorkspaceAssetBundle({
222
- cwd: workspace,
223
- useDefaultVibeForgeMcpServer: false
224
- })
225
- const [data, options] = await resolvePromptAssetSelection({
226
- bundle,
227
- type: undefined
228
- })
229
-
230
- expect(data.targetSkills).toEqual([])
231
- expect(options.systemPrompt).not.toContain('项目已加载如下技能模块')
232
- expect(options.systemPrompt).toContain('<skills>')
233
- expect(options.systemPrompt).toContain('# research')
234
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/research/SKILL.md')
235
- expect(options.systemPrompt).toContain('> 默认无需预先加载正文;仅在任务明确需要该技能时,再读取对应技能文件。')
236
- expect(options.systemPrompt).not.toContain('<skill-content>')
237
- expect(options.systemPrompt).not.toContain('先读 README.md')
238
- })
239
-
240
- it('keeps spec route guidance without default identity in normal mode', async () => {
241
- const workspace = await createWorkspace()
242
-
243
- await writeDocument(
244
- join(workspace, '.ai/specs/release/index.md'),
245
- [
246
- '---',
247
- 'description: 发布流程',
248
- '---',
249
- '执行发布'
250
- ].join('\n')
251
- )
252
-
253
- const bundle = await resolveWorkspaceAssetBundle({
254
- cwd: workspace,
255
- useDefaultVibeForgeMcpServer: false
256
- })
257
- const [, options] = await resolvePromptAssetSelection({
258
- bundle,
259
- type: undefined
260
- })
261
-
262
- expect(options.systemPrompt).toContain('项目存在如下工作流程')
263
- expect(options.systemPrompt).toContain('流程名称:release')
264
- expect(options.systemPrompt).not.toContain('项目推进管理大师')
265
- })
266
-
267
- it('injects spec identity guidance when a spec is actively selected', async () => {
268
- const workspace = await createWorkspace()
269
-
270
- await writeDocument(
271
- join(workspace, '.ai/specs/release/index.md'),
272
- [
273
- '---',
274
- 'description: 发布流程',
275
- '---',
276
- '执行发布'
277
- ].join('\n')
278
- )
279
-
280
- const bundle = await resolveWorkspaceAssetBundle({
281
- cwd: workspace,
282
- useDefaultVibeForgeMcpServer: false
283
- })
284
- const [, options] = await resolvePromptAssetSelection({
27
+ const [data, resolvedOptions] = await resolvePromptAssetSelection({
285
28
  bundle,
286
29
  type: 'spec',
287
30
  name: 'release'
288
31
  })
289
32
 
290
- expect(options.systemPrompt).toContain('项目推进管理大师')
291
- expect(options.systemPrompt).toContain('永远不要单独完成代码开发工作')
292
- expect(options.systemPrompt).toContain('流程名称:release')
293
- })
294
-
295
- it('embeds referenced skills for entity mode and removes them from route guidance', async () => {
296
- const workspace = await createWorkspace()
297
-
298
- await writeDocument(
299
- join(workspace, '.ai/skills/research/SKILL.md'),
300
- [
301
- '---',
302
- 'description: 检索项目信息',
303
- '---',
304
- '先读 README.md'
305
- ].join('\n')
306
- )
307
- await writeDocument(
308
- join(workspace, '.ai/skills/review/SKILL.md'),
309
- [
310
- '---',
311
- 'description: 评审代码改动',
312
- '---',
313
- '检查回归风险'
314
- ].join('\n')
315
- )
316
- await writeDocument(
317
- join(workspace, '.ai/entities/reviewer/README.md'),
318
- [
319
- '---',
320
- 'description: 代码评审实体',
321
- 'skills:',
322
- ' - review',
323
- '---',
324
- '负责代码评审'
325
- ].join('\n')
33
+ expect(data.targetBody).toContain('执行项目发布')
34
+ expect(data.targetBody).not.toContain('执行插件发布')
35
+ expect(data.specs).toHaveLength(1)
36
+ expect(resolvedOptions.systemPrompt).toContain('项目发布流程')
37
+ expect(resolvedOptions.systemPrompt).not.toContain('插件发布流程')
38
+ expect(resolvedOptions.promptAssetIds).toEqual(
39
+ expect.arrayContaining([
40
+ 'spec:.ai/specs/release.md'
41
+ ])
326
42
  )
327
-
328
- const bundle = await resolveWorkspaceAssetBundle({
329
- cwd: workspace,
330
- useDefaultVibeForgeMcpServer: false
331
- })
332
- const [data, options] = await resolvePromptAssetSelection({
333
- bundle,
334
- type: 'entity',
335
- name: 'reviewer'
336
- })
337
-
338
- expect(data.targetSkills.map(skill => skill.resolvedName ?? skill.attributes.name)).toEqual(['review'])
339
- expect(options.systemPrompt).toContain('项目已加载如下技能模块')
340
- expect(options.systemPrompt).toContain('# review')
341
- expect(options.systemPrompt).toContain('<skill-content>')
342
- expect(options.systemPrompt).toContain('检查回归风险')
343
- expect(options.systemPrompt).not.toContain('<skills>\n# review')
344
- expect(options.systemPrompt).toContain('<skills>')
345
- expect(options.systemPrompt).toContain('# research')
346
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/research/SKILL.md')
347
- expect(options.systemPrompt).not.toContain('<skill-content>\n先读 README.md\n</skill-content>')
348
- })
349
-
350
- it('does not preload all skills when the target entity omits skill references', async () => {
351
- const workspace = await createWorkspace()
352
-
353
- await writeDocument(
354
- join(workspace, '.ai/skills/research/SKILL.md'),
355
- [
356
- '---',
357
- 'description: 检索项目信息',
358
- '---',
359
- '先读 README.md'
360
- ].join('\n')
361
- )
362
- await writeDocument(
363
- join(workspace, '.ai/entities/reviewer/README.md'),
364
- [
365
- '---',
366
- 'description: 代码评审实体',
367
- '---',
368
- '负责代码评审'
369
- ].join('\n')
370
- )
371
-
372
- const bundle = await resolveWorkspaceAssetBundle({
373
- cwd: workspace,
374
- useDefaultVibeForgeMcpServer: false
375
- })
376
- const [data, options] = await resolvePromptAssetSelection({
377
- bundle,
378
- type: 'entity',
379
- name: 'reviewer'
380
- })
381
-
382
- expect(data.targetSkills).toEqual([])
383
- expect(options.systemPrompt).not.toContain('项目已加载如下技能模块')
384
- expect(options.systemPrompt).toContain('# research')
385
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/research/SKILL.md')
386
- expect(options.systemPrompt).not.toContain('先读 README.md')
387
43
  })
388
44
  })