bantamkit-mcp 0.29.1 → 0.30.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 +1 -1
- package/assets/tools/memory_dream.json +20 -0
- package/assets/tools/repo_map.json +44 -0
- package/dist/docread.d.ts +82 -8
- package/dist/docread.js +348 -96
- package/dist/eventlog.js +5 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2 -1
- package/dist/mcp/pyargs.d.ts +20 -1
- package/dist/mcp/pyargs.js +80 -1
- package/dist/mcp/server.d.ts +36 -6
- package/dist/mcp/server.js +280 -39
- package/dist/memory/cli.js +44 -2
- package/dist/memory/component.d.ts +73 -2
- package/dist/memory/component.js +157 -5
- package/dist/memory/dream.d.ts +247 -0
- package/dist/memory/dream.js +903 -0
- package/dist/memory/factfile.d.ts +13 -1
- package/dist/memory/factfile.js +7 -17
- package/dist/memory/pyfs.d.ts +20 -12
- package/dist/memory/pyfs.js +21 -51
- package/dist/memory/store.d.ts +88 -1
- package/dist/memory/store.js +168 -5
- package/dist/pysem.d.ts +81 -0
- package/dist/pysem.js +126 -0
- package/dist/repomap.d.ts +247 -0
- package/dist/repomap.js +1040 -0
- package/dist/skillaudit.js +31 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# bantamkit-mcp
|
|
2
2
|
|
|
3
3
|
The bantamkit MCP server as a pure-Node package: `npx bantamkit-mcp`, no Python, no
|
|
4
|
-
`uv`, no `pipx`, no interpreter bootstrap. It serves the same
|
|
4
|
+
`uv`, no `pipx`, no interpreter bootstrap. It serves the same thirteen tools, the same
|
|
5
5
|
`bantamkit_status` prompt and the same two resource templates as `runtime-py`'s server,
|
|
6
6
|
reads and writes the same memory store, and is checked against the Python server frame by
|
|
7
7
|
frame — 4500+ conformance cases, with every intentional difference written down as a
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "memory_dream",
|
|
3
|
+
"description": "Consolidate the memories the project layer and the machine-wide profile layer hold under the SAME NAME, and say exactly what changed. Byte-identical copies collapse to one; a pair that has diverged is UNIONED — every claim from both copies survives, never a winner-takes-all — and a relative date in a body is annotated with the day it resolves to against that fact's own mtime, never against today. The survivor stays in the writable project layer and the profile copy MOVES to that store's archive/: nothing is deleted and any consumed memory can be restored by name. This is a correctness pass, not a token saving: the profile index is derived at read time and is not loaded from a prompt, so consolidating it frees approximately no bytes — what it buys is one copy of a ruling instead of two that can disagree. It defaults to a DRY RUN because the profile store is machine-wide: a memory archived out of it stops answering for every other project on this machine that has no store of its own. Read the plan first, then call it again with dry_run false to apply it.",
|
|
4
|
+
"surfaces": ["mcp"],
|
|
5
|
+
"parameters": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"dry_run": {
|
|
9
|
+
"type": "boolean",
|
|
10
|
+
"description": "Preview only. True (the default) performs every read, merge and projection and writes nothing. Pass false to apply the plan."
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"output_schema": {
|
|
15
|
+
"properties": { "result": { "title": "Result", "type": "string" } },
|
|
16
|
+
"required": ["result"],
|
|
17
|
+
"type": "object",
|
|
18
|
+
"title": "memory_dreamOutput"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "repo_map",
|
|
3
|
+
"description": "A ranked map of a source tree: every file's definitions, ordered by how central that file is to the files you name in `focus`, truncated to a byte budget. Call it when you are about to work on a file and want to know which OTHER files matter to it — the ports, the callers, the module it reaches through a private helper — rather than reading a directory listing and guessing. `focus` is the file or files you are editing, relative to `root` and POSIX-separated; they are excluded from the listing because you already have them open, and an empty focus gives plain centrality over the whole tree. This is a PRECISION pass and it is not a token saving: the gate this feature was supposed to clear was refuted by measurement — discovery is 0.114% of real prompt tokens, because 97.8% of the bill is cache_read — so a map does not make a session cheaper. What it buys is the right file found sooner. Nothing the scanner cannot read is dropped silently: every unread file resolves into a named omission (unknown-language, unreadable-bytes, size-cap, no-definitions, unreachable, per-file-cap, budget) counted in a footer that is NOT charged to the budget. The budget is UTF-8 BYTES of the listing, not tokens; there is no model tokenizer in either runtime and this tool will not pretend to one.",
|
|
4
|
+
"surfaces": [
|
|
5
|
+
"mcp"
|
|
6
|
+
],
|
|
7
|
+
"parameters": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"required": [
|
|
10
|
+
"root"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"root": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Directory to map, absolute or relative to the server's working directory"
|
|
16
|
+
},
|
|
17
|
+
"focus": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"items": {
|
|
20
|
+
"type": "string"
|
|
21
|
+
},
|
|
22
|
+
"description": "The files you are working on, relative to `root` and POSIX-separated. They are excluded from the listing. A name that is not a scanned source is ignored rather than refused."
|
|
23
|
+
},
|
|
24
|
+
"budget": {
|
|
25
|
+
"type": "integer",
|
|
26
|
+
"minimum": 0,
|
|
27
|
+
"description": "UTF-8 bytes of listing to render; default 4000. The omission footer is not charged against it."
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"output_schema": {
|
|
32
|
+
"properties": {
|
|
33
|
+
"result": {
|
|
34
|
+
"title": "Result",
|
|
35
|
+
"type": "string"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"required": [
|
|
39
|
+
"result"
|
|
40
|
+
],
|
|
41
|
+
"type": "object",
|
|
42
|
+
"title": "repo_mapOutput"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/docread.d.ts
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
* (`utf8Scan`).
|
|
35
35
|
*/
|
|
36
36
|
import { BantamError } from './errors.js';
|
|
37
|
+
import { cmpCodepoint, pyRepr, pyStrip } from './pysem.js';
|
|
37
38
|
export declare const NS_S = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}";
|
|
38
39
|
export declare const NS_W = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}";
|
|
39
40
|
export declare const NS_R = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}";
|
|
@@ -52,19 +53,15 @@ export declare const OMIT_UNMAPPED = "unmapped-text";
|
|
|
52
53
|
export declare const OMIT_UNREAD_TAIL = "unread-tail";
|
|
53
54
|
export declare const OMIT_SIZE_CAP = "size-cap";
|
|
54
55
|
export declare const OMIT_UNPLACED_CELL = "unplaced-cell";
|
|
56
|
+
export declare const OMIT_DUPLICATE_CELL = "duplicate-cell";
|
|
55
57
|
/** Extraction failed. The message names what was seen, never just the format's own error. */
|
|
56
58
|
export declare class DocumentReadError extends BantamError {
|
|
57
59
|
}
|
|
58
|
-
|
|
59
|
-
export declare function pyStrip(s: string): string;
|
|
60
|
+
export { cmpCodepoint, pyRepr, pyStrip };
|
|
60
61
|
/** `str.split()` with no argument: runs of whitespace, no empty fields. */
|
|
61
62
|
export declare function pySplit(s: string): string[];
|
|
62
63
|
/** `str.splitlines()`: Python's line boundaries, terminators dropped. */
|
|
63
64
|
export declare function pySplitlines(s: string): string[];
|
|
64
|
-
/** `sorted()` on strings: by code point, which is not what `Array.sort` does above U+FFFF. */
|
|
65
|
-
export declare function cmpCodepoint(a: string, b: string): number;
|
|
66
|
-
/** `repr(str)`. */
|
|
67
|
-
export declare function pyRepr(s: string): string;
|
|
68
65
|
/** `repr(bytes)`. */
|
|
69
66
|
export declare function bytesRepr(buf: Uint8Array): string;
|
|
70
67
|
export interface Utf8Scan {
|
|
@@ -190,6 +187,35 @@ export declare class BadZipFile extends Error {
|
|
|
190
187
|
constructor(message: string);
|
|
191
188
|
}
|
|
192
189
|
export declare function crc32(buf: Uint8Array): number;
|
|
190
|
+
/**
|
|
191
|
+
* How much ONE MEMBER of a zip this reader will decompress and parse. The ceiling on the
|
|
192
|
+
* PARSE, which is a different door from `XLSX_MAX_TEXT_BYTES`: that one bounds the text a
|
|
193
|
+
* workbook renders, and it never stands in front of this, because `readMember` decompresses
|
|
194
|
+
* the member whole and `parsePart` builds a tree from it before the first row is rendered.
|
|
195
|
+
*
|
|
196
|
+
* MEASURED 2026-09-06 on this machine, through `docread.extract` on THIS runtime at the
|
|
197
|
+
* shipped constants, on a sheet of N `<row><c t="inlineStr"><is><t>x</t></is></c></row>`
|
|
198
|
+
* deflated by `deflateRawSync`, peak RSS from `process.resourceUsage().maxRSS`:
|
|
199
|
+
* 400,000 rows are 58,378 bytes on disk, declare 19,600,112, and took RSS from 102.2 MB to
|
|
200
|
+
* 1,039.3 MB in 1,019 ms with NO omission; 1,000,000 rows are 143,939 bytes, declare
|
|
201
|
+
* 49,000,112, and took it from 158.6 MB to 2,290.8 MB in 2,667 ms. Linear and unbounded —
|
|
202
|
+
* the memory is the `XmlElement` tree, not the text, so a budget over the rendering could
|
|
203
|
+
* not see it. The reference measured the same shape at 342.2 MB and 838.8 MB through
|
|
204
|
+
* `tracemalloc`; the numbers are not comparable (one counts a Python allocator, the other a
|
|
205
|
+
* whole process) and the SHAPE is what both of them state.
|
|
206
|
+
*
|
|
207
|
+
* 16 MiB, the number `TEXT_MAX_BYTES` states, because "how much of a file this reader reads"
|
|
208
|
+
* is one number — but under its OWN NAME, because it bounds a member of a container and not a
|
|
209
|
+
* file on a disk, and a bar that varies one must not be varying the other. The reference
|
|
210
|
+
* measured the corpus behind the figure: 25 OOXML/ODF packages under the goal's roots, the
|
|
211
|
+
* largest single XML member among them 4,283,286 bytes, 3.9x under this.
|
|
212
|
+
*
|
|
213
|
+
* A REFUSAL and not a truncation, which is the one place this module departs from
|
|
214
|
+
* "disclose, never truncate": half an XML document is not a smaller XML document, and a tree
|
|
215
|
+
* built from a severed member would carry text that is not what the file says. So the reader
|
|
216
|
+
* stops and names the member, the number the file declares and its own ceiling.
|
|
217
|
+
*/
|
|
218
|
+
export declare const ZIP_MEMBER_MAX_BYTES: number;
|
|
193
219
|
/** `zipfile.ZipFile` read-only: the central directory, and `read(name)` with a CRC check. */
|
|
194
220
|
export declare class ZipReader {
|
|
195
221
|
private readonly data;
|
|
@@ -209,6 +235,8 @@ export declare class ZipReader {
|
|
|
209
235
|
isDir: boolean;
|
|
210
236
|
}[];
|
|
211
237
|
has(name: string): boolean;
|
|
238
|
+
/** `getinfo(name).file_size`: what the central directory DECLARES, before any inflate. */
|
|
239
|
+
declaredSize(name: string): number;
|
|
212
240
|
/** `ZipFile.read(name)`; a missing member is a `KeyError` — here `RangeError`, see `readMember`. */
|
|
213
241
|
read(name: string): Buffer;
|
|
214
242
|
}
|
|
@@ -293,6 +321,32 @@ export declare const XLSX_MAX_COLUMNS = 16384;
|
|
|
293
321
|
*/
|
|
294
322
|
export declare const UNPLACED_SHAPE = "the column of a cell whose reference is not letters then digits";
|
|
295
323
|
export declare const UNPLACED_RANGE = "the column of a cell past XFD, the last column the format has";
|
|
324
|
+
export declare const DUPLICATE_CELL = "the text of a cell a later cell in the same row and column replaced";
|
|
325
|
+
/**
|
|
326
|
+
* How much text ONE WORKBOOK may materialise, all sheets together. `XLSX_MAX_COLUMNS` bounds a
|
|
327
|
+
* ROW and nothing bounded the document, which is a ceiling with a hole in it: the width is
|
|
328
|
+
* bought a row at a time. MEASURED 2026-09-06 on both runtimes: 20,000 rows each holding one
|
|
329
|
+
* `XFD1` cell deflate to 53,967 bytes and materialise 327,680,000 bytes in ~9 s — 6,072x, out
|
|
330
|
+
* of a file small enough to mail.
|
|
331
|
+
*
|
|
332
|
+
* 16 MiB, the same figure `TEXT_MAX_BYTES` carries and for the same kind of reason, but under
|
|
333
|
+
* its OWN NAME because the two bound different things: bytes read off a disk there, bytes
|
|
334
|
+
* rendered out of a container here, and a bar that varies one must not be varying the other.
|
|
335
|
+
* MEASURED 2026-09-06 over the goal's roots: 15 `.xlsx`, the largest 8,664,227 bytes on disk,
|
|
336
|
+
* and the largest RENDERING among them 754,520 bytes — 22x under this budget, so no real
|
|
337
|
+
* workbook on this machine meets it.
|
|
338
|
+
*
|
|
339
|
+
* It bounds the RENDERING and nothing else. The sentence that stood here said it bounded the
|
|
340
|
+
* rendering "because the file is already bounded", and that was FALSE for the whole life of
|
|
341
|
+
* this constant: `readMember` decompressed a member whole and `parsePart` built a tree from
|
|
342
|
+
* it, both before the first row was rendered and both outside this budget, so 58,378 bytes on
|
|
343
|
+
* disk took RSS to 1,039.3 MB with no omission and this ceiling never saw it (review round 5,
|
|
344
|
+
* H1). What bounds the file is `ZIP_MEMBER_MAX_BYTES`, one door earlier; this bounds what
|
|
345
|
+
* comes out of it. The shortfall is disclosed as `OMIT_SIZE_CAP` counting the rows no sheet
|
|
346
|
+
* rendered: a row is what a caller addresses, and a byte count of text that was never built
|
|
347
|
+
* would be a number this reader cannot honestly produce.
|
|
348
|
+
*/
|
|
349
|
+
export declare const XLSX_MAX_TEXT_BYTES: number;
|
|
296
350
|
/**
|
|
297
351
|
* `B7` -> `[1, '']`. The cell's own reference decides its column; XML order is a fallback.
|
|
298
352
|
*
|
|
@@ -320,8 +374,22 @@ export declare const UNPLACED_RANGE = "the column of a cell past XFD, the last c
|
|
|
320
374
|
* spends asking for more.
|
|
321
375
|
*/
|
|
322
376
|
export declare function columnIndex(ref: string | undefined, fallback: number): [number, string];
|
|
323
|
-
|
|
324
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Every declared sheet, under ONE `XLSX_MAX_TEXT_BYTES` budget for the whole workbook.
|
|
379
|
+
*
|
|
380
|
+
* The budget is the document's, so it is made here and not in `sheetRows`, and the shortfall
|
|
381
|
+
* is disclosed at the document's grain for the same reason — it is not a fact about the sheet
|
|
382
|
+
* the budget happened to run out on. The cap is stated BEFORE the media tally, because the
|
|
383
|
+
* media a reader met is a count of what it met underneath the cap.
|
|
384
|
+
*
|
|
385
|
+
* `opened` is the reader `sniff` already had to build to decide this file is an OOXML package
|
|
386
|
+
* (entries (a)/(j)): before it was handed down, one `extract` read the whole archive off disk
|
|
387
|
+
* TWICE. It is a parameter and never a field, so its lifetime is this call — a reader kept
|
|
388
|
+
* across calls would answer from bytes no longer on disk, which is exactly what the document
|
|
389
|
+
* cache one layer up is keyed on (realpath, size, mtime_ns) to avoid.
|
|
390
|
+
*/
|
|
391
|
+
export declare function extractXlsx(path: string, opened?: ZipReader | null): Document;
|
|
392
|
+
export declare function extractDocx(path: string, opened?: ZipReader | null): Document;
|
|
325
393
|
/** `html.unescape`. */
|
|
326
394
|
export declare function unescape(s: string): string;
|
|
327
395
|
/** Rendered rows of one HTML fragment. A pure function of the string: no I/O, no host. */
|
|
@@ -349,6 +417,12 @@ export declare function a2bUu(line: Uint8Array): Buffer;
|
|
|
349
417
|
*/
|
|
350
418
|
export declare function decodeUu(encoded: Buffer): Buffer;
|
|
351
419
|
export declare function extractMhtml(path: string): Document;
|
|
420
|
+
/**
|
|
421
|
+
* Markup to rows, up to `TEXT_MAX_BYTES` of it, saying how much it did not read.
|
|
422
|
+
*
|
|
423
|
+
* BOUNDED, entry (k): this did `readFileSync(path)`, so a 1 GB `.html` was materialised whole
|
|
424
|
+
* where a 1 GB `.txt` had stopped at the cap and counted the rest since J10.
|
|
425
|
+
*/
|
|
352
426
|
export declare function extractHtml(path: string): Document;
|
|
353
427
|
export declare function extractText(path: string): Document;
|
|
354
428
|
/** Dispatch on what the file IS. Anything unreadable raises, naming the container. */
|