memorix 1.1.7 → 1.1.9

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 (185) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CLAUDE.md +6 -1
  3. package/README.md +21 -0
  4. package/README.zh-CN.md +21 -0
  5. package/TEAM.md +86 -86
  6. package/dist/cli/index.js +852 -214
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/dashboard/static/index.html +201 -201
  9. package/dist/dashboard/static/style.css +3584 -3584
  10. package/dist/index.js +129 -62
  11. package/dist/index.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +22 -0
  13. package/dist/memcode-runtime/package.json +4 -4
  14. package/dist/sdk.js +129 -62
  15. package/dist/sdk.js.map +1 -1
  16. package/docs/AGENT_OPERATOR_PLAYBOOK.md +18 -0
  17. package/docs/API_REFERENCE.md +2 -0
  18. package/docs/CONFIGURATION.md +18 -0
  19. package/docs/DESIGN_DECISIONS.md +357 -357
  20. package/docs/SETUP.md +10 -0
  21. package/docs/dev-log/progress.txt +23 -30
  22. package/package.json +1 -1
  23. package/src/audit/index.ts +156 -156
  24. package/src/cli/commands/agent-integrations.ts +623 -0
  25. package/src/cli/commands/audit-list.ts +89 -89
  26. package/src/cli/commands/background.ts +659 -659
  27. package/src/cli/commands/cleanup.ts +255 -255
  28. package/src/cli/commands/codegraph.ts +4 -0
  29. package/src/cli/commands/config-get.ts +9 -2
  30. package/src/cli/commands/doctor.ts +26 -0
  31. package/src/cli/commands/formation.ts +48 -48
  32. package/src/cli/commands/git-hook-install.ts +111 -111
  33. package/src/cli/commands/handoff.ts +66 -66
  34. package/src/cli/commands/hooks-status.ts +63 -63
  35. package/src/cli/commands/ingest-commit.ts +153 -153
  36. package/src/cli/commands/ingest-image.ts +73 -73
  37. package/src/cli/commands/ingest-log.ts +180 -180
  38. package/src/cli/commands/ingest.ts +44 -44
  39. package/src/cli/commands/integrate-shared.ts +15 -15
  40. package/src/cli/commands/lock.ts +96 -96
  41. package/src/cli/commands/message.ts +121 -121
  42. package/src/cli/commands/poll.ts +70 -70
  43. package/src/cli/commands/purge-all-memory.ts +85 -85
  44. package/src/cli/commands/purge-project-memory.ts +83 -83
  45. package/src/cli/commands/reasoning.ts +132 -132
  46. package/src/cli/commands/repair.ts +60 -0
  47. package/src/cli/commands/retention.ts +108 -108
  48. package/src/cli/commands/serve-shared.ts +118 -118
  49. package/src/cli/commands/setup.ts +3 -3
  50. package/src/cli/commands/skills.ts +123 -123
  51. package/src/cli/commands/task.ts +192 -192
  52. package/src/cli/commands/transfer.ts +73 -73
  53. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  54. package/src/cli/index.ts +3 -1
  55. package/src/cli/tui/ChatView.tsx +234 -234
  56. package/src/cli/tui/CommandBar.tsx +312 -312
  57. package/src/cli/tui/ContextRail.tsx +118 -118
  58. package/src/cli/tui/HeaderBar.tsx +72 -72
  59. package/src/cli/tui/LogoBanner.tsx +51 -51
  60. package/src/cli/tui/Panels.tsx +632 -632
  61. package/src/cli/tui/Sidebar.tsx +179 -179
  62. package/src/cli/tui/chat-service.ts +742 -742
  63. package/src/cli/tui/data.ts +547 -547
  64. package/src/cli/tui/index.ts +41 -41
  65. package/src/cli/tui/markdown-render.tsx +371 -371
  66. package/src/cli/tui/theme.ts +178 -178
  67. package/src/cli/tui/use-mouse.ts +157 -157
  68. package/src/cli/tui/useNavigation.ts +56 -56
  69. package/src/cli/update-checker.ts +211 -211
  70. package/src/cli/version.ts +7 -7
  71. package/src/cli/workbench.ts +1 -1
  72. package/src/codegraph/auto-context.ts +6 -0
  73. package/src/codegraph/context-pack.ts +7 -6
  74. package/src/codegraph/exclude.ts +47 -0
  75. package/src/codegraph/lite-provider.ts +5 -24
  76. package/src/codegraph/project-context.ts +13 -15
  77. package/src/compact/token-budget.ts +74 -74
  78. package/src/config/behavior.ts +59 -59
  79. package/src/config/resolved-config.ts +6 -0
  80. package/src/config/toml-loader.ts +4 -0
  81. package/src/config/yaml-loader.ts +7 -0
  82. package/src/dashboard/project-classification.ts +64 -64
  83. package/src/dashboard/static/index.html +201 -201
  84. package/src/dashboard/static/style.css +3584 -3584
  85. package/src/embedding/fastembed-provider.ts +142 -142
  86. package/src/embedding/transformers-provider.ts +111 -111
  87. package/src/git/extractor.ts +209 -209
  88. package/src/git/hooks-path.ts +85 -85
  89. package/src/git/noise-filter.ts +210 -210
  90. package/src/hooks/installers/index.ts +4 -4
  91. package/src/hooks/official-skills.ts +1 -1
  92. package/src/hooks/pattern-detector.ts +173 -173
  93. package/src/hooks/rules/memorix-agent-rules.md +2 -2
  94. package/src/hooks/significance-filter.ts +250 -250
  95. package/src/llm/memory-manager.ts +328 -328
  96. package/src/llm/provider.ts +885 -885
  97. package/src/llm/quality.ts +248 -248
  98. package/src/memory/attribution-guard.ts +249 -249
  99. package/src/memory/auto-relations.ts +107 -107
  100. package/src/memory/consolidation.ts +302 -302
  101. package/src/memory/disclosure-policy.ts +141 -141
  102. package/src/memory/entity-extractor.ts +197 -197
  103. package/src/memory/formation/evaluate.ts +217 -217
  104. package/src/memory/formation/extract.ts +361 -361
  105. package/src/memory/formation/index.ts +417 -417
  106. package/src/memory/formation/resolve.ts +344 -344
  107. package/src/memory/formation/types.ts +315 -315
  108. package/src/memory/freshness.ts +122 -122
  109. package/src/memory/graph.ts +197 -197
  110. package/src/memory/refs.ts +94 -94
  111. package/src/memory/retention.ts +433 -433
  112. package/src/memory/secret-filter.ts +79 -79
  113. package/src/memory/session.ts +523 -523
  114. package/src/multimodal/image-loader.ts +143 -143
  115. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  116. package/src/orchestrate/adapters/claude.ts +111 -111
  117. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  118. package/src/orchestrate/adapters/codex.ts +41 -41
  119. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  120. package/src/orchestrate/adapters/gemini.ts +42 -42
  121. package/src/orchestrate/adapters/index.ts +73 -73
  122. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  123. package/src/orchestrate/adapters/opencode.ts +47 -47
  124. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  125. package/src/orchestrate/adapters/types.ts +77 -77
  126. package/src/orchestrate/capability-router.ts +284 -284
  127. package/src/orchestrate/context-compact.ts +188 -188
  128. package/src/orchestrate/cost-tracker.ts +219 -219
  129. package/src/orchestrate/error-recovery.ts +191 -191
  130. package/src/orchestrate/evidence.ts +140 -140
  131. package/src/orchestrate/ledger.ts +110 -110
  132. package/src/orchestrate/memorix-bridge.ts +380 -380
  133. package/src/orchestrate/output-budget.ts +80 -80
  134. package/src/orchestrate/permission.ts +152 -152
  135. package/src/orchestrate/pipeline-trace.ts +131 -131
  136. package/src/orchestrate/prompt-builder.ts +155 -155
  137. package/src/orchestrate/ring-buffer.ts +37 -37
  138. package/src/orchestrate/task-graph.ts +389 -389
  139. package/src/orchestrate/verify-gate.ts +219 -219
  140. package/src/orchestrate/worktree.ts +232 -232
  141. package/src/project/aliases.ts +374 -374
  142. package/src/project/detector.ts +268 -268
  143. package/src/rules/adapters/claude-code.ts +99 -99
  144. package/src/rules/adapters/codex.ts +97 -97
  145. package/src/rules/adapters/copilot.ts +124 -124
  146. package/src/rules/adapters/cursor.ts +114 -114
  147. package/src/rules/adapters/kiro.ts +126 -126
  148. package/src/rules/adapters/trae.ts +56 -56
  149. package/src/rules/adapters/windsurf.ts +83 -83
  150. package/src/rules/syncer.ts +235 -235
  151. package/src/sdk.ts +327 -327
  152. package/src/search/intent-detector.ts +289 -289
  153. package/src/search/query-expansion.ts +52 -52
  154. package/src/server/formation-timeout.ts +27 -27
  155. package/src/server.ts +3 -0
  156. package/src/skills/mini-skills.ts +386 -386
  157. package/src/store/chat-store.ts +119 -119
  158. package/src/store/file-lock.ts +100 -100
  159. package/src/store/graph-store.ts +249 -249
  160. package/src/store/mini-skill-store.ts +349 -349
  161. package/src/store/obs-store.ts +255 -255
  162. package/src/store/orama-store.ts +15 -8
  163. package/src/store/persistence-json.ts +212 -212
  164. package/src/store/persistence.ts +291 -291
  165. package/src/store/project-affinity.ts +195 -195
  166. package/src/store/session-store.ts +259 -259
  167. package/src/store/sqlite-store.ts +339 -339
  168. package/src/team/event-bus.ts +76 -76
  169. package/src/team/file-locks.ts +173 -173
  170. package/src/team/handoff.ts +167 -167
  171. package/src/team/messages.ts +203 -203
  172. package/src/team/poll.ts +132 -132
  173. package/src/team/tasks.ts +211 -211
  174. package/src/wiki/generator.ts +237 -237
  175. package/src/wiki/knowledge-graph.ts +334 -334
  176. package/src/wiki/types.ts +85 -85
  177. package/src/workspace/mcp-adapters/codex.ts +191 -191
  178. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  179. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  180. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  181. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  182. package/src/workspace/mcp-adapters/trae.ts +134 -134
  183. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  184. package/src/workspace/sanitizer.ts +60 -60
  185. package/src/workspace/workflow-sync.ts +131 -131
@@ -3,6 +3,7 @@ import { readdirSync, readFileSync, statSync, type Dirent } from 'node:fs';
3
3
  import { join, relative } from 'node:path';
4
4
  import type { CodeEdge, CodeFile, CodeSymbol } from './types.js';
5
5
  import { makeCodeEdgeId, makeCodeFileId, makeCodeSymbolId, normalizeCodePath } from './ids.js';
6
+ import { isCodeGraphExcludedPath, normalizeCodeGraphExcludePatterns } from './exclude.js';
6
7
 
7
8
  export interface LiteIndexOptions {
8
9
  projectId: string;
@@ -169,19 +170,6 @@ function languageForPath(path: string): string {
169
170
  return LANGUAGE_BY_EXTENSION.get(ext) ?? 'unknown';
170
171
  }
171
172
 
172
- function isExcluded(path: string, exclude: string[]): boolean {
173
- const normalized = normalizeCodePath(path);
174
- return exclude.some((pattern) => {
175
- const p = normalizeCodePath(pattern);
176
- if (p.endsWith('/**')) {
177
- const base = p.slice(0, -3);
178
- return normalized === base || normalized.startsWith(`${base}/`);
179
- }
180
- if (p.startsWith('**/')) return normalized.endsWith(p.slice(3));
181
- return normalized === p || normalized.startsWith(`${p}/`);
182
- });
183
- }
184
-
185
173
  function walk(root: string, exclude: string[], maxFiles: number): string[] {
186
174
  const out: string[] = [];
187
175
  const visit = (dir: string) => {
@@ -196,9 +184,10 @@ function walk(root: string, exclude: string[], maxFiles: number): string[] {
196
184
  for (const entry of entries) {
197
185
  const abs = join(dir, entry.name);
198
186
  const rel = normalizeCodePath(relative(root, abs));
199
- if (isExcluded(rel, exclude)) continue;
187
+ if (isCodeGraphExcludedPath(rel, exclude)) continue;
200
188
  if (entry.isDirectory()) {
201
- if (entry.name === '.git' || entry.name === 'node_modules') continue;
189
+ if (entry.name === '.git' || entry.name === '.worktrees' || entry.name === '.tmp' || entry.name === 'node_modules') continue;
190
+ if (rel === '.claude/worktrees' || rel.startsWith('.claude/worktrees/')) continue;
202
191
  visit(abs);
203
192
  if (out.length >= maxFiles) return;
204
193
  continue;
@@ -289,15 +278,7 @@ function extractImportEdges(projectId: string, file: CodeFile, text: string, ind
289
278
  }
290
279
 
291
280
  export async function indexProjectLite(options: LiteIndexOptions): Promise<LiteIndexResult> {
292
- const exclude = options.exclude ?? [
293
- 'node_modules/**',
294
- 'dist/**',
295
- 'build/**',
296
- 'coverage/**',
297
- '.next/**',
298
- '.turbo/**',
299
- '.git/**',
300
- ];
281
+ const exclude = normalizeCodeGraphExcludePatterns(options.exclude);
301
282
  const maxFiles = options.maxFiles ?? 5000;
302
283
  const indexedAt = new Date().toISOString();
303
284
  const paths = walk(options.projectRoot, exclude, maxFiles);
@@ -2,6 +2,7 @@ import type { ProjectInfo } from '../types.js';
2
2
  import { evaluateCodeRefFreshness } from './freshness.js';
3
3
  import type { CodeFile, CodeRefStatus, CodeSymbol, ObservationCodeRef } from './types.js';
4
4
  import type { CodeGraphStore } from './store.js';
5
+ import { isCodeGraphExcludedPath } from './exclude.js';
5
6
 
6
7
  export interface ProjectContextObservation {
7
8
  id: number;
@@ -77,25 +78,17 @@ function countLanguages(files: CodeFile[]): LanguageSummary[] {
77
78
  .sort((a, b) => a.language.localeCompare(b.language));
78
79
  }
79
80
 
80
- function normalizePath(path: string): string {
81
- return path.replace(/\\/g, '/');
82
- }
83
-
84
- function isGeneratedPath(path: string): boolean {
85
- return /(^|\/)(dist|build|coverage|\.next|\.turbo|node_modules)\//i.test(normalizePath(path));
86
- }
87
-
88
81
  function suggestedReadRank(path: string): number {
89
- const normalized = normalizePath(path);
82
+ const normalized = path.replace(/\\/g, '/');
90
83
  if (normalized.startsWith('src/')) return 0;
91
84
  if (normalized.startsWith('tests/') || normalized.startsWith('test/')) return 0;
92
85
  if (normalized.startsWith('packages/') && normalized.includes('/src/')) return 2;
93
86
  return 3;
94
87
  }
95
88
 
96
- function compactSuggestedReads(paths: string[], limit = 8): string[] {
89
+ function compactSuggestedReads(paths: string[], limit = 8, exclude?: string[]): string[] {
97
90
  return uniq(paths)
98
- .filter(path => !isGeneratedPath(path))
91
+ .filter(path => !isCodeGraphExcludedPath(path, exclude))
99
92
  .sort((a, b) => suggestedReadRank(a) - suggestedReadRank(b))
100
93
  .slice(0, limit);
101
94
  }
@@ -104,6 +97,7 @@ function collectGraph(
104
97
  store: CodeGraphStore,
105
98
  projectId: string,
106
99
  observations: ProjectContextObservation[],
100
+ exclude?: string[],
107
101
  ): {
108
102
  files: CodeFile[];
109
103
  symbols: CodeSymbol[];
@@ -135,7 +129,9 @@ function collectGraph(
135
129
 
136
130
  const observation = observationsById.get(ref.observationId);
137
131
  if (!observation) continue;
138
- if (result.status === 'current' && file) suggestedReads.push(file.path);
132
+ const excluded = file ? isCodeGraphExcludedPath(file.path, exclude) : false;
133
+ if (result.status === 'current' && file && !excluded) suggestedReads.push(file.path);
134
+ if (excluded) continue;
139
135
  sources.push({
140
136
  observationId: observation.id,
141
137
  title: observation.title,
@@ -153,7 +149,7 @@ function collectGraph(
153
149
  refs,
154
150
  freshness,
155
151
  sources,
156
- suggestedReads: compactSuggestedReads(suggestedReads),
152
+ suggestedReads: compactSuggestedReads(suggestedReads, 8, exclude),
157
153
  };
158
154
  }
159
155
 
@@ -161,9 +157,10 @@ export function buildProjectContextOverview(input: {
161
157
  project: Pick<ProjectInfo, 'id' | 'name' | 'rootPath'>;
162
158
  store: CodeGraphStore;
163
159
  observations: ProjectContextObservation[];
160
+ exclude?: string[];
164
161
  }): ProjectContextOverview {
165
162
  const active = activeObservations(input.observations, input.project.id);
166
- const graph = collectGraph(input.store, input.project.id, active);
163
+ const graph = collectGraph(input.store, input.project.id, active, input.exclude);
167
164
  const status = input.store.status(input.project.id);
168
165
 
169
166
  return {
@@ -190,10 +187,11 @@ export function buildProjectContextExplain(input: {
190
187
  project: Pick<ProjectInfo, 'id' | 'name' | 'rootPath'>;
191
188
  store: CodeGraphStore;
192
189
  observations: ProjectContextObservation[];
190
+ exclude?: string[];
193
191
  }): ProjectContextExplain {
194
192
  const overview = buildProjectContextOverview(input);
195
193
  const active = activeObservations(input.observations, input.project.id);
196
- const graph = collectGraph(input.store, input.project.id, active);
194
+ const graph = collectGraph(input.store, input.project.id, active, input.exclude);
197
195
  return {
198
196
  project: input.project,
199
197
  sources: graph.sources.sort((a, b) => a.observationId - b.observationId || (a.path ?? '').localeCompare(b.path ?? '')),
@@ -1,74 +1,74 @@
1
- /**
2
- * Token Budget Manager
3
- *
4
- * Provides token counting and budget management for Progressive Disclosure.
5
- * Source: gpt-tokenizer (737 stars, JS port of OpenAI's tiktoken)
6
- *
7
- * Used by the Compact Engine to determine which layer of detail
8
- * fits within the caller's token budget.
9
- */
10
-
11
- import { countTokens, isWithinTokenLimit } from 'gpt-tokenizer';
12
-
13
- /**
14
- * Count tokens in a string.
15
- */
16
- export function countTextTokens(text: string): number {
17
- return countTokens(text);
18
- }
19
-
20
- /**
21
- * Check if text fits within a token limit.
22
- * Returns the token count if within limit, false otherwise.
23
- */
24
- export function fitsInBudget(text: string, limit: number): number | false {
25
- return isWithinTokenLimit(text, limit);
26
- }
27
-
28
- /**
29
- * Truncate text to fit within a token budget.
30
- * Truncates at sentence boundaries when possible.
31
- */
32
- export function truncateToTokenBudget(text: string, budget: number): string {
33
- if (fitsInBudget(text, budget) !== false) {
34
- return text;
35
- }
36
-
37
- // Binary search for the right length
38
- const sentences = text.split(/(?<=[.!?])\s+/);
39
- let result = '';
40
-
41
- for (const sentence of sentences) {
42
- const candidate = result ? `${result} ${sentence}` : sentence;
43
- if (fitsInBudget(candidate, budget) === false) {
44
- break;
45
- }
46
- result = candidate;
47
- }
48
-
49
- // If no complete sentence fits, truncate by characters
50
- if (!result) {
51
- // Rough estimate: 1 token ≈ 4 chars for English, ≈ 1.5 chars for Chinese
52
- const estimatedChars = budget * 2;
53
- result = text.slice(0, estimatedChars);
54
- // Refine
55
- while (fitsInBudget(result, budget) === false && result.length > 0) {
56
- result = result.slice(0, Math.floor(result.length * 0.9));
57
- }
58
- if (result.length < text.length) {
59
- result += '...';
60
- }
61
- }
62
-
63
- return result;
64
- }
65
-
66
- /**
67
- * Estimate the token cost of an IndexEntry line.
68
- * Used to predict compact index size.
69
- */
70
- export function estimateIndexEntryTokens(title: string): number {
71
- // Format: "| #ID | Time | Icon | Title | ~Tokens |"
72
- // Overhead is roughly 15 tokens for formatting
73
- return countTextTokens(title) + 15;
74
- }
1
+ /**
2
+ * Token Budget Manager
3
+ *
4
+ * Provides token counting and budget management for Progressive Disclosure.
5
+ * Source: gpt-tokenizer (737 stars, JS port of OpenAI's tiktoken)
6
+ *
7
+ * Used by the Compact Engine to determine which layer of detail
8
+ * fits within the caller's token budget.
9
+ */
10
+
11
+ import { countTokens, isWithinTokenLimit } from 'gpt-tokenizer';
12
+
13
+ /**
14
+ * Count tokens in a string.
15
+ */
16
+ export function countTextTokens(text: string): number {
17
+ return countTokens(text);
18
+ }
19
+
20
+ /**
21
+ * Check if text fits within a token limit.
22
+ * Returns the token count if within limit, false otherwise.
23
+ */
24
+ export function fitsInBudget(text: string, limit: number): number | false {
25
+ return isWithinTokenLimit(text, limit);
26
+ }
27
+
28
+ /**
29
+ * Truncate text to fit within a token budget.
30
+ * Truncates at sentence boundaries when possible.
31
+ */
32
+ export function truncateToTokenBudget(text: string, budget: number): string {
33
+ if (fitsInBudget(text, budget) !== false) {
34
+ return text;
35
+ }
36
+
37
+ // Binary search for the right length
38
+ const sentences = text.split(/(?<=[.!?])\s+/);
39
+ let result = '';
40
+
41
+ for (const sentence of sentences) {
42
+ const candidate = result ? `${result} ${sentence}` : sentence;
43
+ if (fitsInBudget(candidate, budget) === false) {
44
+ break;
45
+ }
46
+ result = candidate;
47
+ }
48
+
49
+ // If no complete sentence fits, truncate by characters
50
+ if (!result) {
51
+ // Rough estimate: 1 token ≈ 4 chars for English, ≈ 1.5 chars for Chinese
52
+ const estimatedChars = budget * 2;
53
+ result = text.slice(0, estimatedChars);
54
+ // Refine
55
+ while (fitsInBudget(result, budget) === false && result.length > 0) {
56
+ result = result.slice(0, Math.floor(result.length * 0.9));
57
+ }
58
+ if (result.length < text.length) {
59
+ result += '...';
60
+ }
61
+ }
62
+
63
+ return result;
64
+ }
65
+
66
+ /**
67
+ * Estimate the token cost of an IndexEntry line.
68
+ * Used to predict compact index size.
69
+ */
70
+ export function estimateIndexEntryTokens(title: string): number {
71
+ // Format: "| #ID | Time | Icon | Title | ~Tokens |"
72
+ // Overhead is roughly 15 tokens for formatting
73
+ return countTextTokens(title) + 15;
74
+ }
@@ -1,59 +1,59 @@
1
- /**
2
- * Behavior Configuration Reader
3
- *
4
- * Reads behavior settings from ~/.memorix/config.json.
5
- * Falls back to sensible defaults if config is missing.
6
- */
7
-
8
- import { readFileSync } from 'node:fs';
9
- import { join } from 'node:path';
10
- import { homedir } from 'node:os';
11
-
12
- export interface BehaviorConfig {
13
- sessionInject: 'full' | 'minimal' | 'silent';
14
- syncAdvisory: boolean;
15
- autoCleanup: boolean;
16
- formationMode: 'shadow' | 'active' | 'fallback';
17
- }
18
-
19
- const DEFAULTS: BehaviorConfig = {
20
- sessionInject: 'minimal',
21
- syncAdvisory: true,
22
- autoCleanup: true,
23
- formationMode: 'active',
24
- };
25
-
26
- let cached: BehaviorConfig | null = null;
27
-
28
- /**
29
- * Load behavior config from ~/.memorix/config.json.
30
- * Caches after first read. Returns defaults if file is missing.
31
- */
32
- export function getBehaviorConfig(): BehaviorConfig {
33
- if (cached) return cached;
34
-
35
- try {
36
- const configPath = join(homedir(), '.memorix', 'config.json');
37
- const raw = readFileSync(configPath, 'utf-8');
38
- const config = JSON.parse(raw);
39
- const behavior = config.behavior ?? {};
40
-
41
- cached = {
42
- sessionInject: behavior.sessionInject ?? DEFAULTS.sessionInject,
43
- syncAdvisory: behavior.syncAdvisory ?? DEFAULTS.syncAdvisory,
44
- autoCleanup: behavior.autoCleanup ?? DEFAULTS.autoCleanup,
45
- formationMode: behavior.formationMode ?? DEFAULTS.formationMode,
46
- };
47
- } catch {
48
- cached = { ...DEFAULTS };
49
- }
50
-
51
- return cached;
52
- }
53
-
54
- /**
55
- * Reset cached config (for testing or after config change).
56
- */
57
- export function resetBehaviorConfigCache(): void {
58
- cached = null;
59
- }
1
+ /**
2
+ * Behavior Configuration Reader
3
+ *
4
+ * Reads behavior settings from ~/.memorix/config.json.
5
+ * Falls back to sensible defaults if config is missing.
6
+ */
7
+
8
+ import { readFileSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { homedir } from 'node:os';
11
+
12
+ export interface BehaviorConfig {
13
+ sessionInject: 'full' | 'minimal' | 'silent';
14
+ syncAdvisory: boolean;
15
+ autoCleanup: boolean;
16
+ formationMode: 'shadow' | 'active' | 'fallback';
17
+ }
18
+
19
+ const DEFAULTS: BehaviorConfig = {
20
+ sessionInject: 'minimal',
21
+ syncAdvisory: true,
22
+ autoCleanup: true,
23
+ formationMode: 'active',
24
+ };
25
+
26
+ let cached: BehaviorConfig | null = null;
27
+
28
+ /**
29
+ * Load behavior config from ~/.memorix/config.json.
30
+ * Caches after first read. Returns defaults if file is missing.
31
+ */
32
+ export function getBehaviorConfig(): BehaviorConfig {
33
+ if (cached) return cached;
34
+
35
+ try {
36
+ const configPath = join(homedir(), '.memorix', 'config.json');
37
+ const raw = readFileSync(configPath, 'utf-8');
38
+ const config = JSON.parse(raw);
39
+ const behavior = config.behavior ?? {};
40
+
41
+ cached = {
42
+ sessionInject: behavior.sessionInject ?? DEFAULTS.sessionInject,
43
+ syncAdvisory: behavior.syncAdvisory ?? DEFAULTS.syncAdvisory,
44
+ autoCleanup: behavior.autoCleanup ?? DEFAULTS.autoCleanup,
45
+ formationMode: behavior.formationMode ?? DEFAULTS.formationMode,
46
+ };
47
+ } catch {
48
+ cached = { ...DEFAULTS };
49
+ }
50
+
51
+ return cached;
52
+ }
53
+
54
+ /**
55
+ * Reset cached config (for testing or after config change).
56
+ */
57
+ export function resetBehaviorConfigCache(): void {
58
+ cached = null;
59
+ }
@@ -51,6 +51,9 @@ export interface ResolvedMemorixConfig {
51
51
  excludePatterns?: string[];
52
52
  noiseKeywords?: string[];
53
53
  };
54
+ codegraph: {
55
+ excludePatterns?: string[];
56
+ };
54
57
  server: {
55
58
  transport?: 'stdio' | 'http';
56
59
  dashboard?: boolean;
@@ -147,6 +150,9 @@ export function getResolvedConfig(options: ResolvedLaneOptions = {}): ResolvedMe
147
150
  excludePatterns: firstArray(toml.git?.exclude_patterns, yaml.git?.excludePatterns),
148
151
  noiseKeywords: firstArray(toml.git?.noise_keywords, yaml.git?.noiseKeywords),
149
152
  },
153
+ codegraph: {
154
+ excludePatterns: firstArray(toml.codegraph?.exclude_patterns, yaml.codegraph?.excludePatterns),
155
+ },
150
156
  server: {
151
157
  transport: first(toml.server?.transport, yaml.server?.transport),
152
158
  dashboard: firstBool(toml.server?.dashboard, yaml.server?.dashboard),
@@ -39,6 +39,9 @@ export interface MemorixTomlConfig {
39
39
  exclude_patterns?: string[];
40
40
  noise_keywords?: string[];
41
41
  };
42
+ codegraph?: {
43
+ exclude_patterns?: string[];
44
+ };
42
45
  server?: {
43
46
  transport?: 'stdio' | 'http';
44
47
  dashboard?: boolean;
@@ -91,6 +94,7 @@ function mergeTomlConfig(base: MemorixTomlConfig, override: MemorixTomlConfig):
91
94
  embedding: { ...base.embedding, ...override.embedding },
92
95
  hooks: { ...base.hooks, ...override.hooks },
93
96
  git: { ...base.git, ...override.git },
97
+ codegraph: { ...base.codegraph, ...override.codegraph },
94
98
  server: { ...base.server, ...override.server },
95
99
  };
96
100
  }
@@ -64,6 +64,12 @@ export interface MemorixYamlConfig {
64
64
  noiseKeywords?: string[];
65
65
  };
66
66
 
67
+ /** CodeGraph / Project Context configuration */
68
+ codegraph?: {
69
+ /** File patterns to exclude from CodeGraph indexing and context suggestions */
70
+ excludePatterns?: string[];
71
+ };
72
+
67
73
  /** Behavior settings */
68
74
  behavior?: {
69
75
  /** Session start injection mode */
@@ -184,6 +190,7 @@ export function loadYamlConfig(projectRoot?: string | null): MemorixYamlConfig {
184
190
  agent: { ...userConfig.agent, ...projectConfig.agent },
185
191
  embedding: { ...userConfig.embedding, ...projectConfig.embedding },
186
192
  git: { ...userConfig.git, ...projectConfig.git },
193
+ codegraph: { ...userConfig.codegraph, ...projectConfig.codegraph },
187
194
  behavior: { ...userConfig.behavior, ...projectConfig.behavior },
188
195
  server: { ...userConfig.server, ...projectConfig.server },
189
196
  team: { ...userConfig.team, ...projectConfig.team },
@@ -1,64 +1,64 @@
1
- /**
2
- * Project classification helpers — shared by /api/projects and /api/identity.
3
- *
4
- * Three kinds:
5
- * - 'real': genuine user projects (e.g. AVIDS2/memorix, github.com/org/repo)
6
- * - 'temporary': test/demo/smoke/e2e scratch projects (local/task-*, local/smoke-*, etc.)
7
- * - 'placeholder': unresolved / obviously broken IDs (placeholder/*, __unresolved__, System32)
8
- *
9
- * A 'dirty' project is one with a clearly broken canonical ID (System32 etc.).
10
- * 'dirty' and 'temporary' are orthogonal axes:
11
- * - local/task-abc → temporary, NOT dirty
12
- * - placeholder/xxx → placeholder, dirty
13
- * - System32\something → real-looking location but dirty (broken ID)
14
- */
15
-
16
- export type ProjectKind = 'real' | 'temporary' | 'placeholder';
17
-
18
- /** Regex list — anything matching is temporary (scratch projects) */
19
- const TEMPORARY_PATTERNS: RegExp[] = [
20
- /^local\/task-/i,
21
- /^local\/smoke-/i,
22
- /^local\/release-smoke-/i,
23
- /^local\/memorix-e2e-/i,
24
- /^local\/orchestrate-/i,
25
- /^local\/scratch-/i,
26
- /^local\/tmp-/i,
27
- ];
28
-
29
- /** Regex list — anything matching is placeholder/unresolved */
30
- const PLACEHOLDER_PATTERNS: RegExp[] = [
31
- /^__unresolved__$/,
32
- /^placeholder\//i,
33
- ];
34
-
35
- /** Regex list — IDs that indicate a broken canonical ID (dirty). */
36
- const DIRTY_PATTERNS: RegExp[] = [
37
- /^placeholder\//i,
38
- /System32/i,
39
- /Microsoft VS Code/i,
40
- /node_modules/i,
41
- /\.vscode/i,
42
- /^local\/[A-Z]:\\/i,
43
- ];
44
-
45
- export function classifyProjectId(id: string): ProjectKind {
46
- if (!id) return 'placeholder';
47
- if (PLACEHOLDER_PATTERNS.some(p => p.test(id))) return 'placeholder';
48
- if (TEMPORARY_PATTERNS.some(p => p.test(id))) return 'temporary';
49
- return 'real';
50
- }
51
-
52
- export function isDirtyProjectId(id: string): boolean {
53
- if (!id) return false;
54
- return DIRTY_PATTERNS.some(p => p.test(id));
55
- }
56
-
57
- /** Friendly label for UI badges */
58
- export function projectKindLabel(kind: ProjectKind): string {
59
- switch (kind) {
60
- case 'real': return 'real';
61
- case 'temporary': return 'temporary';
62
- case 'placeholder': return 'placeholder';
63
- }
64
- }
1
+ /**
2
+ * Project classification helpers — shared by /api/projects and /api/identity.
3
+ *
4
+ * Three kinds:
5
+ * - 'real': genuine user projects (e.g. AVIDS2/memorix, github.com/org/repo)
6
+ * - 'temporary': test/demo/smoke/e2e scratch projects (local/task-*, local/smoke-*, etc.)
7
+ * - 'placeholder': unresolved / obviously broken IDs (placeholder/*, __unresolved__, System32)
8
+ *
9
+ * A 'dirty' project is one with a clearly broken canonical ID (System32 etc.).
10
+ * 'dirty' and 'temporary' are orthogonal axes:
11
+ * - local/task-abc → temporary, NOT dirty
12
+ * - placeholder/xxx → placeholder, dirty
13
+ * - System32\something → real-looking location but dirty (broken ID)
14
+ */
15
+
16
+ export type ProjectKind = 'real' | 'temporary' | 'placeholder';
17
+
18
+ /** Regex list — anything matching is temporary (scratch projects) */
19
+ const TEMPORARY_PATTERNS: RegExp[] = [
20
+ /^local\/task-/i,
21
+ /^local\/smoke-/i,
22
+ /^local\/release-smoke-/i,
23
+ /^local\/memorix-e2e-/i,
24
+ /^local\/orchestrate-/i,
25
+ /^local\/scratch-/i,
26
+ /^local\/tmp-/i,
27
+ ];
28
+
29
+ /** Regex list — anything matching is placeholder/unresolved */
30
+ const PLACEHOLDER_PATTERNS: RegExp[] = [
31
+ /^__unresolved__$/,
32
+ /^placeholder\//i,
33
+ ];
34
+
35
+ /** Regex list — IDs that indicate a broken canonical ID (dirty). */
36
+ const DIRTY_PATTERNS: RegExp[] = [
37
+ /^placeholder\//i,
38
+ /System32/i,
39
+ /Microsoft VS Code/i,
40
+ /node_modules/i,
41
+ /\.vscode/i,
42
+ /^local\/[A-Z]:\\/i,
43
+ ];
44
+
45
+ export function classifyProjectId(id: string): ProjectKind {
46
+ if (!id) return 'placeholder';
47
+ if (PLACEHOLDER_PATTERNS.some(p => p.test(id))) return 'placeholder';
48
+ if (TEMPORARY_PATTERNS.some(p => p.test(id))) return 'temporary';
49
+ return 'real';
50
+ }
51
+
52
+ export function isDirtyProjectId(id: string): boolean {
53
+ if (!id) return false;
54
+ return DIRTY_PATTERNS.some(p => p.test(id));
55
+ }
56
+
57
+ /** Friendly label for UI badges */
58
+ export function projectKindLabel(kind: ProjectKind): string {
59
+ switch (kind) {
60
+ case 'real': return 'real';
61
+ case 'temporary': return 'temporary';
62
+ case 'placeholder': return 'placeholder';
63
+ }
64
+ }