@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
86 lines (85 loc) • 2.31 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { RuleStore } from "./rule-store.js";
import {
BUILT_IN_RULES,
getBuiltinRows,
filterByScope
} from "./built-in-rules.js";
class RuleEngine {
store;
checkFns = /* @__PURE__ */ new Map();
constructor(db) {
this.store = new RuleStore(db);
this.store.seedBuiltins(getBuiltinRows());
for (const rule of BUILT_IN_RULES) {
this.checkFns.set(rule.id, rule.check.bind(rule));
}
}
registerRule(rule) {
this.store.upsert({
id: rule.id,
name: rule.name,
description: rule.description,
trigger_type: rule.trigger,
severity: rule.severity,
scope: rule.scope,
enabled: rule.enabled ? 1 : 0,
builtin: rule.builtin ? 1 : 0
});
this.checkFns.set(rule.id, rule.check.bind(rule));
}
evaluate(context) {
const rows = this.store.getByTrigger(context.trigger);
return this.runRules(rows, context);
}
evaluateAll(context) {
const rows = this.store.getEnabled();
return this.runRules(rows, context);
}
listRules(filter) {
if (filter?.trigger) {
return this.store.getByTrigger(filter.trigger);
}
if (filter?.enabled === false) {
return this.store.getAll();
}
return this.store.getEnabled();
}
enableRule(id) {
return this.store.setEnabled(id, true);
}
disableRule(id) {
return this.store.setEnabled(id, false);
}
getStore() {
return this.store;
}
runRules(rows, context) {
const allViolations = [];
for (const row of rows) {
const checkFn = this.checkFns.get(row.id);
if (!checkFn) continue;
const scopedFiles = filterByScope(context.files, row.scope);
if (scopedFiles.length === 0 && row.trigger_type !== "commit" && row.trigger_type !== "pre-commit")
continue;
const scopedCtx = {
...context,
files: scopedFiles
};
const result = checkFn(scopedCtx);
if (!result.passed) {
allViolations.push(...result.violations);
}
}
return {
passed: allViolations.length === 0,
violations: allViolations
};
}
}
export {
RuleEngine
};