@pcircle/memesh 2.11.0 → 3.0.1

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.
Files changed (76) hide show
  1. package/README.md +153 -52
  2. package/dist/cli/assets/d3.v7.min.js +2 -0
  3. package/dist/cli/view.d.ts +1 -0
  4. package/dist/cli/view.d.ts.map +1 -1
  5. package/dist/cli/view.js +2198 -10
  6. package/dist/cli/view.js.map +1 -1
  7. package/dist/core/config.d.ts +33 -0
  8. package/dist/core/config.d.ts.map +1 -0
  9. package/dist/core/config.js +70 -0
  10. package/dist/core/config.js.map +1 -0
  11. package/dist/core/extractor.d.ts +26 -0
  12. package/dist/core/extractor.d.ts.map +1 -0
  13. package/dist/core/extractor.js +106 -0
  14. package/dist/core/extractor.js.map +1 -0
  15. package/dist/core/lifecycle.d.ts +9 -0
  16. package/dist/core/lifecycle.d.ts.map +1 -0
  17. package/dist/core/lifecycle.js +51 -0
  18. package/dist/core/lifecycle.js.map +1 -0
  19. package/dist/core/operations.d.ts +9 -0
  20. package/dist/core/operations.d.ts.map +1 -0
  21. package/dist/core/operations.js +342 -0
  22. package/dist/core/operations.js.map +1 -0
  23. package/dist/core/query-expander.d.ts +4 -0
  24. package/dist/core/query-expander.d.ts.map +1 -0
  25. package/dist/core/query-expander.js +114 -0
  26. package/dist/core/query-expander.js.map +1 -0
  27. package/dist/core/schema-export.d.ts +2 -0
  28. package/dist/core/schema-export.d.ts.map +1 -0
  29. package/dist/core/schema-export.js +67 -0
  30. package/dist/core/schema-export.js.map +1 -0
  31. package/dist/core/scoring.d.ts +25 -0
  32. package/dist/core/scoring.d.ts.map +1 -0
  33. package/dist/core/scoring.js +40 -0
  34. package/dist/core/scoring.js.map +1 -0
  35. package/dist/core/types.d.ts +124 -0
  36. package/dist/core/types.d.ts.map +1 -0
  37. package/dist/core/types.js +2 -0
  38. package/dist/core/types.js.map +1 -0
  39. package/dist/core/version-check.d.ts +10 -0
  40. package/dist/core/version-check.d.ts.map +1 -0
  41. package/dist/core/version-check.js +44 -0
  42. package/dist/core/version-check.js.map +1 -0
  43. package/dist/db.d.ts.map +1 -1
  44. package/dist/db.js +33 -0
  45. package/dist/db.js.map +1 -1
  46. package/dist/knowledge-graph.d.ts +16 -30
  47. package/dist/knowledge-graph.d.ts.map +1 -1
  48. package/dist/knowledge-graph.js +205 -24
  49. package/dist/knowledge-graph.js.map +1 -1
  50. package/dist/mcp/server.js +8 -1
  51. package/dist/mcp/server.js.map +1 -1
  52. package/dist/mcp/tools.d.ts +1 -92
  53. package/dist/mcp/tools.d.ts.map +1 -1
  54. package/dist/mcp/tools.js +1 -182
  55. package/dist/mcp/tools.js.map +1 -1
  56. package/dist/transports/cli/cli.d.ts +3 -0
  57. package/dist/transports/cli/cli.d.ts.map +1 -0
  58. package/dist/transports/cli/cli.js +378 -0
  59. package/dist/transports/cli/cli.js.map +1 -0
  60. package/dist/transports/http/server.d.ts +5 -0
  61. package/dist/transports/http/server.d.ts.map +1 -0
  62. package/dist/transports/http/server.js +314 -0
  63. package/dist/transports/http/server.js.map +1 -0
  64. package/dist/transports/mcp/handlers.d.ts +184 -0
  65. package/dist/transports/mcp/handlers.d.ts.map +1 -0
  66. package/dist/transports/mcp/handlers.js +271 -0
  67. package/dist/transports/mcp/handlers.js.map +1 -0
  68. package/hooks/hooks.json +24 -0
  69. package/package.json +29 -9
  70. package/plugin.json +15 -7
  71. package/scripts/hooks/post-commit.js +35 -6
  72. package/scripts/hooks/pre-compact.js +198 -0
  73. package/scripts/hooks/session-start.js +73 -25
  74. package/scripts/hooks/session-summary.js +255 -0
  75. package/skills/memesh/SKILL.md +73 -0
  76. package/skills/memesh-review/SKILL.md +54 -0
@@ -0,0 +1,124 @@
1
+ export interface Entity {
2
+ id: number;
3
+ name: string;
4
+ type: string;
5
+ created_at: string;
6
+ metadata?: any;
7
+ observations: string[];
8
+ tags: string[];
9
+ relations?: Relation[];
10
+ archived?: boolean;
11
+ access_count?: number;
12
+ last_accessed_at?: string;
13
+ confidence?: number;
14
+ valid_from?: string;
15
+ valid_until?: string;
16
+ namespace?: string;
17
+ }
18
+ export interface Relation {
19
+ from: string;
20
+ to: string;
21
+ type: string;
22
+ metadata?: any;
23
+ }
24
+ export interface CreateEntityInput {
25
+ name: string;
26
+ type: string;
27
+ observations?: string[];
28
+ tags?: string[];
29
+ metadata?: any;
30
+ namespace?: string;
31
+ }
32
+ export interface SearchOptions {
33
+ tag?: string;
34
+ limit?: number;
35
+ includeArchived?: boolean;
36
+ namespace?: string;
37
+ }
38
+ export interface RememberInput {
39
+ name: string;
40
+ type: string;
41
+ observations?: string[];
42
+ tags?: string[];
43
+ relations?: Array<{
44
+ to: string;
45
+ type: string;
46
+ }>;
47
+ namespace?: string;
48
+ }
49
+ export interface RecallInput {
50
+ query?: string;
51
+ tag?: string;
52
+ limit?: number;
53
+ include_archived?: boolean;
54
+ namespace?: string;
55
+ cross_project?: boolean;
56
+ }
57
+ export interface ForgetInput {
58
+ name: string;
59
+ observation?: string;
60
+ }
61
+ export interface RememberResult {
62
+ stored: boolean;
63
+ entityId: number;
64
+ name: string;
65
+ type: string;
66
+ observations: number;
67
+ tags: number;
68
+ relations: number;
69
+ superseded?: string[];
70
+ relationErrors?: string[];
71
+ }
72
+ export interface ForgetResult {
73
+ archived?: boolean;
74
+ name?: string;
75
+ message?: string;
76
+ observation_removed?: boolean;
77
+ observation?: string;
78
+ remaining_observations?: number;
79
+ }
80
+ export interface ConsolidateInput {
81
+ name?: string;
82
+ tag?: string;
83
+ min_observations?: number;
84
+ }
85
+ export interface ConsolidateResult {
86
+ consolidated: number;
87
+ entities_processed: string[];
88
+ observations_before: number;
89
+ observations_after: number;
90
+ error?: string;
91
+ }
92
+ export interface ExportInput {
93
+ tag?: string;
94
+ namespace?: string;
95
+ limit?: number;
96
+ }
97
+ export interface ExportResult {
98
+ version: string;
99
+ exported_at: string;
100
+ entity_count: number;
101
+ entities: Array<{
102
+ name: string;
103
+ type: string;
104
+ namespace: string;
105
+ observations: string[];
106
+ tags: string[];
107
+ relations: Array<{
108
+ to: string;
109
+ type: string;
110
+ }>;
111
+ }>;
112
+ }
113
+ export interface ImportInput {
114
+ data: ExportResult;
115
+ namespace?: string;
116
+ merge_strategy: 'skip' | 'overwrite' | 'append';
117
+ }
118
+ export interface ImportResult {
119
+ imported: number;
120
+ skipped: number;
121
+ appended: number;
122
+ errors: string[];
123
+ }
124
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ interface UpdateCheck {
2
+ currentVersion: string;
3
+ latestVersion: string | null;
4
+ checkedAt: string;
5
+ updateAvailable: boolean;
6
+ }
7
+ export declare function checkForUpdate(currentVersion: string): Promise<UpdateCheck>;
8
+ export declare function getLastUpdateCheck(): UpdateCheck | null;
9
+ export {};
10
+ //# sourceMappingURL=version-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check.d.ts","sourceRoot":"","sources":["../../src/core/version-check.ts"],"names":[],"mappings":"AAOA,UAAU,WAAW;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAMD,wBAAsB,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA4BjF;AAKD,wBAAgB,kBAAkB,IAAI,WAAW,GAAG,IAAI,CAOvD"}
@@ -0,0 +1,44 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { execFile } from 'child_process';
5
+ const UPDATE_CHECK_PATH = path.join(os.homedir(), '.memesh', 'update-check.json');
6
+ export async function checkForUpdate(currentVersion) {
7
+ const result = {
8
+ currentVersion,
9
+ latestVersion: null,
10
+ checkedAt: new Date().toISOString(),
11
+ updateAvailable: false,
12
+ };
13
+ try {
14
+ const latest = await new Promise((resolve, reject) => {
15
+ execFile('npm', ['show', '@pcircle/memesh', 'version'], { timeout: 5000 }, (err, stdout) => {
16
+ if (err)
17
+ reject(err);
18
+ else
19
+ resolve(stdout.trim());
20
+ });
21
+ });
22
+ result.latestVersion = latest;
23
+ result.updateAvailable = latest !== currentVersion;
24
+ }
25
+ catch {
26
+ }
27
+ try {
28
+ fs.mkdirSync(path.dirname(UPDATE_CHECK_PATH), { recursive: true });
29
+ fs.writeFileSync(UPDATE_CHECK_PATH, JSON.stringify(result, null, 2));
30
+ }
31
+ catch { }
32
+ return result;
33
+ }
34
+ export function getLastUpdateCheck() {
35
+ try {
36
+ if (!fs.existsSync(UPDATE_CHECK_PATH))
37
+ return null;
38
+ return JSON.parse(fs.readFileSync(UPDATE_CHECK_PATH, 'utf8'));
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ //# sourceMappingURL=version-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-check.js","sourceRoot":"","sources":["../../src/core/version-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC;AAalF,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,cAAsB;IACzD,MAAM,MAAM,GAAgB;QAC1B,cAAc;QACd,aAAa,EAAE,IAAI;QACnB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,eAAe,EAAE,KAAK;KACvB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3D,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,iBAAiB,EAAE,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;gBACzF,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAC9B,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC;QAC9B,MAAM,CAAC,eAAe,GAAG,MAAM,KAAK,cAAc,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;IAET,CAAC;IAGD,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,OAAO,MAAM,CAAC;AAChB,CAAC;AAKD,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,iBAAiB,CAAC;YAAE,OAAO,IAAI,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
package/dist/db.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAyDtC,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAiB/D;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAAC,QAAQ,CAG/C"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAkEtC,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAsD/D;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAAC,QAAQ,CAG/C"}
package/dist/db.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import Database from 'better-sqlite3';
2
+ import * as sqliteVec from 'sqlite-vec';
2
3
  import path from 'path';
3
4
  import os from 'os';
4
5
  import fs from 'fs';
6
+ import { runAutoDecay } from './core/lifecycle.js';
5
7
  let db = null;
6
8
  const SCHEMA_SQL = `
7
9
  CREATE TABLE IF NOT EXISTS entities (
@@ -41,6 +43,13 @@ CREATE TABLE IF NOT EXISTS tags (
41
43
 
42
44
  CREATE INDEX IF NOT EXISTS idx_tags_entity ON tags(entity_id);
43
45
  CREATE INDEX IF NOT EXISTS idx_tags_tag ON tags(tag);
46
+ DELETE FROM tags
47
+ WHERE id NOT IN (
48
+ SELECT MIN(id)
49
+ FROM tags
50
+ GROUP BY entity_id, tag
51
+ );
52
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_entity_tag_unique ON tags(entity_id, tag);
44
53
  CREATE INDEX IF NOT EXISTS idx_observations_entity ON observations(entity_id);
45
54
  CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity_id);
46
55
  CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity_id);
@@ -64,6 +73,30 @@ export function openDatabase(dbPath) {
64
73
  db.pragma('foreign_keys = ON');
65
74
  db.exec(SCHEMA_SQL);
66
75
  db.exec(FTS_SQL);
76
+ const columns = db.prepare("PRAGMA table_info(entities)").all();
77
+ if (!columns.some((c) => c.name === 'status')) {
78
+ db.exec("ALTER TABLE entities ADD COLUMN status TEXT NOT NULL DEFAULT 'active'");
79
+ db.exec("CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status)");
80
+ }
81
+ const scoringCols = db.prepare("PRAGMA table_info(entities)").all();
82
+ if (!scoringCols.some((c) => c.name === 'access_count')) {
83
+ db.exec("ALTER TABLE entities ADD COLUMN access_count INTEGER DEFAULT 0");
84
+ db.exec("ALTER TABLE entities ADD COLUMN last_accessed_at TIMESTAMP");
85
+ db.exec("ALTER TABLE entities ADD COLUMN confidence REAL DEFAULT 1.0");
86
+ db.exec("ALTER TABLE entities ADD COLUMN valid_from TIMESTAMP");
87
+ db.exec("ALTER TABLE entities ADD COLUMN valid_until TIMESTAMP");
88
+ }
89
+ if (!scoringCols.some((c) => c.name === 'namespace')) {
90
+ db.exec("ALTER TABLE entities ADD COLUMN namespace TEXT DEFAULT 'personal'");
91
+ db.exec("CREATE INDEX IF NOT EXISTS idx_entities_namespace ON entities(namespace)");
92
+ }
93
+ runAutoDecay(db);
94
+ sqliteVec.load(db);
95
+ db.exec(`
96
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_vec USING vec0(
97
+ embedding float[384]
98
+ );
99
+ `);
67
100
  return db;
68
101
  }
69
102
  export function closeDatabase() {
package/dist/db.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,IAAI,EAAE,GAA6B,IAAI,CAAC;AAExC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyClB,CAAC;AAEF,MAAM,OAAO,GAAG;;;;;CAKf,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,MAAe;IAC1C,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAElB,MAAM,YAAY,GAAG,MAAM;WACtB,OAAO,CAAC,GAAG,CAAC,cAAc;WAC1B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,oBAAoB,CAAC,CAAC;IAE9D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACvC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEvC,EAAE,GAAG,IAAI,QAAQ,CAAC,YAAY,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/B,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEjB,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,EAAE,EAAE,CAAC;QACP,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,EAAE,GAAG,IAAI,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAChD,OAAO,EAAE,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
@@ -1,31 +1,6 @@
1
1
  import Database from 'better-sqlite3';
2
- export interface Entity {
3
- id: number;
4
- name: string;
5
- type: string;
6
- created_at: string;
7
- metadata?: any;
8
- observations: string[];
9
- tags: string[];
10
- relations?: Relation[];
11
- }
12
- export interface Relation {
13
- from: string;
14
- to: string;
15
- type: string;
16
- metadata?: any;
17
- }
18
- export interface CreateEntityInput {
19
- name: string;
20
- type: string;
21
- observations?: string[];
22
- tags?: string[];
23
- metadata?: any;
24
- }
25
- export interface SearchOptions {
26
- tag?: string;
27
- limit?: number;
28
- }
2
+ export type { Entity, Relation, CreateEntityInput, SearchOptions } from './core/types.js';
3
+ import type { Entity, Relation, CreateEntityInput, SearchOptions } from './core/types.js';
29
4
  export declare class KnowledgeGraph {
30
5
  private db;
31
6
  constructor(db: Database.Database);
@@ -33,16 +8,27 @@ export declare class KnowledgeGraph {
33
8
  observations?: string[];
34
9
  tags?: string[];
35
10
  metadata?: any;
11
+ namespace?: string;
36
12
  }): number;
37
13
  createEntitiesBatch(entities: CreateEntityInput[]): void;
38
14
  createRelation(fromName: string, toName: string, relationType: string, metadata?: any): void;
39
15
  getEntity(name: string): Entity | null;
40
16
  getRelations(entityName: string): Relation[];
41
17
  search(query?: string, opts?: SearchOptions): Entity[];
42
- listRecent(limit?: number): Entity[];
43
- deleteEntity(name: string): {
44
- deleted: boolean;
18
+ trackAccess(entityIds: number[]): void;
19
+ findConflicts(entityNames: string[]): string[];
20
+ listRecent(limit?: number, includeArchived?: boolean, namespace?: string): Entity[];
21
+ private listRecentByTag;
22
+ archiveEntity(name: string): {
23
+ archived: boolean;
24
+ name?: string;
25
+ previousStatus?: string;
26
+ };
27
+ removeObservation(entityName: string, observationContent: string): {
28
+ removed: boolean;
29
+ remainingObservations: number;
45
30
  };
31
+ private deleteEntity;
46
32
  private rebuildFts;
47
33
  }
48
34
  //# sourceMappingURL=knowledge-graph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge-graph.d.ts","sourceRoot":"","sources":["../src/knowledge-graph.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,cAAc;IACb,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAEzC,YAAY,CACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,GAAG,CAAA;KAAE,GAClE,MAAM;IAmDT,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAaxD,cAAc,CACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,GAAG,GACb,IAAI;IA2BP,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA+BtC,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,EAAE;IAmB5C,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,EAAE;IAgDtD,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE;IAUpC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE;IAyBhD,OAAO,CAAC,UAAU;CAsBnB"}
1
+ {"version":3,"file":"knowledge-graph.d.ts","sourceRoot":"","sources":["../src/knowledge-graph.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE1F,qBAAa,cAAc;IACb,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAEzC,YAAY,CACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,GAAG,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GACtF,MAAM;IA6DT,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAcxD,cAAc,CACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,GAAG,GACb,IAAI;IA2BP,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAwCtC,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,EAAE;IAmB5C,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,EAAE;IAiHtD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAkBtC,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAuB9C,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE;IAmBnF,OAAO,CAAC,eAAe;IA4BvB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE;IA+B1F,iBAAiB,CACf,UAAU,EAAE,MAAM,EAClB,kBAAkB,EAAE,MAAM,GACzB;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,qBAAqB,EAAE,MAAM,CAAA;KAAE;IA8BtD,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,UAAU;CAsBnB"}
@@ -5,19 +5,25 @@ export class KnowledgeGraph {
5
5
  }
6
6
  createEntity(name, type, opts) {
7
7
  const insertResult = this.db
8
- .prepare('INSERT OR IGNORE INTO entities (name, type, metadata) VALUES (?, ?, ?)')
9
- .run(name, type, opts?.metadata ? JSON.stringify(opts.metadata) : null);
8
+ .prepare('INSERT OR IGNORE INTO entities (name, type, metadata, namespace) VALUES (?, ?, ?, ?)')
9
+ .run(name, type, opts?.metadata ? JSON.stringify(opts.metadata) : null, opts?.namespace ?? 'personal');
10
10
  const isNewEntity = insertResult.changes > 0;
11
11
  const row = this.db
12
- .prepare('SELECT id FROM entities WHERE name = ?')
12
+ .prepare('SELECT id, status FROM entities WHERE name = ?')
13
13
  .get(name);
14
14
  const entityId = row.id;
15
- const prevObs = isNewEntity
15
+ const wasArchived = !isNewEntity && row.status === 'archived';
16
+ if (wasArchived) {
17
+ this.db
18
+ .prepare("UPDATE entities SET status = 'active' WHERE name = ?")
19
+ .run(name);
20
+ }
21
+ const prevObs = isNewEntity || wasArchived
16
22
  ? []
17
23
  : this.db
18
24
  .prepare('SELECT content FROM observations WHERE entity_id = ?')
19
25
  .all(entityId);
20
- const prevObsText = isNewEntity
26
+ const prevObsText = isNewEntity || wasArchived
21
27
  ? undefined
22
28
  : prevObs.map((o) => o.content).join(' ');
23
29
  if (opts?.observations?.length) {
@@ -28,7 +34,7 @@ export class KnowledgeGraph {
28
34
  }
29
35
  this.rebuildFts(entityId, name, prevObsText);
30
36
  if (opts?.tags?.length) {
31
- const insertTag = this.db.prepare('INSERT INTO tags (entity_id, tag) VALUES (?, ?)');
37
+ const insertTag = this.db.prepare('INSERT OR IGNORE INTO tags (entity_id, tag) VALUES (?, ?)');
32
38
  for (const tag of opts.tags) {
33
39
  insertTag.run(entityId, tag);
34
40
  }
@@ -42,6 +48,7 @@ export class KnowledgeGraph {
42
48
  observations: e.observations,
43
49
  tags: e.tags,
44
50
  metadata: e.metadata,
51
+ namespace: e.namespace,
45
52
  });
46
53
  }
47
54
  });
@@ -66,7 +73,7 @@ export class KnowledgeGraph {
66
73
  }
67
74
  getEntity(name) {
68
75
  const row = this.db
69
- .prepare('SELECT id, name, type, created_at, metadata FROM entities WHERE name = ?')
76
+ .prepare('SELECT id, name, type, created_at, metadata, status, access_count, last_accessed_at, confidence, valid_from, valid_until, namespace FROM entities WHERE name = ?')
70
77
  .get(name);
71
78
  if (!row)
72
79
  return null;
@@ -88,6 +95,13 @@ export class KnowledgeGraph {
88
95
  observations,
89
96
  tags,
90
97
  relations: relations.length > 0 ? relations : undefined,
98
+ ...(row.status === 'archived' ? { archived: true } : {}),
99
+ access_count: row.access_count ?? 0,
100
+ last_accessed_at: row.last_accessed_at ?? undefined,
101
+ confidence: row.confidence ?? 1.0,
102
+ valid_from: row.valid_from ?? undefined,
103
+ valid_until: row.valid_until ?? undefined,
104
+ namespace: row.namespace ?? 'personal',
91
105
  };
92
106
  }
93
107
  getRelations(entityName) {
@@ -108,49 +122,216 @@ export class KnowledgeGraph {
108
122
  search(query, opts) {
109
123
  const limit = opts?.limit ?? 20;
110
124
  if (!query || query.trim() === '') {
111
- return this.listRecent(limit);
125
+ if (opts?.tag) {
126
+ return this.listRecentByTag(opts.tag, limit, opts?.includeArchived, opts?.namespace);
127
+ }
128
+ return this.listRecent(limit, opts?.includeArchived, opts?.namespace);
112
129
  }
113
130
  const sanitized = query.replace(/"/g, '""').trim();
114
131
  const tokens = sanitized.split(/\s+/).filter((t) => t.length > 0);
115
132
  if (tokens.length === 0)
116
- return this.listRecent(limit);
133
+ return this.listRecent(limit, opts?.includeArchived, opts?.namespace);
117
134
  const ftsQuery = tokens.map((token) => `"${token}"`).join(' ');
135
+ const statusFilter = opts?.includeArchived ? '' : "AND e.status = 'active'";
136
+ const namespaceFilter = opts?.namespace ? 'AND e.namespace = ?' : '';
118
137
  let ftsRows;
119
138
  try {
120
- ftsRows = this.db
121
- .prepare(`SELECT e.name FROM entities_fts f
122
- JOIN entities e ON e.id = f.rowid
123
- WHERE entities_fts MATCH ?
124
- LIMIT ?`)
125
- .all(ftsQuery, limit);
139
+ if (opts?.tag) {
140
+ const params = [ftsQuery, opts.tag];
141
+ if (opts?.namespace)
142
+ params.push(opts.namespace);
143
+ params.push(limit);
144
+ ftsRows = this.db
145
+ .prepare(`SELECT DISTINCT e.id, e.name FROM entities_fts f
146
+ JOIN entities e ON e.id = f.rowid
147
+ JOIN tags t ON t.entity_id = e.id
148
+ WHERE entities_fts MATCH ?
149
+ AND t.tag = ?
150
+ ${statusFilter}
151
+ ${namespaceFilter}
152
+ ORDER BY e.id DESC
153
+ LIMIT ?`)
154
+ .all(...params);
155
+ }
156
+ else {
157
+ const params = [ftsQuery];
158
+ if (opts?.namespace)
159
+ params.push(opts.namespace);
160
+ params.push(limit);
161
+ ftsRows = this.db
162
+ .prepare(`SELECT e.id, e.name FROM entities_fts f
163
+ JOIN entities e ON e.id = f.rowid
164
+ WHERE entities_fts MATCH ?
165
+ ${statusFilter}
166
+ ${namespaceFilter}
167
+ ORDER BY e.id DESC
168
+ LIMIT ?`)
169
+ .all(...params);
170
+ }
126
171
  }
127
172
  catch (err) {
128
173
  if (err.message?.includes('fts5'))
129
174
  return [];
130
175
  throw err;
131
176
  }
132
- if (ftsRows.length === 0)
133
- return [];
134
177
  const results = [];
178
+ const seenIds = new Set();
135
179
  for (const ftsRow of ftsRows) {
136
180
  const entity = this.getEntity(ftsRow.name);
137
181
  if (!entity)
138
182
  continue;
139
- if (opts?.tag) {
140
- if (!entity.tags.includes(opts.tag))
183
+ seenIds.add(ftsRow.id);
184
+ results.push(entity);
185
+ }
186
+ if (opts?.includeArchived) {
187
+ const tagJoin = opts?.tag ? 'JOIN tags t ON t.entity_id = e.id' : '';
188
+ const tagFilter = opts?.tag ? 'AND t.tag = ?' : '';
189
+ const archivedNamespaceFilter = opts?.namespace ? 'AND e.namespace = ?' : '';
190
+ const archivedParams = [`%${query}%`, `%${query}%`];
191
+ if (opts?.tag)
192
+ archivedParams.push(opts.tag);
193
+ if (opts?.namespace)
194
+ archivedParams.push(opts.namespace);
195
+ const archivedRows = this.db
196
+ .prepare(`SELECT DISTINCT e.id, e.name
197
+ FROM entities e
198
+ LEFT JOIN observations o ON o.entity_id = e.id
199
+ ${tagJoin}
200
+ WHERE e.status = 'archived'
201
+ AND (e.name LIKE ? OR o.content LIKE ?)
202
+ ${tagFilter}
203
+ ${archivedNamespaceFilter}
204
+ ORDER BY e.id DESC
205
+ LIMIT ?`)
206
+ .all(...archivedParams, limit);
207
+ for (const row of archivedRows) {
208
+ if (seenIds.has(row.id))
209
+ continue;
210
+ const entity = this.getEntity(row.name);
211
+ if (!entity)
141
212
  continue;
213
+ results.push(entity);
142
214
  }
143
- results.push(entity);
144
215
  }
216
+ const entityIds = results.map((e) => e.id);
217
+ this.trackAccess(entityIds);
145
218
  return results;
146
219
  }
147
- listRecent(limit) {
220
+ trackAccess(entityIds) {
221
+ if (entityIds.length === 0)
222
+ return;
223
+ const now = new Date().toISOString();
224
+ const stmt = this.db.prepare('UPDATE entities SET access_count = access_count + 1, last_accessed_at = ? WHERE id = ?');
225
+ const txn = this.db.transaction(() => {
226
+ for (const id of entityIds) {
227
+ stmt.run(now, id);
228
+ }
229
+ });
230
+ txn();
231
+ }
232
+ findConflicts(entityNames) {
233
+ if (entityNames.length < 2)
234
+ return [];
235
+ const conflicts = [];
236
+ const placeholders = entityNames.map(() => '?').join(',');
237
+ const rows = this.db.prepare(`
238
+ SELECT e_from.name AS from_name, e_to.name AS to_name
239
+ FROM relations r
240
+ JOIN entities e_from ON r.from_entity_id = e_from.id
241
+ JOIN entities e_to ON r.to_entity_id = e_to.id
242
+ WHERE r.relation_type = 'contradicts'
243
+ AND e_from.name IN (${placeholders})
244
+ AND e_to.name IN (${placeholders})
245
+ `).all(...entityNames, ...entityNames);
246
+ for (const row of rows) {
247
+ conflicts.push(`"${row.from_name}" contradicts "${row.to_name}"`);
248
+ }
249
+ return conflicts;
250
+ }
251
+ listRecent(limit, includeArchived, namespace) {
252
+ const statusFilter = includeArchived ? '' : "AND status = 'active'";
253
+ const namespaceFilter = namespace ? 'AND namespace = ?' : '';
254
+ const params = [];
255
+ if (namespace)
256
+ params.push(namespace);
257
+ params.push(limit ?? 20);
148
258
  const rows = this.db
149
- .prepare('SELECT name FROM entities ORDER BY id DESC LIMIT ?')
150
- .all(limit ?? 20);
151
- return rows
259
+ .prepare(`SELECT name FROM entities WHERE 1=1 ${statusFilter} ${namespaceFilter} ORDER BY id DESC LIMIT ?`)
260
+ .all(...params);
261
+ const results = rows
152
262
  .map((r) => this.getEntity(r.name))
153
263
  .filter((e) => e !== null);
264
+ const entityIds = results.map((e) => e.id);
265
+ this.trackAccess(entityIds);
266
+ return results;
267
+ }
268
+ listRecentByTag(tag, limit, includeArchived, namespace) {
269
+ const statusFilter = includeArchived ? '' : "AND e.status = 'active'";
270
+ const namespaceFilter = namespace ? 'AND e.namespace = ?' : '';
271
+ const params = [tag];
272
+ if (namespace)
273
+ params.push(namespace);
274
+ params.push(limit);
275
+ const rows = this.db
276
+ .prepare(`SELECT DISTINCT e.name
277
+ FROM entities e
278
+ JOIN tags t ON t.entity_id = e.id
279
+ WHERE t.tag = ?
280
+ ${statusFilter}
281
+ ${namespaceFilter}
282
+ ORDER BY e.id DESC
283
+ LIMIT ?`)
284
+ .all(...params);
285
+ const results = rows
286
+ .map((r) => this.getEntity(r.name))
287
+ .filter((e) => e !== null);
288
+ const entityIds = results.map((e) => e.id);
289
+ this.trackAccess(entityIds);
290
+ return results;
291
+ }
292
+ archiveEntity(name) {
293
+ const row = this.db
294
+ .prepare('SELECT id, status FROM entities WHERE name = ?')
295
+ .get(name);
296
+ if (!row)
297
+ return { archived: false };
298
+ const allObs = this.db
299
+ .prepare('SELECT content FROM observations WHERE entity_id = ?')
300
+ .all(row.id);
301
+ const obsText = allObs.map((o) => o.content).join(' ');
302
+ try {
303
+ this.db
304
+ .prepare("INSERT INTO entities_fts (entities_fts, rowid, name, observations) VALUES('delete', ?, ?, ?)")
305
+ .run(row.id, name, obsText);
306
+ }
307
+ catch {
308
+ }
309
+ this.db
310
+ .prepare("UPDATE entities SET status = 'archived' WHERE id = ?")
311
+ .run(row.id);
312
+ return { archived: true, name, previousStatus: row.status };
313
+ }
314
+ removeObservation(entityName, observationContent) {
315
+ const row = this.db
316
+ .prepare('SELECT id FROM entities WHERE name = ?')
317
+ .get(entityName);
318
+ if (!row)
319
+ return { removed: false, remainingObservations: 0 };
320
+ const prevObs = this.db
321
+ .prepare('SELECT content FROM observations WHERE entity_id = ?')
322
+ .all(row.id);
323
+ const prevObsText = prevObs.map((o) => o.content).join(' ');
324
+ const deleteResult = this.db
325
+ .prepare('DELETE FROM observations WHERE entity_id = ? AND content = ?')
326
+ .run(row.id, observationContent);
327
+ if (deleteResult.changes === 0) {
328
+ return { removed: false, remainingObservations: prevObs.length };
329
+ }
330
+ this.rebuildFts(row.id, entityName, prevObsText);
331
+ const remaining = this.db
332
+ .prepare('SELECT COUNT(*) as c FROM observations WHERE entity_id = ?')
333
+ .get(row.id);
334
+ return { removed: true, remainingObservations: remaining.c };
154
335
  }
155
336
  deleteEntity(name) {
156
337
  const row = this.db