memorix 1.4.3 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +20 -6
  3. package/README.zh-CN.md +16 -3
  4. package/TEAM.md +86 -86
  5. package/assets/star-history-dark.svg +8 -8
  6. package/assets/star-history-light.svg +8 -8
  7. package/assets/star-history.json +16 -2
  8. package/dist/cli/index.js +539 -233
  9. package/dist/cli/memcode.js +0 -0
  10. package/dist/index.js +72 -15
  11. package/dist/maintenance-runner.js +9 -7
  12. package/dist/media-audio-runner.js +9 -7
  13. package/dist/media-video-runner.js +9 -7
  14. package/dist/memcode/index.js +9 -7
  15. package/dist/memcode-runtime/CHANGELOG.md +34 -0
  16. package/dist/sdk.js +71 -14
  17. package/dist/types.d.ts +1 -1
  18. package/dist/vector-backfill-runner.js +9 -7
  19. package/docs/AGENT_OPERATOR_PLAYBOOK.md +6 -5
  20. package/docs/API_REFERENCE.md +9 -5
  21. package/docs/CONFIGURATION.md +15 -5
  22. package/docs/DESIGN_DECISIONS.md +357 -357
  23. package/docs/DEVELOPMENT.md +2 -2
  24. package/docs/INTEGRATIONS.md +9 -2
  25. package/docs/KNOWN_ISSUES_AND_ROADMAP.md +4 -3
  26. package/docs/MEMCODE.md +1 -1
  27. package/docs/MODULES.md +1 -1
  28. package/docs/README.md +1 -1
  29. package/docs/SETUP.md +12 -4
  30. package/docs/hooks-architecture.md +2 -1
  31. package/llms-full.txt +7 -6
  32. package/llms.txt +2 -2
  33. package/package.json +1 -1
  34. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  35. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  36. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  37. package/plugins/copilot/memorix/plugin.json +1 -1
  38. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  39. package/plugins/omp/memorix/package.json +1 -1
  40. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  41. package/plugins/pi/memorix/package.json +1 -1
  42. package/server.json +2 -2
  43. package/src/audit/index.ts +156 -156
  44. package/src/cli/commands/agent-integrations.ts +7 -1
  45. package/src/cli/commands/audit-list.ts +89 -89
  46. package/src/cli/commands/formation.ts +48 -48
  47. package/src/cli/commands/git-hook-install.ts +111 -111
  48. package/src/cli/commands/handoff.ts +54 -54
  49. package/src/cli/commands/hooks-install.ts +2 -0
  50. package/src/cli/commands/hooks-preview.ts +5 -0
  51. package/src/cli/commands/hooks-status.ts +63 -63
  52. package/src/cli/commands/ingest.ts +44 -44
  53. package/src/cli/commands/integrate-shared.ts +15 -15
  54. package/src/cli/commands/integrate.ts +2 -0
  55. package/src/cli/commands/lock.ts +82 -82
  56. package/src/cli/commands/media-image.ts +27 -0
  57. package/src/cli/commands/media.ts +8 -1
  58. package/src/cli/commands/memory.ts +44 -34
  59. package/src/cli/commands/message.ts +104 -104
  60. package/src/cli/commands/operator-shared.ts +62 -18
  61. package/src/cli/commands/poll.ts +58 -58
  62. package/src/cli/commands/purge-all-memory.ts +85 -85
  63. package/src/cli/commands/purge-project-memory.ts +83 -83
  64. package/src/cli/commands/reasoning.ts +118 -118
  65. package/src/cli/commands/setup.ts +57 -2
  66. package/src/cli/commands/skills.ts +114 -114
  67. package/src/cli/commands/task.ts +167 -167
  68. package/src/cli/commands/transfer.ts +47 -47
  69. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  70. package/src/cli/commands/uninstall.ts +20 -0
  71. package/src/cli/tui/ChatView.tsx +234 -234
  72. package/src/cli/tui/CommandBar.tsx +312 -312
  73. package/src/cli/tui/ContextRail.tsx +118 -118
  74. package/src/cli/tui/HeaderBar.tsx +72 -72
  75. package/src/cli/tui/LogoBanner.tsx +51 -51
  76. package/src/cli/tui/Sidebar.tsx +179 -179
  77. package/src/cli/tui/index.ts +41 -41
  78. package/src/cli/tui/markdown-render.tsx +371 -371
  79. package/src/cli/tui/use-mouse.ts +157 -157
  80. package/src/cli/tui/useNavigation.ts +56 -56
  81. package/src/cli/version.ts +7 -7
  82. package/src/cli/workbench.ts +1 -1
  83. package/src/dashboard/project-classification.ts +64 -64
  84. package/src/git/hooks-path.ts +85 -85
  85. package/src/hooks/installers/index.ts +43 -2
  86. package/src/hooks/pattern-detector.ts +173 -173
  87. package/src/hooks/significance-filter.ts +250 -250
  88. package/src/hooks/types.ts +2 -0
  89. package/src/integrations/registry.ts +9 -0
  90. package/src/llm/memory-manager.ts +328 -328
  91. package/src/llm/provider.ts +885 -885
  92. package/src/llm/quality.ts +248 -248
  93. package/src/media/minimax.ts +9 -1
  94. package/src/memory/attribution-guard.ts +249 -249
  95. package/src/memory/disclosure-policy.ts +135 -135
  96. package/src/memory/entity-extractor.ts +197 -197
  97. package/src/memory/formation/evaluate.ts +217 -217
  98. package/src/memory/formation/extract.ts +361 -361
  99. package/src/memory/formation/index.ts +417 -417
  100. package/src/memory/formation/resolve.ts +344 -344
  101. package/src/memory/formation/types.ts +315 -315
  102. package/src/memory/freshness.ts +122 -122
  103. package/src/memory/graph.ts +197 -197
  104. package/src/memory/refs.ts +94 -94
  105. package/src/memory/secret-filter.ts +79 -79
  106. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  107. package/src/orchestrate/adapters/claude.ts +111 -111
  108. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  109. package/src/orchestrate/adapters/codex.ts +41 -41
  110. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  111. package/src/orchestrate/adapters/gemini.ts +42 -42
  112. package/src/orchestrate/adapters/index.ts +73 -73
  113. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  114. package/src/orchestrate/adapters/opencode.ts +47 -47
  115. package/src/orchestrate/adapters/types.ts +77 -77
  116. package/src/orchestrate/capability-router.ts +284 -284
  117. package/src/orchestrate/context-compact.ts +188 -188
  118. package/src/orchestrate/cost-tracker.ts +219 -219
  119. package/src/orchestrate/error-recovery.ts +191 -191
  120. package/src/orchestrate/evidence.ts +140 -140
  121. package/src/orchestrate/ledger.ts +110 -110
  122. package/src/orchestrate/memorix-bridge.ts +343 -343
  123. package/src/orchestrate/output-budget.ts +80 -80
  124. package/src/orchestrate/permission.ts +152 -152
  125. package/src/orchestrate/pipeline-trace.ts +131 -131
  126. package/src/orchestrate/prompt-builder.ts +155 -155
  127. package/src/orchestrate/ring-buffer.ts +37 -37
  128. package/src/orchestrate/task-graph.ts +389 -389
  129. package/src/project/aliases.ts +374 -374
  130. package/src/rules/adapters/trae.ts +56 -56
  131. package/src/rules/syncer.ts +235 -235
  132. package/src/search/query-expansion.ts +52 -52
  133. package/src/server/formation-timeout.ts +27 -27
  134. package/src/server.ts +23 -1
  135. package/src/skills/mini-skills.ts +386 -386
  136. package/src/store/chat-store.ts +119 -119
  137. package/src/store/graph-store.ts +249 -249
  138. package/src/store/mini-skill-store.ts +349 -349
  139. package/src/store/persistence-json.ts +212 -212
  140. package/src/store/persistence.ts +291 -291
  141. package/src/store/project-affinity.ts +195 -195
  142. package/src/team/event-bus.ts +76 -76
  143. package/src/team/file-locks.ts +173 -173
  144. package/src/team/handoff.ts +161 -161
  145. package/src/team/messages.ts +203 -203
  146. package/src/team/poll.ts +132 -132
  147. package/src/team/tasks.ts +211 -211
  148. package/src/types.ts +1 -1
  149. package/src/workspace/mcp-adapters/codex.ts +191 -191
  150. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  151. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  152. package/src/workspace/mcp-adapters/dsh.ts +147 -0
  153. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  154. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  155. package/src/workspace/mcp-adapters/trae.ts +134 -134
  156. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  157. package/src/workspace/sanitizer.ts +60 -60
  158. package/dist/memcode-runtime/docs/images/exy.png +0 -0
@@ -1,83 +1,83 @@
1
- /**
2
- * CLI Command: memorix purge project-memory
3
- *
4
- * Delete memories for the current project only.
5
- */
6
-
7
- import { defineCommand } from 'citty';
8
- import * as p from '@clack/prompts';
9
-
10
- export default defineCommand({
11
- meta: {
12
- name: 'project-memory',
13
- description: 'Delete memories for current project',
14
- },
15
- args: {},
16
- run: async ({}) => {
17
- const os = await import('node:os');
18
- let cwd: string;
19
- try { cwd = process.cwd(); } catch { cwd = os.homedir(); }
20
-
21
- p.intro('Purge Project Memory');
22
-
23
- // Show what will be deleted
24
- console.log('');
25
- console.log('Will delete memories for:');
26
- console.log(` Project: ${cwd}`);
27
- console.log('');
28
- console.log('This will remove all observations, entities, and relations');
29
- console.log('associated with this project from the memory database.');
30
- console.log('');
31
- console.log('Project artifacts (hook files) will be preserved.');
32
- console.log('');
33
-
34
- // Confirm
35
- const confirmed = await p.confirm({
36
- message: 'Continue purge?',
37
- });
38
-
39
- if (p.isCancel(confirmed) || !confirmed) {
40
- p.outro('Purge cancelled.');
41
- return;
42
- }
43
-
44
- // Get project ID
45
- const projectId = getProjectId(cwd);
46
-
47
- // Delete project memories
48
- try {
49
- const {
50
- getProjectObservations,
51
- resolveObservations,
52
- } = await import('../../memory/observations.js');
53
- const { withFreshIndex } = await import('../../memory/freshness.js');
54
- const observations = await withFreshIndex(() => getProjectObservations(projectId));
55
- const ids = observations.map((o) => o.id);
56
-
57
- if (ids.length === 0) {
58
- console.log('No observations found for this project.');
59
- p.outro('No memories to purge.');
60
- return;
61
- }
62
-
63
- const result = await resolveObservations(ids, 'archived');
64
-
65
- console.log(`[OK] Archived ${result.resolved.length} observations for project`);
66
-
67
- p.outro(`Project memory purged. ${result.resolved.length} observations archived.`);
68
- } catch (err) {
69
- console.error(`[ERROR] Failed to purge project memory: ${err}`);
70
- p.outro('Purge failed.');
71
- }
72
- },
73
- });
74
-
75
- function getProjectId(projectRoot: string): string {
76
- try {
77
- const { detectProject } = require('../project/detector.js');
78
- const project = detectProject(projectRoot);
79
- if (project) return project.id;
80
- } catch { /* fallback below */ }
81
- const normalized = projectRoot.replace(/\\/g, '/');
82
- return `untracked/${normalized}`;
83
- }
1
+ /**
2
+ * CLI Command: memorix purge project-memory
3
+ *
4
+ * Delete memories for the current project only.
5
+ */
6
+
7
+ import { defineCommand } from 'citty';
8
+ import * as p from '@clack/prompts';
9
+
10
+ export default defineCommand({
11
+ meta: {
12
+ name: 'project-memory',
13
+ description: 'Delete memories for current project',
14
+ },
15
+ args: {},
16
+ run: async ({}) => {
17
+ const os = await import('node:os');
18
+ let cwd: string;
19
+ try { cwd = process.cwd(); } catch { cwd = os.homedir(); }
20
+
21
+ p.intro('Purge Project Memory');
22
+
23
+ // Show what will be deleted
24
+ console.log('');
25
+ console.log('Will delete memories for:');
26
+ console.log(` Project: ${cwd}`);
27
+ console.log('');
28
+ console.log('This will remove all observations, entities, and relations');
29
+ console.log('associated with this project from the memory database.');
30
+ console.log('');
31
+ console.log('Project artifacts (hook files) will be preserved.');
32
+ console.log('');
33
+
34
+ // Confirm
35
+ const confirmed = await p.confirm({
36
+ message: 'Continue purge?',
37
+ });
38
+
39
+ if (p.isCancel(confirmed) || !confirmed) {
40
+ p.outro('Purge cancelled.');
41
+ return;
42
+ }
43
+
44
+ // Get project ID
45
+ const projectId = getProjectId(cwd);
46
+
47
+ // Delete project memories
48
+ try {
49
+ const {
50
+ getProjectObservations,
51
+ resolveObservations,
52
+ } = await import('../../memory/observations.js');
53
+ const { withFreshIndex } = await import('../../memory/freshness.js');
54
+ const observations = await withFreshIndex(() => getProjectObservations(projectId));
55
+ const ids = observations.map((o) => o.id);
56
+
57
+ if (ids.length === 0) {
58
+ console.log('No observations found for this project.');
59
+ p.outro('No memories to purge.');
60
+ return;
61
+ }
62
+
63
+ const result = await resolveObservations(ids, 'archived');
64
+
65
+ console.log(`[OK] Archived ${result.resolved.length} observations for project`);
66
+
67
+ p.outro(`Project memory purged. ${result.resolved.length} observations archived.`);
68
+ } catch (err) {
69
+ console.error(`[ERROR] Failed to purge project memory: ${err}`);
70
+ p.outro('Purge failed.');
71
+ }
72
+ },
73
+ });
74
+
75
+ function getProjectId(projectRoot: string): string {
76
+ try {
77
+ const { detectProject } = require('../project/detector.js');
78
+ const project = detectProject(projectRoot);
79
+ if (project) return project.id;
80
+ } catch { /* fallback below */ }
81
+ const normalized = projectRoot.replace(/\\/g, '/');
82
+ return `untracked/${normalized}`;
83
+ }
@@ -1,6 +1,6 @@
1
- import { defineCommand } from 'citty';
2
- import { compactSearch } from '../../compact/engine.js';
3
- import { storeObservation } from '../../memory/observations.js';
1
+ import { defineCommand } from 'citty';
2
+ import { compactSearch } from '../../compact/engine.js';
3
+ import { storeObservation } from '../../memory/observations.js';
4
4
  import {
5
5
  emitError,
6
6
  emitResult,
@@ -9,65 +9,65 @@ import {
9
9
  parsePositiveInt,
10
10
  resolveCliWriteScope,
11
11
  } from './operator-shared.js';
12
-
13
- export default defineCommand({
14
- meta: {
15
- name: 'reasoning',
16
- description: 'Store and search decision rationale from the operator CLI',
17
- },
18
- args: {
19
- entity: { type: 'string', description: 'Entity this reasoning applies to' },
20
- decision: { type: 'string', description: 'Decision that was made' },
21
- rationale: { type: 'string', description: 'Why this approach was chosen' },
22
- alternatives: { type: 'string', description: 'Comma-separated alternatives that were considered' },
23
- constraints: { type: 'string', description: 'Comma-separated constraints' },
24
- expectedOutcome: { type: 'string', description: 'Expected outcome from this decision' },
25
- risks: { type: 'string', description: 'Comma-separated risks or downsides' },
26
- concepts: { type: 'string', description: 'Comma-separated related concepts' },
27
- files: { type: 'string', description: 'Comma-separated file list' },
28
- relatedCommits: { type: 'string', description: 'Comma-separated related commit hashes' },
12
+
13
+ export default defineCommand({
14
+ meta: {
15
+ name: 'reasoning',
16
+ description: 'Store and search decision rationale from the operator CLI',
17
+ },
18
+ args: {
19
+ entity: { type: 'string', description: 'Entity this reasoning applies to' },
20
+ decision: { type: 'string', description: 'Decision that was made' },
21
+ rationale: { type: 'string', description: 'Why this approach was chosen' },
22
+ alternatives: { type: 'string', description: 'Comma-separated alternatives that were considered' },
23
+ constraints: { type: 'string', description: 'Comma-separated constraints' },
24
+ expectedOutcome: { type: 'string', description: 'Expected outcome from this decision' },
25
+ risks: { type: 'string', description: 'Comma-separated risks or downsides' },
26
+ concepts: { type: 'string', description: 'Comma-separated related concepts' },
27
+ files: { type: 'string', description: 'Comma-separated file list' },
28
+ relatedCommits: { type: 'string', description: 'Comma-separated related commit hashes' },
29
29
  relatedEntities: { type: 'string', description: 'Comma-separated related entity names' },
30
30
  visibility: { type: 'string', description: 'Reasoning visibility: project (default), personal, or team' },
31
- query: { type: 'string', description: 'Search query for reasoning traces' },
32
- limit: { type: 'string', description: 'Search result limit' },
33
- scope: { type: 'string', description: 'project or global scope for search' },
34
- json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
35
- },
36
- run: async ({ args }) => {
37
- const action = (args._ as string[])?.[0] || '';
38
- const asJson = !!args.json;
39
-
40
- try {
31
+ query: { type: 'string', description: 'Search query for reasoning traces' },
32
+ limit: { type: 'string', description: 'Search result limit' },
33
+ scope: { type: 'string', description: 'project or global scope for search' },
34
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
35
+ },
36
+ run: async ({ args }) => {
37
+ const action = (args._ as string[])?.[0] || '';
38
+ const asJson = !!args.json;
39
+
40
+ try {
41
41
  const { project, reader, identity } = await getCliProjectContext({ searchIndex: action === 'search' });
42
-
43
- switch (action) {
44
- case 'store': {
45
- const entityName = (args.entity as string | undefined)?.trim();
46
- const decision = (args.decision as string | undefined)?.trim();
47
- const rationale = (args.rationale as string | undefined)?.trim();
48
- if (!entityName || !decision || !rationale) {
49
- emitError('entity, decision, and rationale are required for "memorix reasoning store"', asJson);
50
- return;
51
- }
52
-
53
- const alternatives = parseCsvList(args.alternatives as string | undefined);
54
- const constraints = parseCsvList(args.constraints as string | undefined);
55
- const risks = parseCsvList(args.risks as string | undefined);
56
- const concepts = parseCsvList(args.concepts as string | undefined);
57
- const filesModified = parseCsvList(args.files as string | undefined);
58
- const relatedCommits = parseCsvList(args.relatedCommits as string | undefined);
59
- const relatedEntities = parseCsvList(args.relatedEntities as string | undefined);
60
-
61
- const narrativeParts: string[] = [rationale];
62
- if (alternatives.length > 0) {
63
- narrativeParts.push(`Alternatives considered: ${alternatives.join('; ')}`);
64
- }
65
- if (constraints.length > 0) {
66
- narrativeParts.push(`Constraints: ${constraints.join('; ')}`);
67
- }
68
- if (args.expectedOutcome) {
69
- narrativeParts.push(`Expected outcome: ${String(args.expectedOutcome).trim()}`);
70
- }
42
+
43
+ switch (action) {
44
+ case 'store': {
45
+ const entityName = (args.entity as string | undefined)?.trim();
46
+ const decision = (args.decision as string | undefined)?.trim();
47
+ const rationale = (args.rationale as string | undefined)?.trim();
48
+ if (!entityName || !decision || !rationale) {
49
+ emitError('entity, decision, and rationale are required for "memorix reasoning store"', asJson);
50
+ return;
51
+ }
52
+
53
+ const alternatives = parseCsvList(args.alternatives as string | undefined);
54
+ const constraints = parseCsvList(args.constraints as string | undefined);
55
+ const risks = parseCsvList(args.risks as string | undefined);
56
+ const concepts = parseCsvList(args.concepts as string | undefined);
57
+ const filesModified = parseCsvList(args.files as string | undefined);
58
+ const relatedCommits = parseCsvList(args.relatedCommits as string | undefined);
59
+ const relatedEntities = parseCsvList(args.relatedEntities as string | undefined);
60
+
61
+ const narrativeParts: string[] = [rationale];
62
+ if (alternatives.length > 0) {
63
+ narrativeParts.push(`Alternatives considered: ${alternatives.join('; ')}`);
64
+ }
65
+ if (constraints.length > 0) {
66
+ narrativeParts.push(`Constraints: ${constraints.join('; ')}`);
67
+ }
68
+ if (args.expectedOutcome) {
69
+ narrativeParts.push(`Expected outcome: ${String(args.expectedOutcome).trim()}`);
70
+ }
71
71
  if (risks.length > 0) {
72
72
  narrativeParts.push(`Risks: ${risks.join('; ')}`);
73
73
  }
@@ -77,70 +77,70 @@ export default defineCommand({
77
77
  );
78
78
 
79
79
  const result = await storeObservation({
80
- entityName,
81
- type: 'reasoning',
82
- title: decision,
83
- narrative: narrativeParts.join('. '),
84
- facts: [
85
- `Decision: ${decision}`,
86
- ...alternatives.map((item) => `Alternative: ${item}`),
87
- ...constraints.map((item) => `Constraint: ${item}`),
88
- ...(args.expectedOutcome ? [`Expected outcome: ${String(args.expectedOutcome).trim()}`] : []),
89
- ...risks.map((item) => `Risk: ${item}`),
90
- ...relatedCommits.map((item) => `Commit: ${item}`),
91
- ...relatedEntities.map((item) => `Related entity: ${item}`),
92
- ],
93
- concepts,
94
- filesModified,
95
- relatedCommits,
96
- relatedEntities,
80
+ entityName,
81
+ type: 'reasoning',
82
+ title: decision,
83
+ narrative: narrativeParts.join('. '),
84
+ facts: [
85
+ `Decision: ${decision}`,
86
+ ...alternatives.map((item) => `Alternative: ${item}`),
87
+ ...constraints.map((item) => `Constraint: ${item}`),
88
+ ...(args.expectedOutcome ? [`Expected outcome: ${String(args.expectedOutcome).trim()}`] : []),
89
+ ...risks.map((item) => `Risk: ${item}`),
90
+ ...relatedCommits.map((item) => `Commit: ${item}`),
91
+ ...relatedEntities.map((item) => `Related entity: ${item}`),
92
+ ],
93
+ concepts,
94
+ filesModified,
95
+ relatedCommits,
96
+ relatedEntities,
97
97
  projectId: project.id,
98
98
  source: 'manual',
99
99
  ...writeScope,
100
- });
101
-
102
- emitResult(
103
- { project, observation: result.observation, upserted: result.upserted },
104
- `${result.upserted ? 'Updated' : 'Stored'} reasoning trace #${result.observation.id}: ${decision}`,
105
- asJson,
106
- );
107
- return;
108
- }
109
-
110
- case 'search': {
111
- const query = (args.query as string | undefined)?.trim();
112
- if (!query) {
113
- emitError('query is required for "memorix reasoning search"', asJson);
114
- return;
115
- }
116
- const scope = (args.scope as string | undefined) === 'global' ? 'global' : 'project';
117
- const limit = parsePositiveInt(args.limit as string | undefined, 10);
118
- const result = await compactSearch({
119
- query,
120
- limit,
100
+ });
101
+
102
+ emitResult(
103
+ { project, observation: result.observation, upserted: result.upserted },
104
+ `${result.upserted ? 'Updated' : 'Stored'} reasoning trace #${result.observation.id}: ${decision}`,
105
+ asJson,
106
+ );
107
+ return;
108
+ }
109
+
110
+ case 'search': {
111
+ const query = (args.query as string | undefined)?.trim();
112
+ if (!query) {
113
+ emitError('query is required for "memorix reasoning search"', asJson);
114
+ return;
115
+ }
116
+ const scope = (args.scope as string | undefined) === 'global' ? 'global' : 'project';
117
+ const limit = parsePositiveInt(args.limit as string | undefined, 10);
118
+ const result = await compactSearch({
119
+ query,
120
+ limit,
121
121
  type: 'reasoning',
122
122
  projectId: scope === 'global' ? undefined : project.id,
123
123
  status: 'active',
124
124
  reader: scope === 'global' ? {} : reader,
125
- });
126
- emitResult(
127
- { project, scope, entries: result.entries },
128
- result.entries.length === 0 ? 'No reasoning traces found.' : `[REASONING] Reasoning traces\n${result.formatted}`,
129
- asJson,
130
- );
131
- return;
132
- }
133
-
134
- default:
135
- console.log('Memorix Reasoning Commands');
136
- console.log('');
137
- console.log('Usage:');
125
+ });
126
+ emitResult(
127
+ { project, scope, entries: result.entries },
128
+ result.entries.length === 0 ? 'No reasoning traces found.' : `[REASONING] Reasoning traces\n${result.formatted}`,
129
+ asJson,
130
+ );
131
+ return;
132
+ }
133
+
134
+ default:
135
+ console.log('Memorix Reasoning Commands');
136
+ console.log('');
137
+ console.log('Usage:');
138
138
  console.log(' memorix reasoning store --entity auth --decision "Use SQLite" --rationale "..." [--visibility project|personal|team]');
139
- console.log(' memorix reasoning search --query "why sqlite" [--scope project|global --limit 10]');
140
- }
141
- } catch (error) {
142
- emitError(error instanceof Error ? error.message : String(error), asJson);
143
- }
144
- },
145
- });
146
-
139
+ console.log(' memorix reasoning search --query "why sqlite" [--scope project|global --limit 10]');
140
+ }
141
+ } catch (error) {
142
+ emitError(error instanceof Error ? error.message : String(error), asJson);
143
+ }
144
+ },
145
+ });
146
+
@@ -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
  }
@@ -699,6 +702,55 @@ function mergeYamlMcpConfig(existingContent: string | null, generatedContent: st
699
702
  return stringifyYaml(merged, { lineWidth: 0 });
700
703
  }
701
704
 
705
+ /**
706
+ * Merge the generated DeepSeek Harness patch rows into an existing
707
+ * `cordis.patch.yml` without clobbering unrelated user patches.
708
+ *
709
+ * DSH patch files are lists of Cordis patch operations. Memorix owns exactly
710
+ * the `@deepseek-ai/dsh-mcp-client` row whose config.serverName is `memorix`;
711
+ * every other operation is user-owned and must survive the merge. When the
712
+ * existing file is not a parseable list, the generated rows are appended to
713
+ * the raw content instead of overwriting it.
714
+ */
715
+ function mergeDshPatchConfig(existingContent: string | null, generatedContent: string): string {
716
+ const generatedTrimmed = generatedContent.trimEnd();
717
+ if (!existingContent?.trim()) {
718
+ return generatedTrimmed.endsWith('\n') ? generatedTrimmed : `${generatedTrimmed}\n`;
719
+ }
720
+
721
+ const isMemorixInsertRow = (row: unknown): boolean => {
722
+ if (!row || typeof row !== 'object' || Array.isArray(row)) return false;
723
+ const record = row as Record<string, unknown>;
724
+ if (record.name !== '@deepseek-ai/dsh-mcp-client') return false;
725
+ const config = record.config;
726
+ return Boolean(
727
+ config && typeof config === 'object' && !Array.isArray(config)
728
+ && (config as Record<string, unknown>).serverName === 'memorix',
729
+ );
730
+ };
731
+
732
+ const opContainsMemorixRow = (op: unknown): boolean => {
733
+ if (!op || typeof op !== 'object' || Array.isArray(op)) return false;
734
+ const insert = (op as Record<string, unknown>).insert;
735
+ return Array.isArray(insert) && insert.some(isMemorixInsertRow);
736
+ };
737
+
738
+ let existingDocument: unknown;
739
+ try {
740
+ existingDocument = parseYaml(existingContent);
741
+ } catch {
742
+ return `${existingContent.trimEnd()}\n\n${generatedTrimmed}\n`;
743
+ }
744
+ if (!Array.isArray(existingDocument)) {
745
+ // A scalar or mapping document is not a patch list — never rewrite it.
746
+ return `${existingContent.trimEnd()}\n\n${generatedTrimmed}\n`;
747
+ }
748
+
749
+ const kept = existingDocument.filter((op) => !opContainsMemorixRow(op));
750
+ const merged = [...kept, ...(parseYaml(generatedContent) as unknown[])];
751
+ return stringifyYaml(merged, { lineWidth: 0 }) + '\n';
752
+ }
753
+
702
754
  export async function installMcpConfig(options: {
703
755
  agent: McpConfigAgent;
704
756
  projectRoot?: string;
@@ -725,7 +777,9 @@ export async function installMcpConfig(options: {
725
777
  ? mergeTomlMcpConfig(existingContent, generated)
726
778
  : options.agent === 'hermes'
727
779
  ? mergeYamlMcpConfig(existingContent, generated)
728
- : mergeJsonMcpConfig(existingContent, generated);
780
+ : options.agent === 'dsh'
781
+ ? mergeDshPatchConfig(existingContent, generated)
782
+ : mergeJsonMcpConfig(existingContent, generated);
729
783
 
730
784
  await mkdir(path.dirname(configPath), { recursive: true });
731
785
  await writeFile(configPath, content, 'utf-8');
@@ -1603,7 +1657,7 @@ export default defineCommand({
1603
1657
  args: {
1604
1658
  agent: {
1605
1659
  type: 'string',
1606
- description: 'Target agent (claude, codex, codebuddy, opencode, cursor, windsurf, copilot, gemini-cli, antigravity, openclaw, hermes, omp, kiro, trae, pi, all)',
1660
+ description: 'Target agent (claude, codex, codebuddy, opencode, cursor, windsurf, copilot, gemini-cli, antigravity, openclaw, hermes, omp, kiro, trae, dsh, pi, all)',
1607
1661
  required: false,
1608
1662
  },
1609
1663
  mcp: {
@@ -1677,6 +1731,7 @@ export default defineCommand({
1677
1731
  { value: 'hermes', label: 'Hermes Agent', hint: 'plugin + hooks + commands + skills + MCP' },
1678
1732
  { value: 'omp', label: 'Oh-my-Pi', hint: 'package + extension + command + MCP' },
1679
1733
  { value: 'pi', label: 'Pi coding agent', hint: 'package + extension + skill' },
1734
+ { value: 'dsh', label: 'DeepSeek Harness', hint: 'MCP patch + AGENTS.md + skills' },
1680
1735
  { value: 'opencode', label: 'OpenCode', hint: 'local plugin + AGENTS.md + MCP' },
1681
1736
  { value: 'windsurf', label: 'Windsurf', hint: 'rules + hooks + MCP' },
1682
1737
  { value: 'all', label: 'All supported agents', hint: 'install detected-compatible files' },