@gamaze/hicortex 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/THIRD_PARTY_NOTICES.md +108 -0
- package/assets/vendor/3d-force-graph.min.js +5 -0
- package/assets/vendor/force-graph.min.js +5 -0
- package/assets/vendor/three.core.min.js +6 -0
- package/assets/vendor/three.module.min.js +6 -0
- package/assets/viz.html +1128 -0
- package/dist/classify-domains.d.ts +98 -0
- package/dist/classify-domains.js +340 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +63 -0
- package/dist/consolidate.d.ts +139 -2
- package/dist/consolidate.js +302 -87
- package/dist/db.js +70 -0
- package/dist/domain-classify.d.ts +164 -0
- package/dist/domain-classify.js +300 -0
- package/dist/extensions.d.ts +12 -0
- package/dist/graph.d.ts +56 -0
- package/dist/graph.js +145 -0
- package/dist/index.js +1 -1
- package/dist/init.d.ts +25 -0
- package/dist/init.js +54 -0
- package/dist/lesson-selection.js +12 -5
- package/dist/lessons-context.js +2 -1
- package/dist/llm.d.ts +67 -0
- package/dist/llm.js +122 -0
- package/dist/mcp-server.js +86 -28
- package/dist/nightly-status.js +9 -28
- package/dist/nightly.js +42 -32
- package/dist/nofit.d.ts +111 -0
- package/dist/nofit.js +176 -0
- package/dist/prompts.d.ts +0 -5
- package/dist/prompts.js +5 -29
- package/dist/relink.d.ts +100 -0
- package/dist/relink.js +277 -0
- package/dist/retrieval.d.ts +16 -1
- package/dist/retrieval.js +34 -2
- package/dist/schema-prototypes.d.ts +149 -0
- package/dist/schema-prototypes.js +329 -0
- package/dist/state.d.ts +32 -0
- package/dist/state.js +29 -0
- package/dist/status.js +12 -19
- package/dist/storage.d.ts +44 -1
- package/dist/storage.js +70 -1
- package/dist/types.d.ts +90 -0
- package/dist/viz.d.ts +69 -0
- package/dist/viz.js +180 -0
- package/domains.example.json +36 -0
- package/package.json +6 -3
package/dist/mcp-server.js
CHANGED
|
@@ -60,6 +60,7 @@ const state_js_1 = require("./state.js");
|
|
|
60
60
|
const embedder_js_1 = require("./embedder.js");
|
|
61
61
|
const storage = __importStar(require("./storage.js"));
|
|
62
62
|
const graph_js_1 = require("./graph.js");
|
|
63
|
+
const viz_js_1 = require("./viz.js");
|
|
63
64
|
const retrieval = __importStar(require("./retrieval.js"));
|
|
64
65
|
const seed_lesson_js_1 = require("./seed-lesson.js");
|
|
65
66
|
const distiller_js_1 = require("./distiller.js");
|
|
@@ -227,9 +228,17 @@ function createMcpServer() {
|
|
|
227
228
|
const state = (0, state_js_1.loadState)(stateDir);
|
|
228
229
|
const moduleIndex = state.moduleIndex;
|
|
229
230
|
if (moduleIndex && moduleIndex.domains.length > 0) {
|
|
230
|
-
const text = moduleIndex.domains.map((d) =>
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
const text = moduleIndex.domains.map((d) => {
|
|
232
|
+
const head = `**${d.name}** (${d.memoryCount} memories, ${d.lessonCount} lessons)`;
|
|
233
|
+
// Content-based domains carry a description and no projects; legacy
|
|
234
|
+
// project-grouping domains carry a project list + keywords.
|
|
235
|
+
if (d.description && d.projects.length === 0) {
|
|
236
|
+
return `${head}\n ${d.description}`;
|
|
237
|
+
}
|
|
238
|
+
return head +
|
|
239
|
+
(d.projects.length > 0 ? `\n Projects: ${d.projects.join(", ")}` : "") +
|
|
240
|
+
(d.keywords.length > 0 ? `\n Keywords: ${d.keywords.join(", ")}` : "");
|
|
241
|
+
}).join("\n\n");
|
|
233
242
|
return { content: [{ type: "text", text }] };
|
|
234
243
|
}
|
|
235
244
|
// Fallback: flat project counts
|
|
@@ -248,7 +257,7 @@ function createMcpServer() {
|
|
|
248
257
|
target_id: zod_1.z.string().optional().describe("Target memory ID (required for path operation)"),
|
|
249
258
|
limit: zod_1.z.coerce.number().optional().describe("Max results (default 10)"),
|
|
250
259
|
domain: zod_1.z.string().optional().describe("Filter hubs by domain"),
|
|
251
|
-
relationship: zod_1.z.string().optional().describe("Filter neighbors by relationship type (e.g., CONTRADICTS, SUPERSEDES,
|
|
260
|
+
relationship: zod_1.z.string().optional().describe("Filter neighbors by relationship type (e.g., extends, relates_to; legacy data may also have CONTRADICTS, SUPERSEDES, updates)"),
|
|
252
261
|
}, async ({ operation, id, target_id, limit: resultLimit, domain: filterDomain, relationship: filterRelationship }) => {
|
|
253
262
|
if (!db)
|
|
254
263
|
return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
|
|
@@ -437,23 +446,12 @@ async function startServer(options = {}) {
|
|
|
437
446
|
// localhost bypass. With no token configured, remote requests are REJECTED
|
|
438
447
|
// (not open): the default bind is 0.0.0.0, so "no token = no auth" would
|
|
439
448
|
// expose the whole memory store to the network.
|
|
449
|
+
// Extracted to viz.ts (createAuthMiddleware) so the middleware is
|
|
450
|
+
// unit-testable; includes the narrow GET /viz?token= browser handoff (#124).
|
|
440
451
|
console.log(authToken
|
|
441
452
|
? `[hicortex] Bearer token auth enabled`
|
|
442
453
|
: `[hicortex] No auth token configured — remote access DISABLED (localhost only). Run init to generate a token.`);
|
|
443
|
-
app.use((
|
|
444
|
-
if (req.path === "/health")
|
|
445
|
-
return next();
|
|
446
|
-
const ip = req.ip ?? req.socket.remoteAddress ?? "";
|
|
447
|
-
if (ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1")
|
|
448
|
-
return next();
|
|
449
|
-
if (authToken && req.headers.authorization === `Bearer ${authToken}`)
|
|
450
|
-
return next();
|
|
451
|
-
res.status(401).json({
|
|
452
|
-
error: authToken
|
|
453
|
-
? "Unauthorized"
|
|
454
|
-
: "No auth token configured on this server — run `npx @gamaze/hicortex init` on the server, then connect with its token.",
|
|
455
|
-
});
|
|
456
|
-
});
|
|
454
|
+
app.use((0, viz_js_1.createAuthMiddleware)(authToken));
|
|
457
455
|
// SSE transport management — each connection gets its own McpServer instance
|
|
458
456
|
const transports = new Map();
|
|
459
457
|
// Health endpoint
|
|
@@ -762,15 +760,27 @@ async function startServer(options = {}) {
|
|
|
762
760
|
// -------------------------------------------------------------------------
|
|
763
761
|
// REST /index — knowledge domain index (same payload as hicortex_index MCP).
|
|
764
762
|
//
|
|
765
|
-
// NOTE
|
|
766
|
-
//
|
|
763
|
+
// NOTE (#124): /viz consumes this JSON surface. Keep the response shape
|
|
764
|
+
// clean: {domains} or {projects} fallback.
|
|
767
765
|
// -------------------------------------------------------------------------
|
|
768
766
|
app.get("/index", (_req, res) => {
|
|
769
767
|
try {
|
|
770
768
|
const state = (0, state_js_1.loadState)(stateDir);
|
|
771
769
|
const moduleIndex = state.moduleIndex;
|
|
772
770
|
if (moduleIndex && moduleIndex.domains && moduleIndex.domains.length > 0) {
|
|
773
|
-
|
|
771
|
+
// domains[].memoryCount = PRIMARY-tag counts (unchanged). tagCounts is
|
|
772
|
+
// additive: total assignments per tag across memory_tags (multi-label
|
|
773
|
+
// breadth, incl. secondary tags). Absent when no tags exist yet.
|
|
774
|
+
let tagCounts;
|
|
775
|
+
if (db) {
|
|
776
|
+
const tagRows = db.prepare("SELECT tag, COUNT(*) as cnt FROM memory_tags GROUP BY tag ORDER BY cnt DESC").all();
|
|
777
|
+
if (tagRows.length > 0) {
|
|
778
|
+
tagCounts = {};
|
|
779
|
+
for (const r of tagRows)
|
|
780
|
+
tagCounts[r.tag] = r.cnt;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
res.json(tagCounts ? { domains: moduleIndex.domains, tagCounts } : { domains: moduleIndex.domains });
|
|
774
784
|
return;
|
|
775
785
|
}
|
|
776
786
|
// Fallback: flat project counts when moduleIndex is not yet built
|
|
@@ -788,15 +798,17 @@ async function startServer(options = {}) {
|
|
|
788
798
|
// -------------------------------------------------------------------------
|
|
789
799
|
// REST /graph — knowledge graph query (same operations as hicortex_graph MCP).
|
|
790
800
|
//
|
|
791
|
-
// Supported ops: neighbors, hubs, path
|
|
801
|
+
// Supported ops: neighbors, hubs, path, export
|
|
792
802
|
// GET /graph?op=neighbors&id=<id>&limit=10&relationship=<rel>
|
|
793
803
|
// GET /graph?op=hubs&limit=10&domain=<domain>
|
|
794
804
|
// GET /graph?op=path&id=<from>&target_id=<to>
|
|
805
|
+
// GET /graph?op=export&domain=&type=&tag=&minStrength=&limit= (#124: /viz data;
|
|
806
|
+
// tag= filters to nodes CARRYING the tag at any weight — graded-schema spec)
|
|
795
807
|
//
|
|
796
|
-
// NOTE
|
|
797
|
-
//
|
|
798
|
-
// clean ({results} for
|
|
799
|
-
//
|
|
808
|
+
// NOTE (#124): this endpoint is the JSON surface consumed by /viz — op=export
|
|
809
|
+
// returns the full {nodes, edges, domains, types, meta} payload the page
|
|
810
|
+
// renders. The response shapes are intentionally clean ({results} for
|
|
811
|
+
// neighbors/path, {hubs} for hubs) — do not add MCP-style text formatting.
|
|
800
812
|
// -------------------------------------------------------------------------
|
|
801
813
|
app.get("/graph", (req, res) => {
|
|
802
814
|
if (!db) {
|
|
@@ -804,13 +816,15 @@ async function startServer(options = {}) {
|
|
|
804
816
|
return;
|
|
805
817
|
}
|
|
806
818
|
const op = typeof req.query.op === "string" ? req.query.op : "";
|
|
807
|
-
const VALID_OPS = ["neighbors", "hubs", "path"];
|
|
819
|
+
const VALID_OPS = ["neighbors", "hubs", "path", "export"];
|
|
808
820
|
if (!VALID_OPS.includes(op)) {
|
|
809
821
|
res.status(400).json({ error: `Invalid op: must be one of ${VALID_OPS.join(", ")}` });
|
|
810
822
|
return;
|
|
811
823
|
}
|
|
812
824
|
const rawLimit = req.query.limit ? Number(req.query.limit) : undefined;
|
|
813
|
-
|
|
825
|
+
// Floor + minimum 1: negative/fractional values would otherwise reach SQL
|
|
826
|
+
// LIMIT (negative = unlimited in SQLite; fractional = binding error).
|
|
827
|
+
const resultLimit = rawLimit && Number.isFinite(rawLimit) && rawLimit >= 1 ? Math.floor(rawLimit) : 10;
|
|
814
828
|
const filterDomain = typeof req.query.domain === "string" && req.query.domain ? req.query.domain : undefined;
|
|
815
829
|
const filterRelationship = typeof req.query.relationship === "string" && req.query.relationship ? req.query.relationship : undefined;
|
|
816
830
|
try {
|
|
@@ -854,11 +868,54 @@ async function startServer(options = {}) {
|
|
|
854
868
|
res.json({ path: path ?? null });
|
|
855
869
|
return;
|
|
856
870
|
}
|
|
871
|
+
if (op === "export") {
|
|
872
|
+
const filterType = typeof req.query.type === "string" && req.query.type ? req.query.type : undefined;
|
|
873
|
+
const filterTag = typeof req.query.tag === "string" && req.query.tag ? req.query.tag : undefined;
|
|
874
|
+
let minStrength;
|
|
875
|
+
if (req.query.minStrength !== undefined) {
|
|
876
|
+
const v = Number(req.query.minStrength);
|
|
877
|
+
if (!Number.isFinite(v) || v < 0 || v > 1) {
|
|
878
|
+
res.status(400).json({ error: "minStrength must be a number between 0 and 1" });
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
minStrength = v;
|
|
882
|
+
}
|
|
883
|
+
// Export has its own default (EXPORT_DEFAULT_LIMIT) — the shared
|
|
884
|
+
// resultLimit default of 10 is for neighbors/hubs. exportGraph clamps
|
|
885
|
+
// to EXPORT_MAX_LIMIT.
|
|
886
|
+
const exportLimit = rawLimit && Number.isFinite(rawLimit) ? rawLimit : graph_js_1.EXPORT_DEFAULT_LIMIT;
|
|
887
|
+
res.json((0, graph_js_1.exportGraph)(db, {
|
|
888
|
+
domain: filterDomain,
|
|
889
|
+
type: filterType,
|
|
890
|
+
tag: filterTag,
|
|
891
|
+
minStrength,
|
|
892
|
+
limit: exportLimit,
|
|
893
|
+
}));
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
857
896
|
}
|
|
858
897
|
catch (err) {
|
|
859
898
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
860
899
|
}
|
|
861
900
|
});
|
|
901
|
+
// -------------------------------------------------------------------------
|
|
902
|
+
// GET /viz — knowledge-graph visualization page (#124).
|
|
903
|
+
//
|
|
904
|
+
// Self-contained HTML (inline CSS/JS, zero external requests) served from
|
|
905
|
+
// assets/viz.html. Fetches /graph?op=export from its own origin. The page
|
|
906
|
+
// SHELL is public (exempted in createAuthMiddleware, like /health — it
|
|
907
|
+
// carries no data); the /graph data fetch is bearer-only. The page collects
|
|
908
|
+
// the token client-side: ?token= URL param (stripped on load) or an in-page
|
|
909
|
+
// prompt on 401, persisted in localStorage.
|
|
910
|
+
// -------------------------------------------------------------------------
|
|
911
|
+
app.get("/viz", (0, viz_js_1.vizHandler)());
|
|
912
|
+
// GET /viz/vendor/:file — pinned renderer bundles for the /viz page (#139).
|
|
913
|
+
//
|
|
914
|
+
// STRICT allowlist (VIZ_VENDOR_FILES in viz.ts): only the exact vendored
|
|
915
|
+
// filenames are served; everything else is 404. Public like the /viz shell
|
|
916
|
+
// (static third-party code from the npm tarball, no data) — the exemption
|
|
917
|
+
// lives in createAuthMiddleware next to the /viz one.
|
|
918
|
+
app.get("/viz/vendor/:file", (0, viz_js_1.vizVendorHandler)());
|
|
862
919
|
// SSE endpoint — each connection gets its own McpServer + transport
|
|
863
920
|
app.get("/sse", async (req, res) => {
|
|
864
921
|
const transport = new sse_js_1.SSEServerTransport("/messages", res);
|
|
@@ -898,6 +955,7 @@ async function startServer(options = {}) {
|
|
|
898
955
|
console.log(`[hicortex] MCP server listening on http://${host}:${port}`);
|
|
899
956
|
console.log(`[hicortex] SSE endpoint: http://${host}:${port}/sse`);
|
|
900
957
|
console.log(`[hicortex] Health: http://${host}:${port}/health`);
|
|
958
|
+
console.log(`[hicortex] Graph viz: http://${host}:${port}/viz`);
|
|
901
959
|
});
|
|
902
960
|
server.on("error", (err) => {
|
|
903
961
|
if (err.code === "EADDRINUSE") {
|
package/dist/nightly-status.js
CHANGED
|
@@ -16,41 +16,22 @@ const node_path_1 = require("node:path");
|
|
|
16
16
|
const node_os_1 = require("node:os");
|
|
17
17
|
const node_child_process_1 = require("node:child_process");
|
|
18
18
|
const db_js_1 = require("./db.js");
|
|
19
|
+
const state_js_1 = require("./state.js");
|
|
19
20
|
const HICORTEX_HOME = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex");
|
|
20
|
-
const LAST_RUN_PATH = (0, node_path_1.join)(HICORTEX_HOME, "nightly-last-run.txt");
|
|
21
21
|
const CONFIG_PATH = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
|
|
22
|
-
const STALE_THRESHOLD_HOURS = 30;
|
|
23
22
|
async function showNightlyStatus() {
|
|
24
23
|
console.log("Hicortex Nightly Pipeline Status");
|
|
25
24
|
console.log("─".repeat(40));
|
|
26
25
|
// Last run
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const ts = (0, node_fs_1.readFileSync)(LAST_RUN_PATH, "utf-8").trim();
|
|
31
|
-
const d = new Date(ts);
|
|
32
|
-
if (!isNaN(d.getTime())) {
|
|
33
|
-
lastRun = d;
|
|
34
|
-
lastRunStr = ts;
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
lastRunStr = `${ts} (invalid)`;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// No file
|
|
26
|
+
const lastRun = (0, state_js_1.describeLastNightly)(HICORTEX_HOME);
|
|
27
|
+
if (!lastRun) {
|
|
28
|
+
console.log("Last run: never");
|
|
42
29
|
}
|
|
43
|
-
if (lastRun) {
|
|
44
|
-
|
|
45
|
-
const ageHours = Math.round(ageMs / (60 * 60 * 1000));
|
|
46
|
-
const ageStr = ageHours < 1 ? "just now" :
|
|
47
|
-
ageHours < 24 ? `${ageHours}h ago` :
|
|
48
|
-
`${Math.round(ageHours / 24)}d ago`;
|
|
49
|
-
const isStale = ageHours > STALE_THRESHOLD_HOURS;
|
|
50
|
-
console.log(`Last run: ${lastRunStr} (${ageStr})${isStale ? " ⚠ STALE" : ""}`);
|
|
30
|
+
else if (lastRun.invalid) {
|
|
31
|
+
console.log(`Last run: ${lastRun.timestamp} (invalid)`);
|
|
51
32
|
}
|
|
52
33
|
else {
|
|
53
|
-
console.log(`Last run: ${
|
|
34
|
+
console.log(`Last run: ${lastRun.timestamp} (${lastRun.ageStr})${lastRun.stale ? " ⚠ STALE" : ""}`);
|
|
54
35
|
}
|
|
55
36
|
// LLM config
|
|
56
37
|
try {
|
|
@@ -148,8 +129,8 @@ async function showNightlyStatus() {
|
|
|
148
129
|
const issues = [];
|
|
149
130
|
if (!lastRun)
|
|
150
131
|
issues.push("Pipeline has never run. Run: hicortex nightly");
|
|
151
|
-
else if (lastRun
|
|
152
|
-
issues.push(
|
|
132
|
+
else if (lastRun.stale) {
|
|
133
|
+
issues.push("Pipeline hasn't run in 30+ hours. Check timer.");
|
|
153
134
|
}
|
|
154
135
|
if (!timerActive)
|
|
155
136
|
issues.push("No timer installed. Nightly pipeline won't run automatically.");
|
package/dist/nightly.js
CHANGED
|
@@ -60,6 +60,8 @@ const embedder_js_1 = require("./embedder.js");
|
|
|
60
60
|
const storage = __importStar(require("./storage.js"));
|
|
61
61
|
const distiller_js_1 = require("./distiller.js");
|
|
62
62
|
const consolidate_js_1 = require("./consolidate.js");
|
|
63
|
+
const domain_classify_js_1 = require("./domain-classify.js");
|
|
64
|
+
const nofit_js_1 = require("./nofit.js");
|
|
63
65
|
const transcript_reader_js_1 = require("./transcript-reader.js");
|
|
64
66
|
const hermes_transcript_reader_js_1 = require("./hermes-transcript-reader.js");
|
|
65
67
|
const pi_transcript_reader_js_1 = require("./pi-transcript-reader.js");
|
|
@@ -133,38 +135,11 @@ async function runNightly(options = {}) {
|
|
|
133
135
|
// is handled by the running daemon over /distill — no local distill LLM needed.
|
|
134
136
|
// No LLM → capture loop still runs (sessions POST to /distill, which will 503
|
|
135
137
|
// transient-fail and hold the watermark), but consolidation is skipped.
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
if (claudePath) {
|
|
140
|
-
llmConfig = (0, llm_js_1.claudeCliConfig)(claudePath);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
console.warn("[hicortex] claude-cli configured but binary not found — consolidation skipped");
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
else if (savedConfig?.llmBackend === "ollama") {
|
|
147
|
-
llmConfig = {
|
|
148
|
-
baseUrl: savedConfig.llmBaseUrl ?? "http://localhost:11434",
|
|
149
|
-
apiKey: "",
|
|
150
|
-
model: savedConfig.llmModel ?? "qwen3.5:4b",
|
|
151
|
-
reflectModel: savedConfig.reflectModel ?? savedConfig.llmModel ?? "qwen3.5:4b",
|
|
152
|
-
provider: "ollama",
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
llmConfig = (0, llm_js_1.resolveExplicitLlmConfig)({
|
|
157
|
-
llmBaseUrl: savedConfig?.llmBaseUrl,
|
|
158
|
-
llmApiKey: savedConfig?.llmApiKey,
|
|
159
|
-
llmModel: savedConfig?.llmModel,
|
|
160
|
-
reflectModel: savedConfig?.reflectModel,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
if (llmConfig && savedConfig?.reflectBaseUrl) {
|
|
164
|
-
llmConfig.reflectBaseUrl = savedConfig.reflectBaseUrl;
|
|
165
|
-
llmConfig.reflectApiKey = savedConfig.reflectApiKey ?? llmConfig.apiKey;
|
|
166
|
-
llmConfig.reflectProvider = savedConfig.reflectProvider ?? llmConfig.provider;
|
|
138
|
+
const resolved = (0, llm_js_1.resolveSavedLlmConfig)(savedConfig);
|
|
139
|
+
if (resolved.reason === "claude_binary_missing") {
|
|
140
|
+
console.warn("[hicortex] claude-cli configured but binary not found — consolidation skipped");
|
|
167
141
|
}
|
|
142
|
+
const llmConfig = resolved.config;
|
|
168
143
|
const llm = llmConfig ? new llm_js_1.LlmClient(llmConfig) : null;
|
|
169
144
|
// Step 1: Read new transcripts (CC + Hermes + Pi + OpenClaw)
|
|
170
145
|
const since = readLastRun();
|
|
@@ -278,8 +253,43 @@ async function runNightly(options = {}) {
|
|
|
278
253
|
skipReflection = true;
|
|
279
254
|
}
|
|
280
255
|
}
|
|
256
|
+
// Content-based domain classification (config-owned `domains`) uses
|
|
257
|
+
// the classify tier (classifyModel/classifyBaseUrl) when configured,
|
|
258
|
+
// else the reflect tier. Pre-flight the endpoint classification will
|
|
259
|
+
// ACTUALLY use (resolveClassifyProbeTarget is the shared source of
|
|
260
|
+
// truth with `hicortex classify-domains`). If it is down, content
|
|
261
|
+
// classification is NOT ready this run (strict — skip, don't fall
|
|
262
|
+
// back). When no `domains` list is configured, this is inert and the
|
|
263
|
+
// legacy project-grouping path runs.
|
|
264
|
+
const cfgDomains = (0, domain_classify_js_1.parseConfigDomains)(savedConfig);
|
|
265
|
+
let contentDomainsReady = true;
|
|
266
|
+
if (cfgDomains) {
|
|
267
|
+
const classifyTarget = (0, llm_js_1.resolveClassifyProbeTarget)(llmConfig);
|
|
268
|
+
if (classifyTarget?.tier === "reflect") {
|
|
269
|
+
// Classification rides the reflect endpoint — reuse the probe above.
|
|
270
|
+
contentDomainsReady = !skipReflection;
|
|
271
|
+
}
|
|
272
|
+
else if (classifyTarget) {
|
|
273
|
+
const health = await (0, llm_js_1.probeOllamaModel)(classifyTarget.baseUrl, classifyTarget.model);
|
|
274
|
+
if (!health.ok) {
|
|
275
|
+
const reason = health.reason === "unreachable"
|
|
276
|
+
? `classify endpoint unreachable (${classifyTarget.baseUrl})`
|
|
277
|
+
: `classify model not loaded (${classifyTarget.model} missing on ${classifyTarget.baseUrl})`;
|
|
278
|
+
console.warn(`[hicortex] ${reason}`);
|
|
279
|
+
contentDomainsReady = false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// classifyTarget === null → base endpoint or API provider, no probe.
|
|
283
|
+
if (!contentDomainsReady) {
|
|
284
|
+
console.warn("[hicortex] content-domain classification skipped — classification endpoint offline (strict)");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
281
287
|
console.log(`[hicortex] Running consolidation...`);
|
|
282
|
-
const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun, skipReflection
|
|
288
|
+
const report = await (0, consolidate_js_1.runConsolidation)(db, llm, embedder_js_1.embed, dryRun, skipReflection, undefined, {
|
|
289
|
+
domains: cfgDomains,
|
|
290
|
+
contentDomainsReady,
|
|
291
|
+
weakPrimaryFloor: (0, nofit_js_1.resolveWeakPrimaryFloor)(savedConfig),
|
|
292
|
+
});
|
|
283
293
|
console.log(`[hicortex] Consolidation ${report.status} in ${report.elapsed_seconds}s` +
|
|
284
294
|
(report.stages.reflection ? ` (${report.stages.reflection.lessons_generated} lessons)` : ""));
|
|
285
295
|
}
|
package/dist/nofit.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No-fit lifecycle — weak-primary floor + no-association decay
|
|
3
|
+
* (owner amendment 07.07 to specs/2026-07-07-graded-schema-memory-tags.md).
|
|
4
|
+
*
|
|
5
|
+
* THE MODEL
|
|
6
|
+
* ---------
|
|
7
|
+
* "Unsorted" is a non-tag: the vocabulary carries NO fallback category, and a
|
|
8
|
+
* genuine LLM no-fit is an EMPTY tag set (domain-classify.ts returns
|
|
9
|
+
* {tags: []}). Every stored memory should still end up with a primary if it
|
|
10
|
+
* reasonably can, so on a no-fit the pipeline derives:
|
|
11
|
+
*
|
|
12
|
+
* 1. WEAK PRIMARY — argmax cosine of the memory embedding across ALL domain
|
|
13
|
+
* prototypes (schema-prototypes.bestPrototypeMatch). If the best cosine
|
|
14
|
+
* >= `weakPrimaryFloor`, the memory is tagged with that single domain
|
|
15
|
+
* (the primary derives naturally via storage.setMemoryTags). It lives —
|
|
16
|
+
* humbly, with one weak association.
|
|
17
|
+
*
|
|
18
|
+
* 2. NO ASSOCIATION — best cosine below the floor means the memory
|
|
19
|
+
* associates with nothing the owner cares about. It is NOT tagged;
|
|
20
|
+
* instead its base_strength is HALVED (floored at
|
|
21
|
+
* NO_ASSOCIATION_MIN_STRENGTH) so the existing decay/prune stage
|
|
22
|
+
* eventually removes it. domain stays NULL, which keeps the memory in
|
|
23
|
+
* the nightly staleness scope: every subsequent run re-classifies it
|
|
24
|
+
* (prototypes evolve — it may fit later) and re-halves ONLY when it is
|
|
25
|
+
* still a no-fit below the floor.
|
|
26
|
+
*
|
|
27
|
+
* COLD-START SAFETY: prototypes are ALWAYS available before any no-fit
|
|
28
|
+
* evaluation — computeDomainPrototypes seeds every configured domain from its
|
|
29
|
+
* description embedding when it has <5 members, so a fresh install cannot
|
|
30
|
+
* produce a null prototype for a configured domain and therefore cannot
|
|
31
|
+
* mass-decay a new corpus. A missing MEMORY embedding routes to null (decay
|
|
32
|
+
* candidate), never to a false weak-primary.
|
|
33
|
+
*
|
|
34
|
+
* RESCUE PATHS (survivability):
|
|
35
|
+
* - Access: storage.strengthenMemory bumps access_count — and
|
|
36
|
+
* stageDecayPrune only ever considers access_count = 0 memories
|
|
37
|
+
* (storage.getPruneCandidates), so a single recall permanently shields a
|
|
38
|
+
* memory from pruning. Halving does not touch last_accessed/access_count.
|
|
39
|
+
* - Re-classification: a later run whose LLM tags it, or whose evolved
|
|
40
|
+
* prototypes clear the floor, gives it a (weak) primary — halving stops.
|
|
41
|
+
* Its strength is NOT restored; only access does that job.
|
|
42
|
+
*
|
|
43
|
+
* PRUNE INTERACTION (verified against stageDecayPrune + effectiveStrength):
|
|
44
|
+
* prune fires when effectiveStrength < 0.01 for a >90-day-old, never-accessed
|
|
45
|
+
* memory. effectiveStrength has an asymptotic floor of base_strength² × 0.1,
|
|
46
|
+
* so at the default 0.5 a memory can NEVER prune (floor 0.025 > 0.01) —
|
|
47
|
+
* halving is what makes pruning reachable at all. From 0.5: four nightly
|
|
48
|
+
* halvings reach the 0.05 strength floor (0.25 → 0.125 → 0.0625 → 0.05); at
|
|
49
|
+
* 0.05 the decay curve crosses 0.01 roughly 143 days after last access.
|
|
50
|
+
*/
|
|
51
|
+
import type Database from "better-sqlite3";
|
|
52
|
+
import type { DomainDef } from "./types.js";
|
|
53
|
+
/**
|
|
54
|
+
* Default weak-primary floor: minimum cosine(memory embedding, best domain
|
|
55
|
+
* prototype) for a no-fit memory to earn a weak primary.
|
|
56
|
+
*
|
|
57
|
+
* TUNING: 0.45 is a starting point for bge-small-en-v1.5 embeddings — it
|
|
58
|
+
* should be tuned from the actual corpus weight distribution (e.g. inspect
|
|
59
|
+
* the memory_tags.weight histogram of LLM-tagged rows and set the floor
|
|
60
|
+
* near its lower tail). Override per install via `weakPrimaryFloor` in
|
|
61
|
+
* ~/.hicortex/config.json.
|
|
62
|
+
*/
|
|
63
|
+
export declare const DEFAULT_WEAK_PRIMARY_FLOOR = 0.45;
|
|
64
|
+
/** Halving never takes base_strength below this (survivable, not zeroed). */
|
|
65
|
+
export declare const NO_ASSOCIATION_MIN_STRENGTH = 0.05;
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the weak-primary floor from a raw config object. Accepts a finite
|
|
68
|
+
* number in (0, 1); anything else (absent, wrong type, out of range) falls
|
|
69
|
+
* back to DEFAULT_WEAK_PRIMARY_FLOOR with a warning for invalid values.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveWeakPrimaryFloor(config: Record<string, unknown> | null | undefined): number;
|
|
72
|
+
/** Outcome of resolving a no-fit memory against the domain prototypes. */
|
|
73
|
+
export type NoFitResolution = {
|
|
74
|
+
kind: "weak_primary";
|
|
75
|
+
domain: string;
|
|
76
|
+
weight: number;
|
|
77
|
+
} | {
|
|
78
|
+
kind: "no_association";
|
|
79
|
+
bestWeight: number | null;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Decide what happens to a no-fit memory (READ-ONLY — no writes, so callers
|
|
83
|
+
* batching writes into a transaction can decide during the scan phase):
|
|
84
|
+
* best prototype cosine >= floor → weak_primary; below the floor (or no
|
|
85
|
+
* embedding / no prototypes at all) → no_association.
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveNoFit(db: Database.Database, memoryId: string, domains: DomainDef[], prototypes: Map<string, Float32Array>, floor: number): NoFitResolution;
|
|
88
|
+
/**
|
|
89
|
+
* Apply a weak primary: tag the memory with the single argmax domain (the
|
|
90
|
+
* primary derives naturally inside setMemoryTags). Logged distinctly so
|
|
91
|
+
* weak primaries are auditable apart from LLM-tagged rows.
|
|
92
|
+
*/
|
|
93
|
+
export declare function applyWeakPrimary(db: Database.Database, memoryId: string, domain: string, weight: number, compartments: Set<string>): void;
|
|
94
|
+
/**
|
|
95
|
+
* Apply no-association decay to a no-fit-below-floor memory:
|
|
96
|
+
* - clear any leftover memory_tags rows (e.g. a legacy "Unsorted" tag from
|
|
97
|
+
* a domain since removed from the config) so refreshPrimaries cannot
|
|
98
|
+
* resurrect a primary from them,
|
|
99
|
+
* - set domain to NULL (keeps the memory in the nightly staleness scope —
|
|
100
|
+
* re-attempted every run as prototypes evolve),
|
|
101
|
+
* - HALVE base_strength, floored at NO_ASSOCIATION_MIN_STRENGTH.
|
|
102
|
+
*
|
|
103
|
+
* Does NOT touch last_accessed / access_count — access-strengthening remains
|
|
104
|
+
* the rescue path (prune only ever considers access_count = 0 memories).
|
|
105
|
+
* Called at most once per memory per run (each pipeline pass visits a memory
|
|
106
|
+
* exactly once), so a single run never double-halves.
|
|
107
|
+
*/
|
|
108
|
+
export declare function applyNoAssociationDecay(db: Database.Database, memoryId: string): {
|
|
109
|
+
previous: number;
|
|
110
|
+
next: number;
|
|
111
|
+
};
|
package/dist/nofit.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* No-fit lifecycle — weak-primary floor + no-association decay
|
|
4
|
+
* (owner amendment 07.07 to specs/2026-07-07-graded-schema-memory-tags.md).
|
|
5
|
+
*
|
|
6
|
+
* THE MODEL
|
|
7
|
+
* ---------
|
|
8
|
+
* "Unsorted" is a non-tag: the vocabulary carries NO fallback category, and a
|
|
9
|
+
* genuine LLM no-fit is an EMPTY tag set (domain-classify.ts returns
|
|
10
|
+
* {tags: []}). Every stored memory should still end up with a primary if it
|
|
11
|
+
* reasonably can, so on a no-fit the pipeline derives:
|
|
12
|
+
*
|
|
13
|
+
* 1. WEAK PRIMARY — argmax cosine of the memory embedding across ALL domain
|
|
14
|
+
* prototypes (schema-prototypes.bestPrototypeMatch). If the best cosine
|
|
15
|
+
* >= `weakPrimaryFloor`, the memory is tagged with that single domain
|
|
16
|
+
* (the primary derives naturally via storage.setMemoryTags). It lives —
|
|
17
|
+
* humbly, with one weak association.
|
|
18
|
+
*
|
|
19
|
+
* 2. NO ASSOCIATION — best cosine below the floor means the memory
|
|
20
|
+
* associates with nothing the owner cares about. It is NOT tagged;
|
|
21
|
+
* instead its base_strength is HALVED (floored at
|
|
22
|
+
* NO_ASSOCIATION_MIN_STRENGTH) so the existing decay/prune stage
|
|
23
|
+
* eventually removes it. domain stays NULL, which keeps the memory in
|
|
24
|
+
* the nightly staleness scope: every subsequent run re-classifies it
|
|
25
|
+
* (prototypes evolve — it may fit later) and re-halves ONLY when it is
|
|
26
|
+
* still a no-fit below the floor.
|
|
27
|
+
*
|
|
28
|
+
* COLD-START SAFETY: prototypes are ALWAYS available before any no-fit
|
|
29
|
+
* evaluation — computeDomainPrototypes seeds every configured domain from its
|
|
30
|
+
* description embedding when it has <5 members, so a fresh install cannot
|
|
31
|
+
* produce a null prototype for a configured domain and therefore cannot
|
|
32
|
+
* mass-decay a new corpus. A missing MEMORY embedding routes to null (decay
|
|
33
|
+
* candidate), never to a false weak-primary.
|
|
34
|
+
*
|
|
35
|
+
* RESCUE PATHS (survivability):
|
|
36
|
+
* - Access: storage.strengthenMemory bumps access_count — and
|
|
37
|
+
* stageDecayPrune only ever considers access_count = 0 memories
|
|
38
|
+
* (storage.getPruneCandidates), so a single recall permanently shields a
|
|
39
|
+
* memory from pruning. Halving does not touch last_accessed/access_count.
|
|
40
|
+
* - Re-classification: a later run whose LLM tags it, or whose evolved
|
|
41
|
+
* prototypes clear the floor, gives it a (weak) primary — halving stops.
|
|
42
|
+
* Its strength is NOT restored; only access does that job.
|
|
43
|
+
*
|
|
44
|
+
* PRUNE INTERACTION (verified against stageDecayPrune + effectiveStrength):
|
|
45
|
+
* prune fires when effectiveStrength < 0.01 for a >90-day-old, never-accessed
|
|
46
|
+
* memory. effectiveStrength has an asymptotic floor of base_strength² × 0.1,
|
|
47
|
+
* so at the default 0.5 a memory can NEVER prune (floor 0.025 > 0.01) —
|
|
48
|
+
* halving is what makes pruning reachable at all. From 0.5: four nightly
|
|
49
|
+
* halvings reach the 0.05 strength floor (0.25 → 0.125 → 0.0625 → 0.05); at
|
|
50
|
+
* 0.05 the decay curve crosses 0.01 roughly 143 days after last access.
|
|
51
|
+
*/
|
|
52
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
53
|
+
if (k2 === undefined) k2 = k;
|
|
54
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
55
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
56
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
57
|
+
}
|
|
58
|
+
Object.defineProperty(o, k2, desc);
|
|
59
|
+
}) : (function(o, m, k, k2) {
|
|
60
|
+
if (k2 === undefined) k2 = k;
|
|
61
|
+
o[k2] = m[k];
|
|
62
|
+
}));
|
|
63
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
64
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
65
|
+
}) : function(o, v) {
|
|
66
|
+
o["default"] = v;
|
|
67
|
+
});
|
|
68
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
69
|
+
var ownKeys = function(o) {
|
|
70
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
71
|
+
var ar = [];
|
|
72
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
73
|
+
return ar;
|
|
74
|
+
};
|
|
75
|
+
return ownKeys(o);
|
|
76
|
+
};
|
|
77
|
+
return function (mod) {
|
|
78
|
+
if (mod && mod.__esModule) return mod;
|
|
79
|
+
var result = {};
|
|
80
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
81
|
+
__setModuleDefault(result, mod);
|
|
82
|
+
return result;
|
|
83
|
+
};
|
|
84
|
+
})();
|
|
85
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
86
|
+
exports.NO_ASSOCIATION_MIN_STRENGTH = exports.DEFAULT_WEAK_PRIMARY_FLOOR = void 0;
|
|
87
|
+
exports.resolveWeakPrimaryFloor = resolveWeakPrimaryFloor;
|
|
88
|
+
exports.resolveNoFit = resolveNoFit;
|
|
89
|
+
exports.applyWeakPrimary = applyWeakPrimary;
|
|
90
|
+
exports.applyNoAssociationDecay = applyNoAssociationDecay;
|
|
91
|
+
const storage = __importStar(require("./storage.js"));
|
|
92
|
+
const schema_prototypes_js_1 = require("./schema-prototypes.js");
|
|
93
|
+
/**
|
|
94
|
+
* Default weak-primary floor: minimum cosine(memory embedding, best domain
|
|
95
|
+
* prototype) for a no-fit memory to earn a weak primary.
|
|
96
|
+
*
|
|
97
|
+
* TUNING: 0.45 is a starting point for bge-small-en-v1.5 embeddings — it
|
|
98
|
+
* should be tuned from the actual corpus weight distribution (e.g. inspect
|
|
99
|
+
* the memory_tags.weight histogram of LLM-tagged rows and set the floor
|
|
100
|
+
* near its lower tail). Override per install via `weakPrimaryFloor` in
|
|
101
|
+
* ~/.hicortex/config.json.
|
|
102
|
+
*/
|
|
103
|
+
exports.DEFAULT_WEAK_PRIMARY_FLOOR = 0.45;
|
|
104
|
+
/** Halving never takes base_strength below this (survivable, not zeroed). */
|
|
105
|
+
exports.NO_ASSOCIATION_MIN_STRENGTH = 0.05;
|
|
106
|
+
/**
|
|
107
|
+
* Resolve the weak-primary floor from a raw config object. Accepts a finite
|
|
108
|
+
* number in (0, 1); anything else (absent, wrong type, out of range) falls
|
|
109
|
+
* back to DEFAULT_WEAK_PRIMARY_FLOOR with a warning for invalid values.
|
|
110
|
+
*/
|
|
111
|
+
function resolveWeakPrimaryFloor(config) {
|
|
112
|
+
const raw = config?.weakPrimaryFloor;
|
|
113
|
+
if (raw === undefined || raw === null)
|
|
114
|
+
return exports.DEFAULT_WEAK_PRIMARY_FLOOR;
|
|
115
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw < 1) {
|
|
116
|
+
return raw;
|
|
117
|
+
}
|
|
118
|
+
console.warn(`[hicortex] invalid weakPrimaryFloor in config (${JSON.stringify(raw)}) — ` +
|
|
119
|
+
`must be a number in (0, 1); using default ${exports.DEFAULT_WEAK_PRIMARY_FLOOR}`);
|
|
120
|
+
return exports.DEFAULT_WEAK_PRIMARY_FLOOR;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Decide what happens to a no-fit memory (READ-ONLY — no writes, so callers
|
|
124
|
+
* batching writes into a transaction can decide during the scan phase):
|
|
125
|
+
* best prototype cosine >= floor → weak_primary; below the floor (or no
|
|
126
|
+
* embedding / no prototypes at all) → no_association.
|
|
127
|
+
*/
|
|
128
|
+
function resolveNoFit(db, memoryId, domains, prototypes, floor) {
|
|
129
|
+
const best = (0, schema_prototypes_js_1.bestPrototypeMatch)(db, memoryId, domains, prototypes);
|
|
130
|
+
if (best && best.weight >= floor) {
|
|
131
|
+
return { kind: "weak_primary", domain: best.domain, weight: best.weight };
|
|
132
|
+
}
|
|
133
|
+
return { kind: "no_association", bestWeight: best?.weight ?? null };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Apply a weak primary: tag the memory with the single argmax domain (the
|
|
137
|
+
* primary derives naturally inside setMemoryTags). Logged distinctly so
|
|
138
|
+
* weak primaries are auditable apart from LLM-tagged rows.
|
|
139
|
+
*/
|
|
140
|
+
function applyWeakPrimary(db, memoryId, domain, weight, compartments) {
|
|
141
|
+
storage.setMemoryTags(db, memoryId, [domain], {
|
|
142
|
+
weights: { [domain]: weight },
|
|
143
|
+
compartments,
|
|
144
|
+
});
|
|
145
|
+
console.log(`[hicortex] weak-primary ${domain} w=${weight.toFixed(2)} for ${memoryId}`);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Apply no-association decay to a no-fit-below-floor memory:
|
|
149
|
+
* - clear any leftover memory_tags rows (e.g. a legacy "Unsorted" tag from
|
|
150
|
+
* a domain since removed from the config) so refreshPrimaries cannot
|
|
151
|
+
* resurrect a primary from them,
|
|
152
|
+
* - set domain to NULL (keeps the memory in the nightly staleness scope —
|
|
153
|
+
* re-attempted every run as prototypes evolve),
|
|
154
|
+
* - HALVE base_strength, floored at NO_ASSOCIATION_MIN_STRENGTH.
|
|
155
|
+
*
|
|
156
|
+
* Does NOT touch last_accessed / access_count — access-strengthening remains
|
|
157
|
+
* the rescue path (prune only ever considers access_count = 0 memories).
|
|
158
|
+
* Called at most once per memory per run (each pipeline pass visits a memory
|
|
159
|
+
* exactly once), so a single run never double-halves.
|
|
160
|
+
*/
|
|
161
|
+
function applyNoAssociationDecay(db, memoryId) {
|
|
162
|
+
const mem = storage.getMemory(db, memoryId);
|
|
163
|
+
if (!mem) {
|
|
164
|
+
throw new Error(`applyNoAssociationDecay: memory not found: ${memoryId}`);
|
|
165
|
+
}
|
|
166
|
+
const previous = mem.base_strength ?? 0.5;
|
|
167
|
+
const next = Math.max(exports.NO_ASSOCIATION_MIN_STRENGTH, previous / 2);
|
|
168
|
+
const tx = db.transaction(() => {
|
|
169
|
+
db.prepare("DELETE FROM memory_tags WHERE memory_id = ?").run(memoryId);
|
|
170
|
+
storage.updateMemory(db, memoryId, { base_strength: next, domain: null });
|
|
171
|
+
});
|
|
172
|
+
tx();
|
|
173
|
+
console.log(`[hicortex] no-association decay ${memoryId}: base_strength ` +
|
|
174
|
+
`${previous.toFixed(3)} → ${next.toFixed(3)} (below weak-primary floor)`);
|
|
175
|
+
return { previous, next };
|
|
176
|
+
}
|
package/dist/prompts.d.ts
CHANGED
|
@@ -19,8 +19,3 @@ export declare function distillation(projectName: string, date: string, transcri
|
|
|
19
19
|
* Used during consolidation (Pro only, one call per nightly when projects change).
|
|
20
20
|
*/
|
|
21
21
|
export declare function domainCuration(projectLines: string): string;
|
|
22
|
-
/**
|
|
23
|
-
* Edge classification prompt. Presents memory pairs and asks the LLM to
|
|
24
|
-
* choose the most specific relationship type for each.
|
|
25
|
-
*/
|
|
26
|
-
export declare function edgeClassification(pairsBlock: string): string;
|