bantamkit-mcp 0.34.2 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -9
- package/assets/tools/shiftwork_plan.json +25 -0
- package/assets/tools/work_plan.json +49 -0
- package/dist/eventlog.js +8 -9
- package/dist/hostinstall.js +22 -0
- package/dist/memory/store.d.ts +29 -0
- package/dist/memory/store.js +45 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ store, JSON Schema validation and shift-work tools. Install it from **npm** with
|
|
|
5
5
|
command, connect it to **Claude Code**, **Claude Desktop**, **Cursor** or **GitHub Copilot in VS
|
|
6
6
|
Code**, and every later launch starts **offline**. No Python, `pip`, `uv`, `pipx` or venv.
|
|
7
7
|
|
|
8
|
-
It serves the same
|
|
8
|
+
It serves the same fourteen tools, `bantamkit_status` prompt and two resource templates as the
|
|
9
9
|
Python server on **PyPI** (`pip install "bantamkit[mcp]"`), and reads and writes the same memory
|
|
10
10
|
store. The two are compared frame by frame: 8,130 conformance cases on 2026-09-15, with every
|
|
11
11
|
intentional difference written down as a ruling.
|
|
@@ -30,7 +30,7 @@ intentional difference written down as a ruling.
|
|
|
30
30
|
| [The operator CLI: `bantamkit-memory`](#the-operator-cli-bantamkit-memory) | The second bin: status, lint, compact, archive, restore |
|
|
31
31
|
| [Memory stores are layered by default](#memory-stores-are-layered-by-default) | Why not to add `--store` by reflex |
|
|
32
32
|
| [The asset pack](#the-asset-pack) | What ships in `assets/` and how it is found |
|
|
33
|
-
| [
|
|
33
|
+
| [Where the Python and Node servers differ](#where-the-python-and-node-servers-differ) | One store; concurrent writes, one hand-edit to avoid |
|
|
34
34
|
| [Development](#development) | Build, test, conformance |
|
|
35
35
|
| [Measurements and history](#measurements-and-history) | Where the measured numbers behind this page live |
|
|
36
36
|
|
|
@@ -50,9 +50,9 @@ npx -y bantamkit-mcp@latest --install claude # or claude-desktop, copilot, cur
|
|
|
50
50
|
From 0.34.0 this runs `npm install --prefix ~/.bantamkit/mcp` once (Windows:
|
|
51
51
|
`%USERPROFILE%\.bantamkit\mcp`, not measured) and records
|
|
52
52
|
`<the node that ran it> ~/.bantamkit/mcp/node_modules/bantamkit-mcp/dist/cli.js`, both absolute.
|
|
53
|
-
No later launch needs npx, the registry or your PATH. Measured from the published 0.34.0
|
|
54
|
-
|
|
55
|
-
cut.
|
|
53
|
+
No later launch needs npx, the registry or your PATH. Measured from the published 0.34.0
|
|
54
|
+
(served-tools: dated — the surface was twelve then): install exit 0 in 8.14 s; the recorded
|
|
55
|
+
command then served 12 tools in 0.09 s with the network cut.
|
|
56
56
|
|
|
57
57
|
- **`@latest` matters:** without it npx may reuse an older cached resolve.
|
|
58
58
|
- **It skips npm when the kept install already reports this version or a newer one.** It never
|
|
@@ -235,10 +235,15 @@ startup. In Claude Code: `/mcp` → reconnect. In Claude Desktop: a full restart
|
|
|
235
235
|
**Verify with `bantamkit_status`, not the install log.** It reports the version and the
|
|
236
236
|
`build_id` of *the code that is answering you*:
|
|
237
237
|
|
|
238
|
+
*(A capture from 0.30.0. The version and build-digest bytes are left as a dated transcript
|
|
239
|
+
rather than hand-edited to a number nobody ran; **line 3 is the exception and tracks the
|
|
240
|
+
surface**, for the reason `docs/status.md` gives — the tool count is a live claim this repo
|
|
241
|
+
gates against a running server, and a marker would only hide it from that gate.)*
|
|
242
|
+
|
|
238
243
|
```
|
|
239
244
|
bantamkit Active 🟢
|
|
240
245
|
version 0.30.0, build sha256:4441619d…
|
|
241
|
-
serving
|
|
246
|
+
serving 14 tools, 1 prompt, 2 resource templates
|
|
242
247
|
```
|
|
243
248
|
|
|
244
249
|
A version that moved and a `build_id` that did not means you are reading a config, not a process.
|
|
@@ -261,7 +266,7 @@ dependency in a temp directory that will be deleted, and `npm update` cannot hel
|
|
|
261
266
|
| Two machines with one config run different builds | [Silent version float](#silent-version-float): compare `build_id` |
|
|
262
267
|
| `bantamkit_status` still shows the old version after an update | The host has not reconnected; restart the server |
|
|
263
268
|
| Recall finds nothing, or the wrong store | Set `BANTAMKIT_MEMORY_DIR`; the reply names the store it searched |
|
|
264
|
-
| `malformed fact file …` | A hand-edited fact has a bare non-string field; see [
|
|
269
|
+
| `malformed fact file …` | A hand-edited fact has a bare non-string field; see [Where the Python and Node servers differ](#where-the-python-and-node-servers-differ) |
|
|
265
270
|
| `AssetNotFound: no assets directory found; set BANTAMKIT_ASSETS` | See [The asset pack](#the-asset-pack) |
|
|
266
271
|
|
|
267
272
|
## Configuration
|
|
@@ -381,9 +386,12 @@ the pack ships whole — 91 files / 238,751 bytes on 2026-09-15 — or `assets_d
|
|
|
381
386
|
3. `<repo>/assets/` — two levels above `dist/`, for a dev checkout with nothing vendored.
|
|
382
387
|
4. Otherwise `AssetNotFound: no assets directory found; set BANTAMKIT_ASSETS`.
|
|
383
388
|
|
|
384
|
-
##
|
|
389
|
+
## Where the Python and Node servers differ
|
|
385
390
|
|
|
386
|
-
Both servers read and write the same
|
|
391
|
+
Both servers read and write the same on-disk store; byte-compatibility is the product. Every
|
|
392
|
+
feature lands in both in one change, and a conformance case compares their answers. Differences
|
|
393
|
+
are deliberate and each is ruled in the
|
|
394
|
+
[divergence table](https://github.com/Ink01101011/bantamkit/blob/main/docs/porting.md#where-the-two-runtimes-deliberately-differ):
|
|
387
395
|
|
|
388
396
|
- **Concurrent writes are safe for the memory store and lossy for the checkpoint, in both
|
|
389
397
|
runtimes.** `index.md` is derived from `facts/`, so the last writer re-enumerates everything
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shiftwork_plan",
|
|
3
|
+
"description": "Shift-work plan: read-only batch view of a checkpoint — which units its `depends_on` graph permits to run in parallel. Answers `{\"result\": \"plan\", \"batches\", \"ready\", \"sequence\", \"width\", \"cursor\"}`: `ready` is `batches[0]`, the units dispatchable right now, and `width` is the largest batch length, the widest fan-out. Units with status `done` or `dropped` are satisfied — dropped from the graph, and edges pointing at them treated as already resolved; `todo`, `in_progress` and `blocked` stay in it. Every unit has priority 0, because the checkpoint schema has no priority field, so order inside a batch is `plan.units` order. Same refusal sentences as `work_plan` for a duplicate id, an unknown dependency or a cycle, and the same refusals `shiftwork_status` gives for a checkpoint it cannot read. It reports what the dependency graph permits; it does not move the cursor, which is echoed unchanged so the single-pointer contract and the batch view can be read side by side. An orchestrator stays responsible for what it actually dispatches: `depends_on` encodes logical order, not file contention. Never mutates.",
|
|
4
|
+
"surfaces": [
|
|
5
|
+
"mcp"
|
|
6
|
+
],
|
|
7
|
+
"parameters": {
|
|
8
|
+
"properties": {
|
|
9
|
+
"checkpoint": {
|
|
10
|
+
"title": "Checkpoint",
|
|
11
|
+
"type": "string"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": [
|
|
15
|
+
"checkpoint"
|
|
16
|
+
],
|
|
17
|
+
"type": "object",
|
|
18
|
+
"title": "shiftwork_planArguments"
|
|
19
|
+
},
|
|
20
|
+
"output_schema": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": true,
|
|
23
|
+
"title": "shiftwork_planDictOutput"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "work_plan",
|
|
3
|
+
"description": "Work plan: turn a dependency graph into the batches that may run in parallel. Takes `nodes`, each `{id, depends_on, priority}` — `priority` defaults to 0 — and answers `{\"result\": \"plan\", \"batches\", \"sequence\", \"width\"}`: batch k holds every node whose dependencies all appear in batches below k, `sequence` is those batches flattened in order, and `width` is the largest batch length — the widest fan-out, which is the number an orchestrator needs to decide whether it can afford the batch. Order inside a batch is priority descending, then the order the nodes were given; both halves are contract, because determinism inside a batch is what two implementations are held to. Empty `nodes` is an ANSWER, not a refusal: no batches, no sequence, width 0. Three refusals, checked in this order and one sentence each: `duplicate node id <id>`, `node <id> depends on <dep>, which no node declares`, and `the graph has a cycle: <a> -> <b> -> <a>`. It reports what the graph permits and decides nothing about what may actually be dispatched together — `depends_on` encodes logical order, not file contention, so a batch this tool calls parallel may still hold work that writes one file. Pure computation over the nodes it was handed: it opens no file and takes no path. Never mutates.",
|
|
4
|
+
"surfaces": [
|
|
5
|
+
"mcp"
|
|
6
|
+
],
|
|
7
|
+
"parameters": {
|
|
8
|
+
"properties": {
|
|
9
|
+
"nodes": {
|
|
10
|
+
"title": "Nodes",
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"id": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The node's identifier, unique across `nodes`"
|
|
18
|
+
},
|
|
19
|
+
"depends_on": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
"description": "Ids this node waits for; every one of them must be declared by some node"
|
|
25
|
+
},
|
|
26
|
+
"priority": {
|
|
27
|
+
"type": "integer",
|
|
28
|
+
"description": "Tie-break inside a batch, descending. Optional; 0 when absent"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": [
|
|
32
|
+
"id",
|
|
33
|
+
"depends_on"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"required": [
|
|
39
|
+
"nodes"
|
|
40
|
+
],
|
|
41
|
+
"type": "object",
|
|
42
|
+
"title": "work_planArguments"
|
|
43
|
+
},
|
|
44
|
+
"output_schema": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"additionalProperties": true,
|
|
47
|
+
"title": "work_planDictOutput"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/eventlog.js
CHANGED
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
* notice — the failure mode a diagnostic can least afford.
|
|
56
56
|
*/
|
|
57
57
|
import { appendFileSync, mkdirSync, renameSync, statSync } from 'node:fs';
|
|
58
|
-
import { cmpCodepoint, osErrorClassName, pyExists, PyOSError, pyJoin, pyName, pyParent
|
|
59
|
-
import { ensureBantamkitGitignore } from './memory/store.js';
|
|
58
|
+
import { cmpCodepoint, osErrorClassName, pyExists, PyOSError, pyJoin, pyName, pyParent } from './memory/pyfs.js';
|
|
59
|
+
import { bantamkitDirFor, ensureBantamkitGitignore } from './memory/store.js';
|
|
60
60
|
/**
|
|
61
61
|
* Environment switch. Unset or `off`/`0`/`false`/`no`/empty -> disabled. `on`/`1`/`true`/
|
|
62
62
|
* `yes` -> the default file inside the memory store. Anything else is taken as the literal
|
|
@@ -254,13 +254,12 @@ export class EventLog {
|
|
|
254
254
|
// USER RULING #2 (J51-8b): the ignore file is written only when THIS call is what
|
|
255
255
|
// creates `.bantamkit`, so existence has to be checked BEFORE the mkdir below -- after
|
|
256
256
|
// it, the directory unconditionally exists and the question is unanswerable.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
257
|
+
//
|
|
258
|
+
// J54-3: the walk moved into `bantamkitDirFor` beside the writer it feeds, so the two
|
|
259
|
+
// creators in this runtime ask the same question in the same words rather than keeping a
|
|
260
|
+
// copy each. The answer here is unchanged -- a log file is never itself named
|
|
261
|
+
// `.bantamkit`, so self-or-ancestors and ancestors give the same directory.
|
|
262
|
+
const bantamkitDir = bantamkitDirFor(path);
|
|
264
263
|
const bantamkitDirExistedBefore = bantamkitDir !== null && pyExists(bantamkitDir);
|
|
265
264
|
// `pyParent` and not `node:path`'s `dirname`: this module is reached from a Windows
|
|
266
265
|
// host through the same code, and the reference's `Path(...).parent` is the spelling
|
package/dist/hostinstall.js
CHANGED
|
@@ -37,6 +37,7 @@ import { homedir } from 'node:os';
|
|
|
37
37
|
import { dirname, join } from 'node:path';
|
|
38
38
|
import { fileURLToPath } from 'node:url';
|
|
39
39
|
import { currentInstall, Undetermined } from './mcp/identity.js';
|
|
40
|
+
import { bantamkitDirFor, ensureBantamkitGitignore } from './memory/store.js';
|
|
40
41
|
import { compareVersions, keptCli, keptManifest, keptPrefix, PACKAGE, runInstaller, shlexJoin } from './npminstall.js';
|
|
41
42
|
import { dumpJson, fromJs } from './pyjson.js';
|
|
42
43
|
export { keptCli, keptPrefix };
|
|
@@ -319,7 +320,28 @@ export function thisCommand(options = {}) {
|
|
|
319
320
|
}
|
|
320
321
|
const command = ['npm', 'install', '--prefix', prefix, `${PACKAGE}@${version}`];
|
|
321
322
|
const rendered = shlexJoin(command);
|
|
323
|
+
// THE THIRD `.bantamkit` CREATOR (J54-3), and the one that creates the most of it: npm makes
|
|
324
|
+
// a missing `--prefix` itself, so this line — not `MemoryStore`, not `EventLog` — is what
|
|
325
|
+
// brings `<homedir>/.bantamkit` into existence on a machine that has never saved a memory.
|
|
326
|
+
// Measured on the machine this was found on: 28 MB of install tree under a `~/.bantamkit`
|
|
327
|
+
// with no `.gitignore`, every byte of it untracked in a `$HOME` that is a git repository.
|
|
328
|
+
//
|
|
329
|
+
// Checked BEFORE npm runs, per user ruling #2, for the reason both other creators check it
|
|
330
|
+
// before their own mkdir: afterwards the directory exists either way and the question
|
|
331
|
+
// "did THIS call create it" is unanswerable. A `~/.bantamkit` that was already there —
|
|
332
|
+
// holding an older kept install, a memory store, or an ignore file the operator deleted on
|
|
333
|
+
// purpose — is left exactly as it is.
|
|
334
|
+
const bantamkitDir = bantamkitDirFor(prefix);
|
|
335
|
+
const bantamkitDirExistedBefore = bantamkitDir !== null && existsSync(bantamkitDir);
|
|
322
336
|
const [code, output] = (options.installer ?? runInstaller)(command);
|
|
337
|
+
// BEFORE THE REFUSALS, AND THAT ORDER IS MEASURED, not tidy. A `npm install --prefix` that
|
|
338
|
+
// FAILS still leaves the prefix behind: measured 2026-09-18 with `npm_config_offline=true`
|
|
339
|
+
// against an empty cache — exit 1, and `<home>/.bantamkit/mcp` there afterwards. A refusal
|
|
340
|
+
// that returned first would leave exactly the directory this closes over: made by bantamkit's
|
|
341
|
+
// own command, holding nothing anybody asked for, and visible to git. The helper never
|
|
342
|
+
// creates `bantamkitDir` itself, so an installer that made nothing writes nothing here.
|
|
343
|
+
if (bantamkitDir !== null)
|
|
344
|
+
ensureBantamkitGitignore(bantamkitDir, !bantamkitDirExistedBefore);
|
|
323
345
|
const refused = `could not make the kept install at ${prefix}, so no host configuration was changed: ${rendered} exited ${code}`;
|
|
324
346
|
if (code !== 0)
|
|
325
347
|
throw new InstallError(`${refused}: ${lastLine(output)}`);
|
package/dist/memory/store.d.ts
CHANGED
|
@@ -33,6 +33,14 @@ export declare const BANTAMKIT_GITIGNORE_TEXT: string;
|
|
|
33
33
|
* a `.bantamkit` that git can see (or ignore one it should not), which is the whole bug this
|
|
34
34
|
* closes.
|
|
35
35
|
*
|
|
36
|
+
* THE CREATORS, ENUMERATED (J54-3), because "anything else" is how one got missed: in THIS
|
|
37
|
+
* runtime there are exactly three — `MemoryStore.ensureDirs`, `EventLog`'s append-time
|
|
38
|
+
* `mkdirSync`, and `hostinstall.thisCommand`, where `--install` from an `npx` cache has npm
|
|
39
|
+
* create `<homedir>/.bantamkit/mcp` (npm makes a missing `--prefix` itself). The first two ask
|
|
40
|
+
* `bantamkitDirFor` which directory the decision is about rather than assuming the root's
|
|
41
|
+
* parent is it; the third is the one the reference does not have at all, because the offline
|
|
42
|
+
* install is Node-only by user ruling. `docs/porting.md` carries the row.
|
|
43
|
+
*
|
|
36
44
|
* NEVER REWRITES. A `.gitignore` that already exists -- whatever its bytes -- is left
|
|
37
45
|
* exactly as it is, kept as a second guard even when `created` is `true`: an operator who
|
|
38
46
|
* deleted it to commit the directory keeps that decision, and this never diffs its own
|
|
@@ -48,6 +56,27 @@ export declare const BANTAMKIT_GITIGNORE_TEXT: string;
|
|
|
48
56
|
* bring `.bantamkit` into existence early.
|
|
49
57
|
*/
|
|
50
58
|
export declare function ensureBantamkitGitignore(directory: string, created: boolean): void;
|
|
59
|
+
/**
|
|
60
|
+
* The `.bantamkit` directory a recursive mkdir of `path` would create or fill, or `null` when
|
|
61
|
+
* there is none: `path` itself if it is named `.bantamkit`, otherwise its nearest ancestor
|
|
62
|
+
* that is. `runtime-py/src/bantamkit/memory/store.py`'s `bantamkit_dir_for` is the reference.
|
|
63
|
+
*
|
|
64
|
+
* WHY A WALK AND NOT `pyParent(path)` (J54-3). Every creator has to hand
|
|
65
|
+
* `ensureBantamkitGitignore` the directory the decision is ABOUT, and the parent is the right
|
|
66
|
+
* answer for exactly one shape of path, `<x>/.bantamkit/memory`. A store rooted AT the
|
|
67
|
+
* `.bantamkit` directory (`--store ~/.bantamkit`) or nested deeper under it creates a
|
|
68
|
+
* `.bantamkit` whose gitignore decision was taken about the wrong directory — so no decision
|
|
69
|
+
* was taken at all, and git could see the whole tree. MEASURED, not hypothetical: `~/.bantamkit`
|
|
70
|
+
* on the machine this was found on holds an empty `facts/` and `archive/` beside `memory/`,
|
|
71
|
+
* left by a store once rooted at it, and no `.gitignore`.
|
|
72
|
+
*
|
|
73
|
+
* NEAREST AND NOT OUTERMOST, which is the choice `EventLog`'s append already made and this
|
|
74
|
+
* function now carries for both creators.
|
|
75
|
+
*
|
|
76
|
+
* READ-ONLY. It looks at names, never at the filesystem, so it is equally valid before or after
|
|
77
|
+
* the mkdir — but `created` is answered by checking THIS path's existence BEFORE.
|
|
78
|
+
*/
|
|
79
|
+
export declare function bantamkitDirFor(path: string): string | null;
|
|
51
80
|
/** `sorted(VALID_TYPES)` is what the error message interpolates, so the order is load-bearing. */
|
|
52
81
|
export declare const VALID_TYPES: readonly ["feedback", "project", "reference", "user"];
|
|
53
82
|
/**
|
package/dist/memory/store.js
CHANGED
|
@@ -42,7 +42,7 @@ import { basename } from 'node:path';
|
|
|
42
42
|
import { BantamError } from '../errors.js';
|
|
43
43
|
import { formatFact, parseFrontmatter, pySplit, pyStrip, todayLocal } from './factfile.js';
|
|
44
44
|
import { PyScalar } from './pyyaml.js';
|
|
45
|
-
import { asPyOSError, cmpCodepoint, matchesMd, pyExists, pyJoin, pyLexists, pyMkdirParents, pyMtimeDate, pyName, pyParent, pyReadText, pyReplace, pyScandirNames, pySuffix, pyUnlink, pyWithSuffix, pyWriteText, PyOSError, sortedPathNames, } from './pyfs.js';
|
|
45
|
+
import { asPyOSError, cmpCodepoint, matchesMd, pyExists, pyJoin, pyLexists, pyMkdirParents, pyMtimeDate, pyName, pyParent, pyParents, pyReadText, pyReplace, pyScandirNames, pySuffix, pyUnlink, pyWithSuffix, pyWriteText, PyOSError, sortedPathNames, } from './pyfs.js';
|
|
46
46
|
/**
|
|
47
47
|
* The exact bytes written into a `.bantamkit/.gitignore` the first time such a directory is
|
|
48
48
|
* brought into existence (user ruling 2026-09-15, `.shiftwork/notes-job51/P0-probes.md`).
|
|
@@ -76,6 +76,14 @@ export const BANTAMKIT_GITIGNORE_TEXT = '# Created by bantamkit: this directory
|
|
|
76
76
|
* a `.bantamkit` that git can see (or ignore one it should not), which is the whole bug this
|
|
77
77
|
* closes.
|
|
78
78
|
*
|
|
79
|
+
* THE CREATORS, ENUMERATED (J54-3), because "anything else" is how one got missed: in THIS
|
|
80
|
+
* runtime there are exactly three — `MemoryStore.ensureDirs`, `EventLog`'s append-time
|
|
81
|
+
* `mkdirSync`, and `hostinstall.thisCommand`, where `--install` from an `npx` cache has npm
|
|
82
|
+
* create `<homedir>/.bantamkit/mcp` (npm makes a missing `--prefix` itself). The first two ask
|
|
83
|
+
* `bantamkitDirFor` which directory the decision is about rather than assuming the root's
|
|
84
|
+
* parent is it; the third is the one the reference does not have at all, because the offline
|
|
85
|
+
* install is Node-only by user ruling. `docs/porting.md` carries the row.
|
|
86
|
+
*
|
|
79
87
|
* NEVER REWRITES. A `.gitignore` that already exists -- whatever its bytes -- is left
|
|
80
88
|
* exactly as it is, kept as a second guard even when `created` is `true`: an operator who
|
|
81
89
|
* deleted it to commit the directory keeps that decision, and this never diffs its own
|
|
@@ -103,6 +111,33 @@ export function ensureBantamkitGitignore(directory, created) {
|
|
|
103
111
|
/* best-effort: a missing ignore file is not worth a lost fact */
|
|
104
112
|
}
|
|
105
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* The `.bantamkit` directory a recursive mkdir of `path` would create or fill, or `null` when
|
|
116
|
+
* there is none: `path` itself if it is named `.bantamkit`, otherwise its nearest ancestor
|
|
117
|
+
* that is. `runtime-py/src/bantamkit/memory/store.py`'s `bantamkit_dir_for` is the reference.
|
|
118
|
+
*
|
|
119
|
+
* WHY A WALK AND NOT `pyParent(path)` (J54-3). Every creator has to hand
|
|
120
|
+
* `ensureBantamkitGitignore` the directory the decision is ABOUT, and the parent is the right
|
|
121
|
+
* answer for exactly one shape of path, `<x>/.bantamkit/memory`. A store rooted AT the
|
|
122
|
+
* `.bantamkit` directory (`--store ~/.bantamkit`) or nested deeper under it creates a
|
|
123
|
+
* `.bantamkit` whose gitignore decision was taken about the wrong directory — so no decision
|
|
124
|
+
* was taken at all, and git could see the whole tree. MEASURED, not hypothetical: `~/.bantamkit`
|
|
125
|
+
* on the machine this was found on holds an empty `facts/` and `archive/` beside `memory/`,
|
|
126
|
+
* left by a store once rooted at it, and no `.gitignore`.
|
|
127
|
+
*
|
|
128
|
+
* NEAREST AND NOT OUTERMOST, which is the choice `EventLog`'s append already made and this
|
|
129
|
+
* function now carries for both creators.
|
|
130
|
+
*
|
|
131
|
+
* READ-ONLY. It looks at names, never at the filesystem, so it is equally valid before or after
|
|
132
|
+
* the mkdir — but `created` is answered by checking THIS path's existence BEFORE.
|
|
133
|
+
*/
|
|
134
|
+
export function bantamkitDirFor(path) {
|
|
135
|
+
for (const candidate of [path, ...pyParents(path)]) {
|
|
136
|
+
if (pyName(candidate) === '.bantamkit')
|
|
137
|
+
return candidate;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
106
141
|
/** `sorted(VALID_TYPES)` is what the error message interpolates, so the order is load-bearing. */
|
|
107
142
|
export const VALID_TYPES = ['feedback', 'project', 'reference', 'user'];
|
|
108
143
|
/**
|
|
@@ -598,8 +633,13 @@ export class MemoryStore {
|
|
|
598
633
|
// Checked BEFORE the mkdir below, per user ruling #2: this is the one moment that can
|
|
599
634
|
// tell whether `.bantamkit` already existed. After the mkdir it unconditionally exists
|
|
600
635
|
// either way, so the answer has to be captured now or not at all.
|
|
601
|
-
|
|
602
|
-
|
|
636
|
+
//
|
|
637
|
+
// J54-3: `bantamkitDirFor`, not `pyParent`. The mkdirs below create every missing
|
|
638
|
+
// component of `this.root` — a root that IS the `.bantamkit` directory, or one nested
|
|
639
|
+
// deeper under it, brought a `.bantamkit` into existence while the decision was being
|
|
640
|
+
// taken about some other directory, which meant no decision at all.
|
|
641
|
+
const bantamkitDir = bantamkitDirFor(this.root);
|
|
642
|
+
const bantamkitDirExistedBefore = bantamkitDir !== null && pyExists(bantamkitDir);
|
|
603
643
|
try {
|
|
604
644
|
pyMkdirParents(pyJoin(this.root, 'facts'));
|
|
605
645
|
pyMkdirParents(pyJoin(this.root, 'archive'));
|
|
@@ -610,7 +650,8 @@ export class MemoryStore {
|
|
|
610
650
|
throw new MemoryValidationError(`memory store could not be created: ${this.root}; the directory is not` +
|
|
611
651
|
' there and this filesystem would not make it, so nothing was written');
|
|
612
652
|
}
|
|
613
|
-
|
|
653
|
+
if (bantamkitDir !== null)
|
|
654
|
+
ensureBantamkitGitignore(bantamkitDir, !bantamkitDirExistedBefore);
|
|
614
655
|
}
|
|
615
656
|
// ---- ops ----
|
|
616
657
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bantamkit-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"description": "Memory MCP server for Claude Code, Cursor, VS Code Copilot and Claude Desktop: per-person memory, JSON Schema validation, shift-work tools. Pure Node; install once with npx, run offline.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|