@yeaft/webchat-agent 0.0.209 → 0.0.210
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/crew-i18n.js +374 -0
- package/crew.js +127 -173
- package/package.json +1 -1
package/crew-i18n.js
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crew Mode i18n — localized template strings for crew system prompts and files.
|
|
3
|
+
*
|
|
4
|
+
* Supported languages: 'zh-CN' (default), 'en'
|
|
5
|
+
* Session language is set at creation and never changes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const messages = {
|
|
9
|
+
'zh-CN': {
|
|
10
|
+
// buildRoleSystemPrompt
|
|
11
|
+
teamCollab: '# 团队协作',
|
|
12
|
+
teamCollabIntro: (goal) => goal ? `你正在一个 AI 团队中工作。项目目标是: ${goal}` : '你正在一个 AI 团队中工作。等待用户提出任务或问题。',
|
|
13
|
+
teamMembers: '团队成员:',
|
|
14
|
+
decisionMakerTag: '决策者',
|
|
15
|
+
|
|
16
|
+
routingRules: '# 路由规则',
|
|
17
|
+
routingIntro: '当你完成当前任务并需要将结果传递给其他角色时,在你的回复最末尾添加一个 ROUTE 块:',
|
|
18
|
+
routeTargets: '可用的路由目标:',
|
|
19
|
+
humanTarget: '人工(只在决策者也无法决定时使用)',
|
|
20
|
+
routeNotes: (dm) => `注意:
|
|
21
|
+
- 如果你的工作还没完成,不需要添加 ROUTE 块
|
|
22
|
+
- 如果你遇到不确定的问题,@ 决策者 "${dm}",而不是直接 @ human
|
|
23
|
+
- 如果你是决策者且遇到需要人类判断的问题,才 @ human
|
|
24
|
+
- 可以一次发多个 ROUTE 块来并行分配任务给不同角色
|
|
25
|
+
- ROUTE 块必须在回复的最末尾
|
|
26
|
+
- 当你的任务已完成且不需要其他角色继续时,ROUTE 回决策者 "${dm}" 做总结
|
|
27
|
+
- 在正文中可用 @角色name 提及某个角色(如 @developer),但这不会触发路由,仅供阅读`,
|
|
28
|
+
|
|
29
|
+
// Decision maker sections
|
|
30
|
+
toolUsage: '# 工具使用',
|
|
31
|
+
toolUsageContent: (isDevTeam) =>
|
|
32
|
+
`PM 可以使用所有工具,包括 Read、Grep、Glob、Bash、Edit、Write。${isDevTeam ? '代码文件的改动仍建议 ROUTE 给 developer 执行,但不做硬性限制。' : ''}`,
|
|
33
|
+
|
|
34
|
+
dmRole: '# 决策者职责',
|
|
35
|
+
dmRoleContent: `你是团队的决策者。其他角色遇到不确定的情况会请求你的决策。
|
|
36
|
+
- 如果你有足够的信息做出决策,直接决定并 @相关角色执行
|
|
37
|
+
- 如果你需要更多信息,@具体角色请求补充
|
|
38
|
+
- 如果问题超出你的能力范围或需要业务判断,@human 请人类决定
|
|
39
|
+
- 你可以随时审查其他角色的工作并给出反馈`,
|
|
40
|
+
|
|
41
|
+
dmDevExtra: `
|
|
42
|
+
- PM 不做代码分析。收到需求后直接将原始需求 ROUTE 给空闲 dev 做技术分析,dev 分析完返回 PM,PM 再拆分任务并直接分配执行。
|
|
43
|
+
- PM 拥有 commit + push + tag 的自主权。只要修改没有大的 regression 影响(测试全通过),PM 可以自行决定 commit、push 和 tag,无需等待人工确认。只有当改动会直接影响对话交互逻辑时,才需要人工介入审核。`,
|
|
44
|
+
|
|
45
|
+
collabMode: '# 协作模式',
|
|
46
|
+
collabModeContent: `这是一个协作讨论团队,不走严格的 PM→执行→审查→测试 工作流。
|
|
47
|
+
- 角色之间可以自由讨论、相互请教、提出不同意见
|
|
48
|
+
- 不需要严格的"分配→执行→审查"流程,鼓励角色之间直接对话
|
|
49
|
+
- 当一个角色需要另一个角色的输入时,直接 ROUTE 给对方并说明需要什么
|
|
50
|
+
- 决策者负责把控整体方向和最终决策,但日常讨论不需要经过决策者中转
|
|
51
|
+
- 每次 ROUTE 仍建议包含 task 和 taskTitle 字段,用于消息按 feature 分组显示`,
|
|
52
|
+
|
|
53
|
+
execGroupStatus: '# 执行组状态',
|
|
54
|
+
parallelRules: '# 并行任务调度规则',
|
|
55
|
+
parallelRulesContent: (maxGroup) => `你有 ${maxGroup} 个开发组可以并行工作。拆分任务时:
|
|
56
|
+
1. 每个子任务分配 task-id(如 task-1)和 taskTitle(如 "实现登录页面")
|
|
57
|
+
2. 优先分配给**空闲**的开发组,避免给忙碌的 dev 发新任务
|
|
58
|
+
3. 一次可以发**多个 ROUTE 块**来并行分配任务:`,
|
|
59
|
+
parallelExample: `4. 每个 dev 完成后会独立经过 reviewer 和 tester 审核,最后 ROUTE 回你
|
|
60
|
+
5. 等待**所有子任务完成**后再做汇总报告
|
|
61
|
+
6. **每次 ROUTE 都必须包含 task 和 taskTitle 字段,不能省略。没有 task 字段的 ROUTE 会导致消息无法按 feature 分组显示**`,
|
|
62
|
+
|
|
63
|
+
groupBusy: (task) => `忙:${task}`,
|
|
64
|
+
groupBusyShort: '忙',
|
|
65
|
+
groupIdle: '空闲',
|
|
66
|
+
groupLabel: (g) => `组${g}`,
|
|
67
|
+
|
|
68
|
+
workflowEnd: '# 工作流终结点',
|
|
69
|
+
workflowEndContent: (isDevTeam) =>
|
|
70
|
+
`团队的工作流有明确的结束条件。当以下任一条件满足时,你应该给出总结并结束当前工作流:
|
|
71
|
+
${isDevTeam ? '1. **代码已提交** - 所有代码修改已经 commit(如需要,可让 developer 执行 git commit)\n' : ''}${isDevTeam ? '2' : '1'}. **需要用户输入** - 遇到需要用户决定的问题时,@human 提出具体问题,等待用户回复
|
|
72
|
+
${isDevTeam ? '3' : '2'}. **任务完成** - 所有任务已完成,给出完成总结(列出完成了什么${isDevTeam ? '、变更了哪些文件' : ''}、还有什么后续建议)
|
|
73
|
+
|
|
74
|
+
重要:不要无限循环地在角色之间传递。当工作实质性完成时,主动给出总结并结束。`,
|
|
75
|
+
|
|
76
|
+
taskList: '# 任务清单',
|
|
77
|
+
taskListContent: `你可以在回复中添加 TASKS 块来发布/更新任务清单,团队界面会自动展示:`,
|
|
78
|
+
taskListNotes: `注意:
|
|
79
|
+
- 每行一个任务,[ ] 表示待办,[x] 表示已完成
|
|
80
|
+
- #taskId 标注对应的 feature ID(如 #task-1),用于精确关联任务完成状态
|
|
81
|
+
- @角色name 标注负责人(可选)
|
|
82
|
+
- 后续回复中可更新 TASKS 块(标记完成的任务)
|
|
83
|
+
- TASKS 块不需要在回复最末尾,可以放在任意位置`,
|
|
84
|
+
taskExample: `---TASKS---
|
|
85
|
+
- [ ] 任务描述 #task-1 @角色name
|
|
86
|
+
- [x] 已完成的任务 #task-2 @角色name
|
|
87
|
+
---END_TASKS---`,
|
|
88
|
+
|
|
89
|
+
featureRecordTitle: '# Feature 工作记录',
|
|
90
|
+
featureRecordContent: `系统会自动管理 \`context/features/{task-id}.md\` 工作记录文件:
|
|
91
|
+
- PM 分配任务时自动创建文件(包含 task-id、标题、需求描述)
|
|
92
|
+
- 每次 ROUTE 传递时自动追加工作记录(角色名、时间、summary)
|
|
93
|
+
- 你收到的消息中会包含 <task-context> 标签,里面是该任务的完整工作记录
|
|
94
|
+
|
|
95
|
+
系统还维护以下文件(自动更新,无需手动管理):
|
|
96
|
+
- \`context/features/index.md\`:所有 feature 的索引(进行中/已完成分类),快速查看项目状态
|
|
97
|
+
- \`context/changelog.md\`:已完成任务的变更记录,每个任务完成时自动追加摘要
|
|
98
|
+
你不需要手动创建或更新这些文件,专注于你的本职工作即可。`,
|
|
99
|
+
|
|
100
|
+
devGroupBinding: '# 开发组绑定',
|
|
101
|
+
devGroupBindingContent: (gi, revLabel, revName, testLabel, testName) =>
|
|
102
|
+
`你属于开发组 ${gi}。你的搭档:
|
|
103
|
+
- 审查者: ${revLabel} (${revName})
|
|
104
|
+
- 测试: ${testLabel} (${testName})
|
|
105
|
+
|
|
106
|
+
开发完成后,请同时发两个 ROUTE 块分别给 ${revName} 和 ${testName}:`,
|
|
107
|
+
devGroupBindingNote: '两者会并行工作,各自完成后独立 ROUTE 回 PM。',
|
|
108
|
+
|
|
109
|
+
implLoginPage: '实现登录页面',
|
|
110
|
+
implLoginSummary: '请实现登录页面,包括表单验证和API调用',
|
|
111
|
+
implRegisterPage: '实现注册页面',
|
|
112
|
+
implRegisterSummary: '请实现注册页面,包括邮箱验证',
|
|
113
|
+
reviewCode: '请审查代码变更',
|
|
114
|
+
testFeature: '请测试功能',
|
|
115
|
+
|
|
116
|
+
// buildInitialTask
|
|
117
|
+
projectStart: '项目启动!',
|
|
118
|
+
goalLabel: '目标',
|
|
119
|
+
firstRoleInstruction: '你是第一个开始工作的角色。请分析目标,开始你的工作。\n完成后,通过 ROUTE 块将结果传递给下一个合适的角色。',
|
|
120
|
+
availableRoles: '团队中可用的角色:',
|
|
121
|
+
|
|
122
|
+
// writeSharedClaudeMd
|
|
123
|
+
projectGoal: '# 项目目标',
|
|
124
|
+
projectCodePath: '# 项目代码路径',
|
|
125
|
+
useAbsolutePath: '所有代码操作请使用此绝对路径。',
|
|
126
|
+
teamMembersTitle: '# 团队成员',
|
|
127
|
+
noMembers: '_暂无成员_',
|
|
128
|
+
workConventions: '# 工作约定',
|
|
129
|
+
workConventionsContent: `- 文档产出写入 context/ 目录
|
|
130
|
+
- 重要决策记录在 context/decisions.md
|
|
131
|
+
- 代码修改使用项目代码路径的绝对路径`,
|
|
132
|
+
stuckRules: '# 卡住上报规则',
|
|
133
|
+
stuckRulesContent: `当你遇到以下情况时,不要自己空转或反复重试,立即 ROUTE 给 PM(pm)请求协调:
|
|
134
|
+
1. 缺少前置依赖(如需要的文件、目录、代码不存在)
|
|
135
|
+
2. 等待其他角色的产出但迟迟没有收到
|
|
136
|
+
3. 任务描述不清楚或有歧义,无法判断正确做法
|
|
137
|
+
4. 遇到超出自己职责范围的问题
|
|
138
|
+
5. 连续尝试 2 次相同操作仍然失败
|
|
139
|
+
上报时请说明:你在做什么任务、卡在哪里、你认为需要谁来协助。PM 会统筹全局,判断是分配给合适的人还是调整任务顺序。`,
|
|
140
|
+
worktreeRules: '# Worktree 隔离规则',
|
|
141
|
+
worktreeRulesContent: `- dev/reviewer/tester 角色必须在各自分配的 worktree 中工作,绝对禁止在项目主目录或 main 分支上修改代码
|
|
142
|
+
- 每个角色的 CLAUDE.md 会标明「代码工作目录」,该路径就是你的 worktree,所有文件操作必须使用该路径
|
|
143
|
+
- PM 和 designer 不使用 worktree,他们在项目主目录下以只读方式工作
|
|
144
|
+
- 绝对禁止在其他开发组的 worktree 中操作代码
|
|
145
|
+
- 代码完成并通过 review 后,dev 自己提 PR 合并到 main 分支
|
|
146
|
+
- PM 不做 cherry-pick,只负责打 tag
|
|
147
|
+
- 每次新任务/新 feature 必须基于最新的 main 分支创建新的 worktree,确保在最新代码上开发`,
|
|
148
|
+
featureRecordShared: `# Feature 工作记录
|
|
149
|
+
系统自动管理 \`context/features/{task-id}.md\` 工作记录文件:
|
|
150
|
+
- PM 通过 ROUTE 分配任务(带 task + taskTitle 字段)时自动创建
|
|
151
|
+
- 每次角色 ROUTE 传递时自动追加工作记录
|
|
152
|
+
- 角色收到消息时自动注入对应 task 文件内容作为上下文
|
|
153
|
+
角色不需要手动创建或更新这些文件。`,
|
|
154
|
+
sharedMemoryTitle: '# 共享记忆',
|
|
155
|
+
sharedMemoryDefault: '_团队共同维护,记录重要的共识、决策和信息。_',
|
|
156
|
+
|
|
157
|
+
// writeRoleClaudeMd
|
|
158
|
+
roleTitle: (label) => `# 角色: ${label}`,
|
|
159
|
+
codeWorkDir: '# 代码工作目录',
|
|
160
|
+
codeWorkDirNote: '所有代码操作请使用此路径。不要使用项目主目录。',
|
|
161
|
+
personalMemory: '# 个人记忆',
|
|
162
|
+
personalMemoryDefault: '_在这里记录重要的信息、决策、进展和待办事项。_',
|
|
163
|
+
|
|
164
|
+
// ensureTaskFile
|
|
165
|
+
featureLabel: 'Feature',
|
|
166
|
+
statusPending: '待开发',
|
|
167
|
+
assigneeLabel: '负责人',
|
|
168
|
+
createdAtLabel: '创建时间',
|
|
169
|
+
requirementDesc: '## 需求描述',
|
|
170
|
+
workRecord: '## 工作记录',
|
|
171
|
+
|
|
172
|
+
// updateFeatureIndex
|
|
173
|
+
featureIndex: '# Feature Index',
|
|
174
|
+
lastUpdated: '最后更新',
|
|
175
|
+
inProgressGroup: (n) => `## 进行中 (${n})`,
|
|
176
|
+
completedGroup: (n) => `## 已完成 (${n})`,
|
|
177
|
+
colTaskId: 'task-id',
|
|
178
|
+
colTitle: '标题',
|
|
179
|
+
colCreatedAt: '创建时间',
|
|
180
|
+
|
|
181
|
+
// appendChangelog
|
|
182
|
+
changelogTitle: '# Changelog',
|
|
183
|
+
completedAt: '完成时间',
|
|
184
|
+
summaryLabel: '摘要',
|
|
185
|
+
noSummary: '(无详细摘要)',
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
'en': {
|
|
189
|
+
// buildRoleSystemPrompt
|
|
190
|
+
teamCollab: '# Team Collaboration',
|
|
191
|
+
teamCollabIntro: (goal) => goal ? `You are working in an AI team. The project goal is: ${goal}` : 'You are working in an AI team. Waiting for the user to assign tasks or questions.',
|
|
192
|
+
teamMembers: 'Team members:',
|
|
193
|
+
decisionMakerTag: 'Decision Maker',
|
|
194
|
+
|
|
195
|
+
routingRules: '# Routing Rules',
|
|
196
|
+
routingIntro: 'When you finish your current task and need to pass the result to another role, add a ROUTE block at the very end of your reply:',
|
|
197
|
+
routeTargets: 'Available routing targets:',
|
|
198
|
+
humanTarget: 'Human (only when the decision maker cannot decide)',
|
|
199
|
+
routeNotes: (dm) => `Notes:
|
|
200
|
+
- If your work is not yet complete, do not add a ROUTE block
|
|
201
|
+
- If you encounter an uncertain issue, @ the decision maker "${dm}" instead of directly @ human
|
|
202
|
+
- Only @ human if you are the decision maker and the issue requires human judgment
|
|
203
|
+
- You can send multiple ROUTE blocks to assign tasks to different roles in parallel
|
|
204
|
+
- ROUTE blocks must be at the very end of your reply
|
|
205
|
+
- When your task is complete and no further role handoff is needed, ROUTE back to the decision maker "${dm}" for summary
|
|
206
|
+
- You can mention a role by @roleName in the body text (e.g., @developer) for reference only — this does not trigger routing`,
|
|
207
|
+
|
|
208
|
+
// Decision maker sections
|
|
209
|
+
toolUsage: '# Tool Usage',
|
|
210
|
+
toolUsageContent: (isDevTeam) =>
|
|
211
|
+
`PM can use all tools including Read, Grep, Glob, Bash, Edit, Write.${isDevTeam ? ' Code changes are still recommended to ROUTE to developer, but not strictly required.' : ''}`,
|
|
212
|
+
|
|
213
|
+
dmRole: '# Decision Maker Responsibilities',
|
|
214
|
+
dmRoleContent: `You are the team's decision maker. Other roles will request your decision when they encounter uncertainties.
|
|
215
|
+
- If you have enough information, make the decision directly and @ the relevant role to execute
|
|
216
|
+
- If you need more information, @ a specific role and request details
|
|
217
|
+
- If the issue is beyond your capability or requires business judgment, @human to let a human decide
|
|
218
|
+
- You can review other roles' work and provide feedback at any time`,
|
|
219
|
+
|
|
220
|
+
dmDevExtra: `
|
|
221
|
+
- PM does not analyze code. Upon receiving requirements, directly ROUTE the raw requirements to an idle dev for technical analysis. After dev analyzes, PM splits tasks and assigns execution.
|
|
222
|
+
- PM has autonomy for commit + push + tag. As long as changes have no significant regression impact (all tests pass), PM can decide to commit, push and tag without waiting for manual confirmation. Only when changes directly affect conversation interaction logic should manual review be required.`,
|
|
223
|
+
|
|
224
|
+
collabMode: '# Collaboration Mode',
|
|
225
|
+
collabModeContent: `This is a collaborative discussion team — no strict PM→Execute→Review→Test workflow.
|
|
226
|
+
- Roles can freely discuss, consult each other, and raise different opinions
|
|
227
|
+
- No strict "assign→execute→review" process — direct dialogue between roles is encouraged
|
|
228
|
+
- When a role needs input from another, directly ROUTE to them and explain what's needed
|
|
229
|
+
- The decision maker oversees the overall direction and final decisions, but daily discussions don't need to go through the decision maker
|
|
230
|
+
- Each ROUTE should still include task and taskTitle fields for message grouping by feature`,
|
|
231
|
+
|
|
232
|
+
execGroupStatus: '# Execution Group Status',
|
|
233
|
+
parallelRules: '# Parallel Task Scheduling Rules',
|
|
234
|
+
parallelRulesContent: (maxGroup) => `You have ${maxGroup} dev groups that can work in parallel. When splitting tasks:
|
|
235
|
+
1. Assign each sub-task a task-id (e.g., task-1) and taskTitle (e.g., "Implement login page")
|
|
236
|
+
2. Prioritize assigning to **idle** dev groups — avoid sending new tasks to busy devs
|
|
237
|
+
3. You can send **multiple ROUTE blocks** to assign tasks in parallel:`,
|
|
238
|
+
parallelExample: `4. Each dev will independently go through reviewer and tester review, then ROUTE back to you
|
|
239
|
+
5. Wait until **all sub-tasks are completed** before providing a summary report
|
|
240
|
+
6. **Every ROUTE must include task and taskTitle fields — they cannot be omitted. ROUTEs without a task field will prevent messages from being grouped by feature**`,
|
|
241
|
+
|
|
242
|
+
groupBusy: (task) => `busy:${task}`,
|
|
243
|
+
groupBusyShort: 'busy',
|
|
244
|
+
groupIdle: 'idle',
|
|
245
|
+
groupLabel: (g) => `Group${g}`,
|
|
246
|
+
|
|
247
|
+
workflowEnd: '# Workflow Termination',
|
|
248
|
+
workflowEndContent: (isDevTeam) =>
|
|
249
|
+
`The team's workflow has clear end conditions. When any of the following conditions are met, you should provide a summary and conclude the current workflow:
|
|
250
|
+
${isDevTeam ? '1. **Code committed** - All code changes have been committed (if needed, let developer execute git commit)\n' : ''}${isDevTeam ? '2' : '1'}. **User input needed** - When encountering issues that require user decision, @human with a specific question and wait for reply
|
|
251
|
+
${isDevTeam ? '3' : '2'}. **Task completed** - All tasks are done, provide a completion summary (list what was accomplished${isDevTeam ? ', which files were changed' : ''}, and any follow-up suggestions)
|
|
252
|
+
|
|
253
|
+
Important: Do not loop endlessly between roles. When work is substantively complete, proactively provide a summary and conclude.`,
|
|
254
|
+
|
|
255
|
+
taskList: '# Task List',
|
|
256
|
+
taskListContent: `You can add a TASKS block in your reply to publish/update a task list, which the team interface will display automatically:`,
|
|
257
|
+
taskListNotes: `Notes:
|
|
258
|
+
- One task per line, [ ] for to-do, [x] for completed
|
|
259
|
+
- #taskId marks the corresponding feature ID (e.g., #task-1) for precise task completion tracking
|
|
260
|
+
- @roleName marks the assignee (optional)
|
|
261
|
+
- You can update the TASKS block in subsequent replies (mark completed tasks)
|
|
262
|
+
- TASKS block does not need to be at the end of the reply — it can be placed anywhere`,
|
|
263
|
+
taskExample: `---TASKS---
|
|
264
|
+
- [ ] Task description #task-1 @roleName
|
|
265
|
+
- [x] Completed task #task-2 @roleName
|
|
266
|
+
---END_TASKS---`,
|
|
267
|
+
|
|
268
|
+
featureRecordTitle: '# Feature Work Records',
|
|
269
|
+
featureRecordContent: `The system automatically manages \`context/features/{task-id}.md\` work record files:
|
|
270
|
+
- Automatically created when PM assigns tasks (includes task-id, title, requirement description)
|
|
271
|
+
- Work records are appended automatically on each ROUTE handoff (role name, time, summary)
|
|
272
|
+
- Your received messages will include <task-context> tags containing the complete work record for that task
|
|
273
|
+
|
|
274
|
+
The system also maintains these files (auto-updated, no manual management needed):
|
|
275
|
+
- \`context/features/index.md\`: Index of all features (categorized as in-progress/completed) for quick project status overview
|
|
276
|
+
- \`context/changelog.md\`: Change log of completed tasks, with summary appended when each task completes
|
|
277
|
+
You don't need to manually create or update these files — focus on your core work.`,
|
|
278
|
+
|
|
279
|
+
devGroupBinding: '# Dev Group Binding',
|
|
280
|
+
devGroupBindingContent: (gi, revLabel, revName, testLabel, testName) =>
|
|
281
|
+
`You belong to dev group ${gi}. Your partners:
|
|
282
|
+
- Reviewer: ${revLabel} (${revName})
|
|
283
|
+
- Tester: ${testLabel} (${testName})
|
|
284
|
+
|
|
285
|
+
After development is complete, send two ROUTE blocks simultaneously to ${revName} and ${testName}:`,
|
|
286
|
+
devGroupBindingNote: 'Both will work in parallel and independently ROUTE back to PM when done.',
|
|
287
|
+
|
|
288
|
+
implLoginPage: 'Implement login page',
|
|
289
|
+
implLoginSummary: 'Please implement the login page including form validation and API calls',
|
|
290
|
+
implRegisterPage: 'Implement registration page',
|
|
291
|
+
implRegisterSummary: 'Please implement the registration page including email verification',
|
|
292
|
+
reviewCode: 'Please review code changes',
|
|
293
|
+
testFeature: 'Please test the feature',
|
|
294
|
+
|
|
295
|
+
// buildInitialTask
|
|
296
|
+
projectStart: 'Project started!',
|
|
297
|
+
goalLabel: 'Goal',
|
|
298
|
+
firstRoleInstruction: 'You are the first role to start working. Analyze the goal and begin your work.\nWhen done, pass the result to the next appropriate role via a ROUTE block.',
|
|
299
|
+
availableRoles: 'Available roles in the team:',
|
|
300
|
+
|
|
301
|
+
// writeSharedClaudeMd
|
|
302
|
+
projectGoal: '# Project Goal',
|
|
303
|
+
projectCodePath: '# Project Code Path',
|
|
304
|
+
useAbsolutePath: 'Use this absolute path for all code operations.',
|
|
305
|
+
teamMembersTitle: '# Team Members',
|
|
306
|
+
noMembers: '_No members yet_',
|
|
307
|
+
workConventions: '# Work Conventions',
|
|
308
|
+
workConventionsContent: `- Write documentation output to context/ directory
|
|
309
|
+
- Record important decisions in context/decisions.md
|
|
310
|
+
- Use the project code path (absolute path) for code changes`,
|
|
311
|
+
stuckRules: '# Escalation Rules',
|
|
312
|
+
stuckRulesContent: `When you encounter the following situations, do not spin or retry repeatedly — immediately ROUTE to PM (pm) for coordination:
|
|
313
|
+
1. Missing prerequisites (required files, directories, or code don't exist)
|
|
314
|
+
2. Waiting for another role's output that hasn't arrived
|
|
315
|
+
3. Task description is unclear or ambiguous, cannot determine the correct approach
|
|
316
|
+
4. Encountered issues outside your role's scope
|
|
317
|
+
5. Same operation has failed after 2 consecutive attempts
|
|
318
|
+
When escalating, explain: what task you're working on, where you're stuck, and who you think should assist. PM will coordinate globally and decide whether to assign to the right person or adjust task order.`,
|
|
319
|
+
worktreeRules: '# Worktree Isolation Rules',
|
|
320
|
+
worktreeRulesContent: `- dev/reviewer/tester roles must work in their assigned worktrees — modifying code in the main project directory or main branch is strictly prohibited
|
|
321
|
+
- Each role's CLAUDE.md specifies the "Code Work Directory" — that path is your worktree, all file operations must use that path
|
|
322
|
+
- PM and designer don't use worktrees — they work read-only in the main project directory
|
|
323
|
+
- Operating code in another dev group's worktree is strictly prohibited
|
|
324
|
+
- After code passes review, dev creates a PR to merge into main branch
|
|
325
|
+
- PM doesn't cherry-pick, only manages tags
|
|
326
|
+
- Each new task/feature must create a new worktree based on the latest main branch to ensure development on latest code`,
|
|
327
|
+
featureRecordShared: `# Feature Work Records
|
|
328
|
+
The system automatically manages \`context/features/{task-id}.md\` work record files:
|
|
329
|
+
- Automatically created when PM assigns tasks via ROUTE (with task + taskTitle fields)
|
|
330
|
+
- Work records are appended on each role ROUTE handoff
|
|
331
|
+
- Task file content is auto-injected as context when a role receives a message
|
|
332
|
+
Roles don't need to manually create or update these files.`,
|
|
333
|
+
sharedMemoryTitle: '# Shared Memory',
|
|
334
|
+
sharedMemoryDefault: '_Team-maintained shared knowledge, decisions, and information._',
|
|
335
|
+
|
|
336
|
+
// writeRoleClaudeMd
|
|
337
|
+
roleTitle: (label) => `# Role: ${label}`,
|
|
338
|
+
codeWorkDir: '# Code Work Directory',
|
|
339
|
+
codeWorkDirNote: 'Use this path for all code operations. Do not use the main project directory.',
|
|
340
|
+
personalMemory: '# Personal Memory',
|
|
341
|
+
personalMemoryDefault: '_Record important information, decisions, progress, and to-do items here._',
|
|
342
|
+
|
|
343
|
+
// ensureTaskFile
|
|
344
|
+
featureLabel: 'Feature',
|
|
345
|
+
statusPending: 'Pending',
|
|
346
|
+
assigneeLabel: 'Assignee',
|
|
347
|
+
createdAtLabel: 'Created',
|
|
348
|
+
requirementDesc: '## Requirement Description',
|
|
349
|
+
workRecord: '## Work Record',
|
|
350
|
+
|
|
351
|
+
// updateFeatureIndex
|
|
352
|
+
featureIndex: '# Feature Index',
|
|
353
|
+
lastUpdated: 'Last updated',
|
|
354
|
+
inProgressGroup: (n) => `## In Progress (${n})`,
|
|
355
|
+
completedGroup: (n) => `## Completed (${n})`,
|
|
356
|
+
colTaskId: 'task-id',
|
|
357
|
+
colTitle: 'Title',
|
|
358
|
+
colCreatedAt: 'Created',
|
|
359
|
+
|
|
360
|
+
// appendChangelog
|
|
361
|
+
changelogTitle: '# Changelog',
|
|
362
|
+
completedAt: 'Completed',
|
|
363
|
+
summaryLabel: 'Summary',
|
|
364
|
+
noSummary: '(No detailed summary)',
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Get a localized string for the given session language.
|
|
370
|
+
* Falls back to 'zh-CN' for unknown languages.
|
|
371
|
+
*/
|
|
372
|
+
export function getMessages(language) {
|
|
373
|
+
return messages[language] || messages['zh-CN'];
|
|
374
|
+
}
|
package/crew.js
CHANGED
|
@@ -19,6 +19,7 @@ import { homedir } from 'os';
|
|
|
19
19
|
import { execFile as execFileCb } from 'child_process';
|
|
20
20
|
import { promisify } from 'util';
|
|
21
21
|
import ctx from './context.js';
|
|
22
|
+
import { getMessages } from './crew-i18n.js';
|
|
22
23
|
|
|
23
24
|
const execFile = promisify(execFileCb);
|
|
24
25
|
|
|
@@ -329,6 +330,7 @@ async function saveSessionMeta(session) {
|
|
|
329
330
|
username: session.username,
|
|
330
331
|
agentId: session.agentId || null,
|
|
331
332
|
teamType: session.teamType || 'dev',
|
|
333
|
+
language: session.language || 'zh-CN',
|
|
332
334
|
costUsd: session.costUsd,
|
|
333
335
|
totalInputTokens: session.totalInputTokens,
|
|
334
336
|
totalOutputTokens: session.totalOutputTokens,
|
|
@@ -714,6 +716,7 @@ export async function resumeCrewSession(msg) {
|
|
|
714
716
|
username: username || meta.username,
|
|
715
717
|
agentId: meta.agentId || ctx.CONFIG?.agentName || null,
|
|
716
718
|
teamType: meta.teamType || 'dev',
|
|
719
|
+
language: meta.language || 'zh-CN',
|
|
717
720
|
createdAt: meta.createdAt || Date.now()
|
|
718
721
|
};
|
|
719
722
|
crewSessions.set(sessionId, session);
|
|
@@ -801,6 +804,7 @@ export async function createCrewSession(msg) {
|
|
|
801
804
|
roles: rawRoles = [], // [{ name, displayName, icon, description, claudeMd, model, budget, isDecisionMaker, count }]
|
|
802
805
|
maxRounds = 20,
|
|
803
806
|
teamType = 'dev',
|
|
807
|
+
language = 'zh-CN',
|
|
804
808
|
userId,
|
|
805
809
|
username
|
|
806
810
|
} = msg;
|
|
@@ -853,6 +857,7 @@ export async function createCrewSession(msg) {
|
|
|
853
857
|
username,
|
|
854
858
|
agentId: ctx.CONFIG?.agentName || null,
|
|
855
859
|
teamType,
|
|
860
|
+
language,
|
|
856
861
|
createdAt: Date.now()
|
|
857
862
|
};
|
|
858
863
|
|
|
@@ -890,7 +895,7 @@ export async function createCrewSession(msg) {
|
|
|
890
895
|
// 初始化共享区(角色目录 + CLAUDE.md)
|
|
891
896
|
session.initProgress = 'roles';
|
|
892
897
|
sendStatusUpdate(session);
|
|
893
|
-
await initSharedDir(sharedDir, goal, roles, projectDir, sharedKnowledge);
|
|
898
|
+
await initSharedDir(sharedDir, goal, roles, projectDir, sharedKnowledge, language);
|
|
894
899
|
|
|
895
900
|
// 初始化 git worktrees
|
|
896
901
|
const groupIndices = [...new Set(roles.filter(r => r.groupIndex > 0).map(r => r.groupIndex))];
|
|
@@ -904,7 +909,7 @@ export async function createCrewSession(msg) {
|
|
|
904
909
|
for (const role of roles) {
|
|
905
910
|
if (role.groupIndex > 0 && worktreeMap.has(role.groupIndex)) {
|
|
906
911
|
role.workDir = worktreeMap.get(role.groupIndex);
|
|
907
|
-
await writeRoleClaudeMd(sharedDir, role);
|
|
912
|
+
await writeRoleClaudeMd(sharedDir, role, language);
|
|
908
913
|
}
|
|
909
914
|
}
|
|
910
915
|
|
|
@@ -923,7 +928,7 @@ export async function createCrewSession(msg) {
|
|
|
923
928
|
if (goal && roles.length > 0 && session.status === 'running') {
|
|
924
929
|
const firstRole = roles.find(r => r.name === 'pm') || roles[0];
|
|
925
930
|
if (firstRole) {
|
|
926
|
-
const initialPrompt = buildInitialTask(goal, firstRole, roles);
|
|
931
|
+
const initialPrompt = buildInitialTask(goal, firstRole, roles, language);
|
|
927
932
|
await dispatchToRole(session, firstRole.name, initialPrompt, 'system');
|
|
928
933
|
}
|
|
929
934
|
}
|
|
@@ -985,7 +990,7 @@ export async function addRoleToSession(msg) {
|
|
|
985
990
|
}
|
|
986
991
|
|
|
987
992
|
// 初始化角色目录(CLAUDE.md + memory.md)
|
|
988
|
-
await initRoleDir(session.sharedDir, r);
|
|
993
|
+
await initRoleDir(session.sharedDir, r, session.language || 'zh-CN');
|
|
989
994
|
|
|
990
995
|
console.log(`[Crew] Role added: ${r.name} (${r.displayName}) to session ${sessionId}`);
|
|
991
996
|
|
|
@@ -1113,7 +1118,7 @@ export async function handleUpdateCrewSession(msg) {
|
|
|
1113
1118
|
* └── {roleName}/
|
|
1114
1119
|
* └── CLAUDE.md ← 角色定义 + 个人记忆
|
|
1115
1120
|
*/
|
|
1116
|
-
async function initSharedDir(sharedDir, goal, roles, projectDir, sharedKnowledge = '') {
|
|
1121
|
+
async function initSharedDir(sharedDir, goal, roles, projectDir, sharedKnowledge = '', language = 'zh-CN') {
|
|
1117
1122
|
await fs.mkdir(sharedDir, { recursive: true });
|
|
1118
1123
|
await fs.mkdir(join(sharedDir, 'context'), { recursive: true });
|
|
1119
1124
|
await fs.mkdir(join(sharedDir, 'sessions'), { recursive: true });
|
|
@@ -1121,17 +1126,17 @@ async function initSharedDir(sharedDir, goal, roles, projectDir, sharedKnowledge
|
|
|
1121
1126
|
|
|
1122
1127
|
// 初始化每个角色的目录
|
|
1123
1128
|
for (const role of roles) {
|
|
1124
|
-
await initRoleDir(sharedDir, role);
|
|
1129
|
+
await initRoleDir(sharedDir, role, language);
|
|
1125
1130
|
}
|
|
1126
1131
|
|
|
1127
1132
|
// 生成 .crew/CLAUDE.md(共享级)
|
|
1128
|
-
await writeSharedClaudeMd(sharedDir, goal, roles, projectDir, sharedKnowledge);
|
|
1133
|
+
await writeSharedClaudeMd(sharedDir, goal, roles, projectDir, sharedKnowledge, language);
|
|
1129
1134
|
}
|
|
1130
1135
|
|
|
1131
1136
|
/**
|
|
1132
1137
|
* 初始化角色目录: .crew/roles/{roleName}/CLAUDE.md
|
|
1133
1138
|
*/
|
|
1134
|
-
async function initRoleDir(sharedDir, role) {
|
|
1139
|
+
async function initRoleDir(sharedDir, role, language = 'zh-CN') {
|
|
1135
1140
|
const roleDir = join(sharedDir, 'roles', role.name);
|
|
1136
1141
|
await fs.mkdir(roleDir, { recursive: true });
|
|
1137
1142
|
|
|
@@ -1141,7 +1146,7 @@ async function initRoleDir(sharedDir, role) {
|
|
|
1141
1146
|
await fs.access(claudeMdPath);
|
|
1142
1147
|
// 已存在,不覆盖(保留角色自己写入的记忆)
|
|
1143
1148
|
} catch {
|
|
1144
|
-
await writeRoleClaudeMd(sharedDir, role);
|
|
1149
|
+
await writeRoleClaudeMd(sharedDir, role, language);
|
|
1145
1150
|
}
|
|
1146
1151
|
}
|
|
1147
1152
|
|
|
@@ -1149,50 +1154,32 @@ async function initRoleDir(sharedDir, role) {
|
|
|
1149
1154
|
* 写入 .crew/CLAUDE.md — 共享级(所有角色自动继承)
|
|
1150
1155
|
* 记忆直接写在 CLAUDE.md 中,Claude Code 会自动加载
|
|
1151
1156
|
*/
|
|
1152
|
-
async function writeSharedClaudeMd(sharedDir, goal, roles, projectDir, sharedKnowledge = '') {
|
|
1157
|
+
async function writeSharedClaudeMd(sharedDir, goal, roles, projectDir, sharedKnowledge = '', language = 'zh-CN') {
|
|
1158
|
+
const m = getMessages(language);
|
|
1153
1159
|
const sharedMemoryContent = sharedKnowledge
|
|
1154
|
-
?
|
|
1155
|
-
:
|
|
1160
|
+
? `${m.sharedMemoryTitle}\n${sharedKnowledge}\n`
|
|
1161
|
+
: `${m.sharedMemoryTitle}\n${m.sharedMemoryDefault}\n`;
|
|
1156
1162
|
|
|
1157
|
-
const claudeMd =
|
|
1163
|
+
const claudeMd = `${m.projectGoal}
|
|
1158
1164
|
${goal}
|
|
1159
1165
|
|
|
1160
|
-
|
|
1166
|
+
${m.projectCodePath}
|
|
1161
1167
|
${projectDir}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
${roles.length > 0 ? roles.map(r => `- ${roleLabel(r)}(${r.name}): ${r.description}${r.isDecisionMaker ?
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
4. 遇到超出自己职责范围的问题
|
|
1178
|
-
5. 连续尝试 2 次相同操作仍然失败
|
|
1179
|
-
上报时请说明:你在做什么任务、卡在哪里、你认为需要谁来协助。PM 会统筹全局,判断是分配给合适的人还是调整任务顺序。
|
|
1180
|
-
|
|
1181
|
-
# Worktree 隔离规则
|
|
1182
|
-
- dev/reviewer/tester 角色必须在各自分配的 worktree 中工作,绝对禁止在项目主目录或 main 分支上修改代码
|
|
1183
|
-
- 每个角色的 CLAUDE.md 会标明「代码工作目录」,该路径就是你的 worktree,所有文件操作必须使用该路径
|
|
1184
|
-
- PM 和 designer 不使用 worktree,他们在项目主目录下以只读方式工作
|
|
1185
|
-
- 绝对禁止在其他开发组的 worktree 中操作代码
|
|
1186
|
-
- 代码完成并通过 review 后,dev 自己提 PR 合并到 main 分支
|
|
1187
|
-
- PM 不做 cherry-pick,只负责打 tag
|
|
1188
|
-
- 每次新任务/新 feature 必须基于最新的 main 分支创建新的 worktree,确保在最新代码上开发
|
|
1189
|
-
|
|
1190
|
-
# Feature 工作记录
|
|
1191
|
-
系统自动管理 \`context/features/{task-id}.md\` 工作记录文件:
|
|
1192
|
-
- PM 通过 ROUTE 分配任务(带 task + taskTitle 字段)时自动创建
|
|
1193
|
-
- 每次角色 ROUTE 传递时自动追加工作记录
|
|
1194
|
-
- 角色收到消息时自动注入对应 task 文件内容作为上下文
|
|
1195
|
-
角色不需要手动创建或更新这些文件。
|
|
1168
|
+
${m.useAbsolutePath}
|
|
1169
|
+
|
|
1170
|
+
${m.teamMembersTitle}
|
|
1171
|
+
${roles.length > 0 ? roles.map(r => `- ${roleLabel(r)}(${r.name}): ${r.description}${r.isDecisionMaker ? ` (${m.decisionMakerTag})` : ''}`).join('\n') : m.noMembers}
|
|
1172
|
+
|
|
1173
|
+
${m.workConventions}
|
|
1174
|
+
${m.workConventionsContent}
|
|
1175
|
+
|
|
1176
|
+
${m.stuckRules}
|
|
1177
|
+
${m.stuckRulesContent}
|
|
1178
|
+
|
|
1179
|
+
${m.worktreeRules}
|
|
1180
|
+
${m.worktreeRulesContent}
|
|
1181
|
+
|
|
1182
|
+
${m.featureRecordShared}
|
|
1196
1183
|
|
|
1197
1184
|
${sharedMemoryContent}`;
|
|
1198
1185
|
|
|
@@ -1203,25 +1190,26 @@ ${sharedMemoryContent}`;
|
|
|
1203
1190
|
* 写入 .crew/roles/{roleName}/CLAUDE.md — 角色级
|
|
1204
1191
|
* 记忆直接追加在此文件中,Claude Code 自动加载
|
|
1205
1192
|
*/
|
|
1206
|
-
async function writeRoleClaudeMd(sharedDir, role) {
|
|
1193
|
+
async function writeRoleClaudeMd(sharedDir, role, language = 'zh-CN') {
|
|
1207
1194
|
const roleDir = join(sharedDir, 'roles', role.name);
|
|
1195
|
+
const m = getMessages(language);
|
|
1208
1196
|
|
|
1209
|
-
let claudeMd =
|
|
1197
|
+
let claudeMd = `${m.roleTitle(roleLabel(role))}
|
|
1210
1198
|
${role.claudeMd || role.description}
|
|
1211
1199
|
`;
|
|
1212
1200
|
|
|
1213
1201
|
// 有独立 worktree 的角色,覆盖代码工作目录
|
|
1214
1202
|
if (role.workDir) {
|
|
1215
1203
|
claudeMd += `
|
|
1216
|
-
|
|
1204
|
+
${m.codeWorkDir}
|
|
1217
1205
|
${role.workDir}
|
|
1218
|
-
|
|
1206
|
+
${m.codeWorkDirNote}
|
|
1219
1207
|
`;
|
|
1220
1208
|
}
|
|
1221
1209
|
|
|
1222
1210
|
claudeMd += `
|
|
1223
|
-
|
|
1224
|
-
|
|
1211
|
+
${m.personalMemory}
|
|
1212
|
+
${m.personalMemoryDefault}
|
|
1225
1213
|
`;
|
|
1226
1214
|
|
|
1227
1215
|
await fs.writeFile(join(roleDir, 'CLAUDE.md'), claudeMd);
|
|
@@ -1232,7 +1220,7 @@ _在这里记录重要的信息、决策、进展和待办事项。_
|
|
|
1232
1220
|
*/
|
|
1233
1221
|
async function updateSharedClaudeMd(session) {
|
|
1234
1222
|
const roles = Array.from(session.roles.values());
|
|
1235
|
-
await writeSharedClaudeMd(session.sharedDir, session.goal, roles, session.projectDir, session.sharedKnowledge);
|
|
1223
|
+
await writeSharedClaudeMd(session.sharedDir, session.goal, roles, session.projectDir, session.sharedKnowledge, session.language || 'zh-CN');
|
|
1236
1224
|
}
|
|
1237
1225
|
|
|
1238
1226
|
// =====================================================================
|
|
@@ -1257,17 +1245,18 @@ async function ensureTaskFile(session, taskId, taskTitle, assignee, summary) {
|
|
|
1257
1245
|
|
|
1258
1246
|
await fs.mkdir(featuresDir, { recursive: true });
|
|
1259
1247
|
|
|
1248
|
+
const m = getMessages(session.language || 'zh-CN');
|
|
1260
1249
|
const now = new Date().toISOString();
|
|
1261
|
-
const content = `#
|
|
1250
|
+
const content = `# ${m.featureLabel}: ${taskTitle}
|
|
1262
1251
|
- task-id: ${taskId}
|
|
1263
|
-
-
|
|
1264
|
-
-
|
|
1265
|
-
-
|
|
1252
|
+
- ${m.statusPending}
|
|
1253
|
+
- ${m.assigneeLabel}: ${assignee}
|
|
1254
|
+
- ${m.createdAtLabel}: ${now}
|
|
1266
1255
|
|
|
1267
|
-
|
|
1256
|
+
${m.requirementDesc}
|
|
1268
1257
|
${summary}
|
|
1269
1258
|
|
|
1270
|
-
|
|
1259
|
+
${m.workRecord}
|
|
1271
1260
|
`;
|
|
1272
1261
|
|
|
1273
1262
|
await fs.writeFile(filePath, content);
|
|
@@ -1340,6 +1329,7 @@ async function updateFeatureIndex(session) {
|
|
|
1340
1329
|
const featuresDir = join(session.sharedDir, 'context', 'features');
|
|
1341
1330
|
await fs.mkdir(featuresDir, { recursive: true });
|
|
1342
1331
|
|
|
1332
|
+
const m = getMessages(session.language || 'zh-CN');
|
|
1343
1333
|
const completed = session._completedTaskIds || new Set();
|
|
1344
1334
|
const allFeatures = Array.from(session.features.values());
|
|
1345
1335
|
|
|
@@ -1349,23 +1339,24 @@ async function updateFeatureIndex(session) {
|
|
|
1349
1339
|
const inProgress = allFeatures.filter(f => !completed.has(f.taskId));
|
|
1350
1340
|
const done = allFeatures.filter(f => completed.has(f.taskId));
|
|
1351
1341
|
|
|
1352
|
-
const
|
|
1353
|
-
|
|
1342
|
+
const locale = (session.language === 'en') ? 'en-US' : 'zh-CN';
|
|
1343
|
+
const now = new Date().toLocaleString(locale, { timeZone: 'Asia/Shanghai' });
|
|
1344
|
+
let content = `${m.featureIndex}\n> ${m.lastUpdated}: ${now}\n`;
|
|
1354
1345
|
|
|
1355
|
-
content += `\n
|
|
1346
|
+
content += `\n${m.inProgressGroup(inProgress.length)}\n`;
|
|
1356
1347
|
if (inProgress.length > 0) {
|
|
1357
|
-
content +=
|
|
1348
|
+
content += `| ${m.colTaskId} | ${m.colTitle} | ${m.colCreatedAt} |\n|---------|------|----------|\n`;
|
|
1358
1349
|
for (const f of inProgress) {
|
|
1359
|
-
const date = f.createdAt ? new Date(f.createdAt).toLocaleDateString(
|
|
1350
|
+
const date = f.createdAt ? new Date(f.createdAt).toLocaleDateString(locale) : '-';
|
|
1360
1351
|
content += `| ${f.taskId} | ${f.taskTitle} | ${date} |\n`;
|
|
1361
1352
|
}
|
|
1362
1353
|
}
|
|
1363
1354
|
|
|
1364
|
-
content += `\n
|
|
1355
|
+
content += `\n${m.completedGroup(done.length)}\n`;
|
|
1365
1356
|
if (done.length > 0) {
|
|
1366
|
-
content +=
|
|
1357
|
+
content += `| ${m.colTaskId} | ${m.colTitle} | ${m.colCreatedAt} |\n|---------|------|----------|\n`;
|
|
1367
1358
|
for (const f of done) {
|
|
1368
|
-
const date = f.createdAt ? new Date(f.createdAt).toLocaleDateString(
|
|
1359
|
+
const date = f.createdAt ? new Date(f.createdAt).toLocaleDateString(locale) : '-';
|
|
1369
1360
|
content += `| ${f.taskId} | ${f.taskTitle} | ${date} |\n`;
|
|
1370
1361
|
}
|
|
1371
1362
|
}
|
|
@@ -1383,6 +1374,8 @@ async function appendChangelog(session, taskId, taskTitle) {
|
|
|
1383
1374
|
await fs.mkdir(contextDir, { recursive: true });
|
|
1384
1375
|
const changelogPath = join(contextDir, 'changelog.md');
|
|
1385
1376
|
|
|
1377
|
+
const m = getMessages(session.language || 'zh-CN');
|
|
1378
|
+
|
|
1386
1379
|
// 读取 feature 文件提取最后一条工作记录作为摘要
|
|
1387
1380
|
const taskContent = await readTaskFile(session, taskId);
|
|
1388
1381
|
let summaryText = '';
|
|
@@ -1397,7 +1390,7 @@ async function appendChangelog(session, taskId, taskTitle) {
|
|
|
1397
1390
|
}
|
|
1398
1391
|
}
|
|
1399
1392
|
if (!summaryText) {
|
|
1400
|
-
summaryText =
|
|
1393
|
+
summaryText = m.noSummary;
|
|
1401
1394
|
}
|
|
1402
1395
|
|
|
1403
1396
|
// 限制摘要长度
|
|
@@ -1405,8 +1398,9 @@ async function appendChangelog(session, taskId, taskTitle) {
|
|
|
1405
1398
|
summaryText = summaryText.substring(0, 497) + '...';
|
|
1406
1399
|
}
|
|
1407
1400
|
|
|
1408
|
-
const
|
|
1409
|
-
const
|
|
1401
|
+
const locale = (session.language === 'en') ? 'en-US' : 'zh-CN';
|
|
1402
|
+
const now = new Date().toLocaleString(locale, { timeZone: 'Asia/Shanghai' });
|
|
1403
|
+
const entry = `\n## ${taskId}: ${taskTitle}\n- ${m.completedAt}: ${now}\n- ${m.summaryLabel}: ${summaryText}\n`;
|
|
1410
1404
|
|
|
1411
1405
|
// 如果文件不存在,先写 header
|
|
1412
1406
|
let exists = false;
|
|
@@ -1416,7 +1410,7 @@ async function appendChangelog(session, taskId, taskTitle) {
|
|
|
1416
1410
|
} catch {}
|
|
1417
1411
|
|
|
1418
1412
|
if (!exists) {
|
|
1419
|
-
await fs.writeFile(changelogPath,
|
|
1413
|
+
await fs.writeFile(changelogPath, `${m.changelogTitle}\n${entry}`);
|
|
1420
1414
|
} else {
|
|
1421
1415
|
await fs.appendFile(changelogPath, entry);
|
|
1422
1416
|
}
|
|
@@ -1587,68 +1581,52 @@ function buildRoleSystemPrompt(role, session) {
|
|
|
1587
1581
|
routeTargets = allRoles.filter(r => r.name !== role.name);
|
|
1588
1582
|
}
|
|
1589
1583
|
|
|
1590
|
-
|
|
1591
|
-
|
|
1584
|
+
const m = getMessages(session.language || 'zh-CN');
|
|
1585
|
+
|
|
1586
|
+
let prompt = `${m.teamCollab}
|
|
1587
|
+
${m.teamCollabIntro(session.goal)}
|
|
1592
1588
|
|
|
1593
|
-
|
|
1594
|
-
${allRoles.map(r => `- ${roleLabel(r)}: ${r.description}${r.isDecisionMaker ?
|
|
1589
|
+
${m.teamMembers}
|
|
1590
|
+
${allRoles.map(r => `- ${roleLabel(r)}: ${r.description}${r.isDecisionMaker ? ` (${m.decisionMakerTag})` : ''}`).join('\n')}`;
|
|
1595
1591
|
|
|
1596
1592
|
const hasMultiInstance = allRoles.some(r => r.groupIndex > 0);
|
|
1597
1593
|
|
|
1598
1594
|
if (routeTargets.length > 0) {
|
|
1599
|
-
prompt += `\n\n
|
|
1600
|
-
|
|
1595
|
+
prompt += `\n\n${m.routingRules}
|
|
1596
|
+
${m.routingIntro}
|
|
1601
1597
|
|
|
1602
1598
|
\`\`\`
|
|
1603
1599
|
---ROUTE---
|
|
1604
|
-
to:
|
|
1605
|
-
summary:
|
|
1600
|
+
to: <roleName>
|
|
1601
|
+
summary: <brief description>
|
|
1606
1602
|
---END_ROUTE---
|
|
1607
1603
|
\`\`\`
|
|
1608
1604
|
|
|
1609
|
-
|
|
1605
|
+
${m.routeTargets}
|
|
1610
1606
|
${routeTargets.map(r => `- ${r.name}: ${roleLabel(r)} — ${r.description}`).join('\n')}
|
|
1611
|
-
- human:
|
|
1607
|
+
- human: ${m.humanTarget}
|
|
1612
1608
|
|
|
1613
|
-
|
|
1614
|
-
- 如果你的工作还没完成,不需要添加 ROUTE 块
|
|
1615
|
-
- 如果你遇到不确定的问题,@ 决策者 "${session.decisionMaker}",而不是直接 @ human
|
|
1616
|
-
- 如果你是决策者且遇到需要人类判断的问题,才 @ human
|
|
1617
|
-
- 可以一次发多个 ROUTE 块来并行分配任务给不同角色
|
|
1618
|
-
- ROUTE 块必须在回复的最末尾
|
|
1619
|
-
- 当你的任务已完成且不需要其他角色继续时,ROUTE 回决策者 "${session.decisionMaker}" 做总结
|
|
1620
|
-
- 在正文中可用 @角色name 提及某个角色(如 @developer),但这不会触发路由,仅供阅读`;
|
|
1609
|
+
${m.routeNotes(session.decisionMaker)}`;
|
|
1621
1610
|
}
|
|
1622
1611
|
|
|
1623
1612
|
// 决策者额外 prompt
|
|
1624
1613
|
if (role.isDecisionMaker) {
|
|
1625
1614
|
const isDevTeam = session.teamType === 'dev';
|
|
1626
1615
|
|
|
1627
|
-
prompt += `\n\n
|
|
1628
|
-
|
|
1616
|
+
prompt += `\n\n${m.toolUsage}
|
|
1617
|
+
${m.toolUsageContent(isDevTeam)}`;
|
|
1629
1618
|
|
|
1630
|
-
prompt += `\n\n
|
|
1631
|
-
|
|
1632
|
-
- 如果你有足够的信息做出决策,直接决定并 @相关角色执行
|
|
1633
|
-
- 如果你需要更多信息,@具体角色请求补充
|
|
1634
|
-
- 如果问题超出你的能力范围或需要业务判断,@human 请人类决定
|
|
1635
|
-
- 你可以随时审查其他角色的工作并给出反馈`;
|
|
1619
|
+
prompt += `\n\n${m.dmRole}
|
|
1620
|
+
${m.dmRoleContent}`;
|
|
1636
1621
|
|
|
1637
1622
|
if (isDevTeam) {
|
|
1638
|
-
prompt +=
|
|
1639
|
-
- PM 不做代码分析。收到需求后直接将原始需求 ROUTE 给空闲 dev 做技术分析,dev 分析完返回 PM,PM 再拆分任务并直接分配执行。
|
|
1640
|
-
- PM 拥有 commit + push + tag 的自主权。只要修改没有大的 regression 影响(测试全通过),PM 可以自行决定 commit、push 和 tag,无需等待人工确认。只有当改动会直接影响对话交互逻辑时,才需要人工介入审核。`;
|
|
1623
|
+
prompt += m.dmDevExtra;
|
|
1641
1624
|
}
|
|
1642
1625
|
|
|
1643
1626
|
// 非开发团队:注入讨论模式 prompt
|
|
1644
1627
|
if (!isDevTeam) {
|
|
1645
|
-
prompt += `\n\n
|
|
1646
|
-
|
|
1647
|
-
- 角色之间可以自由讨论、相互请教、提出不同意见
|
|
1648
|
-
- 不需要严格的"分配→执行→审查"流程,鼓励角色之间直接对话
|
|
1649
|
-
- 当一个角色需要另一个角色的输入时,直接 ROUTE 给对方并说明需要什么
|
|
1650
|
-
- 决策者负责把控整体方向和最终决策,但日常讨论不需要经过决策者中转
|
|
1651
|
-
- 每次 ROUTE 仍建议包含 task 和 taskTitle 字段,用于消息按 feature 分组显示`;
|
|
1628
|
+
prompt += `\n\n${m.collabMode}
|
|
1629
|
+
${m.collabModeContent}`;
|
|
1652
1630
|
}
|
|
1653
1631
|
|
|
1654
1632
|
// 多实例模式(仅开发团队使用):注入开发组状态和调度规则
|
|
@@ -1662,80 +1640,55 @@ PM 可以使用所有工具,包括 Read、Grep、Glob、Bash、Edit、Write。
|
|
|
1662
1640
|
const state = session.roleStates.get(r.name);
|
|
1663
1641
|
const busy = state?.turnActive;
|
|
1664
1642
|
const task = state?.currentTask;
|
|
1665
|
-
if (busy && task) return `${r.name}(
|
|
1666
|
-
if (busy) return `${r.name}(
|
|
1667
|
-
return `${r.name}(
|
|
1643
|
+
if (busy && task) return `${r.name}(${m.groupBusy(task.taskId + ' ' + task.taskTitle)})`;
|
|
1644
|
+
if (busy) return `${r.name}(${m.groupBusyShort})`;
|
|
1645
|
+
return `${r.name}(${m.groupIdle})`;
|
|
1668
1646
|
});
|
|
1669
|
-
groupLines.push(
|
|
1647
|
+
groupLines.push(`${m.groupLabel(g)}: ${memberStrs.join(' ')}`);
|
|
1670
1648
|
}
|
|
1671
1649
|
|
|
1672
|
-
prompt += `\n\n
|
|
1650
|
+
prompt += `\n\n${m.execGroupStatus}
|
|
1673
1651
|
${groupLines.join(' / ')}
|
|
1674
1652
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
1. 每个子任务分配 task-id(如 task-1)和 taskTitle(如 "实现登录页面")
|
|
1678
|
-
2. 优先分配给**空闲**的开发组,避免给忙碌的 dev 发新任务
|
|
1679
|
-
3. 一次可以发**多个 ROUTE 块**来并行分配任务:
|
|
1653
|
+
${m.parallelRules}
|
|
1654
|
+
${m.parallelRulesContent(maxGroup)}
|
|
1680
1655
|
|
|
1681
1656
|
\`\`\`
|
|
1682
1657
|
---ROUTE---
|
|
1683
1658
|
to: dev-1
|
|
1684
1659
|
task: task-1
|
|
1685
|
-
taskTitle:
|
|
1686
|
-
summary:
|
|
1660
|
+
taskTitle: ${m.implLoginPage}
|
|
1661
|
+
summary: ${m.implLoginSummary}
|
|
1687
1662
|
---END_ROUTE---
|
|
1688
1663
|
|
|
1689
1664
|
---ROUTE---
|
|
1690
1665
|
to: dev-2
|
|
1691
1666
|
task: task-2
|
|
1692
|
-
taskTitle:
|
|
1693
|
-
summary:
|
|
1667
|
+
taskTitle: ${m.implRegisterPage}
|
|
1668
|
+
summary: ${m.implRegisterSummary}
|
|
1694
1669
|
---END_ROUTE---
|
|
1695
1670
|
\`\`\`
|
|
1696
1671
|
|
|
1697
|
-
|
|
1698
|
-
5. 等待**所有子任务完成**后再做汇总报告
|
|
1699
|
-
6. **每次 ROUTE 都必须包含 task 和 taskTitle 字段,不能省略。没有 task 字段的 ROUTE 会导致消息无法按 feature 分组显示**`;
|
|
1672
|
+
${m.parallelExample}`;
|
|
1700
1673
|
}
|
|
1701
1674
|
|
|
1702
1675
|
prompt += `\n
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
${isDevTeam ? '1. **代码已提交** - 所有代码修改已经 commit(如需要,可让 developer 执行 git commit)\n' : ''}${isDevTeam ? '2' : '1'}. **需要用户输入** - 遇到需要用户决定的问题时,@human 提出具体问题,等待用户回复
|
|
1706
|
-
${isDevTeam ? '3' : '2'}. **任务完成** - 所有任务已完成,给出完成总结(列出完成了什么${isDevTeam ? '、变更了哪些文件' : ''}、还有什么后续建议)
|
|
1676
|
+
${m.workflowEnd}
|
|
1677
|
+
${m.workflowEndContent(isDevTeam)}
|
|
1707
1678
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
# 任务清单
|
|
1711
|
-
你可以在回复中添加 TASKS 块来发布/更新任务清单,团队界面会自动展示:
|
|
1679
|
+
${m.taskList}
|
|
1680
|
+
${m.taskListContent}
|
|
1712
1681
|
|
|
1713
1682
|
\`\`\`
|
|
1714
|
-
|
|
1715
|
-
- [ ] 任务描述 #task-1 @角色name
|
|
1716
|
-
- [x] 已完成的任务 #task-2 @角色name
|
|
1717
|
-
---END_TASKS---
|
|
1683
|
+
${m.taskExample}
|
|
1718
1684
|
\`\`\`
|
|
1719
1685
|
|
|
1720
|
-
|
|
1721
|
-
- 每行一个任务,[ ] 表示待办,[x] 表示已完成
|
|
1722
|
-
- #taskId 标注对应的 feature ID(如 #task-1),用于精确关联任务完成状态
|
|
1723
|
-
- @角色name 标注负责人(可选)
|
|
1724
|
-
- 后续回复中可更新 TASKS 块(标记完成的任务)
|
|
1725
|
-
- TASKS 块不需要在回复最末尾,可以放在任意位置`;
|
|
1686
|
+
${m.taskListNotes}`;
|
|
1726
1687
|
}
|
|
1727
1688
|
|
|
1728
1689
|
// Feature 进度文件说明(系统自动管理,告知角色即可)
|
|
1729
|
-
prompt += `\n\n
|
|
1730
|
-
|
|
1731
|
-
- PM 分配任务时自动创建文件(包含 task-id、标题、需求描述)
|
|
1732
|
-
- 每次 ROUTE 传递时自动追加工作记录(角色名、时间、summary)
|
|
1733
|
-
- 你收到的消息中会包含 <task-context> 标签,里面是该任务的完整工作记录
|
|
1734
|
-
|
|
1735
|
-
系统还维护以下文件(自动更新,无需手动管理):
|
|
1736
|
-
- \`context/features/index.md\`:所有 feature 的索引(进行中/已完成分类),快速查看项目状态
|
|
1737
|
-
- \`context/changelog.md\`:已完成任务的变更记录,每个任务完成时自动追加摘要
|
|
1738
|
-
你不需要手动创建或更新这些文件,专注于你的本职工作即可。`;
|
|
1690
|
+
prompt += `\n\n${m.featureRecordTitle}
|
|
1691
|
+
${m.featureRecordContent}`;
|
|
1739
1692
|
|
|
1740
1693
|
// 执行者角色的组绑定 prompt(count > 1 时)
|
|
1741
1694
|
if (role.groupIndex > 0 && role.roleType === 'developer') {
|
|
@@ -1743,44 +1696,45 @@ ${isDevTeam ? '3' : '2'}. **任务完成** - 所有任务已完成,给出完
|
|
|
1743
1696
|
const rev = allRoles.find(r => r.roleType === 'reviewer' && r.groupIndex === gi);
|
|
1744
1697
|
const test = allRoles.find(r => r.roleType === 'tester' && r.groupIndex === gi);
|
|
1745
1698
|
if (rev && test) {
|
|
1746
|
-
prompt += `\n\n
|
|
1747
|
-
|
|
1748
|
-
- 审查者: ${roleLabel(rev)} (${rev.name})
|
|
1749
|
-
- 测试: ${roleLabel(test)} (${test.name})
|
|
1750
|
-
|
|
1751
|
-
开发完成后,请同时发两个 ROUTE 块分别给 ${rev.name} 和 ${test.name}:
|
|
1699
|
+
prompt += `\n\n${m.devGroupBinding}
|
|
1700
|
+
${m.devGroupBindingContent(gi, roleLabel(rev), rev.name, roleLabel(test), test.name)}
|
|
1752
1701
|
|
|
1753
1702
|
\`\`\`
|
|
1754
1703
|
---ROUTE---
|
|
1755
1704
|
to: ${rev.name}
|
|
1756
|
-
summary:
|
|
1705
|
+
summary: ${m.reviewCode}
|
|
1757
1706
|
---END_ROUTE---
|
|
1758
1707
|
|
|
1759
1708
|
---ROUTE---
|
|
1760
1709
|
to: ${test.name}
|
|
1761
|
-
summary:
|
|
1710
|
+
summary: ${m.testFeature}
|
|
1762
1711
|
---END_ROUTE---
|
|
1763
1712
|
\`\`\`
|
|
1764
1713
|
|
|
1765
|
-
|
|
1714
|
+
${m.devGroupBindingNote}`;
|
|
1766
1715
|
}
|
|
1767
1716
|
}
|
|
1768
1717
|
|
|
1718
|
+
// Language instruction
|
|
1719
|
+
if (session.language === 'en') {
|
|
1720
|
+
prompt += `\n\n# Language
|
|
1721
|
+
Always respond in English. Use English for all explanations, comments, and communications with the user. Technical terms and code identifiers should remain in their original form.`;
|
|
1722
|
+
} else {
|
|
1723
|
+
prompt += `\n\n# Language
|
|
1724
|
+
Always respond in 中文. Use 中文 for all explanations, comments, and communications with the user. Technical terms and code identifiers should remain in their original form.`;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1769
1727
|
return prompt;
|
|
1770
1728
|
}
|
|
1729
|
+
function buildInitialTask(goal, firstRole, allRoles, language = 'zh-CN') {
|
|
1730
|
+
const m = getMessages(language);
|
|
1731
|
+
return `${m.projectStart}
|
|
1771
1732
|
|
|
1772
|
-
|
|
1773
|
-
* 构建初始任务 prompt
|
|
1774
|
-
*/
|
|
1775
|
-
function buildInitialTask(goal, firstRole, allRoles) {
|
|
1776
|
-
return `项目启动!
|
|
1777
|
-
|
|
1778
|
-
目标: ${goal}
|
|
1733
|
+
${m.goalLabel}: ${goal}
|
|
1779
1734
|
|
|
1780
|
-
|
|
1781
|
-
完成后,通过 ROUTE 块将结果传递给下一个合适的角色。
|
|
1735
|
+
${m.firstRoleInstruction}
|
|
1782
1736
|
|
|
1783
|
-
|
|
1737
|
+
${m.availableRoles}
|
|
1784
1738
|
${allRoles.map(r => `- ${r.name}: ${roleLabel(r)} - ${r.description}`).join('\n')}`;
|
|
1785
1739
|
}
|
|
1786
1740
|
|