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.
@@ -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, created_at, created_at_epoch
966
- ) 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", createdAt, now);
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, summary.request, summary.investigated, summary.learned, summary.completed, summary.next_steps, now);
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
  }