context-loader 1.3.5__tar.gz → 1.3.7__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.5
3
+ Version: 1.3.7
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.5**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
222
+ Current stable release: **1.3.7**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
223
223
 
224
224
  ```bash
225
- uv tool install 'context-loader==1.3.5'
225
+ uv tool install 'context-loader==1.3.7'
226
226
  ```
227
227
 
228
228
  ```bash
229
- pip install 'context-loader==1.3.5'
229
+ pip install 'context-loader==1.3.7'
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.5`. Install its matching published release or an exact source commit.
242
+ Source version: `1.3.7`. 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.5'
247
+ uv tool install 'context-loader==1.3.7'
248
248
  ```
249
249
 
250
250
  Install via `pip`:
251
251
 
252
252
  ```bash
253
- pip install 'context-loader==1.3.5'
253
+ pip install 'context-loader==1.3.7'
254
254
  ```
255
255
 
256
- For development or source-based installs tracking current repository (`1.3.5`):
256
+ For development or source-based installs tracking current repository (`1.3.7`):
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.5"
353
+ "version": "1.3.7"
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.5`. Version 1
473
+ for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.7`. 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.
@@ -512,12 +512,27 @@ heading on the first line is still recognized as a heading.
512
512
 
513
513
  The root `AGENTS.md` is split at Markdown headings outside fenced code blocks. The output always
514
514
  starts with complete early sections fitting a 4-KiB head, then adds complete relevant sections in
515
- source order using exact normalized focus/path tokens and their parent context. Remaining headings
515
+ source order using normalized focus/path tokens and their parent context. Remaining headings
516
516
  appear in an explicit index whose body text is not loaded. With no selection signals, only the small
517
517
  head and index are emitted. Unsafe heading parsing falls back to a bounded head and an explicit
518
518
  manual-recovery notice. A head whose own headings would not fit the budget falls back to the same
519
519
  bounded head without a section index; a single instruction file never fails the whole collection.
520
520
 
521
+ Matching uses NFKC normalization and case folding. Ordinary tokens require at least three characters;
522
+ two-character Han terms such as `安全` and `部署`, and the technical abbreviations `AI`, `CD`, `CI`,
523
+ `DB`, `IO`, `JS`, `ML`, `OS`, `PR`, `QA`, `TS`, `UI` and `UX`, are also eligible. Han terms can match
524
+ within entirely Han words, so `安全` matches `安全规则`; Latin terms still require complete tokens,
525
+ so `CI` does not match `special`. Existing stopwords, scoring and byte budgets still apply. Repeating
526
+ a Han term in a heading and body does not increase its score. This is literal matching without word
527
+ segmentation, synonyms or translation.
528
+
529
+ Two distinct eligible focus terms in the same section body can select that section even when its
530
+ heading does not contain them; a single incidental body term is not enough on its own. Heading
531
+ terms keep their stronger weighting. Generic directory components such as `packages`,
532
+ `data` and `research` are ignored as path terms; exact path occurrences and specific components
533
+ remain eligible. For long instructions, use terms from the actual headings or rule text and inspect
534
+ the omitted-heading index before assuming a rule is absent.
535
+
521
536
  `Omitted source characters` is measured against the whole normalized source, so a bounded source
522
537
  scan that ends before EOF still reports the characters it could not select.
523
538
 
@@ -647,7 +662,7 @@ or candidate-file content.
647
662
 
648
663
  ## Not Included
649
664
 
650
- Version 1.3.5 does not provide AI summaries, project-type detection, loading or transport of
665
+ Version 1.3.7 does not provide AI summaries, project-type detection, loading or transport of
651
666
  nested `AGENTS.md` contents (the JSON `nested_context` field reports bounded existence only),
652
667
  Memory retrieval, semantic ranking, ignore-rule parsing, plugins, profiles, caches, databases,
653
668
  network services, MCP, daemons, GUIs, CI/CD, telemetry, or automatic updates.
@@ -8,14 +8,14 @@ Python standard library.
8
8
 
9
9
  ## Open-source quick start
10
10
 
11
- Current stable release: **1.3.5**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
11
+ Current stable release: **1.3.7**. Local CLI for coding-agent workflows; renders deterministic Markdown or JSON.
12
12
 
13
13
  ```bash
14
- uv tool install 'context-loader==1.3.5'
14
+ uv tool install 'context-loader==1.3.7'
15
15
  ```
16
16
 
17
17
  ```bash
18
- pip install 'context-loader==1.3.5'
18
+ pip install 'context-loader==1.3.7'
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.5`. Install its matching published release or an exact source commit.
31
+ Source version: `1.3.7`. 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.5'
36
+ uv tool install 'context-loader==1.3.7'
37
37
  ```
38
38
 
39
39
  Install via `pip`:
40
40
 
41
41
  ```bash
42
- pip install 'context-loader==1.3.5'
42
+ pip install 'context-loader==1.3.7'
43
43
  ```
44
44
 
45
- For development or source-based installs tracking current repository (`1.3.5`):
45
+ For development or source-based installs tracking current repository (`1.3.7`):
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.5"
142
+ "version": "1.3.7"
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.5`. Version 1
262
+ for `--format json` and `4` for `--format json-compact`, while `tool.version` is `1.3.7`. 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.
@@ -301,12 +301,27 @@ heading on the first line is still recognized as a heading.
301
301
 
302
302
  The root `AGENTS.md` is split at Markdown headings outside fenced code blocks. The output always
303
303
  starts with complete early sections fitting a 4-KiB head, then adds complete relevant sections in
304
- source order using exact normalized focus/path tokens and their parent context. Remaining headings
304
+ source order using normalized focus/path tokens and their parent context. Remaining headings
305
305
  appear in an explicit index whose body text is not loaded. With no selection signals, only the small
306
306
  head and index are emitted. Unsafe heading parsing falls back to a bounded head and an explicit
307
307
  manual-recovery notice. A head whose own headings would not fit the budget falls back to the same
308
308
  bounded head without a section index; a single instruction file never fails the whole collection.
309
309
 
310
+ Matching uses NFKC normalization and case folding. Ordinary tokens require at least three characters;
311
+ two-character Han terms such as `安全` and `部署`, and the technical abbreviations `AI`, `CD`, `CI`,
312
+ `DB`, `IO`, `JS`, `ML`, `OS`, `PR`, `QA`, `TS`, `UI` and `UX`, are also eligible. Han terms can match
313
+ within entirely Han words, so `安全` matches `安全规则`; Latin terms still require complete tokens,
314
+ so `CI` does not match `special`. Existing stopwords, scoring and byte budgets still apply. Repeating
315
+ a Han term in a heading and body does not increase its score. This is literal matching without word
316
+ segmentation, synonyms or translation.
317
+
318
+ Two distinct eligible focus terms in the same section body can select that section even when its
319
+ heading does not contain them; a single incidental body term is not enough on its own. Heading
320
+ terms keep their stronger weighting. Generic directory components such as `packages`,
321
+ `data` and `research` are ignored as path terms; exact path occurrences and specific components
322
+ remain eligible. For long instructions, use terms from the actual headings or rule text and inspect
323
+ the omitted-heading index before assuming a rule is absent.
324
+
310
325
  `Omitted source characters` is measured against the whole normalized source, so a bounded source
311
326
  scan that ends before EOF still reports the characters it could not select.
312
327
 
@@ -436,7 +451,7 @@ or candidate-file content.
436
451
 
437
452
  ## Not Included
438
453
 
439
- Version 1.3.5 does not provide AI summaries, project-type detection, loading or transport of
454
+ Version 1.3.7 does not provide AI summaries, project-type detection, loading or transport of
440
455
  nested `AGENTS.md` contents (the JSON `nested_context` field reports bounded existence only),
441
456
  Memory retrieval, semantic ranking, ignore-rule parsing, plugins, profiles, caches, databases,
442
457
  network services, MCP, daemons, GUIs, CI/CD, telemetry, or automatic updates.
@@ -1,3 +1,3 @@
1
1
  """Project Context Loader."""
2
2
 
3
- __version__ = "1.3.5"
3
+ __version__ = "1.3.7"
@@ -68,6 +68,9 @@ _MAKE_TARGET_RE = re.compile(
68
68
  )
69
69
  _ATX_HEADING_RE = re.compile(r"^ {0,3}(#{1,6})(?:[ \t]+(.*)|[ \t]*)$")
70
70
  _TOKEN_RE = re.compile(r"[^\W_]+", re.UNICODE)
71
+ _SHORT_TECHNICAL_TOKENS = frozenset(
72
+ {"ai", "cd", "ci", "db", "io", "js", "ml", "os", "pr", "qa", "ts", "ui", "ux"}
73
+ )
71
74
  _MATCH_TOKEN_STOPWORDS = frozenset(
72
75
  {
73
76
  "and",
@@ -115,9 +118,11 @@ _PATH_TOKEN_STOPWORDS = _MATCH_TOKEN_STOPWORDS | {
115
118
  "py",
116
119
  "python",
117
120
  "quant",
121
+ "research",
118
122
  "scripts",
119
123
  "src",
120
124
  }
125
+ _MIN_SECTION_RELEVANCE = 4
121
126
  _SELECTION_REASON_ORDER = (
122
127
  "head",
123
128
  "focus_match",
@@ -138,11 +143,35 @@ def _normalized_text(value: str) -> str:
138
143
  return unicodedata.normalize("NFKC", value).casefold()
139
144
 
140
145
 
146
+ def _is_han_token(value: str) -> bool:
147
+ return bool(value) and all(
148
+ unicodedata.name(character, "").startswith("CJK UNIFIED IDEOGRAPH-") for character in value
149
+ )
150
+
151
+
141
152
  def _matching_tokens(value: str, stopwords: frozenset[str]) -> frozenset[str]:
142
153
  return frozenset(
143
154
  token
144
155
  for token in _TOKEN_RE.findall(_normalized_text(value))
145
- if len(token) >= 3 and token not in stopwords
156
+ if token not in stopwords
157
+ and (
158
+ len(token) >= 3
159
+ or token in _SHORT_TECHNICAL_TOKENS
160
+ or (len(token) == 2 and _is_han_token(token))
161
+ )
162
+ )
163
+
164
+
165
+ def _matched_terms(terms: frozenset[str], tokens: frozenset[str]) -> frozenset[str]:
166
+ matches = terms & tokens
167
+ han_terms = tuple(term for term in terms - matches if _is_han_token(term))
168
+ if not han_terms:
169
+ return matches
170
+ # Han text need not delimit words with spaces. Count each query term once,
171
+ # without introducing substring matching for Latin words or abbreviations.
172
+ han_tokens = tuple(token for token in tokens if _is_han_token(token))
173
+ return matches | frozenset(
174
+ term for term in han_terms if any(term in token for token in han_tokens)
146
175
  )
147
176
 
148
177
 
@@ -334,24 +363,24 @@ def _small_head_end(content: str, sections: tuple[MarkdownSection, ...]) -> int:
334
363
  def _section_relevance(
335
364
  section: MarkdownSection, signals: _SelectionSignals
336
365
  ) -> tuple[int, frozenset[str]]:
337
- heading_tokens = frozenset(
338
- token
339
- for token in section.normalized_heading.split()
340
- if len(token) >= 3 and token not in _MATCH_TOKEN_STOPWORDS
341
- )
366
+ heading_tokens = _matching_tokens(section.normalized_heading, _MATCH_TOKEN_STOPWORDS)
342
367
  section_tokens = _matching_tokens(section.text, _MATCH_TOKEN_STOPWORDS)
343
368
  body_tokens = section_tokens - heading_tokens
344
369
  score = 0
345
370
  reasons: set[str] = set()
346
371
 
347
- focus_heading = signals.focus_tokens & heading_tokens
348
- focus_body = signals.focus_tokens & body_tokens
372
+ focus_heading = _matched_terms(signals.focus_tokens, heading_tokens)
373
+ focus_body = _matched_terms(signals.focus_tokens, body_tokens) - focus_heading
349
374
  if focus_heading or focus_body:
350
375
  reasons.add("focus_match")
351
- score += 6 * len(focus_heading) + len(focus_body)
352
-
353
- path_heading = signals.path_tokens & heading_tokens
354
- path_body = signals.path_tokens & body_tokens
376
+ focus_score = 6 * len(focus_heading) + len(focus_body)
377
+ # Two distinct terms in one body suffice without outranking a heading match.
378
+ if len(focus_body) >= 2:
379
+ focus_score = max(_MIN_SECTION_RELEVANCE, focus_score)
380
+ score += focus_score
381
+
382
+ path_heading = _matched_terms(signals.path_tokens, heading_tokens)
383
+ path_body = _matched_terms(signals.path_tokens, body_tokens) - path_heading
355
384
  exact_path = bool(
356
385
  signals.normalized_path
357
386
  and _normalized_text(signals.normalized_path) in _normalized_text(section.text)
@@ -537,7 +566,7 @@ def _select_agents_content(source: CollectedFile, signals: _SelectionSignals) ->
537
566
  for index, section in enumerate(sections):
538
567
  if signals.focus_tokens or signals.path_tokens or signals.normalized_path:
539
568
  score, section_reasons = _section_relevance(section, signals)
540
- if score >= 4:
569
+ if score >= _MIN_SECTION_RELEVANCE:
541
570
  ranked.append((score, index, section_reasons))
542
571
  ranked.sort(key=lambda item: (-item[0], sections[item[1]].start))
543
572
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "context-loader"
7
- version = "1.3.5"
7
+ version = "1.3.7"
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.5"
7
+ version = "1.3.7"
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.5"
49
+ "tool_version": "1.3.7"
50
50
  }
File without changes