@vibe-forge/core 0.7.5 → 0.8.3

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.
Files changed (43) hide show
  1. package/package.json +4 -46
  2. package/src/env.ts +5 -25
  3. package/src/index.ts +0 -5
  4. package/src/types.ts +13 -72
  5. package/src/ws.ts +2 -12
  6. package/src/adapter/index.ts +0 -6
  7. package/src/adapter/loader.ts +0 -11
  8. package/src/adapter/type.ts +0 -117
  9. package/src/config/load.ts +0 -122
  10. package/src/config/types.ts +0 -289
  11. package/src/config.ts +0 -2
  12. package/src/controllers/benchmark/discover.ts +0 -89
  13. package/src/controllers/benchmark/index.ts +0 -24
  14. package/src/controllers/benchmark/result-store.ts +0 -46
  15. package/src/controllers/benchmark/runner.ts +0 -415
  16. package/src/controllers/benchmark/schema.ts +0 -60
  17. package/src/controllers/benchmark/types.ts +0 -80
  18. package/src/controllers/benchmark/utils.ts +0 -144
  19. package/src/controllers/benchmark/workspace.ts +0 -179
  20. package/src/controllers/config/index.ts +0 -214
  21. package/src/controllers/system/assets/completed.mp3 +0 -0
  22. package/src/controllers/system/assets/mcp.png +0 -0
  23. package/src/controllers/system/index.ts +0 -102
  24. package/src/controllers/task/generate-adapter-query-options.ts +0 -25
  25. package/src/controllers/task/index.ts +0 -2
  26. package/src/controllers/task/prepare.ts +0 -74
  27. package/src/controllers/task/run.ts +0 -231
  28. package/src/controllers/task/schema.ts +0 -131
  29. package/src/controllers/task/type.ts +0 -6
  30. package/src/hooks/bridge.ts +0 -368
  31. package/src/hooks/call.ts +0 -74
  32. package/src/hooks/index.ts +0 -41
  33. package/src/hooks/loader.ts +0 -79
  34. package/src/hooks/native.ts +0 -116
  35. package/src/hooks/runtime.ts +0 -139
  36. package/src/hooks/type.ts +0 -145
  37. package/src/utils/cache.ts +0 -58
  38. package/src/utils/create-logger.ts +0 -89
  39. package/src/utils/definition-loader.ts +0 -530
  40. package/src/utils/filter.ts +0 -26
  41. package/src/utils/string-transform.ts +0 -37
  42. package/src/utils/uuid.ts +0 -6
  43. package/src/utils/workspace-assets.ts +0 -919
@@ -1,530 +0,0 @@
1
- import { existsSync } from 'node:fs'
2
- import { readFile } from 'node:fs/promises'
3
- import { basename, dirname, relative, resolve } from 'node:path'
4
- import process from 'node:process'
5
-
6
- import { glob } from 'fast-glob'
7
- import fm from 'front-matter'
8
-
9
- export interface Filter {
10
- include?: string[]
11
- exclude?: string[]
12
- }
13
-
14
- export interface Rule {
15
- name?: string
16
- description?: string
17
- globs?: string | string[]
18
- /**
19
- * 是否默认加载至系统上下文
20
- */
21
- always?: boolean
22
- }
23
-
24
- export interface Spec {
25
- name?: string
26
- always?: boolean
27
- description?: string
28
- tags?: string[]
29
- params?: {
30
- name: string
31
- description?: string
32
- }[]
33
- rules?: string[]
34
- skills?: string[]
35
- mcpServers?: Filter
36
- tools?: Filter
37
- }
38
-
39
- export interface LocalRuleReference {
40
- type?: 'local'
41
- path: string
42
- desc?: string
43
- }
44
-
45
- export interface RemoteRuleReference {
46
- type: 'remote'
47
- tags?: string[]
48
- desc?: string
49
- }
50
-
51
- export type RuleReference = string | LocalRuleReference | RemoteRuleReference
52
-
53
- export interface SkillSelection {
54
- type: 'include' | 'exclude'
55
- list: string[]
56
- }
57
-
58
- export interface Entity {
59
- name?: string
60
- always?: boolean
61
- description?: string
62
- tags?: string[]
63
- prompt?: string
64
- promptPath?: string
65
- rules?: RuleReference[]
66
- skills?: string[] | SkillSelection
67
- mcpServers?: Filter
68
- tools?: Filter
69
- }
70
-
71
- export interface Skill {
72
- name?: string
73
- description?: string
74
- always?: boolean
75
- }
76
-
77
- export interface Definition<T> {
78
- path: string
79
- body: string
80
- attributes: T
81
- }
82
-
83
- /**
84
- * 以结构化的方式加载本地文档数据
85
- */
86
- export const loadLocalDocuments = async <Attrs extends object>(
87
- paths: string[]
88
- ): Promise<Definition<Attrs>[]> => {
89
- const promises = paths.map(async (path) => {
90
- const content = await readFile(path, 'utf-8')
91
- const { body, attributes } = fm<Attrs>(content)
92
- return {
93
- path,
94
- body,
95
- attributes
96
- }
97
- })
98
- return Promise.all(promises)
99
- }
100
-
101
- const normalizePath = (path: string) => path.split('\\').join('/')
102
-
103
- const stripExtension = (fileName: string) => fileName.replace(/\.[^/.]+$/, '')
104
-
105
- const getFirstNonEmptyLine = (text: string) =>
106
- text
107
- .split('\n')
108
- .map(line => line.trim())
109
- .find(Boolean)
110
-
111
- const toPromptPath = (cwd: string, path: string) => {
112
- const relPath = normalizePath(relative(cwd, path))
113
- return relPath.startsWith('..') ? normalizePath(path) : relPath
114
- }
115
-
116
- const resolveDocumentName = (
117
- path: string,
118
- explicitName?: string,
119
- indexFileNames: string[] = []
120
- ) => {
121
- const trimmedName = explicitName?.trim()
122
- if (trimmedName) return trimmedName
123
-
124
- const fileName = basename(path).toLowerCase()
125
- if (indexFileNames.includes(fileName)) {
126
- return basename(dirname(path))
127
- }
128
-
129
- return stripExtension(basename(path))
130
- }
131
-
132
- const resolveDocumentDescription = (
133
- body: string,
134
- explicitDescription?: string,
135
- fallbackName?: string
136
- ) => {
137
- const trimmedDescription = explicitDescription?.trim()
138
- return trimmedDescription || getFirstNonEmptyLine(body) || fallbackName || ''
139
- }
140
-
141
- const resolveSpecIdentifier = (path: string, explicitName?: string) => {
142
- return resolveDocumentName(path, explicitName, ['index.md'])
143
- }
144
-
145
- const toNonEmptyStringArray = (values: unknown): string[] => {
146
- if (!Array.isArray(values)) return []
147
- return values
148
- .filter((value): value is string => typeof value === 'string')
149
- .map(value => value.trim())
150
- .filter(Boolean)
151
- }
152
-
153
- const isLocalRuleReference = (value: RuleReference): value is LocalRuleReference => {
154
- return (
155
- value != null &&
156
- typeof value === 'object' &&
157
- typeof value.path === 'string' &&
158
- (value.type == null || value.type === 'local')
159
- )
160
- }
161
-
162
- const isRemoteRuleReference = (value: RuleReference): value is RemoteRuleReference => {
163
- return (
164
- value != null &&
165
- typeof value === 'object' &&
166
- value.type === 'remote'
167
- )
168
- }
169
-
170
- const resolveRulePattern = (pattern: string, baseDir: string) => {
171
- const trimmed = pattern.trim()
172
- if (!trimmed) return undefined
173
- if (trimmed.startsWith('./') || trimmed.startsWith('../')) {
174
- return normalizePath(resolve(baseDir, trimmed))
175
- }
176
- return trimmed
177
- }
178
-
179
- const createRemoteRuleDefinition = (
180
- rule: RemoteRuleReference,
181
- index: number
182
- ): Definition<Rule> => {
183
- const tags = toNonEmptyStringArray(rule.tags)
184
- const desc = rule.desc?.trim() || (
185
- tags.length > 0
186
- ? `远程知识库标签:${tags.join(', ')}`
187
- : '远程知识库规则引用'
188
- )
189
- const bodyParts = [
190
- desc,
191
- tags.length > 0 ? `知识库标签:${tags.join(', ')}` : undefined,
192
- '该规则来自远程知识库引用,不对应本地文件。'
193
- ].filter((value): value is string => Boolean(value))
194
-
195
- return {
196
- path: `remote-rule-${index + 1}.md`,
197
- body: bodyParts.join('\n'),
198
- attributes: {
199
- name: tags.length > 0 ? `remote:${tags.join(',')}` : `remote-rule-${index + 1}`,
200
- description: desc
201
- }
202
- }
203
- }
204
-
205
- export class DefinitionLoader {
206
- private readonly cwd: string
207
-
208
- constructor(cwd: string = process.cwd()) {
209
- this.cwd = cwd
210
- }
211
-
212
- private async scan(
213
- patterns: string[],
214
- cwd: string = this.cwd
215
- ): Promise<string[]> {
216
- const paths = await glob(patterns, { cwd, absolute: true })
217
- return paths.sort((a, b) => {
218
- const aKey = relative(cwd, a).split('\\').join('/')
219
- const bKey = relative(cwd, b).split('\\').join('/')
220
- return aKey.localeCompare(bKey)
221
- })
222
- }
223
-
224
- async loadRules(
225
- rules: RuleReference[],
226
- options?: {
227
- baseDir?: string
228
- }
229
- ) {
230
- const baseDir = options?.baseDir ?? this.cwd
231
- const definitions: Definition<Rule>[] = []
232
-
233
- for (const [index, rule] of rules.entries()) {
234
- if (typeof rule === 'string') {
235
- const pattern = resolveRulePattern(rule, baseDir)
236
- if (!pattern) continue
237
- definitions.push(
238
- ...await loadLocalDocuments<Rule>(
239
- await this.scan([pattern])
240
- )
241
- )
242
- continue
243
- }
244
-
245
- if (isRemoteRuleReference(rule)) {
246
- definitions.push(createRemoteRuleDefinition(rule, index))
247
- continue
248
- }
249
-
250
- if (!isLocalRuleReference(rule)) continue
251
-
252
- const pattern = resolveRulePattern(rule.path, baseDir)
253
- if (!pattern) continue
254
-
255
- const docs = await loadLocalDocuments<Rule>(
256
- await this.scan([pattern])
257
- )
258
-
259
- definitions.push(
260
- ...docs.map((doc) => ({
261
- ...doc,
262
- attributes: {
263
- ...doc.attributes,
264
- description: rule.desc?.trim() || doc.attributes.description
265
- }
266
- }))
267
- )
268
- }
269
-
270
- return definitions
271
- }
272
- async loadDefaultRules(): Promise<Definition<Rule>[]> {
273
- return this.loadRules([
274
- '.ai/rules/*.md',
275
- '.ai/plugins/*/rules/*.md'
276
- ])
277
- }
278
- generateRulesPrompt(rules: Definition<Rule>[]): string {
279
- const rulesPrompt = rules
280
- .map((rule) => {
281
- const { path, body, attributes } = rule
282
- const name = resolveDocumentName(path, attributes.name)
283
- const desc = resolveDocumentDescription(body, attributes.description, name)
284
- const content = attributes.always && body.trim()
285
- ? `<rule-content>\n${body.trim()}\n</rule-content>\n`
286
- : ''
287
- return ` - ${name}:${desc}\n${content}--------------------\n`
288
- })
289
- .filter(Boolean)
290
- .join('\n')
291
-
292
- return (
293
- '<system-prompt>\n' +
294
- '项目系统规则如下:\n' +
295
- `${rulesPrompt}\n` +
296
- '</system-prompt>\n'
297
- )
298
- }
299
-
300
- async loadSkills(skills?: string[]): Promise<Definition<Skill>[]> {
301
- // 1. Scan for skills in standard locations
302
- // Project root skills: .ai/skills/{name}/SKILL.md
303
- // Plugin skills: .ai/plugins/{plugin}/skills/{name}/SKILL.md
304
-
305
- // Note: The user code uses readdir to iterate plugins.
306
- // We can use glob for easier path finding.
307
-
308
- const patterns = [
309
- '.ai/skills/*/SKILL.md',
310
- '.ai/plugins/*/skills/*/SKILL.md'
311
- ]
312
-
313
- let paths = await this.scan(patterns)
314
-
315
- // Filter by directory name (skill name)
316
- if (skills) {
317
- paths = paths.filter(path => {
318
- return skills.includes(basename(dirname(path)))
319
- })
320
- }
321
-
322
- return loadLocalDocuments<Skill>(paths)
323
- }
324
- async loadDefaultSkills(): Promise<Definition<Skill>[]> {
325
- return this.loadSkills()
326
- }
327
- generateSkillsPrompt(skills: Definition<Skill>[]): string {
328
- return skills
329
- .map((skill) => {
330
- const { path, body, attributes } = skill
331
- const name = resolveDocumentName(path, attributes.name, ['skill.md'])
332
- const desc = resolveDocumentDescription(body, attributes.description, name)
333
- return (
334
- '技能相关信息如下,通过阅读以下内容了解技能的详细信息:\n' +
335
- `- 技能名称:${name}\n` +
336
- `- 技能介绍:${desc}\n` +
337
- `- 技能文件资源路径:${toPromptPath(this.cwd, dirname(path))}\n` +
338
- '- 资源内容:\n' +
339
- '<skill-content>\n' +
340
- `${body.trim()}\n` +
341
- '</skill-content>\n' +
342
- '资源内容中的文件路径相对「技能文件资源路径」路径,通过读取相关工具按照实际需要进行阅读。\n'
343
- )
344
- })
345
- .filter(Boolean)
346
- .join('\n')
347
- }
348
- generateSkillsRoutePrompt(skills: Definition<Skill>[]): string {
349
- return (
350
- '<skills>\n' +
351
- `${
352
- skills
353
- .filter(({ attributes: { always } }) => always !== false)
354
- .map(
355
- ({ path, body, attributes }) => {
356
- const name = resolveDocumentName(path, attributes.name, ['skill.md'])
357
- const desc = resolveDocumentDescription(body, attributes.description, name)
358
- return ` - ${name}:${desc}\n`
359
- }
360
- )
361
- .join('')
362
- }\n` +
363
- '</skills>\n'
364
- )
365
- }
366
-
367
- async loadSpec(name: string): Promise<Definition<Spec> | undefined> {
368
- const patterns = [
369
- `.ai/specs/${name}.md`,
370
- `.ai/specs/${name}/index.md`,
371
- `.ai/plugins/*/specs/${name}.md`,
372
- `.ai/plugins/*/specs/${name}/index.md`
373
- ]
374
- const paths = await this.scan(patterns)
375
- if (paths.length === 0) return undefined
376
-
377
- const projectPath = paths.find(p => p.includes('/.ai/specs/'))
378
- const targetPath = projectPath || paths[0]
379
-
380
- const [doc] = await loadLocalDocuments<Spec>([targetPath])
381
- return doc
382
- }
383
- async loadDefaultSpecs(): Promise<Definition<Spec>[]> {
384
- const patterns = [
385
- '.ai/specs/*.md',
386
- '.ai/specs/*/index.md',
387
- '.ai/plugins/*/specs/*.md',
388
- '.ai/plugins/*/specs/*/index.md'
389
- ]
390
- const paths = await this.scan(patterns)
391
- return loadLocalDocuments<Spec>(paths)
392
- }
393
- generateSpecRoutePrompt(specsDocuments: Definition<Spec>[]): string {
394
- const specsRouteStr = specsDocuments
395
- .filter(({ attributes }) => attributes.always !== false)
396
- .map(({ path, body, attributes }) => {
397
- const name = resolveDocumentName(path, attributes.name, ['index.md'])
398
- const desc = resolveDocumentDescription(body, attributes.description, name)
399
- const identifier = resolveSpecIdentifier(path, attributes.name)
400
- const params = attributes.params ?? []
401
- const paramsPrompt = params.length > 0
402
- ? params
403
- .map(({ name, description }) => ` - ${name}:${description ?? '无'}\n`)
404
- .join('')
405
- : ' - 无\n'
406
-
407
- return (
408
- `- 流程名称:${name}\n` +
409
- ` - 介绍:${desc}\n` +
410
- ` - 标识:${identifier}\n` +
411
- ' - 参数:\n' +
412
- `${paramsPrompt}`
413
- )
414
- })
415
- .join('\n')
416
- return (
417
- '<system-prompt>\n' +
418
- '你是一个专业的项目推进管理大师,能够熟练指导其他实体来为你的目标工作。对你的预期是:\n' +
419
- '\n' +
420
- '- 永远不要单独完成代码开发工作\n' +
421
- '- 必须要协调其他的开发人员来完成任务\n' +
422
- '- 必须让他们按照目标进行完成,不要偏离目标,检查他们任务完成后的汇报内容是否符合要求\n' +
423
- '\n' +
424
- '根据用户需要以及实际的开发目标来决定使用不同的工作流程,调用 `load-spec` mcp tool 完成工作流程的加载。\n' +
425
- '- 根据实际需求传入标识,这不是路径,只能使用工具进行加载\n' +
426
- '- 通过参数的描述以及实际应用场景决定怎么传入参数\n' +
427
- '项目存在如下工作流程:\n' +
428
- `${specsRouteStr}\n` +
429
- '</system-prompt>\n'
430
- )
431
- }
432
-
433
- async loadEntity(name: string): Promise<Definition<Entity> | undefined> {
434
- // 1. Try to load from index.json (Directory based entity)
435
- const jsonPatterns = [
436
- `.ai/entities/${name}/index.json`,
437
- `.ai/plugins/*/entities/${name}/index.json`
438
- ]
439
- const jsonPaths = await this.scan(jsonPatterns)
440
-
441
- if (jsonPaths.length > 0) {
442
- const projectPath = jsonPaths.find(p => p.includes('/.ai/entities/'))
443
- const targetPath = projectPath || jsonPaths[0]
444
- return this.loadEntityFromJson(targetPath)
445
- }
446
-
447
- // 2. Fallback to Markdown file
448
- const patterns = [
449
- `.ai/entities/${name}.md`,
450
- `.ai/entities/${name}/README.md`,
451
- `.ai/plugins/*/entities/${name}.md`,
452
- `.ai/plugins/*/entities/${name}/README.md`
453
- ]
454
- const paths = await this.scan(patterns)
455
- if (paths.length === 0) return undefined
456
-
457
- const projectPath = paths.find(p => p.includes('/.ai/entities/'))
458
- const targetPath = projectPath || paths[0]
459
-
460
- const [doc] = await loadLocalDocuments<Entity>([targetPath])
461
- return doc
462
- }
463
- private async loadEntityFromJson(jsonPath: string): Promise<Definition<Entity>> {
464
- const entityDir = dirname(jsonPath)
465
- const jsonVariables: Record<string, string> = {
466
- workspaceFolder: process.env.WORKSPACE_FOLDER || this.cwd
467
- }
468
-
469
- const content = await readFile(jsonPath, 'utf-8')
470
- const entityJSONContent = content.replace(/\$\{([^}]+)\}/g, (_, key) => jsonVariables[key] ?? `$\{${key}}`)
471
-
472
- const entityData = JSON.parse(entityJSONContent) as Entity
473
-
474
- let prompt = entityData.prompt
475
- if (!prompt) {
476
- const promptPath = entityData.promptPath || 'AGENTS.md'
477
- const resolvedPromptPath = resolve(entityDir, promptPath)
478
- if (existsSync(resolvedPromptPath)) {
479
- prompt = await readFile(resolvedPromptPath, 'utf-8')
480
- }
481
- }
482
-
483
- return {
484
- path: jsonPath,
485
- body: prompt || '',
486
- attributes: entityData
487
- }
488
- }
489
- async loadDefaultEntities(): Promise<Definition<Entity>[]> {
490
- // List both .md and index.json entities
491
- const mdPatterns = [
492
- '.ai/entities/*.md',
493
- '.ai/entities/*/README.md',
494
- '.ai/plugins/*/entities/*.md',
495
- '.ai/plugins/*/entities/*/README.md'
496
- ]
497
- const jsonPatterns = [
498
- '.ai/entities/*/index.json',
499
- '.ai/plugins/*/entities/*/index.json'
500
- ]
501
-
502
- const [mdPaths, jsonPaths] = await Promise.all([
503
- this.scan(mdPatterns),
504
- this.scan(jsonPatterns)
505
- ])
506
-
507
- const mdDocs = await loadLocalDocuments<Entity>(mdPaths)
508
- const jsonDocs = await Promise.all(jsonPaths.map(p => this.loadEntityFromJson(p)))
509
-
510
- return [...mdDocs, ...jsonDocs]
511
- }
512
- generateEntitiesRoutePrompt(entities: Definition<Entity>[]): string {
513
- return (
514
- '<system-prompt>\n' +
515
- '项目存在如下实体:\n' +
516
- `${
517
- entities
518
- .filter(({ attributes }) => attributes.always !== false)
519
- .map(({ path, attributes, body }) => {
520
- const name = resolveDocumentName(path, attributes.name, ['readme.md', 'index.json'])
521
- const desc = resolveDocumentDescription(body, attributes.description, name)
522
- return ` - ${name}:${desc}\n`
523
- })
524
- .join('')
525
- }\n` +
526
- '解决用户问题时,需根据用户需求可以通过 run-tasks 工具指定为实体后,自行调度多个不同类型的实体来完成工作。\n' +
527
- '</system-prompt>\n'
528
- )
529
- }
530
- }
@@ -1,26 +0,0 @@
1
- export const filterObject = <T extends Record<string, any>>(
2
- obj: T,
3
- rules: { include?: string[]; exclude?: string[] }
4
- ): Partial<T> => {
5
- const { include = [], exclude = [] } = rules
6
-
7
- if (include.length === 0 && exclude.length === 0) {
8
- return { ...obj }
9
- }
10
-
11
- const result: Partial<T> = {}
12
-
13
- Object.keys(obj).forEach((key) => {
14
- // If include list is provided, key MUST be in it
15
- if (include.length > 0 && !include.includes(key)) {
16
- return
17
- }
18
- // If key is in exclude list, it MUST NOT be in it
19
- if (exclude.includes(key)) {
20
- return
21
- }
22
- result[key as keyof T] = obj[key]
23
- })
24
-
25
- return result
26
- }
@@ -1,37 +0,0 @@
1
- export function kebabCase(str: string) {
2
- return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
3
- }
4
-
5
- export function camelCase(str: string) {
6
- return str.replace(/[-_]([a-z])/g, (_, letter) => letter.toUpperCase())
7
- }
8
-
9
- export function transformAllObjectKeys(
10
- transformFn: (str: string) => string,
11
- obj: unknown
12
- ): unknown {
13
- const boundTransformFn = transformAllObjectKeys.bind(null, transformFn)
14
- if (typeof obj !== 'object') return obj
15
- if (Array.isArray(obj)) {
16
- return obj.map(boundTransformFn)
17
- }
18
- if (obj === null) {
19
- return null
20
- }
21
- const newObj: Record<string, unknown> = {}
22
- for (const key in obj) {
23
- newObj[transformFn(key)] = boundTransformFn(
24
- // @ts-ignore
25
- obj[key]
26
- )
27
- }
28
- return newObj
29
- }
30
-
31
- export function transformKebabKey<T>(obj: unknown) {
32
- return transformAllObjectKeys(kebabCase, obj) as T
33
- }
34
-
35
- export function transformCamelKey<T>(obj: unknown) {
36
- return transformAllObjectKeys(camelCase, obj) as T
37
- }
package/src/utils/uuid.ts DELETED
@@ -1,6 +0,0 @@
1
- export const uuid = () =>
2
- 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
3
- const r = (Math.random() * 16) | 0
4
- const v = c === 'x' ? r : (r & 0x3) | 0x8
5
- return v.toString(16)
6
- })