openmatrix 0.2.25 → 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.
@@ -72,9 +72,13 @@ export declare class AgentRunner {
72
72
  */
73
73
  private buildAmbiguityDetectionInstruction;
74
74
  /**
75
- * 构建累积上下文 - 从全局 context.md 读取前序 Agent 的决策和知识
75
+ * 构建累积上下文 - 从当前运行的 context.md 读取前序 Agent 的决策和知识
76
76
  */
77
77
  private buildAccumulatedContext;
78
+ /**
79
+ * 构建测试项目上下文 — 注入原始扫描数据供 AI 自行分析
80
+ */
81
+ private buildTestContext;
78
82
  /**
79
83
  * 构建阶段上下文
80
84
  */
@@ -137,6 +137,9 @@ class AgentRunner {
137
137
  const phaseContext = this.buildPhaseContext(task);
138
138
  const accumulatedContext = await this.buildAccumulatedContext(task);
139
139
  const ambiguityInstruction = this.buildAmbiguityDetectionInstruction(task);
140
+ const testContext = task.assignedAgent === 'tester' ? await this.buildTestContext() : '';
141
+ const state = await this.stateManager.getState();
142
+ const runId = state.runId;
140
143
  return `# 任务执行
141
144
 
142
145
  ${ambiguityInstruction}
@@ -148,6 +151,15 @@ ${ambiguityInstruction}
148
151
  - 优先级: ${task.priority}
149
152
  - 超时: ${task.timeout / 1000} 秒
150
153
 
154
+ ## 工作目录隔离
155
+
156
+ 本次运行 ID: ${runId}
157
+ 所有中间产物必须写入运行专属目录: \`.openmatrix/${runId}/tasks/${task.id}/artifacts/\`
158
+ - 中间产物: \`.openmatrix/${runId}/tasks/${task.id}/artifacts/\`
159
+ - 结果报告: \`.openmatrix/${runId}/tasks/${task.id}/artifacts/result.md\`
160
+
161
+ **注意**: 业务代码(src/、tests/ 等)写入项目正常目录,仅中间产物和报告写入运行目录。
162
+
151
163
  ## 当前阶段
152
164
  ${phaseContext}
153
165
 
@@ -157,6 +169,7 @@ ${task.dependencies.length > 0
157
169
  : '无依赖'}
158
170
 
159
171
  ${accumulatedContext}
172
+ ${testContext}
160
173
 
161
174
  ---
162
175
 
@@ -168,7 +181,7 @@ ${agentPrompt.instructions}
168
181
 
169
182
  ## 完成要求
170
183
 
171
- 1. 将执行结果写入: \`.openmatrix/tasks/${task.id}/artifacts/result.md\`
184
+ 1. 将执行结果写入: \`.openmatrix/${runId}/tasks/${task.id}/artifacts/result.md\`
172
185
  (任务状态由 openmatrix complete 命令管理,请勿直接修改 task.json)
173
186
  2. 如需审批,创建审批请求: \`.openmatrix/approvals/\` 目录
174
187
 
@@ -260,11 +273,21 @@ ${agentPrompt.instructions}
260
273
  **重要**: 如果无法解析歧义检测结果,视为无歧义继续执行。`;
261
274
  }
262
275
  /**
263
- * 构建累积上下文 - 从全局 context.md 读取前序 Agent 的决策和知识
276
+ * 构建累积上下文 - 从当前运行的 context.md 读取前序 Agent 的决策和知识
264
277
  */
265
278
  async buildAccumulatedContext(currentTask) {
266
279
  const omPath = path.join(process.cwd(), '.openmatrix');
267
- const contextFile = path.join(omPath, 'context.md');
280
+ // 读取 current.json 获取当前 runId
281
+ let contextFile = path.join(omPath, 'context.md');
282
+ try {
283
+ const currentJson = await fs.readFile(path.join(omPath, 'current.json'), 'utf-8');
284
+ const { runId } = JSON.parse(currentJson);
285
+ if (runId)
286
+ contextFile = path.join(omPath, runId, 'context.md');
287
+ }
288
+ catch {
289
+ // fallback to root context.md
290
+ }
268
291
  try {
269
292
  const content = await fs.readFile(contextFile, 'utf-8');
270
293
  const trimmed = content.trim();
@@ -277,6 +300,33 @@ ${agentPrompt.instructions}
277
300
  你应该基于这些信息来工作,避免重复犯错或与已有决策冲突。
278
301
 
279
302
  ${trimmed}
303
+ `;
304
+ }
305
+ catch {
306
+ return '';
307
+ }
308
+ }
309
+ /**
310
+ * 构建测试项目上下文 — 注入原始扫描数据供 AI 自行分析
311
+ */
312
+ async buildTestContext() {
313
+ try {
314
+ const { performFullScan } = await import('../test/context-analyzer.js');
315
+ const scanResult = performFullScan(process.cwd());
316
+ return `
317
+ ## 项目测试扫描数据(原始数据,由 AI 自行分析决策)
318
+
319
+ \`\`\`json
320
+ ${JSON.stringify({
321
+ frameworks: scanResult.frameworks,
322
+ testStyle: scanResult.testStyle,
323
+ existingTests: scanResult.existingTests.slice(0, 10),
324
+ projectType: scanResult.projectType,
325
+ isFrontend: scanResult.isFrontend,
326
+ hasUIComponents: scanResult.hasUIComponents,
327
+ summary: scanResult.summary
328
+ }, null, 2)}
329
+ \`\`\`
280
330
  `;
281
331
  }
282
332
  catch {
@@ -106,7 +106,18 @@ exports.completeCommand = new commander_1.Command('complete')
106
106
  });
107
107
  // 5. 追加写入全局 context.md (Agent Memory)
108
108
  if (isSuccess) {
109
- const contextFile = path.join(omPath, 'context.md');
109
+ // 读取 current.json 获取当前 runId,context.md 写入运行目录
110
+ let contextDir = omPath;
111
+ try {
112
+ const currentJson = await fs.readFile(path.join(omPath, 'current.json'), 'utf-8');
113
+ const { runId } = JSON.parse(currentJson);
114
+ if (runId)
115
+ contextDir = path.join(omPath, runId);
116
+ }
117
+ catch {
118
+ // fallback to omPath
119
+ }
120
+ const contextFile = path.join(contextDir, 'context.md');
110
121
  const timestamp = new Date().toISOString();
111
122
  // 构建上下文内容
112
123
  const summary = options.summary || '任务已完成';
@@ -116,6 +127,7 @@ exports.completeCommand = new commander_1.Command('complete')
116
127
 
117
128
  `;
118
129
  try {
130
+ await fs.mkdir(contextDir, { recursive: true });
119
131
  // 原子追加写入全局 context.md(O_APPEND flag 保证并发安全)
120
132
  await fs.appendFile(contextFile, contextEntry, 'utf-8');
121
133
  }
@@ -66,7 +66,6 @@ exports.startCommand = new commander_1.Command('start')
66
66
  const omPath = path.join(basePath, '.openmatrix');
67
67
  // 确保目录存在
68
68
  await fs.mkdir(omPath, { recursive: true });
69
- await fs.mkdir(path.join(omPath, 'tasks'), { recursive: true });
70
69
  await fs.mkdir(path.join(omPath, 'approvals'), { recursive: true });
71
70
  // 确保 .openmatrix 被 git 忽略
72
71
  await (0, gitignore_js_1.ensureOpenmatrixGitignore)(basePath);
@@ -88,6 +87,10 @@ exports.startCommand = new commander_1.Command('start')
88
87
  const stateManager = new state_manager_js_1.StateManager(omPath);
89
88
  await stateManager.initialize();
90
89
  const state = await stateManager.getState();
90
+ // 如果上次运行已结束(completed/failed),自动清理旧数据开始新运行
91
+ if (state.status === 'completed' || state.status === 'failed') {
92
+ await stateManager.reset();
93
+ }
91
94
  // 检查是否已有运行中的任务
92
95
  if (state.status === 'running') {
93
96
  if (options.json) {
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmatrix",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
4
4
  "description": "AI Agent task orchestration system with Claude Code Skills integration",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/skills/om.md CHANGED
@@ -154,9 +154,9 @@ Examples:
154
154
  /om 从零搭建后台系统 → AI 推荐设计流程
155
155
 
156
156
  Quality:
157
- strict - TDD + 80% coverage
158
- balanced - 60% coverage
159
- fast - No gates
157
+ 严格模式 - TDD + 80% coverage
158
+ 平衡模式 - 60% coverage
159
+ 快速模式 - No gates
160
160
 
161
161
  Commands:
162
162
  /om:status - 查看执行状态