Initial curated projectbrain source from 8a7b1e0651dbdab97d744cbd2e653ab3970ac6c5
This commit is contained in:
commit
5aa3e8d685
700 files changed
+132801
No files matched your search
@@ -0,0 +1,270 @@
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import { ApprovalState, EvidenceStrength, VerificationState } from "@prisma/client";
|
||||
import { prisma } from "../db/prisma.js";
|
||||
import type {
|
||||
KnowledgeItemDto,
|
||||
KnowledgeListFilters,
|
||||
KnowledgeMutationRequestDto,
|
||||
MemoryRuleDto,
|
||||
MemoryRuleListFilters,
|
||||
MemoryRuleMutationRequestDto,
|
||||
} from "../types/api.js";
|
||||
|
||||
export type DocumentationItemDto = {
|
||||
id: string;
|
||||
projectId: string;
|
||||
title: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
linkedFeatureIds: string[];
|
||||
lastUpdated?: string;
|
||||
primaryContextPackId?: string;
|
||||
selectedContextPackIds?: string[];
|
||||
contextSelectionSummary?: string;
|
||||
approvalState: "pending" | "approved" | "rejected" | "blocked";
|
||||
content?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
type ApprovalStateInput = ApprovalState | "pending" | "approved" | "rejected" | "blocked";
|
||||
|
||||
export type DocumentationListFilters = {
|
||||
kind?: string[];
|
||||
status?: string[];
|
||||
approvalState?: string[];
|
||||
search?: string;
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
|
||||
const toStringArray = (value: unknown): string[] => Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
|
||||
const toJson = (value: unknown): Prisma.InputJsonValue => JSON.parse(JSON.stringify(value)) as Prisma.InputJsonValue;
|
||||
|
||||
export const projectExists = async (projectIdOrSlug: string): Promise<boolean> => {
|
||||
const project = await prisma.project.findFirst({ where: { OR: [{ id: projectIdOrSlug }, { slug: projectIdOrSlug }, { readableId: projectIdOrSlug }], deletedAt: null }, select: { id: true }});
|
||||
return Boolean(project);
|
||||
};
|
||||
|
||||
const getProject = async (projectIdOrSlug: string) => {
|
||||
const project = await prisma.project.findFirst({ where: { OR: [{ id: projectIdOrSlug }, { slug: projectIdOrSlug }, { readableId: projectIdOrSlug }], deletedAt: null }});
|
||||
if (!project) throw new Error(`Project '${projectIdOrSlug}' not found`);
|
||||
return project;
|
||||
};
|
||||
|
||||
const metadata = (value: Prisma.JsonValue | null | undefined): Record<string, unknown> => isRecord(value) ? value : {};
|
||||
|
||||
const trust = (source: string, confidence: number, approvalState: ApprovalState, createdAt: Date) => ({
|
||||
source,
|
||||
confidence,
|
||||
evidence: [],
|
||||
lastChecked: createdAt.toISOString(),
|
||||
approvalState,
|
||||
});
|
||||
|
||||
export const toKnowledgeDto = (item: {
|
||||
slug: string | null; readableId: string | null; project: { slug: string }; title: string; category: string; content: string; source: string; confidence: number; metadataJson: Prisma.JsonValue | null; trustJson: Prisma.JsonValue | null; approvalState: ApprovalState; createdAt: Date; updatedAt: Date;
|
||||
}): KnowledgeItemDto => {
|
||||
const meta = metadata(item.metadataJson);
|
||||
const trustJson = isRecord(item.trustJson) ? item.trustJson : trust(item.source, item.confidence, item.approvalState, item.updatedAt);
|
||||
return {
|
||||
id: item.slug ?? item.readableId ?? item.title,
|
||||
projectId: item.project.slug,
|
||||
title: item.title,
|
||||
category: item.category,
|
||||
content: item.content,
|
||||
linkedFeatureIds: toStringArray(meta.linkedFeatureIds),
|
||||
source: item.source,
|
||||
confidence: item.confidence,
|
||||
includeInContextPacks: toStringArray(meta.includeInContextPacks),
|
||||
trust: trustJson as KnowledgeItemDto["trust"],
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
updatedAt: item.updatedAt.toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
export const toMemoryRuleDto = (rule: {
|
||||
slug: string | null; readableId: string | null; project: { slug: string }; title: string; rule: string; scope: string; category: string; severity: string; includeInContextPacks: boolean; confidence: number; trustJson: Prisma.JsonValue | null; provenanceJson: Prisma.JsonValue | null; approvalState: ApprovalState; createdAt: Date; updatedAt: Date;
|
||||
}): MemoryRuleDto => {
|
||||
const meta = metadata(rule.provenanceJson);
|
||||
const trustJson = isRecord(rule.trustJson) ? rule.trustJson : trust("database memory rule", rule.confidence, rule.approvalState, rule.updatedAt);
|
||||
return {
|
||||
id: rule.slug ?? rule.readableId ?? rule.title,
|
||||
projectId: rule.project.slug,
|
||||
title: rule.title,
|
||||
rule: rule.rule,
|
||||
scope: rule.scope as MemoryRuleDto["scope"],
|
||||
category: rule.category as MemoryRuleDto["category"],
|
||||
severity: rule.severity as MemoryRuleDto["severity"],
|
||||
linkedFeatureIds: toStringArray(meta.linkedFeatureIds),
|
||||
includeInContextPacks: toStringArray(meta.includeInContextPacks),
|
||||
includeInContextPacksDefault: rule.includeInContextPacks,
|
||||
trust: trustJson as MemoryRuleDto["trust"],
|
||||
createdAt: rule.createdAt.toISOString(),
|
||||
updatedAt: rule.updatedAt.toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
export const toDocumentationDto = (doc: {
|
||||
slug: string | null; readableId: string | null; project: { slug: string }; title: string; kind: string; status: string; approvalState: ApprovalState; content: string | null; metadataJson: Prisma.JsonValue | null; createdAt: Date; updatedAt: Date;
|
||||
}): DocumentationItemDto => {
|
||||
const meta = metadata(doc.metadataJson);
|
||||
return {
|
||||
id: doc.slug ?? doc.readableId ?? doc.title,
|
||||
projectId: doc.project.slug,
|
||||
title: doc.title,
|
||||
kind: doc.kind,
|
||||
status: doc.status,
|
||||
linkedFeatureIds: toStringArray(meta.linkedFeatureIds),
|
||||
lastUpdated: typeof meta.lastUpdated === "string" ? meta.lastUpdated : doc.updatedAt.toISOString(),
|
||||
primaryContextPackId: typeof meta.primaryContextPackId === "string" ? meta.primaryContextPackId : undefined,
|
||||
selectedContextPackIds: toStringArray(meta.selectedContextPackIds),
|
||||
contextSelectionSummary: typeof meta.contextSelectionSummary === "string" ? meta.contextSelectionSummary : undefined,
|
||||
approvalState: doc.approvalState,
|
||||
content: doc.content ?? undefined,
|
||||
createdAt: doc.createdAt.toISOString(),
|
||||
updatedAt: doc.updatedAt.toISOString(),
|
||||
};
|
||||
};
|
||||
|
||||
const filterSearch = (haystack: string, search?: string) => !search || haystack.toLowerCase().includes(search.toLowerCase());
|
||||
|
||||
export const listKnowledgeForProject = async (projectId: string, filters: KnowledgeListFilters = {}): Promise<KnowledgeItemDto[]> => {
|
||||
const project = await getProject(projectId);
|
||||
const rows = await prisma.knowledgeItem.findMany({ where: { projectId: project.id, deletedAt: null }, include: { project: { select: { slug: true } } }, orderBy: { updatedAt: "desc" }});
|
||||
return rows.map(toKnowledgeDto).filter((item) =>
|
||||
(!filters.category?.length || filters.category.includes(item.category)) &&
|
||||
(!filters.source?.length || filters.source.includes(item.source)) &&
|
||||
(filters.confidence === undefined || item.confidence === filters.confidence) &&
|
||||
(!filters.featureId || item.linkedFeatureIds.includes(filters.featureId)) &&
|
||||
filterSearch(`${item.title} ${item.content} ${item.category}`, filters.search)
|
||||
);
|
||||
};
|
||||
|
||||
export const getKnowledgeBySlugOrId = async (projectId: string, knowledgeId: string): Promise<KnowledgeItemDto | undefined> => {
|
||||
const project = await getProject(projectId);
|
||||
const row = await prisma.knowledgeItem.findFirst({ where: { projectId: project.id, OR: [{ id: knowledgeId }, { slug: knowledgeId }, { readableId: knowledgeId }], deletedAt: null }, include: { project: { select: { slug: true } } }});
|
||||
return row ? toKnowledgeDto(row) : undefined;
|
||||
};
|
||||
|
||||
export const createKnowledgeItem = async (projectId: string, body: KnowledgeMutationRequestDto & { title: string; content: string }): Promise<KnowledgeItemDto> => {
|
||||
const project = await getProject(projectId);
|
||||
const now = new Date();
|
||||
const confidence = body.confidence ?? 75;
|
||||
const slug = `kn-${project.slug}-${Date.now().toString(36)}`;
|
||||
const row = await prisma.knowledgeItem.create({ data: { projectId: project.id, slug, readableId: slug, title: body.title.trim(), category: body.category?.trim() || "Technical", content: body.content.trim(), source: body.source?.trim() || "manual", confidence, includeInContextPacks: false, approvalState: ApprovalState.pending, evidenceStrength: EvidenceStrength.medium, verificationState: VerificationState.unverified, metadataJson: toJson({ linkedFeatureIds: body.linkedFeatureIds ?? [], includeInContextPacks: [] }), trustJson: toJson(trust("database knowledge create", confidence, ApprovalState.pending, now)), createdBy: "api", updatedBy: "api", sourceType: "database_api" }, include: { project: { select: { slug: true } } }});
|
||||
return toKnowledgeDto(row);
|
||||
};
|
||||
|
||||
export const updateKnowledgeItem = async (projectId: string, knowledgeId: string, body: KnowledgeMutationRequestDto): Promise<KnowledgeItemDto | undefined> => {
|
||||
const project = await getProject(projectId);
|
||||
const current = await prisma.knowledgeItem.findFirst({ where: { projectId: project.id, OR: [{ id: knowledgeId }, { slug: knowledgeId }, { readableId: knowledgeId }], deletedAt: null }});
|
||||
if (!current) return undefined;
|
||||
const meta = metadata(current.metadataJson);
|
||||
const confidence = body.confidence ?? current.confidence;
|
||||
const row = await prisma.knowledgeItem.update({ where: { id: current.id }, data: { ...(body.title !== undefined ? { title: body.title.trim() } : {}), ...(body.category !== undefined ? { category: body.category.trim() } : {}), ...(body.content !== undefined ? { content: body.content.trim() } : {}), ...(body.source !== undefined ? { source: body.source.trim() } : {}), ...(body.confidence !== undefined ? { confidence } : {}), ...(body.linkedFeatureIds !== undefined ? { metadataJson: toJson({ ...meta, linkedFeatureIds: body.linkedFeatureIds }) } : {}), updatedBy: "api", trustJson: toJson(trust("database knowledge update", confidence, current.approvalState, new Date())) }, include: { project: { select: { slug: true } } }});
|
||||
return toKnowledgeDto(row);
|
||||
};
|
||||
|
||||
export const includeKnowledgeInContextPack = async (projectId: string, knowledgeId: string, contextPackId: string): Promise<KnowledgeItemDto | undefined> => {
|
||||
const project = await getProject(projectId);
|
||||
const contextPack = await prisma.contextPack.findFirst({ where: { projectId: project.id, OR: [{ id: contextPackId }, { slug: contextPackId }, { readableId: contextPackId }], deletedAt: null }});
|
||||
if (!contextPack) throw new Error("CONTEXT_PACK_NOT_FOUND");
|
||||
const current = await prisma.knowledgeItem.findFirst({ where: { projectId: project.id, OR: [{ id: knowledgeId }, { slug: knowledgeId }, { readableId: knowledgeId }], deletedAt: null }});
|
||||
if (!current) return undefined;
|
||||
const meta = metadata(current.metadataJson);
|
||||
const list = new Set(toStringArray(meta.includeInContextPacks));
|
||||
list.add(contextPack.slug ?? contextPack.readableId ?? contextPack.id);
|
||||
const row = await prisma.knowledgeItem.update({ where: { id: current.id }, data: { includeInContextPacks: true, metadataJson: toJson({ ...meta, includeInContextPacks: [...list] }), updatedBy: "api" }, include: { project: { select: { slug: true } } }});
|
||||
return toKnowledgeDto(row);
|
||||
};
|
||||
|
||||
export const listMemoryRulesForProject = async (projectId: string, filters: MemoryRuleListFilters = {}): Promise<MemoryRuleDto[]> => {
|
||||
const project = await getProject(projectId);
|
||||
const rows = await prisma.memoryRule.findMany({ where: { projectId: project.id, deletedAt: null }, include: { project: { select: { slug: true } } }, orderBy: { updatedAt: "desc" }});
|
||||
return rows.map(toMemoryRuleDto).filter((item) =>
|
||||
(!filters.scope?.length || filters.scope.includes(item.scope)) &&
|
||||
(!filters.category?.length || filters.category.includes(item.category)) &&
|
||||
(!filters.severity?.length || filters.severity.includes(item.severity)) &&
|
||||
(filters.includeInContextPacks === undefined || item.includeInContextPacksDefault === filters.includeInContextPacks) &&
|
||||
(!filters.featureId || item.linkedFeatureIds.includes(filters.featureId)) &&
|
||||
filterSearch(`${item.title} ${item.rule} ${item.category}`, filters.search)
|
||||
);
|
||||
};
|
||||
|
||||
export const getMemoryRuleBySlugOrId = async (projectId: string, ruleId: string): Promise<MemoryRuleDto | undefined> => {
|
||||
const project = await getProject(projectId);
|
||||
const row = await prisma.memoryRule.findFirst({ where: { projectId: project.id, OR: [{ id: ruleId }, { slug: ruleId }, { readableId: ruleId }], deletedAt: null }, include: { project: { select: { slug: true } } }});
|
||||
return row ? toMemoryRuleDto(row) : undefined;
|
||||
};
|
||||
|
||||
export const createMemoryRuleItem = async (projectId: string, body: MemoryRuleMutationRequestDto & { rule: string }): Promise<MemoryRuleDto> => {
|
||||
const project = await getProject(projectId);
|
||||
const slug = `R-${project.slug.toUpperCase()}-${Date.now().toString(36)}`;
|
||||
const confidence = 78;
|
||||
const now = new Date();
|
||||
const row = await prisma.memoryRule.create({ data: { projectId: project.id, slug, readableId: slug, title: body.title?.trim() || body.rule.trim().slice(0, 60), rule: body.rule.trim(), scope: body.scope ?? "global", category: body.category ?? "Technical", severity: body.severity ?? "important", includeInContextPacks: body.includeInContextPacks ?? false, confidence, approvalState: ApprovalState.pending, evidenceStrength: EvidenceStrength.medium, verificationState: VerificationState.unverified, provenanceJson: toJson({ linkedFeatureIds: body.linkedFeatureIds ?? [], includeInContextPacks: [] }), trustJson: toJson(trust("database memory rule create", confidence, ApprovalState.pending, now)), createdBy: "api", updatedBy: "api", sourceType: "database_api" }, include: { project: { select: { slug: true } } }});
|
||||
return toMemoryRuleDto(row);
|
||||
};
|
||||
|
||||
export const updateMemoryRuleItem = async (projectId: string, ruleId: string, body: MemoryRuleMutationRequestDto): Promise<MemoryRuleDto | undefined> => {
|
||||
const project = await getProject(projectId);
|
||||
const current = await prisma.memoryRule.findFirst({ where: { projectId: project.id, OR: [{ id: ruleId }, { slug: ruleId }, { readableId: ruleId }], deletedAt: null }});
|
||||
if (!current) return undefined;
|
||||
const meta = metadata(current.provenanceJson);
|
||||
const row = await prisma.memoryRule.update({ where: { id: current.id }, data: { ...(body.title !== undefined ? { title: body.title.trim() } : {}), ...(body.rule !== undefined ? { rule: body.rule.trim() } : {}), ...(body.scope !== undefined ? { scope: body.scope } : {}), ...(body.category !== undefined ? { category: body.category } : {}), ...(body.severity !== undefined ? { severity: body.severity } : {}), ...(body.includeInContextPacks !== undefined ? { includeInContextPacks: body.includeInContextPacks } : {}), ...(body.linkedFeatureIds !== undefined ? { provenanceJson: toJson({ ...meta, linkedFeatureIds: body.linkedFeatureIds }) } : {}), updatedBy: "api", trustJson: toJson(trust("database memory rule update", current.confidence, current.approvalState, new Date())) }, include: { project: { select: { slug: true } } }});
|
||||
return toMemoryRuleDto(row);
|
||||
};
|
||||
|
||||
export const includeMemoryRuleInContextPack = async (projectId: string, ruleId: string, contextPackId: string): Promise<MemoryRuleDto | undefined> => {
|
||||
const project = await getProject(projectId);
|
||||
const contextPack = await prisma.contextPack.findFirst({ where: { projectId: project.id, OR: [{ id: contextPackId }, { slug: contextPackId }, { readableId: contextPackId }], deletedAt: null }});
|
||||
if (!contextPack) throw new Error("CONTEXT_PACK_NOT_FOUND");
|
||||
const current = await prisma.memoryRule.findFirst({ where: { projectId: project.id, OR: [{ id: ruleId }, { slug: ruleId }, { readableId: ruleId }], deletedAt: null }});
|
||||
if (!current) return undefined;
|
||||
const meta = metadata(current.provenanceJson);
|
||||
const list = new Set(toStringArray(meta.includeInContextPacks));
|
||||
list.add(contextPack.slug ?? contextPack.readableId ?? contextPack.id);
|
||||
const row = await prisma.memoryRule.update({ where: { id: current.id }, data: { includeInContextPacks: true, provenanceJson: toJson({ ...meta, includeInContextPacks: [...list] }), updatedBy: "api" }, include: { project: { select: { slug: true } } }});
|
||||
return toMemoryRuleDto(row);
|
||||
};
|
||||
|
||||
export const listDocumentationForProject = async (projectId: string, filters: DocumentationListFilters = {}): Promise<DocumentationItemDto[]> => {
|
||||
const project = await getProject(projectId);
|
||||
const rows = await prisma.documentationItem.findMany({ where: { projectId: project.id, deletedAt: null }, include: { project: { select: { slug: true } } }, orderBy: { updatedAt: "desc" }});
|
||||
return rows.map(toDocumentationDto).filter((item) =>
|
||||
(!filters.kind?.length || filters.kind.includes(item.kind)) &&
|
||||
(!filters.status?.length || filters.status.includes(item.status)) &&
|
||||
(!filters.approvalState?.length || filters.approvalState.includes(item.approvalState)) &&
|
||||
filterSearch(`${item.title} ${item.kind} ${item.content ?? ""}`, filters.search)
|
||||
);
|
||||
};
|
||||
|
||||
export const getDocumentationBySlugOrId = async (projectId: string, docId: string): Promise<DocumentationItemDto | undefined> => {
|
||||
const project = await getProject(projectId);
|
||||
const row = await prisma.documentationItem.findFirst({ where: { projectId: project.id, OR: [{ id: docId }, { slug: docId }, { readableId: docId }], deletedAt: null }, include: { project: { select: { slug: true } } }});
|
||||
return row ? toDocumentationDto(row) : undefined;
|
||||
};
|
||||
|
||||
export const createDocumentationItem = async (projectId: string, body: { title: string; kind?: string; status?: string; linkedFeatureIds?: string[]; content?: string; approvalState?: ApprovalStateInput; primaryContextPackId?: string; selectedContextPackIds?: string[]; contextSelectionSummary?: string }): Promise<DocumentationItemDto> => {
|
||||
const project = await getProject(projectId);
|
||||
const slug = `doc-${project.slug}-${Date.now().toString(36)}`;
|
||||
const now = new Date();
|
||||
const row = await prisma.documentationItem.create({ data: { projectId: project.id, slug, readableId: slug, title: body.title.trim(), kind: body.kind?.trim() || "current state report", status: body.status?.trim() || "draft", approvalState: body.approvalState ?? ApprovalState.pending, content: body.content?.trim(), metadataJson: toJson({ linkedFeatureIds: body.linkedFeatureIds ?? [], lastUpdated: now.toISOString(), primaryContextPackId: body.primaryContextPackId, selectedContextPackIds: body.selectedContextPackIds ?? [], contextSelectionSummary: body.contextSelectionSummary }), trustJson: toJson(trust("database documentation create", 75, body.approvalState ?? ApprovalState.pending, now)), provenanceJson: toJson({ source: "database_api" }), createdBy: "api", updatedBy: "api", sourceType: "database_api" }, include: { project: { select: { slug: true } } }});
|
||||
return toDocumentationDto(row);
|
||||
};
|
||||
|
||||
export const updateDocumentationItem = async (projectId: string, docId: string, body: { title?: string; kind?: string; status?: string; linkedFeatureIds?: string[]; content?: string; approvalState?: ApprovalStateInput; primaryContextPackId?: string; selectedContextPackIds?: string[]; contextSelectionSummary?: string }): Promise<DocumentationItemDto | undefined> => {
|
||||
const project = await getProject(projectId);
|
||||
const current = await prisma.documentationItem.findFirst({ where: { projectId: project.id, OR: [{ id: docId }, { slug: docId }, { readableId: docId }], deletedAt: null }});
|
||||
if (!current) return undefined;
|
||||
const meta = metadata(current.metadataJson);
|
||||
const row = await prisma.documentationItem.update({ where: { id: current.id }, data: { ...(body.title !== undefined ? { title: body.title.trim() } : {}), ...(body.kind !== undefined ? { kind: body.kind.trim() } : {}), ...(body.status !== undefined ? { status: body.status.trim() } : {}), ...(body.approvalState !== undefined ? { approvalState: body.approvalState } : {}), ...(body.content !== undefined ? { content: body.content.trim() } : {}), metadataJson: toJson({
|
||||
...meta,
|
||||
...(body.linkedFeatureIds !== undefined ? { linkedFeatureIds: body.linkedFeatureIds } : {}),
|
||||
...(body.primaryContextPackId !== undefined ? { primaryContextPackId: body.primaryContextPackId } : {}),
|
||||
...(body.selectedContextPackIds !== undefined ? { selectedContextPackIds: body.selectedContextPackIds } : {}),
|
||||
...(body.contextSelectionSummary !== undefined ? { contextSelectionSummary: body.contextSelectionSummary } : {}),
|
||||
lastUpdated: new Date().toISOString(),
|
||||
}), updatedBy: "api" }, include: { project: { select: { slug: true } } }});
|
||||
return toDocumentationDto(row);
|
||||
};
|
||||
Reference in new issue
Block a user