py-harness-cli 0.3.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 (92) hide show
  1. finetune/__init__.py +1 -0
  2. finetune/agent_system.py +41 -0
  3. finetune/agent_traces.py +157 -0
  4. finetune/everyday.py +30 -0
  5. finetune/hf_ollama.py +158 -0
  6. finetune/huggingface_store.py +144 -0
  7. finetune/models.py +74 -0
  8. finetune/paths.py +11 -0
  9. finetune/python_vibe.py +788 -0
  10. finetune/splits.py +54 -0
  11. finetune/systems.py +9 -0
  12. harness/__init__.py +42 -0
  13. harness/__main__.py +8 -0
  14. harness/act/__init__.py +6 -0
  15. harness/act/autofix/__init__.py +110 -0
  16. harness/act/autofix/additions.py +217 -0
  17. harness/act/autofix/conflicts.py +124 -0
  18. harness/act/autofix/cover.py +419 -0
  19. harness/act/autofix/mechanical.py +151 -0
  20. harness/act/autofix/missing_imports.py +50 -0
  21. harness/act/autofix/moves.py +439 -0
  22. harness/act/autofix/names.py +339 -0
  23. harness/act/autofix/scaffold.py +224 -0
  24. harness/act/code.py +157 -0
  25. harness/act/gate.py +229 -0
  26. harness/act/parse.py +247 -0
  27. harness/act/patch_fix.py +138 -0
  28. harness/act/tools.py +244 -0
  29. harness/agent/__init__.py +11 -0
  30. harness/agent/dispatch.py +235 -0
  31. harness/agent/loop.py +699 -0
  32. harness/agent/options.py +144 -0
  33. harness/agent/policy.py +856 -0
  34. harness/agent/prompt.py +170 -0
  35. harness/cli.py +393 -0
  36. harness/editor_kit.py +265 -0
  37. harness/guard/__init__.py +6 -0
  38. harness/guard/fallbacks.py +6 -0
  39. harness/guard/loop_guard.py +57 -0
  40. harness/guard/python_vibe.py +68 -0
  41. harness/guard/run.py +41 -0
  42. harness/guard/types.py +19 -0
  43. harness/locate.py +767 -0
  44. harness/mcp_stdio.py +306 -0
  45. harness/memory/__init__.py +5 -0
  46. harness/memory/conversation.py +104 -0
  47. harness/model/__init__.py +6 -0
  48. harness/model/chat_backend.py +100 -0
  49. harness/model/engine.py +165 -0
  50. harness/model/ollama_generate.py +60 -0
  51. harness/model/openai_generate.py +156 -0
  52. harness/model/outbound.py +83 -0
  53. harness/model/route.py +90 -0
  54. harness/observe/__init__.py +6 -0
  55. harness/observe/eval_gate.py +80 -0
  56. harness/observe/eval_loop.py +185 -0
  57. harness/observe/eval_tasks.py +399 -0
  58. harness/observe/report_md.py +102 -0
  59. harness/observe/trace_record.py +79 -0
  60. harness/openai_api.py +81 -0
  61. harness/paths.py +88 -0
  62. harness/py.typed +0 -0
  63. harness/scan/__init__.py +6 -0
  64. harness/scan/app_spec.py +338 -0
  65. harness/scan/design.py +112 -0
  66. harness/scan/existing.py +131 -0
  67. harness/scan/layout.py +254 -0
  68. harness/scan/names.py +308 -0
  69. harness/scan/project_brief.py +287 -0
  70. harness/scan/project_docs.py +42 -0
  71. harness/scan/project_scan.py +49 -0
  72. harness/scan/repo_map.py +101 -0
  73. harness/secrets.py +39 -0
  74. harness/server.py +199 -0
  75. harness/ship/__init__.py +1 -0
  76. harness/ship/bot_pr.py +221 -0
  77. harness/ship/git_ship.py +262 -0
  78. harness/ship/identity.py +62 -0
  79. harness/ship/ticket.py +251 -0
  80. harness/skillkit/__init__.py +6 -0
  81. harness/skillkit/catalog.py +241 -0
  82. harness/skillkit/refuse_change.py +640 -0
  83. harness/skillkit/refuse_finish.py +295 -0
  84. harness/skillkit/target.py +238 -0
  85. harness/task.py +717 -0
  86. py_harness_cli-0.3.0.dist-info/METADATA +177 -0
  87. py_harness_cli-0.3.0.dist-info/RECORD +92 -0
  88. py_harness_cli-0.3.0.dist-info/WHEEL +5 -0
  89. py_harness_cli-0.3.0.dist-info/entry_points.txt +3 -0
  90. py_harness_cli-0.3.0.dist-info/licenses/LICENSE +202 -0
  91. py_harness_cli-0.3.0.dist-info/licenses/NOTICE +6 -0
  92. py_harness_cli-0.3.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,788 @@
1
+ """Python vibe-coding pairs — working code first, short note after."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def all_pairs() -> list[tuple[str, str]]:
7
+ return list(_PAIRS)
8
+
9
+
10
+ _PAIRS: list[tuple[str, str]] = [
11
+ (
12
+ "Write a script that lists the largest files under a directory.",
13
+ """Walks with `pathlib`, sorts by size, prints a table.
14
+
15
+ ```python
16
+ #!/usr/bin/env python3
17
+ from pathlib import Path
18
+ import argparse
19
+
20
+ def main() -> None:
21
+ p = argparse.ArgumentParser()
22
+ p.add_argument("root", type=Path, default=Path("."), nargs="?")
23
+ p.add_argument("-n", type=int, default=20)
24
+ args = p.parse_args()
25
+ files = [f for f in args.root.rglob("*") if f.is_file()]
26
+ files.sort(key=lambda f: f.stat().st_size, reverse=True)
27
+ for f in files[: args.n]:
28
+ print(f"{f.stat().st_size:>12} {f}")
29
+
30
+ if __name__ == "__main__":
31
+ main()
32
+ ```
33
+ """,
34
+ ),
35
+ (
36
+ "JSONL reader that yields dicts and skips bad lines.",
37
+ """Don't crash the whole file on one bad row.
38
+
39
+ ```python
40
+ import json
41
+ from collections.abc import Iterator
42
+ from pathlib import Path
43
+
44
+ def read_jsonl(path: Path) -> Iterator[dict]:
45
+ with path.open(encoding="utf-8") as fh:
46
+ for line_no, line in enumerate(fh, 1):
47
+ line = line.strip()
48
+ if not line:
49
+ continue
50
+ try:
51
+ row = json.loads(line)
52
+ except json.JSONDecodeError as exc:
53
+ print(f"skip {path}:{line_no}: {exc}")
54
+ continue
55
+ if isinstance(row, dict):
56
+ yield row
57
+ ```
58
+ """,
59
+ ),
60
+ (
61
+ "Tiny FastAPI ping + echo.",
62
+ """One file. Run with `uvicorn app:app --reload`.
63
+
64
+ ```python
65
+ from fastapi import FastAPI
66
+ from pydantic import BaseModel
67
+
68
+ app = FastAPI()
69
+
70
+ class Echo(BaseModel):
71
+ text: str
72
+
73
+ @app.get("/health")
74
+ def health() -> dict[str, str]:
75
+ return {"status": "ok"}
76
+
77
+ @app.post("/echo")
78
+ def echo(body: Echo) -> Echo:
79
+ return body
80
+ ```
81
+ """,
82
+ ),
83
+ (
84
+ "Retry a function three times with backoff.",
85
+ """Stdlib only.
86
+
87
+ ```python
88
+ import time
89
+ from collections.abc import Callable
90
+ from typing import TypeVar
91
+
92
+ T = TypeVar("T")
93
+
94
+ def retry(fn: Callable[[], T], *, attempts: int = 3, base: float = 0.2) -> T:
95
+ last: Exception | None = None
96
+ for i in range(attempts):
97
+ try:
98
+ return fn()
99
+ except Exception as exc:
100
+ last = exc
101
+ time.sleep(base * (2 ** i))
102
+ assert last is not None
103
+ raise last
104
+ ```
105
+ """,
106
+ ),
107
+ (
108
+ "Async gather with a timeout per task.",
109
+ """`asyncio.wait_for` per call, then `gather`.
110
+
111
+ ```python
112
+ import asyncio
113
+ from collections.abc import Awaitable, Sequence
114
+ from typing import TypeVar
115
+
116
+ T = TypeVar("T")
117
+
118
+ async def gather_timeout(
119
+ aws: Sequence[Awaitable[T]], timeout: float
120
+ ) -> list[T | BaseException]:
121
+ wrapped = [asyncio.wait_for(aw, timeout) for aw in aws]
122
+ return list(await asyncio.gather(*wrapped, return_exceptions=True))
123
+ ```
124
+ """,
125
+ ),
126
+ (
127
+ "pytest: temp file in, expected string out.",
128
+ """Fixture writes the input; assert the function.
129
+
130
+ ```python
131
+ from pathlib import Path
132
+ import pytest
133
+ from mypkg.io import slugify_file
134
+
135
+ def test_slugify_file(tmp_path: Path) -> None:
136
+ src = tmp_path / "Hello World.TXT"
137
+ src.write_text("x", encoding="utf-8")
138
+ out = slugify_file(src)
139
+ assert out.name == "hello-world.txt"
140
+ ```
141
+ """,
142
+ ),
143
+ (
144
+ "Parse a .env file into a dict. No extra libs.",
145
+ """Ignore comments and blank lines. Last key wins.
146
+
147
+ ```python
148
+ from pathlib import Path
149
+
150
+ def load_dotenv(path: Path) -> dict[str, str]:
151
+ out: dict[str, str] = {}
152
+ for raw in path.read_text(encoding="utf-8").splitlines():
153
+ line = raw.strip()
154
+ if not line or line.startswith("#") or "=" not in line:
155
+ continue
156
+ key, _, value = line.partition("=")
157
+ out[key.strip()] = value.strip().strip("\"'")
158
+ return out
159
+ ```
160
+ """,
161
+ ),
162
+ (
163
+ "Dedup a list of dicts by (name, version) without json.dumps.",
164
+ """Tuple keys, not stringified JSON.
165
+
166
+ ```python
167
+ def unique_packages(rows: list[dict]) -> list[dict]:
168
+ seen: set[tuple[str, str]] = set()
169
+ out: list[dict] = []
170
+ for row in rows:
171
+ key = (row["name"], row["version"])
172
+ if key in seen:
173
+ continue
174
+ seen.add(key)
175
+ out.append(row)
176
+ return out
177
+ ```
178
+ """,
179
+ ),
180
+ (
181
+ "Context manager that cd's and comes back.",
182
+ """
183
+
184
+ ```python
185
+ from contextlib import contextmanager
186
+ from pathlib import Path
187
+ import os
188
+ from collections.abc import Iterator
189
+
190
+ @contextmanager
191
+ def pushd(path: Path) -> Iterator[None]:
192
+ prev = Path.cwd()
193
+ os.chdir(path)
194
+ try:
195
+ yield
196
+ finally:
197
+ os.chdir(prev)
198
+ ```
199
+ """,
200
+ ),
201
+ (
202
+ "SQLite insert-or-ignore for a url cache.",
203
+ """
204
+
205
+ ```python
206
+ import sqlite3
207
+ from pathlib import Path
208
+
209
+ def open_cache(path: Path) -> sqlite3.Connection:
210
+ conn = sqlite3.connect(path)
211
+ conn.execute(
212
+ "CREATE TABLE IF NOT EXISTS pages (url TEXT PRIMARY KEY, body TEXT)"
213
+ )
214
+ return conn
215
+
216
+ def put(conn: sqlite3.Connection, url: str, body: str) -> None:
217
+ conn.execute("INSERT OR IGNORE INTO pages(url, body) VALUES (?, ?)", (url, body))
218
+ conn.commit()
219
+ ```
220
+ """,
221
+ ),
222
+ (
223
+ "httpx GET with a hard timeout and raise for status.",
224
+ """
225
+
226
+ ```python
227
+ import httpx
228
+
229
+ def fetch_text(url: str, timeout: float = 10.0) -> str:
230
+ with httpx.Client(timeout=timeout) as client:
231
+ response = client.get(url)
232
+ response.raise_for_status()
233
+ return response.text
234
+ ```
235
+ """,
236
+ ),
237
+ (
238
+ "dataclass for a train example, frozen.",
239
+ """
240
+
241
+ ```python
242
+ from dataclasses import dataclass
243
+
244
+ @dataclass(frozen=True)
245
+ class Example:
246
+ user: str
247
+ assistant: str
248
+
249
+ def as_pair(self) -> tuple[str, str]:
250
+ return self.user, self.assistant
251
+ ```
252
+ """,
253
+ ),
254
+ (
255
+ "argparse: required input file, optional --out, default stdout.",
256
+ """
257
+
258
+ ```python
259
+ import argparse
260
+ import sys
261
+ from pathlib import Path
262
+
263
+ def build_parser() -> argparse.ArgumentParser:
264
+ p = argparse.ArgumentParser(description="Transform a file")
265
+ p.add_argument("src", type=Path)
266
+ p.add_argument("--out", type=Path)
267
+ return p
268
+
269
+ def main() -> None:
270
+ args = build_parser().parse_args()
271
+ text = args.src.read_text(encoding="utf-8")
272
+ if args.out:
273
+ args.out.write_text(text.upper(), encoding="utf-8")
274
+ else:
275
+ sys.stdout.write(text.upper())
276
+ ```
277
+ """,
278
+ ),
279
+ (
280
+ "Split a list into chunks of n.",
281
+ """
282
+
283
+ ```python
284
+ from collections.abc import Iterator, Sequence
285
+ from typing import TypeVar
286
+
287
+ T = TypeVar("T")
288
+
289
+ def chunks(items: Sequence[T], n: int) -> Iterator[Sequence[T]]:
290
+ if n <= 0:
291
+ raise ValueError("n must be > 0")
292
+ for i in range(0, len(items), n):
293
+ yield items[i : i + n]
294
+ ```
295
+ """,
296
+ ),
297
+ (
298
+ "logging setup that isn't ugly.",
299
+ """One call at the entrypoint.
300
+
301
+ ```python
302
+ import logging
303
+
304
+ def setup_logging(level: str = "INFO") -> None:
305
+ logging.basicConfig(
306
+ level=getattr(logging, level.upper(), logging.INFO),
307
+ format="%(asctime)s %(levelname)s %(name)s: %(message)s",
308
+ )
309
+ ```
310
+ """,
311
+ ),
312
+ (
313
+ "Filter None from a list and keep types honest.",
314
+ """
315
+
316
+ ```python
317
+ from typing import TypeVar
318
+
319
+ T = TypeVar("T")
320
+
321
+ def present(items: list[T | None]) -> list[T]:
322
+ return [item for item in items if item is not None]
323
+ ```
324
+ """,
325
+ ),
326
+ (
327
+ "Read stdin, count words, print top 10.",
328
+ """
329
+
330
+ ```python
331
+ import sys
332
+ from collections import Counter
333
+ import re
334
+
335
+ words = re.findall(r"[A-Za-z']+", sys.stdin.read().lower())
336
+ for word, n in Counter(words).most_common(10):
337
+ print(f"{n:>6} {word}")
338
+ ```
339
+ """,
340
+ ),
341
+ (
342
+ "Make a pathlib helper that refuses to write outside a root.",
343
+ """Stops `../` surprises.
344
+
345
+ ```python
346
+ from pathlib import Path
347
+
348
+ def safe_join(root: Path, relative: str) -> Path:
349
+ dest = (root / relative).resolve()
350
+ if not dest.is_relative_to(root.resolve()):
351
+ raise ValueError(f"escape: {relative}")
352
+ return dest
353
+ ```
354
+ """,
355
+ ),
356
+ (
357
+ "Pydantic v2 model for a chat message.",
358
+ """
359
+
360
+ ```python
361
+ from typing import Literal
362
+ from pydantic import BaseModel, Field
363
+
364
+ class Message(BaseModel):
365
+ role: Literal["system", "user", "assistant"]
366
+ content: str = Field(min_length=1)
367
+ ```
368
+ """,
369
+ ),
370
+ (
371
+ "Replace a nested key in a dict without deepcopy.",
372
+ """Mutate a shallow copy of the path.
373
+
374
+ ```python
375
+ def set_path(row: dict, path: tuple[str, ...], value: object) -> dict:
376
+ if not path:
377
+ raise ValueError("empty path")
378
+ out = dict(row)
379
+ cur = out
380
+ for key in path[:-1]:
381
+ nxt = dict(cur.get(key) or {})
382
+ cur[key] = nxt
383
+ cur = nxt
384
+ cur[path[-1]] = value
385
+ return out
386
+ ```
387
+ """,
388
+ ),
389
+ (
390
+ "Watch a directory and print new files. No watchdog lib.",
391
+ """Poll mtime. Good enough for a local tool.
392
+
393
+ ```python
394
+ import time
395
+ from pathlib import Path
396
+
397
+ def watch(root: Path, seconds: float = 1.0) -> None:
398
+ seen = {p: p.stat().st_mtime for p in root.glob("*") if p.is_file()}
399
+ while True:
400
+ time.sleep(seconds)
401
+ for path in root.glob("*"):
402
+ if not path.is_file():
403
+ continue
404
+ mtime = path.stat().st_mtime
405
+ if path not in seen or seen[path] < mtime:
406
+ print(path)
407
+ seen[path] = mtime
408
+ ```
409
+ """,
410
+ ),
411
+ (
412
+ "Turn a traceback into a one-liner for logs. Don't leak to users.",
413
+ """Log server-side; return a boring string to the client.
414
+
415
+ ```python
416
+ import logging
417
+ import traceback
418
+
419
+ log = logging.getLogger("app")
420
+
421
+ def handle(exc: Exception) -> str:
422
+ log.exception("request failed")
423
+ return "internal error"
424
+ ```
425
+ """,
426
+ ),
427
+ (
428
+ "Minimal Makefile-less test runner: python -m pytest tests -q",
429
+ """Put this in a justfile or alias. That's the vibe.
430
+
431
+ ```text
432
+ python -m pytest tests -q --tb=short
433
+ ```
434
+ """,
435
+ ),
436
+ (
437
+ "Generator that yields matching lines from a huge log.",
438
+ """Don't read the whole file.
439
+
440
+ ```python
441
+ from collections.abc import Iterator
442
+ from pathlib import Path
443
+
444
+ def grep(path: Path, needle: str) -> Iterator[str]:
445
+ with path.open(encoding="utf-8", errors="replace") as fh:
446
+ for line in fh:
447
+ if needle in line:
448
+ yield line.rstrip("\\n")
449
+ ```
450
+ """,
451
+ ),
452
+ (
453
+ "dataclass + Path for CLI config loaded from JSON.",
454
+ """
455
+
456
+ ```python
457
+ import json
458
+ from dataclasses import dataclass
459
+ from pathlib import Path
460
+
461
+ @dataclass(frozen=True)
462
+ class Config:
463
+ root: Path
464
+ workers: int = 4
465
+
466
+ @classmethod
467
+ def load(cls, path: Path) -> "Config":
468
+ raw = json.loads(path.read_text(encoding="utf-8"))
469
+ return cls(root=Path(raw["root"]), workers=int(raw.get("workers", 4)))
470
+ ```
471
+ """,
472
+ ),
473
+ (
474
+ "Fix this: except: pass",
475
+ """Catch what you mean. Log it.
476
+
477
+ ```python
478
+ import logging
479
+
480
+ log = logging.getLogger(__name__)
481
+
482
+ try:
483
+ payload = load()
484
+ except (OSError, ValueError) as exc:
485
+ log.warning("load failed: %s", exc)
486
+ payload = {}
487
+ ```
488
+ """,
489
+ ),
490
+ (
491
+ "Thread pool for a list of CPU-light tasks.",
492
+ """I/O bound → threads. CPU bound → processes.
493
+
494
+ ```python
495
+ from concurrent.futures import ThreadPoolExecutor, as_completed
496
+ from collections.abc import Callable, Iterable
497
+ from typing import TypeVar
498
+
499
+ T = TypeVar("T")
500
+ R = TypeVar("R")
501
+
502
+ def map_threads(fn: Callable[[T], R], items: Iterable[T], workers: int = 8) -> list[R]:
503
+ with ThreadPoolExecutor(max_workers=workers) as pool:
504
+ futs = [pool.submit(fn, item) for item in items]
505
+ return [fut.result() for fut in as_completed(futs)]
506
+ ```
507
+ """,
508
+ ),
509
+ (
510
+ "Pretty-print a table without pandas.",
511
+ """
512
+
513
+ ```python
514
+ def print_table(rows: list[tuple[str, ...]]) -> None:
515
+ if not rows:
516
+ return
517
+ widths = [max(len(r[i]) for r in rows) for i in range(len(rows[0]))]
518
+ for row in rows:
519
+ print(" ".join(cell.ljust(widths[i]) for i, cell in enumerate(row)))
520
+ ```
521
+ """,
522
+ ),
523
+ (
524
+ "Enum for job status, plus a terminal check.",
525
+ """
526
+
527
+ ```python
528
+ from enum import StrEnum
529
+
530
+ class Status(StrEnum):
531
+ queued = "queued"
532
+ running = "running"
533
+ done = "done"
534
+ failed = "failed"
535
+
536
+ def terminal(self) -> bool:
537
+ return self in {Status.done, Status.failed}
538
+ ```
539
+ """,
540
+ ),
541
+ (
542
+ "sha256 of a file in 1MB chunks.",
543
+ """
544
+
545
+ ```python
546
+ import hashlib
547
+ from pathlib import Path
548
+
549
+ def sha256_file(path: Path) -> str:
550
+ digest = hashlib.sha256()
551
+ with path.open("rb") as fh:
552
+ while chunk := fh.read(1024 * 1024):
553
+ digest.update(chunk)
554
+ return digest.hexdigest()
555
+ ```
556
+ """,
557
+ ),
558
+ (
559
+ "Typed CLI using only argparse subcommands: add and ls.",
560
+ """
561
+
562
+ ```python
563
+ import argparse
564
+
565
+ def add(args: argparse.Namespace) -> None:
566
+ print(args.a + args.b)
567
+
568
+ def ls(_: argparse.Namespace) -> None:
569
+ print("ok")
570
+
571
+ def main() -> None:
572
+ p = argparse.ArgumentParser()
573
+ sub = p.add_subparsers(required=True)
574
+ add_p = sub.add_parser("add")
575
+ add_p.add_argument("a", type=int)
576
+ add_p.add_argument("b", type=int)
577
+ add_p.set_defaults(fn=add)
578
+ ls_p = sub.add_parser("ls")
579
+ ls_p.set_defaults(fn=ls)
580
+ args = p.parse_args()
581
+ args.fn(args)
582
+ ```
583
+ """,
584
+ ),
585
+ (
586
+ "Freeze requirements from the current venv the boring way.",
587
+ """
588
+
589
+ ```bash
590
+ python -m pip freeze > requirements.lock
591
+ ```
592
+ """,
593
+ ),
594
+ (
595
+ "Match glob **/*.py and skip .venv.",
596
+ """
597
+
598
+ ```python
599
+ from pathlib import Path
600
+
601
+ def py_files(root: Path) -> list[Path]:
602
+ return [
603
+ p
604
+ for p in root.rglob("*.py")
605
+ if ".venv" not in p.parts and "__pycache__" not in p.parts
606
+ ]
607
+ ```
608
+ """,
609
+ ),
610
+ (
611
+ "httpx + tenacity? nah, just a loop.",
612
+ """Keep it boring.
613
+
614
+ ```python
615
+ import time
616
+ import httpx
617
+
618
+ def get_ok(url: str) -> httpx.Response:
619
+ last: Exception | None = None
620
+ for i in range(4):
621
+ try:
622
+ r = httpx.get(url, timeout=10)
623
+ r.raise_for_status()
624
+ return r
625
+ except httpx.HTTPError as exc:
626
+ last = exc
627
+ time.sleep(0.25 * (i + 1))
628
+ raise last # type: ignore[misc]
629
+ ```
630
+ """,
631
+ ),
632
+ (
633
+ "Split train/valid ids with a seed. No sklearn.",
634
+ """
635
+
636
+ ```python
637
+ import random
638
+
639
+ def split_ids(ids: list[str], valid_frac: float = 0.1, seed: int = 0) -> tuple[list[str], list[str]]:
640
+ rng = random.Random(seed)
641
+ shuffled = list(ids)
642
+ rng.shuffle(shuffled)
643
+ n_valid = max(1, int(len(shuffled) * valid_frac))
644
+ return shuffled[n_valid:], shuffled[:n_valid]
645
+ ```
646
+ """,
647
+ ),
648
+ (
649
+ "Write JSON atomically (temp file + replace).",
650
+ """
651
+
652
+ ```python
653
+ import json
654
+ import os
655
+ from pathlib import Path
656
+
657
+ def write_json(path: Path, data: object) -> None:
658
+ tmp = path.with_suffix(path.suffix + ".tmp")
659
+ tmp.write_text(json.dumps(data, indent=2) + "\\n", encoding="utf-8")
660
+ os.replace(tmp, path)
661
+ ```
662
+ """,
663
+ ),
664
+ (
665
+ "dataclass slots for a hot row.",
666
+ """
667
+
668
+ ```python
669
+ from dataclasses import dataclass
670
+
671
+ @dataclass(slots=True)
672
+ class Row:
673
+ name: str
674
+ n: int
675
+ ```
676
+ """,
677
+ ),
678
+ (
679
+ "How do I run a module as a script in a src layout?",
680
+ """From the repo root, with `src` on the path:
681
+
682
+ ```bash
683
+ PYTHONPATH=src python -m finetune.whatever
684
+ ```
685
+
686
+ Or install the package editable and drop PYTHONPATH.
687
+ """,
688
+ ),
689
+ (
690
+ "Type a JSONL stream as list[dict[str, object]].",
691
+ """Don't pretend you know the schema if you don't.
692
+
693
+ ```python
694
+ import json
695
+ from pathlib import Path
696
+
697
+ def load_rows(path: Path) -> list[dict[str, object]]:
698
+ rows: list[dict[str, object]] = []
699
+ for line in path.read_text(encoding="utf-8").splitlines():
700
+ if line.strip():
701
+ rows.append(json.loads(line))
702
+ return rows
703
+ ```
704
+ """,
705
+ ),
706
+ (
707
+ "Kill a hung subprocess after N seconds.",
708
+ """
709
+
710
+ ```python
711
+ import subprocess
712
+
713
+ def run_capped(cmd: list[str], seconds: int) -> subprocess.CompletedProcess[str]:
714
+ return subprocess.run(
715
+ cmd,
716
+ check=False,
717
+ text=True,
718
+ capture_output=True,
719
+ timeout=seconds,
720
+ )
721
+ ```
722
+ """,
723
+ ),
724
+ (
725
+ "Sort paths so 2 comes before 10.",
726
+ """Natural sort on the name.
727
+
728
+ ```python
729
+ import re
730
+ from pathlib import Path
731
+
732
+ _NUM = re.compile(r"(\\d+)")
733
+
734
+ def key(path: Path) -> tuple:
735
+ parts = _NUM.split(path.name)
736
+ return tuple(int(p) if p.isdigit() else p.lower() for p in parts)
737
+ ```
738
+ """,
739
+ ),
740
+ (
741
+ "One-liner to start a venv and install mlx on 3.13.",
742
+ """
743
+
744
+ ```bash
745
+ /opt/homebrew/bin/python3.13 -m venv .venv
746
+ source .venv/bin/activate
747
+ pip install -U pip
748
+ pip install 'mlx-lm[train]'
749
+ ```
750
+ """,
751
+ ),
752
+ (
753
+ "Don't use mutable default args.",
754
+ """
755
+
756
+ ```python
757
+ def add_tag(row: dict, tag: str, tags: list[str] | None = None) -> list[str]:
758
+ out = list(tags or [])
759
+ out.append(tag)
760
+ row["tags"] = out
761
+ return out
762
+ ```
763
+ """,
764
+ ),
765
+ (
766
+ "Zip two lists and drop extras — or keep them?",
767
+ """`zip` drops. `itertools.zip_longest` keeps.
768
+
769
+ ```python
770
+ from itertools import zip_longest
771
+
772
+ def pairs(left: list[str], right: list[str]) -> list[tuple[str | None, str | None]]:
773
+ return list(zip_longest(left, right))
774
+ ```
775
+ """,
776
+ ),
777
+ (
778
+ "Path.read_text vs open. Which?",
779
+ """`Path.read_text(encoding='utf-8')` for whole files. `open` when you stream.
780
+
781
+ ```python
782
+ from pathlib import Path
783
+
784
+ text = Path("notes.md").read_text(encoding="utf-8")
785
+ ```
786
+ """,
787
+ ),
788
+ ]