buildlog 0.1.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/__init__.py +3 -0
- buildlog/cli.py +437 -0
- buildlog/core/__init__.py +25 -0
- buildlog/core/operations.py +392 -0
- buildlog/distill.py +374 -0
- buildlog/embeddings.py +392 -0
- buildlog/mcp/__init__.py +15 -0
- buildlog/mcp/server.py +29 -0
- buildlog/mcp/tools.py +97 -0
- buildlog/render/__init__.py +41 -0
- buildlog/render/base.py +23 -0
- buildlog/render/claude_md.py +106 -0
- buildlog/render/settings_json.py +96 -0
- buildlog/skills.py +630 -0
- buildlog/stats.py +469 -0
- buildlog-0.1.0.data/data/share/buildlog/copier.yml +35 -0
- buildlog-0.1.0.data/data/share/buildlog/post_gen.py +51 -0
- buildlog-0.1.0.data/data/share/buildlog/template/buildlog/.gitkeep +0 -0
- buildlog-0.1.0.data/data/share/buildlog/template/buildlog/2026-01-01-example.md +269 -0
- buildlog-0.1.0.data/data/share/buildlog/template/buildlog/BUILDLOG_SYSTEM.md +114 -0
- buildlog-0.1.0.data/data/share/buildlog/template/buildlog/_TEMPLATE.md +162 -0
- buildlog-0.1.0.data/data/share/buildlog/template/buildlog/assets/.gitkeep +0 -0
- buildlog-0.1.0.dist-info/METADATA +664 -0
- buildlog-0.1.0.dist-info/RECORD +27 -0
- buildlog-0.1.0.dist-info/WHEEL +4 -0
- buildlog-0.1.0.dist-info/entry_points.txt +3 -0
- buildlog-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Render skills to CLAUDE.md."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from buildlog.skills import _to_imperative
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from buildlog.skills import Skill
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ClaudeMdRenderer:
|
|
17
|
+
"""Appends promoted skills to CLAUDE.md."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, path: Path | None = None, tracking_path: Path | None = None):
|
|
20
|
+
"""Initialize renderer.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
path: Path to CLAUDE.md file. Defaults to CLAUDE.md in current directory.
|
|
24
|
+
tracking_path: Path to promoted.json tracking file.
|
|
25
|
+
Defaults to .buildlog/promoted.json relative to path.
|
|
26
|
+
"""
|
|
27
|
+
self.path = path or Path("CLAUDE.md")
|
|
28
|
+
if tracking_path is None:
|
|
29
|
+
self.tracking_path = self.path.parent / ".buildlog" / "promoted.json"
|
|
30
|
+
else:
|
|
31
|
+
self.tracking_path = tracking_path
|
|
32
|
+
|
|
33
|
+
def render(self, skills: list[Skill]) -> str:
|
|
34
|
+
"""Append skills to CLAUDE.md.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
skills: List of skills to append.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Confirmation message.
|
|
41
|
+
"""
|
|
42
|
+
if not skills:
|
|
43
|
+
return "No skills to promote"
|
|
44
|
+
|
|
45
|
+
# Group by category
|
|
46
|
+
by_category: dict[str, list[Skill]] = {}
|
|
47
|
+
for skill in skills:
|
|
48
|
+
by_category.setdefault(skill.category, []).append(skill)
|
|
49
|
+
|
|
50
|
+
# Build section
|
|
51
|
+
lines = [
|
|
52
|
+
"",
|
|
53
|
+
f"## Learned Rules (auto-generated {datetime.now().strftime('%Y-%m-%d')})",
|
|
54
|
+
"",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
category_titles = {
|
|
58
|
+
"architectural": "Architectural",
|
|
59
|
+
"workflow": "Workflow",
|
|
60
|
+
"tool_usage": "Tool Usage",
|
|
61
|
+
"domain_knowledge": "Domain Knowledge",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for category, cat_skills in by_category.items():
|
|
65
|
+
title = category_titles.get(category, category.replace("_", " ").title())
|
|
66
|
+
lines.append(f"### {title}")
|
|
67
|
+
lines.append("")
|
|
68
|
+
for skill in cat_skills:
|
|
69
|
+
rule = _to_imperative(skill.rule, skill.confidence)
|
|
70
|
+
lines.append(f"- {rule}")
|
|
71
|
+
lines.append("")
|
|
72
|
+
|
|
73
|
+
content = "\n".join(lines)
|
|
74
|
+
|
|
75
|
+
# Append to file
|
|
76
|
+
if self.path.exists():
|
|
77
|
+
existing = self.path.read_text()
|
|
78
|
+
self.path.write_text(existing + content)
|
|
79
|
+
else:
|
|
80
|
+
self.path.write_text(content)
|
|
81
|
+
|
|
82
|
+
# Track promoted skill IDs
|
|
83
|
+
self._track_promoted(skills)
|
|
84
|
+
|
|
85
|
+
return f"Appended {len(skills)} rules to {self.path}"
|
|
86
|
+
|
|
87
|
+
def _track_promoted(self, skills: list[Skill]) -> None:
|
|
88
|
+
"""Track which skills have been promoted."""
|
|
89
|
+
self.tracking_path.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
|
|
91
|
+
# Load existing tracking data (handle corrupt JSON)
|
|
92
|
+
tracking = {"skill_ids": [], "promoted_at": {}}
|
|
93
|
+
if self.tracking_path.exists():
|
|
94
|
+
try:
|
|
95
|
+
tracking = json.loads(self.tracking_path.read_text())
|
|
96
|
+
except json.JSONDecodeError:
|
|
97
|
+
pass # Start fresh if corrupted
|
|
98
|
+
|
|
99
|
+
# Add new skill IDs
|
|
100
|
+
now = datetime.now().isoformat()
|
|
101
|
+
for skill in skills:
|
|
102
|
+
if skill.id not in tracking["skill_ids"]:
|
|
103
|
+
tracking["skill_ids"].append(skill.id)
|
|
104
|
+
tracking["promoted_at"][skill.id] = now
|
|
105
|
+
|
|
106
|
+
self.tracking_path.write_text(json.dumps(tracking, indent=2))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Render skills to .claude/settings.json."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from buildlog.skills import _to_imperative
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from buildlog.skills import Skill
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SettingsJsonRenderer:
|
|
17
|
+
"""Merges promoted skills into .claude/settings.json."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, path: Path | None = None, tracking_path: Path | None = None):
|
|
20
|
+
"""Initialize renderer.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
path: Path to settings.json file. Defaults to .claude/settings.json.
|
|
24
|
+
tracking_path: Path to promoted.json tracking file.
|
|
25
|
+
Defaults to .buildlog/promoted.json.
|
|
26
|
+
"""
|
|
27
|
+
self.path = path or Path(".claude/settings.json")
|
|
28
|
+
self.tracking_path = tracking_path or Path(".buildlog/promoted.json")
|
|
29
|
+
|
|
30
|
+
def render(self, skills: list[Skill]) -> str:
|
|
31
|
+
"""Merge skills into settings.json rules array.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
skills: List of skills to add.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Confirmation message.
|
|
38
|
+
"""
|
|
39
|
+
if not skills:
|
|
40
|
+
return "No skills to promote"
|
|
41
|
+
|
|
42
|
+
# Load existing settings
|
|
43
|
+
if self.path.exists():
|
|
44
|
+
settings = json.loads(self.path.read_text())
|
|
45
|
+
else:
|
|
46
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
settings = {}
|
|
48
|
+
|
|
49
|
+
# Get or create rules array
|
|
50
|
+
rules: list[str] = settings.setdefault("rules", [])
|
|
51
|
+
|
|
52
|
+
# Add new rules (converted to imperative form)
|
|
53
|
+
added = 0
|
|
54
|
+
for skill in skills:
|
|
55
|
+
rule = _to_imperative(skill.rule, skill.confidence)
|
|
56
|
+
if rule not in rules:
|
|
57
|
+
rules.append(rule)
|
|
58
|
+
added += 1
|
|
59
|
+
|
|
60
|
+
# Update buildlog metadata (accumulate, don't replace)
|
|
61
|
+
buildlog_meta = settings.get("_buildlog", {"promoted_skill_ids": []})
|
|
62
|
+
existing_ids = set(buildlog_meta.get("promoted_skill_ids", []))
|
|
63
|
+
new_ids = existing_ids | {s.id for s in skills}
|
|
64
|
+
settings["_buildlog"] = {
|
|
65
|
+
"last_updated": datetime.now().isoformat(),
|
|
66
|
+
"promoted_skill_ids": sorted(new_ids),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Write back
|
|
70
|
+
self.path.write_text(json.dumps(settings, indent=2))
|
|
71
|
+
|
|
72
|
+
# Track promoted skill IDs
|
|
73
|
+
self._track_promoted(skills)
|
|
74
|
+
|
|
75
|
+
return f"Added {added} rules to {self.path} ({len(skills) - added} duplicates skipped)"
|
|
76
|
+
|
|
77
|
+
def _track_promoted(self, skills: list[Skill]) -> None:
|
|
78
|
+
"""Track which skills have been promoted."""
|
|
79
|
+
self.tracking_path.parent.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
|
|
81
|
+
# Load existing tracking data (handle corrupt JSON)
|
|
82
|
+
tracking = {"skill_ids": [], "promoted_at": {}}
|
|
83
|
+
if self.tracking_path.exists():
|
|
84
|
+
try:
|
|
85
|
+
tracking = json.loads(self.tracking_path.read_text())
|
|
86
|
+
except json.JSONDecodeError:
|
|
87
|
+
pass # Start fresh if corrupted
|
|
88
|
+
|
|
89
|
+
# Add new skill IDs
|
|
90
|
+
now = datetime.now().isoformat()
|
|
91
|
+
for skill in skills:
|
|
92
|
+
if skill.id not in tracking["skill_ids"]:
|
|
93
|
+
tracking["skill_ids"].append(skill.id)
|
|
94
|
+
tracking["promoted_at"][skill.id] = now
|
|
95
|
+
|
|
96
|
+
self.tracking_path.write_text(json.dumps(tracking, indent=2))
|