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.
Files changed (28) hide show
  1. buildlog/cli.py +491 -30
  2. buildlog/constants.py +121 -0
  3. buildlog/core/__init__.py +44 -0
  4. buildlog/core/operations.py +1189 -13
  5. buildlog/data/seeds/bragi.yaml +61 -0
  6. buildlog/llm.py +51 -4
  7. buildlog/mcp/__init__.py +51 -3
  8. buildlog/mcp/server.py +40 -0
  9. buildlog/mcp/tools.py +526 -12
  10. buildlog/seed_engine/__init__.py +2 -0
  11. buildlog/seed_engine/llm_extractor.py +121 -0
  12. buildlog/seed_engine/pipeline.py +45 -1
  13. {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/post_gen.py +10 -5
  14. buildlog-0.10.0.data/data/share/buildlog/template/buildlog/.gitkeep +0 -0
  15. buildlog-0.10.0.data/data/share/buildlog/template/buildlog/assets/.gitkeep +0 -0
  16. buildlog-0.10.0.dist-info/METADATA +248 -0
  17. {buildlog-0.8.0.dist-info → buildlog-0.10.0.dist-info}/RECORD +27 -22
  18. buildlog-0.8.0.dist-info/METADATA +0 -151
  19. {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/copier.yml +0 -0
  20. {buildlog-0.8.0.data/data/share/buildlog/template/buildlog → buildlog-0.10.0.data/data/share/buildlog/template/buildlog/.buildlog}/.gitkeep +0 -0
  21. {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
  22. {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/2026-01-01-example.md +0 -0
  23. {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
  24. {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE.md +0 -0
  25. {buildlog-0.8.0.data → buildlog-0.10.0.data}/data/share/buildlog/template/buildlog/_TEMPLATE_QUICK.md +0 -0
  26. {buildlog-0.8.0.dist-info → buildlog-0.10.0.dist-info}/WHEEL +0 -0
  27. {buildlog-0.8.0.dist-info → buildlog-0.10.0.dist-info}/entry_points.txt +0 -0
  28. {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 rules.
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 that haven't been promoted or rejected yet.
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
- """Capture learnings from code review feedback.
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 a reward signal for bandit learning.
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 new experiment session with Thompson Sampling rule selection.
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 experiment session.
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 metrics for a session or all sessions.
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 a comprehensive experiment report.
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 status and rule rankings.
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 review issues and determine next action.
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 creating GitHub issues.
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)
@@ -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",