openmatrix 0.2.3 → 0.2.4

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.
@@ -40,6 +40,7 @@ const state_manager_js_1 = require("../../storage/state-manager.js");
40
40
  const git_commit_manager_js_1 = require("../../orchestrator/git-commit-manager.js");
41
41
  const fs = __importStar(require("fs/promises"));
42
42
  const path = __importStar(require("path"));
43
+ const error_handler_js_1 = require("../../utils/error-handler.js");
43
44
  exports.completeCommand = new commander_1.Command('complete')
44
45
  .description('标记任务完成并更新全局统计')
45
46
  .argument('<taskId>', '任务ID (如 TASK-001)')
@@ -118,8 +119,9 @@ exports.completeCommand = new commander_1.Command('complete')
118
119
  // 原子追加写入全局 context.md(O_APPEND flag 保证并发安全)
119
120
  await fs.appendFile(contextFile, contextEntry, 'utf-8');
120
121
  }
121
- catch {
122
- // 忽略写入错误
122
+ catch (error) {
123
+ // 记录错误但不影响主流程
124
+ (0, error_handler_js_1.logError)(error, { operation: 'appendContextMd', file: contextFile, taskId });
123
125
  }
124
126
  }
125
127
  // 6. Git 自动提交
@@ -8,6 +8,7 @@ const state_machine_js_1 = require("./state-machine.js");
8
8
  const phase_executor_js_1 = require("./phase-executor.js");
9
9
  const retry_manager_js_1 = require("./retry-manager.js");
10
10
  const ai_reviewer_js_1 = require("./ai-reviewer.js");
11
+ const logger_js_1 = require("../utils/logger.js");
11
12
  /**
12
13
  * OrchestratorExecutor - 执行循环核心
13
14
  *
@@ -27,11 +28,20 @@ class OrchestratorExecutor {
27
28
  constructor(stateManager, approvalManager, config) {
28
29
  this.stateManager = stateManager;
29
30
  this.approvalManager = approvalManager;
31
+ // 从 state.config 读取 taskTimeout,如果未定义则使用默认值
32
+ const stateConfig = stateManager.getState().then(s => s.config).catch(() => null);
33
+ const defaultTaskTimeout = 600000; // 10 分钟(毫秒)
30
34
  this.config = {
31
35
  maxConcurrent: 3,
32
- taskTimeout: 600000, // 10 分钟
36
+ taskTimeout: defaultTaskTimeout,
33
37
  ...config
34
38
  };
39
+ // 异步获取配置并更新 taskTimeout
40
+ stateConfig.then(cfg => {
41
+ if (cfg?.taskTimeout) {
42
+ this.config.taskTimeout = cfg.taskTimeout;
43
+ }
44
+ });
35
45
  this.scheduler = new scheduler_js_1.Scheduler(stateManager, {
36
46
  maxConcurrentTasks: this.config.maxConcurrent,
37
47
  taskTimeout: this.config.taskTimeout
@@ -393,9 +403,11 @@ class OrchestratorExecutor {
393
403
  */
394
404
  setupTaskTimeout(taskId) {
395
405
  const timer = setTimeout(async () => {
396
- console.error(`⏰ 任务超时: ${taskId} (${this.config.taskTimeout / 1000}s)`);
406
+ const timeoutSeconds = this.config.taskTimeout / 1000;
407
+ console.error(`⏰ 任务超时: ${taskId} (${timeoutSeconds}s)`);
408
+ logger_js_1.logger.task.timeout(taskId, timeoutSeconds);
397
409
  this.taskTimers.delete(taskId);
398
- await this.scheduler.markTaskFailed(taskId, `Task timed out after ${this.config.taskTimeout / 1000}s`);
410
+ await this.scheduler.markTaskFailed(taskId, `Task timed out after ${timeoutSeconds}s`);
399
411
  }, this.config.taskTimeout);
400
412
  this.taskTimers.set(taskId, timer);
401
413
  }
@@ -40,6 +40,7 @@ const util_1 = require("util");
40
40
  const path = __importStar(require("path"));
41
41
  const fs = __importStar(require("fs/promises"));
42
42
  const gitignore_js_1 = require("../utils/gitignore.js");
43
+ const error_handler_js_1 = require("../utils/error-handler.js");
43
44
  const execAsync = (0, util_1.promisify)(child_process_1.exec);
44
45
  /**
45
46
  * GitCommitManager - Git 自动提交管理器
@@ -66,8 +67,9 @@ class GitCommitManager {
66
67
  const { stdout } = await execAsync('git rev-parse --show-toplevel', { cwd: this.repoPath });
67
68
  this.gitRoot = stdout.trim();
68
69
  }
69
- catch {
70
+ catch (error) {
70
71
  // 不是 git 仓库,回退到 repoPath
72
+ (0, error_handler_js_1.logError)(error, { operation: 'getGitRoot', file: this.repoPath });
71
73
  this.gitRoot = this.repoPath;
72
74
  }
73
75
  return this.gitRoot;
@@ -86,7 +88,8 @@ class GitCommitManager {
86
88
  await execAsync('git rev-parse --is-inside-work-tree', { cwd: this.repoPath });
87
89
  return true;
88
90
  }
89
- catch {
91
+ catch (error) {
92
+ (0, error_handler_js_1.logError)(error, { operation: 'isGitRepo', file: this.repoPath });
90
93
  return false;
91
94
  }
92
95
  }
@@ -108,7 +111,8 @@ class GitCommitManager {
108
111
  .filter(line => line.trim())
109
112
  .map(line => line.slice(3).trim());
110
113
  }
111
- catch {
114
+ catch (error) {
115
+ (0, error_handler_js_1.logError)(error, { operation: 'getUncommittedFiles', file: this.repoPath });
112
116
  return [];
113
117
  }
114
118
  }
@@ -134,7 +138,8 @@ class GitCommitManager {
134
138
  return { status, path: filePath };
135
139
  });
136
140
  }
137
- catch {
141
+ catch (error) {
142
+ (0, error_handler_js_1.logError)(error, { operation: 'getUncommittedFilesWithStatus', file: this.repoPath });
138
143
  return [];
139
144
  }
140
145
  }
@@ -158,8 +163,9 @@ class GitCommitManager {
158
163
  }
159
164
  }
160
165
  }
161
- catch {
162
- // ignore
166
+ catch (error) {
167
+ // ignore diff stats errors
168
+ (0, error_handler_js_1.logError)(error, { operation: 'getDiffStats', file: this.repoPath });
163
169
  }
164
170
  return stats;
165
171
  }
@@ -358,7 +364,12 @@ class GitCommitManager {
358
364
  }
359
365
  if (filesToUnstage.length > 0) {
360
366
  const unstageCmd = filesToUnstage.map(f => `"${f}"`).join(' ');
361
- await execAsync(`git reset HEAD -- ${unstageCmd}`, { cwd: this.repoPath }).catch(() => { });
367
+ try {
368
+ await execAsync(`git reset HEAD -- ${unstageCmd}`, { cwd: this.repoPath });
369
+ }
370
+ catch (error) {
371
+ (0, error_handler_js_1.logError)(error, { operation: 'gitResetFiles', file: this.repoPath, metadata: { files: filesToUnstage } });
372
+ }
362
373
  }
363
374
  // 检查是否有文件被暂存(避免空提交)
364
375
  const { stdout: staged } = await execAsync('git diff --cached --name-only', { cwd: this.repoPath });
@@ -384,7 +395,12 @@ class GitCommitManager {
384
395
  }
385
396
  finally {
386
397
  // 清理临时文件
387
- await fs.unlink(tmpFile).catch(() => { });
398
+ try {
399
+ await fs.unlink(tmpFile);
400
+ }
401
+ catch (error) {
402
+ (0, error_handler_js_1.logError)(error, { operation: 'cleanupCommitMsgTmp', file: tmpFile });
403
+ }
388
404
  }
389
405
  }
390
406
  catch (error) {
@@ -1,32 +1,6 @@
1
1
  import { StateManager } from '../storage/state-manager.js';
2
2
  import { ApprovalManager } from './approval-manager.js';
3
- import type { Approval } from '../types/index.js';
4
- /**
5
- * Meeting 状态
6
- */
7
- export type MeetingStatus = 'pending' | 'in_progress' | 'resolved' | 'cancelled';
8
- /**
9
- * Meeting 类型
10
- */
11
- export type MeetingType = 'blocking' | 'decision' | 'review' | 'planning';
12
- /**
13
- * Meeting 记录
14
- */
15
- export interface Meeting {
16
- id: string;
17
- type: MeetingType;
18
- status: MeetingStatus;
19
- taskId: string;
20
- title: string;
21
- description: string;
22
- blockingReason?: string;
23
- impactScope: string[];
24
- participants: string[];
25
- resolution?: string;
26
- createdAt: string;
27
- startedAt?: string;
28
- resolvedAt?: string;
29
- }
3
+ import type { Approval, Meeting } from '../types/index.js';
30
4
  /**
31
5
  * MeetingManager - Meeting 管理器
32
6
  *
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MeetingManager = void 0;
4
+ // Meeting 类型已在 types/index.ts 中定义,这里不再重复定义
4
5
  /**
5
6
  * MeetingManager - Meeting 管理器
6
7
  *
@@ -8,11 +8,22 @@ export interface RetryItem {
8
8
  attempt: number;
9
9
  nextRetryAt: number;
10
10
  error: string;
11
+ addedAt: string;
11
12
  }
12
13
  export declare class RetryManager {
13
14
  private queue;
14
15
  private config;
15
- constructor(config?: Partial<RetryConfig>);
16
+ private store;
17
+ private queuePath;
18
+ constructor(config?: Partial<RetryConfig>, basePath?: string);
19
+ /**
20
+ * 从文件加载队列(进程重启恢复)
21
+ */
22
+ private loadQueue;
23
+ /**
24
+ * 持久化队列到文件
25
+ */
26
+ private persistQueue;
16
27
  /**
17
28
  * Add a failed task to retry queue
18
29
  */
@@ -37,5 +48,13 @@ export declare class RetryManager {
37
48
  ready: number;
38
49
  pending: number;
39
50
  };
51
+ /**
52
+ * 获取队列内容(用于测试和调试)
53
+ */
54
+ getQueue(): RetryItem[];
55
+ /**
56
+ * 清空队列
57
+ */
58
+ clearQueue(): void;
40
59
  private calculateDelay;
41
60
  }
@@ -1,16 +1,49 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RetryManager = void 0;
4
+ const file_store_js_1 = require("../storage/file-store.js");
4
5
  class RetryManager {
5
6
  queue = [];
6
7
  config;
7
- constructor(config = {}) {
8
+ store = null;
9
+ queuePath = 'retry-queue.json';
10
+ constructor(config = {}, basePath) {
8
11
  this.config = {
9
12
  maxRetries: 3,
10
13
  backoff: 'exponential',
11
14
  baseDelay: 30000, // 30 seconds
12
15
  ...config
13
16
  };
17
+ // 初始化持久化存储
18
+ if (basePath) {
19
+ this.store = new file_store_js_1.FileStore(basePath);
20
+ this.loadQueue();
21
+ }
22
+ }
23
+ /**
24
+ * 从文件加载队列(进程重启恢复)
25
+ */
26
+ loadQueue() {
27
+ if (!this.store)
28
+ return;
29
+ const data = this.store.readJsonSync(this.queuePath);
30
+ if (data?.queue) {
31
+ // 过滤掉已经过期的重试项(超过 maxRetries 或超过 24 小时)
32
+ const now = Date.now();
33
+ const maxAge = 24 * 60 * 60 * 1000; // 24 hours
34
+ this.queue = data.queue.filter(item => {
35
+ const addedTime = new Date(item.addedAt).getTime();
36
+ return item.attempt < this.config.maxRetries && (now - addedTime) < maxAge;
37
+ });
38
+ }
39
+ }
40
+ /**
41
+ * 持久化队列到文件
42
+ */
43
+ persistQueue() {
44
+ if (!this.store)
45
+ return;
46
+ this.store.writeJsonSync(this.queuePath, { queue: this.queue, updatedAt: new Date().toISOString() });
14
47
  }
15
48
  /**
16
49
  * Add a failed task to retry queue
@@ -19,7 +52,7 @@ class RetryManager {
19
52
  const existing = this.queue.find(item => item.taskId === taskId);
20
53
  if (existing) {
21
54
  existing.attempt++;
22
- existing.nextRetryAt = this.calculateDelay(existing.attempt);
55
+ existing.nextRetryAt = Date.now() + this.calculateDelay(existing.attempt);
23
56
  existing.error = error;
24
57
  }
25
58
  else {
@@ -27,9 +60,11 @@ class RetryManager {
27
60
  taskId,
28
61
  attempt: 1,
29
62
  nextRetryAt: Date.now() + this.calculateDelay(1),
30
- error
63
+ error,
64
+ addedAt: new Date().toISOString()
31
65
  });
32
66
  }
67
+ this.persistQueue();
33
68
  }
34
69
  /**
35
70
  * Get tasks ready for retry
@@ -45,6 +80,7 @@ class RetryManager {
45
80
  const index = this.queue.findIndex(item => item.taskId === taskId);
46
81
  if (index !== -1) {
47
82
  this.queue.splice(index, 1);
83
+ this.persistQueue();
48
84
  }
49
85
  }
50
86
  /**
@@ -67,6 +103,19 @@ class RetryManager {
67
103
  pending: this.queue.filter(item => item.nextRetryAt > now).length
68
104
  };
69
105
  }
106
+ /**
107
+ * 获取队列内容(用于测试和调试)
108
+ */
109
+ getQueue() {
110
+ return [...this.queue];
111
+ }
112
+ /**
113
+ * 清空队列
114
+ */
115
+ clearQueue() {
116
+ this.queue = [];
117
+ this.persistQueue();
118
+ }
70
119
  calculateDelay(attempt) {
71
120
  switch (this.config.backoff) {
72
121
  case 'exponential':
@@ -14,7 +14,17 @@ export declare class Scheduler {
14
14
  private stateManager;
15
15
  private stateMachine;
16
16
  private config;
17
+ private cycleCache;
18
+ private lastCycleCheck;
19
+ private cycleCheckTTL;
20
+ private cycleCacheKey;
21
+ /** 默认任务超时(毫秒) */
22
+ private static readonly DEFAULT_TASK_TIMEOUT;
17
23
  constructor(stateManager: StateManager, config?: Partial<SchedulerConfig>);
24
+ /**
25
+ * 计算任务列表的简单哈希(用于缓存键)
26
+ */
27
+ private computeTasksHash;
18
28
  /**
19
29
  * 获取当前正在执行的任务数(从持久化状态读取,非内存缓存)
20
30
  */
@@ -66,8 +76,17 @@ export declare class Scheduler {
66
76
  detectCircularDependencies(tasks: Task[]): string[][];
67
77
  /**
68
78
  * 检测循环依赖并将相关任务标记为 blocked
79
+ * 使用缓存避免重复计算
69
80
  */
70
81
  private handleCircularDependencies;
82
+ /**
83
+ * 获取任务的循环依赖缓存(用于调试)
84
+ */
85
+ getCycleCache(): Map<string, string[]>;
86
+ /**
87
+ * 清除循环依赖缓存
88
+ */
89
+ clearCycleCache(): void;
71
90
  /**
72
91
  * 获取调度状态(从持久化状态读取)
73
92
  */
@@ -6,15 +6,32 @@ class Scheduler {
6
6
  stateManager;
7
7
  stateMachine;
8
8
  config;
9
+ // 循环依赖检测缓存(避免重复计算)
10
+ cycleCache = new Map();
11
+ lastCycleCheck = 0;
12
+ cycleCheckTTL = 30000; // 30 秒缓存
13
+ cycleCacheKey = ''; // 任务列表哈希
14
+ /** 默认任务超时(毫秒) */
15
+ static DEFAULT_TASK_TIMEOUT = 600000; // 10 分钟
9
16
  constructor(stateManager, config) {
10
17
  this.stateManager = stateManager;
11
18
  this.stateMachine = new state_machine_js_1.StateMachine();
12
19
  this.config = {
13
20
  maxConcurrentTasks: 3,
14
- taskTimeout: 600000, // 10 分钟
21
+ taskTimeout: Scheduler.DEFAULT_TASK_TIMEOUT,
15
22
  ...config
16
23
  };
17
24
  }
25
+ /**
26
+ * 计算任务列表的简单哈希(用于缓存键)
27
+ */
28
+ computeTasksHash(tasks) {
29
+ // 使用任务 ID + 状态 + 依赖的组合作为哈希
30
+ return tasks
31
+ .map(t => `${t.id}:${t.status}:${(t.dependencies || []).join(',')}`)
32
+ .sort()
33
+ .join('|');
34
+ }
18
35
  /**
19
36
  * 获取当前正在执行的任务数(从持久化状态读取,非内存缓存)
20
37
  */
@@ -223,9 +240,28 @@ class Scheduler {
223
240
  }
224
241
  /**
225
242
  * 检测循环依赖并将相关任务标记为 blocked
243
+ * 使用缓存避免重复计算
226
244
  */
227
245
  async handleCircularDependencies(tasks) {
246
+ const now = Date.now();
247
+ const hash = this.computeTasksHash(tasks);
248
+ // 检查缓存是否有效
249
+ if (hash === this.cycleCacheKey && now - this.lastCycleCheck < this.cycleCheckTTL) {
250
+ // 使用缓存结果,跳过重新检测
251
+ return;
252
+ }
253
+ // 缓存失效或任务列表变化,重新检测
228
254
  const cycles = this.detectCircularDependencies(tasks);
255
+ // 更新缓存
256
+ this.cycleCache.clear();
257
+ for (const cycle of cycles) {
258
+ for (const taskId of cycle) {
259
+ this.cycleCache.set(taskId, cycle);
260
+ }
261
+ }
262
+ this.cycleCacheKey = hash;
263
+ this.lastCycleCheck = now;
264
+ // 标记阻塞任务
229
265
  for (const cycle of cycles) {
230
266
  for (const taskId of cycle) {
231
267
  const task = tasks.find(t => t.id === taskId);
@@ -238,6 +274,20 @@ class Scheduler {
238
274
  }
239
275
  }
240
276
  }
277
+ /**
278
+ * 获取任务的循环依赖缓存(用于调试)
279
+ */
280
+ getCycleCache() {
281
+ return this.cycleCache;
282
+ }
283
+ /**
284
+ * 清除循环依赖缓存
285
+ */
286
+ clearCycleCache() {
287
+ this.cycleCache.clear();
288
+ this.lastCycleCheck = 0;
289
+ this.cycleCacheKey = '';
290
+ }
241
291
  /**
242
292
  * 获取调度状态(从持久化状态读取)
243
293
  */
@@ -27,6 +27,17 @@ export interface ParsedPlan {
27
27
  /** 原始 plan 文本 */
28
28
  raw: string;
29
29
  }
30
+ /** 从 plan 中提取的结构化信息(用于 fallback 时注入任务描述) */
31
+ export interface PlanMetadata {
32
+ /** 技术栈 */
33
+ techStack: string[];
34
+ /** 接口/API 定义 */
35
+ interfaces: string[];
36
+ /** 数据模型/表 */
37
+ dataModels: string[];
38
+ /** 关键决策 */
39
+ keyDecisions: string[];
40
+ }
30
41
  export interface TaskBreakdown {
31
42
  taskId: string;
32
43
  title: string;
@@ -77,6 +88,15 @@ export declare class TaskPlanner {
77
88
  * 解析 plan 文本,提取模块、技术栈、数据模型等结构化信息
78
89
  */
79
90
  parsePlan(planText: string): ParsedPlan;
91
+ /**
92
+ * 从 plan 中提取结构化信息(用于 fallback 时注入任务描述)
93
+ * 即使无法解析模块,也能保留关键技术信息
94
+ */
95
+ extractPlanMetadata(plan: string): PlanMetadata;
96
+ /**
97
+ * 从 goals 推断模块结构(当 plan 无法解析模块时使用)
98
+ */
99
+ inferModulesFromGoals(parsedTask: ParsedTask): PlanModule[];
80
100
  /**
81
101
  * 分析模块间依赖关系
82
102
  *