memorix 1.2.1 → 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 (199) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +14 -2
  3. package/README.zh-CN.md +14 -2
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +15407 -13779
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1321 -529
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +8458 -8087
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +16 -0
  13. package/dist/sdk.d.ts +7 -2
  14. package/dist/sdk.js +1349 -535
  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 +24 -4
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/README.md +1 -1
  23. package/docs/dev-log/progress.txt +91 -11
  24. package/package.json +1 -1
  25. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  26. package/src/audit/index.ts +156 -156
  27. package/src/cli/command-guide.ts +192 -0
  28. package/src/cli/commands/audit-list.ts +89 -89
  29. package/src/cli/commands/audit.ts +9 -4
  30. package/src/cli/commands/background.ts +659 -659
  31. package/src/cli/commands/cleanup.ts +5 -1
  32. package/src/cli/commands/codegraph.ts +15 -5
  33. package/src/cli/commands/context.ts +3 -2
  34. package/src/cli/commands/doctor.ts +4 -2
  35. package/src/cli/commands/explain.ts +9 -3
  36. package/src/cli/commands/formation.ts +48 -48
  37. package/src/cli/commands/git-hook-install.ts +111 -111
  38. package/src/cli/commands/handoff.ts +75 -61
  39. package/src/cli/commands/hooks-status.ts +63 -63
  40. package/src/cli/commands/identity.ts +116 -0
  41. package/src/cli/commands/ingest-commit.ts +153 -153
  42. package/src/cli/commands/ingest-image.ts +71 -69
  43. package/src/cli/commands/ingest-log.ts +180 -180
  44. package/src/cli/commands/ingest.ts +44 -44
  45. package/src/cli/commands/integrate-shared.ts +15 -15
  46. package/src/cli/commands/lock.ts +93 -92
  47. package/src/cli/commands/memory.ts +58 -21
  48. package/src/cli/commands/message.ts +123 -118
  49. package/src/cli/commands/operator-shared.ts +98 -3
  50. package/src/cli/commands/poll.ts +74 -64
  51. package/src/cli/commands/purge-all-memory.ts +85 -85
  52. package/src/cli/commands/purge-project-memory.ts +83 -83
  53. package/src/cli/commands/reasoning.ts +135 -121
  54. package/src/cli/commands/retention.ts +9 -4
  55. package/src/cli/commands/serve-http.ts +8 -2
  56. package/src/cli/commands/serve-shared.ts +118 -118
  57. package/src/cli/commands/session.ts +29 -3
  58. package/src/cli/commands/skills.ts +124 -119
  59. package/src/cli/commands/status.ts +4 -3
  60. package/src/cli/commands/task.ts +193 -184
  61. package/src/cli/commands/team.ts +14 -10
  62. package/src/cli/commands/transfer.ts +108 -55
  63. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  64. package/src/cli/identity.ts +89 -0
  65. package/src/cli/index.ts +96 -19
  66. package/src/cli/invocation.ts +115 -0
  67. package/src/cli/tui/ChatView.tsx +234 -234
  68. package/src/cli/tui/CommandBar.tsx +312 -312
  69. package/src/cli/tui/ContextRail.tsx +118 -118
  70. package/src/cli/tui/HeaderBar.tsx +72 -72
  71. package/src/cli/tui/LogoBanner.tsx +51 -51
  72. package/src/cli/tui/Sidebar.tsx +179 -179
  73. package/src/cli/tui/chat-service.ts +41 -18
  74. package/src/cli/tui/data.ts +23 -44
  75. package/src/cli/tui/index.ts +41 -41
  76. package/src/cli/tui/markdown-render.tsx +371 -371
  77. package/src/cli/tui/operator-context.ts +60 -0
  78. package/src/cli/tui/use-mouse.ts +157 -157
  79. package/src/cli/tui/useNavigation.ts +56 -56
  80. package/src/cli/tui/views/MemoryView.tsx +10 -8
  81. package/src/cli/update-checker.ts +211 -211
  82. package/src/cli/version.ts +7 -7
  83. package/src/cli/workbench.ts +1 -1
  84. package/src/codegraph/auto-context.ts +31 -2
  85. package/src/codegraph/context-pack.ts +1 -0
  86. package/src/codegraph/project-context.ts +2 -0
  87. package/src/compact/engine.ts +26 -10
  88. package/src/compact/index-format.ts +25 -2
  89. package/src/compact/token-budget.ts +74 -74
  90. package/src/dashboard/project-classification.ts +64 -64
  91. package/src/dashboard/server.ts +46 -9
  92. package/src/embedding/fastembed-provider.ts +142 -142
  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/hooks/admission.ts +117 -0
  97. package/src/hooks/handler.ts +98 -91
  98. package/src/hooks/pattern-detector.ts +173 -173
  99. package/src/hooks/significance-filter.ts +250 -250
  100. package/src/knowledge/context-assembly.ts +97 -0
  101. package/src/knowledge/workset.ts +179 -10
  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/admission.ts +57 -0
  106. package/src/memory/attribution-guard.ts +249 -249
  107. package/src/memory/consolidation.ts +13 -2
  108. package/src/memory/disclosure-policy.ts +140 -135
  109. package/src/memory/entity-extractor.ts +197 -197
  110. package/src/memory/export-import.ts +11 -3
  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-context.ts +8 -2
  118. package/src/memory/graph.ts +197 -197
  119. package/src/memory/observations.ts +162 -4
  120. package/src/memory/quality-audit.ts +2 -0
  121. package/src/memory/refs.ts +94 -94
  122. package/src/memory/retention.ts +22 -2
  123. package/src/memory/secret-filter.ts +79 -79
  124. package/src/memory/session.ts +5 -2
  125. package/src/memory/visibility.ts +80 -0
  126. package/src/multimodal/image-loader.ts +143 -143
  127. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  128. package/src/orchestrate/adapters/claude.ts +111 -111
  129. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  130. package/src/orchestrate/adapters/codex.ts +41 -41
  131. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  132. package/src/orchestrate/adapters/gemini.ts +42 -42
  133. package/src/orchestrate/adapters/index.ts +73 -73
  134. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  135. package/src/orchestrate/adapters/opencode.ts +47 -47
  136. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  137. package/src/orchestrate/adapters/types.ts +77 -77
  138. package/src/orchestrate/capability-router.ts +284 -284
  139. package/src/orchestrate/context-compact.ts +188 -188
  140. package/src/orchestrate/cost-tracker.ts +219 -219
  141. package/src/orchestrate/error-recovery.ts +191 -191
  142. package/src/orchestrate/evidence.ts +140 -140
  143. package/src/orchestrate/ledger.ts +110 -110
  144. package/src/orchestrate/memorix-bridge.ts +378 -340
  145. package/src/orchestrate/output-budget.ts +80 -80
  146. package/src/orchestrate/permission.ts +152 -152
  147. package/src/orchestrate/pipeline-trace.ts +131 -131
  148. package/src/orchestrate/prompt-builder.ts +155 -155
  149. package/src/orchestrate/ring-buffer.ts +37 -37
  150. package/src/orchestrate/task-graph.ts +389 -389
  151. package/src/orchestrate/worktree.ts +232 -232
  152. package/src/project/aliases.ts +374 -374
  153. package/src/project/detector.ts +268 -268
  154. package/src/rules/adapters/claude-code.ts +99 -99
  155. package/src/rules/adapters/codex.ts +97 -97
  156. package/src/rules/adapters/copilot.ts +124 -124
  157. package/src/rules/adapters/cursor.ts +114 -114
  158. package/src/rules/adapters/kiro.ts +126 -126
  159. package/src/rules/adapters/trae.ts +56 -56
  160. package/src/rules/adapters/windsurf.ts +83 -83
  161. package/src/rules/syncer.ts +235 -235
  162. package/src/runtime/control-plane-maintenance.ts +1 -0
  163. package/src/runtime/isolated-maintenance.ts +1 -0
  164. package/src/runtime/lifecycle.ts +18 -0
  165. package/src/runtime/maintenance-jobs.ts +1 -0
  166. package/src/runtime/maintenance-runner.ts +2 -0
  167. package/src/runtime/project-maintenance.ts +89 -0
  168. package/src/sdk.ts +334 -304
  169. package/src/search/intent-detector.ts +289 -289
  170. package/src/search/query-expansion.ts +52 -52
  171. package/src/server/formation-timeout.ts +27 -27
  172. package/src/server.ts +260 -81
  173. package/src/skills/mini-skills.ts +386 -386
  174. package/src/store/chat-store.ts +119 -119
  175. package/src/store/graph-store.ts +249 -249
  176. package/src/store/mini-skill-store.ts +349 -349
  177. package/src/store/orama-store.ts +61 -6
  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/sqlite-db.ts +23 -1
  182. package/src/store/sqlite-store.ts +12 -2
  183. package/src/team/event-bus.ts +76 -76
  184. package/src/team/file-locks.ts +173 -173
  185. package/src/team/handoff.ts +168 -161
  186. package/src/team/messages.ts +203 -203
  187. package/src/team/poll.ts +132 -132
  188. package/src/team/tasks.ts +211 -211
  189. package/src/types.ts +51 -0
  190. package/src/wiki/generator.ts +2 -0
  191. package/src/workspace/mcp-adapters/codex.ts +191 -191
  192. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  193. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  194. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  195. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  196. package/src/workspace/mcp-adapters/trae.ts +134 -134
  197. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  198. package/src/workspace/sanitizer.ts +60 -60
  199. 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
+ }