@homericintelligence/athena-opencode 0.4.4

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 (36) hide show
  1. package/LICENSE +29 -0
  2. package/NOTICE +22 -0
  3. package/README.md +45 -0
  4. package/package.json +29 -0
  5. package/plugin.js +54 -0
  6. package/skills/THIRD_PARTY_LICENSES.md +50 -0
  7. package/skills/_cli.py +152 -0
  8. package/skills/advise/SKILL.md +62 -0
  9. package/skills/advise/scripts/list_retrievable_skills.py +49 -0
  10. package/skills/brainstorm/SKILL.md +110 -0
  11. package/skills/change-review/SKILL.md +68 -0
  12. package/skills/change-review/references/scope-resolution.md +52 -0
  13. package/skills/change-review/scripts/resolve_scope.py +1219 -0
  14. package/skills/finalize-plan/SKILL.md +129 -0
  15. package/skills/git-worktrees/SKILL.md +113 -0
  16. package/skills/git-worktrees/scripts/prepare_worktree.py +153 -0
  17. package/skills/issue-review/SKILL.md +67 -0
  18. package/skills/learn/SKILL.md +208 -0
  19. package/skills/myrmidon-swarm/SKILL.md +93 -0
  20. package/skills/plan-issue/SKILL.md +70 -0
  21. package/skills/pr-review/SKILL.md +114 -0
  22. package/skills/pr-review/references/criteria.md +26 -0
  23. package/skills/pr-review/references/delivery.md +135 -0
  24. package/skills/pr-review/references/evidence.md +233 -0
  25. package/skills/pr-review/references/prevalidated.md +155 -0
  26. package/skills/pr-review/scripts/collect_evidence.py +1478 -0
  27. package/skills/pr-review/scripts/diff_context.py +74 -0
  28. package/skills/pr-review/scripts/materialize_snapshot.py +731 -0
  29. package/skills/pr-review/scripts/pr_identity.py +80 -0
  30. package/skills/pr-review/scripts/resolve_pr.py +258 -0
  31. package/skills/repo-review/SKILL.md +119 -0
  32. package/skills/systematic-debugging/SKILL.md +199 -0
  33. package/skills/systematic-debugging/scripts/repository_evidence.py +77 -0
  34. package/skills/test-driven-development/SKILL.md +75 -0
  35. package/skills/tidy/SKILL.md +71 -0
  36. package/skills/tidy/scripts/run_tidy.py +43 -0
@@ -0,0 +1,731 @@
1
+ #!/usr/bin/env python3
2
+ """Materialize one immutable GitHub pull-request snapshot in an isolated repository."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import shutil
8
+ import stat
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+ from collections.abc import Sequence
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+
16
+ if __package__ in {None, ""}:
17
+ sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
18
+
19
+ from pr_identity import COMMIT_OID, require_commit_oid, require_github_repository
20
+
21
+ from skills._cli import (
22
+ argument_parser,
23
+ git_read_arguments,
24
+ git_read_environment,
25
+ run_command,
26
+ )
27
+
28
+ SNAPSHOT_COMMAND_TIMEOUT_SECONDS = 30.0
29
+ BOUNDED_MATERIALIZE_TIMEOUT_SECONDS = 600.0
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class MaterializedSnapshot:
34
+ """A detached source tree bound to one reviewed GitHub pull request."""
35
+
36
+ root: Path
37
+ source_path: Path
38
+ merge_base: str
39
+ tree_oid: str
40
+
41
+ def as_json(self) -> dict[str, str]:
42
+ """Return the snapshot fields a host needs for immutable inspection."""
43
+ return {
44
+ "merge_base": self.merge_base,
45
+ "root": str(self.root),
46
+ "source_path": str(self.source_path),
47
+ "tree_oid": self.tree_oid,
48
+ }
49
+
50
+
51
+ def canonical_repository_url(repository: str) -> str:
52
+ """Return the sole permitted acquisition endpoint for a GitHub repository."""
53
+ return f"https://github.com/{repository}.git"
54
+
55
+
56
+ def _git(
57
+ *arguments: str,
58
+ cwd: Path | None = None,
59
+ capture_output: bool = False,
60
+ accepted_codes: tuple[int, ...] = (0,),
61
+ temporary_directory: Path | None = None,
62
+ ) -> str:
63
+ """Run a bounded isolated-repository Git command without ambient config."""
64
+ environment = git_read_environment()
65
+ if temporary_directory is not None:
66
+ environment["TMPDIR"] = str(temporary_directory)
67
+ command_options: dict[str, object] = {
68
+ "cwd": cwd,
69
+ "stdout": subprocess.PIPE if capture_output else subprocess.DEVNULL,
70
+ "stderr": subprocess.DEVNULL,
71
+ "env": environment,
72
+ "text": True,
73
+ "check": False,
74
+ "timeout": SNAPSHOT_COMMAND_TIMEOUT_SECONDS,
75
+ }
76
+ try:
77
+ result = run_command(
78
+ ["git", *git_read_arguments(), *arguments], **command_options
79
+ )
80
+ except subprocess.SubprocessError as error:
81
+ raise RuntimeError(
82
+ "cannot materialize the immutable pull-request snapshot"
83
+ ) from error
84
+ if result.returncode not in accepted_codes:
85
+ raise RuntimeError("cannot materialize the immutable pull-request snapshot")
86
+ return result.stdout.strip() if isinstance(result.stdout, str) else ""
87
+
88
+
89
+ def _hdiutil(*arguments: str) -> None:
90
+ """Run the macOS disk-image tool or fail closed when it cannot enforce a quota."""
91
+ try:
92
+ result = run_command(
93
+ ["hdiutil", *arguments],
94
+ stdout=subprocess.DEVNULL,
95
+ stderr=subprocess.DEVNULL,
96
+ text=True,
97
+ check=False,
98
+ )
99
+ except (OSError, RuntimeError, subprocess.SubprocessError) as error:
100
+ raise RuntimeError(
101
+ "host cannot enforce the immutable pull-request snapshot size limit"
102
+ ) from error
103
+ if result.returncode != 0:
104
+ raise RuntimeError(
105
+ "host cannot enforce the immutable pull-request snapshot size limit"
106
+ )
107
+
108
+
109
+ def _mount_tmpfs(source: Path, maximum_bytes: int) -> bool:
110
+ """Mount a Linux tmpfs whose total capacity is the snapshot quota.
111
+
112
+ Returns True only when the mount is actually enforced; the caller must
113
+ verify ``source.is_mount()`` is not relied on elsewhere. On any failure
114
+ (no ``mount`` binary, no privilege, mount rejection) the caller uses the
115
+ bounded user/mount-namespace fallback or fails closed.
116
+ """
117
+ if sys.platform != "linux" or shutil.which("mount") is None:
118
+ return False
119
+ maximum_kibibytes = maximum_bytes // 1024
120
+ if maximum_kibibytes < 1:
121
+ raise RuntimeError("immutable pull-request snapshot has no usable disk space")
122
+ try:
123
+ source.mkdir()
124
+ except FileExistsError:
125
+ # Directory already exists from a prior materialization; reuse it.
126
+ pass
127
+ except OSError:
128
+ return False
129
+ try:
130
+ result = run_command(
131
+ [
132
+ "mount",
133
+ "-t",
134
+ "tmpfs",
135
+ "-o",
136
+ f"size={maximum_kibibytes}k",
137
+ "tmpfs",
138
+ str(source),
139
+ ],
140
+ stdout=subprocess.DEVNULL,
141
+ stderr=subprocess.DEVNULL,
142
+ text=True,
143
+ check=False,
144
+ )
145
+ except (OSError, RuntimeError, subprocess.SubprocessError):
146
+ return False
147
+ return result.returncode == 0 and source.is_mount()
148
+
149
+
150
+ def _create_quota_volume(root: Path, maximum_bytes: int) -> Path | None:
151
+ """Return a bounded source directory, or None for the Linux bounded fallback.
152
+
153
+ darwin: attach a sparse HFS+ volume whose total capacity is the quota.
154
+ linux: mount a tmpfs whose total capacity is the quota when the host is
155
+ privileged; otherwise return None so the caller materializes the
156
+ snapshot inside a bounded user/mount namespace.
157
+ other: fail closed — the host cannot enforce the snapshot size limit.
158
+ """
159
+ maximum_kibibytes = maximum_bytes // 1024
160
+ if maximum_kibibytes < 1:
161
+ raise RuntimeError("immutable pull-request snapshot has no usable disk space")
162
+ if sys.platform == "darwin":
163
+ image = root / "snapshot.sparseimage"
164
+ source = root / "source"
165
+ source.mkdir()
166
+ _hdiutil(
167
+ "create",
168
+ "-quiet",
169
+ "-type",
170
+ "SPARSE",
171
+ "-size",
172
+ f"{maximum_kibibytes}k",
173
+ "-fs",
174
+ "Case-sensitive HFS+",
175
+ "-volname",
176
+ "Athena PR Review",
177
+ "-nospotlight",
178
+ str(image),
179
+ )
180
+ _hdiutil(
181
+ "attach",
182
+ "-quiet",
183
+ "-nobrowse",
184
+ "-mountpoint",
185
+ str(source),
186
+ str(image),
187
+ )
188
+ if not source.is_mount():
189
+ raise RuntimeError(
190
+ "host cannot enforce the immutable pull-request snapshot size limit"
191
+ )
192
+ return source
193
+ if sys.platform.startswith("linux"):
194
+ source = root / "source"
195
+ if _mount_tmpfs(source, maximum_bytes):
196
+ return source
197
+ try:
198
+ source.rmdir()
199
+ except OSError:
200
+ # The mount may have partially created the directory; a failed
201
+ # rmdir is not fatal - the caller treats None as a fallback.
202
+ pass
203
+ return None
204
+ raise RuntimeError(
205
+ "host cannot enforce the immutable pull-request snapshot size limit"
206
+ )
207
+
208
+
209
+ def _detach_volume(source: Path) -> None:
210
+ """Detach the platform quota volume from its mount point."""
211
+ if sys.platform == "darwin":
212
+ _hdiutil("detach", "-force", "-quiet", str(source))
213
+ return
214
+ if sys.platform.startswith("linux"):
215
+ try:
216
+ result = run_command(
217
+ ["umount", str(source)],
218
+ stdout=subprocess.DEVNULL,
219
+ stderr=subprocess.DEVNULL,
220
+ text=True,
221
+ check=False,
222
+ )
223
+ if result.returncode != 0:
224
+ result = run_command(
225
+ ["umount", "-l", str(source)],
226
+ stdout=subprocess.DEVNULL,
227
+ stderr=subprocess.DEVNULL,
228
+ text=True,
229
+ check=False,
230
+ )
231
+ if result.returncode != 0:
232
+ raise RuntimeError("cannot remove the immutable pull-request snapshot")
233
+ except (OSError, RuntimeError, subprocess.SubprocessError) as error:
234
+ raise RuntimeError(
235
+ "cannot remove the immutable pull-request snapshot"
236
+ ) from error
237
+ return
238
+ raise RuntimeError("cannot remove the immutable pull-request snapshot")
239
+
240
+
241
+ def _detach_best_effort(source: Path) -> None:
242
+ """Detach a quota volume without masking a prior failure."""
243
+ try:
244
+ _detach_volume(source)
245
+ except RuntimeError:
246
+ # Detach is best-effort; a prior failure must not be masked by a
247
+ # secondary cleanup error.
248
+ pass
249
+
250
+
251
+ def _require_base_ref(base_ref: str) -> str:
252
+ """Validate the GitHub base branch before it becomes a fetch refspec."""
253
+ if not base_ref or base_ref.startswith("-") or ".." in base_ref:
254
+ raise RuntimeError("GitHub returned an invalid pull-request base ref")
255
+ _git("check-ref-format", "--branch", base_ref)
256
+ return base_ref
257
+
258
+
259
+ def _repository_size(path: Path, *, maximum_bytes: int | None = None) -> int:
260
+ """Return the size of a repository within 90 percent of free disk space."""
261
+ if maximum_bytes is None:
262
+ try:
263
+ maximum_bytes = (shutil.disk_usage(path).free * 9) // 10
264
+ except OSError as error:
265
+ raise RuntimeError(
266
+ "cannot inspect the immutable pull-request snapshot"
267
+ ) from error
268
+ total = 0
269
+ for entry in path.rglob("*"):
270
+ try:
271
+ details = entry.lstat()
272
+ except OSError as error:
273
+ raise RuntimeError(
274
+ "cannot inspect the immutable pull-request snapshot"
275
+ ) from error
276
+ if stat.S_ISREG(details.st_mode):
277
+ total += details.st_size
278
+ if total > maximum_bytes:
279
+ raise RuntimeError(
280
+ "immutable pull-request snapshot exceeds the safe size limit"
281
+ )
282
+ return total
283
+
284
+
285
+ def _verify_no_promisor_configuration(repository: Path) -> None:
286
+ """Reject partial-clone configuration before any immutable object reads."""
287
+ for key in ("extensions.partialClone",):
288
+ value = _git(
289
+ "config",
290
+ "--local",
291
+ "--get",
292
+ key,
293
+ cwd=repository,
294
+ capture_output=True,
295
+ accepted_codes=(0, 1),
296
+ )
297
+ if value:
298
+ raise RuntimeError(
299
+ "immutable pull-request snapshot must not use partial clone configuration"
300
+ )
301
+ promisor = _git(
302
+ "config",
303
+ "--local",
304
+ "--get-regexp",
305
+ r"^remote\..*\.(promisor|partialclonefilter)$",
306
+ cwd=repository,
307
+ capture_output=True,
308
+ accepted_codes=(0, 1),
309
+ )
310
+ if promisor:
311
+ raise RuntimeError(
312
+ "immutable pull-request snapshot must not use promisor configuration"
313
+ )
314
+
315
+
316
+ def _require_commit(repository: Path, revision: str, label: str) -> str:
317
+ """Verify one fetched ref resolves exactly to its captured commit OID."""
318
+ resolved = _git(
319
+ "rev-parse",
320
+ "--verify",
321
+ f"{revision}^{{commit}}",
322
+ cwd=repository,
323
+ capture_output=True,
324
+ )
325
+ return require_commit_oid(resolved, label)
326
+
327
+
328
+ def _make_read_only(root: Path) -> None:
329
+ """Remove write bits from the completed snapshot without following symlinks."""
330
+ entries = sorted(root.rglob("*"), key=lambda entry: len(entry.parts), reverse=True)
331
+ for entry in entries:
332
+ if entry.is_symlink():
333
+ continue
334
+ try:
335
+ mode = entry.stat(follow_symlinks=False).st_mode
336
+ if stat.S_ISDIR(mode):
337
+ entry.chmod(0o555)
338
+ else:
339
+ entry.chmod(0o555 if mode & stat.S_IXUSR else 0o444)
340
+ except OSError as error:
341
+ raise RuntimeError(
342
+ "cannot make the immutable pull-request snapshot read-only"
343
+ ) from error
344
+ root.chmod(0o555)
345
+
346
+
347
+ def _acquire_into(
348
+ source: Path,
349
+ *,
350
+ repository_url: str,
351
+ number: int,
352
+ base_ref: str,
353
+ base_oid: str,
354
+ head_oid: str,
355
+ hooks: Path,
356
+ template: Path,
357
+ maximum_bytes: int,
358
+ ) -> tuple[str, str]:
359
+ """Fetch only the captured base branch and PR head into a source directory.
360
+
361
+ The source directory is provided by the caller (a quota volume or a
362
+ bounded tmpfs). Returns ``(merge_base, tree_oid)`` after verifying every
363
+ immutable binding against the captured OIDs.
364
+ """
365
+ _git(
366
+ "-c",
367
+ f"core.hooksPath={hooks}",
368
+ "-c",
369
+ "init.defaultBranch=athena-review",
370
+ "init",
371
+ "--quiet",
372
+ f"--template={template}",
373
+ "--initial-branch=athena-review",
374
+ str(source),
375
+ temporary_directory=source,
376
+ )
377
+ base_refspec = f"+refs/heads/{base_ref}:refs/athena/base"
378
+ head_refspec = f"+refs/pull/{number}/head:refs/athena/pr/{number}/head"
379
+ _git(
380
+ "-c",
381
+ f"core.hooksPath={hooks}",
382
+ "-c",
383
+ "remote.origin.fetch=",
384
+ "-c",
385
+ "fetch.writeCommitGraph=false",
386
+ "-c",
387
+ "fetch.fsckObjects=true",
388
+ "-c",
389
+ "transfer.fsckObjects=true",
390
+ "fetch",
391
+ "--quiet",
392
+ "--no-tags",
393
+ "--no-write-fetch-head",
394
+ "--no-recurse-submodules",
395
+ "--refmap=",
396
+ repository_url,
397
+ base_refspec,
398
+ head_refspec,
399
+ cwd=source,
400
+ temporary_directory=source,
401
+ )
402
+ _repository_size(source / ".git", maximum_bytes=maximum_bytes)
403
+ _verify_no_promisor_configuration(source)
404
+ if (
405
+ _git("rev-parse", "--is-shallow-repository", cwd=source, capture_output=True)
406
+ != "false"
407
+ ):
408
+ raise RuntimeError("immutable pull-request snapshot requires complete history")
409
+ if _require_commit(source, "refs/athena/base", "fetched base OID") != base_oid:
410
+ raise RuntimeError("fetched base ref does not match the captured base OID")
411
+ if (
412
+ _require_commit(source, f"refs/athena/pr/{number}/head", "fetched head OID")
413
+ != head_oid
414
+ ):
415
+ raise RuntimeError(
416
+ "fetched pull-request ref does not match the captured head OID"
417
+ )
418
+ merge_bases = _git(
419
+ "merge-base",
420
+ "--all",
421
+ base_oid,
422
+ head_oid,
423
+ cwd=source,
424
+ capture_output=True,
425
+ ).splitlines()
426
+ if len(merge_bases) != 1:
427
+ raise RuntimeError(
428
+ "immutable pull-request snapshot requires one unambiguous merge base"
429
+ )
430
+ merge_base = require_commit_oid(merge_bases[0], "immutable merge base")
431
+ tree_oid = _require_commit(source, head_oid, "fetched head OID")
432
+ tree_oid = _git(
433
+ "rev-parse", f"{tree_oid}^{{tree}}", cwd=source, capture_output=True
434
+ )
435
+ if COMMIT_OID.fullmatch(tree_oid) is None:
436
+ raise RuntimeError("Git returned an invalid immutable head tree")
437
+ _git(
438
+ "-c",
439
+ f"core.hooksPath={hooks}",
440
+ "checkout",
441
+ "--quiet",
442
+ "--detach",
443
+ "--no-recurse-submodules",
444
+ head_oid,
445
+ cwd=source,
446
+ temporary_directory=source,
447
+ )
448
+ _repository_size(source, maximum_bytes=maximum_bytes)
449
+ return merge_base, tree_oid
450
+
451
+
452
+ def _bounded_materialize_main(arguments: Sequence[str]) -> int:
453
+ """Materialize inside a bounded user/mount namespace (internal entry).
454
+
455
+ This runs as the child of ``unshare -rm`` on Linux. It mounts a tmpfs
456
+ whose total capacity is the snapshot quota, acquires the snapshot inside
457
+ that bound, copies the verified read-only tree to a host-visible path, and
458
+ prints the result as JSON. Exit 2 means the quota boundary itself could
459
+ not be established; exit 1 means materialization failed.
460
+ """
461
+ parser = argument_parser(description="Bounded snapshot materialization.")
462
+ parser.add_argument("--root", required=True, metavar="ROOT")
463
+ parser.add_argument("--repository-url", required=True, metavar="URL")
464
+ parser.add_argument("--pr-number", required=True, type=int, metavar="NUMBER")
465
+ parser.add_argument("--base-ref", required=True, metavar="BRANCH")
466
+ parser.add_argument("--base-oid", required=True, metavar="BASE_OID")
467
+ parser.add_argument("--head-oid", required=True, metavar="HEAD_OID")
468
+ parser.add_argument("--maximum-bytes", required=True, type=int, metavar="BYTES")
469
+ parsed = parser.parse_args(arguments)
470
+ root = Path(parsed.root).resolve()
471
+ temporary_root = Path(tempfile.gettempdir()).resolve()
472
+ if root.parent != temporary_root or not root.name.startswith("athena-pr-review-"):
473
+ print(
474
+ "refusing to materialize outside the managed temporary directory",
475
+ file=sys.stderr,
476
+ )
477
+ return 1
478
+ try:
479
+ canonical_base = require_commit_oid(parsed.base_oid, "captured base OID")
480
+ canonical_head = require_commit_oid(parsed.head_oid, "captured head OID")
481
+ canonical_base_ref = _require_base_ref(parsed.base_ref)
482
+ bounded = root / "bounded"
483
+ bounded.mkdir()
484
+ if not _mount_tmpfs(bounded, parsed.maximum_bytes):
485
+ print(
486
+ "host cannot enforce the immutable pull-request snapshot size limit",
487
+ file=sys.stderr,
488
+ )
489
+ return 2
490
+ merge_base, tree_oid = _acquire_into(
491
+ bounded,
492
+ repository_url=parsed.repository_url,
493
+ number=parsed.pr_number,
494
+ base_ref=canonical_base_ref,
495
+ base_oid=canonical_base,
496
+ head_oid=canonical_head,
497
+ hooks=root / "empty-hooks",
498
+ template=root / "empty-template",
499
+ maximum_bytes=parsed.maximum_bytes,
500
+ )
501
+ shutil.copytree(bounded, root / "source", symlinks=True)
502
+ _detach_best_effort(bounded)
503
+ _make_read_only(root)
504
+ except (OSError, subprocess.SubprocessError, RuntimeError) as error:
505
+ _detach_best_effort(root / "bounded")
506
+ print(str(error), file=sys.stderr)
507
+ return 1
508
+ print(
509
+ json.dumps(
510
+ {
511
+ "source_path": str(root / "source"),
512
+ "merge_base": merge_base,
513
+ "tree_oid": tree_oid,
514
+ },
515
+ sort_keys=True,
516
+ )
517
+ )
518
+ return 0
519
+
520
+
521
+ def _bounded_materialize(
522
+ root: Path,
523
+ maximum_bytes: int,
524
+ *,
525
+ repository: str,
526
+ number: int,
527
+ base_ref: str,
528
+ base_oid: str,
529
+ head_oid: str,
530
+ ) -> MaterializedSnapshot:
531
+ """Materialize a snapshot inside a bounded Linux user/mount namespace.
532
+
533
+ Spawns this helper under ``unshare -rm`` (rootful within the new namespace
534
+ but unprivileged on the host), where the child mounts a tmpfs whose total
535
+ capacity is the snapshot quota. Every fetch/checkout write is bounded by
536
+ that filesystem; the verified tree is copied to a host-visible read-only
537
+ path before the namespace exits. Fails closed when ``unshare`` or a tmpfs
538
+ mount is unavailable.
539
+ """
540
+ unshare = shutil.which("unshare")
541
+ if unshare is None:
542
+ raise RuntimeError(
543
+ "host cannot enforce the immutable pull-request snapshot size limit"
544
+ )
545
+ command = [
546
+ unshare,
547
+ "-rm",
548
+ "--",
549
+ sys.executable,
550
+ str(Path(__file__).resolve()),
551
+ "--bounded-materialize",
552
+ "--root",
553
+ str(root),
554
+ "--repository-url",
555
+ canonical_repository_url(repository),
556
+ "--pr-number",
557
+ str(number),
558
+ "--base-ref",
559
+ base_ref,
560
+ "--base-oid",
561
+ base_oid,
562
+ "--head-oid",
563
+ head_oid,
564
+ "--maximum-bytes",
565
+ str(maximum_bytes),
566
+ ]
567
+ try:
568
+ result = run_command(
569
+ command,
570
+ capture_output=True,
571
+ text=True,
572
+ check=False,
573
+ timeout=BOUNDED_MATERIALIZE_TIMEOUT_SECONDS,
574
+ )
575
+ except subprocess.TimeoutExpired as error:
576
+ raise RuntimeError(
577
+ "cannot materialize the immutable pull-request snapshot"
578
+ ) from error
579
+ except (OSError, RuntimeError, subprocess.SubprocessError) as error:
580
+ raise RuntimeError(
581
+ "host cannot enforce the immutable pull-request snapshot size limit"
582
+ ) from error
583
+ if result.returncode == 2:
584
+ raise RuntimeError(
585
+ "host cannot enforce the immutable pull-request snapshot size limit"
586
+ )
587
+ if result.returncode != 0:
588
+ raise RuntimeError("cannot materialize the immutable pull-request snapshot")
589
+ try:
590
+ lines = [line for line in result.stdout.splitlines() if line.strip()]
591
+ if not lines:
592
+ raise RuntimeError("cannot materialize the immutable pull-request snapshot")
593
+ record = json.loads(lines[-1])
594
+ if not isinstance(record, dict):
595
+ raise TypeError("cannot materialize the immutable pull-request snapshot")
596
+ source_path = Path(str(record["source_path"]))
597
+ merge_base = require_commit_oid(record["merge_base"], "immutable merge base")
598
+ tree_oid = require_commit_oid(record["tree_oid"], "immutable head tree")
599
+ except (
600
+ KeyError,
601
+ TypeError,
602
+ ValueError,
603
+ json.JSONDecodeError,
604
+ RuntimeError,
605
+ ) as error:
606
+ raise RuntimeError(
607
+ "cannot materialize the immutable pull-request snapshot"
608
+ ) from error
609
+ if source_path != root / "source" or not source_path.is_dir():
610
+ raise RuntimeError("cannot materialize the immutable pull-request snapshot")
611
+ return MaterializedSnapshot(
612
+ root=root, source_path=source_path, merge_base=merge_base, tree_oid=tree_oid
613
+ )
614
+
615
+
616
+ def materialize_snapshot(
617
+ *, repository: str, number: int, base_ref: str, base_oid: str, head_oid: str
618
+ ) -> MaterializedSnapshot:
619
+ """Fetch only a captured base branch and PR head into a fresh repository."""
620
+ canonical_repository = require_github_repository(repository, "GitHub repository")
621
+ if isinstance(number, bool) or not isinstance(number, int) or number < 1:
622
+ raise RuntimeError("pull-request number must be positive")
623
+ canonical_base = require_commit_oid(base_oid, "captured base OID")
624
+ canonical_head = require_commit_oid(head_oid, "captured head OID")
625
+ canonical_base_ref = _require_base_ref(base_ref)
626
+ root = Path(tempfile.mkdtemp(prefix="athena-pr-review-"))
627
+ template = root / "empty-template"
628
+ hooks = root / "empty-hooks"
629
+ template.mkdir()
630
+ hooks.mkdir()
631
+ try:
632
+ maximum_snapshot_bytes = (shutil.disk_usage(root).free * 9) // 10
633
+ if maximum_snapshot_bytes < 1:
634
+ raise RuntimeError(
635
+ "immutable pull-request snapshot has no usable disk space"
636
+ )
637
+ source = _create_quota_volume(root, maximum_snapshot_bytes)
638
+ if source is None:
639
+ return _bounded_materialize(
640
+ root,
641
+ maximum_snapshot_bytes,
642
+ repository=canonical_repository,
643
+ number=number,
644
+ base_ref=canonical_base_ref,
645
+ base_oid=canonical_base,
646
+ head_oid=canonical_head,
647
+ )
648
+ merge_base, tree_oid = _acquire_into(
649
+ source,
650
+ repository_url=canonical_repository_url(canonical_repository),
651
+ number=number,
652
+ base_ref=canonical_base_ref,
653
+ base_oid=canonical_base,
654
+ head_oid=canonical_head,
655
+ hooks=hooks,
656
+ template=template,
657
+ maximum_bytes=maximum_snapshot_bytes,
658
+ )
659
+ _make_read_only(root)
660
+ except (OSError, subprocess.TimeoutExpired):
661
+ remove_snapshot(root)
662
+ raise RuntimeError(
663
+ "cannot materialize the immutable pull-request snapshot"
664
+ ) from None
665
+ except BaseException:
666
+ remove_snapshot(root)
667
+ raise
668
+ return MaterializedSnapshot(
669
+ root=root, source_path=source, merge_base=merge_base, tree_oid=tree_oid
670
+ )
671
+
672
+
673
+ def remove_snapshot(root: Path) -> None:
674
+ """Remove a snapshot that this helper created after host inspection ends."""
675
+ resolved = root.resolve()
676
+ temporary_root = Path(tempfile.gettempdir()).resolve()
677
+ if resolved.parent != temporary_root or not resolved.name.startswith(
678
+ "athena-pr-review-"
679
+ ):
680
+ raise RuntimeError(
681
+ "refusing to remove a snapshot outside the managed temporary directory"
682
+ )
683
+ source = resolved / "source"
684
+ if source.is_mount():
685
+ _detach_volume(source)
686
+
687
+ def make_removable(function: object, path: str, _: object) -> None:
688
+ candidate = Path(path)
689
+ candidate.parent.chmod(0o700)
690
+ if candidate.exists() and not candidate.is_symlink():
691
+ candidate.chmod(0o700)
692
+ if not callable(function):
693
+ raise TypeError("cannot remove the immutable pull-request snapshot")
694
+ function(path)
695
+
696
+ shutil.rmtree(resolved, onexc=make_removable)
697
+
698
+
699
+ def main(argv: Sequence[str] | None = None) -> int:
700
+ command_arguments = list(sys.argv[1:] if argv is None else argv)
701
+ if "--bounded-materialize" in command_arguments:
702
+ child_arguments = [
703
+ argument
704
+ for argument in command_arguments
705
+ if argument != "--bounded-materialize"
706
+ ]
707
+ return _bounded_materialize_main(child_arguments)
708
+ parser = argument_parser(description=__doc__)
709
+ parser.add_argument("--repository", required=True, metavar="OWNER/REPOSITORY")
710
+ parser.add_argument("--pr-number", required=True, type=int, metavar="NUMBER")
711
+ parser.add_argument("--base-ref", required=True, metavar="BRANCH")
712
+ parser.add_argument("--base-oid", required=True, metavar="BASE_OID")
713
+ parser.add_argument("--head-oid", required=True, metavar="HEAD_OID")
714
+ arguments = parser.parse_args(command_arguments)
715
+ try:
716
+ snapshot = materialize_snapshot(
717
+ repository=arguments.repository,
718
+ number=arguments.pr_number,
719
+ base_ref=arguments.base_ref,
720
+ base_oid=arguments.base_oid,
721
+ head_oid=arguments.head_oid,
722
+ )
723
+ except RuntimeError as error:
724
+ print(error, file=sys.stderr)
725
+ return 1
726
+ print(json.dumps(snapshot.as_json(), sort_keys=True))
727
+ return 0
728
+
729
+
730
+ if __name__ == "__main__":
731
+ raise SystemExit(main())