mellos-mapping 0.18.0 → 0.20.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.
- package/README.md +32 -4
- package/README.zh-CN.md +29 -4
- package/dist/server.mjs +185 -62
- package/dist/watch.mjs +306 -216
- package/lib/domain/ops.d.ts +112 -0
- package/lib/domain/ops.js +253 -0
- package/lib/domain/types.d.ts +242 -0
- package/lib/domain/types.js +122 -0
- package/lib/render/render.d.ts +102 -0
- package/lib/render/render.js +859 -0
- package/lib/semantics/semantics.d.ts +120 -0
- package/lib/semantics/semantics.js +256 -0
- package/lib/store/format.d.ts +50 -0
- package/lib/store/format.js +215 -0
- package/lib/store/store.d.ts +96 -0
- package/lib/store/store.js +281 -0
- package/package.json +30 -2
- package/scripts/codex-register.mjs +1 -1
- package/scripts/open-pane.mjs +1 -1
package/dist/watch.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from 'node:module'; const require = createRequire(impor
|
|
|
3
3
|
|
|
4
4
|
// src/watch/watch.ts
|
|
5
5
|
import { realpathSync, statSync } from "node:fs";
|
|
6
|
-
import { join as join2 } from "node:path";
|
|
6
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
8
|
|
|
9
9
|
// src/domain/types.ts
|
|
@@ -199,13 +199,18 @@ function updateNode(map, input) {
|
|
|
199
199
|
return ok({ ...map, nodes: map.nodes.map((n) => n.id === input.id ? updated : n) });
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
// src/
|
|
202
|
+
// src/semantics/semantics.ts
|
|
203
203
|
var ZOOM_MIN = -4;
|
|
204
204
|
var ZOOM_MAX = 2;
|
|
205
205
|
var ZOOM_DEFAULT = 0;
|
|
206
206
|
function clampZoom(n) {
|
|
207
207
|
return Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, Math.round(n)));
|
|
208
208
|
}
|
|
209
|
+
function zoomMode(zoom) {
|
|
210
|
+
if (zoom >= 1) return "detail";
|
|
211
|
+
if (zoom <= -4) return "overview";
|
|
212
|
+
return "boxes";
|
|
213
|
+
}
|
|
209
214
|
function zoomLabel(zoom) {
|
|
210
215
|
switch (zoom) {
|
|
211
216
|
case 2:
|
|
@@ -224,6 +229,136 @@ function zoomLabel(zoom) {
|
|
|
224
229
|
return "overview";
|
|
225
230
|
}
|
|
226
231
|
}
|
|
232
|
+
function isNeutralKind(map) {
|
|
233
|
+
return map.kind !== void 0 && map.kind !== "dev";
|
|
234
|
+
}
|
|
235
|
+
function aggregateMap(map) {
|
|
236
|
+
if (map.groups.length === 0) return void 0;
|
|
237
|
+
const representative = /* @__PURE__ */ new Map();
|
|
238
|
+
for (const n of map.nodes) representative.set(n.id, n.group ?? n.id);
|
|
239
|
+
const nodes = map.groups.map((g) => {
|
|
240
|
+
const members = map.nodes.filter((n) => n.group === g.id);
|
|
241
|
+
const done = members.filter((n) => n.status === "done").length;
|
|
242
|
+
return {
|
|
243
|
+
id: g.id,
|
|
244
|
+
// neutral kinds document structure, not progress — no member counts
|
|
245
|
+
label: isNeutralKind(map) ? g.label : `${g.label} ${done}/${members.length}`,
|
|
246
|
+
layer: g.layer,
|
|
247
|
+
status: groupStatus(map, g.id)
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
for (const n of map.nodes) if (n.group === void 0) nodes.push(n);
|
|
251
|
+
const seen = /* @__PURE__ */ new Set();
|
|
252
|
+
const edges = [];
|
|
253
|
+
for (const e of map.edges) {
|
|
254
|
+
const from = representative.get(e.from);
|
|
255
|
+
const to = representative.get(e.to);
|
|
256
|
+
if (from === to || seen.has(`${from}->${to}`)) continue;
|
|
257
|
+
seen.add(`${from}->${to}`);
|
|
258
|
+
edges.push({ from, to });
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
...map.title !== void 0 ? { title: map.title } : {},
|
|
262
|
+
...map.kind !== void 0 ? { kind: map.kind } : {},
|
|
263
|
+
layers: map.layers,
|
|
264
|
+
groups: [],
|
|
265
|
+
lanes: map.lanes,
|
|
266
|
+
nodes,
|
|
267
|
+
edges
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function focusInfo(map, focusId) {
|
|
271
|
+
const layerNameOf = (layerId) => map.layers.find((l) => l.id === layerId)?.name ?? layerId;
|
|
272
|
+
const group = map.groups.find((g) => g.id === focusId);
|
|
273
|
+
if (group) {
|
|
274
|
+
const members = map.nodes.filter((n) => n.group === group.id);
|
|
275
|
+
const memberIds = new Set(members.map((n) => n.id));
|
|
276
|
+
const rep = (id) => {
|
|
277
|
+
const n = map.nodes.find((x) => x.id === id);
|
|
278
|
+
const owner = n.group !== void 0 ? map.groups.find((g) => g.id === n.group) : void 0;
|
|
279
|
+
return owner !== void 0 ? { id: owner.id, label: owner.label, status: groupStatus(map, owner.id) } : { id: n.id, label: n.label, status: n.status };
|
|
280
|
+
};
|
|
281
|
+
const dedupe = (refs) => {
|
|
282
|
+
const seen = /* @__PURE__ */ new Set();
|
|
283
|
+
const out = [];
|
|
284
|
+
for (const r of refs) {
|
|
285
|
+
if (seen.has(r.id)) continue;
|
|
286
|
+
seen.add(r.id);
|
|
287
|
+
out.push(r);
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
};
|
|
291
|
+
return {
|
|
292
|
+
kind: "group",
|
|
293
|
+
group,
|
|
294
|
+
status: groupStatus(map, group.id),
|
|
295
|
+
layerName: layerNameOf(group.layer),
|
|
296
|
+
members,
|
|
297
|
+
uses: dedupe(
|
|
298
|
+
map.edges.filter((e) => memberIds.has(e.from) && !memberIds.has(e.to)).map((e) => rep(e.to))
|
|
299
|
+
),
|
|
300
|
+
usedBy: dedupe(
|
|
301
|
+
map.edges.filter((e) => memberIds.has(e.to) && !memberIds.has(e.from)).map((e) => rep(e.from))
|
|
302
|
+
)
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const node = map.nodes.find((n) => n.id === focusId);
|
|
306
|
+
if (!node) return void 0;
|
|
307
|
+
const ref = (id, edgeLabel) => {
|
|
308
|
+
const n = map.nodes.find((x) => x.id === id);
|
|
309
|
+
return {
|
|
310
|
+
id,
|
|
311
|
+
label: n?.label ?? id,
|
|
312
|
+
status: n?.status ?? "planned",
|
|
313
|
+
...edgeLabel !== void 0 ? { edgeLabel } : {}
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
const laneLabel = node.lane !== void 0 ? map.lanes.find((l) => l.id === node.lane)?.label : void 0;
|
|
317
|
+
return {
|
|
318
|
+
kind: "node",
|
|
319
|
+
node,
|
|
320
|
+
layerName: layerNameOf(node.layer),
|
|
321
|
+
...laneLabel !== void 0 ? { laneLabel } : {},
|
|
322
|
+
uses: map.edges.filter((e) => e.from === node.id).map((e) => ref(e.to, e.label)),
|
|
323
|
+
usedBy: map.edges.filter((e) => e.to === node.id).map((e) => ref(e.from, e.label))
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function submapRefs(maps) {
|
|
327
|
+
const refs = /* @__PURE__ */ new Set();
|
|
328
|
+
for (const m of maps) {
|
|
329
|
+
for (const n of m?.nodes ?? []) if (n.submap !== void 0) refs.add(n.submap);
|
|
330
|
+
}
|
|
331
|
+
return refs;
|
|
332
|
+
}
|
|
333
|
+
function diveParent(entries, pageId) {
|
|
334
|
+
for (const [key, m] of entries) {
|
|
335
|
+
const node = m?.nodes.find((n) => n.submap === pageId);
|
|
336
|
+
if (node !== void 0) return { parent: key, label: node.label };
|
|
337
|
+
}
|
|
338
|
+
return void 0;
|
|
339
|
+
}
|
|
340
|
+
function mostRecentKey(keys, mtimeOf) {
|
|
341
|
+
let best;
|
|
342
|
+
let bestMtime = -Infinity;
|
|
343
|
+
for (const key of keys) {
|
|
344
|
+
const mtime = mtimeOf(key);
|
|
345
|
+
if (mtime !== void 0 && mtime > bestMtime) {
|
|
346
|
+
best = key;
|
|
347
|
+
bestMtime = mtime;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return best ?? keys[0];
|
|
351
|
+
}
|
|
352
|
+
function flipForSequence(map) {
|
|
353
|
+
if (map.kind !== "sequence") return map;
|
|
354
|
+
return {
|
|
355
|
+
...map,
|
|
356
|
+
layers: map.layers.map((l) => ({ ...l, rank: -l.rank })),
|
|
357
|
+
edges: map.edges.map((e) => ({ from: e.to, to: e.from, ...e.label !== void 0 ? { label: e.label } : {} }))
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/render/render.ts
|
|
227
362
|
var WIDE_RANGES = [
|
|
228
363
|
[4352, 4447],
|
|
229
364
|
// Hangul Jamo
|
|
@@ -488,9 +623,6 @@ function kindGlyph(kind, unicode) {
|
|
|
488
623
|
const pair = NODE_KIND_GLYPHS[kind];
|
|
489
624
|
return pair === void 0 ? void 0 : unicode ? pair[0] : pair[1];
|
|
490
625
|
}
|
|
491
|
-
function isNeutralKind(map) {
|
|
492
|
-
return map.kind !== void 0 && map.kind !== "dev";
|
|
493
|
-
}
|
|
494
626
|
function neutralSkin(unicode) {
|
|
495
627
|
return unicode ? { h: "\u2500", v: "\u2502", corners: ["\u256D", "\u256E", "\u2570", "\u256F"], style: "none" } : { h: "-", v: "|", corners: ["+", "+", "+", "+"], style: "none" };
|
|
496
628
|
}
|
|
@@ -500,21 +632,23 @@ var LEFT_MARGIN = 2;
|
|
|
500
632
|
var DETAIL_BUDGET = { innerMin: 22, innerMax: 32, noteRows: 3 };
|
|
501
633
|
var DETAIL_PLUS_BUDGET = { innerMin: 30, innerMax: 48, noteRows: 12 };
|
|
502
634
|
function zoomGeometry(zoom) {
|
|
635
|
+
const m = zoomMode(zoom);
|
|
636
|
+
const mode = m === "overview" ? "constellation" : m;
|
|
503
637
|
switch (zoom) {
|
|
504
638
|
case 2:
|
|
505
|
-
return { mode
|
|
639
|
+
return { mode, scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false, detail: DETAIL_PLUS_BUDGET };
|
|
506
640
|
case 1:
|
|
507
|
-
return { mode
|
|
641
|
+
return { mode, scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false, detail: DETAIL_BUDGET };
|
|
508
642
|
case 0:
|
|
509
|
-
return { mode
|
|
643
|
+
return { mode, scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false };
|
|
510
644
|
case -1:
|
|
511
|
-
return { mode
|
|
645
|
+
return { mode, scale: 0.85, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false };
|
|
512
646
|
case -2:
|
|
513
|
-
return { mode
|
|
647
|
+
return { mode, scale: 0.7, pad: 0, boxGap: BOX_GAP, breathe: 0, titleGap: 0, barGap: 1, bandCounts: false };
|
|
514
648
|
case -3:
|
|
515
|
-
return { mode
|
|
649
|
+
return { mode, scale: 0.55, pad: 0, boxGap: 1, breathe: 0, titleGap: 0, barGap: 1, bandCounts: true };
|
|
516
650
|
case -4:
|
|
517
|
-
return { mode
|
|
651
|
+
return { mode, scale: 0, pad: 0, boxGap: BOX_GAP, breathe: 0, titleGap: 0, barGap: 1, bandCounts: true };
|
|
518
652
|
}
|
|
519
653
|
}
|
|
520
654
|
var LABEL_BUDGET_MIN = 4;
|
|
@@ -567,41 +701,6 @@ function renderMapWindow(map, opts, viewport) {
|
|
|
567
701
|
hits: built.hits
|
|
568
702
|
};
|
|
569
703
|
}
|
|
570
|
-
function aggregateMap(map) {
|
|
571
|
-
if (map.groups.length === 0) return void 0;
|
|
572
|
-
const representative = /* @__PURE__ */ new Map();
|
|
573
|
-
for (const n of map.nodes) representative.set(n.id, n.group ?? n.id);
|
|
574
|
-
const nodes = map.groups.map((g) => {
|
|
575
|
-
const members = map.nodes.filter((n) => n.group === g.id);
|
|
576
|
-
const done = members.filter((n) => n.status === "done").length;
|
|
577
|
-
return {
|
|
578
|
-
id: g.id,
|
|
579
|
-
// neutral kinds document structure, not progress — no member counts
|
|
580
|
-
label: isNeutralKind(map) ? g.label : `${g.label} ${done}/${members.length}`,
|
|
581
|
-
layer: g.layer,
|
|
582
|
-
status: groupStatus(map, g.id)
|
|
583
|
-
};
|
|
584
|
-
});
|
|
585
|
-
for (const n of map.nodes) if (n.group === void 0) nodes.push(n);
|
|
586
|
-
const seen = /* @__PURE__ */ new Set();
|
|
587
|
-
const edges = [];
|
|
588
|
-
for (const e of map.edges) {
|
|
589
|
-
const from = representative.get(e.from);
|
|
590
|
-
const to = representative.get(e.to);
|
|
591
|
-
if (from === to || seen.has(`${from}->${to}`)) continue;
|
|
592
|
-
seen.add(`${from}->${to}`);
|
|
593
|
-
edges.push({ from, to });
|
|
594
|
-
}
|
|
595
|
-
return {
|
|
596
|
-
...map.title !== void 0 ? { title: map.title } : {},
|
|
597
|
-
...map.kind !== void 0 ? { kind: map.kind } : {},
|
|
598
|
-
layers: map.layers,
|
|
599
|
-
groups: [],
|
|
600
|
-
lanes: map.lanes,
|
|
601
|
-
nodes,
|
|
602
|
-
edges
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
704
|
var AGGREGATE_GEO = {
|
|
606
705
|
mode: "boxes",
|
|
607
706
|
scale: 1,
|
|
@@ -612,14 +711,6 @@ var AGGREGATE_GEO = {
|
|
|
612
711
|
barGap: 1,
|
|
613
712
|
bandCounts: false
|
|
614
713
|
};
|
|
615
|
-
function flipForSequence(map) {
|
|
616
|
-
if (map.kind !== "sequence") return map;
|
|
617
|
-
return {
|
|
618
|
-
...map,
|
|
619
|
-
layers: map.layers.map((l) => ({ ...l, rank: -l.rank })),
|
|
620
|
-
edges: map.edges.map((e) => ({ from: e.to, to: e.from, ...e.label !== void 0 ? { label: e.label } : {} }))
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
714
|
function buildCanvas(map, opts) {
|
|
624
715
|
const oriented = flipForSequence(map);
|
|
625
716
|
const plainGeo = zoomGeometry(opts.zoom ?? ZOOM_DEFAULT);
|
|
@@ -969,62 +1060,12 @@ function drawBox(canvas, box, opts, neutral, focused = false) {
|
|
|
969
1060
|
// src/store/store.ts
|
|
970
1061
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
971
1062
|
import { basename, dirname, join } from "node:path";
|
|
1063
|
+
|
|
1064
|
+
// src/store/format.ts
|
|
972
1065
|
var STATE_FILE_VERSION = 1;
|
|
973
|
-
var STATE_FILE_RELATIVE_PATH = join(".claude", "mellos-mapping.json");
|
|
974
|
-
var PAGES_DIR_NAME = "mellos-mapping.pages";
|
|
975
1066
|
function makePageId(raw) {
|
|
976
1067
|
return ID_RULE.test(raw) ? ok(raw) : err({ kind: "invalid-id", raw, rule: ID_RULE_TEXT });
|
|
977
1068
|
}
|
|
978
|
-
function pageFilePath(defaultFile, page) {
|
|
979
|
-
return page === void 0 ? defaultFile : join(dirname(defaultFile), PAGES_DIR_NAME, `${page}.json`);
|
|
980
|
-
}
|
|
981
|
-
function pageIdOfFile(defaultFile, path) {
|
|
982
|
-
if (path === defaultFile) return void 0;
|
|
983
|
-
const name = basename(path);
|
|
984
|
-
return name.endsWith(".json") ? name.slice(0, -".json".length) : name;
|
|
985
|
-
}
|
|
986
|
-
function listPageFiles(defaultFile) {
|
|
987
|
-
const out = [];
|
|
988
|
-
if (existsSync(defaultFile)) out.push(defaultFile);
|
|
989
|
-
let entries = [];
|
|
990
|
-
try {
|
|
991
|
-
entries = readdirSync(join(dirname(defaultFile), PAGES_DIR_NAME));
|
|
992
|
-
} catch {
|
|
993
|
-
}
|
|
994
|
-
for (const e of entries.sort()) {
|
|
995
|
-
if (e.endsWith(".json")) out.push(join(dirname(defaultFile), PAGES_DIR_NAME, e));
|
|
996
|
-
}
|
|
997
|
-
return out;
|
|
998
|
-
}
|
|
999
|
-
var FOCUS_FILE_NAME = "mellos-mapping.focus";
|
|
1000
|
-
function focusFilePath(defaultFile) {
|
|
1001
|
-
return join(dirname(defaultFile), FOCUS_FILE_NAME);
|
|
1002
|
-
}
|
|
1003
|
-
function takeFocusRequest(defaultFile) {
|
|
1004
|
-
const path = focusFilePath(defaultFile);
|
|
1005
|
-
let raw;
|
|
1006
|
-
try {
|
|
1007
|
-
raw = readFileSync(path, "utf8");
|
|
1008
|
-
} catch {
|
|
1009
|
-
return void 0;
|
|
1010
|
-
}
|
|
1011
|
-
try {
|
|
1012
|
-
rmSync(path, { force: true });
|
|
1013
|
-
} catch {
|
|
1014
|
-
}
|
|
1015
|
-
let parsed;
|
|
1016
|
-
try {
|
|
1017
|
-
parsed = JSON.parse(raw);
|
|
1018
|
-
} catch {
|
|
1019
|
-
return void 0;
|
|
1020
|
-
}
|
|
1021
|
-
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1022
|
-
const page = parsed.page;
|
|
1023
|
-
if (page === void 0 || page === null) return { page: void 0 };
|
|
1024
|
-
if (typeof page !== "string") return void 0;
|
|
1025
|
-
const id = makePageId(page);
|
|
1026
|
-
return id.ok ? { page: id.value } : void 0;
|
|
1027
|
-
}
|
|
1028
1069
|
function describeStoreError(e) {
|
|
1029
1070
|
switch (e.kind) {
|
|
1030
1071
|
case "not-found":
|
|
@@ -1166,6 +1207,81 @@ function parseMap(raw, path) {
|
|
|
1166
1207
|
}
|
|
1167
1208
|
return ok(map);
|
|
1168
1209
|
}
|
|
1210
|
+
|
|
1211
|
+
// src/store/store.ts
|
|
1212
|
+
var STATE_FILE_RELATIVE_PATH = join(".mellos", "map.json");
|
|
1213
|
+
var PAGES_DIR_NAME = "pages";
|
|
1214
|
+
function pageFilePath(defaultFile, page) {
|
|
1215
|
+
return page === void 0 ? defaultFile : join(dirname(defaultFile), PAGES_DIR_NAME, `${page}.json`);
|
|
1216
|
+
}
|
|
1217
|
+
function pageIdOfFile(defaultFile, path) {
|
|
1218
|
+
if (path === defaultFile) return void 0;
|
|
1219
|
+
const name = basename(path);
|
|
1220
|
+
return name.endsWith(".json") ? name.slice(0, -".json".length) : name;
|
|
1221
|
+
}
|
|
1222
|
+
function listPageFiles(defaultFile) {
|
|
1223
|
+
const out = [];
|
|
1224
|
+
if (existsSync(defaultFile)) out.push(defaultFile);
|
|
1225
|
+
let entries = [];
|
|
1226
|
+
try {
|
|
1227
|
+
entries = readdirSync(join(dirname(defaultFile), PAGES_DIR_NAME));
|
|
1228
|
+
} catch {
|
|
1229
|
+
}
|
|
1230
|
+
for (const e of entries.sort()) {
|
|
1231
|
+
if (e.endsWith(".json")) out.push(join(dirname(defaultFile), PAGES_DIR_NAME, e));
|
|
1232
|
+
}
|
|
1233
|
+
return out;
|
|
1234
|
+
}
|
|
1235
|
+
var FOCUS_FILE_NAME = "focus";
|
|
1236
|
+
function focusFilePath(defaultFile) {
|
|
1237
|
+
return join(dirname(defaultFile), FOCUS_FILE_NAME);
|
|
1238
|
+
}
|
|
1239
|
+
function takeFocusRequest(defaultFile) {
|
|
1240
|
+
const path = focusFilePath(defaultFile);
|
|
1241
|
+
let raw;
|
|
1242
|
+
try {
|
|
1243
|
+
raw = readFileSync(path, "utf8");
|
|
1244
|
+
} catch {
|
|
1245
|
+
return void 0;
|
|
1246
|
+
}
|
|
1247
|
+
try {
|
|
1248
|
+
rmSync(path, { force: true });
|
|
1249
|
+
} catch {
|
|
1250
|
+
}
|
|
1251
|
+
let parsed;
|
|
1252
|
+
try {
|
|
1253
|
+
parsed = JSON.parse(raw);
|
|
1254
|
+
} catch {
|
|
1255
|
+
return void 0;
|
|
1256
|
+
}
|
|
1257
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1258
|
+
const page = parsed.page;
|
|
1259
|
+
if (page === void 0 || page === null) return { page: void 0 };
|
|
1260
|
+
if (typeof page !== "string") return void 0;
|
|
1261
|
+
const id = makePageId(page);
|
|
1262
|
+
return id.ok ? { page: id.value } : void 0;
|
|
1263
|
+
}
|
|
1264
|
+
var CONFIG_FILE_NAME = "config.json";
|
|
1265
|
+
function configFilePath(defaultFile) {
|
|
1266
|
+
return join(dirname(defaultFile), CONFIG_FILE_NAME);
|
|
1267
|
+
}
|
|
1268
|
+
var LEGACY_STATE_FILE_RELATIVE_PATH = join(".claude", "mellos-mapping.json");
|
|
1269
|
+
var LEGACY_PAGES_DIR_NAME = "mellos-mapping.pages";
|
|
1270
|
+
var LEGACY_CONFIG_FILE_NAME = "mellos-mapping.config.json";
|
|
1271
|
+
function migrateLegacyStore(defaultFile) {
|
|
1272
|
+
const projectRoot = dirname(dirname(defaultFile));
|
|
1273
|
+
const legacyDefault = join(projectRoot, LEGACY_STATE_FILE_RELATIVE_PATH);
|
|
1274
|
+
const legacyPages = join(dirname(legacyDefault), LEGACY_PAGES_DIR_NAME);
|
|
1275
|
+
const legacyConfig = join(dirname(legacyDefault), LEGACY_CONFIG_FILE_NAME);
|
|
1276
|
+
const hasLegacy = existsSync(legacyDefault) || existsSync(legacyPages) || existsSync(legacyConfig);
|
|
1277
|
+
const hasCurrent = existsSync(defaultFile) || existsSync(join(dirname(defaultFile), PAGES_DIR_NAME)) || existsSync(configFilePath(defaultFile));
|
|
1278
|
+
if (!hasLegacy || hasCurrent) return false;
|
|
1279
|
+
mkdirSync(dirname(defaultFile), { recursive: true });
|
|
1280
|
+
if (existsSync(legacyDefault)) renameSync(legacyDefault, defaultFile);
|
|
1281
|
+
if (existsSync(legacyPages)) renameSync(legacyPages, join(dirname(defaultFile), PAGES_DIR_NAME));
|
|
1282
|
+
if (existsSync(legacyConfig)) renameSync(legacyConfig, configFilePath(defaultFile));
|
|
1283
|
+
return true;
|
|
1284
|
+
}
|
|
1169
1285
|
function loadMapFile(path) {
|
|
1170
1286
|
let text;
|
|
1171
1287
|
try {
|
|
@@ -1320,16 +1436,7 @@ function dividerRow(width, unicode, follow) {
|
|
|
1320
1436
|
return bar;
|
|
1321
1437
|
}
|
|
1322
1438
|
function mostRecentPageFile(files, mtimeOf) {
|
|
1323
|
-
|
|
1324
|
-
let bestMtime = -Infinity;
|
|
1325
|
-
for (const file of files) {
|
|
1326
|
-
const mtime = mtimeOf(file);
|
|
1327
|
-
if (mtime !== void 0 && mtime > bestMtime) {
|
|
1328
|
-
best = file;
|
|
1329
|
-
bestMtime = mtime;
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
return best ?? files[0];
|
|
1439
|
+
return mostRecentKey(files, mtimeOf);
|
|
1333
1440
|
}
|
|
1334
1441
|
var HIDE_CURSOR = "\x1B[?25l";
|
|
1335
1442
|
var SHOW_CURSOR = "\x1B[?25h";
|
|
@@ -1417,10 +1524,7 @@ function tabScrollFor(tabs, width, unicode, scroll, index) {
|
|
|
1417
1524
|
return s;
|
|
1418
1525
|
}
|
|
1419
1526
|
function topLevelFiles(defaultFile, files, mapOf) {
|
|
1420
|
-
const refs =
|
|
1421
|
-
for (const m of mapOf.values()) {
|
|
1422
|
-
for (const n of m?.nodes ?? []) if (n.submap !== void 0) refs.add(n.submap);
|
|
1423
|
-
}
|
|
1527
|
+
const refs = submapRefs(mapOf.values());
|
|
1424
1528
|
return files.filter((f) => {
|
|
1425
1529
|
const id = pageIdOfFile(defaultFile, f);
|
|
1426
1530
|
return id === void 0 || !refs.has(id);
|
|
@@ -1429,12 +1533,8 @@ function topLevelFiles(defaultFile, files, mapOf) {
|
|
|
1429
1533
|
function diveOrigin(defaultFile, file, files, mapOf) {
|
|
1430
1534
|
const id = pageIdOfFile(defaultFile, file);
|
|
1431
1535
|
if (id === void 0) return void 0;
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
const node = mapOf.get(f)?.nodes.find((n) => n.submap === id);
|
|
1435
|
-
if (node !== void 0) return { parent: f, label: node.label };
|
|
1436
|
-
}
|
|
1437
|
-
return void 0;
|
|
1536
|
+
const entries = files.filter((f) => f !== file).map((f) => [f, mapOf.get(f)]);
|
|
1537
|
+
return diveParent(entries, id);
|
|
1438
1538
|
}
|
|
1439
1539
|
function nearestHit(hits, cx, cy) {
|
|
1440
1540
|
let best;
|
|
@@ -1451,28 +1551,14 @@ function nearestHit(hits, cx, cy) {
|
|
|
1451
1551
|
function nodePanel(map, focusId, unicode, width, pinned, rows = PANEL_CONTENT_ROWS) {
|
|
1452
1552
|
const g = (s) => STATUS_GLYPH[s][unicode ? 0 : 1];
|
|
1453
1553
|
const pinMark = pinned ? unicode ? " \u2299 pinned" : " * pinned" : "";
|
|
1454
|
-
const
|
|
1455
|
-
if (
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
const status
|
|
1459
|
-
const layerName2 = map.layers.find((l) => l.id === group.layer)?.name ?? group.layer;
|
|
1554
|
+
const focus = focusInfo(map, focusId);
|
|
1555
|
+
if (focus === void 0) return void 0;
|
|
1556
|
+
const refText = (r) => `${g(r.status)} ${r.label}${r.edgeLabel !== void 0 ? ` (${r.edgeLabel})` : ""}`;
|
|
1557
|
+
if (focus.kind === "group") {
|
|
1558
|
+
const { group, status, layerName: layerName2, members } = focus;
|
|
1460
1559
|
const [right2, left2] = unicode ? ["\u2192", "\u2190"] : ["->", "<-"];
|
|
1461
|
-
const
|
|
1462
|
-
|
|
1463
|
-
const owner = n.group !== void 0 ? map.groups.find((gr) => gr.id === n.group) : void 0;
|
|
1464
|
-
return owner !== void 0 ? `${g(groupStatus(map, owner.id))} ${owner.label}` : `${g(n.status)} ${n.label}`;
|
|
1465
|
-
};
|
|
1466
|
-
const uses2 = [
|
|
1467
|
-
...new Set(
|
|
1468
|
-
map.edges.filter((e) => memberIds.has(e.from) && !memberIds.has(e.to)).map((e) => repLabel(e.to))
|
|
1469
|
-
)
|
|
1470
|
-
];
|
|
1471
|
-
const usedBy2 = [
|
|
1472
|
-
...new Set(
|
|
1473
|
-
map.edges.filter((e) => memberIds.has(e.to) && !memberIds.has(e.from)).map((e) => repLabel(e.from))
|
|
1474
|
-
)
|
|
1475
|
-
];
|
|
1560
|
+
const uses2 = focus.uses.map(refText);
|
|
1561
|
+
const usedBy2 = focus.usedBy.map(refText);
|
|
1476
1562
|
const lines2 = [
|
|
1477
1563
|
{
|
|
1478
1564
|
text: fitWidth(
|
|
@@ -1491,21 +1577,13 @@ function nodePanel(map, focusId, unicode, width, pinned, rows = PANEL_CONTENT_RO
|
|
|
1491
1577
|
while (lines2.length < rows) lines2.push({ text: "", sgr: "" });
|
|
1492
1578
|
return lines2.slice(0, rows);
|
|
1493
1579
|
}
|
|
1494
|
-
const node
|
|
1495
|
-
if (!node) return void 0;
|
|
1580
|
+
const { node, layerName, laneLabel } = focus;
|
|
1496
1581
|
const neutral = isNeutralKind(map);
|
|
1497
|
-
const layerName = map.layers.find((l) => l.id === node.layer)?.name ?? node.layer;
|
|
1498
1582
|
const [right, left] = unicode ? ["\u2192", "\u2190"] : ["->", "<-"];
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
};
|
|
1503
|
-
const withEdgeLabel = (base, label) => label !== void 0 ? `${base} (${label})` : base;
|
|
1504
|
-
const uses = map.edges.filter((e) => e.from === node.id).map((e) => withEdgeLabel(withGlyph(e.to), e.label));
|
|
1505
|
-
const usedBy = map.edges.filter((e) => e.to === node.id).map((e) => withEdgeLabel(withGlyph(e.from), e.label));
|
|
1506
|
-
const pin = pinned ? unicode ? " \u2299 pinned" : " * pinned" : "";
|
|
1583
|
+
const uses = focus.uses.map(refText);
|
|
1584
|
+
const usedBy = focus.usedBy.map(refText);
|
|
1585
|
+
const pin = pinMark;
|
|
1507
1586
|
const headGlyph = neutral ? (node.kind !== void 0 ? kindGlyph(node.kind, unicode) : void 0) ?? (unicode ? "\xB7" : ".") : g(node.status);
|
|
1508
|
-
const laneLabel = node.lane !== void 0 ? map.lanes.find((l) => l.id === node.lane)?.label : void 0;
|
|
1509
1587
|
const headParts = [
|
|
1510
1588
|
`${headGlyph} ${node.label} [${node.id}]`,
|
|
1511
1589
|
layerName,
|
|
@@ -1535,19 +1613,8 @@ function nodePanel(map, focusId, unicode, width, pinned, rows = PANEL_CONTENT_RO
|
|
|
1535
1613
|
}
|
|
1536
1614
|
return lines.slice(0, rows);
|
|
1537
1615
|
}
|
|
1538
|
-
var
|
|
1539
|
-
|
|
1540
|
-
E: ["####", "#", "###", "#", "####"],
|
|
1541
|
-
L: ["#", "#", "#", "#", "####"],
|
|
1542
|
-
O: [" ###", "# #", "# #", "# #", " ###"],
|
|
1543
|
-
S: [" ####", "#", " ###", " #", "####"],
|
|
1544
|
-
A: [" ###", "# #", "#####", "# #", "# #"],
|
|
1545
|
-
P: ["####", "# #", "####", "#", "#"],
|
|
1546
|
-
I: ["###", " #", " #", " #", "###"],
|
|
1547
|
-
N: ["# #", "## #", "# # #", "# ##", "# #"],
|
|
1548
|
-
G: [" ####", "#", "# ##", "# #", " ###"]
|
|
1549
|
-
};
|
|
1550
|
-
var SPLASH_ROWS = 5;
|
|
1616
|
+
var WATER_ROWS = 7;
|
|
1617
|
+
var WATER_COLS_MAX = 60;
|
|
1551
1618
|
var SPLASH_SHADES = {
|
|
1552
1619
|
unicode: ["\u2591", "\u2591", "\u2592", "\u2592", "\u2593", "\u2593", "\u2588", "\u2588"],
|
|
1553
1620
|
ascii: [".", ".", ":", ":", "=", "=", "#", "#"]
|
|
@@ -1557,23 +1624,24 @@ var SPINNER_FRAMES = {
|
|
|
1557
1624
|
unicode: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
|
|
1558
1625
|
ascii: ["|", "/", "-", "\\"]
|
|
1559
1626
|
};
|
|
1560
|
-
function
|
|
1561
|
-
const
|
|
1562
|
-
const
|
|
1563
|
-
const
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
const
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
});
|
|
1576
|
-
|
|
1627
|
+
function elapsedLabel(ms) {
|
|
1628
|
+
const s = Math.max(0, Math.floor(ms / 1e3));
|
|
1629
|
+
const m = Math.floor(s / 60);
|
|
1630
|
+
const h = Math.floor(m / 60);
|
|
1631
|
+
const two = (n) => String(n).padStart(2, "0");
|
|
1632
|
+
return h > 0 ? `${h}:${two(m % 60)}:${two(s % 60)}` : `${m}:${two(s % 60)}`;
|
|
1633
|
+
}
|
|
1634
|
+
function waitingInfo(s, width) {
|
|
1635
|
+
const w = Math.max(1, width);
|
|
1636
|
+
const clock = s.elapsedMs !== void 0 ? ` \xB7 waiting ${elapsedLabel(s.elapsedMs)}` : "";
|
|
1637
|
+
const lines = [
|
|
1638
|
+
`watching ${s.defaultFile}`,
|
|
1639
|
+
` and ${join2(s.pagesDir, "*.json")}`,
|
|
1640
|
+
`polling every ${s.intervalMs} ms${clock}`
|
|
1641
|
+
];
|
|
1642
|
+
for (const b of s.broken) lines.push(`! ${b}`);
|
|
1643
|
+
lines.push("the map appears at the first mmap_declare");
|
|
1644
|
+
return lines.map((l) => fitWidth(l, w));
|
|
1577
1645
|
}
|
|
1578
1646
|
var WAVE_INTERVAL = 18;
|
|
1579
1647
|
var WAVE_LIFETIME = 64;
|
|
@@ -1624,39 +1692,41 @@ function waveAt(ripples, x, y) {
|
|
|
1624
1692
|
function waveLevel(value) {
|
|
1625
1693
|
return Math.max(-WAVE_LEVELS, Math.min(WAVE_LEVELS, Math.round(value * WAVE_GAIN)));
|
|
1626
1694
|
}
|
|
1627
|
-
function splashFrame(notice, frame, width, height, unicode, color) {
|
|
1628
|
-
const
|
|
1629
|
-
|
|
1630
|
-
if (width < artWidth + 2 || height < art.length + 2) return void 0;
|
|
1695
|
+
function splashFrame(notice, info, frame, width, height, unicode, color) {
|
|
1696
|
+
const fieldW = Math.min(width - 4, WATER_COLS_MAX);
|
|
1697
|
+
if (fieldW < 24 || height < WATER_ROWS + info.length + 3) return void 0;
|
|
1631
1698
|
const mode = unicode ? "unicode" : "ascii";
|
|
1632
1699
|
const shades = SPLASH_SHADES[mode];
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1637
|
-
const level = waveLevel(waveAt(ripples, x, y));
|
|
1638
|
-
return color ? `38;5;${WAVE_RAMP[WAVE_LEVELS + level]}` : shades[Math.abs(level)];
|
|
1639
|
-
};
|
|
1640
|
-
const paintRow = (row, y) => {
|
|
1641
|
-
const cells = [...row].map((ch, x) => ch === "#" ? inkOf(x, y) : void 0);
|
|
1700
|
+
const indent = " ".repeat(Math.max(0, Math.floor((width - fieldW) / 2)));
|
|
1701
|
+
const ripples = liveRipples(frame, fieldW, WATER_ROWS);
|
|
1702
|
+
const paintRow = (y) => {
|
|
1703
|
+
const levels = Array.from({ length: fieldW }, (_, x) => waveLevel(waveAt(ripples, x, y)));
|
|
1642
1704
|
let out = "";
|
|
1643
|
-
for (let i = 0; i <
|
|
1644
|
-
const
|
|
1705
|
+
for (let i = 0; i < fieldW; ) {
|
|
1706
|
+
const level = levels[i];
|
|
1645
1707
|
let j = i;
|
|
1646
|
-
while (j <
|
|
1647
|
-
if (
|
|
1648
|
-
else
|
|
1708
|
+
while (j < fieldW && levels[j] === level) j++;
|
|
1709
|
+
if (level === 0) out += " ".repeat(j - i);
|
|
1710
|
+
else {
|
|
1711
|
+
const ink = shades[Math.abs(level)].repeat(j - i);
|
|
1712
|
+
out += color ? `\x1B[38;5;${WAVE_RAMP[WAVE_LEVELS + level]}m${ink}${RESET}` : ink;
|
|
1713
|
+
}
|
|
1649
1714
|
i = j;
|
|
1650
1715
|
}
|
|
1651
1716
|
return out;
|
|
1652
1717
|
};
|
|
1718
|
+
const dim = (s) => color ? `\x1B[90m${s}${RESET}` : s;
|
|
1653
1719
|
const spinner = SPINNER_FRAMES[mode];
|
|
1654
1720
|
const status = fitWidth(`${spinner[frame % spinner.length]} ${notice}`, Math.max(1, width - 2));
|
|
1655
1721
|
const statusIndent = " ".repeat(Math.max(0, Math.floor((width - displayWidth(status)) / 2)));
|
|
1722
|
+
const infoWidth = Math.max(0, ...info.map((l) => displayWidth(l)));
|
|
1723
|
+
const infoIndent = " ".repeat(Math.max(0, Math.floor((width - infoWidth) / 2)));
|
|
1656
1724
|
const block = [
|
|
1657
|
-
...
|
|
1725
|
+
...Array.from({ length: WATER_ROWS }, (_, y) => indent + paintRow(y)),
|
|
1658
1726
|
"",
|
|
1659
|
-
statusIndent + (
|
|
1727
|
+
statusIndent + dim(status),
|
|
1728
|
+
"",
|
|
1729
|
+
...info.map((l) => infoIndent + dim(l))
|
|
1660
1730
|
];
|
|
1661
1731
|
return [...Array.from({ length: Math.max(0, Math.floor((height - block.length) / 2)) }, () => ""), ...block];
|
|
1662
1732
|
}
|
|
@@ -1680,13 +1750,16 @@ function mapPanel(map, unicode, width, rows = PANEL_CONTENT_ROWS) {
|
|
|
1680
1750
|
}
|
|
1681
1751
|
function main() {
|
|
1682
1752
|
const cfg = parseArgs(process.argv.slice(2), process.cwd());
|
|
1753
|
+
migrateLegacyStore(cfg.file);
|
|
1683
1754
|
const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
1684
1755
|
const mouseActive = interactive && cfg.mouse;
|
|
1685
1756
|
let lastFrame = "";
|
|
1686
1757
|
let spinnerFrame = 0;
|
|
1687
1758
|
let splashTick = 0;
|
|
1759
|
+
const startedAt = Date.now();
|
|
1760
|
+
const standbyNotice = "waiting for the first mmap_declare ...";
|
|
1688
1761
|
let map;
|
|
1689
|
-
let notice =
|
|
1762
|
+
let notice = standbyNotice;
|
|
1690
1763
|
let lastCols = process.stdout.columns ?? 0;
|
|
1691
1764
|
let lastRows = process.stdout.rows ?? 0;
|
|
1692
1765
|
let pageFiles = [cfg.file];
|
|
@@ -1755,7 +1828,7 @@ function main() {
|
|
|
1755
1828
|
const entry = pageData.get(file);
|
|
1756
1829
|
if (entry !== void 0 && entry.fresh) pageData.set(file, { ...entry, fresh: false });
|
|
1757
1830
|
map = entry?.map;
|
|
1758
|
-
notice = map
|
|
1831
|
+
notice = map !== void 0 ? "" : entry?.error ?? (file === cfg.file ? standbyNotice : `waiting for ${file} ...`);
|
|
1759
1832
|
const top = topFiles();
|
|
1760
1833
|
const tabIndex = top.indexOf(file);
|
|
1761
1834
|
if (tabIndex >= 0) tabScroll = tabScrollFor(pageTabsOf(top), viewWidth(), cfg.unicode, tabScroll, tabIndex);
|
|
@@ -1813,7 +1886,17 @@ function main() {
|
|
|
1813
1886
|
lastContent = { w: windowed.contentWidth, h: windowed.contentHeight };
|
|
1814
1887
|
if (offsetX !== 0 || offsetY !== 0) panned = ` (+${offsetX},+${offsetY})`;
|
|
1815
1888
|
} else {
|
|
1816
|
-
|
|
1889
|
+
const info = waitingInfo(
|
|
1890
|
+
{
|
|
1891
|
+
defaultFile: cfg.file,
|
|
1892
|
+
pagesDir: join2(dirname2(cfg.file), PAGES_DIR_NAME),
|
|
1893
|
+
intervalMs: cfg.intervalMs,
|
|
1894
|
+
elapsedMs: interactive ? Date.now() - startedAt : void 0,
|
|
1895
|
+
broken: [...pageData.values()].flatMap((e) => e.map === void 0 && e.error !== void 0 ? [e.error] : [])
|
|
1896
|
+
},
|
|
1897
|
+
Math.max(1, viewW - 2)
|
|
1898
|
+
);
|
|
1899
|
+
body = (interactive ? splashFrame(notice, info, splashTick, viewW, viewH, cfg.unicode, cfg.color) : void 0) ?? [fitWidth(notice, viewW), "", ...info.map((l) => fitWidth(` ${l}`, viewW))];
|
|
1817
1900
|
}
|
|
1818
1901
|
if (notice !== "" && map !== void 0) {
|
|
1819
1902
|
body[body.length - 1] = fitWidth(` ${notice}`, viewW);
|
|
@@ -1917,8 +2000,15 @@ function main() {
|
|
|
1917
2000
|
flash = { text: `${cfg.unicode ? "\u229E " : ""}${title} updated`, until: Date.now() + 4e3 };
|
|
1918
2001
|
}
|
|
1919
2002
|
} else if (loaded.error.kind === "malformed-json") {
|
|
2003
|
+
pageData.set(file, {
|
|
2004
|
+
map: entry?.map,
|
|
2005
|
+
mtimeMs: entry?.mtimeMs ?? -1,
|
|
2006
|
+
fresh: entry?.fresh ?? false,
|
|
2007
|
+
error: describeStoreError(loaded.error)
|
|
2008
|
+
});
|
|
2009
|
+
if (file === activeFile && entry?.map === void 0) notice = describeStoreError(loaded.error);
|
|
1920
2010
|
} else {
|
|
1921
|
-
pageData.set(file, { map: entry?.map, mtimeMs, fresh: entry?.fresh ?? false });
|
|
2011
|
+
pageData.set(file, { map: entry?.map, mtimeMs, fresh: entry?.fresh ?? false, error: describeStoreError(loaded.error) });
|
|
1922
2012
|
if (file === activeFile) notice = describeStoreError(loaded.error);
|
|
1923
2013
|
}
|
|
1924
2014
|
}
|
|
@@ -2143,6 +2233,7 @@ export {
|
|
|
2143
2233
|
clampPanelRows,
|
|
2144
2234
|
diveOrigin,
|
|
2145
2235
|
dividerRow,
|
|
2236
|
+
elapsedLabel,
|
|
2146
2237
|
fitWidth,
|
|
2147
2238
|
launchedAsEntry,
|
|
2148
2239
|
liveRipples,
|
|
@@ -2153,14 +2244,13 @@ export {
|
|
|
2153
2244
|
pageTabRow,
|
|
2154
2245
|
panelRowsFromDividerY,
|
|
2155
2246
|
parseArgs,
|
|
2156
|
-
splashArt,
|
|
2157
2247
|
splashFrame,
|
|
2158
2248
|
tabScrollFor,
|
|
2159
2249
|
topLevelFiles,
|
|
2160
2250
|
usableColumns,
|
|
2251
|
+
waitingInfo,
|
|
2161
2252
|
waveAt,
|
|
2162
2253
|
waveHash,
|
|
2163
2254
|
waveLevel,
|
|
2164
|
-
wordArt,
|
|
2165
2255
|
wrapWidth
|
|
2166
2256
|
};
|