buildlog 0.8.0__py3-none-any.whl → 0.9.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.
- buildlog/cli.py +223 -4
- buildlog/core/operations.py +19 -13
- buildlog/llm.py +51 -4
- buildlog/mcp/server.py +4 -0
- buildlog/seed_engine/__init__.py +2 -0
- buildlog/seed_engine/llm_extractor.py +121 -0
- buildlog/seed_engine/pipeline.py +45 -1
- buildlog-0.9.0.dist-info/METADATA +248 -0
- {buildlog-0.8.0.dist-info → buildlog-0.9.0.dist-info}/RECORD +20 -19
- buildlog-0.8.0.dist-info/METADATA +0 -151
- {buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/copier.yml +0 -0
- {buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/post_gen.py +0 -0
- {buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/.gitkeep +0 -0
- {buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/2026-01-01-example.md +0 -0
- {buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
- {buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE.md +0 -0
- {buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE_QUICK.md +0 -0
- {buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/assets/.gitkeep +0 -0
- {buildlog-0.8.0.dist-info → buildlog-0.9.0.dist-info}/WHEEL +0 -0
- {buildlog-0.8.0.dist-info → buildlog-0.9.0.dist-info}/entry_points.txt +0 -0
- {buildlog-0.8.0.dist-info → buildlog-0.9.0.dist-info}/licenses/LICENSE +0 -0
buildlog/cli.py
CHANGED
|
@@ -298,11 +298,11 @@ def new(slug: str, entry_date: str | None, quick: bool):
|
|
|
298
298
|
# Determine date
|
|
299
299
|
if entry_date:
|
|
300
300
|
try:
|
|
301
|
-
# Validate date format
|
|
302
|
-
|
|
303
|
-
date_str =
|
|
301
|
+
# Validate date format AND range (month 1-12, day 1-31)
|
|
302
|
+
parsed = datetime.strptime(entry_date, "%Y-%m-%d").date()
|
|
303
|
+
date_str = parsed.isoformat()
|
|
304
304
|
except ValueError:
|
|
305
|
-
click.echo("Invalid date
|
|
305
|
+
click.echo("Invalid date. Use YYYY-MM-DD with valid values.", err=True)
|
|
306
306
|
raise SystemExit(1)
|
|
307
307
|
else:
|
|
308
308
|
date_str = date.today().isoformat()
|
|
@@ -331,6 +331,161 @@ def new(slug: str, entry_date: str | None, quick: bool):
|
|
|
331
331
|
click.echo(f"\nOpen it: $EDITOR {entry_path}")
|
|
332
332
|
|
|
333
333
|
|
|
334
|
+
@main.command(
|
|
335
|
+
context_settings={"ignore_unknown_options": True, "allow_extra_args": True}
|
|
336
|
+
)
|
|
337
|
+
@click.option(
|
|
338
|
+
"--slug",
|
|
339
|
+
"-s",
|
|
340
|
+
default=None,
|
|
341
|
+
help="Entry slug (default: derived from branch name)",
|
|
342
|
+
)
|
|
343
|
+
@click.option(
|
|
344
|
+
"--entry",
|
|
345
|
+
"-e",
|
|
346
|
+
type=click.Path(),
|
|
347
|
+
default=None,
|
|
348
|
+
help="Explicit entry file to append to",
|
|
349
|
+
)
|
|
350
|
+
@click.option(
|
|
351
|
+
"--no-entry",
|
|
352
|
+
is_flag=True,
|
|
353
|
+
help="Skip buildlog entry update (just run git commit)",
|
|
354
|
+
)
|
|
355
|
+
@click.pass_context
|
|
356
|
+
def commit(ctx, slug: str | None, entry: str | None, no_entry: bool):
|
|
357
|
+
"""Commit code and update the buildlog entry in one step.
|
|
358
|
+
|
|
359
|
+
Wraps `git commit` and appends commit context to today's buildlog
|
|
360
|
+
entry. If no entry exists for today, creates one automatically.
|
|
361
|
+
|
|
362
|
+
All unknown options/arguments are passed through to git commit.
|
|
363
|
+
|
|
364
|
+
Examples:
|
|
365
|
+
|
|
366
|
+
buildlog commit -m "feat: add LLM extractor"
|
|
367
|
+
buildlog commit --slug llm-extractor -m "feat: add LLM extractor"
|
|
368
|
+
buildlog commit --no-entry -m "chore: formatting"
|
|
369
|
+
"""
|
|
370
|
+
buildlog_dir = Path("buildlog")
|
|
371
|
+
|
|
372
|
+
# Build git commit command — extra args are passed through from Click context
|
|
373
|
+
git_cmd = ["git", "commit", *ctx.args]
|
|
374
|
+
|
|
375
|
+
# Run git commit first
|
|
376
|
+
result = subprocess.run(git_cmd, capture_output=True, text=True)
|
|
377
|
+
sys.stdout.write(result.stdout)
|
|
378
|
+
sys.stderr.write(result.stderr)
|
|
379
|
+
|
|
380
|
+
if result.returncode != 0:
|
|
381
|
+
raise SystemExit(result.returncode)
|
|
382
|
+
|
|
383
|
+
if no_entry or not buildlog_dir.exists():
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
# Get commit info from what we just committed
|
|
387
|
+
try:
|
|
388
|
+
commit_hash = subprocess.run(
|
|
389
|
+
["git", "rev-parse", "--short", "HEAD"],
|
|
390
|
+
capture_output=True,
|
|
391
|
+
text=True,
|
|
392
|
+
check=True,
|
|
393
|
+
).stdout.strip()
|
|
394
|
+
commit_msg = subprocess.run(
|
|
395
|
+
["git", "log", "-1", "--format=%s"],
|
|
396
|
+
capture_output=True,
|
|
397
|
+
text=True,
|
|
398
|
+
check=True,
|
|
399
|
+
).stdout.strip()
|
|
400
|
+
# diff-tree needs special handling for root commit
|
|
401
|
+
diff_result = subprocess.run(
|
|
402
|
+
["git", "diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"],
|
|
403
|
+
capture_output=True,
|
|
404
|
+
text=True,
|
|
405
|
+
)
|
|
406
|
+
if diff_result.returncode == 0 and diff_result.stdout.strip():
|
|
407
|
+
files_changed = diff_result.stdout.strip()
|
|
408
|
+
else:
|
|
409
|
+
# Root commit fallback
|
|
410
|
+
files_changed = subprocess.run(
|
|
411
|
+
["git", "diff", "--name-only", "--cached", "HEAD~1"],
|
|
412
|
+
capture_output=True,
|
|
413
|
+
text=True,
|
|
414
|
+
).stdout.strip()
|
|
415
|
+
if not files_changed:
|
|
416
|
+
# Truly initial commit — list all tracked files
|
|
417
|
+
files_changed = subprocess.run(
|
|
418
|
+
["git", "ls-tree", "--name-only", "-r", "HEAD"],
|
|
419
|
+
capture_output=True,
|
|
420
|
+
text=True,
|
|
421
|
+
).stdout.strip()
|
|
422
|
+
except subprocess.CalledProcessError:
|
|
423
|
+
click.echo("Warning: could not read commit info", err=True)
|
|
424
|
+
return
|
|
425
|
+
|
|
426
|
+
# Resolve entry file
|
|
427
|
+
today = date.today().isoformat()
|
|
428
|
+
entry_path = _resolve_entry_path(buildlog_dir, today, slug, entry)
|
|
429
|
+
|
|
430
|
+
# Append commit block
|
|
431
|
+
commit_block = f"\n### `{commit_hash}` — {commit_msg}\n\n"
|
|
432
|
+
if files_changed:
|
|
433
|
+
file_list = files_changed.split("\n")
|
|
434
|
+
commit_block += "Files:\n"
|
|
435
|
+
for f in file_list[:20]: # cap at 20 to avoid noise
|
|
436
|
+
commit_block += f"- `{f}`\n"
|
|
437
|
+
if len(file_list) > 20:
|
|
438
|
+
commit_block += f"- ...and {len(file_list) - 20} more\n"
|
|
439
|
+
commit_block += "\n"
|
|
440
|
+
|
|
441
|
+
# Ensure commits section exists, append to it
|
|
442
|
+
if entry_path.exists():
|
|
443
|
+
content = entry_path.read_text()
|
|
444
|
+
if "## Commits" not in content:
|
|
445
|
+
content = content.rstrip() + "\n\n## Commits\n"
|
|
446
|
+
content += commit_block
|
|
447
|
+
else:
|
|
448
|
+
# Auto-create minimal entry
|
|
449
|
+
content = f"# {today}\n\n## Commits\n{commit_block}"
|
|
450
|
+
|
|
451
|
+
entry_path.write_text(content)
|
|
452
|
+
click.echo(f"buildlog: updated {entry_path}")
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _resolve_entry_path(
|
|
456
|
+
buildlog_dir: Path, today: str, slug: str | None, explicit: str | None
|
|
457
|
+
) -> Path:
|
|
458
|
+
"""Find or create the entry path for today."""
|
|
459
|
+
if explicit:
|
|
460
|
+
return Path(explicit)
|
|
461
|
+
|
|
462
|
+
# Check for existing entry with today's date
|
|
463
|
+
existing = list(buildlog_dir.glob(f"{today}-*.md"))
|
|
464
|
+
if existing:
|
|
465
|
+
return existing[0]
|
|
466
|
+
|
|
467
|
+
# Derive slug from branch name if not provided
|
|
468
|
+
if slug is None:
|
|
469
|
+
try:
|
|
470
|
+
branch = subprocess.run(
|
|
471
|
+
["git", "branch", "--show-current"],
|
|
472
|
+
capture_output=True,
|
|
473
|
+
text=True,
|
|
474
|
+
check=True,
|
|
475
|
+
).stdout.strip()
|
|
476
|
+
# Clean branch name into slug
|
|
477
|
+
slug = branch.split("/")[-1] # strip prefix like feat/
|
|
478
|
+
slug = slug.lower().replace("_", "-")
|
|
479
|
+
slug = "".join(c for c in slug if c.isalnum() or c == "-")
|
|
480
|
+
except subprocess.CalledProcessError:
|
|
481
|
+
slug = "session"
|
|
482
|
+
|
|
483
|
+
if not slug:
|
|
484
|
+
slug = "session"
|
|
485
|
+
|
|
486
|
+
return buildlog_dir / f"{today}-{slug}.md"
|
|
487
|
+
|
|
488
|
+
|
|
334
489
|
@main.command("list")
|
|
335
490
|
def list_entries():
|
|
336
491
|
"""List all buildlog entries."""
|
|
@@ -1277,6 +1432,7 @@ PERSONAS = {
|
|
|
1277
1432
|
"security_karen": "OWASP Top 10 security review",
|
|
1278
1433
|
"test_terrorist": "Comprehensive testing coverage audit",
|
|
1279
1434
|
"ruthless_reviewer": "Code quality and functional principles",
|
|
1435
|
+
"bragi": "Detect and flag LLM-ish prose patterns in markdown",
|
|
1280
1436
|
}
|
|
1281
1437
|
|
|
1282
1438
|
|
|
@@ -1656,6 +1812,69 @@ def gauntlet_learn(issues_file: str, source: str | None, output_json: bool):
|
|
|
1656
1812
|
click.echo(f" Total processed: {result.total_issues_processed}")
|
|
1657
1813
|
|
|
1658
1814
|
|
|
1815
|
+
@gauntlet.command("generate")
|
|
1816
|
+
@click.argument("source_text", type=click.Path(exists=True))
|
|
1817
|
+
@click.option("--persona", "-p", required=True, help="Persona name for the seed file")
|
|
1818
|
+
@click.option(
|
|
1819
|
+
"--output-dir",
|
|
1820
|
+
"-o",
|
|
1821
|
+
type=click.Path(),
|
|
1822
|
+
default=".buildlog/seeds",
|
|
1823
|
+
help="Output directory for seed YAML",
|
|
1824
|
+
)
|
|
1825
|
+
@click.option("--dry-run", is_flag=True, help="Preview without writing")
|
|
1826
|
+
def gauntlet_generate(source_text: str, persona: str, output_dir: str, dry_run: bool):
|
|
1827
|
+
"""Generate seed rules from source text using LLM extraction.
|
|
1828
|
+
|
|
1829
|
+
Runs the seed engine pipeline with LLMExtractor to produce
|
|
1830
|
+
a YAML seed file from arbitrary source content.
|
|
1831
|
+
|
|
1832
|
+
Examples:
|
|
1833
|
+
|
|
1834
|
+
buildlog gauntlet generate docs/security.md --persona security_karen
|
|
1835
|
+
buildlog gauntlet generate notes.txt -p test_terrorist --dry-run
|
|
1836
|
+
"""
|
|
1837
|
+
import json as json_module
|
|
1838
|
+
|
|
1839
|
+
from buildlog.llm import get_llm_backend
|
|
1840
|
+
from buildlog.seed_engine import Pipeline, Source, SourceType
|
|
1841
|
+
|
|
1842
|
+
backend = get_llm_backend()
|
|
1843
|
+
if backend is None:
|
|
1844
|
+
click.echo(
|
|
1845
|
+
"No LLM backend available. Install ollama or set ANTHROPIC_API_KEY.",
|
|
1846
|
+
err=True,
|
|
1847
|
+
)
|
|
1848
|
+
raise SystemExit(1)
|
|
1849
|
+
|
|
1850
|
+
content = Path(source_text).read_text()
|
|
1851
|
+
source = Source(
|
|
1852
|
+
name=Path(source_text).stem,
|
|
1853
|
+
url=f"file://{Path(source_text).resolve()}",
|
|
1854
|
+
source_type=SourceType.REFERENCE_DOC,
|
|
1855
|
+
domain=persona.split("_")[0] if "_" in persona else "general",
|
|
1856
|
+
description=content,
|
|
1857
|
+
)
|
|
1858
|
+
|
|
1859
|
+
pipeline = Pipeline.with_llm(
|
|
1860
|
+
persona=persona,
|
|
1861
|
+
backend=backend,
|
|
1862
|
+
source_content={source.url: content},
|
|
1863
|
+
)
|
|
1864
|
+
|
|
1865
|
+
if dry_run:
|
|
1866
|
+
preview = pipeline.dry_run([source])
|
|
1867
|
+
click.echo(json_module.dumps(preview, indent=2))
|
|
1868
|
+
return
|
|
1869
|
+
|
|
1870
|
+
out = Path(output_dir)
|
|
1871
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
1872
|
+
result = pipeline.run([source], output_dir=out)
|
|
1873
|
+
click.echo(f"Generated {result.rule_count} rules for {persona}")
|
|
1874
|
+
if result.output_path:
|
|
1875
|
+
click.echo(f"Seed file: {result.output_path}")
|
|
1876
|
+
|
|
1877
|
+
|
|
1659
1878
|
@gauntlet.command("loop")
|
|
1660
1879
|
@click.argument("target", type=click.Path(exists=True))
|
|
1661
1880
|
@click.option(
|
buildlog/core/operations.py
CHANGED
|
@@ -653,7 +653,7 @@ def reject(
|
|
|
653
653
|
rejected = {"rejected_at": {}, "skill_ids": []}
|
|
654
654
|
|
|
655
655
|
# Add new rejections
|
|
656
|
-
now = datetime.now().isoformat()
|
|
656
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
657
657
|
newly_rejected: list[str] = []
|
|
658
658
|
for skill_id in skill_ids:
|
|
659
659
|
if skill_id not in rejected["skill_ids"]:
|
|
@@ -2035,6 +2035,18 @@ def gauntlet_process_issues(
|
|
|
2035
2035
|
)
|
|
2036
2036
|
|
|
2037
2037
|
|
|
2038
|
+
def _sanitize_for_gh(text: str, max_len: int = 256) -> str:
|
|
2039
|
+
"""Sanitize text for GitHub issue fields.
|
|
2040
|
+
|
|
2041
|
+
Defense-in-depth: we use list args (not shell=True) for subprocess,
|
|
2042
|
+
but sanitize anyway to prevent injection via gh's argument parsing.
|
|
2043
|
+
"""
|
|
2044
|
+
sanitized = text.replace("\n", " ").replace("\r", " ")
|
|
2045
|
+
if len(sanitized) > max_len:
|
|
2046
|
+
sanitized = sanitized[: max_len - 3] + "..."
|
|
2047
|
+
return sanitized.strip()
|
|
2048
|
+
|
|
2049
|
+
|
|
2038
2050
|
def gauntlet_accept_risk(
|
|
2039
2051
|
remaining_issues: list[dict],
|
|
2040
2052
|
create_github_issues: bool = False,
|
|
@@ -2062,17 +2074,6 @@ def gauntlet_accept_risk(
|
|
|
2062
2074
|
description = issue.get("description", "")
|
|
2063
2075
|
location = issue.get("location", "")
|
|
2064
2076
|
|
|
2065
|
-
# Sanitize inputs for GitHub issue creation
|
|
2066
|
-
# Note: We use list args (not shell=True), so this is defense-in-depth
|
|
2067
|
-
def _sanitize_for_gh(text: str, max_len: int = 256) -> str:
|
|
2068
|
-
"""Sanitize text for GitHub issue fields."""
|
|
2069
|
-
# Remove/replace problematic characters
|
|
2070
|
-
sanitized = text.replace("\n", " ").replace("\r", " ")
|
|
2071
|
-
# Truncate to max length
|
|
2072
|
-
if len(sanitized) > max_len:
|
|
2073
|
-
sanitized = sanitized[: max_len - 3] + "..."
|
|
2074
|
-
return sanitized.strip()
|
|
2075
|
-
|
|
2076
2077
|
safe_severity = _sanitize_for_gh(str(severity), 20)
|
|
2077
2078
|
safe_rule = _sanitize_for_gh(str(rule), 200)
|
|
2078
2079
|
safe_description = _sanitize_for_gh(str(description), 1000)
|
|
@@ -2116,7 +2117,9 @@ def gauntlet_accept_risk(
|
|
|
2116
2117
|
cmd.extend(["--repo", repo])
|
|
2117
2118
|
|
|
2118
2119
|
try:
|
|
2119
|
-
result = subprocess.run(
|
|
2120
|
+
result = subprocess.run(
|
|
2121
|
+
cmd, capture_output=True, text=True, check=True, timeout=30
|
|
2122
|
+
)
|
|
2120
2123
|
# gh issue create outputs the URL
|
|
2121
2124
|
url = result.stdout.strip()
|
|
2122
2125
|
if url:
|
|
@@ -2124,6 +2127,9 @@ def gauntlet_accept_risk(
|
|
|
2124
2127
|
except subprocess.CalledProcessError as e:
|
|
2125
2128
|
# Don't fail entirely, just note the error
|
|
2126
2129
|
error = f"Failed to create some GitHub issues: {e.stderr}"
|
|
2130
|
+
except subprocess.TimeoutExpired:
|
|
2131
|
+
error = "GitHub issue creation timed out (30s limit)."
|
|
2132
|
+
break
|
|
2127
2133
|
except FileNotFoundError:
|
|
2128
2134
|
error = "gh CLI not found. Install GitHub CLI to create issues."
|
|
2129
2135
|
break
|
buildlog/llm.py
CHANGED
|
@@ -28,8 +28,10 @@ __all__ = [
|
|
|
28
28
|
import json
|
|
29
29
|
import logging
|
|
30
30
|
import os
|
|
31
|
+
import time
|
|
31
32
|
from dataclasses import dataclass, field
|
|
32
33
|
from pathlib import Path
|
|
34
|
+
from types import MappingProxyType
|
|
33
35
|
from typing import Protocol, runtime_checkable
|
|
34
36
|
|
|
35
37
|
logger = logging.getLogger(__name__)
|
|
@@ -84,6 +86,14 @@ class LLMConfig:
|
|
|
84
86
|
base_url: str | None = None # Override endpoint
|
|
85
87
|
api_key: str | None = None # From config or env var
|
|
86
88
|
|
|
89
|
+
def __repr__(self) -> str:
|
|
90
|
+
"""Redact api_key to prevent accidental exposure in logs/tracebacks."""
|
|
91
|
+
key_display = "***" if self.api_key else "None"
|
|
92
|
+
return (
|
|
93
|
+
f"LLMConfig(provider={self.provider!r}, model={self.model!r}, "
|
|
94
|
+
f"base_url={self.base_url!r}, api_key={key_display})"
|
|
95
|
+
)
|
|
96
|
+
|
|
87
97
|
@classmethod
|
|
88
98
|
def from_buildlog_config(cls, buildlog_dir: Path) -> LLMConfig | None:
|
|
89
99
|
"""Read from .buildlog/config.yml [llm] section."""
|
|
@@ -225,6 +235,28 @@ def _parse_json_response(text: str) -> list | dict:
|
|
|
225
235
|
return json.loads(text)
|
|
226
236
|
|
|
227
237
|
|
|
238
|
+
# --- Rate limiting ---
|
|
239
|
+
|
|
240
|
+
# Minimum seconds between API calls (per-backend instance).
|
|
241
|
+
_MIN_CALL_INTERVAL = 0.5
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class _RateLimiter:
|
|
245
|
+
"""Simple per-instance rate limiter to prevent API abuse."""
|
|
246
|
+
|
|
247
|
+
def __init__(self, min_interval: float = _MIN_CALL_INTERVAL):
|
|
248
|
+
self._min_interval = min_interval
|
|
249
|
+
self._last_call: float = 0.0
|
|
250
|
+
|
|
251
|
+
def wait(self) -> None:
|
|
252
|
+
"""Block until min_interval has elapsed since last call."""
|
|
253
|
+
now = time.monotonic()
|
|
254
|
+
elapsed = now - self._last_call
|
|
255
|
+
if elapsed < self._min_interval:
|
|
256
|
+
time.sleep(self._min_interval - elapsed)
|
|
257
|
+
self._last_call = time.monotonic()
|
|
258
|
+
|
|
259
|
+
|
|
228
260
|
# --- Implementations ---
|
|
229
261
|
|
|
230
262
|
|
|
@@ -235,6 +267,7 @@ class OllamaBackend:
|
|
|
235
267
|
self._model = model
|
|
236
268
|
self._base_url = base_url
|
|
237
269
|
self._resolved_model: str | None = None
|
|
270
|
+
self._rate_limiter = _RateLimiter()
|
|
238
271
|
|
|
239
272
|
def _get_model(self) -> str:
|
|
240
273
|
"""Resolve model name, auto-detecting largest if not specified."""
|
|
@@ -269,6 +302,7 @@ class OllamaBackend:
|
|
|
269
302
|
|
|
270
303
|
def _chat(self, prompt: str) -> str:
|
|
271
304
|
"""Send a prompt to Ollama and return the response text."""
|
|
305
|
+
self._rate_limiter.wait()
|
|
272
306
|
import ollama as ollama_lib
|
|
273
307
|
|
|
274
308
|
kwargs = {
|
|
@@ -334,6 +368,11 @@ class AnthropicBackend:
|
|
|
334
368
|
self._model = model or "claude-haiku-4-20250514"
|
|
335
369
|
self._api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
|
|
336
370
|
self._client = None
|
|
371
|
+
self._rate_limiter = _RateLimiter()
|
|
372
|
+
|
|
373
|
+
def __repr__(self) -> str:
|
|
374
|
+
"""Redact API key from repr to prevent exposure in logs/tracebacks."""
|
|
375
|
+
return f"AnthropicBackend(model={self._model!r}, api_key=***)"
|
|
337
376
|
|
|
338
377
|
def _get_client(self):
|
|
339
378
|
"""Lazy-load the Anthropic client."""
|
|
@@ -351,6 +390,7 @@ class AnthropicBackend:
|
|
|
351
390
|
|
|
352
391
|
def _chat(self, prompt: str) -> str:
|
|
353
392
|
"""Send a prompt to Claude and return the response text."""
|
|
393
|
+
self._rate_limiter.wait()
|
|
354
394
|
client = self._get_client()
|
|
355
395
|
response = client.messages.create(
|
|
356
396
|
model=self._model,
|
|
@@ -402,15 +442,22 @@ class AnthropicBackend:
|
|
|
402
442
|
|
|
403
443
|
# --- Registry ---
|
|
404
444
|
|
|
405
|
-
|
|
445
|
+
_PROVIDERS: dict[str, type] = {
|
|
406
446
|
"ollama": OllamaBackend,
|
|
407
447
|
"anthropic": AnthropicBackend,
|
|
408
448
|
}
|
|
449
|
+
# Public read-only view. Use register_provider() to add entries.
|
|
450
|
+
PROVIDERS: MappingProxyType[str, type] = MappingProxyType(_PROVIDERS)
|
|
409
451
|
|
|
410
452
|
|
|
411
453
|
def register_provider(name: str, cls: type) -> None:
|
|
412
|
-
"""Register a new LLM provider backend.
|
|
413
|
-
|
|
454
|
+
"""Register a new LLM provider backend.
|
|
455
|
+
|
|
456
|
+
This is the only sanctioned way to mutate the provider registry.
|
|
457
|
+
"""
|
|
458
|
+
if not isinstance(name, str) or not name.strip():
|
|
459
|
+
raise ValueError("Provider name must be a non-empty string")
|
|
460
|
+
_PROVIDERS[name] = cls
|
|
414
461
|
|
|
415
462
|
|
|
416
463
|
def get_llm_backend(
|
|
@@ -439,7 +486,7 @@ def get_llm_backend(
|
|
|
439
486
|
logger.info("No LLM provider available, using regex fallback")
|
|
440
487
|
return None
|
|
441
488
|
|
|
442
|
-
provider_cls =
|
|
489
|
+
provider_cls = _PROVIDERS.get(config.provider)
|
|
443
490
|
if provider_cls is None:
|
|
444
491
|
logger.warning("Unknown LLM provider: %s", config.provider)
|
|
445
492
|
return None
|
buildlog/mcp/server.py
CHANGED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from mcp.server.fastmcp import FastMCP
|
|
6
6
|
|
|
7
7
|
from buildlog.mcp.tools import (
|
|
8
|
+
buildlog_bandit_status,
|
|
8
9
|
buildlog_diff,
|
|
9
10
|
buildlog_experiment_end,
|
|
10
11
|
buildlog_experiment_metrics,
|
|
@@ -43,6 +44,9 @@ mcp.tool()(buildlog_experiment_report)
|
|
|
43
44
|
mcp.tool()(buildlog_gauntlet_issues)
|
|
44
45
|
mcp.tool()(buildlog_gauntlet_accept_risk)
|
|
45
46
|
|
|
47
|
+
# Bandit tools
|
|
48
|
+
mcp.tool()(buildlog_bandit_status)
|
|
49
|
+
|
|
46
50
|
|
|
47
51
|
def main() -> None:
|
|
48
52
|
"""Run the MCP server."""
|
buildlog/seed_engine/__init__.py
CHANGED
|
@@ -33,6 +33,7 @@ from buildlog.seed_engine.categorizers import (
|
|
|
33
33
|
)
|
|
34
34
|
from buildlog.seed_engine.extractors import ManualExtractor, RuleExtractor
|
|
35
35
|
from buildlog.seed_engine.generators import SeedGenerator
|
|
36
|
+
from buildlog.seed_engine.llm_extractor import LLMExtractor
|
|
36
37
|
from buildlog.seed_engine.models import (
|
|
37
38
|
CandidateRule,
|
|
38
39
|
CategorizedRule,
|
|
@@ -59,6 +60,7 @@ __all__ = [
|
|
|
59
60
|
# Extractors
|
|
60
61
|
"RuleExtractor",
|
|
61
62
|
"ManualExtractor",
|
|
63
|
+
"LLMExtractor",
|
|
62
64
|
# Categorizers
|
|
63
65
|
"Categorizer",
|
|
64
66
|
"TagBasedCategorizer",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""LLM-backed rule extraction for the seed engine pipeline.
|
|
2
|
+
|
|
3
|
+
Adapts LLMBackend.extract_rules() into the RuleExtractor interface,
|
|
4
|
+
bridging the LLM module with the seed engine's 4-step pipeline.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from buildlog.seed_engine.extractors import RuleExtractor
|
|
13
|
+
from buildlog.seed_engine.models import CandidateRule, Source
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from buildlog.llm import LLMBackend
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
_PLACEHOLDER = "Not specified by LLM"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LLMExtractor(RuleExtractor):
|
|
24
|
+
"""LLM-backed rule extraction from source content.
|
|
25
|
+
|
|
26
|
+
Wraps any LLMBackend to produce CandidateRules with full
|
|
27
|
+
defensibility fields. Fields the LLM doesn't populate get
|
|
28
|
+
placeholder values so downstream validation passes.
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
from buildlog.llm import OllamaBackend
|
|
32
|
+
from buildlog.seed_engine.llm_extractor import LLMExtractor
|
|
33
|
+
|
|
34
|
+
backend = OllamaBackend(model="llama3.2")
|
|
35
|
+
extractor = LLMExtractor(backend, source_content={"https://...": "..."})
|
|
36
|
+
|
|
37
|
+
rules = extractor.extract(source)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
backend: LLMBackend,
|
|
43
|
+
source_content: dict[str, str] | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Initialize with an LLM backend.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
backend: Any LLMBackend (Ollama, Anthropic, etc.).
|
|
49
|
+
source_content: Optional map of source.url → text content.
|
|
50
|
+
For sources that need pre-fetched content.
|
|
51
|
+
"""
|
|
52
|
+
self._backend = backend
|
|
53
|
+
self._source_content = source_content or {}
|
|
54
|
+
|
|
55
|
+
def extract(self, source: Source) -> list[CandidateRule]:
|
|
56
|
+
"""Extract candidate rules from a source via LLM.
|
|
57
|
+
|
|
58
|
+
Resolution for content:
|
|
59
|
+
1. source_content dict (keyed by source.url)
|
|
60
|
+
2. source.description as fallback
|
|
61
|
+
|
|
62
|
+
Returns empty list on LLM failure (logged, not raised).
|
|
63
|
+
"""
|
|
64
|
+
content = self._source_content.get(source.url, "").strip()
|
|
65
|
+
if not content:
|
|
66
|
+
content = source.description.strip()
|
|
67
|
+
if not content:
|
|
68
|
+
logger.warning("No content for source %s, skipping", source.name)
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
extracted = self._backend.extract_rules(content)
|
|
73
|
+
except Exception:
|
|
74
|
+
logger.exception("LLM extraction failed for %s", source.name)
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
candidates: list[CandidateRule] = []
|
|
78
|
+
for er in extracted:
|
|
79
|
+
if not er.rule.strip():
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
metadata: dict[str, Any] = {
|
|
83
|
+
"extractor": "llm",
|
|
84
|
+
"severity": er.severity,
|
|
85
|
+
"scope": er.scope,
|
|
86
|
+
}
|
|
87
|
+
# Include backend class name (public info only)
|
|
88
|
+
metadata["backend_type"] = type(self._backend).__name__
|
|
89
|
+
|
|
90
|
+
candidates.append(
|
|
91
|
+
CandidateRule(
|
|
92
|
+
rule=er.rule,
|
|
93
|
+
context=er.context or _PLACEHOLDER,
|
|
94
|
+
antipattern=er.antipattern or _PLACEHOLDER,
|
|
95
|
+
rationale=er.rationale or _PLACEHOLDER,
|
|
96
|
+
source=source,
|
|
97
|
+
raw_tags=[er.category] + er.applicability,
|
|
98
|
+
confidence=0.7,
|
|
99
|
+
metadata=metadata,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
logger.info("LLM extracted %d rules from %s", len(candidates), source.name)
|
|
104
|
+
return candidates
|
|
105
|
+
|
|
106
|
+
def validate(self, rule: CandidateRule) -> list[str]:
|
|
107
|
+
"""Validate a candidate rule.
|
|
108
|
+
|
|
109
|
+
Warns on placeholder defensibility fields.
|
|
110
|
+
Requires non-empty rule text.
|
|
111
|
+
"""
|
|
112
|
+
issues: list[str] = []
|
|
113
|
+
if not rule.rule.strip():
|
|
114
|
+
issues.append("Rule text is empty")
|
|
115
|
+
if rule.context == _PLACEHOLDER:
|
|
116
|
+
issues.append("Context is LLM placeholder — consider enriching")
|
|
117
|
+
if rule.antipattern == _PLACEHOLDER:
|
|
118
|
+
issues.append("Antipattern is LLM placeholder — consider enriching")
|
|
119
|
+
if rule.rationale == _PLACEHOLDER:
|
|
120
|
+
issues.append("Rationale is LLM placeholder — consider enriching")
|
|
121
|
+
return issues
|
buildlog/seed_engine/pipeline.py
CHANGED
|
@@ -12,13 +12,16 @@ from __future__ import annotations
|
|
|
12
12
|
import logging
|
|
13
13
|
from dataclasses import dataclass
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import Any
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
16
|
|
|
17
17
|
from buildlog.seed_engine.categorizers import Categorizer, TagBasedCategorizer
|
|
18
18
|
from buildlog.seed_engine.extractors import ManualExtractor, RuleExtractor
|
|
19
19
|
from buildlog.seed_engine.generators import SeedGenerator
|
|
20
20
|
from buildlog.seed_engine.models import CandidateRule, CategorizedRule, Source
|
|
21
21
|
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from buildlog.llm import LLMBackend
|
|
24
|
+
|
|
22
25
|
logger = logging.getLogger(__name__)
|
|
23
26
|
|
|
24
27
|
|
|
@@ -174,6 +177,7 @@ class Pipeline:
|
|
|
174
177
|
Returns:
|
|
175
178
|
List of validation issues (empty if valid).
|
|
176
179
|
"""
|
|
180
|
+
allowed_schemes = {"https", "http", "file"}
|
|
177
181
|
issues = []
|
|
178
182
|
for i, source in enumerate(sources):
|
|
179
183
|
prefix = f"Source {i + 1} ({source.name})"
|
|
@@ -181,10 +185,50 @@ class Pipeline:
|
|
|
181
185
|
issues.append(f"{prefix}: Missing name")
|
|
182
186
|
if not source.url.strip():
|
|
183
187
|
issues.append(f"{prefix}: Missing URL")
|
|
188
|
+
else:
|
|
189
|
+
# Validate URL scheme
|
|
190
|
+
scheme = (
|
|
191
|
+
source.url.split("://")[0].lower() if "://" in source.url else ""
|
|
192
|
+
)
|
|
193
|
+
if scheme not in allowed_schemes:
|
|
194
|
+
issues.append(
|
|
195
|
+
f"{prefix}: URL scheme '{scheme}' not in allowlist {allowed_schemes}"
|
|
196
|
+
)
|
|
184
197
|
if not source.domain.strip():
|
|
185
198
|
issues.append(f"{prefix}: Missing domain")
|
|
186
199
|
return issues
|
|
187
200
|
|
|
201
|
+
@classmethod
|
|
202
|
+
def with_llm(
|
|
203
|
+
cls,
|
|
204
|
+
persona: str,
|
|
205
|
+
backend: LLMBackend,
|
|
206
|
+
source_content: dict[str, str] | None = None,
|
|
207
|
+
default_category: str = "general",
|
|
208
|
+
version: int = 1,
|
|
209
|
+
) -> Pipeline:
|
|
210
|
+
"""Convenience constructor wiring LLMExtractor + TagBasedCategorizer.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
persona: Persona name for the seed file.
|
|
214
|
+
backend: Any LLMBackend implementation.
|
|
215
|
+
source_content: Optional pre-fetched content map.
|
|
216
|
+
default_category: Fallback category for uncategorized rules.
|
|
217
|
+
version: Seed file version.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Pipeline configured with LLMExtractor.
|
|
221
|
+
"""
|
|
222
|
+
from buildlog.seed_engine.llm_extractor import LLMExtractor
|
|
223
|
+
|
|
224
|
+
return cls(
|
|
225
|
+
persona=persona,
|
|
226
|
+
default_category=default_category,
|
|
227
|
+
version=version,
|
|
228
|
+
extractor=LLMExtractor(backend, source_content),
|
|
229
|
+
categorizer=TagBasedCategorizer(default_category=default_category),
|
|
230
|
+
)
|
|
231
|
+
|
|
188
232
|
def dry_run(self, sources: list[Source]) -> dict[str, Any]:
|
|
189
233
|
"""Run pipeline without writing, returning preview.
|
|
190
234
|
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: buildlog
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Engineering notebook for AI-assisted development
|
|
5
|
+
Project-URL: Homepage, https://github.com/Peleke/buildlog-template
|
|
6
|
+
Project-URL: Repository, https://github.com/Peleke/buildlog-template
|
|
7
|
+
Author: Peleke Sengstacke
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai,buildlog,development,documentation,journal
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Documentation
|
|
21
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: click>=8.0.0
|
|
24
|
+
Requires-Dist: copier>=9.0.0
|
|
25
|
+
Requires-Dist: numpy>=1.21.0
|
|
26
|
+
Requires-Dist: pymupdf>=1.26.7
|
|
27
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
28
|
+
Provides-Extra: all
|
|
29
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'all'
|
|
30
|
+
Requires-Dist: mcp>=1.0.0; extra == 'all'
|
|
31
|
+
Requires-Dist: ollama>=0.4.0; extra == 'all'
|
|
32
|
+
Requires-Dist: openai>=1.0.0; extra == 'all'
|
|
33
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == 'all'
|
|
34
|
+
Provides-Extra: anthropic
|
|
35
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'anthropic'
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: black>=24.0.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: flake8>=7.0.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: isort>=5.13.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
46
|
+
Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
|
|
47
|
+
Provides-Extra: embeddings
|
|
48
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == 'embeddings'
|
|
49
|
+
Provides-Extra: engine
|
|
50
|
+
Provides-Extra: llm
|
|
51
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'llm'
|
|
52
|
+
Requires-Dist: ollama>=0.4.0; extra == 'llm'
|
|
53
|
+
Provides-Extra: mcp
|
|
54
|
+
Requires-Dist: mcp>=1.0.0; extra == 'mcp'
|
|
55
|
+
Provides-Extra: ollama
|
|
56
|
+
Requires-Dist: ollama>=0.4.0; extra == 'ollama'
|
|
57
|
+
Provides-Extra: openai
|
|
58
|
+
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
59
|
+
Description-Content-Type: text/markdown
|
|
60
|
+
|
|
61
|
+
<div align="center">
|
|
62
|
+
|
|
63
|
+
# buildlog
|
|
64
|
+
|
|
65
|
+
### A measurable learning loop for AI-assisted work
|
|
66
|
+
|
|
67
|
+
[](https://pypi.org/project/buildlog/)
|
|
68
|
+
[](https://python.org/)
|
|
69
|
+
[](https://github.com/Peleke/buildlog-template/actions/workflows/ci.yml)
|
|
70
|
+
[](https://opensource.org/licenses/MIT)
|
|
71
|
+
[](https://peleke.github.io/buildlog-template/)
|
|
72
|
+
|
|
73
|
+
**Track what works. Prove it. Drop what doesn't.**
|
|
74
|
+
|
|
75
|
+
<img src="assets/hero-banner-perfectdeliberate.png" alt="buildlog - A measurable learning loop for AI-assisted work" width="800"/>
|
|
76
|
+
|
|
77
|
+
> **RE: The art.** Yes, it's AI-generated. Yes, that's hypocritical for a project about rigor over vibes. Looking for an actual artist to pay for a real logo. If you know someone good, [open an issue](https://github.com/Peleke/buildlog-template/issues) or DM me. Budget exists.
|
|
78
|
+
|
|
79
|
+
**[Read the full documentation](https://peleke.github.io/buildlog-template/)**
|
|
80
|
+
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## The Problem
|
|
86
|
+
|
|
87
|
+
Most AI agents do not learn. They execute without retaining context. You can bolt on memory stores and tool routers, but if the system cannot demonstrably improve its decision-making over time, you have a persistent memory store, not a learning system.
|
|
88
|
+
|
|
89
|
+
Every AI-assisted work session produces a trajectory: goals, decisions, tool uses, corrections, outcomes. Almost all of this is discarded. The next session starts from scratch with the same blind spots.
|
|
90
|
+
|
|
91
|
+
buildlog exists to close that gap. It captures structured trajectories from real work, extracts decision patterns, and uses statistical methods to select which patterns to surface in future sessions, then measures whether that selection actually reduced mistakes.
|
|
92
|
+
|
|
93
|
+
buildlog measures whether the system actually got better, and proves it.
|
|
94
|
+
|
|
95
|
+
## How It Works
|
|
96
|
+
|
|
97
|
+
### 1. Capture structured work trajectories
|
|
98
|
+
|
|
99
|
+
Each session is a dated entry documenting what you did, what went wrong, and what you learned. Each session is a structured record of decisions and outcomes, not a chat transcript.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
buildlog init # scaffold a project
|
|
103
|
+
buildlog new my-feature # start a session
|
|
104
|
+
# ... work ...
|
|
105
|
+
buildlog commit -m "feat: add auth"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 2. Extract decision patterns as seeds
|
|
109
|
+
|
|
110
|
+
The seed engine watches your development patterns and extracts **seeds**: atomic observations about what works. A seed might be "always define interfaces before implementations" or "mock at the boundary, not the implementation." Each seed carries a category, a confidence score, and source provenance.
|
|
111
|
+
|
|
112
|
+
Extraction runs through a pipeline: `sources -> extractors -> categorizers -> generators`. Extractors range from regex-based (fast, cheap, brittle) to LLM-backed (accurate, expensive). The pipeline deduplicates semantically using embeddings.
|
|
113
|
+
|
|
114
|
+
### 3. Select which patterns to surface using Thompson Sampling
|
|
115
|
+
|
|
116
|
+
Seeds compete for inclusion in your agent's instruction set. The system treats each seed as an arm in a contextual bandit and uses **Thompson Sampling** to balance exploration (trying under-tested rules) against exploitation (surfacing rules with strong track records).
|
|
117
|
+
|
|
118
|
+
Each seed maintains a Beta posterior updated by observed outcomes. Over time, the system converges on the rules that actually reduce mistakes in your specific codebase and workflow, not rules that sound good in the abstract.
|
|
119
|
+
|
|
120
|
+
### 4. Render to every agent format
|
|
121
|
+
|
|
122
|
+
Selected rules are written into the instruction files your agents actually read:
|
|
123
|
+
|
|
124
|
+
- `CLAUDE.md` (Claude Code)
|
|
125
|
+
- `.cursorrules` (Cursor)
|
|
126
|
+
- `.github/copilot-instructions.md` (GitHub Copilot)
|
|
127
|
+
- Windsurf, Continue.dev, generic `settings.json`
|
|
128
|
+
|
|
129
|
+
The same knowledge base renders to every agent format.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
buildlog skills # render current policy to agent files
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 5. Close the loop with experiments
|
|
136
|
+
|
|
137
|
+
Track whether the selected rules are working. Run experiments, measure Repeated Mistake Rate (RMR) across sessions, and get statistical evidence, not feelings, about what improved.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
buildlog experiment start
|
|
141
|
+
# ... work across sessions ...
|
|
142
|
+
buildlog experiment end
|
|
143
|
+
buildlog experiment report
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## What Else Is In the Box
|
|
147
|
+
|
|
148
|
+
- **Review gauntlet:** automated quality gate with curated reviewer personas. Runs on commits (via Claude Code hooks or CI) and files GitHub issues for findings, categorized by severity.
|
|
149
|
+
- **LLM-backed extraction:** when regex isn't enough, the seed engine can use OpenAI, Anthropic, or Ollama to extract patterns from code and logs. Metered backend tracks token usage and cost.
|
|
150
|
+
- **MCP server:** buildlog exposes itself as an MCP server so agents can query seeds, skills, and build history programmatically during sessions.
|
|
151
|
+
- **npm wrapper:** `npx @peleke.s/buildlog` for JS/TS projects. Thin shim that finds and invokes the Python CLI.
|
|
152
|
+
|
|
153
|
+
## Current Limits
|
|
154
|
+
|
|
155
|
+
This is v0.8, not the end state.
|
|
156
|
+
|
|
157
|
+
- **Extraction quality is uneven.** Regex extractors miss nuance; LLM extractors are accurate but expensive. The middle ground is still being found.
|
|
158
|
+
- **Feedback signals are coarse.** Repeated Mistake Rate works but requires manual tagging. Richer automatic signals (test outcomes, review results, revision distance) are on the roadmap.
|
|
159
|
+
- **Credit assignment is limited.** When multiple rules are active, the system doesn't yet isolate which one was responsible for an outcome.
|
|
160
|
+
- **Single-agent only.** Multi-agent coordination (shared learning across agents) is designed but not implemented.
|
|
161
|
+
- **Long-horizon learning is not modeled.** The bandit operates per-session. Longer arcs of competence building need richer policy models.
|
|
162
|
+
|
|
163
|
+
The roadmap: contextual bandits (now) -> richer policy models -> longer-horizon RL -> multi-agent coordination. Each step builds on the same foundation: measuring whether rule changes actually reduce mistakes.
|
|
164
|
+
|
|
165
|
+
## Installation
|
|
166
|
+
|
|
167
|
+
### Global install (recommended)
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
uv tool install "buildlog[mcp]" # or: pipx install "buildlog[mcp]"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
This puts `buildlog` and `buildlog-mcp` on your PATH. Works from any directory. The `[mcp]` extra is required for the MCP server.
|
|
174
|
+
|
|
175
|
+
### Per-project (virtual environment)
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
uv pip install "buildlog[mcp]" # or: pip install "buildlog[mcp]"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Omit `[mcp]` if you only need the CLI.
|
|
182
|
+
|
|
183
|
+
### For JS/TS projects
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
npx @peleke.s/buildlog init
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### MCP server for Claude Code
|
|
190
|
+
|
|
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
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This exposes buildlog tools (seeds, skills, experiments, gauntlet, bandit status) to any Claude Code session.
|
|
205
|
+
|
|
206
|
+
## Quick Start
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
buildlog init # scaffold a project (run in any repo)
|
|
210
|
+
buildlog new my-feature # start a session
|
|
211
|
+
# ... work ...
|
|
212
|
+
buildlog distill && buildlog skills
|
|
213
|
+
buildlog experiment start
|
|
214
|
+
# ... work across sessions ...
|
|
215
|
+
buildlog experiment end
|
|
216
|
+
buildlog experiment report
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Documentation
|
|
220
|
+
|
|
221
|
+
| Section | Description |
|
|
222
|
+
|---------|------------|
|
|
223
|
+
| [Installation](https://peleke.github.io/buildlog-template/getting-started/installation/) | Setup, extras, and initialization |
|
|
224
|
+
| [Quick Start](https://peleke.github.io/buildlog-template/getting-started/quick-start/) | Full pipeline walkthrough |
|
|
225
|
+
| [Core Concepts](https://peleke.github.io/buildlog-template/getting-started/concepts/) | The problem, the claim, and the metric |
|
|
226
|
+
| [CLI Reference](https://peleke.github.io/buildlog-template/guides/cli-reference/) | Every command documented |
|
|
227
|
+
| [MCP Integration](https://peleke.github.io/buildlog-template/guides/mcp-integration/) | Claude Code setup and available tools |
|
|
228
|
+
| [Experiments](https://peleke.github.io/buildlog-template/guides/experiments/) | Running and measuring experiments |
|
|
229
|
+
| [Review Gauntlet](https://peleke.github.io/buildlog-template/guides/review-gauntlet/) | Reviewer personas and the gauntlet loop |
|
|
230
|
+
| [Multi-Agent Setup](https://peleke.github.io/buildlog-template/guides/multi-agent/) | Render rules to any AI coding agent |
|
|
231
|
+
| [Theory](https://peleke.github.io/buildlog-template/theory/00-background/) | The math behind Thompson Sampling |
|
|
232
|
+
| [Philosophy](https://peleke.github.io/buildlog-template/philosophy/) | Principles and honest limitations |
|
|
233
|
+
|
|
234
|
+
## Contributing
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
git clone https://github.com/Peleke/buildlog-template
|
|
238
|
+
cd buildlog-template
|
|
239
|
+
uv venv && source .venv/bin/activate
|
|
240
|
+
uv pip install -e ".[dev]"
|
|
241
|
+
pytest
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
We're especially interested in better context representations, credit assignment approaches, statistical methodology improvements, and real-world experiment results (positive or negative).
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT License. See [LICENSE](./LICENSE)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
buildlog/__init__.py,sha256=wxluyg3fDOiaKUAOJa0cav39hwtNJS5Y_0X20uL_yi4,90
|
|
2
|
-
buildlog/cli.py,sha256=
|
|
2
|
+
buildlog/cli.py,sha256=CZUG3xYBfdyP47SDyvIgvjcfYDcHfV6ijGAr0B_7t1Y,66683
|
|
3
3
|
buildlog/confidence.py,sha256=jPvN0_3drGHQG1C7iUxUtYjKC62nNKsHHxu6WXMfJFg,10137
|
|
4
4
|
buildlog/distill.py,sha256=PL7UBToBb27BrCOTWGBTDIXGggtrUumHHBs0_MfG6vY,14166
|
|
5
5
|
buildlog/embeddings.py,sha256=vPydWjJVkYp172zFou-lJ737qsu6vRMQAMs143RGIpA,12364
|
|
6
|
-
buildlog/llm.py,sha256=
|
|
6
|
+
buildlog/llm.py,sha256=t_3KnJ_eBmrtHaay4owG3EgGbJrVp_T379P0tTuyJf8,16986
|
|
7
7
|
buildlog/seeds.py,sha256=L-lzO7rrDjMdVHcYKvEBm6scVDfCmFD0koiyafMZDLo,8596
|
|
8
8
|
buildlog/skills.py,sha256=3CihyyAFfJh0w0VNXQauNhGm_RM8m_GIO1iiNLNPmsQ,33789
|
|
9
9
|
buildlog/stats.py,sha256=2WdHdmzUNGobtWngmm9nA_UmqM7DQeAnZL8_rLQN8aw,13256
|
|
10
10
|
buildlog/core/__init__.py,sha256=gPd6ava_sei0sYIS4NdeHgCVwJ2wEjyLt-Rxz_11IWU,1626
|
|
11
11
|
buildlog/core/bandit.py,sha256=0z58s42jpQiBgXdjb465apenWdBVTvoc7bDOcnAcPLo,24565
|
|
12
|
-
buildlog/core/operations.py,sha256=
|
|
12
|
+
buildlog/core/operations.py,sha256=cL2w2Lrk7SBwha1aN9_t9WmQwi0OckCpAaGHCXDQztI,70249
|
|
13
13
|
buildlog/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
buildlog/data/seeds/security_karen.yaml,sha256=Hjwp4j6FB-e0vCXp6AkyhAEkwASR4H1owdhPBID740E,8775
|
|
15
15
|
buildlog/data/seeds/test_terrorist.yaml,sha256=-QY2x6d0LrlM6msMYaYwR43Wny9ZybQmU7pH_E7gSB0,11113
|
|
@@ -20,7 +20,7 @@ buildlog/engine/embeddings.py,sha256=78InITcBbaPZ29tUlb4hJNNVZ1fhxstvVa2fYvOh6QA
|
|
|
20
20
|
buildlog/engine/experiments.py,sha256=38f_UsbY4TP3LTGM9yUsZxoXqxUGOxmLCoYrfZqjVks,20041
|
|
21
21
|
buildlog/engine/types.py,sha256=W7U2B4TE79xACV0cuaidKevmTEskxqXfltH5hjIxV5k,734
|
|
22
22
|
buildlog/mcp/__init__.py,sha256=jCLNUkYFrDcPd5dY9xbaaVANl-ZzdPim1BykgGY7f7U,334
|
|
23
|
-
buildlog/mcp/server.py,sha256=
|
|
23
|
+
buildlog/mcp/server.py,sha256=IlE-xkW5gaOs5wkgusncRD9ugV70FRB6TKDanIqPaoc,1328
|
|
24
24
|
buildlog/mcp/tools.py,sha256=k1f6R7Dgcx0VIKzTNezkcRc0UVkWXWkk-uGkRq1sIiw,17578
|
|
25
25
|
buildlog/render/__init__.py,sha256=jj-o-vPdqpivdFKK85xskCOT8Zn_2tb3Q5Jb0gWLjAg,2567
|
|
26
26
|
buildlog/render/base.py,sha256=gQfvOsH1zttAo10xtEyNsAbqZ4NRSPiDihO-aiGgTsw,533
|
|
@@ -32,23 +32,24 @@ buildlog/render/settings_json.py,sha256=4DS5OWksPrFCa7MIgWIu0t4rxYmItpMdGfTqMX3a
|
|
|
32
32
|
buildlog/render/skill.py,sha256=_7umIS1Ms1oQ2_PopYueFjX41nMq1p28yJp6DhXFdgU,5981
|
|
33
33
|
buildlog/render/tracking.py,sha256=1y_Mf1s3KblV8TC0W9x2a_ynSJHLwkVZJcW1gs1a6q4,1786
|
|
34
34
|
buildlog/render/windsurf.py,sha256=jfJLb-luQLjdBM38_KKp0mZ08bUSDTBPxSLOIFWxawQ,3047
|
|
35
|
-
buildlog/seed_engine/__init__.py,sha256=
|
|
35
|
+
buildlog/seed_engine/__init__.py,sha256=QHo59cq-Ar_M6UthSOu_zpfYR1OF2ePu1qWrbgQqLak,1949
|
|
36
36
|
buildlog/seed_engine/categorizers.py,sha256=uzatJQPG4Vf7z7ZAeyQbL-bVJ8cUKBHG-3U93-BFIRQ,4649
|
|
37
37
|
buildlog/seed_engine/extractors.py,sha256=rkztg62LzFH018ob5Tl1eFp_yfPo8syn3Ib4h6bAyK4,4824
|
|
38
38
|
buildlog/seed_engine/generators.py,sha256=T3N3uHg359xSBypIGmvVWYI0Qh8WLlGIFQWX0_gs0I4,4358
|
|
39
|
+
buildlog/seed_engine/llm_extractor.py,sha256=nfTmN4vuEiPQuhiL_2DX1QZ7TZlDR2vxYy0tVrkf2Vc,4124
|
|
39
40
|
buildlog/seed_engine/models.py,sha256=qESSuoF3CJy8pp96E3Vmb38V-snzeaW0Xg0RfbH8v1U,3418
|
|
40
|
-
buildlog/seed_engine/pipeline.py,sha256=
|
|
41
|
+
buildlog/seed_engine/pipeline.py,sha256=cyR2Vf32Hf0PK5DxnaFcq56ygjJIiJWiIkHY4Uz7AS8,8082
|
|
41
42
|
buildlog/seed_engine/sources.py,sha256=8j9oUFZCSKMr5VpIuAxTPY3wTzfTEmw6M_41_aismiE,11184
|
|
42
|
-
buildlog-0.
|
|
43
|
-
buildlog-0.
|
|
44
|
-
buildlog-0.
|
|
45
|
-
buildlog-0.
|
|
46
|
-
buildlog-0.
|
|
47
|
-
buildlog-0.
|
|
48
|
-
buildlog-0.
|
|
49
|
-
buildlog-0.
|
|
50
|
-
buildlog-0.
|
|
51
|
-
buildlog-0.
|
|
52
|
-
buildlog-0.
|
|
53
|
-
buildlog-0.
|
|
54
|
-
buildlog-0.
|
|
43
|
+
buildlog-0.9.0.data/data/share/buildlog/copier.yml,sha256=hD_UcLGB9eDxTGkWjHAvUoiqQKhLC0w3G-kPKX_p43I,802
|
|
44
|
+
buildlog-0.9.0.data/data/share/buildlog/post_gen.py,sha256=XFlo40LuPpAsBhIRRRtHqvU3_5POss4L401hp35ijhw,1744
|
|
45
|
+
buildlog-0.9.0.data/data/share/buildlog/template/buildlog/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
buildlog-0.9.0.data/data/share/buildlog/template/buildlog/2026-01-01-example.md,sha256=7x9sKmydfmfKyNz9hV7MtYnQJuBwbxNanbPOcpQDDZQ,7040
|
|
47
|
+
buildlog-0.9.0.data/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md,sha256=osclytWwl5jUiTgSpuT4cT3h3oPvCkZ5GPCnFuJZNcY,3802
|
|
48
|
+
buildlog-0.9.0.data/data/share/buildlog/template/buildlog/_TEMPLATE.md,sha256=CUvxgcx1-9XT_EdQ8e_vnuPq_h-u1uhXJgForJU2Pso,2932
|
|
49
|
+
buildlog-0.9.0.data/data/share/buildlog/template/buildlog/_TEMPLATE_QUICK.md,sha256=eUr5MiqLsM6drV7rAq53R1SLkK8G7LkMAUjWKXx81IA,409
|
|
50
|
+
buildlog-0.9.0.data/data/share/buildlog/template/buildlog/assets/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
+
buildlog-0.9.0.dist-info/METADATA,sha256=uFrfGNpWCgBW7CYI3w6aUJDSHucCdVFpEoojWnsImA8,11341
|
|
52
|
+
buildlog-0.9.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
53
|
+
buildlog-0.9.0.dist-info/entry_points.txt,sha256=BMFclPOomp_sgaa0OqBg6LfqCMlqzjZV88ww5TrPPoo,87
|
|
54
|
+
buildlog-0.9.0.dist-info/licenses/LICENSE,sha256=fAgt-akug9nAwIj6M-SIf8u3ck-T7pJTwfmy9vWYASk,1074
|
|
55
|
+
buildlog-0.9.0.dist-info/RECORD,,
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: buildlog
|
|
3
|
-
Version: 0.8.0
|
|
4
|
-
Summary: Engineering notebook for AI-assisted development
|
|
5
|
-
Project-URL: Homepage, https://github.com/Peleke/buildlog-template
|
|
6
|
-
Project-URL: Repository, https://github.com/Peleke/buildlog-template
|
|
7
|
-
Author: Peleke Sengstacke
|
|
8
|
-
License-Expression: MIT
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Keywords: ai,buildlog,development,documentation,journal
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Environment :: Console
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
-
Classifier: Topic :: Documentation
|
|
21
|
-
Classifier: Topic :: Software Development :: Documentation
|
|
22
|
-
Requires-Python: >=3.10
|
|
23
|
-
Requires-Dist: click>=8.0.0
|
|
24
|
-
Requires-Dist: copier>=9.0.0
|
|
25
|
-
Requires-Dist: numpy>=1.21.0
|
|
26
|
-
Requires-Dist: pymupdf>=1.26.7
|
|
27
|
-
Requires-Dist: pyyaml>=6.0.0
|
|
28
|
-
Provides-Extra: all
|
|
29
|
-
Requires-Dist: anthropic>=0.40.0; extra == 'all'
|
|
30
|
-
Requires-Dist: mcp>=1.0.0; extra == 'all'
|
|
31
|
-
Requires-Dist: ollama>=0.4.0; extra == 'all'
|
|
32
|
-
Requires-Dist: openai>=1.0.0; extra == 'all'
|
|
33
|
-
Requires-Dist: sentence-transformers>=2.2.0; extra == 'all'
|
|
34
|
-
Provides-Extra: anthropic
|
|
35
|
-
Requires-Dist: anthropic>=0.40.0; extra == 'anthropic'
|
|
36
|
-
Provides-Extra: dev
|
|
37
|
-
Requires-Dist: black>=24.0.0; extra == 'dev'
|
|
38
|
-
Requires-Dist: flake8>=7.0.0; extra == 'dev'
|
|
39
|
-
Requires-Dist: isort>=5.13.0; extra == 'dev'
|
|
40
|
-
Requires-Dist: mkdocs-material>=9.5.0; extra == 'dev'
|
|
41
|
-
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
42
|
-
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
|
|
43
|
-
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
44
|
-
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
45
|
-
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
46
|
-
Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
|
|
47
|
-
Provides-Extra: embeddings
|
|
48
|
-
Requires-Dist: sentence-transformers>=2.2.0; extra == 'embeddings'
|
|
49
|
-
Provides-Extra: engine
|
|
50
|
-
Provides-Extra: llm
|
|
51
|
-
Requires-Dist: anthropic>=0.40.0; extra == 'llm'
|
|
52
|
-
Requires-Dist: ollama>=0.4.0; extra == 'llm'
|
|
53
|
-
Provides-Extra: mcp
|
|
54
|
-
Requires-Dist: mcp>=1.0.0; extra == 'mcp'
|
|
55
|
-
Provides-Extra: ollama
|
|
56
|
-
Requires-Dist: ollama>=0.4.0; extra == 'ollama'
|
|
57
|
-
Provides-Extra: openai
|
|
58
|
-
Requires-Dist: openai>=1.0.0; extra == 'openai'
|
|
59
|
-
Description-Content-Type: text/markdown
|
|
60
|
-
|
|
61
|
-
<div align="center">
|
|
62
|
-
|
|
63
|
-
# buildlog
|
|
64
|
-
|
|
65
|
-
### The Only Agent Learning System You Can Prove Works
|
|
66
|
-
|
|
67
|
-
[](https://pypi.org/project/buildlog/)
|
|
68
|
-
[](https://python.org/)
|
|
69
|
-
[](https://github.com/Peleke/buildlog-template/actions/workflows/ci.yml)
|
|
70
|
-
[](https://opensource.org/licenses/MIT)
|
|
71
|
-
[](https://peleke.github.io/buildlog-template/)
|
|
72
|
-
|
|
73
|
-
**Falsifiable claims. Measurable outcomes. No vibes.**
|
|
74
|
-
|
|
75
|
-
<img src="assets/hero-banner-perfectdeliberate.png" alt="buildlog - The Only Agent Learning System You Can Prove Works" width="800"/>
|
|
76
|
-
|
|
77
|
-
> **RE: The art** — Yes, it's AI-generated. Yes, that's hypocritical for a project about rigor over vibes. Looking for an actual artist to pay for a real logo. If you know someone good, [open an issue](https://github.com/Peleke/buildlog-template/issues) or DM me. Budget exists.
|
|
78
|
-
|
|
79
|
-
**[Read the full documentation](https://peleke.github.io/buildlog-template/)**
|
|
80
|
-
|
|
81
|
-
</div>
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
Everyone's building "agent memory." Blog posts announce breakthroughs. Products ship with "learning" in the tagline. Ask them one question: **How do you know it works?**
|
|
86
|
-
|
|
87
|
-
buildlog gives you the infrastructure to answer with data. It captures engineering knowledge from work sessions, extracts rules, selects which rules to surface using a Thompson Sampling bandit, and measures impact via Repeated Mistake Rate (RMR) across tracked experiments.
|
|
88
|
-
|
|
89
|
-
## Features
|
|
90
|
-
|
|
91
|
-
- **Structured capture** — Document work sessions as entries with mistakes, decisions, and outcomes
|
|
92
|
-
- **Rule extraction** — Distill and deduplicate patterns into actionable rules
|
|
93
|
-
- **Thompson Sampling bandit** — Automatic rule selection that balances exploration and exploitation
|
|
94
|
-
- **Experiment tracking** — Sessions, mistakes, RMR calculation with statistical rigor
|
|
95
|
-
- **Review gauntlet** — Curated reviewer personas (Security Karen, Test Terrorist) with HITL checkpoints
|
|
96
|
-
- **Multi-agent support** — Render rules to Claude Code, Cursor, GitHub Copilot, Windsurf, Continue.dev
|
|
97
|
-
- **MCP server** — Full Claude Code integration via `buildlog-mcp`
|
|
98
|
-
|
|
99
|
-
## Quick Start
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
uv pip install buildlog # or: pip install buildlog (inside a venv)
|
|
103
|
-
buildlog init
|
|
104
|
-
buildlog new my-feature
|
|
105
|
-
buildlog distill && buildlog skills
|
|
106
|
-
buildlog experiment start
|
|
107
|
-
# ... work ...
|
|
108
|
-
buildlog experiment end
|
|
109
|
-
buildlog experiment report
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Documentation
|
|
113
|
-
|
|
114
|
-
| Section | Description |
|
|
115
|
-
|---------|------------|
|
|
116
|
-
| [Installation](https://peleke.github.io/buildlog-template/getting-started/installation/) | Setup, extras, and initialization |
|
|
117
|
-
| [Quick Start](https://peleke.github.io/buildlog-template/getting-started/quick-start/) | Full pipeline walkthrough |
|
|
118
|
-
| [Core Concepts](https://peleke.github.io/buildlog-template/getting-started/concepts/) | The problem, the claim, and the metric |
|
|
119
|
-
| [CLI Reference](https://peleke.github.io/buildlog-template/guides/cli-reference/) | Every command documented |
|
|
120
|
-
| [MCP Integration](https://peleke.github.io/buildlog-template/guides/mcp-integration/) | Claude Code setup and available tools |
|
|
121
|
-
| [Experiments](https://peleke.github.io/buildlog-template/guides/experiments/) | Running and measuring experiments |
|
|
122
|
-
| [Review Gauntlet](https://peleke.github.io/buildlog-template/guides/review-gauntlet/) | Reviewer personas and the gauntlet loop |
|
|
123
|
-
| [Multi-Agent Setup](https://peleke.github.io/buildlog-template/guides/multi-agent/) | Render rules to any AI coding agent |
|
|
124
|
-
| [Theory](https://peleke.github.io/buildlog-template/theory/00-background/) | The math behind Thompson Sampling |
|
|
125
|
-
| [Philosophy](https://peleke.github.io/buildlog-template/philosophy/) | Principles and honest limitations |
|
|
126
|
-
|
|
127
|
-
## Contributing
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
git clone https://github.com/Peleke/buildlog-template
|
|
131
|
-
cd buildlog-template
|
|
132
|
-
uv venv && source .venv/bin/activate
|
|
133
|
-
uv pip install -e ".[dev]"
|
|
134
|
-
pytest
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
We're especially interested in better context representations, credit assignment approaches, statistical methodology improvements, and real-world experiment results (positive or negative).
|
|
138
|
-
|
|
139
|
-
## License
|
|
140
|
-
|
|
141
|
-
MIT License — see [LICENSE](./LICENSE)
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
<div align="center">
|
|
146
|
-
|
|
147
|
-
**"Agent learning" without measurement is just prompt engineering with extra steps.**
|
|
148
|
-
|
|
149
|
-
**buildlog is measurement.**
|
|
150
|
-
|
|
151
|
-
</div>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md
RENAMED
|
File without changes
|
{buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE.md
RENAMED
|
File without changes
|
{buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE_QUICK.md
RENAMED
|
File without changes
|
{buildlog-0.8.0.data → buildlog-0.9.0.data}/data/share/buildlog/template/buildlog/assets/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|