yamlover 0.3.55 → 0.3.58

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 (48) hide show
  1. package/README.md +48 -5
  2. package/bin/yamlover.js +41 -4
  3. package/dist/agent-docs/AGENTS.md +20 -330
  4. package/dist/agent-docs/AGENTS.yo +621 -0
  5. package/dist/builtin-taxonomy/$defs/.yo/meta.yo +0 -2
  6. package/dist/builtin-taxonomy/$defs/annotation +3 -1
  7. package/dist/builtin-taxonomy/$defs/decision +34 -0
  8. package/dist/builtin-taxonomy/$defs/edit +60 -0
  9. package/dist/builtin-taxonomy/$defs/edit-answer +21 -0
  10. package/dist/builtin-taxonomy/$defs/edit-batch +31 -0
  11. package/dist/builtin-taxonomy/$defs/envelope +169 -0
  12. package/dist/builtin-taxonomy/$defs/event +85 -0
  13. package/dist/builtin-taxonomy/$defs/fragment +7 -9
  14. package/dist/builtin-taxonomy/$defs/link +58 -0
  15. package/dist/builtin-taxonomy/$defs/task +28 -19
  16. package/dist/builtin-taxonomy/$defs/workflow +3 -0
  17. package/dist/builtin-taxonomy/ontos/.yo/body.yo +7 -1
  18. package/dist/client/assets/{decoded-DYF3m3qY.js → decoded-Bj1qJCo_.js} +1 -1
  19. package/dist/client/assets/{djvu--bwAI3Ad.js → djvu-lnurzbzN.js} +1 -1
  20. package/dist/client/assets/{docx-rhrp57OA.js → docx-DpN_PtM8.js} +1 -1
  21. package/dist/client/assets/{heic-CNvQbH_2.js → heic-BqJutS0N.js} +1 -1
  22. package/dist/client/assets/imagemap-rdhcPlWy.js +1 -0
  23. package/dist/client/assets/index-0ALNONIF.js +962 -0
  24. package/dist/client/assets/index-BiETmvLO.css +1 -0
  25. package/dist/client/assets/map-Btm4SlVs.js +1 -0
  26. package/dist/client/assets/paged-Cjy3svw2.js +1 -0
  27. package/dist/client/assets/{openable-CaZBG8d8.js → panzoom-BXEAw479.js} +2 -2
  28. package/dist/client/assets/{pdf-CJhbTDg_.js → pdf-DvC2sbDN.js} +1 -1
  29. package/dist/client/assets/psd-BKmCWsdC.js +11 -0
  30. package/dist/client/assets/{spreadsheet-B71fTduw.js → spreadsheet-Coy5sooG.js} +1 -1
  31. package/dist/client/assets/tiff-IcWiNdYs.js +1 -0
  32. package/dist/client/assets/video-YC-ZvXg7.js +1 -0
  33. package/dist/client/assets/xyflow-CFNcu5cT.js +23 -0
  34. package/dist/client/index.html +2 -2
  35. package/dist/server.js +9228 -5278
  36. package/dist/wire/openapi.yo +935 -0
  37. package/package.json +6 -1
  38. package/dist/agent-docs/CLAUDE.md +0 -7
  39. package/dist/builtin-taxonomy/$defs/board +0 -15
  40. package/dist/client/assets/imagemap-BHOOud3m.js +0 -1
  41. package/dist/client/assets/index-D7Uc99a8.css +0 -1
  42. package/dist/client/assets/index-DDCohDX6.js +0 -759
  43. package/dist/client/assets/map-DRUkC_pT.js +0 -1
  44. package/dist/client/assets/paged-CKzW-eq6.js +0 -1
  45. package/dist/client/assets/psd-CPdDl_Gy.js +0 -11
  46. package/dist/client/assets/tiff-D6Ts7aAc.js +0 -1
  47. package/dist/client/assets/xyflow-DWUupXDf.js +0 -23
  48. /package/dist/client/assets/{openable-CIGW-MKW.css → panzoom-CIGW-MKW.css} +0 -0
package/README.md CHANGED
@@ -168,7 +168,7 @@ take an optional `depth` (container-nesting limit).
168
168
  | `POST / DELETE /api/annotate` | add / remove an annotation on a target |
169
169
  | `POST /api/fragment` | upsert a fragment (region of a target) |
170
170
  | `POST /api/board` | board mutations |
171
- | `POST /api/agent-docs` | install the LLM-agent guide (`AGENTS.md` + `CLAUDE.md`) into the root — a marker-fenced block appended to (or updated in place within) an existing file, never clobbering the human's own rules; idempotent |
171
+ | `POST /api/agent-docs` | install the LLM-agent guide (`AGENTS.yo` — a yamlover chapter covering the language, marklower, chapters, tables, annotations, tasks and boards, the API, with links to the online docs — plus a `AGENTS.md` the stub every agent reads, pointing at it; an existing `CLAUDE.md` is kept in step) into the root — a marker-fenced block, updated in place on reinstall, appended after the human's own rules in a Markdown file, a human's own unfenced `AGENTS.yo` kept untouched; idempotent |
172
172
 
173
173
  #### `POST /api/edit` — the editor
174
174
 
@@ -239,8 +239,8 @@ Registered today (a representative slice — the registry is the source of truth
239
239
  | renderer | matches (format) | draws with |
240
240
  |----------|------------------|------------|
241
241
  | `chapter` | `x-yamlover-chapter` | a positional body: numbered chunks + subchapter links |
242
- | `tag` / `board` | `x-yamlover-onto` / `x-yamlover-board` | tag-hierarchy diagram / board (handled outside the specificity loop) |
243
- | `task` | `x-yamlover-task` | task view |
242
+ | `tag` | `x-yamlover-onto` | tag-hierarchy diagram (handled outside the specificity loop; `tag-board` is a fixed tab on every container) |
243
+ | `task` | `x-yamlover-task` | the planning strip, then the body: prose as a chapter's, the SUBTASKS — own members and linked blockers — as lanes (a board is a task with subtasks) |
244
244
  | `table` | `x-yamlover-table` | a grid (`docs/documents/marklower`): rows/header/caption from the omni entries, merged cells (`colSpan`/`rowSpan`) from resolved relative-index `*` pointers, nested tables inline, marklower cells; prose cells edit in place under the lock |
245
245
  | `marklower` | `text/marklower` (a chapter's prose chunks, by schema propagation) | its own inline grammar (`docs/documents/marklower`): emphasis, `` `code` ``, `$$math$$` via KaTeX, links, and `*[…](…)` media embeds |
246
246
  | `markdown` | `text/markdown` | [marked](https://marked.js.org) |
@@ -293,7 +293,13 @@ Inference splits two ways by how the renderer consumes the file:
293
293
 
294
294
  - Node.js **22.13+** (the engine's store uses the built-in `node:sqlite`, unflagged only from 22.13).
295
295
 
296
- There are **no runtime dependencies**: the client deps are bundled into
296
+ MP4 frame playback and thumbnails require **FFmpeg** (`ffmpeg` and `ffprobe`) on
297
+ the server PATH. HEVC and other non-H.264 MP4 files cache H.264 previews and frame metadata
298
+ under `.yo/videos` for reuse across server restarts; originals stay unchanged. Select the video tab, drag to pan, use modifier-wheel to
299
+ zoom, and use the collapsible time ruler for live scrubbing. Frame steps, five-second
300
+ jumps and playback controls stay in the toolbar; the speaker icon toggles mute.
301
+
302
+ There are **no npm runtime dependencies**: the client deps are bundled into
297
303
  `dist/client` by `vite build` and the server deps into `dist/server.js` by
298
304
  esbuild at `prepack`. The `devDependencies` (React, Vite, the engine/parser, and
299
305
  the renderer libraries) are build- and test-time only. DjVu.js is **vendored**
@@ -315,6 +321,16 @@ runs only the parser and engine suites) and from the editor package's own suite
315
321
  `docs/server/editor/yed`). CI gates on all of them, so run them before pushing. (Run
316
322
  everything from the project root — see the root `package.json`.)
317
323
 
324
+ **Node versions — two numbers for two reasons.** Running the server needs Node
325
+ **≥ 22.13**: the engine's store is `node:sqlite`, and `bin/yamlover.js` checks that
326
+ floor at startup. Running the source-level suites (`node --test …*.test.ts` at the
327
+ root and in the parser, engine and docs2yamlover packages) needs TypeScript
328
+ stripping, which Node does natively only from **22.18** on — every such script passes
329
+ `--experimental-strip-types` (available since 22.6, a no-op once stripping is the
330
+ default), so the declared 22.13 floor runs the tests too. Without the flag, a
331
+ conforming machine below 22.18 fails every file with `ERR_UNKNOWN_FILE_EXTENSION
332
+ ".ts"` before an assertion runs.
333
+
318
334
  ## Layout
319
335
 
320
336
  ```
@@ -328,7 +344,7 @@ src/server/ the engine-backed JSON API
328
344
  tasks.ts background task registry + SSE task frames
329
345
  gitignore.ts .gitignore predicate for surfaced stray files
330
346
  extract/ the thumbnail pipeline: decoder registry (raster/psd/webp/avif) + scaling
331
- agent-docs/ the AGENTS.md / CLAUDE.md guide installed by POST /api/agent-docs
347
+ agent-docs/ the AGENTS.yo guide (+ AGENTS.md stub) installed by POST /api/agent-docs
332
348
  src/client/ the React SPA (tree, node view, render, icons, paths, live SSE)
333
349
  renderers/ facet-predicate renderer registry + per-format renderers; the
334
350
  unlocked editors are yed mounts over ../yed (@yamlover/yed) — docs/server/editor
@@ -336,3 +352,30 @@ src/client/ the React SPA (tree, node view, render, icons, paths, liv
336
352
  test/ Vitest suite (server logic + client components)
337
353
  index.html SPA shell
338
354
  ```
355
+
356
+ ### Gather/decompose in the source editor
357
+
358
+ In the yamlover data view, unlock **Edit**, then choose **Gathered marklower**.
359
+ The panel holds a local text draft and a yamlover preview. **Apply as yamlover**
360
+ decomposes it into the existing tree, using the normal revision-protected save and
361
+ Undo/Redo path. Invalid or cancelled drafts never write. Ctrl+Z inside the textarea
362
+ uses native text undo. An external editor-history change invalidates the draft's base.
363
+
364
+ Gathering refuses bookmarks/comments, gaps, blobs and separately stored members;
365
+ open an inline subtree when a directory is too broad.
366
+
367
+ Gathered marklower can be authored in an ordinary `.yo` scalar, usually a `|` block,
368
+ without a format tag. The engine projects structural continuations into queryable
369
+ chapter nodes while respecting explicit plain/code formats and data islands. Save
370
+ `examples/79-gathered-marklower.yo` using an external editor; normal reconciliation
371
+ picks up the change. Invalid structure displays its source and recovers when repaired.
372
+ The optional `.marklower` exchange concrete remains supported.
373
+
374
+ Projected documents and their interior nodes are browser-read-only until gathered
375
+ source writing is ready. `GET /api/source` returns exact file text at the root or
376
+ decomposed yamlover for an interior node. File renames maintain references inside
377
+ quoted and block scalar carriers. Moves that would remove gathered ordering refuse
378
+ before changing files. Ordinary chapter editing stays WYSIWYG: typed structural
379
+ markers are escaped when saved, including text containing only markers.
380
+ The syntax, migration filter and current limits are documented in
381
+ `docs/documents/marklower/gathered/index.yo`.
package/bin/yamlover.js CHANGED
@@ -72,6 +72,7 @@ let basePath = process.env.BASE_PATH ?? "";
72
72
  // CONTENT READ-ONLY: every user-data-mutating API route answers 403 and the UI hides its edit
73
73
  // affordances; the server still maintains its own index (.yo/index.db). Seeded from
74
74
  // $YAMLOVER_READ_ONLY (same shell-less-image rationale as $BASE_PATH); `--read-only` below too.
75
+ let printOpenApi = false;
75
76
  let readOnly = ["1", "true", "yes"].includes(String(process.env.YAMLOVER_READ_ONLY ?? "").toLowerCase());
76
77
  // Normalize a base path: leading `/`, no trailing `/`; `""`/`"/"` → disabled.
77
78
  function normBase(s) {
@@ -95,11 +96,13 @@ for (let i = 0; i < argv.length; i++) {
95
96
  else if (a === "--no-gitignore") gitignore = false;
96
97
  else if (a === "--prod") prodFlag = true;
97
98
  else if (a === "--read-only") readOnly = true;
99
+ else if (a === "--print-openapi") printOpenApi = true; // print the wire's OpenAPI document (JSON) and exit
98
100
  else if (a === "--help" || a === "-h") {
99
- console.log("usage: npx yamlover [ROOT] [--port N] [--headless] [--host ADDR] [--base-path PREFIX] [--no-gitignore] [--prod] [--read-only]");
101
+ console.log("usage: npx yamlover [ROOT] [--port N] [--headless] [--host ADDR] [--base-path PREFIX] [--no-gitignore] [--prod] [--read-only] [--print-openapi]");
100
102
  console.log(" default: serve on 127.0.0.1 (local only); --headless serves on all interfaces");
101
103
  console.log(" --base-path PREFIX: serve the whole app under PREFIX (e.g. /demo/abc) instead of /");
102
104
  console.log(" --read-only: serve content read-only — every modifying request is refused (403)");
105
+ console.log(" --print-openapi: print the wire's OpenAPI document (JSON, docs/server/wire) and exit");
103
106
  process.exit(0);
104
107
  } else if (!a.startsWith("-")) rootArg = a;
105
108
  }
@@ -187,6 +190,7 @@ function serveCrawlerFile(req, res, pathname) {
187
190
  }
188
191
 
189
192
  let createHandlers;
193
+ let serverModule; // the handler module — also exports openApiDocument (--print-openapi)
190
194
  if (prod) {
191
195
  // -- production: prebuilt static client + bundled server, no Vite ---------- //
192
196
  const distClient = join(pkgRoot, "dist/client");
@@ -196,7 +200,8 @@ if (prod) {
196
200
  console.error(" run `npm run build` in tools/server first.");
197
201
  process.exit(1);
198
202
  }
199
- ({ createHandlers } = await import(pathToFileURL(join(pkgRoot, "dist/server.js")).href));
203
+ serverModule = await import(pathToFileURL(join(pkgRoot, "dist/server.js")).href);
204
+ ({ createHandlers } = serverModule);
200
205
  serveClient = (req, res, url) => serveStatic(req, res, url, distClient, distIndex);
201
206
  } else {
202
207
  // -- live: Vite middleware + ssrLoadModule (repo checkout) ----------------- //
@@ -279,7 +284,8 @@ if (prod) {
279
284
  // The engine-backed handler (engine-api.ts) is loaded via `ssrLoadModule` so its
280
285
  // TS — and the engine/parser source it reaches into (`../../../engine`,
281
286
  // `../../../parser`) — is transpiled live, with no build step.
282
- ({ createHandlers } = await vite.ssrLoadModule("/src/server/engine-api.ts"));
287
+ serverModule = await vite.ssrLoadModule("/src/server/engine-api.ts");
288
+ ({ createHandlers } = serverModule);
283
289
 
284
290
  const indexHtmlPath = join(pkgRoot, "index.html");
285
291
  const spaShell = async (req, res, url) => {
@@ -334,6 +340,12 @@ const ts = () => {
334
340
  // The initial index runs as a BACKGROUND task — the server listens immediately
335
341
  // (serving the previous on-disk index, or an empty tree on a cold start) while
336
342
  // progress lands here and in the web UI (SSE task frames + GET /api/tasks).
343
+ if (printOpenApi) {
344
+ // the document needs no tree: the schemas come from the bundled taxonomy, the routes from the
345
+ // authored openapi.yo — print and leave before any index is touched
346
+ process.stdout.write(JSON.stringify(serverModule.openApiDocument(), null, 2) + "\n");
347
+ process.exit(0);
348
+ }
337
349
  handle = createHandlers(dataRoot, {
338
350
  gitignore,
339
351
  watch: true, // re-index + push on external edits
@@ -466,7 +478,15 @@ function listenWithFallback(p, triesLeft) {
466
478
  const onListening = () => {
467
479
  server.off("error", onError); // bound OK — stop intercepting listen errors
468
480
  console.log(`yamlover ${ts()} serving ${dataRoot}${prod ? "" : " (live/Vite)"}`);
469
- console.log(` http://${shown}:${p}/ (bound to ${host})`);
481
+ const boundPort = server.address().port; // --port 0 asks the OS for a free port.
482
+ const url = `http://${shown}:${boundPort}${basePath}/`;
483
+ console.log(` ${url} (bound to ${host})`);
484
+ // The desktop utility process uses a structured handshake, never log scraping.
485
+ // Wait for indexing too: a listening socket alone does not mean the tree is usable.
486
+ if (process.parentPort) {
487
+ handle.ready.then(() => process.parentPort.postMessage({ type: "yamlover-ready", url }))
488
+ .catch(() => process.exit(1));
489
+ }
470
490
  // Say it out loud: analytics is off in every normal run, so when it IS on the operator
471
491
  // should see which stream is being fed and exactly what paths reach it.
472
492
  if (ga4) {
@@ -479,3 +499,20 @@ function listenWithFallback(p, triesLeft) {
479
499
  }
480
500
 
481
501
  listenWithFallback(port, MAX_PORT_TRIES);
502
+
503
+ // Normal CLI/desktop termination must reap decoder workers and disposable video previews.
504
+ let stopping = false;
505
+ async function shutdown() {
506
+ if (stopping) return;
507
+ stopping = true;
508
+ const deadline = setTimeout(() => process.exit(1), 10000);
509
+ deadline.unref();
510
+ server.close();
511
+ server.closeAllConnections();
512
+ try { await handle.close(); }
513
+ catch (error) { console.error("yamlover shutdown:", error); }
514
+ clearTimeout(deadline);
515
+ process.exit(0);
516
+ }
517
+ process.once("SIGINT", shutdown);
518
+ process.once("SIGTERM", shutdown);
@@ -1,330 +1,20 @@
1
- # Working with this yamlover project (guide for AI agents)
2
-
3
- This project stores structured knowledge as **yamlover** files. You (an AI coding agent) and a
4
- human are editing the **same directory** at the same time: the human browses the tree in the
5
- yamlover web UI (`npx yamlover .`) while you read and edit the files on disk. This document
6
- tells you how to manipulate these files **correctly** so you don't corrupt the data or break the
7
- human's live view.
8
-
9
- > This file is self-contained. You do **not** need any other spec to follow it. The full,
10
- > authoritative spec is the yamlover documentation book (the `docs/` tree of the yamlover
11
- > project itself the language under `docs/language/`, the document model under
12
- > `docs/documents/`); consult it for edge cases.
13
-
14
- ---
15
-
16
- ## 1. What yamlover is
17
-
18
- yamlover is a **distinct, YAML-like language** close to YAML and mostly readable as it, but
19
- **not a strict superset**: `*` and `&` carry different meaning (see below), so a stock YAML
20
- document that uses aliases/anchors does **not** round-trip identically. On top of a YAML-style
21
- surface it adds a small **pointer layer** so that data forms a graph, not just a tree:
22
-
23
- - **`*` pointers** — a value that *refers to* another node (a ref edge, not a copy).
24
- - **`&` path anchors** — declare that "this node also lives over there" (the push side of `*`).
25
- - **`!!` tags** — type/schema markers, including inline schema references `!!<…>`.
26
-
27
- There is a sibling brace surface called **json5p** (`.json5p` files) — the same pointer layer
28
- expressed in JSON5 syntax. Most projects use `.yo`; treat `.json5p` as the JSON-flavored
29
- twin (pointers are written as quoted strings, e.g. `*": pets: 1"`).
30
-
31
- **Important:** because `*` and `&` mean something different than in stock YAML (a `*` is a path
32
- pointer, **not** a YAML alias), these files require the yamlover parser. Do not "fix" them with
33
- a generic YAML formatter — you will destroy the pointers and anchors.
34
-
35
- ---
36
-
37
- ## 2. The one-ordered-container model
38
-
39
- Plain YAML forces a node to be **either** a sequence (all `- item`) **or** a mapping (all
40
- `key: value`). yamlover unifies them: there is **one ordered container**. Every entry has an
41
- integer **position** (0, 1, …) and **may also** carry a string key. Keyless (positional)
42
- and keyed entries coexist in one node — this is the default ("omni"):
43
-
44
- ```yamlover
45
- playlist:
46
- - Intro # position 0 keyless / positional
47
- - Verse # position 1 keyless
48
- title: Greatest Hits # position 2, key=title keyed — AND still positioned
49
- - Chorus # position 3 keyless
50
- encore: *: pets: 0 # position 4, key=encore a keyed pointer, still in order
51
- ```
52
-
53
- A node can even carry a **scalar value AND fields at once**:
54
-
55
- ```yamlover
56
- rating: 5 # the node's own scalar value …
57
- - solid # position 0 positional field
58
- scale: 10 # position 2 keyed field
59
- ```
60
-
61
- (You may see an optional `!!mix` tag marking these shapes. It is a **no-op readability marker**
62
- — mixing and scalar-plus-fields are the default. Don't add or remove it to change meaning; it
63
- doesn't carry any. `!!yo` — whose deprecated aliases `!!var` / `!!omni` still parse — is a
64
- DIFFERENT thing and is **semantic**: see §6.)
65
-
66
- ---
67
-
68
- ## 3. Paths use COLONS, not slashes
69
-
70
- A path is `key: value` chained. The separator is the **colon**: `a: b: c` means "key a → key b →
71
- key c". Canonical style writes `: ` (colon + space) after each step; a colon without the space
72
- also parses.
73
-
74
- `/` is an **ordinary character** now — MIME-type keys (`text/html`), date keys (`01/02/2026`)
75
- and URL-ish keys ride bare in a path. (The old `/`-separator form is DEAD — it no longer
76
- parses as a path: `*/pets` is a dangling reference to the literal key "/pets". Author `:` only.)
77
-
78
- ### The scope ladder — more colons, wider scope
79
-
80
- ```
81
- current: object: path # bare — current scope (siblings)
82
- : document: rooted: path # : — this document's root
83
- :: project: rooted: path # :: — this project's root
84
- ::: yamlover.inthemoon.net: $defs: onto # ::: — the world (an external project)
85
- ```
86
-
87
- - `*pets: 1` — bare: a **sibling** named `pets`, position 1.
88
- - `*: pets: 0` — `:` document root.
89
- - `*:: ontos: genre` — `::` this project's root.
90
- - `*::: host.example: $defs: onto` — `:::` a world/external reference.
91
-
92
- ### The bare-token rule — what a portion means
93
-
94
- Every portion is typed **by its own form**:
95
-
96
- - **pure digits** = the integer key, i.e. a **position**: `*: pets: 1: name` = root → position 1
97
- → key `name`.
98
- - **a bare `~`** = the **null key** (`~: value`; `: v` is the same entry). The *string* key
99
- `"null"` is written `null:`.
100
- - **anything else bare, and any quoted portion** = a **string key**: `: '1'` is the numeric
101
- string key, `: '~'` the literal tilde.
102
-
103
- A key whose bare form would read as something else MUST be quoted: empty, pure digits, `~`,
104
- `-`+digits, or a key containing a space. In a document, a plain `1:` is a **parse error** —
105
- author `'1':` for the numeric string key.
106
-
107
- > **The retired bracket index.** The old `[n]` position form (`*pets[1]`) still *reads* — it is
108
- > a permanent alias — but it is **written never**. Author the bare-integer segment. The one
109
- > surviving bracket operator is `[.±k]` (a position relative to the pointer's own host). The
110
- > former `[]` (append) and `[?]` (any position) brackets were REMOVED — both are now the `-`
111
- > segment: a trailing `: -` on a bookmark appends (§5), and `-` in a query matches any
112
- > position. A literal `-` key must be quoted (`'-'`).
113
-
114
- ---
115
-
116
- ## 4. Pointers `*` (the pull side)
117
-
118
- A `*` value dereferences a path to another node and creates a **ref edge** (not a copy):
119
-
120
- ```yamlover
121
- humans:
122
- - name: Alice
123
- manager: *: pets: 1 # Alice.manager IS the node at root → pets → position 1
124
- feline: *pets: 1 # bare → a sibling
125
- topDog: *: pets: 0 # : → document root
126
- ```
127
-
128
- Pointers are **lazy** and **cycle-safe** — pointing two nodes at each other is fine. Editing the
129
- target changes everything that points at it.
130
-
131
- ---
132
-
133
- ## 5. Path anchors `&` (the push side)
134
-
135
- `&: path` on a node declares "this node **also lives** at that path" — it grafts a **real key**
136
- (there is no separate anchor namespace), so any plain pointer can then reach it:
137
-
138
- ```yamlover
139
- boss: &: chief # this node is ALSO reachable as the document-root key `chief`
140
- name: Rex
141
- team:
142
- lead: *: chief # same node as `boss` — a ref edge, not a copy
143
- ```
144
-
145
- Reverse relations are spelled as anchors. `&: parent: child` means "parent holds me as child":
146
-
147
- ```yamlover
148
- adam:
149
- cain:
150
- &: eve: cain # "eve holds me as cain" — the reverse of eve's cain-edge
151
- ```
152
-
153
- `&: container: -` (a trailing `-` segment) means **positional membership**: "that container also
154
- holds me", appended after the container's own entries:
155
-
156
- ```yamlover
157
- fan:
158
- name: Bob
159
- &: favorites: - # Bob appends himself to `favorites`
160
- ```
161
-
162
- Anchor paths must be **unambiguous** (no wildcards, no trailing position claim — neither a bare
163
- integer nor a relative `[.±k]`) — they create real keys, so they must resolve to exactly one
164
- place. The trailing `-` is an append, not a claim; a `-` anywhere else in an anchor path is
165
- reserved (a parse error today). The old `&: container[]` bracket spelling was removed and no
166
- longer parses.
167
-
168
- > You may encounter the older `~key: *path` back-edge syntax in legacy files. It still parses but
169
- > is deprecated; author new reverse edges as `&` anchors.
170
-
171
- ---
172
-
173
- ## 6. Tags `!!` and `$defs` schemas
174
-
175
- - `!!type` — a YAML-style tag. `!!mix` is the one no-op marker (see §2). The rest are
176
- **semantic** and must not be added or dropped casually:
177
- - `!!set` marks a container whose membership is by identity (duplicates collapse);
178
- - `!!yo` (deprecated aliases `!!var` / `!!omni`) marks a node as **plain yamlover, exempt
179
- from the enclosing document's schema** — a *data island*. Inside a structured document (a
180
- chapter, say) a `!!yo` node is never interpreted by that schema: it is data, drawn by the
181
- generic renderer.
182
- - **Inline schema reference** `!!<…>` binds a node to a reusable schema definition:
183
- ```yamlover
184
- mychapter: !!<*:: yamlover: $defs: chapter>
185
- ```
186
- Reusable schemas live at the project root under **`$defs`** (e.g. `$defs: chapter`,
187
- `$defs: onto`, `$defs: annotation`, `$defs: fragment`). They are referenced project-scoped
188
- (`*:: $defs: name`) or via the self-import (`*:: yamlover: $defs: name` — synonyms inside this
189
- project). Schemas are **metadata** (typing/format/presentation), not data storage.
190
-
191
- ---
192
-
193
- ## 7. Concretes: how a node is stored on disk
194
-
195
- A node can be materialized two ways:
196
-
197
- 1. **Single-file concrete** — a whole document in one `.yo` (or `.json5p`) file.
198
- 2. **Directory concrete** — a directory **is** the node; its files/subdirs are its entries, and
199
- two optional overlay files inside a hidden `.yo/` subdir add data and schema:
200
- - `.yo/body.yo` — **instance** overlay: scalar values, ordering, pointers,
201
- extra keyed/keyless entries layered onto the directory's contents.
202
- - `.yo/meta.yo` — **schema** overlay: typing, format, validation.
203
-
204
- So to add a pointer or a value "to a folder", you edit (or create) that folder's
205
- `.yo/body.yo`. Plain files inside the directory are its members; a `.yo/`
206
- subdir does not appear as a member — it's the overlay.
207
-
208
- A pure pointer-array body (`- *file1` …) is the ORDER overlay: it grants positions to the
209
- members it names (the projection shows each consumed key as a dimmed derived `&` anchor,
210
- `- &file1 value`); a member the body never names stays keyed-only, after the ordered block.
211
- Editing derives storage for NEW children by the INHERITANCE RULES (`concrete-rules.ts` — one
212
- pure module, an explicit `concrete:` always wins): a directory-concrete parent keeps children
213
- directory-concrete; a keyed container child becomes a nested real directory; an untagged
214
- keyless (ordinal) container child becomes an order-numbered subdirectory (`item01`,
215
- `item02`, …) referenced by a `- *: itemNN` pointer-array element; scalars and tagged containers
216
- (tables, typographical lists) go inline into `body.yo`. Title-born subchapter members are
217
- numbered too (`01-Введение`). The numbers are COSMETIC listing order — the body pointer-array is
218
- the order's data, and an existing member is NEVER renamed: an insert between neighbors slots a
219
- sub-number (`item01-1`, `01-1-Новая`). Content inside a file document speaks that file's
220
- language (a `.json5p` interior never switches to yaml).
221
-
222
- **Collapse / expand / promotion — the same node, two shapes.** The two concretes are
223
- freely interconvertible without changing what the data means: a child stored as
224
- `child.yo` (collapsed) and the same child stored as `child/` with a
225
- `.yo/body.yo` (expanded) are equivalent. Converting a single-file node **into**
226
- a directory is called **directory promotion** (the UI's action; the engine keeps inbound
227
- pointers valid across it, like `mv`). If you do it by hand, it is a two-step move: create the
228
- `child/` directory, move the file's contents into `child/.yo/body.yo` (plus any
229
- members as files), and delete the old `child.yo` — then let the engine reindex. Because
230
- it is a move, treat it with the same care as any rename: pointers that addressed the old file
231
- path must still resolve (prefer the mediated `mv`/promotion in the UI, which rewrites them —
232
- see §10).
233
-
234
- ---
235
-
236
- ## 8. Annotations & fragments (tags applied to content)
237
-
238
- The human marks up documents in the UI. These live **on the target node**, not in side files:
239
-
240
- - **`yo: fragments:`** — a mapping of slug → selector (a text span, image/PDF rectangle, or
241
- map box) identifying a region within the node.
242
- - **`yamlover-annotations`** — a sequence; each element applies a tag to the node (or a
243
- fragment). An element is either a bare **tag pointer** or an object with a `tag:` field plus
244
- parameters:
245
- ```yamlover
246
- yamlover-annotations:
247
- - *:: ontos: genre: brevity # parameterless
248
- - {description: 'A math block', tag: *:: ontos: topic: math} # parametrized
249
- ```
250
-
251
- (A flow scalar carrying a SPACE must be quoted — an unquoted `A math block` inside `{…}` is a
252
- parse error.)
253
-
254
- Prefer letting the human create these through the UI. If you must touch them by hand, keep the
255
- exact key names (`yo: fragments:`, `yamlover-annotations`) and the tag-pointer form, and do
256
- not renumber or reorder fragment slugs.
257
-
258
- ---
259
-
260
- ## 9. Escaping (critical when editing keys)
261
-
262
- A literal metacharacter inside a key is **backslash-escaped** (this is per-character, not
263
- quote-based):
264
-
265
- ```yamlover
266
- weird:
267
- cat\:dog: 1 # the literal key "cat:dog" (\: suppresses the separator)
268
- cat/dog: 2 # `/` is ordinary now — no escape needed
269
- ref: *weird: cat\:dog # the second portion is the literal key "cat:dog"
270
- dots: *\.\. # the literal key ".." (NOT the parent scope)
271
- ```
272
-
273
- - Metachars that need escaping in a key include: `:` `[` `]` `*` `&` `~` `#` `\` `?` `!`
274
- `(` `)` `<` `>` `=` `|`.
275
- - The literal keys `..` and `...` are written `\.\.` and `\.\.\.` (bare `..` means "parent").
276
- - **A key containing a space MUST be quoted**: `: tags: 'дорожный знак'`. Use the host surface's
277
- string quoting (`'…'` / `"…"`).
278
-
279
- ---
280
-
281
- ## 10. Co-editing workflow & safety rules
282
-
283
- The yamlover server **watches the filesystem**. Every time you save a file, it reindexes and
284
- pushes the change to the human's browser over a live event stream — so your edits appear in their
285
- UI within a moment, and theirs appear to you on disk. Work with that, not against it:
286
-
287
- - **Make small, valid saves.** A half-written file will reindex as broken. Prefer complete edits.
288
- - **NEVER touch `.yo/index.db`** (nor its `-wal` / `-shm` companions). It is the server's
289
- generated SQLite index — it regenerates itself from the source files. Editing or deleting it
290
- does nothing useful and can confuse a running server. It is the *only* thing in `.yo/`
291
- you must not edit; `body.yo` / `meta.yo` overlays (§7) are normal editable data.
292
- - **Renames and moves break inbound pointers.** Other files may point at a node by its path
293
- (`*: some: node`). If you move or rename it with a plain `mv`, those pointers dangle. The
294
- running server exposes a **mediated move** (`POST /api/mv`) that surgically rewrites inbound
295
- references at their source. Prefer it for moves/renames; if you must move by hand, search the
296
- project for pointers to the old path (`*` followed by the path) and update them too.
297
- - **Don't reformat with a generic YAML/JSON tool.** It will mangle `*`, `&`, `!!<…>`, the
298
- colon paths, and the mixed keyed/keyless ordering. Edit the text directly and preserve style.
299
- - **Settings live in `.yo/settings.yo`** at the project root (e.g. where new tags and
300
- annotations are written). Treat it as configuration; change it only when asked.
301
- - **When unsure of a path, query it.** The server answers `GET /api/query?...` using the path
302
- grammar above, and serves the tree at `GET /api/tree` / a node's yamlover at
303
- `GET /api/content/a/b` (slash-spelled path; digits are positions, `~` the null key).
304
- Use these to confirm a path resolves before you author a pointer to it.
305
-
306
- ---
307
-
308
- ## 11. Quick reference
309
-
310
- ```yamlover
311
- # pointers (pull) — colon paths, the scope ladder
312
- sibling: *pets: 1 # current scope, by position (bare digits = the position)
313
- rooted: *: humans: 0: name # document root → position 0 → key name
314
- strkey: *: counts: '1' # QUOTED digits = the numeric STRING key
315
- nullkey: *: doc: ~ # the null key
316
- projscope: *:: ontos: genre # this project's root
317
- world: *::: host.example: $defs: onto
318
-
319
- # anchors (push) — "I also live there"; real keys; unambiguous only
320
- here: &: chief # also at document-root key `chief`
321
- rev: &: parent: child # parent holds me as `child`
322
- mem: &: favorites: - # appended member of `favorites`
323
-
324
- # tags / schema
325
- node: !!<*:: $defs: chapter> # bind a reusable schema
326
-
327
- # escaping
328
- lit: *weird: cat\:dog # literal colon in a key
329
- space: : tags: 'two words' # spacey key must be quoted
330
- ```
1
+ ## yamlover read `AGENTS.yo` first
2
+
3
+ This directory is a **yamlover** project: its structured knowledge lives in `.yo` files, a
4
+ YAML-like language that is **not** YAML (`*` is a path pointer, `&` a bookmark). The authoring
5
+ rules pointers and bookmarks, marklower prose, chapters, tables, annotations, tasks and boards,
6
+ and the ticket process for an agent and the rules for safely co-editing the tree while the
7
+ yamlover web UI is running are in **`AGENTS.yo`** beside this file (itself a yamlover chapter —
8
+ read it as text; every code span is a literal spelling). Read it before editing any `.yo` /
9
+ `.json5p` file. The full reference is online at https://yamlover.inthemoon.net/docs.
10
+
11
+ The three rules that matter most:
12
+
13
+ - **Never reformat a `.yo` file with a generic YAML tool** — it destroys pointers, bookmarks and
14
+ tags. Edit the text and keep the author's style.
15
+ - **While the server runs, go through its API** — `POST /api/edit`, `/api/mv`, `/api/rekey`,
16
+ `/api/annotate` rather than raw writes for anything structural, and check
17
+ `GET /api/dangling` after a batch of edits.
18
+ - **A ticket's state is one bookmark line in its own file**
19
+ (`&::yamlover:ontos:workflow:dev:in-progress:-`). Move a card by rewriting that line never by
20
+ editing a board's `.yo: lanes:`, which the tags overrule on the next reconcile.