memorix 1.4.4 → 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 (36) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +9 -2
  3. package/README.zh-CN.md +9 -2
  4. package/dist/cli/index.js +348 -127
  5. package/dist/index.js +35 -5
  6. package/dist/memcode-runtime/CHANGELOG.md +15 -0
  7. package/dist/sdk.js +34 -4
  8. package/dist/types.d.ts +1 -1
  9. package/docs/AGENT_OPERATOR_PLAYBOOK.md +3 -2
  10. package/docs/INTEGRATIONS.md +9 -2
  11. package/docs/KNOWN_ISSUES_AND_ROADMAP.md +1 -0
  12. package/docs/MEMCODE.md +1 -1
  13. package/docs/SETUP.md +9 -1
  14. package/llms-full.txt +5 -4
  15. package/llms.txt +1 -1
  16. package/package.json +1 -1
  17. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  18. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  19. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  20. package/plugins/copilot/memorix/plugin.json +1 -1
  21. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  22. package/plugins/omp/memorix/package.json +1 -1
  23. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  24. package/plugins/pi/memorix/package.json +1 -1
  25. package/server.json +2 -2
  26. package/src/cli/commands/agent-integrations.ts +7 -1
  27. package/src/cli/commands/hooks-install.ts +2 -0
  28. package/src/cli/commands/hooks-preview.ts +5 -0
  29. package/src/cli/commands/integrate.ts +2 -0
  30. package/src/cli/commands/setup.ts +57 -2
  31. package/src/cli/commands/uninstall.ts +20 -0
  32. package/src/hooks/installers/index.ts +43 -2
  33. package/src/hooks/types.ts +2 -0
  34. package/src/integrations/registry.ts +9 -0
  35. package/src/types.ts +1 -1
  36. package/src/workspace/mcp-adapters/dsh.ts +147 -0
@@ -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' },
@@ -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
 
@@ -360,6 +360,9 @@ const AGENT_SKILL_DIRS: Partial<Record<AgentName, { project: string; global?: st
360
360
  kiro: { project: path.join('.kiro', 'skills'), global: path.join('.kiro', 'skills') },
361
361
  opencode: { project: path.join('.opencode', 'skills'), global: path.join('.config', 'opencode', 'skills') },
362
362
  trae: { project: path.join('.trae', 'skills'), global: path.join('.trae', 'skills') },
363
+ // DSH's user skill root is <harness home>/skills; the global root below is
364
+ // resolved to $DSH_HOME (or ~/.dsh) for dsh by installOfficialSkillsForAgent.
365
+ dsh: { project: path.join('.dsh', 'skills'), global: 'skills' },
363
366
  };
364
367
 
365
368
  const PACKAGE_OWNED_HOOK_AGENTS = new Set<AgentName>(['openclaw', 'hermes', 'omp']);
@@ -384,7 +387,11 @@ async function installOfficialSkillsForAgent(
384
387
  const relativeDir = global ? dirs?.global : dirs?.project;
385
388
  if (!relativeDir) return [];
386
389
 
387
- const root = global ? os.homedir() : projectRoot;
390
+ const root = global
391
+ ? (agent === 'dsh'
392
+ ? (process.env.DSH_HOME?.trim() || path.join(os.homedir(), '.dsh'))
393
+ : os.homedir())
394
+ : projectRoot;
388
395
  const skillPaths: string[] = [];
389
396
  for (const skillEntry of OFFICIAL_MEMORIX_SKILLS) {
390
397
  const skillPath = path.join(root, relativeDir, skillEntry.name, 'SKILL.md');
@@ -626,6 +633,9 @@ export function getProjectConfigPath(agent: AgentName, projectRoot: string): str
626
633
  case 'trae':
627
634
  // Trae has no hooks system — only rules (.trae/rules/project_rules.md)
628
635
  return path.join(projectRoot, '.trae', 'rules', 'project_rules.md');
636
+ case 'dsh':
637
+ // DeepSeek Harness has no hooks system — only guidance (AGENTS.md)
638
+ return path.join(projectRoot, 'AGENTS.md');
629
639
  case 'opencode':
630
640
  // OpenCode uses plugin files for hooks
631
641
  return path.join(projectRoot, '.opencode', 'plugins', 'memorix.js');
@@ -684,6 +694,9 @@ export function getGlobalConfigPath(agent: AgentName): string {
684
694
  return path.join(home, '.omp', 'agent', 'packages', 'memorix', 'extensions', 'memorix.js');
685
695
  case 'trae':
686
696
  return path.join(home, '.trae', 'rules', 'project_rules.md');
697
+ case 'dsh':
698
+ // DSH reads the user-global AGENTS.md from its harness home.
699
+ return path.join(process.env.DSH_HOME?.trim() || path.join(home, '.dsh'), 'AGENTS.md');
687
700
  default:
688
701
  return path.join(home, '.memorix', 'hooks.json');
689
702
  }
@@ -709,6 +722,9 @@ export function getAgentRulesPath(agent: AgentName, root: string, global = false
709
722
  return path.join(root, '.gemini', 'GEMINI.md');
710
723
  case 'trae':
711
724
  return path.join(root, '.trae', 'rules', 'project_rules.md');
725
+ case 'dsh':
726
+ // The user-global AGENTS.md lives in the harness home DSH reads.
727
+ return path.join(process.env.DSH_HOME?.trim() || path.join(root, '.dsh'), 'AGENTS.md');
712
728
  default:
713
729
  return path.join(root, '.agent', 'rules', 'memorix.md');
714
730
  }
@@ -734,6 +750,8 @@ export function getAgentRulesPath(agent: AgentName, root: string, global = false
734
750
  return path.join(root, 'GEMINI.md');
735
751
  case 'trae':
736
752
  return path.join(root, '.trae', 'rules', 'project_rules.md');
753
+ case 'dsh':
754
+ return path.join(root, 'AGENTS.md');
737
755
  default:
738
756
  return path.join(root, '.agent', 'rules', 'memorix.md');
739
757
  }
@@ -842,6 +860,13 @@ export async function detectInstalledAgents(): Promise<AgentName[]> {
842
860
  agents.push('trae');
843
861
  } catch { /* not installed */ }
844
862
 
863
+ // Check for DeepSeek Harness (its home dir or an explicit DSH_HOME)
864
+ const dshDir = process.env.DSH_HOME?.trim() || path.join(home, '.dsh');
865
+ try {
866
+ await fs.access(dshDir);
867
+ agents.push('dsh');
868
+ } catch { /* not installed */ }
869
+
845
870
  return agents;
846
871
  }
847
872
 
@@ -947,6 +972,22 @@ export async function installHooks(
947
972
  generated: { note: 'Trae has no hooks system, only rules (.trae/rules/project_rules.md) installed' },
948
973
  };
949
974
  }
975
+ case 'dsh':
976
+ // DeepSeek Harness has no hook system — install AGENTS.md guidance and
977
+ // skills; the MCP row is installed by `memorix setup --agent dsh`.
978
+ {
979
+ const rulesPath = await installAgentRules(agent, projectRoot, global);
980
+ const skillPaths = await installOfficialSkillsForAgent(agent, projectRoot, global);
981
+ return {
982
+ agent,
983
+ configPath: rulesPath,
984
+ events: [],
985
+ generated: {
986
+ note: 'DeepSeek Harness has no hook system — installed AGENTS.md guidance and skills; the MCP row is installed by `memorix setup --agent dsh`.',
987
+ ...(skillPaths.length > 0 ? { skillPaths, skillPath: skillPaths[0] } : {}),
988
+ },
989
+ };
990
+ }
950
991
  case 'opencode': {
951
992
  // OpenCode uses JS plugin files for hooks
952
993
  const pluginContent = generateOpenCodePlugin();
@@ -1127,7 +1168,7 @@ async function installAgentRules(agent: AgentName, projectRoot: string, global =
1127
1168
  try {
1128
1169
  await fs.mkdir(path.dirname(rulesPath), { recursive: true });
1129
1170
 
1130
- if (agent === 'claude' || agent === 'codex' || agent === 'opencode' || agent === 'antigravity' || agent === 'gemini-cli') {
1171
+ if (agent === 'claude' || agent === 'codex' || agent === 'opencode' || agent === 'antigravity' || agent === 'gemini-cli' || agent === 'dsh') {
1131
1172
  // For shared context files (CLAUDE.md / AGENTS.md / GEMINI.md), append rather than overwrite.
1132
1173
  try {
1133
1174
  const existing = await fs.readFile(rulesPath, 'utf-8');
@@ -41,6 +41,7 @@ export type AgentName =
41
41
  | 'hermes'
42
42
  | 'opencode'
43
43
  | 'trae'
44
+ | 'dsh'
44
45
  | 'pi'
45
46
  | 'omp';
46
47
 
@@ -61,6 +62,7 @@ export const AGENT_SUPPORT_TIER: Record<AgentName, SupportTier> = {
61
62
  hermes: 'community',
62
63
  opencode: 'community',
63
64
  trae: 'community',
65
+ dsh: 'community',
64
66
  pi: 'community',
65
67
  omp: 'community',
66
68
  antigravity: 'community',
@@ -150,6 +150,15 @@ export const MEMORIX_INTEGRATIONS: MemorixIntegration[] = [
150
150
  install: 'memorix setup --agent trae',
151
151
  surfaces: ['MCP', 'rules'],
152
152
  },
153
+ {
154
+ agent: 'dsh',
155
+ name: 'DeepSeek Harness',
156
+ entry: 'official-config',
157
+ status: 'ready',
158
+ install: 'memorix setup --agent dsh',
159
+ surfaces: ['MCP', 'AGENTS.md', 'skills'],
160
+ note: 'DeepSeek Harness support writes a @deepseek-ai/dsh-mcp-client row into $DSH_HOME/cordis.patch.yml (default ~/.dsh/cordis.patch.yml), guidance into the harness AGENTS.md, and skills under $DSH_HOME/skills.',
161
+ },
153
162
  {
154
163
  agent: 'memcode',
155
164
  name: 'memcode',
package/src/types.ts CHANGED
@@ -654,7 +654,7 @@ export interface MemoryRef {
654
654
  /** MCP config format adapter interface */
655
655
  export interface MCPConfigAdapter {
656
656
  /** Setup-only adapters may support a host that does not participate in workspace sync. */
657
- readonly source: AgentTarget | 'codebuddy';
657
+ readonly source: AgentTarget | 'codebuddy' | 'dsh';
658
658
  /** Parse MCP server entries from a config file */
659
659
  parse(content: string): MCPServerEntry[];
660
660
  /** Generate config file content from MCP server entries */
@@ -0,0 +1,147 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
4
+ import type { MCPConfigAdapter, MCPServerEntry } from '../../types.js';
5
+
6
+ /**
7
+ * DeepSeek Harness (DSH) MCP Configuration Adapter.
8
+ *
9
+ * DSH composes its runtime from Cordis patch layers. External MCP servers are
10
+ * registered as rows of the `@deepseek-ai/dsh-mcp-client` plugin inside a
11
+ * patch list. The user-level layer that applies to every profile lives at:
12
+ *
13
+ * $DSH_HOME/cordis.patch.yml (default: ~/.dsh/cordis.patch.yml)
14
+ *
15
+ * Row shape follows DSH's own shipped Memorix reference
16
+ * (examples/mcp-memory/memorix.cordis.yml in deepseek-ai/deepseek-harness):
17
+ *
18
+ * - insert:
19
+ * - id: memory-memorix
20
+ * name: '@deepseek-ai/dsh-mcp-client'
21
+ * config:
22
+ * serverName: memorix
23
+ * transport: stdio
24
+ * command: memorix
25
+ * args: [serve]
26
+ *
27
+ * The `cwd` key is deliberately omitted so the MCP child inherits the DSH
28
+ * process working directory — the workspace DSH was launched from — which is
29
+ * what keeps Memorix bound to the current Git project.
30
+ *
31
+ * Source: https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/mcp/mcp-client
32
+ */
33
+
34
+ const MCP_CLIENT_PLUGIN = '@deepseek-ai/dsh-mcp-client';
35
+ const MEMORIX_ROW_ID = 'memory-memorix';
36
+
37
+ function resolveDshHome(): string {
38
+ const configured = process.env.DSH_HOME?.trim();
39
+ if (configured) return configured;
40
+ return join(homedir(), '.dsh');
41
+ }
42
+
43
+ function isMemorixRow(row: unknown): row is Record<string, unknown> {
44
+ if (!row || typeof row !== 'object' || Array.isArray(row)) return false;
45
+ const record = row as Record<string, unknown>;
46
+ if (record.name !== MCP_CLIENT_PLUGIN) return false;
47
+ const config = record.config;
48
+ return Boolean(
49
+ config && typeof config === 'object' && !Array.isArray(config)
50
+ && (config as Record<string, unknown>).serverName === 'memorix',
51
+ );
52
+ }
53
+
54
+ function rowToEntry(row: Record<string, unknown>): MCPServerEntry | null {
55
+ const config = row.config as Record<string, unknown>;
56
+ if (!config || typeof config !== 'object') return null;
57
+ const entry: MCPServerEntry = {
58
+ name: 'memorix',
59
+ command: typeof config.command === 'string' ? config.command : '',
60
+ args: Array.isArray(config.args) ? config.args.filter((arg): arg is string => typeof arg === 'string') : [],
61
+ };
62
+ if (typeof config.url === 'string' && config.url) entry.url = config.url;
63
+ if (config.env && typeof config.env === 'object' && !Array.isArray(config.env)) {
64
+ const env: Record<string, string> = {};
65
+ for (const [key, value] of Object.entries(config.env as Record<string, unknown>)) {
66
+ if (typeof value === 'string') env[key] = value;
67
+ }
68
+ if (Object.keys(env).length > 0) entry.env = env;
69
+ }
70
+ if (config.disabled === true) entry.disabled = true;
71
+ return entry;
72
+ }
73
+
74
+ /** Flatten one parsed patch document into its row list, tolerating both
75
+ * `- insert: [...]` operations and bare row lists. */
76
+ function patchRows(document: unknown): Record<string, unknown>[] {
77
+ const rows: Record<string, unknown>[] = [];
78
+ if (!Array.isArray(document)) return rows;
79
+ for (const item of document) {
80
+ if (!item || typeof item !== 'object' || Array.isArray(item)) continue;
81
+ const record = item as Record<string, unknown>;
82
+ if (Array.isArray(record.insert)) {
83
+ for (const row of record.insert) {
84
+ if (row && typeof row === 'object' && !Array.isArray(row)) {
85
+ rows.push(row as Record<string, unknown>);
86
+ }
87
+ }
88
+ continue;
89
+ }
90
+ rows.push(record);
91
+ }
92
+ return rows;
93
+ }
94
+
95
+ export class DshMCPAdapter implements MCPConfigAdapter {
96
+ readonly source = 'dsh' as const;
97
+
98
+ parse(content: string): MCPServerEntry[] {
99
+ if (!content.trim()) return [];
100
+ let document: unknown;
101
+ try {
102
+ document = parseYaml(content);
103
+ } catch {
104
+ return [];
105
+ }
106
+ return patchRows(document)
107
+ .filter(isMemorixRow)
108
+ .map(rowToEntry)
109
+ .filter((entry): entry is MCPServerEntry => entry !== null);
110
+ }
111
+
112
+ generate(servers: MCPServerEntry[]): string {
113
+ const rows = servers
114
+ .filter((server) => server.name === 'memorix')
115
+ .map((server) => {
116
+ const config: Record<string, unknown> = {
117
+ serverName: server.name,
118
+ };
119
+ if (server.url) {
120
+ config.transport = 'streamable-http';
121
+ config.url = server.url;
122
+ if (server.headers && Object.keys(server.headers).length > 0) config.headers = server.headers;
123
+ } else {
124
+ config.transport = 'stdio';
125
+ config.command = server.command;
126
+ if (server.args && server.args.length > 0) config.args = server.args;
127
+ if (server.env && Object.keys(server.env).length > 0) config.env = server.env;
128
+ }
129
+ const row: Record<string, unknown> = {
130
+ id: MEMORIX_ROW_ID,
131
+ name: MCP_CLIENT_PLUGIN,
132
+ config,
133
+ };
134
+ if (server.disabled === true) row.disabled = true;
135
+ return row;
136
+ });
137
+
138
+ if (rows.length === 0) return '';
139
+ return stringifyYaml([{ insert: rows }], { lineWidth: 0 }) + '\n';
140
+ }
141
+
142
+ getConfigPath(_projectRoot?: string): string {
143
+ return join(resolveDshHome(), 'cordis.patch.yml');
144
+ }
145
+ }
146
+
147
+ export { MEMORIX_ROW_ID };