faf-python-sdk 1.1.2__py3-none-any.whl → 1.2.0__py3-none-any.whl

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.4
2
2
  Name: faf-python-sdk
3
- Version: 1.1.2
3
+ Version: 1.2.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
@@ -32,17 +32,33 @@ Description-Content-Type: text/markdown
32
32
 
33
33
  # faf-python-sdk
34
34
 
35
- **Persistent project context for Python.** Parse, validate, and score `.faf` files — the foundation other Python FAF tools build on.
35
+ **Persistent Project Context for Python. Parse, validate, score.**
36
36
 
37
- If you're building MCP servers, CI validators, or any Python tool that needs to understand project context, start here.
37
+ **FAF defines. MD instructs. AI codes.**
38
38
 
39
+ The foundation other Python FAF tools build on. If you're building MCP servers, CI validators, or any Python tool that needs to understand project context, start here.
40
+
41
+ [![FAF](https://mcpaas.live/badge/Wolfe-Jam/faf-python-sdk.svg)](https://builder.faf.one)
39
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/)
40
43
  [![Downloads](https://img.shields.io/pypi/dm/faf-python-sdk?style=for-the-badge&color=blue)](https://pypi.org/project/faf-python-sdk/)
41
- [![Tests](https://img.shields.io/badge/tests-175%20passing-brightgreen?style=for-the-badge)](https://github.com/Wolfe-Jam/faf-python-sdk)
44
+ [![Tests](https://img.shields.io/badge/tests-196%20passing-brightgreen?style=for-the-badge)](https://github.com/Wolfe-Jam/faf-python-sdk)
42
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)
43
46
 
44
47
  **Media Type:** `application/vnd.faf+yaml` (IANA registered)
45
48
 
49
+ ## What's New in v1.2.0 — The Dart Edition
50
+
51
+ 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.
52
+
53
+ The SDK's first detection capability — it now reads a `pubspec.yaml` and tells you *what kind* of Dart project it is, not just parse/validate/score. The detection KNOWLEDGE is vendored byte-identical from faf-cli (the single source) and ships in the wheel:
54
+
55
+ ```python
56
+ from faf_sdk import detect_dart_project
57
+
58
+ d = detect_dart_project(".")
59
+ print(d.app_type, d.framework) # e.g. "mobile" "Flutter"
60
+ ```
61
+
46
62
  ## What's New in v1.1.0
47
63
 
48
64
  **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.
@@ -175,6 +191,8 @@ root = find_project_root()
175
191
  | [grok-faf-mcp](https://npmjs.com/package/grok-faf-mcp) | xAI | npm |
176
192
  | [faf-cli](https://npmjs.com/package/faf-cli) | CLI | npm |
177
193
 
194
+ If `faf-python-sdk` has been useful, consider starring the repo — it helps others find it.
195
+
178
196
  ## Links
179
197
 
180
198
  - **Site:** [faf.one](https://faf.one)
@@ -0,0 +1,12 @@
1
+ faf_sdk/__init__.py,sha256=_rO6yyapx8eswbifcLkxYVOP0FCaBavvzjapi1msdkc,1611
2
+ faf_sdk/dart_detection.json,sha256=jFtKnyyMVVFsunizteFZ2pLhYC1qC2nCIWQB9SdOqKE,1195
3
+ faf_sdk/detect.py,sha256=GothQNKChKSEwke74MRy63VyBYLqOzN1j3yzjsSVpJA,6310
4
+ faf_sdk/discovery.py,sha256=3Jj3lSzPAj9l0_VjJneLAwo1dMXvhneFhcVN4m2ejOw,8643
5
+ faf_sdk/mk4.py,sha256=dA8o3YB0W19Mt_bqcR67HAw4S08zxj6IzEmubTy-pQY,5708
6
+ faf_sdk/parser.py,sha256=lJJysj52X0Q_aGhl4PMXY2PuMqASFHdVRiSZFDdampk,4925
7
+ faf_sdk/types.py,sha256=sm1ezSzCc-93bszr4ite31_xZRKjwqf2utlqN6_0QuM,6615
8
+ faf_sdk/validator.py,sha256=6uneOwar4GYUF52BnAQTu159kE4mh4RWRgG6onVbiG4,5730
9
+ faf_python_sdk-1.2.0.dist-info/METADATA,sha256=igeZU2Gia6yzOAQHAj0Xg49B6UciWl36uD6p7aR_OXk,8008
10
+ faf_python_sdk-1.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
11
+ faf_python_sdk-1.2.0.dist-info/licenses/LICENSE,sha256=ARScF5tFhbQnYO2V5QAuCwhDHcxKdOWTOV81Pxx_j7U,1065
12
+ faf_python_sdk-1.2.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.30.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
faf_sdk/__init__.py CHANGED
@@ -24,6 +24,7 @@ from .parser import parse, parse_file, stringify, FafFile
24
24
  from .validator import validate, ValidationResult
25
25
  from .mk4 import score_faf, Mk4Result, SlotState, LicenseTier
26
26
  from .discovery import find_faf_file, find_project_root, load_fafignore
27
+ from .detect import detect_dart_project, DartProject
27
28
  from .types import (
28
29
  FafData,
29
30
  ProjectInfo,
@@ -34,7 +35,7 @@ from .types import (
34
35
  AIScoring
35
36
  )
36
37
 
37
- __version__ = "1.1.2"
38
+ __version__ = "1.2.0"
38
39
  __all__ = [
39
40
  # Parser
40
41
  "parse",
@@ -53,6 +54,9 @@ __all__ = [
53
54
  "find_faf_file",
54
55
  "find_project_root",
55
56
  "load_fafignore",
57
+ # Detection (Dart/Flutter — A+B hybrid, parity with faf-cli)
58
+ "detect_dart_project",
59
+ "DartProject",
56
60
  # Types
57
61
  "FafData",
58
62
  "ProjectInfo",
@@ -0,0 +1,30 @@
1
+ {
2
+ "version": 1,
3
+ "_doc": "SINGLE SOURCE of Dart/Flutter detection KNOWLEDGE (A+B hybrid, the Truth-is-faf-cli spec). faf-cli imports this directly; faf-python-sdk vendors a synced copy (phase 2); MCPs consume the SDK (phase 3). To bolster Dart/Flutter support — a new state library, server framework, MCP dep — edit THIS file and every engine inherits it. The thin per-language LOGIC (pubspec parse, app-vs-package heuristic, priority branching) lives in code, not here.",
4
+ "flutterDeps": ["flutter"],
5
+ "mcpDeps": ["dart_mcp", "mcp_server", "mcp_dart", "dart_mcp_server", "mcp"],
6
+ "serverFrameworks": [
7
+ ["serverpod", "Serverpod"],
8
+ ["dart_frog", "Dart Frog"],
9
+ ["conduit", "Conduit"],
10
+ ["angel3_framework", "Angel3"],
11
+ ["alfred", "Alfred"],
12
+ ["shelf", "Shelf"]
13
+ ],
14
+ "stateManagement": [
15
+ ["flutter_riverpod", "Riverpod"],
16
+ ["hooks_riverpod", "Riverpod"],
17
+ ["riverpod", "Riverpod"],
18
+ ["flutter_bloc", "Bloc"],
19
+ ["bloc", "Bloc"],
20
+ ["provider", "Provider"],
21
+ ["get", "GetX"],
22
+ ["flutter_mobx", "MobX"],
23
+ ["mobx", "MobX"],
24
+ ["signals", "Signals"]
25
+ ],
26
+ "routing": [
27
+ ["go_router", "go_router"],
28
+ ["auto_route", "auto_route"]
29
+ ]
30
+ }
faf_sdk/detect.py ADDED
@@ -0,0 +1,167 @@
1
+ """
2
+ Dart/Flutter detection — CONTENT-AWARE pubspec classification (Python).
3
+
4
+ PARITY: this mirrors faf-cli src/detect/dart.ts EXACTLY. A pubspec.yaml alone
5
+ does NOT mean Flutter — the same manifest backs Flutter apps, pure-Dart CLIs,
6
+ packages, servers (Dart Frog / Shelf / Serverpod) and MCP servers
7
+ (dart_mcp / mcp_server). We read the dependencies and branch.
8
+
9
+ The detection KNOWLEDGE (which deps mean what) lives in dart_detection.json —
10
+ a byte-identical, synced copy of faf-cli's src/detect/dart-detection.json (the
11
+ single source, A+B hybrid). To bolster Dart support, edit the spec in faf-cli
12
+ (the Truth) and re-run scripts/sync-dart-spec.sh. The thin per-language LOGIC
13
+ (pubspec parse, app-vs-package heuristic, priority branching) lives here.
14
+
15
+ Behavior parity with faf-cli is PROVEN by tests/test_dart_parity.py, which runs
16
+ the SAME shared fixtures faf-cli runs in tests/detect/dart-parity.test.ts.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import os
23
+ import re
24
+ from dataclasses import dataclass
25
+ from pathlib import Path
26
+ from typing import Any, Dict, List, Optional, Set
27
+
28
+ _SPEC: Dict[str, Any] = json.loads(
29
+ (Path(__file__).parent / "dart_detection.json").read_text(encoding="utf-8")
30
+ )
31
+
32
+ # Detection KNOWLEDGE — sourced from dart_detection.json, never hand-listed here.
33
+ FLUTTER_DEPS: List[str] = list(_SPEC["flutterDeps"])
34
+ MCP_DEPS: List[str] = list(_SPEC["mcpDeps"])
35
+ SERVER_FRAMEWORKS: List[List[str]] = [list(e) for e in _SPEC["serverFrameworks"]]
36
+ STATE_MGMT: List[List[str]] = [list(e) for e in _SPEC["stateManagement"]]
37
+ ROUTING: List[List[str]] = [list(e) for e in _SPEC["routing"]]
38
+
39
+
40
+ @dataclass
41
+ class DartProject:
42
+ """Classification of a Dart/Flutter project from its pubspec.yaml."""
43
+
44
+ app_type: str # 'mobile' | 'mcp' | 'backend' | 'cli' | 'library'
45
+ is_flutter: bool
46
+ framework: str # 'Flutter' | 'Dart Frog' | 'Serverpod' | 'Shelf' | ... | ''
47
+ state_management: str # 'Riverpod' | 'Bloc' | 'Provider' | 'GetX' | ... | ''
48
+ routing: str # 'go_router' | 'auto_route' | ''
49
+ testing: str # 'flutter_test' | 'test' | ''
50
+ found: str # human-readable rationale for the .faf `# found:` comment
51
+
52
+ def to_dict(self) -> Dict[str, object]:
53
+ """Project to the faf-cli DartProject shape (camelCase) for parity checks."""
54
+ return {
55
+ "appType": self.app_type,
56
+ "isFlutter": self.is_flutter,
57
+ "framework": self.framework,
58
+ "stateManagement": self.state_management,
59
+ "routing": self.routing,
60
+ "testing": self.testing,
61
+ "found": self.found,
62
+ }
63
+
64
+
65
+ def _pubspec_deps(content: str) -> Set[str]:
66
+ """Collect dependency names from dependencies / dev_dependencies sections."""
67
+ deps: Set[str] = set()
68
+ in_deps = False
69
+ for line in content.split("\n"):
70
+ if re.match(r"^(dependencies|dev_dependencies|dependency_overrides):\s*$", line):
71
+ in_deps = True
72
+ continue
73
+ # A new top-level key (no leading whitespace) ends the dependency section.
74
+ if re.match(r"^\S", line):
75
+ in_deps = False
76
+ if in_deps:
77
+ m = re.match(r"^\s{2}([a-zA-Z0-9_]+):", line)
78
+ if m:
79
+ deps.add(m.group(1).lower())
80
+ return deps
81
+
82
+
83
+ def detect_dart_project(directory: str) -> Optional[DartProject]:
84
+ """Classify a Dart/Flutter project from its pubspec.yaml. None if not Dart."""
85
+ path = Path(directory) / "pubspec.yaml"
86
+ if not path.exists():
87
+ return None
88
+ try:
89
+ content = path.read_text(encoding="utf-8")
90
+ except OSError:
91
+ return None
92
+
93
+ deps = _pubspec_deps(content)
94
+
95
+ def has(dep: str) -> bool:
96
+ return dep.lower() in deps
97
+
98
+ # Flutter: the `flutter` SDK dep, a top-level `flutter:` section, or `sdk: flutter`.
99
+ is_flutter = (
100
+ any(has(d) for d in FLUTTER_DEPS)
101
+ or re.search(r"^flutter:\s*$", content, re.MULTILINE) is not None
102
+ or re.search(r"\bsdk:\s*flutter\b", content) is not None
103
+ )
104
+
105
+ mcp_dep = next((d for d in MCP_DEPS if has(d)), None)
106
+ server = next((e for e in SERVER_FRAMEWORKS if has(e[0])), None)
107
+ state_entry = next((e for e in STATE_MGMT if has(e[0])), None)
108
+ state_management = state_entry[1] if state_entry else ""
109
+ route_entry = next((e for e in ROUTING if has(e[0])), None)
110
+ routing = route_entry[1] if route_entry else ""
111
+ testing = "flutter_test" if has("flutter_test") else ("test" if has("test") else "")
112
+
113
+ # CLI: a top-level `executables:` section, or bin/*.dart entry points.
114
+ has_executables = re.search(r"^executables:\s*$", content, re.MULTILINE) is not None
115
+ has_bin_dart = False
116
+ bin_dir = Path(directory) / "bin"
117
+ if bin_dir.is_dir():
118
+ try:
119
+ has_bin_dart = any(f.endswith(".dart") for f in os.listdir(bin_dir))
120
+ except OSError:
121
+ has_bin_dart = False
122
+ is_cli = has_executables or has_bin_dart
123
+
124
+ framework = ""
125
+ app_type: str
126
+ found: str
127
+
128
+ if is_flutter:
129
+ framework = "Flutter"
130
+ # App vs package: an app has lib/main.dart (the entry) or `publish_to: none`;
131
+ # a reusable Flutter package has neither — it's publishable, lib/ exports only.
132
+ is_app = (Path(directory) / "lib" / "main.dart").exists() or (
133
+ re.search(r"^publish_to:\s*['\"]?none\b", content, re.MULTILINE) is not None
134
+ )
135
+ if is_app:
136
+ app_type = "mobile"
137
+ found = "pubspec.yaml (Flutter app)"
138
+ else:
139
+ app_type = "library"
140
+ found = "pubspec.yaml (Flutter package)"
141
+ elif mcp_dep:
142
+ app_type = "mcp"
143
+ found = f"pubspec.yaml + {mcp_dep} (Dart MCP server)"
144
+ elif server:
145
+ app_type = "backend"
146
+ framework = server[1]
147
+ found = f"pubspec.yaml + {server[0]} (Dart backend)"
148
+ elif is_cli:
149
+ app_type = "cli"
150
+ found = (
151
+ "pubspec.yaml executables: (Dart CLI)"
152
+ if has_executables
153
+ else "pubspec.yaml + bin/*.dart (Dart CLI)"
154
+ )
155
+ else:
156
+ app_type = "library"
157
+ found = "pubspec.yaml (Dart package)"
158
+
159
+ return DartProject(
160
+ app_type=app_type,
161
+ is_flutter=is_flutter,
162
+ framework=framework,
163
+ state_management=state_management,
164
+ routing=routing,
165
+ testing=testing,
166
+ found=found,
167
+ )
@@ -1,10 +0,0 @@
1
- faf_sdk/__init__.py,sha256=mN9DPryL9_sPv-QVw-rZGS6u8dcVDvB1UNu2GGQg7Pk,1445
2
- faf_sdk/discovery.py,sha256=3Jj3lSzPAj9l0_VjJneLAwo1dMXvhneFhcVN4m2ejOw,8643
3
- faf_sdk/mk4.py,sha256=dA8o3YB0W19Mt_bqcR67HAw4S08zxj6IzEmubTy-pQY,5708
4
- faf_sdk/parser.py,sha256=lJJysj52X0Q_aGhl4PMXY2PuMqASFHdVRiSZFDdampk,4925
5
- faf_sdk/types.py,sha256=sm1ezSzCc-93bszr4ite31_xZRKjwqf2utlqN6_0QuM,6615
6
- faf_sdk/validator.py,sha256=6uneOwar4GYUF52BnAQTu159kE4mh4RWRgG6onVbiG4,5730
7
- faf_python_sdk-1.1.2.dist-info/METADATA,sha256=ReVbpyLAD3PG_vInHUbkfJTnarFcmdBrhl_zvd_t_D4,7116
8
- faf_python_sdk-1.1.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
9
- faf_python_sdk-1.1.2.dist-info/licenses/LICENSE,sha256=ARScF5tFhbQnYO2V5QAuCwhDHcxKdOWTOV81Pxx_j7U,1065
10
- faf_python_sdk-1.1.2.dist-info/RECORD,,