@basicmemory/openclaw-basic-memory 0.23.1 → 0.24.0

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.
Files changed (50) hide show
  1. package/README.md +24 -7
  2. package/bm-client.ts +107 -7
  3. package/dist/bm-client.d.ts +33 -1
  4. package/dist/bm-client.js +77 -6
  5. package/dist/index.js +9 -1
  6. package/dist/tools/edit-note.js +8 -1
  7. package/dist/tools/list-memory-projects.js +23 -5
  8. package/dist/tools/list-workspaces.js +3 -1
  9. package/dist/tools/memory-provider.js +2 -1
  10. package/dist/tools/read-note.js +18 -3
  11. package/dist/tools/search-notes.js +3 -1
  12. package/dist/tools/write-note.js +65 -5
  13. package/index.ts +9 -1
  14. package/openclaw.plugin.json +2 -0
  15. package/package.json +1 -1
  16. package/scripts/fetch-skills.ts +87 -36
  17. package/scripts/setup-bm.sh +4 -1
  18. package/skills/manifest.json +31 -16
  19. package/skills/memory-agents/SKILL.md +170 -0
  20. package/skills/memory-capture/SKILL.md +7 -5
  21. package/skills/memory-comark/SKILL.md +204 -0
  22. package/skills/memory-comark/references/components.md +740 -0
  23. package/skills/memory-continue/SKILL.md +11 -1
  24. package/skills/memory-curate/SKILL.md +32 -39
  25. package/skills/memory-defrag/SKILL.md +106 -57
  26. package/skills/memory-ingest/SKILL.md +14 -3
  27. package/skills/memory-lifecycle/SKILL.md +21 -21
  28. package/skills/memory-literary-analysis/SKILL.md +258 -28
  29. package/skills/memory-metadata-search/SKILL.md +29 -4
  30. package/skills/memory-notes/SKILL.md +69 -17
  31. package/skills/memory-okf/SKILL.md +135 -0
  32. package/skills/memory-onboarding/SKILL.md +2 -2
  33. package/skills/memory-onboarding/references/assistant-setup.md +8 -0
  34. package/skills/memory-onboarding/references/conventions.md +3 -3
  35. package/skills/memory-onboarding/references/domain-playbooks.md +1 -1
  36. package/skills/memory-onboarding/references/schema-guide.md +3 -3
  37. package/skills/memory-quest/SKILL.md +129 -0
  38. package/skills/memory-quest/agents/openai.yaml +9 -0
  39. package/skills/memory-reflect/SKILL.md +125 -44
  40. package/skills/memory-research/SKILL.md +9 -15
  41. package/skills/memory-schema/SKILL.md +29 -21
  42. package/skills/memory-tasks/SKILL.md +36 -10
  43. package/tools/edit-note.ts +15 -1
  44. package/tools/list-memory-projects.ts +24 -5
  45. package/tools/list-workspaces.ts +3 -1
  46. package/tools/memory-provider.ts +2 -1
  47. package/tools/read-note.ts +22 -3
  48. package/tools/search-notes.ts +3 -1
  49. package/tools/write-note.ts +94 -4
  50. package/skills/memory-ci-capture/SKILL.md +0 -56
@@ -0,0 +1,740 @@
1
+ # Comark component reference
2
+
3
+ The complete authored vocabulary for `::bm-*` components in Basic Memory notes:
4
+ every component, its props and closed value sets, the data grammars, and live
5
+ query pipelines. Basic Memory Cloud renders these in Notes, the Wiki, and shared
6
+ pages; elsewhere the Markdown stays readable as written.
7
+
8
+ ## The registered vocabulary
9
+
10
+ Thirty-nine components are registered. Nothing outside this list is a component.
11
+
12
+ **Content and layout (23)** — `bm-frame`, `bm-ascii`, `bm-image`, `bm-svg`, `bm-stat`, `bm-kpi`, `bm-table`, `bm-timeline`, `bm-tree`, `bm-spec`, `bm-compare`, `bm-diff`, `bm-sheet`, `bm-check`, `bm-invoice`, `bm-kanban`, `bm-card`, `bm-card-group`, `bm-field`, `bm-field-group`, `bm-meter`, `bm-spark`, `bm-rank`
13
+
14
+ **Charts (16)** — `bm-bars`, `bm-funnel`, `bm-stack`, `bm-waffle`, `bm-plot`, `bm-uptime`, `bm-slope`, `bm-bullet`, `bm-waterfall`, `bm-gantt`, `bm-heatmap`, `bm-matrix`, `bm-cells`, `bm-flow`, `bm-activity`, `bm-calendar`
15
+
16
+ Of these, thirty-one form the deterministic Markdown Graphs vocabulary that strict export can project as inert ASCII. The four media components (`bm-ascii`, `bm-image`, `bm-svg`) plus `bm-kanban` are Basic Memory additions; export treats the media three as media rather than as chart data.
17
+
18
+ ## Choose a component
19
+
20
+ | The content is | Use | Avoid |
21
+ | --- | --- | --- |
22
+ | A framed claim or section | `bm-frame` | A generic card made with HTML |
23
+ | One headline metric | `bm-kpi` | A table with one value |
24
+ | Two to four plain metrics | `bm-stat` | KPI when no trend exists |
25
+ | A compact trend without axes | `bm-spark` | Plot |
26
+ | A series that needs scale | `bm-plot` | Spark |
27
+ | One fill from zero to one | `bm-meter` | Bullet |
28
+ | Actual versus target | `bm-bullet` | Meter |
29
+ | Before and after values | `bm-slope` | Bars |
30
+ | Two small histograms | `bm-bars` | Rank |
31
+ | A short ranked list | `bm-rank` | Bars |
32
+ | Parts of a whole | `bm-stack` | Pie chart |
33
+ | About one hundred filled cells | `bm-waffle` | Stack when cells add no value |
34
+ | A small filled or empty grid | `bm-cells` | Waffle |
35
+ | Drop-off steps | `bm-funnel` | Flow |
36
+ | A short process or relationship path | `bm-flow` | Mermaid for two or three hops |
37
+ | A real graph, state machine, or sequence | A fenced ```mermaid block | `bm-flow` forced into a shape it cannot hold |
38
+ | Events in order | `bm-timeline` | A paragraph of timestamps |
39
+ | Nested files or concepts | `bm-tree` | A flat list |
40
+ | Overlapping work | `bm-gantt` | Timeline |
41
+ | Daily status | `bm-uptime` | Activity |
42
+ | Daily counts over weeks or months | `bm-activity` | Calendar |
43
+ | A month with marked days | `bm-calendar` | Activity |
44
+ | A labeled intensity grid | `bm-heatmap` | Matrix |
45
+ | Exact values on two axes | `bm-matrix` | Heatmap |
46
+ | A running total | `bm-waterfall` | Stack |
47
+ | A small data table | `bm-table` | A hand-built HTML table |
48
+ | A grouped document table | `bm-sheet` | Table |
49
+ | A label/value contract | `bm-spec` | Stat |
50
+ | A feature comparison | `bm-compare` | Matrix |
51
+ | A compact change summary or unified patch | `bm-diff` | A prose bullet list |
52
+ | A task list | `bm-check` | Task state in a prop string |
53
+ | Lanes of work or tasks | `bm-kanban` | Three separate checklists |
54
+ | Invoice parties and line items | `bm-invoice` | Table |
55
+ | A small group of linked resources or summaries | `bm-card-group` with `bm-card` | Raw HTML cards |
56
+ | A typed property contract | `bm-field-group` with `bm-field` | A form or opaque prop string |
57
+ | A local image as a character field | `bm-ascii` | Pasted ASCII art |
58
+ | A local image as a halftone or dither | `bm-image` | A CSS filter on a plain image |
59
+ | A local SVG, optionally with bounded motion | `bm-svg` | Inline `<svg>` markup |
60
+ | A live view over this knowledge base | A query-backed component (see below) | Hand-copied counts that go stale |
61
+
62
+ ## Use the directive form
63
+
64
+ Author components as closed block directives:
65
+
66
+ ```markdown
67
+ ::bm-meter{title="Migration" value="0.67" ticks="14" caption="Users table" tone="positive"}
68
+ ::
69
+ ```
70
+
71
+ Do not put the directive inside a code fence when it should render. Do not treat a code fence as a component when parsing or exporting.
72
+
73
+ ### Authoring errors and repairs
74
+
75
+ Unknown props produce `prop-unknown`, with the component name, source line and
76
+ registered props. For example, `colour="red"` on `bm-bars` is rejected; it is
77
+ never silently discarded. The reader and rich editor show the affected block in
78
+ its own warning frame with the original directive and data. The rest of the note
79
+ continues rendering, and editing nearby prose preserves the malformed source.
80
+ Remove the unknown prop or replace it with a registered one in Markdown source.
81
+ A closed malformed component also supports a reviewed component replacement and
82
+ undo through the agent tools.
83
+
84
+ A block with a body but no closing `::` produces `directive-unclosed` near its
85
+ opener. Its warning preserves the opener and any leading YAML properties while
86
+ following headings, lists and prose stay visible outside the frame. Repair the
87
+ source by adding `::` at the intended boundary; the editor never guesses where
88
+ the block ends. Agent component replacements cannot safely claim the rest of an
89
+ unclosed block, so use source editing for this repair.
90
+
91
+ Existing standalone leaf directives with no body remain valid, though new
92
+ components should use the closed block form above. Nested blocks each need their
93
+ own close. Directive lookalikes inside code fences and escaped literals remain
94
+ literal Markdown.
95
+
96
+ These diagnostics use the same shared parser in browser rendering, WebMCP and
97
+ the Cloud Comark analyzer. Canonical validation and strict Wiki export reject
98
+ malformed source until repaired. Warning frames are recovery behavior, not a
99
+ new authored vocabulary: never write `bm-problem` or other internal transport
100
+ nodes in a note.
101
+
102
+ ### Two attribute forms
103
+
104
+ Inline braces are the compact form. A leading YAML property block, fenced by `---` lines, is the equivalent long form and is required for structured array data.
105
+
106
+ ```markdown
107
+ ::bm-bars
108
+ ---
109
+ title: Throughput
110
+ items:
111
+ - label: Before
112
+ value: 42
113
+ display: 42/s
114
+ - label: After
115
+ value: 96
116
+ display: 96/s
117
+ caption: Before and after the queue rewrite
118
+ ---
119
+ ::
120
+ ```
121
+
122
+ Both forms produce the same canonical node, and the serializer picks between them by attribute count: up to six attributes stay inline, and a denser set becomes a `---` block. Typed array attributes always become a block regardless of the count.
123
+
124
+ Never author a ```yaml attribute fence. Comark's own defaults would emit one, so the profile sets `blockAttributesStyle: 'frontmatter'` and raises `maxInlineAttributes` (see `BASIC_MEMORY_COMARK_RENDER_OPTIONS` in `comark-profile.ts`). A fence carrying attributes would contradict fences staying literal code and would leave one note with two spellings for the same thing.
125
+
126
+ Editing a note in the rich editor rewrites its canonical Markdown: attribute order is normalized, and a structured YAML block comes back as JSON inside the `---` delimiters. Values survive; your formatting does not. Do not hand-tune directive formatting you expect to persist through an editor session.
127
+
128
+ ### Closed value sets
129
+
130
+ | Prop | Components | Values |
131
+ | --- | --- | --- |
132
+ | `variant` | `bm-frame` | `default`, `subtle`, `emphasized` |
133
+ | `density` | frame, table, timeline, tree, diff, sheet, check, invoice, card, card-group, field, field-group | `comfortable`, `compact` |
134
+ | `visual` | `bm-frame` | `dither`, `dither-mesh`, `dither-scan` (omit for an ordinary frame) |
135
+ | `tone` | `bm-stat`, `bm-meter` | `neutral`, `positive`, `warning`, `critical` |
136
+ | `glyphs` | `bm-spark`, `bm-kpi`, `bm-activity` | `bar`, `shade`, `ascii`, `hash` |
137
+ | `palette` | spark, kpi, diff, check, activity, calendar, image | `mono`, `duo`, `multi` (`bm-image` accepts `mono`, `duo`) |
138
+ | `kind` | `bm-diff` | omit for the summary form, `unified` for a patch |
139
+
140
+ Frame visuals are static, decorative, and theme-derived. Do not invent shader code, colors, animation, or interactive behavior in a note.
141
+
142
+ ## Use ordinary Markdown children
143
+
144
+ Use Markdown children for layouts whose content is naturally authored as Markdown.
145
+
146
+ ### Table
147
+
148
+ ```markdown
149
+ ::bm-table{title="Release readiness" caption="Current project state" density="compact"}
150
+ | Area | State | Owner |
151
+ | --- | --- | --- |
152
+ | Wiki | Ready | Product |
153
+ | Graph | Ready | Platform |
154
+ ::
155
+ ```
156
+
157
+ ### Checklist
158
+
159
+ ```markdown
160
+ ::bm-check{title="Conformance" density="compact" palette="duo"}
161
+ - [x] Store the bundle as UTF-8 Markdown files
162
+ - [x] Preserve unknown frontmatter fields
163
+ - [ ] Add citations for external claims
164
+ ::
165
+ ```
166
+
167
+ ### Kanban
168
+
169
+ `bm-kanban` follows mdxcn's GraphBoard. Each heading is a lane and the list under it holds the lane's items. The first heading level in the board names the lanes (`##` or `###`); deeper headings stay inside their lane. Each lane shows its item count, and screen readers hear one summary sentence ("Shipped: 2, Now: 2. 4 items.").
170
+
171
+ - **Bold** marks what is happening now and draws in the accent color.
172
+ - *Italic* marks what comes next and recedes.
173
+ - ` — note` (or ` -- note`) puts a short note under the item.
174
+ - Task items (`- [ ]`, `- [x]`) keep their checkbox, and done tasks recede.
175
+
176
+ Lanes sit side by side and stack on narrow screens. Task checkboxes toggle from the Notes preview and the MCP reader, like any Markdown task; move an item between lanes by editing the source, since drag-and-drop is not implemented. Strict export writes the board as ordinary headings and lists.
177
+
178
+ ```markdown
179
+ ::bm-kanban{title="Roadmap"}
180
+ ### Shipped
181
+ - Callout, Steps, Terminal
182
+
183
+ ### Now
184
+ - **Children for every graph**
185
+ - Board and Score — this drop
186
+
187
+ ### Later
188
+ - *Vue port* — if someone asks twice
189
+ ::
190
+ ```
191
+
192
+ ### Tables: table, sheet, spec, compare, and invoice
193
+
194
+ Table-shaped components share one style, after mdxcn: a muted header over a dotted rule, no column rules, and each column aligned the way the Markdown table says (`---:` right-aligns numbers). A **bold** last row reads as a total, with a rule above it and the accent color.
195
+
196
+ - **`bm-spec`** takes `- Label: value` rows, and still accepts a two-column table. Labels recede and values line up beside them.
197
+ - **`bm-compare`** draws a cell that says only `yes` or `no` as ✓ or –. `accent="Column"` colors the column whose header matches. The first column holds the feature names and does not wrap.
198
+ - **`bm-invoice`** puts each heading before the line items (`### From`, `### Bill to`) in its own column, side by side, then the line-items table at full width. A bold last row is the amount due.
199
+
200
+ ```markdown
201
+ ::bm-spec{title="Type"}
202
+ - Family: Geist Mono
203
+ - Size: 14 / 21
204
+ ::
205
+
206
+ ::bm-compare{title="Plans" accent="Studio"}
207
+ | | Solo | Studio |
208
+ | --- | --- | --- |
209
+ | Registry | yes | yes |
210
+ | Private source | no | yes |
211
+ ::
212
+ ```
213
+
214
+ ### Timeline, tree, rank, sheet, spec, compare, and invoice
215
+
216
+ Author their rows as ordinary Markdown. Do not invent an opaque item string when Markdown already represents the structure.
217
+
218
+ ```markdown
219
+ ::bm-timeline{title="Launch"}
220
+ 1. 2026-09-01 — RFC accepted
221
+ 2. **2026-09-20 — Beta**
222
+ 3. *2026-10-15 — General availability*
223
+ ::
224
+
225
+ ::bm-tree{title="Project"}
226
+ - notes
227
+ - decisions
228
+ - tasks
229
+ - wiki
230
+ ::
231
+
232
+ ::bm-rank{title="Most linked"}
233
+ 1. Pricing model — 14
234
+ 2. Usage emails — 9
235
+ 3. Partner portal — 4
236
+ ::
237
+ ```
238
+
239
+ In a timeline, **bold** marks the current event and *italic* an upcoming one.
240
+
241
+ ### Cards and fields
242
+
243
+ Cards and fields use ordinary Markdown children for their descriptions, links, lists, and examples. The component attributes only describe the ASCII frame and typed label.
244
+
245
+ ```markdown
246
+ ::bm-card-group{title="Resources" density="compact"}
247
+ ::bm-card{title="Component guide" density="compact"}
248
+ Use **canonical Markdown** for the card body.
249
+ ::
250
+ ::
251
+
252
+ ::bm-field-group{title="Frontmatter" density="compact"}
253
+ ::bm-field{name="title" type="string" required="true" density="compact"}
254
+ Human-readable note title.
255
+ ::
256
+ ::
257
+ ```
258
+
259
+ Do not put card copy, field descriptions, links, arbitrary classes, or styles in attributes.
260
+
261
+ ## Media components
262
+
263
+ The three media components take a single ordinary Markdown image child pointing at a project-local file. They never accept a remote URL, inline markup, or a data URI, and their alt text stays the accessible label.
264
+
265
+ ```markdown
266
+ ::bm-ascii{title="Geometric wave" preset="paper" detail="high" contrast="edge" polarity="dark" motion="wave"}
267
+ ![Layered dark blue waves curling around a pale circular moon](/ascii-wave.svg)
268
+ ::
269
+
270
+ ::bm-image{title="Halftone wave" effect="halftone" detail="high" palette="duo" motion="pulse"}
271
+ ![Layered dark blue waves curling around a pale circular moon](/ascii-wave.svg)
272
+ ::
273
+
274
+ ::bm-svg{title="Static baseline" fit="contain" motion="none"}
275
+ ![Layered dark blue waves curling around a pale circular moon](/ascii-wave.svg)
276
+ ::
277
+ ```
278
+
279
+ | Prop | Component | Values |
280
+ | --- | --- | --- |
281
+ | `preset` | `bm-ascii` | `crisp` (default), `paper` |
282
+ | `detail` | `bm-ascii`, `bm-image` | `low`, `medium` (default), `high` |
283
+ | `contrast` | `bm-ascii` | `soft`, `edge` (default) |
284
+ | `polarity` | `bm-ascii` | `dark` (default), `light` |
285
+ | `motion` | `bm-ascii` | `none` (default), `drift`, `pulse`, `wave`, `rain`, `glitch` |
286
+ | `effect` | `bm-image` | `dither`, `halftone` |
287
+ | `palette` | `bm-image` | `mono`, `duo` |
288
+ | `motion` | `bm-image` | `none`, `drift`, `pulse` |
289
+ | `fit` | `bm-svg` | `contain` (default), `cover` |
290
+ | `motion` | `bm-svg` | `none`, `drift`, `pulse`, `float`, `reveal` |
291
+
292
+ `bm-ascii` with `motion="rain"` or `motion="glitch"` generates a full-frame procedural field and needs no image child. All motion becomes static when the viewer requests reduced motion. An SVG that animates itself should use `motion="none"` so the wrapper adds nothing.
293
+
294
+ ## Mermaid diagrams
295
+
296
+ A fenced ```mermaid block renders as a diagram in Notes, Wiki, and the MCP app reader. It is ordinary Markdown, not a component, and needs no directive and no library import.
297
+
298
+ Reach for `bm-flow` first for a two- or three-hop path, because it exports deterministically and matches the surrounding ASCII language. Use Mermaid when the content is a real graph, state machine, sequence, or class relationship that a flow row cannot carry. Never invent Mermaid syntax to fake a chart the vocabulary already covers, and never claim a relationship the source does not support.
299
+
300
+ ## Query-backed live views
301
+
302
+ This is implemented, not proposed. A component with an `operation` attribute becomes a read-only view over the knowledge base that contains it. Prose, query, and presentation stay canonical Markdown; the host supplies current data when the note is viewed.
303
+
304
+ The note selects the presentation, `operation` names a stable read capability, and named attributes supply its inputs. The note cannot supply a URL, credentials, workspace, or project — the host owns identity.
305
+
306
+ ### Write live views as pipelines
307
+
308
+ Prefer this form. A `query` attribute pipes rows into the component, read left to right
309
+ like a Knap filter chain: a source, then optional stages in this order.
310
+
311
+ ```markdown
312
+ ::bm-bars{title="Open decisions by owner" query="notes | where type == decision and status != done | group owner | sort count desc"}
313
+ ::
314
+
315
+ ::bm-rank{title="Points by owner" query="notes | group owner | sum points | sort sum desc | limit 5"}
316
+ ::
317
+
318
+ ::bm-stat{label="Active objectives" query="notes | where type == objective and status == open | count"}
319
+ ::
320
+
321
+ ::bm-table{title="Infra work due soon" query="notes | where tags contains infra and due < 2026-10-15 | fields title, owner, due | sort due"}
322
+ ::
323
+
324
+ ::bm-activity{title="Edits per day" query="activity 30d | daily"}
325
+ ::
326
+ ```
327
+
328
+ | Part | Grammar |
329
+ | --- | --- |
330
+ | Sources | `notes` (exact indexed metadata), `search 'phrase'` or `search 'phrase' hybrid` (ranked), `activity 7d` or `activity 30d` |
331
+ | `where` | `field op value` joined by `and`; ops `==`, `!=`, `<`, `<=`, `>`, `>=`, `contains`. Values are words, numbers, `'quoted phrases'`, or `null` |
332
+ | `group field` | One row per value, with its count (or sum) |
333
+ | `count`, `sum field` | One number over the filtered set, or per group after `group` |
334
+ | `fields a, b, c` | Note rows with these columns (not after an aggregate) |
335
+ | `daily` | Activity only: one exact count per day in the window |
336
+ | `sort field [desc]` | Sort a grouped result by its field, `count`, or `sum` |
337
+ | `limit n` | 1 to 50 rows |
338
+
339
+ Quote phrases with single quotes: the `query` attribute is itself double-quoted, and a
340
+ Comark attribute has no escape for `"`, so `query="search 'checksum conflict'"` is the form
341
+ that works. Double quotes are accepted only where the attribute is single-quoted. Writing
342
+ `\"phrase\"` inside the attribute fails with `query-escaped-quote`. A phrase may contain
343
+ `|`, the other quote, or a backslash-escaped quote of its own kind (`'don\'t'`).
344
+
345
+ Search returns ranked rows (`title`, `permalink`, `score`), bounded rather than exhaustive.
346
+ Add `hybrid` for meaning-based ranking; `where` after a search takes `==` filters on distinct
347
+ fields, and `limit` defaults to 20.
348
+
349
+ Live views refresh when the project's notes change, at most once every 30 seconds per view,
350
+ so a note being edited does not spend the query budget on every autosave.
351
+
352
+ Activity views count content changes (`note.*` and `folder.*` events), not indexing or
353
+ other bookkeeping rows.
354
+
355
+ Semantics worth stating in prose: `!=` keeps every note whose value is not the given one,
356
+ including notes that lack the field or hold text; ordered comparisons (`<`, `<=`, `>`, `>=`),
357
+ sums, and numeric sorts treat plain decimal text as numbers (core stores frontmatter numbers
358
+ as text) and ignore other values; `contains` matches an array member or a substring;
359
+ `search` filters compare with `==` only, and `search` stages accept only `where` and `limit`.
360
+
361
+ Live components: `bm-table`, `bm-stat`, `bm-rank`, `bm-spark`, `bm-kpi`, `bm-timeline` (activity
362
+ without `daily`), `bm-bars`, `bm-funnel`, `bm-stack`, `bm-plot`, and `bm-activity` (with
363
+ `daily`). Charts and ranks need a number per row, so add `group`, `count`, or `sum`, or use
364
+ `search`. Sparks, KPIs, and plots draw a trend, so they need several rows: `group`, `daily`, or
365
+ `search`, and a `limit` of at least 2 (`count` or `sum` alone gives one point).
366
+
367
+ A live chart's Markdown body is its last frozen snapshot. Readers see live rows; when the
368
+ query cannot run, the snapshot shows with the reason. In the editor the body sits behind a
369
+ **snapshot rows** toggle (marked *empty* until filled), and **Freeze snapshot** rewrites it
370
+ from the current rows. Strict export uses the snapshot and refuses a live view that has
371
+ none.
372
+
373
+ In the component's property form the query is a multi-line field. An invalid pipeline
374
+ shows its coded problem and fix there and is not saved, so the chart keeps its last
375
+ working query while you type.
376
+
377
+ Presentation of live results:
378
+
379
+ - Activity timelines read as `Oct 9, 9:41 PM — Title — created`, in the reader's time zone.
380
+ - A table whose titles link to notes leaves out the internal `permalink` column.
381
+ - A `sum` result leaves out a `none` group that sums to zero (notes without the grouped
382
+ field and without the summed field); a counted `none` group stays.
383
+
384
+ An invalid pipeline renders its coded problem and fix inside the component in preview,
385
+ and canonical validation rejects it.
386
+
387
+ ### The v1 catalog (legacy operations)
388
+
389
+ Existing notes use these operations and they keep working. Write new live views as pipelines.
390
+
391
+
392
+ | Operation | Inputs | Components |
393
+ | --- | --- | --- |
394
+ | `bm.search.metadata` | `filter`, `aggregate`, `group-by`, `fields`, `sort`, `limit` | `bm-stat`, `bm-table`, `bm-rank` |
395
+ | `bm.search.fts` | `filter`, `query`, `limit` | `bm-table`, `bm-rank` |
396
+ | `bm.search.hybrid` | `filter`, `query`, `limit` | `bm-table`, `bm-rank` |
397
+ | `bm.activity.recent` | `window`, `metric`, `interval`, `limit` | `bm-table`, `bm-timeline`, `bm-kpi`, `bm-spark` |
398
+
399
+ Any other operation name, or any other component/operation pairing, is rejected as unsupported.
400
+
401
+ ```markdown
402
+ ::bm-table{operation="bm.search.metadata" filter="type=decision;status=open" fields="title,priority,owner,due" sort="due" limit="20" title="Open decisions"}
403
+ ::
404
+
405
+ ::bm-stat{operation="bm.search.metadata" filter="type=objective;status=active" aggregate="count" label="Active objectives"}
406
+ ::
407
+
408
+ ::bm-rank{operation="bm.search.metadata" filter="type=decision" aggregate="count" group-by="owner" title="Open decisions by owner"}
409
+ ::
410
+
411
+ ::bm-table{operation="bm.search.fts" query="checksum conflict" limit="10" title="Exact mentions"}
412
+ ::
413
+
414
+ ::bm-rank{operation="bm.search.hybrid" query="why are edits getting lost?" limit="8" title="Related notes"}
415
+ ::
416
+
417
+ ::bm-spark{operation="bm.activity.recent" metric="notes-updated" window="30d" interval="day" title="Updates over time"}
418
+ ::
419
+ ```
420
+
421
+ ### Validation rules
422
+
423
+ These are enforced at the boundary, so an authored note that breaks one renders an error inside that component rather than silently returning something else.
424
+
425
+ - Besides the operation's own inputs, only `operation`, `version`, `title`, `label`, `caption`, and `density` are accepted. Any other attribute is rejected.
426
+ - `limit` is an integer from 1 to 50.
427
+ - `filter` is at most ten unique `field=value` predicates joined with `;`. Both halves must be non-empty. `null` as a value matches absent or null metadata. Nested scalars use dotted paths. The whole string is capped at 1000 characters; other attributes at 500.
428
+ - `fields` is at most ten comma-separated frontmatter fields matching `[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*`. `sort` is one ascending field with the same shape.
429
+ - `bm.search.metadata`: stats and ranks require `aggregate="count"`; `group-by` is required for `bm-rank` and rejected elsewhere; `fields` and `sort` require a table; a table rejects `aggregate`.
430
+ - `bm.activity.recent`: `window` is `7d` or `30d`, `metric` is `notes-updated`, `interval` is `day`. KPI and spark require `metric="notes-updated"`.
431
+ - `bm.search.fts` and `bm.search.hybrid` require a non-empty `query`.
432
+
433
+ ### Say what the numbers mean
434
+
435
+ Metadata views make exact claims about the filtered set, and their counts are calculated before the row limit applies. FTS and hybrid views are lexical or ranked results over a bounded window — describe them as matches or rankings, never as exhaustive coverage. Similarity is ranking context, not a percentage of all relevant knowledge.
436
+
437
+ ### Versioning
438
+
439
+ The optional `version` attribute versions the selected operation's inputs and result shape; it does not version the global Comark profile. Only `version="1"` is accepted today, and omitting it means contract version 1 permanently rather than whichever version is newest.
440
+
441
+ ### Failure is component-local
442
+
443
+ Treat every live component as an isolated failure boundary. A query error, unsupported operation, timeout, or invalid returned shape renders a concise description inside that component while the rest of the note continues rendering. Never replace the whole note with the failure, and never expose stack traces, credentials, or internal query details in an authored surface.
444
+
445
+ Results are inert typed data and are never reparsed as Markdown. No query result is accepted directly from an SSE payload; SSE only prompts a re-fetch.
446
+
447
+ Strict Wiki export rejects parameterized operations until it can capture parameter-bound snapshots. It never substitutes an operation-only snapshot taken with different filters. Canonical Markdown stays portable either way.
448
+
449
+ ## Write chart data as a Markdown body
450
+
451
+ Prefer this form for every chart. The body is ordinary Markdown, so the note still reads
452
+ as a list or table on GitHub, in Obsidian, or in an agent's raw read, and Basic Memory
453
+ draws the figure. The grammar matches mdxcn's, so the same conventions work in both.
454
+
455
+ | Write | Means |
456
+ | --- | --- |
457
+ | `- label: value` | One row. The label ends at the first `: `. |
458
+ | `**bold**` row or label | The accent: the current, chosen, or total row. |
459
+ | `*italic*` row or label | Recedes: next, rejected, or an aside. |
460
+ | `value — note` | A side note after the row. `--` works too. |
461
+ | `42/s`, `1,200`, `72%` | A number with its unit. The text is shown; the number is plotted. |
462
+ | `a → b → c` | A path. `->` works too. |
463
+ | `ok×40` or `ok*40` | A run of forty. Prefer `×`, because Markdown can read `*` pairs as italics. |
464
+ | A table | Grid data. The header labels the columns and the first column labels the rows. |
465
+
466
+ A chart reads its body only when its data attribute (`data`, `value`, `rows`, `days`, or
467
+ `marks`) is absent. With the attribute present, the body stays prose under the figure.
468
+ Readers see the figure. In the editor the rows sit behind a **source rows** toggle under
469
+ the figure (and in the component's property form), so a change redraws the chart; rows
470
+ the chart cannot read open automatically so the problem can be fixed in place. A body that does not fit renders a coded message with a fix inside
471
+ that component, and profile validation rejects it as `invalid-chart-body`.
472
+
473
+ ```markdown
474
+ ::bm-bars{title="Throughput" caption="Before and after the queue rewrite"}
475
+ - Before: 42/s — cold cache
476
+ - **After: 96/s**
477
+ ::
478
+
479
+ ::bm-funnel{title="Install"}
480
+ - docs: 12,400
481
+ - **copy: 4,100**
482
+ - ship: 860
483
+ ::
484
+
485
+ ::bm-bullet{title="Budgets"}
486
+ - API: 72% / 80
487
+ - Search: 40 / 50 / 100
488
+ ::
489
+
490
+ ::bm-slope{title="Coverage"}
491
+ - auth: 41 → 88
492
+ ::
493
+
494
+ ::bm-waterfall{title="Margin"}
495
+ - Start: 100
496
+ - Refunds: -6
497
+ - **Net: 94**
498
+ ::
499
+
500
+ ::bm-gantt{title="Launch"}
501
+ - RFC: 0% 40% 100%
502
+ - **Build: 25% 80% 60%**
503
+ ::
504
+
505
+ ::bm-uptime{title="API" columns="30"}
506
+ ok×40 degraded ok×20 down×2
507
+ ::
508
+
509
+ ::bm-activity{title="Commits"}
510
+ - 2026-08-03: 0 1 4 2 0×2 3
511
+ ::
512
+
513
+ ::bm-calendar{title="August 2026" year="2026" month="8"}
514
+ - 12: launch
515
+ - **27: today**
516
+ ::
517
+
518
+ ::bm-waffle{title="Tests"}
519
+ 73% — tests green
520
+ ::
521
+
522
+ ::bm-matrix{title="Latency"}
523
+ | | p50 | p95 |
524
+ | --- | --- | --- |
525
+ | Read | 12 | 42 |
526
+ | **Write** | 18 | 57 |
527
+ ::
528
+
529
+ ::bm-flow{title="Request path"}
530
+ - request → **middleware** → handler
531
+ ::
532
+ ```
533
+
534
+ | Chart | Body |
535
+ | --- | --- |
536
+ | `bm-bars`, `bm-funnel`, `bm-stack` | `- label: value` rows; bold marks the funnel stage |
537
+ | `bm-bullet` | `- label: value / target`, or `/ max` as a third number; rows without a max share one scale |
538
+ | `bm-slope` | `- label: before → after` |
539
+ | `bm-waterfall` | `- label: delta`; the first row opens, a bold row or the last row is a total |
540
+ | `bm-gantt` | `- label: start end complete` as fractions or percents |
541
+ | `bm-plot` | `- label: value` rows, or a paragraph of numbers |
542
+ | `bm-uptime` | A paragraph of `ok`, `degraded`, `down` with runs |
543
+ | `bm-activity` | `- YYYY-MM-DD: counts…` for consecutive days, with runs |
544
+ | `bm-calendar` | `- day: label`; the bold day is today. `year` and `month` stay attributes |
545
+ | `bm-waffle` | A paragraph: `73% — caption` |
546
+ | `bm-heatmap`, `bm-matrix`, `bm-cells` | A Markdown table; `bm-cells` accepts `x` for filled |
547
+ | `bm-flow` | One path per list item or paragraph |
548
+
549
+ ### Plain-text figures
550
+
551
+ Every chart has a framed plain-text twin, which strict export writes in place of the
552
+ figure. Do not hand-draw this ASCII in a note; author the component and let the renderer
553
+ draw it.
554
+
555
+ ```text
556
+ +------------------ [ INSTALL ] -------------------+
557
+ | |
558
+ | docs ████████████████████████ 12,400 100% |
559
+ | ▸ copy ████████················ 4,100 33% |
560
+ | ship ██······················ 860 7% |
561
+ | |
562
+ +--------------------------------------------------+
563
+ ```
564
+
565
+ ## Use the bounded data grammar (legacy attributes)
566
+
567
+ Existing notes use this attribute grammar and it keeps working. Write new charts with a
568
+ Markdown body instead.
569
+
570
+
571
+ Chart attributes use a compact string grammar:
572
+
573
+ - `;` separates records.
574
+ - `:` separates a label, numeric fields, and an optional display value.
575
+ - `,` separates series values, labels, matrix columns, or matrix cells.
576
+ - `>` separates nodes in a flow row.
577
+ - `|` separates the fixed fields of a compact diff row.
578
+
579
+ Prefer the YAML property block when a label contains `:`, `;`, or `,`, because the structured form preserves punctuation that the delimited grammar would split.
580
+
581
+ ### Common series
582
+
583
+ ```markdown
584
+ ::bm-bars{title="Throughput" data="Before:42:42/s;After:96:96/s"}
585
+ ::
586
+
587
+ ::bm-stack{title="Bundle" data="js:54:54kb;css:28:28kb;assets:18:18kb"}
588
+ ::
589
+
590
+ ::bm-funnel{title="Install" data="docs:12400:12,400;copy:4100:4,100;ship:860:860" stage="copy"}
591
+ ::
592
+ ```
593
+
594
+ ### Numeric series
595
+
596
+ ```markdown
597
+ ::bm-spark{title="Latency" data="42,55,68,51,76,81,104,92,110" caption="Last nine deploys"}
598
+ ::
599
+
600
+ ::bm-plot{title="P95" data="42,55,68,51,76,81,104" labels="Mon,Sun" rows="6"}
601
+ ::
602
+
603
+ ::bm-kpi{title="Rows migrated" value="1.2M" label="of 1.8M rows" hint="67%" data="120,180,320,440,590,810,1200"}
604
+ ::
605
+ ```
606
+
607
+ ### Specialized rows
608
+
609
+ ```markdown
610
+ ::bm-slope{title="Coverage" data="auth:41:88;billing:72:74;docs:11:40"}
611
+ ::
612
+
613
+ ::bm-bullet{title="Budget" data="API:72:80:100:72%;Web:91:90:100:91%"}
614
+ ::
615
+
616
+ ::bm-waterfall{title="Margin" data="Start:100:start;Revenue:28:in;Costs:-17:out;End:111:end"}
617
+ ::
618
+
619
+ ::bm-gantt{title="Launch" data="RFC:0:0.4:1;Build:0.25:0.8:0.6;Review:0.7:1:0" columns="20"}
620
+ ::
621
+ ```
622
+
623
+ ### Grids and paths
624
+
625
+ ```markdown
626
+ ::bm-heatmap{title="Deploys" columns="M,T,W,T,F" rows="API:1,2,4,3,2;Web:0,1,3,4,2"}
627
+ ::
628
+
629
+ ::bm-matrix{title="Latency" columns="p50,p95,p99" rows="Read:12,42,88;Write:18,57,110"}
630
+ ::
631
+
632
+ ::bm-cells{title="Coverage map" columns="A,B,C,D" rows="Core:1,1,0,1;Cloud:1,0,1,1"}
633
+ ::
634
+
635
+ ::bm-flow{title="Publish path" rows="note>index>wiki;note>graph>relations"}
636
+ ::
637
+ ```
638
+
639
+ ### Status and calendar data
640
+
641
+ ```markdown
642
+ ::bm-uptime{title="API" data="ok,ok,degraded,ok,down,ok" columns="6"}
643
+ ::
644
+
645
+ ::bm-activity{title="Commits" days="2026-08-10:0;2026-08-11:1;2026-08-12:4"}
646
+ ::
647
+
648
+ ::bm-calendar{title="August 2026" year="2026" month="8" marks="12,18,27" today="27" weekStartsOn="1"}
649
+ ::
650
+ ```
651
+
652
+ ### Diff
653
+
654
+ Use the default summary kind for small, non-file changes. Each row is `add`, `remove`, or `keep` followed by a label and value. The optional footer is `label|value`.
655
+
656
+ ```markdown
657
+ ::bm-diff{title="Bundle" rows="keep|vendor|84 kb;add|app|31 kb;remove|sourcemaps|12 kb" footer="shipped|103 kb" palette="duo"}
658
+ ::
659
+ ```
660
+
661
+ Use `kind="unified"` only for a canonical unified patch, placed in a fenced `diff` block.
662
+
663
+ ```markdown
664
+ ::bm-diff{title="Files" kind="unified" footer="shipped|103 kb" palette="duo"}
665
+ ```diff
666
+ --- a/packages/note-renderer
667
+ +++ b/packages/note-renderer
668
+ @@ -1,2 +1,2 @@
669
+ -legacy styles
670
+ +graph harness
671
+ canonical Markdown
672
+ ```
673
+ ::
674
+ ```
675
+
676
+ ## Use the structured data grammar
677
+
678
+ Three components accept typed arrays through the YAML property block. The parser validates them and carries them as lossless JSON through the existing string-valued chart attributes, so punctuation in labels survives.
679
+
680
+ `bm-bars` takes `items` of `{label, value, display?}`:
681
+
682
+ ```markdown
683
+ ::bm-bars
684
+ ---
685
+ title: Throughput
686
+ items:
687
+ - { label: "p50, warm", value: 42, display: 42/s }
688
+ - { label: "p95, cold", value: 96, display: 96/s }
689
+ ---
690
+ ::
691
+ ```
692
+
693
+ `bm-bullet` takes `items` of `{label, value, target, max, display?}`, where `max` is greater than zero:
694
+
695
+ ```markdown
696
+ ::bm-bullet
697
+ ---
698
+ title: Budgets
699
+ items:
700
+ - { label: API, value: 72, target: 80, max: 100, display: 72% }
701
+ - { label: Web, value: 91, target: 90, max: 100, display: 91% }
702
+ ---
703
+ ::
704
+ ```
705
+
706
+ `bm-matrix` takes `columns` as a string array and `rows` as `{label, values}`:
707
+
708
+ ```markdown
709
+ ::bm-matrix
710
+ ---
711
+ title: Latency
712
+ columns: [p50, p95, p99]
713
+ rows:
714
+ - { label: Read, values: [12, 42, 88] }
715
+ - { label: Write, values: [18, 57, 110] }
716
+ caption: Milliseconds by operation
717
+ ---
718
+ ::
719
+ ```
720
+
721
+ Constraints, all enforced without a fallback path:
722
+
723
+ - At most 128 rows, and at most 128 values in a matrix row.
724
+ - Every `label` and `display` is a non-empty string of at most 4096 characters, with no `<` and no control characters.
725
+ - Every numeric field is a finite number within `Number.MAX_SAFE_INTEGER`.
726
+ - A component carries `items` or `data`, never both. Both at once is ambiguous and is rejected by profile validation.
727
+ - Only `label`, `value`, `display` (bars), `label`, `value`, `target`, `max`, `display` (bullet), and `label`, `values` (matrix) are accepted keys. An extra key invalidates the row.
728
+
729
+ An invalid structured value is rejected rather than silently repaired, so validate the shape while authoring instead of relying on recovery.
730
+
731
+ ## Keep deterministic values
732
+
733
+ - Use finite numeric values. Invalid numeric fields in the delimited grammar normalize to safe values; do not rely on that recovery path when authoring.
734
+ - Keep Gantt `start`, `end`, and `complete` values between `0` and `1`.
735
+ - Use `ok`, `degraded`, or `down` for uptime cells.
736
+ - Use ISO `YYYY-MM-DD` dates for activity rows.
737
+ - Use month numbers `1` through `12` and day numbers `1` through `31` for calendar data. `weekStartsOn` is `0` for Sunday or `1` for Monday.
738
+ - Do not author Timer or Countdown. Their wall-clock dependency is incompatible with deterministic Notes, Wiki, and strict export without a frozen `asOf` value.
739
+
740
+ The presentation is inspired by Markdown Graphs, but Basic Memory notes use Comark directives and the shared Vue renderer. Never paste React JSX or an invented ASCII twin into canonical note content.