/** * Workspace 级「PR 文件改动说明 + 续会话 id」持久化。 * * 落盘到 `/.spx/pr-file-summaries.json`,形状 `Record`。 * 用前缀给两类值分命名空间,避免「文件路径」与「会话 key」相撞: * - `sum:${issueNumber}:${path}` → 该文件一句话改动说明(跨 push 保留,不随 head sha 失效)。 * - `ses:${issueNumber}` → 该工单最近一次 deepseek 生成会话的 sessionId,供下次 `--resume`。 * * 文件不存在或解析失败时按空处理。 */ import { promises as fsp } from 'node:fs' import * as path from 'node:path' type SummaryMap = Record function summaryFile(workspaceRoot: string): string { return path.join(workspaceRoot, '.spx', 'pr-file-summaries.json') } function summaryKey(issueNumber: number, filePath: string): string { return `sum:${issueNumber}:${filePath}` } function sessionKey(issueNumber: number): string { return `ses:${issueNumber}` } /** 读出整张映射;文件不存在或解析失败返回空对象。 */ async function readMap(workspaceRoot: string): Promise { let raw: string try { raw = await fsp.readFile(summaryFile(workspaceRoot), 'utf8') } catch { return {} } try { const parsed = JSON.parse(raw) as unknown if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {} const out: SummaryMap = {} for (const [k, v] of Object.entries(parsed as Record)) { if (typeof v === 'string') out[k] = v } return out } catch { return {} } } async function writeMap(workspaceRoot: string, map: SummaryMap): Promise { const dir = path.join(workspaceRoot, '.spx') await fsp.mkdir(dir, { recursive: true }) await fsp.writeFile(summaryFile(workspaceRoot), `${JSON.stringify(map, null, 2)}\n`, 'utf8') } /** 读某工单全部已生成的文件说明:`{ [path]: summary }`。无则空对象。 */ export async function readFileSummaries( workspaceRoot: string, issueNumber: number, ): Promise> { const map = await readMap(workspaceRoot) const prefix = `sum:${issueNumber}:` const out: Record = {} for (const [k, v] of Object.entries(map)) { if (k.startsWith(prefix)) out[k.slice(prefix.length)] = v } return out } /** 写某文件的说明。空串则删除该 key。 */ export async function writeFileSummary( workspaceRoot: string, issueNumber: number, filePath: string, summary: string, ): Promise { const map = await readMap(workspaceRoot) const key = summaryKey(issueNumber, filePath) if (summary.length > 0) map[key] = summary else delete map[key] await writeMap(workspaceRoot, map) } /** 读某工单最近一次生成会话的 sessionId;无则 undefined。 */ export async function readSummarySession( workspaceRoot: string, issueNumber: number, ): Promise { const map = await readMap(workspaceRoot) return map[sessionKey(issueNumber)] || undefined } /** 写某工单续会话 sessionId。传 undefined / 空串则删除(下次从头开会话)。 */ export async function writeSummarySession( workspaceRoot: string, issueNumber: number, sessionId: string | undefined, ): Promise { const map = await readMap(workspaceRoot) const key = sessionKey(issueNumber) if (sessionId && sessionId.length > 0) map[key] = sessionId else delete map[key] await writeMap(workspaceRoot, map) }