From ddb678b46ff10a28e04beeafcfc00c8c8a23ff9f Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 22:21:24 +0800 Subject: [PATCH] feat(plugins): bundle project scaffold marketplace plugin --- ...04-marketplace-scaffold-client-6c4e8a21.md | 52 +++++++++++++ electron/coding-plugins/manifest.ts | 17 ++++- .../makelore-project-scaffold/package.json | 9 --- .../.codex-plugin/plugin.json | 6 +- .../com.makelore/capability.json | 17 +++++ .../project-scaffold/package.json | 6 ++ .../project-scaffold/plugin.json | 14 ++++ .../skills/makelore-project-scaffold/SKILL.md | 0 .../agents/openai.yaml | 0 .../assets/templates/common/package-lock.json | 0 .../assets/templates/common/package.json | 0 .../assets/templates/common/vite.config.js | 0 .../templates/interactive_ai_app/index.html | 0 .../templates/interactive_ai_app/main.js | 0 .../templates/interactive_ai_app/style.css | 0 .../references/submission-requirements.md | 0 .../scripts/scaffold.mjs | 0 shared/coding-plugins.ts | 7 ++ .../project-scaffold}/scaffold.test.mjs | 5 +- tests/unit/coding-plugin-manifest.test.ts | 22 +++++- .../coding-plugin-marketplace-client.test.ts | 4 +- tests/unit/plugin-workspace-model.test.ts | 71 +++++++++--------- tests/unit/project-scaffold-plugin.test.ts | 74 ++++++++++--------- 23 files changed, 218 insertions(+), 86 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260904-marketplace-scaffold-client-6c4e8a21.md delete mode 100644 plugins/makelore-project-scaffold/package.json rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/.codex-plugin/plugin.json (88%) create mode 100644 resources/coding-plugins/project-scaffold/com.makelore/capability.json create mode 100644 resources/coding-plugins/project-scaffold/package.json create mode 100644 resources/coding-plugins/project-scaffold/plugin.json rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/SKILL.md (100%) rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/agents/openai.yaml (100%) rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/assets/templates/common/package-lock.json (100%) rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/assets/templates/common/package.json (100%) rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/assets/templates/common/vite.config.js (100%) rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/index.html (100%) rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/main.js (100%) rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/style.css (100%) rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/references/submission-requirements.md (100%) rename {plugins/makelore-project-scaffold => resources/coding-plugins/project-scaffold}/skills/makelore-project-scaffold/scripts/scaffold.mjs (100%) rename {plugins/makelore-project-scaffold/tests => tests/project-scaffold}/scaffold.test.mjs (98%) diff --git a/.project-docs/30-worklog/tasks/20260904-marketplace-scaffold-client-6c4e8a21.md b/.project-docs/30-worklog/tasks/20260904-marketplace-scaffold-client-6c4e8a21.md new file mode 100644 index 0000000..93609ba --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-marketplace-scaffold-client-6c4e8a21.md @@ -0,0 +1,52 @@ +# Task: Publish scaffold Skill through official Marketplace + +## Identity + +- Task ID: 20260904-marketplace-scaffold-client-6c4e8a21 +- Mode: Feature +- Branch: codex/20260904-integrate-project-scaffold-b6d3e8a1-integrate-project-scaffold +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-integrate-project-scaffold-b6d3e8a1 +- Base commit: 22078060386e38dd5f12af4717dc6c6e3378c2df +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Publish `makelore.project-scaffold` as a code-owned bundled Marketplace plugin. +- Move the existing project-scaffold Skill runtime into `resources/coding-plugins/project-scaffold` so it ships with the signed MakeLore application. +- Register the bundled release in the client plugin registry and preserve the acquire, project-enable, and Agent-assignment lifecycle. +- Cover the official-script boundary with focused manifest, resolver, scaffold, and artifact-package tests. + +## Intent And Constraints + +- The Skill owns creation of the fixed interactive AI application starter and static publish-readiness checks; it does not build, upload, submit, or approve a project. +- Official executable resources may be loaded only from the code-owned bundled plugin root. Downloadable Marketplace artifacts keep the existing text/image-only P0 asset policy, including rejection of `.mjs`. +- Keep one runtime copy of the Skill rather than a Device Package copy plus a bundled copy. +- Use plugin ID `makelore.project-scaffold`, Skill ID `makelore-project-scaffold`, release version `1.0.0`, and minimum client version `2.0.0`. +- Preserve non-overwrite scaffolding and explicit user invocation of the Skill. + +## Outcome + +- Moved the existing scaffold Skill, templates, release reference, and `.mjs` runner into the signed client resource tree at `resources/coding-plugins/project-scaffold`; no second runtime copy remains. +- Added schema-v2 MakeLore capability metadata and retained the portable `.codex-plugin/plugin.json`, both identifying `makelore.project-scaffold` version `1.0.0` and Skill `makelore-project-scaffold`. +- Registered the plugin as a fixed, code-owned, `skill_only` bundled release with ID `00000000-0000-4000-8000-000000000303` and the existing acquire, project-enable, and Agent-assignment lifecycle. +- Narrowed executable-script authority to the fixed bundled root. Downloaded Marketplace ZIPs still reject `.mjs`; they do not install this plugin's runtime. +- Preserved the existing non-overwrite scaffold behavior, legacy `mini_game` / `mini_program` read compatibility, canonical `interactive_ai_app` output, and static publication-readiness guidance. + +## Verification + +- `pnpm exec vitest run tests/unit/coding-plugin-manifest.test.ts tests/unit/coding-plugin-marketplace-client.test.ts tests/unit/project-scaffold-plugin.test.ts --maxWorkers=2`: 54 passed. +- `node --test tests/project-scaffold/scaffold.test.mjs`: 12 passed. +- `pnpm exec vitest run tests/unit/plugin-workspace-model.test.ts tests/unit/coding-plugin-effective-resolver.test.ts tests/unit/coding-composition-paths.test.ts --maxWorkers=2`: 33 passed. +- ESLint on the changed TypeScript files: passed. +- `pnpm run typecheck`: passed. +- Plugin manifest validation: passed. Skill validation: passed with UTF-8 mode and the repository Python environment that provides PyYAML. +- `pnpm test`: 224 test files and 1,879 tests passed, 2 skipped; one unrelated real-process timing assertion exceeded two seconds under full parallel load. Re-running `tests/unit/pi-agent-server-process-real.test.ts` alone with one worker passed all 6 tests, so no unrelated timeout change was made. + +## Follow-ups + +- Run a packaged-app smoke test before the first public client release to prove the signed resource layout and `ELECTRON_RUN_AS_NODE` execution path outside the repository environment. + +## Promotion Candidates + +- Update ADR-008 after integration: project scaffolding is distributed as a code-owned bundled Marketplace plugin, while downloadable artifacts remain non-executable in P0. diff --git a/electron/coding-plugins/manifest.ts b/electron/coding-plugins/manifest.ts index 62c6d09..1ace1ba 100644 --- a/electron/coding-plugins/manifest.ts +++ b/electron/coding-plugins/manifest.ts @@ -12,6 +12,8 @@ import { CODE_OWNED_PLUGIN_PERMISSION_IDS, GAME_RESOURCE_BUNDLED_RELEASE_ID, GAME_RESOURCE_PLUGIN_ID, + PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID, + PROJECT_SCAFFOLD_PLUGIN_ID, DATA_SERVICE_CAPABILITY_IDS, DATA_SERVICE_OPERATION_DEFINITIONS, DATA_SERVICE_PLUGIN_ID, @@ -35,6 +37,7 @@ import { export const BUNDLED_CODING_PLUGIN_ROOTS = Object.freeze([ 'data-service', 'game-resource', + 'project-scaffold', ] as const); const BUNDLED_CODING_PLUGIN_METADATA = Object.freeze({ @@ -52,6 +55,13 @@ const BUNDLED_CODING_PLUGIN_METADATA = Object.freeze({ releaseId: GAME_RESOURCE_BUNDLED_RELEASE_ID, bundledV2: true, }), + 'project-scaffold': Object.freeze({ + pluginId: PROJECT_SCAFFOLD_PLUGIN_ID, + runtimeKind: 'skill_only' as const, + acquisitionMode: 'user_acquired' as const, + releaseId: PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID, + bundledV2: true, + }), }); const CAPABILITY_MANIFEST_RELATIVE_PATH = 'com.makelore/capability.json'; @@ -722,8 +732,11 @@ function trustedMetadata( if (options.bundledV2 && !bundledV2) { fail(filePath, 'trusted metadata.bundledV2', 'is valid only for schema 2 packages'); } - if (bundledV2 && (runtimeKind !== 'platform_hosted' || releaseId === null)) { - fail(filePath, 'trusted metadata.bundledV2', 'requires a hosted runtime and fixed Release ID'); + if (bundledV2 && (runtimeKind !== 'platform_hosted' && runtimeKind !== 'skill_only')) { + fail(filePath, 'trusted metadata.bundledV2', 'requires a distributed runtime'); + } + if (bundledV2 && releaseId === null) { + fail(filePath, 'trusted metadata.bundledV2', 'requires a fixed Release ID'); } const defaultProvenance: CodingPluginPackageProvenance = { source: schemaVersion === 1 || bundledV2 ? 'bundled' : 'marketplace', diff --git a/plugins/makelore-project-scaffold/package.json b/plugins/makelore-project-scaffold/package.json deleted file mode 100644 index 3db8336..0000000 --- a/plugins/makelore-project-scaffold/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "makelore-project-scaffold", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "test": "node --test tests/scaffold.test.mjs" - } -} diff --git a/plugins/makelore-project-scaffold/.codex-plugin/plugin.json b/resources/coding-plugins/project-scaffold/.codex-plugin/plugin.json similarity index 88% rename from plugins/makelore-project-scaffold/.codex-plugin/plugin.json rename to resources/coding-plugins/project-scaffold/.codex-plugin/plugin.json index 3fb368e..a171103 100644 --- a/plugins/makelore-project-scaffold/.codex-plugin/plugin.json +++ b/resources/coding-plugins/project-scaffold/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { - "name": "makelore-project-scaffold", - "version": "0.1.0", + "name": "makelore.project-scaffold", + "version": "1.0.0", "description": "为 MakeLore 交互式 AI 应用生成工程骨架并检查一键提交要求。", "author": { "name": "MakeLore Team" @@ -10,7 +10,7 @@ "displayName": "MakeLore 项目初始化与发布检查", "shortDescription": "初始化工程骨架并检查发布要求。", "longDescription": "读取当前 MakeLore 项目类型,生成固定、可构建的交互式 AI 应用工程骨架,并按平台的一键提交合同区分必需项、直接阻断项、打包排除项和运行期检查。", - "developerName": "MakeLore Team", + "developerName": "MakeLore", "category": "Developer Tools", "capabilities": ["Write"], "defaultPrompt": "使用 $makelore-project-scaffold 初始化当前 MakeLore 交互式 AI 应用项目,并检查是否满足一键提交要求。" diff --git a/resources/coding-plugins/project-scaffold/com.makelore/capability.json b/resources/coding-plugins/project-scaffold/com.makelore/capability.json new file mode 100644 index 0000000..8f35e07 --- /dev/null +++ b/resources/coding-plugins/project-scaffold/com.makelore/capability.json @@ -0,0 +1,17 @@ +{ + "schemaVersion": 2, + "pluginId": "makelore.project-scaffold", + "contractVersion": 1, + "scope": "project", + "runtime": { + "kind": "skill_only" + }, + "skills": [ + { + "id": "makelore-project-scaffold", + "entry": "../skills/makelore-project-scaffold/SKILL.md", + "grants": [] + } + ], + "tools": [] +} diff --git a/resources/coding-plugins/project-scaffold/package.json b/resources/coding-plugins/project-scaffold/package.json new file mode 100644 index 0000000..74f6686 --- /dev/null +++ b/resources/coding-plugins/project-scaffold/package.json @@ -0,0 +1,6 @@ +{ + "name": "makelore.project-scaffold", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/resources/coding-plugins/project-scaffold/plugin.json b/resources/coding-plugins/project-scaffold/plugin.json new file mode 100644 index 0000000..f00c7ee --- /dev/null +++ b/resources/coding-plugins/project-scaffold/plugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "makelore.project-scaffold", + "version": "1.0.0", + "description": "为 MakeLore 交互式 AI 应用生成固定工程骨架并检查发布与审批提交要求。", + "author": { + "name": "MakeLore" + }, + "extensions": { + "com.makelore": { + "capabilityManifest": "./com.makelore/capability.json" + } + } +} diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/SKILL.md b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/SKILL.md similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/SKILL.md rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/SKILL.md diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/agents/openai.yaml b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/agents/openai.yaml similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/agents/openai.yaml rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/agents/openai.yaml diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package-lock.json b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package-lock.json similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package-lock.json rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package-lock.json diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package.json b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package.json similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package.json rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package.json diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/vite.config.js b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/common/vite.config.js similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/vite.config.js rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/common/vite.config.js diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/index.html b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/index.html similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/index.html rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/index.html diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/main.js b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/main.js similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/main.js rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/main.js diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/style.css b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/style.css similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/style.css rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/style.css diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/references/submission-requirements.md b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/references/submission-requirements.md similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/references/submission-requirements.md rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/references/submission-requirements.md diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/scripts/scaffold.mjs b/resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/scripts/scaffold.mjs similarity index 100% rename from plugins/makelore-project-scaffold/skills/makelore-project-scaffold/scripts/scaffold.mjs rename to resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold/scripts/scaffold.mjs diff --git a/shared/coding-plugins.ts b/shared/coding-plugins.ts index 87b78ca..6df0a59 100644 --- a/shared/coding-plugins.ts +++ b/shared/coding-plugins.ts @@ -103,6 +103,8 @@ export const DATA_SERVICE_PREVIEW_RUNTIME_SURFACE = 'data-service-v1' as const; export const GAME_RESOURCE_PLUGIN_ID = 'makelore.game-resource' as const; export const GAME_RESOURCE_BUNDLED_RELEASE_ID = '00000000-0000-4000-8000-000000000105' as const; +export const PROJECT_SCAFFOLD_PLUGIN_ID = 'makelore.project-scaffold' as const; +export const PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID = '00000000-0000-4000-8000-000000000303' as const; export const CODE_OWNED_OPTIONAL_BUNDLED_RELEASES = Object.freeze({ [GAME_RESOURCE_PLUGIN_ID]: Object.freeze({ @@ -110,6 +112,11 @@ export const CODE_OWNED_OPTIONAL_BUNDLED_RELEASES = Object.freeze({ version: '1.0.0', channel: 'stable' as const, }), + [PROJECT_SCAFFOLD_PLUGIN_ID]: Object.freeze({ + releaseId: PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID, + version: '1.0.0', + channel: 'stable' as const, + }), }); export function isCodeOwnedOptionalBundledPluginId(pluginId: string): boolean { diff --git a/plugins/makelore-project-scaffold/tests/scaffold.test.mjs b/tests/project-scaffold/scaffold.test.mjs similarity index 98% rename from plugins/makelore-project-scaffold/tests/scaffold.test.mjs rename to tests/project-scaffold/scaffold.test.mjs index 94a837b..0d78867 100644 --- a/plugins/makelore-project-scaffold/tests/scaffold.test.mjs +++ b/tests/project-scaffold/scaffold.test.mjs @@ -17,7 +17,10 @@ import { spawnSync } from 'node:child_process'; import { fileURLToPath, pathToFileURL } from 'node:url'; import test from 'node:test'; -const pluginRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const pluginRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../../resources/coding-plugins/project-scaffold', +); const scaffoldScript = path.join( pluginRoot, 'skills', diff --git a/tests/unit/coding-plugin-manifest.test.ts b/tests/unit/coding-plugin-manifest.test.ts index dca9909..7ee6cb2 100644 --- a/tests/unit/coding-plugin-manifest.test.ts +++ b/tests/unit/coding-plugin-manifest.test.ts @@ -21,6 +21,7 @@ import { const PACKAGE_ROOT = path.resolve('resources/coding-plugins/data-service'); const GAME_RESOURCE_ROOT = path.resolve('resources/coding-plugins/game-resource'); +const PROJECT_SCAFFOLD_ROOT = path.resolve('resources/coding-plugins/project-scaffold'); async function packageManifests(): Promise<{ root: Record; capability: Record }> { return { @@ -33,12 +34,17 @@ describe('bundled coding plugin manifests', () => { it('loads the fixed Data Service package and immutable declarations', async () => { const definitions = await loadBundledCodingPluginDefinitions(path.resolve('resources/coding-plugins')); const startupDefinitions = loadBundledCodingPluginDefinitionsSync(path.resolve('resources/coding-plugins')); - expect(BUNDLED_CODING_PLUGIN_ROOTS).toEqual(['data-service', 'game-resource']); + expect(BUNDLED_CODING_PLUGIN_ROOTS).toEqual([ + 'data-service', + 'game-resource', + 'project-scaffold', + ]); expect(resolveBundledCodingPluginRootPaths(path.resolve('resources/coding-plugins'))).toEqual([ PACKAGE_ROOT, GAME_RESOURCE_ROOT, + PROJECT_SCAFFOLD_ROOT, ]); - expect(definitions).toHaveLength(2); + expect(definitions).toHaveLength(3); expect(definitions[0]).toMatchObject({ id: 'makelore.data-service', adapterId: 'data-service', @@ -54,6 +60,17 @@ describe('bundled coding plugin manifests', () => { provenance: { source: 'bundled', packageRoot: 'game-resource' }, skills: [{ id: 'game-resource', entryPath: 'skills/game-resource/SKILL.md' }], }, + { + id: 'makelore.project-scaffold', version: '1.0.0', runtimeKind: 'skill_only', + acquisitionMode: 'user_acquired', releaseId: '00000000-0000-4000-8000-000000000303', + provenance: { source: 'bundled', packageRoot: 'project-scaffold' }, + skills: [{ + id: 'makelore-project-scaffold', + entryPath: 'skills/makelore-project-scaffold/SKILL.md', + grants: [], + }], + tools: [], + }, ]); expect(startupDefinitions).toEqual(definitions); expect(Object.isFrozen(startupDefinitions)).toBe(true); @@ -128,6 +145,7 @@ describe('bundled coding plugin manifests', () => { expect(resolveBundledCodingPluginRootPaths(path.resolve('tmp'))).toEqual([ path.resolve('tmp/data-service'), path.resolve('tmp/game-resource'), + path.resolve('tmp/project-scaffold'), ]); }); diff --git a/tests/unit/coding-plugin-marketplace-client.test.ts b/tests/unit/coding-plugin-marketplace-client.test.ts index 75931bc..59cd624 100644 --- a/tests/unit/coding-plugin-marketplace-client.test.ts +++ b/tests/unit/coding-plugin-marketplace-client.test.ts @@ -1419,8 +1419,8 @@ describe('PluginPackageStore', () => { }); const cases = [ { - name: 'unsupported asset extension', - archive: buildSkillOnlyArchive({ 'skills/example-skill/assets/run.exe': Buffer.from('not executable') }), + name: 'executable Skill script in a downloadable artifact', + archive: buildSkillOnlyArchive({ 'skills/example-skill/scripts/run.mjs': Buffer.from('export {};') }), }, { name: 'non-UTF-8 Skill text', diff --git a/tests/unit/plugin-workspace-model.test.ts b/tests/unit/plugin-workspace-model.test.ts index 52e4bcc..ba5504c 100644 --- a/tests/unit/plugin-workspace-model.test.ts +++ b/tests/unit/plugin-workspace-model.test.ts @@ -261,40 +261,45 @@ describe('buildPluginWorkspaceProjection', () => { }); it('keeps bundled and downloadable official command sets inside their delivery boundaries', () => { - const bundledCatalog = { - ...catalog.items[0]!, - pluginId: 'makelore.game-resource', - title: 'Game Resource', - runtimeKind: 'bundled_typed' as const, - }; - const bundledLibrary = { - ...library.items[0]!, - pluginId: bundledCatalog.pluginId, - title: bundledCatalog.title, - }; - const bundledProject = { - ...project, - items: [{ - ...project.items[0]!, - id: bundledCatalog.pluginId, - displayName: bundledCatalog.title, - enabled: false, - state: 'disabled' as const, - }], - }; + for (const [pluginId, title, runtimeKind] of [ + ['makelore.game-resource', 'Game Resource', 'platform_hosted'], + ['makelore.project-scaffold', 'Project Scaffold', 'skill_only'], + ] as const) { + const bundledCatalog = { + ...catalog.items[0]!, + pluginId, + title, + runtimeKind, + }; + const bundledLibrary = { + ...library.items[0]!, + pluginId: bundledCatalog.pluginId, + title: bundledCatalog.title, + }; + const bundledProject = { + ...project, + items: [{ + ...project.items[0]!, + id: bundledCatalog.pluginId, + displayName: bundledCatalog.title, + enabled: false, + state: 'disabled' as const, + }], + }; - expect(commandKinds({ - catalog: { ...catalog, items: [bundledCatalog] }, - library: { ...library, items: [] }, - marketplaceInstallations: {}, - project: null, - })).toEqual(['acquire']); - expect(commandKinds({ - catalog: { ...catalog, items: [bundledCatalog] }, - library: { ...library, items: [bundledLibrary] }, - marketplaceInstallations: {}, - project: bundledProject, - })).toEqual(['remove_from_library', 'enable_project', 'open_agent_assignment']); + expect(commandKinds({ + catalog: { ...catalog, items: [bundledCatalog] }, + library: { ...library, items: [] }, + marketplaceInstallations: {}, + project: null, + })).toEqual(['acquire']); + expect(commandKinds({ + catalog: { ...catalog, items: [bundledCatalog] }, + library: { ...library, items: [bundledLibrary] }, + marketplaceInstallations: {}, + project: bundledProject, + })).toEqual(['remove_from_library', 'enable_project', 'open_agent_assignment']); + } expect(commandKinds({ marketplaceInstallations: {} })).toEqual([ 'remove_from_library', diff --git a/tests/unit/project-scaffold-plugin.test.ts b/tests/unit/project-scaffold-plugin.test.ts index 89b20f7..8be5316 100644 --- a/tests/unit/project-scaffold-plugin.test.ts +++ b/tests/unit/project-scaffold-plugin.test.ts @@ -3,18 +3,22 @@ import { spawnSync } from 'node:child_process'; import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; -import { dirname, join, resolve } from 'node:path'; +import { join, resolve } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { devicePackageKind, inspectDevicePackage, } from '@electron/coding-packages/device-package-format'; -import { DevicePackageManager } from '@electron/coding-packages/device-package-manager'; +import { loadBundledCodingPluginDefinitions } from '@electron/coding-plugins/manifest'; import { createCodingProjectMetadata } from '@electron/coding-projects/project-config'; import { createStaticProjectPackage } from '@electron/services/project-packager'; -import type { ProjectType } from '../../shared/project-config'; +import { + PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID, + PROJECT_SCAFFOLD_PLUGIN_ID, + isCodeOwnedOptionalBundledPluginId, +} from '../../shared/coding-plugins'; -const pluginRoot = resolve('plugins/makelore-project-scaffold'); +const pluginRoot = resolve('resources/coding-plugins/project-scaffold'); const scaffoldScript = join( pluginRoot, 'skills', @@ -42,23 +46,30 @@ afterEach(async () => { }); describe('MakeLore project scaffold plugin', () => { - it('is discovered as a skill-only package with executable Skill scripts', async () => { + it('loads as a fixed code-owned bundled Skill while retaining portable plugin metadata', async () => { const packageJson = JSON.parse(await readFile(join(pluginRoot, 'package.json'), 'utf8')); - const pluginManifest = JSON.parse(await readFile( + const portableManifest = JSON.parse(await readFile( join(pluginRoot, '.codex-plugin', 'plugin.json'), 'utf8', )); + const marketplaceManifest = JSON.parse(await readFile(join(pluginRoot, 'plugin.json'), 'utf8')); const inspected = await inspectDevicePackage(pluginRoot); + const definitions = await loadBundledCodingPluginDefinitions(resolve('resources/coding-plugins')); + const definition = definitions.find(({ id }) => id === PROJECT_SCAFFOLD_PLUGIN_ID); expect({ name: packageJson.name, version: packageJson.version }).toEqual({ - name: pluginManifest.name, - version: pluginManifest.version, + name: marketplaceManifest.name, + version: marketplaceManifest.version, }); - expect(pluginManifest.skills).toBe('./skills/'); + expect({ name: portableManifest.name, version: portableManifest.version }).toEqual({ + name: marketplaceManifest.name, + version: marketplaceManifest.version, + }); + expect(portableManifest.skills).toBe('./skills/'); expect(devicePackageKind(inspected)).toBe('skill-only'); expect(inspected).toMatchObject({ - packageId: 'makelore-project-scaffold', - resolvedVersion: '0.1.0', + packageId: PROJECT_SCAFFOLD_PLUGIN_ID, + resolvedVersion: '1.0.0', extensionEntries: [], hasSkillScripts: true, ignoredLifecycleScripts: [], @@ -67,30 +78,25 @@ describe('MakeLore project scaffold plugin', () => { entryPath: 'skills/makelore-project-scaffold/SKILL.md', }], }); + expect(definition).toMatchObject({ + id: PROJECT_SCAFFOLD_PLUGIN_ID, + version: '1.0.0', + runtimeKind: 'skill_only', + acquisitionMode: 'user_acquired', + 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: [], + }], + tools: [], + }); + expect(isCodeOwnedOptionalBundledPluginId(PROJECT_SCAFFOLD_PLUGIN_ID)).toBe(true); }); - it('prepares, confirms, and installs the real package with every Skill resource', async () => { - const root = await mkdtemp(join(tmpdir(), 'makelore-scaffold-device-package-')); - tempDirectories.push(root); - const manager = new DevicePackageManager({ - rootDir: join(root, 'store'), - now: () => Date.parse('2026-09-04T00:00:00.000Z'), - createId: () => 'project-scaffold-plan', - }); - - const preview = await manager.prepare(pluginRoot, 'prepare-turn'); - expect(preview).toMatchObject({ - kind: 'skill-only', - includesExecutableCode: true, - skillEntries: [{ id: 'makelore-project-scaffold' }], - extensionEntries: [], - }); - expect(preview.warnings.join(' ')).toContain('可执行 Skill 脚本'); - await manager.commit(preview.planId, true, 'confirm-turn'); - - const [resource] = (await manager.resolveEnabledResources()).skillEntries; - expect(resource?.id).toBe('makelore-project-scaffold'); - const skillRoot = dirname(join(resource!.packageRoot, resource!.entryPath)); + it('ships the executable scaffold and every Skill resource in the bundled root', async () => { + const skillRoot = join(pluginRoot, 'skills', 'makelore-project-scaffold'); expect(await readFile(join(skillRoot, 'scripts', 'scaffold.mjs'), 'utf8')) .toContain('schemaVersion: 1'); expect(await readFile(join(skillRoot, 'assets', 'templates', 'common', 'package-lock.json'), 'utf8')) @@ -164,7 +170,7 @@ describe('MakeLore project scaffold plugin', () => { }); it('creates an interactive AI application accepted by the existing source packager', async () => { - const projectType: ProjectType = 'interactive_ai_app'; + const projectType = 'interactive_ai_app' as const; const projectPath = await mkdtemp(join(tmpdir(), `makelore-${projectType}-`)); const outputPath = await mkdtemp(join(tmpdir(), 'makelore-scaffold-package-')); tempDirectories.push(projectPath, outputPath);