@memoire-ai/collector 0.1.0

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 (97) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +45 -0
  3. package/cursor-hooks/README.md +119 -0
  4. package/cursor-hooks/context-inject.sh +118 -0
  5. package/cursor-hooks/hooks.json +39 -0
  6. package/cursor-hooks/save-file-edit.sh +130 -0
  7. package/cursor-hooks/save-observation.sh +116 -0
  8. package/cursor-hooks/save-shell-execution.sh +121 -0
  9. package/cursor-hooks/session-summary.sh +142 -0
  10. package/dist/capture.d.ts +111 -0
  11. package/dist/capture.d.ts.map +1 -0
  12. package/dist/capture.integration.d.ts +2 -0
  13. package/dist/capture.integration.d.ts.map +1 -0
  14. package/dist/capture.integration.js +67 -0
  15. package/dist/capture.integration.js.map +1 -0
  16. package/dist/capture.js +264 -0
  17. package/dist/capture.js.map +1 -0
  18. package/dist/client-summarizer.d.ts +59 -0
  19. package/dist/client-summarizer.d.ts.map +1 -0
  20. package/dist/client-summarizer.js +211 -0
  21. package/dist/client-summarizer.js.map +1 -0
  22. package/dist/client-summarizer.test.d.ts +2 -0
  23. package/dist/client-summarizer.test.d.ts.map +1 -0
  24. package/dist/client-summarizer.test.js +127 -0
  25. package/dist/client-summarizer.test.js.map +1 -0
  26. package/dist/config.d.ts +13 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +131 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/config.test.d.ts +2 -0
  31. package/dist/config.test.d.ts.map +1 -0
  32. package/dist/config.test.js +182 -0
  33. package/dist/config.test.js.map +1 -0
  34. package/dist/cursor-hooks.d.ts +46 -0
  35. package/dist/cursor-hooks.d.ts.map +1 -0
  36. package/dist/cursor-hooks.js +251 -0
  37. package/dist/cursor-hooks.js.map +1 -0
  38. package/dist/cursor-rules.d.ts +42 -0
  39. package/dist/cursor-rules.d.ts.map +1 -0
  40. package/dist/cursor-rules.js +229 -0
  41. package/dist/cursor-rules.js.map +1 -0
  42. package/dist/cursor-rules.test.d.ts +2 -0
  43. package/dist/cursor-rules.test.d.ts.map +1 -0
  44. package/dist/cursor-rules.test.js +55 -0
  45. package/dist/cursor-rules.test.js.map +1 -0
  46. package/dist/dedup.d.ts +22 -0
  47. package/dist/dedup.d.ts.map +1 -0
  48. package/dist/dedup.js +60 -0
  49. package/dist/dedup.js.map +1 -0
  50. package/dist/dedup.test.d.ts +2 -0
  51. package/dist/dedup.test.d.ts.map +1 -0
  52. package/dist/dedup.test.js +83 -0
  53. package/dist/dedup.test.js.map +1 -0
  54. package/dist/hooks/index.d.ts +52 -0
  55. package/dist/hooks/index.d.ts.map +1 -0
  56. package/dist/hooks/index.js +136 -0
  57. package/dist/hooks/index.js.map +1 -0
  58. package/dist/hooks.test.d.ts +2 -0
  59. package/dist/hooks.test.d.ts.map +1 -0
  60. package/dist/hooks.test.js +94 -0
  61. package/dist/hooks.test.js.map +1 -0
  62. package/dist/index.d.ts +9 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +9 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/strip-private.d.ts +12 -0
  67. package/dist/strip-private.d.ts.map +1 -0
  68. package/dist/strip-private.js +28 -0
  69. package/dist/strip-private.js.map +1 -0
  70. package/dist/strip-private.test.d.ts +2 -0
  71. package/dist/strip-private.test.d.ts.map +1 -0
  72. package/dist/strip-private.test.js +37 -0
  73. package/dist/strip-private.test.js.map +1 -0
  74. package/dist/utils.d.ts +3 -0
  75. package/dist/utils.d.ts.map +1 -0
  76. package/dist/utils.js +11 -0
  77. package/dist/utils.js.map +1 -0
  78. package/package.json +28 -0
  79. package/src/capture.integration.ts +98 -0
  80. package/src/capture.ts +352 -0
  81. package/src/client-summarizer.test.ts +144 -0
  82. package/src/client-summarizer.ts +338 -0
  83. package/src/config.test.ts +211 -0
  84. package/src/config.ts +157 -0
  85. package/src/cursor-hooks.ts +309 -0
  86. package/src/cursor-rules.test.ts +63 -0
  87. package/src/cursor-rules.ts +313 -0
  88. package/src/dedup.test.ts +84 -0
  89. package/src/dedup.ts +67 -0
  90. package/src/hooks/index.ts +226 -0
  91. package/src/hooks.test.ts +111 -0
  92. package/src/index.ts +32 -0
  93. package/src/strip-private.test.ts +57 -0
  94. package/src/strip-private.ts +34 -0
  95. package/src/utils.ts +10 -0
  96. package/tsconfig.json +12 -0
  97. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,251 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, copyFileSync } from 'node:fs';
2
+ import { join, dirname, resolve } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { fileURLToPath } from 'node:url';
5
+ // ---------------------------------------------------------------------------
6
+ // Shell script sources (embedded at build time via string literals)
7
+ // ---------------------------------------------------------------------------
8
+ /**
9
+ * Resolve the directory containing the bundled shell scripts.
10
+ * Works whether this module is consumed as ESM source or from `dist/`.
11
+ */
12
+ function getScriptsDir() {
13
+ // cursor-hooks/ lives as a sibling to packages/collector/src/
14
+ // at packages/collector/cursor-hooks/
15
+ const thisFile = fileURLToPath(import.meta.url);
16
+ const thisDir = dirname(thisFile);
17
+ // When running from dist/ (compiled), scripts are at ../../cursor-hooks/
18
+ const fromDist = resolve(thisDir, '..', 'cursor-hooks');
19
+ if (existsSync(fromDist))
20
+ return fromDist;
21
+ // When running from src/ (development), scripts are at ../cursor-hooks/
22
+ const fromSrc = resolve(thisDir, '..', 'cursor-hooks');
23
+ if (existsSync(fromSrc))
24
+ return fromSrc;
25
+ // Fallback: look relative to package root
26
+ const fromRoot = resolve(thisDir, '..', '..', 'cursor-hooks');
27
+ if (existsSync(fromRoot))
28
+ return fromRoot;
29
+ throw new Error('Memoire cursor-hooks scripts directory not found. ' +
30
+ 'Ensure the cursor-hooks/ directory exists alongside the collector package.');
31
+ }
32
+ const SCRIPT_FILES = ['context-inject.sh', 'session-summary.sh', 'save-observation.sh', 'save-shell-execution.sh', 'save-file-edit.sh'];
33
+ // ---------------------------------------------------------------------------
34
+ // generateHooksJson
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Generate a Cursor `hooks.json` configuration object.
38
+ *
39
+ * The returned object can be serialised to `<project>/.cursor/hooks.json`
40
+ * (or merged into an existing one).
41
+ *
42
+ * @param apiUrl - Optional Memoire API URL baked into hook commands.
43
+ * When omitted, scripts read from env / config file at runtime.
44
+ */
45
+ export function generateHooksJson(apiUrl) {
46
+ const envPrefix = apiUrl ? `MEMOIRE_API_URL=${quote(apiUrl)} ` : '';
47
+ return {
48
+ hooks: {
49
+ beforeSubmitPrompt: [
50
+ {
51
+ command: `${envPrefix}sh .cursor/hooks/context-inject.sh "$PROMPT"`,
52
+ description: 'Inject Memoire project context into Cursor rules',
53
+ blocking: true,
54
+ },
55
+ ],
56
+ stop: [
57
+ {
58
+ command: `${envPrefix}sh .cursor/hooks/session-summary.sh`,
59
+ description: 'Generate Memoire session summary',
60
+ blocking: false,
61
+ },
62
+ ],
63
+ afterMCPExecution: [
64
+ {
65
+ command: `${envPrefix}sh .cursor/hooks/save-observation.sh "$TOOL_NAME" "$TOOL_STATUS"`,
66
+ description: 'Track MCP tool calls in Memoire',
67
+ blocking: false,
68
+ },
69
+ ],
70
+ afterShellExecution: [
71
+ {
72
+ command: `${envPrefix}sh .cursor/hooks/save-shell-execution.sh "$COMMAND" "$EXIT_CODE"`,
73
+ description: 'Track shell commands in Memoire',
74
+ blocking: false,
75
+ },
76
+ ],
77
+ afterFileEdit: [
78
+ {
79
+ command: `${envPrefix}sh .cursor/hooks/save-file-edit.sh "$FILE_PATH"`,
80
+ description: 'Track file edits in Memoire',
81
+ blocking: false,
82
+ },
83
+ ],
84
+ },
85
+ };
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // generateSetupInstructions
89
+ // ---------------------------------------------------------------------------
90
+ /**
91
+ * Return human-readable setup instructions for a given project path.
92
+ */
93
+ export function generateSetupInstructions(projectPath) {
94
+ const abs = resolve(projectPath);
95
+ return [
96
+ 'Memoire Cursor Hooks — Setup Instructions',
97
+ '==========================================',
98
+ '',
99
+ '1. Ensure the Memoire API is running (default: http://127.0.0.1:3100)',
100
+ '',
101
+ `2. Run the installer (already done if you see .cursor/hooks/ in ${abs}):`,
102
+ '',
103
+ " import { installCursorHooks } from '@memoire-ai/collector';",
104
+ ` installCursorHooks('${abs}');`,
105
+ '',
106
+ '3. Create ~/.memoire/config with your credentials:',
107
+ '',
108
+ ' MEMOIRE_API_URL=http://127.0.0.1:3100',
109
+ ' MEMOIRE_API_KEY=<your-api-key>',
110
+ ' MEMOIRE_ORG_ID=<your-org-id>',
111
+ ' MEMOIRE_PROJECT_ID=<your-project-id>',
112
+ ' MEMOIRE_USER_ID=<your-user-id>',
113
+ '',
114
+ '4. Restart Cursor to pick up the hooks.',
115
+ '',
116
+ '5. Add to .gitignore:',
117
+ '',
118
+ ' .cursor/rules/memoire-context.mdc',
119
+ '',
120
+ 'The hook scripts (.cursor/hooks/*.sh) can be committed to share with your team.',
121
+ ].join('\n');
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // installCursorHooks
125
+ // ---------------------------------------------------------------------------
126
+ /**
127
+ * Install Memoire Cursor hooks into a project directory.
128
+ *
129
+ * 1. Copies (or merges) `hooks.json` into `<project>/.cursor/hooks.json`
130
+ * 2. Copies shell scripts into `<project>/.cursor/hooks/`
131
+ * 3. Makes shell scripts executable (chmod +x)
132
+ * 4. Optionally writes `~/.memoire/config` with API credentials
133
+ *
134
+ * Safe to call repeatedly — existing hooks are preserved via merge.
135
+ *
136
+ * @param projectPath - Absolute or relative path to the project root
137
+ * @param apiUrl - Memoire API URL (written to config if provided)
138
+ * @param apiKey - Memoire API key (written to config if provided)
139
+ */
140
+ export function installCursorHooks(projectPath, apiUrl, apiKey) {
141
+ const root = resolve(projectPath);
142
+ const cursorDir = join(root, '.cursor');
143
+ const hooksDir = join(cursorDir, 'hooks');
144
+ const hooksJsonPath = join(cursorDir, 'hooks.json');
145
+ // Ensure directories exist
146
+ mkdirSync(hooksDir, { recursive: true });
147
+ // ------------------------------------------------------------------
148
+ // 1. hooks.json — merge with any existing configuration
149
+ // ------------------------------------------------------------------
150
+ const generated = generateHooksJson(apiUrl);
151
+ if (existsSync(hooksJsonPath)) {
152
+ try {
153
+ const existing = JSON.parse(readFileSync(hooksJsonPath, 'utf-8'));
154
+ const merged = mergeHooksJson(existing, generated);
155
+ writeJsonAtomic(hooksJsonPath, merged);
156
+ }
157
+ catch {
158
+ // Existing file is corrupt — overwrite
159
+ writeJsonAtomic(hooksJsonPath, generated);
160
+ }
161
+ }
162
+ else {
163
+ writeJsonAtomic(hooksJsonPath, generated);
164
+ }
165
+ // ------------------------------------------------------------------
166
+ // 2 & 3. Copy shell scripts and make them executable
167
+ // ------------------------------------------------------------------
168
+ const scriptsDir = getScriptsDir();
169
+ for (const script of SCRIPT_FILES) {
170
+ const src = join(scriptsDir, script);
171
+ const dest = join(hooksDir, script);
172
+ if (!existsSync(src)) {
173
+ throw new Error(`Memoire hook script not found: ${src}`);
174
+ }
175
+ copyFileSync(src, dest);
176
+ chmodSync(dest, 0o755);
177
+ }
178
+ // ------------------------------------------------------------------
179
+ // 4. Optionally write ~/.memoire/config
180
+ // ------------------------------------------------------------------
181
+ if (apiUrl || apiKey) {
182
+ writeOrUpdateMemoireConfig(apiUrl, apiKey);
183
+ }
184
+ }
185
+ // ---------------------------------------------------------------------------
186
+ // Internal helpers
187
+ // ---------------------------------------------------------------------------
188
+ /**
189
+ * Merge two hooks.json configs, preserving existing non-Memoire hooks.
190
+ * Memoire hooks (identified by description prefix) are replaced.
191
+ */
192
+ function mergeHooksJson(existing, incoming) {
193
+ const merged = { hooks: { ...existing.hooks } };
194
+ for (const hookName of ['beforeSubmitPrompt', 'stop', 'afterMCPExecution', 'afterShellExecution', 'afterFileEdit']) {
195
+ const incomingEntries = incoming.hooks[hookName] ?? [];
196
+ const existingEntries = existing.hooks[hookName] ?? [];
197
+ // Remove any existing Memoire entries
198
+ const nonMemoire = existingEntries.filter((entry) => !entry.description.toLowerCase().includes('memoire'));
199
+ merged.hooks[hookName] = [...nonMemoire, ...incomingEntries];
200
+ }
201
+ return merged;
202
+ }
203
+ /** Atomic JSON write via temp + rename. */
204
+ function writeJsonAtomic(filePath, data) {
205
+ const dir = dirname(filePath);
206
+ if (!existsSync(dir)) {
207
+ mkdirSync(dir, { recursive: true });
208
+ }
209
+ const tmp = `${filePath}.tmp`;
210
+ writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf-8');
211
+ const { renameSync } = require('node:fs');
212
+ renameSync(tmp, filePath);
213
+ }
214
+ /** Write or update ~/.memoire/config with KEY=VALUE entries. */
215
+ function writeOrUpdateMemoireConfig(apiUrl, apiKey) {
216
+ const configDir = join(homedir(), '.memoire');
217
+ const configPath = join(configDir, 'config');
218
+ mkdirSync(configDir, { recursive: true });
219
+ // Read existing config if present
220
+ const existing = new Map();
221
+ if (existsSync(configPath)) {
222
+ const content = readFileSync(configPath, 'utf-8');
223
+ for (const line of content.split('\n')) {
224
+ const trimmed = line.trim();
225
+ if (!trimmed || trimmed.startsWith('#'))
226
+ continue;
227
+ const eqIdx = trimmed.indexOf('=');
228
+ if (eqIdx > 0) {
229
+ existing.set(trimmed.slice(0, eqIdx).trim(), trimmed.slice(eqIdx + 1).trim());
230
+ }
231
+ }
232
+ }
233
+ // Update with new values (don't overwrite existing values with undefined)
234
+ if (apiUrl)
235
+ existing.set('MEMOIRE_API_URL', apiUrl);
236
+ if (apiKey)
237
+ existing.set('MEMOIRE_API_KEY', apiKey);
238
+ // Write back
239
+ const lines = ['# Memoire configuration (auto-generated)'];
240
+ for (const [key, value] of existing) {
241
+ lines.push(`${key}=${value}`);
242
+ }
243
+ lines.push('');
244
+ writeFileSync(configPath, lines.join('\n'), { encoding: 'utf-8', mode: 0o600 });
245
+ }
246
+ /** Shell-safe quoting for values embedded in hook commands. */
247
+ function quote(value) {
248
+ // Simple single-quote wrapping with escaping
249
+ return `'${value.replace(/'/g, "'\\''")}'`;
250
+ }
251
+ //# sourceMappingURL=cursor-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-hooks.js","sourceRoot":"","sources":["../src/cursor-hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ export declare const DEFAULT_CURSOR_RULES_TOKEN_BUDGET = 900;
2
+ export interface CursorRulesContext {
3
+ projectName: string;
4
+ profile?: {
5
+ architecture?: string;
6
+ conventions?: string[];
7
+ stack?: string[];
8
+ currentFocus?: string;
9
+ };
10
+ recentFacts?: Array<{
11
+ subject: string;
12
+ predicate: string;
13
+ object: string;
14
+ }>;
15
+ recentSummary?: string;
16
+ apiUrl?: string;
17
+ resumeText?: string;
18
+ focusQuery?: string;
19
+ tokenBudget?: number;
20
+ }
21
+ /**
22
+ * Generate and write a `.cursor/rules/memoire-context.mdc` file
23
+ * that Cursor auto-injects into every conversation.
24
+ *
25
+ * Inspired by claude-mem's context injection approach:
26
+ * - alwaysApply: true → auto-included in every chat
27
+ * - Contains project profile + recent facts for zero-config context
28
+ * - Updated after each session end
29
+ * - Atomic write (temp file + rename) to prevent corruption
30
+ */
31
+ export declare function writeCursorRules(workspacePath: string, context: CursorRulesContext): void;
32
+ /**
33
+ * Read existing cursor rules context if present.
34
+ */
35
+ export declare function readCursorRules(workspacePath: string): string | null;
36
+ /**
37
+ * Remove the cursor rules file.
38
+ */
39
+ export declare function removeCursorRules(workspacePath: string): boolean;
40
+ export declare function estimateCursorRulesTokens(content: string): number;
41
+ export declare function buildCursorRulesContent(context: CursorRulesContext): string;
42
+ //# sourceMappingURL=cursor-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-rules.d.ts","sourceRoot":"","sources":["../src/cursor-rules.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAIrD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAezF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQpE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAUhE;AAMD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAyC3E"}
@@ -0,0 +1,229 @@
1
+ import { writeFileSync, mkdirSync, existsSync, renameSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const RULES_FILENAME = 'memoire-context.mdc';
4
+ export const DEFAULT_CURSOR_RULES_TOKEN_BUDGET = 900;
5
+ const CHARS_PER_TOKEN_ESTIMATE = 4;
6
+ const MAX_LINE_LENGTH = 160;
7
+ /**
8
+ * Generate and write a `.cursor/rules/memoire-context.mdc` file
9
+ * that Cursor auto-injects into every conversation.
10
+ *
11
+ * Inspired by claude-mem's context injection approach:
12
+ * - alwaysApply: true → auto-included in every chat
13
+ * - Contains project profile + recent facts for zero-config context
14
+ * - Updated after each session end
15
+ * - Atomic write (temp file + rename) to prevent corruption
16
+ */
17
+ export function writeCursorRules(workspacePath, context) {
18
+ const rulesDir = join(workspacePath, '.cursor', 'rules');
19
+ const rulesFile = join(rulesDir, RULES_FILENAME);
20
+ const tempFile = `${rulesFile}.tmp`;
21
+ // Ensure .cursor/rules/ exists
22
+ if (!existsSync(rulesDir)) {
23
+ mkdirSync(rulesDir, { recursive: true });
24
+ }
25
+ const content = buildCursorRulesContent(context);
26
+ // Atomic write: write to temp, then rename
27
+ writeFileSync(tempFile, content, 'utf-8');
28
+ renameSync(tempFile, rulesFile);
29
+ }
30
+ /**
31
+ * Read existing cursor rules context if present.
32
+ */
33
+ export function readCursorRules(workspacePath) {
34
+ const rulesFile = join(workspacePath, '.cursor', 'rules', RULES_FILENAME);
35
+ if (!existsSync(rulesFile))
36
+ return null;
37
+ try {
38
+ return readFileSync(rulesFile, 'utf-8');
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ /**
45
+ * Remove the cursor rules file.
46
+ */
47
+ export function removeCursorRules(workspacePath) {
48
+ const rulesFile = join(workspacePath, '.cursor', 'rules', RULES_FILENAME);
49
+ if (!existsSync(rulesFile))
50
+ return false;
51
+ try {
52
+ const { unlinkSync } = require('node:fs');
53
+ unlinkSync(rulesFile);
54
+ return true;
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ }
60
+ function generateRulesContent(context) {
61
+ return buildCursorRulesContent(context);
62
+ }
63
+ export function estimateCursorRulesTokens(content) {
64
+ return Math.ceil(content.length / CHARS_PER_TOKEN_ESTIMATE);
65
+ }
66
+ export function buildCursorRulesContent(context) {
67
+ const sections = [];
68
+ const maxChars = Math.max(1200, (context.tokenBudget ?? DEFAULT_CURSOR_RULES_TOKEN_BUDGET) * CHARS_PER_TOKEN_ESTIMATE);
69
+ // YAML frontmatter — alwaysApply ensures Cursor auto-includes this
70
+ sections.push(`---
71
+ alwaysApply: true
72
+ description: "Memoire shared memory context for ${context.projectName} (auto-updated)"
73
+ ---`);
74
+ sections.push(`# Memoire Context: ${context.projectName}`);
75
+ sections.push('');
76
+ sections.push('> Auto-generated by Memoire.');
77
+ sections.push('> Condensed for token efficiency. Use Memoire MCP tools for deeper queries.');
78
+ sections.push('');
79
+ const remainingBudget = {
80
+ value: maxChars - sections.join('\n').length,
81
+ };
82
+ if (context.resumeText) {
83
+ appendResumeSections(sections, context, remainingBudget);
84
+ }
85
+ else {
86
+ appendStructuredSections(sections, context, remainingBudget);
87
+ }
88
+ // Footer with MCP hint
89
+ appendWithinBudget(sections, [
90
+ '---',
91
+ '*Use Memoire MCP tools (`search_context`, `assemble_context`, `search_docs`) for deeper context on demand.*',
92
+ ].join('\n'), remainingBudget);
93
+ if (context.apiUrl) {
94
+ appendWithinBudget(sections, `*API: ${context.apiUrl}*`, remainingBudget);
95
+ }
96
+ return sections.join('\n');
97
+ }
98
+ function appendResumeSections(sections, context, remainingBudget) {
99
+ const parsed = parseResumeSections(context.resumeText ?? '');
100
+ const focus = context.focusQuery?.trim() || parsed.focus;
101
+ if (focus) {
102
+ appendSection(sections, 'Focus', [focus], remainingBudget, 1, 220);
103
+ }
104
+ appendSection(sections, 'Project Profile', parsed.profile.length > 0 ? parsed.profile : buildStructuredProfileItems(context.profile), remainingBudget, 5, 720);
105
+ appendSection(sections, 'Durable Facts', parsed.facts.length > 0 ? parsed.facts : buildFactItems(context.recentFacts), remainingBudget, 6, 820);
106
+ appendSection(sections, 'Current Work', parsed.shared.length > 0 ? parsed.shared : normalizeItems([context.recentSummary ?? '']), remainingBudget, 6, 1200);
107
+ appendSection(sections, 'Relevant Docs', parsed.docs, remainingBudget, 3, 420);
108
+ }
109
+ function appendStructuredSections(sections, context, remainingBudget) {
110
+ appendSection(sections, 'Focus', normalizeItems([context.focusQuery ?? '']), remainingBudget, 1, 220);
111
+ appendSection(sections, 'Project Profile', buildStructuredProfileItems(context.profile), remainingBudget, 5, 720);
112
+ appendSection(sections, 'Durable Facts', buildFactItems(context.recentFacts), remainingBudget, 6, 820);
113
+ appendSection(sections, 'Current Work', normalizeItems([context.recentSummary ?? '']), remainingBudget, 4, 900);
114
+ }
115
+ function buildStructuredProfileItems(profile) {
116
+ if (!profile)
117
+ return [];
118
+ return normalizeItems([
119
+ profile.architecture ? `Architecture: ${profile.architecture}` : '',
120
+ profile.stack?.length ? `Key files: ${profile.stack.join(', ')}` : '',
121
+ profile.currentFocus ? `Current focus: ${profile.currentFocus}` : '',
122
+ ...(profile.conventions ?? []).map((item) => `Convention: ${item}`),
123
+ ]);
124
+ }
125
+ function buildFactItems(facts) {
126
+ return normalizeItems((facts ?? []).map((fact) => `${fact.subject} ${fact.predicate} ${fact.object}`));
127
+ }
128
+ function appendSection(sections, title, items, remainingBudget, maxItems, sectionBudget) {
129
+ const normalized = normalizeItems(items).slice(0, maxItems);
130
+ if (normalized.length === 0 || remainingBudget.value <= 0) {
131
+ return;
132
+ }
133
+ const heading = `## ${title}`;
134
+ const lines = [heading];
135
+ let used = heading.length + 1;
136
+ const allowed = Math.min(sectionBudget, remainingBudget.value);
137
+ for (const item of normalized) {
138
+ const line = `- ${truncateLine(item, MAX_LINE_LENGTH)}`;
139
+ const nextCost = line.length + 1;
140
+ if (used + nextCost > allowed) {
141
+ break;
142
+ }
143
+ lines.push(line);
144
+ used += nextCost;
145
+ }
146
+ if (lines.length === 1) {
147
+ return;
148
+ }
149
+ lines.push('');
150
+ appendWithinBudget(sections, lines.join('\n'), remainingBudget);
151
+ }
152
+ function appendWithinBudget(sections, block, remainingBudget) {
153
+ if (!block || remainingBudget.value <= 0)
154
+ return;
155
+ const blockCost = block.length + 1;
156
+ if (blockCost > remainingBudget.value)
157
+ return;
158
+ sections.push(block);
159
+ remainingBudget.value -= blockCost;
160
+ }
161
+ function parseResumeSections(resume) {
162
+ const lines = resume.split('\n');
163
+ const sectionMap = new Map();
164
+ let current = '';
165
+ let currentSubheading = '';
166
+ let focus;
167
+ for (const rawLine of lines) {
168
+ const line = rawLine.trim();
169
+ if (!line)
170
+ continue;
171
+ if (line.startsWith('# '))
172
+ continue;
173
+ if (line.startsWith('Focus:')) {
174
+ focus = line.slice('Focus:'.length).trim();
175
+ continue;
176
+ }
177
+ if (line.startsWith('## ')) {
178
+ current = line.slice(3).trim().toLowerCase();
179
+ currentSubheading = '';
180
+ sectionMap.set(current, sectionMap.get(current) ?? []);
181
+ continue;
182
+ }
183
+ if (!current)
184
+ continue;
185
+ if (!line.startsWith('-') && line.endsWith(':')) {
186
+ currentSubheading = line.slice(0, -1).trim();
187
+ continue;
188
+ }
189
+ if (line.startsWith('```'))
190
+ continue;
191
+ const cleaned = normalizeResumeLine(line, currentSubheading);
192
+ if (cleaned) {
193
+ sectionMap.get(current)?.push(cleaned);
194
+ }
195
+ }
196
+ return {
197
+ focus,
198
+ profile: normalizeItems(sectionMap.get('project profile') ?? []),
199
+ facts: normalizeItems(sectionMap.get('derived facts') ?? []),
200
+ shared: normalizeItems(sectionMap.get('shared memory') ?? []),
201
+ docs: normalizeItems(sectionMap.get('relevant docs') ?? []),
202
+ };
203
+ }
204
+ function normalizeResumeLine(line, sectionLabel) {
205
+ const stripped = line.replace(/^[-*]\s*/, '').trim();
206
+ if (!stripped)
207
+ return '';
208
+ if (sectionLabel && !stripped.toLowerCase().startsWith(`${sectionLabel.toLowerCase()}:`)) {
209
+ return `${sectionLabel}: ${stripped}`;
210
+ }
211
+ return stripped;
212
+ }
213
+ function normalizeItems(items) {
214
+ const unique = new Set();
215
+ for (const item of items) {
216
+ const normalized = item.replace(/\s+/g, ' ').trim();
217
+ if (!normalized)
218
+ continue;
219
+ unique.add(normalized);
220
+ }
221
+ return Array.from(unique);
222
+ }
223
+ function truncateLine(value, maxLength) {
224
+ if (value.length <= maxLength) {
225
+ return value;
226
+ }
227
+ return `${value.slice(0, maxLength - 1)}…`;
228
+ }
229
+ //# sourceMappingURL=cursor-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-rules.js","sourceRoot":"","sources":["../src/cursor-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cursor-rules.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-rules.test.d.ts","sourceRoot":"","sources":["../src/cursor-rules.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { buildCursorRulesContent, estimateCursorRulesTokens, DEFAULT_CURSOR_RULES_TOKEN_BUDGET, } from './cursor-rules.js';
4
+ test('buildCursorRulesContent compacts oversized resume content into the token budget', () => {
5
+ const repeated = 'OAuth callback flow was verified, middleware was updated, and redirect handling was standardized across the dashboard and API.';
6
+ const hugeResume = [
7
+ '# Memoire Resume',
8
+ 'Focus: fix auth redirects',
9
+ '',
10
+ '## Project Profile',
11
+ 'The project is a shared memory backend for AI coding agents with a hosted dashboard and worker.',
12
+ ...Array.from({ length: 12 }, (_, index) => `- Convention ${index + 1}: ${repeated}`),
13
+ '',
14
+ '## Derived Facts',
15
+ 'Static facts:',
16
+ ...Array.from({ length: 12 }, (_, index) => `- Fact ${index + 1}: ${repeated}`),
17
+ '',
18
+ '## Shared Memory',
19
+ ...Array.from({ length: 20 }, (_, index) => `- Active work ${index + 1}: ${repeated}`),
20
+ '',
21
+ '## Relevant Docs',
22
+ ...Array.from({ length: 8 }, (_, index) => `- next-auth@${index}: ${repeated}`),
23
+ ].join('\n');
24
+ const content = buildCursorRulesContent({
25
+ projectName: 'Memoire',
26
+ resumeText: hugeResume,
27
+ focusQuery: 'fix auth redirects',
28
+ });
29
+ assert.ok(content.includes('## Focus'));
30
+ assert.ok(content.includes('## Project Profile'));
31
+ assert.ok(content.includes('## Current Work'));
32
+ assert.ok(content.includes('Condensed for token efficiency'));
33
+ assert.ok(estimateCursorRulesTokens(content) <= DEFAULT_CURSOR_RULES_TOKEN_BUDGET);
34
+ });
35
+ test('buildCursorRulesContent keeps structured cursor rules bounded too', () => {
36
+ const content = buildCursorRulesContent({
37
+ projectName: 'Memoire',
38
+ profile: {
39
+ architecture: 'Hono API, Postgres, pgvector, worker, dashboard',
40
+ stack: Array.from({ length: 10 }, (_, i) => `apps/api/src/file-${i}.ts`),
41
+ conventions: Array.from({ length: 12 }, (_, i) => `Convention ${i + 1} for handling auth, API edges, and background work`),
42
+ currentFocus: 'Hosted onboarding, docs indexing, and cross-agent handoffs',
43
+ },
44
+ recentFacts: Array.from({ length: 12 }, (_, i) => ({
45
+ subject: `fact-${i + 1}`,
46
+ predicate: 'uses',
47
+ object: 'token-aware context packaging for Cursor rules',
48
+ })),
49
+ recentSummary: 'Shipped token-aware cursor rules packaging and kept the rest of the shared memory flow intact.',
50
+ });
51
+ assert.ok(content.includes('## Project Profile'));
52
+ assert.ok(content.includes('## Durable Facts'));
53
+ assert.ok(estimateCursorRulesTokens(content) <= DEFAULT_CURSOR_RULES_TOKEN_BUDGET);
54
+ });
55
+ //# sourceMappingURL=cursor-rules.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-rules.test.js","sourceRoot":"","sources":["../src/cursor-rules.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Client-side content deduplication using a hash-based sliding window.
3
+ * Prevents noisy hooks from flooding the API with duplicate observations
4
+ * within a short time window (default 30 seconds).
5
+ *
6
+ * Mirrors claude-mem's 30-second content-hash dedup at the edge.
7
+ */
8
+ export declare class ContentDedup {
9
+ private seen;
10
+ private windowMs;
11
+ private cleanupTimer;
12
+ constructor(windowMs?: number);
13
+ /**
14
+ * Returns true if content is a duplicate (already seen within the window).
15
+ * Returns false if content is new — and records it.
16
+ */
17
+ isDuplicate(content: string): boolean;
18
+ /** Remove entries older than the window */
19
+ private evict;
20
+ destroy(): void;
21
+ }
22
+ //# sourceMappingURL=dedup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup.d.ts","sourceRoot":"","sources":["../src/dedup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAA6C;gBAErD,QAAQ,SAAS;IAS7B;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAarC,2CAA2C;IAC3C,OAAO,CAAC,KAAK;IASb,OAAO,IAAI,IAAI;CAOhB"}