import type { ChangeItem, PMFlowExecutionContext, PMFlowReviewDecisionAction, PMFlowRiskLevel, ProjectTruthState, } from "@/features/pmflow-os/types"; export type { PMFlowReviewerRouting, PMFlowReviewerRoutingItem, PMFlowReviewerSuggestion } from "@/features/pmflow-os/types"; type ApiEnvelope = { code: number; message: string; data: T }; const API_BASE = "/api"; function unwrap(raw: ApiEnvelope | T): T { if (raw && typeof raw === "object" && "data" in (raw as Record)) { return (raw as ApiEnvelope).data; } return raw as T; } function extractErrorMessage(payload: unknown, fallback: string): string { if (payload && typeof payload === "object") { const record = payload as Record; if (typeof record.message === "string" && record.message.trim()) return record.message.trim(); if (typeof record.detail === "string" && record.detail.trim()) return record.detail.trim(); } return fallback; } function storedAuthToken(): string | null { if (typeof window === "undefined") return null; return window.localStorage.getItem("auth_token") || window.localStorage.getItem("token") || window.localStorage.getItem("access_token"); } async function request(endpoint: string, init: { method: "GET" | "POST"; body?: unknown }): Promise { const token = storedAuthToken(); const response = await fetch(`${API_BASE}${endpoint}`, { method: init.method, headers: { "Content-Type": "application/json", ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: init.body === undefined ? undefined : JSON.stringify(init.body), }); const payload = await response.json().catch(() => null); if (!response.ok) { throw new Error(extractErrorMessage(payload, `HTTP ${response.status}`)); } if (payload && typeof payload === "object" && "code" in payload) { const code = typeof (payload as Record).code === "number" ? (payload as Record).code : 0; if (code !== 0) { throw new Error(extractErrorMessage(payload, `HTTP ${response.status}`)); } } return unwrap(payload as ApiEnvelope | T); } function get(endpoint: string): Promise { return request(endpoint, { method: "GET" }); } function post(endpoint: string, body: unknown = {}): Promise { return request(endpoint, { method: "POST", body }); } export interface PMFlowExecutionSummary { projectId: string; execution: PMFlowExecutionContext; releaseGate: PMFlowReleaseGate; releaseSignals?: PMFlowReleaseSignals; devSync: Record; promptPackage?: PMFlowPromptPackage | null; repoBindings: PMFlowRepoBinding[]; activeDevTasks: PMFlowDevTask[]; recentProgressEvents: PMFlowProgressEvent[]; recentDeliveries: PMFlowRecentDelivery[]; warnings: string[]; } export type PMFlowExecutionRuntimeTarget = "local_bridge" | "codex" | "github_agent" | "manual"; export type PMFlowExecutionProgressSource = "local_bridge" | "codex" | "github_agent" | "manual"; export type PMFlowExecutionProgressEventType = | "branch_created" | "commit" | "pr_opened" | "pr_synced" | "test_passed" | "test_failed" | "checks_passed" | "failed" | "blocked" | "merged" | "accepted" | "manual_progress"; export type PMFlowReleaseGateStatus = "ready" | "blocked" | "needs_confirmation" | "empty"; export type PMFlowReleaseValidationDecision = "enter_release_candidate" | "keep_blocked"; export interface PMFlowReleaseGateCheck { key: string; label: string; status: "passed" | "failed" | "missing"; detail?: string | null; } export interface PMFlowReleaseGateEvidence { id: string; type: string; summary: string; taskTitle?: string | null; repo?: string | null; branchName?: string | null; commitSha?: string | null; prNumber?: number | null; occurredAt?: string | null; } export interface PMFlowReleaseGate { status: PMFlowReleaseGateStatus; title: string; summary: string; blockingReasons: string[]; nextActions: string[]; checks: PMFlowReleaseGateCheck[]; evidence: PMFlowReleaseGateEvidence[]; latestValidationRun?: PMFlowReleaseValidationRun | null; updatedAt?: string | null; } export interface PMFlowReleaseValidationRun { id: string; projectId: string; charterId?: string | null; releaseNodeId?: string | null; status: "ready" | "blocked" | "needs_confirmation" | string; score?: number | null; keyMetrics: Record; riskItems: Array>; rollbackPlan: Record; validationSummary: Record; createdBy?: string | null; createdAt?: string | null; updatedAt?: string | null; } export interface PMFlowReleaseValidationRunRequest { decision: PMFlowReleaseValidationDecision; note?: string | null; } export interface PMFlowReleaseValidationRunResult { releaseValidationRun: PMFlowReleaseValidationRun; executionSummary: PMFlowExecutionSummary; warnings: string[]; } export interface PMFlowReleaseSignals { status: "active" | "empty" | string; relationship: { releaseGateStatus?: string | null; latestValidationRunId?: string | null; latestValidationRunStatus?: string | null; source?: string | null; changeSetCreated?: boolean; }; summary: { feedbackCount?: number; revenueEventCount?: number; iterationRecommendationCount?: number; latestSignalAt?: string | null; }; feedback: PMFlowReleaseFeedbackIngest[]; revenueEvents: PMFlowReleaseRevenueEvent[]; recommendations: PMFlowIterationRecommendation[]; recommendationGroups?: PMFlowIterationRecommendationGroup[]; nextActions: string[]; warnings: string[]; } export interface PMFlowReleaseFeedbackIngest { id: string; projectId: string; charterId?: string | null; sourceType: string; title?: string | null; content: string; metrics: Record; samples: Array>; status: string; createdAt?: string | null; updatedAt?: string | null; } export interface PMFlowReleaseRevenueEvent { id: string; projectId: string; sourceType: string; category: string; currency: string; amountUsd: number; externalRef?: string | null; eventPayload: Record; occurredAt?: string | null; createdAt?: string | null; updatedAt?: string | null; } export interface PMFlowIterationRecommendation { id: string; projectId: string; charterId?: string | null; feedbackIngestId?: string | null; status: string; priority: string; recommendationSummary: Record; proposedCharterPatch: Record; proposedTasks: Array>; changeSetCreated: boolean; changeSetId?: string | null; groupKey?: string | null; duplicateOf?: string | null; mergedCount?: number; impactLevel?: string | null; riskLevel?: string | null; suggestedChangeTypes?: string[]; reviewPriority?: string | null; quality?: Record; createdAt?: string | null; updatedAt?: string | null; } export interface PMFlowIterationRecommendationGroup { groupKey: string; title?: string | null; recommendationIds: string[]; mergedCount: number; impactLevel: string; riskLevel: string; suggestedChangeTypes: string[]; reviewPriority: string; changeSetCreated: boolean; changeSetId?: string | null; rationale?: string | null; } export interface PMFlowReleaseFeedbackIngestRequest { source_type?: string; title?: string | null; content: string; charter_id?: string | null; metrics?: Record; samples?: Array>; } export interface PMFlowReleaseFeedbackIngestResult { ingest: PMFlowReleaseFeedbackIngest; recommendation: PMFlowIterationRecommendation; releaseSignals: PMFlowReleaseSignals; executionSummary: PMFlowExecutionSummary; warnings: string[]; } export interface PMFlowIterationRecommendationChangeSetDraftRequest { note?: string | null; } export interface PMFlowIterationRecommendationGroupChangeSetDraftRequest { recommendation_ids?: string[]; note?: string | null; } export interface PMFlowIterationRecommendationChangeSetDraftResult { changeSetDetail: PMFlowChangeSetDetail; executionSummary: PMFlowExecutionSummary; warnings: string[]; } export interface PMFlowExecutionHandoffRequest { task_id: string; repo_binding_id?: string | null; runtime_target: PMFlowExecutionRuntimeTarget; note?: string | null; } export interface PMFlowExecutionHandoffConsumeRequest { runtime_target?: PMFlowExecutionRuntimeTarget | null; consumer_label?: string | null; note?: string | null; } export interface PMFlowExecutionProgressEventRequest { task_id: string; event_type: PMFlowExecutionProgressEventType; source?: PMFlowExecutionProgressSource; repo_binding_id?: string | null; branch_name?: string | null; commit_sha?: string | null; pr_number?: number | null; test_summary?: string | null; workflow_state?: string | null; summary?: string | null; event_source_id?: string | null; occurred_at?: string | null; } export interface PMFlowPromptPackage { id: string; project_id: string; charter_id: string; task_id?: string | null; status: string; package_kind: string; package_text: string; package_payload: Record; created_at?: string | null; } export interface PMFlowRepoBinding { id: string; project_id: string; provider: string; owner: string; repo_name: string; full_name: string; default_branch: string; external_repo_id?: string | null; installation_id?: string | null; installation_account_login?: string | null; app_state?: string; status: string; task_link_count?: number; recent_event_count?: number; } export interface PMFlowDevTask { id: string; title: string; description?: string | null; status: string; priority?: string | null; estimated_hours?: number | null; project_id: string; charter_id?: string | null; charter_title?: string | null; assignee?: { id: string; name?: string | null; role?: string | null } | null; prompt_package_count?: number; model_binding?: Record | null; code_link?: { id: string; status: string; branch_name?: string | null; pr_number?: number | null; last_commit_sha?: string | null; repo?: PMFlowRepoBinding | null; } | null; } export interface PMFlowProgressEvent { id: string; event_type: string; source: string; summary?: string | null; workflow_state?: string | null; derived_task_status?: string | null; delivery_id?: string | null; event_source_id?: string | null; occurred_at?: string | null; task?: { id: string; title?: string | null; status?: string | null; priority?: string | null }; user?: { id: string; name?: string | null; role?: string | null } | null; repo?: PMFlowRepoBinding | null; branch_name?: string | null; pr_number?: number | null; commit_sha?: string | null; } export interface PMFlowRecentDelivery { id: string; delivery_id: string; event_type: string; project_id?: string | null; repo_binding_id?: string | null; installation_id?: string | null; status: string; failure_reason?: string | null; repository_full_name?: string | null; received_at?: string | null; } export interface PMFlowThreadMessageResponse { id: string; threadId: string; projectId: string; role: string; author: string; content: string; createdAt: string; status: string; source: string; metadata: Record; } export interface PMFlowProposedChangeItem { group: ChangeItem["group"]; object_type: Exclude; object_id?: string | null; title: string; summary: string; diff?: string; payload?: Record; confidence?: number; risk_level?: PMFlowRiskLevel; } export interface PMFlowChangeSetDetail { changeSet: Record; items: Array>; evidence: Array>; approvals: Array>; } export interface PMFlowChangeSetCommitResult { changeSet: Record; committedItems: Array>; skippedItems: Array>; failedItems: Array>; projectTruthState?: ProjectTruthState | null; } export interface PMFlowReviewDecisionRequest { action: PMFlowReviewDecisionAction; reviewer_id?: number; reason?: string | null; item_ids?: string[]; } export interface PMFlowReviewDecisionResult { changeSetDetail: PMFlowChangeSetDetail; truthState: ProjectTruthState; warnings: string[]; } export async function getPMFlowTruthState(projectId: string): Promise { return get(`/pmflow-os/projects/${projectId}/truth-state`); } export async function getPMFlowExecutionSummary(projectId: string): Promise { return get(`/pmflow-os/projects/${projectId}/execution-summary`); } export async function createPMFlowExecutionHandoff( projectId: string, payload: PMFlowExecutionHandoffRequest ): Promise { return post(`/pmflow-os/projects/${projectId}/execution-handoffs`, payload); } export async function consumePMFlowExecutionHandoff( projectId: string, promptPackageId: string, payload: PMFlowExecutionHandoffConsumeRequest = {} ): Promise { return post(`/pmflow-os/projects/${projectId}/execution-handoffs/${promptPackageId}/consume`, payload); } export async function createPMFlowExecutionProgressEvent( projectId: string, payload: PMFlowExecutionProgressEventRequest ): Promise { return post(`/pmflow-os/projects/${projectId}/execution-progress-events`, payload); } export async function createPMFlowReleaseValidationRun( projectId: string, payload: PMFlowReleaseValidationRunRequest ): Promise { return post(`/pmflow-os/projects/${projectId}/release-validation-runs`, payload); } export async function createPMFlowReleaseFeedbackIngest( projectId: string, payload: PMFlowReleaseFeedbackIngestRequest ): Promise { return post(`/pmflow-os/projects/${projectId}/release-feedback-ingests`, payload); } export async function createPMFlowIterationRecommendationChangeSetDraft( projectId: string, recommendationId: string, payload: PMFlowIterationRecommendationChangeSetDraftRequest = {} ): Promise { return post( `/pmflow-os/projects/${projectId}/iteration-recommendations/${recommendationId}/change-set-drafts`, payload ); } export async function createPMFlowIterationRecommendationGroupChangeSetDraft( projectId: string, groupKey: string, payload: PMFlowIterationRecommendationGroupChangeSetDraftRequest = {} ): Promise { return post( `/pmflow-os/projects/${projectId}/iteration-recommendation-groups/${encodeURIComponent(groupKey)}/change-set-drafts`, payload ); } export async function createPMFlowThreadMessage( projectId: string, threadId: string, payload: { content: string; source?: string; attachments?: Array> } ): Promise { return post(`/pmflow-os/projects/${projectId}/threads/${threadId}/messages`, payload); } export async function createPMFlowExtraction( projectId: string, payload: { thread_id: string; message_id: string; scope?: string; summary?: string; headline?: string; routing_meta?: Record; proposed_items: PMFlowProposedChangeItem[]; } ): Promise { return post(`/pmflow-os/projects/${projectId}/extractions`, payload); } export async function getPMFlowChangeSet(projectId: string, changeSetId: string): Promise { return get(`/pmflow-os/projects/${projectId}/change-sets/${changeSetId}`); } export async function acceptPMFlowChangeItem(changeItemId: string): Promise { return post(`/pmflow-os/change-items/${changeItemId}/accept`); } export async function editPMFlowChangeItem( changeItemId: string, payload: { summary?: string; diff?: string; payload_patch?: Record; reason?: string } ): Promise { return post(`/pmflow-os/change-items/${changeItemId}/edit`, payload); } export async function deferPMFlowChangeItem(changeItemId: string, reason?: string): Promise { return post(`/pmflow-os/change-items/${changeItemId}/defer`, reason ? { reason } : {}); } export async function commitPMFlowChangeSet( changeSetId: string, payload: { item_ids?: string[]; mode?: "atomic"; note?: string } = {} ): Promise { return post(`/pmflow-os/change-sets/${changeSetId}/commit`, { mode: "atomic", ...payload }); } export async function createPMFlowReviewDecision( changeSetId: string, payload: PMFlowReviewDecisionRequest ): Promise { return post(`/pmflow-os/change-sets/${changeSetId}/review-decisions`, payload); }