context-loader 1.3.4__tar.gz → 1.3.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: context-loader
3
- Version: 1.3.4
3
+ Version: 1.3.5
4
4
  Summary: Render deterministic local Git context for any executor
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -219,14 +219,14 @@ Python standard library.
219
219
 
220
220
  ## Open-source quick start
221
221
 
222
- Current stable release: **1.3.4**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
222
+ Current stable release: **1.3.5**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
223
223
 
224
224
  ```bash
225
- uv tool install 'context-loader==1.3.4'
225
+ uv tool install 'context-loader==1.3.5'
226
226
  ```
227
227
 
228
228
  ```bash
229
- pip install 'context-loader==1.3.4'
229
+ pip install 'context-loader==1.3.5'
230
230
  ```
231
231
 
232
232
  ```bash
@@ -239,21 +239,21 @@ project-context --repo /path/to/repo --format json
239
239
 
240
240
  ## Install
241
241
 
242
- Source version: `1.3.4`. Install its matching published release or an exact source commit.
242
+ Source version: `1.3.5`. Install its matching published release or an exact source commit.
243
243
 
244
244
  Install via `uv`:
245
245
 
246
246
  ```bash
247
- uv tool install 'context-loader==1.3.4'
247
+ uv tool install 'context-loader==1.3.5'
248
248
  ```
249
249
 
250
250
  Install via `pip`:
251
251
 
252
252
  ```bash
253
- pip install 'context-loader==1.3.4'
253
+ pip install 'context-loader==1.3.5'
254
254
  ```
255
255
 
256
- For development or source-based installs tracking current repository (`1.3.4`):
256
+ For development or source-based installs tracking current repository (`1.3.5`):
257
257
 
258
258
  ```bash
259
259
  uv tool install git+https://github.com/xuanheng-tech/context-loader.git
@@ -350,7 +350,7 @@ set differs, as described under “Compact model consumption”. The declared co
350
350
  "schema_version": 3,
351
351
  "tool": {
352
352
  "name": "context-loader",
353
- "version": "1.3.4"
353
+ "version": "1.3.5"
354
354
  },
355
355
  "repository": {
356
356
  "requested_path": "/canonical/requested/path",
@@ -470,7 +470,7 @@ although 1.1.0 added the top-level `statuses` key. From release 1.2.0 onward eac
470
470
  names exactly one key set, and a new number is issued whenever a key set would otherwise be reused.
471
471
 
472
472
  The JSON schema version and package version are independent: this build emits `schema_version` `3`
473
- for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.4`. Version 1
473
+ for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.5`. Version 1
474
474
  and 2 are the exact shapes already published in release 1.2.0 and are never reused: because
475
475
  `nested_context` changes the default document's key set, version 3 names the current full-document
476
476
  key set and version 4 names the compact projection.
@@ -596,6 +596,8 @@ bullet is a per-component, read or capture bound, as each one states:
596
596
  repository root the filesystem could not decode makes the machine formats exit 1 where Markdown
597
597
  still renders
598
598
  - Git subprocess output: 16 MiB (bounded while reading)
599
+ - Git collection: one shared 30-second deadline for repository discovery, configuration/submodule
600
+ preflight, refs, status and recent commits
599
601
  - Git status preflight: at most 128 repositories, including initialized nested submodules
600
602
  - Candidate file validation: 16 MiB hard read bound
601
603
 
@@ -611,6 +613,12 @@ Symlinked candidate files are skipped, and directory symlinks are not traversed.
611
613
  Git state is derived only from the working tree and local refs. The command does not fetch or query
612
614
  the remote.
613
615
 
616
+ All Git commands in one collection share the same elapsed-time budget; each command receives only
617
+ the remaining time. Once that budget expires, the loader stops without starting subsequent commands,
618
+ terminates and reaps a running Git child, and exits with empty stdout. This deadline does not impose
619
+ a hard wall-clock limit on blocked filesystem/process-creation calls or operating-system child
620
+ reaping, and does not cover the later context-file collection.
621
+
614
622
  - An unborn working tree reports its symbolic branch name, such as `Branch: main`, and
615
623
  `HEAD: unborn`.
616
624
  - A detached HEAD reports `Branch: detached` and the resolved commit object ID.
@@ -639,7 +647,7 @@ or candidate-file content.
639
647
 
640
648
  ## Not Included
641
649
 
642
- Version 1.3.4 does not provide AI summaries, project-type detection, loading or transport of
650
+ Version 1.3.5 does not provide AI summaries, project-type detection, loading or transport of
643
651
  nested `AGENTS.md` contents (the JSON `nested_context` field reports bounded existence only),
644
652
  Memory retrieval, semantic ranking, ignore-rule parsing, plugins, profiles, caches, databases,
645
653
  network services, MCP, daemons, GUIs, CI/CD, telemetry, or automatic updates.
@@ -654,10 +662,12 @@ just check
654
662
 
655
663
  The shared check also builds a wheel and installs it into a fresh virtual environment. Package
656
664
  acceptance runs the installed CLI outside the source checkout, checking deterministic agreement
657
- between all three formats, linked worktrees, and safe failures. Build and installation run offline
665
+ between all three formats, linked worktrees, long multi-module instructions with focus/path selection,
666
+ explicit truncation and nested-instruction presence, and safe failures. Build and installation run offline
658
667
  using the backend cache populated by `uv run --frozen`; the tests use only disposable repositories.
659
668
  Git deadline regressions verify that silent, streaming, and stdout-closed child processes are
660
- terminated and reaped, and that a timeout leaves CLI stdout empty.
669
+ terminated and reaped, that successful commands cannot reset the shared budget, and that process
670
+ creation consumes it. A timeout leaves CLI stdout empty in every output format.
661
671
 
662
672
  The lockfile uses public PyPI artifact URLs; no organization mirror or local Renovate preset
663
673
  is needed. Renovate keeps scheduled, rate-limited updates and manual merges using a public
@@ -8,14 +8,14 @@ Python standard library.
8
8
 
9
9
  ## Open-source quick start
10
10
 
11
- Current stable release: **1.3.4**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
11
+ Current stable release: **1.3.5**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
12
12
 
13
13
  ```bash
14
- uv tool install 'context-loader==1.3.4'
14
+ uv tool install 'context-loader==1.3.5'
15
15
  ```
16
16
 
17
17
  ```bash
18
- pip install 'context-loader==1.3.4'
18
+ pip install 'context-loader==1.3.5'
19
19
  ```
20
20
 
21
21
  ```bash
@@ -28,21 +28,21 @@ project-context --repo /path/to/repo --format json
28
28
 
29
29
  ## Install
30
30
 
31
- Source version: `1.3.4`. Install its matching published release or an exact source commit.
31
+ Source version: `1.3.5`. Install its matching published release or an exact source commit.
32
32
 
33
33
  Install via `uv`:
34
34
 
35
35
  ```bash
36
- uv tool install 'context-loader==1.3.4'
36
+ uv tool install 'context-loader==1.3.5'
37
37
  ```
38
38
 
39
39
  Install via `pip`:
40
40
 
41
41
  ```bash
42
- pip install 'context-loader==1.3.4'
42
+ pip install 'context-loader==1.3.5'
43
43
  ```
44
44
 
45
- For development or source-based installs tracking current repository (`1.3.4`):
45
+ For development or source-based installs tracking current repository (`1.3.5`):
46
46
 
47
47
  ```bash
48
48
  uv tool install git+https://github.com/xuanheng-tech/context-loader.git
@@ -139,7 +139,7 @@ set differs, as described under “Compact model consumption”. The declared co
139
139
  "schema_version": 3,
140
140
  "tool": {
141
141
  "name": "context-loader",
142
- "version": "1.3.4"
142
+ "version": "1.3.5"
143
143
  },
144
144
  "repository": {
145
145
  "requested_path": "/canonical/requested/path",
@@ -259,7 +259,7 @@ although 1.1.0 added the top-level `statuses` key. From release 1.2.0 onward eac
259
259
  names exactly one key set, and a new number is issued whenever a key set would otherwise be reused.
260
260
 
261
261
  The JSON schema version and package version are independent: this build emits `schema_version` `3`
262
- for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.4`. Version 1
262
+ for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.5`. Version 1
263
263
  and 2 are the exact shapes already published in release 1.2.0 and are never reused: because
264
264
  `nested_context` changes the default document's key set, version 3 names the current full-document
265
265
  key set and version 4 names the compact projection.
@@ -385,6 +385,8 @@ bullet is a per-component, read or capture bound, as each one states:
385
385
  repository root the filesystem could not decode makes the machine formats exit 1 where Markdown
386
386
  still renders
387
387
  - Git subprocess output: 16 MiB (bounded while reading)
388
+ - Git collection: one shared 30-second deadline for repository discovery, configuration/submodule
389
+ preflight, refs, status and recent commits
388
390
  - Git status preflight: at most 128 repositories, including initialized nested submodules
389
391
  - Candidate file validation: 16 MiB hard read bound
390
392
 
@@ -400,6 +402,12 @@ Symlinked candidate files are skipped, and directory symlinks are not traversed.
400
402
  Git state is derived only from the working tree and local refs. The command does not fetch or query
401
403
  the remote.
402
404
 
405
+ All Git commands in one collection share the same elapsed-time budget; each command receives only
406
+ the remaining time. Once that budget expires, the loader stops without starting subsequent commands,
407
+ terminates and reaps a running Git child, and exits with empty stdout. This deadline does not impose
408
+ a hard wall-clock limit on blocked filesystem/process-creation calls or operating-system child
409
+ reaping, and does not cover the later context-file collection.
410
+
403
411
  - An unborn working tree reports its symbolic branch name, such as `Branch: main`, and
404
412
  `HEAD: unborn`.
405
413
  - A detached HEAD reports `Branch: detached` and the resolved commit object ID.
@@ -428,7 +436,7 @@ or candidate-file content.
428
436
 
429
437
  ## Not Included
430
438
 
431
- Version 1.3.4 does not provide AI summaries, project-type detection, loading or transport of
439
+ Version 1.3.5 does not provide AI summaries, project-type detection, loading or transport of
432
440
  nested `AGENTS.md` contents (the JSON `nested_context` field reports bounded existence only),
433
441
  Memory retrieval, semantic ranking, ignore-rule parsing, plugins, profiles, caches, databases,
434
442
  network services, MCP, daemons, GUIs, CI/CD, telemetry, or automatic updates.
@@ -443,10 +451,12 @@ just check
443
451
 
444
452
  The shared check also builds a wheel and installs it into a fresh virtual environment. Package
445
453
  acceptance runs the installed CLI outside the source checkout, checking deterministic agreement
446
- between all three formats, linked worktrees, and safe failures. Build and installation run offline
454
+ between all three formats, linked worktrees, long multi-module instructions with focus/path selection,
455
+ explicit truncation and nested-instruction presence, and safe failures. Build and installation run offline
447
456
  using the backend cache populated by `uv run --frozen`; the tests use only disposable repositories.
448
457
  Git deadline regressions verify that silent, streaming, and stdout-closed child processes are
449
- terminated and reaped, and that a timeout leaves CLI stdout empty.
458
+ terminated and reaped, that successful commands cannot reset the shared budget, and that process
459
+ creation consumes it. A timeout leaves CLI stdout empty in every output format.
450
460
 
451
461
  The lockfile uses public PyPI artifact URLs; no organization mirror or local Renovate preset
452
462
  is needed. Renovate keeps scheduled, rate-limited updates and manual merges using a public
@@ -1,3 +1,3 @@
1
1
  """Project Context Loader."""
2
2
 
3
- __version__ = "1.3.4"
3
+ __version__ = "1.3.5"
@@ -118,8 +118,11 @@ def _read_bounded_process_stdout(
118
118
  proc: subprocess.Popen[bytes],
119
119
  max_bytes: int,
120
120
  timeout_seconds: float,
121
+ *,
122
+ deadline: float | None = None,
121
123
  ) -> bytes:
122
- deadline = time.monotonic() + timeout_seconds
124
+ if deadline is None:
125
+ deadline = time.monotonic() + timeout_seconds
123
126
  buffer = bytearray()
124
127
  assert proc.stdout is not None
125
128
  os.set_blocking(proc.stdout.fileno(), False)
@@ -168,9 +171,23 @@ def _read_bounded_process_stdout(
168
171
  return bytes(buffer)
169
172
 
170
173
 
171
- def _run_git(repo: Path, arguments: tuple[str, ...], *, check: bool = True) -> GitResult:
174
+ def _check_git_deadline(deadline: float) -> None:
175
+ if time.monotonic() >= deadline:
176
+ raise ContextLoaderError("unable to read repository Git state")
177
+
178
+
179
+ def _run_git(
180
+ repo: Path,
181
+ arguments: tuple[str, ...],
182
+ *,
183
+ check: bool = True,
184
+ deadline: float | None = None,
185
+ ) -> GitResult:
186
+ command_deadline = time.monotonic() + GIT_TIMEOUT_SECONDS
187
+ deadline = command_deadline if deadline is None else min(deadline, command_deadline)
172
188
  argv = [GIT_EXECUTABLE, *_GIT_PREFIX, "-C", os.fspath(repo), *arguments]
173
189
  try:
190
+ _check_git_deadline(deadline)
174
191
  with subprocess.Popen( # noqa: S603 - executable and every command are fixed here.
175
192
  argv,
176
193
  cwd="/",
@@ -179,8 +196,11 @@ def _run_git(repo: Path, arguments: tuple[str, ...], *, check: bool = True) -> G
179
196
  stdout=subprocess.PIPE,
180
197
  stderr=subprocess.DEVNULL,
181
198
  ) as proc:
182
- stdout = _read_bounded_process_stdout(proc, MAX_GIT_OUTPUT_BYTES, GIT_TIMEOUT_SECONDS)
199
+ stdout = _read_bounded_process_stdout(
200
+ proc, MAX_GIT_OUTPUT_BYTES, GIT_TIMEOUT_SECONDS, deadline=deadline
201
+ )
183
202
  returncode = proc.returncode
203
+ _check_git_deadline(deadline)
184
204
  except ContextLoaderError:
185
205
  raise
186
206
  except (OSError, subprocess.TimeoutExpired) as exc:
@@ -209,6 +229,16 @@ def discover_repository(
209
229
  raw_path: str | os.PathLike[str], *, require_canonical_root: bool = False
210
230
  ) -> RepositoryLocation:
211
231
  """Resolve an absolute directory and discover its non-bare Git worktree root."""
232
+ return _discover_repository(
233
+ raw_path,
234
+ require_canonical_root=require_canonical_root,
235
+ deadline=time.monotonic() + GIT_TIMEOUT_SECONDS,
236
+ )
237
+
238
+
239
+ def _discover_repository(
240
+ raw_path: str | os.PathLike[str], *, require_canonical_root: bool, deadline: float
241
+ ) -> RepositoryLocation:
212
242
  candidate = Path(raw_path)
213
243
  if not candidate.is_absolute():
214
244
  raise ContextLoaderError("--repo must be an absolute path", exit_code=2)
@@ -219,11 +249,14 @@ def discover_repository(
219
249
  if not canonical.is_dir():
220
250
  raise ContextLoaderError("--repo must name an existing directory", exit_code=2)
221
251
 
222
- bare = _run_git(canonical, ("rev-parse", "--is-bare-repository"), check=False)
252
+ bare = _run_git(
253
+ canonical, ("rev-parse", "--is-bare-repository"), check=False, deadline=deadline
254
+ )
223
255
  root = _run_git(
224
256
  canonical,
225
257
  ("rev-parse", "--path-format=absolute", "--show-toplevel"),
226
258
  check=False,
259
+ deadline=deadline,
227
260
  )
228
261
  if bare.returncode != 0 or root.returncode != 0:
229
262
  raise ContextLoaderError(
@@ -243,6 +276,7 @@ def discover_repository(
243
276
  raise ContextLoaderError(
244
277
  "--repo must be the canonical root of a non-bare Git worktree", exit_code=2
245
278
  )
279
+ _check_git_deadline(deadline)
246
280
  return RepositoryLocation(requested_path=canonical, canonical_root=discovered)
247
281
 
248
282
 
@@ -251,14 +285,18 @@ def validate_repository(raw_path: str | os.PathLike[str]) -> Path:
251
285
  return discover_repository(raw_path, require_canonical_root=True).canonical_root
252
286
 
253
287
 
254
- def _head_and_branch(repo: Path) -> tuple[str, str, str | None]:
255
- symbolic = _run_git(repo, ("symbolic-ref", "--quiet", "--short", "HEAD"), check=False)
288
+ def _head_and_branch(repo: Path, deadline: float) -> tuple[str, str, str | None]:
289
+ symbolic = _run_git(
290
+ repo, ("symbolic-ref", "--quiet", "--short", "HEAD"), check=False, deadline=deadline
291
+ )
256
292
  branch_name = (
257
293
  _decode_path(_without_one_line_ending(symbolic.stdout))
258
294
  if symbolic.returncode == 0
259
295
  else None
260
296
  )
261
- resolved_head = _run_git(repo, ("rev-parse", "--verify", "HEAD^{commit}"), check=False)
297
+ resolved_head = _run_git(
298
+ repo, ("rev-parse", "--verify", "HEAD^{commit}"), check=False, deadline=deadline
299
+ )
262
300
  if resolved_head.returncode != 0:
263
301
  if branch_name is None:
264
302
  raise ContextLoaderError("unable to read repository Git state")
@@ -270,17 +308,17 @@ def _head_and_branch(repo: Path) -> tuple[str, str, str | None]:
270
308
  return branch_name or "detached", head, branch_name
271
309
 
272
310
 
273
- def _config_value(repo: Path, key: str) -> str | None:
274
- result = _run_git(repo, ("config", "--get", key), check=False)
311
+ def _config_value(repo: Path, key: str, deadline: float) -> str | None:
312
+ result = _run_git(repo, ("config", "--get", key), check=False, deadline=deadline)
275
313
  if result.returncode != 0:
276
314
  return None
277
315
  value = _decode_text(_without_one_line_ending(result.stdout))
278
316
  return value or None
279
317
 
280
318
 
281
- def _fallback_upstream(repo: Path, branch_name: str) -> str | None:
282
- remote = _config_value(repo, f"branch.{branch_name}.remote")
283
- merge = _config_value(repo, f"branch.{branch_name}.merge")
319
+ def _fallback_upstream(repo: Path, branch_name: str, deadline: float) -> str | None:
320
+ remote = _config_value(repo, f"branch.{branch_name}.remote", deadline)
321
+ merge = _config_value(repo, f"branch.{branch_name}.merge", deadline)
284
322
  if remote is None or merge is None:
285
323
  return None
286
324
  merge_name = merge.removeprefix("refs/heads/")
@@ -289,7 +327,7 @@ def _fallback_upstream(repo: Path, branch_name: str) -> str | None:
289
327
  return f"{remote}/{merge_name}"
290
328
 
291
329
 
292
- def _upstream_state(repo: Path, branch_name: str | None) -> tuple[str, str]:
330
+ def _upstream_state(repo: Path, branch_name: str | None, deadline: float) -> tuple[str, str]:
293
331
  if branch_name is None:
294
332
  return "not configured", "not available"
295
333
 
@@ -297,9 +335,10 @@ def _upstream_state(repo: Path, branch_name: str | None) -> tuple[str, str]:
297
335
  repo,
298
336
  ("rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"),
299
337
  check=False,
338
+ deadline=deadline,
300
339
  )
301
340
  if resolved.returncode != 0:
302
- configured = _fallback_upstream(repo, branch_name)
341
+ configured = _fallback_upstream(repo, branch_name, deadline)
303
342
  return configured or "not configured", "not available"
304
343
 
305
344
  upstream = _decode_text(_without_one_line_ending(resolved.stdout))
@@ -309,6 +348,7 @@ def _upstream_state(repo: Path, branch_name: str | None) -> tuple[str, str]:
309
348
  repo,
310
349
  ("rev-list", "--left-right", "--count", "HEAD...@{upstream}"),
311
350
  check=False,
351
+ deadline=deadline,
312
352
  )
313
353
  if divergence.returncode != 0:
314
354
  return upstream, "not available"
@@ -318,7 +358,7 @@ def _upstream_state(repo: Path, branch_name: str | None) -> tuple[str, str]:
318
358
  return upstream, f"{counts[0].decode('ascii')} / {counts[1].decode('ascii')}"
319
359
 
320
360
 
321
- def _ensure_safe_status(repo: Path) -> None:
361
+ def _ensure_safe_status(repo: Path, deadline: float) -> None:
322
362
  """Reject executable conversions before status can refresh any index entry.
323
363
 
324
364
  Attributes can select clean/process drivers even during a read-only status.
@@ -340,6 +380,7 @@ def _ensure_safe_status(repo: Path) -> None:
340
380
  current,
341
381
  ("config", "--null", "--get-regexp", r"^filter\..*\.(clean|process)$"),
342
382
  check=False,
383
+ deadline=deadline,
343
384
  )
344
385
  if filters.returncode not in (0, 1):
345
386
  raise ContextLoaderError("unable to safely inspect repository Git filters")
@@ -356,7 +397,7 @@ def _ensure_safe_status(repo: Path) -> None:
356
397
  "repository or submodule config enables Git clean/process filters; "
357
398
  "unable to safely inspect working-tree state"
358
399
  )
359
- index = _run_git(current, ("ls-files", "--stage", "-z"))
400
+ index = _run_git(current, ("ls-files", "--stage", "-z"), deadline=deadline)
360
401
  children: set[Path] = set()
361
402
  for record in index.stdout.split(b"\0"):
362
403
  metadata, separator, raw_path = record.partition(b"\t")
@@ -375,8 +416,8 @@ def _ensure_safe_status(repo: Path) -> None:
375
416
  raise ContextLoaderError("repository submodule inspection exceeded the safety limit")
376
417
 
377
418
 
378
- def _working_tree_changes(repo: Path) -> tuple[WorkingTreeChange, ...]:
379
- _ensure_safe_status(repo)
419
+ def _working_tree_changes(repo: Path, deadline: float) -> tuple[WorkingTreeChange, ...]:
420
+ _ensure_safe_status(repo, deadline)
380
421
  result = _run_git(
381
422
  repo,
382
423
  (
@@ -387,6 +428,7 @@ def _working_tree_changes(repo: Path) -> tuple[WorkingTreeChange, ...]:
387
428
  "--no-renames",
388
429
  "--ignore-submodules=none",
389
430
  ),
431
+ deadline=deadline,
390
432
  )
391
433
  if not result.stdout:
392
434
  return ()
@@ -408,7 +450,7 @@ def _working_tree_changes(repo: Path) -> tuple[WorkingTreeChange, ...]:
408
450
  return tuple(sorted(changes, key=lambda change: change.sort_key))
409
451
 
410
452
 
411
- def _recent_commits(repo: Path, head: str) -> tuple[RecentCommit, ...]:
453
+ def _recent_commits(repo: Path, head: str, deadline: float) -> tuple[RecentCommit, ...]:
412
454
  if head == "unborn":
413
455
  return ()
414
456
  result = _run_git(
@@ -421,6 +463,7 @@ def _recent_commits(repo: Path, head: str) -> tuple[RecentCommit, ...]:
421
463
  "HEAD",
422
464
  "--",
423
465
  ),
466
+ deadline=deadline,
424
467
  )
425
468
  commits: list[RecentCommit] = []
426
469
  for line in result.stdout.splitlines():
@@ -443,13 +486,16 @@ def _recent_commits(repo: Path, head: str) -> tuple[RecentCommit, ...]:
443
486
  def collect_repository(
444
487
  raw_path: str | os.PathLike[str], *, require_canonical_root: bool = True
445
488
  ) -> tuple[RepositoryLocation, RepositoryState]:
446
- """Discover one repository and collect only the Phase 01 Git fields."""
447
- location = discover_repository(raw_path, require_canonical_root=require_canonical_root)
489
+ """Discover and collect local Git state within one shared elapsed-time budget."""
490
+ deadline = time.monotonic() + GIT_TIMEOUT_SECONDS
491
+ location = _discover_repository(
492
+ raw_path, require_canonical_root=require_canonical_root, deadline=deadline
493
+ )
448
494
  repository = location.canonical_root
449
- branch, head, branch_name = _head_and_branch(repository)
450
- upstream, ahead_behind = _upstream_state(repository, branch_name)
451
- changes = _working_tree_changes(repository)
452
- commits = _recent_commits(repository, head)
495
+ branch, head, branch_name = _head_and_branch(repository, deadline)
496
+ upstream, ahead_behind = _upstream_state(repository, branch_name, deadline)
497
+ changes = _working_tree_changes(repository, deadline)
498
+ commits = _recent_commits(repository, head, deadline)
453
499
  state = RepositoryState(
454
500
  repository=repository,
455
501
  branch=branch,
@@ -459,6 +505,7 @@ def collect_repository(
459
505
  changes=changes,
460
506
  commits=commits,
461
507
  )
508
+ _check_git_deadline(deadline)
462
509
  return location, state
463
510
 
464
511
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "context-loader"
7
- version = "1.3.4"
7
+ version = "1.3.5"
8
8
  description = "Render deterministic local Git context for any executor"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12,<3.15"
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "context-loader"
7
- version = "1.3.4"
7
+ version = "1.3.5"
8
8
  description = "Render deterministic local Git context for any executor"
9
9
  license = { file = "LICENSE" }
10
10
  readme = "README.md"
@@ -46,5 +46,5 @@
46
46
  ],
47
47
  "schema_version": 1,
48
48
  "tool_name": "context-loader",
49
- "tool_version": "1.3.4"
49
+ "tool_version": "1.3.5"
50
50
  }
File without changes