pi-mega-compact 0.4.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/LICENSE +24 -0
- package/README.md +375 -0
- package/extensions/DASHBOARD.md +160 -0
- package/extensions/dashboard-server.test.ts +124 -0
- package/extensions/dashboard-server.ts +459 -0
- package/extensions/error-patterns.ts +175 -0
- package/extensions/mega-compact.test.ts +351 -0
- package/extensions/mega-compact.ts +846 -0
- package/extensions/openclaw-mega-compact.ts +370 -0
- package/package.json +61 -0
- package/src/adapt.ts +120 -0
- package/src/boundary.test.ts +61 -0
- package/src/boundary.ts +94 -0
- package/src/canary.ts +126 -0
- package/src/compact.test.ts +99 -0
- package/src/compact.ts +262 -0
- package/src/config/dedup.ts +120 -0
- package/src/config.ts +15 -0
- package/src/dedup/dedup.test.ts +46 -0
- package/src/dedup/digest.ts +40 -0
- package/src/dedup/l1-lsh.ts +67 -0
- package/src/dedup/l1-minhash.ts +90 -0
- package/src/dedup/l1-verify.ts +55 -0
- package/src/dedup/l1.test.ts +57 -0
- package/src/dedup/mmr.ts +54 -0
- package/src/dedup/normalize.ts +41 -0
- package/src/dedup/raptor/guardrails.ts +112 -0
- package/src/dedup/raptor/index.ts +118 -0
- package/src/dedup/raptor/kmeans.ts +156 -0
- package/src/dedup/raptor/raptor.test.ts +238 -0
- package/src/dedup/raptor/retrieval.ts +102 -0
- package/src/dedup/raptor/summarizer.ts +91 -0
- package/src/dedup/raptor/tree.ts +254 -0
- package/src/dedup/sprint12.test.ts +242 -0
- package/src/dedup/topk.ts +61 -0
- package/src/dedup-engine.test.ts +609 -0
- package/src/e2e.test.ts +843 -0
- package/src/embedder.ts +111 -0
- package/src/engine.test.ts +123 -0
- package/src/engine.ts +192 -0
- package/src/extractive.test.ts +156 -0
- package/src/extractive.ts +265 -0
- package/src/httpEmbedder.ts +154 -0
- package/src/log.test.ts +47 -0
- package/src/log.ts +60 -0
- package/src/monitoring.ts +171 -0
- package/src/ratio.bench.test.ts +1316 -0
- package/src/recall.integration.test.ts +96 -0
- package/src/recall.test.ts +59 -0
- package/src/recall.ts +100 -0
- package/src/sprint14.test.ts +245 -0
- package/src/store/backfill.ts +263 -0
- package/src/store/bloom.ts +122 -0
- package/src/store/compression.test.ts +83 -0
- package/src/store/compression.ts +203 -0
- package/src/store/integrity.ts +65 -0
- package/src/store/migrate.test.ts +158 -0
- package/src/store/migrate.ts +108 -0
- package/src/store/sprint10.test.ts +182 -0
- package/src/store/sqlite.ts +519 -0
- package/src/store.test.ts +169 -0
- package/src/store.ts +192 -0
- package/src/supersede.test.ts +42 -0
- package/src/supersede.ts +67 -0
- package/src/tokens.ts +35 -0
- package/src/types.test.ts +10 -0
- package/src/types.ts +49 -0
- package/src/vectorStore.test.ts +480 -0
- package/src/vectorStore.ts +544 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TheArchitectit
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# pi-mega-compact
|
|
2
|
+
|
|
3
|
+
A **layered, local, vector-backed context compressor** for the
|
|
4
|
+
[pi coding agent](https://github.com/earendil-works/pi). It compacts long
|
|
5
|
+
sessions into a **local SQLite store** and offers **deduped inline recall** — all
|
|
6
|
+
running **locally inside the extension**, with **no remote MCP server** and
|
|
7
|
+
**zero network calls at runtime** (PREVENT-PI-004).
|
|
8
|
+
|
|
9
|
+
> **v0.2.0** — storage backend is now **`better-sqlite3`** (a single,
|
|
10
|
+
> in-process, FS-backed SQLite database) replacing the old per-session gzipped
|
|
11
|
+
> JSON checkpoint files. The legacy `.checkpoints.json.gz` snapshots are
|
|
12
|
+
> retained as disaster-recovery fallbacks and auto-imported on first run.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## What this is (the 30-second version)
|
|
17
|
+
|
|
18
|
+
pi's context window is finite. When a session gets long, pi-mega-compact:
|
|
19
|
+
|
|
20
|
+
1. **Watches** context usage and, past a threshold, **compacts** the older part of
|
|
21
|
+
the conversation into a short structured summary + key facts ("a checkpoint").
|
|
22
|
+
2. **Stores** each checkpoint in a **local vector database** (SQLite) with an
|
|
23
|
+
embedding, so similar regions can be found later — and so **duplicate
|
|
24
|
+
work is never stored twice**.
|
|
25
|
+
3. **Recalls** the right checkpoints automatically when you resume a session or
|
|
26
|
+
invoke a recall command, re-injecting only what's relevant (deduped against
|
|
27
|
+
what's already in view).
|
|
28
|
+
|
|
29
|
+
Everything lives on **your disk**. No telemetry, no API, no MCP server, no cloud.
|
|
30
|
+
The only optional network surface is a **user-triggered localhost dashboard** you
|
|
31
|
+
open yourself.
|
|
32
|
+
|
|
33
|
+
### Why "mega"?
|
|
34
|
+
|
|
35
|
+
The compaction pipeline is a **Trident** — three deterministic stages that run
|
|
36
|
+
over your conversation before anything is persisted. The checkpoint it produces
|
|
37
|
+
is small (a summary + key decisions + next steps + files touched), so the same
|
|
38
|
+
session that would otherwise overflow its window keeps going on a fraction of the
|
|
39
|
+
tokens.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
Layer 5 Recall / Inline ONE local vector store → 3 entry points, 1 dedup engine
|
|
47
|
+
Layer 4 Persist / Checkpoint compactSession() → embed + store in SQLite (chkpt_xxx)
|
|
48
|
+
Layer 3 Cluster (vectorize) local vector index → semantic dedup + recall
|
|
49
|
+
Layer 2 Collapse (summarize) summarizeMessages() heuristic + agent summary on /mega-compact
|
|
50
|
+
Layer 1 Supersede (prune) drop obsolete file-reads / superseded turns (zero cost)
|
|
51
|
+
─────────────────────────────────────────────────────────────────────────
|
|
52
|
+
Trigger context/turn_end → % gate → auto_compact_check → fire
|
|
53
|
+
Marker insert compact-marker; dedupe so repeated triggers cost ~0 tokens
|
|
54
|
+
Cancel session_before_compact → { cancel:true } once persisted (no double-compact)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**One store, three ways to read it back — one dedup engine:**
|
|
58
|
+
|
|
59
|
+
| Entry point | Trigger | Behavior |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| **Auto-inline** (Layer 5) | `session_start` / `session_tree` | Resume → `recallAndInline(source:"resume")` prepends the most relevant checkpoints, deduped against current context. |
|
|
62
|
+
| **On-demand recall** | `/mega-recall [query]` | Semantic search the store, dedupe, and inline the top-K. |
|
|
63
|
+
| **Dedup sentinel** | every compact | A lightweight `mega-compact-marker` entry lets auto-inline and recall skip re-injecting / re-vectorizing already-present regions. |
|
|
64
|
+
|
|
65
|
+
**The dedup cascade** (shared across all entry points) collapses redundant work
|
|
66
|
+
so storage and recall stay lean:
|
|
67
|
+
|
|
68
|
+
- **L0 — exact:** SHA-256 content hash + region hash + summary hash. Identical (or
|
|
69
|
+
whitespace/casing-normalized) regions collapse to one row.
|
|
70
|
+
- **L1 — near-dup:** MinHash signatures + LSH bucketing + trigram verification
|
|
71
|
+
catch one-word rewordings that L0 misses.
|
|
72
|
+
- **L2 — semantic:** cosine over the embedding collapses paraphrases; MMR
|
|
73
|
+
diversifies retrieval so a cluster of near-hits yields distinct results.
|
|
74
|
+
- **RAPTOR — pre-compression tree** (shadow mode by default): a hierarchical
|
|
75
|
+
summary tree over checkpoints, built + logged but not served until promoted.
|
|
76
|
+
|
|
77
|
+
Every tier is gated by its own feature flag (see [Configuration](#configuration)).
|
|
78
|
+
A tier can be put in `MARK_ONLY` (record the decision, don't collapse) as a safe
|
|
79
|
+
partial-rollout or auto-degrade state.
|
|
80
|
+
|
|
81
|
+
### Embedding (two modes, both local)
|
|
82
|
+
|
|
83
|
+
The default embedder is **`TrigramEmbedder`** — a deterministic hashed trigram
|
|
84
|
+
bag (512-dim, L2-normalized), **zero dependencies, instant, fully offline**. It
|
|
85
|
+
is heuristic-strength, which is the right bar for "inline the right checkpoint,"
|
|
86
|
+
not production RAG.
|
|
87
|
+
|
|
88
|
+
**Optional: bring-your-own (BYO) localhost embedder.** Set
|
|
89
|
+
`MEGACOMPACT_EMBEDDING_URL` to a **localhost/127.0.0.1** endpoint you run
|
|
90
|
+
yourself (local ONNX/TEI/llamafile/Ollama-embeddings). The extension talks to it
|
|
91
|
+
from `src/httpEmbedder.ts` (loopback-only — a remote host is rejected at config
|
|
92
|
+
time, preserving PREVENT-PI-004). Compacted content never leaves the machine and
|
|
93
|
+
no model ships with the extension. See `src/httpEmbedder.ts` for the
|
|
94
|
+
OpenAI-style contract and the `MEGACOMPACT_EMBEDDING_KEY` / `MEGACOMPACT_EMBEDDING_HEADERS`
|
|
95
|
+
/ `MEGACOMPACT_EMBEDDING_DIM` options.
|
|
96
|
+
|
|
97
|
+
> **Note on MiniLM:** a `MEGACOMPACT_MINILM` flag exists in `src/config/dedup.ts`
|
|
98
|
+
> but defaults to **off**, and the MiniLM (all-MiniLM-L6-v2) ONNX embedder was
|
|
99
|
+
> prototyped then deliberately **not shipped** (async-vs-sync conflict with the
|
|
100
|
+
> synchronous VectorStore, second native dep, no free semantic win without a
|
|
101
|
+
> network call). The `Embedder` interface remains the seam — inject a local
|
|
102
|
+
> embedder (e.g. via your own `MEGACOMPACT_EMBEDDING_URL`) instead.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Installation
|
|
107
|
+
|
|
108
|
+
> **Full step-by-step guide** (pi + OpenClaw + every command + troubleshooting):
|
|
109
|
+
> [`docs/INSTALL_AND_USAGE.md`](docs/INSTALL_AND_USAGE.md).
|
|
110
|
+
|
|
111
|
+
### Requirements
|
|
112
|
+
|
|
113
|
+
- **Node >= 18**
|
|
114
|
+
- `npm install` builds the **`better-sqlite3`** native module (one-time, local
|
|
115
|
+
compile). No network call and no API key are needed at runtime.
|
|
116
|
+
- A pi coding agent install that loads extensions from `~/.pi/agent/extensions/`.
|
|
117
|
+
|
|
118
|
+
### From a git checkout
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
git clone https://github.com/TheArchitectit/pi-mega-compact.git \
|
|
122
|
+
~/.pi/agent/extensions/pi-mega-compact
|
|
123
|
+
cd ~/.pi/agent/extensions/pi-mega-compact
|
|
124
|
+
npm install
|
|
125
|
+
npm run build
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Register with pi
|
|
129
|
+
|
|
130
|
+
Either copy/link the extension into pi's extensions dir (the clone above already
|
|
131
|
+
does), **or** add it to your pi config's `pi.extensions` list:
|
|
132
|
+
|
|
133
|
+
```jsonc
|
|
134
|
+
{
|
|
135
|
+
"pi": {
|
|
136
|
+
"extensions": ["~/.pi/agent/extensions/pi-mega-compact/extensions/mega-compact.ts"]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Or use the bundled helper (needs `jq`):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
./install.sh # copy into ~/.pi/agent/extensions/pi-mega-compact
|
|
145
|
+
./install.sh -s # symlink instead of copy (dev mode)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Verify
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
cd ~/.pi/agent/extensions/pi-mega-compact
|
|
152
|
+
npm test # all unit/integration tests pass (192 as of v0.2.0)
|
|
153
|
+
npm run lint # tsc --noEmit + guardrails scan clean
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Uninstall
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
rm -rf ~/.pi/agent/extensions/pi-mega-compact
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Then remove the path from pi's `pi.extensions` array.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## How to use it
|
|
167
|
+
|
|
168
|
+
Once installed and registered, pi-mega-compact runs **automatically** — you don't
|
|
169
|
+
have to drive it. Past the context threshold it compacts in the background and
|
|
170
|
+
drops a checkpoint; on resume it re-inlines the relevant ones silently.
|
|
171
|
+
|
|
172
|
+
The commands (slash commands inside pi):
|
|
173
|
+
|
|
174
|
+
| Command | Description |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `/mega-compact [summary...]` | Manually compact the current session. A summary arg is used verbatim; otherwise the COLLAPSE heuristics build one. Persists a `chkpt_xxx`. |
|
|
177
|
+
| `/mega-compact off` | Disable auto-compaction for this session. |
|
|
178
|
+
| `/mega-status` | Show config + current context usage + store stats (checkpoint count, dedup rate, tokens saved). |
|
|
179
|
+
| `/mega-recall [query]` | Semantic-search the local store, dedupe against the current window, and inline the top-K relevant checkpoints. No query → uses your latest message. |
|
|
180
|
+
| `/mega-tier [name]` | Set the compaction tier (`low` / `medium` / `high` / `ultra` / `mega`). Shows current tier with no arg. |
|
|
181
|
+
| `/mega-dashboard` | Start the **localhost-only** live dashboard and open it in a browser (token gauge, store stats, live event stream). |
|
|
182
|
+
| `/mega-dashboard-status` | Report dashboard server status. |
|
|
183
|
+
| `/mega-dashboard-stop` | Stop the dashboard server. |
|
|
184
|
+
|
|
185
|
+
### Live stats widget
|
|
186
|
+
|
|
187
|
+
Above the pi editor the extension shows a compact widget:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
⚡ medium │ 142k/200k tokens (71%) │ 3 chkpts │ 🤖 2 agents │ turn 5
|
|
191
|
+
◐ armed │ dedup: 92% │ saved: 45k tok
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- **Tier** — active compaction tier (low/medium/high/ultra/mega)
|
|
195
|
+
- **Token usage** — current / max context window and %
|
|
196
|
+
- **Checkpoints** — persisted checkpoints for the session
|
|
197
|
+
- **Trigger state** — ○ idle, ◐ armed, ● ready
|
|
198
|
+
- **Dedup hit rate** — % of checkpoints collapsed as duplicates
|
|
199
|
+
- **Active agents / turn** — sub-agent count and conversation turn (when > 0)
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Configuration (env-backed)
|
|
204
|
+
|
|
205
|
+
All defaults are in `src/config/dedup.ts` (single source of truth). Set env vars
|
|
206
|
+
before starting pi.
|
|
207
|
+
|
|
208
|
+
| Variable | Default | Meaning |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `MEGACOMPACT_FAST_GATE_PCT` | `70` | Context-usage % that arms the auto-trigger. |
|
|
211
|
+
| `MEGACOMPACT_TIER` | `low` | Named trigger preset — sets the token threshold. `low`(50k) `medium`(100k) `high`(200k) `ultra`(1M) `mega`(10M). |
|
|
212
|
+
| `MEGACOMPACT_THRESHOLD_TOKENS` | _(tier default)_ | Explicit token budget confirming compaction. Overrides `MEGACOMPACT_TIER` when set. |
|
|
213
|
+
| `MEGACOMPACT_ANCHOR_USER_MESSAGES` | `3` | Never drop the most recent N user messages (anchor floor). |
|
|
214
|
+
| `MEGACOMPACT_PRESERVE_RECENT` | `4` | Preserve the most recent N messages verbatim. |
|
|
215
|
+
| `MEGACOMPACT_AUTO` | `true` | Enable the auto-trigger. |
|
|
216
|
+
| `MEGACOMPACT_AUTO_INLINE` | `true` | Auto-inline on resume / branch. |
|
|
217
|
+
| `MEGACOMPACT_AUTO_INLINE_K` | `3` | Top-K checkpoints to auto-inline. |
|
|
218
|
+
| `MEGACOMPACT_DEDUP_SIM` | `0.90` | Cosine threshold to collapse near-dupes. |
|
|
219
|
+
| `MEGACOMPACT_STATE_DIR` | _(none — per-repo default)_ | Override the store location. By default state is per-repo at `<repo>/.pi/mega-compact/`; this env var forces a single explicit dir (used as the fallback for non-git cwds). |
|
|
220
|
+
|
|
221
|
+
#### Dedup pipeline flags (v0.2.0 — single source: `src/config/dedup.ts`)
|
|
222
|
+
|
|
223
|
+
These gate the L0/L1/L2/RAPTOR dedup tiers. Defaults reproduce the all-active
|
|
224
|
+
Sprint 13 behavior. `MARK_ONLY_*` tiers run + record their decision but never
|
|
225
|
+
collapse (safe partial-rollout / auto-degrade state).
|
|
226
|
+
|
|
227
|
+
| Variable | Default | Meaning |
|
|
228
|
+
|---|---|---|
|
|
229
|
+
| `MEGACOMPACT_L0_ENABLED` | `true` | L0 exact content-hash dedup. |
|
|
230
|
+
| `MEGACOMPACT_L1_ENABLED` | `true` | L1 MinHash/LSH near-dup verification. |
|
|
231
|
+
| `MEGACOMPACT_L2_ENABLED` | `true` | L2 semantic cosine dedup + MMR retrieval diversity. |
|
|
232
|
+
| `MEGACOMPACT_RAPTOR_ENABLED` | `false` | RAPTOR pre-compression tree (**shadow mode by default** — builds + logs, does not serve retrieval). |
|
|
233
|
+
| `MEGACOMPACT_MARK_ONLY_L0` | `false` | L0: record, don't collapse. |
|
|
234
|
+
| `MEGACOMPACT_MARK_ONLY_L1` | `false` | L1: record, don't collapse. |
|
|
235
|
+
| `MEGACOMPACT_MARK_ONLY_L2` | `false` | L2: record, don't collapse. |
|
|
236
|
+
| `MEGACOMPACT_MINILM` | `false` | MiniLM embedder flag — **off; not shipped** (see Embedding). BYO via `MEGACOMPACT_EMBEDDING_URL`. |
|
|
237
|
+
| `MEGACOMPACT_EMBEDDING_URL` | _(unset)_ | BYO localhost embedder endpoint (loopback-only; enables `HttpEmbedder`). |
|
|
238
|
+
| `MEGACOMPACT_L2_THRESHOLD` | `0.85` | L2 cosine firing point (trigram-honest; set higher for semantic backends). |
|
|
239
|
+
| `MEGACOMPACT_L1_JACCARD` | `0.8` | L1 MinHash/LSH near-dup Jaccard threshold. |
|
|
240
|
+
| `MEGACOMPACT_MMR_LAMBDA` | `0.5` | MMR retrieval-diversity weight (λ·relevance − (1−λ)·maxSim). |
|
|
241
|
+
| `MEGACOMPACT_SEMDEDUP_COSINE` | `0.95` | Offline SemDeDup pair threshold → `dedup_status='removed'`. |
|
|
242
|
+
| `MEGACOMPACT_FP_RATE_L0` | `0.01` | L0 false-positive alert threshold (auto → MARK_ONLY). |
|
|
243
|
+
| `MEGACOMPACT_FP_RATE_L1L2` | `0.05` | L1/L2 false-positive alert threshold (auto → MARK_ONLY). |
|
|
244
|
+
| `MEGACOMPACT_ALERT_WINDOW_MS` | `600000` | FP-rate rolling window (10 min). |
|
|
245
|
+
| `MEGACOMPACT_P95_BUDGET_MS` | `100` | Per-tier p95 latency budget; canary auto-disables on breach. |
|
|
246
|
+
|
|
247
|
+
See `docs/DEDUP_RUNBOOK.md` for incident response (SEV tiers, first-15-min
|
|
248
|
+
checklist, MARK_ONLY degrade) and `docs/RETENTION_POLICY.md` for TTL / soft-delete
|
|
249
|
+
/ VACUUM.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Reporting for testers (what to capture)
|
|
254
|
+
|
|
255
|
+
If you're testing pi-mega-compact, the maintainers need **local evidence**, not
|
|
256
|
+
guesswork. The store and logs are plain local files — never a network port.
|
|
257
|
+
|
|
258
|
+
1. **Install + run it** (see [Installation](#install)).
|
|
259
|
+
2. **Work a real session** until context fills past the gate (80%+) — you should
|
|
260
|
+
see the status chip flip to `● ready`, then `◐ armed`, a checkpoint persist,
|
|
261
|
+
and context visibly drop.
|
|
262
|
+
3. **Resume and confirm recall:** restart pi, ask about something you worked on
|
|
263
|
+
earlier; relevant checkpoints should auto-inline (or use
|
|
264
|
+
`/mega-recall <topic>`).
|
|
265
|
+
4. **Watch the live signal** while testing:
|
|
266
|
+
```bash
|
|
267
|
+
tail -f ~/.pi/agent/extensions/pi-mega-compact/events.log | jq .
|
|
268
|
+
```
|
|
269
|
+
Each line is `{ts, tier, result, latencyMs, falsePositive?}`.
|
|
270
|
+
5. **Run the dashboard** (`/mega-dashboard`) and check the token gauge, store
|
|
271
|
+
stats, and live event stream.
|
|
272
|
+
6. **Try `/mega-tier`** to see and switch compaction tiers.
|
|
273
|
+
|
|
274
|
+
### What to include in a bug report
|
|
275
|
+
|
|
276
|
+
- Output of `/mega-status` (config + store stats).
|
|
277
|
+
- Output of `/mega-dashboard-status`.
|
|
278
|
+
- Your pi version + OS + Node version (`node -v`).
|
|
279
|
+
- A slice of `events.log` around the problem (the `result`/`tier` lines).
|
|
280
|
+
- `dashboard.json` from the state dir (aggregate metrics: hit rate, FP rate,
|
|
281
|
+
per-tier p95, storage bytes).
|
|
282
|
+
- If you suspect data loss or duplication: the checkpoint count and the
|
|
283
|
+
`sqlite.db` size, plus the output of the DR drill (below).
|
|
284
|
+
|
|
285
|
+
**Disaster-recovery drill** (validates the store against its JSON snapshots and
|
|
286
|
+
rebuilds if corrupt — see `docs/RETENTION_POLICY.md` §5):
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
scripts/dedup-restore-drill.sh ~/.pi/agent/extensions/pi-mega-compact
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Benchmark** (dedup hit rate, compression ratio, per-tier p95, storage at
|
|
293
|
+
100 / 1K / 10K checkpoints):
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
npm run build
|
|
297
|
+
node scripts/dedup-benchmark.mjs 100 1000 10000
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Open issues at: https://github.com/TheArchitectit/pi-mega-compact/issues
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Architecture & layout
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
extensions/mega-compact.ts pi extension entry; wires src/ into pi lifecycle
|
|
308
|
+
src/adapt.ts the single pi↔engine message adapter (index-aligned)
|
|
309
|
+
src/engine.ts Layer 4: compactSession() Trident pipeline + recall()
|
|
310
|
+
src/vectorStore.ts Layer 3: local vector DB (add/search/dedupe + near-dup)
|
|
311
|
+
src/embedder.ts default TrigramEmbedder (deterministic, 512-dim)
|
|
312
|
+
src/httpEmbedder.ts BYO localhost embedder seam (MEGACOMPACT_EMBEDDING_URL)
|
|
313
|
+
src/store/sqlite.ts the "one store" — better-sqlite3 context_chunks + session_state (FTS5 trigram)
|
|
314
|
+
src/store/migrate.ts JSON → SQLite migration (legacy .checkpoints.json.gz retained)
|
|
315
|
+
src/store/backfill.ts resumable backfill orchestrator (L0/L1/L2/RAPTOR)
|
|
316
|
+
src/monitoring.ts local events.log + dashboard.json metrics + FP alerts
|
|
317
|
+
src/canary.ts sequential L0→L1→L2→RAPTOR rollout, auto-disable on p95 breach
|
|
318
|
+
src/config/dedup.ts single source of truth for ALL dedup tier flags + thresholds
|
|
319
|
+
src/store.ts state dir + JSON DR helpers + compression re-exports
|
|
320
|
+
src/compact.ts Layer 2: summarize / merge / autoCompactCheck
|
|
321
|
+
src/supersede.ts Layer 1: obsolete file-read pruning
|
|
322
|
+
src/boundary.ts drop-boundary guards (anchor floor + tool-pair)
|
|
323
|
+
src/tokens.ts deterministic token estimator
|
|
324
|
+
src/types.ts engine-internal types
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
The `src/` directory is **pi-agnostic** and fully unit-tested (`node --test`).
|
|
328
|
+
The extension entry adapts between the engine and pi's runtime types.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Development
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
npm run build # tsc
|
|
336
|
+
npm test # build + node --test on dist/**/*.test.js
|
|
337
|
+
npm run lint # tsc --noEmit + guardrails-scan
|
|
338
|
+
npm run guardrails # regression_check + guardrails-scan
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The agent-guardrails suite (Four Laws, scope, secrets, regression) gates every
|
|
342
|
+
sprint.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Status
|
|
347
|
+
|
|
348
|
+
- ✅ Sprint 1 — core engine (Layers 1–2, pure functions)
|
|
349
|
+
- ✅ Sprint 2 — local vector store (Layer 3)
|
|
350
|
+
- ✅ Sprint 3 — pi extension wiring (Layer 4 persist + trigger)
|
|
351
|
+
- ✅ Sprint 4 — unified recall layer (Layer 5: auto-inline + on-demand + sentinel)
|
|
352
|
+
- ✅ Sprint 5 — commands / UX / config polish (status chip, store stats, debug log)
|
|
353
|
+
- ✅ Sprint 6 — hardening, docs, release (`install.sh`, CHANGELOG, `v0.1.0`)
|
|
354
|
+
- ✅ Sprint 8 — SQLite storage backbone (`better-sqlite3`, one store) + compression v2
|
|
355
|
+
- ✅ Sprints 9–11 — L0 exact-hash + L1 MinHash/LSH near-dup dedup tiers
|
|
356
|
+
- ✅ Sprint 12 — L2 semantic cosine + MMR; BYO localhost embedder (`HttpEmbedder`)
|
|
357
|
+
- ✅ Sprint 13 — RAPTOR hierarchical pre-compression (shadow mode)
|
|
358
|
+
- ✅ Sprint 14 — full pipeline: flags, backfill, monitoring, canary rollout
|
|
359
|
+
- ✅ Sprint 15 — benchmarks, DR drill, docs, `v0.2.0`
|
|
360
|
+
|
|
361
|
+
See `SPRINT_PLAN.md` for the full breakdown and `PLAN.md` for architecture,
|
|
362
|
+
`RESEARCH.md` for the pi-API constraints that shaped it, `CHANGELOG.md` for
|
|
363
|
+
release notes.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Acknowledgements
|
|
368
|
+
|
|
369
|
+
Algorithmic reference (reimplemented in TypeScript, not vendored): memory-mcp
|
|
370
|
+
(`MemoryCompactor` / `compact.py`), claw-code (`trident.rs` / `compact.rs`), and
|
|
371
|
+
neuralwatt-mcr (pi-extension mechanics). Attribution as design sources only.
|
|
372
|
+
|
|
373
|
+
## License
|
|
374
|
+
|
|
375
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Mega-Compact Dashboard
|
|
2
|
+
|
|
3
|
+
A lightweight local web dashboard for monitoring mega-compact's live state — compactions, context usage, checkpoints, and recall hits.
|
|
4
|
+
|
|
5
|
+
Zero npm dependencies. Uses only Node built-in modules (`http`, `fs`, `path`).
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
From a pi session with mega-compact loaded:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
/dashboard
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This will:
|
|
16
|
+
1. Start a local HTTP server on a random port (3000–3999)
|
|
17
|
+
2. Show a confirm dialog asking if you'd like to open it in your browser
|
|
18
|
+
3. Write the dashboard URL to the terminal
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
| Command | Description |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `/dashboard` | Start the dashboard server (or reuse if already running) |
|
|
25
|
+
| `/dashboard-stop` | Stop the running server |
|
|
26
|
+
| `/dashboard-status` | Show the current server status and URL |
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
32
|
+
│ pi session (mega-compact extension) │
|
|
33
|
+
│ ┌────────────────────────────────────────────────────────┐ │
|
|
34
|
+
│ │ DashboardEmitter │ │
|
|
35
|
+
│ │ • writes dashboard.json (full state snapshot) │ │
|
|
36
|
+
│ │ • appends to events.log (JSONL tail) │ │
|
|
37
|
+
│ └────────────────────────────────────────────────────────┘ │
|
|
38
|
+
│ │ writes after each compaction │
|
|
39
|
+
└───────────┼─────────────────────────────────────────────────┘
|
|
40
|
+
▼
|
|
41
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
42
|
+
│ dashboard-server (detached child process) │
|
|
43
|
+
│ • GET / → single-page HTML dashboard │
|
|
44
|
+
│ • GET /api/snapshot → JSON snapshot (reads dashboard.json) │
|
|
45
|
+
│ • GET /api/events → SSE stream (watches events.log) │
|
|
46
|
+
└─────────────────────────────────────────────────────────────┘
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Data Files
|
|
50
|
+
|
|
51
|
+
All files are written to the extension's state directory
|
|
52
|
+
(`~/.pi/agent/extensions/pi-mega-compact/`):
|
|
53
|
+
|
|
54
|
+
| File | Format | Description |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `dashboard.json` | JSON | Full state snapshot, rewritten after each compaction |
|
|
57
|
+
| `events.log` | JSONL | Append-only event log (compact_start, compact_end, checkpoint_persisted, recall_inject) |
|
|
58
|
+
| `port.pid` | JSON | Server port and PID for process management |
|
|
59
|
+
| `runner.mjs` | ESM script | Auto-generated launcher for the dashboard server |
|
|
60
|
+
|
|
61
|
+
### Event Types
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{"ts":"...","type":"compact_start","trigger":"auto|command","tier":"medium","sessionId":"..."}
|
|
65
|
+
{"ts":"...","type":"compact_end","trigger":"auto|command","durationMs":1234,"mode":"mega","fromTokens":100000,"toTokens":5000}
|
|
66
|
+
{"ts":"...","type":"checkpoint_persisted","checkpointId":"chk_1","totalCheckpoints":3}
|
|
67
|
+
{"ts":"...","type":"recall_inject","count":2,"totalTokens":1200,"sources":["chk_1","chk_2"]}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Server Process
|
|
71
|
+
|
|
72
|
+
The server runs as a detached child process, independent of the pi session. It:
|
|
73
|
+
- Auto-discovers the state directory from the `port.pid` file
|
|
74
|
+
- Cleans up stale `port.pid` files from dead processes
|
|
75
|
+
- Supports `SIGHUP` for graceful shutdown
|
|
76
|
+
- Serves static HTML with no external dependencies
|
|
77
|
+
|
|
78
|
+
## Browser UI
|
|
79
|
+
|
|
80
|
+
The dashboard is a single-page application that shows:
|
|
81
|
+
|
|
82
|
+
- **Status bar**: current tier, trigger state, context utilization
|
|
83
|
+
- **Compaction graph**: timeline of compaction events with token counts
|
|
84
|
+
- **Checkpoint list**: recent checkpoints with timestamps
|
|
85
|
+
- **Recall activity**: dedup hits and injection stats
|
|
86
|
+
- **Context gauge**: live token usage vs. threshold
|
|
87
|
+
|
|
88
|
+
The UI uses `EventSource` (SSE) for real-time updates — no polling required.
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
### Running tests
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm run build && node --test dist/extensions/dashboard-server.test.js
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Manual testing
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Start the server directly (for debugging)
|
|
102
|
+
node dist/extensions/dashboard-server.js
|
|
103
|
+
|
|
104
|
+
# Write a test snapshot
|
|
105
|
+
echo '{"updatedAt":"2025-01-01T00:00:00Z","tier":"medium","version":1}' > ~/.pi/agent/extensions/pi-mega-compact/dashboard.json
|
|
106
|
+
|
|
107
|
+
# Watch events in another terminal
|
|
108
|
+
tail -f ~/.pi/agent/extensions/pi-mega-compact/events.log | jq .
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Live Stats Widget
|
|
112
|
+
|
|
113
|
+
The extension displays a compact stats widget above the pi editor at all times:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
⚡ medium │ 142k/200k tokens (71%) │ 3 chkpts │ 🤖 2 agents │ turn 5
|
|
117
|
+
◐ armed │ dedup: 92% │ saved: 45k tok
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The widget shows:
|
|
121
|
+
- **Tier**: active compaction tier (low/medium/high/ultra/mega)
|
|
122
|
+
- **Token usage**: current / max context window and percentage
|
|
123
|
+
- **Checkpoints**: number of persisted checkpoints this session
|
|
124
|
+
- **Trigger state**: ○ idle (< gate %), ◐ armed (≥ gate %, below threshold), ● ready (≥ threshold)
|
|
125
|
+
- **Dedup hit rate**: percentage of compacted regions that were already stored
|
|
126
|
+
- **Tokens saved**: cumulative token savings from compaction
|
|
127
|
+
- **Active agents**: number of running sub-agents (shown when > 0)
|
|
128
|
+
- **Turn index**: current conversation turn number (shown when > 0)
|
|
129
|
+
|
|
130
|
+
The widget updates on every context event, session start, branch navigation, agent start/end, turn start/end, and compaction. It clears automatically on session shutdown.
|
|
131
|
+
|
|
132
|
+
### Agent Tracking
|
|
133
|
+
|
|
134
|
+
The extension tracks active sub-agents in real-time:
|
|
135
|
+
|
|
136
|
+
| Event | Behavior |
|
|
137
|
+
|-------|----------|
|
|
138
|
+
| `agent_start` | Increments active agent count, updates widget |
|
|
139
|
+
| `agent_end` | Decrements active agent count, updates widget |
|
|
140
|
+
| `turn_start` | Tracks current turn index, updates widget |
|
|
141
|
+
| `turn_end` | Logs turn completion, updates widget |
|
|
142
|
+
| `session_start` | Resets agent count and turn counter |
|
|
143
|
+
| `session_shutdown` | Resets agent count and turn counter |
|
|
144
|
+
|
|
145
|
+
## Security
|
|
146
|
+
|
|
147
|
+
- The server only listens on `127.0.0.1` (localhost)
|
|
148
|
+
- No authentication (local-only, not exposed to network)
|
|
149
|
+
- No write endpoints — all APIs are read-only
|
|
150
|
+
- No npm dependencies — only Node.js built-ins
|
|
151
|
+
|
|
152
|
+
## Troubleshooting
|
|
153
|
+
|
|
154
|
+
**Port already in use**: The server picks a random port in 3000–3999. If all are taken, it will retry. Check `/dashboard-status` for the current port.
|
|
155
|
+
|
|
156
|
+
**Server won't start**: Check for stale `port.pid` files. Run `/dashboard-stop` to clean up, then try `/dashboard` again.
|
|
157
|
+
|
|
158
|
+
**No data showing**: The server reads `dashboard.json` and `events.log` from the state directory. These are created after the first compaction. If you haven't compacted yet, run `/megacompact` to trigger one.
|
|
159
|
+
|
|
160
|
+
**Browser doesn't open**: The server URL is always shown in the terminal. Copy it manually or use `xdg-open <url>` (Linux), `open <url>` (macOS), or `start <url>` (Windows).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-server.test.ts — unit tests for the standalone HTTP dashboard server.
|
|
3
|
+
*
|
|
4
|
+
* Tests the core logic (snapshot building, API responses) without requiring
|
|
5
|
+
* a live pi session or model.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test, describe } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// helpers
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
function tmpDir(): string {
|
|
19
|
+
return mkdtempSync(join(tmpdir(), "dashboard-test-"));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeSnapshot(dir: string, data: Record<string, unknown>): void {
|
|
23
|
+
writeFileSync(join(dir, "snapshot.json"), JSON.stringify(data, null, 2));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Tests
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
describe("snapshot.json reading", () => {
|
|
31
|
+
test("returns valid snapshot when file exists", () => {
|
|
32
|
+
const dir = tmpDir();
|
|
33
|
+
const snapshot = {
|
|
34
|
+
updatedAt: new Date().toISOString(),
|
|
35
|
+
tier: "medium",
|
|
36
|
+
version: 1,
|
|
37
|
+
config: { activeTier: "medium" },
|
|
38
|
+
session: { id: "test-123", state: "running", persistedThisSession: true },
|
|
39
|
+
context: { tokens: 50000, percent: 50, contextWindow: 100000 },
|
|
40
|
+
trigger: { armed: true, ready: false, currentTokens: 50000, thresholdTokens: 100000 },
|
|
41
|
+
store: { checkpointCount: 3, totalTokenEstimate: 15000 },
|
|
42
|
+
};
|
|
43
|
+
writeSnapshot(dir, snapshot);
|
|
44
|
+
|
|
45
|
+
const content = readFileSync(join(dir, "snapshot.json"), "utf-8");
|
|
46
|
+
const parsed = JSON.parse(content);
|
|
47
|
+
assert.equal(parsed.tier, "medium");
|
|
48
|
+
assert.equal(parsed.session.id, "test-123");
|
|
49
|
+
assert.equal(parsed.context.percent, 50);
|
|
50
|
+
assert.equal(parsed.store.checkpointCount, 3);
|
|
51
|
+
rmSync(dir, { recursive: true });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("handles missing snapshot.json gracefully", () => {
|
|
55
|
+
const dir = tmpDir();
|
|
56
|
+
const snapshotPath = join(dir, "snapshot.json");
|
|
57
|
+
assert.equal(existsSync(snapshotPath), false);
|
|
58
|
+
rmSync(dir, { recursive: true });
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("dashboard.json (compact snapshot)", () => {
|
|
63
|
+
test("round-trips correctly", () => {
|
|
64
|
+
const dir = tmpDir();
|
|
65
|
+
const data = {
|
|
66
|
+
updatedAt: "2025-01-01T00:00:00.000Z",
|
|
67
|
+
tier: "mega",
|
|
68
|
+
version: 1,
|
|
69
|
+
config: { activeTier: "mega", thresholdTokens: 10000000 },
|
|
70
|
+
session: { id: "abc", state: "running", persistedThisSession: false },
|
|
71
|
+
context: { tokens: null, percent: null, contextWindow: 200000 },
|
|
72
|
+
trigger: { armed: false, ready: false, currentTokens: null, thresholdTokens: 10000000 },
|
|
73
|
+
store: { checkpointCount: 0, totalTokenEstimate: 0 },
|
|
74
|
+
};
|
|
75
|
+
writeFileSync(join(dir, "dashboard.json"), JSON.stringify(data));
|
|
76
|
+
const content = readFileSync(join(dir, "dashboard.json"), "utf-8");
|
|
77
|
+
const parsed = JSON.parse(content);
|
|
78
|
+
assert.equal(parsed.tier, "mega");
|
|
79
|
+
assert.equal(parsed.config.thresholdTokens, 10000000);
|
|
80
|
+
assert.equal(parsed.context.tokens, null);
|
|
81
|
+
rmSync(dir, { recursive: true });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("events.log (JSONL)", () => {
|
|
86
|
+
test("parses multiple JSONL lines", () => {
|
|
87
|
+
const dir = tmpDir();
|
|
88
|
+
const events = [
|
|
89
|
+
{ ts: "2025-01-01T00:00:00.000Z", type: "compact_start", trigger: "auto", tier: "medium", sessionId: "s1" },
|
|
90
|
+
{ ts: "2025-01-01T00:00:01.000Z", type: "compact_end", trigger: "auto", durationMs: 1500, mode: "mega", fromTokens: 100000, toTokens: 5000 },
|
|
91
|
+
{ ts: "2025-01-01T00:00:02.000Z", type: "checkpoint_persisted", checkpointId: "chk_1", totalCheckpoints: 1 },
|
|
92
|
+
];
|
|
93
|
+
writeFileSync(join(dir, "events.log"), events.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
94
|
+
|
|
95
|
+
const lines = readFileSync(join(dir, "events.log"), "utf-8").trim().split("\n");
|
|
96
|
+
assert.equal(lines.length, 3);
|
|
97
|
+
const parsed = lines.map((l) => JSON.parse(l));
|
|
98
|
+
assert.equal(parsed[0].type, "compact_start");
|
|
99
|
+
assert.equal(parsed[1].durationMs, 1500);
|
|
100
|
+
assert.equal(parsed[2].checkpointId, "chk_1");
|
|
101
|
+
rmSync(dir, { recursive: true });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("ignores empty lines", () => {
|
|
105
|
+
const dir = tmpDir();
|
|
106
|
+
writeFileSync(join(dir, "events.log"), '{"type":"test"}\n\n\n{"type":"test2"}\n');
|
|
107
|
+
const lines = readFileSync(join(dir, "events.log"), "utf-8").trim().split("\n").filter(Boolean);
|
|
108
|
+
assert.equal(lines.length, 2);
|
|
109
|
+
rmSync(dir, { recursive: true });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("port.pid file", () => {
|
|
114
|
+
test("round-trips port and pid", () => {
|
|
115
|
+
const dir = tmpDir();
|
|
116
|
+
const info = { port: 3847, pid: 12345 };
|
|
117
|
+
writeFileSync(join(dir, "port.pid"), JSON.stringify(info));
|
|
118
|
+
const content = readFileSync(join(dir, "port.pid"), "utf-8");
|
|
119
|
+
const parsed = JSON.parse(content);
|
|
120
|
+
assert.equal(parsed.port, 3847);
|
|
121
|
+
assert.equal(parsed.pid, 12345);
|
|
122
|
+
rmSync(dir, { recursive: true });
|
|
123
|
+
});
|
|
124
|
+
});
|