From 959b6333b59706b23d8fd802e4e9323fa8a10bf7 Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 20:36:28 +0800 Subject: [PATCH 1/7] feat(projects): add interactive AI app scaffold skill --- ...ation-7e4c2a91__interactive-ai-app-type.md | 45 + ...roject-scaffold-implementation-7e4c2a91.md | 105 ++ .../coding-packages/device-package-format.ts | 24 +- .../coding-packages/device-package-manager.ts | 20 +- electron/coding-projects/project-config.ts | 7 +- .../coding-runtime/pi/agent-server-process.ts | 2 +- electron/coding-runtime/pi/worker-process.ts | 8 +- electron/services/project-packager.ts | 2 +- .../.codex-plugin/plugin.json | 18 + .../makelore-project-scaffold/package.json | 9 + .../skills/makelore-project-scaffold/SKILL.md | 74 ++ .../agents/openai.yaml | 4 + .../assets/templates/common/package-lock.json | 1117 +++++++++++++++++ .../assets/templates/common/package.json | 15 + .../assets/templates/common/vite.config.js | 5 + .../templates/interactive_ai_app/index.html | 20 + .../templates/interactive_ai_app/main.js | 10 + .../templates/interactive_ai_app/style.css | 16 + .../references/submission-requirements.md | 141 +++ .../scripts/scaffold.mjs | 304 +++++ .../tests/scaffold.test.mjs | 289 +++++ shared/project-config.ts | 12 +- src/components/layout/Sidebar.tsx | 11 +- src/components/works/ProjectPublishAction.tsx | 2 +- src/lib/works-project-publish.ts | 2 +- src/pages/Plugins/PluginDetails.tsx | 9 +- src/pages/ProjectConfiguration/index.tsx | 2 +- .../e2e/project-configuration-skills.spec.ts | 10 +- tests/fixtures/fake-pi-rpc-child.mjs | 6 + tests/unit/coding-project-identity.test.ts | 41 +- tests/unit/device-package-manager.test.ts | 36 + .../unit/pi-agent-server-process-real.test.ts | 28 + tests/unit/pi-rpc-foundation.test.ts | 18 +- tests/unit/plugins-page.test.tsx | 27 + tests/unit/project-packager.test.ts | 38 +- tests/unit/project-publish-action.test.tsx | 34 +- tests/unit/project-release-builder.test.ts | 2 +- tests/unit/project-scaffold-plugin.test.ts | 193 +++ tests/unit/works-project-publish.test.ts | 2 +- tests/unit/works-routes.test.ts | 4 +- 40 files changed, 2647 insertions(+), 65 deletions(-) create mode 100644 .project-docs/10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md create mode 100644 .project-docs/30-worklog/tasks/20260904-project-scaffold-implementation-7e4c2a91.md create mode 100644 plugins/makelore-project-scaffold/.codex-plugin/plugin.json create mode 100644 plugins/makelore-project-scaffold/package.json create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/SKILL.md create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/agents/openai.yaml create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package-lock.json create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package.json create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/vite.config.js create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/index.html create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/main.js create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/style.css create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/references/submission-requirements.md create mode 100644 plugins/makelore-project-scaffold/skills/makelore-project-scaffold/scripts/scaffold.mjs create mode 100644 plugins/makelore-project-scaffold/tests/scaffold.test.mjs create mode 100644 tests/unit/project-scaffold-plugin.test.ts diff --git a/.project-docs/10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md b/.project-docs/10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md new file mode 100644 index 0000000..ebaf5b5 --- /dev/null +++ b/.project-docs/10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md @@ -0,0 +1,45 @@ +# 交互式 AI 应用统一类型提案 + +## 文档信息 + +- 状态:Proposed,等待 Integration Gate 提升 +- 所有者:`20260904-project-scaffold-implementation-7e4c2a91` +- 日期:2026-09-04 +- 影响范围:项目创建、项目配置读取、脚手架、发布打包与作品元数据 + +## 决策 + +新建项目的唯一可发布类型改为: + +- 稳定标识:`interactive_ai_app` +- 展示名称:`交互式 AI 应用` + +`mini_game` 与 `mini_program` 不再出现在新建入口,也不再由新客户端写入。两者作为历史只读别名,在项目配置读取边界统一归一化为 `interactive_ai_app`,不批量改写用户已有的 `.makelore/project.json`。归一化后的打包清单和后续提交元数据只输出规范值。 + +`custom` 仍是独立的工作空间类型,并继续禁止进入平台的一键发布流程。 + +## 脚手架与发布合同 + +- 脚手架只维护一套通用 Vite 静态应用模板,不再按小游戏/小程序维护两套业务模板。 +- 历史项目调用脚手架时使用相同的统一模板,脚本返回规范类型,但保留原始项目元数据。 +- 固定 npm/Vite 构建、静态产物合同、浏览器预检、上传和审批要求不因类型合并而放宽。 +- 平台服务应先兼容接收三个值并统一输出 `interactive_ai_app`,再发布只写规范值的新客户端,以支持滚动升级。 + +## 兼容与迁移边界 + +- 不执行数据库或本地配置的批量历史迁移。 +- 不增加版本开关、双写、模板迁移框架或类型特有构建分支。 +- 旧客户端上传 `mini_game` / `mini_program` 时,服务端在校验边界接受并归一化;`custom`、未知值仍拒绝。 +- 历史发布记录保持不可变;公开读取和信息流投影使用规范类型。 + +## 被取代的语义 + +本提案在集成后取代既有文档中“小游戏与小程序是两个可发布项目类型”以及“二者分别生成不同模板”的部分。既有关于静态 Vite 发布、安全预检、制品不可变与审批流程的约束继续有效。 + +## 验收 + +- 新建界面只显示“交互式 AI 应用”和“自定义项目”。 +- 新建可发布项目落盘 `interactive_ai_app`,脚手架生成统一模板。 +- 两种历史配置均可打开和发布,原配置文件不会仅因读取而被改写。 +- 客户端清单、上传元数据和平台公开投影均使用 `interactive_ai_app`。 +- `custom` 与未知类型仍被拒绝发布。 diff --git a/.project-docs/30-worklog/tasks/20260904-project-scaffold-implementation-7e4c2a91.md b/.project-docs/30-worklog/tasks/20260904-project-scaffold-implementation-7e4c2a91.md new file mode 100644 index 0000000..90e9837 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-project-scaffold-implementation-7e4c2a91.md @@ -0,0 +1,105 @@ +# Task: Implement project scaffold Skill plugin + +## Identity + +- Task ID: 20260904-project-scaffold-implementation-7e4c2a91 +- Mode: Feature +- Branch: codex/20260904-diagnose-design-stream-6a4e9c21-diagnose-design-stream +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-diagnose-design-stream-6a4e9c21 +- Base commit: 336e0bb0caf24537b7b0f350aba3e04a37f5544c +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Preserve the current project-creation boundary and add regression coverage proving that no mini-game, mini-program, or custom business scaffold is created. +- Correct the new-project product copy so users are directed to the explicit project-scaffold Skill after creation. +- Expose the application-owned Node executable to direct Pi workers and the shared Agent Server. +- Treat non-empty standard Skill `scripts/` directories as executable code in Device Package preview, confirmation, and details copy. +- Add an independently installable `plugins/makelore-project-scaffold/` package containing one Skill, deterministic scaffold script, fixed templates, submission requirements, and tests. +- Make the Skill itself route publication/readiness requests and carry the current platform contract as four distinct classes: required input, direct blocker, source-package exclusion, and runtime/platform-only confirmation. +- Add regression coverage that keeps the documented source-package exclusion set aligned with the current packager without introducing a second build or publication implementation. +- Verify the unified generated project through the current project packager and local npm/Vite release seam, plus both historical input aliases at the compatibility boundary. +- Replace the two publishable project types with canonical `interactive_ai_app` / “交互式 AI 应用” across creation, configuration, packaging, the scaffold Skill, and Works Square-facing metadata while retaining read compatibility for existing `mini_game` and `mini_program` projects. + +## Intent And Constraints + +- Implement accepted proposal `ML-PROJECT-SCAFFOLD-001` without restoring project-creation side effects. +- Keep immutable `.makelore/project.json.projectType` as the only scaffold selector; do not add scaffold state, a template migration layer, `--force`, or a dedicated Pi extension/tool. +- Execute scaffolding only through the existing Pi `bash` project write lease and `MAKELORE_NODE_EXECUTABLE`; do not fall back to system Node or Python. +- Never overwrite target files. Preflight the full fixed target set and roll back only files/directories created by the current handled invocation. +- Preserve Main-owned fixed npm/Vite build, source/built artifact contract, browser preflight, and Works Square operational review as the release authority. +- Keep feature-task writes within product code, tests, this task record, and task-prefixed supporting records; canonical project memory changes remain promotion candidates for Integration Gate. +- Stage the plugin source under `plugins/makelore-project-scaffold/` for local Device Package installation. Immutable Git tagging, publication, and cross-platform packaged smoke remain release follow-ups and are not claimed by this task. + +## Outcome + +- Confirmed the request's original deletion premise was already true on base `336e0bb`: Project Service creates only `.makelore/project.json` and `knowledge/`. Added creation regressions for the canonical interactive type and `custom`, and removed the Sidebar promise that project creation itself makes a publishable scaffold. +- Added the non-overridable `MAKELORE_NODE_EXECUTABLE` host contract to both direct Pi workers and the shared Agent Server process. A worker overlay cannot spoof the application-selected executable path. +- Extended Device Package inspection so a declared Skill with a non-empty standard `scripts/` subtree is executable code. Skill-only script packages retain `kind: skill-only`, require the existing cross-turn executable confirmation, and show Skill-specific file/network/process authority copy in preview and plugin details. Existing Pi-extension and mixed packages retain executable warnings. +- Added `plugins/makelore-project-scaffold/` as a standard, independently versioned `0.1.0` plugin with one Skill and no Pi extension. Its package and plugin manifests are same-name/same-version and have no install lifecycle scripts. +- Added a deterministic Node-only `scaffold.mjs` selected exclusively by `.makelore/project.json.projectType`. It accepts only `--project-root`, creates one fixed six-file interactive AI application target set, reads all templates and preflights every target before writing, uses exclusive creation, preserves unrelated files, returns the flat version-1 JSON protocol, and rolls back only current-invocation files/directories on handled write failure. It provides no `--force`, type override, migration state, dependency install, network request, or publisher replacement. +- Regenerated the Vite 7.3.1 lockfile with the repository's fixed npm 11.6.2 and verified the unified template through source packaging and a real locked npm/Vite build. Added an approval-readiness reference that distinguishes static preparation from authoritative Main build/preflight/upload and Works Square operational review. +- Expanded the Skill's model-invocation routing and package-facing copy so requests about publishing, one-click submission, forbidden content, failure causes, or release readiness enter an explicit read-only readiness workflow. Project-specific reports now separate confirmed blockers, excluded-package impact, runtime/platform checks, and statically proven requirements, cite project-relative evidence, and use bounded conclusions that never claim approval. +- Reconciled the bundled publication reference against the current project packager, fixed npm/Vite release builder, static-artifact browser preflight, and Works submission route. It now records the full schema-v2 project prerequisite, root build inputs, exact size/count limits, direct path/link/output blockers, root `release.json` prohibition, both viewports and runtime failures, first-submit metadata/cover contract, and the exact case-insensitive excluded directory/file/suffix sets plus dynamic `.env`, credential JSON, Terraform, and `works-` patterns. +- Kept exclusion semantics explicit: excluded paths may remain in a project and are not blockers by existence; the Skill reports their effect only when required source or assets depend on them. The reference also states that an HTTP(S) literal is only a risk until the authoritative preflight makes an actual cross-origin request. +- Did not add a release-check script, duplicate packager, local dependency install, Vite invocation, browser launch, upload, or submission path to the plugin. Main and Works Square remain the only execution and approval authorities. +- Verified the real plugin through Device Package prepare, later-turn confirmation, commit, enabled-resource resolution, and installed script/template/reference reads. +- Replaced the two new-project values with canonical `interactive_ai_app` / “交互式 AI 应用”. New creation, generated `niancode.yml`, submission metadata, and publish UI use only the canonical value. Existing `mini_game` and `mini_program` configs normalize at the read boundary; a read or scaffold invocation does not rewrite the original config, while a later ordinary config mutation may persist the normalized value. +- Replaced the two business-specific starter trees with one generic interactive Vite starter. The scaffold continues to accept both historical values solely as compatibility aliases and reports the canonical type. +- Added the task-owned interactive-type proposal instead of modifying accepted ADRs or canonical project memory in feature mode. +- No existing user project files were removed or migrated. No plugin tag was published and the plugin was not silently installed into the running application. + +## Verification + +- `node --test plugins/makelore-project-scaffold/tests/scaffold.test.mjs`: 12 passed. Covers the canonical value, both historical aliases, exact unified output tree, metadata preservation, conflicts, invalid/missing/custom configs, invalid CLI, missing template, Chinese/space paths, and handled-write rollback. +- Focused type/scaffold/release/UI regressions: 7 Vitest files and 107 tests passed. +- `pnpm typecheck` and scoped ESLint over the changed type, configuration, packaging, UI, route, and test files passed. +- Desktop project-configuration Playwright E2E: 1 passed; the new-project dialog exposed only “交互式 AI 应用” and `custom`, and the publish action remained available for the canonical type. +- Real unified-template smoke: the actual scaffold ran in a verified temporary directory, fixed npm 11.6.2 completed `ci --ignore-scripts`, Vite 7.3.1 built successfully, and non-empty `dist/index.html` was produced; the temporary directory was removed. +- Plugin creator validation passed; Skill creator `quick_validate.py` returned `Skill is valid!` under explicit Python UTF-8 mode. +- `pnpm run build:vite` passed for Renderer, Main, Preload, and utility worker. Only existing Browserslist, mixed-import, and large-chunk warnings remained. +- Full serial main suite: 225/226 files passed; 1886 tests passed and 2 skipped. The only failure was the existing Pi Agent Server two-Bash-call wall-clock assertion (`2657ms < 2000ms`). An immediate isolated rerun again exceeded the fixed threshold (`2170ms`) while its other 5 tests passed; no project-type/scaffold path is involved and this task did not relax the threshold. The separately excluded pressure suite passed 1/1. +- Earlier focused Device Package, Pi environment, Plugin Details, release-builder, and browser-preflight suites recorded by this same task remained green; the type follow-up did not modify those implementations. +- Final `git diff --check` and task-aware project-doc drift check passed; only the task record and task-prefixed proposal changed under `.project-docs/`. +- Merge handoff re-ran `git diff --check` and the task-aware drift check after the explicit integration request; both remained green and no product files changed during handoff. + +## Follow-ups + +- Release `SCF-060`: move or mirror the staged plugin source to the chosen independent distribution repository, create an immutable SemVer tag, document the Device Package install source, and complete a small real-user prepare → confirm → install → initialize trial. Distribution location remains a product/release decision; this task does not invent it. +- Before claiming packaged support, run the Skill through an installed MakeLore build with the actual packaged executable and Git Bash on Windows. Run equivalent packaged smoke on macOS and native Linux; only then claim three-platform support. +- Verify enable/disable, upgrade to a second immutable version, downgrade/reinstall of a known version, and uninstall semantics. Uninstall must not delete files already created in user projects. +- Official Marketplace Skill artifacts currently reject executable `.mjs` assets. Continue using Device Package/Git distribution unless a separately approved Marketplace artifact policy adds executable Skill support. +- Track the existing `pi-agent-server-process-real` `<2000ms` assertion as a separate performance-test stability issue; it currently exceeds the threshold both under the full serial suite and in one isolated rerun, while its functional assertions pass. +- After code/plugin integration and release acceptance, run an Integration Gate task to promote the canonical project-creation and Device Package facts listed below. + +## Promotion Candidates + +- **Canonical interactive AI application type** + - Target canonical documents: `.project-docs/10-decisions/`, `.project-docs/30-worklog/current-state.md`, `.project-docs/40-domain/glossary.md`, `.project-docs/40-domain/business-rules.md`, and relevant architecture/data-flow summaries. + - Proposal: replace the distinct new-project `mini_game` / `mini_program` values and templates with `interactive_ai_app` / “交互式 AI 应用” and one generic interactive Vite scaffold; retain both old values only as historical read aliases. + - Evidence: shared/config normalizer and regressions, new-project E2E, canonical package/submission tests, all three scaffold input tests, real unified-template build, and task proposal `20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md`. + - Future impact: future templates and publishing rules evolve against one stable product type while existing local projects continue to open and publish without a batch rewrite. + - Semantic conflicts: supersedes the canonical two-publishable-type classification and any separate mini-game/mini-program template promises; it preserves fixed npm/Vite build, approval, and immutable release rules. + - Human confirmation required: no additional product decision is required because the user explicitly requested the merge; Integration Gate review is still required before canonical writes. +- **Project creation / scaffold ownership** + - Target canonical documents: `.project-docs/30-worklog/current-state.md`, `20-architecture/system-overview.md`, `20-architecture/data-flow.md`, `40-domain/glossary.md`, `40-domain/business-rules.md`; add a new integration entry to `30-worklog/task-history.md` rather than rewriting historical task rows. + - Proposal: replace current-state claims that mini-game/program creation atomically generates Vite templates with the accepted three-boundary model: Project Service owns only project metadata and `knowledge/`; explicit `makelore-project-scaffold` Skill owns starter files; Main/Works release code owns build, preflight, packaging, upload, and review state. + - Evidence: `electron/coding-projects/project-config.ts`, the canonical/custom creation regression, plugin script/tests, unified-template build smoke, and accepted proposal `ML-PROJECT-SCAFFOLD-001`. + - Future impact: prevents UI/docs/agents from assuming a newly created project is immediately publishable and keeps template evolution independent from project identity. + - Semantic conflicts: supersedes the integrated 2026-08-09 current-state claim; old task records remain historical evidence and must not be edited. + - Human confirmation required: no for factual promotion after the implementation commit is integrated; yes if product wants automatic/bundled initialization instead of the accepted explicit Skill boundary. +- **Executable Skill scripts and host runtime** + - Target canonical documents: `.project-docs/30-worklog/current-state.md`, `20-architecture/system-overview.md`, `20-architecture/module-map.md`, `20-architecture/data-flow.md`, and `40-domain/business-rules.md`. + - Proposal: state that a Device Package Skill with a non-empty standard `scripts/` subtree is executable code requiring preview disclosure and later-turn confirmation even when `kind` remains `skill-only`; parent worker/Agent Server environments expose the non-overridable application Node path through `MAKELORE_NODE_EXECUTABLE`. + - Evidence: `device-package-format.ts`, `device-package-manager.ts`, Plugin Details UI/tests, both Pi process tests, real package prepare/commit test, and Windows Git Bash quoting smoke. + - Future impact: keeps permission messaging accurate for script-backed Skills and gives portable Skills an application-owned runtime without PATH fallback. + - Semantic conflicts: current canonical wording mentions executable Pi extensions only. Existing rule that child workers receive no Device Package resources remains unchanged. + - Human confirmation required: no for promotion after integration; actual packaged platform claims still require the release smokes in Follow-ups. +- **Release obligation** + - Target canonical document: `.project-docs/80-commitments/commitments.md`. + - Proposal: fold this plugin's actual packaged Windows/macOS/Linux execution and Device Package lifecycle trial into the existing client release commitment instead of creating a parallel release gate. + - Evidence: workspace Windows Git Bash and real release-builder smokes are green; immutable tag, installed-app execution, macOS, and Linux evidence are absent. + - Future impact: prevents workspace-only evidence from being mistaken for a distributable cross-platform release. + - Semantic conflicts: the existing commitment already warns that workspace prepare tests do not replace final packaged proof; preserve that stricter rule. + - Human confirmation required: release owner confirmation is required before marking the commitment complete or choosing the public distribution location. diff --git a/electron/coding-packages/device-package-format.ts b/electron/coding-packages/device-package-format.ts index 7475001..7089bba 100644 --- a/electron/coding-packages/device-package-format.ts +++ b/electron/coding-packages/device-package-format.ts @@ -24,6 +24,7 @@ export interface InspectedDevicePackage { resolvedVersion: string | null; skillEntries: DevicePackageSkillEntry[]; extensionEntries: string[]; + hasSkillScripts: boolean; ignoredLifecycleScripts: string[]; } @@ -143,6 +144,25 @@ async function collectSkillFiles(root: string): Promise { return result; } +async function directoryContainsFile(directory: string): Promise { + for (const entry of await readdir(directory, { withFileTypes: true })) { + if (entry.isFile() || entry.isSymbolicLink()) return true; + if (entry.isDirectory() && await directoryContainsFile(path.join(directory, entry.name))) { + return true; + } + } + return false; +} + +async function skillHasScripts(skillPath: string): Promise { + const scriptsPath = path.join(path.dirname(skillPath), 'scripts'); + const metadata = await stat(scriptsPath).catch((error: NodeJS.ErrnoException) => { + if (error.code === 'ENOENT') return null; + throw error; + }); + return metadata?.isDirectory() === true && await directoryContainsFile(scriptsPath); +} + function manifestEntries(value: unknown, field: string): string[] { if (value === undefined) return []; const values = typeof value === 'string' ? [value] : value; @@ -195,7 +215,8 @@ export async function inspectDevicePackage( if (await existsFile(path.join(root, 'SKILL.md'))) skillFiles.push(path.join(root, 'SKILL.md')); const uniqueSkills = new Map(); - for (const skillPath of [...new Set(skillFiles.map((entry) => path.resolve(entry)))]) { + const uniqueSkillPaths = [...new Set(skillFiles.map((entry) => path.resolve(entry)))]; + for (const skillPath of uniqueSkillPaths) { const id = await skillId(skillPath); if (uniqueSkills.has(id)) throw new Error(`Duplicate Skill id: ${id}`); uniqueSkills.set(id, { id, entryPath: relativeContainedPath(root, skillPath) }); @@ -214,6 +235,7 @@ export async function inspectDevicePackage( resolvedVersion: nonempty(packageJson?.version, 128) ?? nonempty(codexManifest?.version, 128), skillEntries: [...uniqueSkills.values()].sort((left, right) => left.id.localeCompare(right.id)), extensionEntries: extensionEntries.sort(), + hasSkillScripts: (await Promise.all(uniqueSkillPaths.map(skillHasScripts))).some(Boolean), ignoredLifecycleScripts: LIFECYCLE_SCRIPT_NAMES.filter((name) => typeof scripts[name] === 'string'), }; } diff --git a/electron/coding-packages/device-package-manager.ts b/electron/coding-packages/device-package-manager.ts index 4e92596..6ac70df 100644 --- a/electron/coding-packages/device-package-manager.ts +++ b/electron/coding-packages/device-package-manager.ts @@ -21,7 +21,13 @@ const PLAN_TTL_MS = 10 * 60 * 1000; const INSTALL_OUTPUT_BYTES = 64 * 1024; const INSTALL_TIMEOUT_MS = 2 * 60 * 1000; const INDEX_SCHEMA_VERSION = 1; -const EXECUTABLE_WARNING = '此包包含可执行 Pi extension,将获得完整桌面权限,包括当前进程可用的文件、网络和进程权限。'; +const EXECUTABLE_WARNING = '此包包含可执行代码,将获得完整桌面权限,包括当前进程可用的文件、网络和进程权限。'; +const SKILL_SCRIPT_WARNING = '此包包含可执行 Skill 脚本;Skill 被调用时,脚本可能使用当前桌面用户可用的文件、网络和进程权限。'; + +function executableWarning(kind: InstallPreviewV1['kind'], includesExecutableCode: boolean): string | null { + if (!includesExecutableCode) return null; + return kind === 'skill-only' ? SKILL_SCRIPT_WARNING : EXECUTABLE_WARNING; +} export class DevicePackageError extends Error { constructor( @@ -375,7 +381,10 @@ export class DevicePackageManager { resolvedSource: string; inspected: Awaited>; }): Promise { - const includesExecutableCode = input.inspected.extensionEntries.length > 0; + const kind = devicePackageKind(input.inspected); + const includesExecutableCode = input.inspected.extensionEntries.length > 0 + || input.inspected.hasSkillScripts; + const warning = executableWarning(kind, includesExecutableCode); const resolvedVersion = input.inspected.resolvedVersion ?? `local-${new Date(this.now()).toISOString().replace(/[-:.]/gu, '')}`; safeStorageSegment(resolvedVersion); @@ -388,12 +397,12 @@ export class DevicePackageManager { packageId: input.inspected.packageId, displayName: input.inspected.displayName, resolvedVersion, - kind: devicePackageKind(input.inspected), + kind, skillEntries: input.inspected.skillEntries, extensionEntries: input.inspected.extensionEntries, includesExecutableCode, ignoredLifecycleScripts: input.inspected.ignoredLifecycleScripts, - warnings: includesExecutableCode ? [EXECUTABLE_WARNING] : [], + warnings: warning ? [warning] : [], scope: 'device-parent-workers', }; const record: DevicePackageRecordV1 = { @@ -435,7 +444,8 @@ export class DevicePackageManager { if (Date.parse(plan.preview.expiresAt) <= this.now()) { throw new DevicePackageError('local_package_plan_expired', 'Install preview has expired'); } - if (plan.preview.includesExecutableCode && !plan.preview.warnings.includes(EXECUTABLE_WARNING)) { + const warning = executableWarning(plan.preview.kind, plan.preview.includesExecutableCode); + if (warning && !plan.preview.warnings.includes(warning)) { throw new DevicePackageError('local_package_confirmation_required', 'Executable permission warning is missing'); } const stagedPackage = path.join(this.stagingDir, planId, 'package'); diff --git a/electron/coding-projects/project-config.ts b/electron/coding-projects/project-config.ts index c53de72..95aeafe 100644 --- a/electron/coding-projects/project-config.ts +++ b/electron/coding-projects/project-config.ts @@ -2,7 +2,7 @@ import { mkdir, stat } from 'node:fs/promises'; import path from 'node:path'; import { isProjectAgentAvatarDataUrl, - isProjectType, + normalizeProjectType, type ProjectAgentResponsibility, type ProjectType, } from '../../shared/project-config'; @@ -199,7 +199,8 @@ export function normalizeCodingProjectConfigV2(value: unknown): CodingProjectCon } const record = value as Partial; if (record.schemaVersion !== 2) throw new Error('Unsupported coding project config schema'); - if (!isProjectType(record.projectType)) throw new Error('Invalid project type'); + const projectType = normalizeProjectType(record.projectType); + if (!projectType) throw new Error('Invalid project type'); if (record.projectId !== undefined && !isCanonicalCodingProjectId(record.projectId)) { throw new Error('Project identity is invalid'); } @@ -216,7 +217,7 @@ export function normalizeCodingProjectConfigV2(value: unknown): CodingProjectCon validateAgentNames(agents); return { schemaVersion: 2, - projectType: record.projectType, + projectType, ...(record.projectId !== undefined ? { projectId: record.projectId } : {}), initialized: record.initialized === true, agents, diff --git a/electron/coding-runtime/pi/agent-server-process.ts b/electron/coding-runtime/pi/agent-server-process.ts index 41ed00b..a049dc5 100644 --- a/electron/coding-runtime/pi/agent-server-process.ts +++ b/electron/coding-runtime/pi/agent-server-process.ts @@ -524,7 +524,7 @@ export class PiAgentServerProcess { ], { cwd: this.options.runtimeRoot, - env: buildPiWorkerEnvironment(this.options.configDir), + env: buildPiWorkerEnvironment(this.options.configDir, this.options.executablePath), stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true, detached: platform() !== 'win32', diff --git a/electron/coding-runtime/pi/worker-process.ts b/electron/coding-runtime/pi/worker-process.ts index 250167d..4dfd9bb 100644 --- a/electron/coding-runtime/pi/worker-process.ts +++ b/electron/coding-runtime/pi/worker-process.ts @@ -180,6 +180,7 @@ function diagnosticPathAliases(value: string | undefined): string[] { export function buildPiWorkerEnvironment( configDir: string, + executablePath: string, overlay: NodeJS.ProcessEnv = {}, inherited: NodeJS.ProcessEnv = process.env, ): NodeJS.ProcessEnv { @@ -196,6 +197,7 @@ export function buildPiWorkerEnvironment( return { ...env, ...overlay, + MAKELORE_NODE_EXECUTABLE: executablePath, ELECTRON_RUN_AS_NODE: '1', PI_CODING_AGENT_DIR: configDir, PI_OFFLINE: '1', @@ -335,7 +337,11 @@ export class PiWorkerProcess { [this.options.cliPath, ...args], { cwd: this.options.cwd, - env: buildPiWorkerEnvironment(this.options.configDir, this.options.env), + env: buildPiWorkerEnvironment( + this.options.configDir, + this.options.executablePath, + this.options.env, + ), stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true, detached: platform() !== 'win32', diff --git a/electron/services/project-packager.ts b/electron/services/project-packager.ts index 7e053c7..8043ce9 100644 --- a/electron/services/project-packager.ts +++ b/electron/services/project-packager.ts @@ -279,7 +279,7 @@ async function readPublishableProjectType(projectPath: string): Promise/scripts/scaffold.mjs" --project-root "$PWD" + ``` + +5. Parse the command's single version-1 JSON result. Success is written only to stdout and includes `status: created`; failure is written only to stderr and includes top-level `code`, `message`, and optional project-relative `paths`. On success, report only the returned `createdFiles`. On failure, report the returned code, message, and paths when present. + +The script performs a complete conflict preflight before writing. Never add `--force`, delete or rename existing user files, retry by overwriting conflicts, or manually fill gaps after a script failure. Resolve the reported conflict with the user before running again. + +## Release-readiness workflow + +Keep this workflow read-only unless the user also asks to fix a reported issue or explicitly starts the product's existing one-click submission flow. + +1. Read `references/submission-requirements.md` in full. Its four categories are semantically different: required input, direct blocker, source-package exclusion, and authoritative runtime/platform check. +2. For a general requirements question, explain those categories and do not claim to have inspected a project. +3. For a project-specific check, gather project-relative evidence for: + - the validity and publishable type of `.makelore/project.json`; + - the required root files and the relevant `package.json` and `package-lock.json` fields; + - source file count, byte limits, path shapes, links or special files, and paths the source packager excludes; + - project-authored source or assets that appear to exist only under an excluded path; + - statically visible build-output risks such as configuration that emits a root `release.json`. +4. Classify every finding instead of flattening it into a blacklist: + - **阻断项**: current evidence proves the one-click flow will reject or cannot accept the project; + - **打包排除影响**: a path will not enter the source archive; its mere presence is not a blocker, but required content there is a release risk; + - **待权威流程验证**: npm installation, Vite execution, built output, both browser viewports, account/ownership, upload, cloud build, and review state; + - **已通过静态项**: a requirement supported by concrete file evidence. +5. Report the evidence path and observed value for every blocker. Do not infer an external-network blocker merely because source text contains an HTTP(S) URL; only an actual cross-origin request during authoritative preflight proves that failure. You may report the literal as a runtime risk. +6. Use exactly one of these conclusions: + - `不能进入一键提交` when at least one confirmed blocker exists; + - `可进入一键提交,仍需运行期验证` when static blockers are absent but the product flow has not run; + - `本地构建与预检已通过,仍待平台结果` only when the current one-click flow actually returned that evidence. + +Never say that a project is approved based on this Skill's inspection. Do not run `npm ci`, Vite configuration, project scripts, upload, or submission merely to answer a readiness question: those steps execute code or change external state and belong to the explicit product flow. Do not delete excluded files just to make the report look clean. + +## Error handling + +Treat these codes as stable outcomes: + +- `USAGE_INVALID`: the script invocation is malformed. +- `PROJECT_CONFIG_MISSING`: `.makelore/project.json` is absent. +- `PROJECT_CONFIG_INVALID`: project metadata is unreadable or unsupported. +- `PROJECT_TYPE_UNSUPPORTED`: the project is neither `interactive_ai_app` nor an existing legacy `mini_game` / `mini_program` project. +- `TARGET_CONFLICT`: at least one target path already exists or cannot be used; show every returned conflict. +- `TEMPLATE_UNAVAILABLE`: the installed plugin is incomplete. +- `WRITE_FAILED`: file creation failed and the script attempted to roll back only files and directories it created in this run. + +## Ownership boundaries + +- MakeLore owns project creation and `.makelore/project.json`. +- This skill owns only the fixed starter files listed by the scaffold script and never edits project metadata. +- New projects use `interactive_ai_app`. Existing `mini_game` and `mini_program` metadata are read-only compatibility aliases and receive the same unified scaffold; the script reports the canonical type without rewriting metadata. +- MakeLore's existing release builder and publisher remain authoritative for build, preflight, packaging, upload, and approval submission. +- The requirements reference is this plugin version's release contract snapshot. If it differs from runtime validation, report the mismatch, follow the runtime result, and do not silently reinterpret an exclusion as a prohibition. diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/agents/openai.yaml b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/agents/openai.yaml new file mode 100644 index 0000000..4b4b022 --- /dev/null +++ b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "MakeLore 项目初始化与发布检查" + short_description: "创建交互式 AI 应用骨架并检查发布要求" + default_prompt: "使用 $makelore-project-scaffold 初始化当前 MakeLore 交互式 AI 应用项目,并检查是否满足一键提交要求。" diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package-lock.json b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package-lock.json new file mode 100644 index 0000000..701d913 --- /dev/null +++ b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package-lock.json @@ -0,0 +1,1117 @@ +{ + "name": "makelore-project", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "makelore-project", + "version": "0.1.0", + "devDependencies": { + "vite": "7.3.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://mirrors.cloud.tencent.com/npm/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://mirrors.cloud.tencent.com/npm/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://mirrors.cloud.tencent.com/npm/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://mirrors.cloud.tencent.com/npm/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://mirrors.cloud.tencent.com/npm/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://mirrors.cloud.tencent.com/npm/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package.json b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package.json new file mode 100644 index 0000000..939fba1 --- /dev/null +++ b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/package.json @@ -0,0 +1,15 @@ +{ + "name": "makelore-project", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.6.2", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vite build", + "preview": "vite preview --host 0.0.0.0" + }, + "devDependencies": { + "vite": "7.3.1" + } +} diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/vite.config.js b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/vite.config.js new file mode 100644 index 0000000..c5257bb --- /dev/null +++ b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/common/vite.config.js @@ -0,0 +1,5 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + base: './', +}); diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/index.html b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/index.html new file mode 100644 index 0000000..eb354da --- /dev/null +++ b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/index.html @@ -0,0 +1,20 @@ + + + + + + MakeLore 交互式 AI 应用 + + +
+
+

MAKELORE

+

交互式 AI 应用

+

这是你的创作起点。修改页面、加入交互,让想法真正运行起来。

+ +

已经准备好了

+
+
+ + + diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/main.js b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/main.js new file mode 100644 index 0000000..6303f22 --- /dev/null +++ b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/main.js @@ -0,0 +1,10 @@ +import './style.css'; + +const button = document.querySelector('button'); +const status = document.querySelector('.status'); +let interactions = 0; + +button.addEventListener('click', () => { + interactions += 1; + status.textContent = `已完成 ${interactions} 次互动`; +}); diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/style.css b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/style.css new file mode 100644 index 0000000..f56b57e --- /dev/null +++ b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/assets/templates/interactive_ai_app/style.css @@ -0,0 +1,16 @@ +:root { + color: #192033; + background: #eef1ff; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +* { box-sizing: border-box; } +body { margin: 0; } +.page { min-height: 100vh; display: grid; place-items: center; padding: 24px; } +.card { width: min(520px, 100%); border: 1px solid rgb(86 91 150 / 18%); border-radius: 28px; background: rgb(255 255 255 / 88%); padding: clamp(28px, 7vw, 52px); box-shadow: 0 24px 80px rgb(62 68 128 / 16%); } +.eyebrow { margin: 0 0 12px; color: #6558d9; font-size: 12px; font-weight: 800; letter-spacing: 0.18em; } +h1 { margin: 0; font-size: clamp(34px, 8vw, 58px); line-height: 1.05; letter-spacing: -0.04em; } +.card > p:not(.eyebrow) { line-height: 1.7; } +button { min-height: 44px; margin-top: 12px; border: 0; border-radius: 14px; background: #6558d9; color: #fff; padding: 12px 20px; font: inherit; font-weight: 750; cursor: pointer; } +button:hover { background: #5145c5; } +.status { color: #596078; font-size: 14px; } diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/references/submission-requirements.md b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/references/submission-requirements.md new file mode 100644 index 0000000..0a98626 --- /dev/null +++ b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/references/submission-requirements.md @@ -0,0 +1,141 @@ +# MakeLore 发布与提交审批要求 + +这份文件是本插件版本对 MakeLore 一键提交合同的说明,供发布前检查和失败归因使用。MakeLore 客户端实际执行的源码打包、固定构建、双视口预检、上传以及平台返回结果始终是唯一权威。若本说明与当前客户端不一致,必须报告差异并以客户端结果为准。 + +## 正确区分四类结果 + +- **必须具备**:缺失或格式不符会直接阻断发布。 +- **直接阻断**:项目当前存在已知会被本地发布链拒绝的内容或状态。 +- **打包排除**:文件可以留在项目里,但不会进入源码归档。存在本身不等于违规;只有把构建或运行必需内容放在这些位置才会造成问题。 +- **运行时或平台确认**:仅凭静态文件不能下结论,必须由 MakeLore 一键提交实际运行后确认。 + +静态检查没有阻断项时,只能得出“可进入一键提交,仍需运行期验证”,不能称为“发布通过”或“审批通过”。 + +## 必须具备的发布输入 + +### 项目身份 + +- 项目目录必须存在且可读。 +- `.makelore/project.json` 必须是 MakeLore 可读取的完整 schema v2 项目配置;不仅 `projectType`,配置中的版本、项目身份、初始化状态、知识目录、Agent 和时间字段也必须保持有效。不要手工缩减为只有项目类型的 JSON。 +- 新项目的 `projectType` 必须是 `interactive_ai_app`。已有项目中的 `mini_game` 或 `mini_program` 会在读取时归一化为同一个“交互式 AI 应用”类型;`custom`、缺失配置和无效配置都不能发布。 + +### 根目录构建输入 + +- `package.json`、`package-lock.json` 和 `index.html` 必须位于项目根目录,且都是普通文件而非符号链接。 +- `package.json` 和 `package-lock.json` 必须分别是有效 JSON 对象,不能是数组、字符串或其他 JSON 值。 +- `package.json` 的 `dependencies.vite` 或 `devDependencies.vite` 必须是非空字符串。 +- `packageManager` 可以省略;若存在,必须是以 `npm@` 开头的字符串。 +- `package-lock.json.lockfileVersion` 必须是整数 `2` 或 `3`。 +- 源 `index.html` 去除空白后必须非空。 +- 当前插件脚手架以 npm 11.6.2 生成锁文件;实际发布仍以当前 MakeLore 客户端随附的固定 npm 运行时为准。 + +### 固定构建合同 + +- 发布器在隔离的源码快照中执行 `npm ci --ignore-scripts --no-audit --no-fund --prefer-offline`,不会运行依赖安装生命周期脚本。项目不得依赖 `preinstall`、`install`、`postinstall` 等脚本生成发布必需文件。 +- 随后使用锁文件实际安装出的项目 Vite 执行构建,并强制 `--base ./`、独立输出目录和清空输出目录。项目根目录已有的 `dist/` 或 `build/` 不会被直接发布。 +- Vite 配置及其插件会以当前桌面用户权限执行;这个构建步骤不是代码沙箱,也不是可信性证明。 +- 最终构建产物根目录必须包含普通、非空、有效 UTF-8 的 `index.html`。 + +### 数量与大小上限 + +- 源码归档最多 2,000 个文件,其中包括发布器生成的 `niancode.yml`,因此用户源码最多占其余名额。 +- 纳入源码包的文件总量最多 200 MiB;生成的源码 ZIP 最多 50 MiB。 +- `package.json` 最大 2 MiB,`package-lock.json` 最大 50 MiB,根 `index.html` 最大 10 MiB。 +- 构建产物最多 2,000 个文件,总量最多 50 MiB。 + +## 会直接阻断发布的内容或状态 + +### 源项目与路径 + +- 项目配置缺失、无效、类型为 `custom`,或项目目录不存在/不可读。 +- 必需根文件缺失、不是普通文件、超过单文件上限、JSON 结构无效、未声明 Vite、声明了非 npm `packageManager`、锁文件版本不受支持,或 `index.html` 为空。 +- 未被排除的任意符号链接、目录链接、socket、设备文件等非普通文件类型。 +- 相对归档路径为空,含反斜杠、空段、`.`、`..`、冒号,或任一路径段以空格或点结尾。 +- 任一路径段是 Windows 保留名 `CON`、`PRN`、`AUX`、`NUL`、`COM1`–`COM9`、`LPT1`–`LPT9`,包括这些名称带扩展名的形式。 +- 在大小写不敏感比较下重复的归档路径,例如同时存在 `Logo.png` 与 `logo.png`。 +- 源码扫描、读取或写入归档期间文件或目录发生变化。 +- 超出源码文件数、源码字节数或源码 ZIP 上限。 + +### 构建与产物 + +- 固定 npm 运行时不可用、`npm ci` 失败、Vite 不可用、Vite 构建失败、构建超时或被取消。 +- 构建输出包含符号链接或其他非普通文件。 +- 构建输出超过 2,000 个文件或 50 MiB。 +- 构建产物根目录缺少 `index.html`,该文件为空或不是有效 UTF-8。 +- 构建产物根目录包含大小写不限的 `release.json`。这里禁止的是**最终构建产物的根文件**;源码中同名文件只有在最终被输出到该位置时才会阻断。 + +### 页面运行预检 + +- 构建首页未能在 1280×720 和 390×844 两个视口中全部加载完成并显示可见文字、背景、有效图片/视频、SVG 或尺寸合理的 Canvas。 +- 未捕获异常、`console.error`、Chromium Log error、非取消的资源加载失败、渲染进程故障或任意 HTTP 响应状态大于等于 400。 +- 预检期间实际发起到本地预览 origin 以外的 HTTP/HTTPS 请求,或页面最终跳转离开精确预览 URL。源码里仅出现 URL 字符串还不能证明会触发该阻断。 +- 两个视口共用的 30 秒预检时限耗尽。每个视口加载后只等待短暂稳定时间,因此首屏不能依赖长时间后台任务才出现。 + +新窗口会被预检环境拒绝打开;项目不应依赖弹窗才能展示首屏或完成启动。 + +## 会从源码归档排除、但存在本身不阻断的内容 + +以下匹配均不区分大小写。目录规则按任意层级的目录名匹配;文件名规则按任意层级的 basename 匹配。 + +### 排除目录名 + +`.cache`、`.git`、`.gnupg`、`.idea`、`.kube`、`.next`、`.makelore`、`.niancode`、`.opencode`、`.project-docs`、`.turbo`、`.vite`、`.vscode`、`.aws`、`.docker`、`.ssh`、`build`、`coverage`、`deploy`、`dist`、`knowledge`、`node_modules`、`secrets`。 + +### 排除文件名 + +`.dockerignore`、`.ds_store`、`.git-credentials`、`.netrc`、`.npmrc`、`art_guide.md`、`asset_plan.md`、`credentials.json`、`debug.log`、`dockerfile`、`docker-compose.yaml`、`docker-compose.yml`、`findings.md`、`gdd.md`、`nginx.conf`、`niancode.yml`、`_netrc`、`id_dsa`、`id_ecdsa`、`id_ed25519`、`id_rsa`、`product_overview.md`、`progress.md`、`release.md`、`task_plan.md`、`tasks.md`、`thumbs.db`、`version.md`、`works-cloud-deploy.json`、`works-deploy-check.json`、`works-publish.json`、`部署报告.md`。 + +### 排除模式 + +- 文件名以 `.env` 开头。 +- 文件名以 `.crt`、`.jks`、`.key`、`.keystore`、`.log`、`.p12`、`.pem`、`.pfx`、`.p8`、`.ppk`、`.tfstate` 或 `.tfvars` 结尾。 +- 文件名任意位置包含 `.tfstate` 或 `.tfvars`。 +- JSON 文件名去掉非字母数字字符后包含 `serviceaccount`、`firebaseadmin` 或 `applicationdefaultcredentials`。 +- 项目根相对路径以 `works-` 开头的文件,包括这类顶层目录中的文件。 + +排除规则的后果: + +- 不要把项目自己编写的源代码、首屏素材或构建配置只放在上述位置;发布快照看不到它们。 +- `node_modules/` 被排除是正常行为,发布器会根据根 `package-lock.json` 重新安装依赖。 +- `.npmrc` 被排除,发布器还会显式使用空的临时 npm 配置;项目不能依赖本机或仓库 `.npmrc` 才能安装。 +- 项目里的 `niancode.yml` 被排除,发布器会生成自己的固定清单。 +- `VERSION.md` 不是必需文件。若它是 64 KiB 以内的稳定普通文件,`Current:` 后不超过 80 字符的值可用于版本名;否则发布器自动生成版本名。无论哪种情况,该文件都不进入源码归档。 +- 源码中已有 `dist/` 和 `build/` 不会被上传;最终发布的是固定流程本次生成的构建产物。 + +不要因为检查发现排除项就要求删除它们。应报告哪些路径被排除,以及是否有证据表明构建或运行依赖这些路径。 + +## 首次提交必须提供的平台资料 + +当前 MakeLore 界面合同要求: + +- 项目名称:必填,最多 160 个字符。 +- 项目简介:必填,最多 2,000 个字符。 +- 发布者姓名:必填,最多 160 个字符。 +- 发布者年龄:必填,为 1–150 的整数。 +- 项目封面:首次提交必填,仅支持 PNG、JPEG 或 WebP,必须非空、内容签名与声明格式相符,最大 10 MiB。 +- 详细介绍可选,最多 10,000 个字符。 +- 适龄范围和难度可选,各最多 40 个字符。 + +作品 `app_id` 和类别由当前项目生成,不要求用户另造。若平台已存在状态为 `draft` 或 `published` 的该作品,当前流程只提交新构建版本,并保留平台上的名称、简介、作者信息和封面。窗口打开后平台状态发生变化会产生资料冲突,本次不会覆盖或继续提交。 + +## 只能由一键提交和平台确认 + +- 当前账号是否已登录,登录态是否仍有效。 +- 当前本地项目是否仍存在,以及本机固定 Node/npm 发布运行时、npm 缓存和依赖来源是否可用。 +- `npm ci`、项目 Vite 配置/插件、实际构建输出和双视口页面运行是否通过。 +- 当前账号是否拥有目标作品,平台资料状态是否与发布窗口一致。 +- 同一项目是否已有本地构建占用,以及平台是否因现有构建、版本或审核状态拒绝新版本。 +- 平台是否接受首次作品资料、封面、源码归档、构建归档和版本化产物合同。 +- 云端构建最终是成功、失败、取消还是状态未知,以及运营审核最终是通过还是拒绝。 + +本 Skill 的静态检查不会安装依赖、执行 Vite 配置、启动浏览器预检、上传归档或提交审核。用户明确发起一键提交后,以该流程返回的错误码、构建状态和平台审核状态为准。 + +## 项目检查时的输出顺序 + +对具体项目做发布就绪检查时,按以下顺序报告,不打分: + +1. **结论**:只能使用 Skill 工作流定义的三种结论之一。 +2. **阻断项**:仅列有当前证据的直接阻断,并附项目相对路径和观察值。 +3. **打包排除影响**:列出实际命中的排除路径及其可能丢失的必需内容;若没有依赖证据,明确写“存在但不阻断”。 +4. **待权威流程验证**:列出本次没有也不能静态证明的构建、浏览器、账号和平台项。 +5. **已通过静态项**:列出已读取并确认的必要输入,不把未检查项写成通过。 diff --git a/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/scripts/scaffold.mjs b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/scripts/scaffold.mjs new file mode 100644 index 0000000..9047eeb --- /dev/null +++ b/plugins/makelore-project-scaffold/skills/makelore-project-scaffold/scripts/scaffold.mjs @@ -0,0 +1,304 @@ +#!/usr/bin/env node + +import { + lstat, + mkdir, + open, + readFile, + rmdir, + unlink, +} from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const skillDirectory = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const templateDirectory = path.join(skillDirectory, "assets", "templates"); +const fileOperations = { mkdir, open, rmdir, unlink }; + +const commonTargets = [ + ["package.json", "common/package.json"], + ["package-lock.json", "common/package-lock.json"], + ["vite.config.js", "common/vite.config.js"], +]; + +const targetsByProjectType = { + interactive_ai_app: [ + ...commonTargets, + ["index.html", "interactive_ai_app/index.html"], + ["src/main.js", "interactive_ai_app/main.js"], + ["src/style.css", "interactive_ai_app/style.css"], + ], +}; + +const canonicalProjectTypes = { + interactive_ai_app: "interactive_ai_app", + mini_game: "interactive_ai_app", + mini_program: "interactive_ai_app", +}; + +class ScaffoldError extends Error { + constructor(code, message, details = {}) { + super(message); + this.code = code; + this.details = details; + } +} + +function parseArguments(argv) { + if (argv.length !== 2 || argv[0] !== "--project-root" || !argv[1]?.trim()) { + throw new ScaffoldError( + "USAGE_INVALID", + "脚本参数无效,必须提供 --project-root 。", + ); + } + + return path.resolve(argv[1]); +} + +async function readProjectType(projectRoot) { + const configPath = path.join(projectRoot, ".makelore", "project.json"); + let source; + + try { + source = await readFile(configPath, "utf8"); + } catch (error) { + if (error?.code === "ENOENT") { + throw new ScaffoldError( + "PROJECT_CONFIG_MISSING", + "当前目录缺少 .makelore/project.json。", + ); + } + throw new ScaffoldError( + "PROJECT_CONFIG_INVALID", + "无法读取 .makelore/project.json。", + ); + } + + let config; + try { + config = JSON.parse(source); + } catch { + throw new ScaffoldError( + "PROJECT_CONFIG_INVALID", + ".makelore/project.json 不是有效 JSON。", + ); + } + + if (!config || typeof config !== "object" || config.schemaVersion !== 2) { + throw new ScaffoldError( + "PROJECT_CONFIG_INVALID", + ".makelore/project.json 必须使用 schemaVersion 2。", + ); + } + + const projectType = typeof config.projectType === "string" + ? canonicalProjectTypes[config.projectType] + : undefined; + if (!projectType || !Object.hasOwn(targetsByProjectType, projectType)) { + throw new ScaffoldError( + "PROJECT_TYPE_UNSUPPORTED", + "仅支持初始化 interactive_ai_app 项目。", + ); + } + + return projectType; +} + +function toNativePath(root, relativePath) { + return path.join(root, ...relativePath.split("/")); +} + +async function readTemplates(targets) { + const loaded = []; + const missing = []; + + for (const [targetPath, templatePath] of targets) { + try { + loaded.push({ + targetPath, + contents: await readFile(toNativePath(templateDirectory, templatePath)), + }); + } catch { + missing.push(targetPath); + } + } + + if (missing.length > 0) { + throw new ScaffoldError( + "TEMPLATE_UNAVAILABLE", + "插件缺少必需的工程模板,请重新安装。", + { paths: missing.sort() }, + ); + } + + return loaded; +} + +async function pathState(candidate) { + try { + return await lstat(candidate); + } catch (error) { + if (error?.code === "ENOENT") { + return undefined; + } + throw error; + } +} + +function parentDirectories(relativePath) { + const parents = []; + let current = path.posix.dirname(relativePath); + + while (current !== ".") { + parents.push(current); + current = path.posix.dirname(current); + } + + return parents.reverse(); +} + +async function preflightTargets(projectRoot, templates) { + const conflicts = new Set(); + const missingDirectories = new Set(); + + for (const { targetPath } of templates) { + let blockedByParent = false; + for (const directory of parentDirectories(targetPath)) { + const state = await pathState(toNativePath(projectRoot, directory)); + if (!state) { + missingDirectories.add(directory); + } else if (!state.isDirectory()) { + conflicts.add(directory); + blockedByParent = true; + break; + } + } + + if (!blockedByParent && await pathState(toNativePath(projectRoot, targetPath))) { + conflicts.add(targetPath); + } + } + + if (conflicts.size > 0) { + throw new ScaffoldError( + "TARGET_CONFLICT", + "目标路径已存在或必需的父路径不是目录,未写入任何文件。", + { paths: [...conflicts].sort() }, + ); + } + + return [...missingDirectories].sort((left, right) => { + const depthDifference = left.split("/").length - right.split("/").length; + return depthDifference || left.localeCompare(right); + }); +} + +function relativeProjectPath(projectRoot, absolutePath) { + const relativePath = path.relative(projectRoot, absolutePath); + return path.sep === "/" ? relativePath : relativePath.split(path.sep).join("/"); +} + +async function rollback(projectRoot, createdFiles, createdDirectories, operations) { + const remainingPaths = []; + for (const filePath of [...createdFiles].reverse()) { + try { + await operations.unlink(filePath); + } catch { + remainingPaths.push(relativeProjectPath(projectRoot, filePath)); + } + } + + for (const directoryPath of [...createdDirectories].reverse()) { + try { + await operations.rmdir(directoryPath); + } catch { + remainingPaths.push(relativeProjectPath(projectRoot, directoryPath)); + } + } + + return [...new Set(remainingPaths)].sort(); +} + +export async function writeScaffold( + projectRoot, + templates, + missingDirectories, + operations = fileOperations, +) { + const createdFiles = []; + const createdDirectories = []; + + try { + for (const relativeDirectory of missingDirectories) { + const absoluteDirectory = toNativePath(projectRoot, relativeDirectory); + await operations.mkdir(absoluteDirectory); + createdDirectories.push(absoluteDirectory); + } + + for (const { targetPath, contents } of templates) { + const absoluteTarget = toNativePath(projectRoot, targetPath); + let handle; + try { + handle = await operations.open(absoluteTarget, "wx"); + createdFiles.push(absoluteTarget); + await handle.writeFile(contents); + } finally { + await handle?.close(); + } + } + } catch { + const remainingPaths = await rollback( + projectRoot, + createdFiles, + createdDirectories, + operations, + ); + throw new ScaffoldError( + "WRITE_FAILED", + "创建工程骨架失败;本次新建内容已尽力回滚。", + remainingPaths.length > 0 ? { paths: remainingPaths } : {}, + ); + } +} + +async function scaffold(argv) { + const projectRoot = parseArguments(argv); + const projectType = await readProjectType(projectRoot); + const templates = await readTemplates(targetsByProjectType[projectType]); + const missingDirectories = await preflightTargets(projectRoot, templates); + await writeScaffold(projectRoot, templates, missingDirectories); + + return { + schemaVersion: 1, + ok: true, + status: "created", + projectType, + createdFiles: templates.map(({ targetPath }) => targetPath).sort(), + }; +} + +const isMain = process.argv[1] + && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url; + +if (isMain) { + try { + const result = await scaffold(process.argv.slice(2)); + process.stdout.write(`${JSON.stringify(result)}\n`); + } catch (error) { + const knownError = + error instanceof ScaffoldError + ? error + : new ScaffoldError("WRITE_FAILED", "创建工程骨架失败;未写入未知来源的内容。"); + + process.stderr.write( + `${JSON.stringify({ + schemaVersion: 1, + ok: false, + code: knownError.code, + message: knownError.message, + ...knownError.details, + })}\n`, + ); + process.exitCode = 1; + } +} diff --git a/plugins/makelore-project-scaffold/tests/scaffold.test.mjs b/plugins/makelore-project-scaffold/tests/scaffold.test.mjs new file mode 100644 index 0000000..94a837b --- /dev/null +++ b/plugins/makelore-project-scaffold/tests/scaffold.test.mjs @@ -0,0 +1,289 @@ +import assert from 'node:assert/strict'; +import { + cp, + mkdtemp, + mkdir, + open, + readFile, + readdir, + rm, + rmdir, + unlink, + writeFile, +} from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +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 scaffoldScript = path.join( + pluginRoot, + 'skills', + 'makelore-project-scaffold', + 'scripts', + 'scaffold.mjs', +); +const { writeScaffold } = await import(pathToFileURL(scaffoldScript).href); + +const expectedFiles = [ + 'index.html', + 'package-lock.json', + 'package.json', + 'src/main.js', + 'src/style.css', + 'vite.config.js', +]; + +async function initializeProject(root, projectType) { + await mkdir(path.join(root, '.makelore')); + await mkdir(path.join(root, 'knowledge')); + const config = `${JSON.stringify({ + schemaVersion: 2, + projectId: 'scaffold-test', + projectType, + }, null, 2)}\n`; + await writeFile(path.join(root, '.makelore', 'project.json'), config, 'utf8'); + return config; +} + +async function createProject(projectType) { + const root = await mkdtemp(path.join(tmpdir(), 'makelore-scaffold-')); + return { root, config: await initializeProject(root, projectType) }; +} + +function runScaffold(projectRoot, scriptPath = scaffoldScript) { + const result = spawnSync( + process.execPath, + [scriptPath, '--project-root', projectRoot], + { encoding: 'utf8' }, + ); + return { + ...result, + payload: JSON.parse((result.status === 0 ? result.stdout : result.stderr).trim()), + }; +} + +async function listTree(root, directory = root) { + const result = []; + for (const entry of await readdir(directory, { withFileTypes: true })) { + const absolutePath = path.join(directory, entry.name); + const relativePath = path.relative(root, absolutePath).split(path.sep).join('/'); + if (entry.isDirectory()) { + result.push(`${relativePath}/`, ...await listTree(root, absolutePath)); + } else { + result.push(relativePath); + } + } + return result.sort(); +} + +for (const sourceProjectType of ['interactive_ai_app', 'mini_game', 'mini_program']) { + test(`creates the unified scaffold for ${sourceProjectType} without changing project metadata`, async () => { + const { root, config } = await createProject(sourceProjectType); + try { + await writeFile(path.join(root, 'notes.txt'), 'keep me', 'utf8'); + const result = runScaffold(root); + assert.equal(result.status, 0, result.stderr); + assert.equal(result.stderr, ''); + assert.deepEqual(result.payload, { + schemaVersion: 1, + ok: true, + status: 'created', + projectType: 'interactive_ai_app', + createdFiles: expectedFiles, + }); + assert.equal( + await readFile(path.join(root, '.makelore', 'project.json'), 'utf8'), + config, + ); + const packageJson = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8')); + const packageLock = JSON.parse(await readFile(path.join(root, 'package-lock.json'), 'utf8')); + assert.equal(packageJson.packageManager, 'npm@11.6.2'); + assert.equal(packageJson.devDependencies.vite, '7.3.1'); + assert.equal(packageLock.lockfileVersion, 3); + assert.match(await readFile(path.join(root, 'vite.config.js'), 'utf8'), /base:\s*['"]\.\/['"]/u); + assert.equal(await readFile(path.join(root, 'notes.txt'), 'utf8'), 'keep me'); + for (const relativePath of expectedFiles) { + assert.ok((await readFile(path.join(root, ...relativePath.split('/')))).length > 0); + } + assert.match(await readFile(path.join(root, 'index.html'), 'utf8'), /src="\.\/src\/main\.js"/u); + assert.match(await readFile(path.join(root, 'src', 'main.js'), 'utf8'), /import '\.\/style\.css'/u); + assert.deepEqual(await listTree(root), [ + '.makelore/', + '.makelore/project.json', + 'knowledge/', + 'notes.txt', + 'src/', + ...expectedFiles, + ].sort()); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); +} + +test('reports every existing target conflict and writes nothing else', async () => { + const { root } = await createProject('interactive_ai_app'); + try { + await writeFile(path.join(root, 'index.html'), 'mine', 'utf8'); + await writeFile(path.join(root, 'package.json'), 'mine', 'utf8'); + + const result = runScaffold(root); + assert.equal(result.status, 1); + assert.equal(result.stdout, ''); + assert.deepEqual(result.payload, { + schemaVersion: 1, + ok: false, + code: 'TARGET_CONFLICT', + message: '目标路径已存在或必需的父路径不是目录,未写入任何文件。', + paths: ['index.html', 'package.json'], + }); + assert.equal(await readFile(path.join(root, 'index.html'), 'utf8'), 'mine'); + assert.equal(await readFile(path.join(root, 'package.json'), 'utf8'), 'mine'); + assert.deepEqual((await readdir(root)).sort(), ['.makelore', 'index.html', 'knowledge', 'package.json']); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('reports a blocking parent path as a target conflict', async () => { + const { root } = await createProject('interactive_ai_app'); + try { + await writeFile(path.join(root, 'src'), 'mine', 'utf8'); + const result = runScaffold(root); + assert.equal(result.status, 1); + assert.equal(result.payload.code, 'TARGET_CONFLICT'); + assert.deepEqual(result.payload.paths, ['src']); + assert.deepEqual((await readdir(root)).sort(), ['.makelore', 'knowledge', 'src']); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('rejects custom projects without creating scaffold files', async () => { + const { root } = await createProject('custom'); + try { + const result = runScaffold(root); + assert.equal(result.status, 1); + assert.equal(result.payload.code, 'PROJECT_TYPE_UNSUPPORTED'); + assert.deepEqual((await readdir(root)).sort(), ['.makelore', 'knowledge']); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('rejects a missing project config without creating scaffold files', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'makelore-scaffold-')); + try { + const result = runScaffold(root); + assert.equal(result.status, 1); + assert.equal(result.payload.code, 'PROJECT_CONFIG_MISSING'); + assert.deepEqual(await readdir(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('rejects invalid project metadata without creating scaffold files', async () => { + const { root } = await createProject('interactive_ai_app'); + try { + await writeFile(path.join(root, '.makelore', 'project.json'), '{broken', 'utf8'); + const result = runScaffold(root); + assert.equal(result.status, 1); + assert.equal(result.payload.code, 'PROJECT_CONFIG_INVALID'); + assert.deepEqual((await readdir(root)).sort(), ['.makelore', 'knowledge']); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('rejects invalid CLI arguments with a single stderr JSON object', () => { + const result = spawnSync(process.execPath, [scaffoldScript], { encoding: 'utf8' }); + assert.equal(result.status, 1); + assert.equal(result.stdout, ''); + assert.deepEqual(JSON.parse(result.stderr.trim()), { + schemaVersion: 1, + ok: false, + code: 'USAGE_INVALID', + message: '脚本参数无效,必须提供 --project-root 。', + }); +}); + +test('reports a missing template before writing any target', async () => { + const outer = await mkdtemp(path.join(tmpdir(), 'makelore-scaffold-template-')); + try { + const projectRoot = path.join(outer, 'project'); + const copiedSkill = path.join(outer, 'skill'); + await mkdir(projectRoot); + await initializeProject(projectRoot, 'interactive_ai_app'); + await cp(path.dirname(path.dirname(scaffoldScript)), copiedSkill, { recursive: true }); + await rm(path.join(copiedSkill, 'assets', 'templates', 'interactive_ai_app', 'main.js')); + + const result = runScaffold(projectRoot, path.join(copiedSkill, 'scripts', 'scaffold.mjs')); + assert.equal(result.status, 1); + assert.equal(result.payload.code, 'TEMPLATE_UNAVAILABLE'); + assert.deepEqual(result.payload.paths, ['src/main.js']); + assert.deepEqual(await listTree(projectRoot), [ + '.makelore/', + '.makelore/project.json', + 'knowledge/', + ]); + } finally { + await rm(outer, { recursive: true, force: true }); + } +}); + +test('runs when both the installed Skill path and project path contain spaces and Chinese', async () => { + const outer = await mkdtemp(path.join(tmpdir(), 'makelore-scaffold-paths-')); + try { + const projectRoot = path.join(outer, '项目 空格'); + const copiedSkill = path.join(outer, '插件 安装', '项目 初始化'); + await mkdir(projectRoot); + await mkdir(path.dirname(copiedSkill), { recursive: true }); + await initializeProject(projectRoot, 'interactive_ai_app'); + await cp(path.dirname(path.dirname(scaffoldScript)), copiedSkill, { recursive: true }); + + const result = runScaffold(projectRoot, path.join(copiedSkill, 'scripts', 'scaffold.mjs')); + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(result.payload.createdFiles, expectedFiles); + } finally { + await rm(outer, { recursive: true, force: true }); + } +}); + +test('rolls back files and directories created before a handled write failure', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'makelore-scaffold-rollback-')); + let openCalls = 0; + try { + await assert.rejects( + writeScaffold( + root, + [ + { targetPath: 'first.txt', contents: Buffer.from('first') }, + { targetPath: 'nested/second.txt', contents: Buffer.from('second') }, + ], + ['nested'], + { + mkdir, + open: async (...args) => { + openCalls += 1; + if (openCalls === 2) throw new Error('injected write failure'); + return await open(...args); + }, + rmdir, + unlink, + }, + ), + (error) => { + assert.equal(error.code, 'WRITE_FAILED'); + assert.equal(error.details.paths, undefined); + return true; + }, + ); + assert.deepEqual(await readdir(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/shared/project-config.ts b/shared/project-config.ts index f657f92..33a2611 100644 --- a/shared/project-config.ts +++ b/shared/project-config.ts @@ -1,13 +1,23 @@ export const PRODUCT_OVERVIEW_FILENAME = 'PRODUCT_OVERVIEW.md'; export const LEGACY_PROMOTION_PLAN_FILENAME = 'PROMOTION_PLAN.md'; -export const PROJECT_TYPES = ['mini_game', 'mini_program', 'custom'] as const; +export const PROJECT_TYPES = ['interactive_ai_app', 'custom'] as const; export type ProjectType = (typeof PROJECT_TYPES)[number]; +const LEGACY_INTERACTIVE_PROJECT_TYPES = new Set(['mini_game', 'mini_program']); + export function isProjectType(value: unknown): value is ProjectType { return typeof value === 'string' && PROJECT_TYPES.includes(value as ProjectType); } +export function normalizeProjectType(value: unknown): ProjectType | undefined { + if (isProjectType(value)) return value; + if (typeof value === 'string' && LEGACY_INTERACTIVE_PROJECT_TYPES.has(value)) { + return 'interactive_ai_app'; + } + return undefined; +} + export type ProjectAgentResponsibility = { mission: string; owns: string[]; diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index b0e8f44..6637de7 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -167,7 +167,7 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi const [newProjectName, setNewProjectName] = useState(''); const [newProjectSelectedPath, setNewProjectSelectedPath] = useState(''); const [newProjectDirectoryMode, setNewProjectDirectoryMode] = useState('use-selected-directory'); - const [newProjectType, setNewProjectType] = useState('mini_game'); + const [newProjectType, setNewProjectType] = useState('interactive_ai_app'); const [newProjectIdentityKind, setNewProjectIdentityKind] = useState('create'); const [newProjectIdentityProjectId, setNewProjectIdentityProjectId] = useState(''); const [createProjectError, setCreateProjectError] = useState(null); @@ -399,7 +399,7 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi setNewProjectName(''); setNewProjectSelectedPath(''); setNewProjectDirectoryMode('use-selected-directory'); - setNewProjectType('mini_game'); + setNewProjectType('interactive_ai_app'); setNewProjectIdentityKind('create'); setNewProjectIdentityProjectId(''); setCreateProjectError(null); @@ -409,7 +409,7 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi const closeCreateProject = () => { if (creatingProject) return; setCreateDialogOpen(false); - setNewProjectType('mini_game'); + setNewProjectType('interactive_ai_app'); setNewProjectIdentityKind('create'); setNewProjectIdentityProjectId(''); setCreateProjectError(null); @@ -856,10 +856,9 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
项目类型 -
+
{([ - ['mini_game', '小游戏', '适合实时互动、Canvas 和关卡玩法;创建后可直接提交审核。'], - ['mini_program', '小程序', '适合页面、表单和轻量工具;创建后可直接提交审核。'], + ['interactive_ai_app', '交互式 AI 应用', '创建可交互、可发布的 AI 应用项目;进入对话后可用项目初始化 Skill 生成工程骨架。'], ['custom', '自定义项目', '只创建项目空间,不配置默认发布方式。'], ] as const).map(([value, title, description]) => (
) :

当前没有可显示的能力投影。

} {detail?.permissions.length ? ( diff --git a/src/pages/ProjectConfiguration/index.tsx b/src/pages/ProjectConfiguration/index.tsx index 6dcbdcc..1e86629 100644 --- a/src/pages/ProjectConfiguration/index.tsx +++ b/src/pages/ProjectConfiguration/index.tsx @@ -460,7 +460,7 @@ export function ProjectConfiguration() {
- {draft.projectType === 'mini_game' || draft.projectType === 'mini_program' ? ( + {draft.projectType === 'interactive_ai_app' ? ( { await page.getByTestId('ai-module-option-programming').click(); await expect(page.getByTestId('main-layout')).toBeVisible(); await page.getByTestId('sidebar-create-project').click(); - await expect(page.getByRole('radio', { name: '小游戏' })).toBeChecked(); - await expect(page.getByRole('radio', { name: '小程序' })).not.toBeChecked(); + await expect(page.getByRole('radio', { name: '交互式 AI 应用' })).toBeChecked(); await expect(page.getByRole('radio', { name: '自定义项目' })).not.toBeChecked(); - await page.getByRole('radio', { name: '小程序' }).click(); - await expect(page.getByRole('radio', { name: '小程序' })).toBeChecked(); - await page.getByRole('radio', { name: '小游戏' }).click(); + await expect(page.getByRole('radio', { name: '小游戏' })).toHaveCount(0); + await expect(page.getByRole('radio', { name: '小程序' })).toHaveCount(0); await expect(page.getByRole('radio', { name: '直接使用所选文件夹(默认)' })).toBeChecked(); await page.getByRole('button', { name: '选择路径' }).click(); await expect(page.getByLabel('项目路径')).toHaveValue(parentPath); @@ -76,7 +74,7 @@ test.describe('Project configuration skills', () => { const now = new Date().toISOString(); await mkdir(path.join(parentPath, '.makelore'), { recursive: true }); await writeFile(path.join(parentPath, '.makelore', 'project.json'), JSON.stringify({ - ...createCodingProjectConfigV2(now, 'mini_game'), + ...createCodingProjectConfigV2(now, 'interactive_ai_app'), initialized: true, agents: [{ id: 'e2e-agent', diff --git a/tests/fixtures/fake-pi-rpc-child.mjs b/tests/fixtures/fake-pi-rpc-child.mjs index 7dc7583..688a858 100644 --- a/tests/fixtures/fake-pi-rpc-child.mjs +++ b/tests/fixtures/fake-pi-rpc-child.mjs @@ -80,6 +80,12 @@ function handle(command) { writeRecord(response(command, { counts })); return; } + if (command.type === 'environment') { + writeRecord(response(command, { + makeloreNodeExecutable: process.env.MAKELORE_NODE_EXECUTABLE ?? null, + })); + return; + } if (command.type === 'stderr_secret') { const secret = process.env.FAKE_PI_SECRET ?? 'missing-secret'; process.stderr.write(`${'diagnostic '.repeat(50)}\n`); diff --git a/tests/unit/coding-project-identity.test.ts b/tests/unit/coding-project-identity.test.ts index f20bd7b..03d7711 100644 --- a/tests/unit/coding-project-identity.test.ts +++ b/tests/unit/coding-project-identity.test.ts @@ -1,6 +1,6 @@ // @vitest-environment node -import { copyFile, mkdir, mkdtemp, realpath, rename, rm, stat } from 'node:fs/promises'; +import { copyFile, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; @@ -45,6 +45,45 @@ function makeStore(localId = 'local-project-id') { } describe('coding project durable identity', () => { + it.each(['interactive_ai_app', 'custom'] as const)( + 'creates only project-owned metadata for %s projects', + async (projectType) => { + const projectPath = await makeRoot(); + const projects = new CodingProjectService(makeStore(), { + createProjectId: () => PROJECT_ID, + }); + + const created = await projects.createProject({ + projectPath, + projectType, + identity: { kind: 'create' }, + }); + + expect(created.config.projectType).toBe(projectType); + expect((await readdir(projectPath)).sort()).toEqual(['.makelore', 'knowledge']); + }, + ); + + it.each(['mini_game', 'mini_program'] as const)( + 'reads legacy %s metadata as the canonical interactive type without rewriting it', + async (legacyProjectType) => { + const projectPath = await makeRoot(); + await mkdir(path.join(projectPath, '.makelore'), { recursive: true }); + const configPath = path.join(projectPath, '.makelore', 'project.json'); + const rawConfig = `${JSON.stringify({ + ...createCodingProjectConfigV2(CREATED, 'interactive_ai_app'), + projectType: legacyProjectType, + }, null, 2)}\n`; + await writeFile(configPath, rawConfig, 'utf8'); + + await expect(readCodingProjectConfigV2(projectPath)).resolves.toMatchObject({ + status: 'valid', + config: { projectType: 'interactive_ai_app' }, + }); + await expect(readFile(configPath, 'utf8')).resolves.toBe(rawConfig); + }, + ); + it('preserves canonical identity and rejects noncanonical values without backfill', () => { const initialized = createCodingProjectConfigV2(CREATED, 'custom', PROJECT_ID); expect(initialized.projectId).toBe(PROJECT_ID); diff --git a/tests/unit/device-package-manager.test.ts b/tests/unit/device-package-manager.test.ts index de370e0..af7ded1 100644 --- a/tests/unit/device-package-manager.test.ts +++ b/tests/unit/device-package-manager.test.ts @@ -85,6 +85,42 @@ describe('DevicePackageManager', () => { expect(changed).toHaveBeenCalledTimes(1); }); + it('discloses executable Skill scripts and preserves their bundled resources', async () => { + const root = await temporaryRoot('script-skill'); + const source = await looseSkill(root, 'script-skill'); + await mkdir(path.join(source, 'scripts'), { recursive: true }); + await mkdir(path.join(source, 'assets'), { recursive: true }); + await mkdir(path.join(source, 'references'), { recursive: true }); + await writeFile(path.join(source, 'scripts/scaffold.mjs'), 'console.log("scaffold");\n'); + await writeFile(path.join(source, 'assets/template.txt'), 'template\n'); + await writeFile(path.join(source, 'references/submission.md'), 'requirements\n'); + const manager = new DevicePackageManager({ + rootDir: path.join(root, 'store'), + now: () => NOW, + createId: () => 'plan-script-skill', + }); + + const preview = await manager.prepare(source, 'turn-prepare'); + + expect(preview).toMatchObject({ + kind: 'skill-only', + includesExecutableCode: true, + extensionEntries: [], + }); + expect(preview.warnings.join(' ')).toContain('Skill 脚本'); + expect(preview.warnings.join(' ')).toContain('文件、网络和进程权限'); + + const installed = await manager.commit(preview.planId, true, 'turn-confirm'); + expect(installed.packages[0]).toMatchObject({ confirmedExecutableCode: true }); + const [resource] = (await manager.resolveEnabledResources()).skillEntries; + expect(await readFile(path.join(resource!.packageRoot, 'scripts/scaffold.mjs'), 'utf8')) + .toContain('scaffold'); + expect(await readFile(path.join(resource!.packageRoot, 'assets/template.txt'), 'utf8')) + .toBe('template\n'); + expect(await readFile(path.join(resource!.packageRoot, 'references/submission.md'), 'utf8')) + .toBe('requirements\n'); + }); + it('recognizes Pi extension and mixed package manifests and shows the desktop-permission warning', async () => { const root = await temporaryRoot('pi-manifests'); const source = path.join(root, 'mixed-package'); diff --git a/tests/unit/pi-agent-server-process-real.test.ts b/tests/unit/pi-agent-server-process-real.test.ts index 79f18f5..b0bb251 100644 --- a/tests/unit/pi-agent-server-process-real.test.ts +++ b/tests/unit/pi-agent-server-process-real.test.ts @@ -5,6 +5,7 @@ import { createServer, type ServerResponse } from 'node:http'; import { createRequire } from 'node:module'; import { tmpdir } from 'node:os'; import path from 'node:path'; +import { pathToFileURL } from 'node:url'; import { afterEach, describe, expect, it } from 'vitest'; import { PiAgentServerProcess } from '../../electron/coding-runtime/pi/agent-server-process'; import { PiManagedExtensionHost } from '../../electron/coding-runtime/pi/extension-host'; @@ -196,6 +197,33 @@ afterEach(async () => { }); describe('Pi Agent Server real process', () => { + it('exposes its executable to Skill scripts', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-agent-server-environment-')); + roots.push(root); + const configDir = path.join(root, 'config'); + const serverPath = path.join(root, 'pi-agent-server-environment.mjs'); + await mkdir(configDir, { recursive: true }); + await writeFile(serverPath, [ + "if (process.env.MAKELORE_NODE_EXECUTABLE !== process.execPath) {", + " throw new Error('MAKELORE_NODE_EXECUTABLE does not match the Agent Server executable');", + '}', + `await import(${JSON.stringify(pathToFileURL(path.resolve('resources/pi-agent-server.mjs')).href)});`, + '', + ].join('\n')); + const server = new PiAgentServerProcess({ + executablePath: process.execPath, + serverPath, + runtimeRoot: path.resolve('node_modules/@earendil-works/pi-coding-agent'), + configDir, + }); + + try { + await expect(server.start()).resolves.toBeUndefined(); + } finally { + await server.stop().catch(() => undefined); + } + }, 10_000); + it('resolves runtime packages when the server resource is outside the project module tree', async () => { const root = await mkdtemp(path.join(tmpdir(), 'makelore-pi-agent-server-packaged-layout-')); roots.push(root); diff --git a/tests/unit/pi-rpc-foundation.test.ts b/tests/unit/pi-rpc-foundation.test.ts index 4f555e7..974bdc2 100644 --- a/tests/unit/pi-rpc-foundation.test.ts +++ b/tests/unit/pi-rpc-foundation.test.ts @@ -469,7 +469,11 @@ describe('Pi worker process', () => { it('inherits only the worker-safe environment allowlist', () => { const env = buildPiWorkerEnvironment( 'D:\\managed-pi', - { MAKELore_PI_SELECTED_API_KEY: 'selected-secret' }, + 'D:\\Makelore App\\Makelore.exe', + { + MAKELore_PI_SELECTED_API_KEY: 'selected-secret', + MAKELORE_NODE_EXECUTABLE: 'D:\\spoofed-node.exe', + }, { PATH: 'D:\\tools', OPENAI_API_KEY: 'unrelated-openai-secret', @@ -484,12 +488,24 @@ describe('Pi worker process', () => { PI_OFFLINE: '1', PI_TELEMETRY: '0', ELECTRON_RUN_AS_NODE: '1', + MAKELORE_NODE_EXECUTABLE: 'D:\\Makelore App\\Makelore.exe', }); expect(env).not.toHaveProperty('OPENAI_API_KEY'); expect(env).not.toHaveProperty('ANTHROPIC_API_KEY'); expect(env).not.toHaveProperty('CUSTOM_APPLICATION_SECRET'); }); + it('exposes the direct worker executable to Skill scripts', async () => { + const worker = await makeWorker({ + env: { MAKELORE_NODE_EXECUTABLE: 'spoofed-node' }, + }); + + await expect(worker.request<{ makeloreNodeExecutable: string }>({ type: 'environment' })) + .resolves.toMatchObject({ + data: { makeloreNodeExecutable: process.execPath }, + }); + }); + it('refuses to put a selected worker credential in argv', async () => { const secret = 'argv-secret-value'; await expect(makeWorker({ diff --git a/tests/unit/plugins-page.test.tsx b/tests/unit/plugins-page.test.tsx index 6707dfa..dced718 100644 --- a/tests/unit/plugins-page.test.tsx +++ b/tests/unit/plugins-page.test.tsx @@ -471,6 +471,33 @@ describe('PluginsView', () => { }); }); + it('describes executable Skill scripts without calling them Pi extensions', () => { + const scriptSkill: PluginWorkspaceItem = { + ...localItem, + key: 'local:makelore-project-scaffold', + pluginId: 'makelore-project-scaffold', + packageId: 'makelore-project-scaffold', + title: 'MakeLore 项目初始化', + local: { + ...localItem.local!, + packageId: 'makelore-project-scaffold', + displayName: 'MakeLore 项目初始化', + kind: 'skill-only', + skillEntries: [{ + id: 'makelore-project-scaffold', + entryPath: 'skills/makelore-project-scaffold/SKILL.md', + }], + extensionEntries: [], + }, + }; + + render(); + + const dialog = screen.getByRole('dialog', { name: 'MakeLore 项目初始化' }); + expect(dialog).toHaveTextContent('包含可执行 Skill 脚本'); + expect(dialog).not.toHaveTextContent('包含可执行 Pi extension'); + }); + it('shows cached, suspended, retired, and retained-device reason copy with an explicit sign-in action', () => { const unavailableItem: PluginWorkspaceItem = { ...officialItem, diff --git a/tests/unit/project-packager.test.ts b/tests/unit/project-packager.test.ts index 7fe2db8..838a88f 100644 --- a/tests/unit/project-packager.test.ts +++ b/tests/unit/project-packager.test.ts @@ -42,7 +42,7 @@ const AdmZip = require('adm-zip') as typeof import('adm-zip'); const tempDirectories: string[] = []; -async function createProject(projectType: ProjectType = 'mini_game'): Promise { +async function createProject(projectType: ProjectType = 'interactive_ai_app'): Promise { const projectPath = await mkdtemp(join(tmpdir(), 'makelore-project-packager-')); tempDirectories.push(projectPath); await mkdir(join(projectPath, 'src'), { recursive: true }); @@ -180,7 +180,7 @@ describe('project packager', () => { ]); expect(archive.readAsText('niancode.yml')).toBe( 'schema_version: 1\n' - + 'project_type: mini_game\n' + + 'project_type: interactive_ai_app\n' + 'kind: web\n' + 'runtime: static\n' + 'build:\n' @@ -188,22 +188,34 @@ describe('project packager', () => { + ' package_manager: npm\n' + ' entry: index.html\n', ); - expect(first.manifest.project_type).toBe('mini_game'); + expect(first.manifest.project_type).toBe('interactive_ai_app'); expect(entries.every((entry) => entry.header.time.getFullYear() === 1980)).toBe(true); await expect(readFile(join(projectPath, 'niancode.yml'), 'utf8')).resolves.toBe('runtime: compose\n'); }); - it('writes the selected publishable project type into the generated manifest', async () => { - const projectPath = await createProject('mini_program'); - const outputDirectory = await mkdtemp(join(tmpdir(), 'makelore-project-archives-')); - tempDirectories.push(outputDirectory); - const archivePath = join(outputDirectory, 'project.zip'); + it.each(['mini_game', 'mini_program'] as const)( + 'normalizes a legacy %s project to the canonical manifest type', + async (legacyProjectType) => { + const projectPath = await createProject(); + await writeFile( + join(projectPath, '.makelore', 'project.json'), + JSON.stringify({ + ...createCodingProjectConfigV2('2026-08-09T00:00:00.000Z', 'interactive_ai_app'), + projectType: legacyProjectType, + }), + 'utf8', + ); + const outputDirectory = await mkdtemp(join(tmpdir(), 'makelore-project-archives-')); + tempDirectories.push(outputDirectory); + const archivePath = join(outputDirectory, 'project.zip'); - const summary = await createStaticProjectPackage({ projectPath, archivePath }); + const summary = await createStaticProjectPackage({ projectPath, archivePath }); - expect(summary.manifest.project_type).toBe('mini_program'); - expect(new AdmZip(archivePath).readAsText('niancode.yml')).toContain('project_type: mini_program\n'); - }); + expect(summary.manifest.project_type).toBe('interactive_ai_app'); + expect(new AdmZip(archivePath).readAsText('niancode.yml')) + .toContain('project_type: interactive_ai_app\n'); + }, + ); it('rejects custom projects before validating Vite source files', async () => { const projectPath = await mkdtemp(join(tmpdir(), 'makelore-custom-packager-')); @@ -222,7 +234,7 @@ describe('project packager', () => { archivePath: join(outputDirectory, 'project.zip'), })).rejects.toMatchObject({ code: 'PROJECT_TYPE_UNPUBLISHABLE', - message: '自定义项目暂未配置发布方式,请新建小游戏或小程序项目', + message: '自定义项目暂未配置发布方式,请新建交互式 AI 应用项目', }); }); diff --git a/tests/unit/project-publish-action.test.tsx b/tests/unit/project-publish-action.test.tsx index caef5e9..cf63175 100644 --- a/tests/unit/project-publish-action.test.tsx +++ b/tests/unit/project-publish-action.test.tsx @@ -110,7 +110,7 @@ describe('ProjectPublishAction', () => { it('creates a new project with a cover DTO and locks after Builder succeeds', async () => { fetchCurrentWorksProjectStatusMock.mockResolvedValue(projectStatus('succeeded')); - render(); + render(); await submitProjectMetadata(); expect(screen.getByRole('button', { name: '正在等待云端检查…' })).toBeDisabled(); @@ -130,7 +130,7 @@ describe('ProjectPublishAction', () => { cover_url: null, creator_name: '小明', creator_age: 12, - category: 'mini_game', + category: 'interactive_ai_app', age_band: null, difficulty: null, }, @@ -153,7 +153,7 @@ describe('ProjectPublishAction', () => { }); it('blocks first submission until a cover is selected', async () => { - render(); + render(); await submitProjectMetadata({ includeCover: false }); @@ -164,7 +164,7 @@ describe('ProjectPublishAction', () => { it('rejects unsupported and oversized cover files before confirmation', async () => { fetchCurrentWorksProjectStatusMock.mockRejectedValue(Object.assign(new Error('missing'), { statusCode: 404 })); - render(); + render(); await act(async () => { fireEvent.click(screen.getByRole('button', { name: '一键提交审核' })); await Promise.resolve(); @@ -185,7 +185,7 @@ describe('ProjectPublishAction', () => { fetchCurrentWorksProjectStatusMock.mockResolvedValue( projectStatus('failed', 'BROWSER_SMOKE_FAILED'), ); - render(); + render(); await submitProjectMetadata(); await flushSubmission(); @@ -204,7 +204,7 @@ describe('ProjectPublishAction', () => { fetchCurrentWorksProjectStatusMock.mockRejectedValue( new Error('socket closed at /srv/private/status.py'), ); - render(); + render(); await submitProjectMetadata(); await flushSubmission(); @@ -227,7 +227,7 @@ describe('ProjectPublishAction', () => { }, }); fetchCurrentWorksProjectStatusMock.mockResolvedValue(projectStatus('succeeded')); - render(); + render(); await submitProjectMetadata(); await flushSubmission(); @@ -248,7 +248,7 @@ describe('ProjectPublishAction', () => { const status = projectStatus('queued'); status.latest_version.id = 'another-version'; fetchCurrentWorksProjectStatusMock.mockResolvedValue(status); - render(); + render(); await submitProjectMetadata(); await flushSubmission(); @@ -268,7 +268,7 @@ describe('ProjectPublishAction', () => { new Error('Traceback: failed at /srv/private/package.ts token=secret'), { code: 'INTERNAL_DATABASE_FAILURE', statusCode: 503 }, )); - render(); + render(); await submitProjectMetadata(); await flushSubmission(); @@ -285,7 +285,7 @@ describe('ProjectPublishAction', () => { new Error('private current metadata token=secret'), { code: 'PROJECT_METADATA_CONFLICT', statusCode: 409 }, )); - render(); + render(); await submitProjectMetadata(); await flushSubmission(); @@ -302,7 +302,7 @@ describe('ProjectPublishAction', () => { new Error('D:/repo/space-cleaner/package-lock.json does not exist'), { code: 'PROJECT_FILE_MISSING' }, )); - render(); + render(); await submitProjectMetadata(); await flushSubmission(); @@ -317,7 +317,7 @@ describe('ProjectPublishAction', () => { it('keeps generated metadata within the server limits', async () => { const longName = `作品-${'x'.repeat(250)}`; - render(); + render(); await submitProjectMetadata(); await flushSubmission(); @@ -328,7 +328,7 @@ describe('ProjectPublishAction', () => { expect(input.project.summary).toBe('这是一个太空清洁小游戏。'); expect(input.project.creator_name).toBe('小明'); expect(input.project.creator_age).toBe(12); - expect(input.project.category).toBe('mini_program'); + expect(input.project.category).toBe('interactive_ai_app'); }); it('submits an existing draft as version-only without editable metadata or replacement cover', async () => { @@ -339,10 +339,10 @@ describe('ProjectPublishAction', () => { cover_url: 'coverimage/draft.jpg', creator_name: '草稿作者', creator_age: 10, - category: 'mini_game', + category: 'interactive_ai_app', }); fetchCurrentWorksProjectStatusMock.mockResolvedValue(draftStatus); - render(); + render(); await act(async () => { fireEvent.click(screen.getByRole('button', { name: '一键提交审核' })); @@ -384,12 +384,12 @@ describe('ProjectPublishAction', () => { cover_url: 'coverimage/existing.jpg', creator_name: '原作者', creator_age: 11, - category: 'mini_game', + category: 'interactive_ai_app', age_band: '6-12岁', difficulty: '入门', }); fetchCurrentWorksProjectStatusMock.mockResolvedValue(publishedStatus); - render(); + render(); await act(async () => { fireEvent.click(screen.getByRole('button', { name: '一键提交审核' })); diff --git a/tests/unit/project-release-builder.test.ts b/tests/unit/project-release-builder.test.ts index fb915e6..1ec2499 100644 --- a/tests/unit/project-release-builder.test.ts +++ b/tests/unit/project-release-builder.test.ts @@ -21,7 +21,7 @@ describe('project release builder', () => { it('builds from the exact source archive and returns the protocol-v1 canonical contract', async () => { root = await mkdtemp(join(tmpdir(), 'release-builder-test-')); await mkdir(join(root, '.makelore'), { recursive: true }); - await writeFile(join(root, '.makelore', 'project.json'), JSON.stringify(createCodingProjectConfigV2(new Date().toISOString(), 'mini_game'))); + await writeFile(join(root, '.makelore', 'project.json'), JSON.stringify(createCodingProjectConfigV2(new Date().toISOString(), 'interactive_ai_app'))); await writeFile(join(root, 'package.json'), JSON.stringify({ name: 'demo', packageManager: 'npm@11.6.2', devDependencies: { vite: '7.3.1' } })); await writeFile(join(root, 'package-lock.json'), JSON.stringify({ name: 'demo', lockfileVersion: 3, requires: true, packages: {} })); await writeFile(join(root, 'index.html'), '
source
'); diff --git a/tests/unit/project-scaffold-plugin.test.ts b/tests/unit/project-scaffold-plugin.test.ts new file mode 100644 index 0000000..89b20f7 --- /dev/null +++ b/tests/unit/project-scaffold-plugin.test.ts @@ -0,0 +1,193 @@ +// @vitest-environment node + +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 { 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 { createCodingProjectMetadata } from '@electron/coding-projects/project-config'; +import { createStaticProjectPackage } from '@electron/services/project-packager'; +import type { ProjectType } from '../../shared/project-config'; + +const pluginRoot = resolve('plugins/makelore-project-scaffold'); +const scaffoldScript = join( + pluginRoot, + 'skills', + 'makelore-project-scaffold', + 'scripts', + 'scaffold.mjs', +); +const tempDirectories: string[] = []; + +function extractStringList(source: string, declaration: string): string[] { + const declarationStart = source.indexOf(`const ${declaration} =`); + expect(declarationStart).toBeGreaterThanOrEqual(0); + const listStart = source.indexOf('[', declarationStart); + const listEnd = source.indexOf(']', listStart); + expect(listStart).toBeGreaterThanOrEqual(0); + expect(listEnd).toBeGreaterThan(listStart); + return [...source.slice(listStart + 1, listEnd).matchAll(/'([^']+)'/g)] + .map((match) => match[1]); +} + +afterEach(async () => { + await Promise.all( + tempDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })), + ); +}); + +describe('MakeLore project scaffold plugin', () => { + it('is discovered as a skill-only package with executable Skill scripts', async () => { + const packageJson = JSON.parse(await readFile(join(pluginRoot, 'package.json'), 'utf8')); + const pluginManifest = JSON.parse(await readFile( + join(pluginRoot, '.codex-plugin', 'plugin.json'), + 'utf8', + )); + const inspected = await inspectDevicePackage(pluginRoot); + + expect({ name: packageJson.name, version: packageJson.version }).toEqual({ + name: pluginManifest.name, + version: pluginManifest.version, + }); + expect(pluginManifest.skills).toBe('./skills/'); + expect(devicePackageKind(inspected)).toBe('skill-only'); + expect(inspected).toMatchObject({ + packageId: 'makelore-project-scaffold', + resolvedVersion: '0.1.0', + extensionEntries: [], + hasSkillScripts: true, + ignoredLifecycleScripts: [], + skillEntries: [{ + id: 'makelore-project-scaffold', + entryPath: 'skills/makelore-project-scaffold/SKILL.md', + }], + }); + }); + + 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)); + expect(await readFile(join(skillRoot, 'scripts', 'scaffold.mjs'), 'utf8')) + .toContain('schemaVersion: 1'); + expect(await readFile(join(skillRoot, 'assets', 'templates', 'common', 'package-lock.json'), 'utf8')) + .toContain('"lockfileVersion": 3'); + expect(await readFile(join(skillRoot, 'references', 'submission-requirements.md'), 'utf8')) + .toContain('提交审批要求'); + }); + + it('carries the complete categorized release-readiness contract', async () => { + const skill = await readFile( + join(pluginRoot, 'skills', 'makelore-project-scaffold', 'SKILL.md'), + 'utf8', + ); + const requirements = await readFile( + join( + pluginRoot, + 'skills', + 'makelore-project-scaffold', + 'references', + 'submission-requirements.md', + ), + 'utf8', + ); + const packagerSource = await readFile('electron/services/project-packager.ts', 'utf8'); + + expect(skill).toContain('Release-readiness workflow'); + expect(skill).toContain('不能进入一键提交'); + expect(skill).toContain('可进入一键提交,仍需运行期验证'); + expect(skill).toContain('Do not infer an external-network blocker'); + + for (const heading of ['必须具备', '直接阻断', '打包排除', '运行时或平台确认']) { + expect(requirements).toContain(`**${heading}**`); + } + for (const value of [ + '.makelore/project.json', + 'schema v2', + 'interactive_ai_app', + 'mini_game', + 'mini_program', + 'custom', + 'npm ci --ignore-scripts', + 'lockfileVersion', + '2,000', + '200 MiB', + '50 MiB', + 'release.json', + 'CON', + '1280×720', + '390×844', + '30 秒', + 'HTTP/HTTPS', + '.env', + 'serviceaccount', + 'works-', + 'PNG、JPEG 或 WebP', + '1–150', + '存在但不阻断', + ]) { + expect(requirements).toContain(value); + } + + const excludedValues = [ + ...extractStringList(packagerSource, 'EXCLUDED_DIRECTORY_NAMES'), + ...extractStringList(packagerSource, 'EXCLUDED_FILE_NAMES'), + ...extractStringList(packagerSource, 'EXCLUDED_FILE_SUFFIXES'), + ]; + expect(excludedValues.length).toBeGreaterThan(50); + for (const value of excludedValues) { + expect(requirements).toContain(value); + } + }); + + it('creates an interactive AI application accepted by the existing source packager', async () => { + const projectType: ProjectType = 'interactive_ai_app'; + const projectPath = await mkdtemp(join(tmpdir(), `makelore-${projectType}-`)); + const outputPath = await mkdtemp(join(tmpdir(), 'makelore-scaffold-package-')); + tempDirectories.push(projectPath, outputPath); + await createCodingProjectMetadata(projectPath, { + projectType, + now: '2026-09-04T00:00:00.000Z', + }); + + const scaffold = spawnSync( + process.execPath, + [scaffoldScript, '--project-root', projectPath], + { encoding: 'utf8' }, + ); + expect(scaffold.status, scaffold.stderr).toBe(0); + + const packaged = await createStaticProjectPackage({ + projectPath, + archivePath: join(outputPath, 'project.zip'), + }); + expect(packaged.manifest.project_type).toBe(projectType); + expect(packaged.archiveName).toBe('project.zip'); + expect(packaged.fileCount).toBeGreaterThan(1); + expect(packaged.archiveBytes).toBeGreaterThan(0); + expect(packaged.excludedPaths).toEqual(expect.arrayContaining(['.makelore/', 'knowledge/'])); + }); +}); diff --git a/tests/unit/works-project-publish.test.ts b/tests/unit/works-project-publish.test.ts index a23cee9..a9f5a7a 100644 --- a/tests/unit/works-project-publish.test.ts +++ b/tests/unit/works-project-publish.test.ts @@ -9,7 +9,7 @@ describe('works project publish guidance', () => { ['PUBLISH_PREFLIGHT_RUNTIME_ERROR', '作品打开时发生错误', '项目预览'], ['PUBLISH_PREFLIGHT_BLANK', '作品打开后没有内容', '移动端布局'], ['PROJECT_FILE_MISSING', '项目文件不完整', '修复当前项目模板'], - ['PROJECT_TYPE_UNPUBLISHABLE', '这个项目没有配置发布方式', '新建小游戏或小程序项目'], + ['PROJECT_TYPE_UNPUBLISHABLE', '这个项目没有配置发布方式', '新建交互式 AI 应用项目'], ['DEPENDENCY_PREFETCH_FAILED', '旧版提交无法继续处理', '升级 Makelore'], ['OUTPUT_MISSING', '本次构建结果未通过平台校验', '本次构建结果'], ['RELEASE_STORE_FAILED', '平台校验任务暂未完成', '构建异常'], diff --git a/tests/unit/works-routes.test.ts b/tests/unit/works-routes.test.ts index 0be93b0..42622b9 100644 --- a/tests/unit/works-routes.test.ts +++ b/tests/unit/works-routes.test.ts @@ -29,7 +29,7 @@ function preparedRelease(projectPath: string) { sourceArchive: { path: join(projectPath, 'private-source.zip'), name: 'project.zip', bytes, summary: { archivePath: join(projectPath, 'private-source.zip'), archiveName: 'project.zip', sha256: 'a'.repeat(64), fileCount: 5, sourceBytes: 10, archiveBytes: 3, excludedCount: 0, excludedPaths: [], - manifest: { schema_version: 1, project_type: 'mini_game', kind: 'web', runtime: 'static', build: { preset: 'vite', package_manager: 'npm', entry: 'index.html' } }, + manifest: { schema_version: 1, project_type: 'interactive_ai_app', kind: 'web', runtime: 'static', build: { preset: 'vite', package_manager: 'npm', entry: 'index.html' } }, } }, builtArchive: { path: join(projectPath, 'private-built.zip'), name: 'built-project.zip', bytes: Buffer.from('built') }, distRoot: join(projectPath, 'private-dist'), @@ -92,7 +92,7 @@ async function writePublishableProject(projectPath: string): Promise { await mkdir(join(projectPath, '.makelore'), { recursive: true }); await writeFile( join(projectPath, '.makelore', 'project.json'), - JSON.stringify(createCodingProjectConfigV2('2026-08-09T00:00:00.000Z', 'mini_game')), + JSON.stringify(createCodingProjectConfigV2('2026-08-09T00:00:00.000Z', 'interactive_ai_app')), 'utf8', ); await writeFile(join(projectPath, 'package.json'), JSON.stringify({ From 22078060386e38dd5f12af4717dc6c6e3378c2df Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 21:09:55 +0800 Subject: [PATCH 2/7] docs: reconcile interactive AI app scaffold boundary --- .project-docs/00-brief/success-criteria.md | 5 +- .../adr-008-interactive-ai-app-scaffold.md | 39 ++++++++++ .project-docs/10-decisions/decision-index.md | 1 + .project-docs/20-architecture/data-flow.md | 11 +-- .project-docs/20-architecture/module-map.md | 8 +- .../20-architecture/system-overview.md | 13 ++-- .project-docs/30-worklog/current-state.md | 24 +++++- .project-docs/30-worklog/task-history.md | 1 + ...904-reconcile-project-scaffold-f7b4d2a9.md | 76 +++++++++++++++++++ .project-docs/40-domain/business-rules.md | 15 ++-- .project-docs/40-domain/glossary.md | 6 +- .project-docs/80-commitments/commitments.md | 4 +- 12 files changed, 174 insertions(+), 29 deletions(-) create mode 100644 .project-docs/10-decisions/adr-008-interactive-ai-app-scaffold.md create mode 100644 .project-docs/30-worklog/tasks/20260904-reconcile-project-scaffold-f7b4d2a9.md diff --git a/.project-docs/00-brief/success-criteria.md b/.project-docs/00-brief/success-criteria.md index 711821a..0d84260 100644 --- a/.project-docs/00-brief/success-criteria.md +++ b/.project-docs/00-brief/success-criteria.md @@ -6,7 +6,8 @@ ## Task Completion Standard -- 可发布小游戏/小程序的一键提交必须由 Electron Main 对安全源码快照执行固定 npm 11.6.2 的 `npm ci --ignore-scripts`,并调用项目 `package-lock.json` 锁定的 Vite 生成静态产物。 +- 可发布交互式 AI 应用的一键提交必须由 Electron Main 对安全源码快照执行固定 npm 11.6.2 的 `npm ci --ignore-scripts`,并调用项目 `package-lock.json` 锁定的 Vite 生成静态产物。 +- Project Scaffold Skill 可生成固定起步文件并只读说明发布要求、禁止项和证据缺口,但不得安装依赖、构建、上传、提审或把静态检查表述为平台批准。 - Electron 双视口预检必须检查与最终 `built_archive` 相同的内存文件字节;预检失败不得上传,预检成功不得被表述为可信审核凭据。 - 上传协议必须同时携带源码归档、构建归档和严格版本化 artifact contract;服务端独立重算摘要、校验合同并固化不可变 Release。 - Renderer 不得获得发布凭据、归档、临时目录、构建 origin 或任意本地路径;旧客户端和旧 sandbox/browser 任务必须提示升级后重新构建提交。 @@ -37,4 +38,4 @@ ## Last Reviewed -2026-08-31 +2026-09-04 diff --git a/.project-docs/10-decisions/adr-008-interactive-ai-app-scaffold.md b/.project-docs/10-decisions/adr-008-interactive-ai-app-scaffold.md new file mode 100644 index 0000000..f80ae7c --- /dev/null +++ b/.project-docs/10-decisions/adr-008-interactive-ai-app-scaffold.md @@ -0,0 +1,39 @@ +# ADR-008: 交互式 AI 应用使用显式 Scaffold Skill + +- Status: Accepted / implemented +- Date: 2026-09-04 +- Applies to: Project creation, `ProjectType`, Device Packages, scaffold Skill, release readiness + +## Context + +历史客户端把 `mini_game` 和 `mini_program` 作为两个可发布产品类型,并在创建项目时自动生成两套业务模板。这把项目身份、起步文件和发布规则绑定在同一次创建操作中,使模板演进和平台发布约束难以独立维护。 + +项目服务当前已经只负责 `.makelore/project.json` 与 `knowledge/`。用户确认将小游戏和小程序合并为一个“交互式 AI 应用”类型,并把生成文件、目录以及提交审批要求放进可独立分发的 Skill。 + +## Decision + +- 新建项目只提供规范产品类型 `interactive_ai_app`(显示为“交互式 AI 应用”)和 `custom`。`ProjectType` 创建后仍不可变。 +- `mini_game` 与 `mini_program` 只作为历史读取和脚手架兼容别名,在内存中归一为 `interactive_ai_app`。读取或运行脚手架不会改写原配置;后续普通配置变更可以保存规范值。缺少类型字段的旧项目仍归一为 `custom`。 +- 项目创建只生成 `.makelore/project.json` 和 `knowledge/`,不生成业务源码、依赖、锁文件或发布模板。 +- 独立 Device Package `makelore-project-scaffold` 提供显式 Scaffold Skill。其脚本按固定版本生成六个交互式 AI 应用起步文件;写入前预检全部目标,不覆盖已有路径,受控失败时只回滚本次创建的文件和目录。 +- Scaffold Skill 不安装依赖、不访问网络、不执行构建、不上传、不提交审核,也不提供 `--force`、类型覆盖或模板迁移状态。Main 继续独占固定 npm/Vite 构建、同字节预检、打包和上传;Works Square 继续独占服务端校验、不可变 Release 与运营审核。 +- 非空标准 Skill `scripts/` 目录属于可执行代码,即使 Device Package 仍为 `skill-only`,也必须在预览和独立后续确认中披露文件、网络、进程及桌面用户权限。应用通过不可覆盖的 `MAKELORE_NODE_EXECUTABLE` 向父 Pi worker/Agent Server 提供自身 Node;不得回退系统 Node。 +- Skill 的发布检查只是只读准备度指导:区分确定阻断、源码包排除影响、运行时/平台待确认项和静态已满足项,并引用项目相对证据。它不能宣称构建、上传、审核或生产发布成功。 + +## Consequences + +- 模板可随独立插件版本演进,不再扩大项目创建服务的职责。 +- 旧项目无需批量迁移即可继续打开、生成脚手架和发布;新写入只使用规范类型。 +- 发布规则在 Skill 中可被 Agent 解释和预检,但权威执行仍只有 Main 与 Works Square,避免形成第二套发布实现。 +- 对外分发仍需不可变 SemVer/Git 版本、真实 Device Package 安装链和已安装 Windows、签名 macOS、native Linux 运行证据;工作区测试不能替代这些发布门禁。 + +## Supersedes + +本决定取代 2026-08-09 的双可发布类型与创建时自动生成模板行为;固定 Vite 构建、artifact contract、运营审核与公共播放边界保持不变。 + +## Related Evidence + +- Source commit: `959b633` +- Integration merge: `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952` +- Source task: `20260904-project-scaffold-implementation-7e4c2a91` +- Proposal: `10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md` diff --git a/.project-docs/10-decisions/decision-index.md b/.project-docs/10-decisions/decision-index.md index f98edc2..8c5fe12 100644 --- a/.project-docs/10-decisions/decision-index.md +++ b/.project-docs/10-decisions/decision-index.md @@ -4,6 +4,7 @@ | ID | Decision | Status | Date | Applies To | Detail | |---|---|---|---|---|---| +| ADR-008 | 交互式 AI 应用使用单一规范类型,项目创建与显式 Scaffold Skill、发布权威分离 | Accepted / implemented | 2026-09-04 | Project creation、`ProjectType`、Device Packages、scaffold、release readiness | `adr-008-interactive-ai-app-scaffold.md` | | ADR-007 | AI Design 采用单一 Current Specification、Living Form 与不可变 Quote 的 V2 权威 | Accepted / implemented | 2026-08-30 | AI Design Renderer、Electron Main、Works Square V2 API | `adr-007-ai-design-living-form-v2.md` | | ADR-002 | Robot V1 采用 Main 门控的引导式热点配网并衔接现有六位 Binding | Accepted / implemented, default on | 2026-08-16 | Robot Renderer、Host API、Electron Main、现有固件热点入口 | `adr-002-robot-guided-hotspot-binding-v1.md` | | ADR-003 | Robot 配网页内扫描并连接 Windows/macOS 热点 | Accepted / implemented with physical release gates pending | 2026-08-16 | Robot Renderer、Host API、Electron Main、Windows WLAN、macOS CoreWLAN/CoreLocation | `adr-003-robot-in-app-hotspot-connection.md` | diff --git a/.project-docs/20-architecture/data-flow.md b/.project-docs/20-architecture/data-flow.md index cd6a2d4..e49ae7d 100644 --- a/.project-docs/20-architecture/data-flow.md +++ b/.project-docs/20-architecture/data-flow.md @@ -8,13 +8,14 @@ | Marketplace device installation | Account Library entry and explicit channel | Main resolve/Admission/download → descriptor/signature/archive/client compatibility verification → immutable Package Store index switch | Stable and explicit Beta remain distinct even when they resolve to the same immutable Release. Failed install/update retains the previous current release; explicit uninstall waits only for protecting worker/account references and completes after the final worker exits. | | Official bundled hosted Plugin acquisition | Account Library entry for Game Resource | server stable bundled Release/Admission + MakeLore resource package | Free acquisition changes Library only. No device download, update, Beta, signature, or device-uninstall action exists for this exact code-owned identity. Historical Hosted Web Search entries are ignored by current clients. | | Selected-model Web Search | Parent Pi turn with an explicitly supported selected model | `makelore_web_search` core tool → frozen model/provider/credential request with provider-native forced search → ordinary model response/usage | No Marketplace Release, Account Library, Admission, Hosted Web Search client, Plugin Charge, or `agent_browser` fallback participates. Unsupported selected models expose no tool; child workers receive none. | -| Conversation-driven Device Package install | Agent tool inspects npm/Git/absolute local Plugin/loose Skill source | Main preview → distinct later user confirmation → immutable device-package generation → new/idle parent worker resources | Renderer has no install picker. Lifecycle scripts never run. Confirmation explicitly states that Pi extensions execute with desktop-user authority. Active workers retain their frozen generation until the turn settles; child workers remain empty. | +| Conversation-driven Device Package install | Agent tool inspects npm/Git/absolute local Plugin/loose Skill source | Main preview → distinct later user confirmation → immutable device-package generation → new/idle parent worker resources | Renderer has no install picker. Lifecycle scripts never run. Pi extensions and non-empty Skill `scripts/` are disclosed as desktop-user executable code before confirmation. Active workers retain their frozen generation until the turn settles; child workers remain empty. | | Effective Plugin worker snapshot | Installed trusted package or acquired official bundled definition + project selection + Agent assignments + current server policy | effective resolver → Registry/resource loader/Extension Host/tool catalog → parent Pi worker | One frozen snapshot supplies Skills, tools, package roots, and runtime authorization. Disable, account/project switch, logout, Renderer crash, Main shutdown, or worker generation change invalidates future actions without mutating persisted unknown assignments; child workers receive no Plugin projection. | | Hosted Game Resource operation | Eligible parent `makelore.game-resource` tool call plus explicit confirmation | frozen Plugin adapter → capability Registry → Main `GameResourceClient` → fixed Works Square game-resource route → provider-neutral receipt/result | Server policy owns pricing, payer, Admission and receipt state. Stable logical operation identity survives response loss/Main restart; `submission_unknown` is not replayed as a fresh request. Result saving uses a bounded project-relative path and the existing project write lease. | | 桌面认证生命周期 | Renderer 登录、刷新与注销请求 | Host API → Main Works Session → Works Square `/api/auth/{login,mobile-login,refresh,logout}` → one-feel auth | Main 加密持有并先持久化轮换 token;客户端不携带 OAuth client secret;连续 7 天未使用才清除会话,终止性 `400`/`401` fail closed | | 用户模块入口策略 | 会话恢复 / 登录 / 刷新 | Electron Main → Works `/api/auth/me` → 四布尔安全投影 → Renderer auth store → 卡片/路由/provider gate | 缺失对象或字段默认 `true`;`design` 映射 `painting`;终止性 `401` 清理 Main/Renderer 会话;全局 `/settings` 不受 Code gate | -| 项目创建 | 新建项目对话框 | Host API → Main 项目初始化 | 创建时固定 `ProjectType`;小游戏/小程序原子生成受控模板,自定义只生成项目空间 | -| 一键提交 | `ProjectPublishAction` | Renderer capability → Host API → Main 本地 npm/Vite build → built snapshot preflight → source+built+contract 上传 | 只对小游戏/小程序开放;首次 create 通过单一 multipart 合同原子提交文字资料与必选 PNG/JPEG/WebP 封面,创建失败或冲突不上传版本;已有 draft/published 只提交版本并沿用云端资料/封面,不做无条件 metadata PATCH | +| 项目创建 | 新建项目对话框 | Host API → Main 项目初始化 | 固定 `interactive_ai_app` 或 `custom`,只生成 `.makelore/project.json` 与 `knowledge/`;历史双类型在读取边界归一,不因读取改写 | +| 显式项目脚手架 | 用户在交互式 AI 应用 Conversation 中要求初始化 | Pi `bash` + 项目 write lease → `MAKELORE_NODE_EXECUTABLE` → `makelore-project-scaffold` 脚本 | 固定六文件目标;全量预检、不覆盖、受控失败只回滚本次路径;不安装依赖、不联网、不构建、不上传、不提审 | +| 一键提交 | `ProjectPublishAction` | Renderer capability → Host API → Main 本地 npm/Vite build → built snapshot preflight → source+built+contract 上传 | 只对规范交互式 AI 应用(含读取时归一的历史别名)开放;首次 create 通过单一 multipart 合同原子提交文字资料与必选 PNG/JPEG/WebP 封面,创建失败或冲突不上传版本;已有 draft/published 只提交版本并沿用云端资料/封面,不做无条件 metadata PATCH | | 构建产物预检 | Main-owned built snapshot | 一次性 loopback origin → fresh Electron WebContents/CDP(桌面、移动) | 检查错误、白屏和外域;不调用 Playwright,检查与上传归档相同字节,但不产生可信 receipt | | 提交绑定 | 云端成功上传响应 | Main → submission binding v2 | 只持久化成功的 app/version/review/hash;落盘失败返回固定告警但不反转提交 | | 运营发布 | Works Square 审核与交付 | 公共 `play_url` | 客户端只消费服务端发布结果;真实合同校验 → OSS/CDN 生产链仍待整链验收 | @@ -42,7 +43,7 @@ ## State Ownership - Main 持有刷新凭据、发布 Token、固定 npm runtime、源码/构建归档、临时目录、幂等键和 submission binding v2;Renderer 不持有归档路径、构建 origin 或自动部署状态。 -- 项目内 `.makelore/project.json` 是 `ProjectType`、Agent 与 Coding 项目配置的唯一权威;Main 在配置写入和目录复用时保持 `ProjectType` 不可变,并在打包时重新读取校验。 +- 项目内 `.makelore/project.json` 是 `ProjectType`、Agent 与 Coding 项目配置的唯一权威;Main 在配置写入和目录复用时保持 `ProjectType` 不可变,并在打包时重新读取校验。历史 `mini_game` / `mini_program` 只在内存中归一为 `interactive_ai_app`,读取和脚手架运行不改写原文件。 - Renderer 仅持有短效公开会话状态和提交展示状态。 - Renderer 可持久化当前账号的四布尔模块入口策略,但不持有原始 Works profile 或 Token。新账号不继承上一账号缓存;网络/暂时上游失败可保留同会话已知策略,终止性 `401` 不得回退到默认开启。 - 本地构建临时目录、HTTP origin 和预检 WebContents/partition 只属于一次调用;预检读取与 `built_archive` 相同的内存字节,但结果不写为可信上传 receipt,也不覆盖生产 opaque-origin。 @@ -80,4 +81,4 @@ ## Last Updated -2026-09-03 +2026-09-04 diff --git a/.project-docs/20-architecture/module-map.md b/.project-docs/20-architecture/module-map.md index 5b9fc8c..4afc58d 100644 --- a/.project-docs/20-architecture/module-map.md +++ b/.project-docs/20-architecture/module-map.md @@ -8,10 +8,12 @@ | `electron/coding-plugins/account-plugin-cache.ts`, `marketplace-client.ts`, and `package-store.ts` | Main-owned Account Library cache, authenticated server client, and atomic immutable device installation store | Account acquisition and device installation are separate. Package bytes switch atomically only after descriptor, signature, archive, client-range, and provenance checks; failed updates preserve the prior current release. | | `electron/coding-plugins/effective-resolver.ts`, `registry.ts`, `project-service.ts`, and `electron/coding-runtime/pi/**` | Effective official Plugin projection, selected-model tools, Device Package resources, and frozen parent logical-thread runtime snapshot | Project enablement and Agent Skill assignment are preserved independently. Trusted Marketplace artifacts, exact acquired official bundled definitions, and immutable local Device Package generations enter through distinct authorities. Child workers remain empty; active threads retain frozen resources until settlement/disposal. | | `electron/coding-runtime/pi/model-tools/**` and `shared/model-tools.ts` | Closed selected-model tool registry and provider-specific Web Search request shaping | The frozen selected model capability controls whether `makelore_web_search` exists. The tool uses that model/provider/credential and normal model billing; no Hosted Plugin adapter, Admission, Plugin Charge, or browser fallback exists. | -| `electron/coding-packages/**`, `electron/api/routes/device-packages.ts`, `shared/device-packages.ts`, and `src/stores/device-packages.ts` | Main-owned conversation install preview/confirmation/commit, immutable local package generations, safe Renderer projection, and parent-worker refresh | Sources are npm, Git, absolute local Plugin directories, or loose `SKILL.md`. Lifecycle scripts are disabled; executable extensions run with desktop-user authority after explicit confirmation. Every generation projects all explicitly installed and currently enabled Skills/extensions; Device Packages never join Account Library, Marketplace Package Store, Release, Channel, or Admission state. | +| `electron/coding-packages/**`, `electron/api/routes/device-packages.ts`, `shared/device-packages.ts`, and `src/stores/device-packages.ts` | Main-owned conversation install preview/confirmation/commit, immutable local package generations, safe Renderer projection, and parent-worker refresh | Sources are npm, Git, absolute local Plugin directories, or loose `SKILL.md`. Lifecycle scripts are disabled; executable extensions and non-empty Skill `scripts/` run with desktop-user authority after disclosure and explicit confirmation. Every generation projects all explicitly installed and currently enabled Skills/extensions; Device Packages never join Account Library, Marketplace Package Store, Release, Channel, or Admission state. | | `electron/coding-plugins/adapters/game-resource.ts` and `electron/services/game-resource-client.ts` | Provider-neutral `makelore.game-resource` hosted tool adapter and Main-owned Works Square transport | Tools materialize only from an eligible frozen `platform_hosted` parent snapshot. Metered mutations require explicit confirmation and stable logical operation identity; Renderer/Pi never receive Provider URLs, credentials, balances, raw responses, or Provider job IDs. | | `electron/api/routes/plugin-marketplace.ts`, `src/stores/{plugin-marketplace,device-packages,coding-plugins}.ts`, and `src/pages/Plugins/` | Existing Main/store authorities plus the pure unified Renderer projection for official catalog, Account Library, official package-device state, local Device Packages, retained IDs, and current-project actions | `/plugins` is the sole canonical surface; legacy Plugin routes only replace-redirect into deterministic filters. `official:`, `local:`, and `retained:` identities stay separate, source failures are isolated, and no Account token, filesystem path, Admission, package bytes, signed URL, or visible install-source picker enters Renderer. | | `src/components/works/ProjectPublishAction.tsx` | 可发布项目的一键提交、云构建轮询与用户可理解状态 | 只通过 Renderer API 提交非敏感元数据;绑定告警不终止轮询 | +| `shared/project-config.ts` and `electron/coding-projects/project-config.ts` | 规范 `ProjectType` 归一与最小项目创建 | 新写入只使用 `interactive_ai_app` / `custom`;历史 `mini_game` / `mini_program` 只读归一,项目创建只生成 metadata 与 `knowledge/` | +| `plugins/makelore-project-scaffold/` | 独立 Scaffold Skill、固定六文件模板、确定性 Node 脚本、发布要求参考与回归测试 | 通过既有 Pi `bash` write lease 和 `MAKELORE_NODE_EXECUTABLE` 显式运行;不覆盖、不安装依赖、不联网,也不复制 build/upload/review 实现 | | `src/lib/works-square.ts` | Renderer 侧 Works Square Host API 契约与安全错误映射 | 不接触 Token、ZIP、本地绝对路径或自动部署状态 | | `electron/api/routes/works.ts` | Works Host API、Renderer capability 门禁、上游安全投影 | 发布凭据、打包、上传与本地绑定均在 Main 内完成 | | `electron/services/project-release-builder.ts` | Main-owned 安全快照、本地 npm/Vite 构建、source+built 双归档与 v1 contract | 固定 npm 11.6.2;项目 Vite 由 lockfile 决定;Vite config/plugins 以桌面用户权限执行 | @@ -59,7 +61,7 @@ - Plugin sidebar/legacy links → canonical `/plugins` projection → existing Renderer stores → bounded Main Marketplace and Device Package routes. Compatibility routes replace-redirect into deterministic filters and do not create a second lifecycle. - Hosted Plugin parent tool → frozen Registry adapter → Main `GameResourceClient` → fixed Works Square game-resource routes. Stable logical operation identity survives response loss and Main restart; ambiguous submission remains reviewable and is never converted into an automatic fresh mutation. Saving a result uses the existing bounded project path and project write lease. - AI 编程 Renderer product Snapshot/commands → typed `/api/coding/*` Host API → Main Coding composition → target `CodingConversationRuntime` → shared Agent Server 内的目标 Pi 逻辑线程;Pi Provider 请求再经 Main AI proxy 访问模型上游。Renderer 不持有 Pi wire、凭据或本地 runtime URL。 -- Project configuration 决定产品分流;Main release builder 生成 source/built/contract,服务端独立重算和校验决定发布安全,本地 `ProjectType` 不是授权结论。 +- Project configuration 只决定产品身份与分流;用户显式调用 Scaffold Skill 才生成固定起步文件;Main release builder 生成 source/built/contract,服务端独立重算和校验决定发布安全。本地 `ProjectType` 或 Skill 准备度结论都不是授权结论。 - Built artifact preflight 检查最终上传的同字节快照,但客户端可被绕过且不产生可信 receipt;服务端仍是合同、摘要和不可变 Release 安全权威。 - Robot Renderer → typed AI hardware API → Main Host route → Robot Hotspot Module → Windows/macOS Adapter。云端 Binding 仍由 Main 代理;热点选择/连接移入页面,但家庭 Wi-Fi 凭据输入仍只留在固件 Portal,系统 Wi-Fi 保留为兜底。 - Prompt Museum Renderer → typed Host API facade → Main fixed list/detail route → Works Square。Museum 只把用户明确选择的 Prompt 原文暂存到进程内 Store 并导航回当前 Canvas;不会直接触发 Agent 命令或生成任务。 @@ -91,4 +93,4 @@ ## Last Updated -2026-09-03 +2026-09-04 diff --git a/.project-docs/20-architecture/system-overview.md b/.project-docs/20-architecture/system-overview.md index 588e5d1..15ad4e8 100644 --- a/.project-docs/20-architecture/system-overview.md +++ b/.project-docs/20-architecture/system-overview.md @@ -10,7 +10,8 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 |---|---|---| | Renderer | 项目配置、一键提交状态 | 不接触账号 Token、ZIP、幂等键或本地绝对路径 | | Host API | 校验本地项目请求并投影安全响应 | 发布 mutation 还必须通过 Renderer capability;Host token/base 不能单独触发发布 | -| Project Configuration & Template | 保存不可变 `ProjectType`,原子生成新项目骨架 | 小游戏/小程序生成受控 Vite 模板;自定义保持最小项目空间 | +| Project Configuration | 保存不可变 `ProjectType`,原子创建最小项目空间 | 只生成 `.makelore/project.json` 与 `knowledge/`;规范类型为 `interactive_ai_app` / `custom`,历史双类型只在读取边界归一 | +| Project Scaffold Skill | 显式生成固定版本的交互式 AI 应用起步文件,并提供发布准备度指导 | 独立 Device Package;完整预检、不覆盖、受控回滚,不安装依赖、不联网、不构建、不上传、不提审 | | Project Release Builder | Main-owned 安全快照、本地 npm/Vite 构建、双归档与 artifact contract | 固定 npm 11.6.2;Vite 由项目 lockfile 锁定;产物与预检使用同一内存字节 | | Works Session & Remembered Password | Main-owned 登录、刷新、注销、七天真实活动滑动续期与可选密码回填 | 登录、刷新、注销统一经过 Works Square;轮换凭据由 Main 安全持有和持久化。记住密码使用独立的 packaged-only OS 加密记录,不进入 Renderer 持久状态或 Works Square;客户端不携带 OAuth client secret | | Module Access Policy | Main-owned `/api/auth/me` projection → Renderer auth state → module chooser/router | Renderer 只接收 Code/Canvas/Learning/Robot 四个布尔值;缺失对象或字段默认开启,服务端 `design` 映射客户端 `painting` | @@ -24,7 +25,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 | Pi Provider & Managed Resources | Provider catalog、thread-local secret projection、model/resource revision、Prompt/Skill/extension materialization、selected-model tools | 父凭据只进入选中逻辑线程的内存 credential store,child 凭据只进入该短命进程;Works `model_capabilities` 由 Main 严格归一化并作为安全 Provider metadata 持久化。Web Search 仅在精确 capability 存在时随冻结的 selected model/provider/credential 进入 parent tool catalog,并走普通模型计费;不回退 `agent_browser` 或独立 Hosted Provider。服务端 reasoning levels 优先于本地 profile,缺字段则清理 override 并回退;不扫描项目或用户的 `.pi/.agents/.codex`,不把 secret 或原始响应放进 argv、catalog 或 Renderer | | Pi Extension, Subagents & Lifecycle | 必需的生成式 Makelore extension、Main 显式选定的已安装 extensions、UI interaction、ephemeral child、write lease 与 background run lease | Makelore bridge 固定为首个 extension,其余选定 extension 全部经 Pi 的 explicit additional paths 加载且 ambient discovery 关闭;child 并发 4、单次最多 8、禁止递归;active/uncertain run 不因页面隐藏或 confirmation timeout 被停止,replacement/stop 必须可解释并清理所有 ownership | | Official Hosted Plugins | Acquired code-owned bundled Game Resource package → effective parent snapshot → code-owned Main adapter → fixed Works Square hosted route | 无设备下载、更新、Beta 或签名步骤;Renderer/Package/Pi 不持有 Provider key、model 或 URL。每次计费操作要求显式确认,child 不继承 hosted tool。历史 Hosted Web Search 不再进入当前客户端。 | -| Device Packages | Conversation install tools → Main-owned inspect/preview/confirm/commit → immutable local generation → parent Skill/Pi-extension resources | 支持 npm、Git、绝对本地 Plugin 目录与 loose `SKILL.md`;没有可见安装入口、Account Library、Release、Admission 或 Marketplace Package Store。可执行 extension 拥有桌面用户权限且生命周期脚本禁用。每个 generation 包含所有显式安装且当前启用的 Skill/extension;新/idle parent 自动刷新,active parent 在 turn settled 后刷新,child 始终为空。 | +| Device Packages | Conversation install tools → Main-owned inspect/preview/confirm/commit → immutable local generation → parent Skill/Pi-extension resources | 支持 npm、Git、绝对本地 Plugin 目录与 loose `SKILL.md`;没有可见安装入口、Account Library、Release、Admission 或 Marketplace Package Store。可执行 extension 与非空 Skill `scripts/` 拥有桌面用户权限,必须披露并独立确认;生命周期脚本禁用。每个 generation 包含所有显式安装且当前启用的 Skill/extension;新/idle parent 自动刷新,active parent 在 turn settled 后刷新,child 始终为空。 | | AI Design Workspace & Living Form | 一个 Workspace 的当前 Direction、Current Specification、持久 Agent Session、conversation timeline、Tasks 与 Assets | 自然对话是主创作面;Living Form 仅以“AI 已理解”的紧凑辅助摘要与可选手动调整投影服务端 Current Specification,Renderer 只持有草稿和已接受投影 | | AI Design Input & Reconciliation | Chat、字段/集合编辑、decision、proposal、lock、Asset binding 与 restore | 全部进入同一 `design.input.apply` reducer;稳定 command/operation ID 支持 unknown-result 重放,revision conflict 刷新权威状态;待提交 chat 从同一 pending operation 临时投影,assistant delta 只能在匹配该 operation 的一个未完成助手气泡中临时绘制且不生成独立整理进度栏 | | AI Design Gateway Routing | Main-owned Works Square V2 adapter 与 Direction event stream | Main 持有 Works Token、stream ticket、WebSocket、重试分类和错误脱敏;事件顺序与 Task progress 不构成 Specification 真值 | @@ -59,6 +60,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 - accepted/uncertain Coding mutation 不得自动重放;RPC confirmation timeout 后仍保留 target run permit、process ownership 和 background lease,直到迟到 success/failure/exit/abort 权威收敛。其他 Conversation 必须继续可用。 - Main 发出 `lifecycle:sleep` 时 Coding 页面必须关闭旧事件流;编程视图挂载、项目上下文变化、页面重新可见或窗口 focus 时必须从 Main 静默刷新已选 Conversation 的权威 Snapshot。该只读收敛不得转化为 prompt/interaction 的自动重放。隐藏 Conversation 红点只代表新的 pending interaction 或新的 completed/failed/aborted terminal transition。 - 正式包中的 Agent Server 必须以显式 staged `pi-runtime` 的 manifest/root 解析 Pi 依赖,校验导入入口未逃逸对应包目录;不得依赖脚本相邻目录、应用 `node_modules` 或系统包解析作为 fallback。 +- 父 Pi worker 与 Agent Server 必须获得应用选择且不可被 worker overlay 覆盖的 `MAKELORE_NODE_EXECUTABLE`。包含脚本的 Device Package Skill 只能显式使用该 Node 路径,不得依赖系统 PATH。 - selected Provider credential 只投影到目标父逻辑线程的内存 credential store 或目标 child 进程;跨账号模型变化必须重建目标逻辑线程。确定性 Works user-context 缺失是 Provider-auth failure:失效缓存 credential、fail fast、固定脱敏提示,不得归类为 Pi crash。 - Works 下发的 per-model reasoning capability 是可选 Main-owned metadata,不是 Renderer 或 one-api 的权威。存在时只接受受支持的安全形状并覆盖目标模型的 @@ -76,8 +78,9 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 - release builder 对 Main-owned 安全快照运行安装包内固定 npm 11.6.2 的 `ci --ignore-scripts`,并显式调用项目 `package-lock.json` 锁定的 Vite。不得使用全局 PATH、预存 `node_modules` 或 Renderer 提供的路径/origin。项目 Vite config/plugins 以桌面用户权限执行,该边界不是 sandbox。 - `preflightStaticArtifact` 以临时 HTTP loopback origin 提供最终 `built_archive` 的同一内存文件快照,并用 fresh 非持久、未挂载的 Electron WebContents/CDP 检查桌面/移动视口、错误、白屏及外域访问;不安装/调用 Playwright。 - 客户端预检是可绕过的 UX fail-fast:没有可信 receipt,也不复刻生产 opaque-origin。服务端不执行项目 Vite,而是独立重算和校验 source/built/contract 字节、固化不可变 Release;人工审核仍不可绕过。未来若要求 runtime 强门禁,需由可信 verifier 绑定精确构建产物。 -- `ProjectType` 由创建请求写入项目配置,UI 与 Host API 不提供类型变更;缺少类型的旧配置归一为 `custom`。 -- 本地 `projectType` 只选择产品路径和内部构建 preset,不是可信授权声明;Main 仍需安全打包,服务端仍需独立校验清单和包体。 +- `ProjectType` 由创建请求写入项目配置,UI 与 Host API 不提供类型变更。新项目只写 `interactive_ai_app` 或 `custom`;历史 `mini_game` / `mini_program` 在读取边界归一为 `interactive_ai_app` 且不因读取被改写,缺少类型的旧配置归一为 `custom`。 +- 项目创建只拥有 `.makelore/project.json` 和 `knowledge/`。交互式 AI 应用起步树由用户明确调用 Scaffold Skill 生成;Skill 的发布准备度结论不执行也不替代 Main build/preflight/package/upload 或 Works Square 校验与审核。 +- 本地 `projectType` 只选择显式脚手架和内部构建路径,不是可信授权声明;Main 仍需安全打包,服务端仍需独立校验清单和包体。 - 云端确认上传成功后,本机 submission binding 失败只能产生固定、无路径的 `binding_warning`,不能把请求改判为失败;Renderer 仍继续轮询服务端校验与 Release 固化状态。 - 公共播放投影只有在上游 `playable === true`、版本名非空且 URL 通过同源 HTTPS、无 userinfo/loopback、长度、精确路径和无 query/fragment 校验时才可播放;不可信数据 fail closed。 - Renderer 只能获得安全状态字段和安全投影的公共播放 URL,不得持有发布凭据、归档路径或自动部署状态。 @@ -99,7 +102,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 ## Related Decisions -- 当前长期边界记录于 README、ADR-002 至 ADR-007(ADR-001 已由 ADR-007 supersede)及各 Integration Gate;后续如改变唯一入口、凭据所有权、构建执行边界、Living Form/Specification 状态归属、Pi 单 runtime/worker/lease 边界、Learning/Marketplace/Web Search 分发边界、Robot 配网边界或重新引入客户端部署协调器,应新增 ADR。 +- 当前长期边界记录于 README、ADR-002 至 ADR-008(ADR-001 已由 ADR-007 supersede)及各 Integration Gate;后续如改变唯一入口、项目创建/Scaffold Skill/发布权威、凭据所有权、构建执行边界、Living Form/Specification 状态归属、Pi 单 runtime/worker/lease 边界、Learning/Marketplace/Web Search 分发边界、Robot 配网边界或重新引入客户端部署协调器,应新增 ADR。 ## Last Updated diff --git a/.project-docs/30-worklog/current-state.md b/.project-docs/30-worklog/current-state.md index 2404612..978e579 100644 --- a/.project-docs/30-worklog/current-state.md +++ b/.project-docs/30-worklog/current-state.md @@ -4,6 +4,24 @@ This file is the integrated default-branch snapshot. Feature tasks record progre ## Integrated Through +- Interactive AI application and explicit scaffold source `959b633` is integrated + through merge `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952` and canonically reconciled by + task `20260904-reconcile-project-scaffold-f7b4d2a9`. New projects use one + `interactive_ai_app` / “交互式 AI 应用” type or `custom`; historical `mini_game` + and `mini_program` values normalize at the read boundary without a batch rewrite. + Project creation owns only `.makelore/project.json` and `knowledge/`. The independent + `makelore-project-scaffold` Device Package provides an explicit, non-overwriting, + fixed six-file Vite starter and read-only publication-readiness guidance; it does not + install, build, upload, submit, or approve. Non-empty Skill `scripts/` are now disclosed + as desktop-user executable code and use the non-overridable application Node exposed as + `MAKELORE_NODE_EXECUTABLE`. Main and Works Square retain build, preflight, artifact, + upload, immutable Release, and review authority. Exact integration verification passed + the 12-test scaffold suite, 123 focused Vitest tests, typecheck, scoped ESLint, and the + Renderer/Main/Preload/utility Vite build. Source evidence additionally includes the + project-configuration Electron flow, real scaffold-to-locked-Vite build, plugin/Skill + validation, and installed Device Package prepare→confirm→commit/resource reads. Immutable + distribution plus installed Windows, signed macOS, and native Linux proof remain pending; + no plugin publication, production upload, approval, or remote push is claimed. - AI Design streamed-reply diagnosis `229b1b1ce39b7f1541a93ea3c980ab82b6d6266c` and client fix `23f96a523eb37d8397bb3766ce95a59d56e59225` from tasks @@ -587,7 +605,7 @@ This file is the integrated default-branch snapshot. Feature tasks record progre ## Current Focus -客户端面向非专业用户提供“创建小游戏或小程序 → 项目配置中一键提交 → Main 本地 npm/Vite 构建 → Electron 双视口预检最终产物 → 上传 source+built 双归档与 contract → 运营审核”的唯一创建者链路。Main 对安全源码快照运行安装包内固定 npm 11.6.2 的 `npm ci --ignore-scripts`,再显式调用项目 `package-lock.json` 锁定的 Vite;Vite config/plugins 以当前桌面用户权限执行,因此只适用于用户信任的本地项目,不是 sandbox。预检由 Main 以临时 loopback origin 和 Electron WebContents/CDP 检查与最终 `built_archive` 相同的内存文件字节,覆盖桌面/移动视口、运行错误、白屏和外域访问;不使用 Playwright。该检查仍可由非官方客户端绕过,不产生可信 receipt,也不复刻生产 opaque-origin。服务端不再替客户端运行项目 Vite,而是把源码、构建归档和 contract 视为不可信输入,逐字节重算与校验并固化不可变 Release;人工审核仍不可绕过。自定义和缺少类型字段的旧项目不提供该入口。已发布作品优先使用安全投影后的 `play_url`,`runtime_url` 仅保留一个客户端版本的兼容回退。 +客户端面向非专业用户提供“创建交互式 AI 应用 → 明确调用 Scaffold Skill 生成起步文件 → 项目配置中一键提交 → Main 本地 npm/Vite 构建 → Electron 双视口预检最终产物 → 上传 source+built 双归档与 contract → 运营审核”的唯一创建者链路。项目创建只生成 `.makelore/project.json` 和 `knowledge/`;`makelore-project-scaffold` 只负责不覆盖的固定起步树与只读发布准备度说明,不能安装、构建、上传、提审或批准。Main 对安全源码快照运行安装包内固定 npm 11.6.2 的 `npm ci --ignore-scripts`,再显式调用项目 `package-lock.json` 锁定的 Vite;Vite config/plugins 以当前桌面用户权限执行,因此只适用于用户信任的本地项目,不是 sandbox。预检由 Main 以临时 loopback origin 和 Electron WebContents/CDP 检查与最终 `built_archive` 相同的内存文件字节,覆盖桌面/移动视口、运行错误、白屏和外域访问;不使用 Playwright。该检查仍可由非官方客户端绕过,不产生可信 receipt,也不复刻生产 opaque-origin。服务端不再替客户端运行项目 Vite,而是把源码、构建归档和 contract 视为不可信输入,逐字节重算与校验并固化不可变 Release;人工审核仍不可绕过。`custom` 和缺少类型字段的旧项目不提供该入口;历史 `mini_game` / `mini_program` 只在读取边界归一为规范 `interactive_ai_app`。已发布作品优先使用安全投影后的 `play_url`,`runtime_url` 仅保留一个客户端版本的兼容回退。 AI Design Canvas 现在默认以对话和一张持续可见的“我的创作”卡服务 8-16 岁创作者;专业字段矩阵收进按需打开的“精细调整”,移动端保持对话优先并只挂载一个卡片/底部面板。Creation Card、精细调整、Quote、Task 与结果提示都只投影权威状态,已知问题按 code 转成通俗中文,未知服务端或 Provider 文本不会直接显示。一个 Workspace 仍只公开一个 current Direction、一个 persistent Agent Session 和一个 Current Specification;conversation timeline 只记录交互历史。Chat、direct edits、decision responses、proposal acceptance、locks、Asset binding 与 restore 都通过 `design.input.apply` 进入同一服务端 reducer,Renderer drafts 在 accepted 前保持本地。Main 持有 Works Token、stream ticket、WebSocket、request deadline、stable command/operation IDs 与错误脱敏;unknown result 只能复用原 identity,结构化业务错误不得重放。Generation 由服务端对 exact Specification revision 编译 immutable Quote,客户端只展示 public output plan、warnings、expiry 与 Token Points,并以 Quote ID 调用 `design.generation.confirm`;Provider Prompt、model、route、storage 和 billing atoms 不进入 Renderer。Task/Asset events 独立收敛 Workspace resources,不改写 Living Form。Development 与 packaged builds 均使用 Works Square V2,V1 DTO、local semantic adapter、mutable Quote PATCH 与 editable provider Prompt 已移除。 @@ -700,7 +718,7 @@ Robot 绑定设备默认先显示“引导配网 / 已有激活码”路径选 ## In Progress -- 成组集成服务端 source+built+contract 校验、OSS immutable Release、CDN/Edge 与 App 消费链后,使用真实账号和生产配置执行客户端提交到作品播放的整链验收。 +- 先部署支持规范 `interactive_ai_app` 与历史别名的服务端,再安装匹配客户端;随后成组验证显式 Scaffold Skill、source+built+contract 校验、OSS immutable Release、运营审核、CDN/Edge 与 App 消费链。 ## Next Recommended Steps @@ -726,7 +744,7 @@ Robot 绑定设备默认先显示“引导配网 / 已有激活码”路径选 - `/api/works/projects/publish-source` 必须在读取凭据和项目文件前校验 Renderer capability;Host token/base 不能替代该 UI 边界。 - `works-cloud-deploy.json` 仅是已安装数据的兼容文件名,不代表客户端仍拥有自动部署协调器;旧中间态不得恢复为后台任务。 - 刷新凭据、发布 Token、ZIP、幂等键和重试只能由 Electron Main 持有。 -- 本地 `projectType` 只决定产品分流和模板选择,不得作为授权依据或替代 Main/服务端的包体校验。 +- 本地 `projectType` 只决定产品分流和显式 Scaffold Skill 选择,Skill 的静态检测也只提供准备度证据;两者都不得作为授权依据或替代 Main/服务端的构建、包体校验和审核。 - 本地构建必须使用安装版 Electron Node、固定 npm 11.6.2 和项目 `package-lock.json` 锁定的 Vite,不得回退到全局 PATH、已有 `node_modules` 或未验证的 npm 闭包;依赖安装需要网络。 - 项目 Vite config/plugins 以桌面用户权限执行,不能称为 sandbox;此风险边界必须在发布说明中保留。 - AI Design 的 Current Specification 是唯一语义权威。Direction events 必须按 Workspace/Direction/revision/operation identity 收敛;assistant delta、event cursor、Task progress 和 Asset updates 不得改写 Living Form。 diff --git a/.project-docs/30-worklog/task-history.md b/.project-docs/30-worklog/task-history.md index dae2b34..be9301a 100644 --- a/.project-docs/30-worklog/task-history.md +++ b/.project-docs/30-worklog/task-history.md @@ -4,6 +4,7 @@ | Date | Task | Outcome | Docs Updated | |---|---|---|---| +| 2026-09-04 | 交互式 AI 应用与显式 Scaffold Skill 集成 | 合并小游戏/小程序为规范 `interactive_ai_app`,项目创建保持最小边界,独立 Skill 生成固定起步树并说明发布要求;Main/Works 继续拥有发布权威 | ADR-008、current-state、architecture、domain、commitments | | 2026-08-07 | 客户端登录七天滑动续期 | Main-owned 刷新凭据与真实活动续期 | README、current-state | | 2026-08-08 | Makelore 一键提交审核 | 项目配置单入口,Main 安全打包与幂等提交 | README、architecture、domain | | 2026-08-08 | 项目真机预览 | 待审 Owner preview 与精确已发布 Runtime | README、architecture、domain | diff --git a/.project-docs/30-worklog/tasks/20260904-reconcile-project-scaffold-f7b4d2a9.md b/.project-docs/30-worklog/tasks/20260904-reconcile-project-scaffold-f7b4d2a9.md new file mode 100644 index 0000000..ce73b4f --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-reconcile-project-scaffold-f7b4d2a9.md @@ -0,0 +1,76 @@ +# Task: Reconcile interactive AI app scaffold Skill + +## Identity + +- Task ID: 20260904-reconcile-project-scaffold-f7b4d2a9 +- Mode: Integration +- Branch: codex/20260904-integrate-project-scaffold-b6d3e8a1-integrate-project-scaffold +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-integrate-project-scaffold-b6d3e8a1 +- Base commit: 4bc3e0ea5331a7d3f3576768c0119c02c0f0f952 +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Promote the reviewed and already-merged scaffold/type behavior from base commit + `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952` into a client ADR, + canonical architecture/domain memory, current state, history, and commitments. +- Record one `interactive_ai_app` product type, explicit Scaffold Skill ownership, + executable Skill-script disclosure, and remaining installed-build release gates. + +## Intent And Constraints + +- Project creation owns only project metadata and `knowledge/`; the Skill must be + explicit and non-overwriting and may not duplicate build/upload/review authority. +- Historical types normalize without a batch rewrite; `custom` remains + non-publishable. +- Main retains fixed npm/Vite build/preflight/package/upload authority and Works + Square retains validation and review authority. +- Source task records/proposals and the primary checkout's unrelated files remain + untouched. + +## Outcome + +- Added ADR-008 and reconciled canonical brief, architecture, domain, current-state, + history, and release commitments with the reviewed source already present at the + task base. +- Established `interactive_ai_app` as the only new publishable product type and kept + `mini_game` / `mini_program` solely as non-rewriting historical read aliases. +- Recorded the three-owner boundary: Project Service creates metadata and `knowledge/`; + the explicit Device Package Skill creates the fixed starter and explains readiness; + Main/Works Square remain the sole build, upload, validation, Release, and approval + authorities. +- Recorded non-empty Skill `scripts/` as executable code requiring disclosure and later + confirmation, with `MAKELORE_NODE_EXECUTABLE` as the application-owned runtime path. +- Folded immutable distribution and installed Windows/signed macOS/native Linux proof + into the existing release commitments. No plugin publication, deployment, production + submission, approval, or remote push was performed. + +## Verification + +- Exact integration tree `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952`: + scaffold Node suite 12/12; focused project/device/publish Vitest suite 123/123; + `pnpm run typecheck`; scoped ESLint; and `pnpm run build:vite` all passed. +- Source task evidence retained: project-configuration Electron E2E 1/1, real + scaffold → fixed npm 11.6.2 → locked Vite build, plugin creator validation, + Skill creator validation, and real Device Package prepare → confirm → commit → + resource reads passed. +- `git diff --check`: passed. +- `check_project_docs.py --target .`: passed. +- `check_doc_drift.py --target . --task-id + 20260904-reconcile-project-scaffold-f7b4d2a9`: passed; only Integration-owned + canonical memory and this task record are changed. +- Canonical consistency scan found only intentional compatibility statements and + preserved historical task/current-state entries for the superseded dual-type model. + +## Follow-ups + +- Distribute the scaffold plugin from an immutable SemVer/Git source and run the + already-recorded installed-package proofs before claiming platform support. +- Deploy the matching server before the client, then run the real-account + create → scaffold → build → submit → approve → CDN/App playback chain. + +## Promotion Candidates + +- None. This Integration task promoted the accepted source candidates directly into + canonical project memory. diff --git a/.project-docs/40-domain/business-rules.md b/.project-docs/40-domain/business-rules.md index df5de0e..d08e4cd 100644 --- a/.project-docs/40-domain/business-rules.md +++ b/.project-docs/40-domain/business-rules.md @@ -53,16 +53,19 @@ Inspect/preview and a distinct later confirmation precede commit; there is no visible install/source-picker UI. Accepted npm, Git, absolute local Plugin-directory, and loose `SKILL.md` sources become immutable generations and default enabled. Lifecycle scripts - never run. Pi extensions execute with desktop-user authority after that fact is shown - in confirmation. New and idle parent workers refresh automatically, active parents - switch only after settlement, and child workers never inherit Device Package resources. + never run. Pi extensions and non-empty standard Skill `scripts/` subtrees are executable + code and execute with desktop-user authority only after that fact is disclosed and + confirmed. Parent Pi workers and the shared Agent Server receive the non-overridable + application Node path as `MAKELORE_NODE_EXECUTABLE`; portable Skills must not fall back + to system Node. New and idle parent workers refresh automatically, active parents switch + only after settlement, and child workers never inherit Device Package resources. - Device Packages appear as a separate `本机` source beside Official Plugins in the unified `/plugins` workspace and never enter Account Library, Marketplace Package Store, Release, Channel, Admission, project enablement, Agent assignment, or server billing state. -- 面向用户的 AI 编程新建流程必须在 `mini_game`、`mini_program`、`custom` 中选择;`ProjectType` 是产品类型,创建后不能通过 UI 或 Host API 修改,未传类型的兼容 API 调用按 `custom` 处理。 -- 新建小游戏和小程序会生成平台固定版本的受控 Vite 发布模板,并可使用项目配置中的单一“提交审核”入口;`custom` 和缺少类型字段的旧项目不提供一键发布。 -- `ProjectType` 不等于 `BuildPreset`:第一期两个可发布产品类型都映射到内部受控 Vite preset;本地 `projectType` 不是授权边界,Main-owned 安全打包、Host API 和服务端包体校验仍必须执行。 +- 面向用户的 AI 编程新建流程必须在 `interactive_ai_app`(“交互式 AI 应用”)和 `custom` 中选择;`ProjectType` 是产品类型,创建后不能通过 UI 或 Host API 修改。历史 `mini_game` / `mini_program` 仅在读取边界归一为 `interactive_ai_app`,读取本身不改写配置;未传类型的兼容 API 调用按 `custom` 处理。 +- 新建项目只原子生成 `.makelore/project.json` 和 `knowledge/`。交互式 AI 应用的固定六文件 Vite 起步树只能由用户明确调用 `makelore-project-scaffold` Skill 生成;脚本必须先预检全部目标、不得覆盖已有路径,受控失败只回滚本次创建内容,且不得安装依赖、访问网络、构建、上传或提交审核。 +- `ProjectType` 不等于 `BuildPreset` 或 Scaffold 状态:规范可发布类型映射到内部受控 Vite preset;本地 `projectType` 和 Skill 检测结果都不是授权边界,Main-owned 安全打包、Host API 和服务端包体校验仍必须执行。 - 非专业用户只执行一次“提交审核”;构建通过后由运营审核,审核通过即直接发布。 - 创建者发布唯一链路是项目配置“一键提交审核” → Main 本地 npm/Vite 构建 → 最终 built snapshot 的客户端 UX 预检 → source+built+artifact contract 上传 → 服务端校验/固化 → 运营审核;不恢复独立发布上传页、云部署工作台、Compose/deploy-check、自动 watcher/arm/upload 或手工 ZIP 入口。 - 本地构建必须由 Main 对安全源码快照使用安装版 Electron Node 和固定 npm 11.6.2 执行 `npm ci --ignore-scripts`,再显式调用项目 `package-lock.json` 安装的 Vite;不得依赖全局 PATH、既有 `node_modules` 或 Renderer 提供的任意路径/origin。 diff --git a/.project-docs/40-domain/glossary.md b/.project-docs/40-domain/glossary.md index 5075c50..d9ae43f 100644 --- a/.project-docs/40-domain/glossary.md +++ b/.project-docs/40-domain/glossary.md @@ -2,9 +2,9 @@ | Term | Meaning | Notes | |---|---|---| -| `ProjectType` | 创建项目时选择且之后不可变的产品类型 | 当前值为 `mini_game`、`mini_program`、`custom`;缺少字段的旧配置按 `custom` 处理 | -| `BuildPreset` | 平台内部用于构建和验收项目包的受控实现 | 不等于 `ProjectType`;第一期小游戏和小程序都使用 Vite preset | -| 受控发布模板 | Main 在新建可发布项目时生成的固定依赖、锁文件和可运行示例 | 保障一键提交的结构基线,但本地模板和 `projectType` 本身都不是授权边界 | +| `ProjectType` | 创建项目时选择且之后不可变的产品类型 | 规范值为 `interactive_ai_app`、`custom`;历史 `mini_game` / `mini_program` 读取时归一为前者,缺少字段按 `custom` 处理 | +| `BuildPreset` | 平台内部用于构建和验收项目包的受控实现 | 不等于 `ProjectType` 或 Scaffold 状态;交互式 AI 应用使用固定 Vite preset | +| Project Scaffold Skill | 用户明确调用、独立版本化的起步文件生成与发布准备度指导 | 不覆盖路径、不安装/构建/上传/提审;项目创建服务和 Main/Works 发布权威不属于该 Skill | | Main-owned Release Build | Electron Main 对安全源码快照执行固定 npm 与项目 lockfile Vite 的本地构建 | Vite config/plugins 以桌面用户权限执行,不是 sandbox;Renderer 不获得路径、归档或 origin | | Artifact Contract | 与 source/built 双归档一并上传的严格版本化清单 | 服务端把三者视为不可信输入并独立逐字节重算、校验 | | Built Artifact Preflight | 上传前对最终 built snapshot 的同一内存字节执行桌面/移动 Electron WebContents/CDP 检查 | 可绕过 UX fail-fast;无可信 receipt,不复刻生产 opaque-origin | diff --git a/.project-docs/80-commitments/commitments.md b/.project-docs/80-commitments/commitments.md index a9d5e47..8d90e8a 100644 --- a/.project-docs/80-commitments/commitments.md +++ b/.project-docs/80-commitments/commitments.md @@ -12,8 +12,8 @@ Track future-facing memory: promised follow-ups, unfinished loops, timed checks, | 2026-08-17 | 验收首次项目封面并补齐已有资料条件写 | 发布包含源 `145a6ce` 的 Makelore 前,或启用 draft/published metadata 编辑前 | Works 服务端/客户端发布集成 | Partial / Pending | 首次封面已由服务端源 `407c883` 与客户端源 `145a6ce` 通过单请求绑定和失败补偿落地;仍需先部署服务端、重新打包客户端,并以真实对象存储/账号验证 404→create 409、cover 失败和版本阻断。已有资料编辑继续等待 metadata revision/ETag 与 draft-only 条件写;在此之前保持 existing version-only。 | | 2026-08-16 | 验收 Prompt Museum 与 Canvas 删除/重报价的真实服务端链路 | 发布包含 `26b52d7` Canvas 行为的安装包前 | 客户端/服务端集成 | Pending | 使用真实 Works 账号核对 Museum 列表/详情/分页/筛选/署名/CDN/Prompt 回填,并确认固定相对媒体路径可经 Main Bearer 代理、401 刷新、10 MiB/可信 raster MIME 边界后在 Renderer 展示;核对最终 Prompt/options 重报价和确认设计点;删除 Workspace 后确认软删除可见性、未提交任务取消/预留积分释放、已运行任务结算。保留 Main 错误脱敏和严格 DTO/HTTPS 投影,不以客户端回归替代服务端验收 | | 2026-08-16 | 验收 default-on Robot Guided Hotspot Binding 的 Windows/macOS 真实设备链路 | 下一份包含页面内热点连接行为的安装包发布前 | 客户端/硬件/服务端集成 | Pending | 核对精确出货固件与固定 Portal、六位码发行/消费语义;执行 Windows Robot 真机扫描/连接、签名 macOS x64/arm64 CoreLocation/CoreWLAN/worker/ASAR/Koffi smoke,以及真实 Host/native Electron 端到端配网+Binding;保留 `NIANCODE_AI_HARDWARE_GUIDED_HOTSPOT_BINDING=0` 回滚并记录支持矩阵 | -| 2026-08-10 | 完成客户端提交到 App `play_url` 播放的真实生产整链验收 | source+built+contract 服务端协议、OSS immutable Release、CDN/Edge 与 App 消费链成组集成后 | 客户端/服务端集成 | Pending | 使用真实账号执行小游戏和小程序创建、客户端本地构建与同字节预检、双归档提交、服务端逐字节校验/不可变 Release 固化、运营批准、CDN 发布、App 播放与监控核对;如需不可绕过 runtime gate,另行设计可信 verifier | -| 2026-08-12 | Windows/macOS 发布流水线保留固定 npm/Pi 运行时与 Device Package 产物门禁 | 每次生成正式 Windows 或 macOS 安装器时 | 客户端发布 | Partial / Pending | 运行目标平台的 publish-runtime、artifact、Pi 与 final packaged proof;启动最终 Agent Server 并确认它从 staged `pi-runtime` manifest/root 初始化,再用安装后的首个 Conversation 确认不出现 runtime-unavailable 投影。安装 exact-main 客户端后,还需完成至少一个 npm/Git/loose-Skill prepare→confirm 流程,并以包含两个 extension 的已启用 Device Package generation 验证首个 Makelore bridge 与所有附加 extension/Skill 均生效、禁用项不加载、新/idle parent 刷新且 child 为空。源 `5d7a235` 已通过本地未签名 macOS arm64 DMG 的 read-only mounted-image initialize/shutdown,DMG SHA-256 为 `6d0216da6c30f7fed537041b37c69811b8e025af3e9cccf85a64c690e29ecb7b`;当前安装的 1.2.6 客户端早于 `5a2f0eb` / `17664c5` 修复。仍需签名/公证、完整 process proof、实际替换安装与首个 Conversation、新 Windows artifact 及安装后 Device Package activation;不得以 prepare-only 或 workspace real-process 测试替代。 | +| 2026-08-10 | 完成客户端提交到 App `play_url` 播放的真实生产整链验收 | source+built+contract 服务端协议、OSS immutable Release、CDN/Edge 与 App 消费链成组集成后 | 客户端/服务端集成 | Pending | 服务端先部署支持规范 `interactive_ai_app` 和历史别名的合同,再安装匹配客户端;使用真实账号执行交互式 AI 应用创建、显式 Scaffold Skill 初始化、客户端本地构建与同字节预检、双归档提交、服务端逐字节校验/不可变 Release 固化、运营批准、CDN 发布、App 播放与监控核对。旧 `mini_game` / `mini_program` 只验证兼容读取,不作为新建选项;如需不可绕过 runtime gate,另行设计可信 verifier | +| 2026-08-12 | Windows/macOS 发布流水线保留固定 npm/Pi 运行时与 Device Package 产物门禁 | 每次生成正式 Windows 或 macOS 安装器时 | 客户端发布 | Partial / Pending | 运行目标平台的 publish-runtime、artifact、Pi 与 final packaged proof;启动最终 Agent Server 并确认它从 staged `pi-runtime` manifest/root 初始化,再用安装后的首个 Conversation 确认不出现 runtime-unavailable 投影。安装 exact-main 客户端后,还需完成至少一个 npm/Git/loose-Skill prepare→confirm 流程,并以包含两个 extension 的已启用 Device Package generation 验证首个 Makelore bridge 与所有附加 extension/Skill 均生效、禁用项不加载、新/idle parent 刷新且 child 为空;同时完成 `makelore-project-scaffold` prepare→confirm→install→initialize,验证非空 Skill `scripts/` 披露、应用 Node 路径、既有文件不覆盖以及卸载不删除项目文件。源 `5d7a235` 已通过本地未签名 macOS arm64 DMG 的 read-only mounted-image initialize/shutdown,DMG SHA-256 为 `6d0216da6c30f7fed537041b37c69811b8e025af3e9cccf85a64c690e29ecb7b`;当前安装的 1.2.6 客户端早于 `5a2f0eb` / `17664c5` 修复。仍需签名/公证、完整 process proof、实际替换安装与首个 Conversation、新 Windows artifact,以及签名 macOS 和 native Linux 的脚手架 Skill 运行证据;不得以 prepare-only 或 workspace real-process 测试替代。 | | 2026-08-10 | 删除客户端 `runtime_url` 兼容回退 | 一个客户端兼容版本结束,且服务端与存量数据稳定提供 `play_url` | 客户端 | Pending | 删除类型字段、读取分支和对应回归测试 | | 2026-08-11 | 部署并验收 AI 绘画多 Conversation 服务端契约 | 发布包含多会话客户端之前 | 客户端/服务端集成 | Pending | 确认迁移 `0033`、Conversation list/create/read/turn/confirm API、持久 Agent Session 与事件字段已上线 | | 2026-08-12 | 复核 AI 编程 Provider 错误与重试投影 | 升级 Pi、Works gateway 或上游 Provider 错误格式时 | 客户端/Pi/provider 集成 | Pending | 重新验证确定性 Works user-context 缺失仍会 expire credential、非重试 fail fast、固定脱敏为 `CODING_PROVIDER_AUTH_REQUIRED`,并且不会被后续 `agent_settled` 改写为 completed;不得把其他 401/429/5xx 扩大归类。 | 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 3/7] 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); From b92e7ba5bbde186626d93787ca358fbf4c2523a3 Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 22:42:29 +0800 Subject: [PATCH 4/7] docs: reconcile official scaffold plugin integration --- .project-docs/00-brief/success-criteria.md | 2 +- .../adr-008-interactive-ai-app-scaffold.md | 16 ++- .project-docs/10-decisions/decision-index.md | 2 +- ...ation-7e4c2a91__interactive-ai-app-type.md | 45 -------- .project-docs/20-architecture/data-flow.md | 4 +- .project-docs/20-architecture/module-map.md | 4 +- .../20-architecture/system-overview.md | 5 +- .project-docs/30-worklog/current-state.md | 33 +++--- .project-docs/30-worklog/task-history.md | 1 + ...04-marketplace-scaffold-client-6c4e8a21.md | 52 --------- ...roject-scaffold-implementation-7e4c2a91.md | 105 ------------------ ...ct-scaffold-integration-client-4f2a8c71.md | 50 +++++++++ ...904-reconcile-project-scaffold-f7b4d2a9.md | 76 ------------- .project-docs/40-domain/business-rules.md | 8 +- .project-docs/40-domain/glossary.md | 2 +- 15 files changed, 98 insertions(+), 307 deletions(-) delete mode 100644 .project-docs/10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md delete mode 100644 .project-docs/30-worklog/tasks/20260904-marketplace-scaffold-client-6c4e8a21.md delete mode 100644 .project-docs/30-worklog/tasks/20260904-project-scaffold-implementation-7e4c2a91.md create mode 100644 .project-docs/30-worklog/tasks/20260904-project-scaffold-integration-client-4f2a8c71.md delete mode 100644 .project-docs/30-worklog/tasks/20260904-reconcile-project-scaffold-f7b4d2a9.md diff --git a/.project-docs/00-brief/success-criteria.md b/.project-docs/00-brief/success-criteria.md index 0d84260..0d7fd65 100644 --- a/.project-docs/00-brief/success-criteria.md +++ b/.project-docs/00-brief/success-criteria.md @@ -7,7 +7,7 @@ ## Task Completion Standard - 可发布交互式 AI 应用的一键提交必须由 Electron Main 对安全源码快照执行固定 npm 11.6.2 的 `npm ci --ignore-scripts`,并调用项目 `package-lock.json` 锁定的 Vite 生成静态产物。 -- Project Scaffold Skill 可生成固定起步文件并只读说明发布要求、禁止项和证据缺口,但不得安装依赖、构建、上传、提审或把静态检查表述为平台批准。 +- 官方 bundled Project Scaffold Skill 可生成固定起步文件并只读说明发布要求、禁止项和证据缺口,但不得安装依赖、构建、上传、提审或把静态检查表述为平台批准;其 `.mjs` 只来自签名客户端固定资源,Marketplace 下载 artifact 仍必须拒绝脚本。 - Electron 双视口预检必须检查与最终 `built_archive` 相同的内存文件字节;预检失败不得上传,预检成功不得被表述为可信审核凭据。 - 上传协议必须同时携带源码归档、构建归档和严格版本化 artifact contract;服务端独立重算摘要、校验合同并固化不可变 Release。 - Renderer 不得获得发布凭据、归档、临时目录、构建 origin 或任意本地路径;旧客户端和旧 sandbox/browser 任务必须提示升级后重新构建提交。 diff --git a/.project-docs/10-decisions/adr-008-interactive-ai-app-scaffold.md b/.project-docs/10-decisions/adr-008-interactive-ai-app-scaffold.md index f80ae7c..2b1fe1c 100644 --- a/.project-docs/10-decisions/adr-008-interactive-ai-app-scaffold.md +++ b/.project-docs/10-decisions/adr-008-interactive-ai-app-scaffold.md @@ -2,30 +2,30 @@ - Status: Accepted / implemented - Date: 2026-09-04 -- Applies to: Project creation, `ProjectType`, Device Packages, scaffold Skill, release readiness +- Applies to: Project creation, `ProjectType`, official bundled Marketplace Plugins, scaffold Skill, release readiness ## Context 历史客户端把 `mini_game` 和 `mini_program` 作为两个可发布产品类型,并在创建项目时自动生成两套业务模板。这把项目身份、起步文件和发布规则绑定在同一次创建操作中,使模板演进和平台发布约束难以独立维护。 -项目服务当前已经只负责 `.makelore/project.json` 与 `knowledge/`。用户确认将小游戏和小程序合并为一个“交互式 AI 应用”类型,并把生成文件、目录以及提交审批要求放进可独立分发的 Skill。 +项目服务当前已经只负责 `.makelore/project.json` 与 `knowledge/`。用户确认将小游戏和小程序合并为一个“交互式 AI 应用”类型,并把生成文件、目录以及提交审批要求放进由官方 Marketplace 管理、随签名客户端交付的 Skill。 ## Decision - 新建项目只提供规范产品类型 `interactive_ai_app`(显示为“交互式 AI 应用”)和 `custom`。`ProjectType` 创建后仍不可变。 - `mini_game` 与 `mini_program` 只作为历史读取和脚手架兼容别名,在内存中归一为 `interactive_ai_app`。读取或运行脚手架不会改写原配置;后续普通配置变更可以保存规范值。缺少类型字段的旧项目仍归一为 `custom`。 - 项目创建只生成 `.makelore/project.json` 和 `knowledge/`,不生成业务源码、依赖、锁文件或发布模板。 -- 独立 Device Package `makelore-project-scaffold` 提供显式 Scaffold Skill。其脚本按固定版本生成六个交互式 AI 应用起步文件;写入前预检全部目标,不覆盖已有路径,受控失败时只回滚本次创建的文件和目录。 +- 代码内置的官方 Marketplace Plugin `makelore.project-scaffold` 提供显式 Skill `makelore-project-scaffold`。它保留 Account Library 获取、项目启用和 Agent 分配语义,但固定版本资源随 MakeLore 客户端交付,不经过 Package Store 下载。其脚本生成六个交互式 AI 应用起步文件;写入前预检全部目标,不覆盖已有路径,受控失败时只回滚本次创建的文件和目录。 - Scaffold Skill 不安装依赖、不访问网络、不执行构建、不上传、不提交审核,也不提供 `--force`、类型覆盖或模板迁移状态。Main 继续独占固定 npm/Vite 构建、同字节预检、打包和上传;Works Square 继续独占服务端校验、不可变 Release 与运营审核。 -- 非空标准 Skill `scripts/` 目录属于可执行代码,即使 Device Package 仍为 `skill-only`,也必须在预览和独立后续确认中披露文件、网络、进程及桌面用户权限。应用通过不可覆盖的 `MAKELORE_NODE_EXECUTABLE` 向父 Pi worker/Agent Server 提供自身 Node;不得回退系统 Node。 +- 官方 Plugin 的 `.mjs` 仅因它位于客户端固定、代码所有的 bundled resource root 中而可执行。应用通过不可覆盖的 `MAKELORE_NODE_EXECUTABLE` 向父 Pi worker/Agent Server 提供自身 Node;不得回退系统 Node。该例外不适用于 Marketplace 下载 artifact:P0 下载包仍只接受文本/图片 Skill 资源并拒绝 `.mjs`。第三方 Device Package 的可执行代码继续走自身的披露与确认边界。 - Skill 的发布检查只是只读准备度指导:区分确定阻断、源码包排除影响、运行时/平台待确认项和静态已满足项,并引用项目相对证据。它不能宣称构建、上传、审核或生产发布成功。 ## Consequences -- 模板可随独立插件版本演进,不再扩大项目创建服务的职责。 +- 模板可随官方 Plugin 版本演进,不再扩大项目创建服务的职责,也不需要把脚本上传为 Marketplace artifact。 - 旧项目无需批量迁移即可继续打开、生成脚手架和发布;新写入只使用规范类型。 - 发布规则在 Skill 中可被 Agent 解释和预检,但权威执行仍只有 Main 与 Works Square,避免形成第二套发布实现。 -- 对外分发仍需不可变 SemVer/Git 版本、真实 Device Package 安装链和已安装 Windows、签名 macOS、native Linux 运行证据;工作区测试不能替代这些发布门禁。 +- 对外分发仍需不可变 SemVer/Git 版本、服务端 bundled Release 元数据、已安装 Windows、签名 macOS 与 native Linux 运行证据;工作区测试不能替代这些发布门禁。 ## Supersedes @@ -35,5 +35,9 @@ - Source commit: `959b633` - Integration merge: `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952` +- Official Marketplace source commit: `ddb678b46ff10a28e04beeafcfc00c8c8a23ff9f` +- Official Marketplace integration merge: `2bc3e4420852388ce46841e05c1cbf49e80b250c` - Source task: `20260904-project-scaffold-implementation-7e4c2a91` +- Marketplace source task: `20260904-marketplace-scaffold-client-6c4e8a21` +- Marketplace integration task: `20260904-project-scaffold-integration-client-4f2a8c71` - Proposal: `10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md` diff --git a/.project-docs/10-decisions/decision-index.md b/.project-docs/10-decisions/decision-index.md index 8c5fe12..68f5a5c 100644 --- a/.project-docs/10-decisions/decision-index.md +++ b/.project-docs/10-decisions/decision-index.md @@ -4,7 +4,7 @@ | ID | Decision | Status | Date | Applies To | Detail | |---|---|---|---|---|---| -| ADR-008 | 交互式 AI 应用使用单一规范类型,项目创建与显式 Scaffold Skill、发布权威分离 | Accepted / implemented | 2026-09-04 | Project creation、`ProjectType`、Device Packages、scaffold、release readiness | `adr-008-interactive-ai-app-scaffold.md` | +| ADR-008 | 交互式 AI 应用使用单一规范类型,项目创建与官方 bundled Scaffold Skill、发布权威分离 | Accepted / implemented, amended 2026-09-04 | 2026-09-04 | Project creation、`ProjectType`、Official Plugins、Marketplace delivery、release readiness | `adr-008-interactive-ai-app-scaffold.md` | | ADR-007 | AI Design 采用单一 Current Specification、Living Form 与不可变 Quote 的 V2 权威 | Accepted / implemented | 2026-08-30 | AI Design Renderer、Electron Main、Works Square V2 API | `adr-007-ai-design-living-form-v2.md` | | ADR-002 | Robot V1 采用 Main 门控的引导式热点配网并衔接现有六位 Binding | Accepted / implemented, default on | 2026-08-16 | Robot Renderer、Host API、Electron Main、现有固件热点入口 | `adr-002-robot-guided-hotspot-binding-v1.md` | | ADR-003 | Robot 配网页内扫描并连接 Windows/macOS 热点 | Accepted / implemented with physical release gates pending | 2026-08-16 | Robot Renderer、Host API、Electron Main、Windows WLAN、macOS CoreWLAN/CoreLocation | `adr-003-robot-in-app-hotspot-connection.md` | diff --git a/.project-docs/10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md b/.project-docs/10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md deleted file mode 100644 index ebaf5b5..0000000 --- a/.project-docs/10-decisions/proposals/20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md +++ /dev/null @@ -1,45 +0,0 @@ -# 交互式 AI 应用统一类型提案 - -## 文档信息 - -- 状态:Proposed,等待 Integration Gate 提升 -- 所有者:`20260904-project-scaffold-implementation-7e4c2a91` -- 日期:2026-09-04 -- 影响范围:项目创建、项目配置读取、脚手架、发布打包与作品元数据 - -## 决策 - -新建项目的唯一可发布类型改为: - -- 稳定标识:`interactive_ai_app` -- 展示名称:`交互式 AI 应用` - -`mini_game` 与 `mini_program` 不再出现在新建入口,也不再由新客户端写入。两者作为历史只读别名,在项目配置读取边界统一归一化为 `interactive_ai_app`,不批量改写用户已有的 `.makelore/project.json`。归一化后的打包清单和后续提交元数据只输出规范值。 - -`custom` 仍是独立的工作空间类型,并继续禁止进入平台的一键发布流程。 - -## 脚手架与发布合同 - -- 脚手架只维护一套通用 Vite 静态应用模板,不再按小游戏/小程序维护两套业务模板。 -- 历史项目调用脚手架时使用相同的统一模板,脚本返回规范类型,但保留原始项目元数据。 -- 固定 npm/Vite 构建、静态产物合同、浏览器预检、上传和审批要求不因类型合并而放宽。 -- 平台服务应先兼容接收三个值并统一输出 `interactive_ai_app`,再发布只写规范值的新客户端,以支持滚动升级。 - -## 兼容与迁移边界 - -- 不执行数据库或本地配置的批量历史迁移。 -- 不增加版本开关、双写、模板迁移框架或类型特有构建分支。 -- 旧客户端上传 `mini_game` / `mini_program` 时,服务端在校验边界接受并归一化;`custom`、未知值仍拒绝。 -- 历史发布记录保持不可变;公开读取和信息流投影使用规范类型。 - -## 被取代的语义 - -本提案在集成后取代既有文档中“小游戏与小程序是两个可发布项目类型”以及“二者分别生成不同模板”的部分。既有关于静态 Vite 发布、安全预检、制品不可变与审批流程的约束继续有效。 - -## 验收 - -- 新建界面只显示“交互式 AI 应用”和“自定义项目”。 -- 新建可发布项目落盘 `interactive_ai_app`,脚手架生成统一模板。 -- 两种历史配置均可打开和发布,原配置文件不会仅因读取而被改写。 -- 客户端清单、上传元数据和平台公开投影均使用 `interactive_ai_app`。 -- `custom` 与未知类型仍被拒绝发布。 diff --git a/.project-docs/20-architecture/data-flow.md b/.project-docs/20-architecture/data-flow.md index e49ae7d..c825e8a 100644 --- a/.project-docs/20-architecture/data-flow.md +++ b/.project-docs/20-architecture/data-flow.md @@ -6,7 +6,7 @@ |---|---|---|---| | Marketplace catalog and Account Library | Signed-in Renderer intent | bounded Host API → Main Marketplace client/cache → Works Square catalog, Library, resolve, Admission, and download APIs | Main binds requests to the current Account and response generation. Free acquisition changes only Account Library; it does not install, enable a project, assign an Agent Skill, or authorize runtime execution. | | Marketplace device installation | Account Library entry and explicit channel | Main resolve/Admission/download → descriptor/signature/archive/client compatibility verification → immutable Package Store index switch | Stable and explicit Beta remain distinct even when they resolve to the same immutable Release. Failed install/update retains the previous current release; explicit uninstall waits only for protecting worker/account references and completes after the final worker exits. | -| Official bundled hosted Plugin acquisition | Account Library entry for Game Resource | server stable bundled Release/Admission + MakeLore resource package | Free acquisition changes Library only. No device download, update, Beta, signature, or device-uninstall action exists for this exact code-owned identity. Historical Hosted Web Search entries are ignored by current clients. | +| Official bundled Plugin acquisition | Account Library entry for Game Resource or Project Scaffold | server stable bundled Release/Admission + exact MakeLore resource package | Free acquisition changes Library only; project enablement and Agent assignment remain separate. No device download, update, Beta, artifact signature, or device-uninstall action exists for either code-owned identity. Game Resource is hosted; Project Scaffold is `skill_only` and may execute only its bundled `.mjs`. Historical Hosted Web Search entries are ignored. | | Selected-model Web Search | Parent Pi turn with an explicitly supported selected model | `makelore_web_search` core tool → frozen model/provider/credential request with provider-native forced search → ordinary model response/usage | No Marketplace Release, Account Library, Admission, Hosted Web Search client, Plugin Charge, or `agent_browser` fallback participates. Unsupported selected models expose no tool; child workers receive none. | | Conversation-driven Device Package install | Agent tool inspects npm/Git/absolute local Plugin/loose Skill source | Main preview → distinct later user confirmation → immutable device-package generation → new/idle parent worker resources | Renderer has no install picker. Lifecycle scripts never run. Pi extensions and non-empty Skill `scripts/` are disclosed as desktop-user executable code before confirmation. Active workers retain their frozen generation until the turn settles; child workers remain empty. | | Effective Plugin worker snapshot | Installed trusted package or acquired official bundled definition + project selection + Agent assignments + current server policy | effective resolver → Registry/resource loader/Extension Host/tool catalog → parent Pi worker | One frozen snapshot supplies Skills, tools, package roots, and runtime authorization. Disable, account/project switch, logout, Renderer crash, Main shutdown, or worker generation change invalidates future actions without mutating persisted unknown assignments; child workers receive no Plugin projection. | @@ -14,7 +14,7 @@ | 桌面认证生命周期 | Renderer 登录、刷新与注销请求 | Host API → Main Works Session → Works Square `/api/auth/{login,mobile-login,refresh,logout}` → one-feel auth | Main 加密持有并先持久化轮换 token;客户端不携带 OAuth client secret;连续 7 天未使用才清除会话,终止性 `400`/`401` fail closed | | 用户模块入口策略 | 会话恢复 / 登录 / 刷新 | Electron Main → Works `/api/auth/me` → 四布尔安全投影 → Renderer auth store → 卡片/路由/provider gate | 缺失对象或字段默认 `true`;`design` 映射 `painting`;终止性 `401` 清理 Main/Renderer 会话;全局 `/settings` 不受 Code gate | | 项目创建 | 新建项目对话框 | Host API → Main 项目初始化 | 固定 `interactive_ai_app` 或 `custom`,只生成 `.makelore/project.json` 与 `knowledge/`;历史双类型在读取边界归一,不因读取改写 | -| 显式项目脚手架 | 用户在交互式 AI 应用 Conversation 中要求初始化 | Pi `bash` + 项目 write lease → `MAKELORE_NODE_EXECUTABLE` → `makelore-project-scaffold` 脚本 | 固定六文件目标;全量预检、不覆盖、受控失败只回滚本次路径;不安装依赖、不联网、不构建、不上传、不提审 | +| 显式项目脚手架 | 用户为已获取、项目启用并分配给 Agent 的 Project Scaffold Plugin 明确要求初始化 | Pi `bash` + 项目 write lease → `MAKELORE_NODE_EXECUTABLE` → bundled `makelore-project-scaffold` 脚本 | 固定六文件目标;全量预检、不覆盖、受控失败只回滚本次路径;不安装依赖、不联网、不构建、不上传、不提审;下载 artifact 不参与 | | 一键提交 | `ProjectPublishAction` | Renderer capability → Host API → Main 本地 npm/Vite build → built snapshot preflight → source+built+contract 上传 | 只对规范交互式 AI 应用(含读取时归一的历史别名)开放;首次 create 通过单一 multipart 合同原子提交文字资料与必选 PNG/JPEG/WebP 封面,创建失败或冲突不上传版本;已有 draft/published 只提交版本并沿用云端资料/封面,不做无条件 metadata PATCH | | 构建产物预检 | Main-owned built snapshot | 一次性 loopback origin → fresh Electron WebContents/CDP(桌面、移动) | 检查错误、白屏和外域;不调用 Playwright,检查与上传归档相同字节,但不产生可信 receipt | | 提交绑定 | 云端成功上传响应 | Main → submission binding v2 | 只持久化成功的 app/version/review/hash;落盘失败返回固定告警但不反转提交 | diff --git a/.project-docs/20-architecture/module-map.md b/.project-docs/20-architecture/module-map.md index 4afc58d..b942174 100644 --- a/.project-docs/20-architecture/module-map.md +++ b/.project-docs/20-architecture/module-map.md @@ -4,7 +4,7 @@ | Path | Responsibility | Owner Notes | |---|---|---| -| `electron/coding-plugins/manifest.ts`, `release-descriptor.ts`, `signature-verifier.ts`, and `trusted-keys.ts` | Closed Plugin package/descriptor parsing, fixed code-owned resource roots, compatibility checks, canonical archive validation, and Ed25519 trust | Data Service plus exact Game Resource roots are code-owned. Other schema-2 `skill_only`/`platform_hosted` packages remain signed Marketplace artifacts and fail closed without the official public key. Hosted Web Search is not a current Plugin root. | +| `electron/coding-plugins/manifest.ts`, `release-descriptor.ts`, `signature-verifier.ts`, and `trusted-keys.ts` | Closed Plugin package/descriptor parsing, fixed code-owned resource roots, compatibility checks, canonical archive validation, and Ed25519 trust | Data Service plus exact Game Resource and Project Scaffold roots are code-owned. Other schema-2 packages still pass descriptor/signature/archive validation; downloadable P0 Skill assets remain text/image-only. Hosted Web Search is not a current Plugin root. | | `electron/coding-plugins/account-plugin-cache.ts`, `marketplace-client.ts`, and `package-store.ts` | Main-owned Account Library cache, authenticated server client, and atomic immutable device installation store | Account acquisition and device installation are separate. Package bytes switch atomically only after descriptor, signature, archive, client-range, and provenance checks; failed updates preserve the prior current release. | | `electron/coding-plugins/effective-resolver.ts`, `registry.ts`, `project-service.ts`, and `electron/coding-runtime/pi/**` | Effective official Plugin projection, selected-model tools, Device Package resources, and frozen parent logical-thread runtime snapshot | Project enablement and Agent Skill assignment are preserved independently. Trusted Marketplace artifacts, exact acquired official bundled definitions, and immutable local Device Package generations enter through distinct authorities. Child workers remain empty; active threads retain frozen resources until settlement/disposal. | | `electron/coding-runtime/pi/model-tools/**` and `shared/model-tools.ts` | Closed selected-model tool registry and provider-specific Web Search request shaping | The frozen selected model capability controls whether `makelore_web_search` exists. The tool uses that model/provider/credential and normal model billing; no Hosted Plugin adapter, Admission, Plugin Charge, or browser fallback exists. | @@ -13,7 +13,7 @@ | `electron/api/routes/plugin-marketplace.ts`, `src/stores/{plugin-marketplace,device-packages,coding-plugins}.ts`, and `src/pages/Plugins/` | Existing Main/store authorities plus the pure unified Renderer projection for official catalog, Account Library, official package-device state, local Device Packages, retained IDs, and current-project actions | `/plugins` is the sole canonical surface; legacy Plugin routes only replace-redirect into deterministic filters. `official:`, `local:`, and `retained:` identities stay separate, source failures are isolated, and no Account token, filesystem path, Admission, package bytes, signed URL, or visible install-source picker enters Renderer. | | `src/components/works/ProjectPublishAction.tsx` | 可发布项目的一键提交、云构建轮询与用户可理解状态 | 只通过 Renderer API 提交非敏感元数据;绑定告警不终止轮询 | | `shared/project-config.ts` and `electron/coding-projects/project-config.ts` | 规范 `ProjectType` 归一与最小项目创建 | 新写入只使用 `interactive_ai_app` / `custom`;历史 `mini_game` / `mini_program` 只读归一,项目创建只生成 metadata 与 `knowledge/` | -| `plugins/makelore-project-scaffold/` | 独立 Scaffold Skill、固定六文件模板、确定性 Node 脚本、发布要求参考与回归测试 | 通过既有 Pi `bash` write lease 和 `MAKELORE_NODE_EXECUTABLE` 显式运行;不覆盖、不安装依赖、不联网,也不复制 build/upload/review 实现 | +| `resources/coding-plugins/project-scaffold/` and `tests/project-scaffold/` | 官方 bundled Scaffold Plugin、固定六文件模板、确定性 Node 脚本、发布要求参考与回归测试 | 通过既有 Pi `bash` write lease 和 `MAKELORE_NODE_EXECUTABLE` 显式运行;脚本信任来自固定客户端资源,而非下载 artifact;不覆盖、不安装依赖、不联网,也不复制 build/upload/review 实现 | | `src/lib/works-square.ts` | Renderer 侧 Works Square Host API 契约与安全错误映射 | 不接触 Token、ZIP、本地绝对路径或自动部署状态 | | `electron/api/routes/works.ts` | Works Host API、Renderer capability 门禁、上游安全投影 | 发布凭据、打包、上传与本地绑定均在 Main 内完成 | | `electron/services/project-release-builder.ts` | Main-owned 安全快照、本地 npm/Vite 构建、source+built 双归档与 v1 contract | 固定 npm 11.6.2;项目 Vite 由 lockfile 决定;Vite config/plugins 以桌面用户权限执行 | diff --git a/.project-docs/20-architecture/system-overview.md b/.project-docs/20-architecture/system-overview.md index 15ad4e8..816cd53 100644 --- a/.project-docs/20-architecture/system-overview.md +++ b/.project-docs/20-architecture/system-overview.md @@ -11,7 +11,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 | Renderer | 项目配置、一键提交状态 | 不接触账号 Token、ZIP、幂等键或本地绝对路径 | | Host API | 校验本地项目请求并投影安全响应 | 发布 mutation 还必须通过 Renderer capability;Host token/base 不能单独触发发布 | | Project Configuration | 保存不可变 `ProjectType`,原子创建最小项目空间 | 只生成 `.makelore/project.json` 与 `knowledge/`;规范类型为 `interactive_ai_app` / `custom`,历史双类型只在读取边界归一 | -| Project Scaffold Skill | 显式生成固定版本的交互式 AI 应用起步文件,并提供发布准备度指导 | 独立 Device Package;完整预检、不覆盖、受控回滚,不安装依赖、不联网、不构建、不上传、不提审 | +| Project Scaffold Skill | 显式生成固定版本的交互式 AI 应用起步文件,并提供发布准备度指导 | 官方 `makelore.project-scaffold` bundled Marketplace Plugin;完整预检、不覆盖、受控回滚,不安装依赖、不联网、不构建、不上传、不提审 | | Project Release Builder | Main-owned 安全快照、本地 npm/Vite 构建、双归档与 artifact contract | 固定 npm 11.6.2;Vite 由项目 lockfile 锁定;产物与预检使用同一内存字节 | | Works Session & Remembered Password | Main-owned 登录、刷新、注销、七天真实活动滑动续期与可选密码回填 | 登录、刷新、注销统一经过 Works Square;轮换凭据由 Main 安全持有和持久化。记住密码使用独立的 packaged-only OS 加密记录,不进入 Renderer 持久状态或 Works Square;客户端不携带 OAuth client secret | | Module Access Policy | Main-owned `/api/auth/me` projection → Renderer auth state → module chooser/router | Renderer 只接收 Code/Canvas/Learning/Robot 四个布尔值;缺失对象或字段默认开启,服务端 `design` 映射客户端 `painting` | @@ -24,7 +24,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 | Pi Conversation Runtime | 一个长驻 Pi `0.84.2` Agent Server 承载每条 active/warm Conversation 的隔离逻辑 Runtime/Session/JSONL channel | 严格 LF JSONL RPC、generation recovery、Snapshot hydration;正式包从 staged `pi-runtime` manifest/root 定位并校验 Pi 包入口;top-level 逻辑 turn 并发 4、warm idle LRU 8;Server 退出统一使旧 channel 失效并按需单实例重启 | | Pi Provider & Managed Resources | Provider catalog、thread-local secret projection、model/resource revision、Prompt/Skill/extension materialization、selected-model tools | 父凭据只进入选中逻辑线程的内存 credential store,child 凭据只进入该短命进程;Works `model_capabilities` 由 Main 严格归一化并作为安全 Provider metadata 持久化。Web Search 仅在精确 capability 存在时随冻结的 selected model/provider/credential 进入 parent tool catalog,并走普通模型计费;不回退 `agent_browser` 或独立 Hosted Provider。服务端 reasoning levels 优先于本地 profile,缺字段则清理 override 并回退;不扫描项目或用户的 `.pi/.agents/.codex`,不把 secret 或原始响应放进 argv、catalog 或 Renderer | | Pi Extension, Subagents & Lifecycle | 必需的生成式 Makelore extension、Main 显式选定的已安装 extensions、UI interaction、ephemeral child、write lease 与 background run lease | Makelore bridge 固定为首个 extension,其余选定 extension 全部经 Pi 的 explicit additional paths 加载且 ambient discovery 关闭;child 并发 4、单次最多 8、禁止递归;active/uncertain run 不因页面隐藏或 confirmation timeout 被停止,replacement/stop 必须可解释并清理所有 ownership | -| Official Hosted Plugins | Acquired code-owned bundled Game Resource package → effective parent snapshot → code-owned Main adapter → fixed Works Square hosted route | 无设备下载、更新、Beta 或签名步骤;Renderer/Package/Pi 不持有 Provider key、model 或 URL。每次计费操作要求显式确认,child 不继承 hosted tool。历史 Hosted Web Search 不再进入当前客户端。 | +| Official Bundled Plugins | Acquired code-owned bundled Game Resource / Project Scaffold package → project enablement and Agent assignment → effective parent snapshot | 两者都不经过设备下载、更新、Beta 或 artifact 签名。Game Resource 进入固定 Works Square hosted route;Project Scaffold 仅提供 Skill,其 `.mjs` 来自签名客户端固定资源。Marketplace 下载包仍只接受 P0 文本/图片资源。child 不继承 Plugin。 | | Device Packages | Conversation install tools → Main-owned inspect/preview/confirm/commit → immutable local generation → parent Skill/Pi-extension resources | 支持 npm、Git、绝对本地 Plugin 目录与 loose `SKILL.md`;没有可见安装入口、Account Library、Release、Admission 或 Marketplace Package Store。可执行 extension 与非空 Skill `scripts/` 拥有桌面用户权限,必须披露并独立确认;生命周期脚本禁用。每个 generation 包含所有显式安装且当前启用的 Skill/extension;新/idle parent 自动刷新,active parent 在 turn settled 后刷新,child 始终为空。 | | AI Design Workspace & Living Form | 一个 Workspace 的当前 Direction、Current Specification、持久 Agent Session、conversation timeline、Tasks 与 Assets | 自然对话是主创作面;Living Form 仅以“AI 已理解”的紧凑辅助摘要与可选手动调整投影服务端 Current Specification,Renderer 只持有草稿和已接受投影 | | AI Design Input & Reconciliation | Chat、字段/集合编辑、decision、proposal、lock、Asset binding 与 restore | 全部进入同一 `design.input.apply` reducer;稳定 command/operation ID 支持 unknown-result 重放,revision conflict 刷新权威状态;待提交 chat 从同一 pending operation 临时投影,assistant delta 只能在匹配该 operation 的一个未完成助手气泡中临时绘制且不生成独立整理进度栏 | @@ -80,6 +80,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展 - 客户端预检是可绕过的 UX fail-fast:没有可信 receipt,也不复刻生产 opaque-origin。服务端不执行项目 Vite,而是独立重算和校验 source/built/contract 字节、固化不可变 Release;人工审核仍不可绕过。未来若要求 runtime 强门禁,需由可信 verifier 绑定精确构建产物。 - `ProjectType` 由创建请求写入项目配置,UI 与 Host API 不提供类型变更。新项目只写 `interactive_ai_app` 或 `custom`;历史 `mini_game` / `mini_program` 在读取边界归一为 `interactive_ai_app` 且不因读取被改写,缺少类型的旧配置归一为 `custom`。 - 项目创建只拥有 `.makelore/project.json` 和 `knowledge/`。交互式 AI 应用起步树由用户明确调用 Scaffold Skill 生成;Skill 的发布准备度结论不执行也不替代 Main build/preflight/package/upload 或 Works Square 校验与审核。 +- `makelore.project-scaffold` 是代码所有的 bundled `skill_only` Marketplace Plugin。其官方 `.mjs` 只从固定客户端资源根加载;下载 artifact 的扩展名/资产校验没有放宽,仍拒绝 `.mjs`。 - 本地 `projectType` 只选择显式脚手架和内部构建路径,不是可信授权声明;Main 仍需安全打包,服务端仍需独立校验清单和包体。 - 云端确认上传成功后,本机 submission binding 失败只能产生固定、无路径的 `binding_warning`,不能把请求改判为失败;Renderer 仍继续轮询服务端校验与 Release 固化状态。 - 公共播放投影只有在上游 `playable === true`、版本名非空且 URL 通过同源 HTTPS、无 userinfo/loopback、长度、精确路径和无 query/fragment 校验时才可播放;不可信数据 fail closed。 diff --git a/.project-docs/30-worklog/current-state.md b/.project-docs/30-worklog/current-state.md index 978e579..fa3cb5d 100644 --- a/.project-docs/30-worklog/current-state.md +++ b/.project-docs/30-worklog/current-state.md @@ -4,24 +4,31 @@ This file is the integrated default-branch snapshot. Feature tasks record progre ## Integrated Through -- Interactive AI application and explicit scaffold source `959b633` is integrated - through merge `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952` and canonically reconciled by - task `20260904-reconcile-project-scaffold-f7b4d2a9`. New projects use one +- Interactive AI application source `959b633` and official Marketplace packaging + source `ddb678b46ff10a28e04beeafcfc00c8c8a23ff9f` are integrated through merges + `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952` and + `2bc3e4420852388ce46841e05c1cbf49e80b250c`, then canonically reconciled by tasks + `20260904-reconcile-project-scaffold-f7b4d2a9` and + `20260904-project-scaffold-integration-client-4f2a8c71`. New projects use one `interactive_ai_app` / “交互式 AI 应用” type or `custom`; historical `mini_game` and `mini_program` values normalize at the read boundary without a batch rewrite. - Project creation owns only `.makelore/project.json` and `knowledge/`. The independent - `makelore-project-scaffold` Device Package provides an explicit, non-overwriting, - fixed six-file Vite starter and read-only publication-readiness guidance; it does not - install, build, upload, submit, or approve. Non-empty Skill `scripts/` are now disclosed - as desktop-user executable code and use the non-overridable application Node exposed as - `MAKELORE_NODE_EXECUTABLE`. Main and Works Square retain build, preflight, artifact, + Project creation owns only `.makelore/project.json` and `knowledge/`. The official + code-owned bundled Marketplace Plugin `makelore.project-scaffold` version `1.0.0` + provides the explicit `makelore-project-scaffold` Skill, a non-overwriting fixed + six-file Vite starter, and read-only publication-readiness guidance; it does not + install, build, upload, submit, or approve. Its `.mjs` uses the non-overridable + application Node exposed as `MAKELORE_NODE_EXECUTABLE` and is executable only because + it ships in the fixed signed-client resource root. Downloadable Marketplace artifacts + remain text/image-only and reject `.mjs`; acquisition, project enablement, Agent + assignment, and immutable bundled Release/Admission remain distinct. Main and Works + Square retain build, preflight, artifact, upload, immutable Release, and review authority. Exact integration verification passed the 12-test scaffold suite, 123 focused Vitest tests, typecheck, scoped ESLint, and the Renderer/Main/Preload/utility Vite build. Source evidence additionally includes the project-configuration Electron flow, real scaffold-to-locked-Vite build, plugin/Skill - validation, and installed Device Package prepare→confirm→commit/resource reads. Immutable - distribution plus installed Windows, signed macOS, and native Linux proof remain pending; - no plugin publication, production upload, approval, or remote push is claimed. + validation and bundled resource loading. A packaged-app smoke plus installed Windows, + signed macOS, and native Linux proof remain pending; no live Marketplace migration, + production upload, approval, deployment, or remote push is claimed. - AI Design streamed-reply diagnosis `229b1b1ce39b7f1541a93ea3c980ab82b6d6266c` and client fix `23f96a523eb37d8397bb3766ce95a59d56e59225` from tasks @@ -605,7 +612,7 @@ This file is the integrated default-branch snapshot. Feature tasks record progre ## Current Focus -客户端面向非专业用户提供“创建交互式 AI 应用 → 明确调用 Scaffold Skill 生成起步文件 → 项目配置中一键提交 → Main 本地 npm/Vite 构建 → Electron 双视口预检最终产物 → 上传 source+built 双归档与 contract → 运营审核”的唯一创建者链路。项目创建只生成 `.makelore/project.json` 和 `knowledge/`;`makelore-project-scaffold` 只负责不覆盖的固定起步树与只读发布准备度说明,不能安装、构建、上传、提审或批准。Main 对安全源码快照运行安装包内固定 npm 11.6.2 的 `npm ci --ignore-scripts`,再显式调用项目 `package-lock.json` 锁定的 Vite;Vite config/plugins 以当前桌面用户权限执行,因此只适用于用户信任的本地项目,不是 sandbox。预检由 Main 以临时 loopback origin 和 Electron WebContents/CDP 检查与最终 `built_archive` 相同的内存文件字节,覆盖桌面/移动视口、运行错误、白屏和外域访问;不使用 Playwright。该检查仍可由非官方客户端绕过,不产生可信 receipt,也不复刻生产 opaque-origin。服务端不再替客户端运行项目 Vite,而是把源码、构建归档和 contract 视为不可信输入,逐字节重算与校验并固化不可变 Release;人工审核仍不可绕过。`custom` 和缺少类型字段的旧项目不提供该入口;历史 `mini_game` / `mini_program` 只在读取边界归一为规范 `interactive_ai_app`。已发布作品优先使用安全投影后的 `play_url`,`runtime_url` 仅保留一个客户端版本的兼容回退。 +客户端面向非专业用户提供“创建交互式 AI 应用 → 明确调用官方 bundled Project Scaffold Skill 生成起步文件 → 项目配置中一键提交 → Main 本地 npm/Vite 构建 → Electron 双视口预检最终产物 → 上传 source+built 双归档与 contract → 运营审核”的唯一创建者链路。项目创建只生成 `.makelore/project.json` 和 `knowledge/`;`makelore.project-scaffold` 只负责不覆盖的固定起步树与只读发布准备度说明,不能安装、构建、上传、提审或批准。其 `.mjs` 只从签名客户端固定资源加载,Marketplace 下载 artifact 仍拒绝脚本。Main 对安全源码快照运行安装包内固定 npm 11.6.2 的 `npm ci --ignore-scripts`,再显式调用项目 `package-lock.json` 锁定的 Vite;Vite config/plugins 以当前桌面用户权限执行,因此只适用于用户信任的本地项目,不是 sandbox。预检由 Main 以临时 loopback origin 和 Electron WebContents/CDP 检查与最终 `built_archive` 相同的内存文件字节,覆盖桌面/移动视口、运行错误、白屏和外域访问;不使用 Playwright。该检查仍可由非官方客户端绕过,不产生可信 receipt,也不复刻生产 opaque-origin。服务端不再替客户端运行项目 Vite,而是把源码、构建归档和 contract 视为不可信输入,逐字节重算与校验并固化不可变 Release;人工审核仍不可绕过。`custom` 和缺少类型字段的旧项目不提供该入口;历史 `mini_game` / `mini_program` 只在读取边界归一为规范 `interactive_ai_app`。已发布作品优先使用安全投影后的 `play_url`,`runtime_url` 仅保留一个客户端版本的兼容回退。 AI Design Canvas 现在默认以对话和一张持续可见的“我的创作”卡服务 8-16 岁创作者;专业字段矩阵收进按需打开的“精细调整”,移动端保持对话优先并只挂载一个卡片/底部面板。Creation Card、精细调整、Quote、Task 与结果提示都只投影权威状态,已知问题按 code 转成通俗中文,未知服务端或 Provider 文本不会直接显示。一个 Workspace 仍只公开一个 current Direction、一个 persistent Agent Session 和一个 Current Specification;conversation timeline 只记录交互历史。Chat、direct edits、decision responses、proposal acceptance、locks、Asset binding 与 restore 都通过 `design.input.apply` 进入同一服务端 reducer,Renderer drafts 在 accepted 前保持本地。Main 持有 Works Token、stream ticket、WebSocket、request deadline、stable command/operation IDs 与错误脱敏;unknown result 只能复用原 identity,结构化业务错误不得重放。Generation 由服务端对 exact Specification revision 编译 immutable Quote,客户端只展示 public output plan、warnings、expiry 与 Token Points,并以 Quote ID 调用 `design.generation.confirm`;Provider Prompt、model、route、storage 和 billing atoms 不进入 Renderer。Task/Asset events 独立收敛 Workspace resources,不改写 Living Form。Development 与 packaged builds 均使用 Works Square V2,V1 DTO、local semantic adapter、mutable Quote PATCH 与 editable provider Prompt 已移除。 diff --git a/.project-docs/30-worklog/task-history.md b/.project-docs/30-worklog/task-history.md index be9301a..781ad84 100644 --- a/.project-docs/30-worklog/task-history.md +++ b/.project-docs/30-worklog/task-history.md @@ -4,6 +4,7 @@ | Date | Task | Outcome | Docs Updated | |---|---|---|---| +| 2026-09-04 | Official Marketplace Project Scaffold integration | 将 `makelore.project-scaffold` 作为代码所有、随签名客户端交付的 bundled `skill_only` Plugin 接入 Account Library/项目启用/Agent 分配;官方 `.mjs` 不经过下载 artifact,下载包仍保持文本/图片 P0 限制 | ADR-008、current-state、architecture、domain、success criteria、integration task | | 2026-09-04 | 交互式 AI 应用与显式 Scaffold Skill 集成 | 合并小游戏/小程序为规范 `interactive_ai_app`,项目创建保持最小边界,独立 Skill 生成固定起步树并说明发布要求;Main/Works 继续拥有发布权威 | ADR-008、current-state、architecture、domain、commitments | | 2026-08-07 | 客户端登录七天滑动续期 | Main-owned 刷新凭据与真实活动续期 | README、current-state | | 2026-08-08 | Makelore 一键提交审核 | 项目配置单入口,Main 安全打包与幂等提交 | README、architecture、domain | 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 deleted file mode 100644 index 93609ba..0000000 --- a/.project-docs/30-worklog/tasks/20260904-marketplace-scaffold-client-6c4e8a21.md +++ /dev/null @@ -1,52 +0,0 @@ -# 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/.project-docs/30-worklog/tasks/20260904-project-scaffold-implementation-7e4c2a91.md b/.project-docs/30-worklog/tasks/20260904-project-scaffold-implementation-7e4c2a91.md deleted file mode 100644 index 90e9837..0000000 --- a/.project-docs/30-worklog/tasks/20260904-project-scaffold-implementation-7e4c2a91.md +++ /dev/null @@ -1,105 +0,0 @@ -# Task: Implement project scaffold Skill plugin - -## Identity - -- Task ID: 20260904-project-scaffold-implementation-7e4c2a91 -- Mode: Feature -- Branch: codex/20260904-diagnose-design-stream-6a4e9c21-diagnose-design-stream -- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-diagnose-design-stream-6a4e9c21 -- Base commit: 336e0bb0caf24537b7b0f350aba3e04a37f5544c -- Owner: codex -- Status: Ready for Integration - -## Scope - -- Preserve the current project-creation boundary and add regression coverage proving that no mini-game, mini-program, or custom business scaffold is created. -- Correct the new-project product copy so users are directed to the explicit project-scaffold Skill after creation. -- Expose the application-owned Node executable to direct Pi workers and the shared Agent Server. -- Treat non-empty standard Skill `scripts/` directories as executable code in Device Package preview, confirmation, and details copy. -- Add an independently installable `plugins/makelore-project-scaffold/` package containing one Skill, deterministic scaffold script, fixed templates, submission requirements, and tests. -- Make the Skill itself route publication/readiness requests and carry the current platform contract as four distinct classes: required input, direct blocker, source-package exclusion, and runtime/platform-only confirmation. -- Add regression coverage that keeps the documented source-package exclusion set aligned with the current packager without introducing a second build or publication implementation. -- Verify the unified generated project through the current project packager and local npm/Vite release seam, plus both historical input aliases at the compatibility boundary. -- Replace the two publishable project types with canonical `interactive_ai_app` / “交互式 AI 应用” across creation, configuration, packaging, the scaffold Skill, and Works Square-facing metadata while retaining read compatibility for existing `mini_game` and `mini_program` projects. - -## Intent And Constraints - -- Implement accepted proposal `ML-PROJECT-SCAFFOLD-001` without restoring project-creation side effects. -- Keep immutable `.makelore/project.json.projectType` as the only scaffold selector; do not add scaffold state, a template migration layer, `--force`, or a dedicated Pi extension/tool. -- Execute scaffolding only through the existing Pi `bash` project write lease and `MAKELORE_NODE_EXECUTABLE`; do not fall back to system Node or Python. -- Never overwrite target files. Preflight the full fixed target set and roll back only files/directories created by the current handled invocation. -- Preserve Main-owned fixed npm/Vite build, source/built artifact contract, browser preflight, and Works Square operational review as the release authority. -- Keep feature-task writes within product code, tests, this task record, and task-prefixed supporting records; canonical project memory changes remain promotion candidates for Integration Gate. -- Stage the plugin source under `plugins/makelore-project-scaffold/` for local Device Package installation. Immutable Git tagging, publication, and cross-platform packaged smoke remain release follow-ups and are not claimed by this task. - -## Outcome - -- Confirmed the request's original deletion premise was already true on base `336e0bb`: Project Service creates only `.makelore/project.json` and `knowledge/`. Added creation regressions for the canonical interactive type and `custom`, and removed the Sidebar promise that project creation itself makes a publishable scaffold. -- Added the non-overridable `MAKELORE_NODE_EXECUTABLE` host contract to both direct Pi workers and the shared Agent Server process. A worker overlay cannot spoof the application-selected executable path. -- Extended Device Package inspection so a declared Skill with a non-empty standard `scripts/` subtree is executable code. Skill-only script packages retain `kind: skill-only`, require the existing cross-turn executable confirmation, and show Skill-specific file/network/process authority copy in preview and plugin details. Existing Pi-extension and mixed packages retain executable warnings. -- Added `plugins/makelore-project-scaffold/` as a standard, independently versioned `0.1.0` plugin with one Skill and no Pi extension. Its package and plugin manifests are same-name/same-version and have no install lifecycle scripts. -- Added a deterministic Node-only `scaffold.mjs` selected exclusively by `.makelore/project.json.projectType`. It accepts only `--project-root`, creates one fixed six-file interactive AI application target set, reads all templates and preflights every target before writing, uses exclusive creation, preserves unrelated files, returns the flat version-1 JSON protocol, and rolls back only current-invocation files/directories on handled write failure. It provides no `--force`, type override, migration state, dependency install, network request, or publisher replacement. -- Regenerated the Vite 7.3.1 lockfile with the repository's fixed npm 11.6.2 and verified the unified template through source packaging and a real locked npm/Vite build. Added an approval-readiness reference that distinguishes static preparation from authoritative Main build/preflight/upload and Works Square operational review. -- Expanded the Skill's model-invocation routing and package-facing copy so requests about publishing, one-click submission, forbidden content, failure causes, or release readiness enter an explicit read-only readiness workflow. Project-specific reports now separate confirmed blockers, excluded-package impact, runtime/platform checks, and statically proven requirements, cite project-relative evidence, and use bounded conclusions that never claim approval. -- Reconciled the bundled publication reference against the current project packager, fixed npm/Vite release builder, static-artifact browser preflight, and Works submission route. It now records the full schema-v2 project prerequisite, root build inputs, exact size/count limits, direct path/link/output blockers, root `release.json` prohibition, both viewports and runtime failures, first-submit metadata/cover contract, and the exact case-insensitive excluded directory/file/suffix sets plus dynamic `.env`, credential JSON, Terraform, and `works-` patterns. -- Kept exclusion semantics explicit: excluded paths may remain in a project and are not blockers by existence; the Skill reports their effect only when required source or assets depend on them. The reference also states that an HTTP(S) literal is only a risk until the authoritative preflight makes an actual cross-origin request. -- Did not add a release-check script, duplicate packager, local dependency install, Vite invocation, browser launch, upload, or submission path to the plugin. Main and Works Square remain the only execution and approval authorities. -- Verified the real plugin through Device Package prepare, later-turn confirmation, commit, enabled-resource resolution, and installed script/template/reference reads. -- Replaced the two new-project values with canonical `interactive_ai_app` / “交互式 AI 应用”. New creation, generated `niancode.yml`, submission metadata, and publish UI use only the canonical value. Existing `mini_game` and `mini_program` configs normalize at the read boundary; a read or scaffold invocation does not rewrite the original config, while a later ordinary config mutation may persist the normalized value. -- Replaced the two business-specific starter trees with one generic interactive Vite starter. The scaffold continues to accept both historical values solely as compatibility aliases and reports the canonical type. -- Added the task-owned interactive-type proposal instead of modifying accepted ADRs or canonical project memory in feature mode. -- No existing user project files were removed or migrated. No plugin tag was published and the plugin was not silently installed into the running application. - -## Verification - -- `node --test plugins/makelore-project-scaffold/tests/scaffold.test.mjs`: 12 passed. Covers the canonical value, both historical aliases, exact unified output tree, metadata preservation, conflicts, invalid/missing/custom configs, invalid CLI, missing template, Chinese/space paths, and handled-write rollback. -- Focused type/scaffold/release/UI regressions: 7 Vitest files and 107 tests passed. -- `pnpm typecheck` and scoped ESLint over the changed type, configuration, packaging, UI, route, and test files passed. -- Desktop project-configuration Playwright E2E: 1 passed; the new-project dialog exposed only “交互式 AI 应用” and `custom`, and the publish action remained available for the canonical type. -- Real unified-template smoke: the actual scaffold ran in a verified temporary directory, fixed npm 11.6.2 completed `ci --ignore-scripts`, Vite 7.3.1 built successfully, and non-empty `dist/index.html` was produced; the temporary directory was removed. -- Plugin creator validation passed; Skill creator `quick_validate.py` returned `Skill is valid!` under explicit Python UTF-8 mode. -- `pnpm run build:vite` passed for Renderer, Main, Preload, and utility worker. Only existing Browserslist, mixed-import, and large-chunk warnings remained. -- Full serial main suite: 225/226 files passed; 1886 tests passed and 2 skipped. The only failure was the existing Pi Agent Server two-Bash-call wall-clock assertion (`2657ms < 2000ms`). An immediate isolated rerun again exceeded the fixed threshold (`2170ms`) while its other 5 tests passed; no project-type/scaffold path is involved and this task did not relax the threshold. The separately excluded pressure suite passed 1/1. -- Earlier focused Device Package, Pi environment, Plugin Details, release-builder, and browser-preflight suites recorded by this same task remained green; the type follow-up did not modify those implementations. -- Final `git diff --check` and task-aware project-doc drift check passed; only the task record and task-prefixed proposal changed under `.project-docs/`. -- Merge handoff re-ran `git diff --check` and the task-aware drift check after the explicit integration request; both remained green and no product files changed during handoff. - -## Follow-ups - -- Release `SCF-060`: move or mirror the staged plugin source to the chosen independent distribution repository, create an immutable SemVer tag, document the Device Package install source, and complete a small real-user prepare → confirm → install → initialize trial. Distribution location remains a product/release decision; this task does not invent it. -- Before claiming packaged support, run the Skill through an installed MakeLore build with the actual packaged executable and Git Bash on Windows. Run equivalent packaged smoke on macOS and native Linux; only then claim three-platform support. -- Verify enable/disable, upgrade to a second immutable version, downgrade/reinstall of a known version, and uninstall semantics. Uninstall must not delete files already created in user projects. -- Official Marketplace Skill artifacts currently reject executable `.mjs` assets. Continue using Device Package/Git distribution unless a separately approved Marketplace artifact policy adds executable Skill support. -- Track the existing `pi-agent-server-process-real` `<2000ms` assertion as a separate performance-test stability issue; it currently exceeds the threshold both under the full serial suite and in one isolated rerun, while its functional assertions pass. -- After code/plugin integration and release acceptance, run an Integration Gate task to promote the canonical project-creation and Device Package facts listed below. - -## Promotion Candidates - -- **Canonical interactive AI application type** - - Target canonical documents: `.project-docs/10-decisions/`, `.project-docs/30-worklog/current-state.md`, `.project-docs/40-domain/glossary.md`, `.project-docs/40-domain/business-rules.md`, and relevant architecture/data-flow summaries. - - Proposal: replace the distinct new-project `mini_game` / `mini_program` values and templates with `interactive_ai_app` / “交互式 AI 应用” and one generic interactive Vite scaffold; retain both old values only as historical read aliases. - - Evidence: shared/config normalizer and regressions, new-project E2E, canonical package/submission tests, all three scaffold input tests, real unified-template build, and task proposal `20260904-project-scaffold-implementation-7e4c2a91__interactive-ai-app-type.md`. - - Future impact: future templates and publishing rules evolve against one stable product type while existing local projects continue to open and publish without a batch rewrite. - - Semantic conflicts: supersedes the canonical two-publishable-type classification and any separate mini-game/mini-program template promises; it preserves fixed npm/Vite build, approval, and immutable release rules. - - Human confirmation required: no additional product decision is required because the user explicitly requested the merge; Integration Gate review is still required before canonical writes. -- **Project creation / scaffold ownership** - - Target canonical documents: `.project-docs/30-worklog/current-state.md`, `20-architecture/system-overview.md`, `20-architecture/data-flow.md`, `40-domain/glossary.md`, `40-domain/business-rules.md`; add a new integration entry to `30-worklog/task-history.md` rather than rewriting historical task rows. - - Proposal: replace current-state claims that mini-game/program creation atomically generates Vite templates with the accepted three-boundary model: Project Service owns only project metadata and `knowledge/`; explicit `makelore-project-scaffold` Skill owns starter files; Main/Works release code owns build, preflight, packaging, upload, and review state. - - Evidence: `electron/coding-projects/project-config.ts`, the canonical/custom creation regression, plugin script/tests, unified-template build smoke, and accepted proposal `ML-PROJECT-SCAFFOLD-001`. - - Future impact: prevents UI/docs/agents from assuming a newly created project is immediately publishable and keeps template evolution independent from project identity. - - Semantic conflicts: supersedes the integrated 2026-08-09 current-state claim; old task records remain historical evidence and must not be edited. - - Human confirmation required: no for factual promotion after the implementation commit is integrated; yes if product wants automatic/bundled initialization instead of the accepted explicit Skill boundary. -- **Executable Skill scripts and host runtime** - - Target canonical documents: `.project-docs/30-worklog/current-state.md`, `20-architecture/system-overview.md`, `20-architecture/module-map.md`, `20-architecture/data-flow.md`, and `40-domain/business-rules.md`. - - Proposal: state that a Device Package Skill with a non-empty standard `scripts/` subtree is executable code requiring preview disclosure and later-turn confirmation even when `kind` remains `skill-only`; parent worker/Agent Server environments expose the non-overridable application Node path through `MAKELORE_NODE_EXECUTABLE`. - - Evidence: `device-package-format.ts`, `device-package-manager.ts`, Plugin Details UI/tests, both Pi process tests, real package prepare/commit test, and Windows Git Bash quoting smoke. - - Future impact: keeps permission messaging accurate for script-backed Skills and gives portable Skills an application-owned runtime without PATH fallback. - - Semantic conflicts: current canonical wording mentions executable Pi extensions only. Existing rule that child workers receive no Device Package resources remains unchanged. - - Human confirmation required: no for promotion after integration; actual packaged platform claims still require the release smokes in Follow-ups. -- **Release obligation** - - Target canonical document: `.project-docs/80-commitments/commitments.md`. - - Proposal: fold this plugin's actual packaged Windows/macOS/Linux execution and Device Package lifecycle trial into the existing client release commitment instead of creating a parallel release gate. - - Evidence: workspace Windows Git Bash and real release-builder smokes are green; immutable tag, installed-app execution, macOS, and Linux evidence are absent. - - Future impact: prevents workspace-only evidence from being mistaken for a distributable cross-platform release. - - Semantic conflicts: the existing commitment already warns that workspace prepare tests do not replace final packaged proof; preserve that stricter rule. - - Human confirmation required: release owner confirmation is required before marking the commitment complete or choosing the public distribution location. diff --git a/.project-docs/30-worklog/tasks/20260904-project-scaffold-integration-client-4f2a8c71.md b/.project-docs/30-worklog/tasks/20260904-project-scaffold-integration-client-4f2a8c71.md new file mode 100644 index 0000000..95d925d --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-project-scaffold-integration-client-4f2a8c71.md @@ -0,0 +1,50 @@ +# Task: Integrate official project scaffold plugin + +## Identity + +- Task ID: 20260904-project-scaffold-integration-client-4f2a8c71 +- Mode: Integration +- Branch: codex/20260904-project-scaffold-integration-client-4f2a8c71-project-scaffold-integration-client +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-project-scaffold-integration-client-4f2a8c71 +- Base commit: 336e0bb0caf24537b7b0f350aba3e04a37f5544c +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Integrate the four-commit interactive-AI-application scaffold chain ending at `ddb678b46ff10a28e04beeafcfc00c8c8a23ff9f` onto the committed `main` frontier `336e0bb0caf24537b7b0f350aba3e04a37f5544c`. +- Reconcile ADR-008 and the canonical architecture/current-state records with the final official Marketplace delivery model. +- Verify the merged client tree and prepare it for safe default-branch promotion without touching the unowned dirty primary checkout. + +## Intent And Constraints + +- Preserve normal Git ancestry; do not rebase, squash, rewrite history, or resolve semantic conflicts by choosing a side mechanically. +- The executable `.mjs` authority belongs only to the fixed code-owned plugin bundled in the signed MakeLore client. Downloadable Marketplace artifacts retain the text/image-only P0 policy. +- Preserve `interactive_ai_app` as the canonical new type and `mini_game` / `mini_program` only as read compatibility aliases. +- Do not package, install, publish, deploy, push, or modify the unknown files in the primary `main` checkout. + +## Outcome + +- Merged the four-commit source chain ending at `ddb678b46ff10a28e04beeafcfc00c8c8a23ff9f` with a normal `--no-ff` merge at `2bc3e4420852388ce46841e05c1cbf49e80b250c`. +- Reconciled ADR-008 and the canonical architecture, domain, success-criteria, current-state, and history records to describe `makelore.project-scaffold` as a code-owned official bundled Marketplace plugin. +- Preserved the narrow execution boundary: the fixed bundled resource root may contain and run `scaffold.mjs`; downloadable Marketplace artifact ingestion remains text/image-only and rejects `.mjs`. +- Kept `interactive_ai_app` canonical while retaining `mini_game` and `mini_program` only as read-compatibility aliases. +- Excluded source-task-owned records from the integrated tree; they remain immutable on their source branches and reachable through the recorded source commits. + +## Verification + +- `validate_plugin.py resources/coding-plugins/project-scaffold` passed. +- `quick_validate.py resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold` passed. +- Focused Vitest integration set: 6 files, 88 tests passed. +- Scaffold Node test suite: 12 tests passed. +- `pnpm typecheck` passed. +- `git diff --check`, `check_project_docs.py`, and the task-aware documentation drift check passed after reconciliation. + +## Follow-ups + +- Promote this integration branch to `main` only after the unowned dirty primary checkout is made clean or explicitly adopted by its owner; this task did not touch those files. +- Packaging, deployment, pushing, and live Marketplace publication remain separate authorized operations. + +## Promotion Candidates + +- None. Accepted project-memory changes were applied directly under the Integration Gate. diff --git a/.project-docs/30-worklog/tasks/20260904-reconcile-project-scaffold-f7b4d2a9.md b/.project-docs/30-worklog/tasks/20260904-reconcile-project-scaffold-f7b4d2a9.md deleted file mode 100644 index ce73b4f..0000000 --- a/.project-docs/30-worklog/tasks/20260904-reconcile-project-scaffold-f7b4d2a9.md +++ /dev/null @@ -1,76 +0,0 @@ -# Task: Reconcile interactive AI app scaffold Skill - -## Identity - -- Task ID: 20260904-reconcile-project-scaffold-f7b4d2a9 -- Mode: Integration -- Branch: codex/20260904-integrate-project-scaffold-b6d3e8a1-integrate-project-scaffold -- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-integrate-project-scaffold-b6d3e8a1 -- Base commit: 4bc3e0ea5331a7d3f3576768c0119c02c0f0f952 -- Owner: codex -- Status: Ready for Integration - -## Scope - -- Promote the reviewed and already-merged scaffold/type behavior from base commit - `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952` into a client ADR, - canonical architecture/domain memory, current state, history, and commitments. -- Record one `interactive_ai_app` product type, explicit Scaffold Skill ownership, - executable Skill-script disclosure, and remaining installed-build release gates. - -## Intent And Constraints - -- Project creation owns only project metadata and `knowledge/`; the Skill must be - explicit and non-overwriting and may not duplicate build/upload/review authority. -- Historical types normalize without a batch rewrite; `custom` remains - non-publishable. -- Main retains fixed npm/Vite build/preflight/package/upload authority and Works - Square retains validation and review authority. -- Source task records/proposals and the primary checkout's unrelated files remain - untouched. - -## Outcome - -- Added ADR-008 and reconciled canonical brief, architecture, domain, current-state, - history, and release commitments with the reviewed source already present at the - task base. -- Established `interactive_ai_app` as the only new publishable product type and kept - `mini_game` / `mini_program` solely as non-rewriting historical read aliases. -- Recorded the three-owner boundary: Project Service creates metadata and `knowledge/`; - the explicit Device Package Skill creates the fixed starter and explains readiness; - Main/Works Square remain the sole build, upload, validation, Release, and approval - authorities. -- Recorded non-empty Skill `scripts/` as executable code requiring disclosure and later - confirmation, with `MAKELORE_NODE_EXECUTABLE` as the application-owned runtime path. -- Folded immutable distribution and installed Windows/signed macOS/native Linux proof - into the existing release commitments. No plugin publication, deployment, production - submission, approval, or remote push was performed. - -## Verification - -- Exact integration tree `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952`: - scaffold Node suite 12/12; focused project/device/publish Vitest suite 123/123; - `pnpm run typecheck`; scoped ESLint; and `pnpm run build:vite` all passed. -- Source task evidence retained: project-configuration Electron E2E 1/1, real - scaffold → fixed npm 11.6.2 → locked Vite build, plugin creator validation, - Skill creator validation, and real Device Package prepare → confirm → commit → - resource reads passed. -- `git diff --check`: passed. -- `check_project_docs.py --target .`: passed. -- `check_doc_drift.py --target . --task-id - 20260904-reconcile-project-scaffold-f7b4d2a9`: passed; only Integration-owned - canonical memory and this task record are changed. -- Canonical consistency scan found only intentional compatibility statements and - preserved historical task/current-state entries for the superseded dual-type model. - -## Follow-ups - -- Distribute the scaffold plugin from an immutable SemVer/Git source and run the - already-recorded installed-package proofs before claiming platform support. -- Deploy the matching server before the client, then run the real-account - create → scaffold → build → submit → approve → CDN/App playback chain. - -## Promotion Candidates - -- None. This Integration task promoted the accepted source candidates directly into - canonical project memory. diff --git a/.project-docs/40-domain/business-rules.md b/.project-docs/40-domain/business-rules.md index d08e4cd..a5ba2f2 100644 --- a/.project-docs/40-domain/business-rules.md +++ b/.project-docs/40-domain/business-rules.md @@ -63,8 +63,14 @@ unified `/plugins` workspace and never enter Account Library, Marketplace Package Store, Release, Channel, Admission, project enablement, Agent assignment, or server billing state. +- Code-owned official bundled Plugins may be `platform_hosted` or `skill_only`. + `makelore.project-scaffold` retains Account Library, project enablement, Agent + assignment, Release, and Admission state while its exact Skill/templates/`.mjs` + ship only in the signed client. Downloadable Marketplace artifacts remain P0 + text/image-only and must reject `.mjs`; official bundled authority is not inferred + from provider metadata or an uploaded ZIP. - 面向用户的 AI 编程新建流程必须在 `interactive_ai_app`(“交互式 AI 应用”)和 `custom` 中选择;`ProjectType` 是产品类型,创建后不能通过 UI 或 Host API 修改。历史 `mini_game` / `mini_program` 仅在读取边界归一为 `interactive_ai_app`,读取本身不改写配置;未传类型的兼容 API 调用按 `custom` 处理。 -- 新建项目只原子生成 `.makelore/project.json` 和 `knowledge/`。交互式 AI 应用的固定六文件 Vite 起步树只能由用户明确调用 `makelore-project-scaffold` Skill 生成;脚本必须先预检全部目标、不得覆盖已有路径,受控失败只回滚本次创建内容,且不得安装依赖、访问网络、构建、上传或提交审核。 +- 新建项目只原子生成 `.makelore/project.json` 和 `knowledge/`。交互式 AI 应用的固定六文件 Vite 起步树只能由用户明确调用官方 bundled `makelore.project-scaffold` Plugin 中的 `makelore-project-scaffold` Skill 生成;脚本必须先预检全部目标、不得覆盖已有路径,受控失败只回滚本次创建内容,且不得安装依赖、访问网络、构建、上传或提交审核。 - `ProjectType` 不等于 `BuildPreset` 或 Scaffold 状态:规范可发布类型映射到内部受控 Vite preset;本地 `projectType` 和 Skill 检测结果都不是授权边界,Main-owned 安全打包、Host API 和服务端包体校验仍必须执行。 - 非专业用户只执行一次“提交审核”;构建通过后由运营审核,审核通过即直接发布。 - 创建者发布唯一链路是项目配置“一键提交审核” → Main 本地 npm/Vite 构建 → 最终 built snapshot 的客户端 UX 预检 → source+built+artifact contract 上传 → 服务端校验/固化 → 运营审核;不恢复独立发布上传页、云部署工作台、Compose/deploy-check、自动 watcher/arm/upload 或手工 ZIP 入口。 diff --git a/.project-docs/40-domain/glossary.md b/.project-docs/40-domain/glossary.md index d9ae43f..471cd0b 100644 --- a/.project-docs/40-domain/glossary.md +++ b/.project-docs/40-domain/glossary.md @@ -4,7 +4,7 @@ |---|---|---| | `ProjectType` | 创建项目时选择且之后不可变的产品类型 | 规范值为 `interactive_ai_app`、`custom`;历史 `mini_game` / `mini_program` 读取时归一为前者,缺少字段按 `custom` 处理 | | `BuildPreset` | 平台内部用于构建和验收项目包的受控实现 | 不等于 `ProjectType` 或 Scaffold 状态;交互式 AI 应用使用固定 Vite preset | -| Project Scaffold Skill | 用户明确调用、独立版本化的起步文件生成与发布准备度指导 | 不覆盖路径、不安装/构建/上传/提审;项目创建服务和 Main/Works 发布权威不属于该 Skill | +| Project Scaffold Skill | 官方 bundled Marketplace Plugin `makelore.project-scaffold` 中由用户明确调用、独立版本化的起步文件生成与发布准备度指导 | `.mjs` 只来自固定客户端资源;不覆盖路径、不安装/构建/上传/提审;项目创建服务和 Main/Works 发布权威不属于该 Skill | | Main-owned Release Build | Electron Main 对安全源码快照执行固定 npm 与项目 lockfile Vite 的本地构建 | Vite config/plugins 以桌面用户权限执行,不是 sandbox;Renderer 不获得路径、归档或 origin | | Artifact Contract | 与 source/built 双归档一并上传的严格版本化清单 | 服务端把三者视为不可信输入并独立逐字节重算、校验 | | Built Artifact Preflight | 上传前对最终 built snapshot 的同一内存字节执行桌面/移动 Electron WebContents/CDP 检查 | 可绕过 UX fail-fast;无可信 receipt,不复刻生产 opaque-origin | From 9ed9bf4a715e300420e7a764a4cf576a6dfc4938 Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Fri, 4 Sep 2026 23:00:11 +0800 Subject: [PATCH 5/7] chore: land project scaffold plugin on main --- .project-docs/30-worklog/current-state.md | 4 +- .project-docs/30-worklog/task-history.md | 1 + ...ct-scaffold-integration-client-4f2a8c71.md | 50 ------------------- ...04-project-scaffold-main-merge-e7b4c291.md | 42 ++++++++++++++++ 4 files changed, 46 insertions(+), 51 deletions(-) delete mode 100644 .project-docs/30-worklog/tasks/20260904-project-scaffold-integration-client-4f2a8c71.md create mode 100644 .project-docs/30-worklog/tasks/20260904-project-scaffold-main-merge-e7b4c291.md diff --git a/.project-docs/30-worklog/current-state.md b/.project-docs/30-worklog/current-state.md index fa3cb5d..6b0c048 100644 --- a/.project-docs/30-worklog/current-state.md +++ b/.project-docs/30-worklog/current-state.md @@ -9,7 +9,9 @@ This file is the integrated default-branch snapshot. Feature tasks record progre `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952` and `2bc3e4420852388ce46841e05c1cbf49e80b250c`, then canonically reconciled by tasks `20260904-reconcile-project-scaffold-f7b4d2a9` and - `20260904-project-scaffold-integration-client-4f2a8c71`. New projects use one + `20260904-project-scaffold-integration-client-4f2a8c71`; verified integration head + `b92e7ba5bbde186626d93787ca358fbf4c2523a3` was landed on local `main` by task + `20260904-project-scaffold-main-merge-e7b4c291`. New projects use one `interactive_ai_app` / “交互式 AI 应用” type or `custom`; historical `mini_game` and `mini_program` values normalize at the read boundary without a batch rewrite. Project creation owns only `.makelore/project.json` and `knowledge/`. The official diff --git a/.project-docs/30-worklog/task-history.md b/.project-docs/30-worklog/task-history.md index 781ad84..4012c1e 100644 --- a/.project-docs/30-worklog/task-history.md +++ b/.project-docs/30-worklog/task-history.md @@ -4,6 +4,7 @@ | Date | Task | Outcome | Docs Updated | |---|---|---|---| +| 2026-09-04 | Land official Project Scaffold Plugin on local main | Fast-forwarded verified integration head `b92e7ba` onto local `main` while preserving three explicitly adopted historical task documents unchanged; no package, publication, deployment, or push was performed | current-state, landing task | | 2026-09-04 | Official Marketplace Project Scaffold integration | 将 `makelore.project-scaffold` 作为代码所有、随签名客户端交付的 bundled `skill_only` Plugin 接入 Account Library/项目启用/Agent 分配;官方 `.mjs` 不经过下载 artifact,下载包仍保持文本/图片 P0 限制 | ADR-008、current-state、architecture、domain、success criteria、integration task | | 2026-09-04 | 交互式 AI 应用与显式 Scaffold Skill 集成 | 合并小游戏/小程序为规范 `interactive_ai_app`,项目创建保持最小边界,独立 Skill 生成固定起步树并说明发布要求;Main/Works 继续拥有发布权威 | ADR-008、current-state、architecture、domain、commitments | | 2026-08-07 | 客户端登录七天滑动续期 | Main-owned 刷新凭据与真实活动续期 | README、current-state | diff --git a/.project-docs/30-worklog/tasks/20260904-project-scaffold-integration-client-4f2a8c71.md b/.project-docs/30-worklog/tasks/20260904-project-scaffold-integration-client-4f2a8c71.md deleted file mode 100644 index 95d925d..0000000 --- a/.project-docs/30-worklog/tasks/20260904-project-scaffold-integration-client-4f2a8c71.md +++ /dev/null @@ -1,50 +0,0 @@ -# Task: Integrate official project scaffold plugin - -## Identity - -- Task ID: 20260904-project-scaffold-integration-client-4f2a8c71 -- Mode: Integration -- Branch: codex/20260904-project-scaffold-integration-client-4f2a8c71-project-scaffold-integration-client -- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260904-project-scaffold-integration-client-4f2a8c71 -- Base commit: 336e0bb0caf24537b7b0f350aba3e04a37f5544c -- Owner: codex -- Status: Ready for Integration - -## Scope - -- Integrate the four-commit interactive-AI-application scaffold chain ending at `ddb678b46ff10a28e04beeafcfc00c8c8a23ff9f` onto the committed `main` frontier `336e0bb0caf24537b7b0f350aba3e04a37f5544c`. -- Reconcile ADR-008 and the canonical architecture/current-state records with the final official Marketplace delivery model. -- Verify the merged client tree and prepare it for safe default-branch promotion without touching the unowned dirty primary checkout. - -## Intent And Constraints - -- Preserve normal Git ancestry; do not rebase, squash, rewrite history, or resolve semantic conflicts by choosing a side mechanically. -- The executable `.mjs` authority belongs only to the fixed code-owned plugin bundled in the signed MakeLore client. Downloadable Marketplace artifacts retain the text/image-only P0 policy. -- Preserve `interactive_ai_app` as the canonical new type and `mini_game` / `mini_program` only as read compatibility aliases. -- Do not package, install, publish, deploy, push, or modify the unknown files in the primary `main` checkout. - -## Outcome - -- Merged the four-commit source chain ending at `ddb678b46ff10a28e04beeafcfc00c8c8a23ff9f` with a normal `--no-ff` merge at `2bc3e4420852388ce46841e05c1cbf49e80b250c`. -- Reconciled ADR-008 and the canonical architecture, domain, success-criteria, current-state, and history records to describe `makelore.project-scaffold` as a code-owned official bundled Marketplace plugin. -- Preserved the narrow execution boundary: the fixed bundled resource root may contain and run `scaffold.mjs`; downloadable Marketplace artifact ingestion remains text/image-only and rejects `.mjs`. -- Kept `interactive_ai_app` canonical while retaining `mini_game` and `mini_program` only as read-compatibility aliases. -- Excluded source-task-owned records from the integrated tree; they remain immutable on their source branches and reachable through the recorded source commits. - -## Verification - -- `validate_plugin.py resources/coding-plugins/project-scaffold` passed. -- `quick_validate.py resources/coding-plugins/project-scaffold/skills/makelore-project-scaffold` passed. -- Focused Vitest integration set: 6 files, 88 tests passed. -- Scaffold Node test suite: 12 tests passed. -- `pnpm typecheck` passed. -- `git diff --check`, `check_project_docs.py`, and the task-aware documentation drift check passed after reconciliation. - -## Follow-ups - -- Promote this integration branch to `main` only after the unowned dirty primary checkout is made clean or explicitly adopted by its owner; this task did not touch those files. -- Packaging, deployment, pushing, and live Marketplace publication remain separate authorized operations. - -## Promotion Candidates - -- None. Accepted project-memory changes were applied directly under the Integration Gate. diff --git a/.project-docs/30-worklog/tasks/20260904-project-scaffold-main-merge-e7b4c291.md b/.project-docs/30-worklog/tasks/20260904-project-scaffold-main-merge-e7b4c291.md new file mode 100644 index 0000000..51b812c --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260904-project-scaffold-main-merge-e7b4c291.md @@ -0,0 +1,42 @@ +# Task: Land official project scaffold plugin on main + +## Identity + +- Task ID: 20260904-project-scaffold-main-merge-e7b4c291 +- Mode: Integration +- Branch: main +- Worktree: D:\Datas\OthersProjects\makelore +- Base commit: 336e0bb0caf24537b7b0f350aba3e04a37f5544c +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Fast-forward the verified integration commit `b92e7ba5bbde186626d93787ca358fbf4c2523a3` onto local `main`. +- Record final default-branch landing evidence while preserving the three explicitly adopted historical task documents unchanged and uncommitted. + +## Intent And Constraints + +- Preserve the accepted official bundled-plugin boundary: only the fixed code-owned resource root may execute `scaffold.mjs`; downloadable Marketplace artifacts continue to reject scripts. +- Preserve normal ancestry and do not package, publish, deploy, push, or alter the adopted historical task documents. + +## Outcome + +- Fast-forwarded local `main` from `336e0bb0caf24537b7b0f350aba3e04a37f5544c` to verified integration head `b92e7ba5bbde186626d93787ca358fbf4c2523a3`. +- Local `main` now contains the unified `interactive_ai_app` type and the official bundled Marketplace Plugin `makelore.project-scaffold`, including its Skill, templates, publication guidance, and `scaffold.mjs`. +- Preserved the three explicitly adopted historical task documents byte-for-byte and left them uncommitted. +- Excluded the completed source integration task record from the final default-branch tree; it remains reachable on its preserved source branch and commit. + +## Verification + +- The fast-forward completed without conflicts and `b92e7ba5bbde186626d93787ca358fbf4c2523a3` is an ancestor of local `main`. +- The exact landed integration head had already passed Plugin and Skill validators, 88 focused Vitest tests, 12 scaffold Node tests, TypeScript typecheck, Git whitespace checks, and project-document gates. +- The task-aware drift check passed while confirming all adopted historical documents remained unchanged. + +## Follow-ups + +- Packaging, installed-client smoke, live Marketplace publication, deployment, and remote push remain separate authorized operations. + +## Promotion Candidates + +- None. This task landed the already accepted and verified integration result. From 6d102b2c6d8af16640f9a2136f39788fce25c1bd Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Sat, 5 Sep 2026 10:41:05 +0800 Subject: [PATCH 6/7] fix: identify bundled official plugins in workspace --- ...0260905-plugin-download-action-6c8e4a21.md | 69 +++++++++++++++++++ src/pages/Plugins/PluginDetails.tsx | 8 ++- src/pages/Plugins/index.tsx | 4 +- src/pages/Plugins/plugin-workspace-model.ts | 5 ++ tests/unit/plugins-page.test.tsx | 39 +++++++++++ 5 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260905-plugin-download-action-6c8e4a21.md diff --git a/.project-docs/30-worklog/tasks/20260905-plugin-download-action-6c8e4a21.md b/.project-docs/30-worklog/tasks/20260905-plugin-download-action-6c8e4a21.md new file mode 100644 index 0000000..3b244a0 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260905-plugin-download-action-6c8e4a21.md @@ -0,0 +1,69 @@ +# Task: Fix missing official plugin acquire and download action + +## Identity + +- Task ID: 20260905-plugin-download-action-6c8e4a21 +- Mode: Feature +- Branch: codex/20260905-plugin-download-action-6c8e4a21-plugin-download-action +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260905-plugin-download-action-6c8e4a21 +- Base commit: 9ed9bf4a715e300420e7a764a4cf576a6dfc4938 +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Reproduce and fix the unified `/plugins` projection for the code-owned bundled + `makelore.project-scaffold` Plugin when the Account Library has not acquired it. +- Keep the existing acquisition, device-package, project-enablement, and Agent + assignment authorities separate; cover the public Renderer seam with a focused + regression test. +- Limit product changes to the unified Plugin workspace projection/presentation and + its focused tests unless the reproduction proves the fault is owned elsewhere. + +## Intent And Constraints + +- A bundled Project Scaffold Plugin never has a device download/update/uninstall + action. Before acquisition it must offer the existing free Account Library acquire + action; after acquisition it may offer project enablement and Agent assignment. +- Do not add a visible local package/Skill installation picker, change server/Main + contracts, or collapse Account Library acquisition into device installation. +- Preserve the occupied client root worktree and its three adopted untracked task + records exactly. Work only in this isolated feature worktree. +- Concurrent Task Gate: Passed. Planning Gate: Passed after loading the current + integrated state, ADR-008, Plugin business rules/module boundaries, and the completed + root Project Scaffold integration task. No active product writer overlaps this scope. + +## Outcome + +- Added one shared projection predicate for code-owned bundled official Plugins and + used it on both the unified Plugin card and detail dialog. An unacquired bundled + Project Scaffold now reports `随应用提供` rather than the false + `未下载官方包` state. +- Preserved the existing authority boundary: the client does not synthesize a + Marketplace catalog entry or acquisition action. The existing `免费获取` action + still appears only when the server catalog contains the Plugin and the authenticated + Account Library snapshot confirms it is not acquired. + +## Verification + +- TDD red: the new public Renderer regression failed because both card and detail + reported the bundled Plugin as not downloaded. +- Focused green: `plugins-page` plus `plugin-workspace-model` = 35 passed. +- `corepack pnpm run typecheck`: passed. +- Scoped ESLint on the three changed product files and focused test: passed. +- `corepack pnpm run build:vite`: passed for Renderer, Main, Preload, and utility + targets with existing size/import warnings. +- Full unit run: 225 files / 1887 passed / 2 skipped, with one unrelated real-process + timing threshold failure (`2657ms < 2000ms`). The exact failed file then passed 6/6 + in isolation; the separately skipped pressure suite passed 1/1. +- `git diff --check`: passed. + +## Follow-ups + +- Deploy the current Works Square server revision and migrate production through + `20260904_project_scaffold_0085`; until then the live catalog has no authority to + offer `免费获取` for `makelore.project-scaffold`. + +## Promotion Candidates + +- None recorded. diff --git a/src/pages/Plugins/PluginDetails.tsx b/src/pages/Plugins/PluginDetails.tsx index 9b77a2e..477d2a8 100644 --- a/src/pages/Plugins/PluginDetails.tsx +++ b/src/pages/Plugins/PluginDetails.tsx @@ -12,7 +12,11 @@ import { DialogTitle, } from '@/components/ui/dialog'; import type { DataServiceInstanceState } from '../../../shared/data-service'; -import type { PluginWorkspaceCommand, PluginWorkspaceItem } from './plugin-workspace-model'; +import { + isBundledOfficialPlugin, + type PluginWorkspaceCommand, + type PluginWorkspaceItem, +} from './plugin-workspace-model'; interface DetailOperation { capabilityId: string; @@ -286,7 +290,7 @@ export function PluginDetails(props: PluginDetailsProps) {
{props.item.source === 'local' ? (props.item.localEnabled ? '本机全局已启用' : '本机全局已停用') - : installation?.version ? `官方包 ${installation.version}` : props.item.delivery === 'system_included' ? '随应用提供' : '未下载官方包'} + : installation?.version ? `官方包 ${installation.version}` : isBundledOfficialPlugin(props.item) ? '随应用提供' : '未下载官方包'}
diff --git a/src/pages/Plugins/index.tsx b/src/pages/Plugins/index.tsx index 7ba6577..ec742fc 100644 --- a/src/pages/Plugins/index.tsx +++ b/src/pages/Plugins/index.tsx @@ -21,7 +21,7 @@ import type { PluginWorkspaceProjection, PluginWorkspaceState, } from './plugin-workspace-model'; -import { buildPluginWorkspaceProjection } from './plugin-workspace-model'; +import { buildPluginWorkspaceProjection, isBundledOfficialPlugin } from './plugin-workspace-model'; import { resolvePluginWorkspaceSearch, serializePluginWorkspaceSearch } from './plugin-workspace-query'; const DELIVERY_TEXT = { @@ -111,7 +111,7 @@ function PluginCard({

{item.summary}

当前项目
{PROJECT_TEXT[item.projectState]}
-
本机状态
{item.source === 'local' ? (item.localEnabled ? '本机全局已启用' : '本机全局已停用') : item.official?.installation?.version ? `官方包 ${item.official.installation.version}` : item.delivery === 'system_included' ? '随应用提供' : '未下载官方包'}
+
本机状态
{item.source === 'local' ? (item.localEnabled ? '本机全局已启用' : '本机全局已停用') : item.official?.installation?.version ? `官方包 ${item.official.installation.version}` : isBundledOfficialPlugin(item) ? '随应用提供' : '未下载官方包'}
伙伴
{agentText(item)}
计费
{BILLING_TEXT[item.billing]}
diff --git a/src/pages/Plugins/plugin-workspace-model.ts b/src/pages/Plugins/plugin-workspace-model.ts index d28d984..0525b45 100644 --- a/src/pages/Plugins/plugin-workspace-model.ts +++ b/src/pages/Plugins/plugin-workspace-model.ts @@ -104,6 +104,11 @@ export interface PluginWorkspaceItem { local: DevicePackageRecordV1 | null; } +export function isBundledOfficialPlugin(item: PluginWorkspaceItem): boolean { + return item.source === 'official' + && (item.delivery === 'system_included' || isCodeOwnedOptionalBundledPluginId(item.pluginId)); +} + export type PluginWorkspaceNotice = | { kind: 'scope_fallback'; message: string } | { kind: 'source_unavailable'; source: 'catalog' | 'library' | 'device' | 'project'; message: string } diff --git a/tests/unit/plugins-page.test.tsx b/tests/unit/plugins-page.test.tsx index dced718..5b31b5f 100644 --- a/tests/unit/plugins-page.test.tsx +++ b/tests/unit/plugins-page.test.tsx @@ -449,6 +449,45 @@ describe('PluginsView', () => { expect(view.onFiltersChange).toHaveBeenCalledWith(expect.objectContaining({ search: 'web' })); }); + it('labels an unacquired code-owned bundled plugin as supplied by MakeLore instead of not downloaded', () => { + const scaffoldItem: PluginWorkspaceItem = { + ...officialItem, + key: 'official:makelore.project-scaffold', + pluginId: 'makelore.project-scaffold', + title: 'MakeLore 项目脚手架', + delivery: 'not_acquired', + projectState: 'disabled', + assignedAgentIds: [], + assignedAgentNames: [], + commands: [], + official: { + catalog: null, + detail: null, + library: null, + installation: null, + project: { + ...projectPlugin, + id: 'makelore.project-scaffold', + displayName: 'MakeLore 项目脚手架', + enabled: false, + state: 'disabled', + }, + }, + }; + const view = props(scaffoldItem); + view.projection = { + items: [scaffoldItem], + selected: scaffoldItem, + counts: { all: 1, available: 0, mine: 0, enabled: 0, update: 0, unavailable: 0 }, + notices: [], + }; + + render(); + + expect(screen.getAllByText('随应用提供')).toHaveLength(2); + expect(screen.queryByText('未下载官方包')).not.toBeInTheDocument(); + }); + it('shows one dialog detail surface, confirms destructive commands, and embeds Data Service settings', () => { const view = props(officialItem); render(); From d642d7607c26dee01ef65b4e70dd756465dea16a Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Sat, 5 Sep 2026 10:46:00 +0800 Subject: [PATCH 7/7] docs: reconcile bundled plugin status fix --- .project-docs/30-worklog/current-state.md | 15 ++++ ...gin-bundled-status-integration-8f2c5a41.md | 60 ++++++++++++++++ ...0260905-plugin-download-action-6c8e4a21.md | 69 ------------------- 3 files changed, 75 insertions(+), 69 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260905-plugin-bundled-status-integration-8f2c5a41.md delete mode 100644 .project-docs/30-worklog/tasks/20260905-plugin-download-action-6c8e4a21.md diff --git a/.project-docs/30-worklog/current-state.md b/.project-docs/30-worklog/current-state.md index 6b0c048..092ea6b 100644 --- a/.project-docs/30-worklog/current-state.md +++ b/.project-docs/30-worklog/current-state.md @@ -4,6 +4,21 @@ This file is the integrated default-branch snapshot. Feature tasks record progre ## Integrated Through +- Unified Plugin workspace bundled-delivery fix source + `6bd9287c879dca93da11e17533f84e3535fc656a` from task + `20260905-plugin-download-action-6c8e4a21` is integrated onto local `main` as + `6d102b2`. Code-owned bundled official Plugins, including + `makelore.project-scaffold`, now show `随应用提供` when no device-package record + exists instead of falsely showing `未下载官方包`. This is presentation-only: + bundled Plugins still have no device download/update/uninstall flow, and the existing + server-authoritative `免费获取` action remains required before project enablement. + Focused Plugin tests, typecheck, scoped lint, and all Vite targets passed; the ordinary + full unit run had one unrelated Pi real-process two-second timing miss among 1,887 + passing tests and 2 skips, and that exact test passed 6/6 in isolation with the + pressure suite passing 1/1. The production Marketplace currently returns + `404 plugin_not_found` for Project Scaffold and still requires deployment of the + current server plus migration through `20260904_project_scaffold_0085`; no deployment + or production database change is claimed here. - Interactive AI application source `959b633` and official Marketplace packaging source `ddb678b46ff10a28e04beeafcfc00c8c8a23ff9f` are integrated through merges `4bc3e0ea5331a7d3f3576768c0119c02c0f0f952` and diff --git a/.project-docs/30-worklog/tasks/20260905-plugin-bundled-status-integration-8f2c5a41.md b/.project-docs/30-worklog/tasks/20260905-plugin-bundled-status-integration-8f2c5a41.md new file mode 100644 index 0000000..cc189b8 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260905-plugin-bundled-status-integration-8f2c5a41.md @@ -0,0 +1,60 @@ +# Task: Integrate bundled official Plugin device-status fix + +## Identity + +- Task ID: 20260905-plugin-bundled-status-integration-8f2c5a41 +- Mode: Integration +- Branch: main +- Worktree: D:\Datas\OthersProjects\makelore +- Base commit: 9ed9bf4a715e300420e7a764a4cf576a6dfc4938 +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Integrate feature commit `6bd9287c879dca93da11e17533f84e3535fc656a` + onto local `main` from its exact parent `9ed9bf4a715e300420e7a764a4cf576a6dfc4938`. +- Reconcile the source outcome into current state and remove only the duplicate source + task record from the default-branch checkpoint. + +## Intent And Constraints + +- Concurrent Task Gate: Passed after releasing the completed prior integration lock and + claiming this exact root/main worktree in Integration mode. +- Planning Gate: Passed after reading the source task outcome, ADR-008, current Plugin + architecture/business rules, current state, and the prior scaffold landing record. +- Preserve the three explicitly adopted historical task records byte-for-byte and + uncommitted; do not stash, reset, clean, move, or delete them. +- Preserve the accepted boundary: Project Scaffold ships in the signed client, has no + device download action, and still requires server-authoritative free account + acquisition before project enablement. + +## Outcome + +- Cherry-picked feature source `6bd9287c879dca93da11e17533f84e3535fc656a` + onto local `main` as product commit `6d102b2` without conflict. +- Removed only the duplicate source task record from the default-branch documentation + checkpoint; it remains intact on the source branch and commit. +- Updated current state with the accepted presentation correction and the separate + production server deployment/migration requirement. +- Preserved all three adopted historical task records byte-for-byte and uncommitted. + +## Verification + +- Source evidence: focused Plugin page/model 35 passed; typecheck and scoped ESLint + passed; all Vite targets passed; isolated Pi timing rerun 6/6 and pressure 1/1 passed. +- Source commit and landed product commit have the exact same tree + `1976d2f27f239dead6d26f99131182c7c657a9db`. +- Root replay was not claimed because the root checkout has no installed Vitest or + TypeScript executables; no dependency mutation was performed there. +- Local server evidence remains separate: Project Scaffold Marketplace tests 2/2 and + sole migration head `20260904_project_scaffold_0085`. + +## Follow-ups + +- Deploy the current Works Square server and migrate production through 0085 so the + public catalog can authorize and expose `免费获取`. + +## Promotion Candidates + +- None recorded. diff --git a/.project-docs/30-worklog/tasks/20260905-plugin-download-action-6c8e4a21.md b/.project-docs/30-worklog/tasks/20260905-plugin-download-action-6c8e4a21.md deleted file mode 100644 index 3b244a0..0000000 --- a/.project-docs/30-worklog/tasks/20260905-plugin-download-action-6c8e4a21.md +++ /dev/null @@ -1,69 +0,0 @@ -# Task: Fix missing official plugin acquire and download action - -## Identity - -- Task ID: 20260905-plugin-download-action-6c8e4a21 -- Mode: Feature -- Branch: codex/20260905-plugin-download-action-6c8e4a21-plugin-download-action -- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260905-plugin-download-action-6c8e4a21 -- Base commit: 9ed9bf4a715e300420e7a764a4cf576a6dfc4938 -- Owner: codex-root -- Status: Ready for Integration - -## Scope - -- Reproduce and fix the unified `/plugins` projection for the code-owned bundled - `makelore.project-scaffold` Plugin when the Account Library has not acquired it. -- Keep the existing acquisition, device-package, project-enablement, and Agent - assignment authorities separate; cover the public Renderer seam with a focused - regression test. -- Limit product changes to the unified Plugin workspace projection/presentation and - its focused tests unless the reproduction proves the fault is owned elsewhere. - -## Intent And Constraints - -- A bundled Project Scaffold Plugin never has a device download/update/uninstall - action. Before acquisition it must offer the existing free Account Library acquire - action; after acquisition it may offer project enablement and Agent assignment. -- Do not add a visible local package/Skill installation picker, change server/Main - contracts, or collapse Account Library acquisition into device installation. -- Preserve the occupied client root worktree and its three adopted untracked task - records exactly. Work only in this isolated feature worktree. -- Concurrent Task Gate: Passed. Planning Gate: Passed after loading the current - integrated state, ADR-008, Plugin business rules/module boundaries, and the completed - root Project Scaffold integration task. No active product writer overlaps this scope. - -## Outcome - -- Added one shared projection predicate for code-owned bundled official Plugins and - used it on both the unified Plugin card and detail dialog. An unacquired bundled - Project Scaffold now reports `随应用提供` rather than the false - `未下载官方包` state. -- Preserved the existing authority boundary: the client does not synthesize a - Marketplace catalog entry or acquisition action. The existing `免费获取` action - still appears only when the server catalog contains the Plugin and the authenticated - Account Library snapshot confirms it is not acquired. - -## Verification - -- TDD red: the new public Renderer regression failed because both card and detail - reported the bundled Plugin as not downloaded. -- Focused green: `plugins-page` plus `plugin-workspace-model` = 35 passed. -- `corepack pnpm run typecheck`: passed. -- Scoped ESLint on the three changed product files and focused test: passed. -- `corepack pnpm run build:vite`: passed for Renderer, Main, Preload, and utility - targets with existing size/import warnings. -- Full unit run: 225 files / 1887 passed / 2 skipped, with one unrelated real-process - timing threshold failure (`2657ms < 2000ms`). The exact failed file then passed 6/6 - in isolation; the separately skipped pressure suite passed 1/1. -- `git diff --check`: passed. - -## Follow-ups - -- Deploy the current Works Square server revision and migrate production through - `20260904_project_scaffold_0085`; until then the live catalog has no authority to - offer `免费获取` for `makelore.project-scaffold`. - -## Promotion Candidates - -- None recorded.