memorix 1.1.9 → 1.1.11

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 (188) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/TEAM.md +86 -86
  3. package/dist/cli/index.js +2452 -845
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/dashboard/static/app.js +29 -3
  6. package/dist/dashboard/static/index.html +201 -201
  7. package/dist/dashboard/static/style.css +3762 -3584
  8. package/dist/index.js +2790 -1310
  9. package/dist/index.js.map +1 -1
  10. package/dist/maintenance-runner.d.ts +40 -0
  11. package/dist/maintenance-runner.js +8193 -0
  12. package/dist/maintenance-runner.js.map +1 -0
  13. package/dist/memcode-runtime/CHANGELOG.md +28 -0
  14. package/dist/memcode-runtime/package.json +4 -4
  15. package/dist/sdk.js +2787 -1307
  16. package/dist/sdk.js.map +1 -1
  17. package/docs/CONFIGURATION.md +15 -4
  18. package/docs/DESIGN_DECISIONS.md +357 -357
  19. package/docs/GIT_MEMORY.md +2 -2
  20. package/docs/PERFORMANCE.md +23 -0
  21. package/docs/dev-log/progress.txt +54 -24
  22. package/package.json +1 -1
  23. package/src/audit/index.ts +156 -156
  24. package/src/cli/commands/audit-list.ts +89 -89
  25. package/src/cli/commands/background.ts +659 -659
  26. package/src/cli/commands/cleanup.ts +279 -255
  27. package/src/cli/commands/codegraph.ts +7 -5
  28. package/src/cli/commands/formation.ts +48 -48
  29. package/src/cli/commands/git-hook-install.ts +111 -111
  30. package/src/cli/commands/handoff.ts +66 -66
  31. package/src/cli/commands/hooks-status.ts +63 -63
  32. package/src/cli/commands/ingest-commit.ts +153 -153
  33. package/src/cli/commands/ingest-image.ts +73 -73
  34. package/src/cli/commands/ingest-log.ts +180 -180
  35. package/src/cli/commands/ingest.ts +44 -44
  36. package/src/cli/commands/integrate-shared.ts +15 -15
  37. package/src/cli/commands/lock.ts +96 -96
  38. package/src/cli/commands/message.ts +121 -121
  39. package/src/cli/commands/poll.ts +70 -70
  40. package/src/cli/commands/purge-all-memory.ts +85 -85
  41. package/src/cli/commands/purge-project-memory.ts +83 -83
  42. package/src/cli/commands/reasoning.ts +132 -132
  43. package/src/cli/commands/retention.ts +107 -108
  44. package/src/cli/commands/serve-http.ts +49 -22
  45. package/src/cli/commands/serve-shared.ts +118 -118
  46. package/src/cli/commands/skills.ts +123 -123
  47. package/src/cli/commands/task.ts +192 -192
  48. package/src/cli/commands/transfer.ts +73 -73
  49. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  50. package/src/cli/tui/ChatView.tsx +234 -234
  51. package/src/cli/tui/CommandBar.tsx +312 -312
  52. package/src/cli/tui/ContextRail.tsx +118 -118
  53. package/src/cli/tui/HeaderBar.tsx +72 -72
  54. package/src/cli/tui/LogoBanner.tsx +51 -51
  55. package/src/cli/tui/Panels.tsx +632 -632
  56. package/src/cli/tui/Sidebar.tsx +179 -179
  57. package/src/cli/tui/chat-service.ts +742 -742
  58. package/src/cli/tui/data.ts +547 -547
  59. package/src/cli/tui/index.ts +41 -41
  60. package/src/cli/tui/markdown-render.tsx +371 -371
  61. package/src/cli/tui/theme.ts +178 -178
  62. package/src/cli/tui/use-mouse.ts +157 -157
  63. package/src/cli/tui/useNavigation.ts +56 -56
  64. package/src/cli/update-checker.ts +211 -211
  65. package/src/cli/version.ts +7 -7
  66. package/src/cli/workbench.ts +1 -1
  67. package/src/codegraph/auto-context.ts +50 -29
  68. package/src/codegraph/binder.ts +109 -16
  69. package/src/codegraph/exclude.ts +10 -0
  70. package/src/codegraph/lite-provider.ts +148 -18
  71. package/src/codegraph/project-context.ts +58 -12
  72. package/src/codegraph/store.ts +182 -0
  73. package/src/compact/token-budget.ts +74 -74
  74. package/src/config/behavior.ts +97 -59
  75. package/src/config/resolved-config.ts +4 -0
  76. package/src/config/toml-loader.ts +2 -0
  77. package/src/config/yaml-loader.ts +3 -1
  78. package/src/dashboard/project-classification.ts +64 -64
  79. package/src/dashboard/server.ts +31 -30
  80. package/src/dashboard/static/index.html +201 -201
  81. package/src/dashboard/static/style.css +3762 -3584
  82. package/src/embedding/fastembed-provider.ts +142 -142
  83. package/src/embedding/transformers-provider.ts +111 -111
  84. package/src/git/extractor.ts +209 -209
  85. package/src/git/hooks-path.ts +85 -85
  86. package/src/git/noise-filter.ts +226 -210
  87. package/src/hooks/handler.ts +10 -2
  88. package/src/hooks/pattern-detector.ts +173 -173
  89. package/src/hooks/significance-filter.ts +250 -250
  90. package/src/llm/memory-manager.ts +328 -328
  91. package/src/llm/provider.ts +885 -885
  92. package/src/llm/quality.ts +248 -248
  93. package/src/memory/attribution-guard.ts +249 -249
  94. package/src/memory/auto-relations.ts +107 -107
  95. package/src/memory/consolidation.ts +328 -302
  96. package/src/memory/disclosure-policy.ts +141 -141
  97. package/src/memory/entity-extractor.ts +197 -197
  98. package/src/memory/export-import.ts +3 -1
  99. package/src/memory/formation/evaluate.ts +217 -217
  100. package/src/memory/formation/extract.ts +361 -361
  101. package/src/memory/formation/index.ts +417 -417
  102. package/src/memory/formation/resolve.ts +344 -344
  103. package/src/memory/formation/types.ts +315 -315
  104. package/src/memory/freshness.ts +122 -122
  105. package/src/memory/graph.ts +197 -197
  106. package/src/memory/observations.ts +91 -27
  107. package/src/memory/refs.ts +94 -94
  108. package/src/memory/retention.ts +511 -433
  109. package/src/memory/secret-filter.ts +79 -79
  110. package/src/memory/session.ts +542 -523
  111. package/src/multimodal/image-loader.ts +143 -143
  112. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  113. package/src/orchestrate/adapters/claude.ts +111 -111
  114. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  115. package/src/orchestrate/adapters/codex.ts +41 -41
  116. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  117. package/src/orchestrate/adapters/gemini.ts +42 -42
  118. package/src/orchestrate/adapters/index.ts +73 -73
  119. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  120. package/src/orchestrate/adapters/opencode.ts +47 -47
  121. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  122. package/src/orchestrate/adapters/types.ts +77 -77
  123. package/src/orchestrate/capability-router.ts +284 -284
  124. package/src/orchestrate/context-compact.ts +188 -188
  125. package/src/orchestrate/cost-tracker.ts +219 -219
  126. package/src/orchestrate/error-recovery.ts +191 -191
  127. package/src/orchestrate/evidence.ts +140 -140
  128. package/src/orchestrate/ledger.ts +110 -110
  129. package/src/orchestrate/memorix-bridge.ts +380 -380
  130. package/src/orchestrate/output-budget.ts +80 -80
  131. package/src/orchestrate/permission.ts +152 -152
  132. package/src/orchestrate/pipeline-trace.ts +131 -131
  133. package/src/orchestrate/prompt-builder.ts +155 -155
  134. package/src/orchestrate/ring-buffer.ts +37 -37
  135. package/src/orchestrate/task-graph.ts +389 -389
  136. package/src/orchestrate/verify-gate.ts +219 -219
  137. package/src/orchestrate/worktree.ts +232 -232
  138. package/src/project/aliases.ts +374 -374
  139. package/src/project/detector.ts +268 -268
  140. package/src/rules/adapters/claude-code.ts +99 -99
  141. package/src/rules/adapters/codex.ts +97 -97
  142. package/src/rules/adapters/copilot.ts +124 -124
  143. package/src/rules/adapters/cursor.ts +114 -114
  144. package/src/rules/adapters/kiro.ts +126 -126
  145. package/src/rules/adapters/trae.ts +56 -56
  146. package/src/rules/adapters/windsurf.ts +83 -83
  147. package/src/rules/syncer.ts +235 -235
  148. package/src/runtime/control-plane-maintenance.ts +62 -0
  149. package/src/runtime/isolated-maintenance.ts +187 -0
  150. package/src/runtime/maintenance-jobs.ts +512 -0
  151. package/src/runtime/maintenance-runner.ts +96 -0
  152. package/src/runtime/maintenance-targets.ts +59 -0
  153. package/src/runtime/project-maintenance.ts +274 -0
  154. package/src/sdk.ts +327 -327
  155. package/src/search/intent-detector.ts +289 -289
  156. package/src/search/query-expansion.ts +52 -52
  157. package/src/server/formation-timeout.ts +27 -27
  158. package/src/server.ts +212 -224
  159. package/src/skills/mini-skills.ts +386 -386
  160. package/src/store/chat-store.ts +119 -119
  161. package/src/store/file-lock.ts +100 -100
  162. package/src/store/graph-store.ts +249 -249
  163. package/src/store/mini-skill-store.ts +349 -349
  164. package/src/store/obs-store.ts +293 -255
  165. package/src/store/persistence-json.ts +212 -212
  166. package/src/store/persistence.ts +291 -291
  167. package/src/store/project-affinity.ts +195 -195
  168. package/src/store/session-store.ts +268 -259
  169. package/src/store/sqlite-db.ts +40 -0
  170. package/src/store/sqlite-store.ts +429 -339
  171. package/src/team/event-bus.ts +76 -76
  172. package/src/team/file-locks.ts +173 -173
  173. package/src/team/handoff.ts +167 -167
  174. package/src/team/messages.ts +203 -203
  175. package/src/team/poll.ts +132 -132
  176. package/src/team/tasks.ts +211 -211
  177. package/src/wiki/generator.ts +237 -237
  178. package/src/wiki/knowledge-graph.ts +334 -334
  179. package/src/wiki/types.ts +85 -85
  180. package/src/workspace/mcp-adapters/codex.ts +191 -191
  181. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  182. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  183. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  184. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  185. package/src/workspace/mcp-adapters/trae.ts +134 -134
  186. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  187. package/src/workspace/sanitizer.ts +60 -60
  188. package/src/workspace/workflow-sync.ts +131 -131
@@ -9,85 +9,85 @@
9
9
  * Set MEMORIX_AUTO_UPDATE=install to opt into silent background install.
10
10
  * Disable via MEMORIX_AUTO_UPDATE=off.
11
11
  */
12
-
13
- import { execFile } from 'node:child_process';
14
- import type { ExecFileException } from 'node:child_process';
15
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
16
- import { join } from 'node:path';
17
- import { homedir } from 'node:os';
18
- import { getCliVersion } from './version.js';
19
-
20
- const PACKAGE_NAME = 'memorix';
21
- const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
22
- const DEFAULT_AUTO_UPDATE_TIMEOUT_MS = 5 * 60 * 1000;
23
- const MIN_AUTO_UPDATE_TIMEOUT_MS = 5_000;
24
- const MAX_AUTO_UPDATE_TIMEOUT_MS = 30 * 60 * 1000;
25
- const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
26
- const CACHE_DIR = join(homedir(), '.memorix');
27
- const CACHE_FILE = join(CACHE_DIR, 'update-check.json');
28
-
12
+
13
+ import { execFile } from 'node:child_process';
14
+ import type { ExecFileException } from 'node:child_process';
15
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
16
+ import { join } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ import { getCliVersion } from './version.js';
19
+
20
+ const PACKAGE_NAME = 'memorix';
21
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
22
+ const DEFAULT_AUTO_UPDATE_TIMEOUT_MS = 5 * 60 * 1000;
23
+ const MIN_AUTO_UPDATE_TIMEOUT_MS = 5_000;
24
+ const MAX_AUTO_UPDATE_TIMEOUT_MS = 30 * 60 * 1000;
25
+ const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
26
+ const CACHE_DIR = join(homedir(), '.memorix');
27
+ const CACHE_FILE = join(CACHE_DIR, 'update-check.json');
28
+
29
29
  export interface UpdateCache {
30
- lastCheck: number;
31
- latestVersion: string;
32
- lastAutoUpdate?: number;
33
- lastAutoUpdateStatus?: 'success' | 'failed';
34
- lastAutoUpdateError?: string;
35
- lastAutoUpdateTimedOut?: boolean;
36
- lastAutoUpdateExitCode?: number | null;
37
- lastAutoUpdateSignal?: string | null;
38
- lastAutoUpdateTimeoutMs?: number;
39
- updatedFrom?: string;
40
- updatedTo?: string;
30
+ lastCheck: number;
31
+ latestVersion: string;
32
+ lastAutoUpdate?: number;
33
+ lastAutoUpdateStatus?: 'success' | 'failed';
34
+ lastAutoUpdateError?: string;
35
+ lastAutoUpdateTimedOut?: boolean;
36
+ lastAutoUpdateExitCode?: number | null;
37
+ lastAutoUpdateSignal?: string | null;
38
+ lastAutoUpdateTimeoutMs?: number;
39
+ updatedFrom?: string;
40
+ updatedTo?: string;
41
41
  }
42
42
 
43
43
  export type AutoUpdateMode = 'off' | 'notify' | 'install';
44
-
45
- /**
46
- * Get the current installed version from package.json.
47
- */
48
- export function getCurrentVersion(): string {
49
- try {
50
- return getCliVersion();
51
- } catch {
52
- return '0.0.0';
53
- }
54
- }
55
-
56
- /**
57
- * Compare two semver strings. Returns true if remote > local.
58
- */
59
- export function isNewer(remote: string, local: string): boolean {
60
- const r = remote.split('.').map(Number);
61
- const l = local.split('.').map(Number);
62
- for (let i = 0; i < 3; i++) {
63
- if ((r[i] ?? 0) > (l[i] ?? 0)) return true;
64
- if ((r[i] ?? 0) < (l[i] ?? 0)) return false;
65
- }
66
- return false;
67
- }
68
-
69
- /**
70
- * Read the update check cache. Exported for doctor/status display.
71
- */
72
- export async function readCache(): Promise<UpdateCache | null> {
73
- try {
74
- const data = await readFile(CACHE_FILE, 'utf-8');
75
- return JSON.parse(data);
76
- } catch {
77
- return null;
78
- }
79
- }
80
-
81
- /**
82
- * Write the update check cache.
83
- */
84
- async function writeCache(cache: UpdateCache): Promise<void> {
85
- try {
86
- await mkdir(CACHE_DIR, { recursive: true });
87
- await writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf-8');
88
- } catch { /* silent */ }
89
- }
90
-
44
+
45
+ /**
46
+ * Get the current installed version from package.json.
47
+ */
48
+ export function getCurrentVersion(): string {
49
+ try {
50
+ return getCliVersion();
51
+ } catch {
52
+ return '0.0.0';
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Compare two semver strings. Returns true if remote > local.
58
+ */
59
+ export function isNewer(remote: string, local: string): boolean {
60
+ const r = remote.split('.').map(Number);
61
+ const l = local.split('.').map(Number);
62
+ for (let i = 0; i < 3; i++) {
63
+ if ((r[i] ?? 0) > (l[i] ?? 0)) return true;
64
+ if ((r[i] ?? 0) < (l[i] ?? 0)) return false;
65
+ }
66
+ return false;
67
+ }
68
+
69
+ /**
70
+ * Read the update check cache. Exported for doctor/status display.
71
+ */
72
+ export async function readCache(): Promise<UpdateCache | null> {
73
+ try {
74
+ const data = await readFile(CACHE_FILE, 'utf-8');
75
+ return JSON.parse(data);
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Write the update check cache.
83
+ */
84
+ async function writeCache(cache: UpdateCache): Promise<void> {
85
+ try {
86
+ await mkdir(CACHE_DIR, { recursive: true });
87
+ await writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf-8');
88
+ } catch { /* silent */ }
89
+ }
90
+
91
91
  /**
92
92
  * Parse MEMORIX_AUTO_UPDATE into an explicit mode.
93
93
  * Default: notify-only.
@@ -99,105 +99,105 @@ function parseAutoUpdateMode(raw: string | undefined): AutoUpdateMode {
99
99
  if (env === 'install' || env === 'true' || env === '1') return 'install';
100
100
  return 'notify';
101
101
  }
102
-
103
- function parseAutoUpdateTimeoutMs(raw: string | undefined): number {
104
- const value = raw?.trim();
105
- if (!value) return DEFAULT_AUTO_UPDATE_TIMEOUT_MS;
106
- const parsed = Number(value);
107
- if (!Number.isFinite(parsed)) return DEFAULT_AUTO_UPDATE_TIMEOUT_MS;
108
- return Math.max(MIN_AUTO_UPDATE_TIMEOUT_MS, Math.min(MAX_AUTO_UPDATE_TIMEOUT_MS, Math.floor(parsed)));
109
- }
110
-
111
- function describeAutoUpdateFailure(error: ExecFileException, timeoutMs: number): {
112
- message: string;
113
- timedOut: boolean;
114
- exitCode: number | null;
115
- signal: string | null;
116
- } {
117
- const timedOut = /timed out/i.test(error.message);
118
- const exitCode = typeof error.code === 'number' ? error.code : null;
119
- const signal = typeof error.signal === 'string' ? error.signal : null;
120
- const details: string[] = [];
121
- if (timedOut) details.push(`timeout ${timeoutMs}ms`);
122
- if (exitCode !== null) details.push(`exit code ${exitCode}`);
123
- if (signal) details.push(`signal ${signal}`);
124
- return {
125
- message: details.length > 0 ? `${error.message} (${details.join(', ')})` : error.message,
126
- timedOut,
127
- exitCode,
128
- signal,
129
- };
130
- }
131
-
132
- /**
133
- * Fetch the latest version from npm registry.
134
- * Uses native https to avoid dependencies. Timeout: 5s.
135
- */
136
- async function fetchLatestVersion(): Promise<string | null> {
137
- try {
138
- const { default: https } = await import('node:https');
139
- return new Promise((resolve) => {
140
- const req = https.get(REGISTRY_URL, { timeout: 5000 }, (res) => {
141
- if (res.statusCode !== 200) { resolve(null); return; }
142
- let body = '';
143
- res.on('data', (chunk: Buffer) => { body += chunk.toString(); });
144
- res.on('end', () => {
145
- try {
146
- const data = JSON.parse(body);
147
- resolve(data.version ?? null);
148
- } catch { resolve(null); }
149
- });
150
- });
151
- req.on('error', () => resolve(null));
152
- req.on('timeout', () => { req.destroy(); resolve(null); });
153
- });
154
- } catch {
155
- return null;
156
- }
157
- }
158
-
159
- /**
160
- * Silently install the latest version in the background.
161
- * Writes result to the cache file so doctor/status can report it.
162
- */
163
- function installUpdateInBackground(targetVersion: string, currentVersion: string, cache: UpdateCache): void {
164
- try {
165
- const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
166
- const timeoutMs = parseAutoUpdateTimeoutMs(process.env.MEMORIX_AUTO_UPDATE_TIMEOUT_MS);
167
- const child = execFile(
168
- npmCmd,
169
- ['install', '-g', `${PACKAGE_NAME}@${targetVersion}`],
170
- { timeout: timeoutMs },
171
- async (error) => {
172
- const failure = error ? describeAutoUpdateFailure(error, timeoutMs) : null;
173
- const updatedCache: UpdateCache = {
174
- ...cache,
175
- lastAutoUpdate: Date.now(),
176
- updatedFrom: currentVersion,
177
- updatedTo: targetVersion,
178
- lastAutoUpdateStatus: error ? 'failed' : 'success',
179
- lastAutoUpdateError: failure?.message,
180
- lastAutoUpdateTimedOut: failure?.timedOut,
181
- lastAutoUpdateExitCode: failure?.exitCode,
182
- lastAutoUpdateSignal: failure?.signal,
183
- lastAutoUpdateTimeoutMs: timeoutMs,
184
- };
185
- await writeCache(updatedCache);
186
- if (error) {
187
- console.error(`[memorix] Auto-update failed: ${failure?.message ?? error.message}`);
188
- } else {
189
- console.error(`[memorix] Auto-updated to v${targetVersion} — restart to apply`);
190
- }
191
- },
192
- );
193
- // Unref so the child process doesn't prevent the main process from exiting
194
- child.unref();
195
- } catch (err) {
196
- console.error(`[memorix] Auto-update spawn failed:`, (err as Error)?.message ?? err);
197
- }
198
- }
199
-
200
- /**
102
+
103
+ function parseAutoUpdateTimeoutMs(raw: string | undefined): number {
104
+ const value = raw?.trim();
105
+ if (!value) return DEFAULT_AUTO_UPDATE_TIMEOUT_MS;
106
+ const parsed = Number(value);
107
+ if (!Number.isFinite(parsed)) return DEFAULT_AUTO_UPDATE_TIMEOUT_MS;
108
+ return Math.max(MIN_AUTO_UPDATE_TIMEOUT_MS, Math.min(MAX_AUTO_UPDATE_TIMEOUT_MS, Math.floor(parsed)));
109
+ }
110
+
111
+ function describeAutoUpdateFailure(error: ExecFileException, timeoutMs: number): {
112
+ message: string;
113
+ timedOut: boolean;
114
+ exitCode: number | null;
115
+ signal: string | null;
116
+ } {
117
+ const timedOut = /timed out/i.test(error.message);
118
+ const exitCode = typeof error.code === 'number' ? error.code : null;
119
+ const signal = typeof error.signal === 'string' ? error.signal : null;
120
+ const details: string[] = [];
121
+ if (timedOut) details.push(`timeout ${timeoutMs}ms`);
122
+ if (exitCode !== null) details.push(`exit code ${exitCode}`);
123
+ if (signal) details.push(`signal ${signal}`);
124
+ return {
125
+ message: details.length > 0 ? `${error.message} (${details.join(', ')})` : error.message,
126
+ timedOut,
127
+ exitCode,
128
+ signal,
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Fetch the latest version from npm registry.
134
+ * Uses native https to avoid dependencies. Timeout: 5s.
135
+ */
136
+ async function fetchLatestVersion(): Promise<string | null> {
137
+ try {
138
+ const { default: https } = await import('node:https');
139
+ return new Promise((resolve) => {
140
+ const req = https.get(REGISTRY_URL, { timeout: 5000 }, (res) => {
141
+ if (res.statusCode !== 200) { resolve(null); return; }
142
+ let body = '';
143
+ res.on('data', (chunk: Buffer) => { body += chunk.toString(); });
144
+ res.on('end', () => {
145
+ try {
146
+ const data = JSON.parse(body);
147
+ resolve(data.version ?? null);
148
+ } catch { resolve(null); }
149
+ });
150
+ });
151
+ req.on('error', () => resolve(null));
152
+ req.on('timeout', () => { req.destroy(); resolve(null); });
153
+ });
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Silently install the latest version in the background.
161
+ * Writes result to the cache file so doctor/status can report it.
162
+ */
163
+ function installUpdateInBackground(targetVersion: string, currentVersion: string, cache: UpdateCache): void {
164
+ try {
165
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
166
+ const timeoutMs = parseAutoUpdateTimeoutMs(process.env.MEMORIX_AUTO_UPDATE_TIMEOUT_MS);
167
+ const child = execFile(
168
+ npmCmd,
169
+ ['install', '-g', `${PACKAGE_NAME}@${targetVersion}`],
170
+ { timeout: timeoutMs },
171
+ async (error) => {
172
+ const failure = error ? describeAutoUpdateFailure(error, timeoutMs) : null;
173
+ const updatedCache: UpdateCache = {
174
+ ...cache,
175
+ lastAutoUpdate: Date.now(),
176
+ updatedFrom: currentVersion,
177
+ updatedTo: targetVersion,
178
+ lastAutoUpdateStatus: error ? 'failed' : 'success',
179
+ lastAutoUpdateError: failure?.message,
180
+ lastAutoUpdateTimedOut: failure?.timedOut,
181
+ lastAutoUpdateExitCode: failure?.exitCode,
182
+ lastAutoUpdateSignal: failure?.signal,
183
+ lastAutoUpdateTimeoutMs: timeoutMs,
184
+ };
185
+ await writeCache(updatedCache);
186
+ if (error) {
187
+ console.error(`[memorix] Auto-update failed: ${failure?.message ?? error.message}`);
188
+ } else {
189
+ console.error(`[memorix] Auto-updated to v${targetVersion} — restart to apply`);
190
+ }
191
+ },
192
+ );
193
+ // Unref so the child process doesn't prevent the main process from exiting
194
+ child.unref();
195
+ } catch (err) {
196
+ console.error(`[memorix] Auto-update spawn failed:`, (err as Error)?.message ?? err);
197
+ }
198
+ }
199
+
200
+ /**
201
201
  * Run the background update check.
202
202
  *
203
203
  * Call this fire-and-forget from entry points (serve-http, TUI).
@@ -215,32 +215,32 @@ export async function checkForUpdates(): Promise<void> {
215
215
 
216
216
  const cache = await readCache();
217
217
  const now = Date.now();
218
-
219
- // Rate limit: skip if checked within the last 24 hours
220
- if (cache && (now - cache.lastCheck) < CHECK_INTERVAL_MS) {
221
- return;
222
- }
223
-
224
- const latestVersion = await fetchLatestVersion();
225
- if (!latestVersion) return;
226
-
227
- const currentVersion = getCurrentVersion();
228
-
229
- // Update cache with check timestamp
230
- const updatedCache: UpdateCache = {
231
- lastCheck: now,
232
- latestVersion,
233
- lastAutoUpdate: cache?.lastAutoUpdate,
234
- lastAutoUpdateStatus: cache?.lastAutoUpdateStatus,
235
- lastAutoUpdateError: cache?.lastAutoUpdateError,
236
- lastAutoUpdateTimedOut: cache?.lastAutoUpdateTimedOut,
237
- lastAutoUpdateExitCode: cache?.lastAutoUpdateExitCode,
238
- lastAutoUpdateSignal: cache?.lastAutoUpdateSignal,
239
- lastAutoUpdateTimeoutMs: cache?.lastAutoUpdateTimeoutMs,
240
- updatedFrom: cache?.updatedFrom,
241
- updatedTo: cache?.updatedTo,
242
- };
243
- await writeCache(updatedCache);
218
+
219
+ // Rate limit: skip if checked within the last 24 hours
220
+ if (cache && (now - cache.lastCheck) < CHECK_INTERVAL_MS) {
221
+ return;
222
+ }
223
+
224
+ const latestVersion = await fetchLatestVersion();
225
+ if (!latestVersion) return;
226
+
227
+ const currentVersion = getCurrentVersion();
228
+
229
+ // Update cache with check timestamp
230
+ const updatedCache: UpdateCache = {
231
+ lastCheck: now,
232
+ latestVersion,
233
+ lastAutoUpdate: cache?.lastAutoUpdate,
234
+ lastAutoUpdateStatus: cache?.lastAutoUpdateStatus,
235
+ lastAutoUpdateError: cache?.lastAutoUpdateError,
236
+ lastAutoUpdateTimedOut: cache?.lastAutoUpdateTimedOut,
237
+ lastAutoUpdateExitCode: cache?.lastAutoUpdateExitCode,
238
+ lastAutoUpdateSignal: cache?.lastAutoUpdateSignal,
239
+ lastAutoUpdateTimeoutMs: cache?.lastAutoUpdateTimeoutMs,
240
+ updatedFrom: cache?.updatedFrom,
241
+ updatedTo: cache?.updatedTo,
242
+ };
243
+ await writeCache(updatedCache);
244
244
 
245
245
  if (isNewer(latestVersion, currentVersion)) {
246
246
  if (mode === 'install') {
@@ -254,12 +254,12 @@ export async function checkForUpdates(): Promise<void> {
254
254
  }
255
255
  } catch {
256
256
  // Entire update check is best-effort — never crash the caller
257
- }
258
- }
259
-
260
- // ── Test helpers (exported for testing only) ──────────────────
261
-
262
- /** @internal */
257
+ }
258
+ }
259
+
260
+ // ── Test helpers (exported for testing only) ──────────────────
261
+
262
+ /** @internal */
263
263
  export const _testing = {
264
264
  CACHE_FILE,
265
265
  CHECK_INTERVAL_MS,
@@ -268,8 +268,8 @@ export const _testing = {
268
268
  parseAutoUpdateTimeoutMs,
269
269
  describeAutoUpdateFailure,
270
270
  fetchLatestVersion,
271
- installUpdateInBackground,
272
- writeCache,
273
- isNewer,
274
- getCurrentVersion,
275
- };
271
+ installUpdateInBackground,
272
+ writeCache,
273
+ isNewer,
274
+ getCurrentVersion,
275
+ };
@@ -1,7 +1,7 @@
1
- import pkg from '../../package.json';
2
-
3
- export function getCliVersion(): string {
4
- return typeof __MEMORIX_VERSION__ !== 'undefined'
5
- ? __MEMORIX_VERSION__
6
- : pkg.version;
7
- }
1
+ import pkg from '../../package.json';
2
+
3
+ export function getCliVersion(): string {
4
+ return typeof __MEMORIX_VERSION__ !== 'undefined'
5
+ ? __MEMORIX_VERSION__
6
+ : pkg.version;
7
+ }
@@ -5,4 +5,4 @@
5
5
  * Kept for backward-compat: `import { startWorkbench } from './workbench.js'`
6
6
  */
7
7
 
8
- export { startWorkbench } from './tui/index.js';
8
+ export { startWorkbench } from './tui/index.js';
@@ -4,10 +4,9 @@ import { getResolvedConfig } from '../config/resolved-config.js';
4
4
  import type { ProjectInfo } from '../types.js';
5
5
  import { backfillMissingObservationCodeRefs, type CodeRefBackfillResult } from './binder.js';
6
6
  import { collectCurrentProjectFacts, type CurrentProjectFacts } from './current-facts.js';
7
- import { indexProjectLite } from './lite-provider.js';
7
+ import { refreshProjectLite } from './lite-provider.js';
8
8
  import {
9
9
  buildProjectContextExplain,
10
- buildProjectContextOverview,
11
10
  type ProjectContextExplain,
12
11
  type ProjectContextObservation,
13
12
  type ProjectContextOverview,
@@ -28,7 +27,7 @@ export type AutoContextRefreshMode = 'auto' | 'always' | 'never';
28
27
  export interface AutoContextRefreshResult {
29
28
  mode: AutoContextRefreshMode;
30
29
  performed: boolean;
31
- reason: 'forced' | 'empty-index' | 'missing-scan-time' | 'stale-index' | 'fresh-enough' | 'disabled' | 'failed';
30
+ reason: 'forced' | 'empty-index' | 'missing-scan-time' | 'stale-index' | 'fresh-enough' | 'disabled' | 'queued' | 'failed';
32
31
  message: string;
33
32
  backfill?: CodeRefBackfillResult;
34
33
  }
@@ -102,12 +101,20 @@ export async function buildAutoProjectContext(input: {
102
101
  maxAgeMs?: number;
103
102
  now?: Date;
104
103
  exclude?: string[];
104
+ maxFileBytes?: number;
105
+ /**
106
+ * When supplied, a needed refresh is queued instead of running in this
107
+ * request. MCP and hook callers use this to keep their response path fast.
108
+ */
109
+ enqueueRefresh?: () => void | Promise<void>;
105
110
  }): Promise<AutoProjectContext> {
106
111
  const refreshMode = input.refresh ?? 'auto';
107
112
  const now = input.now ?? new Date();
108
113
  const task = input.task?.trim();
109
114
  const lens = resolveTaskLens(task);
110
- const exclude = input.exclude ?? getResolvedConfig({ projectRoot: input.project.rootPath }).codegraph.excludePatterns;
115
+ const codegraphConfig = getResolvedConfig({ projectRoot: input.project.rootPath }).codegraph;
116
+ const exclude = input.exclude ?? codegraphConfig.excludePatterns;
117
+ const maxFileBytes = input.maxFileBytes ?? codegraphConfig.maxFileBytes;
111
118
  const store = new CodeGraphStore();
112
119
  await store.init(input.dataDir);
113
120
 
@@ -125,40 +132,54 @@ export async function buildAutoProjectContext(input: {
125
132
  };
126
133
 
127
134
  if (decision.performed) {
128
- try {
129
- const indexed = await indexProjectLite({
130
- projectId: input.project.id,
131
- projectRoot: input.project.rootPath,
132
- exclude,
133
- });
134
- store.replaceProjectIndex(input.project.id, indexed);
135
- const backfill = await backfillMissingObservationCodeRefs(
136
- store,
137
- activeProjectObservations(input.observations, input.project.id) as any,
138
- );
139
- refresh = { ...refresh, backfill };
140
- } catch (error) {
141
- refresh = {
142
- mode: refreshMode,
143
- performed: false,
144
- reason: 'failed',
145
- message: `Project scan failed: ${error instanceof Error ? error.message : String(error)}`,
146
- };
135
+ if (input.enqueueRefresh) {
136
+ try {
137
+ await input.enqueueRefresh();
138
+ refresh = {
139
+ mode: refreshMode,
140
+ performed: false,
141
+ reason: 'queued',
142
+ message: 'Code Memory refresh queued; this brief uses the latest completed scan.',
143
+ };
144
+ } catch (error) {
145
+ refresh = {
146
+ mode: refreshMode,
147
+ performed: false,
148
+ reason: 'failed',
149
+ message: `Could not queue Code Memory refresh: ${error instanceof Error ? error.message : String(error)}`,
150
+ };
151
+ }
152
+ } else {
153
+ try {
154
+ await refreshProjectLite(store, {
155
+ projectId: input.project.id,
156
+ projectRoot: input.project.rootPath,
157
+ exclude,
158
+ maxFileBytes,
159
+ });
160
+ const backfill = await backfillMissingObservationCodeRefs(
161
+ store,
162
+ activeProjectObservations(input.observations, input.project.id) as any,
163
+ );
164
+ refresh = { ...refresh, backfill };
165
+ } catch (error) {
166
+ refresh = {
167
+ mode: refreshMode,
168
+ performed: false,
169
+ reason: 'failed',
170
+ message: `Project scan failed: ${error instanceof Error ? error.message : String(error)}`,
171
+ };
172
+ }
147
173
  }
148
174
  }
149
175
 
150
- const overview = buildProjectContextOverview({
151
- project: input.project,
152
- store,
153
- observations: input.observations,
154
- exclude,
155
- });
156
176
  const explain = buildProjectContextExplain({
157
177
  project: input.project,
158
178
  store,
159
179
  observations: input.observations,
160
180
  exclude,
161
181
  });
182
+ const overview = explain.overview;
162
183
 
163
184
  return {
164
185
  project: input.project,