scai
Version:
> **A local-first AI CLI for understanding, querying, and iterating on large codebases.** > **100% local • No token costs • No cloud • No prompt injection • Private by design**
255 lines (214 loc) • 7.63 kB
JavaScript
import { getDbForRepo } from "./client.js";
export function initSchema() {
const db = getDbForRepo();
// --- Global state ---
db.exec(`
CREATE TABLE IF NOT EXISTS global_state (
id INTEGER PRIMARY KEY CHECK (id = 1),
-- user / system preferences
default_execution_mode TEXT, -- explain | analyze | transform
preferred_language TEXT, -- "en", "da", etc.
-- learned conventions (lightweight)
conventions_json TEXT, -- JSON: naming, formatting, habits
memory_notes TEXT, -- freeform distilled memory
-- bookkeeping
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
`);
// --- Projects ---
db.exec(`
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- binding (technical, not conceptual)
repo_path TEXT NOT NULL, -- absolute path to repo root
-- human-facing
name TEXT NOT NULL, -- project name
summary TEXT, -- evolving description
-- distilled understanding
goals_json TEXT,
constraints_json TEXT,
assumptions_json TEXT,
-- bookkeeping
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_projects_repo
ON projects(repo_path);
`);
// --- Tasks (controlled input only) ---
db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
-- lifecycle
-- active | completed | paused | needs-feedback
status TEXT NOT NULL DEFAULT 'active',
-- user-facing / context
initial_query TEXT NOT NULL,
summary TEXT,
-- resolved intent
agreed_intent TEXT,
intent_category TEXT,
normalized_query TEXT,
intent_confidence REAL,
-- focus / file selection
relevant_files_json TEXT,
missing_files_json TEXT,
-- routing decision
routing_decision_json TEXT,
-- 🔴 task-level feedback / questions (append-only)
task_questions_json TEXT,
-- bookkeeping
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (project_id)
REFERENCES projects(id)
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_tasks_project
ON tasks(project_id);
CREATE INDEX IF NOT EXISTS idx_tasks_status
ON tasks(status);
`);
// --- Task steps ---
db.exec(`
CREATE TABLE IF NOT EXISTS task_steps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER NOT NULL,
file_path TEXT NOT NULL,
action TEXT,
-- pending | completed | skipped | needs-feedback
status TEXT NOT NULL DEFAULT 'pending',
start_time INTEGER,
end_time INTEGER,
result_json TEXT,
notes TEXT,
step_index INTEGER,
-- 🔴 step-level feedback / questions (append-only)
step_questions_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (task_id)
REFERENCES tasks(id)
ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_task_steps_task
ON task_steps(task_id);
CREATE INDEX IF NOT EXISTS idx_task_steps_status
ON task_steps(status);
CREATE UNIQUE INDEX IF NOT EXISTS idx_task_steps_task_file
ON task_steps(task_id, file_path);
`);
// --- Core tables ---
db.exec(`
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT UNIQUE, -- full path
filename TEXT,
summary TEXT, -- optional, can be generated later
content_text TEXT, -- actual file content
type TEXT, -- file type
last_modified TEXT, -- file system last modified
indexed_at TEXT, -- timestamp when indexed
functions_extracted_at TEXT, -- timestamp when functions/classes extracted
processing_status TEXT NOT NULL DEFAULT 'unprocessed' -- tracks pipeline stage
);
CREATE VIRTUAL TABLE IF NOT EXISTS files_fts
USING fts5(
filename,
summary,
path,
content_text,
content='files',
content_rowid='id'
);
`);
// --- Folder capsules ---
db.exec(`
CREATE TABLE IF NOT EXISTS folder_capsules(
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT UNIQUE NOT NULL,
depth INTEGER NOT NULL,
capsule_json TEXT NOT NULL,
confidence REAL,
last_generated TEXT,
source_file_count INTEGER
);
CREATE INDEX IF NOT EXISTS idx_folder_capsules_path
ON folder_capsules(path);
CREATE INDEX IF NOT EXISTS idx_folder_capsules_depth
ON folder_capsules(depth);
`);
// --- Functions table ---
db.exec(`
CREATE TABLE IF NOT EXISTS functions(
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_id INTEGER REFERENCES files(id),
name TEXT,
unique_id TEXT UNIQUE, --e.g. "buildContextualPrompt@cli/src/utils/buildContextualPrompt.ts"
start_line INTEGER,
end_line INTEGER,
content TEXT,
lang TEXT
);
CREATE INDEX IF NOT EXISTS idx_functions_file_id ON functions(file_id);
CREATE INDEX IF NOT EXISTS idx_functions_unique_id ON functions(unique_id);
`);
// --- Graph-specific additions ---
db.exec(`
CREATE TABLE IF NOT EXISTS graph_classes(
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_id INTEGER REFERENCES files(id),
name TEXT,
unique_id TEXT UNIQUE,
start_line INTEGER,
end_line INTEGER,
content TEXT,
lang TEXT
);
CREATE INDEX IF NOT EXISTS idx_graph_classes_file_id ON graph_classes(file_id);
CREATE INDEX IF NOT EXISTS idx_graph_classes_unique_id ON graph_classes(unique_id);
`);
db.exec(`
CREATE TABLE IF NOT EXISTS graph_edges(
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_type TEXT NOT NULL,
source_unique_id TEXT NOT NULL,
target_type TEXT NOT NULL,
target_unique_id TEXT NOT NULL,
relation TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges(source_type, source_unique_id);
CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges(target_type, target_unique_id);
CREATE INDEX IF NOT EXISTS idx_graph_edges_relation ON graph_edges(relation);
`);
db.exec(`
CREATE TABLE IF NOT EXISTS graph_tags_master(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS graph_entity_tags(
id INTEGER PRIMARY KEY AUTOINCREMENT,
entity_type TEXT NOT NULL,
entity_unique_id TEXT NOT NULL,
tag_id INTEGER NOT NULL REFERENCES graph_tags_master(id),
UNIQUE(entity_type, entity_unique_id, tag_id)
);
CREATE INDEX IF NOT EXISTS idx_graph_entity_tags_entity ON graph_entity_tags(entity_type, entity_unique_id);
CREATE INDEX IF NOT EXISTS idx_graph_entity_tags_tag ON graph_entity_tags(tag_id);
`);
db.exec(`
CREATE TABLE IF NOT EXISTS summaries(
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT UNIQUE,
type TEXT NOT NULL CHECK(type IN('folder', 'project')),
summary TEXT,
last_generated TEXT,
child_latest_modified TEXT
);
CREATE INDEX IF NOT EXISTS idx_summaries_type ON summaries(type);
CREATE INDEX IF NOT EXISTS idx_summaries_path ON summaries(path);
`);
console.log("✅ Graph schema initialized (files, functions, classes, edges, tags, summaries, FTS content_text)");
}