@gamaze/hicortex 0.7.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +72 -39
  2. package/dist/claude-md.d.ts +9 -21
  3. package/dist/claude-md.js +9 -241
  4. package/dist/cli.d.ts +3 -2
  5. package/dist/cli.js +29 -11
  6. package/dist/consolidate.js +0 -7
  7. package/dist/db.js +24 -0
  8. package/dist/embedder.d.ts +11 -0
  9. package/dist/embedder.js +27 -0
  10. package/dist/extensions.d.ts +41 -88
  11. package/dist/extensions.js +36 -61
  12. package/dist/features.d.ts +21 -25
  13. package/dist/features.js +47 -83
  14. package/dist/hermes-transcript-reader.d.ts +27 -0
  15. package/dist/hermes-transcript-reader.js +134 -0
  16. package/dist/index.d.ts +16 -4
  17. package/dist/index.js +252 -344
  18. package/dist/init.d.ts +41 -1
  19. package/dist/init.js +545 -190
  20. package/dist/lesson-selection.d.ts +62 -0
  21. package/dist/lesson-selection.js +159 -0
  22. package/dist/lessons-context.d.ts +17 -0
  23. package/dist/lessons-context.js +96 -0
  24. package/dist/llm.d.ts +42 -29
  25. package/dist/llm.js +89 -270
  26. package/dist/mcp-server.d.ts +0 -1
  27. package/dist/mcp-server.js +404 -86
  28. package/dist/nightly.d.ts +9 -6
  29. package/dist/nightly.js +197 -357
  30. package/dist/oc-transcript-reader.d.ts +20 -0
  31. package/dist/oc-transcript-reader.js +61 -0
  32. package/dist/pi-transcript-reader.d.ts +1 -0
  33. package/dist/status.js +22 -2
  34. package/dist/storage.d.ts +7 -1
  35. package/dist/storage.js +28 -7
  36. package/dist/transcript-reader.d.ts +19 -0
  37. package/dist/transcript-reader.js +17 -3
  38. package/dist/types.d.ts +10 -0
  39. package/dist/uninstall.js +31 -1
  40. package/hermes-plugin/hicortex/README.md +77 -0
  41. package/hermes-plugin/hicortex/__init__.py +17 -0
  42. package/hermes-plugin/hicortex/client.py +162 -0
  43. package/hermes-plugin/hicortex/config.py +105 -0
  44. package/hermes-plugin/hicortex/plugin.yaml +12 -0
  45. package/hermes-plugin/hicortex/provider.py +432 -0
  46. package/openclaw.plugin.json +17 -44
  47. package/package.json +7 -5
  48. package/dist/pro-loader.d.ts +0 -33
  49. package/dist/pro-loader.js +0 -187
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Your agents learn from every session — successes and mistakes. Hicortex captures experiences, distills lessons, and applies them automatically. Connect multiple agents to shared memory and they improve together, overnight.
4
4
 
5
- Works with **Claude Code**, **Pi**, **OpenClaw**, and any MCP-compatible agent.
5
+ Works with **Hermes**, **OpenClaw**, **Claude Code**, **Pi**, and any MCP-compatible agent.
6
6
 
7
7
  **Website:** [hicortex.gamaze.com](https://hicortex.gamaze.com) · **Docs:** [hicortex.gamaze.com/docs](https://hicortex.gamaze.com/docs/)
8
8
 
@@ -12,7 +12,7 @@ Works with **Claude Code**, **Pi**, **OpenClaw**, and any MCP-compatible agent.
12
12
  npx @gamaze/hicortex init
13
13
  ```
14
14
 
15
- Detects your environment, installs a persistent MCP server daemon, auto-detects Ollama/Claude CLI/API keys, and registers with Claude Code. One command.
15
+ Detects available LLM candidates (Ollama models, Claude CLI, API keys from env/Hermes/.env/Claude Code settings/OpenClaw), presents a numbered list, and asks you to choose. Installs a persistent MCP server daemon and registers with Claude Code. One command.
16
16
 
17
17
  ## Install — Client Mode (multi-client)
18
18
 
@@ -20,28 +20,51 @@ Detects your environment, installs a persistent MCP server daemon, auto-detects
20
20
  npx @gamaze/hicortex init --server https://your-server.example.com
21
21
  ```
22
22
 
23
- Connects to a remote Hicortex server. Sessions are distilled locally (privacy), memories are sent to the shared server. No local database needed.
23
+ Connects to a remote Hicortex server. No local database or local LLM needed. The nightly job denoises sessions locally (no LLM — just strips tool noise), then POSTs the denoised text to the server. The server distills, embeds, and stores. Raw session content never leaves the machine.
24
+
25
+ ## Install — Hermes
26
+
27
+ The Hermes plugin is a recall-only adapter: it injects fresh lessons every turn and exposes the full 8-tool memory surface, backed by a Hicortex server (local or remote). Capture happens automatically — the server machine's nightly job reads each Hermes profile's `state.db`.
28
+
29
+ ```bash
30
+ # 1. Install the plugin (prompts for server URL + auth token; leave empty for a local server)
31
+ hermes plugins install gamaze-labs/hicortex-hermes-plugin
32
+
33
+ # 2. Activate it as the memory provider, then restart your gateway
34
+ hermes memory setup hicortex
35
+ hermes gateway restart
36
+ ```
37
+
38
+ Find the server's auth token with `hicortex status` on the server machine.
24
39
 
25
40
  ## Install — OpenClaw
26
41
 
42
+ The OC plugin is a recall-only adapter (lessons + memory tools): it requires a Hicortex server. Run the server once on the same machine (or point the plugin at a remote server via `serverUrl` config). Capture happens automatically — the machine's Hicortex nightly reads OpenClaw's session files (`~/.openclaw/agents/*/sessions/`) alongside Claude Code and Hermes sessions.
43
+
27
44
  ```bash
45
+ # 1. Start the Hicortex server (once, on the machine running OC)
46
+ npx @gamaze/hicortex init
47
+
48
+ # 2. Install the plugin
28
49
  openclaw plugins install @gamaze/hicortex
29
50
  openclaw gateway restart
30
51
  ```
31
52
 
53
+ The plugin connects to `http://127.0.0.1:8787` by default. For a remote server, add `serverUrl` and `authToken` (find the token via `hicortex status` on the server) to the plugin config in `~/.openclaw/openclaw.json`.
54
+
32
55
  ## Requirements
33
56
 
34
57
  - Node.js 18+
35
- - LLM: Ollama 9b+ (recommended), Claude CLI, or API key (Anthropic, OpenAI, etc.)
36
- - ~500MB disk for database + embedding model
58
+ - **Server mode:** LLM required — Ollama 9b+ (recommended), Claude CLI, or API key (Anthropic, OpenAI, etc.). ~500MB disk for database + embedding model.
59
+ - **Client mode:** No local LLM needed. Node.js 18+ and network access to the server are sufficient.
60
+ - **OC plugin:** Requires a running Hicortex server. No local LLM, database, or embedder in the plugin itself.
37
61
 
38
62
  ## What Happens Automatically
39
63
 
40
64
  | When | What | How |
41
65
  |------|------|-----|
42
- | Agent start | Recent lessons injected into context | CLAUDE.md / EXPERIENCE.md / OC hook |
43
- | Agent end | Conversation captured | CC + Pi: nightly transcript scan / OC: hook |
44
- | Nightly | Distill → score → reflect → link → inject | Automatic pipeline |
66
+ | Agent start | Recent lessons fetched fresh and injected into context | CC SessionStart hook (calls `hicortex lessons-context`) / Hermes plugin prefetch / OC `before_agent_start` hook |
67
+ | Nightly | Denoise sessions POST /distill → server distills + embeds + stores consolidate (score, reflect, link, decay) | Automatic pipeline — no manual steps |
45
68
 
46
69
  ## Agent Tools (MCP)
47
70
 
@@ -64,7 +87,8 @@ Skills: `/learn` to save explicit learnings.
64
87
  npx @gamaze/hicortex server # Start MCP server (port 8787)
65
88
  npx @gamaze/hicortex init # Set up server mode
66
89
  npx @gamaze/hicortex init --server <url> # Set up client mode
67
- npx @gamaze/hicortex nightly # Run distill + consolidate
90
+ npx @gamaze/hicortex nightly # Run distill + consolidate (full nightly)
91
+ npx @gamaze/hicortex nightly --capture-only # Capture only, skip consolidation (safe for sub-daily runs)
68
92
  npx @gamaze/hicortex nightly --dry-run # Preview without changes
69
93
  npx @gamaze/hicortex status # Show config, DB stats
70
94
  npx @gamaze/hicortex uninstall # Remove CC integration (keeps DB)
@@ -77,8 +101,8 @@ npx @gamaze/hicortex uninstall # Remove CC integration (keeps DB
77
101
  ┌──────────┐ ┌──────────────┐ ┌──────────┐
78
102
  │CC sessions│ │ Shared DB │ │CC sessions│
79
103
  │ ↓ │ POST │ │ POST │ ↓ │
80
- Distill │──/ingest───→│ Embed+Store │←/ingest──│ Distill
81
- │ (local) │ │ ↓ │ │ (local)
104
+ Denoise │──/distill──→│Distill+Store │←/distill─│ Denoise
105
+ │ (no LLM) │ │ ↓ │ │ (no LLM)
82
106
  │ │ MCP │ Consolidate │ MCP │ │
83
107
  │ CC ←│──(search)───│ (score,link, │──(search)→│ CC │
84
108
  │ │ │ reflect) │ │ │
@@ -99,15 +123,16 @@ Config at `~/.hicortex/config.json`. Created by `init`. Key options:
99
123
  |-------|-------------|
100
124
  | `mode` | `"server"` (default) or `"client"` |
101
125
  | `serverUrl` | Remote server URL (client mode) |
102
- | `llmModel` | Model for importance scoring |
103
- | `distillModel` | Model for session distillation (9b+ recommended) |
104
- | `distillBaseUrl` | Separate Ollama instance for distillation |
105
- | `reflectModel` | Model for nightly reflection (largest available) |
106
- | `reflectBaseUrl` | Separate Ollama instance for reflection |
107
- | `authToken` | Bearer token for endpoint auth |
108
- | `licenseKey` | License key for higher tiers |
109
- | `lessonTarget` | Injection target file (default: `~/.claude/CLAUDE.md`) |
110
- | `moduleIndexTokenBudget` | Max tokens for domain index in injection (default: 500) |
126
+ | `llmModel` | Model for importance scoring (server mode) |
127
+ | `distillModel` | Model for session distillation 9b+ recommended (server mode) |
128
+ | `distillBaseUrl` | Separate Ollama instance for distillation (server mode) |
129
+ | `distillFallback` | `"strict"` (default) abort on remote distill failure, retry next run; `"local"` — fall back to base model (lower quality, 0.9.0 behaviour) |
130
+ | `reflectModel` | Model for nightly reflection — largest available (server mode) |
131
+ | `reflectBaseUrl` | Separate Ollama instance for reflection (server mode) |
132
+ | `authToken` | Bearer token for endpoint auth. Generated on first `init` in server mode. Find the active token with `hicortex status` or in `~/.hicortex/config.json`. |
133
+ | `licenseKey` | Commercial license key (optional; for display in `hicortex status`) |
134
+ | `moduleIndexTokenBudget` | Max tokens for domain index in lessons context (default: 500) |
135
+ | `nightlyHour` | Local hour (0–23) for the nightly job installed by `init` (defaults: client 2, server 3). Applied on fresh installs; existing schedules are never overwritten |
111
136
  | `telemetry` | Anonymous usage telemetry, `false` to opt out |
112
137
 
113
138
  Full docs: [hicortex.gamaze.com/docs/configuration.html](https://hicortex.gamaze.com/docs/configuration.html)
@@ -117,20 +142,19 @@ Full docs: [hicortex.gamaze.com/docs/configuration.html](https://hicortex.gamaze
117
142
  | Endpoint | Method | Auth | Description |
118
143
  |----------|--------|------|-------------|
119
144
  | `/health` | GET | No | Server status, memory count, version |
120
- | `/ingest` | POST | Yes | Accept pre-distilled memories from clients |
145
+ | `/distill` | POST | Yes | Canonical capture endpoint (0.9.0+). Accepts denoised session text (`text` string or `messages` array), distills server-side, stores. Used by both server-mode and client-mode nightly jobs. |
146
+ | `/search` | GET | Yes | Semantic memory search |
147
+ | `/context` | GET | Yes | Recent context memories |
148
+ | `/lessons` | GET | Yes | Lessons + memory index (used by CC SessionStart hook) |
149
+ | `/ingest` | POST | Yes | Legacy: accept a single pre-distilled memory from older clients |
121
150
  | `/sse` | GET | Yes | MCP SSE stream for agent connections |
122
151
  | `/messages` | POST | Yes | MCP message endpoint |
123
152
 
124
- ## Pricing
153
+ ## License
125
154
 
126
- | Tier | Price | Memories | Clients |
127
- |------|-------|----------|---------|
128
- | Free | $0 | 250 | Unlimited (trial) |
129
- | Pro | $9/month | Unlimited | Single |
130
- | Team | $29/month | Unlimited | Unlimited |
131
- | Lifetime | $149 | Unlimited | Single |
155
+ **Personal and noncommercial use is free** under the [PolyForm Noncommercial License 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0). Commercial use (for-profit businesses, client work, revenue-generating products) requires a per-seat license — see [hicortex.gamaze.com](https://hicortex.gamaze.com).
132
156
 
133
- [hicortex.gamaze.com](https://hicortex.gamaze.com)
157
+ Versions ≤ 0.7.1 published to npm remain MIT-licensed.
134
158
 
135
159
  ## Uninstall
136
160
 
@@ -147,19 +171,26 @@ Optional config (add to plugin entry in `~/.openclaw/openclaw.json`):
147
171
 
148
172
  | Field | Default | Description |
149
173
  |-------|---------|-------------|
150
- | `licenseKey` | _(none)_ | License key. Free tier (250 memories) without key. |
151
- | `llmBaseUrl` | _(auto)_ | Override LLM base URL |
152
- | `llmApiKey` | _(auto)_ | Override LLM API key |
153
- | `llmModel` | _(auto)_ | Override model for scoring and distillation |
154
- | `reflectModel` | _(auto)_ | Override model for nightly reflection |
155
- | `consolidateHour` | `2` | Hour (0-23, local time) for nightly consolidation |
156
- | `dbPath` | _(auto)_ | Custom database path |
174
+ | `serverUrl` | `http://127.0.0.1:8787` | Hicortex server URL. Change for remote servers. |
175
+ | `authToken` | _(none)_ | Bearer token. Localhost bypasses auth; required for remote servers. Get the token from `hicortex status` on the server. |
176
+ | `licenseKey` | _(none)_ | Commercial license key. Optional; only affects the display in `hicortex status`. |
177
+
178
+ ## LLM Configuration
157
179
 
158
- For CC, set environment variables: `ANTHROPIC_API_KEY` (auto-detected), or `HICORTEX_LLM_BASE_URL` + `HICORTEX_LLM_API_KEY` + `HICORTEX_LLM_MODEL` for custom providers.
180
+ LLM selection is **user-controlled**: `npx @gamaze/hicortex init` detects candidates and asks you to choose. Nothing is silently auto-applied at runtime.
181
+
182
+ | Method | Config key | Notes |
183
+ |--------|-----------|-------|
184
+ | Ollama (local) | `llmBackend: "ollama"` | Set by init; no API key needed |
185
+ | Claude CLI | `llmBackend: "claude-cli"` | Uses CC subscription; no API key needed |
186
+ | Custom provider | `llmBaseUrl` + `llmApiKey` | Any OpenAI-compatible endpoint |
187
+ | Hicortex env vars | `HICORTEX_LLM_BASE_URL` + `HICORTEX_LLM_API_KEY` | Override at runtime |
188
+
189
+ If no LLM is configured, the server starts in **recall-only mode**: search, lessons, and context work; `/distill` and consolidation are disabled. Run `npx @gamaze/hicortex init` to configure.
159
190
 
160
191
  ## Database
161
192
 
162
- Canonical location: `~/.hicortex/hicortex.db`. Existing OC installations at `~/.openclaw/data/hicortex.db` are automatically migrated on upgrade.
193
+ Canonical location: `~/.hicortex/hicortex.db`. The OC plugin no longer owns its own database — it is a thin client to the server. Previously, OC installations at `~/.openclaw/data/hicortex.db` were migrated automatically on upgrade; this migration path remains in the server's `resolveDbPath` for any pre-0.10.0 installations.
163
194
 
164
195
  ## Development
165
196
 
@@ -174,7 +205,9 @@ npm test
174
205
 
175
206
  **Tools not visible to agent (OC):** The plugin auto-adds tools to `tools.allow` on startup. Restart the gateway after install.
176
207
 
177
- **LLM auto-config failed:** Check logs for `[hicortex] WARNING`. Add `llmBaseUrl` to plugin config or set `HICORTEX_LLM_BASE_URL` env var.
208
+ **OC plugin: "Server unreachable":** The plugin requires a running Hicortex server. Run `npx @gamaze/hicortex init` on the same machine, or set `serverUrl` in the plugin config to point at a remote server.
209
+
210
+ **LLM auto-config failed:** Check logs for `[hicortex] WARNING`. Add `llmBaseUrl` to plugin config or set `HICORTEX_LLM_BASE_URL` env var (applies to server setup, not the OC plugin itself).
178
211
 
179
212
  **No lessons generated:** Reflection requires an LLM. Check that your provider is accessible and has sufficient quota.
180
213
 
@@ -1,29 +1,17 @@
1
1
  /**
2
- * CLAUDE.md lesson injectionmanages the Hicortex Learnings block.
2
+ * CLAUDE.md block managementremoveLessonsBlock only (0.9.0+).
3
3
  *
4
- * Injects a dynamic, nightly-updated block into ~/.claude/CLAUDE.md:
5
- * - Top lessons (from reflection, high-confidence)
6
- * - Memory index (projects + counts, primes the agent to search)
7
- * - Current project context (recent decisions for this project)
4
+ * Lesson injection was removed in 0.9.0: lessons are now fetched at query time
5
+ * via the CC SessionStart hook (`hicortex lessons-context`) and by the Hermes
6
+ * plugin's prefetch/system_prompt_block. File-based injection caused EPERM
7
+ * errors on macOS, stale blocks, and unwinnable multi-file bookkeeping.
8
8
  *
9
- * Idempotent: calling twice with the same data produces the same file.
9
+ * removeLessonsBlock is kept so init/uninstall can strip the old static block
10
+ * from machines upgrading from 0.8.0 and earlier.
10
11
  */
11
- import type Database from "better-sqlite3";
12
- /**
13
- * Inject lessons, memory index, and project context into CLAUDE.md.
14
- * Creates the file if it doesn't exist.
15
- * Replaces existing block if present, appends if not.
16
- */
17
- export declare function injectLessons(db: Database.Database, options?: {
18
- claudeMdPath?: string;
19
- stateDir?: string;
20
- project?: string;
21
- }): Promise<{
22
- lessonsCount: number;
23
- path: string;
24
- }>;
25
12
  /**
26
13
  * Remove the Hicortex Learnings block from CLAUDE.md.
27
- * Used by the uninstall command.
14
+ * Used by init (migration from 0.8.0) and uninstall.
15
+ * Returns true if a block was found and removed.
28
16
  */
29
17
  export declare function removeLessonsBlock(claudeMdPath?: string): boolean;
package/dist/claude-md.js CHANGED
@@ -1,259 +1,27 @@
1
1
  "use strict";
2
2
  /**
3
- * CLAUDE.md lesson injectionmanages the Hicortex Learnings block.
3
+ * CLAUDE.md block managementremoveLessonsBlock only (0.9.0+).
4
4
  *
5
- * Injects a dynamic, nightly-updated block into ~/.claude/CLAUDE.md:
6
- * - Top lessons (from reflection, high-confidence)
7
- * - Memory index (projects + counts, primes the agent to search)
8
- * - Current project context (recent decisions for this project)
5
+ * Lesson injection was removed in 0.9.0: lessons are now fetched at query time
6
+ * via the CC SessionStart hook (`hicortex lessons-context`) and by the Hermes
7
+ * plugin's prefetch/system_prompt_block. File-based injection caused EPERM
8
+ * errors on macOS, stale blocks, and unwinnable multi-file bookkeeping.
9
9
  *
10
- * Idempotent: calling twice with the same data produces the same file.
10
+ * removeLessonsBlock is kept so init/uninstall can strip the old static block
11
+ * from machines upgrading from 0.8.0 and earlier.
11
12
  */
12
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- var desc = Object.getOwnPropertyDescriptor(m, k);
15
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
- desc = { enumerable: true, get: function() { return m[k]; } };
17
- }
18
- Object.defineProperty(o, k2, desc);
19
- }) : (function(o, m, k, k2) {
20
- if (k2 === undefined) k2 = k;
21
- o[k2] = m[k];
22
- }));
23
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
- Object.defineProperty(o, "default", { enumerable: true, value: v });
25
- }) : function(o, v) {
26
- o["default"] = v;
27
- });
28
- var __importStar = (this && this.__importStar) || (function () {
29
- var ownKeys = function(o) {
30
- ownKeys = Object.getOwnPropertyNames || function (o) {
31
- var ar = [];
32
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
- return ar;
34
- };
35
- return ownKeys(o);
36
- };
37
- return function (mod) {
38
- if (mod && mod.__esModule) return mod;
39
- var result = {};
40
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
- __setModuleDefault(result, mod);
42
- return result;
43
- };
44
- })();
45
13
  Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.injectLessons = injectLessons;
47
14
  exports.removeLessonsBlock = removeLessonsBlock;
48
15
  const node_fs_1 = require("node:fs");
49
16
  const node_path_1 = require("node:path");
50
17
  const node_os_1 = require("node:os");
51
- const storage = __importStar(require("./storage.js"));
52
- const features_js_1 = require("./features.js");
53
- const extensions_js_1 = require("./extensions.js");
54
- const state_js_1 = require("./state.js");
55
18
  const START_MARKER = "<!-- HICORTEX-LEARNINGS:START -->";
56
19
  const END_MARKER = "<!-- HICORTEX-LEARNINGS:END -->";
57
20
  const DEFAULT_CLAUDE_MD = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "CLAUDE.md");
58
- /**
59
- * Inject lessons, memory index, and project context into CLAUDE.md.
60
- * Creates the file if it doesn't exist.
61
- * Replaces existing block if present, appends if not.
62
- */
63
- async function injectLessons(db, options = {}) {
64
- const claudeMdPath = options.claudeMdPath ?? DEFAULT_CLAUDE_MD;
65
- // Determine limits based on license
66
- const maxLessons = (0, features_js_1.lessonsLimit)();
67
- // Load MODULE_INDEX from state for domain-aware selection
68
- const state = (0, state_js_1.loadState)(options.stateDir);
69
- const moduleIndex = state.moduleIndex;
70
- // --- Lessons ---
71
- const lessons = storage.getLessons(db, 30, options.project);
72
- const selected = await (0, extensions_js_1.getLessonSelector)().select(lessons, {
73
- maxLessons,
74
- project: options.project,
75
- moduleIndex,
76
- });
77
- const lessonLines = selected.map((l) => {
78
- const titleMatch = l.content.match(/## Lesson: (.+)/);
79
- const typeMatch = l.content.match(/\*\*Type:\*\* (\w+)/);
80
- const severityMatch = l.content.match(/\*\*Severity:\*\* (\w+)/);
81
- const title = titleMatch ? titleMatch[1] : l.content.slice(0, 150);
82
- const meta = [severityMatch?.[1], typeMatch?.[1]].filter(Boolean).join(", ");
83
- return `- ${title}${meta ? ` (${meta})` : ""}`;
84
- });
85
- // --- Memory Index ---
86
- const tokenBudget = readModuleIndexTokenBudget();
87
- const indexLines = buildModuleIndex(db, tokenBudget, moduleIndex);
88
- const totalCount = storage.countMemories(db);
89
- const lessonCount = lessons.length;
90
- const sourceCount = countSources(db);
91
- // --- Current Project Context ---
92
- const currentProject = detectCurrentProject(claudeMdPath);
93
- const projectContext = currentProject
94
- ? buildProjectContext(db, currentProject)
95
- : [];
96
- // --- Build Block ---
97
- const blockParts = [START_MARKER, "## Hicortex Memory"];
98
- // Mandatory instruction
99
- 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.");
100
- // Lessons
101
- if (lessonLines.length > 0) {
102
- blockParts.push("", "### Lessons (updated nightly)");
103
- blockParts.push(...lessonLines);
104
- }
105
- // Project context
106
- if (projectContext.length > 0) {
107
- blockParts.push("", `### This Project (${currentProject})`);
108
- blockParts.push(...projectContext);
109
- }
110
- // Memory index
111
- if (indexLines.length > 0) {
112
- blockParts.push("", "### Memory Index");
113
- blockParts.push(...indexLines);
114
- blockParts.push(`${totalCount} memories, ${lessonCount} lessons, ${sourceCount} agents. Search with \`hicortex_search\`.`);
115
- }
116
- blockParts.push(END_MARKER);
117
- const block = blockParts.join("\n");
118
- // --- Write ---
119
- let content = "";
120
- try {
121
- content = (0, node_fs_1.readFileSync)(claudeMdPath, "utf-8");
122
- }
123
- catch {
124
- // File doesn't exist — will create it
125
- }
126
- const startIdx = content.indexOf(START_MARKER);
127
- const endIdx = content.indexOf(END_MARKER);
128
- if (startIdx !== -1 && endIdx !== -1) {
129
- content =
130
- content.slice(0, startIdx) +
131
- block +
132
- content.slice(endIdx + END_MARKER.length);
133
- }
134
- else {
135
- if (content.length > 0 && !content.endsWith("\n"))
136
- content += "\n";
137
- if (content.length > 0)
138
- content += "\n";
139
- content += block + "\n";
140
- }
141
- (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(claudeMdPath), { recursive: true });
142
- (0, node_fs_1.writeFileSync)(claudeMdPath, content);
143
- return { lessonsCount: selected.length, path: claudeMdPath };
144
- }
145
- const DEFAULT_MODULE_INDEX_TOKEN_BUDGET = 500;
146
- // BPE tokenizers average 3.5-4.5 chars/token for English prose. 4 is conservative.
147
- const CHARS_PER_TOKEN = 4;
148
- /**
149
- * Read moduleIndexTokenBudget from config.json. Defaults to 500.
150
- */
151
- function readModuleIndexTokenBudget() {
152
- try {
153
- const configPath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex", "config.json");
154
- const config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
155
- return typeof config.moduleIndexTokenBudget === "number"
156
- ? config.moduleIndexTokenBudget
157
- : DEFAULT_MODULE_INDEX_TOKEN_BUDGET;
158
- }
159
- catch {
160
- return DEFAULT_MODULE_INDEX_TOKEN_BUDGET;
161
- }
162
- }
163
- /**
164
- * Build structured MODULE_INDEX block for injection.
165
- * Uses domain-grouped format if MODULE_INDEX is available,
166
- * falls back to flat "project: count" format otherwise.
167
- */
168
- function buildModuleIndex(db, tokenBudget, moduleIndex) {
169
- if (moduleIndex && moduleIndex.domains.length > 0) {
170
- const charBudget = tokenBudget * CHARS_PER_TOKEN;
171
- let charCount = 0;
172
- const lines = [];
173
- for (const domain of moduleIndex.domains) {
174
- const kwStr = domain.keywords.length > 0
175
- ? `: ${domain.keywords.join(", ")}`
176
- : "";
177
- const domainLine = `${domain.name} (${domain.memoryCount} memories, ${domain.lessonCount} lessons)${kwStr}`;
178
- const projectLine = ` ${domain.projects.join(" | ")}`;
179
- const blockChars = domainLine.length + projectLine.length + 2; // +2 for newlines
180
- if (charCount + blockChars > charBudget && lines.length > 0)
181
- break;
182
- lines.push(domainLine);
183
- lines.push(projectLine);
184
- charCount += blockChars;
185
- }
186
- return lines;
187
- }
188
- // Fallback: flat project index (OSS default)
189
- try {
190
- const rows = db
191
- .prepare(`SELECT project, COUNT(*) as cnt FROM memories
192
- WHERE project IS NOT NULL
193
- GROUP BY project ORDER BY cnt DESC LIMIT 10`)
194
- .all();
195
- return [rows.map((r) => `${r.project}: ${r.cnt}`).join(" | ")];
196
- }
197
- catch {
198
- return [];
199
- }
200
- }
201
- /**
202
- * Count distinct source agents.
203
- */
204
- function countSources(db) {
205
- try {
206
- return db
207
- .prepare("SELECT COUNT(DISTINCT source_agent) as cnt FROM memories")
208
- .get().cnt;
209
- }
210
- catch {
211
- return 0;
212
- }
213
- }
214
- /**
215
- * Detect current project from the CLAUDE.md path.
216
- * CC puts project-specific CLAUDE.md files in ~/.claude/projects/<encoded-path>/
217
- * The global ~/.claude/CLAUDE.md has no project context.
218
- */
219
- function detectCurrentProject(claudeMdPath) {
220
- // Global CLAUDE.md — no project
221
- if (claudeMdPath === DEFAULT_CLAUDE_MD)
222
- return null;
223
- // Project CLAUDE.md: ~/.claude/projects/-Users-foo-myproject/CLAUDE.md
224
- // Extract the last segment of the encoded path
225
- const match = claudeMdPath.match(/projects\/[^/]*-([^/]+)\//);
226
- if (match)
227
- return match[1];
228
- return null;
229
- }
230
- /**
231
- * Build recent decisions/facts for a specific project.
232
- * Returns formatted lines like: "- Shipped v0.4.1 with multi-client (2026-03-28)"
233
- */
234
- function buildProjectContext(db, project) {
235
- try {
236
- const rows = db
237
- .prepare(`SELECT content, created_at FROM memories
238
- WHERE project = ? AND memory_type IN ('decision', 'episode')
239
- ORDER BY created_at DESC LIMIT 5`)
240
- .all(project);
241
- return rows.map((r) => {
242
- const date = r.created_at?.slice(0, 10) ?? "";
243
- // Extract first meaningful line from content
244
- const lines = r.content.split("\n").filter((l) => l.trim().length > 10);
245
- const summary = lines.find((l) => l.startsWith("- ") || l.startsWith("### "))?.replace(/^[-#\s]+/, "").slice(0, 120) ??
246
- r.content.slice(0, 120);
247
- return `- ${summary} (${date})`;
248
- });
249
- }
250
- catch {
251
- return [];
252
- }
253
- }
254
21
  /**
255
22
  * Remove the Hicortex Learnings block from CLAUDE.md.
256
- * Used by the uninstall command.
23
+ * Used by init (migration from 0.8.0) and uninstall.
24
+ * Returns true if a block was found and removed.
257
25
  */
258
26
  function removeLessonsBlock(claudeMdPath = DEFAULT_CLAUDE_MD) {
259
27
  let content;
package/dist/cli.d.ts CHANGED
@@ -5,8 +5,9 @@
5
5
  * Commands:
6
6
  * server Start the MCP HTTP/SSE server (persistent daemon)
7
7
  * init Detect existing setup and configure for CC/OC
8
- * nightly Run distill + consolidate + inject lessons (manual trigger)
9
- * nightly --status Show nightly pipeline health check
8
+ * nightly Run capture + consolidate (manual trigger)
9
+ * nightly --capture-only Capture only, skip consolidation
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
  */
package/dist/cli.js CHANGED
@@ -6,8 +6,9 @@
6
6
  * Commands:
7
7
  * server Start the MCP HTTP/SSE server (persistent daemon)
8
8
  * init Detect existing setup and configure for CC/OC
9
- * nightly Run distill + consolidate + inject lessons (manual trigger)
10
- * nightly --status Show nightly pipeline health check
9
+ * nightly Run capture + consolidate (manual trigger)
10
+ * nightly --capture-only Capture only, skip consolidation
11
+ * nightly --status Show nightly pipeline health check
11
12
  * status Show config, DB stats, adapter status
12
13
  * uninstall Clean removal of CC integration
13
14
  */
@@ -50,8 +51,9 @@ switch (command) {
50
51
  }
51
52
  else {
52
53
  const dryRun = args.includes("--dry-run");
54
+ const captureOnly = args.includes("--capture-only");
53
55
  import("./nightly.js").then(({ runNightly }) => {
54
- runNightly({ dryRun }).catch((err) => {
56
+ runNightly({ dryRun, captureOnly }).catch((err) => {
55
57
  console.error("[hicortex] Nightly pipeline failed:", err);
56
58
  process.exit(1);
57
59
  });
@@ -75,24 +77,40 @@ switch (command) {
75
77
  });
76
78
  });
77
79
  break;
80
+ case "lessons-context":
81
+ // CC SessionStart hook: fetch lessons from the configured server and print
82
+ // a Markdown block to stdout. Fail-soft — any error = silent exit 0 so a
83
+ // broken hook never blocks a CC session.
84
+ import("./lessons-context.js").then(({ fetchLessonsContext }) => {
85
+ fetchLessonsContext()
86
+ .then((block) => {
87
+ if (block)
88
+ process.stdout.write(block + "\n");
89
+ process.exit(0);
90
+ })
91
+ .catch(() => process.exit(0));
92
+ }).catch(() => process.exit(0));
93
+ break;
78
94
  default:
79
95
  console.log(`Hicortex — Human-like memory for self-improving AI agents
80
96
 
81
97
  Usage: hicortex <command> [options]
82
98
 
83
99
  Commands:
84
- server Start the MCP HTTP/SSE server (server mode)
85
- init Set up Hicortex (server mode, local DB + daemon)
86
- init --server <url> Set up as client (remote server, local distillation)
87
- nightly Run nightly distill + consolidate + inject
88
- status Show current configuration and stats
89
- uninstall Remove CC integration (preserves DB)
100
+ server Start the MCP HTTP/SSE server (server mode)
101
+ init Set up Hicortex (server mode, local DB + daemon)
102
+ init --server <url> Set up as client (remote server)
103
+ nightly Run nightly denoise + capture + consolidate
104
+ lessons-context Fetch lessons and print Markdown to stdout (CC SessionStart hook)
105
+ status Show current configuration and stats
106
+ uninstall Remove CC integration (preserves DB)
90
107
 
91
108
  Options:
92
109
  server --port <n> Port (default: 8787)
93
110
  server --host <h> Host (default: 127.0.0.1)
94
- nightly --dry-run Preview without changes
95
- nightly --status Show nightly pipeline health
111
+ nightly --dry-run Preview without changes
112
+ nightly --capture-only Capture only, skip consolidation (safe to run multiple times/day)
113
+ nightly --status Show nightly pipeline health
96
114
 
97
115
  Examples:
98
116
  npx @gamaze/hicortex server
@@ -268,13 +268,6 @@ async function stageReflection(db, memories, llm, budget, embedFn, dryRun) {
268
268
  minor: 0.6,
269
269
  };
270
270
  try {
271
- // Check memory cap before storing lesson
272
- if ((0, features_js_1.memoryCapReached)(storage.countMemories(db))) {
273
- console.log(`[hicortex] Free tier limit (${(0, features_js_1.maxMemoriesAllowed)()} memories). ` +
274
- `Existing memories and lessons still work. New lessons won't be saved. ` +
275
- `Upgrade for unlimited usage: https://hicortex.gamaze.com/`);
276
- break;
277
- }
278
271
  const embedding = await embedFn(content);
279
272
  // Contradiction check: find semantically similar existing lessons.
280
273
  // If a very similar lesson exists, ask the LLM whether the new one
package/dist/db.js CHANGED
@@ -226,6 +226,30 @@ const MIGRATIONS = [
226
226
  db.exec("CREATE INDEX IF NOT EXISTS idx_memories_domain ON memories(domain)");
227
227
  },
228
228
  },
229
+ {
230
+ version: 4,
231
+ name: "unique_source_session",
232
+ up: (db) => {
233
+ // De-duplicate any pre-existing source_session values (e.g. from the
234
+ // /ingest + /distill race before this migration): keep the oldest row per
235
+ // source_session, NULL the rest so they lose their dedup key (the memory
236
+ // itself is preserved). Then add a UNIQUE partial index so the server can
237
+ // idempotently re-distill a segment without double-inserting.
238
+ db.exec(`
239
+ UPDATE memories SET source_session = NULL
240
+ WHERE rowid NOT IN (
241
+ SELECT MIN(rowid) FROM memories
242
+ WHERE source_session IS NOT NULL
243
+ GROUP BY source_session
244
+ )
245
+ AND source_session IS NOT NULL
246
+ `);
247
+ db.exec(`
248
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_source_session_unique
249
+ ON memories(source_session) WHERE source_session IS NOT NULL
250
+ `);
251
+ },
252
+ },
229
253
  ];
230
254
  /**
231
255
  * Run all pending migrations against the database.