feat: make project scaffold project-wide

This commit is contained in:
2026-09-05 12:34:36 +08:00
parent d642d7607c
commit 6710527e8f
9 changed files with 252 additions and 12 deletions

View File

@@ -7,6 +7,8 @@ import {
} from '../../electron/coding-plugins/effective-resolver';
import {
DATA_SERVICE_PLUGIN_DEFINITION,
PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID,
PROJECT_SCAFFOLD_PLUGIN_ID,
type CodingPluginDefinition,
} from '../../shared/coding-plugins';
@@ -53,6 +55,20 @@ const serverDefinition: CodingPluginDefinition = {
surfaces: {},
};
const projectScaffoldDefinition: CodingPluginDefinition = {
...skillOnlyDefinition,
id: PROJECT_SCAFFOLD_PLUGIN_ID,
displayName: 'Project Scaffold',
description: 'Project-scoped scaffold Skill',
releaseId: PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID,
provenance: { source: 'bundled', packageRoot: 'project-scaffold' },
skills: [{
id: 'makelore-project-scaffold',
entryPath: 'skills/makelore-project-scaffold/SKILL.md',
grants: [],
}],
};
const binding = { accountKey: 'account-a', epoch: 4 };
function library(runtimeStatus: 'enabled' | 'suspended' = 'enabled', catalogStatus: 'active' | 'retired' = 'active') {
@@ -487,4 +503,65 @@ describe('effective plugin resolver', () => {
.resolves.toMatchObject({ id: bundled.id, releaseId: bundled.releaseId });
expect(getInstalledRelease).not.toHaveBeenCalled();
});
it('materializes the project-wide Scaffold Skill after project enablement without Agent assignment', async () => {
const scaffoldLibrary = {
...library(),
items: [{
...library().items[0],
pluginId: PROJECT_SCAFFOLD_PLUGIN_ID,
title: projectScaffoldDefinition.displayName,
}],
};
const effective = createEffectivePluginResolver({
definitions: [projectScaffoldDefinition],
getAccountBinding: () => binding,
getLibrary: vi.fn(async () => scaffoldLibrary),
getEnabledPluginIds: vi.fn(async () => [PROJECT_SCAFFOLD_PLUGIN_ID]),
});
await expect(effective.resolve({
projectId: 'project-a',
projectPath: 'C:/project-a',
assignedSkillIds: [],
role: 'parent',
})).resolves.toMatchObject({
pluginReleaseIds: [PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID],
effectiveSkillIds: ['makelore-project-scaffold'],
skillEntries: [{
id: 'makelore-project-scaffold',
entryPath: 'skills/makelore-project-scaffold/SKILL.md',
}],
unavailableReasons: [],
});
await expect(createEffectivePluginResolver({
definitions: [projectScaffoldDefinition],
getAccountBinding: () => binding,
getLibrary: vi.fn(async () => scaffoldLibrary),
getEnabledPluginIds: vi.fn(async () => []),
}).resolve({
projectId: 'project-a',
projectPath: 'C:/project-a',
assignedSkillIds: [],
role: 'parent',
})).resolves.toMatchObject({
effectiveSkillIds: [],
unavailableReasons: [expect.objectContaining({
pluginId: PROJECT_SCAFFOLD_PLUGIN_ID,
code: 'project_disabled',
})],
});
await expect(effective.resolve({
projectId: 'project-a',
projectPath: 'C:/project-a',
assignedSkillIds: [],
role: 'child',
})).resolves.toMatchObject({
pluginReleaseIds: [],
effectiveSkillIds: [],
skillEntries: [],
});
});
});

View File

@@ -298,7 +298,9 @@ describe('buildPluginWorkspaceProjection', () => {
library: { ...library, items: [bundledLibrary] },
marketplaceInstallations: {},
project: bundledProject,
})).toEqual(['remove_from_library', 'enable_project', 'open_agent_assignment']);
})).toEqual(pluginId === 'makelore.project-scaffold'
? ['remove_from_library', 'enable_project']
: ['remove_from_library', 'enable_project', 'open_agent_assignment']);
}
expect(commandKinds({ marketplaceInstallations: {} })).toEqual([

View File

@@ -427,7 +427,7 @@ describe('PluginsView', () => {
expect(screen.getByRole('heading', { name: '插件' })).toBeVisible();
expect(screen.getByText(/MakeLore 运营发布官方插件/)).toBeVisible();
expect(screen.getByText(/获取、项目启用与伙伴分配彼此独立/)).toBeVisible();
expect(screen.getByText(/获取与项目启用彼此独立.*需要定向生效的插件还可分配伙伴/)).toBeVisible();
expect(screen.getByText('开发数据服务')).toBeVisible();
expect(screen.getByText('Pi Web Search')).toBeVisible();
expect(screen.getByRole('heading', { name: '本机全局生效' })).toBeVisible();
@@ -486,6 +486,10 @@ describe('PluginsView', () => {
expect(screen.getAllByText('随应用提供')).toHaveLength(2);
expect(screen.queryByText('未下载官方包')).not.toBeInTheDocument();
expect(screen.getByText('随项目启用')).toBeVisible();
expect(screen.getByRole('heading', { name: '生效范围' })).toBeVisible();
expect(screen.getByText(/启用当前项目后.*无需单独分配/)).toBeVisible();
expect(screen.queryByRole('heading', { name: '伙伴分配' })).not.toBeInTheDocument();
});
it('shows one dialog detail surface, confirms destructive commands, and embeds Data Service settings', () => {