openmatrix 0.2.24 → 0.2.26

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.
@@ -22,6 +22,14 @@ export declare class FileStore {
22
22
  exists(path: string): Promise<boolean>;
23
23
  listFiles(dir: string): Promise<string[]>;
24
24
  listDirs(dir: string): Promise<string[]>;
25
+ /**
26
+ * 递归删除目录
27
+ */
28
+ removeDir(dir: string): Promise<void>;
29
+ /**
30
+ * 删除文件
31
+ */
32
+ removeFile(filePath: string): Promise<void>;
25
33
  /**
26
34
  * 同步写入 JSON 文件
27
35
  */
@@ -108,6 +108,36 @@ class FileStore {
108
108
  return [];
109
109
  }
110
110
  }
111
+ /**
112
+ * 递归删除目录
113
+ */
114
+ async removeDir(dir) {
115
+ const fullPath = (0, path_1.join)(this.basePath, dir);
116
+ try {
117
+ await (0, promises_1.rm)(fullPath, { recursive: true, force: true });
118
+ }
119
+ catch (error) {
120
+ const nodeError = error;
121
+ if (nodeError.code !== 'ENOENT') {
122
+ throw error;
123
+ }
124
+ }
125
+ }
126
+ /**
127
+ * 删除文件
128
+ */
129
+ async removeFile(filePath) {
130
+ const fullPath = (0, path_1.join)(this.basePath, filePath);
131
+ try {
132
+ await (0, promises_1.unlink)(fullPath);
133
+ }
134
+ catch (error) {
135
+ const nodeError = error;
136
+ if (nodeError.code !== 'ENOENT') {
137
+ throw error;
138
+ }
139
+ }
140
+ }
111
141
  // ============ 同步方法(用于构造器等场景)============
112
142
  /**
113
143
  * 同步写入 JSON 文件
@@ -1,6 +1,8 @@
1
1
  import type { GlobalState, Task, Approval, ApprovalStatus, Meeting, MeetingStatus } from '../types/index.js';
2
2
  export declare class StateManager {
3
+ private rootStore;
3
4
  private store;
5
+ private basePath;
4
6
  private stateCache;
5
7
  private lockDepth;
6
8
  constructor(basePath: string);
@@ -18,6 +20,11 @@ export declare class StateManager {
18
20
  */
19
21
  private tryCleanStaleLock;
20
22
  initialize(): Promise<void>;
23
+ /**
24
+ * 重置状态 — 清理旧运行数据,为新运行做准备
25
+ * 删除旧 runId 目录,生成新 runId,更新 current.json
26
+ */
27
+ reset(): Promise<void>;
21
28
  getState(): Promise<GlobalState>;
22
29
  updateState(updates: Partial<GlobalState>): Promise<void>;
23
30
  createTask(input: {
@@ -13,11 +13,16 @@ const DEFAULT_CONFIG = {
13
13
  model: 'claude-sonnet-4-6'
14
14
  };
15
15
  class StateManager {
16
+ rootStore;
16
17
  store;
18
+ basePath;
17
19
  stateCache = null;
18
- lockDepth = 0; // 可重入:同一进程内嵌套调用不阻塞
20
+ lockDepth = 0;
19
21
  constructor(basePath) {
20
- this.store = new file_store_js_1.FileStore(basePath);
22
+ this.basePath = basePath;
23
+ this.rootStore = new file_store_js_1.FileStore(basePath);
24
+ // store will be set in initialize() or reset() to point to {basePath}/{runId}/
25
+ this.store = this.rootStore;
21
26
  }
22
27
  /**
23
28
  * 跨进程文件锁 — 防止多个 openmatrix CLI 进程同时读写 state.json
@@ -84,49 +89,101 @@ class StateManager {
84
89
  }
85
90
  }
86
91
  async initialize() {
87
- const existing = await this.store.readJson('state.json');
88
- if (!existing) {
89
- const initialState = {
90
- version: '1.0',
91
- runId: this.generateRunId(),
92
- status: 'initialized',
93
- currentPhase: 'planning',
94
- startedAt: new Date().toISOString(),
95
- config: DEFAULT_CONFIG,
96
- statistics: {
97
- totalTasks: 0,
98
- completed: 0,
99
- inProgress: 0,
100
- failed: 0,
101
- pending: 0,
102
- scheduled: 0,
103
- blocked: 0,
104
- waiting: 0,
105
- verify: 0,
106
- accept: 0,
107
- retry_queue: 0
108
- }
109
- };
110
- await this.store.writeJson('state.json', initialState);
111
- this.stateCache = initialState;
92
+ // 读取根索引,确定当前 runId
93
+ const current = await this.rootStore.readJson('current.json');
94
+ if (current?.runId) {
95
+ // 切换 store 到当前运行目录
96
+ this.store = new file_store_js_1.FileStore((0, path_1.join)(this.basePath, current.runId));
97
+ const existing = await this.store.readJson('state.json');
98
+ if (existing) {
99
+ existing.statistics = {
100
+ totalTasks: existing.statistics?.totalTasks ?? 0,
101
+ completed: existing.statistics?.completed ?? 0,
102
+ inProgress: existing.statistics?.inProgress ?? 0,
103
+ failed: existing.statistics?.failed ?? 0,
104
+ pending: existing.statistics?.pending ?? 0,
105
+ scheduled: existing.statistics?.scheduled ?? 0,
106
+ blocked: existing.statistics?.blocked ?? 0,
107
+ waiting: existing.statistics?.waiting ?? 0,
108
+ verify: existing.statistics?.verify ?? 0,
109
+ accept: existing.statistics?.accept ?? 0,
110
+ retry_queue: existing.statistics?.retry_queue ?? 0
111
+ };
112
+ this.stateCache = existing;
113
+ return;
114
+ }
112
115
  }
113
- else {
114
- // 合并旧状态的统计字段(兼容旧版本,statistics 可能不存在)
115
- existing.statistics = {
116
- totalTasks: existing.statistics?.totalTasks ?? 0,
117
- completed: existing.statistics?.completed ?? 0,
118
- inProgress: existing.statistics?.inProgress ?? 0,
119
- failed: existing.statistics?.failed ?? 0,
120
- pending: existing.statistics?.pending ?? 0,
121
- scheduled: existing.statistics?.scheduled ?? 0,
122
- blocked: existing.statistics?.blocked ?? 0,
123
- waiting: existing.statistics?.waiting ?? 0,
124
- verify: existing.statistics?.verify ?? 0,
125
- accept: existing.statistics?.accept ?? 0,
126
- retry_queue: existing.statistics?.retry_queue ?? 0
127
- };
128
- this.stateCache = existing;
116
+ // 没有 current.json 或 state.json 不存在 — 创建新运行
117
+ const runId = this.generateRunId();
118
+ this.store = new file_store_js_1.FileStore((0, path_1.join)(this.basePath, runId));
119
+ await this.rootStore.writeJson('current.json', { runId });
120
+ const initialState = {
121
+ version: '1.0',
122
+ runId,
123
+ status: 'initialized',
124
+ currentPhase: 'planning',
125
+ startedAt: new Date().toISOString(),
126
+ config: DEFAULT_CONFIG,
127
+ statistics: {
128
+ totalTasks: 0,
129
+ completed: 0,
130
+ inProgress: 0,
131
+ failed: 0,
132
+ pending: 0,
133
+ scheduled: 0,
134
+ blocked: 0,
135
+ waiting: 0,
136
+ verify: 0,
137
+ accept: 0,
138
+ retry_queue: 0
139
+ }
140
+ };
141
+ await this.store.writeJson('state.json', initialState);
142
+ this.stateCache = initialState;
143
+ }
144
+ /**
145
+ * 重置状态 — 清理旧运行数据,为新运行做准备
146
+ * 删除旧 runId 目录,生成新 runId,更新 current.json
147
+ */
148
+ async reset() {
149
+ // 清理旧 runId 目录
150
+ const oldRunId = this.stateCache?.runId;
151
+ if (oldRunId) {
152
+ await this.rootStore.removeDir(oldRunId);
129
153
  }
154
+ // 兼容旧版本的目录
155
+ await this.rootStore.removeDir('tasks');
156
+ await this.rootStore.removeDir('approvals');
157
+ await this.rootStore.removeDir('meetings');
158
+ await this.rootStore.removeDir('runs');
159
+ await this.rootStore.removeFile('context.md');
160
+ // 创建新运行
161
+ const runId = this.generateRunId();
162
+ this.store = new file_store_js_1.FileStore((0, path_1.join)(this.basePath, runId));
163
+ await this.rootStore.writeJson('current.json', { runId });
164
+ const freshState = {
165
+ version: '1.0',
166
+ runId,
167
+ status: 'initialized',
168
+ currentPhase: 'planning',
169
+ startedAt: new Date().toISOString(),
170
+ config: DEFAULT_CONFIG,
171
+ statistics: {
172
+ totalTasks: 0,
173
+ completed: 0,
174
+ inProgress: 0,
175
+ failed: 0,
176
+ pending: 0,
177
+ scheduled: 0,
178
+ blocked: 0,
179
+ waiting: 0,
180
+ verify: 0,
181
+ accept: 0,
182
+ retry_queue: 0
183
+ }
184
+ };
185
+ await this.store.writeJson('state.json', freshState);
186
+ this.stateCache = freshState;
130
187
  }
131
188
  async getState() {
132
189
  if (!this.stateCache) {
@@ -207,7 +264,6 @@ class StateManager {
207
264
  });
208
265
  }
209
266
  async getTask(taskId) {
210
- // Try subdirectory structure first, fall back to flat file
211
267
  let task = await this.store.readJson(`tasks/${taskId}/task.json`);
212
268
  if (!task) {
213
269
  task = await this.store.readJson(`tasks/${taskId}.json`);
@@ -225,7 +281,7 @@ class StateManager {
225
281
  ...updates,
226
282
  updatedAt: new Date().toISOString()
227
283
  };
228
- // Always write to subdirectory structure
284
+ // Always write to current run directory
229
285
  await this.store.writeJson(`tasks/${taskId}/task.json`, updatedTask);
230
286
  // Update statistics if status changed
231
287
  if (updates.status && updates.status !== oldStatus) {
@@ -249,7 +305,6 @@ class StateManager {
249
305
  continue;
250
306
  const task = await this.store.readJson(`tasks/${file}`);
251
307
  if (task) {
252
- // Avoid duplicate if already found in subdirectory
253
308
  if (!tasks.some(t => t.id === task.id)) {
254
309
  tasks.push(task);
255
310
  }
@@ -143,10 +143,25 @@ function generateTestContent(source, config) {
143
143
  // 导入语句
144
144
  const importPath = source.path.replace(/\.(ts|tsx)$/, '').replace(/\.(js|jsx)$/, '');
145
145
  const imports = generateImports(source.exports, importPath, usesTypeScript);
146
+ // 需要生命周期钩子的文件类型
147
+ const needsLifecycle = ['service', 'util', 'api', 'module'].includes(source.fileType);
148
+ // 需要 vi 导入的测试类型(使用 Mock 验证副作用)
149
+ const needsViImport = ['service', 'util', 'api', 'component', 'module'].includes(source.fileType);
146
150
  // 测试块
147
151
  const testBlocks = source.exports.map(exp => generateTestBlock(exp, framework, usesDescribeIt, source.fileType));
148
152
  // 组装内容
149
- const content = `${imports}\n\n${testBlocks.join('\n\n')}\n`;
153
+ const content = `${needsViImport ? `import { vi } from 'vitest';\n` : ''}${imports}
154
+ ${needsLifecycle ? `
155
+ beforeEach(() => {
156
+ vi.clearAllMocks();
157
+ });
158
+
159
+ afterEach(() => {
160
+ vi.restoreAllMocks();
161
+ });
162
+ ` : ''}
163
+ ${testBlocks.join('\n\n')}
164
+ `;
150
165
  return content;
151
166
  }
152
167
  /**
@@ -193,13 +208,15 @@ function generateBusinessTestCases(exportName, fileType) {
193
208
  name: 'should return valid result on successful execution',
194
209
  arrange: `// Arrange - 准备有效的输入参数
195
210
  const params = { id: 'test-id', data: {} };
196
- const mockDependencies = {};`,
211
+ const spy = vi.spyOn({ ${exportName} }, '${exportName}');`,
197
212
  act: `// Act - 执行服务方法
198
213
  const result = await ${exportName}(params);`,
199
214
  assert: [
200
215
  'expect(result).toBeDefined();',
201
216
  'expect(result).toHaveProperty(\'data\');',
202
- 'expect(result.error).toBeUndefined();'
217
+ 'expect(result.error).toBeUndefined();',
218
+ `expect(spy).toHaveBeenCalledWith(params);`,
219
+ 'expect(spy).toHaveBeenCalledTimes(1);'
203
220
  ]
204
221
  },
205
222
  {
@@ -239,12 +256,15 @@ function generateBusinessTestCases(exportName, fileType) {
239
256
  name: 'should process valid input correctly',
240
257
  arrange: `// Arrange - 准备有效输入
241
258
  const input = 'test-value';
242
- const expected = 'expected-result';`,
259
+ const expected = 'expected-result';
260
+ const spy = vi.spyOn({ ${exportName} }, '${exportName}');`,
243
261
  act: `// Act
244
262
  const result = ${exportName}(input);`,
245
263
  assert: [
246
264
  'expect(result).toBeDefined();',
247
- 'expect(typeof result).toBe(\'string\');'
265
+ 'expect(typeof result).toBe(\'string\');',
266
+ `expect(spy).toHaveBeenCalledWith(input);`,
267
+ 'expect(spy).toHaveBeenCalledTimes(1);'
248
268
  ]
249
269
  },
250
270
  {
@@ -333,19 +353,25 @@ function generateBusinessTestCases(exportName, fileType) {
333
353
  {
334
354
  name: 'should return success response for valid request',
335
355
  arrange: `// Arrange - 准备有效的 API 请求参数
336
- const request = { method: \'GET\', path: \'/api/test\' };`,
356
+ const request = { method: \'GET\', path: \'/api/test\' };
357
+ const mockResponse = { status: 200, data: { id: 1 } };
358
+ const spy = vi.spyOn({ ${exportName} }, '${exportName}').mockResolvedValue(mockResponse);`,
337
359
  act: `// Act - 执行 API 调用
338
360
  const response = await ${exportName}(request);`,
339
361
  assert: [
340
362
  'expect(response).toBeDefined();',
341
363
  'expect(response.status).toBe(200);',
342
- 'expect(response.data).toBeDefined();'
364
+ 'expect(response.data).toBeDefined();',
365
+ `expect(spy).toHaveBeenCalledWith(request);`,
366
+ 'expect(spy).toHaveBeenCalledTimes(1);'
343
367
  ]
344
368
  },
345
369
  {
346
370
  name: 'should handle 404 not found',
347
371
  arrange: `// Arrange - 准备不存在的资源请求
348
- const request = { method: \'GET\', path: \'/api/nonexistent\' };`,
372
+ const request = { method: \'GET\', path: \'/api/nonexistent\' };
373
+ const mockResponse = { status: 404, error: \'Not Found\' };
374
+ vi.spyOn({ ${exportName} }, '${exportName}').mockResolvedValue(mockResponse);`,
349
375
  act: `// Act - 请求不存在的资源
350
376
  const response = await ${exportName}(request);`,
351
377
  assert: [
@@ -356,7 +382,9 @@ function generateBusinessTestCases(exportName, fileType) {
356
382
  {
357
383
  name: 'should handle invalid request body',
358
384
  arrange: `// Arrange - 准备无效的请求体
359
- const request = { method: \'POST\', path: \'/api/test\', body: null };`,
385
+ const request = { method: \'POST\', path: \'/api/test\', body: null };
386
+ const mockResponse = { status: 400, error: \'invalid request\' };
387
+ vi.spyOn({ ${exportName} }, '${exportName}').mockResolvedValue(mockResponse);`,
360
388
  act: `// Act - 发送无效请求
361
389
  const response = await ${exportName}(request);`,
362
390
  assert: [
@@ -368,10 +396,10 @@ function generateBusinessTestCases(exportName, fileType) {
368
396
  name: 'should handle network errors gracefully',
369
397
  arrange: `// Arrange - 模拟网络错误
370
398
  const request = { method: \'GET\', path: \'/api/error\' };
371
- // Mock network failure`,
399
+ vi.spyOn({ ${exportName} }, '${exportName}').mockRejectedValue(new Error('Network Error'));`,
372
400
  act: `// Act - 处理错误响应`,
373
401
  assert: [
374
- `await expect(${exportName}(request)).resolves.toBeDefined();`,
402
+ `await expect(${exportName}(request)).rejects.toThrow('Network Error');`,
375
403
  '// 应该返回错误对象而非抛出异常'
376
404
  ]
377
405
  }
@@ -228,8 +228,10 @@ export interface ParsedTask {
228
228
  title: string;
229
229
  description: string;
230
230
  goals: string[];
231
- /** 每个 goal 的类型标注 (AI 在提取时标注),与 goals 数组一一对应 */
231
+ /** 每个 goal 的类型标注 (AI 必填),与 goals 数组一一对应 */
232
232
  goalTypes?: GoalType[];
233
+ /** 每个 goal 的复杂度标注 (AI 必填),与 goals 数组一一对应 */
234
+ goalComplexity?: ('low' | 'medium' | 'high')[];
233
235
  constraints: string[];
234
236
  deliverables: string[];
235
237
  rawContent: string;
package/package.json CHANGED
@@ -1,61 +1,61 @@
1
- {
2
- "name": "openmatrix",
3
- "version": "0.2.24",
4
- "description": "AI Agent task orchestration system with Claude Code Skills integration",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "bin": {
8
- "openmatrix": "dist/cli/index.js"
9
- },
10
- "files": [
11
- "dist",
12
- "skills",
13
- "scripts",
14
- "README.md"
15
- ],
16
- "scripts": {
17
- "build": "tsc",
18
- "dev": "tsx src/cli/index.ts",
19
- "test": "vitest",
20
- "lint": "eslint src --ext .ts",
21
- "typecheck": "tsc --noEmit",
22
- "postinstall": "node scripts/install-skills.js"
23
- },
24
- "keywords": [
25
- "claude",
26
- "claude-code",
27
- "ai-agent",
28
- "task-orchestration",
29
- "automation",
30
- "multi-agent"
31
- ],
32
- "author": "",
33
- "license": "MIT",
34
- "type": "commonjs",
35
- "repository": {
36
- "type": "git",
37
- "url": "git+https://github.com/bigfish1913/openmatrix.git"
38
- },
39
- "homepage": "https://github.com/bigfish1913/openmatrix#readme",
40
- "bugs": {
41
- "url": "https://github.com/bigfish1913/openmatrix/issues"
42
- },
43
- "dependencies": {
44
- "chalk": "^5.6.2",
45
- "chokidar": "^5.0.0",
46
- "commander": "^14.0.3",
47
- "winston": "^3.19.0"
48
- },
49
- "devDependencies": {
50
- "@types/node": "^22.0.0",
51
- "@typescript-eslint/eslint-plugin": "^8.58.1",
52
- "@typescript-eslint/parser": "^8.58.1",
53
- "@vitest/coverage-v8": "^1.6.1",
54
- "eslint": "^10.2.0",
55
- "typescript": "^5.3.3",
56
- "vitest": "^1.6.0"
57
- },
58
- "engines": {
59
- "node": ">=18.0.0"
60
- }
61
- }
1
+ {
2
+ "name": "openmatrix",
3
+ "version": "0.2.26",
4
+ "description": "AI Agent task orchestration system with Claude Code Skills integration",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "openmatrix": "dist/cli/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "skills",
13
+ "scripts",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsx src/cli/index.ts",
19
+ "test": "vitest",
20
+ "lint": "eslint src --ext .ts",
21
+ "typecheck": "tsc --noEmit",
22
+ "postinstall": "node scripts/install-skills.js"
23
+ },
24
+ "keywords": [
25
+ "claude",
26
+ "claude-code",
27
+ "ai-agent",
28
+ "task-orchestration",
29
+ "automation",
30
+ "multi-agent"
31
+ ],
32
+ "author": "",
33
+ "license": "MIT",
34
+ "type": "commonjs",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/bigfish1913/openmatrix.git"
38
+ },
39
+ "homepage": "https://github.com/bigfish1913/openmatrix#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/bigfish1913/openmatrix/issues"
42
+ },
43
+ "dependencies": {
44
+ "chalk": "^5.6.2",
45
+ "chokidar": "^5.0.0",
46
+ "commander": "^14.0.3",
47
+ "winston": "^3.19.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.0.0",
51
+ "@typescript-eslint/eslint-plugin": "^8.58.1",
52
+ "@typescript-eslint/parser": "^8.58.1",
53
+ "@vitest/coverage-v8": "^1.6.1",
54
+ "eslint": "^10.2.0",
55
+ "typescript": "^5.3.3",
56
+ "vitest": "^1.6.0"
57
+ },
58
+ "engines": {
59
+ "node": ">=18.0.0"
60
+ }
61
+ }