openmatrix 0.2.18 → 0.2.20
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/test.d.ts +20 -0
- package/dist/cli/commands/test.js +216 -0
- package/dist/cli/index.js +2 -0
- package/dist/test/context-analyzer.d.ts +76 -0
- package/dist/test/context-analyzer.js +778 -0
- package/dist/test/generator.d.ts +17 -0
- package/dist/test/generator.js +403 -0
- package/dist/types/index.d.ts +309 -0
- package/package.json +1 -1
- package/skills/brainstorm.md +61 -22
- package/skills/om.md +53 -45
- package/skills/test.md +719 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test 命令 - 项目测试状况扫描
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - 检测测试框架
|
|
6
|
+
* - 扫描测试文件和源文件
|
|
7
|
+
* - 发现测试覆盖缺失
|
|
8
|
+
* - 运行测试验证
|
|
9
|
+
*
|
|
10
|
+
* 模块依赖: types → cli/commands/test → test/context-analyzer
|
|
11
|
+
*/
|
|
12
|
+
import { Command } from 'commander';
|
|
13
|
+
import type { TestScanResult } from '../../types/index.js';
|
|
14
|
+
import { detectTestFrameworks, detectProjectType, scanTestFiles, scanSourceFiles, scanDirectory, inferSourceFile, inferFileType, extractExports, hasCorrespondingTest, inferTestTypes, detectFrontend, detectTestStyle, detectCoverageReport } from '../../test/context-analyzer.js';
|
|
15
|
+
export { detectTestFrameworks, detectProjectType, scanTestFiles, scanSourceFiles, scanDirectory, inferSourceFile, inferFileType, extractExports, hasCorrespondingTest, inferTestTypes, detectFrontend, detectTestStyle, detectCoverageReport };
|
|
16
|
+
/**
|
|
17
|
+
* 执行测试扫描(CLI 入口)
|
|
18
|
+
*/
|
|
19
|
+
export declare function performScan(projectRoot: string, target?: string): TestScanResult;
|
|
20
|
+
export declare const testCommand: Command;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.testCommand = exports.detectCoverageReport = exports.detectTestStyle = exports.detectFrontend = exports.inferTestTypes = exports.hasCorrespondingTest = exports.extractExports = exports.inferFileType = exports.inferSourceFile = exports.scanDirectory = exports.scanSourceFiles = exports.scanTestFiles = exports.detectProjectType = exports.detectTestFrameworks = void 0;
|
|
37
|
+
exports.performScan = performScan;
|
|
38
|
+
// src/cli/commands/test.ts
|
|
39
|
+
/**
|
|
40
|
+
* Test 命令 - 项目测试状况扫描
|
|
41
|
+
*
|
|
42
|
+
* 功能:
|
|
43
|
+
* - 检测测试框架
|
|
44
|
+
* - 扫描测试文件和源文件
|
|
45
|
+
* - 发现测试覆盖缺失
|
|
46
|
+
* - 运行测试验证
|
|
47
|
+
*
|
|
48
|
+
* 模块依赖: types → cli/commands/test → test/context-analyzer
|
|
49
|
+
*/
|
|
50
|
+
const commander_1 = require("commander");
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
// 从 context-analyzer 模块导入核心函数
|
|
53
|
+
const context_analyzer_js_1 = require("../../test/context-analyzer.js");
|
|
54
|
+
Object.defineProperty(exports, "detectTestFrameworks", { enumerable: true, get: function () { return context_analyzer_js_1.detectTestFrameworks; } });
|
|
55
|
+
Object.defineProperty(exports, "detectProjectType", { enumerable: true, get: function () { return context_analyzer_js_1.detectProjectType; } });
|
|
56
|
+
Object.defineProperty(exports, "scanTestFiles", { enumerable: true, get: function () { return context_analyzer_js_1.scanTestFiles; } });
|
|
57
|
+
Object.defineProperty(exports, "scanSourceFiles", { enumerable: true, get: function () { return context_analyzer_js_1.scanSourceFiles; } });
|
|
58
|
+
Object.defineProperty(exports, "scanDirectory", { enumerable: true, get: function () { return context_analyzer_js_1.scanDirectory; } });
|
|
59
|
+
Object.defineProperty(exports, "inferSourceFile", { enumerable: true, get: function () { return context_analyzer_js_1.inferSourceFile; } });
|
|
60
|
+
Object.defineProperty(exports, "inferFileType", { enumerable: true, get: function () { return context_analyzer_js_1.inferFileType; } });
|
|
61
|
+
Object.defineProperty(exports, "extractExports", { enumerable: true, get: function () { return context_analyzer_js_1.extractExports; } });
|
|
62
|
+
Object.defineProperty(exports, "hasCorrespondingTest", { enumerable: true, get: function () { return context_analyzer_js_1.hasCorrespondingTest; } });
|
|
63
|
+
Object.defineProperty(exports, "inferTestTypes", { enumerable: true, get: function () { return context_analyzer_js_1.inferTestTypes; } });
|
|
64
|
+
Object.defineProperty(exports, "detectFrontend", { enumerable: true, get: function () { return context_analyzer_js_1.detectFrontend; } });
|
|
65
|
+
Object.defineProperty(exports, "detectTestStyle", { enumerable: true, get: function () { return context_analyzer_js_1.detectTestStyle; } });
|
|
66
|
+
Object.defineProperty(exports, "detectCoverageReport", { enumerable: true, get: function () { return context_analyzer_js_1.detectCoverageReport; } });
|
|
67
|
+
/**
|
|
68
|
+
* 执行测试扫描(CLI 入口)
|
|
69
|
+
*/
|
|
70
|
+
function performScan(projectRoot, target) {
|
|
71
|
+
return (0, context_analyzer_js_1.performFullScan)(projectRoot, target);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 运行测试验证
|
|
75
|
+
*/
|
|
76
|
+
async function runTests(projectRoot, frameworks) {
|
|
77
|
+
const primaryFramework = frameworks.find(f => f.isPrimary) || frameworks[0];
|
|
78
|
+
if (!primaryFramework || primaryFramework.framework === 'unknown') {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
output: 'No test framework detected'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const testCommand = primaryFramework.commands.test;
|
|
85
|
+
if (!testCommand) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
output: 'No test command available'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const { spawn } = await import('child_process');
|
|
93
|
+
const parts = testCommand.split(' ');
|
|
94
|
+
const command = parts[0];
|
|
95
|
+
const args = parts.slice(1);
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
let output = '';
|
|
98
|
+
const proc = spawn(command, args, {
|
|
99
|
+
cwd: projectRoot,
|
|
100
|
+
shell: true,
|
|
101
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
102
|
+
});
|
|
103
|
+
proc.stdout.on('data', (data) => {
|
|
104
|
+
output += data.toString();
|
|
105
|
+
});
|
|
106
|
+
proc.stderr.on('data', (data) => {
|
|
107
|
+
output += data.toString();
|
|
108
|
+
});
|
|
109
|
+
proc.on('close', (code) => {
|
|
110
|
+
let coverage;
|
|
111
|
+
const coverageMatch = output.match(/All files[^|]*\|[^|]*\|[^|]*\|[^|]*\|[^|]*\|\s*([\d.]+)%/);
|
|
112
|
+
if (coverageMatch) {
|
|
113
|
+
coverage = parseFloat(coverageMatch[1]);
|
|
114
|
+
}
|
|
115
|
+
resolve({
|
|
116
|
+
success: code === 0,
|
|
117
|
+
output,
|
|
118
|
+
coverage
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
proc.on('error', (err) => {
|
|
122
|
+
resolve({
|
|
123
|
+
success: false,
|
|
124
|
+
output: `Failed to run tests: ${err.message}`
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
output: 'Failed to spawn test process'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.testCommand = new commander_1.Command('test')
|
|
137
|
+
.description('扫描项目测试状况 - 检测框架、扫描文件、发现缺失')
|
|
138
|
+
.argument('[target]', '扫描目标目录或文件')
|
|
139
|
+
.option('--json', '输出 JSON 格式')
|
|
140
|
+
.option('--verify', '扫描后运行测试验证')
|
|
141
|
+
.action(async (target, options) => {
|
|
142
|
+
const projectRoot = process.cwd();
|
|
143
|
+
if (!fs.existsSync(projectRoot)) {
|
|
144
|
+
console.log('❌ 项目目录不存在');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const result = performScan(projectRoot, target);
|
|
148
|
+
if (options.verify) {
|
|
149
|
+
console.log('🔍 执行测试验证...\n');
|
|
150
|
+
const verifyResult = await runTests(projectRoot, result.frameworks);
|
|
151
|
+
result.coverageReport = {
|
|
152
|
+
total: verifyResult.coverage || 0,
|
|
153
|
+
files: []
|
|
154
|
+
};
|
|
155
|
+
if (!options.json) {
|
|
156
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
157
|
+
console.log(`测试验证: ${verifyResult.success ? '✅ 通过' : '❌ 失败'}`);
|
|
158
|
+
if (verifyResult.coverage) {
|
|
159
|
+
console.log(`覆盖率: ${verifyResult.coverage}%`);
|
|
160
|
+
}
|
|
161
|
+
if (!verifyResult.success) {
|
|
162
|
+
console.log('\n错误输出:');
|
|
163
|
+
console.log(verifyResult.output.slice(0, 500));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (options.json) {
|
|
168
|
+
console.log(JSON.stringify(result, null, 2));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// 格式化输出
|
|
172
|
+
console.log('\n🔍 测试扫描结果');
|
|
173
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
174
|
+
console.log(`扫描时间: ${result.timestamp}`);
|
|
175
|
+
console.log(`项目类型: ${result.projectType}`);
|
|
176
|
+
console.log(`前端项目: ${result.isFrontend ? '是' : '否'}`);
|
|
177
|
+
console.log(`UI组件: ${result.hasUIComponents ? '有' : '无'}`);
|
|
178
|
+
console.log('\n📊 测试框架:');
|
|
179
|
+
for (const framework of result.frameworks) {
|
|
180
|
+
const primary = framework.isPrimary ? ' (主要)' : '';
|
|
181
|
+
console.log(` - ${framework.framework}${primary}`);
|
|
182
|
+
if (framework.version)
|
|
183
|
+
console.log(` 版本: ${framework.version}`);
|
|
184
|
+
if (framework.configFile)
|
|
185
|
+
console.log(` 配置: ${framework.configFile}`);
|
|
186
|
+
console.log(` 支持类型: ${framework.supportedTypes.join(', ')}`);
|
|
187
|
+
}
|
|
188
|
+
console.log('\n📁 文件统计:');
|
|
189
|
+
console.log(` 现有测试: ${result.existingTests.length} 个`);
|
|
190
|
+
console.log(` 未覆盖源: ${result.uncoveredSources.length} 个`);
|
|
191
|
+
if (result.coverageReport) {
|
|
192
|
+
console.log(`\n📈 覆盖率: ${result.coverageReport.total}%`);
|
|
193
|
+
}
|
|
194
|
+
if (result.testStyle) {
|
|
195
|
+
console.log('\n📝 测试风格:');
|
|
196
|
+
console.log(` 命名约定: ${result.testStyle.namingConvention}`);
|
|
197
|
+
console.log(` 断言库: ${result.testStyle.assertionLibrary}`);
|
|
198
|
+
console.log(` TypeScript: ${result.testStyle.usesTypeScript ? '是' : '否'}`);
|
|
199
|
+
}
|
|
200
|
+
if (result.uncoveredSources.length > 0) {
|
|
201
|
+
console.log('\n⚠️ 未覆盖的源文件:');
|
|
202
|
+
for (const source of result.uncoveredSources.slice(0, 10)) {
|
|
203
|
+
console.log(` - ${source.path} (${source.fileType})`);
|
|
204
|
+
if (source.exports.length > 0) {
|
|
205
|
+
console.log(` 导出: ${source.exports.slice(0, 3).join(', ')}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (result.uncoveredSources.length > 10) {
|
|
209
|
+
console.log(` ... 还有 ${result.uncoveredSources.length - 10} 个文件`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
213
|
+
console.log('\n💡 下一步:');
|
|
214
|
+
console.log(' 使用 /om:test 进入测试生成流程');
|
|
215
|
+
console.log(' 或使用 openmatrix test --verify 运行测试验证');
|
|
216
|
+
});
|
package/dist/cli/index.js
CHANGED
|
@@ -54,6 +54,7 @@ const debug_js_1 = require("./commands/debug.js");
|
|
|
54
54
|
const complete_js_1 = require("./commands/complete.js");
|
|
55
55
|
const step_js_1 = require("./commands/step.js");
|
|
56
56
|
const deploy_js_1 = require("./commands/deploy.js");
|
|
57
|
+
const test_js_1 = require("./commands/test.js");
|
|
57
58
|
// 读取 package.json 版本
|
|
58
59
|
let version = '0.0.0';
|
|
59
60
|
try {
|
|
@@ -87,5 +88,6 @@ program.addCommand(brainstorm_js_1.brainstormCommand);
|
|
|
87
88
|
program.addCommand(research_js_1.researchCommand);
|
|
88
89
|
program.addCommand(debug_js_1.debugCommand);
|
|
89
90
|
program.addCommand(deploy_js_1.deployCommand);
|
|
91
|
+
program.addCommand(test_js_1.testCommand);
|
|
90
92
|
// 默认帮助
|
|
91
93
|
program.parse();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { TestFrameworkInfo, TestType, ExistingTestFile, UncoveredSourceFile, ProjectType, TestStyle, FrontendInfo, CoverageReport } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 检测测试框架
|
|
4
|
+
*/
|
|
5
|
+
export declare function detectTestFrameworks(projectRoot: string): TestFrameworkInfo[];
|
|
6
|
+
/**
|
|
7
|
+
* 检测项目类型
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectProjectType(projectRoot: string): ProjectType;
|
|
10
|
+
/**
|
|
11
|
+
* 扫描测试文件
|
|
12
|
+
*/
|
|
13
|
+
export declare function scanTestFiles(projectRoot: string, testDirs: string[]): ExistingTestFile[];
|
|
14
|
+
/**
|
|
15
|
+
* 扫描源文件
|
|
16
|
+
*/
|
|
17
|
+
export declare function scanSourceFiles(projectRoot: string, sourceDirs: string[], existingTests: ExistingTestFile[]): UncoveredSourceFile[];
|
|
18
|
+
/**
|
|
19
|
+
* 递归扫描目录
|
|
20
|
+
*/
|
|
21
|
+
export declare function scanDirectory(dir: string, callback: (filePath: string) => void): void;
|
|
22
|
+
/**
|
|
23
|
+
* 推断关联的源文件
|
|
24
|
+
*/
|
|
25
|
+
export declare function inferSourceFile(testPath: string, projectRoot: string): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* 推断文件类型
|
|
28
|
+
*/
|
|
29
|
+
export declare function inferFileType(filePath: string): UncoveredSourceFile['fileType'];
|
|
30
|
+
/**
|
|
31
|
+
* 提取导出(简化版)
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractExports(filePath: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* 检查是否有对应的测试文件
|
|
36
|
+
*/
|
|
37
|
+
export declare function hasCorrespondingTest(sourcePath: string, existingTests: ExistingTestFile[]): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* 推断建议的测试类型
|
|
40
|
+
*/
|
|
41
|
+
export declare function inferTestTypes(fileType: UncoveredSourceFile['fileType'], filePath: string): TestType[];
|
|
42
|
+
/**
|
|
43
|
+
* 检测测试风格
|
|
44
|
+
*/
|
|
45
|
+
export declare function detectTestStyle(projectRoot: string, existingTests: ExistingTestFile[]): TestStyle | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* 检测是否有前端项目
|
|
48
|
+
*/
|
|
49
|
+
export declare function detectFrontend(projectRoot: string, projectType: ProjectType): FrontendInfo;
|
|
50
|
+
/**
|
|
51
|
+
* 检测覆盖率报告
|
|
52
|
+
*/
|
|
53
|
+
export declare function detectCoverageReport(projectRoot: string): CoverageReport | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* 执行完整扫描并返回结果
|
|
56
|
+
*/
|
|
57
|
+
export declare function performFullScan(projectRoot: string, target?: string): {
|
|
58
|
+
timestamp: string;
|
|
59
|
+
projectRoot: string;
|
|
60
|
+
target: string;
|
|
61
|
+
frameworks: TestFrameworkInfo[];
|
|
62
|
+
existingTests: ExistingTestFile[];
|
|
63
|
+
uncoveredSources: UncoveredSourceFile[];
|
|
64
|
+
projectType: ProjectType;
|
|
65
|
+
isFrontend: boolean;
|
|
66
|
+
hasUIComponents: boolean;
|
|
67
|
+
coverageReport: CoverageReport | undefined;
|
|
68
|
+
testStyle: TestStyle | undefined;
|
|
69
|
+
summary: {
|
|
70
|
+
frameworkCount: number;
|
|
71
|
+
existingTestCount: number;
|
|
72
|
+
uncoveredSourceCount: number;
|
|
73
|
+
hasTestConfig: boolean;
|
|
74
|
+
hasCoverageConfig: boolean;
|
|
75
|
+
};
|
|
76
|
+
};
|