context-loader 1.3.1__tar.gz → 1.3.3__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.1
3
+ Version: 1.3.3
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.1**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
222
+ Current stable release: **1.3.3**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
223
223
 
224
224
  ```bash
225
- uv tool install 'context-loader==1.3.1'
225
+ uv tool install 'context-loader==1.3.3'
226
226
  ```
227
227
 
228
228
  ```bash
229
- pip install 'context-loader==1.3.1'
229
+ pip install 'context-loader==1.3.3'
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.1`. Install its matching published release or an exact source commit.
242
+ Source version: `1.3.3`. 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.1'
247
+ uv tool install 'context-loader==1.3.3'
248
248
  ```
249
249
 
250
250
  Install via `pip`:
251
251
 
252
252
  ```bash
253
- pip install 'context-loader==1.3.1'
253
+ pip install 'context-loader==1.3.3'
254
254
  ```
255
255
 
256
- For development or source-based installs tracking current repository (`1.3.1`):
256
+ For development or source-based installs tracking current repository (`1.3.3`):
257
257
 
258
258
  ```bash
259
259
  uv tool install git+https://github.com/xuanheng-tech/context-loader.git
@@ -319,6 +319,8 @@ and bare repositories are rejected in every mode.
319
319
  `--focus` and `--path` are optional, bounded selection signals for the root `AGENTS.md`. `--path`
320
320
  must be repository-relative. The collector does not retain either input in output or audit data.
321
321
  Calls that omit both options remain valid and use the conservative fallback described below.
322
+ Each value option (`--repo`, `--focus`, `--path`, `--format`) may be supplied once;
323
+ duplicates are rejected before collection rather than silently overriding an earlier value.
322
324
 
323
325
  ## Markdown Output
324
326
 
@@ -348,7 +350,7 @@ set differs, as described under “Compact model consumption”. The declared co
348
350
  "schema_version": 3,
349
351
  "tool": {
350
352
  "name": "context-loader",
351
- "version": "1.3.1"
353
+ "version": "1.3.3"
352
354
  },
353
355
  "repository": {
354
356
  "requested_path": "/canonical/requested/path",
@@ -395,12 +397,19 @@ context, in assembly order, after the existing newline normalization and truncat
395
397
  distinguishes `repository` from `global`; version 1.0.0's fixed root-file selection currently emits
396
398
  only `repository` sources and does not add any global-file discovery.
397
399
 
398
- `statuses` lists machine-readable collection and render conditions that previously appeared only inside `context` Markdown — plus the nested-context truncation flags, which exist only in machine form: skipped or absent sources, truncated sources, unreadable directory-tree entries, sections omitted under the global output budget, truncated working-tree or declared-command listings, and truncated nested-`AGENTS.md` presence reports. Each entry has stable `code`, `subject_kind`, and `subject` fields. The two `nested_agents_*` codes mirror the `nested_context` truncation booleans for consumers that branch only on `statuses`; the full `nested_context` object remains a first-class field, never folded into `statuses`. Callers can ignore `statuses` safely.
400
+ `statuses` lists skipped or absent sources, truncated sources, unreadable tree entries,
401
+ directories whose listing exceeds its retention cap, globally omitted sections, and truncated
402
+ working-tree, command, tree or nested-`AGENTS.md` listings. A skipped or absent source reports
403
+ the observed condition (`not_present`, `skipped_symlink`, `skipped_not_regular`,
404
+ `skipped_encoding`, `skipped_unreadable`); its sentence in `context` comes from that same
405
+ condition. Each entry has stable `code`, `subject_kind`, and `subject` fields. The two
406
+ `nested_agents_*` codes mirror the `nested_context` truncation booleans; the complete object
407
+ remains a separate field. Callers can ignore `statuses` safely.
399
408
 
400
409
  The optional `selection` object is present only on a rendered `AGENTS.md` source. Its section entries
401
410
  contain heading, heading level, and fixed selection reasons; it never contains the original focus or
402
411
  target path. Existing per-source fields remain unchanged; the document's exact key
403
- set (now including `nested_context`) is named by its `schema_version`, described below.
412
+ set (which includes `nested_context`) is named by its `schema_version`, described below.
404
413
 
405
414
  `nested_context` is always present and existence-only: `files` lists repository-relative paths of
406
415
  non-directory `AGENTS.md` entries under subdirectories, found by a bounded scan that enumerates
@@ -414,8 +423,9 @@ budget metered on the emitted array itself — each path's escaped, JSON-seriali
414
423
  separator, with the array's brackets charged to the first entry — so the emitted array never costs
415
424
  more than the budget it declares. Because quoting is counted, an array already near the cap can
416
425
  report one path fewer than an older release did, and then says so through `list_truncated`.
417
- `scan_truncated` means the depth (4) or directory-count (2,000) budget was reached, or a directory
418
- or entry could not be read, so absence of a path is not proof of absence of the file. Nested paths
426
+ `scan_truncated` means the depth (4) or directory-count (2,000) budget was reached, a directory
427
+ held more entries than one listing retains (1,024), or a directory or entry could not be
428
+ read, so absence of a path is not proof of absence of the file. Nested paths
419
429
  are sanitized with the same escaping the Markdown uses for repository-derived text, so a nested name
420
430
  the filesystem could not decode never breaks the document;
421
431
  see “Limits” for the root paths that are emitted verbatim. The corresponding
@@ -423,6 +433,16 @@ see “Limits” for the root paths that are emitted verbatim. The corresponding
423
433
  Presence is not instruction: whether a nested file applies, and its text, remain the caller's
424
434
  judgment; Markdown output is unchanged.
425
435
 
436
+ Escaped names remain reversible and distinct: a literal backslash becomes `\\`, a backtick
437
+ becomes `\x60`, ASCII controls and undecodable filesystem bytes become `\xNN`, and other
438
+ non-printable Unicode characters become `\uNNNN` or `\UNNNNNNNN`. Printable Unicode stays
439
+ unchanged. After JSON decoding, recover POSIX path bytes by reading escapes from left to right:
440
+ `\\` yields one backslash byte, `\xNN` yields that byte, and Unicode escapes and ordinary
441
+ characters yield their UTF-8 bytes. Thus a backtick, a literal `\x60`, an undecodable byte
442
+ `0x80`, and Unicode `U+0080` all have different representations. This applies to nested paths
443
+ and displayed tree/change/status names; the verbatim root/source paths retain their existing
444
+ semantics.
445
+
426
446
  ### Compact model consumption (`--format json-compact`)
427
447
 
428
448
  `--format json-compact` emits a `schema_version` 4 document: exactly the version-3 document with
@@ -447,10 +467,10 @@ not otherwise change default Markdown bytes, exit codes or determinism.
447
467
 
448
468
  One historical exception is acknowledged: releases 1.0.0 and 1.1.0 both emitted `schema_version` 1
449
469
  although 1.1.0 added the top-level `statuses` key. From release 1.2.0 onward each emitted number
450
- names exactly one key set, and the `nested_context` renumber below enforces that rule going forward.
470
+ names exactly one key set, and a new number is issued whenever a key set would otherwise be reused.
451
471
 
452
472
  The JSON schema version and package version are independent: this build emits `schema_version` `3`
453
- for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.1`. Version 1
473
+ for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.3`. Version 1
454
474
  and 2 are the exact shapes already published in release 1.2.0 and are never reused: because
455
475
  `nested_context` changes the default document's key set, version 3 names the current full-document
456
476
  key set and version 4 names the compact projection.
@@ -524,11 +544,51 @@ bullet is a per-component, read or capture bound, as each one states:
524
544
  16 KiB, including a 4-KiB maximum small head
525
545
  - `README.md`: 16 KiB
526
546
  - Each entry file: 8 KiB
527
- - All entry-file bodies: 24 KiB
547
+ - All entry-file bodies: 24 KiB, including truncation markers. Room is reserved for markers
548
+ on later text sources, so exhausting the budget never hides that their bodies were cut.
528
549
  - Declared commands: 8 KiB
529
- - Directory tree: 12 KiB, 300 entries, and depth 2
530
- - Nested `AGENTS.md` presence scan: depth 4, 2,000 directories, at most 32 reported paths and
531
- a 4 KiB budget metered on the emitted array; file contents are never read
550
+ - Directory tree: 12 KiB of listing body, 300 entries, and depth 2. Each directory is
551
+ retains and classifies up to 512 entries, keeping the alphabetically first names so the listing never
552
+ depends on operating system order. A directory that holds more is reported by a single
553
+ `Listing incomplete:` line stating the total, the limit and up to three names, and by at most
554
+ eight `directory_listing_incomplete` status entries naming individual directories plus one
555
+ aggregate entry giving the totals when more directories were capped than are named: beyond the
556
+ retained examples a capped directory is counted but not individually named, and no listing
557
+ claims completeness it does not have. That total covers the capped directories the listing
558
+ reached: a directory is reached only when its parent offered it inside the parent's own
559
+ alphabetical prefix and the entry budget kept it, so capped siblings displaced by the budget are
560
+ disclosed as their parent's truncation rather than as separate numbers.
561
+ Three limits act in sequence and a name survives only if all three leave room. The 512-entry
562
+ retention limit decides which names a directory offers at all; above it only an alphabetical
563
+ prefix is classified. All names must still be scanned to find that prefix, subject to the
564
+ separate hard name-scan bound below; the retention cap bounds memory and classification work.
565
+ The 300-entry item budget is then shared between a directory's
566
+ subdirectories and its non-directories: each may take up to half of what remains before the other
567
+ claims the surplus, which stops alphabetically-earlier directories from evicting every file, but
568
+ it is a share rather than a floor -- with a single slot left the non-directories take it. The 12
569
+ KiB listing budget then decides which kept names are rendered. Descent
570
+ happens after a directory's own entries, so deep content cannot displace top-level names, but
571
+ the tool promises no specific file: a root `README.md` is listed only when all three limits leave
572
+ room for it -- the enumeration offered it, the entry budget still has a slot, and the byte budget
573
+ still has room -- and each of those three can fail on its own. Being offered is not the same as
574
+ an uncapped root: the 512-entry cut drops only the names that sort after its retained prefix, so
575
+ an early-sorting `README.md` is still listed from a capped root while a late-sorting one never
576
+ reaches the listing. Whenever one of the three bites, the listing says so through the
577
+ note, the marker, or a `truncated` status instead of presenting itself as complete.
578
+ The `Listing incomplete:` line is charged to the 12 KiB listing budget before the body is cut,
579
+ so the claim can never inflate the section; its cost is paid in listing bytes, so a long note
580
+ removes that many bytes of entries, and the `truncated` status plus the marker inside the
581
+ fence say so. The heading and the code-fence lines are not metered, so the whole Markdown
582
+ section can exceed 12 KiB by that fixed few dozen bytes.
583
+ - Nested `AGENTS.md` presence scan: depth 4, 2,000 directories, at most 32 reported paths, a
584
+ 4 KiB budget metered on the emitted array, and at most 1,024 entries retained and classified
585
+ per directory. Names are scanned to select that prefix, subject to the separate hard
586
+ name-scan bound below; file contents are never read
587
+ - Directory name scans stop on the first entry beyond 65,536 names per directory. An
588
+ over-limit directory's entire partial prefix is discarded to keep results independent of
589
+ operating system order. The tree emits `Skipped: directory name scan limit reached.` and
590
+ a `directory_scan_limit_exceeded` status; the nested presence scan sets `scan_truncated`.
591
+ This bounds name iteration, not elapsed time on a blocked filesystem call.
532
592
  - Working-tree changes: 100 paths and 4 KiB
533
593
  - Recent commits: 8
534
594
  - JSON-only listings have no byte budget of their own: `statuses` subjects are escaped names bounded
@@ -536,6 +596,7 @@ bullet is a per-component, read or capture bound, as each one states:
536
596
  repository root the filesystem could not decode makes the machine formats exit 1 where Markdown
537
597
  still renders
538
598
  - Git subprocess output: 16 MiB (bounded while reading)
599
+ - Git status preflight: at most 128 repositories, including initialized nested submodules
539
600
  - Candidate file validation: 16 MiB hard read bound
540
601
 
541
602
  Truncation occurs only at complete UTF-8 and line boundaries and is marked explicitly.
@@ -559,6 +620,13 @@ the remote.
559
620
  this is normal after cloning an empty remote.
560
621
  - `Ahead / behind: not available` means the commit relationship cannot currently be computed.
561
622
  Counts are reported only when the configured upstream commit is available locally.
623
+ - Executable Git clean/process filters can run during `status`, even with optional locks
624
+ disabled. Before reading status, the loader checks effective repository configuration,
625
+ including includes, worktree configuration and initialized nested submodules. A non-empty
626
+ clean/process command causes a safe error with no stdout, even if no current attribute uses
627
+ it. Disabling a driver silently would change cleanliness semantics. Filter-dependent repositories
628
+ (including locally configured Git LFS drivers) therefore need ordinary Git inspection outside
629
+ this loader. Git protocols are also disabled to prevent lazy object retrieval.
562
630
 
563
631
  ## Exit Codes
564
632
 
@@ -571,7 +639,7 @@ or candidate-file content.
571
639
 
572
640
  ## Not Included
573
641
 
574
- Version 1.3.1 does not provide AI summaries, project-type detection, loading or transport of
642
+ Version 1.3.3 does not provide AI summaries, project-type detection, loading or transport of
575
643
  nested `AGENTS.md` contents (the JSON `nested_context` field reports bounded existence only),
576
644
  Memory retrieval, semantic ranking, ignore-rule parsing, plugins, profiles, caches, databases,
577
645
  network services, MCP, daemons, GUIs, CI/CD, telemetry, or automatic updates.
@@ -584,6 +652,17 @@ Run the complete local check:
584
652
  just check
585
653
  ```
586
654
 
655
+ The lockfile uses public PyPI artifact URLs; no organization mirror or local Renovate preset
656
+ is needed. Renovate keeps scheduled, rate-limited updates and manual merges using a public
657
+ recommended preset plus the repository's explicit settings. Python 3.12 remains the tested
658
+ runtime; widening that range requires testing those interpreters first.
659
+
660
+ Git test fixtures discard inherited `GIT_*` routing/configuration before creating repositories.
661
+ Permission-sensitive tests must run as a non-root user. Gitea uses `just ci-check`: non-root
662
+ runners execute directly; root containers use `runuser` with an existing account named by
663
+ `CONTEXT_LOADER_CI_USER` (default `ubuntu`). That account must be able to execute the installed
664
+ `uv`, Python and `just`. No `sudo` dependency is required for this CI step.
665
+
587
666
  ## Version maintenance
588
667
 
589
668
  The versions in `pyproject.toml` and `context_loader/__init__.py`, the `tool_version` in
@@ -604,11 +683,17 @@ only verifies the public release, mirrors its exact formal tag object if missing
604
683
  the Gitea Release record. It builds no package and has no PyPI publishing credentials.
605
684
  No private endpoint or credential is needed on GitHub.
606
685
 
686
+ For environments that permit direct public reads but share an exhausted proxy quota,
687
+ set the Gitea Actions repository variable `RELEASE_PUBLIC_API_DIRECT=true`. The record
688
+ step appends only public GitHub API/PyPI hosts to the existing proxy bypass lists. Without
689
+ this explicit option, the workflow uses its inherited proxy settings.
690
+
607
691
  Release checks use Python 3.12, Git, and the runner's existing OpenSSL CLI. They compare downloaded
608
692
  PyPI file bytes, metadata and SHA-256, plus the publisher/tag/commit claims in PyPI's HTTPS-served
609
693
  provenance. This is an identity check, not an independent cryptographic Sigstore verifier.
610
694
 
611
695
  - Tag/version or expected-commit mismatch and `just check` failure stop before build/upload.
696
+ - Annotated tag headers must match the exact tag name and commit, without extra or duplicate headers.
612
697
  - The build refuses to run unless `dist/` is empty, and refuses any produced file set other than
613
698
  the current version's wheel and sdist. A leftover artifact can carry a release filename while
614
699
  holding different bytes, so it is never treated as the current release.
@@ -8,14 +8,14 @@ Python standard library.
8
8
 
9
9
  ## Open-source quick start
10
10
 
11
- Current stable release: **1.3.1**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
11
+ Current stable release: **1.3.3**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
12
12
 
13
13
  ```bash
14
- uv tool install 'context-loader==1.3.1'
14
+ uv tool install 'context-loader==1.3.3'
15
15
  ```
16
16
 
17
17
  ```bash
18
- pip install 'context-loader==1.3.1'
18
+ pip install 'context-loader==1.3.3'
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.1`. Install its matching published release or an exact source commit.
31
+ Source version: `1.3.3`. 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.1'
36
+ uv tool install 'context-loader==1.3.3'
37
37
  ```
38
38
 
39
39
  Install via `pip`:
40
40
 
41
41
  ```bash
42
- pip install 'context-loader==1.3.1'
42
+ pip install 'context-loader==1.3.3'
43
43
  ```
44
44
 
45
- For development or source-based installs tracking current repository (`1.3.1`):
45
+ For development or source-based installs tracking current repository (`1.3.3`):
46
46
 
47
47
  ```bash
48
48
  uv tool install git+https://github.com/xuanheng-tech/context-loader.git
@@ -108,6 +108,8 @@ and bare repositories are rejected in every mode.
108
108
  `--focus` and `--path` are optional, bounded selection signals for the root `AGENTS.md`. `--path`
109
109
  must be repository-relative. The collector does not retain either input in output or audit data.
110
110
  Calls that omit both options remain valid and use the conservative fallback described below.
111
+ Each value option (`--repo`, `--focus`, `--path`, `--format`) may be supplied once;
112
+ duplicates are rejected before collection rather than silently overriding an earlier value.
111
113
 
112
114
  ## Markdown Output
113
115
 
@@ -137,7 +139,7 @@ set differs, as described under “Compact model consumption”. The declared co
137
139
  "schema_version": 3,
138
140
  "tool": {
139
141
  "name": "context-loader",
140
- "version": "1.3.1"
142
+ "version": "1.3.3"
141
143
  },
142
144
  "repository": {
143
145
  "requested_path": "/canonical/requested/path",
@@ -184,12 +186,19 @@ context, in assembly order, after the existing newline normalization and truncat
184
186
  distinguishes `repository` from `global`; version 1.0.0's fixed root-file selection currently emits
185
187
  only `repository` sources and does not add any global-file discovery.
186
188
 
187
- `statuses` lists machine-readable collection and render conditions that previously appeared only inside `context` Markdown — plus the nested-context truncation flags, which exist only in machine form: skipped or absent sources, truncated sources, unreadable directory-tree entries, sections omitted under the global output budget, truncated working-tree or declared-command listings, and truncated nested-`AGENTS.md` presence reports. Each entry has stable `code`, `subject_kind`, and `subject` fields. The two `nested_agents_*` codes mirror the `nested_context` truncation booleans for consumers that branch only on `statuses`; the full `nested_context` object remains a first-class field, never folded into `statuses`. Callers can ignore `statuses` safely.
189
+ `statuses` lists skipped or absent sources, truncated sources, unreadable tree entries,
190
+ directories whose listing exceeds its retention cap, globally omitted sections, and truncated
191
+ working-tree, command, tree or nested-`AGENTS.md` listings. A skipped or absent source reports
192
+ the observed condition (`not_present`, `skipped_symlink`, `skipped_not_regular`,
193
+ `skipped_encoding`, `skipped_unreadable`); its sentence in `context` comes from that same
194
+ condition. Each entry has stable `code`, `subject_kind`, and `subject` fields. The two
195
+ `nested_agents_*` codes mirror the `nested_context` truncation booleans; the complete object
196
+ remains a separate field. Callers can ignore `statuses` safely.
188
197
 
189
198
  The optional `selection` object is present only on a rendered `AGENTS.md` source. Its section entries
190
199
  contain heading, heading level, and fixed selection reasons; it never contains the original focus or
191
200
  target path. Existing per-source fields remain unchanged; the document's exact key
192
- set (now including `nested_context`) is named by its `schema_version`, described below.
201
+ set (which includes `nested_context`) is named by its `schema_version`, described below.
193
202
 
194
203
  `nested_context` is always present and existence-only: `files` lists repository-relative paths of
195
204
  non-directory `AGENTS.md` entries under subdirectories, found by a bounded scan that enumerates
@@ -203,8 +212,9 @@ budget metered on the emitted array itself — each path's escaped, JSON-seriali
203
212
  separator, with the array's brackets charged to the first entry — so the emitted array never costs
204
213
  more than the budget it declares. Because quoting is counted, an array already near the cap can
205
214
  report one path fewer than an older release did, and then says so through `list_truncated`.
206
- `scan_truncated` means the depth (4) or directory-count (2,000) budget was reached, or a directory
207
- or entry could not be read, so absence of a path is not proof of absence of the file. Nested paths
215
+ `scan_truncated` means the depth (4) or directory-count (2,000) budget was reached, a directory
216
+ held more entries than one listing retains (1,024), or a directory or entry could not be
217
+ read, so absence of a path is not proof of absence of the file. Nested paths
208
218
  are sanitized with the same escaping the Markdown uses for repository-derived text, so a nested name
209
219
  the filesystem could not decode never breaks the document;
210
220
  see “Limits” for the root paths that are emitted verbatim. The corresponding
@@ -212,6 +222,16 @@ see “Limits” for the root paths that are emitted verbatim. The corresponding
212
222
  Presence is not instruction: whether a nested file applies, and its text, remain the caller's
213
223
  judgment; Markdown output is unchanged.
214
224
 
225
+ Escaped names remain reversible and distinct: a literal backslash becomes `\\`, a backtick
226
+ becomes `\x60`, ASCII controls and undecodable filesystem bytes become `\xNN`, and other
227
+ non-printable Unicode characters become `\uNNNN` or `\UNNNNNNNN`. Printable Unicode stays
228
+ unchanged. After JSON decoding, recover POSIX path bytes by reading escapes from left to right:
229
+ `\\` yields one backslash byte, `\xNN` yields that byte, and Unicode escapes and ordinary
230
+ characters yield their UTF-8 bytes. Thus a backtick, a literal `\x60`, an undecodable byte
231
+ `0x80`, and Unicode `U+0080` all have different representations. This applies to nested paths
232
+ and displayed tree/change/status names; the verbatim root/source paths retain their existing
233
+ semantics.
234
+
215
235
  ### Compact model consumption (`--format json-compact`)
216
236
 
217
237
  `--format json-compact` emits a `schema_version` 4 document: exactly the version-3 document with
@@ -236,10 +256,10 @@ not otherwise change default Markdown bytes, exit codes or determinism.
236
256
 
237
257
  One historical exception is acknowledged: releases 1.0.0 and 1.1.0 both emitted `schema_version` 1
238
258
  although 1.1.0 added the top-level `statuses` key. From release 1.2.0 onward each emitted number
239
- names exactly one key set, and the `nested_context` renumber below enforces that rule going forward.
259
+ names exactly one key set, and a new number is issued whenever a key set would otherwise be reused.
240
260
 
241
261
  The JSON schema version and package version are independent: this build emits `schema_version` `3`
242
- for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.1`. Version 1
262
+ for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.3`. Version 1
243
263
  and 2 are the exact shapes already published in release 1.2.0 and are never reused: because
244
264
  `nested_context` changes the default document's key set, version 3 names the current full-document
245
265
  key set and version 4 names the compact projection.
@@ -313,11 +333,51 @@ bullet is a per-component, read or capture bound, as each one states:
313
333
  16 KiB, including a 4-KiB maximum small head
314
334
  - `README.md`: 16 KiB
315
335
  - Each entry file: 8 KiB
316
- - All entry-file bodies: 24 KiB
336
+ - All entry-file bodies: 24 KiB, including truncation markers. Room is reserved for markers
337
+ on later text sources, so exhausting the budget never hides that their bodies were cut.
317
338
  - Declared commands: 8 KiB
318
- - Directory tree: 12 KiB, 300 entries, and depth 2
319
- - Nested `AGENTS.md` presence scan: depth 4, 2,000 directories, at most 32 reported paths and
320
- a 4 KiB budget metered on the emitted array; file contents are never read
339
+ - Directory tree: 12 KiB of listing body, 300 entries, and depth 2. Each directory is
340
+ retains and classifies up to 512 entries, keeping the alphabetically first names so the listing never
341
+ depends on operating system order. A directory that holds more is reported by a single
342
+ `Listing incomplete:` line stating the total, the limit and up to three names, and by at most
343
+ eight `directory_listing_incomplete` status entries naming individual directories plus one
344
+ aggregate entry giving the totals when more directories were capped than are named: beyond the
345
+ retained examples a capped directory is counted but not individually named, and no listing
346
+ claims completeness it does not have. That total covers the capped directories the listing
347
+ reached: a directory is reached only when its parent offered it inside the parent's own
348
+ alphabetical prefix and the entry budget kept it, so capped siblings displaced by the budget are
349
+ disclosed as their parent's truncation rather than as separate numbers.
350
+ Three limits act in sequence and a name survives only if all three leave room. The 512-entry
351
+ retention limit decides which names a directory offers at all; above it only an alphabetical
352
+ prefix is classified. All names must still be scanned to find that prefix, subject to the
353
+ separate hard name-scan bound below; the retention cap bounds memory and classification work.
354
+ The 300-entry item budget is then shared between a directory's
355
+ subdirectories and its non-directories: each may take up to half of what remains before the other
356
+ claims the surplus, which stops alphabetically-earlier directories from evicting every file, but
357
+ it is a share rather than a floor -- with a single slot left the non-directories take it. The 12
358
+ KiB listing budget then decides which kept names are rendered. Descent
359
+ happens after a directory's own entries, so deep content cannot displace top-level names, but
360
+ the tool promises no specific file: a root `README.md` is listed only when all three limits leave
361
+ room for it -- the enumeration offered it, the entry budget still has a slot, and the byte budget
362
+ still has room -- and each of those three can fail on its own. Being offered is not the same as
363
+ an uncapped root: the 512-entry cut drops only the names that sort after its retained prefix, so
364
+ an early-sorting `README.md` is still listed from a capped root while a late-sorting one never
365
+ reaches the listing. Whenever one of the three bites, the listing says so through the
366
+ note, the marker, or a `truncated` status instead of presenting itself as complete.
367
+ The `Listing incomplete:` line is charged to the 12 KiB listing budget before the body is cut,
368
+ so the claim can never inflate the section; its cost is paid in listing bytes, so a long note
369
+ removes that many bytes of entries, and the `truncated` status plus the marker inside the
370
+ fence say so. The heading and the code-fence lines are not metered, so the whole Markdown
371
+ section can exceed 12 KiB by that fixed few dozen bytes.
372
+ - Nested `AGENTS.md` presence scan: depth 4, 2,000 directories, at most 32 reported paths, a
373
+ 4 KiB budget metered on the emitted array, and at most 1,024 entries retained and classified
374
+ per directory. Names are scanned to select that prefix, subject to the separate hard
375
+ name-scan bound below; file contents are never read
376
+ - Directory name scans stop on the first entry beyond 65,536 names per directory. An
377
+ over-limit directory's entire partial prefix is discarded to keep results independent of
378
+ operating system order. The tree emits `Skipped: directory name scan limit reached.` and
379
+ a `directory_scan_limit_exceeded` status; the nested presence scan sets `scan_truncated`.
380
+ This bounds name iteration, not elapsed time on a blocked filesystem call.
321
381
  - Working-tree changes: 100 paths and 4 KiB
322
382
  - Recent commits: 8
323
383
  - JSON-only listings have no byte budget of their own: `statuses` subjects are escaped names bounded
@@ -325,6 +385,7 @@ bullet is a per-component, read or capture bound, as each one states:
325
385
  repository root the filesystem could not decode makes the machine formats exit 1 where Markdown
326
386
  still renders
327
387
  - Git subprocess output: 16 MiB (bounded while reading)
388
+ - Git status preflight: at most 128 repositories, including initialized nested submodules
328
389
  - Candidate file validation: 16 MiB hard read bound
329
390
 
330
391
  Truncation occurs only at complete UTF-8 and line boundaries and is marked explicitly.
@@ -348,6 +409,13 @@ the remote.
348
409
  this is normal after cloning an empty remote.
349
410
  - `Ahead / behind: not available` means the commit relationship cannot currently be computed.
350
411
  Counts are reported only when the configured upstream commit is available locally.
412
+ - Executable Git clean/process filters can run during `status`, even with optional locks
413
+ disabled. Before reading status, the loader checks effective repository configuration,
414
+ including includes, worktree configuration and initialized nested submodules. A non-empty
415
+ clean/process command causes a safe error with no stdout, even if no current attribute uses
416
+ it. Disabling a driver silently would change cleanliness semantics. Filter-dependent repositories
417
+ (including locally configured Git LFS drivers) therefore need ordinary Git inspection outside
418
+ this loader. Git protocols are also disabled to prevent lazy object retrieval.
351
419
 
352
420
  ## Exit Codes
353
421
 
@@ -360,7 +428,7 @@ or candidate-file content.
360
428
 
361
429
  ## Not Included
362
430
 
363
- Version 1.3.1 does not provide AI summaries, project-type detection, loading or transport of
431
+ Version 1.3.3 does not provide AI summaries, project-type detection, loading or transport of
364
432
  nested `AGENTS.md` contents (the JSON `nested_context` field reports bounded existence only),
365
433
  Memory retrieval, semantic ranking, ignore-rule parsing, plugins, profiles, caches, databases,
366
434
  network services, MCP, daemons, GUIs, CI/CD, telemetry, or automatic updates.
@@ -373,6 +441,17 @@ Run the complete local check:
373
441
  just check
374
442
  ```
375
443
 
444
+ The lockfile uses public PyPI artifact URLs; no organization mirror or local Renovate preset
445
+ is needed. Renovate keeps scheduled, rate-limited updates and manual merges using a public
446
+ recommended preset plus the repository's explicit settings. Python 3.12 remains the tested
447
+ runtime; widening that range requires testing those interpreters first.
448
+
449
+ Git test fixtures discard inherited `GIT_*` routing/configuration before creating repositories.
450
+ Permission-sensitive tests must run as a non-root user. Gitea uses `just ci-check`: non-root
451
+ runners execute directly; root containers use `runuser` with an existing account named by
452
+ `CONTEXT_LOADER_CI_USER` (default `ubuntu`). That account must be able to execute the installed
453
+ `uv`, Python and `just`. No `sudo` dependency is required for this CI step.
454
+
376
455
  ## Version maintenance
377
456
 
378
457
  The versions in `pyproject.toml` and `context_loader/__init__.py`, the `tool_version` in
@@ -393,11 +472,17 @@ only verifies the public release, mirrors its exact formal tag object if missing
393
472
  the Gitea Release record. It builds no package and has no PyPI publishing credentials.
394
473
  No private endpoint or credential is needed on GitHub.
395
474
 
475
+ For environments that permit direct public reads but share an exhausted proxy quota,
476
+ set the Gitea Actions repository variable `RELEASE_PUBLIC_API_DIRECT=true`. The record
477
+ step appends only public GitHub API/PyPI hosts to the existing proxy bypass lists. Without
478
+ this explicit option, the workflow uses its inherited proxy settings.
479
+
396
480
  Release checks use Python 3.12, Git, and the runner's existing OpenSSL CLI. They compare downloaded
397
481
  PyPI file bytes, metadata and SHA-256, plus the publisher/tag/commit claims in PyPI's HTTPS-served
398
482
  provenance. This is an identity check, not an independent cryptographic Sigstore verifier.
399
483
 
400
484
  - Tag/version or expected-commit mismatch and `just check` failure stop before build/upload.
485
+ - Annotated tag headers must match the exact tag name and commit, without extra or duplicate headers.
401
486
  - The build refuses to run unless `dist/` is empty, and refuses any produced file set other than
402
487
  the current version's wheel and sdist. A leftover artifact can carry a release filename while
403
488
  holding different bytes, so it is never treated as the current release.
@@ -1,3 +1,3 @@
1
1
  """Project Context Loader."""
2
2
 
3
- __version__ = "1.3.1"
3
+ __version__ = "1.3.3"