buildlog 0.9.0__py3-none-any.whl → 0.10.1__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 +304 -26
  2. buildlog/constants.py +160 -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.1.data}/data/share/buildlog/post_gen.py +10 -5
  10. buildlog-0.10.1.data/data/share/buildlog/template/buildlog/.gitkeep +0 -0
  11. buildlog-0.10.1.data/data/share/buildlog/template/buildlog/assets/.gitkeep +0 -0
  12. {buildlog-0.9.0.dist-info → buildlog-0.10.1.dist-info}/METADATA +28 -26
  13. {buildlog-0.9.0.dist-info → buildlog-0.10.1.dist-info}/RECORD +23 -19
  14. {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/copier.yml +0 -0
  15. {buildlog-0.9.0.data/data/share/buildlog/template/buildlog → buildlog-0.10.1.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.1.data/data/share/buildlog/template/buildlog/.buildlog/seeds}/.gitkeep +0 -0
  17. {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/template/buildlog/2026-01-01-example.md +0 -0
  18. {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
  19. {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/template/buildlog/_TEMPLATE.md +0 -0
  20. {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/template/buildlog/_TEMPLATE_QUICK.md +0 -0
  21. {buildlog-0.9.0.dist-info → buildlog-0.10.1.dist-info}/WHEEL +0 -0
  22. {buildlog-0.9.0.dist-info → buildlog-0.10.1.dist-info}/entry_points.txt +0 -0
  23. {buildlog-0.9.0.dist-info → buildlog-0.10.1.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,161 @@ 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, also writes usage instructions to ~/.claude/CLAUDE.md
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
+ mcp_already_registered = "buildlog" in data["mcpServers"]
194
+
195
+ if not mcp_already_registered:
196
+ data["mcpServers"]["buildlog"] = {"command": "buildlog-mcp", "args": []}
197
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
198
+ settings_path.write_text(json_module.dumps(data, indent=2) + "\n")
199
+ click.echo(f"Registered buildlog MCP server in {location}")
200
+ else:
201
+ click.echo(f"buildlog MCP server already registered in {location}")
202
+
203
+ # In global mode, also write/update ~/.claude/CLAUDE.md with usage instructions
204
+ if global_mode:
205
+ _init_global_claude_md(settings_path.parent)
206
+ click.echo(
207
+ "Claude Code now has buildlog tools + instructions in all projects."
208
+ )
209
+
210
+ except Exception as e:
211
+ click.echo(f"Warning: could not register MCP server: {e}", err=True)
212
+
213
+
214
+ def _init_global_claude_md(claude_dir: Path) -> None:
215
+ """Write buildlog usage instructions to ~/.claude/CLAUDE.md.
216
+
217
+ Creates or appends to the global CLAUDE.md so Claude knows how to use
218
+ buildlog tools automatically in any project.
219
+ """
220
+ from buildlog.constants import CLAUDE_MD_GLOBAL_SECTION
221
+
222
+ claude_md_path = claude_dir / "CLAUDE.md"
223
+
224
+ try:
225
+ if claude_md_path.exists():
226
+ content = claude_md_path.read_text()
227
+ # Check if buildlog section already exists
228
+ if "## buildlog" in content:
229
+ click.echo("buildlog instructions already in ~/.claude/CLAUDE.md")
230
+ return
231
+ # Append to existing file
232
+ with open(claude_md_path, "a") as f:
233
+ f.write(CLAUDE_MD_GLOBAL_SECTION)
234
+ click.echo("Added buildlog instructions to ~/.claude/CLAUDE.md")
235
+ else:
236
+ # Create new file with header
237
+ header = "# Global Claude Instructions\n\nThese instructions apply to all projects.\n"
238
+ claude_md_path.write_text(header + CLAUDE_MD_GLOBAL_SECTION)
239
+ click.echo("Created ~/.claude/CLAUDE.md with buildlog instructions")
240
+ except Exception as e:
241
+ click.echo(f"Warning: could not write CLAUDE.md: {e}", err=True)
242
+
243
+
244
+ @main.command("init-mcp")
245
+ @click.option(
246
+ "--global",
247
+ "global_",
248
+ is_flag=True,
249
+ help="Register globally in ~/.claude/settings.json (works in any project)",
250
+ )
251
+ def init_mcp(global_: bool):
252
+ """Register buildlog as an MCP server for Claude Code.
253
+
254
+ Creates or updates .claude/settings.json with the buildlog MCP
255
+ server configuration. Idempotent — safe to run multiple times.
256
+
257
+ Use --global to register in ~/.claude/settings.json so buildlog
258
+ tools are available in every project without per-project init.
259
+
260
+ Examples:
261
+
262
+ buildlog init-mcp # local (current project)
263
+ buildlog init-mcp --global # global (all projects)
264
+ """
265
+ if global_:
266
+ settings_path = Path.home() / ".claude" / "settings.json"
267
+ _init_mcp(settings_path=settings_path, global_mode=True)
268
+ else:
269
+ _init_mcp()
270
+
271
+
272
+ @main.command("mcp-test")
273
+ def mcp_test():
274
+ """Verify the MCP server starts and all tools are registered.
275
+
276
+ Checks that the buildlog-mcp server can be imported and lists
277
+ all registered tools. Exits 0 if all 29 tools are found, 1 otherwise.
278
+
279
+ Examples:
280
+
281
+ buildlog mcp-test
282
+ """
283
+ try:
284
+ from buildlog.mcp.server import mcp as mcp_server
285
+ except ImportError:
286
+ click.echo("MCP not installed. Run: pip install buildlog", err=True)
287
+ raise SystemExit(1)
288
+
289
+ try:
290
+ # FastMCP stores tools internally
291
+ tools = mcp_server._tool_manager._tools
292
+ tool_names = sorted(tools.keys())
293
+ except AttributeError:
294
+ # Fallback: try to count via the public API pattern
295
+ click.echo("Warning: could not inspect tools via internal API", err=True)
296
+ tool_names = []
297
+
298
+ expected = 29
299
+ click.echo(f"buildlog MCP server: {len(tool_names)} tools registered")
300
+ for name in tool_names:
301
+ click.echo(f" {name}")
302
+
303
+ if len(tool_names) >= expected:
304
+ click.echo(f"\nAll {expected} tools registered.")
305
+ raise SystemExit(0)
306
+ else:
307
+ click.echo(f"\nExpected {expected} tools, found {len(tool_names)}.", err=True)
308
+ raise SystemExit(1)
309
+
310
+
145
311
  @main.command()
146
312
  @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
147
313
  def overview(output_json: bool):
148
314
  """Show the full state of your buildlog at a glance.
149
315
 
150
316
  Entries, skills, promoted rules, experiments — everything in one view.
317
+ Works even without buildlog init (shows uninitialized state).
151
318
 
152
319
  Examples:
153
320
 
@@ -158,9 +325,38 @@ def overview(output_json: bool):
158
325
 
159
326
  buildlog_dir = Path("buildlog")
160
327
 
328
+ # Handle uninitialized state gracefully
161
329
  if not buildlog_dir.exists():
162
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
163
- raise SystemExit(1)
330
+ result = {
331
+ "initialized": False,
332
+ "entries": 0,
333
+ "skills": {
334
+ "total": 0,
335
+ "by_confidence": {},
336
+ "promoted": 0,
337
+ "rejected": 0,
338
+ "pending": 0,
339
+ },
340
+ "active_session": None,
341
+ "render_targets": [],
342
+ "message": "buildlog not initialized. Run 'buildlog init' to enable full features.",
343
+ }
344
+ if output_json:
345
+ click.echo(json_module.dumps(result, indent=2))
346
+ else:
347
+ click.echo("buildlog overview")
348
+ click.echo("=" * 40)
349
+ click.echo(" Status: Not initialized")
350
+ click.echo()
351
+ click.echo("Get started:")
352
+ click.echo(" buildlog init --defaults # Initialize buildlog")
353
+ click.echo()
354
+ click.echo("Or use globally without init:")
355
+ click.echo(" buildlog init-mcp --global # Register MCP server globally")
356
+ click.echo(
357
+ " buildlog gauntlet list # Review personas work without init"
358
+ )
359
+ return
164
360
 
165
361
  # Count entries
166
362
  entries = sorted(buildlog_dir.glob("20??-??-??-*.md"))
@@ -208,6 +404,7 @@ def overview(output_json: bool):
208
404
  from buildlog.render import RENDERERS
209
405
 
210
406
  result = {
407
+ "initialized": True,
211
408
  "entries": len(entries),
212
409
  "skills": {
213
410
  "total": total_skills,
@@ -586,7 +783,7 @@ def distill(
586
783
  """Extract patterns from all buildlog entries.
587
784
 
588
785
  Parses the Improvements section of each buildlog entry and aggregates
589
- insights into structured output (JSON or YAML).
786
+ insights into structured output (JSON or YAML). Returns empty result if not initialized.
590
787
 
591
788
  Examples:
592
789
 
@@ -596,11 +793,25 @@ def distill(
596
793
  buildlog distill --since 2026-01-01 # Filter by date
597
794
  buildlog distill --category workflow # Filter by category
598
795
  """
796
+ import json as json_module
797
+
599
798
  buildlog_dir = Path("buildlog")
600
799
 
800
+ # Handle uninitialized state gracefully
601
801
  if not buildlog_dir.exists():
602
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
603
- raise SystemExit(1)
802
+ empty_result = {
803
+ "initialized": False,
804
+ "patterns": [],
805
+ "statistics": {"total_patterns": 0, "total_entries": 0},
806
+ "message": "buildlog not initialized",
807
+ }
808
+ if fmt == "json":
809
+ click.echo(json_module.dumps(empty_result, indent=2))
810
+ else:
811
+ click.echo(
812
+ "# buildlog not initialized - run 'buildlog init' first\npatterns: []"
813
+ )
814
+ return
604
815
 
605
816
  # Convert datetime to date if provided
606
817
  since_date = since.date() if since else None
@@ -651,6 +862,7 @@ def stats(output_json: bool, detailed: bool, since_date: str | None):
651
862
  """Show buildlog statistics and analytics.
652
863
 
653
864
  Provides insights on buildlog usage, coverage, and quality.
865
+ Returns empty stats if not initialized.
654
866
 
655
867
  Examples:
656
868
 
@@ -659,11 +871,27 @@ def stats(output_json: bool, detailed: bool, since_date: str | None):
659
871
  buildlog stats --detailed # Include top sources
660
872
  buildlog stats --since 2026-01-01
661
873
  """
874
+ import json as json_module
875
+
662
876
  buildlog_dir = Path("buildlog")
663
877
 
878
+ # Handle uninitialized state gracefully
664
879
  if not buildlog_dir.exists():
665
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
666
- raise SystemExit(1)
880
+ empty_stats = {
881
+ "initialized": False,
882
+ "total_entries": 0,
883
+ "total_patterns": 0,
884
+ "categories": {},
885
+ "date_range": None,
886
+ "message": "buildlog not initialized",
887
+ }
888
+ if output_json:
889
+ click.echo(json_module.dumps(empty_stats, indent=2))
890
+ else:
891
+ click.echo("buildlog stats")
892
+ click.echo("=" * 40)
893
+ click.echo(" Not initialized. Run 'buildlog init' first.")
894
+ return
667
895
 
668
896
  # Parse since date if provided
669
897
  parsed_since = None
@@ -726,7 +954,7 @@ def skills(
726
954
  """Generate agent-consumable skills from buildlog patterns.
727
955
 
728
956
  Transforms distilled patterns into actionable rules with deduplication,
729
- confidence scoring, and stable IDs.
957
+ confidence scoring, and stable IDs. Returns empty set if not initialized.
730
958
 
731
959
  Examples:
732
960
 
@@ -743,9 +971,31 @@ def skills(
743
971
  """
744
972
  buildlog_dir = Path("buildlog")
745
973
 
974
+ # Handle uninitialized state gracefully - return empty skill set
746
975
  if not buildlog_dir.exists():
747
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
748
- raise SystemExit(1)
976
+ if fmt == "json":
977
+ import json as json_module
978
+
979
+ click.echo(
980
+ json_module.dumps(
981
+ {
982
+ "initialized": False,
983
+ "skills": {},
984
+ "total_skills": 0,
985
+ "message": "buildlog not initialized",
986
+ },
987
+ indent=2,
988
+ )
989
+ )
990
+ elif fmt == "yaml":
991
+ click.echo(
992
+ "# buildlog not initialized - run 'buildlog init' first\nskills: {}\ntotal_skills: 0"
993
+ )
994
+ else:
995
+ click.echo(
996
+ "No buildlog/ directory found. Run 'buildlog init' to extract skills."
997
+ )
998
+ return
749
999
 
750
1000
  # Convert datetime to date if provided
751
1001
  since_date = since.date() if since else None
@@ -935,7 +1185,7 @@ def status_cmd(min_confidence: str, output_json: bool):
935
1185
  """Show extracted skills by category and confidence.
936
1186
 
937
1187
  Displays all skills extracted from buildlog entries, grouped by category,
938
- with confidence levels and promotion status.
1188
+ with confidence levels and promotion status. Returns empty state if not initialized.
939
1189
 
940
1190
  Examples:
941
1191
 
@@ -948,9 +1198,23 @@ def status_cmd(min_confidence: str, output_json: bool):
948
1198
 
949
1199
  buildlog_dir = Path("buildlog")
950
1200
 
1201
+ # Handle uninitialized state gracefully
951
1202
  if not buildlog_dir.exists():
952
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
953
- raise SystemExit(1)
1203
+ empty_result = {
1204
+ "initialized": False,
1205
+ "total_skills": 0,
1206
+ "total_entries": 0,
1207
+ "skills": {},
1208
+ "by_confidence": {"high": 0, "medium": 0, "low": 0},
1209
+ "promotable_ids": [],
1210
+ "error": None,
1211
+ }
1212
+ if output_json:
1213
+ click.echo(json_module.dumps(empty_result, indent=2))
1214
+ else:
1215
+ click.echo("Skills: 0 total from 0 entries")
1216
+ click.echo(" buildlog not initialized. Run 'buildlog init' first.")
1217
+ return
954
1218
 
955
1219
  result = status(buildlog_dir, min_confidence=min_confidence) # type: ignore[arg-type]
956
1220
 
@@ -1092,6 +1356,7 @@ def diff_cmd(output_json: bool):
1092
1356
  """Show skills pending review (not yet promoted or rejected).
1093
1357
 
1094
1358
  Useful for seeing what's new since the last time you reviewed skills.
1359
+ Returns empty diff if not initialized.
1095
1360
 
1096
1361
  Examples:
1097
1362
 
@@ -1103,9 +1368,22 @@ def diff_cmd(output_json: bool):
1103
1368
 
1104
1369
  buildlog_dir = Path("buildlog")
1105
1370
 
1371
+ # Handle uninitialized state gracefully
1106
1372
  if not buildlog_dir.exists():
1107
- click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
1108
- raise SystemExit(1)
1373
+ empty_result = {
1374
+ "initialized": False,
1375
+ "pending": {},
1376
+ "total_pending": 0,
1377
+ "already_promoted": 0,
1378
+ "already_rejected": 0,
1379
+ "error": None,
1380
+ }
1381
+ if output_json:
1382
+ click.echo(json_module.dumps(empty_result, indent=2))
1383
+ else:
1384
+ click.echo("Pending: 0 | Promoted: 0 | Rejected: 0")
1385
+ click.echo(" buildlog not initialized. Run 'buildlog init' first.")
1386
+ return
1109
1387
 
1110
1388
  result = core_diff(buildlog_dir)
1111
1389
 
buildlog/constants.py ADDED
@@ -0,0 +1,160 @@
1
+ """Shared constants for buildlog, including the CLAUDE.md integration section."""
2
+
3
+ # Shorter version for global ~/.claude/CLAUDE.md - focused on "always on" usage
4
+ CLAUDE_MD_GLOBAL_SECTION = """
5
+ ## buildlog (Always On)
6
+
7
+ buildlog is your ambient data capture layer. It runs in ALL projects via MCP.
8
+ Use these tools to track work, extract learnings, and feed downstream systems.
9
+
10
+ ### Core Loop (use after every significant commit)
11
+
12
+ 1. `buildlog_overview()` — check project state
13
+ 2. `buildlog_commit(message="...")` — git commit with auto entry logging
14
+ 3. `buildlog_gauntlet_rules()` + review + `buildlog_gauntlet_issues([...])` — run gauntlet
15
+ 4. `buildlog_log_reward(outcome="accepted")` — close the feedback loop
16
+
17
+ ### Key Tools
18
+
19
+ | Tool | When to Use |
20
+ |------|-------------|
21
+ | `buildlog_overview()` | Start of session, check state |
22
+ | `buildlog_commit(message)` | Wrap git commits with logging |
23
+ | `buildlog_entry_new(slug)` | Create journal entry |
24
+ | `buildlog_gauntlet_rules()` | Load reviewer personas |
25
+ | `buildlog_gauntlet_issues(issues)` | Process review findings |
26
+ | `buildlog_log_reward(outcome)` | Feedback after approval |
27
+ | `buildlog_skills()` | Extract patterns from entries |
28
+ | `buildlog_status()` | See extracted skills |
29
+ | `buildlog_promote(skill_ids)` | Surface to agent rules |
30
+
31
+ ### Outputs (ambient capture for downstream)
32
+
33
+ - Journal entries: `buildlog/*.md`
34
+ - Reward signals: `buildlog/.buildlog/reward_events.jsonl`
35
+ - Extracted skills: `buildlog/.buildlog/promoted.json`
36
+ - Review learnings: `buildlog/.buildlog/review_learnings.json`
37
+
38
+ This data feeds automated content generation, engineering logs, and learning systems.
39
+ """
40
+
41
+ # Full version for per-project CLAUDE.md - comprehensive reference
42
+ CLAUDE_MD_BUILDLOG_SECTION = """
43
+ ## buildlog Integration
44
+
45
+ buildlog is configured as an MCP server. Use the tools below to maintain
46
+ a learning loop: write entries, extract skills, run gauntlet reviews, and
47
+ track experiments.
48
+
49
+ ### Always On: Commit -> Gauntlet -> Learn Loop
50
+
51
+ After every significant commit or feature completion:
52
+
53
+ 1. **Create/update entry**: `buildlog_entry_new(slug="what-you-built")`
54
+ 2. **Run gauntlet review**: `buildlog_gauntlet_rules()` to load rules, then review code against them
55
+ 3. **Process findings**: `buildlog_gauntlet_issues(issues=[...])` to categorize and persist learnings
56
+ 4. **Fix criticals/majors**, re-run until clean or accept risk: `buildlog_gauntlet_accept_risk(remaining_issues=[...])`
57
+ 5. **Log reward**: `buildlog_log_reward(outcome="accepted")` when work is approved
58
+
59
+ ### Skill Extraction & Promotion Workflow
60
+
61
+ 1. `buildlog_status()` — see extracted skills by category
62
+ 2. `buildlog_diff()` — see skills pending review
63
+ 3. `buildlog_promote(skill_ids=[...], target="claude_md")` — surface to agent rules
64
+ 4. `buildlog_reject(skill_ids=[...])` — mark false positives
65
+
66
+ ### Gauntlet Review Loop Workflow
67
+
68
+ 1. `buildlog_gauntlet_rules()` — load reviewer persona rules
69
+ 2. Review code against rules, collect issues
70
+ 3. `buildlog_gauntlet_issues(issues=[...])` — categorize, persist learnings, get next action
71
+ 4. If action="fix_criticals": fix and re-run
72
+ 5. If action="checkpoint_majors"/"checkpoint_minors": ask user
73
+ 6. `buildlog_gauntlet_accept_risk(remaining_issues=[...])` — accept remaining
74
+ 7. `buildlog_learn_from_review(issues=[...])` — persist learnings
75
+
76
+ ### Reward Signal / Thompson Sampling Workflow
77
+
78
+ 1. `buildlog_log_reward(outcome="accepted"|"revision"|"rejected")` — explicit feedback
79
+ 2. `buildlog_rewards()` — view reward history and statistics
80
+ 3. `buildlog_bandit_status()` — see which rules the bandit favors
81
+
82
+ ### Session Tracking & Experiment Workflow
83
+
84
+ 1. `buildlog_experiment_start(error_class="missing_test")` — begin tracked session
85
+ 2. `buildlog_log_mistake(error_class="...", description="...")` — log mistakes
86
+ 3. `buildlog_experiment_end()` — end session, calculate metrics
87
+ 4. `buildlog_experiment_metrics()` — per-session or aggregate stats
88
+ 5. `buildlog_experiment_report()` — comprehensive report
89
+
90
+ ### Tool Reference (29 tools)
91
+
92
+ **Commit & Entries:**
93
+ - `buildlog_commit(message, git_args, slug, no_entry)` — git commit with auto buildlog entry
94
+ - `buildlog_entry_new(slug, entry_date, quick)` — create entry
95
+ - `buildlog_entry_list()` — list all entries
96
+ - `buildlog_overview()` — project state at a glance
97
+
98
+ **Skill Management:**
99
+ - `buildlog_status(min_confidence="low")` — extracted skills
100
+ - `buildlog_promote(skill_ids, target="claude_md")` — promote to agent rules
101
+ - `buildlog_reject(skill_ids)` — reject false positives
102
+ - `buildlog_diff()` — pending skills
103
+ - `buildlog_distill(since, category)` — extract patterns from entries
104
+ - `buildlog_skills(since, min_frequency)` — generate skill set from entries
105
+ - `buildlog_stats(since, detailed)` — buildlog statistics and insights
106
+
107
+ **Gauntlet Review:**
108
+ - `buildlog_gauntlet_prompt(target, personas)` — generate review prompt with rules
109
+ - `buildlog_gauntlet_loop(target, personas, max_iterations, stop_at, auto_gh_issues)` — full loop config
110
+ - `buildlog_gauntlet_rules(persona, format)` — load reviewer rules
111
+ - `buildlog_gauntlet_issues(issues, iteration)` — process findings
112
+ - `buildlog_gauntlet_accept_risk(remaining_issues)` — accept risk
113
+ - `buildlog_gauntlet_list_personas()` — list available reviewer personas
114
+ - `buildlog_gauntlet_generate(source_text, persona, dry_run)` — generate rules from source text
115
+
116
+ **Review Learning:**
117
+ - `buildlog_learn_from_review(issues, source)` — persist review learnings
118
+
119
+ **Reward & Bandit:**
120
+ - `buildlog_log_reward(outcome, rules_active)` — log feedback
121
+ - `buildlog_rewards(limit)` — reward history
122
+ - `buildlog_bandit_status(context)` — bandit state
123
+
124
+ **Experiments:**
125
+ - `buildlog_experiment_start(error_class)` — start session
126
+ - `buildlog_experiment_end()` — end session
127
+ - `buildlog_log_mistake(error_class, description)` — log mistake
128
+ - `buildlog_experiment_metrics(session_id)` — metrics
129
+ - `buildlog_experiment_report()` — full report
130
+
131
+ **Project Setup:**
132
+ - `buildlog_init(no_mcp, no_claude_md)` — initialize buildlog in project
133
+ - `buildlog_update()` — update buildlog template to latest
134
+
135
+ ### When to Use Each Tool
136
+
137
+ - **At session start**: `buildlog_overview()` for context
138
+ - **During active dev**: `buildlog_entry_new()` to document work
139
+ - **After commits**: `buildlog_commit()` or `buildlog_gauntlet_prompt()` + review + `buildlog_gauntlet_issues()`
140
+ - **After review approval**: `buildlog_log_reward(outcome="accepted")`
141
+ - **For learning**: `buildlog_distill()`, `buildlog_skills()`, `buildlog_stats()`
142
+ - **For skill promotion**: `buildlog_status()` -> `buildlog_diff()` -> `buildlog_promote()`
143
+ - **To accept risk**: `buildlog_gauntlet_accept_risk()`
144
+ - **For experiments**: `buildlog_experiment_start()` -> work -> `buildlog_experiment_end()`
145
+
146
+ ### Integration with Commits
147
+
148
+ `buildlog commit -m "message"` wraps git commit and auto-logs to today's entry.
149
+
150
+ ### Reference Files
151
+
152
+ - `buildlog/.buildlog/promoted.json` — promoted skill IDs
153
+ - `buildlog/.buildlog/rejected.json` — rejected skill IDs
154
+ - `buildlog/.buildlog/review_learnings.json` — review-based learnings
155
+ - `buildlog/.buildlog/reward_events.jsonl` — reward signals
156
+ - `buildlog/.buildlog/sessions.jsonl` — session tracking
157
+ - `buildlog/.buildlog/mistakes.jsonl` — mistake tracking
158
+ - `buildlog/.buildlog/active_session.json` — current session
159
+ - `buildlog/bandit_state.jsonl` — Thompson Sampling state
160
+ """