roam-research-mcp 1.6.0 → 2.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,126 +1,78 @@
1
- # Roam Markdown Cheatsheet — Generic Foundation v2.0.0
1
+ # Roam Markdown Cheatsheet v2.1.0
2
2
 
3
- > ⚠️ **MODEL DIRECTIVE**: Always consult this cheatsheet BEFORE making any Roam tool calls. Syntax errors in Roam are unforgiving.
3
+ ## Core Syntax
4
4
 
5
- ---
6
-
7
- ## Quick Reference: Core Syntax
8
-
9
- ### Text Formatting
10
- | Style | Syntax | Example |
11
- |-------|--------|---------|
12
- | Bold | `**text**` | **bold text** |
13
- | Italic | `__text__` | __italic text__ |
14
- | Highlight | `^^text^^` | ^^highlighted^^ |
15
- | Strikethrough | `~~text~~` | ~~struck~~ |
16
- | Inline code | `` `code` `` | `code` |
17
- | LaTeX | `$$E=mc^2$$` | rendered math |
5
+ ### Formatting
6
+ `**bold**` · `__italic__` · `^^highlight^^` · `~~strike~~` · `` `code` `` · `$$LaTeX$$`
18
7
 
19
8
  ### Links & References
20
- | Type | Syntax | Notes |
21
- |------|--------|-------|
22
- | Page reference | `[[Page Name]]` | Creates/links to page |
23
- | Block reference | `((block-uid))` | Embeds block content inline |
24
- | Block embed | `{{[[embed]]: ((block-uid))}}` | Full block embed with children |
25
- | External link | `[text](URL)` | Standard markdown |
26
- | Aliased page ref | `[display text]([[Actual Page]])` | Shows custom text, links to page |
27
- | Aliased block ref | `[display text](<((block-uid))>)` | Links to specific block |
28
- | Image embed | `![alt text](URL)` | Inline image |
29
-
30
- ### Tags & Hashtags
31
- | Type | Syntax | When to Use |
32
- |------|--------|-------------|
33
- | Single word | `#tag` | Simple categorization |
34
- | Multi-word | `#[[multiple words]]` | Phrases, compound concepts |
35
- | Hyphenated | `#self-esteem` | Naturally hyphenated terms |
36
-
37
- ⚠️ **CRITICAL**: Never concatenate multi-word tags. `#knowledgemanagement` ≠ `#[[knowledge management]]`
9
+ - **Page ref:** `[[Page Name]]` creates/links to page
10
+ - **Block ref:** `((block-uid))` — embeds block content inline
11
+ - **Block embed:** `{{[[embed]]: ((block-uid))}}` full block with children
12
+ - **External:** `[text](URL)`
13
+ - **Aliased page:** `[display text]([[Actual Page]])`
14
+ - **Aliased block:** `[display text](<((block-uid))>)` note the angle brackets
15
+ - **Image:** `![alt](URL)`
16
+
17
+ ### Tags
18
+ - Single word: `#tag`
19
+ - Multi-word: `#[[multiple words]]`
20
+ - Hyphenated: `#self-esteem`
21
+
22
+ ⚠️ Never concatenate: `#knowledgemanagement` `#[[knowledge management]]`
23
+ ⚠️ `#` always creates tags write `Step 1` not `#1`
38
24
 
39
25
  ### Dates
40
- - **Always use ordinal format**: `[[January 1st, 2025]]`, `[[December 23rd, 2024]]`
41
- - Ordinals: 1st, 2nd, 3rd, 4th–20th, 21st, 22nd, 23rd, 24th–30th, 31st
26
+ Always ordinal format: `[[January 1st, 2025]]`, `[[December 23rd, 2024]]`
42
27
 
43
- ### Task Management
44
- | Status | Syntax |
45
- |--------|--------|
46
- | Todo | `{{[[TODO]]}} task description` |
47
- | Done | `{{[[DONE]]}} task description` |
28
+ ### Tasks
29
+ - Todo: `{{[[TODO]]}} task`
30
+ - Done: `{{[[DONE]]}} task`
48
31
 
49
- ### Attributes (Properties)
32
+ ### Attributes
50
33
  ```
51
34
  Type:: Book
52
35
  Author:: [[Person Name]]
53
36
  Rating:: 4/5
54
- Source:: https://example.com
55
37
  ```
56
38
 
57
- **Purpose**: Attributes create structured metadata that is **queryable across your entire graph**. The attribute name becomes a page reference, so only use `::` when the attribute is a reusable property that applies to multiple pages or concepts.
58
-
59
- **When to USE attributes:**
60
- | Attribute | Why It's Good |
61
- |-----------|---------------|
62
- | `Type:: Book` | Reusable across all media you consume |
63
- | `Author:: [[Person]]` | Links to author page, queryable |
64
- | `Status:: In Progress` | Standard project states, queryable |
65
- | `Source:: URL` | Consistent sourcing across notes |
66
- | `Date:: [[January 1st, 2025]]` | Enables date-based queries |
67
-
68
- **When NOT to use attributes:**
69
- | ❌ Wrong | ✅ Use Instead | Why |
70
- |----------|----------------|-----|
71
- | `Step 1:: Do this thing` | `**Step 1:** Do this thing` | Step numbers are page-specific, not queryable concepts |
72
- | `Note:: Some observation` | Just write the text, or use `#note` | One-off labels don't need attribute syntax |
73
- | `Summary:: The main point` | `**Summary:** The main point` | Section headers are formatting, not metadata |
74
- | `Definition:: Some text` | `Term:: Definition` | Only use for actual definitions you want to query |
75
- | `Implementation Tier 3 (Societal Restructuring):: Some text` | `** Implementation Tier 3 (Societal Restructuring)**: Some text` | Label is specific to current concept |
39
+ **Use `::` when:** queryable across graph (Type, Author, Status, Source, Date)
40
+ **Use bold instead when:** page-specific labels (Step 1, Summary, Note)
76
41
 
77
- ⚠️ **The Test**: Ask yourself: "Will I ever query for all blocks with this attribute across my graph?" If no, use **bold formatting** (`**Label:**`) instead of `::` syntax.
78
-
79
- NOTE: Never combine bold markdown formatting with `::`. Roam formats attributes in bold by default. ✅ `<attribute>::` ❌ `**<attribute>**::`
80
-
81
- ---
42
+ ⚠️ Test: "Will I query all blocks with this attribute?" If no use `**Label:**` instead
43
+ ⚠️ Never `**Attr**::` — Roam auto-bolds attributes
82
44
 
83
45
  ## Block Structures
84
46
 
85
- ### Bullet Points
86
- - Use `-` (dash) followed by space
87
- - Nested bullets: indent with tab or spaces
47
+ ### Bullets
88
48
  ```
89
- - Parent item
90
- - Child item
91
- - Grandchild item
49
+ - Parent
50
+ - Child
51
+ - Grandchild
92
52
  ```
93
53
 
94
54
  ### Code Blocks
95
55
  ````
96
56
  ```javascript
97
- const example = () => {
98
- return "syntax highlighted";
99
- }
57
+ const x = 1;
100
58
  ```
101
59
  ````
102
60
 
103
61
  ### Queries
104
62
  ```
105
63
  {{[[query]]: {and: [[tag1]] [[tag2]]}}}
106
- {{[[query]]: {or: [[optionA]] [[optionB]]}}}
107
- {{[[query]]: {not: [[exclude-this]]}}}
64
+ {{[[query]]: {or: [[A]] [[B]]}}}
65
+ {{[[query]]: {not: [[exclude]]}}}
108
66
  {{[[query]]: {between: [[January 1st, 2025]] [[January 31st, 2025]]}}}
109
67
  ```
110
68
 
111
69
  ### Calculator
112
- ```
113
- {{[[calc]]: 2 + 2}}
114
- {{[[calc]]: 100 * 0.15}}
115
- ```
116
-
117
- ---
70
+ `{{[[calc]]: 2 + 2}}`
118
71
 
119
72
  ## Complex Structures
120
73
 
121
74
  ### Tables
122
- Tables use nested indentation. Each column header/cell nests ONE LEVEL DEEPER than previous.
123
-
75
+ Each column nests ONE LEVEL DEEPER than previous:
124
76
  ```
125
77
  {{[[table]]}}
126
78
  - Header 1
@@ -129,219 +81,114 @@ Tables use nested indentation. Each column header/cell nests ONE LEVEL DEEPER th
129
81
  - Row 1 Label
130
82
  - Cell 1.1
131
83
  - Cell 1.2
132
- - Cell 1.3
133
84
  - Row 2 Label
134
85
  - Cell 2.1
135
86
  - Cell 2.2
136
- - Cell 2.3
137
87
  ```
88
+ Keep tables ≤5 columns.
138
89
 
139
- **Rules:**
140
- - `{{[[table]]}}` is level 1
141
- - First header/row-label at level 2
142
- - Each subsequent column nests +1 level deeper
143
- - Keep tables ≤5 columns for readability
144
-
145
- ### Kanban Boards
90
+ ### Kanban
146
91
  ```
147
92
  {{[[kanban]]}}
148
- - Column 1 Title
149
- - Card 1 content
150
- - Card 2 content
151
- - Column 2 Title
152
- - Card 3 content
93
+ - Column 1
94
+ - Card 1
95
+ - Card 2
96
+ - Column 2
97
+ - Card 3
153
98
  ```
154
99
 
155
- ### Mermaid Diagrams
100
+ ### Mermaid
156
101
  ```
157
102
  {{[[mermaid]]}}
158
103
  - graph TD
159
104
  - A[Start] --> B{Decision}
160
105
  - B -->|Yes| C[Action]
161
- - B -->|No| D[Alternative]
162
- ```
163
-
164
- ### Hiccup (Custom HTML)
165
- `:hiccup [:iframe {:width "600" :height "400" :src "https://example.com"}]`
166
-
167
- `:hiccup [:div {:style {:color "red"}} "Custom styled content"]`
168
-
169
- ---
170
-
171
- ## Anti-Patterns — DON'T DO THIS
172
-
173
- | ❌ Wrong | ✅ Correct | Why |
174
- |----------|-----------|-----|
175
- | `Step 1:: Do this` | `**Step 1:** Do this` | `::` creates queryable attributes; use bold for page-specific labels |
176
- | `#multiplewords` | `#[[multiple words]]` | Concatenated tags create dead references |
177
- | `[[january 1, 2025]]` | `[[January 1st, 2025]]` | Must use ordinal format with proper capitalization |
178
- | `[text](((block-uid)))` | `[text](<((block-uid))>)` | Block ref links need angle bracket wrapper |
179
- | `{{embed: ((uid))}}` | `{{[[embed]]: ((uid))}}` | Embed requires double brackets around keyword |
180
- | Deeply nested tables (6+ cols) | Max 4-5 columns | Becomes unreadable/unmanageable |
181
- | `- *bullet` | `- bullet` | Use dash, not asterisk for bullets |
182
- | `[[TODO]] task` | `{{[[TODO]]}} task` | TODO needs double curly braces |
183
-
184
- ---
185
-
186
- ## Tool Selection Decision Tree
187
-
188
106
  ```
189
- CREATING CONTENT IN ROAM:
190
-
191
- ┌─ Is this a NEW standalone page with structure?
192
- │ └─ YES → roam_create_page (with content array)
193
-
194
- ├─ Adding content to EXISTING page/block?
195
- │ ├─ Simple outline structure → roam_create_outline
196
- │ │ (provide page_title_uid and/or block_text_uid)
197
- │ │
198
- │ └─ Complex/nested markdown → roam_import_markdown
199
- │ (for deeply nested content, tables, etc.)
200
-
201
- ├─ Need to CREATE, UPDATE, MOVE, or DELETE individual blocks?
202
- │ └─ roam_process_batch_actions
203
- │ (fine-grained control, temporary UIDs for parent refs)
204
-
205
- ├─ Adding a memory/note to remember?
206
- │ └─ roam_remember (auto-tags with MEMORIES_TAG)
207
-
208
- ├─ Adding TODO items to today?
209
- │ └─ roam_add_todo (creates individual TODO blocks)
210
-
211
- └─ SEARCHING/READING:
212
- ├─ Find by tag → roam_search_for_tag
213
- ├─ Find by text → roam_search_by_text
214
- ├─ Find by date range → roam_search_by_date
215
- ├─ Find by status → roam_search_by_status
216
- ├─ Get page content → roam_fetch_page_by_title
217
- ├─ Get block + children → roam_fetch_block_with_children
218
- ├─ Recall memories → roam_recall
219
- └─ Complex queries → roam_datomic_query
220
- ```
221
-
222
- ---
223
-
224
- ## API Efficiency Guidelines (Rate Limit Avoidance)
225
-
226
- The Roam API has rate limits. Follow these guidelines to minimize API calls:
227
-
228
- ### Tool Efficiency Ranking (Best to Worst)
229
- 1. **`roam_process_batch_actions`** - Single API call for multiple operations (MOST EFFICIENT)
230
- 2. **`roam_create_page`** - Batches content with page creation
231
- 3. **`roam_create_outline` / `roam_import_markdown`** - Include verification queries (use for smaller operations)
232
- 4. **Multiple sequential tool calls** - Each call = multiple API requests (AVOID)
233
-
234
- ### Best Practices for Intensive Operations
235
107
 
236
- #### When Updating/Revising a Page:
237
- 1. Fetch the page content ONCE at the start
238
- 2. Plan ALL changes needed (creates, updates, deletes)
239
- 3. Execute ALL changes in a SINGLE `roam_process_batch_actions` call
240
- 4. Do NOT fetch-modify-fetch-modify in a loop
108
+ ### Hiccup
109
+ `:hiccup [:iframe {:width "600" :height "400" :src "URL"}]`
241
110
 
242
- #### When Creating Large Content:
243
- - For 10+ blocks: Use `roam_process_batch_actions` with nested structure
244
- - For simple outlines (<10 blocks): `roam_create_outline` is fine
111
+ ## Anti-Patterns
245
112
 
246
- #### UID Caching:
247
- - Save UIDs from previous operations - don't re-query for them
248
- - Use `page_uid` instead of `page_title` when available (avoids lookup query)
249
- - Use `block_uid` instead of `block_text_uid` when you have it
250
-
251
- #### UID Placeholders for Nested Blocks:
252
- When using `roam_process_batch_actions` to create nested blocks, use **placeholder tokens** instead of generating UIDs yourself. The server generates proper random UIDs and returns a mapping.
253
-
254
- **Syntax:** `{{uid:name}}` where `name` is any identifier you choose.
255
-
256
- **Example:**
113
+ | Wrong | ✅ Correct |
114
+ |----------|-----------|
115
+ | `#multiplewords` | `#[[multiple words]]` |
116
+ | `#1`, `#2` | `Step 1`, `No. 1` |
117
+ | `[[january 1, 2025]]` | `[[January 1st, 2025]]` |
118
+ | `[text](((uid)))` | `[text](<((uid))>)` |
119
+ | `{{embed: ((uid))}}` | `{{[[embed]]: ((uid))}}` |
120
+ | `[[TODO]] task` | `{{[[TODO]]}} task` |
121
+ | `- *bullet` | `- bullet` |
122
+ | `* bullet` | `- bullet` |
123
+ | `**Attr**:: val` | `Attr:: val` |
124
+
125
+ ## Tool Selection
126
+
127
+ ```
128
+ CREATING:
129
+ ├─ New page + structure → roam_create_page
130
+ ├─ Add to existing page/block:
131
+ │ ├─ Simple outline → roam_create_outline
132
+ │ └─ Complex markdown → roam_import_markdown
133
+ ├─ Revise entire page → roam_update_page_markdown
134
+ ├─ Fine-grained CRUD → roam_process_batch_actions
135
+ ├─ Table → roam_create_table
136
+ ├─ Memory → roam_remember
137
+ └─ Todos → roam_add_todo
138
+
139
+ SEARCHING:
140
+ ├─ By tag → roam_search_for_tag
141
+ ├─ By text → roam_search_by_text
142
+ ├─ By date → roam_search_by_date
143
+ ├─ By status → roam_search_by_status
144
+ ├─ Block refs → roam_search_block_refs
145
+ ├─ Modified today → roam_find_pages_modified_today
146
+ ├─ Page content → roam_fetch_page_by_title
147
+ ├─ Block + children → roam_fetch_block_with_children
148
+ ├─ Memories → roam_recall
149
+ └─ Complex → roam_datomic_query
150
+ ```
151
+
152
+ ## API Efficiency
153
+
154
+ **Ranking (best → worst):**
155
+ 1. `roam_update_page_markdown` — single call: fetch + diff + update
156
+ 2. `roam_process_batch_actions` — batch multiple ops
157
+ 3. `roam_create_page` — batches content with creation
158
+ 4. `roam_create_outline` / `roam_import_markdown` — includes verification
159
+ 5. Multiple sequential calls — avoid
160
+
161
+ **Best practices:**
162
+ - Use `roam_update_page_markdown` for revisions (handles everything)
163
+ - For 10+ blocks: `roam_process_batch_actions`
164
+ - Cache UIDs — use `page_uid` over `page_title` when available
165
+ - Never fetch-modify-fetch-modify in loops
166
+
167
+ ### UID Placeholders
168
+ Use `{{uid:name}}` for parent refs in batch actions:
257
169
  ```json
258
170
  [
259
- { "action": "create-block", "uid": "{{uid:parent}}", "string": "Parent Block", "location": { "parent-uid": "pageUid123", "order": 0 } },
260
- { "action": "create-block", "string": "Child Block", "location": { "parent-uid": "{{uid:parent}}", "order": 0 } }
171
+ {"action": "create-block", "uid": "{{uid:parent}}", "string": "Parent", "location": {"parent-uid": "pageUid", "order": 0}},
172
+ {"action": "create-block", "string": "Child", "location": {"parent-uid": "{{uid:parent}}", "order": 0}}
261
173
  ]
262
174
  ```
263
-
264
- **Response includes UID mapping:**
265
- ```json
266
- {
267
- "success": true,
268
- "uid_map": {
269
- "parent": "Xk7mN2pQ9"
270
- }
271
- }
272
- ```
273
-
274
- **Why placeholders?** LLMs are not reliable random generators. The server uses cryptographically secure randomness to generate proper 9-character Roam UIDs.
275
-
276
- ### Example: Efficient Page Revision
277
-
278
- **Instead of:**
279
- ```
280
- 1. roam_fetch_page_by_title → get content
281
- 2. roam_create_outline → add section 1
282
- 3. roam_create_outline → add section 2
283
- 4. roam_import_markdown → add more content
284
- ```
285
-
286
- **Do this:**
287
- ```
288
- 1. roam_fetch_page_by_title → get page UID and content
289
- 2. roam_process_batch_actions → ALL creates/updates in one call
290
- ```
291
-
292
- ---
175
+ Server returns `{"uid_map": {"parent": "Xk7mN2pQ9"}}`.
293
176
 
294
177
  ## Structural Defaults
295
178
 
296
- When unsure how to structure output:
179
+ - **Hierarchy:** 2-4 levels preferred, rarely exceed 5
180
+ - **Blocks:** One idea per block
181
+ - **Page refs vs tags:** `[[Page]]` for expandable concepts, `#tag` for filtering
182
+ - **Embed vs ref:** `((uid))` inline, `{{[[embed]]: ((uid))}}` with children, `[text](<((uid))>)` link only
183
+ - **No empty blocks or `---` dividers** — use hierarchy for visual separation
297
184
 
298
- 1. **Hierarchy depth**: Prefer 2-4 levels; rarely exceed 5
299
- 2. **Block length**: Keep blocks atomic — one idea per block
300
- 3. **Page refs vs hashtags**:
301
- - `[[Page]]` for concepts you'll expand into full pages
302
- - `#tag` for categorization/filtering
303
- 4. **When to embed vs reference**:
304
- - `((uid))` — inline reference (shows content)
305
- - `{{[[embed]]: ((uid))}}` — full block with children
306
- - `[text](<((uid))>)` — clickable link only
307
-
308
- ---
309
- ## Visual Separation — Hierarchy First, Separators Never
185
+ ## Output Conventions
310
186
 
311
- Empty blocks and decorative dividers create clutter. Roam's outliner structure provides all the visual separation you need.
312
-
313
- | Avoid | Instead |
314
- |----------|-----------|
315
- | Blank blocks | Let hierarchy create space — child blocks are visually indented |
316
- | `---` dividers | Use a **heading block** to signal section breaks |
317
- | Decorative lines `───` | Nest content under a parent block as a structural container |
318
-
319
- **Principle**: If you feel the urge to add visual breathing room, you probably need *better structure*, not more blocks. Promote a block to a heading, or reorganize into parent/child relationships.
320
-
321
- **The test**: If you'd delete it during cleanup, don't create it.
322
-
323
- ---
324
-
325
- ## Output Format Conventions
326
-
327
- ### Quotes
328
- ```
329
- <quote text> —[[Author Name]] #quote #[[relevant topic]]
330
- ```
331
-
332
- ### Definitions
333
- ```
334
- Term:: Definition text #definition #[[domain]]
335
- ```
336
-
337
- ### Questions for Future
338
- ```
339
- {{[[TODO]]}} Research: <question> #[[open questions]]
340
- ```
187
+ **Quote:** `<text> —[[Author]] #quote`
188
+ **Definition:** `Term:: definition #definition`
189
+ **Open question:** `{{[[TODO]]}} Research: <question> #[[open questions]]`
341
190
 
342
191
  ---
343
-
344
- *End of Generic Foundation — Personalization section follows during user setup.*
345
192
  # Roam Preferences — Personalization Layer
346
193
 
347
194
  > This section contains YOUR specific conventions, tagging philosophy, and graph-specific rules. Customize to match your workflow.
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Resolver for page/block title lookups before batch execution
3
+ */
4
+ import { q } from '@roam-research/roam-api-sdk';
5
+ import { capitalizeWords } from '../../tools/helpers/text.js';
6
+ import { formatRoamDate } from '../../utils/helpers.js';
7
+ import { pageUidCache } from '../../cache/page-uid-cache.js';
8
+ /**
9
+ * Resolve a page title to its UID, trying multiple case variations
10
+ */
11
+ export async function resolvePageUid(graph, title) {
12
+ // Check cache first
13
+ const cachedUid = pageUidCache.get(title);
14
+ if (cachedUid) {
15
+ return cachedUid;
16
+ }
17
+ // Try different case variations
18
+ const variations = [
19
+ title,
20
+ capitalizeWords(title),
21
+ title.toLowerCase()
22
+ ];
23
+ const orClause = variations.map(v => `[?e :node/title "${v}"]`).join(' ');
24
+ const searchQuery = `[:find ?uid .
25
+ :where [?e :block/uid ?uid]
26
+ (or ${orClause})]`;
27
+ const result = await q(graph, searchQuery, []);
28
+ const uid = (result === null || result === undefined) ? null : String(result);
29
+ // Cache the result
30
+ if (uid) {
31
+ pageUidCache.set(title, uid);
32
+ }
33
+ return uid;
34
+ }
35
+ /**
36
+ * Get today's daily page title in Roam format
37
+ */
38
+ export function getDailyPageTitle() {
39
+ return formatRoamDate(new Date());
40
+ }
41
+ /**
42
+ * Resolve today's daily page UID
43
+ */
44
+ export async function resolveDailyPageUid(graph) {
45
+ const dailyTitle = getDailyPageTitle();
46
+ return resolvePageUid(graph, dailyTitle);
47
+ }
48
+ /**
49
+ * Collect all unique page titles that need resolution from commands
50
+ */
51
+ export function collectPageTitles(commands) {
52
+ const titles = new Set();
53
+ for (const cmd of commands) {
54
+ const params = cmd.params;
55
+ // Commands that can have 'page' param
56
+ if ('page' in params && typeof params.page === 'string') {
57
+ titles.add(params.page);
58
+ }
59
+ // Remember command can have heading that needs parent page resolution
60
+ // But heading lookup is handled separately
61
+ // Todo/remember without explicit page need daily page
62
+ if (cmd.command === 'todo' || cmd.command === 'remember') {
63
+ if (!('page' in params) && !('pageUid' in params) && !('parent' in params)) {
64
+ titles.add(getDailyPageTitle());
65
+ }
66
+ }
67
+ }
68
+ return titles;
69
+ }
70
+ /**
71
+ * Check if any commands need daily page resolution
72
+ */
73
+ export function needsDailyPage(commands) {
74
+ for (const cmd of commands) {
75
+ const params = cmd.params;
76
+ if (cmd.command === 'todo' || cmd.command === 'remember') {
77
+ if (!('page' in params) && !('pageUid' in params) && !('parent' in params)) {
78
+ return true;
79
+ }
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+ /**
85
+ * Resolve all page titles to UIDs
86
+ * Returns a map of title -> uid
87
+ */
88
+ export async function resolveAllPages(graph, titles) {
89
+ const resolved = new Map();
90
+ // Resolve in parallel for efficiency
91
+ const entries = Array.from(titles);
92
+ const results = await Promise.all(entries.map(async (title) => {
93
+ const uid = await resolvePageUid(graph, title);
94
+ return [title, uid];
95
+ }));
96
+ for (const [title, uid] of results) {
97
+ if (uid) {
98
+ resolved.set(title, uid);
99
+ }
100
+ }
101
+ return resolved;
102
+ }
103
+ /**
104
+ * Create initial resolution context
105
+ */
106
+ export function createResolutionContext() {
107
+ return {
108
+ pageUids: new Map(),
109
+ placeholders: new Map(),
110
+ levelStack: [],
111
+ currentParent: null,
112
+ dailyPageUid: null
113
+ };
114
+ }
115
+ /**
116
+ * Resolve a parent reference - could be a UID, placeholder, or page title
117
+ */
118
+ export function resolveParentRef(ref, context) {
119
+ // Check if it's a placeholder reference {{name}}
120
+ const placeholderMatch = ref.match(/^\{\{([^}]+)\}\}$/);
121
+ if (placeholderMatch) {
122
+ const name = placeholderMatch[1];
123
+ return context.placeholders.get(name) || `{{uid:${name}}}`;
124
+ }
125
+ // Check if it's a resolved page title
126
+ if (context.pageUids.has(ref)) {
127
+ return context.pageUids.get(ref);
128
+ }
129
+ // Assume it's a direct UID
130
+ return ref;
131
+ }
132
+ /**
133
+ * Generate a placeholder UID for tracking
134
+ * Returns the placeholder in {{uid:name}} format for batch processing
135
+ */
136
+ export function generatePlaceholder(name) {
137
+ return `{{uid:${name}}}`;
138
+ }