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/api/routes/config.py
CHANGED
|
@@ -187,12 +187,12 @@ async def check_loop_type_requirements_status(slug: str, loop_type: str):
|
|
|
187
187
|
detail=f"No requirements found for loop type: {loop_type}",
|
|
188
188
|
)
|
|
189
189
|
|
|
190
|
-
# Check
|
|
190
|
+
# Check for authenticated account
|
|
191
191
|
from ralphx.core.database import Database
|
|
192
192
|
|
|
193
193
|
global_db = Database()
|
|
194
|
-
|
|
195
|
-
has_auth =
|
|
194
|
+
accounts = global_db.list_accounts(include_inactive=False, include_deleted=False)
|
|
195
|
+
has_auth = len(accounts) > 0
|
|
196
196
|
|
|
197
197
|
# Get resource counts by type
|
|
198
198
|
resources = project_db.list_resources(enabled=True)
|
|
@@ -425,59 +425,6 @@ async def import_jsonl_with_format(
|
|
|
425
425
|
tmp_path.unlink(missing_ok=True)
|
|
426
426
|
|
|
427
427
|
|
|
428
|
-
# ============================================================================
|
|
429
|
-
# Namespace Endpoints
|
|
430
|
-
# ============================================================================
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
class NamespaceInfo(BaseModel):
|
|
434
|
-
"""Information about a namespace."""
|
|
435
|
-
|
|
436
|
-
namespace: str
|
|
437
|
-
item_count: int
|
|
438
|
-
pending_count: int
|
|
439
|
-
completed_count: int
|
|
440
|
-
processed_count: int
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
@router.get("/{slug}/namespaces", response_model=list[NamespaceInfo])
|
|
444
|
-
async def list_namespaces(slug: str):
|
|
445
|
-
"""List all namespaces with item counts.
|
|
446
|
-
|
|
447
|
-
Returns namespaces that have work items, along with counts by status.
|
|
448
|
-
"""
|
|
449
|
-
manager, project, project_db = get_project(slug)
|
|
450
|
-
|
|
451
|
-
# Get all items grouped by namespace
|
|
452
|
-
items, _ = project_db.list_work_items(limit=10000)
|
|
453
|
-
|
|
454
|
-
namespace_stats = {}
|
|
455
|
-
for item in items:
|
|
456
|
-
ns = item.get("namespace")
|
|
457
|
-
if not ns:
|
|
458
|
-
continue
|
|
459
|
-
|
|
460
|
-
if ns not in namespace_stats:
|
|
461
|
-
namespace_stats[ns] = {
|
|
462
|
-
"namespace": ns,
|
|
463
|
-
"item_count": 0,
|
|
464
|
-
"pending_count": 0,
|
|
465
|
-
"completed_count": 0,
|
|
466
|
-
"processed_count": 0,
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
namespace_stats[ns]["item_count"] += 1
|
|
470
|
-
status = item.get("status", "pending")
|
|
471
|
-
if status == "pending":
|
|
472
|
-
namespace_stats[ns]["pending_count"] += 1
|
|
473
|
-
elif status == "completed":
|
|
474
|
-
namespace_stats[ns]["completed_count"] += 1
|
|
475
|
-
elif status == "processed":
|
|
476
|
-
namespace_stats[ns]["processed_count"] += 1
|
|
477
|
-
|
|
478
|
-
return [NamespaceInfo(**ns) for ns in namespace_stats.values()]
|
|
479
|
-
|
|
480
|
-
|
|
481
428
|
# ============================================================================
|
|
482
429
|
# Project Resources (Shared Library) Endpoints
|
|
483
430
|
# ============================================================================
|
|
@@ -70,7 +70,7 @@ async def _read_upload_with_limit(file: UploadFile, max_size: int = MAX_UPLOAD_S
|
|
|
70
70
|
class ExportPreviewResponse(BaseModel):
|
|
71
71
|
"""Response for export preview."""
|
|
72
72
|
workflow_name: str
|
|
73
|
-
|
|
73
|
+
workflow_id: str
|
|
74
74
|
steps_count: int
|
|
75
75
|
items_total: int
|
|
76
76
|
resources_count: int
|
|
@@ -108,7 +108,7 @@ class ResourcePreviewResponse(BaseModel):
|
|
|
108
108
|
class ImportPreviewResponse(BaseModel):
|
|
109
109
|
"""Response for import preview."""
|
|
110
110
|
workflow_name: str
|
|
111
|
-
|
|
111
|
+
workflow_id: str
|
|
112
112
|
exported_at: str
|
|
113
113
|
ralphx_version: str
|
|
114
114
|
schema_version: int
|
|
@@ -138,7 +138,7 @@ class ConflictInfo(BaseModel):
|
|
|
138
138
|
class MergePreviewResponse(BaseModel):
|
|
139
139
|
"""Response for merge preview."""
|
|
140
140
|
workflow_name: str
|
|
141
|
-
|
|
141
|
+
workflow_id: str
|
|
142
142
|
items_count: int
|
|
143
143
|
resources_count: int
|
|
144
144
|
conflicts: list[ConflictInfo]
|
|
@@ -194,7 +194,6 @@ class WorkflowSummaryResponse(BaseModel):
|
|
|
194
194
|
"""Summary of a workflow in project export."""
|
|
195
195
|
id: str
|
|
196
196
|
name: str
|
|
197
|
-
namespace: str
|
|
198
197
|
steps_count: int
|
|
199
198
|
items_count: int
|
|
200
199
|
resources_count: int
|
|
@@ -323,7 +322,7 @@ async def preview_workflow_export(slug: str, workflow_id: str):
|
|
|
323
322
|
|
|
324
323
|
return ExportPreviewResponse(
|
|
325
324
|
workflow_name=preview.workflow_name,
|
|
326
|
-
|
|
325
|
+
workflow_id=preview.workflow_id,
|
|
327
326
|
steps_count=preview.steps_count,
|
|
328
327
|
items_total=preview.items_total,
|
|
329
328
|
resources_count=preview.resources_count,
|
|
@@ -404,7 +403,7 @@ async def preview_workflow_import(
|
|
|
404
403
|
|
|
405
404
|
return ImportPreviewResponse(
|
|
406
405
|
workflow_name=preview.workflow_name,
|
|
407
|
-
|
|
406
|
+
workflow_id=preview.workflow_id,
|
|
408
407
|
exported_at=preview.exported_at,
|
|
409
408
|
ralphx_version=preview.ralphx_version,
|
|
410
409
|
schema_version=preview.schema_version,
|
|
@@ -551,7 +550,7 @@ async def preview_workflow_merge(
|
|
|
551
550
|
|
|
552
551
|
return MergePreviewResponse(
|
|
553
552
|
workflow_name=preview.workflow_name,
|
|
554
|
-
|
|
553
|
+
workflow_id=preview.workflow_id,
|
|
555
554
|
items_count=preview.items_count,
|
|
556
555
|
resources_count=preview.resources_count,
|
|
557
556
|
conflicts=conflicts,
|
|
@@ -639,7 +638,6 @@ async def preview_project_export(
|
|
|
639
638
|
WorkflowSummaryResponse(
|
|
640
639
|
id=w.id,
|
|
641
640
|
name=w.name,
|
|
642
|
-
namespace=w.namespace,
|
|
643
641
|
steps_count=w.steps_count,
|
|
644
642
|
items_count=w.items_count,
|
|
645
643
|
resources_count=w.resources_count,
|
|
@@ -727,7 +725,6 @@ async def preview_project_import(
|
|
|
727
725
|
WorkflowSummaryResponse(
|
|
728
726
|
id=w.id,
|
|
729
727
|
name=w.name,
|
|
730
|
-
namespace=w.namespace,
|
|
731
728
|
steps_count=w.steps_count,
|
|
732
729
|
items_count=w.items_count,
|
|
733
730
|
resources_count=w.resources_count,
|
ralphx/api/routes/loops.py
CHANGED
|
@@ -849,14 +849,14 @@ async def create_simple_loop(slug: str, request: CreateSimpleLoopRequest):
|
|
|
849
849
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
850
850
|
detail=f"Source loop '{source_loop}' not found in this project.",
|
|
851
851
|
)
|
|
852
|
-
|
|
852
|
+
source_loop_name = source_loop # Use source loop name
|
|
853
853
|
elif request.stories_source and request.stories_source.type == "content":
|
|
854
|
-
# For JSONL uploads, derive
|
|
855
|
-
|
|
854
|
+
# For JSONL uploads, derive source from request or loop_id
|
|
855
|
+
source_loop_name = request.stories_source.namespace or loop_id.replace("-", "_")
|
|
856
856
|
|
|
857
857
|
config_yaml = generate_simple_implementation_config(
|
|
858
858
|
name=loop_id,
|
|
859
|
-
|
|
859
|
+
source_loop=source_loop_name,
|
|
860
860
|
display_name=display_name,
|
|
861
861
|
description=description,
|
|
862
862
|
)
|
ralphx/api/routes/planning.py
CHANGED
|
@@ -269,9 +269,18 @@ async def stream_planning_response(slug: str, workflow_id: str):
|
|
|
269
269
|
detail="Planning session is not active",
|
|
270
270
|
)
|
|
271
271
|
|
|
272
|
-
# Get workflow for context
|
|
272
|
+
# Get workflow and current step for context
|
|
273
273
|
workflow = pdb.get_workflow(workflow_id)
|
|
274
274
|
|
|
275
|
+
# Get the step to access its config (tools, model, timeout)
|
|
276
|
+
step = pdb.get_workflow_step(session["step_id"])
|
|
277
|
+
step_config = step.get("config", {}) if step else {}
|
|
278
|
+
|
|
279
|
+
# Extract configuration from step
|
|
280
|
+
allowed_tools = step_config.get("allowedTools", [])
|
|
281
|
+
model = step_config.get("model", "opus") # Default to opus for design docs
|
|
282
|
+
timeout = step_config.get("timeout", 180)
|
|
283
|
+
|
|
275
284
|
async def generate_response():
|
|
276
285
|
"""Generate streaming response from Claude."""
|
|
277
286
|
import json
|
|
@@ -290,7 +299,12 @@ async def stream_planning_response(slug: str, workflow_id: str):
|
|
|
290
299
|
accumulated = ""
|
|
291
300
|
|
|
292
301
|
try:
|
|
293
|
-
async for event in service.stream_response(
|
|
302
|
+
async for event in service.stream_response(
|
|
303
|
+
messages,
|
|
304
|
+
model=model,
|
|
305
|
+
tools=allowed_tools if allowed_tools else None,
|
|
306
|
+
timeout=timeout,
|
|
307
|
+
):
|
|
294
308
|
if event.type == AdapterEvent.TEXT:
|
|
295
309
|
text = event.text or ""
|
|
296
310
|
accumulated += text
|
|
@@ -405,9 +419,9 @@ async def complete_planning_session(
|
|
|
405
419
|
|
|
406
420
|
# Get workflow info
|
|
407
421
|
workflow = pdb.get_workflow(workflow_id)
|
|
408
|
-
namespace = workflow["namespace"]
|
|
409
422
|
|
|
410
423
|
# Save artifacts as project resources
|
|
424
|
+
# Use workflow_id for unique filenames (namespace was removed in schema v16)
|
|
411
425
|
from pathlib import Path
|
|
412
426
|
from datetime import datetime
|
|
413
427
|
|
|
@@ -416,7 +430,7 @@ async def complete_planning_session(
|
|
|
416
430
|
resource_path = Path(project["path"]) / ".ralphx" / "resources"
|
|
417
431
|
resource_path.mkdir(parents=True, exist_ok=True)
|
|
418
432
|
|
|
419
|
-
doc_filename = f"design-doc-{
|
|
433
|
+
doc_filename = f"design-doc-{workflow_id}.md"
|
|
420
434
|
doc_path = resource_path / doc_filename
|
|
421
435
|
doc_path.write_text(artifacts["design_doc"])
|
|
422
436
|
|
|
@@ -442,7 +456,7 @@ async def complete_planning_session(
|
|
|
442
456
|
resource_path = Path(project["path"]) / ".ralphx" / "resources"
|
|
443
457
|
resource_path.mkdir(parents=True, exist_ok=True)
|
|
444
458
|
|
|
445
|
-
guardrails_filename = f"guardrails-{
|
|
459
|
+
guardrails_filename = f"guardrails-{workflow_id}.md"
|
|
446
460
|
guardrails_path = resource_path / guardrails_filename
|
|
447
461
|
guardrails_path.write_text(artifacts["guardrails"])
|
|
448
462
|
|
ralphx/api/routes/templates.py
CHANGED
|
@@ -4,7 +4,7 @@ Templates are global, read-only, and shipped with RalphX.
|
|
|
4
4
|
No authentication required - templates are public.
|
|
5
5
|
|
|
6
6
|
Includes:
|
|
7
|
-
- Loop templates (
|
|
7
|
+
- Loop templates (extractgen_requirements, implementation, etc.)
|
|
8
8
|
- Loop builder templates (planning, implementation with Phase 1)
|
|
9
9
|
- Permission templates (planning, implementation, read_only, etc.)
|
|
10
10
|
"""
|
|
@@ -87,7 +87,7 @@ async def get_template_by_name(name: str) -> TemplateDetail:
|
|
|
87
87
|
No authentication required - templates are public.
|
|
88
88
|
|
|
89
89
|
Args:
|
|
90
|
-
name: Template name (e.g., '
|
|
90
|
+
name: Template name (e.g., 'extractgen_requirements', 'implementation')
|
|
91
91
|
|
|
92
92
|
Returns:
|
|
93
93
|
Full template with config and YAML representation
|
ralphx/api/routes/workflows.py
CHANGED
|
@@ -62,7 +62,6 @@ class WorkflowResponse(BaseModel):
|
|
|
62
62
|
id: str
|
|
63
63
|
template_id: Optional[str] = None
|
|
64
64
|
name: str
|
|
65
|
-
namespace: str
|
|
66
65
|
status: str
|
|
67
66
|
current_step: int
|
|
68
67
|
created_at: str
|
|
@@ -222,23 +221,6 @@ def _get_project_db(slug: str) -> ProjectDatabase:
|
|
|
222
221
|
return ProjectDatabase(project["path"])
|
|
223
222
|
|
|
224
223
|
|
|
225
|
-
def _generate_namespace(name: str) -> str:
|
|
226
|
-
"""Generate a valid namespace from a workflow name."""
|
|
227
|
-
import re
|
|
228
|
-
|
|
229
|
-
# Convert to lowercase, replace spaces with dashes
|
|
230
|
-
ns = name.lower().replace(" ", "-")
|
|
231
|
-
# Remove invalid characters
|
|
232
|
-
ns = re.sub(r"[^a-z0-9_-]", "", ns)
|
|
233
|
-
# Ensure it starts with a letter
|
|
234
|
-
if not ns or not ns[0].isalpha():
|
|
235
|
-
ns = "w" + ns
|
|
236
|
-
# Truncate to 64 chars and add unique suffix
|
|
237
|
-
ns = ns[:56]
|
|
238
|
-
suffix = uuid.uuid4().hex[:7]
|
|
239
|
-
return f"{ns}-{suffix}"
|
|
240
|
-
|
|
241
|
-
|
|
242
224
|
def _workflow_to_response(
|
|
243
225
|
workflow: dict, steps: list[dict], pdb: Optional[ProjectDatabase] = None
|
|
244
226
|
) -> WorkflowResponse:
|
|
@@ -366,7 +348,6 @@ def _workflow_to_response(
|
|
|
366
348
|
id=workflow["id"],
|
|
367
349
|
template_id=workflow.get("template_id"),
|
|
368
350
|
name=workflow["name"],
|
|
369
|
-
namespace=workflow["namespace"],
|
|
370
351
|
status=workflow["status"],
|
|
371
352
|
current_step=workflow["current_step"],
|
|
372
353
|
created_at=workflow["created_at"],
|
|
@@ -511,9 +492,8 @@ async def create_workflow(slug: str, request: CreateWorkflowRequest):
|
|
|
511
492
|
"""
|
|
512
493
|
pdb = _get_project_db(slug)
|
|
513
494
|
|
|
514
|
-
# Generate unique ID
|
|
495
|
+
# Generate unique ID
|
|
515
496
|
workflow_id = f"wf-{uuid.uuid4().hex[:12]}"
|
|
516
|
-
namespace = _generate_namespace(request.name)
|
|
517
497
|
|
|
518
498
|
# Get template steps if template specified (templates still use "phases" internally)
|
|
519
499
|
template_steps = []
|
|
@@ -531,7 +511,6 @@ async def create_workflow(slug: str, request: CreateWorkflowRequest):
|
|
|
531
511
|
workflow = pdb.create_workflow(
|
|
532
512
|
id=workflow_id,
|
|
533
513
|
name=request.name,
|
|
534
|
-
namespace=namespace,
|
|
535
514
|
template_id=request.template_id,
|
|
536
515
|
status="draft",
|
|
537
516
|
)
|
ralphx/cli.py
CHANGED
|
@@ -1319,7 +1319,10 @@ def mcp_server() -> None:
|
|
|
1319
1319
|
67 tools for full RalphX management through Claude Code.
|
|
1320
1320
|
|
|
1321
1321
|
To add RalphX to Claude Code, run:
|
|
1322
|
-
claude mcp add ralphx -- ralphx mcp
|
|
1322
|
+
Linux/Mac: claude mcp add ralphx -e PYTHONDONTWRITEBYTECODE=1 -- "$(which ralphx)" mcp
|
|
1323
|
+
Mac (zsh): if "which" fails, first run: conda init zsh && source ~/.zshrc
|
|
1324
|
+
Windows: find path with "where.exe ralphx", then:
|
|
1325
|
+
claude mcp add ralphx -e PYTHONDONTWRITEBYTECODE=1 -- C:\\path\\to\\ralphx.exe mcp
|
|
1323
1326
|
|
|
1324
1327
|
Then in Claude Code, you can ask Claude to:
|
|
1325
1328
|
- Manage projects (add, remove, list, diagnose)
|