@gobing-ai/ts-rule-engine 0.3.14 → 0.3.17

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 CHANGED
@@ -31,7 +31,7 @@ erDiagram
31
31
 
32
32
  | Entity | One-line Description |
33
33
  |--------|----------------------|
34
- | `RuleEngine` | Orchestrates evaluation of enabled rules against a workspace directory. Supports opt-in early exit via `stopOnFirst` parameter. Accepts an optional `EventBus<RuleEngineEvents>` for structured in-process observability. |
34
+ | `RuleEngine` | Orchestrates evaluation of enabled rules against a workspace directory. Constructor accepts `RuleEngineOptions` (host, persistence, events, runId, runMeta, etc.). Supports opt-in early exit via `stopOnFirst` parameter. |
35
35
  | `RuleEngineHost` | Capability host backed by `CapabilityRegistry` from `@gobing-ai/ts-runtime/extension` — holds evaluators, resolvers, formatters, and fixer providers, each with origin tracking for safe override detection. |
36
36
  | `CapabilityRegistry<T>` | Generic named registry (shared with `ts-rule-engine`, `ts-dual-workflow-engine`) that tags each entry with its `origin` (`'builtin'`, `'extension'`, `'caller'`). |
37
37
  | `ConstraintRule` | Declarative policy unit: id, severity, include/exclude globs, evaluator type + config, and optional fix config. |
@@ -45,6 +45,8 @@ erDiagram
45
45
  | `Fix` | Candidate byte-range replacement; collected separately from findings, written only on explicit `applyFixes()`. |
46
46
  | `RuleEngineResult` | Aggregate `{ findings, fixes }` returned from a single evaluation run. |
47
47
  | `RuleEngineEvents` | Typed event map for rule-engine observability. All events prefixed `rule.` — see [Observability](#observability). |
48
+ | `RulePersistenceAdapter` | Durable adapter contract. Engine calls `insertRun`/`updateRunStatus` for the run, `insertEvalRun`/`updateEvalRun` per rule. DB-backed and memory-backed implementations included. |
49
+ | `RULE_ENGINE_SCHEMA_SQL` | Engine-owned DDL export: `rule_runs` + `rule_eval_runs` tables plus the FK index. Consumers apply it up-front before creating a `DbRulePersistenceAdapter`. |
48
50
  | `bundledRulesRoot()` | Resolves the absolute path to the bundled `rules/` directory shipped with this package — portable defaults usable as the lowest-priority preset root. |
49
51
  ## Mental Model
50
52
 
@@ -65,11 +67,12 @@ flowchart TD
65
67
  Core concepts:
66
68
 
67
69
  - `ConstraintRule`: one policy check. It has an `id`, `severity`, evaluator type/config, optional include/exclude globs, and optional fix config.
68
- - `RuleEngine`: runs enabled rules against a `workdir`. The constructor auto-registers built-in evaluators, formatters, resolvers, and fixer providers.
70
+ - `RuleEngine`: runs enabled rules against a `workdir`. The constructor auto-registers built-in evaluators, formatters, resolvers, and fixer providers. Accepts `RuleEngineOptions` for a custom host, `EventBus`, `processExecutor`, `persistence` adapter, `runId`, and `runMeta`.
69
71
  - `RuleEngineHost`: capability container backed by four `CapabilityRegistry` instances from `@gobing-ai/ts-runtime/extension`. Each entry tracks its origin (`'builtin'` / `'extension'` / `'caller'`) so the engine can detect and report conflicting registrations.
70
72
  - `RuleEvaluator`: implementation of one rule type, such as `regex`, `path`, or `coverage-gate`.
71
73
  - `Fix`: byte-range replacement candidate. Fixes are collected separately from findings and only written when you call `applyFixes()`.
72
74
  - Preset: YAML/JSON file that composes rule categories and can expose extension modules.
75
+ - `RulePersistenceAdapter`: durable run/eval history. The engine writes directly to the adapter at lifecycle boundaries; a DB-backed adapter and a test-friendly memory adapter ship with the package.
73
76
  - `bundledRulesRoot()`: resolves the path to the `rules/` directory shipped with this package. Pass it as the lowest-priority root to `loadPreset()` so project-local and user-global roots shadow individual files while inheriting the rest.
74
77
 
75
78
  ## Execution Flow
@@ -819,15 +822,121 @@ When no `events` option is provided, the engine incurs zero observability overhe
819
822
 
820
823
  `rule.eval.error` is emitted when an evaluator **throws** — it signals a crash, not a policy violation. The engine still produces a `kind: 'error'` finding for the thrown rule. A normal policy violation emits **no** `rule.eval.error`. Don't conflate the two: subscribe to `rule.eval.error` for crash alerting, and inspect findings for policy results.
821
824
 
825
+ ## Persistence
826
+
827
+ The engine supports a **durable persistence adapter** pattern (mirrors `@gobing-ai/ts-dual-workflow-engine`). When a `RulePersistenceAdapter` is injected via `RuleEngineOptions`, the engine writes run and per-rule evaluation rows directly — no EventBus subscriber needed.
828
+
829
+ ### DB Schema
830
+
831
+ ```mermaid
832
+ erDiagram
833
+ RULE_RUNS ||--o{ RULE_EVAL_RUNS : "run_id → id"
834
+ RULE_RUNS {
835
+ TEXT id PK
836
+ TEXT preset
837
+ TEXT source_kind "preset | file"
838
+ TEXT source_value
839
+ TEXT status "running | done | failed"
840
+ INTEGER rule_count
841
+ INTEGER finding_count
842
+ INTEGER fix_count
843
+ INTEGER applied_fix_count
844
+ TEXT fail_on
845
+ TEXT stop_on_first
846
+ TEXT fix_mode "none | suggest | auto"
847
+ INTEGER dry_run
848
+ TEXT started_at "ISO 8601"
849
+ TEXT completed_at "ISO 8601"
850
+ INTEGER duration_ms
851
+ TEXT metadata_json "{}"
852
+ TEXT created_at "ISO 8601"
853
+ TEXT updated_at "ISO 8601"
854
+ }
855
+ RULE_EVAL_RUNS {
856
+ TEXT id PK
857
+ TEXT run_id FK
858
+ TEXT rule_id
859
+ TEXT severity "error | warning | info"
860
+ TEXT evaluator
861
+ TEXT status "running | done | failed | skipped"
862
+ INTEGER finding_count
863
+ INTEGER fix_count
864
+ INTEGER duration_ms
865
+ TEXT error
866
+ TEXT findings_json
867
+ TEXT fixes_json
868
+ TEXT started_at "ISO 8601"
869
+ TEXT completed_at "ISO 8601"
870
+ TEXT created_at "ISO 8601"
871
+ TEXT updated_at "ISO 8601"
872
+ }
873
+ ```
874
+
875
+ - **`rule_runs`** is the list/query unit — one row per engine invocation.
876
+ - **`rule_eval_runs`** is the per-rule detail unit — one row per evaluated rule, FK'd to `rule_runs`.
877
+ - Timestamps are `TEXT` (ISO 8601) for portability across SQLite drivers including D1.
878
+ - `status`: `running` during evaluation, `done` on normal completion (including policy failures), `failed` for engine/runtime errors. `skipped` when `stopOnFirst` prevents later rules from running.
879
+
880
+ ### Adapter Contract
881
+
882
+ ```ts
883
+ interface RulePersistenceAdapter {
884
+ insertRun(input: RuleRunInput): Promise<void>;
885
+ updateRunStatus(runId, status, findingCount, fixCount, appliedFixCount, durationMs): Promise<void>;
886
+ insertEvalRun(input: RuleEvalRunInput): Promise<void>;
887
+ updateEvalRun(input: RuleEvalRunUpdate): Promise<void>;
888
+ }
889
+ ```
890
+
891
+ Two implementations ship with the package:
892
+
893
+ | Adapter | What it does |
894
+ |---------|-------------|
895
+ | `DbRulePersistenceAdapter` | Writes directly to SQLite via a `@gobing-ai/ts-db` `DbAdapter`. Consumers apply the schema up-front with `RULE_ENGINE_SCHEMA_SQL`. |
896
+ | `MemoryRulePersistenceAdapter` | Stores rows in `Map<string, Row>` — designed for unit tests. Lets test code assert on persisted run/eval state. |
897
+
898
+ ### Usage
899
+
900
+ ```ts
901
+ import { RuleEngine, DbRulePersistenceAdapter, RULE_ENGINE_SCHEMA_SQL } from '@gobing-ai/ts-rule-engine';
902
+ import { createDbAdapter } from '@gobing-ai/ts-db';
903
+
904
+ const db = await createDbAdapter({ driver: 'bun-sqlite', url: ':memory:' });
905
+ await db.exec(RULE_ENGINE_SCHEMA_SQL);
906
+
907
+ const engine = new RuleEngine({
908
+ persistence: new DbRulePersistenceAdapter(db),
909
+ runId: 'my-run-1',
910
+ runMeta: {
911
+ sourceKind: 'preset',
912
+ sourceValue: 'recommended',
913
+ preset: 'recommended',
914
+ failOn: 'error',
915
+ stopOnFirst: 'false',
916
+ fixMode: 'none',
917
+ dryRun: false,
918
+ },
919
+ });
920
+
921
+ const result = await engine.evaluate(rules, process.cwd());
922
+ // At this point, rule_runs has 'my-run-1' with status='done', findings, and duration.
923
+ ```
924
+
925
+ - `runId`: caller-provided or engine-generated (`crypto.randomUUID()`). Persisted so downstream tools like `spur rule trace` can query history.
926
+ - `runMeta`: metadata written to the initial run row — preset name, source/file info, fix mode, dry-run flag.
927
+ - Persistence is **incremental**: the run row is inserted before the first rule, finalized after the last. Per-rule eval rows are inserted as each rule starts and updated when it finishes. A polling `trace` can surface partial progress.
928
+ - When `persistence` is absent, the engine works normally — zero persistence overhead.
929
+
822
930
  ## Package Boundary
823
931
 
824
- This package owns rule definitions, preset loading, evaluators, formatters, test-path resolvers, fixer providers, extension loading, the `CapabilityRegistry` re-export from `@gobing-ai/ts-runtime/extension`, and bundled rule presets.
932
+ This package owns rule definitions, preset loading, evaluators, formatters, test-path resolvers, fixer providers, extension loading, the `CapabilityRegistry` re-export from `@gobing-ai/ts-runtime/extension`, the persistence adapter contract and implementations, the engine-owned schema SQL, and bundled rule presets.
825
933
 
826
934
  It does **not** own:
827
935
 
828
936
  - CLI argument parsing or process exit policy
829
937
  - `ProcessExecutor` implementation (injected via `RuleEngineOptions`)
938
+ - `DbAdapter` creation and lifecycle (injected via `RulePersistenceAdapter`)
830
939
  - Repository-specific rule catalogs (project rules live in `.spur/rules/`)
831
940
  - CI integration or publishing workflows
832
941
 
833
- Downstream tools (e.g. `spur`) consume the library and add their own CLI, config discovery, and execution policy.
942
+ Downstream tools (e.g. `spur`) consume the library and add their own CLI, config discovery, run-id stamping, and execution policy.
package/dist/engine.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { ProcessExecutor } from '@gobing-ai/ts-runtime';
3
3
  import type { RuleEngineEvents } from './events';
4
4
  import { type FixApplicationResult } from './fixers/fixers';
5
5
  import { RuleEngineHost } from './host/rule-engine-host';
6
+ import type { RulePersistenceAdapter } from './persistence/adapter';
6
7
  import type { ConstraintRule, Fix, FixMode, RuleEngineResult, RuleEvaluator } from './types';
7
8
  /** Options for constructing a RuleEngine. */
8
9
  export interface RuleEngineOptions {
@@ -14,6 +15,12 @@ export interface RuleEngineOptions {
14
15
  events?: EventBus<RuleEngineEvents>;
15
16
  /** Optional logger; defaults to the shared `rule-engine` category logger. */
16
17
  logger?: Logger;
18
+ /** Optional persistence adapter for durable run/eval history. */
19
+ persistence?: RulePersistenceAdapter;
20
+ /** Caller-provided run ID; engine generates one if absent. */
21
+ runId?: string;
22
+ /** Caller metadata for the run row (preset, failOn, source info, etc.). */
23
+ runMeta?: Record<string, unknown>;
17
24
  }
18
25
  /** Orchestrates enabled constraint rules through a typed evaluator host. */
19
26
  export declare class RuleEngine {
@@ -21,6 +28,9 @@ export declare class RuleEngine {
21
28
  readonly host: RuleEngineHost;
22
29
  private readonly events;
23
30
  private readonly logger;
31
+ private readonly persistence;
32
+ private readonly runId;
33
+ private readonly runMeta;
24
34
  constructor(options?: RuleEngineOptions);
25
35
  /** Register or replace an evaluator. */
26
36
  registerEvaluator(type: string, evaluator: RuleEvaluator): void;
@@ -40,6 +50,10 @@ export declare class RuleEngine {
40
50
  * provider by evaluator type and calls `createFixes`. The effective fix mode
41
51
  * is the minimum of the rule's configured mode and `maxFixMode`.
42
52
  *
53
+ * When `options.persistence` is set, this method writes a run row before
54
+ * evaluation and per-rule eval rows during the loop. A `done`/`failed`
55
+ * status is written after the loop completes.
56
+ *
43
57
  * @param rules - Normalized rule definitions to evaluate.
44
58
  * @param workdir - Working directory to scan.
45
59
  * @param maxFixMode - Highest fix authority requested by the caller.
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,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,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,mCAAmC;IACnC,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACpC,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;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;gBAEpB,OAAO,GAAE,iBAAsB;IAQ3C,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;;;;;;;;;;;;;;OAcG;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;IAoH5B;;;;;;;OAOG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,EAAE,EAAE,MAAM,UAAQ,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAG1G"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,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,mCAAmC;IACnC,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACpC,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;gBAElD,OAAO,GAAE,iBAAsB;IAW3C,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;IAkK5B;;;;;;;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
@@ -9,12 +9,18 @@ export class RuleEngine {
9
9
  host;
10
10
  events;
11
11
  logger;
12
+ persistence;
13
+ runId;
14
+ runMeta;
12
15
  constructor(options = {}) {
13
16
  this.host = options.host ?? new RuleEngineHost();
14
17
  registerBuiltins(this.host, options.processExecutor);
15
18
  registerBuiltinFixers(this.host, options.processExecutor);
16
19
  this.events = options.events;
17
20
  this.logger = options.logger ?? getLogger('rule-engine');
21
+ this.persistence = options.persistence;
22
+ this.runId = options.runId ?? crypto.randomUUID();
23
+ this.runMeta = options.runMeta;
18
24
  }
19
25
  /** Register or replace an evaluator. */
20
26
  registerEvaluator(type, evaluator) {
@@ -38,6 +44,10 @@ export class RuleEngine {
38
44
  * provider by evaluator type and calls `createFixes`. The effective fix mode
39
45
  * is the minimum of the rule's configured mode and `maxFixMode`.
40
46
  *
47
+ * When `options.persistence` is set, this method writes a run row before
48
+ * evaluation and per-rule eval rows during the loop. A `done`/`failed`
49
+ * status is written after the loop completes.
50
+ *
41
51
  * @param rules - Normalized rule definitions to evaluate.
42
52
  * @param workdir - Working directory to scan.
43
53
  * @param maxFixMode - Highest fix authority requested by the caller.
@@ -49,6 +59,20 @@ export class RuleEngine {
49
59
  async evaluateWithFixes(rules, workdir, maxFixMode = 'auto', stopOnFirst) {
50
60
  const enabledRules = rules.filter((r) => r.enabled !== false);
51
61
  const runStartMs = Date.now();
62
+ // Insert the run row before any evaluation.
63
+ const runMeta = this.runMeta ?? {};
64
+ await this.persistence?.insertRun({
65
+ id: this.runId,
66
+ sourceKind: runMeta.sourceKind ?? 'preset',
67
+ sourceValue: runMeta.sourceValue,
68
+ preset: runMeta.preset,
69
+ ruleCount: enabledRules.length,
70
+ failOn: runMeta.failOn,
71
+ stopOnFirst: runMeta.stopOnFirst,
72
+ fixMode: maxFixMode,
73
+ dryRun: runMeta.dryRun ?? false,
74
+ metadataJson: runMeta.metadataJson,
75
+ });
52
76
  return await traceAsync('rule.run', async () => {
53
77
  this.logger.info('rule run started', { enabled: enabledRules.length, total: rules.length });
54
78
  addSpanEvent('rule.run.start', { rules: enabledRules.length, total: rules.length });
@@ -61,12 +85,21 @@ export class RuleEngine {
61
85
  if (rule.enabled === false)
62
86
  continue;
63
87
  index++;
88
+ // Persist eval start.
89
+ await this.persistence?.insertEvalRun({
90
+ id: `${this.runId}:${rule.id}`,
91
+ runId: this.runId,
92
+ ruleId: rule.id,
93
+ severity: rule.severity,
94
+ evaluator: rule.evaluator.type,
95
+ });
64
96
  const evalStartMs = Date.now();
65
97
  this.logger.debug('eval start', { ruleId: rule.id, index, total: enabledRules.length });
66
98
  addSpanEvent('rule.eval.start', { ruleId: rule.id, index, total: enabledRules.length });
67
99
  void this.events?.emit('rule.eval.start', { ruleId: rule.id, index, total: enabledRules.length });
68
100
  let ruleFindings = [];
69
101
  let ruleEvalFixes = [];
102
+ let evalError;
70
103
  try {
71
104
  const result = await this.host.evaluators
72
105
  .get(rule.evaluator.type)
@@ -75,15 +108,15 @@ export class RuleEngine {
75
108
  ruleEvalFixes = result.fixes;
76
109
  }
77
110
  catch (error) {
78
- const message = error instanceof Error ? error.message : String(error);
111
+ evalError = error instanceof Error ? error.message : String(error);
79
112
  ruleFindings = [
80
- createFinding(rule, message, null, {
113
+ createFinding(rule, evalError, null, {
81
114
  code: `evaluator:${rule.evaluator.type}`,
82
115
  kind: 'error',
83
116
  }),
84
117
  ];
85
- addSpanEvent('rule.eval.error', { ruleId: rule.id, error: message });
86
- void this.events?.emit('rule.eval.error', { ruleId: rule.id, error: message });
118
+ addSpanEvent('rule.eval.error', { ruleId: rule.id, error: evalError });
119
+ void this.events?.emit('rule.eval.error', { ruleId: rule.id, error: evalError });
87
120
  }
88
121
  const durationMs = Date.now() - evalStartMs;
89
122
  addSpanEvent('rule.eval.done', {
@@ -96,6 +129,16 @@ export class RuleEngine {
96
129
  findings: ruleFindings.length,
97
130
  durationMs,
98
131
  });
132
+ // Persist eval completion.
133
+ await this.persistence?.updateEvalRun({
134
+ runId: this.runId,
135
+ ruleId: rule.id,
136
+ status: evalError ? 'failed' : 'done',
137
+ findingCount: ruleFindings.length,
138
+ fixCount: ruleEvalFixes.length,
139
+ durationMs,
140
+ ...(evalError ? { error: evalError } : {}),
141
+ });
99
142
  findings.push(...ruleFindings);
100
143
  fixes.push(...ruleEvalFixes);
101
144
  const ruleMode = rule.fix?.mode ?? 'none';
@@ -142,6 +185,9 @@ export class RuleEngine {
142
185
  durationMs: runDurationMs,
143
186
  stoppedEarly: stoppedEarlyLocal,
144
187
  });
188
+ // Finalize the run row.
189
+ await this.persistence?.updateRunStatus(this.runId, 'done', findings.length, fixes.length, 0, // appliedFixCount — set by the caller after applying fixes
190
+ runDurationMs);
145
191
  return { findings, fixes };
146
192
  }, { attributes: { 'rule.count': enabledRules.length } });
147
193
  }
@@ -64,7 +64,20 @@ export declare function scanFiles(options: ScanFilesOptions): Promise<ScannedFil
64
64
  export declare function relativeToWorkdir(workdir: string, path: string): string;
65
65
  /** Return parent directory for a workdir-relative path. */
66
66
  export declare function relativeParent(path: string): string;
67
- /** Return true when a path matches any supplied fragment or suffix. */
67
+ /**
68
+ * Return true when a path matches any supplied pattern.
69
+ *
70
+ * Each pattern is matched in two passes, most-specific first:
71
+ * 1. If it contains a glob metachar (`*`), try anchored {@link matchesGlob} —
72
+ * so `packages/**\/*.ts` resolves segment-by-segment instead of collapsing
73
+ * to a bare fragment (the latter silently matched nothing, leaving every
74
+ * glob-scoped loose-mode rule scanning zero files).
75
+ * 2. Otherwise (or if the glob did not match), fall back to the legacy
76
+ * fragment/suffix substring test, where stars are stripped — preserving
77
+ * bare fragments like `.ts`, `src/`, `/tests/` that loose-mode evaluators
78
+ * have always accepted, and keeping unanchored patterns like `*.ts`
79
+ * (→ suffix `.ts`) matching nested paths.
80
+ */
68
81
  export declare function matchesAny(path: string, patterns: string[] | undefined): boolean;
69
82
  /**
70
83
  * Segment-aware glob matching with `**` (any depth) and `*` (single segment).
@@ -1 +1 @@
1
- {"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/evaluators/file-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/evaluators/file-utils.ts"],"names":[],"mappings":"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"}
@@ -55,13 +55,30 @@ export function relativeParent(path) {
55
55
  const parent = dirnamePath(path);
56
56
  return parent === '.' ? '' : parent;
57
57
  }
58
- /** Return true when a path matches any supplied fragment or suffix. */
58
+ /**
59
+ * Return true when a path matches any supplied pattern.
60
+ *
61
+ * Each pattern is matched in two passes, most-specific first:
62
+ * 1. If it contains a glob metachar (`*`), try anchored {@link matchesGlob} —
63
+ * so `packages/**\/*.ts` resolves segment-by-segment instead of collapsing
64
+ * to a bare fragment (the latter silently matched nothing, leaving every
65
+ * glob-scoped loose-mode rule scanning zero files).
66
+ * 2. Otherwise (or if the glob did not match), fall back to the legacy
67
+ * fragment/suffix substring test, where stars are stripped — preserving
68
+ * bare fragments like `.ts`, `src/`, `/tests/` that loose-mode evaluators
69
+ * have always accepted, and keeping unanchored patterns like `*.ts`
70
+ * (→ suffix `.ts`) matching nested paths.
71
+ */
59
72
  export function matchesAny(path, patterns) {
60
73
  if (patterns === undefined || patterns.length === 0)
61
74
  return true;
75
+ const normalizedPath = path.replaceAll('\\', '/');
62
76
  return patterns.some((pattern) => {
63
- const clean = pattern.replaceAll('\\', '/').replaceAll('**/', '').replaceAll('*', '');
64
- return clean.length === 0 || path.includes(clean) || path.endsWith(clean);
77
+ const normalized = pattern.replaceAll('\\', '/');
78
+ if (normalized.includes('*') && matchesGlob(normalizedPath, normalized))
79
+ return true;
80
+ const clean = normalized.replaceAll('**/', '').replaceAll('*', '');
81
+ return clean.length === 0 || normalizedPath.includes(clean) || normalizedPath.endsWith(clean);
65
82
  });
66
83
  }
67
84
  /**
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export * from './formatters/json';
10
10
  export * from './formatters/text';
11
11
  export * from './host/bundled-rules';
12
12
  export * from './host/rule-engine-host';
13
+ export * from './persistence';
13
14
  export * from './resolvers/test-path-resolver';
14
15
  export * from './types';
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAClH,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AAGzB,OAAO,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAC5E,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAClH,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AAGzB,OAAO,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAC5E,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAC9B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -14,5 +14,6 @@ export * from './formatters/json.js';
14
14
  export * from './formatters/text.js';
15
15
  export * from './host/bundled-rules.js';
16
16
  export * from './host/rule-engine-host.js';
17
+ export * from './persistence/index.js';
17
18
  export * from './resolvers/test-path-resolver.js';
18
19
  export * from './types.js';
@@ -0,0 +1,53 @@
1
+ import type { FixMode } from '../types';
2
+ /** Input for inserting a rule run row (engine caller provides run metadata). */
3
+ export interface RuleRunInput {
4
+ id: string;
5
+ preset?: string;
6
+ sourceKind: 'preset' | 'file';
7
+ sourceValue?: string;
8
+ ruleCount: number;
9
+ failOn?: string;
10
+ stopOnFirst?: string;
11
+ fixMode: FixMode;
12
+ dryRun: boolean;
13
+ metadataJson?: string;
14
+ }
15
+ /** Input for inserting a per-rule eval row. */
16
+ export interface RuleEvalRunInput {
17
+ id: string;
18
+ runId: string;
19
+ ruleId: string;
20
+ severity: string;
21
+ evaluator: string;
22
+ }
23
+ /** Input for updating a per-rule eval row after evaluation completes. */
24
+ export interface RuleEvalRunUpdate {
25
+ runId: string;
26
+ ruleId: string;
27
+ status: 'done' | 'failed' | 'skipped';
28
+ findingCount: number;
29
+ fixCount: number;
30
+ durationMs: number;
31
+ /** Evaluator error message, if status is 'failed'. */
32
+ error?: string;
33
+ /** Bounded JSON for findings. */
34
+ findingsJson?: string;
35
+ /** Bounded JSON for fixes. */
36
+ fixesJson?: string;
37
+ }
38
+ /**
39
+ * Persistence adapter contract for the rule engine.
40
+ * Implementations write directly to a durable store; the engine does NOT
41
+ * use EventBus subscribers for durability.
42
+ */
43
+ export interface RulePersistenceAdapter {
44
+ /** Insert a run row as 'running'. Must be idempotent. */
45
+ insertRun(input: RuleRunInput): Promise<void>;
46
+ /** Finalize a run row after evaluation completes. */
47
+ updateRunStatus(runId: string, status: 'done' | 'failed', findingCount: number, fixCount: number, appliedFixCount: number, durationMs: number): Promise<void>;
48
+ /** Insert a per-rule eval row as 'running'. Must be idempotent. */
49
+ insertEvalRun(input: RuleEvalRunInput): Promise<void>;
50
+ /** Finalize a per-rule eval row after evaluation completes. */
51
+ updateEvalRun(input: RuleEvalRunUpdate): Promise<void>;
52
+ }
53
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/persistence/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,gFAAgF;AAChF,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,yDAAyD;IACzD,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C,qDAAqD;IACrD,eAAe,CACX,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GAAG,QAAQ,EACzB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,mEAAmE;IACnE,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtD,+DAA+D;IAC/D,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D"}
File without changes
@@ -0,0 +1,12 @@
1
+ import type { DbAdapter } from '@gobing-ai/ts-db';
2
+ import type { RuleEvalRunInput, RuleEvalRunUpdate, RulePersistenceAdapter, RuleRunInput } from './adapter';
3
+ /** Persist engine run/eval rows to SQLite via a ts-db DbAdapter. */
4
+ export declare class DbRulePersistenceAdapter implements RulePersistenceAdapter {
5
+ private readonly db;
6
+ constructor(db: DbAdapter);
7
+ insertRun(input: RuleRunInput): Promise<void>;
8
+ updateRunStatus(runId: string, status: 'done' | 'failed', findingCount: number, fixCount: number, appliedFixCount: number, durationMs: number): Promise<void>;
9
+ insertEvalRun(input: RuleEvalRunInput): Promise<void>;
10
+ updateEvalRun(input: RuleEvalRunUpdate): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=db-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-adapter.d.ts","sourceRoot":"","sources":["../../src/persistence/db-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE3G,oEAAoE;AACpE,qBAAa,wBAAyB,YAAW,sBAAsB;IACvD,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,SAAS;IAEpC,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB7C,eAAe,CACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GAAG,QAAQ,EACzB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC;IAkBV,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBrD,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CAoB/D"}
@@ -0,0 +1,33 @@
1
+ /** Persist engine run/eval rows to SQLite via a ts-db DbAdapter. */
2
+ export class DbRulePersistenceAdapter {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ async insertRun(input) {
8
+ const now = new Date().toISOString();
9
+ await this.db.run(`INSERT INTO rule_runs (id, preset, source_kind, source_value, status, rule_count,
10
+ fail_on, stop_on_first, fix_mode, dry_run, metadata_json, started_at, created_at, updated_at)
11
+ VALUES (?, ?, ?, ?, 'running', ?, ?, ?, ?, ?, ?, ?, ?, ?)`, input.id, input.preset ?? null, input.sourceKind, input.sourceValue ?? null, input.ruleCount, input.failOn ?? null, input.stopOnFirst ?? null, input.fixMode, input.dryRun ? 1 : 0, input.metadataJson ?? '{}', now, now, now);
12
+ }
13
+ async updateRunStatus(runId, status, findingCount, fixCount, appliedFixCount, durationMs) {
14
+ const now = new Date().toISOString();
15
+ await this.db.run(`UPDATE rule_runs
16
+ SET status = ?, finding_count = ?, fix_count = ?, applied_fix_count = ?,
17
+ duration_ms = ?, completed_at = ?, updated_at = ?
18
+ WHERE id = ?`, status, findingCount, fixCount, appliedFixCount, durationMs, now, now, runId);
19
+ }
20
+ async insertEvalRun(input) {
21
+ const now = new Date().toISOString();
22
+ await this.db.run(`INSERT INTO rule_eval_runs (id, run_id, rule_id, severity, evaluator, status,
23
+ started_at, created_at, updated_at)
24
+ VALUES (?, ?, ?, ?, ?, 'running', ?, ?, ?)`, input.id, input.runId, input.ruleId, input.severity, input.evaluator, now, now, now);
25
+ }
26
+ async updateEvalRun(input) {
27
+ const now = new Date().toISOString();
28
+ await this.db.run(`UPDATE rule_eval_runs
29
+ SET status = ?, finding_count = ?, fix_count = ?, duration_ms = ?,
30
+ error = ?, findings_json = ?, fixes_json = ?, completed_at = ?, updated_at = ?
31
+ WHERE run_id = ? AND rule_id = ?`, input.status, input.findingCount, input.fixCount, input.durationMs, input.error ?? null, input.findingsJson ?? null, input.fixesJson ?? null, now, now, input.runId, input.ruleId);
32
+ }
33
+ }
@@ -0,0 +1,5 @@
1
+ export type { RuleEvalRunInput, RuleEvalRunUpdate, RulePersistenceAdapter, RuleRunInput } from './adapter';
2
+ export { DbRulePersistenceAdapter } from './db-adapter';
3
+ export { MemoryRulePersistenceAdapter } from './memory-adapter';
4
+ export { RULE_ENGINE_SCHEMA_SQL } from './schema';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/persistence/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC3G,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { DbRulePersistenceAdapter } from './db-adapter.js';
2
+ export { MemoryRulePersistenceAdapter } from './memory-adapter.js';
3
+ export { RULE_ENGINE_SCHEMA_SQL } from './schema.js';
@@ -0,0 +1,39 @@
1
+ import type { RuleEvalRunInput, RuleEvalRunUpdate, RulePersistenceAdapter, RuleRunInput } from './adapter';
2
+ /** Runtime row shape for the memory adapter. */
3
+ interface RuleRunRow extends RuleRunInput {
4
+ status: 'running' | 'done' | 'failed';
5
+ findingCount: number;
6
+ fixCount: number;
7
+ appliedFixCount: number;
8
+ durationMs: number | null;
9
+ completedAt: string | null;
10
+ createdAt: string;
11
+ updatedAt: string;
12
+ }
13
+ interface RuleEvalRow extends RuleEvalRunInput {
14
+ status: 'running' | 'done' | 'failed' | 'skipped';
15
+ findingCount: number;
16
+ fixCount: number;
17
+ durationMs: number | null;
18
+ error: string | null;
19
+ findingsJson: string | null;
20
+ fixesJson: string | null;
21
+ completedAt: string | null;
22
+ createdAt: string;
23
+ updatedAt: string;
24
+ }
25
+ /**
26
+ * In-memory persistence adapter for unit tests.
27
+ * Stores rows in Maps so tests can assert on persisted state.
28
+ */
29
+ export declare class MemoryRulePersistenceAdapter implements RulePersistenceAdapter {
30
+ constructor();
31
+ readonly runs: Map<string, RuleRunRow>;
32
+ readonly evals: Map<string, RuleEvalRow>;
33
+ insertRun(input: RuleRunInput): Promise<void>;
34
+ updateRunStatus(runId: string, status: 'done' | 'failed', findingCount: number, fixCount: number, appliedFixCount: number, durationMs: number): Promise<void>;
35
+ insertEvalRun(input: RuleEvalRunInput): Promise<void>;
36
+ updateEvalRun(input: RuleEvalRunUpdate): Promise<void>;
37
+ }
38
+ export {};
39
+ //# sourceMappingURL=memory-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-adapter.d.ts","sourceRoot":"","sources":["../../src/persistence/memory-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE3G,gDAAgD;AAChD,UAAU,UAAW,SAAQ,YAAY;IACrC,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,WAAY,SAAQ,gBAAgB;IAC1C,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,4BAA6B,YAAW,sBAAsB;;IAKvE,QAAQ,CAAC,IAAI,0BAAiC;IAC9C,QAAQ,CAAC,KAAK,2BAAkC;IAE1C,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAe7C,eAAe,CACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GAAG,QAAQ,EACzB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC;IAYV,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBrD,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CAc/D"}