@gobing-ai/ts-rule-engine 0.2.1
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.md +3 -0
- package/dist/config/loader.d.ts +13 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +98 -0
- package/dist/engine.d.ts +21 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +36 -0
- package/dist/evaluators/agent-detection-evaluator.d.ts +10 -0
- package/dist/evaluators/agent-detection-evaluator.d.ts.map +1 -0
- package/dist/evaluators/agent-detection-evaluator.js +33 -0
- package/dist/evaluators/exit-code-evaluator.d.ts +10 -0
- package/dist/evaluators/exit-code-evaluator.d.ts.map +1 -0
- package/dist/evaluators/exit-code-evaluator.js +38 -0
- package/dist/evaluators/file-utils.d.ts +23 -0
- package/dist/evaluators/file-utils.d.ts.map +1 -0
- package/dist/evaluators/file-utils.js +35 -0
- package/dist/evaluators/forbidden-import-evaluator.d.ts +8 -0
- package/dist/evaluators/forbidden-import-evaluator.d.ts.map +1 -0
- package/dist/evaluators/forbidden-import-evaluator.js +42 -0
- package/dist/evaluators/path-evaluator.d.ts +9 -0
- package/dist/evaluators/path-evaluator.d.ts.map +1 -0
- package/dist/evaluators/path-evaluator.js +39 -0
- package/dist/evaluators/regex-evaluator.d.ts +8 -0
- package/dist/evaluators/regex-evaluator.d.ts.map +1 -0
- package/dist/evaluators/regex-evaluator.js +36 -0
- package/dist/evaluators/secrets-scanner-evaluator.d.ts +8 -0
- package/dist/evaluators/secrets-scanner-evaluator.d.ts.map +1 -0
- package/dist/evaluators/secrets-scanner-evaluator.js +24 -0
- package/dist/formatters/json.d.ts +8 -0
- package/dist/formatters/json.d.ts.map +1 -0
- package/dist/formatters/json.js +8 -0
- package/dist/formatters/text.d.ts +8 -0
- package/dist/formatters/text.d.ts.map +1 -0
- package/dist/formatters/text.js +17 -0
- package/dist/host/builtins.d.ts +5 -0
- package/dist/host/builtins.d.ts.map +1 -0
- package/dist/host/builtins.js +23 -0
- package/dist/host/capability-registry.d.ts +24 -0
- package/dist/host/capability-registry.d.ts.map +1 -0
- package/dist/host/capability-registry.js +28 -0
- package/dist/host/rule-engine-host.d.ts +11 -0
- package/dist/host/rule-engine-host.d.ts.map +1 -0
- package/dist/host/rule-engine-host.js +12 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/types.d.ts +218 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +49 -0
- package/package.json +61 -0
- package/src/config/loader.ts +122 -0
- package/src/engine.ts +50 -0
- package/src/evaluators/agent-detection-evaluator.ts +37 -0
- package/src/evaluators/exit-code-evaluator.ts +42 -0
- package/src/evaluators/file-utils.ts +55 -0
- package/src/evaluators/forbidden-import-evaluator.ts +50 -0
- package/src/evaluators/path-evaluator.ts +48 -0
- package/src/evaluators/regex-evaluator.ts +49 -0
- package/src/evaluators/secrets-scanner-evaluator.ts +34 -0
- package/src/formatters/json.ts +11 -0
- package/src/formatters/text.ts +20 -0
- package/src/host/builtins.ts +26 -0
- package/src/host/capability-registry.ts +41 -0
- package/src/host/rule-engine-host.ts +15 -0
- package/src/index.ts +7 -0
- package/src/types.ts +197 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AgentDetectionEvaluator } from '../evaluators/agent-detection-evaluator.js';
|
|
2
|
+
import { ExitCodeEvaluator } from '../evaluators/exit-code-evaluator.js';
|
|
3
|
+
import { ForbiddenImportEvaluator } from '../evaluators/forbidden-import-evaluator.js';
|
|
4
|
+
import { PathEvaluator } from '../evaluators/path-evaluator.js';
|
|
5
|
+
import { RegexEvaluator } from '../evaluators/regex-evaluator.js';
|
|
6
|
+
import { SecretsScannerEvaluator } from '../evaluators/secrets-scanner-evaluator.js';
|
|
7
|
+
import { JsonFormatter } from '../formatters/json.js';
|
|
8
|
+
import { TextFormatter } from '../formatters/text.js';
|
|
9
|
+
/** Register bundled evaluators and formatters on a host. */
|
|
10
|
+
export function registerBuiltins(host, executor) {
|
|
11
|
+
const regex = new RegexEvaluator();
|
|
12
|
+
const path = new PathEvaluator();
|
|
13
|
+
host.evaluators.register('regex', regex, 'builtin');
|
|
14
|
+
host.evaluators.register('rg', regex, 'builtin');
|
|
15
|
+
host.evaluators.register('path', path, 'builtin');
|
|
16
|
+
host.evaluators.register('file-exist', path, 'builtin');
|
|
17
|
+
host.evaluators.register('forbidden-import', new ForbiddenImportEvaluator(), 'builtin');
|
|
18
|
+
host.evaluators.register('exit-code', new ExitCodeEvaluator(executor), 'builtin');
|
|
19
|
+
host.evaluators.register('secrets-scanner', new SecretsScannerEvaluator(), 'builtin');
|
|
20
|
+
host.evaluators.register('agent-detection', new AgentDetectionEvaluator(), 'builtin');
|
|
21
|
+
host.formatters.register('text', new TextFormatter(), 'builtin');
|
|
22
|
+
host.formatters.register('json', new JsonFormatter(), 'builtin');
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Registry origin for a host capability. */
|
|
2
|
+
export type CapabilityOrigin = 'builtin' | 'extension';
|
|
3
|
+
/** Registry entry metadata. */
|
|
4
|
+
export interface CapabilityEntry<TCapability> {
|
|
5
|
+
/** Capability implementation. */
|
|
6
|
+
capability: TCapability;
|
|
7
|
+
/** Registration origin. */
|
|
8
|
+
origin: CapabilityOrigin;
|
|
9
|
+
}
|
|
10
|
+
/** Typed registry used by the rule engine host. */
|
|
11
|
+
export declare class CapabilityRegistry<TCapability> {
|
|
12
|
+
private readonly kind;
|
|
13
|
+
private readonly capabilities;
|
|
14
|
+
constructor(kind: string);
|
|
15
|
+
/** Register or replace a capability. */
|
|
16
|
+
register(name: string, capability: TCapability, origin?: CapabilityOrigin): void;
|
|
17
|
+
/** Return true when a capability exists. */
|
|
18
|
+
has(name: string): boolean;
|
|
19
|
+
/** Get a registered capability or throw a clear error. */
|
|
20
|
+
get(name: string): TCapability;
|
|
21
|
+
/** List registered capability names. */
|
|
22
|
+
list(): string[];
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=capability-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-registry.d.ts","sourceRoot":"","sources":["../../src/host/capability-registry.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,CAAC;AAEvD,+BAA+B;AAC/B,MAAM,WAAW,eAAe,CAAC,WAAW;IACxC,iCAAiC;IACjC,UAAU,EAAE,WAAW,CAAC;IACxB,2BAA2B;IAC3B,MAAM,EAAE,gBAAgB,CAAC;CAC5B;AAED,mDAAmD;AACnD,qBAAa,kBAAkB,CAAC,WAAW;IAG3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmD;gBAEnD,IAAI,EAAE,MAAM;IAEzC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAE,gBAA8B,GAAG,IAAI;IAI7F,4CAA4C;IAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,0DAA0D;IAC1D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW;IAQ9B,wCAAwC;IACxC,IAAI,IAAI,MAAM,EAAE;CAGnB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Typed registry used by the rule engine host. */
|
|
2
|
+
export class CapabilityRegistry {
|
|
3
|
+
kind;
|
|
4
|
+
capabilities = new Map();
|
|
5
|
+
constructor(kind) {
|
|
6
|
+
this.kind = kind;
|
|
7
|
+
}
|
|
8
|
+
/** Register or replace a capability. */
|
|
9
|
+
register(name, capability, origin = 'extension') {
|
|
10
|
+
this.capabilities.set(name, { capability, origin });
|
|
11
|
+
}
|
|
12
|
+
/** Return true when a capability exists. */
|
|
13
|
+
has(name) {
|
|
14
|
+
return this.capabilities.has(name);
|
|
15
|
+
}
|
|
16
|
+
/** Get a registered capability or throw a clear error. */
|
|
17
|
+
get(name) {
|
|
18
|
+
const entry = this.capabilities.get(name);
|
|
19
|
+
if (entry === undefined) {
|
|
20
|
+
throw new Error(`Unknown ${this.kind}: ${name}`);
|
|
21
|
+
}
|
|
22
|
+
return entry.capability;
|
|
23
|
+
}
|
|
24
|
+
/** List registered capability names. */
|
|
25
|
+
list() {
|
|
26
|
+
return [...this.capabilities.keys()];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ResultFormatter, RuleEvaluator } from '../types';
|
|
2
|
+
import { CapabilityRegistry } from './capability-registry';
|
|
3
|
+
/** Host container for rule-engine capabilities. */
|
|
4
|
+
export declare class RuleEngineHost {
|
|
5
|
+
/** Evaluator registry keyed by evaluator type. */
|
|
6
|
+
readonly evaluators: CapabilityRegistry<RuleEvaluator>;
|
|
7
|
+
/** Formatter registry keyed by formatter name. */
|
|
8
|
+
readonly formatters: CapabilityRegistry<ResultFormatter>;
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=rule-engine-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-engine-host.d.ts","sourceRoot":"","sources":["../../src/host/rule-engine-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,mDAAmD;AACnD,qBAAa,cAAc;IACvB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACvD,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;;CAM5D"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CapabilityRegistry } from './capability-registry.js';
|
|
2
|
+
/** Host container for rule-engine capabilities. */
|
|
3
|
+
export class RuleEngineHost {
|
|
4
|
+
/** Evaluator registry keyed by evaluator type. */
|
|
5
|
+
evaluators;
|
|
6
|
+
/** Formatter registry keyed by formatter name. */
|
|
7
|
+
formatters;
|
|
8
|
+
constructor() {
|
|
9
|
+
this.evaluators = new CapabilityRegistry('evaluator');
|
|
10
|
+
this.formatters = new CapabilityRegistry('formatter');
|
|
11
|
+
}
|
|
12
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './config/loader';
|
|
2
|
+
export * from './engine';
|
|
3
|
+
export * from './formatters/json';
|
|
4
|
+
export * from './formatters/text';
|
|
5
|
+
export * from './host/capability-registry';
|
|
6
|
+
export * from './host/rule-engine-host';
|
|
7
|
+
export * from './types';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './config/loader.js';
|
|
2
|
+
export * from './engine.js';
|
|
3
|
+
export * from './formatters/json.js';
|
|
4
|
+
export * from './formatters/text.js';
|
|
5
|
+
export * from './host/capability-registry.js';
|
|
6
|
+
export * from './host/rule-engine-host.js';
|
|
7
|
+
export * from './types.js';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Finding severity emitted by the rule engine. */
|
|
3
|
+
export type RuleSeverity = 'error' | 'warning' | 'info';
|
|
4
|
+
/** Fix authority level for candidate fixes. */
|
|
5
|
+
export type FixMode = 'none' | 'suggest' | 'auto';
|
|
6
|
+
/** Structured configuration for a rule evaluator. */
|
|
7
|
+
export interface RuleEvaluatorConfig {
|
|
8
|
+
/** Evaluator type key registered in the host. */
|
|
9
|
+
type: string;
|
|
10
|
+
/** Evaluator-specific options. */
|
|
11
|
+
config?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
/** Constraint rule definition loaded from YAML or JSON. */
|
|
14
|
+
export interface ConstraintRule {
|
|
15
|
+
/** Stable rule identifier. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Human-readable description. */
|
|
18
|
+
description: string;
|
|
19
|
+
/** Whether this rule is active. */
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
/** Finding severity emitted by this rule. */
|
|
22
|
+
severity: RuleSeverity;
|
|
23
|
+
/** Evaluator configuration. */
|
|
24
|
+
evaluator: RuleEvaluatorConfig;
|
|
25
|
+
/** Optional include globs or paths. */
|
|
26
|
+
include?: string[];
|
|
27
|
+
/** Optional exclude globs or paths. */
|
|
28
|
+
exclude?: string[];
|
|
29
|
+
/** Optional fix metadata. */
|
|
30
|
+
fix?: RuleFixConfig;
|
|
31
|
+
}
|
|
32
|
+
/** Fix configuration authored on a rule. */
|
|
33
|
+
export interface RuleFixConfig {
|
|
34
|
+
/** Maximum fix mode allowed by the rule. */
|
|
35
|
+
mode: FixMode;
|
|
36
|
+
/** Optional replacement text used by simple fixers. */
|
|
37
|
+
replacement?: string;
|
|
38
|
+
/** Optional fixer-specific parameters. */
|
|
39
|
+
params?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
/** Rule file shape before normalization. */
|
|
42
|
+
export interface ConstraintRuleFile {
|
|
43
|
+
/** File-level default include patterns. */
|
|
44
|
+
include?: string[];
|
|
45
|
+
/** File-level default exclude patterns. */
|
|
46
|
+
exclude?: string[];
|
|
47
|
+
/** File-level default severity. */
|
|
48
|
+
severity?: RuleSeverity;
|
|
49
|
+
/** Rule definitions. */
|
|
50
|
+
rules: ConstraintRule[];
|
|
51
|
+
}
|
|
52
|
+
/** Preset definition that composes category folders or other presets. */
|
|
53
|
+
export interface PresetDefinition {
|
|
54
|
+
/** Preset name. */
|
|
55
|
+
name: string;
|
|
56
|
+
/** Category folders or preset names to compose. */
|
|
57
|
+
extends: string[];
|
|
58
|
+
/** Rule IDs to disable. */
|
|
59
|
+
disable?: string[];
|
|
60
|
+
/** Per-rule overrides. */
|
|
61
|
+
overrides?: Record<string, {
|
|
62
|
+
fix?: {
|
|
63
|
+
mode: FixMode;
|
|
64
|
+
};
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
/** Candidate fix emitted by an evaluator or fixer. */
|
|
68
|
+
export interface Fix {
|
|
69
|
+
/** Rule that produced this fix. */
|
|
70
|
+
ruleId: string;
|
|
71
|
+
/** Relative or absolute target file path. */
|
|
72
|
+
filePath: string;
|
|
73
|
+
/** Byte start offset. */
|
|
74
|
+
start: number;
|
|
75
|
+
/** Byte end offset. */
|
|
76
|
+
end: number;
|
|
77
|
+
/** Replacement content. */
|
|
78
|
+
replacement: string;
|
|
79
|
+
/** Whether this fix may be applied automatically. */
|
|
80
|
+
mode: Exclude<FixMode, 'none'>;
|
|
81
|
+
}
|
|
82
|
+
/** Finding emitted by a constraint rule. */
|
|
83
|
+
export interface ConstraintFinding {
|
|
84
|
+
/** Rule identifier. */
|
|
85
|
+
ruleId: string;
|
|
86
|
+
/** Finding severity. */
|
|
87
|
+
severity: RuleSeverity;
|
|
88
|
+
/** Finding message. */
|
|
89
|
+
message: string;
|
|
90
|
+
/** Relative or absolute path involved in the finding. */
|
|
91
|
+
filePath: string | null;
|
|
92
|
+
/** Optional one-based line number. */
|
|
93
|
+
line?: number;
|
|
94
|
+
/** Optional column number. */
|
|
95
|
+
column?: number;
|
|
96
|
+
/** Machine-readable evaluator/source code. */
|
|
97
|
+
code?: string;
|
|
98
|
+
}
|
|
99
|
+
/** Aggregate result returned by a rule evaluator. */
|
|
100
|
+
export interface RuleEvaluationResult {
|
|
101
|
+
/** Findings emitted by the evaluator. */
|
|
102
|
+
findings: ConstraintFinding[];
|
|
103
|
+
/** Candidate fixes emitted by the evaluator. */
|
|
104
|
+
fixes: Fix[];
|
|
105
|
+
}
|
|
106
|
+
/** Context passed to evaluator implementations. */
|
|
107
|
+
export interface RuleContext {
|
|
108
|
+
/** Working directory being evaluated. */
|
|
109
|
+
workdir: string;
|
|
110
|
+
/** Rule being evaluated. */
|
|
111
|
+
rule: ConstraintRule;
|
|
112
|
+
}
|
|
113
|
+
/** Evaluator implementation contract. */
|
|
114
|
+
export interface RuleEvaluator {
|
|
115
|
+
/** Evaluate a rule against the supplied context. */
|
|
116
|
+
evaluate(rule: ConstraintRule, context: RuleContext): Promise<RuleEvaluationResult>;
|
|
117
|
+
}
|
|
118
|
+
/** Formatter implementation contract. */
|
|
119
|
+
export interface ResultFormatter {
|
|
120
|
+
/** Format an engine result for CLI or machine output. */
|
|
121
|
+
format(result: RuleEngineResult): string;
|
|
122
|
+
}
|
|
123
|
+
/** Engine-level evaluation result. */
|
|
124
|
+
export interface RuleEngineResult {
|
|
125
|
+
/** Findings emitted by enabled rules. */
|
|
126
|
+
findings: ConstraintFinding[];
|
|
127
|
+
/** Candidate fixes emitted by enabled rules. */
|
|
128
|
+
fixes: Fix[];
|
|
129
|
+
}
|
|
130
|
+
/** Create a finding with inherited rule severity. */
|
|
131
|
+
export declare function createFinding(rule: ConstraintRule, message: string, filePath: string | null, extras?: Omit<Partial<ConstraintFinding>, 'ruleId' | 'severity' | 'message' | 'filePath'>): ConstraintFinding;
|
|
132
|
+
/** Zod schema for rule fix configuration. */
|
|
133
|
+
export declare const RuleFixConfigSchema: z.ZodDefault<z.ZodObject<{
|
|
134
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
135
|
+
none: "none";
|
|
136
|
+
suggest: "suggest";
|
|
137
|
+
auto: "auto";
|
|
138
|
+
}>>;
|
|
139
|
+
replacement: z.ZodOptional<z.ZodString>;
|
|
140
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
141
|
+
}, z.core.$strip>>;
|
|
142
|
+
/** Zod schema for a single constraint rule. */
|
|
143
|
+
export declare const ConstraintRuleSchema: z.ZodObject<{
|
|
144
|
+
id: z.ZodString;
|
|
145
|
+
description: z.ZodDefault<z.ZodString>;
|
|
146
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
147
|
+
severity: z.ZodDefault<z.ZodEnum<{
|
|
148
|
+
error: "error";
|
|
149
|
+
warning: "warning";
|
|
150
|
+
info: "info";
|
|
151
|
+
}>>;
|
|
152
|
+
evaluator: z.ZodObject<{
|
|
153
|
+
type: z.ZodString;
|
|
154
|
+
config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
155
|
+
}, z.core.$strip>;
|
|
156
|
+
include: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
157
|
+
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
158
|
+
fix: z.ZodOptional<z.ZodDefault<z.ZodObject<{
|
|
159
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
160
|
+
none: "none";
|
|
161
|
+
suggest: "suggest";
|
|
162
|
+
auto: "auto";
|
|
163
|
+
}>>;
|
|
164
|
+
replacement: z.ZodOptional<z.ZodString>;
|
|
165
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
166
|
+
}, z.core.$strip>>>;
|
|
167
|
+
}, z.core.$strip>;
|
|
168
|
+
/** Zod schema for a constraint rule file. */
|
|
169
|
+
export declare const ConstraintRuleFileSchema: z.ZodObject<{
|
|
170
|
+
include: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
171
|
+
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
172
|
+
severity: z.ZodOptional<z.ZodEnum<{
|
|
173
|
+
error: "error";
|
|
174
|
+
warning: "warning";
|
|
175
|
+
info: "info";
|
|
176
|
+
}>>;
|
|
177
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
178
|
+
id: z.ZodString;
|
|
179
|
+
description: z.ZodDefault<z.ZodString>;
|
|
180
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
181
|
+
severity: z.ZodDefault<z.ZodEnum<{
|
|
182
|
+
error: "error";
|
|
183
|
+
warning: "warning";
|
|
184
|
+
info: "info";
|
|
185
|
+
}>>;
|
|
186
|
+
evaluator: z.ZodObject<{
|
|
187
|
+
type: z.ZodString;
|
|
188
|
+
config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
189
|
+
}, z.core.$strip>;
|
|
190
|
+
include: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
191
|
+
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
192
|
+
fix: z.ZodOptional<z.ZodDefault<z.ZodObject<{
|
|
193
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
194
|
+
none: "none";
|
|
195
|
+
suggest: "suggest";
|
|
196
|
+
auto: "auto";
|
|
197
|
+
}>>;
|
|
198
|
+
replacement: z.ZodOptional<z.ZodString>;
|
|
199
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
200
|
+
}, z.core.$strip>>>;
|
|
201
|
+
}, z.core.$strip>>;
|
|
202
|
+
}, z.core.$strip>;
|
|
203
|
+
/** Zod schema for a preset definition. */
|
|
204
|
+
export declare const PresetDefinitionSchema: z.ZodObject<{
|
|
205
|
+
name: z.ZodString;
|
|
206
|
+
extends: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
207
|
+
disable: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
208
|
+
overrides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
209
|
+
fix: z.ZodOptional<z.ZodObject<{
|
|
210
|
+
mode: z.ZodEnum<{
|
|
211
|
+
none: "none";
|
|
212
|
+
suggest: "suggest";
|
|
213
|
+
auto: "auto";
|
|
214
|
+
}>;
|
|
215
|
+
}, z.core.$strip>>;
|
|
216
|
+
}, z.core.$strip>>>;
|
|
217
|
+
}, z.core.$strip>;
|
|
218
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Create a finding with inherited rule severity. */
|
|
3
|
+
export function createFinding(rule, message, filePath, extras = {}) {
|
|
4
|
+
return {
|
|
5
|
+
ruleId: rule.id,
|
|
6
|
+
severity: rule.severity,
|
|
7
|
+
message,
|
|
8
|
+
filePath,
|
|
9
|
+
...extras,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** Zod schema for rule fix configuration. */
|
|
13
|
+
export const RuleFixConfigSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
mode: z.enum(['none', 'suggest', 'auto']).default('none'),
|
|
16
|
+
replacement: z.string().optional(),
|
|
17
|
+
params: z.record(z.string(), z.unknown()).optional(),
|
|
18
|
+
})
|
|
19
|
+
.default({ mode: 'none' });
|
|
20
|
+
/** Zod schema for a single constraint rule. */
|
|
21
|
+
export const ConstraintRuleSchema = z.object({
|
|
22
|
+
id: z.string().min(1),
|
|
23
|
+
description: z.string().default(''),
|
|
24
|
+
enabled: z.boolean().default(true),
|
|
25
|
+
severity: z.enum(['error', 'warning', 'info']).default('error'),
|
|
26
|
+
evaluator: z.object({
|
|
27
|
+
type: z.string().min(1),
|
|
28
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
29
|
+
}),
|
|
30
|
+
include: z.array(z.string()).optional(),
|
|
31
|
+
exclude: z.array(z.string()).optional(),
|
|
32
|
+
fix: RuleFixConfigSchema.optional(),
|
|
33
|
+
});
|
|
34
|
+
/** Zod schema for a constraint rule file. */
|
|
35
|
+
export const ConstraintRuleFileSchema = z.object({
|
|
36
|
+
include: z.array(z.string()).optional(),
|
|
37
|
+
exclude: z.array(z.string()).optional(),
|
|
38
|
+
severity: z.enum(['error', 'warning', 'info']).optional(),
|
|
39
|
+
rules: z.array(ConstraintRuleSchema),
|
|
40
|
+
});
|
|
41
|
+
/** Zod schema for a preset definition. */
|
|
42
|
+
export const PresetDefinitionSchema = z.object({
|
|
43
|
+
name: z.string().min(1),
|
|
44
|
+
extends: z.array(z.string()).default([]),
|
|
45
|
+
disable: z.array(z.string()).optional(),
|
|
46
|
+
overrides: z
|
|
47
|
+
.record(z.string(), z.object({ fix: z.object({ mode: z.enum(['none', 'suggest', 'auto']) }).optional() }))
|
|
48
|
+
.optional(),
|
|
49
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gobing-ai/ts-rule-engine",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "@gobing-ai/ts-rule-engine — Constraint rule schemas, loading, evaluation, and result formatting.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"typescript",
|
|
7
|
+
"rules",
|
|
8
|
+
"policy",
|
|
9
|
+
"quality",
|
|
10
|
+
"engine"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/gobing-ai/ts-libs.git",
|
|
15
|
+
"directory": "packages/rule-engine"
|
|
16
|
+
},
|
|
17
|
+
"author": "Robin Min <minlongbing@gmail.com>",
|
|
18
|
+
"contributors": [
|
|
19
|
+
"Robin Min <minlongbing@gmail.com>"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"private": false,
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.build.json && bun ../../scripts/builder.ts fix-dist-esm-extensions dist",
|
|
40
|
+
"test": "NODE_ENV=test bun test --coverage --coverage-dir=.coverage --reporter=dots",
|
|
41
|
+
"test:full": "NODE_ENV=test bun test --update-snapshots --coverage --coverage-dir=.coverage",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"lint": "biome check . && bun run typecheck",
|
|
44
|
+
"format": "biome check . --write",
|
|
45
|
+
"check": "bun run lint && bun run test",
|
|
46
|
+
"prepublishOnly": "bun run build",
|
|
47
|
+
"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"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@gobing-ai/ts-ai-runner": "workspace:*",
|
|
51
|
+
"@gobing-ai/ts-runtime": "workspace:*",
|
|
52
|
+
"yaml": "^2.7.0",
|
|
53
|
+
"zod": "^4.1.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/bun": "1.3.14"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
2
|
+
import { NodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
|
+
import {
|
|
5
|
+
type ConstraintRule,
|
|
6
|
+
type ConstraintRuleFile,
|
|
7
|
+
ConstraintRuleFileSchema,
|
|
8
|
+
ConstraintRuleSchema,
|
|
9
|
+
type PresetDefinition,
|
|
10
|
+
PresetDefinitionSchema,
|
|
11
|
+
} from '../types';
|
|
12
|
+
|
|
13
|
+
/** Options for loading rule presets. */
|
|
14
|
+
export interface RuleLoaderOptions {
|
|
15
|
+
/** Project working directory. */
|
|
16
|
+
workdir: string;
|
|
17
|
+
/** Rule root directory. Defaults to ".spur/rules". */
|
|
18
|
+
rulesRoot?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Load and normalize a preset by name. */
|
|
22
|
+
export async function loadPresetRules(name: string, options: RuleLoaderOptions): Promise<ConstraintRule[]> {
|
|
23
|
+
const fs = new NodeFileSystem();
|
|
24
|
+
const root = resolve(options.workdir, options.rulesRoot ?? '.spur/rules');
|
|
25
|
+
const presetPath = await findDefinitionPath(root, name);
|
|
26
|
+
if (presetPath === null) return [];
|
|
27
|
+
const preset = PresetDefinitionSchema.parse(await readStructuredFile(presetPath)) as PresetDefinition;
|
|
28
|
+
const rules: ConstraintRule[] = [];
|
|
29
|
+
for (const entry of preset.extends) {
|
|
30
|
+
rules.push(...(await loadPresetEntry(root, entry, new Set([name]))));
|
|
31
|
+
}
|
|
32
|
+
const disabled = new Set(preset.disable ?? []);
|
|
33
|
+
const normalized = rules.filter((rule) => !disabled.has(rule.id));
|
|
34
|
+
for (const rule of normalized) {
|
|
35
|
+
const override = preset.overrides?.[rule.id];
|
|
36
|
+
if (override?.fix !== undefined) {
|
|
37
|
+
rule.fix = { ...(rule.fix ?? { mode: 'none' }), ...override.fix };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
await fs.exists(root);
|
|
41
|
+
return normalized;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Load a direct rule file from disk. */
|
|
45
|
+
export async function loadRuleFile(filePath: string): Promise<ConstraintRule[]> {
|
|
46
|
+
return normalizeRuleFile(await readStructuredFile(resolve(filePath)), dirname(resolve(filePath)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function loadPresetEntry(root: string, entry: string, seen: Set<string>): Promise<ConstraintRule[]> {
|
|
50
|
+
const presetPath = await findDefinitionPath(root, entry);
|
|
51
|
+
if (presetPath !== null && !seen.has(entry)) {
|
|
52
|
+
seen.add(entry);
|
|
53
|
+
const preset = PresetDefinitionSchema.safeParse(await readStructuredFile(presetPath));
|
|
54
|
+
if (preset.success) {
|
|
55
|
+
const rules: ConstraintRule[] = [];
|
|
56
|
+
for (const child of preset.data.extends) rules.push(...(await loadPresetEntry(root, child, seen)));
|
|
57
|
+
return rules.filter((rule) => !(preset.data.disable ?? []).includes(rule.id));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const categoryDir = resolve(root, entry);
|
|
62
|
+
const fs = new NodeFileSystem();
|
|
63
|
+
if (!(await fs.exists(categoryDir))) return [];
|
|
64
|
+
const entries = (await fs.readDir(categoryDir)).filter((file) => /\.(ya?ml|json)$/i.test(file)).sort();
|
|
65
|
+
const rules: ConstraintRule[] = [];
|
|
66
|
+
for (const file of entries) {
|
|
67
|
+
rules.push(...(await loadRuleFile(join(categoryDir, file))));
|
|
68
|
+
}
|
|
69
|
+
return rules;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function findDefinitionPath(root: string, name: string): Promise<string | null> {
|
|
73
|
+
const fs = new NodeFileSystem();
|
|
74
|
+
const candidates = [
|
|
75
|
+
resolve(root, `${name}.yaml`),
|
|
76
|
+
resolve(root, `${name}.yml`),
|
|
77
|
+
resolve(root, `${name}.json`),
|
|
78
|
+
resolve(root, name, 'index.yaml'),
|
|
79
|
+
resolve(root, name, 'index.yml'),
|
|
80
|
+
resolve(root, name, 'index.json'),
|
|
81
|
+
];
|
|
82
|
+
for (const candidate of candidates) {
|
|
83
|
+
if (await fs.exists(candidate)) return candidate;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function readStructuredFile(path: string): Promise<unknown> {
|
|
89
|
+
const content = await new NodeFileSystem().readFile(path);
|
|
90
|
+
return extname(path) === '.json' ? JSON.parse(content) : parse(content);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeRuleFile(raw: unknown, sourceDir: string): ConstraintRule[] {
|
|
94
|
+
const maybeFile = ConstraintRuleFileSchema.safeParse(raw);
|
|
95
|
+
if (maybeFile.success) return normalizeFileRules(maybeFile.data, sourceDir);
|
|
96
|
+
const maybeRule = ConstraintRuleSchema.safeParse(raw);
|
|
97
|
+
if (maybeRule.success) return [normalizeRule(maybeRule.data, {}, sourceDir)];
|
|
98
|
+
throw new Error(`Invalid rule file: ${basename(sourceDir)}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeFileRules(file: ConstraintRuleFile, sourceDir: string): ConstraintRule[] {
|
|
102
|
+
return file.rules.map((rule) =>
|
|
103
|
+
normalizeRule(
|
|
104
|
+
{
|
|
105
|
+
...rule,
|
|
106
|
+
severity: rule.severity ?? file.severity ?? 'error',
|
|
107
|
+
include: rule.include ?? file.include,
|
|
108
|
+
exclude: rule.exclude ?? file.exclude,
|
|
109
|
+
},
|
|
110
|
+
{},
|
|
111
|
+
sourceDir,
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeRule(rule: ConstraintRule, _defaults: Partial<ConstraintRule>, _sourceDir: string): ConstraintRule {
|
|
117
|
+
return {
|
|
118
|
+
...rule,
|
|
119
|
+
enabled: rule.enabled ?? true,
|
|
120
|
+
severity: rule.severity ?? 'error',
|
|
121
|
+
};
|
|
122
|
+
}
|
package/src/engine.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
2
|
+
import { registerBuiltins } from './host/builtins';
|
|
3
|
+
import { RuleEngineHost } from './host/rule-engine-host';
|
|
4
|
+
import type { ConstraintFinding, ConstraintRule, RuleEngineResult, RuleEvaluator } from './types';
|
|
5
|
+
import { createFinding } from './types';
|
|
6
|
+
|
|
7
|
+
/** Options for constructing a RuleEngine. */
|
|
8
|
+
export interface RuleEngineOptions {
|
|
9
|
+
/** Optional executor supplied to process-backed evaluators. */
|
|
10
|
+
processExecutor?: ProcessExecutor;
|
|
11
|
+
/** Optional preconfigured host. */
|
|
12
|
+
host?: RuleEngineHost;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Orchestrates enabled constraint rules through a typed evaluator host. */
|
|
16
|
+
export class RuleEngine {
|
|
17
|
+
/** Capability host used by this engine. */
|
|
18
|
+
readonly host: RuleEngineHost;
|
|
19
|
+
|
|
20
|
+
constructor(options: RuleEngineOptions = {}) {
|
|
21
|
+
this.host = options.host ?? new RuleEngineHost();
|
|
22
|
+
registerBuiltins(this.host, options.processExecutor);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Register or replace an evaluator. */
|
|
26
|
+
registerEvaluator(type: string, evaluator: RuleEvaluator): void {
|
|
27
|
+
this.host.evaluators.register(type, evaluator, 'extension');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Evaluate all enabled rules against a working directory. */
|
|
31
|
+
async evaluate(rules: ConstraintRule[], workdir: string): Promise<RuleEngineResult> {
|
|
32
|
+
const findings: ConstraintFinding[] = [];
|
|
33
|
+
const fixes = [];
|
|
34
|
+
for (const rule of rules) {
|
|
35
|
+
if (rule.enabled === false) continue;
|
|
36
|
+
try {
|
|
37
|
+
const result = await this.host.evaluators.get(rule.evaluator.type).evaluate(rule, { rule, workdir });
|
|
38
|
+
findings.push(...result.findings);
|
|
39
|
+
fixes.push(...result.fixes);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
findings.push(
|
|
42
|
+
createFinding(rule, error instanceof Error ? error.message : String(error), null, {
|
|
43
|
+
code: `evaluator:${rule.evaluator.type}`,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { findings, fixes };
|
|
49
|
+
}
|
|
50
|
+
}
|