mirror of
https://github.com/obra/superpowers.git
synced 2026-04-25 02:59:05 +08:00
Migrate conversation search to use PERSONAL_SUPERPOWERS_DIR
Updates conversation indexing and search to use the new personal superpowers directory structure with environment variable support. Changes: - Added src/paths.ts for centralized directory resolution - Updated db.ts, indexer.ts, verify.ts to use paths.ts - Created migrate-to-config.sh for data migration from ~/.clank - Updated all documentation references from ~/.clank to ~/.config/superpowers - Database paths automatically updated during migration Migration tested with 5,017 conversations and 6,385 exchanges.
This commit is contained in:
@@ -1,13 +1,9 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { ConversationExchange } from './types.js';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import * as sqliteVec from 'sqlite-vec';
|
||||
|
||||
function getDbPath(): string {
|
||||
return process.env.TEST_DB_PATH || path.join(os.homedir(), '.clank', 'conversation-index', 'db.sqlite');
|
||||
}
|
||||
import { getDbPath } from './paths.js';
|
||||
|
||||
export function migrateSchema(db: Database.Database): void {
|
||||
const hasColumn = db.prepare(`
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import { verifyIndex, repairIndex } from './verify.js';
|
||||
import { indexSession, indexUnprocessed, indexConversations } from './indexer.js';
|
||||
import { initDatabase } from './db.js';
|
||||
import { getDbPath, getArchiveDir } from './paths.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
@@ -74,14 +74,14 @@ async function main() {
|
||||
console.log('Rebuilding entire index...');
|
||||
|
||||
// Delete database
|
||||
const dbPath = path.join(os.homedir(), '.clank', 'conversation-index', 'db.sqlite');
|
||||
const dbPath = getDbPath();
|
||||
if (fs.existsSync(dbPath)) {
|
||||
fs.unlinkSync(dbPath);
|
||||
console.log('Deleted existing database');
|
||||
}
|
||||
|
||||
// Delete all summary files
|
||||
const archiveDir = path.join(os.homedir(), '.clank', 'conversation-archive');
|
||||
const archiveDir = getArchiveDir();
|
||||
if (fs.existsSync(archiveDir)) {
|
||||
const projects = fs.readdirSync(archiveDir);
|
||||
for (const project of projects) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { parseConversation } from './parser.js';
|
||||
import { initEmbeddings, generateExchangeEmbedding } from './embeddings.js';
|
||||
import { summarizeConversation } from './summarizer.js';
|
||||
import { ConversationExchange } from './types.js';
|
||||
import { getArchiveDir, getExcludeConfigPath } from './paths.js';
|
||||
|
||||
// Set max output tokens for Claude SDK (used by summarizer)
|
||||
process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = '20000';
|
||||
@@ -19,10 +20,6 @@ function getProjectsDir(): string {
|
||||
return process.env.TEST_PROJECTS_DIR || path.join(os.homedir(), '.claude', 'projects');
|
||||
}
|
||||
|
||||
function getArchiveDir(): string {
|
||||
return process.env.TEST_ARCHIVE_DIR || path.join(os.homedir(), '.clank', 'conversation-archive');
|
||||
}
|
||||
|
||||
// Projects to exclude from indexing (configurable via env or config file)
|
||||
function getExcludedProjects(): string[] {
|
||||
// Check env variable first
|
||||
@@ -31,7 +28,7 @@ function getExcludedProjects(): string[] {
|
||||
}
|
||||
|
||||
// Check for config file
|
||||
const configPath = path.join(os.homedir(), '.clank', 'conversation-index', 'exclude.txt');
|
||||
const configPath = getExcludeConfigPath();
|
||||
if (fs.existsSync(configPath)) {
|
||||
const content = fs.readFileSync(configPath, 'utf-8');
|
||||
return content.split('\n').map(line => line.trim()).filter(line => line && !line.startsWith('#'));
|
||||
@@ -71,7 +68,7 @@ export async function indexConversations(
|
||||
|
||||
console.log('Scanning for conversation files...');
|
||||
const PROJECTS_DIR = getProjectsDir();
|
||||
const ARCHIVE_DIR = getArchiveDir();
|
||||
const ARCHIVE_DIR = getArchiveDir(); // Now uses paths.ts
|
||||
const projects = fs.readdirSync(PROJECTS_DIR);
|
||||
|
||||
let totalExchanges = 0;
|
||||
@@ -195,7 +192,7 @@ export async function indexSession(sessionId: string, concurrency: number = 1):
|
||||
|
||||
// Find the conversation file for this session
|
||||
const PROJECTS_DIR = getProjectsDir();
|
||||
const ARCHIVE_DIR = getArchiveDir();
|
||||
const ARCHIVE_DIR = getArchiveDir(); // Now uses paths.ts
|
||||
const projects = fs.readdirSync(PROJECTS_DIR);
|
||||
const excludedProjects = getExcludedProjects();
|
||||
let found = false;
|
||||
@@ -268,7 +265,7 @@ export async function indexUnprocessed(concurrency: number = 1): Promise<void> {
|
||||
await initEmbeddings();
|
||||
|
||||
const PROJECTS_DIR = getProjectsDir();
|
||||
const ARCHIVE_DIR = getArchiveDir();
|
||||
const ARCHIVE_DIR = getArchiveDir(); // Now uses paths.ts
|
||||
const projects = fs.readdirSync(PROJECTS_DIR);
|
||||
const excludedProjects = getExcludedProjects();
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Get the personal superpowers directory
|
||||
*
|
||||
* Precedence:
|
||||
* 1. PERSONAL_SUPERPOWERS_DIR env var (if set)
|
||||
* 2. XDG_CONFIG_HOME/superpowers (if XDG_CONFIG_HOME is set)
|
||||
* 3. ~/.config/superpowers (default)
|
||||
*/
|
||||
export function getSuperpowersDir(): string {
|
||||
if (process.env.PERSONAL_SUPERPOWERS_DIR) {
|
||||
return process.env.PERSONAL_SUPERPOWERS_DIR;
|
||||
}
|
||||
|
||||
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
||||
if (xdgConfigHome) {
|
||||
return path.join(xdgConfigHome, 'superpowers');
|
||||
}
|
||||
|
||||
return path.join(os.homedir(), '.config', 'superpowers');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation archive directory
|
||||
*/
|
||||
export function getArchiveDir(): string {
|
||||
// Allow test override
|
||||
if (process.env.TEST_ARCHIVE_DIR) {
|
||||
return process.env.TEST_ARCHIVE_DIR;
|
||||
}
|
||||
|
||||
return path.join(getSuperpowersDir(), 'conversation-archive');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation index directory
|
||||
*/
|
||||
export function getIndexDir(): string {
|
||||
return path.join(getSuperpowersDir(), 'conversation-index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database path
|
||||
*/
|
||||
export function getDbPath(): string {
|
||||
return path.join(getIndexDir(), 'db.sqlite');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exclude config path
|
||||
*/
|
||||
export function getExcludeConfigPath(): string {
|
||||
return path.join(getIndexDir(), 'exclude.txt');
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { parseConversation } from './parser.js';
|
||||
import { initDatabase, getAllExchanges, getFileLastIndexed } from './db.js';
|
||||
import { getArchiveDir } from './paths.js';
|
||||
|
||||
export interface VerificationResult {
|
||||
missing: Array<{ path: string; reason: string }>;
|
||||
@@ -11,11 +11,6 @@ export interface VerificationResult {
|
||||
corrupted: Array<{ path: string; error: string }>;
|
||||
}
|
||||
|
||||
// Allow overriding paths for testing
|
||||
function getArchiveDir(): string {
|
||||
return process.env.TEST_ARCHIVE_DIR || path.join(os.homedir(), '.clank', 'conversation-archive');
|
||||
}
|
||||
|
||||
export async function verifyIndex(): Promise<VerificationResult> {
|
||||
const result: VerificationResult = {
|
||||
missing: [],
|
||||
|
||||
Reference in New Issue
Block a user