@vibe-forge/core 0.7.4 → 0.8.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.
Files changed (40) 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 +12 -72
  5. package/src/ws.ts +3 -12
  6. package/src/adapter/index.ts +0 -6
  7. package/src/adapter/loader.ts +0 -11
  8. package/src/adapter/type.ts +0 -112
  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 -218
  25. package/src/controllers/task/index.ts +0 -2
  26. package/src/controllers/task/prepare.ts +0 -68
  27. package/src/controllers/task/run.ts +0 -191
  28. package/src/controllers/task/schema.ts +0 -131
  29. package/src/controllers/task/type.ts +0 -6
  30. package/src/hooks/call.ts +0 -74
  31. package/src/hooks/index.ts +0 -39
  32. package/src/hooks/loader.ts +0 -75
  33. package/src/hooks/runtime.ts +0 -139
  34. package/src/hooks/type.ts +0 -120
  35. package/src/utils/cache.ts +0 -58
  36. package/src/utils/create-logger.ts +0 -89
  37. package/src/utils/definition-loader.ts +0 -530
  38. package/src/utils/filter.ts +0 -26
  39. package/src/utils/string-transform.ts +0 -37
  40. package/src/utils/uuid.ts +0 -6
@@ -1,39 +0,0 @@
1
- import type { Writable } from 'node:stream'
2
-
3
- import type { HookInputs, HookOutputs } from './type'
4
-
5
- export interface HookContext {
6
- logger: {
7
- stream: Writable
8
- info: (...args: unknown[]) => void
9
- warn: (...args: unknown[]) => void
10
- debug: (...args: unknown[]) => void
11
- error: (...args: unknown[]) => void
12
- }
13
- }
14
-
15
- export type Plugin =
16
- & {
17
- name?: string
18
- }
19
- & {
20
- [P in keyof HookInputs]: (
21
- ctx: HookContext,
22
- input: HookInputs[P],
23
- next: () => Promise<HookOutputs[P]>
24
- ) => Promise<HookOutputs[P]>
25
- }
26
-
27
- export const definePlugin = (plugin: Partial<Plugin>) => plugin
28
-
29
- export interface PluginMap {}
30
-
31
- export type PluginConfig =
32
- | (Partial<Plugin> | (() => Partial<Plugin>))[]
33
- | Record<string, Record<string, unknown>>
34
- | Partial<PluginMap>
35
-
36
- export * from './call'
37
- export * from './loader'
38
- export * from './runtime'
39
- export * from './type'
@@ -1,75 +0,0 @@
1
- import type { Plugin, PluginConfig } from './index'
2
-
3
- /**
4
- * 解析单个插件配置
5
- */
6
- const loadPlugin = async (
7
- name: string,
8
- config: Record<string, unknown>
9
- ): Promise<Partial<Plugin> | null> => {
10
- try {
11
- // eslint-disable-next-line ts/no-require-imports
12
- const module = require(`${name}/hooks`)
13
-
14
- // 兼容 ESM default export 和 CJS module.exports
15
- const factory: (
16
- // 直接导出插件对象
17
- | Partial<Plugin>
18
- // 导出工厂函数,接受配置并返回插件对象
19
- | ((config: Record<string, unknown>) => Partial<Plugin>)
20
- ) = module.default ?? module
21
-
22
- if (typeof factory === 'function') {
23
- // TODO: 这里可以注入更多上下文,如全局配置、版本信息等
24
- return factory(config)
25
- } else if (typeof factory === 'object' && factory !== null) {
26
- return factory
27
- }
28
-
29
- console.warn(`Plugin ${name} does not export a valid plugin factory or object.`)
30
- return null
31
- } catch (e) {
32
- console.error(`Failed to load plugin ${name}:`, e)
33
- return null
34
- }
35
- }
36
-
37
- export const resolvePlugins = async (config: PluginConfig): Promise<Partial<Plugin>[]> => {
38
- // 1. 处理数组形式配置 (直接实例化或函数调用)
39
- if (Array.isArray(config)) {
40
- return config.map((p) => (typeof p === 'function' ? p() : p))
41
- }
42
-
43
- // 2. 处理对象形式配置 (动态加载)
44
- const entries = Object.entries(config)
45
- if (entries.length === 0) return []
46
-
47
- // 并行加载所有插件
48
- const modules = await Promise.allSettled(
49
- entries.map(([pkgName, pkgConfig]) => {
50
- // 如果不是以 @ 或 @vibe-forge/plugin- 开头,则默认加上 @vibe-forge/plugin- 前缀
51
- const resolvedName = pkgName.startsWith('@') ? pkgName : `@vibe-forge/plugin-${pkgName}`
52
- // dprint-ignore
53
- return (
54
- loadPlugin(resolvedName, pkgConfig as Record<string, unknown>) ??
55
- loadPlugin(pkgName, pkgConfig as Record<string, unknown>)
56
- )
57
- })
58
- )
59
-
60
- // 收集成功加载的插件
61
- const plugins: Partial<Plugin>[] = []
62
-
63
- modules.forEach((result, index) => {
64
- const pkgName = entries[index][0]
65
- if (result.status === 'fulfilled') {
66
- if (result.value) {
67
- plugins.push(result.value)
68
- }
69
- } else {
70
- console.error(`Error loading plugin ${pkgName}:`, result.reason)
71
- }
72
- })
73
-
74
- return plugins
75
- }
@@ -1,139 +0,0 @@
1
- import { Buffer } from 'node:buffer'
2
- import process from 'node:process'
3
-
4
- import { loadConfig, resetConfigCache } from '#~/config/load.js'
5
- import { resolveServerLogLevel } from '#~/env.js'
6
- import { createLogger } from '#~/utils/create-logger.js'
7
- import { transformCamelKey } from '#~/utils/string-transform.js'
8
-
9
- import type { HookInput, HookInputs, HookOutputCore, HookOutputs } from './type'
10
- import type { HookContext, Plugin } from './index'
11
- import { resolvePlugins } from './loader'
12
-
13
- export const callPluginHook = async <K extends keyof HookInputs>(
14
- eventName: K,
15
- context: HookContext,
16
- input: HookInputs[K],
17
- plugins: Partial<Plugin>[] = []
18
- ): Promise<HookOutputs[K]> => {
19
- const { logger } = context
20
- const filteredPlugins = plugins.filter(
21
- (
22
- item
23
- ): item is
24
- & {
25
- name?: string
26
- }
27
- & {
28
- [P in K]: NonNullable<Plugin[P]>
29
- } => !!item && !!item[eventName]
30
- )
31
-
32
- let index = 0
33
-
34
- const next = async (): Promise<HookOutputs[K]> => {
35
- if (index >= filteredPlugins.length) {
36
- return { continue: true }
37
- }
38
-
39
- const currentPlugin = filteredPlugins[index]
40
- const { name = '<anonymous>', [eventName]: hook } = currentPlugin
41
- index++
42
-
43
- const withNameLogger = {
44
- ...logger,
45
- info: logger.info.bind(logger, `[plugin.${name}]`),
46
- warn: logger.warn.bind(logger, `[plugin.${name}]`),
47
- debug: logger.debug.bind(logger, `[plugin.${name}]`),
48
- error: logger.error.bind(logger, `[plugin.${name}]`)
49
- }
50
-
51
- try {
52
- return await hook(
53
- {
54
- ...context,
55
- logger: withNameLogger
56
- },
57
- input,
58
- next
59
- )
60
- } catch (error) {
61
- if (error instanceof Error && !error.name.includes('[plugin.')) {
62
- error.name = `${error.name}[plugin.${name}]`
63
- }
64
- throw error
65
- }
66
- }
67
-
68
- return next()
69
- }
70
-
71
- export const executeHookInput = async (
72
- input: HookInput,
73
- env: Record<string, string | null | undefined> = process.env
74
- ) => {
75
- const workspaceFolder = env.__VF_PROJECT_WORKSPACE_FOLDER__ ?? input.cwd ?? process.env.HOME ?? '/'
76
- const ctxId = env.__VF_PROJECT_AI_CTX_ID__ ?? input.sessionId ?? 'default'
77
- const logPrefix = env.__VF_PROJECT_AI_LOG_PREFIX__ ?? ''
78
- const loggerBase = createLogger(
79
- workspaceFolder,
80
- ctxId,
81
- input.sessionId,
82
- logPrefix,
83
- resolveServerLogLevel(env)
84
- )
85
-
86
- const logger: typeof loggerBase = {
87
- ...loggerBase,
88
- info: (...args) => loggerBase.info(`[${input.hookEventName}]`, ...args),
89
- warn: (...args) => loggerBase.warn(`[${input.hookEventName}]`, ...args),
90
- debug: (...args) => loggerBase.debug(`[${input.hookEventName}]`, ...args),
91
- error: (...args) => loggerBase.error(`[${input.hookEventName}]`, ...args)
92
- }
93
-
94
- resetConfigCache()
95
- const jsonVariables = {
96
- ...env,
97
- WORKSPACE_FOLDER: workspaceFolder,
98
- __VF_PROJECT_WORKSPACE_FOLDER__: workspaceFolder
99
- }
100
- const [config, userConfig] = await loadConfig({ jsonVariables })
101
- const plugins = [
102
- ...await resolvePlugins(config?.plugins ?? []),
103
- ...await resolvePlugins(userConfig?.plugins ?? [])
104
- ]
105
-
106
- return callPluginHook(
107
- input.hookEventName,
108
- { logger },
109
- input as HookInputs[typeof input.hookEventName],
110
- plugins
111
- )
112
- }
113
-
114
- export const readHookInput = async () => {
115
- const stdoutBuffer = await new Promise<Buffer>((resolve) => {
116
- const chunks: Buffer[] = []
117
- process.stdin.on('data', chunk => chunks.push(chunk))
118
- process.stdin.once('end', () => resolve(Buffer.concat(chunks)))
119
- })
120
-
121
- return transformCamelKey<HookInput>(
122
- JSON.parse(stdoutBuffer.toString() || '{}')
123
- )
124
- }
125
-
126
- export const runHookCli = async () => {
127
- try {
128
- const input = await readHookInput()
129
- const result = await executeHookInput(input)
130
- console.log(JSON.stringify(result))
131
- } catch (error) {
132
- console.log(
133
- JSON.stringify({
134
- continue: false,
135
- stopReason: `run hook error: ${String(error)}`
136
- } satisfies HookOutputCore)
137
- )
138
- }
139
- }
package/src/hooks/type.ts DELETED
@@ -1,120 +0,0 @@
1
- import type { ToolInput, ToolOutput } from '../tools'
2
-
3
- /**
4
- * https://docs.anthropic.com/en/docs/claude-code/hooks#hook-input
5
- */
6
- export interface HookInputCore {
7
- cwd: string
8
- sessionId: string
9
- hookEventName: keyof HookInputs
10
- }
11
-
12
- export interface HookInputs {
13
- /**
14
- * https://docs.anthropic.com/en/docs/claude-code/hooks#pretooluse-input
15
- */
16
- PreToolUse: HookInputCore & ToolInput
17
- /**
18
- * https://docs.anthropic.com/en/docs/claude-code/hooks#posttooluse-input
19
- */
20
- PostToolUse: HookInputCore & ToolOutput
21
- Notification: HookInputCore
22
- UserPromptSubmit: HookInputCore & { prompt: string }
23
- Stop: HookInputCore
24
- SubagentStop: HookInputCore
25
- PreCompact: HookInputCore
26
- SessionStart: HookInputCore
27
- SessionEnd: HookInputCore & {
28
- reason: string
29
- }
30
-
31
- StartTasks: HookInputCore & {
32
- tasks: Array<{
33
- description: string
34
- type: 'default' | 'spec' | 'entity'
35
- name?: string
36
- adapter?: string
37
- background?: boolean
38
- }>
39
- }
40
- GenerateSystemPrompt: HookInputCore & {
41
- type?: 'spec' | 'entity'
42
- name?: string
43
- data?: unknown
44
- }
45
- TaskStart: HookInputCore & {
46
- adapter?: string
47
- options: unknown
48
- adapterOptions: unknown
49
- }
50
- TaskStop: HookInputCore & {
51
- exitCode?: number
52
- stderr?: string
53
- adapter?: string
54
-
55
- options: unknown
56
- adapterOptions: unknown
57
- }
58
- }
59
-
60
- export type HookInput = HookInputs[keyof HookInputs]
61
-
62
- /**
63
- * https://docs.anthropic.com/en/docs/claude-code/hooks#common-json-fields
64
- */
65
- export interface HookOutputCore {
66
- /**
67
- * Whether Claude should continue after hook execution
68
- * @default true
69
- */
70
- continue?: boolean
71
- /**
72
- * Message shown when continue is false
73
- */
74
- stopReason?: string
75
- /**
76
- * Hide stdout from transcript mode
77
- * @default false
78
- */
79
- suppressOutput?: boolean
80
- /**
81
- * Optional warning message shown to the user
82
- */
83
- systemMessage?: string
84
- }
85
-
86
- export interface HookOutputs {
87
- /**
88
- * https://docs.anthropic.com/en/docs/claude-code/hooks#pretooluse-decision-control
89
- */
90
- PreToolUse: HookOutputCore & {
91
- hookSpecificOutput?: {
92
- hookEventName: 'PreToolUse'
93
- permissionDecision: 'allow' | 'deny' | 'ask'
94
- permissionDecisionReason: string
95
- }
96
- }
97
- /**
98
- * https://docs.anthropic.com/en/docs/claude-code/hooks#posttooluse-decision-control
99
- */
100
- PostToolUse: HookOutputCore & {
101
- hookSpecificOutput?: {
102
- hookEventName: 'PostToolUse'
103
- additionalContext: string
104
- }
105
- }
106
- Notification: HookOutputCore
107
- UserPromptSubmit: HookOutputCore
108
- Stop: HookOutputCore
109
- SessionStart: HookOutputCore
110
- SessionEnd: HookOutputCore
111
- SubagentStop: HookOutputCore
112
- PreCompact: HookOutputCore
113
-
114
- StartTasks: HookOutputCore
115
- GenerateSystemPrompt: HookOutputCore
116
- TaskStart: HookOutputCore
117
- TaskStop: HookOutputCore
118
- }
119
-
120
- export type HookOutput = HookOutputs[keyof HookOutputs]
@@ -1,58 +0,0 @@
1
- import * as fs from 'node:fs/promises'
2
- import { dirname, resolve } from 'node:path'
3
-
4
- import type { Cache } from '@vibe-forge/core'
5
-
6
- declare module '@vibe-forge/core' {
7
- interface Cache {
8
- }
9
- }
10
-
11
- export const getCachePath = (
12
- cwd: string,
13
- taskId: string,
14
- sessionId: string | undefined,
15
- key: keyof Cache
16
- ) => {
17
- const taskDir = resolve(cwd, '.ai/caches', taskId)
18
- const cacheDir = sessionId ? resolve(taskDir, sessionId) : taskDir
19
- return resolve(cacheDir, `${key}.json`)
20
- }
21
-
22
- export const setCache = async <K extends keyof Cache>(
23
- cwd: string,
24
- taskId: string,
25
- sessionId: string | undefined,
26
- key: K,
27
- value: Cache[K]
28
- ) => {
29
- const cachePath = getCachePath(cwd, taskId, sessionId, key)
30
- const cacheDir = dirname(cachePath)
31
- try {
32
- await fs.access(cacheDir)
33
- } catch {
34
- await fs.mkdir(cacheDir, { recursive: true })
35
- }
36
- await fs.writeFile(cachePath, JSON.stringify(value, null, 2), {
37
- flag: 'w'
38
- })
39
- return { cachePath }
40
- }
41
-
42
- export const getCache = async <K extends keyof Cache>(
43
- cwd: string,
44
- taskId: string,
45
- sessionId: string | undefined,
46
- key: K
47
- ): Promise<Cache[K] | undefined> => {
48
- const cachePath = getCachePath(cwd, taskId, sessionId, key)
49
- try {
50
- await fs.access(cachePath)
51
- } catch (error) {
52
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
53
- return undefined
54
- }
55
- throw error
56
- }
57
- return JSON.parse(await fs.readFile(cachePath, 'utf-8'))
58
- }
@@ -1,89 +0,0 @@
1
- import { createWriteStream, existsSync, mkdirSync } from 'node:fs'
2
- import { dirname, resolve } from 'node:path'
3
-
4
- import type { LogLevel } from '#~/env.js'
5
-
6
- export interface Logger {
7
- stream: NodeJS.WritableStream
8
- info: (...args: unknown[]) => void
9
- warn: (...args: unknown[]) => void
10
- debug: (...args: unknown[]) => void
11
- error: (...args: unknown[]) => void
12
- }
13
-
14
- const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
15
- debug: 10,
16
- info: 20,
17
- warn: 30,
18
- error: 40
19
- }
20
-
21
- export const createLogger = (
22
- cwd: string,
23
- taskId: string,
24
- sessionId: string,
25
- logPrefix = '',
26
- level: LogLevel = 'info'
27
- ) => {
28
- const normalizedSessionId = sessionId ?? 'default'
29
- const taskDir = resolve(
30
- cwd,
31
- `.ai${logPrefix}/logs/${taskId}`
32
- )
33
-
34
- const loggerFilePath = resolve(
35
- taskDir,
36
- `${normalizedSessionId}.log.md`
37
- )
38
- // 默认日志文件不存在时,创建一个默认的日志文件
39
- if (!existsSync(loggerFilePath)) {
40
- mkdirSync(dirname(loggerFilePath), { recursive: true })
41
- }
42
- const loggerStream = createWriteStream(loggerFilePath, {
43
- flags: 'a'
44
- })
45
- const createLog = (tag: string, currentLevel: LogLevel) => (...args: unknown[]) => {
46
- if (LOG_LEVEL_PRIORITY[currentLevel] < LOG_LEVEL_PRIORITY[level]) {
47
- return
48
- }
49
- const msg = args
50
- .map((arg) => {
51
- if (typeof arg === 'string') {
52
- return arg
53
- }
54
- if (arg instanceof Error) {
55
- return (
56
- '\n```text\n' +
57
- `${arg.stack}\n` +
58
- '```'
59
- )
60
- }
61
- return (
62
- '\n```json\n' +
63
- `${JSON.stringify(arg, null, 2)}\n` +
64
- '```'
65
- )
66
- })
67
- .join(' ')
68
- const now = new Date().toLocaleString()
69
- if (loggerStream.writableEnded) {
70
- const tempLoggerStream = createWriteStream(loggerFilePath, {
71
- flags: 'a'
72
- })
73
- tempLoggerStream.write(
74
- `# [${now}] __E__ UNEXPECTED LOGGER STREAM ENDED\n`
75
- )
76
- tempLoggerStream.write(`# [${now}] __${tag}__ ${msg}\n`)
77
- tempLoggerStream.end()
78
- return
79
- }
80
- loggerStream.write(`# [${now}] __${tag}__ ${msg}\n`)
81
- }
82
- return {
83
- stream: loggerStream,
84
- info: createLog('I', 'info'),
85
- warn: createLog('W', 'warn'),
86
- debug: createLog('D', 'debug'),
87
- error: createLog('E', 'error')
88
- }
89
- }