om-memory-system 3.0.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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +587 -0
  3. package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
  4. package/dist/adapters/opencode/auto-capture-host.js +29 -0
  5. package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
  6. package/dist/adapters/opencode/auto-capture-summary.js +147 -0
  7. package/dist/adapters/opencode/conversation.d.ts +2 -0
  8. package/dist/adapters/opencode/conversation.js +50 -0
  9. package/dist/adapters/opencode/user-prompt.d.ts +9 -0
  10. package/dist/adapters/opencode/user-prompt.js +26 -0
  11. package/dist/adapters/pi/capture.d.ts +41 -0
  12. package/dist/adapters/pi/capture.js +91 -0
  13. package/dist/adapters/pi/conversation.d.ts +38 -0
  14. package/dist/adapters/pi/conversation.js +118 -0
  15. package/dist/adapters/pi/extension.d.ts +18 -0
  16. package/dist/adapters/pi/extension.js +234 -0
  17. package/dist/adapters/pi/import-command.d.ts +29 -0
  18. package/dist/adapters/pi/import-command.js +232 -0
  19. package/dist/adapters/pi/profile.d.ts +39 -0
  20. package/dist/adapters/pi/profile.js +148 -0
  21. package/dist/adapters/pi/provider.d.ts +50 -0
  22. package/dist/adapters/pi/provider.js +77 -0
  23. package/dist/config.d.ts +290 -0
  24. package/dist/config.js +742 -0
  25. package/dist/core/capture-context.d.ts +4 -0
  26. package/dist/core/capture-context.js +132 -0
  27. package/dist/core/capture.d.ts +15 -0
  28. package/dist/core/capture.js +68 -0
  29. package/dist/core/extraction.d.ts +80 -0
  30. package/dist/core/extraction.js +140 -0
  31. package/dist/core/host.d.ts +52 -0
  32. package/dist/core/host.js +1 -0
  33. package/dist/core/memory-operations.d.ts +27 -0
  34. package/dist/core/memory-operations.js +247 -0
  35. package/dist/core/retrieval.d.ts +12 -0
  36. package/dist/core/retrieval.js +72 -0
  37. package/dist/importer/discovery.d.ts +31 -0
  38. package/dist/importer/discovery.js +88 -0
  39. package/dist/importer/importer.d.ts +87 -0
  40. package/dist/importer/importer.js +260 -0
  41. package/dist/importer/ledger.d.ts +44 -0
  42. package/dist/importer/ledger.js +137 -0
  43. package/dist/importer/session-loader.d.ts +19 -0
  44. package/dist/importer/session-loader.js +12 -0
  45. package/dist/index.d.ts +24 -0
  46. package/dist/index.js +601 -0
  47. package/dist/plugin.d.ts +9 -0
  48. package/dist/plugin.js +11 -0
  49. package/dist/services/ai/ai-provider-factory.d.ts +7 -0
  50. package/dist/services/ai/ai-provider-factory.js +41 -0
  51. package/dist/services/ai/api-key-placeholder.d.ts +1 -0
  52. package/dist/services/ai/api-key-placeholder.js +14 -0
  53. package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
  54. package/dist/services/ai/internal-capture-sessions.js +33 -0
  55. package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
  56. package/dist/services/ai/opencode-diagnostics.js +36 -0
  57. package/dist/services/ai/opencode-host-config.d.ts +10 -0
  58. package/dist/services/ai/opencode-host-config.js +51 -0
  59. package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
  60. package/dist/services/ai/opencode-provider-loader.js +4 -0
  61. package/dist/services/ai/opencode-provider.d.ts +92 -0
  62. package/dist/services/ai/opencode-provider.js +419 -0
  63. package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
  64. package/dist/services/ai/opencode-sdk-client.js +38 -0
  65. package/dist/services/ai/profile-llm-client.d.ts +2 -0
  66. package/dist/services/ai/profile-llm-client.js +27 -0
  67. package/dist/services/ai/provider-config.d.ts +17 -0
  68. package/dist/services/ai/provider-config.js +30 -0
  69. package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
  70. package/dist/services/ai/providers/anthropic-messages.js +220 -0
  71. package/dist/services/ai/providers/base-provider.d.ts +24 -0
  72. package/dist/services/ai/providers/base-provider.js +24 -0
  73. package/dist/services/ai/providers/google-gemini.d.ts +15 -0
  74. package/dist/services/ai/providers/google-gemini.js +228 -0
  75. package/dist/services/ai/providers/minimax.d.ts +35 -0
  76. package/dist/services/ai/providers/minimax.js +55 -0
  77. package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
  78. package/dist/services/ai/providers/openai-chat-completion.js +387 -0
  79. package/dist/services/ai/providers/openai-responses.d.ts +13 -0
  80. package/dist/services/ai/providers/openai-responses.js +182 -0
  81. package/dist/services/ai/providers/orcarouter.d.ts +51 -0
  82. package/dist/services/ai/providers/orcarouter.js +66 -0
  83. package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
  84. package/dist/services/ai/session/ai-session-manager.js +211 -0
  85. package/dist/services/ai/session/session-types.d.ts +42 -0
  86. package/dist/services/ai/session/session-types.js +1 -0
  87. package/dist/services/ai/tools/tool-schema.d.ts +40 -0
  88. package/dist/services/ai/tools/tool-schema.js +24 -0
  89. package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
  90. package/dist/services/ai/validators/user-profile-validator.js +114 -0
  91. package/dist/services/api-handlers.d.ts +186 -0
  92. package/dist/services/api-handlers.js +1284 -0
  93. package/dist/services/auth-token.d.ts +14 -0
  94. package/dist/services/auth-token.js +59 -0
  95. package/dist/services/auto-capture.d.ts +3 -0
  96. package/dist/services/auto-capture.js +127 -0
  97. package/dist/services/cleanup-service.d.ts +22 -0
  98. package/dist/services/cleanup-service.js +109 -0
  99. package/dist/services/client.d.ts +134 -0
  100. package/dist/services/client.js +327 -0
  101. package/dist/services/context.d.ts +10 -0
  102. package/dist/services/context.js +27 -0
  103. package/dist/services/cors.d.ts +12 -0
  104. package/dist/services/cors.js +52 -0
  105. package/dist/services/deduplication-service.d.ts +28 -0
  106. package/dist/services/deduplication-service.js +111 -0
  107. package/dist/services/embedding.d.ts +36 -0
  108. package/dist/services/embedding.js +200 -0
  109. package/dist/services/jsonc.d.ts +6 -0
  110. package/dist/services/jsonc.js +76 -0
  111. package/dist/services/language-detector.d.ts +2 -0
  112. package/dist/services/language-detector.js +33 -0
  113. package/dist/services/legacy-migration.d.ts +77 -0
  114. package/dist/services/legacy-migration.js +343 -0
  115. package/dist/services/logger.d.ts +1 -0
  116. package/dist/services/logger.js +119 -0
  117. package/dist/services/memory-portability-service.d.ts +37 -0
  118. package/dist/services/memory-portability-service.js +340 -0
  119. package/dist/services/memory-scope.d.ts +16 -0
  120. package/dist/services/memory-scope.js +43 -0
  121. package/dist/services/migration-service.d.ts +42 -0
  122. package/dist/services/migration-service.js +307 -0
  123. package/dist/services/onnxruntime-resolve.d.ts +24 -0
  124. package/dist/services/onnxruntime-resolve.js +174 -0
  125. package/dist/services/privacy.d.ts +26 -0
  126. package/dist/services/privacy.js +69 -0
  127. package/dist/services/request-timeouts.d.ts +9 -0
  128. package/dist/services/request-timeouts.js +11 -0
  129. package/dist/services/runtime-require.d.ts +17 -0
  130. package/dist/services/runtime-require.js +75 -0
  131. package/dist/services/secret-resolver.d.ts +1 -0
  132. package/dist/services/secret-resolver.js +65 -0
  133. package/dist/services/shard-inventory-service.d.ts +69 -0
  134. package/dist/services/shard-inventory-service.js +315 -0
  135. package/dist/services/shard-path-migration-service.d.ts +31 -0
  136. package/dist/services/shard-path-migration-service.js +367 -0
  137. package/dist/services/tag-prefix-migration.d.ts +60 -0
  138. package/dist/services/tag-prefix-migration.js +439 -0
  139. package/dist/services/tag-prefix-scan.d.ts +26 -0
  140. package/dist/services/tag-prefix-scan.js +91 -0
  141. package/dist/services/tags.d.ts +29 -0
  142. package/dist/services/tags.js +251 -0
  143. package/dist/services/turso/connection-manager.d.ts +15 -0
  144. package/dist/services/turso/connection-manager.js +163 -0
  145. package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
  146. package/dist/services/turso/cross-process-write-lock.js +89 -0
  147. package/dist/services/turso/legacy-migrator.d.ts +1 -0
  148. package/dist/services/turso/legacy-migrator.js +540 -0
  149. package/dist/services/turso/lifecycle.d.ts +1 -0
  150. package/dist/services/turso/lifecycle.js +14 -0
  151. package/dist/services/turso/operation-lock.d.ts +2 -0
  152. package/dist/services/turso/operation-lock.js +61 -0
  153. package/dist/services/turso/ready.d.ts +2 -0
  154. package/dist/services/turso/ready.js +36 -0
  155. package/dist/services/turso/shard-manager.d.ts +38 -0
  156. package/dist/services/turso/shard-manager.js +496 -0
  157. package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
  158. package/dist/services/turso/sqlite-handle-release.js +56 -0
  159. package/dist/services/turso/turso-db.d.ts +18 -0
  160. package/dist/services/turso/turso-db.js +53 -0
  161. package/dist/services/turso/types.d.ts +42 -0
  162. package/dist/services/turso/types.js +1 -0
  163. package/dist/services/turso/vector-search.d.ts +41 -0
  164. package/dist/services/turso/vector-search.js +384 -0
  165. package/dist/services/turso/vector-utils.d.ts +7 -0
  166. package/dist/services/turso/vector-utils.js +67 -0
  167. package/dist/services/user-memory-learning.d.ts +97 -0
  168. package/dist/services/user-memory-learning.js +673 -0
  169. package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
  170. package/dist/services/user-profile/ai-cleanup.js +486 -0
  171. package/dist/services/user-profile/profile-context.d.ts +1 -0
  172. package/dist/services/user-profile/profile-context.js +105 -0
  173. package/dist/services/user-profile/profile-utils.d.ts +2 -0
  174. package/dist/services/user-profile/profile-utils.js +45 -0
  175. package/dist/services/user-profile/types.d.ts +92 -0
  176. package/dist/services/user-profile/types.js +1 -0
  177. package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
  178. package/dist/services/user-profile/user-profile-manager.js +1660 -0
  179. package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
  180. package/dist/services/user-prompt/user-prompt-manager.js +303 -0
  181. package/dist/services/web-api-auth.d.ts +3 -0
  182. package/dist/services/web-api-auth.js +25 -0
  183. package/dist/services/web-auth.d.ts +46 -0
  184. package/dist/services/web-auth.js +105 -0
  185. package/dist/services/web-server.d.ts +57 -0
  186. package/dist/services/web-server.js +659 -0
  187. package/dist/shared/api/index.d.ts +4 -0
  188. package/dist/shared/api/index.js +2 -0
  189. package/dist/shared/api/portability-schemas.d.ts +56 -0
  190. package/dist/shared/api/portability-schemas.js +39 -0
  191. package/dist/shared/api/schemas.d.ts +295 -0
  192. package/dist/shared/api/schemas.js +134 -0
  193. package/dist/shared/api/types.d.ts +28 -0
  194. package/dist/shared/api/types.js +1 -0
  195. package/dist/types/index.d.ts +27 -0
  196. package/dist/types/index.js +1 -0
  197. package/dist/utils/context-limit.d.ts +10 -0
  198. package/dist/utils/context-limit.js +43 -0
  199. package/dist/utils/math.d.ts +7 -0
  200. package/dist/utils/math.js +48 -0
  201. package/dist/utils/profile.d.ts +2 -0
  202. package/dist/utils/profile.js +15 -0
  203. package/dist/v2/adapter.d.ts +5 -0
  204. package/dist/v2/adapter.js +218 -0
  205. package/dist/v2/legacy-client.d.ts +15 -0
  206. package/dist/v2/legacy-client.js +257 -0
  207. package/dist/v2/memory-bridge.d.ts +18 -0
  208. package/dist/v2/memory-bridge.js +29 -0
  209. package/dist/v2/plugin.d.ts +3 -0
  210. package/dist/v2/plugin.js +24 -0
  211. package/dist/web/assets/index-DPjEpD1h.css +2 -0
  212. package/dist/web/assets/index-do26c4JJ.js +122 -0
  213. package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  214. package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  215. package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  216. package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  217. package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  218. package/dist/web/favicon.ico +0 -0
  219. package/dist/web/index.html +14 -0
  220. package/dist/web/omms-icon.png +0 -0
  221. package/package.json +138 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Create a CommonJS require() bound to the current ESM module.
3
+ *
4
+ * OpenCode ships as a Bun `--compile` binary. In that host, `import.meta.url`
5
+ * can be empty for dynamically loaded plugins, so `createRequire(import.meta.url)`
6
+ * yields `Cannot find module '…' from ''` (#210). Prefer Bun's module-bound
7
+ * `import.meta.require` when available, then fall back to a valid file/path anchor.
8
+ */
9
+ import { createRequire } from "node:module";
10
+ import { join } from "node:path";
11
+ function isUsableAnchor(value) {
12
+ return typeof value === "string" && value.trim().length > 0 && value !== "undefined";
13
+ }
14
+ function collectAnchors(meta) {
15
+ const anchors = [];
16
+ if (isUsableAnchor(meta.url))
17
+ anchors.push(meta.url);
18
+ if (isUsableAnchor(meta.path))
19
+ anchors.push(meta.path);
20
+ const dir = isUsableAnchor(meta.dirname)
21
+ ? meta.dirname
22
+ : isUsableAnchor(meta.dir)
23
+ ? meta.dir
24
+ : null;
25
+ if (dir) {
26
+ // createRequire needs a filename, not a directory.
27
+ anchors.push(join(dir, "runtime-require.js"));
28
+ }
29
+ if (typeof meta.resolve === "function") {
30
+ try {
31
+ const resolved = meta.resolve("./package.json");
32
+ if (isUsableAnchor(resolved))
33
+ anchors.push(resolved);
34
+ }
35
+ catch {
36
+ // import.meta.resolve may be unavailable or reject relative specifiers.
37
+ }
38
+ }
39
+ return anchors;
40
+ }
41
+ /**
42
+ * Build a require() for resolving/loading packages next to this plugin module.
43
+ * Throws a concrete diagnostic when no usable Bun/Node anchor exists.
44
+ *
45
+ * Prefer a path/url-anchored createRequire over Bun's import.meta.require when a
46
+ * concrete file anchor exists. In OpenCode's Bun --compile host, import.meta.url
47
+ * and import.meta.path are still valid for dynamically imported plugins even when
48
+ * some require referrers later surface as `from ''` (#210).
49
+ */
50
+ export function createRuntimeRequire(meta) {
51
+ const anchors = collectAnchors(meta);
52
+ const errors = [];
53
+ for (const anchor of anchors) {
54
+ try {
55
+ return createRequire(anchor);
56
+ }
57
+ catch (error) {
58
+ const message = error instanceof Error ? error.message : String(error);
59
+ errors.push(`${anchor}: ${message}`);
60
+ }
61
+ }
62
+ if (typeof meta.require === "function") {
63
+ return meta.require;
64
+ }
65
+ throw new Error([
66
+ "Unable to create a module require for the OpenCode plugin host.",
67
+ `import.meta.url=${JSON.stringify(meta.url)}`,
68
+ `import.meta.path=${JSON.stringify(meta.path)}`,
69
+ "Bun import.meta.require is unavailable.",
70
+ errors.length > 0 ? `createRequire attempts: ${errors.join("; ")}` : null,
71
+ "This usually means the compiled OpenCode/Bun host cleared the plugin module referrer (#210).",
72
+ ]
73
+ .filter(Boolean)
74
+ .join(" "));
75
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveSecretValue(value: string | undefined): string | undefined;
@@ -0,0 +1,65 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir, platform } from "node:os";
4
+ function expandPath(path) {
5
+ if (path.startsWith("~/")) {
6
+ return join(homedir(), path.slice(2));
7
+ }
8
+ if (path === "~") {
9
+ return homedir();
10
+ }
11
+ return path;
12
+ }
13
+ // Any group or other bit set means someone besides the owner can reach the
14
+ // secret. Owner bits are deliberately excluded: 0600 and 0400 are both fine.
15
+ const GROUP_AND_OTHER_BITS = 0o077;
16
+ function checkFilePermissions(filePath) {
17
+ if (platform() === "win32") {
18
+ return;
19
+ }
20
+ try {
21
+ const stats = statSync(filePath);
22
+ const mode = stats.mode & 0o777;
23
+ // Test the bits, not the numeric value. `mode > 0o600` compared a bitmask as
24
+ // an integer, so it missed every mode that grants group/other access while
25
+ // sorting numerically lower -- 0404 (world-readable) and 0006
26
+ // (world-writable) passed silently, while 0640, strictly less permissive
27
+ // than 0644, warned. The comparison and the property being checked were
28
+ // simply unrelated.
29
+ const exposedBits = mode & GROUP_AND_OTHER_BITS;
30
+ if (exposedBits !== 0) {
31
+ console.warn(`Warning: Secret file ${filePath} has permissive permissions (${mode.toString(8).padStart(3, "0")}) granting group/other access. Recommend chmod 600.`);
32
+ }
33
+ }
34
+ catch {
35
+ console.warn(`Warning: Could not check file permissions for ${filePath}`);
36
+ }
37
+ }
38
+ export function resolveSecretValue(value) {
39
+ if (!value) {
40
+ return undefined;
41
+ }
42
+ if (value.startsWith("file://")) {
43
+ const filePath = expandPath(value.slice(7));
44
+ if (!existsSync(filePath)) {
45
+ throw new Error(`Secret file not found: ${filePath}`);
46
+ }
47
+ try {
48
+ checkFilePermissions(filePath);
49
+ const content = readFileSync(filePath, "utf-8");
50
+ return content.trim();
51
+ }
52
+ catch (error) {
53
+ throw new Error(`Failed to read secret file ${filePath}: ${error}`, { cause: error });
54
+ }
55
+ }
56
+ if (value.startsWith("env://")) {
57
+ const envVar = value.slice(6);
58
+ const envValue = process.env[envVar];
59
+ if (!envValue) {
60
+ throw new Error(`Environment variable not found: ${envVar}`);
61
+ }
62
+ return envValue;
63
+ }
64
+ return value;
65
+ }
@@ -0,0 +1,69 @@
1
+ export type ShardInventoryStatus = "current" | "linked" | "orphaned" | "missing-file" | "empty" | "ambiguous";
2
+ export interface ProjectShardDescriptor {
3
+ shardId: number | null;
4
+ shardIndex: number;
5
+ dbPath: string;
6
+ fileName: string;
7
+ isActive: boolean;
8
+ vectorCount: number;
9
+ memoryCount: number;
10
+ fileExists: boolean;
11
+ }
12
+ export interface ProjectShardGroup {
13
+ scope: "project";
14
+ scopeHash: string;
15
+ shardIndices: number[];
16
+ shards: ProjectShardDescriptor[];
17
+ memoryCount: number;
18
+ containerTag: string | null;
19
+ projectPath: string | null;
20
+ projectPathCandidates: Array<{
21
+ path: string;
22
+ count: number;
23
+ }>;
24
+ projectName: string | null;
25
+ gitRepoUrl: string | null;
26
+ pathExists: boolean;
27
+ status: ShardInventoryStatus;
28
+ matchesCurrentProject: boolean;
29
+ }
30
+ export interface ListShardsResult {
31
+ success: true;
32
+ storagePath: string;
33
+ currentProject: {
34
+ tag: string;
35
+ scopeHash: string;
36
+ projectPath: string;
37
+ };
38
+ shards: ProjectShardGroup[];
39
+ summary: {
40
+ total: number;
41
+ current: number;
42
+ linked: number;
43
+ orphaned: number;
44
+ missingFile: number;
45
+ empty: number;
46
+ ambiguous: number;
47
+ };
48
+ }
49
+ export interface ResolveMigrationSourceOptions {
50
+ fromPath?: string;
51
+ fromHash?: string;
52
+ }
53
+ export interface ResolvedMigrationSource {
54
+ scopeHash: string;
55
+ matchedBy: "fromHash" | "storedProjectPath" | "liveProjectIdentity";
56
+ group: ProjectShardGroup;
57
+ }
58
+ export interface ResolveMigrationSourceResult {
59
+ success: boolean;
60
+ source?: ResolvedMigrationSource;
61
+ candidates?: ProjectShardGroup[];
62
+ error?: string;
63
+ }
64
+ export declare function normalizeProjectPath(path: string): string;
65
+ export declare class ShardInventoryService {
66
+ listShards(currentDirectory: string): Promise<ListShardsResult>;
67
+ resolveMigrationSource(currentDirectory: string, options: ResolveMigrationSourceOptions): Promise<ResolveMigrationSourceResult>;
68
+ }
69
+ export declare const shardInventoryService: ShardInventoryService;
@@ -0,0 +1,315 @@
1
+ import { basename, join, normalize, resolve } from "node:path";
2
+ import { existsSync, readdirSync } from "node:fs";
3
+ import { CONFIG } from "../config.js";
4
+ import { assertSafeScopeHash, isValidScopeHash } from "./memory-scope.js";
5
+ import { getProjectTagInfo } from "./tags.js";
6
+ import { ensureTursoReady } from "./turso/ready.js";
7
+ import { tursoShardManager } from "./turso/shard-manager.js";
8
+ import { tursoConnectionManager } from "./turso/connection-manager.js";
9
+ import { tursoVectorSearch } from "./turso/vector-search.js";
10
+ const SHARD_FILENAME_RE = /^(user|project)_([a-f0-9]{16})_shard_(\d+)\.db$/;
11
+ export function normalizeProjectPath(path) {
12
+ const normalized = normalize(resolve(path)).replace(/\\/g, "/");
13
+ if (normalized.length > 1 && normalized.endsWith("/")) {
14
+ return normalized.replace(/\/+$/, "");
15
+ }
16
+ return normalized;
17
+ }
18
+ function listProjectShardDbPaths() {
19
+ const dir = join(CONFIG.storagePath, "projects");
20
+ if (!existsSync(dir))
21
+ return [];
22
+ return readdirSync(dir)
23
+ .filter((file) => file.endsWith(".db") && !file.includes(".bak") && !file.includes(".tmp"))
24
+ .map((file) => join(dir, file));
25
+ }
26
+ async function inspectShardContent(dbPath) {
27
+ if (!existsSync(dbPath)) {
28
+ return {
29
+ memoryCount: 0,
30
+ pathCandidates: [],
31
+ projectName: null,
32
+ gitRepoUrl: null,
33
+ containerTag: null,
34
+ };
35
+ }
36
+ const db = await tursoConnectionManager.getConnection(dbPath);
37
+ const memoryCount = await tursoVectorSearch.countAllVectors(db);
38
+ const pathCounts = await tursoVectorSearch.getProjectPathCounts(db);
39
+ const tags = await tursoVectorSearch.getDistinctTags(db);
40
+ const first = tags[0];
41
+ // Collapse path+tag rows into path totals while keeping a representative tag.
42
+ const byPath = new Map();
43
+ for (const row of pathCounts) {
44
+ const key = normalizeProjectPath(row.projectPath);
45
+ const existing = byPath.get(key);
46
+ if (existing) {
47
+ existing.count += row.count;
48
+ if (!existing.containerTag && row.containerTag) {
49
+ existing.containerTag = row.containerTag;
50
+ }
51
+ }
52
+ else {
53
+ byPath.set(key, {
54
+ path: key,
55
+ count: row.count,
56
+ containerTag: row.containerTag,
57
+ });
58
+ }
59
+ }
60
+ const pathCandidates = [...byPath.values()].sort((a, b) => b.count - a.count || a.path.localeCompare(b.path));
61
+ return {
62
+ memoryCount,
63
+ pathCandidates,
64
+ projectName: first?.project_name ? String(first.project_name) : null,
65
+ gitRepoUrl: first?.git_repo_url ? String(first.git_repo_url) : null,
66
+ containerTag: pathCandidates[0]?.containerTag ??
67
+ (first?.container_tag ? String(first.container_tag) : null),
68
+ };
69
+ }
70
+ function deriveStatus(input) {
71
+ if (input.pathCandidates.length > 1) {
72
+ return "ambiguous";
73
+ }
74
+ if (input.allFilesMissing) {
75
+ return "missing-file";
76
+ }
77
+ if (input.memoryCount === 0) {
78
+ return "empty";
79
+ }
80
+ if (input.matchesCurrentProject) {
81
+ return "current";
82
+ }
83
+ if (input.projectPath && !input.pathExists) {
84
+ return "orphaned";
85
+ }
86
+ if (input.projectPath && input.pathExists) {
87
+ return "linked";
88
+ }
89
+ if (input.anyFileMissing) {
90
+ return "missing-file";
91
+ }
92
+ return "orphaned";
93
+ }
94
+ export class ShardInventoryService {
95
+ async listShards(currentDirectory) {
96
+ await ensureTursoReady();
97
+ const current = getProjectTagInfo(currentDirectory);
98
+ const currentHash = current.tag.split("_").pop();
99
+ assertSafeScopeHash(currentHash);
100
+ const registered = await tursoShardManager.getAllShards("project", "");
101
+ const byHash = new Map();
102
+ for (const shard of registered) {
103
+ const entry = byHash.get(shard.scopeHash) ?? { registered: [], filePaths: new Set() };
104
+ entry.registered.push(shard);
105
+ entry.filePaths.add(shard.dbPath);
106
+ byHash.set(shard.scopeHash, entry);
107
+ }
108
+ for (const path of listProjectShardDbPaths()) {
109
+ const match = SHARD_FILENAME_RE.exec(basename(path));
110
+ if (!match || match[1] !== "project")
111
+ continue;
112
+ const scopeHash = match[2];
113
+ const entry = byHash.get(scopeHash) ?? { registered: [], filePaths: new Set() };
114
+ entry.filePaths.add(path);
115
+ byHash.set(scopeHash, entry);
116
+ }
117
+ const groups = [];
118
+ for (const [scopeHash, entry] of byHash) {
119
+ const shardIndexes = new Set();
120
+ for (const shard of entry.registered) {
121
+ shardIndexes.add(shard.shardIndex);
122
+ }
123
+ for (const path of entry.filePaths) {
124
+ const match = SHARD_FILENAME_RE.exec(basename(path));
125
+ if (match)
126
+ shardIndexes.add(Number(match[3]));
127
+ }
128
+ const descriptors = [];
129
+ let memoryCount = 0;
130
+ const pathCountMap = new Map();
131
+ let projectName = null;
132
+ let gitRepoUrl = null;
133
+ let containerTag = null;
134
+ for (const shardIndex of [...shardIndexes].sort((a, b) => a - b)) {
135
+ const registeredShard = entry.registered.find((shard) => shard.shardIndex === shardIndex) ?? null;
136
+ const dbPath = registeredShard?.dbPath ??
137
+ tursoShardManager.getShardPath("project", scopeHash, shardIndex);
138
+ const fileExists = existsSync(dbPath);
139
+ const content = await inspectShardContent(dbPath);
140
+ memoryCount += content.memoryCount;
141
+ for (const candidate of content.pathCandidates) {
142
+ pathCountMap.set(candidate.path, (pathCountMap.get(candidate.path) ?? 0) + candidate.count);
143
+ if (!containerTag && candidate.containerTag) {
144
+ containerTag = candidate.containerTag;
145
+ }
146
+ }
147
+ if (!projectName && content.projectName)
148
+ projectName = content.projectName;
149
+ if (!gitRepoUrl && content.gitRepoUrl)
150
+ gitRepoUrl = content.gitRepoUrl;
151
+ if (!containerTag && content.containerTag)
152
+ containerTag = content.containerTag;
153
+ descriptors.push({
154
+ shardId: registeredShard?.id ?? null,
155
+ shardIndex,
156
+ dbPath,
157
+ fileName: basename(dbPath),
158
+ isActive: registeredShard?.isActive ?? shardIndex === Math.max(...shardIndexes),
159
+ vectorCount: registeredShard?.vectorCount ?? content.memoryCount,
160
+ memoryCount: content.memoryCount,
161
+ fileExists,
162
+ });
163
+ }
164
+ const projectPathCandidates = [...pathCountMap.entries()]
165
+ .map(([path, count]) => ({ path, count }))
166
+ .sort((a, b) => b.count - a.count || a.path.localeCompare(b.path));
167
+ const projectPath = projectPathCandidates[0]?.path ?? null;
168
+ const pathExists = projectPath ? existsSync(projectPath) : false;
169
+ const matchesCurrentProject = scopeHash === currentHash;
170
+ const anyFileMissing = descriptors.some((shard) => !shard.fileExists);
171
+ const allFilesMissing = descriptors.length > 0 && descriptors.every((shard) => !shard.fileExists);
172
+ groups.push({
173
+ scope: "project",
174
+ scopeHash,
175
+ shardIndices: descriptors.map((shard) => shard.shardIndex),
176
+ shards: descriptors,
177
+ memoryCount,
178
+ containerTag: containerTag ?? `${CONFIG.containerTagPrefix}_project_${scopeHash}`,
179
+ projectPath,
180
+ projectPathCandidates,
181
+ projectName,
182
+ gitRepoUrl,
183
+ pathExists,
184
+ status: deriveStatus({
185
+ matchesCurrentProject,
186
+ memoryCount,
187
+ pathExists,
188
+ projectPath,
189
+ pathCandidates: projectPathCandidates,
190
+ anyFileMissing,
191
+ allFilesMissing,
192
+ }),
193
+ matchesCurrentProject,
194
+ });
195
+ }
196
+ groups.sort((a, b) => {
197
+ if (a.matchesCurrentProject !== b.matchesCurrentProject) {
198
+ return a.matchesCurrentProject ? -1 : 1;
199
+ }
200
+ return b.memoryCount - a.memoryCount || a.scopeHash.localeCompare(b.scopeHash);
201
+ });
202
+ const summary = {
203
+ total: groups.length,
204
+ current: groups.filter((g) => g.status === "current").length,
205
+ linked: groups.filter((g) => g.status === "linked").length,
206
+ orphaned: groups.filter((g) => g.status === "orphaned").length,
207
+ missingFile: groups.filter((g) => g.status === "missing-file").length,
208
+ empty: groups.filter((g) => g.status === "empty").length,
209
+ ambiguous: groups.filter((g) => g.status === "ambiguous").length,
210
+ };
211
+ return {
212
+ success: true,
213
+ storagePath: CONFIG.storagePath,
214
+ currentProject: {
215
+ tag: current.tag,
216
+ scopeHash: currentHash,
217
+ projectPath: current.projectPath || getProjectTagInfo(currentDirectory).displayName,
218
+ },
219
+ shards: groups,
220
+ summary,
221
+ };
222
+ }
223
+ async resolveMigrationSource(currentDirectory, options) {
224
+ const inventory = await this.listShards(currentDirectory);
225
+ const { fromHash, fromPath } = options;
226
+ if (fromHash) {
227
+ if (!isValidScopeHash(fromHash)) {
228
+ return {
229
+ success: false,
230
+ error: `Invalid fromHash: expected 16 lowercase hex characters, got "${fromHash}"`,
231
+ };
232
+ }
233
+ const group = inventory.shards.find((shard) => shard.scopeHash === fromHash);
234
+ if (!group) {
235
+ return {
236
+ success: false,
237
+ error: `No project shards found for hash ${fromHash}. Run memory list-shards to inspect available shards.`,
238
+ };
239
+ }
240
+ if (group.memoryCount === 0) {
241
+ return {
242
+ success: false,
243
+ error: `Shard hash ${fromHash} exists but contains no memories.`,
244
+ };
245
+ }
246
+ return {
247
+ success: true,
248
+ source: {
249
+ scopeHash: fromHash,
250
+ matchedBy: "fromHash",
251
+ group,
252
+ },
253
+ };
254
+ }
255
+ if (!fromPath || !fromPath.trim()) {
256
+ return {
257
+ success: false,
258
+ error: "fromPath or fromHash is required for migrate",
259
+ };
260
+ }
261
+ const normalizedFrom = normalizeProjectPath(fromPath);
262
+ const pathMatches = inventory.shards.filter((group) => group.projectPathCandidates.some((candidate) => candidate.path === normalizedFrom));
263
+ if (pathMatches.length === 1) {
264
+ const group = pathMatches[0];
265
+ if (group.memoryCount === 0) {
266
+ return {
267
+ success: false,
268
+ error: `Matched project path ${normalizedFrom} but the shard contains no memories.`,
269
+ };
270
+ }
271
+ return {
272
+ success: true,
273
+ source: {
274
+ scopeHash: group.scopeHash,
275
+ matchedBy: "storedProjectPath",
276
+ group,
277
+ },
278
+ };
279
+ }
280
+ if (pathMatches.length > 1) {
281
+ return {
282
+ success: false,
283
+ error: `Ambiguous fromPath match for ${normalizedFrom}. Use fromHash with memory list-shards to select one shard.`,
284
+ candidates: pathMatches,
285
+ };
286
+ }
287
+ // If the old directory still exists, resolve its live project identity.
288
+ if (existsSync(normalizedFrom)) {
289
+ try {
290
+ const live = getProjectTagInfo(normalizedFrom);
291
+ const liveHash = live.tag.split("_").pop();
292
+ const group = inventory.shards.find((shard) => shard.scopeHash === liveHash);
293
+ if (group && group.memoryCount > 0) {
294
+ return {
295
+ success: true,
296
+ source: {
297
+ scopeHash: liveHash,
298
+ matchedBy: "liveProjectIdentity",
299
+ group,
300
+ },
301
+ };
302
+ }
303
+ }
304
+ catch {
305
+ // Fall through to the generic missing-source error.
306
+ }
307
+ }
308
+ return {
309
+ success: false,
310
+ error: `No shard found for fromPath ${normalizedFrom}. ` +
311
+ `If the old directory no longer exists, run memory list-shards and migrate with fromHash.`,
312
+ };
313
+ }
314
+ }
315
+ export const shardInventoryService = new ShardInventoryService();
@@ -0,0 +1,31 @@
1
+ import { type ResolveMigrationSourceOptions, type ResolvedMigrationSource } from "./shard-inventory-service.js";
2
+ export interface PathMigrationOptions extends ResolveMigrationSourceOptions {
3
+ currentDirectory: string;
4
+ dryRun?: boolean;
5
+ allowLinkedSource?: boolean;
6
+ }
7
+ export interface PathMigrationAction {
8
+ type: "rename" | "archive-empty-target" | "update-memories" | "update-prompts" | "reassign-metadata";
9
+ detail: string;
10
+ }
11
+ export interface PathMigrationResult {
12
+ success: boolean;
13
+ dryRun: boolean;
14
+ migratedShards?: number;
15
+ migratedMemories?: number;
16
+ oldPath?: string | null;
17
+ newPath?: string;
18
+ oldHash?: string;
19
+ newHash?: string;
20
+ matchedBy?: ResolvedMigrationSource["matchedBy"];
21
+ actions?: PathMigrationAction[];
22
+ error?: string;
23
+ candidates?: unknown[];
24
+ }
25
+ export declare class ShardPathMigrationService {
26
+ migrate(options: PathMigrationOptions): Promise<PathMigrationResult>;
27
+ private rollbackSwap;
28
+ /** Roll back an interrupted staged swap before normal storage access resumes. */
29
+ recoverInterruptedSwap(): Promise<void>;
30
+ }
31
+ export declare const shardPathMigrationService: ShardPathMigrationService;