loki-mode 5.51.0 → 5.52.1
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.
- package/README.md +4 -56
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/hooks/validate-bash.sh +5 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/alternative-installations.md +3 -3
- package/docs/certification/01-core-concepts/lab.md +174 -0
- package/docs/certification/01-core-concepts/lesson.md +182 -0
- package/docs/certification/01-core-concepts/quiz.md +93 -0
- package/docs/certification/02-enterprise-features/lab.md +154 -0
- package/docs/certification/02-enterprise-features/lesson.md +202 -0
- package/docs/certification/02-enterprise-features/quiz.md +93 -0
- package/docs/certification/03-advanced-patterns/lab.md +138 -0
- package/docs/certification/03-advanced-patterns/lesson.md +199 -0
- package/docs/certification/03-advanced-patterns/quiz.md +93 -0
- package/docs/certification/04-production-deployment/lab.md +160 -0
- package/docs/certification/04-production-deployment/lesson.md +261 -0
- package/docs/certification/04-production-deployment/quiz.md +93 -0
- package/docs/certification/05-troubleshooting/lab.md +254 -0
- package/docs/certification/05-troubleshooting/lesson.md +266 -0
- package/docs/certification/05-troubleshooting/quiz.md +93 -0
- package/docs/certification/README.md +80 -0
- package/docs/certification/answer-key.md +117 -0
- package/docs/certification/certification-exam.md +471 -0
- package/docs/certification/sample-prds/microservices-platform.md +100 -0
- package/docs/certification/sample-prds/saas-dashboard.md +60 -0
- package/docs/certification/sample-prds/todo-app.md +44 -0
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +230 -0
- package/package.json +1 -1
- package/src/plugins/agent-plugin.js +123 -0
- package/src/plugins/gate-plugin.js +153 -0
- package/src/plugins/index.js +116 -0
- package/src/plugins/integration-plugin.js +174 -0
- package/src/plugins/loader.js +275 -0
- package/src/plugins/mcp-plugin.js +190 -0
- package/src/plugins/schemas/agent.json +59 -0
- package/src/plugins/schemas/integration.json +62 -0
- package/src/plugins/schemas/mcp_tool.json +73 -0
- package/src/plugins/schemas/quality_gate.json +52 -0
- package/src/plugins/validator.js +297 -0
- /package/dashboard/{secrets.py → app_secrets.py} +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Microservices Platform PRD
|
|
2
|
+
|
|
3
|
+
**Complexity Tier:** Complex (10+ files, multiple services, external integrations)
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
A multi-service e-commerce platform with independent microservices communicating via message queues. Includes user management, product catalog, order processing, payment handling, and notification delivery. Deployed on Kubernetes with monitoring and observability.
|
|
8
|
+
|
|
9
|
+
## Services
|
|
10
|
+
|
|
11
|
+
### User Service
|
|
12
|
+
- Registration and authentication (OAuth2 + JWT)
|
|
13
|
+
- User profile management
|
|
14
|
+
- Role-based access control (admin, seller, buyer)
|
|
15
|
+
- Rate limiting on auth endpoints
|
|
16
|
+
|
|
17
|
+
### Product Service
|
|
18
|
+
- CRUD operations for products
|
|
19
|
+
- Category and tag management
|
|
20
|
+
- Full-text search with Elasticsearch
|
|
21
|
+
- Image upload to S3-compatible storage
|
|
22
|
+
- Inventory tracking with optimistic locking
|
|
23
|
+
|
|
24
|
+
### Order Service
|
|
25
|
+
- Shopping cart management (Redis-backed)
|
|
26
|
+
- Order creation with inventory reservation
|
|
27
|
+
- Order status tracking (created, paid, shipped, delivered, cancelled)
|
|
28
|
+
- Saga pattern for distributed transactions
|
|
29
|
+
|
|
30
|
+
### Payment Service
|
|
31
|
+
- Stripe integration for payment processing
|
|
32
|
+
- Webhook handling for payment events
|
|
33
|
+
- Refund processing
|
|
34
|
+
- Payment status reconciliation
|
|
35
|
+
|
|
36
|
+
### Notification Service
|
|
37
|
+
- Email notifications (order confirmation, shipping updates)
|
|
38
|
+
- Event-driven via message queue consumption
|
|
39
|
+
- Template-based email rendering
|
|
40
|
+
- Delivery status tracking
|
|
41
|
+
|
|
42
|
+
## Architecture
|
|
43
|
+
|
|
44
|
+
### Communication
|
|
45
|
+
- Synchronous: REST APIs between services (via API gateway)
|
|
46
|
+
- Asynchronous: RabbitMQ message queues for event-driven flows
|
|
47
|
+
- Events: OrderCreated, PaymentCompleted, OrderShipped, UserRegistered
|
|
48
|
+
|
|
49
|
+
### Data Storage
|
|
50
|
+
- PostgreSQL per service (database-per-service pattern)
|
|
51
|
+
- Redis for session storage and shopping cart
|
|
52
|
+
- Elasticsearch for product search
|
|
53
|
+
- S3-compatible object storage for images
|
|
54
|
+
|
|
55
|
+
### Infrastructure
|
|
56
|
+
- Docker containers for all services
|
|
57
|
+
- Kubernetes manifests (Deployment, Service, Ingress, ConfigMap, Secret)
|
|
58
|
+
- Helm chart for parameterized deployment
|
|
59
|
+
- Health check endpoints per service (`/health`, `/ready`)
|
|
60
|
+
- Horizontal Pod Autoscaler based on CPU/request metrics
|
|
61
|
+
|
|
62
|
+
## Tech Stack
|
|
63
|
+
|
|
64
|
+
- **Services:** Node.js with TypeScript, Express
|
|
65
|
+
- **Database:** PostgreSQL with Prisma ORM
|
|
66
|
+
- **Cache:** Redis
|
|
67
|
+
- **Search:** Elasticsearch
|
|
68
|
+
- **Queue:** RabbitMQ
|
|
69
|
+
- **Payments:** Stripe SDK
|
|
70
|
+
- **Container:** Docker
|
|
71
|
+
- **Orchestration:** Kubernetes
|
|
72
|
+
- **Monitoring:** Prometheus metrics endpoints, Grafana dashboards
|
|
73
|
+
|
|
74
|
+
## Non-Functional Requirements
|
|
75
|
+
|
|
76
|
+
- Service response time < 200ms (p99)
|
|
77
|
+
- Message queue processing latency < 500ms
|
|
78
|
+
- 99.9% uptime target
|
|
79
|
+
- Zero-downtime deployments (rolling updates)
|
|
80
|
+
- Unit test coverage > 80% per service
|
|
81
|
+
- Integration tests for cross-service flows
|
|
82
|
+
- E2E test for complete purchase flow
|
|
83
|
+
- Security: OWASP Top 10, secret management via K8s Secrets, network policies
|
|
84
|
+
|
|
85
|
+
## Success Criteria
|
|
86
|
+
|
|
87
|
+
- All 5 services start and pass health checks
|
|
88
|
+
- User can register, browse products, add to cart, and place an order
|
|
89
|
+
- Payment webhook processes correctly (requires Stripe test keys)
|
|
90
|
+
- Notifications are queued and sent on order events
|
|
91
|
+
- Kubernetes manifests deploy all services
|
|
92
|
+
- Prometheus endpoints expose metrics
|
|
93
|
+
- All unit and integration tests pass
|
|
94
|
+
- No Critical or High security findings
|
|
95
|
+
|
|
96
|
+
## Notes
|
|
97
|
+
|
|
98
|
+
- Stripe integration requires a test API key (`STRIPE_TEST_KEY`). Mark as "requires provider API key" if not available.
|
|
99
|
+
- Elasticsearch and RabbitMQ require running instances. Docker Compose is provided for local development.
|
|
100
|
+
- Kubernetes deployment requires a cluster (minikube, kind, or cloud provider).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# SaaS Analytics Dashboard PRD
|
|
2
|
+
|
|
3
|
+
**Complexity Tier:** Standard (3-10 files, features with auth and database)
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
A web-based analytics dashboard for a SaaS product. Users can sign up, log in, view charts of their usage metrics, and manage their account settings.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
### Authentication
|
|
12
|
+
- Email/password registration with bcrypt hashing
|
|
13
|
+
- Login with JWT token issuance
|
|
14
|
+
- Protected routes requiring valid JWT
|
|
15
|
+
- Password reset flow (email-based)
|
|
16
|
+
|
|
17
|
+
### Dashboard
|
|
18
|
+
- Overview page with key metrics (active users, revenue, API calls)
|
|
19
|
+
- Line chart showing daily active users over 30 days
|
|
20
|
+
- Bar chart showing API call volume by endpoint
|
|
21
|
+
- Date range selector for filtering data
|
|
22
|
+
|
|
23
|
+
### API
|
|
24
|
+
- RESTful API with OpenAPI specification
|
|
25
|
+
- `POST /api/auth/register` -- Create account
|
|
26
|
+
- `POST /api/auth/login` -- Authenticate and receive JWT
|
|
27
|
+
- `GET /api/metrics/overview` -- Key metrics summary
|
|
28
|
+
- `GET /api/metrics/users?range=30d` -- User activity data
|
|
29
|
+
- `GET /api/metrics/api-calls?range=30d` -- API call data
|
|
30
|
+
- `GET /api/account` -- Account details
|
|
31
|
+
- `PUT /api/account` -- Update account settings
|
|
32
|
+
|
|
33
|
+
### Database
|
|
34
|
+
- PostgreSQL for persistent storage
|
|
35
|
+
- Users table (id, email, password_hash, created_at)
|
|
36
|
+
- Metrics table (id, user_id, metric_type, value, recorded_at)
|
|
37
|
+
- Database migrations for schema management
|
|
38
|
+
|
|
39
|
+
## Tech Stack
|
|
40
|
+
|
|
41
|
+
- **Frontend:** React with TypeScript, Tailwind CSS, Recharts for charts
|
|
42
|
+
- **Backend:** Node.js with Express, TypeScript
|
|
43
|
+
- **Database:** PostgreSQL with Knex.js for migrations
|
|
44
|
+
- **Auth:** bcrypt + JWT
|
|
45
|
+
|
|
46
|
+
## Non-Functional Requirements
|
|
47
|
+
|
|
48
|
+
- API response time < 200ms for all endpoints
|
|
49
|
+
- Frontend loads in < 3 seconds on 3G connection
|
|
50
|
+
- Unit test coverage > 80%
|
|
51
|
+
- Integration tests for auth flow and API endpoints
|
|
52
|
+
- OWASP Top 10 compliance (SQL injection prevention, XSS protection, CSRF tokens)
|
|
53
|
+
|
|
54
|
+
## Success Criteria
|
|
55
|
+
|
|
56
|
+
- User can register, log in, and view dashboard
|
|
57
|
+
- Charts render with sample data
|
|
58
|
+
- All API endpoints match OpenAPI spec
|
|
59
|
+
- Unit and integration tests pass
|
|
60
|
+
- No Critical or High security findings in review
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Todo App PRD
|
|
2
|
+
|
|
3
|
+
**Complexity Tier:** Simple (1-2 files, basic CRUD)
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
A command-line todo application written in Node.js. No external dependencies, no database, no authentication.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
### Functional
|
|
12
|
+
- Add a todo item with a title (string, max 200 characters)
|
|
13
|
+
- List all todo items with their status (pending/complete)
|
|
14
|
+
- Mark a todo item as complete by ID
|
|
15
|
+
- Delete a todo item by ID
|
|
16
|
+
- Each todo has: id (auto-generated UUID), title, status, createdAt timestamp
|
|
17
|
+
|
|
18
|
+
### Non-Functional
|
|
19
|
+
- Store todos in a local JSON file (`todos.json`)
|
|
20
|
+
- Data persists between runs
|
|
21
|
+
- CLI responds in under 100ms for all operations
|
|
22
|
+
- No external npm dependencies (use built-in `fs`, `crypto`, `path` modules)
|
|
23
|
+
|
|
24
|
+
## Tech Stack
|
|
25
|
+
|
|
26
|
+
- Node.js (built-in modules only)
|
|
27
|
+
- JSON file storage
|
|
28
|
+
|
|
29
|
+
## CLI Interface
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
todo add "Buy groceries" # Add a new todo
|
|
33
|
+
todo list # List all todos
|
|
34
|
+
todo done <id> # Mark as complete
|
|
35
|
+
todo delete <id> # Delete a todo
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Success Criteria
|
|
39
|
+
|
|
40
|
+
- All 4 CRUD operations work correctly
|
|
41
|
+
- Data persists in `todos.json` between runs
|
|
42
|
+
- Unit tests pass with >80% coverage
|
|
43
|
+
- No external dependencies in `package.json`
|
|
44
|
+
- Error handling for: missing title, invalid ID, file read/write failures
|
package/mcp/__init__.py
CHANGED
package/mcp/server.py
CHANGED
|
@@ -979,6 +979,236 @@ async def get_pending_tasks() -> str:
|
|
|
979
979
|
return json.dumps({"error": "Access denied", "pending_tasks": [], "count": 0})
|
|
980
980
|
|
|
981
981
|
|
|
982
|
+
# ============================================================
|
|
983
|
+
# ENTERPRISE TOOLS (P0-1)
|
|
984
|
+
# ============================================================
|
|
985
|
+
|
|
986
|
+
@mcp.tool()
|
|
987
|
+
async def loki_start_project(prd_content: str = "", prd_path: str = "") -> str:
|
|
988
|
+
"""
|
|
989
|
+
Start a new Loki Mode project from a PRD.
|
|
990
|
+
|
|
991
|
+
Args:
|
|
992
|
+
prd_content: Inline PRD content (takes priority over prd_path)
|
|
993
|
+
prd_path: Path to a PRD file on disk
|
|
994
|
+
|
|
995
|
+
Returns:
|
|
996
|
+
JSON with project initialization status
|
|
997
|
+
"""
|
|
998
|
+
_emit_tool_event_async('loki_start_project', 'start', parameters={'prd_path': prd_path})
|
|
999
|
+
try:
|
|
1000
|
+
content = prd_content
|
|
1001
|
+
if not content and prd_path:
|
|
1002
|
+
resolved = safe_path_join('.', prd_path)
|
|
1003
|
+
if os.path.exists(resolved):
|
|
1004
|
+
with safe_open(resolved, 'r') as f:
|
|
1005
|
+
content = f.read()
|
|
1006
|
+
else:
|
|
1007
|
+
return json.dumps({"error": f"PRD file not found: {prd_path}"})
|
|
1008
|
+
|
|
1009
|
+
if not content:
|
|
1010
|
+
return json.dumps({"error": "No PRD content or path provided"})
|
|
1011
|
+
|
|
1012
|
+
# Initialize project state
|
|
1013
|
+
os.makedirs('.loki/state', exist_ok=True)
|
|
1014
|
+
project = {
|
|
1015
|
+
"status": "initialized",
|
|
1016
|
+
"prd_length": len(content),
|
|
1017
|
+
"prd_path": prd_path or "inline",
|
|
1018
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
1019
|
+
}
|
|
1020
|
+
state_path = safe_path_join('.loki', 'state', 'project.json')
|
|
1021
|
+
with safe_open(state_path, 'w') as f:
|
|
1022
|
+
json.dump(project, f, indent=2)
|
|
1023
|
+
|
|
1024
|
+
_emit_tool_event_async('loki_start_project', 'complete', result_status='success')
|
|
1025
|
+
return json.dumps({"success": True, **project})
|
|
1026
|
+
except PathTraversalError as e:
|
|
1027
|
+
return json.dumps({"error": f"Access denied: {e}"})
|
|
1028
|
+
except Exception as e:
|
|
1029
|
+
logger.error(f"Start project failed: {e}")
|
|
1030
|
+
_emit_tool_event_async('loki_start_project', 'complete', result_status='error', error=str(e))
|
|
1031
|
+
return json.dumps({"error": str(e)})
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
@mcp.tool()
|
|
1035
|
+
async def loki_project_status() -> str:
|
|
1036
|
+
"""
|
|
1037
|
+
Get the current project status including RARV cycle state, agent activity, and task progress.
|
|
1038
|
+
|
|
1039
|
+
Returns:
|
|
1040
|
+
JSON with project status, phase, iteration, agents, and task counts
|
|
1041
|
+
"""
|
|
1042
|
+
_emit_tool_event_async('loki_project_status', 'start', parameters={})
|
|
1043
|
+
try:
|
|
1044
|
+
status = {}
|
|
1045
|
+
|
|
1046
|
+
# Read orchestrator state
|
|
1047
|
+
orch_path = safe_path_join('.loki', 'state', 'orchestrator.json')
|
|
1048
|
+
if os.path.exists(orch_path):
|
|
1049
|
+
with safe_open(orch_path, 'r') as f:
|
|
1050
|
+
status["orchestrator"] = json.load(f)
|
|
1051
|
+
|
|
1052
|
+
# Read project state
|
|
1053
|
+
proj_path = safe_path_join('.loki', 'state', 'project.json')
|
|
1054
|
+
if os.path.exists(proj_path):
|
|
1055
|
+
with safe_open(proj_path, 'r') as f:
|
|
1056
|
+
status["project"] = json.load(f)
|
|
1057
|
+
|
|
1058
|
+
# Read task queue summary
|
|
1059
|
+
queue_path = safe_path_join('.loki', 'state', 'task-queue.json')
|
|
1060
|
+
if os.path.exists(queue_path):
|
|
1061
|
+
with safe_open(queue_path, 'r') as f:
|
|
1062
|
+
queue = json.load(f)
|
|
1063
|
+
tasks = queue.get("tasks", [])
|
|
1064
|
+
status["tasks"] = {
|
|
1065
|
+
"total": len(tasks),
|
|
1066
|
+
"pending": sum(1 for t in tasks if t.get("status") == "pending"),
|
|
1067
|
+
"in_progress": sum(1 for t in tasks if t.get("status") == "in-progress"),
|
|
1068
|
+
"completed": sum(1 for t in tasks if t.get("status") == "completed"),
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if not status:
|
|
1072
|
+
status = {"status": "no_project", "message": "No active project found"}
|
|
1073
|
+
|
|
1074
|
+
_emit_tool_event_async('loki_project_status', 'complete', result_status='success')
|
|
1075
|
+
return json.dumps(status, default=str)
|
|
1076
|
+
except PathTraversalError:
|
|
1077
|
+
return json.dumps({"error": "Access denied"})
|
|
1078
|
+
except Exception as e:
|
|
1079
|
+
logger.error(f"Project status failed: {e}")
|
|
1080
|
+
_emit_tool_event_async('loki_project_status', 'complete', result_status='error', error=str(e))
|
|
1081
|
+
return json.dumps({"error": str(e)})
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
@mcp.tool()
|
|
1085
|
+
async def loki_agent_metrics() -> str:
|
|
1086
|
+
"""
|
|
1087
|
+
Get agent metrics including token usage, task completion rates, and timing.
|
|
1088
|
+
|
|
1089
|
+
Returns:
|
|
1090
|
+
JSON with per-agent metrics and aggregates
|
|
1091
|
+
"""
|
|
1092
|
+
_emit_tool_event_async('loki_agent_metrics', 'start', parameters={})
|
|
1093
|
+
try:
|
|
1094
|
+
metrics = {"agents": [], "aggregate": {}}
|
|
1095
|
+
|
|
1096
|
+
# Read efficiency metrics
|
|
1097
|
+
metrics_dir = safe_path_join('.loki', 'metrics', 'efficiency')
|
|
1098
|
+
if os.path.isdir(metrics_dir):
|
|
1099
|
+
for fname in os.listdir(metrics_dir):
|
|
1100
|
+
if fname.endswith('.json'):
|
|
1101
|
+
fpath = os.path.join(metrics_dir, fname)
|
|
1102
|
+
with open(fpath, 'r') as f:
|
|
1103
|
+
metrics["agents"].append(json.load(f))
|
|
1104
|
+
|
|
1105
|
+
# Read token economics
|
|
1106
|
+
econ_path = safe_path_join('.loki', 'metrics', 'token-economics.json')
|
|
1107
|
+
if os.path.exists(econ_path):
|
|
1108
|
+
with safe_open(econ_path, 'r') as f:
|
|
1109
|
+
metrics["token_economics"] = json.load(f)
|
|
1110
|
+
|
|
1111
|
+
metrics["agent_count"] = len(metrics["agents"])
|
|
1112
|
+
_emit_tool_event_async('loki_agent_metrics', 'complete', result_status='success')
|
|
1113
|
+
return json.dumps(metrics, default=str)
|
|
1114
|
+
except PathTraversalError:
|
|
1115
|
+
return json.dumps({"error": "Access denied"})
|
|
1116
|
+
except Exception as e:
|
|
1117
|
+
logger.error(f"Agent metrics failed: {e}")
|
|
1118
|
+
_emit_tool_event_async('loki_agent_metrics', 'complete', result_status='error', error=str(e))
|
|
1119
|
+
return json.dumps({"error": str(e)})
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
@mcp.tool()
|
|
1123
|
+
async def loki_checkpoint_restore(checkpoint_id: str = "") -> str:
|
|
1124
|
+
"""
|
|
1125
|
+
List available checkpoints or restore project state from a specific checkpoint.
|
|
1126
|
+
|
|
1127
|
+
Args:
|
|
1128
|
+
checkpoint_id: ID of checkpoint to restore (empty = list all)
|
|
1129
|
+
|
|
1130
|
+
Returns:
|
|
1131
|
+
JSON with available checkpoints or restoration result
|
|
1132
|
+
"""
|
|
1133
|
+
_emit_tool_event_async('loki_checkpoint_restore', 'start', parameters={'checkpoint_id': checkpoint_id})
|
|
1134
|
+
try:
|
|
1135
|
+
cp_dir = safe_path_join('.loki', 'state', 'checkpoints')
|
|
1136
|
+
if not os.path.isdir(cp_dir):
|
|
1137
|
+
return json.dumps({"checkpoints": [], "message": "No checkpoints directory"})
|
|
1138
|
+
|
|
1139
|
+
checkpoints = []
|
|
1140
|
+
for fname in sorted(os.listdir(cp_dir)):
|
|
1141
|
+
if fname.endswith('.json'):
|
|
1142
|
+
fpath = os.path.join(cp_dir, fname)
|
|
1143
|
+
with open(fpath, 'r') as f:
|
|
1144
|
+
cp = json.load(f)
|
|
1145
|
+
cp["id"] = fname.replace('.json', '')
|
|
1146
|
+
checkpoints.append(cp)
|
|
1147
|
+
|
|
1148
|
+
if not checkpoint_id:
|
|
1149
|
+
_emit_tool_event_async('loki_checkpoint_restore', 'complete', result_status='success')
|
|
1150
|
+
return json.dumps({"checkpoints": checkpoints, "count": len(checkpoints)})
|
|
1151
|
+
|
|
1152
|
+
# Find and restore specific checkpoint
|
|
1153
|
+
target = next((c for c in checkpoints if c["id"] == checkpoint_id), None)
|
|
1154
|
+
if not target:
|
|
1155
|
+
return json.dumps({"error": f"Checkpoint not found: {checkpoint_id}"})
|
|
1156
|
+
|
|
1157
|
+
# Write checkpoint state as current state
|
|
1158
|
+
state_path = safe_path_join('.loki', 'state', 'orchestrator.json')
|
|
1159
|
+
with safe_open(state_path, 'w') as f:
|
|
1160
|
+
json.dump(target, f, indent=2)
|
|
1161
|
+
|
|
1162
|
+
_emit_tool_event_async('loki_checkpoint_restore', 'complete', result_status='success')
|
|
1163
|
+
return json.dumps({"restored": True, "checkpoint_id": checkpoint_id})
|
|
1164
|
+
except PathTraversalError:
|
|
1165
|
+
return json.dumps({"error": "Access denied"})
|
|
1166
|
+
except Exception as e:
|
|
1167
|
+
logger.error(f"Checkpoint restore failed: {e}")
|
|
1168
|
+
_emit_tool_event_async('loki_checkpoint_restore', 'complete', result_status='error', error=str(e))
|
|
1169
|
+
return json.dumps({"error": str(e)})
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
@mcp.tool()
|
|
1173
|
+
async def loki_quality_report() -> str:
|
|
1174
|
+
"""
|
|
1175
|
+
Get quality gate results including blind review scores, council verdicts, and test coverage.
|
|
1176
|
+
|
|
1177
|
+
Returns:
|
|
1178
|
+
JSON with quality gate status, review results, and coverage metrics
|
|
1179
|
+
"""
|
|
1180
|
+
_emit_tool_event_async('loki_quality_report', 'start', parameters={})
|
|
1181
|
+
try:
|
|
1182
|
+
report = {"gates": [], "council": None, "coverage": None}
|
|
1183
|
+
|
|
1184
|
+
# Read quality gate results
|
|
1185
|
+
gates_path = safe_path_join('.loki', 'state', 'quality-gates.json')
|
|
1186
|
+
if os.path.exists(gates_path):
|
|
1187
|
+
with safe_open(gates_path, 'r') as f:
|
|
1188
|
+
report["gates"] = json.load(f)
|
|
1189
|
+
|
|
1190
|
+
# Read council results
|
|
1191
|
+
council_path = safe_path_join('.loki', 'state', 'council-results.json')
|
|
1192
|
+
if os.path.exists(council_path):
|
|
1193
|
+
with safe_open(council_path, 'r') as f:
|
|
1194
|
+
report["council"] = json.load(f)
|
|
1195
|
+
|
|
1196
|
+
# Read coverage
|
|
1197
|
+
coverage_path = safe_path_join('.loki', 'metrics', 'coverage.json')
|
|
1198
|
+
if os.path.exists(coverage_path):
|
|
1199
|
+
with safe_open(coverage_path, 'r') as f:
|
|
1200
|
+
report["coverage"] = json.load(f)
|
|
1201
|
+
|
|
1202
|
+
_emit_tool_event_async('loki_quality_report', 'complete', result_status='success')
|
|
1203
|
+
return json.dumps(report, default=str)
|
|
1204
|
+
except PathTraversalError:
|
|
1205
|
+
return json.dumps({"error": "Access denied"})
|
|
1206
|
+
except Exception as e:
|
|
1207
|
+
logger.error(f"Quality report failed: {e}")
|
|
1208
|
+
_emit_tool_event_async('loki_quality_report', 'complete', result_status='error', error=str(e))
|
|
1209
|
+
return json.dumps({"error": str(e)})
|
|
1210
|
+
|
|
1211
|
+
|
|
982
1212
|
# ============================================================
|
|
983
1213
|
# PROMPTS - Pre-built prompt templates
|
|
984
1214
|
# ============================================================
|
package/package.json
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { BUILTIN_AGENT_NAMES } = require('./validator');
|
|
4
|
+
|
|
5
|
+
// In-memory registry for custom agent plugins
|
|
6
|
+
const _registeredAgents = new Map();
|
|
7
|
+
|
|
8
|
+
class AgentPlugin {
|
|
9
|
+
/**
|
|
10
|
+
* Register a custom agent plugin.
|
|
11
|
+
* Cannot override built-in agent types (additive only).
|
|
12
|
+
*
|
|
13
|
+
* @param {object} pluginConfig - Validated agent plugin config
|
|
14
|
+
* @param {object} [registry] - Optional external registry to also register into
|
|
15
|
+
* @returns {{ success: boolean, error?: string }}
|
|
16
|
+
*/
|
|
17
|
+
static register(pluginConfig, registry) {
|
|
18
|
+
if (!pluginConfig || pluginConfig.type !== 'agent') {
|
|
19
|
+
return { success: false, error: 'Invalid plugin config: type must be "agent"' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const name = pluginConfig.name;
|
|
23
|
+
|
|
24
|
+
// Prevent overriding built-in agents
|
|
25
|
+
if (BUILTIN_AGENT_NAMES.includes(name)) {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
error: `Cannot override built-in agent type: "${name}"`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check for duplicate custom registration
|
|
33
|
+
if (_registeredAgents.has(name)) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: `Agent plugin "${name}" is already registered`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Build agent definition compatible with swarm registry format
|
|
41
|
+
const agentDef = {
|
|
42
|
+
name: name,
|
|
43
|
+
type: 'custom',
|
|
44
|
+
category: pluginConfig.category || 'custom',
|
|
45
|
+
description: pluginConfig.description,
|
|
46
|
+
prompt_template: pluginConfig.prompt_template,
|
|
47
|
+
trigger: pluginConfig.trigger || null,
|
|
48
|
+
quality_gate: pluginConfig.quality_gate || false,
|
|
49
|
+
capabilities: pluginConfig.capabilities || [],
|
|
50
|
+
registered_at: new Date().toISOString(),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
_registeredAgents.set(name, agentDef);
|
|
54
|
+
|
|
55
|
+
// If an external registry object is provided, add to its custom category
|
|
56
|
+
if (registry && typeof registry === 'object') {
|
|
57
|
+
if (registry.customAgents) {
|
|
58
|
+
registry.customAgents[name] = agentDef;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { success: true };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Unregister a custom agent plugin.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} pluginName - Name of the agent plugin to remove
|
|
69
|
+
* @param {object} [registry] - Optional external registry to also remove from
|
|
70
|
+
* @returns {{ success: boolean, error?: string }}
|
|
71
|
+
*/
|
|
72
|
+
static unregister(pluginName, registry) {
|
|
73
|
+
if (!_registeredAgents.has(pluginName)) {
|
|
74
|
+
return { success: false, error: `Agent plugin "${pluginName}" is not registered` };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_registeredAgents.delete(pluginName);
|
|
78
|
+
|
|
79
|
+
if (registry && registry.customAgents) {
|
|
80
|
+
delete registry.customAgents[pluginName];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { success: true };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* List all registered custom agent plugins.
|
|
88
|
+
*
|
|
89
|
+
* @returns {object[]} Array of agent definitions
|
|
90
|
+
*/
|
|
91
|
+
static listRegistered() {
|
|
92
|
+
return Array.from(_registeredAgents.values());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get a specific registered agent plugin by name.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} name - Agent plugin name
|
|
99
|
+
* @returns {object|null} Agent definition or null
|
|
100
|
+
*/
|
|
101
|
+
static get(name) {
|
|
102
|
+
return _registeredAgents.get(name) || null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if a custom agent is registered.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} name - Agent name
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
*/
|
|
111
|
+
static isRegistered(name) {
|
|
112
|
+
return _registeredAgents.has(name);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Clear all registered custom agents (primarily for testing).
|
|
117
|
+
*/
|
|
118
|
+
static _clearAll() {
|
|
119
|
+
_registeredAgents.clear();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { AgentPlugin };
|