统一 Rust 与 TypeScript 格式化门禁
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口 完成项目 TypeScript/Prettier 与 Rust 全量格式化 修复 Pingora expected executable 门禁的空白敏感误报 同步开发运维文档与 AGC skill pack 格式化忽略规则
This commit is contained in:
@@ -1 +1 @@
|
|||||||
{"containers":[],"config":{}}
|
{ "containers": [], "config": {} }
|
||||||
|
|||||||
@@ -7,23 +7,33 @@ import { fileURLToPath } from 'node:url';
|
|||||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||||
const repoRoot = resolve(scriptDir, '..', '..');
|
const repoRoot = resolve(scriptDir, '..', '..');
|
||||||
const logDir = resolve(repoRoot, '.codex', 'logs');
|
const logDir = resolve(repoRoot, '.codex', 'logs');
|
||||||
const hasCodegraphConfig = existsSync(resolve(repoRoot, '.codegraph', 'config.json'));
|
const hasCodegraphConfig = existsSync(
|
||||||
|
resolve(repoRoot, '.codegraph', 'config.json'),
|
||||||
|
);
|
||||||
const npmCommand = process.platform === 'win32' ? 'cmd' : 'npm';
|
const npmCommand = process.platform === 'win32' ? 'cmd' : 'npm';
|
||||||
|
|
||||||
if (!hasCodegraphConfig) {
|
if (!hasCodegraphConfig) {
|
||||||
console.log('[codex-hook] 未发现 .codegraph/config.json,跳过 CodeGraph 同步。');
|
console.log(
|
||||||
|
'[codex-hook] 未发现 .codegraph/config.json,跳过 CodeGraph 同步。',
|
||||||
|
);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = spawnSync(npmCommand, process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run codegraph:sync'] : ['run', 'codegraph:sync'], {
|
const result = spawnSync(
|
||||||
cwd: repoRoot,
|
npmCommand,
|
||||||
shell: false,
|
process.platform === 'win32'
|
||||||
encoding: 'utf8',
|
? ['/d', '/s', '/c', 'npm run codegraph:sync']
|
||||||
env: {
|
: ['run', 'codegraph:sync'],
|
||||||
...process.env,
|
{
|
||||||
NO_COLOR: process.env.NO_COLOR ?? '1',
|
cwd: repoRoot,
|
||||||
|
shell: false,
|
||||||
|
encoding: 'utf8',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
NO_COLOR: process.env.NO_COLOR ?? '1',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
mkdirSync(logDir, { recursive: true });
|
mkdirSync(logDir, { recursive: true });
|
||||||
if (result.stdout) {
|
if (result.stdout) {
|
||||||
@@ -44,7 +54,9 @@ if (result.signal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((result.status ?? 0) !== 0) {
|
if ((result.status ?? 0) !== 0) {
|
||||||
console.error('[codex-hook] CodeGraph 同步失败,请手动运行 npm run codegraph:sync 查看详情。');
|
console.error(
|
||||||
|
'[codex-hook] CodeGraph 同步失败,请手动运行 npm run codegraph:sync 查看详情。',
|
||||||
|
);
|
||||||
process.exit(result.status ?? 1);
|
process.exit(result.status ?? 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,16 +28,11 @@
|
|||||||
"longDescription": "Plan, prototype, and build browser games with guided workflows for gameplay systems, UI, asset pipelines, and playtesting across 2D and 3D projects.",
|
"longDescription": "Plan, prototype, and build browser games with guided workflows for gameplay systems, UI, asset pipelines, and playtesting across 2D and 3D projects.",
|
||||||
"developerName": "OpenAI",
|
"developerName": "OpenAI",
|
||||||
"category": "Coding",
|
"category": "Coding",
|
||||||
"capabilities": [
|
"capabilities": ["Interactive", "Write"],
|
||||||
"Interactive",
|
|
||||||
"Write"
|
|
||||||
],
|
|
||||||
"websiteURL": "https://openai.com/",
|
"websiteURL": "https://openai.com/",
|
||||||
"privacyPolicyURL": "https://openai.com/policies/privacy-policy/",
|
"privacyPolicyURL": "https://openai.com/policies/privacy-policy/",
|
||||||
"termsOfServiceURL": "https://openai.com/policies/terms-of-use/",
|
"termsOfServiceURL": "https://openai.com/policies/terms-of-use/",
|
||||||
"defaultPrompt": [
|
"defaultPrompt": ["Design a browser game and plan the core loop"],
|
||||||
"Design a browser game and plan the core loop"
|
|
||||||
],
|
|
||||||
"brandColor": "#0F766E",
|
"brandColor": "#0F766E",
|
||||||
"composerIcon": "./assets/game-studio.svg",
|
"composerIcon": "./assets/game-studio.svg",
|
||||||
"logo": "./assets/app-icon.png",
|
"logo": "./assets/app-icon.png",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
name: game-studio
|
name: game-studio
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
description: Design, prototype, and ship browser games with guided 2D and 3D workflows,
|
description:
|
||||||
|
Design, prototype, and ship browser games with guided 2D and 3D workflows,
|
||||||
asset pipelines, and playtesting support.
|
asset pipelines, and playtesting support.
|
||||||
author: OpenAI
|
author: OpenAI
|
||||||
kind: standalone
|
kind: standalone
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ Use this as the canonical minimal pattern for loading shipped 3D content.
|
|||||||
## Vanilla Three.js
|
## Vanilla Three.js
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
||||||
|
|
||||||
const draco = new DRACOLoader();
|
const draco = new DRACOLoader();
|
||||||
draco.setDecoderPath("/draco/");
|
draco.setDecoderPath('/draco/');
|
||||||
|
|
||||||
const gltfLoader = new GLTFLoader();
|
const gltfLoader = new GLTFLoader();
|
||||||
gltfLoader.setDRACOLoader(draco);
|
gltfLoader.setDRACOLoader(draco);
|
||||||
|
|
||||||
gltfLoader.load("/assets/hero.glb", (gltf) => {
|
gltfLoader.load('/assets/hero.glb', (gltf) => {
|
||||||
const root = gltf.scene;
|
const root = gltf.scene;
|
||||||
root.traverse((node) => {
|
root.traverse((node) => {
|
||||||
if ("castShadow" in node) {
|
if ('castShadow' in node) {
|
||||||
node.castShadow = true;
|
node.castShadow = true;
|
||||||
node.receiveShadow = true;
|
node.receiveShadow = true;
|
||||||
}
|
}
|
||||||
@@ -29,10 +29,10 @@ gltfLoader.load("/assets/hero.glb", (gltf) => {
|
|||||||
## React Three Fiber
|
## React Three Fiber
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useGLTF } from "@react-three/drei";
|
import { useGLTF } from '@react-three/drei';
|
||||||
|
|
||||||
function HeroModel() {
|
function HeroModel() {
|
||||||
const gltf = useGLTF("/assets/hero.glb");
|
const gltf = useGLTF('/assets/hero.glb');
|
||||||
return <primitive object={gltf.scene} />;
|
return <primitive object={gltf.scene} />;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ Use this as the smallest canonical pattern for adding physics without letting it
|
|||||||
## Vanilla Three.js
|
## Vanilla Three.js
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import RAPIER from "@dimforge/rapier3d-compat";
|
import RAPIER from '@dimforge/rapier3d-compat';
|
||||||
|
|
||||||
await RAPIER.init();
|
await RAPIER.init();
|
||||||
|
|
||||||
const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 });
|
const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 });
|
||||||
const body = world.createRigidBody(RAPIER.RigidBodyDesc.dynamic().setTranslation(0, 2, 0));
|
const body = world.createRigidBody(
|
||||||
|
RAPIER.RigidBodyDesc.dynamic().setTranslation(0, 2, 0),
|
||||||
|
);
|
||||||
world.createCollider(RAPIER.ColliderDesc.cuboid(0.5, 0.5, 0.5), body);
|
world.createCollider(RAPIER.ColliderDesc.cuboid(0.5, 0.5, 0.5), body);
|
||||||
|
|
||||||
renderer.setAnimationLoop(() => {
|
renderer.setAnimationLoop(() => {
|
||||||
@@ -24,7 +26,7 @@ renderer.setAnimationLoop(() => {
|
|||||||
## React Three Fiber
|
## React Three Fiber
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { Physics, RigidBody } from "@react-three/rapier";
|
import { Physics, RigidBody } from '@react-three/rapier';
|
||||||
|
|
||||||
<Physics gravity={[0, -9.81, 0]}>
|
<Physics gravity={[0, -9.81, 0]}>
|
||||||
<RigidBody colliders="cuboid">
|
<RigidBody colliders="cuboid">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ src/
|
|||||||
## `src/App.tsx`
|
## `src/App.tsx`
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { Canvas } from "@react-three/fiber";
|
import { Canvas } from '@react-three/fiber';
|
||||||
|
|
||||||
function Spinner() {
|
function Spinner() {
|
||||||
return (
|
return (
|
||||||
@@ -27,7 +27,7 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<Canvas camera={{ position: [0, 1.5, 4], fov: 60 }}>
|
<Canvas camera={{ position: [0, 1.5, 4], fov: 60 }}>
|
||||||
<color attach="background" args={["#101418"]} />
|
<color attach="background" args={['#101418']} />
|
||||||
<ambientLight intensity={0.7} />
|
<ambientLight intensity={0.7} />
|
||||||
<directionalLight position={[4, 6, 3]} intensity={1.2} />
|
<directionalLight position={[4, 6, 3]} intensity={1.2} />
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
|||||||
@@ -12,12 +12,17 @@ src/
|
|||||||
## `src/main.ts`
|
## `src/main.ts`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import * as THREE from "three";
|
import * as THREE from 'three';
|
||||||
|
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
scene.background = new THREE.Color("#101418");
|
scene.background = new THREE.Color('#101418');
|
||||||
|
|
||||||
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 200);
|
const camera = new THREE.PerspectiveCamera(
|
||||||
|
60,
|
||||||
|
window.innerWidth / window.innerHeight,
|
||||||
|
0.1,
|
||||||
|
200,
|
||||||
|
);
|
||||||
camera.position.set(0, 1.5, 4);
|
camera.position.set(0, 1.5, 4);
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
@@ -32,11 +37,11 @@ scene.add(light);
|
|||||||
|
|
||||||
const mesh = new THREE.Mesh(
|
const mesh = new THREE.Mesh(
|
||||||
new THREE.BoxGeometry(1, 1, 1),
|
new THREE.BoxGeometry(1, 1, 1),
|
||||||
new THREE.MeshStandardMaterial({ color: "#3dd9b8" }),
|
new THREE.MeshStandardMaterial({ color: '#3dd9b8' }),
|
||||||
);
|
);
|
||||||
scene.add(mesh);
|
scene.add(mesh);
|
||||||
|
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener('resize', () => {
|
||||||
camera.aspect = window.innerWidth / window.innerHeight;
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
camera.updateProjectionMatrix();
|
camera.updateProjectionMatrix();
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Game Playtest"
|
display_name: 'Game Playtest'
|
||||||
short_description: "Run browser-game playtests and QA"
|
short_description: 'Run browser-game playtests and QA'
|
||||||
default_prompt: "Playtest the browser game, check core interactions and visual state changes, and report concrete issues."
|
default_prompt: 'Playtest the browser game, check core interactions and visual state changes, and report concrete issues.'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Game Studio"
|
display_name: 'Game Studio'
|
||||||
short_description: "Route browser-game work to the right path"
|
short_description: 'Route browser-game work to the right path'
|
||||||
default_prompt: "Help me choose the right browser-game stack and workflow before implementation starts."
|
default_prompt: 'Help me choose the right browser-game stack and workflow before implementation starts.'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Game UI Frontend"
|
display_name: 'Game UI Frontend'
|
||||||
short_description: "Design browser-game HUDs, menus, and overlays"
|
short_description: 'Design browser-game HUDs, menus, and overlays'
|
||||||
default_prompt: "Design a browser-game UI layer that supports the play experience without crowding the playfield."
|
default_prompt: 'Design a browser-game UI layer that supports the play experience without crowding the playfield.'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Phaser 2D Game"
|
display_name: 'Phaser 2D Game'
|
||||||
short_description: "Build 2D browser games with Phaser"
|
short_description: 'Build 2D browser games with Phaser'
|
||||||
default_prompt: "Implement this 2D browser game with Phaser, TypeScript, and a clear gameplay architecture."
|
default_prompt: 'Implement this 2D browser game with Phaser, TypeScript, and a clear gameplay architecture.'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "React Three Fiber Game"
|
display_name: 'React Three Fiber Game'
|
||||||
short_description: "Build React-hosted 3D browser games"
|
short_description: 'Build React-hosted 3D browser games'
|
||||||
default_prompt: "Build this 3D browser game with React Three Fiber and keep the 3D runtime aligned with the React app shell."
|
default_prompt: 'Build this 3D browser game with React Three Fiber and keep the 3D runtime aligned with the React app shell.'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Sprite Pipeline"
|
display_name: 'Sprite Pipeline'
|
||||||
short_description: "Generate and normalize 2D sprite animations"
|
short_description: 'Generate and normalize 2D sprite animations'
|
||||||
default_prompt: "Create and normalize 2D sprite animation assets for a browser game with consistent scale and anchors."
|
default_prompt: 'Create and normalize 2D sprite animation assets for a browser game with consistent scale and anchors.'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Three WebGL Game"
|
display_name: 'Three WebGL Game'
|
||||||
short_description: "Build browser-game runtimes with Three.js"
|
short_description: 'Build browser-game runtimes with Three.js'
|
||||||
default_prompt: "Implement this browser-game runtime with plain Three.js and keep the scene architecture easy to debug."
|
default_prompt: 'Implement this browser-game runtime with plain Three.js and keep the scene architecture easy to debug.'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Web 3D Asset Pipeline"
|
display_name: 'Web 3D Asset Pipeline'
|
||||||
short_description: "Prepare and optimize browser-game 3D assets"
|
short_description: 'Prepare and optimize browser-game 3D assets'
|
||||||
default_prompt: "Prepare these browser-game 3D assets for shipping as predictable runtime-ready GLB or glTF files."
|
default_prompt: 'Prepare these browser-game 3D assets for shipping as predictable runtime-ready GLB or glTF files.'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Web Game Foundations"
|
display_name: 'Web Game Foundations'
|
||||||
short_description: "Set browser-game architecture before implementation"
|
short_description: 'Set browser-game architecture before implementation'
|
||||||
default_prompt: "Establish the core architecture for this browser game before implementation starts."
|
default_prompt: 'Establish the core architecture for this browser game before implementation starts.'
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ license: MIT
|
|||||||
metadata:
|
metadata:
|
||||||
codex:
|
codex:
|
||||||
tags: [BDD, Gherkin, 验收标准, 用户故事, 测试, Genarrative]
|
tags: [BDD, Gherkin, 验收标准, 用户故事, 测试, Genarrative]
|
||||||
related_skills: [writing-plans, test-driven-development, systematic-debugging, requesting-code-review]
|
related_skills:
|
||||||
|
[
|
||||||
|
writing-plans,
|
||||||
|
test-driven-development,
|
||||||
|
systematic-debugging,
|
||||||
|
requesting-code-review,
|
||||||
|
]
|
||||||
---
|
---
|
||||||
|
|
||||||
# BDD 行为驱动开发流程
|
# BDD 行为驱动开发流程
|
||||||
@@ -218,14 +224,14 @@ Feature: Work publish permission
|
|||||||
|
|
||||||
## 映射到测试类型
|
## 映射到测试类型
|
||||||
|
|
||||||
| BDD 场景关注点 | 推荐测试层级 | 示例 |
|
| BDD 场景关注点 | 推荐测试层级 | 示例 |
|
||||||
| --- | --- | --- |
|
| ---------------------------- | ----------------- | ------------------------------------ |
|
||||||
| 纯领域规则、状态机、校验 | Rust/TS 单元测试 | reducer、module-*、schema validator |
|
| 纯领域规则、状态机、校验 | Rust/TS 单元测试 | reducer、module-\*、schema validator |
|
||||||
| DTO 契约、API 请求响应 | API/contract 测试 | Axum handler、shared-contracts serde |
|
| DTO 契约、API 请求响应 | API/contract 测试 | Axum handler、shared-contracts serde |
|
||||||
| 页面渲染、按钮状态、表单校验 | 组件测试 | Vitest + Testing Library |
|
| 页面渲染、按钮状态、表单校验 | 组件测试 | Vitest + Testing Library |
|
||||||
| 路由、tab、页面阶段切换 | 前端集成测试 | appPageRoutes、FlowShell 行为 |
|
| 路由、tab、页面阶段切换 | 前端集成测试 | appPageRoutes、FlowShell 行为 |
|
||||||
| 登录态、发布、运行态完整链路 | E2E/smoke | Playwright 或项目 smoke 脚本 |
|
| 登录态、发布、运行态完整链路 | E2E/smoke | Playwright 或项目 smoke 脚本 |
|
||||||
| 埋点、副作用、后台导出 | 后端集成/API 测试 | tracking event、admin export |
|
| 埋点、副作用、后台导出 | 后端集成/API 测试 | tracking event、admin export |
|
||||||
|
|
||||||
原则:
|
原则:
|
||||||
|
|
||||||
@@ -255,8 +261,8 @@ describe('帮助与反馈入口', () => {
|
|||||||
// Given ...
|
// Given ...
|
||||||
// When ...
|
// When ...
|
||||||
// Then ...
|
// Then ...
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Rust 测试命名建议:
|
Rust 测试命名建议:
|
||||||
@@ -276,13 +282,13 @@ fn anonymous_user_cannot_publish_generated_draft() {
|
|||||||
|
|
||||||
### Gherkin/BDD 场景默认落点
|
### Gherkin/BDD 场景默认落点
|
||||||
|
|
||||||
| 产物类型 | 推荐路径 | 适用场景 |
|
| 产物类型 | 推荐路径 | 适用场景 |
|
||||||
| --- | --- | --- |
|
| --------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||||
| 实施前分析 / 临时计划 | 当前任务说明或 `.tmp/<task-name>-bdd-scenarios.md` | 某次 Codex 开发任务前,用于澄清行为、拆测试、辅助实现;不作为长期产品依据。 |
|
| 实施前分析 / 临时计划 | 当前任务说明或 `.tmp/<task-name>-bdd-scenarios.md` | 某次 Codex 开发任务前,用于澄清行为、拆测试、辅助实现;不作为长期产品依据。 |
|
||||||
| 正式产品验收 / PRD 场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】<功能名>BDD场景-YYYY-MM-DD.md` | 产品、测试、开发都需要长期参考的验收标准、用户故事、功能边界。 |
|
| 正式产品验收 / PRD 场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【产品验收】<功能名>BDD场景-YYYY-MM-DD.md` | 产品、测试、开发都需要长期参考的验收标准、用户故事、功能边界。 |
|
||||||
| 技术/API/领域行为场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【技术验收】<功能名>BDD场景-YYYY-MM-DD.md` | 后端 API、领域规则、状态机、SpacetimeDB reducer/table、SSE/异步任务、埋点副作用。 |
|
| 技术/API/领域行为场景 | 当前 `docs/` 融合文档,必要时新增 `docs/【技术验收】<功能名>BDD场景-YYYY-MM-DD.md` | 后端 API、领域规则、状态机、SpacetimeDB reducer/table、SSE/异步任务、埋点副作用。 |
|
||||||
| 自动化 Gherkin feature 文件 | `tests/features/*.feature` 或 `e2e/features/*.feature` | 项目已接入 Cucumber/Playwright BDD 等 Gherkin runner 时。未接入前不要随意新建测试 runner 目录。 |
|
| 自动化 Gherkin feature 文件 | `tests/features/*.feature` 或 `e2e/features/*.feature` | 项目已接入 Cucumber/Playwright BDD 等 Gherkin runner 时。未接入前不要随意新建测试 runner 目录。 |
|
||||||
| 稳定流程或团队经验 | `docs/project-memory/shared-memory/` 或 `.codex/skills/` | 不是某个功能验收,而是长期可复用的团队流程、坑点、执行规范。 |
|
| 稳定流程或团队经验 | `docs/project-memory/shared-memory/` 或 `.codex/skills/` | 不是某个功能验收,而是长期可复用的团队流程、坑点、执行规范。 |
|
||||||
|
|
||||||
默认规则:
|
默认规则:
|
||||||
|
|
||||||
@@ -313,15 +319,17 @@ e2e/features/invite-code.feature
|
|||||||
|
|
||||||
BDD 文档建议包含:
|
BDD 文档建议包含:
|
||||||
|
|
||||||
```markdown
|
````markdown
|
||||||
# <功能名> BDD 验收场景
|
# <功能名> BDD 验收场景
|
||||||
|
|
||||||
## 背景
|
## 背景
|
||||||
|
|
||||||
- 需求来源:
|
- 需求来源:
|
||||||
- 相关文档:
|
- 相关文档:
|
||||||
- 相关入口/接口:
|
- 相关入口/接口:
|
||||||
|
|
||||||
## 角色与目标
|
## 角色与目标
|
||||||
|
|
||||||
- 角色:
|
- 角色:
|
||||||
- 目标:
|
- 目标:
|
||||||
- 非目标:
|
- 非目标:
|
||||||
@@ -336,16 +344,19 @@ BDD 文档建议包含:
|
|||||||
当 ...
|
当 ...
|
||||||
那么 ...
|
那么 ...
|
||||||
```
|
```
|
||||||
|
````
|
||||||
|
|
||||||
## 测试映射
|
## 测试映射
|
||||||
|
|
||||||
| 场景 | 测试层级 | 目标文件 | 状态 |
|
| 场景 | 测试层级 | 目标文件 | 状态 |
|
||||||
| --- | --- | --- | --- |
|
| ---- | --------- | -------- | ------- |
|
||||||
| ... | component | ... | planned |
|
| ... | component | ... | planned |
|
||||||
|
|
||||||
## 开放问题
|
## 开放问题
|
||||||
|
|
||||||
- ...
|
- ...
|
||||||
```
|
|
||||||
|
````
|
||||||
|
|
||||||
注意:上面的 Markdown 模板中如果嵌套代码块,需要在真实文档里调整围栏长度,避免代码块提前闭合。
|
注意:上面的 Markdown 模板中如果嵌套代码块,需要在真实文档里调整围栏长度,避免代码块提前闭合。
|
||||||
|
|
||||||
@@ -384,7 +395,7 @@ BDD 文档建议包含:
|
|||||||
npm run check:encoding
|
npm run check:encoding
|
||||||
npm run typecheck
|
npm run typecheck
|
||||||
npm run test -- --run <相关测试文件>
|
npm run test -- --run <相关测试文件>
|
||||||
```
|
````
|
||||||
|
|
||||||
- [ ] 若涉及后端 Rust/API,按相关 DDD/SpacetimeDB 文档运行对应 cargo/npm/API smoke 验证。
|
- [ ] 若涉及后端 Rust/API,按相关 DDD/SpacetimeDB 文档运行对应 cargo/npm/API smoke 验证。
|
||||||
- [ ] 若产生长期有效经验,已同步到 `docs/project-memory/shared-memory/` 或合适的仓库级 skill。
|
- [ ] 若产生长期有效经验,已同步到 `docs/project-memory/shared-memory/` 或合适的仓库级 skill。
|
||||||
|
|||||||
@@ -4,7 +4,17 @@ description: 在 Genarrative/陶泥儿后台新增或修改管理页、后台 BF
|
|||||||
license: MIT
|
license: MIT
|
||||||
metadata:
|
metadata:
|
||||||
codex:
|
codex:
|
||||||
tags: [Genarrative, 陶泥儿后台, admin-web, 后台接口, Excel导出, Rust, Axum, SpacetimeDB]
|
tags:
|
||||||
|
[
|
||||||
|
Genarrative,
|
||||||
|
陶泥儿后台,
|
||||||
|
admin-web,
|
||||||
|
后台接口,
|
||||||
|
Excel导出,
|
||||||
|
Rust,
|
||||||
|
Axum,
|
||||||
|
SpacetimeDB,
|
||||||
|
]
|
||||||
related_skills: [genarrative-play-type-integration]
|
related_skills: [genarrative-play-type-integration]
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -206,6 +216,7 @@ npm install
|
|||||||
3. `cargo fmt --all` 可能格式化不相关 Rust 文件;提交前用 `git status` 检查并 revert 非本任务文件。
|
3. `cargo fmt --all` 可能格式化不相关 Rust 文件;提交前用 `git status` 检查并 revert 非本任务文件。
|
||||||
4. patch 工具对 Rust 单文件 lint 可能用 Rust 2015 edition 误报 `async fn is not permitted in Rust 2015`;以 `cargo test/check` 为准。
|
4. patch 工具对 Rust 单文件 lint 可能用 Rust 2015 edition 误报 `async fn is not permitted in Rust 2015`;以 `cargo test/check` 为准。
|
||||||
5. `adminRoutes` 新增 route id 后,`AdminShell.routeIcons` 必须同步,否则 TypeScript 会因 `satisfies Record<AdminRouteId, ...>` 报错。
|
5. `adminRoutes` 新增 route id 后,`AdminShell.routeIcons` 必须同步,否则 TypeScript 会因 `satisfies Record<AdminRouteId, ...>` 报错。
|
||||||
|
|
||||||
- 后台页面中的中文和 JSON 预览要避免整文件重写导致编码问题;修改后运行 `npm run check:encoding`。
|
- 后台页面中的中文和 JSON 预览要避免整文件重写导致编码问题;修改后运行 `npm run check:encoding`。
|
||||||
- 后台数据页移动端要保证表格横向滚动,不要让整页布局撑坏。
|
- 后台数据页移动端要保证表格横向滚动,不要让整页布局撑坏。
|
||||||
- 若用户追问“之前不是说要把 npm run dev 修好吗”这类已承诺的 dev 启动问题,不要只解释;先复现 `npm run dev`,再按启动日志修脚本并验证到服务就绪。WSL/Linux 下本地开发应走 `spacetime start --data-dir=server-rs/.spacetimedb/local/data` 这一类数据目录隔离,不再用项目级 `--root-dir`,详见 `references/dev-rust-stack-startup-2026-05-08.md`。
|
- 若用户追问“之前不是说要把 npm run dev 修好吗”这类已承诺的 dev 启动问题,不要只解释;先复现 `npm run dev`,再按启动日志修脚本并验证到服务就绪。WSL/Linux 下本地开发应走 `spacetime start --data-dir=server-rs/.spacetimedb/local/data` 这一类数据目录隔离,不再用项目级 `--root-dir`,详见 `references/dev-rust-stack-startup-2026-05-08.md`。
|
||||||
|
|||||||
+4
@@ -1,10 +1,12 @@
|
|||||||
# 本次后台表查询接入的可复用经验
|
# 本次后台表查询接入的可复用经验
|
||||||
|
|
||||||
## 需求落点
|
## 需求落点
|
||||||
|
|
||||||
- 后台“总览”页的表统计仍保留,只把每张表的表名改成可点击跳转到 `#tables?table=<name>`。
|
- 后台“总览”页的表统计仍保留,只把每张表的表名改成可点击跳转到 `#tables?table=<name>`。
|
||||||
- 新增独立 `#tables` 页承载表选择、关键词搜索、结构化字段筛选、limit、行详情弹窗(详情内保留字段复制,列头漏斗按钮可按列添加条件;每条条件可勾选启用或停用,停用时保留字段和值;`in` / `notIn` 使用逐项值标签,支持粘贴多行值,逗号不再作为隐式分隔符)。
|
- 新增独立 `#tables` 页承载表选择、关键词搜索、结构化字段筛选、limit、行详情弹窗(详情内保留字段复制,列头漏斗按钮可按列添加条件;每条条件可勾选启用或停用,停用时保留字段和值;`in` / `notIn` 使用逐项值标签,支持粘贴多行值,逗号不再作为隐式分隔符)。
|
||||||
|
|
||||||
## 后端实现要点
|
## 后端实现要点
|
||||||
|
|
||||||
- 新增只读接口:
|
- 新增只读接口:
|
||||||
- `GET /admin/api/database/tables`
|
- `GET /admin/api/database/tables`
|
||||||
- `GET /admin/api/database/tables/{table_name}/rows`
|
- `GET /admin/api/database/tables/{table_name}/rows`
|
||||||
@@ -17,12 +19,14 @@
|
|||||||
- SpacetimeDB HTTP SQL 返回可能是 statement array + rows,解析时要兼容这一层结构。
|
- SpacetimeDB HTTP SQL 返回可能是 statement array + rows,解析时要兼容这一层结构。
|
||||||
|
|
||||||
## 前端实现要点
|
## 前端实现要点
|
||||||
|
|
||||||
- `adminRoutes` 必须新增 `tables`,`AdminShell.routeIcons` 也要同步覆盖。
|
- `adminRoutes` 必须新增 `tables`,`AdminShell.routeIcons` 也要同步覆盖。
|
||||||
- `AdminApp` 需要显式渲染 `AdminDatabaseTablesPage`。
|
- `AdminApp` 需要显式渲染 `AdminDatabaseTablesPage`。
|
||||||
- 预览表格数据行直接点击(或行自身聚焦后按 Enter / Space)打开详情,行内按钮 / 输入控件的键盘操作不冒泡打开详情;详情按钮不单独占列。详情字段仅提供复制操作,成功、剪贴板失败和空字段复制都使用右下角自动消失的 Toast,JSON 预览与平台亮色 / 暗色主题保持一致,表头保持单行并在空间不足时省略显示。表单和标题区查询按钮共用筛选完整性校验。
|
- 预览表格数据行直接点击(或行自身聚焦后按 Enter / Space)打开详情,行内按钮 / 输入控件的键盘操作不冒泡打开详情;详情按钮不单独占列。详情字段仅提供复制操作,成功、剪贴板失败和空字段复制都使用右下角自动消失的 Toast,JSON 预览与平台亮色 / 暗色主题保持一致,表头保持单行并在空间不足时省略显示。表单和标题区查询按钮共用筛选完整性校验。
|
||||||
- worktree 下可能没有本地 `node_modules/typescript/bin/tsc`,而根目录有依赖;在验证前可以临时把根目录 `node_modules` 软链到 worktree 再执行 `npm run admin-web:typecheck`,验证后删除软链,避免污染 git 状态。
|
- worktree 下可能没有本地 `node_modules/typescript/bin/tsc`,而根目录有依赖;在验证前可以临时把根目录 `node_modules` 软链到 worktree 再执行 `npm run admin-web:typecheck`,验证后删除软链,避免污染 git 状态。
|
||||||
|
|
||||||
## 验证结果
|
## 验证结果
|
||||||
|
|
||||||
- `cargo test -p api-server admin_database -- --nocapture` 通过。
|
- `cargo test -p api-server admin_database -- --nocapture` 通过。
|
||||||
- `cargo fmt --manifest-path Cargo.toml -p api-server -p shared-contracts --check` 通过。
|
- `cargo fmt --manifest-path Cargo.toml -p api-server -p shared-contracts --check` 通过。
|
||||||
- `npm run admin-web:typecheck` 通过。
|
- `npm run admin-web:typecheck` 通过。
|
||||||
|
|||||||
+4
@@ -1,6 +1,7 @@
|
|||||||
# `npm run dev` / `scripts/dev-rust-stack.sh` 启动修复记录
|
# `npm run dev` / `scripts/dev-rust-stack.sh` 启动修复记录
|
||||||
|
|
||||||
## 症状
|
## 症状
|
||||||
|
|
||||||
- 多个 worktree 同时本地开发时,SpacetimeDB 数据库名可能相同,早期曾用项目级 CLI root 隔离 CLI 状态来规避冲突。
|
- 多个 worktree 同时本地开发时,SpacetimeDB 数据库名可能相同,早期曾用项目级 CLI root 隔离 CLI 状态来规避冲突。
|
||||||
- 实测后确认:真正需要隔离的是 standalone 的 `data-dir`,不需要把 publish 也绑到项目级 CLI root。
|
- 实测后确认:真正需要隔离的是 standalone 的 `data-dir`,不需要把 publish 也绑到项目级 CLI root。
|
||||||
- 早期脚本曾通过把用户级 SpacetimeDB 可执行文件目录同步到 `server-rs/.spacetimedb/local/bin/current` 来满足 standalone 回调需求,但这会把整套可执行文件复制进项目本地目录,维护成本高,也容易和用户级 CLI 版本漂移。
|
- 早期脚本曾通过把用户级 SpacetimeDB 可执行文件目录同步到 `server-rs/.spacetimedb/local/bin/current` 来满足 standalone 回调需求,但这会把整套可执行文件复制进项目本地目录,维护成本高,也容易和用户级 CLI 版本漂移。
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
- `api-server` 首次冷编译时,默认 300 秒超时不够,容易在就绪前被回收。
|
- `api-server` 首次冷编译时,默认 300 秒超时不够,容易在就绪前被回收。
|
||||||
|
|
||||||
## 当前方案
|
## 当前方案
|
||||||
|
|
||||||
1. SpacetimeDB 可执行文件继续使用用户环境里的 `spacetime` 命令
|
1. SpacetimeDB 可执行文件继续使用用户环境里的 `spacetime` 命令
|
||||||
- 启动 standalone 时不再复制 `spacetimedb-cli`、版本目录或 `bin/current`。
|
- 启动 standalone 时不再复制 `spacetimedb-cli`、版本目录或 `bin/current`。
|
||||||
- `spacetime start` 不再通过工程内 CLI root 寻找可执行文件。
|
- `spacetime start` 不再通过工程内 CLI root 寻找可执行文件。
|
||||||
@@ -29,6 +31,7 @@
|
|||||||
- `API_SERVER_TIMEOUT_SECONDS` 保持 600,降低首次冷编译误判失败概率。
|
- `API_SERVER_TIMEOUT_SECONDS` 保持 600,降低首次冷编译误判失败概率。
|
||||||
|
|
||||||
## 复现 / 验证
|
## 复现 / 验证
|
||||||
|
|
||||||
- 运行脚本语法检查:`bash -n scripts/dev-rust-stack.sh`。
|
- 运行脚本语法检查:`bash -n scripts/dev-rust-stack.sh`。
|
||||||
- 运行帮助检查:`bash scripts/dev-rust-stack.sh --help`,确认有 `--spacetime-data-dir`。
|
- 运行帮助检查:`bash scripts/dev-rust-stack.sh --help`,确认有 `--spacetime-data-dir`。
|
||||||
- 运行 `npm run dev` 后观察日志:
|
- 运行 `npm run dev` 后观察日志:
|
||||||
@@ -41,6 +44,7 @@
|
|||||||
- api-server 进入健康检查等待并最终可访问 `/healthz`。
|
- api-server 进入健康检查等待并最终可访问 `/healthz`。
|
||||||
|
|
||||||
## 相关文件
|
## 相关文件
|
||||||
|
|
||||||
- `scripts/dev-rust-stack.sh`
|
- `scripts/dev-rust-stack.sh`
|
||||||
- `server-rs/.spacetimedb/local/data/`
|
- `server-rs/.spacetimedb/local/data/`
|
||||||
- `server-rs/.cargo/config.toml`
|
- `server-rs/.cargo/config.toml`
|
||||||
|
|||||||
+7
@@ -1,28 +1,34 @@
|
|||||||
# 本地 private table SQL 权限修复
|
# 本地 private table SQL 权限修复
|
||||||
|
|
||||||
场景:
|
场景:
|
||||||
|
|
||||||
- 后台或 api-server 通过 SpacetimeDB HTTP SQL 读取 `tracking_event` 这类 private table。
|
- 后台或 api-server 通过 SpacetimeDB HTTP SQL 读取 `tracking_event` 这类 private table。
|
||||||
- 本地清库、重建 standalone 或重新发布模块后,原 CLI token 失效,SQL 可能报 `no such table ... If the table exists, it may be marked private`。
|
- 本地清库、重建 standalone 或重新发布模块后,原 CLI token 失效,SQL 可能报 `no such table ... If the table exists, it may be marked private`。
|
||||||
|
|
||||||
操作步骤:
|
操作步骤:
|
||||||
|
|
||||||
1. 清空本地 SpacetimeDB 数据目录
|
1. 清空本地 SpacetimeDB 数据目录
|
||||||
|
|
||||||
- 使用项目脚本停止本地实例后,备份或删除 `server-rs/.spacetimedb/local/data`。
|
- 使用项目脚本停止本地实例后,备份或删除 `server-rs/.spacetimedb/local/data`。
|
||||||
- 只清本地开发环境,不要误伤远端或其他 worktree。
|
- 只清本地开发环境,不要误伤远端或其他 worktree。
|
||||||
|
|
||||||
2. 启动本地 standalone
|
2. 启动本地 standalone
|
||||||
|
|
||||||
- 用项目约定的 `scripts/dev-rust-stack.sh` 或等价命令启动 `spacetime`。
|
- 用项目约定的 `scripts/dev-rust-stack.sh` 或等价命令启动 `spacetime`。
|
||||||
- 确认 `/v1/ping` 可访问后再取 identity。
|
- 确认 `/v1/ping` 可访问后再取 identity。
|
||||||
|
|
||||||
3. 通过 `/v1/identity` 获取新 token 和 identity
|
3. 通过 `/v1/identity` 获取新 token 和 identity
|
||||||
|
|
||||||
- 使用 `POST http://127.0.0.1:3101/v1/identity`
|
- 使用 `POST http://127.0.0.1:3101/v1/identity`
|
||||||
- 只记录 identity,不要在日志中打印 token 明文。
|
- 只记录 identity,不要在日志中打印 token 明文。
|
||||||
|
|
||||||
4. 用新 token 登录 CLI
|
4. 用新 token 登录 CLI
|
||||||
|
|
||||||
- 运行:`spacetime login --token <token>`
|
- 运行:`spacetime login --token <token>`
|
||||||
- 这会把 token 写到本地 CLI 配置,后续 HTTP SQL 可读 private table。
|
- 这会把 token 写到本地 CLI 配置,后续 HTTP SQL 可读 private table。
|
||||||
|
|
||||||
5. 重新验证 SQL
|
5. 重新验证 SQL
|
||||||
|
|
||||||
- 使用带 token 的 `POST /v1/database/<db>/sql`
|
- 使用带 token 的 `POST /v1/database/<db>/sql`
|
||||||
- 先尝试 `SELECT ... FROM tracking_event LIMIT 1`
|
- 先尝试 `SELECT ... FROM tracking_event LIMIT 1`
|
||||||
- 若成功,再让 api-server 走同样 token。
|
- 若成功,再让 api-server 走同样 token。
|
||||||
@@ -32,6 +38,7 @@
|
|||||||
- 输出日志时统一 `[REDACTED]`。
|
- 输出日志时统一 `[REDACTED]`。
|
||||||
|
|
||||||
排查要点:
|
排查要点:
|
||||||
|
|
||||||
- `ORDER BY` 和 private table 是两个独立问题,先分开修。
|
- `ORDER BY` 和 private table 是两个独立问题,先分开修。
|
||||||
- 清库后旧 token 很可能不再能看见 private table,不代表表不存在。
|
- 清库后旧 token 很可能不再能看见 private table,不代表表不存在。
|
||||||
- 若 `/v1/identity` 返回的 token 没权限,再检查当前 standalone 是否就是刚启动的本地实例、database 名是否一致、模块是否已重新发布。
|
- 若 `/v1/identity` 返回的 token 没权限,再检查当前 standalone 是否就是刚启动的本地实例、database 名是否一致、模块是否已重新发布。
|
||||||
|
|||||||
@@ -4,8 +4,14 @@ description: 在 Genarrative 中排查或修改登录、access token、refresh c
|
|||||||
license: MIT
|
license: MIT
|
||||||
metadata:
|
metadata:
|
||||||
codex:
|
codex:
|
||||||
tags: [Genarrative, auth, session, cookie, refresh-token, AuthGate, tracking]
|
tags:
|
||||||
related_skills: [systematic-debugging, test-driven-development, genarrative-profile-features]
|
[Genarrative, auth, session, cookie, refresh-token, AuthGate, tracking]
|
||||||
|
related_skills:
|
||||||
|
[
|
||||||
|
systematic-debugging,
|
||||||
|
test-driven-development,
|
||||||
|
genarrative-profile-features,
|
||||||
|
]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Genarrative 认证会话与登录埋点链路
|
# Genarrative 认证会话与登录埋点链路
|
||||||
|
|||||||
@@ -4,7 +4,16 @@ description: 在 Genarrative 中修改 npm run dev / dev:spacetime / dev:api-ser
|
|||||||
license: MIT
|
license: MIT
|
||||||
metadata:
|
metadata:
|
||||||
codex:
|
codex:
|
||||||
tags: [Genarrative, dev-stack, 端口探测, Vite, api-server, SpacetimeDB, npm-run-dev]
|
tags:
|
||||||
|
[
|
||||||
|
Genarrative,
|
||||||
|
dev-stack,
|
||||||
|
端口探测,
|
||||||
|
Vite,
|
||||||
|
api-server,
|
||||||
|
SpacetimeDB,
|
||||||
|
npm-run-dev,
|
||||||
|
]
|
||||||
related_skills: [genarrative-admin-backoffice]
|
related_skills: [genarrative-admin-backoffice]
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
interface:
|
interface:
|
||||||
display_name: "Genarrative External Editor API"
|
display_name: 'Genarrative External Editor API'
|
||||||
short_description: "Route async canvas generation safely"
|
short_description: 'Route async canvas generation safely'
|
||||||
default_prompt: "Use $genarrative-external-editor-api to discover the hosted integration, prepare a canvas session, and submit and poll asset generation into the canvas and library."
|
default_prompt: 'Use $genarrative-external-editor-api to discover the hosted integration, prepare a canvas session, and submit and poll asset generation into the canvas and library.'
|
||||||
policy:
|
policy:
|
||||||
allow_implicit_invocation: true
|
allow_implicit_invocation: true
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user