@arbiterforge/ca-pi 0.6.0

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 (206) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +558 -0
  3. package/package.json +35 -0
  4. package/plugins/ca-pi/CHANGELOG.md +1030 -0
  5. package/plugins/ca-pi/COMMANDS.md +90 -0
  6. package/plugins/ca-pi/ORCHESTRATOR.md +159 -0
  7. package/plugins/ca-pi/SKILLS.md +47 -0
  8. package/plugins/ca-pi/SPRINT.md +142 -0
  9. package/plugins/ca-pi/agents/INDEX.md +31 -0
  10. package/plugins/ca-pi/agents/architecture-drift-reviewer.md +86 -0
  11. package/plugins/ca-pi/agents/auth-crypto-reviewer.md +60 -0
  12. package/plugins/ca-pi/agents/backend-author.md +60 -0
  13. package/plugins/ca-pi/agents/checkpoint-aggregator.md +111 -0
  14. package/plugins/ca-pi/agents/coverage-auditor.md +71 -0
  15. package/plugins/ca-pi/agents/decision-challenger.md +116 -0
  16. package/plugins/ca-pi/agents/dependency-reviewer.md +79 -0
  17. package/plugins/ca-pi/agents/design-quality-reviewer.md +80 -0
  18. package/plugins/ca-pi/agents/finding-triage.md +86 -0
  19. package/plugins/ca-pi/agents/frontend-author.md +64 -0
  20. package/plugins/ca-pi/agents/grader.md +173 -0
  21. package/plugins/ca-pi/agents/infra-author.md +64 -0
  22. package/plugins/ca-pi/agents/map-deps.md +35 -0
  23. package/plugins/ca-pi/agents/map-structure.md +37 -0
  24. package/plugins/ca-pi/agents/migration-reviewer.md +65 -0
  25. package/plugins/ca-pi/agents/scout.md +127 -0
  26. package/plugins/ca-pi/agents/security-reviewer.md +72 -0
  27. package/plugins/ca-pi/agents/tribunal-lens-reviewer.md +65 -0
  28. package/plugins/ca-pi/extensions/codearbiter-child.js +1885 -0
  29. package/plugins/ca-pi/extensions/codearbiter.js +9802 -0
  30. package/plugins/ca-pi/generated/command-catalog.json +197 -0
  31. package/plugins/ca-pi/generated/roles.json +213 -0
  32. package/plugins/ca-pi/helpers/windows-supervisor.js +205 -0
  33. package/plugins/ca-pi/hooks/_activationlib.py +196 -0
  34. package/plugins/ca-pi/hooks/_arbiterstatelib.py +208 -0
  35. package/plugins/ca-pi/hooks/_babysitlib.py +76 -0
  36. package/plugins/ca-pi/hooks/_bashguardlib.py +1667 -0
  37. package/plugins/ca-pi/hooks/_boxlib.py +131 -0
  38. package/plugins/ca-pi/hooks/_colorlib.py +304 -0
  39. package/plugins/ca-pi/hooks/_durabilitylib.py +186 -0
  40. package/plugins/ca-pi/hooks/_entrylib.py +41 -0
  41. package/plugins/ca-pi/hooks/_fmtlib.py +161 -0
  42. package/plugins/ca-pi/hooks/_gitexec.py +45 -0
  43. package/plugins/ca-pi/hooks/_githooks.py +920 -0
  44. package/plugins/ca-pi/hooks/_gitlib.py +110 -0
  45. package/plugins/ca-pi/hooks/_hooklib.py +595 -0
  46. package/plugins/ca-pi/hooks/_host.py +115 -0
  47. package/plugins/ca-pi/hooks/_intentlib.py +242 -0
  48. package/plugins/ca-pi/hooks/_ledgerlib.py +1035 -0
  49. package/plugins/ca-pi/hooks/_metricslib.py +709 -0
  50. package/plugins/ca-pi/hooks/_pathnorm.py +74 -0
  51. package/plugins/ca-pi/hooks/_planfilelib.py +664 -0
  52. package/plugins/ca-pi/hooks/_previewlib.py +193 -0
  53. package/plugins/ca-pi/hooks/_protectedlib.py +312 -0
  54. package/plugins/ca-pi/hooks/_protectedstatelib.py +411 -0
  55. package/plugins/ca-pi/hooks/_provenancelib.py +971 -0
  56. package/plugins/ca-pi/hooks/_prunelib.py +1398 -0
  57. package/plugins/ca-pi/hooks/_prunepolicy.py +235 -0
  58. package/plugins/ca-pi/hooks/_readinjectlib.py +1080 -0
  59. package/plugins/ca-pi/hooks/_releaselib.py +2657 -0
  60. package/plugins/ca-pi/hooks/_scopelib.py +262 -0
  61. package/plugins/ca-pi/hooks/_segmentslib.py +278 -0
  62. package/plugins/ca-pi/hooks/_sensitivelib.py +270 -0
  63. package/plugins/ca-pi/hooks/_sessionlib.py +78 -0
  64. package/plugins/ca-pi/hooks/_sloplib.py +244 -0
  65. package/plugins/ca-pi/hooks/_standuplib.py +214 -0
  66. package/plugins/ca-pi/hooks/_subagentslib.py +219 -0
  67. package/plugins/ca-pi/hooks/_taskboardlib.py +1088 -0
  68. package/plugins/ca-pi/hooks/_updatelib.py +278 -0
  69. package/plugins/ca-pi/hooks/babysit.py +47 -0
  70. package/plugins/ca-pi/hooks/boardsync.py +129 -0
  71. package/plugins/ca-pi/hooks/doctor.py +420 -0
  72. package/plugins/ca-pi/hooks/git-enforce.py +325 -0
  73. package/plugins/ca-pi/hooks/hostapi.py +460 -0
  74. package/plugins/ca-pi/hooks/init-codearbiter.py +225 -0
  75. package/plugins/ca-pi/hooks/metrics.py +62 -0
  76. package/plugins/ca-pi/hooks/migration-pass.py +129 -0
  77. package/plugins/ca-pi/hooks/pi-bridge.py +543 -0
  78. package/plugins/ca-pi/hooks/post-write-edit.py +231 -0
  79. package/plugins/ca-pi/hooks/pre-bash.py +90 -0
  80. package/plugins/ca-pi/hooks/pre-edit.py +284 -0
  81. package/plugins/ca-pi/hooks/pre-read.py +81 -0
  82. package/plugins/ca-pi/hooks/pre-write.py +217 -0
  83. package/plugins/ca-pi/hooks/preview.py +69 -0
  84. package/plugins/ca-pi/hooks/prune-transcript.py +232 -0
  85. package/plugins/ca-pi/hooks/releasehash.py +216 -0
  86. package/plugins/ca-pi/hooks/security-pass.py +139 -0
  87. package/plugins/ca-pi/hooks/session-start.py +1218 -0
  88. package/plugins/ca-pi/hooks/statusline.py +736 -0
  89. package/plugins/ca-pi/hooks/taskwrite.py +351 -0
  90. package/plugins/ca-pi/hooks/update-refresh.py +51 -0
  91. package/plugins/ca-pi/hooks/wire-statusline.py +435 -0
  92. package/plugins/ca-pi/includes/anti-slop-design/INDEX.md +55 -0
  93. package/plugins/ca-pi/includes/anti-slop-design/color.md +43 -0
  94. package/plugins/ca-pi/includes/anti-slop-design/core.md +244 -0
  95. package/plugins/ca-pi/includes/anti-slop-design/images.md +32 -0
  96. package/plugins/ca-pi/includes/anti-slop-design/layout.md +45 -0
  97. package/plugins/ca-pi/includes/anti-slop-design/medium-cli.md +39 -0
  98. package/plugins/ca-pi/includes/anti-slop-design/medium-dataviz.md +43 -0
  99. package/plugins/ca-pi/includes/anti-slop-design/medium-diagram.md +35 -0
  100. package/plugins/ca-pi/includes/anti-slop-design/medium-documents.md +70 -0
  101. package/plugins/ca-pi/includes/anti-slop-design/medium-slides.md +30 -0
  102. package/plugins/ca-pi/includes/anti-slop-design/medium-web.md +39 -0
  103. package/plugins/ca-pi/includes/anti-slop-design/typography.md +51 -0
  104. package/plugins/ca-pi/includes/author-tdd-workflow.md +14 -0
  105. package/plugins/ca-pi/includes/compaction-charter.md +16 -0
  106. package/plugins/ca-pi/includes/cut-docs.md +16 -0
  107. package/plugins/ca-pi/includes/dev-mode.md +30 -0
  108. package/plugins/ca-pi/includes/farm.md +237 -0
  109. package/plugins/ca-pi/includes/fresh-verification.md +14 -0
  110. package/plugins/ca-pi/includes/harvest.md +69 -0
  111. package/plugins/ca-pi/includes/maturity-coverage.md +102 -0
  112. package/plugins/ca-pi/includes/pi-host-notes.md +69 -0
  113. package/plugins/ca-pi/includes/redirect.md +69 -0
  114. package/plugins/ca-pi/includes/reference-map.md +22 -0
  115. package/plugins/ca-pi/includes/review-matrix.md +14 -0
  116. package/plugins/ca-pi/includes/reviewer-contract.md +53 -0
  117. package/plugins/ca-pi/includes/routing-table.md +47 -0
  118. package/plugins/ca-pi/includes/security-gate-record.md +22 -0
  119. package/plugins/ca-pi/includes/smarts/core.md +90 -0
  120. package/plugins/ca-pi/includes/smarts/decision-log-format.md +56 -0
  121. package/plugins/ca-pi/routines/INDEX.md +32 -0
  122. package/plugins/ca-pi/routines/brainstorming/SKILL.md +122 -0
  123. package/plugins/ca-pi/routines/commit-gate/SKILL.md +151 -0
  124. package/plugins/ca-pi/routines/context-check/SKILL.md +85 -0
  125. package/plugins/ca-pi/routines/context-creation/SKILL.md +171 -0
  126. package/plugins/ca-pi/routines/crypto-compliance/SKILL.md +41 -0
  127. package/plugins/ca-pi/routines/debug/SKILL.md +99 -0
  128. package/plugins/ca-pi/routines/decision-lifecycle/SKILL.md +104 -0
  129. package/plugins/ca-pi/routines/decision-lifecycle/references/adr-template.md +74 -0
  130. package/plugins/ca-pi/routines/decision-variance/SKILL.md +147 -0
  131. package/plugins/ca-pi/routines/decompose/SKILL.md +168 -0
  132. package/plugins/ca-pi/routines/dispatching-parallel-agents/SKILL.md +76 -0
  133. package/plugins/ca-pi/routines/executing-plans/SKILL.md +83 -0
  134. package/plugins/ca-pi/routines/finishing-a-development-branch/SKILL.md +91 -0
  135. package/plugins/ca-pi/routines/post-merge-cleanup/SKILL.md +233 -0
  136. package/plugins/ca-pi/routines/refactor/SKILL.md +91 -0
  137. package/plugins/ca-pi/routines/release/SKILL.md +315 -0
  138. package/plugins/ca-pi/routines/secret-handling/SKILL.md +67 -0
  139. package/plugins/ca-pi/routines/security-architecture/SKILL.md +63 -0
  140. package/plugins/ca-pi/routines/skill-author/SKILL.md +108 -0
  141. package/plugins/ca-pi/routines/skill-author/references/skill-template.md +58 -0
  142. package/plugins/ca-pi/routines/subagent-driven-development/SKILL.md +149 -0
  143. package/plugins/ca-pi/routines/subagent-driven-development/references/farm-dispatch.md +145 -0
  144. package/plugins/ca-pi/routines/tdd/SKILL.md +139 -0
  145. package/plugins/ca-pi/routines/tribunal/SKILL.md +109 -0
  146. package/plugins/ca-pi/routines/tribunal/references/ai-markers.md +29 -0
  147. package/plugins/ca-pi/routines/tribunal/references/cost-and-models.md +64 -0
  148. package/plugins/ca-pi/routines/tribunal/references/finding-record.md +27 -0
  149. package/plugins/ca-pi/routines/tribunal/references/issue-filing.md +47 -0
  150. package/plugins/ca-pi/routines/tribunal/references/lenses/appsec.md +22 -0
  151. package/plugins/ca-pi/routines/tribunal/references/lenses/architecture.md +23 -0
  152. package/plugins/ca-pi/routines/tribunal/references/lenses/coverage.md +20 -0
  153. package/plugins/ca-pi/routines/tribunal/references/lenses/infra.md +24 -0
  154. package/plugins/ca-pi/routines/tribunal/references/lenses/migration.md +22 -0
  155. package/plugins/ca-pi/routines/tribunal/references/lenses/observability.md +21 -0
  156. package/plugins/ca-pi/routines/tribunal/references/lenses/performance.md +22 -0
  157. package/plugins/ca-pi/routines/tribunal/references/lenses/reliability.md +23 -0
  158. package/plugins/ca-pi/routines/tribunal/references/lenses/secrets-supply.md +22 -0
  159. package/plugins/ca-pi/routines/tribunal/references/lenses/test-fidelity.md +24 -0
  160. package/plugins/ca-pi/routines/tribunal/references/lenses/typesafety.md +21 -0
  161. package/plugins/ca-pi/routines/tribunal/references/report.md +19 -0
  162. package/plugins/ca-pi/routines/tribunal/references/schemas.md +58 -0
  163. package/plugins/ca-pi/routines/tribunal/references/telemetry.md +28 -0
  164. package/plugins/ca-pi/routines/tribunal/references/triage.md +53 -0
  165. package/plugins/ca-pi/routines/using-git-worktrees/SKILL.md +85 -0
  166. package/plugins/ca-pi/routines/writing-plans/SKILL.md +129 -0
  167. package/plugins/ca-pi/routines/writing-plans/references/farm-plan.md +50 -0
  168. package/plugins/ca-pi/skills/ca-add-dep/SKILL.md +88 -0
  169. package/plugins/ca-pi/skills/ca-adr/SKILL.md +30 -0
  170. package/plugins/ca-pi/skills/ca-adr-status/SKILL.md +30 -0
  171. package/plugins/ca-pi/skills/ca-arbiter/SKILL.md +36 -0
  172. package/plugins/ca-pi/skills/ca-audit/SKILL.md +51 -0
  173. package/plugins/ca-pi/skills/ca-btw/SKILL.md +23 -0
  174. package/plugins/ca-pi/skills/ca-checkpoint/SKILL.md +50 -0
  175. package/plugins/ca-pi/skills/ca-chore/SKILL.md +58 -0
  176. package/plugins/ca-pi/skills/ca-cleanup/SKILL.md +55 -0
  177. package/plugins/ca-pi/skills/ca-commands/SKILL.md +21 -0
  178. package/plugins/ca-pi/skills/ca-commit/SKILL.md +27 -0
  179. package/plugins/ca-pi/skills/ca-conflict/SKILL.md +61 -0
  180. package/plugins/ca-pi/skills/ca-context-check/SKILL.md +32 -0
  181. package/plugins/ca-pi/skills/ca-create-context/SKILL.md +32 -0
  182. package/plugins/ca-pi/skills/ca-debug/SKILL.md +42 -0
  183. package/plugins/ca-pi/skills/ca-decompose/SKILL.md +30 -0
  184. package/plugins/ca-pi/skills/ca-dev/SKILL.md +42 -0
  185. package/plugins/ca-pi/skills/ca-doctor/SKILL.md +44 -0
  186. package/plugins/ca-pi/skills/ca-feature/SKILL.md +105 -0
  187. package/plugins/ca-pi/skills/ca-fix/SKILL.md +42 -0
  188. package/plugins/ca-pi/skills/ca-init/SKILL.md +56 -0
  189. package/plugins/ca-pi/skills/ca-metrics/SKILL.md +80 -0
  190. package/plugins/ca-pi/skills/ca-new-skill/SKILL.md +34 -0
  191. package/plugins/ca-pi/skills/ca-override/SKILL.md +72 -0
  192. package/plugins/ca-pi/skills/ca-pr/SKILL.md +61 -0
  193. package/plugins/ca-pi/skills/ca-preview/SKILL.md +86 -0
  194. package/plugins/ca-pi/skills/ca-prune/SKILL.md +100 -0
  195. package/plugins/ca-pi/skills/ca-reconcile/SKILL.md +43 -0
  196. package/plugins/ca-pi/skills/ca-refactor/SKILL.md +43 -0
  197. package/plugins/ca-pi/skills/ca-release/SKILL.md +57 -0
  198. package/plugins/ca-pi/skills/ca-review/SKILL.md +63 -0
  199. package/plugins/ca-pi/skills/ca-spike/SKILL.md +41 -0
  200. package/plugins/ca-pi/skills/ca-sprint/SKILL.md +44 -0
  201. package/plugins/ca-pi/skills/ca-standup/SKILL.md +112 -0
  202. package/plugins/ca-pi/skills/ca-status/SKILL.md +56 -0
  203. package/plugins/ca-pi/skills/ca-task/SKILL.md +61 -0
  204. package/plugins/ca-pi/skills/ca-threat-model/SKILL.md +45 -0
  205. package/plugins/ca-pi/skills/ca-tribunal/SKILL.md +44 -0
  206. package/plugins/ca-pi/skills/ca-watch/SKILL.md +78 -0
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env python3
2
+ # codeArbiter — statusline display-formatting helpers (extracted from
3
+ # statusline.py, architecture-004).
4
+ #
5
+ # Owns the small pure formatters the box uses to turn raw numbers/paths/durations
6
+ # into the compact strings it renders: token/USD formatting, a home-relative path
7
+ # abbreviator, human-friendly durations, ISO-timestamp parsing, and the per-call
8
+ # burn sparkline. Carries no box-layout or segment-assembly logic.
9
+ #
10
+ # Design principles (mirroring _ledgerlib.py):
11
+ # - Stdlib only; no third-party imports ever.
12
+ # - Zero side effects at import time.
13
+ # - Pure functions, fully testable with synthetic input.
14
+ # - Never raise on malformed input — degrade to a safe default.
15
+ #
16
+ # Public API:
17
+ # fmt_tok(n) -> str compact token count (999 / 1.5K / 1.0M)
18
+ # usd(c) -> str whole-dollar-above-$100 USD format
19
+ # usd_fine(c) -> str finer-grained USD format (statusline cost cells)
20
+ # short_path(p) -> str home-relative, middle-truncated path
21
+ # human_dur(secs) -> str compact duration (3h12m, 4d6h, 45m, 30s)
22
+ # parse_iso(s) -> float|None ISO-8601 (tolerating trailing Z) -> epoch seconds
23
+ # to_epoch(v) -> float|None ISO string OR numeric (s/ms) epoch -> epoch seconds
24
+ # sparkline(vals, grad=True) -> str colored sparkline of recent values
25
+
26
+ import os
27
+ from datetime import datetime, timezone
28
+
29
+ try:
30
+ import _colorlib
31
+ _RESET, _V2, _SPARK, _gradient_h = (_colorlib.RESET, _colorlib.V2,
32
+ _colorlib.SPARK, _colorlib.gradient_h)
33
+ except Exception: # pragma: no cover — never let an import break the statusline
34
+ _colorlib = None
35
+ _RESET, _V2, _SPARK = "", "", "▁▂▃▄▅▆▇█"
36
+
37
+ def _gradient_h(text, width, c_from=(120, 80, 200), c_to=(205, 140, 255)):
38
+ return text
39
+
40
+
41
+ def sync_palette():
42
+ """Refresh captured color exports after _colorlib runtime activation."""
43
+ global _RESET, _V2, _gradient_h
44
+ if _colorlib is not None:
45
+ _RESET, _V2, _gradient_h = _colorlib.RESET, _colorlib.V2, _colorlib.gradient_h
46
+
47
+
48
+ def num(x, default=0.0):
49
+ """Coerce any host value to float; tolerate strings, None, and containers."""
50
+ try:
51
+ return float(x)
52
+ except (TypeError, ValueError):
53
+ return default
54
+
55
+
56
+ def fmt_tok(n):
57
+ n = num(n)
58
+ if n >= 999_500: # round into M before the K branch can print 1000.0K
59
+ return f"{n/1_000_000:.1f}M"
60
+ if n >= 1000:
61
+ return f"{n/1000:.1f}K"
62
+ return str(int(n))
63
+
64
+
65
+ def usd(c):
66
+ c = num(c)
67
+ return f"${c:.0f}" if c >= 100 else f"${c:.2f}"
68
+
69
+
70
+ def usd_fine(c):
71
+ c = num(c)
72
+ if c >= 100:
73
+ return f"${c:.0f}"
74
+ if c >= 10:
75
+ return f"${c:.1f}"
76
+ if 0 < c < 0.01: # don't render a measured nonzero cost as $0.00
77
+ return "<$.01"
78
+ return f"${c:.2f}"
79
+
80
+
81
+ def short_path(p):
82
+ if not p:
83
+ return "?"
84
+ p = str(p).replace("\\", "/").rstrip("/")
85
+ unc = p.startswith("//") # UNC share: \\server\share -> //server/share
86
+ home = os.path.expanduser("~").replace("\\", "/").rstrip("/")
87
+ if p == home:
88
+ return "~"
89
+ if home and p.startswith(home + "/"):
90
+ p = "~" + p[len(home):]
91
+ parts = [x for x in p.split("/") if x]
92
+ if not parts:
93
+ return "//" if unc else "/"
94
+ if unc:
95
+ # keep //server/share intact, abbreviate any deep middle
96
+ if len(parts) <= 3:
97
+ return "//" + "/".join(parts)
98
+ return "//" + "/".join(parts[:2] + [s[0] for s in parts[2:-2]] + parts[-2:])
99
+ drive = parts[0].endswith(":") or parts[0] == "~"
100
+ prefix = "" if drive else "/"
101
+ if len(parts) <= 3:
102
+ return prefix + "/".join(parts)
103
+ return prefix + "/".join([parts[0]] + [s[0] for s in parts[1:-2]] + parts[-2:])
104
+
105
+
106
+ def human_dur(secs):
107
+ """Compact human duration: 3h12m, 4d6h, 45m, 30s."""
108
+ secs = int(max(0, secs))
109
+ if secs < 60:
110
+ return f"{secs}s"
111
+ m = secs // 60
112
+ if m < 60:
113
+ return f"{m}m"
114
+ h = m // 60
115
+ m = m % 60
116
+ if h < 24:
117
+ return f"{h}h{m:02d}m" if m else f"{h}h"
118
+ d = h // 24
119
+ h = h % 24
120
+ return f"{d}d{h}h" if h else f"{d}d"
121
+
122
+
123
+ def parse_iso(s):
124
+ """Parse an ISO-8601 timestamp (tolerating a trailing Z) to epoch seconds."""
125
+ if not s or not isinstance(s, str):
126
+ return None
127
+ try:
128
+ dt = datetime.fromisoformat(s.strip().replace("Z", "+00:00"))
129
+ if dt.tzinfo is None:
130
+ dt = dt.replace(tzinfo=timezone.utc) # naive timestamps are UTC, not local
131
+ return dt.timestamp()
132
+ except Exception:
133
+ return None
134
+
135
+
136
+ def to_epoch(v):
137
+ """A reset timestamp may arrive as an ISO-8601 string OR a numeric epoch
138
+ (seconds or milliseconds). Normalize to epoch seconds; None if unusable."""
139
+ if v is None or isinstance(v, bool):
140
+ return None
141
+ if isinstance(v, (int, float)):
142
+ v = float(v)
143
+ if v > 1e12: # milliseconds
144
+ v /= 1000.0
145
+ return v if v > 0 else None
146
+ return parse_iso(v)
147
+
148
+
149
+ def sparkline(vals, grad=True):
150
+ vals = [num(v, None) for v in vals]
151
+ vals = [v for v in vals if v is not None]
152
+ if len(vals) < 2:
153
+ return ""
154
+ lo, hi = min(vals), max(vals)
155
+ span = hi - lo
156
+ chars = []
157
+ for v in vals:
158
+ idx = 3 if span <= 0 else int((v - lo) / span * (len(_SPARK) - 1) + 0.5)
159
+ chars.append(_SPARK[max(0, min(len(_SPARK) - 1, idx))])
160
+ s = "".join(chars)
161
+ return _gradient_h(s, len(s)) if grad else f"{_V2}{s}{_RESET}"
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+ # codeArbiter — optional absolute executable identities for shared Git helpers.
3
+ #
4
+ # Claude and Codex retain their historical bare-executable behavior when the
5
+ # variables are absent. The Pi bridge supplies validated absolute identities;
6
+ # every shared Git subprocess then consumes the same identity, and the managed
7
+ # Git hook carries it into the later hook process.
8
+ #
9
+ # Public API:
10
+ # trusted_git_executable() -> str|None validated CODEARBITER_GIT_EXECUTABLE, or
11
+ # None if unset
12
+ # trusted_python_executable() -> str|None validated CODEARBITER_PYTHON_EXECUTABLE, or
13
+ # None if unset
14
+ # git_executable() -> str trusted_git_executable() or the bare "git"
15
+ # fallback
16
+
17
+ import os
18
+
19
+
20
+ GIT_ENV = "CODEARBITER_GIT_EXECUTABLE"
21
+ PYTHON_ENV = "CODEARBITER_PYTHON_EXECUTABLE"
22
+
23
+
24
+ def _trusted_environment_path(name):
25
+ value = os.environ.get(name)
26
+ if not value:
27
+ return None
28
+ if not os.path.isabs(value):
29
+ raise RuntimeError(f"{name} must be absolute")
30
+ canonical = os.path.realpath(value)
31
+ if not os.path.isfile(canonical):
32
+ raise RuntimeError(f"{name} is unavailable")
33
+ return canonical
34
+
35
+
36
+ def trusted_git_executable():
37
+ return _trusted_environment_path(GIT_ENV)
38
+
39
+
40
+ def trusted_python_executable():
41
+ return _trusted_environment_path(PYTHON_ENV)
42
+
43
+
44
+ def git_executable():
45
+ return trusted_git_executable() or "git"