@vibe-forge/workspace-assets 0.9.1-alpha.0 → 0.9.2-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.
package/src/bundle.ts CHANGED
@@ -1,178 +1,29 @@
1
- import process from 'node:process'
1
+ import type { Config, PluginConfig, WorkspaceAssetBundle } from '@vibe-forge/types'
2
2
 
3
- import {
4
- DEFAULT_VIBE_FORGE_MCP_SERVER_NAME,
5
- buildConfigJsonVariables,
6
- loadConfig,
7
- resolveDefaultVibeForgeMcpServerConfig
8
- } from '@vibe-forge/config'
9
- import { DefinitionLoader } from '@vibe-forge/definition-loader'
10
- import type { Config, WorkspaceAsset, WorkspaceAssetBundle } from '@vibe-forge/types'
11
- import { resolveDocumentName, resolveSpecIdentifier } from '@vibe-forge/utils'
12
-
13
- import {
14
- createDocumentAsset,
15
- dedupeDocumentAssets,
16
- dedupeDocumentAssetsByIdentifier,
17
- resolveRuleIdentifier,
18
- resolveSkillIdentifier
19
- } from './document-assets'
20
- import { mergeRecord, uniqueValues } from './helpers'
21
- import {
22
- createClaudeNativePluginAssets,
23
- createHookPluginAssets,
24
- loadOpenCodeOverlayAssets,
25
- loadPluginMcpAssets
26
- } from './plugin-assets'
27
-
28
- const readConfigForWorkspace = async (cwd: string) => {
29
- return loadConfig({
30
- cwd,
31
- jsonVariables: buildConfigJsonVariables(cwd, process.env)
32
- })
33
- }
3
+ import { collectWorkspaceAssets } from './bundle-internal'
34
4
 
35
5
  export async function resolveWorkspaceAssetBundle(params: {
36
6
  cwd: string
37
7
  configs?: [Config?, Config?]
8
+ plugins?: PluginConfig
9
+ overlaySource?: string
38
10
  useDefaultVibeForgeMcpServer?: boolean
39
11
  }): Promise<WorkspaceAssetBundle> {
40
- const [config, userConfig] = params.configs ?? await readConfigForWorkspace(params.cwd)
41
- const enabledPlugins = mergeRecord(config?.enabledPlugins, userConfig?.enabledPlugins)
42
- const extraKnownMarketplaces = mergeRecord(config?.extraKnownMarketplaces, userConfig?.extraKnownMarketplaces)
43
- const loader = new DefinitionLoader(params.cwd)
44
-
45
- const [
46
- rawRules,
47
- rawSpecs,
48
- rawEntities,
49
- rawSkills,
50
- pluginMcpAssets,
51
- openCodeOverlayAssets
52
- ] = await Promise.all([
53
- loader.loadDefaultRules(),
54
- loader.loadDefaultSpecs(),
55
- loader.loadDefaultEntities(),
56
- loader.loadDefaultSkills(),
57
- loadPluginMcpAssets(params.cwd, enabledPlugins),
58
- loadOpenCodeOverlayAssets(params.cwd, enabledPlugins)
59
- ])
60
-
61
- const assets: WorkspaceAsset[] = []
62
-
63
- const rules = dedupeDocumentAssetsByIdentifier(
64
- dedupeDocumentAssets(
65
- rawRules.map((definition) => createDocumentAsset({ cwd: params.cwd, kind: 'rule', definition })),
66
- enabledPlugins
67
- ),
68
- asset => resolveRuleIdentifier(asset.payload.definition.path, asset.payload.definition.attributes.name)
69
- )
70
- const specs = dedupeDocumentAssetsByIdentifier(
71
- dedupeDocumentAssets(
72
- rawSpecs.map((definition) => createDocumentAsset({ cwd: params.cwd, kind: 'spec', definition })),
73
- enabledPlugins
74
- ),
75
- asset => resolveSpecIdentifier(asset.payload.definition.path, asset.payload.definition.attributes.name)
76
- )
77
- const entities = dedupeDocumentAssetsByIdentifier(
78
- dedupeDocumentAssets(
79
- rawEntities.map((definition) => createDocumentAsset({ cwd: params.cwd, kind: 'entity', definition })),
80
- enabledPlugins
81
- ),
82
- asset =>
83
- resolveDocumentName(
84
- asset.payload.definition.path,
85
- asset.payload.definition.attributes.name,
86
- ['readme.md', 'index.json']
87
- )
88
- )
89
- const skills = dedupeDocumentAssetsByIdentifier(
90
- dedupeDocumentAssets(
91
- rawSkills.map((definition) => createDocumentAsset({ cwd: params.cwd, kind: 'skill', definition })),
92
- enabledPlugins
93
- ),
94
- asset => resolveSkillIdentifier(asset.payload.definition.path, asset.payload.definition.attributes.name)
95
- )
96
-
97
- assets.push(...rules, ...specs, ...entities, ...skills)
98
-
99
- const mcpServers = new Map<string, Extract<WorkspaceAsset, { kind: 'mcpServer' }>>()
100
- if (params.useDefaultVibeForgeMcpServer !== false) {
101
- const defaultVibeForgeMcpServer = resolveDefaultVibeForgeMcpServerConfig()
102
- if (defaultVibeForgeMcpServer != null) {
103
- mcpServers.set(DEFAULT_VIBE_FORGE_MCP_SERVER_NAME, {
104
- id: `mcpServer:fallback:${DEFAULT_VIBE_FORGE_MCP_SERVER_NAME}`,
105
- kind: 'mcpServer',
106
- origin: 'fallback',
107
- scope: 'adapter',
108
- enabled: true,
109
- targets: ['claude-code', 'codex', 'opencode'],
110
- payload: {
111
- name: DEFAULT_VIBE_FORGE_MCP_SERVER_NAME,
112
- config: defaultVibeForgeMcpServer
113
- }
114
- })
115
- }
116
- }
117
- const userMcpServers = userConfig?.mcpServers ?? {}
118
- for (const [name, serverConfig] of Object.entries(userMcpServers)) {
119
- mcpServers.set(name, {
120
- id: `mcpServer:user:${name}`,
121
- kind: 'mcpServer',
122
- origin: 'config',
123
- scope: 'user',
124
- enabled: true,
125
- targets: ['claude-code', 'codex', 'opencode'],
126
- payload: {
127
- name,
128
- config: serverConfig
129
- }
130
- })
131
- }
132
- for (const asset of pluginMcpAssets) {
133
- mcpServers.set(asset.payload.name, asset)
134
- }
135
- for (const [name, serverConfig] of Object.entries(config?.mcpServers ?? {})) {
136
- mcpServers.set(name, {
137
- id: `mcpServer:project:${name}`,
138
- kind: 'mcpServer',
139
- origin: 'config',
140
- scope: 'project',
141
- enabled: true,
142
- targets: ['claude-code', 'codex', 'opencode'],
143
- payload: {
144
- name,
145
- config: serverConfig
146
- }
147
- })
148
- }
149
- assets.push(...mcpServers.values())
150
-
151
- const hookPlugins = [
152
- ...createHookPluginAssets(userConfig?.plugins, enabledPlugins, 'user'),
153
- ...createHookPluginAssets(config?.plugins, enabledPlugins, 'project')
154
- ]
155
- const claudeNativePlugins = createClaudeNativePluginAssets(enabledPlugins)
156
- assets.push(...hookPlugins, ...claudeNativePlugins, ...openCodeOverlayAssets)
12
+ const collected = await collectWorkspaceAssets(params)
157
13
 
158
14
  return {
159
15
  cwd: params.cwd,
160
- assets,
161
- rules,
162
- specs,
163
- entities,
164
- skills,
165
- mcpServers: Object.fromEntries(mcpServers.entries()),
166
- hookPlugins,
167
- enabledPlugins,
168
- extraKnownMarketplaces,
169
- defaultIncludeMcpServers: uniqueValues([
170
- ...(config?.defaultIncludeMcpServers ?? []),
171
- ...(userConfig?.defaultIncludeMcpServers ?? [])
172
- ]),
173
- defaultExcludeMcpServers: uniqueValues([
174
- ...(config?.defaultExcludeMcpServers ?? []),
175
- ...(userConfig?.defaultExcludeMcpServers ?? [])
176
- ])
16
+ pluginConfigs: collected.pluginConfigs,
17
+ pluginInstances: collected.pluginInstances,
18
+ assets: collected.assets,
19
+ rules: collected.rules,
20
+ specs: collected.specs,
21
+ entities: collected.entities,
22
+ skills: collected.skills,
23
+ mcpServers: collected.mcpServers,
24
+ hookPlugins: collected.hookPlugins,
25
+ opencodeOverlayAssets: collected.opencodeOverlayAssets,
26
+ defaultIncludeMcpServers: collected.defaultIncludeMcpServers,
27
+ defaultExcludeMcpServers: collected.defaultExcludeMcpServers
177
28
  }
178
29
  }
@@ -1,41 +1,3 @@
1
1
  import type { WorkspaceAsset } from '@vibe-forge/types'
2
2
 
3
- export interface WorkspaceDocumentPayload<TDefinition> {
4
- definition: TDefinition
5
- sourcePath: string
6
- }
7
-
8
- export interface WorkspaceOverlayPayload {
9
- sourcePath: string
10
- entryName: string
11
- targetSubpath: string
12
- }
13
-
14
- export type WorkspaceOpenCodeOverlayAsset =
15
- | (
16
- & Extract<WorkspaceAsset, { kind: 'nativePlugin' }>
17
- & { payload: WorkspaceOverlayPayload }
18
- )
19
- | Extract<WorkspaceAsset, { kind: 'agent' | 'command' | 'mode' }>
20
-
21
- export type WorkspaceDocumentAsset<TDefinition> =
22
- & Extract<
23
- WorkspaceAsset,
24
- { kind: 'rule' | 'spec' | 'entity' | 'skill' }
25
- >
26
- & {
27
- payload: WorkspaceDocumentPayload<TDefinition & { path: string }>
28
- }
29
-
30
- export const isOverlayPayload = (payload: unknown): payload is WorkspaceOverlayPayload => (
31
- payload != null &&
32
- typeof payload === 'object' &&
33
- typeof (payload as WorkspaceOverlayPayload).sourcePath === 'string' &&
34
- typeof (payload as WorkspaceOverlayPayload).entryName === 'string' &&
35
- typeof (payload as WorkspaceOverlayPayload).targetSubpath === 'string'
36
- )
37
-
38
- export const isOpenCodeOverlayAsset = (asset: WorkspaceAsset): asset is WorkspaceOpenCodeOverlayAsset => (
39
- (asset.kind === 'nativePlugin' || asset.kind === 'agent' || asset.kind === 'command' || asset.kind === 'mode') &&
40
- isOverlayPayload(asset.payload)
41
- )
3
+ export const isOpenCodeOverlayAsset = (asset: WorkspaceAsset) => asset.kind === 'nativePlugin'
@@ -0,0 +1,184 @@
1
+ import {
2
+ isAlwaysRule,
3
+ resolveDefinitionName,
4
+ resolveDocumentDescription,
5
+ resolveSpecIdentifier
6
+ } from '@vibe-forge/definition-core'
7
+ import type { Definition, Entity, Rule, Skill, Spec } from '@vibe-forge/types'
8
+ import { resolvePromptPath } from '@vibe-forge/utils'
9
+
10
+ const toMarkdownBlockquote = (content: string) => (
11
+ content
12
+ .trim()
13
+ .split('\n')
14
+ .map(line => line === '' ? '>' : `> ${line}`)
15
+ .join('\n')
16
+ )
17
+
18
+ const buildOptionalRuleGuidance = (cwd: string, rule: Definition<Rule>) => {
19
+ const name = resolveDefinitionName(rule)
20
+ const desc = resolveDocumentDescription(rule.body, rule.attributes.description, name)
21
+ return [
22
+ `Use when: ${desc}`,
23
+ `Rule file path: ${resolvePromptPath(cwd, rule.path)}`,
24
+ 'Only read this rule file when the task matches the scenario above.'
25
+ ].join('\n')
26
+ }
27
+
28
+ const buildSkillSummary = (
29
+ cwd: string,
30
+ skill: Definition<Skill>,
31
+ guidance: string
32
+ ) => {
33
+ const name = resolveDefinitionName(skill, ['skill.md'])
34
+ const desc = resolveDocumentDescription(skill.body, skill.attributes.description, name)
35
+ return toMarkdownBlockquote(
36
+ [
37
+ `Skill description: ${desc}`,
38
+ `Skill file path: ${resolvePromptPath(cwd, skill.path)}`,
39
+ guidance
40
+ ].join('\n')
41
+ )
42
+ }
43
+
44
+ const renderSkillModule = (
45
+ cwd: string,
46
+ skill: Definition<Skill>,
47
+ options: {
48
+ guidance: string
49
+ includeContent?: boolean
50
+ }
51
+ ) => {
52
+ const parts = [
53
+ `# ${resolveDefinitionName(skill, ['skill.md'])}`,
54
+ '',
55
+ buildSkillSummary(cwd, skill, options.guidance)
56
+ ]
57
+
58
+ if (options.includeContent) {
59
+ parts.push('', '<skill-content>', skill.body.trim(), '</skill-content>')
60
+ }
61
+
62
+ return parts.join('\n')
63
+ }
64
+
65
+ export const generateRulesPrompt = (cwd: string, rules: Definition<Rule>[]) => {
66
+ const rulesPrompt = rules
67
+ .map((rule) => {
68
+ const content = isAlwaysRule(rule.attributes) && rule.body.trim()
69
+ ? rule.body.trim()
70
+ : buildOptionalRuleGuidance(cwd, rule)
71
+ return `# ${resolveDefinitionName(rule)}\n\n${toMarkdownBlockquote(content)}`
72
+ })
73
+ .filter(Boolean)
74
+ .join('\n\n')
75
+
76
+ return (
77
+ '<system-prompt>\n' +
78
+ 'The project system rules are:\n' +
79
+ `${rulesPrompt}\n` +
80
+ '</system-prompt>\n'
81
+ )
82
+ }
83
+
84
+ export const generateSkillsPrompt = (cwd: string, skills: Definition<Skill>[]) => {
85
+ const modules = skills
86
+ .map(skill =>
87
+ renderSkillModule(cwd, skill, {
88
+ guidance:
89
+ 'Resolve relative paths in the resource content relative to the directory containing this skill file.',
90
+ includeContent: true
91
+ })
92
+ )
93
+ .filter(Boolean)
94
+ .join('\n\n')
95
+
96
+ if (modules === '') return ''
97
+
98
+ return `<system-prompt>\nThe following skill modules are loaded for the project:\n${modules}\n</system-prompt>\n`
99
+ }
100
+
101
+ export const generateSkillsRoutePrompt = (cwd: string, skills: Definition<Skill>[]) => {
102
+ const modules = skills
103
+ .filter(({ attributes: { always } }) => always !== false)
104
+ .map(skill =>
105
+ renderSkillModule(cwd, skill, {
106
+ guidance:
107
+ 'Do not preload the body by default; read the corresponding skill file only when the task clearly requires it.'
108
+ })
109
+ )
110
+ .filter(Boolean)
111
+ .join('\n\n')
112
+
113
+ if (modules === '') return ''
114
+
115
+ return `<skills>\n${modules}\n</skills>\n`
116
+ }
117
+
118
+ export const generateSpecRoutePrompt = (
119
+ specsDocuments: Definition<Spec>[],
120
+ options?: { active?: boolean }
121
+ ) => {
122
+ const specsRouteStr = specsDocuments
123
+ .filter(({ attributes }) => attributes.always !== false)
124
+ .map((definition) => {
125
+ const name = resolveDefinitionName(definition, ['index.md'])
126
+ const desc = resolveDocumentDescription(definition.body, definition.attributes.description, name)
127
+ const identifier = definition.resolvedName?.trim() ||
128
+ resolveSpecIdentifier(definition.path, definition.attributes.name)
129
+ const params = definition.attributes.params ?? []
130
+ const paramsPrompt = params.length > 0
131
+ ? params
132
+ .map(({ name, description }) => ` - ${name}: ${description ?? 'None'}\n`)
133
+ .join('')
134
+ : ' - None\n'
135
+
136
+ return (
137
+ `- Workflow name: ${name}\n` +
138
+ ` - Description: ${desc}\n` +
139
+ ` - Identifier: ${identifier}\n` +
140
+ ' - Parameters:\n' +
141
+ `${paramsPrompt}`
142
+ )
143
+ })
144
+ .join('\n')
145
+
146
+ const activeIdentityPrompt = options?.active
147
+ ? (
148
+ 'You are a professional project execution manager who can skillfully direct other entities to work toward your goal. Expectations:\n' +
149
+ '\n' +
150
+ '- Never complete code development work alone\n' +
151
+ '- You must coordinate other developers to complete tasks\n' +
152
+ '- You must keep them aligned with the goal and verify that their completion reports meet the requirements\n' +
153
+ '\n'
154
+ )
155
+ : ''
156
+
157
+ return (
158
+ `<system-prompt>\n${activeIdentityPrompt}Choose the appropriate workflow based on the user's needs and the actual development goal, and use the workflow identifier to locate and load the corresponding definition.\n` +
159
+ '- Pass the identifier based on the actual need. This is not a path; use the standard workflow loading capability to resolve it.\n' +
160
+ '- Decide how to pass parameters based on their descriptions and actual usage scenarios.\n' +
161
+ 'The project includes the following workflows:\n' +
162
+ `${specsRouteStr}\n` +
163
+ '</system-prompt>\n'
164
+ )
165
+ }
166
+
167
+ export const generateEntitiesRoutePrompt = (entities: Definition<Entity>[]) => {
168
+ return (
169
+ '<system-prompt>\n' +
170
+ 'The project includes the following entities:\n' +
171
+ `${
172
+ entities
173
+ .filter(({ attributes }) => attributes.always !== false)
174
+ .map((definition) => {
175
+ const name = resolveDefinitionName(definition, ['readme.md', 'index.json'])
176
+ const desc = resolveDocumentDescription(definition.body, definition.attributes.description, name)
177
+ return ` - ${name}: ${desc}\n`
178
+ })
179
+ .join('')
180
+ }\n` +
181
+ 'When 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.\n' +
182
+ '</system-prompt>\n'
183
+ )
184
+ }