buildlog 0.2.0__py3-none-any.whl → 0.4.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/confidence.py +311 -0
- buildlog/core/__init__.py +8 -0
- buildlog/core/operations.py +343 -2
- buildlog/mcp/__init__.py +2 -0
- buildlog/mcp/server.py +2 -0
- buildlog/mcp/tools.py +46 -1
- buildlog/skills.py +233 -11
- {buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/post_gen.py +11 -7
- {buildlog-0.2.0.dist-info → buildlog-0.4.0.dist-info}/METADATA +134 -2
- buildlog-0.4.0.dist-info/RECORD +30 -0
- buildlog-0.2.0.dist-info/RECORD +0 -29
- {buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/copier.yml +0 -0
- {buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/template/buildlog/.gitkeep +0 -0
- {buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/template/buildlog/2026-01-01-example.md +0 -0
- {buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
- {buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE.md +0 -0
- {buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/template/buildlog/assets/.gitkeep +0 -0
- {buildlog-0.2.0.dist-info → buildlog-0.4.0.dist-info}/WHEEL +0 -0
- {buildlog-0.2.0.dist-info → buildlog-0.4.0.dist-info}/entry_points.txt +0 -0
- {buildlog-0.2.0.dist-info → buildlog-0.4.0.dist-info}/licenses/LICENSE +0 -0
buildlog/skills.py
CHANGED
|
@@ -5,11 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
__all__ = [
|
|
6
6
|
"Skill",
|
|
7
7
|
"SkillSet",
|
|
8
|
+
"ConfidenceConfig", # Re-exported for convenience
|
|
8
9
|
"_deduplicate_insights",
|
|
9
10
|
"_calculate_confidence",
|
|
10
11
|
"_extract_tags",
|
|
11
12
|
"_generate_skill_id",
|
|
12
13
|
"_to_imperative",
|
|
14
|
+
"_build_confidence_metrics",
|
|
13
15
|
"generate_skills",
|
|
14
16
|
"format_skills",
|
|
15
17
|
]
|
|
@@ -23,11 +25,26 @@ from datetime import date, datetime, timezone
|
|
|
23
25
|
from pathlib import Path
|
|
24
26
|
from typing import Final, Literal, TypedDict
|
|
25
27
|
|
|
28
|
+
from buildlog.confidence import ConfidenceConfig, ConfidenceMetrics
|
|
29
|
+
from buildlog.confidence import calculate_confidence as calculate_continuous_confidence
|
|
30
|
+
from buildlog.confidence import get_confidence_tier
|
|
26
31
|
from buildlog.distill import CATEGORIES, PatternDict, distill_all
|
|
27
32
|
from buildlog.embeddings import EmbeddingBackend, get_backend, get_default_backend
|
|
28
33
|
|
|
29
34
|
logger = logging.getLogger(__name__)
|
|
30
35
|
|
|
36
|
+
|
|
37
|
+
def _load_review_learnings(buildlog_dir: Path) -> dict:
|
|
38
|
+
"""Load review learnings from .buildlog/review_learnings.json."""
|
|
39
|
+
learnings_path = buildlog_dir / ".buildlog" / "review_learnings.json"
|
|
40
|
+
if not learnings_path.exists():
|
|
41
|
+
return {"learnings": {}}
|
|
42
|
+
try:
|
|
43
|
+
return json.loads(learnings_path.read_text())
|
|
44
|
+
except (json.JSONDecodeError, OSError):
|
|
45
|
+
return {"learnings": {}}
|
|
46
|
+
|
|
47
|
+
|
|
31
48
|
# Configuration constants
|
|
32
49
|
MIN_SIMILARITY_THRESHOLD: Final[float] = 0.7
|
|
33
50
|
HIGH_CONFIDENCE_FREQUENCY: Final[int] = 3
|
|
@@ -39,8 +56,8 @@ OutputFormat = Literal["yaml", "json", "markdown", "rules", "settings"]
|
|
|
39
56
|
ConfidenceLevel = Literal["high", "medium", "low"]
|
|
40
57
|
|
|
41
58
|
|
|
42
|
-
class
|
|
43
|
-
"""
|
|
59
|
+
class _SkillDictRequired(TypedDict):
|
|
60
|
+
"""Required fields for skill dictionary (base class)."""
|
|
44
61
|
|
|
45
62
|
id: str
|
|
46
63
|
category: str
|
|
@@ -51,6 +68,17 @@ class SkillDict(TypedDict):
|
|
|
51
68
|
tags: list[str]
|
|
52
69
|
|
|
53
70
|
|
|
71
|
+
class SkillDict(_SkillDictRequired, total=False):
|
|
72
|
+
"""Type for skill dictionary representation.
|
|
73
|
+
|
|
74
|
+
Inherits required fields from _SkillDictRequired.
|
|
75
|
+
Optional fields are only present when continuous confidence is enabled.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
confidence_score: float
|
|
79
|
+
confidence_tier: str
|
|
80
|
+
|
|
81
|
+
|
|
54
82
|
class SkillSetDict(TypedDict):
|
|
55
83
|
"""Type for full skill set dictionary."""
|
|
56
84
|
|
|
@@ -66,6 +94,17 @@ class Skill:
|
|
|
66
94
|
|
|
67
95
|
Represents a single actionable rule derived from one or more
|
|
68
96
|
similar insights across buildlog entries.
|
|
97
|
+
|
|
98
|
+
Attributes:
|
|
99
|
+
id: Stable identifier for the skill.
|
|
100
|
+
category: Category (architectural, workflow, etc.).
|
|
101
|
+
rule: The actionable rule text.
|
|
102
|
+
frequency: How many times this pattern was seen.
|
|
103
|
+
confidence: Discrete confidence level (high/medium/low).
|
|
104
|
+
sources: List of source files where this pattern appeared.
|
|
105
|
+
tags: Extracted technology/concept tags.
|
|
106
|
+
confidence_score: Continuous confidence score (0-1), if calculated.
|
|
107
|
+
confidence_tier: Descriptive tier (speculative/provisional/stable/entrenched).
|
|
69
108
|
"""
|
|
70
109
|
|
|
71
110
|
id: str
|
|
@@ -75,10 +114,16 @@ class Skill:
|
|
|
75
114
|
confidence: ConfidenceLevel
|
|
76
115
|
sources: list[str] = field(default_factory=list)
|
|
77
116
|
tags: list[str] = field(default_factory=list)
|
|
117
|
+
confidence_score: float | None = None
|
|
118
|
+
confidence_tier: str | None = None
|
|
78
119
|
|
|
79
120
|
def to_dict(self) -> SkillDict:
|
|
80
|
-
"""Convert to dictionary for serialization.
|
|
81
|
-
|
|
121
|
+
"""Convert to dictionary for serialization.
|
|
122
|
+
|
|
123
|
+
Only includes optional fields (confidence_score, confidence_tier)
|
|
124
|
+
when they are set.
|
|
125
|
+
"""
|
|
126
|
+
result = SkillDict(
|
|
82
127
|
id=self.id,
|
|
83
128
|
category=self.category,
|
|
84
129
|
rule=self.rule,
|
|
@@ -87,6 +132,11 @@ class Skill:
|
|
|
87
132
|
sources=self.sources,
|
|
88
133
|
tags=self.tags,
|
|
89
134
|
)
|
|
135
|
+
if self.confidence_score is not None:
|
|
136
|
+
result["confidence_score"] = self.confidence_score
|
|
137
|
+
if self.confidence_tier is not None:
|
|
138
|
+
result["confidence_tier"] = self.confidence_tier
|
|
139
|
+
return result
|
|
90
140
|
|
|
91
141
|
|
|
92
142
|
@dataclass
|
|
@@ -253,7 +303,7 @@ def _deduplicate_insights(
|
|
|
253
303
|
patterns: list[PatternDict],
|
|
254
304
|
threshold: float = MIN_SIMILARITY_THRESHOLD,
|
|
255
305
|
backend: EmbeddingBackend | None = None,
|
|
256
|
-
) -> list[tuple[str, int, list[str], date | None]]:
|
|
306
|
+
) -> list[tuple[str, int, list[str], date | None, date | None]]:
|
|
257
307
|
"""Deduplicate similar insights into merged rules.
|
|
258
308
|
|
|
259
309
|
Args:
|
|
@@ -262,7 +312,8 @@ def _deduplicate_insights(
|
|
|
262
312
|
backend: Embedding backend for similarity computation.
|
|
263
313
|
|
|
264
314
|
Returns:
|
|
265
|
-
List of (rule, frequency, sources, most_recent_date) tuples.
|
|
315
|
+
List of (rule, frequency, sources, most_recent_date, earliest_date) tuples.
|
|
316
|
+
Both dates can be None if no valid dates are found in the patterns.
|
|
266
317
|
"""
|
|
267
318
|
if not patterns:
|
|
268
319
|
return []
|
|
@@ -289,7 +340,7 @@ def _deduplicate_insights(
|
|
|
289
340
|
groups.append([pattern])
|
|
290
341
|
|
|
291
342
|
# Convert groups to deduplicated rules
|
|
292
|
-
results: list[tuple[str, int, list[str], date | None]] = []
|
|
343
|
+
results: list[tuple[str, int, list[str], date | None, date | None]] = []
|
|
293
344
|
|
|
294
345
|
for group in groups:
|
|
295
346
|
# Use the shortest insight as the canonical rule (often cleaner)
|
|
@@ -298,7 +349,7 @@ def _deduplicate_insights(
|
|
|
298
349
|
frequency = len(group)
|
|
299
350
|
sources = sorted(set(p["source"] for p in group))
|
|
300
351
|
|
|
301
|
-
# Find most recent
|
|
352
|
+
# Find most recent and earliest dates
|
|
302
353
|
dates: list[date] = []
|
|
303
354
|
for p in group:
|
|
304
355
|
try:
|
|
@@ -307,18 +358,61 @@ def _deduplicate_insights(
|
|
|
307
358
|
pass
|
|
308
359
|
|
|
309
360
|
most_recent = max(dates) if dates else None
|
|
310
|
-
|
|
361
|
+
earliest = min(dates) if dates else None
|
|
362
|
+
results.append((rule, frequency, sources, most_recent, earliest))
|
|
311
363
|
|
|
312
364
|
return results
|
|
313
365
|
|
|
314
366
|
|
|
367
|
+
def _build_confidence_metrics(
|
|
368
|
+
frequency: int,
|
|
369
|
+
most_recent: date | None,
|
|
370
|
+
earliest: date | None,
|
|
371
|
+
) -> ConfidenceMetrics:
|
|
372
|
+
"""Build ConfidenceMetrics from deduplication results.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
frequency: Number of times the pattern was seen.
|
|
376
|
+
most_recent: Most recent occurrence date.
|
|
377
|
+
earliest: Earliest occurrence date.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
ConfidenceMetrics for continuous confidence calculation.
|
|
381
|
+
"""
|
|
382
|
+
# Use midnight UTC for date-based timestamps
|
|
383
|
+
now = datetime.now(timezone.utc)
|
|
384
|
+
|
|
385
|
+
if most_recent is not None:
|
|
386
|
+
last_reinforced = datetime(
|
|
387
|
+
most_recent.year, most_recent.month, most_recent.day, tzinfo=timezone.utc
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
last_reinforced = now
|
|
391
|
+
|
|
392
|
+
if earliest is not None:
|
|
393
|
+
first_seen = datetime(
|
|
394
|
+
earliest.year, earliest.month, earliest.day, tzinfo=timezone.utc
|
|
395
|
+
)
|
|
396
|
+
else:
|
|
397
|
+
first_seen = last_reinforced
|
|
398
|
+
|
|
399
|
+
return ConfidenceMetrics(
|
|
400
|
+
reinforcement_count=frequency,
|
|
401
|
+
last_reinforced=last_reinforced,
|
|
402
|
+
contradiction_count=0, # Deferred: no contradiction tracking yet
|
|
403
|
+
first_seen=first_seen,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
315
407
|
def generate_skills(
|
|
316
408
|
buildlog_dir: Path,
|
|
317
409
|
min_frequency: int = 1,
|
|
318
410
|
since_date: date | None = None,
|
|
319
411
|
embedding_backend: str | None = None,
|
|
412
|
+
confidence_config: ConfidenceConfig | None = None,
|
|
413
|
+
include_review_learnings: bool = True,
|
|
320
414
|
) -> SkillSet:
|
|
321
|
-
"""Generate skills from buildlog patterns.
|
|
415
|
+
"""Generate skills from buildlog patterns and review learnings.
|
|
322
416
|
|
|
323
417
|
Args:
|
|
324
418
|
buildlog_dir: Path to the buildlog directory.
|
|
@@ -326,6 +420,12 @@ def generate_skills(
|
|
|
326
420
|
since_date: Only include patterns from this date onward.
|
|
327
421
|
embedding_backend: Name of embedding backend for deduplication.
|
|
328
422
|
Options: "token" (default), "sentence-transformers", "openai".
|
|
423
|
+
confidence_config: Configuration for continuous confidence scoring.
|
|
424
|
+
If provided, skills will include confidence_score and confidence_tier.
|
|
425
|
+
If None, only discrete confidence levels (high/medium/low) are computed.
|
|
426
|
+
include_review_learnings: Whether to include learnings from code reviews.
|
|
427
|
+
When True, loads .buildlog/review_learnings.json and merges
|
|
428
|
+
review learnings into the skill set.
|
|
329
429
|
|
|
330
430
|
Returns:
|
|
331
431
|
SkillSet with generated skills.
|
|
@@ -341,6 +441,9 @@ def generate_skills(
|
|
|
341
441
|
)
|
|
342
442
|
logger.info("Using embedding backend: %s", backend.name)
|
|
343
443
|
|
|
444
|
+
# Capture reference time for confidence calculations
|
|
445
|
+
t_now = datetime.now(timezone.utc) if confidence_config else None
|
|
446
|
+
|
|
344
447
|
skills_by_category: dict[str, list[Skill]] = {}
|
|
345
448
|
|
|
346
449
|
for category in CATEGORIES:
|
|
@@ -348,10 +451,22 @@ def generate_skills(
|
|
|
348
451
|
deduplicated = _deduplicate_insights(patterns, backend=backend)
|
|
349
452
|
|
|
350
453
|
skills: list[Skill] = []
|
|
351
|
-
for rule, frequency, sources, most_recent in deduplicated:
|
|
454
|
+
for rule, frequency, sources, most_recent, earliest in deduplicated:
|
|
352
455
|
if frequency < min_frequency:
|
|
353
456
|
continue
|
|
354
457
|
|
|
458
|
+
# Calculate continuous confidence if config provided
|
|
459
|
+
confidence_score: float | None = None
|
|
460
|
+
confidence_tier: str | None = None
|
|
461
|
+
if confidence_config is not None and t_now is not None:
|
|
462
|
+
metrics = _build_confidence_metrics(frequency, most_recent, earliest)
|
|
463
|
+
confidence_score = calculate_continuous_confidence(
|
|
464
|
+
metrics, confidence_config, t_now
|
|
465
|
+
)
|
|
466
|
+
confidence_tier = get_confidence_tier(
|
|
467
|
+
confidence_score, confidence_config
|
|
468
|
+
).value
|
|
469
|
+
|
|
355
470
|
skill = Skill(
|
|
356
471
|
id=_generate_skill_id(category, rule),
|
|
357
472
|
category=category,
|
|
@@ -360,6 +475,8 @@ def generate_skills(
|
|
|
360
475
|
confidence=_calculate_confidence(frequency, most_recent),
|
|
361
476
|
sources=sources,
|
|
362
477
|
tags=_extract_tags(rule),
|
|
478
|
+
confidence_score=confidence_score,
|
|
479
|
+
confidence_tier=confidence_tier,
|
|
363
480
|
)
|
|
364
481
|
skills.append(skill)
|
|
365
482
|
|
|
@@ -367,6 +484,111 @@ def generate_skills(
|
|
|
367
484
|
skills.sort(key=lambda s: (-s.frequency, s.rule))
|
|
368
485
|
skills_by_category[category] = skills
|
|
369
486
|
|
|
487
|
+
# Merge review learnings if requested
|
|
488
|
+
if include_review_learnings:
|
|
489
|
+
review_data = _load_review_learnings(buildlog_dir)
|
|
490
|
+
learnings = review_data.get("learnings", {})
|
|
491
|
+
|
|
492
|
+
for learning_id, learning_dict in learnings.items():
|
|
493
|
+
category = learning_dict.get("category", "workflow")
|
|
494
|
+
rule = learning_dict.get("rule", "")
|
|
495
|
+
|
|
496
|
+
if not rule:
|
|
497
|
+
continue
|
|
498
|
+
|
|
499
|
+
# Parse timestamps for confidence calculation
|
|
500
|
+
first_seen_str = learning_dict.get("first_seen", "")
|
|
501
|
+
last_reinforced_str = learning_dict.get("last_reinforced", "")
|
|
502
|
+
|
|
503
|
+
try:
|
|
504
|
+
first_seen = datetime.fromisoformat(first_seen_str)
|
|
505
|
+
if first_seen.tzinfo is None:
|
|
506
|
+
first_seen = first_seen.replace(tzinfo=timezone.utc)
|
|
507
|
+
except (ValueError, TypeError):
|
|
508
|
+
first_seen = datetime.now(timezone.utc)
|
|
509
|
+
|
|
510
|
+
try:
|
|
511
|
+
last_reinforced = datetime.fromisoformat(last_reinforced_str)
|
|
512
|
+
if last_reinforced.tzinfo is None:
|
|
513
|
+
last_reinforced = last_reinforced.replace(tzinfo=timezone.utc)
|
|
514
|
+
except (ValueError, TypeError):
|
|
515
|
+
last_reinforced = datetime.now(timezone.utc)
|
|
516
|
+
|
|
517
|
+
# Get frequency from reinforcement count
|
|
518
|
+
frequency = learning_dict.get("reinforcement_count", 1)
|
|
519
|
+
|
|
520
|
+
# Check for duplicate rules in existing skills (by ID match)
|
|
521
|
+
existing_skill = None
|
|
522
|
+
if category in skills_by_category:
|
|
523
|
+
for skill in skills_by_category[category]:
|
|
524
|
+
if skill.id == learning_id:
|
|
525
|
+
existing_skill = skill
|
|
526
|
+
break
|
|
527
|
+
|
|
528
|
+
if existing_skill is not None:
|
|
529
|
+
# Merge: boost the existing skill's frequency
|
|
530
|
+
existing_skill = Skill(
|
|
531
|
+
id=existing_skill.id,
|
|
532
|
+
category=existing_skill.category,
|
|
533
|
+
rule=existing_skill.rule,
|
|
534
|
+
frequency=existing_skill.frequency + frequency,
|
|
535
|
+
confidence=existing_skill.confidence,
|
|
536
|
+
sources=existing_skill.sources
|
|
537
|
+
+ [learning_dict.get("source", "review")],
|
|
538
|
+
tags=existing_skill.tags,
|
|
539
|
+
confidence_score=existing_skill.confidence_score,
|
|
540
|
+
confidence_tier=existing_skill.confidence_tier,
|
|
541
|
+
)
|
|
542
|
+
# Replace in list
|
|
543
|
+
skills_by_category[category] = [
|
|
544
|
+
existing_skill if s.id == existing_skill.id else s
|
|
545
|
+
for s in skills_by_category[category]
|
|
546
|
+
]
|
|
547
|
+
else:
|
|
548
|
+
# Create new skill from review learning
|
|
549
|
+
review_conf_score: float | None = None
|
|
550
|
+
review_conf_tier: str | None = None
|
|
551
|
+
|
|
552
|
+
if confidence_config is not None and t_now is not None:
|
|
553
|
+
metrics = ConfidenceMetrics(
|
|
554
|
+
reinforcement_count=frequency,
|
|
555
|
+
last_reinforced=last_reinforced,
|
|
556
|
+
contradiction_count=learning_dict.get("contradiction_count", 0),
|
|
557
|
+
first_seen=first_seen,
|
|
558
|
+
)
|
|
559
|
+
review_conf_score = calculate_continuous_confidence(
|
|
560
|
+
metrics, confidence_config, t_now
|
|
561
|
+
)
|
|
562
|
+
review_conf_tier = get_confidence_tier(
|
|
563
|
+
review_conf_score, confidence_config
|
|
564
|
+
).value
|
|
565
|
+
|
|
566
|
+
# Calculate discrete confidence from most recent date
|
|
567
|
+
discrete_confidence = _calculate_confidence(
|
|
568
|
+
frequency, last_reinforced.date()
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
skill = Skill(
|
|
572
|
+
id=learning_id,
|
|
573
|
+
category=category,
|
|
574
|
+
rule=rule,
|
|
575
|
+
frequency=frequency,
|
|
576
|
+
confidence=discrete_confidence,
|
|
577
|
+
sources=[learning_dict.get("source", "review")],
|
|
578
|
+
tags=_extract_tags(rule),
|
|
579
|
+
confidence_score=review_conf_score,
|
|
580
|
+
confidence_tier=review_conf_tier,
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
# Add to category
|
|
584
|
+
if category not in skills_by_category:
|
|
585
|
+
skills_by_category[category] = []
|
|
586
|
+
skills_by_category[category].append(skill)
|
|
587
|
+
|
|
588
|
+
# Re-sort categories after adding review learnings
|
|
589
|
+
for category in skills_by_category:
|
|
590
|
+
skills_by_category[category].sort(key=lambda s: (-s.frequency, s.rule))
|
|
591
|
+
|
|
370
592
|
return SkillSet(
|
|
371
593
|
generated_at=datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
372
594
|
source_entries=result.entry_count,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Post-generation script to update CLAUDE.md with buildlog instructions."""
|
|
3
3
|
|
|
4
|
-
import os
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
|
|
7
|
-
CLAUDE_MD_SECTION =
|
|
6
|
+
CLAUDE_MD_SECTION = """
|
|
8
7
|
## Build Journal
|
|
9
8
|
|
|
10
|
-
After completing significant work (features, debugging sessions, deployments,
|
|
9
|
+
After completing significant work (features, debugging sessions, deployments,
|
|
10
|
+
2+ hour focused sessions), write a build journal entry.
|
|
11
11
|
|
|
12
12
|
**Location:** `buildlog/YYYY-MM-DD-{slug}.md`
|
|
13
13
|
**Template:** `buildlog/_TEMPLATE.md`
|
|
@@ -15,18 +15,21 @@ After completing significant work (features, debugging sessions, deployments, 2+
|
|
|
15
15
|
### Required Sections
|
|
16
16
|
1. **The Goal** - What we built and why
|
|
17
17
|
2. **What We Built** - Architecture diagram, components table
|
|
18
|
-
3. **The Journey** - Chronological INCLUDING mistakes, wrong turns, actual
|
|
18
|
+
3. **The Journey** - Chronological INCLUDING mistakes, wrong turns, actual errors
|
|
19
19
|
4. **Test Results** - Actual commands run, actual outputs received
|
|
20
20
|
5. **Code Samples** - Key snippets with context (not full files)
|
|
21
21
|
6. **AI Experience Reflection** - Meta-commentary on the collaboration
|
|
22
|
-
7. **Improvements** - Actionable learnings: architectural, workflow, tool usage
|
|
22
|
+
7. **Improvements** - Actionable learnings: architectural, workflow, tool usage
|
|
23
23
|
|
|
24
|
-
The **Improvements** section is critical - capture concrete insights like
|
|
24
|
+
The **Improvements** section is critical - capture concrete insights like
|
|
25
|
+
"Should have defined the API contract before implementing the client"
|
|
26
|
+
not vague observations like "Should have planned better."
|
|
25
27
|
|
|
26
28
|
**Quality bar:** Publishable as a $500+ Envato Tuts+/Manning tutorial.
|
|
27
29
|
|
|
28
30
|
After significant work, ask: "Should I write a build journal entry for this?"
|
|
29
|
-
|
|
31
|
+
"""
|
|
32
|
+
|
|
30
33
|
|
|
31
34
|
def main():
|
|
32
35
|
claude_md = Path("CLAUDE.md")
|
|
@@ -47,5 +50,6 @@ def main():
|
|
|
47
50
|
|
|
48
51
|
print("Added Build Journal section to CLAUDE.md")
|
|
49
52
|
|
|
53
|
+
|
|
50
54
|
if __name__ == "__main__":
|
|
51
55
|
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: buildlog
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Engineering notebook for AI-assisted development
|
|
5
5
|
Project-URL: Homepage, https://github.com/Peleke/buildlog-template
|
|
6
6
|
Project-URL: Repository, https://github.com/Peleke/buildlog-template
|
|
@@ -33,6 +33,7 @@ Requires-Dist: black>=24.0.0; extra == 'dev'
|
|
|
33
33
|
Requires-Dist: flake8>=7.0.0; extra == 'dev'
|
|
34
34
|
Requires-Dist: isort>=5.13.0; extra == 'dev'
|
|
35
35
|
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
|
|
36
37
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
37
38
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
38
39
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
@@ -59,7 +60,7 @@ Description-Content-Type: text/markdown
|
|
|
59
60
|
|
|
60
61
|
**Capture your work as publishable content. Include the fuckups.**
|
|
61
62
|
|
|
62
|
-
<img src="assets/
|
|
63
|
+
<img src="assets/hero-notebook.png" alt="buildlog - Engineering Notebook for AI-Assisted Development" width="800"/>
|
|
63
64
|
|
|
64
65
|
[Quick Start](#-quick-start) · [The Pipeline](#-the-pipeline) · [Commands](#-commands) · [Philosophy](#-philosophy)
|
|
65
66
|
|
|
@@ -677,6 +678,7 @@ The MCP server lets Claude Code interact with your buildlog rules directly. Your
|
|
|
677
678
|
| `buildlog_promote` | Write rules to CLAUDE.md, settings.json, or **Agent Skills** |
|
|
678
679
|
| `buildlog_reject` | Mark rules to exclude from future suggestions |
|
|
679
680
|
| `buildlog_diff` | Show rules pending review (not yet promoted/rejected) |
|
|
681
|
+
| `buildlog_learn_from_review` | Capture learnings from code review feedback |
|
|
680
682
|
|
|
681
683
|
### Promotion Targets via MCP
|
|
682
684
|
|
|
@@ -716,6 +718,136 @@ Claude: [calls buildlog_promote with target="skill"]
|
|
|
716
718
|
|
|
717
719
|
---
|
|
718
720
|
|
|
721
|
+
## Review Learning System
|
|
722
|
+
|
|
723
|
+
Beyond manual buildlog entries, buildlog can **learn from code reviews** in real-time. Every review becomes a teaching moment—rules get extracted, persisted, and gain confidence through reinforcement.
|
|
724
|
+
|
|
725
|
+
### How It Works
|
|
726
|
+
|
|
727
|
+
```mermaid
|
|
728
|
+
flowchart LR
|
|
729
|
+
A["Code Review"] --> B["Extract Rules"]
|
|
730
|
+
B --> C["learn_from_review()"]
|
|
731
|
+
C --> D["Persist to<br/>.buildlog/review_learnings.json"]
|
|
732
|
+
D --> E["Rules gain confidence<br/>through reinforcement"]
|
|
733
|
+
E --> F["Inject into<br/>future sessions"]
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### The MCP Tool
|
|
737
|
+
|
|
738
|
+
```python
|
|
739
|
+
buildlog_learn_from_review(
|
|
740
|
+
issues=[
|
|
741
|
+
{
|
|
742
|
+
"severity": "critical",
|
|
743
|
+
"category": "architectural",
|
|
744
|
+
"description": "No bounds validation on score input",
|
|
745
|
+
"rule_learned": "Validate invariants at function boundaries"
|
|
746
|
+
}
|
|
747
|
+
],
|
|
748
|
+
source="PR#42"
|
|
749
|
+
)
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
When the same rule is learned from multiple reviews, its confidence increases automatically.
|
|
753
|
+
|
|
754
|
+
### Reviewer Skills (The Brutal Feedback Loop)
|
|
755
|
+
|
|
756
|
+
buildlog ships with **ruthless reviewer personas** that output structured JSON compatible with `buildlog_learn_from_review()`. Every review teaches the system.
|
|
757
|
+
|
|
758
|
+
| Skill | Trigger | Focus |
|
|
759
|
+
|-------|---------|-------|
|
|
760
|
+
| **Ruthless Reviewer** | `review`, `code review` | Code quality, FP principles, invariants |
|
|
761
|
+
| **Test Terrorist** | `test review`, `coverage audit` | ALL test types: unit, integration, E2E, contract, property-based |
|
|
762
|
+
| **Security Karen** | `security review`, `owasp` | OWASP Top 10, input validation, auth |
|
|
763
|
+
| **Review Gauntlet** | `gauntlet`, `destroy my code` | All three reviewers in sequence |
|
|
764
|
+
|
|
765
|
+
#### The Review Gauntlet
|
|
766
|
+
|
|
767
|
+
Run all three reviewers for maximum brutality:
|
|
768
|
+
|
|
769
|
+
```
|
|
770
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
771
|
+
│ THE REVIEW GAUNTLET │
|
|
772
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
773
|
+
│ │
|
|
774
|
+
│ Your Code │
|
|
775
|
+
│ │ │
|
|
776
|
+
│ ▼ │
|
|
777
|
+
│ ┌──────────────────┐ │
|
|
778
|
+
│ │ RUTHLESS │ "Is this pure? Would it compile │
|
|
779
|
+
│ │ REVIEWER │ in Haskell?" │
|
|
780
|
+
│ └────────┬─────────┘ │
|
|
781
|
+
│ ▼ │
|
|
782
|
+
│ ┌──────────────────┐ │
|
|
783
|
+
│ │ TEST │ "Where are your contract tests? │
|
|
784
|
+
│ │ TERRORIST │ Show me the Gherkin." │
|
|
785
|
+
│ └────────┬─────────┘ │
|
|
786
|
+
│ ▼ │
|
|
787
|
+
│ ┌──────────────────┐ │
|
|
788
|
+
│ │ SECURITY │ "I need to speak to your security │
|
|
789
|
+
│ │ KAREN │ manager about this SQL query." │
|
|
790
|
+
│ └────────┬─────────┘ │
|
|
791
|
+
│ ▼ │
|
|
792
|
+
│ Combined Issues → buildlog_learn_from_review() │
|
|
793
|
+
│ ▼ │
|
|
794
|
+
│ Future sessions get smarter │
|
|
795
|
+
│ │
|
|
796
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
Each reviewer outputs structured JSON:
|
|
800
|
+
|
|
801
|
+
```json
|
|
802
|
+
{
|
|
803
|
+
"verdict": "BLOCKED",
|
|
804
|
+
"issues": [
|
|
805
|
+
{
|
|
806
|
+
"severity": "critical",
|
|
807
|
+
"category": "architectural",
|
|
808
|
+
"location": "src/api/handler.py:45",
|
|
809
|
+
"description": "Score bounds not validated",
|
|
810
|
+
"rule_learned": "Validate invariants at function boundaries",
|
|
811
|
+
"functional_principle": "Parse, don't validate"
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
}
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
After review, call `buildlog_learn_from_review(issues=...)` to persist the learnings.
|
|
818
|
+
|
|
819
|
+
### Test Terrorist Coverage
|
|
820
|
+
|
|
821
|
+
The Test Terrorist knows ALL test types:
|
|
822
|
+
|
|
823
|
+
| Layer | Test Types |
|
|
824
|
+
|-------|-----------|
|
|
825
|
+
| **Fundamentals** | Unit, Integration, E2E, Smoke |
|
|
826
|
+
| **User Flows** | BDD scenarios, persistence tests |
|
|
827
|
+
| **Advanced** | Contract (Pact), Property-based (Hypothesis), Metamorphic, Statistical, Mutation |
|
|
828
|
+
| **Specialized** | Chaos, Load/Performance, Accessibility |
|
|
829
|
+
|
|
830
|
+
**Contract tests are NON-NEGOTIABLE** for service boundaries. The Test Terrorist will find your missing ones.
|
|
831
|
+
|
|
832
|
+
### Security Karen's OWASP Obsession
|
|
833
|
+
|
|
834
|
+
Security Karen audits against OWASP Top 10 (2021):
|
|
835
|
+
|
|
836
|
+
- **A01**: Broken Access Control
|
|
837
|
+
- **A02**: Cryptographic Failures
|
|
838
|
+
- **A03**: Injection
|
|
839
|
+
- **A04**: Insecure Design
|
|
840
|
+
- **A05**: Security Misconfiguration
|
|
841
|
+
- **A06**: Vulnerable Components
|
|
842
|
+
- **A07**: Auth Failures
|
|
843
|
+
- **A08**: Integrity Failures
|
|
844
|
+
- **A09**: Logging Failures
|
|
845
|
+
- **A10**: SSRF
|
|
846
|
+
|
|
847
|
+
Plus: secrets management, input validation, API security, error handling.
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
719
851
|
## Philosophy
|
|
720
852
|
|
|
721
853
|
### 1. Write Fast, Not Pretty
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
buildlog/__init__.py,sha256=FrgjyZhC19YyB40rOXHJWTA4xKWx2Yn2heIhVraaZ7A,90
|
|
2
|
+
buildlog/cli.py,sha256=cmg77_RVJx8mdtStApS1KXxBUUB8Id6psHZjtHo33iE,14350
|
|
3
|
+
buildlog/confidence.py,sha256=EOkPxIH1_y7k6B3Hl7Wn0iR2qK_lumvOyyyqUdafXVY,9382
|
|
4
|
+
buildlog/distill.py,sha256=fqXW_YyBFIFhwIWhnR-TQ7U65gypqG-mcAzNBr-qaag,11262
|
|
5
|
+
buildlog/embeddings.py,sha256=vPydWjJVkYp172zFou-lJ737qsu6vRMQAMs143RGIpA,12364
|
|
6
|
+
buildlog/skills.py,sha256=C_tqspj9lVRkjF-g-KpKt8WoQ-Xq0e0l1VtXly_Naks,30055
|
|
7
|
+
buildlog/stats.py,sha256=2WdHdmzUNGobtWngmm9nA_UmqM7DQeAnZL8_rLQN8aw,13256
|
|
8
|
+
buildlog/core/__init__.py,sha256=ifByeEE3sM13EHWIKNAOjNc3zx_4qYEfyXdB4Woumuw,594
|
|
9
|
+
buildlog/core/operations.py,sha256=Q9wX8kfnpDBAC8ySHYXZi3vA9w0vuOyzOYl6JYB6YyM,22006
|
|
10
|
+
buildlog/mcp/__init__.py,sha256=jCLNUkYFrDcPd5dY9xbaaVANl-ZzdPim1BykgGY7f7U,334
|
|
11
|
+
buildlog/mcp/server.py,sha256=vlfXlRvGfoXBhkHv2y5e1jc7JH2Hkv7G1obSdxlQ14w,583
|
|
12
|
+
buildlog/mcp/tools.py,sha256=Rfk3dS4bLflvaUa5O2YLdvBx354ZQ_Lfy-Uoaz_AjGo,4397
|
|
13
|
+
buildlog/render/__init__.py,sha256=VxJWEmcX7pSiC3W-ytsHv9gNVUr4hJrVHBW084kEnAI,1993
|
|
14
|
+
buildlog/render/base.py,sha256=gQfvOsH1zttAo10xtEyNsAbqZ4NRSPiDihO-aiGgTsw,533
|
|
15
|
+
buildlog/render/claude_md.py,sha256=Z_E6MbJyVM_hJSoB4KL2rvbt5UEQHekTpJijj106lsI,2624
|
|
16
|
+
buildlog/render/settings_json.py,sha256=4DS5OWksPrFCa7MIgWIu0t4rxYmItpMdGfTqMX3aMNs,2495
|
|
17
|
+
buildlog/render/skill.py,sha256=_7umIS1Ms1oQ2_PopYueFjX41nMq1p28yJp6DhXFdgU,5981
|
|
18
|
+
buildlog/render/tracking.py,sha256=6O0RIU-1gjVG-_S5dmXLz6RCMsQoHOR2u5___UpqXEo,1294
|
|
19
|
+
buildlog-0.4.0.data/data/share/buildlog/copier.yml,sha256=A-1JKV59kOe0BQosGUBgRCg7iQozP_qyA3zfoHwpBKY,927
|
|
20
|
+
buildlog-0.4.0.data/data/share/buildlog/post_gen.py,sha256=XFlo40LuPpAsBhIRRRtHqvU3_5POss4L401hp35ijhw,1744
|
|
21
|
+
buildlog-0.4.0.data/data/share/buildlog/template/buildlog/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
buildlog-0.4.0.data/data/share/buildlog/template/buildlog/2026-01-01-example.md,sha256=7x9sKmydfmfKyNz9hV7MtYnQJuBwbxNanbPOcpQDDZQ,7040
|
|
23
|
+
buildlog-0.4.0.data/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md,sha256=osclytWwl5jUiTgSpuT4cT3h3oPvCkZ5GPCnFuJZNcY,3802
|
|
24
|
+
buildlog-0.4.0.data/data/share/buildlog/template/buildlog/_TEMPLATE.md,sha256=CUvxgcx1-9XT_EdQ8e_vnuPq_h-u1uhXJgForJU2Pso,2932
|
|
25
|
+
buildlog-0.4.0.data/data/share/buildlog/template/buildlog/assets/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
buildlog-0.4.0.dist-info/METADATA,sha256=adRpA8HpJlbAFLk7UCbHPqhWXNU7Xp9WEfYo-e4GqXI,30019
|
|
27
|
+
buildlog-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
28
|
+
buildlog-0.4.0.dist-info/entry_points.txt,sha256=BMFclPOomp_sgaa0OqBg6LfqCMlqzjZV88ww5TrPPoo,87
|
|
29
|
+
buildlog-0.4.0.dist-info/licenses/LICENSE,sha256=fAgt-akug9nAwIj6M-SIf8u3ck-T7pJTwfmy9vWYASk,1074
|
|
30
|
+
buildlog-0.4.0.dist-info/RECORD,,
|
buildlog-0.2.0.dist-info/RECORD
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
buildlog/__init__.py,sha256=FrgjyZhC19YyB40rOXHJWTA4xKWx2Yn2heIhVraaZ7A,90
|
|
2
|
-
buildlog/cli.py,sha256=cmg77_RVJx8mdtStApS1KXxBUUB8Id6psHZjtHo33iE,14350
|
|
3
|
-
buildlog/distill.py,sha256=fqXW_YyBFIFhwIWhnR-TQ7U65gypqG-mcAzNBr-qaag,11262
|
|
4
|
-
buildlog/embeddings.py,sha256=vPydWjJVkYp172zFou-lJ737qsu6vRMQAMs143RGIpA,12364
|
|
5
|
-
buildlog/skills.py,sha256=gu26MuIBgo-Jm6GQcVvbXHmV6MNp-HX3h7WC5buiyJ8,20686
|
|
6
|
-
buildlog/stats.py,sha256=2WdHdmzUNGobtWngmm9nA_UmqM7DQeAnZL8_rLQN8aw,13256
|
|
7
|
-
buildlog/core/__init__.py,sha256=07N1gRiPQQTBtLp9rsEErh39sgXWZSlEKWBn708SoQk,412
|
|
8
|
-
buildlog/core/operations.py,sha256=o01z2Sy0fgiBK6Z90Lkg6ACoqihH3-HC-hkPBSdj9mA,10656
|
|
9
|
-
buildlog/mcp/__init__.py,sha256=Eaoa7aRWa428ORxyvneH1cKW7XxuBpF4qvM9mTEH7Ds,268
|
|
10
|
-
buildlog/mcp/server.py,sha256=sgEMYBYq1tNKddvtyTpQ_M1dZHj7FhePDzwz7saIPH0,512
|
|
11
|
-
buildlog/mcp/tools.py,sha256=KbO1NjogCWHECTIzFYSeAikJGG8fIMDNDOBicdKXDdY,2818
|
|
12
|
-
buildlog/render/__init__.py,sha256=VxJWEmcX7pSiC3W-ytsHv9gNVUr4hJrVHBW084kEnAI,1993
|
|
13
|
-
buildlog/render/base.py,sha256=gQfvOsH1zttAo10xtEyNsAbqZ4NRSPiDihO-aiGgTsw,533
|
|
14
|
-
buildlog/render/claude_md.py,sha256=Z_E6MbJyVM_hJSoB4KL2rvbt5UEQHekTpJijj106lsI,2624
|
|
15
|
-
buildlog/render/settings_json.py,sha256=4DS5OWksPrFCa7MIgWIu0t4rxYmItpMdGfTqMX3aMNs,2495
|
|
16
|
-
buildlog/render/skill.py,sha256=_7umIS1Ms1oQ2_PopYueFjX41nMq1p28yJp6DhXFdgU,5981
|
|
17
|
-
buildlog/render/tracking.py,sha256=6O0RIU-1gjVG-_S5dmXLz6RCMsQoHOR2u5___UpqXEo,1294
|
|
18
|
-
buildlog-0.2.0.data/data/share/buildlog/copier.yml,sha256=A-1JKV59kOe0BQosGUBgRCg7iQozP_qyA3zfoHwpBKY,927
|
|
19
|
-
buildlog-0.2.0.data/data/share/buildlog/post_gen.py,sha256=ffVG-1MMXbffKT8OMNvaQmyVDcBjwD8qTYpCaoyyZAQ,1778
|
|
20
|
-
buildlog-0.2.0.data/data/share/buildlog/template/buildlog/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
buildlog-0.2.0.data/data/share/buildlog/template/buildlog/2026-01-01-example.md,sha256=7x9sKmydfmfKyNz9hV7MtYnQJuBwbxNanbPOcpQDDZQ,7040
|
|
22
|
-
buildlog-0.2.0.data/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md,sha256=osclytWwl5jUiTgSpuT4cT3h3oPvCkZ5GPCnFuJZNcY,3802
|
|
23
|
-
buildlog-0.2.0.data/data/share/buildlog/template/buildlog/_TEMPLATE.md,sha256=CUvxgcx1-9XT_EdQ8e_vnuPq_h-u1uhXJgForJU2Pso,2932
|
|
24
|
-
buildlog-0.2.0.data/data/share/buildlog/template/buildlog/assets/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
-
buildlog-0.2.0.dist-info/METADATA,sha256=pRmXAYYlZhVCwjRXdbTQdtmf8Bz2KijyBGHn65hPYKQ,24161
|
|
26
|
-
buildlog-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
27
|
-
buildlog-0.2.0.dist-info/entry_points.txt,sha256=BMFclPOomp_sgaa0OqBg6LfqCMlqzjZV88ww5TrPPoo,87
|
|
28
|
-
buildlog-0.2.0.dist-info/licenses/LICENSE,sha256=fAgt-akug9nAwIj6M-SIf8u3ck-T7pJTwfmy9vWYASk,1074
|
|
29
|
-
buildlog-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md
RENAMED
|
File without changes
|
{buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE.md
RENAMED
|
File without changes
|
{buildlog-0.2.0.data → buildlog-0.4.0.data}/data/share/buildlog/template/buildlog/assets/.gitkeep
RENAMED
|
File without changes
|