mellos-mapping 0.24.0 → 0.26.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 +82 -12
- package/README.zh-CN.md +69 -10
- package/dist/hook-session-start.mjs +69 -13
- package/dist/native/LICENSES.txt +443 -0
- package/dist/native/darwin-arm64/fs-native-extensions.node +0 -0
- package/dist/native/darwin-x64/fs-native-extensions.node +0 -0
- package/dist/native/linux-arm64/fs-native-extensions.node +0 -0
- package/dist/native/linux-x64/fs-native-extensions.node +0 -0
- package/dist/native/win32-arm64/fs-native-extensions.node +0 -0
- package/dist/native/win32-x64/fs-native-extensions.node +0 -0
- package/dist/native-lock.cjs +346 -0
- package/dist/omp-extension.mjs +359 -0
- package/dist/server.mjs +199 -139
- package/dist/terminal-worker.mjs +116 -74
- package/dist/watch.mjs +125 -83
- package/dist/web.mjs +170 -113
- package/docs/codex.md +4 -1
- package/docs/locking.md +115 -0
- package/docs/map-api.md +29 -13
- package/lib/store/migration.js +19 -12
- package/lib/store/native-lock.d.ts +5 -0
- package/lib/store/native-lock.js +27 -0
- package/lib/store/transaction.d.ts +9 -2
- package/lib/store/transaction.js +79 -50
- package/package.json +11 -3
- package/scripts/install-mmap-command.mjs +179 -13
package/dist/terminal-worker.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { PassThrough, Writable } from "node:stream";
|
|
3
3
|
|
|
4
4
|
// src/watch/watch.ts
|
|
5
|
-
import { realpathSync as
|
|
5
|
+
import { realpathSync as realpathSync3, statSync as statSync2 } from "node:fs";
|
|
6
6
|
import { dirname as dirname9, join as join8 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// src/domain/types.ts
|
|
@@ -262,9 +262,8 @@ function resolveProjectDirectory(cwd, stopAt = [homedir(), tmpdir()]) {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
// src/store/transaction.ts
|
|
265
|
-
import {
|
|
266
|
-
import { dirname as dirname2, join as join2 } from "node:path";
|
|
267
|
-
import { randomUUID, createHash } from "node:crypto";
|
|
265
|
+
import { closeSync, fstatSync, lstatSync, mkdirSync, openSync, realpathSync as realpathSync2 } from "node:fs";
|
|
266
|
+
import { basename, dirname as dirname2, join as join2 } from "node:path";
|
|
268
267
|
|
|
269
268
|
// src/domain/context.ts
|
|
270
269
|
function sourceError(raw) {
|
|
@@ -554,6 +553,30 @@ function parseMap(raw, path) {
|
|
|
554
553
|
return textError ? err({ kind: "bad-shape", path, detail: textError }) : ok(map);
|
|
555
554
|
}
|
|
556
555
|
|
|
556
|
+
// src/store/native-lock.ts
|
|
557
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
558
|
+
import { createRequire } from "node:module";
|
|
559
|
+
import { fileURLToPath } from "node:url";
|
|
560
|
+
var loaded;
|
|
561
|
+
function nativeLock() {
|
|
562
|
+
if (loaded) return loaded;
|
|
563
|
+
if (Number(process.versions.napi ?? 0) < 9) {
|
|
564
|
+
throw new Error("Project locks require Node-API 9 (Node 18.17+ or 20.3+).");
|
|
565
|
+
}
|
|
566
|
+
const candidates = [
|
|
567
|
+
new URL("./native-lock.cjs", import.meta.url),
|
|
568
|
+
new URL("../../dist/native-lock.cjs", import.meta.url)
|
|
569
|
+
];
|
|
570
|
+
const entry = candidates.find((candidate) => existsSync2(candidate));
|
|
571
|
+
if (!entry) throw new Error("The installed package is missing dist/native-lock.cjs; reinstall the complete package.");
|
|
572
|
+
const backend = createRequire(import.meta.url)(fileURLToPath(entry));
|
|
573
|
+
if (typeof backend.tryLock !== "function" || typeof backend.unlock !== "function") {
|
|
574
|
+
throw new Error("The installed native lock backend is invalid.");
|
|
575
|
+
}
|
|
576
|
+
loaded = backend;
|
|
577
|
+
return backend;
|
|
578
|
+
}
|
|
579
|
+
|
|
557
580
|
// src/store/transaction.ts
|
|
558
581
|
var LedgerError = class extends Error {
|
|
559
582
|
constructor(code, message, details = {}) {
|
|
@@ -564,61 +587,78 @@ var LedgerError = class extends Error {
|
|
|
564
587
|
};
|
|
565
588
|
function storeDirectory(file) {
|
|
566
589
|
const dir = dirname2(file);
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
function deadOwner(lock) {
|
|
590
|
+
if (basename(dir).toLowerCase() === "pages") return dirname2(dir);
|
|
591
|
+
let canonical = dir;
|
|
570
592
|
try {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
593
|
+
canonical = realpathSync2.native(dir);
|
|
594
|
+
} catch (error) {
|
|
595
|
+
if (error.code !== "ENOENT") throw error;
|
|
596
|
+
}
|
|
597
|
+
return basename(canonical).toLowerCase() === "pages" ? dirname2(canonical) : canonical;
|
|
598
|
+
}
|
|
599
|
+
function checkLockPath(lock) {
|
|
600
|
+
const entry = lstatSync(lock, { throwIfNoEntry: false });
|
|
601
|
+
if (entry?.isDirectory()) {
|
|
602
|
+
throw new LedgerError(
|
|
603
|
+
"LOCK_MIGRATION_REQUIRED",
|
|
604
|
+
`Legacy directory lock at ${lock}. Stop all old MCP servers, viewers and watchers for this project, then move that directory aside for inspection and retry. Never remove the new regular lock file.`,
|
|
605
|
+
{ path: lock }
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (entry && !entry.isFile()) {
|
|
609
|
+
throw new LedgerError("LOCK_UNAVAILABLE", `Project lock must be a regular file, not a symlink or special file: ${lock}`, { path: lock });
|
|
581
610
|
}
|
|
582
611
|
}
|
|
583
612
|
function withStoreLock(file, action) {
|
|
613
|
+
let backend;
|
|
614
|
+
try {
|
|
615
|
+
backend = nativeLock();
|
|
616
|
+
} catch (error) {
|
|
617
|
+
throw new LedgerError("LOCK_UNAVAILABLE", `Cannot load the operating-system lock backend: ${error instanceof Error ? error.message : String(error)}`);
|
|
618
|
+
}
|
|
584
619
|
const dir = storeDirectory(file);
|
|
585
620
|
mkdirSync(dir, { recursive: true });
|
|
586
621
|
const lock = join2(dir, ".write-lock");
|
|
587
|
-
|
|
588
|
-
|
|
622
|
+
checkLockPath(lock);
|
|
623
|
+
let fd;
|
|
589
624
|
try {
|
|
590
|
-
|
|
625
|
+
fd = openSync(lock, "a+", 384);
|
|
591
626
|
} catch (error) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
try {
|
|
595
|
-
mkdirSync(join2(lock, ".reap"));
|
|
596
|
-
} catch {
|
|
597
|
-
throw new LedgerError("BUSY", "Another process is recovering the writer lock.");
|
|
598
|
-
}
|
|
599
|
-
if (!deadOwner(lock)) {
|
|
600
|
-
rmSync(join2(lock, ".reap"), { recursive: true, force: true });
|
|
601
|
-
throw new LedgerError("BUSY", "Writer ownership changed.");
|
|
602
|
-
}
|
|
603
|
-
rmSync(lock, { recursive: true });
|
|
604
|
-
try {
|
|
605
|
-
mkdirSync(lock);
|
|
606
|
-
} catch {
|
|
607
|
-
throw new LedgerError("BUSY", "Another writer acquired the recovered lock.");
|
|
608
|
-
}
|
|
627
|
+
checkLockPath(lock);
|
|
628
|
+
throw new LedgerError("LOCK_UNAVAILABLE", `Cannot open project lock ${lock}: ${error instanceof Error ? error.message : String(error)}`, { path: lock });
|
|
609
629
|
}
|
|
610
|
-
let
|
|
630
|
+
let acquired = false;
|
|
611
631
|
try {
|
|
612
|
-
|
|
613
|
-
|
|
632
|
+
checkLockPath(lock);
|
|
633
|
+
if (!fstatSync(fd).isFile()) throw new LedgerError("LOCK_UNAVAILABLE", `Project lock is not a regular file: ${lock}`);
|
|
634
|
+
try {
|
|
635
|
+
acquired = backend.tryLock(fd);
|
|
636
|
+
} catch (error) {
|
|
637
|
+
throw new LedgerError("LOCK_UNAVAILABLE", `Cannot acquire project lock ${lock}: ${error instanceof Error ? error.message : String(error)}`, { path: lock });
|
|
638
|
+
}
|
|
639
|
+
if (!acquired) throw new LedgerError("BUSY", `Another writer owns ${lock}; retry after it completes.`);
|
|
614
640
|
return action();
|
|
615
641
|
} finally {
|
|
616
642
|
try {
|
|
617
|
-
if (
|
|
643
|
+
if (acquired) backend.unlock(fd);
|
|
618
644
|
} catch {
|
|
645
|
+
warnLockCleanup(`Could not explicitly unlock ${lock}; closing its file handle.`);
|
|
646
|
+
} finally {
|
|
647
|
+
try {
|
|
648
|
+
closeSync(fd);
|
|
649
|
+
} catch {
|
|
650
|
+
warnLockCleanup(`Could not close the project lock handle for ${lock}; restart this process before retrying writes.`);
|
|
651
|
+
}
|
|
619
652
|
}
|
|
620
653
|
}
|
|
621
654
|
}
|
|
655
|
+
function warnLockCleanup(message) {
|
|
656
|
+
try {
|
|
657
|
+
process.stderr.write(`mellos-mapping: ${message}
|
|
658
|
+
`);
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
}
|
|
622
662
|
|
|
623
663
|
// src/semantics/vocabulary.ts
|
|
624
664
|
var STATUS_GLYPHS = {
|
|
@@ -1748,7 +1788,7 @@ function paint(scene, opts) {
|
|
|
1748
1788
|
}
|
|
1749
1789
|
|
|
1750
1790
|
// src/store/atomic.ts
|
|
1751
|
-
import { mkdirSync as mkdirSync2, renameSync, rmSync
|
|
1791
|
+
import { mkdirSync as mkdirSync2, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
1752
1792
|
import { dirname as dirname3 } from "node:path";
|
|
1753
1793
|
var RENAME_BACKOFF_STEP_MS = 10;
|
|
1754
1794
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOENT"]);
|
|
@@ -1757,7 +1797,7 @@ function sleepSync(ms) {
|
|
|
1757
1797
|
}
|
|
1758
1798
|
function discardTemp(tmp) {
|
|
1759
1799
|
try {
|
|
1760
|
-
|
|
1800
|
+
rmSync(tmp, { force: true });
|
|
1761
1801
|
} catch {
|
|
1762
1802
|
}
|
|
1763
1803
|
}
|
|
@@ -1768,7 +1808,7 @@ function writeAtomic(path, contents, maxAttempts) {
|
|
|
1768
1808
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
1769
1809
|
try {
|
|
1770
1810
|
mkdirSync2(dirname3(path), { recursive: true });
|
|
1771
|
-
|
|
1811
|
+
writeFileSync(tmp, contents, "utf8");
|
|
1772
1812
|
} catch (e) {
|
|
1773
1813
|
discardTemp(tmp);
|
|
1774
1814
|
return err({ kind: "save-failed", path, detail: `writing the temp file failed: ${errnoOf(e)}` });
|
|
@@ -1791,8 +1831,8 @@ function writeAtomic(path, contents, maxAttempts) {
|
|
|
1791
1831
|
}
|
|
1792
1832
|
|
|
1793
1833
|
// src/store/pages.ts
|
|
1794
|
-
import { existsSync as
|
|
1795
|
-
import { basename, dirname as dirname4, join as join3 } from "node:path";
|
|
1834
|
+
import { existsSync as existsSync3, readdirSync, rmSync as rmSync2 } from "node:fs";
|
|
1835
|
+
import { basename as basename2, dirname as dirname4, join as join3 } from "node:path";
|
|
1796
1836
|
var STORE_DIR_NAME = ".mellos";
|
|
1797
1837
|
var STATE_FILE_RELATIVE_PATH = join3(STORE_DIR_NAME, "map.json");
|
|
1798
1838
|
var PAGES_DIR_NAME = "pages";
|
|
@@ -1801,12 +1841,12 @@ function pageFilePath(defaultFile, page) {
|
|
|
1801
1841
|
}
|
|
1802
1842
|
function pageIdOfFile(defaultFile, path) {
|
|
1803
1843
|
if (path === defaultFile) return void 0;
|
|
1804
|
-
const name =
|
|
1844
|
+
const name = basename2(path);
|
|
1805
1845
|
return name.endsWith(".json") ? name.slice(0, -".json".length) : name;
|
|
1806
1846
|
}
|
|
1807
1847
|
function listPageFiles(defaultFile) {
|
|
1808
1848
|
const out = [];
|
|
1809
|
-
if (
|
|
1849
|
+
if (existsSync3(defaultFile)) out.push(defaultFile);
|
|
1810
1850
|
let entries = [];
|
|
1811
1851
|
try {
|
|
1812
1852
|
entries = readdirSync(join3(dirname4(defaultFile), PAGES_DIR_NAME));
|
|
@@ -1819,7 +1859,7 @@ function listPageFiles(defaultFile) {
|
|
|
1819
1859
|
}
|
|
1820
1860
|
function deletePageFile(path) {
|
|
1821
1861
|
try {
|
|
1822
|
-
|
|
1862
|
+
rmSync2(path, { force: true });
|
|
1823
1863
|
return ok(void 0);
|
|
1824
1864
|
} catch (e) {
|
|
1825
1865
|
return err({ kind: "delete-failed", path, detail: errnoOf(e) });
|
|
@@ -1827,7 +1867,7 @@ function deletePageFile(path) {
|
|
|
1827
1867
|
}
|
|
1828
1868
|
|
|
1829
1869
|
// src/store/channels.ts
|
|
1830
|
-
import { existsSync as
|
|
1870
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, rmSync as rmSync4 } from "node:fs";
|
|
1831
1871
|
import { dirname as dirname6, join as join5 } from "node:path";
|
|
1832
1872
|
|
|
1833
1873
|
// src/store/json-text.ts
|
|
@@ -1839,7 +1879,7 @@ function stripBom(text) {
|
|
|
1839
1879
|
}
|
|
1840
1880
|
|
|
1841
1881
|
// src/store/viewers.ts
|
|
1842
|
-
import { readdirSync as readdirSync2, readFileSync
|
|
1882
|
+
import { readdirSync as readdirSync2, readFileSync, statSync, rmSync as rmSync3 } from "node:fs";
|
|
1843
1883
|
import { dirname as dirname5, join as join4 } from "node:path";
|
|
1844
1884
|
var VIEWERS_DIR_NAME = "viewers";
|
|
1845
1885
|
var VIEWER_FILE_VERSION = 1;
|
|
@@ -1857,7 +1897,7 @@ function publishViewer(defaultFile, pid, report) {
|
|
|
1857
1897
|
}
|
|
1858
1898
|
function retireViewer(defaultFile, pid) {
|
|
1859
1899
|
try {
|
|
1860
|
-
|
|
1900
|
+
rmSync3(viewerFilePath(defaultFile, pid), { force: true });
|
|
1861
1901
|
} catch {
|
|
1862
1902
|
}
|
|
1863
1903
|
}
|
|
@@ -1874,15 +1914,15 @@ function paneChannelPath(defaultFile, channel, pid) {
|
|
|
1874
1914
|
}
|
|
1875
1915
|
function takeFocusRequest(defaultFile, pid) {
|
|
1876
1916
|
const targeted = pid === void 0 ? void 0 : focusFilePath(defaultFile, pid);
|
|
1877
|
-
const path = targeted !== void 0 &&
|
|
1917
|
+
const path = targeted !== void 0 && existsSync4(targeted) ? targeted : focusFilePath(defaultFile);
|
|
1878
1918
|
let raw;
|
|
1879
1919
|
try {
|
|
1880
|
-
raw =
|
|
1920
|
+
raw = readFileSync2(path, "utf8");
|
|
1881
1921
|
} catch {
|
|
1882
1922
|
return void 0;
|
|
1883
1923
|
}
|
|
1884
1924
|
try {
|
|
1885
|
-
|
|
1925
|
+
rmSync4(path, { force: true });
|
|
1886
1926
|
} catch {
|
|
1887
1927
|
}
|
|
1888
1928
|
let parsed;
|
|
@@ -1904,10 +1944,10 @@ function quitFilePath(defaultFile, pid) {
|
|
|
1904
1944
|
}
|
|
1905
1945
|
function takeQuitRequest(defaultFile, pid) {
|
|
1906
1946
|
const targeted = pid === void 0 ? void 0 : quitFilePath(defaultFile, pid);
|
|
1907
|
-
const path = targeted !== void 0 &&
|
|
1947
|
+
const path = targeted !== void 0 && existsSync4(targeted) ? targeted : quitFilePath(defaultFile);
|
|
1908
1948
|
let raw;
|
|
1909
1949
|
try {
|
|
1910
|
-
raw =
|
|
1950
|
+
raw = readFileSync2(path, "utf8");
|
|
1911
1951
|
} catch {
|
|
1912
1952
|
return false;
|
|
1913
1953
|
}
|
|
@@ -1922,7 +1962,7 @@ function takeQuitRequest(defaultFile, pid) {
|
|
|
1922
1962
|
}
|
|
1923
1963
|
function sweepQuitRequest(defaultFile, pid) {
|
|
1924
1964
|
try {
|
|
1925
|
-
|
|
1965
|
+
rmSync4(quitFilePath(defaultFile, pid), { force: true });
|
|
1926
1966
|
} catch {
|
|
1927
1967
|
}
|
|
1928
1968
|
}
|
|
@@ -1935,7 +1975,7 @@ function configFilePath(defaultFile) {
|
|
|
1935
1975
|
}
|
|
1936
1976
|
|
|
1937
1977
|
// src/store/migration.ts
|
|
1938
|
-
import { existsSync as
|
|
1978
|
+
import { existsSync as existsSync5, renameSync as renameSync2 } from "node:fs";
|
|
1939
1979
|
import { dirname as dirname8, join as join7 } from "node:path";
|
|
1940
1980
|
var LEGACY_STATE_FILE_RELATIVE_PATH = join7(".claude", "mellos-mapping.json");
|
|
1941
1981
|
var LEGACY_PAGES_DIR_NAME = "mellos-mapping.pages";
|
|
@@ -1945,22 +1985,24 @@ function migrateLegacyStore(defaultFile) {
|
|
|
1945
1985
|
const legacyDefault = join7(projectRoot, LEGACY_STATE_FILE_RELATIVE_PATH);
|
|
1946
1986
|
const legacyPages = join7(dirname8(legacyDefault), LEGACY_PAGES_DIR_NAME);
|
|
1947
1987
|
const legacyConfig = join7(dirname8(legacyDefault), LEGACY_CONFIG_FILE_NAME);
|
|
1948
|
-
const hasLegacy =
|
|
1949
|
-
const hasCurrent =
|
|
1950
|
-
if (!hasLegacy || hasCurrent) return false;
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1988
|
+
const hasLegacy = () => existsSync5(legacyDefault) || existsSync5(legacyPages) || existsSync5(legacyConfig);
|
|
1989
|
+
const hasCurrent = () => existsSync5(defaultFile) || existsSync5(join7(dirname8(defaultFile), PAGES_DIR_NAME)) || existsSync5(configFilePath(defaultFile));
|
|
1990
|
+
if (!hasLegacy() || hasCurrent()) return false;
|
|
1991
|
+
return withStoreLock(defaultFile, () => {
|
|
1992
|
+
if (!hasLegacy() || hasCurrent()) return false;
|
|
1993
|
+
if (existsSync5(legacyDefault)) renameSync2(legacyDefault, defaultFile);
|
|
1994
|
+
if (existsSync5(legacyPages)) renameSync2(legacyPages, join7(dirname8(defaultFile), PAGES_DIR_NAME));
|
|
1995
|
+
if (existsSync5(legacyConfig)) renameSync2(legacyConfig, configFilePath(defaultFile));
|
|
1996
|
+
return true;
|
|
1997
|
+
});
|
|
1956
1998
|
}
|
|
1957
1999
|
|
|
1958
2000
|
// src/store/maps.ts
|
|
1959
|
-
import { readFileSync as
|
|
2001
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1960
2002
|
function loadMapFile(path) {
|
|
1961
2003
|
let text;
|
|
1962
2004
|
try {
|
|
1963
|
-
text =
|
|
2005
|
+
text = readFileSync3(path, "utf8");
|
|
1964
2006
|
} catch (e) {
|
|
1965
2007
|
const code = e.code;
|
|
1966
2008
|
if (code === "ENOENT") return err({ kind: "not-found", path });
|
|
@@ -2345,22 +2387,22 @@ function scan(state, input2) {
|
|
|
2345
2387
|
pages.push(held);
|
|
2346
2388
|
continue;
|
|
2347
2389
|
}
|
|
2348
|
-
const
|
|
2349
|
-
if (
|
|
2390
|
+
const loaded2 = input2.load(file);
|
|
2391
|
+
if (loaded2.ok) {
|
|
2350
2392
|
if (!first) changed.push(file);
|
|
2351
2393
|
const fresh = !first && file !== previousActive;
|
|
2352
2394
|
if (fresh) freshened.push(file);
|
|
2353
|
-
pages.push({ file, state: { kind: "loaded", map:
|
|
2395
|
+
pages.push({ file, state: { kind: "loaded", map: loaded2.value, mtimeMs }, fresh });
|
|
2354
2396
|
continue;
|
|
2355
2397
|
}
|
|
2356
2398
|
pages.push({
|
|
2357
2399
|
file,
|
|
2358
2400
|
state: {
|
|
2359
2401
|
kind: "faulted",
|
|
2360
|
-
fault:
|
|
2402
|
+
fault: loaded2.error,
|
|
2361
2403
|
lastGood: mapOf(held),
|
|
2362
2404
|
mtimeMs,
|
|
2363
|
-
transient: isTransient(
|
|
2405
|
+
transient: isTransient(loaded2.error)
|
|
2364
2406
|
},
|
|
2365
2407
|
fresh: held?.fresh ?? false
|
|
2366
2408
|
});
|