openmatrix 0.1.61 → 0.1.62
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 +48 -2
- package/dist/orchestrator/answer-mapper.d.ts +24 -0
- package/dist/orchestrator/answer-mapper.js +108 -0
- package/dist/orchestrator/interactive-question-generator.js +77 -51
- package/dist/orchestrator/smart-question-analyzer.d.ts +8 -0
- package/dist/orchestrator/smart-question-analyzer.js +72 -0
- package/dist/orchestrator/task-planner.d.ts +1 -1
- package/dist/orchestrator/task-planner.js +13 -7
- package/package.json +1 -1
- package/scripts/install-skills.js +35 -29
- package/skills/approve.md +11 -14
- package/skills/brainstorm.md +21 -14
|
@@ -10,6 +10,7 @@ exports.approveCommand = new commander_1.Command('approve')
|
|
|
10
10
|
.argument('[approvalId]', '审批ID')
|
|
11
11
|
.option('-d, --decision <decision>', '决策 (approve/modify/reject)')
|
|
12
12
|
.option('-c, --comment <comment>', '备注说明')
|
|
13
|
+
.option('--json', '输出 JSON 格式 (供 Skill 解析)')
|
|
13
14
|
.action(async (approvalId, options) => {
|
|
14
15
|
const basePath = process.cwd();
|
|
15
16
|
const omPath = `${basePath}/.openmatrix`;
|
|
@@ -20,23 +21,33 @@ exports.approveCommand = new commander_1.Command('approve')
|
|
|
20
21
|
if (!approvalId) {
|
|
21
22
|
const pendingApprovals = await approvalManager.getPendingApprovals();
|
|
22
23
|
if (pendingApprovals.length === 0) {
|
|
23
|
-
|
|
24
|
+
if (options.json) {
|
|
25
|
+
console.log(JSON.stringify({ status: 'empty', pending: [] }));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log('✅ 没有待处理的审批');
|
|
29
|
+
}
|
|
24
30
|
return;
|
|
25
31
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
if (options.json) {
|
|
33
|
+
console.log(JSON.stringify({ status: 'pending', pending: pendingApprovals }));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.log('📋 待处理审批:\n');
|
|
37
|
+
pendingApprovals.forEach((approval, i) => {
|
|
38
|
+
const typeEmoji = {
|
|
39
|
+
plan: '📋',
|
|
40
|
+
merge: '🔀',
|
|
41
|
+
deploy: '🚀',
|
|
42
|
+
meeting: '🔴',
|
|
43
|
+
custom: '📝'
|
|
44
|
+
};
|
|
45
|
+
const emoji = typeEmoji[approval.type] || '📝';
|
|
46
|
+
console.log(` [${i + 1}] ${emoji} ${approval.id}: ${approval.title}`);
|
|
47
|
+
console.log(` 类型: ${approval.type} | 任务: ${approval.taskId}`);
|
|
48
|
+
});
|
|
49
|
+
console.log('\n💡 使用 openmatrix approve <ID> 处理审批');
|
|
50
|
+
}
|
|
40
51
|
return;
|
|
41
52
|
}
|
|
42
53
|
// 获取审批
|
|
@@ -38,6 +38,10 @@ exports.brainstormCommand = void 0;
|
|
|
38
38
|
const commander_1 = require("commander");
|
|
39
39
|
const state_manager_js_1 = require("../../storage/state-manager.js");
|
|
40
40
|
const gitignore_js_1 = require("../../utils/gitignore.js");
|
|
41
|
+
const smart_question_analyzer_js_1 = require("../../orchestrator/smart-question-analyzer.js");
|
|
42
|
+
const interactive_question_generator_js_1 = require("../../orchestrator/interactive-question-generator.js");
|
|
43
|
+
const task_parser_js_1 = require("../../orchestrator/task-parser.js");
|
|
44
|
+
const answer_mapper_js_1 = require("../../orchestrator/answer-mapper.js");
|
|
41
45
|
const fs = __importStar(require("fs/promises"));
|
|
42
46
|
const path = __importStar(require("path"));
|
|
43
47
|
exports.brainstormCommand = new commander_1.Command('brainstorm')
|
|
@@ -175,8 +179,8 @@ exports.brainstormCommand = new commander_1.Command('brainstorm')
|
|
|
175
179
|
break;
|
|
176
180
|
}
|
|
177
181
|
}
|
|
178
|
-
// 生成头脑风暴问题
|
|
179
|
-
const questions =
|
|
182
|
+
// 生成头脑风暴问题 — 使用智能管道
|
|
183
|
+
const questions = await generateSmartQuestions(taskContent, basePath);
|
|
180
184
|
// 检测是否涉及垂直领域
|
|
181
185
|
const domainDetection = detectVerticalDomain(taskContent);
|
|
182
186
|
// 创建会话
|
|
@@ -411,3 +415,45 @@ function generateBrainstormQuestions(taskContent, taskTitle) {
|
|
|
411
415
|
}
|
|
412
416
|
return questions;
|
|
413
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* 智能问题生成 — 使用 SmartQuestionAnalyzer + InteractiveQuestionGenerator
|
|
420
|
+
*/
|
|
421
|
+
async function generateSmartQuestions(taskContent, basePath) {
|
|
422
|
+
try {
|
|
423
|
+
// 1. 解析任务为 ParsedTask
|
|
424
|
+
const parser = new task_parser_js_1.TaskParser();
|
|
425
|
+
const parsedTask = parser.parse(taskContent);
|
|
426
|
+
// 2. 分析项目上下文 + 推断答案
|
|
427
|
+
const analyzer = new smart_question_analyzer_js_1.SmartQuestionAnalyzer(basePath);
|
|
428
|
+
const analysisResult = await analyzer.analyze(taskContent, parsedTask);
|
|
429
|
+
// 3. 映射推断结果为规范 brainstorm ID
|
|
430
|
+
const inferenceMap = (0, answer_mapper_js_1.translateAnalyzerInferences)(analysisResult.inferences);
|
|
431
|
+
// 4. 创建问题生成器 + 设置推断
|
|
432
|
+
const questionGen = new interactive_question_generator_js_1.InteractiveQuestionGenerator();
|
|
433
|
+
questionGen.setInferences(inferenceMap);
|
|
434
|
+
// 5. 生成基础问题 + 上下文问题
|
|
435
|
+
const session = questionGen.startSession(parsedTask);
|
|
436
|
+
questionGen.addContextualQuestions(parsedTask, session.questions);
|
|
437
|
+
// 6. 转换为 BrainstormQuestion[] 格式
|
|
438
|
+
const questions = session.questions
|
|
439
|
+
.filter(q => !session.skippedQuestionIds?.includes(q.id)) // 跳过高置信度推断的问题
|
|
440
|
+
.sort((a, b) => (a.priority ?? 5) - (b.priority ?? 5))
|
|
441
|
+
.map(q => ({
|
|
442
|
+
id: q.id,
|
|
443
|
+
question: q.question,
|
|
444
|
+
header: q.category,
|
|
445
|
+
options: (q.options || []).map(o => ({
|
|
446
|
+
label: o.label,
|
|
447
|
+
description: o.description || ''
|
|
448
|
+
})),
|
|
449
|
+
multiSelect: q.type === 'multiple',
|
|
450
|
+
why: ''
|
|
451
|
+
}));
|
|
452
|
+
return questions;
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
// Fallback: 如果智能管道出错,使用静态问题
|
|
456
|
+
console.error(`⚠️ 智能问题生成失败,使用静态问题: ${error instanceof Error ? error.message : error}`);
|
|
457
|
+
return generateBrainstormQuestions(taskContent, '');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { QuestionInference } from './smart-question-analyzer.js';
|
|
2
|
+
/**
|
|
3
|
+
* 将 brainstorm 答案键翻译为 TaskPlanner 期望的键
|
|
4
|
+
*
|
|
5
|
+
* 支持两种输入:
|
|
6
|
+
* 1. 规范 ID (如 objective, quality_level) → 直接映射
|
|
7
|
+
* 2. 旧 brainstorm ID (如 core_objective, quality) → 先转规范 ID 再映射
|
|
8
|
+
*/
|
|
9
|
+
export declare function translateBrainstormAnswers(answers: Record<string, string | string[]>): Record<string, string | string[]>;
|
|
10
|
+
/**
|
|
11
|
+
* 将 SmartQuestionAnalyzer 推理结果转换为 brainstorm 规范 ID
|
|
12
|
+
*/
|
|
13
|
+
export declare function translateAnalyzerInferences(inferences: QuestionInference[]): Map<string, {
|
|
14
|
+
answer: string | string[];
|
|
15
|
+
reason: string;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* 从 brainstorm 答案中提取 tasks-input.json 所需的字段
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractTasksInputFields(answers: Record<string, string | string[]>): {
|
|
21
|
+
quality?: string;
|
|
22
|
+
mode?: string;
|
|
23
|
+
e2eTests?: boolean;
|
|
24
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.translateBrainstormAnswers = translateBrainstormAnswers;
|
|
4
|
+
exports.translateAnalyzerInferences = translateAnalyzerInferences;
|
|
5
|
+
exports.extractTasksInputFields = extractTasksInputFields;
|
|
6
|
+
/**
|
|
7
|
+
* 答案键映射 — 统一三套 ID 体系
|
|
8
|
+
*
|
|
9
|
+
* Brainstorm 问题 ID → 规范 ID → TaskPlanner 期望的键
|
|
10
|
+
*/
|
|
11
|
+
/** Brainstorm 旧 ID → 规范 ID */
|
|
12
|
+
const BRAINSTORM_TO_CANONICAL = {
|
|
13
|
+
core_objective: 'objective',
|
|
14
|
+
user_value: 'user_value',
|
|
15
|
+
complexity: 'complexity',
|
|
16
|
+
tech_constraints: 'tech_stack',
|
|
17
|
+
risks: 'risks',
|
|
18
|
+
acceptance: 'acceptance',
|
|
19
|
+
priority: 'priority',
|
|
20
|
+
quality: 'quality_level',
|
|
21
|
+
execution_mode: 'execution_mode',
|
|
22
|
+
e2e_tests: 'e2e_tests',
|
|
23
|
+
};
|
|
24
|
+
/** 规范 ID → TaskPlanner extractUserContext() 期望的键 */
|
|
25
|
+
const CANONICAL_TO_PLANNER = {
|
|
26
|
+
objective: { '目标': '', objective: '' },
|
|
27
|
+
tech_stack: { '技术栈': '', techStack: '' },
|
|
28
|
+
test_coverage: { '测试': '', testCoverage: '' },
|
|
29
|
+
documentation_level: { '文档': '', documentationLevel: '' },
|
|
30
|
+
e2e_tests: { 'E2E测试': '', e2eTests: '', e2e: '' },
|
|
31
|
+
e2e_type: { 'E2E类型': '', e2eType: '' },
|
|
32
|
+
};
|
|
33
|
+
/** Analyzer 旧 ID → 规范 ID */
|
|
34
|
+
const ANALYZER_TO_CANONICAL = {
|
|
35
|
+
quality_level: 'quality_level',
|
|
36
|
+
tech_stack: 'tech_stack',
|
|
37
|
+
doc_level: 'documentation_level',
|
|
38
|
+
e2e_test: 'e2e_tests',
|
|
39
|
+
execution_mode: 'execution_mode',
|
|
40
|
+
objective: 'objective',
|
|
41
|
+
test_coverage: 'test_coverage',
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* 将 brainstorm 答案键翻译为 TaskPlanner 期望的键
|
|
45
|
+
*
|
|
46
|
+
* 支持两种输入:
|
|
47
|
+
* 1. 规范 ID (如 objective, quality_level) → 直接映射
|
|
48
|
+
* 2. 旧 brainstorm ID (如 core_objective, quality) → 先转规范 ID 再映射
|
|
49
|
+
*/
|
|
50
|
+
function translateBrainstormAnswers(answers) {
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const [key, value] of Object.entries(answers)) {
|
|
53
|
+
// 步骤 1: 旧 brainstorm ID → 规范 ID
|
|
54
|
+
const canonical = BRAINSTORM_TO_CANONICAL[key] || key;
|
|
55
|
+
// 步骤 2: 规范 ID → planner 键
|
|
56
|
+
const plannerKeys = CANONICAL_TO_PLANNER[canonical];
|
|
57
|
+
if (plannerKeys) {
|
|
58
|
+
for (const plannerKey of Object.keys(plannerKeys)) {
|
|
59
|
+
result[plannerKey] = value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 同时保留原始键和规范 ID(向后兼容)
|
|
63
|
+
result[key] = value;
|
|
64
|
+
if (canonical !== key) {
|
|
65
|
+
result[canonical] = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 将 SmartQuestionAnalyzer 推理结果转换为 brainstorm 规范 ID
|
|
72
|
+
*/
|
|
73
|
+
function translateAnalyzerInferences(inferences) {
|
|
74
|
+
const result = new Map();
|
|
75
|
+
for (const inf of inferences) {
|
|
76
|
+
if (inf.inferredAnswer === undefined)
|
|
77
|
+
continue;
|
|
78
|
+
const canonical = ANALYZER_TO_CANONICAL[inf.questionId] || inf.questionId;
|
|
79
|
+
result.set(canonical, {
|
|
80
|
+
answer: inf.inferredAnswer,
|
|
81
|
+
reason: inf.reason
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 从 brainstorm 答案中提取 tasks-input.json 所需的字段
|
|
88
|
+
*/
|
|
89
|
+
function extractTasksInputFields(answers) {
|
|
90
|
+
const result = {};
|
|
91
|
+
// quality_level / quality → quality 字段
|
|
92
|
+
const quality = answers['quality_level'] || answers['quality'];
|
|
93
|
+
if (typeof quality === 'string') {
|
|
94
|
+
result.quality = quality;
|
|
95
|
+
}
|
|
96
|
+
// execution_mode → mode 字段
|
|
97
|
+
const mode = answers['execution_mode'];
|
|
98
|
+
if (typeof mode === 'string') {
|
|
99
|
+
result.mode = mode;
|
|
100
|
+
}
|
|
101
|
+
// e2e_tests → e2eTests 布尔字段
|
|
102
|
+
const e2e = answers['e2e_tests'] || answers['e2eTests'] || answers['E2E测试'];
|
|
103
|
+
if (e2e !== undefined) {
|
|
104
|
+
const e2eStr = Array.isArray(e2e) ? e2e[0] : e2e;
|
|
105
|
+
result.e2eTests = e2eStr === 'true' || e2eStr === '启用 E2E 测试' || e2eStr === '是';
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
@@ -193,18 +193,18 @@ class InteractiveQuestionGenerator {
|
|
|
193
193
|
*/
|
|
194
194
|
generateBaseQuestions(task) {
|
|
195
195
|
const questions = [];
|
|
196
|
-
// 1.
|
|
196
|
+
// 1. 任务目标
|
|
197
197
|
questions.push({
|
|
198
198
|
id: 'objective',
|
|
199
|
-
question: '
|
|
199
|
+
question: '这个任务的核心目标是什么?想要解决什么问题?',
|
|
200
200
|
type: 'single',
|
|
201
201
|
required: true,
|
|
202
202
|
category: 'objective',
|
|
203
203
|
priority: 1,
|
|
204
204
|
options: [
|
|
205
|
-
{ key: 'new_feature', label: '实现新功能', description: '
|
|
206
|
-
{ key: 'bug_fix', label: '
|
|
207
|
-
{ key: 'refactor', label: '重构优化', description: '
|
|
205
|
+
{ key: 'new_feature', label: '实现新功能', description: '添加新的功能特性,扩展系统能力' },
|
|
206
|
+
{ key: 'bug_fix', label: '修复问题', description: '修复 Bug 或解决已知问题' },
|
|
207
|
+
{ key: 'refactor', label: '重构优化', description: '改进代码结构、性能或可维护性' },
|
|
208
208
|
{ key: 'other', label: '其他', description: '其他类型任务' }
|
|
209
209
|
],
|
|
210
210
|
followUpCondition: {
|
|
@@ -221,14 +221,28 @@ class InteractiveQuestionGenerator {
|
|
|
221
221
|
]
|
|
222
222
|
}
|
|
223
223
|
});
|
|
224
|
-
// 2.
|
|
224
|
+
// 2. 质量级别
|
|
225
|
+
questions.push({
|
|
226
|
+
id: 'quality_level',
|
|
227
|
+
question: '选择质量门禁级别(决定测试覆盖、Lint、安全扫描等要求)',
|
|
228
|
+
type: 'single',
|
|
229
|
+
required: true,
|
|
230
|
+
category: 'quality',
|
|
231
|
+
priority: 2,
|
|
232
|
+
options: [
|
|
233
|
+
{ key: 'strict', label: 'strict', description: 'TDD + 80% 覆盖率 + 严格 Lint + 安全扫描 — 生产级代码' },
|
|
234
|
+
{ key: 'balanced', label: 'balanced (推荐)', description: '60% 覆盖率 + Lint + 安全扫描 — 日常开发' },
|
|
235
|
+
{ key: 'fast', label: 'fast', description: '无质量门禁 — 快速原型/验证' }
|
|
236
|
+
]
|
|
237
|
+
});
|
|
238
|
+
// 3. 技术栈
|
|
225
239
|
questions.push({
|
|
226
240
|
id: 'tech_stack',
|
|
227
241
|
question: '使用什么技术栈?',
|
|
228
242
|
type: 'multiple',
|
|
229
243
|
required: true,
|
|
230
244
|
category: 'technical',
|
|
231
|
-
priority:
|
|
245
|
+
priority: 3,
|
|
232
246
|
options: [
|
|
233
247
|
{ key: 'typescript', label: 'TypeScript', description: '类型安全的 JavaScript' },
|
|
234
248
|
{ key: 'javascript', label: 'JavaScript', description: '标准 JavaScript' },
|
|
@@ -247,64 +261,33 @@ class InteractiveQuestionGenerator {
|
|
|
247
261
|
type: 'text',
|
|
248
262
|
required: true,
|
|
249
263
|
category: 'technical',
|
|
250
|
-
priority:
|
|
264
|
+
priority: 3.5
|
|
251
265
|
}
|
|
252
266
|
]
|
|
253
267
|
}
|
|
254
268
|
});
|
|
255
|
-
//
|
|
256
|
-
questions.push({
|
|
257
|
-
id: 'data_storage',
|
|
258
|
-
question: '需要什么类型的存储?',
|
|
259
|
-
type: 'single',
|
|
260
|
-
required: true,
|
|
261
|
-
category: 'technical',
|
|
262
|
-
priority: 3,
|
|
263
|
-
options: [
|
|
264
|
-
{ key: 'postgresql', label: 'PostgreSQL', description: '关系型数据库' },
|
|
265
|
-
{ key: 'mongodb', label: 'MongoDB', description: '文档数据库' },
|
|
266
|
-
{ key: 'sqlite', label: 'SQLite', description: '本地数据库' },
|
|
267
|
-
{ key: 'none', label: '无需存储', description: '不需要数据持久化' }
|
|
268
|
-
]
|
|
269
|
-
});
|
|
270
|
-
// 4. 认证方式
|
|
269
|
+
// 4. 执行模式
|
|
271
270
|
questions.push({
|
|
272
|
-
id: '
|
|
273
|
-
question: '
|
|
271
|
+
id: 'execution_mode',
|
|
272
|
+
question: '选择执行模式(控制 AI 执行过程中的审批节点)',
|
|
274
273
|
type: 'single',
|
|
275
274
|
required: true,
|
|
276
|
-
category: '
|
|
275
|
+
category: 'quality',
|
|
277
276
|
priority: 4,
|
|
278
277
|
options: [
|
|
279
|
-
{ key: '
|
|
280
|
-
{ key: '
|
|
281
|
-
{ key: '
|
|
282
|
-
{ key: 'none', label: '无需认证', description: '无用户系统' }
|
|
278
|
+
{ key: 'auto', label: 'auto (推荐)', description: '全自动执行,无需人工审批,遇到阻塞自动 Meeting' },
|
|
279
|
+
{ key: 'confirm-key', label: 'confirm-key', description: '关键节点审批(计划、合并、部署)' },
|
|
280
|
+
{ key: 'confirm-all', label: 'confirm-all', description: '每个阶段都需人工确认' }
|
|
283
281
|
]
|
|
284
282
|
});
|
|
285
|
-
// 5.
|
|
286
|
-
questions.push({
|
|
287
|
-
id: 'api_style',
|
|
288
|
-
question: 'API 采用什么风格?',
|
|
289
|
-
type: 'single',
|
|
290
|
-
required: true,
|
|
291
|
-
category: 'technical',
|
|
292
|
-
priority: 5,
|
|
293
|
-
options: [
|
|
294
|
-
{ key: 'rest', label: 'RESTful', description: 'REST API' },
|
|
295
|
-
{ key: 'graphql', label: 'GraphQL', description: 'GraphQL API' },
|
|
296
|
-
{ key: 'grpc', label: 'gRPC', description: '高性能 RPC' },
|
|
297
|
-
{ key: 'mixed', label: '混合', description: '多种风格混合' }
|
|
298
|
-
]
|
|
299
|
-
});
|
|
300
|
-
// 6. 测试要求
|
|
283
|
+
// 5. 测试覆盖率
|
|
301
284
|
questions.push({
|
|
302
285
|
id: 'test_coverage',
|
|
303
286
|
question: '测试覆盖率要求?',
|
|
304
287
|
type: 'single',
|
|
305
288
|
required: true,
|
|
306
289
|
category: 'quality',
|
|
307
|
-
priority:
|
|
290
|
+
priority: 5,
|
|
308
291
|
options: [
|
|
309
292
|
{ key: 'high', label: '>80% (严格)', description: '完整单元测试和集成测试' },
|
|
310
293
|
{ key: 'medium', label: '>60% (标准)', description: '核心功能测试' },
|
|
@@ -312,14 +295,14 @@ class InteractiveQuestionGenerator {
|
|
|
312
295
|
{ key: 'none', label: '无要求', description: '不需要测试' }
|
|
313
296
|
]
|
|
314
297
|
});
|
|
315
|
-
//
|
|
298
|
+
// 6. 文档要求
|
|
316
299
|
questions.push({
|
|
317
|
-
id: '
|
|
300
|
+
id: 'documentation_level',
|
|
318
301
|
question: '需要什么级别的文档?',
|
|
319
302
|
type: 'single',
|
|
320
303
|
required: true,
|
|
321
304
|
category: 'quality',
|
|
322
|
-
priority:
|
|
305
|
+
priority: 6,
|
|
323
306
|
options: [
|
|
324
307
|
{ key: 'full', label: '完整文档', description: 'API + 使用指南 + 架构' },
|
|
325
308
|
{ key: 'basic', label: '基础文档', description: 'README + API' },
|
|
@@ -327,6 +310,49 @@ class InteractiveQuestionGenerator {
|
|
|
327
310
|
{ key: 'none', label: '无需文档', description: '不生成文档' }
|
|
328
311
|
]
|
|
329
312
|
});
|
|
313
|
+
// 7. E2E 测试
|
|
314
|
+
questions.push({
|
|
315
|
+
id: 'e2e_tests',
|
|
316
|
+
question: '是否启用端到端 (E2E) 测试?(适用于 Web/Mobile/GUI 项目,耗时较长)',
|
|
317
|
+
type: 'single',
|
|
318
|
+
required: false,
|
|
319
|
+
category: 'quality',
|
|
320
|
+
priority: 7,
|
|
321
|
+
options: [
|
|
322
|
+
{ key: 'true', label: '启用 E2E 测试', description: '使用 Playwright/Cypress 等框架进行端到端测试' },
|
|
323
|
+
{ key: 'false', label: '不启用 (推荐)', description: '仅进行单元测试和集成测试,节省时间' }
|
|
324
|
+
]
|
|
325
|
+
});
|
|
326
|
+
// 8. 风险评估
|
|
327
|
+
questions.push({
|
|
328
|
+
id: 'risks',
|
|
329
|
+
question: '这个任务可能面临哪些风险或挑战?',
|
|
330
|
+
type: 'multiple',
|
|
331
|
+
required: true,
|
|
332
|
+
category: 'risk',
|
|
333
|
+
priority: 8,
|
|
334
|
+
options: [
|
|
335
|
+
{ key: 'technical', label: '技术风险', description: '技术实现存在不确定性' },
|
|
336
|
+
{ key: 'time', label: '时间风险', description: '需要在短时间内完成' },
|
|
337
|
+
{ key: 'compatibility', label: '兼容性风险', description: '可能影响现有功能' },
|
|
338
|
+
{ key: 'none', label: '无明显风险', description: '任务清晰,风险可控' }
|
|
339
|
+
]
|
|
340
|
+
});
|
|
341
|
+
// 9. 验收标准
|
|
342
|
+
questions.push({
|
|
343
|
+
id: 'acceptance',
|
|
344
|
+
question: '如何判断任务完成?有哪些验收标准?',
|
|
345
|
+
type: 'multiple',
|
|
346
|
+
required: true,
|
|
347
|
+
category: 'scope',
|
|
348
|
+
priority: 9,
|
|
349
|
+
options: [
|
|
350
|
+
{ key: 'functional', label: '功能完整', description: '所有功能按预期工作' },
|
|
351
|
+
{ key: 'tested', label: '测试覆盖', description: '有足够的测试覆盖' },
|
|
352
|
+
{ key: 'performance', label: '性能达标', description: '满足性能要求' },
|
|
353
|
+
{ key: 'documented', label: '文档完善', description: '有完整的使用文档' }
|
|
354
|
+
]
|
|
355
|
+
});
|
|
330
356
|
return questions;
|
|
331
357
|
}
|
|
332
358
|
/**
|
|
@@ -176,6 +176,10 @@ class SmartQuestionAnalyzer {
|
|
|
176
176
|
inferences.push(this.inferE2ETest(desc, context));
|
|
177
177
|
// 5. 推断执行模式
|
|
178
178
|
inferences.push(this.inferExecutionMode(desc, context));
|
|
179
|
+
// 6. 推断任务目标类型
|
|
180
|
+
inferences.push(this.inferObjective(desc, context));
|
|
181
|
+
// 7. 推断测试覆盖率级别
|
|
182
|
+
inferences.push(this.inferTestCoverage(desc, context));
|
|
179
183
|
return inferences;
|
|
180
184
|
}
|
|
181
185
|
/**
|
|
@@ -384,6 +388,74 @@ class SmartQuestionAnalyzer {
|
|
|
384
388
|
inference.reason = '无法确定执行模式';
|
|
385
389
|
return inference;
|
|
386
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* 推断任务目标类型
|
|
393
|
+
*/
|
|
394
|
+
inferObjective(desc, context) {
|
|
395
|
+
const inference = {
|
|
396
|
+
questionId: 'objective',
|
|
397
|
+
confidence: 'low',
|
|
398
|
+
reason: ''
|
|
399
|
+
};
|
|
400
|
+
if (/(fix|bug|修复|hotfix|patch)/i.test(desc)) {
|
|
401
|
+
inference.inferredAnswer = 'bug_fix';
|
|
402
|
+
inference.confidence = 'medium';
|
|
403
|
+
inference.reason = '任务描述包含修复关键词';
|
|
404
|
+
}
|
|
405
|
+
else if (/(implement|add|新功能|实现|开发|feature|新增)/i.test(desc)) {
|
|
406
|
+
inference.inferredAnswer = 'new_feature';
|
|
407
|
+
inference.confidence = 'medium';
|
|
408
|
+
inference.reason = '任务描述包含新功能关键词';
|
|
409
|
+
}
|
|
410
|
+
else if (/(refactor|优化|重构|improve|性能)/i.test(desc)) {
|
|
411
|
+
inference.inferredAnswer = 'refactor';
|
|
412
|
+
inference.confidence = 'medium';
|
|
413
|
+
inference.reason = '任务描述包含重构关键词';
|
|
414
|
+
}
|
|
415
|
+
else if (/(test|测试|coverage|覆盖)/i.test(desc)) {
|
|
416
|
+
inference.inferredAnswer = 'test';
|
|
417
|
+
inference.confidence = 'medium';
|
|
418
|
+
inference.reason = '任务描述包含测试关键词';
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
inference.reason = '无法确定任务目标类型';
|
|
422
|
+
}
|
|
423
|
+
return inference;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* 推断测试覆盖率级别
|
|
427
|
+
*/
|
|
428
|
+
inferTestCoverage(desc, context) {
|
|
429
|
+
const inference = {
|
|
430
|
+
questionId: 'test_coverage',
|
|
431
|
+
confidence: 'low',
|
|
432
|
+
reason: ''
|
|
433
|
+
};
|
|
434
|
+
if (context.hasTests && /(test|测试)/i.test(desc)) {
|
|
435
|
+
inference.inferredAnswer = 'high';
|
|
436
|
+
inference.confidence = 'medium';
|
|
437
|
+
inference.reason = '项目已有测试,且任务涉及测试';
|
|
438
|
+
}
|
|
439
|
+
else if (/(implement|add|新功能|实现|feature)/i.test(desc)) {
|
|
440
|
+
inference.inferredAnswer = 'medium';
|
|
441
|
+
inference.confidence = 'low';
|
|
442
|
+
inference.reason = '新功能任务,中等测试覆盖率';
|
|
443
|
+
}
|
|
444
|
+
else if (/(fix|bug|修复)/i.test(desc)) {
|
|
445
|
+
inference.inferredAnswer = 'low';
|
|
446
|
+
inference.confidence = 'medium';
|
|
447
|
+
inference.reason = 'Bug 修复,需要回归测试但覆盖率要求不高';
|
|
448
|
+
}
|
|
449
|
+
else if (/(prototype|poc|demo|快速|原型)/i.test(desc)) {
|
|
450
|
+
inference.inferredAnswer = 'none';
|
|
451
|
+
inference.confidence = 'medium';
|
|
452
|
+
inference.reason = '原型任务,不需要测试';
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
inference.reason = '无法确定测试覆盖率级别';
|
|
456
|
+
}
|
|
457
|
+
return inference;
|
|
458
|
+
}
|
|
387
459
|
/**
|
|
388
460
|
* 生成推断摘要
|
|
389
461
|
*/
|
|
@@ -16,7 +16,7 @@ export interface UserAnswers {
|
|
|
16
16
|
techStack?: string[];
|
|
17
17
|
testCoverage?: string;
|
|
18
18
|
documentationLevel?: string;
|
|
19
|
-
additionalContext?: Record<string, string>;
|
|
19
|
+
additionalContext?: Record<string, string | string[]>;
|
|
20
20
|
/** 是否启用 E2E 测试 */
|
|
21
21
|
e2eTests?: boolean;
|
|
22
22
|
/** E2E 测试类型 (web/mobile/gui) */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TaskPlanner = void 0;
|
|
4
|
+
const answer_mapper_js_1 = require("./answer-mapper.js");
|
|
4
5
|
/**
|
|
5
6
|
* TaskPlanner - 任务拆解器
|
|
6
7
|
*
|
|
@@ -261,16 +262,21 @@ ${userContext.documentationLevel}
|
|
|
261
262
|
* 提取用户上下文
|
|
262
263
|
*/
|
|
263
264
|
extractUserContext(answers) {
|
|
264
|
-
|
|
265
|
-
const
|
|
265
|
+
// 先翻译 brainstorm 规范键为 planner 期望的键
|
|
266
|
+
const translated = (0, answer_mapper_js_1.translateBrainstormAnswers)(answers);
|
|
267
|
+
const merged = { ...answers, ...translated };
|
|
268
|
+
// 辅助函数:提取字符串值(处理 string[] 情况)
|
|
269
|
+
const str = (v) => Array.isArray(v) ? v.join(', ') : v;
|
|
270
|
+
const e2eAnswer = str(merged['E2E测试'] || merged['e2eTests'] || merged['e2e']);
|
|
271
|
+
const e2eTypeAnswer = str(merged['E2E类型'] || merged['e2eType']);
|
|
266
272
|
return {
|
|
267
|
-
objective:
|
|
268
|
-
techStack: this.parseArrayAnswer(
|
|
269
|
-
testCoverage:
|
|
270
|
-
documentationLevel:
|
|
273
|
+
objective: str(merged['目标'] || merged['objective']),
|
|
274
|
+
techStack: this.parseArrayAnswer(str(merged['技术栈'] || merged['techStack']) || ''),
|
|
275
|
+
testCoverage: str(merged['测试'] || merged['testCoverage']),
|
|
276
|
+
documentationLevel: str(merged['文档'] || merged['documentationLevel']),
|
|
271
277
|
e2eTests: e2eAnswer === 'true' || e2eAnswer === '✅ 启用 E2E 测试' || e2eAnswer === '是',
|
|
272
278
|
e2eType: e2eTypeAnswer || 'web',
|
|
273
|
-
additionalContext:
|
|
279
|
+
additionalContext: merged
|
|
274
280
|
};
|
|
275
281
|
}
|
|
276
282
|
parseArrayAnswer(answer) {
|
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
|
这会返回:
|
|
@@ -271,13 +271,17 @@ $ARGUMENTS
|
|
|
271
271
|
|
|
272
272
|
| 问题 ID | 目的 | 为什么重要 |
|
|
273
273
|
|---------|------|-----------|
|
|
274
|
-
|
|
|
275
|
-
|
|
|
276
|
-
|
|
|
277
|
-
|
|
|
278
|
-
|
|
|
279
|
-
|
|
|
280
|
-
|
|
|
274
|
+
| objective | 明确任务目标(新功能/修复/重构) | 选择正确的实现策略 |
|
|
275
|
+
| quality_level | 质量门禁级别(strict/balanced/fast) | 影响测试覆盖、Lint、安全扫描要求 |
|
|
276
|
+
| tech_stack | 技术栈选择 | 决定使用什么框架和工具 |
|
|
277
|
+
| execution_mode | 执行模式(auto/confirm-key/confirm-all) | 控制审批节点和自动化程度 |
|
|
278
|
+
| test_coverage | 测试覆盖率要求 | 影响测试任务生成 |
|
|
279
|
+
| documentation_level | 文档要求级别 | 影响文档任务生成 |
|
|
280
|
+
| e2e_tests | 是否启用 E2E 测试 | Web/Mobile/GUI 项目适用 |
|
|
281
|
+
| risks | 风险评估 | 提前规划应对策略 |
|
|
282
|
+
| acceptance | 验收标准 | 判断任务完成度 |
|
|
283
|
+
|
|
284
|
+
> **智能预填**:当 `SmartQuestionAnalyzer` 对某个问题有高置信度推断时,该问题会被自动跳过,不需要用户回答。
|
|
281
285
|
|
|
282
286
|
## 与 start 的集成
|
|
283
287
|
|
|
@@ -286,12 +290,15 @@ $ARGUMENTS
|
|
|
286
290
|
```json
|
|
287
291
|
{
|
|
288
292
|
"answers": {
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"
|
|
293
|
-
"
|
|
294
|
-
"
|
|
293
|
+
"objective": "new_feature",
|
|
294
|
+
"quality_level": "balanced",
|
|
295
|
+
"tech_stack": ["typescript", "react"],
|
|
296
|
+
"execution_mode": "auto",
|
|
297
|
+
"test_coverage": "medium",
|
|
298
|
+
"documentation_level": "basic",
|
|
299
|
+
"e2e_tests": "false",
|
|
300
|
+
"risks": ["technical", "compatibility"],
|
|
301
|
+
"acceptance": ["functional", "tested"]
|
|
295
302
|
},
|
|
296
303
|
"insights": [
|
|
297
304
|
"需要考虑安全性",
|