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.
- package/LICENSE +21 -0
- package/README.md +398 -0
- package/assets/contracts/default.yaml +90 -0
- package/assets/evals/devteam/manifest.yaml +351 -0
- package/assets/evals/devteam/repo/HISTORY.md +18 -0
- package/assets/evals/devteam/repo/README.md +12 -0
- package/assets/evals/devteam/repo/docs/architecture.md +17 -0
- package/assets/evals/devteam/repo/docs/runbook.md +10 -0
- package/assets/evals/devteam/repo/issues/142-settlement-timeout.md +23 -0
- package/assets/evals/devteam/repo/patches/0009-retry-budget.patch +38 -0
- package/assets/evals/devteam/repo/src/ledger/__init__.py +3 -0
- package/assets/evals/devteam/repo/src/ledger/config.py +35 -0
- package/assets/evals/devteam/repo/src/ledger/errors.py +13 -0
- package/assets/evals/devteam/repo/src/ledger/posting.py +12 -0
- package/assets/evals/devteam/repo/src/ledger/registry.py +7 -0
- package/assets/evals/devteam/repo/src/ledger/report.py +9 -0
- package/assets/evals/devteam/repo/src/ledger/retry.py +17 -0
- package/assets/evals/devteam/repo/src/ledger/settle.py +16 -0
- package/assets/evals/devteam/repo/src/ledger/validate.py +14 -0
- package/assets/evals/devteam/repo/tests/test_posting.py +13 -0
- package/assets/evals/devteam/repo/tests/test_settle.py +9 -0
- package/assets/evals/devteam/tasks/dt-error-contract.yaml +186 -0
- package/assets/evals/devteam/tasks/dt-handler-map.yaml +183 -0
- package/assets/evals/devteam/tasks/dt-patch-before-after.yaml +182 -0
- package/assets/evals/devteam/tasks/dt-retry-attempts.yaml +181 -0
- package/assets/evals/devteam/tasks/dt-settlement-config.yaml +185 -0
- package/assets/evals/devteam/tasks/dt-symbol-home.yaml +181 -0
- package/assets/evals/devteam/tasks/dt-trace-blame.yaml +182 -0
- package/assets/evals/devteam/tasks/dt-unread-key.yaml +180 -0
- package/assets/evals/document/tasks/doc-large-in-137.yaml +38 -0
- package/assets/evals/document/tasks/doc-large-in-359.yaml +44 -0
- package/assets/evals/document/tasks/doc-large-in-372.yaml +38 -0
- package/assets/evals/document/tasks/doc-large-out-11764.yaml +37 -0
- package/assets/evals/document/tasks/doc-large-out-4137.yaml +37 -0
- package/assets/evals/document/tasks/doc-large-out-8022.yaml +37 -0
- package/assets/evals/document/tasks/doc-small-137.yaml +37 -0
- package/assets/evals/document/tasks/doc-small-261.yaml +37 -0
- package/assets/evals/document/tasks/doc-small-388.yaml +37 -0
- package/assets/evals/fixtures/.gitkeep +0 -0
- package/assets/evals/fixtures/catalog.json +6 -0
- package/assets/evals/perturbations/task-completion.yaml +576 -0
- package/assets/evals/tasks/.gitkeep +0 -0
- package/assets/evals/tasks/extract-contact.yaml +14 -0
- package/assets/evals/tasks/extract-invoice.yaml +14 -0
- package/assets/evals/tasks/extract-order.yaml +15 -0
- package/assets/evals/tasks/extract-schedule.yaml +14 -0
- package/assets/evals/tasks/extract-versions.yaml +17 -0
- package/assets/evals/tasks/nav-prod-port.yaml +84 -0
- package/assets/evals/tasks/nav-release-bundle.yaml +87 -0
- package/assets/evals/tasks/recall-audit-retention.yaml +17 -0
- package/assets/evals/tasks/recall-cache-ttl.yaml +13 -0
- package/assets/evals/tasks/recall-db-port.yaml +17 -0
- package/assets/evals/tasks/recall-deploy.yaml +13 -0
- package/assets/evals/tasks/recall-env-endpoint.yaml +18 -0
- package/assets/evals/tasks/recall-oncall-rotation.yaml +21 -0
- package/assets/evals/tasks/recall-oncall.yaml +13 -0
- package/assets/evals/tasks/recall-org-quota.yaml +18 -0
- package/assets/evals/tasks/recall-owner.yaml +13 -0
- package/assets/evals/tasks/shop-basket-total.yaml +10 -0
- package/assets/evals/tasks/shop-cheapest.yaml +9 -0
- package/assets/evals/tasks/shop-compare.yaml +9 -0
- package/assets/evals/tasks/shop-gadget-value.yaml +9 -0
- package/assets/evals/tasks/shop-stock-total.yaml +9 -0
- package/assets/evals/tasks/shop-total.yaml +9 -0
- package/assets/profiles/default.yaml +31 -0
- package/assets/profiles/patient.yaml +31 -0
- package/assets/rubrics/.gitkeep +0 -0
- package/assets/rubrics/code-quality.yaml +20 -0
- package/assets/rubrics/grounded-completion.yaml +37 -0
- package/assets/rubrics/task-completion.yaml +28 -0
- package/assets/schemas/shiftwork-checkpoint.json +188 -0
- package/assets/skills/.gitkeep +0 -0
- package/assets/skills/file-graph.md +7 -0
- package/assets/skills/memory.md +35 -0
- package/assets/tools/.gitkeep +0 -0
- package/assets/tools/bantamkit_read.json +48 -0
- package/assets/tools/bantamkit_status.json +25 -0
- package/assets/tools/build_identity.json +17 -0
- package/assets/tools/document_list.json +12 -0
- package/assets/tools/document_read.json +31 -0
- package/assets/tools/file_graph.json +12 -0
- package/assets/tools/memory_compact.json +31 -0
- package/assets/tools/memory_recall.json +38 -0
- package/assets/tools/memory_save.json +61 -0
- package/assets/tools/shiftwork_clock_in.json +25 -0
- package/assets/tools/shiftwork_clock_out.json +60 -0
- package/assets/tools/shiftwork_status.json +25 -0
- package/assets/tools/skill_audit.json +70 -0
- package/assets/tools/validate_json.json +31 -0
- package/dist/assets.d.ts +48 -0
- package/dist/assets.js +109 -0
- package/dist/charsets.d.ts +17 -0
- package/dist/charsets.js +580 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +280 -0
- package/dist/contract.d.ts +145 -0
- package/dist/contract.js +608 -0
- package/dist/docread.d.ts +357 -0
- package/dist/docread.js +3315 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +14 -0
- package/dist/eventlog.d.ts +137 -0
- package/dist/eventlog.js +294 -0
- package/dist/htmlentities.d.ts +2 -0
- package/dist/htmlentities.js +16 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +13 -0
- package/dist/mcp/identity.d.ts +25 -0
- package/dist/mcp/identity.js +275 -0
- package/dist/mcp/pyargs.d.ts +80 -0
- package/dist/mcp/pyargs.js +362 -0
- package/dist/mcp/sdkjson.d.ts +56 -0
- package/dist/mcp/sdkjson.js +91 -0
- package/dist/mcp/server.d.ts +45 -0
- package/dist/mcp/server.js +740 -0
- package/dist/mcp/status.d.ts +210 -0
- package/dist/mcp/status.js +357 -0
- package/dist/mcp/transport.d.ts +68 -0
- package/dist/mcp/transport.js +111 -0
- package/dist/mcpreport.d.ts +214 -0
- package/dist/mcpreport.js +758 -0
- package/dist/memory/cli.d.ts +2 -0
- package/dist/memory/cli.js +396 -0
- package/dist/memory/component.d.ts +256 -0
- package/dist/memory/component.js +497 -0
- package/dist/memory/factfile.d.ts +120 -0
- package/dist/memory/factfile.js +406 -0
- package/dist/memory/layers.d.ts +92 -0
- package/dist/memory/layers.js +410 -0
- package/dist/memory/pyfs.d.ts +521 -0
- package/dist/memory/pyfs.js +1608 -0
- package/dist/memory/pyyaml.d.ts +155 -0
- package/dist/memory/pyyaml.js +868 -0
- package/dist/memory/store.d.ts +478 -0
- package/dist/memory/store.js +1034 -0
- package/dist/pyargparse.d.ts +141 -0
- package/dist/pyargparse.js +813 -0
- package/dist/pyjson.d.ts +110 -0
- package/dist/pyjson.js +524 -0
- package/dist/pyjsonschema.d.ts +50 -0
- package/dist/pyjsonschema.js +951 -0
- package/dist/shiftwork.d.ts +60 -0
- package/dist/shiftwork.js +324 -0
- package/dist/skillaudit.d.ts +172 -0
- package/dist/skillaudit.js +914 -0
- package/dist/statusline.d.ts +106 -0
- package/dist/statusline.js +281 -0
- package/package.json +45 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export interface Options {
|
|
3
|
+
k: number;
|
|
4
|
+
indexBudget: number;
|
|
5
|
+
store: string | null;
|
|
6
|
+
start: string | null;
|
|
7
|
+
assetsRoot: boolean;
|
|
8
|
+
mcpReport: boolean;
|
|
9
|
+
statusline: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** `_parse_args`, arm for arm, including the mutually exclusive group and `-h`. */
|
|
12
|
+
export declare function parseArgs(argv: readonly string[]): Options;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `bantamkit-mcp` entry point: the stdio MCP server, and six flags.
|
|
4
|
+
*
|
|
5
|
+
* THE COMMAND LINE IS DATA, NOT PRINTED TEXT. `PARSER` below is the whole description of
|
|
6
|
+
* this CLI, and `pyargparse.ts` renders it — the help table, the usage line at whatever
|
|
7
|
+
* width the terminal is, and every `bantamkit-mcp: error: …` block — with CPython
|
|
8
|
+
* `argparse`'s own algorithm. There WAS a hand-written `USAGE` constant here; it printed to
|
|
9
|
+
* the wrong stream, it had already gone stale (it never grew `[--assets-root]`), and a
|
|
10
|
+
* second hardcoded copy of a generated string is exactly the defect the `cli` conformance
|
|
11
|
+
* suite was built to catch. Add a flag to `PARSER` and the help cannot fall behind it.
|
|
12
|
+
*
|
|
13
|
+
* N1 shipped this file as a refusal — exit 69 rather than a stub that pretended to be a
|
|
14
|
+
* server — because stdout is the JSON-RPC channel and anything printed there reaches a host
|
|
15
|
+
* as a malformed frame instead of as "not wired yet". THE REFUSAL IS RETIRED HERE, and it has
|
|
16
|
+
* to be: the production invocation passes NO arguments at all (`.mcp.json` and the user-scope
|
|
17
|
+
* config both call the launcher bare), so the bare form is the one that must serve. What the
|
|
18
|
+
* refusal was protecting is kept and is now checked directly — `test/server.test.mjs` asserts
|
|
19
|
+
* that every line a real session put on stdout parses as a JSON-RPC frame, that stdout never
|
|
20
|
+
* ends mid-line, and that a clean session writes nothing to stderr either.
|
|
21
|
+
*
|
|
22
|
+
* PRODUCTION RUNS LAYERED. No `--store` means `Memory.layered`, which prepends `[project] `
|
|
23
|
+
* to a recall line; a build validated only against `--store` would ship a recall string the
|
|
24
|
+
* real deployment never produces.
|
|
25
|
+
*
|
|
26
|
+
* `--mcp-report` is the second flag that prints and returns before a transport is opened. Its
|
|
27
|
+
* contract is `docs/mcpreport.md` and its implementation is `src/mcpreport.ts`; what belongs
|
|
28
|
+
* HERE is only that it is registered after `--index-budget` and before the store group, which
|
|
29
|
+
* is what keeps the wrapped usage line byte-identical to the reference's.
|
|
30
|
+
*
|
|
31
|
+
* `--assets-root` stays. It is the arm-2 (packaged) resolution running inside a real install,
|
|
32
|
+
* so `npx`/`npm i -g` can be checked end to end without speaking the protocol. It is also the
|
|
33
|
+
* only path in this file that writes to stdout without being a frame, and it exits before a
|
|
34
|
+
* transport is ever started.
|
|
35
|
+
*
|
|
36
|
+
* THERE IS NO `--which` HERE, AND THE REASON IS NOT THE ONE THIS COMMENT USED TO GIVE.
|
|
37
|
+
* It said the flag "has no consumer anywhere in the repository or its history" and that
|
|
38
|
+
* therefore "no Node `--which` is budgeted here". Both halves are false as of `3dbedd3` on
|
|
39
|
+
* this branch: `tools/bantamkit-mcp-node` ships `--which`, and
|
|
40
|
+
* `runtime-ts/test/launcher.test.mjs` exercises it in two nodes — one over a checkout that
|
|
41
|
+
* has never been built, one over a worktree, where the flag is the only place `deps_root`
|
|
42
|
+
* diverging from `checkout` is observable at all. It is budgeted, it exists, and it is
|
|
43
|
+
* tested. (The narrow fact that comment was built on does survive: no `mcpreach` program has
|
|
44
|
+
* ever been added on any ref — `git log --all --diff-filter=A -- '*mcpreach*'` is empty,
|
|
45
|
+
* measured again 2026-08-24. A missing consumer was never the same claim as a missing flag.)
|
|
46
|
+
*
|
|
47
|
+
* WHAT IS ACTUALLY TRUE OF THIS FILE. `--which` reports where a CHECKOUT resolved its halves
|
|
48
|
+
* — the source tree, the dependency root, the entry point, the SDK — and a published `npx`
|
|
49
|
+
* package does not run from a checkout. There is no second half to report and no ambiguity
|
|
50
|
+
* about which one answered, so the flag has nothing to say at package level. The question it
|
|
51
|
+
* reaches for is answered on the wire instead, by `build_identity`. `runtime-ts/README.md`
|
|
52
|
+
* and `docs/install.md` carry the same correction in their own words.
|
|
53
|
+
*/
|
|
54
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
55
|
+
import { assetsRoot } from './assets.js';
|
|
56
|
+
import { BantamError } from './errors.js';
|
|
57
|
+
import { buildReport, resolveEventLogPath } from './mcpreport.js';
|
|
58
|
+
import { Memory } from './memory/component.js';
|
|
59
|
+
import { DEFAULT_INDEX_BUDGET } from './memory/store.js';
|
|
60
|
+
import { buildServer } from './mcp/server.js';
|
|
61
|
+
import { RawStdioTransport } from './mcp/transport.js';
|
|
62
|
+
import { ArgvError, formatHelp, formatUsageBlock, HelpRequested, helpWidth, parseArgs as parseWithSpec, pyIntStrict, } from './pyargparse.js';
|
|
63
|
+
import { statusLine } from './statusline.js';
|
|
64
|
+
/** `SystemExit("...")`: the message on stderr, exit 1. */
|
|
65
|
+
class Refusal extends Error {
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `_parse_args`'s parser, as DATA — the one description of this command line in the runtime.
|
|
69
|
+
*
|
|
70
|
+
* THERE IS NO SECOND COPY OF THE USAGE LINE. There used to be: a `USAGE` constant here,
|
|
71
|
+
* written by hand, printed to the wrong stream, and already stale (it never grew
|
|
72
|
+
* `[--assets-root]`). Every string a user sees — the usage line at whatever width the
|
|
73
|
+
* terminal is, the option table, and the `bantamkit-mcp: error: …` block — is generated from
|
|
74
|
+
* this object by `pyargparse.ts`, so a flag added below cannot go missing from the help.
|
|
75
|
+
*
|
|
76
|
+
* POSITION IS WIRE-VISIBLE, exactly as it is in the reference: argparse prints optionals in
|
|
77
|
+
* the order they were added, so this array — not any format string — decides where
|
|
78
|
+
* `[--assets-root]` sits in the generated usage.
|
|
79
|
+
*/
|
|
80
|
+
const PARSER = {
|
|
81
|
+
prog: 'bantamkit-mcp',
|
|
82
|
+
description: 'bantamkit MCP server (stdio): per-person memory + JSON validation.',
|
|
83
|
+
actions: [
|
|
84
|
+
{
|
|
85
|
+
optionStrings: ['-h', '--help'],
|
|
86
|
+
dest: 'help',
|
|
87
|
+
kind: 'help',
|
|
88
|
+
help: 'show this help message and exit',
|
|
89
|
+
defaultValue: false,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
optionStrings: ['--assets-root'],
|
|
93
|
+
dest: 'assets_root',
|
|
94
|
+
kind: 'storeTrue',
|
|
95
|
+
help: 'print the resolved asset pack root and its file count, then exit',
|
|
96
|
+
defaultValue: false,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
optionStrings: ['--k'],
|
|
100
|
+
dest: 'k',
|
|
101
|
+
kind: 'store',
|
|
102
|
+
help: 'default recall budget (default: 3)',
|
|
103
|
+
convert: pyIntStrict,
|
|
104
|
+
typeName: 'int',
|
|
105
|
+
defaultValue: 3,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
optionStrings: ['--index-budget'],
|
|
109
|
+
dest: 'index_budget',
|
|
110
|
+
metavar: 'BYTES',
|
|
111
|
+
kind: 'store',
|
|
112
|
+
help: `memory index byte budget (default: ${DEFAULT_INDEX_BUDGET})`,
|
|
113
|
+
convert: pyIntStrict,
|
|
114
|
+
typeName: 'int',
|
|
115
|
+
defaultValue: DEFAULT_INDEX_BUDGET,
|
|
116
|
+
},
|
|
117
|
+
// WHY THIS IS A FLAG ON THIS PROCESS AND NOT A NEW ENTRY POINT, and why it sits HERE.
|
|
118
|
+
// The lifecycle argument against printing from this process -- stdout is the JSON-RPC
|
|
119
|
+
// channel -- is true of a RUNNING server and not of a flag that prints and returns before
|
|
120
|
+
// a transport exists; `--assets-root` above is the precedent. It is decisive rather than
|
|
121
|
+
// merely convenient on this side: `package.json` declares exactly one bin, so anything
|
|
122
|
+
// hung off a second entry point is unreachable in the pure-npx install that is the
|
|
123
|
+
// shipped product.
|
|
124
|
+
//
|
|
125
|
+
// POSITION IS WIRE-VISIBLE AND IT IS MEASURED. `--assets-root` sits first because it
|
|
126
|
+
// needs NOTHING; this one honours `--store`/`--start` to find the event log, so it is
|
|
127
|
+
// registered after the flags it consumes and before the store group. At the 80-column
|
|
128
|
+
// fallback argparse breaks the usage after `[--index-budget BYTES]`, and that first line
|
|
129
|
+
// is pinned in `test_mcpserver.py` and as a THROWING precondition in
|
|
130
|
+
// `tools/conformance/suites/cli.mjs`. Registering here leaves it byte-identical;
|
|
131
|
+
// registering earlier would move it and turn a differential suite into a re-baselining
|
|
132
|
+
// one. Do not reorder this array to taste.
|
|
133
|
+
{
|
|
134
|
+
optionStrings: ['--mcp-report'],
|
|
135
|
+
dest: 'mcp_report',
|
|
136
|
+
kind: 'storeTrue',
|
|
137
|
+
help: "print an analysis of the host MCP log joined with bantamkit's event log, then exit",
|
|
138
|
+
defaultValue: false,
|
|
139
|
+
},
|
|
140
|
+
// THE SAME THREE ARGUMENTS AS `--mcp-report`, and the same place for the same reason:
|
|
141
|
+
// it prints and returns before a transport exists; the single bin this package declares
|
|
142
|
+
// makes a flag the ONLY reachable surface in the shipped npx install, which is what a
|
|
143
|
+
// Claude Code `statusLine` registration has to name; and it honours `--store`/`--start`
|
|
144
|
+
// to find the event log, so it belongs after the flags it consumes. Registering it here
|
|
145
|
+
// leaves the FIRST line of the 80-column usage -- the pinned one -- byte-identical.
|
|
146
|
+
{
|
|
147
|
+
optionStrings: ['--statusline'],
|
|
148
|
+
dest: 'statusline',
|
|
149
|
+
kind: 'storeTrue',
|
|
150
|
+
help: 'print one status line for a host status bar, then exit',
|
|
151
|
+
defaultValue: false,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
optionStrings: ['--store'],
|
|
155
|
+
dest: 'store',
|
|
156
|
+
kind: 'store',
|
|
157
|
+
help: 'single memory store path (disables layering)',
|
|
158
|
+
defaultValue: null,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
optionStrings: ['--start'],
|
|
162
|
+
dest: 'start',
|
|
163
|
+
kind: 'store',
|
|
164
|
+
help: 'directory to start project-store discovery from (default: cwd)',
|
|
165
|
+
defaultValue: null,
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
groups: [[6, 7]],
|
|
169
|
+
};
|
|
170
|
+
/** `_parse_args`, arm for arm, including the mutually exclusive group and `-h`. */
|
|
171
|
+
export function parseArgs(argv) {
|
|
172
|
+
const values = parseWithSpec(PARSER, argv);
|
|
173
|
+
return {
|
|
174
|
+
k: values['k'],
|
|
175
|
+
indexBudget: values['index_budget'],
|
|
176
|
+
store: values['store'],
|
|
177
|
+
start: values['start'],
|
|
178
|
+
assetsRoot: values['assets_root'],
|
|
179
|
+
mcpReport: values['mcp_report'],
|
|
180
|
+
statusline: values['statusline'],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** `_build_memory`, including the two refusals argparse cannot express. */
|
|
184
|
+
function buildMemory(options) {
|
|
185
|
+
if (options.k < 1)
|
|
186
|
+
throw new Refusal('--k must be >= 1');
|
|
187
|
+
if (options.indexBudget < 1)
|
|
188
|
+
throw new Refusal('--index-budget must be >= 1');
|
|
189
|
+
const shared = { k: options.k, indexBudget: options.indexBudget };
|
|
190
|
+
if (options.store !== null) {
|
|
191
|
+
if (options.store === '')
|
|
192
|
+
throw new Refusal('--store requires a non-empty path');
|
|
193
|
+
return new Memory(options.store, shared);
|
|
194
|
+
}
|
|
195
|
+
return Memory.layered(options.start, shared);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The version this build declares — the same bytes npm packs the tarball from.
|
|
199
|
+
*
|
|
200
|
+
* `RB-P45`'s defect has no Node analogue to fall back from: there is no installed dist-info
|
|
201
|
+
* that can go stale, because `package.json` IS the install record and it ships in the package.
|
|
202
|
+
*/
|
|
203
|
+
function version() {
|
|
204
|
+
return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
205
|
+
}
|
|
206
|
+
async function main(argv) {
|
|
207
|
+
const options = parseArgs(argv);
|
|
208
|
+
if (options.assetsRoot) {
|
|
209
|
+
const root = assetsRoot();
|
|
210
|
+
let files = 0;
|
|
211
|
+
for (const entry of readdirSync(root, { withFileTypes: true, recursive: true })) {
|
|
212
|
+
if (entry.isFile())
|
|
213
|
+
files += 1;
|
|
214
|
+
}
|
|
215
|
+
process.stdout.write(`${root}\n${files} files\n`);
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
if (options.mcpReport) {
|
|
219
|
+
// Same discipline as `--assets-root`, and the same place in `main`: BEFORE
|
|
220
|
+
// `buildMemory`, which touches the filesystem, and before the transport exists at all.
|
|
221
|
+
// NOTHING HERE IS WRITTEN — `mcpreport` opens the host's log read-only, creates no
|
|
222
|
+
// directory under its root, and `resolveEventLogPath` DESIGNATES a store path without
|
|
223
|
+
// bringing a store into existence. The report is an observation, not a session.
|
|
224
|
+
//
|
|
225
|
+
// `process.stdout.write` of a UTF-8 string is LF on every platform, which is what the
|
|
226
|
+
// reference goes through `sys.stdout.buffer` to get: `print` would emit CRLF on Windows
|
|
227
|
+
// where this emits LF, and the conformance suite compares these bytes.
|
|
228
|
+
process.stdout.write(buildReport(process.env, resolveEventLogPath(process.env, options.store, options.start)));
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
if (options.statusline) {
|
|
232
|
+
// Same discipline and the same place as `--mcp-report`: before `buildMemory`, before a
|
|
233
|
+
// transport exists, and nothing is written anywhere. `statusLine` is TOTAL -- there is
|
|
234
|
+
// no failure arm to print here, which is the whole property this surface exists to
|
|
235
|
+
// hold. `process.stdout.write` of a UTF-8 string is LF on every platform, which is
|
|
236
|
+
// what the reference goes through `sys.stdout.buffer` to get.
|
|
237
|
+
process.stdout.write(`${statusLine(process.env, options.store, options.start)}\n`);
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
const memory = buildMemory(options);
|
|
241
|
+
const wire = new RawStdioTransport();
|
|
242
|
+
const server = buildServer(memory, wire, version());
|
|
243
|
+
await server.connect(wire);
|
|
244
|
+
// `asyncio.run(server.run_stdio_async())` returns when the client closes stdin. Here the
|
|
245
|
+
// process stays alive on the stdin listener until the transport closes, which is what an
|
|
246
|
+
// EOF on stdin means: the host has gone.
|
|
247
|
+
await new Promise((resolve) => {
|
|
248
|
+
wire.onclose = () => resolve();
|
|
249
|
+
process.stdin.on('end', () => void wire.close());
|
|
250
|
+
});
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (error instanceof HelpRequested) {
|
|
258
|
+
// `-h` is an ACTION, not a flag read after parsing: argparse prints and exits 0 the
|
|
259
|
+
// moment it is taken, which is why `-h --nope` is help-and-0 and not an argv error. It
|
|
260
|
+
// goes to STDOUT — the one place in this file besides `--assets-root` that writes there
|
|
261
|
+
// without being a JSON-RPC frame, and it exits before a transport is ever started.
|
|
262
|
+
process.stdout.write(formatHelp(PARSER, helpWidth()));
|
|
263
|
+
process.exitCode = 0;
|
|
264
|
+
}
|
|
265
|
+
else if (error instanceof ArgvError) {
|
|
266
|
+
process.stderr.write(`${formatUsageBlock(PARSER, helpWidth())}bantamkit-mcp: error: ${error.message}\n`);
|
|
267
|
+
process.exitCode = 2;
|
|
268
|
+
}
|
|
269
|
+
else if (error instanceof Refusal) {
|
|
270
|
+
process.stderr.write(`${error.message}\n`);
|
|
271
|
+
process.exitCode = 1;
|
|
272
|
+
}
|
|
273
|
+
else if (error instanceof BantamError) {
|
|
274
|
+
process.stderr.write(`bantamkit-mcp: ${error.message}\n`);
|
|
275
|
+
process.exitCode = 1;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { BantamError } from './errors.js';
|
|
2
|
+
import { type PyValue } from './pyjson.js';
|
|
3
|
+
/**
|
|
4
|
+
* `contract.REQUIRED_KEYS`, in Python's order — the order the error message lists them.
|
|
5
|
+
*
|
|
6
|
+
* `bantamkit_read_page_next` and `bantamkit_read_unknown_part` are deliberately NOT here:
|
|
7
|
+
* the reference's tuple does not carry them (R1 added the sentences to the asset and not to
|
|
8
|
+
* the tuple), and `missing key(s): ...` lists this tuple verbatim, so a Node list that was
|
|
9
|
+
* two keys longer would refuse a thin contract with a sentence Python does not print. The
|
|
10
|
+
* two renderers below fetch their key at call time and raise by name if it is absent, which
|
|
11
|
+
* is the `KeyError` the reference would raise at the same moment.
|
|
12
|
+
*/
|
|
13
|
+
export declare const REQUIRED_KEYS: readonly ["schema_instruction", "schema_retry", "critique_feedback", "parse_error", "validation_error", "json_answer_retry", "loop_note", "loop_warn", "evidence_line", "evidence_no_observation", "evidence_empty", "document_manifest_empty", "document_manifest_part", "document_manifest_header_row", "document_manifest_first_row", "document_manifest_last_row", "document_manifest_package_media", "document_manifest_omitted_media", "document_manifest_omitted_blank", "document_manifest_omitted_format", "document_manifest_omitted_unread_page", "document_manifest_unread_no_operator", "document_manifest_unread_unmapped", "document_manifest_unread_whitespace", "document_manifest_unread_no_character", "document_manifest_omitted_unmapped", "document_manifest_omitted_other", "document_page_header", "document_page_next", "document_page_end", "document_page_truncated", "document_unknown", "document_offset_past_end", "document_error", "document_paste_preamble", "document_paste_part", "document_paste_complete", "document_paste_truncated", "document_paste_none", "tool_failed", "tool_arguments", "tool_arguments_none", "tool_argument_types", "tool_argument_type"];
|
|
14
|
+
export declare class ContractParseError extends BantamError {
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `yaml.safe_load` reduced to the document `assets/contracts/default.yaml` actually is.
|
|
18
|
+
*
|
|
19
|
+
* HOW default.yaml IS READ, AND WHY THAT IS SAFE WITH NO YAML ENGINE IN THE PACKAGE.
|
|
20
|
+
* N4's `parseConfigDocument` reads a DIFFERENT document — a hand-written `config.yaml` whose
|
|
21
|
+
* values are paths and lists — and this one has different needs, so it gets its own reader
|
|
22
|
+
* rather than a widened one. Measured shape of the shipped file (86 lines, 9,194 bytes):
|
|
23
|
+
* 40 whole-line comments, 1 blank line, 1 `key: <plain scalar>` (`name: default`), and 44
|
|
24
|
+
* `key: "<double-quoted scalar>"` on ONE line each. No block scalars, no wraps, no nesting,
|
|
25
|
+
* no sequences, no anchors, no tags, no multi-document stream. Every value is a string; the
|
|
26
|
+
* only escapes present are `\n` (3) and `\"` (16).
|
|
27
|
+
*
|
|
28
|
+
* So this reads exactly that: comments, blanks, an optional leading `---`, and root-level
|
|
29
|
+
* entries whose value is either a double-quoted scalar closing on its own line or a plain
|
|
30
|
+
* scalar. The double-quoted escape table is YAML 1.1's in full, so a contract that gains a
|
|
31
|
+
* `\t` or a `é` tomorrow is already read; the plain scalar goes through
|
|
32
|
+
* `resolveImplicitTag`, the SAME resolver the fact-file emitter uses, so the two halves of
|
|
33
|
+
* this package cannot disagree about what `null` or `2026-08-23` means.
|
|
34
|
+
*
|
|
35
|
+
* SAFE, in the sense the word carries for YAML, is structural: no tag handling, no anchor or
|
|
36
|
+
* alias expansion, no merge key, no constructor dispatch. `yaml.safe_load` earns the name by
|
|
37
|
+
* refusing `!!python/object`; this refuses every `!`, `&` and `*` by not implementing them.
|
|
38
|
+
* One left-to-right pass, no backtracking, so a hostile file costs time linear in its length.
|
|
39
|
+
*
|
|
40
|
+
* ANYTHING OUTSIDE THAT LANGUAGE RAISES. The failure mode of guessing is a contract key
|
|
41
|
+
* whose value is subtly wrong, which reaches the model as a malformed sentence nobody
|
|
42
|
+
* notices; the failure mode of raising is `AssetNotFound`-shaped and loud. The gate for this
|
|
43
|
+
* claim is not the reader's own opinion: `tools/conformance/suites/validate.mjs` compares
|
|
44
|
+
* this parse of the SHIPPED asset against `yaml.safe_load`'s, key for key and byte for byte,
|
|
45
|
+
* and `test/contract.test.mjs` refuses the constructs above by name.
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseContractDocument(text: string): Map<string, string>;
|
|
48
|
+
/**
|
|
49
|
+
* `load_contract`. Reads the asset EVERY call, as Python does — the prep probe measured that
|
|
50
|
+
* `contracts/default.yaml` is touched only on a validation failure, so a cache would buy
|
|
51
|
+
* nothing and would make a hot-edited contract silently stale.
|
|
52
|
+
*/
|
|
53
|
+
export declare function loadContract(name?: string): Record<string, string>;
|
|
54
|
+
export declare function schemaRetryFeedback(error: string): string;
|
|
55
|
+
export declare function parseErrorMessage(detail: unknown): string;
|
|
56
|
+
export declare function validationErrorMessage(where: string, detail: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* The primitive shape of one omission, as `docread.Omission.asDict()` hands it over. The
|
|
59
|
+
* reference reads `dict` keys with `[]` and `.get`; the optional ones are optional here too.
|
|
60
|
+
*/
|
|
61
|
+
export interface OmissionRecord {
|
|
62
|
+
readonly subject: string;
|
|
63
|
+
readonly count: number;
|
|
64
|
+
readonly size?: number;
|
|
65
|
+
readonly where?: readonly string[];
|
|
66
|
+
readonly what?: string;
|
|
67
|
+
readonly facts?: Readonly<Record<string, number>>;
|
|
68
|
+
}
|
|
69
|
+
/** One part of a manifest, as `evalrun._document_tools` and `bantamkit_read` build it. */
|
|
70
|
+
export interface ManifestPart {
|
|
71
|
+
readonly document: string;
|
|
72
|
+
readonly kind: string;
|
|
73
|
+
readonly index: number;
|
|
74
|
+
readonly part: string;
|
|
75
|
+
readonly row_count: number;
|
|
76
|
+
readonly rows: readonly string[];
|
|
77
|
+
readonly omissions?: readonly OmissionRecord[];
|
|
78
|
+
}
|
|
79
|
+
/** A document's own omissions, which the manifest prints before that document's parts. */
|
|
80
|
+
export interface ManifestDocument {
|
|
81
|
+
readonly document: string;
|
|
82
|
+
readonly omissions?: readonly OmissionRecord[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `document_manifest`: the answer to "what exists", as one observation — per part, the row
|
|
86
|
+
* count, the numbering, the header, the first and the last data row, and every omission the
|
|
87
|
+
* rendering made. A document's own omissions print before its first part; one whose
|
|
88
|
+
* omissions matched no part is still said, after every part, because `pop` makes that
|
|
89
|
+
* reachable on the reference and dropping the line would put the silence back one level up.
|
|
90
|
+
*/
|
|
91
|
+
export declare function documentManifest(parts: readonly ManifestPart[], documents?: readonly ManifestDocument[] | null): string;
|
|
92
|
+
/**
|
|
93
|
+
* `document_page`: one page, with its own coordinates and its continuation, both in band.
|
|
94
|
+
*
|
|
95
|
+
* `nextKey` names the contract sentence for the continuation, because that sentence names
|
|
96
|
+
* the TOOL to call again: `document_read` for the eval pair, `bantamkit_read` for the MCP
|
|
97
|
+
* reader. Every other line of a page is the same bytes on both.
|
|
98
|
+
*/
|
|
99
|
+
export declare function documentPage(document: string, part: string, offset: number, rows: readonly string[], rowCount: number, nextOffset: number | null, truncatedBytes?: number, nextKey?: string): string;
|
|
100
|
+
export declare function documentOffsetPastEnd(part: string, offset: number, rowCount: number): string;
|
|
101
|
+
/**
|
|
102
|
+
* `bantamkit_read_unknown_part`: a part the file does not have — a fact about the FILE
|
|
103
|
+
* ("in {path}; it has: ...") and not about "this task", which is why it is not
|
|
104
|
+
* `document_unknown`'s sentence.
|
|
105
|
+
*/
|
|
106
|
+
export declare function bantamkitReadUnknownPart(part: string, path: string, available: readonly string[]): string;
|
|
107
|
+
/**
|
|
108
|
+
* `document_error`: a reader failure, kept in the reader's own words, with the `error:`
|
|
109
|
+
* prefix every other tool observation in the harness uses. `str(detail)` on the reference;
|
|
110
|
+
* an `Error`'s `message` is that string here (`DocumentReadError` and `PyOSError` both put
|
|
111
|
+
* the whole sentence there).
|
|
112
|
+
*/
|
|
113
|
+
export declare function documentError(detail: unknown): string;
|
|
114
|
+
/**
|
|
115
|
+
* `tool_failed`: a handler raised, and `detail` is the handler's OWN words.
|
|
116
|
+
*
|
|
117
|
+
* The argument-shaped failures are settled before the handler is entered (`validateArguments`
|
|
118
|
+
* here, pydantic on the reference), so what reaches this is an exception from INSIDE one —
|
|
119
|
+
* `skill_audit`'s three refusals are the only ones today. `str(detail)` on the reference; an
|
|
120
|
+
* `Error`'s `message` is that string here, which is what `SkillAuditError` carries.
|
|
121
|
+
*/
|
|
122
|
+
export declare function toolFailed(tool: string, detail: unknown): string;
|
|
123
|
+
/**
|
|
124
|
+
* `extract_json`: a fenced block if there is one, otherwise the first `{`/`[` onward.
|
|
125
|
+
*
|
|
126
|
+
* Both arms are here because both reach the model. The fence is `re.search` with `DOTALL`
|
|
127
|
+
* and a non-greedy body, so it takes the FIRST fence and prose on either side is discarded;
|
|
128
|
+
* with no fence, `find` takes the earliest brace or bracket — including one inside a string
|
|
129
|
+
* of prose, which is faithful and occasionally wrong in exactly Python's way. `raw_decode`
|
|
130
|
+
* then stops at the end of that one value, which is why `{"a": 1} — anything else?` parses.
|
|
131
|
+
*/
|
|
132
|
+
export declare function extractJson(text: string): PyValue;
|
|
133
|
+
/**
|
|
134
|
+
* `schema_error`: a pointed validation error for `output`, or `null` if it satisfies
|
|
135
|
+
* `schema`.
|
|
136
|
+
*
|
|
137
|
+
* `where` is `"/".join(e.absolute_path)` with `"root"` standing in for the empty path, and
|
|
138
|
+
* `detail` is `ValidationError.message` VERBATIM. The error is the one `best_match` chose,
|
|
139
|
+
* not the first one found — see `pyjsonschema.bestMatch`.
|
|
140
|
+
*
|
|
141
|
+
* The schema may be a `PyValue` (exact — use `pyjson.parseJson` when the raw text is still
|
|
142
|
+
* in hand) or a plain JS value, which is lifted with `pyjson.fromJs` and its one documented
|
|
143
|
+
* loss.
|
|
144
|
+
*/
|
|
145
|
+
export declare function schemaError(output: string, schema: PyValue | unknown): string | null;
|