buildlog 0.9.0__py3-none-any.whl → 0.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. buildlog/cli.py +268 -26
  2. buildlog/constants.py +121 -0
  3. buildlog/core/__init__.py +44 -0
  4. buildlog/core/operations.py +1170 -0
  5. buildlog/data/seeds/bragi.yaml +61 -0
  6. buildlog/mcp/__init__.py +51 -3
  7. buildlog/mcp/server.py +36 -0
  8. buildlog/mcp/tools.py +526 -12
  9. {buildlog-0.9.0.data → buildlog-0.10.0.data}/data/share/buildlog/post_gen.py +10 -5
  10. buildlog-0.10.0.data/data/share/buildlog/template/buildlog/.gitkeep +0 -0
  11. buildlog-0.10.0.data/data/share/buildlog/template/buildlog/assets/.gitkeep +0 -0
  12. {buildlog-0.9.0.dist-info → buildlog-0.10.0.dist-info}/METADATA +22 -22
  13. {buildlog-0.9.0.dist-info → buildlog-0.10.0.dist-info}/RECORD +23 -19
  14. {buildlog-0.9.0.data → buildlog-0.10.0.data}/data/share/buildlog/copier.yml +0 -0
  15. {buildlog-0.9.0.data/data/share/buildlog/template/buildlog → buildlog-0.10.0.data/data/share/buildlog/template/buildlog/.buildlog}/.gitkeep +0 -0
  16. {buildlog-0.9.0.data/data/share/buildlog/template/buildlog/assets → buildlog-0.10.0.data/data/share/buildlog/template/buildlog/.buildlog/seeds}/.gitkeep +0 -0
  17. {buildlog-0.9.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/2026-01-01-example.md +0 -0
  18. {buildlog-0.9.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
  19. {buildlog-0.9.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE.md +0 -0
  20. {buildlog-0.9.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE_QUICK.md +0 -0
  21. {buildlog-0.9.0.dist-info → buildlog-0.10.0.dist-info}/WHEEL +0 -0
  22. {buildlog-0.9.0.dist-info → buildlog-0.10.0.dist-info}/entry_points.txt +0 -0
  23. {buildlog-0.9.0.dist-info → buildlog-0.10.0.dist-info}/licenses/LICENSE +0 -0
buildlog/cli.py CHANGED
@@ -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
 
buildlog/constants.py ADDED
@@ -0,0 +1,121 @@
1
+ """Shared constants for buildlog, including the CLAUDE.md integration section."""
2
+
3
+ CLAUDE_MD_BUILDLOG_SECTION = """
4
+ ## buildlog Integration
5
+
6
+ buildlog is configured as an MCP server. Use the tools below to maintain
7
+ a learning loop: write entries, extract skills, run gauntlet reviews, and
8
+ track experiments.
9
+
10
+ ### Always On: Commit -> Gauntlet -> Learn Loop
11
+
12
+ After every significant commit or feature completion:
13
+
14
+ 1. **Create/update entry**: `buildlog_entry_new(slug="what-you-built")`
15
+ 2. **Run gauntlet review**: `buildlog_gauntlet_rules()` to load rules, then review code against them
16
+ 3. **Process findings**: `buildlog_gauntlet_issues(issues=[...])` to categorize and persist learnings
17
+ 4. **Fix criticals/majors**, re-run until clean or accept risk: `buildlog_gauntlet_accept_risk(remaining_issues=[...])`
18
+ 5. **Log reward**: `buildlog_log_reward(outcome="accepted")` when work is approved
19
+
20
+ ### Skill Extraction & Promotion Workflow
21
+
22
+ 1. `buildlog_status()` — see extracted skills by category
23
+ 2. `buildlog_diff()` — see skills pending review
24
+ 3. `buildlog_promote(skill_ids=[...], target="claude_md")` — surface to agent rules
25
+ 4. `buildlog_reject(skill_ids=[...])` — mark false positives
26
+
27
+ ### Gauntlet Review Loop Workflow
28
+
29
+ 1. `buildlog_gauntlet_rules()` — load reviewer persona rules
30
+ 2. Review code against rules, collect issues
31
+ 3. `buildlog_gauntlet_issues(issues=[...])` — categorize, persist learnings, get next action
32
+ 4. If action="fix_criticals": fix and re-run
33
+ 5. If action="checkpoint_majors"/"checkpoint_minors": ask user
34
+ 6. `buildlog_gauntlet_accept_risk(remaining_issues=[...])` — accept remaining
35
+ 7. `buildlog_learn_from_review(issues=[...])` — persist learnings
36
+
37
+ ### Reward Signal / Thompson Sampling Workflow
38
+
39
+ 1. `buildlog_log_reward(outcome="accepted"|"revision"|"rejected")` — explicit feedback
40
+ 2. `buildlog_rewards()` — view reward history and statistics
41
+ 3. `buildlog_bandit_status()` — see which rules the bandit favors
42
+
43
+ ### Session Tracking & Experiment Workflow
44
+
45
+ 1. `buildlog_experiment_start(error_class="missing_test")` — begin tracked session
46
+ 2. `buildlog_log_mistake(error_class="...", description="...")` — log mistakes
47
+ 3. `buildlog_experiment_end()` — end session, calculate metrics
48
+ 4. `buildlog_experiment_metrics()` — per-session or aggregate stats
49
+ 5. `buildlog_experiment_report()` — comprehensive report
50
+
51
+ ### Tool Reference (29 tools)
52
+
53
+ **Commit & Entries:**
54
+ - `buildlog_commit(message, git_args, slug, no_entry)` — git commit with auto buildlog entry
55
+ - `buildlog_entry_new(slug, entry_date, quick)` — create entry
56
+ - `buildlog_entry_list()` — list all entries
57
+ - `buildlog_overview()` — project state at a glance
58
+
59
+ **Skill Management:**
60
+ - `buildlog_status(min_confidence="low")` — extracted skills
61
+ - `buildlog_promote(skill_ids, target="claude_md")` — promote to agent rules
62
+ - `buildlog_reject(skill_ids)` — reject false positives
63
+ - `buildlog_diff()` — pending skills
64
+ - `buildlog_distill(since, category)` — extract patterns from entries
65
+ - `buildlog_skills(since, min_frequency)` — generate skill set from entries
66
+ - `buildlog_stats(since, detailed)` — buildlog statistics and insights
67
+
68
+ **Gauntlet Review:**
69
+ - `buildlog_gauntlet_prompt(target, personas)` — generate review prompt with rules
70
+ - `buildlog_gauntlet_loop(target, personas, max_iterations, stop_at, auto_gh_issues)` — full loop config
71
+ - `buildlog_gauntlet_rules(persona, format)` — load reviewer rules
72
+ - `buildlog_gauntlet_issues(issues, iteration)` — process findings
73
+ - `buildlog_gauntlet_accept_risk(remaining_issues)` — accept risk
74
+ - `buildlog_gauntlet_list_personas()` — list available reviewer personas
75
+ - `buildlog_gauntlet_generate(source_text, persona, dry_run)` — generate rules from source text
76
+
77
+ **Review Learning:**
78
+ - `buildlog_learn_from_review(issues, source)` — persist review learnings
79
+
80
+ **Reward & Bandit:**
81
+ - `buildlog_log_reward(outcome, rules_active)` — log feedback
82
+ - `buildlog_rewards(limit)` — reward history
83
+ - `buildlog_bandit_status(context)` — bandit state
84
+
85
+ **Experiments:**
86
+ - `buildlog_experiment_start(error_class)` — start session
87
+ - `buildlog_experiment_end()` — end session
88
+ - `buildlog_log_mistake(error_class, description)` — log mistake
89
+ - `buildlog_experiment_metrics(session_id)` — metrics
90
+ - `buildlog_experiment_report()` — full report
91
+
92
+ **Project Setup:**
93
+ - `buildlog_init(no_mcp, no_claude_md)` — initialize buildlog in project
94
+ - `buildlog_update()` — update buildlog template to latest
95
+
96
+ ### When to Use Each Tool
97
+
98
+ - **At session start**: `buildlog_overview()` for context
99
+ - **During active dev**: `buildlog_entry_new()` to document work
100
+ - **After commits**: `buildlog_commit()` or `buildlog_gauntlet_prompt()` + review + `buildlog_gauntlet_issues()`
101
+ - **After review approval**: `buildlog_log_reward(outcome="accepted")`
102
+ - **For learning**: `buildlog_distill()`, `buildlog_skills()`, `buildlog_stats()`
103
+ - **For skill promotion**: `buildlog_status()` -> `buildlog_diff()` -> `buildlog_promote()`
104
+ - **To accept risk**: `buildlog_gauntlet_accept_risk()`
105
+ - **For experiments**: `buildlog_experiment_start()` -> work -> `buildlog_experiment_end()`
106
+
107
+ ### Integration with Commits
108
+
109
+ `buildlog commit -m "message"` wraps git commit and auto-logs to today's entry.
110
+
111
+ ### Reference Files
112
+
113
+ - `buildlog/.buildlog/promoted.json` — promoted skill IDs
114
+ - `buildlog/.buildlog/rejected.json` — rejected skill IDs
115
+ - `buildlog/.buildlog/review_learnings.json` — review-based learnings
116
+ - `buildlog/.buildlog/reward_events.jsonl` — reward signals
117
+ - `buildlog/.buildlog/sessions.jsonl` — session tracking
118
+ - `buildlog/.buildlog/mistakes.jsonl` — mistake tracking
119
+ - `buildlog/.buildlog/active_session.json` — current session
120
+ - `buildlog/bandit_state.jsonl` — Thompson Sampling state
121
+ """
buildlog/core/__init__.py CHANGED
@@ -1,14 +1,23 @@
1
1
  """Core operations for buildlog skill management."""
2
2
 
3
3
  from buildlog.core.operations import (
4
+ CommitResult,
5
+ CreateEntryResult,
4
6
  DiffResult,
5
7
  EndSessionResult,
6
8
  GauntletAcceptRiskResult,
9
+ GauntletGenerateResult,
10
+ GauntletLoopConfigResult,
7
11
  GauntletLoopResult,
12
+ GauntletPromptResult,
13
+ GauntletRulesResult,
14
+ InitResult,
8
15
  LearnFromReviewResult,
16
+ ListEntriesResult,
9
17
  LogMistakeResult,
10
18
  LogRewardResult,
11
19
  Mistake,
20
+ OverviewResult,
12
21
  PromoteResult,
13
22
  RejectResult,
14
23
  ReviewIssue,
@@ -19,22 +28,33 @@ from buildlog.core.operations import (
19
28
  SessionMetrics,
20
29
  StartSessionResult,
21
30
  StatusResult,
31
+ UpdateResult,
32
+ commit,
33
+ create_entry,
22
34
  diff,
23
35
  end_session,
24
36
  find_skills_by_ids,
25
37
  gauntlet_accept_risk,
38
+ gauntlet_generate,
39
+ gauntlet_loop_config,
26
40
  gauntlet_process_issues,
41
+ generate_gauntlet_prompt,
27
42
  get_bandit_status,
28
43
  get_experiment_report,
44
+ get_gauntlet_rules,
45
+ get_overview,
29
46
  get_rewards,
30
47
  get_session_metrics,
48
+ init_buildlog,
31
49
  learn_from_review,
50
+ list_entries,
32
51
  log_mistake,
33
52
  log_reward,
34
53
  promote,
35
54
  reject,
36
55
  start_session,
37
56
  status,
57
+ update_buildlog,
38
58
  )
39
59
 
40
60
  __all__ = [
@@ -58,6 +78,11 @@ __all__ = [
58
78
  # Gauntlet loop
59
79
  "GauntletLoopResult",
60
80
  "GauntletAcceptRiskResult",
81
+ # Entry & overview
82
+ "GauntletRulesResult",
83
+ "OverviewResult",
84
+ "CreateEntryResult",
85
+ "ListEntriesResult",
61
86
  "status",
62
87
  "promote",
63
88
  "reject",
@@ -76,4 +101,23 @@ __all__ = [
76
101
  # Gauntlet loop operations
77
102
  "gauntlet_process_issues",
78
103
  "gauntlet_accept_risk",
104
+ # Entry & overview operations
105
+ "get_gauntlet_rules",
106
+ "get_overview",
107
+ "create_entry",
108
+ "list_entries",
109
+ # P0: Gauntlet loop
110
+ "CommitResult",
111
+ "GauntletPromptResult",
112
+ "GauntletLoopConfigResult",
113
+ "commit",
114
+ "generate_gauntlet_prompt",
115
+ "gauntlet_loop_config",
116
+ # P2: Nice-to-have
117
+ "GauntletGenerateResult",
118
+ "InitResult",
119
+ "UpdateResult",
120
+ "gauntlet_generate",
121
+ "init_buildlog",
122
+ "update_buildlog",
79
123
  ]