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.
- package/CHANGELOG.md +24 -0
- package/README.md +30 -4
- package/README.zh-CN.md +28 -2
- package/assets/star-history-dark.svg +30 -0
- package/assets/star-history-light.svg +30 -0
- package/assets/star-history.json +599 -0
- package/dist/cli/index.js +1945 -579
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1292 -267
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.js +97 -21
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +24 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +1294 -269
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +99 -1
- package/dist/types.js.map +1 -1
- package/dist/vector-backfill-runner.js +97 -21
- package/dist/vector-backfill-runner.js.map +1 -1
- package/docs/1.3-MEMORY-ARCHITECTURE.md +236 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +5 -3
- package/docs/API_REFERENCE.md +20 -1
- package/docs/ARCHITECTURE.md +34 -1
- package/docs/DEVELOPMENT.md +10 -3
- package/docs/INTEGRATIONS.md +1 -1
- package/docs/MODULES.md +27 -1
- package/docs/README.md +3 -3
- package/docs/SETUP.md +6 -4
- package/docs/dev-log/progress.txt +22 -0
- package/docs/hooks-architecture.md +1 -1
- package/llms-full.txt +5 -1
- package/llms.txt +2 -0
- package/package.json +7 -2
- package/plugins/antigravity/memorix/rules/memorix.md +2 -0
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/copilot/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/cursor/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/plugins/gemini/memorix/GEMINI.md +2 -0
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/plugins/pi/memorix/skills/memorix-memory/SKILL.md +3 -0
- package/server.json +38 -0
- package/src/cli/command-guide.ts +9 -1
- package/src/cli/commands/agent-integrations.ts +12 -6
- package/src/cli/commands/codegraph.ts +1 -0
- package/src/cli/commands/memory.ts +217 -1
- package/src/cli/commands/setup.ts +200 -23
- package/src/codegraph/auto-context.ts +11 -0
- package/src/codegraph/context-pack.ts +3 -0
- package/src/embedding/api-provider.ts +23 -9
- package/src/embedding/provider.ts +21 -10
- package/src/hooks/installers/index.ts +2 -0
- package/src/knowledge/context-assembly.ts +2 -1
- package/src/knowledge/workset.ts +77 -4
- package/src/memory/long-term-store.ts +242 -0
- package/src/memory/long-term-types.ts +136 -0
- package/src/memory/long-term.ts +626 -0
- package/src/memory/owner.ts +75 -0
- package/src/server.ts +112 -30
- package/src/store/sqlite-db.ts +74 -0
- package/src/types.ts +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memorix",
|
|
3
|
-
"
|
|
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
|
|
|
@@ -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.
|
|
@@ -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.
|
|
@@ -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
|
|
|
@@ -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
|
+
}
|
package/src/cli/command-guide.ts
CHANGED
|
@@ -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()}
|
|
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
|
|
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) === '
|
|
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
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
|
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
|
+
}
|