mellos-mapping 0.22.1 → 0.23.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 +11 -4
- package/README.zh-CN.md +10 -4
- package/dist/hook-session-start.mjs +3 -1
- package/dist/mmap.mjs +1 -1
- package/dist/preview.mjs +41 -2
- package/dist/server.mjs +857 -412
- package/dist/store-paths.mjs +29 -1
- package/dist/terminal-worker.mjs +443 -300
- package/dist/watch.mjs +443 -300
- package/dist/web.mjs +176 -65
- package/docs/codex.md +7 -1
- package/docs/map-api.md +148 -0
- package/lib/domain/context.d.ts +11 -0
- package/lib/domain/context.js +27 -0
- package/lib/domain/text.js +11 -0
- package/lib/domain/types.d.ts +3 -0
- package/lib/store/format.js +18 -3
- package/lib/store/project.d.ts +2 -0
- package/lib/store/project.js +29 -0
- package/lib/store/store.d.ts +6 -1
- package/lib/store/store.js +6 -1
- package/lib/store/transaction.d.ts +12 -0
- package/lib/store/transaction.js +91 -0
- package/package.json +4 -2
- package/scripts/codex-register.mjs +1 -1
- package/scripts/mmap.mjs +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function sourceError(raw) {
|
|
2
|
+
if (!Array.isArray(raw) || raw.length > 100)
|
|
3
|
+
return 'sources must be an array of at most 100 file references';
|
|
4
|
+
for (const item of raw) {
|
|
5
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
6
|
+
return 'source must be an object';
|
|
7
|
+
const s = item;
|
|
8
|
+
if (Object.keys(s).some(k => k !== 'path' && k !== 'sha256'))
|
|
9
|
+
return 'unknown source field';
|
|
10
|
+
if (typeof s.path !== 'string' || s.path.length > 1024 || !s.path || /[\u0000-\u001f\u007f-\u009f\\:]/.test(s.path) || s.path.startsWith('/') || s.path.split('/').some(p => !p || p === '.' || p === '..'))
|
|
11
|
+
return 'source path must be relative to the project, with forward slashes and no traversal';
|
|
12
|
+
if (s.sha256 !== undefined && (typeof s.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(s.sha256)))
|
|
13
|
+
return 'source sha256 must be a lowercase SHA256 hash';
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
export function contextError(raw) {
|
|
18
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
19
|
+
return 'context must be an object';
|
|
20
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
21
|
+
if (key !== 'summary' && key !== 'next')
|
|
22
|
+
return 'unknown context field';
|
|
23
|
+
if (typeof value !== 'string' || value.length > 2000 || /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/.test(value))
|
|
24
|
+
return 'context fields must be text of at most 2000 characters';
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
package/lib/domain/text.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { contextError, sourceError } from './context.js';
|
|
1
2
|
// Explicit ranges also work in the JSON Schema published by the MCP adapter.
|
|
2
3
|
export const NO_CONTROLS = /^[^\u0000-\u001f\u007f-\u009f]*$/;
|
|
3
4
|
export const NO_CONTROLS_TEXT = 'one line of text; control characters (ESC, newline, tab) are not allowed';
|
|
4
5
|
export const NO_CONTROLS_BUT_BREAKS = /^[^\u0000-\u0008\u000b-\u001f\u007f-\u009f]*$/;
|
|
5
6
|
export const NO_CONTROLS_BUT_BREAKS_TEXT = 'text with optional newlines (\\n) and tabs; other control characters (ESC, BEL, CR) are not allowed';
|
|
6
7
|
export function mapTextError(map) {
|
|
8
|
+
if (map.context !== undefined) {
|
|
9
|
+
const error = contextError(map.context);
|
|
10
|
+
if (error)
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
7
13
|
const check = (field, value, multiline = false) => value === undefined || (multiline ? NO_CONTROLS_BUT_BREAKS : NO_CONTROLS).test(value)
|
|
8
14
|
? undefined : `${field}: ${multiline ? NO_CONTROLS_BUT_BREAKS_TEXT : NO_CONTROLS_TEXT}`;
|
|
9
15
|
let error = check('title', map.title);
|
|
@@ -22,6 +28,11 @@ export function mapTextError(map) {
|
|
|
22
28
|
}
|
|
23
29
|
}
|
|
24
30
|
for (const [i, node] of map.nodes.entries()) {
|
|
31
|
+
if (node.sources !== undefined) {
|
|
32
|
+
const error = sourceError(node.sources);
|
|
33
|
+
if (error)
|
|
34
|
+
return `nodes[${i}]: ${error}`;
|
|
35
|
+
}
|
|
25
36
|
for (const name of ['label', 'evidence', 'detail']) {
|
|
26
37
|
// Version-1 files and document exports also support multiline evidence.
|
|
27
38
|
// MCP keeps its narrower one-line evidence input for concise updates.
|
package/lib/domain/types.d.ts
CHANGED
|
@@ -171,7 +171,9 @@ export interface MapLane {
|
|
|
171
171
|
readonly label: string;
|
|
172
172
|
}
|
|
173
173
|
/** A unit of work living in exactly one band. */
|
|
174
|
+
export type { SourceRef, MapContext } from './context.js';
|
|
174
175
|
export interface MapNode {
|
|
176
|
+
readonly sources?: readonly import('./context.js').SourceRef[];
|
|
175
177
|
readonly id: NodeId;
|
|
176
178
|
readonly label: string;
|
|
177
179
|
readonly layer: LayerId;
|
|
@@ -198,6 +200,7 @@ export interface DepEdge {
|
|
|
198
200
|
}
|
|
199
201
|
/** The whole map. A plain immutable value — operations return new maps. */
|
|
200
202
|
export interface MellosMap {
|
|
203
|
+
readonly context?: import('./context.js').MapContext;
|
|
201
204
|
readonly title?: string;
|
|
202
205
|
/** Presentation intent; absent means 'dev' (the progress ledger). */
|
|
203
206
|
readonly kind?: MapKind;
|
package/lib/store/format.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { declareGroup, declareLane, declareLayer, declareNode, linkNodes, setKind, setTitle, updateNode } from '../domain/ops.js';
|
|
24
24
|
import { mapTextError } from '../domain/text.js';
|
|
25
|
+
import { sourceError, contextError } from '../domain/context.js';
|
|
25
26
|
import { EMPTY_MAP, ID_RULE, ID_RULE_TEXT, describeMapError, err, makeGroupId, makeLaneId, makeLayerId, makeMapKind, makeNodeId, makeNodeKind, makeNodeStatus, makeRank, makeSubmapRef, ok, } from '../domain/types.js';
|
|
26
27
|
/** On-disk format version. Bump only with a documented migration. */
|
|
27
28
|
export const STATE_FILE_VERSION = 1;
|
|
@@ -99,8 +100,8 @@ function optionalString(rec, key, where, path) {
|
|
|
99
100
|
export function parseMap(raw, path) {
|
|
100
101
|
if (!isRecord(raw))
|
|
101
102
|
return err({ kind: 'bad-shape', path, detail: 'root is not an object' });
|
|
102
|
-
if (raw['version'] !== STATE_FILE_VERSION) {
|
|
103
|
-
return err({ kind: 'bad-shape', path, detail: `version is ${String(raw['version'])}, expected ${STATE_FILE_VERSION}` });
|
|
103
|
+
if (raw['version'] !== STATE_FILE_VERSION && raw['version'] !== 2) {
|
|
104
|
+
return err({ kind: 'bad-shape', path, detail: `version is ${String(raw['version'])}, expected ${STATE_FILE_VERSION} or 2` });
|
|
104
105
|
}
|
|
105
106
|
const layers = arrayField(raw, 'layers', path, 'required');
|
|
106
107
|
if (!layers.ok)
|
|
@@ -118,6 +119,12 @@ export function parseMap(raw, path) {
|
|
|
118
119
|
if (!groups.ok)
|
|
119
120
|
return groups;
|
|
120
121
|
let map = EMPTY_MAP;
|
|
122
|
+
if (raw['context'] !== undefined) {
|
|
123
|
+
const error = contextError(raw['context']);
|
|
124
|
+
if (error)
|
|
125
|
+
return err({ kind: 'bad-shape', path, detail: error });
|
|
126
|
+
map = { ...map, context: raw['context'] };
|
|
127
|
+
}
|
|
121
128
|
const title = optionalString(raw, 'title', 'map', path);
|
|
122
129
|
if (!title.ok)
|
|
123
130
|
return title;
|
|
@@ -292,6 +299,12 @@ export function parseMap(raw, path) {
|
|
|
292
299
|
return err({ kind: 'invariant-violation', path, violation: updated.error });
|
|
293
300
|
map = updated.value;
|
|
294
301
|
}
|
|
302
|
+
if (rawNode['sources'] !== undefined) {
|
|
303
|
+
const error = sourceError(rawNode['sources']);
|
|
304
|
+
if (error)
|
|
305
|
+
return err({ kind: 'bad-shape', path, detail: `${where}: ${error}` });
|
|
306
|
+
map = { ...map, nodes: map.nodes.map(n => n.id === id.value ? { ...n, sources: rawNode['sources'] } : n) };
|
|
307
|
+
}
|
|
295
308
|
}
|
|
296
309
|
for (const [i, rawEdge] of edges.value.entries()) {
|
|
297
310
|
const where = `edges[${i}]`;
|
|
@@ -323,7 +336,9 @@ export function parseMap(raw, path) {
|
|
|
323
336
|
/** Serialize a map into the on-disk shape. Inverse of parseMap for valid maps (F1). */
|
|
324
337
|
export function serializeMap(map) {
|
|
325
338
|
const body = {
|
|
326
|
-
|
|
339
|
+
// Older runtimes must refuse maps with provenance rather than silently erasing it.
|
|
340
|
+
version: map.context !== undefined || map.nodes.some(n => n.sources !== undefined) ? 2 : STATE_FILE_VERSION,
|
|
341
|
+
...(map.context !== undefined ? { context: map.context } : {}),
|
|
327
342
|
...(map.title !== undefined ? { title: map.title } : {}),
|
|
328
343
|
...(map.kind !== undefined ? { kind: map.kind } : {}),
|
|
329
344
|
layers: map.layers,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
|
+
/** Nearest existing store or Git root; never cross a nested repository/worktree boundary. */
|
|
5
|
+
export function resolveProjectDirectory(cwd, stopAt = [homedir(), tmpdir()]) {
|
|
6
|
+
let start = resolve(cwd);
|
|
7
|
+
try {
|
|
8
|
+
start = realpathSync(start);
|
|
9
|
+
}
|
|
10
|
+
catch { /* Explicit test/manual paths may not exist yet. */ }
|
|
11
|
+
let dir = start;
|
|
12
|
+
const boundaries = new Set(stopAt.map(path => { try {
|
|
13
|
+
return realpathSync(path);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return resolve(path);
|
|
17
|
+
} }));
|
|
18
|
+
while (true) {
|
|
19
|
+
// A user's global .mellos directory is not an ancestor project's store.
|
|
20
|
+
if (dir !== start && boundaries.has(dir))
|
|
21
|
+
return start;
|
|
22
|
+
if (existsSync(join(dir, '.git')) || existsSync(join(dir, '.mellos', 'map.json')) || existsSync(join(dir, '.mellos', 'pages')))
|
|
23
|
+
return dir;
|
|
24
|
+
const parent = dirname(dir);
|
|
25
|
+
if (parent === dir)
|
|
26
|
+
return start;
|
|
27
|
+
dir = parent;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/lib/store/store.d.ts
CHANGED
|
@@ -16,10 +16,13 @@
|
|
|
16
16
|
* The concurrency model P2 buys, stated plainly:
|
|
17
17
|
* - Several writers may target one project at once. Each save is atomic and
|
|
18
18
|
* lands whole, so a reader never sees half a map — but there is NO
|
|
19
|
-
* lost-update protection
|
|
19
|
+
* lost-update protection in this low-level save API: two saves race, and the last
|
|
20
20
|
* rename wins, silently discarding what the other writer computed from an
|
|
21
21
|
* older read. Pages are the isolation unit (one effort = one page); two
|
|
22
22
|
* sessions that must not clobber each other belong on two pages.
|
|
23
|
+
* Current MCP/viewer writers additionally use transaction.ts to lock the
|
|
24
|
+
* complete read/modify/write operation, with optional revision checks.
|
|
25
|
+
* Direct library saves and older processes do not acquire that lock.
|
|
23
26
|
* - The temp file carries the writer's pid and a random suffix, so
|
|
24
27
|
* concurrent writers never share one and never install each other's
|
|
25
28
|
* half-written content.
|
|
@@ -47,3 +50,5 @@ export { VIEWERS_DIR_NAME, VIEWER_FILE_VERSION, VIEWER_HEARTBEAT_MS, VIEWER_STAL
|
|
|
47
50
|
export { CONFIG_FILE_NAME, CONFIG_FILE_VERSION, configFilePath, userConfigFilePath, MAPPING_POLICIES, type MappingPolicy, type InvalidPolicy, makeMappingPolicy, describeMappingPolicy, loadMappingPolicy, saveMappingPolicy, POLICY_SCOPES, type PolicyScope, type MappingPolicyScopes, effectiveMappingPolicy } from './policy.js';
|
|
48
51
|
export { LEGACY_STATE_FILE_RELATIVE_PATH, migrateLegacyStore } from './migration.js';
|
|
49
52
|
export { loadMapFile, saveMapFile } from './maps.js';
|
|
53
|
+
export { withStoreLock, revisionOf, assertRevision, LedgerError } from './transaction.js';
|
|
54
|
+
export { resolveProjectDirectory } from './project.js';
|
package/lib/store/store.js
CHANGED
|
@@ -16,10 +16,13 @@
|
|
|
16
16
|
* The concurrency model P2 buys, stated plainly:
|
|
17
17
|
* - Several writers may target one project at once. Each save is atomic and
|
|
18
18
|
* lands whole, so a reader never sees half a map — but there is NO
|
|
19
|
-
* lost-update protection
|
|
19
|
+
* lost-update protection in this low-level save API: two saves race, and the last
|
|
20
20
|
* rename wins, silently discarding what the other writer computed from an
|
|
21
21
|
* older read. Pages are the isolation unit (one effort = one page); two
|
|
22
22
|
* sessions that must not clobber each other belong on two pages.
|
|
23
|
+
* Current MCP/viewer writers additionally use transaction.ts to lock the
|
|
24
|
+
* complete read/modify/write operation, with optional revision checks.
|
|
25
|
+
* Direct library saves and older processes do not acquire that lock.
|
|
23
26
|
* - The temp file carries the writer's pid and a random suffix, so
|
|
24
27
|
* concurrent writers never share one and never install each other's
|
|
25
28
|
* half-written content.
|
|
@@ -48,3 +51,5 @@ export { VIEWERS_DIR_NAME, VIEWER_FILE_VERSION, VIEWER_HEARTBEAT_MS, VIEWER_STAL
|
|
|
48
51
|
export { CONFIG_FILE_NAME, CONFIG_FILE_VERSION, configFilePath, userConfigFilePath, MAPPING_POLICIES, makeMappingPolicy, describeMappingPolicy, loadMappingPolicy, saveMappingPolicy, POLICY_SCOPES, effectiveMappingPolicy } from './policy.js';
|
|
49
52
|
export { LEGACY_STATE_FILE_RELATIVE_PATH, migrateLegacyStore } from './migration.js';
|
|
50
53
|
export { loadMapFile, saveMapFile } from './maps.js';
|
|
54
|
+
export { withStoreLock, revisionOf, assertRevision, LedgerError } from './transaction.js';
|
|
55
|
+
export { resolveProjectDirectory } from './project.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MellosMap } from '../domain/types.js';
|
|
2
|
+
export declare class LedgerError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly details: Record<string, unknown>;
|
|
5
|
+
constructor(code: string, message: string, details?: Record<string, unknown>);
|
|
6
|
+
}
|
|
7
|
+
export declare const revisionOf: (map: MellosMap) => string;
|
|
8
|
+
export declare function assertRevision(actual: string, expected?: string): void;
|
|
9
|
+
/** A named page and the default page share the same project lock. */
|
|
10
|
+
export declare function storeDirectory(file: string): string;
|
|
11
|
+
/** No timed polling: contention is an explicit retryable BUSY result. Never steal a live lock. */
|
|
12
|
+
export declare function withStoreLock<T>(file: string, action: () => T): T;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Cooperative, cross-process transactions for MCP and HTTP writers. */
|
|
2
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
5
|
+
import { serializeMap } from './format.js';
|
|
6
|
+
export class LedgerError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
details;
|
|
9
|
+
constructor(code, message, details = {}) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.details = details;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export const revisionOf = (map) => createHash('sha256').update(serializeMap(map)).digest('hex');
|
|
16
|
+
export function assertRevision(actual, expected) {
|
|
17
|
+
if (expected !== undefined && expected !== actual)
|
|
18
|
+
throw new LedgerError('CONFLICT', 'Map changed; read the current revision before retrying.', { expectedRevision: expected, actualRevision: actual });
|
|
19
|
+
}
|
|
20
|
+
/** A named page and the default page share the same project lock. */
|
|
21
|
+
export function storeDirectory(file) {
|
|
22
|
+
const dir = dirname(file);
|
|
23
|
+
return dir.endsWith('/pages') || dir.endsWith('\\pages') ? dirname(dir) : dir;
|
|
24
|
+
}
|
|
25
|
+
function deadOwner(lock) {
|
|
26
|
+
try {
|
|
27
|
+
const owner = JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8'));
|
|
28
|
+
if (!Number.isSafeInteger(owner.pid) || owner.pid <= 0)
|
|
29
|
+
return false;
|
|
30
|
+
try {
|
|
31
|
+
process.kill(owner.pid, 0);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
return e.code === 'ESRCH';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** No timed polling: contention is an explicit retryable BUSY result. Never steal a live lock. */
|
|
43
|
+
export function withStoreLock(file, action) {
|
|
44
|
+
const dir = storeDirectory(file);
|
|
45
|
+
mkdirSync(dir, { recursive: true });
|
|
46
|
+
const lock = join(dir, '.write-lock');
|
|
47
|
+
const owner = join(lock, 'owner.json');
|
|
48
|
+
const token = randomUUID();
|
|
49
|
+
try {
|
|
50
|
+
mkdirSync(lock);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error.code !== 'EEXIST')
|
|
54
|
+
throw error;
|
|
55
|
+
// Reapers serialize inside the old directory and recheck its owner after acquiring.
|
|
56
|
+
// Missing/invalid owners are deliberately not reclaimed on an age heuristic.
|
|
57
|
+
if (!deadOwner(lock))
|
|
58
|
+
throw new LedgerError('BUSY', `Another writer owns ${lock}; retry after it completes. An orphan without owner metadata needs manual inspection.`);
|
|
59
|
+
try {
|
|
60
|
+
mkdirSync(join(lock, '.reap'));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new LedgerError('BUSY', 'Another process is recovering the writer lock.');
|
|
64
|
+
}
|
|
65
|
+
if (!deadOwner(lock)) {
|
|
66
|
+
rmSync(join(lock, '.reap'), { recursive: true, force: true });
|
|
67
|
+
throw new LedgerError('BUSY', 'Writer ownership changed.');
|
|
68
|
+
}
|
|
69
|
+
rmSync(lock, { recursive: true });
|
|
70
|
+
try {
|
|
71
|
+
mkdirSync(lock);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new LedgerError('BUSY', 'Another writer acquired the recovered lock.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
let initialized = false;
|
|
78
|
+
try {
|
|
79
|
+
writeFileSync(owner, JSON.stringify({ pid: process.pid, token }), { flag: 'wx' });
|
|
80
|
+
initialized = true;
|
|
81
|
+
return action();
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
// Only remove the directory still owned by this invocation.
|
|
85
|
+
try {
|
|
86
|
+
if (!initialized || JSON.parse(readFileSync(owner, 'utf8')).token === token)
|
|
87
|
+
rmSync(lock, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
catch { /* A cleanup error must not misreport a successfully committed map as unsaved. */ }
|
|
90
|
+
}
|
|
91
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mellos-mapping",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"mcpName": "io.github.GuangminJu/mellos-mapping",
|
|
5
5
|
"description": "A live layered dependency map for bottom-up development — MCP server + terminal pane. Ghost the design first, then light nodes up from the bottom as they are built and verified.",
|
|
6
6
|
"type": "module",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"lib",
|
|
71
71
|
"README.zh-CN.md",
|
|
72
72
|
"docs/codex.md",
|
|
73
|
+
"docs/map-api.md",
|
|
73
74
|
"scripts/codex-register.mjs",
|
|
74
75
|
"scripts/codex-cli.mjs",
|
|
75
76
|
"scripts/install-mmap-command.mjs",
|
|
@@ -92,9 +93,10 @@
|
|
|
92
93
|
"check:release": "node scripts/check-release.mjs",
|
|
93
94
|
"check:package": "node scripts/check-package-surface.mjs",
|
|
94
95
|
"check:codex": "node scripts/check-codex-package.mjs",
|
|
96
|
+
"check:reuse": "node scripts/check-reuse.mjs",
|
|
95
97
|
"benchmark:render": "node scripts/benchmark-render.mjs",
|
|
96
98
|
"prepack": "npm run build",
|
|
97
|
-
"verify": "npm run typecheck && npm run test && npm run build && npm run check:package && npm run check:codex && npm run check:release"
|
|
99
|
+
"verify": "npm run typecheck && npm run test && npm run build && npm run check:reuse && npm run check:package && npm run check:codex && npm run check:release"
|
|
98
100
|
},
|
|
99
101
|
"devDependencies": {
|
|
100
102
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
@@ -42,7 +42,7 @@ if (launchedAsEntry(import.meta.url)) {
|
|
|
42
42
|
const installed = registerServer(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
43
43
|
console.log(`mellos-mapping MCP registered with Codex: ${installed.nodePath} ${installed.serverPath}`);
|
|
44
44
|
console.log('State files resolve to each session’s working directory (.mellos/map.json).');
|
|
45
|
-
console.log('Start a new Codex conversation to load the skills and
|
|
45
|
+
console.log('Start a new Codex conversation to load the skills and eight mmap tools.');
|
|
46
46
|
} catch (error) {
|
|
47
47
|
console.error(error instanceof Error ? error.message : String(error));
|
|
48
48
|
process.exitCode = 1;
|
package/scripts/mmap.mjs
CHANGED
|
@@ -170,7 +170,7 @@ async function main() {
|
|
|
170
170
|
console.error(parsed.error);
|
|
171
171
|
process.exit(1);
|
|
172
172
|
}
|
|
173
|
-
const candidates =
|
|
173
|
+
const candidates = [store.resolveProjectDirectory(process.cwd())];
|
|
174
174
|
const marker = storeMarkerOf(store.STATE_FILE_RELATIVE_PATH);
|
|
175
175
|
const project = nearestProject(candidates, candidates.map((dir) => existsSync(join(dir, marker))));
|
|
176
176
|
const cfg = { ...parsed.value, projectDir: project.root };
|