Compare commits

..

10 Commits

Author SHA1 Message Date
lhk229 0847b1bb83 Merge remote-tracking branch 'origin/rm/design-v2' into rm/design-v2
Project CI / Repository checks (pull_request) Successful in 2m42s
Project CI / Frontend tests (pull_request) Successful in 3m0s
Project CI / Native shell tests (pull_request) Failing after 3m51s
Project CI / Backend tests (pull_request) Successful in 5m46s
2026-09-14 07:23:48 +00:00
lhk229 8f691d4c8c Merge branch 'master' into rm/design-v2
Project CI / Repository checks (pull_request) Failing after 3m20s
Project CI / Frontend tests (pull_request) Failing after 2m42s
Project CI / Native shell tests (pull_request) Failing after 3m57s
Project CI / Backend tests (pull_request) Successful in 6m23s
2026-09-14 15:22:30 +08:00
lhk229 d3b9f326f0 删除孤立的策划 GDD 模型模块
移除已无调用方的 planning_gdd_model.rs 及模块导出

删除仅依赖退役提交错误类型的无效判定函数
2026-09-14 07:20:13 +00:00
lhk229 7eb2326a76 移除退役策划 V2 运行态刷新入口
Project CI / Repository checks (pull_request) Failing after 2m22s
Project CI / Frontend tests (pull_request) Failing after 3m7s
Project CI / Native shell tests (pull_request) Failing after 3m47s
Project CI / Backend tests (pull_request) Successful in 6m38s
删除旧版策划状态随项目总控运行态刷新的专用 effect

移除仅服务该 effect 的 planningLane 判定模块
2026-09-14 06:30:07 +00:00
lhk229 9fc28790a4 移除退役策划 V2 运行面板参数
删除项目总控运行面板中已无调用方的 GDD 审批状态参数
2026-09-14 06:26:54 +00:00
lhk229 cea45a608d 删除退役策划 V2 前端审批面板
移除旧版 GDD 审批卡和策划运行条组件

项目总控视图仅保留新版 Design Agent 与通用运行面板
2026-09-14 06:23:16 +00:00
lhk229 ee03ade156 Merge remote-tracking branch 'origin/master' into rm/design-v2
Project CI / Repository checks (pull_request) Failing after 3m24s
Project CI / Frontend tests (pull_request) Failing after 3m2s
Project CI / Backend tests (pull_request) Successful in 6m55s
Project CI / Native shell tests (pull_request) Failing after 4m20s
2026-09-14 06:19:46 +00:00
lhk229 4933c6c380 删除退役策划 V2 前端测试契约
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
移除 Plan GDD 审批测试套件及注册入口
2026-09-14 06:15:13 +00:00
lhk229 931f3eae0a 删除退役策划 V2 Rust Runtime
移除 Planning V2 协议模块与 Tauri 命令注册

保留新版 Design Agent、做游戏 Agent 与共享运行时
2026-09-14 06:07:20 +00:00
lhk229 c5f8fa1ef6 清理旧策划 V1 残留注释
删除已失效的 --plan 策划自动应答说明
2026-09-14 05:54:24 +00:00
173 changed files with 1997 additions and 16199 deletions
+84 -335
View File
@@ -27,28 +27,9 @@ env:
RUSTC_WRAPPER: ''
CARGO_BUILD_RUSTC_WRAPPER: ''
# job 声明顺序就是 runner 领取顺序,因此把最长尾的客户端 Rust 门禁排在前面,
# 让它在最少的等待下占用并发槽位;其余 job 按时长递减排列。
#
# 客户端(微信壳 / Expo 移动壳 / Tauri 桌面壳 / AI 游戏创作壳)门禁原先全部串在
# `Native shell tests` 一个 job 里,实测 18 分 37 秒。现在按门禁组拆成一个分组一个 job:
# `Native shell tests`(契约 + H5 / 微信 / 移动 / 桌面壳门禁 + 发布构建 smoke)、
# `AI game creator shell web tests`typecheck + 壳内测试)、`AI game creator shell
# Rust shard 1/4` 到 `4/4`AGC 壳 bin 单测按名单分 4 片)、`AI game creator shell
# Rust smoke`agent-run smoke)与 `AI game creator shell Rust crates`AGC 壳依赖的
# 共享 / 平台 crate 测试)。各自的命令与拆分前逐一对应,本地
# `npm run check:native-shells` 仍是同一条串行序列。
#
# AGC 壳的 bin 单测(2466 条)按名单分 4 片、一片一个 job:片内保持 `--test-threads=1`
# (当年线程并行会互相干扰的是进程内后台锁与异步终态),片与片之间靠 job 级并发摊开。
# 不要改回「一个 job 里多进程并行这几片」:同一容器内它们会争抢共享 HOME、target 与固定
# 临时路径,实测比整套串行还慢。每个分片 job 都会自校验「片并集等于全集且互斥」。
jobs:
# AGC 壳自身的 Rust bin 单测分片,4 片各自独立 job 并发执行、片内仍保持
# `--test-threads=1`。这里不装 npm 依赖:壳 Rust 门禁只用 cargo 与 node 内建模块,
# 也只需要 AGC 壳自己那份锁定依赖。
ai-game-creator-shell-rust-shard-1:
name: AI game creator shell Rust shard 1/4
repository-checks:
name: Repository checks
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
@@ -60,228 +41,76 @@ jobs:
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare AI game creator shell Rust dependencies
- name: Resolve comparison base
shell: bash
run: |
set -euo pipefail
# AGC 壳有独立 Cargo.lock,其 path 依赖已含 platform-llm、platform-agent、
# agent-runtime-core 与 shared-contracts,因此只锁这一份 manifest 就能覆盖壳测试
# 与 smoke 的全部第三方依赖;server-rs 那次预热归 crate 级 job,不在这里重复。
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
base_ref="$(node -e '
const fs = require("node:fs");
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
process.stdout.write(event.pull_request?.base?.sha ?? event.before ?? "");
')"
if [[ -n "${base_ref}" && ! "${base_ref}" =~ ^0+$ ]]; then
git cat-file -e "${base_ref}^{commit}" 2>/dev/null || {
echo "comparison base commit is unavailable: ${base_ref}" >&2
exit 1
fi
sleep $((attempt * 2))
done
}
else
base_ref="$(git merge-base HEAD origin/master 2>/dev/null || git rev-parse HEAD)"
fi
resolved_base_ref="$(git rev-parse --verify "${base_ref}^{commit}" 2>/dev/null || true)"
head_ref="$(git rev-parse HEAD)"
if [[ "${resolved_base_ref}" == "${head_ref}" ]]; then
resolved_base_ref="$(git rev-parse --verify HEAD^ 2>/dev/null || true)"
fi
if [[ -z "${resolved_base_ref}" ]]; then
echo 'comparison base must resolve to a commit distinct from HEAD.' >&2
exit 1
fi
base_ref="${resolved_base_ref}"
if [[ "${GITHUB_EVENT_NAME:-}" == 'pull_request' ]] \
&& ! git merge-base --is-ancestor "${base_ref}" HEAD; then
echo 'pull request head does not contain the latest base commit; update the branch and rerun CI.' >&2
exit 1
fi
echo "SPACETIME_SCHEMA_BASE_REF=${base_ref}" >> "${GITHUB_ENV}"
- name: Run AI game creator shell Rust shard 1/4
run: npm run check:native-shells:agc-rust-shard-1
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
ai-game-creator-shell-rust-shard-2:
name: AI game creator shell Rust shard 2/4
- name: Run repository checks
run: npm run check:repository-ci
frontend-tests:
name: Frontend tests
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
- name: Checkout source from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_FETCH_DEPTH: '1'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare AI game creator shell Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
- name: Run AI game creator shell Rust shard 2/4
run: npm run check:native-shells:agc-rust-shard-2
- name: Run frontend and script tests
run: npm run test
ai-game-creator-shell-rust-shard-3:
name: AI game creator shell Rust shard 3/4
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Run BgFilter worker smoke harness tests
run: npm run bgfilter-worker:smoke-test
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Validate production health patrol behavior
run: npm run check:production-health-patrol
- name: Prepare AI game creator shell Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Validate production API release behavior
run: npm run check:production-api-release
- name: Run AI game creator shell Rust shard 3/4
run: npm run check:native-shells:agc-rust-shard-3
ai-game-creator-shell-rust-shard-4:
name: AI game creator shell Rust shard 4/4
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare AI game creator shell Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell Rust shard 4/4
run: npm run check:native-shells:agc-rust-shard-4
# agent-run smoke 会 spawn `cargo run`(走壳自己的 manifest),同样不装 npm 依赖,
# 单独一个 job,免得把已经压到 4 分钟级的片 job 拖长。
ai-game-creator-shell-rust-smoke:
name: AI game creator shell Rust smoke
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare AI game creator shell Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'AI game creator shell Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell agent-run smoke
run: npm run check:native-shells:agc-rust-smoke
# AGC 壳依赖的共享 / 平台 crate 测试用的是 server-rs workspace 与两个无锁独立 crate
# 的 manifest,属另一套依赖图,因此单独一个 job 预热、单独跑。
ai-game-creator-shell-rust-crates:
name: AI game creator shell Rust crates
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Prepare server-rs Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path server-rs/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'server-rs Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Prepare standalone Rust crate dependencies
shell: bash
run: |
set -euo pipefail
# agent-runtime-core / agent-runtime-orchestration 被 server-rs/Cargo.toml 的
# exclude 排除,不参与上面的 workspace 锁文件,因此上面那次锁定 fetch 覆盖不到它们;
# 而 `npm run ai-game-creator-shell:check:rust:crates` 会用
# `cargo test --manifest-path` 单独跑这两个 crate。不在这里预热的话,这两条测试
# 会在测试阶段自己 `Updating crates.io index`crates.io 一抖动整条 job 就红
# (见 #327 / PR #316 run 1950)。
# 两个 crate 都没有提交 Cargo.lock,所以这里只能做不带锁标志的 fetch:
# 加锁标志会因为缺少锁文件直接失败。生成的 Cargo.lock 落在两个 crate 目录内,
# 已被各自的 .gitignore 忽略,只留在容器里;随后的测试阶段因此能用锁定版本
# 解析,不再触碰 registry index。
for manifest_path in \
server-rs/crates/agent-runtime-core/Cargo.toml \
server-rs/crates/agent-runtime-orchestration/Cargo.toml; do
for attempt in $(seq 1 5); do
if cargo fetch \
--target x86_64-unknown-linux-gnu \
--manifest-path "${manifest_path}"; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo "standalone crate dependency fetch failed after 5 attempts: ${manifest_path}" >&2
exit 1
fi
sleep $((attempt * 2))
done
done
- name: Run AI game creator shell shared crate gates
run: npm run check:native-shells:agc-rust-crates
- name: Validate production API deploy behavior
run: npm run check:production-api-deploy
backend-tests:
name: Backend tests
@@ -365,8 +194,6 @@ jobs:
- name: Check SpacetimeDB module
run: cargo check --locked -p spacetime-module --manifest-path server-rs/Cargo.toml
# 客户端的壳级与契约门禁:静态契约断言、H5 / 微信 / 移动 / 桌面壳运行时门禁,
# 以及依赖发布产物的构建 smoke。
native-shell-tests:
name: Native shell tests
runs-on: genarrative-ci
@@ -388,6 +215,7 @@ jobs:
run: |
set -euo pipefail
for manifest_path in \
server-rs/Cargo.toml \
apps/desktop-shell/src-tauri/Cargo.toml \
apps/ai-game-creator-shell/src-tauri/Cargo.toml; do
for attempt in $(seq 1 5); do
@@ -404,118 +232,39 @@ jobs:
done
done
- name: Run native shell contract gates
run: npm run check:native-shells:contract
- name: Run native shell gates
run: npm run check:native-shells:shells
- name: Run native shell release build smoke
run: npm run check:native-shells:release
- name: Ensure native lockfiles are unchanged
run: git diff --exit-code -- apps/desktop-shell/src-tauri/Cargo.lock apps/ai-game-creator-shell/src-tauri/Cargo.lock
frontend-tests:
name: Frontend tests
runs-on: genarrative-ci
steps:
- name: Checkout source from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '1'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
- name: Run frontend and script tests
run: npm run test
- name: Run BgFilter worker smoke harness tests
run: npm run bgfilter-worker:smoke-test
- name: Validate production health patrol behavior
run: npm run check:production-health-patrol
- name: Validate production API release behavior
run: npm run check:production-api-release
- name: Validate production API deploy behavior
run: npm run check:production-api-deploy
repository-checks:
name: Repository checks
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Resolve comparison base
- name: Prepare standalone Rust crate dependencies
shell: bash
run: |
set -euo pipefail
base_ref="$(node -e '
const fs = require("node:fs");
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
process.stdout.write(event.pull_request?.base?.sha ?? event.before ?? "");
')"
if [[ -n "${base_ref}" && ! "${base_ref}" =~ ^0+$ ]]; then
git cat-file -e "${base_ref}^{commit}" 2>/dev/null || {
echo "comparison base commit is unavailable: ${base_ref}" >&2
exit 1
}
else
base_ref="$(git merge-base HEAD origin/master 2>/dev/null || git rev-parse HEAD)"
fi
resolved_base_ref="$(git rev-parse --verify "${base_ref}^{commit}" 2>/dev/null || true)"
head_ref="$(git rev-parse HEAD)"
if [[ "${resolved_base_ref}" == "${head_ref}" ]]; then
resolved_base_ref="$(git rev-parse --verify HEAD^ 2>/dev/null || true)"
fi
if [[ -z "${resolved_base_ref}" ]]; then
echo 'comparison base must resolve to a commit distinct from HEAD.' >&2
exit 1
fi
base_ref="${resolved_base_ref}"
if [[ "${GITHUB_EVENT_NAME:-}" == 'pull_request' ]] \
&& ! git merge-base --is-ancestor "${base_ref}" HEAD; then
echo 'pull request head does not contain the latest base commit; update the branch and rerun CI.' >&2
exit 1
fi
echo "SPACETIME_SCHEMA_BASE_REF=${base_ref}" >> "${GITHUB_ENV}"
# agent-runtime-core / agent-runtime-orchestration 被 server-rs/Cargo.toml 的
# exclude 排除,不参与上面的 workspace 锁文件,因此上面那次锁定 fetch 覆盖不到它们;
# 而 check:native-shells 会经 agent-runtime-*:check 用 `cargo test --manifest-path`
# 单独跑这两个 crate。不在这里预热的话,这两条测试会在测试阶段自己
# `Updating crates.io index`crates.io 一抖动整条 native shell 作业就红
# (见 #327 / PR #316 run 1950)。
# 两个 crate 都没有提交 Cargo.lock,所以这里只能做不带锁标志的 fetch:
# 加锁标志会因为缺少锁文件直接失败。生成的 Cargo.lock 落在两个 crate 目录内,
# 已被各自的 .gitignore 忽略,只留在容器里;随后的测试阶段因此能用锁定版本
# 解析,不再触碰 registry index。
for manifest_path in \
server-rs/crates/agent-runtime-core/Cargo.toml \
server-rs/crates/agent-runtime-orchestration/Cargo.toml; do
for attempt in $(seq 1 5); do
if cargo fetch \
--target x86_64-unknown-linux-gnu \
--manifest-path "${manifest_path}"; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo "standalone crate dependency fetch failed after 5 attempts: ${manifest_path}" >&2
exit 1
fi
sleep $((attempt * 2))
done
done
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
- name: Run native shell gates
run: npm run check:native-shells
- name: Run repository checks
run: npm run check:repository-ci
# 客户端的 AI 游戏创作壳前端门禁:typecheck 与壳内测试,不触碰 Cargo。
ai-game-creator-shell-web-tests:
name: AI game creator shell web tests
runs-on: genarrative-ci
steps:
- name: Checkout full history from Gitea
env:
GENARRATIVE_GITEA_FETCH_DEPTH: '0'
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: genarrative-gitea-checkout
- name: Validate preinstalled CI job image and sandbox
run: GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1 bash scripts/check-gitea-ci-job-image.sh
- name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh
- name: Run AI game creator shell web gates
run: npm run check:native-shells:agc-web
- name: Ensure native lockfiles are unchanged
run: git diff --exit-code -- apps/desktop-shell/src-tauri/Cargo.lock apps/ai-game-creator-shell/src-tauri/Cargo.lock
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@genarrative/ai-game-creator-shell",
"private": true,
"version": "0.1.29",
"version": "0.1.27",
"type": "module",
"scripts": {
"dev": "node scripts/start-tauri-dev.mjs",
@@ -957,9 +957,6 @@ async function runInteractiveCargo(cliArguments, setActiveChild) {
return result;
}
// 立项策划跑 standard 档,`agent.delegate` 这类动作按项目权限策略必须逐个确认,
// 而确认和问询都只从 CLI 的 stdin 读。自主构建档没有这一步,所以只有 --plan 需要
// 一个把「人坐在终端前敲 approve」自动化掉的应答器;判据本身仍然走后端确认命令。
const swarmConfirmationPromptPattern = /输入 approve 或 reject$/u;
const swarmUserInputPromptPattern = /请选择 1-\d+,或直接输入其他答案:$/u;
@@ -108,8 +108,6 @@ const rustSharedContractSource = fs.readFileSync(
);
const allowedUncalledTauriCommands = [
'append_direct_project_conversation_message',
// TODO: Remove the retired binding command after the legacy runtime path is removed.
'bind_components',
'chat_with_game_creator_agent',
'check_ui_editor_font_glyph_coverage',
'create_ui_design_resource',
@@ -1,461 +0,0 @@
#!/usr/bin/env node
// AGC 壳 Rust 套件的分片运行器。
//
// 背景:AGC 壳的 Rust 单测集中在 src/main.rs 的 bin target(实测 2466 条),仓库口径用
// `--test-threads=1` 跑,理由是并行调度会让共享 Agent Runtime 后台锁与异步终态的用例在
// **同一进程内**互相干扰(见 development-workflow 的 Tauri suite 单线程口径)。代价是
// 整套用例串行跑满 507 秒,占掉 CI 上 `AI game creator shell Rust tests` job 的大头。
//
// 这里保留「片内串行」的既有口径,只把用例集合切成 N 片:
// - CI 用 `--shard-index=<i>` 让**每个 job 只跑一片**,靠多个 job 并发把整套用例摊开;
// - 本地不传 `--shard-index` 时把 N 片放进 N 个**独立进程**并行(--concurrency 可调),
// 保留一条命令跑全量的入口。
// 片并集必须等于全集、且不得重复,数量不符即失败,防止分片规则改动后静默漏跑;该校验
// 与「只跑一片」无关,因此在每个 job 上都会执行。
//
// 注意:同一容器内多进程并行这套用例(共享 HOME、target、固定临时路径)实测会互相拖慢,
// 比串行还慢,所以 CI 走「一个 job 一片」而不是单 job 内并行。
//
// 用法:
// node scripts/run-rust-shell-test-shards.mjs --shards=4 --shard-index=2 # CI:只跑第 2 片
// node scripts/run-rust-shell-test-shards.mjs [--shards=4] [--concurrency=4] # 本地:全量
// node scripts/run-rust-shell-test-shards.mjs --manifest=<Cargo.toml> --target-kind=lib --no-locked
//
// 参数:
// --shards=<n> 分片数,默认 4
// --shard-index=<i> 只跑第 i 片(1..shards);不传则跑全部分片
// --concurrency=<n> 同时运行的片数,默认等于分片数;--shard-index 时恒为 1
// --manifest=<path> Cargo.toml,默认 ../src-tauri/Cargo.toml(相对本脚本)
// --target-kind=<k> bin | lib,默认 bin(本地自测小 crate 时用 lib
// --bin=<name> bin target 名,默认 genarrative-ai-game-creator-shell
// --package=<name> target-kind=lib 时要跑的包名(配合 lib 目标使用)
// --no-locked 传给 cargo 时不带 --locked(只对没有提交 Cargo.lock 的 crate 需要)
// --shard-tmp-root=<path> 片专属 TMPDIR 的父目录,默认 <系统临时目录>/agc-rust-shards
import { spawn } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
const shellRoot = path.resolve(scriptDirectory, '..');
function fail(message) {
console.error(`[rust-shards] ${message}`);
process.exit(1);
}
function parsePositiveInteger(name, rawValue) {
if (!/^[1-9][0-9]*$/.test(rawValue)) {
fail(`${name} must be a positive integer, received: ${rawValue}`);
}
return Number(rawValue);
}
const options = {
shards: 4,
shardIndex: undefined,
concurrency: undefined,
manifestPath: path.join(shellRoot, 'src-tauri', 'Cargo.toml'),
targetKind: 'bin',
binName: 'genarrative-ai-game-creator-shell',
packageName: undefined,
locked: true,
shardTmpRoot: path.join(os.tmpdir(), 'agc-rust-shards'),
};
for (const rawArgument of process.argv.slice(2)) {
if (rawArgument === '--no-locked') {
options.locked = false;
continue;
}
const separatorIndex = rawArgument.indexOf('=');
if (!rawArgument.startsWith('--') || separatorIndex === -1) {
fail(`unexpected argument: ${rawArgument}`);
}
const name = rawArgument.slice(2, separatorIndex);
const value = rawArgument.slice(separatorIndex + 1);
switch (name) {
case 'shards':
options.shards = parsePositiveInteger('--shards', value);
break;
case 'shard-index':
options.shardIndex = parsePositiveInteger('--shard-index', value);
break;
case 'concurrency':
options.concurrency = parsePositiveInteger('--concurrency', value);
break;
case 'manifest':
options.manifestPath = path.resolve(process.cwd(), value);
break;
case 'target-kind':
if (value !== 'bin' && value !== 'lib') {
fail(`--target-kind must be bin or lib, received: ${value}`);
}
options.targetKind = value;
break;
case 'bin':
options.binName = value;
break;
case 'package':
options.packageName = value;
break;
case 'shard-tmp-root':
options.shardTmpRoot = path.resolve(process.cwd(), value);
break;
default:
fail(`unexpected argument: ${rawArgument}`);
}
}
if (!fs.existsSync(options.manifestPath)) {
fail(`manifest does not exist: ${options.manifestPath}`);
}
if (options.shardIndex !== undefined && options.shardIndex > options.shards) {
fail(
`--shard-index (${options.shardIndex}) must be within --shards (${options.shards})`,
);
}
const concurrency = options.concurrency ?? options.shards;
const crateRoot = path.dirname(options.manifestPath);
function buildCargoArguments(target) {
const cargoArguments = ['test'];
if (options.locked) {
cargoArguments.push('--locked');
}
cargoArguments.push('--manifest-path', options.manifestPath);
if (target.kind === 'lib') {
if (options.packageName !== undefined) {
cargoArguments.push('-p', options.packageName);
}
cargoArguments.push('--lib');
return cargoArguments;
}
cargoArguments.push('--bin', target.name);
return cargoArguments;
}
function formatDuration(milliseconds) {
return `${(milliseconds / 1000).toFixed(1)}s`;
}
// 编译一次,直接拿到测试可执行文件:后续每片都运行同一个二进制,不再各自调用 cargo,
// 免得 N 个 cargo 去争 package cache 与 target 目录锁。
function resolveTestExecutable() {
return new Promise((resolve, reject) => {
const cargoArguments = buildCargoArguments({
kind: options.targetKind,
name: options.binName,
});
cargoArguments.push('--no-run', '--message-format=json');
console.log(`[rust-shards] cargo ${cargoArguments.join(' ')}`);
const child = spawn('cargo', cargoArguments, {
cwd: crateRoot,
env: process.env,
stdio: ['ignore', 'pipe', 'inherit'],
});
let buffered = '';
const executables = [];
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
buffered += chunk;
const lines = buffered.split('\n');
buffered = lines.pop() ?? '';
for (const line of lines) {
if (!line.startsWith('{')) {
continue;
}
let message;
try {
message = JSON.parse(line);
} catch {
continue;
}
if (
message.reason === 'compiler-artifact' &&
typeof message.executable === 'string'
) {
executables.push(message.executable);
}
}
});
child.on('error', (error) => {
reject(new Error(`unable to start cargo: ${error.message}`));
});
child.on('close', (code) => {
if (code !== 0) {
reject(
new Error(
`cargo ${cargoArguments.join(' ')} failed with exit code ${code}`,
),
);
return;
}
const uniqueExecutables = [...new Set(executables)];
if (uniqueExecutables.length !== 1) {
reject(
new Error(
`expected exactly one test executable for the ${options.targetKind} target, found ${uniqueExecutables.length}: ${uniqueExecutables.join(', ')}`,
),
);
return;
}
resolve(uniqueExecutables[0]);
});
});
}
function listTestNames(executable) {
return new Promise((resolve, reject) => {
const child = spawn(executable, ['--list'], {
cwd: crateRoot,
env: process.env,
stdio: ['ignore', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
stdout += chunk;
});
child.stderr.on('data', (chunk) => {
stderr += chunk;
});
child.on('error', (error) => {
reject(new Error(`unable to list tests: ${error.message}`));
});
child.on('close', (code) => {
if (code !== 0) {
reject(
new Error(
`listing tests failed with exit code ${code}: ${stderr.trim()}`,
),
);
return;
}
const names = [];
for (const line of stdout.split('\n')) {
const match = /^(.*): test$/.exec(line.trim());
if (match !== null && match[1].length > 0) {
names.push(match[1]);
}
}
resolve(names);
});
});
}
function splitTestNames(testNames, shardCount) {
const sortedNames = [...testNames].sort();
const shards = Array.from({ length: shardCount }, () => []);
sortedNames.forEach((testName, index) => {
shards[index % shardCount].push(testName);
});
return shards;
}
function assertShardsCoverEveryTest(testNames, shards) {
const flattened = shards.flat();
if (flattened.length !== testNames.length) {
fail(
`shard split covered ${flattened.length} of ${testNames.length} tests; the split rule must be exhaustive`,
);
}
if (new Set(flattened).size !== flattened.length) {
fail(
'shard split selected the same test more than once; the split rule must be disjoint',
);
}
const missing = testNames.filter((testName) => !flattened.includes(testName));
if (missing.length > 0) {
fail(
`shard split is missing tests, for example: ${missing.slice(0, 5).join(', ')}`,
);
}
}
function runShard(executable, shardIndex, shardCount, shardTestNames) {
const label = `shard ${shardIndex + 1}/${shardCount}`;
const shardTmpDirectory = path.join(
options.shardTmpRoot,
`shard-${shardIndex + 1}`,
);
fs.rmSync(shardTmpDirectory, { recursive: true, force: true });
fs.mkdirSync(shardTmpDirectory, { recursive: true });
const startedAt = Date.now();
return new Promise((resolve) => {
const child = spawn(
executable,
['--exact', ...shardTestNames, '--test-threads=1'],
{
cwd: crateRoot,
env: {
...process.env,
TMPDIR: shardTmpDirectory,
TMP: shardTmpDirectory,
TEMP: shardTmpDirectory,
},
stdio: ['ignore', 'pipe', 'pipe'],
},
);
const failureLines = [];
let inFailureList = false;
let stderr = '';
const consumeLine = (rawLine) => {
const line = rawLine.replace(/\r$/, '');
if (line.includes('failures:')) {
inFailureList = true;
return;
}
if (inFailureList) {
if (line.trim().length === 0) {
inFailureList = false;
return;
}
failureLines.push(line.trim());
}
};
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
let stdoutBuffer = '';
child.stdout.on('data', (chunk) => {
stdoutBuffer += chunk;
const lines = stdoutBuffer.split('\n');
stdoutBuffer = lines.pop() ?? '';
for (const line of lines) {
consumeLine(line);
}
});
child.stderr.on('data', (chunk) => {
stderr += chunk;
});
child.on('error', (error) => {
resolve({
label,
ok: false,
durationMs: Date.now() - startedAt,
testCount: shardTestNames.length,
failures: [`unable to start test binary: ${error.message}`],
stderr,
});
});
child.on('close', (code) => {
resolve({
label,
ok: code === 0,
durationMs: Date.now() - startedAt,
testCount: shardTestNames.length,
failures: failureLines,
stderr,
});
});
});
}
async function runWithConcurrency(shards, runner) {
const results = new Array(shards.length);
let nextIndex = 0;
const workers = Array.from(
{ length: Math.min(concurrency, shards.length) },
async () => {
while (nextIndex < shards.length) {
const index = nextIndex;
nextIndex += 1;
results[index] = await runner(shards[index], index);
}
},
);
await Promise.all(workers);
return results;
}
async function main() {
const executable = await resolveTestExecutable();
const testNames = await listTestNames(executable);
if (testNames.length === 0) {
fail(
`no tests discovered in ${options.manifestPath} (${options.targetKind})`,
);
}
const shards = splitTestNames(testNames, options.shards);
assertShardsCoverEveryTest(testNames, shards);
const selectedShards =
options.shardIndex === undefined
? shards.map((shardTestNames, index) => ({ index, shardTestNames }))
: [
{
index: options.shardIndex - 1,
shardTestNames: shards[options.shardIndex - 1],
},
];
if (options.shardIndex === undefined) {
console.log(
`[rust-shards] ${testNames.length} tests, ${shards.length} shard(s), concurrency ${Math.min(concurrency, shards.length)}`,
);
for (const { index, shardTestNames } of selectedShards) {
console.log(
`[rust-shards] shard ${index + 1}/${shards.length}: ${shardTestNames.length} test(s)`,
);
}
} else {
console.log(
`[rust-shards] ${testNames.length} tests, ${shards.length} shard(s), running shard ${options.shardIndex}/${shards.length} (${selectedShards[0].shardTestNames.length} test(s))`,
);
}
const results = await runWithConcurrency(
selectedShards,
({ index, shardTestNames }) =>
runShard(executable, index, shards.length, shardTestNames),
);
let failed = false;
for (const result of results) {
if (result.ok) {
console.log(
`[rust-shards] ${result.label} ok: ${result.testCount} test(s) in ${formatDuration(result.durationMs)}`,
);
continue;
}
failed = true;
console.error(
`[rust-shards] ${result.label} FAILED: ${result.testCount} test(s) in ${formatDuration(result.durationMs)}`,
);
for (const failure of result.failures) {
console.error(`[rust-shards] ${failure}`);
}
if (result.stderr.trim().length > 0) {
console.error(
`[rust-shards] stderr: ${result.stderr.trim().split('\n').slice(-20).join('\n[rust-shards] ')}`,
);
}
}
if (failed) {
process.exit(1);
}
console.log('[rust-shards] OK');
}
main().catch((error) => {
fail(error instanceof Error ? error.message : String(error));
});
@@ -8,7 +8,6 @@ export const SKILL_PACK_SCHEMA_VERSION = 'agc-skill-pack.v1';
export const EXPECTED_SKILL_NAMES = Object.freeze([
'agc-browser-playtest',
'agc-client-projection',
'agc-game-production-workflow',
'agc-project-structure',
'agc-web-game-development',
'taonier-art-assets',
+1 -1
View File
@@ -1725,7 +1725,7 @@ dependencies = [
[[package]]
name = "genarrative-ai-game-creator-shell"
version = "0.1.29"
version = "0.1.27"
dependencies = [
"agent-runtime-core",
"axum",
@@ -1,6 +1,6 @@
[package]
name = "genarrative-ai-game-creator-shell"
version = "0.1.29"
version = "0.1.27"
edition = "2021"
publish = false
@@ -1,26 +0,0 @@
---
name: agc-game-production-workflow
description: Orchestrate a complete AGC game delivery from an approved brief to a playable, art-integrated, browser-validated product. Use when creating a new game, implementing a substantial game brief, or turning a planning document into a finished game.
---
# AGC Game Production Workflow
Use this Skill as the top-level SOP for a new game or a substantial game brief. The tools are stages in one delivery chain, not independent suggestions. Do not stop after producing a plan, after writing code, or after generating an image.
## Stage flow
1. **Brief and scope** — Read the current planning output and project instructions. Extract the game loop, player actions, entities, visual requirements, target viewports, and the completion evidence. If the brief is incomplete, ask focused questions before side effects.
2. **Project and asset inventory** — Inspect the existing project structure and call `agc_list_registered_assets` (and `agc_list_project_files` when needed). Record which requested visuals already have usable registered identities and which are missing. Do not invent asset identities from filenames.
3. **Visual production** — For missing or unsuitable visuals, call the reviewed `agc_tools` workflow: use `taonier_prepare_game_art` for a complete package, or `agc_generate_image` / `agc_edit_image` for focused assets. Read returned paths, identities, and warnings. A warning or partial package requires a narrower retry or independent assets before continuing.
4. **Game implementation** — Implement the complete playable loop and wire the returned project-relative asset paths into the actual runtime. Every required character, object, background, effect, and UI visual must have a real source or an explicit brief-level decision to remain code-native. Generated assets that are unused, documentation-only, or replaced by emoji/CSS placeholders do not satisfy this stage.
5. **Build and local verification** — Run the projects bootstrap/install and verify/build commands. Confirm the actual playable entry under `dist` (or the editor runtime for a supported editor project) and fix build or asset-loading failures before preview.
6. **Browser playtest** — Call `agc_browser_playtest` for desktop and mobile evidence after meaningful changes. Check the game loop, input, layout, asset loading, and visible use of the generated art. Fix findings and repeat stages 46 until the evidence is clean.
7. **Delivery** — Report the implemented behavior, real asset paths and identities used, build result, playtest evidence, warnings, and any explicit remaining gap. Do not claim complete while a required stage is failed, skipped without the briefs justification, or missing evidence.
## Stage transitions
Advance only when the current stage has its output: brief → inventory; inventory → art decision; art decision → usable registered assets or an explicit no-art decision; implementation → source references to those assets; build → playable entry; playtest → evidence; delivery → truthful report. If a tool fails, preserve its error and stop or repair at that stage instead of silently substituting a later-stage placeholder.
For a small edit to an existing game where the brief and suitable assets are unchanged, use the focused edit path and do not regenerate art. This exception does not apply to a new game or a substantial planning brief.
Read the referenced specialist Skills for their detailed contracts: `agc-project-structure`, `taonier-art-assets`, `agc-web-game-development`, `agc-client-projection`, and `agc-browser-playtest`.
@@ -1,4 +0,0 @@
interface:
display_name: "完整游戏生产流程"
short_description: "从策划案到真实美术接入和试玩验收的连续交付"
default_prompt: "Use $agc-game-production-workflow to take the current game brief through inventory, art, implementation, build, playtest, and delivery."
@@ -1,5 +0,0 @@
# Workflow contract
The production Skill owns sequencing and transition evidence. Specialist Skills own the detailed safety and data rules for each tool family. A specialist tool result is never a delivery result by itself: image generation must be followed by registered identity inspection and runtime integration; code writing must be followed by build verification; a successful preview launch must be followed by desktop and mobile playtest evidence when the brief targets both.
The no-art exception is valid only when the brief explicitly requests a code-native visual treatment or the inventory proves that all required visuals are already registered and suitable. Emoji, CSS primitives, random local files, and generated files that are not referenced by the runtime are not evidence of an integrated art package.
@@ -14,7 +14,7 @@ Implement the user's actual game request in the current project as an npm-manage
3. Build with the project's npm script before previewing. The playable entry is the package directory's `dist/index.html`; never report an unbuilt bare-module page as playable. Import assets or configure public assets so all runtime media is included in dist; preview and exports cannot read outside it.
4. Build a complete playable loop: visible objective, responsive input, meaningful state changes, success or failure feedback, and a reliable restart path where the game needs one.
5. Fit the active game scene to desktop and mobile viewports without accidental page scrollbars. Reserve deliberate safe space for HUD elements instead of covering interactive content.
6. Invoke `taonier-art-assets` for every new game brief that needs visual assets. First reuse suitable registered Taonier art; when the brief's required visual elements are missing or unsuitable, call the reviewed `agc_tools` generation/edit workflow in the same task. After the tool returns, wire its relative paths into the game and verify the rendered result. A game with unused generated assets or placeholder emoji/CSS where requested art should appear is not complete. Load media defensively only for genuinely optional effects, and never relabel a local placeholder as platform art.
6. Reuse registered Taonier art when available through `agc_tools`. Load media defensively and keep gameplay usable when an optional derivative is absent; never relabel a local placeholder as platform art.
7. Let Phaser own the render loop and input dispatch. Avoid duplicate scenes, stale event listeners, and state that survives restart unintentionally.
8. After a meaningful game change, use the browser playtest Skill and fix issues shown by real evidence before reporting completion.
@@ -1,29 +1,7 @@
{
"schemaVersion": "agc-skill-pack.v1",
"version": "2026-08-26.13",
"version": "2026-08-26.12",
"skills": [
{
"name": "agc-game-production-workflow",
"purpose": "把完整游戏从策划案按阶段推进到真实素材接入、构建、试玩和交付",
"triggers": [
"从策划案创建完整游戏",
"实现完整游戏交付",
"需要衔接策划、素材、代码、构建和试玩"
],
"requiredTools": [
"agc_tools.agc_list_registered_assets",
"agc_tools.agc_generate_image",
"agc_tools.agc_edit_image",
"agc_tools.taonier_prepare_game_art",
"agc_tools.agc_browser_playtest"
],
"files": [
"SKILL.md",
"agents/openai.yaml",
"references/workflow-contract.md"
],
"sha256": "91082fdff4123f1e1fcf930af433cbea51a8c9d26991678b19028b344ea49f39"
},
{
"name": "agc-project-structure",
"purpose": "约束当前项目根、游戏代码、美术素材与客户端状态的职责边界",
@@ -53,7 +31,6 @@
"已有陶泥儿素材需要接入玩法"
],
"requiredTools": [
"agc_tools.agc_list_registered_assets",
"agc_tools.agc_generate_image",
"agc_tools.agc_edit_image",
"agc_tools.taonier_prepare_game_art"
@@ -63,7 +40,7 @@
"agents/openai.yaml",
"references/platform-art-contract.md"
],
"sha256": "bd1e415aac0cd0f97090296f34c67898dd731d1e177ec91a56027f9b68a88b37"
"sha256": "82e4b2ee8ca8147b51ca206b0565b3cc244dc5d3cddb8343875001c0beb4711f"
},
{
"name": "agc-web-game-development",
@@ -80,7 +57,7 @@
"agents/openai.yaml",
"references/game-quality-checklist.md"
],
"sha256": "05b5cfbf7a40fd303717491f5cea84ff339a73359c9678b283fd54d2b5c45efd"
"sha256": "0649c72dd53e05ad7c87b28def1397c2badf61b0c308091196c40f7c48a8b36a"
},
{
"name": "agc-browser-playtest",
@@ -5,15 +5,7 @@ description: Prepare, recover, inspect, and integrate real Taonier platform game
# Taonier Art Assets
Use real platform assets only through the reviewed `agc_tools` MCP server. When
building a new game from a brief that names characters, objects, backgrounds,
effects, or other visual elements, this Skill is an execution step: inspect
existing assets, generate or reuse suitable art, process it when needed, and
integrate the returned paths into the playable game before reporting the game
complete. Do not treat the art step as optional merely because the user did
not repeat “生图” in the latest message.
Use
Use real platform assets only through the reviewed `agc_tools` MCP server. Use
`agc_generate_image` for a single ordinary image, character image, visual-spec
image, UI design image, or publication material; use `agc_edit_image` for an
edit of an existing registered image; use `taonier_prepare_game_art` only for
@@ -25,11 +17,11 @@ the complete game-art package and its canonical slices.
## Workflow
1. Inspect existing `assets/` and registered project evidence before requesting new art. Reuse suitable assets when they satisfy the current brief. If the brief requires visual elements that are absent or unsuitable, call the appropriate generation tool during the same game implementation task; do not continue with placeholder art and silently defer generation.
1. Inspect existing `assets/` and registered project evidence before requesting new art. Reuse suitable assets when the user did not ask to regenerate them.
2. For one new image, call `agc_generate_image` with `kind="image"` (or `character`, `icon-spec`, `ui-prototype`, or `publication-material` when that is the explicit intent). For changes to an existing registered image, call `agc_edit_image` with its `sourceLocalAssetId`; do not fake an edit with a new-image request. For a complete game-art package, call `taonier_prepare_game_art` only when the current intent requires new or recoverable platform art. Use `mode="regenerate"` only after the latest User message is a standalone reviewed immediate-confirmation command such as `请重新生成美术`; punctuation may end it, but no brief, condition, negation, alternative, cost qualifier, deferral, or other text may accompany it. Describe the desired style and gameplay constraints in an earlier non-billable turn, then obtain the standalone confirmation turn; otherwise use `mode="reuse-or-create"`. Quoted UI copy or examples, explanations, questions, historical wording, model/MCP arguments do not authorize regeneration. Pass a concise game-specific visual brief that names the required gameplay entities, background exclusions, tiling needs, and viewport constraints. Do not call either generation tool for greetings, date questions, or text-only code fixes.
3. Treat the tool result as authoritative. Read `mode`, `assetPaths`, `slicePaths`, `resources`, and every entry in both `warnings` and `sliceWarnings`. `resources` is the client's safe projection of registered Canvas identities; use only its returned relative paths and identities. Never invent a resource, slice, platform identity, warning-free result, or successful regeneration.
4. A newly created or explicitly regenerated standard package is complete only when `slicePaths` contains the four canonical independent slices. An empty or partial `slicePaths` result never satisfies an independent-asset requirement: if a `sliceWarning` reports too many or unusable elements, narrow the edit/generation brief or generate the needed independent images and continue the integration; do not guess atlas coordinates, fabricate derivatives, or silently fall back to placeholders. A trusted legacy complete sheet may still be used without slices only when the current request does not require independent assets.
5. Inspect the returned background, complete sheet, and available slice previews before integrating them. Then use suitable returned runtime assets in the game's actual visible experience and confirm their visible use in desktop and mobile playtest evidence. The implementation is incomplete while generated assets remain unused, are referenced only by documentation, or are replaced by emoji, CSS shapes, or other placeholders where the brief requires the generated art. `art-spec.png` is a reference specification, not a runtime background, character, prop, or effect. Background exclusions, seamless tiling, entity semantics, and final draw dimensions are visual/runtime acceptance checks; a prompt alone does not prove them. A hidden or side-panel preview does not count as gameplay use.
4. A newly created or explicitly regenerated standard package is complete only when `slicePaths` contains the four canonical independent slices. An empty or partial `slicePaths` result never satisfies an independent-asset requirement; stop and report the warning instead of guessing atlas coordinates or fabricating derivatives. A trusted legacy complete sheet may still be used without slices only when the current request does not require independent assets.
5. Inspect the returned background, complete sheet, and available slice previews before integrating them. Then use suitable returned runtime assets in the game's actual visible experience and confirm their visible use in desktop and mobile playtest evidence. `art-spec.png` is a reference specification, not a runtime background, character, prop, or effect. Background exclusions, seamless tiling, entity semantics, and final draw dimensions are visual/runtime acceptance checks; a prompt alone does not prove them. A hidden or side-panel preview does not count as gameplay use.
6. Preserve warning details in the final report. If the tool reports missing credentials, uncertain operation state, invalid provenance, download failure, or decode failure, stop and report the actionable reason; do not substitute generated CSS shapes and call the platform step complete.
Before interpreting async recovery, source-preserved warnings, or slice warnings, read `references/platform-art-contract.md`.
@@ -4999,7 +4999,7 @@ case "$extra_roots" in *'"method":"skills/extraRoots/set"'*) ;; *) exit 87 ;; es
printf '%s\n' '{"id":2,"result":{}}'
IFS= read -r skills_list
case "$skills_list" in *'"method":"skills/list"'*) ;; *) exit 88 ;; esac
printf '%s\n' '{"id":3,"result":{"data":[{"skills":[{"name":"agc-browser-playtest"},{"name":"agc-client-projection"},{"name":"agc-game-production-workflow"},{"name":"agc-project-structure"},{"name":"agc-web-game-development"},{"name":"taonier-art-assets"}],"errors":[]}]}}'
printf '%s\n' '{"id":3,"result":{"data":[{"skills":[{"name":"agc-browser-playtest"},{"name":"agc-client-projection"},{"name":"agc-project-structure"},{"name":"agc-web-game-development"},{"name":"taonier-art-assets"}],"errors":[]}]}}'
while IFS= read -r line; do :; done
"#,
)
@@ -795,15 +795,10 @@ pub(crate) async fn continue_design_agent_at(
.ok_or("策划 Agent 当前正在工作")?;
let mut session = match read_design_session(root)? {
Some(session) => session,
None => {
if read_planning_session_v2(root)?.is_some() {
return Err("此项目包含旧策划会话,请查看原有记录或在新项目开始五阶段策划".into());
}
new_design_session(
&project_id,
&load_game_creator_app_config()?.selected_model_id,
)
}
None => new_design_session(
&project_id,
&load_game_creator_app_config()?.selected_model_id,
),
};
if session.project_id != project_id {
return Err("策划会话与当前项目不匹配".into());
File diff suppressed because one or more lines are too long
@@ -1156,10 +1156,6 @@ mod tests {
assert!(with_canvas.contains("根据当前玩法需求编写规格和界面建议"));
assert!(with_canvas.contains("用途、数量、输出路径、尺寸、参考资源和是否需要 spritesheet"));
assert!(with_canvas.contains("再调用 canvas.asset_generate"));
assert!(with_canvas.contains("调用 canvas.asset_generate"));
assert!(with_canvas.contains("不要使用固定图片合同"));
assert!(with_canvas.contains("不修改 game/index.html"));
assert!(!with_canvas.contains("不调用 canvas.asset_generate"));
}
#[test]
@@ -801,7 +801,7 @@ fn agent_runtime_action_receipt_safe_detail_with_owner(
let initial_step = route.get("initialStep")?.as_str()?;
let render_mode = route.get("renderMode")?.as_str()?;
if resource_id.is_empty()
|| initial_step != "asset-separation"
|| initial_step != "visual-binding"
|| render_mode != "final-preview"
{
return None;
@@ -78,12 +78,6 @@ pub(super) fn game_creator_agent_final_reply_error_allows_fallback(error: &str)
matches!(kind.as_str(), "empty-response" | "deserialize")
}
/// 这些错误只描述本次 Provider input 或候选 GDD;真正的 session CAS 冲突不在
/// 此列——那说明 durable session 已被推进或损坏,必须 reconcile。
fn plan_submit_error_is_business_rejection(error: &PlanningStorageError) -> bool {
matches!(error.code(), "PLAN_INVALID_REQUEST" | "PLAN_SIZE_LIMIT")
}
const AGENT_RUNTIME_PLAN_UPDATE_IDLE_LIMIT: u32 = 4;
/// 最终回复被收束门禁拦下后 run 会原地续跑重试。多数 blocker 是模型自己能解的
@@ -7,9 +7,6 @@ mod design_session;
mod finalization;
mod json_sidecar;
mod models;
mod planning_gdd_model;
mod planning_policy_v2;
mod planning_session_v2;
mod provider_control;
mod provider_retry;
mod real_e2e_checkpoint;
@@ -24,9 +21,6 @@ pub(crate) use design_session::*;
pub(in crate::agent) use finalization::*;
pub(in crate::agent) use json_sidecar::*;
pub(in crate::agent) use models::*;
pub(crate) use planning_gdd_model::*;
pub(crate) use planning_policy_v2::*;
pub(crate) use planning_session_v2::*;
pub(in crate::agent) use provider_control::*;
pub(in crate::agent) use provider_retry::*;
pub(in crate::agent) use real_e2e_checkpoint::*;
@@ -6,16 +6,15 @@ use std::path::{Component, Path};
const AGC_SKILL_PACK_MANIFEST: &[u8] = include_bytes!("../../resources/agc-skills/manifest.json");
const AGC_SKILL_PACK_SCHEMA_VERSION: &str = "agc-skill-pack.v1";
pub(crate) const AGC_SKILL_PACK_EXPECTED_NAMES: [&str; 6] = [
pub(crate) const AGC_SKILL_PACK_EXPECTED_NAMES: [&str; 5] = [
"agc-browser-playtest",
"agc-client-projection",
"agc-game-production-workflow",
"agc-project-structure",
"agc-web-game-development",
"taonier-art-assets",
];
const AGC_SKILL_PACK_FILES: [(&str, &[u8]); 18] = [
const AGC_SKILL_PACK_FILES: [(&str, &[u8]); 15] = [
(
"agc-browser-playtest/SKILL.md",
include_bytes!("../../resources/agc-skills/agc-browser-playtest/SKILL.md"),
@@ -44,20 +43,6 @@ const AGC_SKILL_PACK_FILES: [(&str, &[u8]); 18] = [
"../../resources/agc-skills/agc-client-projection/references/projection-contract.md"
),
),
(
"agc-game-production-workflow/SKILL.md",
include_bytes!("../../resources/agc-skills/agc-game-production-workflow/SKILL.md"),
),
(
"agc-game-production-workflow/agents/openai.yaml",
include_bytes!("../../resources/agc-skills/agc-game-production-workflow/agents/openai.yaml"),
),
(
"agc-game-production-workflow/references/workflow-contract.md",
include_bytes!(
"../../resources/agc-skills/agc-game-production-workflow/references/workflow-contract.md"
),
),
(
"agc-project-structure/SKILL.md",
include_bytes!("../../resources/agc-skills/agc-project-structure/SKILL.md"),
@@ -306,10 +291,10 @@ mod tests {
use super::*;
#[test]
fn bundled_skill_pack_is_exactly_the_six_reviewed_skills() {
fn bundled_skill_pack_is_exactly_the_five_reviewed_skills() {
let manifest = validated_skill_pack_manifest().expect("validated manifest");
assert_eq!(manifest.schema_version, "agc-skill-pack.v1");
assert_eq!(manifest.skills.len(), 6);
assert_eq!(manifest.skills.len(), 5);
assert!(manifest.skills.iter().all(|entry| entry.sha256.len() == 64));
let serialized = serde_json::to_string(
&manifest

Some files were not shown because too many files have changed in this diff Show More