faf-python-sdk 1.4.0__tar.gz → 2.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 (164) hide show
  1. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/.github/workflows/pypi.yml +8 -0
  2. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/.github/workflows/testpypi.yml +7 -7
  3. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/.gitignore +3 -0
  4. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/CHANGELOG.md +62 -0
  5. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/CLAUDE.md +2 -2
  6. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/CONTRIBUTING.md +2 -1
  7. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/PKG-INFO +28 -17
  8. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/README.md +27 -16
  9. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/faf_sdk/__init__.py +3 -3
  10. faf_python_sdk-2.0.0/faf_sdk/_kernel_yaml.py +493 -0
  11. faf_python_sdk-2.0.0/faf_sdk/_libyaml_scanner.py +874 -0
  12. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/faf_sdk/interop.py +2 -1
  13. faf_python_sdk-2.0.0/faf_sdk/mk4.py +256 -0
  14. faf_python_sdk-2.0.0/project.faf +46 -0
  15. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/pyproject.toml +1 -1
  16. faf_python_sdk-2.0.0/scripts/gen_fuzz_cases.py +117 -0
  17. faf_python_sdk-2.0.0/scripts/kernel_oracle.js +27 -0
  18. faf_python_sdk-2.0.0/scripts/package-lock.json +23 -0
  19. faf_python_sdk-2.0.0/scripts/package.json +9 -0
  20. faf_python_sdk-2.0.0/scripts/record_kernel_expected.py +51 -0
  21. faf_python_sdk-2.0.0/tests/fixtures/corpus/agents-md-facts.faf +39 -0
  22. faf_python_sdk-2.0.0/tests/fixtures/corpus/bun-sticky-faf.faf +160 -0
  23. faf_python_sdk-2.0.0/tests/fixtures/corpus/bun-sticky-zig.faf +63 -0
  24. faf_python_sdk-2.0.0/tests/fixtures/corpus/claude-faf-mcp.faf +56 -0
  25. faf_python_sdk-2.0.0/tests/fixtures/corpus/claude-fafm-sdk.faf +46 -0
  26. faf_python_sdk-2.0.0/tests/fixtures/corpus/create-faf.faf +44 -0
  27. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-agent-public.faf +149 -0
  28. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-builder-public.faf +20 -0
  29. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-chrome-extension.faf +113 -0
  30. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-clawdbot.faf +41 -0
  31. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-cli.faf +101 -0
  32. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-crate.faf +65 -0
  33. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-enterprise-public.faf +340 -0
  34. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-foundation-site.faf +41 -0
  35. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-grid.faf +39 -0
  36. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-mcp-rig.faf +51 -0
  37. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-mcp.faf +59 -0
  38. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-memory-mcp.faf +53 -0
  39. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-moltbot.faf +60 -0
  40. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-one-svelte-new.faf +39 -0
  41. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-radio-public.faf +122 -0
  42. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-radio-rust.faf +54 -0
  43. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-rust.faf +48 -0
  44. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-server-card-ref.faf +54 -0
  45. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-skills-site.faf +39 -0
  46. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-skills.faf +38 -0
  47. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-taf-demo.faf +14 -0
  48. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-taf-git.faf +255 -0
  49. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-voice-public.faf +119 -0
  50. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-vscode.faf +57 -0
  51. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-wasm-core.faf +46 -0
  52. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-wasm-gen.faf +49 -0
  53. faf_python_sdk-2.0.0/tests/fixtures/corpus/faf.faf +46 -0
  54. faf_python_sdk-2.0.0/tests/fixtures/corpus/fafipedia.faf +41 -0
  55. faf_python_sdk-2.0.0/tests/fixtures/corpus/gemini-faf-mcp.faf +200 -0
  56. faf_python_sdk-2.0.0/tests/fixtures/corpus/grok-faf-elite.faf +119 -0
  57. faf_python_sdk-2.0.0/tests/fixtures/corpus/grok-faf-mcp.faf +57 -0
  58. faf_python_sdk-2.0.0/tests/fixtures/corpus/grok-faf-plugin.faf +41 -0
  59. faf_python_sdk-2.0.0/tests/fixtures/corpus/grok-faf-voice.faf +49 -0
  60. faf_python_sdk-2.0.0/tests/fixtures/corpus/hello-faf.faf +82 -0
  61. faf_python_sdk-2.0.0/tests/fixtures/corpus/homebrew-faf.faf +41 -0
  62. faf_python_sdk-2.0.0/tests/fixtures/corpus/mcp-context-card.faf +28 -0
  63. faf_python_sdk-2.0.0/tests/fixtures/corpus/mcp-project-context.faf +34 -0
  64. faf_python_sdk-2.0.0/tests/fixtures/corpus/mcpaas-cf.faf +45 -0
  65. faf_python_sdk-2.0.0/tests/fixtures/corpus/mcpaas-sdk.faf +39 -0
  66. faf_python_sdk-2.0.0/tests/fixtures/corpus/rust-faf-mcp.faf +75 -0
  67. faf_python_sdk-2.0.0/tests/fixtures/corpus/slash-nextjs.faf +85 -0
  68. faf_python_sdk-2.0.0/tests/fixtures/corpus/slash-tokens.faf +40 -0
  69. faf_python_sdk-2.0.0/tests/fixtures/corpus/v0-zero-faf-builder.faf +3 -0
  70. faf_python_sdk-2.0.0/tests/fixtures/corpus/wjttc-public.faf +159 -0
  71. faf_python_sdk-2.0.0/tests/fixtures/corpus/wolfejam-dev.faf +118 -0
  72. faf_python_sdk-2.0.0/tests/fixtures/corpus/wolfejames-com.faf +118 -0
  73. faf_python_sdk-2.0.0/tests/fixtures/corpus/xai-faf-rag-public.faf +21 -0
  74. faf_python_sdk-2.0.0/tests/fixtures/corpus/xai-faf-rust-public.faf +98 -0
  75. faf_python_sdk-2.0.0/tests/fixtures/corpus/xai-faf-zeph.faf +49 -0
  76. faf_python_sdk-2.0.0/tests/fixtures/corpus/xai-faf-zig-public.faf +74 -0
  77. faf_python_sdk-2.0.0/tests/fixtures/corpus/zero-faf-builder.faf +50 -0
  78. faf_python_sdk-2.0.0/tests/fixtures/edge/all21-base-slotignored.faf +25 -0
  79. faf_python_sdk-2.0.0/tests/fixtures/edge/all33-slotignored.faf +38 -0
  80. faf_python_sdk-2.0.0/tests/fixtures/edge/anchors-and-aliases.faf +5 -0
  81. faf_python_sdk-2.0.0/tests/fixtures/edge/app-type-is-not-read.faf +4 -0
  82. faf_python_sdk-2.0.0/tests/fixtures/edge/base21-legacy-keys-12-markers.faf +38 -0
  83. faf_python_sdk-2.0.0/tests/fixtures/edge/base21-legacy-keys-no-markers.faf +25 -0
  84. faf_python_sdk-2.0.0/tests/fixtures/edge/base21-short-keys-12-markers.faf +38 -0
  85. faf_python_sdk-2.0.0/tests/fixtures/edge/base21-short-keys-no-markers.faf +25 -0
  86. faf_python_sdk-2.0.0/tests/fixtures/edge/bom.faf +3 -0
  87. faf_python_sdk-2.0.0/tests/fixtures/edge/comments-only.faf +2 -0
  88. faf_python_sdk-2.0.0/tests/fixtures/edge/crlf.faf +3 -0
  89. faf_python_sdk-2.0.0/tests/fixtures/edge/document-markers.faf +5 -0
  90. faf_python_sdk-2.0.0/tests/fixtures/edge/empty-file.faf +0 -0
  91. faf_python_sdk-2.0.0/tests/fixtures/edge/empty-values.faf +12 -0
  92. faf_python_sdk-2.0.0/tests/fixtures/edge/enterprise-partial.faf +29 -0
  93. faf_python_sdk-2.0.0/tests/fixtures/edge/flow-style-full.faf +1 -0
  94. faf_python_sdk-2.0.0/tests/fixtures/edge/flow-style.faf +1 -0
  95. faf_python_sdk-2.0.0/tests/fixtures/edge/full33.faf +38 -0
  96. faf_python_sdk-2.0.0/tests/fixtures/edge/legacy-keys-only.faf +10 -0
  97. faf_python_sdk-2.0.0/tests/fixtures/edge/local-tag-is-empty.faf +7 -0
  98. faf_python_sdk-2.0.0/tests/fixtures/edge/merge-key-not-applied.faf +7 -0
  99. faf_python_sdk-2.0.0/tests/fixtures/edge/nested-values.faf +11 -0
  100. faf_python_sdk-2.0.0/tests/fixtures/edge/numbers-and-bools.faf +12 -0
  101. faf_python_sdk-2.0.0/tests/fixtures/edge/placeholders-all.faf +19 -0
  102. faf_python_sdk-2.0.0/tests/fixtures/edge/placeholders-tbd-todo.faf +12 -0
  103. faf_python_sdk-2.0.0/tests/fixtures/edge/placeholders-whitespace.faf +10 -0
  104. faf_python_sdk-2.0.0/tests/fixtures/edge/rounding-1-of-8.faf +31 -0
  105. faf_python_sdk-2.0.0/tests/fixtures/edge/rounding-3-of-8.faf +33 -0
  106. faf_python_sdk-2.0.0/tests/fixtures/edge/rounding-5-of-8.faf +35 -0
  107. faf_python_sdk-2.0.0/tests/fixtures/edge/section-not-a-mapping.faf +4 -0
  108. faf_python_sdk-2.0.0/tests/fixtures/edge/short-keys.faf +10 -0
  109. faf_python_sdk-2.0.0/tests/fixtures/edge/short-vs-legacy-precedence.faf +16 -0
  110. faf_python_sdk-2.0.0/tests/fixtures/edge/slotignored-exact.faf +8 -0
  111. faf_python_sdk-2.0.0/tests/fixtures/edge/tabs-in-values.faf +4 -0
  112. faf_python_sdk-2.0.0/tests/fixtures/edge/tier-32-of-33.faf +37 -0
  113. faf_python_sdk-2.0.0/tests/fixtures/edge/tier-bronze.faf +35 -0
  114. faf_python_sdk-2.0.0/tests/fixtures/edge/tier-green.faf +32 -0
  115. faf_python_sdk-2.0.0/tests/fixtures/edge/tier-silver.faf +37 -0
  116. faf_python_sdk-2.0.0/tests/fixtures/edge/tier-yellow.faf +28 -0
  117. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-128bit-int.faf +2 -0
  118. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-alias-bomb.faf +7 -0
  119. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-bad-escape.faf +2 -0
  120. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-control-char.faf +2 -0
  121. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-duplicate-key.faf +3 -0
  122. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-duplicate-section.faf +4 -0
  123. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-flow-colon.faf +1 -0
  124. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-int-tag-mismatch.faf +2 -0
  125. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-null-tag-mismatch.faf +2 -0
  126. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-syntax.faf +2 -0
  127. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-tab-indent.faf +2 -0
  128. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-too-deep.faf +2 -0
  129. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-two-documents.faf +5 -0
  130. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-unknown-anchor.faf +2 -0
  131. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-unknown-directive.faf +4 -0
  132. faf_python_sdk-2.0.0/tests/fixtures/edge/unreadable-yaml-1.3.faf +4 -0
  133. faf_python_sdk-2.0.0/tests/fixtures/edge_cases.json +274 -0
  134. faf_python_sdk-2.0.0/tests/fixtures/fuzz_cases.json +462 -0
  135. faf_python_sdk-2.0.0/tests/fixtures/kernel_expected.json +848 -0
  136. faf_python_sdk-2.0.0/tests/parity_support.py +118 -0
  137. faf_python_sdk-2.0.0/tests/test_always33_parity.py +126 -0
  138. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/test_mk4.py +92 -23
  139. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/test_wjttc.py +4 -4
  140. faf_python_sdk-1.4.0/faf_sdk/mk4.py +0 -216
  141. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/CODE_OF_CONDUCT.md +0 -0
  142. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/LICENSE +0 -0
  143. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/SECURITY.md +0 -0
  144. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/docs/GROK-INTEGRATION.md +0 -0
  145. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/docs/TECHNICAL-SPEC.md +0 -0
  146. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/examples/basic_usage.py +0 -0
  147. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/examples/grok_integration.py +0 -0
  148. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/faf_sdk/dart_detection.json +0 -0
  149. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/faf_sdk/detect.py +0 -0
  150. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/faf_sdk/discovery.py +0 -0
  151. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/faf_sdk/parser.py +0 -0
  152. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/faf_sdk/py.typed +0 -0
  153. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/faf_sdk/types.py +0 -0
  154. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/faf_sdk/validator.py +0 -0
  155. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/scripts/sync-dart-spec.sh +0 -0
  156. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/__init__.py +0 -0
  157. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/dart_parity_fixtures.json +0 -0
  158. /faf_python_sdk-1.4.0/project.faf → /faf_python_sdk-2.0.0/tests/fixtures/corpus/faf-python-sdk.faf +0 -0
  159. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/stress_test.py +0 -0
  160. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/test_dart_parity.py +0 -0
  161. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/test_discovery.py +0 -0
  162. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/test_interop.py +0 -0
  163. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/test_parser.py +0 -0
  164. {faf_python_sdk-1.4.0 → faf_python_sdk-2.0.0}/tests/test_validator.py +0 -0
@@ -10,5 +10,13 @@ jobs:
10
10
  - uses: actions/setup-python@v5
11
11
  with:
12
12
  python-version: "3.11"
13
+ # Gate: the full suite, with the live faf-kernel oracle required, passes
14
+ # before anything is built or published.
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: "22"
18
+ - run: npm install --prefix scripts
19
+ - run: pip install -e ".[dev]"
20
+ - run: FAF_REQUIRE_KERNEL=1 python -m pytest tests/ -q
13
21
  - run: pip install build && python -m build
14
22
  - uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,13 +1,13 @@
1
1
  name: Publish to TestPyPI
2
2
 
3
- # Fires on every version tag push (e.g. `git push origin v0.1.5`).
4
- # Stage 1 of the publish flow: stage to TestPyPI for verification BEFORE
5
- # creating a GitHub Release (which then fires pypi.yml for production).
3
+ # Fires on every version tag push. Stage with a pre-release tag (e.g.
4
+ # `v2.1.0rc1` on a version-only commit), so the real version is never burned on
5
+ # TestPyPI before review.
6
6
  #
7
- # Flow:
8
- # 1. git tag v0.1.5 && git push origin v0.1.5 → this workflow runs
9
- # 2. verify install from TestPyPI → manual / via verify_install.py
10
- # 3. gh release create v0.1.5 → triggers pypi.yml (production)
7
+ # Flow (/pubpypi):
8
+ # 1. tag vX.Y.ZrcN + push → this workflow (TestPyPI)
9
+ # 2. verify install from TestPyPI, independent review
10
+ # 3. on GO: tag vX.Y.Z on main, then run pypi.yml (workflow_dispatch) from that tag
11
11
 
12
12
  on:
13
13
  push:
@@ -42,3 +42,6 @@ htmlcov/
42
42
  # OS
43
43
  .DS_Store
44
44
  Thumbs.db
45
+
46
+ # Node (kernel oracle for the parity harness)
47
+ node_modules/
@@ -3,6 +3,68 @@
3
3
  All notable changes to faf-python-sdk are documented here.
4
4
  Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
5
 
6
+ ## [2.0.0] - 2026-09-28 — The Always33 Edition
7
+
8
+ One engine, one number: faf-python-sdk scores all 33 slots exactly like faf-kernel — the same score faf-cli 8, claude-faf-mcp 7, faf-mcp 4 and grok-faf-mcp 2 give.
9
+
10
+ `score_faf(yaml)` returns what faf-kernel (Wolfe-Jam/faf-rust `crates/faf-kernel`,
11
+ npm `faf-scoring-kernel@3.0.0`) returns, on every file. A parity harness
12
+ (`tests/test_always33_parity.py`) checks score, tier, populated, empty, ignored,
13
+ active, total and every slot state against the kernel's recorded answers: 845/845
14
+ fixtures (58 real `project.faf` files from public Wolfe-Jam repos, 55 rule
15
+ fixtures, 272 YAML-layer cases, 460 seeded fuzz documents).
16
+
17
+ ### Changed (breaking — scores move)
18
+ - **Always 33 slots.** The 12 enterprise slots count unless the file marks them
19
+ `slotignored`; `slotignored` slots drop out of the denominator
20
+ (`active = 33 − ignored`). A file with the 21 base slots filled and no markers
21
+ scores 64% (21/33); the same file plus the 12 markers scores 100% (21/21).
22
+ - `score_faf(yaml, tier=...)` still accepts `tier`, but it no longer changes the
23
+ slot count: `LicenseTier.BASE` and `LicenseTier.ENTERPRISE` give the same result.
24
+ - `Mk4Result.slots` (and `to_dict()["slots"]`) always lists 33 slots, in kernel
25
+ order, under the kernel's canonical names: `stack.framework`, `stack.css`,
26
+ `stack.state`, `stack.api`, `stack.db`, `stack.pkg_manager` (were
27
+ `stack.frontend`, `stack.css_framework`, `stack.state_management`,
28
+ `stack.api_type`, `stack.database`, `stack.package_manager`). `Mk4Result` keeps
29
+ its fields.
30
+ - `tbd` and `todo` (case-insensitive) are placeholders and score as empty, with
31
+ the existing list (`none`, `null`, `n/a`, `unknown`, `not applicable`, …).
32
+ - Short keys are read: `stack.framework`, `css`, `state`, `api`, `db`,
33
+ `pkg_manager`. The legacy key is read only when the short key is empty.
34
+ - Rounding is half away from zero, as the kernel does (1/8 = 12.5% → 13, where
35
+ Python's `round()` gave 12).
36
+ - YAML is read the way the kernel reads it (serde_yaml_ng on libyaml), not with
37
+ `yaml.safe_load`: flow-style YAML, a leading BOM and tabs inside values parse
38
+ as they do in the kernel; `<<` is an ordinary key (no YAML 1.1 merge); a
39
+ redefined anchor name resolves the way the kernel resolves it (an alias
40
+ follows the kernel's anchor ids, so it can land on a later anchor); duplicate
41
+ keys, a second document, an empty `?` key inside `[...]` (`[?, a]`, `[?]`),
42
+ alias expansion past the kernel's repetition limit and nesting deeper than
43
+ 128 are unreadable, and nesting past that limit (`[`/`{` or block) is rejected
44
+ in linear time; a local tag (`!x value`) scores the slot empty.
45
+ - Unreadable YAML scores 0 with every slot empty. `score_faf` does not raise for
46
+ any `str` or `bytes` input.
47
+
48
+ ### Added
49
+ - `faf_sdk.mk4.SLOTS` (the 33 slot paths, kernel order), `TOTAL_SLOTS`,
50
+ `LEGACY_ALIASES`.
51
+ - Parity harness and fixtures under `tests/fixtures/`; the kernel oracle
52
+ (`scripts/kernel_oracle.js`, pinned in `scripts/package.json`) and
53
+ `scripts/record_kernel_expected.py`, which records the kernel's answers.
54
+
55
+ ### Upgrading
56
+ - Files without the 12 enterprise markers score lower. `faf auto` (faf-cli)
57
+ writes the 12 markers; with them, a complete 21-slot file scores 100%.
58
+ - Code that reads `result.slots` by name: use the canonical short names above.
59
+ - YAML the kernel can't read now scores 0: a duplicate key (1.4.0 kept the last
60
+ value), a second document (`---`), an empty `?` key inside `[...]`, nesting
61
+ deeper than 128. `tbd` and `todo` now count as empty.
62
+ - Rounding is half away from zero (12.5% → 13), as the kernel rounds.
63
+
64
+ ### Notes
65
+ - `generate_agents_md` / `generate_gemini_md` remain deprecated aliases in 2.0.0.
66
+ - `validate()`'s completeness score is unchanged; it is not the Mk4 score.
67
+
6
68
  ## [1.4.0] - 2026-09-08 — The Interop Edition
7
69
 
8
70
  The interop functions get their real names: `author_agents_md` / `author_gemini_md` are public, `render_*` is the impl, `generate_*` is deprecated (removed in 2.0).
@@ -1,5 +1,5 @@
1
1
  <!-- faf:start -->
2
- <!-- faf: faf-python-sdk | Python | | Python SDK for parsing, validating, scoring, and authoring .faf project context — the Mk4 engine + interop generators other Python FAF tools build on -->
2
+ <!-- faf: faf-python-sdk | Python | library | Python SDK for parsing, validating, scoring, and authoring .faf project context — the Mk4 engine + interop generators other Python FAF tools build on -->
3
3
  <!-- faf: claim=project.faf | family=FAF -->
4
4
 
5
5
  # CLAUDE.md — faf-python-sdk
@@ -29,5 +29,5 @@ Python SDK for parsing, validating, scoring, and authoring .faf project context
29
29
 
30
30
  ---
31
31
 
32
- *STATUS: BI-SYNC ACTIVE — 2026-09-07T02:08:39.970Z*
32
+ *STATUS: SYNC ACTIVE — 2026-09-28T17:59:27.771Z*
33
33
  <!-- faf:end -->
@@ -26,7 +26,8 @@ That's it. You're ready to run tests and ship a fix.
26
26
  Run the full check pass:
27
27
 
28
28
  ```bash
29
- pytest tests/ -v # all tests must pass
29
+ npm install --prefix scripts # the faf-kernel oracle (once)
30
+ FAF_REQUIRE_KERNEL=1 pytest tests/ -v # all tests must pass, incl. the live-kernel check
30
31
  mypy faf_sdk/ # strict typing — no untyped defs
31
32
  ```
32
33
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: faf-python-sdk
3
- Version: 1.4.0
3
+ Version: 2.0.0
4
4
  Summary: Persistent project context for Python — parse, validate, and score `.faf` files. The foundation other Python FAF tools (gemini-faf-mcp, custom MCP servers, CI validators) build on. IANA-registered application/vnd.faf+yaml.
5
5
  Project-URL: Homepage, https://faf.one
6
6
  Project-URL: Documentation, https://github.com/Wolfe-Jam/faf-python-sdk
@@ -41,12 +41,25 @@ The foundation other Python FAF tools build on. If you're building MCP servers,
41
41
  [![FAF](https://mcpaas.live/badge/Wolfe-Jam/faf-python-sdk.svg)](https://builder.faf.one)
42
42
  [![PyPI](https://img.shields.io/pypi/v/faf-python-sdk?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/faf-python-sdk/)
43
43
  [![Downloads](https://img.shields.io/pypi/dm/faf-python-sdk?style=for-the-badge&color=blue)](https://pypi.org/project/faf-python-sdk/)
44
- [![Tests](https://img.shields.io/badge/tests-216%20passing-brightgreen?style=for-the-badge)](https://github.com/Wolfe-Jam/faf-python-sdk)
44
+ [![Tests](https://img.shields.io/badge/tests-1070%20passing-brightgreen?style=for-the-badge)](https://github.com/Wolfe-Jam/faf-python-sdk)
45
45
  [![IANA](https://img.shields.io/badge/IANA-registered-informational?style=for-the-badge)](https://www.iana.org/assignments/media-types/application/vnd.faf+yaml)
46
46
 
47
47
  **Media Type:** `application/vnd.faf+yaml` (IANA registered)
48
48
 
49
- ## What's New in v1.4.0 — The Interop Edition
49
+ ## What's New in v2.0.0 — The Always33 Edition
50
+
51
+ One engine, one number: faf-python-sdk scores all 33 slots exactly like faf-kernel — the same score faf-cli 8, claude-faf-mcp 7, faf-mcp 4 and grok-faf-mcp 2 give.
52
+
53
+ - **Always 33 slots.** The 12 enterprise slots count unless marked `slotignored`. 21 base slots filled with no markers: 64% (21/33). The same file plus the 12 markers: 100% (21/21). `faf auto` (faf-cli) writes the markers.
54
+ - `tbd` / `todo` are placeholders; short keys (`framework`, `css`, `state`, `api`, `db`, `pkg_manager`) are read.
55
+ - YAML is read the way the kernel reads it; unreadable YAML scores 0 without raising.
56
+ - `score_faf(yaml, tier=...)` still accepts `tier`; it no longer changes the slot count.
57
+ - **Upgrading:** `result.slots` lists all 33 slots under the kernel's names (`stack.framework`, `css`, `state`, `api`, `db`, `pkg_manager`; were `frontend`, `css_framework`, `state_management`, `api_type`, `database`, `package_manager`). Code that reads slots by name needs the new names. YAML the kernel can't read now scores 0 (a duplicate key, a second document, nesting deeper than 128), and rounding is half away from zero (12.5% → 13), as the kernel does.
58
+ - Parity harness: 845/845 fixtures match faf-kernel (`faf-scoring-kernel@3.0.0`), including the `project.faf` of 58 public repos.
59
+
60
+ See [CHANGELOG.md](CHANGELOG.md) for the full list of changes.
61
+
62
+ ## v1.4.0 — The Interop Edition
50
63
 
51
64
  The interop functions get their real names: `author_agents_md` / `author_gemini_md` are public, `render_*` is the impl, `generate_*` is deprecated (removed in 2.0).
52
65
 
@@ -63,7 +76,7 @@ print(author_agents_md(faf.data.raw)) # takes the raw dict — carries top-lev
63
76
 
64
77
  Any Python FAF tool that authors an AI-context file wraps this now — never hand-roll one. `gemini-faf-mcp` 2.7.0's `faf_agents` / `faf_gemini` are the reference wrappers.
65
78
 
66
- ## What's New in v1.2.0 — The Dart Edition
79
+ ## v1.2.0 — The Dart Edition
67
80
 
68
81
  Adds `detect_dart_project()`: content-aware Dart/Flutter detection from a `pubspec.yaml` (Flutter app vs package · Dart MCP / backend / CLI / library), reproducing faf-cli's engine byte-for-byte — 20 shared fixtures, parity-tested.
69
82
 
@@ -74,7 +87,7 @@ d = detect_dart_project(".")
74
87
  print(d.app_type, d.framework) # e.g. "mobile" "Flutter"
75
88
  ```
76
89
 
77
- ## What's New in v1.1.0
90
+ ## v1.1.0
78
91
 
79
92
  **Mk4 Championship Scoring Engine** — the same 33-slot scoring algorithm used by the Rust compiler and TypeScript CLI, now in Python. Same slots, same formula, same scores. Every FAF tool in every language now agrees on what 100% means.
80
93
 
@@ -114,26 +127,24 @@ print(f"Slots: {result.populated}/{result.total} populated")
114
127
 
115
128
  ## Mk4 Scoring
116
129
 
117
- The Mk4 engine scores `.faf` files by checking 21 universal slots (project metadata, human context, tech stack). Each slot is **Populated**, **Empty**, or **Slotignored**. The score is the percentage of active slots that are populated.
130
+ The Mk4 engine scores `.faf` files against 33 slots (project metadata, human context, tech stack, and 12 enterprise slots), exactly as faf-kernel does. Each slot is **Populated**, **Empty**, or **Slotignored**. The score is populated ÷ active, where active = 33 − slotignored.
118
131
 
119
132
  ```python
120
- from faf_sdk import score_faf, LicenseTier
133
+ from faf_sdk import score_faf
121
134
 
122
- # Base scoring (21 slots)
123
135
  result = score_faf(yaml_content)
124
136
  print(result.score) # 0-100
125
- print(result.tier) # Trophy/Gold/Silver/Bronze/Green/Yellow/Red
137
+ print(result.tier) # TROPHY / GOLD / SILVER / BRONZE / GREEN / YELLOW / RED / WHITE
126
138
  print(result.populated) # slots with real data
127
- print(result.active) # total minus slotignored
128
- print(result.slots) # per-slot breakdown
129
-
130
- # Enterprise scoring (33 slots — adds monorepo/infra)
131
- result = score_faf(yaml_content, LicenseTier.ENTERPRISE)
139
+ print(result.active) # 33 minus slotignored
140
+ print(result.slots) # per-slot breakdown, 33 entries in kernel order
132
141
  ```
133
142
 
134
- **Placeholder rejection:** Values like `"null"`, `"unknown"`, `"n/a"`, `"Describe your project goal"` are detected and scored as Empty — not Populated.
143
+ **Placeholder rejection:** Values like `"null"`, `"none"`, `"unknown"`, `"n/a"`, `"tbd"`, `"todo"`, `"Describe your project goal"` (case-insensitive) are scored as Empty — not Populated.
144
+
145
+ **Slotignored:** Set any slot to `slotignored` to exclude it from scoring. A project that does not use the 12 enterprise slots marks them `slotignored` (`faf auto` writes them) and can still reach 100%.
135
146
 
136
- **Slotignored:** Set any slot to `slotignored` to exclude it from scoring. A backend-only project can mark `frontend: slotignored` and still reach 100%.
147
+ **Short keys:** `stack.framework`, `css`, `state`, `api`, `db`, `pkg_manager` are the canonical names; the legacy `frontend`, `css_framework`, `state_management`, `api_type`, `database`, `package_manager` are read when the short key is empty.
137
148
 
138
149
  ## Parsing
139
150
 
@@ -188,7 +199,7 @@ root = find_project_root()
188
199
 
189
200
  | Function | Returns | Description |
190
201
  |----------|---------|-------------|
191
- | `score_faf(yaml, tier?)` | `Mk4Result` | Mk4 score (21 or 33 slots) |
202
+ | `score_faf(yaml)` | `Mk4Result` | Mk4 score, always 33 slots (faf-kernel parity) |
192
203
  | `parse(content)` | `FafFile` | Parse YAML string |
193
204
  | `parse_file(path)` | `FafFile` | Parse from file path |
194
205
  | `validate(faf)` | `ValidationResult` | Structure validation + warnings |
@@ -9,12 +9,25 @@ The foundation other Python FAF tools build on. If you're building MCP servers,
9
9
  [![FAF](https://mcpaas.live/badge/Wolfe-Jam/faf-python-sdk.svg)](https://builder.faf.one)
10
10
  [![PyPI](https://img.shields.io/pypi/v/faf-python-sdk?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/faf-python-sdk/)
11
11
  [![Downloads](https://img.shields.io/pypi/dm/faf-python-sdk?style=for-the-badge&color=blue)](https://pypi.org/project/faf-python-sdk/)
12
- [![Tests](https://img.shields.io/badge/tests-216%20passing-brightgreen?style=for-the-badge)](https://github.com/Wolfe-Jam/faf-python-sdk)
12
+ [![Tests](https://img.shields.io/badge/tests-1070%20passing-brightgreen?style=for-the-badge)](https://github.com/Wolfe-Jam/faf-python-sdk)
13
13
  [![IANA](https://img.shields.io/badge/IANA-registered-informational?style=for-the-badge)](https://www.iana.org/assignments/media-types/application/vnd.faf+yaml)
14
14
 
15
15
  **Media Type:** `application/vnd.faf+yaml` (IANA registered)
16
16
 
17
- ## What's New in v1.4.0 — The Interop Edition
17
+ ## What's New in v2.0.0 — The Always33 Edition
18
+
19
+ One engine, one number: faf-python-sdk scores all 33 slots exactly like faf-kernel — the same score faf-cli 8, claude-faf-mcp 7, faf-mcp 4 and grok-faf-mcp 2 give.
20
+
21
+ - **Always 33 slots.** The 12 enterprise slots count unless marked `slotignored`. 21 base slots filled with no markers: 64% (21/33). The same file plus the 12 markers: 100% (21/21). `faf auto` (faf-cli) writes the markers.
22
+ - `tbd` / `todo` are placeholders; short keys (`framework`, `css`, `state`, `api`, `db`, `pkg_manager`) are read.
23
+ - YAML is read the way the kernel reads it; unreadable YAML scores 0 without raising.
24
+ - `score_faf(yaml, tier=...)` still accepts `tier`; it no longer changes the slot count.
25
+ - **Upgrading:** `result.slots` lists all 33 slots under the kernel's names (`stack.framework`, `css`, `state`, `api`, `db`, `pkg_manager`; were `frontend`, `css_framework`, `state_management`, `api_type`, `database`, `package_manager`). Code that reads slots by name needs the new names. YAML the kernel can't read now scores 0 (a duplicate key, a second document, nesting deeper than 128), and rounding is half away from zero (12.5% → 13), as the kernel does.
26
+ - Parity harness: 845/845 fixtures match faf-kernel (`faf-scoring-kernel@3.0.0`), including the `project.faf` of 58 public repos.
27
+
28
+ See [CHANGELOG.md](CHANGELOG.md) for the full list of changes.
29
+
30
+ ## v1.4.0 — The Interop Edition
18
31
 
19
32
  The interop functions get their real names: `author_agents_md` / `author_gemini_md` are public, `render_*` is the impl, `generate_*` is deprecated (removed in 2.0).
20
33
 
@@ -31,7 +44,7 @@ print(author_agents_md(faf.data.raw)) # takes the raw dict — carries top-lev
31
44
 
32
45
  Any Python FAF tool that authors an AI-context file wraps this now — never hand-roll one. `gemini-faf-mcp` 2.7.0's `faf_agents` / `faf_gemini` are the reference wrappers.
33
46
 
34
- ## What's New in v1.2.0 — The Dart Edition
47
+ ## v1.2.0 — The Dart Edition
35
48
 
36
49
  Adds `detect_dart_project()`: content-aware Dart/Flutter detection from a `pubspec.yaml` (Flutter app vs package · Dart MCP / backend / CLI / library), reproducing faf-cli's engine byte-for-byte — 20 shared fixtures, parity-tested.
37
50
 
@@ -42,7 +55,7 @@ d = detect_dart_project(".")
42
55
  print(d.app_type, d.framework) # e.g. "mobile" "Flutter"
43
56
  ```
44
57
 
45
- ## What's New in v1.1.0
58
+ ## v1.1.0
46
59
 
47
60
  **Mk4 Championship Scoring Engine** — the same 33-slot scoring algorithm used by the Rust compiler and TypeScript CLI, now in Python. Same slots, same formula, same scores. Every FAF tool in every language now agrees on what 100% means.
48
61
 
@@ -82,26 +95,24 @@ print(f"Slots: {result.populated}/{result.total} populated")
82
95
 
83
96
  ## Mk4 Scoring
84
97
 
85
- The Mk4 engine scores `.faf` files by checking 21 universal slots (project metadata, human context, tech stack). Each slot is **Populated**, **Empty**, or **Slotignored**. The score is the percentage of active slots that are populated.
98
+ The Mk4 engine scores `.faf` files against 33 slots (project metadata, human context, tech stack, and 12 enterprise slots), exactly as faf-kernel does. Each slot is **Populated**, **Empty**, or **Slotignored**. The score is populated ÷ active, where active = 33 − slotignored.
86
99
 
87
100
  ```python
88
- from faf_sdk import score_faf, LicenseTier
101
+ from faf_sdk import score_faf
89
102
 
90
- # Base scoring (21 slots)
91
103
  result = score_faf(yaml_content)
92
104
  print(result.score) # 0-100
93
- print(result.tier) # Trophy/Gold/Silver/Bronze/Green/Yellow/Red
105
+ print(result.tier) # TROPHY / GOLD / SILVER / BRONZE / GREEN / YELLOW / RED / WHITE
94
106
  print(result.populated) # slots with real data
95
- print(result.active) # total minus slotignored
96
- print(result.slots) # per-slot breakdown
97
-
98
- # Enterprise scoring (33 slots — adds monorepo/infra)
99
- result = score_faf(yaml_content, LicenseTier.ENTERPRISE)
107
+ print(result.active) # 33 minus slotignored
108
+ print(result.slots) # per-slot breakdown, 33 entries in kernel order
100
109
  ```
101
110
 
102
- **Placeholder rejection:** Values like `"null"`, `"unknown"`, `"n/a"`, `"Describe your project goal"` are detected and scored as Empty — not Populated.
111
+ **Placeholder rejection:** Values like `"null"`, `"none"`, `"unknown"`, `"n/a"`, `"tbd"`, `"todo"`, `"Describe your project goal"` (case-insensitive) are scored as Empty — not Populated.
112
+
113
+ **Slotignored:** Set any slot to `slotignored` to exclude it from scoring. A project that does not use the 12 enterprise slots marks them `slotignored` (`faf auto` writes them) and can still reach 100%.
103
114
 
104
- **Slotignored:** Set any slot to `slotignored` to exclude it from scoring. A backend-only project can mark `frontend: slotignored` and still reach 100%.
115
+ **Short keys:** `stack.framework`, `css`, `state`, `api`, `db`, `pkg_manager` are the canonical names; the legacy `frontend`, `css_framework`, `state_management`, `api_type`, `database`, `package_manager` are read when the short key is empty.
105
116
 
106
117
  ## Parsing
107
118
 
@@ -156,7 +167,7 @@ root = find_project_root()
156
167
 
157
168
  | Function | Returns | Description |
158
169
  |----------|---------|-------------|
159
- | `score_faf(yaml, tier?)` | `Mk4Result` | Mk4 score (21 or 33 slots) |
170
+ | `score_faf(yaml)` | `Mk4Result` | Mk4 score, always 33 slots (faf-kernel parity) |
160
171
  | `parse(content)` | `FafFile` | Parse YAML string |
161
172
  | `parse_file(path)` | `FafFile` | Parse from file path |
162
173
  | `validate(faf)` | `ValidationResult` | Structure validation + warnings |
@@ -30,8 +30,8 @@ from .interop import (
30
30
  author_gemini_md,
31
31
  render_agents_md,
32
32
  render_gemini_md,
33
- generate_agents_md, # deprecated alias — removed in 2.0
34
- generate_gemini_md, # deprecated alias — removed in 2.0
33
+ generate_agents_md, # deprecated alias — kept in 2.0, removal planned
34
+ generate_gemini_md, # deprecated alias — kept in 2.0, removal planned
35
35
  faf_meta_tag,
36
36
  )
37
37
  from .types import (
@@ -44,7 +44,7 @@ from .types import (
44
44
  AIScoring
45
45
  )
46
46
 
47
- __version__ = "1.4.0"
47
+ __version__ = "2.0.0"
48
48
  __all__ = [
49
49
  # Parser
50
50
  "parse",