memorix 1.2.0 → 1.2.2

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 (212) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +18 -4
  3. package/README.zh-CN.md +18 -4
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +15919 -14055
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1997 -1021
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +8481 -8005
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +30 -1
  13. package/dist/sdk.d.ts +7 -2
  14. package/dist/sdk.js +2022 -1024
  15. package/dist/sdk.js.map +1 -1
  16. package/dist/types.d.ts +49 -1
  17. package/dist/types.js.map +1 -1
  18. package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
  19. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
  20. package/docs/API_REFERENCE.md +27 -5
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/DEVELOPMENT.md +4 -0
  23. package/docs/README.md +1 -1
  24. package/docs/SETUP.md +7 -1
  25. package/docs/dev-log/progress.txt +91 -11
  26. package/docs/knowledge/workflows/memorix-release.md +57 -0
  27. package/package.json +1 -1
  28. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  29. package/src/audit/index.ts +156 -156
  30. package/src/cli/command-guide.ts +192 -0
  31. package/src/cli/commands/audit-list.ts +89 -89
  32. package/src/cli/commands/audit.ts +9 -4
  33. package/src/cli/commands/background.ts +659 -659
  34. package/src/cli/commands/cleanup.ts +5 -1
  35. package/src/cli/commands/codegraph.ts +17 -8
  36. package/src/cli/commands/context.ts +3 -2
  37. package/src/cli/commands/doctor.ts +4 -2
  38. package/src/cli/commands/explain.ts +9 -3
  39. package/src/cli/commands/formation.ts +48 -48
  40. package/src/cli/commands/git-hook-install.ts +111 -111
  41. package/src/cli/commands/handoff.ts +75 -61
  42. package/src/cli/commands/hooks-status.ts +63 -63
  43. package/src/cli/commands/identity.ts +116 -0
  44. package/src/cli/commands/ingest-commit.ts +153 -153
  45. package/src/cli/commands/ingest-image.ts +71 -69
  46. package/src/cli/commands/ingest-log.ts +180 -180
  47. package/src/cli/commands/ingest.ts +44 -44
  48. package/src/cli/commands/integrate-shared.ts +15 -15
  49. package/src/cli/commands/knowledge.ts +40 -0
  50. package/src/cli/commands/lock.ts +93 -92
  51. package/src/cli/commands/memory.ts +58 -21
  52. package/src/cli/commands/message.ts +123 -118
  53. package/src/cli/commands/operator-shared.ts +98 -3
  54. package/src/cli/commands/poll.ts +74 -64
  55. package/src/cli/commands/purge-all-memory.ts +85 -85
  56. package/src/cli/commands/purge-project-memory.ts +83 -83
  57. package/src/cli/commands/reasoning.ts +135 -121
  58. package/src/cli/commands/retention.ts +9 -4
  59. package/src/cli/commands/serve-http.ts +22 -43
  60. package/src/cli/commands/serve-shared.ts +118 -118
  61. package/src/cli/commands/session.ts +29 -3
  62. package/src/cli/commands/setup.ts +9 -3
  63. package/src/cli/commands/skills.ts +124 -119
  64. package/src/cli/commands/status.ts +4 -3
  65. package/src/cli/commands/task.ts +193 -184
  66. package/src/cli/commands/team.ts +14 -10
  67. package/src/cli/commands/transfer.ts +108 -55
  68. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  69. package/src/cli/identity.ts +89 -0
  70. package/src/cli/index.ts +96 -19
  71. package/src/cli/invocation.ts +115 -0
  72. package/src/cli/tui/ChatView.tsx +234 -234
  73. package/src/cli/tui/CommandBar.tsx +312 -312
  74. package/src/cli/tui/ContextRail.tsx +118 -118
  75. package/src/cli/tui/HeaderBar.tsx +72 -72
  76. package/src/cli/tui/LogoBanner.tsx +51 -51
  77. package/src/cli/tui/Sidebar.tsx +179 -179
  78. package/src/cli/tui/chat-service.ts +41 -18
  79. package/src/cli/tui/data.ts +23 -44
  80. package/src/cli/tui/index.ts +41 -41
  81. package/src/cli/tui/markdown-render.tsx +371 -371
  82. package/src/cli/tui/operator-context.ts +60 -0
  83. package/src/cli/tui/use-mouse.ts +157 -157
  84. package/src/cli/tui/useNavigation.ts +56 -56
  85. package/src/cli/tui/views/MemoryView.tsx +10 -8
  86. package/src/cli/update-checker.ts +211 -211
  87. package/src/cli/version.ts +7 -7
  88. package/src/cli/workbench.ts +1 -1
  89. package/src/codegraph/auto-context.ts +34 -17
  90. package/src/codegraph/context-pack.ts +1 -0
  91. package/src/codegraph/current-facts.ts +19 -1
  92. package/src/codegraph/project-context.ts +2 -0
  93. package/src/codegraph/task-lens.ts +49 -5
  94. package/src/compact/engine.ts +26 -10
  95. package/src/compact/index-format.ts +25 -2
  96. package/src/compact/token-budget.ts +74 -74
  97. package/src/dashboard/project-classification.ts +64 -64
  98. package/src/dashboard/server.ts +58 -52
  99. package/src/embedding/fastembed-provider.ts +142 -142
  100. package/src/embedding/transformers-provider.ts +111 -111
  101. package/src/git/extractor.ts +209 -209
  102. package/src/git/hooks-path.ts +85 -85
  103. package/src/hooks/admission.ts +117 -0
  104. package/src/hooks/handler.ts +98 -91
  105. package/src/hooks/pattern-detector.ts +173 -173
  106. package/src/hooks/significance-filter.ts +250 -250
  107. package/src/knowledge/claims.ts +51 -1
  108. package/src/knowledge/context-assembly.ts +97 -0
  109. package/src/knowledge/types.ts +1 -0
  110. package/src/knowledge/workflows.ts +34 -3
  111. package/src/knowledge/workset.ts +179 -10
  112. package/src/llm/memory-manager.ts +328 -328
  113. package/src/llm/provider.ts +885 -885
  114. package/src/llm/quality.ts +248 -248
  115. package/src/memory/admission.ts +57 -0
  116. package/src/memory/attribution-guard.ts +249 -249
  117. package/src/memory/auto-relations.ts +21 -0
  118. package/src/memory/consolidation.ts +13 -2
  119. package/src/memory/disclosure-policy.ts +140 -135
  120. package/src/memory/entity-extractor.ts +197 -197
  121. package/src/memory/export-import.ts +11 -3
  122. package/src/memory/formation/evaluate.ts +217 -217
  123. package/src/memory/formation/extract.ts +361 -361
  124. package/src/memory/formation/index.ts +417 -417
  125. package/src/memory/formation/resolve.ts +344 -344
  126. package/src/memory/formation/types.ts +315 -315
  127. package/src/memory/freshness.ts +122 -122
  128. package/src/memory/graph-context.ts +8 -2
  129. package/src/memory/graph-scope.ts +46 -0
  130. package/src/memory/graph.ts +197 -197
  131. package/src/memory/observations.ts +162 -4
  132. package/src/memory/quality-audit.ts +2 -0
  133. package/src/memory/refs.ts +94 -94
  134. package/src/memory/retention.ts +22 -2
  135. package/src/memory/secret-filter.ts +79 -79
  136. package/src/memory/session.ts +5 -2
  137. package/src/memory/visibility.ts +80 -0
  138. package/src/multimodal/image-loader.ts +143 -143
  139. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  140. package/src/orchestrate/adapters/claude.ts +111 -111
  141. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  142. package/src/orchestrate/adapters/codex.ts +41 -41
  143. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  144. package/src/orchestrate/adapters/gemini.ts +42 -42
  145. package/src/orchestrate/adapters/index.ts +73 -73
  146. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  147. package/src/orchestrate/adapters/opencode.ts +47 -47
  148. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  149. package/src/orchestrate/adapters/types.ts +77 -77
  150. package/src/orchestrate/capability-router.ts +284 -284
  151. package/src/orchestrate/context-compact.ts +188 -188
  152. package/src/orchestrate/cost-tracker.ts +219 -219
  153. package/src/orchestrate/error-recovery.ts +191 -191
  154. package/src/orchestrate/evidence.ts +140 -140
  155. package/src/orchestrate/ledger.ts +110 -110
  156. package/src/orchestrate/memorix-bridge.ts +378 -340
  157. package/src/orchestrate/output-budget.ts +80 -80
  158. package/src/orchestrate/permission.ts +152 -152
  159. package/src/orchestrate/pipeline-trace.ts +131 -131
  160. package/src/orchestrate/prompt-builder.ts +155 -155
  161. package/src/orchestrate/ring-buffer.ts +37 -37
  162. package/src/orchestrate/task-graph.ts +389 -389
  163. package/src/orchestrate/verify-gate.ts +33 -10
  164. package/src/orchestrate/worktree.ts +232 -232
  165. package/src/project/aliases.ts +374 -374
  166. package/src/project/detector.ts +268 -268
  167. package/src/rules/adapters/claude-code.ts +99 -99
  168. package/src/rules/adapters/codex.ts +97 -97
  169. package/src/rules/adapters/copilot.ts +124 -124
  170. package/src/rules/adapters/cursor.ts +114 -114
  171. package/src/rules/adapters/kiro.ts +126 -126
  172. package/src/rules/adapters/trae.ts +56 -56
  173. package/src/rules/adapters/windsurf.ts +83 -83
  174. package/src/rules/syncer.ts +235 -235
  175. package/src/runtime/control-plane-maintenance.ts +1 -0
  176. package/src/runtime/isolated-maintenance.ts +1 -0
  177. package/src/runtime/lifecycle.ts +18 -0
  178. package/src/runtime/maintenance-jobs.ts +1 -0
  179. package/src/runtime/maintenance-runner.ts +2 -0
  180. package/src/runtime/project-maintenance.ts +89 -0
  181. package/src/sdk.ts +334 -304
  182. package/src/search/intent-detector.ts +289 -289
  183. package/src/search/query-expansion.ts +52 -52
  184. package/src/server/formation-timeout.ts +27 -27
  185. package/src/server.ts +334 -93
  186. package/src/skills/mini-skills.ts +386 -386
  187. package/src/store/chat-store.ts +119 -119
  188. package/src/store/graph-store.ts +249 -249
  189. package/src/store/mini-skill-store.ts +349 -349
  190. package/src/store/orama-store.ts +61 -6
  191. package/src/store/persistence-json.ts +212 -212
  192. package/src/store/persistence.ts +291 -291
  193. package/src/store/project-affinity.ts +195 -195
  194. package/src/store/sqlite-db.ts +23 -1
  195. package/src/store/sqlite-store.ts +12 -2
  196. package/src/team/event-bus.ts +76 -76
  197. package/src/team/file-locks.ts +173 -173
  198. package/src/team/handoff.ts +168 -161
  199. package/src/team/messages.ts +203 -203
  200. package/src/team/poll.ts +132 -132
  201. package/src/team/tasks.ts +211 -211
  202. package/src/types.ts +51 -0
  203. package/src/wiki/generator.ts +2 -0
  204. package/src/workspace/mcp-adapters/codex.ts +191 -191
  205. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  206. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  207. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  208. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  209. package/src/workspace/mcp-adapters/trae.ts +134 -134
  210. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  211. package/src/workspace/sanitizer.ts +60 -60
  212. package/src/workspace/workflow-sync.ts +131 -131
@@ -1,73 +1,75 @@
1
- import { defineCommand } from 'citty';
2
- import { readFileSync } from 'node:fs';
3
- import path from 'node:path';
4
- import { analyzeImage } from '../../multimodal/image-loader.js';
5
- import { storeObservation } from '../../memory/observations.js';
6
- import { emitError, emitResult, getCliProjectContext } from './operator-shared.js';
7
-
8
- function inferMimeType(filePath: string): string {
9
- const ext = path.extname(filePath).toLowerCase();
10
- switch (ext) {
11
- case '.jpg':
12
- case '.jpeg':
13
- return 'image/jpeg';
14
- case '.gif':
15
- return 'image/gif';
16
- case '.webp':
17
- return 'image/webp';
18
- default:
19
- return 'image/png';
20
- }
21
- }
22
-
23
- export default defineCommand({
24
- meta: {
25
- name: 'image',
26
- description: 'Analyze an image and store the result as memory',
27
- },
28
- args: {
29
- path: { type: 'string', description: 'Path to the image file' },
30
- prompt: { type: 'string', description: 'Custom analysis prompt' },
1
+ import { defineCommand } from 'citty';
2
+ import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { analyzeImage } from '../../multimodal/image-loader.js';
5
+ import { storeObservation } from '../../memory/observations.js';
6
+ import { emitError, emitResult, getCliProjectContext, resolveCliWriteScope } from './operator-shared.js';
7
+
8
+ function inferMimeType(filePath: string): string {
9
+ const ext = path.extname(filePath).toLowerCase();
10
+ switch (ext) {
11
+ case '.jpg':
12
+ case '.jpeg':
13
+ return 'image/jpeg';
14
+ case '.gif':
15
+ return 'image/gif';
16
+ case '.webp':
17
+ return 'image/webp';
18
+ default:
19
+ return 'image/png';
20
+ }
21
+ }
22
+
23
+ export default defineCommand({
24
+ meta: {
25
+ name: 'image',
26
+ description: 'Analyze an image and store the result as memory',
27
+ },
28
+ args: {
29
+ path: { type: 'string', description: 'Path to the image file' },
30
+ prompt: { type: 'string', description: 'Custom analysis prompt' },
31
31
  mimeType: { type: 'string', description: 'Explicit MIME type override' },
32
- json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
33
- },
34
- run: async ({ args }) => {
35
- const asJson = !!args.json;
36
- try {
37
- const imagePath = (args.path as string | undefined)?.trim();
38
- if (!imagePath) {
39
- emitError('path is required for "memorix ingest image"', asJson);
40
- return;
41
- }
42
- const { project } = await getCliProjectContext();
43
- const resolvedPath = path.resolve(process.cwd(), imagePath);
44
- const base64 = readFileSync(resolvedPath).toString('base64');
45
- const filename = path.basename(resolvedPath);
46
- const analysis = await analyzeImage({
47
- base64,
48
- filename,
49
- mimeType: (args.mimeType as string | undefined) || inferMimeType(resolvedPath),
50
- prompt: args.prompt as string | undefined,
51
- });
52
-
32
+ visibility: { type: 'string', description: 'Memory visibility: project (default), personal, or team' },
33
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
34
+ },
35
+ run: async ({ args }) => {
36
+ const asJson = !!args.json;
37
+ try {
38
+ const imagePath = (args.path as string | undefined)?.trim();
39
+ if (!imagePath) {
40
+ emitError('path is required for "memorix ingest image"', asJson);
41
+ return;
42
+ }
43
+ const { project, reader, identity } = await getCliProjectContext();
44
+ const resolvedPath = path.resolve(project.rootPath, imagePath);
45
+ const base64 = readFileSync(resolvedPath).toString('base64');
46
+ const filename = path.basename(resolvedPath);
47
+ const analysis = await analyzeImage({
48
+ base64,
49
+ filename,
50
+ mimeType: (args.mimeType as string | undefined) || inferMimeType(resolvedPath),
51
+ prompt: args.prompt as string | undefined,
52
+ });
53
+
53
54
  const result = await storeObservation({
54
- entityName: filename.replace(/\.[^.]+$/, '') || `image-${Date.now()}`,
55
- type: 'discovery',
56
- title: `Image analysis: ${filename}`,
57
- narrative: analysis.description,
58
- concepts: analysis.tags,
59
- facts: analysis.entities,
55
+ entityName: filename.replace(/\.[^.]+$/, '') || `image-${Date.now()}`,
56
+ type: 'discovery',
57
+ title: `Image analysis: ${filename}`,
58
+ narrative: analysis.description,
59
+ concepts: analysis.tags,
60
+ facts: analysis.entities,
60
61
  projectId: project.id,
61
62
  source: 'manual',
62
- });
63
-
64
- emitResult(
65
- { project, analysis, observation: result.observation },
66
- `Stored image analysis #${result.observation.id}: ${filename}`,
67
- asJson,
68
- );
69
- } catch (error) {
70
- emitError(error instanceof Error ? error.message : String(error), asJson);
71
- }
72
- },
73
- });
63
+ ...resolveCliWriteScope({ reader, identity }, args.visibility as string | undefined),
64
+ });
65
+
66
+ emitResult(
67
+ { project, analysis, observation: result.observation },
68
+ `Stored image analysis #${result.observation.id}: ${filename}`,
69
+ asJson,
70
+ );
71
+ } catch (error) {
72
+ emitError(error instanceof Error ? error.message : String(error), asJson);
73
+ }
74
+ },
75
+ });
@@ -1,183 +1,183 @@
1
- /**
2
- * CLI Command: memorix ingest log
3
- *
4
- * Batch ingest recent git commits as memories.
5
- * This is the Git→Memory direction — unique to Memorix.
6
- */
7
-
8
- import { defineCommand } from 'citty';
9
- import * as p from '@clack/prompts';
10
-
11
- type IngestableCommit = {
12
- hash: string;
13
- shortHash: string;
14
- subject: string;
15
- };
16
-
17
- type IngestLogResult = {
18
- stored: number;
19
- dupSkipped: number;
20
- errSkipped: number;
21
- };
22
-
23
- /**
24
- * Shared dedup logic for batch git ingest.
25
- * Exported so tests can exercise the production path rather than reimplementing it.
26
- */
27
- export async function ingestCommitsWithDedup<T extends IngestableCommit>(
28
- commits: T[],
29
- existingHashes: Set<string>,
30
- ingestOne: (commit: T) => Promise<void>,
31
- log: (line: string) => void = console.log,
32
- ): Promise<IngestLogResult> {
33
- let stored = 0;
34
- let dupSkipped = 0;
35
- let errSkipped = 0;
36
-
37
- for (const commit of commits) {
38
- if (existingHashes.has(commit.hash)) {
39
- dupSkipped++;
1
+ /**
2
+ * CLI Command: memorix ingest log
3
+ *
4
+ * Batch ingest recent git commits as memories.
5
+ * This is the Git→Memory direction — unique to Memorix.
6
+ */
7
+
8
+ import { defineCommand } from 'citty';
9
+ import * as p from '@clack/prompts';
10
+
11
+ type IngestableCommit = {
12
+ hash: string;
13
+ shortHash: string;
14
+ subject: string;
15
+ };
16
+
17
+ type IngestLogResult = {
18
+ stored: number;
19
+ dupSkipped: number;
20
+ errSkipped: number;
21
+ };
22
+
23
+ /**
24
+ * Shared dedup logic for batch git ingest.
25
+ * Exported so tests can exercise the production path rather than reimplementing it.
26
+ */
27
+ export async function ingestCommitsWithDedup<T extends IngestableCommit>(
28
+ commits: T[],
29
+ existingHashes: Set<string>,
30
+ ingestOne: (commit: T) => Promise<void>,
31
+ log: (line: string) => void = console.log,
32
+ ): Promise<IngestLogResult> {
33
+ let stored = 0;
34
+ let dupSkipped = 0;
35
+ let errSkipped = 0;
36
+
37
+ for (const commit of commits) {
38
+ if (existingHashes.has(commit.hash)) {
39
+ dupSkipped++;
40
40
  log(` [SKIP] ${commit.shortHash} ${commit.subject} — already ingested`);
41
- continue;
42
- }
43
- try {
44
- await ingestOne(commit);
45
- stored++;
46
- existingHashes.add(commit.hash);
47
- log(` [OK] ${commit.shortHash} ${commit.subject}`);
48
- } catch (err) {
49
- errSkipped++;
50
- const message = err instanceof Error ? err.message : String(err);
41
+ continue;
42
+ }
43
+ try {
44
+ await ingestOne(commit);
45
+ stored++;
46
+ existingHashes.add(commit.hash);
47
+ log(` [OK] ${commit.shortHash} ${commit.subject}`);
48
+ } catch (err) {
49
+ errSkipped++;
50
+ const message = err instanceof Error ? err.message : String(err);
51
51
  log(` [SKIP] ${commit.shortHash} ${commit.subject} — error: ${message}`);
52
- }
53
- }
54
-
55
- return { stored, dupSkipped, errSkipped };
56
- }
57
-
58
- export default defineCommand({
59
- meta: {
60
- name: 'log',
61
- description: 'Batch ingest recent git commits as memories',
62
- },
63
- args: {
64
- count: {
65
- type: 'string',
66
- description: 'Number of recent commits to ingest (default: 10)',
67
- required: false,
68
- },
69
- },
70
- run: async ({ args }) => {
71
- const os = await import('node:os');
72
- let cwd: string;
73
- try { cwd = process.cwd(); } catch { cwd = os.homedir(); }
74
-
75
- const count = parseInt(args.count || '10', 10);
76
-
77
- p.intro(`Ingest recent ${count} commits`);
78
-
79
- try {
80
- const { getRecentCommits, ingestCommit } = await import('../../git/extractor.js');
81
- const rawCommits = getRecentCommits(cwd, count);
82
-
83
- if (rawCommits.length === 0) {
84
- console.log('No commits found.');
85
- p.outro('Nothing to ingest.');
86
- return;
87
- }
88
-
89
- // Apply noise filter
90
- const { filterCommits } = await import('../../git/noise-filter.js');
91
- const { getGitConfig } = await import('../../config.js');
92
- const gitCfg = getGitConfig();
93
- const { kept: commits, skipped: noiseCommits } = filterCommits(rawCommits, {
94
- skipMergeCommits: gitCfg.skipMergeCommits,
95
- excludePatterns: gitCfg.excludePatterns,
96
- noiseKeywords: gitCfg.noiseKeywords,
97
- });
98
-
99
- if (commits.length === 0) {
100
- console.log(`All ${rawCommits.length} commits filtered as noise.`);
101
- p.outro('Nothing to ingest.');
102
- return;
103
- }
104
-
105
- // Show commits
106
- console.log('');
107
- console.log(`Found ${rawCommits.length} commits (${noiseCommits.length} filtered as noise):`);
108
- console.log('');
109
- for (const commit of commits) {
110
- console.log(` ${commit.shortHash} ${commit.subject}`);
111
- }
112
- if (noiseCommits.length > 0) {
113
- console.log('');
114
- console.log(` Filtered (noise):`);
115
- for (const { commit, reason } of noiseCommits) {
52
+ }
53
+ }
54
+
55
+ return { stored, dupSkipped, errSkipped };
56
+ }
57
+
58
+ export default defineCommand({
59
+ meta: {
60
+ name: 'log',
61
+ description: 'Batch ingest recent git commits as memories',
62
+ },
63
+ args: {
64
+ count: {
65
+ type: 'string',
66
+ description: 'Number of recent commits to ingest (default: 10)',
67
+ required: false,
68
+ },
69
+ },
70
+ run: async ({ args }) => {
71
+ const os = await import('node:os');
72
+ let cwd: string;
73
+ try { cwd = process.cwd(); } catch { cwd = os.homedir(); }
74
+
75
+ const count = parseInt(args.count || '10', 10);
76
+
77
+ p.intro(`Ingest recent ${count} commits`);
78
+
79
+ try {
80
+ const { getRecentCommits, ingestCommit } = await import('../../git/extractor.js');
81
+ const rawCommits = getRecentCommits(cwd, count);
82
+
83
+ if (rawCommits.length === 0) {
84
+ console.log('No commits found.');
85
+ p.outro('Nothing to ingest.');
86
+ return;
87
+ }
88
+
89
+ // Apply noise filter
90
+ const { filterCommits } = await import('../../git/noise-filter.js');
91
+ const { getGitConfig } = await import('../../config.js');
92
+ const gitCfg = getGitConfig();
93
+ const { kept: commits, skipped: noiseCommits } = filterCommits(rawCommits, {
94
+ skipMergeCommits: gitCfg.skipMergeCommits,
95
+ excludePatterns: gitCfg.excludePatterns,
96
+ noiseKeywords: gitCfg.noiseKeywords,
97
+ });
98
+
99
+ if (commits.length === 0) {
100
+ console.log(`All ${rawCommits.length} commits filtered as noise.`);
101
+ p.outro('Nothing to ingest.');
102
+ return;
103
+ }
104
+
105
+ // Show commits
106
+ console.log('');
107
+ console.log(`Found ${rawCommits.length} commits (${noiseCommits.length} filtered as noise):`);
108
+ console.log('');
109
+ for (const commit of commits) {
110
+ console.log(` ${commit.shortHash} ${commit.subject}`);
111
+ }
112
+ if (noiseCommits.length > 0) {
113
+ console.log('');
114
+ console.log(` Filtered (noise):`);
115
+ for (const { commit, reason } of noiseCommits) {
116
116
  console.log(` [SKIP] ${commit.shortHash} ${commit.subject} — ${reason}`);
117
- }
118
- }
119
- console.log('');
120
-
121
- // Confirm
122
- const confirmed = await p.confirm({
123
- message: `Ingest ${commits.length} commits as memories?`,
124
- });
125
-
126
- if (p.isCancel(confirmed) || !confirmed) {
127
- p.outro('Ingest cancelled.');
128
- return;
129
- }
130
-
131
- // Store each commit
132
- const { initObservations, storeObservation } = await import('../../memory/observations.js');
133
- const { getProjectDataDir } = await import('../../store/persistence.js');
134
- const { detectProject } = await import('../../project/detector.js');
135
- const { initObservationStore, getObservationStore: getStore } = await import('../../store/obs-store.js');
136
-
137
- const project = detectProject(cwd);
138
- if (!project) {
139
- p.log.error('No .git found — not a project directory.');
140
- return;
141
- }
142
- const dataDir = await getProjectDataDir(project.id);
143
- await initObservationStore(dataDir);
144
- await initObservations(dataDir);
145
-
146
- // Dedup: load existing commit hashes to skip already-ingested commits (#48)
147
- const existingObs = await getStore().loadAll() as Array<{ commitHash?: string }>;
148
- const existingHashes = new Set(
149
- existingObs.map(o => o.commitHash).filter((hash): hash is string => typeof hash === 'string' && hash.length > 0),
150
- );
151
-
152
- const { stored, dupSkipped, errSkipped } = await ingestCommitsWithDedup(
153
- commits,
154
- existingHashes,
155
- async (commit) => {
156
- const result = ingestCommit(commit);
157
- await storeObservation({
158
- entityName: result.entityName,
159
- type: result.type as any,
160
- title: result.title,
161
- sourceDetail: 'git-ingest',
162
- narrative: result.narrative,
163
- facts: result.facts,
164
- concepts: result.concepts,
165
- filesModified: result.filesModified,
166
- projectId: project.id,
167
- source: 'git',
168
- commitHash: commit.hash,
169
- });
170
- },
171
- console.log,
172
- );
173
-
174
- const parts = [`Ingested ${stored}/${commits.length} commits`];
175
- if (dupSkipped) parts.push(`${dupSkipped} already stored`);
176
- if (errSkipped) parts.push(`${errSkipped} errors`);
177
- p.outro(parts.join(', ') + '.');
178
- } catch (err) {
179
- console.error(`Failed to ingest log: ${err}`);
180
- p.outro('Ingest failed. Make sure you are in a git repository.');
181
- }
182
- },
183
- });
117
+ }
118
+ }
119
+ console.log('');
120
+
121
+ // Confirm
122
+ const confirmed = await p.confirm({
123
+ message: `Ingest ${commits.length} commits as memories?`,
124
+ });
125
+
126
+ if (p.isCancel(confirmed) || !confirmed) {
127
+ p.outro('Ingest cancelled.');
128
+ return;
129
+ }
130
+
131
+ // Store each commit
132
+ const { initObservations, storeObservation } = await import('../../memory/observations.js');
133
+ const { getProjectDataDir } = await import('../../store/persistence.js');
134
+ const { detectProject } = await import('../../project/detector.js');
135
+ const { initObservationStore, getObservationStore: getStore } = await import('../../store/obs-store.js');
136
+
137
+ const project = detectProject(cwd);
138
+ if (!project) {
139
+ p.log.error('No .git found — not a project directory.');
140
+ return;
141
+ }
142
+ const dataDir = await getProjectDataDir(project.id);
143
+ await initObservationStore(dataDir);
144
+ await initObservations(dataDir);
145
+
146
+ // Dedup: load existing commit hashes to skip already-ingested commits (#48)
147
+ const existingObs = await getStore().loadAll() as Array<{ commitHash?: string }>;
148
+ const existingHashes = new Set(
149
+ existingObs.map(o => o.commitHash).filter((hash): hash is string => typeof hash === 'string' && hash.length > 0),
150
+ );
151
+
152
+ const { stored, dupSkipped, errSkipped } = await ingestCommitsWithDedup(
153
+ commits,
154
+ existingHashes,
155
+ async (commit) => {
156
+ const result = ingestCommit(commit);
157
+ await storeObservation({
158
+ entityName: result.entityName,
159
+ type: result.type as any,
160
+ title: result.title,
161
+ sourceDetail: 'git-ingest',
162
+ narrative: result.narrative,
163
+ facts: result.facts,
164
+ concepts: result.concepts,
165
+ filesModified: result.filesModified,
166
+ projectId: project.id,
167
+ source: 'git',
168
+ commitHash: commit.hash,
169
+ });
170
+ },
171
+ console.log,
172
+ );
173
+
174
+ const parts = [`Ingested ${stored}/${commits.length} commits`];
175
+ if (dupSkipped) parts.push(`${dupSkipped} already stored`);
176
+ if (errSkipped) parts.push(`${errSkipped} errors`);
177
+ p.outro(parts.join(', ') + '.');
178
+ } catch (err) {
179
+ console.error(`Failed to ingest log: ${err}`);
180
+ p.outro('Ingest failed. Make sure you are in a git repository.');
181
+ }
182
+ },
183
+ });
@@ -1,44 +1,44 @@
1
- /**
2
- * CLI Command: memorix ingest
3
- *
4
- * Parent command for Git→Memory and image ingestion.
5
- */
6
-
7
- import { defineCommand } from 'citty';
8
-
9
- export default defineCommand({
10
- meta: {
11
- name: 'ingest',
12
- description: 'Ingest engineering knowledge from Git and images',
13
- },
14
- subCommands: {
15
- commit: () => import('./ingest-commit.js').then((module) => module.default),
16
- log: () => import('./ingest-log.js').then((module) => module.default),
17
- image: () => import('./ingest-image.js').then((module) => module.default),
18
- },
19
- run: async ({ args }) => {
20
- const action = (args._ as string[])?.[0] || '';
21
- if (action === 'commit') {
22
- const module = await import('./ingest-commit.js');
23
- await module.default.run?.({ args: { ...args, _: [] }, rawArgs: [], cmd: module.default } as any);
24
- return;
25
- }
26
- if (action === 'log') {
27
- const module = await import('./ingest-log.js');
28
- await module.default.run?.({ args: { ...args, _: [] }, rawArgs: [], cmd: module.default } as any);
29
- return;
30
- }
31
- if (action === 'image') {
32
- const module = await import('./ingest-image.js');
33
- await module.default.run?.({ args: { ...args, _: [] }, rawArgs: [], cmd: module.default } as any);
34
- return;
35
- }
36
-
37
- console.log('Memorix Ingest Commands');
38
- console.log('');
39
- console.log('Usage:');
40
- console.log(' memorix ingest commit [--ref HEAD]');
41
- console.log(' memorix ingest log [--count 10]');
42
- console.log(' memorix ingest image --path ./diagram.png');
43
- },
44
- });
1
+ /**
2
+ * CLI Command: memorix ingest
3
+ *
4
+ * Parent command for Git→Memory and image ingestion.
5
+ */
6
+
7
+ import { defineCommand } from 'citty';
8
+
9
+ export default defineCommand({
10
+ meta: {
11
+ name: 'ingest',
12
+ description: 'Ingest engineering knowledge from Git and images',
13
+ },
14
+ subCommands: {
15
+ commit: () => import('./ingest-commit.js').then((module) => module.default),
16
+ log: () => import('./ingest-log.js').then((module) => module.default),
17
+ image: () => import('./ingest-image.js').then((module) => module.default),
18
+ },
19
+ run: async ({ args }) => {
20
+ const action = (args._ as string[])?.[0] || '';
21
+ if (action === 'commit') {
22
+ const module = await import('./ingest-commit.js');
23
+ await module.default.run?.({ args: { ...args, _: [] }, rawArgs: [], cmd: module.default } as any);
24
+ return;
25
+ }
26
+ if (action === 'log') {
27
+ const module = await import('./ingest-log.js');
28
+ await module.default.run?.({ args: { ...args, _: [] }, rawArgs: [], cmd: module.default } as any);
29
+ return;
30
+ }
31
+ if (action === 'image') {
32
+ const module = await import('./ingest-image.js');
33
+ await module.default.run?.({ args: { ...args, _: [] }, rawArgs: [], cmd: module.default } as any);
34
+ return;
35
+ }
36
+
37
+ console.log('Memorix Ingest Commands');
38
+ console.log('');
39
+ console.log('Usage:');
40
+ console.log(' memorix ingest commit [--ref HEAD]');
41
+ console.log(' memorix ingest log [--count 10]');
42
+ console.log(' memorix ingest image --path ./diagram.png');
43
+ },
44
+ });
@@ -1,15 +1,15 @@
1
- import { homedir } from 'node:os';
2
-
3
- export function getIntegrationTargetRoot(
4
- useGlobalDefaults: boolean,
5
- cwd: string,
6
- homeDir = homedir(),
7
- ): string {
8
- return useGlobalDefaults ? homeDir : cwd;
9
- }
10
-
11
- export function getIntegrationScopeLabel(useGlobalDefaults: boolean): string {
12
- return useGlobalDefaults
13
- ? 'global defaults'
14
- : 'current project';
15
- }
1
+ import { homedir } from 'node:os';
2
+
3
+ export function getIntegrationTargetRoot(
4
+ useGlobalDefaults: boolean,
5
+ cwd: string,
6
+ homeDir = homedir(),
7
+ ): string {
8
+ return useGlobalDefaults ? homeDir : cwd;
9
+ }
10
+
11
+ export function getIntegrationScopeLabel(useGlobalDefaults: boolean): string {
12
+ return useGlobalDefaults
13
+ ? 'global defaults'
14
+ : 'current project';
15
+ }