@k-l-lambda/portolan 0.0.0-stage → 0.1.1
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 +104 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +42 -0
- package/dist/cli.js.map +1 -0
- package/dist/derive.d.ts +17 -0
- package/dist/derive.js +67 -0
- package/dist/derive.js.map +1 -0
- package/dist/edit.d.ts +69 -0
- package/dist/edit.js +302 -0
- package/dist/edit.js.map +1 -0
- package/dist/format.d.ts +9 -0
- package/dist/format.js +35 -0
- package/dist/format.js.map +1 -0
- package/dist/generated/line-parser.d.ts +213 -0
- package/dist/generated/line-parser.js +1920 -0
- package/dist/history.d.ts +90 -0
- package/dist/history.js +182 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/parse.d.ts +33 -0
- package/dist/parse.js +468 -0
- package/dist/parse.js.map +1 -0
- package/dist/resolve.d.ts +41 -0
- package/dist/resolve.js +214 -0
- package/dist/resolve.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +376 -0
- package/dist/server.js.map +1 -0
- package/dist/threads.d.ts +93 -0
- package/dist/threads.js +128 -0
- package/dist/threads.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/rhumb-spec.md +289 -0
- package/docs/skill.md +320 -0
- package/package.json +78 -4
- package/web/dist/assets/index-CB0_S5Fp.js +106 -0
- package/web/dist/assets/index-Czapgw6a.css +1 -0
- package/web/dist/favicon.svg +16 -0
- package/web/dist/index.html +14 -0
package/README.md
CHANGED
|
@@ -1,3 +1,105 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="web/public/favicon.svg" alt="Portolan logo: a compass rose with a red north point" width="96" height="96">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Portolan</h1>
|
|
6
|
+
|
|
7
|
+
Portolan is a shared map of work for humans and AI agents. It is a mind map that is also a TODO list and a task log, showing the relations, progress and plans across an agent's whole workflow.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Agents now do long, branching work: surveys, experiments, refactors that split into sub-tasks and depend on each other. Logs and diaries record what happened, but not the shape of the work. Chat history is linear. It is hard to see what is done, what is blocked and what comes next.
|
|
12
|
+
|
|
13
|
+
Existing tools cover pieces of this. Agent task trackers keep a task graph, mind-map UIs draw trees, plan-annotation tools let you comment on an agent's plan. None of them gives one shared map that is human-readable, git-friendly, and edited by both the human and the agent.
|
|
14
|
+
|
|
15
|
+
## Core ideas
|
|
16
|
+
|
|
17
|
+
- The map holds structure only: work items, status, hierarchy and relations. Details stay in the diary, and the map links into it.
|
|
18
|
+
- The map is plain text in the Rhumb DSL (`.rhumb`). It is designed to be easy for LLMs to write: it borrows GFM task lists, YAML and Markdown links, and adds only a small sentence syntax for edges.
|
|
19
|
+
- It is designed to diff well in git: one change is one line.
|
|
20
|
+
- Annotations live in a sidecar file next to the map, so humans and agents can discuss a node in a thread without cluttering the map.
|
|
21
|
+
|
|
22
|
+
## The name
|
|
23
|
+
|
|
24
|
+
A portolan chart was a nautical map drawn from sailors' logbooks, with rhumb lines connecting the ports. It is a map that grows out of logs, which is what this project tries to be. Rhumb is the name of the DSL; its edges are the rhumb lines between work items.
|
|
25
|
+
|
|
26
|
+
## What a map looks like
|
|
27
|
+
|
|
28
|
+
```rhumb
|
|
29
|
+
---
|
|
30
|
+
rhumb: 0.1
|
|
31
|
+
title: Portolan
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
- [/] Portolan ^portolan
|
|
35
|
+
- [x] Prior-art survey ^survey
|
|
36
|
+
- Closest pieces: [Backlog.md](https://github.com/MrLesk/Backlog.md), Beads, Plannotator
|
|
37
|
+
- [/] Rhumb DSL {owner: claude} ^rhumb
|
|
38
|
+
- [x] Spec 0.1 ^rhumb-spec
|
|
39
|
+
- [x] Parser ^rhumb-parser
|
|
40
|
+
- [ ] Visual frontend ^ui
|
|
41
|
+
- [?] Agent loop for threads ^threads-agent
|
|
42
|
+
|
|
43
|
+
%% relations
|
|
44
|
+
rhumb-parser needs rhumb-spec
|
|
45
|
+
ui needs rhumb-parser: needs a stable AST
|
|
46
|
+
threads-agent relates ui
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Each `- [ ]` item is a node with a status (`[ ]` todo, `[/]` doing, `[x]` done, `[-]` dropped, `[!]` blocked, `[?]` idea) and a stable `^id`. Items without a checkbox are notes. Links are anchors into the diary or elsewhere. Lines like `a needs b` are edges. Progress and readiness are derived by the tools, never stored in the file.
|
|
50
|
+
|
|
51
|
+
## Status
|
|
52
|
+
|
|
53
|
+
Portolan is experimental and changing quickly. What exists today:
|
|
54
|
+
|
|
55
|
+
- Rhumb 0.1 spec and a Peggy-based parser with tests
|
|
56
|
+
- `check` (parse errors, consistency, anchor resolution) and a first `fmt` (assigns missing IDs, normalizes status aliases)
|
|
57
|
+
- An edit API (set status, add or delete nodes, rename IDs, add or remove edges) that keeps untouched lines intact
|
|
58
|
+
- A thread store for annotations in an append-only `.threads.jsonl` sidecar
|
|
59
|
+
- A local server that serves the map, derived values and threads, and pushes file changes live
|
|
60
|
+
- A first web view, with a mind map and a diary panel, still being built
|
|
61
|
+
|
|
62
|
+
The server has no authentication and binds to `127.0.0.1` only. Do not expose it.
|
|
63
|
+
|
|
64
|
+
Next:
|
|
65
|
+
|
|
66
|
+
- Annotation UI in the web view
|
|
67
|
+
- An agent loop that delivers human comments in threads to the agent and writes replies back
|
|
68
|
+
- A fuller formatter
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npm install -g @k-l-lambda/portolan # or: npx @k-l-lambda/portolan serve <dir>
|
|
74
|
+
portolan serve path/to/maps # then open http://127.0.0.1:4310
|
|
75
|
+
rhumb check path/to/plan.rhumb
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The package ships the compiled CLI, the library API (`import { parse } from "@k-l-lambda/portolan"`) and the built web app. It needs Node 22.12 or newer.
|
|
79
|
+
|
|
80
|
+
## Quick start
|
|
81
|
+
|
|
82
|
+
pnpm is required. `npm install` crashes with npm 10.9 on this dependency set.
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
pnpm install
|
|
86
|
+
pnpm test
|
|
87
|
+
pnpm build:web
|
|
88
|
+
|
|
89
|
+
node src/cli.ts check examples/portolan.rhumb
|
|
90
|
+
node src/cli.ts serve examples
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Then open http://127.0.0.1:4310.
|
|
94
|
+
|
|
95
|
+
## Docs
|
|
96
|
+
|
|
97
|
+
- [docs/rhumb-spec.md](docs/rhumb-spec.md): the Rhumb 0.1 syntax specification
|
|
98
|
+
- [docs/skill.md](docs/skill.md): guide for agents that maintain a Rhumb map
|
|
99
|
+
|
|
100
|
+
## Releasing
|
|
101
|
+
|
|
102
|
+
Bump `version` in `package.json` and push to `main`. The [publish workflow](.github/workflows/publish.yml) publishes that version to npm if it is not there yet, using npm Trusted Publishing (no token secret; provenance is added automatically).
|
|
103
|
+
|
|
104
|
+
The very first release has to be published by hand (`npm publish --access public`), because a trusted publisher can only be configured on a package that already exists. After that, on npmjs.com open the package settings, add a Trusted Publisher for GitHub Actions with user `k-l-lambda`, repository `portolan` and workflow `publish.yml`.
|
|
2
105
|
|
|
3
|
-
This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// rhumb check <file> parse + derived + anchor diagnostics; exit 1 on errors
|
|
3
|
+
// rhumb fmt <file> assign missing IDs and normalize status aliases in place
|
|
4
|
+
// portolan serve <dir|file> [--port N] web UI + API for every .rhumb under dir (127.0.0.1 only)
|
|
5
|
+
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { derive } from "./derive.js";
|
|
8
|
+
import { format } from "./format.js";
|
|
9
|
+
import { parse } from "./parse.js";
|
|
10
|
+
import { checkAnchors, contextFor } from "./resolve.js";
|
|
11
|
+
import { createRhumbServer } from "./server.js";
|
|
12
|
+
const [cmd, file, ...rest] = process.argv.slice(2);
|
|
13
|
+
if (!cmd || !file || !["check", "fmt", "serve"].includes(cmd)) {
|
|
14
|
+
console.error("usage: portolan <check|fmt> <file.rhumb>\n portolan serve <dir|file.rhumb> [--port N]");
|
|
15
|
+
process.exit(2);
|
|
16
|
+
}
|
|
17
|
+
const path = resolve(file);
|
|
18
|
+
const source = cmd === "serve" ? "" : readFileSync(path, "utf8");
|
|
19
|
+
if (cmd === "check") {
|
|
20
|
+
const doc = parse(source, { fileName: path });
|
|
21
|
+
const read = (p) => (existsSync(p) && statSync(p).isFile() ? readFileSync(p, "utf8") : null);
|
|
22
|
+
const diags = [...doc.diagnostics, ...derive(doc).diagnostics, ...checkAnchors(doc, contextFor(path, doc, read))]
|
|
23
|
+
.sort((a, b) => a.line - b.line);
|
|
24
|
+
for (const d of diags)
|
|
25
|
+
console.log(`${file}:${d.line}: ${d.level} ${d.code} ${d.message}`);
|
|
26
|
+
process.exit(diags.some((d) => d.level === "error") ? 1 : 0);
|
|
27
|
+
}
|
|
28
|
+
if (cmd === "fmt") {
|
|
29
|
+
const out = format(source);
|
|
30
|
+
if (out.source !== source)
|
|
31
|
+
writeFileSync(path, out.source);
|
|
32
|
+
for (const a of out.assigned)
|
|
33
|
+
console.log(`${file}:${a.line}: assigned ^${a.id}`);
|
|
34
|
+
}
|
|
35
|
+
if (cmd === "serve") {
|
|
36
|
+
const i = rest.indexOf("--port");
|
|
37
|
+
const port = i >= 0 ? Number(rest[i + 1]) : 4310;
|
|
38
|
+
createRhumbServer({ root: path }).listen(port, "127.0.0.1", () => {
|
|
39
|
+
console.log(`portolan: serving ${path} at http://127.0.0.1:${port} (no auth, loopback only)`);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/derive.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Diagnostic, RhumbDocument } from "./types.ts";
|
|
2
|
+
export interface NodeState {
|
|
3
|
+
line: number;
|
|
4
|
+
id: string | null;
|
|
5
|
+
/** Done leaves / committed leaves in the subtree; null when nothing is committed. */
|
|
6
|
+
progress: {
|
|
7
|
+
done: number;
|
|
8
|
+
total: number;
|
|
9
|
+
} | null;
|
|
10
|
+
/** todo, and every dependency is done (or dropped, see W006). */
|
|
11
|
+
ready: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface Derived {
|
|
14
|
+
nodes: NodeState[];
|
|
15
|
+
diagnostics: Diagnostic[];
|
|
16
|
+
}
|
|
17
|
+
export declare function derive(doc: RhumbDocument): Derived;
|
package/dist/derive.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Derived semantics (spec section 6): computed from the AST, never written to the file.
|
|
2
|
+
import { diagnostic } from "./parse.js";
|
|
3
|
+
const OPEN = new Set(["todo", "doing", "blocked"]);
|
|
4
|
+
export function derive(doc) {
|
|
5
|
+
const all = [];
|
|
6
|
+
const walk = (nodes) => nodes.forEach((n) => (all.push(n), walk(n.children)));
|
|
7
|
+
walk(doc.nodes);
|
|
8
|
+
const byId = new Map();
|
|
9
|
+
for (const n of all)
|
|
10
|
+
if (n.id !== null && !byId.has(n.id))
|
|
11
|
+
byId.set(n.id, n);
|
|
12
|
+
// Dependencies as x needs y, with the edge line for diagnostics.
|
|
13
|
+
const needs = new Map();
|
|
14
|
+
for (const e of doc.edges) {
|
|
15
|
+
if (e.kind !== "needs" && e.kind !== "blocks")
|
|
16
|
+
continue;
|
|
17
|
+
const [x, y] = e.kind === "needs" ? [byId.get(e.from), byId.get(e.to)] : [byId.get(e.to), byId.get(e.from)];
|
|
18
|
+
if (!x || !y)
|
|
19
|
+
continue;
|
|
20
|
+
if (!needs.has(x))
|
|
21
|
+
needs.set(x, []);
|
|
22
|
+
needs.get(x).push({ target: y, line: e.line });
|
|
23
|
+
}
|
|
24
|
+
const diagnostics = [];
|
|
25
|
+
const progress = new Map();
|
|
26
|
+
const count = (n) => {
|
|
27
|
+
let r;
|
|
28
|
+
if (n.children.length === 0) {
|
|
29
|
+
const committed = n.status !== "dropped" && n.status !== "idea";
|
|
30
|
+
r = { done: committed && n.status === "done" ? 1 : 0, total: committed ? 1 : 0 };
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
r = n.children.map(count).reduce((a, b) => ({ done: a.done + b.done, total: a.total + b.total }));
|
|
34
|
+
}
|
|
35
|
+
progress.set(n, r);
|
|
36
|
+
return r;
|
|
37
|
+
};
|
|
38
|
+
doc.nodes.forEach(count);
|
|
39
|
+
const hasOpenDescendant = (n) => n.children.some((c) => OPEN.has(c.status ?? "") || hasOpenDescendant(c));
|
|
40
|
+
const nodes = all.map((n) => {
|
|
41
|
+
const deps = needs.get(n) ?? [];
|
|
42
|
+
for (const d of deps) {
|
|
43
|
+
if (d.target.status === "dropped") {
|
|
44
|
+
diagnostics.push(diagnostic("W006", d.line, `"${n.id}" depends on dropped node "${d.target.id}"`));
|
|
45
|
+
}
|
|
46
|
+
else if ((n.status === "doing" || n.status === "done") && d.target.status !== "done") {
|
|
47
|
+
diagnostics.push(diagnostic("W008", n.line, `"${n.id}" is ${n.status} but "${d.target.id}" is not done`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (n.status === "done" && hasOpenDescendant(n)) {
|
|
51
|
+
diagnostics.push(diagnostic("W007", n.line, "Node is done but its subtree still has open work"));
|
|
52
|
+
}
|
|
53
|
+
if (n.status === "todo" && n.children.some((c) => c.status === "doing" || c.status === "done")) {
|
|
54
|
+
diagnostics.push(diagnostic("I003", n.line, "Children have started; consider marking this node doing"));
|
|
55
|
+
}
|
|
56
|
+
const p = progress.get(n);
|
|
57
|
+
return {
|
|
58
|
+
line: n.line,
|
|
59
|
+
id: n.id,
|
|
60
|
+
progress: p.total > 0 ? p : null,
|
|
61
|
+
ready: n.status === "todo" && deps.every((d) => d.target.status === "done" || d.target.status === "dropped"),
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
diagnostics.sort((a, b) => a.line - b.line);
|
|
65
|
+
return { nodes, diagnostics };
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=derive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive.js","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"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"}
|
package/dist/edit.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { EdgeKind, Status } from "./types.ts";
|
|
2
|
+
export type EditOp = {
|
|
3
|
+
op: "set-status";
|
|
4
|
+
id: string;
|
|
5
|
+
status: Status;
|
|
6
|
+
} | {
|
|
7
|
+
op: "set-title";
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
}
|
|
11
|
+
/** Sets one attribute; `null` (and `false` for `star`) removes the key. */
|
|
12
|
+
| {
|
|
13
|
+
op: "set-attr";
|
|
14
|
+
id: string;
|
|
15
|
+
key: string;
|
|
16
|
+
value: AttrValue | null;
|
|
17
|
+
} | {
|
|
18
|
+
op: "add-node";
|
|
19
|
+
parent: string | null;
|
|
20
|
+
title: string;
|
|
21
|
+
status?: Status;
|
|
22
|
+
id?: string;
|
|
23
|
+
} | {
|
|
24
|
+
op: "remove-node";
|
|
25
|
+
id: string;
|
|
26
|
+
recursive?: boolean;
|
|
27
|
+
} | {
|
|
28
|
+
op: "rename-id";
|
|
29
|
+
id: string;
|
|
30
|
+
to: string;
|
|
31
|
+
} | {
|
|
32
|
+
op: "add-edge";
|
|
33
|
+
from: string;
|
|
34
|
+
kind: EdgeKind;
|
|
35
|
+
to: string;
|
|
36
|
+
label?: string;
|
|
37
|
+
} | {
|
|
38
|
+
op: "remove-edge";
|
|
39
|
+
from: string;
|
|
40
|
+
kind: EdgeKind;
|
|
41
|
+
to: string;
|
|
42
|
+
}
|
|
43
|
+
/** Reserved: moving a subtree needs re-indentation of notes and children. Not implemented in 0.1. */
|
|
44
|
+
| {
|
|
45
|
+
op: "move-node";
|
|
46
|
+
id: string;
|
|
47
|
+
parent: string | null;
|
|
48
|
+
};
|
|
49
|
+
export interface EditResult {
|
|
50
|
+
source: string;
|
|
51
|
+
/** ID of the node an add-node created. */
|
|
52
|
+
id?: string;
|
|
53
|
+
/** Old → new IDs from rename-id, so callers can retarget sidecar data (threads). */
|
|
54
|
+
renamed?: {
|
|
55
|
+
from: string;
|
|
56
|
+
to: string;
|
|
57
|
+
};
|
|
58
|
+
/** IDs removed by remove-node (the node and its descendants). */
|
|
59
|
+
removed?: string[];
|
|
60
|
+
}
|
|
61
|
+
export declare class EditError extends Error {
|
|
62
|
+
}
|
|
63
|
+
export type AttrValue = string | number | boolean | (string | number | boolean)[];
|
|
64
|
+
export declare function applyEdit(source: string, edit: EditOp): EditResult;
|
|
65
|
+
export declare function formatAttrs(attrs: Record<string, unknown>): string;
|
|
66
|
+
/** `_` + 6 random base32 characters, unique among `taken`. */
|
|
67
|
+
export declare function generateId(taken: {
|
|
68
|
+
has(id: string): boolean;
|
|
69
|
+
}): string;
|
package/dist/edit.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// Line-level edit operations shared by the CLI, agents and the UI.
|
|
2
|
+
//
|
|
3
|
+
// Every operation rewrites only the lines it touches, so comments, blank lines and
|
|
4
|
+
// formatting elsewhere survive and a single change stays a small diff. After each
|
|
5
|
+
// edit the result is re-parsed; an edit that introduces new errors is rejected.
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { parse as parseLine } from "./generated/line-parser.js";
|
|
8
|
+
import { EDGE_KINDS, GEN_ID, HAND_ID, parse, splitIndent } from "./parse.js";
|
|
9
|
+
export class EditError extends Error {
|
|
10
|
+
}
|
|
11
|
+
const ATTR_KEY = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
12
|
+
const SYMBOL = {
|
|
13
|
+
todo: " ", doing: "/", done: "x", dropped: "-", blocked: "!", idea: "?",
|
|
14
|
+
};
|
|
15
|
+
export function applyEdit(source, edit) {
|
|
16
|
+
const doc = parse(source);
|
|
17
|
+
if (doc.diagnostics.some((d) => d.code === "E012")) {
|
|
18
|
+
throw new EditError("Front matter is invalid; fix it before editing");
|
|
19
|
+
}
|
|
20
|
+
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
21
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
22
|
+
const trailingNewline = lines[lines.length - 1] === "";
|
|
23
|
+
if (trailingNewline)
|
|
24
|
+
lines.pop();
|
|
25
|
+
const result = run(doc, lines, edit);
|
|
26
|
+
const out = lines.join(eol) + (trailingNewline || lines.length > 0 ? eol : "");
|
|
27
|
+
const errorsBefore = countErrors(doc);
|
|
28
|
+
const after = parse(out);
|
|
29
|
+
if (countErrors(after) > errorsBefore) {
|
|
30
|
+
const added = after.diagnostics.find((d) => d.level === "error");
|
|
31
|
+
throw new EditError(`Edit would introduce ${added.code}: ${added.message}`);
|
|
32
|
+
}
|
|
33
|
+
return { ...result, source: out };
|
|
34
|
+
}
|
|
35
|
+
function run(doc, lines, edit) {
|
|
36
|
+
const index = indexNodes(doc);
|
|
37
|
+
const find = (id) => {
|
|
38
|
+
const node = index.byId.get(id);
|
|
39
|
+
if (!node)
|
|
40
|
+
throw new EditError(`Unknown node "${id}"`);
|
|
41
|
+
return node;
|
|
42
|
+
};
|
|
43
|
+
switch (edit.op) {
|
|
44
|
+
case "set-status": {
|
|
45
|
+
const node = find(edit.id);
|
|
46
|
+
const i = node.line - 1;
|
|
47
|
+
lines[i] = lines[i].replace(/^(\s*[-*]\s+\[)[^\]](\])/, `$1${SYMBOL[edit.status]}$2`);
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
case "set-title": {
|
|
51
|
+
const node = find(edit.id);
|
|
52
|
+
checkTitle(edit.title);
|
|
53
|
+
lines[node.line - 1] = rewriteNode(lines[node.line - 1], edit.title, node.attrs, node.id);
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
case "set-attr": {
|
|
57
|
+
const node = find(edit.id);
|
|
58
|
+
if (!ATTR_KEY.test(edit.key))
|
|
59
|
+
throw new EditError(`Invalid attribute key "${edit.key}"`);
|
|
60
|
+
const scalar = (v) => typeof v === "string" || typeof v === "boolean" || (typeof v === "number" && Number.isFinite(v));
|
|
61
|
+
const v = edit.value;
|
|
62
|
+
if (v !== null && !scalar(v) && !(Array.isArray(v) && v.every(scalar))) {
|
|
63
|
+
throw new EditError("Attribute value must be a string, number, boolean or list of those");
|
|
64
|
+
}
|
|
65
|
+
if (typeof v === "string" && /[\r\n]/.test(v))
|
|
66
|
+
throw new EditError("Attribute value must be a single line");
|
|
67
|
+
if (edit.key === "star" && v !== null && typeof v !== "boolean")
|
|
68
|
+
throw new EditError("star must be true or false");
|
|
69
|
+
// Key order is kept; a new key goes last. Unstarring removes the key instead of writing false.
|
|
70
|
+
const attrs = { ...node.attrs };
|
|
71
|
+
if (v === null || (edit.key === "star" && v === false))
|
|
72
|
+
delete attrs[edit.key];
|
|
73
|
+
else
|
|
74
|
+
attrs[edit.key] = v;
|
|
75
|
+
lines[node.line - 1] = rewriteNode(lines[node.line - 1], node.title, attrs, node.id);
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
case "add-node": {
|
|
79
|
+
checkTitle(edit.title);
|
|
80
|
+
const parent = edit.parent === null ? null : find(edit.parent);
|
|
81
|
+
const id = edit.id ?? uniqueId(slug(edit.title), index.byId);
|
|
82
|
+
if (!HAND_ID.test(id) && !GEN_ID.test(id))
|
|
83
|
+
throw new EditError(`Invalid ID "${id}"`);
|
|
84
|
+
if (index.byId.has(id))
|
|
85
|
+
throw new EditError(`ID "${id}" already exists`);
|
|
86
|
+
const siblings = parent ? parent.children : doc.nodes;
|
|
87
|
+
const indent = siblings.length > 0
|
|
88
|
+
? splitIndent(lines[siblings[0].line - 1]).indent
|
|
89
|
+
: parent ? splitIndent(lines[parent.line - 1]).indent + 2 : 0;
|
|
90
|
+
// Insert after the last line of the parent's subtree (or of the last root).
|
|
91
|
+
const anchor = parent ?? doc.nodes[doc.nodes.length - 1];
|
|
92
|
+
const at = anchor ? lastLine(anchor) : bodyStart(lines);
|
|
93
|
+
lines.splice(at, 0, nodeLine(indent, SYMBOL[edit.status ?? "todo"], edit.title, {}, id));
|
|
94
|
+
return { id };
|
|
95
|
+
}
|
|
96
|
+
case "remove-node": {
|
|
97
|
+
const node = find(edit.id);
|
|
98
|
+
if (node.children.length > 0 && !edit.recursive) {
|
|
99
|
+
throw new EditError(`Node "${edit.id}" has children; pass recursive to remove the subtree`);
|
|
100
|
+
}
|
|
101
|
+
const removed = subtree(node).map((n) => n.id).filter((id) => id !== null);
|
|
102
|
+
const gone = new Set(removed);
|
|
103
|
+
lines.splice(node.line - 1, lastLine(node) - node.line + 1);
|
|
104
|
+
// rewriteEdges re-reads lines, so it is safe after the splice shifted them.
|
|
105
|
+
rewriteEdges(lines, (from, _kind, to) => (gone.has(from) ? [] : to.filter((t) => !gone.has(t))));
|
|
106
|
+
return { removed };
|
|
107
|
+
}
|
|
108
|
+
case "rename-id": {
|
|
109
|
+
const node = find(edit.id);
|
|
110
|
+
if (!HAND_ID.test(edit.to))
|
|
111
|
+
throw new EditError(`Invalid ID "${edit.to}"`);
|
|
112
|
+
if (index.byId.has(edit.to))
|
|
113
|
+
throw new EditError(`ID "${edit.to}" already exists`);
|
|
114
|
+
const i = node.line - 1;
|
|
115
|
+
lines[i] = lines[i].replace(new RegExp(`\\^${escapeRe(edit.id)}\\s*$`), `^${edit.to}`);
|
|
116
|
+
rewriteEdges(lines, (_from, _kind, to) => to.map((t) => (t === edit.id ? edit.to : t)), (from) => from === edit.id ? edit.to : from);
|
|
117
|
+
return { renamed: { from: edit.id, to: edit.to } };
|
|
118
|
+
}
|
|
119
|
+
case "add-edge": {
|
|
120
|
+
find(edit.from);
|
|
121
|
+
find(edit.to);
|
|
122
|
+
if (!EDGE_KINDS.has(edit.kind))
|
|
123
|
+
throw new EditError(`Unknown edge kind "${edit.kind}"`);
|
|
124
|
+
const label = edit.label ?? null;
|
|
125
|
+
if (doc.edges.some((e) => e.kind === edit.kind && e.label === label
|
|
126
|
+
&& ((e.from === edit.from && e.to === edit.to)
|
|
127
|
+
|| (edit.kind === "relates" && !label && e.from === edit.to && e.to === edit.from)))) {
|
|
128
|
+
throw new EditError("Edge already exists");
|
|
129
|
+
}
|
|
130
|
+
const hasEdges = lines.some((l) => isEdgeLine(l));
|
|
131
|
+
if (!hasEdges && lines.length > 0 && lines[lines.length - 1].trim() !== "")
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push(edgeLine(edit.from, edit.kind, [edit.to], edit.label ?? null));
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
case "remove-edge": {
|
|
137
|
+
let hit = false;
|
|
138
|
+
rewriteEdges(lines, (from, kind, to) => {
|
|
139
|
+
if (from !== edit.from || kind !== edit.kind)
|
|
140
|
+
return to;
|
|
141
|
+
const kept = to.filter((t) => t !== edit.to);
|
|
142
|
+
hit ||= kept.length !== to.length;
|
|
143
|
+
return kept;
|
|
144
|
+
});
|
|
145
|
+
if (!hit)
|
|
146
|
+
throw new EditError("Edge not found");
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
case "move-node":
|
|
150
|
+
throw new EditError("move-node is not implemented yet");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function indexNodes(doc) {
|
|
154
|
+
const byId = new Map();
|
|
155
|
+
const walk = (nodes) => {
|
|
156
|
+
for (const n of nodes) {
|
|
157
|
+
if (n.id !== null && !byId.has(n.id))
|
|
158
|
+
byId.set(n.id, n);
|
|
159
|
+
walk(n.children);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
walk(doc.nodes);
|
|
163
|
+
return { byId };
|
|
164
|
+
}
|
|
165
|
+
function subtree(node) {
|
|
166
|
+
return [node, ...node.children.flatMap(subtree)];
|
|
167
|
+
}
|
|
168
|
+
/** Last 1-based line that belongs to the node: itself, its notes and its descendants. */
|
|
169
|
+
function lastLine(node) {
|
|
170
|
+
let last = node.line;
|
|
171
|
+
for (const n of node.notes)
|
|
172
|
+
last = Math.max(last, n.line);
|
|
173
|
+
for (const c of node.children)
|
|
174
|
+
last = Math.max(last, lastLine(c));
|
|
175
|
+
return last;
|
|
176
|
+
}
|
|
177
|
+
function bodyStart(lines) {
|
|
178
|
+
if (lines[0]?.trimEnd() !== "---")
|
|
179
|
+
return 0;
|
|
180
|
+
const end = lines.findIndex((l, i) => i > 0 && l.trimEnd() === "---");
|
|
181
|
+
return end + 1;
|
|
182
|
+
}
|
|
183
|
+
function countErrors(doc) {
|
|
184
|
+
return doc.diagnostics.filter((d) => d.level === "error").length;
|
|
185
|
+
}
|
|
186
|
+
function checkTitle(title) {
|
|
187
|
+
if (title.trim() === "")
|
|
188
|
+
throw new EditError("Title must not be empty");
|
|
189
|
+
if (/[\r\n]/.test(title))
|
|
190
|
+
throw new EditError("Title must be a single line");
|
|
191
|
+
}
|
|
192
|
+
/** Rebuilds a node line, keeping its indentation, list marker and status symbol as written. */
|
|
193
|
+
function rewriteNode(line, title, attrs, id) {
|
|
194
|
+
const prefix = /^(\s*[-*]\s+\[[^\]]\])/.exec(line)?.[1];
|
|
195
|
+
if (!prefix)
|
|
196
|
+
throw new EditError("Not a node line");
|
|
197
|
+
let text = `${prefix} ${escapeTitle(title)}`;
|
|
198
|
+
if (Object.keys(attrs).length > 0)
|
|
199
|
+
text += ` ${formatAttrs(attrs)}`;
|
|
200
|
+
if (id)
|
|
201
|
+
text += ` ^${id}`;
|
|
202
|
+
return text;
|
|
203
|
+
}
|
|
204
|
+
function nodeLine(indent, symbol, title, attrs, id) {
|
|
205
|
+
let text = `${" ".repeat(indent)}- [${symbol}] ${escapeTitle(title)}`;
|
|
206
|
+
if (Object.keys(attrs).length > 0)
|
|
207
|
+
text += ` ${formatAttrs(attrs)}`;
|
|
208
|
+
if (id)
|
|
209
|
+
text += ` ^${id}`;
|
|
210
|
+
return text;
|
|
211
|
+
}
|
|
212
|
+
/** Escape only what the parser would otherwise strip as a trailing `^id` or `{attrs}`. */
|
|
213
|
+
function escapeTitle(title) {
|
|
214
|
+
let t = title.trim();
|
|
215
|
+
t = t.replace(/(^|\s)\^(\S+)$/, "$1\\^$2");
|
|
216
|
+
if (t.endsWith("}")) {
|
|
217
|
+
const open = t.lastIndexOf("{");
|
|
218
|
+
if (open >= 0 && (open === 0 || /\s/.test(t[open - 1])))
|
|
219
|
+
t = t.slice(0, open) + "\\" + t.slice(open);
|
|
220
|
+
}
|
|
221
|
+
return t;
|
|
222
|
+
}
|
|
223
|
+
export function formatAttrs(attrs) {
|
|
224
|
+
const value = (v) => {
|
|
225
|
+
if (Array.isArray(v))
|
|
226
|
+
return `[${v.map(value).join(", ")}]`;
|
|
227
|
+
if (typeof v === "string") {
|
|
228
|
+
// Plain only when YAML would read it back as the same string.
|
|
229
|
+
const plain = /^[\w./@-][\w ./@-]*$/.test(v) && v.trim() === v
|
|
230
|
+
&& !/^(true|false|null|~|yes|no|on|off|[-+]?(\d[\d_]*)?\.?\d+([eE][-+]?\d+)?|0x[\da-f]+|0o[0-7]+)$/i.test(v);
|
|
231
|
+
return plain ? v : JSON.stringify(v);
|
|
232
|
+
}
|
|
233
|
+
return JSON.stringify(v);
|
|
234
|
+
};
|
|
235
|
+
return `{${Object.entries(attrs).map(([k, v]) => `${k}: ${value(v)}`).join(", ")}}`;
|
|
236
|
+
}
|
|
237
|
+
function edgeLine(from, kind, to, label) {
|
|
238
|
+
return `${from} ${kind} ${to.join(", ")}${label ? `: ${label}` : ""}`;
|
|
239
|
+
}
|
|
240
|
+
function isEdgeLine(line) {
|
|
241
|
+
if (/^\s/.test(line))
|
|
242
|
+
return false;
|
|
243
|
+
try {
|
|
244
|
+
return parseLine(line.trimEnd(), { startRule: "Line" }).type === "edge";
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Rewrites every edge statement. `targets` returns the new target list (empty deletes the
|
|
252
|
+
* line); `source` optionally renames the source ID. Lines that do not change are left as is.
|
|
253
|
+
*/
|
|
254
|
+
function rewriteEdges(lines, targets, source = (f) => f) {
|
|
255
|
+
for (let i = lines.length - 1; i >= bodyStart(lines); i--) {
|
|
256
|
+
const line = lines[i];
|
|
257
|
+
if (!isEdgeLine(line))
|
|
258
|
+
continue;
|
|
259
|
+
const e = parseLine(line.trimEnd(), { startRule: "Line" });
|
|
260
|
+
if (e.from.file !== null || e.to.some((t) => t.file !== null))
|
|
261
|
+
continue;
|
|
262
|
+
const oldTo = e.to.map((t) => t.id);
|
|
263
|
+
const newTo = targets(e.from.id, e.kind, oldTo);
|
|
264
|
+
const newFrom = source(e.from.id);
|
|
265
|
+
if (newTo.length === 0) {
|
|
266
|
+
lines.splice(i, 1);
|
|
267
|
+
}
|
|
268
|
+
else if (newFrom !== e.from.id || newTo.join(",") !== oldTo.join(",")) {
|
|
269
|
+
lines[i] = edgeLine(newFrom, e.kind, newTo, e.label);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function slug(title) {
|
|
274
|
+
return title
|
|
275
|
+
.toLowerCase()
|
|
276
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
277
|
+
.replace(/^-+|-+$/g, "")
|
|
278
|
+
.slice(0, 40)
|
|
279
|
+
.replace(/-+$/, "");
|
|
280
|
+
}
|
|
281
|
+
function uniqueId(base, taken) {
|
|
282
|
+
if (base === "")
|
|
283
|
+
return generateId(taken);
|
|
284
|
+
if (!taken.has(base))
|
|
285
|
+
return base;
|
|
286
|
+
for (let n = 2;; n++)
|
|
287
|
+
if (!taken.has(`${base}-${n}`))
|
|
288
|
+
return `${base}-${n}`;
|
|
289
|
+
}
|
|
290
|
+
const BASE32 = "abcdefghijklmnopqrstuvwxyz234567";
|
|
291
|
+
/** `_` + 6 random base32 characters, unique among `taken`. */
|
|
292
|
+
export function generateId(taken) {
|
|
293
|
+
for (;;) {
|
|
294
|
+
const id = "_" + [...randomBytes(6)].map((b) => BASE32[b % 32]).join("");
|
|
295
|
+
if (!taken.has(id))
|
|
296
|
+
return id;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function escapeRe(s) {
|
|
300
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=edit.js.map
|
package/dist/edit.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit.js","sourceRoot":"","sources":["../src/edit.ts"],"names":[],"mappings":"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"}
|