memorix 1.2.9 → 1.3.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 (68) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +30 -4
  3. package/README.zh-CN.md +28 -2
  4. package/assets/star-history-dark.svg +30 -0
  5. package/assets/star-history-light.svg +30 -0
  6. package/assets/star-history.json +599 -0
  7. package/dist/cli/index.js +1945 -579
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/index.js +1292 -267
  10. package/dist/index.js.map +1 -1
  11. package/dist/maintenance-runner.js +97 -21
  12. package/dist/maintenance-runner.js.map +1 -1
  13. package/dist/memcode-runtime/CHANGELOG.md +24 -0
  14. package/dist/sdk.d.ts +1 -1
  15. package/dist/sdk.js +1294 -269
  16. package/dist/sdk.js.map +1 -1
  17. package/dist/types.d.ts +99 -1
  18. package/dist/types.js.map +1 -1
  19. package/dist/vector-backfill-runner.js +97 -21
  20. package/dist/vector-backfill-runner.js.map +1 -1
  21. package/docs/1.3-MEMORY-ARCHITECTURE.md +236 -0
  22. package/docs/AGENT_OPERATOR_PLAYBOOK.md +5 -3
  23. package/docs/API_REFERENCE.md +20 -1
  24. package/docs/ARCHITECTURE.md +34 -1
  25. package/docs/DEVELOPMENT.md +10 -3
  26. package/docs/INTEGRATIONS.md +1 -1
  27. package/docs/MODULES.md +27 -1
  28. package/docs/README.md +3 -3
  29. package/docs/SETUP.md +6 -4
  30. package/docs/dev-log/progress.txt +22 -0
  31. package/docs/hooks-architecture.md +1 -1
  32. package/llms-full.txt +5 -1
  33. package/llms.txt +2 -0
  34. package/package.json +7 -2
  35. package/plugins/antigravity/memorix/rules/memorix.md +2 -0
  36. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  37. package/plugins/claude/memorix/skills/memorix-memory/SKILL.md +3 -0
  38. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  39. package/plugins/codex/memorix/skills/memorix-memory/SKILL.md +3 -0
  40. package/plugins/copilot/memorix/plugin.json +1 -1
  41. package/plugins/copilot/memorix/skills/memorix-memory/SKILL.md +3 -0
  42. package/plugins/cursor/memorix/skills/memorix-memory/SKILL.md +3 -0
  43. package/plugins/gemini/memorix/GEMINI.md +2 -0
  44. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  45. package/plugins/omp/memorix/package.json +1 -1
  46. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  47. package/plugins/pi/memorix/package.json +1 -1
  48. package/plugins/pi/memorix/skills/memorix-memory/SKILL.md +3 -0
  49. package/server.json +38 -0
  50. package/src/cli/command-guide.ts +9 -1
  51. package/src/cli/commands/agent-integrations.ts +12 -6
  52. package/src/cli/commands/codegraph.ts +1 -0
  53. package/src/cli/commands/memory.ts +217 -1
  54. package/src/cli/commands/setup.ts +200 -23
  55. package/src/codegraph/auto-context.ts +11 -0
  56. package/src/codegraph/context-pack.ts +3 -0
  57. package/src/embedding/api-provider.ts +23 -9
  58. package/src/embedding/provider.ts +21 -10
  59. package/src/hooks/installers/index.ts +2 -0
  60. package/src/knowledge/context-assembly.ts +2 -1
  61. package/src/knowledge/workset.ts +77 -4
  62. package/src/memory/long-term-store.ts +242 -0
  63. package/src/memory/long-term-types.ts +136 -0
  64. package/src/memory/long-term.ts +626 -0
  65. package/src/memory/owner.ts +75 -0
  66. package/src/server.ts +112 -30
  67. package/src/store/sqlite-db.ts +74 -0
  68. package/src/types.ts +20 -0
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.2.9",
3
+ "mcpName": "io.github.AVIDS2/memorix",
4
+ "version": "1.3.0",
4
5
  "description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
5
6
  "type": "module",
6
7
  "sideEffects": false,
@@ -33,6 +34,7 @@
33
34
  "files": [
34
35
  "dist",
35
36
  "src",
37
+ "server.json",
36
38
  "!src/**/*.js",
37
39
  "!src/**/*.js.map",
38
40
  "!src/**/*.d.ts",
@@ -63,9 +65,12 @@
63
65
  "test:e2e": "vitest run tests/e2e/",
64
66
  "benchmark:codegraph": "node scripts/benchmark-codegraph-provider.cjs",
65
67
  "benchmark:retrieval": "npm run build && node scripts/benchmark-retrieval.mjs",
68
+ "generate:star-history": "node scripts/generate-star-history.mjs",
69
+ "sync:mcp-registry": "node scripts/sync-mcp-registry-manifest.mjs",
70
+ "check:mcp-registry": "node scripts/check-mcp-registry-manifest.mjs",
66
71
  "audit:pi-upstream": "node scripts/pi-upstream-audit.mjs",
67
72
  "lint": "tsc --noEmit",
68
- "prepublishOnly": "npm run build && npm test"
73
+ "prepublishOnly": "npm run sync:mcp-registry && npm run check:mcp-registry && npm run build && npm test"
69
74
  },
70
75
  "keywords": [
71
76
  "mcp",
@@ -9,6 +9,8 @@ Use Memorix when prior workspace context, decisions, fixes, or handoff state wou
9
9
  - Use `memorix_search` for focused lookup.
10
10
  - Use `memorix_detail` before relying on a specific memory.
11
11
  - Use `memorix_store` for durable workspace knowledge.
12
+ - Use `longTerm` on `memorix_store` only for a stable fact, reusable procedure, or completed episode that merits explicit review. It creates a candidate, not live context; keep the default project scope and do not use it for routine updates.
13
+ - A `user` + `portable` durable memory in a task brief is intentionally reusable across projects. When it matches the task, use it as background even if it originated elsewhere; it is not a current-project fact. Expand it only when needed with `memorix_detail` and `typedRefs: ["durable:<id>"]`, stating the missing fact as `purpose`.
12
14
  - Use `memorix_store_reasoning` for the reason behind a technical decision.
13
15
  - Use `memorix_resolve` when completed or outdated memories should stop surfacing.
14
16
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memorix",
4
- "version": "1.2.9",
4
+ "version": "1.3.0",
5
5
  "description": "Shared workspace memory for Claude Code and other AI coding agents.",
6
6
  "author": {
7
7
  "name": "AVIDS2",
@@ -17,6 +17,7 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
17
17
  | Need the full source for a search hit | `memorix_detail` | `memorix memory detail --id <id>` |
18
18
  | Need the sequence around one memory | `memorix_timeline` | `memorix memory timeline --id <id>` |
19
19
  | Learned reusable project knowledge | `memorix_store` | `memorix memory store --type <type> --entity <name> --title "<title>" "<text>"` |
20
+ | Stable source-backed fact or procedure needing long-term review | `memorix_store` with `longTerm` | `memorix memory long-term qualify|approve --id <id> --reason "..."` |
20
21
  | Task or bug is complete/outdated | `memorix_resolve` | `memorix memory resolve --ids <ids>` |
21
22
 
22
23
  ## Search Rules
@@ -41,4 +42,6 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
41
42
  | Accepted compromise | `trade-off` |
42
43
 
43
44
  - Use concise titles, stable entity names, relevant `filesModified`, and `topicKey` for evolving topics.
45
+ - Use `longTerm` only for a stable fact, reusable procedure, or completed episode that merits explicit review. It creates a candidate, not live context; keep the default project scope and do not use it for routine updates.
46
+ - A `user` + `portable` durable memory in a task brief is intentionally reusable across projects. When it matches the task, use it as background even if it originated elsewhere; it is not a current-project fact. Expand it only when needed with `memorix_detail` and `typedRefs: ["durable:<id>"]`, stating the missing fact as `purpose`.
44
47
  - Do not store secrets, credentials, raw private transcripts, trivial commands, or routine file reads.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.2.9",
3
+ "version": "1.3.0",
4
4
  "description": "Shared workspace memory for Codex and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -18,6 +18,7 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
18
18
  | Need the full source for a search hit | `memorix_detail` | `memorix memory detail --id <id>` |
19
19
  | Need the sequence around one memory | `memorix_timeline` | `memorix memory timeline --id <id>` |
20
20
  | Learned reusable project knowledge | `memorix_store` | `memorix memory store --type <type> --entity <name> --title "<title>" "<text>"` |
21
+ | Stable source-backed fact or procedure needing long-term review | `memorix_store` with `longTerm` | `memorix memory long-term qualify|approve --id <id> --reason "..."` |
21
22
  | Task or bug is complete/outdated | `memorix_resolve` | `memorix memory resolve --ids <ids>` |
22
23
 
23
24
  ## Search Rules
@@ -42,4 +43,6 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
42
43
  | Accepted compromise | `trade-off` |
43
44
 
44
45
  - Use concise titles, stable entity names, relevant `filesModified`, and `topicKey` for evolving topics.
46
+ - Use `longTerm` only for a stable fact, reusable procedure, or completed episode that merits explicit review. It creates a candidate, not live context; keep the default project scope and do not use it for routine updates.
47
+ - A `user` + `portable` durable memory in a task brief is intentionally reusable across projects. When it matches the task, use it as background even if it originated elsewhere; it is not a current-project fact. Expand it only when needed with `memorix_detail` and `typedRefs: ["durable:<id>"]`, stating the missing fact as `purpose`.
45
48
  - Do not store secrets, credentials, raw private transcripts, trivial commands, or routine file reads.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.2.9",
3
+ "version": "1.3.0",
4
4
  "description": "Shared local workspace memory for GitHub Copilot CLI and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -18,6 +18,7 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
18
18
  | Need the full source for a search hit | `memorix_detail` | `memorix memory detail --id <id>` |
19
19
  | Need the sequence around one memory | `memorix_timeline` | `memorix memory timeline --id <id>` |
20
20
  | Learned reusable project knowledge | `memorix_store` | `memorix memory store --type <type> --entity <name> --title "<title>" "<text>"` |
21
+ | Stable source-backed fact or procedure needing long-term review | `memorix_store` with `longTerm` | `memorix memory long-term qualify|approve --id <id> --reason "..."` |
21
22
  | Task or bug is complete/outdated | `memorix_resolve` | `memorix memory resolve --ids <ids>` |
22
23
 
23
24
  ## Search Rules
@@ -42,4 +43,6 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
42
43
  | Accepted compromise | `trade-off` |
43
44
 
44
45
  - Use concise titles, stable entity names, relevant `filesModified`, and `topicKey` for evolving topics.
46
+ - Use `longTerm` only for a stable fact, reusable procedure, or completed episode that merits explicit review. It creates a candidate, not live context; keep the default project scope and do not use it for routine updates.
47
+ - A `user` + `portable` durable memory in a task brief is intentionally reusable across projects. When it matches the task, use it as background even if it originated elsewhere; it is not a current-project fact. Expand it only when needed with `memorix_detail` and `typedRefs: ["durable:<id>"]`, stating the missing fact as `purpose`.
45
48
  - Do not store secrets, credentials, raw private transcripts, trivial commands, or routine file reads.
@@ -18,6 +18,7 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
18
18
  | Need the full source for a search hit | `memorix_detail` | `memorix memory detail --id <id>` |
19
19
  | Need the sequence around one memory | `memorix_timeline` | `memorix memory timeline --id <id>` |
20
20
  | Learned reusable project knowledge | `memorix_store` | `memorix memory store --type <type> --entity <name> --title "<title>" "<text>"` |
21
+ | Stable source-backed fact or procedure needing long-term review | `memorix_store` with `longTerm` | `memorix memory long-term qualify|approve --id <id> --reason "..."` |
21
22
  | Task or bug is complete/outdated | `memorix_resolve` | `memorix memory resolve --ids <ids>` |
22
23
 
23
24
  ## Search Rules
@@ -42,4 +43,6 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
42
43
  | Accepted compromise | `trade-off` |
43
44
 
44
45
  - Use concise titles, stable entity names, relevant `filesModified`, and `topicKey` for evolving topics.
46
+ - Use `longTerm` only for a stable fact, reusable procedure, or completed episode that merits explicit review. It creates a candidate, not live context; keep the default project scope and do not use it for routine updates.
47
+ - A `user` + `portable` durable memory in a task brief is intentionally reusable across projects. When it matches the task, use it as background even if it originated elsewhere; it is not a current-project fact. Expand it only when needed with `memorix_detail` and `typedRefs: ["durable:<id>"]`, stating the missing fact as `purpose`.
45
48
  - Do not store secrets, credentials, raw private transcripts, trivial commands, or routine file reads.
@@ -11,6 +11,8 @@ Use Memorix when prior workspace context, decisions, fixes, or handoff state wou
11
11
  - Use `memorix_search` for focused lookup.
12
12
  - Use `memorix_detail` before relying on a specific memory.
13
13
  - Use `memorix_store` for durable workspace knowledge.
14
+ - Use `longTerm` on `memorix_store` only for a stable fact, reusable procedure, or completed episode that merits explicit review. It creates a candidate, not live context; keep the default project scope and do not use it for routine updates.
15
+ - A `user` + `portable` durable memory in a task brief is intentionally reusable across projects. When it matches the task, use it as background even if it originated elsewhere; it is not a current-project fact. Expand it only when needed with `memorix_detail` and `typedRefs: ["durable:<id>"]`, stating the missing fact as `purpose`.
14
16
  - Use `memorix_store_reasoning` for the reason behind a technical decision.
15
17
  - Use `memorix_resolve` when completed or outdated memories should stop surfacing.
16
18
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.2.9",
3
+ "version": "1.3.0",
4
4
  "mcpServers": {
5
5
  "memorix": {
6
6
  "command": "memorix",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-omp-package",
3
- "version": "1.2.9",
3
+ "version": "1.3.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.2.9",
3
+ "version": "1.3.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.2.9",
3
+ "version": "1.3.0",
4
4
  "description": "Memorix shared workspace memory package for Pi coding agent.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -18,6 +18,7 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
18
18
  | Need the full source for a search hit | `memorix_detail` | `memorix memory detail --id <id>` |
19
19
  | Need the sequence around one memory | `memorix_timeline` | `memorix memory timeline --id <id>` |
20
20
  | Learned reusable project knowledge | `memorix_store` | `memorix memory store --type <type> --entity <name> --title "<title>" "<text>"` |
21
+ | Stable source-backed fact or procedure needing long-term review | `memorix_store` with `longTerm` | `memorix memory long-term qualify|approve --id <id> --reason "..."` |
21
22
  | Task or bug is complete/outdated | `memorix_resolve` | `memorix memory resolve --ids <ids>` |
22
23
 
23
24
  ## Search Rules
@@ -42,4 +43,6 @@ Use Memorix as the shared memory layer for the active workspace when Memorix too
42
43
  | Accepted compromise | `trade-off` |
43
44
 
44
45
  - Use concise titles, stable entity names, relevant `filesModified`, and `topicKey` for evolving topics.
46
+ - Use `longTerm` only for a stable fact, reusable procedure, or completed episode that merits explicit review. It creates a candidate, not live context; keep the default project scope and do not use it for routine updates.
47
+ - A `user` + `portable` durable memory in a task brief is intentionally reusable across projects. When it matches the task, use it as background even if it originated elsewhere; it is not a current-project fact. Expand it only when needed with `memorix_detail` and `typedRefs: ["durable:<id>"]`, stating the missing fact as `purpose`.
45
48
  - Do not store secrets, credentials, raw private transcripts, trivial commands, or routine file reads.
package/server.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.AVIDS2/memorix",
4
+ "title": "Memorix",
5
+ "description": "Local-first persistent project memory for AI coding agents across MCP clients and sessions.",
6
+ "repository": {
7
+ "url": "https://github.com/AVIDS2/memorix",
8
+ "source": "github"
9
+ },
10
+ "version": "1.3.0",
11
+ "websiteUrl": "https://github.com/AVIDS2/memorix#readme",
12
+ "icons": [
13
+ {
14
+ "src": "https://raw.githubusercontent.com/AVIDS2/memorix/main/assets/logo.png",
15
+ "mimeType": "image/png",
16
+ "sizes": [
17
+ "1024x1024"
18
+ ]
19
+ }
20
+ ],
21
+ "packages": [
22
+ {
23
+ "registryType": "npm",
24
+ "identifier": "memorix",
25
+ "version": "1.3.0",
26
+ "runtimeHint": "npx",
27
+ "packageArguments": [
28
+ {
29
+ "type": "positional",
30
+ "value": "serve"
31
+ }
32
+ ],
33
+ "transport": {
34
+ "type": "stdio"
35
+ }
36
+ }
37
+ ]
38
+ }
@@ -15,8 +15,16 @@ const GUIDES: Record<string, CliCommandGuide> = {
15
15
  'memorix memory resolve --ids 42,43 [--status resolved|archived]',
16
16
  'memorix memory consolidate --action preview|execute',
17
17
  'memorix memory promote --ids 42,43 [--trigger "..."] [--instruction "..."]',
18
+ 'memorix memory long-term list [--all]',
19
+ 'memorix memory long-term add --kind semantic --scope user --portability portable --title "..." --text "..." [--tags "..."] [--applicability "..."]',
20
+ 'memorix memory long-term promote --from-observation 42 --kind procedural --scope project',
21
+ 'memorix memory long-term qualify|approve|archive --id <id> --reason "..."',
22
+ 'memorix memory long-term supersede --id <old-id> --superseded-by <qualified-id> --reason "..."',
23
+ ],
24
+ notes: [
25
+ 'Personal and team visibility require `memorix identity join` or `memorix identity use` first.',
26
+ 'Candidates never enter a task Workset. Only manual or user-confirmed `user + portable` memory may cross local projects.',
18
27
  ],
19
- notes: ['Personal and team visibility require `memorix identity join` or `memorix identity use` first.'],
20
28
  },
21
29
  identity: {
22
30
  summary: 'Select the active local actor for private memory and coordination commands.',
@@ -12,6 +12,7 @@ import {
12
12
  getSetupAgentTargets,
13
13
  installPluginPackage,
14
14
  installMcpConfig,
15
+ migrateLegacyCodexIntegration,
15
16
  tryInstallCodexPlugin,
16
17
  type McpConfigAgent,
17
18
  } from './setup.js';
@@ -228,7 +229,7 @@ function asRecordArray(value: unknown): JsonRecord[] {
228
229
  }
229
230
 
230
231
  function codexPluginPath(): string {
231
- return `${homedir()}/.codex/plugins/${CODEX_PLUGIN_NAME}`;
232
+ return `${homedir()}/plugins/${CODEX_PLUGIN_NAME}`;
232
233
  }
233
234
 
234
235
  function codexPluginMcpPath(): string {
@@ -254,7 +255,10 @@ function isMemorixCodexPlugin(value: JsonRecord): boolean {
254
255
 
255
256
  export function parseCodexPluginList(output: string): AgentPluginCheck['runtime'] | null {
256
257
  try {
257
- const parsed = asRecord(JSON.parse(output));
258
+ const start = output.indexOf('{');
259
+ const end = output.lastIndexOf('}');
260
+ if (start < 0 || end < start) return null;
261
+ const parsed = asRecord(JSON.parse(output.slice(start, end + 1)));
258
262
  if (!parsed) return null;
259
263
  const entries = [...asRecordArray(parsed.installed), ...asRecordArray(parsed.available)];
260
264
  const plugin = entries.find(isMemorixCodexPlugin);
@@ -407,7 +411,7 @@ async function inspectCodexMarketplace(): Promise<AgentPluginCheck> {
407
411
 
408
412
  const source = asRecord(entry.source);
409
413
  const sourcePath = typeof source?.path === 'string' ? source.path : '';
410
- const issues = source?.source === 'local' && normalizeMarketplacePath(sourcePath) === '.codex/plugins/memorix'
414
+ const issues = source?.source === 'local' && normalizeMarketplacePath(sourcePath) === 'plugins/memorix'
411
415
  ? []
412
416
  : ['codex-marketplace-entry-stale'];
413
417
  return {
@@ -1060,9 +1064,11 @@ export async function repairAgentIntegrations(options: {
1060
1064
  } else {
1061
1065
  if (!options.dry) {
1062
1066
  await installPluginPackage({ agent: 'codex' });
1063
- if (needsInstall) {
1064
- const install = tryInstallCodexPlugin();
1065
- if (!install.ok) skipped.push('codex:plugin:global:install-pending');
1067
+ const install = tryInstallCodexPlugin();
1068
+ if (install.ok) {
1069
+ await migrateLegacyCodexIntegration({ projectRoot });
1070
+ } else {
1071
+ skipped.push('codex:plugin:global:install-pending');
1066
1072
  }
1067
1073
  }
1068
1074
  changed.push('codex:plugin:global');
@@ -217,6 +217,7 @@ export default defineCommand({
217
217
  ...(external.caution
218
218
  ? { runtimeCautions: [{ kind: 'external-codegraph-fallback' as const, message: external.caution }] }
219
219
  : {}),
220
+ reader,
220
221
  });
221
222
  emitResult({ project, pack, providerQuality: external.quality }, buildContextPackPrompt(pack), asJson);
222
223
  return;
@@ -51,17 +51,30 @@ export default defineCommand({
51
51
  instruction: { type: 'string', description: 'Custom instruction for promoted mini-skills' },
52
52
  tags: { type: 'string', description: 'Comma-separated extra tags for promoted mini-skills' },
53
53
  skillId: { type: 'string', description: 'Mini-skill ID for list/delete actions' },
54
+ kind: { type: 'string', description: 'Long-term memory kind: episodic, semantic, or procedural' },
55
+ scope: { type: 'string', description: 'Long-term memory scope: project, user, or team' },
56
+ portability: { type: 'string', description: 'Long-term memory portability: project-bound or portable' },
57
+ applicability: { type: 'string', description: 'When a long-term memory applies' },
58
+ fromObservation: { type: 'string', description: 'Observation ID to promote into long-term memory' },
59
+ supersededBy: { type: 'string', description: 'Qualified or approved long-term memory that replaces the current record' },
60
+ 'superseded-by': { type: 'string', description: 'Kebab-case alias for --supersededBy' },
61
+ reason: { type: 'string', description: 'Evidence review reason for qualify, approve, or archive' },
62
+ all: { type: 'boolean', description: 'Include archived and superseded long-term memories in list output' },
54
63
  json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
55
64
  },
56
65
  run: async ({ args }) => {
57
66
  const action = (args._ as string[])?.[0] || '';
58
67
  const positional = ((args._ as string[]) ?? []).slice(1);
68
+ const longTermAction = action === 'long-term'
69
+ ? (positional[0] || (args.action as string | undefined) || 'list').trim().toLowerCase()
70
+ : undefined;
59
71
  const asJson = !!args.json;
60
72
 
61
73
  try {
62
74
  const readOnlyActions = new Set(['', 'search', 'graph-context', 'recent', 'suggest-topic-key', 'detail', 'timeline']);
63
75
  const needsSearchIndex = action === 'search' || action === 'detail' || action === 'timeline';
64
- const context = readOnlyActions.has(action)
76
+ const longTermReadOnly = longTermAction === 'list' || longTermAction === 'show';
77
+ const context = readOnlyActions.has(action) || longTermReadOnly
65
78
  ? await getCliReadContext({ searchIndex: needsSearchIndex })
66
79
  : await getCliProjectContext({ searchIndex: true });
67
80
  const { project, dataDir, reader, identity } = context;
@@ -425,6 +438,178 @@ export default defineCommand({
425
438
  return;
426
439
  }
427
440
 
441
+ case 'long-term': {
442
+ const resolvedLongTermAction = longTermAction ?? 'list';
443
+ const longTermPositional = positional.slice(1);
444
+ const {
445
+ archiveLongTermMemory,
446
+ approveLongTermMemory,
447
+ createManualLongTermMemory,
448
+ getLongTermMemoryDetail,
449
+ listLongTermMemories,
450
+ promoteObservationToLongTermMemory,
451
+ qualifyLongTermMemory,
452
+ supersedeLongTermMemory,
453
+ } = await import('../../memory/long-term.js');
454
+ const { resolveLocalMemoryOwner } = await import('../../memory/owner.js');
455
+ const owner = await resolveLocalMemoryOwner(dataDir, { create: false });
456
+ const longTermReader = {
457
+ projectId: project.id,
458
+ ...(owner ? { ownerId: owner.id } : {}),
459
+ ...(reader.agentId ? { agentId: reader.agentId } : {}),
460
+ ...(reader.isTeamMember ? { isTeamMember: true } : {}),
461
+ };
462
+
463
+ const memoryId = (): string => {
464
+ const id = (args.id as string | undefined)?.trim()
465
+ || (args.fromObservation as string | undefined)?.trim()
466
+ || longTermPositional[0]?.trim();
467
+ if (!id) throw new Error('long-term memory id is required.');
468
+ return id;
469
+ };
470
+ const scope = () => longTermScope(args.scope as string | undefined);
471
+ const kind = () => longTermKind(args.kind as string | undefined);
472
+ const portability = () => longTermPortability(args.portability as string | undefined);
473
+ const reason = () => requiredLongTermReason(args.reason as string | undefined);
474
+
475
+ switch (resolvedLongTermAction) {
476
+ case 'list': {
477
+ const memories = await listLongTermMemories({
478
+ dataDir,
479
+ reader: longTermReader,
480
+ includeInactive: !!args.all,
481
+ limit: parsePositiveInt(args.limit as string | undefined, 50),
482
+ });
483
+ emitResult(
484
+ { project, memories },
485
+ memories.length === 0
486
+ ? 'No long-term memories are visible in the active scope.'
487
+ : memories.map(item => `- ${item.memory.id} [${item.memory.state}/${item.memory.kind}/${item.memory.scope}] ${item.memory.title}`).join('\n'),
488
+ asJson,
489
+ );
490
+ return;
491
+ }
492
+
493
+ case 'show': {
494
+ const detail = await getLongTermMemoryDetail({ dataDir, id: memoryId(), reader: longTermReader });
495
+ emitResult(
496
+ { project, ...detail },
497
+ [
498
+ `${detail.memory.id} [${detail.memory.state}/${detail.memory.kind}/${detail.memory.scope}]`,
499
+ detail.memory.title,
500
+ detail.memory.content,
501
+ `Evidence: ${detail.evidence.length}`,
502
+ `Audit events: ${detail.events.length}`,
503
+ ].join('\n'),
504
+ asJson,
505
+ );
506
+ return;
507
+ }
508
+
509
+ case 'add': {
510
+ const content = getStringArg(args.text as string | undefined, longTermPositional);
511
+ if (!content) {
512
+ emitError('text is required for "memorix memory long-term add"', asJson);
513
+ return;
514
+ }
515
+ const result = await createManualLongTermMemory({
516
+ dataDir,
517
+ projectId: project.id,
518
+ scope: scope(),
519
+ kind: kind(),
520
+ title: (args.title as string | undefined)?.trim() || content.slice(0, 100),
521
+ content,
522
+ facts: parseCsvList(args.facts as string | undefined),
523
+ tags: parseCsvList(args.tags as string | undefined),
524
+ applicability: (args.applicability as string | undefined)?.trim(),
525
+ portability: portability(),
526
+ reader: longTermReader,
527
+ });
528
+ emitResult(
529
+ { project, ...result },
530
+ `Created long-term memory candidate ${result.memory.id}. Qualify it after checking its evidence.`,
531
+ asJson,
532
+ );
533
+ return;
534
+ }
535
+
536
+ case 'promote': {
537
+ const rawId = (args.fromObservation as string | undefined)?.trim()
538
+ || (args.id as string | undefined)?.trim()
539
+ || longTermPositional[0]?.trim();
540
+ const observationId = Number.parseInt(rawId || '', 10);
541
+ if (!Number.isFinite(observationId)) {
542
+ emitError('fromObservation or id must be an observation ID for "memorix memory long-term promote"', asJson);
543
+ return;
544
+ }
545
+ const observation = getObservation(observationId, project.id);
546
+ if (!observation || !canManageObservation(observation, reader)) {
547
+ emitError('Observation was not found or is outside the active write scope.', asJson);
548
+ return;
549
+ }
550
+ const result = await promoteObservationToLongTermMemory({
551
+ dataDir,
552
+ observation,
553
+ scope: scope(),
554
+ kind: kind(),
555
+ tags: parseCsvList(args.tags as string | undefined),
556
+ applicability: (args.applicability as string | undefined)?.trim(),
557
+ reader: longTermReader,
558
+ });
559
+ emitResult(
560
+ { project, sourceObservationId: observation.id, ...result },
561
+ `Promoted observation #${observation.id} to long-term memory candidate ${result.memory.id}.`,
562
+ asJson,
563
+ );
564
+ return;
565
+ }
566
+
567
+ case 'qualify': {
568
+ await getLongTermMemoryDetail({ dataDir, id: memoryId(), reader: longTermReader });
569
+ const memory = await qualifyLongTermMemory({ dataDir, id: memoryId(), reason: reason() });
570
+ emitResult({ project, memory }, `Qualified long-term memory ${memory.id}.`, asJson);
571
+ return;
572
+ }
573
+
574
+ case 'approve': {
575
+ await getLongTermMemoryDetail({ dataDir, id: memoryId(), reader: longTermReader });
576
+ const memory = await approveLongTermMemory({ dataDir, id: memoryId(), reason: reason() });
577
+ emitResult({ project, memory }, `Approved long-term memory ${memory.id}.`, asJson);
578
+ return;
579
+ }
580
+
581
+ case 'archive': {
582
+ await getLongTermMemoryDetail({ dataDir, id: memoryId(), reader: longTermReader });
583
+ const memory = await archiveLongTermMemory({ dataDir, id: memoryId(), reason: reason() });
584
+ emitResult({ project, memory }, `Archived long-term memory ${memory.id}.`, asJson);
585
+ return;
586
+ }
587
+
588
+ case 'supersede': {
589
+ const supersededBy = (args.supersededBy as string | undefined)?.trim()
590
+ || (args['superseded-by'] as string | undefined)?.trim();
591
+ if (!supersededBy) {
592
+ emitError('supersededBy is required for "memorix memory long-term supersede"', asJson);
593
+ return;
594
+ }
595
+ await getLongTermMemoryDetail({ dataDir, id: memoryId(), reader: longTermReader });
596
+ await getLongTermMemoryDetail({ dataDir, id: supersededBy, reader: longTermReader });
597
+ const memory = await supersedeLongTermMemory({
598
+ dataDir,
599
+ id: memoryId(),
600
+ supersededBy,
601
+ reason: reason(),
602
+ });
603
+ emitResult({ project, memory }, `Superseded long-term memory ${memory.id} with ${supersededBy}.`, asJson);
604
+ return;
605
+ }
606
+
607
+ default:
608
+ emitError('long-term action must be list, show, add, promote, qualify, approve, archive, or supersede.', asJson);
609
+ }
610
+ return;
611
+ }
612
+
428
613
  default:
429
614
  console.log('Memorix Memory Commands');
430
615
  console.log('');
@@ -440,6 +625,13 @@ export default defineCommand({
440
625
  console.log(' memorix memory deduplicate [--query "..."] [--dryRun]');
441
626
  console.log(' memorix memory consolidate [--action preview|execute] [--threshold 0.45]');
442
627
  console.log(' memorix memory promote --ids 42,43 [--trigger "..."] [--instruction "..."]');
628
+ console.log(' memorix memory long-term list [--all]');
629
+ console.log(' memorix memory long-term add --kind semantic --scope user --portability portable --title "..." --text "..." [--tags "..."] [--applicability "..."]');
630
+ console.log(' memorix memory long-term promote --fromObservation 42 --kind procedural --scope project');
631
+ console.log(' memorix memory long-term qualify --id <id> --reason "verified against evidence"');
632
+ console.log(' memorix memory long-term approve --id <id> --reason "reviewed by operator"');
633
+ console.log(' memorix memory long-term archive --id <id> --reason "no longer current"');
634
+ console.log(' memorix memory long-term supersede --id <old-id> --superseded-by <qualified-id> --reason "replaced"');
443
635
  }
444
636
  } catch (error) {
445
637
  emitError(error instanceof Error ? error.message : String(error), asJson);
@@ -459,3 +651,27 @@ function getIdArg(args: Record<string, unknown>, positional: string[]): string {
459
651
  positional.join(',')
460
652
  );
461
653
  }
654
+
655
+ function longTermKind(value: string | undefined): 'episodic' | 'semantic' | 'procedural' {
656
+ const normalized = (value ?? 'semantic').trim().toLowerCase();
657
+ if (normalized === 'episodic' || normalized === 'semantic' || normalized === 'procedural') return normalized;
658
+ throw new Error('long-term kind must be episodic, semantic, or procedural.');
659
+ }
660
+
661
+ function longTermScope(value: string | undefined): 'project' | 'user' | 'team' {
662
+ const normalized = (value ?? 'project').trim().toLowerCase();
663
+ if (normalized === 'project' || normalized === 'user' || normalized === 'team') return normalized;
664
+ throw new Error('long-term scope must be project, user, or team.');
665
+ }
666
+
667
+ function longTermPortability(value: string | undefined): 'project-bound' | 'portable' {
668
+ const normalized = (value ?? 'project-bound').trim().toLowerCase();
669
+ if (normalized === 'project-bound' || normalized === 'portable') return normalized;
670
+ throw new Error('long-term portability must be project-bound or portable.');
671
+ }
672
+
673
+ function requiredLongTermReason(value: string | undefined): string {
674
+ const reason = value?.trim();
675
+ if (!reason) throw new Error('reason is required for this long-term memory transition.');
676
+ return reason;
677
+ }