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.
- package/dist/cli/commands/approve.js +26 -15
- package/dist/cli/commands/brainstorm.js +169 -2
- package/dist/orchestrator/answer-mapper.d.ts +24 -0
- package/dist/orchestrator/answer-mapper.js +108 -0
- package/dist/orchestrator/executor.d.ts +19 -0
- package/dist/orchestrator/executor.js +76 -1
- package/dist/orchestrator/interactive-question-generator.js +77 -51
- package/dist/orchestrator/scheduler.d.ts +15 -1
- package/dist/orchestrator/scheduler.js +90 -17
- package/dist/orchestrator/smart-question-analyzer.d.ts +8 -0
- package/dist/orchestrator/smart-question-analyzer.js +72 -0
- package/dist/orchestrator/state-machine.js +2 -0
- package/dist/orchestrator/task-planner.d.ts +1 -1
- package/dist/orchestrator/task-planner.js +13 -7
- package/dist/storage/state-manager.d.ts +5 -0
- package/dist/storage/state-manager.js +81 -53
- package/package.json +1 -1
- package/scripts/install-skills.js +35 -29
- package/skills/approve.md +11 -14
- package/skills/brainstorm.md +34 -14
|
@@ -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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
...state
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
for (const target of targets) {
|
|
20
34
|
try {
|
|
21
|
-
if
|
|
22
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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(
|
|
43
|
+
const dest = path.join(target.dir, file);
|
|
37
44
|
try {
|
|
38
45
|
fs.copyFileSync(src, dest);
|
|
39
|
-
|
|
46
|
+
installed++;
|
|
40
47
|
} catch (copyErr) {
|
|
41
|
-
console.log(
|
|
48
|
+
console.log(` ⚠️ Skipped: ${file} (${copyErr.message})`);
|
|
42
49
|
}
|
|
43
|
-
}
|
|
50
|
+
}
|
|
44
51
|
|
|
45
|
-
console.log(
|
|
46
|
-
}
|
|
47
|
-
console.log(
|
|
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
|
|
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
|
## 与执行循环的集成
|
package/skills/brainstorm.md
CHANGED
|
@@ -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
|
-
|
|
|
275
|
-
|
|
|
276
|
-
|
|
|
277
|
-
|
|
|
278
|
-
|
|
|
279
|
-
|
|
|
280
|
-
|
|
|
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
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"
|
|
293
|
-
"
|
|
294
|
-
"
|
|
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
|
"需要考虑安全性",
|