@orquentra/codebuddy 2.14.0 → 2.15.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/ARCHITECTURE.md +98 -0
- package/README.md +87 -6
- package/dist/{chunk-LW75SKZR.js → chunk-4BSO3HST.js} +126 -18
- package/dist/chunk-4BSO3HST.js.map +1 -0
- package/dist/{chunk-CAEPHLHC.js → chunk-57642NGU.js} +2 -44
- package/dist/chunk-57642NGU.js.map +1 -0
- package/dist/{chunk-ESZXCZIN.js → chunk-6YI2D5F5.js} +33 -5
- package/dist/chunk-6YI2D5F5.js.map +1 -0
- package/dist/{chunk-4DKYKDHI.js → chunk-7IPSDDO5.js} +5 -3
- package/dist/{chunk-4DKYKDHI.js.map → chunk-7IPSDDO5.js.map} +1 -1
- package/dist/chunk-BTTQWSDS.js +372 -0
- package/dist/chunk-BTTQWSDS.js.map +1 -0
- package/dist/{chunk-YJPCS2GP.js → chunk-D5E7SGI5.js} +57 -18
- package/dist/chunk-D5E7SGI5.js.map +1 -0
- package/dist/chunk-EMWGLKPR.js +45 -0
- package/dist/chunk-EMWGLKPR.js.map +1 -0
- package/dist/chunk-ITXE27SZ.js +358 -0
- package/dist/chunk-ITXE27SZ.js.map +1 -0
- package/dist/{chunk-LRQNPYCK.js → chunk-JJQDVGF7.js} +13 -7
- package/dist/chunk-JJQDVGF7.js.map +1 -0
- package/dist/{chunk-XSNW6Y3X.js → chunk-KQCP3ZYH.js} +28 -24
- package/dist/chunk-KQCP3ZYH.js.map +1 -0
- package/dist/{chunk-YWDH6DQ6.js → chunk-KYRM3HPP.js} +21 -19
- package/dist/{chunk-YWDH6DQ6.js.map → chunk-KYRM3HPP.js.map} +1 -1
- package/dist/{chunk-KNR2P2UY.js → chunk-M75SN4D4.js} +19 -4
- package/dist/chunk-M75SN4D4.js.map +1 -0
- package/dist/{chunk-L6BRYZ6G.js → chunk-RZ266NHE.js} +7 -7
- package/dist/chunk-U7KDM62P.js +43 -0
- package/dist/chunk-U7KDM62P.js.map +1 -0
- package/dist/chunk-UATJCD2T.js +368 -0
- package/dist/chunk-UATJCD2T.js.map +1 -0
- package/dist/{chunk-YNSTIMCF.js → chunk-UR3DF74I.js} +352 -157
- package/dist/chunk-UR3DF74I.js.map +1 -0
- package/dist/chunk-UTPC66MV.js +128 -0
- package/dist/chunk-UTPC66MV.js.map +1 -0
- package/dist/chunk-WHKXUXHE.js +156 -0
- package/dist/chunk-WHKXUXHE.js.map +1 -0
- package/dist/{chunk-7QMHIJBA.js → chunk-YMXFSHXT.js} +969 -968
- package/dist/chunk-YMXFSHXT.js.map +1 -0
- package/dist/cli/index.js +4 -4
- package/dist/config-TXQJCCVI.js +15 -0
- package/dist/expand-KWYGNCKZ.js +98 -0
- package/dist/expand-KWYGNCKZ.js.map +1 -0
- package/dist/file-vectors-MKXTSGGH.js +19 -0
- package/dist/index-facts-4MTEHKMN.js +38 -0
- package/dist/index-pass-TBDUEZCB.js +14 -0
- package/dist/index.js +24 -22
- package/dist/{install-EU35ERK5.js → install-E3JWINTN.js} +11 -10
- package/dist/{lean-43ADYTBW.js → lean-MLTAFS5L.js} +8 -7
- package/dist/local-75DJECVR.js +48 -0
- package/dist/local-llm-MECVJTVU.js +19 -0
- package/dist/local-llm-MECVJTVU.js.map +1 -0
- package/dist/{main-SFC5LV4E.js → main-RLMKQZW4.js} +332 -123
- package/dist/main-RLMKQZW4.js.map +1 -0
- package/dist/{memory-TGHVTBME.js → memory-PFFVMTCH.js} +9 -6
- package/dist/{memory-TGHVTBME.js.map → memory-PFFVMTCH.js.map} +1 -1
- package/dist/{operations-6Z6ZDVBP.js → operations-STR7S5UO.js} +8 -7
- package/dist/operations-STR7S5UO.js.map +1 -0
- package/dist/{rerank-IQAEWLQR.js → rerank-XXKWDI5Q.js} +7 -4
- package/dist/{rerank-IQAEWLQR.js.map → rerank-XXKWDI5Q.js.map} +1 -1
- package/dist/{runtime-W5AW32SD.js → runtime-SWFWGTQ4.js} +9 -8
- package/dist/runtime-SWFWGTQ4.js.map +1 -0
- package/dist/summaries-QXWFFDUZ.js +31 -0
- package/dist/summaries-QXWFFDUZ.js.map +1 -0
- package/dist/{zvec-24HLAFGN.js → zvec-F73IBWO5.js} +13 -5
- package/dist/zvec-F73IBWO5.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-7QMHIJBA.js.map +0 -1
- package/dist/chunk-CAEPHLHC.js.map +0 -1
- package/dist/chunk-ESC4Y3L7.js +0 -204
- package/dist/chunk-ESC4Y3L7.js.map +0 -1
- package/dist/chunk-ESZXCZIN.js.map +0 -1
- package/dist/chunk-KNR2P2UY.js.map +0 -1
- package/dist/chunk-LRQNPYCK.js.map +0 -1
- package/dist/chunk-LW75SKZR.js.map +0 -1
- package/dist/chunk-SFMIMLXR.js +0 -63
- package/dist/chunk-SFMIMLXR.js.map +0 -1
- package/dist/chunk-XSNW6Y3X.js.map +0 -1
- package/dist/chunk-YJPCS2GP.js.map +0 -1
- package/dist/chunk-YNSTIMCF.js.map +0 -1
- package/dist/config-GEZFLXK6.js +0 -12
- package/dist/main-SFC5LV4E.js.map +0 -1
- package/dist/summaries-32PHOB6L.js +0 -20
- /package/dist/{chunk-L6BRYZ6G.js.map → chunk-RZ266NHE.js.map} +0 -0
- /package/dist/{config-GEZFLXK6.js.map → config-TXQJCCVI.js.map} +0 -0
- /package/dist/{install-EU35ERK5.js.map → file-vectors-MKXTSGGH.js.map} +0 -0
- /package/dist/{lean-43ADYTBW.js.map → index-facts-4MTEHKMN.js.map} +0 -0
- /package/dist/{operations-6Z6ZDVBP.js.map → index-pass-TBDUEZCB.js.map} +0 -0
- /package/dist/{runtime-W5AW32SD.js.map → install-E3JWINTN.js.map} +0 -0
- /package/dist/{summaries-32PHOB6L.js.map → lean-MLTAFS5L.js.map} +0 -0
- /package/dist/{zvec-24HLAFGN.js.map → local-75DJECVR.js.map} +0 -0
|
@@ -11,1009 +11,1010 @@ import {
|
|
|
11
11
|
__export
|
|
12
12
|
} from "./chunk-PZ5AY32C.js";
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
14
|
+
// src/db/schema.ts
|
|
15
|
+
var schema_exports = {};
|
|
16
|
+
__export(schema_exports, {
|
|
17
|
+
HNSW_EF_CONSTRUCTION: () => HNSW_EF_CONSTRUCTION,
|
|
18
|
+
HNSW_M: () => HNSW_M,
|
|
19
|
+
VECTOR_DIMENSIONS: () => VECTOR_DIMENSIONS,
|
|
20
|
+
auditActionEnum: () => auditActionEnum,
|
|
21
|
+
auditLog: () => auditLog,
|
|
22
|
+
checkpoints: () => checkpoints,
|
|
23
|
+
embeddingOwnerTypeEnum: () => embeddingOwnerTypeEnum,
|
|
24
|
+
embeddingStatusEnum: () => embeddingStatusEnum,
|
|
25
|
+
embeddings: () => embeddings,
|
|
26
|
+
facts: () => facts,
|
|
27
|
+
interactionRoleEnum: () => interactionRoleEnum,
|
|
28
|
+
interactions: () => interactions,
|
|
29
|
+
memoryTypeEnum: () => memoryTypeEnum,
|
|
30
|
+
modelCallStatusEnum: () => modelCallStatusEnum,
|
|
31
|
+
modelCallTypeEnum: () => modelCallTypeEnum,
|
|
32
|
+
modelCalls: () => modelCalls,
|
|
33
|
+
namespaces: () => namespaces,
|
|
34
|
+
sessionSummaries: () => sessionSummaries,
|
|
35
|
+
shareModeEnum: () => shareModeEnum,
|
|
36
|
+
shares: () => shares
|
|
37
|
+
});
|
|
38
|
+
import { sql } from "drizzle-orm";
|
|
39
|
+
import {
|
|
40
|
+
boolean,
|
|
41
|
+
customType,
|
|
42
|
+
index,
|
|
43
|
+
integer,
|
|
44
|
+
jsonb,
|
|
45
|
+
numeric,
|
|
46
|
+
pgEnum,
|
|
47
|
+
pgTable,
|
|
48
|
+
text,
|
|
49
|
+
timestamp,
|
|
50
|
+
uniqueIndex
|
|
51
|
+
} from "drizzle-orm/pg-core";
|
|
52
|
+
var VECTOR_DIMENSIONS = 384;
|
|
53
|
+
var HNSW_M = 16;
|
|
54
|
+
var HNSW_EF_CONSTRUCTION = 64;
|
|
55
|
+
var vector = customType({
|
|
56
|
+
dataType() {
|
|
57
|
+
return `vector(${VECTOR_DIMENSIONS})`;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
var memoryTypeEnum = pgEnum("memory_type", ["interaction", "fact", "summary"]);
|
|
61
|
+
var interactionRoleEnum = pgEnum("interaction_role", ["user", "assistant", "system"]);
|
|
62
|
+
var embeddingOwnerTypeEnum = pgEnum("embedding_owner_type", [
|
|
63
|
+
"interaction",
|
|
64
|
+
"fact",
|
|
65
|
+
"summary"
|
|
66
|
+
]);
|
|
67
|
+
var embeddingStatusEnum = pgEnum("embedding_status", [
|
|
68
|
+
"pending",
|
|
69
|
+
"processing",
|
|
70
|
+
"ready",
|
|
71
|
+
"failed"
|
|
72
|
+
]);
|
|
73
|
+
var shareModeEnum = pgEnum("share_mode", ["reference", "snapshot"]);
|
|
74
|
+
var modelCallTypeEnum = pgEnum("model_call_type", ["embedding", "llm"]);
|
|
75
|
+
var modelCallStatusEnum = pgEnum("model_call_status", [
|
|
76
|
+
"success",
|
|
77
|
+
"failed",
|
|
78
|
+
"timeout",
|
|
79
|
+
"rate_limited",
|
|
80
|
+
"gated",
|
|
81
|
+
"fallback"
|
|
82
|
+
]);
|
|
83
|
+
var auditActionEnum = pgEnum("audit_action", [
|
|
84
|
+
"remember",
|
|
85
|
+
"remember_batch",
|
|
86
|
+
"share",
|
|
87
|
+
"forget",
|
|
88
|
+
"config"
|
|
89
|
+
]);
|
|
90
|
+
var createdAt = timestamp("created_at", { withTimezone: true }).notNull().defaultNow();
|
|
91
|
+
var updatedAt = timestamp("updated_at", { withTimezone: true }).notNull().defaultNow();
|
|
92
|
+
var namespaces = pgTable(
|
|
93
|
+
"namespaces",
|
|
94
|
+
{
|
|
95
|
+
id: text("id").primaryKey(),
|
|
96
|
+
name: text("name").notNull(),
|
|
97
|
+
createdAt,
|
|
98
|
+
updatedAt
|
|
28
99
|
},
|
|
29
|
-
|
|
30
|
-
|
|
100
|
+
(table) => ({
|
|
101
|
+
nameUnique: uniqueIndex("namespaces_name_unique").on(table.name)
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
var interactions = pgTable(
|
|
105
|
+
"interactions",
|
|
106
|
+
{
|
|
107
|
+
id: text("id").primaryKey(),
|
|
108
|
+
namespaceId: text("namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
109
|
+
sessionId: text("session_id").notNull(),
|
|
110
|
+
role: interactionRoleEnum("role").notNull().default("user"),
|
|
111
|
+
content: text("content").notNull(),
|
|
112
|
+
contentHash: text("content_hash").notNull(),
|
|
113
|
+
tokenCount: integer("token_count").notNull().default(0),
|
|
114
|
+
createdByAgent: text("created_by_agent"),
|
|
115
|
+
createdAt
|
|
31
116
|
},
|
|
32
|
-
|
|
33
|
-
|
|
117
|
+
(table) => ({
|
|
118
|
+
namespaceContentHashUnique: uniqueIndex("interactions_namespace_content_hash_unique").on(
|
|
119
|
+
table.namespaceId,
|
|
120
|
+
table.contentHash
|
|
121
|
+
),
|
|
122
|
+
namespaceSessionCreatedIdx: index("interactions_namespace_session_created_idx").on(
|
|
123
|
+
table.namespaceId,
|
|
124
|
+
table.sessionId,
|
|
125
|
+
table.createdAt
|
|
126
|
+
)
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
var sessionSummaries = pgTable(
|
|
130
|
+
"session_summaries",
|
|
131
|
+
{
|
|
132
|
+
id: text("id").primaryKey(),
|
|
133
|
+
namespaceId: text("namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
134
|
+
sessionId: text("session_id").notNull(),
|
|
135
|
+
content: text("content").notNull(),
|
|
136
|
+
version: integer("version").notNull().default(1),
|
|
137
|
+
tokenCount: integer("token_count").notNull().default(0),
|
|
138
|
+
createdByAgent: text("created_by_agent"),
|
|
139
|
+
/** See facts.fileBacked. */
|
|
140
|
+
fileBacked: boolean("file_backed").notNull().default(false),
|
|
141
|
+
createdAt
|
|
34
142
|
},
|
|
35
|
-
|
|
36
|
-
".
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
143
|
+
(table) => ({
|
|
144
|
+
namespaceSessionVersionIdx: index("session_summaries_namespace_session_version_idx").on(
|
|
145
|
+
table.namespaceId,
|
|
146
|
+
table.sessionId,
|
|
147
|
+
table.version
|
|
148
|
+
)
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
var facts = pgTable(
|
|
152
|
+
"facts",
|
|
153
|
+
{
|
|
154
|
+
id: text("id").primaryKey(),
|
|
155
|
+
namespaceId: text("namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
156
|
+
subject: text("subject").notNull(),
|
|
157
|
+
predicate: text("predicate").notNull(),
|
|
158
|
+
object: text("object").notNull(),
|
|
159
|
+
propositionKey: text("proposition_key"),
|
|
160
|
+
/** Kept apart from content_hash so a reindex (which recomputes the hash) keeps it. */
|
|
161
|
+
idempotencyKey: text("idempotency_key"),
|
|
162
|
+
content: text("content").notNull(),
|
|
163
|
+
contentHash: text("content_hash").notNull(),
|
|
164
|
+
confidence: numeric("confidence", { precision: 5, scale: 4 }).notNull().default("1"),
|
|
165
|
+
/**
|
|
166
|
+
* Historical provenance only — deliberately not a foreign key. Interactions
|
|
167
|
+
* are ephemeral DB rows; Markdown facts must re-import after a rebuild that
|
|
168
|
+
* lost them, and the ID must survive so `sourceDeleted` can be tracked.
|
|
169
|
+
*/
|
|
170
|
+
sourceInteractionId: text("source_interaction_id"),
|
|
171
|
+
sourceDeleted: boolean("source_deleted").notNull().default(false),
|
|
172
|
+
supersededBy: text("superseded_by"),
|
|
173
|
+
/**
|
|
174
|
+
* True once a reindex imported this row from its Markdown file. Only such
|
|
175
|
+
* rows are deleted when the file disappears; DB-only legacy rows are kept.
|
|
176
|
+
*/
|
|
177
|
+
fileBacked: boolean("file_backed").notNull().default(false),
|
|
178
|
+
embeddingModel: text("embedding_model"),
|
|
179
|
+
createdByAgent: text("created_by_agent"),
|
|
180
|
+
createdAt,
|
|
181
|
+
updatedAt
|
|
44
182
|
},
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
183
|
+
(table) => ({
|
|
184
|
+
namespaceContentHashUnique: uniqueIndex("facts_namespace_content_hash_unique").on(
|
|
185
|
+
table.namespaceId,
|
|
186
|
+
table.contentHash
|
|
187
|
+
),
|
|
188
|
+
namespaceSubjectIdx: index("facts_namespace_subject_idx").on(table.namespaceId, table.subject),
|
|
189
|
+
namespaceIdempotencyIdx: index("facts_namespace_idempotency_idx").on(
|
|
190
|
+
table.namespaceId,
|
|
191
|
+
table.idempotencyKey
|
|
192
|
+
),
|
|
193
|
+
sourceInteractionIdx: index("facts_source_interaction_idx").on(table.sourceInteractionId)
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
var embeddings = pgTable(
|
|
197
|
+
"embeddings",
|
|
198
|
+
{
|
|
199
|
+
id: text("id").primaryKey(),
|
|
200
|
+
namespaceId: text("namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
201
|
+
ownerType: embeddingOwnerTypeEnum("owner_type").notNull(),
|
|
202
|
+
ownerId: text("owner_id").notNull(),
|
|
203
|
+
vector: vector("vector").notNull(),
|
|
204
|
+
embeddingModel: text("embedding_model").notNull(),
|
|
205
|
+
embeddingSpace: text("embedding_space"),
|
|
206
|
+
status: embeddingStatusEnum("status").notNull().default("pending"),
|
|
207
|
+
attempts: integer("attempts").notNull().default(0),
|
|
208
|
+
lastError: text("last_error"),
|
|
209
|
+
/** Lease deadline while status='processing'; null otherwise. */
|
|
210
|
+
leaseUntil: timestamp("lease_until", { withTimezone: true }),
|
|
211
|
+
leaseToken: text("lease_token"),
|
|
212
|
+
createdAt,
|
|
213
|
+
updatedAt
|
|
73
214
|
},
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"js-tiktoken": "^1.0.21",
|
|
105
|
-
"p-queue": "^10.0.0",
|
|
106
|
-
"p-retry": "^8.0.1",
|
|
107
|
-
"p-timeout": "^8.1.0",
|
|
108
|
-
picocolors: "^1.1.1",
|
|
109
|
-
pino: "^10.4.0",
|
|
110
|
-
postgres: "^3.4.7",
|
|
111
|
-
yaml: "^2.9.0",
|
|
112
|
-
zod: "^4.6.7"
|
|
215
|
+
(table) => ({
|
|
216
|
+
ownerUnique: uniqueIndex("embeddings_owner_unique").on(table.ownerType, table.ownerId),
|
|
217
|
+
namespaceModelStatusIdx: index("embeddings_namespace_model_status_idx").on(
|
|
218
|
+
table.namespaceId,
|
|
219
|
+
table.embeddingModel,
|
|
220
|
+
table.status
|
|
221
|
+
),
|
|
222
|
+
namespaceSpaceStatusIdx: index("embeddings_namespace_space_status_idx").on(
|
|
223
|
+
table.namespaceId,
|
|
224
|
+
table.embeddingSpace,
|
|
225
|
+
table.status
|
|
226
|
+
),
|
|
227
|
+
vectorHnswIdx: index("embeddings_vector_hnsw_idx").using(
|
|
228
|
+
"hnsw",
|
|
229
|
+
sql`${table.vector} vector_cosine_ops`
|
|
230
|
+
)
|
|
231
|
+
})
|
|
232
|
+
);
|
|
233
|
+
var shares = pgTable(
|
|
234
|
+
"shares",
|
|
235
|
+
{
|
|
236
|
+
id: text("id").primaryKey(),
|
|
237
|
+
fromNamespaceId: text("from_namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
238
|
+
toNamespaceId: text("to_namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
239
|
+
sourceFactId: text("source_fact_id").notNull().references(() => facts.id, { onDelete: "cascade" }),
|
|
240
|
+
snapshotFactId: text("snapshot_fact_id").references(() => facts.id, { onDelete: "set null" }),
|
|
241
|
+
mode: shareModeEnum("mode").notNull().default("reference"),
|
|
242
|
+
tombstoned: boolean("tombstoned").notNull().default(false),
|
|
243
|
+
createdByAgent: text("created_by_agent"),
|
|
244
|
+
createdAt
|
|
113
245
|
},
|
|
114
|
-
|
|
115
|
-
|
|
246
|
+
(table) => ({
|
|
247
|
+
shareUnique: uniqueIndex("shares_target_source_unique").on(
|
|
248
|
+
table.toNamespaceId,
|
|
249
|
+
table.sourceFactId,
|
|
250
|
+
table.mode
|
|
251
|
+
),
|
|
252
|
+
fromNamespaceIdx: index("shares_from_namespace_idx").on(table.fromNamespaceId)
|
|
253
|
+
})
|
|
254
|
+
);
|
|
255
|
+
var checkpoints = pgTable(
|
|
256
|
+
"checkpoints",
|
|
257
|
+
{
|
|
258
|
+
id: text("id").primaryKey(),
|
|
259
|
+
namespaceId: text("namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
260
|
+
threadId: text("thread_id").notNull(),
|
|
261
|
+
checkpointId: text("checkpoint_id").notNull(),
|
|
262
|
+
parentId: text("parent_id"),
|
|
263
|
+
channelValues: jsonb("channel_values").notNull().default(sql`'{}'::jsonb`),
|
|
264
|
+
metadata: jsonb("metadata").notNull().default(sql`'{}'::jsonb`),
|
|
265
|
+
createdAt
|
|
116
266
|
},
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
267
|
+
(table) => ({
|
|
268
|
+
checkpointUnique: uniqueIndex("checkpoints_thread_checkpoint_unique").on(
|
|
269
|
+
table.namespaceId,
|
|
270
|
+
table.threadId,
|
|
271
|
+
table.checkpointId
|
|
272
|
+
)
|
|
273
|
+
})
|
|
274
|
+
);
|
|
275
|
+
var modelCalls = pgTable(
|
|
276
|
+
"model_calls",
|
|
277
|
+
{
|
|
278
|
+
id: text("id").primaryKey(),
|
|
279
|
+
namespaceId: text("namespace_id").references(() => namespaces.id, { onDelete: "set null" }),
|
|
280
|
+
model: text("model").notNull(),
|
|
281
|
+
type: modelCallTypeEnum("type").notNull(),
|
|
282
|
+
latencyMs: integer("latency_ms"),
|
|
283
|
+
tokens: integer("tokens"),
|
|
284
|
+
status: modelCallStatusEnum("status").notNull(),
|
|
285
|
+
error: text("error"),
|
|
286
|
+
metadata: jsonb("metadata").notNull().default(sql`'{}'::jsonb`),
|
|
287
|
+
createdAt
|
|
121
288
|
},
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
289
|
+
(table) => ({
|
|
290
|
+
modelCreatedIdx: index("model_calls_model_created_idx").on(table.model, table.createdAt),
|
|
291
|
+
namespaceCreatedIdx: index("model_calls_namespace_created_idx").on(
|
|
292
|
+
table.namespaceId,
|
|
293
|
+
table.createdAt
|
|
294
|
+
)
|
|
295
|
+
})
|
|
296
|
+
);
|
|
297
|
+
var auditLog = pgTable(
|
|
298
|
+
"audit_log",
|
|
299
|
+
{
|
|
300
|
+
id: text("id").primaryKey(),
|
|
301
|
+
namespaceId: text("namespace_id").references(() => namespaces.id, { onDelete: "set null" }),
|
|
302
|
+
action: auditActionEnum("action").notNull(),
|
|
303
|
+
entityType: text("entity_type").notNull(),
|
|
304
|
+
entityId: text("entity_id"),
|
|
305
|
+
createdByAgent: text("created_by_agent"),
|
|
306
|
+
metadata: jsonb("metadata").notNull().default(sql`'{}'::jsonb`),
|
|
307
|
+
createdAt
|
|
130
308
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// src/core/memory-file-store.ts
|
|
140
|
-
import { mkdir, readdir, readFile, rm, stat } from "fs/promises";
|
|
141
|
-
import { dirname, join, relative, resolve, sep } from "path";
|
|
142
|
-
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
143
|
-
import { z } from "zod";
|
|
144
|
-
|
|
145
|
-
// src/core/memory-lifecycle.ts
|
|
146
|
-
function isEligibleMemory(fact, options = {}) {
|
|
147
|
-
if (options.namespace && fact.namespace !== options.namespace) return false;
|
|
148
|
-
if (fact.sourceDeleted || fact.supersededBy) return false;
|
|
149
|
-
if (fact.category === "incident" && fact.resolvedBy && !options.includeResolved) return false;
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
309
|
+
(table) => ({
|
|
310
|
+
namespaceActionCreatedIdx: index("audit_log_namespace_action_created_idx").on(
|
|
311
|
+
table.namespaceId,
|
|
312
|
+
table.action,
|
|
313
|
+
table.createdAt
|
|
314
|
+
)
|
|
315
|
+
})
|
|
316
|
+
);
|
|
152
317
|
|
|
153
|
-
// src/core/memory-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
schemaVersion: z.literal(1),
|
|
158
|
-
id: z.string().regex(/^fact_[a-z0-9]+$/),
|
|
159
|
-
namespace: z.string().min(1),
|
|
160
|
-
type: z.literal("fact"),
|
|
161
|
-
subject: z.string(),
|
|
162
|
-
predicate: z.string(),
|
|
163
|
-
object: z.string(),
|
|
164
|
-
/** Explicit single-valued claim identity; facts sharing it are rival assertions. */
|
|
165
|
-
propositionKey: z.string().min(1).nullable().default(null),
|
|
166
|
-
/** The caller's `remember` idempotency key, so a retry after reindex dedupes. */
|
|
167
|
-
idempotencyKey: z.string().min(1).max(200).nullable().default(null),
|
|
168
|
-
confidence: z.number().min(0).max(1),
|
|
169
|
-
createdAt: z.preprocess(
|
|
170
|
-
(value) => value instanceof Date ? value.toISOString() : value,
|
|
171
|
-
z.string().datetime()
|
|
172
|
-
),
|
|
173
|
-
createdByAgent: z.string().nullable(),
|
|
174
|
-
sourceInteractionId: z.string().nullable(),
|
|
175
|
-
sourceDeleted: z.boolean(),
|
|
176
|
-
category: z.enum(FACT_CATEGORIES).default("general"),
|
|
177
|
-
paths: z.array(z.string().min(1)).default([]),
|
|
178
|
-
severity: z.enum(INCIDENT_SEVERITIES).nullable().default(null),
|
|
179
|
-
introducedBy: z.string().nullable().default(null),
|
|
180
|
-
resolvedBy: z.string().nullable().default(null),
|
|
181
|
-
fingerprint: z.string().regex(/^[a-f0-9]{64}$/).nullable().default(null),
|
|
182
|
-
sourcePlanId: z.string().nullable().default(null),
|
|
183
|
-
sourceSessionId: z.string().nullable().default(null),
|
|
184
|
-
verification: z.array(z.string()).default([]),
|
|
185
|
-
supersededBy: z.string().nullable().default(null),
|
|
186
|
-
/** Namespaces granted a live reference to this fact (durable form of reference shares). */
|
|
187
|
-
sharedWith: z.array(z.string().min(1)).default([])
|
|
188
|
-
});
|
|
189
|
-
var summaryFrontMatterSchema = z.object({
|
|
190
|
-
schemaVersion: z.literal(1),
|
|
191
|
-
id: z.string().regex(/^sum_[a-z0-9]+$/),
|
|
192
|
-
namespace: z.string().min(1),
|
|
193
|
-
type: z.literal("summary"),
|
|
194
|
-
sessionId: z.string().min(1),
|
|
195
|
-
version: z.number().int().positive(),
|
|
196
|
-
tokenCount: z.number().int().nonnegative(),
|
|
197
|
-
createdAt: z.preprocess(
|
|
198
|
-
(value) => value instanceof Date ? value.toISOString() : value,
|
|
199
|
-
z.string().datetime()
|
|
200
|
-
),
|
|
201
|
-
createdByAgent: z.string().nullable()
|
|
202
|
-
});
|
|
203
|
-
var SCAN_BATCH = 64;
|
|
204
|
-
var parsedFiles = /* @__PURE__ */ new Map();
|
|
205
|
-
var loadedCaches = /* @__PURE__ */ new Set();
|
|
206
|
-
function parseCachePath(root, directory) {
|
|
207
|
-
return join(
|
|
208
|
-
root,
|
|
209
|
-
".codebuddy",
|
|
210
|
-
"cache",
|
|
211
|
-
`parsed-${directory.split(sep).at(-1) ?? "memory"}.json`
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
async function loadParseCache(root, directory) {
|
|
215
|
-
if (loadedCaches.has(directory)) return;
|
|
216
|
-
loadedCaches.add(directory);
|
|
217
|
-
try {
|
|
218
|
-
const raw = JSON.parse(await readFile(parseCachePath(root, directory), "utf8"));
|
|
219
|
-
if (raw.version !== VERSION) return;
|
|
220
|
-
for (const [name, entry] of Object.entries(raw.files)) {
|
|
221
|
-
const path = join(directory, name);
|
|
222
|
-
if (!parsedFiles.has(path)) parsedFiles.set(path, entry);
|
|
223
|
-
}
|
|
224
|
-
} catch {
|
|
225
|
-
}
|
|
318
|
+
// src/core/memory-search.ts
|
|
319
|
+
function candidateLimit(limit) {
|
|
320
|
+
if (!Number.isFinite(limit)) return 256;
|
|
321
|
+
return Math.max(1, Math.min(1024, Math.trunc(limit)));
|
|
226
322
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
for (const [path, entry] of parsedFiles) {
|
|
230
|
-
if (dirname(path) === directory) files[relative(directory, path)] = entry;
|
|
231
|
-
}
|
|
232
|
-
try {
|
|
233
|
-
const path = parseCachePath(root, directory);
|
|
234
|
-
await mkdir(dirname(path), { recursive: true });
|
|
235
|
-
await writeAtomic(path, JSON.stringify({ version: VERSION, files }));
|
|
236
|
-
} catch {
|
|
237
|
-
}
|
|
323
|
+
function validSearchVector(vector2) {
|
|
324
|
+
return vector2.length === VECTOR_DIMENSIONS && vector2.every((value) => Number.isFinite(Math.fround(value))) && vector2.some((value) => value !== 0);
|
|
238
325
|
}
|
|
239
|
-
var
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
...input.includeResolved !== void 0 ? { includeResolved: input.includeResolved } : {}
|
|
356
|
-
})).filter((fact) => fact.paths.map(normalizeMemoryPath).some((path) => wanted.has(path)));
|
|
357
|
-
}
|
|
358
|
-
async readSummary(pathOrId) {
|
|
359
|
-
const path = pathOrId.endsWith(".md") ? this.safePath(pathOrId) : this.summaryPath(pathOrId);
|
|
360
|
-
await assertNoSymlinkBetween(this.repositoryRoot, path);
|
|
361
|
-
const parsed = parseFrontMatter(await readFile(path, "utf8"));
|
|
362
|
-
return {
|
|
363
|
-
...summaryFrontMatterSchema.parse(parseYaml(parsed.frontMatter)),
|
|
364
|
-
content: parsed.content.trim(),
|
|
365
|
-
path
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
async listSummaries() {
|
|
369
|
-
return (await this.scanSummaries()).summaries;
|
|
370
|
-
}
|
|
371
|
-
async scanSummaries() {
|
|
372
|
-
const scan = await this.scan(
|
|
373
|
-
this.summariesDirectory,
|
|
374
|
-
/^sum_[a-z0-9]+\.md$/,
|
|
375
|
-
(path) => this.readSummary(path)
|
|
376
|
-
);
|
|
377
|
-
return { summaries: scan.items, invalid: scan.invalid };
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Parse every managed file in `directory`. Hot path: the prompt hook and
|
|
381
|
-
* capture scan facts several times per process, and the MCP server for its
|
|
382
|
-
* lifetime, so each parsed file is cached by path, mtime and size (a changed
|
|
383
|
-
* file is re-read, from any process) and files are read in parallel batches.
|
|
384
|
-
* The directory chain is checked for symlinks once; a `withFileTypes` entry
|
|
385
|
-
* that is a symlink is never `isFile()`, so no file escapes through one.
|
|
386
|
-
*/
|
|
387
|
-
async scan(directory, managedName, read) {
|
|
388
|
-
try {
|
|
389
|
-
await this.prepareDirectory(directory);
|
|
390
|
-
await assertNoSymlinkBetween(this.repositoryRoot, directory);
|
|
391
|
-
await loadParseCache(this.repositoryRoot, directory);
|
|
392
|
-
let reparsed = false;
|
|
393
|
-
const entries = (await readdir(directory, { withFileTypes: true })).filter((entry) => entry.isFile() && managedName.test(entry.name)).sort((left, right) => left.name.localeCompare(right.name));
|
|
394
|
-
const live = new Set(entries.map((entry) => join(directory, entry.name)));
|
|
395
|
-
for (const path of [...parsedFiles.keys()]) {
|
|
396
|
-
if (dirname(path) === directory && !live.has(path)) {
|
|
397
|
-
parsedFiles.delete(path);
|
|
398
|
-
reparsed = true;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
const outcomes = [];
|
|
402
|
-
for (let start = 0; start < entries.length; start += SCAN_BATCH) {
|
|
403
|
-
const batch = entries.slice(start, start + SCAN_BATCH);
|
|
404
|
-
outcomes.push(
|
|
405
|
-
...await Promise.all(
|
|
406
|
-
batch.map(async (entry) => {
|
|
407
|
-
const path = join(directory, entry.name);
|
|
408
|
-
const id = entry.name.slice(0, -".md".length);
|
|
409
|
-
try {
|
|
410
|
-
const info = await stat(path);
|
|
411
|
-
const cached = parsedFiles.get(path);
|
|
412
|
-
const fresh = cached !== void 0 && cached.mtimeMs === info.mtimeMs && cached.size === info.size;
|
|
413
|
-
const item = fresh ? cached.item : await read(path, true);
|
|
414
|
-
if (!fresh) reparsed = true;
|
|
415
|
-
if (item.id !== id) {
|
|
416
|
-
throw new Error(`file ID ${id} differs from front matter ID ${item.id}`);
|
|
417
|
-
}
|
|
418
|
-
parsedFiles.set(path, { mtimeMs: info.mtimeMs, size: info.size, item });
|
|
419
|
-
return { item: { ...item } };
|
|
420
|
-
} catch (error) {
|
|
421
|
-
parsedFiles.delete(path);
|
|
422
|
-
return {
|
|
423
|
-
invalid: {
|
|
424
|
-
id,
|
|
425
|
-
path,
|
|
426
|
-
error: error.message.split("\n")[0]?.slice(0, 300) ?? "unreadable"
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
})
|
|
431
|
-
)
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
if (reparsed) await saveParseCache(this.repositoryRoot, directory);
|
|
435
|
-
const result = { items: [], invalid: [] };
|
|
436
|
-
for (const outcome of outcomes) {
|
|
437
|
-
if ("item" in outcome) result.items.push(outcome.item);
|
|
438
|
-
else result.invalid.push(outcome.invalid);
|
|
439
|
-
}
|
|
440
|
-
return result;
|
|
441
|
-
} catch (error) {
|
|
442
|
-
if (error.code === "ENOENT") return { items: [], invalid: [] };
|
|
443
|
-
throw error;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
async deleteFact(id) {
|
|
447
|
-
await rm(this.factPath(id), { force: true });
|
|
448
|
-
}
|
|
449
|
-
async markIncidentResolved(id, resolvedBy) {
|
|
450
|
-
const fact = await this.readFact(id);
|
|
451
|
-
await this.writeFact(copyFact(fact, { resolvedBy }));
|
|
452
|
-
}
|
|
453
|
-
async markFactSuperseded(id, supersededBy) {
|
|
454
|
-
const fact = await this.readFact(id);
|
|
455
|
-
await this.writeFact(copyFact(fact, { supersededBy }));
|
|
456
|
-
}
|
|
457
|
-
async setFactSharedWith(id, sharedWith) {
|
|
458
|
-
const fact = await this.readFact(id);
|
|
459
|
-
await this.writeFact(copyFact(fact, { sharedWith: [...new Set(sharedWith)].sort() }));
|
|
460
|
-
}
|
|
461
|
-
/** Persist interaction deletion in Markdown before the derived DB is updated. */
|
|
462
|
-
async markSourceInteractionDeleted(namespace, sourceInteractionId) {
|
|
463
|
-
const { facts: facts2 } = await this.scanFacts();
|
|
464
|
-
for (const fact of facts2) {
|
|
465
|
-
if (fact.namespace !== namespace || fact.sourceInteractionId !== sourceInteractionId)
|
|
466
|
-
continue;
|
|
467
|
-
if (fact.sourceDeleted) continue;
|
|
468
|
-
await this.writeFact(copyFact(fact, { sourceDeleted: true }));
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
/** Persist that a fact's source interaction was forgotten, keeping its provenance id. */
|
|
472
|
-
async markFactSourceDeleted(id) {
|
|
473
|
-
const fact = await this.readFact(id);
|
|
474
|
-
await this.writeFact(copyFact(fact, { sourceDeleted: true }));
|
|
475
|
-
}
|
|
476
|
-
factPath(id) {
|
|
477
|
-
if (!/^fact_[a-z0-9]+$/.test(id)) {
|
|
478
|
-
throw new Error(`Invalid fact id: ${id}`);
|
|
479
|
-
}
|
|
480
|
-
return this.safePath(join(this.factsDirectory, `${id}.md`));
|
|
481
|
-
}
|
|
482
|
-
summaryPath(id) {
|
|
483
|
-
if (!/^sum_[a-z0-9]+$/.test(id)) throw new Error(`Invalid summary id: ${id}`);
|
|
484
|
-
return this.safePath(join(this.summariesDirectory, `${id}.md`));
|
|
485
|
-
}
|
|
486
|
-
safePath(path) {
|
|
487
|
-
const absolute = resolve(path);
|
|
488
|
-
const relativePath = relative(this.repositoryRoot, absolute);
|
|
489
|
-
if (relativePath === ".." || relativePath.startsWith(`..${sep}`) || relativePath === "") {
|
|
490
|
-
throw new Error(`Memory path escapes repository root: ${path}`);
|
|
491
|
-
}
|
|
492
|
-
return absolute;
|
|
493
|
-
}
|
|
494
|
-
async prepareDirectory(directory) {
|
|
495
|
-
await assertNoSymlinkBetween(this.repositoryRoot, dirname(directory));
|
|
496
|
-
await mkdir(directory, { recursive: true, mode: 448 });
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
function copyFact(fact, patch) {
|
|
500
|
-
const { path: _path, schemaVersion: _schemaVersion, type: _type, ...write } = fact;
|
|
501
|
-
return { ...write, ...patch };
|
|
326
|
+
var QUERY_STOPWORDS = /* @__PURE__ */ new Set([
|
|
327
|
+
"a",
|
|
328
|
+
"about",
|
|
329
|
+
"after",
|
|
330
|
+
"again",
|
|
331
|
+
"all",
|
|
332
|
+
"also",
|
|
333
|
+
"always",
|
|
334
|
+
"an",
|
|
335
|
+
"and",
|
|
336
|
+
"any",
|
|
337
|
+
"are",
|
|
338
|
+
"as",
|
|
339
|
+
"at",
|
|
340
|
+
"back",
|
|
341
|
+
"be",
|
|
342
|
+
"been",
|
|
343
|
+
"before",
|
|
344
|
+
"being",
|
|
345
|
+
"but",
|
|
346
|
+
"by",
|
|
347
|
+
"can",
|
|
348
|
+
"could",
|
|
349
|
+
"did",
|
|
350
|
+
"do",
|
|
351
|
+
"does",
|
|
352
|
+
"done",
|
|
353
|
+
"each",
|
|
354
|
+
"even",
|
|
355
|
+
"ever",
|
|
356
|
+
"for",
|
|
357
|
+
"from",
|
|
358
|
+
"get",
|
|
359
|
+
"gets",
|
|
360
|
+
"got",
|
|
361
|
+
"had",
|
|
362
|
+
"has",
|
|
363
|
+
"have",
|
|
364
|
+
"here",
|
|
365
|
+
"how",
|
|
366
|
+
"i",
|
|
367
|
+
"if",
|
|
368
|
+
"in",
|
|
369
|
+
"into",
|
|
370
|
+
"is",
|
|
371
|
+
"it",
|
|
372
|
+
"its",
|
|
373
|
+
"just",
|
|
374
|
+
"like",
|
|
375
|
+
"make",
|
|
376
|
+
"me",
|
|
377
|
+
"more",
|
|
378
|
+
"most",
|
|
379
|
+
"my",
|
|
380
|
+
"never",
|
|
381
|
+
"no",
|
|
382
|
+
"not",
|
|
383
|
+
"now",
|
|
384
|
+
"of",
|
|
385
|
+
"on",
|
|
386
|
+
"only",
|
|
387
|
+
"or",
|
|
388
|
+
"other",
|
|
389
|
+
"our",
|
|
390
|
+
"out",
|
|
391
|
+
"over",
|
|
392
|
+
"please",
|
|
393
|
+
"really",
|
|
394
|
+
"same",
|
|
395
|
+
"should",
|
|
396
|
+
"so",
|
|
397
|
+
"some",
|
|
398
|
+
"sometimes",
|
|
399
|
+
"still",
|
|
400
|
+
"such",
|
|
401
|
+
"than",
|
|
402
|
+
"that",
|
|
403
|
+
"the",
|
|
404
|
+
"their",
|
|
405
|
+
"them",
|
|
406
|
+
"then",
|
|
407
|
+
"there",
|
|
408
|
+
"these",
|
|
409
|
+
"they",
|
|
410
|
+
"this",
|
|
411
|
+
"those",
|
|
412
|
+
"through",
|
|
413
|
+
"to",
|
|
414
|
+
"too",
|
|
415
|
+
"under",
|
|
416
|
+
"up",
|
|
417
|
+
"us",
|
|
418
|
+
"very",
|
|
419
|
+
"was",
|
|
420
|
+
"we",
|
|
421
|
+
"were",
|
|
422
|
+
"what",
|
|
423
|
+
"when",
|
|
424
|
+
"where",
|
|
425
|
+
"which",
|
|
426
|
+
"while",
|
|
427
|
+
"who",
|
|
428
|
+
"why",
|
|
429
|
+
"will",
|
|
430
|
+
"with",
|
|
431
|
+
"would",
|
|
432
|
+
"yet",
|
|
433
|
+
"you",
|
|
434
|
+
"your"
|
|
435
|
+
]);
|
|
436
|
+
function lexicalTerms(query) {
|
|
437
|
+
return [
|
|
438
|
+
...new Set(
|
|
439
|
+
query.slice(0, 4096).toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []
|
|
440
|
+
)
|
|
441
|
+
].filter((term) => !QUERY_STOPWORDS.has(term)).slice(0, 32).map((term) => term.slice(0, 64));
|
|
502
442
|
}
|
|
503
|
-
|
|
504
|
-
return
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return false;
|
|
514
|
-
}
|
|
515
|
-
return true;
|
|
516
|
-
})
|
|
517
|
-
);
|
|
443
|
+
var GENERIC_TERMS = new Set(
|
|
444
|
+
"add added adding bug build call change changed changes check code config configure configuration create data default delete error errors feature file files fix fixed handle issue line load logic new option options problem project read remove result return run running save set setting settings show step support test tests thing time update updated use used value values version want way work works write".split(" ")
|
|
445
|
+
);
|
|
446
|
+
var RARE_TERM_SHARE = 5e-3;
|
|
447
|
+
var RARE_TERM_MIN_DF = 2;
|
|
448
|
+
function admitsLexically(shared, documentFrequency, corpusSize) {
|
|
449
|
+
const specific = shared.filter((term) => !GENERIC_TERMS.has(term));
|
|
450
|
+
if (specific.length >= 2) return true;
|
|
451
|
+
const rare = Math.max(RARE_TERM_MIN_DF, Math.floor(RARE_TERM_SHARE * corpusSize));
|
|
452
|
+
return specific.some((term) => (documentFrequency.get(term) ?? 0) <= rare);
|
|
518
453
|
}
|
|
519
|
-
|
|
520
|
-
|
|
454
|
+
var CLAIM_BOILERPLATE = /* @__PURE__ */ new Set([
|
|
455
|
+
"decided",
|
|
456
|
+
"decide",
|
|
457
|
+
"decision",
|
|
458
|
+
"made",
|
|
459
|
+
"use",
|
|
460
|
+
"uses",
|
|
461
|
+
"used",
|
|
462
|
+
"using",
|
|
463
|
+
"now",
|
|
464
|
+
"going",
|
|
465
|
+
"will"
|
|
466
|
+
]);
|
|
467
|
+
function termOverlap(a, b) {
|
|
468
|
+
const terms = (text2) => new Set(
|
|
469
|
+
(text2.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []).filter(
|
|
470
|
+
(term) => !QUERY_STOPWORDS.has(term) && !CLAIM_BOILERPLATE.has(term)
|
|
471
|
+
)
|
|
472
|
+
);
|
|
473
|
+
const left = terms(a);
|
|
474
|
+
const right = terms(b);
|
|
475
|
+
if (left.size === 0 || right.size === 0) return 0;
|
|
476
|
+
let shared = 0;
|
|
477
|
+
for (const term of left) if (right.has(term)) shared += 1;
|
|
478
|
+
return shared / (left.size + right.size - shared);
|
|
521
479
|
}
|
|
522
|
-
function
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
480
|
+
function memoryConceptPatterns(query) {
|
|
481
|
+
const text2 = query.slice(0, 4096).toLowerCase();
|
|
482
|
+
const patterns = [];
|
|
483
|
+
if (/\b(?:lines?|formatter|formatting)\b/.test(text2) && /\b(?:wide|width|length|long|characters?|columns?)\b/.test(text2)) {
|
|
484
|
+
patterns.push("line[_ -]?(length|width)|max[_ -]?line[_ -]?(length|width)|print[_ -]?width");
|
|
485
|
+
}
|
|
486
|
+
if (/\b(?:interpreter|runtime|language|python|node|ruby|php|java|jdk|dotnet|rust|golang)\b/.test(
|
|
487
|
+
text2
|
|
488
|
+
) && /\b(?:oldest|earliest|minimum|min|version|release|supports?)\b/.test(text2)) {
|
|
489
|
+
patterns.push(
|
|
490
|
+
"requires[-_ ]?(python|node(js)?|ruby|php|java|jdk|dotnet|rust|golang)|(python|node(js)?|ruby|php|java|jdk|dotnet|rust|golang|interpreter|runtime)[^.!?]{0,40}(>=|minimum|newer)|minimum[^.!?]{0,40}(python|node(js)?|ruby|php|java|jdk|dotnet|rust|golang|interpreter|runtime)"
|
|
491
|
+
);
|
|
532
492
|
}
|
|
493
|
+
return patterns;
|
|
533
494
|
}
|
|
495
|
+
var MEMORY_CONCEPT_SCORE = 0.5;
|
|
534
496
|
|
|
535
|
-
//
|
|
536
|
-
var
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
embeddingOwnerTypeEnum: () => embeddingOwnerTypeEnum,
|
|
545
|
-
embeddingStatusEnum: () => embeddingStatusEnum,
|
|
546
|
-
embeddings: () => embeddings,
|
|
547
|
-
facts: () => facts,
|
|
548
|
-
interactionRoleEnum: () => interactionRoleEnum,
|
|
549
|
-
interactions: () => interactions,
|
|
550
|
-
memoryTypeEnum: () => memoryTypeEnum,
|
|
551
|
-
modelCallStatusEnum: () => modelCallStatusEnum,
|
|
552
|
-
modelCallTypeEnum: () => modelCallTypeEnum,
|
|
553
|
-
modelCalls: () => modelCalls,
|
|
554
|
-
namespaces: () => namespaces,
|
|
555
|
-
sessionSummaries: () => sessionSummaries,
|
|
556
|
-
shareModeEnum: () => shareModeEnum,
|
|
557
|
-
shares: () => shares
|
|
558
|
-
});
|
|
559
|
-
import { sql } from "drizzle-orm";
|
|
560
|
-
import {
|
|
561
|
-
boolean,
|
|
562
|
-
customType,
|
|
563
|
-
index,
|
|
564
|
-
integer,
|
|
565
|
-
jsonb,
|
|
566
|
-
numeric,
|
|
567
|
-
pgEnum,
|
|
568
|
-
pgTable,
|
|
569
|
-
text,
|
|
570
|
-
timestamp,
|
|
571
|
-
uniqueIndex
|
|
572
|
-
} from "drizzle-orm/pg-core";
|
|
573
|
-
var VECTOR_DIMENSIONS = 384;
|
|
574
|
-
var HNSW_M = 16;
|
|
575
|
-
var HNSW_EF_CONSTRUCTION = 64;
|
|
576
|
-
var vector = customType({
|
|
577
|
-
dataType() {
|
|
578
|
-
return `vector(${VECTOR_DIMENSIONS})`;
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
var memoryTypeEnum = pgEnum("memory_type", ["interaction", "fact", "summary"]);
|
|
582
|
-
var interactionRoleEnum = pgEnum("interaction_role", ["user", "assistant", "system"]);
|
|
583
|
-
var embeddingOwnerTypeEnum = pgEnum("embedding_owner_type", [
|
|
584
|
-
"interaction",
|
|
585
|
-
"fact",
|
|
586
|
-
"summary"
|
|
587
|
-
]);
|
|
588
|
-
var embeddingStatusEnum = pgEnum("embedding_status", [
|
|
589
|
-
"pending",
|
|
590
|
-
"processing",
|
|
591
|
-
"ready",
|
|
592
|
-
"failed"
|
|
593
|
-
]);
|
|
594
|
-
var shareModeEnum = pgEnum("share_mode", ["reference", "snapshot"]);
|
|
595
|
-
var modelCallTypeEnum = pgEnum("model_call_type", ["embedding", "llm"]);
|
|
596
|
-
var modelCallStatusEnum = pgEnum("model_call_status", [
|
|
597
|
-
"success",
|
|
598
|
-
"failed",
|
|
599
|
-
"timeout",
|
|
600
|
-
"rate_limited",
|
|
601
|
-
"gated",
|
|
602
|
-
"fallback"
|
|
603
|
-
]);
|
|
604
|
-
var auditActionEnum = pgEnum("audit_action", [
|
|
605
|
-
"remember",
|
|
606
|
-
"remember_batch",
|
|
607
|
-
"share",
|
|
608
|
-
"forget",
|
|
609
|
-
"config"
|
|
610
|
-
]);
|
|
611
|
-
var createdAt = timestamp("created_at", { withTimezone: true }).notNull().defaultNow();
|
|
612
|
-
var updatedAt = timestamp("updated_at", { withTimezone: true }).notNull().defaultNow();
|
|
613
|
-
var namespaces = pgTable(
|
|
614
|
-
"namespaces",
|
|
615
|
-
{
|
|
616
|
-
id: text("id").primaryKey(),
|
|
617
|
-
name: text("name").notNull(),
|
|
618
|
-
createdAt,
|
|
619
|
-
updatedAt
|
|
497
|
+
// package.json
|
|
498
|
+
var package_default = {
|
|
499
|
+
name: "@orquentra/codebuddy",
|
|
500
|
+
version: "2.15.0",
|
|
501
|
+
description: "CodeBuddy by Orquentra: local-first MCP server and CLI for project context and durable memory in Claude Code, Codex, and other AI coding agents.",
|
|
502
|
+
type: "module",
|
|
503
|
+
license: "MIT",
|
|
504
|
+
publishConfig: {
|
|
505
|
+
access: "public"
|
|
620
506
|
},
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
var interactions = pgTable(
|
|
626
|
-
"interactions",
|
|
627
|
-
{
|
|
628
|
-
id: text("id").primaryKey(),
|
|
629
|
-
namespaceId: text("namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
630
|
-
sessionId: text("session_id").notNull(),
|
|
631
|
-
role: interactionRoleEnum("role").notNull().default("user"),
|
|
632
|
-
content: text("content").notNull(),
|
|
633
|
-
contentHash: text("content_hash").notNull(),
|
|
634
|
-
tokenCount: integer("token_count").notNull().default(0),
|
|
635
|
-
createdByAgent: text("created_by_agent"),
|
|
636
|
-
createdAt
|
|
507
|
+
author: "Orquentra Inc",
|
|
508
|
+
repository: {
|
|
509
|
+
type: "git",
|
|
510
|
+
url: "https://github.com/Orquentra-Inc/CodeBuddy"
|
|
637
511
|
},
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
table.namespaceId,
|
|
641
|
-
table.contentHash
|
|
642
|
-
),
|
|
643
|
-
namespaceSessionCreatedIdx: index("interactions_namespace_session_created_idx").on(
|
|
644
|
-
table.namespaceId,
|
|
645
|
-
table.sessionId,
|
|
646
|
-
table.createdAt
|
|
647
|
-
)
|
|
648
|
-
})
|
|
649
|
-
);
|
|
650
|
-
var sessionSummaries = pgTable(
|
|
651
|
-
"session_summaries",
|
|
652
|
-
{
|
|
653
|
-
id: text("id").primaryKey(),
|
|
654
|
-
namespaceId: text("namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
655
|
-
sessionId: text("session_id").notNull(),
|
|
656
|
-
content: text("content").notNull(),
|
|
657
|
-
version: integer("version").notNull().default(1),
|
|
658
|
-
tokenCount: integer("token_count").notNull().default(0),
|
|
659
|
-
createdByAgent: text("created_by_agent"),
|
|
660
|
-
/** See facts.fileBacked. */
|
|
661
|
-
fileBacked: boolean("file_backed").notNull().default(false),
|
|
662
|
-
createdAt
|
|
512
|
+
engines: {
|
|
513
|
+
node: ">=20"
|
|
663
514
|
},
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
515
|
+
bin: {
|
|
516
|
+
codebuddy: "./dist/cli/index.js"
|
|
517
|
+
},
|
|
518
|
+
exports: {
|
|
519
|
+
".": {
|
|
520
|
+
types: "./dist/index.d.ts",
|
|
521
|
+
import: "./dist/index.js"
|
|
522
|
+
},
|
|
523
|
+
"./langgraph": {
|
|
524
|
+
types: "./dist/langgraph/index.d.ts",
|
|
525
|
+
import: "./dist/langgraph/index.js"
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
files: [
|
|
529
|
+
"dist",
|
|
530
|
+
"src/db/migrations",
|
|
531
|
+
"README.md",
|
|
532
|
+
"CLAUDE_CODEX.md",
|
|
533
|
+
"ARCHITECTURE.md",
|
|
534
|
+
"WORKFLOW.md",
|
|
535
|
+
"LICENSE",
|
|
536
|
+
"docker-compose.yml",
|
|
537
|
+
"examples"
|
|
538
|
+
],
|
|
539
|
+
scripts: {
|
|
540
|
+
build: "tsup",
|
|
541
|
+
dev: "tsx src/cli/index.ts",
|
|
542
|
+
"db:generate": "drizzle-kit generate",
|
|
543
|
+
"db:migrate": "drizzle-kit migrate",
|
|
544
|
+
typecheck: "tsc --noEmit",
|
|
545
|
+
test: "vitest run --passWithNoTests",
|
|
546
|
+
"test:pg": "bash scripts/test-pg-recovery.sh",
|
|
547
|
+
"eval:agent-ab": "tsx scripts/eval-agent-ab.mts",
|
|
548
|
+
"eval:memory-ab": "tsx scripts/eval-memory-ab.mts",
|
|
549
|
+
"eval:retrieval": "tsx scripts/eval-retrieval.mts",
|
|
550
|
+
"eval:savings": "tsx scripts/eval-task-savings.mts",
|
|
551
|
+
"branches:prune": "bash scripts/prune-merged-branches.sh",
|
|
552
|
+
lint: "biome check .",
|
|
553
|
+
format: "biome format --write .",
|
|
554
|
+
prepublishOnly: "node scripts/check-release.mjs && npm run typecheck && npm test && npm run build",
|
|
555
|
+
"eval:semantic": "tsx scripts/eval-semantic.mts"
|
|
703
556
|
},
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
attempts: integer("attempts").notNull().default(0),
|
|
729
|
-
lastError: text("last_error"),
|
|
730
|
-
/** Lease deadline while status='processing'; null otherwise. */
|
|
731
|
-
leaseUntil: timestamp("lease_until", { withTimezone: true }),
|
|
732
|
-
leaseToken: text("lease_token"),
|
|
733
|
-
createdAt,
|
|
734
|
-
updatedAt
|
|
557
|
+
keywords: [
|
|
558
|
+
"codebuddy",
|
|
559
|
+
"orquentra",
|
|
560
|
+
"mcp",
|
|
561
|
+
"mcp-server",
|
|
562
|
+
"model-context-protocol",
|
|
563
|
+
"claude-code",
|
|
564
|
+
"codex",
|
|
565
|
+
"opencode",
|
|
566
|
+
"ai-agent",
|
|
567
|
+
"coding-agent",
|
|
568
|
+
"context-engine",
|
|
569
|
+
"memory",
|
|
570
|
+
"agent",
|
|
571
|
+
"multi-agent",
|
|
572
|
+
"pgvector",
|
|
573
|
+
"postgres",
|
|
574
|
+
"huggingface",
|
|
575
|
+
"langgraph",
|
|
576
|
+
"claude"
|
|
577
|
+
],
|
|
578
|
+
homepage: "https://github.com/Orquentra-Inc/CodeBuddy",
|
|
579
|
+
bugs: {
|
|
580
|
+
url: "https://github.com/Orquentra-Inc/CodeBuddy/issues"
|
|
735
581
|
},
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
)
|
|
752
|
-
})
|
|
753
|
-
);
|
|
754
|
-
var shares = pgTable(
|
|
755
|
-
"shares",
|
|
756
|
-
{
|
|
757
|
-
id: text("id").primaryKey(),
|
|
758
|
-
fromNamespaceId: text("from_namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
759
|
-
toNamespaceId: text("to_namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
760
|
-
sourceFactId: text("source_fact_id").notNull().references(() => facts.id, { onDelete: "cascade" }),
|
|
761
|
-
snapshotFactId: text("snapshot_fact_id").references(() => facts.id, { onDelete: "set null" }),
|
|
762
|
-
mode: shareModeEnum("mode").notNull().default("reference"),
|
|
763
|
-
tombstoned: boolean("tombstoned").notNull().default(false),
|
|
764
|
-
createdByAgent: text("created_by_agent"),
|
|
765
|
-
createdAt
|
|
582
|
+
dependencies: {
|
|
583
|
+
"@clack/prompts": "^1.8.1",
|
|
584
|
+
"@huggingface/inference": "^4.13.19",
|
|
585
|
+
"@modelcontextprotocol/sdk": "^1.17.5",
|
|
586
|
+
commander: "^14.0.0",
|
|
587
|
+
"drizzle-orm": "^0.45.2",
|
|
588
|
+
"js-tiktoken": "^1.0.21",
|
|
589
|
+
"p-queue": "^10.0.0",
|
|
590
|
+
"p-retry": "^8.0.1",
|
|
591
|
+
"p-timeout": "^8.1.0",
|
|
592
|
+
picocolors: "^1.1.1",
|
|
593
|
+
pino: "^10.4.0",
|
|
594
|
+
postgres: "^3.4.7",
|
|
595
|
+
yaml: "^2.9.0",
|
|
596
|
+
zod: "^4.6.7"
|
|
766
597
|
},
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
table.toNamespaceId,
|
|
770
|
-
table.sourceFactId,
|
|
771
|
-
table.mode
|
|
772
|
-
),
|
|
773
|
-
fromNamespaceIdx: index("shares_from_namespace_idx").on(table.fromNamespaceId)
|
|
774
|
-
})
|
|
775
|
-
);
|
|
776
|
-
var checkpoints = pgTable(
|
|
777
|
-
"checkpoints",
|
|
778
|
-
{
|
|
779
|
-
id: text("id").primaryKey(),
|
|
780
|
-
namespaceId: text("namespace_id").notNull().references(() => namespaces.id, { onDelete: "cascade" }),
|
|
781
|
-
threadId: text("thread_id").notNull(),
|
|
782
|
-
checkpointId: text("checkpoint_id").notNull(),
|
|
783
|
-
parentId: text("parent_id"),
|
|
784
|
-
channelValues: jsonb("channel_values").notNull().default(sql`'{}'::jsonb`),
|
|
785
|
-
metadata: jsonb("metadata").notNull().default(sql`'{}'::jsonb`),
|
|
786
|
-
createdAt
|
|
598
|
+
peerDependencies: {
|
|
599
|
+
"@langchain/langgraph": "^0.2.0 || ^0.3.0"
|
|
787
600
|
},
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
table.checkpointId
|
|
793
|
-
)
|
|
794
|
-
})
|
|
795
|
-
);
|
|
796
|
-
var modelCalls = pgTable(
|
|
797
|
-
"model_calls",
|
|
798
|
-
{
|
|
799
|
-
id: text("id").primaryKey(),
|
|
800
|
-
namespaceId: text("namespace_id").references(() => namespaces.id, { onDelete: "set null" }),
|
|
801
|
-
model: text("model").notNull(),
|
|
802
|
-
type: modelCallTypeEnum("type").notNull(),
|
|
803
|
-
latencyMs: integer("latency_ms"),
|
|
804
|
-
tokens: integer("tokens"),
|
|
805
|
-
status: modelCallStatusEnum("status").notNull(),
|
|
806
|
-
error: text("error"),
|
|
807
|
-
metadata: jsonb("metadata").notNull().default(sql`'{}'::jsonb`),
|
|
808
|
-
createdAt
|
|
601
|
+
peerDependenciesMeta: {
|
|
602
|
+
"@langchain/langgraph": {
|
|
603
|
+
optional: true
|
|
604
|
+
}
|
|
809
605
|
},
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
var auditLog = pgTable(
|
|
819
|
-
"audit_log",
|
|
820
|
-
{
|
|
821
|
-
id: text("id").primaryKey(),
|
|
822
|
-
namespaceId: text("namespace_id").references(() => namespaces.id, { onDelete: "set null" }),
|
|
823
|
-
action: auditActionEnum("action").notNull(),
|
|
824
|
-
entityType: text("entity_type").notNull(),
|
|
825
|
-
entityId: text("entity_id"),
|
|
826
|
-
createdByAgent: text("created_by_agent"),
|
|
827
|
-
metadata: jsonb("metadata").notNull().default(sql`'{}'::jsonb`),
|
|
828
|
-
createdAt
|
|
606
|
+
devDependencies: {
|
|
607
|
+
"@biomejs/biome": "^2.5.0",
|
|
608
|
+
"@types/node": "^24.3.0",
|
|
609
|
+
"drizzle-kit": "^0.31.10",
|
|
610
|
+
tsup: "^8.5.0",
|
|
611
|
+
tsx: "^4.20.5",
|
|
612
|
+
typescript: "^5.9.2",
|
|
613
|
+
vitest: "^4.1.12"
|
|
829
614
|
},
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
table.createdAt
|
|
835
|
-
)
|
|
836
|
-
})
|
|
837
|
-
);
|
|
615
|
+
optionalDependencies: {
|
|
616
|
+
"@huggingface/transformers": "^4.3.2"
|
|
617
|
+
}
|
|
618
|
+
};
|
|
838
619
|
|
|
839
|
-
// src/
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
620
|
+
// src/version.ts
|
|
621
|
+
var VERSION = package_default.version;
|
|
622
|
+
|
|
623
|
+
// src/core/memory-file-store.ts
|
|
624
|
+
import { mkdir, readdir, readFile, rm, stat } from "fs/promises";
|
|
625
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
626
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
627
|
+
import { z } from "zod";
|
|
628
|
+
|
|
629
|
+
// src/core/memory-lifecycle.ts
|
|
630
|
+
function isEligibleMemory(fact, options = {}) {
|
|
631
|
+
if (options.namespace && fact.namespace !== options.namespace) return false;
|
|
632
|
+
if (fact.sourceDeleted || fact.supersededBy) return false;
|
|
633
|
+
if (fact.category === "incident" && fact.resolvedBy && !options.includeResolved) return false;
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/core/memory-file-store.ts
|
|
638
|
+
var FACT_CATEGORIES = ["general", "incident"];
|
|
639
|
+
var INCIDENT_SEVERITIES = ["low", "medium", "high", "critical"];
|
|
640
|
+
var factFrontMatterSchema = z.object({
|
|
641
|
+
schemaVersion: z.literal(1),
|
|
642
|
+
id: z.string().regex(/^fact_[a-z0-9]+$/),
|
|
643
|
+
namespace: z.string().min(1),
|
|
644
|
+
type: z.literal("fact"),
|
|
645
|
+
subject: z.string(),
|
|
646
|
+
predicate: z.string(),
|
|
647
|
+
object: z.string(),
|
|
648
|
+
/** Explicit single-valued claim identity; facts sharing it are rival assertions. */
|
|
649
|
+
propositionKey: z.string().min(1).nullable().default(null),
|
|
650
|
+
/** The caller's `remember` idempotency key, so a retry after reindex dedupes. */
|
|
651
|
+
idempotencyKey: z.string().min(1).max(200).nullable().default(null),
|
|
652
|
+
confidence: z.number().min(0).max(1),
|
|
653
|
+
createdAt: z.preprocess(
|
|
654
|
+
(value) => value instanceof Date ? value.toISOString() : value,
|
|
655
|
+
z.string().datetime()
|
|
656
|
+
),
|
|
657
|
+
createdByAgent: z.string().nullable(),
|
|
658
|
+
sourceInteractionId: z.string().nullable(),
|
|
659
|
+
sourceDeleted: z.boolean(),
|
|
660
|
+
category: z.enum(FACT_CATEGORIES).default("general"),
|
|
661
|
+
paths: z.array(z.string().min(1)).default([]),
|
|
662
|
+
severity: z.enum(INCIDENT_SEVERITIES).nullable().default(null),
|
|
663
|
+
introducedBy: z.string().nullable().default(null),
|
|
664
|
+
resolvedBy: z.string().nullable().default(null),
|
|
665
|
+
fingerprint: z.string().regex(/^[a-f0-9]{64}$/).nullable().default(null),
|
|
666
|
+
sourcePlanId: z.string().nullable().default(null),
|
|
667
|
+
sourceSessionId: z.string().nullable().default(null),
|
|
668
|
+
verification: z.array(z.string()).default([]),
|
|
669
|
+
supersededBy: z.string().nullable().default(null),
|
|
670
|
+
/** Namespaces granted a live reference to this fact (durable form of reference shares). */
|
|
671
|
+
sharedWith: z.array(z.string().min(1)).default([])
|
|
672
|
+
});
|
|
673
|
+
var summaryFrontMatterSchema = z.object({
|
|
674
|
+
schemaVersion: z.literal(1),
|
|
675
|
+
id: z.string().regex(/^sum_[a-z0-9]+$/),
|
|
676
|
+
namespace: z.string().min(1),
|
|
677
|
+
type: z.literal("summary"),
|
|
678
|
+
sessionId: z.string().min(1),
|
|
679
|
+
version: z.number().int().positive(),
|
|
680
|
+
tokenCount: z.number().int().nonnegative(),
|
|
681
|
+
createdAt: z.preprocess(
|
|
682
|
+
(value) => value instanceof Date ? value.toISOString() : value,
|
|
683
|
+
z.string().datetime()
|
|
684
|
+
),
|
|
685
|
+
createdByAgent: z.string().nullable()
|
|
686
|
+
});
|
|
687
|
+
var SCAN_BATCH = 64;
|
|
688
|
+
var parsedFiles = /* @__PURE__ */ new Map();
|
|
689
|
+
var loadedCaches = /* @__PURE__ */ new Set();
|
|
690
|
+
function parseCachePath(root, directory) {
|
|
691
|
+
return join(
|
|
692
|
+
root,
|
|
693
|
+
".codebuddy",
|
|
694
|
+
"cache",
|
|
695
|
+
`parsed-${directory.split(sep).at(-1) ?? "memory"}.json`
|
|
696
|
+
);
|
|
843
697
|
}
|
|
844
|
-
function
|
|
845
|
-
|
|
698
|
+
async function loadParseCache(root, directory) {
|
|
699
|
+
if (loadedCaches.has(directory)) return;
|
|
700
|
+
loadedCaches.add(directory);
|
|
701
|
+
try {
|
|
702
|
+
const raw = JSON.parse(await readFile(parseCachePath(root, directory), "utf8"));
|
|
703
|
+
if (raw.version !== VERSION) return;
|
|
704
|
+
for (const [name, entry] of Object.entries(raw.files)) {
|
|
705
|
+
const path = join(directory, name);
|
|
706
|
+
if (!parsedFiles.has(path)) parsedFiles.set(path, entry);
|
|
707
|
+
}
|
|
708
|
+
} catch {
|
|
709
|
+
}
|
|
846
710
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
"are",
|
|
859
|
-
"as",
|
|
860
|
-
"at",
|
|
861
|
-
"back",
|
|
862
|
-
"be",
|
|
863
|
-
"been",
|
|
864
|
-
"before",
|
|
865
|
-
"being",
|
|
866
|
-
"but",
|
|
867
|
-
"by",
|
|
868
|
-
"can",
|
|
869
|
-
"could",
|
|
870
|
-
"did",
|
|
871
|
-
"do",
|
|
872
|
-
"does",
|
|
873
|
-
"done",
|
|
874
|
-
"each",
|
|
875
|
-
"even",
|
|
876
|
-
"ever",
|
|
877
|
-
"for",
|
|
878
|
-
"from",
|
|
879
|
-
"get",
|
|
880
|
-
"gets",
|
|
881
|
-
"got",
|
|
882
|
-
"had",
|
|
883
|
-
"has",
|
|
884
|
-
"have",
|
|
885
|
-
"here",
|
|
886
|
-
"how",
|
|
887
|
-
"i",
|
|
888
|
-
"if",
|
|
889
|
-
"in",
|
|
890
|
-
"into",
|
|
891
|
-
"is",
|
|
892
|
-
"it",
|
|
893
|
-
"its",
|
|
894
|
-
"just",
|
|
895
|
-
"like",
|
|
896
|
-
"make",
|
|
897
|
-
"me",
|
|
898
|
-
"more",
|
|
899
|
-
"most",
|
|
900
|
-
"my",
|
|
901
|
-
"never",
|
|
902
|
-
"no",
|
|
903
|
-
"not",
|
|
904
|
-
"now",
|
|
905
|
-
"of",
|
|
906
|
-
"on",
|
|
907
|
-
"only",
|
|
908
|
-
"or",
|
|
909
|
-
"other",
|
|
910
|
-
"our",
|
|
911
|
-
"out",
|
|
912
|
-
"over",
|
|
913
|
-
"please",
|
|
914
|
-
"really",
|
|
915
|
-
"same",
|
|
916
|
-
"should",
|
|
917
|
-
"so",
|
|
918
|
-
"some",
|
|
919
|
-
"sometimes",
|
|
920
|
-
"still",
|
|
921
|
-
"such",
|
|
922
|
-
"than",
|
|
923
|
-
"that",
|
|
924
|
-
"the",
|
|
925
|
-
"their",
|
|
926
|
-
"them",
|
|
927
|
-
"then",
|
|
928
|
-
"there",
|
|
929
|
-
"these",
|
|
930
|
-
"they",
|
|
931
|
-
"this",
|
|
932
|
-
"those",
|
|
933
|
-
"through",
|
|
934
|
-
"to",
|
|
935
|
-
"too",
|
|
936
|
-
"under",
|
|
937
|
-
"up",
|
|
938
|
-
"us",
|
|
939
|
-
"very",
|
|
940
|
-
"was",
|
|
941
|
-
"we",
|
|
942
|
-
"were",
|
|
943
|
-
"what",
|
|
944
|
-
"when",
|
|
945
|
-
"where",
|
|
946
|
-
"which",
|
|
947
|
-
"while",
|
|
948
|
-
"who",
|
|
949
|
-
"why",
|
|
950
|
-
"will",
|
|
951
|
-
"with",
|
|
952
|
-
"would",
|
|
953
|
-
"yet",
|
|
954
|
-
"you",
|
|
955
|
-
"your"
|
|
956
|
-
]);
|
|
957
|
-
function lexicalTerms(query) {
|
|
958
|
-
return [
|
|
959
|
-
...new Set(
|
|
960
|
-
query.slice(0, 4096).toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []
|
|
961
|
-
)
|
|
962
|
-
].filter((term) => !QUERY_STOPWORDS.has(term)).slice(0, 32).map((term) => term.slice(0, 64));
|
|
711
|
+
async function saveParseCache(root, directory) {
|
|
712
|
+
const files = {};
|
|
713
|
+
for (const [path, entry] of parsedFiles) {
|
|
714
|
+
if (dirname(path) === directory) files[relative(directory, path)] = entry;
|
|
715
|
+
}
|
|
716
|
+
try {
|
|
717
|
+
const path = parseCachePath(root, directory);
|
|
718
|
+
await mkdir(dirname(path), { recursive: true });
|
|
719
|
+
await writeAtomic(path, JSON.stringify({ version: VERSION, files }));
|
|
720
|
+
} catch {
|
|
721
|
+
}
|
|
963
722
|
}
|
|
964
|
-
var
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
723
|
+
var MemoryFileStore = class {
|
|
724
|
+
repositoryRoot;
|
|
725
|
+
factsDirectory;
|
|
726
|
+
summariesDirectory;
|
|
727
|
+
constructor(repositoryRoot = process.cwd()) {
|
|
728
|
+
this.repositoryRoot = resolve(repositoryRoot);
|
|
729
|
+
this.factsDirectory = join(this.repositoryRoot, ".codebuddy", "memory", "facts");
|
|
730
|
+
this.summariesDirectory = join(this.repositoryRoot, ".codebuddy", "memory", "summaries");
|
|
731
|
+
}
|
|
732
|
+
async writeFact(raw) {
|
|
733
|
+
const input = redactPersisted(raw);
|
|
734
|
+
const path = this.factPath(input.id);
|
|
735
|
+
await this.prepareDirectory(this.factsDirectory);
|
|
736
|
+
await assertNoSymlinkBetween(this.repositoryRoot, dirname(path));
|
|
737
|
+
const metadata = {
|
|
738
|
+
schemaVersion: 1,
|
|
739
|
+
id: input.id,
|
|
740
|
+
namespace: input.namespace,
|
|
741
|
+
type: "fact",
|
|
742
|
+
subject: input.subject,
|
|
743
|
+
predicate: input.predicate,
|
|
744
|
+
object: input.object,
|
|
745
|
+
propositionKey: input.propositionKey,
|
|
746
|
+
idempotencyKey: input.idempotencyKey,
|
|
747
|
+
confidence: input.confidence,
|
|
748
|
+
createdAt: input.createdAt,
|
|
749
|
+
createdByAgent: input.createdByAgent,
|
|
750
|
+
sourceInteractionId: input.sourceInteractionId,
|
|
751
|
+
sourceDeleted: input.sourceDeleted,
|
|
752
|
+
category: input.category,
|
|
753
|
+
paths: input.paths,
|
|
754
|
+
severity: input.severity,
|
|
755
|
+
introducedBy: input.introducedBy,
|
|
756
|
+
resolvedBy: input.resolvedBy,
|
|
757
|
+
fingerprint: input.fingerprint,
|
|
758
|
+
sourcePlanId: input.sourcePlanId,
|
|
759
|
+
sourceSessionId: input.sourceSessionId,
|
|
760
|
+
verification: input.verification,
|
|
761
|
+
supersededBy: input.supersededBy,
|
|
762
|
+
sharedWith: input.sharedWith
|
|
763
|
+
};
|
|
764
|
+
const frontMatter = stringifyYaml(stripUndefinedAndDefaultIncidentFields(metadata));
|
|
765
|
+
await writeAtomic(path, composeMarkdown(frontMatter, input.content));
|
|
766
|
+
return path;
|
|
767
|
+
}
|
|
768
|
+
async writeSummary(input) {
|
|
769
|
+
const path = this.summaryPath(input.id);
|
|
770
|
+
await this.prepareDirectory(this.summariesDirectory);
|
|
771
|
+
await assertNoSymlinkBetween(this.repositoryRoot, dirname(path));
|
|
772
|
+
const metadata = stringifyYaml({
|
|
773
|
+
schemaVersion: 1,
|
|
774
|
+
id: input.id,
|
|
775
|
+
namespace: input.namespace,
|
|
776
|
+
type: "summary",
|
|
777
|
+
sessionId: input.sessionId,
|
|
778
|
+
version: input.version,
|
|
779
|
+
tokenCount: input.tokenCount,
|
|
780
|
+
createdAt: input.createdAt,
|
|
781
|
+
createdByAgent: input.createdByAgent
|
|
782
|
+
});
|
|
783
|
+
await writeAtomic(path, composeMarkdown(metadata, input.content));
|
|
784
|
+
return path;
|
|
785
|
+
}
|
|
786
|
+
async readFact(pathOrId) {
|
|
787
|
+
return this.readFactFile(pathOrId, false);
|
|
788
|
+
}
|
|
789
|
+
/** `trusted`: the caller (scan) already proved the path is a regular file in a checked directory. */
|
|
790
|
+
async readFactFile(pathOrId, trusted) {
|
|
791
|
+
const path = pathOrId.endsWith(".md") ? this.safePath(pathOrId) : this.factPath(pathOrId);
|
|
792
|
+
if (!trusted) await assertNoSymlinkBetween(this.repositoryRoot, path);
|
|
793
|
+
const parsed = parseFrontMatter(await readFile(path, "utf8"));
|
|
794
|
+
const metadata = factFrontMatterSchema.parse(parseYaml(parsed.frontMatter));
|
|
795
|
+
return {
|
|
796
|
+
...metadata,
|
|
797
|
+
content: parsed.content.trim(),
|
|
798
|
+
path
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
async listFacts() {
|
|
802
|
+
return (await this.scanFacts()).facts;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Parse every managed fact file. Hand-written markdown is ignored, and a
|
|
806
|
+
* schema-invalid/corrupt fact is reported in `invalid` (not thrown) so one
|
|
807
|
+
* bad file can't break recall, capture or reindexing for the whole project.
|
|
808
|
+
* Invalid files stay on disk for manual repair.
|
|
809
|
+
*/
|
|
810
|
+
async scanFacts() {
|
|
811
|
+
const scan = await this.scan(
|
|
812
|
+
this.factsDirectory,
|
|
813
|
+
/^fact_[a-z0-9]+\.md$/,
|
|
814
|
+
(path, trusted) => this.readFactFile(path, trusted)
|
|
815
|
+
);
|
|
816
|
+
return { facts: scan.items, invalid: scan.invalid };
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* List incident facts for the namespace, ranked by severity then recency.
|
|
820
|
+
* Unlike {@link findIncidentFactsForPaths} this does not filter by path, so
|
|
821
|
+
* callers (e.g. the context engine's project bootstrap) can surface the
|
|
822
|
+
* repository's incident hotspots before any specific edit target is known.
|
|
823
|
+
*/
|
|
824
|
+
async listIncidentFacts(input = {}) {
|
|
825
|
+
const incidents = (await this.listFacts()).filter(
|
|
826
|
+
(fact) => fact.category === "incident" && Boolean(fact.severity) && isEligibleMemory(fact, input)
|
|
827
|
+
);
|
|
828
|
+
return incidents.sort((left, right) => {
|
|
829
|
+
const severity = severityRank(right.severity) - severityRank(left.severity);
|
|
830
|
+
if (severity !== 0) return severity;
|
|
831
|
+
return right.createdAt.localeCompare(left.createdAt);
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
async findIncidentFactsForPaths(input) {
|
|
835
|
+
const wanted = new Set(input.paths.map(normalizeMemoryPath));
|
|
836
|
+
if (wanted.size === 0) return [];
|
|
837
|
+
return (await this.listIncidentFacts({
|
|
838
|
+
...input.namespace !== void 0 ? { namespace: input.namespace } : {},
|
|
839
|
+
...input.includeResolved !== void 0 ? { includeResolved: input.includeResolved } : {}
|
|
840
|
+
})).filter((fact) => fact.paths.map(normalizeMemoryPath).some((path) => wanted.has(path)));
|
|
841
|
+
}
|
|
842
|
+
async readSummary(pathOrId) {
|
|
843
|
+
const path = pathOrId.endsWith(".md") ? this.safePath(pathOrId) : this.summaryPath(pathOrId);
|
|
844
|
+
await assertNoSymlinkBetween(this.repositoryRoot, path);
|
|
845
|
+
const parsed = parseFrontMatter(await readFile(path, "utf8"));
|
|
846
|
+
return {
|
|
847
|
+
...summaryFrontMatterSchema.parse(parseYaml(parsed.frontMatter)),
|
|
848
|
+
content: parsed.content.trim(),
|
|
849
|
+
path
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
async listSummaries() {
|
|
853
|
+
return (await this.scanSummaries()).summaries;
|
|
854
|
+
}
|
|
855
|
+
async scanSummaries() {
|
|
856
|
+
const scan = await this.scan(
|
|
857
|
+
this.summariesDirectory,
|
|
858
|
+
/^sum_[a-z0-9]+\.md$/,
|
|
859
|
+
(path) => this.readSummary(path)
|
|
860
|
+
);
|
|
861
|
+
return { summaries: scan.items, invalid: scan.invalid };
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Parse every managed file in `directory`. Hot path: the prompt hook and
|
|
865
|
+
* capture scan facts several times per process, and the MCP server for its
|
|
866
|
+
* lifetime, so each parsed file is cached by path, mtime and size (a changed
|
|
867
|
+
* file is re-read, from any process) and files are read in parallel batches.
|
|
868
|
+
* The directory chain is checked for symlinks once; a `withFileTypes` entry
|
|
869
|
+
* that is a symlink is never `isFile()`, so no file escapes through one.
|
|
870
|
+
*/
|
|
871
|
+
async scan(directory, managedName, read) {
|
|
872
|
+
try {
|
|
873
|
+
await this.prepareDirectory(directory);
|
|
874
|
+
await assertNoSymlinkBetween(this.repositoryRoot, directory);
|
|
875
|
+
await loadParseCache(this.repositoryRoot, directory);
|
|
876
|
+
let reparsed = false;
|
|
877
|
+
const entries = (await readdir(directory, { withFileTypes: true })).filter((entry) => entry.isFile() && managedName.test(entry.name)).sort((left, right) => left.name.localeCompare(right.name));
|
|
878
|
+
const live = new Set(entries.map((entry) => join(directory, entry.name)));
|
|
879
|
+
for (const path of [...parsedFiles.keys()]) {
|
|
880
|
+
if (dirname(path) === directory && !live.has(path)) {
|
|
881
|
+
parsedFiles.delete(path);
|
|
882
|
+
reparsed = true;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
const outcomes = [];
|
|
886
|
+
for (let start = 0; start < entries.length; start += SCAN_BATCH) {
|
|
887
|
+
const batch = entries.slice(start, start + SCAN_BATCH);
|
|
888
|
+
outcomes.push(
|
|
889
|
+
...await Promise.all(
|
|
890
|
+
batch.map(async (entry) => {
|
|
891
|
+
const path = join(directory, entry.name);
|
|
892
|
+
const id = entry.name.slice(0, -".md".length);
|
|
893
|
+
try {
|
|
894
|
+
const info = await stat(path);
|
|
895
|
+
const cached = parsedFiles.get(path);
|
|
896
|
+
const fresh = cached !== void 0 && cached.mtimeMs === info.mtimeMs && cached.size === info.size;
|
|
897
|
+
const item = fresh ? cached.item : await read(path, true);
|
|
898
|
+
if (!fresh) reparsed = true;
|
|
899
|
+
if (item.id !== id) {
|
|
900
|
+
throw new Error(`file ID ${id} differs from front matter ID ${item.id}`);
|
|
901
|
+
}
|
|
902
|
+
parsedFiles.set(path, { mtimeMs: info.mtimeMs, size: info.size, item });
|
|
903
|
+
return { item: { ...item } };
|
|
904
|
+
} catch (error) {
|
|
905
|
+
parsedFiles.delete(path);
|
|
906
|
+
return {
|
|
907
|
+
invalid: {
|
|
908
|
+
id,
|
|
909
|
+
path,
|
|
910
|
+
error: error.message.split("\n")[0]?.slice(0, 300) ?? "unreadable"
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
})
|
|
915
|
+
)
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
if (reparsed) await saveParseCache(this.repositoryRoot, directory);
|
|
919
|
+
const result = { items: [], invalid: [] };
|
|
920
|
+
for (const outcome of outcomes) {
|
|
921
|
+
if ("item" in outcome) result.items.push(outcome.item);
|
|
922
|
+
else result.invalid.push(outcome.invalid);
|
|
923
|
+
}
|
|
924
|
+
return result;
|
|
925
|
+
} catch (error) {
|
|
926
|
+
if (error.code === "ENOENT") return { items: [], invalid: [] };
|
|
927
|
+
throw error;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
async deleteFact(id) {
|
|
931
|
+
await rm(this.factPath(id), { force: true });
|
|
932
|
+
}
|
|
933
|
+
async markIncidentResolved(id, resolvedBy) {
|
|
934
|
+
const fact = await this.readFact(id);
|
|
935
|
+
await this.writeFact(copyFact(fact, { resolvedBy }));
|
|
936
|
+
}
|
|
937
|
+
async markFactSuperseded(id, supersededBy) {
|
|
938
|
+
const fact = await this.readFact(id);
|
|
939
|
+
await this.writeFact(copyFact(fact, { supersededBy }));
|
|
940
|
+
}
|
|
941
|
+
async setFactSharedWith(id, sharedWith) {
|
|
942
|
+
const fact = await this.readFact(id);
|
|
943
|
+
await this.writeFact(copyFact(fact, { sharedWith: [...new Set(sharedWith)].sort() }));
|
|
944
|
+
}
|
|
945
|
+
/** Persist interaction deletion in Markdown before the derived DB is updated. */
|
|
946
|
+
async markSourceInteractionDeleted(namespace, sourceInteractionId) {
|
|
947
|
+
const { facts: facts2 } = await this.scanFacts();
|
|
948
|
+
for (const fact of facts2) {
|
|
949
|
+
if (fact.namespace !== namespace || fact.sourceInteractionId !== sourceInteractionId)
|
|
950
|
+
continue;
|
|
951
|
+
if (fact.sourceDeleted) continue;
|
|
952
|
+
await this.writeFact(copyFact(fact, { sourceDeleted: true }));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
/** Persist that a fact's source interaction was forgotten, keeping its provenance id. */
|
|
956
|
+
async markFactSourceDeleted(id) {
|
|
957
|
+
const fact = await this.readFact(id);
|
|
958
|
+
await this.writeFact(copyFact(fact, { sourceDeleted: true }));
|
|
959
|
+
}
|
|
960
|
+
factPath(id) {
|
|
961
|
+
if (!/^fact_[a-z0-9]+$/.test(id)) {
|
|
962
|
+
throw new Error(`Invalid fact id: ${id}`);
|
|
963
|
+
}
|
|
964
|
+
return this.safePath(join(this.factsDirectory, `${id}.md`));
|
|
965
|
+
}
|
|
966
|
+
summaryPath(id) {
|
|
967
|
+
if (!/^sum_[a-z0-9]+$/.test(id)) throw new Error(`Invalid summary id: ${id}`);
|
|
968
|
+
return this.safePath(join(this.summariesDirectory, `${id}.md`));
|
|
969
|
+
}
|
|
970
|
+
safePath(path) {
|
|
971
|
+
const absolute = resolve(path);
|
|
972
|
+
const relativePath = relative(this.repositoryRoot, absolute);
|
|
973
|
+
if (relativePath === ".." || relativePath.startsWith(`..${sep}`) || relativePath === "") {
|
|
974
|
+
throw new Error(`Memory path escapes repository root: ${path}`);
|
|
975
|
+
}
|
|
976
|
+
return absolute;
|
|
977
|
+
}
|
|
978
|
+
async prepareDirectory(directory) {
|
|
979
|
+
await assertNoSymlinkBetween(this.repositoryRoot, dirname(directory));
|
|
980
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
function copyFact(fact, patch) {
|
|
984
|
+
const { path: _path, schemaVersion: _schemaVersion, type: _type, ...write } = fact;
|
|
985
|
+
return { ...write, ...patch };
|
|
974
986
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const terms = (text2) => new Set(
|
|
990
|
-
(text2.toLowerCase().match(/[\p{L}\p{N}_]+/gu) ?? []).filter(
|
|
991
|
-
(term) => !QUERY_STOPWORDS.has(term) && !CLAIM_BOILERPLATE.has(term)
|
|
992
|
-
)
|
|
987
|
+
function stripUndefinedAndDefaultIncidentFields(metadata) {
|
|
988
|
+
return Object.fromEntries(
|
|
989
|
+
Object.entries(metadata).filter(([key, value]) => {
|
|
990
|
+
if (value === void 0) return false;
|
|
991
|
+
if (key === "category" && value === "general") return false;
|
|
992
|
+
if ((key === "paths" || key === "sharedWith") && Array.isArray(value) && value.length === 0)
|
|
993
|
+
return false;
|
|
994
|
+
if (["severity", "introducedBy", "resolvedBy", "propositionKey", "idempotencyKey"].includes(
|
|
995
|
+
key
|
|
996
|
+
) && (value === null || value === void 0)) {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
return true;
|
|
1000
|
+
})
|
|
993
1001
|
);
|
|
994
|
-
const left = terms(a);
|
|
995
|
-
const right = terms(b);
|
|
996
|
-
if (left.size === 0 || right.size === 0) return 0;
|
|
997
|
-
let shared = 0;
|
|
998
|
-
for (const term of left) if (right.has(term)) shared += 1;
|
|
999
|
-
return shared / (left.size + right.size - shared);
|
|
1000
1002
|
}
|
|
1001
|
-
function
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1003
|
+
function normalizeMemoryPath(path) {
|
|
1004
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1005
|
+
}
|
|
1006
|
+
function severityRank(severity) {
|
|
1007
|
+
switch (severity) {
|
|
1008
|
+
case "critical":
|
|
1009
|
+
return 4;
|
|
1010
|
+
case "high":
|
|
1011
|
+
return 3;
|
|
1012
|
+
case "medium":
|
|
1013
|
+
return 2;
|
|
1014
|
+
case "low":
|
|
1015
|
+
return 1;
|
|
1013
1016
|
}
|
|
1014
|
-
return patterns;
|
|
1015
1017
|
}
|
|
1016
|
-
var MEMORY_CONCEPT_SCORE = 0.5;
|
|
1017
1018
|
|
|
1018
1019
|
export {
|
|
1019
1020
|
isEligibleMemory,
|
|
@@ -1038,4 +1039,4 @@ export {
|
|
|
1038
1039
|
INCIDENT_SEVERITIES,
|
|
1039
1040
|
MemoryFileStore
|
|
1040
1041
|
};
|
|
1041
|
-
//# sourceMappingURL=chunk-
|
|
1042
|
+
//# sourceMappingURL=chunk-YMXFSHXT.js.map
|