dsh-flakefinder 0.2.1 → 0.3.0
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/LICENSE +21 -0
- package/README.en.md +15 -2
- package/README.md +17 -4
- package/cordis.patch.yml +1 -1
- package/lib/args.js +12 -2
- package/lib/classify.js +22 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/parse.js +25 -9
- package/lib/tools.js +54 -1
- package/package.json +60 -61
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 stardustlc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
CHANGED
|
@@ -18,7 +18,11 @@ A DeepSeek Harness plugin for test stability: run tests repeatedly, classify fla
|
|
|
18
18
|
| `flaky_quarantine` | Write flaky cases to `.flakefinder.json` | yes (approval) |
|
|
19
19
|
| `flaky_clear` | Remove recovered cases from the manifest | yes (approval) |
|
|
20
20
|
|
|
21
|
-
Frameworks: vitest / jest / pytest / node:test, auto-detected in that order.
|
|
21
|
+
Frameworks: vitest / jest / pytest (setup.cfg / pyproject / pytest.ini / tox.ini) / node:test, auto-detected in that order. JUnit XML entities and TAP SKIP directives are parsed.
|
|
22
|
+
|
|
23
|
+
## Compatibility
|
|
24
|
+
|
|
25
|
+
Verified against `@deepseek-ai/dsh@0.1.1-rc.2` on 2026-08-26. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
|
|
22
26
|
|
|
23
27
|
## Install
|
|
24
28
|
|
|
@@ -26,6 +30,15 @@ Frameworks: vitest / jest / pytest / node:test, auto-detected in that order.
|
|
|
26
30
|
dsh plugin --profile web add dsh-flakefinder
|
|
27
31
|
```
|
|
28
32
|
|
|
33
|
+
## Uninstall
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
dsh plugin --profile web remove dsh-flakefinder
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
|
|
40
|
+
|
|
41
|
+
|
|
29
42
|
## Example
|
|
30
43
|
|
|
31
44
|
```text
|
|
@@ -47,4 +60,4 @@ pnpm test
|
|
|
47
60
|
pnpm typecheck
|
|
48
61
|
```
|
|
49
62
|
|
|
50
|
-
MIT
|
|
63
|
+
MIT
|
package/README.md
CHANGED
|
@@ -22,10 +22,14 @@ DeepSeek Harness 测试稳定性插件:支持 vitest / jest / pytest / node:te
|
|
|
22
22
|
|
|
23
23
|
- vitest:读取 `node_modules/vitest/vitest.mjs`,使用 JSON reporter
|
|
24
24
|
- jest:读取 `node_modules/jest/bin/jest.js`,使用 `--json --outputFile`
|
|
25
|
-
- pytest:`python -m pytest --junitxml`,解析 JUnit XML
|
|
26
|
-
- node:test:`node --test --test-reporter=tap`,解析 TAP
|
|
25
|
+
- pytest:`python -m pytest --junitxml`,解析 JUnit XML(支持 setup.cfg / pyproject / pytest.ini / tox.ini 探测与 XML 实体解码)
|
|
26
|
+
- node:test:`node --test --test-reporter=tap`,解析 TAP(含 SKIP 指令归类)
|
|
27
27
|
- `framework: auto` 按 vitest → jest → pytest → node:test 自动探测
|
|
28
28
|
|
|
29
|
+
## 兼容性
|
|
30
|
+
|
|
31
|
+
在 `@deepseek-ai/dsh@0.1.1-rc.2` 上验证(2026-08-26)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
|
|
32
|
+
|
|
29
33
|
## 安装
|
|
30
34
|
|
|
31
35
|
```bash
|
|
@@ -42,6 +46,15 @@ dsh plugin --profile web add dsh-flakefinder
|
|
|
42
46
|
writeApproval: true
|
|
43
47
|
```
|
|
44
48
|
|
|
49
|
+
## 卸载
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
dsh plugin --profile web remove dsh-flakefinder
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
|
|
56
|
+
|
|
57
|
+
|
|
45
58
|
## 使用示例
|
|
46
59
|
|
|
47
60
|
```text
|
|
@@ -87,7 +100,7 @@ Agent:
|
|
|
87
100
|
|
|
88
101
|
- Node >= 22.13,TypeScript,零运行时依赖
|
|
89
102
|
- 测试进程走 DSH 官方 subprocess 服务,argv 数组、无 shell
|
|
90
|
-
- 全量单测
|
|
103
|
+
- 全量单测 40+:解析、判定、存储、审批门、pytest 计划、subprocess 超时、注册与 manifest
|
|
91
104
|
- `pnpm test`:构建 + `node --test`
|
|
92
105
|
|
|
93
106
|
## 发布门禁
|
|
@@ -100,4 +113,4 @@ Agent:
|
|
|
100
113
|
|
|
101
114
|
## License
|
|
102
115
|
|
|
103
|
-
MIT
|
|
116
|
+
MIT
|
package/cordis.patch.yml
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# writeApproval: true # quarantine/clear 是否走审批门(默认 true)
|
|
11
11
|
# dataDir: '' # 历史存储目录(默认 DSH_HOME/.dsh-flakefinder)
|
|
12
12
|
# quarantineFile: '' # 隔离清单(默认项目根 .flakefinder.json)
|
|
13
|
-
|
|
13
|
+
# pythonPath: '' # pytest 使用的 Python 解释器(默认 python/python3)
|
|
14
14
|
#
|
|
15
15
|
- insert:
|
|
16
16
|
- id: flakefinder
|
package/lib/args.js
CHANGED
|
@@ -25,10 +25,20 @@ function hasPytestConfig(cwd) {
|
|
|
25
25
|
if (fs.existsSync(path.join(cwd, 'pytest.ini')) || fs.existsSync(path.join(cwd, 'tox.ini')))
|
|
26
26
|
return true;
|
|
27
27
|
const pyproject = path.join(cwd, 'pyproject.toml');
|
|
28
|
-
if (
|
|
28
|
+
if (fs.existsSync(pyproject)) {
|
|
29
|
+
try {
|
|
30
|
+
if (/\[tool\.pytest(\.ini_options)?\]/.test(fs.readFileSync(pyproject, 'utf8')))
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// 读不了就当没有配置,继续探测 setup.cfg。
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const setupCfg = path.join(cwd, 'setup.cfg');
|
|
38
|
+
if (!fs.existsSync(setupCfg))
|
|
29
39
|
return false;
|
|
30
40
|
try {
|
|
31
|
-
return /\[tool
|
|
41
|
+
return /\[tool:pytest\]/.test(fs.readFileSync(setupCfg, 'utf8'));
|
|
32
42
|
}
|
|
33
43
|
catch {
|
|
34
44
|
return false;
|
package/lib/classify.js
CHANGED
|
@@ -71,7 +71,10 @@ export function aggregate(runs) {
|
|
|
71
71
|
}
|
|
72
72
|
let verdict;
|
|
73
73
|
const passRuns = records.filter(run => run.success).length;
|
|
74
|
-
if (tests.
|
|
74
|
+
if (tests.length === 0) {
|
|
75
|
+
verdict = 'empty';
|
|
76
|
+
}
|
|
77
|
+
else if (tests.every(test => test.verdict === 'skipped')) {
|
|
75
78
|
verdict = 'skipped';
|
|
76
79
|
}
|
|
77
80
|
else if (passRuns === records.length) {
|
|
@@ -83,6 +86,24 @@ export function aggregate(runs) {
|
|
|
83
86
|
else {
|
|
84
87
|
verdict = 'flaky';
|
|
85
88
|
}
|
|
89
|
+
// 运行级 flaky 但无具体 flaky 用例:通常是某次运行崩溃(report=null)未产出报告。
|
|
90
|
+
// 补一条合成记录,避免 flaky 信号丢失(历史写入 verdict=flaky 但 flakyCount=0)。
|
|
91
|
+
if (verdict === 'flaky' && flakyCount === 0) {
|
|
92
|
+
const crashRuns = runs.filter(run => run.report === null).length;
|
|
93
|
+
if (crashRuns > 0) {
|
|
94
|
+
tests.push({
|
|
95
|
+
id: '(crash)',
|
|
96
|
+
file: '(crash)',
|
|
97
|
+
name: '运行崩溃(未产出报告)',
|
|
98
|
+
passes: 0,
|
|
99
|
+
failures: crashRuns,
|
|
100
|
+
skips: 0,
|
|
101
|
+
failureRate: 100,
|
|
102
|
+
verdict: 'flaky',
|
|
103
|
+
});
|
|
104
|
+
flakyCount += 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
86
107
|
tests.sort((a, b) => a.id.localeCompare(b.id));
|
|
87
108
|
return {
|
|
88
109
|
runs: records,
|
package/lib/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { type FlakeConfig } from './config.js';
|
|
|
11
11
|
import { type SubprocessSpawnLike } from './runner.js';
|
|
12
12
|
import { type FlakeToolDefinition } from './tools.js';
|
|
13
13
|
/** cordis 服务注入:apply 里要用 ctx.subprocess 与 ctx.tools。 */
|
|
14
|
+
export declare const name = "flakefinder";
|
|
14
15
|
export declare const inject: string[];
|
|
15
16
|
/** 插件所需的最小 ctx 面。 */
|
|
16
17
|
export interface FlakePluginContext {
|
package/lib/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createSubprocessRunner } from './runner.js';
|
|
|
12
12
|
import { createStore } from './store.js';
|
|
13
13
|
import { buildFlakeTools } from './tools.js';
|
|
14
14
|
/** cordis 服务注入:apply 里要用 ctx.subprocess 与 ctx.tools。 */
|
|
15
|
+
export const name = 'flakefinder';
|
|
15
16
|
export const inject = ['subprocess', 'tools'];
|
|
16
17
|
/**
|
|
17
18
|
* 插件入口:解析配置、封装执行器与存储、注册五个工具。
|
package/lib/parse.js
CHANGED
|
@@ -73,9 +73,22 @@ function reportedCount(reported, fallback) {
|
|
|
73
73
|
return fallback;
|
|
74
74
|
}
|
|
75
75
|
function attrOf(tag, name) {
|
|
76
|
-
const m = new RegExp(name + '="([^"]*)"').exec(tag);
|
|
76
|
+
const m = new RegExp('(?:^|\\s)' + name + '="([^"]*)"').exec(tag);
|
|
77
77
|
return m === null ? '' : m[1];
|
|
78
78
|
}
|
|
79
|
+
/** 解码 XML 属性里的实体与数字字符引用。 */
|
|
80
|
+
function decodeXmlEntities(text) {
|
|
81
|
+
const named = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" };
|
|
82
|
+
return text.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (raw, body) => {
|
|
83
|
+
const key = body.toLowerCase();
|
|
84
|
+
if (key.startsWith('#')) {
|
|
85
|
+
const hex = key.startsWith('#x');
|
|
86
|
+
const code = parseInt(hex ? key.slice(2) : key.slice(1), hex ? 16 : 10);
|
|
87
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : raw;
|
|
88
|
+
}
|
|
89
|
+
return named[key] ?? raw;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
79
92
|
/** 解析 pytest 的 JUnit XML 报告。 */
|
|
80
93
|
export function parseJunitReport(text) {
|
|
81
94
|
const tests = [];
|
|
@@ -84,15 +97,15 @@ export function parseJunitReport(text) {
|
|
|
84
97
|
for (const m of text.matchAll(block)) {
|
|
85
98
|
const tag = m[1];
|
|
86
99
|
const body = m[2];
|
|
87
|
-
const classname = attrOf(tag, 'classname') || '(pytest)';
|
|
88
|
-
const name = attrOf(tag, 'name') || '(unnamed)';
|
|
100
|
+
const classname = decodeXmlEntities(attrOf(tag, 'classname')) || '(pytest)';
|
|
101
|
+
const name = decodeXmlEntities(attrOf(tag, 'name')) || '(unnamed)';
|
|
89
102
|
const status = /<(?:failure|error)\b/.test(body) ? 'failed' : /<skipped\b/.test(body) ? 'skipped' : 'passed';
|
|
90
103
|
tests.push({ id: classname + ' > ' + name, file: classname, name, status, durationMs: Math.max(0, Number(attrOf(tag, 'time')) || 0) });
|
|
91
104
|
}
|
|
92
105
|
for (const m of text.matchAll(single)) {
|
|
93
106
|
const tag = m[1];
|
|
94
|
-
const classname = attrOf(tag, 'classname') || '(pytest)';
|
|
95
|
-
const name = attrOf(tag, 'name') || '(unnamed)';
|
|
107
|
+
const classname = decodeXmlEntities(attrOf(tag, 'classname')) || '(pytest)';
|
|
108
|
+
const name = decodeXmlEntities(attrOf(tag, 'name')) || '(unnamed)';
|
|
96
109
|
tests.push({ id: classname + ' > ' + name, file: classname, name, status: 'passed', durationMs: Math.max(0, Number(attrOf(tag, 'time')) || 0) });
|
|
97
110
|
}
|
|
98
111
|
const failed = tests.filter(item => item.status === 'failed').length;
|
|
@@ -110,23 +123,26 @@ export function parseTapReport(text) {
|
|
|
110
123
|
continue;
|
|
111
124
|
const failed = match[1] === 'not ok';
|
|
112
125
|
index += 1;
|
|
113
|
-
const
|
|
126
|
+
const rawName = match[3] ?? '(unnamed)';
|
|
127
|
+
const skipped = /#\s*SKIP\b/i.test(rawName);
|
|
128
|
+
const name = rawName.replace(/\s+#.*$/, '');
|
|
114
129
|
tests.push({
|
|
115
130
|
id: 'node:' + String(index) + ' > ' + name,
|
|
116
131
|
file: '(node:test)',
|
|
117
132
|
name,
|
|
118
|
-
status: failed ? 'failed' : 'passed',
|
|
133
|
+
status: skipped ? 'skipped' : failed ? 'failed' : 'passed',
|
|
119
134
|
durationMs: 0,
|
|
120
135
|
});
|
|
121
136
|
}
|
|
122
137
|
const failed = tests.filter(item => item.status === 'failed').length;
|
|
138
|
+
const skipped = tests.filter(item => item.status === 'skipped').length;
|
|
123
139
|
return {
|
|
124
140
|
framework: 'node',
|
|
125
141
|
success: failed === 0 && tests.length > 0,
|
|
126
142
|
total: tests.length,
|
|
127
|
-
passed: tests.length - failed,
|
|
143
|
+
passed: tests.length - failed - skipped,
|
|
128
144
|
failed,
|
|
129
|
-
skipped
|
|
145
|
+
skipped,
|
|
130
146
|
tests,
|
|
131
147
|
};
|
|
132
148
|
}
|
package/lib/tools.js
CHANGED
|
@@ -301,7 +301,60 @@ export function buildFlakeTools(cfg, runner, store) {
|
|
|
301
301
|
quarantine.gate = approvalGate('flaky_quarantine', '把测试用例写入 .flakefinder.json 隔离清单');
|
|
302
302
|
clear.gate = approvalGate('flaky_clear', '从 .flakefinder.json 隔离清单移除测试用例');
|
|
303
303
|
}
|
|
304
|
-
|
|
304
|
+
const flakyHealth = {
|
|
305
|
+
name: 'flaky_health',
|
|
306
|
+
description: 'dsh-flakefinder 自检:验证 Python 解释器与 pytest 是否可用(pytest 框架必需),并汇总默认配置。遇到问题时先运行本工具定位。',
|
|
307
|
+
parameters: compileParameters({}),
|
|
308
|
+
output: {
|
|
309
|
+
schema: { type: 'object', additionalProperties: true },
|
|
310
|
+
render: (_args, value) => {
|
|
311
|
+
const rec = asRecord(value);
|
|
312
|
+
const checks = Array.isArray(rec.checks) ? rec.checks : [];
|
|
313
|
+
const lines = ['dsh-flakefinder 自检' + (rec.ok === true ? ':正常。' : ':发现问题。')];
|
|
314
|
+
for (const item of checks) {
|
|
315
|
+
const c = asRecord(item);
|
|
316
|
+
lines.push('- ' + c.name + ':' + (c.ok === true ? '✅' : '❌ ' + String(c.detail ?? '')));
|
|
317
|
+
}
|
|
318
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
async execute() {
|
|
322
|
+
const checks = [];
|
|
323
|
+
let ok = true;
|
|
324
|
+
try {
|
|
325
|
+
const result = await runner.run([cfg.pythonPath, '--version'], { timeoutMs: 15000 });
|
|
326
|
+
const version = (result.stdout + result.stderr).split(/\r?\n/)[0]?.trim() ?? '';
|
|
327
|
+
if (result.exitCode === 0)
|
|
328
|
+
checks.push({ name: 'python', ok: true, detail: version + '(' + cfg.pythonPath + ')' });
|
|
329
|
+
else {
|
|
330
|
+
ok = false;
|
|
331
|
+
checks.push({ name: 'python', ok: false, detail: '退出码 ' + String(result.exitCode) });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
ok = false;
|
|
336
|
+
checks.push({ name: 'python', ok: false, detail: error instanceof Error ? error.message : String(error) });
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const result = await runner.run([cfg.pythonPath, '-m', 'pytest', '--version'], { timeoutMs: 20000 });
|
|
340
|
+
const version = (result.stdout + result.stderr).split(/\r?\n/)[0]?.trim() ?? '';
|
|
341
|
+
if (result.exitCode === 0)
|
|
342
|
+
checks.push({ name: 'pytest', ok: true, detail: version });
|
|
343
|
+
else {
|
|
344
|
+
ok = false;
|
|
345
|
+
checks.push({ name: 'pytest', ok: false, detail: '未安装或不可用(pip install pytest)' });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
ok = false;
|
|
350
|
+
checks.push({ name: 'pytest', ok: false, detail: error instanceof Error ? error.message : String(error) });
|
|
351
|
+
}
|
|
352
|
+
checks.push({ name: '默认配置', ok: true, detail: 'defaultRuns=' + cfg.defaultRuns + ', timeoutMs=' + cfg.timeoutMs });
|
|
353
|
+
return { ok, plugin: 'dsh-flakefinder', checks };
|
|
354
|
+
},
|
|
355
|
+
timeoutMs: 40000,
|
|
356
|
+
};
|
|
357
|
+
return [detect, history, quarantine, clear, report, flakyHealth];
|
|
305
358
|
}
|
|
306
359
|
function runsTimeout(cfg) {
|
|
307
360
|
return Math.min(10 * 60 * 1000, cfg.maxRuns * cfg.timeoutMs + 15000);
|
package/package.json
CHANGED
|
@@ -1,61 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-flakefinder",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DSH 测试稳定性插件:flaky_detect/history/quarantine/clear/report 五工具,支持 vitest/jest/pytest/node:test,重复运行测试并识别不稳定用例,JSON 历史与隔离清单,零运行时依赖。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"types": "lib/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./lib/index.d.ts",
|
|
11
|
-
"default": "./lib/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
14
|
-
"./package.json": "./package.json"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"lib",
|
|
18
|
-
"cordis.patch.yml",
|
|
19
|
-
"README.md",
|
|
20
|
-
"README.en.md"
|
|
21
|
-
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "tsc -p tsconfig.json",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-flakefinder",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "DSH 测试稳定性插件:flaky_detect/history/quarantine/clear/report 五工具,支持 vitest/jest/pytest/node:test,重复运行测试并识别不稳定用例,JUnit/TAP 完整解析,JSON 历史与隔离清单,零运行时依赖。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"lib",
|
|
18
|
+
"cordis.patch.yml",
|
|
19
|
+
"README.md",
|
|
20
|
+
"README.en.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
25
|
+
"test": "pnpm run build && node --test \"test/*.test.mjs\"",
|
|
26
|
+
"prepublishOnly": "pnpm run build"
|
|
27
|
+
},
|
|
28
|
+
"dsh": {
|
|
29
|
+
"bundle": {
|
|
30
|
+
"patch": "./cordis.patch.yml"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"dsh",
|
|
35
|
+
"deepseek-harness",
|
|
36
|
+
"plugin",
|
|
37
|
+
"testing",
|
|
38
|
+
"flaky-test",
|
|
39
|
+
"quality",
|
|
40
|
+
"dsh-plugin"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^24.0.0",
|
|
48
|
+
"typescript": "^5.6.0"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/STARDUSTLC666/dsh-flakefinder.git"
|
|
53
|
+
},
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/STARDUSTLC666/dsh-flakefinder/issues"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/STARDUSTLC666/dsh-flakefinder#readme",
|
|
58
|
+
"author": "stardustlc",
|
|
59
|
+
"packageManager": "pnpm@11.7.0"
|
|
60
|
+
}
|