memorix 1.1.10 → 1.1.12

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 (201) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +2630 -886
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/dashboard/static/app.js +29 -3
  8. package/dist/dashboard/static/index.html +201 -201
  9. package/dist/dashboard/static/style.css +3762 -3584
  10. package/dist/index.js +2884 -1283
  11. package/dist/index.js.map +1 -1
  12. package/dist/maintenance-runner.d.ts +40 -0
  13. package/dist/maintenance-runner.js +8409 -0
  14. package/dist/maintenance-runner.js.map +1 -0
  15. package/dist/memcode-runtime/CHANGELOG.md +38 -0
  16. package/dist/memcode-runtime/package.json +4 -4
  17. package/dist/sdk.js +2881 -1280
  18. package/dist/sdk.js.map +1 -1
  19. package/docs/AGENT_OPERATOR_PLAYBOOK.md +1 -1
  20. package/docs/CONFIGURATION.md +15 -4
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/GIT_MEMORY.md +2 -2
  23. package/docs/INTEGRATIONS.md +1 -1
  24. package/docs/PERFORMANCE.md +23 -0
  25. package/docs/dev-log/progress.txt +62 -26
  26. package/docs/hooks-architecture.md +24 -12
  27. package/package.json +1 -1
  28. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -0
  29. package/plugins/codex/memorix/hooks/hooks.json +30 -0
  30. package/src/audit/index.ts +156 -156
  31. package/src/cli/commands/audit-list.ts +89 -89
  32. package/src/cli/commands/background.ts +659 -659
  33. package/src/cli/commands/cleanup.ts +279 -255
  34. package/src/cli/commands/codegraph.ts +7 -5
  35. package/src/cli/commands/formation.ts +48 -48
  36. package/src/cli/commands/git-hook-install.ts +111 -111
  37. package/src/cli/commands/handoff.ts +66 -66
  38. package/src/cli/commands/hooks-install.ts +2 -1
  39. package/src/cli/commands/hooks-status.ts +63 -63
  40. package/src/cli/commands/ingest-commit.ts +153 -153
  41. package/src/cli/commands/ingest-image.ts +73 -73
  42. package/src/cli/commands/ingest-log.ts +180 -180
  43. package/src/cli/commands/ingest.ts +44 -44
  44. package/src/cli/commands/integrate-shared.ts +15 -15
  45. package/src/cli/commands/lock.ts +96 -96
  46. package/src/cli/commands/message.ts +121 -121
  47. package/src/cli/commands/poll.ts +70 -70
  48. package/src/cli/commands/purge-all-memory.ts +85 -85
  49. package/src/cli/commands/purge-project-memory.ts +83 -83
  50. package/src/cli/commands/reasoning.ts +132 -132
  51. package/src/cli/commands/retention.ts +107 -108
  52. package/src/cli/commands/serve-http.ts +49 -22
  53. package/src/cli/commands/serve-shared.ts +118 -118
  54. package/src/cli/commands/setup.ts +7 -1
  55. package/src/cli/commands/skills.ts +123 -123
  56. package/src/cli/commands/task.ts +192 -192
  57. package/src/cli/commands/transfer.ts +73 -73
  58. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  59. package/src/cli/tui/ChatView.tsx +234 -234
  60. package/src/cli/tui/CommandBar.tsx +312 -312
  61. package/src/cli/tui/ContextRail.tsx +118 -118
  62. package/src/cli/tui/HeaderBar.tsx +72 -72
  63. package/src/cli/tui/LogoBanner.tsx +51 -51
  64. package/src/cli/tui/Panels.tsx +632 -632
  65. package/src/cli/tui/Sidebar.tsx +179 -179
  66. package/src/cli/tui/chat-service.ts +736 -742
  67. package/src/cli/tui/data.ts +545 -547
  68. package/src/cli/tui/index.ts +41 -41
  69. package/src/cli/tui/markdown-render.tsx +371 -371
  70. package/src/cli/tui/theme.ts +178 -178
  71. package/src/cli/tui/use-mouse.ts +157 -157
  72. package/src/cli/tui/useNavigation.ts +56 -56
  73. package/src/cli/update-checker.ts +211 -211
  74. package/src/cli/version.ts +7 -7
  75. package/src/cli/workbench.ts +1 -1
  76. package/src/codegraph/auto-context.ts +49 -22
  77. package/src/codegraph/exclude.ts +10 -0
  78. package/src/codegraph/lite-provider.ts +146 -16
  79. package/src/codegraph/project-context.ts +23 -0
  80. package/src/codegraph/store.ts +123 -0
  81. package/src/compact/token-budget.ts +74 -74
  82. package/src/config/behavior.ts +97 -59
  83. package/src/config/resolved-config.ts +4 -0
  84. package/src/config/toml-loader.ts +2 -0
  85. package/src/config/yaml-loader.ts +3 -1
  86. package/src/dashboard/project-classification.ts +64 -64
  87. package/src/dashboard/server.ts +31 -30
  88. package/src/dashboard/static/index.html +201 -201
  89. package/src/dashboard/static/style.css +3762 -3584
  90. package/src/embedding/api-provider.ts +105 -6
  91. package/src/embedding/fastembed-provider.ts +142 -142
  92. package/src/embedding/provider.ts +31 -3
  93. package/src/embedding/transformers-provider.ts +111 -111
  94. package/src/git/extractor.ts +209 -209
  95. package/src/git/hooks-path.ts +85 -85
  96. package/src/git/noise-filter.ts +226 -210
  97. package/src/hooks/handler.ts +61 -16
  98. package/src/hooks/installers/index.ts +48 -7
  99. package/src/hooks/normalizer.ts +15 -0
  100. package/src/hooks/pattern-detector.ts +173 -173
  101. package/src/hooks/significance-filter.ts +250 -250
  102. package/src/llm/memory-manager.ts +328 -328
  103. package/src/llm/provider.ts +885 -885
  104. package/src/llm/quality.ts +248 -248
  105. package/src/memory/attribution-guard.ts +249 -249
  106. package/src/memory/auto-relations.ts +107 -107
  107. package/src/memory/consolidation.ts +328 -302
  108. package/src/memory/disclosure-policy.ts +141 -141
  109. package/src/memory/entity-extractor.ts +197 -197
  110. package/src/memory/export-import.ts +3 -1
  111. package/src/memory/formation/evaluate.ts +217 -217
  112. package/src/memory/formation/extract.ts +361 -361
  113. package/src/memory/formation/index.ts +417 -417
  114. package/src/memory/formation/resolve.ts +344 -344
  115. package/src/memory/formation/types.ts +315 -315
  116. package/src/memory/freshness.ts +122 -122
  117. package/src/memory/graph.ts +197 -197
  118. package/src/memory/observations.ts +181 -42
  119. package/src/memory/refs.ts +94 -94
  120. package/src/memory/retention.ts +511 -433
  121. package/src/memory/secret-filter.ts +79 -79
  122. package/src/memory/session.ts +542 -523
  123. package/src/multimodal/image-loader.ts +143 -143
  124. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  125. package/src/orchestrate/adapters/claude.ts +111 -111
  126. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  127. package/src/orchestrate/adapters/codex.ts +41 -41
  128. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  129. package/src/orchestrate/adapters/gemini.ts +42 -42
  130. package/src/orchestrate/adapters/index.ts +73 -73
  131. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  132. package/src/orchestrate/adapters/opencode.ts +47 -47
  133. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  134. package/src/orchestrate/adapters/types.ts +77 -77
  135. package/src/orchestrate/capability-router.ts +284 -284
  136. package/src/orchestrate/context-compact.ts +188 -188
  137. package/src/orchestrate/cost-tracker.ts +219 -219
  138. package/src/orchestrate/error-recovery.ts +191 -191
  139. package/src/orchestrate/evidence.ts +140 -140
  140. package/src/orchestrate/ledger.ts +110 -110
  141. package/src/orchestrate/memorix-bridge.ts +380 -380
  142. package/src/orchestrate/output-budget.ts +80 -80
  143. package/src/orchestrate/permission.ts +152 -152
  144. package/src/orchestrate/pipeline-trace.ts +131 -131
  145. package/src/orchestrate/prompt-builder.ts +155 -155
  146. package/src/orchestrate/ring-buffer.ts +37 -37
  147. package/src/orchestrate/task-graph.ts +389 -389
  148. package/src/orchestrate/verify-gate.ts +219 -219
  149. package/src/orchestrate/worktree.ts +232 -232
  150. package/src/project/aliases.ts +374 -374
  151. package/src/project/detector.ts +268 -268
  152. package/src/rules/adapters/claude-code.ts +99 -99
  153. package/src/rules/adapters/codex.ts +97 -97
  154. package/src/rules/adapters/copilot.ts +124 -124
  155. package/src/rules/adapters/cursor.ts +114 -114
  156. package/src/rules/adapters/kiro.ts +126 -126
  157. package/src/rules/adapters/trae.ts +56 -56
  158. package/src/rules/adapters/windsurf.ts +83 -83
  159. package/src/rules/syncer.ts +235 -235
  160. package/src/runtime/control-plane-maintenance.ts +62 -0
  161. package/src/runtime/isolated-maintenance.ts +187 -0
  162. package/src/runtime/maintenance-jobs.ts +512 -0
  163. package/src/runtime/maintenance-runner.ts +96 -0
  164. package/src/runtime/maintenance-targets.ts +59 -0
  165. package/src/runtime/project-maintenance.ts +274 -0
  166. package/src/sdk.ts +327 -327
  167. package/src/search/intent-detector.ts +289 -289
  168. package/src/search/query-expansion.ts +52 -52
  169. package/src/server/formation-timeout.ts +27 -27
  170. package/src/server.ts +212 -224
  171. package/src/skills/mini-skills.ts +386 -386
  172. package/src/store/chat-store.ts +119 -119
  173. package/src/store/file-lock.ts +100 -100
  174. package/src/store/graph-store.ts +249 -249
  175. package/src/store/mini-skill-store.ts +349 -349
  176. package/src/store/obs-store.ts +293 -255
  177. package/src/store/orama-store.ts +169 -17
  178. package/src/store/persistence-json.ts +212 -212
  179. package/src/store/persistence.ts +291 -291
  180. package/src/store/project-affinity.ts +195 -195
  181. package/src/store/session-store.ts +268 -259
  182. package/src/store/sqlite-db.ts +40 -0
  183. package/src/store/sqlite-store.ts +429 -339
  184. package/src/team/event-bus.ts +76 -76
  185. package/src/team/file-locks.ts +173 -173
  186. package/src/team/handoff.ts +167 -167
  187. package/src/team/messages.ts +203 -203
  188. package/src/team/poll.ts +132 -132
  189. package/src/team/tasks.ts +211 -211
  190. package/src/wiki/generator.ts +237 -237
  191. package/src/wiki/knowledge-graph.ts +334 -334
  192. package/src/wiki/types.ts +85 -85
  193. package/src/workspace/mcp-adapters/codex.ts +191 -191
  194. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  195. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  196. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  197. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  198. package/src/workspace/mcp-adapters/trae.ts +134 -134
  199. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  200. package/src/workspace/sanitizer.ts +60 -60
  201. 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,7 +4,7 @@ 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
10
  type ProjectContextExplain,
@@ -27,7 +27,7 @@ export type AutoContextRefreshMode = 'auto' | 'always' | 'never';
27
27
  export interface AutoContextRefreshResult {
28
28
  mode: AutoContextRefreshMode;
29
29
  performed: boolean;
30
- 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';
31
31
  message: string;
32
32
  backfill?: CodeRefBackfillResult;
33
33
  }
@@ -101,12 +101,20 @@ export async function buildAutoProjectContext(input: {
101
101
  maxAgeMs?: number;
102
102
  now?: Date;
103
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>;
104
110
  }): Promise<AutoProjectContext> {
105
111
  const refreshMode = input.refresh ?? 'auto';
106
112
  const now = input.now ?? new Date();
107
113
  const task = input.task?.trim();
108
114
  const lens = resolveTaskLens(task);
109
- 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;
110
118
  const store = new CodeGraphStore();
111
119
  await store.init(input.dataDir);
112
120
 
@@ -124,25 +132,44 @@ export async function buildAutoProjectContext(input: {
124
132
  };
125
133
 
126
134
  if (decision.performed) {
127
- try {
128
- const indexed = await indexProjectLite({
129
- projectId: input.project.id,
130
- projectRoot: input.project.rootPath,
131
- exclude,
132
- });
133
- store.replaceProjectIndex(input.project.id, indexed);
134
- const backfill = await backfillMissingObservationCodeRefs(
135
- store,
136
- activeProjectObservations(input.observations, input.project.id) as any,
137
- );
138
- refresh = { ...refresh, backfill };
139
- } catch (error) {
140
- refresh = {
141
- mode: refreshMode,
142
- performed: false,
143
- reason: 'failed',
144
- message: `Project scan failed: ${error instanceof Error ? error.message : String(error)}`,
145
- };
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
+ }
146
173
  }
147
174
  }
148
175
 
@@ -9,6 +9,16 @@ export const DEFAULT_CODEGRAPH_EXCLUDES = [
9
9
  '.tmp/**',
10
10
  '.worktrees/**',
11
11
  '.claude/worktrees/**',
12
+ 'vendor/**',
13
+ '.venv/**',
14
+ 'venv/**',
15
+ '.tox/**',
16
+ 'target/**',
17
+ '.gradle/**',
18
+ '.mvn/**',
19
+ 'obj/**',
20
+ 'Pods/**',
21
+ 'DerivedData/**',
12
22
  ];
13
23
 
14
24
  export function normalizeCodeGraphExcludePatterns(exclude?: string[]): string[] {