@gamaze/hicortex 0.4.2 → 0.4.3
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/dist/cli.d.ts +1 -0
- package/dist/cli.js +23 -9
- package/dist/distiller.d.ts +4 -2
- package/dist/distiller.js +31 -10
- package/dist/mcp-server.js +34 -0
- package/dist/nightly-status.d.ts +11 -0
- package/dist/nightly-status.js +167 -0
- package/dist/nightly.js +84 -0
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* server Start the MCP HTTP/SSE server (persistent daemon)
|
|
7
7
|
* init Detect existing setup and configure for CC/OC
|
|
8
8
|
* nightly Run distill + consolidate + inject lessons (manual trigger)
|
|
9
|
+
* nightly --status Show nightly pipeline health check
|
|
9
10
|
* status Show config, DB stats, adapter status
|
|
10
11
|
* uninstall Clean removal of CC integration
|
|
11
12
|
*/
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* server Start the MCP HTTP/SSE server (persistent daemon)
|
|
8
8
|
* init Detect existing setup and configure for CC/OC
|
|
9
9
|
* nightly Run distill + consolidate + inject lessons (manual trigger)
|
|
10
|
+
* nightly --status Show nightly pipeline health check
|
|
10
11
|
* status Show config, DB stats, adapter status
|
|
11
12
|
* uninstall Clean removal of CC integration
|
|
12
13
|
*/
|
|
@@ -38,13 +39,24 @@ switch (command) {
|
|
|
38
39
|
break;
|
|
39
40
|
}
|
|
40
41
|
case "nightly": {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const args = process.argv.slice(3);
|
|
43
|
+
if (args.includes("--status")) {
|
|
44
|
+
import("./nightly-status.js").then(({ showNightlyStatus }) => {
|
|
45
|
+
showNightlyStatus().catch((err) => {
|
|
46
|
+
console.error("[hicortex] Status check failed:", err);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|
|
46
49
|
});
|
|
47
|
-
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const dryRun = args.includes("--dry-run");
|
|
53
|
+
import("./nightly.js").then(({ runNightly }) => {
|
|
54
|
+
runNightly({ dryRun }).catch((err) => {
|
|
55
|
+
console.error("[hicortex] Nightly pipeline failed:", err);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
48
60
|
break;
|
|
49
61
|
}
|
|
50
62
|
case "status":
|
|
@@ -77,13 +89,15 @@ Commands:
|
|
|
77
89
|
uninstall Remove CC integration (preserves DB)
|
|
78
90
|
|
|
79
91
|
Options:
|
|
80
|
-
server --port <n>
|
|
81
|
-
server --host <h>
|
|
82
|
-
nightly --dry-run
|
|
92
|
+
server --port <n> Port (default: 8787)
|
|
93
|
+
server --host <h> Host (default: 127.0.0.1)
|
|
94
|
+
nightly --dry-run Preview without changes
|
|
95
|
+
nightly --status Show nightly pipeline health
|
|
83
96
|
|
|
84
97
|
Examples:
|
|
85
98
|
npx @gamaze/hicortex server
|
|
86
99
|
npx @gamaze/hicortex init
|
|
100
|
+
npx @gamaze/hicortex nightly --status
|
|
87
101
|
npx @gamaze/hicortex init --server https://myserver.example.com
|
|
88
102
|
npx @gamaze/hicortex status`);
|
|
89
103
|
process.exit(command ? 1 : 0);
|
package/dist/distiller.d.ts
CHANGED
|
@@ -7,8 +7,10 @@ import type { LlmClient } from "./llm.js";
|
|
|
7
7
|
/**
|
|
8
8
|
* Estimate a safe chunk size in chars based on the LLM provider and model.
|
|
9
9
|
* - API providers (Anthropic, OpenAI, claude-cli): no chunking needed (large context windows)
|
|
10
|
-
* - Ollama: query /api/show for context_length
|
|
11
|
-
* -
|
|
10
|
+
* - Ollama: query /api/show for context_length AND parameter_count, cap based on both
|
|
11
|
+
* - Small models (<8B params): max 20K chars (~5K tokens) — keeps CPU inference under ~60s
|
|
12
|
+
* - Larger models: up to 60K chars (~15K tokens)
|
|
13
|
+
* - Fallback: 20K chars
|
|
12
14
|
*/
|
|
13
15
|
export declare function detectChunkSize(provider: string, model: string, baseUrl?: string): Promise<number>;
|
|
14
16
|
/**
|
package/dist/distiller.js
CHANGED
|
@@ -11,11 +11,18 @@ exports.distillSession = distillSession;
|
|
|
11
11
|
const prompts_js_1 = require("./prompts.js");
|
|
12
12
|
const MAX_TRANSCRIPT_CHARS = 80_000;
|
|
13
13
|
const MIN_CONVERSATION_CHARS = 200;
|
|
14
|
+
// Chunk size limits by model parameter count (for local/CPU inference)
|
|
15
|
+
// Small models are slow on CPU — cap input size to keep inference under ~60s
|
|
16
|
+
const SMALL_MODEL_PARAMS = 8_000_000_000; // 8B — threshold for "small"
|
|
17
|
+
const SMALL_MODEL_MAX_CHUNK_CHARS = 20_000; // ~5K tokens — safe for 4-8B on CPU
|
|
18
|
+
const LARGE_MODEL_MAX_CHUNK_CHARS = 60_000; // ~15K tokens — ok for 8B+ on GPU or API
|
|
14
19
|
/**
|
|
15
20
|
* Estimate a safe chunk size in chars based on the LLM provider and model.
|
|
16
21
|
* - API providers (Anthropic, OpenAI, claude-cli): no chunking needed (large context windows)
|
|
17
|
-
* - Ollama: query /api/show for context_length
|
|
18
|
-
* -
|
|
22
|
+
* - Ollama: query /api/show for context_length AND parameter_count, cap based on both
|
|
23
|
+
* - Small models (<8B params): max 20K chars (~5K tokens) — keeps CPU inference under ~60s
|
|
24
|
+
* - Larger models: up to 60K chars (~15K tokens)
|
|
25
|
+
* - Fallback: 20K chars
|
|
19
26
|
*/
|
|
20
27
|
async function detectChunkSize(provider, model, baseUrl) {
|
|
21
28
|
// API-based providers handle large contexts natively — no chunking needed
|
|
@@ -33,16 +40,30 @@ async function detectChunkSize(provider, model, baseUrl) {
|
|
|
33
40
|
});
|
|
34
41
|
if (resp.ok) {
|
|
35
42
|
const data = await resp.json();
|
|
36
|
-
// Try to extract context length from model_info
|
|
37
43
|
const info = data.model_info ?? {};
|
|
44
|
+
// Extract parameter count for speed-aware capping
|
|
45
|
+
const paramKey = Object.keys(info).find((k) => k.endsWith("parameter_count"));
|
|
46
|
+
const paramCount = paramKey && typeof info[paramKey] === "number"
|
|
47
|
+
? info[paramKey]
|
|
48
|
+
: 0;
|
|
49
|
+
const isSmallModel = paramCount > 0 && paramCount < SMALL_MODEL_PARAMS;
|
|
50
|
+
// Extract context length for context-aware capping
|
|
38
51
|
const ctxKey = Object.keys(info).find((k) => k.endsWith("context_length") || k.endsWith("context_window"));
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
const contextTokens = ctxKey && typeof info[ctxKey] === "number"
|
|
53
|
+
? info[ctxKey]
|
|
54
|
+
: 0;
|
|
55
|
+
// Determine max chunk size based on model size (speed constraint)
|
|
56
|
+
// Unknown param count defaults to conservative (small model) — safe for any hardware
|
|
57
|
+
const maxBySpeed = !isSmallModel && paramCount > 0 ? LARGE_MODEL_MAX_CHUNK_CHARS : SMALL_MODEL_MAX_CHUNK_CHARS;
|
|
58
|
+
// Determine max chunk size based on context window (fits-in-context constraint)
|
|
59
|
+
const maxByContext = contextTokens > 0
|
|
60
|
+
? Math.floor(contextTokens * 0.6 * 4) // 60% of context, ~4 chars/token
|
|
61
|
+
: MAX_TRANSCRIPT_CHARS;
|
|
62
|
+
const chunkChars = Math.min(maxBySpeed, maxByContext);
|
|
63
|
+
console.log(`[hicortex] Model: ${paramCount > 0 ? `${(paramCount / 1e9).toFixed(1)}B params` : "unknown size"}, ` +
|
|
64
|
+
`context: ${contextTokens > 0 ? `${contextTokens} tokens` : "unknown"}, ` +
|
|
65
|
+
`chunk size: ${chunkChars} chars${isSmallModel ? " (small model cap)" : ""}`);
|
|
66
|
+
return chunkChars;
|
|
46
67
|
}
|
|
47
68
|
}
|
|
48
69
|
catch {
|
package/dist/mcp-server.js
CHANGED
|
@@ -368,6 +368,40 @@ async function startServer(options = {}) {
|
|
|
368
368
|
llm: `${llmConfig.provider}/${llmConfig.model}`,
|
|
369
369
|
});
|
|
370
370
|
});
|
|
371
|
+
// REST /lessons — return lessons + memory index for client CLAUDE.md injection
|
|
372
|
+
app.get("/lessons", (_req, res) => {
|
|
373
|
+
if (!db) {
|
|
374
|
+
res.status(503).json({ error: "Server not initialized" });
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const lessons = storage.getLessons(db, 30);
|
|
379
|
+
const totalCount = storage.countMemories(db);
|
|
380
|
+
// Project index
|
|
381
|
+
const projects = db
|
|
382
|
+
.prepare("SELECT project, COUNT(*) as cnt FROM memories WHERE project IS NOT NULL GROUP BY project ORDER BY cnt DESC LIMIT 10")
|
|
383
|
+
.all();
|
|
384
|
+
const sourceCount = db.prepare("SELECT COUNT(DISTINCT source_agent) as cnt FROM memories").get().cnt;
|
|
385
|
+
const lessonCount = lessons.length;
|
|
386
|
+
res.json({
|
|
387
|
+
lessons: lessons.map(l => ({
|
|
388
|
+
content: l.content,
|
|
389
|
+
created_at: l.created_at,
|
|
390
|
+
base_strength: l.base_strength,
|
|
391
|
+
access_count: l.access_count,
|
|
392
|
+
})),
|
|
393
|
+
index: {
|
|
394
|
+
total: totalCount,
|
|
395
|
+
lessonCount,
|
|
396
|
+
sourceCount,
|
|
397
|
+
projects: projects.map(p => ({ name: p.project, count: p.cnt })),
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
403
|
+
}
|
|
404
|
+
});
|
|
371
405
|
// REST /ingest — accept pre-distilled memories from remote clients
|
|
372
406
|
app.post("/ingest", async (req, res) => {
|
|
373
407
|
if (!db) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nightly pipeline status — lightweight check without running the pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Shows:
|
|
5
|
+
* - Last run timestamp + age
|
|
6
|
+
* - Timer/schedule status (systemd/launchd)
|
|
7
|
+
* - DB memory count
|
|
8
|
+
* - Distillation source breakdown
|
|
9
|
+
* - Staleness warnings
|
|
10
|
+
*/
|
|
11
|
+
export declare function showNightlyStatus(): Promise<void>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Nightly pipeline status — lightweight check without running the pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Shows:
|
|
6
|
+
* - Last run timestamp + age
|
|
7
|
+
* - Timer/schedule status (systemd/launchd)
|
|
8
|
+
* - DB memory count
|
|
9
|
+
* - Distillation source breakdown
|
|
10
|
+
* - Staleness warnings
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.showNightlyStatus = showNightlyStatus;
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
16
|
+
const node_os_1 = require("node:os");
|
|
17
|
+
const node_child_process_1 = require("node:child_process");
|
|
18
|
+
const db_js_1 = require("./db.js");
|
|
19
|
+
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
|
+
const CONFIG_PATH = (0, node_path_1.join)(HICORTEX_HOME, "config.json");
|
|
22
|
+
const STALE_THRESHOLD_HOURS = 30;
|
|
23
|
+
async function showNightlyStatus() {
|
|
24
|
+
console.log("Hicortex Nightly Pipeline Status");
|
|
25
|
+
console.log("─".repeat(40));
|
|
26
|
+
// Last run
|
|
27
|
+
let lastRun = null;
|
|
28
|
+
let lastRunStr = "never";
|
|
29
|
+
try {
|
|
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
|
|
42
|
+
}
|
|
43
|
+
if (lastRun) {
|
|
44
|
+
const ageMs = Date.now() - lastRun.getTime();
|
|
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" : ""}`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(`Last run: ${lastRunStr}`);
|
|
54
|
+
}
|
|
55
|
+
// LLM config
|
|
56
|
+
try {
|
|
57
|
+
const config = JSON.parse((0, node_fs_1.readFileSync)(CONFIG_PATH, "utf-8"));
|
|
58
|
+
const backend = config.llmBackend ?? "auto-detect";
|
|
59
|
+
const model = config.llmModel ?? "default";
|
|
60
|
+
const mode = config.mode === "client" ? "client → " + (config.serverUrl ?? "?") : "server (local)";
|
|
61
|
+
console.log(`Mode: ${mode}`);
|
|
62
|
+
console.log(`LLM backend: ${backend}${backend !== "auto-detect" ? ` (${model})` : ""}`);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
console.log("Config: not configured (run: hicortex init)");
|
|
66
|
+
}
|
|
67
|
+
// Timer/schedule
|
|
68
|
+
const os = (0, node_os_1.platform)();
|
|
69
|
+
let timerActive = false;
|
|
70
|
+
let timerInfo = "not installed";
|
|
71
|
+
if (os === "darwin") {
|
|
72
|
+
try {
|
|
73
|
+
const out = (0, node_child_process_1.execSync)("launchctl list 2>/dev/null | grep hicortex-nightly", {
|
|
74
|
+
encoding: "utf-8",
|
|
75
|
+
timeout: 3000,
|
|
76
|
+
});
|
|
77
|
+
if (out.trim()) {
|
|
78
|
+
timerActive = true;
|
|
79
|
+
timerInfo = "launchd (loaded)";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch { /* not installed */ }
|
|
83
|
+
}
|
|
84
|
+
else if (os === "linux") {
|
|
85
|
+
try {
|
|
86
|
+
const active = (0, node_child_process_1.execSync)("systemctl --user is-active hicortex-nightly.timer 2>/dev/null", {
|
|
87
|
+
encoding: "utf-8",
|
|
88
|
+
timeout: 3000,
|
|
89
|
+
}).trim();
|
|
90
|
+
if (active === "active" || active === "waiting") {
|
|
91
|
+
timerActive = true;
|
|
92
|
+
try {
|
|
93
|
+
const next = (0, node_child_process_1.execSync)("systemctl --user show hicortex-nightly.timer --property=NextElapseUSecRealtime 2>/dev/null", {
|
|
94
|
+
encoding: "utf-8",
|
|
95
|
+
timeout: 3000,
|
|
96
|
+
}).trim();
|
|
97
|
+
const match = next.match(/=(\d+)/);
|
|
98
|
+
if (match) {
|
|
99
|
+
const nextDate = new Date(Number(match[1]) / 1000);
|
|
100
|
+
timerInfo = `systemd (active, next: ${nextDate.toISOString()})`;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
timerInfo = `systemd (${active})`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
timerInfo = `systemd (${active})`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch { /* not installed */ }
|
|
112
|
+
}
|
|
113
|
+
console.log(`Timer: ${timerInfo}${!timerActive ? " ⚠ Pipeline will NOT run automatically" : ""}`);
|
|
114
|
+
// DB stats
|
|
115
|
+
const dbPath = (0, db_js_1.resolveDbPath)();
|
|
116
|
+
if ((0, node_fs_1.existsSync)(dbPath)) {
|
|
117
|
+
try {
|
|
118
|
+
const { initDb } = await import("./db.js");
|
|
119
|
+
const db = initDb(dbPath);
|
|
120
|
+
const count = db.prepare("SELECT COUNT(*) as c FROM memories").get().c;
|
|
121
|
+
let linkCount = 0;
|
|
122
|
+
try {
|
|
123
|
+
linkCount = db.prepare("SELECT COUNT(*) as c FROM memory_links").get().c;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// memory_links table may not exist in older DBs
|
|
127
|
+
}
|
|
128
|
+
// Source breakdown (top 5)
|
|
129
|
+
const sources = db.prepare("SELECT source_agent, COUNT(*) as cnt FROM memories GROUP BY source_agent ORDER BY cnt DESC LIMIT 5").all();
|
|
130
|
+
console.log(`\nMemories: ${count} (${linkCount} links)`);
|
|
131
|
+
if (sources.length > 0) {
|
|
132
|
+
console.log("Sources:");
|
|
133
|
+
for (const s of sources) {
|
|
134
|
+
console.log(` ${s.source_agent || "unknown"}: ${s.cnt}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
db.close();
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.log(`\nDB: error (${err instanceof Error ? err.message : String(err)})`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.log(`\nDB: not found (run: hicortex init)`);
|
|
145
|
+
}
|
|
146
|
+
// Health assessment
|
|
147
|
+
console.log("\n" + "─".repeat(40));
|
|
148
|
+
const issues = [];
|
|
149
|
+
if (!lastRun)
|
|
150
|
+
issues.push("Pipeline has never run. Run: hicortex nightly");
|
|
151
|
+
else if (lastRun && (Date.now() - lastRun.getTime()) > STALE_THRESHOLD_HOURS * 60 * 60 * 1000) {
|
|
152
|
+
issues.push(`Pipeline hasn't run in ${STALE_THRESHOLD_HOURS}+ hours. Check timer.`);
|
|
153
|
+
}
|
|
154
|
+
if (!timerActive)
|
|
155
|
+
issues.push("No timer installed. Nightly pipeline won't run automatically.");
|
|
156
|
+
if (!(0, node_fs_1.existsSync)(dbPath))
|
|
157
|
+
issues.push("No database found. Run: hicortex init");
|
|
158
|
+
if (issues.length === 0) {
|
|
159
|
+
console.log("✓ Nightly pipeline healthy");
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
console.log("Issues:");
|
|
163
|
+
for (const issue of issues) {
|
|
164
|
+
console.log(` ⚠ ${issue}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
package/dist/nightly.js
CHANGED
|
@@ -400,7 +400,91 @@ async function runClientNightly(config, dryRun) {
|
|
|
400
400
|
console.error(`[hicortex] Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
|
+
// Inject lessons from server into CLAUDE.md
|
|
404
|
+
if (!dryRun) {
|
|
405
|
+
try {
|
|
406
|
+
await injectLessonsFromServer(serverUrl, authToken);
|
|
407
|
+
}
|
|
408
|
+
catch (err) {
|
|
409
|
+
console.error(`[hicortex] CLAUDE.md injection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
403
412
|
if (!dryRun)
|
|
404
413
|
writeLastRun();
|
|
405
414
|
console.log(`[hicortex] Client nightly complete: ${memoriesIngested} memories from ${sessionsSent} sessions → ${serverUrl}`);
|
|
406
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Fetch lessons + memory index from server and inject into CLAUDE.md.
|
|
418
|
+
* Client mode equivalent of the server's injectLessons(db, ...).
|
|
419
|
+
*/
|
|
420
|
+
async function injectLessonsFromServer(serverUrl, authToken) {
|
|
421
|
+
const resp = await fetch(`${serverUrl}/lessons`, {
|
|
422
|
+
headers: authToken ? { "Authorization": `Bearer ${authToken}` } : {},
|
|
423
|
+
signal: AbortSignal.timeout(10_000),
|
|
424
|
+
});
|
|
425
|
+
if (!resp.ok) {
|
|
426
|
+
console.log(`[hicortex] Could not fetch lessons from server (${resp.status})`);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const data = await resp.json();
|
|
430
|
+
const maxLessons = 10;
|
|
431
|
+
const selected = data.lessons.slice(0, maxLessons);
|
|
432
|
+
// Format lessons
|
|
433
|
+
const lessonLines = selected.map((l) => {
|
|
434
|
+
const titleMatch = l.content.match(/## Lesson: (.+)/);
|
|
435
|
+
const typeMatch = l.content.match(/\*\*Type:\*\* (\w+)/);
|
|
436
|
+
const severityMatch = l.content.match(/\*\*Severity:\*\* (\w+)/);
|
|
437
|
+
const title = titleMatch ? titleMatch[1] : l.content.slice(0, 150);
|
|
438
|
+
const meta = [severityMatch?.[1], typeMatch?.[1]].filter(Boolean).join(", ");
|
|
439
|
+
return `- ${title}${meta ? ` (${meta})` : ""}`;
|
|
440
|
+
});
|
|
441
|
+
// Format project index
|
|
442
|
+
const projectIndex = data.index.projects.map(p => `${p.name}: ${p.count}`);
|
|
443
|
+
// Build block
|
|
444
|
+
const START_MARKER = "<!-- HICORTEX-LEARNINGS:START -->";
|
|
445
|
+
const END_MARKER = "<!-- HICORTEX-LEARNINGS:END -->";
|
|
446
|
+
const blockParts = [START_MARKER, "## Hicortex Memory"];
|
|
447
|
+
blockParts.push("", "You have access to shared long-term memory across all agents and sessions.", "BEFORE making decisions, search memory: `hicortex_search` for prior decisions on the same topic.", "Use `hicortex_context` at session start for recent project state.");
|
|
448
|
+
if (lessonLines.length > 0) {
|
|
449
|
+
blockParts.push("", "### Lessons (updated nightly)");
|
|
450
|
+
blockParts.push(...lessonLines);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
blockParts.push("", "### Getting Started");
|
|
454
|
+
blockParts.push("- Search past decisions with `hicortex_search` before starting work");
|
|
455
|
+
blockParts.push("- Save important decisions with `hicortex_ingest`");
|
|
456
|
+
blockParts.push("- Lessons will appear here after the first nightly run");
|
|
457
|
+
}
|
|
458
|
+
if (projectIndex.length > 0) {
|
|
459
|
+
blockParts.push("", "### Memory Index");
|
|
460
|
+
blockParts.push(projectIndex.join(" | "));
|
|
461
|
+
blockParts.push(`${data.index.total} memories, ${data.index.lessonCount} lessons, ${data.index.sourceCount} agents. Search with \`hicortex_search\`.`);
|
|
462
|
+
}
|
|
463
|
+
blockParts.push(END_MARKER);
|
|
464
|
+
const block = blockParts.join("\n");
|
|
465
|
+
// Write to CLAUDE.md
|
|
466
|
+
const { readFileSync, writeFileSync, mkdirSync } = await import("node:fs");
|
|
467
|
+
const { join, dirname } = await import("node:path");
|
|
468
|
+
const { homedir } = await import("node:os");
|
|
469
|
+
const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
|
|
470
|
+
let content = "";
|
|
471
|
+
try {
|
|
472
|
+
content = readFileSync(claudeMdPath, "utf-8");
|
|
473
|
+
}
|
|
474
|
+
catch { }
|
|
475
|
+
const startIdx = content.indexOf(START_MARKER);
|
|
476
|
+
const endIdx = content.indexOf(END_MARKER);
|
|
477
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
478
|
+
content = content.slice(0, startIdx) + block + content.slice(endIdx + END_MARKER.length);
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
if (content.length > 0 && !content.endsWith("\n"))
|
|
482
|
+
content += "\n";
|
|
483
|
+
if (content.length > 0)
|
|
484
|
+
content += "\n";
|
|
485
|
+
content += block + "\n";
|
|
486
|
+
}
|
|
487
|
+
mkdirSync(dirname(claudeMdPath), { recursive: true });
|
|
488
|
+
writeFileSync(claudeMdPath, content);
|
|
489
|
+
console.log(`[hicortex] CLAUDE.md updated: ${lessonLines.length} lessons, ${data.index.total} memories indexed`);
|
|
490
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Human-like memory for self-improving AI agents. Automatic capturing, nightly reflection, and cross-agent learning. Works with Claude Code and OpenClaw.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|