@dommaker/harness 1.8.1 → 1.10.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/CHANGELOG.md +16 -0
- package/README.md +10 -13
- package/dist/cli/commands/check.d.ts +3 -3
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +41 -78
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/constraints-report.d.ts +2 -10
- package/dist/cli/commands/constraints-report.d.ts.map +1 -1
- package/dist/cli/commands/constraints-report.js +4 -57
- package/dist/cli/commands/constraints-report.js.map +1 -1
- package/dist/cli/commands/constraints-retire.d.ts +9 -23
- package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
- package/dist/cli/commands/constraints-retire.js +39 -121
- package/dist/cli/commands/constraints-retire.js.map +1 -1
- package/dist/cli/commands/constraints.d.ts +3 -8
- package/dist/cli/commands/constraints.d.ts.map +1 -1
- package/dist/cli/commands/constraints.js +8 -23
- package/dist/cli/commands/constraints.js.map +1 -1
- package/dist/cli/commands/definitions.js +2 -2
- package/dist/cli/commands/init.d.ts +0 -32
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +7 -149
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +14 -17
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/scaffold-templates.d.ts +0 -2
- package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
- package/dist/cli/commands/scaffold-templates.js +2 -37
- package/dist/cli/commands/scaffold-templates.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +0 -2
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +0 -10
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/commands/status.js +10 -10
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.d.ts +0 -2
- package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.js +4 -23
- package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
- package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts +22 -0
- package/dist/cli/commands/sync-docs/capabilities-syncer.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/capabilities-syncer.js +113 -8
- package/dist/cli/commands/sync-docs/capabilities-syncer.js.map +1 -1
- package/dist/cli/commands/sync-docs/index.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/index.js +36 -4
- package/dist/cli/commands/sync-docs/index.js.map +1 -1
- package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
- package/dist/core/constraints/capabilities-parser.js +16 -4
- package/dist/core/constraints/capabilities-parser.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +15 -24
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +33 -72
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
- package/dist/core/constraints/checkers/governance-presence.d.ts +2 -1
- package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
- package/dist/core/constraints/checkers/governance-presence.js +4 -3
- package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
- package/dist/core/constraints/checkers/index.d.ts +1 -1
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +4 -8
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.d.ts +3 -4
- package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.js +4 -5
- package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
- package/dist/core/constraints/checkers/no-test-simplification.js +1 -1
- package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
- package/dist/core/constraints/checkers/types.d.ts +1 -1
- package/dist/core/constraints/checkers/types.js +1 -1
- package/dist/core/constraints/definitions/guidelines.d.ts +4 -4
- package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
- package/dist/core/constraints/definitions/guidelines.js +9 -25
- package/dist/core/constraints/definitions/guidelines.js.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.d.ts +4 -4
- package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.js +10 -81
- package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
- package/dist/core/constraints/definitions.d.ts +7 -9
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +13 -16
- package/dist/core/constraints/definitions.js.map +1 -1
- package/dist/core/constraints/index.d.ts +2 -5
- package/dist/core/constraints/index.d.ts.map +1 -1
- package/dist/core/constraints/index.js +3 -13
- package/dist/core/constraints/index.js.map +1 -1
- package/dist/core/constraints/injection-writer.d.ts +10 -38
- package/dist/core/constraints/injection-writer.d.ts.map +1 -1
- package/dist/core/constraints/injection-writer.js +17 -54
- package/dist/core/constraints/injection-writer.js.map +1 -1
- package/dist/core/constraints/run-env.d.ts +1 -10
- package/dist/core/constraints/run-env.d.ts.map +1 -1
- package/dist/core/constraints/run-env.js +1 -22
- package/dist/core/constraints/run-env.js.map +1 -1
- package/dist/core/constraints/usage-report.d.ts +5 -7
- package/dist/core/constraints/usage-report.d.ts.map +1 -1
- package/dist/core/constraints/usage-report.js +5 -10
- package/dist/core/constraints/usage-report.js.map +1 -1
- package/dist/core/effective-constraints.d.ts +12 -22
- package/dist/core/effective-constraints.d.ts.map +1 -1
- package/dist/core/effective-constraints.js +13 -30
- package/dist/core/effective-constraints.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -9
- package/dist/core/index.js.map +1 -1
- package/dist/core/project-config-loader.d.ts +4 -42
- package/dist/core/project-config-loader.d.ts.map +1 -1
- package/dist/core/project-config-loader.js +16 -125
- package/dist/core/project-config-loader.js.map +1 -1
- package/dist/core/validators/passes-gate.d.ts +2 -2
- package/dist/core/validators/passes-gate.js +6 -6
- package/dist/core/validators/passes-gate.js.map +1 -1
- package/dist/hooks/bootstrap.d.ts +9 -12
- package/dist/hooks/bootstrap.d.ts.map +1 -1
- package/dist/hooks/bootstrap.js +9 -31
- package/dist/hooks/bootstrap.js.map +1 -1
- package/dist/hooks/index.d.ts +5 -7
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +4 -10
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +9 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -23
- package/dist/index.js.map +1 -1
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +11 -12
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/monitoring/traces.d.ts +4 -4
- package/dist/monitoring/traces.d.ts.map +1 -1
- package/dist/monitoring/traces.js +8 -8
- package/dist/monitoring/traces.js.map +1 -1
- package/dist/presets/standard.d.ts +8 -10
- package/dist/presets/standard.d.ts.map +1 -1
- package/dist/presets/standard.js +10 -16
- package/dist/presets/standard.js.map +1 -1
- package/dist/types/constraint.d.ts +24 -35
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js +8 -7
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/passes-gate.d.ts +5 -5
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/dist/types/project-config.d.ts +8 -66
- package/dist/types/project-config.d.ts.map +1 -1
- package/dist/types/project-config.js +3 -2
- package/dist/types/project-config.js.map +1 -1
- package/dist/types/trace.d.ts +8 -10
- package/dist/types/trace.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/CONTEXT.md +3 -3
- package/src/__tests__/checker-extra.test.ts +37 -61
- package/src/__tests__/checker.test.ts +10 -115
- package/src/__tests__/effective-constraints.test.ts +18 -83
- package/src/__tests__/iron-laws.test.ts +71 -90
- package/src/__tests__/passes-gate-check.test.ts +2 -2
- package/src/__tests__/project-config-loader-extra.test.ts +22 -123
- package/src/__tests__/project-config-loader.test.ts +5 -33
- package/src/__tests__/public-exports.test.ts +31 -11
- package/src/__tests__/public-type-surface.test.ts +2 -13
- package/src/__tests__/public-value-surface.test.ts +1 -7
- package/src/__tests__/skip-semantics.test.ts +21 -37
- package/src/__tests__/trace-analyzer-extra.test.ts +14 -14
- package/src/__tests__/trace-analyzer.test.ts +21 -21
- package/src/__tests__/trace-collector.test.ts +22 -22
- package/src/__tests__/traces.test.ts +27 -28
- package/src/cli/commands/CONTEXT.md +12 -9
- package/src/cli/commands/__tests__/check-read-count.test.ts +4 -15
- package/src/cli/commands/__tests__/check-skip-output.test.ts +14 -14
- package/src/cli/commands/__tests__/check.test.ts +14 -31
- package/src/cli/commands/__tests__/constraints-report.test.ts +12 -80
- package/src/cli/commands/__tests__/constraints-retire.test.ts +42 -231
- package/src/cli/commands/__tests__/init-governance-io.test.ts +12 -58
- package/src/cli/commands/__tests__/init-ondisk.test.ts +12 -20
- package/src/cli/commands/__tests__/init.test.ts +0 -11
- package/src/cli/commands/__tests__/project-path-anchoring.test.ts +2 -2
- package/src/cli/commands/__tests__/registry.test.ts +39 -3
- package/src/cli/commands/__tests__/report-extra.test.ts +4 -4
- package/src/cli/commands/__tests__/report.test.ts +1 -1
- package/src/cli/commands/__tests__/scaffold.test.ts +4 -14
- package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +3 -3
- package/src/cli/commands/__tests__/status-extra.test.ts +14 -14
- package/src/cli/commands/__tests__/status.test.ts +12 -12
- package/src/cli/commands/__tests__/sync-docs-agents.test.ts +1 -1
- package/src/cli/commands/__tests__/sync-docs-table-layout.test.ts +259 -0
- package/src/cli/commands/check.ts +43 -82
- package/src/cli/commands/constraints-report.ts +4 -59
- package/src/cli/commands/constraints-retire.ts +49 -154
- package/src/cli/commands/constraints.ts +11 -26
- package/src/cli/commands/definitions.ts +2 -2
- package/src/cli/commands/init.ts +8 -179
- package/src/cli/commands/report.ts +19 -23
- package/src/cli/commands/scaffold-templates.ts +1 -37
- package/src/cli/commands/scaffold.ts +0 -11
- package/src/cli/commands/status.ts +10 -10
- package/src/cli/commands/sync-docs/agents-syncer.ts +4 -25
- package/src/cli/commands/sync-docs/capabilities-syncer.ts +139 -9
- package/src/cli/commands/sync-docs/index.ts +41 -4
- package/src/core/CONTEXT.md +9 -9
- package/src/core/constraints/__tests__/capability-counts.test.ts +5 -5
- package/src/core/constraints/__tests__/collect-constraints.test.ts +13 -8
- package/src/core/constraints/__tests__/git-evidence.test.ts +4 -7
- package/src/core/constraints/__tests__/injection-writer.test.ts +8 -61
- package/src/core/constraints/__tests__/run-env.test.ts +5 -34
- package/src/core/constraints/__tests__/trace-injection.test.ts +1 -1
- package/src/core/constraints/capabilities-parser.ts +17 -5
- package/src/core/constraints/checker.ts +39 -88
- package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +5 -8
- package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +1 -53
- package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +5 -25
- package/src/core/constraints/checkers/docs-freshness.ts +1 -1
- package/src/core/constraints/checkers/governance-presence.ts +4 -3
- package/src/core/constraints/checkers/index.ts +4 -10
- package/src/core/constraints/checkers/iron-flags.ts +3 -12
- package/src/core/constraints/checkers/no-test-simplification.ts +1 -1
- package/src/core/constraints/checkers/types.ts +1 -1
- package/src/core/constraints/definitions/guidelines.ts +8 -25
- package/src/core/constraints/definitions/iron-laws.ts +9 -82
- package/src/core/constraints/definitions.ts +14 -15
- package/src/core/constraints/index.ts +4 -19
- package/src/core/constraints/injection-writer.ts +16 -61
- package/src/core/constraints/run-env.ts +1 -33
- package/src/core/constraints/usage-report.ts +8 -15
- package/src/core/effective-constraints.ts +14 -45
- package/src/core/index.ts +3 -10
- package/src/core/project-config-loader.ts +20 -145
- package/src/core/validators/passes-gate.ts +6 -6
- package/src/failure/CONTEXT.md +1 -1
- package/src/hooks/CONTEXT.md +12 -22
- package/src/hooks/__tests__/bootstrap.test.ts +24 -75
- package/src/hooks/bootstrap.ts +9 -48
- package/src/hooks/index.ts +5 -20
- package/src/index.ts +9 -31
- package/src/monitoring/__tests__/trace-file-anchoring.test.ts +1 -1
- package/src/monitoring/__tests__/traces.test.ts +22 -22
- package/src/monitoring/trace-analyzer.ts +12 -13
- package/src/monitoring/traces.ts +8 -8
- package/src/presets/__tests__/standard.test.ts +12 -16
- package/src/presets/standard.ts +14 -23
- package/src/types/__tests__/types.test.ts +3 -3
- package/src/types/constraint.ts +26 -40
- package/src/types/passes-gate.ts +5 -5
- package/src/types/project-config.ts +8 -77
- package/src/types/trace.ts +8 -11
- package/dist/core/constraints/agent-prompt-renderer.d.ts +0 -32
- package/dist/core/constraints/agent-prompt-renderer.d.ts.map +0 -1
- package/dist/core/constraints/agent-prompt-renderer.js +0 -55
- package/dist/core/constraints/agent-prompt-renderer.js.map +0 -1
- package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts +0 -6
- package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts.map +0 -1
- package/dist/core/constraints/checkers/no-bypass-checkpoint.js +0 -40
- package/dist/core/constraints/checkers/no-bypass-checkpoint.js.map +0 -1
- package/dist/core/constraints/definitions/prompts.d.ts +0 -12
- package/dist/core/constraints/definitions/prompts.d.ts.map +0 -1
- package/dist/core/constraints/definitions/prompts.js +0 -400
- package/dist/core/constraints/definitions/prompts.js.map +0 -1
- package/dist/core/constraints/injection-drift.d.ts +0 -61
- package/dist/core/constraints/injection-drift.d.ts.map +0 -1
- package/dist/core/constraints/injection-drift.js +0 -131
- package/dist/core/constraints/injection-drift.js.map +0 -1
- package/dist/core/constraints/injection-renderer.d.ts +0 -23
- package/dist/core/constraints/injection-renderer.d.ts.map +0 -1
- package/dist/core/constraints/injection-renderer.js +0 -44
- package/dist/core/constraints/injection-renderer.js.map +0 -1
- package/dist/hooks/config.d.ts +0 -30
- package/dist/hooks/config.d.ts.map +0 -1
- package/dist/hooks/config.js +0 -34
- package/dist/hooks/config.js.map +0 -1
- package/dist/hooks/pipeline.d.ts +0 -54
- package/dist/hooks/pipeline.d.ts.map +0 -1
- package/dist/hooks/pipeline.js +0 -173
- package/dist/hooks/pipeline.js.map +0 -1
- package/dist/hooks/registry.d.ts +0 -70
- package/dist/hooks/registry.d.ts.map +0 -1
- package/dist/hooks/registry.js +0 -141
- package/dist/hooks/registry.js.map +0 -1
- package/dist/hooks/types.d.ts +0 -113
- package/dist/hooks/types.d.ts.map +0 -1
- package/dist/hooks/types.js +0 -9
- package/dist/hooks/types.js.map +0 -1
- package/src/__tests__/hooks-pipeline.test.ts +0 -201
- package/src/cli/commands/__tests__/check-drift.test.ts +0 -158
- package/src/cli/commands/__tests__/init-injection.test.ts +0 -466
- package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +0 -129
- package/src/core/constraints/__tests__/injection-drift.test.ts +0 -247
- package/src/core/constraints/agent-prompt-renderer.ts +0 -67
- package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +0 -76
- package/src/core/constraints/checkers/no-bypass-checkpoint.ts +0 -40
- package/src/core/constraints/definitions/prompts.ts +0 -414
- package/src/core/constraints/injection-drift.ts +0 -142
- package/src/core/constraints/injection-renderer.ts +0 -44
- package/src/hooks/__tests__/config.test.ts +0 -19
- package/src/hooks/__tests__/pipeline.test.ts +0 -195
- package/src/hooks/__tests__/registry.test.ts +0 -115
- package/src/hooks/config.ts +0 -33
- package/src/hooks/pipeline.ts +0 -202
- package/src/hooks/registry.ts +0 -166
- package/src/hooks/types.ts +0 -120
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HookPipeline errorStrategy 语义测试(G7 映射语义确认)
|
|
3
|
-
*
|
|
4
|
-
* block/warn 行为是 studio safeCallHook 语义在管线侧的无损承载面:
|
|
5
|
-
* - 'block':hook 失败 → passed=false、blockedBy 记名、停止执行后续 hook
|
|
6
|
-
* - 'warn':hook 失败 → 记录警告、继续执行后续 hook、passed 不受影响
|
|
7
|
-
*
|
|
8
|
-
* #159 起 `enabled` / `errorStrategy` 的唯一声明点是 HookConfig:
|
|
9
|
-
* HookDefinition 不再携带这两个字段,有效值在注册环节由配置表填充。
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { HookPipeline } from '../pipeline';
|
|
13
|
-
import { HookRegistry } from '../registry';
|
|
14
|
-
import type { HookConfig, HookDefinition } from '../types';
|
|
15
|
-
|
|
16
|
-
function makeConfig(name: string, overrides: Partial<HookConfig> = {}): HookConfig {
|
|
17
|
-
return { name, enabled: true, errorStrategy: 'warn', ...overrides };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function makeRegistry(
|
|
21
|
-
entries: Array<{ hook: HookDefinition; config?: Partial<HookConfig> }>
|
|
22
|
-
): { registry: HookRegistry; pipeline: HookPipeline } {
|
|
23
|
-
const registry = new HookRegistry();
|
|
24
|
-
for (const { hook, config } of entries) {
|
|
25
|
-
registry.register(hook, makeConfig(hook.name, config));
|
|
26
|
-
}
|
|
27
|
-
return { registry, pipeline: new HookPipeline(registry) };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('HookPipeline errorStrategy 语义', () => {
|
|
31
|
-
it('block:失败阻断管线(passed=false、blockedBy 记名、停止后续 hook)', async () => {
|
|
32
|
-
const second = jest.fn().mockResolvedValue({ passed: true });
|
|
33
|
-
const { pipeline } = makeRegistry([
|
|
34
|
-
{ hook: { name: 'first', phase: 'before', execute: async () => ({ passed: false, error: 'boom' }) }, config: { errorStrategy: 'block' } },
|
|
35
|
-
{ hook: { name: 'second', phase: 'before', execute: second } },
|
|
36
|
-
]);
|
|
37
|
-
|
|
38
|
-
const result = await pipeline.run('before', {});
|
|
39
|
-
|
|
40
|
-
expect(result.passed).toBe(false);
|
|
41
|
-
expect(result.blockedBy).toEqual(['first']);
|
|
42
|
-
expect(second).not.toHaveBeenCalled();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('warn:失败记录警告并继续(passed 不受影响、后续 hook 照常执行)', async () => {
|
|
46
|
-
const second = jest.fn().mockResolvedValue({ passed: true });
|
|
47
|
-
const { pipeline } = makeRegistry([
|
|
48
|
-
{ hook: { name: 'first', phase: 'before', execute: async () => ({ passed: false, error: 'soft' }) }, config: { errorStrategy: 'warn' } },
|
|
49
|
-
{ hook: { name: 'second', phase: 'before', execute: second } },
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
const result = await pipeline.run('before', {});
|
|
53
|
-
|
|
54
|
-
expect(result.passed).toBe(true);
|
|
55
|
-
expect(result.warnings).toEqual(['first']);
|
|
56
|
-
expect(result.blockedBy).toEqual([]);
|
|
57
|
-
expect(second).toHaveBeenCalled();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('block 失败停在当前 phase,不改变其它 phase 的 hook', async () => {
|
|
61
|
-
const afterHook = jest.fn().mockResolvedValue({ passed: true });
|
|
62
|
-
const { pipeline } = makeRegistry([
|
|
63
|
-
{ hook: { name: 'blocker', phase: 'before', execute: async () => ({ passed: false }) }, config: { errorStrategy: 'block' } },
|
|
64
|
-
{ hook: { name: 'afterHook', phase: 'after', execute: afterHook } },
|
|
65
|
-
]);
|
|
66
|
-
|
|
67
|
-
const before = await pipeline.run('before', {});
|
|
68
|
-
expect(before.passed).toBe(false);
|
|
69
|
-
|
|
70
|
-
const after = await pipeline.run('after', {});
|
|
71
|
-
expect(after.passed).toBe(true);
|
|
72
|
-
expect(afterHook).toHaveBeenCalled();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('配置 enabled:false 的 hook 不进入管线执行(配置表是唯一声明点)', async () => {
|
|
76
|
-
const disabledHook = jest.fn().mockResolvedValue({ passed: true });
|
|
77
|
-
const enabledHook = jest.fn().mockResolvedValue({ passed: true });
|
|
78
|
-
const { pipeline } = makeRegistry([
|
|
79
|
-
{ hook: { name: 'disabled', phase: 'before', execute: disabledHook }, config: { enabled: false } },
|
|
80
|
-
{ hook: { name: 'enabled', phase: 'before', execute: enabledHook } },
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
|
-
const result = await pipeline.run('before', {});
|
|
84
|
-
|
|
85
|
-
expect(result.passed).toBe(true);
|
|
86
|
-
expect(disabledHook).not.toHaveBeenCalled();
|
|
87
|
-
expect(enabledHook).toHaveBeenCalled();
|
|
88
|
-
expect(result.records.map(r => r.hookName)).toEqual(['enabled']);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('HookPipeline.runOne 按名执行单个 hook(#167)', () => {
|
|
93
|
-
it('按名执行并返回观测记录,不连带其它 hook', async () => {
|
|
94
|
-
const other = jest.fn().mockResolvedValue({ passed: true });
|
|
95
|
-
const target = jest.fn().mockResolvedValue({ passed: true });
|
|
96
|
-
const { pipeline } = makeRegistry([
|
|
97
|
-
{ hook: { name: 'target', phase: 'before', execute: target } },
|
|
98
|
-
{ hook: { name: 'other', phase: 'before', execute: other } },
|
|
99
|
-
]);
|
|
100
|
-
|
|
101
|
-
const record = await pipeline.runOne('target', {});
|
|
102
|
-
|
|
103
|
-
expect(record.hookName).toBe('target');
|
|
104
|
-
expect(record.phase).toBe('before');
|
|
105
|
-
expect(record.passed).toBe(true);
|
|
106
|
-
expect(record.skipped).toBeUndefined();
|
|
107
|
-
expect(typeof record.durationMs).toBe('number');
|
|
108
|
-
expect(target).toHaveBeenCalledTimes(1);
|
|
109
|
-
expect(other).not.toHaveBeenCalled();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('enabled:false → 实现体零调用,返回 skipped:true / passed:true 记录', async () => {
|
|
113
|
-
const disabled = jest.fn().mockResolvedValue({ passed: true });
|
|
114
|
-
const { pipeline } = makeRegistry([
|
|
115
|
-
{ hook: { name: 'disabled', phase: 'before', execute: disabled }, config: { enabled: false } },
|
|
116
|
-
]);
|
|
117
|
-
|
|
118
|
-
const record = await pipeline.runOne('disabled', {});
|
|
119
|
-
|
|
120
|
-
expect(disabled).not.toHaveBeenCalled();
|
|
121
|
-
expect(record.skipped).toBe(true);
|
|
122
|
-
expect(record.passed).toBe(true);
|
|
123
|
-
expect(record.hookName).toBe('disabled');
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('block + 实现体抛错 → 抛错,message 含 hook 名与原文', async () => {
|
|
127
|
-
const { pipeline } = makeRegistry([
|
|
128
|
-
{
|
|
129
|
-
hook: { name: 'guard', phase: 'before', execute: async () => { throw new Error('iron-law violated'); } },
|
|
130
|
-
config: { errorStrategy: 'block' },
|
|
131
|
-
},
|
|
132
|
-
]);
|
|
133
|
-
|
|
134
|
-
await expect(pipeline.runOne('guard', {})).rejects.toThrow('iron-law violated');
|
|
135
|
-
await expect(pipeline.runOne('guard', {})).rejects.toThrow('[harness] hook "guard" blocked:');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('block + 实现体返回 passed:false → 抛错,message 含 result.error 原文', async () => {
|
|
139
|
-
const { pipeline } = makeRegistry([
|
|
140
|
-
{ hook: { name: 'guard', phase: 'before', execute: async () => ({ passed: false, error: 'boom' }) }, config: { errorStrategy: 'block' } },
|
|
141
|
-
]);
|
|
142
|
-
|
|
143
|
-
await expect(pipeline.runOne('guard', {})).rejects.toThrow('[harness] hook "guard" blocked: boom');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('warn + 失败 → resolve,返回 passed:false 带 error 的记录', async () => {
|
|
147
|
-
const { pipeline } = makeRegistry([
|
|
148
|
-
{
|
|
149
|
-
hook: { name: 'soft', phase: 'before', execute: async () => { throw new Error('soft-fail'); } },
|
|
150
|
-
config: { errorStrategy: 'warn' },
|
|
151
|
-
},
|
|
152
|
-
]);
|
|
153
|
-
|
|
154
|
-
const record = await pipeline.runOne('soft', {});
|
|
155
|
-
|
|
156
|
-
expect(record.passed).toBe(false);
|
|
157
|
-
expect(record.error).toBe('soft-fail');
|
|
158
|
-
expect(record.skipped).toBeUndefined();
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('未知 hook 名 → 抛错(与注册表闭环口径一致,不静默跳过)', async () => {
|
|
162
|
-
const { pipeline } = makeRegistry([
|
|
163
|
-
{ hook: { name: 'known', phase: 'before', execute: async () => ({ passed: true }) } },
|
|
164
|
-
]);
|
|
165
|
-
|
|
166
|
-
await expect(pipeline.runOne('no_such_hook', {})).rejects.toThrow('no_such_hook');
|
|
167
|
-
await expect(pipeline.runOne('no_such_hook', {})).rejects.toThrow('未注册');
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('sampleRate < 1 时采样语义与 run 路径一致(未抽中 → 零调用 + sampled 记录)', async () => {
|
|
171
|
-
const execute = jest.fn().mockResolvedValue({ passed: true });
|
|
172
|
-
const { pipeline } = makeRegistry([
|
|
173
|
-
{ hook: { name: 'sampled', phase: 'before', sampleRate: 0.5, execute } },
|
|
174
|
-
]);
|
|
175
|
-
|
|
176
|
-
const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.9);
|
|
177
|
-
try {
|
|
178
|
-
const record = await pipeline.runOne('sampled', {});
|
|
179
|
-
expect(execute).not.toHaveBeenCalled();
|
|
180
|
-
expect(record.sampled).toBe(true);
|
|
181
|
-
expect(record.passed).toBe(true);
|
|
182
|
-
} finally {
|
|
183
|
-
randomSpy.mockRestore();
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const hitSpy = jest.spyOn(Math, 'random').mockReturnValue(0.1);
|
|
187
|
-
try {
|
|
188
|
-
const record = await pipeline.runOne('sampled', {});
|
|
189
|
-
expect(execute).toHaveBeenCalledTimes(1);
|
|
190
|
-
expect(record.sampled).toBeUndefined();
|
|
191
|
-
} finally {
|
|
192
|
-
hitSpy.mockRestore();
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
});
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook 注册表闭环双向校验测试(H5,复制 checker 闭环模式)
|
|
3
|
-
*
|
|
4
|
-
* 两个失败方向 + 重复声明/注册都必须抛错;闭环通过时不抛错。
|
|
5
|
-
*
|
|
6
|
-
* #159 起注册即配对:`register(hook, config)` 以 HookConfig 填充有效
|
|
7
|
-
* `enabled` / `errorStrategy`——配置表是这两个语义的唯一声明点,
|
|
8
|
-
* HookDefinition 类型上不再存在同名字段(矛盾在构造上不可能出现)。
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { assertHookRegistryClosed, HookRegistry } from '../registry';
|
|
12
|
-
import type { HookConfig, HookDefinition } from '../types';
|
|
13
|
-
|
|
14
|
-
function makeHook(name: string): HookDefinition {
|
|
15
|
-
return {
|
|
16
|
-
name,
|
|
17
|
-
phase: 'before',
|
|
18
|
-
execute: async () => ({ passed: true }),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function makeConfig(name: string, overrides: Partial<HookConfig> = {}): HookConfig {
|
|
23
|
-
return { name, enabled: true, errorStrategy: 'block', ...overrides };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
describe('assertHookRegistryClosed', () => {
|
|
27
|
-
it('声明与注册一一对应时通过', () => {
|
|
28
|
-
const configs = [makeConfig('beforeAgentExecute'), makeConfig('afterReview')];
|
|
29
|
-
const hooks = [makeHook('beforeAgentExecute'), makeHook('afterReview')];
|
|
30
|
-
expect(() => assertHookRegistryClosed(configs, hooks)).not.toThrow();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('空声明 + 空注册通过(无 hook 的 consumer 合法)', () => {
|
|
34
|
-
expect(() => assertHookRegistryClosed([], [])).not.toThrow();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('引用未注册 → 抛错:声明了配置但没有注册实现', () => {
|
|
38
|
-
const configs = [makeConfig('beforeAgentExecute'), makeConfig('ghostHook')];
|
|
39
|
-
const hooks = [makeHook('beforeAgentExecute')];
|
|
40
|
-
expect(() => assertHookRegistryClosed(configs, hooks)).toThrow(
|
|
41
|
-
/hook 配置 "ghostHook" 引用的实现未注册/
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('注册无定义 → 抛错:注册了实现但没有配置声明', () => {
|
|
46
|
-
const configs = [makeConfig('beforeAgentExecute')];
|
|
47
|
-
const hooks = [makeHook('beforeAgentExecute'), makeHook('orphanHook')];
|
|
48
|
-
expect(() => assertHookRegistryClosed(configs, hooks)).toThrow(
|
|
49
|
-
/hook "orphanHook" 注册了实现但没有对应配置声明/
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('重复声明 → 抛错', () => {
|
|
54
|
-
const configs = [makeConfig('dup'), makeConfig('dup')];
|
|
55
|
-
const hooks = [makeHook('dup')];
|
|
56
|
-
expect(() => assertHookRegistryClosed(configs, hooks)).toThrow(/重复声明/);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('重复注册 → 抛错', () => {
|
|
60
|
-
const configs = [makeConfig('dup')];
|
|
61
|
-
const hooks = [makeHook('dup'), makeHook('dup')];
|
|
62
|
-
expect(() => assertHookRegistryClosed(configs, hooks)).toThrow(/重复注册/);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('与 HookRegistry.listAll 组合可用(consumer 注册点用法)', () => {
|
|
66
|
-
const registry = new HookRegistry();
|
|
67
|
-
registry.registerAll([makeHook('beforeAgentExecute')], [makeConfig('beforeAgentExecute')]);
|
|
68
|
-
expect(() => assertHookRegistryClosed([makeConfig('beforeAgentExecute')], registry.listAll()))
|
|
69
|
-
.not.toThrow();
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('HookRegistry 注册即配对(#159 配置表唯一化)', () => {
|
|
74
|
-
it('register 以 HookConfig 填充有效 enabled / errorStrategy', () => {
|
|
75
|
-
const registry = new HookRegistry();
|
|
76
|
-
registry.register(makeHook('a'), makeConfig('a', { enabled: false, errorStrategy: 'block' }));
|
|
77
|
-
|
|
78
|
-
const effective = registry.get('a');
|
|
79
|
-
expect(effective?.enabled).toBe(false);
|
|
80
|
-
expect(effective?.errorStrategy).toBe('block');
|
|
81
|
-
// 配置 enabled:false → 不进 enabled 列表
|
|
82
|
-
expect(registry.getEnabled('before')).toEqual([]);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('register 定义与配置名称不一致 → 抛错', () => {
|
|
86
|
-
const registry = new HookRegistry();
|
|
87
|
-
expect(() => registry.register(makeHook('a'), makeConfig('b'))).toThrow(/名称不一致/);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('registerAll 有实现缺配置声明 → 抛错', () => {
|
|
91
|
-
const registry = new HookRegistry();
|
|
92
|
-
expect(() =>
|
|
93
|
-
registry.registerAll([makeHook('a'), makeHook('orphan')], [makeConfig('a')])
|
|
94
|
-
).toThrow(/hook "orphan" 没有对应的 HookConfig 声明/);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('HookDefinition 类型上不再存在 enabled / errorStrategy(编译期钉)', () => {
|
|
98
|
-
const withStrategy: HookDefinition = {
|
|
99
|
-
name: 'x',
|
|
100
|
-
phase: 'before',
|
|
101
|
-
// @ts-expect-error HookDefinition 不再存在 errorStrategy 字段(#159)
|
|
102
|
-
errorStrategy: 'block',
|
|
103
|
-
execute: async () => ({ passed: true }),
|
|
104
|
-
};
|
|
105
|
-
const withEnabled: HookDefinition = {
|
|
106
|
-
name: 'y',
|
|
107
|
-
phase: 'before',
|
|
108
|
-
// @ts-expect-error HookDefinition 不再存在 enabled 字段(#159)
|
|
109
|
-
enabled: false,
|
|
110
|
-
execute: async () => ({ passed: true }),
|
|
111
|
-
};
|
|
112
|
-
expect(withStrategy.name).toBe('x');
|
|
113
|
-
expect(withEnabled.name).toBe('y');
|
|
114
|
-
});
|
|
115
|
-
});
|
package/src/hooks/config.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook 配置归一(G7)
|
|
3
|
-
*
|
|
4
|
-
* studio 侧 per-hook 运行时配置(`blocking: boolean`)与 harness 侧
|
|
5
|
-
* `HookConfig.errorStrategy` 双轨归一:以 errorStrategy 为唯一真相,
|
|
6
|
-
* `blocking` → errorStrategy 为无损映射。
|
|
7
|
-
*
|
|
8
|
-
* 映射语义(确认于 H5):
|
|
9
|
-
* - blocking=true → 'block':hook 失败阻断管线(停止执行后续 hook,
|
|
10
|
-
* 管线结果 passed=false、blockedBy 记名)——对应 studio safeCallHook 抛错
|
|
11
|
-
* - blocking=false → 'warn':hook 失败记录警告并继续执行后续 hook
|
|
12
|
-
* (管线结果 warnings 记名,passed 不受影响)——对应 studio safeCallHook
|
|
13
|
-
* console.warn 继续
|
|
14
|
-
*
|
|
15
|
-
* #159 起 `HookConfig` 是 `enabled` / `errorStrategy` 的唯一声明点:
|
|
16
|
-
* HookDefinition 不再携带这两个字段,注册环节以配置表填充有效值,
|
|
17
|
-
* 有效策略集合收敛为 'block' | 'warn'(原 'ignore' 退出有效面)。
|
|
18
|
-
*
|
|
19
|
-
* studio 侧随动(studio #150 A4):hooks/config.ts 删 DEFAULTS/safeCallHook,
|
|
20
|
-
* blocking→errorStrategy 无损映射进 toHookDef;HARNESS_HOOK_DISABLE 保留 studio。
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { HookErrorStrategy } from './types';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* blocking 布尔 → errorStrategy 无损映射(block/warn)
|
|
27
|
-
*
|
|
28
|
-
* @param blocking studio 侧「失败是否阻断」语义
|
|
29
|
-
* @returns 'block'(阻断)| 'warn'(警告继续)
|
|
30
|
-
*/
|
|
31
|
-
export function toErrorStrategy(blocking: boolean): HookErrorStrategy {
|
|
32
|
-
return blocking ? 'block' : 'warn';
|
|
33
|
-
}
|
package/src/hooks/pipeline.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HookPipeline — hook 有序执行、错误隔离、采样
|
|
3
|
-
*
|
|
4
|
-
* 执行顺序:before hooks → consumer operation → after hooks
|
|
5
|
-
* 每个 hook 独立 try/catch,单个 hook 失败不影���其他 hook。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
EffectiveHook,
|
|
10
|
-
HookPhase,
|
|
11
|
-
HookExecutionRecord,
|
|
12
|
-
PipelineResult,
|
|
13
|
-
} from './types';
|
|
14
|
-
import type { HookRegistry } from './registry';
|
|
15
|
-
|
|
16
|
-
export class HookPipeline<C = unknown> {
|
|
17
|
-
private registry: HookRegistry<C, unknown>;
|
|
18
|
-
|
|
19
|
-
constructor(registry: HookRegistry<C, unknown>) {
|
|
20
|
-
this.registry = registry;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 执行指定 phase 的所有 enabled hook
|
|
25
|
-
*
|
|
26
|
-
* @param phase hook 时机
|
|
27
|
-
* @param context 传递给每个 hook 的上下文
|
|
28
|
-
* @returns 管线执行结果
|
|
29
|
-
*/
|
|
30
|
-
async run(
|
|
31
|
-
phase: HookPhase,
|
|
32
|
-
context: C
|
|
33
|
-
): Promise<PipelineResult> {
|
|
34
|
-
const hooks = this.registry.getEnabled(phase);
|
|
35
|
-
const records: HookExecutionRecord[] = [];
|
|
36
|
-
const blockedBy: string[] = [];
|
|
37
|
-
const warnings: string[] = [];
|
|
38
|
-
let passed = true;
|
|
39
|
-
|
|
40
|
-
for (const hook of hooks) {
|
|
41
|
-
const record = await this.executeOne(hook, context);
|
|
42
|
-
records.push(record);
|
|
43
|
-
|
|
44
|
-
if (!record.passed) {
|
|
45
|
-
if (hook.errorStrategy === 'block') {
|
|
46
|
-
passed = false;
|
|
47
|
-
blockedBy.push(hook.name);
|
|
48
|
-
break; // blocking hook 失败,停止执行后续 hook
|
|
49
|
-
}
|
|
50
|
-
// 'warn':记录警告继续(有效集合仅 block/warn,策略由 HookConfig 必填声明)
|
|
51
|
-
warnings.push(hook.name);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return { passed, records, blockedBy, warnings };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 按名执行单个 hook(#167)
|
|
60
|
-
*
|
|
61
|
-
* 与 run() 共用 executeOne 的采样与错误隔离路径(判定与错误策略的实现体
|
|
62
|
-
* 只有一份);enabled / errorStrategy 的值来源仍是注册环节填充的
|
|
63
|
-
* EffectiveHook(唯一声明点 HookConfig,#159)。
|
|
64
|
-
*
|
|
65
|
-
* - enabled:false → 实现体零调用,返回 skipped:true / passed:true 记录
|
|
66
|
-
* (镜像 sampled 先例:未执行 = 记录上一面旗,不是异常)
|
|
67
|
-
* - errorStrategy 'block' 且失败 → 抛 Error(message 含 hook 名与原文);
|
|
68
|
-
* 'warn' 且失败 → resolve,返回 passed:false 带 error 的记录
|
|
69
|
-
* - 未知名 → 抛错(与 assertHookRegistryClosed 闭环口径一致,拒绝静默缺失)
|
|
70
|
-
*
|
|
71
|
-
* @param name hook 名称(须已注册)
|
|
72
|
-
* @param context 传递给 hook 的上下文
|
|
73
|
-
* @returns 管线观测记录
|
|
74
|
-
*/
|
|
75
|
-
async runOne(name: string, context: C): Promise<HookExecutionRecord> {
|
|
76
|
-
const hook = this.registry.get(name);
|
|
77
|
-
if (!hook) {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`[harness] hook 执行失败:hook "${name}" 未注册。请注册同名 HookDefinition 与 HookConfig 声明。`
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!hook.enabled) {
|
|
84
|
-
const now = Date.now();
|
|
85
|
-
return {
|
|
86
|
-
hookName: hook.name,
|
|
87
|
-
phase: hook.phase,
|
|
88
|
-
startedAt: now,
|
|
89
|
-
completedAt: now,
|
|
90
|
-
durationMs: 0,
|
|
91
|
-
passed: true,
|
|
92
|
-
skipped: true,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const record = await this.executeOne(hook, context);
|
|
97
|
-
if (!record.passed && hook.errorStrategy === 'block') {
|
|
98
|
-
throw new Error(
|
|
99
|
-
`[harness] hook "${hook.name}" blocked: ${record.error ?? '未知错误'}`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
return record;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 执行 before + after 全套管线
|
|
107
|
-
*
|
|
108
|
-
* @param context 上下文
|
|
109
|
-
* @param operation 业务操作(在 before 和 after 之间执行)
|
|
110
|
-
* @returns 管线结果 + 操作结果
|
|
111
|
-
*/
|
|
112
|
-
async runFull<R>(
|
|
113
|
-
context: C,
|
|
114
|
-
operation: () => Promise<R>
|
|
115
|
-
): Promise<{ pipelineResult: PipelineResult; operationResult?: R }> {
|
|
116
|
-
// 1. Before hooks
|
|
117
|
-
const beforeResult = await this.run('before', context);
|
|
118
|
-
if (!beforeResult.passed) {
|
|
119
|
-
return { pipelineResult: beforeResult };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 2. Execute operation
|
|
123
|
-
let operationResult: R;
|
|
124
|
-
try {
|
|
125
|
-
operationResult = await operation();
|
|
126
|
-
} catch (err) {
|
|
127
|
-
// 操作失败:仍执行 after hooks,但传递错误信息
|
|
128
|
-
const afterResult = await this.run('after', {
|
|
129
|
-
...context,
|
|
130
|
-
_operationError: (err as Error).message,
|
|
131
|
-
} as unknown as C);
|
|
132
|
-
return {
|
|
133
|
-
pipelineResult: {
|
|
134
|
-
passed: false,
|
|
135
|
-
records: [...beforeResult.records, ...afterResult.records],
|
|
136
|
-
blockedBy: [...beforeResult.blockedBy, '_operation'],
|
|
137
|
-
warnings: [...beforeResult.warnings, ...afterResult.warnings],
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 3. After hooks
|
|
143
|
-
const afterResult = await this.run('after', context);
|
|
144
|
-
return {
|
|
145
|
-
pipelineResult: {
|
|
146
|
-
passed: beforeResult.passed && afterResult.passed,
|
|
147
|
-
records: [...beforeResult.records, ...afterResult.records],
|
|
148
|
-
blockedBy: [...beforeResult.blockedBy, ...afterResult.blockedBy],
|
|
149
|
-
warnings: [...beforeResult.warnings, ...afterResult.warnings],
|
|
150
|
-
},
|
|
151
|
-
operationResult,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 执行单个 hook(带采样和错误隔离)
|
|
157
|
-
*/
|
|
158
|
-
private async executeOne(
|
|
159
|
-
hook: EffectiveHook<C, unknown>,
|
|
160
|
-
context: C
|
|
161
|
-
): Promise<HookExecutionRecord> {
|
|
162
|
-
const startedAt = Date.now();
|
|
163
|
-
|
|
164
|
-
// 采样检查
|
|
165
|
-
if (hook.sampleRate !== undefined && hook.sampleRate < 1) {
|
|
166
|
-
if (Math.random() > hook.sampleRate) {
|
|
167
|
-
return {
|
|
168
|
-
hookName: hook.name,
|
|
169
|
-
phase: hook.phase,
|
|
170
|
-
startedAt,
|
|
171
|
-
completedAt: Date.now(),
|
|
172
|
-
durationMs: 0,
|
|
173
|
-
passed: true,
|
|
174
|
-
sampled: true,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
const result = await hook.execute(context);
|
|
181
|
-
return {
|
|
182
|
-
hookName: hook.name,
|
|
183
|
-
phase: hook.phase,
|
|
184
|
-
startedAt,
|
|
185
|
-
completedAt: Date.now(),
|
|
186
|
-
durationMs: Date.now() - startedAt,
|
|
187
|
-
passed: result.passed !== false,
|
|
188
|
-
error: result.error,
|
|
189
|
-
};
|
|
190
|
-
} catch (err) {
|
|
191
|
-
return {
|
|
192
|
-
hookName: hook.name,
|
|
193
|
-
phase: hook.phase,
|
|
194
|
-
startedAt,
|
|
195
|
-
completedAt: Date.now(),
|
|
196
|
-
durationMs: Date.now() - startedAt,
|
|
197
|
-
passed: false,
|
|
198
|
-
error: (err as Error).message,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|