bantamkit-mcp 0.26.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.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +398 -0
  3. package/assets/contracts/default.yaml +90 -0
  4. package/assets/evals/devteam/manifest.yaml +351 -0
  5. package/assets/evals/devteam/repo/HISTORY.md +18 -0
  6. package/assets/evals/devteam/repo/README.md +12 -0
  7. package/assets/evals/devteam/repo/docs/architecture.md +17 -0
  8. package/assets/evals/devteam/repo/docs/runbook.md +10 -0
  9. package/assets/evals/devteam/repo/issues/142-settlement-timeout.md +23 -0
  10. package/assets/evals/devteam/repo/patches/0009-retry-budget.patch +38 -0
  11. package/assets/evals/devteam/repo/src/ledger/__init__.py +3 -0
  12. package/assets/evals/devteam/repo/src/ledger/config.py +35 -0
  13. package/assets/evals/devteam/repo/src/ledger/errors.py +13 -0
  14. package/assets/evals/devteam/repo/src/ledger/posting.py +12 -0
  15. package/assets/evals/devteam/repo/src/ledger/registry.py +7 -0
  16. package/assets/evals/devteam/repo/src/ledger/report.py +9 -0
  17. package/assets/evals/devteam/repo/src/ledger/retry.py +17 -0
  18. package/assets/evals/devteam/repo/src/ledger/settle.py +16 -0
  19. package/assets/evals/devteam/repo/src/ledger/validate.py +14 -0
  20. package/assets/evals/devteam/repo/tests/test_posting.py +13 -0
  21. package/assets/evals/devteam/repo/tests/test_settle.py +9 -0
  22. package/assets/evals/devteam/tasks/dt-error-contract.yaml +186 -0
  23. package/assets/evals/devteam/tasks/dt-handler-map.yaml +183 -0
  24. package/assets/evals/devteam/tasks/dt-patch-before-after.yaml +182 -0
  25. package/assets/evals/devteam/tasks/dt-retry-attempts.yaml +181 -0
  26. package/assets/evals/devteam/tasks/dt-settlement-config.yaml +185 -0
  27. package/assets/evals/devteam/tasks/dt-symbol-home.yaml +181 -0
  28. package/assets/evals/devteam/tasks/dt-trace-blame.yaml +182 -0
  29. package/assets/evals/devteam/tasks/dt-unread-key.yaml +180 -0
  30. package/assets/evals/document/tasks/doc-large-in-137.yaml +38 -0
  31. package/assets/evals/document/tasks/doc-large-in-359.yaml +44 -0
  32. package/assets/evals/document/tasks/doc-large-in-372.yaml +38 -0
  33. package/assets/evals/document/tasks/doc-large-out-11764.yaml +37 -0
  34. package/assets/evals/document/tasks/doc-large-out-4137.yaml +37 -0
  35. package/assets/evals/document/tasks/doc-large-out-8022.yaml +37 -0
  36. package/assets/evals/document/tasks/doc-small-137.yaml +37 -0
  37. package/assets/evals/document/tasks/doc-small-261.yaml +37 -0
  38. package/assets/evals/document/tasks/doc-small-388.yaml +37 -0
  39. package/assets/evals/fixtures/.gitkeep +0 -0
  40. package/assets/evals/fixtures/catalog.json +6 -0
  41. package/assets/evals/perturbations/task-completion.yaml +576 -0
  42. package/assets/evals/tasks/.gitkeep +0 -0
  43. package/assets/evals/tasks/extract-contact.yaml +14 -0
  44. package/assets/evals/tasks/extract-invoice.yaml +14 -0
  45. package/assets/evals/tasks/extract-order.yaml +15 -0
  46. package/assets/evals/tasks/extract-schedule.yaml +14 -0
  47. package/assets/evals/tasks/extract-versions.yaml +17 -0
  48. package/assets/evals/tasks/nav-prod-port.yaml +84 -0
  49. package/assets/evals/tasks/nav-release-bundle.yaml +87 -0
  50. package/assets/evals/tasks/recall-audit-retention.yaml +17 -0
  51. package/assets/evals/tasks/recall-cache-ttl.yaml +13 -0
  52. package/assets/evals/tasks/recall-db-port.yaml +17 -0
  53. package/assets/evals/tasks/recall-deploy.yaml +13 -0
  54. package/assets/evals/tasks/recall-env-endpoint.yaml +18 -0
  55. package/assets/evals/tasks/recall-oncall-rotation.yaml +21 -0
  56. package/assets/evals/tasks/recall-oncall.yaml +13 -0
  57. package/assets/evals/tasks/recall-org-quota.yaml +18 -0
  58. package/assets/evals/tasks/recall-owner.yaml +13 -0
  59. package/assets/evals/tasks/shop-basket-total.yaml +10 -0
  60. package/assets/evals/tasks/shop-cheapest.yaml +9 -0
  61. package/assets/evals/tasks/shop-compare.yaml +9 -0
  62. package/assets/evals/tasks/shop-gadget-value.yaml +9 -0
  63. package/assets/evals/tasks/shop-stock-total.yaml +9 -0
  64. package/assets/evals/tasks/shop-total.yaml +9 -0
  65. package/assets/profiles/default.yaml +31 -0
  66. package/assets/profiles/patient.yaml +31 -0
  67. package/assets/rubrics/.gitkeep +0 -0
  68. package/assets/rubrics/code-quality.yaml +20 -0
  69. package/assets/rubrics/grounded-completion.yaml +37 -0
  70. package/assets/rubrics/task-completion.yaml +28 -0
  71. package/assets/schemas/shiftwork-checkpoint.json +188 -0
  72. package/assets/skills/.gitkeep +0 -0
  73. package/assets/skills/file-graph.md +7 -0
  74. package/assets/skills/memory.md +35 -0
  75. package/assets/tools/.gitkeep +0 -0
  76. package/assets/tools/bantamkit_read.json +48 -0
  77. package/assets/tools/bantamkit_status.json +25 -0
  78. package/assets/tools/build_identity.json +17 -0
  79. package/assets/tools/document_list.json +12 -0
  80. package/assets/tools/document_read.json +31 -0
  81. package/assets/tools/file_graph.json +12 -0
  82. package/assets/tools/memory_compact.json +31 -0
  83. package/assets/tools/memory_recall.json +38 -0
  84. package/assets/tools/memory_save.json +61 -0
  85. package/assets/tools/shiftwork_clock_in.json +25 -0
  86. package/assets/tools/shiftwork_clock_out.json +60 -0
  87. package/assets/tools/shiftwork_status.json +25 -0
  88. package/assets/tools/skill_audit.json +70 -0
  89. package/assets/tools/validate_json.json +31 -0
  90. package/dist/assets.d.ts +48 -0
  91. package/dist/assets.js +109 -0
  92. package/dist/charsets.d.ts +17 -0
  93. package/dist/charsets.js +580 -0
  94. package/dist/cli.d.ts +12 -0
  95. package/dist/cli.js +280 -0
  96. package/dist/contract.d.ts +145 -0
  97. package/dist/contract.js +608 -0
  98. package/dist/docread.d.ts +357 -0
  99. package/dist/docread.js +3315 -0
  100. package/dist/errors.d.ts +11 -0
  101. package/dist/errors.js +14 -0
  102. package/dist/eventlog.d.ts +137 -0
  103. package/dist/eventlog.js +294 -0
  104. package/dist/htmlentities.d.ts +2 -0
  105. package/dist/htmlentities.js +16 -0
  106. package/dist/index.d.ts +23 -0
  107. package/dist/index.js +13 -0
  108. package/dist/mcp/identity.d.ts +25 -0
  109. package/dist/mcp/identity.js +275 -0
  110. package/dist/mcp/pyargs.d.ts +80 -0
  111. package/dist/mcp/pyargs.js +362 -0
  112. package/dist/mcp/sdkjson.d.ts +56 -0
  113. package/dist/mcp/sdkjson.js +91 -0
  114. package/dist/mcp/server.d.ts +45 -0
  115. package/dist/mcp/server.js +740 -0
  116. package/dist/mcp/status.d.ts +210 -0
  117. package/dist/mcp/status.js +357 -0
  118. package/dist/mcp/transport.d.ts +68 -0
  119. package/dist/mcp/transport.js +111 -0
  120. package/dist/mcpreport.d.ts +214 -0
  121. package/dist/mcpreport.js +758 -0
  122. package/dist/memory/cli.d.ts +2 -0
  123. package/dist/memory/cli.js +396 -0
  124. package/dist/memory/component.d.ts +256 -0
  125. package/dist/memory/component.js +497 -0
  126. package/dist/memory/factfile.d.ts +120 -0
  127. package/dist/memory/factfile.js +406 -0
  128. package/dist/memory/layers.d.ts +92 -0
  129. package/dist/memory/layers.js +410 -0
  130. package/dist/memory/pyfs.d.ts +521 -0
  131. package/dist/memory/pyfs.js +1608 -0
  132. package/dist/memory/pyyaml.d.ts +155 -0
  133. package/dist/memory/pyyaml.js +868 -0
  134. package/dist/memory/store.d.ts +478 -0
  135. package/dist/memory/store.js +1034 -0
  136. package/dist/pyargparse.d.ts +141 -0
  137. package/dist/pyargparse.js +813 -0
  138. package/dist/pyjson.d.ts +110 -0
  139. package/dist/pyjson.js +524 -0
  140. package/dist/pyjsonschema.d.ts +50 -0
  141. package/dist/pyjsonschema.js +951 -0
  142. package/dist/shiftwork.d.ts +60 -0
  143. package/dist/shiftwork.js +324 -0
  144. package/dist/skillaudit.d.ts +172 -0
  145. package/dist/skillaudit.js +914 -0
  146. package/dist/statusline.d.ts +106 -0
  147. package/dist/statusline.js +281 -0
  148. package/package.json +45 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * How many of the log's most recent records the line describes.
3
+ *
4
+ * The bar answers "what is happening now"; "what has ever happened" is `--mcp-report`'s
5
+ * question and it reads both generations to answer it. A fixed window also bounds the two
6
+ * counts on the line, so the rendered string cannot grow with the size of somebody's log.
7
+ */
8
+ export declare const WINDOW = 50;
9
+ /**
10
+ * The most bytes read off the end of the log, ever.
11
+ *
12
+ * The log rotates itself at 1 MiB, so under bantamkit's own default this never fires. It
13
+ * fires when `BANTAMKIT_EVENT_LOG` names a LITERAL path — an operator can point that at
14
+ * anything, and a redraw path may not be at the mercy of how big that anything is. A
15
+ * partial first line is discarded, so the tail parses identically either way.
16
+ *
17
+ * The rotated generation `<path>.1` is NOT read. It is history, and history is the
18
+ * analyst's job.
19
+ */
20
+ export declare const TAIL_BYTES = 262144;
21
+ /** Every `outcome` `docs/eventlog.md` defines, and nothing else is renderable. */
22
+ export declare const OUTCOMES: ReadonlySet<string>;
23
+ /**
24
+ * The three outcomes that mean BANTAMKIT is in trouble, rather than the caller's input.
25
+ *
26
+ * `raised` is a handler that fell over. `refused-budget` is the store's index budget
27
+ * stopping a save — `docs/status.md`'s `index-budget-low`, one step too late.
28
+ * `empty-unreadable-layer` is its `memory-layer-unreadable`, observed from outside.
29
+ *
30
+ * Deliberately absent: `refused-validation`, `error`, `escalate`, `invalid` and the other
31
+ * `empty-*` values. Every one of those is the tool working on input it was right to
32
+ * refuse, and a bar that turned orange for them is a bar the operator learns to ignore.
33
+ */
34
+ export declare const ADVERSE: readonly string[];
35
+ export declare const ACTIVE = "bantamkit Active \uD83D\uDFE2";
36
+ export declare const DEGRADED = "bantamkit Degraded \uD83D\uDFE0";
37
+ export declare const UNKNOWN = "bantamkit Unknown \u26AA";
38
+ /** One space, U+00B7, one space. */
39
+ export declare const SEP = " \u00B7 ";
40
+ export declare const OFF = "event log off";
41
+ export declare const ABSENT = "no event log yet";
42
+ export declare const UNREADABLE = "event log unreadable";
43
+ export declare const EMPTY = "event log empty";
44
+ export declare const SURPRISE = "state unavailable";
45
+ /** What one look at the log found. Integers and closed-vocabulary tokens only. */
46
+ export interface Reading {
47
+ state: 'active' | 'degraded' | 'unknown';
48
+ events: number;
49
+ problems: number;
50
+ tool: string;
51
+ outcome: string;
52
+ reason: string;
53
+ }
54
+ /**
55
+ * The last `TAIL_BYTES` of `path`, with a partial leading line discarded.
56
+ *
57
+ * Read as BYTES and never decoded as a whole: a truncated tail can start mid-codepoint,
58
+ * and decoding the buffer in one go would replace that byte and leave every intact record
59
+ * behind it fine — but the reference decodes per line, so this does too, and one bad line
60
+ * costs one record on both sides.
61
+ *
62
+ * Throws the underlying filesystem error; the caller turns it into a rendered state.
63
+ */
64
+ export declare function readTail(path: string): Buffer;
65
+ /**
66
+ * `[tool, outcome]` for every line that is a record this module is willing to render.
67
+ *
68
+ * THIS IS THE LEAK GUARD AND IT IS STRUCTURAL, NOT A FILTER. What comes back is a list of
69
+ * pairs of validated tokens; there is no field here that can hold a path, a memory body, a
70
+ * query or a `detail` value, so no later code CAN print one. A record with an unknown
71
+ * outcome, a tool that is not `^[a-z0-9_]{1,64}$`, a non-object body, or unparseable JSON
72
+ * is dropped whole — never rendered under a fallback spelling, because a fallback spelling
73
+ * is a way for the file's own bytes to reach the bar.
74
+ */
75
+ export declare function parse(data: Buffer): Array<[string, string]>;
76
+ /**
77
+ * The window's state: degraded if it holds an `ADVERSE` outcome, else active.
78
+ *
79
+ * The record the line NAMES is the most recent adverse one when degraded, and the most
80
+ * recent one when active. Naming the last record while degraded would hide the fault
81
+ * behind whatever happened to run after it.
82
+ */
83
+ export declare function summarise(pairs: ReadonlyArray<readonly [string, string]>): Reading;
84
+ /**
85
+ * Resolve the log the same way the server would, read it, and summarise it.
86
+ *
87
+ * `resolveEventLogPath` is reused rather than reimplemented so this runtime has ONE
88
+ * vocabulary for `BANTAMKIT_EVENT_LOG`. It DESIGNATES a store path without creating one,
89
+ * so drawing a status bar never brings a memory store into existence.
90
+ */
91
+ export declare function probe(env: Readonly<Record<string, string | undefined>>, store?: string | null, start?: string | null): Reading;
92
+ /**
93
+ * The one line, without its newline. Never empty, never multi-line.
94
+ *
95
+ * An empty line would make the host's bar flicker between one row and none, which is worse
96
+ * than any wording; that is why the unknown state has a rendering at all.
97
+ */
98
+ export declare function render(reading: Reading): string;
99
+ /**
100
+ * The total entry point: a string for every input, including inputs that are wrong.
101
+ *
102
+ * The bare `catch` is not laziness, it is the contract. This runs on somebody's redraw path
103
+ * with no operator watching; the failure it must not have is a traceback where a status bar
104
+ * should be.
105
+ */
106
+ export declare function statusLine(env: Readonly<Record<string, string | undefined>>, store?: string | null, start?: string | null): string;
@@ -0,0 +1,281 @@
1
+ /**
2
+ * One line for a host's status bar, rendered from what a finished session left on disk.
3
+ *
4
+ * The contract is `docs/statusline.md` and the reference is
5
+ * `runtime-py/src/bantamkit/statusline.py`; `tools/conformance/suites/statusline.mjs`
6
+ * runs both as PROCESSES over one synthetic fixture set and compares stdout, stderr and
7
+ * the exit code. This file implements the document, not the Python — but every constant
8
+ * below has a named counterpart there, so a reader can put the two side by side.
9
+ *
10
+ * WHY THIS IS NOT A SIXTH HOST. `bantamkit_status` and its prompt (`docs/status.md`) are
11
+ * rendered ON DEMAND. This one is rendered WITHOUT ANYONE ASKING, on the host's redraw
12
+ * path, and `statusLine` is a key in `~/.claude/settings.json` — **Claude Code only**. It
13
+ * has no analogue in Claude Desktop, in the Copilot CLI, or in either VS Code extension.
14
+ *
15
+ * THREE PROPERTIES, EACH THE OPPOSITE OF A DEFECT JOB39 MEASURED BY CONNECTING.
16
+ *
17
+ * * **It never starts a server.** Nothing here opens a transport or builds a `Memory`.
18
+ * It reads at most one file.
19
+ * * **It never fails loudly.** `statusLine()` is total: every path returns a string,
20
+ * and the outermost `catch` renders the UNKNOWN state. The packaged `bin` dies with a
21
+ * raw Node stack trace on a bad `cwd`; a redraw path may not reproduce that. Unknown
22
+ * is a state to render, not an error to raise.
23
+ * * **It reads what is already true.** No clock, no heartbeat, no timer.
24
+ *
25
+ * WHY THE EVENT LOG AND NOT `--mcp-report`. `--mcp-report` reads source B too, but only
26
+ * after source A. Measured on this machine 2026-08-24, 25 runs each: bare `node -e 0` p50
27
+ * 18.1 ms, `--statusline` p50 69.4 ms, `--mcp-report` p50 88.6 ms over 22 slug directories,
28
+ * 112 files and 1674 records. The 19 ms is the smaller half of the reason: source A is
29
+ * UNBOUNDED and bantamkit does not own it — the host never rotates it, and it grew from
30
+ * 1533 records to 1674 in five days. Source B is bounded by its own rotation rule and
31
+ * bounded again here by `TAIL_BYTES`. The third reason is shape: `--mcp-report` prints
32
+ * forty lines and a bar takes one, so an adapter over it would have to PARSE the report.
33
+ *
34
+ * METADATA ONLY, AND HERE IT IS STRUCTURAL. `parse` keeps exactly two strings per record:
35
+ * a `tool` matching `^[a-z0-9_]{1,64}$` and an `outcome` from `OUTCOMES`. `detail`, `ts`
36
+ * and `v` are never read. No parsed record has a field that could hold borrowed text, so
37
+ * no code path CAN print one.
38
+ */
39
+ import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
40
+ import { resolveEventLogPath } from './mcpreport.js';
41
+ /**
42
+ * How many of the log's most recent records the line describes.
43
+ *
44
+ * The bar answers "what is happening now"; "what has ever happened" is `--mcp-report`'s
45
+ * question and it reads both generations to answer it. A fixed window also bounds the two
46
+ * counts on the line, so the rendered string cannot grow with the size of somebody's log.
47
+ */
48
+ export const WINDOW = 50;
49
+ /**
50
+ * The most bytes read off the end of the log, ever.
51
+ *
52
+ * The log rotates itself at 1 MiB, so under bantamkit's own default this never fires. It
53
+ * fires when `BANTAMKIT_EVENT_LOG` names a LITERAL path — an operator can point that at
54
+ * anything, and a redraw path may not be at the mercy of how big that anything is. A
55
+ * partial first line is discarded, so the tail parses identically either way.
56
+ *
57
+ * The rotated generation `<path>.1` is NOT read. It is history, and history is the
58
+ * analyst's job.
59
+ */
60
+ export const TAIL_BYTES = 262144;
61
+ /** Every `outcome` `docs/eventlog.md` defines, and nothing else is renderable. */
62
+ export const OUTCOMES = new Set([
63
+ // memory_save
64
+ 'saved',
65
+ 'duplicate',
66
+ 'refused-validation',
67
+ 'refused-budget',
68
+ // memory_recall
69
+ 'answered',
70
+ 'empty-no-match',
71
+ 'empty-unreadable-layer',
72
+ 'empty-nothing-saved',
73
+ // validate_json
74
+ 'valid',
75
+ 'invalid',
76
+ // shiftwork_clock_in
77
+ 'brief',
78
+ 'escalate',
79
+ 'success',
80
+ 'error',
81
+ // shiftwork_clock_out / shiftwork_status
82
+ 'ok',
83
+ 'status',
84
+ // build_identity
85
+ 'complete',
86
+ 'partial',
87
+ // any handler that fell over
88
+ 'raised',
89
+ ]);
90
+ /**
91
+ * The three outcomes that mean BANTAMKIT is in trouble, rather than the caller's input.
92
+ *
93
+ * `raised` is a handler that fell over. `refused-budget` is the store's index budget
94
+ * stopping a save — `docs/status.md`'s `index-budget-low`, one step too late.
95
+ * `empty-unreadable-layer` is its `memory-layer-unreadable`, observed from outside.
96
+ *
97
+ * Deliberately absent: `refused-validation`, `error`, `escalate`, `invalid` and the other
98
+ * `empty-*` values. Every one of those is the tool working on input it was right to
99
+ * refuse, and a bar that turned orange for them is a bar the operator learns to ignore.
100
+ */
101
+ export const ADVERSE = ['raised', 'refused-budget', 'empty-unreadable-layer'];
102
+ /** The host's log validates tool names with exactly this, and so does `mcpreport.ts`. */
103
+ const TOOL = /^[a-z0-9_]{1,64}$/;
104
+ export const ACTIVE = 'bantamkit Active 🟢';
105
+ export const DEGRADED = 'bantamkit Degraded 🟠';
106
+ export const UNKNOWN = 'bantamkit Unknown ⚪';
107
+ /** One space, U+00B7, one space. */
108
+ export const SEP = ' · ';
109
+ export const OFF = 'event log off';
110
+ export const ABSENT = 'no event log yet';
111
+ export const UNREADABLE = 'event log unreadable';
112
+ export const EMPTY = 'event log empty';
113
+ export const SURPRISE = 'state unavailable';
114
+ const unknown = (reason) => ({
115
+ state: 'unknown',
116
+ events: 0,
117
+ problems: 0,
118
+ tool: '',
119
+ outcome: '',
120
+ reason,
121
+ });
122
+ /**
123
+ * The last `TAIL_BYTES` of `path`, with a partial leading line discarded.
124
+ *
125
+ * Read as BYTES and never decoded as a whole: a truncated tail can start mid-codepoint,
126
+ * and decoding the buffer in one go would replace that byte and leave every intact record
127
+ * behind it fine — but the reference decodes per line, so this does too, and one bad line
128
+ * costs one record on both sides.
129
+ *
130
+ * Throws the underlying filesystem error; the caller turns it into a rendered state.
131
+ */
132
+ export function readTail(path) {
133
+ const fd = openSync(path, 'r');
134
+ let data;
135
+ try {
136
+ const size = fstatSync(fd).size;
137
+ const start = size > TAIL_BYTES ? size - TAIL_BYTES : 0;
138
+ const length = size - start;
139
+ const buffer = Buffer.allocUnsafe(length);
140
+ let filled = 0;
141
+ while (filled < length) {
142
+ const got = readSync(fd, buffer, filled, length - filled, start + filled);
143
+ if (got === 0)
144
+ break;
145
+ filled += got;
146
+ }
147
+ data = buffer.subarray(0, filled);
148
+ if (start > 0) {
149
+ const cut = data.indexOf(0x0a);
150
+ data = cut < 0 ? Buffer.alloc(0) : data.subarray(cut + 1);
151
+ }
152
+ }
153
+ finally {
154
+ closeSync(fd);
155
+ }
156
+ return data;
157
+ }
158
+ /**
159
+ * `[tool, outcome]` for every line that is a record this module is willing to render.
160
+ *
161
+ * THIS IS THE LEAK GUARD AND IT IS STRUCTURAL, NOT A FILTER. What comes back is a list of
162
+ * pairs of validated tokens; there is no field here that can hold a path, a memory body, a
163
+ * query or a `detail` value, so no later code CAN print one. A record with an unknown
164
+ * outcome, a tool that is not `^[a-z0-9_]{1,64}$`, a non-object body, or unparseable JSON
165
+ * is dropped whole — never rendered under a fallback spelling, because a fallback spelling
166
+ * is a way for the file's own bytes to reach the bar.
167
+ */
168
+ export function parse(data) {
169
+ // `fatal: true` is the reference's `bytes.decode('utf-8')`, which RAISES. Node's default
170
+ // decoder substitutes U+FFFD instead, and a record whose `detail` held an invalid byte
171
+ // would then survive here and be dropped there — a divergence in a compared line, bought
172
+ // for nothing. One bad line costs one record on both sides.
173
+ const utf8 = new TextDecoder('utf-8', { fatal: true });
174
+ const pairs = [];
175
+ let cursor = 0;
176
+ while (cursor <= data.length) {
177
+ let stop = data.indexOf(0x0a, cursor);
178
+ if (stop < 0)
179
+ stop = data.length;
180
+ const line = data.subarray(cursor, stop);
181
+ cursor = stop + 1;
182
+ if (line.length === 0)
183
+ continue;
184
+ let record;
185
+ try {
186
+ record = JSON.parse(utf8.decode(line));
187
+ }
188
+ catch {
189
+ continue;
190
+ }
191
+ if (record === null || typeof record !== 'object' || Array.isArray(record))
192
+ continue;
193
+ const { tool, outcome } = record;
194
+ if (typeof tool !== 'string' || typeof outcome !== 'string')
195
+ continue;
196
+ if (!TOOL.test(tool) || !OUTCOMES.has(outcome))
197
+ continue;
198
+ pairs.push([tool, outcome]);
199
+ }
200
+ return pairs;
201
+ }
202
+ /**
203
+ * The window's state: degraded if it holds an `ADVERSE` outcome, else active.
204
+ *
205
+ * The record the line NAMES is the most recent adverse one when degraded, and the most
206
+ * recent one when active. Naming the last record while degraded would hide the fault
207
+ * behind whatever happened to run after it.
208
+ */
209
+ export function summarise(pairs) {
210
+ const window = pairs.slice(Math.max(0, pairs.length - WINDOW));
211
+ if (window.length === 0)
212
+ return unknown(EMPTY);
213
+ const problems = window.filter(([, outcome]) => ADVERSE.includes(outcome));
214
+ const [tool, outcome] = problems.length > 0 ? problems[problems.length - 1] : window[window.length - 1];
215
+ return {
216
+ state: problems.length > 0 ? 'degraded' : 'active',
217
+ events: window.length,
218
+ problems: problems.length,
219
+ tool,
220
+ outcome,
221
+ reason: '',
222
+ };
223
+ }
224
+ /**
225
+ * Resolve the log the same way the server would, read it, and summarise it.
226
+ *
227
+ * `resolveEventLogPath` is reused rather than reimplemented so this runtime has ONE
228
+ * vocabulary for `BANTAMKIT_EVENT_LOG`. It DESIGNATES a store path without creating one,
229
+ * so drawing a status bar never brings a memory store into existence.
230
+ */
231
+ export function probe(env, store, start) {
232
+ let path;
233
+ try {
234
+ path = resolveEventLogPath(env, store, start);
235
+ }
236
+ catch {
237
+ return unknown(UNREADABLE);
238
+ }
239
+ if (path === null)
240
+ return unknown(OFF);
241
+ let data;
242
+ try {
243
+ data = readTail(path);
244
+ }
245
+ catch (error) {
246
+ return unknown(error.code === 'ENOENT' ? ABSENT : UNREADABLE);
247
+ }
248
+ return summarise(parse(data));
249
+ }
250
+ /**
251
+ * The one line, without its newline. Never empty, never multi-line.
252
+ *
253
+ * An empty line would make the host's bar flicker between one row and none, which is worse
254
+ * than any wording; that is why the unknown state has a rendering at all.
255
+ */
256
+ export function render(reading) {
257
+ if (reading.state === 'unknown')
258
+ return `${UNKNOWN}${SEP}${reading.reason}`;
259
+ const events = `${reading.events} event${reading.events === 1 ? '' : 's'}`;
260
+ const named = `${reading.tool} ${reading.outcome}`;
261
+ if (reading.state === 'degraded') {
262
+ const problems = `${reading.problems} problem${reading.problems === 1 ? '' : 's'}`;
263
+ return `${DEGRADED}${SEP}${events}${SEP}${problems}${SEP}${named}`;
264
+ }
265
+ return `${ACTIVE}${SEP}${events}${SEP}${named}`;
266
+ }
267
+ /**
268
+ * The total entry point: a string for every input, including inputs that are wrong.
269
+ *
270
+ * The bare `catch` is not laziness, it is the contract. This runs on somebody's redraw path
271
+ * with no operator watching; the failure it must not have is a traceback where a status bar
272
+ * should be.
273
+ */
274
+ export function statusLine(env, store, start) {
275
+ try {
276
+ return render(probe(env, store, start));
277
+ }
278
+ catch {
279
+ return `${UNKNOWN}${SEP}${SURPRISE}`;
280
+ }
281
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "bantamkit-mcp",
3
+ "version": "0.26.0",
4
+ "description": "bantamkit MCP server — pure Node, runnable with npx",
5
+ "type": "module",
6
+ "bin": {
7
+ "bantamkit-mcp": "dist/cli.js",
8
+ "bantamkit-memory": "dist/memory/cli.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "assets"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "sync-assets": "node scripts/sync-assets.mjs",
28
+ "prepack": "node scripts/sync-assets.mjs",
29
+ "pretest": "npm run build",
30
+ "test": "node scripts/run-tests.mjs"
31
+ },
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/Ink01101011/bantamkit.git",
36
+ "directory": "runtime-ts"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.10.2",
40
+ "typescript": "^5.7.2"
41
+ },
42
+ "dependencies": {
43
+ "@modelcontextprotocol/sdk": "1.30.0"
44
+ }
45
+ }