/** * DevLoop · changelogWriter * * 跨 PRD/SDD/units 的版本变更流(M-15)。append-only NDJSON。 * - 由 DocStore 在 writePRD/writeSDD/writeUnits 成功后调用 * - 外部 Agent 不直接调 * * 规格:docs/prd/newfunction/_spec/sdd-control.md · C-17 * docs/prd/newfunction/_spec/sdd-model.md · M-15 */ const fs = require('fs'); const path = require('path'); const pathResolver = require('./path-resolver'); function _logPath(pid) { return pathResolver.resolveChangelog(pid); } const VALID_KINDS = ['prd_rev', 'sdd_rev', 'units_rev', 'integration_spec_rev']; function append(pid, entry) { if (!VALID_KINDS.includes(entry.kind)) { throw new Error(`changelogWriter.append: invalid kind "${entry.kind}"`); } if (typeof entry.rev !== 'number') { throw new Error('changelogWriter.append: rev (number) required'); } if (!entry.summary) { throw new Error('changelogWriter.append: summary required'); } const full = { ts: new Date().toISOString(), kind: entry.kind, rev: entry.rev, by: entry.by || 'system', trigger: entry.trigger || 'unknown', summary: entry.summary, diff: entry.diff || { sections_added: [], sections_changed: [], sections_removed: [], units_added: [], units_changed: [], units_removed: [], }, archive_ref: entry.archive_ref || null, }; const line = JSON.stringify(full) + '\n'; const p = _logPath(pid); fs.mkdirSync(path.dirname(p), { recursive: true }); fs.appendFileSync(p, line); return full; } function read(pid, opts = {}) { const p = _logPath(pid); if (!fs.existsSync(p)) return []; const limit = opts.limit || 100; const lines = fs.readFileSync(p, 'utf8').split('\n').filter(Boolean); const entries = []; for (let i = lines.length - 1; i >= 0; i--) { let e; try { e = JSON.parse(lines[i]); } catch { continue; } if (opts.kind && e.kind !== opts.kind) continue; if (opts.since && new Date(e.ts).getTime() < new Date(opts.since).getTime()) break; entries.push(e); if (entries.length >= limit) break; } return entries.reverse(); } /** 取某 kind 最近一条 */ function latest(pid, kind) { const recent = read(pid, { kind, limit: 1 }); return recent[0] || null; } module.exports = { append, read, latest, VALID_KINDS };