memorix 1.1.7 → 1.1.9
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 +22 -0
- package/CLAUDE.md +6 -1
- package/README.md +21 -0
- package/README.zh-CN.md +21 -0
- package/TEAM.md +86 -86
- package/dist/cli/index.js +852 -214
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/index.html +201 -201
- package/dist/dashboard/static/style.css +3584 -3584
- package/dist/index.js +129 -62
- package/dist/index.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +22 -0
- package/dist/memcode-runtime/package.json +4 -4
- package/dist/sdk.js +129 -62
- package/dist/sdk.js.map +1 -1
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +18 -0
- package/docs/API_REFERENCE.md +2 -0
- package/docs/CONFIGURATION.md +18 -0
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/SETUP.md +10 -0
- package/docs/dev-log/progress.txt +23 -30
- package/package.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/commands/agent-integrations.ts +623 -0
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/cleanup.ts +255 -255
- package/src/cli/commands/codegraph.ts +4 -0
- package/src/cli/commands/config-get.ts +9 -2
- package/src/cli/commands/doctor.ts +26 -0
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +66 -66
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +73 -73
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +96 -96
- package/src/cli/commands/message.ts +121 -121
- package/src/cli/commands/poll.ts +70 -70
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +132 -132
- package/src/cli/commands/repair.ts +60 -0
- package/src/cli/commands/retention.ts +108 -108
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/setup.ts +3 -3
- package/src/cli/commands/skills.ts +123 -123
- package/src/cli/commands/task.ts +192 -192
- package/src/cli/commands/transfer.ts +73 -73
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Panels.tsx +632 -632
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/chat-service.ts +742 -742
- package/src/cli/tui/data.ts +547 -547
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/theme.ts +178 -178
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/auto-context.ts +6 -0
- package/src/codegraph/context-pack.ts +7 -6
- package/src/codegraph/exclude.ts +47 -0
- package/src/codegraph/lite-provider.ts +5 -24
- package/src/codegraph/project-context.ts +13 -15
- package/src/compact/token-budget.ts +74 -74
- package/src/config/behavior.ts +59 -59
- package/src/config/resolved-config.ts +6 -0
- package/src/config/toml-loader.ts +4 -0
- package/src/config/yaml-loader.ts +7 -0
- package/src/dashboard/project-classification.ts +64 -64
- package/src/dashboard/static/index.html +201 -201
- package/src/dashboard/static/style.css +3584 -3584
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/git/noise-filter.ts +210 -210
- package/src/hooks/installers/index.ts +4 -4
- package/src/hooks/official-skills.ts +1 -1
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/rules/memorix-agent-rules.md +2 -2
- package/src/hooks/significance-filter.ts +250 -250
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/auto-relations.ts +107 -107
- package/src/memory/consolidation.ts +302 -302
- package/src/memory/disclosure-policy.ts +141 -141
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph.ts +197 -197
- package/src/memory/refs.ts +94 -94
- package/src/memory/retention.ts +433 -433
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +523 -523
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +380 -380
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/verify-gate.ts +219 -219
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/sdk.ts +327 -327
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/server.ts +3 -0
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/file-lock.ts +100 -100
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/obs-store.ts +255 -255
- package/src/store/orama-store.ts +15 -8
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/session-store.ts +259 -259
- package/src/store/sqlite-store.ts +339 -339
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +167 -167
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/wiki/generator.ts +237 -237
- package/src/wiki/knowledge-graph.ts +334 -334
- package/src/wiki/types.ts +85 -85
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
|
@@ -1,255 +1,255 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Command: memorix cleanup
|
|
3
|
-
*
|
|
4
|
-
* Identifies and removes low-quality auto-generated observations.
|
|
5
|
-
* Also detects and archives demo/test/system-self pollution.
|
|
6
|
-
* Inspired by Mem0's memory consolidation and Graphiti's temporal pruning.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* memorix cleanup — Interactive: preview & confirm deletion of low-quality
|
|
10
|
-
* memorix cleanup --noise — Also archive demo/test/Memorix-self pollution
|
|
11
|
-
* memorix cleanup --dry — Preview only, no changes
|
|
12
|
-
* memorix cleanup --force — Apply without confirmation
|
|
13
|
-
* memorix cleanup --project X — Target a specific projectId
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { defineCommand } from 'citty';
|
|
17
|
-
import { detectProject } from '../../project/detector.js';
|
|
18
|
-
import { getProjectDataDir } from '../../store/persistence.js';
|
|
19
|
-
import { getObservationStore, initObservationStore } from '../../store/obs-store.js';
|
|
20
|
-
|
|
21
|
-
/** Patterns that indicate auto-generated, low-value observations */
|
|
22
|
-
const LOW_QUALITY_PATTERNS = [
|
|
23
|
-
/^Session activity/i,
|
|
24
|
-
/^Updated \S+\.\w+$/i,
|
|
25
|
-
/^Created \S+\.\w+$/i,
|
|
26
|
-
/^Deleted \S+\.\w+$/i,
|
|
27
|
-
/^Modified \S+\.\w+$/i,
|
|
28
|
-
/^Ran command:/i,
|
|
29
|
-
/^Read file:/i,
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
/** Patterns for demo/test noise — matches session.ts NOISE_PATTERNS */
|
|
33
|
-
const NOISE_PATTERNS = [
|
|
34
|
-
/\[测试\]/i, /\[test\]/i, /验证/i, /兼容/i, /\bcompat(?:ibility)?\b/i,
|
|
35
|
-
/\bdemo\b/i, /展示/i, /全能力/i, /handoff/i, /交接/i,
|
|
36
|
-
/for_memmcp_test/i, /\bbenchmark\b/i, /\bsandbox\b/i, /\bplayground\b/i,
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
/** Patterns for Memorix system self-reference — should not pollute unrelated projects */
|
|
40
|
-
const SYSTEM_SELF_PATTERNS = [
|
|
41
|
-
/memorix.demo/i, /memorix.*全能力/i, /memorix.*工具.*能力/i,
|
|
42
|
-
/memorix.*runtime.*mode/i, /memorix.*运行模式/i, /memorix.*control.plane/i,
|
|
43
|
-
/session.*inject(?:ion)?/i, /注入.*逻辑/i,
|
|
44
|
-
/\b22\s*(?:个|tools?).*(?:工具|能力|capabilit)/i,
|
|
45
|
-
/memorix.*(?:v\d|版本|version)/i, /memorix.*(?:兼容|compat)/i,
|
|
46
|
-
/memorix.*(?:测试|test)/i, /memmcp/i,
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
/** Check if an observation title matches low-quality patterns */
|
|
50
|
-
function isLowQuality(title: string): boolean {
|
|
51
|
-
return LOW_QUALITY_PATTERNS.some(p => p.test(title.trim()));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Check if observation text matches noise/demo/test/system-self patterns */
|
|
55
|
-
function isNoisePollution(obs: { title?: string; narrative?: string; entityName?: string; facts?: string[]; concepts?: string[] }): { isNoise: boolean; reason: string } {
|
|
56
|
-
const text = [obs.title, obs.narrative, obs.entityName, ...(obs.facts ?? []), ...(obs.concepts ?? [])]
|
|
57
|
-
.filter(Boolean).join('\n');
|
|
58
|
-
for (const p of SYSTEM_SELF_PATTERNS) {
|
|
59
|
-
if (p.test(text)) return { isNoise: true, reason: 'system-self' };
|
|
60
|
-
}
|
|
61
|
-
for (const p of NOISE_PATTERNS) {
|
|
62
|
-
if (p.test(text)) return { isNoise: true, reason: 'demo/test/noise' };
|
|
63
|
-
}
|
|
64
|
-
return { isNoise: false, reason: '' };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export default defineCommand({
|
|
68
|
-
meta: {
|
|
69
|
-
name: 'cleanup',
|
|
70
|
-
description: 'Remove low-quality auto-generated observations',
|
|
71
|
-
},
|
|
72
|
-
args: {
|
|
73
|
-
dry: {
|
|
74
|
-
type: 'boolean',
|
|
75
|
-
description: 'Preview only — do not delete anything',
|
|
76
|
-
default: false,
|
|
77
|
-
},
|
|
78
|
-
force: {
|
|
79
|
-
type: 'boolean',
|
|
80
|
-
description: 'Delete without confirmation',
|
|
81
|
-
default: false,
|
|
82
|
-
},
|
|
83
|
-
noise: {
|
|
84
|
-
type: 'boolean',
|
|
85
|
-
description: 'Also archive demo/test/system-self pollution observations',
|
|
86
|
-
default: false,
|
|
87
|
-
},
|
|
88
|
-
project: {
|
|
89
|
-
type: 'string',
|
|
90
|
-
description: 'Target a specific projectId (e.g., AVIDS2/blog)',
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
async run({ args }) {
|
|
94
|
-
let projectId: string;
|
|
95
|
-
let projectName: string;
|
|
96
|
-
|
|
97
|
-
if (args.project) {
|
|
98
|
-
projectId = args.project;
|
|
99
|
-
projectName = args.project.split('/').pop() || args.project;
|
|
100
|
-
} else {
|
|
101
|
-
const project = detectProject();
|
|
102
|
-
if (!project) {
|
|
103
|
-
console.error('[ERROR] No .git found — not a project directory.');
|
|
104
|
-
console.error('Use --project <id> to target a specific project, or run from a git repo.');
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
projectId = project.id;
|
|
108
|
-
projectName = project.name;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
console.log(`\nProject: ${projectName} (${projectId})\n`);
|
|
112
|
-
|
|
113
|
-
const dataDir = await getProjectDataDir(projectId);
|
|
114
|
-
await initObservationStore(dataDir);
|
|
115
|
-
const allObs = await getObservationStore().loadAll() as Array<{
|
|
116
|
-
id?: number;
|
|
117
|
-
type?: string;
|
|
118
|
-
title?: string;
|
|
119
|
-
narrative?: string;
|
|
120
|
-
entityName?: string;
|
|
121
|
-
facts?: string[];
|
|
122
|
-
concepts?: string[];
|
|
123
|
-
timestamp?: string;
|
|
124
|
-
projectId?: string;
|
|
125
|
-
status?: string;
|
|
126
|
-
}>;
|
|
127
|
-
|
|
128
|
-
if (allObs.length === 0) {
|
|
129
|
-
console.log('[OK] No observations found - nothing to clean up.');
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Filter to project-scoped observations only
|
|
134
|
-
const projectObs = allObs.filter(o => o.projectId === projectId && (o.status ?? 'active') === 'active');
|
|
135
|
-
|
|
136
|
-
// Categorize: low-quality
|
|
137
|
-
const lowQuality = projectObs.filter(o => isLowQuality(o.title ?? ''));
|
|
138
|
-
const highQuality = projectObs.filter(o => !isLowQuality(o.title ?? ''));
|
|
139
|
-
|
|
140
|
-
// Find duplicates (same title + type + entity)
|
|
141
|
-
const seen = new Set<string>();
|
|
142
|
-
const duplicates: typeof projectObs = [];
|
|
143
|
-
const unique: typeof projectObs = [];
|
|
144
|
-
for (const obs of highQuality) {
|
|
145
|
-
const key = `${obs.type}|${obs.title}|${obs.entityName}`;
|
|
146
|
-
if (seen.has(key)) {
|
|
147
|
-
duplicates.push(obs);
|
|
148
|
-
} else {
|
|
149
|
-
seen.add(key);
|
|
150
|
-
unique.push(obs);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Find noise pollution (demo/test/system-self)
|
|
155
|
-
const noiseHits: Array<{ obs: typeof projectObs[0]; reason: string }> = [];
|
|
156
|
-
if (args.noise) {
|
|
157
|
-
for (const obs of projectObs) {
|
|
158
|
-
if (lowQuality.includes(obs) || duplicates.includes(obs)) continue;
|
|
159
|
-
const { isNoise, reason } = isNoisePollution(obs);
|
|
160
|
-
if (isNoise) noiseHits.push({ obs, reason });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const toRemove = [...lowQuality, ...duplicates];
|
|
165
|
-
const toArchive = noiseHits.map(h => h.obs);
|
|
166
|
-
|
|
167
|
-
console.log(`Analysis (active observations for ${projectId}):`);
|
|
168
|
-
console.log(` Total active: ${projectObs.length}`);
|
|
169
|
-
console.log(` High quality: ${unique.length - toArchive.length}`);
|
|
170
|
-
console.log(` Low quality: ${lowQuality.length}`);
|
|
171
|
-
console.log(` Duplicates: ${duplicates.length}`);
|
|
172
|
-
if (args.noise) {
|
|
173
|
-
console.log(` Noise pollution: ${toArchive.length}`);
|
|
174
|
-
}
|
|
175
|
-
console.log(` To delete: ${toRemove.length}`);
|
|
176
|
-
if (args.noise) {
|
|
177
|
-
console.log(` To archive: ${toArchive.length}`);
|
|
178
|
-
}
|
|
179
|
-
console.log();
|
|
180
|
-
|
|
181
|
-
if (toRemove.length === 0 && toArchive.length === 0) {
|
|
182
|
-
console.log('[OK] All observations are clean — nothing to clean up!');
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Preview deletions
|
|
187
|
-
if (toRemove.length > 0) {
|
|
188
|
-
console.log('Items to DELETE:');
|
|
189
|
-
toRemove.slice(0, 10).forEach(o => {
|
|
190
|
-
const tag = isLowQuality(o.title ?? '') ? '(low-quality)' : '(duplicate)';
|
|
191
|
-
console.log(` ${tag} #${o.id ?? '?'} "${o.title}" [${o.type}]`);
|
|
192
|
-
});
|
|
193
|
-
if (toRemove.length > 10) {
|
|
194
|
-
console.log(` ... and ${toRemove.length - 10} more`);
|
|
195
|
-
}
|
|
196
|
-
console.log();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Preview noise archival
|
|
200
|
-
if (toArchive.length > 0) {
|
|
201
|
-
console.log('Items to ARCHIVE (noise pollution):');
|
|
202
|
-
noiseHits.slice(0, 15).forEach(({ obs, reason }) => {
|
|
203
|
-
console.log(` (${reason}) #${obs.id ?? '?'} "${obs.title}" [${obs.type}] entity=${obs.entityName}`);
|
|
204
|
-
});
|
|
205
|
-
if (toArchive.length > 15) {
|
|
206
|
-
console.log(` ... and ${toArchive.length - 15} more`);
|
|
207
|
-
}
|
|
208
|
-
console.log();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (args.dry) {
|
|
212
|
-
console.log('[DRY RUN] No changes made.');
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (!args.force) {
|
|
217
|
-
const readline = await import('node:readline');
|
|
218
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
219
|
-
const desc = [
|
|
220
|
-
toRemove.length > 0 ? `delete ${toRemove.length}` : '',
|
|
221
|
-
toArchive.length > 0 ? `archive ${toArchive.length}` : '',
|
|
222
|
-
].filter(Boolean).join(' and ');
|
|
223
|
-
const answer = await new Promise<string>(resolve => {
|
|
224
|
-
rl.question(`Proceed to ${desc} observations? (y/N) `, resolve);
|
|
225
|
-
});
|
|
226
|
-
rl.close();
|
|
227
|
-
|
|
228
|
-
if (answer.trim().toLowerCase() !== 'y') {
|
|
229
|
-
console.log('Cancelled.');
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Archive noise observations (set status to 'archived' instead of deleting)
|
|
235
|
-
const archiveIds = new Set(toArchive.map(o => o.id));
|
|
236
|
-
let archivedCount = 0;
|
|
237
|
-
for (const obs of allObs) {
|
|
238
|
-
if (archiveIds.has(obs.id)) {
|
|
239
|
-
(obs as any).status = 'archived';
|
|
240
|
-
archivedCount++;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Delete low-quality/duplicate observations
|
|
245
|
-
const removeIds = new Set(toRemove.map(o => JSON.stringify(o)));
|
|
246
|
-
const remaining = allObs.filter(o => !removeIds.has(JSON.stringify(o)));
|
|
247
|
-
|
|
248
|
-
await getObservationStore().bulkReplace(remaining as any);
|
|
249
|
-
|
|
250
|
-
const parts: string[] = [];
|
|
251
|
-
if (toRemove.length > 0) parts.push(`deleted ${toRemove.length}`);
|
|
252
|
-
if (archivedCount > 0) parts.push(`archived ${archivedCount}`);
|
|
253
|
-
console.log(`[OK] ${parts.join(', ')}. ${remaining.length} observations remaining.`);
|
|
254
|
-
},
|
|
255
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* CLI Command: memorix cleanup
|
|
3
|
+
*
|
|
4
|
+
* Identifies and removes low-quality auto-generated observations.
|
|
5
|
+
* Also detects and archives demo/test/system-self pollution.
|
|
6
|
+
* Inspired by Mem0's memory consolidation and Graphiti's temporal pruning.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* memorix cleanup — Interactive: preview & confirm deletion of low-quality
|
|
10
|
+
* memorix cleanup --noise — Also archive demo/test/Memorix-self pollution
|
|
11
|
+
* memorix cleanup --dry — Preview only, no changes
|
|
12
|
+
* memorix cleanup --force — Apply without confirmation
|
|
13
|
+
* memorix cleanup --project X — Target a specific projectId
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { defineCommand } from 'citty';
|
|
17
|
+
import { detectProject } from '../../project/detector.js';
|
|
18
|
+
import { getProjectDataDir } from '../../store/persistence.js';
|
|
19
|
+
import { getObservationStore, initObservationStore } from '../../store/obs-store.js';
|
|
20
|
+
|
|
21
|
+
/** Patterns that indicate auto-generated, low-value observations */
|
|
22
|
+
const LOW_QUALITY_PATTERNS = [
|
|
23
|
+
/^Session activity/i,
|
|
24
|
+
/^Updated \S+\.\w+$/i,
|
|
25
|
+
/^Created \S+\.\w+$/i,
|
|
26
|
+
/^Deleted \S+\.\w+$/i,
|
|
27
|
+
/^Modified \S+\.\w+$/i,
|
|
28
|
+
/^Ran command:/i,
|
|
29
|
+
/^Read file:/i,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/** Patterns for demo/test noise — matches session.ts NOISE_PATTERNS */
|
|
33
|
+
const NOISE_PATTERNS = [
|
|
34
|
+
/\[测试\]/i, /\[test\]/i, /验证/i, /兼容/i, /\bcompat(?:ibility)?\b/i,
|
|
35
|
+
/\bdemo\b/i, /展示/i, /全能力/i, /handoff/i, /交接/i,
|
|
36
|
+
/for_memmcp_test/i, /\bbenchmark\b/i, /\bsandbox\b/i, /\bplayground\b/i,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/** Patterns for Memorix system self-reference — should not pollute unrelated projects */
|
|
40
|
+
const SYSTEM_SELF_PATTERNS = [
|
|
41
|
+
/memorix.demo/i, /memorix.*全能力/i, /memorix.*工具.*能力/i,
|
|
42
|
+
/memorix.*runtime.*mode/i, /memorix.*运行模式/i, /memorix.*control.plane/i,
|
|
43
|
+
/session.*inject(?:ion)?/i, /注入.*逻辑/i,
|
|
44
|
+
/\b22\s*(?:个|tools?).*(?:工具|能力|capabilit)/i,
|
|
45
|
+
/memorix.*(?:v\d|版本|version)/i, /memorix.*(?:兼容|compat)/i,
|
|
46
|
+
/memorix.*(?:测试|test)/i, /memmcp/i,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/** Check if an observation title matches low-quality patterns */
|
|
50
|
+
function isLowQuality(title: string): boolean {
|
|
51
|
+
return LOW_QUALITY_PATTERNS.some(p => p.test(title.trim()));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Check if observation text matches noise/demo/test/system-self patterns */
|
|
55
|
+
function isNoisePollution(obs: { title?: string; narrative?: string; entityName?: string; facts?: string[]; concepts?: string[] }): { isNoise: boolean; reason: string } {
|
|
56
|
+
const text = [obs.title, obs.narrative, obs.entityName, ...(obs.facts ?? []), ...(obs.concepts ?? [])]
|
|
57
|
+
.filter(Boolean).join('\n');
|
|
58
|
+
for (const p of SYSTEM_SELF_PATTERNS) {
|
|
59
|
+
if (p.test(text)) return { isNoise: true, reason: 'system-self' };
|
|
60
|
+
}
|
|
61
|
+
for (const p of NOISE_PATTERNS) {
|
|
62
|
+
if (p.test(text)) return { isNoise: true, reason: 'demo/test/noise' };
|
|
63
|
+
}
|
|
64
|
+
return { isNoise: false, reason: '' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default defineCommand({
|
|
68
|
+
meta: {
|
|
69
|
+
name: 'cleanup',
|
|
70
|
+
description: 'Remove low-quality auto-generated observations',
|
|
71
|
+
},
|
|
72
|
+
args: {
|
|
73
|
+
dry: {
|
|
74
|
+
type: 'boolean',
|
|
75
|
+
description: 'Preview only — do not delete anything',
|
|
76
|
+
default: false,
|
|
77
|
+
},
|
|
78
|
+
force: {
|
|
79
|
+
type: 'boolean',
|
|
80
|
+
description: 'Delete without confirmation',
|
|
81
|
+
default: false,
|
|
82
|
+
},
|
|
83
|
+
noise: {
|
|
84
|
+
type: 'boolean',
|
|
85
|
+
description: 'Also archive demo/test/system-self pollution observations',
|
|
86
|
+
default: false,
|
|
87
|
+
},
|
|
88
|
+
project: {
|
|
89
|
+
type: 'string',
|
|
90
|
+
description: 'Target a specific projectId (e.g., AVIDS2/blog)',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
async run({ args }) {
|
|
94
|
+
let projectId: string;
|
|
95
|
+
let projectName: string;
|
|
96
|
+
|
|
97
|
+
if (args.project) {
|
|
98
|
+
projectId = args.project;
|
|
99
|
+
projectName = args.project.split('/').pop() || args.project;
|
|
100
|
+
} else {
|
|
101
|
+
const project = detectProject();
|
|
102
|
+
if (!project) {
|
|
103
|
+
console.error('[ERROR] No .git found — not a project directory.');
|
|
104
|
+
console.error('Use --project <id> to target a specific project, or run from a git repo.');
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
projectId = project.id;
|
|
108
|
+
projectName = project.name;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(`\nProject: ${projectName} (${projectId})\n`);
|
|
112
|
+
|
|
113
|
+
const dataDir = await getProjectDataDir(projectId);
|
|
114
|
+
await initObservationStore(dataDir);
|
|
115
|
+
const allObs = await getObservationStore().loadAll() as Array<{
|
|
116
|
+
id?: number;
|
|
117
|
+
type?: string;
|
|
118
|
+
title?: string;
|
|
119
|
+
narrative?: string;
|
|
120
|
+
entityName?: string;
|
|
121
|
+
facts?: string[];
|
|
122
|
+
concepts?: string[];
|
|
123
|
+
timestamp?: string;
|
|
124
|
+
projectId?: string;
|
|
125
|
+
status?: string;
|
|
126
|
+
}>;
|
|
127
|
+
|
|
128
|
+
if (allObs.length === 0) {
|
|
129
|
+
console.log('[OK] No observations found - nothing to clean up.');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Filter to project-scoped observations only
|
|
134
|
+
const projectObs = allObs.filter(o => o.projectId === projectId && (o.status ?? 'active') === 'active');
|
|
135
|
+
|
|
136
|
+
// Categorize: low-quality
|
|
137
|
+
const lowQuality = projectObs.filter(o => isLowQuality(o.title ?? ''));
|
|
138
|
+
const highQuality = projectObs.filter(o => !isLowQuality(o.title ?? ''));
|
|
139
|
+
|
|
140
|
+
// Find duplicates (same title + type + entity)
|
|
141
|
+
const seen = new Set<string>();
|
|
142
|
+
const duplicates: typeof projectObs = [];
|
|
143
|
+
const unique: typeof projectObs = [];
|
|
144
|
+
for (const obs of highQuality) {
|
|
145
|
+
const key = `${obs.type}|${obs.title}|${obs.entityName}`;
|
|
146
|
+
if (seen.has(key)) {
|
|
147
|
+
duplicates.push(obs);
|
|
148
|
+
} else {
|
|
149
|
+
seen.add(key);
|
|
150
|
+
unique.push(obs);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Find noise pollution (demo/test/system-self)
|
|
155
|
+
const noiseHits: Array<{ obs: typeof projectObs[0]; reason: string }> = [];
|
|
156
|
+
if (args.noise) {
|
|
157
|
+
for (const obs of projectObs) {
|
|
158
|
+
if (lowQuality.includes(obs) || duplicates.includes(obs)) continue;
|
|
159
|
+
const { isNoise, reason } = isNoisePollution(obs);
|
|
160
|
+
if (isNoise) noiseHits.push({ obs, reason });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const toRemove = [...lowQuality, ...duplicates];
|
|
165
|
+
const toArchive = noiseHits.map(h => h.obs);
|
|
166
|
+
|
|
167
|
+
console.log(`Analysis (active observations for ${projectId}):`);
|
|
168
|
+
console.log(` Total active: ${projectObs.length}`);
|
|
169
|
+
console.log(` High quality: ${unique.length - toArchive.length}`);
|
|
170
|
+
console.log(` Low quality: ${lowQuality.length}`);
|
|
171
|
+
console.log(` Duplicates: ${duplicates.length}`);
|
|
172
|
+
if (args.noise) {
|
|
173
|
+
console.log(` Noise pollution: ${toArchive.length}`);
|
|
174
|
+
}
|
|
175
|
+
console.log(` To delete: ${toRemove.length}`);
|
|
176
|
+
if (args.noise) {
|
|
177
|
+
console.log(` To archive: ${toArchive.length}`);
|
|
178
|
+
}
|
|
179
|
+
console.log();
|
|
180
|
+
|
|
181
|
+
if (toRemove.length === 0 && toArchive.length === 0) {
|
|
182
|
+
console.log('[OK] All observations are clean — nothing to clean up!');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Preview deletions
|
|
187
|
+
if (toRemove.length > 0) {
|
|
188
|
+
console.log('Items to DELETE:');
|
|
189
|
+
toRemove.slice(0, 10).forEach(o => {
|
|
190
|
+
const tag = isLowQuality(o.title ?? '') ? '(low-quality)' : '(duplicate)';
|
|
191
|
+
console.log(` ${tag} #${o.id ?? '?'} "${o.title}" [${o.type}]`);
|
|
192
|
+
});
|
|
193
|
+
if (toRemove.length > 10) {
|
|
194
|
+
console.log(` ... and ${toRemove.length - 10} more`);
|
|
195
|
+
}
|
|
196
|
+
console.log();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Preview noise archival
|
|
200
|
+
if (toArchive.length > 0) {
|
|
201
|
+
console.log('Items to ARCHIVE (noise pollution):');
|
|
202
|
+
noiseHits.slice(0, 15).forEach(({ obs, reason }) => {
|
|
203
|
+
console.log(` (${reason}) #${obs.id ?? '?'} "${obs.title}" [${obs.type}] entity=${obs.entityName}`);
|
|
204
|
+
});
|
|
205
|
+
if (toArchive.length > 15) {
|
|
206
|
+
console.log(` ... and ${toArchive.length - 15} more`);
|
|
207
|
+
}
|
|
208
|
+
console.log();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (args.dry) {
|
|
212
|
+
console.log('[DRY RUN] No changes made.');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!args.force) {
|
|
217
|
+
const readline = await import('node:readline');
|
|
218
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
219
|
+
const desc = [
|
|
220
|
+
toRemove.length > 0 ? `delete ${toRemove.length}` : '',
|
|
221
|
+
toArchive.length > 0 ? `archive ${toArchive.length}` : '',
|
|
222
|
+
].filter(Boolean).join(' and ');
|
|
223
|
+
const answer = await new Promise<string>(resolve => {
|
|
224
|
+
rl.question(`Proceed to ${desc} observations? (y/N) `, resolve);
|
|
225
|
+
});
|
|
226
|
+
rl.close();
|
|
227
|
+
|
|
228
|
+
if (answer.trim().toLowerCase() !== 'y') {
|
|
229
|
+
console.log('Cancelled.');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Archive noise observations (set status to 'archived' instead of deleting)
|
|
235
|
+
const archiveIds = new Set(toArchive.map(o => o.id));
|
|
236
|
+
let archivedCount = 0;
|
|
237
|
+
for (const obs of allObs) {
|
|
238
|
+
if (archiveIds.has(obs.id)) {
|
|
239
|
+
(obs as any).status = 'archived';
|
|
240
|
+
archivedCount++;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Delete low-quality/duplicate observations
|
|
245
|
+
const removeIds = new Set(toRemove.map(o => JSON.stringify(o)));
|
|
246
|
+
const remaining = allObs.filter(o => !removeIds.has(JSON.stringify(o)));
|
|
247
|
+
|
|
248
|
+
await getObservationStore().bulkReplace(remaining as any);
|
|
249
|
+
|
|
250
|
+
const parts: string[] = [];
|
|
251
|
+
if (toRemove.length > 0) parts.push(`deleted ${toRemove.length}`);
|
|
252
|
+
if (archivedCount > 0) parts.push(`archived ${archivedCount}`);
|
|
253
|
+
console.log(`[OK] ${parts.join(', ')}. ${remaining.length} observations remaining.`);
|
|
254
|
+
},
|
|
255
|
+
});
|
|
@@ -3,6 +3,7 @@ import { CodeGraphStore } from '../../codegraph/store.js';
|
|
|
3
3
|
import { indexProjectLite } from '../../codegraph/lite-provider.js';
|
|
4
4
|
import { assembleContextPackForTask, buildContextPackPrompt } from '../../codegraph/context-pack.js';
|
|
5
5
|
import { backfillMissingObservationCodeRefs } from '../../codegraph/binder.js';
|
|
6
|
+
import { getResolvedConfig } from '../../config/resolved-config.js';
|
|
6
7
|
import { getAllObservations } from '../../memory/observations.js';
|
|
7
8
|
import { emitError, emitResult, getCliProjectContext, parsePositiveInt } from './operator-shared.js';
|
|
8
9
|
|
|
@@ -49,6 +50,7 @@ export default defineCommand({
|
|
|
49
50
|
const store = new CodeGraphStore();
|
|
50
51
|
await store.init(dataDir);
|
|
51
52
|
const explicitAction = Boolean(positional[0] || (args.action as string | undefined));
|
|
53
|
+
const exclude = getResolvedConfig({ projectRoot: project.rootPath }).codegraph.excludePatterns;
|
|
52
54
|
|
|
53
55
|
switch (action) {
|
|
54
56
|
case 'status': {
|
|
@@ -64,6 +66,7 @@ export default defineCommand({
|
|
|
64
66
|
const indexed = await indexProjectLite({
|
|
65
67
|
projectId: project.id,
|
|
66
68
|
projectRoot: project.rootPath,
|
|
69
|
+
exclude,
|
|
67
70
|
});
|
|
68
71
|
store.replaceProjectIndex(project.id, indexed);
|
|
69
72
|
const activeObservations = getAllObservations()
|
|
@@ -99,6 +102,7 @@ export default defineCommand({
|
|
|
99
102
|
task,
|
|
100
103
|
observations,
|
|
101
104
|
limit,
|
|
105
|
+
exclude,
|
|
102
106
|
});
|
|
103
107
|
emitResult({ project, pack }, buildContextPackPrompt(pack), asJson);
|
|
104
108
|
return;
|
|
@@ -29,12 +29,19 @@ export default defineCommand({
|
|
|
29
29
|
function readDotted(source: Record<string, unknown>, key: string): unknown {
|
|
30
30
|
let cursor: unknown = source;
|
|
31
31
|
for (const part of key.split('.')) {
|
|
32
|
-
if (!cursor || typeof cursor !== 'object'
|
|
33
|
-
|
|
32
|
+
if (!cursor || typeof cursor !== 'object') return undefined;
|
|
33
|
+
const record = cursor as Record<string, unknown>;
|
|
34
|
+
const normalizedPart = part in record ? part : snakeToCamel(part);
|
|
35
|
+
if (!(normalizedPart in record)) return undefined;
|
|
36
|
+
cursor = record[normalizedPart];
|
|
34
37
|
}
|
|
35
38
|
return cursor;
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
function snakeToCamel(value: string): string {
|
|
42
|
+
return value.replace(/_([a-z])/g, (_, char: string) => char.toUpperCase());
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
function formatValue(value: unknown, key: string): string {
|
|
39
46
|
if (typeof value === 'string') return shouldRedactKey(key) ? '<redacted>' : value;
|
|
40
47
|
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
@@ -13,6 +13,14 @@ export default defineCommand({
|
|
|
13
13
|
description: 'Diagnose Memorix health — project identity, embedding, data, conflicts',
|
|
14
14
|
},
|
|
15
15
|
args: {
|
|
16
|
+
agent: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'Agent to inspect for "doctor agents" (default: all)',
|
|
19
|
+
},
|
|
20
|
+
scope: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Scope for "doctor agents": local, project, global, or all',
|
|
23
|
+
},
|
|
16
24
|
json: {
|
|
17
25
|
type: 'boolean',
|
|
18
26
|
description: 'Output as JSON instead of human-readable text',
|
|
@@ -25,6 +33,21 @@ export default defineCommand({
|
|
|
25
33
|
},
|
|
26
34
|
},
|
|
27
35
|
run: async ({ args }) => {
|
|
36
|
+
const positional = (args._ as string[]) ?? [];
|
|
37
|
+
if (positional[0] === 'agents') {
|
|
38
|
+
const { inspectAgentIntegrations, formatAgentIntegrationReport } = await import('./agent-integrations.js');
|
|
39
|
+
const report = await inspectAgentIntegrations({
|
|
40
|
+
agent: args.agent as string | undefined,
|
|
41
|
+
scope: args.scope as string | undefined,
|
|
42
|
+
});
|
|
43
|
+
if (args.json) {
|
|
44
|
+
console.log(JSON.stringify({ agents: report }, null, 2));
|
|
45
|
+
} else {
|
|
46
|
+
console.log(formatAgentIntegrationReport(report));
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
28
51
|
const { existsSync, readFileSync } = await import('node:fs');
|
|
29
52
|
const { join, basename } = await import('node:path');
|
|
30
53
|
const { homedir } = await import('node:os');
|
|
@@ -295,8 +318,10 @@ export default defineCommand({
|
|
|
295
318
|
try {
|
|
296
319
|
const { CodeGraphStore } = await import('../../codegraph/store.js');
|
|
297
320
|
const { buildProjectContextOverview } = await import('../../codegraph/project-context.js');
|
|
321
|
+
const { getResolvedConfig } = await import('../../config/resolved-config.js');
|
|
298
322
|
const codeStore = new CodeGraphStore();
|
|
299
323
|
await codeStore.init(dataDir);
|
|
324
|
+
const exclude = getResolvedConfig({ projectRoot: projectRoot || process.cwd() }).codegraph.excludePatterns;
|
|
300
325
|
const overview = buildProjectContextOverview({
|
|
301
326
|
project: {
|
|
302
327
|
id: projectId,
|
|
@@ -305,6 +330,7 @@ export default defineCommand({
|
|
|
305
330
|
},
|
|
306
331
|
store: codeStore,
|
|
307
332
|
observations: projectObservations,
|
|
333
|
+
exclude,
|
|
308
334
|
});
|
|
309
335
|
const languageText = overview.code.languages.length > 0
|
|
310
336
|
? overview.code.languages.map((item: any) => `${item.language} ${item.files}`).join(', ')
|