削减策划agent的工作流负担 #373
File diff suppressed because one or more lines are too long
@@ -74,7 +74,7 @@ description: 写游戏策划案(GDD)系统架构时使用。在顶层设计
|
||||
三个接口:**对上**承顶层系统范围表并跑循环覆盖检查;**对内**地图↔职责↔依赖
|
||||
三方一致、主数据归属唯一;**对下**目录映射 + MVP 闭环喂系统文档站。
|
||||
|
||||
## 四、怎么写(模板即流程,十二节按序)
|
||||
## 四、怎么写(模板参考结构,建议按此组织)
|
||||
(本节是带写法要领的教学版;实际填写的纯净模板在 templates/architecture.md)
|
||||
|
||||
### 1. 架构定位与目标
|
||||
@@ -85,7 +85,7 @@ description: 写游戏策划案(GDD)系统架构时使用。在顶层设计
|
||||
→ 没有变更记录的架构文档,第二轮迭代就会变成黑箱。
|
||||
|
||||
### 2. 系统地图
|
||||
Sxx 编号清单(核心系统 2~12 个)+ 支撑层(存档/UI,不拥有核心规则)。
|
||||
Sxx 编号清单(核心系统通常 1-5 个,有明确要求可超出 5 个)+ 支撑层(存档/UI,不拥有核心规则)。
|
||||
P0 段五列表:
|
||||
| 系统 | 目的 | 输入 | 输出 | P0 原因 |
|
||||
→ 每行 P0 原因必须答"删了它,__ 塌";答不出的降级或合并。
|
||||
|
||||
@@ -69,7 +69,7 @@ description: 写游戏策划案(GDD)概念层时使用。把一句话游戏
|
||||
记住三个接口:**对内**锚点仲裁一切;**对下**张力变取舍表、定稿变硬约束;
|
||||
**对上**边界画线防止越层。九节不是清单,是一台咬合的机器。
|
||||
|
||||
## 四、怎么写(模板即流程,九节按序)
|
||||
## 四、怎么写(模板参考结构,建议按此组织)
|
||||
(本节是带写法要领的教学版;实际填写的纯净模板在 templates/concept-design.md)
|
||||
|
||||
### 1. 一句话概念
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
name: game-gdd-system-doc
|
||||
description: 写单个系统的设计文档(Sxx)时的总纲——通用纪律、十二节同构骨架、
|
||||
description: 写单个系统的设计文档(Sxx)时的总纲——通用纪律、十二类常见内容、
|
||||
红线与分析文档格式。每类系统的专属写法与模板在 modules/system-types/ 下对应目录的 SKILL.md
|
||||
与对应模块的模板.md 里,按需取用。
|
||||
---
|
||||
@@ -26,9 +26,9 @@ description: 写单个系统的设计文档(Sxx)时的总纲——通用纪
|
||||
1. 架构已定稿:找到本系统的 Sxx 编号、职责表行、依赖方向——这是合同。
|
||||
2. 在 01~12 文件夹里选最接近的系统类型(可组合,如"钓鱼"=05 采集+06 战斗
|
||||
的判定部分),读取对应的 `SKILL.md` 与 `模板.md`。
|
||||
3. 该文件夹标注"必读例子"的,先读例子全文做密度锚。
|
||||
3. 该文件夹标注"参考例子"的,可先读例子了解写法。
|
||||
|
||||
## 三、十二节总览:写什么、为什么、怎么咬合
|
||||
## 三、常见内容总览:写什么、为什么、怎么咬合
|
||||
|
||||
系统文档回答四个问题:
|
||||
**这个系统为什么存在(1~2)→ 玩家怎么用它(3~5)→ 它怎么运转(6~8)→
|
||||
@@ -52,7 +52,7 @@ description: 写单个系统的设计文档(Sxx)时的总纲——通用纪
|
||||
咬合:**对上**服从架构三条合同(编号/职责/依赖);**对内**状态与接口不越
|
||||
职责边界;**对下**第 7 节交接喂 TDD。
|
||||
|
||||
## 四、十二节通用写法
|
||||
## 四、常见内容的参考写法
|
||||
(各系统类型的特殊写法见对应文件夹 SKILL.md;纯净模板在其 模板.md)
|
||||
|
||||
1 系统目的:若删除它,__ 会塌——一句话说不出 = 该系统不该存在。
|
||||
@@ -65,7 +65,7 @@ description: 写单个系统的设计文档(Sxx)时的总纲——通用纪
|
||||
8 反馈:每种关键结果给独立反馈形态;失败必须说明原因和恢复路径。
|
||||
9 内部循环:动词链;可拆单次/区域/长期三层。
|
||||
10 输入输出与依赖:引用具名系统与具名数据,禁泛称"资源"。
|
||||
11 边界与非目标:照该类型 skill 的"三不"写全;必含"字段数值归 TDD"一条。
|
||||
11 边界与非目标:参考该类型 skill 的“三不”说明边界;建议说明字段与数值的交接边界。
|
||||
12 开放问题:结构级才留;手感数值类标"待原型验证"。
|
||||
|
||||
## 五、分析文档(全局一份,按层分节)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -80,7 +80,7 @@ description: 写游戏策划案(GDD)顶层设计时使用。在概念层定
|
||||
(大⇄小⇄最小单位)+ 资源三段全;**对下**系统范围表喂架构的系统地图、
|
||||
顶层定稿当架构的紧箍咒、验证标准当原型试玩判据。
|
||||
|
||||
## 四、怎么写(模板即流程,十六节按序)
|
||||
## 四、怎么写(模板参考结构,建议按此组织)
|
||||
(本节是带写法要领的教学版;实际填写的纯净模板在 templates/top-design.md)
|
||||
|
||||
### 1. 顶层定位与规模锚点
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
|
||||
你是游戏策划协作 Agent,与用户持续协作完成游戏设计。像普通策划同事一样交流,使用工作区文件工具读写资料;所有文件路径使用相对路径。根据当前对话、阶段上下文和已有文档决定下一步行动。修改文件后,简要说明修改内容和相对路径。对不确定内容区分用户确认、Agent 建议和待原型验证事项;不要把建议写成用户已确认的决定。
|
||||
|
||||
优先完成能够依据已有信息推进的工作,不要为每个设计空白都询问用户。局部、可逆的问题可以先提出合理方案并标为暂定。会影响当前阶段范围、关键规则、下游实现或其他重要方向,且必须由用户决定的问题,应先通过纯文本或问询工具询问,等待用户回答,并据此更新相关产物;不要带着这类未决问题提交阶段审批。
|
||||
优先完成能够依据已有信息推进的工作,不要为每个设计空白都询问用户。局部、可逆的问题可以先提出合理方案并标为暂定。会影响当前阶段范围、关键规则、下游实现或其他重要方向,且必须由用户决定的问题,应先通过纯文本或问询工具询问,等待用户回答。决定稳定后,再更新受影响的正式产物和必要的过程记录;不要带着这类未决问题提交阶段审批。
|
||||
|
||||
分析阶段优先记录当前目标、上层约束、候选方案、取舍、用户已确认或 Agent 暂定的边界,以及必须检查的验收项。除非用户明确要求展开讨论,不要先在回复中逐节起草与正式文档重复的长篇正文;形成结论后直接写入正式产物,再进行一次必要的一致性检查。文件操作前只需说明简短计划、目标文件和主要变化。
|
||||
|
||||
正式策划文档在文档头部写明版本标记,例如“版本:v1”。由你自行维护版本号:只有整体修订、阶段性定稿或用户意见造成实质内容变化时才递增;错别字、措辞润色、单个局部修改和小范围补充不单独递增。
|
||||
|
||||
阶段审批是每个阶段的最终检查,表示本阶段产物已经完成,无未决内容,交给用户做最终检阅,不承担问询功能。提交前,解决所有影响本阶段完成的关键问题,或明确说明它们不阻塞本阶段交付,并更新相关产物。可以保留不阻塞当前阶段的后续事项和待原型验证项。
|
||||
|
||||
过程文档用于记录关键依据、决定和待办,不要求实时完整,也不应重复正式设计文档。阶段内优先完成主要设计内容;只有稳定且影响后续工作的决定才需要同步到多个过程文档。阶段提交前,补齐影响验收的关键记录。
|
||||
|
||||
阶段获批后,产物中已经采用的方案作为后续工作的依据,并保留原有决策来源。除非用户主动质疑或出现新的约束冲突,不要反复要求确认历史暂定决定。
|
||||
|
||||
用户说“继续”时,继续推进当前阶段最有价值的工作。判断本阶段已完成并准备交用户检阅时,应调用 `submit_phase_for_approval`;只有该工具调用成功,才算正式提交审批。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{"type":"function","function":{"name":"get_workflow_status","description":"读取当前策划工作流状态,只返回阶段列表、当前阶段、已批准阶段和待审批阶段;不推进阶段、不提交审批、不修改文件。","parameters":{"type":"object","properties":{},"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"list_resources","description":"列出固定资源的逻辑目录、资源 ID、标题和简介。资源是只读的随包文档;不要猜测物理路径。","parameters":{"type":"object","properties":{},"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"read_resource","description":"读取一份固定资源文档全文。每次读取一个 resource_id;资源只读。读到未实现占位文档时由你自行判断和处理。","parameters":{"type":"object","properties":{"resource_id":{"type":"string"}},"required":["resource_id"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"patch_file","description":"局部修改 UTF-8 文件,优先用于已有文件的小范围修订。先读文件,以唯一且非空的 old_text 精确匹配并替换为 new_text;new_text 为空可删除片段,保留原文并追加可插入。匹配失败不修改文件。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"},"old_text":{"type":"string"},"new_text":{"type":"string"}},"required":["path","old_text","new_text"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"patch_file","description":"局部修改 UTF-8 文件。使用 old_text/new_text,或使用 edits 一次进行多个独立替换;每个 old_text 必须非空且在原文件中唯一,匹配失败、重复或范围重叠时不修改文件。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"},"old_text":{"type":"string"},"new_text":{"type":"string"},"edits":{"type":"array","items":{"type":"object","properties":{"old_text":{"type":"string"},"new_text":{"type":"string"}},"required":["old_text","new_text"],"additionalProperties":false}}},"required":["path"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"delete_path","description":"谨慎使用;永久删除工作区内的文件或目录;目录会连同全部内容递归删除,不备份。先确认目标及删除范围。path 使用相对路径,不能删除工作区根目录,也不能经过链接。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"list_dir","description":"列出工作目录内的文件和目录。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
||||
{"type":"function","function":{"name":"read_file","description":"读取工作目录内的 UTF-8 文本文件。path 使用相对路径。","parameters":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"],"additionalProperties":false}}},
|
||||
|
||||
@@ -264,22 +264,45 @@ pub(crate) fn execute_design_file_tool(
|
||||
}
|
||||
"patch_file" => {
|
||||
let relative = required_tool_path(args)?;
|
||||
let old = args
|
||||
.get("old_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("缺少 old_text")?;
|
||||
let new = args
|
||||
.get("new_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("缺少 new_text")?;
|
||||
if old.is_empty() {
|
||||
return Err("old_text 不能为空".to_string());
|
||||
}
|
||||
let edits = if let Some(items) = args.get("edits").and_then(Value::as_array) {
|
||||
if items.is_empty() {
|
||||
return Err("edits 不能为空".to_string());
|
||||
}
|
||||
items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, item)| {
|
||||
let old = item
|
||||
.get("old_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| format!("edits[{index}].old_text 必须是字符串"))?;
|
||||
let new = item
|
||||
.get("new_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| format!("edits[{index}].new_text 必须是字符串"))?;
|
||||
if old.is_empty() {
|
||||
return Err(format!("edits[{index}].old_text 不能为空"));
|
||||
}
|
||||
Ok((old.to_string(), new.to_string()))
|
||||
})
|
||||
.collect::<Result<Vec<_>, String>>()?
|
||||
} else {
|
||||
let old = args
|
||||
.get("old_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("缺少 old_text")?;
|
||||
let new = args
|
||||
.get("new_text")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("缺少 new_text")?;
|
||||
if old.is_empty() {
|
||||
return Err("old_text 不能为空".to_string());
|
||||
}
|
||||
vec![(old.to_string(), new.to_string())]
|
||||
};
|
||||
let (display, path) = resolve_design_workspace_path(root, &relative)?;
|
||||
if !path.is_file() {
|
||||
return Ok(Value::String(format!(
|
||||
"局部修改失败:文件不存在:{display}"
|
||||
)));
|
||||
return Err(format!("文件不存在:{display}"));
|
||||
}
|
||||
let content =
|
||||
fs::read_to_string(&path).map_err(|error| format!("读取失败:{error}"))?;
|
||||
@@ -288,20 +311,52 @@ pub(crate) fn execute_design_file_tool(
|
||||
} else {
|
||||
"\n"
|
||||
};
|
||||
let old = old.replace("\r\n", "\n").replace('\n', newline);
|
||||
let new = new.replace("\r\n", "\n").replace('\n', newline);
|
||||
let count = content.matches(&old).count();
|
||||
if count != 1 {
|
||||
return Err(format!(
|
||||
"原文匹配 {count} 处,需要唯一匹配;请重新读取文件并扩大匹配范围"
|
||||
));
|
||||
let normalized = edits
|
||||
.into_iter()
|
||||
.map(|(old, new)| {
|
||||
(
|
||||
old.replace("\r\n", "\n").replace('\n', newline),
|
||||
new.replace("\r\n", "\n").replace('\n', newline),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut matches = Vec::new();
|
||||
for (index, (old, new)) in normalized.iter().enumerate() {
|
||||
let count = content.matches(old).count();
|
||||
if count == 0 {
|
||||
return Err(format!("edits[{index}] 原文未找到:{display}"));
|
||||
}
|
||||
if count != 1 {
|
||||
return Err(format!(
|
||||
"edits[{index}] 原文匹配 {count} 处,必须唯一:{display}"
|
||||
));
|
||||
}
|
||||
let start = content.find(old).expect("count checked");
|
||||
let end = start + old.len();
|
||||
if let Some((other_index, _other_start, _other_end)) = matches
|
||||
.iter()
|
||||
.find(|(_, other_start, other_end)| start < *other_end && *other_start < end)
|
||||
{
|
||||
return Err(format!(
|
||||
"edits[{index}] 与 edits[{other_index}] 修改范围重叠:{display}"
|
||||
));
|
||||
}
|
||||
matches.push((index, start, end));
|
||||
let _ = new;
|
||||
}
|
||||
crate::write_game_creator_private_file(
|
||||
&path,
|
||||
content.replacen(&old, &new, 1).as_bytes(),
|
||||
"策划工作区文件",
|
||||
)?;
|
||||
Ok(Value::String(format!("已局部修改 {display}")))
|
||||
let mut updated = content.clone();
|
||||
for (index, start, end) in matches.into_iter().rev() {
|
||||
let (_, new) = &normalized[index];
|
||||
updated.replace_range(start..end, new);
|
||||
}
|
||||
if updated == content {
|
||||
return Err(format!("没有产生修改:{display}"));
|
||||
}
|
||||
crate::write_game_creator_private_file(&path, updated.as_bytes(), "策划工作区文件")?;
|
||||
Ok(Value::String(format!(
|
||||
"已局部修改 {display}({} 处)",
|
||||
normalized.len()
|
||||
)))
|
||||
}
|
||||
"delete_path" => {
|
||||
let relative = required_tool_path(args)?;
|
||||
@@ -645,6 +700,29 @@ mod tests {
|
||||
)
|
||||
.expect("patch");
|
||||
assert!(patched.as_str().unwrap().contains("已局部修改"));
|
||||
execute_design_file_tool(
|
||||
root,
|
||||
"write_file",
|
||||
&json!({"path":"notes/multi.md","content":"甲\n乙\n丙"}),
|
||||
)
|
||||
.expect("write multi");
|
||||
let multi = execute_design_file_tool(
|
||||
root,
|
||||
"patch_file",
|
||||
&json!({
|
||||
"path":"notes/multi.md",
|
||||
"edits":[
|
||||
{"old_text":"甲","new_text":"一"},
|
||||
{"old_text":"丙","new_text":"三"}
|
||||
]
|
||||
}),
|
||||
)
|
||||
.expect("multi patch");
|
||||
assert!(multi.as_str().unwrap().contains("2 处"));
|
||||
assert_eq!(
|
||||
fs::read_to_string(root.join("design_artifacts/notes/multi.md")).expect("read multi"),
|
||||
"一\n乙\n三"
|
||||
);
|
||||
execute_design_file_tool(root, "delete_path", &json!({"path":"notes"}))
|
||||
.expect("delete dir");
|
||||
assert!(!root.join("design_artifacts/notes").exists());
|
||||
|
||||
Reference in New Issue
Block a user