mellos-mapping 0.20.0 → 0.20.3

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.
Files changed (43) hide show
  1. package/README.md +343 -63
  2. package/README.zh-CN.md +297 -54
  3. package/dist/hook-session-start.mjs +239 -0
  4. package/dist/mmap.mjs +338 -0
  5. package/dist/server.mjs +1615 -810
  6. package/dist/store-paths.mjs +107 -0
  7. package/dist/watch.mjs +1392 -761
  8. package/lib/domain/ops.d.ts +71 -12
  9. package/lib/domain/ops.js +145 -14
  10. package/lib/domain/types.d.ts +47 -6
  11. package/lib/domain/types.js +44 -4
  12. package/lib/render/canvas.d.ts +50 -0
  13. package/lib/render/canvas.js +210 -0
  14. package/lib/render/draw.d.ts +37 -0
  15. package/lib/render/draw.js +111 -0
  16. package/lib/render/layout.d.ts +89 -0
  17. package/lib/render/layout.js +200 -0
  18. package/lib/render/options.d.ts +39 -0
  19. package/lib/render/options.js +10 -0
  20. package/lib/render/render.d.ts +32 -46
  21. package/lib/render/render.js +58 -789
  22. package/lib/render/routing.d.ts +56 -0
  23. package/lib/render/routing.js +244 -0
  24. package/lib/render/skins.d.ts +54 -0
  25. package/lib/render/skins.js +99 -0
  26. package/lib/render/width.d.ts +24 -0
  27. package/lib/render/width.js +139 -0
  28. package/lib/render/zoom-geometry.d.ts +52 -0
  29. package/lib/render/zoom-geometry.js +56 -0
  30. package/lib/semantics/semantics.d.ts +53 -4
  31. package/lib/semantics/semantics.js +130 -6
  32. package/lib/semantics/vocabulary.d.ts +79 -0
  33. package/lib/semantics/vocabulary.js +112 -0
  34. package/lib/store/format.d.ts +17 -0
  35. package/lib/store/format.js +185 -66
  36. package/lib/store/store.d.ts +220 -20
  37. package/lib/store/store.js +491 -38
  38. package/package.json +11 -4
  39. package/scripts/codex-register.mjs +89 -20
  40. package/scripts/install-mmap-command.mjs +293 -0
  41. package/scripts/mmap.mjs +213 -0
  42. package/scripts/open-pane.mjs +115 -254
  43. package/scripts/pane-core.mjs +418 -0
package/README.md CHANGED
@@ -93,7 +93,43 @@ claude plugin marketplace add GuangminJu/mellos-mapping && claude plugin install
93
93
  ```
94
94
 
95
95
  Requires Node.js 18+ on PATH (Claude Code itself requires Node, so you
96
- already have it). No build step: the MCP server and watcher ship pre-bundled.
96
+ already have it). No build step: `dist/` is committed, so a clone runs as-is —
97
+ `dist/server.mjs` (the MCP server), `dist/watch.mjs` (the pane),
98
+ `dist/mmap.mjs` (the `mmap` toggle), `dist/hook-session-start.mjs` (the
99
+ `SessionStart` hook that `hooks/hooks.json` registers) and
100
+ `dist/store-paths.mjs` (the store's path vocabulary, which the plain-node pane
101
+ launcher imports instead of restating filenames).
102
+
103
+ The first session after installing asks you **one** question — how eager
104
+ mapping should be — and records the answer for every project you will ever
105
+ open. From then on the hook carries it into each new session by itself; there
106
+ is no per-project setup step. See
107
+ [Setup: choose when maps open](#setup-choose-when-maps-open).
108
+
109
+ The `mmap` terminal command (the pane's open/close toggle) installs itself on
110
+ Windows: the same hook notices on session start when the shim is missing or
111
+ points at an older install, writes `mmap.cmd` (cmd, PowerShell) and `mmap`
112
+ (git-bash) into `%LOCALAPPDATA%\mellos-mapping\bin`, appends that one
113
+ directory to your **user** PATH, and tells you so through the assistant. The
114
+ PATH change reaches only new processes — and a new tab of a running Windows
115
+ Terminal inherits the old environment, so close the terminal app entirely and
116
+ reopen it before the first `mmap`. The PATH edit keeps the
117
+ installer's guarantees: nothing happens when the entry is already there, and
118
+ a PATH that `setx` would damage (flattened `%VARIABLE%` references, truncation
119
+ past its limit) is refused outright, with the entry to add by hand named
120
+ instead.
121
+
122
+ The step behind it is still a command of its own, for the cases the hook does
123
+ not cover — `--uninstall`, or re-adding a PATH entry you removed while the
124
+ shims stayed put:
125
+
126
+ ```
127
+ node "<plugin dir>/scripts/install-mmap-command.mjs" [--uninstall]
128
+ ```
129
+
130
+ (`--json` prints the install outcome as one JSON line instead of prose — the
131
+ mode the hook itself calls it in.) `npm i -g mellos-mapping` provides the
132
+ same `mmap` via `bin`, no shims involved.
97
133
 
98
134
  ## Update
99
135
 
@@ -106,6 +142,30 @@ marketplace clone — the first command is what actually pulls this repo.
106
142
  Restart Claude Code to apply. Releases are version bumps on `master`.
107
143
  (In-app, `/plugin` opens the same management UI.)
108
144
 
145
+ ### Upgrading from 0.19
146
+
147
+ 0.20 moved the store out of `.claude/` — the map belongs to this tool, not to
148
+ one client — into `.mellos/`. The server and the watcher each perform the move
149
+ once at startup, and print exactly one line on stderr when they do
150
+ (`mellos-mapping: moved the legacy .claude map store to .mellos/ — commit the
151
+ move.`):
152
+
153
+ | 0.19 and earlier | 0.20 and later |
154
+ | --- | --- |
155
+ | `.claude/mellos-mapping.json` | `.mellos/map.json` |
156
+ | `.claude/mellos-mapping.pages/` | `.mellos/pages/` |
157
+ | `.claude/mellos-mapping.config.json` | `.mellos/config.json` |
158
+
159
+ Nothing is merged and nothing is overwritten: a project that already has a
160
+ `.mellos/` store (map, pages or config) is left untouched, whatever the legacy
161
+ directory still holds. If you keep your maps in git, commit the move —
162
+ `git add -A .claude .mellos` records it as renames rather than as a pile of
163
+ deletions plus untracked files.
164
+
165
+ That one move is the only time either process touches `.claude/`. Afterwards
166
+ the tools write nowhere but `.mellos/`, and never outside the project
167
+ directory they resolved at startup.
168
+
109
169
  ## Codex CLI
110
170
 
111
171
  The same repo doubles as a Codex plugin (codex-cli 0.147+). Three lines:
@@ -134,8 +194,9 @@ rerunning with `--page` retargets it instead of opening another. The pane
134
194
  auto-follows the page being written — the map the agent is operating on
135
195
  right now; press `f` to toggle that (a manual page switch also turns it
136
196
  off), or start with `--no-follow`. Elsewhere run
137
- `node <plugin root>/dist/watch.mjs` (same `--page` / `--no-follow` flags)
138
- from the project directory in a second terminal (or any terminal split).
197
+ `node <plugin root>/dist/watch.mjs` from the project directory in a second
198
+ terminal (or any terminal split). Both take the same flags — see
199
+ [Pane flags](#pane-flags).
139
200
 
140
201
  ## Any MCP client
141
202
 
@@ -153,19 +214,30 @@ The map file lands in the client session's working directory
153
214
  npx -y -p mellos-mapping mellos-mapping-watch
154
215
  ```
155
216
 
217
+ The server picks its project directory in this order: `MELLOS_MAPPING_CWD`
218
+ (an explicit override, for clients that spawn servers from a fixed
219
+ directory), then `CLAUDE_PROJECT_DIR` (what Claude Code sets for plugin MCP
220
+ servers), then the server process's own working directory. Set
221
+ `MELLOS_MAPPING_CWD` when your client would otherwise start the server
222
+ somewhere other than the project you are working in.
223
+
156
224
  The skill/discipline layer is Claude Code + Codex specific; other clients
157
- get the four `mmap_*` tools and the pane, and bring their own prompting.
225
+ get the five `mmap_*` tools and the pane, and bring their own prompting.
158
226
 
159
227
  ## Use
160
228
 
161
229
  1. Ask Claude to build something non-trivial. The bundled skill has Claude
162
230
  declare the ghost design and keep the map current as it works.
163
- 2. Run `/mellos-mapping:mmap` to open the live pane (Windows Terminal split
164
- on Windows, tmux split inside tmux, or a printed command to run in any
165
- second terminal). On Windows the pane opens in the terminal window
166
- hosting YOUR session, even with several windows open; pass `--window` to
167
- put the map in its own dedicated window instead. Prefer `--ascii` if
168
- your font lacks box-drawing glyphs.
231
+ 2. The pane opens itself. Every write tells Claude whether anybody is
232
+ actually looking (see [Who is watching](#who-is-watching)), and Claude
233
+ opens or retargets the pane with `mmap_open` when nobody is — you never
234
+ have to remember to. Open or close it yourself with `mmap` in any
235
+ terminal, or `/mellos-mapping:mmap` in the conversation (Windows
236
+ Terminal split on Windows, tmux split inside tmux, or a printed command
237
+ to run in any second terminal). On Windows the pane opens in the terminal
238
+ window hosting YOUR session, even with several windows open; pass
239
+ `--window` to put the map in its own dedicated window instead. Prefer
240
+ `--ascii` if your font lacks box-drawing glyphs.
169
241
  3. Watch nodes light up from the bottom. Interrupt when the picture worries
170
242
  you — that is what it is for.
171
243
 
@@ -180,22 +252,33 @@ htop and tmux speak):
180
252
  | wheel / `+` `-` | zoom, anchored on the focused node (see the ladder below) |
181
253
  | left-drag | grab and pan when the map outgrows the pane |
182
254
  | shift+wheel | scroll vertically |
255
+ | wheel tilt (horizontal) | pan sideways |
183
256
  | `hjkl` / arrows | nudge the view |
184
257
  | `Tab` / `Shift+Tab` / `1-9` / click a tab | switch pages (parallel maps) |
258
+ | wheel on the tab row / click `‹` `›` | browse an overflowing tab strip without switching pages |
259
+ | `f` | toggle auto-follow (see [Pages](#pages)) |
260
+ | `x`, or click the `×` on the active tab | ask to delete the page on screen; press again inside the window and its file is removed (see [Pages](#pages)) |
185
261
  | double-click a `⊞` node | dive into its sub-map (a child page) |
186
262
  | `Backspace` / `Esc` | climb back out of the last dive |
187
263
  | drag the `⋯` divider | resize the detail panel — pull it up to read long design notes in full |
188
264
  | `0` | reset pan and zoom |
189
- | `q` | quit the pane |
265
+ | `q` / `Ctrl+C` | quit the pane |
266
+
267
+ Every other key is inert, on purpose: an escape sequence the pane does not
268
+ know (F-keys, Home/End, PgUp/PgDn, Insert/Delete, modified arrows) is
269
+ consumed whole and does nothing, rather than having its payload bytes read
270
+ as hotkeys.
190
271
 
191
272
  Zooming scales the picture first and switches display mode only at the ends
192
273
  of the ladder, so every level still shows meaningful data:
193
274
 
194
275
  ```
195
- detail ← 100% ← 85% ← 70% ← 55% ← overview
276
+ detail+detail ← 100% ← 85% ← 70% ← 55% ← overview
196
277
  ```
197
278
 
198
- - **zoom in past 100%** — evidence and design notes unfold inside the boxes;
279
+ - **zoom in past 100%** — `detail` unfolds evidence and the first three lines
280
+ of the design notes inside the boxes; `detail+` widens them into reading
281
+ cards (up to twelve note rows);
199
282
  - **85–55%** — whitespace tightens and labels truncate proportionally, boxes
200
283
  stay boxes;
201
284
  - **below 55%** — labels would stop meaning anything, so the map AGGREGATES:
@@ -206,27 +289,133 @@ detail ← 100% ← 85% ← 70% ← 55% ← overview
206
289
  no groups falls back to a pure glyph constellation with per-band counts.)
207
290
  The footer always names the level.
208
291
 
209
- The two detail rows live at a fixed spot between map and hint line, showing
210
- the focused node's status, layer, evidence and both wire directions
211
- (`uses → … · used by ← …`) nothing ever floats over the map.
292
+ Below the map, between it and the hint line, sits a fixed-height detail
293
+ panel: a separator you can drag, a status-colored header, the focused node's
294
+ evidence, both wire directions (`uses → … · used by ← …`, each neighbour
295
+ carrying its own status glyph) and its design notes, word-wrapped. With
296
+ nothing focused it shows the map's dashboard instead. Fixed height — details
297
+ never float over the map and the layout never jumps.
298
+
299
+ ### Glyphs
300
+
301
+ One status, one glyph, everywhere a map is drawn — the pane's boxes, its tab
302
+ strip and detail panel, and any other client reading the same store:
303
+
304
+ | Unicode | ASCII | Meaning |
305
+ | --- | --- | --- |
306
+ | `·` | `.` | planned — declared, not started |
307
+ | `⠿` | `*` | in-progress, at rest — a box that can animate spins through the braille frames (`⠋⠙⠹…`) instead, or a four-bar cycle in ASCII |
308
+ | `■` | `#` | done, with evidence |
309
+ | `□` | `o` | done, with **no** evidence recorded — same claim, nothing behind it |
310
+ | `✗` | `X` | regressed: was done, now broken |
311
+ | `⊞` | `+` | badge: the node links a sub-map; double-click dives in |
312
+
313
+ The legend under the picture names the four statuses; `□ done, no evidence`
314
+ joins it only on a map that actually contains one — the four are the
315
+ vocabulary, that one is a rule being broken here and now. Documentation
316
+ diagram kinds replace the status legend with their node-kind glyphs.
212
317
 
213
- `--no-mouse` disables mouse reporting if your terminal multiplexer wants the
214
- mouse for itself.
318
+ ### Pane flags
319
+
320
+ Both the pane launcher (`scripts/open-pane.mjs <project dir>`) and the
321
+ watcher (`dist/watch.mjs`) take the same watcher flags; the launcher forwards
322
+ them verbatim and rejects anything it does not know rather than dropping it.
323
+
324
+ | Flag | Effect |
325
+ | --- | --- |
326
+ | `--page <slug>` | open on this page; with a pane already running, retarget that pane instead of opening another |
327
+ | `--ascii` | pure-ASCII repertoire, for fonts without box-drawing glyphs |
328
+ | `--no-color` | no ANSI color |
329
+ | `--no-mouse` | no mouse reporting, if your terminal multiplexer wants the mouse for itself |
330
+ | `--no-follow` | start with auto-follow off |
331
+ | `--interval <ms>` | poll interval; default 250, floored at 50 |
332
+
333
+ Launcher-only: `--window` opens the dedicated "mellos-mapping" window instead
334
+ of splitting the session's window, and `--force` opens another pane even
335
+ though one is already running for this project. Watcher-only: `--file <path>`
336
+ names the default page's state file (the launcher derives it from the project
337
+ directory).
338
+
339
+ ### The mmap command
340
+
341
+ `mmap`, typed in any terminal, is a **toggle**: it opens the map pane for the
342
+ project you are standing in, or closes the one that is already open.
343
+
344
+ | You type | What it does |
345
+ | --- | --- |
346
+ | `mmap` | nothing watching this project → open the pane; something watching → close it |
347
+ | `mmap <page-slug>` | open on that page, or retarget an already-open pane to it — never closes |
348
+ | `mmap --window` | open in the dedicated "mellos-mapping" window instead of splitting this one |
349
+ | `mmap --force` | open another pane even though one is already running |
350
+
351
+ The project is found the way git finds its root: from the current directory
352
+ upwards, to the nearest one holding a `.mellos/` store. Standing in a project
353
+ that has no map yet is fine — the pane opens on its standby screen and says so
354
+ until the first `mmap_declare`.
355
+
356
+ Closing goes through the store rather than through a signal: `mmap` writes a
357
+ one-shot request beside the map, the pane consumes it on its next poll (250 ms
358
+ by default) and exits, handing the terminal back exactly as it found it — mouse
359
+ reporting off, cursor visible. A pane still on the standby screen closes the
360
+ same way. The request is deleted as it is read, and a leftover from a pane that
361
+ died is swept when the next one starts, so a stale request can never close a
362
+ fresh pane.
363
+
364
+ Every watcher flag above works here too, forwarded verbatim; an unknown one is
365
+ a usage error, never dropped in silence. `mmap` needs
366
+ [installing once](#install) unless you have the npm package. Inside a Claude
367
+ Code conversation, `/mellos-mapping:mmap` opens the same pane.
215
368
 
216
369
  ### Pages
217
370
 
218
371
  A project can keep several maps side by side — **one effort = one page**.
219
372
  Claude targets a page by passing `page` to any `mmap_*` tool; the pane grows
220
373
  a tab bar as soon as a second page exists. The active tab is bold in its
221
- map's aggregate status color; when a background page's file changes, its tab
222
- lights up in status color instead of stealing your view. Each page remembers
223
- its own pan, zoom and pinned node. Because every page is its own file, two
224
- Claude sessions writing two pages can never clobber each other this is
225
- also the answer to running several Claude sessions in one project.
226
-
227
- State lives in `.mellos/map.json` (the default page) plus
228
- `.mellos/pages/<page>.json` for named pages plain JSON,
229
- safe to commit if you want the maps' history in git.
374
+ map's aggregate status color. Each page remembers its own pan, zoom and
375
+ pinned node.
376
+
377
+ By default the pane **follows the page being written**the map the agent is
378
+ operating on right now so declares and updates bring the audience along by
379
+ themselves. `f` toggles that, a manual page switch turns it off, and
380
+ `--no-follow` starts it off; with follow off, a background page's change
381
+ lights its tab in status color instead of stealing your view. An explicit
382
+ `--page` outranks follow, and a page requested before it exists is shown the
383
+ moment it appears.
384
+
385
+ **Deleting a page.** An effort ends; its page does not have to stay. In the
386
+ pane, `x` — or the `×` the active tab carries when the mouse is on — *asks*:
387
+ the footer says `press x again to delete <page>`, and a second press within
388
+ three seconds removes that page's file. Switching page, `Esc` or simply
389
+ waiting takes the request back. The `×` sits on the active tab only, so
390
+ clicking an inactive one switches to it first and offers its `×` on the next
391
+ frame. From a tool call it is `mmap_remove {pages: ["slug", …]}`, applied
392
+ after that call's map edits. Either way the file is gone for good — the maps
393
+ are plain JSON, so committing them is the only undo there is.
394
+
395
+ State lives in the tool-owned `.mellos/` directory at the project root:
396
+
397
+ | Path | What it is |
398
+ | --- | --- |
399
+ | `.mellos/map.json` | the default page — optional; a project whose work lives on named pages has none |
400
+ | `.mellos/pages/<slug>.json` | one file per named page |
401
+ | `.mellos/config.json` | the project's mapping policy (see [Setup](#setup-choose-when-maps-open)) |
402
+ | `.mellos/focus` | one-shot "show this page" request from a launcher to a running pane; the pane consumes it and deletes it within a poll tick |
403
+ | `.mellos/quit` | one-shot "close yourself" request from the `mmap` toggle, consumed and deleted the same way |
404
+ | `.mellos/viewers/<pid>.json` | one report per live pane — the page it is showing, whether auto-follow is on — refreshed every second while it runs (see [Who is watching](#who-is-watching)) |
405
+ | `<any of the above>.<pid>.<random>.tmp` | a save in flight; it is renamed over its target or removed. A leftover means a write failed (and was reported) and even its cleanup could not run |
406
+
407
+ The map files are plain JSON, safe to commit if you want the maps' history in
408
+ git. The other three are runtime chatter between a pane and whoever is talking
409
+ to it — gitignore `focus`, `quit` and `viewers/` if you commit the store.
410
+
411
+ **Concurrency, stated plainly.** Every save is atomic — written to a private
412
+ sibling temp file and renamed over the target — so a reader polling the store
413
+ sees the previous complete map or the new one, never a torn write. There is
414
+ no lost-update protection: two writers saving the *same* page race, and the
415
+ last rename wins, silently discarding what the other computed from an older
416
+ read. Pages are the isolation unit — two sessions that must not clobber each
417
+ other belong on two pages, which is also the answer to running several Claude
418
+ sessions in one project.
230
419
 
231
420
  ### Diagram kinds
232
421
 
@@ -252,8 +441,11 @@ A node can link a child page with `submap: <page-slug>` — the pane badges it
252
441
  map of maps, built entirely from pages: no new storage, no new invariants.
253
442
  Whether a node deserves a sub-map is the AI's judgment call — most don't.
254
443
 
255
- Sub-maps are interior detail, not siblings: a page referenced as a submap
256
- never occupies a tab. Inside a dive the tab row becomes a breadcrumb —
444
+ Sub-maps are interior detail, not siblings: a page some *other* page dives
445
+ into never occupies a tab. Two refinements keep the tab strip from erasing
446
+ itself — a page whose own node names itself hides nobody, and a link cycle
447
+ keeps its tabs unless a page outside the cycle dives in, because a cycle has
448
+ no outside to climb back to. Inside a dive the tab row becomes a breadcrumb —
257
449
  `⌫ parent map ▸ node` — and clicking it (or `Backspace`) climbs back out.
258
450
  When a hidden sub-map changes in the background, the footer says so.
259
451
 
@@ -261,64 +453,152 @@ When a hidden sub-map changes in the background, the footer says so.
261
453
 
262
454
  | Tool | Purpose |
263
455
  | --- | --- |
264
- | `mmap_declare` | Grow the map: title, diagram kind, layer bands, lanes, groups (subsystems), nodes, edges — optionally labeled (all-or-nothing batch) |
265
- | `mmap_update` | Record progress: `planned → in-progress → done` (+evidence), `regressed`, group/lane membership, node kind |
266
- | `mmap_remove` | Revise: drop edges, nodes, groups, lanes, empty bands |
267
- | `mmap_view` | Render the current map as text inline (optional `zoom`) |
456
+ | `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) |
457
+ | `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 |
458
+ | `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) |
459
+ | `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 |
268
460
  | `mmap_setup` | Get/set the project's mapping policy — when maps open |
461
+ | `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 |
462
+
463
+ A batch applies bands → groups → lanes → node updates, and within one node
464
+ update `layer` moves the node before its other fields, so a node can move and
465
+ join a group on its new band in one item.
466
+
467
+ What the boundary refuses, so the ledger never records something it did not
468
+ mean:
469
+
470
+ - **an unknown key**, naming it — a misspelled `evidance` is an error, not a
471
+ silently dropped field, at every nesting depth;
472
+ - **control characters** in text fields — an ESC sequence stored in a label
473
+ would let a map repaint the terminal of everyone who opens it. `detail` is
474
+ the exception: newlines and tabs are how a note is written, everything else
475
+ (ESC, BEL, lone CR) is still refused;
476
+ - **an empty string** where a field is optional — `null` is how a field is
477
+ cleared, never a blank that renders as a box nobody can tell from a real
478
+ one;
479
+ - **a node whose `submap` names the page the call itself targets** — a link
480
+ with no bottom, not a parent link;
481
+ - **a page deletion aimed at the page the same call targets, or at a slug the
482
+ project does not have** — one call must not edit a map it is deleting, and
483
+ a name matching no page is a typo far more often than a race; the refusal
484
+ lists the pages that do exist.
485
+
486
+ A write that does not land answers `save failed, nothing changed (retry)`:
487
+ the previous file is intact and calling again is the whole recovery.
488
+
489
+ ### Who is watching
490
+
491
+ A map nobody has on screen is a file, not a map — and nothing in the system
492
+ used to be able to tell the difference. An assistant would declare a design,
493
+ light nodes up as it built them, and report all of it into a store you had
494
+ never opened a pane for.
495
+
496
+ Every pane now publishes a small report while it runs — `.mellos/viewers/`,
497
+ one file per pane, refreshed once a second — and every write and every view
498
+ ends with what those reports say:
499
+
500
+ | The line | What it means |
501
+ | --- | --- |
502
+ | `pane: CLOSED` | nobody is seeing this map; the assistant opens one with `mmap_open` instead of asking you to |
503
+ | `pane: open on this page` | you are watching this land |
504
+ | `pane: open on <other>, auto-follow on` | the pane follows the page last written, so it arrives here by itself |
505
+ | `pane: open on <other>, auto-follow OFF` | you pinned that page by hand: the change is real and NOT on your screen. The assistant is told to say so rather than move your view |
506
+
507
+ The same reports answer "is a pane already open?" for `mmap` and for the
508
+ launcher — a question that used to cost a Windows-only process scan and could
509
+ not say which page was on screen. A report whose pane stopped refreshing it is
510
+ ignored after five seconds and deleted after a minute, so a killed pane cannot
511
+ go on claiming an audience.
269
512
 
270
513
  ### Setup: choose when maps open
271
514
 
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):
515
+ How eager mapping should be is a habit, not a property of a repository — so it
516
+ is chosen **once, for you**, in the first session after you install:
274
517
 
275
518
  - `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.
519
+ technical dependencies. The assistant opens the pane on its own initiative;
520
+ your recorded answer is its standing consent, so it stops asking.
521
+ - `complex` — the same, but only for medium or complex tasks: several modules,
522
+ a new subsystem, roughly an hour of work or more.
523
+ - `on-request` map only when you explicitly ask. In a project with no map,
524
+ the plugin then says nothing at all zero noise is the point.
525
+
526
+ The answer lands in `<your home>/.mellos/config.json` and reaches every session
527
+ through the plugin's `SessionStart` hook, which reads it and hands the
528
+ assistant the matching instruction before you have typed anything. Nothing has
529
+ to be set up per project, ever again.
530
+
531
+ One project can still differ: `mmap_setup {policy, scope: "project"}` records
532
+ a policy in that project's `.mellos/config.json`, and a project policy
533
+ overrides the user one. `/mmap setup` re-runs the question for either scope
534
+ whenever you want to change your mind. The policy guides the assistant; it
535
+ never blocks the tools, and asking for a map explicitly always works under any
536
+ policy.
537
+
538
+ Hosts without hooks (Codex CLI, a bare MCP client) get the question another
539
+ way: while no policy exists in either scope, every `mmap_declare` reply carries
540
+ a note telling the assistant to ask you. That note goes quiet for good — in
541
+ every project — the moment you have answered anywhere.
283
542
 
284
543
  Structural invariants enforced by the tools: layers form a total order by
285
- rank; every node lives in exactly one layer; edges point **strictly
286
- downward** — which makes the graph acyclic by construction; nodes may not
287
- depend on same-layer siblings (if A needs sibling B, either B is really a
288
- lower concept or A and B are one node).
544
+ rank (an integer in 0..99, 0 = bottom, unique per map); every node lives in
545
+ exactly one layer; edges point **strictly downward** — which makes the graph
546
+ acyclic by construction; nodes may not depend on same-layer siblings (if A
547
+ needs sibling B, either B is really a lower concept or A and B are one node);
548
+ a group clusters nodes within one band; and node ids and group ids share
549
+ **one namespace** — an id names a node or a group, never both, because both
550
+ render as boxes and one id must mean one box.
289
551
 
290
552
  ## Development
291
553
 
292
554
  ```
293
555
  npm install
294
- npm run verify # typecheck + tests + bundle
556
+ npm run verify
295
557
  ```
296
558
 
559
+ `verify` is four steps, in this order: `typecheck`, `test`, `build` (bundles
560
+ `dist/`, emits `lib/` with declarations, cleaning both first), and
561
+ `check:package` — which packs the tarball through the real `prepack`
562
+ lifecycle and fails if any `exports` or `bin` target is missing from it.
563
+
297
564
  The repo is itself layered bottom-up, and each layer has its spec:
298
565
 
299
- | Layer | Code | Owns |
300
- | --- | --- | --- |
301
- | 0 domain | `src/domain/` | the map value, structural invariants, pure ops |
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 |
305
- | 2 apply | `src/server/apply.ts` | tool inputs → transactional op sequences |
306
- | 3 server | `src/server/server.ts` | the four MCP tools over stdio |
307
- | 4 render | `src/render/`, `src/watch/` | ASCII renderer and the polling pane |
566
+ | Layer | Code | Spec | Owns |
567
+ | --- | --- | --- | --- |
568
+ | 0 domain | `src/domain/` | `ops.test.ts` | the map value, structural invariants, pure ops |
569
+ | 1 format | `src/store/format.ts` | `store.test.ts` | the state-file format: replay-validated parse, serialize — I/O-free |
570
+ | 1 store | `src/store/store.ts` | `store.test.ts`, `atomic-save.test.ts` | atomic state-file persistence on Node |
571
+ | 1 semantics | `src/semantics/` | `semantics.test.ts` | medium-neutral view semantics: zoom ladder, group aggregation, page-set rules, sequence flip, the shared glyph vocabulary |
572
+ | 2 apply | `src/server/apply.ts` | `apply.test.ts` | tool inputs → transactional op sequences |
573
+ | 3 server | `src/server/server.ts` | `server.test.ts`, `save-failure.test.ts` | the five MCP tools over stdio |
574
+ | 4 render | `src/render/` | `render.test.ts`, `routing.test.ts` | the ASCII renderer and its wire routing |
575
+ | 4 pane | `src/watch/` | `watch.test.ts`, `pane-state.test.ts`, `input.test.ts` | the polling pane: page set, input parsing, panel and chrome |
576
+ | — launchers | `scripts/` | `open-pane.test.mjs`, `codex-register.test.mjs` | plain-node entry points |
577
+ | — packaging | `package.json` | `tests/lockfile.test.ts`, `browser-safe.test.ts` | what ships, and to whom |
308
578
 
309
579
  `dist/` is committed deliberately: plugin installation clones this repo and
310
- runs nothing, so entry points ship bundled.
580
+ runs nothing, so entry points ship bundled. CI diffs the committed `dist/`
581
+ against a fresh build, so a source change that forgets the rebuild fails.
311
582
 
312
583
  ### Library
313
584
 
314
585
  The lower layers are also a library (`npm run build` emits `lib/` with type
315
586
  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.
587
+
588
+ | Subpath | Contents | Browser-safe |
589
+ | --- | --- | --- |
590
+ | `mellos-mapping/domain/types` | the map value, ids, ranks, statuses, errors | yes |
591
+ | `mellos-mapping/domain/ops` | pure operations over a map | yes |
592
+ | `mellos-mapping/format` | state-file parse / serialize, page ids | yes |
593
+ | `mellos-mapping/semantics` | zoom ladder, group aggregation, focus and page-set rules, the shared glyph vocabulary | yes |
594
+ | `mellos-mapping/render` | the terminal renderer | gated the same way (it is pure), but its output is character cells — for terminal hosts |
595
+ | `mellos-mapping/store` | filesystem persistence, atomic saves, focus file, policy | Node only |
596
+ | `mellos-mapping/server` | the bundled MCP server entry — a spawn target, not a module to import | Node only |
597
+
598
+ **Browser-safe** means no Node builtins anywhere in the import closure, gated
599
+ by a test, so a graphical client (a web panel, an editor view) can parse state
600
+ files and reuse the exact aggregation, zoom and glyph semantics the terminal
601
+ pane draws with.
322
602
 
323
603
  ## License
324
604