engrm 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +684 -12
- package/dist/hooks/elicitation-result.js +85 -5
- package/dist/hooks/post-tool-use.js +340 -5
- package/dist/hooks/pre-compact.js +106 -10
- package/dist/hooks/sentinel.js +90 -4
- package/dist/hooks/session-start.js +342 -11
- package/dist/hooks/stop.js +903 -4
- package/dist/server.js +155 -11
- package/package.json +1 -1
|
@@ -4,9 +4,9 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// src/config.ts
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { homedir, hostname } from "node:os";
|
|
7
|
+
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import {
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
10
|
var CONFIG_DIR = join(homedir(), ".engrm");
|
|
11
11
|
var SETTINGS_PATH = join(CONFIG_DIR, "settings.json");
|
|
12
12
|
var DB_PATH = join(CONFIG_DIR, "engrm.db");
|
|
@@ -15,7 +15,22 @@ function getDbPath() {
|
|
|
15
15
|
}
|
|
16
16
|
function generateDeviceId() {
|
|
17
17
|
const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
18
|
-
|
|
18
|
+
let mac = "";
|
|
19
|
+
const ifaces = networkInterfaces();
|
|
20
|
+
for (const entries of Object.values(ifaces)) {
|
|
21
|
+
if (!entries)
|
|
22
|
+
continue;
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (!entry.internal && entry.mac && entry.mac !== "00:00:00:00:00:00") {
|
|
25
|
+
mac = entry.mac;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (mac)
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
const material = `${host}:${mac || "no-mac"}`;
|
|
33
|
+
const suffix = createHash("sha256").update(material).digest("hex").slice(0, 8);
|
|
19
34
|
return `${host}-${suffix}`;
|
|
20
35
|
}
|
|
21
36
|
function createDefaultConfig() {
|
|
@@ -57,7 +72,10 @@ function createDefaultConfig() {
|
|
|
57
72
|
observer: {
|
|
58
73
|
enabled: true,
|
|
59
74
|
mode: "per_event",
|
|
60
|
-
model: "
|
|
75
|
+
model: "sonnet"
|
|
76
|
+
},
|
|
77
|
+
transcript_analysis: {
|
|
78
|
+
enabled: false
|
|
61
79
|
}
|
|
62
80
|
};
|
|
63
81
|
}
|
|
@@ -116,9 +134,19 @@ function loadConfig() {
|
|
|
116
134
|
enabled: asBool(config["observer"]?.["enabled"], defaults.observer.enabled),
|
|
117
135
|
mode: asObserverMode(config["observer"]?.["mode"], defaults.observer.mode),
|
|
118
136
|
model: asString(config["observer"]?.["model"], defaults.observer.model)
|
|
137
|
+
},
|
|
138
|
+
transcript_analysis: {
|
|
139
|
+
enabled: asBool(config["transcript_analysis"]?.["enabled"], defaults.transcript_analysis.enabled)
|
|
119
140
|
}
|
|
120
141
|
};
|
|
121
142
|
}
|
|
143
|
+
function saveConfig(config) {
|
|
144
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
145
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(config, null, 2) + `
|
|
148
|
+
`, "utf-8");
|
|
149
|
+
}
|
|
122
150
|
function configExists() {
|
|
123
151
|
return existsSync(SETTINGS_PATH);
|
|
124
152
|
}
|
|
@@ -514,6 +542,56 @@ function runMigrations(db) {
|
|
|
514
542
|
}
|
|
515
543
|
}
|
|
516
544
|
}
|
|
545
|
+
function ensureObservationTypes(db) {
|
|
546
|
+
try {
|
|
547
|
+
db.exec("INSERT INTO observations (session_id, project_id, type, title, user_id, device_id, agent, created_at, created_at_epoch) " + "VALUES ('_typecheck', 1, 'message', '_test', '_test', '_test', '_test', '2000-01-01', 0)");
|
|
548
|
+
db.exec("DELETE FROM observations WHERE session_id = '_typecheck'");
|
|
549
|
+
} catch {
|
|
550
|
+
db.exec("BEGIN TRANSACTION");
|
|
551
|
+
try {
|
|
552
|
+
db.exec(`
|
|
553
|
+
CREATE TABLE observations_repair (
|
|
554
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT,
|
|
555
|
+
project_id INTEGER NOT NULL REFERENCES projects(id),
|
|
556
|
+
type TEXT NOT NULL CHECK (type IN (
|
|
557
|
+
'bugfix','discovery','decision','pattern','change','feature',
|
|
558
|
+
'refactor','digest','standard','message')),
|
|
559
|
+
title TEXT NOT NULL, narrative TEXT, facts TEXT, concepts TEXT,
|
|
560
|
+
files_read TEXT, files_modified TEXT,
|
|
561
|
+
quality REAL DEFAULT 0.5 CHECK (quality BETWEEN 0.0 AND 1.0),
|
|
562
|
+
lifecycle TEXT DEFAULT 'active' CHECK (lifecycle IN ('active','aging','archived','purged','pinned')),
|
|
563
|
+
sensitivity TEXT DEFAULT 'shared' CHECK (sensitivity IN ('shared','personal','secret')),
|
|
564
|
+
user_id TEXT NOT NULL, device_id TEXT NOT NULL, agent TEXT DEFAULT 'claude-code',
|
|
565
|
+
created_at TEXT NOT NULL, created_at_epoch INTEGER NOT NULL,
|
|
566
|
+
archived_at_epoch INTEGER,
|
|
567
|
+
compacted_into INTEGER REFERENCES observations(id) ON DELETE SET NULL,
|
|
568
|
+
superseded_by INTEGER REFERENCES observations(id) ON DELETE SET NULL,
|
|
569
|
+
remote_source_id TEXT
|
|
570
|
+
);
|
|
571
|
+
INSERT INTO observations_repair SELECT * FROM observations;
|
|
572
|
+
DROP TABLE observations;
|
|
573
|
+
ALTER TABLE observations_repair RENAME TO observations;
|
|
574
|
+
CREATE INDEX IF NOT EXISTS idx_observations_project ON observations(project_id);
|
|
575
|
+
CREATE INDEX IF NOT EXISTS idx_observations_type ON observations(type);
|
|
576
|
+
CREATE INDEX IF NOT EXISTS idx_observations_created ON observations(created_at_epoch);
|
|
577
|
+
CREATE INDEX IF NOT EXISTS idx_observations_session ON observations(session_id);
|
|
578
|
+
CREATE INDEX IF NOT EXISTS idx_observations_lifecycle ON observations(lifecycle);
|
|
579
|
+
CREATE INDEX IF NOT EXISTS idx_observations_quality ON observations(quality);
|
|
580
|
+
CREATE INDEX IF NOT EXISTS idx_observations_user ON observations(user_id);
|
|
581
|
+
CREATE INDEX IF NOT EXISTS idx_observations_superseded ON observations(superseded_by);
|
|
582
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_observations_remote_source ON observations(remote_source_id) WHERE remote_source_id IS NOT NULL;
|
|
583
|
+
DROP TABLE IF EXISTS observations_fts;
|
|
584
|
+
CREATE VIRTUAL TABLE observations_fts USING fts5(
|
|
585
|
+
title, narrative, facts, concepts, content=observations, content_rowid=id
|
|
586
|
+
);
|
|
587
|
+
INSERT INTO observations_fts(observations_fts) VALUES('rebuild');
|
|
588
|
+
`);
|
|
589
|
+
db.exec("COMMIT");
|
|
590
|
+
} catch (err) {
|
|
591
|
+
db.exec("ROLLBACK");
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
517
595
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
518
596
|
|
|
519
597
|
// src/storage/sqlite.ts
|
|
@@ -580,6 +658,7 @@ class MemDatabase {
|
|
|
580
658
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
581
659
|
this.vecAvailable = this.loadVecExtension();
|
|
582
660
|
runMigrations(this.db);
|
|
661
|
+
ensureObservationTypes(this.db);
|
|
583
662
|
}
|
|
584
663
|
loadVecExtension() {
|
|
585
664
|
try {
|
|
@@ -1057,7 +1136,7 @@ function estimateTokens(text) {
|
|
|
1057
1136
|
}
|
|
1058
1137
|
function buildSessionContext(db, cwd, options = {}) {
|
|
1059
1138
|
const opts = typeof options === "number" ? { maxCount: options } : options;
|
|
1060
|
-
const tokenBudget = opts.tokenBudget ??
|
|
1139
|
+
const tokenBudget = opts.tokenBudget ?? 3000;
|
|
1061
1140
|
const maxCount = opts.maxCount;
|
|
1062
1141
|
const detected = detectProject(cwd);
|
|
1063
1142
|
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
@@ -1079,6 +1158,12 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1079
1158
|
AND superseded_by IS NULL
|
|
1080
1159
|
ORDER BY quality DESC, created_at_epoch DESC
|
|
1081
1160
|
LIMIT ?`).all(project.id, MAX_PINNED);
|
|
1161
|
+
const MAX_RECENT = 5;
|
|
1162
|
+
const recent = db.db.query(`SELECT * FROM observations
|
|
1163
|
+
WHERE project_id = ? AND lifecycle IN ('active', 'aging')
|
|
1164
|
+
AND superseded_by IS NULL
|
|
1165
|
+
ORDER BY created_at_epoch DESC
|
|
1166
|
+
LIMIT ?`).all(project.id, MAX_RECENT);
|
|
1082
1167
|
const candidateLimit = maxCount ?? 50;
|
|
1083
1168
|
const candidates = db.db.query(`SELECT * FROM observations
|
|
1084
1169
|
WHERE project_id = ? AND lifecycle IN ('active', 'aging')
|
|
@@ -1106,6 +1191,12 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1106
1191
|
});
|
|
1107
1192
|
}
|
|
1108
1193
|
const seenIds = new Set(pinned.map((o) => o.id));
|
|
1194
|
+
const dedupedRecent = recent.filter((o) => {
|
|
1195
|
+
if (seenIds.has(o.id))
|
|
1196
|
+
return false;
|
|
1197
|
+
seenIds.add(o.id);
|
|
1198
|
+
return true;
|
|
1199
|
+
});
|
|
1109
1200
|
const deduped = candidates.filter((o) => !seenIds.has(o.id));
|
|
1110
1201
|
for (const obs of crossProjectCandidates) {
|
|
1111
1202
|
if (!seenIds.has(obs.id)) {
|
|
@@ -1122,8 +1213,8 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1122
1213
|
return scoreB - scoreA;
|
|
1123
1214
|
});
|
|
1124
1215
|
if (maxCount !== undefined) {
|
|
1125
|
-
const remaining = Math.max(0, maxCount - pinned.length);
|
|
1126
|
-
const all = [...pinned, ...sorted.slice(0, remaining)];
|
|
1216
|
+
const remaining = Math.max(0, maxCount - pinned.length - dedupedRecent.length);
|
|
1217
|
+
const all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
|
|
1127
1218
|
return {
|
|
1128
1219
|
project_name: project.name,
|
|
1129
1220
|
canonical_id: project.canonical_id,
|
|
@@ -1139,6 +1230,11 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1139
1230
|
remainingBudget -= cost;
|
|
1140
1231
|
selected.push(obs);
|
|
1141
1232
|
}
|
|
1233
|
+
for (const obs of dedupedRecent) {
|
|
1234
|
+
const cost = estimateObservationTokens(obs, selected.length);
|
|
1235
|
+
remainingBudget -= cost;
|
|
1236
|
+
selected.push(obs);
|
|
1237
|
+
}
|
|
1142
1238
|
for (const obs of sorted) {
|
|
1143
1239
|
const cost = estimateObservationTokens(obs, selected.length);
|
|
1144
1240
|
if (remainingBudget - cost < 0 && selected.length > 0)
|
|
@@ -1146,7 +1242,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1146
1242
|
remainingBudget -= cost;
|
|
1147
1243
|
selected.push(obs);
|
|
1148
1244
|
}
|
|
1149
|
-
const summaries = db.getRecentSummaries(project.id,
|
|
1245
|
+
const summaries = db.getRecentSummaries(project.id, 5);
|
|
1150
1246
|
let securityFindings = [];
|
|
1151
1247
|
try {
|
|
1152
1248
|
const weekAgo = Math.floor(Date.now() / 1000) - 7 * 86400;
|
|
@@ -1166,7 +1262,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1166
1262
|
};
|
|
1167
1263
|
}
|
|
1168
1264
|
function estimateObservationTokens(obs, index) {
|
|
1169
|
-
const DETAILED_THRESHOLD =
|
|
1265
|
+
const DETAILED_THRESHOLD = 5;
|
|
1170
1266
|
const titleCost = estimateTokens(`- **[${obs.type}]** ${obs.title} (2026-01-01, q=0.5)`);
|
|
1171
1267
|
if (index >= DETAILED_THRESHOLD) {
|
|
1172
1268
|
return titleCost;
|
|
@@ -1178,7 +1274,7 @@ function formatContextForInjection(context) {
|
|
|
1178
1274
|
if (context.observations.length === 0) {
|
|
1179
1275
|
return `Project: ${context.project_name} (no prior observations)`;
|
|
1180
1276
|
}
|
|
1181
|
-
const DETAILED_COUNT =
|
|
1277
|
+
const DETAILED_COUNT = 5;
|
|
1182
1278
|
const lines = [
|
|
1183
1279
|
`## Project Memory: ${context.project_name}`,
|
|
1184
1280
|
`${context.session_count} relevant observation(s) from prior sessions:`,
|
package/dist/hooks/sentinel.js
CHANGED
|
@@ -4,9 +4,9 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// src/config.ts
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { homedir, hostname } from "node:os";
|
|
7
|
+
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import {
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
10
|
var CONFIG_DIR = join(homedir(), ".engrm");
|
|
11
11
|
var SETTINGS_PATH = join(CONFIG_DIR, "settings.json");
|
|
12
12
|
var DB_PATH = join(CONFIG_DIR, "engrm.db");
|
|
@@ -15,7 +15,22 @@ function getDbPath() {
|
|
|
15
15
|
}
|
|
16
16
|
function generateDeviceId() {
|
|
17
17
|
const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
18
|
-
|
|
18
|
+
let mac = "";
|
|
19
|
+
const ifaces = networkInterfaces();
|
|
20
|
+
for (const entries of Object.values(ifaces)) {
|
|
21
|
+
if (!entries)
|
|
22
|
+
continue;
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (!entry.internal && entry.mac && entry.mac !== "00:00:00:00:00:00") {
|
|
25
|
+
mac = entry.mac;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (mac)
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
const material = `${host}:${mac || "no-mac"}`;
|
|
33
|
+
const suffix = createHash("sha256").update(material).digest("hex").slice(0, 8);
|
|
19
34
|
return `${host}-${suffix}`;
|
|
20
35
|
}
|
|
21
36
|
function createDefaultConfig() {
|
|
@@ -57,7 +72,10 @@ function createDefaultConfig() {
|
|
|
57
72
|
observer: {
|
|
58
73
|
enabled: true,
|
|
59
74
|
mode: "per_event",
|
|
60
|
-
model: "
|
|
75
|
+
model: "sonnet"
|
|
76
|
+
},
|
|
77
|
+
transcript_analysis: {
|
|
78
|
+
enabled: false
|
|
61
79
|
}
|
|
62
80
|
};
|
|
63
81
|
}
|
|
@@ -116,9 +134,19 @@ function loadConfig() {
|
|
|
116
134
|
enabled: asBool(config["observer"]?.["enabled"], defaults.observer.enabled),
|
|
117
135
|
mode: asObserverMode(config["observer"]?.["mode"], defaults.observer.mode),
|
|
118
136
|
model: asString(config["observer"]?.["model"], defaults.observer.model)
|
|
137
|
+
},
|
|
138
|
+
transcript_analysis: {
|
|
139
|
+
enabled: asBool(config["transcript_analysis"]?.["enabled"], defaults.transcript_analysis.enabled)
|
|
119
140
|
}
|
|
120
141
|
};
|
|
121
142
|
}
|
|
143
|
+
function saveConfig(config) {
|
|
144
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
145
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(config, null, 2) + `
|
|
148
|
+
`, "utf-8");
|
|
149
|
+
}
|
|
122
150
|
function configExists() {
|
|
123
151
|
return existsSync(SETTINGS_PATH);
|
|
124
152
|
}
|
|
@@ -514,6 +542,56 @@ function runMigrations(db) {
|
|
|
514
542
|
}
|
|
515
543
|
}
|
|
516
544
|
}
|
|
545
|
+
function ensureObservationTypes(db) {
|
|
546
|
+
try {
|
|
547
|
+
db.exec("INSERT INTO observations (session_id, project_id, type, title, user_id, device_id, agent, created_at, created_at_epoch) " + "VALUES ('_typecheck', 1, 'message', '_test', '_test', '_test', '_test', '2000-01-01', 0)");
|
|
548
|
+
db.exec("DELETE FROM observations WHERE session_id = '_typecheck'");
|
|
549
|
+
} catch {
|
|
550
|
+
db.exec("BEGIN TRANSACTION");
|
|
551
|
+
try {
|
|
552
|
+
db.exec(`
|
|
553
|
+
CREATE TABLE observations_repair (
|
|
554
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT,
|
|
555
|
+
project_id INTEGER NOT NULL REFERENCES projects(id),
|
|
556
|
+
type TEXT NOT NULL CHECK (type IN (
|
|
557
|
+
'bugfix','discovery','decision','pattern','change','feature',
|
|
558
|
+
'refactor','digest','standard','message')),
|
|
559
|
+
title TEXT NOT NULL, narrative TEXT, facts TEXT, concepts TEXT,
|
|
560
|
+
files_read TEXT, files_modified TEXT,
|
|
561
|
+
quality REAL DEFAULT 0.5 CHECK (quality BETWEEN 0.0 AND 1.0),
|
|
562
|
+
lifecycle TEXT DEFAULT 'active' CHECK (lifecycle IN ('active','aging','archived','purged','pinned')),
|
|
563
|
+
sensitivity TEXT DEFAULT 'shared' CHECK (sensitivity IN ('shared','personal','secret')),
|
|
564
|
+
user_id TEXT NOT NULL, device_id TEXT NOT NULL, agent TEXT DEFAULT 'claude-code',
|
|
565
|
+
created_at TEXT NOT NULL, created_at_epoch INTEGER NOT NULL,
|
|
566
|
+
archived_at_epoch INTEGER,
|
|
567
|
+
compacted_into INTEGER REFERENCES observations(id) ON DELETE SET NULL,
|
|
568
|
+
superseded_by INTEGER REFERENCES observations(id) ON DELETE SET NULL,
|
|
569
|
+
remote_source_id TEXT
|
|
570
|
+
);
|
|
571
|
+
INSERT INTO observations_repair SELECT * FROM observations;
|
|
572
|
+
DROP TABLE observations;
|
|
573
|
+
ALTER TABLE observations_repair RENAME TO observations;
|
|
574
|
+
CREATE INDEX IF NOT EXISTS idx_observations_project ON observations(project_id);
|
|
575
|
+
CREATE INDEX IF NOT EXISTS idx_observations_type ON observations(type);
|
|
576
|
+
CREATE INDEX IF NOT EXISTS idx_observations_created ON observations(created_at_epoch);
|
|
577
|
+
CREATE INDEX IF NOT EXISTS idx_observations_session ON observations(session_id);
|
|
578
|
+
CREATE INDEX IF NOT EXISTS idx_observations_lifecycle ON observations(lifecycle);
|
|
579
|
+
CREATE INDEX IF NOT EXISTS idx_observations_quality ON observations(quality);
|
|
580
|
+
CREATE INDEX IF NOT EXISTS idx_observations_user ON observations(user_id);
|
|
581
|
+
CREATE INDEX IF NOT EXISTS idx_observations_superseded ON observations(superseded_by);
|
|
582
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_observations_remote_source ON observations(remote_source_id) WHERE remote_source_id IS NOT NULL;
|
|
583
|
+
DROP TABLE IF EXISTS observations_fts;
|
|
584
|
+
CREATE VIRTUAL TABLE observations_fts USING fts5(
|
|
585
|
+
title, narrative, facts, concepts, content=observations, content_rowid=id
|
|
586
|
+
);
|
|
587
|
+
INSERT INTO observations_fts(observations_fts) VALUES('rebuild');
|
|
588
|
+
`);
|
|
589
|
+
db.exec("COMMIT");
|
|
590
|
+
} catch (err) {
|
|
591
|
+
db.exec("ROLLBACK");
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
517
595
|
var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max, m) => Math.max(max, m.version), 0);
|
|
518
596
|
|
|
519
597
|
// src/storage/sqlite.ts
|
|
@@ -580,6 +658,7 @@ class MemDatabase {
|
|
|
580
658
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
581
659
|
this.vecAvailable = this.loadVecExtension();
|
|
582
660
|
runMigrations(this.db);
|
|
661
|
+
ensureObservationTypes(this.db);
|
|
583
662
|
}
|
|
584
663
|
loadVecExtension() {
|
|
585
664
|
try {
|
|
@@ -1165,6 +1244,13 @@ async function main() {
|
|
|
1165
1244
|
}
|
|
1166
1245
|
try {
|
|
1167
1246
|
const filePath = String(event.tool_input["file_path"] ?? "unknown");
|
|
1247
|
+
const defaultSkips = [/migrations?\./, /\.test\./, /\.spec\./, /\.lock$/, /package\.json$/];
|
|
1248
|
+
const customSkips = (config.sentinel.skip_patterns || []).map((p) => new RegExp(p));
|
|
1249
|
+
const allSkips = [...defaultSkips, ...customSkips];
|
|
1250
|
+
if (allSkips.some((re) => re.test(filePath))) {
|
|
1251
|
+
db.close();
|
|
1252
|
+
process.exit(0);
|
|
1253
|
+
}
|
|
1168
1254
|
const content = event.tool_name === "Write" ? String(event.tool_input["content"] ?? "") : String(event.tool_input["new_string"] ?? "");
|
|
1169
1255
|
const result = await auditCodeChange(config, db, event.tool_name, filePath, content);
|
|
1170
1256
|
if (result.verdict === "PASS") {
|