memorix 1.4.5 → 1.4.6

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 (75) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/CLAUDE.md +7 -3
  3. package/README.md +5 -2
  4. package/dist/cli/index.js +1456 -1013
  5. package/dist/index.js +1265 -988
  6. package/dist/{maintenance-jobs-B1-W0ssz.d.ts → maintenance-jobs-DVRF-46r.d.ts} +1 -1
  7. package/dist/maintenance-runner.d.ts +1 -1
  8. package/dist/maintenance-runner.js +1755 -829
  9. package/dist/media-audio-runner.js +1760 -835
  10. package/dist/media-video-runner.js +1755 -830
  11. package/dist/memcode-runtime/CHANGELOG.md +54 -0
  12. package/dist/sdk.js +1257 -987
  13. package/dist/types.d.ts +6 -0
  14. package/dist/vector-backfill-runner.d.ts +1 -1
  15. package/dist/vector-backfill-runner.js +1751 -826
  16. package/docs/1.4.5-AGENT-AUTO-MEMORY-RESEARCH.md +95 -0
  17. package/docs/API_REFERENCE.md +23 -4
  18. package/docs/CONFIGURATION.md +17 -3
  19. package/docs/SETUP.md +5 -3
  20. package/package.json +1 -1
  21. package/plugins/antigravity/memorix/mcp_config.json +3 -1
  22. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  23. package/plugins/claude/memorix/.mcp.json +1 -1
  24. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  25. package/plugins/codebuddy/memorix/.mcp.json +1 -1
  26. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  27. package/plugins/codex/memorix/.mcp.json +1 -1
  28. package/plugins/copilot/memorix/.mcp.json +1 -1
  29. package/plugins/copilot/memorix/plugin.json +1 -1
  30. package/plugins/gemini/memorix/gemini-extension.json +4 -2
  31. package/plugins/omp/memorix/package.json +1 -1
  32. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  33. package/plugins/openclaw/memorix/.mcp.json +3 -1
  34. package/plugins/pi/memorix/package.json +1 -1
  35. package/server.json +2 -2
  36. package/src/cli/commands/dashboard.ts +14 -3
  37. package/src/cli/commands/ingest-commit.ts +47 -3
  38. package/src/cli/commands/memory.ts +17 -4
  39. package/src/cli/commands/purge.ts +120 -0
  40. package/src/cli/commands/serve-http.ts +39 -5
  41. package/src/cli/commands/setup.ts +3 -1
  42. package/src/cli/index.ts +3 -804
  43. package/src/codegraph/auto-context.ts +82 -1
  44. package/src/config/yaml-loader.ts +3 -8
  45. package/src/config.ts +0 -5
  46. package/src/evaluation/session-replay.ts +98 -0
  47. package/src/git/extractor.ts +5 -3
  48. package/src/hooks/installers/index.ts +15 -0
  49. package/src/knowledge/workset.ts +58 -0
  50. package/src/memory/long-term.ts +97 -2
  51. package/src/runtime/control-plane-maintenance.ts +1 -0
  52. package/src/runtime/isolated-maintenance.ts +1 -0
  53. package/src/runtime/lifecycle.ts +19 -0
  54. package/src/runtime/maintenance-jobs.ts +1 -0
  55. package/src/runtime/project-maintenance.ts +8 -0
  56. package/src/search/surfaced-registry.ts +62 -0
  57. package/src/server.ts +74 -14
  58. package/src/store/orama-store.ts +17 -1
  59. package/src/types.ts +6 -0
  60. package/src/cli/commands/audit-list.ts +0 -89
  61. package/src/cli/commands/purge-all-memory.ts +0 -85
  62. package/src/cli/commands/purge-project-memory.ts +0 -83
  63. package/src/cli/commands/uninstall-project-artifacts.ts +0 -86
  64. package/src/cli/tui/Sidebar.tsx +0 -179
  65. package/src/cli/tui/interactive-commands.ts +0 -261
  66. package/src/cli/tui/markdown-render.tsx +0 -371
  67. package/src/cli/tui/use-mouse.ts +0 -157
  68. package/src/cli/tui/useNavigation.ts +0 -56
  69. package/src/orchestrate/context-compact.ts +0 -188
  70. package/src/team/file-locks.ts +0 -173
  71. package/src/team/index.ts +0 -23
  72. package/src/team/messages.ts +0 -203
  73. package/src/team/persistence.ts +0 -96
  74. package/src/team/registry.ts +0 -158
  75. package/src/team/tasks.ts +0 -211
@@ -0,0 +1,95 @@
1
+ # Research: Claude Code Auto Memory and What Memorix Should Borrow
2
+
3
+ > Status: research note (2026-08-14, against the 1.4.5 line). This document
4
+ > records findings and direction; it is not a shipped feature and not a claim
5
+ > that any item below is implemented.
6
+
7
+ ## Question
8
+
9
+ Claude Code's Auto Memory gives a single agent a file-based, recallable
10
+ workspace memory: `~/.claude/projects/<git-root>/memory/` with a `MEMORY.md`
11
+ entrypoint index and typed memory files. What is the design, how does it
12
+ compare with Memorix, and which parts are worth absorbing?
13
+
14
+ ## Sourcing caveat
15
+
16
+ The mechanism-level details below come from community source-level analyses
17
+ and third-party write-ups, not from an official Anthropic specification:
18
+
19
+ - Source-level analysis of the memory system (`memdir`): project-memory.mdx
20
+ in a community Claude Code docs mirror
21
+ (https://github.com/claude-code-best/claude-code/blob/master/docs/context/project-memory.mdx)
22
+ - Independent critical analysis:
23
+ https://dev.to/chen_zhang_bac430bc7f6b95/claude-codes-memory-4-layers-of-complexity-still-just-grep-and-a-200-line-cap-2kn9
24
+ - Community adoption discussion:
25
+ https://developer.aliyun.com/article/1713756
26
+
27
+ Treat feature-flag names (`KAIROS`, `TEAMMEM`) and thresholds as observed
28
+ behavior of a moving target, not as stable contracts.
29
+
30
+ ## The design, in brief
31
+
32
+ - Pure Markdown files, no database, no vector store. One directory per
33
+ sanitized Git root, shared across worktrees of that repository.
34
+ - `MEMORY.md` is the entrypoint index: at most 200 lines AND 25 KB, one
35
+ `- [Title](file.md) — one-line hook` pointer per line, loaded into every
36
+ session. The knowledge lives in topic files, loaded on demand.
37
+ - A closed four-type taxonomy (user / feedback / project / reference), each
38
+ type with explicit `<when_to_save>`, `<how_to_use>`, and `<body_structure>`
39
+ rules. Each memory file carries frontmatter whose `description` field is
40
+ written for the recall system, not for humans.
41
+ - The core saving rule: only store information that cannot be derived from
42
+ the current project state. Code, file paths, and Git history are
43
+ re-derivable and do not belong in memory.
44
+ - Recall is a lightweight model side-query that picks at most 5 relevant
45
+ memories from the manifest, with denoising for recently used tools and
46
+ dedup of already-surfaced files.
47
+ - Memory is injected as a user-context message (prompt-cache friendly).
48
+ - Drift defense is prompt-level: a memory that names a file, function, or
49
+ flag is a claim about the past — verify before recommending. An explicit
50
+ "ignore memory" instruction means proceed as if `MEMORY.md` were empty.
51
+ - Consolidation ("dream") is time/volume-triggered, merging contradictions
52
+ and pruning stale entries. Long-running sessions may switch to append-only
53
+ daily logs that a later pass distills.
54
+ - Security: the memory directory resolves only from user-level input
55
+ (environment override or a user setting). Project-level settings are
56
+ deliberately excluded from that resolution chain, so a hostile repository
57
+ cannot redirect memory storage to arbitrary paths.
58
+
59
+ ## Comparison with Memorix
60
+
61
+ | Aspect | Claude Code Auto Memory | Memorix (1.4.5) |
62
+ | --- | --- | --- |
63
+ | Audience | One agent, one product | Any MCP/CLI/SDK/hooks agent, shared |
64
+ | Storage | Markdown files | SQLite canonical store + Orama search |
65
+ | Entry injection | Full index, fixed 200-line/25 KB cap | Task-lensed Workset, budget-governed |
66
+ | Taxonomy | Closed 4 types | 11 observation types + reasoning + Git + long-term |
67
+ | Recall | Paid model side-query (needs a key) | Local BM25 primary + bounded semantic fallback |
68
+ | Drift defense | Prompt-level verify-before-use | Freshness, attribution guard, evidence governor |
69
+ | Cleanup | Time/volume-triggered consolidation | Retention + dedup + consolidation pipeline |
70
+ | Path security | User-level-only resolution | User-level-only by construction (env var only) |
71
+
72
+ The critical review's complaint — memory trapped in one product's sandbox,
73
+ no cross-agent sharing — is exactly the space Memorix occupies.
74
+
75
+ ## What Memorix should borrow (directions, not commitments)
76
+
77
+ Shipped (2026-08-14, continuing on the 1.4.x line):
78
+ 1. **The "only store the underivable" rule, made explicit.** Now part of every
79
+ generated guidance file, with the dual-channel rule and the verify-before-use
80
+ / ignore semantics.
81
+ 2. **Dual-channel feedback.** Guidance now tells agents to record validated
82
+ approaches and explicit user confirmations, not only corrections.
83
+ 3. **Session-level dedup of already-surfaced memory.** `memorix_search`
84
+ demotes rows already shown in the same session (never removes them) and
85
+ remembers the surfaced set per session; measured 25% → 19% duplicate rate
86
+ with hits and budget unchanged.
87
+ 4. **Explicit ignore semantics.** Guidance now states the contract: an ignore
88
+ instruction means proceed as if memory were empty.
89
+ 5. **Keep the security boundary tested.** Pinned by
90
+ `tests/config/data-dir-boundary.test.ts`.
91
+
92
+ Scoring lives in `tests/evaluation/session-replay-evaluation.test.ts`
93
+ (retrieval) and `tests/evaluation/guidance-content.test.ts` (guidance); both
94
+ are deterministic and offline. A future change to these behaviors must update
95
+ the pinned scores only with a measured improvement.
@@ -223,7 +223,7 @@ Important inputs:
223
223
  - optional `relatedCommits`
224
224
  - optional `relatedEntities`
225
225
  - optional `visibility`: `project` (default), `personal`, or `team`
226
- - optional `longTerm`: an explicit source-backed long-term candidate with `kind` (`episodic`, `semantic`, or `procedural`), an optional `scope` (`project`, `user`, or `team`), tags, and applicability
226
+ - optional `longTerm`: an explicit source-backed long-term record with `kind` (`episodic`, `semantic`, or `procedural`), an optional `scope` (`project`, `user`, or `team`), tags, and applicability
227
227
 
228
228
  Visibility controls who can retrieve a record through agent-facing memory
229
229
  tools. Normal memories default to `project`. `personal` and `team` writes
@@ -236,11 +236,15 @@ files, `memorix_store` returns without writing. Use `overrideReadOnly: true`
236
236
  only when the user explicitly asks to preserve a record during that task.
237
237
 
238
238
  `longTerm` is deliberately opt-in. It stores the normal Observation first, then
239
- creates a separate candidate with an evidence reference. Candidates are not
240
- inserted into a Task Workset. Review them with `memorix memory long-term
241
- list|show|qualify|approve|archive|supersede`. Source-derived entries remain
239
+ creates a separate record with an evidence reference. Because an explicit
240
+ request carries its own source evidence, the new record is auto-qualified on
241
+ the spot and enters Task Worksets as durable context; approval remains an
242
+ explicit operator review. Manage records with `memorix memory long-term
243
+ list|show|approve|archive|supersede`. Source-derived entries remain
242
244
  project-bound; only a manual or user-confirmed `user + portable` record created
243
245
  through the CLI may cross local projects for the same local installation.
246
+ Hook-captured and git-derived candidates stay in the candidate state until
247
+ qualified.
244
248
 
245
249
  Example:
246
250
 
@@ -540,6 +544,21 @@ Use it to inspect:
540
544
  - stage timing
541
545
  - recent pipeline behavior
542
546
 
547
+ ### `memorix_audit_project`
548
+
549
+ Scan the current project for observations that may have been written to the
550
+ wrong project bucket — entities that appear exclusively in a different
551
+ project. Read-only; nothing is changed.
552
+
553
+ Available in the `full` tool profile (`MEMORIX_MODE=full` or `--mode full`).
554
+
555
+ Important inputs:
556
+
557
+ - optional `threshold` — minimum occurrences in another project to flag
558
+ (default: 2)
559
+
560
+ Archive confirmed mis-attributed observations with `memorix_resolve`.
561
+
543
562
  ---
544
563
 
545
564
  ## 7. Skills and Promotion Tools
@@ -294,10 +294,24 @@ Server and dashboard behavior.
294
294
 
295
295
  Common keys:
296
296
 
297
- - `transport = "stdio"`
298
- - `port = 3211`
297
+ - `transport = "stdio"` — advisory. An explicit `memorix serve-http` still
298
+ runs on HTTP and prints a notice; this key documents the intended mode.
299
+ - `port = 3211` — default listener port for `memorix serve-http` when
300
+ `--port` is not given.
299
301
  - `dashboard = true`
300
- - `dashboard_port = 3210`
302
+ - `dashboard_port = 3210` — default port for `memorix dashboard` when
303
+ `--port` is not given.
304
+
305
+ ### Reserved sections (parsed, not yet enforced)
306
+
307
+ These sections exist so future releases do not need a config format change.
308
+ Today no runtime behavior reads them; set them only as forward-looking notes.
309
+
310
+ - `[team]` (`enabled`, `workspace_collection`) — coordination defaults.
311
+ - `[hooks]` (`native_memcode`, `external_agents`) — hook bootstrap policy.
312
+ - The former `mcpServers` key in `memorix.yml` was removed: no code ever
313
+ consumed it, and Memorix is not an MCP aggregator. Unknown keys are
314
+ ignored, so old files keep loading.
301
315
 
302
316
  ---
303
317
 
package/docs/SETUP.md CHANGED
@@ -17,9 +17,9 @@ Common runtime entry points:
17
17
 
18
18
  | Entry | Use it for |
19
19
  | --- | --- |
20
- | `memorix setup --agent <agent> --global` | one-command user-level agent integration |
20
+ | `memorix setup --agent <agent> --global` | one-command user-level agent integration; installs the stdio server with `--mode lite` so every tool taught in the generated guidance is actually reachable |
21
21
  | `memorix` CLI commands | direct workflows: setup, search/store, Git Memory, import/export, dashboard, diagnostics, orchestration, and automation |
22
- | `memorix serve` | stdio MCP server for IDEs and coding agents; defaults to a compact `micro` tool profile |
22
+ | `memorix serve` | stdio MCP server for IDEs and coding agents; defaults to a compact `micro` tool profile when launched manually |
23
23
  | `memorix background start` | long-lived HTTP MCP service plus dashboard |
24
24
  | `memorix serve-http --port 3211` | foreground HTTP MCP for debugging or supervised launches |
25
25
  | `memorix` / `memcode` | bundled terminal agent that uses the same Memorix memory pool |
@@ -151,7 +151,9 @@ memorix serve
151
151
 
152
152
  Use this when your agent only needs a raw local stdio MCP process or you are debugging a manual config. The agent starts `memorix serve` and communicates with it over stdio.
153
153
 
154
- The stdio server defaults to `--mode micro`, which exposes 9 high-signal tools so agent context is not crowded by rarely used schemas. Use `memorix serve --mode lite` for the extended solo memory surface, `--mode team` for coordination tools, or `--mode full` for advanced and MCP Memory compatibility tools.
154
+ A bare `memorix serve` defaults to `--mode micro`, which exposes 9 high-signal tools so agent context is not crowded by rarely used schemas. Use `memorix serve --mode lite` for the extended solo memory surface, `--mode team` for coordination tools, or `--mode full` for advanced and MCP Memory compatibility tools.
155
+
156
+ `memorix setup` writes `--mode lite` into the installed MCP config: the generated guidance teaches the extended surface (sessions, timeline, reasoning, retention), so the installed server must actually expose it. Override with `MEMORIX_MODE` if you prefer a different profile.
155
157
 
156
158
  Generic stdio MCP config:
157
159
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memorix",
3
3
  "mcpName": "io.github.AVIDS2/memorix",
4
- "version": "1.4.5",
4
+ "version": "1.4.6",
5
5
  "description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -3,7 +3,9 @@
3
3
  "memorix": {
4
4
  "command": "memorix",
5
5
  "args": [
6
- "serve"
6
+ "serve",
7
+ "--mode",
8
+ "lite"
7
9
  ]
8
10
  }
9
11
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memorix",
4
- "version": "1.4.5",
4
+ "version": "1.4.6",
5
5
  "description": "Shared workspace memory for Claude Code and other AI coding agents.",
6
6
  "author": {
7
7
  "name": "AVIDS2",
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "memorix": {
4
4
  "command": "memorix",
5
- "args": ["serve"],
5
+ "args": ["serve", "--mode", "lite"],
6
6
  "alwaysLoad": true
7
7
  }
8
8
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "description": "Local-first project memory for CodeBuddy Code and other coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -3,7 +3,7 @@
3
3
  "memorix": {
4
4
  "type": "stdio",
5
5
  "command": "memorix",
6
- "args": ["serve"]
6
+ "args": ["serve", "--mode", "lite"]
7
7
  }
8
8
  }
9
9
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "description": "Shared workspace memory for Codex and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "memorix": {
4
4
  "command": "memorix",
5
- "args": ["serve"]
5
+ "args": ["serve", "--mode", "lite"]
6
6
  }
7
7
  }
8
8
  }
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "memorix": {
4
4
  "command": "memorix",
5
- "args": ["serve"]
5
+ "args": ["serve", "--mode", "lite"]
6
6
  }
7
7
  }
8
8
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "description": "Shared local workspace memory for GitHub Copilot CLI and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "mcpServers": {
5
5
  "memorix": {
6
6
  "command": "memorix",
7
7
  "args": [
8
- "serve"
8
+ "serve",
9
+ "--mode",
10
+ "lite"
9
11
  ]
10
12
  }
11
13
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-omp-package",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
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.4.5",
3
+ "version": "1.4.6",
4
4
  "description": "Shared workspace memory for OpenClaw and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -3,7 +3,9 @@
3
3
  "memorix": {
4
4
  "command": "memorix",
5
5
  "args": [
6
- "serve"
6
+ "serve",
7
+ "--mode",
8
+ "lite"
7
9
  ]
8
10
  }
9
11
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-pi-package",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "description": "Memorix shared workspace memory package for Pi coding agent.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/server.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "url": "https://github.com/AVIDS2/memorix",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.4.5",
10
+ "version": "1.4.6",
11
11
  "websiteUrl": "https://github.com/AVIDS2/memorix#readme",
12
12
  "icons": [
13
13
  {
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "memorix",
25
- "version": "1.4.5",
25
+ "version": "1.4.6",
26
26
  "runtimeHint": "npx",
27
27
  "packageArguments": [
28
28
  {
@@ -22,8 +22,8 @@ export default defineCommand({
22
22
  args: {
23
23
  port: {
24
24
  type: 'string',
25
- description: 'Port to run the dashboard on (default: 3210)',
26
- default: '3210',
25
+ description: 'Port to run the dashboard on (default: [server].dashboardPort or 3210)',
26
+ required: false,
27
27
  },
28
28
  },
29
29
  run: async ({ args }) => {
@@ -31,13 +31,24 @@ export default defineCommand({
31
31
  const { getProjectDataDir } = await import('../../store/persistence.js');
32
32
  const { startDashboard } = await import('../../dashboard/server.js');
33
33
 
34
+ // The [server].dashboardPort config is a real default source; the
35
+ // explicit --port flag always wins.
36
+ let configuredDashboardPort: number | undefined;
37
+ try {
38
+ const { getServerConfig } = await import('../../config.js');
39
+ const serverConfig = getServerConfig();
40
+ configuredDashboardPort = typeof serverConfig.dashboardPort === 'number'
41
+ ? serverConfig.dashboardPort
42
+ : undefined;
43
+ } catch { /* config files are optional */ }
44
+
34
45
  const project = detectProject();
35
46
  if (!project) {
36
47
  console.error('Memorix requires a git repo to establish project identity. Run `git init` in this workspace first.');
37
48
  process.exit(1);
38
49
  }
39
50
  const dataDir = await getProjectDataDir(project.id);
40
- const port = parseTcpPortOrReport(args.port as string | undefined, 3210);
51
+ const port = parseTcpPortOrReport(args.port as string | undefined, configuredDashboardPort ?? 3210);
41
52
  if (port === undefined) {
42
53
  process.exitCode = 1;
43
54
  return;
@@ -7,6 +7,37 @@
7
7
 
8
8
  import { defineCommand } from 'citty';
9
9
  import * as p from '@clack/prompts';
10
+ import type { CommitInfo } from '../../git/extractor.js';
11
+
12
+ export interface GitIngestPolicyInput {
13
+ ingestOnCommit?: boolean;
14
+ maxDiffSize?: number;
15
+ }
16
+
17
+ /**
18
+ * The documented git policy settings are real gates, not display-only facts:
19
+ * `ingest_on_commit = false` disables post-commit ingestion, and
20
+ * `max_diff_size` caps how much diff content is captured into the narrative.
21
+ */
22
+ export function applyGitIngestPolicy(
23
+ commit: CommitInfo,
24
+ policy: GitIngestPolicyInput,
25
+ ): { skip: boolean; reason?: string; diffSummary: string } {
26
+ if (policy.ingestOnCommit === false) {
27
+ return {
28
+ skip: true,
29
+ reason: 'ingest_on_commit is disabled',
30
+ diffSummary: commit.diffSummary,
31
+ };
32
+ }
33
+ const cap = policy.maxDiffSize;
34
+ return {
35
+ skip: false,
36
+ diffSummary: typeof cap === 'number' && Number.isFinite(cap)
37
+ ? commit.diffSummary.slice(0, Math.max(0, Math.floor(cap)))
38
+ : commit.diffSummary,
39
+ };
40
+ }
10
41
 
11
42
  export default defineCommand({
12
43
  meta: {
@@ -42,13 +73,26 @@ export default defineCommand({
42
73
  if (!auto) p.intro(`Ingest commit: ${ref}`);
43
74
 
44
75
  try {
76
+ const { getGitConfig } = await import('../../config.js');
77
+ const gitCfg = getGitConfig();
45
78
  const { getCommitInfo, ingestCommit } = await import('../../git/extractor.js');
46
- const commit = getCommitInfo(cwd, ref);
79
+ const commit = getCommitInfo(cwd, ref, gitCfg.maxDiffSize ?? 500);
80
+
81
+ // The documented ingest_on_commit gate: post-commit execution stops
82
+ // here when disabled. An explicit manual run always proceeds.
83
+ const policy = applyGitIngestPolicy(commit, {
84
+ ingestOnCommit: gitCfg.ingestOnCommit,
85
+ maxDiffSize: gitCfg.maxDiffSize,
86
+ });
87
+ if (policy.skip && auto) {
88
+ console.error(`[memorix] Skipped ${commit.shortHash}: ${policy.reason}`);
89
+ process.exit(0);
90
+ return;
91
+ }
92
+ commit.diffSummary = policy.diffSummary;
47
93
 
48
94
  // Noise filter: skip low-value commits (typo, format, lockfile, merge, etc.)
49
95
  const { shouldFilterCommit } = await import('../../git/noise-filter.js');
50
- const { getGitConfig } = await import('../../config.js');
51
- const gitCfg = getGitConfig();
52
96
  const filterResult = shouldFilterCommit(commit, {
53
97
  skipMergeCommits: gitCfg.skipMergeCommits,
54
98
  excludePatterns: gitCfg.excludePatterns,
@@ -467,6 +467,7 @@ export default defineCommand({
467
467
  createManualLongTermMemory,
468
468
  getLongTermMemoryDetail,
469
469
  listLongTermMemories,
470
+ maybeAutoQualifyLongTermMemory,
470
471
  promoteObservationToLongTermMemory,
471
472
  qualifyLongTermMemory,
472
473
  supersedeLongTermMemory,
@@ -545,9 +546,15 @@ export default defineCommand({
545
546
  portability: portability(),
546
547
  reader: longTermReader,
547
548
  });
549
+ // Operator-written records carry their own source evidence.
550
+ const qualified = await maybeAutoQualifyLongTermMemory({
551
+ dataDir,
552
+ id: result.memory.id,
553
+ source: 'manual',
554
+ });
548
555
  emitResult(
549
- { project, ...result },
550
- `Created long-term memory candidate ${result.memory.id}. Qualify it after checking its evidence.`,
556
+ { project, ...result, memory: qualified ?? result.memory },
557
+ `Created long-term memory ${result.memory.id}.`,
551
558
  asJson,
552
559
  );
553
560
  return;
@@ -576,9 +583,15 @@ export default defineCommand({
576
583
  applicability: asStringArg(args.applicability),
577
584
  reader: longTermReader,
578
585
  });
586
+ // An explicit promote request carries its own source evidence.
587
+ const qualified = await maybeAutoQualifyLongTermMemory({
588
+ dataDir,
589
+ id: result.memory.id,
590
+ sourceDetail: 'explicit',
591
+ });
579
592
  emitResult(
580
- { project, sourceObservationId: observation.id, ...result },
581
- `Promoted observation #${observation.id} to long-term memory candidate ${result.memory.id}.`,
593
+ { project, sourceObservationId: observation.id, ...result, memory: qualified ?? result.memory },
594
+ `Promoted observation #${observation.id} to long-term memory ${result.memory.id}.`,
582
595
  asJson,
583
596
  );
584
597
  return;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * CLI Command: memorix purge
3
+ *
4
+ * Retire memories from retrieval. Project-scoped by default; --all covers
5
+ * every project. Observations and long-term records are archived (never
6
+ * hard-deleted) so the audit trail survives. Refuses to run outside a
7
+ * terminal without --yes.
8
+ */
9
+
10
+ import { defineCommand } from 'citty';
11
+ import { detectProject } from '../../project/detector.js';
12
+ import { getProjectDataDir } from '../../store/persistence.js';
13
+
14
+ export default defineCommand({
15
+ meta: {
16
+ name: 'purge',
17
+ description: 'Retire memories from retrieval (current project by default, --all for every project)',
18
+ },
19
+ args: {
20
+ all: {
21
+ type: 'boolean',
22
+ description: 'Purge every project instead of the current one',
23
+ default: false,
24
+ },
25
+ yes: {
26
+ type: 'boolean',
27
+ description: 'Skip the interactive confirmation',
28
+ default: false,
29
+ },
30
+ json: {
31
+ type: 'boolean',
32
+ description: 'Emit a machine-readable result',
33
+ default: false,
34
+ },
35
+ },
36
+ async run({ args }) {
37
+ const purgeAll = Boolean(args.all);
38
+ const yes = Boolean(args.yes);
39
+ const asJson = Boolean(args.json);
40
+
41
+ // Confirmation gate first: a destructive wipe never proceeds in a
42
+ // non-interactive shell without an explicit --yes.
43
+ if (!yes && !process.stdin.isTTY) {
44
+ console.error('[ERROR] Non-interactive environment. Add --yes to confirm.');
45
+ console.error('[ERROR] Refusing to purge memories without confirmation.');
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ if (!yes) {
50
+ const readline = await import('node:readline');
51
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
52
+ const question = purgeAll
53
+ ? 'Retire ALL memories from every project? (y/N) '
54
+ : 'Retire all memories for this project? (y/N) ';
55
+ const answer = await new Promise<string>((resolve) => rl.question(question, resolve));
56
+ rl.close();
57
+ if (answer.trim().toLowerCase() !== 'y') {
58
+ console.log('Cancelled.');
59
+ return;
60
+ }
61
+ }
62
+
63
+ let projectId: string | null = null;
64
+ if (!purgeAll) {
65
+ const project = detectProject();
66
+ if (!project) {
67
+ console.error('[ERROR] No .git found — run from a project directory or pass --all.');
68
+ process.exitCode = 1;
69
+ return;
70
+ }
71
+ projectId = project.id;
72
+ }
73
+
74
+ const dataDir = await getProjectDataDir(projectId ?? '');
75
+ const { initObservationStore } = await import('../../store/obs-store.js');
76
+ const { getAllObservations, resolveObservations } = await import('../../memory/observations.js');
77
+ const { withFreshIndex } = await import('../../memory/freshness.js');
78
+ await initObservationStore(dataDir);
79
+
80
+ const observations = await withFreshIndex(() => getAllObservations());
81
+ const target = purgeAll ? observations : observations.filter((o) => o.projectId === projectId);
82
+ const ids = target
83
+ .map((o) => o.id)
84
+ .filter((id): id is number => typeof id === 'number');
85
+ const archived = ids.length > 0
86
+ ? (await resolveObservations(ids, 'archived')).resolved.length
87
+ : 0;
88
+
89
+ const { LongTermMemoryStore } = await import('../../memory/long-term-store.js');
90
+ const { archiveLongTermMemory } = await import('../../memory/long-term.js');
91
+ const longTermStore = new LongTermMemoryStore();
92
+ await longTermStore.init(dataDir);
93
+ let longTermArchived = 0;
94
+ for (const memory of longTermStore.list()) {
95
+ if (memory.state === 'archived' || memory.state === 'superseded') continue;
96
+ if (!purgeAll && memory.originProjectId !== projectId) continue;
97
+ try {
98
+ await archiveLongTermMemory({
99
+ dataDir,
100
+ id: memory.id,
101
+ reason: purgeAll ? 'purge --all' : 'purge (project) ' + (projectId ?? ''),
102
+ });
103
+ longTermArchived += 1;
104
+ } catch { /* already inactive */ }
105
+ }
106
+
107
+ const result = {
108
+ scope: purgeAll ? 'all' : projectId,
109
+ observationsArchived: archived,
110
+ longTermArchived,
111
+ };
112
+ if (asJson) {
113
+ console.log(JSON.stringify(result));
114
+ } else {
115
+ console.log(
116
+ `[OK] Purged ${purgeAll ? 'all projects' : result.scope}: archived ${archived} observations and ${longTermArchived} long-term memories.`,
117
+ );
118
+ }
119
+ },
120
+ });