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.
- package/dist/cli/agents/registry.d.ts +17 -0
- package/dist/cli/agents/registry.js +132 -0
- package/dist/cli/commands/agents.d.ts +1 -0
- package/dist/cli/commands/agents.js +48 -0
- package/dist/cli/commands/disable.d.ts +1 -0
- package/dist/cli/commands/disable.js +179 -0
- package/dist/cli/commands/doctor.d.ts +1 -0
- package/dist/cli/commands/doctor.js +157 -0
- package/dist/{commands → cli/commands}/drift-test.js +39 -26
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/commands/init.js +90 -0
- package/dist/{commands → cli/commands}/login.js +19 -18
- package/dist/{commands → cli/commands}/logout.js +1 -1
- package/dist/{commands → cli/commands}/proxy-status.js +1 -1
- package/dist/cli/commands/setup.d.ts +6 -0
- package/dist/cli/commands/setup.js +309 -0
- package/dist/{commands → cli/commands}/status.js +1 -1
- package/dist/{commands → cli/commands}/sync.d.ts +1 -0
- package/dist/{commands → cli/commands}/sync.js +59 -4
- package/dist/{commands → cli/commands}/uninstall.js +2 -2
- package/dist/cli/index.js +270 -0
- package/dist/{lib → core/cloud}/cloud-sync.d.ts +3 -3
- package/dist/{lib → core/cloud}/cloud-sync.js +10 -10
- package/dist/{lib → core/extraction}/correction-builder-proxy.d.ts +1 -1
- package/dist/{lib → core/extraction}/correction-builder-proxy.js +0 -4
- package/dist/{lib → core/extraction}/drift-checker-proxy.d.ts +13 -9
- package/dist/core/extraction/drift-checker-proxy.js +510 -0
- package/dist/{lib → core/extraction}/llm-extractor.d.ts +8 -38
- package/dist/{lib → core/extraction}/llm-extractor.js +132 -220
- package/dist/{lib → core}/store/sessions.js +3 -19
- package/dist/core/store/store.d.ts +1 -0
- package/dist/{lib → core/store}/store.js +1 -1
- package/dist/{lib → core}/store/types.d.ts +0 -4
- package/dist/integrations/mcp/cache.d.ts +27 -0
- package/dist/integrations/mcp/cache.js +106 -0
- package/dist/integrations/mcp/capture/antigravity-parser.d.ts +26 -0
- package/dist/integrations/mcp/capture/antigravity-parser.js +272 -0
- package/dist/integrations/mcp/capture/antigravity-scanner.d.ts +24 -0
- package/dist/integrations/mcp/capture/antigravity-scanner.js +153 -0
- package/dist/integrations/mcp/capture/antigravity-sync-tracker.d.ts +29 -0
- package/dist/integrations/mcp/capture/antigravity-sync-tracker.js +115 -0
- package/dist/integrations/mcp/capture/cli-extractor.d.ts +18 -0
- package/dist/integrations/mcp/capture/cli-extractor.js +258 -0
- package/dist/integrations/mcp/capture/cli-synced.d.ts +4 -0
- package/dist/integrations/mcp/capture/cli-synced.js +62 -0
- package/dist/integrations/mcp/capture/cli-transform.d.ts +30 -0
- package/dist/integrations/mcp/capture/cli-transform.js +62 -0
- package/dist/integrations/mcp/capture/cli-watcher.d.ts +31 -0
- package/dist/integrations/mcp/capture/cli-watcher.js +106 -0
- package/dist/integrations/mcp/capture/hook-handler.d.ts +2 -0
- package/dist/integrations/mcp/capture/hook-handler.js +157 -0
- package/dist/integrations/mcp/capture/sqlite-reader.d.ts +35 -0
- package/dist/integrations/mcp/capture/sqlite-reader.js +388 -0
- package/dist/integrations/mcp/capture/sync-tracker.d.ts +16 -0
- package/dist/integrations/mcp/capture/sync-tracker.js +102 -0
- package/dist/integrations/mcp/clients/cursor/rules-installer.d.ts +19 -0
- package/dist/integrations/mcp/clients/cursor/rules-installer.js +123 -0
- package/dist/integrations/mcp/index.d.ts +1 -0
- package/dist/integrations/mcp/index.js +94 -0
- package/dist/integrations/mcp/logger.d.ts +8 -0
- package/dist/integrations/mcp/logger.js +50 -0
- package/dist/integrations/mcp/server.d.ts +5 -0
- package/dist/integrations/mcp/server.js +58 -0
- package/dist/integrations/mcp/tools/expand.d.ts +1 -0
- package/dist/integrations/mcp/tools/expand.js +53 -0
- package/dist/integrations/mcp/tools/preview.d.ts +1 -0
- package/dist/integrations/mcp/tools/preview.js +64 -0
- package/dist/integrations/proxy/agents/base.d.ts +43 -0
- package/dist/integrations/proxy/agents/base.js +13 -0
- package/dist/{proxy/utils → integrations/proxy/agents/claude}/extractors.d.ts +4 -8
- package/dist/{proxy/utils → integrations/proxy/agents/claude}/extractors.js +4 -33
- package/dist/{proxy → integrations/proxy/agents/claude}/forwarder.d.ts +1 -1
- package/dist/{proxy → integrations/proxy/agents/claude}/forwarder.js +22 -6
- package/dist/integrations/proxy/agents/claude/index.d.ts +43 -0
- package/dist/integrations/proxy/agents/claude/index.js +386 -0
- package/dist/{proxy/action-parser.d.ts → integrations/proxy/agents/claude/parser.d.ts} +1 -1
- package/dist/integrations/proxy/agents/codex/extractors.d.ts +6 -0
- package/dist/integrations/proxy/agents/codex/extractors.js +49 -0
- package/dist/integrations/proxy/agents/codex/forwarder.d.ts +9 -0
- package/dist/integrations/proxy/agents/codex/forwarder.js +125 -0
- package/dist/integrations/proxy/agents/codex/index.d.ts +44 -0
- package/dist/integrations/proxy/agents/codex/index.js +371 -0
- package/dist/integrations/proxy/agents/codex/parser.d.ts +11 -0
- package/dist/integrations/proxy/agents/codex/parser.js +104 -0
- package/dist/integrations/proxy/agents/codex/patch.d.ts +12 -0
- package/dist/integrations/proxy/agents/codex/patch.js +40 -0
- package/dist/integrations/proxy/agents/codex/settings.d.ts +18 -0
- package/dist/integrations/proxy/agents/codex/settings.js +73 -0
- package/dist/integrations/proxy/agents/codex/types.d.ts +59 -0
- package/dist/integrations/proxy/agents/codex/types.js +2 -0
- package/dist/integrations/proxy/agents/index.d.ts +11 -0
- package/dist/integrations/proxy/agents/index.js +25 -0
- package/dist/integrations/proxy/agents/types.d.ts +77 -0
- package/dist/integrations/proxy/agents/types.js +2 -0
- package/dist/{proxy → integrations/proxy/cache}/extended-cache.js +2 -6
- package/dist/{proxy → integrations/proxy}/config.js +1 -1
- package/dist/{proxy → integrations/proxy}/handlers/preprocess.d.ts +3 -3
- package/dist/integrations/proxy/handlers/preprocess.js +194 -0
- package/dist/integrations/proxy/index.js +20 -0
- package/dist/integrations/proxy/injection/memory-injection.d.ts +56 -0
- package/dist/integrations/proxy/injection/memory-injection.js +252 -0
- package/dist/integrations/proxy/orchestrator.d.ts +30 -0
- package/dist/integrations/proxy/orchestrator.js +954 -0
- package/dist/integrations/proxy/request-processor.d.ts +14 -0
- package/dist/integrations/proxy/request-processor.js +68 -0
- package/dist/{proxy → integrations/proxy}/response-processor.d.ts +4 -3
- package/dist/{proxy → integrations/proxy}/response-processor.js +51 -43
- package/dist/{proxy → integrations/proxy}/server.d.ts +0 -1
- package/dist/integrations/proxy/server.js +146 -0
- package/dist/{proxy → integrations/proxy}/types.d.ts +4 -0
- package/dist/{proxy → integrations/proxy}/utils/logging.d.ts +1 -0
- package/dist/{proxy → integrations/proxy}/utils/logging.js +5 -0
- package/package.json +31 -10
- package/postinstall.js +62 -6
- package/dist/cli.js +0 -149
- package/dist/commands/capture.d.ts +0 -6
- package/dist/commands/capture.js +0 -324
- package/dist/commands/disable.d.ts +0 -1
- package/dist/commands/disable.js +0 -14
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/doctor.js +0 -89
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.js +0 -52
- package/dist/commands/inject.d.ts +0 -5
- package/dist/commands/inject.js +0 -88
- package/dist/commands/prompt-inject.d.ts +0 -4
- package/dist/commands/prompt-inject.js +0 -451
- package/dist/commands/unregister.d.ts +0 -1
- package/dist/commands/unregister.js +0 -28
- package/dist/lib/anchor-extractor.d.ts +0 -30
- package/dist/lib/anchor-extractor.js +0 -296
- package/dist/lib/correction-builder.d.ts +0 -10
- package/dist/lib/correction-builder.js +0 -226
- package/dist/lib/drift-checker-proxy.js +0 -373
- package/dist/lib/drift-checker.d.ts +0 -66
- package/dist/lib/drift-checker.js +0 -341
- package/dist/lib/hooks.d.ts +0 -38
- package/dist/lib/hooks.js +0 -291
- package/dist/lib/jsonl-parser.d.ts +0 -87
- package/dist/lib/jsonl-parser.js +0 -281
- package/dist/lib/session-parser.d.ts +0 -44
- package/dist/lib/session-parser.js +0 -256
- package/dist/lib/store.d.ts +0 -1
- package/dist/proxy/cache.d.ts +0 -32
- package/dist/proxy/cache.js +0 -47
- package/dist/proxy/handlers/preprocess.js +0 -186
- package/dist/proxy/index.js +0 -30
- package/dist/proxy/injection/delta-tracking.d.ts +0 -11
- package/dist/proxy/injection/delta-tracking.js +0 -94
- package/dist/proxy/injection/injectors.d.ts +0 -7
- package/dist/proxy/injection/injectors.js +0 -139
- package/dist/proxy/request-processor.d.ts +0 -27
- package/dist/proxy/request-processor.js +0 -233
- package/dist/proxy/server.js +0 -1289
- /package/dist/{commands → cli/commands}/drift-test.d.ts +0 -0
- /package/dist/{commands → cli/commands}/login.d.ts +0 -0
- /package/dist/{commands → cli/commands}/logout.d.ts +0 -0
- /package/dist/{commands → cli/commands}/proxy-status.d.ts +0 -0
- /package/dist/{commands → cli/commands}/status.d.ts +0 -0
- /package/dist/{commands → cli/commands}/uninstall.d.ts +0 -0
- /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
- /package/dist/{lib → core/cloud}/api-client.d.ts +0 -0
- /package/dist/{lib → core/cloud}/api-client.js +0 -0
- /package/dist/{lib → core/cloud}/credentials.d.ts +0 -0
- /package/dist/{lib → core/cloud}/credentials.js +0 -0
- /package/dist/{lib → core}/store/convenience.d.ts +0 -0
- /package/dist/{lib → core}/store/convenience.js +0 -0
- /package/dist/{lib → core}/store/database.d.ts +0 -0
- /package/dist/{lib → core}/store/database.js +0 -0
- /package/dist/{lib → core}/store/drift.d.ts +0 -0
- /package/dist/{lib → core}/store/drift.js +0 -0
- /package/dist/{lib → core}/store/index.d.ts +0 -0
- /package/dist/{lib → core}/store/index.js +0 -0
- /package/dist/{lib → core}/store/sessions.d.ts +0 -0
- /package/dist/{lib → core}/store/steps.d.ts +0 -0
- /package/dist/{lib → core}/store/steps.js +0 -0
- /package/dist/{lib → core}/store/tasks.d.ts +0 -0
- /package/dist/{lib → core}/store/tasks.js +0 -0
- /package/dist/{lib → core}/store/types.js +0 -0
- /package/dist/{proxy/action-parser.js → integrations/proxy/agents/claude/parser.js} +0 -0
- /package/dist/{lib → integrations/proxy/agents/claude}/settings.d.ts +0 -0
- /package/dist/{lib → integrations/proxy/agents/claude}/settings.js +0 -0
- /package/dist/{proxy → integrations/proxy/cache}/extended-cache.d.ts +0 -0
- /package/dist/{proxy → integrations/proxy}/config.d.ts +0 -0
- /package/dist/{proxy → integrations/proxy}/index.d.ts +0 -0
- /package/dist/{proxy → integrations/proxy}/types.js +0 -0
- /package/dist/{lib → utils}/debug.d.ts +0 -0
- /package/dist/{lib → utils}/debug.js +0 -0
- /package/dist/{lib → utils}/utils.d.ts +0 -0
- /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;
|