diff --git a/SUMMARY.md b/SUMMARY.md index f5f2d06..c1a6f9e 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -10,6 +10,7 @@ - [Documentation Guidelines](documentation.md) - [Meetings](meetings/README.md) - [Guidelines](meetings/guidelines.md) + - [Notes Tool](meetings/notes/index.md) --- diff --git a/meetings/notes/index.md b/meetings/notes/index.md new file mode 100644 index 0000000..c5a6a45 --- /dev/null +++ b/meetings/notes/index.md @@ -0,0 +1,187 @@ +# Process Meeting Notes + +Paste Google Docs Markdown export on the left; cleaned Markdown for GitHub Discussions appears on the right. + + + +
+
+ Name map +

JSON mapping emails and display names to GitHub usernames. Loaded from name-map.json on first visit; edits are saved in your browser.

+ + +
+ +
+
+

Input

+ +
+
+
+

Output

+ +
+ +
+
+
+ + diff --git a/meetings/notes/name-map.json b/meetings/notes/name-map.json new file mode 100644 index 0000000..a7b5dbe --- /dev/null +++ b/meetings/notes/name-map.json @@ -0,0 +1,25 @@ +{ + "people": [ + { + "github": "calebmcquaid", + "names": [ + "Caleb" + ] + }, + { + "github": "cassidyjames", + "emails": ["cassidyjames@roost.tools"], + "names": ["Cassidy James Blaede", "Cassidy"] + }, + { + "github": "juanmrad", + "emails": ["juan@roost.tools"], + "names": ["Juan Mrad", "Juan"] + }, + { + "github": "julietshen", + "emails": ["juliet@roost.tools"], + "names": ["Juliet Shen", "Juliet"] + } + ] +} diff --git a/meetings/notes/process-notes.mjs b/meetings/notes/process-notes.mjs new file mode 100644 index 0000000..cdef4cf --- /dev/null +++ b/meetings/notes/process-notes.mjs @@ -0,0 +1,34 @@ +#!/usr/bin/env node +/** + * Process ROOST meeting notes from Google Docs Markdown export to GitHub Discussions format. + * + * Usage: + * node process-notes.mjs notes.md # from file + * cat notes.md | node process-notes.mjs # from stdin + * + * Transformation logic lives in transform.js. Name mapping lives in name-map.json. + */ +import { readFileSync, existsSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import { loadNameMap, processNotes } from './transform.js'; + +const configPath = join(dirname(fileURLToPath(import.meta.url)), 'name-map.json'); + +let emailToGithub = {}, nameToGithub = {}; +if (existsSync(configPath)) { + ({ emailToGithub, nameToGithub } = loadNameMap(JSON.parse(readFileSync(configPath, 'utf8')))); +} else { + process.stderr.write(`Warning: ${configPath} not found; name mapping disabled\n`); +} + +let text; +if (process.argv[2]) { + text = readFileSync(process.argv[2], 'utf8'); +} else { + const chunks = []; + for await (const chunk of process.stdin) chunks.push(chunk); + text = Buffer.concat(chunks).toString('utf8'); +} + +process.stdout.write(processNotes(text, emailToGithub, nameToGithub)); diff --git a/meetings/notes/transform.js b/meetings/notes/transform.js new file mode 100644 index 0000000..8324b18 --- /dev/null +++ b/meetings/notes/transform.js @@ -0,0 +1,68 @@ +/** + * Pure transformation logic for processing ROOST meeting notes. + * Used by both process-notes.mjs (CLI) and index.html (web app). + */ + +export function loadNameMap(data) { + const emailToGithub = {}, nameToGithub = {}; + for (const person of data.people ?? []) { + for (const email of person.emails ?? []) + emailToGithub[email.toLowerCase()] = person.github; + for (const name of person.names ?? []) + nameToGithub[name.toLowerCase()] = person.github; + } + return { emailToGithub, nameToGithub }; +} + +function escapeRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +export function processNotes(text, emailToGithub, nameToGithub) { + let inActions = false; + const lines = text.split('\n').map(line => { + line = line.replace(/\s+$/, ''); + + if (/^## Actions\b/.test(line)) inActions = true; + else if (/^## /.test(line)) inActions = false; + + // Issue/PR links: [\#NNN](https://github.com/ORG/REPO/issues/NNN) → #NNN + line = line.replace( + /\[\\?#(\d+)\]\(https:\/\/github\.com\/[^)]+\/(?:issues|pull)\/\d+\/?\)/g, + '#$1' + ); + + // [Name](mailto:email) → @github-username + line = line.replace(/\[([^\]]+)\]\(mailto:([^)]+)\)/g, (match, _name, email) => { + const github = emailToGithub[email.toLowerCase()]; + return github ? `@${github}` : match; + }); + + // Unescape Google Docs artifacts: \! \( \) \[ \] + line = line.replace(/\\([!()\[\]])/g, '$1'); + + // * bullets → - bullets (preserving indentation) + line = line.replace(/^(\s*)\* /g, '$1- '); + + // Actions section: ensure all list items are checklists + if (inActions) line = line.replace(/^(\s*)-\s+(?!\[)/, '$1- [ ] '); + + return line; + }); + + let result = lines.join('\n'); + + // Replace bare names → @-mentions, longest names first to avoid partial matches + for (const [name, github] of Object.entries(nameToGithub) + .sort(([a], [b]) => b.length - a.length)) { + result = result.replace( + new RegExp('(?