diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9d3358403..8a4b2d0c6 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,12 +1,432 @@ +const globals = require('globals'); + module.exports = { root: true, - ignorePatterns: ['ignored/**'], + env: { + es2022: true, + }, parser: '@typescript-eslint/parser', - parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, - plugins: ['simple-import-sort', 'react-hooks', 'react-refresh'], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.browser, + ...globals.node, + }, + overrides: [ + { + files: ['scripts/**/*.{ts,js,mjs,cjs}'], + rules: { + 'no-console': 'off', + }, + }, + { + files: ['**/scripts/**/*.{ts,js,mjs,cjs}'], + rules: { + 'no-console': 'off', + }, + }, + { + files: ['**/*.test.{ts,tsx,js,mjs,cjs}'], + rules: { + 'no-console': 'off', + 'unused-imports/no-unused-vars': 'off', + }, + }, + { + files: ['miniprogram/**/*.js'], + globals: { + App: 'readonly', + }, + }, + { + files: [ + 'apps/admin-web/src/pages/*.tsx', + 'src/components/platform-entry/PlatformActiveMobileWelcomeDialog.tsx', + 'src/components/platform-entry/PlatformMobileHomeWelcomeDialog.tsx', + ], + rules: { + 'react-refresh/only-export-components': 'off', + }, + }, + { + files: ['src/components/platform-entry/PlatformEntryFlowShellImpl.tsx'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, + { + files: [ + 'src/active-main.tsx', + 'src/ActiveApp.tsx', + 'src/AuthenticatedApp.tsx', + 'src/hooks/useGameSettings.ts', + 'src/routing/activeApp*.ts', + 'src/routing/activeApp*.tsx', + 'src/components/auth/**/*.{ts,tsx}', + 'src/components/common/**/*.{ts,tsx}', + 'src/components/creation-home/**/*.{ts,tsx}', + 'src/components/image-editor/**/*.{ts,tsx}', + 'src/components/project/**/*.{ts,tsx}', + 'src/components/platform-entry/PlatformActiveProfileView*.tsx', + 'src/components/platform-entry/PlatformActiveMobileWelcomeDialog*.tsx', + 'src/components/platform-entry/PlatformEntryActiveFlowShell*.tsx', + 'src/components/platform-entry/PlatformEntryFlowShell.tsx', + 'src/components/platform-entry/PlatformProfileApiKeysModal.tsx', + 'src/components/platform-entry/PlatformProfileModalShell.tsx', + 'src/components/platform-entry/PlatformProfilePrimitives.tsx', + 'src/components/platform-entry/PlatformProfileRechargeModal.tsx', + 'src/components/platform-entry/PlatformProfileReferralModal.tsx', + 'src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.tsx', + 'src/components/platform-entry/PlatformProfileWalletLedgerModal.tsx', + 'src/components/platform-entry/PlatformRechargePaymentStatusDialogs.tsx', + 'src/components/platform-entry/platformActiveProfileModel.ts', + 'src/components/platform-entry/platformEntryActiveTypes.ts', + 'src/components/platform-entry/platformProfileFundsModel.ts', + 'src/components/platform-entry/platformProfileHostClipboard.ts', + 'src/components/platform-entry/usePlatformProfileCenterController.ts', + 'src/services/image-editor/**/*.{ts,tsx}', + 'src/services/platform-entry/**/*.{ts,tsx}', + ], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: [ + '**/components/rpg-entry/**', + '**/components/*-creation/**', + '**/components/*-result/**', + '**/components/*-runtime/**', + '**/components/creation-agent/**', + '**/components/creative-agent/**', + '**/components/custom-world-*/**', + '**/components/unified-creation/**', + '**/services/rpg-entry/**', + '**/services/rpg-runtime/**', + '**/services/*-creation/**', + '**/services/*-runtime/**', + '**/services/*-works/**', + '**/services/creation-agent/**', + '**/services/creative-agent/**', + '**/services/puzzle-*/**', + '**/services/storyEngine/**', + ], + message: '现役前端不得重新导入已退役的创作模板模块。', + }, + ], + }, + ], + }, + }, + { + files: ['src/components/game-canvas/**/*.tsx'], + rules: { + 'react-refresh/only-export-components': 'off', + }, + }, + { + files: ['src/components/GameShell.tsx', 'src/hooks/useCombatFlow.ts'], + rules: { + 'simple-import-sort/imports': 'off', + 'simple-import-sort/exports': 'off', + }, + }, + ], + plugins: [ + '@typescript-eslint', + 'react-hooks', + 'react-refresh', + 'simple-import-sort', + 'unused-imports', + ], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + 'prettier', + ], + ignorePatterns: [ + 'dist', + 'dist_check', + 'dist_check_monster_position', + 'coverage', + 'build', + 'output', + 'retired/**', + 'node_modules', + 'server-rs/target', + 'server-rs/target-*', + 'apps/desktop-shell/src-tauri/target', + 'apps/ai-game-creator-shell/src/features/ui-editor/types/**', + 'apps/ai-game-creator-shell/src/features/project-workspace/generated/**', + 'target', + 'src/main.tsx', + 'src/App.tsx', + 'src/routing/appPageRoutes.ts', + 'src/routing/appPageRoutes.test.ts', + 'src/routing/appRoutes.tsx', + 'src/routing/appRoutes.test.ts', + 'src/services/appTitle.ts', + 'src/services/appTitle.test.ts', + 'src/components/CharacterAnimator.tsx', + 'src/components/*.ts', + 'src/components/*.tsx', + '!src/components/ResolvedAssetAudio.tsx', + '!src/components/ResolvedAssetImage.tsx', + '!src/components/ResolvedAssetVideo.tsx', + 'src/hooks/*.ts', + 'src/hooks/*.tsx', + '!src/hooks/useGameSettings.ts', + '!src/hooks/useHostNavigationCanGoBack.ts', + '!src/hooks/useHostNavigationCanGoBack.test.tsx', + '!src/hooks/useResolvedAssetReadUrl.ts', + '!src/hooks/useResolvedAssetReadUrl.test.tsx', + 'src/persistence/*.ts', + 'src/persistence/*.tsx', + '!src/persistence/gameSettingsStorage.ts', + '!src/persistence/gameSettingsStorage.test.ts', + '!src/persistence/storage.ts', + '!src/persistence/storage.test.ts', + 'src/routing/*.ts', + 'src/routing/*.tsx', + '!src/routing/activeAppPageRoutes.ts', + '!src/routing/activeAppPageRoutes.test.ts', + '!src/routing/activeAppRoutes.tsx', + '!src/routing/activeAppRoutes.test.ts', + 'src/services/*.ts', + 'src/services/*.tsx', + '!src/services/activeAppTitle.ts', + '!src/services/activeAppTitle.test.ts', + '!src/services/apiClient.ts', + '!src/services/apiClient.test.ts', + '!src/services/assetReadUrlService.ts', + '!src/services/assetReadUrlService.test.ts', + '!src/services/authService.ts', + '!src/services/authService.test.ts', + '!src/services/clipboard.ts', + '!src/services/clipboard.test.ts', + '!src/services/frontendRuntimeConfigService.ts', + '!src/services/frontendRuntimeConfigService.test.ts', + '!src/services/sseStream.ts', + '!src/services/sseStream.test.ts', + 'src/AdventurePanel.tsx', + 'src/AdventureEntityModal.tsx', + 'src/App.tsx', + 'src/App.test.tsx', + 'src/CharacterCreation.tsx', + 'src/RpgRuntimeApp.tsx', + 'src/routing/appPageRoutes.ts', + 'src/routing/appPageRoutes.test.ts', + 'src/routing/appRoutes.tsx', + 'src/routing/appRoutes.test.ts', + 'src/*PlaygroundApp.tsx', + 'src/ChildMotionDemoApp.tsx', + 'src/Match3DPlaygroundApp.tsx', + 'src/components/child-motion-demo/**', + 'src/components/asset-studio/**', + 'src/components/bark-battle-creation/**', + 'src/components/big-fish-creation/**', + 'src/components/big-fish-result/**', + 'src/components/big-fish-runtime/**', + 'src/components/creation-agent/**', + 'src/components/creative-agent/**', + 'src/components/custom-world-agent/**', + 'src/components/custom-world-home/**', + 'src/components/edutainment-creation/**', + 'src/components/edutainment-result/**', + 'src/components/edutainment-runtime/**', + 'src/components/game-canvas/**', + 'src/components/jump-hop-result/**', + 'src/components/jump-hop-runtime/**', + 'src/components/match3d-result/**', + 'src/components/match3d-runtime/**', + 'src/components/puzzle-clear-creation/**', + 'src/components/puzzle-clear-result/**', + 'src/components/puzzle-clear-runtime/**', + 'src/components/puzzle-gallery/**', + 'src/components/puzzle-result/**', + 'src/components/puzzle-runtime/**', + 'src/components/rpg-creation-asset-studio/**', + 'src/components/rpg-creation-result/**', + 'src/components/rpg-runtime-panels/**', + 'src/components/square-hole-creation/**', + 'src/components/square-hole-result/**', + 'src/components/square-hole-runtime/**', + 'src/components/unified-creation/**', + 'src/components/visual-novel-creation/**', + 'src/components/visual-novel-result/**', + 'src/components/visual-novel-runtime/**', + 'src/components/wooden-fish-result/**', + 'src/components/wooden-fish-runtime/**', + 'src/components/common/PublishShare*', + 'src/components/common/publishShare*', + 'src/components/platform-entry/PlatformEntryCreation*', + 'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx', + 'src/components/platform-entry/platformEntryTypes.ts', + 'src/components/platform-entry/PlatformEntryHome*', + 'src/components/platform-entry/PlatformEntryWorld*', + 'src/components/platform-entry/PlatformMobileHome*', + 'src/components/platform-entry/PlatformWork*', + 'src/components/platform-entry/PlatformDraft*', + 'src/components/platform-entry/PlatformTask*', + 'src/components/platform-entry/PlatformError*', + 'src/components/platform-entry/barkBattle*', + 'src/components/platform-entry/platformCreation*', + 'src/components/platform-entry/platformDialog*', + 'src/components/platform-entry/platformDraft*', + 'src/components/platform-entry/platformEdutainment*', + 'src/components/platform-entry/platformEntryCreation*', + 'src/components/platform-entry/platformExternal*', + 'src/components/platform-entry/platformGeneration*', + 'src/components/platform-entry/platformHost*', + 'src/components/platform-entry/platformMiniGame*', + 'src/components/platform-entry/platformPlayed*', + 'src/components/platform-entry/platformPublic*', + 'src/components/platform-entry/platformPuzzle*', + 'src/components/platform-entry/platformRecommend*', + 'src/components/platform-entry/platformRpg*', + 'src/components/platform-entry/platformSelection*', + 'src/components/platform-entry/puzzleDraft*', + 'src/components/platform-entry/usePlatformCreation*', + 'src/components/platform-entry/usePlatformEntry*', + 'src/components/platform-entry/PlatformEntryFlowShellImpl/**', + 'src/components/platform-entry/platformMatch3DRuntimeProfile*', + 'src/components/unified-creation/workspaces/JumpHopCreationWorkspace*', + 'src/components/unified-creation/workspaces/Match3DCreationWorkspace*', + 'src/components/rpg-creation-editor/**', + 'src/components/rpg-entry/**', + 'src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx', + 'src/components/custom-world-home/CustomWorldCreationHub.test.tsx', + 'src/components/custom-world-home/CustomWorldCreationHub.testAdapter.tsx', + 'src/components/custom-world-home/creationWorkShelf.test.ts', + 'src/components/rpg-runtime-shell/**', + 'src/hooks/rpg-runtime-story/**', + 'src/hooks/rpg-session/**', + 'src/hooks/combat/**', + 'src/hooks/useCombatFlow.ts', + 'src/hooks/useStoryOptions.ts', + 'src/prompts/customWorldPrompts.ts', + 'src/services/ai.ts', + 'src/services/appTitle.ts', + 'src/services/appTitle.test.ts', + 'src/services/miniGameDraftGenerationProgress.ts', + 'src/services/creationEntryConfigService.ts', + 'src/services/creationUrlState*', + 'src/services/customWorld*', + 'src/services/input-devices/**', + 'src/services/publicWorkCode.ts', + 'src/services/runtimeGuestAuth.ts', + 'src/services/runtimeRequest*', + 'src/services/runtimeAudioFeedback.ts', + 'src/services/useMocapInput*', + 'src/services/wechatMiniProgramSubscribe*', + 'src/services/bark-battle-creation/**', + 'src/services/bark-battle-runtime/**', + 'src/services/big-fish-creation/**', + 'src/services/big-fish-gallery/**', + 'src/services/big-fish-runtime/**', + 'src/services/big-fish-works/**', + 'src/services/creation-agent/**', + 'src/services/creation-audio/**', + 'src/services/creative-agent/**', + 'src/services/edutainment-baby-drawing/**', + 'src/services/edutainment-baby-object/**', + 'src/services/child-motion-demo/**', + 'src/services/jump-hop/**', + 'src/services/match3d-creation/**', + 'src/services/match3d-runtime/**', + 'src/services/match3d-works/**', + 'src/services/match3dGeneratedModelCache*', + 'src/services/match3dSpritesheetParser*', + 'src/services/puzzle-agent/**', + 'src/services/puzzle-gallery/**', + 'src/services/puzzle-onboarding/**', + 'src/services/puzzle-runtime/**', + 'src/services/puzzle-works/**', + 'src/services/rpg-creation/**', + 'src/services/rpg-entry/**', + 'src/services/rpg-runtime/**', + 'src/services/square-hole-creation/**', + 'src/services/square-hole-runtime/**', + 'src/services/square-hole-works/**', + 'src/services/storyEngine/**', + 'src/services/visual-novel-creation/**', + 'src/services/visual-novel-runtime/**', + 'src/services/visual-novel-works/**', + 'src/services/wooden-fish/**', + 'src/types.ts', + 'src/types/**', + 'src/uiAssets.ts', + 'scripts/loadtest/**', + 'packages/shared/src/contracts/jumpHop.ts', + 'packages/shared/src/contracts/match3dAgent.ts', + 'packages/shared/src/contracts/match3dRuntime.ts', + 'packages/shared/src/contracts/match3dWorks.ts', + 'packages/shared/src/contracts/barkBattle*', + 'packages/shared/src/contracts/bigFish*', + 'packages/shared/src/contracts/creationAgent*', + 'packages/shared/src/contracts/creationAudio*', + 'packages/shared/src/contracts/creativeAgent*', + 'packages/shared/src/contracts/customWorld*', + 'packages/shared/src/contracts/edutainment*', + 'packages/shared/src/contracts/playTypes*', + 'packages/shared/src/contracts/publicWork*', + 'packages/shared/src/contracts/puzzle*', + 'packages/shared/src/contracts/rpg*', + 'packages/shared/src/contracts/squareHole*', + 'packages/shared/src/contracts/story*', + 'packages/shared/src/contracts/visualNovel*', + 'packages/shared/src/contracts/woodenFish*', + 'scripts/export-match3d-resource-pipeline*', + 'scripts/generate-child-motion-demo-assets.mjs', + 'src/services/puzzle-clear/**', + 'src/games/**', + 'src/data/**', + 'src/prompts/**', + 'apps/admin-web/src/pages/AdminCreationEntrySwitchPage*', + 'apps/admin-web/src/pages/AdminWorkVisibilityPage*', + 'src/services/recommendedRuntimeGuestLaunch.test.ts', + 'src/data/sceneEncounterPreviews.ts', + 'public/Icons', + 'media', + '.codex-logs', + '*.log', + '.preview.*', + 'temp-build-goal-check/**', + 'tmp_*', + 'tmp/**', + 'npc-editor-*', + 'temp-write-check.txt', + '**/__pycache__/**', + '*.timestamp-*.mjs', + ], rules: { + '@typescript-eslint/no-var-requires': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-refresh/only-export-components': [ + 'error', + { allowConstantExport: true }, + ], 'simple-import-sort/imports': 'error', - 'react-hooks/exhaustive-deps': 'warn', - 'react-refresh/only-export-components': 'off', + 'simple-import-sort/exports': 'error', + '@typescript-eslint/no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + 'no-constant-condition': 'error', + 'no-console': ['error', { allow: ['warn', 'error'] }], + 'no-useless-escape': 'error', + 'prefer-const': 'error', }, }; diff --git a/.prettierrc.json b/.prettierrc.json index 0bed832ed..7d2081a95 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1 +1,5 @@ -{"singleQuote":true,"semi":true,"trailingComma":"all"} \ No newline at end of file +{ + "singleQuote": true, + "semi": true, + "trailingComma": "all" +} diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 9a92bbdfe..7d0e734d6 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -6669,7 +6669,6 @@ export function App({ setProjectSupervisorRuntimeError(''); try { const directTurnInput: { - projectId: string; projectPath: string; prompt: string; clientTurnId: string; @@ -6677,7 +6676,6 @@ export function App({ attachments?: DirectCodexTurnAttachment[]; userItem: ReturnType; } = { - projectId: localProject.manifest.projectId, projectPath: directProjectPath, prompt, clientTurnId, diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/types/FillMethod.ts b/apps/ai-game-creator-shell/src/features/ui-editor/types/FillMethod.ts index 3edc54e91..1651dbbee 100644 --- a/apps/ai-game-creator-shell/src/features/ui-editor/types/FillMethod.ts +++ b/apps/ai-game-creator-shell/src/features/ui-editor/types/FillMethod.ts @@ -1,8 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { HorizontalFillOrigin } from "./HorizontalFillOrigin"; -import type { Radial90Origin } from "./Radial90Origin"; import type { Radial180Origin } from "./Radial180Origin"; import type { Radial360Origin } from "./Radial360Origin"; +import type { Radial90Origin } from "./Radial90Origin"; import type { VerticalFillOrigin } from "./VerticalFillOrigin"; export type FillMethod = { "Horizontal": HorizontalFillOrigin } | { "Vertical": VerticalFillOrigin } | { "Radial90": { origin: Radial90Origin, clockwise: boolean, } } | { "Radial180": { origin: Radial180Origin, clockwise: boolean, } } | { "Radial360": { origin: Radial360Origin, clockwise: boolean, } }; diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx index c9bd95a93..b8fc97384 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx @@ -1448,7 +1448,6 @@ export default function ProjectDevelopmentView({ onMakeGame, }: ProjectDevelopmentViewProps) { const professionalDagVisible = orchestrationMode === 'professional-dag'; - const resourceHydrationEnabled = orchestrationMode !== 'single-supervisor'; const [mode, setMode] = useState('resources'); const [runtimeInspectMode, setRuntimeInspectMode] = useState(false); const [resourceBookState, dispatchResourceBook] = useReducer( @@ -2146,7 +2145,6 @@ export default function ProjectDevelopmentView({ }); useEffect(() => { - if (!resourceHydrationEnabled) return undefined; let cancelled = false; if (sortMode !== 'dependency') { setResourceGraphState({ @@ -2185,17 +2183,14 @@ export default function ProjectDevelopmentView({ hasSnapshot: canReuseSnapshot, }; }); - const readResourceGraph = () => - invoke( - 'read_local_project_resource_graph', - { - projectPath, - expectedProjectId: manifest.projectId, - resources: resourceGraphInputs, - }, - ); - void new Promise((resolve) => setTimeout(resolve, 0)) - .then(readResourceGraph) + void invoke( + 'read_local_project_resource_graph', + { + projectPath, + expectedProjectId: manifest.projectId, + resources: resourceGraphInputs, + }, + ) .then((readModel) => { if (!cancelled) { setResourceGraphState({ @@ -2225,7 +2220,6 @@ export default function ProjectDevelopmentView({ resourceGraphInputs, resourceGraphProjectScopeKey, resourceGraphScopeKey, - resourceHydrationEnabled, sortMode, ]); @@ -2519,14 +2513,14 @@ export default function ProjectDevelopmentView({ useEffect(() => { resourceLayoutReadNoticeScopeRef.current = null; setResourceLayoutReadNotice(null); - }, [manifest.projectId, projectPath, resourceHydrationEnabled]); + }, [manifest.projectId, projectPath]); /** * 替换血缘只活在本次会话:项目身份一变就作废,不依赖"组件恰好被卸载"。 * 与上一条读时归并提示同一口径(切项目后上一次的状态必须清掉)。 */ useEffect(() => { setResourceVersionReplacementLineage(null); - }, [manifest.projectId, projectPath, resourceHydrationEnabled]); + }, [manifest.projectId, projectPath]); useEffect(() => { const report = dependencyLayout.readReport ?? typeLayout.readReport; if ( @@ -7196,7 +7190,6 @@ export default function ProjectDevelopmentView({ * effect 的未处理 Promise 拒绝。 */ useEffect(() => { - if (!resourceHydrationEnabled) return undefined; const invoke = window.__TAURI__?.core?.invoke; if (!invoke || !projectPath.trim() || !manifest.projectId.trim()) { return undefined; @@ -7206,46 +7199,45 @@ export default function ProjectDevelopmentView({ setResourceAssetGenerationTasksPanelOpen(false); resourceAssetGenerationPanelSubmissionRef.current = null; setResourceAssetGenerationPanelReopen(null); - void new Promise((resolve) => setTimeout(resolve, 0)).then( - async () => { - const reportUnavailable = () => { - if (!cancelled) { - setResourceWorkbenchNotice( - '生成任务列表读取失败,暂时无法恢复历史任务', - ); - } - }; - let records: LocalProjectAssetGenerationTaskRecord[]; - try { - const response = await invoke< - LocalProjectAssetGenerationTaskRecord[] - >('list_local_project_asset_generations', { projectPath }); - if (!Array.isArray(response)) { - reportUnavailable(); - return; - } - records = response; - } catch { + void (async () => { + const reportUnavailable = () => { + if (!cancelled) { + setResourceWorkbenchNotice( + '生成任务列表读取失败,暂时无法恢复历史任务', + ); + } + }; + let records: LocalProjectAssetGenerationTaskRecord[]; + try { + const response = await invoke( + 'list_local_project_asset_generations', + { projectPath }, + ); + if (!Array.isArray(response)) { reportUnavailable(); return; } - if (cancelled) { - return; - } - const restored = mergeResourceCanvasAssetGenerationTasksWithRecords( - resourceAssetGenerationTasksRef.current.filter( - (task) => task.projectId === projectId, - ), - records, - ); - resourceAssetGenerationTasksRef.current = restored; - setResourceAssetGenerationTasks(restored); - }, - ); + records = response; + } catch { + reportUnavailable(); + return; + } + if (cancelled) { + return; + } + const restored = mergeResourceCanvasAssetGenerationTasksWithRecords( + resourceAssetGenerationTasksRef.current.filter( + (task) => task.projectId === projectId, + ), + records, + ); + resourceAssetGenerationTasksRef.current = restored; + setResourceAssetGenerationTasks(restored); + })(); return () => { cancelled = true; }; - }, [manifest.projectId, projectPath, resourceHydrationEnabled]); + }, [manifest.projectId, projectPath]); /** * 「生成任务」面板里点一条已完成任务:复用既有聚焦链定位到它的素材卡。 diff --git a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts index af621b7b5..6eb1cb305 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts @@ -1564,6 +1564,12 @@ export function registerHomeProjectCreationTests() { prompt: '你好,今天多少号', creationType: 'art', clientTurnId: expect.any(String), + userItem: { + id: expect.stringMatching(/^direct-codex:[A-Za-z0-9-]+:user$/), + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: '你好,今天多少号' }], + }, }); expect(invoke).not.toHaveBeenCalledWith( 'chat_with_game_creator_home_direct_codex', @@ -1664,6 +1670,12 @@ export function registerHomeProjectCreationTests() { prompt: '按这个角色做游戏', creationType: 'game', clientTurnId: expect.any(String), + userItem: { + id: expect.stringMatching(/^direct-codex:[A-Za-z0-9-]+:user$/), + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: '按这个角色做游戏' }], + }, attachments: [ { name: '角色参考.png', @@ -1694,6 +1706,12 @@ export function registerHomeProjectCreationTests() { projectPath: automaticProjectPath, prompt: '再补一句玩法', clientTurnId: expect.any(String), + userItem: { + id: expect.stringMatching(/^direct-codex:[A-Za-z0-9-]+:user$/), + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: '再补一句玩法' }], + }, }); const followUpPayload = invoke.mock.calls.find( ([command, args]) => @@ -2433,16 +2451,9 @@ export function registerHomeProjectCreationTests() { policy: { deniedCommands: [], confirmCommands: [] }, }; } - if ( - command === 'read_local_conversation' || - command === 'read_direct_project_conversation' - ) { - return { - path: `${projectPath}/.agent/conversations/project.jsonl`, - agentId: null, - sessionId: null, - messages: [...persistedMessages], - }; + if (command === 'read_direct_project_history_slice') { + expect(args).toEqual({ projectPath, limit: 20 }); + return { items: [...persistedMessages], hasMore: false }; } if (command === 'append_local_conversation_message') { throw new Error( @@ -2451,18 +2462,14 @@ export function registerHomeProjectCreationTests() { } if (command === 'chat_with_game_creator_direct_codex') { const clientTurnId = String(args?.clientTurnId ?? ''); - persistedMessages.push( - { - role: 'user', - content: String(args?.prompt ?? ''), - messageId: `direct-codex:${clientTurnId}:user`, - }, - { - role: 'assistant', - content: 'DIRECT_EXISTING_PROJECT_OK', - messageId: `direct-codex:${clientTurnId}:assistant`, - }, - ); + persistedMessages.push(args?.userItem as Record, { + role: 'assistant', + type: 'message', + content: [ + { type: 'output_text', text: 'DIRECT_EXISTING_PROJECT_OK' }, + ], + id: `direct-codex:${clientTurnId}:assistant`, + }); return 'DIRECT_EXISTING_PROJECT_OK'; } throw new Error(`unexpected invoke ${command}`); @@ -2486,6 +2493,12 @@ export function registerHomeProjectCreationTests() { projectPath, prompt: '继续修改已有项目', clientTurnId: expect.any(String), + userItem: { + id: expect.stringMatching(/^direct-codex:[A-Za-z0-9-]+:user$/), + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: '继续修改已有项目' }], + }, }, ); }); @@ -2524,16 +2537,9 @@ export function registerHomeProjectCreationTests() { expect(args).toEqual({ projectPath }); return manifest; } - if ( - command === 'read_local_conversation' || - command === 'read_direct_project_conversation' - ) { - return { - path: `${projectPath}/.agent/conversations/project.jsonl`, - agentId: null, - sessionId: null, - messages: [...persistedMessages], - }; + if (command === 'read_direct_project_history_slice') { + expect(args).toEqual({ projectPath, limit: 20 }); + return { items: [...persistedMessages], hasMore: false }; } if (command === 'append_local_permission_log') { return {}; @@ -2544,49 +2550,30 @@ export function registerHomeProjectCreationTests() { policy: { deniedCommands: [], confirmCommands: [] }, }; } - if (command === 'append_local_conversation_message') { - const message = args?.message as Record; - const messageId = String(args?.messageId ?? ''); - if ( - !messageId || - !persistedMessages.some( - (candidate) => candidate.messageId === messageId, - ) - ) { - persistedMessages.push({ - schemaVersion: 'game-creator-conversation.v1', - ...message, - messageId, - updatedAt: Number( - message.updatedAt ?? persistedMessages.length + 1, - ), - }); - } - return { - path: `${projectPath}/.agent/conversations/project.jsonl`, - agentId: null, - sessionId: null, - messages: [...persistedMessages], - }; - } if (command === 'chat_with_game_creator_direct_codex') { const clientTurnId = String(args?.clientTurnId ?? ''); - persistedMessages.push( - { - schemaVersion: 'game-creator-conversation.v1', + expect(args).toEqual({ + projectPath, + prompt: '生成一个游戏', + clientTurnId: expect.any(String), + userItem: { + id: `direct-codex:${clientTurnId}:user`, + type: 'message', role: 'user', - content: String(args?.prompt ?? ''), - messageId: `direct-codex:${clientTurnId}:user`, - updatedAt: 1, + content: [{ type: 'input_text', text: '生成一个游戏' }], }, - { - schemaVersion: 'game-creator-conversation.v1', - role: 'assistant', - content: '陶泥儿智能创作 鉴权失败,请重新登录后重试', - messageId: `direct-codex:${clientTurnId}:assistant`, - updatedAt: 2, - }, - ); + }); + persistedMessages.push(args?.userItem as Record, { + id: `direct-codex:${clientTurnId}:assistant`, + type: 'message', + role: 'assistant', + content: [ + { + type: 'output_text', + text: '陶泥儿智能创作 鉴权失败,请重新登录后重试', + }, + ], + }); throw new Error('codex-app-server-error:unauthorized'); } throw new Error(`unexpected invoke ${command}`); @@ -2609,15 +2596,25 @@ export function registerHomeProjectCreationTests() { await waitFor(() => { expect(persistedMessages).toHaveLength(2); }); - expect(persistedMessages).toEqual( - expect.arrayContaining([ - expect.objectContaining({ role: 'user', content: '生成一个游戏' }), - expect.objectContaining({ - role: 'assistant', - content: '陶泥儿智能创作 鉴权失败,请重新登录后重试', - }), - ]), - ); + expect(persistedMessages).toEqual([ + { + id: expect.stringMatching(/^direct-codex:[A-Za-z0-9-]+:user$/), + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: '生成一个游戏' }], + }, + { + id: expect.stringMatching(/^direct-codex:[A-Za-z0-9-]+:assistant$/), + type: 'message', + role: 'assistant', + content: [ + { + type: 'output_text', + text: '陶泥儿智能创作 鉴权失败,请重新登录后重试', + }, + ], + }, + ]); expect(JSON.stringify(persistedMessages)).not.toContain( 'codex-app-server-error:unauthorized', ); diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index b290998a1..3cc309c8d 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -7312,6 +7312,17 @@ export function registerProjectSupervisorSurfaceTests() { projectPath, prompt: '从旧任务状态继续生成,但使用 direct Codex', clientTurnId: expect.any(String), + userItem: { + id: expect.stringMatching(/^direct-codex:[A-Za-z0-9-]+:user$/), + type: 'message', + role: 'user', + content: [ + { + type: 'input_text', + text: '从旧任务状态继续生成,但使用 direct Codex', + }, + ], + }, }); expect(invoke).not.toHaveBeenCalledWith( 'cancel_game_creator_agent_runtime_task', @@ -7802,6 +7813,12 @@ export function registerProjectSupervisorSurfaceTests() { projectPath, prompt: '先完成正式客户端玩法拆解', clientTurnId: firstTurnId, + userItem: { + id: `direct-codex:${firstTurnId}:user`, + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: '先完成正式客户端玩法拆解' }], + }, }); await act(async () => { @@ -8048,6 +8065,12 @@ export function registerProjectSupervisorSurfaceTests() { projectPath, prompt: '补充:优先复用现有素材', clientTurnId: expect.any(String), + userItem: { + id: expect.stringMatching(/^direct-codex:[A-Za-z0-9-]+:user$/), + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: '补充:优先复用现有素材' }], + }, }, ); }); @@ -8209,8 +8232,9 @@ export function registerProjectSupervisorSurfaceTests() { }); expect(professionalRuntimeReadCount).toBe(0); await waitFor(() => - expect(invoke).toHaveBeenCalledWith('read_direct_project_conversation', { + expect(invoke).toHaveBeenCalledWith('read_direct_project_history_slice', { projectPath, + limit: 20, }), ); // 默认任务占位行也不能触发专业 Agent 历史的批量读取。 @@ -8434,6 +8458,12 @@ export function registerProjectSupervisorSurfaceTests() { projectPath, prompt: '修改玩家移动脚本', clientTurnId: expect.any(String), + userItem: { + id: expect.stringMatching(/^direct-codex:[A-Za-z0-9-]+:user$/), + type: 'message', + role: 'user', + content: [{ type: 'input_text', text: '修改玩家移动脚本' }], + }, }, ); }); diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index fa96f595f..1ae8c60d6 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -5609,3 +5609,9 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - **现象**:`Genarrative-Stdb-Module-Publish` 在备份和 SpacetimeDB 就绪后,于 `spacetime publish` 报 `Permission denied`。 - **原因**:Jenkins 以 root 运行时 `${HOME}/data/tmp` 位于 `/root` 下;即使发布临时子目录已 `chown` 给 `spacetimedb`,父目录仍不可遍历。 - **处理**:发布给 `--run-as-user` 的 WASM 临时目录改用 `/var/tmp`,继续使用随机目录并在退出时清理。 + +## Git hook 测试必须清除继承的 Git 仓库环境 + +- 在 hook 内运行临时仓库测试时,`cwd` 不会覆盖继承的 `GIT_DIR`、`GIT_WORK_TREE` 或 `GIT_INDEX_FILE`。未隔离的 Git/lint-staged 子进程可能向真实仓库提交 fixture,甚至把测试版 ESLint、Prettier 配置带入主分支。 +- fixture 子进程统一清除 `GIT_*` 环境,并用一次性外层 linked worktree 验证引用、索引、配置不变;原有工程检查规则保持完整,不能用逐项关闭规则修复 fixture 污染。 +- Vitest 的 `toHaveBeenCalledWith` 匹配任意一次调用,失败输出会列出其它命令;应先定位相同命令的真实参数差异,不能由其它调用的序号推断时序故障。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 7d6c1ca0b..2bdeedb04 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -1,5 +1,9 @@ # AI 游戏创作智能体 App 实施计划 +## DirectProject 用户消息契约验证 + +`chat_with_game_creator_direct_codex` 必须携带 `projectPath`、`prompt`、稳定的 `clientTurnId` 和完整 `userItem`;`creationType` 与 `attachments` 按实际输入传递。Rust 通过 `projectPath` 解析项目身份,不接收额外 `projectId`。界面测试必须核对 `userItem` 的消息身份、角色、正文及附件内容,拒绝回合用例仍验证实际返回的错误原因。重开项目的历史恢复测试使用 `read_direct_project_history_slice` 的 canonical raw items 与 `hasMore`,首屏 `limit: 20`。资源图和生成任务的读取继续遵守原有工作台恢复逻辑,不因聊天断言失败延迟、关闭或改变它们。 + ## 2026-09-15 DirectProject 长回合平台会话保活 DirectProject 的生图、素材处理、构建和试玩可能跨越短生命周期 access token 的有效期。普通 `/api/*` 请求和 Codex app-server 已有 401 刷新路径,但 AGC 工具由 Rust 工具桥直接使用客户端当前会话,工具内部的 401 不会自动触发前端刷新。客户端在 DirectProject 回合处于 busy 状态时每 5 分钟调用现有 `requestPlatformSessionRefresh()`;刷新仍复用单飞请求、generation 校验和 native session 安装,不改变凭据来源,也不把 401 降级为成功。刷新失败保持静默,由原始 AGC 工具错误按现有鉴权失败合同返回,避免后台保活覆盖真实错误。 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index 1ca7a142f..d14bcff3d 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -1,5 +1,11 @@ # 本地开发验证与生产运维 +## 构建回归的隔离与发布文件权限 + +Git hook 的临时仓库测试必须清除子进程继承的仓库定位环境(例如 `GIT_DIR`、`GIT_WORK_TREE`、`GIT_INDEX_FILE`);仅设置 `cwd` 不能隔离 Git。回归应从带这些变量的外层仓库运行,验证外层引用、索引与配置不变。临时测试文件必须留在独立目录并清理,不得通过测试生成主仓库提交或覆盖 ESLint、Prettier 配置。修复 lint 配置时保留原有规则、忽略范围与零警告门禁,不以关闭规则代替排障。 + +Stdb 发布以 root 准备文件、再切换 `spacetimedb` 用户执行时,WASM 必须放在服务用户可遍历的父目录下。生产脚本在 `/var/tmp` 创建随机私有目录并移交给发布用户,退出时清理;不能仅修改子目录所有权后继续从 `/root` 下读取。发布成功的判据仍是完整 Full Build 的 Stdb、API、Web 发布及退出维护模式全部成功。 + 更新时间:`2026-08-05` ## 标准开发流程 diff --git a/ignored/legacy.ts b/ignored/legacy.ts deleted file mode 100644 index 1e3e9ab8f..000000000 --- a/ignored/legacy.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { zebra } from './zebra'; -import { alpha } from './alpha'; -void zebra; void alpha; -// staged ignored change diff --git a/partial.ts b/partial.ts deleted file mode 100644 index 30eff13f8..000000000 --- a/partial.ts +++ /dev/null @@ -1,2 +0,0 @@ -const original={value:1} -const keep = 2; diff --git a/sample.ts b/sample.ts deleted file mode 100644 index a56d432a8..000000000 --- a/sample.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { alpha } from './alpha'; -import { zebra } from './zebra'; -const staged = { value: 1 }; -void zebra; -void alpha; -void staged; diff --git a/scripts/git-hooks.test.mjs b/scripts/git-hooks.test.mjs index c285265cb..295973c8c 100644 --- a/scripts/git-hooks.test.mjs +++ b/scripts/git-hooks.test.mjs @@ -9,7 +9,7 @@ import { symlinkSync, writeFileSync, } from 'node:fs'; -import { tmpdir } from 'node:os'; +import { homedir } from 'node:os'; import { delimiter, dirname, join, resolve } from 'node:path'; import { test } from 'node:test'; import { fileURLToPath } from 'node:url'; @@ -34,7 +34,7 @@ test('pre-commit hook fixes staged imports and formatting without swallowing uns 'npm run format:staged\n', ); - const tempRepo = mkdtempSync(join(tmpdir(), 'genarrative-git-hooks-')); + const tempRepo = createTempDirectory('genarrative-git-hooks-'); try { git(tempRepo, 'init', '--quiet'); git(tempRepo, 'config', 'user.email', 'git-hooks-test@example.invalid'); @@ -128,7 +128,7 @@ test('pre-commit hook fixes staged imports and formatting without swallowing uns cwd: tempRepo, encoding: 'utf8', env: { - ...process.env, + ...isolatedGitEnvironment(), PATH: `${join(repoRoot, 'node_modules', '.bin')}${delimiter}${process.env.PATH ?? ''}`, }, input: JSON.stringify(lintStagedConfig), @@ -162,7 +162,7 @@ test('pre-commit hook fixes staged imports and formatting without swallowing uns }); test('pre-push runs repository parity only for master updates', () => { - const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-pre-push-')); + const tempDir = createTempDirectory('genarrative-pre-push-'); try { const npmLog = join(tempDir, 'repo', 'npm.log'); const tempRepo = join(tempDir, 'repo'); @@ -214,7 +214,7 @@ exit 1 { cwd: tempRepo, encoding: 'utf8', - env: process.env, + env: isolatedGitEnvironment(), }, ); }; @@ -237,6 +237,92 @@ exit 1 } }); +test('hook fixtures do not mutate the calling linked worktree or its index', () => { + const tempDir = createTempDirectory('genarrative-hook-isolation-'); + try { + const outerRepo = join(tempDir, 'outer'); + const worktree = join(tempDir, 'worktree'); + mkdirSync(outerRepo); + git(outerRepo, 'init', '--quiet'); + git(outerRepo, 'config', 'user.email', 'outer@example.invalid'); + git(outerRepo, 'config', 'user.name', 'Outer Repository'); + writeFileSync(join(outerRepo, 'sentinel.txt'), 'committed\n'); + git(outerRepo, 'add', 'sentinel.txt'); + git( + outerRepo, + '-c', + 'commit.gpgsign=false', + 'commit', + '--quiet', + '-m', + 'sentinel', + ); + git( + outerRepo, + 'worktree', + 'add', + '--quiet', + '-b', + 'fixture-caller', + worktree, + ); + writeFileSync(join(worktree, 'sentinel.txt'), 'staged\n'); + git(worktree, 'add', 'sentinel.txt'); + writeFileSync(join(worktree, 'sentinel.txt'), 'unstaged\n'); + const gitDir = git(worktree, 'rev-parse', '--absolute-git-dir').trim(); + const indexPath = join(gitDir, 'index'); + const before = { + refs: git(outerRepo, 'show-ref'), + config: readFileSync(join(outerRepo, '.git', 'config'), 'utf8'), + index: readFileSync(indexPath), + status: git(worktree, 'status', '--porcelain'), + }; + const result = spawnSync( + process.execPath, + [ + '--test', + '--test-reporter=tap', + '--test-name-pattern=^pre-(commit|push)', + fileURLToPath(import.meta.url), + ], + { + cwd: worktree, + encoding: 'utf8', + env: { + ...isolatedGitEnvironment(), + NODE_TEST_CONTEXT: undefined, + GIT_DIR: gitDir, + GIT_COMMON_DIR: join(outerRepo, '.git'), + GIT_WORK_TREE: worktree, + GIT_INDEX_FILE: indexPath, + GIT_CONFIG_COUNT: '1', + GIT_CONFIG_KEY_0: 'core.worktree', + GIT_CONFIG_VALUE_0: worktree, + }, + }, + ); + assert.equal( + result.status, + 0, + `${result.stdout ?? ''}${result.stderr ?? ''}`, + ); + assert.match(result.stdout, /# pass 2\b/u); + assert.equal(git(outerRepo, 'show-ref'), before.refs); + assert.equal( + readFileSync(join(outerRepo, '.git', 'config'), 'utf8'), + before.config, + ); + assert.deepEqual(readFileSync(indexPath), before.index); + assert.equal(git(worktree, 'status', '--porcelain'), before.status); + assert.equal( + readFileSync(join(worktree, 'sentinel.txt'), 'utf8'), + 'unstaged\n', + ); + } finally { + rmSync(tempDir, { force: true, recursive: true }); + } +}); + test('Gitea Repository checks and master pre-push share the same repository command', () => { const workflow = readFileSync( join(repoRoot, '.gitea', 'workflows', 'project-ci.yml'), @@ -290,5 +376,23 @@ function toBashPath(path) { } function git(cwd, ...args) { - return execFileSync('git', args, { cwd, encoding: 'utf8' }); + return execFileSync('git', args, { + cwd, + encoding: 'utf8', + env: isolatedGitEnvironment(), + }); +} + +function isolatedGitEnvironment() { + // Git hooks export repository/index paths that override cwd, including in + // linked worktrees. Fixture Git and lint-staged must never inherit them. + return Object.fromEntries( + Object.entries(process.env).filter(([name]) => !/^GIT_/iu.test(name)), + ); +} + +function createTempDirectory(prefix) { + const tempRoot = join(homedir(), 'data', 'tmp'); + mkdirSync(tempRoot, { recursive: true }); + return mkdtempSync(join(tempRoot, prefix)); } diff --git a/tracked.txt b/tracked.txt deleted file mode 100644 index 180b47c18..000000000 --- a/tracked.txt +++ /dev/null @@ -1 +0,0 @@ -baseline