lgit-cli 3.7.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.
- lgit/__init__.py +75 -0
- lgit/__main__.py +8 -0
- lgit/analysis.py +326 -0
- lgit/api.py +1077 -0
- lgit/cache.py +338 -0
- lgit/changelog.py +523 -0
- lgit/cli.py +1104 -0
- lgit/compose.py +2110 -0
- lgit/config.py +437 -0
- lgit/diffing.py +384 -0
- lgit/errors.py +137 -0
- lgit/git.py +852 -0
- lgit/map_reduce.py +508 -0
- lgit/markdown_output.py +709 -0
- lgit/models.py +924 -0
- lgit/normalization.py +411 -0
- lgit/patch.py +784 -0
- lgit/profile.py +426 -0
- lgit/py.typed +0 -0
- lgit/repo.py +287 -0
- lgit/resources/__init__.py +1 -0
- lgit/resources/commit_types.json +242 -0
- lgit/resources/prompts/analysis/default.md +237 -0
- lgit/resources/prompts/analysis/markdown.md +112 -0
- lgit/resources/prompts/changelog/default.md +89 -0
- lgit/resources/prompts/changelog/markdown.md +60 -0
- lgit/resources/prompts/compose-bind/default.md +40 -0
- lgit/resources/prompts/compose-bind/markdown.md +41 -0
- lgit/resources/prompts/compose-intent/default.md +63 -0
- lgit/resources/prompts/compose-intent/markdown.md +59 -0
- lgit/resources/prompts/fast/default.md +46 -0
- lgit/resources/prompts/fast/markdown.md +51 -0
- lgit/resources/prompts/map/default.md +67 -0
- lgit/resources/prompts/map/markdown.md +63 -0
- lgit/resources/prompts/reduce/default.md +81 -0
- lgit/resources/prompts/reduce/markdown.md +68 -0
- lgit/resources/prompts/summary/default.md +74 -0
- lgit/resources/prompts/summary/markdown.md +77 -0
- lgit/resources/validation_data.json +1 -0
- lgit/rewrite.py +392 -0
- lgit/style.py +295 -0
- lgit/templates.py +385 -0
- lgit/testing/__init__.py +62 -0
- lgit/testing/compare.py +57 -0
- lgit/testing/fixture.py +386 -0
- lgit/testing/report.py +201 -0
- lgit/testing/runner.py +256 -0
- lgit/tokens.py +90 -0
- lgit/validation.py +545 -0
- lgit_cli-3.7.0.dist-info/METADATA +288 -0
- lgit_cli-3.7.0.dist-info/RECORD +54 -0
- lgit_cli-3.7.0.dist-info/WHEEL +4 -0
- lgit_cli-3.7.0.dist-info/entry_points.txt +2 -0
- lgit_cli-3.7.0.dist-info/licenses/LICENSE +21 -0
lgit/profile.py
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
"""JSONL profiling, trace, progress, and timing helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from types import TracebackType
|
|
15
|
+
from typing import Any, Self
|
|
16
|
+
|
|
17
|
+
TARGET = "lgit"
|
|
18
|
+
|
|
19
|
+
_TRACE_LOCK = threading.Lock()
|
|
20
|
+
_TRACE: TraceGuard | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(slots=True)
|
|
24
|
+
class TraceGuard:
|
|
25
|
+
"""Own an open JSONL trace file and flush events on close."""
|
|
26
|
+
|
|
27
|
+
path: Path
|
|
28
|
+
_file: Any
|
|
29
|
+
_closed: bool = False
|
|
30
|
+
|
|
31
|
+
def close(self) -> None:
|
|
32
|
+
"""Flush and close the trace file."""
|
|
33
|
+
|
|
34
|
+
global _TRACE
|
|
35
|
+
if self._closed:
|
|
36
|
+
return
|
|
37
|
+
_write_event("trace_stopped", path=str(self.path), pid=os.getpid())
|
|
38
|
+
with _TRACE_LOCK:
|
|
39
|
+
self._file.flush()
|
|
40
|
+
self._file.close()
|
|
41
|
+
self._closed = True
|
|
42
|
+
if _TRACE is self:
|
|
43
|
+
_TRACE = None
|
|
44
|
+
|
|
45
|
+
def __enter__(self) -> Self:
|
|
46
|
+
"""Return this active guard."""
|
|
47
|
+
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def __exit__(
|
|
51
|
+
self,
|
|
52
|
+
exc_type: type[BaseException] | None,
|
|
53
|
+
exc: BaseException | None,
|
|
54
|
+
tb: TracebackType | None,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Close the trace file when leaving a context."""
|
|
57
|
+
|
|
58
|
+
self.close()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True, slots=True)
|
|
62
|
+
class TimingPhase:
|
|
63
|
+
"""One named phase duration in a finalized timing report."""
|
|
64
|
+
|
|
65
|
+
phase: str
|
|
66
|
+
duration_ms: float
|
|
67
|
+
share_pct: float = 0.0
|
|
68
|
+
|
|
69
|
+
def to_dict(self) -> dict[str, float | str]:
|
|
70
|
+
"""Return the Rust-compatible JSON shape for this phase."""
|
|
71
|
+
|
|
72
|
+
return {"phase": self.phase, "duration_ms": self.duration_ms, "share_pct": self.share_pct}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True, slots=True)
|
|
76
|
+
class TimingReport:
|
|
77
|
+
"""Final timing report matching the Rust ``TimingReport`` JSON shape."""
|
|
78
|
+
|
|
79
|
+
total_ms: float
|
|
80
|
+
phases: list[TimingPhase]
|
|
81
|
+
|
|
82
|
+
def to_dict(self) -> dict[str, Any]:
|
|
83
|
+
"""Return the Rust-compatible JSON shape for this report."""
|
|
84
|
+
|
|
85
|
+
return {"total_ms": self.total_ms, "phases": [phase.to_dict() for phase in self.phases]}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(slots=True)
|
|
89
|
+
class TimingCollector:
|
|
90
|
+
"""Collect named phase timings and finalize share percentages at the end."""
|
|
91
|
+
|
|
92
|
+
enabled: bool = True
|
|
93
|
+
_start: float = field(default_factory=time.perf_counter)
|
|
94
|
+
phases: list[TimingPhase] = field(default_factory=list)
|
|
95
|
+
|
|
96
|
+
def record(self, phase: str, seconds: float) -> None:
|
|
97
|
+
"""Record one phase duration, preserving Rust rounding and trace events."""
|
|
98
|
+
|
|
99
|
+
record_timing(self, phase, seconds)
|
|
100
|
+
|
|
101
|
+
def finalize(self, total_seconds: float | None = None) -> TimingReport:
|
|
102
|
+
"""Build a timing report, using elapsed collector lifetime by default."""
|
|
103
|
+
|
|
104
|
+
return finalize_timings(self, total_seconds)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(slots=True)
|
|
108
|
+
class ProfileSection:
|
|
109
|
+
"""Synchronous and asynchronous context manager for a traced section."""
|
|
110
|
+
|
|
111
|
+
name: str
|
|
112
|
+
collector: TimingCollector | None = None
|
|
113
|
+
_start: float = 0.0
|
|
114
|
+
|
|
115
|
+
def __enter__(self) -> Self:
|
|
116
|
+
"""Enter a synchronous profiling section."""
|
|
117
|
+
|
|
118
|
+
self._start = time.perf_counter()
|
|
119
|
+
_write_event("section_started", section=self.name)
|
|
120
|
+
return self
|
|
121
|
+
|
|
122
|
+
def __exit__(
|
|
123
|
+
self,
|
|
124
|
+
exc_type: type[BaseException] | None,
|
|
125
|
+
exc: BaseException | None,
|
|
126
|
+
tb: TracebackType | None,
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Leave a synchronous profiling section and record elapsed time."""
|
|
129
|
+
|
|
130
|
+
self._finish(exc)
|
|
131
|
+
|
|
132
|
+
async def __aenter__(self) -> Self:
|
|
133
|
+
"""Enter an asynchronous profiling section."""
|
|
134
|
+
|
|
135
|
+
self._start = time.perf_counter()
|
|
136
|
+
_write_event("section_started", section=self.name)
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
async def __aexit__(
|
|
140
|
+
self,
|
|
141
|
+
exc_type: type[BaseException] | None,
|
|
142
|
+
exc: BaseException | None,
|
|
143
|
+
tb: TracebackType | None,
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Leave an asynchronous profiling section and record elapsed time."""
|
|
146
|
+
|
|
147
|
+
self._finish(exc)
|
|
148
|
+
|
|
149
|
+
def _finish(self, exc: BaseException | None) -> None:
|
|
150
|
+
seconds = time.perf_counter() - self._start if self._start else 0.0
|
|
151
|
+
fields: dict[str, Any] = {
|
|
152
|
+
"section": self.name,
|
|
153
|
+
"elapsed_ms": seconds * 1000.0,
|
|
154
|
+
"elapsed_us": _duration_us(seconds),
|
|
155
|
+
}
|
|
156
|
+
if exc is not None:
|
|
157
|
+
fields["error"] = f"{type(exc).__name__}: {exc}"
|
|
158
|
+
_write_event("section_finished", **fields)
|
|
159
|
+
if self.collector is not None:
|
|
160
|
+
record_timing(self.collector, self.name, seconds)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def env_flag_value_enabled(value: str | None) -> bool:
|
|
164
|
+
"""Return Rust-compatible truthiness for LLM_GIT_* boolean env values."""
|
|
165
|
+
|
|
166
|
+
if value is None:
|
|
167
|
+
return False
|
|
168
|
+
return value.strip().lower() not in {"", "0", "false", "no", "off"}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def env_flag_enabled(name: str) -> bool:
|
|
172
|
+
"""Return true when environment variable ``name`` is set to an enabled value."""
|
|
173
|
+
|
|
174
|
+
return env_flag_value_enabled(os.environ.get(name))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def trace_enabled() -> bool:
|
|
178
|
+
"""Return true when ``LLM_GIT_TRACE`` enables API trace logging."""
|
|
179
|
+
|
|
180
|
+
return env_flag_enabled("LLM_GIT_TRACE")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def progress_enabled() -> bool:
|
|
184
|
+
"""Return true when LLM progress lines should be printed."""
|
|
185
|
+
|
|
186
|
+
return env_flag_enabled("LLM_GIT_PROGRESS") or trace_enabled()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def trace_file_path(args: Any | None = None) -> Path | None:
|
|
190
|
+
"""Resolve CLI/env JSONL trace output path, preferring CLI args."""
|
|
191
|
+
|
|
192
|
+
if args is not None:
|
|
193
|
+
trace_output = getattr(args, "trace_output", None)
|
|
194
|
+
if trace_output is not None:
|
|
195
|
+
return Path(trace_output)
|
|
196
|
+
env_path = os.environ.get("LLM_GIT_TRACE_FILE")
|
|
197
|
+
return Path(env_path) if env_path is not None and env_path != "" else None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def timings_enabled(args: Any | None = None) -> bool:
|
|
201
|
+
"""Return true when phase timing collection should be enabled."""
|
|
202
|
+
|
|
203
|
+
if args is not None and (
|
|
204
|
+
getattr(args, "debug_output", None) is not None or getattr(args, "trace_output", None) is not None
|
|
205
|
+
):
|
|
206
|
+
return True
|
|
207
|
+
return trace_file_path() is not None or "LLM_GIT_TRACE" in os.environ
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def init_file_tracing(path: str | os.PathLike[str]) -> TraceGuard:
|
|
211
|
+
"""Initialize process-wide JSONL profiling to ``path``."""
|
|
212
|
+
|
|
213
|
+
global _TRACE
|
|
214
|
+
trace_path = Path(path)
|
|
215
|
+
if trace_path.parent and str(trace_path.parent) != ".":
|
|
216
|
+
trace_path.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
file = trace_path.open("a", encoding="utf-8", buffering=1)
|
|
218
|
+
guard = TraceGuard(path=trace_path, _file=file)
|
|
219
|
+
with _TRACE_LOCK:
|
|
220
|
+
_TRACE = guard
|
|
221
|
+
_write_event("trace_started", path=str(trace_path), pid=os.getpid())
|
|
222
|
+
return guard
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def enabled() -> bool:
|
|
226
|
+
"""Return true when file tracing is currently active."""
|
|
227
|
+
|
|
228
|
+
return _TRACE is not None and not _TRACE._closed
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def section(name: str, collector: TimingCollector | None = None) -> ProfileSection:
|
|
232
|
+
"""Create a profiling context manager for a logical section."""
|
|
233
|
+
|
|
234
|
+
return ProfileSection(name, collector)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def create_timing_collector(enabled: bool = True) -> TimingCollector:
|
|
238
|
+
"""Create a phase-timing collector."""
|
|
239
|
+
|
|
240
|
+
return TimingCollector(enabled=enabled)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def record_timing(collector: TimingCollector | None, phase: str, seconds: float) -> None:
|
|
244
|
+
"""Record a named phase duration and emit the Rust-compatible trace event."""
|
|
245
|
+
|
|
246
|
+
_write_event(
|
|
247
|
+
"timing_recorded",
|
|
248
|
+
section=phase,
|
|
249
|
+
elapsed_ms=seconds * 1000.0,
|
|
250
|
+
elapsed_us=_duration_us(seconds),
|
|
251
|
+
)
|
|
252
|
+
if collector is not None and collector.enabled:
|
|
253
|
+
collector.phases.append(TimingPhase(phase=phase, duration_ms=round_ms(seconds), share_pct=0.0))
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def finalize_timings(
|
|
257
|
+
collector: TimingCollector | list[TimingPhase], total_seconds: float | None = None
|
|
258
|
+
) -> TimingReport:
|
|
259
|
+
"""Finalize collected timings by computing total milliseconds and shares."""
|
|
260
|
+
|
|
261
|
+
if isinstance(collector, TimingCollector):
|
|
262
|
+
phases = collector.phases
|
|
263
|
+
elapsed = time.perf_counter() - collector._start if total_seconds is None else total_seconds
|
|
264
|
+
else:
|
|
265
|
+
phases = collector
|
|
266
|
+
elapsed = 0.0 if total_seconds is None else total_seconds
|
|
267
|
+
|
|
268
|
+
total_ms = round_ms(elapsed)
|
|
269
|
+
finalized: list[TimingPhase] = []
|
|
270
|
+
for phase in phases:
|
|
271
|
+
share_pct = _round_one_decimal((phase.duration_ms / total_ms) * 100.0) if total_ms > 0.0 else 0.0
|
|
272
|
+
finalized.append(TimingPhase(phase=phase.phase, duration_ms=phase.duration_ms, share_pct=share_pct))
|
|
273
|
+
return TimingReport(total_ms=total_ms, phases=finalized)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def format_timing_report(timings: TimingCollector | TimingReport) -> str:
|
|
277
|
+
"""Format the human ``[TIMING]`` report printed when ``LLM_GIT_TRACE`` is set."""
|
|
278
|
+
|
|
279
|
+
report = _coerce_report(timings)
|
|
280
|
+
lines = [f"[TIMING] total={report.total_ms:.1f}ms"]
|
|
281
|
+
lines.extend(
|
|
282
|
+
f"[TIMING] {phase.phase:>28} {phase.duration_ms:>8.1f}ms {phase.share_pct:>5.1f}%" for phase in report.phases
|
|
283
|
+
)
|
|
284
|
+
return "\n".join(lines)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def write_timings_json(path: str | os.PathLike[str], timings: TimingCollector | TimingReport) -> Path:
|
|
288
|
+
"""Write a pretty ``timings.json`` debug artifact and return its path."""
|
|
289
|
+
|
|
290
|
+
output_path = _timings_json_path(path)
|
|
291
|
+
if output_path.parent and str(output_path.parent) != ".":
|
|
292
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
293
|
+
output_path.write_text(json.dumps(_coerce_report(timings).to_dict(), indent=2), encoding="utf-8")
|
|
294
|
+
return output_path
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def emit_timing_report(args: Any, timings: TimingCollector | TimingReport) -> TimingReport:
|
|
298
|
+
"""Write/debug-log/print a finalized timing report using Rust CLI rules."""
|
|
299
|
+
|
|
300
|
+
report = _coerce_report(timings)
|
|
301
|
+
debug_output = getattr(args, "debug_output", None)
|
|
302
|
+
if debug_output is not None:
|
|
303
|
+
write_timings_json(debug_output, report)
|
|
304
|
+
|
|
305
|
+
_write_event("timing_report_finished", total_ms=report.total_ms, phase_count=len(report.phases))
|
|
306
|
+
|
|
307
|
+
if "LLM_GIT_TRACE" in os.environ:
|
|
308
|
+
print(format_timing_report(report), file=sys.stderr)
|
|
309
|
+
return report
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def print_llm_progress(message: str | Callable[[], str]) -> None:
|
|
313
|
+
"""Print an LLM progress line when ``LLM_GIT_PROGRESS`` or trace is enabled."""
|
|
314
|
+
|
|
315
|
+
if not progress_enabled():
|
|
316
|
+
return
|
|
317
|
+
text = message() if callable(message) else message
|
|
318
|
+
try:
|
|
319
|
+
from . import style
|
|
320
|
+
|
|
321
|
+
style.print_info(text)
|
|
322
|
+
except Exception:
|
|
323
|
+
print(text, file=sys.stderr)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def print_trace(message: str) -> None:
|
|
327
|
+
"""Print a low-level ``[TRACE]`` line when ``LLM_GIT_TRACE`` is enabled."""
|
|
328
|
+
|
|
329
|
+
if not trace_enabled():
|
|
330
|
+
return
|
|
331
|
+
if _stdout_is_status_stream():
|
|
332
|
+
print("\r\x1b[K", end="", file=sys.stdout, flush=True)
|
|
333
|
+
print(f"[TRACE] {message}", file=sys.stderr)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def trace_event(event: str, *, level: str = "INFO", **fields: Any) -> None:
|
|
337
|
+
"""Emit one JSONL trace event if file tracing is active."""
|
|
338
|
+
|
|
339
|
+
_write_event(event, level=level, **fields)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _coerce_report(timings: TimingCollector | TimingReport) -> TimingReport:
|
|
343
|
+
if isinstance(timings, TimingReport):
|
|
344
|
+
return timings
|
|
345
|
+
return timings.finalize()
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _timings_json_path(path: str | os.PathLike[str]) -> Path:
|
|
349
|
+
output_path = Path(path)
|
|
350
|
+
if output_path.exists() and output_path.is_dir():
|
|
351
|
+
return output_path / "timings.json"
|
|
352
|
+
if output_path.name != "timings.json" and output_path.suffix == "":
|
|
353
|
+
return output_path / "timings.json"
|
|
354
|
+
return output_path
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _round_one_decimal(value: float) -> float:
|
|
358
|
+
return math.floor(value * 10.0 + 0.5) / 10.0
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def round_ms(seconds: float) -> float:
|
|
362
|
+
"""Round seconds to milliseconds with Rust's one-decimal rounding rule."""
|
|
363
|
+
|
|
364
|
+
return _round_one_decimal(seconds * 1000.0)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _duration_us(seconds: float) -> int:
|
|
368
|
+
return max(0, min(int(seconds * 1_000_000), (1 << 64) - 1))
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _stdout_is_status_stream() -> bool:
|
|
372
|
+
try:
|
|
373
|
+
from . import style
|
|
374
|
+
|
|
375
|
+
return not style.pipe_mode()
|
|
376
|
+
except Exception:
|
|
377
|
+
return sys.stdout.isatty()
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _write_event(event: str, *, level: str = "INFO", **fields: Any) -> None:
|
|
381
|
+
guard = _TRACE
|
|
382
|
+
if guard is None or guard._closed:
|
|
383
|
+
return
|
|
384
|
+
record = {
|
|
385
|
+
"ts": time.time(),
|
|
386
|
+
"target": TARGET,
|
|
387
|
+
"level": level,
|
|
388
|
+
"event": event,
|
|
389
|
+
**fields,
|
|
390
|
+
}
|
|
391
|
+
try:
|
|
392
|
+
line = json.dumps(record, sort_keys=True, separators=(",", ":"), default=str)
|
|
393
|
+
with _TRACE_LOCK:
|
|
394
|
+
if not guard._closed:
|
|
395
|
+
guard._file.write(line + "\n")
|
|
396
|
+
except Exception:
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
__all__ = [
|
|
401
|
+
"TARGET",
|
|
402
|
+
"ProfileSection",
|
|
403
|
+
"TimingCollector",
|
|
404
|
+
"TimingPhase",
|
|
405
|
+
"TimingReport",
|
|
406
|
+
"TraceGuard",
|
|
407
|
+
"create_timing_collector",
|
|
408
|
+
"emit_timing_report",
|
|
409
|
+
"enabled",
|
|
410
|
+
"env_flag_enabled",
|
|
411
|
+
"env_flag_value_enabled",
|
|
412
|
+
"finalize_timings",
|
|
413
|
+
"format_timing_report",
|
|
414
|
+
"init_file_tracing",
|
|
415
|
+
"print_llm_progress",
|
|
416
|
+
"print_trace",
|
|
417
|
+
"progress_enabled",
|
|
418
|
+
"record_timing",
|
|
419
|
+
"round_ms",
|
|
420
|
+
"section",
|
|
421
|
+
"timings_enabled",
|
|
422
|
+
"trace_enabled",
|
|
423
|
+
"trace_event",
|
|
424
|
+
"trace_file_path",
|
|
425
|
+
"write_timings_json",
|
|
426
|
+
]
|
lgit/py.typed
ADDED
|
File without changes
|