@k-l-lambda/portolan 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ import type { RhumbDocument } from "./types.ts";
2
+ export interface LineTime {
3
+ /** Unix seconds. */
4
+ time: number;
5
+ /** `commit`: from git blame; `local`: uncommitted change, dated by file mtime. */
6
+ source: "commit" | "local";
7
+ sha: string | null;
8
+ }
9
+ export interface Commit {
10
+ sha: string;
11
+ time: number;
12
+ summary: string;
13
+ }
14
+ export interface FileHistory {
15
+ /** Repository root, or null when the file is not inside a git work tree. */
16
+ repo: string | null;
17
+ head: string | null;
18
+ tracked: boolean;
19
+ /** File modification time, unix seconds. */
20
+ mtime: number;
21
+ /** Lines that differ from HEAD (1-based). */
22
+ dirtyLines: number;
23
+ /** Most recent commit that touched this file. */
24
+ lastCommit: Commit | null;
25
+ /** Index 0 is line 1. */
26
+ lines: LineTime[];
27
+ commits: Record<string, Commit>;
28
+ }
29
+ export interface NodeTime {
30
+ line: number;
31
+ /** Unix seconds: the time used for freshness. */
32
+ time: number;
33
+ /**
34
+ * `commit` / `local`: the node line's own time (see LineTime). `link`: the node links to a
35
+ * dated entry (e.g. `diary:2026/1008`) that is earlier than its commit time, so the
36
+ * linked date is a better estimate of when the work happened than when the map was committed.
37
+ * `children`: the node has no links but has child nodes; it takes the newest child's time.
38
+ */
39
+ source: "commit" | "local" | "link" | "children";
40
+ /** Time of the node's own line, kept so the UI can show both. */
41
+ lineTime: LineTime;
42
+ /** The dated link that set `time`, when source is `link`. */
43
+ link: {
44
+ date: string;
45
+ target: string;
46
+ } | null;
47
+ /** The child whose time was used, when source is `children`. */
48
+ child: {
49
+ line: number;
50
+ id: string | null;
51
+ } | null;
52
+ }
53
+ /** Parses `git blame --line-porcelain` output. */
54
+ export declare function parseBlame(out: string): {
55
+ lines: {
56
+ sha: string;
57
+ time: number;
58
+ }[];
59
+ commits: Record<string, Commit>;
60
+ };
61
+ /** Tracks git history for files; one instance per server. */
62
+ export declare class HistoryTracker {
63
+ private repoOf;
64
+ private heads;
65
+ private cache;
66
+ private polling;
67
+ /** Repository root containing `dir`, or null. */
68
+ repo(dir: string): Promise<string | null>;
69
+ private head;
70
+ /** Re-reads HEAD of every known repo; returns the repos whose HEAD changed. */
71
+ pollHeads(): Promise<string[]>;
72
+ file(abs: string, version: string): Promise<FileHistory>;
73
+ private compute;
74
+ }
75
+ /**
76
+ * Date in a link path: `2026/1008`, `2026-10-08` or `2026/10/08` (a separator after the year
77
+ * is required, so issue numbers like `20251201` are not dates). Returns the date and the end
78
+ * of that local day in unix seconds, or null.
79
+ */
80
+ export declare function linkDate(target: string): {
81
+ date: string;
82
+ time: number;
83
+ } | null;
84
+ /**
85
+ * Update time of each node: the time of the node's own line, unless the node links to a
86
+ * dated entry whose day is before the day of the line's own time (its commit, or the file
87
+ * mtime for an uncommitted line); then the latest such linked date wins (end of that day).
88
+ * A link dated the same day does not count as earlier.
89
+ */
90
+ export declare function nodeTimes(doc: RhumbDocument, history: FileHistory): NodeTime[];
@@ -0,0 +1,182 @@
1
+ // Per-line update times for .rhumb files, from git history.
2
+ //
3
+ // A line changed in the working tree (or a file that is untracked or outside any repo)
4
+ // is dated by the file's modification time; every other line by the commit that last
5
+ // touched it (`git blame`). Results are cached per file content, mtime and repo HEAD.
6
+ import { execFile } from "node:child_process";
7
+ import { statSync } from "node:fs";
8
+ import { basename, dirname } from "node:path";
9
+ const ZERO_SHA = "0".repeat(40);
10
+ const GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" };
11
+ function git(cwd, args) {
12
+ return new Promise((resolve, reject) => {
13
+ execFile("git", args, { cwd, env: GIT_ENV, timeout: 10_000, maxBuffer: 32 << 20 }, (err, stdout) => err ? reject(err) : resolve(stdout));
14
+ });
15
+ }
16
+ /** Parses `git blame --line-porcelain` output. */
17
+ export function parseBlame(out) {
18
+ const lines = [];
19
+ const commits = {};
20
+ let sha = "";
21
+ let time = 0;
22
+ let summary = "";
23
+ for (const row of out.split("\n")) {
24
+ const header = /^([0-9a-f]{40}) \d+ (\d+)/.exec(row);
25
+ if (header) {
26
+ sha = header[1];
27
+ continue;
28
+ }
29
+ if (row.startsWith("committer-time "))
30
+ time = Number(row.slice(15));
31
+ else if (row.startsWith("summary "))
32
+ summary = row.slice(8);
33
+ else if (row.startsWith("\t")) {
34
+ lines.push({ sha, time });
35
+ if (sha !== ZERO_SHA && !commits[sha])
36
+ commits[sha] = { sha, time, summary };
37
+ }
38
+ }
39
+ return { lines, commits };
40
+ }
41
+ /** Tracks git history for files; one instance per server. */
42
+ export class HistoryTracker {
43
+ repoOf = new Map();
44
+ heads = new Map();
45
+ cache = new Map();
46
+ polling = false;
47
+ /** Repository root containing `dir`, or null. */
48
+ repo(dir) {
49
+ let p = this.repoOf.get(dir);
50
+ if (!p) {
51
+ p = git(dir, ["rev-parse", "--show-toplevel"]).then((s) => s.trim() || null, () => null);
52
+ this.repoOf.set(dir, p);
53
+ }
54
+ return p;
55
+ }
56
+ async head(repo) {
57
+ if (!this.heads.has(repo)) {
58
+ this.heads.set(repo, await git(repo, ["rev-parse", "HEAD"]).then((s) => s.trim(), () => null));
59
+ }
60
+ return this.heads.get(repo);
61
+ }
62
+ /** Re-reads HEAD of every known repo; returns the repos whose HEAD changed. */
63
+ async pollHeads() {
64
+ if (this.polling)
65
+ return [];
66
+ this.polling = true;
67
+ try {
68
+ const changed = [];
69
+ for (const [repo, old] of this.heads) {
70
+ const now = await git(repo, ["rev-parse", "HEAD"]).then((s) => s.trim(), () => null);
71
+ if (now !== old) {
72
+ this.heads.set(repo, now);
73
+ changed.push(repo);
74
+ }
75
+ }
76
+ return changed;
77
+ }
78
+ finally {
79
+ this.polling = false;
80
+ }
81
+ }
82
+ async file(abs, version) {
83
+ const mtime = Math.floor(statSync(abs).mtimeMs / 1000);
84
+ const repo = await this.repo(dirname(abs));
85
+ const head = repo ? await this.head(repo) : null;
86
+ const key = `${version}:${mtime}:${head}`;
87
+ const hit = this.cache.get(abs);
88
+ if (hit?.key === key)
89
+ return hit.value;
90
+ const value = await this.compute(abs, repo, head, mtime);
91
+ this.cache.set(abs, { key, value });
92
+ return value;
93
+ }
94
+ async compute(abs, repo, head, mtime) {
95
+ const local = (count) => Array.from({ length: count }, () => ({ time: mtime, source: "local", sha: null }));
96
+ const base = {
97
+ repo, head, tracked: false, mtime, dirtyLines: 0, lastCommit: null, lines: [], commits: {},
98
+ };
99
+ if (!repo || !head)
100
+ return base;
101
+ let blame;
102
+ try {
103
+ blame = parseBlame(await git(dirname(abs), ["blame", "--line-porcelain", "--", basename(abs)]));
104
+ }
105
+ catch {
106
+ return base; // untracked: every line is local
107
+ }
108
+ const lines = blame.lines.map((l) => l.sha === ZERO_SHA ? { time: mtime, source: "local", sha: null } : { time: l.time, source: "commit", sha: l.sha });
109
+ const last = await git(dirname(abs), ["log", "-1", "--format=%H%x00%ct%x00%s", "--", basename(abs)])
110
+ .then((s) => s.trim().split("\0"), () => []);
111
+ return {
112
+ ...base,
113
+ tracked: true,
114
+ dirtyLines: lines.filter((l) => l.source === "local").length,
115
+ lastCommit: last.length === 3 ? { sha: last[0], time: Number(last[1]), summary: last[2] } : null,
116
+ lines: lines.length > 0 ? lines : local(0),
117
+ commits: blame.commits,
118
+ };
119
+ }
120
+ }
121
+ /**
122
+ * Date in a link path: `2026/1008`, `2026-10-08` or `2026/10/08` (a separator after the year
123
+ * is required, so issue numbers like `20251201` are not dates). Returns the date and the end
124
+ * of that local day in unix seconds, or null.
125
+ */
126
+ export function linkDate(target) {
127
+ const path = target.split("#")[0];
128
+ const re = /(?:^|[^0-9])(20\d\d|19\d\d)[-/](\d\d)[-/]?(\d\d)(?![0-9])/g;
129
+ for (let m; (m = re.exec(path));) {
130
+ const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
131
+ const day = new Date(y, mo - 1, d, 23, 59, 59);
132
+ if (day.getFullYear() === y && day.getMonth() === mo - 1 && day.getDate() === d) {
133
+ return { date: `${m[1]}-${m[2]}-${m[3]}`, time: Math.floor(day.getTime() / 1000) };
134
+ }
135
+ }
136
+ return null;
137
+ }
138
+ /** Local calendar day of a unix time, as `YYYY-MM-DD`. */
139
+ function localDay(time) {
140
+ const d = new Date(time * 1000);
141
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
142
+ }
143
+ /**
144
+ * Update time of each node: the time of the node's own line, unless the node links to a
145
+ * dated entry whose day is before the day of the line's own time (its commit, or the file
146
+ * mtime for an uncommitted line); then the latest such linked date wins (end of that day).
147
+ * A link dated the same day does not count as earlier.
148
+ */
149
+ export function nodeTimes(doc, history) {
150
+ const out = [];
151
+ // Returns the node's time after its children are computed, so parents can use them.
152
+ const visit = (n) => {
153
+ const slot = out.length;
154
+ out.push(null); // keep pre-order output
155
+ const kids = n.children.map(visit);
156
+ const lineTime = history.lines[n.line - 1] ?? { time: history.mtime, source: "local", sha: null };
157
+ const base = { line: n.line, lineTime, link: null, child: null };
158
+ let t;
159
+ if (n.anchors.length === 0 && kids.length > 0) {
160
+ // No links: the node is as fresh as its newest child.
161
+ const newest = kids.reduce((a, b) => (b.time > a.time ? b : a));
162
+ const child = n.children[kids.indexOf(newest)];
163
+ t = { ...base, time: newest.time, source: "children", child: { line: child.line, id: child.id } };
164
+ }
165
+ else {
166
+ let link = null;
167
+ for (const a of n.anchors) {
168
+ const d = linkDate(a.kind === "url" ? a.target : a.path ?? a.target);
169
+ if (d && (!link || d.time > link.time))
170
+ link = { ...d, target: a.target };
171
+ }
172
+ t = link !== null && link.date < localDay(lineTime.time)
173
+ ? { ...base, time: link.time, source: "link", link: { date: link.date, target: link.target } }
174
+ : { ...base, time: lineTime.time, source: lineTime.source };
175
+ }
176
+ out[slot] = t;
177
+ return t;
178
+ };
179
+ doc.nodes.forEach(visit);
180
+ return out;
181
+ }
182
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export { isStarred, parse, RHUMB_VERSION } from "./parse.ts";
2
+ export { applyEdit, EditError, type EditOp, type EditResult } from "./edit.ts";
3
+ export { format, type FormatResult } from "./format.ts";
4
+ export { derive, type Derived, type NodeState } from "./derive.ts";
5
+ export { checkAnchors, contextFor, excerpt, githubSlug, resolveAnchor, type ResolveContext, type Resolved } from "./resolve.ts";
6
+ export { foldThreads, ThreadStore, type Message, type Thread, type ThreadAction, type ThreadEvent } from "./threads.ts";
7
+ export { createRhumbServer, versionOf, type ServerOptions } from "./server.ts";
8
+ export { HistoryTracker, linkDate, nodeTimes, parseBlame, type Commit, type FileHistory, type LineTime, type NodeTime } from "./history.ts";
9
+ export type * from "./types.ts";
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { isStarred, parse, RHUMB_VERSION } from "./parse.js";
2
+ export { applyEdit, EditError } from "./edit.js";
3
+ export { format } from "./format.js";
4
+ export { derive } from "./derive.js";
5
+ export { checkAnchors, contextFor, excerpt, githubSlug, resolveAnchor } from "./resolve.js";
6
+ export { foldThreads, ThreadStore } from "./threads.js";
7
+ export { createRhumbServer, versionOf } from "./server.js";
8
+ export { HistoryTracker, linkDate, nodeTimes, parseBlame } from "./history.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAgC,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAqB,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,MAAM,EAAgC,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAsC,MAAM,cAAc,CAAC;AAChI,OAAO,EAAE,WAAW,EAAE,WAAW,EAAkE,MAAM,cAAc,CAAC;AACxH,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAsB,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAA+D,MAAM,cAAc,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { Diagnostic, EdgeKind, RhumbDocument, RhumbNode } from "./types.ts";
2
+ export declare const RHUMB_VERSION = "0.1";
3
+ export declare const EDGE_KINDS: Set<EdgeKind>;
4
+ export declare const HAND_ID: RegExp;
5
+ export declare const GEN_ID: RegExp;
6
+ export declare function diagnostic(code: string, line: number, message: string): Diagnostic;
7
+ export interface ParseOptions {
8
+ /** Used as the default title when front matter has none. */
9
+ fileName?: string;
10
+ }
11
+ export declare function parse(source: string, options?: ParseOptions): RhumbDocument;
12
+ /** Leading whitespace width with tabs advancing to the next multiple of 4 (CommonMark). */
13
+ export declare function splitIndent(line: string): {
14
+ indent: number;
15
+ rest: string;
16
+ };
17
+ export interface Fragment {
18
+ slug: string | null;
19
+ text_fragment: string | null;
20
+ line_prefix: string | null;
21
+ line_offset: number | null;
22
+ lines: [number, number] | null;
23
+ error: string | null;
24
+ }
25
+ /**
26
+ * Parses the part after `#`, narrowing left to right:
27
+ * `slug`, then `:~:text=…` (rest of the fragment) or `^=prefix` / ``^=`prefix` ``, then `+L3` / `-L2`;
28
+ * or an absolute `L42` / `L42-L50`.
29
+ * GitHub slugs are lowercase and never contain `+ ^ = \` : ~`, so these separators are unambiguous.
30
+ */
31
+ export declare function parseFragment(raw: string): Fragment;
32
+ /** A node is starred only with `{star: true}`; any other value (W010) counts as not starred. */
33
+ export declare function isStarred(node: Pick<RhumbNode, "attrs">): boolean;