ralphx 0.2.2__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.
Files changed (45) hide show
  1. ralphx/__init__.py +1 -1
  2. ralphx/api/main.py +9 -1
  3. ralphx/api/routes/auth.py +730 -65
  4. ralphx/api/routes/config.py +3 -56
  5. ralphx/api/routes/export_import.py +795 -0
  6. ralphx/api/routes/loops.py +4 -4
  7. ralphx/api/routes/planning.py +19 -5
  8. ralphx/api/routes/projects.py +84 -2
  9. ralphx/api/routes/templates.py +115 -2
  10. ralphx/api/routes/workflows.py +22 -22
  11. ralphx/cli.py +21 -6
  12. ralphx/core/auth.py +346 -171
  13. ralphx/core/database.py +615 -167
  14. ralphx/core/executor.py +0 -3
  15. ralphx/core/loop.py +15 -2
  16. ralphx/core/loop_templates.py +69 -3
  17. ralphx/core/planning_service.py +109 -21
  18. ralphx/core/preview.py +9 -25
  19. ralphx/core/project_db.py +175 -75
  20. ralphx/core/project_export.py +469 -0
  21. ralphx/core/project_import.py +670 -0
  22. ralphx/core/sample_project.py +430 -0
  23. ralphx/core/templates.py +46 -9
  24. ralphx/core/workflow_executor.py +35 -5
  25. ralphx/core/workflow_export.py +606 -0
  26. ralphx/core/workflow_import.py +1149 -0
  27. ralphx/examples/sample_project/DESIGN.md +345 -0
  28. ralphx/examples/sample_project/README.md +37 -0
  29. ralphx/examples/sample_project/guardrails.md +57 -0
  30. ralphx/examples/sample_project/stories.jsonl +10 -0
  31. ralphx/mcp/__init__.py +6 -2
  32. ralphx/mcp/registry.py +3 -3
  33. ralphx/mcp/server.py +99 -29
  34. ralphx/mcp/tools/__init__.py +4 -0
  35. ralphx/mcp/tools/help.py +204 -0
  36. ralphx/mcp/tools/workflows.py +114 -32
  37. ralphx/mcp_server.py +6 -2
  38. ralphx/static/assets/index-0ovNnfOq.css +1 -0
  39. ralphx/static/assets/index-CY9s08ZB.js +251 -0
  40. ralphx/static/assets/index-CY9s08ZB.js.map +1 -0
  41. ralphx/static/index.html +14 -0
  42. {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/METADATA +34 -12
  43. {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/RECORD +45 -30
  44. {ralphx-0.2.2.dist-info → ralphx-0.3.5.dist-info}/WHEEL +0 -0
  45. {ralphx-0.2.2.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(self, project: Project) -> dict:
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
@@ -227,6 +227,72 @@ Return your findings as JSON:
227
227
  """
228
228
 
229
229
 
230
+ # ============================================================================
231
+ # Web-Generated Requirements Prompt
232
+ # ============================================================================
233
+
234
+ WEBGEN_REQUIREMENTS_PROMPT = """# Web-Generated Requirements Discovery
235
+
236
+ You are researching industry best practices to find requirements MISSING from the design document.
237
+
238
+ ## Design Document Summary
239
+ {{design_doc_summary}}
240
+
241
+ ## Existing Stories (DO NOT DUPLICATE)
242
+ Total: {{total_stories}}
243
+ {{existing_stories}}
244
+
245
+ ## Category Statistics (for ID assignment)
246
+ {{category_stats}}
247
+
248
+ ## Your Task
249
+
250
+ 1. **Identify the domain** from the design document (e.g., "e-commerce", "healthcare", "fintech")
251
+ 2. **Research** using WebSearch:
252
+ - "{domain} application best practices {{current_year}}"
253
+ - "{domain} security requirements"
254
+ - "{domain} compliance regulations"
255
+ - "common {domain} features users expect"
256
+ 3. **Find gaps** - requirements NOT in existing stories
257
+ 4. **Generate stories** for those gaps
258
+
259
+ ## Output Format
260
+
261
+ ```json
262
+ {
263
+ "domain_identified": "e-commerce",
264
+ "searches_performed": ["e-commerce best practices 2026", "..."],
265
+ "gaps_found": [
266
+ {"gap": "No rate limiting", "source": "OWASP guidelines"},
267
+ {"gap": "Missing GDPR compliance", "source": "EU regulations"}
268
+ ],
269
+ "stories": [
270
+ {
271
+ "id": "SEC-045",
272
+ "title": "Implement rate limiting",
273
+ "content": "As a system administrator, I want rate limiting on API endpoints so that the system is protected from abuse.",
274
+ "acceptance_criteria": ["Rate limit of 100 req/min per user", "429 response when exceeded", "Configurable limits"],
275
+ "priority": 2,
276
+ "category": "SEC",
277
+ "complexity": "medium",
278
+ "source": "webgen_requirements",
279
+ "rationale": "OWASP recommends rate limiting for all public APIs"
280
+ }
281
+ ]
282
+ }
283
+ ```
284
+
285
+ ## Rules
286
+
287
+ 1. Use standard CATEGORY-NNN IDs (check category_stats for next number)
288
+ 2. Add `"source": "webgen_requirements"` to each story's metadata
289
+ 3. Include `rationale` explaining WHY this requirement matters
290
+ 4. If web search returns nothing useful, return empty stories array with explanation
291
+ 5. DO NOT duplicate existing stories - check IDs and titles carefully
292
+ 6. Focus on GAPS - things genuinely missing, not rephrasing existing stories
293
+ """
294
+
295
+
230
296
  # ============================================================================
231
297
  # Implementation Loop Template
232
298
  # ============================================================================
@@ -684,7 +750,7 @@ limits:
684
750
 
685
751
  def generate_simple_implementation_config(
686
752
  name: str,
687
- namespace: Optional[str] = None,
753
+ source_loop: Optional[str] = None,
688
754
  display_name: str = "Implementation",
689
755
  description: str = "",
690
756
  max_iterations: Optional[int] = None,
@@ -695,7 +761,7 @@ def generate_simple_implementation_config(
695
761
 
696
762
  Args:
697
763
  name: Unique loop ID (auto-generated, e.g., implementation-20260115_1).
698
- namespace: Namespace to consume stories from.
764
+ source_loop: Source loop name to consume items from.
699
765
  display_name: User-facing name (can be duplicated across loops).
700
766
  description: Optional user-provided description.
701
767
  max_iterations: Override for max iterations (default: 50).
@@ -705,7 +771,7 @@ def generate_simple_implementation_config(
705
771
  Returns:
706
772
  YAML configuration string.
707
773
  """
708
- source_section = f" source: {namespace}" if namespace else ""
774
+ source_section = f" source: {source_loop}" if source_loop else ""
709
775
  desc_line = description if description else "Implement user stories as working code"
710
776
 
711
777
  # Apply defaults if not specified
@@ -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 a product planning assistant helping users design software products.
68
-
69
- Your role is to:
70
- 1. Understand what the user wants to build
71
- 2. Ask clarifying questions about requirements, users, and scope
72
- 3. Help them think through the architecture and design
73
- 4. Eventually produce a comprehensive design document
74
-
75
- Guidelines:
76
- - Ask focused, specific questions (2-3 at a time max)
77
- - Summarize your understanding periodically
78
- - Be conversational but efficient
79
- - Focus on: problem space, target users, core features, technical constraints
80
- - When the user is ready, offer to generate the design document
81
-
82
- Do NOT:
83
- - Write code unless explicitly asked
84
- - Make assumptions without confirming
85
- - Overwhelm with too many questions at once
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=None, # No tools for planning chat
290
- timeout=120,
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
- namespace = item.get("namespace", "unknown")
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("{{namespace}}", namespace)
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 from the source namespace for preview.
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
- namespace = self._get_source_namespace()
391
- if not namespace:
379
+ if not self._is_consumer_loop():
392
380
  return None
393
381
 
394
- # Get first completed item from namespace
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 in namespace]",
418
- "namespace": namespace,
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["{{namespace}}"] = sample_item.get("namespace", "")
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["{{namespace}}"] = self._get_source_namespace() or ""
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: