@gobing-ai/ts-rule-engine 0.4.27 → 0.4.30
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/config/extensions.d.ts +17 -7
- package/dist/config/extensions.d.ts.map +1 -1
- package/dist/config/extensions.js +16 -32
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +18 -3
- package/dist/evaluators/file-discovery.d.ts.map +1 -1
- package/dist/evaluators/file-discovery.js +10 -0
- package/dist/events.d.ts +6 -0
- package/dist/events.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/config/extensions.ts +28 -47
- package/src/engine.ts +18 -3
- package/src/evaluators/file-discovery.ts +10 -0
- package/src/events.ts +12 -4
|
@@ -2,14 +2,22 @@ import type { Logger } from '@gobing-ai/ts-infra';
|
|
|
2
2
|
import type { RuleEngineHost } from '../host/rule-engine-host';
|
|
3
3
|
/** A capability kind a preset extension can contribute. */
|
|
4
4
|
export type ExtensionKind = 'resolvers' | 'evaluators' | 'fixers' | 'formatters';
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* A single extension module reference: the authored relative path plus the
|
|
7
|
+
* declaring directory (task 0060 C2). Keeping the authored string means the
|
|
8
|
+
* shared loader's `assertRelativeExtensionPath` sees the real declaration — a
|
|
9
|
+
* `..` segment or absolute path is rejected there, not after a basename smash
|
|
10
|
+
* has already stripped it.
|
|
11
|
+
*/
|
|
6
12
|
export interface ExtensionRef {
|
|
7
13
|
/** Capability registry the module registers into. */
|
|
8
14
|
readonly kind: ExtensionKind;
|
|
9
|
-
/**
|
|
10
|
-
readonly
|
|
15
|
+
/** Relative path as authored (e.g. `./exts/foo.ts`). */
|
|
16
|
+
readonly path: string;
|
|
17
|
+
/** Absolute directory the authored `path` is resolved against. */
|
|
18
|
+
readonly baseDir: string;
|
|
11
19
|
/** Name of the preset that declared this extension (for diagnostics). */
|
|
12
|
-
readonly
|
|
20
|
+
readonly sourceName: string;
|
|
13
21
|
}
|
|
14
22
|
/** Options controlling preset-extension loading. */
|
|
15
23
|
export interface LoadExtensionsOptions {
|
|
@@ -37,9 +45,11 @@ export interface LoadExtensionsOptions {
|
|
|
37
45
|
/**
|
|
38
46
|
* Collect extension refs declared by a preset's or rule file's `extensions` block.
|
|
39
47
|
*
|
|
40
|
-
* Paths are
|
|
41
|
-
*
|
|
42
|
-
*
|
|
48
|
+
* Paths are kept **as authored** and paired with the declaring file's directory as
|
|
49
|
+
* `baseDir` — no eager resolution, so the shared loader's traversal guard sees the
|
|
50
|
+
* real declaration (task 0060 C2). Use the returned refs with
|
|
51
|
+
* {@link loadExtensionsIntoHost}. Rule files and presets are treated identically —
|
|
52
|
+
* both flow through the same trust gate at load time.
|
|
43
53
|
*/
|
|
44
54
|
export declare function collectExtensions(sourceName: string, sourceDir: string, extensions: Partial<Record<ExtensionKind, string[] | undefined>> | undefined): ExtensionRef[];
|
|
45
55
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../src/config/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../src/config/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAOlD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEjF;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IACzB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B;AAED,oDAAoD;AACpD,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,sEAAsE;IACtE,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,oFAAoF;IACpF,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CAC1C;AAUD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC7B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,GAC7E,YAAY,EAAE,CAShB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,CACxC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GACpC,OAAO,CAAC,IAAI,CAAC,CA2Cf"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createNodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
2
2
|
import { loadExtensionModules } from '@gobing-ai/ts-runtime/extension';
|
|
3
3
|
/** Host registries that can receive extension capabilities (all four kinds). */
|
|
4
4
|
const HOST_REGISTRY_BY_KIND = {
|
|
@@ -10,9 +10,11 @@ const HOST_REGISTRY_BY_KIND = {
|
|
|
10
10
|
/**
|
|
11
11
|
* Collect extension refs declared by a preset's or rule file's `extensions` block.
|
|
12
12
|
*
|
|
13
|
-
* Paths are
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Paths are kept **as authored** and paired with the declaring file's directory as
|
|
14
|
+
* `baseDir` — no eager resolution, so the shared loader's traversal guard sees the
|
|
15
|
+
* real declaration (task 0060 C2). Use the returned refs with
|
|
16
|
+
* {@link loadExtensionsIntoHost}. Rule files and presets are treated identically —
|
|
17
|
+
* both flow through the same trust gate at load time.
|
|
16
18
|
*/
|
|
17
19
|
export function collectExtensions(sourceName, sourceDir, extensions) {
|
|
18
20
|
if (extensions === undefined)
|
|
@@ -20,7 +22,7 @@ export function collectExtensions(sourceName, sourceDir, extensions) {
|
|
|
20
22
|
const refs = [];
|
|
21
23
|
for (const kind of ['resolvers', 'evaluators', 'fixers', 'formatters']) {
|
|
22
24
|
for (const path of extensions[kind] ?? []) {
|
|
23
|
-
refs.push({ kind,
|
|
25
|
+
refs.push({ kind, sourceName, path, baseDir: sourceDir });
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
return refs;
|
|
@@ -44,33 +46,15 @@ export function collectExtensions(sourceName, sourceDir, extensions) {
|
|
|
44
46
|
export async function loadExtensionsIntoHost(host, refs, options = {}) {
|
|
45
47
|
if (refs.length === 0)
|
|
46
48
|
return;
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const segments = normalizeSeparators(toFilePath(ref.absPath)).split('/');
|
|
57
|
-
if (segments.includes('..')) {
|
|
58
|
-
throw new Error(`extension path "${ref.absPath}" declared by "${ref.presetName}" must not contain ".." traversal`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Adapt rule-engine ExtensionRef → shared ExtensionRef so the generic loader
|
|
62
|
-
// governs every import. The shared loader resolves (baseDir, path) →
|
|
63
|
-
// absPath internally; we supply dirname/basename so the resolved path
|
|
64
|
-
// reconstructs the caller's original absPath.
|
|
65
|
-
const sharedRefs = refs.map((ref) => {
|
|
66
|
-
const fp = toFilePath(ref.absPath);
|
|
67
|
-
return {
|
|
68
|
-
kind: ref.kind,
|
|
69
|
-
path: `./${basenamePath(fp)}`,
|
|
70
|
-
baseDir: dirnamePath(fp),
|
|
71
|
-
sourceName: ref.presetName,
|
|
72
|
-
};
|
|
73
|
-
});
|
|
49
|
+
// Map 1:1 onto the shared ref shape — no dirname/basename smash, so the shared
|
|
50
|
+
// loader's assertRelativeExtensionPath + realPath confinement govern the authored
|
|
51
|
+
// path exactly as declared (task 0060 C2).
|
|
52
|
+
const sharedRefs = refs.map((ref) => ({
|
|
53
|
+
kind: ref.kind,
|
|
54
|
+
path: ref.path,
|
|
55
|
+
baseDir: ref.baseDir,
|
|
56
|
+
sourceName: ref.sourceName,
|
|
57
|
+
}));
|
|
74
58
|
const moduleLoader = options.moduleLoader ?? defaultModuleLoader;
|
|
75
59
|
// Real import policy gets real confinement by default (ADR-022 addendum): a
|
|
76
60
|
// caller relying on the default dynamic-import loader that has not set
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAgB,QAAQ,EAAyB,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAwB,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACpG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAIH,KAAK,oBAAoB,EAE5B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAqB,cAAc,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGhH,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAC9B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,mCAAmC;IACnC,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACpC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC5C,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,4EAA4E;AAC5E,qBAAa,UAAU;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAE9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAChE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;gBAE5B,OAAO,GAAE,iBAAsB;IAc3C,wCAAwC;IACxC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,GAAG,IAAI;IAI/D;;;;;;;OAOG;IACG,QAAQ,CACV,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAC3C,OAAO,CAAC,gBAAgB,CAAC;IAI5B;;;;;;;;;;;;;;;;;;OAkBG;IACG,iBAAiB,CACnB,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,OAAgB,EAC5B,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAC3C,OAAO,CAAC,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAgB,QAAQ,EAAyB,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAwB,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACpG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAIH,KAAK,oBAAoB,EAE5B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAqB,cAAc,EAAE,GAAG,EAAE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGhH,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAC9B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,mCAAmC;IACnC,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACpC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC5C,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,4EAA4E;AAC5E,qBAAa,UAAU;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAE9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAChE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqC;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;gBAE5B,OAAO,GAAE,iBAAsB;IAc3C,wCAAwC;IACxC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,GAAG,IAAI;IAI/D;;;;;;;OAOG;IACG,QAAQ,CACV,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAC3C,OAAO,CAAC,gBAAgB,CAAC;IAI5B;;;;;;;;;;;;;;;;;;OAkBG;IACG,iBAAiB,CACnB,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,OAAgB,EAC5B,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAC3C,OAAO,CAAC,gBAAgB,CAAC;IAkL5B;;;;;;;OAOG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,EAAE,EAAE,MAAM,UAAQ,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAG1G"}
|
package/dist/engine.js
CHANGED
|
@@ -81,7 +81,11 @@ export class RuleEngine {
|
|
|
81
81
|
return await traceAsync('rule.run', async () => {
|
|
82
82
|
this.logger.info('rule run started', { enabled: enabledRules.length, total: rules.length });
|
|
83
83
|
addSpanEvent('rule.run.start', { rules: enabledRules.length, total: rules.length });
|
|
84
|
-
void this.events?.emit('rule.run.start', {
|
|
84
|
+
void this.events?.emit('rule.run.start', {
|
|
85
|
+
rules: enabledRules.length,
|
|
86
|
+
total: rules.length,
|
|
87
|
+
severity: 'info',
|
|
88
|
+
});
|
|
85
89
|
const findings = [];
|
|
86
90
|
const fixes = [];
|
|
87
91
|
let index = 0;
|
|
@@ -101,7 +105,12 @@ export class RuleEngine {
|
|
|
101
105
|
const evalStartMs = Date.now();
|
|
102
106
|
this.logger.debug('eval start', { ruleId: rule.id, index, total: enabledRules.length });
|
|
103
107
|
addSpanEvent('rule.eval.start', { ruleId: rule.id, index, total: enabledRules.length });
|
|
104
|
-
void this.events?.emit('rule.eval.start', {
|
|
108
|
+
void this.events?.emit('rule.eval.start', {
|
|
109
|
+
ruleId: rule.id,
|
|
110
|
+
index,
|
|
111
|
+
total: enabledRules.length,
|
|
112
|
+
severity: 'info',
|
|
113
|
+
});
|
|
105
114
|
let ruleFindings = [];
|
|
106
115
|
let ruleEvalFixes = [];
|
|
107
116
|
let evalError;
|
|
@@ -121,7 +130,11 @@ export class RuleEngine {
|
|
|
121
130
|
}),
|
|
122
131
|
];
|
|
123
132
|
addSpanEvent('rule.eval.error', { ruleId: rule.id, error: evalError });
|
|
124
|
-
void this.events?.emit('rule.eval.error', {
|
|
133
|
+
void this.events?.emit('rule.eval.error', {
|
|
134
|
+
ruleId: rule.id,
|
|
135
|
+
error: evalError,
|
|
136
|
+
severity: 'error',
|
|
137
|
+
});
|
|
125
138
|
}
|
|
126
139
|
const durationMs = Date.now() - evalStartMs;
|
|
127
140
|
addSpanEvent('rule.eval.done', {
|
|
@@ -134,6 +147,7 @@ export class RuleEngine {
|
|
|
134
147
|
findings: ruleFindings.length,
|
|
135
148
|
durationMs,
|
|
136
149
|
details: ruleFindings,
|
|
150
|
+
severity: 'info',
|
|
137
151
|
});
|
|
138
152
|
// Persist eval completion.
|
|
139
153
|
await this.persistence?.updateEvalRun({
|
|
@@ -190,6 +204,7 @@ export class RuleEngine {
|
|
|
190
204
|
findings: findings.length,
|
|
191
205
|
durationMs: runDurationMs,
|
|
192
206
|
stoppedEarly: stoppedEarlyLocal,
|
|
207
|
+
severity: 'info',
|
|
193
208
|
});
|
|
194
209
|
// Finalize the run row.
|
|
195
210
|
await this.persistence?.updateRunStatus(this.runId, 'done', findings.length, fixes.length, 0, // appliedFixCount — set by the caller after applying fixes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-discovery.d.ts","sourceRoot":"","sources":["../../src/evaluators/file-discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGH,KAAK,UAAU,EAIlB,MAAM,uBAAuB,CAAC;AAG/B,yCAAyC;AACzC,MAAM,WAAW,sBAAsB;IACnC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,0BAA0B;IAC1B,EAAE,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,aAAgF,CAAC;AAE9G,qFAAqF;AACrF,wBAAsB,aAAa,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWtF;AAED,gDAAgD;AAChD,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,aAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAErH;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;
|
|
1
|
+
{"version":3,"file":"file-discovery.d.ts","sourceRoot":"","sources":["../../src/evaluators/file-discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGH,KAAK,UAAU,EAIlB,MAAM,uBAAuB,CAAC;AAG/B,yCAAyC;AACzC,MAAM,WAAW,sBAAsB;IACnC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,0BAA0B;IAC1B,EAAE,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,aAAgF,CAAC;AAE9G,qFAAqF;AACrF,wBAAsB,aAAa,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWtF;AAED,gDAAgD;AAChD,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,aAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAErH;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AASD,8EAA8E;AAC9E,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7C,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC7B,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;;;;;OAQG;IACH,SAAS,EAAE,aAAa,CAAC;IACzB,0BAA0B;IAC1B,EAAE,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAcjF;AAeD,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD"}
|
|
@@ -25,6 +25,12 @@ export async function discoverFiles(options) {
|
|
|
25
25
|
export async function readWorkdirFile(workdir, filePath, fs = createNodeFileSystem()) {
|
|
26
26
|
return await fs.readFile(resolvePath(workdir, filePath));
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-file byte cap for {@link scanFiles} (task 0060 R15). Larger files are skipped
|
|
30
|
+
* with no content buffer so a single oversized artifact cannot balloon memory while
|
|
31
|
+
* a rule corpus is assembled; evaluators still see every smaller file unchanged.
|
|
32
|
+
*/
|
|
33
|
+
const MAX_SCANNED_FILE_BYTES = 2_000_000;
|
|
28
34
|
/**
|
|
29
35
|
* Discover in-scope files and read each once — the shared scaffolding behind the
|
|
30
36
|
* line-scanning evaluators. Owns discovery, scope filtering (per `matchMode`), and
|
|
@@ -41,6 +47,10 @@ export async function scanFiles(options) {
|
|
|
41
47
|
: await discoverFilesByGlob(options.workdir, options.include, options.exclude, fs);
|
|
42
48
|
const scanned = [];
|
|
43
49
|
for (const file of files) {
|
|
50
|
+
// Skip oversized files instead of buffering them (task 0060 R15).
|
|
51
|
+
const stat = await fs.stat(resolvePath(options.workdir, file));
|
|
52
|
+
if (stat !== null && stat.size > MAX_SCANNED_FILE_BYTES)
|
|
53
|
+
continue;
|
|
44
54
|
scanned.push({ file, content: await readWorkdirFile(options.workdir, file, fs) });
|
|
45
55
|
}
|
|
46
56
|
return scanned;
|
package/dist/events.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EventSeverity } from '@gobing-ai/ts-infra';
|
|
1
2
|
import type { ConstraintFinding } from './types';
|
|
2
3
|
/** Typed event map for rule-engine run observability. All events prefixed `rule.`. */
|
|
3
4
|
export type RuleEngineEvents = {
|
|
@@ -5,12 +6,14 @@ export type RuleEngineEvents = {
|
|
|
5
6
|
'rule.run.start': (data: {
|
|
6
7
|
rules: number;
|
|
7
8
|
total: number;
|
|
9
|
+
severity: EventSeverity;
|
|
8
10
|
}) => void;
|
|
9
11
|
/** Emitted immediately before a single rule's evaluator is invoked. */
|
|
10
12
|
'rule.eval.start': (data: {
|
|
11
13
|
ruleId: string;
|
|
12
14
|
index: number;
|
|
13
15
|
total: number;
|
|
16
|
+
severity: EventSeverity;
|
|
14
17
|
}) => void;
|
|
15
18
|
/**
|
|
16
19
|
* Emitted after a single rule evaluation finishes successfully.
|
|
@@ -23,11 +26,13 @@ export type RuleEngineEvents = {
|
|
|
23
26
|
findings: number;
|
|
24
27
|
durationMs: number;
|
|
25
28
|
details: ConstraintFinding[];
|
|
29
|
+
severity: EventSeverity;
|
|
26
30
|
}) => void;
|
|
27
31
|
/** Emitted when a rule evaluator throws (in addition to the `kind:'error'` finding). */
|
|
28
32
|
'rule.eval.error': (data: {
|
|
29
33
|
ruleId: string;
|
|
30
34
|
error: string;
|
|
35
|
+
severity: EventSeverity;
|
|
31
36
|
}) => void;
|
|
32
37
|
/** Emitted after the last rule finishes (or was short-circuited). */
|
|
33
38
|
'rule.run.done': (data: {
|
|
@@ -35,6 +40,7 @@ export type RuleEngineEvents = {
|
|
|
35
40
|
findings: number;
|
|
36
41
|
durationMs: number;
|
|
37
42
|
stoppedEarly: boolean;
|
|
43
|
+
severity: EventSeverity;
|
|
38
44
|
}) => void;
|
|
39
45
|
};
|
|
40
46
|
//# sourceMappingURL=events.d.ts.map
|
package/dist/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,kDAAkD;IAClD,gBAAgB,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,kDAAkD;IAClD,gBAAgB,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,aAAa,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5F,uEAAuE;IACvE,iBAAiB,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,aAAa,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7G;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,iBAAiB,EAAE,CAAC;QAC7B,QAAQ,EAAE,aAAa,CAAC;KAC3B,KAAK,IAAI,CAAC;IACX,wFAAwF;IACxF,iBAAiB,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,aAAa,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9F,qEAAqE;IACrE,eAAe,EAAE,CAAC,IAAI,EAAE;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,EAAE,aAAa,CAAC;KAC3B,KAAK,IAAI,CAAC;CACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-rule-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.30",
|
|
4
4
|
"description": "@gobing-ai/ts-rule-engine — Constraint rule schemas, loading, evaluation, and result formatting.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-rule-engine-v<version> && git push --tags' && exit 1"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@gobing-ai/ts-ai-runner": "^0.4.
|
|
53
|
-
"@gobing-ai/ts-infra": "^0.4.
|
|
54
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
55
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
52
|
+
"@gobing-ai/ts-ai-runner": "^0.4.30",
|
|
53
|
+
"@gobing-ai/ts-infra": "^0.4.30",
|
|
54
|
+
"@gobing-ai/ts-runtime": "^0.4.30",
|
|
55
|
+
"@gobing-ai/ts-db": "^0.4.30",
|
|
56
56
|
"yaml": "^2.7.0",
|
|
57
57
|
"zod": "^4.1.0"
|
|
58
58
|
},
|
package/src/config/extensions.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import type { Logger } from '@gobing-ai/ts-infra';
|
|
2
|
-
import {
|
|
3
|
-
basenamePath,
|
|
4
|
-
createNodeFileSystem,
|
|
5
|
-
dirnamePath,
|
|
6
|
-
fileUrlToPath,
|
|
7
|
-
normalizeSeparators,
|
|
8
|
-
resolvePath,
|
|
9
|
-
} from '@gobing-ai/ts-runtime';
|
|
2
|
+
import { createNodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
10
3
|
import type {
|
|
11
4
|
ExtensionRef as SharedExtensionRef,
|
|
12
5
|
LoadExtensionsOptions as SharedLoadExtensionsOptions,
|
|
@@ -16,14 +9,22 @@ import type { RuleEngineHost } from '../host/rule-engine-host';
|
|
|
16
9
|
/** A capability kind a preset extension can contribute. */
|
|
17
10
|
export type ExtensionKind = 'resolvers' | 'evaluators' | 'fixers' | 'formatters';
|
|
18
11
|
|
|
19
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* A single extension module reference: the authored relative path plus the
|
|
14
|
+
* declaring directory (task 0060 C2). Keeping the authored string means the
|
|
15
|
+
* shared loader's `assertRelativeExtensionPath` sees the real declaration — a
|
|
16
|
+
* `..` segment or absolute path is rejected there, not after a basename smash
|
|
17
|
+
* has already stripped it.
|
|
18
|
+
*/
|
|
20
19
|
export interface ExtensionRef {
|
|
21
20
|
/** Capability registry the module registers into. */
|
|
22
21
|
readonly kind: ExtensionKind;
|
|
23
|
-
/**
|
|
24
|
-
readonly
|
|
22
|
+
/** Relative path as authored (e.g. `./exts/foo.ts`). */
|
|
23
|
+
readonly path: string;
|
|
24
|
+
/** Absolute directory the authored `path` is resolved against. */
|
|
25
|
+
readonly baseDir: string;
|
|
25
26
|
/** Name of the preset that declared this extension (for diagnostics). */
|
|
26
|
-
readonly
|
|
27
|
+
readonly sourceName: string;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/** Options controlling preset-extension loading. */
|
|
@@ -61,9 +62,11 @@ const HOST_REGISTRY_BY_KIND: Record<ExtensionKind, 'resolvers' | 'evaluators' |
|
|
|
61
62
|
/**
|
|
62
63
|
* Collect extension refs declared by a preset's or rule file's `extensions` block.
|
|
63
64
|
*
|
|
64
|
-
* Paths are
|
|
65
|
-
*
|
|
66
|
-
*
|
|
65
|
+
* Paths are kept **as authored** and paired with the declaring file's directory as
|
|
66
|
+
* `baseDir` — no eager resolution, so the shared loader's traversal guard sees the
|
|
67
|
+
* real declaration (task 0060 C2). Use the returned refs with
|
|
68
|
+
* {@link loadExtensionsIntoHost}. Rule files and presets are treated identically —
|
|
69
|
+
* both flow through the same trust gate at load time.
|
|
67
70
|
*/
|
|
68
71
|
export function collectExtensions(
|
|
69
72
|
sourceName: string,
|
|
@@ -74,7 +77,7 @@ export function collectExtensions(
|
|
|
74
77
|
const refs: ExtensionRef[] = [];
|
|
75
78
|
for (const kind of ['resolvers', 'evaluators', 'fixers', 'formatters'] as ExtensionKind[]) {
|
|
76
79
|
for (const path of extensions[kind] ?? []) {
|
|
77
|
-
refs.push({ kind,
|
|
80
|
+
refs.push({ kind, sourceName, path, baseDir: sourceDir });
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
return refs;
|
|
@@ -103,37 +106,15 @@ export async function loadExtensionsIntoHost(
|
|
|
103
106
|
): Promise<void> {
|
|
104
107
|
if (refs.length === 0) return;
|
|
105
108
|
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// every platform, so a platform SEP split would miss segments on Windows.
|
|
116
|
-
const segments = normalizeSeparators(toFilePath(ref.absPath)).split('/');
|
|
117
|
-
if (segments.includes('..')) {
|
|
118
|
-
throw new Error(
|
|
119
|
-
`extension path "${ref.absPath}" declared by "${ref.presetName}" must not contain ".." traversal`,
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Adapt rule-engine ExtensionRef → shared ExtensionRef so the generic loader
|
|
125
|
-
// governs every import. The shared loader resolves (baseDir, path) →
|
|
126
|
-
// absPath internally; we supply dirname/basename so the resolved path
|
|
127
|
-
// reconstructs the caller's original absPath.
|
|
128
|
-
const sharedRefs: SharedExtensionRef<ExtensionKind>[] = refs.map((ref) => {
|
|
129
|
-
const fp = toFilePath(ref.absPath);
|
|
130
|
-
return {
|
|
131
|
-
kind: ref.kind,
|
|
132
|
-
path: `./${basenamePath(fp)}`,
|
|
133
|
-
baseDir: dirnamePath(fp),
|
|
134
|
-
sourceName: ref.presetName,
|
|
135
|
-
};
|
|
136
|
-
});
|
|
109
|
+
// Map 1:1 onto the shared ref shape — no dirname/basename smash, so the shared
|
|
110
|
+
// loader's assertRelativeExtensionPath + realPath confinement govern the authored
|
|
111
|
+
// path exactly as declared (task 0060 C2).
|
|
112
|
+
const sharedRefs: SharedExtensionRef<ExtensionKind>[] = refs.map((ref) => ({
|
|
113
|
+
kind: ref.kind,
|
|
114
|
+
path: ref.path,
|
|
115
|
+
baseDir: ref.baseDir,
|
|
116
|
+
sourceName: ref.sourceName,
|
|
117
|
+
}));
|
|
137
118
|
|
|
138
119
|
const moduleLoader = options.moduleLoader ?? defaultModuleLoader;
|
|
139
120
|
// Real import policy gets real confinement by default (ADR-022 addendum): a
|
package/src/engine.ts
CHANGED
|
@@ -141,7 +141,11 @@ export class RuleEngine {
|
|
|
141
141
|
async () => {
|
|
142
142
|
this.logger.info('rule run started', { enabled: enabledRules.length, total: rules.length });
|
|
143
143
|
addSpanEvent('rule.run.start', { rules: enabledRules.length, total: rules.length });
|
|
144
|
-
void this.events?.emit('rule.run.start', {
|
|
144
|
+
void this.events?.emit('rule.run.start', {
|
|
145
|
+
rules: enabledRules.length,
|
|
146
|
+
total: rules.length,
|
|
147
|
+
severity: 'info',
|
|
148
|
+
});
|
|
145
149
|
|
|
146
150
|
const findings: ConstraintFinding[] = [];
|
|
147
151
|
const fixes: Fix[] = [];
|
|
@@ -164,7 +168,12 @@ export class RuleEngine {
|
|
|
164
168
|
const evalStartMs = Date.now();
|
|
165
169
|
this.logger.debug('eval start', { ruleId: rule.id, index, total: enabledRules.length });
|
|
166
170
|
addSpanEvent('rule.eval.start', { ruleId: rule.id, index, total: enabledRules.length });
|
|
167
|
-
void this.events?.emit('rule.eval.start', {
|
|
171
|
+
void this.events?.emit('rule.eval.start', {
|
|
172
|
+
ruleId: rule.id,
|
|
173
|
+
index,
|
|
174
|
+
total: enabledRules.length,
|
|
175
|
+
severity: 'info',
|
|
176
|
+
});
|
|
168
177
|
|
|
169
178
|
let ruleFindings: ConstraintFinding[] = [];
|
|
170
179
|
let ruleEvalFixes: Fix[] = [];
|
|
@@ -184,7 +193,11 @@ export class RuleEngine {
|
|
|
184
193
|
}),
|
|
185
194
|
];
|
|
186
195
|
addSpanEvent('rule.eval.error', { ruleId: rule.id, error: evalError });
|
|
187
|
-
void this.events?.emit('rule.eval.error', {
|
|
196
|
+
void this.events?.emit('rule.eval.error', {
|
|
197
|
+
ruleId: rule.id,
|
|
198
|
+
error: evalError,
|
|
199
|
+
severity: 'error',
|
|
200
|
+
});
|
|
188
201
|
}
|
|
189
202
|
|
|
190
203
|
const durationMs = Date.now() - evalStartMs;
|
|
@@ -198,6 +211,7 @@ export class RuleEngine {
|
|
|
198
211
|
findings: ruleFindings.length,
|
|
199
212
|
durationMs,
|
|
200
213
|
details: ruleFindings,
|
|
214
|
+
severity: 'info',
|
|
201
215
|
});
|
|
202
216
|
|
|
203
217
|
// Persist eval completion.
|
|
@@ -262,6 +276,7 @@ export class RuleEngine {
|
|
|
262
276
|
findings: findings.length,
|
|
263
277
|
durationMs: runDurationMs,
|
|
264
278
|
stoppedEarly: stoppedEarlyLocal,
|
|
279
|
+
severity: 'info',
|
|
265
280
|
});
|
|
266
281
|
|
|
267
282
|
// Finalize the run row.
|
|
@@ -60,6 +60,13 @@ export interface ScannedFile {
|
|
|
60
60
|
readonly content: string;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Per-file byte cap for {@link scanFiles} (task 0060 R15). Larger files are skipped
|
|
65
|
+
* with no content buffer so a single oversized artifact cannot balloon memory while
|
|
66
|
+
* a rule corpus is assembled; evaluators still see every smaller file unchanged.
|
|
67
|
+
*/
|
|
68
|
+
const MAX_SCANNED_FILE_BYTES = 2_000_000;
|
|
69
|
+
|
|
63
70
|
/** How `scanFiles` matches `include` / `exclude` against discovered paths. */
|
|
64
71
|
export type ScanMatchMode = 'loose' | 'glob';
|
|
65
72
|
|
|
@@ -102,6 +109,9 @@ export async function scanFiles(options: ScanFilesOptions): Promise<ScannedFile[
|
|
|
102
109
|
: await discoverFilesByGlob(options.workdir, options.include, options.exclude, fs);
|
|
103
110
|
const scanned: ScannedFile[] = [];
|
|
104
111
|
for (const file of files) {
|
|
112
|
+
// Skip oversized files instead of buffering them (task 0060 R15).
|
|
113
|
+
const stat = await fs.stat(resolvePath(options.workdir, file));
|
|
114
|
+
if (stat !== null && stat.size > MAX_SCANNED_FILE_BYTES) continue;
|
|
105
115
|
scanned.push({ file, content: await readWorkdirFile(options.workdir, file, fs) });
|
|
106
116
|
}
|
|
107
117
|
return scanned;
|
package/src/events.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import type { EventSeverity } from '@gobing-ai/ts-infra';
|
|
1
2
|
import type { ConstraintFinding } from './types';
|
|
2
3
|
|
|
3
4
|
/** Typed event map for rule-engine run observability. All events prefixed `rule.`. */
|
|
4
5
|
export type RuleEngineEvents = {
|
|
5
6
|
/** Emitted before the first rule is evaluated. */
|
|
6
|
-
'rule.run.start': (data: { rules: number; total: number }) => void;
|
|
7
|
+
'rule.run.start': (data: { rules: number; total: number; severity: EventSeverity }) => void;
|
|
7
8
|
/** Emitted immediately before a single rule's evaluator is invoked. */
|
|
8
|
-
'rule.eval.start': (data: { ruleId: string; index: number; total: number }) => void;
|
|
9
|
+
'rule.eval.start': (data: { ruleId: string; index: number; total: number; severity: EventSeverity }) => void;
|
|
9
10
|
/**
|
|
10
11
|
* Emitted after a single rule evaluation finishes successfully.
|
|
11
12
|
* `findings` is the count; `details` carries the actual finding objects so
|
|
@@ -17,9 +18,16 @@ export type RuleEngineEvents = {
|
|
|
17
18
|
findings: number;
|
|
18
19
|
durationMs: number;
|
|
19
20
|
details: ConstraintFinding[];
|
|
21
|
+
severity: EventSeverity;
|
|
20
22
|
}) => void;
|
|
21
23
|
/** Emitted when a rule evaluator throws (in addition to the `kind:'error'` finding). */
|
|
22
|
-
'rule.eval.error': (data: { ruleId: string; error: string }) => void;
|
|
24
|
+
'rule.eval.error': (data: { ruleId: string; error: string; severity: EventSeverity }) => void;
|
|
23
25
|
/** Emitted after the last rule finishes (or was short-circuited). */
|
|
24
|
-
'rule.run.done': (data: {
|
|
26
|
+
'rule.run.done': (data: {
|
|
27
|
+
rules: number;
|
|
28
|
+
findings: number;
|
|
29
|
+
durationMs: number;
|
|
30
|
+
stoppedEarly: boolean;
|
|
31
|
+
severity: EventSeverity;
|
|
32
|
+
}) => void;
|
|
25
33
|
};
|