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
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The operator's half of the lifecycle position, on the Node side: `bantamkit-memory`.
|
|
4
|
+
*
|
|
5
|
+
* `docs/memory.md` states, as a design decision, that `lint`, `archived` and `restore`
|
|
6
|
+
* are **not** agent tools — "lifecycle is an operator decision, not a model decision" —
|
|
7
|
+
* and the eleven-tool surface holds to it; `compact` is the one exception since job42
|
|
8
|
+
* (`memory_compact`, the on-refusal path the model reaches itself). `runtime-py` made that
|
|
9
|
+
* position coherent in August 2026 by giving the operator `python -m bantamkit.memory`.
|
|
10
|
+
* `runtime-ts` did not. MEASURED 2026-08-24 before this file existed: `node dist/cli.js -h`
|
|
11
|
+
* and `python -m bantamkit.mcpserver -h` print byte-identical help, neither offers a
|
|
12
|
+
* subcommand, and the maintenance CLI existed only in Python — so an operator who ran
|
|
13
|
+
* `npx bantamkit-mcp` and nothing else could not compact, could not lint, and could not
|
|
14
|
+
* restore an archived fact. Not a documentation gap: there was no code path.
|
|
15
|
+
*
|
|
16
|
+
* WHY A SECOND `bin` AND NOT A FLAG ON `bantamkit-mcp`
|
|
17
|
+
* ---------------------------------------------------
|
|
18
|
+
* The reference's reason for a separate entry point (`bantamkit-mcp` speaks JSON-RPC over
|
|
19
|
+
* stdout, so a lifecycle report printed there corrupts the wire) does NOT decide it here:
|
|
20
|
+
* `--assets-root`, `--mcp-report` and `--statusline` all print and return before a transport
|
|
21
|
+
* exists, and `src/cli.ts` says so at each of them. The reason a subcommand is refused is
|
|
22
|
+
* different and it is measured: `bantamkit-mcp`'s help is a BYTE-COMPARED artifact against
|
|
23
|
+
* `python -m bantamkit.mcpserver -h` (`tools/conformance/suites/cli.mjs`). Hanging a
|
|
24
|
+
* subparsers action off that parser would add `{memory} ...` to its usage line and a whole
|
|
25
|
+
* `positional arguments:` section to its help, against a reference that has neither. The
|
|
26
|
+
* parity that already holds would have to be broken to reach the surface this file adds.
|
|
27
|
+
*
|
|
28
|
+
* A second `bin` costs nothing on either install route. `npm i -g bantamkit-mcp` puts both
|
|
29
|
+
* commands on PATH; without an install, `npx -p bantamkit-mcp bantamkit-memory status` runs
|
|
30
|
+
* this file straight out of the registry. `package.json` used to declare exactly one bin,
|
|
31
|
+
* and `src/cli.ts` still reasons from that — see the `--statusline` comment there, which is
|
|
32
|
+
* about a Claude Code `statusLine` registration and is unaffected: a host that names one
|
|
33
|
+
* command still names one command.
|
|
34
|
+
*
|
|
35
|
+
* THE ONE STRING THAT CANNOT BE THE SAME
|
|
36
|
+
* --------------------------------------
|
|
37
|
+
* The Python operator types `python -m bantamkit.memory`; the Node operator types
|
|
38
|
+
* `bantamkit-memory`. There is no third spelling that both could use: the reference's `prog`
|
|
39
|
+
* is a Python `-m` invocation and a pure-npm install has no Python in it at all, while
|
|
40
|
+
* `bantamkit-memory` names a console script that CPython does not install. Everything
|
|
41
|
+
* downstream of `prog` moves with it — the usage line, every `…: error:` prefix, and the two
|
|
42
|
+
* remediation sentences that name a command to run (`lint`'s `try:` line and `compact`'s
|
|
43
|
+
* `restore one with:`). Those two are the reason this is a substitution and not a cosmetic
|
|
44
|
+
* difference: printing `python -m bantamkit.memory compact …` to someone holding an npx
|
|
45
|
+
* install would be an instruction they cannot follow.
|
|
46
|
+
*
|
|
47
|
+
* The rule, stated so a conformance case can rule on it and MEASURED at 2026-08-24: the two
|
|
48
|
+
* CLIs produce IDENTICAL BYTES after substituting `python -m bantamkit.memory` ->
|
|
49
|
+
* `bantamkit-memory`, EXCEPT that a usage line long enough to wrap wraps at different
|
|
50
|
+
* points, because the hanging indent argparse computes is `len(prefix) + len(prog) + 1`.
|
|
51
|
+
* Verified both halves: at `COLUMNS=200` all four help forms (`-h`, `status -h`,
|
|
52
|
+
* `restore -h`, `compact -h`) are byte-identical after the substitution and nothing else; at
|
|
53
|
+
* `COLUMNS=80` the ONLY diff in each is the continuation indent (31 columns here against 41
|
|
54
|
+
* there) and, for the top parser, whether the line needed to wrap at all. So the divergence
|
|
55
|
+
* is `prog` and the arithmetic downstream of `prog`, not a second rendering algorithm — a
|
|
56
|
+
* ruling should compare at a width where the line fits, and pin the wrap separately.
|
|
57
|
+
* `PROG` below is the only place the Node spelling appears.
|
|
58
|
+
*
|
|
59
|
+
* Scope is deliberately the writable project layer only, exactly as the reference has it.
|
|
60
|
+
* `Memory.layered()` also mounts read-only grants and the profile store, and
|
|
61
|
+
* `MemoryStore.compact()` touches neither; resolving through `discoverProjectStore` keeps
|
|
62
|
+
* this CLI unable to archive a store it does not own, rather than merely unlikely to.
|
|
63
|
+
*
|
|
64
|
+
* Exit codes: 0 success, 1 an operational failure the operator must act on (over budget,
|
|
65
|
+
* malformed fact, refused restore), 2 argparse usage error.
|
|
66
|
+
*/
|
|
67
|
+
import { BantamError } from '../errors.js';
|
|
68
|
+
import { ArgumentTypeError, ArgvError, formatHelp, formatUsageBlock, HelpRequested, helpWidth, parseArgs, pyIntStrict, } from '../pyargparse.js';
|
|
69
|
+
import { discoverProjectStore } from './layers.js';
|
|
70
|
+
import { pyJoin } from './pyfs.js';
|
|
71
|
+
import { DEFAULT_INDEX_BUDGET, MemoryBudgetExceeded, MemoryStore, MemoryValidationError, pyText, } from './store.js';
|
|
72
|
+
/** The Node operator's spelling of the reference's `python -m bantamkit.memory`. */
|
|
73
|
+
const PROG = 'bantamkit-memory';
|
|
74
|
+
/** `SystemExit("...")`: the message on stderr, exit 1. */
|
|
75
|
+
class Refusal extends Error {
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `_positive`: a budget of 0 or less silently makes every save fail, so refuse it at the edge.
|
|
79
|
+
*
|
|
80
|
+
* The TWO failure sentences are both the reference's and they are different on purpose:
|
|
81
|
+
* `--budget x` never reaches this body (`int()` raises and argparse prints
|
|
82
|
+
* `invalid _positive value: 'x'`), while `--budget 0` gets this `ArgumentTypeError`'s own
|
|
83
|
+
* text. `typeName` below is `_positive` because argparse interpolates the CALLABLE's
|
|
84
|
+
* `__name__`, and the reference's callable is named `_positive`.
|
|
85
|
+
*/
|
|
86
|
+
function positive(text) {
|
|
87
|
+
const value = pyIntStrict(text);
|
|
88
|
+
if (value < 1)
|
|
89
|
+
throw new ArgumentTypeError('must be >= 1');
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `common(sub)`: the store selector and the budget, on every subcommand.
|
|
94
|
+
*
|
|
95
|
+
* ORDER IS WIRE-VISIBLE. argparse prints optionals in the order they were added, so this
|
|
96
|
+
* array — not any format string — decides that `[--store STORE | --start START]` precedes
|
|
97
|
+
* `[--budget BYTES]` in every sub-usage, and that `--reserve` follows both under `compact`.
|
|
98
|
+
* The mutually exclusive group is `[1, 2]` because `-h` is added first by the parser itself.
|
|
99
|
+
*/
|
|
100
|
+
const common = (extra = []) => [
|
|
101
|
+
{
|
|
102
|
+
optionStrings: ['-h', '--help'],
|
|
103
|
+
dest: 'help',
|
|
104
|
+
kind: 'help',
|
|
105
|
+
help: 'show this help message and exit',
|
|
106
|
+
defaultValue: false,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
optionStrings: ['--store'],
|
|
110
|
+
dest: 'store',
|
|
111
|
+
kind: 'store',
|
|
112
|
+
help: 'memory store path (skips project discovery)',
|
|
113
|
+
defaultValue: null,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
optionStrings: ['--start'],
|
|
117
|
+
dest: 'start',
|
|
118
|
+
kind: 'store',
|
|
119
|
+
help: 'directory to start project-store discovery from (default: cwd)',
|
|
120
|
+
defaultValue: null,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
optionStrings: ['--budget'],
|
|
124
|
+
dest: 'budget',
|
|
125
|
+
metavar: 'BYTES',
|
|
126
|
+
kind: 'store',
|
|
127
|
+
help: `index byte budget (default: ${DEFAULT_INDEX_BUDGET})`,
|
|
128
|
+
convert: positive,
|
|
129
|
+
typeName: '_positive',
|
|
130
|
+
defaultValue: DEFAULT_INDEX_BUDGET,
|
|
131
|
+
},
|
|
132
|
+
...extra,
|
|
133
|
+
];
|
|
134
|
+
const sub = (name, actions) => ({
|
|
135
|
+
prog: `${PROG} ${name}`,
|
|
136
|
+
// `add_parser` takes no `description=` in the reference, so the sub-help has no paragraph
|
|
137
|
+
// between its usage line and its first section. An empty string is that absence.
|
|
138
|
+
description: '',
|
|
139
|
+
actions,
|
|
140
|
+
groups: [[1, 2]],
|
|
141
|
+
});
|
|
142
|
+
/** `_parse_args`'s parser, as DATA — the one description of this command line. */
|
|
143
|
+
const PARSER = {
|
|
144
|
+
prog: PROG,
|
|
145
|
+
description: 'Operator lifecycle for a bantamkit memory store: inspect, lint, compact, ' +
|
|
146
|
+
'archive and restore. Not an agent surface.',
|
|
147
|
+
actions: [
|
|
148
|
+
{
|
|
149
|
+
optionStrings: ['-h', '--help'],
|
|
150
|
+
dest: 'help',
|
|
151
|
+
kind: 'help',
|
|
152
|
+
help: 'show this help message and exit',
|
|
153
|
+
defaultValue: false,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
// `add_subparsers(dest="command", required=True)` — no `help=`, which is why the
|
|
157
|
+
// `{status,…}` row prints bare above its five indented children.
|
|
158
|
+
optionStrings: [],
|
|
159
|
+
dest: 'command',
|
|
160
|
+
kind: 'subparsers',
|
|
161
|
+
help: '',
|
|
162
|
+
defaultValue: null,
|
|
163
|
+
subcommands: [
|
|
164
|
+
{ name: 'status', help: 'index size, budget, headroom, archive count', parser: sub('status', common()) },
|
|
165
|
+
{
|
|
166
|
+
name: 'lint',
|
|
167
|
+
help: 'exit 1 if the store is malformed or over budget',
|
|
168
|
+
parser: sub('lint', common()),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'compact',
|
|
172
|
+
help: 'archive the stalest facts',
|
|
173
|
+
parser: sub('compact', common([
|
|
174
|
+
{
|
|
175
|
+
optionStrings: ['--reserve'],
|
|
176
|
+
dest: 'reserve',
|
|
177
|
+
metavar: 'BYTES',
|
|
178
|
+
kind: 'store',
|
|
179
|
+
help: 'headroom to leave below the budget (default: the largest index line kept)',
|
|
180
|
+
convert: positive,
|
|
181
|
+
typeName: '_positive',
|
|
182
|
+
defaultValue: null,
|
|
183
|
+
},
|
|
184
|
+
])),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'archived',
|
|
188
|
+
help: 'list what compaction has moved out',
|
|
189
|
+
parser: sub('archived', common()),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'archive',
|
|
193
|
+
help: 'move one named fact out',
|
|
194
|
+
parser: sub('archive', [
|
|
195
|
+
...common(),
|
|
196
|
+
{
|
|
197
|
+
optionStrings: [],
|
|
198
|
+
dest: 'name',
|
|
199
|
+
kind: 'positional',
|
|
200
|
+
help: 'name of the fact to archive',
|
|
201
|
+
defaultValue: null,
|
|
202
|
+
},
|
|
203
|
+
]),
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'restore',
|
|
207
|
+
help: 'move an archived fact back',
|
|
208
|
+
parser: sub('restore', [
|
|
209
|
+
...common(),
|
|
210
|
+
{
|
|
211
|
+
optionStrings: [],
|
|
212
|
+
dest: 'name',
|
|
213
|
+
kind: 'positional',
|
|
214
|
+
help: 'name of the archived fact',
|
|
215
|
+
defaultValue: null,
|
|
216
|
+
},
|
|
217
|
+
]),
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
groups: [],
|
|
223
|
+
};
|
|
224
|
+
function parse(argv) {
|
|
225
|
+
const values = parseArgs(PARSER, argv);
|
|
226
|
+
return {
|
|
227
|
+
command: values['command'],
|
|
228
|
+
store: values['store'],
|
|
229
|
+
start: values['start'],
|
|
230
|
+
budget: values['budget'],
|
|
231
|
+
reserve: (values['reserve'] ?? null),
|
|
232
|
+
name: values['name'],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** `_open`: the writable project store, and nothing else this CLI could reach. */
|
|
236
|
+
function open(args) {
|
|
237
|
+
if (args.store !== null) {
|
|
238
|
+
if (args.store === '')
|
|
239
|
+
throw new Refusal('--store requires a non-empty path');
|
|
240
|
+
return new MemoryStore(args.store, { indexBudget: args.budget });
|
|
241
|
+
}
|
|
242
|
+
return new MemoryStore(discoverProjectStore(args.start), { indexBudget: args.budget });
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* `len(store.index_text().encode())` — the number the budget is compared against.
|
|
246
|
+
*
|
|
247
|
+
* It is the LF text's length on both runtimes. `runtime-py` writes `index.md` through
|
|
248
|
+
* `write_text` with `newline=None`, so on Windows the file on disk is longer than the number
|
|
249
|
+
* measured here; that disagreement is a runtime-py defect and is not reproduced.
|
|
250
|
+
*/
|
|
251
|
+
const size = (store) => Buffer.byteLength(store.indexText(), 'utf8');
|
|
252
|
+
const out = (text) => {
|
|
253
|
+
process.stdout.write(`${text}\n`);
|
|
254
|
+
};
|
|
255
|
+
const err = (text) => {
|
|
256
|
+
process.stderr.write(`${text}\n`);
|
|
257
|
+
};
|
|
258
|
+
function cmdStatus(store) {
|
|
259
|
+
const bytes = size(store);
|
|
260
|
+
out(`store: ${store.root}`);
|
|
261
|
+
out(`facts: ${store.factCount()}`);
|
|
262
|
+
out(`index: ${bytes} bytes`);
|
|
263
|
+
out(`budget: ${store.indexBudget}`);
|
|
264
|
+
out(`headroom: ${store.indexBudget - bytes}`);
|
|
265
|
+
out(`archived: ${store.archived().length}`);
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
function cmdLint(store) {
|
|
269
|
+
try {
|
|
270
|
+
store.lint();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
if (error instanceof MemoryBudgetExceeded) {
|
|
274
|
+
// Not the store's own message: that one names `compact()`, which is a call in a
|
|
275
|
+
// language and the wrong remedy to hand someone holding a shell.
|
|
276
|
+
const where = `--store ${store.root}`;
|
|
277
|
+
err(`lint: FAIL — index is ${size(store)} bytes, budget is ${store.indexBudget}\n` +
|
|
278
|
+
` try: ${PROG} compact ${where} --budget ${store.indexBudget}`);
|
|
279
|
+
return 1;
|
|
280
|
+
}
|
|
281
|
+
if (error instanceof MemoryValidationError) {
|
|
282
|
+
err(`lint: FAIL — ${error.message}`);
|
|
283
|
+
return 1;
|
|
284
|
+
}
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
out(`lint: ok — ${store.factCount()} facts, ${size(store)}/${store.indexBudget} bytes`);
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
function cmdCompact(store, args) {
|
|
291
|
+
const before = size(store);
|
|
292
|
+
const result = store.compact(args.reserve);
|
|
293
|
+
out(`compacted ${result.archived.length} fact(s)`);
|
|
294
|
+
out(`index: ${before} -> ${result.indexAfter} bytes ` +
|
|
295
|
+
`(budget ${result.budget}, target ${result.target}, ` +
|
|
296
|
+
`reserve ${result.reserve}, headroom ${result.headroom})`);
|
|
297
|
+
if (result.archived.length === 0) {
|
|
298
|
+
out('nothing to archive — the index is already at or below the target');
|
|
299
|
+
return 0;
|
|
300
|
+
}
|
|
301
|
+
// `archive/` is on disk and nothing reads it back on its own, so the names have to land
|
|
302
|
+
// here or the operator never learns what left.
|
|
303
|
+
out(`archived -> ${result.archiveDir}`);
|
|
304
|
+
for (const fact of result.archived) {
|
|
305
|
+
out(` ${pyText(fact.name)} (${pyText(fact.type)}, ${fact.indexBytes} bytes)`);
|
|
306
|
+
}
|
|
307
|
+
out(`restore one with: ${PROG} restore <name> --store ${store.root}`);
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
function cmdArchived(store) {
|
|
311
|
+
const names = store.archived();
|
|
312
|
+
out(`archived facts: ${names.length} (${pyJoin(store.root, 'archive')})`);
|
|
313
|
+
for (const name of names)
|
|
314
|
+
out(` ${name}`);
|
|
315
|
+
return 0;
|
|
316
|
+
}
|
|
317
|
+
function cmdArchive(store, args) {
|
|
318
|
+
try {
|
|
319
|
+
store.archive(args.name);
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
if (error instanceof MemoryValidationError) {
|
|
323
|
+
err(`archive failed: ${error.message}`);
|
|
324
|
+
return 1;
|
|
325
|
+
}
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
out(`archived '${args.name}' — index now ${size(store)}/${store.indexBudget} bytes`);
|
|
329
|
+
return 0;
|
|
330
|
+
}
|
|
331
|
+
function cmdRestore(store, args) {
|
|
332
|
+
try {
|
|
333
|
+
store.restore(args.name);
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
if (error instanceof MemoryBudgetExceeded) {
|
|
337
|
+
err(`restore failed: '${args.name}' would put the index over the budget of ` +
|
|
338
|
+
`${store.indexBudget} bytes. Nothing changed — raise --budget or compact first.`);
|
|
339
|
+
return 1;
|
|
340
|
+
}
|
|
341
|
+
if (error instanceof MemoryValidationError) {
|
|
342
|
+
err(`restore failed: ${error.message}`);
|
|
343
|
+
return 1;
|
|
344
|
+
}
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
out(`restored '${args.name}' — index now ${size(store)}/${store.indexBudget} bytes`);
|
|
348
|
+
return 0;
|
|
349
|
+
}
|
|
350
|
+
const COMMANDS = {
|
|
351
|
+
status: cmdStatus,
|
|
352
|
+
lint: cmdLint,
|
|
353
|
+
compact: cmdCompact,
|
|
354
|
+
archived: cmdArchived,
|
|
355
|
+
archive: cmdArchive,
|
|
356
|
+
restore: cmdRestore,
|
|
357
|
+
};
|
|
358
|
+
export function main(argv) {
|
|
359
|
+
const args = parse(argv);
|
|
360
|
+
return COMMANDS[args.command](open(args), args);
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
process.exitCode = main(process.argv.slice(2));
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
if (error instanceof HelpRequested) {
|
|
367
|
+
// `-h` is an ACTION, not a flag read after parsing: argparse prints and exits 0 the
|
|
368
|
+
// moment it is taken. `error.spec` is WHOSE help — `restore -h` prints the sub-parser's.
|
|
369
|
+
process.stdout.write(formatHelp(error.spec ?? PARSER, helpWidth()));
|
|
370
|
+
process.exitCode = 0;
|
|
371
|
+
}
|
|
372
|
+
else if (error instanceof ArgvError) {
|
|
373
|
+
// And whose USAGE, which is not always this parser's: a bad `--budget` under `status` is
|
|
374
|
+
// `bantamkit-memory status: error: …`, while an unrecognized argument the sub-parser
|
|
375
|
+
// handed back is reported by the top parser. Both measured against the reference.
|
|
376
|
+
const spec = error.spec ?? PARSER;
|
|
377
|
+
process.stderr.write(`${formatUsageBlock(spec, helpWidth())}${spec.prog}: error: ${error.message}\n`);
|
|
378
|
+
process.exitCode = 2;
|
|
379
|
+
}
|
|
380
|
+
else if (error instanceof Refusal) {
|
|
381
|
+
process.stderr.write(`${error.message}\n`);
|
|
382
|
+
process.exitCode = 1;
|
|
383
|
+
}
|
|
384
|
+
else if (error instanceof BantamError) {
|
|
385
|
+
// The store errors the five commands do NOT catch — an unreadable `facts/` under
|
|
386
|
+
// `status`, `compact` or `archived`. The reference lets those escape `main` and CPython
|
|
387
|
+
// prints a TRACEBACK, exiting 1. The exit code matches; the stderr text cannot, because
|
|
388
|
+
// a Python traceback is not a portable artifact. `src/cli.ts` sets the precedent for the
|
|
389
|
+
// shape used here. Carried as a reported divergence, not a silent one.
|
|
390
|
+
process.stderr.write(`${PROG}: ${error.message}\n`);
|
|
391
|
+
process.exitCode = 1;
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { type StoreBinding } from './layers.js';
|
|
2
|
+
import { MemoryStore } from './store.js';
|
|
3
|
+
/**
|
|
4
|
+
* The model's spelling adapted to the store's contract: lowercase, `_`/space -> `-`.
|
|
5
|
+
*
|
|
6
|
+
* The store's pattern does not move; the component bends the argument to it. Measured cause:
|
|
7
|
+
* 5 of the 7b probe runs burned their whole turn budget retrying `memory_save` with the
|
|
8
|
+
* snake_case names the model invents. Non-strings are handed on untouched so that store
|
|
9
|
+
* validation still speaks.
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeName<T>(name: T): T;
|
|
12
|
+
/**
|
|
13
|
+
* The last layer `layered` appends, named once so other code can recognise it.
|
|
14
|
+
*
|
|
15
|
+
* It was spelled inline and nowhere else, which is how it came to be prescribed as a place
|
|
16
|
+
* to start a project store: nothing that wrote the advice could see that the directory it
|
|
17
|
+
* was recommending is also the machine-wide layer.
|
|
18
|
+
*/
|
|
19
|
+
export declare function profileStore(): string;
|
|
20
|
+
/** The name a layer answers under: the PROJECT directory, not the store directory. */
|
|
21
|
+
export declare function layerLabel(root: string): string;
|
|
22
|
+
export interface MemoryOptions {
|
|
23
|
+
k?: number;
|
|
24
|
+
indexBudget?: number;
|
|
25
|
+
/**
|
|
26
|
+
* The clock, frozen. `Memory` in the reference takes no such argument — the store it wraps
|
|
27
|
+
* does, and the conformance harness freezes every layer's `_today` after construction.
|
|
28
|
+
* This is the same seam, reachable without touching a private field; the default is the
|
|
29
|
+
* store's own `date.today()`, so nothing about a real run changes.
|
|
30
|
+
*/
|
|
31
|
+
today?: () => string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* What `save` DECIDED, beside the sentence it says about it.
|
|
35
|
+
*
|
|
36
|
+
* THE POINT OF THIS TYPE IS THAT `status` IS NOT DERIVED FROM `reply`. `Memory.save` has
|
|
37
|
+
* four outcomes — stored, deduped, refused by validation, refused by the budget — and all
|
|
38
|
+
* four leave the process as one string that the MCP host records as "completed
|
|
39
|
+
* successfully in Nms". Anything downstream that wanted to tell them apart had exactly one
|
|
40
|
+
* route: match the reply text. That route is a re-derivation which breaks silently the day
|
|
41
|
+
* the wording improves, and it is why this field exists.
|
|
42
|
+
*
|
|
43
|
+
* `status` is one of `saved`, `duplicate`, `refused-validation`, `refused-budget`, and
|
|
44
|
+
* every one of them is read off a decision the code had already made — `SaveResult`'s own
|
|
45
|
+
* `status`, or which `catch` arm matched. `reply` is the unchanged string `save()` has
|
|
46
|
+
* always returned; nothing reads it.
|
|
47
|
+
*/
|
|
48
|
+
export interface SaveOutcome {
|
|
49
|
+
readonly reply: string;
|
|
50
|
+
readonly status: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* What `compact` DID, beside the sentence it says about it.
|
|
54
|
+
*
|
|
55
|
+
* Same seam as `SaveOutcome` and `RecallOutcome`: `status` is read off a decision the store
|
|
56
|
+
* already made — whether `CompactResult.archived` is empty — and never off the reply.
|
|
57
|
+
* `archived` (**not** deleted, see `MemoryStore.compact`) is the ONLY place the count
|
|
58
|
+
* survives as a number, because the reply spells it out in prose and the host records only
|
|
59
|
+
* that a `memory_compact` "completed successfully".
|
|
60
|
+
*
|
|
61
|
+
* `status` is `archived` or `nothing-archived`. `reply` is the unchanged string `compact()`
|
|
62
|
+
* has always returned; nothing reads it.
|
|
63
|
+
*/
|
|
64
|
+
export interface CompactOutcome {
|
|
65
|
+
readonly reply: string;
|
|
66
|
+
readonly status: string;
|
|
67
|
+
readonly archived: number;
|
|
68
|
+
readonly indexBefore: number;
|
|
69
|
+
readonly indexAfter: number;
|
|
70
|
+
readonly budget: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What `recall` DID, beside the facts it formatted.
|
|
74
|
+
*
|
|
75
|
+
* `recall` walks the layers in order, stops the moment the budget is spent, dedupes by
|
|
76
|
+
* name, and — when nothing matched — picks one of three different verdicts about WHY (see
|
|
77
|
+
* `nothingToReport`). None of that survived into the reply the host times: an empty recall
|
|
78
|
+
* and a three-fact recall are the same log line to it, and the three empties are
|
|
79
|
+
* indistinguishable from each other even to a reader of the reply, because telling them
|
|
80
|
+
* apart means matching prose.
|
|
81
|
+
*
|
|
82
|
+
* `status` is `answered` or one of `empty-no-match` / `empty-unreadable-layer` /
|
|
83
|
+
* `empty-nothing-saved`, keyed on the same branch that chooses the verdict sentence rather
|
|
84
|
+
* than on the sentence. `source` is the KIND of the layer that answered first — `project`,
|
|
85
|
+
* `extra` or `profile` — deliberately not the layer's full label, because `extra:<name>`
|
|
86
|
+
* carries a directory name off the operator's disk and the log this feeds is
|
|
87
|
+
* metadata-only.
|
|
88
|
+
*/
|
|
89
|
+
export interface RecallOutcome {
|
|
90
|
+
readonly reply: string;
|
|
91
|
+
readonly status: string;
|
|
92
|
+
readonly budget: number;
|
|
93
|
+
readonly layers: number;
|
|
94
|
+
readonly reached: number;
|
|
95
|
+
readonly returned: number;
|
|
96
|
+
readonly candidates: number;
|
|
97
|
+
readonly source: string | null;
|
|
98
|
+
readonly unreadable: number;
|
|
99
|
+
}
|
|
100
|
+
export declare class Memory {
|
|
101
|
+
readonly store: MemoryStore;
|
|
102
|
+
readonly k: number;
|
|
103
|
+
private readonly layers;
|
|
104
|
+
private showLayers;
|
|
105
|
+
/**
|
|
106
|
+
* How this store came to be the store, captured once. It cannot be re-derived later: the
|
|
107
|
+
* constructor above has already made the directory, so a store that was only DESIGNATED a
|
|
108
|
+
* moment ago is indistinguishable on disk from one that was found empty. `null` means the
|
|
109
|
+
* caller named the path outright and no resolution happened to report.
|
|
110
|
+
*/
|
|
111
|
+
private readonly binding;
|
|
112
|
+
constructor(store: string, options?: MemoryOptions, binding?: StoreBinding | null);
|
|
113
|
+
/**
|
|
114
|
+
* Project store (resolved) + configured read-only grants + profile store.
|
|
115
|
+
*
|
|
116
|
+
* `resolveProjectStore` rather than `discoverProjectStore`: the path both return is the
|
|
117
|
+
* same path, but only the binding carries WHY it is that path and whether it holds
|
|
118
|
+
* anything — and `recall` cannot recover either fact afterwards, because constructing the
|
|
119
|
+
* store creates the directory.
|
|
120
|
+
*/
|
|
121
|
+
static layered(start?: string | null, options?: MemoryOptions): Memory;
|
|
122
|
+
/** The labels, in order. `_layers` is private in the reference too; this is for the tests. */
|
|
123
|
+
layerLabels(): string[];
|
|
124
|
+
/** The reply the model reads. Unchanged, and still the tool's advertised return. */
|
|
125
|
+
save(type: string, name: string, description: string, body: string, links?: readonly string[] | null): string;
|
|
126
|
+
/**
|
|
127
|
+
* `save`, with the branch it took carried out alongside the sentence it wrote.
|
|
128
|
+
*
|
|
129
|
+
* Same body, same order, same four exits: the only change is that each exit now NAMES
|
|
130
|
+
* itself. That is the whole of the narrow seam the MCP server needs to log an outcome
|
|
131
|
+
* without matching text, and it moves no wording and no return type — `save()` above
|
|
132
|
+
* still answers `string`, and `assets/tools/memory_save.json` still advertises `str`.
|
|
133
|
+
*/
|
|
134
|
+
saveOutcome(type: string, name: string, description: string, body: string, links?: readonly string[] | null): SaveOutcome;
|
|
135
|
+
/**
|
|
136
|
+
* `k` is the model asking for MORE, never for less than the store's default.
|
|
137
|
+
*
|
|
138
|
+
* Measured cause (RB-P1, qwen2.5:14b-instruct): 57 of 60 `memory_recall` calls across 12
|
|
139
|
+
* seeded runs sent `k: 1`, and honouring it truncated recall to the single best-scoring
|
|
140
|
+
* fact. The floor is the operator's configured default, not a constant, so a consumer who
|
|
141
|
+
* really wants top-1 says so once at construction.
|
|
142
|
+
*/
|
|
143
|
+
recall(query: string, k?: number | null): string;
|
|
144
|
+
/**
|
|
145
|
+
* `recall`, carrying the walk it performed as numbers rather than as prose.
|
|
146
|
+
*
|
|
147
|
+
* The reply is byte-for-byte what `recall` has always returned; every field beside it is
|
|
148
|
+
* counted here because it CANNOT be recovered afterwards. How far down the layer list the
|
|
149
|
+
* budget got, how many of the layers reached were readable, how many facts those layers
|
|
150
|
+
* held against how many came back, and which of the three empty verdicts fired — all of
|
|
151
|
+
* it is gone by the time the string exists, and the host's log records only that a
|
|
152
|
+
* `memory_recall` "completed successfully".
|
|
153
|
+
*
|
|
154
|
+
* `candidates` counts `facts/*.md` in the layers actually READ, via `countFacts` — the
|
|
155
|
+
* same filter `MemoryStore.recall` scores over, so the ratio to `returned` is between two
|
|
156
|
+
* counts of one population and not between two different ones. A layer whose directory
|
|
157
|
+
* refuses to list contributes nothing and raises the `unreadable` count instead of being
|
|
158
|
+
* scored as empty; that distinction is the whole subject of `nothingToReport` below and
|
|
159
|
+
* must not be undone here.
|
|
160
|
+
*/
|
|
161
|
+
recallOutcome(query: string, k?: number | null): RecallOutcome;
|
|
162
|
+
/**
|
|
163
|
+
* Archive the stalest facts until a fact as large as the biggest one kept will fit.
|
|
164
|
+
*
|
|
165
|
+
* The reply is the ONLY place the model learns what left the index: `archive/` is a
|
|
166
|
+
* directory the model calling this will never look in — if the return value does not carry
|
|
167
|
+
* what was lost, nothing does. Ported from `Memory.compact` in `component.py`, which is
|
|
168
|
+
* written for a model; the tool's advertised return is this string, unchanged.
|
|
169
|
+
*/
|
|
170
|
+
compact(reserve?: number | null): string;
|
|
171
|
+
/**
|
|
172
|
+
* `compact`, with the decision it took carried beside the sentence it wrote.
|
|
173
|
+
*
|
|
174
|
+
* `this.store` is the writable project layer and nothing else: `layered` appends grants
|
|
175
|
+
* and the profile store to `_layers` only, never to `this.store`, so this cannot reach
|
|
176
|
+
* either (`test/layers.test.mjs` pins a profile fact through a compaction). The reply is
|
|
177
|
+
* byte-for-byte what `compact` has always returned on the reference: Python `f"{int}"`
|
|
178
|
+
* is a plain decimal, and `result.archive_dir` is `str(Path)`, which `archiveDir` already
|
|
179
|
+
* is — `MemoryStore.compact` builds it with `pyJoin`.
|
|
180
|
+
*/
|
|
181
|
+
compactOutcome(reserve?: number | null): CompactOutcome;
|
|
182
|
+
/**
|
|
183
|
+
* `(index bytes, budget)` for the writable project store; bytes may be `null`.
|
|
184
|
+
*
|
|
185
|
+
* Measured exactly the way `MemoryStore.checkIndexBudget` measures — the UTF-8 length of
|
|
186
|
+
* `indexText()` — so the number a log records and the number a refusal was decided
|
|
187
|
+
* against are one number and not two that can drift apart.
|
|
188
|
+
*
|
|
189
|
+
* `null` means the store could not be read, and it is never 0: an unreadable store
|
|
190
|
+
* reporting an empty index is the same slander this module spends `nothingToReport`
|
|
191
|
+
* refusing to commit, and it would read as infinite headroom at exactly the moment there
|
|
192
|
+
* is none. The cost is a full parse of `facts/`, which is why nothing calls this on the
|
|
193
|
+
* tool path unless a log is actually enabled.
|
|
194
|
+
*/
|
|
195
|
+
indexAccounting(): [indexBytes: number | null, budget: number];
|
|
196
|
+
/**
|
|
197
|
+
* An empty recall is at least three different situations; say which one.
|
|
198
|
+
*
|
|
199
|
+
* Answers `[status, text]`. The status is the SAME BRANCH that picks the sentence, handed
|
|
200
|
+
* out rather than re-derived: a caller that needs to tell the three empties apart — the
|
|
201
|
+
* MCP event log does — would otherwise have to match the prose this comment exists to say
|
|
202
|
+
* is allowed to improve.
|
|
203
|
+
*
|
|
204
|
+
* Until job37 all of them returned "no memories matched. Try different words", which tells
|
|
205
|
+
* a person to rephrase a question against a filing cabinet that may not exist.
|
|
206
|
+
*
|
|
207
|
+
* The verdict is keyed on what could have been read RIGHT NOW, counted the way
|
|
208
|
+
* `MemoryStore._facts` reads, not on the construction-time binding — a store that was empty
|
|
209
|
+
* at construction and has been saved to since really can answer. The DIAGNOSIS is keyed on
|
|
210
|
+
* the binding, because "designated" stops being visible on disk the moment `MemoryStore`
|
|
211
|
+
* creates the directory.
|
|
212
|
+
*/
|
|
213
|
+
private nothingToReport;
|
|
214
|
+
/**
|
|
215
|
+
* How many facts a store holds, or `null` when that cannot be established.
|
|
216
|
+
*
|
|
217
|
+
* A store that cannot be listed answers `null` and never 0: "I could not read it" and "it
|
|
218
|
+
* holds nothing" are the two answers this whole unit exists to keep apart.
|
|
219
|
+
*/
|
|
220
|
+
private static factCount;
|
|
221
|
+
/** Roots of the layers that could not be listed at all — never counted as 0. */
|
|
222
|
+
private unreadableLayers;
|
|
223
|
+
/**
|
|
224
|
+
* Facts the layers actually consulted could have matched, counted live.
|
|
225
|
+
*
|
|
226
|
+
* An unreadable layer contributes nothing rather than counting as empty: `recall` already
|
|
227
|
+
* skips it, and it must not become evidence that there was nothing to read.
|
|
228
|
+
*/
|
|
229
|
+
private searchableFacts;
|
|
230
|
+
/**
|
|
231
|
+
* Is this store also the machine-wide profile layer `layered` appends?
|
|
232
|
+
*
|
|
233
|
+
* Resolved on both sides because the walk returns its store path unresolved while
|
|
234
|
+
* `Path.home()` may itself be a symlink; a comparison that missed on a symlink would put
|
|
235
|
+
* the harmful advice back exactly where it does damage.
|
|
236
|
+
*/
|
|
237
|
+
private static isProfileStore;
|
|
238
|
+
/**
|
|
239
|
+
* Did the walk actually leave the directory it started in?
|
|
240
|
+
*
|
|
241
|
+
* `origin === "walk"` says a walk ran, not that it climbed. A walk that terminates at step
|
|
242
|
+
* zero has bound `searchedFrom`'s OWN store, and the clause that reads well for an ancestor
|
|
243
|
+
* binding — "which has no store of its own" — is then simply false about the commonest case
|
|
244
|
+
* there is: a project whose store exists and is empty.
|
|
245
|
+
*/
|
|
246
|
+
private walkAscended;
|
|
247
|
+
/**
|
|
248
|
+
* Why THIS store, when the project layer is the one that cannot answer.
|
|
249
|
+
*
|
|
250
|
+
* Silent while the project store holds facts: that is the case that already works, and it
|
|
251
|
+
* keeps its exact wording. The remedy names `MEMORY_DIR_ENV` from `layers`, never a
|
|
252
|
+
* literal, so the message and the resolver cannot drift onto two different variables.
|
|
253
|
+
*/
|
|
254
|
+
private bindingDiagnosis;
|
|
255
|
+
private format;
|
|
256
|
+
}
|