yamlover 0.3.56 → 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.
- package/README.md +48 -5
- package/bin/yamlover.js +41 -4
- package/dist/agent-docs/AGENTS.md +20 -333
- package/dist/agent-docs/AGENTS.yo +621 -0
- package/dist/builtin-taxonomy/$defs/.yo/meta.yo +0 -2
- package/dist/builtin-taxonomy/$defs/decision +34 -0
- package/dist/builtin-taxonomy/$defs/edit +60 -0
- package/dist/builtin-taxonomy/$defs/edit-answer +21 -0
- package/dist/builtin-taxonomy/$defs/edit-batch +31 -0
- package/dist/builtin-taxonomy/$defs/envelope +169 -0
- package/dist/builtin-taxonomy/$defs/event +85 -0
- package/dist/builtin-taxonomy/$defs/link +58 -0
- package/dist/builtin-taxonomy/$defs/task +28 -19
- package/dist/builtin-taxonomy/$defs/workflow +3 -0
- package/dist/builtin-taxonomy/ontos/.yo/body.yo +7 -1
- package/dist/client/assets/{decoded-CytDRqwJ.js → decoded-Bj1qJCo_.js} +1 -1
- package/dist/client/assets/{djvu-Da-iyAV_.js → djvu-lnurzbzN.js} +1 -1
- package/dist/client/assets/{docx-CX_gHuMq.js → docx-DpN_PtM8.js} +1 -1
- package/dist/client/assets/{heic-Bf9423pJ.js → heic-BqJutS0N.js} +1 -1
- package/dist/client/assets/imagemap-rdhcPlWy.js +1 -0
- package/dist/client/assets/index-0ALNONIF.js +962 -0
- package/dist/client/assets/index-BiETmvLO.css +1 -0
- package/dist/client/assets/map-Btm4SlVs.js +1 -0
- package/dist/client/assets/paged-Cjy3svw2.js +1 -0
- package/dist/client/assets/{openable-D48EbC_P.js → panzoom-BXEAw479.js} +2 -2
- package/dist/client/assets/{pdf-Dj6UMm-B.js → pdf-DvC2sbDN.js} +1 -1
- package/dist/client/assets/psd-BKmCWsdC.js +11 -0
- package/dist/client/assets/{spreadsheet-BmqsAKDF.js → spreadsheet-Coy5sooG.js} +1 -1
- package/dist/client/assets/tiff-IcWiNdYs.js +1 -0
- package/dist/client/assets/video-YC-ZvXg7.js +1 -0
- package/dist/client/assets/xyflow-CFNcu5cT.js +23 -0
- package/dist/client/index.html +2 -2
- package/dist/server.js +8818 -5114
- package/dist/wire/openapi.yo +935 -0
- package/package.json +6 -1
- package/dist/agent-docs/CLAUDE.md +0 -7
- package/dist/builtin-taxonomy/$defs/board +0 -15
- package/dist/client/assets/imagemap-BaCsSWT2.js +0 -1
- package/dist/client/assets/index-CLllgDtO.js +0 -765
- package/dist/client/assets/index-CYdxG8JF.css +0 -1
- package/dist/client/assets/map-C0h_m0kt.js +0 -1
- package/dist/client/assets/paged-YrFta0Z1.js +0 -1
- package/dist/client/assets/psd-vtV8RE5t.js +0 -11
- package/dist/client/assets/tiff-DTjYlW1c.js +0 -1
- package/dist/client/assets/xyflow-DLSA3Qi6.js +0 -23
- /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`
|
|
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`
|
|
243
|
-
| `task` | `x-yamlover-task` | task
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,333 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
This
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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. `.yo` is the reserved hidden technical key
|
|
242
|
-
(the legacy spelling `yo:` is read forever — reuse it where a file already has it, never add
|
|
243
|
-
a second overlay key).
|
|
244
|
-
- **membership bookmarks** — applying a tag is one own-line `&…:-` bookmark on the target
|
|
245
|
-
(the node, or a fragment member):
|
|
246
|
-
```yamlover
|
|
247
|
-
.yo: fragments:
|
|
248
|
-
mq1abc-x2:
|
|
249
|
-
&:: ontos: topic: math:- # this fragment IS filed under topic/math
|
|
250
|
-
type: text
|
|
251
|
-
exact: the marked words
|
|
252
|
-
```
|
|
253
|
-
(You may still meet a legacy `yamlover-annotations:` array — read-only history; do not grow
|
|
254
|
-
it.)
|
|
255
|
-
|
|
256
|
-
Any key starting with `.` is HIDDEN from the TOC and listings but browsable by direct path.
|
|
257
|
-
The `.yo` subtree is engine-managed: do not edit it by hand except through the UI verbs; the
|
|
258
|
-
one editable file is `.yo/settings.yo`. If you must touch fragments by hand, keep the exact
|
|
259
|
-
key names and do not renumber or reorder fragment slugs.
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## 9. Escaping (critical when editing keys)
|
|
264
|
-
|
|
265
|
-
A literal metacharacter inside a key is **backslash-escaped** (this is per-character, not
|
|
266
|
-
quote-based):
|
|
267
|
-
|
|
268
|
-
```yamlover
|
|
269
|
-
weird:
|
|
270
|
-
cat\:dog: 1 # the literal key "cat:dog" (\: suppresses the separator)
|
|
271
|
-
cat/dog: 2 # `/` is ordinary now — no escape needed
|
|
272
|
-
ref: *weird: cat\:dog # the second portion is the literal key "cat:dog"
|
|
273
|
-
dots: *\.\. # the literal key ".." (NOT the parent scope)
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
- Metachars that need escaping in a key include: `:` `[` `]` `*` `&` `~` `#` `\` `?` `!`
|
|
277
|
-
`(` `)` `<` `>` `=` `|`.
|
|
278
|
-
- The literal keys `..` and `...` are written `\.\.` and `\.\.\.` (bare `..` means "parent").
|
|
279
|
-
- **A key containing a space MUST be quoted**: `: tags: 'дорожный знак'`. Use the host surface's
|
|
280
|
-
string quoting (`'…'` / `"…"`).
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## 10. Co-editing workflow & safety rules
|
|
285
|
-
|
|
286
|
-
The yamlover server **watches the filesystem**. Every time you save a file, it reindexes and
|
|
287
|
-
pushes the change to the human's browser over a live event stream — so your edits appear in their
|
|
288
|
-
UI within a moment, and theirs appear to you on disk. Work with that, not against it:
|
|
289
|
-
|
|
290
|
-
- **Make small, valid saves.** A half-written file will reindex as broken. Prefer complete edits.
|
|
291
|
-
- **NEVER touch `.yo/index.db`** (nor its `-wal` / `-shm` companions). It is the server's
|
|
292
|
-
generated SQLite index — it regenerates itself from the source files. Editing or deleting it
|
|
293
|
-
does nothing useful and can confuse a running server. It is the *only* thing in `.yo/`
|
|
294
|
-
you must not edit; `body.yo` / `meta.yo` overlays (§7) are normal editable data.
|
|
295
|
-
- **Renames and moves break inbound pointers.** Other files may point at a node by its path
|
|
296
|
-
(`*: some: node`). If you move or rename it with a plain `mv`, those pointers dangle. The
|
|
297
|
-
running server exposes a **mediated move** (`POST /api/mv`) that surgically rewrites inbound
|
|
298
|
-
references at their source. Prefer it for moves/renames; if you must move by hand, search the
|
|
299
|
-
project for pointers to the old path (`*` followed by the path) and update them too.
|
|
300
|
-
- **Don't reformat with a generic YAML/JSON tool.** It will mangle `*`, `&`, `!!<…>`, the
|
|
301
|
-
colon paths, and the mixed keyed/keyless ordering. Edit the text directly and preserve style.
|
|
302
|
-
- **Settings live in `.yo/settings.yo`** at the project root (e.g. where new tags and
|
|
303
|
-
annotations are written). Treat it as configuration; change it only when asked.
|
|
304
|
-
- **When unsure of a path, query it.** The server answers `GET /api/query?...` using the path
|
|
305
|
-
grammar above, and serves the tree at `GET /api/tree` / a node's yamlover at
|
|
306
|
-
`GET /api/content/a/b` (slash-spelled path; digits are positions, `~` the null key).
|
|
307
|
-
Use these to confirm a path resolves before you author a pointer to it.
|
|
308
|
-
|
|
309
|
-
---
|
|
310
|
-
|
|
311
|
-
## 11. Quick reference
|
|
312
|
-
|
|
313
|
-
```yamlover
|
|
314
|
-
# pointers (pull) — colon paths, the scope ladder
|
|
315
|
-
sibling: *pets: 1 # current scope, by position (bare digits = the position)
|
|
316
|
-
rooted: *: humans: 0: name # document root → position 0 → key name
|
|
317
|
-
strkey: *: counts: '1' # QUOTED digits = the numeric STRING key
|
|
318
|
-
nullkey: *: doc: ~ # the null key
|
|
319
|
-
projscope: *:: ontos: genre # this project's root
|
|
320
|
-
world: *::: host.example: $defs: onto
|
|
321
|
-
|
|
322
|
-
# anchors (push) — "I also live there"; real keys; unambiguous only
|
|
323
|
-
here: &: chief # also at document-root key `chief`
|
|
324
|
-
rev: &: parent: child # parent holds me as `child`
|
|
325
|
-
mem: &: favorites: - # appended member of `favorites`
|
|
326
|
-
|
|
327
|
-
# tags / schema
|
|
328
|
-
node: !!<*:: $defs: chapter> # bind a reusable schema
|
|
329
|
-
|
|
330
|
-
# escaping
|
|
331
|
-
lit: *weird: cat\:dog # literal colon in a key
|
|
332
|
-
space: : tags: 'two words' # spacey key must be quoted
|
|
333
|
-
```
|
|
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.
|