context-loader 1.3.2__tar.gz → 1.3.4__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.
- {context_loader-1.3.2 → context_loader-1.3.4}/PKG-INFO +101 -21
- {context_loader-1.3.2 → context_loader-1.3.4}/README.md +99 -19
- {context_loader-1.3.2 → context_loader-1.3.4}/context_loader/__init__.py +1 -1
- {context_loader-1.3.2 → context_loader-1.3.4}/context_loader/application.py +16 -7
- {context_loader-1.3.2 → context_loader-1.3.4}/context_loader/collect.py +171 -34
- {context_loader-1.3.2 → context_loader-1.3.4}/context_loader/filesystem.py +23 -11
- {context_loader-1.3.2 → context_loader-1.3.4}/context_loader/git.py +62 -0
- {context_loader-1.3.2 → context_loader-1.3.4}/context_loader/model.py +4 -4
- {context_loader-1.3.2 → context_loader-1.3.4}/context_loader/render.py +82 -45
- {context_loader-1.3.2 → context_loader-1.3.4}/pyproject.toml +2 -2
- {context_loader-1.3.2 → context_loader-1.3.4}/pyproject.toml.orig +2 -2
- {context_loader-1.3.2 → context_loader-1.3.4}/tool_cli_contract.json +1 -1
- {context_loader-1.3.2 → context_loader-1.3.4}/LICENSE +0 -0
- {context_loader-1.3.2 → context_loader-1.3.4}/context_loader/cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: context-loader
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.4
|
|
4
4
|
Summary: Render deterministic local Git context for any executor
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -204,7 +204,7 @@ License: Apache License
|
|
|
204
204
|
See the License for the specific language governing permissions and
|
|
205
205
|
limitations under the License.
|
|
206
206
|
|
|
207
|
-
Requires-Python: >=3.12, <3.
|
|
207
|
+
Requires-Python: >=3.12, <3.15
|
|
208
208
|
Project-URL: Repository, https://github.com/xuanheng-tech/context-loader
|
|
209
209
|
Project-URL: Issues, https://github.com/xuanheng-tech/context-loader/issues
|
|
210
210
|
Description-Content-Type: text/markdown
|
|
@@ -219,14 +219,14 @@ Python standard library.
|
|
|
219
219
|
|
|
220
220
|
## Open-source quick start
|
|
221
221
|
|
|
222
|
-
Current stable release: **1.3.
|
|
222
|
+
Current stable release: **1.3.4**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
|
|
223
223
|
|
|
224
224
|
```bash
|
|
225
|
-
uv tool install 'context-loader==1.3.
|
|
225
|
+
uv tool install 'context-loader==1.3.4'
|
|
226
226
|
```
|
|
227
227
|
|
|
228
228
|
```bash
|
|
229
|
-
pip install 'context-loader==1.3.
|
|
229
|
+
pip install 'context-loader==1.3.4'
|
|
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.
|
|
242
|
+
Source version: `1.3.4`. 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.
|
|
247
|
+
uv tool install 'context-loader==1.3.4'
|
|
248
248
|
```
|
|
249
249
|
|
|
250
250
|
Install via `pip`:
|
|
251
251
|
|
|
252
252
|
```bash
|
|
253
|
-
pip install 'context-loader==1.3.
|
|
253
|
+
pip install 'context-loader==1.3.4'
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
-
For development or source-based installs tracking current repository (`1.3.
|
|
256
|
+
For development or source-based installs tracking current repository (`1.3.4`):
|
|
257
257
|
|
|
258
258
|
```bash
|
|
259
259
|
uv tool install git+https://github.com/xuanheng-tech/context-loader.git
|
|
@@ -279,7 +279,7 @@ part of either distribution; run them from a checkout of the matching tag.
|
|
|
279
279
|
|
|
280
280
|
## Platform and Runtime Requirements
|
|
281
281
|
|
|
282
|
-
- **Python**: Python 3.12 (`>=3.12,<3.
|
|
282
|
+
- **Python**: Python 3.12–3.14 (`>=3.12,<3.15`). Runtime code uses only the Python standard library with zero runtime dependencies.
|
|
283
283
|
- **Git**: Requires the standard `git` CLI at the fixed absolute path `/usr/bin/git`. The
|
|
284
284
|
executable is never resolved through `PATH`, so an installation elsewhere is not used and
|
|
285
285
|
every invocation fails with exit `1`. Git runs with a sanitized environment that ignores
|
|
@@ -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.
|
|
353
|
+
"version": "1.3.4"
|
|
354
354
|
},
|
|
355
355
|
"repository": {
|
|
356
356
|
"requested_path": "/canonical/requested/path",
|
|
@@ -397,7 +397,14 @@ context, in assembly order, after the existing newline normalization and truncat
|
|
|
397
397
|
distinguishes `repository` from `global`; version 1.0.0's fixed root-file selection currently emits
|
|
398
398
|
only `repository` sources and does not add any global-file discovery.
|
|
399
399
|
|
|
400
|
-
`statuses` lists
|
|
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.
|
|
401
408
|
|
|
402
409
|
The optional `selection` object is present only on a rendered `AGENTS.md` source. Its section entries
|
|
403
410
|
contain heading, heading level, and fixed selection reasons; it never contains the original focus or
|
|
@@ -417,7 +424,7 @@ separator, with the array's brackets charged to the first entry — so the emitt
|
|
|
417
424
|
more than the budget it declares. Because quoting is counted, an array already near the cap can
|
|
418
425
|
report one path fewer than an older release did, and then says so through `list_truncated`.
|
|
419
426
|
`scan_truncated` means the depth (4) or directory-count (2,000) budget was reached, a directory
|
|
420
|
-
held more entries than one
|
|
427
|
+
held more entries than one listing retains (1,024), or a directory or entry could not be
|
|
421
428
|
read, so absence of a path is not proof of absence of the file. Nested paths
|
|
422
429
|
are sanitized with the same escaping the Markdown uses for repository-derived text, so a nested name
|
|
423
430
|
the filesystem could not decode never breaks the document;
|
|
@@ -426,6 +433,16 @@ see “Limits” for the root paths that are emitted verbatim. The corresponding
|
|
|
426
433
|
Presence is not instruction: whether a nested file applies, and its text, remain the caller's
|
|
427
434
|
judgment; Markdown output is unchanged.
|
|
428
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
|
+
|
|
429
446
|
### Compact model consumption (`--format json-compact`)
|
|
430
447
|
|
|
431
448
|
`--format json-compact` emits a `schema_version` 4 document: exactly the version-3 document with
|
|
@@ -453,7 +470,7 @@ although 1.1.0 added the top-level `statuses` key. From release 1.2.0 onward eac
|
|
|
453
470
|
names exactly one key set, and a new number is issued whenever a key set would otherwise be reused.
|
|
454
471
|
|
|
455
472
|
The JSON schema version and package version are independent: this build emits `schema_version` `3`
|
|
456
|
-
for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.
|
|
473
|
+
for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.4`. Version 1
|
|
457
474
|
and 2 are the exact shapes already published in release 1.2.0 and are never reused: because
|
|
458
475
|
`nested_context` changes the default document's key set, version 3 names the current full-document
|
|
459
476
|
key set and version 4 names the compact projection.
|
|
@@ -527,10 +544,11 @@ bullet is a per-component, read or capture bound, as each one states:
|
|
|
527
544
|
16 KiB, including a 4-KiB maximum small head
|
|
528
545
|
- `README.md`: 16 KiB
|
|
529
546
|
- Each entry file: 8 KiB
|
|
530
|
-
- 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.
|
|
531
549
|
- Declared commands: 8 KiB
|
|
532
550
|
- Directory tree: 12 KiB of listing body, 300 entries, and depth 2. Each directory is
|
|
533
|
-
|
|
551
|
+
retains and classifies up to 512 entries, keeping the alphabetically first names so the listing never
|
|
534
552
|
depends on operating system order. A directory that holds more is reported by a single
|
|
535
553
|
`Listing incomplete:` line stating the total, the limit and up to three names, and by at most
|
|
536
554
|
eight `directory_listing_incomplete` status entries naming individual directories plus one
|
|
@@ -541,8 +559,10 @@ bullet is a per-component, read or capture bound, as each one states:
|
|
|
541
559
|
alphabetical prefix and the entry budget kept it, so capped siblings displaced by the budget are
|
|
542
560
|
disclosed as their parent's truncation rather than as separate numbers.
|
|
543
561
|
Three limits act in sequence and a name survives only if all three leave room. The 512-entry
|
|
544
|
-
|
|
545
|
-
prefix is
|
|
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
|
|
546
566
|
subdirectories and its non-directories: each may take up to half of what remains before the other
|
|
547
567
|
claims the surplus, which stops alphabetically-earlier directories from evicting every file, but
|
|
548
568
|
it is a share rather than a floor -- with a single slot left the non-directories take it. The 12
|
|
@@ -561,8 +581,14 @@ bullet is a per-component, read or capture bound, as each one states:
|
|
|
561
581
|
fence say so. The heading and the code-fence lines are not metered, so the whole Markdown
|
|
562
582
|
section can exceed 12 KiB by that fixed few dozen bytes.
|
|
563
583
|
- Nested `AGENTS.md` presence scan: depth 4, 2,000 directories, at most 32 reported paths, a
|
|
564
|
-
4 KiB budget metered on the emitted array, and at most 1,024 entries
|
|
565
|
-
|
|
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.
|
|
566
592
|
- Working-tree changes: 100 paths and 4 KiB
|
|
567
593
|
- Recent commits: 8
|
|
568
594
|
- JSON-only listings have no byte budget of their own: `statuses` subjects are escaped names bounded
|
|
@@ -570,6 +596,7 @@ bullet is a per-component, read or capture bound, as each one states:
|
|
|
570
596
|
repository root the filesystem could not decode makes the machine formats exit 1 where Markdown
|
|
571
597
|
still renders
|
|
572
598
|
- Git subprocess output: 16 MiB (bounded while reading)
|
|
599
|
+
- Git status preflight: at most 128 repositories, including initialized nested submodules
|
|
573
600
|
- Candidate file validation: 16 MiB hard read bound
|
|
574
601
|
|
|
575
602
|
Truncation occurs only at complete UTF-8 and line boundaries and is marked explicitly.
|
|
@@ -593,6 +620,13 @@ the remote.
|
|
|
593
620
|
this is normal after cloning an empty remote.
|
|
594
621
|
- `Ahead / behind: not available` means the commit relationship cannot currently be computed.
|
|
595
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.
|
|
596
630
|
|
|
597
631
|
## Exit Codes
|
|
598
632
|
|
|
@@ -605,7 +639,7 @@ or candidate-file content.
|
|
|
605
639
|
|
|
606
640
|
## Not Included
|
|
607
641
|
|
|
608
|
-
Version 1.3.
|
|
642
|
+
Version 1.3.4 does not provide AI summaries, project-type detection, loading or transport of
|
|
609
643
|
nested `AGENTS.md` contents (the JSON `nested_context` field reports bounded existence only),
|
|
610
644
|
Memory retrieval, semantic ranking, ignore-rule parsing, plugins, profiles, caches, databases,
|
|
611
645
|
network services, MCP, daemons, GUIs, CI/CD, telemetry, or automatic updates.
|
|
@@ -618,6 +652,44 @@ Run the complete local check:
|
|
|
618
652
|
just check
|
|
619
653
|
```
|
|
620
654
|
|
|
655
|
+
The shared check also builds a wheel and installs it into a fresh virtual environment. Package
|
|
656
|
+
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
|
|
658
|
+
using the backend cache populated by `uv run --frozen`; the tests use only disposable repositories.
|
|
659
|
+
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.
|
|
661
|
+
|
|
662
|
+
The lockfile uses public PyPI artifact URLs; no organization mirror or local Renovate preset
|
|
663
|
+
is needed. Renovate keeps scheduled, rate-limited updates and manual merges using a public
|
|
664
|
+
recommended preset plus the repository's explicit settings. The GitHub quality gate runs on
|
|
665
|
+
Python 3.12.13, 3.13.13 and 3.14.5; the Gitea root-runner gate and release builds use 3.12.13.
|
|
666
|
+
Other interpreter/platform combinations require validation before widening the supported range.
|
|
667
|
+
|
|
668
|
+
Git test fixtures discard inherited `GIT_*` routing/configuration before creating repositories.
|
|
669
|
+
Permission-sensitive tests must run as a non-root user. Gitea uses `just ci-check`, which installs
|
|
670
|
+
Python 3.12.13 without user-level executable links and keeps its interpreter/cache under
|
|
671
|
+
`$RUNNER_TEMP/context-loader-ci` (or ignored `.ci-runtime/context-loader-ci` locally).
|
|
672
|
+
Non-root runners execute directly; root containers transfer ownership of that runtime and the
|
|
673
|
+
checkout, then use `runuser` with an existing account named by `CONTEXT_LOADER_CI_USER` (default
|
|
674
|
+
`ubuntu`). That account must be able to execute the installed `uv` and `just`; it does not need
|
|
675
|
+
access to `/root` or a root-owned Python/cache. No `sudo` dependency is required for this CI step.
|
|
676
|
+
|
|
677
|
+
### Performance baseline
|
|
678
|
+
|
|
679
|
+
On Linux with GNU `/usr/bin/time`, measure an explicit installed CLI using an existing empty
|
|
680
|
+
scratch directory:
|
|
681
|
+
|
|
682
|
+
```bash
|
|
683
|
+
python scripts/benchmark.py --cli /absolute/path/to/project-context --scratch /absolute/empty/scratch
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
The opt-in benchmark creates only synthetic repositories under that directory and reports median
|
|
687
|
+
elapsed time, peak RSS (KiB) and JSON-compact output bytes over five runs. It checks identical output
|
|
688
|
+
between runs and covers 1,000 AGENTS headings, a 5,000-file directory, and 1,000 untracked files.
|
|
689
|
+
Keep the CLI version, scratch path, interpreter and machine comparable when comparing runs;
|
|
690
|
+
measurements are observations, not timing thresholds in CI. Retain or remove the scratch directory
|
|
691
|
+
through your own temporary-resource workflow after inspection.
|
|
692
|
+
|
|
621
693
|
## Version maintenance
|
|
622
694
|
|
|
623
695
|
The versions in `pyproject.toml` and `context_loader/__init__.py`, the `tool_version` in
|
|
@@ -638,6 +710,14 @@ only verifies the public release, mirrors its exact formal tag object if missing
|
|
|
638
710
|
the Gitea Release record. It builds no package and has no PyPI publishing credentials.
|
|
639
711
|
No private endpoint or credential is needed on GitHub.
|
|
640
712
|
|
|
713
|
+
For environments that permit direct public reads but share an exhausted proxy quota,
|
|
714
|
+
set the Gitea Actions repository variable `RELEASE_PUBLIC_API_DIRECT=true`. The record
|
|
715
|
+
step appends only public GitHub API/PyPI hosts to the existing proxy bypass lists. Without
|
|
716
|
+
this explicit option, the workflow uses its inherited proxy settings.
|
|
717
|
+
Both existing bypass lists and an explicit `*` wildcard are preserved. Public HTTPS GET
|
|
718
|
+
transport failures get at most three attempts within the existing total retry budget;
|
|
719
|
+
API writes stop on transport failure so their outcome can be verified before retrying.
|
|
720
|
+
|
|
641
721
|
Release checks use Python 3.12, Git, and the runner's existing OpenSSL CLI. They compare downloaded
|
|
642
722
|
PyPI file bytes, metadata and SHA-256, plus the publisher/tag/commit claims in PyPI's HTTPS-served
|
|
643
723
|
provenance. This is an identity check, not an independent cryptographic Sigstore verifier.
|
|
@@ -8,14 +8,14 @@ Python standard library.
|
|
|
8
8
|
|
|
9
9
|
## Open-source quick start
|
|
10
10
|
|
|
11
|
-
Current stable release: **1.3.
|
|
11
|
+
Current stable release: **1.3.4**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
uv tool install 'context-loader==1.3.
|
|
14
|
+
uv tool install 'context-loader==1.3.4'
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
pip install 'context-loader==1.3.
|
|
18
|
+
pip install 'context-loader==1.3.4'
|
|
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.
|
|
31
|
+
Source version: `1.3.4`. 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.
|
|
36
|
+
uv tool install 'context-loader==1.3.4'
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Install via `pip`:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
pip install 'context-loader==1.3.
|
|
42
|
+
pip install 'context-loader==1.3.4'
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
For development or source-based installs tracking current repository (`1.3.
|
|
45
|
+
For development or source-based installs tracking current repository (`1.3.4`):
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
uv tool install git+https://github.com/xuanheng-tech/context-loader.git
|
|
@@ -68,7 +68,7 @@ part of either distribution; run them from a checkout of the matching tag.
|
|
|
68
68
|
|
|
69
69
|
## Platform and Runtime Requirements
|
|
70
70
|
|
|
71
|
-
- **Python**: Python 3.12 (`>=3.12,<3.
|
|
71
|
+
- **Python**: Python 3.12–3.14 (`>=3.12,<3.15`). Runtime code uses only the Python standard library with zero runtime dependencies.
|
|
72
72
|
- **Git**: Requires the standard `git` CLI at the fixed absolute path `/usr/bin/git`. The
|
|
73
73
|
executable is never resolved through `PATH`, so an installation elsewhere is not used and
|
|
74
74
|
every invocation fails with exit `1`. Git runs with a sanitized environment that ignores
|
|
@@ -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.
|
|
142
|
+
"version": "1.3.4"
|
|
143
143
|
},
|
|
144
144
|
"repository": {
|
|
145
145
|
"requested_path": "/canonical/requested/path",
|
|
@@ -186,7 +186,14 @@ context, in assembly order, after the existing newline normalization and truncat
|
|
|
186
186
|
distinguishes `repository` from `global`; version 1.0.0's fixed root-file selection currently emits
|
|
187
187
|
only `repository` sources and does not add any global-file discovery.
|
|
188
188
|
|
|
189
|
-
`statuses` lists
|
|
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.
|
|
190
197
|
|
|
191
198
|
The optional `selection` object is present only on a rendered `AGENTS.md` source. Its section entries
|
|
192
199
|
contain heading, heading level, and fixed selection reasons; it never contains the original focus or
|
|
@@ -206,7 +213,7 @@ separator, with the array's brackets charged to the first entry — so the emitt
|
|
|
206
213
|
more than the budget it declares. Because quoting is counted, an array already near the cap can
|
|
207
214
|
report one path fewer than an older release did, and then says so through `list_truncated`.
|
|
208
215
|
`scan_truncated` means the depth (4) or directory-count (2,000) budget was reached, a directory
|
|
209
|
-
held more entries than one
|
|
216
|
+
held more entries than one listing retains (1,024), or a directory or entry could not be
|
|
210
217
|
read, so absence of a path is not proof of absence of the file. Nested paths
|
|
211
218
|
are sanitized with the same escaping the Markdown uses for repository-derived text, so a nested name
|
|
212
219
|
the filesystem could not decode never breaks the document;
|
|
@@ -215,6 +222,16 @@ see “Limits” for the root paths that are emitted verbatim. The corresponding
|
|
|
215
222
|
Presence is not instruction: whether a nested file applies, and its text, remain the caller's
|
|
216
223
|
judgment; Markdown output is unchanged.
|
|
217
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
|
+
|
|
218
235
|
### Compact model consumption (`--format json-compact`)
|
|
219
236
|
|
|
220
237
|
`--format json-compact` emits a `schema_version` 4 document: exactly the version-3 document with
|
|
@@ -242,7 +259,7 @@ although 1.1.0 added the top-level `statuses` key. From release 1.2.0 onward eac
|
|
|
242
259
|
names exactly one key set, and a new number is issued whenever a key set would otherwise be reused.
|
|
243
260
|
|
|
244
261
|
The JSON schema version and package version are independent: this build emits `schema_version` `3`
|
|
245
|
-
for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.
|
|
262
|
+
for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.4`. Version 1
|
|
246
263
|
and 2 are the exact shapes already published in release 1.2.0 and are never reused: because
|
|
247
264
|
`nested_context` changes the default document's key set, version 3 names the current full-document
|
|
248
265
|
key set and version 4 names the compact projection.
|
|
@@ -316,10 +333,11 @@ bullet is a per-component, read or capture bound, as each one states:
|
|
|
316
333
|
16 KiB, including a 4-KiB maximum small head
|
|
317
334
|
- `README.md`: 16 KiB
|
|
318
335
|
- Each entry file: 8 KiB
|
|
319
|
-
- 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.
|
|
320
338
|
- Declared commands: 8 KiB
|
|
321
339
|
- Directory tree: 12 KiB of listing body, 300 entries, and depth 2. Each directory is
|
|
322
|
-
|
|
340
|
+
retains and classifies up to 512 entries, keeping the alphabetically first names so the listing never
|
|
323
341
|
depends on operating system order. A directory that holds more is reported by a single
|
|
324
342
|
`Listing incomplete:` line stating the total, the limit and up to three names, and by at most
|
|
325
343
|
eight `directory_listing_incomplete` status entries naming individual directories plus one
|
|
@@ -330,8 +348,10 @@ bullet is a per-component, read or capture bound, as each one states:
|
|
|
330
348
|
alphabetical prefix and the entry budget kept it, so capped siblings displaced by the budget are
|
|
331
349
|
disclosed as their parent's truncation rather than as separate numbers.
|
|
332
350
|
Three limits act in sequence and a name survives only if all three leave room. The 512-entry
|
|
333
|
-
|
|
334
|
-
prefix is
|
|
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
|
|
335
355
|
subdirectories and its non-directories: each may take up to half of what remains before the other
|
|
336
356
|
claims the surplus, which stops alphabetically-earlier directories from evicting every file, but
|
|
337
357
|
it is a share rather than a floor -- with a single slot left the non-directories take it. The 12
|
|
@@ -350,8 +370,14 @@ bullet is a per-component, read or capture bound, as each one states:
|
|
|
350
370
|
fence say so. The heading and the code-fence lines are not metered, so the whole Markdown
|
|
351
371
|
section can exceed 12 KiB by that fixed few dozen bytes.
|
|
352
372
|
- Nested `AGENTS.md` presence scan: depth 4, 2,000 directories, at most 32 reported paths, a
|
|
353
|
-
4 KiB budget metered on the emitted array, and at most 1,024 entries
|
|
354
|
-
|
|
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.
|
|
355
381
|
- Working-tree changes: 100 paths and 4 KiB
|
|
356
382
|
- Recent commits: 8
|
|
357
383
|
- JSON-only listings have no byte budget of their own: `statuses` subjects are escaped names bounded
|
|
@@ -359,6 +385,7 @@ bullet is a per-component, read or capture bound, as each one states:
|
|
|
359
385
|
repository root the filesystem could not decode makes the machine formats exit 1 where Markdown
|
|
360
386
|
still renders
|
|
361
387
|
- Git subprocess output: 16 MiB (bounded while reading)
|
|
388
|
+
- Git status preflight: at most 128 repositories, including initialized nested submodules
|
|
362
389
|
- Candidate file validation: 16 MiB hard read bound
|
|
363
390
|
|
|
364
391
|
Truncation occurs only at complete UTF-8 and line boundaries and is marked explicitly.
|
|
@@ -382,6 +409,13 @@ the remote.
|
|
|
382
409
|
this is normal after cloning an empty remote.
|
|
383
410
|
- `Ahead / behind: not available` means the commit relationship cannot currently be computed.
|
|
384
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.
|
|
385
419
|
|
|
386
420
|
## Exit Codes
|
|
387
421
|
|
|
@@ -394,7 +428,7 @@ or candidate-file content.
|
|
|
394
428
|
|
|
395
429
|
## Not Included
|
|
396
430
|
|
|
397
|
-
Version 1.3.
|
|
431
|
+
Version 1.3.4 does not provide AI summaries, project-type detection, loading or transport of
|
|
398
432
|
nested `AGENTS.md` contents (the JSON `nested_context` field reports bounded existence only),
|
|
399
433
|
Memory retrieval, semantic ranking, ignore-rule parsing, plugins, profiles, caches, databases,
|
|
400
434
|
network services, MCP, daemons, GUIs, CI/CD, telemetry, or automatic updates.
|
|
@@ -407,6 +441,44 @@ Run the complete local check:
|
|
|
407
441
|
just check
|
|
408
442
|
```
|
|
409
443
|
|
|
444
|
+
The shared check also builds a wheel and installs it into a fresh virtual environment. Package
|
|
445
|
+
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
|
|
447
|
+
using the backend cache populated by `uv run --frozen`; the tests use only disposable repositories.
|
|
448
|
+
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.
|
|
450
|
+
|
|
451
|
+
The lockfile uses public PyPI artifact URLs; no organization mirror or local Renovate preset
|
|
452
|
+
is needed. Renovate keeps scheduled, rate-limited updates and manual merges using a public
|
|
453
|
+
recommended preset plus the repository's explicit settings. The GitHub quality gate runs on
|
|
454
|
+
Python 3.12.13, 3.13.13 and 3.14.5; the Gitea root-runner gate and release builds use 3.12.13.
|
|
455
|
+
Other interpreter/platform combinations require validation before widening the supported range.
|
|
456
|
+
|
|
457
|
+
Git test fixtures discard inherited `GIT_*` routing/configuration before creating repositories.
|
|
458
|
+
Permission-sensitive tests must run as a non-root user. Gitea uses `just ci-check`, which installs
|
|
459
|
+
Python 3.12.13 without user-level executable links and keeps its interpreter/cache under
|
|
460
|
+
`$RUNNER_TEMP/context-loader-ci` (or ignored `.ci-runtime/context-loader-ci` locally).
|
|
461
|
+
Non-root runners execute directly; root containers transfer ownership of that runtime and the
|
|
462
|
+
checkout, then use `runuser` with an existing account named by `CONTEXT_LOADER_CI_USER` (default
|
|
463
|
+
`ubuntu`). That account must be able to execute the installed `uv` and `just`; it does not need
|
|
464
|
+
access to `/root` or a root-owned Python/cache. No `sudo` dependency is required for this CI step.
|
|
465
|
+
|
|
466
|
+
### Performance baseline
|
|
467
|
+
|
|
468
|
+
On Linux with GNU `/usr/bin/time`, measure an explicit installed CLI using an existing empty
|
|
469
|
+
scratch directory:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
python scripts/benchmark.py --cli /absolute/path/to/project-context --scratch /absolute/empty/scratch
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
The opt-in benchmark creates only synthetic repositories under that directory and reports median
|
|
476
|
+
elapsed time, peak RSS (KiB) and JSON-compact output bytes over five runs. It checks identical output
|
|
477
|
+
between runs and covers 1,000 AGENTS headings, a 5,000-file directory, and 1,000 untracked files.
|
|
478
|
+
Keep the CLI version, scratch path, interpreter and machine comparable when comparing runs;
|
|
479
|
+
measurements are observations, not timing thresholds in CI. Retain or remove the scratch directory
|
|
480
|
+
through your own temporary-resource workflow after inspection.
|
|
481
|
+
|
|
410
482
|
## Version maintenance
|
|
411
483
|
|
|
412
484
|
The versions in `pyproject.toml` and `context_loader/__init__.py`, the `tool_version` in
|
|
@@ -427,6 +499,14 @@ only verifies the public release, mirrors its exact formal tag object if missing
|
|
|
427
499
|
the Gitea Release record. It builds no package and has no PyPI publishing credentials.
|
|
428
500
|
No private endpoint or credential is needed on GitHub.
|
|
429
501
|
|
|
502
|
+
For environments that permit direct public reads but share an exhausted proxy quota,
|
|
503
|
+
set the Gitea Actions repository variable `RELEASE_PUBLIC_API_DIRECT=true`. The record
|
|
504
|
+
step appends only public GitHub API/PyPI hosts to the existing proxy bypass lists. Without
|
|
505
|
+
this explicit option, the workflow uses its inherited proxy settings.
|
|
506
|
+
Both existing bypass lists and an explicit `*` wildcard are preserved. Public HTTPS GET
|
|
507
|
+
transport failures get at most three attempts within the existing total retry budget;
|
|
508
|
+
API writes stop on transport failure so their outcome can be verified before retrying.
|
|
509
|
+
|
|
430
510
|
Release checks use Python 3.12, Git, and the runner's existing OpenSSL CLI. They compare downloaded
|
|
431
511
|
PyPI file bytes, metadata and SHA-256, plus the publisher/tag/commit claims in PyPI's HTTPS-served
|
|
432
512
|
provenance. This is an identity check, not an independent cryptographic Sigstore verifier.
|
|
@@ -26,7 +26,6 @@ from .model import (
|
|
|
26
26
|
from .render import (
|
|
27
27
|
RenderedSourceBody,
|
|
28
28
|
render_markdown_with_details,
|
|
29
|
-
rendered_source_contents,
|
|
30
29
|
)
|
|
31
30
|
|
|
32
31
|
# Each value names one exact document shape and is never reused: 1 and 2 are the
|
|
@@ -70,9 +69,7 @@ class ContextStatus:
|
|
|
70
69
|
code: str
|
|
71
70
|
subject_kind: str
|
|
72
71
|
subject: str
|
|
73
|
-
#
|
|
74
|
-
# deduplication must key on this and never on `subject`, or two different directories
|
|
75
|
-
# could collapse into one claim. It is not serialized.
|
|
72
|
+
# Deduplicate raw facts independently of display formatting. Not serialized.
|
|
76
73
|
identity: str = field(default="", compare=False)
|
|
77
74
|
|
|
78
75
|
|
|
@@ -191,7 +188,19 @@ def _build_statuses(
|
|
|
191
188
|
for entry in project.directory_tree.entries:
|
|
192
189
|
if entry.kind == "unreadable_directory":
|
|
193
190
|
subject = entry.path if entry.path else "."
|
|
194
|
-
statuses.append(
|
|
191
|
+
statuses.append(
|
|
192
|
+
_status("unreadable", "tree_entry", display_text(subject), identity=subject)
|
|
193
|
+
)
|
|
194
|
+
elif entry.kind == "scan_limit_directory":
|
|
195
|
+
subject = entry.path if entry.path else "."
|
|
196
|
+
statuses.append(
|
|
197
|
+
_status(
|
|
198
|
+
"directory_scan_limit_exceeded",
|
|
199
|
+
"tree_entry",
|
|
200
|
+
display_text(subject),
|
|
201
|
+
identity=subject,
|
|
202
|
+
)
|
|
203
|
+
)
|
|
195
204
|
tree = project.directory_tree
|
|
196
205
|
named = list(dict.fromkeys(tree.incomplete_directories))
|
|
197
206
|
for path in named:
|
|
@@ -212,7 +221,7 @@ def _build_statuses(
|
|
|
212
221
|
"directory_listing_incomplete",
|
|
213
222
|
"tree",
|
|
214
223
|
f"{tree.incomplete_count} directories exceeded the "
|
|
215
|
-
f"{tree.enumeration_limit}-entry per-directory
|
|
224
|
+
f"{tree.enumeration_limit}-entry per-directory retention limit; "
|
|
216
225
|
f"{unnamed} further {stem} not named individually",
|
|
217
226
|
identity=f"aggregate:{tree.incomplete_count}:{unnamed}",
|
|
218
227
|
)
|
|
@@ -254,7 +263,7 @@ def load_project_context(
|
|
|
254
263
|
raise ContextLoaderError(str(exc), exit_code=2) from None
|
|
255
264
|
rendered = render_markdown_with_details(state, project)
|
|
256
265
|
context = rendered.output.decode("utf-8")
|
|
257
|
-
rendered_bodies =
|
|
266
|
+
rendered_bodies = rendered.source_bodies
|
|
258
267
|
sources = _sources(state.repository, rendered_bodies)
|
|
259
268
|
statuses = _build_statuses(
|
|
260
269
|
project,
|