iil-reflex 0.1.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.
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.egg
9
+ .venv/
10
+ venv/
11
+ .env
12
+ .env.test
13
+ *.so
14
+ .pytest_cache/
15
+ .coverage
16
+ htmlcov/
17
+ .ruff_cache/
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: iil-reflex
3
+ Version: 0.1.0
4
+ Summary: REFLEX — Reflexive Evidence-based Loop for UI Development
5
+ Project-URL: Homepage, https://github.com/achimdehnert/iil-reflex
6
+ Project-URL: Repository, https://github.com/achimdehnert/iil-reflex
7
+ Project-URL: Documentation, https://knowledge.iil.pet/doc/adr-162-reflex
8
+ Author-email: Achim Dehnert <achim@iil.gmbh>
9
+ License: MIT
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Quality Assurance
16
+ Classifier: Topic :: Software Development :: Testing
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: iil-promptfw<1,>=0.7.0
19
+ Requires-Dist: pyyaml>=6.0
20
+ Provides-Extra: aifw
21
+ Requires-Dist: iil-aifw>=0.9.0; extra == 'aifw'
22
+ Provides-Extra: all
23
+ Requires-Dist: iil-aifw>=0.9.0; extra == 'all'
24
+ Requires-Dist: litellm>=1.40; extra == 'all'
25
+ Requires-Dist: playwright>=1.40; extra == 'all'
26
+ Requires-Dist: pytest-cov; extra == 'all'
27
+ Requires-Dist: pytest>=8.0; extra == 'all'
28
+ Requires-Dist: ruff>=0.4; extra == 'all'
29
+ Provides-Extra: dev
30
+ Requires-Dist: litellm>=1.40; extra == 'dev'
31
+ Requires-Dist: pytest-cov; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.4; extra == 'dev'
34
+ Provides-Extra: llm
35
+ Requires-Dist: litellm>=1.40; extra == 'llm'
36
+ Provides-Extra: playwright
37
+ Requires-Dist: playwright>=1.40; extra == 'playwright'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # iil-reflex
41
+
42
+ **REFLEX — Reflexive Evidence-based Loop for UI Development**
43
+
44
+ Evidence-based UI development methodology with LLM-powered domain agent,
45
+ UC quality checker, and failure classifier. Pure Python — no Django dependency.
46
+
47
+ ## Architecture
48
+
49
+ ```
50
+ reflex/
51
+ ├── agent.py # DomainAgent (variable domain, LLM-powered)
52
+ ├── quality.py # UC Quality Checker (11 criteria, rule-based)
53
+ ├── classify.py # Failure Classifier (decision tree + LLM)
54
+ ├── config.py # ReflexConfig from reflex.yaml
55
+ ├── providers.py # KnowledgeProvider, DocumentProvider (Protocol)
56
+ ├── types.py # Dataclasses (Results, Questions, Entries)
57
+ └── templates/ # promptfw .jinja2 templates (6 templates)
58
+ ```
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ pip install iil-reflex
64
+ # Optional: Playwright for Zirkel 2
65
+ pip install iil-reflex[playwright]
66
+ ```
67
+
68
+ ## Quick Start
69
+
70
+ ```python
71
+ from reflex.agent import DomainAgent
72
+ from reflex.config import ReflexConfig
73
+ from reflex.quality import UCQualityChecker
74
+
75
+ # 1. Load hub config
76
+ config = ReflexConfig.from_yaml("reflex.yaml")
77
+
78
+ # 2. UC Quality Check (rule-based, no LLM needed)
79
+ checker = UCQualityChecker(config)
80
+ result = checker.check(uc_text="...", uc_slug="sds-upload")
81
+ print(f"Score: {result.score_percent}%, Passed: {result.passed}")
82
+
83
+ # 3. Domain Agent (LLM-powered)
84
+ agent = DomainAgent(
85
+ config=config,
86
+ llm=your_llm_provider,
87
+ knowledge=your_knowledge_provider, # optional
88
+ documents=your_document_provider, # optional
89
+ )
90
+ research = agent.research("SDS Upload Pipeline")
91
+ questions = agent.generate_interview(research)
92
+ kb = agent.distill_kb(research, expert_answers={...})
93
+ ```
94
+
95
+ ## Hub Configuration (reflex.yaml)
96
+
97
+ ```yaml
98
+ hub_name: risk-hub
99
+ vertical: chemical_safety
100
+ domain_keywords: ["SDS", "CAS", "GHS", "REACH"]
101
+ quality:
102
+ min_acceptance_criteria: 2
103
+ max_uc_steps: 7
104
+ require_error_cases: true
105
+ viewports:
106
+ - {name: mobile, width: 375, height: 812}
107
+ - {name: desktop, width: 1280, height: 800}
108
+ htmx_patterns:
109
+ banned: ["hx-boost"]
110
+ required_on_forms: ["hx-indicator"]
111
+ permissions_matrix:
112
+ /substances/: {anonymous: 302, viewer: 200, admin: 200}
113
+ ```
114
+
115
+ ## Provider Pattern
116
+
117
+ All external dependencies use Protocols (Dependency Inversion):
118
+
119
+ ```python
120
+ from reflex.providers import KnowledgeProvider, LLMProvider
121
+
122
+ class OutlineProvider(KnowledgeProvider):
123
+ def search(self, query, limit=5):
124
+ return mcp3_search_knowledge(query, limit=limit)
125
+
126
+ class GroqProvider(LLMProvider):
127
+ def complete(self, messages, action_code=""):
128
+ return groq_client.chat(messages=messages)
129
+ ```
130
+
131
+ Mock providers included for testing:
132
+ `MockKnowledgeProvider`, `MockDocumentProvider`, `MockLLMProvider`
133
+
134
+ ## REFLEX Methodology
135
+
136
+ Three quality circles — no artifact without evidence:
137
+
138
+ 1. **Zirkel 0** — Domain KB (DomainAgent + Expert sign-off)
139
+ 2. **Zirkel 1** — UC Quality (11 criteria, 100% score required)
140
+ 3. **Zirkel 2** — Playwright Tests (1 test per acceptance criterion)
141
+
142
+ Failure Classification:
143
+ - **UC_PROBLEM** → UC needs revision (Zirkel 1 restart)
144
+ - **UI_PROBLEM** → Wireframe needs fix
145
+ - **INFRA_PROBLEM** → Server/browser/network issue
146
+
147
+ ## Dependencies
148
+
149
+ - `iil-promptfw>=0.7.0` — prompt template rendering
150
+ - `pyyaml>=6.0` — config file parsing
151
+ - Optional: `playwright` — for Zirkel 2 test execution
152
+
153
+ ## Related
154
+
155
+ - [ADR-162](https://knowledge.iil.pet/doc/adr-162-reflex) — Full ADR
156
+ - [iil-promptfw](https://github.com/achimdehnert/promptfw) — Prompt framework
157
+ - Platform: [achimdehnert/platform](https://github.com/achimdehnert/platform)
158
+
159
+ ## License
160
+
161
+ MIT
@@ -0,0 +1,122 @@
1
+ # iil-reflex
2
+
3
+ **REFLEX — Reflexive Evidence-based Loop for UI Development**
4
+
5
+ Evidence-based UI development methodology with LLM-powered domain agent,
6
+ UC quality checker, and failure classifier. Pure Python — no Django dependency.
7
+
8
+ ## Architecture
9
+
10
+ ```
11
+ reflex/
12
+ ├── agent.py # DomainAgent (variable domain, LLM-powered)
13
+ ├── quality.py # UC Quality Checker (11 criteria, rule-based)
14
+ ├── classify.py # Failure Classifier (decision tree + LLM)
15
+ ├── config.py # ReflexConfig from reflex.yaml
16
+ ├── providers.py # KnowledgeProvider, DocumentProvider (Protocol)
17
+ ├── types.py # Dataclasses (Results, Questions, Entries)
18
+ └── templates/ # promptfw .jinja2 templates (6 templates)
19
+ ```
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install iil-reflex
25
+ # Optional: Playwright for Zirkel 2
26
+ pip install iil-reflex[playwright]
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ```python
32
+ from reflex.agent import DomainAgent
33
+ from reflex.config import ReflexConfig
34
+ from reflex.quality import UCQualityChecker
35
+
36
+ # 1. Load hub config
37
+ config = ReflexConfig.from_yaml("reflex.yaml")
38
+
39
+ # 2. UC Quality Check (rule-based, no LLM needed)
40
+ checker = UCQualityChecker(config)
41
+ result = checker.check(uc_text="...", uc_slug="sds-upload")
42
+ print(f"Score: {result.score_percent}%, Passed: {result.passed}")
43
+
44
+ # 3. Domain Agent (LLM-powered)
45
+ agent = DomainAgent(
46
+ config=config,
47
+ llm=your_llm_provider,
48
+ knowledge=your_knowledge_provider, # optional
49
+ documents=your_document_provider, # optional
50
+ )
51
+ research = agent.research("SDS Upload Pipeline")
52
+ questions = agent.generate_interview(research)
53
+ kb = agent.distill_kb(research, expert_answers={...})
54
+ ```
55
+
56
+ ## Hub Configuration (reflex.yaml)
57
+
58
+ ```yaml
59
+ hub_name: risk-hub
60
+ vertical: chemical_safety
61
+ domain_keywords: ["SDS", "CAS", "GHS", "REACH"]
62
+ quality:
63
+ min_acceptance_criteria: 2
64
+ max_uc_steps: 7
65
+ require_error_cases: true
66
+ viewports:
67
+ - {name: mobile, width: 375, height: 812}
68
+ - {name: desktop, width: 1280, height: 800}
69
+ htmx_patterns:
70
+ banned: ["hx-boost"]
71
+ required_on_forms: ["hx-indicator"]
72
+ permissions_matrix:
73
+ /substances/: {anonymous: 302, viewer: 200, admin: 200}
74
+ ```
75
+
76
+ ## Provider Pattern
77
+
78
+ All external dependencies use Protocols (Dependency Inversion):
79
+
80
+ ```python
81
+ from reflex.providers import KnowledgeProvider, LLMProvider
82
+
83
+ class OutlineProvider(KnowledgeProvider):
84
+ def search(self, query, limit=5):
85
+ return mcp3_search_knowledge(query, limit=limit)
86
+
87
+ class GroqProvider(LLMProvider):
88
+ def complete(self, messages, action_code=""):
89
+ return groq_client.chat(messages=messages)
90
+ ```
91
+
92
+ Mock providers included for testing:
93
+ `MockKnowledgeProvider`, `MockDocumentProvider`, `MockLLMProvider`
94
+
95
+ ## REFLEX Methodology
96
+
97
+ Three quality circles — no artifact without evidence:
98
+
99
+ 1. **Zirkel 0** — Domain KB (DomainAgent + Expert sign-off)
100
+ 2. **Zirkel 1** — UC Quality (11 criteria, 100% score required)
101
+ 3. **Zirkel 2** — Playwright Tests (1 test per acceptance criterion)
102
+
103
+ Failure Classification:
104
+ - **UC_PROBLEM** → UC needs revision (Zirkel 1 restart)
105
+ - **UI_PROBLEM** → Wireframe needs fix
106
+ - **INFRA_PROBLEM** → Server/browser/network issue
107
+
108
+ ## Dependencies
109
+
110
+ - `iil-promptfw>=0.7.0` — prompt template rendering
111
+ - `pyyaml>=6.0` — config file parsing
112
+ - Optional: `playwright` — for Zirkel 2 test execution
113
+
114
+ ## Related
115
+
116
+ - [ADR-162](https://knowledge.iil.pet/doc/adr-162-reflex) — Full ADR
117
+ - [iil-promptfw](https://github.com/achimdehnert/promptfw) — Prompt framework
118
+ - Platform: [achimdehnert/platform](https://github.com/achimdehnert/platform)
119
+
120
+ ## License
121
+
122
+ MIT
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "iil-reflex"
7
+ version = "0.1.0"
8
+ description = "REFLEX — Reflexive Evidence-based Loop for UI Development"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ { name = "Achim Dehnert", email = "achim@iil.gmbh" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: Software Development :: Quality Assurance",
22
+ "Topic :: Software Development :: Testing",
23
+ ]
24
+ dependencies = [
25
+ "iil-promptfw>=0.7.0,<1",
26
+ "pyyaml>=6.0",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ llm = ["litellm>=1.40"]
31
+ aifw = ["iil-aifw>=0.9.0"]
32
+ playwright = ["playwright>=1.40"]
33
+ dev = [
34
+ "pytest>=8.0",
35
+ "pytest-cov",
36
+ "ruff>=0.4",
37
+ "litellm>=1.40",
38
+ ]
39
+ all = [
40
+ "iil-reflex[llm,aifw,playwright,dev]",
41
+ ]
42
+
43
+ [project.scripts]
44
+ reflex = "reflex.__main__:main"
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/achimdehnert/iil-reflex"
48
+ Repository = "https://github.com/achimdehnert/iil-reflex"
49
+ Documentation = "https://knowledge.iil.pet/doc/adr-162-reflex"
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ exclude = [".windsurf", ".github"]
53
+
54
+ [tool.hatch.build.targets.wheel]
55
+ packages = ["reflex"]
56
+
57
+
58
+ [tool.ruff]
59
+ target-version = "py311"
60
+ line-length = 99
61
+
62
+ [tool.ruff.lint]
63
+ select = ["E", "F", "I", "W", "UP", "B", "SIM"]
64
+
65
+ [tool.pytest.ini_options]
66
+ testpaths = ["tests"]
@@ -0,0 +1,27 @@
1
+ """
2
+ REFLEX — Reflexive Evidence-based Loop for UI Development.
3
+
4
+ Pure Python package for evidence-based UI quality methodology.
5
+ No Django dependency in core — Django integration stays in each hub.
6
+
7
+ Architecture:
8
+ reflex.agent → DomainAgent (variable domain, LLM-powered)
9
+ reflex.quality → UC Quality Checker (11 criteria)
10
+ reflex.classify → Failure Classifier (UC_PROBLEM vs UI_PROBLEM)
11
+ reflex.config → ReflexConfig from reflex.yaml
12
+ reflex.providers → KnowledgeProvider, DocumentProvider (Protocol)
13
+ reflex.llm_providers → AifwProvider, LiteLLMProvider (via iil-aifw / litellm)
14
+ reflex.types → Dataclasses (Results, Questions, Entries)
15
+ reflex.templates/ → promptfw .jinja2 templates (package_data)
16
+ reflex.__main__ → CLI: python -m reflex check/research/classify/info
17
+
18
+ Usage:
19
+ from reflex.agent import DomainAgent
20
+ from reflex.config import ReflexConfig
21
+
22
+ config = ReflexConfig.from_yaml("reflex.yaml")
23
+ agent = DomainAgent(config=config)
24
+ result = agent.research("SDS Upload Pipeline")
25
+ """
26
+
27
+ __version__ = "0.1.0"
@@ -0,0 +1,241 @@
1
+ """
2
+ REFLEX CLI — Command-line interface for UC quality checks and domain research.
3
+
4
+ Usage:
5
+ python -m reflex check <uc-file> [--config reflex.yaml]
6
+ python -m reflex research <topic> [--config reflex.yaml] [--backend groq]
7
+ python -m reflex classify <test-name> <error-msg> [--uc-file <path>]
8
+ python -m reflex info [--config reflex.yaml]
9
+
10
+ Examples:
11
+ python -m reflex check docs/uc/UC-001-sds-upload.md
12
+ python -m reflex research "Zoneneinteilung nach ATEX" --config reflex.yaml
13
+ python -m reflex classify "test_should_show_error" "AssertionError: heading"
14
+ python -m reflex info --config reflex.yaml
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import json
21
+ import sys
22
+ from pathlib import Path
23
+
24
+
25
+ def cmd_check(args: argparse.Namespace) -> int:
26
+ """Run UCQualityChecker on a UC file."""
27
+ from reflex.config import ReflexConfig
28
+ from reflex.quality import UCQualityChecker
29
+
30
+ config = ReflexConfig.from_yaml(args.config) if args.config else ReflexConfig.from_dict({})
31
+ checker = UCQualityChecker(config)
32
+
33
+ uc_path = Path(args.uc_file)
34
+ if not uc_path.exists():
35
+ print(f"ERROR: UC file not found: {uc_path}", file=sys.stderr)
36
+ return 1
37
+
38
+ uc_text = uc_path.read_text(encoding="utf-8")
39
+ result = checker.check(uc_text, uc_slug=uc_path.stem)
40
+
41
+ print(f"\n{'='*60}")
42
+ print(f" REFLEX UC Quality Check: {uc_path.name}")
43
+ print(f"{'='*60}")
44
+ print(f" Score: {result.score_percent}% ({sum(1 for c in result.criteria if c.passed)}/{len(result.criteria)} criteria)")
45
+ print(f" Passed: {'YES' if result.passed else 'NO'}")
46
+ print(f"{'='*60}\n")
47
+
48
+ for c in result.criteria:
49
+ icon = "✅" if c.passed else "❌"
50
+ print(f" {icon} {c.name}")
51
+ if not c.passed:
52
+ print(f" Evidence: {c.evidence}")
53
+ print(f" Fix: {c.suggestion}")
54
+ print()
55
+
56
+ if result.failed_criteria:
57
+ print(f" {len(result.failed_criteria)} criteria failed — see suggestions above.\n")
58
+ return 1
59
+ print(" All criteria passed!\n")
60
+ return 0
61
+
62
+
63
+ def cmd_research(args: argparse.Namespace) -> int:
64
+ """Run DomainAgent.research() on a topic."""
65
+ from reflex.agent import DomainAgent
66
+ from reflex.config import ReflexConfig
67
+ from reflex.llm_providers import get_provider
68
+
69
+ config = ReflexConfig.from_yaml(args.config) if args.config else ReflexConfig.from_dict({})
70
+
71
+ provider_kwargs = {}
72
+ if args.model and args.backend in ("litellm", "auto"):
73
+ provider_kwargs["model"] = args.model
74
+ llm = get_provider(backend=args.backend, **provider_kwargs)
75
+
76
+ agent = DomainAgent(config=config, llm=llm)
77
+ topic = " ".join(args.topic)
78
+
79
+ model_name = getattr(llm, "model", args.backend)
80
+ print(f"\nResearching: {topic}")
81
+ print(f" Vertical: {config.vertical}")
82
+ print(f" Backend: {args.backend} ({model_name})")
83
+ print(f" {'─'*50}")
84
+
85
+ result = agent.research(topic)
86
+
87
+ print(f"\n Confidence: {result.confidence:.0%}")
88
+ print(f" Sources: {', '.join(result.sources_used) or 'LLM only'}")
89
+
90
+ if result.facts:
91
+ print(f"\n Facts ({len(result.facts)}):")
92
+ for f in result.facts:
93
+ print(f" • {f}")
94
+
95
+ if result.gaps:
96
+ print(f"\n Gaps ({len(result.gaps)}):")
97
+ for g in result.gaps:
98
+ print(f" ? {g}")
99
+
100
+ if result.contradictions:
101
+ print(f"\n Contradictions ({len(result.contradictions)}):")
102
+ for c in result.contradictions:
103
+ print(f" ⚠ {c}")
104
+
105
+ print()
106
+
107
+ if args.json:
108
+ output = {
109
+ "topic": result.topic,
110
+ "vertical": result.vertical,
111
+ "facts": result.facts,
112
+ "gaps": result.gaps,
113
+ "contradictions": result.contradictions,
114
+ "confidence": result.confidence,
115
+ "sources_used": result.sources_used,
116
+ }
117
+ print(json.dumps(output, indent=2, ensure_ascii=False))
118
+
119
+ return 0
120
+
121
+
122
+ def cmd_classify(args: argparse.Namespace) -> int:
123
+ """Run FailureClassifier on a test failure."""
124
+ from reflex.classify import FailureClassifier
125
+
126
+ uc_text = ""
127
+ if args.uc_file:
128
+ uc_path = Path(args.uc_file)
129
+ if uc_path.exists():
130
+ uc_text = uc_path.read_text(encoding="utf-8")
131
+
132
+ classifier = FailureClassifier()
133
+ result = classifier.classify(
134
+ test_name=args.test_name,
135
+ error_message=args.error_message,
136
+ uc_text=uc_text,
137
+ )
138
+
139
+ print(f"\n{'='*60}")
140
+ print(f" REFLEX Failure Classification")
141
+ print(f"{'='*60}")
142
+ print(f" Test: {args.test_name}")
143
+ print(f" Type: {result.failure_type.value}")
144
+ print(f" Confidence: {result.confidence:.0%}")
145
+ print(f" Reasoning: {result.reasoning}")
146
+ print(f" Action: {result.suggested_action}")
147
+ if result.affected_criterion:
148
+ print(f" Criterion: {result.affected_criterion}")
149
+ print()
150
+
151
+ return 0
152
+
153
+
154
+ def cmd_info(args: argparse.Namespace) -> int:
155
+ """Show REFLEX config info."""
156
+ from reflex.config import ReflexConfig
157
+
158
+ if not args.config:
159
+ print("ERROR: --config is required for info command", file=sys.stderr)
160
+ return 1
161
+
162
+ config = ReflexConfig.from_yaml(args.config)
163
+
164
+ print(f"\n{'='*60}")
165
+ print(f" REFLEX Config: {args.config}")
166
+ print(f"{'='*60}")
167
+ print(f" Hub: {config.hub_name}")
168
+ print(f" Vertical: {config.vertical}")
169
+ print(f" Keywords: {', '.join(config.domain_keywords[:8])}")
170
+ print(f" Viewports: {', '.join(v.name for v in config.viewports)}")
171
+ print(f"\n Quality Rules:")
172
+ print(f" Max steps: {config.quality.max_uc_steps}")
173
+ print(f" Min AK: {config.quality.min_acceptance_criteria}")
174
+ print(f" Require error cases: {config.quality.require_error_cases}")
175
+ print(f" Forbid impl details: {config.quality.forbid_implementation_details}")
176
+ print(f" Forbid soft lang: {config.quality.forbid_soft_language}")
177
+
178
+ if config.htmx_patterns.banned:
179
+ print(f"\n HTMX Banned: {', '.join(config.htmx_patterns.banned)}")
180
+
181
+ if config.permissions_matrix:
182
+ print(f"\n Permission Matrix ({len(config.permissions_matrix)} URLs):")
183
+ for url, roles in config.permissions_matrix.items():
184
+ print(f" {url}: {roles}")
185
+
186
+ print()
187
+ return 0
188
+
189
+
190
+ def main() -> int:
191
+ parser = argparse.ArgumentParser(
192
+ prog="reflex",
193
+ description="REFLEX — Reflexive Evidence-based Loop for UI Development",
194
+ )
195
+ parser.add_argument("--config", "-c", help="Path to reflex.yaml")
196
+
197
+ sub = parser.add_subparsers(dest="command")
198
+
199
+ # check
200
+ p_check = sub.add_parser("check", help="Check UC quality (11 criteria)")
201
+ p_check.add_argument("uc_file", help="Path to UC markdown file")
202
+
203
+ # research
204
+ p_research = sub.add_parser("research", help="Domain research via LLM")
205
+ p_research.add_argument("topic", nargs="+", help="Research topic")
206
+ p_research.add_argument(
207
+ "--backend", "-b", default="litellm",
208
+ help="LLM backend: auto (aifw if Django, else litellm), aifw, litellm",
209
+ )
210
+ p_research.add_argument(
211
+ "--model", "-m", default="groq/llama-3.3-70b-versatile",
212
+ help="litellm model string (e.g. groq/llama-3.3-70b-versatile, openai/gpt-4o-mini)",
213
+ )
214
+ p_research.add_argument("--json", "-j", action="store_true", help="Output JSON")
215
+
216
+ # classify
217
+ p_classify = sub.add_parser("classify", help="Classify test failure")
218
+ p_classify.add_argument("test_name", help="Test function name")
219
+ p_classify.add_argument("error_message", help="Error message")
220
+ p_classify.add_argument("--uc-file", help="UC file for context")
221
+
222
+ # info
223
+ sub.add_parser("info", help="Show config info")
224
+
225
+ args = parser.parse_args()
226
+
227
+ if not args.command:
228
+ parser.print_help()
229
+ return 0
230
+
231
+ commands = {
232
+ "check": cmd_check,
233
+ "research": cmd_research,
234
+ "classify": cmd_classify,
235
+ "info": cmd_info,
236
+ }
237
+ return commands[args.command](args)
238
+
239
+
240
+ if __name__ == "__main__":
241
+ sys.exit(main())