extract-tasks.mjs
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
1 day ago
| 1 | /** |
| 2 | * Markdown task extraction (Issue #1 Phase C7). |
| 3 | */ |
| 4 | |
| 5 | import { listMarkdownFiles, readNote, normalizeSlug, normalizeTags } from './vault.mjs'; |
| 6 | |
| 7 | const TASK_OPEN = /^(\s*)- \[([ xX])\]\s*(.+)$/gm; |
| 8 | |
| 9 | /** |
| 10 | * Markdown checkbox tasks from a note body (Obsidian / GitHub style `- [ ]` / `- [x]`). |
| 11 | * @param {unknown} body |
| 12 | * @param {{ path?: string, status?: 'open'|'done'|'all' }} [options] |
| 13 | * @returns {{ text: string, path: string, line: number, status: 'open'|'done' }[]} |
| 14 | */ |
| 15 | export function extractCheckboxTasksFromBody(body, options = {}) { |
| 16 | const status = options.status || 'all'; |
| 17 | const pathKey = options.path != null ? String(options.path).replace(/\\/g, '/') : ''; |
| 18 | const text = body != null ? String(body) : ''; |
| 19 | const tasks = []; |
| 20 | TASK_OPEN.lastIndex = 0; |
| 21 | let m; |
| 22 | while ((m = TASK_OPEN.exec(text)) !== null) { |
| 23 | const checked = m[2].toLowerCase() === 'x'; |
| 24 | const isOpen = !checked; |
| 25 | if (status === 'open' && !isOpen) continue; |
| 26 | if (status === 'done' && isOpen) continue; |
| 27 | const taskText = m[3].trim(); |
| 28 | const lineIdx = text.slice(0, m.index).split('\n').length; |
| 29 | tasks.push({ |
| 30 | text: taskText, |
| 31 | path: pathKey, |
| 32 | line: lineIdx, |
| 33 | status: isOpen ? 'open' : 'done', |
| 34 | }); |
| 35 | } |
| 36 | return tasks; |
| 37 | } |
| 38 | |
| 39 | /** |
| 40 | * @param {import('./config.mjs').loadConfig extends () => infer R ? R : never} config |
| 41 | * @param {{ folder?: string, project?: string, tag?: string, since?: string, status?: 'open'|'done'|'all' }} options |
| 42 | */ |
| 43 | export function runExtractTasks(config, options = {}) { |
| 44 | const status = options.status || 'all'; |
| 45 | let paths = listMarkdownFiles(config.vault_path, { ignore: config.ignore }); |
| 46 | |
| 47 | if (options.folder) { |
| 48 | const prefix = options.folder.replace(/\\/g, '/').replace(/\/$/, '') + '/'; |
| 49 | const exact = options.folder.replace(/\\/g, '/').replace(/\/$/, ''); |
| 50 | paths = paths.filter((p) => p === exact || p.startsWith(prefix)); |
| 51 | } |
| 52 | |
| 53 | const wantTag = options.tag != null ? normalizeSlug(String(options.tag)) : null; |
| 54 | const wantProject = options.project != null ? normalizeSlug(String(options.project)) : null; |
| 55 | const since = options.since != null ? String(options.since).trim().slice(0, 10) : null; |
| 56 | |
| 57 | const tasks = []; |
| 58 | |
| 59 | for (const p of paths) { |
| 60 | let note; |
| 61 | try { |
| 62 | note = readNote(config.vault_path, p); |
| 63 | } catch (_) { |
| 64 | continue; |
| 65 | } |
| 66 | if (wantProject && note.project !== wantProject) continue; |
| 67 | if (wantTag) { |
| 68 | const tags = note.tags?.length ? note.tags : normalizeTags(note.frontmatter?.tags); |
| 69 | if (!tags.includes(wantTag)) continue; |
| 70 | } |
| 71 | if (since) { |
| 72 | const d = (note.date || note.updated || '').slice(0, 10); |
| 73 | if (!d || d < since) continue; |
| 74 | } |
| 75 | |
| 76 | const body = note.body || ''; |
| 77 | for (const t of extractCheckboxTasksFromBody(body, { path: note.path.replace(/\\/g, '/'), status })) { |
| 78 | tasks.push(t); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | return { tasks }; |
| 83 | } |
File History
2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠
1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6
docs: accept Calendar Events v0 spec with Phase 0 security …
Human
1 day ago