buildlog 0.5.0__tar.gz → 0.6.0__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.
Files changed (38) hide show
  1. {buildlog-0.5.0 → buildlog-0.6.0}/PKG-INFO +82 -11
  2. {buildlog-0.5.0 → buildlog-0.6.0}/README.md +81 -10
  3. {buildlog-0.5.0 → buildlog-0.6.0}/pyproject.toml +1 -1
  4. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/cli.py +379 -3
  5. buildlog-0.6.0/src/buildlog/seed_engine/__init__.py +74 -0
  6. buildlog-0.6.0/src/buildlog/seed_engine/categorizers.py +145 -0
  7. buildlog-0.6.0/src/buildlog/seed_engine/extractors.py +148 -0
  8. buildlog-0.6.0/src/buildlog/seed_engine/generators.py +144 -0
  9. buildlog-0.6.0/src/buildlog/seed_engine/models.py +113 -0
  10. buildlog-0.6.0/src/buildlog/seed_engine/pipeline.py +202 -0
  11. buildlog-0.6.0/src/buildlog/seed_engine/sources.py +362 -0
  12. buildlog-0.6.0/src/buildlog/seeds.py +211 -0
  13. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/skills.py +26 -3
  14. {buildlog-0.5.0 → buildlog-0.6.0}/.gitignore +0 -0
  15. {buildlog-0.5.0 → buildlog-0.6.0}/LICENSE +0 -0
  16. {buildlog-0.5.0 → buildlog-0.6.0}/copier.yml +0 -0
  17. {buildlog-0.5.0 → buildlog-0.6.0}/post_gen.py +0 -0
  18. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/__init__.py +0 -0
  19. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/confidence.py +0 -0
  20. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/core/__init__.py +0 -0
  21. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/core/operations.py +0 -0
  22. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/distill.py +0 -0
  23. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/embeddings.py +0 -0
  24. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/mcp/__init__.py +0 -0
  25. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/mcp/server.py +0 -0
  26. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/mcp/tools.py +0 -0
  27. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/render/__init__.py +0 -0
  28. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/render/base.py +0 -0
  29. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/render/claude_md.py +0 -0
  30. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/render/settings_json.py +0 -0
  31. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/render/skill.py +0 -0
  32. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/render/tracking.py +0 -0
  33. {buildlog-0.5.0 → buildlog-0.6.0}/src/buildlog/stats.py +0 -0
  34. {buildlog-0.5.0 → buildlog-0.6.0}/template/buildlog/.gitkeep +0 -0
  35. {buildlog-0.5.0 → buildlog-0.6.0}/template/buildlog/2026-01-01-example.md +0 -0
  36. {buildlog-0.5.0 → buildlog-0.6.0}/template/buildlog/BUILDLOG_SYSTEM.md +0 -0
  37. {buildlog-0.5.0 → buildlog-0.6.0}/template/buildlog/_TEMPLATE.md +0 -0
  38. {buildlog-0.5.0 → buildlog-0.6.0}/template/buildlog/assets/.gitkeep +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: buildlog
3
- Version: 0.5.0
3
+ Version: 0.6.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
@@ -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
- [The Problem](#the-problem) · [The Claim](#the-claim) · [The Mechanism](#the-mechanism) · [Quick Start](#quick-start)
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 for bandit |
309
- | `buildlog_rewards` | View reward history |
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
- [The Problem](#the-problem) · [The Claim](#the-claim) · [The Mechanism](#the-mechanism) · [Quick Start](#quick-start)
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 for bandit |
261
- | `buildlog_rewards` | View reward history |
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "buildlog"
7
- version = "0.5.0"
7
+ version = "0.6.0"
8
8
  description = "Engineering notebook for AI-assisted development"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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 list():
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"), reverse=True # Most recent first
185
+ buildlog_dir.glob("20??-??-??-*.md"),
186
+ reverse=True, # Most recent first
186
187
  )
187
188
 
188
189
  if not entries:
@@ -876,5 +877,380 @@ 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 load_all_seeds
925
+
926
+ # Find seeds directory
927
+ buildlog_dir = Path("buildlog")
928
+ seeds_dir = buildlog_dir / ".buildlog" / "seeds"
929
+
930
+ # Also check .buildlog at repo root (common for installed templates)
931
+ if not seeds_dir.exists():
932
+ seeds_dir = Path(".buildlog") / "seeds"
933
+
934
+ seeds = load_all_seeds(seeds_dir)
935
+
936
+ if output_json:
937
+ data = {
938
+ "personas": {
939
+ name: {
940
+ "description": PERSONAS.get(name, "Custom persona"),
941
+ "rules_count": len(sf.rules),
942
+ "version": sf.version,
943
+ }
944
+ for name, sf in seeds.items()
945
+ },
946
+ "total_rules": sum(len(sf.rules) for sf in seeds.values()),
947
+ }
948
+ click.echo(json_module.dumps(data, indent=2))
949
+ else:
950
+ click.echo("Review Gauntlet Personas")
951
+ click.echo("=" * 50)
952
+
953
+ if not seeds:
954
+ click.echo("\nNo seed files found.")
955
+ click.echo("Initialize with: buildlog init")
956
+ click.echo("Or create seeds in: .buildlog/seeds/")
957
+ return
958
+
959
+ total = 0
960
+ for name, sf in sorted(seeds.items()):
961
+ desc = PERSONAS.get(name, "Custom persona")
962
+ click.echo(f"\n {name}")
963
+ click.echo(f" {desc}")
964
+ click.echo(f" Rules: {len(sf.rules)} (v{sf.version})")
965
+ total += len(sf.rules)
966
+
967
+ click.echo(f"\nTotal: {len(seeds)} personas, {total} rules")
968
+
969
+
970
+ @gauntlet.command("rules")
971
+ @click.option(
972
+ "--persona",
973
+ "-p",
974
+ default="all",
975
+ help="Persona to show rules for (or 'all')",
976
+ )
977
+ @click.option(
978
+ "--format",
979
+ "fmt",
980
+ type=click.Choice(["yaml", "json", "markdown"]),
981
+ default="yaml",
982
+ help="Output format",
983
+ )
984
+ @click.option("--output", "-o", type=click.Path(), help="Output file")
985
+ def gauntlet_rules(persona: str, fmt: str, output: str | None):
986
+ """Show rules for reviewer personas.
987
+
988
+ Use this to see what rules are loaded for each persona,
989
+ or export them for use in prompts.
990
+
991
+ Examples:
992
+
993
+ buildlog gauntlet rules # All rules (YAML)
994
+ buildlog gauntlet rules -p security_karen # Single persona
995
+ buildlog gauntlet rules --format json -o rules.json
996
+ buildlog gauntlet rules --format markdown # For docs
997
+ """
998
+ import json as json_module
999
+
1000
+ from buildlog.seeds import load_all_seeds
1001
+
1002
+ # Find seeds directory
1003
+ seeds_dir = Path(".buildlog") / "seeds"
1004
+ if not seeds_dir.exists():
1005
+ seeds_dir = Path("buildlog") / ".buildlog" / "seeds"
1006
+
1007
+ seeds = load_all_seeds(seeds_dir)
1008
+
1009
+ if not seeds:
1010
+ click.echo("No seed files found.", err=True)
1011
+ click.echo("Initialize with: buildlog init", err=True)
1012
+ raise SystemExit(1)
1013
+
1014
+ # Filter personas
1015
+ if persona != "all":
1016
+ if persona not in seeds:
1017
+ available = ", ".join(seeds.keys())
1018
+ click.echo(f"Unknown persona: {persona}", err=True)
1019
+ click.echo(f"Available: {available}", err=True)
1020
+ raise SystemExit(1)
1021
+ seeds = {persona: seeds[persona]}
1022
+
1023
+ # Build output data
1024
+ if fmt == "json":
1025
+ data = {}
1026
+ for name, sf in seeds.items():
1027
+ data[name] = {
1028
+ "version": sf.version,
1029
+ "rules": [
1030
+ {
1031
+ "rule": r.rule,
1032
+ "category": r.category,
1033
+ "context": r.context,
1034
+ "antipattern": r.antipattern,
1035
+ "rationale": r.rationale,
1036
+ "tags": r.tags,
1037
+ "references": [
1038
+ {"url": ref.url, "title": ref.title} for ref in r.references
1039
+ ],
1040
+ }
1041
+ for r in sf.rules
1042
+ ],
1043
+ }
1044
+ formatted = json_module.dumps(data, indent=2)
1045
+
1046
+ elif fmt == "markdown":
1047
+ lines = ["# Review Gauntlet Rules\n"]
1048
+ for name, sf in seeds.items():
1049
+ lines.append(f"## {name.replace('_', ' ').title()}\n")
1050
+ lines.append(f"*{len(sf.rules)} rules, v{sf.version}*\n")
1051
+ for i, r in enumerate(sf.rules, 1):
1052
+ lines.append(f"### {i}. {r.rule}\n")
1053
+ lines.append(f"**Category**: {r.category} ")
1054
+ lines.append(f"**Tags**: {', '.join(r.tags)}\n")
1055
+ if r.context:
1056
+ lines.append(f"**When**: {r.context}\n")
1057
+ if r.antipattern:
1058
+ lines.append(f"**Antipattern**: {r.antipattern}\n")
1059
+ if r.rationale:
1060
+ lines.append(f"**Why**: {r.rationale}\n")
1061
+ if r.references:
1062
+ lines.append("**References**:")
1063
+ for ref in r.references:
1064
+ lines.append(f"- [{ref.title}]({ref.url})")
1065
+ lines.append("")
1066
+ formatted = "\n".join(lines)
1067
+
1068
+ else: # yaml
1069
+ import yaml as yaml_module
1070
+
1071
+ data = {}
1072
+ for name, sf in seeds.items():
1073
+ data[name] = {
1074
+ "version": sf.version,
1075
+ "rules": [
1076
+ {
1077
+ "rule": r.rule,
1078
+ "category": r.category,
1079
+ "context": r.context,
1080
+ "antipattern": r.antipattern,
1081
+ "rationale": r.rationale,
1082
+ "tags": r.tags,
1083
+ }
1084
+ for r in sf.rules
1085
+ ],
1086
+ }
1087
+ formatted = yaml_module.dump(data, default_flow_style=False, sort_keys=False)
1088
+
1089
+ # Output
1090
+ if output:
1091
+ output_path = Path(output)
1092
+ output_path.write_text(formatted, encoding="utf-8")
1093
+ total = sum(len(sf.rules) for sf in seeds.values())
1094
+ click.echo(f"Wrote {total} rules to {output_path}")
1095
+ else:
1096
+ click.echo(formatted)
1097
+
1098
+
1099
+ @gauntlet.command("prompt")
1100
+ @click.argument("target", type=click.Path(exists=True))
1101
+ @click.option(
1102
+ "--persona",
1103
+ "-p",
1104
+ multiple=True,
1105
+ help="Personas to include (default: all)",
1106
+ )
1107
+ @click.option("--output", "-o", type=click.Path(), help="Output file")
1108
+ def gauntlet_prompt(target: str, persona: tuple[str, ...], output: str | None):
1109
+ """Generate a review prompt for the gauntlet.
1110
+
1111
+ Creates a prompt with rules and target code that can be
1112
+ used with Claude or another LLM to run a review.
1113
+
1114
+ Examples:
1115
+
1116
+ buildlog gauntlet prompt src/
1117
+ buildlog gauntlet prompt src/api.py -p security_karen
1118
+ buildlog gauntlet prompt . -o review_prompt.md
1119
+ """
1120
+ from buildlog.seeds import load_all_seeds
1121
+
1122
+ # Find seeds directory
1123
+ seeds_dir = Path(".buildlog") / "seeds"
1124
+ if not seeds_dir.exists():
1125
+ seeds_dir = Path("buildlog") / ".buildlog" / "seeds"
1126
+
1127
+ seeds = load_all_seeds(seeds_dir)
1128
+
1129
+ if not seeds:
1130
+ click.echo("No seed files found.", err=True)
1131
+ raise SystemExit(1)
1132
+
1133
+ # Filter personas
1134
+ if persona:
1135
+ seeds = {k: v for k, v in seeds.items() if k in persona}
1136
+ if not seeds:
1137
+ click.echo(f"No matching personas: {', '.join(persona)}", err=True)
1138
+ raise SystemExit(1)
1139
+
1140
+ # Build the prompt
1141
+ target_path = Path(target)
1142
+ lines = [
1143
+ "# Review Gauntlet Prompt\n",
1144
+ "You are running the Review Gauntlet. Apply these rules ruthlessly.\n",
1145
+ "## Target\n",
1146
+ f"Review: `{target_path}`\n",
1147
+ "## Reviewers and Rules\n",
1148
+ ]
1149
+
1150
+ for name, sf in seeds.items():
1151
+ persona_name = name.replace("_", " ").title()
1152
+ lines.append(f"### {persona_name}\n")
1153
+ for r in sf.rules:
1154
+ lines.append(f"- **{r.rule}**")
1155
+ if r.antipattern:
1156
+ lines.append(f" - Antipattern: {r.antipattern}")
1157
+ lines.append("")
1158
+
1159
+ lines.extend(
1160
+ [
1161
+ "## Output Format\n",
1162
+ "For each issue found, output:\n",
1163
+ "```json",
1164
+ "{",
1165
+ ' "reviewer": "<persona>",',
1166
+ ' "severity": "critical|major|minor|nitpick",',
1167
+ ' "category": "<category>",',
1168
+ ' "location": "<file:line>",',
1169
+ ' "description": "<what is wrong>",',
1170
+ ' "rule_learned": "<generalizable rule>"',
1171
+ "}",
1172
+ "```\n",
1173
+ "## Instructions\n",
1174
+ "1. Read the target code thoroughly",
1175
+ "2. Apply each rule from each reviewer",
1176
+ "3. Report ALL violations found",
1177
+ "4. Be ruthless - this is the gauntlet",
1178
+ "",
1179
+ ]
1180
+ )
1181
+
1182
+ formatted = "\n".join(lines)
1183
+
1184
+ if output:
1185
+ output_path = Path(output)
1186
+ output_path.write_text(formatted, encoding="utf-8")
1187
+ click.echo(f"Wrote prompt to {output_path}")
1188
+ else:
1189
+ click.echo(formatted)
1190
+
1191
+
1192
+ @gauntlet.command("learn")
1193
+ @click.argument("issues_file", type=click.Path(exists=True))
1194
+ @click.option("--source", "-s", help="Source identifier (e.g., 'gauntlet:PR#42')")
1195
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON")
1196
+ def gauntlet_learn(issues_file: str, source: str | None, output_json: bool):
1197
+ """Persist learnings from a gauntlet review.
1198
+
1199
+ Takes a JSON file of issues (in the gauntlet output format)
1200
+ and calls learn_from_review to persist them.
1201
+
1202
+ Examples:
1203
+
1204
+ buildlog gauntlet learn review_issues.json
1205
+ buildlog gauntlet learn issues.json --source "gauntlet:2026-01-22"
1206
+ """
1207
+ import json as json_module
1208
+ from dataclasses import asdict
1209
+
1210
+ from buildlog.core import learn_from_review
1211
+
1212
+ buildlog_dir = Path("buildlog")
1213
+
1214
+ if not buildlog_dir.exists():
1215
+ click.echo("No buildlog/ directory found. Run 'buildlog init' first.", err=True)
1216
+ raise SystemExit(1)
1217
+
1218
+ # Load issues
1219
+ try:
1220
+ with open(issues_file) as f:
1221
+ data = json_module.load(f)
1222
+ except json_module.JSONDecodeError as e:
1223
+ click.echo(f"Invalid JSON: {e}", err=True)
1224
+ raise SystemExit(1)
1225
+
1226
+ # Handle different formats
1227
+ if isinstance(data, list):
1228
+ issues = data
1229
+ elif isinstance(data, dict) and "all_issues" in data:
1230
+ issues = data["all_issues"]
1231
+ elif isinstance(data, dict) and "issues" in data:
1232
+ issues = data["issues"]
1233
+ else:
1234
+ click.echo(
1235
+ "Expected list of issues or dict with 'issues'/'all_issues'", err=True
1236
+ )
1237
+ raise SystemExit(1)
1238
+
1239
+ if not issues:
1240
+ click.echo("No issues found in file.", err=True)
1241
+ raise SystemExit(1)
1242
+
1243
+ # Learn from review
1244
+ result = learn_from_review(buildlog_dir, issues, source=source or "gauntlet")
1245
+
1246
+ if output_json:
1247
+ click.echo(json_module.dumps(asdict(result), indent=2))
1248
+ else:
1249
+ click.echo(f"✓ {result.message}")
1250
+ click.echo(f" New learnings: {result.new_learnings}")
1251
+ click.echo(f" Reinforced: {result.reinforced_learnings}")
1252
+ click.echo(f" Total processed: {result.total_issues_processed}")
1253
+
1254
+
879
1255
  if __name__ == "__main__":
880
1256
  main()