Add structured project tools with rich relationships
Replace chunk-based project tools with dedicated structured project tools that store projects as single Qdrant points with rich JSON payloads. Adds list_projects, get_project, upsert_project, update_project, and delete_project tools with Zod discriminated union validation for per-category relationship operations.
This commit is contained in:
102
src/lib/projects.ts
Normal file
102
src/lib/projects.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { createHash } from "crypto";
|
||||
import { embed } from "./embeddings.js";
|
||||
import { getClient, COLLECTION } from "./qdrant.js";
|
||||
|
||||
// --- Relationship types ---
|
||||
|
||||
export interface HostRelationship {
|
||||
host: string;
|
||||
role: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface SoftwareRelationship {
|
||||
name: string;
|
||||
role: string;
|
||||
version?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface GitRepoRelationship {
|
||||
repo: string;
|
||||
path?: string;
|
||||
role: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface LocalDirRelationship {
|
||||
path: string;
|
||||
role: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface ProjectRelationships {
|
||||
hosts: HostRelationship[];
|
||||
software: SoftwareRelationship[];
|
||||
git_repos: GitRepoRelationship[];
|
||||
local_dirs: LocalDirRelationship[];
|
||||
}
|
||||
|
||||
// --- Project payload (stored as Qdrant point payload) ---
|
||||
|
||||
export interface ProjectPayload {
|
||||
slug: string;
|
||||
name: string;
|
||||
status: string;
|
||||
summary: string;
|
||||
tags: string[];
|
||||
current_state: string;
|
||||
blockers: string[];
|
||||
decisions_pending: string[];
|
||||
relationships: ProjectRelationships;
|
||||
key_facts: string[];
|
||||
history: string[];
|
||||
updated: string;
|
||||
type: "project";
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
export function projectPointId(slug: string): string {
|
||||
const hash = createHash("sha256").update(`project\0${slug}`).digest("hex");
|
||||
return [
|
||||
hash.slice(0, 8),
|
||||
hash.slice(8, 12),
|
||||
hash.slice(12, 16),
|
||||
hash.slice(16, 20),
|
||||
hash.slice(20, 32),
|
||||
].join("-");
|
||||
}
|
||||
|
||||
export function buildEmbeddingText(payload: ProjectPayload): string {
|
||||
const parts = [payload.summary, payload.current_state, ...payload.key_facts, ...payload.tags];
|
||||
return parts.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
export function emptyRelationships(): ProjectRelationships {
|
||||
return { hosts: [], software: [], git_repos: [], local_dirs: [] };
|
||||
}
|
||||
|
||||
// --- Data access ---
|
||||
|
||||
export async function getProjectPoint(slug: string): Promise<ProjectPayload | null> {
|
||||
const client = getClient();
|
||||
const id = projectPointId(slug);
|
||||
try {
|
||||
const points = await client.retrieve(COLLECTION, { ids: [id], with_payload: true, with_vector: false });
|
||||
if (points.length === 0) return null;
|
||||
return points[0].payload as unknown as ProjectPayload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function upsertProjectPoint(payload: ProjectPayload): Promise<string> {
|
||||
const client = getClient();
|
||||
const id = projectPointId(payload.slug);
|
||||
const vector = await embed(buildEmbeddingText(payload));
|
||||
await client.upsert(COLLECTION, {
|
||||
points: [{ id, vector, payload: payload as unknown as Record<string, unknown> }],
|
||||
});
|
||||
return id;
|
||||
}
|
||||
Reference in New Issue
Block a user