ralphx 0.3.4__py3-none-any.whl → 0.3.5__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.
- ralphx/__init__.py +1 -1
- ralphx/api/routes/auth.py +703 -94
- ralphx/api/routes/config.py +3 -56
- ralphx/api/routes/export_import.py +6 -9
- ralphx/api/routes/loops.py +4 -4
- ralphx/api/routes/planning.py +19 -5
- ralphx/api/routes/templates.py +2 -2
- ralphx/api/routes/workflows.py +1 -22
- ralphx/cli.py +4 -1
- ralphx/core/auth.py +346 -171
- ralphx/core/database.py +588 -164
- ralphx/core/executor.py +0 -3
- ralphx/core/loop.py +15 -2
- ralphx/core/loop_templates.py +3 -3
- ralphx/core/planning_service.py +109 -21
- ralphx/core/preview.py +9 -25
- ralphx/core/project_db.py +124 -72
- ralphx/core/project_export.py +1 -5
- ralphx/core/project_import.py +14 -29
- ralphx/core/sample_project.py +1 -5
- ralphx/core/templates.py +9 -9
- ralphx/core/workflow_export.py +4 -7
- ralphx/core/workflow_import.py +3 -27
- ralphx/mcp/__init__.py +6 -2
- ralphx/mcp/registry.py +3 -3
- ralphx/mcp/tools/workflows.py +114 -32
- ralphx/mcp_server.py +6 -2
- ralphx/static/assets/index-0ovNnfOq.css +1 -0
- ralphx/static/assets/index-CY9s08ZB.js +251 -0
- ralphx/static/assets/index-CY9s08ZB.js.map +1 -0
- ralphx/static/index.html +2 -2
- {ralphx-0.3.4.dist-info → ralphx-0.3.5.dist-info}/METADATA +33 -12
- {ralphx-0.3.4.dist-info → ralphx-0.3.5.dist-info}/RECORD +35 -35
- ralphx/static/assets/index-CcRDyY3b.css +0 -1
- ralphx/static/assets/index-CcxfTosc.js +0 -251
- ralphx/static/assets/index-CcxfTosc.js.map +0 -1
- {ralphx-0.3.4.dist-info → ralphx-0.3.5.dist-info}/WHEEL +0 -0
- {ralphx-0.3.4.dist-info → ralphx-0.3.5.dist-info}/entry_points.txt +0 -0
ralphx/core/executor.py
CHANGED
|
@@ -763,9 +763,6 @@ class LoopExecutor:
|
|
|
763
763
|
template = template.replace("{{input_item.title}}", title)
|
|
764
764
|
template = template.replace("{{input_item}}", content) # Alias
|
|
765
765
|
template = template.replace("{{workflow_id}}", workflow_id_val)
|
|
766
|
-
# Backward compatibility for old templates using namespace/source_loop
|
|
767
|
-
template = template.replace("{{namespace}}", workflow_id_val)
|
|
768
|
-
template = template.replace("{{source_loop}}", workflow_id_val)
|
|
769
766
|
|
|
770
767
|
# hank-rcm style variables (for PROMPT_IMPL.md compatibility)
|
|
771
768
|
# Using {VAR} format to match ralph_impl.sh templates exactly
|
ralphx/core/loop.py
CHANGED
|
@@ -188,11 +188,15 @@ class LoopLoader:
|
|
|
188
188
|
def register_loop(
|
|
189
189
|
self,
|
|
190
190
|
config: LoopConfig,
|
|
191
|
+
workflow_id: Optional[str] = None,
|
|
192
|
+
step_id: Optional[int] = None,
|
|
191
193
|
) -> str:
|
|
192
194
|
"""Register a loop configuration in the database.
|
|
193
195
|
|
|
194
196
|
Args:
|
|
195
197
|
config: Validated loop configuration.
|
|
198
|
+
workflow_id: Parent workflow ID. Required for workflow-first architecture.
|
|
199
|
+
step_id: Parent workflow step ID. Required for workflow-first architecture.
|
|
196
200
|
|
|
197
201
|
Returns:
|
|
198
202
|
The loop ID.
|
|
@@ -213,6 +217,8 @@ class LoopLoader:
|
|
|
213
217
|
id=loop_id,
|
|
214
218
|
name=config.name,
|
|
215
219
|
config_yaml=config.to_yaml(),
|
|
220
|
+
workflow_id=workflow_id,
|
|
221
|
+
step_id=step_id,
|
|
216
222
|
)
|
|
217
223
|
return loop_id
|
|
218
224
|
|
|
@@ -287,11 +293,18 @@ class LoopLoader:
|
|
|
287
293
|
|
|
288
294
|
return sorted(set(loop_files))
|
|
289
295
|
|
|
290
|
-
def sync_loops(
|
|
296
|
+
def sync_loops(
|
|
297
|
+
self,
|
|
298
|
+
project: Project,
|
|
299
|
+
workflow_id: Optional[str] = None,
|
|
300
|
+
step_id: Optional[int] = None,
|
|
301
|
+
) -> dict:
|
|
291
302
|
"""Sync loop configurations from project files to database.
|
|
292
303
|
|
|
293
304
|
Args:
|
|
294
305
|
project: Project to sync.
|
|
306
|
+
workflow_id: Parent workflow ID. Required for workflow-first architecture.
|
|
307
|
+
step_id: Parent workflow step ID. Required for workflow-first architecture.
|
|
295
308
|
|
|
296
309
|
Returns:
|
|
297
310
|
Dictionary with sync results (added, updated, removed counts).
|
|
@@ -312,7 +325,7 @@ class LoopLoader:
|
|
|
312
325
|
|
|
313
326
|
# Check if already exists
|
|
314
327
|
existing = self._require_db().get_loop(config.name)
|
|
315
|
-
self.register_loop(config)
|
|
328
|
+
self.register_loop(config, workflow_id=workflow_id, step_id=step_id)
|
|
316
329
|
|
|
317
330
|
if existing:
|
|
318
331
|
updated += 1
|
ralphx/core/loop_templates.py
CHANGED
|
@@ -750,7 +750,7 @@ limits:
|
|
|
750
750
|
|
|
751
751
|
def generate_simple_implementation_config(
|
|
752
752
|
name: str,
|
|
753
|
-
|
|
753
|
+
source_loop: Optional[str] = None,
|
|
754
754
|
display_name: str = "Implementation",
|
|
755
755
|
description: str = "",
|
|
756
756
|
max_iterations: Optional[int] = None,
|
|
@@ -761,7 +761,7 @@ def generate_simple_implementation_config(
|
|
|
761
761
|
|
|
762
762
|
Args:
|
|
763
763
|
name: Unique loop ID (auto-generated, e.g., implementation-20260115_1).
|
|
764
|
-
|
|
764
|
+
source_loop: Source loop name to consume items from.
|
|
765
765
|
display_name: User-facing name (can be duplicated across loops).
|
|
766
766
|
description: Optional user-provided description.
|
|
767
767
|
max_iterations: Override for max iterations (default: 50).
|
|
@@ -771,7 +771,7 @@ def generate_simple_implementation_config(
|
|
|
771
771
|
Returns:
|
|
772
772
|
YAML configuration string.
|
|
773
773
|
"""
|
|
774
|
-
source_section = f" source: {
|
|
774
|
+
source_section = f" source: {source_loop}" if source_loop else ""
|
|
775
775
|
desc_line = description if description else "Implement user stories as working code"
|
|
776
776
|
|
|
777
777
|
# Apply defaults if not specified
|
ralphx/core/planning_service.py
CHANGED
|
@@ -64,25 +64,109 @@ This format is machine-parsed. Non-compliance breaks the system.
|
|
|
64
64
|
# =============================================================================
|
|
65
65
|
|
|
66
66
|
PLANNING_BEHAVIOR = '''<behavior>
|
|
67
|
-
You are
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
|
|
67
|
+
You are an expert product architect and technical consultant helping users design
|
|
68
|
+
software products. You combine deep technical knowledge with strong product sense.
|
|
69
|
+
|
|
70
|
+
## Your Core Mission
|
|
71
|
+
|
|
72
|
+
Transform vague ideas into comprehensive, implementable design documents through
|
|
73
|
+
collaborative discovery. You're not just a Q&A bot—you're a thinking partner who
|
|
74
|
+
challenges assumptions, identifies blind spots, and brings industry expertise.
|
|
75
|
+
|
|
76
|
+
## Discovery Phases
|
|
77
|
+
|
|
78
|
+
Work through these phases naturally (you don't need to announce them):
|
|
79
|
+
|
|
80
|
+
### Phase 1: Problem Space Understanding
|
|
81
|
+
- What problem are we solving? Why does it matter?
|
|
82
|
+
- Who experiences this problem? (specific personas, not generic "users")
|
|
83
|
+
- What do they currently do? What's broken about that?
|
|
84
|
+
- What would success look like for them?
|
|
85
|
+
|
|
86
|
+
### Phase 2: Solution Requirements
|
|
87
|
+
- Core features (MVP vs nice-to-have)
|
|
88
|
+
- User workflows and key interactions
|
|
89
|
+
- Data the system needs to handle
|
|
90
|
+
- Integration points with other systems
|
|
91
|
+
- Constraints: budget, timeline, team skills, existing infrastructure
|
|
92
|
+
|
|
93
|
+
### Phase 3: Technical Architecture
|
|
94
|
+
- System components and how they communicate
|
|
95
|
+
- Data model and storage choices
|
|
96
|
+
- API design (if applicable)
|
|
97
|
+
- Authentication and authorization approach
|
|
98
|
+
- Third-party services and dependencies
|
|
99
|
+
|
|
100
|
+
### Phase 4: Infrastructure & Operations
|
|
101
|
+
- Hosting environment (cloud provider, on-prem, hybrid)
|
|
102
|
+
- Deployment strategy (containers, serverless, VMs)
|
|
103
|
+
- Scaling considerations
|
|
104
|
+
- Monitoring and observability
|
|
105
|
+
- Backup and disaster recovery
|
|
106
|
+
|
|
107
|
+
### Phase 5: Security & Compliance
|
|
108
|
+
- Data sensitivity and protection requirements
|
|
109
|
+
- Authentication mechanisms
|
|
110
|
+
- Compliance requirements (GDPR, HIPAA, SOC2, etc.)
|
|
111
|
+
- Threat model considerations
|
|
112
|
+
|
|
113
|
+
## How to Ask Questions
|
|
114
|
+
|
|
115
|
+
Ask 2-4 focused questions at a time. For each question:
|
|
116
|
+
- Explain WHY you're asking (what decision it informs)
|
|
117
|
+
- Offer concrete options when helpful, not just open-ended questions
|
|
118
|
+
- Share your initial thinking or recommendation if you have one
|
|
119
|
+
|
|
120
|
+
Good: "For authentication, are you thinking OAuth (Google/GitHub login) for simplicity,
|
|
121
|
+
or do you need custom username/password? OAuth is faster to implement and more secure,
|
|
122
|
+
but custom auth gives you more control over the user experience."
|
|
123
|
+
|
|
124
|
+
Bad: "How do you want to handle authentication?"
|
|
125
|
+
|
|
126
|
+
## Using Web Search
|
|
127
|
+
|
|
128
|
+
When you have web search available, use it strategically:
|
|
129
|
+
- Research industry best practices for the specific domain
|
|
130
|
+
- Look up current pricing/capabilities of services you might recommend
|
|
131
|
+
- Find examples of similar products for inspiration
|
|
132
|
+
- Verify technical approaches are current (technologies evolve fast)
|
|
133
|
+
- Look for common pitfalls in this type of application
|
|
134
|
+
|
|
135
|
+
IMPORTANT: Always tell the user when you're searching and summarize what you learned.
|
|
136
|
+
This builds trust and shows you're doing real research, not just making things up.
|
|
137
|
+
|
|
138
|
+
## Progressive Refinement
|
|
139
|
+
|
|
140
|
+
As you learn more:
|
|
141
|
+
- Periodically summarize your understanding ("Here's what I have so far...")
|
|
142
|
+
- Explicitly call out assumptions you're making
|
|
143
|
+
- Revisit earlier decisions if new information changes things
|
|
144
|
+
- Be willing to push back if something doesn't make sense
|
|
145
|
+
|
|
146
|
+
## Re-engagement Support
|
|
147
|
+
|
|
148
|
+
If the user is returning to continue or update an existing design doc:
|
|
149
|
+
- Acknowledge what exists and ask what they want to change
|
|
150
|
+
- Don't re-ask questions that are already answered in the doc
|
|
151
|
+
- Focus on the delta—what's new, changed, or needs refinement
|
|
152
|
+
|
|
153
|
+
## Offering to Generate
|
|
154
|
+
|
|
155
|
+
When you have enough information for a solid design document:
|
|
156
|
+
- Summarize the key decisions that have been made
|
|
157
|
+
- List any important questions that remain unanswered
|
|
158
|
+
- Offer to generate the design doc (the user will click a button)
|
|
159
|
+
|
|
160
|
+
The user can generate the document at ANY time—it doesn't have to be "complete."
|
|
161
|
+
Better to generate something and iterate than to wait forever for perfection.
|
|
162
|
+
|
|
163
|
+
## What NOT To Do
|
|
164
|
+
|
|
165
|
+
- Don't write code or implementation details (that's for later steps)
|
|
166
|
+
- Don't make major assumptions without confirming
|
|
167
|
+
- Don't be a yes-person—challenge ideas that seem problematic
|
|
168
|
+
- Don't overwhelm with too many questions
|
|
169
|
+
- Don't be generic—tailor advice to their specific situation
|
|
86
170
|
</behavior>'''
|
|
87
171
|
|
|
88
172
|
ARTIFACT_BEHAVIOR = '''<behavior>
|
|
@@ -264,6 +348,8 @@ Start your response with <design_doc> immediately.
|
|
|
264
348
|
self,
|
|
265
349
|
messages: list[dict],
|
|
266
350
|
model: str = "sonnet",
|
|
351
|
+
tools: Optional[list[str]] = None,
|
|
352
|
+
timeout: int = 180,
|
|
267
353
|
) -> AsyncIterator[StreamEvent]:
|
|
268
354
|
"""Stream Claude's response to the conversation.
|
|
269
355
|
|
|
@@ -272,6 +358,8 @@ Start your response with <design_doc> immediately.
|
|
|
272
358
|
Args:
|
|
273
359
|
messages: Conversation history.
|
|
274
360
|
model: Model to use (sonnet, opus, haiku).
|
|
361
|
+
tools: Optional list of tools to enable (e.g., ['WebSearch', 'WebFetch']).
|
|
362
|
+
timeout: Timeout in seconds (default 180 for web search).
|
|
275
363
|
|
|
276
364
|
Yields:
|
|
277
365
|
StreamEvent objects as Claude responds.
|
|
@@ -286,8 +374,8 @@ Start your response with <design_doc> immediately.
|
|
|
286
374
|
async for event in adapter.stream(
|
|
287
375
|
prompt=prompt,
|
|
288
376
|
model=model,
|
|
289
|
-
tools=
|
|
290
|
-
timeout=
|
|
377
|
+
tools=tools,
|
|
378
|
+
timeout=timeout,
|
|
291
379
|
):
|
|
292
380
|
yield event
|
|
293
381
|
|
ralphx/core/preview.py
CHANGED
|
@@ -337,16 +337,14 @@ class PromptPreviewEngine:
|
|
|
337
337
|
title = item.get("title") or ""
|
|
338
338
|
metadata = item.get("metadata")
|
|
339
339
|
metadata_json = json.dumps(metadata) if metadata else "{}"
|
|
340
|
-
|
|
340
|
+
workflow_id = item.get("workflow_id", "unknown")
|
|
341
341
|
|
|
342
342
|
# Substitution order: most specific first
|
|
343
343
|
result = template.replace("{{input_item.metadata}}", metadata_json)
|
|
344
344
|
result = result.replace("{{input_item.content}}", content)
|
|
345
345
|
result = result.replace("{{input_item.title}}", title)
|
|
346
346
|
result = result.replace("{{input_item}}", content)
|
|
347
|
-
result = result.replace("{{
|
|
348
|
-
# Backward compatibility
|
|
349
|
-
result = result.replace("{{source_loop}}", namespace)
|
|
347
|
+
result = result.replace("{{workflow_id}}", workflow_id)
|
|
350
348
|
|
|
351
349
|
return result
|
|
352
350
|
|
|
@@ -372,28 +370,17 @@ class PromptPreviewEngine:
|
|
|
372
370
|
return False
|
|
373
371
|
return bool(self.config.item_types.input.source)
|
|
374
372
|
|
|
375
|
-
def _get_source_namespace(self) -> Optional[str]:
|
|
376
|
-
"""Get the source namespace for consumer loops.
|
|
377
|
-
|
|
378
|
-
For consumer loops, this is the source loop name used as the namespace.
|
|
379
|
-
"""
|
|
380
|
-
if not self._is_consumer_loop():
|
|
381
|
-
return None
|
|
382
|
-
return self.config.item_types.input.source
|
|
383
|
-
|
|
384
373
|
def _get_sample_item(self) -> Optional[dict]:
|
|
385
|
-
"""Get a sample item
|
|
374
|
+
"""Get a sample item for preview (for consumer loops).
|
|
386
375
|
|
|
387
376
|
Returns:
|
|
388
377
|
Sample item dict or None.
|
|
389
378
|
"""
|
|
390
|
-
|
|
391
|
-
if not namespace:
|
|
379
|
+
if not self._is_consumer_loop():
|
|
392
380
|
return None
|
|
393
381
|
|
|
394
|
-
# Get first completed item
|
|
382
|
+
# Get first completed item
|
|
395
383
|
items, _ = self.db.list_work_items(
|
|
396
|
-
namespace=namespace,
|
|
397
384
|
status="completed",
|
|
398
385
|
limit=1,
|
|
399
386
|
)
|
|
@@ -403,7 +390,6 @@ class PromptPreviewEngine:
|
|
|
403
390
|
|
|
404
391
|
# Fallback: get any pending item
|
|
405
392
|
items, _ = self.db.list_work_items(
|
|
406
|
-
namespace=namespace,
|
|
407
393
|
status="pending",
|
|
408
394
|
limit=1,
|
|
409
395
|
)
|
|
@@ -414,8 +400,8 @@ class PromptPreviewEngine:
|
|
|
414
400
|
# Return a placeholder
|
|
415
401
|
return {
|
|
416
402
|
"id": "sample-001",
|
|
417
|
-
"content": "[Sample item content - no items
|
|
418
|
-
"
|
|
403
|
+
"content": "[Sample item content - no items available]",
|
|
404
|
+
"source": source_name,
|
|
419
405
|
}
|
|
420
406
|
|
|
421
407
|
def _explain_strategy(self) -> str:
|
|
@@ -472,15 +458,13 @@ class PromptPreviewEngine:
|
|
|
472
458
|
variables["{{input_item.content}}"] = sample_item.get("content", "")[:50] + "..."
|
|
473
459
|
variables["{{input_item.title}}"] = sample_item.get("title", "")
|
|
474
460
|
variables["{{input_item.metadata}}"] = "{...}"
|
|
475
|
-
variables["{{
|
|
476
|
-
variables["{{source_loop}}"] = sample_item.get("namespace", "") # Backward compat
|
|
461
|
+
variables["{{workflow_id}}"] = sample_item.get("workflow_id", "")
|
|
477
462
|
else:
|
|
478
463
|
variables["{{input_item}}"] = "[Claimed item content]"
|
|
479
464
|
variables["{{input_item.content}}"] = "[Claimed item content]"
|
|
480
465
|
variables["{{input_item.title}}"] = "[Claimed item title]"
|
|
481
466
|
variables["{{input_item.metadata}}"] = "[Claimed item metadata as JSON]"
|
|
482
|
-
variables["{{
|
|
483
|
-
variables["{{source_loop}}"] = self._get_source_namespace() or "" # Backward compat
|
|
467
|
+
variables["{{workflow_id}}"] = "[Workflow ID]"
|
|
484
468
|
|
|
485
469
|
# Design doc variable (if configured)
|
|
486
470
|
if self.config.context and self.config.context.design_doc:
|