grov 0.5.11 → 0.6.13

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 (190) hide show
  1. package/dist/cli/agents/registry.d.ts +17 -0
  2. package/dist/cli/agents/registry.js +132 -0
  3. package/dist/cli/commands/agents.d.ts +1 -0
  4. package/dist/cli/commands/agents.js +48 -0
  5. package/dist/cli/commands/disable.d.ts +1 -0
  6. package/dist/cli/commands/disable.js +179 -0
  7. package/dist/cli/commands/doctor.d.ts +1 -0
  8. package/dist/cli/commands/doctor.js +157 -0
  9. package/dist/{commands → cli/commands}/drift-test.js +39 -26
  10. package/dist/cli/commands/init.d.ts +1 -0
  11. package/dist/cli/commands/init.js +90 -0
  12. package/dist/{commands → cli/commands}/login.js +19 -18
  13. package/dist/{commands → cli/commands}/logout.js +1 -1
  14. package/dist/{commands → cli/commands}/proxy-status.js +1 -1
  15. package/dist/cli/commands/setup.d.ts +6 -0
  16. package/dist/cli/commands/setup.js +309 -0
  17. package/dist/{commands → cli/commands}/status.js +1 -1
  18. package/dist/{commands → cli/commands}/sync.d.ts +1 -0
  19. package/dist/{commands → cli/commands}/sync.js +59 -4
  20. package/dist/{commands → cli/commands}/uninstall.js +2 -2
  21. package/dist/cli/index.js +270 -0
  22. package/dist/{lib → core/cloud}/cloud-sync.d.ts +3 -3
  23. package/dist/{lib → core/cloud}/cloud-sync.js +10 -10
  24. package/dist/{lib → core/extraction}/correction-builder-proxy.d.ts +1 -1
  25. package/dist/{lib → core/extraction}/correction-builder-proxy.js +0 -4
  26. package/dist/{lib → core/extraction}/drift-checker-proxy.d.ts +13 -9
  27. package/dist/core/extraction/drift-checker-proxy.js +510 -0
  28. package/dist/{lib → core/extraction}/llm-extractor.d.ts +8 -38
  29. package/dist/{lib → core/extraction}/llm-extractor.js +132 -220
  30. package/dist/{lib → core}/store/sessions.js +3 -19
  31. package/dist/core/store/store.d.ts +1 -0
  32. package/dist/{lib → core/store}/store.js +1 -1
  33. package/dist/{lib → core}/store/types.d.ts +0 -4
  34. package/dist/integrations/mcp/cache.d.ts +27 -0
  35. package/dist/integrations/mcp/cache.js +106 -0
  36. package/dist/integrations/mcp/capture/antigravity-parser.d.ts +26 -0
  37. package/dist/integrations/mcp/capture/antigravity-parser.js +272 -0
  38. package/dist/integrations/mcp/capture/antigravity-scanner.d.ts +24 -0
  39. package/dist/integrations/mcp/capture/antigravity-scanner.js +153 -0
  40. package/dist/integrations/mcp/capture/antigravity-sync-tracker.d.ts +29 -0
  41. package/dist/integrations/mcp/capture/antigravity-sync-tracker.js +115 -0
  42. package/dist/integrations/mcp/capture/cli-extractor.d.ts +18 -0
  43. package/dist/integrations/mcp/capture/cli-extractor.js +258 -0
  44. package/dist/integrations/mcp/capture/cli-synced.d.ts +4 -0
  45. package/dist/integrations/mcp/capture/cli-synced.js +62 -0
  46. package/dist/integrations/mcp/capture/cli-transform.d.ts +30 -0
  47. package/dist/integrations/mcp/capture/cli-transform.js +62 -0
  48. package/dist/integrations/mcp/capture/cli-watcher.d.ts +31 -0
  49. package/dist/integrations/mcp/capture/cli-watcher.js +106 -0
  50. package/dist/integrations/mcp/capture/hook-handler.d.ts +2 -0
  51. package/dist/integrations/mcp/capture/hook-handler.js +157 -0
  52. package/dist/integrations/mcp/capture/sqlite-reader.d.ts +35 -0
  53. package/dist/integrations/mcp/capture/sqlite-reader.js +388 -0
  54. package/dist/integrations/mcp/capture/sync-tracker.d.ts +16 -0
  55. package/dist/integrations/mcp/capture/sync-tracker.js +102 -0
  56. package/dist/integrations/mcp/clients/cursor/rules-installer.d.ts +19 -0
  57. package/dist/integrations/mcp/clients/cursor/rules-installer.js +123 -0
  58. package/dist/integrations/mcp/index.d.ts +1 -0
  59. package/dist/integrations/mcp/index.js +94 -0
  60. package/dist/integrations/mcp/logger.d.ts +8 -0
  61. package/dist/integrations/mcp/logger.js +50 -0
  62. package/dist/integrations/mcp/server.d.ts +5 -0
  63. package/dist/integrations/mcp/server.js +58 -0
  64. package/dist/integrations/mcp/tools/expand.d.ts +1 -0
  65. package/dist/integrations/mcp/tools/expand.js +53 -0
  66. package/dist/integrations/mcp/tools/preview.d.ts +1 -0
  67. package/dist/integrations/mcp/tools/preview.js +64 -0
  68. package/dist/integrations/proxy/agents/base.d.ts +43 -0
  69. package/dist/integrations/proxy/agents/base.js +13 -0
  70. package/dist/{proxy/utils → integrations/proxy/agents/claude}/extractors.d.ts +4 -8
  71. package/dist/{proxy/utils → integrations/proxy/agents/claude}/extractors.js +4 -33
  72. package/dist/{proxy → integrations/proxy/agents/claude}/forwarder.d.ts +1 -1
  73. package/dist/{proxy → integrations/proxy/agents/claude}/forwarder.js +22 -6
  74. package/dist/integrations/proxy/agents/claude/index.d.ts +43 -0
  75. package/dist/integrations/proxy/agents/claude/index.js +386 -0
  76. package/dist/{proxy/action-parser.d.ts → integrations/proxy/agents/claude/parser.d.ts} +1 -1
  77. package/dist/integrations/proxy/agents/codex/extractors.d.ts +6 -0
  78. package/dist/integrations/proxy/agents/codex/extractors.js +49 -0
  79. package/dist/integrations/proxy/agents/codex/forwarder.d.ts +9 -0
  80. package/dist/integrations/proxy/agents/codex/forwarder.js +125 -0
  81. package/dist/integrations/proxy/agents/codex/index.d.ts +44 -0
  82. package/dist/integrations/proxy/agents/codex/index.js +371 -0
  83. package/dist/integrations/proxy/agents/codex/parser.d.ts +11 -0
  84. package/dist/integrations/proxy/agents/codex/parser.js +104 -0
  85. package/dist/integrations/proxy/agents/codex/patch.d.ts +12 -0
  86. package/dist/integrations/proxy/agents/codex/patch.js +40 -0
  87. package/dist/integrations/proxy/agents/codex/settings.d.ts +18 -0
  88. package/dist/integrations/proxy/agents/codex/settings.js +73 -0
  89. package/dist/integrations/proxy/agents/codex/types.d.ts +59 -0
  90. package/dist/integrations/proxy/agents/codex/types.js +2 -0
  91. package/dist/integrations/proxy/agents/index.d.ts +11 -0
  92. package/dist/integrations/proxy/agents/index.js +25 -0
  93. package/dist/integrations/proxy/agents/types.d.ts +77 -0
  94. package/dist/integrations/proxy/agents/types.js +2 -0
  95. package/dist/{proxy → integrations/proxy/cache}/extended-cache.js +2 -6
  96. package/dist/{proxy → integrations/proxy}/config.js +1 -1
  97. package/dist/{proxy → integrations/proxy}/handlers/preprocess.d.ts +3 -3
  98. package/dist/integrations/proxy/handlers/preprocess.js +194 -0
  99. package/dist/integrations/proxy/index.js +20 -0
  100. package/dist/integrations/proxy/injection/memory-injection.d.ts +56 -0
  101. package/dist/integrations/proxy/injection/memory-injection.js +252 -0
  102. package/dist/integrations/proxy/orchestrator.d.ts +30 -0
  103. package/dist/integrations/proxy/orchestrator.js +954 -0
  104. package/dist/integrations/proxy/request-processor.d.ts +14 -0
  105. package/dist/integrations/proxy/request-processor.js +68 -0
  106. package/dist/{proxy → integrations/proxy}/response-processor.d.ts +4 -3
  107. package/dist/{proxy → integrations/proxy}/response-processor.js +51 -43
  108. package/dist/{proxy → integrations/proxy}/server.d.ts +0 -1
  109. package/dist/integrations/proxy/server.js +146 -0
  110. package/dist/{proxy → integrations/proxy}/types.d.ts +4 -0
  111. package/dist/{proxy → integrations/proxy}/utils/logging.d.ts +1 -0
  112. package/dist/{proxy → integrations/proxy}/utils/logging.js +5 -0
  113. package/package.json +31 -10
  114. package/postinstall.js +62 -6
  115. package/dist/cli.js +0 -149
  116. package/dist/commands/capture.d.ts +0 -6
  117. package/dist/commands/capture.js +0 -324
  118. package/dist/commands/disable.d.ts +0 -1
  119. package/dist/commands/disable.js +0 -14
  120. package/dist/commands/doctor.d.ts +0 -1
  121. package/dist/commands/doctor.js +0 -89
  122. package/dist/commands/init.d.ts +0 -1
  123. package/dist/commands/init.js +0 -52
  124. package/dist/commands/inject.d.ts +0 -5
  125. package/dist/commands/inject.js +0 -88
  126. package/dist/commands/prompt-inject.d.ts +0 -4
  127. package/dist/commands/prompt-inject.js +0 -451
  128. package/dist/commands/unregister.d.ts +0 -1
  129. package/dist/commands/unregister.js +0 -28
  130. package/dist/lib/anchor-extractor.d.ts +0 -30
  131. package/dist/lib/anchor-extractor.js +0 -296
  132. package/dist/lib/correction-builder.d.ts +0 -10
  133. package/dist/lib/correction-builder.js +0 -226
  134. package/dist/lib/drift-checker-proxy.js +0 -373
  135. package/dist/lib/drift-checker.d.ts +0 -66
  136. package/dist/lib/drift-checker.js +0 -341
  137. package/dist/lib/hooks.d.ts +0 -38
  138. package/dist/lib/hooks.js +0 -291
  139. package/dist/lib/jsonl-parser.d.ts +0 -87
  140. package/dist/lib/jsonl-parser.js +0 -281
  141. package/dist/lib/session-parser.d.ts +0 -44
  142. package/dist/lib/session-parser.js +0 -256
  143. package/dist/lib/store.d.ts +0 -1
  144. package/dist/proxy/cache.d.ts +0 -32
  145. package/dist/proxy/cache.js +0 -47
  146. package/dist/proxy/handlers/preprocess.js +0 -186
  147. package/dist/proxy/index.js +0 -30
  148. package/dist/proxy/injection/delta-tracking.d.ts +0 -11
  149. package/dist/proxy/injection/delta-tracking.js +0 -94
  150. package/dist/proxy/injection/injectors.d.ts +0 -7
  151. package/dist/proxy/injection/injectors.js +0 -139
  152. package/dist/proxy/request-processor.d.ts +0 -27
  153. package/dist/proxy/request-processor.js +0 -233
  154. package/dist/proxy/server.js +0 -1289
  155. /package/dist/{commands → cli/commands}/drift-test.d.ts +0 -0
  156. /package/dist/{commands → cli/commands}/login.d.ts +0 -0
  157. /package/dist/{commands → cli/commands}/logout.d.ts +0 -0
  158. /package/dist/{commands → cli/commands}/proxy-status.d.ts +0 -0
  159. /package/dist/{commands → cli/commands}/status.d.ts +0 -0
  160. /package/dist/{commands → cli/commands}/uninstall.d.ts +0 -0
  161. /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
  162. /package/dist/{lib → core/cloud}/api-client.d.ts +0 -0
  163. /package/dist/{lib → core/cloud}/api-client.js +0 -0
  164. /package/dist/{lib → core/cloud}/credentials.d.ts +0 -0
  165. /package/dist/{lib → core/cloud}/credentials.js +0 -0
  166. /package/dist/{lib → core}/store/convenience.d.ts +0 -0
  167. /package/dist/{lib → core}/store/convenience.js +0 -0
  168. /package/dist/{lib → core}/store/database.d.ts +0 -0
  169. /package/dist/{lib → core}/store/database.js +0 -0
  170. /package/dist/{lib → core}/store/drift.d.ts +0 -0
  171. /package/dist/{lib → core}/store/drift.js +0 -0
  172. /package/dist/{lib → core}/store/index.d.ts +0 -0
  173. /package/dist/{lib → core}/store/index.js +0 -0
  174. /package/dist/{lib → core}/store/sessions.d.ts +0 -0
  175. /package/dist/{lib → core}/store/steps.d.ts +0 -0
  176. /package/dist/{lib → core}/store/steps.js +0 -0
  177. /package/dist/{lib → core}/store/tasks.d.ts +0 -0
  178. /package/dist/{lib → core}/store/tasks.js +0 -0
  179. /package/dist/{lib → core}/store/types.js +0 -0
  180. /package/dist/{proxy/action-parser.js → integrations/proxy/agents/claude/parser.js} +0 -0
  181. /package/dist/{lib → integrations/proxy/agents/claude}/settings.d.ts +0 -0
  182. /package/dist/{lib → integrations/proxy/agents/claude}/settings.js +0 -0
  183. /package/dist/{proxy → integrations/proxy/cache}/extended-cache.d.ts +0 -0
  184. /package/dist/{proxy → integrations/proxy}/config.d.ts +0 -0
  185. /package/dist/{proxy → integrations/proxy}/index.d.ts +0 -0
  186. /package/dist/{proxy → integrations/proxy}/types.js +0 -0
  187. /package/dist/{lib → utils}/debug.d.ts +0 -0
  188. /package/dist/{lib → utils}/debug.js +0 -0
  189. /package/dist/{lib → utils}/utils.d.ts +0 -0
  190. /package/dist/{lib → utils}/utils.js +0 -0
@@ -0,0 +1,106 @@
1
+ // MCP In-Memory Cache
2
+ // Caches preview results for expand calls
3
+ // Uses 8-char memory IDs for lookup (consistent with local proxy)
4
+ // Keyed by project path with TTL
5
+ // ─────────────────────────────────────────────────────────────
6
+ // Configuration
7
+ // ─────────────────────────────────────────────────────────────
8
+ const TTL_MS = 10 * 60 * 1000; // 10 minutes
9
+ // ─────────────────────────────────────────────────────────────
10
+ // Cache Storage
11
+ // ─────────────────────────────────────────────────────────────
12
+ // Keyed by project path
13
+ const previewCache = new Map();
14
+ // ─────────────────────────────────────────────────────────────
15
+ // Project Path
16
+ // ─────────────────────────────────────────────────────────────
17
+ /**
18
+ * Get current project path
19
+ * Used as cache key and for API filtering
20
+ *
21
+ * Cursor sets WORKSPACE_FOLDER_PATHS env var with the open workspace path.
22
+ * We extract just the folder name to match how proxy stores project_path.
23
+ */
24
+ export function getProjectPath() {
25
+ // Check explicit project path (set by grov init antigravity)
26
+ if (process.env.GROV_PROJECT_PATH) {
27
+ return process.env.GROV_PROJECT_PATH;
28
+ }
29
+ const workspacePaths = process.env.WORKSPACE_FOLDER_PATHS;
30
+ if (workspacePaths) {
31
+ // Can be multiple paths separated by some delimiter, take first one
32
+ const firstPath = workspacePaths.split(':')[0] || workspacePaths;
33
+ // Extract just the folder name (e.g., "/home/marian/Grov" -> "Grov")
34
+ const folderName = firstPath.split('/').filter(Boolean).pop();
35
+ if (folderName) {
36
+ return folderName;
37
+ }
38
+ }
39
+ // Fallback to cwd folder name
40
+ const cwdParts = process.cwd().split('/').filter(Boolean);
41
+ return cwdParts.pop() || process.cwd();
42
+ }
43
+ // ─────────────────────────────────────────────────────────────
44
+ // Cache Operations
45
+ // ─────────────────────────────────────────────────────────────
46
+ /**
47
+ * Store memories from preview call
48
+ * Indexes by 8-char ID prefix for fast lookup
49
+ */
50
+ export function setPreviewCache(memories) {
51
+ const projectPath = getProjectPath();
52
+ const memoriesById = new Map();
53
+ const cachedIds = [];
54
+ for (const m of memories) {
55
+ const shortId = m.id.substring(0, 8);
56
+ memoriesById.set(shortId, m);
57
+ cachedIds.push(shortId);
58
+ }
59
+ previewCache.set(projectPath, {
60
+ memoriesById,
61
+ timestamp: Date.now(),
62
+ cachedIds,
63
+ });
64
+ }
65
+ /**
66
+ * Get cached entry (internal use)
67
+ * Returns null if cache expired or missing
68
+ */
69
+ function getPreviewCache() {
70
+ const projectPath = getProjectPath();
71
+ const entry = previewCache.get(projectPath);
72
+ if (!entry)
73
+ return null;
74
+ // Check TTL
75
+ if (Date.now() - entry.timestamp > TTL_MS) {
76
+ previewCache.delete(projectPath);
77
+ return null;
78
+ }
79
+ return entry;
80
+ }
81
+ /**
82
+ * Get memory by 8-char ID
83
+ * Handles both 8-char and full UUID (extracts first 8 chars)
84
+ */
85
+ export function getMemoryById(id) {
86
+ const cache = getPreviewCache();
87
+ if (!cache)
88
+ return null;
89
+ // Normalize to 8-char
90
+ const shortId = id.substring(0, 8);
91
+ return cache.memoriesById.get(shortId) || null;
92
+ }
93
+ /**
94
+ * Get list of cached IDs (for error messages)
95
+ */
96
+ export function getCachedIds() {
97
+ const cache = getPreviewCache();
98
+ return cache?.cachedIds || [];
99
+ }
100
+ /**
101
+ * Clear preview cache (e.g., when project changes)
102
+ */
103
+ export function clearPreviewCache() {
104
+ const projectPath = getProjectPath();
105
+ previewCache.delete(projectPath);
106
+ }
@@ -0,0 +1,26 @@
1
+ export interface AntigravitySession {
2
+ sessionId: string;
3
+ projectPath: string;
4
+ linkedCommit: string | null;
5
+ title: string;
6
+ metadataSummary: string;
7
+ planContent: string;
8
+ taskContent: string;
9
+ filesTouched: string[];
10
+ completionStatus: 'complete' | 'partial';
11
+ updatedAt: string;
12
+ }
13
+ export declare function antigravityExists(): boolean;
14
+ export declare function isAntigravityConfigured(): boolean;
15
+ /**
16
+ * Get all session UUIDs from the brain folder
17
+ */
18
+ export declare function getAllSessionIds(): string[];
19
+ /**
20
+ * Parse a single session by UUID
21
+ */
22
+ export declare function parseSession(sessionId: string): AntigravitySession | null;
23
+ /**
24
+ * Get sessions sorted by updatedAt (most recent first)
25
+ */
26
+ export declare function getSessionsSortedByDate(): AntigravitySession[];
@@ -0,0 +1,272 @@
1
+ // Antigravity Session Parser
2
+ // Reads plan files from ~/.gemini/antigravity/brain/
3
+ // and file tracking from ~/.gemini/antigravity/code_tracker/
4
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
5
+ import { homedir } from 'os';
6
+ import { join } from 'path';
7
+ const ANTIGRAVITY_DIR = join(homedir(), '.gemini', 'antigravity');
8
+ const BRAIN_DIR = join(ANTIGRAVITY_DIR, 'brain');
9
+ const CODE_TRACKER_DIR = join(ANTIGRAVITY_DIR, 'code_tracker', 'active');
10
+ const MCP_CONFIG_PATH = join(ANTIGRAVITY_DIR, 'mcp_config.json');
11
+ // ─────────────────────────────────────────────────────────────
12
+ // Directory Checks
13
+ // ─────────────────────────────────────────────────────────────
14
+ export function antigravityExists() {
15
+ return existsSync(BRAIN_DIR);
16
+ }
17
+ export function isAntigravityConfigured() {
18
+ if (!existsSync(MCP_CONFIG_PATH))
19
+ return false;
20
+ try {
21
+ const config = JSON.parse(readFileSync(MCP_CONFIG_PATH, 'utf-8'));
22
+ return !!config.mcpServers?.grov;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ // ─────────────────────────────────────────────────────────────
29
+ // Session Discovery
30
+ // ─────────────────────────────────────────────────────────────
31
+ /**
32
+ * Get all session UUIDs from the brain folder
33
+ */
34
+ export function getAllSessionIds() {
35
+ if (!existsSync(BRAIN_DIR))
36
+ return [];
37
+ try {
38
+ return readdirSync(BRAIN_DIR)
39
+ .filter(name => {
40
+ const path = join(BRAIN_DIR, name);
41
+ // Must be a directory and look like a UUID
42
+ return statSync(path).isDirectory() && isValidUuid(name);
43
+ });
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ }
49
+ /**
50
+ * Check if string looks like a UUID
51
+ */
52
+ function isValidUuid(str) {
53
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
54
+ }
55
+ // ─────────────────────────────────────────────────────────────
56
+ // File Reading Helpers
57
+ // ─────────────────────────────────────────────────────────────
58
+ function readTextFile(path) {
59
+ if (!existsSync(path))
60
+ return null;
61
+ try {
62
+ return readFileSync(path, 'utf-8');
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ function readJsonFile(path) {
69
+ const content = readTextFile(path);
70
+ if (!content)
71
+ return null;
72
+ try {
73
+ return JSON.parse(content);
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ function parseTaskMd(content) {
80
+ const lines = content.split('\n');
81
+ // Extract title from first heading
82
+ let title = '';
83
+ for (const line of lines) {
84
+ if (line.startsWith('# ')) {
85
+ title = line.substring(2).trim();
86
+ break;
87
+ }
88
+ }
89
+ // Count checkboxes for completion status
90
+ const checkedCount = (content.match(/- \[x\]/gi) || []).length;
91
+ const uncheckedCount = (content.match(/- \[ \]/g) || []).length;
92
+ const totalBoxes = checkedCount + uncheckedCount;
93
+ const completionStatus = totalBoxes === 0 || checkedCount === totalBoxes ? 'complete' : 'partial';
94
+ return {
95
+ title: title || 'Untitled Task',
96
+ completionStatus,
97
+ content,
98
+ };
99
+ }
100
+ // ─────────────────────────────────────────────────────────────
101
+ // Implementation Plan Parsing
102
+ // ─────────────────────────────────────────────────────────────
103
+ /**
104
+ * Extract file paths from [MODIFY] markers in implementation plan
105
+ */
106
+ function extractFilesFromPlan(content) {
107
+ const files = [];
108
+ // Match [MODIFY] [filename](file://...) patterns
109
+ const modifyRegex = /\[MODIFY\]\s*\[([^\]]+)\]/g;
110
+ let match;
111
+ while ((match = modifyRegex.exec(content)) !== null) {
112
+ files.push(match[1]);
113
+ }
114
+ // Also match file:// URLs and extract filenames
115
+ const fileUrlRegex = /file:\/\/[^\s\)]+\/([^\/\s\)]+\.[a-z]+)/gi;
116
+ while ((match = fileUrlRegex.exec(content)) !== null) {
117
+ const filename = match[1];
118
+ if (!files.includes(filename)) {
119
+ files.push(filename);
120
+ }
121
+ }
122
+ return files;
123
+ }
124
+ /**
125
+ * Parse code_tracker/active/ folder for project and files
126
+ * Folder format: {ProjectName}_{commitHash}/
127
+ * File format: {hash}_{filename}
128
+ */
129
+ function parseCodeTracker() {
130
+ if (!existsSync(CODE_TRACKER_DIR))
131
+ return [];
132
+ const results = [];
133
+ try {
134
+ const projectFolders = readdirSync(CODE_TRACKER_DIR);
135
+ for (const folder of projectFolders) {
136
+ const folderPath = join(CODE_TRACKER_DIR, folder);
137
+ if (!statSync(folderPath).isDirectory())
138
+ continue;
139
+ // Parse folder name: ProjectName_commitHash
140
+ const lastUnderscore = folder.lastIndexOf('_');
141
+ if (lastUnderscore === -1)
142
+ continue;
143
+ const projectPath = folder.substring(0, lastUnderscore);
144
+ const linkedCommit = folder.substring(lastUnderscore + 1);
145
+ // Get files in folder
146
+ const files = readdirSync(folderPath);
147
+ const filesTouched = [];
148
+ for (const file of files) {
149
+ // File format: {hash}_{filename}
150
+ const underscoreIdx = file.indexOf('_');
151
+ if (underscoreIdx > 0) {
152
+ const filename = file.substring(underscoreIdx + 1);
153
+ filesTouched.push(filename);
154
+ }
155
+ }
156
+ results.push({
157
+ projectPath,
158
+ linkedCommit: linkedCommit || null,
159
+ filesTouched,
160
+ });
161
+ }
162
+ }
163
+ catch {
164
+ // Ignore errors
165
+ }
166
+ return results;
167
+ }
168
+ /**
169
+ * Find the most recent code tracker entry (by modification time)
170
+ */
171
+ function getMostRecentCodeTracker() {
172
+ if (!existsSync(CODE_TRACKER_DIR))
173
+ return null;
174
+ try {
175
+ const folders = readdirSync(CODE_TRACKER_DIR)
176
+ .map(name => ({
177
+ name,
178
+ path: join(CODE_TRACKER_DIR, name),
179
+ }))
180
+ .filter(f => statSync(f.path).isDirectory())
181
+ .map(f => ({
182
+ ...f,
183
+ mtime: statSync(f.path).mtime.getTime(),
184
+ }))
185
+ .sort((a, b) => b.mtime - a.mtime);
186
+ if (folders.length === 0)
187
+ return null;
188
+ const mostRecent = folders[0];
189
+ const lastUnderscore = mostRecent.name.lastIndexOf('_');
190
+ if (lastUnderscore === -1)
191
+ return null;
192
+ const projectPath = mostRecent.name.substring(0, lastUnderscore);
193
+ const linkedCommit = mostRecent.name.substring(lastUnderscore + 1);
194
+ const files = readdirSync(mostRecent.path);
195
+ const filesTouched = [];
196
+ for (const file of files) {
197
+ const underscoreIdx = file.indexOf('_');
198
+ if (underscoreIdx > 0) {
199
+ filesTouched.push(file.substring(underscoreIdx + 1));
200
+ }
201
+ }
202
+ return { projectPath, linkedCommit, filesTouched };
203
+ }
204
+ catch {
205
+ return null;
206
+ }
207
+ }
208
+ // ─────────────────────────────────────────────────────────────
209
+ // Session Parsing
210
+ // ─────────────────────────────────────────────────────────────
211
+ /**
212
+ * Parse a single session by UUID
213
+ */
214
+ export function parseSession(sessionId) {
215
+ const sessionDir = join(BRAIN_DIR, sessionId);
216
+ if (!existsSync(sessionDir))
217
+ return null;
218
+ // Read task.md
219
+ const taskContent = readTextFile(join(sessionDir, 'task.md'));
220
+ if (!taskContent)
221
+ return null; // No task = skip
222
+ const taskParsed = parseTaskMd(taskContent);
223
+ // Read implementation_plan.md (optional)
224
+ const planContent = readTextFile(join(sessionDir, 'implementation_plan.md')) || '';
225
+ // Read metadata files
226
+ const taskMetadata = readJsonFile(join(sessionDir, 'task.md.metadata.json'));
227
+ const planMetadata = readJsonFile(join(sessionDir, 'implementation_plan.md.metadata.json'));
228
+ // Prefer plan metadata summary, fall back to task metadata
229
+ const metadataSummary = planMetadata?.summary || taskMetadata?.summary || taskParsed.title;
230
+ const updatedAt = planMetadata?.updatedAt || taskMetadata?.updatedAt || new Date().toISOString();
231
+ // Get files from plan content
232
+ const filesFromPlan = extractFilesFromPlan(planContent);
233
+ // Get files from code tracker (use most recent as approximation)
234
+ const codeTracker = getMostRecentCodeTracker();
235
+ // Merge files, prefer code tracker
236
+ const allFiles = new Set([
237
+ ...(codeTracker?.filesTouched || []),
238
+ ...filesFromPlan,
239
+ ]);
240
+ return {
241
+ sessionId,
242
+ projectPath: codeTracker?.projectPath || 'unknown',
243
+ linkedCommit: codeTracker?.linkedCommit || null,
244
+ title: taskParsed.title,
245
+ metadataSummary,
246
+ planContent,
247
+ taskContent: taskParsed.content,
248
+ filesTouched: Array.from(allFiles),
249
+ completionStatus: taskParsed.completionStatus,
250
+ updatedAt,
251
+ };
252
+ }
253
+ /**
254
+ * Get sessions sorted by updatedAt (most recent first)
255
+ */
256
+ export function getSessionsSortedByDate() {
257
+ const sessionIds = getAllSessionIds();
258
+ const sessions = [];
259
+ for (const id of sessionIds) {
260
+ const session = parseSession(id);
261
+ if (session) {
262
+ sessions.push(session);
263
+ }
264
+ }
265
+ // Sort by updatedAt descending
266
+ sessions.sort((a, b) => {
267
+ const dateA = new Date(a.updatedAt).getTime();
268
+ const dateB = new Date(b.updatedAt).getTime();
269
+ return dateB - dateA;
270
+ });
271
+ return sessions;
272
+ }
@@ -0,0 +1,24 @@
1
+ export interface ScanResult {
2
+ scanned: number;
3
+ synced: number;
4
+ updated: number;
5
+ skipped: number;
6
+ failed: number;
7
+ errors: string[];
8
+ }
9
+ /**
10
+ * Perform a single scan of Antigravity sessions
11
+ */
12
+ export declare function scanOnce(): Promise<ScanResult>;
13
+ /**
14
+ * Start the periodic scanner
15
+ */
16
+ export declare function startScanner(): void;
17
+ /**
18
+ * Stop the periodic scanner
19
+ */
20
+ export declare function stopScanner(): void;
21
+ /**
22
+ * Check if scanner is running
23
+ */
24
+ export declare function isScannerRunning(): boolean;
@@ -0,0 +1,153 @@
1
+ // Antigravity Periodic Scanner
2
+ // Scans ~/.gemini/antigravity/brain/ for new/updated sessions
3
+ // and syncs them to the Grov API
4
+ import { antigravityExists, getAllSessionIds, parseSession, } from './antigravity-parser.js';
5
+ import { isSynced, needsUpdate, markSynced, pruneOldEntries, } from './antigravity-sync-tracker.js';
6
+ import { getAccessToken, getSyncStatus } from '../../../core/cloud/credentials.js';
7
+ import { getApiUrl } from '../../../core/cloud/api-client.js';
8
+ import { mcpLog } from '../logger.js';
9
+ // ─────────────────────────────────────────────────────────────
10
+ // Constants
11
+ // ─────────────────────────────────────────────────────────────
12
+ const SCAN_INTERVAL_MS = 3 * 60 * 1000; // 3 minutes
13
+ let scannerInterval = null;
14
+ async function syncSessionToApi(session, teamId, token) {
15
+ const apiUrl = getApiUrl();
16
+ const url = `${apiUrl}/teams/${teamId}/antigravity/extract`;
17
+ try {
18
+ const response = await fetch(url, {
19
+ method: 'POST',
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ Authorization: `Bearer ${token}`,
23
+ },
24
+ body: JSON.stringify(session),
25
+ });
26
+ if (!response.ok) {
27
+ const text = await response.text();
28
+ return { success: false, error: `HTTP ${response.status}: ${text}` };
29
+ }
30
+ const result = (await response.json());
31
+ return {
32
+ success: result.success,
33
+ action: result.action,
34
+ memoryId: result.memoryId,
35
+ };
36
+ }
37
+ catch (err) {
38
+ return { success: false, error: err instanceof Error ? err.message : 'Unknown error' };
39
+ }
40
+ }
41
+ /**
42
+ * Perform a single scan of Antigravity sessions
43
+ */
44
+ export async function scanOnce() {
45
+ const result = {
46
+ scanned: 0,
47
+ synced: 0,
48
+ updated: 0,
49
+ skipped: 0,
50
+ failed: 0,
51
+ errors: [],
52
+ };
53
+ // Check prerequisites
54
+ if (!antigravityExists()) {
55
+ mcpLog('[ANTIGRAVITY-SCANNER] Antigravity not installed, skipping scan');
56
+ return result;
57
+ }
58
+ const syncStatus = getSyncStatus();
59
+ if (!syncStatus?.enabled || !syncStatus.teamId) {
60
+ mcpLog('[ANTIGRAVITY-SCANNER] Sync not enabled or no team configured');
61
+ return result;
62
+ }
63
+ const token = await getAccessToken();
64
+ if (!token) {
65
+ mcpLog('[ANTIGRAVITY-SCANNER] No access token, skipping scan');
66
+ return result;
67
+ }
68
+ // Get all sessions
69
+ const sessionIds = getAllSessionIds();
70
+ mcpLog(`[ANTIGRAVITY-SCANNER] Found ${sessionIds.length} total sessions`);
71
+ for (const sessionId of sessionIds) {
72
+ result.scanned++;
73
+ // Parse session
74
+ const session = parseSession(sessionId);
75
+ if (!session) {
76
+ mcpLog(`[ANTIGRAVITY-SCANNER] Failed to parse session ${sessionId}`);
77
+ continue;
78
+ }
79
+ // Check if needs sync
80
+ const alreadySynced = isSynced(sessionId);
81
+ const hasUpdates = needsUpdate(sessionId, session.planContent);
82
+ if (alreadySynced && !hasUpdates) {
83
+ result.skipped++;
84
+ continue;
85
+ }
86
+ // Sync to API
87
+ mcpLog(`[ANTIGRAVITY-SCANNER] Syncing session ${sessionId.slice(0, 8)}... (${alreadySynced ? 'update' : 'new'})`);
88
+ const syncResult = await syncSessionToApi(session, syncStatus.teamId, token);
89
+ if (syncResult.success) {
90
+ markSynced(sessionId, session.planContent);
91
+ if (syncResult.action === 'insert') {
92
+ result.synced++;
93
+ mcpLog(`[ANTIGRAVITY-SCANNER] Inserted memory ${syncResult.memoryId?.slice(0, 8)}...`);
94
+ }
95
+ else if (syncResult.action === 'update') {
96
+ result.updated++;
97
+ mcpLog(`[ANTIGRAVITY-SCANNER] Updated memory ${syncResult.memoryId?.slice(0, 8)}...`);
98
+ }
99
+ else {
100
+ result.skipped++;
101
+ mcpLog(`[ANTIGRAVITY-SCANNER] Skipped (no extractable content)`);
102
+ }
103
+ }
104
+ else {
105
+ result.failed++;
106
+ result.errors.push(`Session ${sessionId.slice(0, 8)}: ${syncResult.error}`);
107
+ mcpLog(`[ANTIGRAVITY-SCANNER] Failed: ${syncResult.error}`);
108
+ }
109
+ }
110
+ // Prune old entries periodically
111
+ pruneOldEntries();
112
+ mcpLog(`[ANTIGRAVITY-SCANNER] Scan complete: scanned=${result.scanned}, synced=${result.synced}, updated=${result.updated}, skipped=${result.skipped}, failed=${result.failed}`);
113
+ return result;
114
+ }
115
+ // ─────────────────────────────────────────────────────────────
116
+ // Scanner Control
117
+ // ─────────────────────────────────────────────────────────────
118
+ /**
119
+ * Start the periodic scanner
120
+ */
121
+ export function startScanner() {
122
+ if (scannerInterval) {
123
+ mcpLog('[ANTIGRAVITY-SCANNER] Scanner already running');
124
+ return;
125
+ }
126
+ mcpLog('[ANTIGRAVITY-SCANNER] Starting periodic scanner (3 min interval)');
127
+ // Run immediately
128
+ scanOnce().catch(err => {
129
+ mcpLog(`[ANTIGRAVITY-SCANNER] Initial scan error: ${err}`);
130
+ });
131
+ // Then run periodically
132
+ scannerInterval = setInterval(() => {
133
+ scanOnce().catch(err => {
134
+ mcpLog(`[ANTIGRAVITY-SCANNER] Scan error: ${err}`);
135
+ });
136
+ }, SCAN_INTERVAL_MS);
137
+ }
138
+ /**
139
+ * Stop the periodic scanner
140
+ */
141
+ export function stopScanner() {
142
+ if (scannerInterval) {
143
+ clearInterval(scannerInterval);
144
+ scannerInterval = null;
145
+ mcpLog('[ANTIGRAVITY-SCANNER] Scanner stopped');
146
+ }
147
+ }
148
+ /**
149
+ * Check if scanner is running
150
+ */
151
+ export function isScannerRunning() {
152
+ return scannerInterval !== null;
153
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Simple hash for detecting plan content changes
3
+ */
4
+ export declare function hashContent(content: string): string;
5
+ /**
6
+ * Check if a session has been synced
7
+ */
8
+ export declare function isSynced(sessionId: string): boolean;
9
+ /**
10
+ * Check if a session needs update (plan content changed)
11
+ */
12
+ export declare function needsUpdate(sessionId: string, planContent: string): boolean;
13
+ /**
14
+ * Mark a session as synced
15
+ */
16
+ export declare function markSynced(sessionId: string, planContent?: string): void;
17
+ /**
18
+ * Get all synced session IDs
19
+ */
20
+ export declare function getSyncedSessionIds(): string[];
21
+ /**
22
+ * Get sessions that haven't been synced yet
23
+ */
24
+ export declare function getUnsynced(allSessionIds: string[]): string[];
25
+ /**
26
+ * Remove old entries to keep file size manageable
27
+ * Keeps most recent 500 entries
28
+ */
29
+ export declare function pruneOldEntries(keepCount?: number): void;