buildlog 0.9.0__tar.gz → 0.10.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 (60) hide show
  1. {buildlog-0.9.0 → buildlog-0.10.0}/PKG-INFO +22 -22
  2. {buildlog-0.9.0 → buildlog-0.10.0}/README.md +20 -19
  3. {buildlog-0.9.0 → buildlog-0.10.0}/post_gen.py +10 -5
  4. {buildlog-0.9.0 → buildlog-0.10.0}/pyproject.toml +4 -6
  5. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/cli.py +268 -26
  6. buildlog-0.10.0/src/buildlog/constants.py +121 -0
  7. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/core/__init__.py +44 -0
  8. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/core/operations.py +1170 -0
  9. buildlog-0.10.0/src/buildlog/data/seeds/bragi.yaml +61 -0
  10. buildlog-0.10.0/src/buildlog/mcp/__init__.py +65 -0
  11. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/mcp/server.py +36 -0
  12. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/mcp/tools.py +526 -12
  13. buildlog-0.10.0/template/buildlog/.gitkeep +0 -0
  14. buildlog-0.10.0/template/buildlog/assets/.gitkeep +0 -0
  15. buildlog-0.9.0/src/buildlog/mcp/__init__.py +0 -17
  16. {buildlog-0.9.0 → buildlog-0.10.0}/.gitignore +0 -0
  17. {buildlog-0.9.0 → buildlog-0.10.0}/LICENSE +0 -0
  18. {buildlog-0.9.0 → buildlog-0.10.0}/copier.yml +0 -0
  19. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/__init__.py +0 -0
  20. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/confidence.py +0 -0
  21. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/core/bandit.py +0 -0
  22. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/data/__init__.py +0 -0
  23. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/data/seeds/security_karen.yaml +0 -0
  24. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/data/seeds/test_terrorist.yaml +0 -0
  25. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/distill.py +0 -0
  26. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/embeddings.py +0 -0
  27. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/engine/__init__.py +0 -0
  28. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/engine/bandit.py +0 -0
  29. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/engine/confidence.py +0 -0
  30. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/engine/embeddings.py +0 -0
  31. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/engine/experiments.py +0 -0
  32. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/engine/types.py +0 -0
  33. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/llm.py +0 -0
  34. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/__init__.py +0 -0
  35. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/base.py +0 -0
  36. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/claude_md.py +0 -0
  37. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/continue_dev.py +0 -0
  38. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/copilot.py +0 -0
  39. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/cursor.py +0 -0
  40. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/settings_json.py +0 -0
  41. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/skill.py +0 -0
  42. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/tracking.py +0 -0
  43. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/render/windsurf.py +0 -0
  44. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/seed_engine/__init__.py +0 -0
  45. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/seed_engine/categorizers.py +0 -0
  46. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/seed_engine/extractors.py +0 -0
  47. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/seed_engine/generators.py +0 -0
  48. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/seed_engine/llm_extractor.py +0 -0
  49. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/seed_engine/models.py +0 -0
  50. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/seed_engine/pipeline.py +0 -0
  51. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/seed_engine/sources.py +0 -0
  52. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/seeds.py +0 -0
  53. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/skills.py +0 -0
  54. {buildlog-0.9.0 → buildlog-0.10.0}/src/buildlog/stats.py +0 -0
  55. {buildlog-0.9.0/template/buildlog → buildlog-0.10.0/template/buildlog/.buildlog}/.gitkeep +0 -0
  56. {buildlog-0.9.0/template/buildlog/assets → buildlog-0.10.0/template/buildlog/.buildlog/seeds}/.gitkeep +0 -0
  57. {buildlog-0.9.0 → buildlog-0.10.0}/template/buildlog/2026-01-01-example.md +0 -0
  58. {buildlog-0.9.0 → buildlog-0.10.0}/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
  59. {buildlog-0.9.0 → buildlog-0.10.0}/template/buildlog/_TEMPLATE.md +0 -0
  60. {buildlog-0.9.0 → buildlog-0.10.0}/template/buildlog/_TEMPLATE_QUICK.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: buildlog
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: Engineering notebook for AI-assisted development
5
5
  Project-URL: Homepage, https://github.com/Peleke/buildlog-template
6
6
  Project-URL: Repository, https://github.com/Peleke/buildlog-template
@@ -22,12 +22,12 @@ Classifier: Topic :: Software Development :: Documentation
22
22
  Requires-Python: >=3.10
23
23
  Requires-Dist: click>=8.0.0
24
24
  Requires-Dist: copier>=9.0.0
25
+ Requires-Dist: mcp>=1.0.0
25
26
  Requires-Dist: numpy>=1.21.0
26
27
  Requires-Dist: pymupdf>=1.26.7
27
28
  Requires-Dist: pyyaml>=6.0.0
28
29
  Provides-Extra: all
29
30
  Requires-Dist: anthropic>=0.40.0; extra == 'all'
30
- Requires-Dist: mcp>=1.0.0; extra == 'all'
31
31
  Requires-Dist: ollama>=0.4.0; extra == 'all'
32
32
  Requires-Dist: openai>=1.0.0; extra == 'all'
33
33
  Requires-Dist: sentence-transformers>=2.2.0; extra == 'all'
@@ -51,7 +51,6 @@ Provides-Extra: llm
51
51
  Requires-Dist: anthropic>=0.40.0; extra == 'llm'
52
52
  Requires-Dist: ollama>=0.4.0; extra == 'llm'
53
53
  Provides-Extra: mcp
54
- Requires-Dist: mcp>=1.0.0; extra == 'mcp'
55
54
  Provides-Extra: ollama
56
55
  Requires-Dist: ollama>=0.4.0; extra == 'ollama'
57
56
  Provides-Extra: openai
@@ -164,22 +163,29 @@ The roadmap: contextual bandits (now) -> richer policy models -> longer-horizon
164
163
 
165
164
  ## Installation
166
165
 
166
+ ### Quick start
167
+
168
+ ```bash
169
+ pip install buildlog # MCP server included by default
170
+ buildlog init --defaults # scaffold project, register MCP, update CLAUDE.md
171
+ ```
172
+
173
+ That's it. Claude Code will now have access to all 29 buildlog tools.
174
+
167
175
  ### Global install (recommended)
168
176
 
169
177
  ```bash
170
- uv tool install "buildlog[mcp]" # or: pipx install "buildlog[mcp]"
178
+ uv tool install buildlog # or: pipx install buildlog
171
179
  ```
172
180
 
173
- This puts `buildlog` and `buildlog-mcp` on your PATH. Works from any directory. The `[mcp]` extra is required for the MCP server.
181
+ This puts `buildlog` and `buildlog-mcp` on your PATH. Works from any directory.
174
182
 
175
183
  ### Per-project (virtual environment)
176
184
 
177
185
  ```bash
178
- uv pip install "buildlog[mcp]" # or: pip install "buildlog[mcp]"
186
+ uv pip install buildlog # or: pip install buildlog
179
187
  ```
180
188
 
181
- Omit `[mcp]` if you only need the CLI.
182
-
183
189
  ### For JS/TS projects
184
190
 
185
191
  ```bash
@@ -188,17 +194,11 @@ npx @peleke.s/buildlog init
188
194
 
189
195
  ### MCP server for Claude Code
190
196
 
191
- Add to `~/.claude/claude_code_config.json`:
192
-
193
- ```json
194
- {
195
- "mcpServers": {
196
- "buildlog": {
197
- "command": "buildlog-mcp",
198
- "args": []
199
- }
200
- }
201
- }
197
+ `buildlog init` auto-registers the MCP server. For existing projects:
198
+
199
+ ```bash
200
+ buildlog init-mcp # register MCP in .claude/settings.json
201
+ buildlog mcp-test # verify all 29 tools are registered
202
202
  ```
203
203
 
204
204
  This exposes buildlog tools (seeds, skills, experiments, gauntlet, bandit status) to any Claude Code session.
@@ -206,10 +206,10 @@ This exposes buildlog tools (seeds, skills, experiments, gauntlet, bandit status
206
206
  ## Quick Start
207
207
 
208
208
  ```bash
209
- buildlog init # scaffold a project (run in any repo)
210
- buildlog new my-feature # start a session
209
+ buildlog init --defaults # scaffold + MCP + CLAUDE.md
210
+ buildlog new my-feature # start a session
211
211
  # ... work ...
212
- buildlog distill && buildlog skills
212
+ buildlog commit -m "feat: add auth"
213
213
  buildlog experiment start
214
214
  # ... work across sessions ...
215
215
  buildlog experiment end
@@ -104,22 +104,29 @@ The roadmap: contextual bandits (now) -> richer policy models -> longer-horizon
104
104
 
105
105
  ## Installation
106
106
 
107
+ ### Quick start
108
+
109
+ ```bash
110
+ pip install buildlog # MCP server included by default
111
+ buildlog init --defaults # scaffold project, register MCP, update CLAUDE.md
112
+ ```
113
+
114
+ That's it. Claude Code will now have access to all 29 buildlog tools.
115
+
107
116
  ### Global install (recommended)
108
117
 
109
118
  ```bash
110
- uv tool install "buildlog[mcp]" # or: pipx install "buildlog[mcp]"
119
+ uv tool install buildlog # or: pipx install buildlog
111
120
  ```
112
121
 
113
- This puts `buildlog` and `buildlog-mcp` on your PATH. Works from any directory. The `[mcp]` extra is required for the MCP server.
122
+ This puts `buildlog` and `buildlog-mcp` on your PATH. Works from any directory.
114
123
 
115
124
  ### Per-project (virtual environment)
116
125
 
117
126
  ```bash
118
- uv pip install "buildlog[mcp]" # or: pip install "buildlog[mcp]"
127
+ uv pip install buildlog # or: pip install buildlog
119
128
  ```
120
129
 
121
- Omit `[mcp]` if you only need the CLI.
122
-
123
130
  ### For JS/TS projects
124
131
 
125
132
  ```bash
@@ -128,17 +135,11 @@ npx @peleke.s/buildlog init
128
135
 
129
136
  ### MCP server for Claude Code
130
137
 
131
- Add to `~/.claude/claude_code_config.json`:
132
-
133
- ```json
134
- {
135
- "mcpServers": {
136
- "buildlog": {
137
- "command": "buildlog-mcp",
138
- "args": []
139
- }
140
- }
141
- }
138
+ `buildlog init` auto-registers the MCP server. For existing projects:
139
+
140
+ ```bash
141
+ buildlog init-mcp # register MCP in .claude/settings.json
142
+ buildlog mcp-test # verify all 29 tools are registered
142
143
  ```
143
144
 
144
145
  This exposes buildlog tools (seeds, skills, experiments, gauntlet, bandit status) to any Claude Code session.
@@ -146,10 +147,10 @@ This exposes buildlog tools (seeds, skills, experiments, gauntlet, bandit status
146
147
  ## Quick Start
147
148
 
148
149
  ```bash
149
- buildlog init # scaffold a project (run in any repo)
150
- buildlog new my-feature # start a session
150
+ buildlog init --defaults # scaffold + MCP + CLAUDE.md
151
+ buildlog new my-feature # start a session
151
152
  # ... work ...
152
- buildlog distill && buildlog skills
153
+ buildlog commit -m "feat: add auth"
153
154
  buildlog experiment start
154
155
  # ... work across sessions ...
155
156
  buildlog experiment end
@@ -3,7 +3,11 @@
3
3
 
4
4
  from pathlib import Path
5
5
 
6
- CLAUDE_MD_SECTION = """
6
+ try:
7
+ from buildlog.constants import CLAUDE_MD_BUILDLOG_SECTION
8
+ except ImportError:
9
+ # Fallback for when buildlog isn't installed (e.g., copier from GitHub)
10
+ CLAUDE_MD_BUILDLOG_SECTION = """
7
11
  ## Build Journal
8
12
 
9
13
  After completing significant work (features, debugging sessions, deployments,
@@ -40,15 +44,16 @@ def main():
40
44
 
41
45
  content = claude_md.read_text()
42
46
 
43
- if "## Build Journal" in content:
44
- print("Build Journal section already exists in CLAUDE.md")
47
+ # Check for either old or new section marker
48
+ if "## buildlog Integration" in content or "## Build Journal" in content:
49
+ print("buildlog section already exists in CLAUDE.md")
45
50
  return
46
51
 
47
52
  # Append to end of file
48
53
  with open(claude_md, "a") as f:
49
- f.write("\n" + CLAUDE_MD_SECTION)
54
+ f.write("\n" + CLAUDE_MD_BUILDLOG_SECTION)
50
55
 
51
- print("Added Build Journal section to CLAUDE.md")
56
+ print("Added buildlog Integration section to CLAUDE.md")
52
57
 
53
58
 
54
59
  if __name__ == "__main__":
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "buildlog"
7
- version = "0.9.0"
7
+ version = "0.10.0"
8
8
  description = "Engineering notebook for AI-assisted development"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -32,6 +32,7 @@ dependencies = [
32
32
  "pyyaml>=6.0.0",
33
33
  "numpy>=1.21.0",
34
34
  "pymupdf>=1.26.7",
35
+ "mcp>=1.0.0",
35
36
  ]
36
37
 
37
38
  [project.optional-dependencies]
@@ -60,15 +61,12 @@ llm = [
60
61
  "ollama>=0.4.0",
61
62
  "anthropic>=0.40.0",
62
63
  ]
63
- # MCP server for Claude Code integration
64
- mcp = [
65
- "mcp>=1.0.0",
66
- ]
64
+ # MCP server for Claude Code integration (now a default dependency, kept for backwards compat)
65
+ mcp = []
67
66
  # All optional features
68
67
  all = [
69
68
  "sentence-transformers>=2.2.0",
70
69
  "openai>=1.0.0",
71
- "mcp>=1.0.0",
72
70
  "ollama>=0.4.0",
73
71
  "anthropic>=0.40.0",
74
72
  ]
@@ -50,12 +50,13 @@ def main():
50
50
 
51
51
  @main.command()
52
52
  @click.option("--no-claude-md", is_flag=True, help="Don't update CLAUDE.md")
53
+ @click.option("--no-mcp", is_flag=True, help="Don't register MCP server")
53
54
  @click.option(
54
55
  "--defaults",
55
56
  is_flag=True,
56
57
  help="Use default values for all prompts (non-interactive)",
57
58
  )
58
- def init(no_claude_md: bool, defaults: bool):
59
+ def init(no_claude_md: bool, no_mcp: bool, defaults: bool):
59
60
  """Initialize buildlog in the current directory.
60
61
 
61
62
  Sets up the buildlog/ directory with templates and optionally
@@ -109,23 +110,40 @@ def init(no_claude_md: bool, defaults: bool):
109
110
  click.echo("Failed to initialize buildlog.", err=True)
110
111
  raise SystemExit(1)
111
112
 
113
+ # Ensure .buildlog/ directory exists (copier skips dot-prefixed paths)
114
+ dot_buildlog = buildlog_dir / ".buildlog"
115
+ dot_buildlog.mkdir(exist_ok=True)
116
+ (dot_buildlog / "seeds").mkdir(exist_ok=True)
117
+
112
118
  # Update CLAUDE.md if it exists and user didn't opt out
113
119
  if not no_claude_md:
114
120
  claude_md = Path("CLAUDE.md")
115
121
  if claude_md.exists():
116
122
  content = claude_md.read_text()
117
- if "## Build Journal" not in content:
118
- section = (
119
- "\n## Build Journal\n\n"
120
- "After completing significant work (features, debugging sessions, "
121
- "deployments,\n"
122
- "2+ hour focused sessions), write a build journal entry.\n\n"
123
- "**Location:** `buildlog/YYYY-MM-DD-{slug}.md`\n"
124
- "**Template:** `buildlog/_TEMPLATE.md`\n"
125
- )
123
+ if (
124
+ "## buildlog Integration" not in content
125
+ and "## Build Journal" not in content
126
+ ):
127
+ try:
128
+ from buildlog.constants import CLAUDE_MD_BUILDLOG_SECTION
129
+
130
+ section = CLAUDE_MD_BUILDLOG_SECTION
131
+ except ImportError:
132
+ section = (
133
+ "\n## Build Journal\n\n"
134
+ "After completing significant work (features, debugging "
135
+ "sessions, deployments,\n"
136
+ "2+ hour focused sessions), write a build journal entry.\n\n"
137
+ "**Location:** `buildlog/YYYY-MM-DD-{slug}.md`\n"
138
+ "**Template:** `buildlog/_TEMPLATE.md`\n"
139
+ )
126
140
  with open(claude_md, "a") as f:
127
141
  f.write(section)
128
- click.echo("Added Build Journal section to CLAUDE.md")
142
+ click.echo("Added buildlog Integration section to CLAUDE.md")
143
+
144
+ # Register MCP server unless opted out
145
+ if not no_mcp:
146
+ _init_mcp()
129
147
 
130
148
  click.echo("\n✓ buildlog initialized!")
131
149
  click.echo()
@@ -142,12 +160,125 @@ def init(no_claude_md: bool, defaults: bool):
142
160
  click.echo("Start now: buildlog new my-first-task --quick")
143
161
 
144
162
 
163
+ def _init_mcp(settings_path: Path | None = None, global_mode: bool = False) -> None:
164
+ """Register buildlog as an MCP server in settings.json.
165
+
166
+ Args:
167
+ settings_path: Path to settings.json. Defaults to .claude/settings.json
168
+ global_mode: If True, display global-specific messaging
169
+ """
170
+ import json as json_module
171
+
172
+ if settings_path is None:
173
+ settings_path = Path(".claude") / "settings.json"
174
+
175
+ location = "~/.claude/settings.json" if global_mode else ".claude/settings.json"
176
+
177
+ try:
178
+ if settings_path.exists():
179
+ try:
180
+ data = json_module.loads(settings_path.read_text())
181
+ except json_module.JSONDecodeError:
182
+ click.echo(
183
+ f"Warning: {location} is malformed, skipping MCP registration",
184
+ err=True,
185
+ )
186
+ return
187
+ else:
188
+ data = {}
189
+
190
+ if "mcpServers" not in data:
191
+ data["mcpServers"] = {}
192
+
193
+ if "buildlog" in data["mcpServers"]:
194
+ click.echo(f"buildlog MCP server already registered in {location}")
195
+ return
196
+
197
+ data["mcpServers"]["buildlog"] = {"command": "buildlog-mcp", "args": []}
198
+
199
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
200
+ settings_path.write_text(json_module.dumps(data, indent=2) + "\n")
201
+ click.echo(f"Registered buildlog MCP server in {location}")
202
+ if global_mode:
203
+ click.echo("Claude Code now has access to buildlog tools in all projects.")
204
+ except Exception as e:
205
+ click.echo(f"Warning: could not register MCP server: {e}", err=True)
206
+
207
+
208
+ @main.command("init-mcp")
209
+ @click.option(
210
+ "--global",
211
+ "global_",
212
+ is_flag=True,
213
+ help="Register globally in ~/.claude/settings.json (works in any project)",
214
+ )
215
+ def init_mcp(global_: bool):
216
+ """Register buildlog as an MCP server for Claude Code.
217
+
218
+ Creates or updates .claude/settings.json with the buildlog MCP
219
+ server configuration. Idempotent — safe to run multiple times.
220
+
221
+ Use --global to register in ~/.claude/settings.json so buildlog
222
+ tools are available in every project without per-project init.
223
+
224
+ Examples:
225
+
226
+ buildlog init-mcp # local (current project)
227
+ buildlog init-mcp --global # global (all projects)
228
+ """
229
+ if global_:
230
+ settings_path = Path.home() / ".claude" / "settings.json"
231
+ _init_mcp(settings_path=settings_path, global_mode=True)
232
+ else:
233
+ _init_mcp()
234
+
235
+
236
+ @main.command("mcp-test")
237
+ def mcp_test():
238
+ """Verify the MCP server starts and all tools are registered.
239
+
240
+ Checks that the buildlog-mcp server can be imported and lists
241
+ all registered tools. Exits 0 if all 29 tools are found, 1 otherwise.
242
+
243
+ Examples:
244
+
245
+ buildlog mcp-test
246
+ """
247
+ try:
248
+ from buildlog.mcp.server import mcp as mcp_server
249
+ except ImportError:
250
+ click.echo("MCP not installed. Run: pip install buildlog", err=True)
251
+ raise SystemExit(1)
252
+
253
+ try:
254
+ # FastMCP stores tools internally
255
+ tools = mcp_server._tool_manager._tools
256
+ tool_names = sorted(tools.keys())
257
+ except AttributeError:
258
+ # Fallback: try to count via the public API pattern
259
+ click.echo("Warning: could not inspect tools via internal API", err=True)
260
+ tool_names = []
261
+
262
+ expected = 29
263
+ click.echo(f"buildlog MCP server: {len(tool_names)} tools registered")
264
+ for name in tool_names:
265
+ click.echo(f" {name}")
266
+
267
+ if len(tool_names) >= expected:
268
+ click.echo(f"\nAll {expected} tools registered.")
269
+ raise SystemExit(0)
270
+ else:
271
+ click.echo(f"\nExpected {expected} tools, found {len(tool_names)}.", err=True)
272
+ raise SystemExit(1)
273
+
274
+
145
275
  @main.command()
146
276
  @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
147
277
  def overview(output_json: bool):
148
278
  """Show the full state of your buildlog at a glance.
149
279
 
150
280
  Entries, skills, promoted rules, experiments — everything in one view.
281
+ Works even without buildlog init (shows uninitialized state).
151
282
 
152
283
  Examples:
153
284
 
@@ -158,9 +289,38 @@ def overview(output_json: bool):
158
289
 
159
290
  buildlog_dir = Path("buildlog")
160
291
 
292
+ # Handle uninitialized state gracefully
161
293
  if not buildlog_dir.exists():
162
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
163
- raise SystemExit(1)
294
+ result = {
295
+ "initialized": False,
296
+ "entries": 0,
297
+ "skills": {
298
+ "total": 0,
299
+ "by_confidence": {},
300
+ "promoted": 0,
301
+ "rejected": 0,
302
+ "pending": 0,
303
+ },
304
+ "active_session": None,
305
+ "render_targets": [],
306
+ "message": "buildlog not initialized. Run 'buildlog init' to enable full features.",
307
+ }
308
+ if output_json:
309
+ click.echo(json_module.dumps(result, indent=2))
310
+ else:
311
+ click.echo("buildlog overview")
312
+ click.echo("=" * 40)
313
+ click.echo(" Status: Not initialized")
314
+ click.echo()
315
+ click.echo("Get started:")
316
+ click.echo(" buildlog init --defaults # Initialize buildlog")
317
+ click.echo()
318
+ click.echo("Or use globally without init:")
319
+ click.echo(" buildlog init-mcp --global # Register MCP server globally")
320
+ click.echo(
321
+ " buildlog gauntlet list # Review personas work without init"
322
+ )
323
+ return
164
324
 
165
325
  # Count entries
166
326
  entries = sorted(buildlog_dir.glob("20??-??-??-*.md"))
@@ -208,6 +368,7 @@ def overview(output_json: bool):
208
368
  from buildlog.render import RENDERERS
209
369
 
210
370
  result = {
371
+ "initialized": True,
211
372
  "entries": len(entries),
212
373
  "skills": {
213
374
  "total": total_skills,
@@ -586,7 +747,7 @@ def distill(
586
747
  """Extract patterns from all buildlog entries.
587
748
 
588
749
  Parses the Improvements section of each buildlog entry and aggregates
589
- insights into structured output (JSON or YAML).
750
+ insights into structured output (JSON or YAML). Returns empty result if not initialized.
590
751
 
591
752
  Examples:
592
753
 
@@ -596,11 +757,25 @@ def distill(
596
757
  buildlog distill --since 2026-01-01 # Filter by date
597
758
  buildlog distill --category workflow # Filter by category
598
759
  """
760
+ import json as json_module
761
+
599
762
  buildlog_dir = Path("buildlog")
600
763
 
764
+ # Handle uninitialized state gracefully
601
765
  if not buildlog_dir.exists():
602
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
603
- raise SystemExit(1)
766
+ empty_result = {
767
+ "initialized": False,
768
+ "patterns": [],
769
+ "statistics": {"total_patterns": 0, "total_entries": 0},
770
+ "message": "buildlog not initialized",
771
+ }
772
+ if fmt == "json":
773
+ click.echo(json_module.dumps(empty_result, indent=2))
774
+ else:
775
+ click.echo(
776
+ "# buildlog not initialized - run 'buildlog init' first\npatterns: []"
777
+ )
778
+ return
604
779
 
605
780
  # Convert datetime to date if provided
606
781
  since_date = since.date() if since else None
@@ -651,6 +826,7 @@ def stats(output_json: bool, detailed: bool, since_date: str | None):
651
826
  """Show buildlog statistics and analytics.
652
827
 
653
828
  Provides insights on buildlog usage, coverage, and quality.
829
+ Returns empty stats if not initialized.
654
830
 
655
831
  Examples:
656
832
 
@@ -659,11 +835,27 @@ def stats(output_json: bool, detailed: bool, since_date: str | None):
659
835
  buildlog stats --detailed # Include top sources
660
836
  buildlog stats --since 2026-01-01
661
837
  """
838
+ import json as json_module
839
+
662
840
  buildlog_dir = Path("buildlog")
663
841
 
842
+ # Handle uninitialized state gracefully
664
843
  if not buildlog_dir.exists():
665
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
666
- raise SystemExit(1)
844
+ empty_stats = {
845
+ "initialized": False,
846
+ "total_entries": 0,
847
+ "total_patterns": 0,
848
+ "categories": {},
849
+ "date_range": None,
850
+ "message": "buildlog not initialized",
851
+ }
852
+ if output_json:
853
+ click.echo(json_module.dumps(empty_stats, indent=2))
854
+ else:
855
+ click.echo("buildlog stats")
856
+ click.echo("=" * 40)
857
+ click.echo(" Not initialized. Run 'buildlog init' first.")
858
+ return
667
859
 
668
860
  # Parse since date if provided
669
861
  parsed_since = None
@@ -726,7 +918,7 @@ def skills(
726
918
  """Generate agent-consumable skills from buildlog patterns.
727
919
 
728
920
  Transforms distilled patterns into actionable rules with deduplication,
729
- confidence scoring, and stable IDs.
921
+ confidence scoring, and stable IDs. Returns empty set if not initialized.
730
922
 
731
923
  Examples:
732
924
 
@@ -743,9 +935,31 @@ def skills(
743
935
  """
744
936
  buildlog_dir = Path("buildlog")
745
937
 
938
+ # Handle uninitialized state gracefully - return empty skill set
746
939
  if not buildlog_dir.exists():
747
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
748
- raise SystemExit(1)
940
+ if fmt == "json":
941
+ import json as json_module
942
+
943
+ click.echo(
944
+ json_module.dumps(
945
+ {
946
+ "initialized": False,
947
+ "skills": {},
948
+ "total_skills": 0,
949
+ "message": "buildlog not initialized",
950
+ },
951
+ indent=2,
952
+ )
953
+ )
954
+ elif fmt == "yaml":
955
+ click.echo(
956
+ "# buildlog not initialized - run 'buildlog init' first\nskills: {}\ntotal_skills: 0"
957
+ )
958
+ else:
959
+ click.echo(
960
+ "No buildlog/ directory found. Run 'buildlog init' to extract skills."
961
+ )
962
+ return
749
963
 
750
964
  # Convert datetime to date if provided
751
965
  since_date = since.date() if since else None
@@ -935,7 +1149,7 @@ def status_cmd(min_confidence: str, output_json: bool):
935
1149
  """Show extracted skills by category and confidence.
936
1150
 
937
1151
  Displays all skills extracted from buildlog entries, grouped by category,
938
- with confidence levels and promotion status.
1152
+ with confidence levels and promotion status. Returns empty state if not initialized.
939
1153
 
940
1154
  Examples:
941
1155
 
@@ -948,9 +1162,23 @@ def status_cmd(min_confidence: str, output_json: bool):
948
1162
 
949
1163
  buildlog_dir = Path("buildlog")
950
1164
 
1165
+ # Handle uninitialized state gracefully
951
1166
  if not buildlog_dir.exists():
952
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
953
- raise SystemExit(1)
1167
+ empty_result = {
1168
+ "initialized": False,
1169
+ "total_skills": 0,
1170
+ "total_entries": 0,
1171
+ "skills": {},
1172
+ "by_confidence": {"high": 0, "medium": 0, "low": 0},
1173
+ "promotable_ids": [],
1174
+ "error": None,
1175
+ }
1176
+ if output_json:
1177
+ click.echo(json_module.dumps(empty_result, indent=2))
1178
+ else:
1179
+ click.echo("Skills: 0 total from 0 entries")
1180
+ click.echo(" buildlog not initialized. Run 'buildlog init' first.")
1181
+ return
954
1182
 
955
1183
  result = status(buildlog_dir, min_confidence=min_confidence) # type: ignore[arg-type]
956
1184
 
@@ -1092,6 +1320,7 @@ def diff_cmd(output_json: bool):
1092
1320
  """Show skills pending review (not yet promoted or rejected).
1093
1321
 
1094
1322
  Useful for seeing what's new since the last time you reviewed skills.
1323
+ Returns empty diff if not initialized.
1095
1324
 
1096
1325
  Examples:
1097
1326
 
@@ -1103,9 +1332,22 @@ def diff_cmd(output_json: bool):
1103
1332
 
1104
1333
  buildlog_dir = Path("buildlog")
1105
1334
 
1335
+ # Handle uninitialized state gracefully
1106
1336
  if not buildlog_dir.exists():
1107
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
1108
- raise SystemExit(1)
1337
+ empty_result = {
1338
+ "initialized": False,
1339
+ "pending": {},
1340
+ "total_pending": 0,
1341
+ "already_promoted": 0,
1342
+ "already_rejected": 0,
1343
+ "error": None,
1344
+ }
1345
+ if output_json:
1346
+ click.echo(json_module.dumps(empty_result, indent=2))
1347
+ else:
1348
+ click.echo("Pending: 0 | Promoted: 0 | Rejected: 0")
1349
+ click.echo(" buildlog not initialized. Run 'buildlog init' first.")
1350
+ return
1109
1351
 
1110
1352
  result = core_diff(buildlog_dir)
1111
1353