@synth-coder/memhub 0.2.2 → 0.2.4

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 (116) hide show
  1. package/.factory/commands/opsx-apply.md +150 -0
  2. package/.factory/commands/opsx-archive.md +155 -0
  3. package/.factory/commands/opsx-explore.md +171 -0
  4. package/.factory/commands/opsx-propose.md +104 -0
  5. package/.factory/skills/openspec-apply-change/SKILL.md +156 -0
  6. package/.factory/skills/openspec-archive-change/SKILL.md +114 -0
  7. package/.factory/skills/openspec-explore/SKILL.md +288 -0
  8. package/.factory/skills/openspec-propose/SKILL.md +110 -0
  9. package/.github/workflows/ci.yml +48 -12
  10. package/.github/workflows/release.yml +67 -0
  11. package/AGENTS.md +158 -17
  12. package/README.md +147 -66
  13. package/README.zh-CN.md +75 -23
  14. package/dist/src/cli/agents/claude-code.d.ts +5 -0
  15. package/dist/src/cli/agents/claude-code.d.ts.map +1 -0
  16. package/dist/src/cli/agents/claude-code.js +14 -0
  17. package/dist/src/cli/agents/claude-code.js.map +1 -0
  18. package/dist/src/cli/agents/cline.d.ts +5 -0
  19. package/dist/src/cli/agents/cline.d.ts.map +1 -0
  20. package/dist/src/cli/agents/cline.js +14 -0
  21. package/dist/src/cli/agents/cline.js.map +1 -0
  22. package/dist/src/cli/agents/codex.d.ts +5 -0
  23. package/dist/src/cli/agents/codex.d.ts.map +1 -0
  24. package/dist/src/cli/agents/codex.js +14 -0
  25. package/dist/src/cli/agents/codex.js.map +1 -0
  26. package/dist/src/cli/agents/cursor.d.ts +5 -0
  27. package/dist/src/cli/agents/cursor.d.ts.map +1 -0
  28. package/dist/src/cli/agents/cursor.js +14 -0
  29. package/dist/src/cli/agents/cursor.js.map +1 -0
  30. package/dist/src/cli/agents/factory-droid.d.ts +5 -0
  31. package/dist/src/cli/agents/factory-droid.d.ts.map +1 -0
  32. package/dist/src/cli/agents/factory-droid.js +14 -0
  33. package/dist/src/cli/agents/factory-droid.js.map +1 -0
  34. package/dist/src/cli/agents/gemini-cli.d.ts +5 -0
  35. package/dist/src/cli/agents/gemini-cli.d.ts.map +1 -0
  36. package/dist/src/cli/agents/gemini-cli.js +14 -0
  37. package/dist/src/cli/agents/gemini-cli.js.map +1 -0
  38. package/dist/src/cli/agents/index.d.ts +14 -0
  39. package/dist/src/cli/agents/index.d.ts.map +1 -0
  40. package/dist/src/cli/agents/index.js +30 -0
  41. package/dist/src/cli/agents/index.js.map +1 -0
  42. package/dist/src/cli/agents/windsurf.d.ts +5 -0
  43. package/dist/src/cli/agents/windsurf.d.ts.map +1 -0
  44. package/dist/src/cli/agents/windsurf.js +14 -0
  45. package/dist/src/cli/agents/windsurf.js.map +1 -0
  46. package/dist/src/cli/index.d.ts +8 -0
  47. package/dist/src/cli/index.d.ts.map +1 -0
  48. package/dist/src/cli/index.js +168 -0
  49. package/dist/src/cli/index.js.map +1 -0
  50. package/dist/src/cli/init.d.ts +34 -0
  51. package/dist/src/cli/init.d.ts.map +1 -0
  52. package/dist/src/cli/init.js +160 -0
  53. package/dist/src/cli/init.js.map +1 -0
  54. package/dist/src/cli/instructions.d.ts +29 -0
  55. package/dist/src/cli/instructions.d.ts.map +1 -0
  56. package/dist/src/cli/instructions.js +141 -0
  57. package/dist/src/cli/instructions.js.map +1 -0
  58. package/dist/src/cli/types.d.ts +22 -0
  59. package/dist/src/cli/types.d.ts.map +1 -0
  60. package/dist/src/cli/types.js +86 -0
  61. package/dist/src/cli/types.js.map +1 -0
  62. package/dist/src/contracts/schemas.js.map +1 -1
  63. package/dist/src/server/mcp-server.d.ts +8 -0
  64. package/dist/src/server/mcp-server.d.ts.map +1 -1
  65. package/dist/src/server/mcp-server.js +30 -16
  66. package/dist/src/server/mcp-server.js.map +1 -1
  67. package/dist/src/services/embedding-service.d.ts.map +1 -1
  68. package/dist/src/services/embedding-service.js +1 -1
  69. package/dist/src/services/embedding-service.js.map +1 -1
  70. package/dist/src/services/memory-service.d.ts +1 -0
  71. package/dist/src/services/memory-service.d.ts.map +1 -1
  72. package/dist/src/services/memory-service.js +125 -82
  73. package/dist/src/services/memory-service.js.map +1 -1
  74. package/dist/src/storage/markdown-storage.d.ts.map +1 -1
  75. package/dist/src/storage/markdown-storage.js +1 -1
  76. package/dist/src/storage/markdown-storage.js.map +1 -1
  77. package/dist/src/storage/vector-index.d.ts.map +1 -1
  78. package/dist/src/storage/vector-index.js +4 -5
  79. package/dist/src/storage/vector-index.js.map +1 -1
  80. package/docs/README.md +21 -0
  81. package/docs/mcp-tools.md +136 -0
  82. package/docs/user-guide.md +182 -0
  83. package/package.json +22 -19
  84. package/src/cli/agents/claude-code.ts +14 -0
  85. package/src/cli/agents/cline.ts +14 -0
  86. package/src/cli/agents/codex.ts +14 -0
  87. package/src/cli/agents/cursor.ts +14 -0
  88. package/src/cli/agents/factory-droid.ts +14 -0
  89. package/src/cli/agents/gemini-cli.ts +14 -0
  90. package/src/cli/agents/index.ts +36 -0
  91. package/src/cli/agents/windsurf.ts +14 -0
  92. package/src/cli/index.ts +192 -0
  93. package/src/cli/init.ts +218 -0
  94. package/src/cli/instructions.ts +156 -0
  95. package/src/cli/types.ts +112 -0
  96. package/src/contracts/mcp.ts +1 -1
  97. package/src/contracts/schemas.ts +4 -4
  98. package/src/contracts/types.ts +4 -4
  99. package/src/server/mcp-server.ts +36 -29
  100. package/src/services/embedding-service.ts +80 -80
  101. package/src/services/memory-service.ts +142 -107
  102. package/src/storage/markdown-storage.ts +1 -9
  103. package/src/storage/vector-index.ts +117 -118
  104. package/test/cli/init.test.ts +380 -0
  105. package/test/server/mcp-server.test.ts +45 -3
  106. package/test/services/memory-service.test.ts +16 -4
  107. package/test/storage/frontmatter-parser.test.ts +1 -1
  108. package/test/storage/markdown-storage.test.ts +19 -10
  109. package/test/storage/vector-index.test.ts +129 -133
  110. package/test/utils/slugify.test.ts +5 -1
  111. package/docs/architecture.md +0 -349
  112. package/docs/contracts.md +0 -119
  113. package/docs/prompt-template.md +0 -79
  114. package/docs/proposals/mcp-typescript-sdk-refactor.md +0 -568
  115. package/docs/proposals/proposal-close-gates.md +0 -58
  116. package/docs/tool-calling-policy.md +0 -107
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MemHub CLI entry point
4
+ * - No args: start MCP server
5
+ * - With args: run CLI commands
6
+ */
7
+
8
+ import { readFileSync, existsSync } from 'fs';
9
+ import { join, dirname } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import * as p from '@clack/prompts';
12
+ import { AGENTS, type AgentType } from './types.js';
13
+ import { initAgent, selectAgentInteractive } from './init.js';
14
+ import { createMcpServer } from '../server/mcp-server.js';
15
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+
20
+ interface PackageJson {
21
+ version?: string;
22
+ }
23
+
24
+ // Get package version
25
+ let packageJsonPath = join(__dirname, '../../../package.json');
26
+ if (!existsSync(packageJsonPath)) {
27
+ packageJsonPath = join(__dirname, '../../package.json');
28
+ }
29
+
30
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as PackageJson;
31
+ const VERSION = packageJson.version || '0.0.0';
32
+
33
+ /**
34
+ * Start MCP server (no args mode)
35
+ */
36
+ async function startMcpServer(): Promise<void> {
37
+ const server = createMcpServer();
38
+ const transport = new StdioServerTransport();
39
+ await server.connect(transport);
40
+ console.error('MemHub MCP Server running on stdio');
41
+ }
42
+
43
+ function printHelp(): void {
44
+ // eslint-disable-next-line no-console
45
+ console.log(`
46
+ MemHub CLI v${VERSION} - Git-friendly memory for AI agents
47
+
48
+ Usage:
49
+ memhub Start MCP server (default)
50
+ memhub init [options] Configure MemHub for your AI agent
51
+ memhub --help Show this help message
52
+ memhub --version Show version
53
+
54
+ Options:
55
+ -a, --agent <agent> Agent type (skip interactive selection)
56
+ -f, --force Update existing configuration
57
+ -l, --local Configure for current project (default: global)
58
+
59
+ Supported agents:
60
+ ${AGENTS.map(a => ` ${a.id.padEnd(15)} ${a.name}`).join('\n')}
61
+
62
+ Examples:
63
+ memhub # Start MCP server
64
+ memhub init # Interactive selection (global)
65
+ memhub init --local # Interactive selection (project)
66
+ memhub init --agent cursor # Configure for Cursor (global)
67
+ memhub init -a claude-code -l # Configure for Claude Code (project)
68
+ `);
69
+ }
70
+
71
+ function printVersion(): void {
72
+ // eslint-disable-next-line no-console
73
+ console.log(`MemHub CLI v${VERSION}`);
74
+ }
75
+
76
+ function parseAgent(value: string): AgentType | null {
77
+ const validAgents = AGENTS.map(a => a.id);
78
+ if (validAgents.includes(value as AgentType)) {
79
+ return value as AgentType;
80
+ }
81
+ console.error(`Invalid agent: ${value}`);
82
+ console.error(`Valid agents: ${validAgents.join(', ')}`);
83
+ return null;
84
+ }
85
+
86
+ async function runCli(args: string[]): Promise<void> {
87
+ // Parse command
88
+ const command = args[0];
89
+
90
+ if (command === '--help' || command === '-h') {
91
+ printHelp();
92
+ process.exit(0);
93
+ }
94
+
95
+ if (command === '--version' || command === '-v') {
96
+ printVersion();
97
+ process.exit(0);
98
+ }
99
+
100
+ if (command !== 'init') {
101
+ p.log.error(`Unknown command: ${command}`);
102
+ p.log.info('Run "memhub --help" for usage information.');
103
+ process.exit(1);
104
+ }
105
+
106
+ // Parse init options
107
+ let agent: AgentType | undefined;
108
+ let force = false;
109
+ let local = false;
110
+
111
+ for (let i = 1; i < args.length; i++) {
112
+ const arg = args[i];
113
+
114
+ if (arg === '-a' || arg === '--agent') {
115
+ const value = args[++i];
116
+ if (!value) {
117
+ p.log.error('--agent requires a value');
118
+ process.exit(1);
119
+ }
120
+ const parsed = parseAgent(value);
121
+ if (!parsed) {
122
+ process.exit(1);
123
+ }
124
+ agent = parsed;
125
+ } else if (arg === '-f' || arg === '--force') {
126
+ force = true;
127
+ } else if (arg === '-l' || arg === '--local') {
128
+ local = true;
129
+ } else {
130
+ p.log.error(`Unknown option: ${arg}`);
131
+ process.exit(1);
132
+ }
133
+ }
134
+
135
+ // Start interactive session
136
+ p.intro(`MemHub v${VERSION}`);
137
+
138
+ // If no agent specified, run interactive selection
139
+ if (!agent) {
140
+ const selectedAgent = await selectAgentInteractive();
141
+ if (!selectedAgent) {
142
+ p.cancel('Operation cancelled.');
143
+ process.exit(0);
144
+ }
145
+ agent = selectedAgent;
146
+ }
147
+
148
+ // Run init with spinner
149
+ const s = p.spinner();
150
+ s.start(`Configuring MemHub for ${agent}...`);
151
+
152
+ const result = initAgent({
153
+ agent,
154
+ force,
155
+ local,
156
+ });
157
+
158
+ if (result.success) {
159
+ s.stop(`Configured for ${result.agent.name}`);
160
+ p.log.success(`MCP config: ${result.configPath}`);
161
+
162
+ if (result.instructionsUpdated) {
163
+ p.log.success(`Instructions: ${result.instructionsPath} (${result.instructionsReason})`);
164
+ } else {
165
+ p.log.info(`Instructions: ${result.instructionsPath} (${result.instructionsReason})`);
166
+ }
167
+
168
+ p.outro(`Restart your agent to start using MemHub.`);
169
+ } else {
170
+ s.stop('Configuration failed');
171
+ p.log.error(result.error);
172
+ process.exit(1);
173
+ }
174
+ }
175
+
176
+ async function main(): Promise<void> {
177
+ const args = process.argv.slice(2);
178
+
179
+ // No args: start MCP server
180
+ if (args.length === 0) {
181
+ await startMcpServer();
182
+ return;
183
+ }
184
+
185
+ // With args: run CLI
186
+ await runCli(args);
187
+ }
188
+
189
+ main().catch(error => {
190
+ console.error('Fatal error:', error);
191
+ process.exit(1);
192
+ });
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Init command implementation
3
+ * Generates MCP configuration for different AI agents
4
+ */
5
+
6
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
7
+ import { dirname, join } from 'path';
8
+ import { homedir } from 'os';
9
+ import * as p from '@clack/prompts';
10
+ import { AGENTS, type AgentType, type AgentConfig, getAgentById } from './types.js';
11
+ import { getConfigGenerator } from './agents/index.js';
12
+ import { updateInstructionsContent } from './instructions.js';
13
+ import { parse as parseToml, stringify as stringifyToml } from 'smol-toml';
14
+
15
+ export interface InitOptions {
16
+ readonly agent: AgentType;
17
+ /** @deprecated Use local instead */
18
+ readonly projectPath?: string;
19
+ readonly force?: boolean;
20
+ readonly local?: boolean;
21
+ }
22
+
23
+ export interface InitResult {
24
+ readonly success: true;
25
+ readonly configPath: string;
26
+ readonly instructionsPath: string;
27
+ readonly instructionsUpdated: boolean;
28
+ readonly instructionsReason: string;
29
+ readonly agent: AgentConfig;
30
+ }
31
+
32
+ export interface InitError {
33
+ readonly success: false;
34
+ readonly error: string;
35
+ }
36
+
37
+ export type InitOutcome = InitResult | InitError;
38
+
39
+ /**
40
+ * Parse config file content based on format
41
+ */
42
+ function parseConfig(
43
+ content: string,
44
+ format: 'json' | 'markdown' | 'toml'
45
+ ): Record<string, unknown> {
46
+ if (format === 'toml') {
47
+ return parseToml(content) as Record<string, unknown>;
48
+ }
49
+ return JSON.parse(content) as Record<string, unknown>;
50
+ }
51
+
52
+ /**
53
+ * Stringify config to string based on format
54
+ */
55
+ function stringifyConfig(
56
+ config: Record<string, unknown>,
57
+ format: 'json' | 'markdown' | 'toml'
58
+ ): string {
59
+ if (format === 'toml') {
60
+ return stringifyToml(config);
61
+ }
62
+ return JSON.stringify(config, null, 2);
63
+ }
64
+
65
+ /**
66
+ * Merge memhub config into existing config
67
+ * Preserves all existing servers, adds/updates memhub
68
+ */
69
+ function mergeMcpConfig(
70
+ existing: Record<string, unknown>,
71
+ newConfig: Record<string, unknown>
72
+ ): Record<string, unknown> {
73
+ const result = { ...existing };
74
+
75
+ // Ensure mcpServers object exists
76
+ if (typeof result.mcpServers !== 'object' || result.mcpServers === null) {
77
+ result.mcpServers = {};
78
+ }
79
+
80
+ // Merge memhub server
81
+ const existingServers = result.mcpServers as Record<string, unknown>;
82
+ const newServers = newConfig.mcpServers as Record<string, unknown>;
83
+
84
+ for (const [serverName, serverConfig] of Object.entries(newServers)) {
85
+ existingServers[serverName] = serverConfig;
86
+ }
87
+
88
+ return result;
89
+ }
90
+
91
+ /**
92
+ * Run interactive agent selection using clack
93
+ */
94
+ export async function selectAgentInteractive(): Promise<AgentType | null> {
95
+ const selection = await p.select({
96
+ message: 'Select your AI agent',
97
+ options: AGENTS.map(agent => ({
98
+ value: agent.id,
99
+ label: agent.name,
100
+ hint: agent.description,
101
+ })),
102
+ });
103
+
104
+ if (p.isCancel(selection)) {
105
+ return null;
106
+ }
107
+
108
+ return selection;
109
+ }
110
+
111
+ /**
112
+ * Generate and write MCP configuration for the specified agent
113
+ */
114
+ export function initAgent(options: InitOptions): InitOutcome {
115
+ const { agent, force = false, local = false, projectPath } = options;
116
+
117
+ const agentConfig = getAgentById(agent);
118
+ if (!agentConfig) {
119
+ return {
120
+ success: false,
121
+ error: `Unknown agent: ${agent}. Valid agents: ${AGENTS.map(a => a.id).join(', ')}`,
122
+ };
123
+ }
124
+
125
+ // Determine base path and file paths based on local flag
126
+ const basePath = projectPath ?? (local ? process.cwd() : homedir());
127
+ const configFile = local ? agentConfig.configFile : agentConfig.globalConfigFile;
128
+ const instructionsFile = local
129
+ ? agentConfig.instructionsFile
130
+ : agentConfig.globalInstructionsFile;
131
+
132
+ const configPath = join(basePath, configFile);
133
+ const configDir = dirname(configPath);
134
+ const instructionsPath = join(basePath, instructionsFile);
135
+ const instructionsDir = dirname(instructionsPath);
136
+
137
+ // Generate MCP configuration
138
+ const generator = getConfigGenerator(agent);
139
+ const newConfig = generator(basePath);
140
+
141
+ let finalConfig: Record<string, unknown>;
142
+
143
+ // Check if config already exists
144
+ const configFormat = agentConfig.configFormat;
145
+ if (existsSync(configPath)) {
146
+ if (force) {
147
+ // Force: still merge, but this updates memhub entry
148
+ try {
149
+ const existingContent = readFileSync(configPath, 'utf-8');
150
+ const existingConfig = parseConfig(existingContent, configFormat);
151
+ finalConfig = mergeMcpConfig(existingConfig, newConfig);
152
+ } catch {
153
+ // Invalid config, use new config
154
+ finalConfig = newConfig;
155
+ }
156
+ } else {
157
+ // No force: check if memhub already exists
158
+ try {
159
+ const existingContent = readFileSync(configPath, 'utf-8');
160
+ const existingConfig = parseConfig(existingContent, configFormat);
161
+ const servers = existingConfig.mcpServers as Record<string, unknown> | undefined;
162
+
163
+ if (servers && 'memhub' in servers) {
164
+ return {
165
+ success: false,
166
+ error: `MemHub is already configured in ${configFile}. Use --force to update.`,
167
+ };
168
+ }
169
+
170
+ // Merge with existing config
171
+ finalConfig = mergeMcpConfig(existingConfig, newConfig);
172
+ } catch {
173
+ return {
174
+ success: false,
175
+ error: `Failed to parse existing config at ${configFile}. Use --force to overwrite.`,
176
+ };
177
+ }
178
+ }
179
+ } else {
180
+ finalConfig = newConfig;
181
+ }
182
+
183
+ // Ensure directories exist
184
+ if (!existsSync(configDir)) {
185
+ mkdirSync(configDir, { recursive: true });
186
+ }
187
+ if (!existsSync(instructionsDir)) {
188
+ mkdirSync(instructionsDir, { recursive: true });
189
+ }
190
+
191
+ // Write MCP configuration
192
+ writeFileSync(configPath, stringifyConfig(finalConfig, configFormat), 'utf-8');
193
+
194
+ // Handle instructions file
195
+ let existingContent = '';
196
+ if (existsSync(instructionsPath)) {
197
+ existingContent = readFileSync(instructionsPath, 'utf-8');
198
+ }
199
+
200
+ const {
201
+ content: updatedContent,
202
+ updated: instructionsUpdated,
203
+ reason: instructionsReason,
204
+ } = updateInstructionsContent(existingContent, agentConfig);
205
+
206
+ if (instructionsUpdated || !existsSync(instructionsPath)) {
207
+ writeFileSync(instructionsPath, updatedContent, 'utf-8');
208
+ }
209
+
210
+ return {
211
+ success: true,
212
+ configPath: configFile,
213
+ instructionsPath: instructionsFile,
214
+ instructionsUpdated,
215
+ instructionsReason,
216
+ agent: agentConfig,
217
+ };
218
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * MemHub instructions generator
3
+ * Generates agent-specific instruction content with versioned tags
4
+ */
5
+
6
+ import type { AgentConfig } from './types.js';
7
+
8
+ const MEMHUB_VERSION = '0.2.3';
9
+
10
+ const START_TAG = `<!-- MEMHUB:v${MEMHUB_VERSION}:START -->`;
11
+ const END_TAG = '<!-- MEMHUB:END -->';
12
+
13
+ /**
14
+ * Extract MemHub version from content if present
15
+ */
16
+ export function extractMemHubVersion(content: string): string | null {
17
+ const match = content.match(/<!-- MEMHUB:v([\d.]+):START -->/);
18
+ return match ? match[1] : null;
19
+ }
20
+
21
+ /**
22
+ * Check if MemHub instructions exist and need update
23
+ */
24
+ export function needsUpdate(content: string): boolean {
25
+ const version = extractMemHubVersion(content);
26
+ if (!version) return true;
27
+ return version !== MEMHUB_VERSION;
28
+ }
29
+
30
+ /**
31
+ * Generate MemHub instructions content
32
+ */
33
+ export function generateInstructionsContent(agent: AgentConfig): string {
34
+ const isMarkdown = agent.instructionsFormat === 'markdown';
35
+
36
+ if (isMarkdown) {
37
+ return `${START_TAG}
38
+ ## MemHub - AI Memory System
39
+
40
+ MemHub is a Git-friendly memory system for storing decisions, preferences, and reusable knowledge.
41
+
42
+ ### When to Call memory_load
43
+
44
+ **Proactively call** memory_load in these scenarios:
45
+
46
+ - Starting a new conversation or task
47
+ - User mentions "before", "remember", "last time" keywords
48
+ - Uncertain about user preferences or constraints
49
+ - Need project context (tech stack, conventions, architecture)
50
+
51
+ ### When to Call memory_update
52
+
53
+ **Proactively store** memories in these scenarios:
54
+
55
+ - User explicitly expresses a preference ("I prefer functional components")
56
+ - User makes a decision with reasoning ("Using PostgreSQL because...")
57
+ - Discover important project context (tech stack, constraints, patterns)
58
+ - User corrects your assumptions ("Actually, we don't use Redux")
59
+
60
+ ### Usage Principles
61
+
62
+ 1. **Load on demand** - Call memory_load at task start to get context
63
+ 2. **Store timely** - Call memory_update when learning valuable information
64
+ 3. **Query precisely** - Use tags to filter relevant memories
65
+ 4. **Describe concisely** - Be specific in content, helpful in title
66
+
67
+ ### Memory Types
68
+
69
+ | entryType | Purpose |
70
+ |-----------|---------|
71
+ | preference | User preferences (coding style, framework choices) |
72
+ | decision | Architecture decisions, technology choices |
73
+ | context | Project context (team, processes, constraints) |
74
+ | fact | Learned facts, important notes |
75
+
76
+ ${END_TAG}`;
77
+ }
78
+
79
+ // Plain text format (for .cursorrules, .clinerules, etc.)
80
+ return `${START_TAG}
81
+ # MemHub - AI Memory System
82
+
83
+ MemHub is a Git-friendly memory system for storing decisions, preferences, and reusable knowledge.
84
+
85
+ ## When to Call memory_load
86
+
87
+ - Starting a new conversation or task
88
+ - User mentions "before", "remember", "last time" keywords
89
+ - Uncertain about user preferences or constraints
90
+ - Need project context (tech stack, conventions, architecture)
91
+
92
+ ## When to Call memory_update
93
+
94
+ - User explicitly expresses a preference
95
+ - User makes a decision with reasoning
96
+ - Discover important project context
97
+ - User corrects your assumptions
98
+
99
+ ## Memory Types
100
+
101
+ - preference: User preferences
102
+ - decision: Architecture decisions
103
+ - context: Project context
104
+ - fact: Learned facts
105
+
106
+ ## Principle
107
+
108
+ Will my future self benefit from knowing this? If yes, store it.
109
+
110
+ ${END_TAG}`;
111
+ }
112
+
113
+ /**
114
+ * Update instructions file content
115
+ * - If no MemHub section: prepend new instructions
116
+ * - If MemHub section exists and outdated: replace with new version
117
+ * - If MemHub section exists and current: no change
118
+ */
119
+ export function updateInstructionsContent(
120
+ existingContent: string,
121
+ agent: AgentConfig
122
+ ): { content: string; updated: boolean; reason: string } {
123
+ const trimmedContent = existingContent.trim();
124
+ const hasMemHub = trimmedContent.includes('<!-- MEMHUB:');
125
+
126
+ if (!hasMemHub) {
127
+ // No MemHub section: prepend
128
+ const newInstructions = generateInstructionsContent(agent);
129
+ return {
130
+ content: `${newInstructions}\n\n${trimmedContent}`,
131
+ updated: true,
132
+ reason: 'MemHub instructions added',
133
+ };
134
+ }
135
+
136
+ const currentVersion = extractMemHubVersion(trimmedContent);
137
+ if (currentVersion === MEMHUB_VERSION) {
138
+ // Already up to date
139
+ return {
140
+ content: existingContent,
141
+ updated: false,
142
+ reason: 'Already up to date',
143
+ };
144
+ }
145
+
146
+ // Need to update: replace old section with new
147
+ const newInstructions = generateInstructionsContent(agent);
148
+ const pattern = /<!-- MEMHUB:v[\d.]+:START -->[\s\S]*?<!-- MEMHUB:END -->/;
149
+ const updatedContent = trimmedContent.replace(pattern, newInstructions);
150
+
151
+ return {
152
+ content: updatedContent,
153
+ updated: true,
154
+ reason: `Updated from v${currentVersion} to v${MEMHUB_VERSION}`,
155
+ };
156
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * CLI types and constants
3
+ */
4
+
5
+ export type AgentType =
6
+ | 'cursor'
7
+ | 'claude-code'
8
+ | 'cline'
9
+ | 'windsurf'
10
+ | 'factory-droid'
11
+ | 'gemini-cli'
12
+ | 'codex';
13
+
14
+ export interface AgentConfig {
15
+ readonly id: AgentType;
16
+ readonly name: string;
17
+ readonly description: string;
18
+ /** Local config file path (relative to project root) */
19
+ readonly configFile: string;
20
+ /** Global config file path (relative to home directory) */
21
+ readonly globalConfigFile: string;
22
+ readonly configFormat: 'json' | 'markdown' | 'toml';
23
+ /** Local instructions file path */
24
+ readonly instructionsFile: string;
25
+ /** Global instructions file path (relative to home directory) */
26
+ readonly globalInstructionsFile: string;
27
+ readonly instructionsFormat: 'markdown' | 'plain';
28
+ }
29
+
30
+ export const AGENTS: readonly AgentConfig[] = [
31
+ {
32
+ id: 'cursor',
33
+ name: 'Cursor',
34
+ description: 'AI code editor with MCP support',
35
+ configFile: '.cursor/mcp.json',
36
+ globalConfigFile: '.cursor/mcp.json',
37
+ configFormat: 'json',
38
+ instructionsFile: '.cursorrules',
39
+ globalInstructionsFile: '.cursorrules',
40
+ instructionsFormat: 'plain',
41
+ },
42
+ {
43
+ id: 'claude-code',
44
+ name: 'Claude Code',
45
+ description: 'Anthropic CLI for Claude',
46
+ configFile: '.mcp.json',
47
+ globalConfigFile: '.claude/settings.json',
48
+ configFormat: 'json',
49
+ instructionsFile: 'CLAUDE.md',
50
+ globalInstructionsFile: '.claude/CLAUDE.md',
51
+ instructionsFormat: 'markdown',
52
+ },
53
+ {
54
+ id: 'cline',
55
+ name: 'Cline',
56
+ description: 'VS Code extension for AI coding',
57
+ configFile: '.cline/mcp.json',
58
+ globalConfigFile: '.cline/mcp.json',
59
+ configFormat: 'json',
60
+ instructionsFile: '.clinerules',
61
+ globalInstructionsFile: '.clinerules',
62
+ instructionsFormat: 'plain',
63
+ },
64
+ {
65
+ id: 'windsurf',
66
+ name: 'Windsurf',
67
+ description: 'Codeium AI editor',
68
+ configFile: '.codeium/windsurf/mcp_config.json',
69
+ globalConfigFile: '.codeium/windsurf/mcp_config.json',
70
+ configFormat: 'json',
71
+ instructionsFile: '.windsurfrules',
72
+ globalInstructionsFile: '.windsurfrules',
73
+ instructionsFormat: 'plain',
74
+ },
75
+ {
76
+ id: 'factory-droid',
77
+ name: 'Factory Droid',
78
+ description: 'Factory AI coding agent',
79
+ configFile: '.factory/mcp.json',
80
+ globalConfigFile: '.factory/mcp.json',
81
+ configFormat: 'json',
82
+ instructionsFile: '.factory/AGENTS.md',
83
+ globalInstructionsFile: '.factory/AGENTS.md',
84
+ instructionsFormat: 'markdown',
85
+ },
86
+ {
87
+ id: 'gemini-cli',
88
+ name: 'Gemini CLI',
89
+ description: 'Google Gemini command line tool',
90
+ configFile: '.gemini/settings.json',
91
+ globalConfigFile: '.gemini/settings.json',
92
+ configFormat: 'json',
93
+ instructionsFile: 'GEMINI.md',
94
+ globalInstructionsFile: '.gemini/GEMINI.md',
95
+ instructionsFormat: 'markdown',
96
+ },
97
+ {
98
+ id: 'codex',
99
+ name: 'Codex',
100
+ description: 'OpenAI CLI coding agent',
101
+ configFile: '.codex/config.toml',
102
+ globalConfigFile: '.codex/config.toml',
103
+ configFormat: 'toml',
104
+ instructionsFile: 'AGENTS.md',
105
+ globalInstructionsFile: '.codex/AGENTS.md',
106
+ instructionsFormat: 'markdown',
107
+ },
108
+ ] as const;
109
+
110
+ export function getAgentById(id: AgentType): AgentConfig | undefined {
111
+ return AGENTS.find(agent => agent.id === id);
112
+ }
@@ -220,4 +220,4 @@ export type ToolInput<T extends ToolName> = T extends 'memory_load'
220
220
  category?: string;
221
221
  importance?: number;
222
222
  }
223
- : never;
223
+ : never;
@@ -41,10 +41,10 @@ export const ImportanceSchema = z.number().int().min(1).max(5);
41
41
 
42
42
  /** STM memory entry type */
43
43
  export const MemoryEntryTypeSchema = z.enum([
44
- 'preference', // User likes/dislikes
45
- 'decision', // Technical choices with reasoning
46
- 'context', // Project/environment information
47
- 'fact', // Objective knowledge
44
+ 'preference', // User likes/dislikes
45
+ 'decision', // Technical choices with reasoning
46
+ 'context', // Project/environment information
47
+ 'fact', // Objective knowledge
48
48
  ]);
49
49
 
50
50
  // ============================================================================