openmatrix 0.2.25 → 0.2.27

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) {
@@ -203,8 +206,23 @@ async function handleTasksJson(options, stateManager, state, omPath, basePath) {
203
206
  if (jsonStr.startsWith('@')) {
204
207
  const filePath = jsonStr.slice(1);
205
208
  // 如果是相对路径,转换为绝对路径
206
- const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(basePath, filePath);
207
- jsonStr = await fs.readFile(resolvedPath, 'utf-8');
209
+ let resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(basePath, filePath);
210
+ // 特殊处理:如果文件名是 tasks-input.json 且不带路径,尝试从 runId 目录读取
211
+ if (filePath === 'tasks-input.json' || filePath.endsWith('/tasks-input.json')) {
212
+ // 先尝试从 runId 目录读取
213
+ const state = await stateManager.getState();
214
+ const runIdPath = path.join(omPath, state.runId, 'tasks-input.json');
215
+ try {
216
+ jsonStr = await fs.readFile(runIdPath, 'utf-8');
217
+ }
218
+ catch {
219
+ // 如果 runId 目录不存在,尝试原有路径
220
+ jsonStr = await fs.readFile(resolvedPath, 'utf-8');
221
+ }
222
+ }
223
+ else {
224
+ jsonStr = await fs.readFile(resolvedPath, 'utf-8');
225
+ }
208
226
  }
209
227
  tasksInput = JSON.parse(jsonStr);
210
228
  }
@@ -238,18 +256,14 @@ async function handleTasksJson(options, stateManager, state, omPath, basePath) {
238
256
  console.log(`🔬 已加载研究领域: ${researchContext.domain}`);
239
257
  }
240
258
  }
241
- // 读取独立的技术方案文档(plan.md)
259
+ // 读取独立的技术方案文档(从当前 runId 目录)
242
260
  let planContent;
243
- const planPath = path.join(omPath, 'plan.md');
244
- try {
245
- planContent = await fs.readFile(planPath, 'utf-8');
246
- if (!options.json) {
247
- console.log(`📄 已加载技术方案: plan.md`);
248
- }
249
- }
250
- catch {
251
- // plan.md 不存在时继续(可能由 AI 后续生成或无 plan)
261
+ planContent = await stateManager.getPlan();
262
+ if (planContent && !options.json) {
263
+ console.log(`📄 已加载技术方案: plan.md`);
252
264
  }
265
+ // 转换为 string | undefined(供 TaskPlanner 使用)
266
+ const planContentForPlanner = planContent || undefined;
253
267
  // 构建 ParsedTask
254
268
  const parsedTask = {
255
269
  title: resolvedInput.title || tasksInput.title,
@@ -282,7 +296,7 @@ async function handleTasksJson(options, stateManager, state, omPath, basePath) {
282
296
  }
283
297
  // 使用 TaskPlanner 拆分(保持原有拆分逻辑)
284
298
  const planner = new task_planner_js_1.TaskPlanner();
285
- const subTasks = planner.breakdown(parsedTask, extraAnswers, qualityConfig, planContent);
299
+ const subTasks = planner.breakdown(parsedTask, extraAnswers, qualityConfig, planContentForPlanner);
286
300
  // 创建任务到状态管理器,并建立 ID 映射
287
301
  // TaskPlanner 生成的 taskId 和 StateManager 创建的 id 不同,
288
302
  // 需要映射后才能正确设置 dependencies
@@ -70,7 +70,19 @@ async function showStatus(manager, reporter, options) {
70
70
  const state = await manager.getState();
71
71
  const tasks = await manager.listTasks();
72
72
  if (options.json) {
73
- console.log(JSON.stringify({ state, tasks }, null, 2));
73
+ // 添加文件状态字段
74
+ const hasPlan = await manager.hasPlan();
75
+ const hasTasksInput = await manager.hasTasksInput();
76
+ const hasResearchContext = await manager.hasResearchContext();
77
+ console.log(JSON.stringify({
78
+ state,
79
+ tasks,
80
+ files: {
81
+ hasPlan,
82
+ hasTasksInput,
83
+ hasResearchContext
84
+ }
85
+ }, null, 2));
74
86
  return;
75
87
  }
76
88
  // Header
@@ -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: {
@@ -55,6 +62,114 @@ export declare class StateManager {
55
62
  updateApproval(approval: Approval): Promise<void>;
56
63
  getApprovalsByStatus(status: ApprovalStatus): Promise<Approval[]>;
57
64
  getAllApprovals(): Promise<Approval[]>;
65
+ /**
66
+ * 保存技术方案文档 (plan.md)
67
+ * 路径: .openmatrix/{runId}/plan.md
68
+ */
69
+ savePlan(content: string): Promise<void>;
70
+ /**
71
+ * 读取技术方案文档
72
+ * @returns plan.md 内容,不存在返回 null
73
+ */
74
+ getPlan(): Promise<string | null>;
75
+ /**
76
+ * 检查 plan.md 是否存在
77
+ */
78
+ hasPlan(): Promise<boolean>;
79
+ /**
80
+ * 保存任务输入元数据
81
+ * 路径: .openmatrix/{runId}/tasks-input.json
82
+ */
83
+ saveTasksInput(data: {
84
+ title: string;
85
+ description?: string;
86
+ goals: string[];
87
+ goalTypes?: string[];
88
+ goalComplexity?: ('low' | 'medium' | 'high')[];
89
+ constraints?: string[];
90
+ deliverables?: string[];
91
+ answers?: Record<string, string>;
92
+ }): Promise<void>;
93
+ /**
94
+ * 读取任务输入元数据
95
+ * @returns tasks-input.json 数据,不存在返回 null
96
+ */
97
+ getTasksInput(): Promise<{
98
+ title: string;
99
+ description?: string;
100
+ goals: string[];
101
+ goalTypes?: string[];
102
+ goalComplexity?: ('low' | 'medium' | 'high')[];
103
+ constraints?: string[];
104
+ deliverables?: string[];
105
+ answers?: Record<string, string>;
106
+ } | null>;
107
+ /**
108
+ * 检查 tasks-input.json 是否存在
109
+ */
110
+ hasTasksInput(): Promise<boolean>;
111
+ /**
112
+ * 保存研究会话
113
+ * 路径: .openmatrix/{runId}/research/session.json
114
+ */
115
+ saveResearchSession(session: Record<string, unknown>): Promise<void>;
116
+ /**
117
+ * 读取研究会话
118
+ */
119
+ getResearchSession(): Promise<Record<string, unknown> | null>;
120
+ /**
121
+ * 保存研究报告
122
+ */
123
+ saveResearchReport(content: string): Promise<void>;
124
+ /**
125
+ * 读取研究报告
126
+ */
127
+ getResearchReport(): Promise<string | null>;
128
+ /**
129
+ * 保存研究上下文 (供 start 使用)
130
+ */
131
+ saveResearchContext(context: {
132
+ topic: string;
133
+ domain: string;
134
+ goals?: string[];
135
+ constraints?: string[];
136
+ deliverables?: string[];
137
+ reportPath?: string;
138
+ knowledgePath?: string;
139
+ }): Promise<void>;
140
+ /**
141
+ * 读取研究上下文
142
+ */
143
+ getResearchContext(): Promise<{
144
+ topic: string;
145
+ domain: string;
146
+ goals?: string[];
147
+ constraints?: string[];
148
+ deliverables?: string[];
149
+ reportPath?: string;
150
+ knowledgePath?: string;
151
+ } | null>;
152
+ /**
153
+ * 检查研究上下文是否存在
154
+ */
155
+ hasResearchContext(): Promise<boolean>;
156
+ /**
157
+ * 保存知识条目
158
+ */
159
+ saveKnowledgeFinding(index: number, content: string): Promise<void>;
160
+ /**
161
+ * 保存头脑风暴会话
162
+ */
163
+ saveBrainstormSession(session: Record<string, unknown>): Promise<void>;
164
+ /**
165
+ * 读取头脑风暴会话
166
+ */
167
+ getBrainstormSession(): Promise<Record<string, unknown> | null>;
168
+ /**
169
+ * 迁移旧版本文件到 runId 目录
170
+ * 只在首次检测到旧文件时执行
171
+ */
172
+ migrateFromLegacy(): Promise<void>;
58
173
  saveMeeting(meeting: Meeting): Promise<void>;
59
174
  getMeeting(meetingId: string): Promise<Meeting | null>;
60
175
  getMeetingsByStatus(status: MeetingStatus): Promise<Meeting[]>;