memorix 1.6.1 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/TEAM.md +86 -86
  3. package/dist/cli/index.js +2582 -1845
  4. package/dist/index.js +1355 -683
  5. package/dist/maintenance-runner.js +1621 -1096
  6. package/dist/media-audio-runner.js +1612 -1088
  7. package/dist/media-video-runner.js +1612 -1088
  8. package/dist/memcode-runtime/CHANGELOG.md +35 -0
  9. package/dist/sdk.js +2007 -1396
  10. package/dist/vector-backfill-runner.js +1610 -1086
  11. package/docs/DESIGN_DECISIONS.md +357 -357
  12. package/docs/PERFORMANCE.md +15 -0
  13. package/package.json +2 -1
  14. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  15. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  16. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  17. package/plugins/copilot/memorix/plugin.json +1 -1
  18. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  19. package/plugins/omp/memorix/package.json +1 -1
  20. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  21. package/plugins/pi/memorix/package.json +1 -1
  22. package/server.json +2 -2
  23. package/src/audit/index.ts +156 -156
  24. package/src/cli/commands/background.ts +51 -5
  25. package/src/cli/commands/formation.ts +48 -48
  26. package/src/cli/commands/git-hook-install.ts +111 -111
  27. package/src/cli/commands/handoff.ts +54 -54
  28. package/src/cli/commands/hooks-status.ts +63 -63
  29. package/src/cli/commands/ingest.ts +44 -44
  30. package/src/cli/commands/integrate-shared.ts +15 -15
  31. package/src/cli/commands/lock.ts +82 -82
  32. package/src/cli/commands/message.ts +104 -104
  33. package/src/cli/commands/operator-shared.ts +18 -11
  34. package/src/cli/commands/poll.ts +58 -58
  35. package/src/cli/commands/reasoning.ts +118 -118
  36. package/src/cli/commands/serve-http.ts +29 -7
  37. package/src/cli/commands/serve-shared.ts +49 -6
  38. package/src/cli/commands/serve.ts +4 -0
  39. package/src/cli/commands/skills.ts +114 -114
  40. package/src/cli/commands/task.ts +167 -167
  41. package/src/cli/commands/transfer.ts +47 -47
  42. package/src/cli/index.ts +406 -406
  43. package/src/cli/tui/ChatView.tsx +234 -234
  44. package/src/cli/tui/CommandBar.tsx +312 -312
  45. package/src/cli/tui/ContextRail.tsx +118 -118
  46. package/src/cli/tui/HeaderBar.tsx +72 -72
  47. package/src/cli/tui/LogoBanner.tsx +51 -51
  48. package/src/cli/tui/index.ts +41 -41
  49. package/src/cli/version.ts +7 -7
  50. package/src/cli/workbench.ts +1 -1
  51. package/src/dashboard/project-classification.ts +64 -64
  52. package/src/embedding/api-provider.ts +503 -27
  53. package/src/embedding/provider.ts +20 -3
  54. package/src/git/hooks-path.ts +85 -85
  55. package/src/hooks/lifecycle.ts +1 -0
  56. package/src/hooks/pattern-detector.ts +173 -173
  57. package/src/hooks/significance-filter.ts +250 -250
  58. package/src/llm/memory-manager.ts +328 -328
  59. package/src/llm/quality.ts +248 -248
  60. package/src/memory/attribution-guard.ts +249 -249
  61. package/src/memory/disclosure-policy.ts +135 -135
  62. package/src/memory/entity-extractor.ts +197 -197
  63. package/src/memory/formation/evaluate.ts +217 -217
  64. package/src/memory/freshness.ts +122 -122
  65. package/src/memory/graph.ts +197 -197
  66. package/src/memory/observations.ts +68 -18
  67. package/src/memory/refs.ts +94 -94
  68. package/src/memory/secret-filter.ts +79 -79
  69. package/src/memory/session.ts +13 -3
  70. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  71. package/src/orchestrate/adapters/claude.ts +111 -111
  72. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  73. package/src/orchestrate/adapters/codex.ts +41 -41
  74. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  75. package/src/orchestrate/adapters/gemini.ts +42 -42
  76. package/src/orchestrate/adapters/index.ts +73 -73
  77. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  78. package/src/orchestrate/adapters/opencode.ts +47 -47
  79. package/src/orchestrate/adapters/types.ts +77 -77
  80. package/src/orchestrate/capability-router.ts +284 -284
  81. package/src/orchestrate/cost-tracker.ts +219 -219
  82. package/src/orchestrate/error-recovery.ts +191 -191
  83. package/src/orchestrate/evidence.ts +140 -140
  84. package/src/orchestrate/ledger.ts +110 -110
  85. package/src/orchestrate/memorix-bridge.ts +343 -343
  86. package/src/orchestrate/output-budget.ts +80 -80
  87. package/src/orchestrate/permission.ts +152 -152
  88. package/src/orchestrate/pipeline-trace.ts +131 -131
  89. package/src/orchestrate/prompt-builder.ts +155 -155
  90. package/src/orchestrate/ring-buffer.ts +37 -37
  91. package/src/orchestrate/task-graph.ts +389 -389
  92. package/src/project/aliases.ts +374 -374
  93. package/src/project/launch-root.ts +115 -0
  94. package/src/rules/adapters/trae.ts +56 -56
  95. package/src/rules/syncer.ts +235 -235
  96. package/src/runtime/isolated-maintenance.ts +12 -1
  97. package/src/sdk.ts +12 -8
  98. package/src/search/query-expansion.ts +52 -52
  99. package/src/server/formation-timeout.ts +27 -27
  100. package/src/server.ts +38 -5
  101. package/src/skills/mini-skills.ts +386 -386
  102. package/src/store/chat-store.ts +119 -119
  103. package/src/store/graph-store.ts +249 -249
  104. package/src/store/mini-skill-store.ts +349 -349
  105. package/src/store/obs-store.ts +2 -2
  106. package/src/store/orama-store.ts +21 -3
  107. package/src/store/persistence-json.ts +212 -212
  108. package/src/store/persistence.ts +291 -291
  109. package/src/store/project-affinity.ts +195 -195
  110. package/src/store/sqlite-db.ts +2 -0
  111. package/src/store/sqlite-store.ts +19 -4
  112. package/src/team/event-bus.ts +76 -76
  113. package/src/team/handoff.ts +161 -161
  114. package/src/team/poll.ts +132 -132
  115. package/src/workspace/mcp-adapters/codex.ts +191 -191
  116. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  117. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  118. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  119. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  120. package/src/workspace/mcp-adapters/trae.ts +134 -134
  121. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  122. package/src/workspace/sanitizer.ts +60 -60
@@ -78,6 +78,7 @@ On the release development machine used for this check, the healthy HTTP service
78
78
  | `MEMORIX_RERANK_TIMEOUT_MS` | provider default | Bound slow LLM rerank calls |
79
79
  | `memorix memory search --quality fast` | n/a | Force a fully local retrieval path for a latency-sensitive call |
80
80
  | `npm run benchmark:retrieval -- --records 1000 --runs 100` | n/a | Reproduce hot in-process lexical retrieval latency; not an end-to-end claim |
81
+ | `npm run gate:large-store -- --records 40000` | n/a | Exercise SDK, HTTP MCP, hook persistence, cache integrity, and reopen behavior against a large isolated store |
81
82
  | `[codegraph].max_file_bytes` | `2097152` | Raise only when a large file is intentional source that should enter Code Memory |
82
83
  | `memorix retention status` | report only | Inspect whether memory growth needs cleanup |
83
84
  | `memorix retention archive` | explicit | Archive expired memories when the project gets noisy |
@@ -96,6 +97,20 @@ On the release development machine used for this check, the healthy HTTP service
96
97
  `/api/maintenance` on that local dashboard before assuming a Code Memory scan
97
98
  or lifecycle task completed.
98
99
 
100
+ ## Large-Store Release Gate
101
+
102
+ `npm run gate:large-store -- --records 40000` builds Memorix and creates a
103
+ temporary Git project with an isolated data directory. It runs with embeddings
104
+ disabled, so it does not use provider credentials or make paid network calls.
105
+ The gate measures steady-state writes, first lexical search, process memory,
106
+ HTTP MCP and hook persistence, and an SDK close/reopen cycle. It also verifies
107
+ that the personal hook candidate remains durable on disk without becoming
108
+ visible to an unbound SDK reader.
109
+
110
+ Results are machine-specific and should be compared on the same OS and Node
111
+ version. The release gate is an integrity and regression check, not a public
112
+ latency guarantee.
113
+
99
114
  ## Current Optimization Opportunities
100
115
 
101
116
  These are not release blockers, but they are reasonable future improvements:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memorix",
3
3
  "mcpName": "io.github.AVIDS2/memorix",
4
- "version": "1.6.1",
4
+ "version": "1.6.3",
5
5
  "description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -67,6 +67,7 @@
67
67
  "test:e2e": "vitest run tests/e2e/",
68
68
  "benchmark:codegraph": "node scripts/benchmark-codegraph-provider.cjs",
69
69
  "benchmark:retrieval": "npm run build && node scripts/benchmark-retrieval.mjs",
70
+ "gate:large-store": "npm run build && node scripts/large-store-gate.mjs",
70
71
  "generate:star-history": "node scripts/generate-star-history.mjs",
71
72
  "sync:mcp-registry": "node scripts/sync-mcp-registry-manifest.mjs",
72
73
  "check:mcp-registry": "node scripts/check-mcp-registry-manifest.mjs",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memorix",
4
- "version": "1.6.1",
4
+ "version": "1.6.3",
5
5
  "description": "Shared workspace memory for Claude Code and other AI coding agents.",
6
6
  "author": {
7
7
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Local-first project memory for CodeBuddy Code and other coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Shared workspace memory for Codex and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Shared local workspace memory for GitHub Copilot CLI and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "mcpServers": {
5
5
  "memorix": {
6
6
  "command": "memorix",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-omp-package",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Memorix shared workspace memory package for Oh-my-Pi.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Shared workspace memory for OpenClaw and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-pi-package",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Memorix shared workspace memory package for Pi coding agent.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/server.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "url": "https://github.com/AVIDS2/memorix",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.6.1",
10
+ "version": "1.6.3",
11
11
  "websiteUrl": "https://github.com/AVIDS2/memorix#readme",
12
12
  "icons": [
13
13
  {
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "memorix",
25
- "version": "1.6.1",
25
+ "version": "1.6.3",
26
26
  "runtimeHint": "npx",
27
27
  "packageArguments": [
28
28
  {
@@ -1,156 +1,156 @@
1
- /**
2
- * Audit Module - Track Memorix-written files
3
- *
4
- * Records all files written by Memorix to distinguish them from
5
- * project-native files. Provides audit trail for cleanup and rollback.
6
- */
7
-
8
- import * as fs from 'node:fs/promises';
9
- import * as path from 'node:path';
10
- import { homedir } from 'node:os';
11
-
12
- function getAuditFilePath(): string {
13
- return process.env.MEMORIX_AUDIT_FILE || path.join(homedir(), '.memorix', 'audit.json');
14
- }
15
-
16
- export interface AuditEntry {
17
- type: 'hook' | 'rule' | 'other';
18
- agent?: string;
19
- path: string;
20
- createdAt: string;
21
- }
22
-
23
- export interface ProjectAudit {
24
- projectRoot: string;
25
- installedAt: string;
26
- entries: AuditEntry[];
27
- }
28
-
29
- export interface AuditData {
30
- version: string;
31
- projects: Record<string, ProjectAudit>;
32
- }
33
-
34
- /**
35
- * Load audit data from disk.
36
- */
37
- export async function loadAudit(): Promise<AuditData> {
38
- try {
39
- const content = await fs.readFile(getAuditFilePath(), 'utf-8');
40
- return JSON.parse(content) as AuditData;
41
- } catch {
42
- // No audit file yet
43
- return {
44
- version: '1.0.0',
45
- projects: {},
46
- };
47
- }
48
- }
49
-
50
- /**
51
- * Save audit data to disk.
52
- */
53
- export async function saveAudit(data: AuditData): Promise<void> {
54
- const auditFile = getAuditFilePath();
55
- const dir = path.dirname(auditFile);
56
- await fs.mkdir(dir, { recursive: true });
57
- await fs.writeFile(auditFile, JSON.stringify(data, null, 2), 'utf-8');
58
- }
59
-
60
- /**
61
- * Get project ID from project root via .git detection.
62
- */
63
- export function getProjectId(projectRoot: string): string {
64
- try {
65
- const { detectProject } = require('../project/detector.js');
66
- const project = detectProject(projectRoot);
67
- if (project) return project.id;
68
- } catch { /* fallback below */ }
69
- const normalized = projectRoot.replace(/\\/g, '/');
70
- return `untracked/${normalized}`;
71
- }
72
-
73
- /**
74
- * Record a file written by Memorix.
75
- */
76
- export async function recordFile(
77
- projectRoot: string,
78
- type: AuditEntry['type'],
79
- filePath: string,
80
- agent?: string,
81
- ): Promise<void> {
82
- const data = await loadAudit();
83
- const projectId = getProjectId(projectRoot);
84
-
85
- if (!data.projects[projectId]) {
86
- data.projects[projectId] = {
87
- projectRoot,
88
- installedAt: new Date().toISOString(),
89
- entries: [],
90
- };
91
- }
92
-
93
- // Check if entry already exists
94
- const existingIndex = data.projects[projectId].entries.findIndex(
95
- (e) => e.path === filePath
96
- );
97
-
98
- if (existingIndex === -1) {
99
- data.projects[projectId].entries.push({
100
- type,
101
- agent,
102
- path: filePath,
103
- createdAt: new Date().toISOString(),
104
- });
105
- }
106
-
107
- await saveAudit(data);
108
- }
109
-
110
- /**
111
- * Get all files written by Memorix for a project.
112
- */
113
- export async function getProjectFiles(projectRoot: string): Promise<AuditEntry[]> {
114
- const data = await loadAudit();
115
- const projectId = getProjectId(projectRoot);
116
- return data.projects[projectId]?.entries || [];
117
- }
118
-
119
- /**
120
- * Remove a file from audit (when uninstalled).
121
- */
122
- export async function removeFile(projectRoot: string, filePath: string): Promise<void> {
123
- const data = await loadAudit();
124
- const projectId = getProjectId(projectRoot);
125
-
126
- if (!data.projects[projectId]) return;
127
-
128
- data.projects[projectId].entries = data.projects[projectId].entries.filter(
129
- (e) => e.path !== filePath
130
- );
131
-
132
- // If no entries left, remove the project
133
- if (data.projects[projectId].entries.length === 0) {
134
- delete data.projects[projectId];
135
- }
136
-
137
- await saveAudit(data);
138
- }
139
-
140
- /**
141
- * Get all audit entries across all projects.
142
- */
143
- export async function getAllAuditEntries(): Promise<
144
- Array<{ projectId: string; entry: AuditEntry }>
145
- > {
146
- const data = await loadAudit();
147
- const entries: Array<{ projectId: string; entry: AuditEntry }> = [];
148
-
149
- for (const [projectId, project] of Object.entries(data.projects)) {
150
- for (const entry of project.entries) {
151
- entries.push({ projectId, entry });
152
- }
153
- }
154
-
155
- return entries;
156
- }
1
+ /**
2
+ * Audit Module - Track Memorix-written files
3
+ *
4
+ * Records all files written by Memorix to distinguish them from
5
+ * project-native files. Provides audit trail for cleanup and rollback.
6
+ */
7
+
8
+ import * as fs from 'node:fs/promises';
9
+ import * as path from 'node:path';
10
+ import { homedir } from 'node:os';
11
+
12
+ function getAuditFilePath(): string {
13
+ return process.env.MEMORIX_AUDIT_FILE || path.join(homedir(), '.memorix', 'audit.json');
14
+ }
15
+
16
+ export interface AuditEntry {
17
+ type: 'hook' | 'rule' | 'other';
18
+ agent?: string;
19
+ path: string;
20
+ createdAt: string;
21
+ }
22
+
23
+ export interface ProjectAudit {
24
+ projectRoot: string;
25
+ installedAt: string;
26
+ entries: AuditEntry[];
27
+ }
28
+
29
+ export interface AuditData {
30
+ version: string;
31
+ projects: Record<string, ProjectAudit>;
32
+ }
33
+
34
+ /**
35
+ * Load audit data from disk.
36
+ */
37
+ export async function loadAudit(): Promise<AuditData> {
38
+ try {
39
+ const content = await fs.readFile(getAuditFilePath(), 'utf-8');
40
+ return JSON.parse(content) as AuditData;
41
+ } catch {
42
+ // No audit file yet
43
+ return {
44
+ version: '1.0.0',
45
+ projects: {},
46
+ };
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Save audit data to disk.
52
+ */
53
+ export async function saveAudit(data: AuditData): Promise<void> {
54
+ const auditFile = getAuditFilePath();
55
+ const dir = path.dirname(auditFile);
56
+ await fs.mkdir(dir, { recursive: true });
57
+ await fs.writeFile(auditFile, JSON.stringify(data, null, 2), 'utf-8');
58
+ }
59
+
60
+ /**
61
+ * Get project ID from project root via .git detection.
62
+ */
63
+ export function getProjectId(projectRoot: string): string {
64
+ try {
65
+ const { detectProject } = require('../project/detector.js');
66
+ const project = detectProject(projectRoot);
67
+ if (project) return project.id;
68
+ } catch { /* fallback below */ }
69
+ const normalized = projectRoot.replace(/\\/g, '/');
70
+ return `untracked/${normalized}`;
71
+ }
72
+
73
+ /**
74
+ * Record a file written by Memorix.
75
+ */
76
+ export async function recordFile(
77
+ projectRoot: string,
78
+ type: AuditEntry['type'],
79
+ filePath: string,
80
+ agent?: string,
81
+ ): Promise<void> {
82
+ const data = await loadAudit();
83
+ const projectId = getProjectId(projectRoot);
84
+
85
+ if (!data.projects[projectId]) {
86
+ data.projects[projectId] = {
87
+ projectRoot,
88
+ installedAt: new Date().toISOString(),
89
+ entries: [],
90
+ };
91
+ }
92
+
93
+ // Check if entry already exists
94
+ const existingIndex = data.projects[projectId].entries.findIndex(
95
+ (e) => e.path === filePath
96
+ );
97
+
98
+ if (existingIndex === -1) {
99
+ data.projects[projectId].entries.push({
100
+ type,
101
+ agent,
102
+ path: filePath,
103
+ createdAt: new Date().toISOString(),
104
+ });
105
+ }
106
+
107
+ await saveAudit(data);
108
+ }
109
+
110
+ /**
111
+ * Get all files written by Memorix for a project.
112
+ */
113
+ export async function getProjectFiles(projectRoot: string): Promise<AuditEntry[]> {
114
+ const data = await loadAudit();
115
+ const projectId = getProjectId(projectRoot);
116
+ return data.projects[projectId]?.entries || [];
117
+ }
118
+
119
+ /**
120
+ * Remove a file from audit (when uninstalled).
121
+ */
122
+ export async function removeFile(projectRoot: string, filePath: string): Promise<void> {
123
+ const data = await loadAudit();
124
+ const projectId = getProjectId(projectRoot);
125
+
126
+ if (!data.projects[projectId]) return;
127
+
128
+ data.projects[projectId].entries = data.projects[projectId].entries.filter(
129
+ (e) => e.path !== filePath
130
+ );
131
+
132
+ // If no entries left, remove the project
133
+ if (data.projects[projectId].entries.length === 0) {
134
+ delete data.projects[projectId];
135
+ }
136
+
137
+ await saveAudit(data);
138
+ }
139
+
140
+ /**
141
+ * Get all audit entries across all projects.
142
+ */
143
+ export async function getAllAuditEntries(): Promise<
144
+ Array<{ projectId: string; entry: AuditEntry }>
145
+ > {
146
+ const data = await loadAudit();
147
+ const entries: Array<{ projectId: string; entry: AuditEntry }> = [];
148
+
149
+ for (const [projectId, project] of Object.entries(data.projects)) {
150
+ for (const entry of project.entries) {
151
+ entries.push({ projectId, entry });
152
+ }
153
+ }
154
+
155
+ return entries;
156
+ }
@@ -19,9 +19,13 @@
19
19
 
20
20
  import { defineCommand } from 'citty';
21
21
  import * as fs from 'node:fs';
22
+ import path from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
22
24
  import { spawn, execFileSync } from 'node:child_process';
23
25
  import { randomBytes } from 'node:crypto';
26
+ import os from 'node:os';
24
27
  import { parseTcpPort } from '../port.js';
28
+ import { homeProjectRootError, inheritProjectRootFromHome, isHomeDirectory } from '../../project/launch-root.js';
25
29
 
26
30
  // ============================================================
27
31
  // Paths & Types
@@ -38,6 +42,16 @@ interface BackgroundState {
38
42
  startCwd?: string;
39
43
  }
40
44
 
45
+ export function resolveBackgroundCwd(cwd: string, homeDir: string = os.homedir()): string {
46
+ if (!isHomeDirectory(cwd, homeDir)) return cwd;
47
+ const inherited = inheritProjectRootFromHome({
48
+ homeDir,
49
+ envProjectRoot: process.env.MEMORIX_PROJECT_ROOT,
50
+ });
51
+ if (inherited) return inherited;
52
+ throw new Error(homeProjectRootError(cwd));
53
+ }
54
+
41
55
  function getMemorixDir(): string {
42
56
  const home = process.env.HOME || process.env.USERPROFILE || '';
43
57
  // Use join-style path to ensure native separators on Windows
@@ -73,6 +87,33 @@ function parseBackgroundPort(value: string | undefined): number {
73
87
  return parseTcpPort(value, 3211);
74
88
  }
75
89
 
90
+ /**
91
+ * Resolve the CLI file that `background start` should spawn.
92
+ * `process.argv[1]` follows the launcher shim and can point at a different
93
+ * install (for example `$HOME/node_modules/memorix` after `bun add` in $HOME).
94
+ * When this module is bundled into `dist/cli/index.js`, `../index.js` is the
95
+ * library stdio entry (`dist/index.js`) — never spawn that.
96
+ */
97
+ function resolveBackgroundCliEntryFrom(here: string, argv1: string): string {
98
+ const dir = path.dirname(here);
99
+ if (path.basename(here) === 'index.js' && path.basename(dir) === 'cli') {
100
+ return here;
101
+ }
102
+ const compiledSibling = path.resolve(dir, '..', 'index.js');
103
+ if (fs.existsSync(compiledSibling) && path.basename(path.dirname(compiledSibling)) === 'cli') {
104
+ return compiledSibling;
105
+ }
106
+ return argv1;
107
+ }
108
+
109
+ function resolveBackgroundCliEntry(argv1 = process.argv[1]): string {
110
+ try {
111
+ return resolveBackgroundCliEntryFrom(fileURLToPath(import.meta.url), argv1);
112
+ } catch {
113
+ return argv1;
114
+ }
115
+ }
116
+
76
117
  function parseLogLines(value: string | undefined): number {
77
118
  return parseBoundedPositiveInteger(value, 50, 'Log line count', 10_000);
78
119
  }
@@ -343,13 +384,15 @@ export async function doStart(port: number): Promise<void> {
343
384
 
344
385
  // 5. Find the memorix CLI entry point
345
386
  // We need to spawn `node <cli-entry> serve-http --port <port>`
346
- // The entry point is the same binary that's running now
347
- const cliEntry = process.argv[1]; // e.g., dist/cli/index.js or node_modules/.bin/memorix
387
+ // Prefer this package's compiled CLI so a bun/npm shim cannot spawn a
388
+ // different (often unpatched) memorix from ~/node_modules.
389
+ const cliEntry = resolveBackgroundCliEntry();
348
390
 
349
391
  // 6. Start the service without a visible console. A regular non-detached child
350
392
  // can be reclaimed with its parent by terminal hosts on Windows; a detached Node
351
393
  // child can flash a console. Start-Process gives us a hidden, independent child
352
394
  // and its real Node PID. POSIX retains the direct detached child path.
395
+ const startCwd = resolveBackgroundCwd(process.cwd());
353
396
  let pid: number;
354
397
  try {
355
398
  if (process.platform === 'win32') {
@@ -357,7 +400,7 @@ export async function doStart(port: number): Promise<void> {
357
400
  nodePath: process.execPath,
358
401
  cliEntry,
359
402
  port,
360
- cwd: process.cwd(),
403
+ cwd: startCwd,
361
404
  stdoutLogFile: `${logFile}.stdout`,
362
405
  stderrLogFile: logFile,
363
406
  });
@@ -367,7 +410,7 @@ export async function doStart(port: number): Promise<void> {
367
410
  detached: true,
368
411
  stdio: ['ignore', logFd, logFd],
369
412
  env: { ...process.env },
370
- cwd: process.cwd(),
413
+ cwd: startCwd,
371
414
  });
372
415
  child.unref();
373
416
  fs.closeSync(logFd);
@@ -388,7 +431,7 @@ export async function doStart(port: number): Promise<void> {
388
431
  startedAt: new Date().toISOString(),
389
432
  logFile,
390
433
  instanceToken,
391
- startCwd: process.cwd(),
434
+ startCwd,
392
435
  });
393
436
 
394
437
  // 8. Print essential info immediately
@@ -792,4 +835,7 @@ export const _testing = {
792
835
  parseBackgroundPort,
793
836
  parseLogLines,
794
837
  formatBackgroundCommandError,
838
+ resolveBackgroundCliEntry,
839
+ resolveBackgroundCliEntryFrom,
840
+ resolveBackgroundCwd,
795
841
  };
@@ -1,48 +1,48 @@
1
- import { defineCommand } from 'citty';
2
- import { getBeforeAfterMetrics, getMetricsSummary } from '../../memory/formation/index.js';
3
- import { emitError, emitResult } from './operator-shared.js';
4
-
5
- export default defineCommand({
6
- meta: {
7
- name: 'formation',
8
- description: 'Inspect Memory Formation Pipeline runtime metrics',
9
- },
10
- args: {
11
- json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
12
- },
13
- run: async ({ args }) => {
14
- const action = (args._ as string[])?.[0] || '';
15
- const asJson = !!args.json;
16
-
17
- try {
18
- switch (action) {
19
- case 'metrics': {
20
- const summary = getMetricsSummary();
21
- const beforeAfter = getBeforeAfterMetrics();
22
- emitResult(
23
- { summary, beforeAfter },
24
- summary.total === 0
25
- ? 'Formation Pipeline: No metrics collected yet.'
26
- : [
27
- 'Formation Pipeline Metrics',
28
- `- Total observations processed: ${summary.total}`,
29
- `- Average value score: ${summary.avgValueScore.toFixed(3)}`,
30
- `- Average processing time: ${summary.avgDurationMs.toFixed(1)}ms`,
31
- ].join('\n'),
32
- asJson,
33
- );
34
- return;
35
- }
36
-
37
- default:
38
- console.log('Memorix Formation Commands');
39
- console.log('');
40
- console.log('Usage:');
41
- console.log(' memorix formation metrics');
42
- }
43
- } catch (error) {
44
- emitError(error instanceof Error ? error.message : String(error), asJson);
45
- }
46
- },
47
- });
48
-
1
+ import { defineCommand } from 'citty';
2
+ import { getBeforeAfterMetrics, getMetricsSummary } from '../../memory/formation/index.js';
3
+ import { emitError, emitResult } from './operator-shared.js';
4
+
5
+ export default defineCommand({
6
+ meta: {
7
+ name: 'formation',
8
+ description: 'Inspect Memory Formation Pipeline runtime metrics',
9
+ },
10
+ args: {
11
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
12
+ },
13
+ run: async ({ args }) => {
14
+ const action = (args._ as string[])?.[0] || '';
15
+ const asJson = !!args.json;
16
+
17
+ try {
18
+ switch (action) {
19
+ case 'metrics': {
20
+ const summary = getMetricsSummary();
21
+ const beforeAfter = getBeforeAfterMetrics();
22
+ emitResult(
23
+ { summary, beforeAfter },
24
+ summary.total === 0
25
+ ? 'Formation Pipeline: No metrics collected yet.'
26
+ : [
27
+ 'Formation Pipeline Metrics',
28
+ `- Total observations processed: ${summary.total}`,
29
+ `- Average value score: ${summary.avgValueScore.toFixed(3)}`,
30
+ `- Average processing time: ${summary.avgDurationMs.toFixed(1)}ms`,
31
+ ].join('\n'),
32
+ asJson,
33
+ );
34
+ return;
35
+ }
36
+
37
+ default:
38
+ console.log('Memorix Formation Commands');
39
+ console.log('');
40
+ console.log('Usage:');
41
+ console.log(' memorix formation metrics');
42
+ }
43
+ } catch (error) {
44
+ emitError(error instanceof Error ? error.message : String(error), asJson);
45
+ }
46
+ },
47
+ });
48
+