openmatrix 0.1.61 → 0.1.63

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.
@@ -12,9 +12,25 @@ const DEFAULT_CONFIG = {
12
12
  class StateManager {
13
13
  store;
14
14
  stateCache = null;
15
+ writeLock = Promise.resolve();
15
16
  constructor(basePath) {
16
17
  this.store = new file_store_js_1.FileStore(basePath);
17
18
  }
19
+ /**
20
+ * 串行化异步写操作,防止并发 read-modify-write 竞态
21
+ */
22
+ async withLock(fn) {
23
+ const prev = this.writeLock;
24
+ let resolve;
25
+ this.writeLock = new Promise(r => { resolve = r; });
26
+ await prev;
27
+ try {
28
+ return await fn();
29
+ }
30
+ finally {
31
+ resolve();
32
+ }
33
+ }
18
34
  async initialize() {
19
35
  const existing = await this.store.readJson('state.json');
20
36
  if (!existing) {
@@ -47,46 +63,53 @@ class StateManager {
47
63
  return this.stateCache;
48
64
  }
49
65
  async updateState(updates) {
50
- const state = await this.getState();
51
- const newState = { ...state, ...updates };
52
- await this.store.writeJson('state.json', newState);
53
- this.stateCache = newState;
66
+ await this.withLock(async () => {
67
+ const state = await this.getState();
68
+ const newState = { ...state, ...updates };
69
+ await this.store.writeJson('state.json', newState);
70
+ this.stateCache = newState;
71
+ });
54
72
  }
55
73
  async createTask(input) {
56
- const taskId = this.generateTaskId();
57
- const now = new Date().toISOString();
58
- const task = {
59
- id: taskId,
60
- title: input.title,
61
- description: input.description,
62
- status: 'pending',
63
- priority: input.priority,
64
- timeout: input.timeout,
65
- dependencies: input.dependencies,
66
- assignedAgent: input.assignedAgent,
67
- phases: {
68
- develop: { status: 'pending', duration: null },
69
- verify: { status: 'pending', duration: null },
70
- accept: { status: 'pending', duration: null }
71
- },
72
- retryCount: 0,
73
- error: null,
74
- createdAt: now,
75
- updatedAt: now
76
- };
77
- await this.store.writeJson(`tasks/${taskId}/task.json`, task);
78
- // Create artifacts subdirectory
79
- await this.store.ensureDir(`tasks/${taskId}/artifacts`);
80
- // Update statistics
81
- const state = await this.getState();
82
- await this.updateState({
83
- statistics: {
84
- ...state.statistics,
85
- totalTasks: state.statistics.totalTasks + 1,
86
- pending: state.statistics.pending + 1
87
- }
74
+ return this.withLock(async () => {
75
+ const taskId = this.generateTaskId();
76
+ const now = new Date().toISOString();
77
+ const task = {
78
+ id: taskId,
79
+ title: input.title,
80
+ description: input.description,
81
+ status: 'pending',
82
+ priority: input.priority,
83
+ timeout: input.timeout,
84
+ dependencies: input.dependencies,
85
+ assignedAgent: input.assignedAgent,
86
+ phases: {
87
+ develop: { status: 'pending', duration: null },
88
+ verify: { status: 'pending', duration: null },
89
+ accept: { status: 'pending', duration: null }
90
+ },
91
+ retryCount: 0,
92
+ error: null,
93
+ createdAt: now,
94
+ updatedAt: now
95
+ };
96
+ await this.store.writeJson(`tasks/${taskId}/task.json`, task);
97
+ // Create artifacts subdirectory
98
+ await this.store.ensureDir(`tasks/${taskId}/artifacts`);
99
+ // Update statistics
100
+ const state = await this.getState();
101
+ const newState = {
102
+ ...state,
103
+ statistics: {
104
+ ...state.statistics,
105
+ totalTasks: state.statistics.totalTasks + 1,
106
+ pending: state.statistics.pending + 1
107
+ }
108
+ };
109
+ await this.store.writeJson('state.json', newState);
110
+ this.stateCache = newState;
111
+ return task;
88
112
  });
89
- return task;
90
113
  }
91
114
  async getTask(taskId) {
92
115
  // Try subdirectory structure first, fall back to flat file
@@ -97,21 +120,23 @@ class StateManager {
97
120
  return task;
98
121
  }
99
122
  async updateTask(taskId, updates) {
100
- const task = await this.getTask(taskId);
101
- if (!task)
102
- throw new Error(`Task ${taskId} not found`);
103
- const oldStatus = task.status;
104
- const updatedTask = {
105
- ...task,
106
- ...updates,
107
- updatedAt: new Date().toISOString()
108
- };
109
- // Always write to subdirectory structure
110
- await this.store.writeJson(`tasks/${taskId}/task.json`, updatedTask);
111
- // Update statistics if status changed
112
- if (updates.status && updates.status !== oldStatus) {
113
- await this.updateTaskStatistics(oldStatus, updates.status);
114
- }
123
+ await this.withLock(async () => {
124
+ const task = await this.getTask(taskId);
125
+ if (!task)
126
+ throw new Error(`Task ${taskId} not found`);
127
+ const oldStatus = task.status;
128
+ const updatedTask = {
129
+ ...task,
130
+ ...updates,
131
+ updatedAt: new Date().toISOString()
132
+ };
133
+ // Always write to subdirectory structure
134
+ await this.store.writeJson(`tasks/${taskId}/task.json`, updatedTask);
135
+ // Update statistics if status changed
136
+ if (updates.status && updates.status !== oldStatus) {
137
+ await this.updateTaskStatistics(oldStatus, updates.status);
138
+ }
139
+ });
115
140
  }
116
141
  async listTasks() {
117
142
  const tasks = [];
@@ -168,6 +193,7 @@ class StateManager {
168
193
  return await this.store.readMarkdown(`tasks/${taskId}/context.md`);
169
194
  }
170
195
  async updateTaskStatistics(oldStatus, newStatus) {
196
+ // 直接写入状态,不经过 updateState(避免重入锁死锁)
171
197
  const state = await this.getState();
172
198
  const stats = { ...state.statistics };
173
199
  // Decrement old status count
@@ -188,7 +214,9 @@ class StateManager {
188
214
  stats.completed++;
189
215
  else if (newStatus === 'failed')
190
216
  stats.failed++;
191
- await this.updateState({ statistics: stats });
217
+ const newState = { ...state, statistics: stats };
218
+ await this.store.writeJson('state.json', newState);
219
+ this.stateCache = newState;
192
220
  }
193
221
  generateRunId() {
194
222
  const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmatrix",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
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",
@@ -7,45 +7,51 @@ const os = require('os');
7
7
  // Change to home directory to avoid cwd issues
8
8
  process.chdir(os.homedir());
9
9
 
10
- try {
11
- // Get the package skills directory - use the path relative to this script
12
- const scriptDir = __dirname;
13
- const skillsDir = path.join(scriptDir, '..', 'skills');
10
+ // Get the package skills directory - use the path relative to this script
11
+ const scriptDir = __dirname;
12
+ const skillsDir = path.join(scriptDir, '..', 'skills');
13
+
14
+ // Target directories for different AI coding tools
15
+ const targets = [
16
+ {
17
+ name: 'Claude Code',
18
+ dir: path.join(os.homedir(), '.claude', 'commands', 'om'),
19
+ },
20
+ {
21
+ name: 'OpenCode',
22
+ dir: path.join(os.homedir(), '.config', 'opencode', 'commands', 'om'),
23
+ },
24
+ ];
25
+
26
+ if (!fs.existsSync(skillsDir)) {
27
+ console.log('Skills directory not found, skipping installation.');
28
+ process.exit(0);
29
+ }
14
30
 
15
- // Get user's .claude directory
16
- const claudeDir = path.join(os.homedir(), '.claude');
17
- const commandsDir = path.join(claudeDir, 'commands', 'om');
31
+ const files = fs.readdirSync(skillsDir).filter(f => f.endsWith('.md'));
18
32
 
19
- // Create commands directory if it doesn't exist
33
+ for (const target of targets) {
20
34
  try {
21
- if (!fs.existsSync(commandsDir)) {
22
- fs.mkdirSync(commandsDir, { recursive: true });
35
+ // Create commands directory if it doesn't exist
36
+ if (!fs.existsSync(target.dir)) {
37
+ fs.mkdirSync(target.dir, { recursive: true });
23
38
  }
24
- } catch (mkdirErr) {
25
- console.log(`⚠️ Cannot create ${commandsDir}`);
26
- console.log(` Please run: mkdir -p ${commandsDir}`);
27
- process.exit(0);
28
- }
29
39
 
30
- // Copy skill files
31
- if (fs.existsSync(skillsDir)) {
32
- const files = fs.readdirSync(skillsDir).filter(f => f.endsWith('.md'));
33
-
34
- files.forEach(file => {
40
+ let installed = 0;
41
+ for (const file of files) {
35
42
  const src = path.join(skillsDir, file);
36
- const dest = path.join(commandsDir, file);
43
+ const dest = path.join(target.dir, file);
37
44
  try {
38
45
  fs.copyFileSync(src, dest);
39
- console.log(`✓ Installed: om:${path.basename(file, '.md')}`);
46
+ installed++;
40
47
  } catch (copyErr) {
41
- console.log(`⚠️ Skipped: om:${path.basename(file, '.md')} (${copyErr.message})`);
48
+ console.log(` ⚠️ Skipped: ${file} (${copyErr.message})`);
42
49
  }
43
- });
50
+ }
44
51
 
45
- console.log(`\n✅ OpenMatrix skills installed to ${commandsDir}`);
46
- } else {
47
- console.log('Skills directory not found, skipping installation.');
52
+ console.log(`✅ ${target.name}: ${installed} skills installed to ${target.dir}`);
53
+ } catch (err) {
54
+ console.log(`⚠️ ${target.name}: skipped (${err.message})`);
55
+ console.log(` Please run: mkdir -p ${target.dir}`);
48
56
  }
49
- } catch (err) {
50
- console.log('OpenMatrix skills installation skipped:', err.message);
51
57
  }
package/skills/approve.md CHANGED
@@ -14,7 +14,7 @@ description: 审批待处理项(包括计划、合并、部署、Meeting)
14
14
  <process>
15
15
  1. **获取待审批列表**
16
16
  ```bash
17
- openmatrix approve --list
17
+ openmatrix approve
18
18
  ```
19
19
 
20
20
  2. **如果没有待审批项**
@@ -152,9 +152,11 @@ description: 审批待处理项(包括计划、合并、部署、Meeting)
152
152
 
153
153
  5. **执行审批**
154
154
  ```bash
155
- openmatrix approve <approvalId> -d <approve|reject|modify> [-c "备注"]
155
+ openmatrix approve <approvalId> -d <approve|reject|modify> [-c "备注"] --json
156
156
  ```
157
157
 
158
+ **注意**: approvalId 是位置参数,不要使用 --id。正确格式: `openmatrix approve APPR-001 -d approve --json`
159
+
158
160
  6. **更新状态**
159
161
 
160
162
  - 写入审批结果到 `approvals/{id}.json`
@@ -214,18 +216,13 @@ Meeting 审批需要更细致的交互:
214
216
  ## CLI 命令
215
217
 
216
218
  ```bash
217
- # 列出待审批
218
- openmatrix approve
219
-
220
- # 处理审批
221
- openmatrix approve APPR-001 -d approve -c "同意此方案"
222
- openmatrix approve APPR-001 -d reject -c "需要重新设计"
223
- openmatrix approve APPR-001 -d modify -c "增加测试覆盖率要求"
224
-
225
- # Meeting 专用 (使用 meeting 命令)
226
- openmatrix meeting APPR-002 --action provide-info --info "数据库连接字符串是..."
227
- openmatrix meeting APPR-002 --action skip --message "此任务可选"
228
- openmatrix meeting APPR-002 --action cancel --message "需求变更,停止执行"
219
+ # 列出待审批 (不传 ID)
220
+ openmatrix approve --json
221
+
222
+ # 处理审批 (ID 是位置参数,不要用 --id)
223
+ openmatrix approve APPR-001 -d approve -c "同意此方案" --json
224
+ openmatrix approve APPR-001 -d reject -c "需要重新设计" --json
225
+ openmatrix approve APPR-001 -d modify -c "增加测试覆盖率要求" --json
229
226
  ```
230
227
 
231
228
  ## 与执行循环的集成
@@ -23,7 +23,7 @@ OpenMatrix 独立运行,不依赖外部任务编排系统。
23
23
 
24
24
  调用 CLI 创建头脑风暴会话:
25
25
  ```bash
26
- openmatrix brainstorm --json
26
+ openmatrix brainstorm "$ARGUMENTS" --json
27
27
  ```
28
28
 
29
29
  这会返回:
@@ -269,15 +269,32 @@ $ARGUMENTS
269
269
 
270
270
  ## 问题类型
271
271
 
272
+ ### 项目配置问题(智能管道生成)
273
+
272
274
  | 问题 ID | 目的 | 为什么重要 |
273
275
  |---------|------|-----------|
274
- | core_objective | 明确核心目标 | 选择正确的实现策略 |
275
- | user_value | 了解用户价值 | 设计合适的接口 |
276
- | complexity | 评估复杂度 | 决定实施策略 |
277
- | tech_constraints | 技术约束 | 影响方案选择 |
278
- | risks | 风险评估 | 提前规划应对 |
279
- | acceptance | 验收标准 | 判断完成度 |
280
- | priority | 优先级 | 资源分配 |
276
+ | objective | 明确任务目标(新功能/修复/重构) | 选择正确的实现策略 |
277
+ | quality_level | 质量门禁级别(strict/balanced/fast) | 影响测试覆盖、Lint、安全扫描要求 |
278
+ | tech_stack | 技术栈选择 | 决定使用什么框架和工具 |
279
+ | execution_mode | 执行模式(auto/confirm-key/confirm-all) | 控制审批节点和自动化程度 |
280
+ | test_coverage | 测试覆盖率要求 | 影响测试任务生成 |
281
+ | documentation_level | 文档要求级别 | 影响文档任务生成 |
282
+ | e2e_tests | 是否启用 E2E 测试 | Web/Mobile/GUI 项目适用 |
283
+ | risks | 风险评估 | 提前规划应对策略 |
284
+ | acceptance | 验收标准 | 判断任务完成度 |
285
+
286
+ > **智能预填**:当 `SmartQuestionAnalyzer` 对某个问题有高置信度推断时,该问题会被自动跳过,不需要用户回答。
287
+
288
+ ### 领域分析问题(底层逻辑思考)
289
+
290
+ | 问题 ID | 目的 | 为什么重要 |
291
+ |---------|------|-----------|
292
+ | domain_entities | 核心领域实体建模 | 决定数据模型和 API 设计的基础 |
293
+ | data_flow | 数据流转路径分析 | 决定架构选型(请求驱动 vs 事件驱动 vs 流处理) |
294
+ | invariants | 关键不变量/业务约束 | 决定哪里需要加锁、事务、校验 |
295
+ | core_scenarios | 核心用户场景链路 | 决定 MVP 功能范围和优先级排序 |
296
+
297
+ > **领域分析的价值**:这四个问题帮助 AI 在执行前建立对系统的深层理解,而不是机械地按需求列表编码。答案会作为上下文注入到每个 Agent 的执行提示词中。
281
298
 
282
299
  ## 与 start 的集成
283
300
 
@@ -286,12 +303,15 @@ $ARGUMENTS
286
303
  ```json
287
304
  {
288
305
  "answers": {
289
- "core_objective": "实现新功能",
290
- "user_value": "终端用户",
291
- "complexity": "中等",
292
- "risks": ["技术风险", "兼容性风险"],
293
- "acceptance": ["功能完整", "测试覆盖"],
294
- "priority": "中优先级"
306
+ "objective": "new_feature",
307
+ "quality_level": "balanced",
308
+ "tech_stack": ["typescript", "react"],
309
+ "execution_mode": "auto",
310
+ "test_coverage": "medium",
311
+ "documentation_level": "basic",
312
+ "e2e_tests": "false",
313
+ "risks": ["technical", "compatibility"],
314
+ "acceptance": ["functional", "tested"]
295
315
  },
296
316
  "insights": [
297
317
  "需要考虑安全性",