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.
- buildlog/cli.py +304 -26
- buildlog/constants.py +160 -0
- buildlog/core/__init__.py +44 -0
- buildlog/core/operations.py +1170 -0
- buildlog/data/seeds/bragi.yaml +61 -0
- buildlog/mcp/__init__.py +51 -3
- buildlog/mcp/server.py +36 -0
- buildlog/mcp/tools.py +526 -12
- {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/post_gen.py +10 -5
- buildlog-0.10.1.data/data/share/buildlog/template/buildlog/.gitkeep +0 -0
- buildlog-0.10.1.data/data/share/buildlog/template/buildlog/assets/.gitkeep +0 -0
- {buildlog-0.9.0.dist-info → buildlog-0.10.1.dist-info}/METADATA +28 -26
- {buildlog-0.9.0.dist-info → buildlog-0.10.1.dist-info}/RECORD +23 -19
- {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/copier.yml +0 -0
- {buildlog-0.9.0.data/data/share/buildlog/template/buildlog → buildlog-0.10.1.data/data/share/buildlog/template/buildlog/.buildlog}/.gitkeep +0 -0
- {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
- {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/template/buildlog/2026-01-01-example.md +0 -0
- {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
- {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/template/buildlog/_TEMPLATE.md +0 -0
- {buildlog-0.9.0.data → buildlog-0.10.1.data}/data/share/buildlog/template/buildlog/_TEMPLATE_QUICK.md +0 -0
- {buildlog-0.9.0.dist-info → buildlog-0.10.1.dist-info}/WHEEL +0 -0
- {buildlog-0.9.0.dist-info → buildlog-0.10.1.dist-info}/entry_points.txt +0 -0
- {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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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
|
-
|
|
666
|
-
|
|
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
|
-
|
|
748
|
-
|
|
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
|
-
|
|
953
|
-
|
|
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
|
-
|
|
1108
|
-
|
|
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
|
+
"""
|