extract-tasks.mjs
83 lines 2.8 KB
Raw
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