Add plane-sync.js one-way (Plane -> TODO.md) synchronization for the MITHOME project. Config reads from .mcp.json with .env override. CLI: --list, --dry-run, --yes, --project, --verbose, --help. Interactive discrepancy resolution (status mismatches, orphans, new issues), backup before write, rate limiting. Pure functions covered by node:test unit tests. Includes PLANE-SYNC-GUIDE.md and TODO.md changelog entry.
75 lines
2.3 KiB
JavaScript
75 lines
2.3 KiB
JavaScript
// WHY: single source of truth for group -> section, cancelled is reported not placed
|
|
function groupToSection(group) {
|
|
switch (group) {
|
|
case 'completed':
|
|
return 'completed';
|
|
case 'started':
|
|
return 'inProgress';
|
|
case 'unstarted':
|
|
return 'todo';
|
|
case 'backlog':
|
|
return 'backlog';
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function buildDiscrepancies(planeIssues, todoRows, identifier) {
|
|
const todoById = new Map(todoRows.map(row => [row.id, row]));
|
|
const planeById = new Map();
|
|
const cancelled = [];
|
|
|
|
for (const issue of planeIssues) {
|
|
const id = `${identifier}-${issue.sequence_id}`;
|
|
const group = (issue.state && issue.state.group) || 'backlog';
|
|
const section = groupToSection(group);
|
|
if (!section) {
|
|
cancelled.push({ id, name: issue.name });
|
|
continue;
|
|
}
|
|
planeById.set(id, { id, name: issue.name, section, group });
|
|
}
|
|
|
|
const mismatches = [];
|
|
const newIssues = [];
|
|
const orphans = [];
|
|
|
|
for (const [id, p] of planeById) {
|
|
const t = todoById.get(id);
|
|
if (t && t.section !== p.section) mismatches.push({ id, fromSection: t.section, toSection: p.section, name: p.name });
|
|
if (!t) newIssues.push({ id, section: p.section, name: p.name });
|
|
}
|
|
for (const [id, t] of todoById) {
|
|
if (!planeById.has(id)) orphans.push({ id, section: t.section, name: t.name });
|
|
}
|
|
|
|
return { mismatches, newIssues, orphans, cancelled, planeById, todoById };
|
|
}
|
|
|
|
// WHY: Plane is authoritative, so plane rows always win; decisions only move/add local rows
|
|
function buildFinalSections(buildData, decisions) {
|
|
const final = { completed: [], todo: [], inProgress: [], backlog: [] };
|
|
const { planeById, todoById, mismatches } = buildData;
|
|
|
|
for (const [id, p] of planeById) {
|
|
const t = todoById.get(id);
|
|
const note = t ? t.note : '';
|
|
const isMismatch = mismatches.some(m => m.id === id);
|
|
if (isMismatch && !decisions.moveToPlane.has(id)) {
|
|
final[t.section].push({ id, name: p.name, note });
|
|
} else {
|
|
final[p.section].push({ id, name: p.name, note });
|
|
}
|
|
}
|
|
|
|
for (const orphan of buildData.orphans) {
|
|
if (decisions.keepOrphan.has(orphan.id)) {
|
|
final[orphan.section].push({ id: orphan.id, name: orphan.name, note: '' });
|
|
}
|
|
}
|
|
|
|
return final;
|
|
}
|
|
|
|
module.exports = { groupToSection, buildDiscrepancies, buildFinalSections };
|