team-anya-cli 0.1.0

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.
Files changed (163) hide show
  1. package/README.md +38 -0
  2. package/anya/prompts/execution-guides/git-delivery.md +38 -0
  3. package/anya/prompts/execution-guides/testing-and-self-heal.md +28 -0
  4. package/anya/prompts/protocols/brief-assembly.md +55 -0
  5. package/anya/prompts/protocols/report.md +175 -0
  6. package/anya/prompts/protocols/review.md +90 -0
  7. package/anya/prompts/task-claude-md.template.md +32 -0
  8. package/apps/server/dist/broker/cc-broker.js +257 -0
  9. package/apps/server/dist/cli.js +296 -0
  10. package/apps/server/dist/config.js +76 -0
  11. package/apps/server/dist/daemon.js +51 -0
  12. package/apps/server/dist/gateway/chat-sync.js +135 -0
  13. package/apps/server/dist/gateway/command-router.js +114 -0
  14. package/apps/server/dist/gateway/commands/cancel.js +32 -0
  15. package/apps/server/dist/gateway/commands/help.js +16 -0
  16. package/apps/server/dist/gateway/commands/index.js +26 -0
  17. package/apps/server/dist/gateway/commands/restart.js +34 -0
  18. package/apps/server/dist/gateway/commands/status.js +34 -0
  19. package/apps/server/dist/gateway/commands/tasks.js +33 -0
  20. package/apps/server/dist/gateway/feishu-sender.js +346 -0
  21. package/apps/server/dist/gateway/feishu-ws.js +254 -0
  22. package/apps/server/dist/gateway/http.js +994 -0
  23. package/apps/server/dist/gateway/media-downloader.js +149 -0
  24. package/apps/server/dist/gateway/message-events.js +10 -0
  25. package/apps/server/dist/gateway/message-intake.js +50 -0
  26. package/apps/server/dist/gateway/message-queue.js +104 -0
  27. package/apps/server/dist/gateway/session-reader.js +142 -0
  28. package/apps/server/dist/gateway/ws-push.js +115 -0
  29. package/apps/server/dist/loid/brain.js +104 -0
  30. package/apps/server/dist/loid/clarifier.js +162 -0
  31. package/apps/server/dist/loid/context-builder.js +413 -0
  32. package/apps/server/dist/loid/mcp-server.js +104 -0
  33. package/apps/server/dist/loid/memory-settler.js +189 -0
  34. package/apps/server/dist/loid/opportunity-manager.js +148 -0
  35. package/apps/server/dist/loid/profile-updater.js +179 -0
  36. package/apps/server/dist/loid/reporter.js +148 -0
  37. package/apps/server/dist/loid/schemas.js +117 -0
  38. package/apps/server/dist/loid/self-calibrator.js +314 -0
  39. package/apps/server/dist/loid/session-manager.js +217 -0
  40. package/apps/server/dist/loid/session.js +271 -0
  41. package/apps/server/dist/loid/worktree-manager.js +191 -0
  42. package/apps/server/dist/main.js +337 -0
  43. package/apps/server/dist/tracing/index.js +2 -0
  44. package/apps/server/dist/tracing/trace-context.js +92 -0
  45. package/apps/server/dist/types/message.js +2 -0
  46. package/apps/server/dist/yor/yor-mcp-server.js +104 -0
  47. package/apps/server/dist/yor/yor-orchestrator.js +233 -0
  48. package/apps/web/dist/assets/index-CHIT0Dya.css +1 -0
  49. package/apps/web/dist/assets/index-CJzAjoVH.js +798 -0
  50. package/apps/web/dist/index.html +13 -0
  51. package/package.json +42 -0
  52. package/packages/cc-client/dist/claude-code-backend.js +664 -0
  53. package/packages/cc-client/dist/index.js +2 -0
  54. package/packages/cc-client/package.json +11 -0
  55. package/packages/core/dist/constants.js +59 -0
  56. package/packages/core/dist/errors.js +35 -0
  57. package/packages/core/dist/index.js +7 -0
  58. package/packages/core/dist/office-init.js +97 -0
  59. package/packages/core/dist/scope/checker.js +114 -0
  60. package/packages/core/dist/scope/defaults.js +40 -0
  61. package/packages/core/dist/scope/index.js +3 -0
  62. package/packages/core/dist/state-machine.js +85 -0
  63. package/packages/core/dist/types/audit.js +12 -0
  64. package/packages/core/dist/types/backend.js +2 -0
  65. package/packages/core/dist/types/commitment.js +17 -0
  66. package/packages/core/dist/types/communication.js +18 -0
  67. package/packages/core/dist/types/index.js +8 -0
  68. package/packages/core/dist/types/opportunity.js +27 -0
  69. package/packages/core/dist/types/org.js +26 -0
  70. package/packages/core/dist/types/task.js +46 -0
  71. package/packages/core/package.json +10 -0
  72. package/packages/db/dist/client.js +69 -0
  73. package/packages/db/dist/index.js +603 -0
  74. package/packages/db/dist/schema/audit-events.js +13 -0
  75. package/packages/db/dist/schema/cc-sessions.js +14 -0
  76. package/packages/db/dist/schema/chats.js +33 -0
  77. package/packages/db/dist/schema/commitments.js +18 -0
  78. package/packages/db/dist/schema/communication-events.js +14 -0
  79. package/packages/db/dist/schema/index.js +12 -0
  80. package/packages/db/dist/schema/message-log.js +20 -0
  81. package/packages/db/dist/schema/opportunities.js +23 -0
  82. package/packages/db/dist/schema/org.js +36 -0
  83. package/packages/db/dist/schema/projects.js +23 -0
  84. package/packages/db/dist/schema/tasks.js +46 -0
  85. package/packages/db/dist/schema/trace-spans.js +19 -0
  86. package/packages/db/package.json +12 -0
  87. package/packages/db/src/migrations/0000_simple_magneto.sql +148 -0
  88. package/packages/db/src/migrations/0001_nifty_morph.sql +42 -0
  89. package/packages/db/src/migrations/0002_common_joshua_kane.sql +20 -0
  90. package/packages/db/src/migrations/0003_add_cc_sessions.sql +13 -0
  91. package/packages/db/src/migrations/0004_jittery_triathlon.sql +1 -0
  92. package/packages/db/src/migrations/meta/0000_snapshot.json +987 -0
  93. package/packages/db/src/migrations/meta/0001_snapshot.json +1280 -0
  94. package/packages/db/src/migrations/meta/0002_snapshot.json +1417 -0
  95. package/packages/db/src/migrations/meta/0004_snapshot.json +1505 -0
  96. package/packages/db/src/migrations/meta/_journal.json +41 -0
  97. package/packages/mcp-tools/dist/index.js +41 -0
  98. package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
  99. package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
  100. package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
  101. package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
  102. package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
  103. package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
  104. package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
  105. package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
  106. package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
  107. package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
  108. package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
  109. package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
  110. package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
  111. package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
  112. package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
  113. package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
  114. package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
  115. package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
  116. package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
  117. package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
  118. package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
  119. package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
  120. package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
  121. package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
  122. package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
  123. package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
  124. package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +177 -0
  125. package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +38 -0
  126. package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
  127. package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
  128. package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
  129. package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +15 -0
  130. package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
  131. package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
  132. package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
  133. package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
  134. package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +191 -0
  135. package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
  136. package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
  137. package/packages/mcp-tools/dist/layer3/channel-send.js +90 -0
  138. package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
  139. package/packages/mcp-tools/dist/registry.js +779 -0
  140. package/packages/mcp-tools/package.json +13 -0
  141. package/workspace/.claude/settings.local.json +9 -0
  142. package/workspace/.mcp.json +12 -0
  143. package/workspace/CHARTER.md +73 -0
  144. package/workspace/CLAUDE.md +49 -0
  145. package/workspace/PROTOCOL.md +126 -0
  146. package/workspace/TOOLS.md +464 -0
  147. package/workspace/audit/.gitkeep +0 -0
  148. package/workspace/loid/CLAUDE.md +12 -0
  149. package/workspace/loid/PLAYBOOK.md +198 -0
  150. package/workspace/loid/PROFILE.md +78 -0
  151. package/workspace/memory/commitments/.gitkeep +0 -0
  152. package/workspace/memory/execution/.gitkeep +0 -0
  153. package/workspace/memory/people/.gitkeep +0 -0
  154. package/workspace/memory/projects/.gitkeep +0 -0
  155. package/workspace/memory/self/.gitkeep +0 -0
  156. package/workspace/reference/identity/.gitkeep +0 -0
  157. package/workspace/reference/org/escalation.yaml +24 -0
  158. package/workspace/reference/org/ownership.yaml +28 -0
  159. package/workspace/reports/.gitkeep +0 -0
  160. package/workspace/yor/CLAUDE.md +22 -0
  161. package/workspace/yor/PLAYBOOK.md +73 -0
  162. package/workspace/yor/PROFILE.md +52 -0
  163. package/workspace/yor/SELF-HEAL.md +39 -0
@@ -0,0 +1,271 @@
1
+ import { insertCCSession, updateCCSessionEnded } from '@team-anya/db';
2
+ // ── 常量 ──
3
+ const DEFAULT_IDLE_TIMEOUT_MS = 2 * 60 * 60 * 1000; // 2 小时
4
+ const DEFAULT_MAX_TURNS = 50;
5
+ // ── LoidSession ──
6
+ export class LoidSession {
7
+ state = 'idle';
8
+ broker;
9
+ instanceId;
10
+ config;
11
+ callbacks;
12
+ pendingMessages = [];
13
+ turnCount = 0;
14
+ maxTurns;
15
+ idleTimer = null;
16
+ lastActivityAt = new Date();
17
+ connected = false;
18
+ cliSessionId = null;
19
+ /** Broker 事件监听器引用(用于 dispose 时清理) */
20
+ boundResultListener = null;
21
+ boundCrashListener = null;
22
+ boundExitedListener = null;
23
+ constructor(config, callbacks) {
24
+ this.config = config;
25
+ this.callbacks = callbacks;
26
+ this.maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
27
+ this.broker = config.broker;
28
+ this.instanceId = config.instanceId;
29
+ }
30
+ // ── 公开方法 ──
31
+ /**
32
+ * 通过 CCBroker 启动 CC 进程并建立连接
33
+ */
34
+ async connect() {
35
+ // 注册 broker 事件监听(过滤当前 instanceId)
36
+ this.boundResultListener = (id, _role, result) => {
37
+ if (id === this.instanceId) {
38
+ this.handleResult(result);
39
+ }
40
+ };
41
+ this.boundCrashListener = (id, _role, _error) => {
42
+ if (id === this.instanceId) {
43
+ this.handleCrash();
44
+ }
45
+ };
46
+ this.boundExitedListener = (id) => {
47
+ if (id === this.instanceId && this.connected) {
48
+ if (this.state !== 'disposed') {
49
+ this.config.logger?.error('[LoidSession] CC 进程异常退出,触发崩溃恢复');
50
+ this.handleCrash();
51
+ }
52
+ }
53
+ };
54
+ this.broker.on('instance.result', this.boundResultListener);
55
+ this.broker.on('instance.crash', this.boundCrashListener);
56
+ this.broker.on('instance.exited', this.boundExitedListener);
57
+ // 监听 CC session ID
58
+ const sessionIdListener = (id, cliSessionId) => {
59
+ if (id === this.instanceId && this.config.db) {
60
+ this.cliSessionId = cliSessionId;
61
+ insertCCSession(this.config.db, {
62
+ session_id: cliSessionId,
63
+ role: 'loid',
64
+ instance_id: this.instanceId,
65
+ chat_id: this.config.chatId,
66
+ project_path: this.config.backendConfig.workingDir,
67
+ });
68
+ this.config.logger?.info(`[LoidSession] 记录 CC session: ${cliSessionId}`);
69
+ // 一次性监听,获取后移除
70
+ this.broker.removeListener('instance.sessionId', sessionIdListener);
71
+ }
72
+ };
73
+ this.broker.on('instance.sessionId', sessionIdListener);
74
+ await this.broker.spawn({
75
+ id: this.instanceId,
76
+ role: 'loid',
77
+ backendConfig: this.config.backendConfig,
78
+ logFile: this.config.logFile,
79
+ });
80
+ this.connected = true;
81
+ this.resetIdleTimer();
82
+ }
83
+ /**
84
+ * 投递消息:IDLE 时直接发送,EXECUTING 时缓冲
85
+ * 返回 false 表示 session 不接受消息(DISPOSED)
86
+ */
87
+ enqueue(msg) {
88
+ if (this.state === 'disposed') {
89
+ return false;
90
+ }
91
+ this.lastActivityAt = new Date();
92
+ if (this.state === 'idle') {
93
+ this.state = 'executing';
94
+ this.clearIdleTimer();
95
+ try {
96
+ this.broker.sendPrompt(this.instanceId, msg.prompt);
97
+ }
98
+ catch (err) {
99
+ this.config.logger?.error('[LoidSession] sendPrompt 失败:', err);
100
+ this.handleCrash();
101
+ }
102
+ }
103
+ else {
104
+ // EXECUTING → 缓冲
105
+ this.pendingMessages.push(msg);
106
+ }
107
+ return true;
108
+ }
109
+ /**
110
+ * 获取当前状态
111
+ */
112
+ getState() {
113
+ return this.state;
114
+ }
115
+ /**
116
+ * 获取当前轮次数
117
+ */
118
+ getTurnCount() {
119
+ return this.turnCount;
120
+ }
121
+ /**
122
+ * 获取缓冲区消息数
123
+ */
124
+ getPendingCount() {
125
+ return this.pendingMessages.length;
126
+ }
127
+ /**
128
+ * 获取最后活跃时间(供 LRU 淘汰使用)
129
+ */
130
+ getLastActivityAt() {
131
+ return this.lastActivityAt;
132
+ }
133
+ /**
134
+ * 销毁 session,释放资源
135
+ */
136
+ async dispose() {
137
+ if (this.state === 'disposed')
138
+ return;
139
+ this.state = 'disposed';
140
+ this.clearIdleTimer();
141
+ this.connected = false;
142
+ // 更新 cc_sessions.ended_at
143
+ if (this.cliSessionId && this.config.db) {
144
+ updateCCSessionEnded(this.config.db, this.cliSessionId);
145
+ }
146
+ this.removeBrokerListeners();
147
+ await this.broker.dispose(this.instanceId).catch(() => { });
148
+ }
149
+ // ── 私有方法 ──
150
+ /**
151
+ * CC 执行完一轮后的 drain loop
152
+ */
153
+ handleResult(result) {
154
+ this.callbacks.onResult?.(result);
155
+ this.turnCount++;
156
+ this.lastActivityAt = new Date();
157
+ if (this.state === 'disposed')
158
+ return;
159
+ // drain: 缓冲区有消息 → 合并发送
160
+ if (this.pendingMessages.length > 0) {
161
+ const buffered = this.pendingMessages.splice(0);
162
+ const prompt = this.formatBufferedMessages(buffered);
163
+ try {
164
+ this.broker.sendPrompt(this.instanceId, prompt);
165
+ }
166
+ catch (err) {
167
+ this.config.logger?.error('[LoidSession] drain sendPrompt 失败:', err);
168
+ this.handleCrash();
169
+ }
170
+ return;
171
+ }
172
+ // 达到硬上限 → 直接 dispose(不生成摘要)
173
+ if (this.turnCount >= this.maxTurns) {
174
+ this.config.logger?.info(`[LoidSession] 达到硬上限 (${this.maxTurns} 轮),销毁 session`);
175
+ this.disposeAndNotify();
176
+ return;
177
+ }
178
+ // 无待处理消息 → 回 IDLE,启动空闲计时器
179
+ this.state = 'idle';
180
+ this.resetIdleTimer();
181
+ }
182
+ /**
183
+ * 崩溃处理:交还未处理消息,标记 disposed
184
+ */
185
+ handleCrash() {
186
+ if (this.state === 'disposed')
187
+ return;
188
+ this.state = 'disposed';
189
+ this.clearIdleTimer();
190
+ this.connected = false;
191
+ this.removeBrokerListeners();
192
+ const orphans = this.pendingMessages.splice(0);
193
+ this.callbacks.onCrash(orphans);
194
+ }
195
+ /**
196
+ * dispose 并通知回调
197
+ */
198
+ async disposeAndNotify() {
199
+ await this.dispose();
200
+ this.callbacks.onDisposed();
201
+ }
202
+ /**
203
+ * 重置空闲计时器
204
+ */
205
+ resetIdleTimer() {
206
+ this.clearIdleTimer();
207
+ const timeout = this.config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
208
+ this.idleTimer = setTimeout(() => {
209
+ if (this.state === 'idle') {
210
+ this.config.logger?.info(`[LoidSession] 空闲超时 (${timeout}ms),销毁 session`);
211
+ this.disposeAndNotify();
212
+ }
213
+ }, timeout);
214
+ }
215
+ /**
216
+ * 清除空闲计时器
217
+ */
218
+ clearIdleTimer() {
219
+ if (this.idleTimer) {
220
+ clearTimeout(this.idleTimer);
221
+ this.idleTimer = null;
222
+ }
223
+ }
224
+ /**
225
+ * 移除 broker 事件监听器
226
+ */
227
+ removeBrokerListeners() {
228
+ if (this.boundResultListener) {
229
+ this.broker.removeListener('instance.result', this.boundResultListener);
230
+ this.boundResultListener = null;
231
+ }
232
+ if (this.boundCrashListener) {
233
+ this.broker.removeListener('instance.crash', this.boundCrashListener);
234
+ this.boundCrashListener = null;
235
+ }
236
+ if (this.boundExitedListener) {
237
+ this.broker.removeListener('instance.exited', this.boundExitedListener);
238
+ this.boundExitedListener = null;
239
+ }
240
+ }
241
+ /**
242
+ * 合并缓冲消息为单个 prompt
243
+ * 临时内联实现(正式版本由 context-builder 的 formatBufferedMessages 提供)
244
+ */
245
+ formatBufferedMessages(messages) {
246
+ if (messages.length === 1) {
247
+ return messages[0].prompt;
248
+ }
249
+ const lines = [];
250
+ lines.push(`你在处理上一件事的过程中,该对话又收到了 ${messages.length} 条新消息:`);
251
+ lines.push('');
252
+ for (let i = 0; i < messages.length; i++) {
253
+ const msg = messages[i];
254
+ const time = msg.enqueuedAt.toLocaleTimeString('zh-CN', {
255
+ hour: '2-digit',
256
+ minute: '2-digit',
257
+ second: '2-digit',
258
+ hour12: false,
259
+ });
260
+ const typeLabel = msg.type === 'delivery' ? ' [交付]'
261
+ : msg.type === 'recovery' ? ' [恢复]'
262
+ : '';
263
+ lines.push(`--- 消息 ${i + 1} (${time}${typeLabel}) ---`);
264
+ lines.push(msg.prompt);
265
+ lines.push('');
266
+ }
267
+ lines.push('请综合这些新消息,决定下一步行动。');
268
+ return lines.join('\n');
269
+ }
270
+ }
271
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1,191 @@
1
+ import { execFile as execFileCb } from 'node:child_process';
2
+ import { mkdir, access, readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { getAllProjects, upsertProject, getProjectRepos, syncProjectRepos } from '@team-anya/db';
6
+ const execFile = promisify(execFileCb);
7
+ export class WorktreeManager {
8
+ workspacePath;
9
+ projectsConfigPath;
10
+ reposPath;
11
+ db;
12
+ logger;
13
+ config = null;
14
+ constructor(deps) {
15
+ this.workspacePath = deps.workspacePath;
16
+ this.projectsConfigPath = deps.projectsConfigPath;
17
+ this.reposPath = deps.reposPath;
18
+ this.db = deps.db;
19
+ this.logger = deps.logger ?? { info: console.log, error: console.error };
20
+ }
21
+ /**
22
+ * 加载项目配置
23
+ *
24
+ * 优先从 DB 读取。如果 DB 为空且 JSON 文件存在,则从 JSON 导入到 DB。
25
+ */
26
+ async loadConfig() {
27
+ if (this.config !== null)
28
+ return this.config;
29
+ // 先从 DB 读取
30
+ const dbProjects = getAllProjects(this.db);
31
+ if (dbProjects.length > 0) {
32
+ this.config = {
33
+ projects: dbProjects.map(p => {
34
+ const repos = getProjectRepos(this.db, p.project_id);
35
+ return {
36
+ projectId: p.project_id,
37
+ name: p.name,
38
+ description: p.description ?? undefined,
39
+ repos: repos.map(r => ({
40
+ name: r.name,
41
+ gitUrl: r.git_url ?? undefined,
42
+ repoPath: r.repo_path ?? undefined,
43
+ defaultBranch: r.default_branch ?? undefined,
44
+ })),
45
+ claudeMd: p.claude_md ?? undefined,
46
+ platform: p.platform ?? 'github',
47
+ };
48
+ }),
49
+ };
50
+ return this.config;
51
+ }
52
+ // DB 为空,尝试从 JSON 文件导入
53
+ try {
54
+ await access(this.projectsConfigPath);
55
+ const content = await readFile(this.projectsConfigPath, 'utf-8');
56
+ const parsed = JSON.parse(content);
57
+ const jsonProjects = (parsed.projects ?? []);
58
+ // 导入到 DB(项目 + repos 关联表)
59
+ for (const p of jsonProjects) {
60
+ upsertProject(this.db, {
61
+ project_id: p.projectId,
62
+ name: p.name,
63
+ description: p.description,
64
+ platform: p.platform ?? 'github',
65
+ claude_md: p.claudeMd,
66
+ });
67
+ syncProjectRepos(this.db, p.projectId, p.repos.map(r => ({
68
+ name: r.name,
69
+ git_url: r.gitUrl,
70
+ repo_path: r.repoPath,
71
+ default_branch: r.defaultBranch,
72
+ })));
73
+ }
74
+ this.logger.info(`[WorktreeManager] 从 JSON 导入 ${jsonProjects.length} 个项目到 DB`);
75
+ this.config = { projects: jsonProjects };
76
+ this.validateUniqueIds(this.config);
77
+ }
78
+ catch (err) {
79
+ if (err instanceof Error && err.message.startsWith('ID 冲突')) {
80
+ throw err;
81
+ }
82
+ this.logger.info('[WorktreeManager] 项目注册表为空,使用空列表');
83
+ this.config = { projects: [] };
84
+ }
85
+ return this.config;
86
+ }
87
+ /**
88
+ * 向后兼容:返回 projects 列表
89
+ */
90
+ async loadProjects() {
91
+ const config = await this.loadConfig();
92
+ return config.projects;
93
+ }
94
+ /**
95
+ * 重新从 DB 加载(清除缓存)
96
+ */
97
+ invalidateCache() {
98
+ this.config = null;
99
+ }
100
+ validateUniqueIds(config) {
101
+ const ids = new Set();
102
+ for (const p of config.projects) {
103
+ if (ids.has(p.projectId)) {
104
+ throw new Error(`ID 冲突: "${p.projectId}" 在配置中重复出现`);
105
+ }
106
+ ids.add(p.projectId);
107
+ }
108
+ }
109
+ /**
110
+ * 解析 repoPath:如果配置中未指定,使用 {reposPath}/{name} 作为默认值
111
+ */
112
+ resolveRepoPath(repoPath, name) {
113
+ return repoPath ?? join(this.reposPath, name);
114
+ }
115
+ findProject(id) {
116
+ return this.config?.projects.find(p => p.projectId === id);
117
+ }
118
+ /**
119
+ * 自动初始化:遍历所有配置的仓库,缺失的自动 clone
120
+ * 适合在服务启动时或手动初始化时调用
121
+ */
122
+ async ensureRepos() {
123
+ await this.loadConfig();
124
+ await mkdir(this.reposPath, { recursive: true });
125
+ const result = { cloned: [], existed: [], failed: [] };
126
+ // 收集所有需要检查的仓库
127
+ for (const project of this.config.projects) {
128
+ for (const repo of project.repos) {
129
+ const repoPath = this.resolveRepoPath(repo.repoPath, repo.name);
130
+ // 检查是否已存在
131
+ try {
132
+ await access(join(repoPath, '.git'));
133
+ result.existed.push(repo.name);
134
+ continue;
135
+ }
136
+ catch {
137
+ // 不存在,需要 clone
138
+ }
139
+ if (!repo.gitUrl) {
140
+ result.failed.push({
141
+ name: repo.name,
142
+ error: `仓库不存在且未配置 gitUrl: ${repoPath}`,
143
+ });
144
+ continue;
145
+ }
146
+ try {
147
+ this.logger.info(`[WorktreeManager] 克隆 ${repo.name}: ${repo.gitUrl}`);
148
+ const cloneArgs = ['clone'];
149
+ if (repo.defaultBranch) {
150
+ cloneArgs.push('--branch', repo.defaultBranch);
151
+ }
152
+ cloneArgs.push(repo.gitUrl, repoPath);
153
+ await execFile('git', cloneArgs);
154
+ result.cloned.push(repo.name);
155
+ this.logger.info(`[WorktreeManager] 克隆完成: ${repo.name}`);
156
+ }
157
+ catch (err) {
158
+ const msg = err instanceof Error ? err.message : String(err);
159
+ result.failed.push({ name: repo.name, error: msg });
160
+ this.logger.error(`[WorktreeManager] 克隆失败 (${repo.name}):`, err);
161
+ }
162
+ }
163
+ }
164
+ return result;
165
+ }
166
+ /**
167
+ * 获取项目配置(不执行任何 git 命令)
168
+ * 找到项目 → 返回 project 模式(repos 数组)
169
+ * 未找到 → 返回 adhoc 模式
170
+ */
171
+ async getProjectConfig(projectId) {
172
+ await this.loadConfig();
173
+ if (projectId) {
174
+ const project = this.findProject(projectId);
175
+ if (project) {
176
+ return {
177
+ mode: 'project',
178
+ platform: project.platform ?? 'github',
179
+ repos: project.repos.map(repo => ({
180
+ name: repo.name,
181
+ repo_path: this.resolveRepoPath(repo.repoPath, repo.name),
182
+ default_branch: repo.defaultBranch ?? 'main',
183
+ })),
184
+ claudeMd: project.claudeMd,
185
+ };
186
+ }
187
+ }
188
+ return { mode: 'adhoc', platform: 'local' };
189
+ }
190
+ }
191
+ //# sourceMappingURL=worktree-manager.js.map