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/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 SkillDict(TypedDict):
43
- """Type for skill dictionary representation."""
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
- return SkillDict(
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 date
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
- results.append((rule, frequency, sources, most_recent))
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, 2+ hour focused sessions), write a build journal entry.
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 error messages
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, domain knowledge
22
+ 7. **Improvements** - Actionable learnings: architectural, workflow, tool usage
23
23
 
24
- The **Improvements** section is critical - capture concrete insights like "Should have defined the API contract before implementing the client" not vague observations like "Should have planned better."
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.2.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/hero3.png" alt="Chaos to Order - buildlog transforms messy development sessions into structured knowledge" width="800"/>
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,,
@@ -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,,