@jungjaehoon/mama-core 4.1.1 → 5.0.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/README.md +10 -10
- package/db/migrations/099-commitment-revision-graph.sql +3 -0
- package/dist/api/catalog.js +3 -6
- package/dist/db-adapter/node-sqlite-adapter.d.ts +5 -1
- package/dist/db-adapter/node-sqlite-adapter.js +27 -24
- package/dist/db-manager.d.ts +2 -13
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -6
- package/dist/knowledge/commitments.d.ts +2 -0
- package/dist/knowledge/commitments.js +1 -0
- package/dist/knowledge/graph-query.d.ts +10 -1
- package/dist/knowledge/graph-query.js +118 -25
- package/dist/knowledge/index.d.ts +11 -3
- package/dist/knowledge/index.js +9 -10
- package/dist/knowledge/judgments.d.ts +3 -1
- package/dist/knowledge/judgments.js +46 -26
- package/dist/knowledge/links.d.ts +50 -0
- package/dist/knowledge/links.js +152 -0
- package/dist/mama-api.d.ts +22 -7
- package/dist/mama-api.js +64 -15
- package/dist/memory/api.d.ts +16 -11
- package/dist/memory/api.js +376 -400
- package/dist/memory/decision-links.d.ts +54 -0
- package/dist/memory/decision-links.js +195 -0
- package/dist/memory/graph-read.d.ts +0 -16
- package/dist/memory/graph-read.js +10 -51
- package/dist/memory/judgment-types.d.ts +1 -20
- package/dist/memory/types.d.ts +13 -0
- package/dist/memory/write-adapters.d.ts +1 -39
- package/dist/memory/write-adapters.js +0 -98
- package/package.json +1 -1
- package/dist/knowledge/commitment-revision-migration.d.ts +0 -4
- package/dist/knowledge/commitment-revision-migration.js +0 -50
- package/dist/knowledge/decision-edges.d.ts +0 -70
- package/dist/knowledge/decision-edges.js +0 -123
- package/dist/memory/evolution-engine.d.ts +0 -22
- package/dist/memory/evolution-engine.js +0 -133
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
-
/**
|
|
3
|
-
* decision_edges + link_audit_log write boundary.
|
|
4
|
-
*
|
|
5
|
-
* Every mutation of the decision-edge graph goes through this module, so
|
|
6
|
-
* `decision_edges` and `link_audit_log` each have exactly one writing module
|
|
7
|
-
* inside `knowledge/` (the sibling judgments.ts owns the judgment-command
|
|
8
|
-
* edge insert). Each function performs the edge change and its audit row
|
|
9
|
-
* inside ONE adapter transaction - a link write and its audit row commit or
|
|
10
|
-
* fail together.
|
|
11
|
-
*/
|
|
12
|
-
type EdgeWriter = Pick<DatabaseAdapter, 'prepare' | 'transaction'>;
|
|
13
|
-
export interface DecisionEdgeKey {
|
|
14
|
-
fromId: string;
|
|
15
|
-
toId: string;
|
|
16
|
-
relationship: string;
|
|
17
|
-
}
|
|
18
|
-
export interface DecisionEdgeRow extends DecisionEdgeKey {
|
|
19
|
-
reason: string | null;
|
|
20
|
-
createdBy: string | null;
|
|
21
|
-
approvedByUser: number | null;
|
|
22
|
-
decisionId: string | null;
|
|
23
|
-
evidence: string | null;
|
|
24
|
-
createdAt: number | null;
|
|
25
|
-
}
|
|
26
|
-
export interface ProposedDecisionEdge extends DecisionEdgeKey {
|
|
27
|
-
reason: string;
|
|
28
|
-
decisionId: string | null;
|
|
29
|
-
evidence: string | null;
|
|
30
|
-
}
|
|
31
|
-
export interface DecisionEdgeDeleteFailure {
|
|
32
|
-
link: string;
|
|
33
|
-
error: string;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Insert-or-replace one decision edge row with explicit governance columns.
|
|
37
|
-
* Used by the public createEdge API (createdBy 'llm', approvedByUser 1) and by
|
|
38
|
-
* the backup-restore path (columns replayed from the backup file).
|
|
39
|
-
*/
|
|
40
|
-
export declare function upsertDecisionEdge(adapter: EdgeWriter, edge: DecisionEdgeRow): void;
|
|
41
|
-
/**
|
|
42
|
-
* Record a proposed edge pending user approval, plus its 'proposed' audit row.
|
|
43
|
-
*/
|
|
44
|
-
export declare function proposeDecisionEdge(adapter: EdgeWriter, edge: ProposedDecisionEdge): void;
|
|
45
|
-
/**
|
|
46
|
-
* Approve a pending edge, plus its 'approved' audit row.
|
|
47
|
-
*/
|
|
48
|
-
export declare function approveDecisionEdge(adapter: EdgeWriter, key: DecisionEdgeKey): void;
|
|
49
|
-
/**
|
|
50
|
-
* Reject a proposed edge: the 'rejected' audit row is written before the edge
|
|
51
|
-
* is deleted, in the same transaction.
|
|
52
|
-
*/
|
|
53
|
-
export declare function rejectDecisionEdge(adapter: EdgeWriter, key: DecisionEdgeKey, reason: string): void;
|
|
54
|
-
/**
|
|
55
|
-
* Remove the auto-generated edge population (created_by='user' with no
|
|
56
|
-
* proposal context) and record one 'deprecated' audit row per removed link.
|
|
57
|
-
* `links` is the already-scanned target set; it only feeds the audit rows.
|
|
58
|
-
*/
|
|
59
|
-
export declare function deprecateAutoDecisionEdges(adapter: EdgeWriter, links: readonly DecisionEdgeKey[], reason: string): void;
|
|
60
|
-
/**
|
|
61
|
-
* Delete one batch of edges, writing a 'deprecated' audit row for each. A
|
|
62
|
-
* per-link failure is collected and the batch continues, matching the cleanup
|
|
63
|
-
* tool's accounting; a failure of the batch itself throws to the caller.
|
|
64
|
-
*/
|
|
65
|
-
export declare function deleteDecisionEdgesWithAudit(adapter: EdgeWriter, links: readonly DecisionEdgeKey[], reason: string): {
|
|
66
|
-
deleted: number;
|
|
67
|
-
failures: DecisionEdgeDeleteFailure[];
|
|
68
|
-
};
|
|
69
|
-
export {};
|
|
70
|
-
//# sourceMappingURL=decision-edges.d.ts.map
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.upsertDecisionEdge = upsertDecisionEdge;
|
|
4
|
-
exports.proposeDecisionEdge = proposeDecisionEdge;
|
|
5
|
-
exports.approveDecisionEdge = approveDecisionEdge;
|
|
6
|
-
exports.rejectDecisionEdge = rejectDecisionEdge;
|
|
7
|
-
exports.deprecateAutoDecisionEdges = deprecateAutoDecisionEdges;
|
|
8
|
-
exports.deleteDecisionEdgesWithAudit = deleteDecisionEdgesWithAudit;
|
|
9
|
-
/**
|
|
10
|
-
* Insert-or-replace one decision edge row with explicit governance columns.
|
|
11
|
-
* Used by the public createEdge API (createdBy 'llm', approvedByUser 1) and by
|
|
12
|
-
* the backup-restore path (columns replayed from the backup file).
|
|
13
|
-
*/
|
|
14
|
-
function upsertDecisionEdge(adapter, edge) {
|
|
15
|
-
adapter
|
|
16
|
-
.prepare(`INSERT OR REPLACE INTO decision_edges
|
|
17
|
-
(from_id, to_id, relationship, reason, created_by, approved_by_user, decision_id, evidence, created_at)
|
|
18
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
19
|
-
.run(edge.fromId, edge.toId, edge.relationship, edge.reason, edge.createdBy, edge.approvedByUser, edge.decisionId, edge.evidence, edge.createdAt);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Record a proposed edge pending user approval, plus its 'proposed' audit row.
|
|
23
|
-
*/
|
|
24
|
-
function proposeDecisionEdge(adapter, edge) {
|
|
25
|
-
adapter.transaction(() => {
|
|
26
|
-
adapter
|
|
27
|
-
.prepare(`INSERT INTO decision_edges
|
|
28
|
-
(from_id, to_id, relationship, reason, created_by, approved_by_user, decision_id, evidence, created_at)
|
|
29
|
-
VALUES (?, ?, ?, ?, 'llm', 0, ?, ?, ?)`)
|
|
30
|
-
.run(edge.fromId, edge.toId, edge.relationship, edge.reason, edge.decisionId, edge.evidence, Date.now());
|
|
31
|
-
adapter
|
|
32
|
-
.prepare(`INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
|
|
33
|
-
VALUES (?, ?, ?, 'proposed', 'llm', ?, ?)`)
|
|
34
|
-
.run(edge.fromId, edge.toId, edge.relationship, edge.reason, Date.now());
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Approve a pending edge, plus its 'approved' audit row.
|
|
39
|
-
*/
|
|
40
|
-
function approveDecisionEdge(adapter, key) {
|
|
41
|
-
adapter.transaction(() => {
|
|
42
|
-
adapter
|
|
43
|
-
.prepare(`UPDATE decision_edges
|
|
44
|
-
SET approved_by_user = 1, approved_at = ?
|
|
45
|
-
WHERE from_id = ? AND to_id = ? AND relationship = ?`)
|
|
46
|
-
.run(Date.now(), key.fromId, key.toId, key.relationship);
|
|
47
|
-
adapter
|
|
48
|
-
.prepare(`INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, created_at)
|
|
49
|
-
VALUES (?, ?, ?, 'approved', 'user', ?)`)
|
|
50
|
-
.run(key.fromId, key.toId, key.relationship, Date.now());
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Reject a proposed edge: the 'rejected' audit row is written before the edge
|
|
55
|
-
* is deleted, in the same transaction.
|
|
56
|
-
*/
|
|
57
|
-
function rejectDecisionEdge(adapter, key, reason) {
|
|
58
|
-
adapter.transaction(() => {
|
|
59
|
-
adapter
|
|
60
|
-
.prepare(`INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
|
|
61
|
-
VALUES (?, ?, ?, 'rejected', 'user', ?, ?)`)
|
|
62
|
-
.run(key.fromId, key.toId, key.relationship, reason, Date.now());
|
|
63
|
-
adapter
|
|
64
|
-
.prepare(`DELETE FROM decision_edges
|
|
65
|
-
WHERE from_id = ? AND to_id = ? AND relationship = ?`)
|
|
66
|
-
.run(key.fromId, key.toId, key.relationship);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Remove the auto-generated edge population (created_by='user' with no
|
|
71
|
-
* proposal context) and record one 'deprecated' audit row per removed link.
|
|
72
|
-
* `links` is the already-scanned target set; it only feeds the audit rows.
|
|
73
|
-
*/
|
|
74
|
-
function deprecateAutoDecisionEdges(adapter, links, reason) {
|
|
75
|
-
adapter.transaction(() => {
|
|
76
|
-
adapter
|
|
77
|
-
.prepare(`DELETE FROM decision_edges WHERE created_by = 'user' AND decision_id IS NULL`)
|
|
78
|
-
.run();
|
|
79
|
-
const auditStmt = adapter.prepare(`INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
|
|
80
|
-
VALUES (?, ?, ?, 'deprecated', 'system', ?, ?)`);
|
|
81
|
-
const timestamp = Date.now();
|
|
82
|
-
for (const link of links) {
|
|
83
|
-
auditStmt.run(link.fromId, link.toId, link.relationship, reason, timestamp);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Delete one batch of edges, writing a 'deprecated' audit row for each. A
|
|
89
|
-
* per-link failure is collected and the batch continues, matching the cleanup
|
|
90
|
-
* tool's accounting; a failure of the batch itself throws to the caller.
|
|
91
|
-
*/
|
|
92
|
-
function deleteDecisionEdgesWithAudit(adapter, links, reason) {
|
|
93
|
-
const deleteStmt = adapter.prepare(`DELETE FROM decision_edges
|
|
94
|
-
WHERE from_id = ? AND to_id = ? AND relationship = ?`);
|
|
95
|
-
const auditStmt = adapter.prepare(`INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
|
|
96
|
-
VALUES (?, ?, ?, 'deprecated', 'system', ?, ?)`);
|
|
97
|
-
let deleted = 0;
|
|
98
|
-
const failures = [];
|
|
99
|
-
const processLinks = () => {
|
|
100
|
-
for (const link of links) {
|
|
101
|
-
try {
|
|
102
|
-
deleteStmt.run(link.fromId, link.toId, link.relationship);
|
|
103
|
-
auditStmt.run(link.fromId, link.toId, link.relationship, reason, Date.now());
|
|
104
|
-
deleted++;
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
failures.push({
|
|
108
|
-
link: `${link.fromId}->${link.toId}`,
|
|
109
|
-
error: error instanceof Error ? error.message : String(error),
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
// Use transaction if available, otherwise run directly
|
|
115
|
-
if (adapter.transaction) {
|
|
116
|
-
adapter.transaction(processLinks);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
processLinks();
|
|
120
|
-
}
|
|
121
|
-
return { deleted, failures };
|
|
122
|
-
}
|
|
123
|
-
//# sourceMappingURL=decision-edges.js.map
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { MemoryEdge, MemoryRecord } from './types.js';
|
|
2
|
-
interface EvolutionInput {
|
|
3
|
-
incoming: Pick<MemoryRecord, 'topic' | 'summary' | 'kind'>;
|
|
4
|
-
existing: Array<Pick<MemoryRecord, 'id' | 'topic' | 'summary' | 'kind'> & {
|
|
5
|
-
_semanticMatch?: boolean;
|
|
6
|
-
}>;
|
|
7
|
-
}
|
|
8
|
-
export interface EvolutionResult {
|
|
9
|
-
edges: MemoryEdge[];
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Resolve how an incoming memory relates to existing memories.
|
|
13
|
-
*
|
|
14
|
-
* Supersede rules (conservative — avoid information loss):
|
|
15
|
-
* 1. Raw → Extracted: structured fact replaces raw conversation (same topic, raw kind)
|
|
16
|
-
* 2. Same fact update: same topic + high summary overlap (≥0.6) between two extracted facts
|
|
17
|
-
*
|
|
18
|
-
* Everything else → builds_on (preserves independent facts under same topic)
|
|
19
|
-
*/
|
|
20
|
-
export declare function resolveMemoryEvolution(input: EvolutionInput): EvolutionResult;
|
|
21
|
-
export {};
|
|
22
|
-
//# sourceMappingURL=evolution-engine.d.ts.map
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveMemoryEvolution = resolveMemoryEvolution;
|
|
4
|
-
const STOP_WORDS = new Set([
|
|
5
|
-
'the',
|
|
6
|
-
'is',
|
|
7
|
-
'a',
|
|
8
|
-
'an',
|
|
9
|
-
'to',
|
|
10
|
-
'in',
|
|
11
|
-
'on',
|
|
12
|
-
'of',
|
|
13
|
-
'for',
|
|
14
|
-
'and',
|
|
15
|
-
'or',
|
|
16
|
-
'but',
|
|
17
|
-
'it',
|
|
18
|
-
'we',
|
|
19
|
-
'i',
|
|
20
|
-
'this',
|
|
21
|
-
'that',
|
|
22
|
-
'with',
|
|
23
|
-
'as',
|
|
24
|
-
'at',
|
|
25
|
-
'by',
|
|
26
|
-
'from',
|
|
27
|
-
'be',
|
|
28
|
-
'are',
|
|
29
|
-
'was',
|
|
30
|
-
'were',
|
|
31
|
-
'been',
|
|
32
|
-
'has',
|
|
33
|
-
'have',
|
|
34
|
-
'had',
|
|
35
|
-
'do',
|
|
36
|
-
'does',
|
|
37
|
-
'did',
|
|
38
|
-
'will',
|
|
39
|
-
'would',
|
|
40
|
-
'can',
|
|
41
|
-
'could',
|
|
42
|
-
'should',
|
|
43
|
-
'not',
|
|
44
|
-
'no',
|
|
45
|
-
'so',
|
|
46
|
-
'if',
|
|
47
|
-
'up',
|
|
48
|
-
'out',
|
|
49
|
-
'all',
|
|
50
|
-
'its',
|
|
51
|
-
'user',
|
|
52
|
-
'assistant',
|
|
53
|
-
]);
|
|
54
|
-
const MIN_TOKEN_LENGTH = 3;
|
|
55
|
-
function summaryOverlapRatio(left, right) {
|
|
56
|
-
const tokenize = (s) => new Set(s
|
|
57
|
-
.toLowerCase()
|
|
58
|
-
.split(/[^a-z0-9_]+/)
|
|
59
|
-
.filter((t) => t.length >= MIN_TOKEN_LENGTH && !STOP_WORDS.has(t)));
|
|
60
|
-
const leftTokens = tokenize(left);
|
|
61
|
-
const rightTokens = tokenize(right);
|
|
62
|
-
if (leftTokens.size === 0 && rightTokens.size === 0) {
|
|
63
|
-
return left.toLowerCase().trim() === right.toLowerCase().trim() ? 1 : 0;
|
|
64
|
-
}
|
|
65
|
-
const smaller = leftTokens.size <= rightTokens.size ? leftTokens : rightTokens;
|
|
66
|
-
const larger = leftTokens.size > rightTokens.size ? leftTokens : rightTokens;
|
|
67
|
-
let shared = 0;
|
|
68
|
-
for (const t of smaller) {
|
|
69
|
-
if (larger.has(t))
|
|
70
|
-
shared++;
|
|
71
|
-
}
|
|
72
|
-
return shared / Math.max(smaller.size, 1);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Resolve how an incoming memory relates to existing memories.
|
|
76
|
-
*
|
|
77
|
-
* Supersede rules (conservative — avoid information loss):
|
|
78
|
-
* 1. Raw → Extracted: structured fact replaces raw conversation (same topic, raw kind)
|
|
79
|
-
* 2. Same fact update: same topic + high summary overlap (≥0.6) between two extracted facts
|
|
80
|
-
*
|
|
81
|
-
* Everything else → builds_on (preserves independent facts under same topic)
|
|
82
|
-
*/
|
|
83
|
-
function resolveMemoryEvolution(input) {
|
|
84
|
-
const edges = [];
|
|
85
|
-
for (const existing of input.existing) {
|
|
86
|
-
if (existing.topic === input.incoming.topic) {
|
|
87
|
-
// Rule 1: Raw → Extracted supersede (ingestConversation saves raw first, then extracted)
|
|
88
|
-
// Raw records have kind='fact' but summary starts with conversation text (role prefixes)
|
|
89
|
-
const existingIsRaw = existing.kind === 'fact' && /^(user:|assistant:)/i.test(existing.summary.trim());
|
|
90
|
-
if (existingIsRaw) {
|
|
91
|
-
edges.push({
|
|
92
|
-
from_id: 'incoming',
|
|
93
|
-
to_id: existing.id,
|
|
94
|
-
type: 'supersedes',
|
|
95
|
-
reason: 'Structured extraction replaces raw conversation',
|
|
96
|
-
});
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
// Rule 2: Same fact update — supersede if summaries share meaningful overlap.
|
|
100
|
-
// Threshold 0.3 balances: "Use SQLite" → "Switch to PostgreSQL" (same topic, genuine update)
|
|
101
|
-
// vs "User's cat Luna" / "User's wedding plan" (same topic, independent facts).
|
|
102
|
-
const overlap = summaryOverlapRatio(existing.summary, input.incoming.summary);
|
|
103
|
-
if (overlap >= 0.3) {
|
|
104
|
-
edges.push({
|
|
105
|
-
from_id: 'incoming',
|
|
106
|
-
to_id: existing.id,
|
|
107
|
-
type: 'supersedes',
|
|
108
|
-
reason: `Updated fact (${(overlap * 100).toFixed(0)}% overlap)`,
|
|
109
|
-
});
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
// Different content under same topic → independent facts, link as builds_on
|
|
113
|
-
edges.push({
|
|
114
|
-
from_id: 'incoming',
|
|
115
|
-
to_id: existing.id,
|
|
116
|
-
type: 'builds_on',
|
|
117
|
-
reason: `Related but distinct (${(overlap * 100).toFixed(0)}% overlap)`,
|
|
118
|
-
});
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
// Semantic candidates (vector similarity >= 0.82) → builds_on
|
|
122
|
-
if (existing._semanticMatch) {
|
|
123
|
-
edges.push({
|
|
124
|
-
from_id: 'incoming',
|
|
125
|
-
to_id: existing.id,
|
|
126
|
-
type: 'builds_on',
|
|
127
|
-
reason: 'Semantically similar memory detected via vector search',
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return { edges };
|
|
132
|
-
}
|
|
133
|
-
//# sourceMappingURL=evolution-engine.js.map
|