brainkeeper 0.2.2__tar.gz → 0.3.0__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.
Files changed (70) hide show
  1. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/CHANGELOG.md +14 -0
  2. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/PKG-INFO +16 -9
  3. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/README.md +15 -8
  4. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/pyproject.toml +1 -1
  5. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/__init__.py +1 -1
  6. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/cli/__init__.py +13 -15
  7. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/cli/init.py +12 -5
  8. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/cli/serve.py +5 -2
  9. brainkeeper-0.3.0/tests/unit/test_cli.py +45 -0
  10. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/uv.lock +1 -1
  11. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/.github/workflows/ci.yml +0 -0
  12. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/.github/workflows/release.yml +0 -0
  13. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/.gitignore +0 -0
  14. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/LICENSE +0 -0
  15. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/docs/branding/architecture.jpg +0 -0
  16. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/docs/branding/logo.svg +0 -0
  17. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/docs/branding/palette.md +0 -0
  18. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/docs/design.md +0 -0
  19. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/docs/plans/2026-04-24-brainkeeper-spec.md +0 -0
  20. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/docs/plans/2026-04-24-vault-remediation.md +0 -0
  21. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/docs/plans/2026-04-27-mcp-core.md +0 -0
  22. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/docs/plans/vault-audit-summary-2026-04-24.md +0 -0
  23. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/README.md +0 -0
  24. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/SPEC.md +0 -0
  25. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/examples/.gitkeep +0 -0
  26. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/examples/.invalid/README.md +0 -0
  27. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/examples/.invalid/absolute-path.yaml +0 -0
  28. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/examples/.invalid/unknown-root-key.yaml +0 -0
  29. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/examples/daniels-vault.yaml +0 -0
  30. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/examples/minimal.yaml +0 -0
  31. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/examples/zettelkasten.yaml +0 -0
  32. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/spec/schema/brainkeeper.schema.json +0 -0
  33. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/__main__.py +0 -0
  34. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/core/__init__.py +0 -0
  35. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/core/config.py +0 -0
  36. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/core/frontmatter.py +0 -0
  37. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/core/fs.py +0 -0
  38. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/core/index.py +0 -0
  39. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/core/rescanner.py +0 -0
  40. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/core/watcher.py +0 -0
  41. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/mcp/__init__.py +0 -0
  42. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/mcp/prompts/__init__.py +0 -0
  43. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/mcp/prompts/triage.py +0 -0
  44. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/mcp/server.py +0 -0
  45. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/mcp/tools/__init__.py +0 -0
  46. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/mcp/tools/convention.py +0 -0
  47. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/mcp/tools/primitives.py +0 -0
  48. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/mcp/tools/semantic.py +0 -0
  49. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/src/brainkeeper/spec/__init__.py +0 -0
  50. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/conftest.py +0 -0
  51. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/fixtures/minimal-vault/00 Inbox/.gitkeep +0 -0
  52. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/fixtures/minimal-vault/10 Journal/.gitkeep +0 -0
  53. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/fixtures/minimal-vault/20 Projects/.gitkeep +0 -0
  54. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/fixtures/minimal-vault/30 Areas/.gitkeep +0 -0
  55. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/fixtures/minimal-vault/40 Brain/.gitkeep +0 -0
  56. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/fixtures/minimal-vault/90 Archive/.gitkeep +0 -0
  57. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/fixtures/minimal-vault/brainkeeper.yaml +0 -0
  58. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/integration/test_convention.py +0 -0
  59. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/integration/test_primitives.py +0 -0
  60. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/integration/test_prompts.py +0 -0
  61. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/integration/test_semantic.py +0 -0
  62. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/unit/test_config.py +0 -0
  63. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/unit/test_frontmatter.py +0 -0
  64. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/unit/test_fs.py +0 -0
  65. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/unit/test_index.py +0 -0
  66. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/unit/test_rescanner.py +0 -0
  67. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/unit/test_server.py +0 -0
  68. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tests/unit/test_watcher.py +0 -0
  69. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tools/__init__.py +0 -0
  70. {brainkeeper-0.2.2 → brainkeeper-0.3.0}/tools/audit_vault.py +0 -0
@@ -9,6 +9,19 @@ Two artifacts are versioned independently:
9
9
  - **`spec-vX.Y.Z`**: the brainkeeper specification (`spec/`).
10
10
  - **`brainkeeper-vX.Y.Z`**: the `brainkeeper` Python package, which contains the vault engine library, the MCP server, and the CLI.
11
11
 
12
+ ## [brainkeeper-v0.3.0] - 2026-07-06
13
+
14
+ > ⚠️ **Breaking release.** The vault location changes: it is now fixed at `~/.brainkeeper/vault`, deliberately not configurable. Existing vaults must be moved into place and `--vault` flags removed (see Migration below).
15
+
16
+ ### Changed
17
+ - **Breaking:** `serve` loses `--vault`. The server always serves `~/.brainkeeper/vault`; if it doesn't exist, `serve` exits with an error pointing at `brainkeeper init`.
18
+ - **Breaking:** `init` loses its `path` positional. It always scaffolds `~/.brainkeeper/vault`.
19
+ - New `brainkeeper.cli.vault_path()` helper: the single source of truth for the fixed vault location.
20
+
21
+ ### Migration
22
+ - Move any existing vault into place instead of running `init` (the target must not exist yet): `mkdir -p ~/.brainkeeper && mv ~/MyVault ~/.brainkeeper/vault`.
23
+ - Remove `--vault` from every MCP client entry that references `brainkeeper serve`. The command is now just `uvx brainkeeper serve`.
24
+
12
25
  ## [brainkeeper-v0.2.2] - 2026-05-05
13
26
 
14
27
  ### Fixed
@@ -136,6 +149,7 @@ First public release of the `brainkeeper` Python package. Implements spec v0.1.4
136
149
  - Archive semantics narrowed to completed projects only; retired Areas are deleted or distilled into Brain, not archived.
137
150
  - Domain tags: cardinality relaxed to 0..n (optional but recommended); vocabulary derived from folders under `projects/` and `areas/` rather than an enumerated list in the config.
138
151
 
152
+ [brainkeeper-v0.3.0]: https://github.com/dasirra/brainkeeper/releases/tag/brainkeeper-v0.3.0
139
153
  [brainkeeper-v0.2.2]: https://github.com/dasirra/brainkeeper/releases/tag/brainkeeper-v0.2.2
140
154
  [brainkeeper-v0.2.1]: https://github.com/dasirra/brainkeeper/releases/tag/brainkeeper-v0.2.1
141
155
  [spec-v0.2.0]: https://github.com/dasirra/brainkeeper/releases/tag/spec-v0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainkeeper
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Structured Markdown vault standard with a Python vault engine and MCP interface
5
5
  Project-URL: Homepage, https://github.com/dasirra/brainkeeper
6
6
  Project-URL: Repository, https://github.com/dasirra/brainkeeper
@@ -94,15 +94,22 @@ Either way, you need Python 3.11 or newer.
94
94
  If you don't already have a brainkeeper vault, create one:
95
95
 
96
96
  ```bash
97
- uvx brainkeeper init ~/MyVault
97
+ uvx brainkeeper init
98
98
  ```
99
99
 
100
- This creates the six layer directories and drops a `brainkeeper.yaml` (the minimal reference config) at the vault root. Open the YAML to adjust folder names and area substructure to taste. Validate against the schema at any time:
100
+ This scaffolds the vault at `~/.brainkeeper/vault`: the six layer directories plus a `brainkeeper.yaml` (the minimal reference config) at the vault root. Open the YAML to adjust folder names and area substructure to taste. Validate against the schema at any time:
101
101
 
102
102
  ```bash
103
103
  uvx check-jsonschema \
104
104
  --schemafile https://raw.githubusercontent.com/dasirra/brainkeeper/main/spec/schema/brainkeeper.schema.json \
105
- ~/MyVault/brainkeeper.yaml
105
+ ~/.brainkeeper/vault/brainkeeper.yaml
106
+ ```
107
+
108
+ Already have a vault elsewhere? Move it into place instead of running `init` (the target must not exist yet):
109
+
110
+ ```bash
111
+ mkdir -p ~/.brainkeeper
112
+ mv ~/MyVault ~/.brainkeeper/vault
106
113
  ```
107
114
 
108
115
  ### 3. Configure your MCP client
@@ -112,7 +119,7 @@ The MCP server is launched by your LLM harness over stdio. Pick the snippet for
112
119
  **Claude Code** (one command):
113
120
 
114
121
  ```bash
115
- claude mcp add --scope user brainkeeper -- uvx brainkeeper serve --vault ~/MyVault
122
+ claude mcp add --scope user brainkeeper -- uvx brainkeeper serve
116
123
  ```
117
124
 
118
125
  **Claude Desktop**: edit the config file at
@@ -127,15 +134,15 @@ and add:
127
134
  "mcpServers": {
128
135
  "brainkeeper": {
129
136
  "command": "uvx",
130
- "args": ["brainkeeper", "serve", "--vault", "/Users/you/MyVault"]
137
+ "args": ["brainkeeper", "serve"]
131
138
  }
132
139
  }
133
140
  }
134
141
  ```
135
142
 
136
- Then restart Claude Desktop. Use an absolute path; `~` is not expanded inside this JSON.
143
+ Then restart Claude Desktop.
137
144
 
138
- **Other clients**: any MCP-capable harness that speaks stdio works. The command is `uvx brainkeeper serve --vault /absolute/path/to/vault` (or `brainkeeper serve --vault ...` if you `pip install`ed).
145
+ **Other clients**: any MCP-capable harness that speaks stdio works. The command is `uvx brainkeeper serve` (or `brainkeeper serve` if you `pip install`ed).
139
146
 
140
147
  ### 4. Verify
141
148
 
@@ -206,7 +213,7 @@ The current spec version is **v0.1.4**. See [`CHANGELOG.md`](https://github.com/
206
213
 
207
214
  This is the first public release. Known constraints:
208
215
 
209
- - **One vault per server instance.** Each MCP process serves a single `--vault`. Multi-vault setups need multiple server entries in your client config.
216
+ - **One vault per server instance.** The server always serves the fixed vault at `~/.brainkeeper/vault`. Multiple vaults are not supported.
210
217
  - **`move_note` does not rewrite wikilinks.** Inbound links to a moved note become stale until you fix them manually. Planned for a later release.
211
218
  - **No always-on indexing.** The MCP runs only while its host (Claude Code, Claude Desktop, etc.) is running. There is no background daemon.
212
219
  - **The MCP encodes the spec contract.** If you let an LLM use raw filesystem tools (Read, Write, Edit) on the vault path, it will produce notes that violate the frontmatter contract or land in the wrong layer. Tell your agent to use only the brainkeeper tools when working inside the vault.
@@ -58,15 +58,22 @@ Either way, you need Python 3.11 or newer.
58
58
  If you don't already have a brainkeeper vault, create one:
59
59
 
60
60
  ```bash
61
- uvx brainkeeper init ~/MyVault
61
+ uvx brainkeeper init
62
62
  ```
63
63
 
64
- This creates the six layer directories and drops a `brainkeeper.yaml` (the minimal reference config) at the vault root. Open the YAML to adjust folder names and area substructure to taste. Validate against the schema at any time:
64
+ This scaffolds the vault at `~/.brainkeeper/vault`: the six layer directories plus a `brainkeeper.yaml` (the minimal reference config) at the vault root. Open the YAML to adjust folder names and area substructure to taste. Validate against the schema at any time:
65
65
 
66
66
  ```bash
67
67
  uvx check-jsonschema \
68
68
  --schemafile https://raw.githubusercontent.com/dasirra/brainkeeper/main/spec/schema/brainkeeper.schema.json \
69
- ~/MyVault/brainkeeper.yaml
69
+ ~/.brainkeeper/vault/brainkeeper.yaml
70
+ ```
71
+
72
+ Already have a vault elsewhere? Move it into place instead of running `init` (the target must not exist yet):
73
+
74
+ ```bash
75
+ mkdir -p ~/.brainkeeper
76
+ mv ~/MyVault ~/.brainkeeper/vault
70
77
  ```
71
78
 
72
79
  ### 3. Configure your MCP client
@@ -76,7 +83,7 @@ The MCP server is launched by your LLM harness over stdio. Pick the snippet for
76
83
  **Claude Code** (one command):
77
84
 
78
85
  ```bash
79
- claude mcp add --scope user brainkeeper -- uvx brainkeeper serve --vault ~/MyVault
86
+ claude mcp add --scope user brainkeeper -- uvx brainkeeper serve
80
87
  ```
81
88
 
82
89
  **Claude Desktop**: edit the config file at
@@ -91,15 +98,15 @@ and add:
91
98
  "mcpServers": {
92
99
  "brainkeeper": {
93
100
  "command": "uvx",
94
- "args": ["brainkeeper", "serve", "--vault", "/Users/you/MyVault"]
101
+ "args": ["brainkeeper", "serve"]
95
102
  }
96
103
  }
97
104
  }
98
105
  ```
99
106
 
100
- Then restart Claude Desktop. Use an absolute path; `~` is not expanded inside this JSON.
107
+ Then restart Claude Desktop.
101
108
 
102
- **Other clients**: any MCP-capable harness that speaks stdio works. The command is `uvx brainkeeper serve --vault /absolute/path/to/vault` (or `brainkeeper serve --vault ...` if you `pip install`ed).
109
+ **Other clients**: any MCP-capable harness that speaks stdio works. The command is `uvx brainkeeper serve` (or `brainkeeper serve` if you `pip install`ed).
103
110
 
104
111
  ### 4. Verify
105
112
 
@@ -170,7 +177,7 @@ The current spec version is **v0.1.4**. See [`CHANGELOG.md`](https://github.com/
170
177
 
171
178
  This is the first public release. Known constraints:
172
179
 
173
- - **One vault per server instance.** Each MCP process serves a single `--vault`. Multi-vault setups need multiple server entries in your client config.
180
+ - **One vault per server instance.** The server always serves the fixed vault at `~/.brainkeeper/vault`. Multiple vaults are not supported.
174
181
  - **`move_note` does not rewrite wikilinks.** Inbound links to a moved note become stale until you fix them manually. Planned for a later release.
175
182
  - **No always-on indexing.** The MCP runs only while its host (Claude Code, Claude Desktop, etc.) is running. There is no background daemon.
176
183
  - **The MCP encodes the spec contract.** If you let an LLM use raw filesystem tools (Read, Write, Edit) on the vault path, it will produce notes that violate the frontmatter contract or land in the wrong layer. Tell your agent to use only the brainkeeper tools when working inside the vault.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brainkeeper"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  description = "Structured Markdown vault standard with a Python vault engine and MCP interface"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -1,3 +1,3 @@
1
1
  """brainkeeper: structured Markdown vault standard with engine, MCP, and CLI."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.3.0"
@@ -9,6 +9,12 @@ from . import serve as _serve
9
9
  from . import init as _init
10
10
 
11
11
 
12
+ def vault_path() -> Path:
13
+ """Fixed vault location: the single source of truth."""
14
+ # resolve() keeps the watcher/index path keys canonical under symlinked homes
15
+ return (Path.home() / ".brainkeeper" / "vault").resolve()
16
+
17
+
12
18
  def main(argv: list[str] | None = None) -> int:
13
19
  parser = argparse.ArgumentParser(
14
20
  prog="brainkeeper",
@@ -17,28 +23,20 @@ def main(argv: list[str] | None = None) -> int:
17
23
  sub = parser.add_subparsers(dest="command", metavar="COMMAND")
18
24
 
19
25
  sp_serve = sub.add_parser("serve", help="Start the MCP server")
20
- sp_serve.add_argument(
21
- "--vault",
22
- type=Path,
23
- required=True,
24
- help="Path to vault root containing brainkeeper.yaml",
25
- )
26
26
  sp_serve.add_argument("-v", "--verbose", action="store_true")
27
27
 
28
- sp_init = sub.add_parser("init", help="Bootstrap a new vault")
29
- sp_init.add_argument(
30
- "path", type=Path, help="Path for the new vault root (created if absent)"
31
- )
28
+ sub.add_parser("init", help="Bootstrap the vault at ~/.brainkeeper/vault")
32
29
 
33
30
  args = parser.parse_args(argv)
34
31
 
32
+ if args.command is None:
33
+ parser.print_help()
34
+ return 0
35
+
36
+ args.vault = vault_path()
35
37
  if args.command == "serve":
36
38
  return _serve.run(args)
37
- if args.command == "init":
38
- return _init.run(args)
39
-
40
- parser.print_help()
41
- return 0
39
+ return _init.run(args)
42
40
 
43
41
 
44
42
  if __name__ == "__main__":
@@ -31,11 +31,18 @@ def _find_minimal_yaml() -> Path:
31
31
 
32
32
 
33
33
  def run(args) -> int:
34
- vault = args.path.expanduser().resolve()
35
- vault.mkdir(parents=True, exist_ok=True)
36
-
37
- for layer in _LAYER_DIRS:
38
- (vault / layer).mkdir(exist_ok=True)
34
+ vault = args.vault
35
+ try:
36
+ vault.mkdir(parents=True, exist_ok=True)
37
+ for layer in _LAYER_DIRS:
38
+ (vault / layer).mkdir(exist_ok=True)
39
+ except (FileExistsError, NotADirectoryError):
40
+ print(
41
+ f"error: cannot create {vault}: a file is in the way. "
42
+ "Remove it and re-run `brainkeeper init`.",
43
+ file=sys.stderr,
44
+ )
45
+ return 1
39
46
 
40
47
  config_path = vault / "brainkeeper.yaml"
41
48
  if config_path.exists():
@@ -13,9 +13,12 @@ def run(args) -> int:
13
13
  format="%(asctime)s %(levelname)s %(name)s: %(message)s",
14
14
  stream=sys.stderr,
15
15
  )
16
- vault = args.vault.expanduser().resolve()
16
+ vault = args.vault
17
17
  if not vault.is_dir():
18
- print(f"error: vault path is not a directory: {vault}", file=sys.stderr)
18
+ print(
19
+ f"error: no vault at {vault}. Run `brainkeeper init` to create it.",
20
+ file=sys.stderr,
21
+ )
19
22
  return 1
20
23
  srv = BrainkeeperServer(vault)
21
24
  srv.run_stdio()
@@ -0,0 +1,45 @@
1
+ from pathlib import Path
2
+
3
+ from brainkeeper.cli import main
4
+
5
+ _LAYER_DIRS = [
6
+ "00 Inbox",
7
+ "10 Journal",
8
+ "20 Projects",
9
+ "30 Areas",
10
+ "40 Brain",
11
+ "90 Archive",
12
+ ]
13
+
14
+
15
+ def test_init_creates_layers_and_config(tmp_path: Path, monkeypatch):
16
+ monkeypatch.setattr(Path, "home", lambda: tmp_path)
17
+ assert main(["init"]) == 0
18
+ vault = tmp_path / ".brainkeeper" / "vault"
19
+ for layer in _LAYER_DIRS:
20
+ assert (vault / layer).is_dir()
21
+ assert (vault / "brainkeeper.yaml").exists()
22
+
23
+
24
+ def test_reinit_warns_and_preserves_config(tmp_path: Path, monkeypatch, capsys):
25
+ monkeypatch.setattr(Path, "home", lambda: tmp_path)
26
+ main(["init"])
27
+ config_path = tmp_path / ".brainkeeper" / "vault" / "brainkeeper.yaml"
28
+ config_path.write_text("# user edit\n")
29
+
30
+ assert main(["init"]) == 0
31
+ assert "already exists" in capsys.readouterr().err
32
+ assert config_path.read_text() == "# user edit\n"
33
+
34
+
35
+ def test_serve_missing_vault_exits_1(tmp_path: Path, monkeypatch, capsys):
36
+ monkeypatch.setattr(Path, "home", lambda: tmp_path)
37
+ assert main(["serve"]) == 1
38
+ assert "brainkeeper init" in capsys.readouterr().err
39
+
40
+
41
+ def test_init_blocked_by_file_exits_1(tmp_path: Path, monkeypatch, capsys):
42
+ monkeypatch.setattr(Path, "home", lambda: tmp_path)
43
+ (tmp_path / ".brainkeeper").write_text("not a directory\n")
44
+ assert main(["init"]) == 1
45
+ assert "error" in capsys.readouterr().err
@@ -78,7 +78,7 @@ wheels = [
78
78
 
79
79
  [[package]]
80
80
  name = "brainkeeper"
81
- version = "0.2.2"
81
+ version = "0.3.0"
82
82
  source = { editable = "." }
83
83
  dependencies = [
84
84
  { name = "fastmcp" },
File without changes
File without changes
File without changes
File without changes
File without changes