brain-framework 18.1.2__tar.gz → 18.1.4__tar.gz
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.
- {brain_framework-18.1.2 → brain_framework-18.1.4}/PKG-INFO +5 -5
- {brain_framework-18.1.2 → brain_framework-18.1.4}/README.md +4 -4
- {brain_framework-18.1.2 → brain_framework-18.1.4}/pyproject.toml +3 -1
- {brain_framework-18.1.2 → brain_framework-18.1.4}/pyproject.toml.orig +4 -1
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/__init__.py +1 -1
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/cli.py +12 -3
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/config.py +55 -36
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/health.py +4 -5
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/index.py +6 -3
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/markdown.py +4 -1
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/mcp.py +2 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/models.py +6 -3
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/pages.py +3 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/retrieve.py +2 -2
- {brain_framework-18.1.2 → brain_framework-18.1.4}/LICENSE +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/THIRD_PARTY_NOTICES.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/__main__.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/collect.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/evaluate.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/graph.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/history.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/install.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/links.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/ontology.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/py.typed +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/records.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/replies.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/schedule.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/schemas.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/README.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-maintain/SKILL.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-maintain/references/conflicts.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-maintain/references/integrations.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-maintain/references/operations.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-setup/SKILL.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-setup/references/discovery.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-setup/references/import.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-setup/references/scan.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-setup/scripts/inventory.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/SKILL.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/actions.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/consolidate.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/context.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/decisions.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/evidence.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/handoff.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/learn.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/links.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/retrieval.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/review.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/share.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/scripts/check-handoff.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/scripts/evidence.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/scripts/guarded-write.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/scripts/new-action.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/templates/action.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/templates/concept.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/templates/project.md +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/storage.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/update.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/usage.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/validate.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/watch.py +0 -0
- {brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/watch_settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brain-framework
|
|
3
|
-
Version: 18.1.
|
|
3
|
+
Version: 18.1.4
|
|
4
4
|
Summary: 🧠 Brain Framework: from information to informed actions.
|
|
5
5
|
Keywords: ai,ai-agents,agent-memory,context-engineering,knowledge-management,local-first,mcp,model-context-protocol,python,second-brain
|
|
6
6
|
Author: Médéric Hurier
|
|
@@ -29,7 +29,7 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
<p align="center">
|
|
30
30
|
<img
|
|
31
31
|
src="https://raw.githubusercontent.com/fmind/brain-framework/main/docs/assets/brain-framework.svg"
|
|
32
|
-
alt="Brain Framework:
|
|
32
|
+
alt="Brain Framework: a robot head whose shell opens on a brain, above twin aperture eyes"
|
|
33
33
|
width="256"
|
|
34
34
|
height="256"
|
|
35
35
|
>
|
|
@@ -62,7 +62,7 @@ Brain Framework (BF) changes the starting point. Create a brain, a plain folder,
|
|
|
62
62
|
<a href="https://raw.githubusercontent.com/fmind/brain-framework/main/docs/assets/blank-vs-brain.svg">
|
|
63
63
|
<img
|
|
64
64
|
src="https://raw.githubusercontent.com/fmind/brain-framework/main/docs/assets/blank-vs-brain.svg"
|
|
65
|
-
alt="Two fictional sessions ask whether the new website can launch. Opened anywhere, the agent asks you to paste Jira, GitHub and Google Cloud. Opened in a brain, it reads project:new-website and answers: not yet, Jira still blocks launch on accessibility review, citing each record."
|
|
65
|
+
alt="Two fictional sessions ask whether the new website can launch. Opened anywhere, the agent asks you to paste Jira, GitHub and Google Cloud. Opened in a brain, it reads project:new-website and answers: not yet, Jira still blocks launch on accessibility review, citing each record, then notes the launch hold in the project note."
|
|
66
66
|
width="960"
|
|
67
67
|
>
|
|
68
68
|
</a>
|
|
@@ -181,7 +181,7 @@ Reading files gives you one document at a time, and grep finds words, not connec
|
|
|
181
181
|
bf read project:new-website
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
The reply holds the project note and its backlinks, grouped by relation: four records from four tools, side by side, in the shared vocabulary (trimmed here to refs and
|
|
184
|
+
The reply holds the project note and its backlinks, grouped by relation: four records from four tools, side by side, in the shared vocabulary (trimmed here to refs, kinds and statuses):
|
|
185
185
|
|
|
186
186
|
```json
|
|
187
187
|
{
|
|
@@ -308,7 +308,7 @@ After reviewing your sensors, run `bf watch` to keep them refreshed and see fail
|
|
|
308
308
|
<img
|
|
309
309
|
src="https://raw.githubusercontent.com/fmind/brain-framework/main/docs/assets/watch.svg"
|
|
310
310
|
alt="Watch dashboard for the fictional offline demo: five sensors sorted by state, with a failed sensor's details beside their last success, next due time, item count and record changes."
|
|
311
|
-
width="
|
|
311
|
+
width="960"
|
|
312
312
|
>
|
|
313
313
|
</a>
|
|
314
314
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<img
|
|
3
3
|
src="https://raw.githubusercontent.com/fmind/brain-framework/main/docs/assets/brain-framework.svg"
|
|
4
|
-
alt="Brain Framework:
|
|
4
|
+
alt="Brain Framework: a robot head whose shell opens on a brain, above twin aperture eyes"
|
|
5
5
|
width="256"
|
|
6
6
|
height="256"
|
|
7
7
|
>
|
|
@@ -34,7 +34,7 @@ Brain Framework (BF) changes the starting point. Create a brain, a plain folder,
|
|
|
34
34
|
<a href="https://raw.githubusercontent.com/fmind/brain-framework/main/docs/assets/blank-vs-brain.svg">
|
|
35
35
|
<img
|
|
36
36
|
src="https://raw.githubusercontent.com/fmind/brain-framework/main/docs/assets/blank-vs-brain.svg"
|
|
37
|
-
alt="Two fictional sessions ask whether the new website can launch. Opened anywhere, the agent asks you to paste Jira, GitHub and Google Cloud. Opened in a brain, it reads project:new-website and answers: not yet, Jira still blocks launch on accessibility review, citing each record."
|
|
37
|
+
alt="Two fictional sessions ask whether the new website can launch. Opened anywhere, the agent asks you to paste Jira, GitHub and Google Cloud. Opened in a brain, it reads project:new-website and answers: not yet, Jira still blocks launch on accessibility review, citing each record, then notes the launch hold in the project note."
|
|
38
38
|
width="960"
|
|
39
39
|
>
|
|
40
40
|
</a>
|
|
@@ -153,7 +153,7 @@ Reading files gives you one document at a time, and grep finds words, not connec
|
|
|
153
153
|
bf read project:new-website
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
The reply holds the project note and its backlinks, grouped by relation: four records from four tools, side by side, in the shared vocabulary (trimmed here to refs and
|
|
156
|
+
The reply holds the project note and its backlinks, grouped by relation: four records from four tools, side by side, in the shared vocabulary (trimmed here to refs, kinds and statuses):
|
|
157
157
|
|
|
158
158
|
```json
|
|
159
159
|
{
|
|
@@ -280,7 +280,7 @@ After reviewing your sensors, run `bf watch` to keep them refreshed and see fail
|
|
|
280
280
|
<img
|
|
281
281
|
src="https://raw.githubusercontent.com/fmind/brain-framework/main/docs/assets/watch.svg"
|
|
282
282
|
alt="Watch dashboard for the fictional offline demo: five sensors sorted by state, with a failed sensor's details beside their last success, next due time, item count and record changes."
|
|
283
|
-
width="
|
|
283
|
+
width="960"
|
|
284
284
|
>
|
|
285
285
|
</a>
|
|
286
286
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "brain-framework"
|
|
3
|
-
version = "18.1.
|
|
3
|
+
version = "18.1.4"
|
|
4
4
|
description = "🧠 Brain Framework: from information to informed actions."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -146,6 +146,8 @@ skip_empty = true
|
|
|
146
146
|
[dependency-groups]
|
|
147
147
|
dev = [
|
|
148
148
|
"jsonschema>=4.26.0,<5",
|
|
149
|
+
"markdown>=3.11",
|
|
150
|
+
"packaging>=26.3",
|
|
149
151
|
"pytest-cov>=7.1.0",
|
|
150
152
|
"pytest-watcher>=0.6.3",
|
|
151
153
|
"pytest>=9.1.1",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# https://packaging.python.org/en/latest/specifications/pyproject-toml/
|
|
2
2
|
[project]
|
|
3
3
|
name = "brain-framework"
|
|
4
|
-
version = "18.1.
|
|
4
|
+
version = "18.1.4"
|
|
5
5
|
description = "🧠 Brain Framework: from information to informed actions."
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.14"
|
|
@@ -56,6 +56,9 @@ module-name = "bf"
|
|
|
56
56
|
[dependency-groups]
|
|
57
57
|
dev = [
|
|
58
58
|
"jsonschema>=4.26.0,<5",
|
|
59
|
+
# Imported directly by tests/test_readme.py, as Zensical renders headings, and scripts/notices.py.
|
|
60
|
+
"markdown>=3.11",
|
|
61
|
+
"packaging>=26.3",
|
|
59
62
|
"pytest-cov>=7.1.0",
|
|
60
63
|
"pytest-watcher>=0.6.3",
|
|
61
64
|
"pytest>=9.1.1",
|
|
@@ -591,6 +591,8 @@ def perform(
|
|
|
591
591
|
if hook:
|
|
592
592
|
_option("--hook", _slug, hook)
|
|
593
593
|
names, args = (), values
|
|
594
|
+
elif values and not values[0].strip():
|
|
595
|
+
raise typer.BadParameter("give a routine name; an empty value names none", param_hint="ROUTINE")
|
|
594
596
|
elif values:
|
|
595
597
|
names, args = values[:1], values[1:]
|
|
596
598
|
else:
|
|
@@ -855,14 +857,20 @@ def _input() -> bytes:
|
|
|
855
857
|
return data
|
|
856
858
|
|
|
857
859
|
|
|
858
|
-
# Values that choose where a command acts, as help names them. Given empty, as an unset variable gives
|
|
859
|
-
# would silently choose another brain or
|
|
860
|
+
# Values that choose where or on what a command acts, as help names them. Given empty, as an unset variable gives
|
|
861
|
+
# them, they would silently choose another brain, directory, window or operation, so they are invalid input; an
|
|
862
|
+
# omitted one keeps its default.
|
|
860
863
|
_GIVEN = {
|
|
861
864
|
"--brain": "give a brain name or path; an empty value would select another brain",
|
|
862
865
|
"--hook": "give an event name; an empty value would run the first argument as a routine",
|
|
863
866
|
"--output": "give a directory; an empty value would name the brain's root",
|
|
867
|
+
"--reproject": "give a sensor name; an empty value would rebuild the cache instead",
|
|
868
|
+
"--routine": "give a routine name; an empty value names none",
|
|
869
|
+
"--sensor": "give a sensor name; an empty value names none",
|
|
870
|
+
"--since": "give a start time; an empty value would collect the default window",
|
|
864
871
|
"DIR": "give a directory; an empty value would name the working directory",
|
|
865
872
|
"PATH": "give a directory; an empty value would name the working directory",
|
|
873
|
+
"SENSOR": "give a sensor name from bf.yaml",
|
|
866
874
|
}
|
|
867
875
|
|
|
868
876
|
|
|
@@ -903,7 +911,8 @@ class _Command(TyperCommand):
|
|
|
903
911
|
for param in self.params:
|
|
904
912
|
value = given.get(param.name)
|
|
905
913
|
reason = _GIVEN.get(_named(param.get_error_hint(ctx)))
|
|
906
|
-
|
|
914
|
+
values = value if isinstance(value, list) else [value]
|
|
915
|
+
if reason and any(isinstance(item, str) and not item.strip() for item in values):
|
|
907
916
|
return typer.BadParameter(reason, ctx, param)
|
|
908
917
|
return None
|
|
909
918
|
|
|
@@ -212,20 +212,17 @@ def register(store: Store) -> dict[str, object]:
|
|
|
212
212
|
replaced = ""
|
|
213
213
|
if (existing := registry.brains.get(name)) and existing.path != str(store.root):
|
|
214
214
|
try:
|
|
215
|
-
registered =
|
|
215
|
+
registered = _registered(existing)
|
|
216
216
|
except Error as error:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
f"another brain is already registered as {name} at {existing.path}; remove that entry from "
|
|
227
|
-
f"{path}, or rename one of the brains in its bf.yaml"
|
|
228
|
-
)
|
|
217
|
+
raise Error(
|
|
218
|
+
f"cannot reach the brain registered as {name} at {existing.path}; restore access to it, or "
|
|
219
|
+
f"remove that entry from {path}"
|
|
220
|
+
) from error
|
|
221
|
+
if registered is not None and registered.identity != store.identity:
|
|
222
|
+
raise Error(
|
|
223
|
+
f"another brain is already registered as {name} at {existing.path}; remove that entry from "
|
|
224
|
+
f"{path}, or rename one of the brains in its bf.yaml"
|
|
225
|
+
)
|
|
229
226
|
replaced = existing.path
|
|
230
227
|
registry.brains[name] = Registration(path=str(store.root))
|
|
231
228
|
document = {"brains": {key: value.model_dump() for key, value in sorted(registry.brains.items())}}
|
|
@@ -254,12 +251,30 @@ ABSENT = (
|
|
|
254
251
|
)
|
|
255
252
|
|
|
256
253
|
|
|
254
|
+
UNREACHABLE = "registered brain directory exists but cannot be reached; restore access to it"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _registered(entry: Registration) -> Store | None:
|
|
258
|
+
"""A registered brain, or None when its directory is absent: missing, or no longer a directory.
|
|
259
|
+
|
|
260
|
+
One that exists but cannot be reached, such as a locked folder or a disconnected mount, may still hold that
|
|
261
|
+
brain and return: it fails with UNREACHABLE, so neither selection nor bf register treats it as moved.
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
return Store(Path(entry.path).expanduser())
|
|
265
|
+
except Error as error:
|
|
266
|
+
cause = error.__cause__
|
|
267
|
+
if isinstance(cause, OSError) and not isinstance(cause, FileNotFoundError | NotADirectoryError):
|
|
268
|
+
raise Error(UNREACHABLE) from cause
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
|
|
257
272
|
class Selection(list[Store]):
|
|
258
|
-
"""Selected roots, plus
|
|
273
|
+
"""Selected roots, plus each registered brain this machine cannot use, by name, with its error."""
|
|
259
274
|
|
|
260
|
-
def __init__(self, stores: Iterable[Store] = (),
|
|
275
|
+
def __init__(self, stores: Iterable[Store] = (), unavailable: dict[str, str] | None = None) -> None:
|
|
261
276
|
super().__init__(stores)
|
|
262
|
-
self.
|
|
277
|
+
self.unavailable = unavailable or {}
|
|
263
278
|
|
|
264
279
|
|
|
265
280
|
def related(roots: list[Store]) -> tuple[list[Store], list[dict[str, object]]]:
|
|
@@ -272,7 +287,8 @@ def related(roots: list[Store]) -> tuple[list[Store], list[dict[str, object]]]:
|
|
|
272
287
|
candidates.setdefault(store.identity, store)
|
|
273
288
|
names: dict[str, set[str]] = {}
|
|
274
289
|
problems: list[dict[str, object]] = [
|
|
275
|
-
{"brain": name, "error":
|
|
290
|
+
{"brain": name, "error": error}
|
|
291
|
+
for name, error in (roots.unavailable.items() if isinstance(roots, Selection) else ())
|
|
276
292
|
]
|
|
277
293
|
for store in roots:
|
|
278
294
|
try:
|
|
@@ -342,14 +358,11 @@ def _located(value: str, *, execute: bool = False) -> Store:
|
|
|
342
358
|
local, referenced = _claim(value, registered=entry is not None)
|
|
343
359
|
if entry is not None:
|
|
344
360
|
try:
|
|
345
|
-
store =
|
|
346
|
-
except Error
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
):
|
|
351
|
-
raise Error(f"cannot reach the brain registered as {value}; restore access to its directory") from None
|
|
352
|
-
raise Error(f"{value}: {ABSENT}") from None
|
|
361
|
+
store = _registered(entry)
|
|
362
|
+
except Error:
|
|
363
|
+
raise Error(f"cannot reach the brain registered as {value}; restore access to its directory") from None
|
|
364
|
+
if store is None:
|
|
365
|
+
raise Error(f"{value}: {ABSENT}")
|
|
353
366
|
if local is not None and local.identity != store.identity:
|
|
354
367
|
raise Error(
|
|
355
368
|
f"ambiguous brain name {value}: the registry and the enclosing brain name different directories; "
|
|
@@ -405,26 +418,32 @@ def select(value: str = "") -> list[Store]:
|
|
|
405
418
|
return [_located(chosen)]
|
|
406
419
|
if nearest := _nearest():
|
|
407
420
|
return [nearest]
|
|
408
|
-
# Retrieval reports registered brains
|
|
409
|
-
stores
|
|
421
|
+
# Retrieval reports registered brains this machine cannot use instead of silently answering without them.
|
|
422
|
+
stores: list[Store] = []
|
|
423
|
+
names: list[str] = []
|
|
424
|
+
unavailable: dict[str, str] = {}
|
|
410
425
|
for name, entry in sorted(user_config().brains.items()):
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
426
|
+
try:
|
|
427
|
+
store = _registered(entry)
|
|
428
|
+
except Error as error:
|
|
429
|
+
unavailable[name] = str(error)
|
|
430
|
+
continue
|
|
431
|
+
if store is None:
|
|
432
|
+
unavailable[name] = ABSENT
|
|
415
433
|
else:
|
|
416
|
-
|
|
417
|
-
|
|
434
|
+
stores.append(store)
|
|
435
|
+
names.append(name)
|
|
436
|
+
if unavailable and not stores:
|
|
418
437
|
raise Error(
|
|
419
|
-
f"registered brains are
|
|
420
|
-
"their new place, or remove their registry entries"
|
|
438
|
+
f"registered brains are unavailable on this machine: {', '.join(unavailable)}; restore them or access to "
|
|
439
|
+
"them, run bf register at their new place, or remove their registry entries"
|
|
421
440
|
)
|
|
422
441
|
if not stores:
|
|
423
442
|
raise Error("no brain selected; pass --brain, run inside a brain, or register one with bf register")
|
|
424
443
|
# The only registered brain may have lost its bf.yaml: name that before a command reads or caches it. Of several,
|
|
425
444
|
# retrieval reports each one whose bf.yaml does not load.
|
|
426
445
|
only = [_configured(stores[0], f"registered brain {names[0]}")] if len(stores) == 1 else stores
|
|
427
|
-
return Selection(only,
|
|
446
|
+
return Selection(only, unavailable)
|
|
428
447
|
|
|
429
448
|
|
|
430
449
|
def brain_name(store: Store) -> str:
|
|
@@ -8,7 +8,7 @@ from datetime import UTC, datetime, timedelta
|
|
|
8
8
|
from typing import cast
|
|
9
9
|
|
|
10
10
|
from bf import index, usage
|
|
11
|
-
from bf.config import
|
|
11
|
+
from bf.config import Selection, brain_name, load, related
|
|
12
12
|
from bf.history import ROUTINES, log_path, state
|
|
13
13
|
from bf.models import CACHE, Config, Error, Program
|
|
14
14
|
from bf.storage import Store
|
|
@@ -126,9 +126,8 @@ def attention(store: Store, now: datetime | None = None) -> list[dict[str, objec
|
|
|
126
126
|
def report(stores: list[Store], now: datetime | None = None) -> dict[str, object]:
|
|
127
127
|
"""Per brain: cache, notes, records, source and routine freshness, errors, logs and usage."""
|
|
128
128
|
now = now or datetime.now(UTC)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
]
|
|
129
|
+
unavailable = stores.unavailable if isinstance(stores, Selection) else {}
|
|
130
|
+
brains: list[dict[str, object]] = [{"brain": name, "error": error} for name, error in unavailable.items()]
|
|
132
131
|
healthy = not brains
|
|
133
132
|
for store in stores:
|
|
134
133
|
try:
|
|
@@ -185,7 +184,7 @@ def report(stores: list[Store], now: datetime | None = None) -> dict[str, object
|
|
|
185
184
|
)
|
|
186
185
|
# A reference that retrieval cannot include makes every search and read incomplete, like a skipped file.
|
|
187
186
|
for problem in related(stores)[1]:
|
|
188
|
-
if problem["
|
|
187
|
+
if problem["brain"] in unavailable:
|
|
189
188
|
continue
|
|
190
189
|
healthy = False
|
|
191
190
|
owners = [entry for entry in brains if entry["brain"] == problem["brain"] and "problems" in entry]
|
|
@@ -39,7 +39,7 @@ from bf.models import (
|
|
|
39
39
|
)
|
|
40
40
|
from bf.storage import UNNAMED, BusyError, Store, building, generation, reader, unnamed, writer
|
|
41
41
|
|
|
42
|
-
SCHEMA =
|
|
42
|
+
SCHEMA = 33
|
|
43
43
|
CACHE = ".bf/index.sqlite"
|
|
44
44
|
# A full build fills this file beside the live cache, then renames it over CACHE.
|
|
45
45
|
BUILD = CACHE + ".new"
|
|
@@ -1060,9 +1060,10 @@ def _verbatim(snippet: str, original: str) -> str:
|
|
|
1060
1060
|
"""The original words behind a snippet of a folded passage, so `m²` or `…` never reads as `m2` or `...`.
|
|
1061
1061
|
|
|
1062
1062
|
Folding each whitespace-separated run yields the folded passage, so every snippet position maps to one run.
|
|
1063
|
+
ASCII is already folded, which keeps a large passage with a single `²` or no-break space fast.
|
|
1063
1064
|
"""
|
|
1064
1065
|
runs = re.findall(r"\s+|\S+", original)
|
|
1065
|
-
forms = [fold(run) for run in runs]
|
|
1066
|
+
forms = [run if run.isascii() else fold(run) for run in runs]
|
|
1066
1067
|
head = _ELLIPSIS if snippet.startswith(_ELLIPSIS) else ""
|
|
1067
1068
|
tail = _ELLIPSIS if snippet.endswith(_ELLIPSIS) else ""
|
|
1068
1069
|
window = snippet.removeprefix(head).removesuffix(tail)
|
|
@@ -1071,7 +1072,9 @@ def _verbatim(snippet: str, original: str) -> str:
|
|
|
1071
1072
|
return snippet
|
|
1072
1073
|
end, position, kept = start + len(window), 0, []
|
|
1073
1074
|
for run, form in zip(runs, forms, strict=True):
|
|
1074
|
-
if
|
|
1075
|
+
if position >= end:
|
|
1076
|
+
break
|
|
1077
|
+
if start < position + len(form):
|
|
1075
1078
|
kept.append(run)
|
|
1076
1079
|
position += len(form)
|
|
1077
1080
|
return head + "".join(kept) + tail
|
|
@@ -457,9 +457,12 @@ def note(path: str, data: bytes | Markdown) -> Note:
|
|
|
457
457
|
# H2+ sections are separate passages, so a search can land on the answering section.
|
|
458
458
|
# Headings rank through each passage's title; its text, and so its excerpt, starts below them.
|
|
459
459
|
# A section also ranks under its parents: `## Vega` then `### Budget` answers "Vega budget".
|
|
460
|
+
# An H1 after the first section, as in a copied document, is one too, even the title's: it ends the section
|
|
461
|
+
# above it, as an exact read of that section's ref does, so search never lands on a section without the match.
|
|
460
462
|
source_lines = lines(markdown.text)
|
|
461
|
-
sections = [h for h in markdown.headings if h.level >= 2]
|
|
462
463
|
titled = next((h for h in markdown.headings if h.level == 1 and h.title == title), None)
|
|
464
|
+
first = next((n for n, h in enumerate(markdown.headings) if h.level >= 2), len(markdown.headings))
|
|
465
|
+
sections = markdown.headings[first:]
|
|
463
466
|
hidden = range(titled.line, titled.end) if titled else range(0)
|
|
464
467
|
opening = range(markdown.offset, sections[0].line if sections else len(source_lines))
|
|
465
468
|
introduction = "".join(source_lines[n] for n in opening if n not in hidden)
|
|
@@ -193,6 +193,8 @@ def server(stores: list[Store] | Callable[[], list[Store]]) -> MCPServer:
|
|
|
193
193
|
title="Brain Framework",
|
|
194
194
|
version=__version__,
|
|
195
195
|
instructions=INSTRUCTIONS,
|
|
196
|
+
# The SDK logs each rejected or failed call at INFO on stderr, in its own format: replies already carry them.
|
|
197
|
+
log_level="WARNING",
|
|
196
198
|
tools=[
|
|
197
199
|
_strict(Tool.from_function(function, name=name, title=title, annotations=annotations), output)
|
|
198
200
|
for name, title, function, output in (
|
|
@@ -452,9 +452,12 @@ class Record(Model):
|
|
|
452
452
|
return value
|
|
453
453
|
|
|
454
454
|
def readable(self) -> Record:
|
|
455
|
-
"""An exact read pages only `text`: everything else must fit its first page within the reply limit.
|
|
456
|
-
|
|
457
|
-
|
|
455
|
+
"""An exact read pages only `text`: everything else must fit its first page within the reply limit.
|
|
456
|
+
|
|
457
|
+
Counted in encoded bytes, as the reply limit is: non-ASCII or escaped characters take more than one.
|
|
458
|
+
"""
|
|
459
|
+
others = [self.id, self.title, self.url, self.links, self.aliases, self.attributes, self.fields]
|
|
460
|
+
if len(encode(others)) > MAX_FIELDS:
|
|
458
461
|
raise ValueError("fields other than text exceed 2 MiB; keep bulky content in text")
|
|
459
462
|
return self
|
|
460
463
|
|
|
@@ -102,6 +102,9 @@ def period(value: str, now: datetime | None = None) -> Period | None:
|
|
|
102
102
|
if value in {"today", "yesterday"}:
|
|
103
103
|
day = now.date() - timedelta(days=value == "yesterday")
|
|
104
104
|
elif value[-1] in "hdw":
|
|
105
|
+
# An empty window holds nothing: `0d` would be a page no search scope could match.
|
|
106
|
+
if not int(value[:-1]):
|
|
107
|
+
raise ValueError
|
|
105
108
|
start = now - timedelta(hours=int(value[:-1]) * {"h": 1, "d": 24, "w": 168}[value[-1]])
|
|
106
109
|
return Period(timestamp(start.isoformat()), timestamp(now.isoformat()))
|
|
107
110
|
elif ".." in value:
|
|
@@ -406,8 +406,8 @@ def _holders(
|
|
|
406
406
|
for store in selected:
|
|
407
407
|
try:
|
|
408
408
|
value = _read(store, ref)
|
|
409
|
-
except InputError
|
|
410
|
-
# Malformed input
|
|
409
|
+
except InputError:
|
|
410
|
+
# Malformed input is the ref's problem, not a brain's. A copy without the section is that brain's.
|
|
411
411
|
raise
|
|
412
412
|
except (Error, OSError, sqlite3.DatabaseError) as error:
|
|
413
413
|
if len(selected) == 1:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-maintain/references/conflicts.md
RENAMED
|
File without changes
|
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-maintain/references/operations.md
RENAMED
|
File without changes
|
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-setup/references/discovery.md
RENAMED
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-setup/references/import.md
RENAMED
|
File without changes
|
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-setup/scripts/inventory.py
RENAMED
|
File without changes
|
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/actions.md
RENAMED
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/consolidate.md
RENAMED
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/context.md
RENAMED
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/decisions.md
RENAMED
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/evidence.md
RENAMED
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/handoff.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/references/retrieval.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/scripts/check-handoff.py
RENAMED
|
File without changes
|
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/scripts/guarded-write.py
RENAMED
|
File without changes
|
{brain_framework-18.1.2 → brain_framework-18.1.4}/src/bf/skills/bf-use/scripts/new-action.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|