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/README.md
CHANGED
|
@@ -147,7 +147,7 @@ npx -y mellos-mapping
|
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
The map file lands in the client session's working directory
|
|
150
|
-
(`.
|
|
150
|
+
(`.mellos/map.json`). Open the live pane from the same project:
|
|
151
151
|
|
|
152
152
|
```
|
|
153
153
|
npx -y -p mellos-mapping mellos-mapping-watch
|
|
@@ -224,8 +224,8 @@ its own pan, zoom and pinned node. Because every page is its own file, two
|
|
|
224
224
|
Claude sessions writing two pages can never clobber each other — this is
|
|
225
225
|
also the answer to running several Claude sessions in one project.
|
|
226
226
|
|
|
227
|
-
State lives in `.
|
|
228
|
-
`.
|
|
227
|
+
State lives in `.mellos/map.json` (the default page) plus
|
|
228
|
+
`.mellos/pages/<page>.json` for named pages — plain JSON,
|
|
229
229
|
safe to commit if you want the maps' history in git.
|
|
230
230
|
|
|
231
231
|
### Diagram kinds
|
|
@@ -265,6 +265,21 @@ When a hidden sub-map changes in the background, the footer says so.
|
|
|
265
265
|
| `mmap_update` | Record progress: `planned → in-progress → done` (+evidence), `regressed`, group/lane membership, node kind |
|
|
266
266
|
| `mmap_remove` | Revise: drop edges, nodes, groups, lanes, empty bands |
|
|
267
267
|
| `mmap_view` | Render the current map as text inline (optional `zoom`) |
|
|
268
|
+
| `mmap_setup` | Get/set the project's mapping policy — when maps open |
|
|
269
|
+
|
|
270
|
+
### Setup: choose when maps open
|
|
271
|
+
|
|
272
|
+
Each project chooses how eager mapping is, once, via `/mmap setup` (or the
|
|
273
|
+
first time the assistant declares a map — the reply nudges it to ask you):
|
|
274
|
+
|
|
275
|
+
- `always` — map every structured task: workflows, designs, architecture,
|
|
276
|
+
technical dependencies.
|
|
277
|
+
- `complex` — map only medium or complex tasks (the default until configured).
|
|
278
|
+
- `on-request` — map only when you explicitly ask.
|
|
279
|
+
|
|
280
|
+
The choice is stored in `.claude/mellos-mapping.config.json` and guides the
|
|
281
|
+
assistant; it never blocks the tools, and asking for a map explicitly always
|
|
282
|
+
works under any policy.
|
|
268
283
|
|
|
269
284
|
Structural invariants enforced by the tools: layers form a total order by
|
|
270
285
|
rank; every node lives in exactly one layer; edges point **strictly
|
|
@@ -284,7 +299,9 @@ The repo is itself layered bottom-up, and each layer has its spec:
|
|
|
284
299
|
| Layer | Code | Owns |
|
|
285
300
|
| --- | --- | --- |
|
|
286
301
|
| 0 domain | `src/domain/` | the map value, structural invariants, pure ops |
|
|
287
|
-
| 1
|
|
302
|
+
| 1 format | `src/store/format.ts` | the state-file format: replay-validated parse, serialize — I/O-free |
|
|
303
|
+
| 1 store | `src/store/store.ts` | atomic state-file persistence on Node |
|
|
304
|
+
| 1 semantics | `src/semantics/` | medium-neutral view semantics: zoom ladder, group aggregation, sequence flip |
|
|
288
305
|
| 2 apply | `src/server/apply.ts` | tool inputs → transactional op sequences |
|
|
289
306
|
| 3 server | `src/server/server.ts` | the four MCP tools over stdio |
|
|
290
307
|
| 4 render | `src/render/`, `src/watch/` | ASCII renderer and the polling pane |
|
|
@@ -292,6 +309,17 @@ The repo is itself layered bottom-up, and each layer has its spec:
|
|
|
292
309
|
`dist/` is committed deliberately: plugin installation clones this repo and
|
|
293
310
|
runs nothing, so entry points ship bundled.
|
|
294
311
|
|
|
312
|
+
### Library
|
|
313
|
+
|
|
314
|
+
The lower layers are also a library (`npm run build` emits `lib/` with type
|
|
315
|
+
declarations; npm packs it). Subpath exports mirror the source:
|
|
316
|
+
`mellos-mapping/domain/types`, `/domain/ops`, `/format`, `/semantics` are
|
|
317
|
+
**browser-safe** — no Node builtins in their import closure, gated by a test —
|
|
318
|
+
so a graphical client (a web panel, an editor view) can parse state files and
|
|
319
|
+
reuse the exact aggregation and zoom semantics the terminal pane draws with.
|
|
320
|
+
`/store` (Node filesystem persistence) and `/render` (the terminal renderer)
|
|
321
|
+
complete the surface for Node hosts.
|
|
322
|
+
|
|
295
323
|
## License
|
|
296
324
|
|
|
297
325
|
MIT
|
package/README.zh-CN.md
CHANGED
|
@@ -134,7 +134,7 @@ CLI……)都能用标准 stdio 条目接入:
|
|
|
134
134
|
npx -y mellos-mapping
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
地图文件落在客户端会话的工作目录(`.
|
|
137
|
+
地图文件落在客户端会话的工作目录(`.mellos/map.json`)。在同
|
|
138
138
|
一项目里打开实况面板:
|
|
139
139
|
|
|
140
140
|
```
|
|
@@ -204,8 +204,8 @@ npx -y -p mellos-mapping mellos-mapping-watch
|
|
|
204
204
|
每页就是一个独立文件,两个 Claude 会话各写各页永远不会互相覆盖——这也是
|
|
205
205
|
同一项目里跑多个 Claude 会话的正确姿势。
|
|
206
206
|
|
|
207
|
-
地图状态存在 `.
|
|
208
|
-
`.
|
|
207
|
+
地图状态存在 `.mellos/map.json`(默认页)和
|
|
208
|
+
`.mellos/pages/<页名>.json`(命名页)——纯 JSON,想在 git
|
|
209
209
|
里留下地图的历史就把它们提交进去。
|
|
210
210
|
|
|
211
211
|
### 图种
|
|
@@ -242,6 +242,19 @@ npx -y -p mellos-mapping mellos-mapping-watch
|
|
|
242
242
|
| `mmap_update` | 记录进度:`planned → in-progress → done`(附证据)、`regressed`、分组/泳道归属、节点 kind |
|
|
243
243
|
| `mmap_remove` | 修订:删除边、节点、分组、泳道、空层 |
|
|
244
244
|
| `mmap_view` | 把当前地图渲染成文本,直接在对话里看(可选 `zoom` 参数) |
|
|
245
|
+
| `mmap_setup` | 查/设本项目的建图策略——什么时候开地图 |
|
|
246
|
+
|
|
247
|
+
### Setup:选择什么时候建图
|
|
248
|
+
|
|
249
|
+
每个项目选一次建图的积极程度,用 `/mmap setup`(或 AI 第一次 declare 时,
|
|
250
|
+
工具响应会引导它来问你):
|
|
251
|
+
|
|
252
|
+
- `always` —— 任何有结构的任务都建图:流程、设计、架构、技术依赖。
|
|
253
|
+
- `complex` —— 只在中等或复杂任务时建图(未配置时的默认行为)。
|
|
254
|
+
- `on-request` —— 只在你明确要求时建图。
|
|
255
|
+
|
|
256
|
+
选择保存在 `.claude/mellos-mapping.config.json`,用于引导 AI;它从不阻止
|
|
257
|
+
工具本身——无论什么策略,明确要求建图永远有效。
|
|
245
258
|
|
|
246
259
|
工具强制的结构不变量:层按 rank 构成全序;每个节点恰好属于一层;边**严格
|
|
247
260
|
向下**——因此图从构造上就是无环的;节点不能依赖同层兄弟(如果 A 需要
|
|
@@ -259,7 +272,9 @@ npm run verify # 类型检查 + 测试 + 打包
|
|
|
259
272
|
| 层 | 代码 | 职责 |
|
|
260
273
|
| --- | --- | --- |
|
|
261
274
|
| 0 domain | `src/domain/` | 地图值、结构不变量、纯操作 |
|
|
262
|
-
| 1
|
|
275
|
+
| 1 format | `src/store/format.ts` | 状态文件格式:重放校验的解析与序列化,零 I/O |
|
|
276
|
+
| 1 store | `src/store/store.ts` | Node 上的原子化状态文件持久化 |
|
|
277
|
+
| 1 semantics | `src/semantics/` | 媒介无关的视图语义:缩放阶梯、分组聚合、时序翻转 |
|
|
263
278
|
| 2 apply | `src/server/apply.ts` | 工具输入 → 事务性操作序列 |
|
|
264
279
|
| 3 server | `src/server/server.ts` | stdio 上的四个 MCP 工具 |
|
|
265
280
|
| 4 render | `src/render/`、`src/watch/` | ASCII 渲染器和轮询面板 |
|
|
@@ -267,6 +282,16 @@ npm run verify # 类型检查 + 测试 + 打包
|
|
|
267
282
|
`dist/` 是刻意提交的:插件安装就是克隆本仓库、不运行任何东西,所以入口
|
|
268
283
|
文件以打包形式随仓库分发。
|
|
269
284
|
|
|
285
|
+
### 库
|
|
286
|
+
|
|
287
|
+
底部各层同时是一个库(`npm run build` 产出带类型声明的 `lib/`,npm 打包
|
|
288
|
+
收录)。子路径导出与源码结构一一对应:`mellos-mapping/domain/types`、
|
|
289
|
+
`/domain/ops`、`/format`、`/semantics` 是**浏览器安全**的——import 闭包中
|
|
290
|
+
没有任何 Node 内建模块,由测试门禁守护——图形客户端(web 面板、编辑器
|
|
291
|
+
视图)可以直接解析状态文件,并复用与终端面板完全一致的聚合与缩放语义。
|
|
292
|
+
`/store`(Node 文件系统持久化)与 `/render`(终端渲染器)补齐 Node 宿主
|
|
293
|
+
所需的完整表面。
|
|
294
|
+
|
|
270
295
|
## 许可证
|
|
271
296
|
|
|
272
297
|
MIT
|
package/dist/server.mjs
CHANGED
|
@@ -21388,13 +21388,66 @@ function removeLayer(map, id) {
|
|
|
21388
21388
|
return ok({ ...map, layers: map.layers.filter((l) => l.id !== id) });
|
|
21389
21389
|
}
|
|
21390
21390
|
|
|
21391
|
-
// src/
|
|
21391
|
+
// src/semantics/semantics.ts
|
|
21392
21392
|
var ZOOM_MIN = -4;
|
|
21393
21393
|
var ZOOM_MAX = 2;
|
|
21394
21394
|
var ZOOM_DEFAULT = 0;
|
|
21395
21395
|
function clampZoom(n) {
|
|
21396
21396
|
return Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, Math.round(n)));
|
|
21397
21397
|
}
|
|
21398
|
+
function zoomMode(zoom) {
|
|
21399
|
+
if (zoom >= 1) return "detail";
|
|
21400
|
+
if (zoom <= -4) return "overview";
|
|
21401
|
+
return "boxes";
|
|
21402
|
+
}
|
|
21403
|
+
function isNeutralKind(map) {
|
|
21404
|
+
return map.kind !== void 0 && map.kind !== "dev";
|
|
21405
|
+
}
|
|
21406
|
+
function aggregateMap(map) {
|
|
21407
|
+
if (map.groups.length === 0) return void 0;
|
|
21408
|
+
const representative = /* @__PURE__ */ new Map();
|
|
21409
|
+
for (const n of map.nodes) representative.set(n.id, n.group ?? n.id);
|
|
21410
|
+
const nodes = map.groups.map((g) => {
|
|
21411
|
+
const members = map.nodes.filter((n) => n.group === g.id);
|
|
21412
|
+
const done = members.filter((n) => n.status === "done").length;
|
|
21413
|
+
return {
|
|
21414
|
+
id: g.id,
|
|
21415
|
+
// neutral kinds document structure, not progress — no member counts
|
|
21416
|
+
label: isNeutralKind(map) ? g.label : `${g.label} ${done}/${members.length}`,
|
|
21417
|
+
layer: g.layer,
|
|
21418
|
+
status: groupStatus(map, g.id)
|
|
21419
|
+
};
|
|
21420
|
+
});
|
|
21421
|
+
for (const n of map.nodes) if (n.group === void 0) nodes.push(n);
|
|
21422
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21423
|
+
const edges = [];
|
|
21424
|
+
for (const e of map.edges) {
|
|
21425
|
+
const from = representative.get(e.from);
|
|
21426
|
+
const to = representative.get(e.to);
|
|
21427
|
+
if (from === to || seen.has(`${from}->${to}`)) continue;
|
|
21428
|
+
seen.add(`${from}->${to}`);
|
|
21429
|
+
edges.push({ from, to });
|
|
21430
|
+
}
|
|
21431
|
+
return {
|
|
21432
|
+
...map.title !== void 0 ? { title: map.title } : {},
|
|
21433
|
+
...map.kind !== void 0 ? { kind: map.kind } : {},
|
|
21434
|
+
layers: map.layers,
|
|
21435
|
+
groups: [],
|
|
21436
|
+
lanes: map.lanes,
|
|
21437
|
+
nodes,
|
|
21438
|
+
edges
|
|
21439
|
+
};
|
|
21440
|
+
}
|
|
21441
|
+
function flipForSequence(map) {
|
|
21442
|
+
if (map.kind !== "sequence") return map;
|
|
21443
|
+
return {
|
|
21444
|
+
...map,
|
|
21445
|
+
layers: map.layers.map((l) => ({ ...l, rank: -l.rank })),
|
|
21446
|
+
edges: map.edges.map((e) => ({ from: e.to, to: e.from, ...e.label !== void 0 ? { label: e.label } : {} }))
|
|
21447
|
+
};
|
|
21448
|
+
}
|
|
21449
|
+
|
|
21450
|
+
// src/render/render.ts
|
|
21398
21451
|
var WIDE_RANGES = [
|
|
21399
21452
|
[4352, 4447],
|
|
21400
21453
|
// Hangul Jamo
|
|
@@ -21659,9 +21712,6 @@ function kindGlyph(kind, unicode) {
|
|
|
21659
21712
|
const pair = NODE_KIND_GLYPHS[kind];
|
|
21660
21713
|
return pair === void 0 ? void 0 : unicode ? pair[0] : pair[1];
|
|
21661
21714
|
}
|
|
21662
|
-
function isNeutralKind(map) {
|
|
21663
|
-
return map.kind !== void 0 && map.kind !== "dev";
|
|
21664
|
-
}
|
|
21665
21715
|
function neutralSkin(unicode) {
|
|
21666
21716
|
return unicode ? { h: "\u2500", v: "\u2502", corners: ["\u256D", "\u256E", "\u2570", "\u256F"], style: "none" } : { h: "-", v: "|", corners: ["+", "+", "+", "+"], style: "none" };
|
|
21667
21717
|
}
|
|
@@ -21671,21 +21721,23 @@ var LEFT_MARGIN = 2;
|
|
|
21671
21721
|
var DETAIL_BUDGET = { innerMin: 22, innerMax: 32, noteRows: 3 };
|
|
21672
21722
|
var DETAIL_PLUS_BUDGET = { innerMin: 30, innerMax: 48, noteRows: 12 };
|
|
21673
21723
|
function zoomGeometry(zoom) {
|
|
21724
|
+
const m = zoomMode(zoom);
|
|
21725
|
+
const mode = m === "overview" ? "constellation" : m;
|
|
21674
21726
|
switch (zoom) {
|
|
21675
21727
|
case 2:
|
|
21676
|
-
return { mode
|
|
21728
|
+
return { mode, scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false, detail: DETAIL_PLUS_BUDGET };
|
|
21677
21729
|
case 1:
|
|
21678
|
-
return { mode
|
|
21730
|
+
return { mode, scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false, detail: DETAIL_BUDGET };
|
|
21679
21731
|
case 0:
|
|
21680
|
-
return { mode
|
|
21732
|
+
return { mode, scale: 1, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false };
|
|
21681
21733
|
case -1:
|
|
21682
|
-
return { mode
|
|
21734
|
+
return { mode, scale: 0.85, pad: 1, boxGap: BOX_GAP, breathe: 1, titleGap: 1, barGap: 1, bandCounts: false };
|
|
21683
21735
|
case -2:
|
|
21684
|
-
return { mode
|
|
21736
|
+
return { mode, scale: 0.7, pad: 0, boxGap: BOX_GAP, breathe: 0, titleGap: 0, barGap: 1, bandCounts: false };
|
|
21685
21737
|
case -3:
|
|
21686
|
-
return { mode
|
|
21738
|
+
return { mode, scale: 0.55, pad: 0, boxGap: 1, breathe: 0, titleGap: 0, barGap: 1, bandCounts: true };
|
|
21687
21739
|
case -4:
|
|
21688
|
-
return { mode
|
|
21740
|
+
return { mode, scale: 0, pad: 0, boxGap: BOX_GAP, breathe: 0, titleGap: 0, barGap: 1, bandCounts: true };
|
|
21689
21741
|
}
|
|
21690
21742
|
}
|
|
21691
21743
|
var LABEL_BUDGET_MIN = 4;
|
|
@@ -21733,41 +21785,6 @@ function renderMap(map, opts) {
|
|
|
21733
21785
|
const built = buildCanvas(map, opts);
|
|
21734
21786
|
return built.canvas.emit(opts);
|
|
21735
21787
|
}
|
|
21736
|
-
function aggregateMap(map) {
|
|
21737
|
-
if (map.groups.length === 0) return void 0;
|
|
21738
|
-
const representative = /* @__PURE__ */ new Map();
|
|
21739
|
-
for (const n of map.nodes) representative.set(n.id, n.group ?? n.id);
|
|
21740
|
-
const nodes = map.groups.map((g) => {
|
|
21741
|
-
const members = map.nodes.filter((n) => n.group === g.id);
|
|
21742
|
-
const done = members.filter((n) => n.status === "done").length;
|
|
21743
|
-
return {
|
|
21744
|
-
id: g.id,
|
|
21745
|
-
// neutral kinds document structure, not progress — no member counts
|
|
21746
|
-
label: isNeutralKind(map) ? g.label : `${g.label} ${done}/${members.length}`,
|
|
21747
|
-
layer: g.layer,
|
|
21748
|
-
status: groupStatus(map, g.id)
|
|
21749
|
-
};
|
|
21750
|
-
});
|
|
21751
|
-
for (const n of map.nodes) if (n.group === void 0) nodes.push(n);
|
|
21752
|
-
const seen = /* @__PURE__ */ new Set();
|
|
21753
|
-
const edges = [];
|
|
21754
|
-
for (const e of map.edges) {
|
|
21755
|
-
const from = representative.get(e.from);
|
|
21756
|
-
const to = representative.get(e.to);
|
|
21757
|
-
if (from === to || seen.has(`${from}->${to}`)) continue;
|
|
21758
|
-
seen.add(`${from}->${to}`);
|
|
21759
|
-
edges.push({ from, to });
|
|
21760
|
-
}
|
|
21761
|
-
return {
|
|
21762
|
-
...map.title !== void 0 ? { title: map.title } : {},
|
|
21763
|
-
...map.kind !== void 0 ? { kind: map.kind } : {},
|
|
21764
|
-
layers: map.layers,
|
|
21765
|
-
groups: [],
|
|
21766
|
-
lanes: map.lanes,
|
|
21767
|
-
nodes,
|
|
21768
|
-
edges
|
|
21769
|
-
};
|
|
21770
|
-
}
|
|
21771
21788
|
var AGGREGATE_GEO = {
|
|
21772
21789
|
mode: "boxes",
|
|
21773
21790
|
scale: 1,
|
|
@@ -21778,14 +21795,6 @@ var AGGREGATE_GEO = {
|
|
|
21778
21795
|
barGap: 1,
|
|
21779
21796
|
bandCounts: false
|
|
21780
21797
|
};
|
|
21781
|
-
function flipForSequence(map) {
|
|
21782
|
-
if (map.kind !== "sequence") return map;
|
|
21783
|
-
return {
|
|
21784
|
-
...map,
|
|
21785
|
-
layers: map.layers.map((l) => ({ ...l, rank: -l.rank })),
|
|
21786
|
-
edges: map.edges.map((e) => ({ from: e.to, to: e.from, ...e.label !== void 0 ? { label: e.label } : {} }))
|
|
21787
|
-
};
|
|
21788
|
-
}
|
|
21789
21798
|
function buildCanvas(map, opts) {
|
|
21790
21799
|
const oriented = flipForSequence(map);
|
|
21791
21800
|
const plainGeo = zoomGeometry(opts.zoom ?? ZOOM_DEFAULT);
|
|
@@ -22135,12 +22144,9 @@ function drawBox(canvas, box, opts, neutral, focused = false) {
|
|
|
22135
22144
|
// src/store/store.ts
|
|
22136
22145
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
22137
22146
|
import { basename, dirname, join } from "node:path";
|
|
22147
|
+
|
|
22148
|
+
// src/store/format.ts
|
|
22138
22149
|
var STATE_FILE_VERSION = 1;
|
|
22139
|
-
var STATE_FILE_RELATIVE_PATH = join(".claude", "mellos-mapping.json");
|
|
22140
|
-
var PAGES_DIR_NAME = "mellos-mapping.pages";
|
|
22141
|
-
function pageFilePath(defaultFile, page) {
|
|
22142
|
-
return page === void 0 ? defaultFile : join(dirname(defaultFile), PAGES_DIR_NAME, `${page}.json`);
|
|
22143
|
-
}
|
|
22144
22150
|
function describeStoreError(e) {
|
|
22145
22151
|
switch (e.kind) {
|
|
22146
22152
|
case "not-found":
|
|
@@ -22295,6 +22301,84 @@ function serializeMap(map) {
|
|
|
22295
22301
|
};
|
|
22296
22302
|
return JSON.stringify(body, null, 2) + "\n";
|
|
22297
22303
|
}
|
|
22304
|
+
|
|
22305
|
+
// src/store/store.ts
|
|
22306
|
+
var STATE_FILE_RELATIVE_PATH = join(".mellos", "map.json");
|
|
22307
|
+
var PAGES_DIR_NAME = "pages";
|
|
22308
|
+
function pageFilePath(defaultFile, page) {
|
|
22309
|
+
return page === void 0 ? defaultFile : join(dirname(defaultFile), PAGES_DIR_NAME, `${page}.json`);
|
|
22310
|
+
}
|
|
22311
|
+
var CONFIG_FILE_NAME = "config.json";
|
|
22312
|
+
var CONFIG_FILE_VERSION = 1;
|
|
22313
|
+
function configFilePath(defaultFile) {
|
|
22314
|
+
return join(dirname(defaultFile), CONFIG_FILE_NAME);
|
|
22315
|
+
}
|
|
22316
|
+
var MAPPING_POLICIES = ["always", "complex", "on-request"];
|
|
22317
|
+
function makeMappingPolicy(raw) {
|
|
22318
|
+
return MAPPING_POLICIES.includes(raw) ? ok(raw) : err({ kind: "invalid-policy", raw, allowed: MAPPING_POLICIES });
|
|
22319
|
+
}
|
|
22320
|
+
function describeMappingPolicy(policy) {
|
|
22321
|
+
switch (policy) {
|
|
22322
|
+
case "always":
|
|
22323
|
+
return "map every structured task \u2014 workflows, designs, architecture, technical dependencies";
|
|
22324
|
+
case "complex":
|
|
22325
|
+
return "map only medium or complex tasks \u2014 several modules, a new subsystem, roughly an hour or more";
|
|
22326
|
+
case "on-request":
|
|
22327
|
+
return "map only when the user explicitly asks";
|
|
22328
|
+
}
|
|
22329
|
+
}
|
|
22330
|
+
function isRecord2(v) {
|
|
22331
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
22332
|
+
}
|
|
22333
|
+
function loadMappingPolicy(defaultFile) {
|
|
22334
|
+
const path = configFilePath(defaultFile);
|
|
22335
|
+
let text2;
|
|
22336
|
+
try {
|
|
22337
|
+
text2 = readFileSync(path, "utf8");
|
|
22338
|
+
} catch (e) {
|
|
22339
|
+
if (e.code === "ENOENT") return ok(void 0);
|
|
22340
|
+
throw e;
|
|
22341
|
+
}
|
|
22342
|
+
let raw;
|
|
22343
|
+
try {
|
|
22344
|
+
raw = JSON.parse(text2);
|
|
22345
|
+
} catch (e) {
|
|
22346
|
+
return err({ kind: "malformed-json", path, detail: e.message });
|
|
22347
|
+
}
|
|
22348
|
+
if (!isRecord2(raw)) return err({ kind: "bad-shape", path, detail: "root is not an object" });
|
|
22349
|
+
if (raw["version"] !== CONFIG_FILE_VERSION) {
|
|
22350
|
+
return err({ kind: "bad-shape", path, detail: `version is ${String(raw["version"])}, expected ${CONFIG_FILE_VERSION}` });
|
|
22351
|
+
}
|
|
22352
|
+
const rawPolicy = raw["policy"];
|
|
22353
|
+
if (rawPolicy === void 0) return ok(void 0);
|
|
22354
|
+
if (typeof rawPolicy !== "string") return err({ kind: "bad-shape", path, detail: "policy is not a string" });
|
|
22355
|
+
const policy = makeMappingPolicy(rawPolicy);
|
|
22356
|
+
return policy.ok ? ok(policy.value) : err({ kind: "bad-shape", path, detail: `policy is "${rawPolicy}", expected one of: ${MAPPING_POLICIES.join(" | ")}` });
|
|
22357
|
+
}
|
|
22358
|
+
function saveMappingPolicy(defaultFile, policy) {
|
|
22359
|
+
const path = configFilePath(defaultFile);
|
|
22360
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
22361
|
+
const tmp = path + ".tmp";
|
|
22362
|
+
writeFileSync(tmp, JSON.stringify({ version: CONFIG_FILE_VERSION, policy }, null, 2) + "\n", "utf8");
|
|
22363
|
+
renameSync(tmp, path);
|
|
22364
|
+
}
|
|
22365
|
+
var LEGACY_STATE_FILE_RELATIVE_PATH = join(".claude", "mellos-mapping.json");
|
|
22366
|
+
var LEGACY_PAGES_DIR_NAME = "mellos-mapping.pages";
|
|
22367
|
+
var LEGACY_CONFIG_FILE_NAME = "mellos-mapping.config.json";
|
|
22368
|
+
function migrateLegacyStore(defaultFile) {
|
|
22369
|
+
const projectRoot = dirname(dirname(defaultFile));
|
|
22370
|
+
const legacyDefault = join(projectRoot, LEGACY_STATE_FILE_RELATIVE_PATH);
|
|
22371
|
+
const legacyPages = join(dirname(legacyDefault), LEGACY_PAGES_DIR_NAME);
|
|
22372
|
+
const legacyConfig = join(dirname(legacyDefault), LEGACY_CONFIG_FILE_NAME);
|
|
22373
|
+
const hasLegacy = existsSync(legacyDefault) || existsSync(legacyPages) || existsSync(legacyConfig);
|
|
22374
|
+
const hasCurrent = existsSync(defaultFile) || existsSync(join(dirname(defaultFile), PAGES_DIR_NAME)) || existsSync(configFilePath(defaultFile));
|
|
22375
|
+
if (!hasLegacy || hasCurrent) return false;
|
|
22376
|
+
mkdirSync(dirname(defaultFile), { recursive: true });
|
|
22377
|
+
if (existsSync(legacyDefault)) renameSync(legacyDefault, defaultFile);
|
|
22378
|
+
if (existsSync(legacyPages)) renameSync(legacyPages, join(dirname(defaultFile), PAGES_DIR_NAME));
|
|
22379
|
+
if (existsSync(legacyConfig)) renameSync(legacyConfig, configFilePath(defaultFile));
|
|
22380
|
+
return true;
|
|
22381
|
+
}
|
|
22298
22382
|
function loadMapFile(path) {
|
|
22299
22383
|
let text2;
|
|
22300
22384
|
try {
|
|
@@ -22515,7 +22599,7 @@ function summarize(map) {
|
|
|
22515
22599
|
|
|
22516
22600
|
// src/server/server.ts
|
|
22517
22601
|
var SERVER_NAME = "mellos-mapping";
|
|
22518
|
-
var SERVER_VERSION = "0.
|
|
22602
|
+
var SERVER_VERSION = "0.20.0";
|
|
22519
22603
|
var ID = external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "lowercase letters, digits and dashes, 1-64 chars").describe("stable kebab-case identifier");
|
|
22520
22604
|
var PAGE = ID.optional().describe(
|
|
22521
22605
|
"page (parallel map) this call targets; omit for the default page. One effort = one page: start a NEW effort on its own page named after the effort, so concurrent sessions never write over each other and the pane can switch between pages."
|
|
@@ -22552,6 +22636,13 @@ function buildServer(stateFile) {
|
|
|
22552
22636
|
saveMapFile(file, applied.value);
|
|
22553
22637
|
return text(summarize(applied.value) + (page !== void 0 ? ` [page: ${page}]` : ""));
|
|
22554
22638
|
};
|
|
22639
|
+
const setupNudge = () => {
|
|
22640
|
+
const policy = loadMappingPolicy(stateFile);
|
|
22641
|
+
if (!policy.ok) return `
|
|
22642
|
+
note: ${describeStoreError(policy.error)} \u2014 fix it or rerun setup (mmap_setup).`;
|
|
22643
|
+
if (policy.value !== void 0) return "";
|
|
22644
|
+
return "\nnote: mapping policy not set for this project. Ask the user when maps should open \u2014 " + MAPPING_POLICIES.map((p) => `${p} (${describeMappingPolicy(p)})`).join("; ") + " \u2014 then record the answer with mmap_setup.";
|
|
22645
|
+
};
|
|
22555
22646
|
server.registerTool(
|
|
22556
22647
|
"mmap_declare",
|
|
22557
22648
|
{
|
|
@@ -22599,7 +22690,12 @@ function buildServer(stateFile) {
|
|
|
22599
22690
|
edges: external_exports.array(EDGE.extend({ label: external_exports.string().max(80).optional().describe("what flows along the edge") })).optional()
|
|
22600
22691
|
}
|
|
22601
22692
|
},
|
|
22602
|
-
(input) =>
|
|
22693
|
+
(input) => {
|
|
22694
|
+
const result = mutate(input.page, (map) => applyDeclare(map, input));
|
|
22695
|
+
if (result.isError === true) return result;
|
|
22696
|
+
const nudge = setupNudge();
|
|
22697
|
+
return nudge === "" ? result : text((result.content[0]?.text ?? "") + nudge);
|
|
22698
|
+
}
|
|
22603
22699
|
);
|
|
22604
22700
|
server.registerTool(
|
|
22605
22701
|
"mmap_update",
|
|
@@ -22641,6 +22737,31 @@ function buildServer(stateFile) {
|
|
|
22641
22737
|
},
|
|
22642
22738
|
(input) => mutate(input.page, (map) => applyRemove(map, input))
|
|
22643
22739
|
);
|
|
22740
|
+
server.registerTool(
|
|
22741
|
+
"mmap_setup",
|
|
22742
|
+
{
|
|
22743
|
+
title: "Configure when maps open",
|
|
22744
|
+
description: `Get or set this project's mapping policy \u2014 WHEN the assistant opens a Mellos map. Call with no arguments to read it. If it reports "not set", ask the USER to choose (never pick for them): always = ` + describeMappingPolicy("always") + "; complex = " + describeMappingPolicy("complex") + "; on-request = " + describeMappingPolicy("on-request") + ". Then call again with their choice to persist it. The policy guides you; it never blocks the tools, and an explicit user request for a map always wins.",
|
|
22745
|
+
inputSchema: {
|
|
22746
|
+
policy: external_exports.enum(MAPPING_POLICIES).optional().describe("the user's choice to persist; omit to read the current policy")
|
|
22747
|
+
}
|
|
22748
|
+
},
|
|
22749
|
+
(input) => {
|
|
22750
|
+
if (input.policy !== void 0) {
|
|
22751
|
+
const policy = input.policy;
|
|
22752
|
+
saveMappingPolicy(stateFile, policy);
|
|
22753
|
+
return text(`mapping policy set: ${policy} \u2014 ${describeMappingPolicy(policy)} [${configFilePath(stateFile)}]`);
|
|
22754
|
+
}
|
|
22755
|
+
const loaded = loadMappingPolicy(stateFile);
|
|
22756
|
+
if (!loaded.ok) return text(describeStoreError(loaded.error), true);
|
|
22757
|
+
if (loaded.value === void 0) {
|
|
22758
|
+
return text(
|
|
22759
|
+
"mapping policy not set. Ask the user to choose one of: " + MAPPING_POLICIES.map((p) => `${p} (${describeMappingPolicy(p)})`).join("; ") + " \u2014 then call mmap_setup with their choice. Until then act as complex."
|
|
22760
|
+
);
|
|
22761
|
+
}
|
|
22762
|
+
return text(`mapping policy: ${loaded.value} \u2014 ${describeMappingPolicy(loaded.value)}`);
|
|
22763
|
+
}
|
|
22764
|
+
);
|
|
22644
22765
|
server.registerTool(
|
|
22645
22766
|
"mmap_view",
|
|
22646
22767
|
{
|
|
@@ -22665,7 +22786,9 @@ function resolveStateFile(env, cwd) {
|
|
|
22665
22786
|
return join2(projectDir, STATE_FILE_RELATIVE_PATH);
|
|
22666
22787
|
}
|
|
22667
22788
|
async function main() {
|
|
22668
|
-
const
|
|
22789
|
+
const stateFile = resolveStateFile(process.env, process.cwd());
|
|
22790
|
+
migrateLegacyStore(stateFile);
|
|
22791
|
+
const server = buildServer(stateFile);
|
|
22669
22792
|
await server.connect(new StdioServerTransport());
|
|
22670
22793
|
}
|
|
22671
22794
|
function launchedAsEntry(argv1, moduleUrl) {
|