@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 +4 -1
- package/__tests__/__snapshots__/workspace-assets-rich.snapshot.json +1 -1
- package/__tests__/adapter-asset-plan.spec.ts +24 -12
- package/__tests__/bundle.spec.ts +59 -31
- package/__tests__/prompt-builders.spec.ts +198 -0
- package/__tests__/prompt-selection.spec.ts +26 -22
- package/__tests__/snapshot.ts +4 -2
- package/__tests__/test-helpers.ts +5 -3
- package/__tests__/workspace-assets.snapshot.spec.ts +24 -12
- package/package.json +4 -3
- package/src/adapter-asset-plan.ts +155 -0
- package/src/bundle-internal.ts +548 -0
- package/src/bundle.ts +29 -0
- package/src/index.ts +3 -1368
- package/src/prompt-builders.ts +184 -0
- package/src/prompt-selection.ts +191 -0
- package/src/selection-internal.ts +271 -0
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
|
-
-
|
|
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
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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(
|
package/__tests__/bundle.spec.ts
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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('>
|
|
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('>
|
|
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('>
|
|
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('>
|
|
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('>
|
|
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('
|
|
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('
|
|
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('>
|
|
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('>
|
|
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
|
})
|
package/__tests__/snapshot.ts
CHANGED
|
@@ -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}:${
|
|
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(
|
|
27
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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.
|
|
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.
|
|
34
|
-
"@vibe-forge/
|
|
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"
|