memorix 1.1.7 → 1.1.8
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 +13 -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 +730 -150
- 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 +15 -8
- package/dist/index.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +13 -0
- package/dist/memcode-runtime/package.json +4 -4
- package/dist/sdk.js +15 -8
- package/dist/sdk.js.map +1 -1
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +18 -0
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/SETUP.md +10 -0
- package/docs/dev-log/progress.txt +35 -26
- 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/doctor.ts +23 -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/context-pack.ts +5 -1
- package/src/codegraph/lite-provider.ts +5 -1
- package/src/codegraph/project-context.ts +5 -1
- package/src/compact/token-budget.ts +74 -74
- package/src/config/behavior.ts +59 -59
- 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/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/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
|
+
});
|
|
@@ -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');
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import { defineCommand } from 'citty';
|
|
2
|
-
import { getBeforeAfterMetrics, getMetricsSummary } from '../../memory/formation/index.js';
|
|
3
|
-
import { emitError, emitResult } from './operator-shared.js';
|
|
4
|
-
|
|
5
|
-
export default defineCommand({
|
|
6
|
-
meta: {
|
|
7
|
-
name: 'formation',
|
|
8
|
-
description: 'Inspect Memory Formation Pipeline runtime metrics',
|
|
9
|
-
},
|
|
10
|
-
args: {
|
|
11
|
-
json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
|
|
12
|
-
},
|
|
13
|
-
run: async ({ args }) => {
|
|
14
|
-
const action = (args._ as string[])?.[0] || '';
|
|
15
|
-
const asJson = !!args.json;
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
switch (action) {
|
|
19
|
-
case 'metrics': {
|
|
20
|
-
const summary = getMetricsSummary();
|
|
21
|
-
const beforeAfter = getBeforeAfterMetrics();
|
|
22
|
-
emitResult(
|
|
23
|
-
{ summary, beforeAfter },
|
|
24
|
-
summary.total === 0
|
|
25
|
-
? 'Formation Pipeline: No metrics collected yet.'
|
|
26
|
-
: [
|
|
27
|
-
'Formation Pipeline Metrics',
|
|
28
|
-
`- Total observations processed: ${summary.total}`,
|
|
29
|
-
`- Average value score: ${summary.avgValueScore.toFixed(3)}`,
|
|
30
|
-
`- Average processing time: ${summary.avgDurationMs.toFixed(1)}ms`,
|
|
31
|
-
].join('\n'),
|
|
32
|
-
asJson,
|
|
33
|
-
);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
default:
|
|
38
|
-
console.log('Memorix Formation Commands');
|
|
39
|
-
console.log('');
|
|
40
|
-
console.log('Usage:');
|
|
41
|
-
console.log(' memorix formation metrics');
|
|
42
|
-
}
|
|
43
|
-
} catch (error) {
|
|
44
|
-
emitError(error instanceof Error ? error.message : String(error), asJson);
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { getBeforeAfterMetrics, getMetricsSummary } from '../../memory/formation/index.js';
|
|
3
|
+
import { emitError, emitResult } from './operator-shared.js';
|
|
4
|
+
|
|
5
|
+
export default defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: 'formation',
|
|
8
|
+
description: 'Inspect Memory Formation Pipeline runtime metrics',
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
|
|
12
|
+
},
|
|
13
|
+
run: async ({ args }) => {
|
|
14
|
+
const action = (args._ as string[])?.[0] || '';
|
|
15
|
+
const asJson = !!args.json;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
switch (action) {
|
|
19
|
+
case 'metrics': {
|
|
20
|
+
const summary = getMetricsSummary();
|
|
21
|
+
const beforeAfter = getBeforeAfterMetrics();
|
|
22
|
+
emitResult(
|
|
23
|
+
{ summary, beforeAfter },
|
|
24
|
+
summary.total === 0
|
|
25
|
+
? 'Formation Pipeline: No metrics collected yet.'
|
|
26
|
+
: [
|
|
27
|
+
'Formation Pipeline Metrics',
|
|
28
|
+
`- Total observations processed: ${summary.total}`,
|
|
29
|
+
`- Average value score: ${summary.avgValueScore.toFixed(3)}`,
|
|
30
|
+
`- Average processing time: ${summary.avgDurationMs.toFixed(1)}ms`,
|
|
31
|
+
].join('\n'),
|
|
32
|
+
asJson,
|
|
33
|
+
);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
default:
|
|
38
|
+
console.log('Memorix Formation Commands');
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log('Usage:');
|
|
41
|
+
console.log(' memorix formation metrics');
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
emitError(error instanceof Error ? error.message : String(error), asJson);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|