dsh-flakefinder 0.3.0 → 0.3.2
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/README.en.md +2 -2
- package/README.md +2 -2
- package/lib/index.d.ts +4 -5
- package/lib/index.js +10 -9
- package/lib/tools.d.ts +0 -1
- package/lib/tools.js +2 -34
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -22,7 +22,7 @@ Frameworks: vitest / jest / pytest (setup.cfg / pyproject / pytest.ini / tox.ini
|
|
|
22
22
|
|
|
23
23
|
## Compatibility
|
|
24
24
|
|
|
25
|
-
Verified against `@deepseek-ai/dsh@0.1.
|
|
25
|
+
Verified against source-run `@deepseek-ai/dsh@0.1.2-alpha.4` on 2026-09-02. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
|
|
26
26
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
@@ -60,4 +60,4 @@ pnpm test
|
|
|
60
60
|
pnpm typecheck
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
MIT
|
|
63
|
+
MIT
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ DeepSeek Harness 测试稳定性插件:支持 vitest / jest / pytest / node:te
|
|
|
28
28
|
|
|
29
29
|
## 兼容性
|
|
30
30
|
|
|
31
|
-
在 `@deepseek-ai/dsh@0.1.
|
|
31
|
+
在 `@deepseek-ai/dsh@0.1.2-alpha.4` 源码模式下验证(2026-09-02)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
|
|
32
32
|
|
|
33
33
|
## 安装
|
|
34
34
|
|
|
@@ -113,4 +113,4 @@ Agent:
|
|
|
113
113
|
|
|
114
114
|
## License
|
|
115
115
|
|
|
116
|
-
MIT
|
|
116
|
+
MIT
|
package/lib/index.d.ts
CHANGED
|
@@ -19,12 +19,11 @@ export interface FlakePluginContext {
|
|
|
19
19
|
spawn: SubprocessSpawnLike;
|
|
20
20
|
};
|
|
21
21
|
tools: {
|
|
22
|
-
register(definition: FlakeToolDefinition
|
|
23
|
-
prepend?: boolean;
|
|
24
|
-
}): () => void;
|
|
22
|
+
register(definition: FlakeToolDefinition): () => void;
|
|
25
23
|
};
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
on?(event: string, listener: (...args: any[]) => unknown, options?: {
|
|
25
|
+
prepend?: boolean;
|
|
26
|
+
}): (() => void) | void;
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
30
29
|
* 插件入口:解析配置、封装执行器与存储、注册五个工具。
|
package/lib/index.js
CHANGED
|
@@ -31,17 +31,18 @@ export function apply(ctx, config) {
|
|
|
31
31
|
const tools = buildFlakeTools(cfg, runner, store);
|
|
32
32
|
const disposers = [];
|
|
33
33
|
for (const definition of tools) {
|
|
34
|
-
|
|
35
|
-
if (wrapped.gate !== undefined) {
|
|
36
|
-
const original = wrapped.gate.bind(wrapped);
|
|
37
|
-
wrapped.gate = async (exec, next) => {
|
|
38
|
-
const record = (typeof exec === 'object' && exec !== null ? exec : {});
|
|
39
|
-
return original({ ...record, approval: ctx.get?.('approval') }, next);
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
disposers.push(ctx.tools.register(wrapped, { prepend: true }));
|
|
34
|
+
disposers.push(ctx.tools.register(definition));
|
|
43
35
|
}
|
|
44
36
|
if (typeof ctx.on === 'function') {
|
|
37
|
+
if (cfg.writeApproval) {
|
|
38
|
+
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
39
|
+
if (exec.name === 'flaky_quarantine')
|
|
40
|
+
return { kind: 'ask', reason: '把测试用例写入 .flakefinder.json 隔离清单' };
|
|
41
|
+
if (exec.name === 'flaky_clear')
|
|
42
|
+
return { kind: 'ask', reason: '从 .flakefinder.json 隔离清单移除测试用例' };
|
|
43
|
+
return next();
|
|
44
|
+
}, { prepend: true });
|
|
45
|
+
}
|
|
45
46
|
ctx.on('dispose', () => {
|
|
46
47
|
for (const dispose of disposers)
|
|
47
48
|
dispose();
|
package/lib/tools.d.ts
CHANGED
|
@@ -24,7 +24,6 @@ export interface FlakeToolDefinition {
|
|
|
24
24
|
render(args: unknown, value: unknown): ContentBlock[];
|
|
25
25
|
};
|
|
26
26
|
execute(args: unknown, exec: unknown): Promise<unknown>;
|
|
27
|
-
gate?(exec: unknown, next: () => Promise<unknown>): Promise<unknown>;
|
|
28
27
|
timeoutMs?: number;
|
|
29
28
|
}
|
|
30
29
|
/** 构建五个工具定义。 */
|
package/lib/tools.js
CHANGED
|
@@ -22,10 +22,6 @@ function compileParameters(spec) {
|
|
|
22
22
|
node.description = prop.description;
|
|
23
23
|
if (typeof prop?.items === 'string')
|
|
24
24
|
node.items = { type: prop.items };
|
|
25
|
-
if (prop?.minItems !== undefined)
|
|
26
|
-
node.minItems = prop.minItems;
|
|
27
|
-
if (prop?.maxItems !== undefined)
|
|
28
|
-
node.maxItems = prop.maxItems;
|
|
29
25
|
properties[key] = node;
|
|
30
26
|
}
|
|
31
27
|
return { type: 'object', properties, ...(required.length > 0 ? { required } : {}) };
|
|
@@ -200,7 +196,7 @@ export function buildFlakeTools(cfg, runner, store) {
|
|
|
200
196
|
name: 'flaky_quarantine',
|
|
201
197
|
description: '把确认的 flaky 用例写入项目根 .flakefinder.json 隔离清单(不修改测试源码)。需要 flaky_detect 判定为 flaky 后再使用。',
|
|
202
198
|
parameters: compileParameters({
|
|
203
|
-
tests: { type: 'array', items: 'string',
|
|
199
|
+
tests: { type: 'array', items: 'string', required: true, description: '1-100 条测试引用:文件路径,或 "文件路径 > 用例名"。' },
|
|
204
200
|
reason: { type: 'string', required: true, description: '隔离原因(会写进清单,例如:定时器竞态,见 issue #12)。' },
|
|
205
201
|
}),
|
|
206
202
|
output: {
|
|
@@ -227,7 +223,7 @@ export function buildFlakeTools(cfg, runner, store) {
|
|
|
227
223
|
name: 'flaky_clear',
|
|
228
224
|
description: '从隔离清单移除已经恢复稳定的用例。建议先 flaky_detect 验证多轮全部通过后再清除。',
|
|
229
225
|
parameters: compileParameters({
|
|
230
|
-
tests: { type: 'array', items: 'string',
|
|
226
|
+
tests: { type: 'array', items: 'string', required: true, description: '1-100 条测试引用:文件路径,或 "文件路径 > 用例名"。' },
|
|
231
227
|
}),
|
|
232
228
|
output: {
|
|
233
229
|
schema: {
|
|
@@ -297,10 +293,6 @@ export function buildFlakeTools(cfg, runner, store) {
|
|
|
297
293
|
},
|
|
298
294
|
timeoutMs: 10000,
|
|
299
295
|
};
|
|
300
|
-
if (cfg.writeApproval) {
|
|
301
|
-
quarantine.gate = approvalGate('flaky_quarantine', '把测试用例写入 .flakefinder.json 隔离清单');
|
|
302
|
-
clear.gate = approvalGate('flaky_clear', '从 .flakefinder.json 隔离清单移除测试用例');
|
|
303
|
-
}
|
|
304
296
|
const flakyHealth = {
|
|
305
297
|
name: 'flaky_health',
|
|
306
298
|
description: 'dsh-flakefinder 自检:验证 Python 解释器与 pytest 是否可用(pytest 框架必需),并汇总默认配置。遇到问题时先运行本工具定位。',
|
|
@@ -359,27 +351,3 @@ export function buildFlakeTools(cfg, runner, store) {
|
|
|
359
351
|
function runsTimeout(cfg) {
|
|
360
352
|
return Math.min(10 * 60 * 1000, cfg.maxRuns * cfg.timeoutMs + 15000);
|
|
361
353
|
}
|
|
362
|
-
/** 写操作审批门:复用 dsh-docker 的中文拒绝语义。 */
|
|
363
|
-
function approvalGate(toolName, action) {
|
|
364
|
-
return async (exec, next) => {
|
|
365
|
-
const record = (typeof exec === 'object' && exec !== null ? exec : {});
|
|
366
|
-
const approval = record.approval;
|
|
367
|
-
if (approval === undefined) {
|
|
368
|
-
return { kind: 'deny', reason: toolName + ' 需要确认,但当前环境没有审批通道(如 headless)。如确定安全,可在配置中设置 writeApproval: false。' };
|
|
369
|
-
}
|
|
370
|
-
const outcome = await approval.request({
|
|
371
|
-
agent: record.agent,
|
|
372
|
-
toolName: record.toolName,
|
|
373
|
-
callId: record.callId,
|
|
374
|
-
reason: action,
|
|
375
|
-
signal: record.signal,
|
|
376
|
-
});
|
|
377
|
-
if (outcome === 'allowed-once')
|
|
378
|
-
return next();
|
|
379
|
-
if (outcome === 'cancelled')
|
|
380
|
-
return { kind: 'deny', reason: action + ' 被取消,未执行。' };
|
|
381
|
-
if (outcome === 'unavailable')
|
|
382
|
-
return { kind: 'deny', reason: action + ' 不可用(没有可用的审批界面),未执行。' };
|
|
383
|
-
return { kind: 'deny', reason: action + ' 未获批准:要么你拒绝了,要么当前会话处于 Full Access。若确需直接写入,可设置 writeApproval: false(自行承担风险)。' };
|
|
384
|
-
};
|
|
385
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-flakefinder",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "DSH 测试稳定性插件:flaky_detect/history/quarantine/clear/report 五工具,支持 vitest/jest/pytest/node:test,重复运行测试并识别不稳定用例,JUnit/TAP 完整解析,JSON 历史与隔离清单,零运行时依赖。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|