memorix 1.4.4 → 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.
- package/CHANGELOG.md +69 -0
- package/CLAUDE.md +7 -3
- package/README.md +14 -4
- package/README.zh-CN.md +9 -2
- package/dist/cli/index.js +1802 -1138
- package/dist/index.js +1298 -991
- package/dist/{maintenance-jobs-B1-W0ssz.d.ts → maintenance-jobs-DVRF-46r.d.ts} +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +1755 -829
- package/dist/media-audio-runner.js +1760 -835
- package/dist/media-video-runner.js +1755 -830
- package/dist/memcode-runtime/CHANGELOG.md +69 -0
- package/dist/sdk.js +1290 -990
- package/dist/types.d.ts +7 -1
- package/dist/vector-backfill-runner.d.ts +1 -1
- package/dist/vector-backfill-runner.js +1751 -826
- package/docs/1.4.5-AGENT-AUTO-MEMORY-RESEARCH.md +95 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +3 -2
- package/docs/API_REFERENCE.md +23 -4
- package/docs/CONFIGURATION.md +17 -3
- package/docs/INTEGRATIONS.md +9 -2
- package/docs/KNOWN_ISSUES_AND_ROADMAP.md +1 -0
- package/docs/MEMCODE.md +1 -1
- package/docs/SETUP.md +14 -4
- package/llms-full.txt +5 -4
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/plugins/antigravity/memorix/mcp_config.json +3 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/memorix/.mcp.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.mcp.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.mcp.json +1 -1
- package/plugins/copilot/memorix/.mcp.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +4 -2
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/openclaw/memorix/.mcp.json +3 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/commands/agent-integrations.ts +7 -1
- package/src/cli/commands/dashboard.ts +14 -3
- package/src/cli/commands/hooks-install.ts +2 -0
- package/src/cli/commands/hooks-preview.ts +5 -0
- package/src/cli/commands/ingest-commit.ts +47 -3
- package/src/cli/commands/integrate.ts +2 -0
- package/src/cli/commands/memory.ts +17 -4
- package/src/cli/commands/purge.ts +120 -0
- package/src/cli/commands/serve-http.ts +39 -5
- package/src/cli/commands/setup.ts +60 -3
- package/src/cli/commands/uninstall.ts +20 -0
- package/src/cli/index.ts +3 -804
- package/src/codegraph/auto-context.ts +82 -1
- package/src/config/yaml-loader.ts +3 -8
- package/src/config.ts +0 -5
- package/src/evaluation/session-replay.ts +98 -0
- package/src/git/extractor.ts +5 -3
- package/src/hooks/installers/index.ts +58 -2
- package/src/hooks/types.ts +2 -0
- package/src/integrations/registry.ts +9 -0
- package/src/knowledge/workset.ts +58 -0
- package/src/memory/long-term.ts +97 -2
- package/src/runtime/control-plane-maintenance.ts +1 -0
- package/src/runtime/isolated-maintenance.ts +1 -0
- package/src/runtime/lifecycle.ts +19 -0
- package/src/runtime/maintenance-jobs.ts +1 -0
- package/src/runtime/project-maintenance.ts +8 -0
- package/src/search/surfaced-registry.ts +62 -0
- package/src/server.ts +74 -14
- package/src/store/orama-store.ts +17 -1
- package/src/types.ts +7 -1
- package/src/workspace/mcp-adapters/dsh.ts +147 -0
- package/src/cli/commands/audit-list.ts +0 -89
- package/src/cli/commands/purge-all-memory.ts +0 -85
- package/src/cli/commands/purge-project-memory.ts +0 -83
- package/src/cli/commands/uninstall-project-artifacts.ts +0 -86
- package/src/cli/tui/Sidebar.tsx +0 -179
- package/src/cli/tui/interactive-commands.ts +0 -261
- package/src/cli/tui/markdown-render.tsx +0 -371
- package/src/cli/tui/use-mouse.ts +0 -157
- package/src/cli/tui/useNavigation.ts +0 -56
- package/src/orchestrate/context-compact.ts +0 -188
- package/src/team/file-locks.ts +0 -173
- package/src/team/index.ts +0 -23
- package/src/team/messages.ts +0 -203
- package/src/team/persistence.ts +0 -96
- package/src/team/registry.ts +0 -158
- package/src/team/tasks.ts +0 -211
|
@@ -159,6 +159,7 @@ function getAgentLabel(agent: string): string {
|
|
|
159
159
|
hermes: 'Hermes Agent',
|
|
160
160
|
omp: 'Oh-my-Pi',
|
|
161
161
|
trae: 'Trae',
|
|
162
|
+
dsh: 'DeepSeek Harness',
|
|
162
163
|
};
|
|
163
164
|
return labels[agent] || agent;
|
|
164
165
|
}
|
|
@@ -177,6 +178,7 @@ function getAgentHint(agent: string): string {
|
|
|
177
178
|
hermes: 'Hermes plugin hooks; use `memorix setup --agent hermes`',
|
|
178
179
|
omp: 'Oh-my-Pi package hooks; use `memorix setup --agent omp`',
|
|
179
180
|
trae: '.trae/rules/project_rules.md (rules only, no hooks system)',
|
|
181
|
+
dsh: '~/.dsh/cordis.patch.yml MCP row via `memorix setup --agent dsh` (no hooks system)',
|
|
180
182
|
codex: 'Codex plugin hooks; use `memorix setup --agent codex`',
|
|
181
183
|
};
|
|
182
184
|
return hints[agent] || '';
|
|
@@ -152,6 +152,7 @@ function estimateFileSize(agent: string, configPath: string): string {
|
|
|
152
152
|
antigravity: '~2KB',
|
|
153
153
|
opencode: '~3KB',
|
|
154
154
|
trae: '~1KB',
|
|
155
|
+
dsh: '~1KB',
|
|
155
156
|
};
|
|
156
157
|
return sizes[agent] || '~2KB';
|
|
157
158
|
}
|
|
@@ -171,6 +172,8 @@ function getRulesPath(agent: string, cwd: string, global: boolean): string {
|
|
|
171
172
|
return path.join(home, '.gemini', 'GEMINI.md');
|
|
172
173
|
case 'trae':
|
|
173
174
|
return path.join(home, '.trae', 'rules', 'project_rules.md');
|
|
175
|
+
case 'dsh':
|
|
176
|
+
return path.join(process.env.DSH_HOME?.trim() || path.join(home, '.dsh'), 'AGENTS.md');
|
|
174
177
|
default:
|
|
175
178
|
return '';
|
|
176
179
|
}
|
|
@@ -186,6 +189,8 @@ function getRulesPath(agent: string, cwd: string, global: boolean): string {
|
|
|
186
189
|
return path.join(cwd, 'GEMINI.md');
|
|
187
190
|
case 'trae':
|
|
188
191
|
return path.join(cwd, '.trae', 'rules', 'project_rules.md');
|
|
192
|
+
case 'dsh':
|
|
193
|
+
return path.join(cwd, 'AGENTS.md');
|
|
189
194
|
default:
|
|
190
195
|
return '';
|
|
191
196
|
}
|
|
@@ -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,
|
|
@@ -114,6 +114,7 @@ function getAgentLabel(agent: string): string {
|
|
|
114
114
|
antigravity: 'Antigravity',
|
|
115
115
|
'gemini-cli': 'Gemini CLI',
|
|
116
116
|
trae: 'Trae',
|
|
117
|
+
dsh: 'DeepSeek Harness',
|
|
117
118
|
};
|
|
118
119
|
return labels[agent] || agent;
|
|
119
120
|
}
|
|
@@ -130,6 +131,7 @@ function getAgentHint(agent: string): string {
|
|
|
130
131
|
antigravity: 'plugin + MCP + hooks + skills',
|
|
131
132
|
'gemini-cli': 'settings + GEMINI.md',
|
|
132
133
|
trae: 'rules',
|
|
134
|
+
dsh: 'MCP patch + AGENTS.md + skills',
|
|
133
135
|
};
|
|
134
136
|
return hints[agent] || 'integration files';
|
|
135
137
|
}
|
|
@@ -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
|
|
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
|
|
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
|
+
});
|
|
@@ -39,6 +39,36 @@ export function parseSessionTimeoutMs(raw: string | undefined): number {
|
|
|
39
39
|
return Math.floor(parsed);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
export interface ServeHttpConfiguredDefaults {
|
|
43
|
+
port?: number;
|
|
44
|
+
transport?: 'stdio' | 'http';
|
|
45
|
+
dashboard?: boolean;
|
|
46
|
+
dashboardPort?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The [server] config section is a real default source, not display-only:
|
|
51
|
+
* `port` becomes the listener default and an explicit `transport: "stdio"`
|
|
52
|
+
* produces a notice (an explicit serve-http command still wins). CLI flags
|
|
53
|
+
* always beat config.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveServeHttpDefaults(
|
|
56
|
+
argsPort: string | undefined,
|
|
57
|
+
configured: ServeHttpConfiguredDefaults,
|
|
58
|
+
): { port: number; transportNotice?: string } {
|
|
59
|
+
const configuredPort = typeof configured.port === 'number' && Number.isFinite(configured.port)
|
|
60
|
+
? configured.port
|
|
61
|
+
: undefined;
|
|
62
|
+
const port = parseTcpPortOrReport(argsPort, configuredPort ?? 3211);
|
|
63
|
+
if (port === undefined) {
|
|
64
|
+
return { port: 3211 };
|
|
65
|
+
}
|
|
66
|
+
const transportNotice = configured.transport === 'stdio'
|
|
67
|
+
? '[memorix] config server.transport = "stdio"; serve-http continues on HTTP (the explicit command wins).'
|
|
68
|
+
: undefined;
|
|
69
|
+
return { port, transportNotice };
|
|
70
|
+
}
|
|
71
|
+
|
|
42
72
|
export default defineCommand({
|
|
43
73
|
meta: {
|
|
44
74
|
name: 'serve-http',
|
|
@@ -81,11 +111,15 @@ export default defineCommand({
|
|
|
81
111
|
const { writeFileSync, mkdirSync } = await import('node:fs');
|
|
82
112
|
const { homedir } = await import('node:os');
|
|
83
113
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
114
|
+
// The [server] config section feeds startup defaults; CLI flags win.
|
|
115
|
+
let configuredDefaults: ServeHttpConfiguredDefaults = {};
|
|
116
|
+
try {
|
|
117
|
+
const { getServerConfig } = await import('../../config.js');
|
|
118
|
+
configuredDefaults = getServerConfig();
|
|
119
|
+
} catch { /* config files are optional */ }
|
|
120
|
+
const resolved = resolveServeHttpDefaults(args.port as string | undefined, configuredDefaults);
|
|
121
|
+
if (resolved.transportNotice) console.error(resolved.transportNotice);
|
|
122
|
+
const port = resolved.port;
|
|
89
123
|
const host = args.host || '127.0.0.1';
|
|
90
124
|
const toolProfile = resolveToolProfile({ explicit: args.mode, envValue: process.env.MEMORIX_MODE, fallback: 'team' });
|
|
91
125
|
|
|
@@ -30,6 +30,7 @@ import { OmpMCPAdapter } from '../../workspace/mcp-adapters/omp.js';
|
|
|
30
30
|
import { KiroMCPAdapter } from '../../workspace/mcp-adapters/kiro.js';
|
|
31
31
|
import { OpenCodeMCPAdapter } from '../../workspace/mcp-adapters/opencode.js';
|
|
32
32
|
import { TraeMCPAdapter } from '../../workspace/mcp-adapters/trae.js';
|
|
33
|
+
import { DshMCPAdapter } from '../../workspace/mcp-adapters/dsh.js';
|
|
33
34
|
import { CodeBuddyMCPAdapter } from '../../workspace/mcp-adapters/codebuddy.js';
|
|
34
35
|
import { getSetupIntegrationRows as readSetupIntegrationRows } from '../../integrations/registry.js';
|
|
35
36
|
import { OFFICIAL_MEMORIX_SKILLS } from '../../hooks/official-skills.js';
|
|
@@ -190,6 +191,7 @@ const SUPPORTED_SETUP_AGENTS: AgentName[] = [
|
|
|
190
191
|
'omp',
|
|
191
192
|
'kiro',
|
|
192
193
|
'trae',
|
|
194
|
+
'dsh',
|
|
193
195
|
'pi',
|
|
194
196
|
];
|
|
195
197
|
|
|
@@ -402,6 +404,7 @@ export function getMcpAdapter(agent: McpConfigAgent): MCPConfigAdapter {
|
|
|
402
404
|
kiro: new KiroMCPAdapter(),
|
|
403
405
|
opencode: new OpenCodeMCPAdapter(),
|
|
404
406
|
trae: new TraeMCPAdapter(),
|
|
407
|
+
dsh: new DshMCPAdapter(),
|
|
405
408
|
};
|
|
406
409
|
return adapters[agent];
|
|
407
410
|
}
|
|
@@ -419,7 +422,9 @@ export function buildMemorixServer(mcp: Exclude<SetupMcpTransport, 'none'>): MCP
|
|
|
419
422
|
return {
|
|
420
423
|
name: 'memorix',
|
|
421
424
|
command: 'memorix',
|
|
422
|
-
|
|
425
|
+
// The installed surface must expose every tool the generated guidance
|
|
426
|
+
// teaches. lite covers the full taught set; micro would hide half of it.
|
|
427
|
+
args: ['serve', '--mode', 'lite'],
|
|
423
428
|
};
|
|
424
429
|
}
|
|
425
430
|
|
|
@@ -699,6 +704,55 @@ function mergeYamlMcpConfig(existingContent: string | null, generatedContent: st
|
|
|
699
704
|
return stringifyYaml(merged, { lineWidth: 0 });
|
|
700
705
|
}
|
|
701
706
|
|
|
707
|
+
/**
|
|
708
|
+
* Merge the generated DeepSeek Harness patch rows into an existing
|
|
709
|
+
* `cordis.patch.yml` without clobbering unrelated user patches.
|
|
710
|
+
*
|
|
711
|
+
* DSH patch files are lists of Cordis patch operations. Memorix owns exactly
|
|
712
|
+
* the `@deepseek-ai/dsh-mcp-client` row whose config.serverName is `memorix`;
|
|
713
|
+
* every other operation is user-owned and must survive the merge. When the
|
|
714
|
+
* existing file is not a parseable list, the generated rows are appended to
|
|
715
|
+
* the raw content instead of overwriting it.
|
|
716
|
+
*/
|
|
717
|
+
function mergeDshPatchConfig(existingContent: string | null, generatedContent: string): string {
|
|
718
|
+
const generatedTrimmed = generatedContent.trimEnd();
|
|
719
|
+
if (!existingContent?.trim()) {
|
|
720
|
+
return generatedTrimmed.endsWith('\n') ? generatedTrimmed : `${generatedTrimmed}\n`;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const isMemorixInsertRow = (row: unknown): boolean => {
|
|
724
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) return false;
|
|
725
|
+
const record = row as Record<string, unknown>;
|
|
726
|
+
if (record.name !== '@deepseek-ai/dsh-mcp-client') return false;
|
|
727
|
+
const config = record.config;
|
|
728
|
+
return Boolean(
|
|
729
|
+
config && typeof config === 'object' && !Array.isArray(config)
|
|
730
|
+
&& (config as Record<string, unknown>).serverName === 'memorix',
|
|
731
|
+
);
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const opContainsMemorixRow = (op: unknown): boolean => {
|
|
735
|
+
if (!op || typeof op !== 'object' || Array.isArray(op)) return false;
|
|
736
|
+
const insert = (op as Record<string, unknown>).insert;
|
|
737
|
+
return Array.isArray(insert) && insert.some(isMemorixInsertRow);
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
let existingDocument: unknown;
|
|
741
|
+
try {
|
|
742
|
+
existingDocument = parseYaml(existingContent);
|
|
743
|
+
} catch {
|
|
744
|
+
return `${existingContent.trimEnd()}\n\n${generatedTrimmed}\n`;
|
|
745
|
+
}
|
|
746
|
+
if (!Array.isArray(existingDocument)) {
|
|
747
|
+
// A scalar or mapping document is not a patch list — never rewrite it.
|
|
748
|
+
return `${existingContent.trimEnd()}\n\n${generatedTrimmed}\n`;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const kept = existingDocument.filter((op) => !opContainsMemorixRow(op));
|
|
752
|
+
const merged = [...kept, ...(parseYaml(generatedContent) as unknown[])];
|
|
753
|
+
return stringifyYaml(merged, { lineWidth: 0 }) + '\n';
|
|
754
|
+
}
|
|
755
|
+
|
|
702
756
|
export async function installMcpConfig(options: {
|
|
703
757
|
agent: McpConfigAgent;
|
|
704
758
|
projectRoot?: string;
|
|
@@ -725,7 +779,9 @@ export async function installMcpConfig(options: {
|
|
|
725
779
|
? mergeTomlMcpConfig(existingContent, generated)
|
|
726
780
|
: options.agent === 'hermes'
|
|
727
781
|
? mergeYamlMcpConfig(existingContent, generated)
|
|
728
|
-
|
|
782
|
+
: options.agent === 'dsh'
|
|
783
|
+
? mergeDshPatchConfig(existingContent, generated)
|
|
784
|
+
: mergeJsonMcpConfig(existingContent, generated);
|
|
729
785
|
|
|
730
786
|
await mkdir(path.dirname(configPath), { recursive: true });
|
|
731
787
|
await writeFile(configPath, content, 'utf-8');
|
|
@@ -1603,7 +1659,7 @@ export default defineCommand({
|
|
|
1603
1659
|
args: {
|
|
1604
1660
|
agent: {
|
|
1605
1661
|
type: 'string',
|
|
1606
|
-
description: 'Target agent (claude, codex, codebuddy, opencode, cursor, windsurf, copilot, gemini-cli, antigravity, openclaw, hermes, omp, kiro, trae, pi, all)',
|
|
1662
|
+
description: 'Target agent (claude, codex, codebuddy, opencode, cursor, windsurf, copilot, gemini-cli, antigravity, openclaw, hermes, omp, kiro, trae, dsh, pi, all)',
|
|
1607
1663
|
required: false,
|
|
1608
1664
|
},
|
|
1609
1665
|
mcp: {
|
|
@@ -1677,6 +1733,7 @@ export default defineCommand({
|
|
|
1677
1733
|
{ value: 'hermes', label: 'Hermes Agent', hint: 'plugin + hooks + commands + skills + MCP' },
|
|
1678
1734
|
{ value: 'omp', label: 'Oh-my-Pi', hint: 'package + extension + command + MCP' },
|
|
1679
1735
|
{ value: 'pi', label: 'Pi coding agent', hint: 'package + extension + skill' },
|
|
1736
|
+
{ value: 'dsh', label: 'DeepSeek Harness', hint: 'MCP patch + AGENTS.md + skills' },
|
|
1680
1737
|
{ value: 'opencode', label: 'OpenCode', hint: 'local plugin + AGENTS.md + MCP' },
|
|
1681
1738
|
{ value: 'windsurf', label: 'Windsurf', hint: 'rules + hooks + MCP' },
|
|
1682
1739
|
{ value: 'all', label: 'All supported agents', hint: 'install detected-compatible files' },
|
|
@@ -56,6 +56,15 @@ function checkTOML(p: string): boolean {
|
|
|
56
56
|
} catch { return false; }
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function checkDshPatch(p: string): boolean {
|
|
60
|
+
if (!exists(p)) return false;
|
|
61
|
+
try {
|
|
62
|
+
const content = fs.readFileSync(p, 'utf-8');
|
|
63
|
+
if (!content.includes('memory-memorix')) return false;
|
|
64
|
+
return /serverName:\s*['"]?memorix['"]?/.test(content);
|
|
65
|
+
} catch { return false; }
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
/** Detects memorix MCP config entries in both global and project-level paths. */
|
|
60
69
|
function getMCPConfigEntries(home: string, cwd: string): MCPConfigEntry[] {
|
|
61
70
|
const entries: MCPConfigEntry[] = [];
|
|
@@ -216,6 +225,17 @@ function getMCPConfigEntries(home: string, cwd: string): MCPConfigEntry[] {
|
|
|
216
225
|
note: 'Remove the "memorix" key from mcpServers in this file.',
|
|
217
226
|
});
|
|
218
227
|
|
|
228
|
+
// DeepSeek Harness
|
|
229
|
+
const dshPatchPath = path.join(process.env.DSH_HOME?.trim() || path.join(home, '.dsh'), 'cordis.patch.yml');
|
|
230
|
+
entries.push({
|
|
231
|
+
agent: 'DeepSeek Harness',
|
|
232
|
+
path: dshPatchPath,
|
|
233
|
+
kind: 'global',
|
|
234
|
+
format: 'json',
|
|
235
|
+
detected: checkDshPatch(dshPatchPath),
|
|
236
|
+
note: 'Remove the memory-memorix insert row from this patch file.',
|
|
237
|
+
});
|
|
238
|
+
|
|
219
239
|
return entries;
|
|
220
240
|
}
|
|
221
241
|
|