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
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ Clone the branch for your host, then run one command. No build is required.
|
|
|
62
62
|
| `chatgpt-app` | ChatGPT desktop, Codex mode | `node install.mjs` |
|
|
63
63
|
|
|
64
64
|
Requires Node.js 18+ and the corresponding host CLI on PATH. The installer checks
|
|
65
|
-
release integrity and all
|
|
65
|
+
release integrity and all eight MCP tools, retains the runtime outside the clone,
|
|
66
66
|
and configures the host. Start a new conversation after installation.
|
|
67
67
|
See [release and branch instructions](docs/releasing.md).
|
|
68
68
|
|
|
@@ -158,7 +158,7 @@ directory they resolved at startup.
|
|
|
158
158
|
|
|
159
159
|
This is Codex mode in the ChatGPT desktop app (also called Codex App).
|
|
160
160
|
From the source branch run the following command; on `chatgpt-app`, omit the
|
|
161
|
-
host argument. It configures the desktop skill, marketplace and
|
|
161
|
+
host argument. It configures the desktop skill, marketplace and eight MCP tools.
|
|
162
162
|
|
|
163
163
|
```
|
|
164
164
|
node install.mjs chatgpt-app
|
|
@@ -239,7 +239,7 @@ servers), then the server process's own working directory. Set
|
|
|
239
239
|
somewhere other than the project you are working in.
|
|
240
240
|
|
|
241
241
|
The skill/discipline layer is Claude Code + Codex specific; other clients
|
|
242
|
-
get the
|
|
242
|
+
get the eight `mmap_*` tools and the pane, and bring their own prompting.
|
|
243
243
|
|
|
244
244
|
## Use
|
|
245
245
|
|
|
@@ -494,12 +494,19 @@ When a hidden sub-map changes in the background, the footer says so.
|
|
|
494
494
|
|
|
495
495
|
## MCP tools
|
|
496
496
|
|
|
497
|
+
Start with `mmap_read` to resume an existing page; a new conversation is not a
|
|
498
|
+
new effort. Use stable IDs, filtered reads and revision-checked writes. The
|
|
499
|
+
[persistent-map API guide](docs/map-api.md) covers complete CRUD, atomic mixed
|
|
500
|
+
batches, context checkpoints, source hashes and worktree boundaries.
|
|
501
|
+
|
|
497
502
|
| Tool | Purpose |
|
|
498
503
|
| --- | --- |
|
|
499
504
|
| `mmap_declare` | Grow the map: title (`null` removes it), diagram kind, layer bands, lanes, groups (subsystems), nodes — with `status`, `evidence`, `detail`, `kind`, `group`, `lane`, `submap` — and edges, optionally labeled (all-or-nothing batch) |
|
|
500
505
|
| `mmap_update` | Record progress **and revise**: status (`planned → in-progress → done` +evidence, `regressed`), relabel a node, move it to another band (`layer`), join/leave a group or lane, set a node kind or a `submap`; rename and re-rank bands (`layers`), relabel groups (`groups`) and lanes (`lanes`); `null` clears any clearable field |
|
|
501
506
|
| `mmap_remove` | Revise: drop edges, nodes, groups, lanes, empty bands — and, with `pages`, whole pages, file and all (permanent; applied after this call's map edits) |
|
|
502
507
|
| `mmap_view` | Render the current map as text inline (optional `zoom`, `-4`…`2`), ending with a `pages:` line naming every page the project has and which one you are looking at |
|
|
508
|
+
| `mmap_read` | Structured page discovery, precise IDs, filtered/paginated reads, context and source changes |
|
|
509
|
+
| `mmap_batch` | One-page mixed create/update/remove transaction with a revision check |
|
|
503
510
|
| `mmap_setup` | Get/set the project's mapping policy — when maps open |
|
|
504
511
|
| `mmap_open` | Put the map on your screen: open the pane, or retarget an open one to a `page` (`window: true` for the dedicated window). It answers with whether a pane actually reported in afterwards, not merely that a command ran — and it can never close one |
|
|
505
512
|
|
|
@@ -624,7 +631,7 @@ The repo is itself layered bottom-up, and each layer has its spec:
|
|
|
624
631
|
| 1 store | `src/store/store.ts` | `store.test.ts`, `atomic-save.test.ts` | atomic state-file persistence on Node |
|
|
625
632
|
| 1 semantics | `src/semantics/` | `semantics.test.ts` | medium-neutral view semantics: zoom ladder, group aggregation, page-set rules, sequence flip, the shared glyph vocabulary |
|
|
626
633
|
| 2 apply | `src/server/apply.ts` | `apply.test.ts` | tool inputs → transactional op sequences |
|
|
627
|
-
| 3 server | `src/server/server.ts` | `server.test.ts`, `save-failure.test.ts` | the
|
|
634
|
+
| 3 server | `src/server/server.ts` | `server.test.ts`, `save-failure.test.ts` | the eight MCP tools over stdio |
|
|
628
635
|
| 4 render | `src/render/` | `render.test.ts`, `routing.test.ts` | the ASCII renderer and its wire routing |
|
|
629
636
|
| 4 pane | `src/watch/` | `watch.test.ts`, `pane-state.test.ts`, `input.test.ts` | the polling pane: page set, input parsing, panel and chrome |
|
|
630
637
|
| — launchers | `scripts/` | `open-pane.test.mjs`, `codex-register.test.mjs` | plain-node entry points |
|
package/README.zh-CN.md
CHANGED
|
@@ -55,7 +55,7 @@ Claude 为你构建系统时,对话旁边的分屏实时显示这个系统的*
|
|
|
55
55
|
| `chatgpt-app` | ChatGPT 桌面 App 的 Codex 模式 | `node install.mjs` |
|
|
56
56
|
|
|
57
57
|
前置要求为 Node.js 18+ 和对应宿主的 CLI,并确保命令在 PATH 中。安装器检查
|
|
58
|
-
|
|
58
|
+
发行文件和八个 MCP 工具,将运行时保留到克隆目录之外,完成宿主配置。
|
|
59
59
|
安装后开启新对话。详见[发布与分支说明](docs/releasing.md)。
|
|
60
60
|
|
|
61
61
|
Claude Code 也可以通过插件市场安装:
|
|
@@ -141,7 +141,7 @@ the move.`):
|
|
|
141
141
|
|
|
142
142
|
本版用于 ChatGPT 桌面 App 的 Codex 模式(也称 Codex App)。在源码分支运行
|
|
143
143
|
以下命令;在 `chatgpt-app` 分支运行时省略宿主参数。它会一次配置桌面专用技能、
|
|
144
|
-
|
|
144
|
+
插件市场与八个 MCP 工具。
|
|
145
145
|
|
|
146
146
|
```
|
|
147
147
|
node install.mjs chatgpt-app
|
|
@@ -223,7 +223,7 @@ npx -y -p mellos-mapping mellos-mapping-watch
|
|
|
223
223
|
插件 MCP 服务器设置的约定),最后才是服务器进程自己的工作目录。如果你的
|
|
224
224
|
客户端会在你实际工作的项目之外启动服务器,就设 `MELLOS_MAPPING_CWD`。
|
|
225
225
|
|
|
226
|
-
技能/纪律层是 Claude Code 与 Codex
|
|
226
|
+
技能/纪律层是 Claude Code 与 Codex 专属的;其他客户端获得八个 `mmap_*`
|
|
227
227
|
工具和面板,提示词自备。
|
|
228
228
|
|
|
229
229
|
## 使用
|
|
@@ -448,12 +448,18 @@ Claude 会话的正确姿势。
|
|
|
448
448
|
|
|
449
449
|
## MCP 工具
|
|
450
450
|
|
|
451
|
+
先用 `mmap_read` 查找并恢复已有页面;新对话不等于新任务。通过稳定 ID、过滤查询
|
|
452
|
+
和版本检查做增量修改。[持久化地图接口说明](docs/map-api.md)包含完整增删改查、
|
|
453
|
+
混合事务、恢复摘要、源码哈希和 worktree 边界。
|
|
454
|
+
|
|
451
455
|
| 工具 | 用途 |
|
|
452
456
|
| --- | --- |
|
|
453
457
|
| `mmap_declare` | 生长地图:标题(传 `null` 删掉)、图种、层级横条、泳道、分组(子系统)、节点(可带 `status`、`evidence`、`detail`、`kind`、`group`、`lane`、`submap`)、边(可带标签);批量,全有或全无 |
|
|
454
458
|
| `mmap_update` | 记录进度**并修订**:状态(`planned → in-progress → done` 附证据、`regressed`)、改节点标签、把节点搬到另一层(`layer`)、加入/退出分组或泳道、设节点 kind 或 `submap`;给层改名和改 rank(`layers`)、给分组改标签(`groups`)、给泳道改标签(`lanes`);任何可清空的字段传 `null` 即清空 |
|
|
455
459
|
| `mmap_remove` | 修订:删除边、节点、分组、泳道、空层——以及用 `pages` 删掉整页,连文件一起(永久;在本次调用的地图修改之后执行) |
|
|
456
460
|
| `mmap_view` | 把当前地图渲染成文本,直接在对话里看(可选 `zoom`,`-4`…`2`);每次响应结尾都有一行 `pages:`,列出本项目有哪些页、以及你正在看哪一页 |
|
|
461
|
+
| `mmap_read` | 结构化页面发现、稳定 ID、过滤分页查询、恢复摘要与源码变化检查 |
|
|
462
|
+
| `mmap_batch` | 单页增删改混合事务与版本冲突检查 |
|
|
457
463
|
| `mmap_setup` | 查/设本项目的建图策略——什么时候开地图 |
|
|
458
464
|
| `mmap_open` | 把地图放到你屏幕上:开面板,或把已开的面板切到某一 `page`(`window: true` 用独立窗口)。它回答的是"事后有没有面板真的报到",而不只是"命令跑过了"——而且它永远关不掉面板 |
|
|
459
465
|
|
|
@@ -561,7 +567,7 @@ npm run verify
|
|
|
561
567
|
| 1 store | `src/store/store.ts` | `store.test.ts`、`atomic-save.test.ts` | Node 上的原子化状态文件持久化 |
|
|
562
568
|
| 1 semantics | `src/semantics/` | `semantics.test.ts` | 媒介无关的视图语义:缩放阶梯、分组聚合、页集规则、时序翻转、共享字形词汇表 |
|
|
563
569
|
| 2 apply | `src/server/apply.ts` | `apply.test.ts` | 工具输入 → 事务性操作序列 |
|
|
564
|
-
| 3 server | `src/server/server.ts` | `server.test.ts`、`save-failure.test.ts` | stdio
|
|
570
|
+
| 3 server | `src/server/server.ts` | `server.test.ts`、`save-failure.test.ts` | stdio 上的八个 MCP 工具 |
|
|
565
571
|
| 4 render | `src/render/` | `render.test.ts`、`routing.test.ts` | ASCII 渲染器与它的走线 |
|
|
566
572
|
| 4 pane | `src/watch/` | `watch.test.ts`、`pane-state.test.ts`、`input.test.ts` | 轮询面板:页集、输入解析、详情面板与外框 |
|
|
567
573
|
| — 启动脚本 | `scripts/` | `open-pane.test.mjs`、`codex-register.test.mjs` | 纯 node 的入口 |
|
|
@@ -113,7 +113,8 @@ function sessionStartContext(input) {
|
|
|
113
113
|
`mellos-mapping policy: ${policy} \u2014 ${describeMappingPolicy(policy)}.`,
|
|
114
114
|
scope,
|
|
115
115
|
" 1. Load the mellos-mapping skill and follow it.",
|
|
116
|
-
" 2.
|
|
116
|
+
" 2. Read existing pages and context with mmap_read; a new conversation is not a new effort.",
|
|
117
|
+
" Reuse the matching page and verified nodes. Use mmap_declare only for missing structure.",
|
|
117
118
|
" 3. Open the map pane WITHOUT asking \u2014 the recorded policy is the user's standing consent:",
|
|
118
119
|
' mmap_open {page: "<the page this effort lives on>"}',
|
|
119
120
|
" It opens the pane beside this conversation, or retargets one that is already open.",
|
|
@@ -121,6 +122,7 @@ function sessionStartContext(input) {
|
|
|
121
122
|
" looking. `pane: CLOSED` means the user cannot see this map \u2014 call mmap_open then too.",
|
|
122
123
|
" 5. Keep the map current as the work proceeds: in-progress when a node is started,",
|
|
123
124
|
" done WITH EVIDENCE when its verification passes, regressed when something breaks.",
|
|
125
|
+
" Save a concise context.next checkpoint and use expectedRevision for incremental writes.",
|
|
124
126
|
"An explicit request from the user always outranks this."
|
|
125
127
|
].join("\n");
|
|
126
128
|
}
|
package/dist/mmap.mjs
CHANGED
|
@@ -418,7 +418,7 @@ async function main() {
|
|
|
418
418
|
console.error(parsed.error);
|
|
419
419
|
process.exit(1);
|
|
420
420
|
}
|
|
421
|
-
const candidates =
|
|
421
|
+
const candidates = [store.resolveProjectDirectory(process.cwd())];
|
|
422
422
|
const marker = storeMarkerOf(store.STATE_FILE_RELATIVE_PATH);
|
|
423
423
|
const project = nearestProject(candidates, candidates.map((dir) => existsSync2(join2(dir, marker))));
|
|
424
424
|
const cfg = { ...parsed.value, projectDir: project.root };
|
package/dist/preview.mjs
CHANGED
|
@@ -222,12 +222,37 @@ function updateNode(map, input) {
|
|
|
222
222
|
return ok({ ...map, nodes: map.nodes.map((n) => n.id === input.id ? updated : n) });
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
// src/domain/context.ts
|
|
226
|
+
function sourceError(raw) {
|
|
227
|
+
if (!Array.isArray(raw) || raw.length > 100) return "sources must be an array of at most 100 file references";
|
|
228
|
+
for (const item of raw) {
|
|
229
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return "source must be an object";
|
|
230
|
+
const s = item;
|
|
231
|
+
if (Object.keys(s).some((k) => k !== "path" && k !== "sha256")) return "unknown source field";
|
|
232
|
+
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 === "..")) return "source path must be relative to the project, with forward slashes and no traversal";
|
|
233
|
+
if (s.sha256 !== void 0 && (typeof s.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(s.sha256))) return "source sha256 must be a lowercase SHA256 hash";
|
|
234
|
+
}
|
|
235
|
+
return void 0;
|
|
236
|
+
}
|
|
237
|
+
function contextError(raw) {
|
|
238
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return "context must be an object";
|
|
239
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
240
|
+
if (key !== "summary" && key !== "next") return "unknown context field";
|
|
241
|
+
if (typeof value !== "string" || value.length > 2e3 || /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/.test(value)) return "context fields must be text of at most 2000 characters";
|
|
242
|
+
}
|
|
243
|
+
return void 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
225
246
|
// src/domain/text.ts
|
|
226
247
|
var NO_CONTROLS = /^[^\u0000-\u001f\u007f-\u009f]*$/;
|
|
227
248
|
var NO_CONTROLS_TEXT = "one line of text; control characters (ESC, newline, tab) are not allowed";
|
|
228
249
|
var NO_CONTROLS_BUT_BREAKS = /^[^\u0000-\u0008\u000b-\u001f\u007f-\u009f]*$/;
|
|
229
250
|
var NO_CONTROLS_BUT_BREAKS_TEXT = "text with optional newlines (\\n) and tabs; other control characters (ESC, BEL, CR) are not allowed";
|
|
230
251
|
function mapTextError(map) {
|
|
252
|
+
if (map.context !== void 0) {
|
|
253
|
+
const error2 = contextError(map.context);
|
|
254
|
+
if (error2) return error2;
|
|
255
|
+
}
|
|
231
256
|
const check = (field, value, multiline = false) => value === void 0 || (multiline ? NO_CONTROLS_BUT_BREAKS : NO_CONTROLS).test(value) ? void 0 : `${field}: ${multiline ? NO_CONTROLS_BUT_BREAKS_TEXT : NO_CONTROLS_TEXT}`;
|
|
232
257
|
let error = check("title", map.title);
|
|
233
258
|
if (error) return error;
|
|
@@ -242,6 +267,10 @@ function mapTextError(map) {
|
|
|
242
267
|
}
|
|
243
268
|
}
|
|
244
269
|
for (const [i, node] of map.nodes.entries()) {
|
|
270
|
+
if (node.sources !== void 0) {
|
|
271
|
+
const error2 = sourceError(node.sources);
|
|
272
|
+
if (error2) return `nodes[${i}]: ${error2}`;
|
|
273
|
+
}
|
|
245
274
|
for (const name of ["label", "evidence", "detail"]) {
|
|
246
275
|
error = check(`nodes[${i}].${name}`, node[name], name !== "label");
|
|
247
276
|
if (error) return error;
|
|
@@ -307,8 +336,8 @@ function optionalString(rec, key, where, path) {
|
|
|
307
336
|
}
|
|
308
337
|
function parseMap(raw, path) {
|
|
309
338
|
if (!isRecord(raw)) return err({ kind: "bad-shape", path, detail: "root is not an object" });
|
|
310
|
-
if (raw["version"] !== STATE_FILE_VERSION) {
|
|
311
|
-
return err({ kind: "bad-shape", path, detail: `version is ${String(raw["version"])}, expected ${STATE_FILE_VERSION}` });
|
|
339
|
+
if (raw["version"] !== STATE_FILE_VERSION && raw["version"] !== 2) {
|
|
340
|
+
return err({ kind: "bad-shape", path, detail: `version is ${String(raw["version"])}, expected ${STATE_FILE_VERSION} or 2` });
|
|
312
341
|
}
|
|
313
342
|
const layers = arrayField(raw, "layers", path, "required");
|
|
314
343
|
if (!layers.ok) return layers;
|
|
@@ -321,6 +350,11 @@ function parseMap(raw, path) {
|
|
|
321
350
|
const groups = arrayField(raw, "groups", path, "optional");
|
|
322
351
|
if (!groups.ok) return groups;
|
|
323
352
|
let map = EMPTY_MAP;
|
|
353
|
+
if (raw["context"] !== void 0) {
|
|
354
|
+
const error = contextError(raw["context"]);
|
|
355
|
+
if (error) return err({ kind: "bad-shape", path, detail: error });
|
|
356
|
+
map = { ...map, context: raw["context"] };
|
|
357
|
+
}
|
|
324
358
|
const title = optionalString(raw, "title", "map", path);
|
|
325
359
|
if (!title.ok) return title;
|
|
326
360
|
if (title.value !== void 0) map = setTitle(map, title.value);
|
|
@@ -449,6 +483,11 @@ function parseMap(raw, path) {
|
|
|
449
483
|
if (!updated.ok) return err({ kind: "invariant-violation", path, violation: updated.error });
|
|
450
484
|
map = updated.value;
|
|
451
485
|
}
|
|
486
|
+
if (rawNode["sources"] !== void 0) {
|
|
487
|
+
const error = sourceError(rawNode["sources"]);
|
|
488
|
+
if (error) return err({ kind: "bad-shape", path, detail: `${where}: ${error}` });
|
|
489
|
+
map = { ...map, nodes: map.nodes.map((n) => n.id === id.value ? { ...n, sources: rawNode["sources"] } : n) };
|
|
490
|
+
}
|
|
452
491
|
}
|
|
453
492
|
for (const [i, rawEdge] of edges.value.entries()) {
|
|
454
493
|
const where = `edges[${i}]`;
|