feat(plane): add Plane-TODO sync script
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.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { parseTodoFile, buildTable, regenerate, seqNum, SECTIONS } = require('./todo');
|
||||
const { groupToSection, buildDiscrepancies, buildFinalSections } = require('./sync');
|
||||
|
||||
const SAMPLE_TODO = `# mozdIT Weboldal
|
||||
|
||||
## Projekt Áttekintés
|
||||
Next.js 15 alapú weboldal.
|
||||
|
||||
---
|
||||
## ✅ Befejezett
|
||||
|
||||
| Plane | Feladat | Státusz |
|
||||
|-------|---------|---------|
|
||||
| MITHOME-1 | Kezdőlap | ✅ |
|
||||
| MITHOME-15 | Prod app + domain konfiguráció + HTTPS | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 TODO (Soron következő — v1.0.1 roadmap)
|
||||
|
||||
| Plane | Feladat | Státusz | Megjegyzés |
|
||||
|-------|---------|---------|-----------|
|
||||
| MITHOME-13 | CI pipeline | ⏳ | Elsődleges prioritás |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 In Progress
|
||||
|
||||
| Plane | Feladat | Státusz | Megjegyzés |
|
||||
|-------|---------|---------|-----------|
|
||||
| MITHOME-17 | Winston logger + Loki integráció | 🔄 | részben kész |
|
||||
|
||||
---
|
||||
|
||||
## 📋 Backlog (v1.0.2+)
|
||||
|
||||
| Plane | Feladat | Státusz |
|
||||
|-------|---------|---------|
|
||||
| MITHOME-19 | Accessibility | 📋 |
|
||||
|
||||
---
|
||||
|
||||
## Technikai Stack
|
||||
- **Frontend**: Next.js 15
|
||||
`;
|
||||
|
||||
test('parseTodoFile parses sections and rows with notes', () => {
|
||||
const { sections, rows } = parseTodoFile(SAMPLE_TODO);
|
||||
assert.equal(sections.completed.rows.length, 2);
|
||||
assert.equal(sections.completed.header, '## ✅ Befejezett');
|
||||
assert.equal(sections.todo.rows[0].note, 'Elsődleges prioritás');
|
||||
assert.equal(sections.inProgress.rows[0].note, 'részben kész');
|
||||
assert.equal(rows.length, 5);
|
||||
assert.equal(rows[1].section, 'completed');
|
||||
assert.equal(rows[1].id, 'MITHOME-15');
|
||||
});
|
||||
|
||||
test('parseTodoFile ignores table header and separator lines', () => {
|
||||
const { rows } = parseTodoFile(SAMPLE_TODO);
|
||||
assert.ok(!rows.some(r => r.name === 'Feladat'));
|
||||
});
|
||||
|
||||
test('groupToSection maps Plane groups', () => {
|
||||
assert.equal(groupToSection('completed'), 'completed');
|
||||
assert.equal(groupToSection('started'), 'inProgress');
|
||||
assert.equal(groupToSection('unstarted'), 'todo');
|
||||
assert.equal(groupToSection('backlog'), 'backlog');
|
||||
assert.equal(groupToSection('cancelled'), null);
|
||||
});
|
||||
|
||||
test('seqNum extracts trailing number from identifier', () => {
|
||||
assert.equal(seqNum('MITHOME-13'), 13);
|
||||
assert.equal(seqNum('MITHOME-1'), 1);
|
||||
assert.equal(seqNum('MITHOME-27'), 27);
|
||||
assert.equal(seqNum('MITHOME-'), 0);
|
||||
});
|
||||
|
||||
test('buildDiscrepancies detects mismatches, new issues, orphans and cancelled', () => {
|
||||
const planeIssues = [
|
||||
{ sequence_id: 1, name: 'Kezdőlap', state: { group: 'completed' } },
|
||||
{ sequence_id: 15, name: 'Prod app + domain konfiguráció + HTTPS', state: { group: 'backlog' } },
|
||||
{ sequence_id: 18, name: 'Adatvédelmi tájékoztató oldal', state: { group: 'completed' } },
|
||||
{ sequence_id: 30, name: 'Új feladat', state: { group: 'started' } },
|
||||
{ sequence_id: 40, name: 'Törölt feladat', state: { group: 'cancelled' } }
|
||||
];
|
||||
const { rows } = parseTodoFile(SAMPLE_TODO);
|
||||
const result = buildDiscrepancies(planeIssues, rows, 'MITHOME');
|
||||
|
||||
assert.equal(result.mismatches.length, 1);
|
||||
assert.equal(result.mismatches[0].id, 'MITHOME-15');
|
||||
assert.equal(result.mismatches[0].fromSection, 'completed');
|
||||
assert.equal(result.mismatches[0].toSection, 'backlog');
|
||||
|
||||
assert.equal(result.newIssues.length, 2);
|
||||
assert.deepEqual(result.newIssues.map(n => n.id), ['MITHOME-18', 'MITHOME-30']);
|
||||
|
||||
assert.equal(result.orphans.length, 3);
|
||||
assert.deepEqual(result.orphans.map(o => o.id).sort(), ['MITHOME-13', 'MITHOME-17', 'MITHOME-19']);
|
||||
|
||||
assert.deepEqual(result.cancelled, [{ id: 'MITHOME-40', name: 'Törölt feladat' }]);
|
||||
});
|
||||
|
||||
test('buildFinalSections keeps plane rows and applies decisions', () => {
|
||||
const planeIssues = [
|
||||
{ sequence_id: 1, name: 'Kezdőlap', state: { group: 'completed' } },
|
||||
{ sequence_id: 15, name: 'Prod app + domain konfiguráció + HTTPS', state: { group: 'backlog' } },
|
||||
{ sequence_id: 17, name: 'Winston logger + Loki integráció', state: { group: 'started' } },
|
||||
{ sequence_id: 30, name: 'Új feladat', state: { group: 'started' } }
|
||||
];
|
||||
const { rows } = parseTodoFile(SAMPLE_TODO);
|
||||
const buildData = buildDiscrepancies(planeIssues, rows, 'MITHOME');
|
||||
const decisions = {
|
||||
moveToPlane: new Set(['MITHOME-15']),
|
||||
keepOrphan: new Set(['MITHOME-13', 'MITHOME-17', 'MITHOME-19']),
|
||||
addNew: new Set(['MITHOME-30'])
|
||||
};
|
||||
const final = buildFinalSections(buildData, decisions);
|
||||
|
||||
assert.deepEqual(final.completed.map(r => r.id), ['MITHOME-1']);
|
||||
assert.deepEqual(final.backlog.map(r => r.id), ['MITHOME-15', 'MITHOME-19']);
|
||||
assert.deepEqual(final.inProgress.map(r => r.id), ['MITHOME-17', 'MITHOME-30']);
|
||||
assert.equal(final.inProgress.find(r => r.id === 'MITHOME-17').note, 'részben kész');
|
||||
});
|
||||
|
||||
test('regenerate preserves prefix/suffix and rebuilds sections', () => {
|
||||
const planeIssues = [
|
||||
{ sequence_id: 1, name: 'Kezdőlap', state: { group: 'completed' } },
|
||||
{ sequence_id: 15, name: 'Prod app + domain konfiguráció + HTTPS', state: { group: 'backlog' } },
|
||||
{ sequence_id: 13, name: 'CI pipeline', state: { group: 'unstarted' } },
|
||||
{ sequence_id: 17, name: 'Winston logger + Loki integráció', state: { group: 'started' } },
|
||||
{ sequence_id: 19, name: 'Accessibility', state: { group: 'backlog' } }
|
||||
];
|
||||
const { rows } = parseTodoFile(SAMPLE_TODO);
|
||||
const buildData = buildDiscrepancies(planeIssues, rows, 'MITHOME');
|
||||
const decisions = {
|
||||
moveToPlane: new Set(['MITHOME-15']),
|
||||
keepOrphan: new Set(['MITHOME-13', 'MITHOME-17', 'MITHOME-19']),
|
||||
addNew: new Set()
|
||||
};
|
||||
const final = buildFinalSections(buildData, decisions);
|
||||
const output = regenerate(SAMPLE_TODO, final);
|
||||
|
||||
assert.ok(output.includes('# mozdIT Weboldal'));
|
||||
assert.ok(output.includes('Next.js 15 alapú weboldal.'));
|
||||
assert.ok(output.includes('## Technikai Stack'));
|
||||
assert.ok(output.includes('- **Frontend**: Next.js 15'));
|
||||
assert.ok(output.includes('| MITHOME-15 | Prod app + domain konfiguráció + HTTPS | 📋 |'));
|
||||
assert.ok(!output.includes('| MITHOME-15 | Prod app + domain konfiguráció + HTTPS | ✅ |'));
|
||||
assert.ok(output.includes('## ✅ Befejezett'));
|
||||
assert.ok(output.includes('| MITHOME-1 | Kezdőlap | ✅ |'));
|
||||
});
|
||||
|
||||
test('buildTable sorts rows by sequence number', () => {
|
||||
const table = buildTable(SECTIONS[0], [
|
||||
{ id: 'MITHOME-15', name: 'B' },
|
||||
{ id: 'MITHOME-1', name: 'A' }
|
||||
]);
|
||||
const lines = table.split('\n');
|
||||
assert.equal(lines[2], '| MITHOME-1 | A | ✅ |');
|
||||
assert.equal(lines[3], '| MITHOME-15 | B | ✅ |');
|
||||
});
|
||||
Reference in New Issue
Block a user