engrm 0.4.11 → 0.4.13
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 +53 -2
- package/dist/cli.js +138 -7
- package/dist/hooks/elicitation-result.js +118 -5
- package/dist/hooks/post-tool-use.js +120 -6
- package/dist/hooks/pre-compact.js +116 -23
- package/dist/hooks/sentinel.js +114 -4
- package/dist/hooks/session-start.js +162 -25
- package/dist/hooks/stop.js +211 -15
- package/dist/hooks/user-prompt-submit.js +114 -4
- package/dist/server.js +2442 -1229
- package/package.json +1 -1
|
@@ -713,6 +713,18 @@ var MIGRATIONS = [
|
|
|
713
713
|
CREATE INDEX IF NOT EXISTS idx_tool_events_created
|
|
714
714
|
ON tool_events(created_at_epoch DESC, id DESC);
|
|
715
715
|
`
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
version: 11,
|
|
719
|
+
description: "Add observation provenance from tool and prompt chronology",
|
|
720
|
+
sql: `
|
|
721
|
+
ALTER TABLE observations ADD COLUMN source_tool TEXT;
|
|
722
|
+
ALTER TABLE observations ADD COLUMN source_prompt_number INTEGER;
|
|
723
|
+
CREATE INDEX IF NOT EXISTS idx_observations_source_tool
|
|
724
|
+
ON observations(source_tool, created_at_epoch DESC);
|
|
725
|
+
CREATE INDEX IF NOT EXISTS idx_observations_source_prompt
|
|
726
|
+
ON observations(session_id, source_prompt_number DESC);
|
|
727
|
+
`
|
|
716
728
|
}
|
|
717
729
|
];
|
|
718
730
|
function isVecExtensionLoaded(db) {
|
|
@@ -766,6 +778,8 @@ function inferLegacySchemaVersion(db) {
|
|
|
766
778
|
version = Math.max(version, 9);
|
|
767
779
|
if (tableExists(db, "tool_events"))
|
|
768
780
|
version = Math.max(version, 10);
|
|
781
|
+
if (columnExists(db, "observations", "source_tool"))
|
|
782
|
+
version = Math.max(version, 11);
|
|
769
783
|
return version;
|
|
770
784
|
}
|
|
771
785
|
function runMigrations(db) {
|
|
@@ -848,6 +862,86 @@ var LATEST_SCHEMA_VERSION = MIGRATIONS.filter((m) => !m.condition).reduce((max,
|
|
|
848
862
|
|
|
849
863
|
// src/storage/sqlite.ts
|
|
850
864
|
import { createHash as createHash2 } from "node:crypto";
|
|
865
|
+
|
|
866
|
+
// src/intelligence/summary-sections.ts
|
|
867
|
+
function extractSummaryItems(section, limit) {
|
|
868
|
+
if (!section || !section.trim())
|
|
869
|
+
return [];
|
|
870
|
+
const rawLines = section.split(`
|
|
871
|
+
`).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
872
|
+
const items = [];
|
|
873
|
+
const seen = new Set;
|
|
874
|
+
let heading = null;
|
|
875
|
+
for (const rawLine of rawLines) {
|
|
876
|
+
const line = stripSectionPrefix(rawLine);
|
|
877
|
+
if (!line)
|
|
878
|
+
continue;
|
|
879
|
+
const headingOnly = parseHeading(line);
|
|
880
|
+
if (headingOnly) {
|
|
881
|
+
heading = headingOnly;
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
const isBullet = /^[-*•]\s+/.test(line);
|
|
885
|
+
const stripped = line.replace(/^[-*•]\s+/, "").trim();
|
|
886
|
+
if (!stripped)
|
|
887
|
+
continue;
|
|
888
|
+
const item = heading && isBullet ? `${heading}: ${stripped}` : stripped;
|
|
889
|
+
const normalized = normalizeItem(item);
|
|
890
|
+
if (!normalized || seen.has(normalized))
|
|
891
|
+
continue;
|
|
892
|
+
seen.add(normalized);
|
|
893
|
+
items.push(item);
|
|
894
|
+
if (limit && items.length >= limit)
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
return items;
|
|
898
|
+
}
|
|
899
|
+
function formatSummaryItems(section, maxLen) {
|
|
900
|
+
const items = extractSummaryItems(section);
|
|
901
|
+
if (items.length === 0)
|
|
902
|
+
return null;
|
|
903
|
+
const cleaned = items.map((item) => `- ${item}`).join(`
|
|
904
|
+
`);
|
|
905
|
+
if (cleaned.length <= maxLen)
|
|
906
|
+
return cleaned;
|
|
907
|
+
const truncated = cleaned.slice(0, maxLen).trimEnd();
|
|
908
|
+
const lastBreak = Math.max(truncated.lastIndexOf(`
|
|
909
|
+
`), truncated.lastIndexOf(" "));
|
|
910
|
+
const safe = lastBreak > maxLen * 0.5 ? truncated.slice(0, lastBreak) : truncated;
|
|
911
|
+
return `${safe.trimEnd()}…`;
|
|
912
|
+
}
|
|
913
|
+
function normalizeSummarySection(section) {
|
|
914
|
+
const items = extractSummaryItems(section);
|
|
915
|
+
if (items.length === 0) {
|
|
916
|
+
const cleaned = section?.replace(/\s+/g, " ").trim() ?? "";
|
|
917
|
+
return cleaned || null;
|
|
918
|
+
}
|
|
919
|
+
return items.map((item) => `- ${item}`).join(`
|
|
920
|
+
`);
|
|
921
|
+
}
|
|
922
|
+
function normalizeSummaryRequest(value) {
|
|
923
|
+
const cleaned = value?.replace(/\s+/g, " ").trim() ?? "";
|
|
924
|
+
return cleaned || null;
|
|
925
|
+
}
|
|
926
|
+
function stripSectionPrefix(value) {
|
|
927
|
+
return value.replace(/^(request|investigated|learned|completed|next steps|summary):\s*/i, "").trim();
|
|
928
|
+
}
|
|
929
|
+
function parseHeading(value) {
|
|
930
|
+
const boldMatch = value.match(/^\*{1,2}\s*(.+?)\s*:\*{1,2}$/);
|
|
931
|
+
if (boldMatch?.[1]) {
|
|
932
|
+
return boldMatch[1].trim().replace(/\s+/g, " ");
|
|
933
|
+
}
|
|
934
|
+
const plainMatch = value.match(/^(.+?):$/);
|
|
935
|
+
if (plainMatch?.[1]) {
|
|
936
|
+
return plainMatch[1].trim().replace(/\s+/g, " ");
|
|
937
|
+
}
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
function normalizeItem(value) {
|
|
941
|
+
return value.toLowerCase().replace(/\*+/g, "").replace(/\s+/g, " ").trim();
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// src/storage/sqlite.ts
|
|
851
945
|
var IS_BUN = typeof globalThis.Bun !== "undefined";
|
|
852
946
|
function openDatabase(dbPath) {
|
|
853
947
|
if (IS_BUN) {
|
|
@@ -884,6 +978,7 @@ function openNodeDatabase(dbPath) {
|
|
|
884
978
|
const BetterSqlite3 = __require("better-sqlite3");
|
|
885
979
|
const raw = new BetterSqlite3(dbPath);
|
|
886
980
|
return {
|
|
981
|
+
__raw: raw,
|
|
887
982
|
query(sql) {
|
|
888
983
|
const stmt = raw.prepare(sql);
|
|
889
984
|
return {
|
|
@@ -921,7 +1016,7 @@ class MemDatabase {
|
|
|
921
1016
|
loadVecExtension() {
|
|
922
1017
|
try {
|
|
923
1018
|
const sqliteVec = __require("sqlite-vec");
|
|
924
|
-
sqliteVec.load(this.db);
|
|
1019
|
+
sqliteVec.load(this.db.__raw ?? this.db);
|
|
925
1020
|
return true;
|
|
926
1021
|
} catch {
|
|
927
1022
|
return false;
|
|
@@ -962,8 +1057,9 @@ class MemDatabase {
|
|
|
962
1057
|
const result = this.db.query(`INSERT INTO observations (
|
|
963
1058
|
session_id, project_id, type, title, narrative, facts, concepts,
|
|
964
1059
|
files_read, files_modified, quality, lifecycle, sensitivity,
|
|
965
|
-
user_id, device_id, agent,
|
|
966
|
-
|
|
1060
|
+
user_id, device_id, agent, source_tool, source_prompt_number,
|
|
1061
|
+
created_at, created_at_epoch
|
|
1062
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(obs.session_id ?? null, obs.project_id, obs.type, obs.title, obs.narrative ?? null, obs.facts ?? null, obs.concepts ?? null, obs.files_read ?? null, obs.files_modified ?? null, obs.quality, obs.lifecycle ?? "active", obs.sensitivity ?? "shared", obs.user_id, obs.device_id, obs.agent ?? "claude-code", obs.source_tool ?? null, obs.source_prompt_number ?? null, createdAt, now);
|
|
967
1063
|
const id = Number(result.lastInsertRowid);
|
|
968
1064
|
const row = this.getObservationById(id);
|
|
969
1065
|
this.ftsInsert(row);
|
|
@@ -1204,6 +1300,13 @@ class MemDatabase {
|
|
|
1204
1300
|
ORDER BY prompt_number ASC
|
|
1205
1301
|
LIMIT ?`).all(sessionId, limit);
|
|
1206
1302
|
}
|
|
1303
|
+
getLatestSessionPromptNumber(sessionId) {
|
|
1304
|
+
const row = this.db.query(`SELECT prompt_number FROM user_prompts
|
|
1305
|
+
WHERE session_id = ?
|
|
1306
|
+
ORDER BY prompt_number DESC
|
|
1307
|
+
LIMIT 1`).get(sessionId);
|
|
1308
|
+
return row?.prompt_number ?? null;
|
|
1309
|
+
}
|
|
1207
1310
|
insertToolEvent(input) {
|
|
1208
1311
|
const createdAt = input.created_at_epoch ?? Math.floor(Date.now() / 1000);
|
|
1209
1312
|
const result = this.db.query(`INSERT INTO tool_events (
|
|
@@ -1313,8 +1416,15 @@ class MemDatabase {
|
|
|
1313
1416
|
}
|
|
1314
1417
|
insertSessionSummary(summary) {
|
|
1315
1418
|
const now = Math.floor(Date.now() / 1000);
|
|
1419
|
+
const normalized = {
|
|
1420
|
+
request: normalizeSummaryRequest(summary.request),
|
|
1421
|
+
investigated: normalizeSummarySection(summary.investigated),
|
|
1422
|
+
learned: normalizeSummarySection(summary.learned),
|
|
1423
|
+
completed: normalizeSummarySection(summary.completed),
|
|
1424
|
+
next_steps: normalizeSummarySection(summary.next_steps)
|
|
1425
|
+
};
|
|
1316
1426
|
const result = this.db.query(`INSERT INTO session_summaries (session_id, project_id, user_id, request, investigated, learned, completed, next_steps, created_at_epoch)
|
|
1317
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id,
|
|
1427
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(summary.session_id, summary.project_id, summary.user_id, normalized.request, normalized.investigated, normalized.learned, normalized.completed, normalized.next_steps, now);
|
|
1318
1428
|
const id = Number(result.lastInsertRowid);
|
|
1319
1429
|
return this.db.query("SELECT * FROM session_summaries WHERE id = ?").get(id);
|
|
1320
1430
|
}
|