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.
- iil_reflex-0.1.0/.gitignore +17 -0
- iil_reflex-0.1.0/PKG-INFO +161 -0
- iil_reflex-0.1.0/README.md +122 -0
- iil_reflex-0.1.0/pyproject.toml +66 -0
- iil_reflex-0.1.0/reflex/__init__.py +27 -0
- iil_reflex-0.1.0/reflex/__main__.py +241 -0
- iil_reflex-0.1.0/reflex/agent.py +353 -0
- iil_reflex-0.1.0/reflex/classify.py +252 -0
- iil_reflex-0.1.0/reflex/config.py +131 -0
- iil_reflex-0.1.0/reflex/llm_providers.py +195 -0
- iil_reflex-0.1.0/reflex/providers.py +124 -0
- iil_reflex-0.1.0/reflex/quality.py +256 -0
- iil_reflex-0.1.0/reflex/templates/domain_interview.jinja2 +43 -0
- iil_reflex-0.1.0/reflex/templates/domain_kb_distill.jinja2 +36 -0
- iil_reflex-0.1.0/reflex/templates/domain_research.jinja2 +34 -0
- iil_reflex-0.1.0/reflex/templates/failure_classify.jinja2 +39 -0
- iil_reflex-0.1.0/reflex/templates/uc_quality_check.jinja2 +31 -0
- iil_reflex-0.1.0/reflex/templates/wireframe_review.jinja2 +54 -0
- iil_reflex-0.1.0/reflex/types.py +268 -0
- iil_reflex-0.1.0/tests/__init__.py +0 -0
- iil_reflex-0.1.0/tests/conftest.py +79 -0
- iil_reflex-0.1.0/tests/test_agent.py +134 -0
- iil_reflex-0.1.0/tests/test_classify.py +127 -0
- iil_reflex-0.1.0/tests/test_cli.py +89 -0
- iil_reflex-0.1.0/tests/test_config.py +109 -0
- iil_reflex-0.1.0/tests/test_llm_providers.py +114 -0
- iil_reflex-0.1.0/tests/test_quality.py +118 -0
|
@@ -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())
|