@vibe-forge/tsconfigs 0.8.0 → 0.9.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/dist/apps/cli/__tests__/clear.spec.d.ts +1 -0
- package/dist/apps/cli/__tests__/clear.spec.js +72 -0
- package/dist/apps/cli/src/commands/clear.d.ts +4 -0
- package/dist/apps/cli/src/commands/clear.js +62 -39
- package/dist/apps/client/src/main.d.ts +1 -0
- package/dist/apps/client/src/main.js +1 -0
- package/dist/apps/server/__tests__/db/connection.spec.d.ts +1 -0
- package/dist/apps/server/__tests__/db/connection.spec.js +58 -0
- package/dist/apps/server/__tests__/db/index.spec.js +129 -5
- package/dist/apps/server/__tests__/db/schema.spec.js +3 -6
- package/dist/apps/server/__tests__/db/sqlite.spec.d.ts +1 -0
- package/dist/apps/server/__tests__/db/sqlite.spec.js +51 -0
- package/dist/apps/server/__tests__/services/session-start.spec.js +3 -3
- package/dist/apps/server/src/db/automation/repo.d.ts +2 -2
- package/dist/apps/server/src/db/channelSessions/repo.d.ts +2 -2
- package/dist/apps/server/src/db/connection.d.ts +2 -2
- package/dist/apps/server/src/db/connection.js +2 -2
- package/dist/apps/server/src/db/index.d.ts +2 -2
- package/dist/apps/server/src/db/schema.d.ts +3 -3
- package/dist/apps/server/src/db/sessions/messages.repo.d.ts +2 -2
- package/dist/apps/server/src/db/sessions/repo.d.ts +2 -2
- package/dist/apps/server/src/db/sessions/repo.js +1 -1
- package/dist/apps/server/src/db/sessions/tags.repo.d.ts +2 -2
- package/dist/apps/server/src/db/sqlite.d.ts +44 -0
- package/dist/apps/server/src/db/sqlite.js +83 -0
- package/dist/apps/server/src/index.js +1 -1
- package/dist/apps/server/src/routes/config.js +1 -1
- package/dist/apps/server/src/services/config/index.d.ts +2 -8
- package/dist/apps/server/src/services/config/index.js +3 -39
- package/dist/apps/server/src/services/session/index.js +1 -1
- package/dist/apps/server/src/services/session/notification.js +1 -1
- package/dist/packages/adapters/claude-code/__tests__/default-config.spec.js +15 -0
- package/dist/packages/adapters/claude-code/__tests__/prepare.spec.js +61 -1
- package/dist/packages/adapters/claude-code/__tests__/router-daemon.spec.d.ts +1 -0
- package/dist/packages/adapters/claude-code/__tests__/router-daemon.spec.js +183 -0
- package/dist/packages/adapters/claude-code/src/adapter-config.d.ts +1 -0
- package/dist/packages/adapters/claude-code/src/runtime/init.js +0 -45
- package/dist/packages/adapters/claude-code/src/runtime/prepare.d.ts +6 -9
- package/dist/packages/adapters/claude-code/src/runtime/prepare.js +25 -27
- package/dist/packages/adapters/claude-code/src/runtime/router-daemon.d.ts +19 -0
- package/dist/packages/adapters/claude-code/src/runtime/router-daemon.js +189 -0
- package/dist/packages/channels/lark/src/index.d.ts +4 -4
- package/dist/packages/config/__tests__/load.spec.js +219 -2
- package/dist/packages/config/__tests__/merge.spec.d.ts +1 -0
- package/dist/packages/config/__tests__/merge.spec.js +92 -0
- package/dist/packages/config/src/index.d.ts +1 -0
- package/dist/packages/config/src/index.js +1 -0
- package/dist/packages/config/src/load.d.ts +1 -1
- package/dist/packages/config/src/load.js +167 -53
- package/dist/packages/config/src/merge.d.ts +7 -0
- package/dist/packages/config/src/merge.js +92 -0
- package/dist/packages/tsconfigs/tsconfig.bundler.test.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.bundler.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.bundler.web.test.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.bundler.web.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.node.test.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.node.tsbuildinfo +1 -1
- package/dist/packages/types/src/config.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/adapter-asset-plan.spec.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/adapter-asset-plan.spec.js +121 -0
- package/dist/packages/workspace-assets/__tests__/bundle.spec.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/bundle.spec.js +61 -0
- package/dist/packages/workspace-assets/__tests__/prompt-selection.spec.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/prompt-selection.spec.js +29 -0
- package/dist/packages/workspace-assets/__tests__/snapshot.d.ts +15 -0
- package/dist/packages/workspace-assets/__tests__/snapshot.js +203 -0
- package/dist/packages/workspace-assets/__tests__/test-helpers.d.ts +2 -0
- package/dist/packages/workspace-assets/__tests__/test-helpers.js +17 -0
- package/dist/packages/workspace-assets/__tests__/workspace-assets.snapshot.spec.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/workspace-assets.snapshot.spec.js +172 -0
- package/package.json +1 -1
|
@@ -50,6 +50,7 @@ export interface NotificationConfig {
|
|
|
50
50
|
events?: Partial<Record<NotificationTrigger, NotificationEventConfig>>;
|
|
51
51
|
}
|
|
52
52
|
export interface Config {
|
|
53
|
+
extend?: string | string[];
|
|
53
54
|
baseDir?: string;
|
|
54
55
|
adapters?: AdapterConfigMap;
|
|
55
56
|
models?: Record<string, ModelMetadataConfig>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { buildAdapterAssetPlan, resolvePromptAssetSelection, resolveWorkspaceAssetBundle } from '#~/index.js';
|
|
4
|
+
import { createWorkspace, writeDocument } from './test-helpers';
|
|
5
|
+
describe('buildAdapterAssetPlan', () => {
|
|
6
|
+
it('builds codex diagnostics for prompt, mcp, native hooks, and unsupported claude native plugins', async () => {
|
|
7
|
+
const workspace = await createWorkspace();
|
|
8
|
+
await writeDocument(join(workspace, '.ai.config.json'), JSON.stringify({
|
|
9
|
+
plugins: {
|
|
10
|
+
logger: {}
|
|
11
|
+
},
|
|
12
|
+
enabledPlugins: {
|
|
13
|
+
logger: true
|
|
14
|
+
},
|
|
15
|
+
mcpServers: {
|
|
16
|
+
docs: {
|
|
17
|
+
command: 'npx',
|
|
18
|
+
args: ['docs-server']
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}));
|
|
22
|
+
await writeDocument(join(workspace, '.ai/skills/research/SKILL.md'), '---\ndescription: 检索资料\n---\n阅读 README.md');
|
|
23
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
24
|
+
cwd: workspace,
|
|
25
|
+
configs: [{
|
|
26
|
+
plugins: {
|
|
27
|
+
logger: {}
|
|
28
|
+
},
|
|
29
|
+
enabledPlugins: {
|
|
30
|
+
logger: true
|
|
31
|
+
},
|
|
32
|
+
mcpServers: {
|
|
33
|
+
docs: {
|
|
34
|
+
command: 'npx',
|
|
35
|
+
args: ['docs-server']
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, undefined],
|
|
39
|
+
useDefaultVibeForgeMcpServer: false
|
|
40
|
+
});
|
|
41
|
+
const [, resolvedOptions] = await resolvePromptAssetSelection({
|
|
42
|
+
bundle,
|
|
43
|
+
type: undefined,
|
|
44
|
+
name: undefined,
|
|
45
|
+
input: {
|
|
46
|
+
skills: {
|
|
47
|
+
include: ['research']
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
const plan = buildAdapterAssetPlan({
|
|
52
|
+
adapter: 'codex',
|
|
53
|
+
bundle,
|
|
54
|
+
options: {
|
|
55
|
+
promptAssetIds: resolvedOptions.promptAssetIds,
|
|
56
|
+
mcpServers: resolvedOptions.mcpServers,
|
|
57
|
+
skills: {
|
|
58
|
+
include: ['research']
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
expect(plan.mcpServers).toHaveProperty('docs');
|
|
63
|
+
expect(plan.native.codexHooks?.supportedEvents).toEqual([
|
|
64
|
+
'SessionStart',
|
|
65
|
+
'UserPromptSubmit',
|
|
66
|
+
'PreToolUse',
|
|
67
|
+
'PostToolUse',
|
|
68
|
+
'Stop'
|
|
69
|
+
]);
|
|
70
|
+
expect(plan.diagnostics).toEqual(expect.arrayContaining([
|
|
71
|
+
expect.objectContaining({
|
|
72
|
+
adapter: 'codex',
|
|
73
|
+
status: 'prompt'
|
|
74
|
+
}),
|
|
75
|
+
expect.objectContaining({
|
|
76
|
+
adapter: 'codex',
|
|
77
|
+
status: 'native',
|
|
78
|
+
assetId: 'hookPlugin:project:logger'
|
|
79
|
+
}),
|
|
80
|
+
expect.objectContaining({
|
|
81
|
+
adapter: 'codex',
|
|
82
|
+
status: 'translated',
|
|
83
|
+
assetId: 'mcpServer:project:docs'
|
|
84
|
+
}),
|
|
85
|
+
expect.objectContaining({
|
|
86
|
+
adapter: 'codex',
|
|
87
|
+
status: 'skipped',
|
|
88
|
+
assetId: 'nativePlugin:claude-code:logger'
|
|
89
|
+
})
|
|
90
|
+
]));
|
|
91
|
+
});
|
|
92
|
+
it('builds opencode overlays for skills and native commands', async () => {
|
|
93
|
+
const workspace = await createWorkspace();
|
|
94
|
+
await writeDocument(join(workspace, '.ai/skills/research/SKILL.md'), '---\ndescription: 检索资料\n---\n阅读 README.md');
|
|
95
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/opencode/commands/review.md'), '# review');
|
|
96
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
97
|
+
cwd: workspace,
|
|
98
|
+
configs: [undefined, undefined],
|
|
99
|
+
useDefaultVibeForgeMcpServer: false
|
|
100
|
+
});
|
|
101
|
+
const plan = buildAdapterAssetPlan({
|
|
102
|
+
adapter: 'opencode',
|
|
103
|
+
bundle,
|
|
104
|
+
options: {
|
|
105
|
+
skills: {
|
|
106
|
+
include: ['research']
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
expect(plan.overlays).toEqual(expect.arrayContaining([
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
kind: 'skill',
|
|
113
|
+
targetPath: 'skills/research'
|
|
114
|
+
}),
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
kind: 'command',
|
|
117
|
+
targetPath: 'commands/review.md'
|
|
118
|
+
})
|
|
119
|
+
]));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { resolveWorkspaceAssetBundle } from '#~/index.js';
|
|
5
|
+
import { createWorkspace, writeDocument } from './test-helpers';
|
|
6
|
+
describe('resolveWorkspaceAssetBundle', () => {
|
|
7
|
+
it('treats enabledPlugins as a global asset switch', async () => {
|
|
8
|
+
const workspace = await createWorkspace();
|
|
9
|
+
await writeDocument(join(workspace, '.ai.config.json'), JSON.stringify({
|
|
10
|
+
plugins: {
|
|
11
|
+
logger: {}
|
|
12
|
+
},
|
|
13
|
+
enabledPlugins: {
|
|
14
|
+
logger: false,
|
|
15
|
+
demo: false
|
|
16
|
+
}
|
|
17
|
+
}));
|
|
18
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/skills/research/SKILL.md'), '---\ndescription: 检索资料\n---\n阅读 README.md');
|
|
19
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/rules/review.md'), '---\ndescription: 评审规则\n---\n必须检查风险');
|
|
20
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/mcp/browser.json'), JSON.stringify({ command: 'npx', args: ['browser-server'] }));
|
|
21
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/opencode/commands/review.md'), '# review');
|
|
22
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
23
|
+
cwd: workspace,
|
|
24
|
+
configs: [{
|
|
25
|
+
plugins: {
|
|
26
|
+
logger: {}
|
|
27
|
+
},
|
|
28
|
+
enabledPlugins: {
|
|
29
|
+
logger: false,
|
|
30
|
+
demo: false
|
|
31
|
+
}
|
|
32
|
+
}, undefined],
|
|
33
|
+
useDefaultVibeForgeMcpServer: false
|
|
34
|
+
});
|
|
35
|
+
expect(bundle.skills).toHaveLength(0);
|
|
36
|
+
expect(bundle.rules).toHaveLength(0);
|
|
37
|
+
expect(Object.keys(bundle.mcpServers)).toHaveLength(0);
|
|
38
|
+
expect(bundle.hookPlugins).toHaveLength(0);
|
|
39
|
+
expect(bundle.assets.some((asset) => asset.pluginId === 'demo' && asset.enabled))
|
|
40
|
+
.toBe(false);
|
|
41
|
+
});
|
|
42
|
+
it('adds the built-in Vibe Forge MCP server when enabled and omits it when disabled', async () => {
|
|
43
|
+
const workspace = await createWorkspace();
|
|
44
|
+
const enabledBundle = await resolveWorkspaceAssetBundle({
|
|
45
|
+
cwd: workspace,
|
|
46
|
+
configs: [undefined, undefined],
|
|
47
|
+
useDefaultVibeForgeMcpServer: true
|
|
48
|
+
});
|
|
49
|
+
expect(enabledBundle.mcpServers).toHaveProperty('vibe-forge');
|
|
50
|
+
expect(enabledBundle.mcpServers['vibe-forge']?.payload.config).toEqual(expect.objectContaining({
|
|
51
|
+
command: process.execPath,
|
|
52
|
+
args: [expect.stringMatching(/packages\/mcp\/cli\.js$/)]
|
|
53
|
+
}));
|
|
54
|
+
const disabledBundle = await resolveWorkspaceAssetBundle({
|
|
55
|
+
cwd: workspace,
|
|
56
|
+
configs: [undefined, undefined],
|
|
57
|
+
useDefaultVibeForgeMcpServer: false
|
|
58
|
+
});
|
|
59
|
+
expect(disabledBundle.mcpServers).not.toHaveProperty('vibe-forge');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { resolvePromptAssetSelection, resolveWorkspaceAssetBundle } from '#~/index.js';
|
|
4
|
+
import { createWorkspace, writeDocument } from './test-helpers';
|
|
5
|
+
describe('resolvePromptAssetSelection', () => {
|
|
6
|
+
it('prefers project prompt assets over plugin assets with the same identifier', async () => {
|
|
7
|
+
const workspace = await createWorkspace();
|
|
8
|
+
await writeDocument(join(workspace, '.ai/specs/release.md'), '---\ndescription: 项目发布流程\n---\n执行项目发布');
|
|
9
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/specs/release/index.md'), '---\ndescription: 插件发布流程\n---\n执行插件发布');
|
|
10
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
11
|
+
cwd: workspace,
|
|
12
|
+
configs: [undefined, undefined],
|
|
13
|
+
useDefaultVibeForgeMcpServer: false
|
|
14
|
+
});
|
|
15
|
+
const [data, resolvedOptions] = await resolvePromptAssetSelection({
|
|
16
|
+
bundle,
|
|
17
|
+
type: 'spec',
|
|
18
|
+
name: 'release'
|
|
19
|
+
});
|
|
20
|
+
expect(data.targetBody).toContain('执行项目发布');
|
|
21
|
+
expect(data.targetBody).not.toContain('执行插件发布');
|
|
22
|
+
expect(data.specs).toHaveLength(1);
|
|
23
|
+
expect(resolvedOptions.systemPrompt).toContain('项目发布流程');
|
|
24
|
+
expect(resolvedOptions.systemPrompt).not.toContain('插件发布流程');
|
|
25
|
+
expect(resolvedOptions.promptAssetIds).toEqual(expect.arrayContaining([
|
|
26
|
+
'spec:.ai/specs/release.md'
|
|
27
|
+
]));
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AdapterAssetPlan, Filter, PromptAssetResolution, WorkspaceAssetBundle } from '@vibe-forge/types';
|
|
2
|
+
export declare const serializeWorkspaceAssetsSnapshot: (params: {
|
|
3
|
+
cwd: string;
|
|
4
|
+
bundle: WorkspaceAssetBundle;
|
|
5
|
+
selection: {
|
|
6
|
+
resolution: PromptAssetResolution;
|
|
7
|
+
options: {
|
|
8
|
+
systemPrompt?: string;
|
|
9
|
+
tools?: Filter;
|
|
10
|
+
mcpServers?: Filter;
|
|
11
|
+
promptAssetIds?: string[];
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
plans: AdapterAssetPlan[];
|
|
15
|
+
}) => string;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { resolveDocumentName, resolveSpecIdentifier } from '@vibe-forge/utils';
|
|
3
|
+
import { isOpenCodeOverlayAsset } from '#~/internal-types.js';
|
|
4
|
+
const sortStrings = (values) => [...values].sort((left, right) => left.localeCompare(right));
|
|
5
|
+
const isConfigNativePluginAsset = (asset) => !isOpenCodeOverlayAsset(asset);
|
|
6
|
+
const sanitizeValue = (value, cwd) => (value
|
|
7
|
+
.replaceAll(cwd, '<workspace>')
|
|
8
|
+
.replaceAll(process.execPath, '<node-path>'));
|
|
9
|
+
const normalizeValue = (value, cwd) => {
|
|
10
|
+
if (typeof value === 'string') {
|
|
11
|
+
return sanitizeValue(value, cwd);
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
return value.map(item => normalizeValue(item, cwd));
|
|
15
|
+
}
|
|
16
|
+
if (value != null && typeof value === 'object') {
|
|
17
|
+
return Object.fromEntries(Object.entries(value)
|
|
18
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
19
|
+
.map(([key, entry]) => [key, normalizeValue(entry, cwd)]));
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
const sortFilter = (filter) => {
|
|
24
|
+
if (filter == null)
|
|
25
|
+
return undefined;
|
|
26
|
+
return {
|
|
27
|
+
include: filter.include == null ? undefined : sortStrings(filter.include),
|
|
28
|
+
exclude: filter.exclude == null ? undefined : sortStrings(filter.exclude)
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const resolveDefinitionIdentifier = (kind, definition) => {
|
|
32
|
+
if (kind === 'spec') {
|
|
33
|
+
return resolveSpecIdentifier(definition.path, definition.attributes.name);
|
|
34
|
+
}
|
|
35
|
+
if (kind === 'entity') {
|
|
36
|
+
return resolveDocumentName(definition.path, definition.attributes.name, ['readme.md', 'index.json']);
|
|
37
|
+
}
|
|
38
|
+
if (kind === 'skill') {
|
|
39
|
+
return resolveDocumentName(definition.path, definition.attributes.name, ['skill.md']);
|
|
40
|
+
}
|
|
41
|
+
return resolveDocumentName(definition.path, definition.attributes.name);
|
|
42
|
+
};
|
|
43
|
+
const summarizeDefinition = (kind, definition, cwd) => ({
|
|
44
|
+
identifier: resolveDefinitionIdentifier(kind, definition),
|
|
45
|
+
path: sanitizeValue(definition.path, cwd),
|
|
46
|
+
attributes: normalizeValue(definition.attributes, cwd),
|
|
47
|
+
body: definition.body.trim()
|
|
48
|
+
});
|
|
49
|
+
const summarizeDocumentAsset = (asset, cwd) => ({
|
|
50
|
+
id: asset.id,
|
|
51
|
+
kind: asset.kind,
|
|
52
|
+
origin: asset.origin,
|
|
53
|
+
scope: asset.scope,
|
|
54
|
+
pluginId: asset.pluginId,
|
|
55
|
+
enabled: asset.enabled,
|
|
56
|
+
targets: sortStrings(asset.targets),
|
|
57
|
+
definition: summarizeDefinition(asset.kind, asset.payload.definition, cwd)
|
|
58
|
+
});
|
|
59
|
+
const summarizeMcpServer = (asset, cwd) => ({
|
|
60
|
+
id: asset.id,
|
|
61
|
+
origin: asset.origin,
|
|
62
|
+
scope: asset.scope,
|
|
63
|
+
pluginId: asset.pluginId,
|
|
64
|
+
enabled: asset.enabled,
|
|
65
|
+
targets: sortStrings(asset.targets),
|
|
66
|
+
name: asset.payload.name,
|
|
67
|
+
config: normalizeValue(asset.payload.config, cwd)
|
|
68
|
+
});
|
|
69
|
+
const summarizeHookPlugin = (asset, cwd) => ({
|
|
70
|
+
id: asset.id,
|
|
71
|
+
origin: asset.origin,
|
|
72
|
+
scope: asset.scope,
|
|
73
|
+
pluginId: asset.pluginId,
|
|
74
|
+
enabled: asset.enabled,
|
|
75
|
+
targets: sortStrings(asset.targets),
|
|
76
|
+
packageName: asset.payload.packageName,
|
|
77
|
+
config: normalizeValue(asset.payload.config, cwd)
|
|
78
|
+
});
|
|
79
|
+
const summarizeNativePlugin = (asset, cwd) => {
|
|
80
|
+
if (isOpenCodeOverlayAsset(asset)) {
|
|
81
|
+
return {
|
|
82
|
+
id: asset.id,
|
|
83
|
+
kind: asset.kind,
|
|
84
|
+
origin: asset.origin,
|
|
85
|
+
scope: asset.scope,
|
|
86
|
+
pluginId: asset.pluginId,
|
|
87
|
+
enabled: asset.enabled,
|
|
88
|
+
targets: sortStrings(asset.targets),
|
|
89
|
+
entryName: asset.payload.entryName,
|
|
90
|
+
sourcePath: sanitizeValue(asset.payload.sourcePath, cwd),
|
|
91
|
+
targetSubpath: asset.payload.targetSubpath
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
id: asset.id,
|
|
96
|
+
kind: asset.kind,
|
|
97
|
+
origin: asset.origin,
|
|
98
|
+
scope: asset.scope,
|
|
99
|
+
pluginId: asset.pluginId,
|
|
100
|
+
enabled: asset.enabled,
|
|
101
|
+
targets: sortStrings(asset.targets),
|
|
102
|
+
name: isConfigNativePluginAsset(asset) ? asset.payload.name : undefined
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
const summarizeOverlayAsset = (asset, cwd) => {
|
|
106
|
+
if (!isOpenCodeOverlayAsset(asset)) {
|
|
107
|
+
return summarizeNativePlugin(asset, cwd);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
id: asset.id,
|
|
111
|
+
kind: asset.kind,
|
|
112
|
+
origin: asset.origin,
|
|
113
|
+
scope: asset.scope,
|
|
114
|
+
pluginId: asset.pluginId,
|
|
115
|
+
enabled: asset.enabled,
|
|
116
|
+
targets: sortStrings(asset.targets),
|
|
117
|
+
entryName: asset.payload.entryName,
|
|
118
|
+
sourcePath: sanitizeValue(asset.payload.sourcePath, cwd),
|
|
119
|
+
targetSubpath: asset.payload.targetSubpath
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const summarizeDiagnostics = (diagnostics) => ([...diagnostics]
|
|
123
|
+
.sort((left, right) => {
|
|
124
|
+
const assetIdDiff = left.assetId.localeCompare(right.assetId);
|
|
125
|
+
if (assetIdDiff !== 0)
|
|
126
|
+
return assetIdDiff;
|
|
127
|
+
return left.status.localeCompare(right.status);
|
|
128
|
+
})
|
|
129
|
+
.map(diagnostic => ({
|
|
130
|
+
assetId: diagnostic.assetId,
|
|
131
|
+
adapter: diagnostic.adapter,
|
|
132
|
+
status: diagnostic.status,
|
|
133
|
+
reason: diagnostic.reason
|
|
134
|
+
})));
|
|
135
|
+
const summarizePlan = (plan, cwd) => ({
|
|
136
|
+
adapter: plan.adapter,
|
|
137
|
+
mcpServers: normalizeValue(plan.mcpServers, cwd),
|
|
138
|
+
overlays: [...plan.overlays]
|
|
139
|
+
.sort((left, right) => left.assetId.localeCompare(right.assetId))
|
|
140
|
+
.map(entry => ({
|
|
141
|
+
assetId: entry.assetId,
|
|
142
|
+
kind: entry.kind,
|
|
143
|
+
sourcePath: sanitizeValue(entry.sourcePath, cwd),
|
|
144
|
+
targetPath: entry.targetPath
|
|
145
|
+
})),
|
|
146
|
+
native: normalizeValue(plan.native, cwd),
|
|
147
|
+
diagnostics: summarizeDiagnostics(plan.diagnostics)
|
|
148
|
+
});
|
|
149
|
+
const summarizeSelection = (resolution, options, cwd) => ({
|
|
150
|
+
resolution: {
|
|
151
|
+
rules: resolution.rules.map(rule => summarizeDefinition('rule', rule, cwd)),
|
|
152
|
+
targetSkills: resolution.targetSkills.map(skill => summarizeDefinition('skill', skill, cwd)),
|
|
153
|
+
entities: resolution.entities.map(entity => summarizeDefinition('entity', entity, cwd)),
|
|
154
|
+
skills: resolution.skills.map(skill => summarizeDefinition('skill', skill, cwd)),
|
|
155
|
+
specs: resolution.specs.map(spec => summarizeDefinition('spec', spec, cwd)),
|
|
156
|
+
targetBody: resolution.targetBody.trim(),
|
|
157
|
+
promptAssetIds: sortStrings(resolution.promptAssetIds)
|
|
158
|
+
},
|
|
159
|
+
options: {
|
|
160
|
+
systemPrompt: options.systemPrompt?.trim(),
|
|
161
|
+
tools: sortFilter(options.tools),
|
|
162
|
+
mcpServers: sortFilter(options.mcpServers),
|
|
163
|
+
promptAssetIds: options.promptAssetIds == null ? undefined : sortStrings(options.promptAssetIds)
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
export const serializeWorkspaceAssetsSnapshot = (params) => {
|
|
167
|
+
const { bundle, cwd } = params;
|
|
168
|
+
const overlayAssets = bundle.assets.filter((asset) => (asset.kind === 'agent' ||
|
|
169
|
+
asset.kind === 'command' ||
|
|
170
|
+
asset.kind === 'mode' ||
|
|
171
|
+
(asset.kind === 'nativePlugin' && isOpenCodeOverlayAsset(asset))));
|
|
172
|
+
const claudeNativePlugins = bundle.assets.filter((asset) => (asset.kind === 'nativePlugin' && !isOpenCodeOverlayAsset(asset)));
|
|
173
|
+
const snapshot = {
|
|
174
|
+
bundle: {
|
|
175
|
+
cwd: '<workspace>',
|
|
176
|
+
enabledPlugins: normalizeValue(bundle.enabledPlugins, cwd),
|
|
177
|
+
extraKnownMarketplaces: normalizeValue(bundle.extraKnownMarketplaces, cwd),
|
|
178
|
+
defaultIncludeMcpServers: sortStrings(bundle.defaultIncludeMcpServers),
|
|
179
|
+
defaultExcludeMcpServers: sortStrings(bundle.defaultExcludeMcpServers),
|
|
180
|
+
rules: bundle.rules.map(rule => summarizeDocumentAsset(rule, cwd)),
|
|
181
|
+
specs: bundle.specs.map(spec => summarizeDocumentAsset(spec, cwd)),
|
|
182
|
+
entities: bundle.entities.map(entity => summarizeDocumentAsset(entity, cwd)),
|
|
183
|
+
skills: bundle.skills.map(skill => summarizeDocumentAsset(skill, cwd)),
|
|
184
|
+
mcpServers: Object.values(bundle.mcpServers)
|
|
185
|
+
.sort((left, right) => left.payload.name.localeCompare(right.payload.name))
|
|
186
|
+
.map(server => summarizeMcpServer(server, cwd)),
|
|
187
|
+
hookPlugins: bundle.hookPlugins
|
|
188
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
189
|
+
.map(plugin => summarizeHookPlugin(plugin, cwd)),
|
|
190
|
+
claudeNativePlugins: claudeNativePlugins
|
|
191
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
192
|
+
.map(asset => summarizeNativePlugin(asset, cwd)),
|
|
193
|
+
opencodeOverlayAssets: overlayAssets
|
|
194
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
195
|
+
.map(asset => summarizeOverlayAsset(asset, cwd))
|
|
196
|
+
},
|
|
197
|
+
selection: summarizeSelection(params.selection.resolution, params.selection.options, cwd),
|
|
198
|
+
plans: Object.fromEntries([...params.plans]
|
|
199
|
+
.sort((left, right) => left.adapter.localeCompare(right.adapter))
|
|
200
|
+
.map(plan => [plan.adapter, summarizePlan(plan, cwd)]))
|
|
201
|
+
};
|
|
202
|
+
return `${JSON.stringify(snapshot, null, 2)}\n`;
|
|
203
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { afterEach } from 'vitest';
|
|
5
|
+
const tempDirs = [];
|
|
6
|
+
export const createWorkspace = async () => {
|
|
7
|
+
const dir = await mkdtemp(join(tmpdir(), 'workspace-assets-'));
|
|
8
|
+
tempDirs.push(dir);
|
|
9
|
+
return dir;
|
|
10
|
+
};
|
|
11
|
+
export const writeDocument = async (filePath, content) => {
|
|
12
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
13
|
+
await writeFile(filePath, content);
|
|
14
|
+
};
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })));
|
|
17
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { buildAdapterAssetPlan, resolvePromptAssetSelection, resolveWorkspaceAssetBundle } from '#~/index.js';
|
|
5
|
+
import { serializeWorkspaceAssetsSnapshot } from './snapshot';
|
|
6
|
+
import { createWorkspace, writeDocument } from './test-helpers';
|
|
7
|
+
const resolveSnapshotPath = (name) => (fileURLToPath(new URL(`./__snapshots__/${name}.snapshot.json`, import.meta.url)));
|
|
8
|
+
describe('workspace assets snapshots', () => {
|
|
9
|
+
it('projects a rich workspace bundle, prompt selection, and adapter plans', async () => {
|
|
10
|
+
const workspace = await createWorkspace();
|
|
11
|
+
const projectConfig = {
|
|
12
|
+
plugins: {
|
|
13
|
+
logger: {
|
|
14
|
+
level: 'info'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
enabledPlugins: {
|
|
18
|
+
logger: true,
|
|
19
|
+
demo: true,
|
|
20
|
+
legacy: false,
|
|
21
|
+
telemetry: true
|
|
22
|
+
},
|
|
23
|
+
mcpServers: {
|
|
24
|
+
docs: {
|
|
25
|
+
command: 'npx',
|
|
26
|
+
args: ['docs-server']
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
defaultIncludeMcpServers: ['docs', 'browser'],
|
|
30
|
+
extraKnownMarketplaces: {
|
|
31
|
+
internal: {
|
|
32
|
+
source: {
|
|
33
|
+
source: 'git',
|
|
34
|
+
url: 'https://plugins.internal.example.com'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const userConfig = {
|
|
40
|
+
plugins: {
|
|
41
|
+
telemetry: {
|
|
42
|
+
mode: 'summary'
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
enabledPlugins: {
|
|
46
|
+
telemetry: true
|
|
47
|
+
},
|
|
48
|
+
mcpServers: {
|
|
49
|
+
notes: {
|
|
50
|
+
command: 'node',
|
|
51
|
+
args: ['tools/notes-mcp.js']
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
defaultExcludeMcpServers: ['notes']
|
|
55
|
+
};
|
|
56
|
+
await writeDocument(join(workspace, '.ai/rules/review.md'), [
|
|
57
|
+
'---',
|
|
58
|
+
'description: 项目评审规则',
|
|
59
|
+
'always: true',
|
|
60
|
+
'---',
|
|
61
|
+
'必须检查发布改动的回归风险。'
|
|
62
|
+
].join('\n'));
|
|
63
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/rules/review.md'), [
|
|
64
|
+
'---',
|
|
65
|
+
'description: 插件评审规则',
|
|
66
|
+
'always: true',
|
|
67
|
+
'---',
|
|
68
|
+
'这是插件内的 review 规则,不应覆盖项目规则。'
|
|
69
|
+
].join('\n'));
|
|
70
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/rules/security.md'), [
|
|
71
|
+
'---',
|
|
72
|
+
'description: 插件安全规则',
|
|
73
|
+
'---',
|
|
74
|
+
'上线前要检查权限与密钥暴露。'
|
|
75
|
+
].join('\n'));
|
|
76
|
+
await writeDocument(join(workspace, '.ai/specs/release/index.md'), [
|
|
77
|
+
'---',
|
|
78
|
+
'description: 正式发布流程',
|
|
79
|
+
'rules:',
|
|
80
|
+
' - .ai/rules/review.md',
|
|
81
|
+
'skills:',
|
|
82
|
+
' - research',
|
|
83
|
+
'mcpServers:',
|
|
84
|
+
' include:',
|
|
85
|
+
' - docs',
|
|
86
|
+
' - browser',
|
|
87
|
+
' exclude:',
|
|
88
|
+
' - browser',
|
|
89
|
+
'tools:',
|
|
90
|
+
' include:',
|
|
91
|
+
' - Read',
|
|
92
|
+
' - Edit',
|
|
93
|
+
'---',
|
|
94
|
+
'执行正式发布,并整理变更摘要。'
|
|
95
|
+
].join('\n'));
|
|
96
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/specs/release/index.md'), [
|
|
97
|
+
'---',
|
|
98
|
+
'description: 插件发布流程',
|
|
99
|
+
'---',
|
|
100
|
+
'插件 release 不应覆盖项目 release。'
|
|
101
|
+
].join('\n'));
|
|
102
|
+
await writeDocument(join(workspace, '.ai/entities/architect/README.md'), [
|
|
103
|
+
'---',
|
|
104
|
+
'description: 负责拆解方案的实体',
|
|
105
|
+
'---',
|
|
106
|
+
'把发布任务拆解成执行步骤。'
|
|
107
|
+
].join('\n'));
|
|
108
|
+
await writeDocument(join(workspace, '.ai/skills/research/SKILL.md'), [
|
|
109
|
+
'---',
|
|
110
|
+
'description: 检索资料',
|
|
111
|
+
'---',
|
|
112
|
+
'先阅读 README.md,再补充结论。'
|
|
113
|
+
].join('\n'));
|
|
114
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/skills/research/SKILL.md'), [
|
|
115
|
+
'---',
|
|
116
|
+
'description: 插件 research',
|
|
117
|
+
'---',
|
|
118
|
+
'插件 research 不应覆盖项目 skill。'
|
|
119
|
+
].join('\n'));
|
|
120
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/skills/audit/SKILL.md'), [
|
|
121
|
+
'---',
|
|
122
|
+
'description: 审计输出',
|
|
123
|
+
'---',
|
|
124
|
+
'检查最终输出是否覆盖风险项。'
|
|
125
|
+
].join('\n'));
|
|
126
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/mcp/browser.json'), JSON.stringify({
|
|
127
|
+
name: 'browser',
|
|
128
|
+
command: 'npx',
|
|
129
|
+
args: ['browser-mcp']
|
|
130
|
+
}, null, 2));
|
|
131
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/opencode/plugins/demo-plugin.js'), 'export default {};\n');
|
|
132
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/opencode/agents/release-helper.md'), '# release-helper\n');
|
|
133
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/opencode/commands/review.md'), '# review\n');
|
|
134
|
+
await writeDocument(join(workspace, '.ai/plugins/demo/opencode/modes/strict.md'), '# strict\n');
|
|
135
|
+
const bundle = await resolveWorkspaceAssetBundle({
|
|
136
|
+
cwd: workspace,
|
|
137
|
+
configs: [projectConfig, userConfig],
|
|
138
|
+
useDefaultVibeForgeMcpServer: false
|
|
139
|
+
});
|
|
140
|
+
const [resolution, options] = await resolvePromptAssetSelection({
|
|
141
|
+
bundle,
|
|
142
|
+
type: 'spec',
|
|
143
|
+
name: 'release',
|
|
144
|
+
input: {
|
|
145
|
+
skills: {
|
|
146
|
+
include: ['research']
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
const adapters = ['claude-code', 'codex', 'opencode'];
|
|
151
|
+
const plans = adapters.map(adapter => (buildAdapterAssetPlan({
|
|
152
|
+
adapter,
|
|
153
|
+
bundle,
|
|
154
|
+
options: {
|
|
155
|
+
promptAssetIds: options.promptAssetIds,
|
|
156
|
+
mcpServers: options.mcpServers,
|
|
157
|
+
skills: {
|
|
158
|
+
include: ['research']
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
})));
|
|
162
|
+
await expect(serializeWorkspaceAssetsSnapshot({
|
|
163
|
+
cwd: workspace,
|
|
164
|
+
bundle,
|
|
165
|
+
selection: {
|
|
166
|
+
resolution,
|
|
167
|
+
options
|
|
168
|
+
},
|
|
169
|
+
plans
|
|
170
|
+
})).toMatchFileSnapshot(resolveSnapshotPath('workspace-assets-rich'));
|
|
171
|
+
});
|
|
172
|
+
});
|
package/package.json
CHANGED