memorix 1.8.8 → 1.9.0

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 (66) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +15 -1
  3. package/README.zh-CN.md +15 -1
  4. package/dist/cli/index.js +3722 -1599
  5. package/dist/cli/memcode.js +0 -0
  6. package/dist/index.js +634 -363
  7. package/dist/maintenance-runner.js +285 -103
  8. package/dist/media-audio-runner.js +285 -103
  9. package/dist/media-video-runner.js +285 -103
  10. package/dist/memcode-runtime/CHANGELOG.md +36 -0
  11. package/dist/sdk.js +627 -356
  12. package/dist/vector-backfill-runner.d.ts +4 -1
  13. package/dist/vector-backfill-runner.js +288 -104
  14. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -2
  15. package/docs/API_REFERENCE.md +14 -0
  16. package/docs/CONFIGURATION.md +46 -0
  17. package/docs/DEVELOPMENT.md +2 -2
  18. package/docs/INTEGRATIONS.md +1 -0
  19. package/docs/MULTI-DEVICE-SYNC-RESEARCH.md +344 -0
  20. package/docs/PERFORMANCE.md +32 -0
  21. package/docs/README.md +4 -2
  22. package/docs/hooks-architecture.md +18 -13
  23. package/llms-full.txt +4 -1
  24. package/llms.txt +3 -1
  25. package/package.json +2 -1
  26. package/plugins/antigravity/memorix/plugin.json +1 -1
  27. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  28. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  29. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  30. package/plugins/copilot/memorix/plugin.json +1 -1
  31. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  32. package/plugins/hermes/memorix/plugin.yaml +1 -1
  33. package/plugins/omp/memorix/package.json +1 -1
  34. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  35. package/plugins/pi/memorix/package.json +1 -1
  36. package/server.json +2 -2
  37. package/src/cli/command-guide.ts +9 -1
  38. package/src/cli/commands/agent-integrations.ts +2 -1
  39. package/src/cli/commands/hooks-install.ts +2 -0
  40. package/src/cli/commands/integrate.ts +2 -0
  41. package/src/cli/commands/serve-http.ts +11 -0
  42. package/src/cli/commands/setup.ts +11 -4
  43. package/src/cli/commands/sync-store.ts +191 -0
  44. package/src/cli/commands/sync.ts +8 -0
  45. package/src/hooks/handler.ts +6 -0
  46. package/src/hooks/installers/index.ts +181 -6
  47. package/src/hooks/normalizer.ts +60 -1
  48. package/src/hooks/types.ts +3 -1
  49. package/src/integrations/registry.ts +9 -0
  50. package/src/memory/observations.ts +15 -2
  51. package/src/runtime/maintenance-targets.ts +6 -0
  52. package/src/runtime/vector-backfill-runner.ts +185 -7
  53. package/src/store/orama-store.ts +85 -102
  54. package/src/sync/adapters/fs.ts +179 -0
  55. package/src/sync/adapters/github.ts +258 -0
  56. package/src/sync/adapters/object-store.ts +267 -0
  57. package/src/sync/adapters/postgres.ts +269 -0
  58. package/src/sync/engine.ts +353 -0
  59. package/src/sync/journal.ts +162 -0
  60. package/src/sync/merge.ts +116 -0
  61. package/src/sync/namespace.ts +15 -0
  62. package/src/sync/paging.ts +45 -0
  63. package/src/sync/policy.ts +37 -0
  64. package/src/sync/remote-factory.ts +49 -0
  65. package/src/sync/store-port.ts +288 -0
  66. package/src/sync/types.ts +183 -0
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.8.8"
3
+ "version": "1.9.0"
4
4
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memorix",
4
- "version": "1.8.8",
4
+ "version": "1.9.0",
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.8.8",
3
+ "version": "1.9.0",
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.8.8",
3
+ "version": "1.9.0",
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.8.8",
3
+ "version": "1.9.0",
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.8.8",
3
+ "version": "1.9.0",
4
4
  "mcpServers": {
5
5
  "memorix": {
6
6
  "command": "memorix",
@@ -1,5 +1,5 @@
1
1
  name: memorix
2
- version: "1.8.8"
2
+ version: "1.9.0"
3
3
  description: Shared workspace memory bridge for Hermes Agent.
4
4
  author: AVIDS2
5
5
  license: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-omp-package",
3
- "version": "1.8.8",
3
+ "version": "1.9.0",
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.8.8",
3
+ "version": "1.9.0",
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.8.8",
3
+ "version": "1.9.0",
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.8.8",
10
+ "version": "1.9.0",
11
11
  "websiteUrl": "https://mem.rglens.com",
12
12
  "icons": [
13
13
  {
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "memorix",
25
- "version": "1.8.8",
25
+ "version": "1.9.0",
26
26
  "runtimeHint": "npx",
27
27
  "packageArguments": [
28
28
  {
@@ -209,7 +209,15 @@ const GUIDES: Record<string, CliCommandGuide> = {
209
209
  },
210
210
  sync: {
211
211
  summary: 'Synchronize agent rules and workspace artifacts through explicit routes.',
212
- usage: ['memorix sync rules --action status', 'memorix sync workspace --action scan'],
212
+ usage: [
213
+ 'memorix sync rules --action status',
214
+ 'memorix sync workspace --action scan',
215
+ 'memorix sync store status --json',
216
+ 'memorix sync store push|pull [--dry] [--json]',
217
+ 'memorix sync store compact --dry --through device=sequence',
218
+ 'memorix sync store device rotate',
219
+ ],
220
+ notes: ['Store sync is opt-in, project-scoped, and never copies the live SQLite database.'],
213
221
  },
214
222
  ingest: {
215
223
  summary: 'Ingest Git and image evidence into project memory.',
@@ -126,6 +126,7 @@ const GUIDANCE_AGENTS = new Set<AgentName>([
126
126
  'trae',
127
127
  'dsh',
128
128
  'workbuddy',
129
+ 'grok',
129
130
  ]);
130
131
 
131
132
  const CODEX_PLUGIN_NAME = 'memorix';
@@ -167,7 +168,7 @@ function worstStatus(statuses: AgentIntegrationStatus[]): AgentIntegrationStatus
167
168
  }
168
169
 
169
170
  function isMcpConfigAgent(agent: AgentName): agent is McpConfigAgent {
170
- return agent !== 'pi';
171
+ return agent !== 'pi' && agent !== 'grok';
171
172
  }
172
173
 
173
174
  function isActionableMcpRepairIssue(issue: string): boolean {
@@ -166,6 +166,7 @@ function getAgentLabel(agent: string): string {
166
166
  trae: 'Trae',
167
167
  dsh: 'DeepSeek Harness',
168
168
  workbuddy: 'WorkBuddy',
169
+ grok: 'Grok Build',
169
170
  };
170
171
  return labels[agent] || agent;
171
172
  }
@@ -186,6 +187,7 @@ function getAgentHint(agent: string): string {
186
187
  trae: '.trae/rules/project_rules.md (rules only, no hooks system)',
187
188
  dsh: '~/.dsh/cordis.patch.yml MCP row via `memorix setup --agent dsh` (no hooks system)',
188
189
  workbuddy: '~/.workbuddy/mcp.json MCP row via `memorix setup --agent workbuddy` (no hooks system)',
190
+ grok: '~/.grok/hooks/memorix.json (own file, not RuleSync dest)',
189
191
  codex: 'Codex plugin hooks; use `memorix setup --agent codex`',
190
192
  };
191
193
  return hints[agent] || '';
@@ -116,6 +116,7 @@ function getAgentLabel(agent: string): string {
116
116
  trae: 'Trae',
117
117
  dsh: 'DeepSeek Harness',
118
118
  workbuddy: 'WorkBuddy',
119
+ grok: 'Grok Build',
119
120
  };
120
121
  return labels[agent] || agent;
121
122
  }
@@ -134,6 +135,7 @@ function getAgentHint(agent: string): string {
134
135
  trae: 'rules',
135
136
  dsh: 'MCP patch + AGENTS.md + skills',
136
137
  workbuddy: 'MCP + AGENTS.md',
138
+ grok: 'hooks file (~/.grok/hooks/memorix.json)',
137
139
  };
138
140
  return hints[agent] || 'integration files';
139
141
  }
@@ -21,6 +21,7 @@
21
21
 
22
22
  import { defineCommand } from 'citty';
23
23
  import type { IncomingMessage, ServerResponse } from 'node:http';
24
+ import { getHeapStatistics } from 'node:v8';
24
25
  import type { ObservationStore } from '../../store/obs-store.js';
25
26
  import { resolveToolProfile } from '../../server/tool-profile.js';
26
27
  import { scopeKnowledgeGraphToProject } from '../../memory/graph-scope.js';
@@ -1714,6 +1715,8 @@ export default defineCommand({
1714
1715
  // This is the readiness signal for background start / status.
1715
1716
  if (url.pathname === '/health') {
1716
1717
  const { getEmbeddingRuntimeHealth } = await import('../../embedding/provider.js');
1718
+ const memory = process.memoryUsage();
1719
+ const heap = getHeapStatistics();
1717
1720
  res.writeHead(200, { 'Content-Type': 'application/json' });
1718
1721
  res.end(JSON.stringify({
1719
1722
  status: 'ok',
@@ -1725,6 +1728,14 @@ export default defineCommand({
1725
1728
  projectId: defaultProject?.id ?? '__unresolved__',
1726
1729
  projectName: defaultProject?.name ?? null,
1727
1730
  readyAt: httpReadyAt,
1731
+ memory: {
1732
+ rss: memory.rss,
1733
+ heapUsed: memory.heapUsed,
1734
+ heapTotal: memory.heapTotal,
1735
+ external: memory.external,
1736
+ arrayBuffers: memory.arrayBuffers,
1737
+ heapLimit: heap.heap_size_limit,
1738
+ },
1728
1739
  embedding: getEmbeddingRuntimeHealth(),
1729
1740
  }));
1730
1741
  return;
@@ -195,6 +195,7 @@ const SUPPORTED_SETUP_AGENTS: AgentName[] = [
195
195
  'dsh',
196
196
  'workbuddy',
197
197
  'pi',
198
+ 'grok',
198
199
  ];
199
200
 
200
201
  export function getSetupAgentTargets(): AgentName[] {
@@ -211,7 +212,9 @@ export function buildSetupPlan(options: {
211
212
  plugin?: boolean;
212
213
  global?: boolean;
213
214
  }): SetupPlan {
214
- const mcp = options.mcp ?? 'stdio';
215
+ // Grok owns its MCP configuration through `grok mcp`; Memorix setup only
216
+ // installs the independent hooks/rules integration for this target.
217
+ const mcp = options.agent === 'grok' ? 'none' : options.mcp ?? 'stdio';
215
218
  const actions: SetupAction[] = [];
216
219
  const isPackageOwnedAgent = options.agent !== 'all' && PACKAGE_OWNED_INTEGRATION_AGENTS.has(options.agent);
217
220
  const includeHooks = options.hooks ?? true;
@@ -388,7 +391,7 @@ function relativePosix(from: string, to: string): string {
388
391
  return rel;
389
392
  }
390
393
 
391
- export type McpConfigAgent = Exclude<AgentName, 'pi'>;
394
+ export type McpConfigAgent = Exclude<AgentName, 'pi' | 'grok'>;
392
395
 
393
396
  export function getMcpAdapter(agent: McpConfigAgent): MCPConfigAdapter {
394
397
  const adapters: Record<McpConfigAgent, MCPConfigAdapter> = {
@@ -1453,7 +1456,7 @@ export async function installAgentSetup(agent: AgentName, plan: SetupPlan, globa
1453
1456
  const hasCodeBuddyPlugin = plan.actions.includes('codebuddy-plugin') && agent === 'codebuddy';
1454
1457
  // Codex's supported default is the user-level plugin. Never create a
1455
1458
  // project-local .codex/config.toml as an implicit fallback.
1456
- const wantsMcpConfig = plan.mcp !== 'none' && agent !== 'pi' && !(agent === 'codex' && !global);
1459
+ const wantsMcpConfig = plan.mcp !== 'none' && agent !== 'pi' && agent !== 'grok' && !(agent === 'codex' && !global);
1457
1460
 
1458
1461
  if (PLUGIN_PACKAGE_AGENTS.has(agent) && plan.includePlugin && !global) {
1459
1462
  p.log.info(`${agent}: project setup leaves user-level plugins untouched. Run \`memorix setup --agent ${agent} --global\` to install its plugin, skills, and lifecycle hooks.`);
@@ -1633,6 +1636,9 @@ export async function installAgentSetup(agent: AgentName, plan: SetupPlan, globa
1633
1636
 
1634
1637
  if (agent === 'pi') {
1635
1638
  p.log.info('pi: Pi has no MCP config lane in the current CLI; use the installed package extension and skill.');
1639
+ } else if (agent === 'grok') {
1640
+ p.log.info('grok: lifecycle hooks write ~/.grok/hooks/memorix.json. MCP stays host-owned (do not write config.toml).');
1641
+ if (!global) p.log.info('grok: project hooks require `/hooks-trust` or launching Grok with `--trust`.');
1636
1642
  } else if (plan.mcp === 'stdio') {
1637
1643
  p.log.info(`${agent}: MCP server command is \`memorix serve\``);
1638
1644
  } else if (plan.mcp === 'http') {
@@ -1669,7 +1675,7 @@ export default defineCommand({
1669
1675
  args: {
1670
1676
  agent: {
1671
1677
  type: 'string',
1672
- description: 'Target agent (claude, codex, codebuddy, opencode, cursor, windsurf, copilot, gemini-cli, antigravity, openclaw, hermes, omp, kiro, trae, dsh, pi, all)',
1678
+ description: 'Target agent (claude, codex, codebuddy, opencode, cursor, windsurf, copilot, gemini-cli, antigravity, openclaw, hermes, omp, kiro, trae, dsh, pi, grok, all)',
1673
1679
  required: false,
1674
1680
  },
1675
1681
  mcp: {
@@ -1738,6 +1744,7 @@ export default defineCommand({
1738
1744
  { value: 'codex', label: 'Codex', hint: 'plugin package + marketplace + MCP' },
1739
1745
  { value: 'copilot', label: 'GitHub Copilot CLI', hint: 'plugin package + hooks + MCP' },
1740
1746
  { value: 'cursor', label: 'Cursor', hint: 'MCP + rules + skills' },
1747
+ { value: 'grok', label: 'Grok Build', hint: 'hooks file; MCP stays host-owned' },
1741
1748
  { value: 'gemini-cli', label: 'Gemini CLI', hint: 'extension + MCP' },
1742
1749
  { value: 'openclaw', label: 'OpenClaw', hint: 'bundle + hook pack + skills + MCP' },
1743
1750
  { value: 'hermes', label: 'Hermes Agent', hint: 'plugin + hooks + commands + skills + MCP' },
@@ -0,0 +1,191 @@
1
+ /**
2
+ * `memorix sync store <push|pull|status>` — multi-device store replication.
3
+ *
4
+ * Separated from `memorix sync rules|workspace` (cross-agent rules) so the two
5
+ * meanings of "sync" never collide. Store sync is opt-in and provider-agnostic;
6
+ * the remote is selected by `MEMORIX_SYNC_PROVIDER`.
7
+ */
8
+
9
+ import { defineCommand } from 'citty';
10
+
11
+ import { getCliProjectContext, emitError, emitResult } from './operator-shared.js';
12
+ import { getObservationStore, initObservationStore } from '../../store/obs-store.js';
13
+ import { runSync } from '../../sync/engine.js';
14
+ import { createSqliteSyncStore } from '../../sync/store-port.js';
15
+ import { resolveSyncConfig, createRemote } from '../../sync/remote-factory.js';
16
+ import type { SyncReport } from '../../sync/types.js';
17
+
18
+ function renderReport(report: SyncReport): string {
19
+ const lines = [
20
+ `Store sync (${report.remote}) — device ${report.deviceId}${report.dryRun ? ' [dry-run]' : ''}`,
21
+ `- Pushed changes: ${report.pushed}`,
22
+ `- Pulled batches: ${report.pulledBatches}`,
23
+ `- Applied upserts: ${report.applied}`,
24
+ `- Applied deletes: ${report.tombstoned}`,
25
+ `- Skipped (older): ${report.skipped}`,
26
+ `- Excluded locally: ${report.excluded}`,
27
+ `- Conflicts recorded: ${report.conflicts}`,
28
+ `- Pending outbox: ${report.pending}`,
29
+ ];
30
+ if (report.dryRun && report.decisions.length > 0) {
31
+ lines.push('', 'Decisions:');
32
+ for (const d of report.decisions) {
33
+ lines.push(` ${d.syncKey} ${d.kind} -> ${d.outcome} (${d.reason})`);
34
+ }
35
+ }
36
+ return lines.join('\n');
37
+ }
38
+
39
+ export default defineCommand({
40
+ meta: {
41
+ name: 'sync-store',
42
+ description: 'Replicate the local observation store across your devices (opt-in, provider-agnostic)',
43
+ },
44
+ args: {
45
+ dry: { type: 'boolean', description: 'Preview changes without writing anything', default: false },
46
+ json: { type: 'boolean', description: 'Emit JSON', default: false },
47
+ 'push-only': { type: 'boolean', description: 'Only push local changes', default: false },
48
+ 'pull-only': { type: 'boolean', description: 'Only pull remote changes', default: false },
49
+ through: { type: 'string', description: 'Compaction cutoff: device=sequence,device=sequence' },
50
+ yes: { type: 'boolean', description: 'Confirm destructive remote compaction', default: false },
51
+ },
52
+ run: async ({ args }) => {
53
+ const asJson = Boolean(args.json);
54
+ const action = (args._ as string[])?.[0] || 'status';
55
+ if (action === 'device') {
56
+ const { project, dataDir } = await getCliProjectContext();
57
+ await initObservationStore(dataDir);
58
+ const syncStore = createSqliteSyncStore(dataDir, getObservationStore(), project.id);
59
+ const subaction = (args._ as string[])?.[1] || '';
60
+ if (subaction !== 'rotate') {
61
+ emitError('expected "memorix sync store device rotate"', asJson);
62
+ process.exitCode = 2;
63
+ return;
64
+ }
65
+ const deviceId = syncStore.rotateDevice();
66
+ emitResult({ project: project.id, deviceId, rotated: true }, `Sync device identity rotated: ${deviceId}`, asJson);
67
+ return;
68
+ }
69
+ if (action !== 'push' && action !== 'pull' && action !== 'status' && action !== 'compact') {
70
+ emitError(`unknown action "${action}" (expected push|pull|status|compact|device rotate)`, asJson);
71
+ process.exitCode = 2;
72
+ return;
73
+ }
74
+
75
+ let config;
76
+ try {
77
+ config = resolveSyncConfig();
78
+ } catch (err) {
79
+ emitError((err as Error).message, asJson);
80
+ process.exitCode = 2;
81
+ return;
82
+ }
83
+
84
+ if (!config.enabled || !config.provider) {
85
+ emitResult(
86
+ { enabled: false },
87
+ 'Store sync is disabled. Set MEMORIX_SYNC_PROVIDER=fs|github|s3|postgres to enable it.',
88
+ asJson,
89
+ );
90
+ return;
91
+ }
92
+
93
+ const { project, dataDir } = await getCliProjectContext();
94
+ await initObservationStore(dataDir);
95
+ const obsStore = getObservationStore();
96
+ const syncStore = createSqliteSyncStore(dataDir, obsStore, project.id);
97
+
98
+ if (action === 'compact') {
99
+ if (!args.yes && !args.dry) {
100
+ emitError('remote compaction deletes relay events; repeat with --yes after specifying --through device=sequence', asJson);
101
+ process.exitCode = 2;
102
+ return;
103
+ }
104
+ const raw = String(args.through ?? '');
105
+ const through: Record<string, number> = {};
106
+ for (const item of raw.split(',').map((value) => value.trim()).filter(Boolean)) {
107
+ const [device, sequence] = item.split('=', 2);
108
+ const parsed = Number(sequence);
109
+ if (!device || !Number.isSafeInteger(parsed) || parsed < 0) {
110
+ emitError('invalid --through; expected device=sequence[,device=sequence]', asJson);
111
+ process.exitCode = 2;
112
+ return;
113
+ }
114
+ through[device] = parsed;
115
+ }
116
+ if (Object.keys(through).length === 0) {
117
+ emitError('--through is required for compaction', asJson);
118
+ process.exitCode = 2;
119
+ return;
120
+ }
121
+ let remote;
122
+ try {
123
+ remote = await createRemote(config.provider, syncStore.namespace());
124
+ } catch (err) {
125
+ emitError((err as Error).message, asJson);
126
+ process.exitCode = 1;
127
+ return;
128
+ }
129
+ try {
130
+ await remote.init({ create: false });
131
+ const result = await remote.compact(through, { dryRun: Boolean(args.dry) });
132
+ emitResult({ project: project.id, through, ...result, dryRun: Boolean(args.dry) }, `Compaction candidates: ${result.candidates}; deleted: ${result.deleted}`, asJson);
133
+ } catch (err) {
134
+ emitError((err as Error).message, asJson);
135
+ process.exitCode = 1;
136
+ } finally {
137
+ await remote.close();
138
+ }
139
+ return;
140
+ }
141
+
142
+ if (action === 'status') {
143
+ let remote;
144
+ try {
145
+ remote = await createRemote(config.provider, syncStore.namespace());
146
+ } catch (err) {
147
+ emitError((err as Error).message, asJson);
148
+ process.exitCode = 1;
149
+ return;
150
+ }
151
+ try {
152
+ const report = await runSync(syncStore, remote, {
153
+ deviceId: syncStore.deviceId(),
154
+ dryRun: true,
155
+ push: true,
156
+ pull: true,
157
+ });
158
+ emitResult({ project: project.id, ...report }, renderReport(report), asJson);
159
+ } catch (err) {
160
+ emitError((err as Error).message, asJson);
161
+ process.exitCode = 1;
162
+ }
163
+ return;
164
+ }
165
+
166
+ const doPush = action === 'push' || !args['pull-only'];
167
+ const doPull = action === 'pull' || !args['push-only'];
168
+
169
+ let remote;
170
+ try {
171
+ remote = await createRemote(config.provider, syncStore.namespace());
172
+ } catch (err) {
173
+ emitError((err as Error).message, asJson);
174
+ process.exitCode = 1;
175
+ return;
176
+ }
177
+
178
+ try {
179
+ const report = await runSync(syncStore, remote, {
180
+ deviceId: syncStore.deviceId(),
181
+ dryRun: Boolean(args.dry),
182
+ push: doPush,
183
+ pull: doPull,
184
+ });
185
+ emitResult({ project: project.id, ...report }, renderReport(report), asJson);
186
+ } catch (err) {
187
+ emitError((err as Error).message, asJson);
188
+ process.exitCode = 1;
189
+ }
190
+ },
191
+ });
@@ -51,6 +51,14 @@ export default defineCommand({
51
51
  },
52
52
  run: async ({ args }) => {
53
53
  const section = (args._ as string[])?.[0] || '';
54
+
55
+ // Store replication is a distinct concern from rules/workspace sync.
56
+ if (section === 'store') {
57
+ const storeCmd = (await import('./sync-store.js')).default;
58
+ const rest = (args._ as string[]).slice(1);
59
+ await storeCmd.run!({ args: { ...args, _: rest } } as never);
60
+ return;
61
+ }
54
62
  const asJson = !!args.json;
55
63
 
56
64
  if (section === 'rules' || section === 'workspace') {
@@ -737,6 +737,12 @@ export function formatHookOutput(
737
737
  };
738
738
  }
739
739
 
740
+ // Grok's documented hook contract only gives passive hooks a success exit
741
+ // signal; it does not define a Claude-style additionalContext response.
742
+ // Returning an empty object keeps Memorix capture-only and never changes
743
+ // Grok's prompt, tool, or stop decisions.
744
+ if (agent === 'grok') return {};
745
+
740
746
  const finalOutput: Record<string, unknown> = { ...output };
741
747
  const hookSpecificOutputEvents = new Set([
742
748
  'PreToolUse',