buildlog 0.8.0__py3-none-any.whl → 0.10.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 +491 -30
- buildlog/constants.py +121 -0
- buildlog/core/__init__.py +44 -0
- buildlog/core/operations.py +1189 -13
- buildlog/data/seeds/bragi.yaml +61 -0
- buildlog/llm.py +51 -4
- buildlog/mcp/__init__.py +51 -3
- buildlog/mcp/server.py +40 -0
- buildlog/mcp/tools.py +526 -12
- buildlog/seed_engine/__init__.py +2 -0
- buildlog/seed_engine/llm_extractor.py +121 -0
- buildlog/seed_engine/pipeline.py +45 -1
- {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/post_gen.py +10 -5
- buildlog-0.10.0.data/data/share/buildlog/template/buildlog/.gitkeep +0 -0
- buildlog-0.10.0.data/data/share/buildlog/template/buildlog/assets/.gitkeep +0 -0
- buildlog-0.10.0.dist-info/METADATA +248 -0
- {buildlog-0.8.0.dist-info → buildlog-0.10.0.dist-info}/RECORD +27 -22
- buildlog-0.8.0.dist-info/METADATA +0 -151
- {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/copier.yml +0 -0
- {buildlog-0.8.0.data/data/share/buildlog/template/buildlog → buildlog-0.10.0.data/data/share/buildlog/template/buildlog/.buildlog}/.gitkeep +0 -0
- {buildlog-0.8.0.data/data/share/buildlog/template/buildlog/assets → buildlog-0.10.0.data/data/share/buildlog/template/buildlog/.buildlog/seeds}/.gitkeep +0 -0
- {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/2026-01-01-example.md +0 -0
- {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
- {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE.md +0 -0
- {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE_QUICK.md +0 -0
- {buildlog-0.8.0.dist-info → buildlog-0.10.0.dist-info}/WHEEL +0 -0
- {buildlog-0.8.0.dist-info → buildlog-0.10.0.dist-info}/entry_points.txt +0 -0
- {buildlog-0.8.0.dist-info → buildlog-0.10.0.dist-info}/licenses/LICENSE +0 -0
buildlog/mcp/tools.py
CHANGED
|
@@ -10,19 +10,29 @@ from pathlib import Path
|
|
|
10
10
|
from typing import Literal
|
|
11
11
|
|
|
12
12
|
from buildlog.core import (
|
|
13
|
+
commit,
|
|
14
|
+
create_entry,
|
|
13
15
|
diff,
|
|
14
16
|
end_session,
|
|
17
|
+
gauntlet_generate,
|
|
18
|
+
gauntlet_loop_config,
|
|
19
|
+
generate_gauntlet_prompt,
|
|
15
20
|
get_bandit_status,
|
|
16
21
|
get_experiment_report,
|
|
22
|
+
get_gauntlet_rules,
|
|
23
|
+
get_overview,
|
|
17
24
|
get_rewards,
|
|
18
25
|
get_session_metrics,
|
|
26
|
+
init_buildlog,
|
|
19
27
|
learn_from_review,
|
|
28
|
+
list_entries,
|
|
20
29
|
log_mistake,
|
|
21
30
|
log_reward,
|
|
22
31
|
promote,
|
|
23
32
|
reject,
|
|
24
33
|
start_session,
|
|
25
34
|
status,
|
|
35
|
+
update_buildlog,
|
|
26
36
|
)
|
|
27
37
|
|
|
28
38
|
|
|
@@ -56,7 +66,7 @@ def buildlog_promote(
|
|
|
56
66
|
target: str = "claude_md",
|
|
57
67
|
buildlog_dir: str = "buildlog",
|
|
58
68
|
) -> dict:
|
|
59
|
-
"""Promote skills to your agent's
|
|
69
|
+
"""Promote selected skills to your agent's rule files.
|
|
60
70
|
|
|
61
71
|
Writes selected skills to agent-specific rule files.
|
|
62
72
|
|
|
@@ -97,7 +107,7 @@ def buildlog_reject(
|
|
|
97
107
|
def buildlog_diff(
|
|
98
108
|
buildlog_dir: str = "buildlog",
|
|
99
109
|
) -> dict:
|
|
100
|
-
"""Show skills
|
|
110
|
+
"""Show skills pending promotion or rejection.
|
|
101
111
|
|
|
102
112
|
Useful for seeing what's new since your last review.
|
|
103
113
|
|
|
@@ -116,7 +126,7 @@ def buildlog_learn_from_review(
|
|
|
116
126
|
source: str | None = None,
|
|
117
127
|
buildlog_dir: str = "buildlog",
|
|
118
128
|
) -> dict:
|
|
119
|
-
"""
|
|
129
|
+
"""Extract and persist learnings from code review feedback.
|
|
120
130
|
|
|
121
131
|
Call this after a review loop completes to persist learnings.
|
|
122
132
|
Each issue's rule_learned becomes a tracked learning that gains
|
|
@@ -164,7 +174,7 @@ def buildlog_log_reward(
|
|
|
164
174
|
notes: str | None = None,
|
|
165
175
|
buildlog_dir: str = "buildlog",
|
|
166
176
|
) -> dict:
|
|
167
|
-
"""Log
|
|
177
|
+
"""Log outcome feedback for bandit learning (accepted/revision/rejected).
|
|
168
178
|
|
|
169
179
|
Call this after agent work to provide feedback on the outcome.
|
|
170
180
|
This enables learning which rules are effective in which contexts.
|
|
@@ -269,7 +279,7 @@ def buildlog_experiment_start(
|
|
|
269
279
|
select_k: int = 3,
|
|
270
280
|
buildlog_dir: str = "buildlog",
|
|
271
281
|
) -> dict:
|
|
272
|
-
"""Start a
|
|
282
|
+
"""Start a tracked session with Thompson Sampling rule selection.
|
|
273
283
|
|
|
274
284
|
Begins tracking for a learning experiment. Uses Thompson Sampling
|
|
275
285
|
to select which rules will be "active" for this session based on
|
|
@@ -308,7 +318,7 @@ def buildlog_experiment_end(
|
|
|
308
318
|
notes: str | None = None,
|
|
309
319
|
buildlog_dir: str = "buildlog",
|
|
310
320
|
) -> dict:
|
|
311
|
-
"""End the current
|
|
321
|
+
"""End the current session and calculate metrics.
|
|
312
322
|
|
|
313
323
|
Finalizes the session and calculates metrics including:
|
|
314
324
|
- Total mistakes logged
|
|
@@ -341,7 +351,7 @@ def buildlog_log_mistake(
|
|
|
341
351
|
corrected_by_rule: str | None = None,
|
|
342
352
|
buildlog_dir: str = "buildlog",
|
|
343
353
|
) -> dict:
|
|
344
|
-
"""Log a mistake during the current session.
|
|
354
|
+
"""Log a mistake during the current session for RMR tracking.
|
|
345
355
|
|
|
346
356
|
Records the mistake and checks if it's a repeat of a prior mistake
|
|
347
357
|
(from earlier sessions). This enables measuring repeated-mistake rates.
|
|
@@ -374,7 +384,7 @@ def buildlog_experiment_metrics(
|
|
|
374
384
|
session_id: str | None = None,
|
|
375
385
|
buildlog_dir: str = "buildlog",
|
|
376
386
|
) -> dict:
|
|
377
|
-
"""Get
|
|
387
|
+
"""Get per-session or aggregate mistake rates and rule changes.
|
|
378
388
|
|
|
379
389
|
Returns mistake rates and rule changes for analysis.
|
|
380
390
|
|
|
@@ -400,7 +410,7 @@ def buildlog_experiment_metrics(
|
|
|
400
410
|
def buildlog_experiment_report(
|
|
401
411
|
buildlog_dir: str = "buildlog",
|
|
402
412
|
) -> dict:
|
|
403
|
-
"""Generate
|
|
413
|
+
"""Generate comprehensive report: summary, sessions, error classes.
|
|
404
414
|
|
|
405
415
|
Returns summary statistics, per-session breakdown, and error class analysis.
|
|
406
416
|
|
|
@@ -424,7 +434,7 @@ def buildlog_bandit_status(
|
|
|
424
434
|
context: str | None = None,
|
|
425
435
|
top_k: int = 10,
|
|
426
436
|
) -> dict:
|
|
427
|
-
"""Get Thompson Sampling bandit
|
|
437
|
+
"""Get Thompson Sampling bandit state and rule rankings by context.
|
|
428
438
|
|
|
429
439
|
Shows the bandit's learned beliefs about which rules are effective
|
|
430
440
|
for each error class context. Higher mean = bandit believes rule
|
|
@@ -466,7 +476,7 @@ def buildlog_gauntlet_issues(
|
|
|
466
476
|
source: str | None = None,
|
|
467
477
|
buildlog_dir: str = "buildlog",
|
|
468
478
|
) -> dict:
|
|
469
|
-
"""Process gauntlet
|
|
479
|
+
"""Process gauntlet issues and determine next action (fix/checkpoint/clean).
|
|
470
480
|
|
|
471
481
|
Call this after running a gauntlet review. It categorizes issues by
|
|
472
482
|
severity, persists learnings, and returns the appropriate next action.
|
|
@@ -525,7 +535,7 @@ def buildlog_gauntlet_accept_risk(
|
|
|
525
535
|
create_github_issues: bool = False,
|
|
526
536
|
repo: str | None = None,
|
|
527
537
|
) -> dict:
|
|
528
|
-
"""Accept risk for remaining issues, optionally
|
|
538
|
+
"""Accept risk for remaining issues, optionally create GitHub issues.
|
|
529
539
|
|
|
530
540
|
Call this when the user decides to accept remaining issues as risk
|
|
531
541
|
(e.g., only minors remain and they want to move on).
|
|
@@ -558,3 +568,507 @@ def buildlog_gauntlet_accept_risk(
|
|
|
558
568
|
repo=repo,
|
|
559
569
|
)
|
|
560
570
|
return asdict(result)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
# -----------------------------------------------------------------------------
|
|
574
|
+
# Entry & Overview MCP Tools
|
|
575
|
+
# -----------------------------------------------------------------------------
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def buildlog_gauntlet_rules(
|
|
579
|
+
persona: str | None = None,
|
|
580
|
+
format: str = "json",
|
|
581
|
+
buildlog_dir: str = "buildlog",
|
|
582
|
+
) -> dict:
|
|
583
|
+
"""Load gauntlet reviewer rules. Call before reviewing code to get rules.
|
|
584
|
+
|
|
585
|
+
Returns rules from curated reviewer personas (security_karen,
|
|
586
|
+
test_terrorist, bragi, etc.) in the requested format.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
persona: Filter to a specific persona, or None for all
|
|
590
|
+
format: Output format (json, yaml, markdown)
|
|
591
|
+
buildlog_dir: Path to buildlog directory
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
Dict with formatted rules, total_rules, personas list
|
|
595
|
+
"""
|
|
596
|
+
result = get_gauntlet_rules(persona=persona, format=format)
|
|
597
|
+
return asdict(result)
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def buildlog_overview(
|
|
601
|
+
buildlog_dir: str = "buildlog",
|
|
602
|
+
) -> dict:
|
|
603
|
+
"""Get project buildlog state at a glance. Call at session start for context.
|
|
604
|
+
|
|
605
|
+
Returns entry count, skill summary, active session, and render targets.
|
|
606
|
+
|
|
607
|
+
Args:
|
|
608
|
+
buildlog_dir: Path to buildlog directory
|
|
609
|
+
|
|
610
|
+
Returns:
|
|
611
|
+
Dict with entries, skills, active_session, render_targets
|
|
612
|
+
"""
|
|
613
|
+
result = get_overview(Path(buildlog_dir))
|
|
614
|
+
return asdict(result)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def buildlog_entry_new(
|
|
618
|
+
slug: str,
|
|
619
|
+
entry_date: str | None = None,
|
|
620
|
+
quick: bool = False,
|
|
621
|
+
buildlog_dir: str = "buildlog",
|
|
622
|
+
) -> dict:
|
|
623
|
+
"""Create a new buildlog journal entry for documenting work.
|
|
624
|
+
|
|
625
|
+
Creates a new dated entry from the template with slug sanitization.
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
slug: Short identifier (e.g., 'auth-api', 'bugfix-login')
|
|
629
|
+
entry_date: Date in YYYY-MM-DD format, or None for today
|
|
630
|
+
quick: Use short template if True
|
|
631
|
+
buildlog_dir: Path to buildlog directory
|
|
632
|
+
|
|
633
|
+
Returns:
|
|
634
|
+
Dict with entry_path, entry_name, date_str, template_used, message
|
|
635
|
+
|
|
636
|
+
Example:
|
|
637
|
+
buildlog_entry_new(slug="auth-api")
|
|
638
|
+
buildlog_entry_new(slug="bugfix", entry_date="2026-01-15", quick=True)
|
|
639
|
+
"""
|
|
640
|
+
result = create_entry(
|
|
641
|
+
Path(buildlog_dir),
|
|
642
|
+
slug=slug,
|
|
643
|
+
entry_date=entry_date,
|
|
644
|
+
quick=quick,
|
|
645
|
+
)
|
|
646
|
+
return asdict(result)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def buildlog_entry_list(
|
|
650
|
+
buildlog_dir: str = "buildlog",
|
|
651
|
+
) -> dict:
|
|
652
|
+
"""List all buildlog journal entries, most recent first.
|
|
653
|
+
|
|
654
|
+
Returns entry names and titles extracted from first lines.
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
buildlog_dir: Path to buildlog directory
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
Dict with entries list [{name, title}], count, message
|
|
661
|
+
"""
|
|
662
|
+
result = list_entries(Path(buildlog_dir))
|
|
663
|
+
return asdict(result)
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
# =============================================================================
|
|
667
|
+
# P0: Gauntlet loop tools
|
|
668
|
+
# =============================================================================
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def buildlog_commit(
|
|
672
|
+
message: str,
|
|
673
|
+
slug: str | None = None,
|
|
674
|
+
no_entry: bool = False,
|
|
675
|
+
extra_args: list[str] | None = None,
|
|
676
|
+
buildlog_dir: str = "buildlog",
|
|
677
|
+
) -> dict:
|
|
678
|
+
"""Commit code and append commit block to today's buildlog entry.
|
|
679
|
+
|
|
680
|
+
Wraps git commit and updates the buildlog journal. Call after making
|
|
681
|
+
changes to record progress.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
message: Commit message (passed as -m to git)
|
|
685
|
+
slug: Entry slug (default: derived from branch name)
|
|
686
|
+
no_entry: Skip buildlog entry update (just git commit)
|
|
687
|
+
extra_args: Additional git commit args (e.g., ["--amend"])
|
|
688
|
+
buildlog_dir: Path to buildlog directory
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
Dict with commit_hash, commit_message, files_changed,
|
|
692
|
+
entry_path, entry_updated, message, error
|
|
693
|
+
"""
|
|
694
|
+
git_args = ["-m", message]
|
|
695
|
+
if extra_args:
|
|
696
|
+
git_args.extend(extra_args)
|
|
697
|
+
|
|
698
|
+
result = commit(
|
|
699
|
+
Path(buildlog_dir),
|
|
700
|
+
git_args=git_args,
|
|
701
|
+
slug=slug,
|
|
702
|
+
no_entry=no_entry,
|
|
703
|
+
)
|
|
704
|
+
return asdict(result)
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def buildlog_gauntlet_prompt(
|
|
708
|
+
target: str,
|
|
709
|
+
personas: list[str] | None = None,
|
|
710
|
+
) -> dict:
|
|
711
|
+
"""Generate a gauntlet review prompt for target code.
|
|
712
|
+
|
|
713
|
+
Creates a prompt combining reviewer persona rules with a target path.
|
|
714
|
+
Use this to kick off a gauntlet review: read the prompt, review the
|
|
715
|
+
target code, then report issues via buildlog_gauntlet_issues.
|
|
716
|
+
|
|
717
|
+
Args:
|
|
718
|
+
target: Path to target code (file or directory, e.g., "src/")
|
|
719
|
+
personas: Persona names to include (default: all)
|
|
720
|
+
|
|
721
|
+
Returns:
|
|
722
|
+
Dict with prompt, target, personas, total_rules, message, error
|
|
723
|
+
"""
|
|
724
|
+
result = generate_gauntlet_prompt(target=target, personas=personas)
|
|
725
|
+
return asdict(result)
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def buildlog_gauntlet_loop(
|
|
729
|
+
target: str,
|
|
730
|
+
personas: list[str] | None = None,
|
|
731
|
+
max_iterations: int = 10,
|
|
732
|
+
stop_at: str = "minors",
|
|
733
|
+
auto_gh_issues: bool = False,
|
|
734
|
+
) -> dict:
|
|
735
|
+
"""Start the gauntlet review loop: get config, rules, and instructions.
|
|
736
|
+
|
|
737
|
+
Returns everything needed to run the review-fix-repeat loop.
|
|
738
|
+
Workflow: call this -> review code -> buildlog_gauntlet_issues ->
|
|
739
|
+
follow action -> buildlog_commit -> repeat.
|
|
740
|
+
|
|
741
|
+
Args:
|
|
742
|
+
target: Path to target code (e.g., "src/", "src/api.py")
|
|
743
|
+
personas: Persona names (default: all)
|
|
744
|
+
max_iterations: Max review-fix iterations (default: 10)
|
|
745
|
+
stop_at: Stop after clearing: "criticals", "majors", or "minors"
|
|
746
|
+
auto_gh_issues: Create GitHub issues for accepted risk items
|
|
747
|
+
|
|
748
|
+
Returns:
|
|
749
|
+
Dict with target, personas, max_iterations, stop_at, rules_by_persona,
|
|
750
|
+
instructions, issue_format, prompt, message, error
|
|
751
|
+
"""
|
|
752
|
+
result = gauntlet_loop_config(
|
|
753
|
+
target=target,
|
|
754
|
+
personas=personas,
|
|
755
|
+
max_iterations=max_iterations,
|
|
756
|
+
stop_at=stop_at,
|
|
757
|
+
auto_gh_issues=auto_gh_issues,
|
|
758
|
+
)
|
|
759
|
+
return asdict(result)
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
# =============================================================================
|
|
763
|
+
# P1: Learning pipeline tools
|
|
764
|
+
# =============================================================================
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def buildlog_distill(
|
|
768
|
+
since: str | None = None,
|
|
769
|
+
category: str | None = None,
|
|
770
|
+
llm: bool = False,
|
|
771
|
+
buildlog_dir: str = "buildlog",
|
|
772
|
+
) -> dict:
|
|
773
|
+
"""Extract patterns from all buildlog entries.
|
|
774
|
+
|
|
775
|
+
Parses the Improvements section of each entry and aggregates
|
|
776
|
+
insights by category with statistics.
|
|
777
|
+
|
|
778
|
+
Args:
|
|
779
|
+
since: Only entries from this date onward (YYYY-MM-DD)
|
|
780
|
+
category: Filter to one category (architectural, workflow,
|
|
781
|
+
tool_usage, domain_knowledge)
|
|
782
|
+
llm: Use LLM for richer extraction
|
|
783
|
+
buildlog_dir: Path to buildlog directory
|
|
784
|
+
|
|
785
|
+
Returns:
|
|
786
|
+
Dict with extracted_at, entry_count, patterns, statistics
|
|
787
|
+
"""
|
|
788
|
+
from datetime import date as date_cls
|
|
789
|
+
|
|
790
|
+
from buildlog.distill import distill_all
|
|
791
|
+
|
|
792
|
+
since_date = None
|
|
793
|
+
if since:
|
|
794
|
+
try:
|
|
795
|
+
since_date = date_cls.fromisoformat(since)
|
|
796
|
+
except ValueError:
|
|
797
|
+
return {
|
|
798
|
+
"extracted_at": "",
|
|
799
|
+
"entry_count": 0,
|
|
800
|
+
"patterns": {},
|
|
801
|
+
"statistics": {
|
|
802
|
+
"total_patterns": 0,
|
|
803
|
+
"by_category": {},
|
|
804
|
+
"by_month": {},
|
|
805
|
+
},
|
|
806
|
+
"error": (f"Invalid date format: {since}. Use YYYY-MM-DD."),
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
valid_categories = (
|
|
810
|
+
"architectural",
|
|
811
|
+
"workflow",
|
|
812
|
+
"tool_usage",
|
|
813
|
+
"domain_knowledge",
|
|
814
|
+
)
|
|
815
|
+
if category and category not in valid_categories:
|
|
816
|
+
return {
|
|
817
|
+
"extracted_at": "",
|
|
818
|
+
"entry_count": 0,
|
|
819
|
+
"patterns": {},
|
|
820
|
+
"statistics": {
|
|
821
|
+
"total_patterns": 0,
|
|
822
|
+
"by_category": {},
|
|
823
|
+
"by_month": {},
|
|
824
|
+
},
|
|
825
|
+
"error": (
|
|
826
|
+
f"Invalid category: {category}."
|
|
827
|
+
f" Must be one of: {', '.join(valid_categories)}"
|
|
828
|
+
),
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
dir_path = Path(buildlog_dir)
|
|
832
|
+
if not dir_path.exists():
|
|
833
|
+
return {
|
|
834
|
+
"extracted_at": "",
|
|
835
|
+
"entry_count": 0,
|
|
836
|
+
"patterns": {},
|
|
837
|
+
"statistics": {
|
|
838
|
+
"total_patterns": 0,
|
|
839
|
+
"by_category": {},
|
|
840
|
+
"by_month": {},
|
|
841
|
+
},
|
|
842
|
+
"error": f"No buildlog directory found at {buildlog_dir}",
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
result = distill_all(dir_path, since=since_date, category_filter=category, llm=llm)
|
|
846
|
+
return dict(result.to_dict())
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def buildlog_skills(
|
|
850
|
+
min_frequency: int = 1,
|
|
851
|
+
since: str | None = None,
|
|
852
|
+
llm: bool = False,
|
|
853
|
+
buildlog_dir: str = "buildlog",
|
|
854
|
+
) -> dict:
|
|
855
|
+
"""Generate agent-consumable skills from buildlog patterns.
|
|
856
|
+
|
|
857
|
+
Transforms distilled patterns into actionable rules with deduplication,
|
|
858
|
+
confidence scoring, and stable IDs. Foundation for promoting rules.
|
|
859
|
+
|
|
860
|
+
Args:
|
|
861
|
+
min_frequency: Only include skills seen at least N times
|
|
862
|
+
since: Only entries from this date onward (YYYY-MM-DD)
|
|
863
|
+
llm: Use LLM for extraction and scoring
|
|
864
|
+
buildlog_dir: Path to buildlog directory
|
|
865
|
+
|
|
866
|
+
Returns:
|
|
867
|
+
Dict with generated_at, source_entries, total_skills, skills
|
|
868
|
+
"""
|
|
869
|
+
from datetime import date as date_cls
|
|
870
|
+
|
|
871
|
+
from buildlog.skills import generate_skills as gen_skills
|
|
872
|
+
|
|
873
|
+
since_date = None
|
|
874
|
+
if since:
|
|
875
|
+
try:
|
|
876
|
+
since_date = date_cls.fromisoformat(since)
|
|
877
|
+
except ValueError:
|
|
878
|
+
return {
|
|
879
|
+
"generated_at": "",
|
|
880
|
+
"source_entries": 0,
|
|
881
|
+
"total_skills": 0,
|
|
882
|
+
"skills": {},
|
|
883
|
+
"error": (f"Invalid date format: {since}. Use YYYY-MM-DD."),
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
dir_path = Path(buildlog_dir)
|
|
887
|
+
if not dir_path.exists():
|
|
888
|
+
return {
|
|
889
|
+
"generated_at": "",
|
|
890
|
+
"source_entries": 0,
|
|
891
|
+
"total_skills": 0,
|
|
892
|
+
"skills": {},
|
|
893
|
+
"error": f"No buildlog directory found at {buildlog_dir}",
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
skill_set = gen_skills(
|
|
897
|
+
dir_path,
|
|
898
|
+
min_frequency=min_frequency,
|
|
899
|
+
since_date=since_date,
|
|
900
|
+
llm=llm,
|
|
901
|
+
)
|
|
902
|
+
return dict(skill_set.to_dict())
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
def buildlog_stats(
|
|
906
|
+
since: str | None = None,
|
|
907
|
+
detailed: bool = False,
|
|
908
|
+
buildlog_dir: str = "buildlog",
|
|
909
|
+
) -> dict:
|
|
910
|
+
"""Show buildlog statistics and analytics.
|
|
911
|
+
|
|
912
|
+
Provides insights on entry counts, improvement coverage,
|
|
913
|
+
categories, streaks, and quality warnings.
|
|
914
|
+
|
|
915
|
+
Args:
|
|
916
|
+
since: Only entries from this date onward (YYYY-MM-DD)
|
|
917
|
+
detailed: Include top sources breakdown
|
|
918
|
+
buildlog_dir: Path to buildlog directory
|
|
919
|
+
|
|
920
|
+
Returns:
|
|
921
|
+
Dict with entries, insights, top_sources, pipeline,
|
|
922
|
+
streak, warnings
|
|
923
|
+
"""
|
|
924
|
+
from datetime import date as date_cls
|
|
925
|
+
|
|
926
|
+
from buildlog.stats import calculate_stats, stats_to_dict
|
|
927
|
+
|
|
928
|
+
since_date = None
|
|
929
|
+
if since:
|
|
930
|
+
try:
|
|
931
|
+
since_date = date_cls.fromisoformat(since)
|
|
932
|
+
except ValueError:
|
|
933
|
+
return {"error": (f"Invalid date format: {since}. Use YYYY-MM-DD.")}
|
|
934
|
+
|
|
935
|
+
dir_path = Path(buildlog_dir)
|
|
936
|
+
if not dir_path.exists():
|
|
937
|
+
return {"error": f"No buildlog directory found at {buildlog_dir}"}
|
|
938
|
+
|
|
939
|
+
stats_result = calculate_stats(dir_path, since_date=since_date)
|
|
940
|
+
result: dict = dict(stats_to_dict(stats_result))
|
|
941
|
+
|
|
942
|
+
if not detailed:
|
|
943
|
+
result["top_sources"] = []
|
|
944
|
+
|
|
945
|
+
return result
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def buildlog_gauntlet_list_personas(
|
|
949
|
+
buildlog_dir: str = "buildlog",
|
|
950
|
+
) -> dict:
|
|
951
|
+
"""List available gauntlet reviewer personas and rule counts.
|
|
952
|
+
|
|
953
|
+
Shows all reviewer personas from seed files. Use to discover
|
|
954
|
+
what review perspectives are available before running a gauntlet.
|
|
955
|
+
|
|
956
|
+
Args:
|
|
957
|
+
buildlog_dir: Path to buildlog directory
|
|
958
|
+
|
|
959
|
+
Returns:
|
|
960
|
+
Dict with personas, total_rules, total_personas
|
|
961
|
+
"""
|
|
962
|
+
from buildlog.seeds import get_default_seeds_dir, load_all_seeds
|
|
963
|
+
|
|
964
|
+
seeds_dir = get_default_seeds_dir()
|
|
965
|
+
|
|
966
|
+
if seeds_dir is None:
|
|
967
|
+
return {
|
|
968
|
+
"personas": {},
|
|
969
|
+
"total_rules": 0,
|
|
970
|
+
"total_personas": 0,
|
|
971
|
+
"error": ("No seed files found." " Check your buildlog installation."),
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
seeds = load_all_seeds(seeds_dir)
|
|
975
|
+
|
|
976
|
+
if not seeds:
|
|
977
|
+
return {
|
|
978
|
+
"personas": {},
|
|
979
|
+
"total_rules": 0,
|
|
980
|
+
"total_personas": 0,
|
|
981
|
+
"error": "No seed files found in seeds directory.",
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
personas_info = {
|
|
985
|
+
name: {
|
|
986
|
+
"rules_count": len(sf.rules),
|
|
987
|
+
"version": sf.version,
|
|
988
|
+
}
|
|
989
|
+
for name, sf in seeds.items()
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
return {
|
|
993
|
+
"personas": personas_info,
|
|
994
|
+
"total_rules": sum(len(sf.rules) for sf in seeds.values()),
|
|
995
|
+
"total_personas": len(seeds),
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
# =============================================================================
|
|
1000
|
+
# P2: Nice-to-have tools
|
|
1001
|
+
# =============================================================================
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
def buildlog_gauntlet_generate(
|
|
1005
|
+
source_text: str,
|
|
1006
|
+
persona: str,
|
|
1007
|
+
output_dir: str | None = None,
|
|
1008
|
+
dry_run: bool = False,
|
|
1009
|
+
buildlog_dir: str = "buildlog",
|
|
1010
|
+
) -> dict:
|
|
1011
|
+
"""Generate seed rules from source text using LLM extraction.
|
|
1012
|
+
|
|
1013
|
+
Runs the seed engine pipeline to produce a YAML seed file
|
|
1014
|
+
from arbitrary source content (docs, notes, standards).
|
|
1015
|
+
|
|
1016
|
+
Args:
|
|
1017
|
+
source_text: The text content to extract rules from
|
|
1018
|
+
persona: Persona name for the seed file
|
|
1019
|
+
output_dir: Output dir for seed YAML (default: .buildlog/seeds)
|
|
1020
|
+
dry_run: Preview without writing to disk
|
|
1021
|
+
buildlog_dir: Path to buildlog directory
|
|
1022
|
+
|
|
1023
|
+
Returns:
|
|
1024
|
+
Dict with persona, rule_count, output_path, preview, message
|
|
1025
|
+
"""
|
|
1026
|
+
out = Path(output_dir) if output_dir else Path(buildlog_dir) / ".buildlog" / "seeds"
|
|
1027
|
+
result = gauntlet_generate(
|
|
1028
|
+
source_text=source_text,
|
|
1029
|
+
persona=persona,
|
|
1030
|
+
output_dir=out,
|
|
1031
|
+
dry_run=dry_run,
|
|
1032
|
+
)
|
|
1033
|
+
return asdict(result)
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def buildlog_init(
|
|
1037
|
+
defaults: bool = True,
|
|
1038
|
+
no_claude_md: bool = False,
|
|
1039
|
+
no_mcp: bool = False,
|
|
1040
|
+
) -> dict:
|
|
1041
|
+
"""Initialize buildlog in the current project directory.
|
|
1042
|
+
|
|
1043
|
+
Sets up buildlog/ with templates, optionally updates CLAUDE.md,
|
|
1044
|
+
and registers the MCP server. Always runs non-interactively.
|
|
1045
|
+
|
|
1046
|
+
Args:
|
|
1047
|
+
defaults: Use default values (always True for MCP)
|
|
1048
|
+
no_claude_md: Don't update CLAUDE.md
|
|
1049
|
+
no_mcp: Don't register MCP server
|
|
1050
|
+
|
|
1051
|
+
Returns:
|
|
1052
|
+
Dict with initialized, buildlog_dir, claude_md_updated,
|
|
1053
|
+
mcp_registered, message, error
|
|
1054
|
+
"""
|
|
1055
|
+
result = init_buildlog(
|
|
1056
|
+
project_dir=Path("."),
|
|
1057
|
+
defaults=True,
|
|
1058
|
+
no_claude_md=no_claude_md,
|
|
1059
|
+
no_mcp=no_mcp,
|
|
1060
|
+
)
|
|
1061
|
+
return asdict(result)
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
def buildlog_update() -> dict:
|
|
1065
|
+
"""Update buildlog templates to the latest version.
|
|
1066
|
+
|
|
1067
|
+
Runs copier update to pull the latest template changes.
|
|
1068
|
+
Requires buildlog to have been initialized first.
|
|
1069
|
+
|
|
1070
|
+
Returns:
|
|
1071
|
+
Dict with updated, message, error
|
|
1072
|
+
"""
|
|
1073
|
+
result = update_buildlog(project_dir=Path("."))
|
|
1074
|
+
return asdict(result)
|
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",
|