buildlog 0.5.0__tar.gz → 0.6.1__tar.gz
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-0.5.0 → buildlog-0.6.1}/PKG-INFO +82 -11
- {buildlog-0.5.0 → buildlog-0.6.1}/README.md +81 -10
- {buildlog-0.5.0 → buildlog-0.6.1}/pyproject.toml +1 -1
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/cli.py +391 -3
- buildlog-0.6.1/src/buildlog/data/seeds/security_karen.yaml +162 -0
- buildlog-0.6.1/src/buildlog/data/seeds/test_terrorist.yaml +280 -0
- buildlog-0.6.1/src/buildlog/seed_engine/__init__.py +74 -0
- buildlog-0.6.1/src/buildlog/seed_engine/categorizers.py +145 -0
- buildlog-0.6.1/src/buildlog/seed_engine/extractors.py +148 -0
- buildlog-0.6.1/src/buildlog/seed_engine/generators.py +144 -0
- buildlog-0.6.1/src/buildlog/seed_engine/models.py +113 -0
- buildlog-0.6.1/src/buildlog/seed_engine/pipeline.py +202 -0
- buildlog-0.6.1/src/buildlog/seed_engine/sources.py +362 -0
- buildlog-0.6.1/src/buildlog/seeds.py +261 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/skills.py +26 -3
- buildlog-0.6.1/template/buildlog/assets/.gitkeep +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/.gitignore +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/LICENSE +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/copier.yml +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/post_gen.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/__init__.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/confidence.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/core/__init__.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/core/operations.py +0 -0
- /buildlog-0.5.0/template/buildlog/.gitkeep → /buildlog-0.6.1/src/buildlog/data/__init__.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/distill.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/embeddings.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/mcp/__init__.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/mcp/server.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/mcp/tools.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/render/__init__.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/render/base.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/render/claude_md.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/render/settings_json.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/render/skill.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/render/tracking.py +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/src/buildlog/stats.py +0 -0
- {buildlog-0.5.0/template/buildlog/assets → buildlog-0.6.1/template/buildlog}/.gitkeep +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/template/buildlog/2026-01-01-example.md +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
- {buildlog-0.5.0 → buildlog-0.6.1}/template/buildlog/_TEMPLATE.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: buildlog
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
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
|
|
@@ -48,10 +48,6 @@ Description-Content-Type: text/markdown
|
|
|
48
48
|
|
|
49
49
|
<div align="center">
|
|
50
50
|
|
|
51
|
-
> **NOTE:** Forgive the agent dump; edit incoming.
|
|
52
|
-
>
|
|
53
|
-
> **RE: The art** — Yes, it's AI-generated. Yes, that's hypocritical for a project about rigor over vibes. Now that this is no longer internal, looking for an actual artist to pay for a real logo. If you know someone good, [open an issue](https://github.com/Peleke/buildlog-template/issues) or DM me. Budget exists.
|
|
54
|
-
|
|
55
51
|
# buildlog
|
|
56
52
|
|
|
57
53
|
### The Only Agent Learning System You Can Prove Works
|
|
@@ -65,7 +61,9 @@ Description-Content-Type: text/markdown
|
|
|
65
61
|
|
|
66
62
|
<img src="assets/hero-banner-perfectdeliberate.png" alt="buildlog - The Only Agent Learning System You Can Prove Works" width="800"/>
|
|
67
63
|
|
|
68
|
-
|
|
64
|
+
> **RE: The art** — Yes, it's AI-generated. Yes, that's hypocritical for a project about rigor over vibes. Looking for an actual artist to pay for a real logo. If you know someone good, [open an issue](https://github.com/Peleke/buildlog-template/issues) or DM me. Budget exists.
|
|
65
|
+
|
|
66
|
+
[The Problem](#the-problem) · [The Claim](#the-claim) · [The Mechanism](#the-mechanism) · [Quick Start](#quick-start) · [Review Gauntlet](#review-gauntlet)
|
|
69
67
|
|
|
70
68
|
---
|
|
71
69
|
|
|
@@ -223,6 +221,58 @@ buildlog_log_reward(
|
|
|
223
221
|
|
|
224
222
|
---
|
|
225
223
|
|
|
224
|
+
## Review Gauntlet
|
|
225
|
+
|
|
226
|
+
Run your code through ruthless reviewer personas, each with curated rules from authoritative sources.
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# See available reviewers
|
|
230
|
+
buildlog gauntlet list
|
|
231
|
+
|
|
232
|
+
# Output:
|
|
233
|
+
# Review Gauntlet Personas
|
|
234
|
+
# ==================================================
|
|
235
|
+
# security_karen
|
|
236
|
+
# OWASP Top 10 security review
|
|
237
|
+
# Rules: 13 (v1)
|
|
238
|
+
#
|
|
239
|
+
# test_terrorist
|
|
240
|
+
# Comprehensive testing coverage audit
|
|
241
|
+
# Rules: 21 (v1)
|
|
242
|
+
#
|
|
243
|
+
# Total: 2 personas, 34 rules
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Reviewer Personas
|
|
247
|
+
|
|
248
|
+
| Persona | Focus | Rules |
|
|
249
|
+
|---------|-------|-------|
|
|
250
|
+
| **Security Karen** | OWASP Top 10, auth, injection, secrets | 13 |
|
|
251
|
+
| **Test Terrorist** | Coverage, property-based, metamorphic, contracts | 21 |
|
|
252
|
+
| **Ruthless Reviewer** | Code quality, FP principles | Coming soon |
|
|
253
|
+
|
|
254
|
+
Each rule includes:
|
|
255
|
+
- **Context**: When to apply it
|
|
256
|
+
- **Antipattern**: What violation looks like
|
|
257
|
+
- **Rationale**: Why it matters (with citations)
|
|
258
|
+
|
|
259
|
+
### Usage
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# Generate a review prompt
|
|
263
|
+
buildlog gauntlet prompt src/api.py
|
|
264
|
+
|
|
265
|
+
# Export rules for manual review
|
|
266
|
+
buildlog gauntlet rules --format markdown -o review_checklist.md
|
|
267
|
+
|
|
268
|
+
# After running a review, persist learnings
|
|
269
|
+
buildlog gauntlet learn review_issues.json --source "PR#42"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The gauntlet integrates with the learning loop—issues found become rules that accumulate confidence.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
226
276
|
## Experiment Infrastructure
|
|
227
277
|
|
|
228
278
|
buildlog ships with infrastructure to run actual experiments:
|
|
@@ -305,14 +355,35 @@ Available tools:
|
|
|
305
355
|
| `buildlog_reject` | Mark false positives |
|
|
306
356
|
| `buildlog_diff` | Rules pending review |
|
|
307
357
|
| `buildlog_learn_from_review` | Extract rules from code review |
|
|
308
|
-
| `buildlog_log_reward` | Record reward signal
|
|
309
|
-
| `
|
|
310
|
-
| `buildlog_start_session` | Begin tracked experiment session |
|
|
311
|
-
| `buildlog_end_session` | End session |
|
|
358
|
+
| `buildlog_log_reward` | Record reward signal |
|
|
359
|
+
| `buildlog_start_session` | Begin tracked experiment |
|
|
312
360
|
| `buildlog_log_mistake` | Record mistake during session |
|
|
313
|
-
| `buildlog_session_metrics` | Get session statistics |
|
|
314
361
|
| `buildlog_experiment_report` | Full experiment report |
|
|
315
362
|
|
|
363
|
+
### CLI Commands
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
buildlog init # Initialize buildlog
|
|
367
|
+
buildlog new <slug> # Create entry
|
|
368
|
+
buildlog list # List entries
|
|
369
|
+
buildlog distill # Extract patterns
|
|
370
|
+
buildlog skills # Generate rules
|
|
371
|
+
buildlog stats # Usage statistics
|
|
372
|
+
buildlog reward <outcome> # Log reward signal
|
|
373
|
+
|
|
374
|
+
# Experiments
|
|
375
|
+
buildlog experiment start # Begin tracked session
|
|
376
|
+
buildlog experiment log-mistake # Record mistake
|
|
377
|
+
buildlog experiment end # End session
|
|
378
|
+
buildlog experiment report # Full report
|
|
379
|
+
|
|
380
|
+
# Review Gauntlet
|
|
381
|
+
buildlog gauntlet list # Show reviewers
|
|
382
|
+
buildlog gauntlet rules # Export rules
|
|
383
|
+
buildlog gauntlet prompt <path> # Generate review prompt
|
|
384
|
+
buildlog gauntlet learn <file> # Persist learnings
|
|
385
|
+
```
|
|
386
|
+
|
|
316
387
|
---
|
|
317
388
|
|
|
318
389
|
## What This Is Not
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
> **NOTE:** Forgive the agent dump; edit incoming.
|
|
4
|
-
>
|
|
5
|
-
> **RE: The art** — Yes, it's AI-generated. Yes, that's hypocritical for a project about rigor over vibes. Now that this is no longer internal, looking for an actual artist to pay for a real logo. If you know someone good, [open an issue](https://github.com/Peleke/buildlog-template/issues) or DM me. Budget exists.
|
|
6
|
-
|
|
7
3
|
# buildlog
|
|
8
4
|
|
|
9
5
|
### The Only Agent Learning System You Can Prove Works
|
|
@@ -17,7 +13,9 @@
|
|
|
17
13
|
|
|
18
14
|
<img src="assets/hero-banner-perfectdeliberate.png" alt="buildlog - The Only Agent Learning System You Can Prove Works" width="800"/>
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
> **RE: The art** — Yes, it's AI-generated. Yes, that's hypocritical for a project about rigor over vibes. Looking for an actual artist to pay for a real logo. If you know someone good, [open an issue](https://github.com/Peleke/buildlog-template/issues) or DM me. Budget exists.
|
|
17
|
+
|
|
18
|
+
[The Problem](#the-problem) · [The Claim](#the-claim) · [The Mechanism](#the-mechanism) · [Quick Start](#quick-start) · [Review Gauntlet](#review-gauntlet)
|
|
21
19
|
|
|
22
20
|
---
|
|
23
21
|
|
|
@@ -175,6 +173,58 @@ buildlog_log_reward(
|
|
|
175
173
|
|
|
176
174
|
---
|
|
177
175
|
|
|
176
|
+
## Review Gauntlet
|
|
177
|
+
|
|
178
|
+
Run your code through ruthless reviewer personas, each with curated rules from authoritative sources.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# See available reviewers
|
|
182
|
+
buildlog gauntlet list
|
|
183
|
+
|
|
184
|
+
# Output:
|
|
185
|
+
# Review Gauntlet Personas
|
|
186
|
+
# ==================================================
|
|
187
|
+
# security_karen
|
|
188
|
+
# OWASP Top 10 security review
|
|
189
|
+
# Rules: 13 (v1)
|
|
190
|
+
#
|
|
191
|
+
# test_terrorist
|
|
192
|
+
# Comprehensive testing coverage audit
|
|
193
|
+
# Rules: 21 (v1)
|
|
194
|
+
#
|
|
195
|
+
# Total: 2 personas, 34 rules
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Reviewer Personas
|
|
199
|
+
|
|
200
|
+
| Persona | Focus | Rules |
|
|
201
|
+
|---------|-------|-------|
|
|
202
|
+
| **Security Karen** | OWASP Top 10, auth, injection, secrets | 13 |
|
|
203
|
+
| **Test Terrorist** | Coverage, property-based, metamorphic, contracts | 21 |
|
|
204
|
+
| **Ruthless Reviewer** | Code quality, FP principles | Coming soon |
|
|
205
|
+
|
|
206
|
+
Each rule includes:
|
|
207
|
+
- **Context**: When to apply it
|
|
208
|
+
- **Antipattern**: What violation looks like
|
|
209
|
+
- **Rationale**: Why it matters (with citations)
|
|
210
|
+
|
|
211
|
+
### Usage
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Generate a review prompt
|
|
215
|
+
buildlog gauntlet prompt src/api.py
|
|
216
|
+
|
|
217
|
+
# Export rules for manual review
|
|
218
|
+
buildlog gauntlet rules --format markdown -o review_checklist.md
|
|
219
|
+
|
|
220
|
+
# After running a review, persist learnings
|
|
221
|
+
buildlog gauntlet learn review_issues.json --source "PR#42"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The gauntlet integrates with the learning loop—issues found become rules that accumulate confidence.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
178
228
|
## Experiment Infrastructure
|
|
179
229
|
|
|
180
230
|
buildlog ships with infrastructure to run actual experiments:
|
|
@@ -257,14 +307,35 @@ Available tools:
|
|
|
257
307
|
| `buildlog_reject` | Mark false positives |
|
|
258
308
|
| `buildlog_diff` | Rules pending review |
|
|
259
309
|
| `buildlog_learn_from_review` | Extract rules from code review |
|
|
260
|
-
| `buildlog_log_reward` | Record reward signal
|
|
261
|
-
| `
|
|
262
|
-
| `buildlog_start_session` | Begin tracked experiment session |
|
|
263
|
-
| `buildlog_end_session` | End session |
|
|
310
|
+
| `buildlog_log_reward` | Record reward signal |
|
|
311
|
+
| `buildlog_start_session` | Begin tracked experiment |
|
|
264
312
|
| `buildlog_log_mistake` | Record mistake during session |
|
|
265
|
-
| `buildlog_session_metrics` | Get session statistics |
|
|
266
313
|
| `buildlog_experiment_report` | Full experiment report |
|
|
267
314
|
|
|
315
|
+
### CLI Commands
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
buildlog init # Initialize buildlog
|
|
319
|
+
buildlog new <slug> # Create entry
|
|
320
|
+
buildlog list # List entries
|
|
321
|
+
buildlog distill # Extract patterns
|
|
322
|
+
buildlog skills # Generate rules
|
|
323
|
+
buildlog stats # Usage statistics
|
|
324
|
+
buildlog reward <outcome> # Log reward signal
|
|
325
|
+
|
|
326
|
+
# Experiments
|
|
327
|
+
buildlog experiment start # Begin tracked session
|
|
328
|
+
buildlog experiment log-mistake # Record mistake
|
|
329
|
+
buildlog experiment end # End session
|
|
330
|
+
buildlog experiment report # Full report
|
|
331
|
+
|
|
332
|
+
# Review Gauntlet
|
|
333
|
+
buildlog gauntlet list # Show reviewers
|
|
334
|
+
buildlog gauntlet rules # Export rules
|
|
335
|
+
buildlog gauntlet prompt <path> # Generate review prompt
|
|
336
|
+
buildlog gauntlet learn <file> # Persist learnings
|
|
337
|
+
```
|
|
338
|
+
|
|
268
339
|
---
|
|
269
340
|
|
|
270
341
|
## What This Is Not
|
|
@@ -172,8 +172,8 @@ def new(slug: str, entry_date: str | None):
|
|
|
172
172
|
click.echo(f"\nOpen it: $EDITOR {entry_path}")
|
|
173
173
|
|
|
174
174
|
|
|
175
|
-
@main.command()
|
|
176
|
-
def
|
|
175
|
+
@main.command("list")
|
|
176
|
+
def list_entries():
|
|
177
177
|
"""List all buildlog entries."""
|
|
178
178
|
buildlog_dir = Path("buildlog")
|
|
179
179
|
|
|
@@ -182,7 +182,8 @@ def list():
|
|
|
182
182
|
raise SystemExit(1)
|
|
183
183
|
|
|
184
184
|
entries = sorted(
|
|
185
|
-
buildlog_dir.glob("20??-??-??-*.md"),
|
|
185
|
+
buildlog_dir.glob("20??-??-??-*.md"),
|
|
186
|
+
reverse=True, # Most recent first
|
|
186
187
|
)
|
|
187
188
|
|
|
188
189
|
if not entries:
|
|
@@ -876,5 +877,392 @@ def experiment_report(output_json: bool):
|
|
|
876
877
|
)
|
|
877
878
|
|
|
878
879
|
|
|
880
|
+
# -----------------------------------------------------------------------------
|
|
881
|
+
# Gauntlet Commands (Review Personas)
|
|
882
|
+
# -----------------------------------------------------------------------------
|
|
883
|
+
|
|
884
|
+
PERSONAS = {
|
|
885
|
+
"security_karen": "OWASP Top 10 security review",
|
|
886
|
+
"test_terrorist": "Comprehensive testing coverage audit",
|
|
887
|
+
"ruthless_reviewer": "Code quality and functional principles",
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
@main.group()
|
|
892
|
+
def gauntlet():
|
|
893
|
+
"""Run the review gauntlet with curated personas.
|
|
894
|
+
|
|
895
|
+
The gauntlet runs your code through multiple ruthless reviewers,
|
|
896
|
+
each with domain-specific rules loaded from seed files.
|
|
897
|
+
|
|
898
|
+
Personas:
|
|
899
|
+
- security_karen: OWASP security review (12 rules)
|
|
900
|
+
- test_terrorist: Testing coverage audit (21 rules)
|
|
901
|
+
- ruthless_reviewer: Code quality review (coming soon)
|
|
902
|
+
|
|
903
|
+
Example workflow:
|
|
904
|
+
|
|
905
|
+
buildlog gauntlet list # See available personas
|
|
906
|
+
buildlog gauntlet rules --persona all # Show all rules
|
|
907
|
+
buildlog gauntlet prompt src/ # Generate review prompt
|
|
908
|
+
"""
|
|
909
|
+
pass
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
@gauntlet.command("list")
|
|
913
|
+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
|
914
|
+
def gauntlet_list(output_json: bool):
|
|
915
|
+
"""List available reviewer personas and their rule counts.
|
|
916
|
+
|
|
917
|
+
Examples:
|
|
918
|
+
|
|
919
|
+
buildlog gauntlet list
|
|
920
|
+
buildlog gauntlet list --json
|
|
921
|
+
"""
|
|
922
|
+
import json as json_module
|
|
923
|
+
|
|
924
|
+
from buildlog.seeds import get_default_seeds_dir, load_all_seeds
|
|
925
|
+
|
|
926
|
+
# Find seeds directory (local overrides > buildlog template > package bundled)
|
|
927
|
+
seeds_dir = get_default_seeds_dir()
|
|
928
|
+
|
|
929
|
+
if seeds_dir is None:
|
|
930
|
+
if output_json:
|
|
931
|
+
click.echo('{"personas": {}, "total_rules": 0, "error": "No seeds found"}')
|
|
932
|
+
else:
|
|
933
|
+
click.echo("No seed files found.")
|
|
934
|
+
click.echo("Seeds are bundled with buildlog - check your installation.")
|
|
935
|
+
return
|
|
936
|
+
|
|
937
|
+
seeds = load_all_seeds(seeds_dir)
|
|
938
|
+
|
|
939
|
+
if output_json:
|
|
940
|
+
data = {
|
|
941
|
+
"personas": {
|
|
942
|
+
name: {
|
|
943
|
+
"description": PERSONAS.get(name, "Custom persona"),
|
|
944
|
+
"rules_count": len(sf.rules),
|
|
945
|
+
"version": sf.version,
|
|
946
|
+
}
|
|
947
|
+
for name, sf in seeds.items()
|
|
948
|
+
},
|
|
949
|
+
"total_rules": sum(len(sf.rules) for sf in seeds.values()),
|
|
950
|
+
}
|
|
951
|
+
click.echo(json_module.dumps(data, indent=2))
|
|
952
|
+
else:
|
|
953
|
+
click.echo("Review Gauntlet Personas")
|
|
954
|
+
click.echo("=" * 50)
|
|
955
|
+
|
|
956
|
+
if not seeds:
|
|
957
|
+
click.echo("\nNo seed files found.")
|
|
958
|
+
click.echo("Initialize with: buildlog init")
|
|
959
|
+
click.echo("Or create seeds in: .buildlog/seeds/")
|
|
960
|
+
return
|
|
961
|
+
|
|
962
|
+
total = 0
|
|
963
|
+
for name, sf in sorted(seeds.items()):
|
|
964
|
+
desc = PERSONAS.get(name, "Custom persona")
|
|
965
|
+
click.echo(f"\n {name}")
|
|
966
|
+
click.echo(f" {desc}")
|
|
967
|
+
click.echo(f" Rules: {len(sf.rules)} (v{sf.version})")
|
|
968
|
+
total += len(sf.rules)
|
|
969
|
+
|
|
970
|
+
click.echo(f"\nTotal: {len(seeds)} personas, {total} rules")
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
@gauntlet.command("rules")
|
|
974
|
+
@click.option(
|
|
975
|
+
"--persona",
|
|
976
|
+
"-p",
|
|
977
|
+
default="all",
|
|
978
|
+
help="Persona to show rules for (or 'all')",
|
|
979
|
+
)
|
|
980
|
+
@click.option(
|
|
981
|
+
"--format",
|
|
982
|
+
"fmt",
|
|
983
|
+
type=click.Choice(["yaml", "json", "markdown"]),
|
|
984
|
+
default="yaml",
|
|
985
|
+
help="Output format",
|
|
986
|
+
)
|
|
987
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file")
|
|
988
|
+
def gauntlet_rules(persona: str, fmt: str, output: str | None):
|
|
989
|
+
"""Show rules for reviewer personas.
|
|
990
|
+
|
|
991
|
+
Use this to see what rules are loaded for each persona,
|
|
992
|
+
or export them for use in prompts.
|
|
993
|
+
|
|
994
|
+
Examples:
|
|
995
|
+
|
|
996
|
+
buildlog gauntlet rules # All rules (YAML)
|
|
997
|
+
buildlog gauntlet rules -p security_karen # Single persona
|
|
998
|
+
buildlog gauntlet rules --format json -o rules.json
|
|
999
|
+
buildlog gauntlet rules --format markdown # For docs
|
|
1000
|
+
"""
|
|
1001
|
+
import json as json_module
|
|
1002
|
+
|
|
1003
|
+
from buildlog.seeds import get_default_seeds_dir, load_all_seeds
|
|
1004
|
+
|
|
1005
|
+
# Find seeds directory (local overrides > buildlog template > package bundled)
|
|
1006
|
+
seeds_dir = get_default_seeds_dir()
|
|
1007
|
+
|
|
1008
|
+
if seeds_dir is None:
|
|
1009
|
+
click.echo("No seed files found.", err=True)
|
|
1010
|
+
click.echo(
|
|
1011
|
+
"Seeds are bundled with buildlog - check your installation.", err=True
|
|
1012
|
+
)
|
|
1013
|
+
raise SystemExit(1)
|
|
1014
|
+
|
|
1015
|
+
seeds = load_all_seeds(seeds_dir)
|
|
1016
|
+
|
|
1017
|
+
if not seeds:
|
|
1018
|
+
click.echo("No seed files found in directory.", err=True)
|
|
1019
|
+
raise SystemExit(1)
|
|
1020
|
+
|
|
1021
|
+
# Filter personas
|
|
1022
|
+
if persona != "all":
|
|
1023
|
+
if persona not in seeds:
|
|
1024
|
+
available = ", ".join(seeds.keys())
|
|
1025
|
+
click.echo(f"Unknown persona: {persona}", err=True)
|
|
1026
|
+
click.echo(f"Available: {available}", err=True)
|
|
1027
|
+
raise SystemExit(1)
|
|
1028
|
+
seeds = {persona: seeds[persona]}
|
|
1029
|
+
|
|
1030
|
+
# Build output data
|
|
1031
|
+
if fmt == "json":
|
|
1032
|
+
data = {}
|
|
1033
|
+
for name, sf in seeds.items():
|
|
1034
|
+
data[name] = {
|
|
1035
|
+
"version": sf.version,
|
|
1036
|
+
"rules": [
|
|
1037
|
+
{
|
|
1038
|
+
"rule": r.rule,
|
|
1039
|
+
"category": r.category,
|
|
1040
|
+
"context": r.context,
|
|
1041
|
+
"antipattern": r.antipattern,
|
|
1042
|
+
"rationale": r.rationale,
|
|
1043
|
+
"tags": r.tags,
|
|
1044
|
+
"references": [
|
|
1045
|
+
{"url": ref.url, "title": ref.title} for ref in r.references
|
|
1046
|
+
],
|
|
1047
|
+
}
|
|
1048
|
+
for r in sf.rules
|
|
1049
|
+
],
|
|
1050
|
+
}
|
|
1051
|
+
formatted = json_module.dumps(data, indent=2)
|
|
1052
|
+
|
|
1053
|
+
elif fmt == "markdown":
|
|
1054
|
+
lines = ["# Review Gauntlet Rules\n"]
|
|
1055
|
+
for name, sf in seeds.items():
|
|
1056
|
+
lines.append(f"## {name.replace('_', ' ').title()}\n")
|
|
1057
|
+
lines.append(f"*{len(sf.rules)} rules, v{sf.version}*\n")
|
|
1058
|
+
for i, r in enumerate(sf.rules, 1):
|
|
1059
|
+
lines.append(f"### {i}. {r.rule}\n")
|
|
1060
|
+
lines.append(f"**Category**: {r.category} ")
|
|
1061
|
+
lines.append(f"**Tags**: {', '.join(r.tags)}\n")
|
|
1062
|
+
if r.context:
|
|
1063
|
+
lines.append(f"**When**: {r.context}\n")
|
|
1064
|
+
if r.antipattern:
|
|
1065
|
+
lines.append(f"**Antipattern**: {r.antipattern}\n")
|
|
1066
|
+
if r.rationale:
|
|
1067
|
+
lines.append(f"**Why**: {r.rationale}\n")
|
|
1068
|
+
if r.references:
|
|
1069
|
+
lines.append("**References**:")
|
|
1070
|
+
for ref in r.references:
|
|
1071
|
+
lines.append(f"- [{ref.title}]({ref.url})")
|
|
1072
|
+
lines.append("")
|
|
1073
|
+
formatted = "\n".join(lines)
|
|
1074
|
+
|
|
1075
|
+
else: # yaml
|
|
1076
|
+
import yaml as yaml_module
|
|
1077
|
+
|
|
1078
|
+
data = {}
|
|
1079
|
+
for name, sf in seeds.items():
|
|
1080
|
+
data[name] = {
|
|
1081
|
+
"version": sf.version,
|
|
1082
|
+
"rules": [
|
|
1083
|
+
{
|
|
1084
|
+
"rule": r.rule,
|
|
1085
|
+
"category": r.category,
|
|
1086
|
+
"context": r.context,
|
|
1087
|
+
"antipattern": r.antipattern,
|
|
1088
|
+
"rationale": r.rationale,
|
|
1089
|
+
"tags": r.tags,
|
|
1090
|
+
}
|
|
1091
|
+
for r in sf.rules
|
|
1092
|
+
],
|
|
1093
|
+
}
|
|
1094
|
+
formatted = yaml_module.dump(data, default_flow_style=False, sort_keys=False)
|
|
1095
|
+
|
|
1096
|
+
# Output
|
|
1097
|
+
if output:
|
|
1098
|
+
output_path = Path(output)
|
|
1099
|
+
output_path.write_text(formatted, encoding="utf-8")
|
|
1100
|
+
total = sum(len(sf.rules) for sf in seeds.values())
|
|
1101
|
+
click.echo(f"Wrote {total} rules to {output_path}")
|
|
1102
|
+
else:
|
|
1103
|
+
click.echo(formatted)
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
@gauntlet.command("prompt")
|
|
1107
|
+
@click.argument("target", type=click.Path(exists=True))
|
|
1108
|
+
@click.option(
|
|
1109
|
+
"--persona",
|
|
1110
|
+
"-p",
|
|
1111
|
+
multiple=True,
|
|
1112
|
+
help="Personas to include (default: all)",
|
|
1113
|
+
)
|
|
1114
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file")
|
|
1115
|
+
def gauntlet_prompt(target: str, persona: tuple[str, ...], output: str | None):
|
|
1116
|
+
"""Generate a review prompt for the gauntlet.
|
|
1117
|
+
|
|
1118
|
+
Creates a prompt with rules and target code that can be
|
|
1119
|
+
used with Claude or another LLM to run a review.
|
|
1120
|
+
|
|
1121
|
+
Examples:
|
|
1122
|
+
|
|
1123
|
+
buildlog gauntlet prompt src/
|
|
1124
|
+
buildlog gauntlet prompt src/api.py -p security_karen
|
|
1125
|
+
buildlog gauntlet prompt . -o review_prompt.md
|
|
1126
|
+
"""
|
|
1127
|
+
from buildlog.seeds import get_default_seeds_dir, load_all_seeds
|
|
1128
|
+
|
|
1129
|
+
# Find seeds directory (local overrides > buildlog template > package bundled)
|
|
1130
|
+
seeds_dir = get_default_seeds_dir()
|
|
1131
|
+
|
|
1132
|
+
if seeds_dir is None:
|
|
1133
|
+
click.echo("No seed files found.", err=True)
|
|
1134
|
+
click.echo(
|
|
1135
|
+
"Seeds are bundled with buildlog - check your installation.", err=True
|
|
1136
|
+
)
|
|
1137
|
+
raise SystemExit(1)
|
|
1138
|
+
|
|
1139
|
+
seeds = load_all_seeds(seeds_dir)
|
|
1140
|
+
|
|
1141
|
+
if not seeds:
|
|
1142
|
+
click.echo("No seed files found in directory.", err=True)
|
|
1143
|
+
raise SystemExit(1)
|
|
1144
|
+
|
|
1145
|
+
# Filter personas
|
|
1146
|
+
if persona:
|
|
1147
|
+
seeds = {k: v for k, v in seeds.items() if k in persona}
|
|
1148
|
+
if not seeds:
|
|
1149
|
+
click.echo(f"No matching personas: {', '.join(persona)}", err=True)
|
|
1150
|
+
raise SystemExit(1)
|
|
1151
|
+
|
|
1152
|
+
# Build the prompt
|
|
1153
|
+
target_path = Path(target)
|
|
1154
|
+
lines = [
|
|
1155
|
+
"# Review Gauntlet Prompt\n",
|
|
1156
|
+
"You are running the Review Gauntlet. Apply these rules ruthlessly.\n",
|
|
1157
|
+
"## Target\n",
|
|
1158
|
+
f"Review: `{target_path}`\n",
|
|
1159
|
+
"## Reviewers and Rules\n",
|
|
1160
|
+
]
|
|
1161
|
+
|
|
1162
|
+
for name, sf in seeds.items():
|
|
1163
|
+
persona_name = name.replace("_", " ").title()
|
|
1164
|
+
lines.append(f"### {persona_name}\n")
|
|
1165
|
+
for r in sf.rules:
|
|
1166
|
+
lines.append(f"- **{r.rule}**")
|
|
1167
|
+
if r.antipattern:
|
|
1168
|
+
lines.append(f" - Antipattern: {r.antipattern}")
|
|
1169
|
+
lines.append("")
|
|
1170
|
+
|
|
1171
|
+
lines.extend(
|
|
1172
|
+
[
|
|
1173
|
+
"## Output Format\n",
|
|
1174
|
+
"For each issue found, output:\n",
|
|
1175
|
+
"```json",
|
|
1176
|
+
"{",
|
|
1177
|
+
' "reviewer": "<persona>",',
|
|
1178
|
+
' "severity": "critical|major|minor|nitpick",',
|
|
1179
|
+
' "category": "<category>",',
|
|
1180
|
+
' "location": "<file:line>",',
|
|
1181
|
+
' "description": "<what is wrong>",',
|
|
1182
|
+
' "rule_learned": "<generalizable rule>"',
|
|
1183
|
+
"}",
|
|
1184
|
+
"```\n",
|
|
1185
|
+
"## Instructions\n",
|
|
1186
|
+
"1. Read the target code thoroughly",
|
|
1187
|
+
"2. Apply each rule from each reviewer",
|
|
1188
|
+
"3. Report ALL violations found",
|
|
1189
|
+
"4. Be ruthless - this is the gauntlet",
|
|
1190
|
+
"",
|
|
1191
|
+
]
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
formatted = "\n".join(lines)
|
|
1195
|
+
|
|
1196
|
+
if output:
|
|
1197
|
+
output_path = Path(output)
|
|
1198
|
+
output_path.write_text(formatted, encoding="utf-8")
|
|
1199
|
+
click.echo(f"Wrote prompt to {output_path}")
|
|
1200
|
+
else:
|
|
1201
|
+
click.echo(formatted)
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
@gauntlet.command("learn")
|
|
1205
|
+
@click.argument("issues_file", type=click.Path(exists=True))
|
|
1206
|
+
@click.option("--source", "-s", help="Source identifier (e.g., 'gauntlet:PR#42')")
|
|
1207
|
+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
|
1208
|
+
def gauntlet_learn(issues_file: str, source: str | None, output_json: bool):
|
|
1209
|
+
"""Persist learnings from a gauntlet review.
|
|
1210
|
+
|
|
1211
|
+
Takes a JSON file of issues (in the gauntlet output format)
|
|
1212
|
+
and calls learn_from_review to persist them.
|
|
1213
|
+
|
|
1214
|
+
Examples:
|
|
1215
|
+
|
|
1216
|
+
buildlog gauntlet learn review_issues.json
|
|
1217
|
+
buildlog gauntlet learn issues.json --source "gauntlet:2026-01-22"
|
|
1218
|
+
"""
|
|
1219
|
+
import json as json_module
|
|
1220
|
+
from dataclasses import asdict
|
|
1221
|
+
|
|
1222
|
+
from buildlog.core import learn_from_review
|
|
1223
|
+
|
|
1224
|
+
buildlog_dir = Path("buildlog")
|
|
1225
|
+
|
|
1226
|
+
if not buildlog_dir.exists():
|
|
1227
|
+
click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
|
|
1228
|
+
raise SystemExit(1)
|
|
1229
|
+
|
|
1230
|
+
# Load issues
|
|
1231
|
+
try:
|
|
1232
|
+
with open(issues_file) as f:
|
|
1233
|
+
data = json_module.load(f)
|
|
1234
|
+
except json_module.JSONDecodeError as e:
|
|
1235
|
+
click.echo(f"Invalid JSON: {e}", err=True)
|
|
1236
|
+
raise SystemExit(1)
|
|
1237
|
+
|
|
1238
|
+
# Handle different formats
|
|
1239
|
+
if isinstance(data, list):
|
|
1240
|
+
issues = data
|
|
1241
|
+
elif isinstance(data, dict) and "all_issues" in data:
|
|
1242
|
+
issues = data["all_issues"]
|
|
1243
|
+
elif isinstance(data, dict) and "issues" in data:
|
|
1244
|
+
issues = data["issues"]
|
|
1245
|
+
else:
|
|
1246
|
+
click.echo(
|
|
1247
|
+
"Expected list of issues or dict with 'issues'/'all_issues'", err=True
|
|
1248
|
+
)
|
|
1249
|
+
raise SystemExit(1)
|
|
1250
|
+
|
|
1251
|
+
if not issues:
|
|
1252
|
+
click.echo("No issues found in file.", err=True)
|
|
1253
|
+
raise SystemExit(1)
|
|
1254
|
+
|
|
1255
|
+
# Learn from review
|
|
1256
|
+
result = learn_from_review(buildlog_dir, issues, source=source or "gauntlet")
|
|
1257
|
+
|
|
1258
|
+
if output_json:
|
|
1259
|
+
click.echo(json_module.dumps(asdict(result), indent=2))
|
|
1260
|
+
else:
|
|
1261
|
+
click.echo(f"✓ {result.message}")
|
|
1262
|
+
click.echo(f" New learnings: {result.new_learnings}")
|
|
1263
|
+
click.echo(f" Reinforced: {result.reinforced_learnings}")
|
|
1264
|
+
click.echo(f" Total processed: {result.total_issues_processed}")
|
|
1265
|
+
|
|
1266
|
+
|
|
879
1267
|
if __name__ == "__main__":
|
|
880
1268
|
main()
|