memorix 1.2.0 → 1.2.2

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 (212) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +18 -4
  3. package/README.zh-CN.md +18 -4
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +15919 -14055
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1997 -1021
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +8481 -8005
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +30 -1
  13. package/dist/sdk.d.ts +7 -2
  14. package/dist/sdk.js +2022 -1024
  15. package/dist/sdk.js.map +1 -1
  16. package/dist/types.d.ts +49 -1
  17. package/dist/types.js.map +1 -1
  18. package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
  19. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
  20. package/docs/API_REFERENCE.md +27 -5
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/DEVELOPMENT.md +4 -0
  23. package/docs/README.md +1 -1
  24. package/docs/SETUP.md +7 -1
  25. package/docs/dev-log/progress.txt +91 -11
  26. package/docs/knowledge/workflows/memorix-release.md +57 -0
  27. package/package.json +1 -1
  28. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  29. package/src/audit/index.ts +156 -156
  30. package/src/cli/command-guide.ts +192 -0
  31. package/src/cli/commands/audit-list.ts +89 -89
  32. package/src/cli/commands/audit.ts +9 -4
  33. package/src/cli/commands/background.ts +659 -659
  34. package/src/cli/commands/cleanup.ts +5 -1
  35. package/src/cli/commands/codegraph.ts +17 -8
  36. package/src/cli/commands/context.ts +3 -2
  37. package/src/cli/commands/doctor.ts +4 -2
  38. package/src/cli/commands/explain.ts +9 -3
  39. package/src/cli/commands/formation.ts +48 -48
  40. package/src/cli/commands/git-hook-install.ts +111 -111
  41. package/src/cli/commands/handoff.ts +75 -61
  42. package/src/cli/commands/hooks-status.ts +63 -63
  43. package/src/cli/commands/identity.ts +116 -0
  44. package/src/cli/commands/ingest-commit.ts +153 -153
  45. package/src/cli/commands/ingest-image.ts +71 -69
  46. package/src/cli/commands/ingest-log.ts +180 -180
  47. package/src/cli/commands/ingest.ts +44 -44
  48. package/src/cli/commands/integrate-shared.ts +15 -15
  49. package/src/cli/commands/knowledge.ts +40 -0
  50. package/src/cli/commands/lock.ts +93 -92
  51. package/src/cli/commands/memory.ts +58 -21
  52. package/src/cli/commands/message.ts +123 -118
  53. package/src/cli/commands/operator-shared.ts +98 -3
  54. package/src/cli/commands/poll.ts +74 -64
  55. package/src/cli/commands/purge-all-memory.ts +85 -85
  56. package/src/cli/commands/purge-project-memory.ts +83 -83
  57. package/src/cli/commands/reasoning.ts +135 -121
  58. package/src/cli/commands/retention.ts +9 -4
  59. package/src/cli/commands/serve-http.ts +22 -43
  60. package/src/cli/commands/serve-shared.ts +118 -118
  61. package/src/cli/commands/session.ts +29 -3
  62. package/src/cli/commands/setup.ts +9 -3
  63. package/src/cli/commands/skills.ts +124 -119
  64. package/src/cli/commands/status.ts +4 -3
  65. package/src/cli/commands/task.ts +193 -184
  66. package/src/cli/commands/team.ts +14 -10
  67. package/src/cli/commands/transfer.ts +108 -55
  68. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  69. package/src/cli/identity.ts +89 -0
  70. package/src/cli/index.ts +96 -19
  71. package/src/cli/invocation.ts +115 -0
  72. package/src/cli/tui/ChatView.tsx +234 -234
  73. package/src/cli/tui/CommandBar.tsx +312 -312
  74. package/src/cli/tui/ContextRail.tsx +118 -118
  75. package/src/cli/tui/HeaderBar.tsx +72 -72
  76. package/src/cli/tui/LogoBanner.tsx +51 -51
  77. package/src/cli/tui/Sidebar.tsx +179 -179
  78. package/src/cli/tui/chat-service.ts +41 -18
  79. package/src/cli/tui/data.ts +23 -44
  80. package/src/cli/tui/index.ts +41 -41
  81. package/src/cli/tui/markdown-render.tsx +371 -371
  82. package/src/cli/tui/operator-context.ts +60 -0
  83. package/src/cli/tui/use-mouse.ts +157 -157
  84. package/src/cli/tui/useNavigation.ts +56 -56
  85. package/src/cli/tui/views/MemoryView.tsx +10 -8
  86. package/src/cli/update-checker.ts +211 -211
  87. package/src/cli/version.ts +7 -7
  88. package/src/cli/workbench.ts +1 -1
  89. package/src/codegraph/auto-context.ts +34 -17
  90. package/src/codegraph/context-pack.ts +1 -0
  91. package/src/codegraph/current-facts.ts +19 -1
  92. package/src/codegraph/project-context.ts +2 -0
  93. package/src/codegraph/task-lens.ts +49 -5
  94. package/src/compact/engine.ts +26 -10
  95. package/src/compact/index-format.ts +25 -2
  96. package/src/compact/token-budget.ts +74 -74
  97. package/src/dashboard/project-classification.ts +64 -64
  98. package/src/dashboard/server.ts +58 -52
  99. package/src/embedding/fastembed-provider.ts +142 -142
  100. package/src/embedding/transformers-provider.ts +111 -111
  101. package/src/git/extractor.ts +209 -209
  102. package/src/git/hooks-path.ts +85 -85
  103. package/src/hooks/admission.ts +117 -0
  104. package/src/hooks/handler.ts +98 -91
  105. package/src/hooks/pattern-detector.ts +173 -173
  106. package/src/hooks/significance-filter.ts +250 -250
  107. package/src/knowledge/claims.ts +51 -1
  108. package/src/knowledge/context-assembly.ts +97 -0
  109. package/src/knowledge/types.ts +1 -0
  110. package/src/knowledge/workflows.ts +34 -3
  111. package/src/knowledge/workset.ts +179 -10
  112. package/src/llm/memory-manager.ts +328 -328
  113. package/src/llm/provider.ts +885 -885
  114. package/src/llm/quality.ts +248 -248
  115. package/src/memory/admission.ts +57 -0
  116. package/src/memory/attribution-guard.ts +249 -249
  117. package/src/memory/auto-relations.ts +21 -0
  118. package/src/memory/consolidation.ts +13 -2
  119. package/src/memory/disclosure-policy.ts +140 -135
  120. package/src/memory/entity-extractor.ts +197 -197
  121. package/src/memory/export-import.ts +11 -3
  122. package/src/memory/formation/evaluate.ts +217 -217
  123. package/src/memory/formation/extract.ts +361 -361
  124. package/src/memory/formation/index.ts +417 -417
  125. package/src/memory/formation/resolve.ts +344 -344
  126. package/src/memory/formation/types.ts +315 -315
  127. package/src/memory/freshness.ts +122 -122
  128. package/src/memory/graph-context.ts +8 -2
  129. package/src/memory/graph-scope.ts +46 -0
  130. package/src/memory/graph.ts +197 -197
  131. package/src/memory/observations.ts +162 -4
  132. package/src/memory/quality-audit.ts +2 -0
  133. package/src/memory/refs.ts +94 -94
  134. package/src/memory/retention.ts +22 -2
  135. package/src/memory/secret-filter.ts +79 -79
  136. package/src/memory/session.ts +5 -2
  137. package/src/memory/visibility.ts +80 -0
  138. package/src/multimodal/image-loader.ts +143 -143
  139. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  140. package/src/orchestrate/adapters/claude.ts +111 -111
  141. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  142. package/src/orchestrate/adapters/codex.ts +41 -41
  143. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  144. package/src/orchestrate/adapters/gemini.ts +42 -42
  145. package/src/orchestrate/adapters/index.ts +73 -73
  146. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  147. package/src/orchestrate/adapters/opencode.ts +47 -47
  148. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  149. package/src/orchestrate/adapters/types.ts +77 -77
  150. package/src/orchestrate/capability-router.ts +284 -284
  151. package/src/orchestrate/context-compact.ts +188 -188
  152. package/src/orchestrate/cost-tracker.ts +219 -219
  153. package/src/orchestrate/error-recovery.ts +191 -191
  154. package/src/orchestrate/evidence.ts +140 -140
  155. package/src/orchestrate/ledger.ts +110 -110
  156. package/src/orchestrate/memorix-bridge.ts +378 -340
  157. package/src/orchestrate/output-budget.ts +80 -80
  158. package/src/orchestrate/permission.ts +152 -152
  159. package/src/orchestrate/pipeline-trace.ts +131 -131
  160. package/src/orchestrate/prompt-builder.ts +155 -155
  161. package/src/orchestrate/ring-buffer.ts +37 -37
  162. package/src/orchestrate/task-graph.ts +389 -389
  163. package/src/orchestrate/verify-gate.ts +33 -10
  164. package/src/orchestrate/worktree.ts +232 -232
  165. package/src/project/aliases.ts +374 -374
  166. package/src/project/detector.ts +268 -268
  167. package/src/rules/adapters/claude-code.ts +99 -99
  168. package/src/rules/adapters/codex.ts +97 -97
  169. package/src/rules/adapters/copilot.ts +124 -124
  170. package/src/rules/adapters/cursor.ts +114 -114
  171. package/src/rules/adapters/kiro.ts +126 -126
  172. package/src/rules/adapters/trae.ts +56 -56
  173. package/src/rules/adapters/windsurf.ts +83 -83
  174. package/src/rules/syncer.ts +235 -235
  175. package/src/runtime/control-plane-maintenance.ts +1 -0
  176. package/src/runtime/isolated-maintenance.ts +1 -0
  177. package/src/runtime/lifecycle.ts +18 -0
  178. package/src/runtime/maintenance-jobs.ts +1 -0
  179. package/src/runtime/maintenance-runner.ts +2 -0
  180. package/src/runtime/project-maintenance.ts +89 -0
  181. package/src/sdk.ts +334 -304
  182. package/src/search/intent-detector.ts +289 -289
  183. package/src/search/query-expansion.ts +52 -52
  184. package/src/server/formation-timeout.ts +27 -27
  185. package/src/server.ts +334 -93
  186. package/src/skills/mini-skills.ts +386 -386
  187. package/src/store/chat-store.ts +119 -119
  188. package/src/store/graph-store.ts +249 -249
  189. package/src/store/mini-skill-store.ts +349 -349
  190. package/src/store/orama-store.ts +61 -6
  191. package/src/store/persistence-json.ts +212 -212
  192. package/src/store/persistence.ts +291 -291
  193. package/src/store/project-affinity.ts +195 -195
  194. package/src/store/sqlite-db.ts +23 -1
  195. package/src/store/sqlite-store.ts +12 -2
  196. package/src/team/event-bus.ts +76 -76
  197. package/src/team/file-locks.ts +173 -173
  198. package/src/team/handoff.ts +168 -161
  199. package/src/team/messages.ts +203 -203
  200. package/src/team/poll.ts +132 -132
  201. package/src/team/tasks.ts +211 -211
  202. package/src/types.ts +51 -0
  203. package/src/wiki/generator.ts +2 -0
  204. package/src/workspace/mcp-adapters/codex.ts +191 -191
  205. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  206. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  207. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  208. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  209. package/src/workspace/mcp-adapters/trae.ts +134 -134
  210. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  211. package/src/workspace/sanitizer.ts +60 -60
  212. package/src/workspace/workflow-sync.ts +131 -131
@@ -1,86 +1,86 @@
1
- /**
2
- * CLI Command: memorix uninstall project-artifacts
3
- *
4
- * Uninstall project-level hook files only (preserve memory data).
5
- */
6
-
7
- import { defineCommand } from 'citty';
8
- import * as p from '@clack/prompts';
9
-
10
- export default defineCommand({
11
- meta: {
12
- name: 'project-artifacts',
13
- description: 'Uninstall project hook files (preserve memory data)',
14
- },
15
- args: {
16
- agent: {
17
- type: 'string',
18
- description: 'Target agent (all if omitted)',
19
- required: false,
20
- },
21
- },
22
- run: async ({ args }) => {
23
- const { detectInstalledAgents, uninstallHooks } = await import('../../hooks/installers/index.js');
24
- const os = await import('node:os');
25
- let cwd: string;
26
- try { cwd = process.cwd(); } catch { cwd = os.homedir(); }
27
-
28
- p.intro('Uninstall Project Artifacts');
29
-
30
- let agents: string[];
31
- if (args.agent) {
32
- agents = [args.agent];
33
- } else {
34
- agents = await detectInstalledAgents();
35
- }
36
-
37
- if (agents.length === 0) {
38
- console.log('No supported agents detected.');
39
- return;
40
- }
41
-
42
- // Show what will be uninstalled
43
- console.log('');
44
- console.log('Will uninstall hooks for:');
45
- for (const agent of agents) {
46
- console.log(` - ${agent}`);
47
- }
48
- console.log('');
49
- console.log('This will remove hook files from the project:');
50
- console.log(' - .claude/settings.local.json');
51
- console.log(' - .windsurf/hooks.json');
52
- console.log(' - .cursor/hooks.json');
53
- console.log(' - .github/hooks/memorix.json');
54
- console.log(' - .cursor/rules/memorix.mdc');
55
- console.log(' - etc.');
56
- console.log('');
57
- console.log('Memory data will be preserved.');
58
- console.log('');
59
-
60
- // Confirm
61
- const confirmed = await p.confirm({
62
- message: 'Continue uninstall?',
63
- });
64
-
65
- if (p.isCancel(confirmed) || !confirmed) {
66
- p.outro('Uninstall cancelled.');
67
- return;
68
- }
69
-
70
- // Uninstall
71
- for (const agent of agents) {
72
- const ok = await uninstallHooks(
73
- agent as import('../../hooks/types.js').AgentName,
74
- cwd,
75
- false, // project-level
76
- );
77
- if (ok) {
78
- console.log(`[OK] ${agent}: hooks removed`);
79
- } else {
1
+ /**
2
+ * CLI Command: memorix uninstall project-artifacts
3
+ *
4
+ * Uninstall project-level hook files only (preserve memory data).
5
+ */
6
+
7
+ import { defineCommand } from 'citty';
8
+ import * as p from '@clack/prompts';
9
+
10
+ export default defineCommand({
11
+ meta: {
12
+ name: 'project-artifacts',
13
+ description: 'Uninstall project hook files (preserve memory data)',
14
+ },
15
+ args: {
16
+ agent: {
17
+ type: 'string',
18
+ description: 'Target agent (all if omitted)',
19
+ required: false,
20
+ },
21
+ },
22
+ run: async ({ args }) => {
23
+ const { detectInstalledAgents, uninstallHooks } = await import('../../hooks/installers/index.js');
24
+ const os = await import('node:os');
25
+ let cwd: string;
26
+ try { cwd = process.cwd(); } catch { cwd = os.homedir(); }
27
+
28
+ p.intro('Uninstall Project Artifacts');
29
+
30
+ let agents: string[];
31
+ if (args.agent) {
32
+ agents = [args.agent];
33
+ } else {
34
+ agents = await detectInstalledAgents();
35
+ }
36
+
37
+ if (agents.length === 0) {
38
+ console.log('No supported agents detected.');
39
+ return;
40
+ }
41
+
42
+ // Show what will be uninstalled
43
+ console.log('');
44
+ console.log('Will uninstall hooks for:');
45
+ for (const agent of agents) {
46
+ console.log(` - ${agent}`);
47
+ }
48
+ console.log('');
49
+ console.log('This will remove hook files from the project:');
50
+ console.log(' - .claude/settings.local.json');
51
+ console.log(' - .windsurf/hooks.json');
52
+ console.log(' - .cursor/hooks.json');
53
+ console.log(' - .github/hooks/memorix.json');
54
+ console.log(' - .cursor/rules/memorix.mdc');
55
+ console.log(' - etc.');
56
+ console.log('');
57
+ console.log('Memory data will be preserved.');
58
+ console.log('');
59
+
60
+ // Confirm
61
+ const confirmed = await p.confirm({
62
+ message: 'Continue uninstall?',
63
+ });
64
+
65
+ if (p.isCancel(confirmed) || !confirmed) {
66
+ p.outro('Uninstall cancelled.');
67
+ return;
68
+ }
69
+
70
+ // Uninstall
71
+ for (const agent of agents) {
72
+ const ok = await uninstallHooks(
73
+ agent as import('../../hooks/types.js').AgentName,
74
+ cwd,
75
+ false, // project-level
76
+ );
77
+ if (ok) {
78
+ console.log(`[OK] ${agent}: hooks removed`);
79
+ } else {
80
80
  console.log(`[SKIP] ${agent}: no hooks found`);
81
- }
82
- }
83
-
84
- p.outro('Project artifacts uninstalled. Memory data preserved.');
85
- },
86
- });
81
+ }
82
+ }
83
+
84
+ p.outro('Project artifacts uninstalled. Memory data preserved.');
85
+ },
86
+ });
@@ -0,0 +1,89 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import type { ObservationReader, ProjectInfo } from '../types.js';
4
+ import type { TeamStore } from '../team/team-store.js';
5
+
6
+ export interface CliIdentity {
7
+ agentId: string;
8
+ projectId: string;
9
+ activatedAt: string;
10
+ }
11
+
12
+ export interface CliIdentityResolution {
13
+ identity: CliIdentity | null;
14
+ reader: ObservationReader;
15
+ warning?: string;
16
+ }
17
+
18
+ function identityPath(dataDir: string): string {
19
+ return path.join(dataDir, 'cli-identity.json');
20
+ }
21
+
22
+ function isCliIdentity(value: unknown): value is CliIdentity {
23
+ if (!value || typeof value !== 'object') return false;
24
+ const candidate = value as Partial<CliIdentity>;
25
+ return typeof candidate.agentId === 'string'
26
+ && typeof candidate.projectId === 'string'
27
+ && typeof candidate.activatedAt === 'string';
28
+ }
29
+
30
+ export async function loadCliIdentity(dataDir: string, projectId: string): Promise<CliIdentity | null> {
31
+ try {
32
+ const parsed = JSON.parse(await fs.readFile(identityPath(dataDir), 'utf8'));
33
+ return isCliIdentity(parsed) && parsed.projectId === projectId ? parsed : null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ export async function saveCliIdentity(dataDir: string, identity: CliIdentity): Promise<void> {
40
+ await fs.writeFile(identityPath(dataDir), `${JSON.stringify(identity, null, 2)}\n`, 'utf8');
41
+ }
42
+
43
+ export async function clearCliIdentity(dataDir: string): Promise<void> {
44
+ await fs.rm(identityPath(dataDir), { force: true });
45
+ }
46
+
47
+ /**
48
+ * An unbound terminal remains project-scoped. Private and team access becomes
49
+ * available only after the operator explicitly chooses an active team identity.
50
+ */
51
+ export async function resolveCliIdentity(options: {
52
+ project: ProjectInfo;
53
+ dataDir: string;
54
+ teamStore: TeamStore;
55
+ explicitActorId?: string;
56
+ }): Promise<CliIdentityResolution> {
57
+ const stored = await loadCliIdentity(options.dataDir, options.project.id);
58
+ const requestedAgentId = options.explicitActorId ?? stored?.agentId;
59
+ const projectReader: ObservationReader = { projectId: options.project.id };
60
+ if (!requestedAgentId) return { identity: null, reader: projectReader };
61
+
62
+ const agent = options.teamStore.getAgent(requestedAgentId);
63
+ const activeForProject = agent
64
+ && agent.project_id === options.project.id
65
+ && agent.status === 'active';
66
+ if (!activeForProject) {
67
+ if (options.explicitActorId) {
68
+ throw new Error(`CLI actor "${requestedAgentId}" is not an active member of this project.`);
69
+ }
70
+ return {
71
+ identity: null,
72
+ reader: projectReader,
73
+ warning: `Saved CLI identity "${requestedAgentId}" is no longer active for this project.`,
74
+ };
75
+ }
76
+
77
+ return {
78
+ identity: stored ?? {
79
+ agentId: agent.agent_id,
80
+ projectId: options.project.id,
81
+ activatedAt: new Date().toISOString(),
82
+ },
83
+ reader: {
84
+ projectId: options.project.id,
85
+ agentId: agent.agent_id,
86
+ isTeamMember: true,
87
+ },
88
+ };
89
+ }
package/src/cli/index.ts CHANGED
@@ -19,6 +19,8 @@ import { execSync, spawn } from 'node:child_process';
19
19
  import { getCliVersion } from './version.js';
20
20
  import { importBundledMemcode } from './memcode-bootstrap.js';
21
21
  import { installCliPipeErrorGuard } from './pipe-errors.js';
22
+ import { normalizeCliInvocation } from './invocation.js';
23
+ import { printCliGuideForHelp, renderCliGuide } from './command-guide.js';
22
24
 
23
25
  installCliPipeErrorGuard();
24
26
 
@@ -129,8 +131,9 @@ async function getWorkbenchHeader(): Promise<string[]> {
129
131
  const dataDir = await getProjectDataDir(proj.id);
130
132
  await initStore(dataDir);
131
133
  const { getObservationStore: getStore } = await import('../store/obs-store.js');
132
- const obs = await getStore().loadAll() as any[];
133
- const active = obs.filter((o: any) => (o.status ?? 'active') === 'active').length;
134
+ const { filterReadableObservations } = await import('../memory/visibility.js');
135
+ const obs = filterReadableObservations(await getStore().loadAll(), { projectId: proj.id });
136
+ const active = obs.filter((o) => (o.status ?? 'active') === 'active').length;
134
137
  if (active > 0) {
135
138
  memLabel = `${BOLD}${active}${RESET} ${DIM}active${RESET}`;
136
139
  }
@@ -801,6 +804,7 @@ async function runSearch(query: string): Promise<void> {
801
804
 
802
805
  try {
803
806
  const { searchObservations, getDb, hydrateIndex } = await import('../store/orama-store.js');
807
+ const { filterReadableObservations } = await import('../memory/visibility.js');
804
808
  const { getProjectDataDir } = await import('../store/persistence.js');
805
809
  const { detectProject } = await import('../project/detector.js');
806
810
  const { initObservations } = await import('../memory/observations.js');
@@ -825,7 +829,7 @@ async function runSearch(query: string): Promise<void> {
825
829
  await hydrateIndex(allObs);
826
830
  mark('hydrateIndex');
827
831
 
828
- const results = await searchObservations({ query, limit: 10, projectId: project.id });
832
+ const results = await searchObservations({ query, limit: 10, projectId: project.id, reader: { projectId: project.id } });
829
833
  mark('search');
830
834
  s.stop('Search complete');
831
835
 
@@ -855,14 +859,16 @@ async function runList(): Promise<void> {
855
859
  const { getProjectDataDir } = await import('../store/persistence.js');
856
860
  const { detectProject } = await import('../project/detector.js');
857
861
  const { initObservationStore: initStore, getObservationStore: getStore } = await import('../store/obs-store.js');
862
+ const { filterReadableObservations } = await import('../memory/visibility.js');
858
863
 
859
864
  const project = detectProject(process.cwd());
860
865
  if (!project) { s.stop('No git repo'); p.log.error(NO_GIT_MSG); return; }
861
866
  const dataDir = await getProjectDataDir(project.id);
862
867
  await initStore(dataDir);
863
- const observations = await getStore().loadAll() as unknown as Array<{
864
- id: number; title: string; type: string; timestamp: string; status?: string;
865
- }>;
868
+ const observations = filterReadableObservations(
869
+ await getStore().loadAll(),
870
+ { projectId: project.id },
871
+ );
866
872
 
867
873
  const active = observations.filter(o => (o.status ?? 'active') === 'active');
868
874
  const recent = active.slice(-10).reverse();
@@ -876,7 +882,7 @@ async function runList(): Promise<void> {
876
882
 
877
883
  console.log('');
878
884
  for (const o of recent) {
879
- const typeLabel = { gotcha: '[!]', decision: '[D]', 'problem-solution': '[S]', discovery: '[?]', 'how-it-works': '[H]', 'what-changed': '[C]' }[o.type] ?? '[·]';
885
+ const typeLabel = ({ gotcha: '[!]', decision: '[D]', 'problem-solution': '[S]', discovery: '[?]', 'how-it-works': '[H]', 'what-changed': '[C]' } as Record<string, string>)[o.type] ?? '[·]';
880
886
  console.log(` ${typeLabel} #${o.id} ${o.title?.slice(0, 60) ?? '(untitled)'}`);
881
887
  }
882
888
  console.log('');
@@ -927,11 +933,26 @@ async function runCommand(cmd: string, _args: string[] = []): Promise<void> {
927
933
  // Main command
928
934
  // ============================================================
929
935
 
936
+ async function runMemoryShortcut(action: string, args: Record<string, unknown>): Promise<void> {
937
+ const { detectProject } = await import('../project/detector.js');
938
+ if (!detectProject(process.cwd())) {
939
+ console.log(NO_GIT_MSG);
940
+ process.exitCode = 1;
941
+ return;
942
+ }
943
+ const memory = await import('./commands/memory.js');
944
+ await memory.default.run?.({
945
+ args: { ...args, _: [action] },
946
+ rawArgs: [],
947
+ cmd: memory.default,
948
+ } as any);
949
+ }
950
+
930
951
  const main = defineCommand({
931
952
  meta: {
932
953
  name: 'memorix',
933
954
  version: getCliVersion(),
934
- description: 'Local-first memory control plane for AI coding agents via MCP',
955
+ description: 'Local-first memory control plane for AI coding agents through CLI, MCP, and local workflows',
935
956
  },
936
957
  subCommands: {
937
958
  // One-shot product commands (primary user paths)
@@ -960,18 +981,55 @@ const main = defineCommand({
960
981
  },
961
982
  })),
962
983
  search: () => Promise.resolve(defineCommand({
963
- meta: { name: 'search', description: 'Search memories' },
964
- args: { query: { type: 'positional', description: 'Search query', required: true } },
965
- async run({ args }) { await runSearch(args.query as string); },
984
+ meta: { name: 'search', description: 'Shortcut for `memorix memory search`' },
985
+ args: {
986
+ query: { type: 'positional', description: 'Search query', required: true },
987
+ limit: { type: 'string', description: 'Maximum results' },
988
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
989
+ },
990
+ async run({ args }) {
991
+ await runMemoryShortcut('search', {
992
+ query: args.query,
993
+ limit: args.limit,
994
+ json: args.json,
995
+ });
996
+ },
966
997
  })),
967
998
  remember: () => Promise.resolve(defineCommand({
968
- meta: { name: 'remember', description: 'Store a quick memory' },
969
- args: { text: { type: 'positional', description: 'Text to remember', required: true } },
970
- async run({ args }) { await runRemember(args.text as string); },
999
+ meta: { name: 'remember', description: 'Shortcut for `memorix memory store`' },
1000
+ args: {
1001
+ text: { type: 'positional', description: 'Text to remember', required: true },
1002
+ title: { type: 'string', description: 'Optional observation title' },
1003
+ type: { type: 'string', description: 'Observation type' },
1004
+ visibility: { type: 'string', description: 'project (default), personal, or team' },
1005
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
1006
+ },
1007
+ async run({ args }) {
1008
+ await runMemoryShortcut('store', {
1009
+ text: args.text,
1010
+ title: args.title,
1011
+ type: args.type,
1012
+ visibility: args.visibility,
1013
+ json: args.json,
1014
+ });
1015
+ },
971
1016
  })),
972
1017
  recent: () => Promise.resolve(defineCommand({
973
- meta: { name: 'recent', description: 'View recent memories' },
974
- async run() { await runList(); },
1018
+ meta: { name: 'recent', description: 'Shortcut for `memorix memory recent`' },
1019
+ args: {
1020
+ limit: { type: 'string', description: 'Maximum results' },
1021
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
1022
+ },
1023
+ async run({ args }) { await runMemoryShortcut('recent', { limit: args.limit, json: args.json }); },
1024
+ })),
1025
+ help: () => Promise.resolve(defineCommand({
1026
+ meta: { name: 'help', description: 'Show action-oriented help for a command group' },
1027
+ args: {
1028
+ command: { type: 'positional', description: 'Command group to inspect', required: false },
1029
+ },
1030
+ async run({ args }) {
1031
+ console.log(renderCliGuide(args.command as string | undefined));
1032
+ },
975
1033
  })),
976
1034
  // Infrastructure commands
977
1035
  init: () => import('./commands/init.js').then(m => m.default),
@@ -996,6 +1054,7 @@ const main = defineCommand({
996
1054
  audit: () => import('./commands/audit.js').then(m => m.default),
997
1055
  transfer: () => import('./commands/transfer.js').then(m => m.default),
998
1056
  skills: () => import('./commands/skills.js').then(m => m.default),
1057
+ identity: () => import('./commands/identity.js').then(m => m.default),
999
1058
  session: () => import('./commands/session.js').then(m => m.default),
1000
1059
  team: () => import('./commands/team.js').then(m => m.default),
1001
1060
  task: () => import('./commands/task.js').then(m => m.default),
@@ -1031,6 +1090,13 @@ const main = defineCommand({
1031
1090
  cleanup: () => import('./commands/cleanup.js').then(m => m.default),
1032
1091
  uninstall: () => import('./commands/uninstall.js').then(m => m.default),
1033
1092
  orchestrate: () => import('./commands/orchestrate.js').then(m => m.default),
1093
+ workbench: () => Promise.resolve(defineCommand({
1094
+ meta: { name: 'workbench', description: 'Open the interactive terminal memory control plane' },
1095
+ async run() {
1096
+ const { startWorkbench } = await import('./workbench.js');
1097
+ await startWorkbench();
1098
+ },
1099
+ })),
1034
1100
  memcode: () => Promise.resolve(defineCommand({
1035
1101
  meta: { name: 'memcode', description: 'Enter memcode TUI — native coding agent with memory' },
1036
1102
  async run() {
@@ -1048,8 +1114,8 @@ const main = defineCommand({
1048
1114
  // Guard: if citty already resolved a subcommand, its run() was called before this.
1049
1115
  // Detect by checking if the first CLI arg matches a registered subcommand name.
1050
1116
  const firstArg = process.argv[2];
1051
- const knownSubs = ['ask', 'search', 'remember', 'recent', 'memcode', 'config',
1052
- 'init', 'setup', 'integrate', 'memory', 'context', 'explain', 'codegraph', 'knowledge', 'reasoning', 'retention', 'formation', 'audit', 'transfer', 'skills',
1117
+ const knownSubs = ['ask', 'search', 'remember', 'recent', 'help', 'workbench', 'memcode', 'config',
1118
+ 'init', 'setup', 'integrate', 'memory', 'context', 'explain', 'codegraph', 'knowledge', 'reasoning', 'retention', 'formation', 'audit', 'transfer', 'skills', 'identity',
1053
1119
  'session', 'team', 'task', 'message', 'lock', 'handoff', 'poll',
1054
1120
  'receipt',
1055
1121
  'serve', 'serve-http', 'status', 'sync',
@@ -1080,6 +1146,8 @@ const main = defineCommand({
1080
1146
  console.error(`Memorix v${getCliVersion()} — Local-first memory control plane\n`);
1081
1147
  console.error('Usage: memorix <command>\n');
1082
1148
  console.error('Commands:');
1149
+ console.error(' help Show action-oriented help (`memorix memory --help`)');
1150
+ console.error(' workbench Open interactive terminal memory control plane');
1083
1151
  console.error(' memcode Enter memcode TUI (native coding agent)');
1084
1152
  console.error(' ask "q" Ask Memorix a question (single-shot chat)');
1085
1153
  console.error(' Pipe: echo "q" | memorix ask');
@@ -1096,6 +1164,7 @@ const main = defineCommand({
1096
1164
  console.error(' audit Audit trail and project attribution checks');
1097
1165
  console.error(' transfer Export/import memory snapshots');
1098
1166
  console.error(' skills List/generate/show project skills');
1167
+ console.error(' identity Select the explicit CLI actor for private/team memory');
1099
1168
  console.error(' team Join/status/role operations for coordination state');
1100
1169
  console.error(' task Create/claim/complete/list team tasks');
1101
1170
  console.error(' message Send/broadcast/read team messages');
@@ -1121,4 +1190,12 @@ const main = defineCommand({
1121
1190
  },
1122
1191
  });
1123
1192
 
1124
- runMain(main);
1193
+ try {
1194
+ normalizeCliInvocation();
1195
+ if (!printCliGuideForHelp()) {
1196
+ runMain(main);
1197
+ }
1198
+ } catch (error) {
1199
+ console.error(`Memorix CLI invocation error: ${error instanceof Error ? error.message : String(error)}`);
1200
+ process.exitCode = 1;
1201
+ }
@@ -0,0 +1,115 @@
1
+ import { existsSync, statSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
4
+ export interface CliInvocation {
5
+ projectRoot?: string;
6
+ actorId?: string;
7
+ }
8
+
9
+ type InvocationOption = 'projectRoot' | 'actorId';
10
+
11
+ const GLOBAL_OPTIONS: Record<string, InvocationOption> = {
12
+ '--cwd': 'projectRoot',
13
+ '--project-root': 'projectRoot',
14
+ '--as': 'actorId',
15
+ '--actor': 'actorId',
16
+ };
17
+
18
+ // Existing commands use camelCase Citty keys. Preserve them for compatibility
19
+ // while accepting the shell-native kebab-case spelling everywhere.
20
+ const FLAG_ALIASES: Record<string, string> = {
21
+ '--agent-type': '--agentType',
22
+ '--instance-id': '--instanceId',
23
+ '--agent-id': '--agentId',
24
+ '--from-agent-id': '--fromAgentId',
25
+ '--to-agent-id': '--toAgentId',
26
+ '--join-team': '--joinTeam',
27
+ '--session-id': '--sessionId',
28
+ '--task-id': '--taskId',
29
+ '--topic-key': '--topicKey',
30
+ '--graph-limit': '--graphLimit',
31
+ '--graph-query': '--graphQuery',
32
+ '--skill-id': '--skillId',
33
+ '--required-role': '--requiredRole',
34
+ '--preferred-role': '--preferredRole',
35
+ '--to-role': '--toRole',
36
+ '--handoff-status': '--handoffStatus',
37
+ '--max-concurrent': '--maxConcurrent',
38
+ '--role-id': '--roleId',
39
+ '--preferred-agent-types': '--preferredAgentTypes',
40
+ '--files-modified': '--filesModified',
41
+ '--expected-outcome': '--expectedOutcome',
42
+ '--related-commits': '--relatedCommits',
43
+ '--related-entities': '--relatedEntities',
44
+ '--mime-type': '--mimeType',
45
+ '--mark-read': '--markRead',
46
+ '--mark-inbox-read': '--markInboxRead',
47
+ };
48
+
49
+ function readOption(argument: string): { name: string; value?: string } {
50
+ const equals = argument.indexOf('=');
51
+ return equals === -1
52
+ ? { name: argument }
53
+ : { name: argument.slice(0, equals), value: argument.slice(equals + 1) };
54
+ }
55
+
56
+ /**
57
+ * Normalize the small set of global operator flags before Citty resolves a
58
+ * command. This gives every CLI command the same project and actor anchors
59
+ * without duplicating those flags across dozens of command definitions.
60
+ */
61
+ export function normalizeCliInvocation(argv: string[] = process.argv): CliInvocation {
62
+ const normalized = argv.slice(0, 2);
63
+ const invocation: CliInvocation = {};
64
+
65
+ for (let index = 2; index < argv.length; index += 1) {
66
+ const argument = argv[index];
67
+ if (argument === '--') {
68
+ normalized.push(...argv.slice(index));
69
+ break;
70
+ }
71
+
72
+ const { name, value: inlineValue } = readOption(argument);
73
+ const target = GLOBAL_OPTIONS[name];
74
+ if (!target) {
75
+ const alias = FLAG_ALIASES[name];
76
+ normalized.push(alias
77
+ ? (inlineValue === undefined ? alias : `${alias}=${inlineValue}`)
78
+ : argument);
79
+ continue;
80
+ }
81
+
82
+ const value = inlineValue ?? argv[index + 1];
83
+ if (!value || value.startsWith('--')) {
84
+ throw new Error(`${name} requires a value`);
85
+ }
86
+ invocation[target] = value;
87
+ if (inlineValue === undefined) index += 1;
88
+ }
89
+
90
+ if (invocation.projectRoot) {
91
+ const projectRoot = resolve(invocation.projectRoot);
92
+ if (!existsSync(projectRoot) || !statSync(projectRoot).isDirectory()) {
93
+ throw new Error(`CLI project directory does not exist: ${projectRoot}`);
94
+ }
95
+ process.chdir(projectRoot);
96
+ process.env.MEMORIX_CLI_PROJECT_ROOT = projectRoot;
97
+ invocation.projectRoot = projectRoot;
98
+ }
99
+
100
+ if (invocation.actorId) {
101
+ process.env.MEMORIX_CLI_ACTOR_ID = invocation.actorId;
102
+ }
103
+
104
+ process.argv = normalized;
105
+ return invocation;
106
+ }
107
+
108
+ export function getCliInvocation(): CliInvocation {
109
+ const projectRoot = process.env.MEMORIX_CLI_PROJECT_ROOT?.trim();
110
+ const actorId = process.env.MEMORIX_CLI_ACTOR_ID?.trim();
111
+ return {
112
+ ...(projectRoot ? { projectRoot } : {}),
113
+ ...(actorId ? { actorId } : {}),
114
+ };
115
+ }