Skip to content

Implement Gemini CLI session importer with ProjectRegistry support for future versions #1

@Haehnchen

Description

@Haehnchen

Gemini CLI Adapter Implementation Guide

Overview

This document describes the implementation of the Gemini CLI session adapter for my-mega-memory. It covers the current structure, the project hash problem, and how to implement the future solution when the ProjectRegistry feature becomes available.

Current Gemini CLI Structure

Session File Location

~/.gemini/tmp/{projectHash}/chats/session-{timestamp}-{id}.json

Example:

~/.gemini/tmp/78e0c7326cf29ebec3d3e54ec35141c0af295bdb67fc76f00e79dc9cff73ba00/chats/session-2026-02-08T17-31-26d30166.json

Session File Format

{
  "sessionId": "26d30166-6e7c-4881-bd90-233138784e9d",
  "projectHash": "78e0c7326cf29ebec3d3e54ec35141c0af295bdb67fc76f00e79dc9cff73ba00",
  "startTime": "2026-02-08T17:31:42.123Z",
  "lastUpdated": "2026-02-08T17:31:42.123Z",
  "messages": [
    {
      "id": "dd659bea-ce51-4962-892d-db3487116dcf",
      "timestamp": "2026-02-08T17:31:42.123Z",
      "type": "user|gemini|error",
      "content": "message content",
      "thoughts": [
        {
          "subject": "thought subject",
          "description": "thought description",
          "timestamp": "2026-02-08T17:31:42.123Z"
        }
      ],
      "tokens": {
        "input": 100,
        "output": 50,
        "cached": 0,
        "thoughts": 10,
        "tool": 0,
        "total": 160
      },
      "model": "gemini-3-pro-preview"
    }
  ]
}

Message Types

  • user - User messages
  • gemini - Assistant responses (may include thoughts array)
  • error - Error messages

The Project Hash Problem

How the Hash is Generated

The projectHash is a SHA256 hash of the absolute project path:

// From: external/gemini-cli/packages/core/src/utils/paths.ts:325
export function getProjectHash(projectRoot: string): string {
  return crypto.createHash('sha256').update(projectRoot).digest('hex');
}

Example:

Path: /home/daniel/projects/my-mega-memory
Hash: 78e0c7326cf29ebec3d3e54ec35141c0af295bdb67fc76f00e79dc9cff73ba00

Why the Hash Cannot Be Reversed

SHA256 is a one-way cryptographic hash function. It is mathematically impossible to reverse the hash back to the original path.

Current Status (Version 0.27.3)

As of Gemini CLI v0.27.3 (commit d5a135b14), there is no mapping file that associates hashes with project names.

Current Solution: Use the hash (first 8 characters) as the project name:

Project name: gemini-78e0c73
Project path: gemini-78e0c73 (same, since no real path is known)

Future Solution: ProjectRegistry Feature

Feature Availability

The ProjectRegistry feature was introduced in commit 6fb3b0900 ("Shorten temp directory #17901"), which is 172 commits after v0.27.3. This feature will be available in v0.28.x or later nightly builds.

What Changes

When the ProjectRegistry feature is active, Gemini CLI creates:

  1. ~/.gemini/projects.json - Maps project paths to short identifiers (slugs)
  2. ~/.gemini/history/ - New directory structure with slug-based organization
  3. Slug-based temp directories - ~/.gemini/tmp/{slug}/ instead of hash-based

projects.json Structure

{
  "projects": {
    "/home/daniel/projects/my-mega-memory": "my-mega-memory",
    "/home/daniel/projects/other-project": "other-project-1",
    "/home/daniel/projects/my-project-2": "my-project-2"
  }
}

How to Reverse-Lookup a Hash

Since the projects.json contains path -> slug mappings, we need to compute the hash for each path to find a match:

import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

interface ProjectsJson {
  projects: Record<string, string>;
}

function computeProjectHash(projectPath: string): string {
  return crypto.createHash('sha256').update(projectPath).digest('hex');
}

/**
 * Find project name by hash using projects.json
 * @returns { projectPath, slug } or null if not found
 */
function findProjectByHash(projectHash: string): { projectPath: string; slug: string } | null {
  const projectsJsonPath = path.join(os.homedir(), '.gemini', 'projects.json');

  if (!fs.existsSync(projectsJsonPath)) {
    return null; // Feature not active yet
  }

  try {
    const content = fs.readFileSync(projectsJsonPath, 'utf-8');
    const data = JSON.parse(content) as ProjectsJson;

    for (const [projectPath, slug] of Object.entries(data.projects)) {
      const hash = computeProjectHash(projectPath);
      if (hash === projectHash) {
        return { projectPath, slug };
      }
    }

    return null; // Hash not found in registry
  } catch (e) {
    console.error('Error reading projects.json:', e);
    return null;
  }
}

Implementation Instructions

Step 1: Check if projects.json Exists

Before trying to resolve hashes, check if the ProjectRegistry feature is active:

const PROJECTS_JSON_PATH = path.join(os.homedir(), '.gemini', 'projects.json');
function isProjectRegistryActive(): boolean {
  return fs.existsSync(PROJECTS_JSON_PATH);
}

Step 2: Update GeminiSessionFinder

Modify src/adapters/gemini/index.ts to resolve project names:

export class GeminiSessionFinder {
  // ... existing code ...

  private resolveProjectName(projectHash: string): string {
    // Try to resolve using projects.json if available
    const resolved = this.findProjectByHash(projectHash);
    if (resolved) {
      return resolved.slug; // or resolved.projectPath for full path
    }

    // Fallback: use hash as project name
    return `gemini-${projectHash.slice(0, 8)}`;
  }

  private findProjectByHash(projectHash: string): { projectPath: string; slug: string } | null {
    // Implementation from above
    // ... (insert findProjectByHash code here)
  }
}

Step 3: Update listSessions Method

When listing sessions, use the resolved project name:

listSessions(): GeminiSessionInfo[] {
  const sessions: GeminiSessionInfo[] = [];
  // ... existing code ...

  for (const projectHash of projectDirs) {
    const projectName = this.resolveProjectName(projectHash);
    const projectPath = this.resolveProjectPath(projectHash); // if needed

    // ... rest of implementation ...
  }

  return sessions;
}

Step 4: Handle Migration Scenarios

When users upgrade from v0.27.3 to a version with ProjectRegistry:

  1. Old sessions still use hash-based directories
  2. New sessions use slug-based directories
  3. The adapter should handle both:
private listSessionDirectories(): string[] {
  const tmpDir = path.join(os.homedir(), '.gemini', 'tmp');

  // List all subdirectories (both hash and slug based)
  if (!fs.existsSync(tmpDir)) {
    return [];
  }

  return fs.readdirSync(tmpDir, { withFileTypes: true })
    .filter(dirent => dirent.isDirectory())
    .map(dirent => dirent.name);
}

private isHashDirectory(name: string): boolean {
  // Hash directories are 64 hex characters
  return /^[a-f0-9]{64}$/.test(name);
}

private resolveProjectDirectory(dirName: string): { hash?: string; slug?: string } {
  if (this.isHashDirectory(dirName)) {
    return { hash: dirName };
  }
  return { slug: dirName };
}

Step 5: Update GeminiAdapter

Modify src/adapters/gemini/adapter.ts to use resolved project names:

async getSessions(): Promise<SessionWithProject[]> {
  const sessions: SessionWithProject[] = [];
  const sessionInfos = this.finder.listSessions();

  for (const info of sessionInfos) {
    // projectName and projectPath are now resolved by the finder
    const projectName = info.projectName; // e.g., "my-mega-memory" or "gemini-78e0c73"
    const projectPath = info.projectPath; // e.g., "/home/daniel/projects/my-mega-memory" or fallback

    sessions.push({
      session,
      provider: SessionProvider.GEMINI,
      projectPath,
      projectName,
      title: session.title,
      created: toDateTimeString(info.created),
      updated: toDateTimeString(info.updated),
    });
  }

  return sessions;
}

Testing

Test Case 1: No projects.json (v0.27.3)

// Should fall back to hash-based names
expect(projectName).toBe('gemini-78e0c73');

Test Case 2: With projects.json (v0.28.x+)

// Should use resolved slug from registry
expect(projectName).toBe('my-mega-memory');

Test Case 3: Hash not in registry

// Should fall back to hash-based name
expect(projectName).toBe('gemini-01ae9d04');

Migration Strategy

When users upgrade to v0.28.x+, the adapter will automatically:

  1. Detect if projects.json exists
  2. Use it for hash resolution when available
  3. Fall back to hash-based names for unmapped projects
  4. Handle both hash-based and slug-based directories

No manual migration is required for the adapter.

References

  • Gemini CLI Source: external/gemini-cli/
  • ProjectRegistry: external/gemini-cli/packages/core/src/config/projectRegistry.ts
  • Storage (hash generation): external/gemini-cli/packages/core/src/config/storage.ts
  • Paths utilities: external/gemini-cli/packages/core/src/utils/paths.ts

Version Information

Component Version Commit Status
Current User Version v0.27.3 d5a135b14 No ProjectRegistry
ProjectRegistry Feature v0.28.x+ 6fb3b0900 Includes projects.json

The ProjectRegistry feature was introduced 172 commits after v0.27.3.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions