bantamkit-mcp 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +398 -0
  3. package/assets/contracts/default.yaml +90 -0
  4. package/assets/evals/devteam/manifest.yaml +351 -0
  5. package/assets/evals/devteam/repo/HISTORY.md +18 -0
  6. package/assets/evals/devteam/repo/README.md +12 -0
  7. package/assets/evals/devteam/repo/docs/architecture.md +17 -0
  8. package/assets/evals/devteam/repo/docs/runbook.md +10 -0
  9. package/assets/evals/devteam/repo/issues/142-settlement-timeout.md +23 -0
  10. package/assets/evals/devteam/repo/patches/0009-retry-budget.patch +38 -0
  11. package/assets/evals/devteam/repo/src/ledger/__init__.py +3 -0
  12. package/assets/evals/devteam/repo/src/ledger/config.py +35 -0
  13. package/assets/evals/devteam/repo/src/ledger/errors.py +13 -0
  14. package/assets/evals/devteam/repo/src/ledger/posting.py +12 -0
  15. package/assets/evals/devteam/repo/src/ledger/registry.py +7 -0
  16. package/assets/evals/devteam/repo/src/ledger/report.py +9 -0
  17. package/assets/evals/devteam/repo/src/ledger/retry.py +17 -0
  18. package/assets/evals/devteam/repo/src/ledger/settle.py +16 -0
  19. package/assets/evals/devteam/repo/src/ledger/validate.py +14 -0
  20. package/assets/evals/devteam/repo/tests/test_posting.py +13 -0
  21. package/assets/evals/devteam/repo/tests/test_settle.py +9 -0
  22. package/assets/evals/devteam/tasks/dt-error-contract.yaml +186 -0
  23. package/assets/evals/devteam/tasks/dt-handler-map.yaml +183 -0
  24. package/assets/evals/devteam/tasks/dt-patch-before-after.yaml +182 -0
  25. package/assets/evals/devteam/tasks/dt-retry-attempts.yaml +181 -0
  26. package/assets/evals/devteam/tasks/dt-settlement-config.yaml +185 -0
  27. package/assets/evals/devteam/tasks/dt-symbol-home.yaml +181 -0
  28. package/assets/evals/devteam/tasks/dt-trace-blame.yaml +182 -0
  29. package/assets/evals/devteam/tasks/dt-unread-key.yaml +180 -0
  30. package/assets/evals/document/tasks/doc-large-in-137.yaml +38 -0
  31. package/assets/evals/document/tasks/doc-large-in-359.yaml +44 -0
  32. package/assets/evals/document/tasks/doc-large-in-372.yaml +38 -0
  33. package/assets/evals/document/tasks/doc-large-out-11764.yaml +37 -0
  34. package/assets/evals/document/tasks/doc-large-out-4137.yaml +37 -0
  35. package/assets/evals/document/tasks/doc-large-out-8022.yaml +37 -0
  36. package/assets/evals/document/tasks/doc-small-137.yaml +37 -0
  37. package/assets/evals/document/tasks/doc-small-261.yaml +37 -0
  38. package/assets/evals/document/tasks/doc-small-388.yaml +37 -0
  39. package/assets/evals/fixtures/.gitkeep +0 -0
  40. package/assets/evals/fixtures/catalog.json +6 -0
  41. package/assets/evals/perturbations/task-completion.yaml +576 -0
  42. package/assets/evals/tasks/.gitkeep +0 -0
  43. package/assets/evals/tasks/extract-contact.yaml +14 -0
  44. package/assets/evals/tasks/extract-invoice.yaml +14 -0
  45. package/assets/evals/tasks/extract-order.yaml +15 -0
  46. package/assets/evals/tasks/extract-schedule.yaml +14 -0
  47. package/assets/evals/tasks/extract-versions.yaml +17 -0
  48. package/assets/evals/tasks/nav-prod-port.yaml +84 -0
  49. package/assets/evals/tasks/nav-release-bundle.yaml +87 -0
  50. package/assets/evals/tasks/recall-audit-retention.yaml +17 -0
  51. package/assets/evals/tasks/recall-cache-ttl.yaml +13 -0
  52. package/assets/evals/tasks/recall-db-port.yaml +17 -0
  53. package/assets/evals/tasks/recall-deploy.yaml +13 -0
  54. package/assets/evals/tasks/recall-env-endpoint.yaml +18 -0
  55. package/assets/evals/tasks/recall-oncall-rotation.yaml +21 -0
  56. package/assets/evals/tasks/recall-oncall.yaml +13 -0
  57. package/assets/evals/tasks/recall-org-quota.yaml +18 -0
  58. package/assets/evals/tasks/recall-owner.yaml +13 -0
  59. package/assets/evals/tasks/shop-basket-total.yaml +10 -0
  60. package/assets/evals/tasks/shop-cheapest.yaml +9 -0
  61. package/assets/evals/tasks/shop-compare.yaml +9 -0
  62. package/assets/evals/tasks/shop-gadget-value.yaml +9 -0
  63. package/assets/evals/tasks/shop-stock-total.yaml +9 -0
  64. package/assets/evals/tasks/shop-total.yaml +9 -0
  65. package/assets/profiles/default.yaml +31 -0
  66. package/assets/profiles/patient.yaml +31 -0
  67. package/assets/rubrics/.gitkeep +0 -0
  68. package/assets/rubrics/code-quality.yaml +20 -0
  69. package/assets/rubrics/grounded-completion.yaml +37 -0
  70. package/assets/rubrics/task-completion.yaml +28 -0
  71. package/assets/schemas/shiftwork-checkpoint.json +188 -0
  72. package/assets/skills/.gitkeep +0 -0
  73. package/assets/skills/file-graph.md +7 -0
  74. package/assets/skills/memory.md +35 -0
  75. package/assets/tools/.gitkeep +0 -0
  76. package/assets/tools/bantamkit_read.json +48 -0
  77. package/assets/tools/bantamkit_status.json +25 -0
  78. package/assets/tools/build_identity.json +17 -0
  79. package/assets/tools/document_list.json +12 -0
  80. package/assets/tools/document_read.json +31 -0
  81. package/assets/tools/file_graph.json +12 -0
  82. package/assets/tools/memory_compact.json +31 -0
  83. package/assets/tools/memory_recall.json +38 -0
  84. package/assets/tools/memory_save.json +61 -0
  85. package/assets/tools/shiftwork_clock_in.json +25 -0
  86. package/assets/tools/shiftwork_clock_out.json +60 -0
  87. package/assets/tools/shiftwork_status.json +25 -0
  88. package/assets/tools/skill_audit.json +70 -0
  89. package/assets/tools/validate_json.json +31 -0
  90. package/dist/assets.d.ts +48 -0
  91. package/dist/assets.js +109 -0
  92. package/dist/charsets.d.ts +17 -0
  93. package/dist/charsets.js +580 -0
  94. package/dist/cli.d.ts +12 -0
  95. package/dist/cli.js +280 -0
  96. package/dist/contract.d.ts +145 -0
  97. package/dist/contract.js +608 -0
  98. package/dist/docread.d.ts +357 -0
  99. package/dist/docread.js +3315 -0
  100. package/dist/errors.d.ts +11 -0
  101. package/dist/errors.js +14 -0
  102. package/dist/eventlog.d.ts +137 -0
  103. package/dist/eventlog.js +294 -0
  104. package/dist/htmlentities.d.ts +2 -0
  105. package/dist/htmlentities.js +16 -0
  106. package/dist/index.d.ts +23 -0
  107. package/dist/index.js +13 -0
  108. package/dist/mcp/identity.d.ts +25 -0
  109. package/dist/mcp/identity.js +275 -0
  110. package/dist/mcp/pyargs.d.ts +80 -0
  111. package/dist/mcp/pyargs.js +362 -0
  112. package/dist/mcp/sdkjson.d.ts +56 -0
  113. package/dist/mcp/sdkjson.js +91 -0
  114. package/dist/mcp/server.d.ts +45 -0
  115. package/dist/mcp/server.js +740 -0
  116. package/dist/mcp/status.d.ts +210 -0
  117. package/dist/mcp/status.js +357 -0
  118. package/dist/mcp/transport.d.ts +68 -0
  119. package/dist/mcp/transport.js +111 -0
  120. package/dist/mcpreport.d.ts +214 -0
  121. package/dist/mcpreport.js +758 -0
  122. package/dist/memory/cli.d.ts +2 -0
  123. package/dist/memory/cli.js +396 -0
  124. package/dist/memory/component.d.ts +256 -0
  125. package/dist/memory/component.js +497 -0
  126. package/dist/memory/factfile.d.ts +120 -0
  127. package/dist/memory/factfile.js +406 -0
  128. package/dist/memory/layers.d.ts +92 -0
  129. package/dist/memory/layers.js +410 -0
  130. package/dist/memory/pyfs.d.ts +521 -0
  131. package/dist/memory/pyfs.js +1608 -0
  132. package/dist/memory/pyyaml.d.ts +155 -0
  133. package/dist/memory/pyyaml.js +868 -0
  134. package/dist/memory/store.d.ts +478 -0
  135. package/dist/memory/store.js +1034 -0
  136. package/dist/pyargparse.d.ts +141 -0
  137. package/dist/pyargparse.js +813 -0
  138. package/dist/pyjson.d.ts +110 -0
  139. package/dist/pyjson.js +524 -0
  140. package/dist/pyjsonschema.d.ts +50 -0
  141. package/dist/pyjsonschema.js +951 -0
  142. package/dist/shiftwork.d.ts +60 -0
  143. package/dist/shiftwork.js +324 -0
  144. package/dist/skillaudit.d.ts +172 -0
  145. package/dist/skillaudit.js +914 -0
  146. package/dist/statusline.d.ts +106 -0
  147. package/dist/statusline.js +281 -0
  148. package/package.json +45 -0
@@ -0,0 +1,521 @@
1
+ /** The errno names this port claims to render in CPython's words. The suite reads it. */
2
+ export declare const STRERROR_NAMES: readonly string[];
3
+ /**
4
+ * `os.strerror` for one errno NAME — the table alone, with no Windows arm in front of it.
5
+ *
6
+ * The conformance case that checks these wordings used to read them back through
7
+ * `asPyOSError`, which now answers with the WIN32 message on Windows for exactly the codes
8
+ * the table also carries. Reading the table directly keeps that case measuring the thing its
9
+ * name says, and leaves the winerror wordings to the `winerror table` case beside it.
10
+ */
11
+ export declare function pyStrerror(code: string): string | undefined;
12
+ /**
13
+ * `type(exc).__name__` for an `OSError` with this errno NAME — the table and its default.
14
+ *
15
+ * ONE reader of `OSERROR_SUBCLASS` for two callers, because the default is half the rule:
16
+ * an unlisted errno is `OSError` in CPython, and a second `?? 'OSError'` written somewhere
17
+ * else is a second place for the two halves to drift apart. `PyOSError`'s own constructor
18
+ * takes its `name` from here, and `eventlog.ts` asks the same question of an exception it
19
+ * is about to record — the port had already decided this question in the constructor and
20
+ * the event log answered it with the JavaScript constructor name instead, recording
21
+ * `PyOSError` where CPython records `NotADirectoryError`.
22
+ */
23
+ export declare function osErrorClassName(code: string): string;
24
+ /**
25
+ * An `OSError` as Python prints one.
26
+ *
27
+ * `str(OSError)` is `[Errno {errno}] {strerror}: {filename!r}`, and that text is not
28
+ * decoration: `_ensure_dirs` lets a `FileExistsError` out of `save` unconverted, and
29
+ * `component.Memory.save` turns whatever comes out into the sentence a model reads.
30
+ * `errno` is the platform number, which is what Python prints and what libuv already
31
+ * gives us as the magnitude of `err.errno`.
32
+ */
33
+ export declare class PyOSError extends Error {
34
+ readonly errno: number;
35
+ readonly code: string;
36
+ readonly strerror: string;
37
+ readonly filename: string | null;
38
+ /** `OSError.filename2` — set by the two-path syscalls, and PRINTED when it is. */
39
+ readonly filename2: string | null;
40
+ /**
41
+ * `OSError.winerror`, which exists ONLY on Windows and CHANGES `__str__`.
42
+ *
43
+ * CPython carries both numbers for a call that went through the Win32 API: the translated
44
+ * POSIX `errno` and the raw Win32 code. `OSError.__str__` prefers the latter and prints
45
+ * `[WinError %d] <the Win32 message>`. `null` here means "this OSError has no winerror",
46
+ * which is every OSError on POSIX and every OSError CPython raises on Windows out of the
47
+ * C runtime rather than out of the Win32 API — see `asPyOSError`'s `origin`.
48
+ */
49
+ readonly winerror: number | null;
50
+ constructor(errno: number, code: string, strerror: string, filename: string | null, filename2?: string | null, winerror?: number | null);
51
+ }
52
+ /** The winerrors this port claims to render in CPython's words. The suite reads it. */
53
+ export declare const WINERROR_NUMBERS: readonly number[];
54
+ /** One Win32 wording, as CPython trims it. The `winerror table` case checks every entry. */
55
+ export declare function pyWinStrerror(winerror: number): string | undefined;
56
+ /**
57
+ * Which SHAPE of Windows call produced an error, because CPython's answer depends on it.
58
+ *
59
+ * `os.stat`, `os.scandir`, `os.mkdir`, `os.replace` and `os.unlink` go through the Win32 API
60
+ * and raise an OSError with `winerror` SET. `open()` does NOT: it goes through the C runtime,
61
+ * which fills in `errno` alone, and `str()` of that error is the ordinary `[Errno %d]` form
62
+ * with the CRT's own POSIX wording. MEASURED, run 32646521489: opening a directory for write
63
+ * is `[Errno 13] Permission denied` on CPython-for-Windows — not `[WinError 5]`, and not the
64
+ * `[Errno 21] Is a directory` libuv reports. Getting this split wrong renders the right
65
+ * number in the wrong sentence.
66
+ */
67
+ export type OSErrorOrigin = 'win32' | 'crt' | 'scandir';
68
+ /**
69
+ * libuv's code -> the Win32 error CPython would have carried, or `null` for "no winerror".
70
+ *
71
+ * THE MAPPING IS MANY-TO-ONE IN THE DIRECTION THAT LOSES INFORMATION, which is why this
72
+ * cannot be a plain table. libuv translates every Win32 error into one POSIX-shaped code, so
73
+ * `ERROR_FILE_NOT_FOUND` (2) and `ERROR_PATH_NOT_FOUND` (3) both arrive as `ENOENT` and the
74
+ * distinction Windows drew has to be RE-DERIVED. Win32 picks 3 whenever the component that
75
+ * failed was needed AS A DIRECTORY and 2 when only the final name was missing, so the probe
76
+ * is exactly that: which of the components is not there.
77
+ *
78
+ * Verified against the reference for both arms on run 32646521489 — `os.replace` onto a
79
+ * missing destination DIRECTORY is 3, `os.replace` of a missing source FILE beside an
80
+ * existing directory is 2. `exists` is injected so the derivation is a pure function with a
81
+ * node that goes red on a laptop.
82
+ */
83
+ export declare function winerrorFor(code: string, origin: OSErrorOrigin, path: string | null, dest: string | null, exists: (candidate: string) => boolean): number | null;
84
+ /**
85
+ * `winerror_to_errno` — CPython's translation, which is NOT the one libuv already applied.
86
+ *
87
+ * THE CLASS COMES OFF THE ERRNO, and the errno CPython carries is the one IT derived from
88
+ * the Win32 code, not the one libuv derived. MEASURED, run 32651049453: `os.unlink` of a
89
+ * name holding a newline is `ERROR_INVALID_NAME` (123), which CPython translates to EINVAL
90
+ * — and EINVAL is not in `errnomap`, so the exception is a plain `OSError`. libuv had folded
91
+ * the same failure into ENOENT, so this port raised `FileNotFoundError` and a caller's
92
+ * `except FileNotFoundError` would have swallowed an error the reference lets past.
93
+ *
94
+ * Every other arm here is an identity on the codes this port can reach (2/3 -> ENOENT,
95
+ * 183 -> EEXIST, 267 -> ENOTDIR), which is why only the 123 case was visible. The table is
96
+ * written out in full anyway: an arm that happens to be an identity today is exactly the one
97
+ * that stops being one without anybody noticing.
98
+ */
99
+ export declare function winerrorToCode(winerror: number): string;
100
+ /**
101
+ * The C runtime's answer where it is NOT libuv's, for the calls `open()` makes.
102
+ *
103
+ * One entry, and it is measured: opening a directory for writing is `EISDIR` to libuv and
104
+ * `EACCES` to the Windows CRT, so CPython prints `[Errno 13] Permission denied` for the
105
+ * `.tmp` that shiftwork's clock-out could not write. Everything else the CRT can raise here
106
+ * — ENOENT, EACCES, EEXIST, EINVAL — already agrees with libuv in both number and wording.
107
+ */
108
+ export declare function crtCode(code: string, path?: string | null): string;
109
+ /**
110
+ * Does any component of this path hold a character Win32 forbids in a name?
111
+ *
112
+ * `< > : " | ? *` and every control character below U+0020. The separators are excluded
113
+ * because they SPLIT the name rather than sitting in one, and the drive's colon is excluded
114
+ * for the same reason — it is part of the anchor, not of a component. Windows also strips a
115
+ * trailing space or dot silently rather than refusing, so neither is treated as invalid.
116
+ */
117
+ export declare function ntNameIsInvalid(path: string | null): boolean;
118
+ /**
119
+ * A Node `fs` rejection, restated as the `OSError` CPython would have raised.
120
+ *
121
+ * An errno outside the table keeps libuv's own lowercase wording rather than a fabricated
122
+ * one — a wrong capital letter in a sentence a model reads is worse than a visibly
123
+ * different one, and the table is the thing the conformance suite can check.
124
+ *
125
+ * `origin` says which of CPython's two error paths the call would have taken on Windows;
126
+ * off Windows it changes nothing, because there is only one path there.
127
+ */
128
+ export declare function asPyOSError(error: unknown, fallbackPath?: string, fallbackDest?: string, origin?: OSErrorOrigin): PyOSError;
129
+ /** `UnicodeDecodeError`, which is a `ValueError` in Python and is NOT an `OSError`. */
130
+ export declare class PyUnicodeDecodeError extends Error {
131
+ readonly start: number;
132
+ readonly end: number;
133
+ readonly reason: string;
134
+ constructor(bytes: Uint8Array, start: number, end: number, reason: string);
135
+ }
136
+ /**
137
+ * `bytes.decode("utf-8")` with `errors="strict"`, including the message.
138
+ *
139
+ * `TextDecoder` without `fatal` substitutes U+FFFD, which would let a corrupt fact file
140
+ * through and have `_stamp` write the substitution back over the original bytes — a
141
+ * silent, destructive divergence rather than a loud one. `fatal: true` raises, but with a
142
+ * `TypeError` whose text says nothing about where. `_facts` reads OUTSIDE its `try`, so
143
+ * this message is what a model sees for a binary file in `facts/`, verbatim.
144
+ *
145
+ * The three reasons and the reported span are CPython's, derived by running its decoder
146
+ * over 220 sequences and matched against all of them (the `utf-8 decode` cases in the
147
+ * conformance suite re-run that comparison).
148
+ *
149
+ * `ignoreBOM: true` IS THE DEFAULT SPELLING BACKWARDS, and it is the point. `TextDecoder`
150
+ * defaults `ignoreBOM` to FALSE, which means "treat a leading EF BB BF as a byte-order mark
151
+ * and DROP it". CPython's utf-8 codec has no such rule — `b'\xef\xbb\xbf'.decode('utf-8')`
152
+ * is `'\ufeff'`, one character, and `utf-8-sig` is the codec that strips it. The validator
153
+ * above already accepted EF BB BF as an ordinary three-byte sequence, so this module was
154
+ * checking one thing and returning another: N8 measured a BOM'd checkpoint that CPython
155
+ * REFUSES (`json.loads` answers `Unexpected UTF-8 BOM (decode using utf-8-sig)`) and that
156
+ * this port parsed and then WROTE. Every text read in the package now comes through here,
157
+ * `assets.ts` included, so there is one UTF-8 decode and not two with opposite answers.
158
+ */
159
+ export declare function pyDecodeUtf8(bytes: Uint8Array): string;
160
+ /**
161
+ * `Path.read_text(encoding="utf-8")`: strict UTF-8 AND universal-newline translation.
162
+ *
163
+ * The translation half is N2's `decodeFactBytes`, re-derived nowhere: this reads the bytes,
164
+ * decodes them strictly, and hands the string to the same fold. A CRLF fact file therefore
165
+ * parses in both runtimes, which is the property the codec suite already measures.
166
+ */
167
+ export declare function pyReadText(path: string): string;
168
+ /**
169
+ * `io.TextIOWrapper`'s outgoing newline translation, which is `newline=None`'s whole effect.
170
+ *
171
+ * THIS REVERSES N2's RULING, and the reason it was reversed is worth more than the ruling
172
+ * was. N2 wrote LF on every platform, arguing that CRLF "already disagrees with the store's
173
+ * own arithmetic" and that reproducing it "would make the same Fact emit different bytes on
174
+ * two machines". Both halves describe the REFERENCE, not a defect being avoided: CPython's
175
+ * `_check_index_budget` counts the LF string it just built while `write_text` puts CRLF on
176
+ * the disk, and CPython already emits different bytes for the same Fact on Windows and on
177
+ * macOS. A port that "fixes" that is not byte-compatible with the thing it stands beside —
178
+ * and byte-compatibility is the product, because the Python server and this one read and
179
+ * write the SAME store on the SAME machine. MEASURED, run 32646521489: 83 conformance cases
180
+ * differed by exactly this, one byte per line, in files both runtimes had just written.
181
+ *
182
+ * The arithmetic does not move. Python counts the in-memory LF text and so does this port;
183
+ * only the bytes leaving through `write` are translated, which is where CPython does it too.
184
+ *
185
+ * The translation is unconditional on `\n`, INCLUDING the `\n` of a `\r\n`: CPython emits
186
+ * `\r\r\n` for a string that already held a CRLF, and so does this.
187
+ */
188
+ export declare function toCrlf(text: string): string;
189
+ /** The same translation, gated the way CPython gates it: `#ifdef MS_WINDOWS`. */
190
+ export declare function pyNewlineOut(text: string): string;
191
+ /** `os.linesep` as one string — the line terminator a text-mode write actually emits. */
192
+ export declare const PY_LINESEP: string;
193
+ /** `Path.write_text(text, encoding="utf-8")`, newline translation included. */
194
+ export declare function pyWriteText(path: string, text: string): void;
195
+ /**
196
+ * `path.open("a", encoding="utf-8").write(text)` — the accounting log's append.
197
+ *
198
+ * Same newline translation as `pyWriteText`, for the same reason. One `appendFileSync` is
199
+ * one `open(O_APPEND)/write/close`, which is what CPython's `with` block does, so a line is
200
+ * never interleaved with another process's.
201
+ */
202
+ export declare function pyAppendText(path: string, text: string): void;
203
+ /**
204
+ * `os.path.lexists(path)` — `lstat` without following, and FALSE for every error.
205
+ *
206
+ * Not `lstatSync(p, {throwIfNoEntry: false})`: that returns `undefined` for ENOENT but
207
+ * still throws for EACCES on the parent, and `lexists` catches `(OSError, ValueError)`
208
+ * wholesale. This is the middle arm of the `_listing` three-way, so getting the error
209
+ * behaviour wrong turns an unreadable store back into an empty one.
210
+ */
211
+ export declare function pyLexists(path: string): boolean;
212
+ export declare function pyExists(path: string): boolean;
213
+ /**
214
+ * `Path.mkdir(parents=True, exist_ok=True)`.
215
+ *
216
+ * `exist_ok` suppresses `FileExistsError` only when the path is a DIRECTORY: CPython
217
+ * re-raises when a regular file sits there, which is exactly how `save` fails on a store
218
+ * whose `facts/` is a file — before the listing, with a bare `FileExistsError`
219
+ * (`test_a_facts_path_that_is_not_a_directory_is_unreadable_not_empty` says so). Node's
220
+ * recursive `mkdirSync` raises EEXIST there too; this only restates the error.
221
+ */
222
+ export declare function pyMkdirParents(path: string): void;
223
+ /**
224
+ * `os.scandir(directory)` reduced to names, raising the way CPython does.
225
+ *
226
+ * The caller decides what a failure MEANS — that decision is `store._listing`'s and
227
+ * `layers.count_facts` makes a different one from the same syscall.
228
+ */
229
+ export declare function pyScandirNames(directory: string): string[];
230
+ /** The decision inside `scandirShape`, without the platform gate, so a laptop can fail it. */
231
+ export declare function scandirIsNotADirectory(code: string, lexists: () => boolean): boolean;
232
+ /**
233
+ * `fnmatch.fnmatch(name, "*.md")`, which is the match `pathlib.glob` performs.
234
+ *
235
+ * Two things it is not: it is not "ends with .md" on Windows, where `os.path.normcase`
236
+ * lowercases first and `FACT.MD` is therefore a fact; and it has no dotfile or
237
+ * directory-entry special case, so `.hidden.md` counts and a DIRECTORY called `x.md`
238
+ * counts. `_listing`'s docstring makes not changing the counted set an explicit
239
+ * requirement, so this deliberately keeps every oddity.
240
+ */
241
+ export declare function matchesMd(name: string): boolean;
242
+ /** `os.path.normcase` — identity off Windows, `s.replace('/', '\\').lower()` on it. */
243
+ export declare function normcase(name: string): string;
244
+ /**
245
+ * Python's `<` on `str`, which is CODEPOINT order. JS compares UTF-16 code units.
246
+ *
247
+ * They disagree for every astral character: `['\u{1F414}.md', '!.md'].sort()` puts the
248
+ * chicken first because its lead surrogate is 0xD83D, and Python puts it last because
249
+ * 0x1F414 > 0xFF01. `_fact_paths` sorts, `recall` breaks score ties on the name, and the
250
+ * index is written in that order — so a single astral fact name is enough to make two
251
+ * `index.md` files that differ.
252
+ */
253
+ export declare function cmpCodepoint(a: string, b: string): number;
254
+ /**
255
+ * `sorted(directory / name for name in names)` — sorting `Path`s, not strings.
256
+ *
257
+ * `PurePath` compares `_parts_normcase` tuples. Every path here shares a parent, so the
258
+ * comparison reduces to the last component; what survives is the CASE FOLD, which
259
+ * `_parts_normcase` applies on Windows and nowhere else. Ties under that fold keep listing
260
+ * order, because both runtimes sort stably.
261
+ */
262
+ export declare function sortedPathNames(names: readonly string[]): string[];
263
+ /**
264
+ * `sorted(paths)` when the paths are NESTED — the case `sortedPathNames` above cannot cover.
265
+ *
266
+ * `PurePath.__lt__` compares the `_parts_normcase` TUPLE, and a tuple comparison stops at
267
+ * the first component that differs; it never sees the separator. Sorting the same paths as
268
+ * STRINGS does see it, and `-` (0x2D) sorts before `/` (0x2F), so the two orders are
269
+ * opposite for a pair like `a/b` and `a-b`:
270
+ *
271
+ * sorted([Path("assets/a/b"), Path("assets/a-b")]) -> ['assets/a/b', 'assets/a-b']
272
+ * sorted(["assets/a/b", "assets/a-b"]) -> ['assets/a-b', 'assets/a/b']
273
+ *
274
+ * N3 measured that this does not bite `_fact_paths`, whose paths are all siblings of one
275
+ * directory. It DOES bite `mcpserver._tree_digest`, which walks a nested tree and folds the
276
+ * order straight into `assets_digest` — a digest computed in string order over the shipped
277
+ * pack is a different digest, silently, and `build_identity` exists precisely to be
278
+ * trustworthy about that. A shorter tuple sorts first when it is a prefix of the longer one,
279
+ * which is `[] < ['b']` and needs no special case.
280
+ */
281
+ export declare function sortedPathParts(paths: readonly (readonly string[])[]): string[][];
282
+ /**
283
+ * `PurePath.__truediv__` and `str(Path)`, for the shapes a store root can take.
284
+ *
285
+ * Not `path.join`: `join('/a/b/..', 'facts')` collapses to `/a/facts` while
286
+ * `str(Path('/a/b/..') / 'facts')` keeps `/a/b/../facts`. Both open the same file, but the
287
+ * store PRINTS this path in the sentence a model reads for an unreadable store, and a port
288
+ * whose error names a different path than the reference's is a difference nobody put in
289
+ * writing. Empty and `.` components are dropped, `..` is kept, and the anchor survives —
290
+ * which is `PurePath`'s parsing, not a normalization.
291
+ *
292
+ * On Windows the separator is `\` and `/` is also accepted as one; drive-relative paths
293
+ * (`C:foo`) and UNC roots are outside what a store root has ever been and are not modelled.
294
+ */
295
+ export declare function pyJoin(...parts: string[]): string;
296
+ /**
297
+ * `ntpath.splitroot` — drive, root, and the rest, ported branch for branch.
298
+ *
299
+ * The old spelling of `parsePath` counted leading separators the way `PurePosixPath` does,
300
+ * and that reading is WRONG ON WINDOWS in the direction that changes an answer: `//a/b` is
301
+ * not "a root and two components", it is a UNC share whose whole text is the drive, with no
302
+ * parents at all and nothing for `_walk_to_store` to iterate. MEASURED against CPython 3.12
303
+ * on run 32646521489 — `PureWindowsPath('//a/b').parents` is `[]` and this port answered
304
+ * `['\\\\a', '\\\\']`, which is a walk over two directories that do not exist.
305
+ *
306
+ * The result is spelled with the CALLER's separators, not normalised ones, exactly as
307
+ * `ntpath.splitroot` returns slices of its argument.
308
+ */
309
+ export declare function ntSplitRoot(path: string): [string, string, string];
310
+ /**
311
+ * `PurePath._parse_path` under the Windows flavour: drive, root and the `_tail` components.
312
+ *
313
+ * The `drv_parts` arm is not decoration. `splitroot` hands back a bare `\\server\share` with
314
+ * an EMPTY root, and pathlib puts the root back on when the drive names a real share (four
315
+ * pieces) or a `\\?\UNC\server\share` (six) — which is why `//a/b` prints with a trailing
316
+ * separator and `//a` does not.
317
+ */
318
+ export declare function parseWindowsPath(path: string): {
319
+ drive: string;
320
+ root: string;
321
+ tail: string[];
322
+ };
323
+ /**
324
+ * `ntpath.join` — what `PurePath.__truediv__` actually feeds the parser.
325
+ *
326
+ * `PurePath('//a') / 'facts'` is `\\a\facts\`, not `\\a\facts`, because the join produces
327
+ * `//a\facts` and the RE-PARSE reads all of it as a share. Nothing else reproduces that.
328
+ */
329
+ export declare function ntJoin(...parts: string[]): string;
330
+ /**
331
+ * `PurePath._format_parsed_parts` under the Windows flavour — `str(PureWindowsPath)`.
332
+ *
333
+ * Exported, and every Windows arm below goes through it, so the flavour has exactly ONE
334
+ * implementation and the conformance suite can drive it against `PureWindowsPath` from a
335
+ * laptop instead of from a runner.
336
+ */
337
+ export declare function winFormat(drive: string, root: string, tail: readonly string[]): string;
338
+ /** `str(PureWindowsPath(path))`. */
339
+ export declare function winStr(path: string): string;
340
+ /** `PureWindowsPath.parents`. `//a/b` is a share and has NONE. */
341
+ export declare function winParents(path: string): string[];
342
+ /** `PureWindowsPath.name`. */
343
+ export declare function winName(path: string): string;
344
+ /** `PureWindowsPath.is_absolute()` — `bool(drive and root)`, so a bare `//a` is NOT one. */
345
+ export declare function winIsAbsolute(path: string): boolean;
346
+ /** `PureWindowsPath.with_suffix`, which rebuilds the path in the flavour's own spelling. */
347
+ export declare function winWithSuffix(path: string, suffix: string): string;
348
+ /**
349
+ * `PurePath.parents` — `_walk_to_store` iterates `(base, *base.parents)` and stops at `/`.
350
+ *
351
+ * The tail is not the anchor: `/a/b` yields `/a` then `/` and stops, while `a/b` yields `a`
352
+ * then `.` — a relative start walks to the CWD-relative dot and no further, which is why
353
+ * `_resolved_base` resolves before walking. `/` and `.` have no parents at all.
354
+ */
355
+ export declare function pyParents(path: string): string[];
356
+ /** `PurePath.parent`. `/`'s parent is `/` and `.`'s is `.`, as in Python. */
357
+ export declare function pyParent(path: string): string;
358
+ /** `PurePath.name` — the last component, or `''` for a bare anchor. */
359
+ export declare function pyName(path: string): string;
360
+ /**
361
+ * `PurePath.is_absolute()`.
362
+ *
363
+ * On Windows a root without a drive is NOT absolute (`PureWindowsPath('/a')` is relative to
364
+ * the current drive), which is why this is not "starts with a separator". `_pinned_store`
365
+ * raises on a relative pin, so getting this wrong there would accept a pin the reference
366
+ * refuses, or refuse one it accepts.
367
+ */
368
+ export declare function pyIsAbsolute(path: string): boolean;
369
+ /**
370
+ * `RuntimeError`, which `Path.expanduser()` and `Path.resolve()` raise. NOT an `OSError`, so
371
+ * nothing that catches `OSError` catches it — `_pinned_store`'s `except OSError` included.
372
+ */
373
+ export declare class PyRuntimeError extends Error {
374
+ constructor(message: string);
375
+ }
376
+ /**
377
+ * `Path.home()`, which is `os.path.expanduser('~')`.
378
+ *
379
+ * `$HOME` FIRST, and only then the passwd database — verified by overriding it. That order
380
+ * is what lets the tests and the conformance harness point the profile layer somewhere that
381
+ * is not the operator's own facts; a port that read the uid's home directly would recall
382
+ * against, and STAMP, the real store on every run.
383
+ *
384
+ * An empty `HOME` is still a set `HOME` in Python (the test is `'HOME' not in os.environ`),
385
+ * and `''.rstrip('/') or '/'` makes it `/`. `os.homedir()` falls back to the passwd entry
386
+ * there instead, so the rule is spelled out rather than delegated.
387
+ *
388
+ * On Windows Python reads `USERPROFILE`, then `HOMEDRIVE`+`HOMEPATH`; `os.homedir()` reads
389
+ * `USERPROFILE` and then the API. The first arm is the same, and it is the one a host sets.
390
+ */
391
+ export declare function pyHome(): string;
392
+ /**
393
+ * `Path(raw).expanduser()`.
394
+ *
395
+ * Only a FIRST component beginning with `~` expands, and only when the path has no anchor;
396
+ * `/~/x` and `a/~` are left alone. An MCP host passes `env` verbatim with no shell, so the
397
+ * tilde arrives literal and this is the only thing that expands it — see `_pinned_store`.
398
+ *
399
+ * RULING — `~someone-else`. Python asks the passwd database and resolves any account on the
400
+ * machine (measured: `~root` -> `/var/root`). Node has no `getpwnam`, so this resolves `~`
401
+ * and `~<the current user>` and raises Python's own `RuntimeError` for anything else, where
402
+ * Python would have answered a path. Reproducing it means shelling out to `dscl`/`getent` at
403
+ * runtime — a subprocess, in a pure-Node package, for a spelling of the pin nothing has ever
404
+ * used. Carried as a must-differ case in `tools/conformance/suites/recall-strings.mjs`, so
405
+ * it is re-measured on every run rather than remembered.
406
+ */
407
+ export declare function pyExpanduser(path: string): string;
408
+ /** `Path.cwd()`. */
409
+ export declare function pyCwd(): string;
410
+ /**
411
+ * `Path.resolve()` with `strict=False`: `posixpath.realpath`, then pathlib's ELOOP check.
412
+ *
413
+ * WHY NOT `fs.realpathSync`: it is strict. `_resolved_base` resolves a START DIRECTORY that
414
+ * may not exist, and `load_grants` resolves a granted path precisely in order to then ask
415
+ * whether it exists. Python resolves as far as the filesystem goes and appends the rest
416
+ * verbatim — measured, `<bed>/nope/x` resolves to itself, and so does `<0o000-dir>/x/y`.
417
+ *
418
+ * `..` is applied to the path resolved SO FAR, not lexically to the input, so through a
419
+ * symlinked directory it lands in the target's parent. That is what makes a symlinked
420
+ * worktree bind the store beside its real checkout.
421
+ *
422
+ * A symlink LOOP is the one failure that is not swallowed: non-strict `realpath` returns the
423
+ * unresolved path, `Path.resolve` then stats it, gets ELOOP, and raises
424
+ * `RuntimeError("Symlink loop from %r" % e.filename)`. The `%r` is `repr`, not a pair of
425
+ * apostrophes — the second spelling of the `OSError.__str__` bug N8 found, and fixed the
426
+ * same way. Measured against CPython here.
427
+ *
428
+ * WINDOWS IS AN APPROXIMATION AND SAYS SO. `ntpath.realpath` goes through
429
+ * `GetFinalPathNameByHandle` and returns forms this cannot reproduce; there this resolves
430
+ * what it can natively and falls back to the lexical form. NOT MEASURED — no Windows runner
431
+ * has run this port.
432
+ */
433
+ export declare function pyResolve(path: string): string;
434
+ /**
435
+ * `ntpath.split` — the head keeps its trailing separators only where they are the root.
436
+ *
437
+ * Read off `Lib/ntpath.py` at 3.12, which is the interpreter the reference side runs:
438
+ * `return d + r + head.rstrip(seps), tail`, with no fallback to the unstripped head.
439
+ */
440
+ export declare function ntSplit(path: string): [string, string];
441
+ /**
442
+ * `ntpath.isabs`, which is NOT `PureWindowsPath.is_absolute()`.
443
+ *
444
+ * `ntpath` calls `\a` absolute — a leading separator is enough — while pathlib calls it
445
+ * relative, because a rooted path with no drive is relative to the CURRENT drive. CPython's
446
+ * own source labels the difference a LEGACY BUG and keeps it. Both readings are right for
447
+ * their own caller, and
448
+ * `realpath` is `ntpath`'s caller: it prepends the cwd on `not isabs(path)`, so using
449
+ * pathlib's stricter test there would join the cwd onto a path Windows already resolves
450
+ * from the drive root.
451
+ */
452
+ export declare function ntIsAbs(path: string): boolean;
453
+ /**
454
+ * `Path.is_dir()` — and it RE-RAISES, which is the half that matters.
455
+ *
456
+ * `pathlib` swallows only `_IGNORED_ERRNOS`; `EACCES` is not one of them, so a candidate
457
+ * inside a directory the process cannot traverse raises `PermissionError` out of the WALK
458
+ * itself. MEASURED against CPython 3.12 here: `_walk_to_store` through a `0o000` ancestor
459
+ * raises rather than skipping the candidate, which refutes the claim in `_pinned_store`'s
460
+ * docstring that the walk "lives with" an unreadable candidate. `existsSync` and
461
+ * `statSync(p, {throwIfNoEntry: false})` both answer `false` there and would bind a
462
+ * different store in silence.
463
+ */
464
+ export declare function pyIsDir(path: string): boolean;
465
+ /** `Path.is_file()`, with the same swallow set as `pyIsDir`. */
466
+ export declare function pyIsFile(path: string): boolean;
467
+ /**
468
+ * `os.stat(p)` reduced to "is it a directory", unguarded.
469
+ *
470
+ * `_pinned_store` uses `os.stat` rather than `is_dir()` on purpose: a pin is a single path
471
+ * the operator named out loud, so an unreadable one gets the accurate reason instead of
472
+ * being reported as a typo.
473
+ */
474
+ export declare function pyStatIsDir(path: string): boolean;
475
+ /**
476
+ * `repr(str)`, which `_pinned_store` interpolates with `{raw!r}` for a relative pin.
477
+ *
478
+ * Python quotes with `'` unless the value contains a `'` and no `"`; it escapes `\\`, the
479
+ * quote, `\t`, `\n`, `\r`, and every codepoint that is not `str.isprintable()` — that is,
480
+ * everything in a `C*` category and every separator except the space itself. JS spells the
481
+ * same predicate `\p{C}` / `\p{Z}` under the `u` flag, so this is the rule rather than a
482
+ * table. The escapes are `\xNN` below U+0100, `\uNNNN` below U+10000, `\UNNNNNNNN` above.
483
+ *
484
+ * The one place it can drift is a codepoint whose category changed between CPython 3.12's
485
+ * Unicode 15.0 and the ICU this Node was built against: a newly assigned character is `Cn`
486
+ * (not printable, escaped) for Python and assigned (printable, raw) here. A pin path made of
487
+ * brand-new codepoints is the only input that reaches it.
488
+ */
489
+ export declare function pyRepr(value: string): string;
490
+ /**
491
+ * `repr(float)`.
492
+ *
493
+ * CPython formats with `PyOS_double_to_string(v, 'r', 0, Py_DTSF_ADD_DOT_0)`: the shortest
494
+ * decimal that round-trips, rendered in scientific notation when the decimal point falls at
495
+ * or left of position -4 or right of position 16, and with a forced `.0` otherwise. JS
496
+ * agrees on the DIGITS (`toExponential()` with no argument is also shortest-round-trip) and
497
+ * on nothing else: it switches to scientific at 1e21 and 1e-7, writes `10000000000000000`
498
+ * where Python writes `1e+16`, and never pads the exponent to two digits.
499
+ */
500
+ export declare function pyFloatRepr(x: number): string;
501
+ /**
502
+ * `PurePath.with_suffix(suffix)` on the LAST component.
503
+ *
504
+ * `_write_fact` writes `path.with_suffix(".md.tmp")`, and the rule is not "append": a name
505
+ * whose last component already has a suffix has that suffix REPLACED, while a component
506
+ * that is all leading dots has one appended. `facts/a.b.md` -> `facts/a.b.md.tmp`, and the
507
+ * degenerate `facts/.md` (a fact whose frontmatter name is empty) -> `facts/.md.md.tmp`.
508
+ */
509
+ export declare function pyWithSuffix(path: string, suffix: string): string;
510
+ /**
511
+ * `PurePath.suffix` as a value, which `shiftwork.clock_out` needs on its own:
512
+ * `path.with_suffix(path.suffix + ".tmp")` turns `cp.json` into `cp.json.tmp` and a
513
+ * suffix-less `cp` into `cp.tmp`, and the two arms are only distinguishable by reading the
514
+ * suffix first.
515
+ */
516
+ export declare function pySuffix(path: string): string;
517
+ /** `Path.unlink()` and `Path.replace(target)`, with CPython's error text. */
518
+ export declare function pyUnlink(path: string): void;
519
+ export declare function pyReplace(source: string, target: string): void;
520
+ /** `date.fromtimestamp(path.stat().st_mtime).isoformat()` — LOCAL, like `date.today()`. */
521
+ export declare function pyMtimeDate(path: string): string;