gcf-python 2.0.0__tar.gz → 2.1.1__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.
Files changed (35) hide show
  1. {gcf_python-2.0.0 → gcf_python-2.1.1}/CHANGELOG.md +12 -0
  2. {gcf_python-2.0.0 → gcf_python-2.1.1}/PKG-INFO +21 -21
  3. {gcf_python-2.0.0 → gcf_python-2.1.1}/README.md +20 -20
  4. gcf_python-2.1.1/assets/gcf-python-diagram.png +0 -0
  5. {gcf_python-2.0.0 → gcf_python-2.1.1}/pyproject.toml +1 -1
  6. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/decode.py +1 -2
  7. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/decode_generic.py +39 -9
  8. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/scalar.py +4 -1
  9. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_conformance_v2.py +10 -0
  10. {gcf_python-2.0.0 → gcf_python-2.1.1}/.github/FUNDING.yml +0 -0
  11. {gcf_python-2.0.0 → gcf_python-2.1.1}/.github/workflows/ci.yml +0 -0
  12. {gcf_python-2.0.0 → gcf_python-2.1.1}/.github/workflows/publish.yml +0 -0
  13. {gcf_python-2.0.0 → gcf_python-2.1.1}/.gitignore +0 -0
  14. {gcf_python-2.0.0 → gcf_python-2.1.1}/LICENSE +0 -0
  15. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/__init__.py +0 -0
  16. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/__main__.py +0 -0
  17. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/cli.py +0 -0
  18. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/constants.py +0 -0
  19. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/delta.py +0 -0
  20. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/encode.py +0 -0
  21. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/generic.py +0 -0
  22. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/session.py +0 -0
  23. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/stream.py +0 -0
  24. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/stream_generic.py +0 -0
  25. {gcf_python-2.0.0 → gcf_python-2.1.1}/src/gcf/types.py +0 -0
  26. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/__init__.py +0 -0
  27. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_decode.py +0 -0
  28. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_delta.py +0 -0
  29. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_encode.py +0 -0
  30. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_generic.py +0 -0
  31. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_roundtrip.py +0 -0
  32. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_roundtrip_v2.py +0 -0
  33. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_session.py +0 -0
  34. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_stream.py +0 -0
  35. {gcf_python-2.0.0 → gcf_python-2.1.1}/tests/test_stream_generic.py +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.1.0 (2026-06-14)
4
+
5
+ ### Spec v3.1
6
+
7
+ - `tool` field in graph profile header is now optional (SHOULD be present for MCP, not required)
8
+
9
+ ### Bug Fixes
10
+
11
+ - Quote strings containing commas (conformance: `inline-schema/006_inline_with_quoted_values`)
12
+ - Decode v2-format indented attachments in tabular rows (conformance: `decode/002_attachment`)
13
+ - Reject duplicate attachments on the same row (conformance: `errors-v2/027_duplicate_attachment`)
14
+
3
15
  ## v2.0.0 (2026-06-12)
4
16
 
5
17
  ### Breaking Changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gcf-python
3
- Version: 2.0.0
3
+ Version: 2.1.1
4
4
  Summary: Drop-in JSON replacement for AI pipelines. 79% fewer tokens. 90.7% comprehension across 10 models. Zero dependencies.
5
5
  Project-URL: Homepage, https://github.com/blackwell-systems/gcf-python
6
6
  Project-URL: Documentation, https://blackwell-systems.github.io/gcf/
@@ -217,26 +217,26 @@ Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows.
217
217
 
218
218
  GCF wins 13/15 datasets on the expanded [token efficiency benchmark](https://github.com/blackwell-systems/toon/tree/gcf-comparison). Full results: [gcformat.com/guide/benchmarks](https://gcformat.com/guide/benchmarks.html)
219
219
 
220
- ## Links
221
-
222
- - [Documentation](https://gcformat.com/)
223
- - [Playground](https://gcformat.com/playground.html)
224
- - [Specification](https://github.com/blackwell-systems/gcf)
225
- - [Go library](https://github.com/blackwell-systems/gcf-go)
226
- - [TypeScript library](https://github.com/blackwell-systems/gcf-typescript)
227
- - [MCP Proxy](https://github.com/blackwell-systems/gcf-proxy) (zero-code adoption)
228
- - [GCF vs TOON](https://gcformat.com/guide/vs-toon.html)
229
- - [TOON benchmark fork](https://github.com/blackwell-systems/toon/tree/gcf-comparison)
230
-
231
-
232
- <details>
233
- <summary>More links</summary>
234
-
235
- - [betterthanjson.com](https://betterthanjson.com)
236
- - [jsonalternative.com](https://jsonalternative.com)
237
- - [betterthantoon.com](https://betterthantoon.com)
238
-
239
- </details>
220
+ ## Implementations
221
+
222
+ | Language | Package | Repository |
223
+ |----------|---------|-----------|
224
+ | Go | `go get github.com/blackwell-systems/gcf-go` | [gcf-go](https://github.com/blackwell-systems/gcf-go) |
225
+ | TypeScript | `npm install @blackwell-systems/gcf` | [gcf-typescript](https://github.com/blackwell-systems/gcf-typescript) |
226
+ | Python | `pip install gcf-python` | [gcf-python](https://github.com/blackwell-systems/gcf-python) |
227
+ | Rust | `cargo add gcf` | [gcf-rust](https://github.com/blackwell-systems/gcf-rust) |
228
+ | Swift | Swift Package Manager | [gcf-swift](https://github.com/blackwell-systems/gcf-swift) |
229
+ | Kotlin | JitPack | [gcf-kotlin](https://github.com/blackwell-systems/gcf-kotlin) |
230
+ | MCP Proxy | `pip install gcf-proxy` | [gcf-proxy](https://github.com/blackwell-systems/gcf-proxy) (bidirectional, session dedup, HTTP frontend) |
231
+ | Claude Code Plugin | `/plugin install` | [gcf-claude-plugin](https://github.com/blackwell-systems/gcf-claude-plugin) (one-command install, session stats hook) |
232
+ | Codex Plugin | `codex plugin add` | [gcf-codex-plugin](https://github.com/blackwell-systems/gcf-codex-plugin) (one-command install, session stats hook) |
233
+ | VS Code | `ext install blackwell-systems.gcf-vscode` | [gcf-vscode](https://marketplace.visualstudio.com/items?itemName=blackwell-systems.gcf-vscode) (syntax highlighting) |
234
+ | n8n | `npm install n8n-nodes-gcf` | [gcf-n8n-nodes](https://github.com/blackwell-systems/gcf-n8n-nodes) (workflow encode/decode) |
235
+ | Tree-sitter | `npm install tree-sitter-gcf` | [tree-sitter-gcf](https://github.com/blackwell-systems/tree-sitter-gcf) |
236
+
237
+ Zero runtime dependencies. MIT licensed. All implementations support both generic profile (`encodeGeneric`) and graph profile (`encode`). CLI included in all 6 languages.
238
+
239
+ **Specification:** [SPEC v3.1 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 157 conformance fixtures, 33,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. All implementations at v2.1.0+ (Go v1.2.0). Cross-language 6x6 matrix verified.
240
240
 
241
241
  ## License
242
242
 
@@ -192,26 +192,26 @@ Works on dicts, lists, and primitives. Lists of uniform dicts get tabular rows.
192
192
 
193
193
  GCF wins 13/15 datasets on the expanded [token efficiency benchmark](https://github.com/blackwell-systems/toon/tree/gcf-comparison). Full results: [gcformat.com/guide/benchmarks](https://gcformat.com/guide/benchmarks.html)
194
194
 
195
- ## Links
196
-
197
- - [Documentation](https://gcformat.com/)
198
- - [Playground](https://gcformat.com/playground.html)
199
- - [Specification](https://github.com/blackwell-systems/gcf)
200
- - [Go library](https://github.com/blackwell-systems/gcf-go)
201
- - [TypeScript library](https://github.com/blackwell-systems/gcf-typescript)
202
- - [MCP Proxy](https://github.com/blackwell-systems/gcf-proxy) (zero-code adoption)
203
- - [GCF vs TOON](https://gcformat.com/guide/vs-toon.html)
204
- - [TOON benchmark fork](https://github.com/blackwell-systems/toon/tree/gcf-comparison)
205
-
206
-
207
- <details>
208
- <summary>More links</summary>
209
-
210
- - [betterthanjson.com](https://betterthanjson.com)
211
- - [jsonalternative.com](https://jsonalternative.com)
212
- - [betterthantoon.com](https://betterthantoon.com)
213
-
214
- </details>
195
+ ## Implementations
196
+
197
+ | Language | Package | Repository |
198
+ |----------|---------|-----------|
199
+ | Go | `go get github.com/blackwell-systems/gcf-go` | [gcf-go](https://github.com/blackwell-systems/gcf-go) |
200
+ | TypeScript | `npm install @blackwell-systems/gcf` | [gcf-typescript](https://github.com/blackwell-systems/gcf-typescript) |
201
+ | Python | `pip install gcf-python` | [gcf-python](https://github.com/blackwell-systems/gcf-python) |
202
+ | Rust | `cargo add gcf` | [gcf-rust](https://github.com/blackwell-systems/gcf-rust) |
203
+ | Swift | Swift Package Manager | [gcf-swift](https://github.com/blackwell-systems/gcf-swift) |
204
+ | Kotlin | JitPack | [gcf-kotlin](https://github.com/blackwell-systems/gcf-kotlin) |
205
+ | MCP Proxy | `pip install gcf-proxy` | [gcf-proxy](https://github.com/blackwell-systems/gcf-proxy) (bidirectional, session dedup, HTTP frontend) |
206
+ | Claude Code Plugin | `/plugin install` | [gcf-claude-plugin](https://github.com/blackwell-systems/gcf-claude-plugin) (one-command install, session stats hook) |
207
+ | Codex Plugin | `codex plugin add` | [gcf-codex-plugin](https://github.com/blackwell-systems/gcf-codex-plugin) (one-command install, session stats hook) |
208
+ | VS Code | `ext install blackwell-systems.gcf-vscode` | [gcf-vscode](https://marketplace.visualstudio.com/items?itemName=blackwell-systems.gcf-vscode) (syntax highlighting) |
209
+ | n8n | `npm install n8n-nodes-gcf` | [gcf-n8n-nodes](https://github.com/blackwell-systems/gcf-n8n-nodes) (workflow encode/decode) |
210
+ | Tree-sitter | `npm install tree-sitter-gcf` | [tree-sitter-gcf](https://github.com/blackwell-systems/tree-sitter-gcf) |
211
+
212
+ Zero runtime dependencies. MIT licensed. All implementations support both generic profile (`encodeGeneric`) and graph profile (`encode`). CLI included in all 6 languages.
213
+
214
+ **Specification:** [SPEC v3.1 Stable](https://github.com/blackwell-systems/gcf/blob/main/SPEC.md) with 157 conformance fixtures, 33,000,000,000+ lossless round-trips verified across 5 formats and 6 languages. All implementations at v2.1.0+ (Go v1.2.0). Cross-language 6x6 matrix verified.
215
215
 
216
216
  ## License
217
217
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gcf-python"
7
- version = "2.0.0"
7
+ version = "2.1.1"
8
8
  description = "Drop-in JSON replacement for AI pipelines. 79% fewer tokens. 90.7% comprehension across 10 models. Zero dependencies."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -34,8 +34,7 @@ def decode(input_text: str) -> Payload:
34
34
  raise DecodeError(f"invalid header, expected 'GCF ...' got {header!r}")
35
35
  _parse_header(header[4:], p)
36
36
 
37
- if not p.tool:
38
- raise DecodeError("missing_tool: header missing required 'tool' field")
37
+ # v3.1: tool field is optional (SHOULD be present for MCP tool responses, not required).
39
38
 
40
39
  # Detect delta mode.
41
40
  is_delta = "delta=true" in header
@@ -151,6 +151,17 @@ def _parse_object_body(
151
151
  i += consumed
152
152
  continue
153
153
 
154
+ # Key=value. Check before inline array so bracket patterns in quoted
155
+ # values (e.g. text="ERR[404]: Not Found") are not misinterpreted.
156
+ eq_idx = _find_kv_split(content)
157
+ if eq_idx > 0:
158
+ name = _parse_key_from_header(content[:eq_idx])
159
+ _check_dup(out, name)
160
+ out[name] = parse_scalar(content[eq_idx + 1:])
161
+ i += 1
162
+ continue
163
+
164
+ # Inline array (e.g. items[3]: a,b,c). Only reached if no = found.
154
165
  if not content.startswith("@") and not content.startswith("##"):
155
166
  bracket_idx = content.find("[")
156
167
  if bracket_idx > 0:
@@ -166,14 +177,6 @@ def _parse_object_body(
166
177
  i += 1
167
178
  continue
168
179
 
169
- eq_idx = _find_kv_split(content)
170
- if eq_idx > 0:
171
- name = _parse_key_from_header(content[:eq_idx])
172
- _check_dup(out, name)
173
- out[name] = parse_scalar(content[eq_idx + 1:])
174
- i += 1
175
- continue
176
-
177
180
  i += 1
178
181
  return i - start
179
182
 
@@ -191,7 +194,13 @@ def _find_kv_split(s: str) -> int:
191
194
  return i + 1 if i + 1 < len(s) and s[i + 1] == "=" else -1
192
195
  i += 1
193
196
  return -1
194
- return s.find("=")
197
+ eq_idx = s.find("=")
198
+ if eq_idx < 0:
199
+ return -1
200
+ bracket_idx = s.find("[")
201
+ if bracket_idx >= 0 and bracket_idx < eq_idx:
202
+ return -1
203
+ return eq_idx
195
204
 
196
205
 
197
206
  def _parse_key_from_header(s: str) -> str:
@@ -442,6 +451,10 @@ def _parse_tabular_body(
442
451
  if a_content is None:
443
452
  break
444
453
 
454
+ # Handle v2 indented attachments: strip one extra indent level.
455
+ if not a_content.startswith(".") and a_content.startswith(" ."):
456
+ a_content = a_content[2:]
457
+
445
458
  # Line starts with ".": traditional or prefixed inline.
446
459
  if a_content.startswith("."):
447
460
  rest = a_content[1:]
@@ -459,6 +472,8 @@ def _parse_tabular_body(
459
472
  p = parse_scalar(inline_vals[k], tabular_context=True)
460
473
  if p is not MISSING:
461
474
  obj[inf] = p
475
+ if att_name in attachment_values:
476
+ raise ValueError(f"duplicate_attachment: {att_name}")
462
477
  attachment_values[att_name] = obj
463
478
  i += 1
464
479
  continue
@@ -469,6 +484,8 @@ def _parse_tabular_body(
469
484
  )
470
485
  if not rows and parsed_fields:
471
486
  shared_array_schemas[att_name_t] = parsed_fields
487
+ if att_name_t in attachment_values:
488
+ raise ValueError(f"duplicate_attachment: {att_name_t}")
472
489
  attachment_values[att_name_t] = att_val
473
490
  i += consumed
474
491
  continue
@@ -503,6 +520,19 @@ def _parse_tabular_body(
503
520
  if f not in attachment_values:
504
521
  raise ValueError(f"missing_attachment: {f}")
505
522
 
523
+ # Check for extra attachment lines after all fields resolved (duplicate).
524
+ if i < len(lines):
525
+ extra_line = lines[i]
526
+ extra_content = ""
527
+ if depth == 0 or extra_line.startswith(ind):
528
+ extra_content = extra_line[len(ind):] if depth > 0 else extra_line
529
+ if not extra_content.startswith(".") and extra_content.startswith(" ."):
530
+ extra_content = extra_content[2:]
531
+ if extra_content.startswith("."):
532
+ extra_name, _ = _parse_attachment_name(extra_content[1:])
533
+ if extra_name in attachment_values:
534
+ raise ValueError(f"duplicate_attachment: {extra_name}")
535
+
506
536
  if not row_has_id or not all_att_fields:
507
537
  att_indent = ind + " "
508
538
  if i < len(lines) and lines[i].startswith(att_indent):
@@ -8,6 +8,7 @@ from typing import Any
8
8
 
9
9
  _JSON_NUMBER_RE = re.compile(r"^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$")
10
10
  _NUMERIC_LIKE_RE = re.compile(r"^[+-]\.?\d|^\.\d|^0\d")
11
+ _INLINE_ARRAY_RE = re.compile(r"\[[^\]]*\]\s*:")
11
12
  _BARE_KEY_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
12
13
 
13
14
 
@@ -36,9 +37,11 @@ def needs_quote(s: str) -> bool:
36
37
  return True
37
38
  if s[0] in ("#", "@", "."):
38
39
  return True
40
+ if _INLINE_ARRAY_RE.search(s):
41
+ return True
39
42
  for c in s:
40
43
  o = ord(c)
41
- if c in ('"', "\\", "|") or o < 0x20 or c in ("\n", "\r"):
44
+ if c in ('"', "\\", "|", ",") or o < 0x20 or c in ("\n", "\r"):
42
45
  return True
43
46
  # C1 control characters
44
47
  if 0x80 <= o <= 0x9F:
@@ -97,6 +97,16 @@ def test_conformance(rel_path, data):
97
97
  _json_norm(data["expected"]), _json_norm(got)
98
98
  ), f"decode mismatch:\n got: {got}\n exp: {data['expected']}"
99
99
 
100
+ elif op == "roundtrip":
101
+ # Encode, verify GCF output matches expected, then decode and verify round-trip.
102
+ got = encode_generic(data["input"])
103
+ if isinstance(data["expected"], str):
104
+ assert got == data["expected"], f"encode mismatch:\n got: {got!r}\n exp: {data['expected']!r}"
105
+ decoded = decode_generic(got)
106
+ assert _structural_equal(
107
+ _json_norm(data["input"]), _json_norm(decoded)
108
+ ), f"round-trip mismatch:\n input: {data['input']}\n decoded: {decoded}"
109
+
100
110
  elif op == "error":
101
111
  # v3 decoder may surface different error categories for same invalid input.
102
112
  # The requirement is that it rejects.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes