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
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
4
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// node_modules/which-runtime/index.js
|
|
8
|
+
var require_which_runtime = __commonJS({
|
|
9
|
+
"node_modules/which-runtime/index.js"(exports2) {
|
|
10
|
+
var { runtime, platform, arch, version } = typeof Bare !== "undefined" ? {
|
|
11
|
+
runtime: "bare",
|
|
12
|
+
platform: global.Bare.platform,
|
|
13
|
+
arch: global.Bare.arch,
|
|
14
|
+
version: global.Bare.version
|
|
15
|
+
} : typeof process !== "undefined" ? {
|
|
16
|
+
runtime: "node",
|
|
17
|
+
platform: global.process.platform,
|
|
18
|
+
arch: global.process.arch,
|
|
19
|
+
version: global.process.version
|
|
20
|
+
} : typeof Window !== "undefined" ? { runtime: "browser", platform: "unknown", arch: "unknown", version: "unknown" } : { runtime: "unknown", platform: "unknown", arch: "unknown", version: "unknown" };
|
|
21
|
+
exports2.runtime = runtime;
|
|
22
|
+
exports2.version = version;
|
|
23
|
+
exports2.platform = platform;
|
|
24
|
+
exports2.arch = arch;
|
|
25
|
+
exports2.isBare = runtime === "bare";
|
|
26
|
+
exports2.isBareKit = exports2.isBare && typeof BareKit !== "undefined";
|
|
27
|
+
exports2.isPear = typeof Pear !== "undefined";
|
|
28
|
+
exports2.isNode = runtime === "node";
|
|
29
|
+
exports2.isBrowser = runtime === "browser";
|
|
30
|
+
exports2.isWindows = platform === "win32";
|
|
31
|
+
exports2.isLinux = platform === "linux";
|
|
32
|
+
exports2.isMac = platform === "darwin";
|
|
33
|
+
exports2.isIOS = platform === "ios" || platform === "ios-simulator";
|
|
34
|
+
exports2.isAndroid = platform === "android";
|
|
35
|
+
exports2.isElectron = typeof process !== "undefined" && !!global.process.versions?.electron;
|
|
36
|
+
exports2.isElectronRenderer = exports2.isElectron && global.process.type === "renderer";
|
|
37
|
+
exports2.isElectronWorker = exports2.isElectron && global.process.type === "worker";
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// scripts/native-lock-binding.cjs
|
|
42
|
+
var require_native_lock_binding = __commonJS({
|
|
43
|
+
"scripts/native-lock-binding.cjs"(exports2, module2) {
|
|
44
|
+
var { join } = require("node:path");
|
|
45
|
+
var platform = `${process.platform}-${process.arch}`;
|
|
46
|
+
if (!["darwin", "linux", "win32"].includes(process.platform) || !["arm64", "x64"].includes(process.arch)) {
|
|
47
|
+
const error = new Error(`Native file locking is unavailable for ${platform}. Supported platforms: Windows 10+, macOS 13.0+, and Linux with glibc 2.28+ on x64 or arm64. The OS must also meet the installed Node.js version's requirements.`);
|
|
48
|
+
error.code = "NATIVE_LOCK_UNSUPPORTED";
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
module2.exports = require(join(__dirname, "native", platform, "fs-native-extensions.node"));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// node_modules/fs-native-extensions/index.js
|
|
56
|
+
var require_fs_native_extensions = __commonJS({
|
|
57
|
+
"node_modules/fs-native-extensions/index.js"(exports2) {
|
|
58
|
+
var { isWindows } = require_which_runtime();
|
|
59
|
+
var binding = require_native_lock_binding();
|
|
60
|
+
function onwork(err, result) {
|
|
61
|
+
if (err) this.reject(err);
|
|
62
|
+
else this.resolve(result);
|
|
63
|
+
}
|
|
64
|
+
exports2.tryLock = function tryLock2(fd, offset = 0, length = 0, opts = {}) {
|
|
65
|
+
if (typeof offset === "object") {
|
|
66
|
+
opts = offset;
|
|
67
|
+
offset = 0;
|
|
68
|
+
}
|
|
69
|
+
if (typeof length === "object") {
|
|
70
|
+
opts = length;
|
|
71
|
+
length = 0;
|
|
72
|
+
}
|
|
73
|
+
if (typeof opts !== "object" || opts === null) {
|
|
74
|
+
opts = {};
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
binding.tryLock(fd, offset, length, opts.shared !== true);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (err.code === "EAGAIN") return false;
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
exports2.waitForLock = function waitForLock(fd, offset = 0, length = 0, opts = {}) {
|
|
85
|
+
if (typeof offset === "object") {
|
|
86
|
+
opts = offset;
|
|
87
|
+
offset = 0;
|
|
88
|
+
}
|
|
89
|
+
if (typeof length === "object") {
|
|
90
|
+
opts = length;
|
|
91
|
+
length = 0;
|
|
92
|
+
}
|
|
93
|
+
if (typeof opts !== "object" || opts === null) {
|
|
94
|
+
opts = {};
|
|
95
|
+
}
|
|
96
|
+
const req = {
|
|
97
|
+
handle: null,
|
|
98
|
+
resolve: null,
|
|
99
|
+
reject: null
|
|
100
|
+
};
|
|
101
|
+
const promise = new Promise((resolve, reject) => {
|
|
102
|
+
req.resolve = resolve;
|
|
103
|
+
req.reject = reject;
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
req.handle = binding.waitForLock(fd, offset, length, opts.shared !== true, req, onwork);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return Promise.reject(err);
|
|
109
|
+
}
|
|
110
|
+
return promise;
|
|
111
|
+
};
|
|
112
|
+
exports2.waitForLockSync = function waitForLockSync(fd, offset = 0, length = 0, opts = {}) {
|
|
113
|
+
if (typeof offset === "object") {
|
|
114
|
+
opts = offset;
|
|
115
|
+
offset = 0;
|
|
116
|
+
}
|
|
117
|
+
if (typeof length === "object") {
|
|
118
|
+
opts = length;
|
|
119
|
+
length = 0;
|
|
120
|
+
}
|
|
121
|
+
if (typeof opts !== "object" || opts === null) {
|
|
122
|
+
opts = {};
|
|
123
|
+
}
|
|
124
|
+
binding.waitForLockSync(fd, offset, length, opts.shared !== true);
|
|
125
|
+
};
|
|
126
|
+
exports2.tryDowngradeLock = function tryDowngradeLock(fd, offset = 0, length = 0) {
|
|
127
|
+
try {
|
|
128
|
+
binding.tryDowngradeLock(fd, offset, length);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (err.code === "EAGAIN") return false;
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
135
|
+
exports2.waitForDowngradeLock = function downgradeLock(fd, offset = 0, length = 0) {
|
|
136
|
+
const req = {
|
|
137
|
+
handle: null,
|
|
138
|
+
resolve: null,
|
|
139
|
+
reject: null
|
|
140
|
+
};
|
|
141
|
+
const promise = new Promise((resolve, reject) => {
|
|
142
|
+
req.resolve = resolve;
|
|
143
|
+
req.reject = reject;
|
|
144
|
+
});
|
|
145
|
+
try {
|
|
146
|
+
req.handle = binding.waitForDowngradeLock(fd, offset, length, req, onwork);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
return Promise.reject(err);
|
|
149
|
+
}
|
|
150
|
+
return promise;
|
|
151
|
+
};
|
|
152
|
+
exports2.waitForDowngradeLockSync = function waitForDowngradeLockSync(fd, offset = 0, length = 0) {
|
|
153
|
+
binding.waitForDowngradeLockSync(fd, offset, length);
|
|
154
|
+
};
|
|
155
|
+
exports2.tryUpgradeLock = function tryUpgradeLock(fd, offset = 0, length = 0) {
|
|
156
|
+
try {
|
|
157
|
+
binding.tryUpgradeLock(fd, offset, length);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (err.code === "EAGAIN") return false;
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
};
|
|
164
|
+
exports2.waitForUpgradeLock = function upgradeLock(fd, offset = 0, length = 0) {
|
|
165
|
+
const req = {
|
|
166
|
+
handle: null,
|
|
167
|
+
resolve: null,
|
|
168
|
+
reject: null
|
|
169
|
+
};
|
|
170
|
+
const promise = new Promise((resolve, reject) => {
|
|
171
|
+
req.resolve = resolve;
|
|
172
|
+
req.reject = reject;
|
|
173
|
+
});
|
|
174
|
+
try {
|
|
175
|
+
req.handle = binding.waitForUpgradeLock(fd, offset, length, req, onwork);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
return Promise.reject(err);
|
|
178
|
+
}
|
|
179
|
+
return promise;
|
|
180
|
+
};
|
|
181
|
+
exports2.waitForUpgradeLockSync = function waitForUpgradeLockSync(fd, offset = 0, length = 0) {
|
|
182
|
+
binding.waitForUpgradeLockSync(fd, offset, length);
|
|
183
|
+
};
|
|
184
|
+
exports2.unlock = function unlock2(fd, offset = 0, length = 0) {
|
|
185
|
+
binding.unlock(fd, offset, length);
|
|
186
|
+
};
|
|
187
|
+
exports2.trim = function trim(fd, offset, length) {
|
|
188
|
+
const req = {
|
|
189
|
+
handle: null,
|
|
190
|
+
resolve: null,
|
|
191
|
+
reject: null
|
|
192
|
+
};
|
|
193
|
+
const promise = new Promise((resolve, reject) => {
|
|
194
|
+
req.resolve = resolve;
|
|
195
|
+
req.reject = reject;
|
|
196
|
+
});
|
|
197
|
+
try {
|
|
198
|
+
req.handle = binding.trim(fd, offset, length, req, onwork);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return Promise.reject(err);
|
|
201
|
+
}
|
|
202
|
+
return promise;
|
|
203
|
+
};
|
|
204
|
+
exports2.trimSync = function trimSync(fd, offset, length) {
|
|
205
|
+
binding.trimSync(fd, offset, length);
|
|
206
|
+
};
|
|
207
|
+
exports2.sparse = function sparse(fd) {
|
|
208
|
+
if (!isWindows) return Promise.resolve();
|
|
209
|
+
const req = {
|
|
210
|
+
handle: null,
|
|
211
|
+
resolve: null,
|
|
212
|
+
reject: null
|
|
213
|
+
};
|
|
214
|
+
const promise = new Promise((resolve, reject) => {
|
|
215
|
+
req.resolve = resolve;
|
|
216
|
+
req.reject = reject;
|
|
217
|
+
});
|
|
218
|
+
try {
|
|
219
|
+
req.handle = binding.sparse(fd, req, onwork);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return Promise.reject(err);
|
|
222
|
+
}
|
|
223
|
+
return promise;
|
|
224
|
+
};
|
|
225
|
+
exports2.sparseSync = function sparseSync(fd) {
|
|
226
|
+
if (!isWindows) return;
|
|
227
|
+
binding.sparseSync(fd);
|
|
228
|
+
};
|
|
229
|
+
exports2.swap = function swap(from, to) {
|
|
230
|
+
const req = {
|
|
231
|
+
handle: null,
|
|
232
|
+
resolve: null,
|
|
233
|
+
reject: null
|
|
234
|
+
};
|
|
235
|
+
const promise = new Promise((resolve, reject) => {
|
|
236
|
+
req.resolve = resolve;
|
|
237
|
+
req.reject = reject;
|
|
238
|
+
});
|
|
239
|
+
try {
|
|
240
|
+
req.handle = binding.swap(from, to, req, onwork);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
return Promise.reject(err);
|
|
243
|
+
}
|
|
244
|
+
return promise;
|
|
245
|
+
};
|
|
246
|
+
exports2.swapSync = function swapSync(from, to) {
|
|
247
|
+
binding.swapSync(from, to);
|
|
248
|
+
};
|
|
249
|
+
exports2.getAttr = function getAttr(fd, name) {
|
|
250
|
+
const req = {
|
|
251
|
+
handle: null,
|
|
252
|
+
resolve: null,
|
|
253
|
+
reject: null
|
|
254
|
+
};
|
|
255
|
+
const promise = new Promise((resolve, reject) => {
|
|
256
|
+
req.resolve = resolve;
|
|
257
|
+
req.reject = reject;
|
|
258
|
+
});
|
|
259
|
+
try {
|
|
260
|
+
req.handle = binding.getAttr(fd, name, req, onwork);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
return Promise.reject(err);
|
|
263
|
+
}
|
|
264
|
+
return promise.then((buffer) => buffer === null ? null : Buffer.from(buffer));
|
|
265
|
+
};
|
|
266
|
+
exports2.getAttrSync = function getAttrSync(fd, name) {
|
|
267
|
+
const buffer = binding.getAttrSync(fd, name);
|
|
268
|
+
return buffer === null ? null : Buffer.from(buffer);
|
|
269
|
+
};
|
|
270
|
+
exports2.setAttr = function setAttr(fd, name, value, encoding) {
|
|
271
|
+
if (typeof value === "string") value = Buffer.from(value, encoding);
|
|
272
|
+
const req = {
|
|
273
|
+
value,
|
|
274
|
+
handle: null,
|
|
275
|
+
resolve: null,
|
|
276
|
+
reject: null
|
|
277
|
+
};
|
|
278
|
+
const promise = new Promise((resolve, reject) => {
|
|
279
|
+
req.resolve = resolve;
|
|
280
|
+
req.reject = reject;
|
|
281
|
+
});
|
|
282
|
+
try {
|
|
283
|
+
req.handle = binding.setAttr(
|
|
284
|
+
fd,
|
|
285
|
+
name,
|
|
286
|
+
value.buffer,
|
|
287
|
+
value.byteOffset,
|
|
288
|
+
value.byteLength,
|
|
289
|
+
req,
|
|
290
|
+
onwork
|
|
291
|
+
);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
return Promise.reject(err);
|
|
294
|
+
}
|
|
295
|
+
return promise;
|
|
296
|
+
};
|
|
297
|
+
exports2.setAttrSync = function setAttrSync(fd, name, value, encoding) {
|
|
298
|
+
if (typeof value === "string") value = Buffer.from(value, encoding);
|
|
299
|
+
binding.setAttrSync(fd, name, value.buffer, value.byteOffset, value.byteLength);
|
|
300
|
+
};
|
|
301
|
+
exports2.removeAttr = function removeAttr(fd, name) {
|
|
302
|
+
const req = {
|
|
303
|
+
handle: null,
|
|
304
|
+
resolve: null,
|
|
305
|
+
reject: null
|
|
306
|
+
};
|
|
307
|
+
const promise = new Promise((resolve, reject) => {
|
|
308
|
+
req.resolve = resolve;
|
|
309
|
+
req.reject = reject;
|
|
310
|
+
});
|
|
311
|
+
try {
|
|
312
|
+
req.handle = binding.removeAttr(fd, name, req, onwork);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
return Promise.reject(err);
|
|
315
|
+
}
|
|
316
|
+
return promise;
|
|
317
|
+
};
|
|
318
|
+
exports2.removeAttrSync = function removeAttrSync(fd, name) {
|
|
319
|
+
binding.removeAttrSync(fd, name);
|
|
320
|
+
};
|
|
321
|
+
exports2.listAttrs = function listAttrs(fd) {
|
|
322
|
+
const req = {
|
|
323
|
+
handle: null,
|
|
324
|
+
resolve: null,
|
|
325
|
+
reject: null
|
|
326
|
+
};
|
|
327
|
+
const promise = new Promise((resolve, reject) => {
|
|
328
|
+
req.resolve = resolve;
|
|
329
|
+
req.reject = reject;
|
|
330
|
+
});
|
|
331
|
+
try {
|
|
332
|
+
req.handle = binding.listAttrs(fd, req, onwork);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
return Promise.reject(err);
|
|
335
|
+
}
|
|
336
|
+
return promise;
|
|
337
|
+
};
|
|
338
|
+
exports2.listAttrsSync = function listAttrsSync(fd) {
|
|
339
|
+
return binding.listAttrsSync(fd);
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// scripts/native-lock.cjs
|
|
345
|
+
var { tryLock, unlock } = require_fs_native_extensions();
|
|
346
|
+
module.exports = { tryLock, unlock };
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// src/host/omp/extension.ts
|
|
2
|
+
import { homedir as homedir2 } from "node:os";
|
|
3
|
+
import { dirname as dirname5, join as join5 } from "node:path";
|
|
4
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5
|
+
|
|
6
|
+
// src/hook/session-start.ts
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { existsSync, readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
|
+
|
|
13
|
+
// src/domain/types.ts
|
|
14
|
+
var ok = (value) => ({ ok: true, value });
|
|
15
|
+
var err = (error) => ({ ok: false, error });
|
|
16
|
+
var RANK_MIN = 0;
|
|
17
|
+
var RANK_MAX = 99;
|
|
18
|
+
var RANK_RULE_TEXT = `an integer in ${RANK_MIN}..${RANK_MAX}, 0 = bottom / most primitive`;
|
|
19
|
+
|
|
20
|
+
// src/store/pages.ts
|
|
21
|
+
import { basename, dirname, join } from "node:path";
|
|
22
|
+
var STORE_DIR_NAME = ".mellos";
|
|
23
|
+
var STATE_FILE_RELATIVE_PATH = join(STORE_DIR_NAME, "map.json");
|
|
24
|
+
var PAGES_DIR_NAME = "pages";
|
|
25
|
+
|
|
26
|
+
// src/store/json-text.ts
|
|
27
|
+
function isRecord(v) {
|
|
28
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
29
|
+
}
|
|
30
|
+
function stripBom(text) {
|
|
31
|
+
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/store/policy.ts
|
|
35
|
+
import { readFileSync } from "node:fs";
|
|
36
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
37
|
+
var CONFIG_FILE_NAME = "config.json";
|
|
38
|
+
var CONFIG_FILE_VERSION = 1;
|
|
39
|
+
function configFilePath(defaultFile) {
|
|
40
|
+
return join2(dirname2(defaultFile), CONFIG_FILE_NAME);
|
|
41
|
+
}
|
|
42
|
+
function userConfigFilePath(userBase) {
|
|
43
|
+
return join2(userBase, STORE_DIR_NAME, CONFIG_FILE_NAME);
|
|
44
|
+
}
|
|
45
|
+
var MAPPING_POLICIES = ["always", "complex", "on-request"];
|
|
46
|
+
function makeMappingPolicy(raw) {
|
|
47
|
+
return MAPPING_POLICIES.includes(raw) ? ok(raw) : err({ kind: "invalid-policy", raw, allowed: MAPPING_POLICIES });
|
|
48
|
+
}
|
|
49
|
+
function describeMappingPolicy(policy) {
|
|
50
|
+
switch (policy) {
|
|
51
|
+
case "always":
|
|
52
|
+
return "map every structured task \u2014 workflows, designs, architecture, technical dependencies";
|
|
53
|
+
case "complex":
|
|
54
|
+
return "map only medium or complex tasks \u2014 several modules, a new subsystem, roughly an hour or more";
|
|
55
|
+
case "on-request":
|
|
56
|
+
return "map only when the user explicitly asks";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function loadMappingPolicy(path) {
|
|
60
|
+
let text;
|
|
61
|
+
try {
|
|
62
|
+
text = readFileSync(path, "utf8");
|
|
63
|
+
} catch (e) {
|
|
64
|
+
if (e.code === "ENOENT") return ok(void 0);
|
|
65
|
+
throw e;
|
|
66
|
+
}
|
|
67
|
+
let raw;
|
|
68
|
+
try {
|
|
69
|
+
raw = JSON.parse(stripBom(text));
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return err({ kind: "malformed-json", path, detail: e.message });
|
|
72
|
+
}
|
|
73
|
+
if (!isRecord(raw)) return err({ kind: "bad-shape", path, detail: "root is not an object" });
|
|
74
|
+
if (raw["version"] !== CONFIG_FILE_VERSION) {
|
|
75
|
+
return err({ kind: "bad-shape", path, detail: `version is ${String(raw["version"])}, expected ${CONFIG_FILE_VERSION}` });
|
|
76
|
+
}
|
|
77
|
+
const rawPolicy = raw["policy"];
|
|
78
|
+
if (rawPolicy === void 0) return ok(void 0);
|
|
79
|
+
if (typeof rawPolicy !== "string") return err({ kind: "bad-shape", path, detail: "policy is not a string" });
|
|
80
|
+
const policy = makeMappingPolicy(rawPolicy);
|
|
81
|
+
return policy.ok ? ok(policy.value) : err({ kind: "bad-shape", path, detail: `policy is "${rawPolicy}", expected one of: ${MAPPING_POLICIES.join(" | ")}` });
|
|
82
|
+
}
|
|
83
|
+
function effectiveMappingPolicy(projectConfigFile, userConfigFile) {
|
|
84
|
+
const project = loadMappingPolicy(projectConfigFile);
|
|
85
|
+
if (!project.ok) return project;
|
|
86
|
+
const user = loadMappingPolicy(userConfigFile);
|
|
87
|
+
if (!user.ok) return user;
|
|
88
|
+
const effective = project.value ?? user.value;
|
|
89
|
+
const source = project.value !== void 0 ? "project" : user.value !== void 0 ? "user" : void 0;
|
|
90
|
+
return ok({ project: project.value, user: user.value, effective, source });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/store/migration.ts
|
|
94
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
95
|
+
var LEGACY_STATE_FILE_RELATIVE_PATH = join3(".claude", "mellos-mapping.json");
|
|
96
|
+
|
|
97
|
+
// src/hook/session-start.ts
|
|
98
|
+
function sessionStartContext(input) {
|
|
99
|
+
const { policy, hasStore } = input;
|
|
100
|
+
if (policy === void 0) {
|
|
101
|
+
return [
|
|
102
|
+
"mellos-mapping \u2014 first-run setup. This question is asked ONCE EVER, not once per project.",
|
|
103
|
+
"Before starting structured work here, ask the user how eager mapping should be:",
|
|
104
|
+
...MAPPING_POLICIES.map((p) => ` ${p} \u2014 ${describeMappingPolicy(p)}`),
|
|
105
|
+
'Record their answer with mmap_setup {policy: "<their choice>", scope: "user"} \u2014 it then applies',
|
|
106
|
+
'to every project they open, and a single project can still override it with scope: "project".',
|
|
107
|
+
"Do not choose for them, and do not ask again once it is recorded."
|
|
108
|
+
].join("\n");
|
|
109
|
+
}
|
|
110
|
+
if (policy === "on-request") {
|
|
111
|
+
return hasStore ? "mellos-mapping: this project has a Mellos map. The user's policy is on-request \u2014 use the mmap tools only when they ask." : void 0;
|
|
112
|
+
}
|
|
113
|
+
const scope = policy === "always" ? "For ANY structured task in this project \u2014 workflows, designs, architecture, technical dependencies:" : "For any MEDIUM OR COMPLEX task in this project \u2014 several modules, a new subsystem, roughly an hour of work or more (skip trivial edits):";
|
|
114
|
+
return [
|
|
115
|
+
`mellos-mapping policy: ${policy} \u2014 ${describeMappingPolicy(policy)}.`,
|
|
116
|
+
scope,
|
|
117
|
+
" 1. Load the mellos-mapping skill and follow it.",
|
|
118
|
+
" 2. Read existing pages and context with mmap_read; a new conversation is not a new effort.",
|
|
119
|
+
" Reuse the matching page and verified nodes. Use mmap_declare only for missing structure.",
|
|
120
|
+
" 3. Open the map pane WITHOUT asking \u2014 the recorded policy is the user's standing consent:",
|
|
121
|
+
' mmap_open {page: "<the page this effort lives on>"}',
|
|
122
|
+
" It opens the pane beside this conversation, or retargets one that is already open.",
|
|
123
|
+
" 4. Watch the `pane:` line every write answers with: it says whether anybody is actually",
|
|
124
|
+
" looking. `pane: CLOSED` means the user cannot see this map \u2014 call mmap_open then too.",
|
|
125
|
+
" 5. Keep the map current as the work proceeds: in-progress when a node is started,",
|
|
126
|
+
" done WITH EVIDENCE when its verification passes, regressed when something breaks.",
|
|
127
|
+
" Save a concise context.next checkpoint and use expectedRevision for incremental writes.",
|
|
128
|
+
"An explicit request from the user always outranks this."
|
|
129
|
+
].join("\n");
|
|
130
|
+
}
|
|
131
|
+
function hasMap(stateFile) {
|
|
132
|
+
return existsSync(stateFile) || existsSync(join4(dirname4(stateFile), PAGES_DIR_NAME));
|
|
133
|
+
}
|
|
134
|
+
function mmapBinDir(localAppData) {
|
|
135
|
+
return join4(localAppData, "mellos-mapping", "bin");
|
|
136
|
+
}
|
|
137
|
+
function mmapShimFilePath(localAppData) {
|
|
138
|
+
return join4(mmapBinDir(localAppData), "mmap.cmd");
|
|
139
|
+
}
|
|
140
|
+
function mmapFallbackDirs(localAppData, home) {
|
|
141
|
+
return [join4(localAppData, "Microsoft", "WindowsApps"), join4(home, ".local", "bin")];
|
|
142
|
+
}
|
|
143
|
+
function pathNames(rawPath, dir) {
|
|
144
|
+
const norm = (s) => s.trim().replace(/^"|"$/g, "").replace(/[\\/]+$/, "").toLowerCase();
|
|
145
|
+
return rawPath.split(";").some((entry) => entry.trim() !== "" && norm(entry) === norm(dir));
|
|
146
|
+
}
|
|
147
|
+
function readIfPresent(path) {
|
|
148
|
+
try {
|
|
149
|
+
return readFileSync2(path, "utf8");
|
|
150
|
+
} catch {
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function mmapShimCurrent(shimContent, mmapPath) {
|
|
155
|
+
if (shimContent === void 0) return false;
|
|
156
|
+
return shimContent.includes(`"${mmapPath}"`) || shimContent.includes(`"${mmapPath.replaceAll("\\", "/")}"`);
|
|
157
|
+
}
|
|
158
|
+
function shimIsOurs(content) {
|
|
159
|
+
if (content.includes("mellos-mapping mmap shim")) return true;
|
|
160
|
+
const quoted = /"([^"]*mmap\.mjs)"/.exec(content)?.[1];
|
|
161
|
+
return quoted !== void 0 && /mellos-mapping/i.test(quoted);
|
|
162
|
+
}
|
|
163
|
+
function mmapCommandResolves(localAppData, mmapPath, environment, read, exists) {
|
|
164
|
+
if (pathNames(environment.path, mmapBinDir(localAppData))) return true;
|
|
165
|
+
if (environment.home === void 0 || environment.home === "") return false;
|
|
166
|
+
for (const dir of mmapFallbackDirs(localAppData, environment.home)) {
|
|
167
|
+
if (!exists(dir) || !pathNames(environment.path, dir)) continue;
|
|
168
|
+
const files = ["mmap.cmd", "mmap"].map((name) => read(join4(dir, name))).filter((content) => content !== void 0);
|
|
169
|
+
if (files.length === 0) continue;
|
|
170
|
+
const ours = files.filter((content) => shimIsOurs(content));
|
|
171
|
+
if (ours.length === 0) return true;
|
|
172
|
+
return ours.every((content) => mmapShimCurrent(content, mmapPath));
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
function installContextLine(outcome) {
|
|
177
|
+
if (typeof outcome !== "object" || outcome === null) return void 0;
|
|
178
|
+
const o = outcome;
|
|
179
|
+
if (o.kind !== "installed" || typeof o.binDir !== "string") return void 0;
|
|
180
|
+
if (o.path === "updated") {
|
|
181
|
+
return [
|
|
182
|
+
"mellos-mapping: the `mmap` terminal command was just installed for the user",
|
|
183
|
+
`(${o.binDir} was added to their user PATH). Typed in any project terminal, \`mmap\``,
|
|
184
|
+
"toggles the map pane. The PATH change reaches only NEW processes \u2014 and a new tab of",
|
|
185
|
+
"a running Windows Terminal inherits the old environment, so if the user says `mmap`",
|
|
186
|
+
"is not recognized, tell them to close Windows Terminal entirely and reopen it."
|
|
187
|
+
].join("\n");
|
|
188
|
+
}
|
|
189
|
+
if (o.path === "refused" || o.path === "error") {
|
|
190
|
+
const reason = typeof o.reason === "string" ? o.reason : "the PATH edit failed";
|
|
191
|
+
const aliasDir = typeof o.alias === "object" && o.alias !== null && "dir" in o.alias && typeof o.alias.dir === "string" ? o.alias.dir : void 0;
|
|
192
|
+
if (aliasDir !== void 0) {
|
|
193
|
+
return [
|
|
194
|
+
`mellos-mapping: the \`mmap\` terminal command should work: its shims live in`,
|
|
195
|
+
`${o.binDir}, and \u2014 because the user PATH could not be changed (${reason}) \u2014 a second copy`,
|
|
196
|
+
`was written to ${aliasDir}, a directory PATH already names (so \`mmap\` resolves there unless`,
|
|
197
|
+
"something earlier in PATH claims the name first).",
|
|
198
|
+
"The user can therefore type `mmap` in a new terminal right now; the command opens the map",
|
|
199
|
+
"pane for the project it is typed in, or closes the open one."
|
|
200
|
+
].join("\n");
|
|
201
|
+
}
|
|
202
|
+
return [
|
|
203
|
+
`mellos-mapping: the \`mmap\` command's launcher was written to ${o.binDir},`,
|
|
204
|
+
`but the user PATH was NOT changed: ${reason}.`,
|
|
205
|
+
"If the user wants the `mmap` pane-toggle command, tell them to add that directory to",
|
|
206
|
+
'their user PATH (Settings > "Edit environment variables for their account").'
|
|
207
|
+
].join("\n");
|
|
208
|
+
}
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
function ensureMmapCommand(pluginRoot) {
|
|
212
|
+
if (process.platform !== "win32") return void 0;
|
|
213
|
+
const localAppData = process.env["LOCALAPPDATA"];
|
|
214
|
+
if (localAppData === void 0 || localAppData === "") return void 0;
|
|
215
|
+
const mmapPath = join4(pluginRoot, "dist", "mmap.mjs");
|
|
216
|
+
const shim = readIfPresent(mmapShimFilePath(localAppData));
|
|
217
|
+
const resolves = mmapShimCurrent(shim, mmapPath) && mmapCommandResolves(
|
|
218
|
+
localAppData,
|
|
219
|
+
mmapPath,
|
|
220
|
+
{ path: process.env["PATH"] ?? "", home: process.env["USERPROFILE"] ?? process.env["HOME"] },
|
|
221
|
+
readIfPresent,
|
|
222
|
+
existsSync
|
|
223
|
+
);
|
|
224
|
+
if (resolves) return void 0;
|
|
225
|
+
const runtime = "bun" in process.versions ? "node" : process.execPath;
|
|
226
|
+
const run = spawnSync(
|
|
227
|
+
runtime,
|
|
228
|
+
[join4(pluginRoot, "scripts", "install-mmap-command.mjs"), "--json"],
|
|
229
|
+
{ encoding: "utf8", windowsHide: true, timeout: 15e3 }
|
|
230
|
+
);
|
|
231
|
+
if (run.status !== 0 || typeof run.stdout !== "string") return void 0;
|
|
232
|
+
let outcome;
|
|
233
|
+
try {
|
|
234
|
+
outcome = JSON.parse(run.stdout);
|
|
235
|
+
} catch {
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
return installContextLine(outcome);
|
|
239
|
+
}
|
|
240
|
+
function parseHookInput(raw) {
|
|
241
|
+
let parsed;
|
|
242
|
+
try {
|
|
243
|
+
parsed = JSON.parse(raw);
|
|
244
|
+
} catch {
|
|
245
|
+
return { cwd: void 0 };
|
|
246
|
+
}
|
|
247
|
+
if (typeof parsed !== "object" || parsed === null) return { cwd: void 0 };
|
|
248
|
+
const cwd = parsed.cwd;
|
|
249
|
+
return { cwd: typeof cwd === "string" && cwd !== "" ? cwd : void 0 };
|
|
250
|
+
}
|
|
251
|
+
function hookOutput(additionalContext) {
|
|
252
|
+
return JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext } });
|
|
253
|
+
}
|
|
254
|
+
async function readAll(stream) {
|
|
255
|
+
const chunks = [];
|
|
256
|
+
for await (const chunk of stream) chunks.push(Buffer.from(chunk));
|
|
257
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
258
|
+
}
|
|
259
|
+
async function main() {
|
|
260
|
+
const raw = process.stdin.isTTY === true ? "" : await readAll(process.stdin);
|
|
261
|
+
const projectDir = parseHookInput(raw).cwd ?? process.cwd();
|
|
262
|
+
const stateFile = join4(projectDir, STATE_FILE_RELATIVE_PATH);
|
|
263
|
+
const scopes = effectiveMappingPolicy(configFilePath(stateFile), userConfigFilePath(homedir()));
|
|
264
|
+
if (!scopes.ok) return;
|
|
265
|
+
const pluginRoot = dirname4(dirname4(fileURLToPath(import.meta.url)));
|
|
266
|
+
const context = sessionStartContext({
|
|
267
|
+
policy: scopes.value.effective,
|
|
268
|
+
hasStore: hasMap(stateFile)
|
|
269
|
+
});
|
|
270
|
+
let installNote;
|
|
271
|
+
try {
|
|
272
|
+
installNote = ensureMmapCommand(pluginRoot);
|
|
273
|
+
} catch {
|
|
274
|
+
installNote = void 0;
|
|
275
|
+
}
|
|
276
|
+
const parts = [context, installNote].filter((p) => p !== void 0);
|
|
277
|
+
if (parts.length > 0) process.stdout.write(hookOutput(parts.join("\n\n")));
|
|
278
|
+
}
|
|
279
|
+
function launchedAsEntry(argv1, moduleUrl) {
|
|
280
|
+
if (argv1 === void 0) return false;
|
|
281
|
+
try {
|
|
282
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
283
|
+
} catch {
|
|
284
|
+
return pathToFileURL(argv1).href === moduleUrl;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (launchedAsEntry(process.argv[1], import.meta.url)) {
|
|
288
|
+
main().catch(() => process.exit(0));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/host/omp/extension.ts
|
|
292
|
+
var SESSION_CONTEXT_TYPE = "mellos-mapping.session-context";
|
|
293
|
+
function sessionParagraph(projectDir) {
|
|
294
|
+
const stateFile = join5(projectDir, STATE_FILE_RELATIVE_PATH);
|
|
295
|
+
const scopes = effectiveMappingPolicy(configFilePath(stateFile), userConfigFilePath(homedir2()));
|
|
296
|
+
if (!scopes.ok) return void 0;
|
|
297
|
+
return sessionStartContext({ policy: scopes.value.effective, hasStore: hasMap(stateFile) });
|
|
298
|
+
}
|
|
299
|
+
function mellosMappingOmp(pi) {
|
|
300
|
+
const pluginRoot = dirname5(dirname5(fileURLToPath2(import.meta.url)));
|
|
301
|
+
let armed = true;
|
|
302
|
+
let installNote;
|
|
303
|
+
let told;
|
|
304
|
+
const rearm = async () => {
|
|
305
|
+
armed = true;
|
|
306
|
+
};
|
|
307
|
+
pi.setLabel?.("Mellos Mapping");
|
|
308
|
+
pi.on("session_start", async () => {
|
|
309
|
+
armed = true;
|
|
310
|
+
try {
|
|
311
|
+
installNote = ensureMmapCommand(pluginRoot);
|
|
312
|
+
} catch {
|
|
313
|
+
installNote = void 0;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
pi.on("session_switch", rearm);
|
|
317
|
+
pi.on("session_branch", rearm);
|
|
318
|
+
pi.on("session_compact", rearm);
|
|
319
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
320
|
+
const cwd = ctx !== null && typeof ctx === "object" && "cwd" in ctx ? ctx.cwd : void 0;
|
|
321
|
+
const projectDir = typeof cwd === "string" && cwd !== "" ? cwd : process.cwd();
|
|
322
|
+
let paragraph;
|
|
323
|
+
let broken = false;
|
|
324
|
+
try {
|
|
325
|
+
paragraph = sessionParagraph(projectDir);
|
|
326
|
+
} catch {
|
|
327
|
+
paragraph = void 0;
|
|
328
|
+
}
|
|
329
|
+
if (paragraph === void 0) {
|
|
330
|
+
broken = !storeIsReadable(projectDir);
|
|
331
|
+
}
|
|
332
|
+
const content = [paragraph, installNote].filter((part) => part !== void 0).join("\n\n");
|
|
333
|
+
const owed = armed || paragraph !== void 0 && paragraph !== told;
|
|
334
|
+
if (!owed || broken || content === "") {
|
|
335
|
+
if (!broken) armed = false;
|
|
336
|
+
return void 0;
|
|
337
|
+
}
|
|
338
|
+
armed = false;
|
|
339
|
+
told = paragraph;
|
|
340
|
+
const message = {
|
|
341
|
+
customType: SESSION_CONTEXT_TYPE,
|
|
342
|
+
content,
|
|
343
|
+
// Standing instruction, not conversation: the model reads it, and the
|
|
344
|
+
// transcript does not grow a message the user never sent.
|
|
345
|
+
display: false,
|
|
346
|
+
attribution: "agent"
|
|
347
|
+
};
|
|
348
|
+
return { message };
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function storeIsReadable(projectDir) {
|
|
352
|
+
const stateFile = join5(projectDir, STATE_FILE_RELATIVE_PATH);
|
|
353
|
+
return effectiveMappingPolicy(configFilePath(stateFile), userConfigFilePath(homedir2())).ok;
|
|
354
|
+
}
|
|
355
|
+
export {
|
|
356
|
+
SESSION_CONTEXT_TYPE,
|
|
357
|
+
mellosMappingOmp as default,
|
|
358
|
+
sessionParagraph
|
|
359
|
+
};
|