// SPEC: PJ2026-010404 项目管理 draft-2026-06-25-p0-project-management-mdtodo. // Responsibility: Cloud Web client for the public project-management same-origin API. import { fetchJson } from "./client"; import type { ApiResult } from "@/types"; export interface ProjectManagementEnvelope { contractVersion?: string; serviceId?: string; ok?: boolean; valuesRedacted?: boolean; } export interface ProjectNavigationItem { id: string; label: string; apiRoute?: string; } export interface ProjectNavigationCapabilities { mdtodoProjection?: boolean; workbenchLaunch?: boolean; workbenchLinks?: boolean; sourceOfTruth?: string; [key: string]: unknown; } export interface ProjectNavigation { id: string; label: string; actor?: { id?: string | null; role?: string | null; authMethod?: string | null } | null; items?: ProjectNavigationItem[]; capabilities?: ProjectNavigationCapabilities; } export interface ProjectProjectionSummary { sourceId?: string; documentCount?: number; taskCount?: number; projectedAt?: string; startedAt?: string; } export interface ProjectNavigationResponse extends ProjectManagementEnvelope { navigation?: ProjectNavigation; projection?: ProjectProjectionSummary | null; } export interface ProjectRecord { projectId: string; name?: string; title?: string; sourceIds?: string[]; sourceOfTruth?: string; status?: string; } export interface ProjectSource { sourceId: string; kind?: string; sourceKind?: string; displayName?: string; projectId?: string; status?: string; hwpodId?: string | null; nodeId?: string | null; workspaceRootRef?: string | null; mdtodoRootRef?: string | null; maxFiles?: number; updatedAt?: string; valuesRedacted?: boolean; } export interface ProjectSourceInput { sourceId?: string; sourceKind?: string; displayName?: string; projectId?: string; hwpodId?: string; nodeId?: string; workspaceRootRef?: string; mdtodoRootRef?: string; maxFiles?: number; } export interface MdtodoFileRecord { sourceId: string; fileRef: string; projectId?: string; relativePath?: string; title?: string; fingerprint?: string; taskCount?: number; parseError?: string | null; revision?: number; lastIndexedAt?: string; updatedAt?: string; } export interface MdtodoTaskRecord { taskRef: string; projectId?: string; sourceId?: string; fileRef?: string; taskId?: string; title?: string; status?: string; parentTaskRef?: string | null; depth?: number; lineNumber?: number; ordinal?: number; linkCount?: number; updatedAt?: string; } export interface ProjectWorkbenchLinkRecord { linkId: string; projectId?: string; taskRef?: string | null; sessionId?: string | null; traceId?: string | null; createdBy?: string | null; role?: string; createdAt?: string; updatedAt?: string; } export interface ProjectListResponse extends ProjectManagementEnvelope { projects?: ProjectRecord[] } export interface ProjectSourceListResponse extends ProjectManagementEnvelope { sources?: ProjectSource[] } export interface ProjectSourceMutationResponse extends ProjectManagementEnvelope { source?: ProjectSource } export interface ProjectSourceProbeResponse extends ProjectManagementEnvelope { probe?: { ok?: boolean; status?: string; sourceId?: string; sourceKind?: string; entries?: number; valuesRedacted?: boolean } } export interface ProjectSourceReindexResponse extends ProjectManagementEnvelope { projection?: ProjectProjectionSummary & { sourceKind?: string; rootRef?: string; valuesRedacted?: boolean } } export interface MdtodoFileListResponse extends ProjectManagementEnvelope { files?: MdtodoFileRecord[] } export interface MdtodoTaskListResponse extends ProjectManagementEnvelope { tasks?: MdtodoTaskRecord[] } export interface ProjectWorkbenchLinkListResponse extends ProjectManagementEnvelope { links?: ProjectWorkbenchLinkRecord[] } export interface MdtodoTaskQuery { sourceId?: string | null; fileRef?: string | null; projectId?: string | null; } export interface ProjectWorkbenchLinkQuery { taskRef?: string | null; projectId?: string | null; sessionId?: string | null; } export const projectManagementAPI = { navigation: (): Promise> => fetchJson("/v1/project-management/navigation", { timeoutMs: 12000, timeoutName: "project navigation" }), projects: (): Promise> => fetchJson("/v1/project-management/projects", { timeoutMs: 12000, timeoutName: "project list" }), sources: (): Promise> => fetchJson("/v1/project-management/mdtodo/sources", { timeoutMs: 12000, timeoutName: "project mdtodo sources" }), createSource: (input: ProjectSourceInput): Promise> => fetchJson("/v1/project-management/mdtodo/sources", { method: "POST", body: JSON.stringify(input), timeoutMs: 12000, timeoutName: "project mdtodo source create" }), updateSource: (sourceId: string, input: ProjectSourceInput): Promise> => fetchJson(`/v1/project-management/mdtodo/sources/${encodeURIComponent(sourceId)}`, { method: "PATCH", body: JSON.stringify(input), timeoutMs: 12000, timeoutName: "project mdtodo source update" }), probeSource: (sourceId: string): Promise> => fetchJson(`/v1/project-management/mdtodo/sources/${encodeURIComponent(sourceId)}/probe`, { method: "POST", body: JSON.stringify({}), timeoutMs: 12000, timeoutName: "project mdtodo source probe" }), reindexSource: (sourceId: string): Promise> => fetchJson(`/v1/project-management/mdtodo/sources/${encodeURIComponent(sourceId)}/reindex`, { method: "POST", body: JSON.stringify({}), timeoutMs: 12000, timeoutName: "project mdtodo source reindex" }), files: (sourceId?: string | null): Promise> => fetchJson(`/v1/project-management/mdtodo/files${queryString({ sourceId })}`, { timeoutMs: 12000, timeoutName: "project mdtodo files" }), tasks: (query: MdtodoTaskQuery = {}): Promise> => fetchJson(`/v1/project-management/mdtodo/tasks${queryString({ sourceId: query.sourceId, fileRef: query.fileRef, projectId: query.projectId })}`, { timeoutMs: 12000, timeoutName: "project mdtodo tasks" }), workbenchLinks: (query: ProjectWorkbenchLinkQuery = {}): Promise> => fetchJson(`/v1/project-management/workbench-links${queryString({ taskRef: query.taskRef, projectId: query.projectId, sessionId: query.sessionId })}`, { timeoutMs: 12000, timeoutName: "project workbench links" }) }; function queryString(values: Record): string { const params = new URLSearchParams(); for (const [key, value] of Object.entries(values)) { if (value) params.set(key, value); } const text = params.toString(); return text ? `?${text}` : ""; }