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.
103 lines
2.5 KiB
TypeScript
103 lines
2.5 KiB
TypeScript
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;
|
|
}
|