bantamkit 0.27.0__py3-none-any.whl
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.
- bantamkit/__init__.py +32 -0
- bantamkit/agent.py +458 -0
- bantamkit/assets/contracts/default.yaml +90 -0
- bantamkit/assets/evals/devteam/manifest.yaml +351 -0
- bantamkit/assets/evals/devteam/repo/HISTORY.md +18 -0
- bantamkit/assets/evals/devteam/repo/README.md +12 -0
- bantamkit/assets/evals/devteam/repo/docs/architecture.md +17 -0
- bantamkit/assets/evals/devteam/repo/docs/runbook.md +10 -0
- bantamkit/assets/evals/devteam/repo/issues/142-settlement-timeout.md +23 -0
- bantamkit/assets/evals/devteam/repo/patches/0009-retry-budget.patch +38 -0
- bantamkit/assets/evals/devteam/repo/src/ledger/__init__.py +3 -0
- bantamkit/assets/evals/devteam/repo/src/ledger/config.py +35 -0
- bantamkit/assets/evals/devteam/repo/src/ledger/errors.py +13 -0
- bantamkit/assets/evals/devteam/repo/src/ledger/posting.py +12 -0
- bantamkit/assets/evals/devteam/repo/src/ledger/registry.py +7 -0
- bantamkit/assets/evals/devteam/repo/src/ledger/report.py +9 -0
- bantamkit/assets/evals/devteam/repo/src/ledger/retry.py +17 -0
- bantamkit/assets/evals/devteam/repo/src/ledger/settle.py +16 -0
- bantamkit/assets/evals/devteam/repo/src/ledger/validate.py +14 -0
- bantamkit/assets/evals/devteam/repo/tests/test_posting.py +13 -0
- bantamkit/assets/evals/devteam/repo/tests/test_settle.py +9 -0
- bantamkit/assets/evals/devteam/tasks/dt-error-contract.yaml +186 -0
- bantamkit/assets/evals/devteam/tasks/dt-handler-map.yaml +183 -0
- bantamkit/assets/evals/devteam/tasks/dt-patch-before-after.yaml +182 -0
- bantamkit/assets/evals/devteam/tasks/dt-retry-attempts.yaml +181 -0
- bantamkit/assets/evals/devteam/tasks/dt-settlement-config.yaml +185 -0
- bantamkit/assets/evals/devteam/tasks/dt-symbol-home.yaml +181 -0
- bantamkit/assets/evals/devteam/tasks/dt-trace-blame.yaml +182 -0
- bantamkit/assets/evals/devteam/tasks/dt-unread-key.yaml +180 -0
- bantamkit/assets/evals/document/tasks/doc-large-in-137.yaml +38 -0
- bantamkit/assets/evals/document/tasks/doc-large-in-359.yaml +44 -0
- bantamkit/assets/evals/document/tasks/doc-large-in-372.yaml +38 -0
- bantamkit/assets/evals/document/tasks/doc-large-out-11764.yaml +37 -0
- bantamkit/assets/evals/document/tasks/doc-large-out-4137.yaml +37 -0
- bantamkit/assets/evals/document/tasks/doc-large-out-8022.yaml +37 -0
- bantamkit/assets/evals/document/tasks/doc-small-137.yaml +37 -0
- bantamkit/assets/evals/document/tasks/doc-small-261.yaml +37 -0
- bantamkit/assets/evals/document/tasks/doc-small-388.yaml +37 -0
- bantamkit/assets/evals/fixtures/.gitkeep +0 -0
- bantamkit/assets/evals/fixtures/catalog.json +6 -0
- bantamkit/assets/evals/perturbations/task-completion.yaml +576 -0
- bantamkit/assets/evals/tasks/.gitkeep +0 -0
- bantamkit/assets/evals/tasks/extract-contact.yaml +14 -0
- bantamkit/assets/evals/tasks/extract-invoice.yaml +14 -0
- bantamkit/assets/evals/tasks/extract-order.yaml +15 -0
- bantamkit/assets/evals/tasks/extract-schedule.yaml +14 -0
- bantamkit/assets/evals/tasks/extract-versions.yaml +17 -0
- bantamkit/assets/evals/tasks/nav-prod-port.yaml +84 -0
- bantamkit/assets/evals/tasks/nav-release-bundle.yaml +87 -0
- bantamkit/assets/evals/tasks/recall-audit-retention.yaml +17 -0
- bantamkit/assets/evals/tasks/recall-cache-ttl.yaml +13 -0
- bantamkit/assets/evals/tasks/recall-db-port.yaml +17 -0
- bantamkit/assets/evals/tasks/recall-deploy.yaml +13 -0
- bantamkit/assets/evals/tasks/recall-env-endpoint.yaml +18 -0
- bantamkit/assets/evals/tasks/recall-oncall-rotation.yaml +21 -0
- bantamkit/assets/evals/tasks/recall-oncall.yaml +13 -0
- bantamkit/assets/evals/tasks/recall-org-quota.yaml +18 -0
- bantamkit/assets/evals/tasks/recall-owner.yaml +13 -0
- bantamkit/assets/evals/tasks/shop-basket-total.yaml +10 -0
- bantamkit/assets/evals/tasks/shop-cheapest.yaml +9 -0
- bantamkit/assets/evals/tasks/shop-compare.yaml +9 -0
- bantamkit/assets/evals/tasks/shop-gadget-value.yaml +9 -0
- bantamkit/assets/evals/tasks/shop-stock-total.yaml +9 -0
- bantamkit/assets/evals/tasks/shop-total.yaml +9 -0
- bantamkit/assets/profiles/default.yaml +31 -0
- bantamkit/assets/profiles/patient.yaml +31 -0
- bantamkit/assets/rubrics/.gitkeep +0 -0
- bantamkit/assets/rubrics/code-quality.yaml +20 -0
- bantamkit/assets/rubrics/grounded-completion.yaml +37 -0
- bantamkit/assets/rubrics/task-completion.yaml +28 -0
- bantamkit/assets/schemas/shiftwork-checkpoint.json +188 -0
- bantamkit/assets/skills/.gitkeep +0 -0
- bantamkit/assets/skills/file-graph.md +7 -0
- bantamkit/assets/skills/memory.md +35 -0
- bantamkit/assets/tools/.gitkeep +0 -0
- bantamkit/assets/tools/bantamkit_read.json +48 -0
- bantamkit/assets/tools/bantamkit_status.json +25 -0
- bantamkit/assets/tools/build_identity.json +17 -0
- bantamkit/assets/tools/document_list.json +12 -0
- bantamkit/assets/tools/document_read.json +31 -0
- bantamkit/assets/tools/file_graph.json +12 -0
- bantamkit/assets/tools/memory_compact.json +31 -0
- bantamkit/assets/tools/memory_recall.json +38 -0
- bantamkit/assets/tools/memory_save.json +61 -0
- bantamkit/assets/tools/shiftwork_clock_in.json +25 -0
- bantamkit/assets/tools/shiftwork_clock_out.json +60 -0
- bantamkit/assets/tools/shiftwork_status.json +25 -0
- bantamkit/assets/tools/skill_audit.json +70 -0
- bantamkit/assets/tools/validate_json.json +31 -0
- bantamkit/assets.py +67 -0
- bantamkit/budget.py +114 -0
- bantamkit/client.py +329 -0
- bantamkit/contract.py +522 -0
- bantamkit/criticreplay.py +3241 -0
- bantamkit/critique.py +301 -0
- bantamkit/docread.py +1744 -0
- bantamkit/evalrun.py +2003 -0
- bantamkit/eventlog.py +282 -0
- bantamkit/filegraph.py +218 -0
- bantamkit/loopguard.py +101 -0
- bantamkit/mcpreport.py +763 -0
- bantamkit/mcpserver.py +1334 -0
- bantamkit/memory/__init__.py +28 -0
- bantamkit/memory/__main__.py +291 -0
- bantamkit/memory/component.py +569 -0
- bantamkit/memory/divergence.py +744 -0
- bantamkit/memory/layers.py +257 -0
- bantamkit/memory/store.py +940 -0
- bantamkit/pdfread.py +1402 -0
- bantamkit/profile.py +46 -0
- bantamkit/shiftwork.py +212 -0
- bantamkit/skillaudit.py +853 -0
- bantamkit/statusline.py +313 -0
- bantamkit/structured.py +125 -0
- bantamkit/textutil.py +30 -0
- bantamkit-0.27.0.dist-info/METADATA +207 -0
- bantamkit-0.27.0.dist-info/RECORD +119 -0
- bantamkit-0.27.0.dist-info/WHEEL +4 -0
- bantamkit-0.27.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from bantamkit.memory.component import CompactOutcome, Memory, RecallOutcome, SaveOutcome
|
|
2
|
+
from bantamkit.memory.divergence import (
|
|
3
|
+
BodyDiff,
|
|
4
|
+
DivergenceReport,
|
|
5
|
+
StoredFact,
|
|
6
|
+
UnparseableFact,
|
|
7
|
+
bantamkit_store_root,
|
|
8
|
+
compare_stores,
|
|
9
|
+
native_store_root,
|
|
10
|
+
parse_fact,
|
|
11
|
+
read_store,
|
|
12
|
+
)
|
|
13
|
+
from bantamkit.memory.layers import (
|
|
14
|
+
MEMORY_DIR_ENV,
|
|
15
|
+
StoreBinding,
|
|
16
|
+
discover_project_store,
|
|
17
|
+
resolve_project_store,
|
|
18
|
+
)
|
|
19
|
+
from bantamkit.memory.store import (
|
|
20
|
+
DEFAULT_INDEX_BUDGET,
|
|
21
|
+
ArchivedFact,
|
|
22
|
+
CompactResult,
|
|
23
|
+
Fact,
|
|
24
|
+
MemoryBudgetExceeded,
|
|
25
|
+
MemoryStore,
|
|
26
|
+
MemoryValidationError,
|
|
27
|
+
SaveResult,
|
|
28
|
+
)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""The operator's half of the lifecycle position: `python -m bantamkit.memory`.
|
|
2
|
+
|
|
3
|
+
`docs/memory.md` states, as a design decision, that `lint`, `archived` and
|
|
4
|
+
`restore` are **not** agent tools — "lifecycle is an operator decision, not a
|
|
5
|
+
model decision" — and the eleven-tool surface holds to it; `compact` is the one
|
|
6
|
+
exception since job42 (`memory_compact`, the on-refusal path the model reaches
|
|
7
|
+
itself). That position is only coherent if the operator can actually make the
|
|
8
|
+
decision. Measured
|
|
9
|
+
2026-08-21, they could not: `index_budget` appeared on no argument parser, and
|
|
10
|
+
the four lifecycle ops were reachable only by importing `MemoryStore` and calling
|
|
11
|
+
them from Python. A design that hands lifecycle to an operator and gives the
|
|
12
|
+
operator no lever is not a position, it is an omission.
|
|
13
|
+
|
|
14
|
+
Why a separate entry point rather than a flag on `bantamkit-mcp`: that process
|
|
15
|
+
speaks the MCP protocol over stdout, so anything a lifecycle report printed there
|
|
16
|
+
would corrupt the wire. Lifecycle needs a stream it owns. `python -m` is also the
|
|
17
|
+
invocation this repo already treats as the one a host reaches for when the
|
|
18
|
+
console script is not on PATH (see the `__main__` guard in `mcpserver.py`), and
|
|
19
|
+
it adds no packaging surface and no dependency.
|
|
20
|
+
|
|
21
|
+
Scope is deliberately the writable project layer only. `Memory.layered()` also
|
|
22
|
+
mounts read-only grants and the profile store, and `MemoryStore.compact()`
|
|
23
|
+
touches neither; resolving through `discover_project_store` keeps the CLI unable
|
|
24
|
+
to archive a store it does not own, rather than merely unlikely to.
|
|
25
|
+
|
|
26
|
+
Exit codes: 0 success, 1 an operational failure the operator must act on (over
|
|
27
|
+
budget, malformed fact, refused restore), 2 argparse usage error.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import argparse
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
from bantamkit.client import BantamError
|
|
37
|
+
from bantamkit.memory.layers import discover_project_store
|
|
38
|
+
from bantamkit.memory.store import (
|
|
39
|
+
DEFAULT_INDEX_BUDGET,
|
|
40
|
+
MemoryBudgetExceeded,
|
|
41
|
+
MemoryStore,
|
|
42
|
+
MemoryValidationError,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
#: The one place this CLI's own name is spelled, and `runtime-ts` has the same constant
|
|
46
|
+
#: under the same name (`PROG` in `src/memory/cli.ts`) holding `bantamkit-memory`. The
|
|
47
|
+
#: `memorycli` conformance suite compares the two CLIs after substituting one for the
|
|
48
|
+
#: other, so everything downstream of it -- the usage line, every `...: error:` prefix,
|
|
49
|
+
#: the sentence `main` prints for a store it could not read, and the two remediation
|
|
50
|
+
#: lines that name a command for the operator to RUN -- has to move with it or the two
|
|
51
|
+
#: halves drift apart one string at a time.
|
|
52
|
+
#:
|
|
53
|
+
#: One remediation line in this runtime is NOT downstream of this constant and cannot be
|
|
54
|
+
#: without the MCP server importing this module: the `index-budget-low` sentence in
|
|
55
|
+
#: `mcpserver.py` spells the same command as a literal. It is held to this value from the
|
|
56
|
+
#: outside instead, by `tests/test_status_surface.py`'s
|
|
57
|
+
#: `test_the_index_remedy_names_the_command_this_install_actually_provides`.
|
|
58
|
+
_PROG = "python -m bantamkit.memory"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _lf_utf8(stream: object) -> None:
|
|
62
|
+
"""Make one of this process's text streams write LF and UTF-8 on every platform.
|
|
63
|
+
|
|
64
|
+
`sys.stdout` and `sys.stderr` are text streams opened with `newline=None`, which
|
|
65
|
+
translates every `\n` to `os.linesep` on the way out: a no-op on macOS and Linux, and
|
|
66
|
+
CRLF on Windows. Their encoding is the locale's, which on Windows is a code page, not
|
|
67
|
+
UTF-8. So the same command run on the same store printed different BYTES on Windows than
|
|
68
|
+
it did here, and the port -- whose `process.stdout.write` emits LF and UTF-8 everywhere
|
|
69
|
+
-- was byte-identical to this CLI on one operating system and not on the other.
|
|
70
|
+
|
|
71
|
+
`mcpserver.py`'s `_print_assets_root`, `_print_mcp_report` and `_print_status_line` each
|
|
72
|
+
solve their own half of this by writing through `sys.stdout.buffer`, and that is the
|
|
73
|
+
established idiom in this repository. It CANNOT be the idiom here, and the reason is
|
|
74
|
+
measured rather than assumed: on Windows' defaults, reproduced on macOS with
|
|
75
|
+
`TextIOWrapper(..., encoding="cp1252", newline="\r\n")`, `-h` emits 16 CRLFs and
|
|
76
|
+
`status --nope` emits 3 -- and every one of them is written by `argparse`, into
|
|
77
|
+
`sys.stdout`/`sys.stderr` by name, from a frame no call site in this module owns. A sweep
|
|
78
|
+
of the `print()` calls below would have fixed the 6 CRLFs of `status` and left the 16 of
|
|
79
|
+
`-h`. The stream is the thing that translates, so the stream is the thing to fix.
|
|
80
|
+
|
|
81
|
+
Guarded on `reconfigure` rather than on a type, because `sys.stdout` is not always a
|
|
82
|
+
`TextIOWrapper`: `pytest`'s capture, a `StringIO` and a closed-stdout `pythonw` all reach
|
|
83
|
+
this line, and a stream that cannot be reconfigured is one that was never doing platform
|
|
84
|
+
translation in the first place.
|
|
85
|
+
"""
|
|
86
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
87
|
+
if reconfigure is not None:
|
|
88
|
+
reconfigure(encoding="utf-8", newline="\n")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _positive(text: str) -> int:
|
|
92
|
+
"""A budget of 0 or less silently makes every save fail; refuse it at the edge."""
|
|
93
|
+
value = int(text)
|
|
94
|
+
if value < 1:
|
|
95
|
+
raise argparse.ArgumentTypeError("must be >= 1")
|
|
96
|
+
return value
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
100
|
+
parser = argparse.ArgumentParser(
|
|
101
|
+
prog=_PROG,
|
|
102
|
+
description=(
|
|
103
|
+
"Operator lifecycle for a bantamkit memory store: inspect, lint, "
|
|
104
|
+
"compact, archive and restore. Not an agent surface."
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
subs = parser.add_subparsers(dest="command", required=True)
|
|
108
|
+
|
|
109
|
+
def common(sub: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
|
110
|
+
stores = sub.add_mutually_exclusive_group()
|
|
111
|
+
stores.add_argument("--store", help="memory store path (skips project discovery)")
|
|
112
|
+
stores.add_argument(
|
|
113
|
+
"--start", help="directory to start project-store discovery from (default: cwd)"
|
|
114
|
+
)
|
|
115
|
+
sub.add_argument(
|
|
116
|
+
"--budget",
|
|
117
|
+
type=_positive,
|
|
118
|
+
default=DEFAULT_INDEX_BUDGET,
|
|
119
|
+
metavar="BYTES",
|
|
120
|
+
help=f"index byte budget (default: {DEFAULT_INDEX_BUDGET})",
|
|
121
|
+
)
|
|
122
|
+
return sub
|
|
123
|
+
|
|
124
|
+
common(subs.add_parser("status", help="index size, budget, headroom, archive count"))
|
|
125
|
+
common(subs.add_parser("lint", help="exit 1 if the store is malformed or over budget"))
|
|
126
|
+
compact = common(subs.add_parser("compact", help="archive the stalest facts"))
|
|
127
|
+
compact.add_argument(
|
|
128
|
+
"--reserve",
|
|
129
|
+
type=_positive,
|
|
130
|
+
default=None,
|
|
131
|
+
metavar="BYTES",
|
|
132
|
+
help="headroom to leave below the budget (default: the largest index line kept)",
|
|
133
|
+
)
|
|
134
|
+
common(subs.add_parser("archived", help="list what compaction has moved out"))
|
|
135
|
+
archive = common(subs.add_parser("archive", help="move one named fact out"))
|
|
136
|
+
archive.add_argument("name", help="name of the fact to archive")
|
|
137
|
+
restore = common(subs.add_parser("restore", help="move an archived fact back"))
|
|
138
|
+
restore.add_argument("name", help="name of the archived fact")
|
|
139
|
+
|
|
140
|
+
return parser.parse_args(argv)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _open(args: argparse.Namespace) -> MemoryStore:
|
|
144
|
+
if args.store is not None:
|
|
145
|
+
if not args.store:
|
|
146
|
+
raise SystemExit("--store requires a non-empty path")
|
|
147
|
+
root = Path(args.store)
|
|
148
|
+
else:
|
|
149
|
+
root = discover_project_store(args.start)
|
|
150
|
+
return MemoryStore(root, index_budget=args.budget)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _size(store: MemoryStore) -> int:
|
|
154
|
+
return len(store.index_text().encode())
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _cmd_status(store: MemoryStore, args: argparse.Namespace) -> int:
|
|
158
|
+
size = _size(store)
|
|
159
|
+
print(f"store: {store.root}")
|
|
160
|
+
print(f"facts: {len(store._facts())}")
|
|
161
|
+
print(f"index: {size} bytes")
|
|
162
|
+
print(f"budget: {store.index_budget}")
|
|
163
|
+
print(f"headroom: {store.index_budget - size}")
|
|
164
|
+
print(f"archived: {len(store.archived())}")
|
|
165
|
+
return 0
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _cmd_lint(store: MemoryStore, args: argparse.Namespace) -> int:
|
|
169
|
+
try:
|
|
170
|
+
store.lint()
|
|
171
|
+
except MemoryBudgetExceeded:
|
|
172
|
+
# Not the store's own message: that one names `compact()`, which is a Python
|
|
173
|
+
# call and the wrong remedy to hand someone holding a shell.
|
|
174
|
+
where = f"--store {store.root}"
|
|
175
|
+
print(
|
|
176
|
+
f"lint: FAIL — index is {_size(store)} bytes, budget is {store.index_budget}\n"
|
|
177
|
+
f" try: {_PROG} compact {where} "
|
|
178
|
+
f"--budget {store.index_budget}",
|
|
179
|
+
file=sys.stderr,
|
|
180
|
+
)
|
|
181
|
+
return 1
|
|
182
|
+
except MemoryValidationError as e:
|
|
183
|
+
print(f"lint: FAIL — {e}", file=sys.stderr)
|
|
184
|
+
return 1
|
|
185
|
+
print(f"lint: ok — {len(store._facts())} facts, {_size(store)}/{store.index_budget} bytes")
|
|
186
|
+
return 0
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _cmd_compact(store: MemoryStore, args: argparse.Namespace) -> int:
|
|
190
|
+
before = _size(store)
|
|
191
|
+
result = store.compact(reserve=args.reserve)
|
|
192
|
+
print(f"compacted {len(result.archived)} fact(s)")
|
|
193
|
+
print(
|
|
194
|
+
f"index: {before} -> {result.index_after} bytes "
|
|
195
|
+
f"(budget {result.budget}, target {result.target}, "
|
|
196
|
+
f"reserve {result.reserve}, headroom {result.headroom})"
|
|
197
|
+
)
|
|
198
|
+
if not result.archived:
|
|
199
|
+
print("nothing to archive — the index is already at or below the target")
|
|
200
|
+
return 0
|
|
201
|
+
# `archive/` is on disk and nothing reads it back on its own, so the names have to
|
|
202
|
+
# land here or the operator never learns what left.
|
|
203
|
+
print(f"archived -> {result.archive_dir}")
|
|
204
|
+
for fact in result.archived:
|
|
205
|
+
print(f" {fact.name} ({fact.type}, {fact.index_bytes} bytes)")
|
|
206
|
+
print(f"restore one with: {_PROG} restore <name> --store {store.root}")
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _cmd_archived(store: MemoryStore, args: argparse.Namespace) -> int:
|
|
211
|
+
names = store.archived()
|
|
212
|
+
print(f"archived facts: {len(names)} ({store.root / 'archive'})")
|
|
213
|
+
for name in names:
|
|
214
|
+
print(f" {name}")
|
|
215
|
+
return 0
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _cmd_archive(store: MemoryStore, args: argparse.Namespace) -> int:
|
|
219
|
+
try:
|
|
220
|
+
store.archive(args.name)
|
|
221
|
+
except MemoryValidationError as e:
|
|
222
|
+
print(f"archive failed: {e}", file=sys.stderr)
|
|
223
|
+
return 1
|
|
224
|
+
print(f"archived '{args.name}' — index now {_size(store)}/{store.index_budget} bytes")
|
|
225
|
+
return 0
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _cmd_restore(store: MemoryStore, args: argparse.Namespace) -> int:
|
|
229
|
+
try:
|
|
230
|
+
store.restore(args.name)
|
|
231
|
+
except MemoryBudgetExceeded:
|
|
232
|
+
print(
|
|
233
|
+
f"restore failed: '{args.name}' would put the index over the budget of "
|
|
234
|
+
f"{store.index_budget} bytes. Nothing changed — raise --budget or compact first.",
|
|
235
|
+
file=sys.stderr,
|
|
236
|
+
)
|
|
237
|
+
return 1
|
|
238
|
+
except MemoryValidationError as e:
|
|
239
|
+
print(f"restore failed: {e}", file=sys.stderr)
|
|
240
|
+
return 1
|
|
241
|
+
print(f"restored '{args.name}' — index now {_size(store)}/{store.index_budget} bytes")
|
|
242
|
+
return 0
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
_COMMANDS = {
|
|
246
|
+
"status": _cmd_status,
|
|
247
|
+
"lint": _cmd_lint,
|
|
248
|
+
"compact": _cmd_compact,
|
|
249
|
+
"archived": _cmd_archived,
|
|
250
|
+
"archive": _cmd_archive,
|
|
251
|
+
"restore": _cmd_restore,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def main(argv: list[str] | None = None) -> int:
|
|
256
|
+
"""Run one subcommand and return its exit code. The exit code is the ONLY channel.
|
|
257
|
+
|
|
258
|
+
`status`, `compact` and `archived` catch nothing of their own -- there is no
|
|
259
|
+
remediation to offer for a store that cannot be read, only a report -- so before
|
|
260
|
+
this clause a `MemoryValidationError` out of `_listing` unwound all the way through
|
|
261
|
+
CPython, which printed a two-stage traceback carrying interpreter absolute paths and
|
|
262
|
+
line numbers from inside `store.py`. Exit 1 either way; the difference is entirely in
|
|
263
|
+
what the operator is handed, and a stack trace names this repository's files rather
|
|
264
|
+
than the store the operator asked about.
|
|
265
|
+
|
|
266
|
+
`BantamError` and nothing wider, exactly as `runtime-ts/src/memory/cli.ts` has it: a
|
|
267
|
+
bug in bantamkit is still a traceback, because that one IS a report for a maintainer.
|
|
268
|
+
What is caught here is the class of failures that are ABOUT the operator's store --
|
|
269
|
+
unreadable, unlistable, malformed -- and every one of them already carries a sentence
|
|
270
|
+
that names the directory and says what the consequence would have been. The prefix is
|
|
271
|
+
this CLI's own `prog`, so the line reads as the program speaking rather than as an
|
|
272
|
+
error string from nowhere, and it is byte-identical to the port's after the one
|
|
273
|
+
substitution the conformance suite makes.
|
|
274
|
+
|
|
275
|
+
`SystemExit` is deliberately not caught: argparse's usage errors are exit 2 and
|
|
276
|
+
`_open`'s empty-`--store` refusal is its own sentence already on stderr.
|
|
277
|
+
"""
|
|
278
|
+
# BEFORE `_parse_args`, because `-h` and every usage error are written by argparse
|
|
279
|
+
# straight into these two streams and never come back through this frame.
|
|
280
|
+
_lf_utf8(sys.stdout)
|
|
281
|
+
_lf_utf8(sys.stderr)
|
|
282
|
+
args = _parse_args(argv)
|
|
283
|
+
try:
|
|
284
|
+
return _COMMANDS[args.command](_open(args), args)
|
|
285
|
+
except BantamError as e:
|
|
286
|
+
print(f"{_PROG}: {e}", file=sys.stderr)
|
|
287
|
+
return 1
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
if __name__ == "__main__":
|
|
291
|
+
raise SystemExit(main())
|