gcf-python 0.5.1__tar.gz → 1.0.0__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 (36) hide show
  1. gcf_python-1.0.0/.github/FUNDING.yml +1 -0
  2. gcf_python-1.0.0/.github/workflows/ci.yml +30 -0
  3. {gcf_python-0.5.1 → gcf_python-1.0.0}/.github/workflows/publish.yml +5 -1
  4. {gcf_python-0.5.1 → gcf_python-1.0.0}/CHANGELOG.md +20 -0
  5. {gcf_python-0.5.1 → gcf_python-1.0.0}/LICENSE +1 -1
  6. {gcf_python-0.5.1 → gcf_python-1.0.0}/PKG-INFO +13 -3
  7. {gcf_python-0.5.1 → gcf_python-1.0.0}/README.md +11 -1
  8. {gcf_python-0.5.1 → gcf_python-1.0.0}/pyproject.toml +2 -2
  9. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/__init__.py +1 -1
  10. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/decode.py +17 -7
  11. gcf_python-1.0.0/src/gcf/decode_generic.py +505 -0
  12. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/delta.py +1 -1
  13. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/encode.py +1 -1
  14. gcf_python-1.0.0/src/gcf/generic.py +179 -0
  15. gcf_python-1.0.0/src/gcf/scalar.py +298 -0
  16. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/session.py +1 -1
  17. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/stream.py +9 -9
  18. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/stream_generic.py +7 -27
  19. gcf_python-1.0.0/tests/test_conformance_v2.py +105 -0
  20. {gcf_python-0.5.1 → gcf_python-1.0.0}/tests/test_decode.py +13 -13
  21. {gcf_python-0.5.1 → gcf_python-1.0.0}/tests/test_encode.py +2 -2
  22. {gcf_python-0.5.1 → gcf_python-1.0.0}/tests/test_generic.py +14 -14
  23. gcf_python-1.0.0/tests/test_roundtrip_v2.py +246 -0
  24. {gcf_python-0.5.1 → gcf_python-1.0.0}/tests/test_stream.py +3 -3
  25. {gcf_python-0.5.1 → gcf_python-1.0.0}/tests/test_stream_generic.py +4 -4
  26. gcf_python-0.5.1/.github/workflows/ci.yml +0 -21
  27. gcf_python-0.5.1/src/gcf/decode_generic.py +0 -255
  28. gcf_python-0.5.1/src/gcf/generic.py +0 -156
  29. {gcf_python-0.5.1 → gcf_python-1.0.0}/.gitignore +0 -0
  30. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/cli.py +0 -0
  31. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/constants.py +0 -0
  32. {gcf_python-0.5.1 → gcf_python-1.0.0}/src/gcf/types.py +0 -0
  33. {gcf_python-0.5.1 → gcf_python-1.0.0}/tests/__init__.py +0 -0
  34. {gcf_python-0.5.1 → gcf_python-1.0.0}/tests/test_delta.py +0 -0
  35. {gcf_python-0.5.1 → gcf_python-1.0.0}/tests/test_roundtrip.py +0 -0
  36. {gcf_python-0.5.1 → gcf_python-1.0.0}/tests/test_session.py +0 -0
@@ -0,0 +1 @@
1
+ github: [blackwell-systems]
@@ -0,0 +1,30 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ repository: blackwell-systems/gcf
20
+ path: gcf
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+ - run: pip install -e ".[dev]" || pip install -e . && pip install pytest
25
+ - name: Unit tests
26
+ run: pytest tests/ -v --ignore=tests/test_conformance_v2.py --ignore=tests/test_roundtrip_v2.py
27
+ - name: Conformance tests (133 fixtures)
28
+ run: pytest tests/test_conformance_v2.py -v
29
+ - name: Property-based round-trip (100K random + 100K adversarial)
30
+ run: pytest tests/test_roundtrip_v2.py -v
@@ -8,7 +8,7 @@ jobs:
8
8
  publish:
9
9
  runs-on: ubuntu-latest
10
10
  permissions:
11
- contents: read
11
+ contents: write
12
12
  id-token: write
13
13
  steps:
14
14
  - uses: actions/checkout@v4
@@ -22,3 +22,7 @@ jobs:
22
22
  env:
23
23
  TWINE_USERNAME: __token__
24
24
  TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
25
+ - name: Create GitHub release
26
+ uses: softprops/action-gh-release@v2
27
+ with:
28
+ generate_release_notes: true
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.0.0 (2026-06-10)
4
+
5
+ SPEC v2.0 implementation. 126/133 conformance fixtures passing (7 skipped: session, delta, binary UTF-8, negative zero, graph encode). 40M property-based round-trips with zero failures.
6
+
7
+ ### Breaking changes from v0.5.0
8
+
9
+ - `encode_generic` emits `GCF profile=generic` header
10
+ - `decode_generic` requires `GCF profile=` header
11
+ - Strings colliding with typed literals are quoted
12
+ - Full JSON string escaping and number grammar
13
+ - `-` for null, `~` for absent, `^` for nested attachments
14
+ - `##! summary` trailer replaces `## _summary`
15
+ - Graph encoder emits `profile=graph`
16
+
17
+ ### New
18
+
19
+ - `scalar.py`: common scalar grammar (quoting, escaping, parsing, number formatting)
20
+ - Conformance test runner (133 fixtures)
21
+ - Property-based round-trip tests (40M verified, configurable via `GCF_ITERATIONS`)
22
+
3
23
  ## v0.5.0 (2026-06-06)
4
24
 
5
25
  - `GenericStreamEncoder`: zero-buffering tabular streaming encode (begin_array/write_row/end_array/write_kv/write_section/write_inline_array)
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Blackwell Systems
3
+ Copyright (c) 2026 Dayna Blackwell
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gcf-python
3
- Version: 0.5.1
4
- Summary: Python implementation of GCF (Graph Compact Format): token-optimized wire format for LLM tool responses
3
+ Version: 1.0.0
4
+ Summary: Drop-in JSON replacement for AI pipelines. 79% fewer tokens. 90.5% 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/
7
7
  Project-URL: Specification, https://github.com/blackwell-systems/gcf
@@ -228,6 +228,16 @@ GCF wins all 6 datasets on [TOON's own benchmark](https://github.com/blackwell-s
228
228
  - [GCF vs TOON](https://gcformat.com/guide/vs-toon.html)
229
229
  - [TOON benchmark fork](https://github.com/blackwell-systems/toon/tree/gcf-comparison)
230
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>
240
+
231
241
  ## License
232
242
 
233
- MIT
243
+ MIT - [Dayna Blackwell](https://github.com/blackwell-systems)
@@ -203,6 +203,16 @@ GCF wins all 6 datasets on [TOON's own benchmark](https://github.com/blackwell-s
203
203
  - [GCF vs TOON](https://gcformat.com/guide/vs-toon.html)
204
204
  - [TOON benchmark fork](https://github.com/blackwell-systems/toon/tree/gcf-comparison)
205
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>
215
+
206
216
  ## License
207
217
 
208
- MIT
218
+ MIT - [Dayna Blackwell](https://github.com/blackwell-systems)
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gcf-python"
7
- version = "0.5.1"
8
- description = "Python implementation of GCF (Graph Compact Format): token-optimized wire format for LLM tool responses"
7
+ version = "1.0.0"
8
+ description = "Drop-in JSON replacement for AI pipelines. 79% fewer tokens. 90.5% comprehension across 10 models. Zero dependencies."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
11
11
  requires-python = ">=3.9"
@@ -65,4 +65,4 @@ __all__ = [
65
65
  "encode_with_session",
66
66
  ]
67
67
 
68
- __version__ = "0.1.3"
68
+ __version__ = "1.0.0"
@@ -35,7 +35,11 @@ def decode(input_text: str) -> Payload:
35
35
  _parse_header(header[4:], p)
36
36
 
37
37
  if not p.tool:
38
- raise DecodeError("header missing required 'tool' field")
38
+ raise DecodeError("missing_tool: header missing required 'tool' field")
39
+
40
+ # Detect delta mode.
41
+ is_delta = "delta=true" in header
42
+ valid_delta_sections = {"removed", "added", "edges_removed", "edges_added"}
39
43
 
40
44
  # Parse body: symbols and edges.
41
45
  symbols: list[Symbol] = []
@@ -48,6 +52,10 @@ def decode(input_text: str) -> Payload:
48
52
  if not line:
49
53
  continue
50
54
 
55
+ # Skip ##! summary trailer.
56
+ if line.startswith("##! "):
57
+ continue
58
+
51
59
  # Group header.
52
60
  if line.startswith("## "):
53
61
  group = line[3:]
@@ -55,6 +63,8 @@ def decode(input_text: str) -> Payload:
55
63
  bracket_idx = group.find(" [")
56
64
  if bracket_idx >= 0:
57
65
  group = group[:bracket_idx]
66
+ if is_delta and group not in valid_delta_sections:
67
+ raise DecodeError(f"malformed_delta: invalid delta section {group!r}")
58
68
  in_edges = group == "edges"
59
69
  if not in_edges:
60
70
  if group == "targets":
@@ -113,19 +123,19 @@ def _parse_header(fields: str, p: Payload) -> None:
113
123
  def _parse_symbol_line(line: str, distance: int) -> tuple[Symbol, int]:
114
124
  """Parse a symbol line into a Symbol and its local ID."""
115
125
  if not line.startswith("@"):
116
- raise DecodeError(f"expected symbol line starting with @, got {line!r}")
126
+ raise DecodeError(f"invalid_node_line: expected symbol line starting with @, got {line!r}")
117
127
 
118
128
  parts = line.split()
119
129
  if len(parts) < 5:
120
130
  raise DecodeError(
121
- f"symbol line needs at least 5 fields, got {len(parts)} in {line!r}"
131
+ f"invalid_node_line: symbol line needs at least 5 fields, got {len(parts)} in {line!r}"
122
132
  )
123
133
 
124
134
  id_str = parts[0][1:] # strip @
125
135
  try:
126
136
  sym_id = int(id_str)
127
137
  except ValueError as e:
128
- raise DecodeError(f"invalid symbol id {id_str!r}: {e}") from e
138
+ raise DecodeError(f"invalid_symbol_id: invalid symbol id {id_str!r}: {e}") from e
129
139
 
130
140
  kind = parts[1]
131
141
  kind = KIND_EXPAND.get(kind, kind)
@@ -135,7 +145,7 @@ def _parse_symbol_line(line: str, distance: int) -> tuple[Symbol, int]:
135
145
  try:
136
146
  score = float(parts[3])
137
147
  except ValueError as e:
138
- raise DecodeError(f"invalid score {parts[3]!r}: {e}") from e
148
+ raise DecodeError(f"invalid_score: invalid score {parts[3]!r}: {e}") from e
139
149
 
140
150
  provenance = parts[4]
141
151
 
@@ -157,7 +167,7 @@ def _parse_edge_line(line: str, sym_by_id: dict[int, Symbol]) -> Edge:
157
167
  ref = parts[0]
158
168
  lt_idx = ref.find("<")
159
169
  if lt_idx < 0:
160
- raise DecodeError(f"edge line missing '<' separator in {ref!r}")
170
+ raise DecodeError(f"invalid_edge_syntax: edge line missing '<' separator in {ref!r}")
161
171
 
162
172
  target_id_str = ref[1:lt_idx] # strip leading @
163
173
  source_id_str = ref[lt_idx + 2:] # strip <@
@@ -176,7 +186,7 @@ def _parse_edge_line(line: str, sym_by_id: dict[int, Symbol]) -> Edge:
176
186
  source_sym = sym_by_id.get(source_id)
177
187
  if target_sym is None or source_sym is None:
178
188
  raise DecodeError(
179
- f"edge references unknown symbol id(s): target={target_id} source={source_id}"
189
+ f"unknown_edge_reference: edge references unknown symbol id(s): target={target_id} source={source_id}"
180
190
  )
181
191
 
182
192
  edge_type = parts[1]