@vibe-forge/workspace-assets 0.9.0 → 0.9.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/AGENTS.md CHANGED
@@ -15,6 +15,8 @@
15
15
  - `resolveWorkspaceAssetBundle()`
16
16
  - `src/prompt-selection.ts`
17
17
  - `resolvePromptAssetSelection()`
18
+ - `src/prompt-builders.ts`
19
+ - rules / skills / specs / entities prompt 文本渲染
18
20
  - `src/adapter-asset-plan.ts`
19
21
  - `buildAdapterAssetPlan()`
20
22
  - `__tests__/bundle.spec.ts`
@@ -28,6 +30,7 @@
28
30
  - 本包负责:
29
31
  - workspace asset bundle 组装
30
32
  - prompt asset 选择
33
+ - prompt 文本拼装
31
34
  - adapter asset plan 组装
32
35
  - 本包不负责:
33
36
  - 定义文档发现与解析
@@ -37,7 +40,7 @@
37
40
  ## 维护约定
38
41
 
39
42
  - 只维护 workspace asset 领域逻辑;定义文档读取留在 `@vibe-forge/definition-loader`,cache 留在 `@vibe-forge/utils`。
40
- - 文档路径规范化与命名规则复用 `@vibe-forge/utils/document-path`,不要在本包重复维护。
43
+ - 通用路径处理复用 `@vibe-forge/utils`;definition 名称/标识/摘要与 remote rule 投影复用 `@vibe-forge/definition-core`;prompt builder 仍留在本包内维护。
41
44
  - 共享 contract 继续依赖 `@vibe-forge/types`,不要把 task / hooks / mcp 逻辑反向塞进来。
42
45
  - 新增 asset 类型、prompt 选择规则或 adapter 投影时,优先补对应职责下的 spec 文件,不要继续把单测堆回一个综合 spec。
43
46
  - 影响 bundle / prompt selection / adapter plan 整体投影时,同步检查 `workspace-assets-rich.snapshot.json`;必要时用 `pnpm -C packages/workspace-assets test -- --update` 更新快照。
@@ -467,7 +467,7 @@
467
467
  ]
468
468
  },
469
469
  "options": {
470
- "systemPrompt": "<system-prompt>\n项目系统规则如下:\n# review\n\n> 必须检查发布改动的回归风险。\n\n# demo/security\n\n> 适用场景:插件安全规则\n> 规则文件路径:node_modules/@vibe-forge/plugin-demo/rules/security.md\n> 仅在任务满足上述场景时,再阅读该规则文件。\n</system-prompt>\n\n\n<system-prompt>\n项目已加载如下技能模块:\n# research\n\n> 技能介绍:检索资料\n> 技能文件路径:.ai/skills/research/SKILL.md\n> 资源内容中的相对路径相对该技能文件所在目录解析。\n\n<skill-content>\n先阅读 README.md,再补充结论。\n</skill-content>\n</system-prompt>\n\n\n<system-prompt>\n项目存在如下实体:\n - architect:负责拆解方案的实体\n\n解决用户问题时,需根据用户需求可以通过 run-tasks 工具指定为实体后,自行调度多个不同类型的实体来完成工作。\n</system-prompt>\n\n\n\n\n<system-prompt>\n你是一个专业的项目推进管理大师,能够熟练指导其他实体来为你的目标工作。对你的预期是:\n\n- 永远不要单独完成代码开发工作\n- 必须要协调其他的开发人员来完成任务\n- 必须让他们按照目标进行完成,不要偏离目标,检查他们任务完成后的汇报内容是否符合要求\n\n根据用户需要以及实际的开发目标来决定使用不同的工作流程,调用 `load-spec` mcp tool 完成工作流程的加载。\n- 根据实际需求传入标识,这不是路径,只能使用工具进行加载\n- 通过参数的描述以及实际应用场景决定怎么传入参数\n项目存在如下工作流程:\n- 流程名称:release\n - 介绍:正式发布流程\n - 标识:release\n - 参数:\n - 无\n\n- 流程名称:demo/release\n - 介绍:插件发布流程\n - 标识:demo/release\n - 参数:\n - 无\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 `vibe-forge.StartTasks` as needed and have them coordinate multiple entity types to complete the work; use `GetTaskInfo` and `wait` to track progress when needed.\n</system-prompt>\n\n\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 resolve the matching definition by workflow identifier.\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",
@@ -11,17 +11,25 @@ describe('buildAdapterAssetPlan', () => {
11
11
  const workspace = await createWorkspace()
12
12
 
13
13
  await installPluginPackage(workspace, '@vibe-forge/plugin-logger', {
14
- 'package.json': JSON.stringify({
15
- name: '@vibe-forge/plugin-logger',
16
- version: '1.0.0'
17
- }, null, 2),
14
+ 'package.json': JSON.stringify(
15
+ {
16
+ name: '@vibe-forge/plugin-logger',
17
+ version: '1.0.0'
18
+ },
19
+ null,
20
+ 2
21
+ ),
18
22
  'hooks.js': 'module.exports = {}\n'
19
23
  })
20
24
  await installPluginPackage(workspace, '@vibe-forge/plugin-demo', {
21
- 'package.json': JSON.stringify({
22
- name: '@vibe-forge/plugin-demo',
23
- version: '1.0.0'
24
- }, null, 2),
25
+ 'package.json': JSON.stringify(
26
+ {
27
+ name: '@vibe-forge/plugin-demo',
28
+ version: '1.0.0'
29
+ },
30
+ null,
31
+ 2
32
+ ),
25
33
  'opencode/commands/review.md': '# review\n'
26
34
  })
27
35
  await writeDocument(
@@ -118,10 +126,14 @@ describe('buildAdapterAssetPlan', () => {
118
126
  const workspace = await createWorkspace()
119
127
 
120
128
  await installPluginPackage(workspace, '@vibe-forge/plugin-demo', {
121
- 'package.json': JSON.stringify({
122
- name: '@vibe-forge/plugin-demo',
123
- version: '1.0.0'
124
- }, null, 2),
129
+ 'package.json': JSON.stringify(
130
+ {
131
+ name: '@vibe-forge/plugin-demo',
132
+ version: '1.0.0'
133
+ },
134
+ null,
135
+ 2
136
+ ),
125
137
  'opencode/commands/review.md': '# review\n'
126
138
  })
127
139
  await writeDocument(
@@ -11,20 +11,28 @@ describe('resolveWorkspaceAssetBundle', () => {
11
11
  const workspace = await createWorkspace()
12
12
 
13
13
  await installPluginPackage(workspace, '@vibe-forge/plugin-demo', {
14
- 'package.json': JSON.stringify({
15
- name: '@vibe-forge/plugin-demo',
16
- version: '1.0.0'
17
- }, null, 2),
14
+ 'package.json': JSON.stringify(
15
+ {
16
+ name: '@vibe-forge/plugin-demo',
17
+ version: '1.0.0'
18
+ },
19
+ null,
20
+ 2
21
+ ),
18
22
  'skills/research/SKILL.md': '---\ndescription: 检索资料\n---\n阅读 README.md',
19
23
  'rules/review.md': '---\ndescription: 评审规则\n---\n必须检查风险',
20
24
  'mcp/browser.json': JSON.stringify({ command: 'npx', args: ['browser-server'] }, null, 2),
21
25
  'opencode/commands/review.md': '# review\n'
22
26
  })
23
27
  await installPluginPackage(workspace, '@vibe-forge/plugin-logger', {
24
- 'package.json': JSON.stringify({
25
- name: '@vibe-forge/plugin-logger',
26
- version: '1.0.0'
27
- }, null, 2),
28
+ 'package.json': JSON.stringify(
29
+ {
30
+ name: '@vibe-forge/plugin-logger',
31
+ version: '1.0.0'
32
+ },
33
+ null,
34
+ 2
35
+ ),
28
36
  'hooks.js': 'module.exports = {}\n'
29
37
  })
30
38
 
@@ -87,17 +95,25 @@ describe('resolveWorkspaceAssetBundle', () => {
87
95
  const workspace = await createWorkspace()
88
96
 
89
97
  await installPluginPackage(workspace, '@vibe-forge/plugin-demo', {
90
- 'package.json': JSON.stringify({
91
- name: '@vibe-forge/plugin-demo',
92
- version: '1.0.0'
93
- }, null, 2),
98
+ 'package.json': JSON.stringify(
99
+ {
100
+ name: '@vibe-forge/plugin-demo',
101
+ version: '1.0.0'
102
+ },
103
+ null,
104
+ 2
105
+ ),
94
106
  'skills/research/SKILL.md': '---\ndescription: 检索资料\n---\n阅读 README.md'
95
107
  })
96
108
  await installPluginPackage(workspace, '@vibe-forge/plugin-bundle', {
97
- 'package.json': JSON.stringify({
98
- name: '@vibe-forge/plugin-bundle',
99
- version: '1.0.0'
100
- }, null, 2),
109
+ 'package.json': JSON.stringify(
110
+ {
111
+ name: '@vibe-forge/plugin-bundle',
112
+ version: '1.0.0'
113
+ },
114
+ null,
115
+ 2
116
+ ),
101
117
  'index.js': [
102
118
  'module.exports = {',
103
119
  ' __vibeForgePluginManifest: true,',
@@ -112,10 +128,14 @@ describe('resolveWorkspaceAssetBundle', () => {
112
128
  ].join('\n')
113
129
  })
114
130
  await installPluginPackage(workspace, '@vibe-forge/plugin-review', {
115
- 'package.json': JSON.stringify({
116
- name: '@vibe-forge/plugin-review',
117
- version: '1.0.0'
118
- }, null, 2),
131
+ 'package.json': JSON.stringify(
132
+ {
133
+ name: '@vibe-forge/plugin-review',
134
+ version: '1.0.0'
135
+ },
136
+ null,
137
+ 2
138
+ ),
119
139
  'skills/audit/SKILL.md': '---\ndescription: 代码审计\n---\n检查 child plugin 是否启用'
120
140
  })
121
141
 
@@ -144,10 +164,14 @@ describe('resolveWorkspaceAssetBundle', () => {
144
164
  const workspace = await createWorkspace()
145
165
 
146
166
  await installPluginPackage(workspace, '@vibe-forge/plugin-logger', {
147
- 'package.json': JSON.stringify({
148
- name: '@vibe-forge/plugin-logger',
149
- version: '1.0.0'
150
- }, null, 2),
167
+ 'package.json': JSON.stringify(
168
+ {
169
+ name: '@vibe-forge/plugin-logger',
170
+ version: '1.0.0'
171
+ },
172
+ null,
173
+ 2
174
+ ),
151
175
  'hooks.js': 'module.exports = {}\n'
152
176
  })
153
177
 
@@ -179,13 +203,17 @@ describe('resolveWorkspaceAssetBundle', () => {
179
203
  const workspace = await createWorkspace()
180
204
 
181
205
  await installPluginPackage(workspace, '@vibe-forge/plugin-bad-manifest', {
182
- 'package.json': JSON.stringify({
183
- name: '@vibe-forge/plugin-bad-manifest',
184
- version: '1.0.0',
185
- exports: {
186
- '.': './index.js'
187
- }
188
- }, null, 2),
206
+ 'package.json': JSON.stringify(
207
+ {
208
+ name: '@vibe-forge/plugin-bad-manifest',
209
+ version: '1.0.0',
210
+ exports: {
211
+ '.': './index.js'
212
+ }
213
+ },
214
+ null,
215
+ 2
216
+ ),
189
217
  'index.js': [
190
218
  'module.exports = {',
191
219
  ' __vibeForgePluginManifest: true,',
@@ -0,0 +1,198 @@
1
+ import { join } from 'node:path'
2
+
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import {
6
+ generateEntitiesRoutePrompt,
7
+ generateRulesPrompt,
8
+ generateSkillsPrompt,
9
+ generateSkillsRoutePrompt,
10
+ generateSpecRoutePrompt
11
+ } from '#~/prompt-builders.js'
12
+
13
+ describe('workspace asset prompt builders', () => {
14
+ it('builds skill prompts with stable names, descriptions, and relative paths', () => {
15
+ const cwd = '/tmp/project'
16
+
17
+ const prompt = generateSkillsPrompt(cwd, [
18
+ {
19
+ path: join(cwd, '.ai/skills/research/SKILL.md'),
20
+ body: '阅读 README.md\n',
21
+ attributes: {
22
+ description: '检索项目信息'
23
+ }
24
+ }
25
+ ])
26
+
27
+ expect(prompt).toContain('The following skill modules are loaded for the project')
28
+ expect(prompt).toContain('# research')
29
+ expect(prompt).toContain('> Skill description: 检索项目信息')
30
+ expect(prompt).toContain('> Skill file path: .ai/skills/research/SKILL.md')
31
+ expect(prompt).toContain('<skill-content>')
32
+ expect(prompt).not.toContain('/tmp/project/.ai/skills/research/SKILL.md')
33
+ })
34
+
35
+ it('builds rules prompts with embedded always rules and summary-only optional rules', () => {
36
+ const cwd = '/tmp/project'
37
+
38
+ const prompt = generateRulesPrompt(cwd, [
39
+ {
40
+ path: join(cwd, '.ai/rules/base.md'),
41
+ body: '始终检查公共边界。',
42
+ attributes: {
43
+ alwaysApply: true
44
+ }
45
+ },
46
+ {
47
+ path: join(cwd, '.ai/rules/optional.md'),
48
+ body: '仅在需要时展开。',
49
+ attributes: {
50
+ description: '按需规则',
51
+ alwaysApply: false
52
+ }
53
+ }
54
+ ])
55
+
56
+ expect(prompt).toContain('# base')
57
+ expect(prompt).toContain('> 始终检查公共边界。')
58
+ expect(prompt).toContain('> Use when: 按需规则')
59
+ expect(prompt).toContain('> Rule file path: .ai/rules/optional.md')
60
+ expect(prompt).not.toContain('> 仅在需要时展开。')
61
+ })
62
+
63
+ it('builds rule prompts with markdown headings and blockquotes', () => {
64
+ const cwd = '/tmp/project'
65
+
66
+ const prompt = generateRulesPrompt(cwd, [
67
+ {
68
+ path: join(cwd, '.ai/rules/required.md'),
69
+ body: '# 标题\n\n正文',
70
+ attributes: {
71
+ alwaysApply: true
72
+ }
73
+ },
74
+ {
75
+ path: join(cwd, '.ai/rules/summary-only.md'),
76
+ body: '不应该内联',
77
+ attributes: {
78
+ description: '只展示摘要',
79
+ alwaysApply: false
80
+ }
81
+ }
82
+ ])
83
+
84
+ expect(prompt).toContain('# required')
85
+ expect(prompt).toContain('> # 标题')
86
+ expect(prompt).toContain('> 正文')
87
+ expect(prompt).toContain('# summary-only')
88
+ expect(prompt).toContain('> Use when: 只展示摘要')
89
+ expect(prompt).toContain('> Rule file path: .ai/rules/summary-only.md')
90
+ expect(prompt).not.toContain('> 不应该内联')
91
+ expect(prompt).not.toContain('--------------------')
92
+ })
93
+
94
+ it('builds spec route prompts with logical identifiers and active identity guidance', () => {
95
+ const cwd = '/tmp/project'
96
+
97
+ const prompt = generateSpecRoutePrompt([
98
+ {
99
+ path: join(cwd, '.ai/specs/release/index.md'),
100
+ body: '发布流程',
101
+ attributes: {
102
+ params: [
103
+ {
104
+ name: 'version',
105
+ description: '版本号'
106
+ }
107
+ ]
108
+ }
109
+ }
110
+ ], { active: true })
111
+
112
+ expect(prompt).toContain('professional project execution manager')
113
+ expect(prompt).toContain('Workflow name: release')
114
+ expect(prompt).toContain('Identifier: release')
115
+ expect(prompt).toContain(' - version: 版本号')
116
+ })
117
+
118
+ it('builds spec route prompts without exposing file paths', () => {
119
+ const cwd = '/tmp/project'
120
+
121
+ const prompt = generateSpecRoutePrompt([
122
+ {
123
+ path: join(cwd, '.ai/specs/release/index.md'),
124
+ body: '发布流程\n执行发布任务',
125
+ attributes: {
126
+ params: [
127
+ {
128
+ name: 'version',
129
+ description: '版本号'
130
+ }
131
+ ]
132
+ }
133
+ },
134
+ {
135
+ path: join(cwd, '.ai/specs/internal.md'),
136
+ body: '内部流程',
137
+ attributes: {
138
+ always: false
139
+ }
140
+ }
141
+ ])
142
+
143
+ expect(prompt).toContain('Workflow name: release')
144
+ expect(prompt).toContain('Description: 发布流程')
145
+ expect(prompt).toContain('Identifier: release')
146
+ expect(prompt).toContain(' - version: 版本号')
147
+ expect(prompt).not.toContain('professional project execution manager')
148
+ expect(prompt).not.toContain('.ai/specs/release/index.md')
149
+ expect(prompt).not.toContain('internal')
150
+ })
151
+
152
+ it('builds entity routes from summaries instead of full bodies', () => {
153
+ const cwd = '/tmp/project'
154
+
155
+ const prompt = generateEntitiesRoutePrompt([
156
+ {
157
+ path: join(cwd, '.ai/entities/reviewer/README.md'),
158
+ body: '负责代码审查\n需要关注变更风险',
159
+ attributes: {}
160
+ },
161
+ {
162
+ path: join(cwd, '.ai/entities/hidden.md'),
163
+ body: '不应暴露',
164
+ attributes: {
165
+ name: 'hidden',
166
+ always: false
167
+ }
168
+ }
169
+ ])
170
+
171
+ expect(prompt).toContain('reviewer: 负责代码审查')
172
+ expect(prompt).not.toContain('需要关注变更风险')
173
+ expect(prompt).not.toContain('hidden')
174
+ })
175
+
176
+ it('builds skill route prompts without preloading content', () => {
177
+ const cwd = '/tmp/project'
178
+
179
+ const prompt = generateSkillsRoutePrompt(cwd, [
180
+ {
181
+ path: join(cwd, '.ai/skills/research/SKILL.md'),
182
+ body: '阅读 README.md\n',
183
+ attributes: {
184
+ description: '检索项目信息'
185
+ }
186
+ }
187
+ ])
188
+
189
+ expect(prompt).toContain('# research')
190
+ expect(prompt).toContain('> Skill description: 检索项目信息')
191
+ expect(prompt).toContain('> Skill file path: .ai/skills/research/SKILL.md')
192
+ expect(prompt).toContain(
193
+ '> Do not preload the body by default; read the corresponding skill file only when the task clearly requires it.'
194
+ )
195
+ expect(prompt).not.toContain('<skill-content>')
196
+ expect(prompt).not.toContain('阅读 README.md')
197
+ })
198
+ })
@@ -43,9 +43,9 @@ describe('resolvePromptAssetSelection', () => {
43
43
  expect(options.systemPrompt).toContain('# base')
44
44
  expect(options.systemPrompt).toContain('> 始终检查公共边界。')
45
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('> 仅在任务满足上述场景时,再阅读该规则文件。')
46
+ expect(options.systemPrompt).toContain('> Use when: 按需参考规则')
47
+ expect(options.systemPrompt).toContain('> Rule file path: .ai/rules/optional.md')
48
+ expect(options.systemPrompt).toContain('> Only read this rule file when the task matches the scenario above.')
49
49
  expect(options.systemPrompt).not.toContain('> 只有在特定场景才需要展开。')
50
50
  })
51
51
 
@@ -144,8 +144,8 @@ describe('resolvePromptAssetSelection', () => {
144
144
  expect(resolvedOptions.systemPrompt).toContain('> 正文第一行')
145
145
  expect(resolvedOptions.systemPrompt).toContain('> 正文第二行')
146
146
  expect(resolvedOptions.systemPrompt).toContain('# summary-only')
147
- expect(resolvedOptions.systemPrompt).toContain('> 适用场景:只展示摘要')
148
- expect(resolvedOptions.systemPrompt).toContain('> 规则文件路径:.ai/rules/summary-only.md')
147
+ expect(resolvedOptions.systemPrompt).toContain('> Use when: 只展示摘要')
148
+ expect(resolvedOptions.systemPrompt).toContain('> Rule file path: .ai/rules/summary-only.md')
149
149
  expect(resolvedOptions.systemPrompt).not.toContain('> 不应该出现在引用正文里')
150
150
  expect(resolvedOptions.systemPrompt).not.toContain('--------------------')
151
151
  })
@@ -194,14 +194,16 @@ describe('resolvePromptAssetSelection', () => {
194
194
  })
195
195
 
196
196
  expect(data.targetSkills.map(skill => skill.resolvedName ?? skill.attributes.name)).toEqual(['research'])
197
- expect(options.systemPrompt).toContain('项目已加载如下技能模块')
197
+ expect(options.systemPrompt).toContain('The following skill modules are loaded for the project')
198
198
  expect(options.systemPrompt).toContain('# research')
199
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/research/SKILL.md')
199
+ expect(options.systemPrompt).toContain('> Skill file path: .ai/skills/research/SKILL.md')
200
200
  expect(options.systemPrompt).toContain('<skill-content>')
201
201
  expect(options.systemPrompt).toContain('先读 README.md')
202
202
  expect(options.systemPrompt).toContain('# review')
203
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/review/SKILL.md')
204
- expect(options.systemPrompt).toContain('> 默认无需预先加载正文;仅在任务明确需要该技能时,再读取对应技能文件。')
203
+ expect(options.systemPrompt).toContain('> Skill file path: .ai/skills/review/SKILL.md')
204
+ expect(options.systemPrompt).toContain(
205
+ '> Do not preload the body by default; read the corresponding skill file only when the task clearly requires it.'
206
+ )
205
207
  expect(options.systemPrompt).not.toContain('<skill-content>\n检查回归风险\n</skill-content>')
206
208
  })
207
209
 
@@ -228,11 +230,13 @@ describe('resolvePromptAssetSelection', () => {
228
230
  })
229
231
 
230
232
  expect(data.targetSkills).toEqual([])
231
- expect(options.systemPrompt).not.toContain('项目已加载如下技能模块')
233
+ expect(options.systemPrompt).not.toContain('The following skill modules are loaded for the project')
232
234
  expect(options.systemPrompt).toContain('<skills>')
233
235
  expect(options.systemPrompt).toContain('# research')
234
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/research/SKILL.md')
235
- expect(options.systemPrompt).toContain('> 默认无需预先加载正文;仅在任务明确需要该技能时,再读取对应技能文件。')
236
+ expect(options.systemPrompt).toContain('> Skill file path: .ai/skills/research/SKILL.md')
237
+ expect(options.systemPrompt).toContain(
238
+ '> Do not preload the body by default; read the corresponding skill file only when the task clearly requires it.'
239
+ )
236
240
  expect(options.systemPrompt).not.toContain('<skill-content>')
237
241
  expect(options.systemPrompt).not.toContain('先读 README.md')
238
242
  })
@@ -259,9 +263,9 @@ describe('resolvePromptAssetSelection', () => {
259
263
  type: undefined
260
264
  })
261
265
 
262
- expect(options.systemPrompt).toContain('项目存在如下工作流程')
263
- expect(options.systemPrompt).toContain('流程名称:release')
264
- expect(options.systemPrompt).not.toContain('项目推进管理大师')
266
+ expect(options.systemPrompt).toContain('The project includes the following workflows')
267
+ expect(options.systemPrompt).toContain('Workflow name: release')
268
+ expect(options.systemPrompt).not.toContain('professional project execution manager')
265
269
  })
266
270
 
267
271
  it('injects spec identity guidance when a spec is actively selected', async () => {
@@ -287,9 +291,9 @@ describe('resolvePromptAssetSelection', () => {
287
291
  name: 'release'
288
292
  })
289
293
 
290
- expect(options.systemPrompt).toContain('项目推进管理大师')
291
- expect(options.systemPrompt).toContain('永远不要单独完成代码开发工作')
292
- expect(options.systemPrompt).toContain('流程名称:release')
294
+ expect(options.systemPrompt).toContain('professional project execution manager')
295
+ expect(options.systemPrompt).toContain('Never complete code development work alone')
296
+ expect(options.systemPrompt).toContain('Workflow name: release')
293
297
  })
294
298
 
295
299
  it('embeds referenced skills for entity mode and removes them from route guidance', async () => {
@@ -336,14 +340,14 @@ describe('resolvePromptAssetSelection', () => {
336
340
  })
337
341
 
338
342
  expect(data.targetSkills.map(skill => skill.resolvedName ?? skill.attributes.name)).toEqual(['review'])
339
- expect(options.systemPrompt).toContain('项目已加载如下技能模块')
343
+ expect(options.systemPrompt).toContain('The following skill modules are loaded for the project')
340
344
  expect(options.systemPrompt).toContain('# review')
341
345
  expect(options.systemPrompt).toContain('<skill-content>')
342
346
  expect(options.systemPrompt).toContain('检查回归风险')
343
347
  expect(options.systemPrompt).not.toContain('<skills>\n# review')
344
348
  expect(options.systemPrompt).toContain('<skills>')
345
349
  expect(options.systemPrompt).toContain('# research')
346
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/research/SKILL.md')
350
+ expect(options.systemPrompt).toContain('> Skill file path: .ai/skills/research/SKILL.md')
347
351
  expect(options.systemPrompt).not.toContain('<skill-content>\n先读 README.md\n</skill-content>')
348
352
  })
349
353
 
@@ -380,9 +384,9 @@ describe('resolvePromptAssetSelection', () => {
380
384
  })
381
385
 
382
386
  expect(data.targetSkills).toEqual([])
383
- expect(options.systemPrompt).not.toContain('项目已加载如下技能模块')
387
+ expect(options.systemPrompt).not.toContain('The following skill modules are loaded for the project')
384
388
  expect(options.systemPrompt).toContain('# research')
385
- expect(options.systemPrompt).toContain('> 技能文件路径:.ai/skills/research/SKILL.md')
389
+ expect(options.systemPrompt).toContain('> Skill file path: .ai/skills/research/SKILL.md')
386
390
  expect(options.systemPrompt).not.toContain('先读 README.md')
387
391
  })
388
392
  })
@@ -1,5 +1,6 @@
1
1
  import process from 'node:process'
2
2
 
3
+ import { resolveDocumentName, resolveSpecIdentifier } from '@vibe-forge/definition-core'
3
4
  import type {
4
5
  AdapterAssetPlan,
5
6
  AssetDiagnostic,
@@ -14,7 +15,6 @@ import type {
14
15
  WorkspaceAssetBundle,
15
16
  WorkspaceAssetKind
16
17
  } from '@vibe-forge/types'
17
- import { resolveDocumentName, resolveSpecIdentifier } from '@vibe-forge/utils'
18
18
 
19
19
  const sortStrings = (values: string[]) => [...values].sort((left, right) => left.localeCompare(right))
20
20
 
@@ -94,7 +94,9 @@ const buildSnapshotAssetId = (
94
94
  asset: WorkspaceAsset,
95
95
  cwd: string
96
96
  ) => (
97
- `${asset.kind}:${asset.origin}:${asset.instancePath ?? 'workspace'}:${asset.displayName}:${sanitizeValue(asset.sourcePath, cwd)}`
97
+ `${asset.kind}:${asset.origin}:${asset.instancePath ?? 'workspace'}:${asset.displayName}:${
98
+ sanitizeValue(asset.sourcePath, cwd)
99
+ }`
98
100
  )
99
101
 
100
102
  const summarizeBaseAsset = (
@@ -23,9 +23,11 @@ export const installPluginPackage = async (
23
23
  files: Record<string, string>
24
24
  ) => {
25
25
  const packageDir = join(workspace, 'node_modules', ...packageName.split('/'))
26
- await Promise.all(Object.entries(files).map(async ([relativePath, content]) => {
27
- await writeDocument(join(packageDir, relativePath), content)
28
- }))
26
+ await Promise.all(
27
+ Object.entries(files).map(async ([relativePath, content]) => {
28
+ await writeDocument(join(packageDir, relativePath), content)
29
+ })
30
+ )
29
31
  }
30
32
 
31
33
  afterEach(async () => {
@@ -56,10 +56,14 @@ describe('workspace assets snapshots', () => {
56
56
  }
57
57
 
58
58
  await installPluginPackage(workspace, '@vibe-forge/plugin-demo', {
59
- 'package.json': JSON.stringify({
60
- name: '@vibe-forge/plugin-demo',
61
- version: '1.0.0'
62
- }, null, 2),
59
+ 'package.json': JSON.stringify(
60
+ {
61
+ name: '@vibe-forge/plugin-demo',
62
+ version: '1.0.0'
63
+ },
64
+ null,
65
+ 2
66
+ ),
63
67
  'hooks.js': 'module.exports = {}\n',
64
68
  'rules/security.md': [
65
69
  '---',
@@ -94,17 +98,25 @@ describe('workspace assets snapshots', () => {
94
98
  'opencode/plugins/demo-plugin.js': 'export default {}\n'
95
99
  })
96
100
  await installPluginPackage(workspace, '@vibe-forge/plugin-logger', {
97
- 'package.json': JSON.stringify({
98
- name: '@vibe-forge/plugin-logger',
99
- version: '1.0.0'
100
- }, null, 2),
101
+ 'package.json': JSON.stringify(
102
+ {
103
+ name: '@vibe-forge/plugin-logger',
104
+ version: '1.0.0'
105
+ },
106
+ null,
107
+ 2
108
+ ),
101
109
  'hooks.js': 'module.exports = {}\n'
102
110
  })
103
111
  await installPluginPackage(workspace, '@vibe-forge/plugin-telemetry', {
104
- 'package.json': JSON.stringify({
105
- name: '@vibe-forge/plugin-telemetry',
106
- version: '1.0.0'
107
- }, null, 2),
112
+ 'package.json': JSON.stringify(
113
+ {
114
+ name: '@vibe-forge/plugin-telemetry',
115
+ version: '1.0.0'
116
+ },
117
+ null,
118
+ 2
119
+ ),
108
120
  'hooks.js': 'module.exports = {}\n'
109
121
  })
110
122
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-forge/workspace-assets",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Workspace asset resolution and adapter asset planning for Vibe Forge",
5
5
  "imports": {
6
6
  "#~/*.js": {
@@ -29,9 +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": "^0.9.0",
32
33
  "@vibe-forge/types": "^0.9.0",
33
- "@vibe-forge/utils": "^0.9.0",
34
- "@vibe-forge/config": "^0.9.0"
34
+ "@vibe-forge/utils": "^0.9.1",
35
+ "@vibe-forge/definition-core": "^0.9.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/js-yaml": "^4.0.9"