loki-mode 11.4.2 → 11.6.0
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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/chat.sh +6 -3
- package/autonomy/lib/engine10-push.sh +19 -1
- package/autonomy/lib/help-core.sh +1 -1
- package/autonomy/lib/proof-generator.py +23 -5
- package/autonomy/lib/proof-verify.py +607 -5
- package/autonomy/lib/run-summary.sh +17 -1
- package/autonomy/lib/setup-requirements.json +35 -0
- package/autonomy/loki +244 -237
- package/autonomy/notification-checker.py +1 -1
- package/autonomy/provider-offer.sh +95 -47
- package/autonomy/route.sh +38 -6
- package/autonomy/run.sh +42 -14
- package/autonomy/setup-wizard.sh +19 -10
- package/autonomy/verify.sh +40 -1
- package/completions/_loki +3 -1
- package/completions/loki.bash +9 -3
- package/dashboard/__init__.py +3 -10
- package/dashboard/control.py +5 -435
- package/docs/ARCHITECTURE.md +1 -2
- package/docs/CLI-CORE-VERBS.md +2 -6
- package/docs/CLI-REFERENCE.md +3 -1
- package/docs/COMPONENTS.md +0 -2
- package/docs/EVALUATING.md +27 -0
- package/docs/HARNESS-INTELLIGENCE-PLAN.md +20 -0
- package/docs/INSTALLATION.md +1 -1
- package/docs/KNOWN-ISSUES.md +10 -1
- package/docs/PROOF-PASSPORT.md +114 -0
- package/docs/alternative-installations.md +103 -0
- package/docs/authentication.md +19 -1
- package/docs/cost-controls.md +139 -0
- package/docs/dev/release-checklist.md +9 -8
- package/docs/enterprise/security.md +3 -234
- package/docs/environment-variables.md +36 -0
- package/docs/exit-codes.md +102 -2
- package/docs/spec-first-intent.md +29 -0
- package/docs/v10/ARCHITECT-CUTS.md +304 -0
- package/docs/v10/CLI-MODERN.md +2 -2
- package/docs/v10/DECISIONS.md +5 -5
- package/docs/v10/ENGINE.md +735 -267
- package/docs/v10/GUARDS.md +16 -14
- package/docs/v10/GUIDE.md +5 -7
- package/docs/v10/PROGRESS.md +12 -1
- package/docs/v11/MARKET-SLICES.md +1 -5
- package/integrations/openclaw/README.md +20 -0
- package/integrations/vibe-kanban.md +12 -50
- package/loki-ts/data/model-pricing.json +1 -0
- package/loki-ts/dist/loki.js +1156 -1103
- package/mcp/__init__.py +1 -1
- package/mcp/_sdk_loader.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +2063 -1032
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/plugins/loki-mode/README.md +41 -1
- package/plugins/loki-mode/commands/handoff.md +12 -0
- package/plugins/loki-mode/commands/loki-verify.md +9 -2
- package/plugins/loki-mode/commands/start.md +20 -0
- package/plugins/loki-mode/hooks/hooks.json +11 -0
- package/plugins/loki-mode/scripts/loki-stop-verify.sh +84 -0
- package/references/quality-control.md +1 -116
- package/skills/00-index.md +2 -2
- package/skills/artifacts.md +0 -22
- package/skills/production.md +2 -38
- package/web-app/dist/assets/{AdminPage-CYJ2BWRC.js → AdminPage-DhXdJNQk.js} +1 -1
- package/web-app/dist/assets/{Avatar-DdK4wynI.js → Avatar-ixVj76fM.js} +1 -1
- package/web-app/dist/assets/{Badge-CrX3MI7J.js → Badge-BGMSp22s.js} +1 -1
- package/web-app/dist/assets/{Button-709-wwGM.js → Button-pYJc32tG.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BapdTInO.js → CockpitPage-C1JBBouA.js} +1 -1
- package/web-app/dist/assets/{ComparePage-DT99pGvA.js → ComparePage-qD0NV4RK.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-B0uJSys8.js → ErrorBoundary-C-yxO58j.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-BtMFgElP.js → EvidenceReceiptPanel-ByPzuE1S.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-DjJuU941.js → GitHubIssuesPanel-BNv_2BEJ.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-HXfEX1BC.js → GitHubPRsPanel-CuLZQ9CS.js} +1 -1
- package/web-app/dist/assets/{HomePage-Cqyves-S.js → HomePage-CS_FW8Je.js} +2 -2
- package/web-app/dist/assets/{LoginPage-jUbtC7_Y.js → LoginPage-DwiNf1Wm.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DHRsqjgR.js → MagicPage-CmQybLPZ.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-B-pED05R.js → MetricsPage-D9ERdpv8.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-CsbUs21R.js → NotFoundPage-D9ccgdbF.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BtlLEQwB.js → ProjectPage-B9Jslmgv.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-DaRbRjSp.js → ProjectsPage-DnU99Go2.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-DALrtP3F.js → SettingsPage-DcTyx6wo.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-CoPHSNP0.js → ShowcasePage-jEY9ghSr.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-kOuSN50O.js → SystemSettingsPage-CSGltW-8.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-DGWK0LMp.js → TeamsPage-gUuJXb9l.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-C-_10CwA.js → TemplatesPage-37Zhe--v.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-Cz_vPbXL.js → TerminalOutput-B09hRwaR.js} +1 -1
- package/web-app/dist/assets/{activity-icIsXJVa.js → activity-q_4MY9J0.js} +1 -1
- package/web-app/dist/assets/{bell-DbPR2_4r.js → bell-X8OC_PFP.js} +1 -1
- package/web-app/dist/assets/{bot-DvjP3Yh5.js → bot-C-N1PUOD.js} +1 -1
- package/web-app/dist/assets/{check-D1OBxHeU.js → check-B2c76Jmp.js} +1 -1
- package/web-app/dist/assets/{chevron-left-D09jDZ29.js → chevron-left-BF76YLNw.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DyZMLOcC.js → circle-alert-AeFKBD8h.js} +1 -1
- package/web-app/dist/assets/{clock-CdpdJTId.js → clock-CiXRf-FH.js} +1 -1
- package/web-app/dist/assets/{cloud-CoLTlmoj.js → cloud-DKQ8SRJ5.js} +1 -1
- package/web-app/dist/assets/{code-xml-DrkE2ZZY.js → code-xml-DEb6jXWP.js} +1 -1
- package/web-app/dist/assets/{database-CMQQuM2R.js → database-6W7e-E9c.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-CuioDaOG.js → dollar-sign-Ds2GOuyW.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-Cx9j9K1S.js → file-code-corner-CZU0jg52.js} +1 -1
- package/web-app/dist/assets/{file-plus-CYD2zaaI.js → file-plus-DTVYnXCN.js} +1 -1
- package/web-app/dist/assets/{globe-6w-hnQkL.js → globe-0mUzKYDe.js} +1 -1
- package/web-app/dist/assets/{hammer-BpqtautL.js → hammer-BMHZg3YX.js} +1 -1
- package/web-app/dist/assets/{index-Dzs-9EgJ.js → index-BvOTvjax.js} +3 -3
- package/web-app/dist/assets/{layers-D5hd7jb7.js → layers-Df7TdcWB.js} +1 -1
- package/web-app/dist/assets/{loader-circle-C6Y9x_7H.js → loader-circle-kPsVfrSY.js} +1 -1
- package/web-app/dist/assets/{lock-D51L7rYh.js → lock-CXYcbEc5.js} +1 -1
- package/web-app/dist/assets/{package-BJF8hkP8.js → package-Cqhua1ID.js} +1 -1
- package/web-app/dist/assets/{plus-D5kUQ79n.js → plus-lseXhsn-.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BKdlCa7F.js → refresh-cw-OZriI6gD.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-_bd_YI1e.js → rotate-ccw-CKCfjPg8.js} +1 -1
- package/web-app/dist/assets/{scroll-text-DbkegIxF.js → scroll-text-DoVyyfKb.js} +1 -1
- package/web-app/dist/assets/{server-DAwqFIsq.js → server-DGy7TflW.js} +1 -1
- package/web-app/dist/assets/{shield-alert-BuR1tPHu.js → shield-alert-BCzRcMXi.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-BF6p3mRd.js → thumbs-up-C_znJd5g.js} +1 -1
- package/web-app/dist/assets/{trash-2-BVpnkqqz.js → trash-2-BosCbeeA.js} +1 -1
- package/web-app/dist/assets/{trending-down-DpsDvRwW.js → trending-down-CkR5zDkV.js} +1 -1
- package/web-app/dist/assets/{trending-up-mFogTjt3.js → trending-up-DpUIfOH4.js} +1 -1
- package/web-app/dist/assets/{upload-4T8zpBsi.js → upload-CH9aKc8L.js} +1 -1
- package/web-app/dist/assets/{usePolling-DPXQv8WO.js → usePolling-pzxrHJeg.js} +1 -1
- package/web-app/dist/assets/{user-BYGIFaTm.js → user-B7OHa-VQ.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/dashboard/run.py +0 -63
- package/docs/openclaw-integration.md +0 -52
- package/docs/v10/HANDOFF-NEXT-WEEK.md +0 -40
- package/docs/v10/MORNING-BRIEF-1003.md +0 -25
- package/docs/v10/MORNING-BRIEF.md +0 -52
- package/docs/v10/TODO.md +0 -35
- package/mcp/resources.py +0 -351
- package/mcp/tools.py +0 -200
package/dashboard/control.py
CHANGED
|
@@ -1,62 +1,23 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Loki Mode
|
|
2
|
+
Loki Mode session-control helpers
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
uvicorn dashboard.control:app --host 0.0.0.0 --port 57374
|
|
9
|
-
# Or run with the CLI:
|
|
10
|
-
loki dashboard start
|
|
4
|
+
Shared helpers for the dashboard: session status, atomic JSON writes, skill-dir
|
|
5
|
+
discovery and the browser-mutation Origin check. The HTTP control routes live
|
|
6
|
+
in dashboard/server.py (the only dashboard app).
|
|
11
7
|
"""
|
|
12
8
|
|
|
13
|
-
import asyncio
|
|
14
9
|
import fcntl
|
|
15
10
|
import json
|
|
16
11
|
import os
|
|
17
|
-
import signal
|
|
18
|
-
import subprocess
|
|
19
12
|
import tempfile
|
|
20
13
|
from datetime import datetime, timezone
|
|
21
14
|
from pathlib import Path
|
|
22
15
|
from typing import Optional
|
|
23
16
|
from urllib.parse import urlsplit
|
|
24
17
|
|
|
25
|
-
from fastapi import
|
|
26
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
27
|
-
from fastapi.responses import JSONResponse, StreamingResponse
|
|
18
|
+
from fastapi import Request
|
|
28
19
|
from pydantic import BaseModel
|
|
29
20
|
|
|
30
|
-
# Auth gating for the standalone control app.
|
|
31
|
-
#
|
|
32
|
-
# This module also defines a self-contained FastAPI `app` whose own docstring
|
|
33
|
-
# invites operators to expose it via `uvicorn dashboard.control:app`. When that
|
|
34
|
-
# happens the state-mutating endpoints (start/stop/pause/resume) MUST honor the
|
|
35
|
-
# same scope checks as the primary dashboard (dashboard/server.py), otherwise a
|
|
36
|
-
# user who follows the docstring stands up an unauthenticated control plane that
|
|
37
|
-
# can launch arbitrary builds and kill running sessions even when
|
|
38
|
-
# LOKI_ENTERPRISE_AUTH / OIDC are configured.
|
|
39
|
-
#
|
|
40
|
-
# auth.require_scope is a no-op (allows access) when no auth method is enabled,
|
|
41
|
-
# so this import is safe for the default anonymous-localhost workflow and only
|
|
42
|
-
# enforces when the operator has explicitly turned auth on. The import is
|
|
43
|
-
# defensive: if the package context is unavailable (e.g. the file is run from a
|
|
44
|
-
# path where the relative import fails) we fall back to a gate that always
|
|
45
|
-
# allows, preserving the prior behavior rather than crashing import.
|
|
46
|
-
try:
|
|
47
|
-
from . import auth as _auth
|
|
48
|
-
|
|
49
|
-
def _require_control_scope():
|
|
50
|
-
return Depends(_auth.require_scope("control"))
|
|
51
|
-
except Exception: # pragma: no cover - defensive fallback for non-package runs
|
|
52
|
-
def _require_control_scope():
|
|
53
|
-
async def _noop() -> bool:
|
|
54
|
-
return True
|
|
55
|
-
|
|
56
|
-
return Depends(_noop)
|
|
57
|
-
|
|
58
|
-
_CONTROL_DEP = _require_control_scope()
|
|
59
|
-
|
|
60
21
|
|
|
61
22
|
def _normalized_http_origin(value: str) -> tuple[str, str, int] | None:
|
|
62
23
|
"""Return a comparable browser origin, or ``None`` when malformed.
|
|
@@ -250,104 +211,6 @@ def atomic_write_json(file_path: Path, data: dict, use_lock: bool = True):
|
|
|
250
211
|
except Exception as e:
|
|
251
212
|
raise RuntimeError(f"Failed to write {file_path}: {e}")
|
|
252
213
|
|
|
253
|
-
# FastAPI app
|
|
254
|
-
app = FastAPI(
|
|
255
|
-
title="Loki Mode Control API",
|
|
256
|
-
description="Session control endpoints for Loki Mode dashboard",
|
|
257
|
-
version="1.0.0"
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
# CORS middleware for dashboard frontend - restricted to localhost by default.
|
|
261
|
-
# Set LOKI_DASHBOARD_CORS to override (comma-separated origins).
|
|
262
|
-
_cors_default = "http://localhost:57374,http://127.0.0.1:57374"
|
|
263
|
-
_cors_raw = os.environ.get("LOKI_DASHBOARD_CORS", _cors_default)
|
|
264
|
-
_cors_origins = [o.strip() for o in _cors_raw.split(",") if o.strip()]
|
|
265
|
-
if "*" in _cors_origins or _cors_raw.strip() == "*":
|
|
266
|
-
if os.environ.get("LOKI_ENV") == "production":
|
|
267
|
-
raise RuntimeError(
|
|
268
|
-
"Wildcard CORS ('*') is not allowed in production. "
|
|
269
|
-
"Set LOKI_DASHBOARD_CORS to a specific origin list, "
|
|
270
|
-
"or set LOKI_ENV != production."
|
|
271
|
-
)
|
|
272
|
-
import logging
|
|
273
|
-
|
|
274
|
-
logging.getLogger(__name__).warning(
|
|
275
|
-
"LOKI_DASHBOARD_CORS is set to '*' -- all origins are allowed. "
|
|
276
|
-
"This is insecure for production deployments."
|
|
277
|
-
)
|
|
278
|
-
app.add_middleware(
|
|
279
|
-
CORSMiddleware,
|
|
280
|
-
allow_origins=_cors_origins,
|
|
281
|
-
allow_credentials=True,
|
|
282
|
-
allow_methods=["*"],
|
|
283
|
-
allow_headers=["*"],
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
@app.middleware("http")
|
|
288
|
-
async def browser_mutation_boundary(request: Request, call_next):
|
|
289
|
-
"""Refuse cross-origin browser mutations before control side effects."""
|
|
290
|
-
if request.method in ("POST", "PUT", "PATCH", "DELETE"):
|
|
291
|
-
if not browser_mutation_origin_allowed(request, _cors_origins):
|
|
292
|
-
return JSONResponse(
|
|
293
|
-
status_code=403,
|
|
294
|
-
content={"detail": "cross-origin mutation refused"},
|
|
295
|
-
)
|
|
296
|
-
return await call_next(request)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
# Request/Response models
|
|
300
|
-
class StartRequest(BaseModel):
|
|
301
|
-
"""Request body for starting a Loki Mode session."""
|
|
302
|
-
prd: Optional[str] = None
|
|
303
|
-
provider: str = "claude"
|
|
304
|
-
parallel: bool = False
|
|
305
|
-
background: bool = True
|
|
306
|
-
|
|
307
|
-
def validate_provider(self) -> None:
|
|
308
|
-
"""Validate provider is from allowed list."""
|
|
309
|
-
allowed_providers = ["claude", "codex", "gemini", "cline", "aider"]
|
|
310
|
-
if self.provider not in allowed_providers:
|
|
311
|
-
raise ValueError(f"Invalid provider: {self.provider}. Must be one of: {', '.join(allowed_providers)}")
|
|
312
|
-
|
|
313
|
-
def validate_prd_path(self) -> None:
|
|
314
|
-
"""Validate PRD path is safe and exists."""
|
|
315
|
-
if not self.prd:
|
|
316
|
-
return
|
|
317
|
-
|
|
318
|
-
# Check for path traversal sequences
|
|
319
|
-
if ".." in self.prd:
|
|
320
|
-
raise ValueError("PRD path contains path traversal sequence (..)")
|
|
321
|
-
|
|
322
|
-
# Resolve to absolute path and verify it exists
|
|
323
|
-
prd_path = Path(self.prd).resolve()
|
|
324
|
-
if not prd_path.exists():
|
|
325
|
-
raise ValueError(f"PRD file does not exist: {self.prd}")
|
|
326
|
-
|
|
327
|
-
# Verify it's a file, not a directory
|
|
328
|
-
if not prd_path.is_file():
|
|
329
|
-
raise ValueError(f"PRD path is not a file: {self.prd}")
|
|
330
|
-
|
|
331
|
-
# Verify path resolves within CWD or a reasonable parent.
|
|
332
|
-
# FAIL CLOSED if the cwd is gone: this is a path-confinement check, so
|
|
333
|
-
# an unresolvable base must reject the path, never skip the comparison.
|
|
334
|
-
try:
|
|
335
|
-
cwd = Path.cwd().resolve()
|
|
336
|
-
except OSError as exc:
|
|
337
|
-
raise ValueError(
|
|
338
|
-
"cannot validate PRD path: the working directory no longer "
|
|
339
|
-
"exists (%s)" % exc
|
|
340
|
-
) from exc
|
|
341
|
-
try:
|
|
342
|
-
prd_path.relative_to(cwd)
|
|
343
|
-
except ValueError:
|
|
344
|
-
# Not within CWD - check if it's within user's home or project directory
|
|
345
|
-
home = Path.home().resolve()
|
|
346
|
-
try:
|
|
347
|
-
prd_path.relative_to(home)
|
|
348
|
-
except ValueError:
|
|
349
|
-
raise ValueError(f"PRD path is outside allowed directories: {self.prd}")
|
|
350
|
-
|
|
351
214
|
|
|
352
215
|
class StatusResponse(BaseModel):
|
|
353
216
|
"""Current session status."""
|
|
@@ -365,13 +228,6 @@ class StatusResponse(BaseModel):
|
|
|
365
228
|
timestamp: str
|
|
366
229
|
|
|
367
230
|
|
|
368
|
-
class ControlResponse(BaseModel):
|
|
369
|
-
"""Generic control operation response."""
|
|
370
|
-
success: bool
|
|
371
|
-
message: str
|
|
372
|
-
pid: Optional[int] = None
|
|
373
|
-
|
|
374
|
-
|
|
375
231
|
# Utility functions
|
|
376
232
|
def read_file_safe(filepath: Path) -> str:
|
|
377
233
|
"""Read file contents safely, returning empty string on error."""
|
|
@@ -520,289 +376,3 @@ def get_status() -> StatusResponse:
|
|
|
520
376
|
complexity=complexity,
|
|
521
377
|
timestamp=datetime.now(timezone.utc).isoformat()
|
|
522
378
|
)
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
# API Endpoints
|
|
526
|
-
@app.get("/api/control/health")
|
|
527
|
-
async def health_check():
|
|
528
|
-
"""Health check endpoint."""
|
|
529
|
-
return {"status": "ok", "version": get_version()}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
@app.get("/api/control/status", response_model=StatusResponse)
|
|
533
|
-
async def get_session_status():
|
|
534
|
-
"""Get current session status."""
|
|
535
|
-
return get_status()
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
@app.post("/api/control/start", response_model=ControlResponse, dependencies=[_CONTROL_DEP])
|
|
539
|
-
async def start_session(request: StartRequest):
|
|
540
|
-
"""
|
|
541
|
-
Start a Loki Mode session.
|
|
542
|
-
|
|
543
|
-
Args:
|
|
544
|
-
request: Start request with PRD path, provider, and options
|
|
545
|
-
|
|
546
|
-
Returns:
|
|
547
|
-
ControlResponse with success status and PID
|
|
548
|
-
"""
|
|
549
|
-
# Validate input
|
|
550
|
-
try:
|
|
551
|
-
request.validate_provider()
|
|
552
|
-
request.validate_prd_path()
|
|
553
|
-
except ValueError as e:
|
|
554
|
-
raise HTTPException(status_code=400, detail=str(e))
|
|
555
|
-
|
|
556
|
-
# Check if already running
|
|
557
|
-
status = get_status()
|
|
558
|
-
if status.state == "running":
|
|
559
|
-
raise HTTPException(
|
|
560
|
-
status_code=409,
|
|
561
|
-
detail=f"Session already running with PID {status.pid}"
|
|
562
|
-
)
|
|
563
|
-
|
|
564
|
-
# Verify run.sh exists
|
|
565
|
-
if not RUN_SH.exists():
|
|
566
|
-
raise HTTPException(
|
|
567
|
-
status_code=500,
|
|
568
|
-
detail=f"run.sh not found at {RUN_SH}"
|
|
569
|
-
)
|
|
570
|
-
|
|
571
|
-
# Build command arguments
|
|
572
|
-
args = [str(RUN_SH), "--provider", request.provider]
|
|
573
|
-
if request.parallel:
|
|
574
|
-
args.append("--parallel")
|
|
575
|
-
if request.background:
|
|
576
|
-
args.append("--bg")
|
|
577
|
-
if request.prd:
|
|
578
|
-
args.append(request.prd)
|
|
579
|
-
|
|
580
|
-
try:
|
|
581
|
-
# Start the process
|
|
582
|
-
# RUN_SH is a trusted fixed path; provider and PRD were validated above,
|
|
583
|
-
# and argv is passed directly without a command shell.
|
|
584
|
-
process = subprocess.Popen(
|
|
585
|
-
args, # lgtm[py/command-line-injection]
|
|
586
|
-
stdout=subprocess.DEVNULL,
|
|
587
|
-
stderr=subprocess.DEVNULL,
|
|
588
|
-
start_new_session=True,
|
|
589
|
-
# A deleted cwd would make Popen itself raise; fall back to the
|
|
590
|
-
# skill tree rather than failing to launch the run at all.
|
|
591
|
-
cwd=str(_cwd_or_skill_dir()),
|
|
592
|
-
shell=False,
|
|
593
|
-
)
|
|
594
|
-
|
|
595
|
-
# Save provider for status tracking
|
|
596
|
-
(STATE_DIR / "provider").write_text(request.provider)
|
|
597
|
-
|
|
598
|
-
# Emit start event
|
|
599
|
-
emit_event("session_start", {
|
|
600
|
-
"pid": process.pid,
|
|
601
|
-
"provider": request.provider,
|
|
602
|
-
"prd": request.prd,
|
|
603
|
-
"parallel": request.parallel
|
|
604
|
-
})
|
|
605
|
-
|
|
606
|
-
return ControlResponse(
|
|
607
|
-
success=True,
|
|
608
|
-
message=f"Session started with provider {request.provider}",
|
|
609
|
-
pid=process.pid
|
|
610
|
-
)
|
|
611
|
-
except Exception as e:
|
|
612
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
@app.post("/api/control/stop", response_model=ControlResponse, dependencies=[_CONTROL_DEP])
|
|
616
|
-
async def stop_session():
|
|
617
|
-
"""
|
|
618
|
-
Stop the current Loki Mode session.
|
|
619
|
-
|
|
620
|
-
Creates a STOP file to signal graceful shutdown and attempts
|
|
621
|
-
to terminate the process directly.
|
|
622
|
-
"""
|
|
623
|
-
stop_file = LOKI_DIR / "STOP"
|
|
624
|
-
pid_file = LOKI_DIR / "loki.pid"
|
|
625
|
-
|
|
626
|
-
# Create STOP file for graceful shutdown
|
|
627
|
-
stop_file.parent.mkdir(parents=True, exist_ok=True)
|
|
628
|
-
stop_file.write_text(datetime.now(timezone.utc).isoformat())
|
|
629
|
-
|
|
630
|
-
# Also try to terminate process directly
|
|
631
|
-
pid_str = read_file_safe(pid_file)
|
|
632
|
-
pid = None
|
|
633
|
-
if pid_str.isdigit():
|
|
634
|
-
pid = int(pid_str)
|
|
635
|
-
if is_process_running(pid):
|
|
636
|
-
try:
|
|
637
|
-
os.kill(pid, signal.SIGTERM)
|
|
638
|
-
except (OSError, ProcessLookupError):
|
|
639
|
-
pass
|
|
640
|
-
|
|
641
|
-
# Mark session.json as stopped (skill-invoked sessions)
|
|
642
|
-
session_file = LOKI_DIR / "session.json"
|
|
643
|
-
if session_file.exists():
|
|
644
|
-
try:
|
|
645
|
-
# Read current session data
|
|
646
|
-
session_data = json.loads(session_file.read_text())
|
|
647
|
-
session_data["status"] = "stopped"
|
|
648
|
-
|
|
649
|
-
# Atomic write with file locking to prevent race conditions
|
|
650
|
-
atomic_write_json(session_file, session_data, use_lock=True)
|
|
651
|
-
except (json.JSONDecodeError, OSError, KeyError, RuntimeError):
|
|
652
|
-
pass
|
|
653
|
-
|
|
654
|
-
# Emit stop event
|
|
655
|
-
emit_event("session_stop", {"pid": pid, "reason": "user_request"})
|
|
656
|
-
|
|
657
|
-
return ControlResponse(
|
|
658
|
-
success=True,
|
|
659
|
-
message="Stop signal sent",
|
|
660
|
-
pid=pid
|
|
661
|
-
)
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
@app.post("/api/control/pause", response_model=ControlResponse, dependencies=[_CONTROL_DEP])
|
|
665
|
-
async def pause_session():
|
|
666
|
-
"""
|
|
667
|
-
Pause the current Loki Mode session.
|
|
668
|
-
|
|
669
|
-
Creates a PAUSE file that signals the runner to pause
|
|
670
|
-
after completing the current task.
|
|
671
|
-
"""
|
|
672
|
-
pause_file = LOKI_DIR / "PAUSE"
|
|
673
|
-
|
|
674
|
-
# Create PAUSE file
|
|
675
|
-
pause_file.parent.mkdir(parents=True, exist_ok=True)
|
|
676
|
-
pause_file.write_text(datetime.now(timezone.utc).isoformat())
|
|
677
|
-
|
|
678
|
-
# Get current PID for response
|
|
679
|
-
pid_file = LOKI_DIR / "loki.pid"
|
|
680
|
-
pid_str = read_file_safe(pid_file)
|
|
681
|
-
pid = int(pid_str) if pid_str.isdigit() else None
|
|
682
|
-
|
|
683
|
-
# Emit pause event
|
|
684
|
-
emit_event("session_pause", {"pid": pid})
|
|
685
|
-
|
|
686
|
-
return ControlResponse(
|
|
687
|
-
success=True,
|
|
688
|
-
message="Pause signal sent - session will pause after current task",
|
|
689
|
-
pid=pid
|
|
690
|
-
)
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
@app.post("/api/control/resume", response_model=ControlResponse, dependencies=[_CONTROL_DEP])
|
|
694
|
-
async def resume_session():
|
|
695
|
-
"""
|
|
696
|
-
Resume a paused Loki Mode session.
|
|
697
|
-
|
|
698
|
-
Removes the PAUSE and STOP files to allow the session to continue.
|
|
699
|
-
"""
|
|
700
|
-
pause_file = LOKI_DIR / "PAUSE"
|
|
701
|
-
stop_file = LOKI_DIR / "STOP"
|
|
702
|
-
|
|
703
|
-
# Remove control files
|
|
704
|
-
try:
|
|
705
|
-
pause_file.unlink(missing_ok=True)
|
|
706
|
-
except Exception:
|
|
707
|
-
pass
|
|
708
|
-
|
|
709
|
-
try:
|
|
710
|
-
stop_file.unlink(missing_ok=True)
|
|
711
|
-
except Exception:
|
|
712
|
-
pass
|
|
713
|
-
|
|
714
|
-
# Get current PID for response
|
|
715
|
-
pid_file = LOKI_DIR / "loki.pid"
|
|
716
|
-
pid_str = read_file_safe(pid_file)
|
|
717
|
-
pid = int(pid_str) if pid_str.isdigit() else None
|
|
718
|
-
|
|
719
|
-
# Emit resume event
|
|
720
|
-
emit_event("session_resume", {"pid": pid})
|
|
721
|
-
|
|
722
|
-
return ControlResponse(
|
|
723
|
-
success=True,
|
|
724
|
-
message="Session resumed",
|
|
725
|
-
pid=pid
|
|
726
|
-
)
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
@app.get("/api/control/events")
|
|
730
|
-
async def stream_events():
|
|
731
|
-
"""
|
|
732
|
-
Stream events as Server-Sent Events (SSE).
|
|
733
|
-
|
|
734
|
-
Provides real-time updates to the dashboard frontend.
|
|
735
|
-
"""
|
|
736
|
-
async def event_generator():
|
|
737
|
-
# Send initial status
|
|
738
|
-
status = get_status()
|
|
739
|
-
yield f"data: {json.dumps(status.model_dump())}\n\n"
|
|
740
|
-
|
|
741
|
-
# Track file position for incremental reads
|
|
742
|
-
last_position = 0
|
|
743
|
-
if EVENTS_FILE.exists():
|
|
744
|
-
last_position = EVENTS_FILE.stat().st_size
|
|
745
|
-
|
|
746
|
-
# Stream updates
|
|
747
|
-
while True:
|
|
748
|
-
# Send status update every 2 seconds
|
|
749
|
-
status = get_status()
|
|
750
|
-
yield f"event: status\ndata: {json.dumps(status.model_dump())}\n\n"
|
|
751
|
-
|
|
752
|
-
# Check for new events
|
|
753
|
-
if EVENTS_FILE.exists():
|
|
754
|
-
current_size = EVENTS_FILE.stat().st_size
|
|
755
|
-
if current_size > last_position:
|
|
756
|
-
with open(EVENTS_FILE, "r") as f:
|
|
757
|
-
f.seek(last_position)
|
|
758
|
-
for line in f:
|
|
759
|
-
line = line.strip()
|
|
760
|
-
if line:
|
|
761
|
-
yield f"event: log\ndata: {line}\n\n"
|
|
762
|
-
last_position = current_size
|
|
763
|
-
|
|
764
|
-
await asyncio_sleep(2)
|
|
765
|
-
|
|
766
|
-
return StreamingResponse(
|
|
767
|
-
event_generator(),
|
|
768
|
-
media_type="text/event-stream",
|
|
769
|
-
headers={
|
|
770
|
-
"Cache-Control": "no-cache",
|
|
771
|
-
"Connection": "keep-alive"
|
|
772
|
-
}
|
|
773
|
-
)
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
@app.get("/api/control/logs")
|
|
777
|
-
async def get_logs(lines: int = 50):
|
|
778
|
-
"""
|
|
779
|
-
Get recent log lines from the session log.
|
|
780
|
-
|
|
781
|
-
Args:
|
|
782
|
-
lines: Number of lines to return (default 50, max 10000)
|
|
783
|
-
"""
|
|
784
|
-
lines = min(max(lines, 1), 10000)
|
|
785
|
-
log_file = LOG_DIR / "session.log"
|
|
786
|
-
|
|
787
|
-
if not log_file.exists():
|
|
788
|
-
return {"logs": [], "total": 0}
|
|
789
|
-
|
|
790
|
-
content = log_file.read_text()
|
|
791
|
-
all_lines = [line for line in content.split("\n") if line.strip()]
|
|
792
|
-
recent_lines = all_lines[-lines:]
|
|
793
|
-
|
|
794
|
-
return {"logs": recent_lines, "total": len(all_lines)}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
# Helper for async sleep
|
|
798
|
-
async def asyncio_sleep(seconds: float):
|
|
799
|
-
"""Async sleep helper."""
|
|
800
|
-
await asyncio.sleep(seconds)
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
# Run with uvicorn if executed directly
|
|
804
|
-
if __name__ == "__main__":
|
|
805
|
-
import uvicorn
|
|
806
|
-
port = int(os.environ.get("LOKI_DASHBOARD_PORT", "57374"))
|
|
807
|
-
host = os.environ.get("LOKI_DASHBOARD_HOST", "127.0.0.1")
|
|
808
|
-
uvicorn.run(app, host=host, port=port)
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -215,8 +215,7 @@ memory/ Memory system (Python package, ~19 modules)
|
|
|
215
215
|
|
|
216
216
|
mcp/ Model Context Protocol server
|
|
217
217
|
server.py MCP server (tools + resources + prompts)
|
|
218
|
-
|
|
219
|
-
resources.py MCP resources
|
|
218
|
+
magic_tools.py / managed_tools.py Tool registries
|
|
220
219
|
lsp_proxy.py LSP proxy (references, definitions, symbols)
|
|
221
220
|
|
|
222
221
|
dashboard/ FastAPI control-plane backend
|
package/docs/CLI-CORE-VERBS.md
CHANGED
|
@@ -98,12 +98,8 @@ The process exit code equals `exit_code`.
|
|
|
98
98
|
| 3 | Missing component, or verifier error |
|
|
99
99
|
| 64 | Usage error for `verify`, `quick` and `start` (unknown option, missing task) |
|
|
100
100
|
|
|
101
|
-
Severity rises with the code. `loki verify`
|
|
102
|
-
|
|
103
|
-
Gate CI with `loki verify || fail` and treat any code of 2 or higher as worse
|
|
104
|
-
than CONCERNS. From 11.4.0 this is a breaking change for scripts that tested
|
|
105
|
-
`rc == 1` on an empty diff or a non-git directory. Per-command detail:
|
|
106
|
-
`docs/exit-codes.md`.
|
|
101
|
+
Severity rises with the code. `loki verify` codes and the CI-gate guidance are
|
|
102
|
+
in `docs/exit-codes.md`.
|
|
107
103
|
|
|
108
104
|
## Unknown commands
|
|
109
105
|
|
package/docs/CLI-REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CLI reference
|
|
2
2
|
|
|
3
|
-
Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.
|
|
3
|
+
Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.6.0. Do not edit by hand.
|
|
4
4
|
Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for the full inventory.
|
|
5
5
|
|
|
6
6
|
| Command | Description |
|
|
@@ -12,6 +12,8 @@ Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for th
|
|
|
12
12
|
| `loki provider show` | Show current provider |
|
|
13
13
|
| `loki provider list` | List providers and install status |
|
|
14
14
|
| `loki provider set` | Switch the active provider |
|
|
15
|
+
| `loki provider info` | Provider details; --json prints the capability contract |
|
|
16
|
+
| `loki provider matrix` | Capability matrix per provider and engine |
|
|
15
17
|
| `loki memory` | Cross-project learnings |
|
|
16
18
|
| `loki memory list` | All learnings |
|
|
17
19
|
| `loki memory index` | Show or rebuild the memory index |
|
package/docs/COMPONENTS.md
CHANGED
|
@@ -249,13 +249,11 @@ search) to MCP clients.
|
|
|
249
249
|
**Key files:**
|
|
250
250
|
- `server.py` - MCP server (47 tools: 31 in-file + magic + gated managed + 8 cp; plus
|
|
251
251
|
3 resources, 2 prompts).
|
|
252
|
-
- `tools.py` - Core tool implementations.
|
|
253
252
|
- `magic_tools.py` - Magic Modules tools.
|
|
254
253
|
- `cp_tools.py` - Read-only Control Plane data tools (`cp_*`, GET only; the CP URL
|
|
255
254
|
and token come from the MCP child env and are never returned).
|
|
256
255
|
- `managed_tools.py` - Managed-memory tool (`loki_memory_redact`, gated on
|
|
257
256
|
`LOKI_MANAGED_AGENTS=true` and `LOKI_MANAGED_MEMORY=true`).
|
|
258
|
-
- `resources.py` - MCP resources.
|
|
259
257
|
- `lsp_proxy.py` - LSP proxy (find references, go to definition, symbol lookup).
|
|
260
258
|
- `learning_collector.py` - Learning signal collection.
|
|
261
259
|
- `_sdk_loader.py` - Bootstraps the Python MCP SDK on first run.
|
package/docs/EVALUATING.md
CHANGED
|
@@ -184,6 +184,33 @@ per-call cost was not recorded.
|
|
|
184
184
|
|
|
185
185
|
---
|
|
186
186
|
|
|
187
|
+
## 8. Harness attribution: what Loki adds to the same model
|
|
188
|
+
|
|
189
|
+
Question: what does Loki add to the same underlying coding agent, and is the benefit worth its cost? Status: pilot. Six tasks, small n. A pilot reports variance; it is not proof of superiority. Running it is a separate, paid step.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
loki bench attribution --dry-run # validate task set, print plan, no model call
|
|
193
|
+
loki bench attribution --model <model-id> --execute # run, spends API budget
|
|
194
|
+
loki bench attribution --report <result.json> # re-render the markdown report
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Source checkout only. Implementation: `benchmarks/bench/attribution.py`, reusing `runner._apply_overlay`, `report._median` and `benchmarks/bench/adapters` (process-tree reaping, the Claude JSON parser, the Loki adapter and its cost collection). Options: `--tasks id,id`, `--repeats N` (default 1), `--model ID` (default `claude-sonnet-5`, or `LOKI_BENCH_MODEL`), `--budget-usd X` (default 5), `--timeout SECONDS` (default 900, capped by each task's `agent_timeout_s`), `--out-dir DIR`, `--tasks-dir DIR`. Without `--execute` it only prints the plan and never spawns `claude` or `loki`.
|
|
198
|
+
|
|
199
|
+
Arms and controls: `raw_claude` runs `claude -p <prompt> --output-format json --model M --dangerously-skip-permissions --max-budget-usd B`; `loki` runs `loki start <prompt-file> --provider claude --no-dashboard --yes` with `LOKI_SESSION_MODEL=M` and `LOKI_BUDGET_LIMIT=B`. Both get a byte-identical seed in a fresh git repo with one fixed "seed" commit, the same prompt text (the PRD file for Loki is kept outside the workspace), the same wall-clock cap with the process tree reaped on timeout, separate scratch directories, a scoped child environment and `LOKI_NO_BROWSER=1`. The arm that goes first alternates per task and repeat. Every result JSON pins `claude --version`, `loki version`, the Loki commit SHA, Python version, model, budget, timeout, repeats, and per task the sha256 of the seed tree, acceptance tree and prompt. Disclosed difference: Loki runs its own planning, review and verification loop; that overhead is what is measured. Provider comparisons are out of scope so model and harness effects are not mixed.
|
|
200
|
+
|
|
201
|
+
Tasks live under `benchmarks/attribution/tasks/<id>/` (2 bug fixes, 2 features, 1 refactor, 1 integration): `task.json`, `prompt.md` (the only instruction either arm sees), `seed/` (workspace), `acceptance/` (hidden test, copied in only after the run, overwriting same-named files) and `reference/` (known-good overlay, used only by tests). Exit code 0 from `acceptance.cmd` means accepted. The loader rejects a task whose acceptance test also appears in the seed. Neither arm grades itself: no council, RARV-C or LLM judge takes part in scoring.
|
|
202
|
+
|
|
203
|
+
Metrics, per arm and per task class, computed by `compute_metrics`; failures, timeouts and harness errors stay in every denominator:
|
|
204
|
+
|
|
205
|
+
- Acceptance rate: accepted / assigned, with a 95% Wilson interval.
|
|
206
|
+
- False-completion rate: (reported success and not accepted) / reported success; null when nothing reported success. Reported success means `claude -p` exits 0 with `is_error` false, or `loki start` exits 0.
|
|
207
|
+
- Cost per accepted task: total measured spend across all attempts / accepted. Null (`undefined_no_accepted`) when none accepted. A run with no cost figure is `unmeasured`, never zero: some unmeasured gives `partial_lower_bound` (per-accepted null, lower bound only), all unmeasured gives `unmeasured`. Cost is API-dollar accounting; subscription use is not converted.
|
|
208
|
+
- Elapsed time: median over accepted runs, with completion rate, timeout count and error count; median over all runs is also given.
|
|
209
|
+
|
|
210
|
+
Not measured: human effort, recovery success, review quality. Output: `--out-dir` (default `benchmarks/attribution/results/`) receives `attribution-<UTC stamp>.json` and the matching `.md`. Publish failures, timeouts and exclusions with the report; do not drop rows. Do not claim superiority from overlapping intervals on a handful of runs; use the pilot to size a larger run with `--repeats` above 1. Tests: `python3 benchmarks/bench/tests/test_attribution.py` (no model call).
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
187
214
|
## What we do not have
|
|
188
215
|
|
|
189
216
|
Stating this plainly, because you will find it out anyway and it is cheaper for
|
|
@@ -133,6 +133,26 @@ Cap overrides (only meaningful when feature 8 is on):
|
|
|
133
133
|
Profile knobs: `LOKI_REPO_PROFILE_TTL_SECONDS` (86400),
|
|
134
134
|
`LOKI_REPO_PROFILE_MAX_CHARS` (2000).
|
|
135
135
|
|
|
136
|
+
## Repo commands and baseline (REPOMEM, same module)
|
|
137
|
+
|
|
138
|
+
`loki-ts/src/runner/repo_profile.ts` also owns the commands profile the engine10 path uses (`loki start`). The goal: the user never explains how the repo builds and tests, and a failure that was already there never blocks the run. It is not gated by `LOKI_REPO_PROFILE`; disable it with `LOKI_REPO_BASELINE=0`. Both profiles share one manifest hash (`manifestHash`: sha256 over name and content of package.json, requirements.txt, Makefile, pyproject.toml, tox.ini, pytest.ini, setup.cfg, go.mod, Cargo.toml and `.github/workflows/*.yml`).
|
|
139
|
+
|
|
140
|
+
Artifact: `.loki/state/repo-profile.json` (version 1, `CommandProfile`), written by engine10 intake.
|
|
141
|
+
|
|
142
|
+
- `revision`: HEAD the baseline ran on. `manifest_hash`: as above.
|
|
143
|
+
- `commands[]`: `{kind: test|lint|typecheck|build, cmd, source, validated, exit_code, duration_s, failing_ids}`. `validated` is true only when the command ran to completion on the untouched tree, false when it could not run (missing, timed out), null when discovered but not executed. Only the first test command is executed.
|
|
144
|
+
- `baseline`: `{ran, command, exit_code, duration_s, timed_out, failing_ids, attributable, reason?}`. `attributable` means exit 0, or non-zero with parseable failing ids (`failIds`).
|
|
145
|
+
|
|
146
|
+
Lifecycle:
|
|
147
|
+
|
|
148
|
+
1. Intake (after the dirty-tree refusal and the `.loki/` exclude, before any agent edit): same manifest hash and HEAD reuses the file; same hash and a new HEAD keeps the commands and re-runs only the baseline; a changed hash rediscovers and re-runs (`ensureProfile`).
|
|
149
|
+
2. Baseline: `sh -c <test cmd>` in the repo, 60s timeout (SIGKILL), scoped env (`plainTestEnv`, `PYTHONDONTWRITEBYTECODE=1`, `CI=1`). Untracked files the run created are removed so the diff stays the agent's.
|
|
150
|
+
3. Implementer brief (`briefContext`, e10ext/context.ts): a "Repo commands" block and the known-red test ids (`profileBrief`). Bytes depend only on the profile content (no timestamps).
|
|
151
|
+
4. Verify (`verifyStage`): `attribute()` marks a failing check pre-existing only when the profile revision equals the run base sha, the baseline was attributable, the check is neither a Wall/task-named target nor in a file the diff touched, and every one of its failing ids is in the baseline. Those join `pre_red` and `pre_red_checks`. Everything else that fails is introduced and blocks as before; a check with no parsed ids is introduced. Verify data gains `baseline_line` (`baseline: N pre-existing failing check(s) (not blocking), M introduced (blocking)`) and `introduced_checks`; seal lists the line under NOT PROVEN only when a baseline failure was excused.
|
|
152
|
+
5. Surface: `loki doctor --repo` (and `--repo --json`) prints the profile, read-only, from the current directory.
|
|
153
|
+
|
|
154
|
+
`e10ext/repomemory.ts` still holds the last verified command, flaky list and failure causes (dynamic per-repo cache data, not discovery).
|
|
155
|
+
|
|
136
156
|
## Acceptance
|
|
137
157
|
|
|
138
158
|
1. With all flags unset, `bun test tests/parity/` stays 60/60 byte-exact.
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Loki Mode, by [Autonomi](https://www.autonomi.dev/), turns a spec or an issue into a verified pull request with a receipt you can check. This page covers installing it, a first run, and the options around it.
|
|
4
4
|
|
|
5
|
-
**Version:** v11.
|
|
5
|
+
**Version:** v11.6.0
|
|
6
6
|
|
|
7
7
|
**Engine note:** `loki start` is the primary command. `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` all run the Loki 10 engine, whatever the flag order or input form (PRD file, issue ref, task or one-word brief). The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, or for one call when a flag Loki 10 cannot honor is present. See the [user guide](v10/GUIDE.md) and the [5-minute quickstart](QUICKSTART.md).
|
|
8
8
|
|
package/docs/KNOWN-ISSUES.md
CHANGED
|
@@ -4,4 +4,13 @@ Issues in the current release that are known and not yet fixed. Report new ones
|
|
|
4
4
|
|
|
5
5
|
## Which engine runs
|
|
6
6
|
|
|
7
|
-
From 11.4.0, `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine whatever the flag order or input form. The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, or for one call when a flag Loki 10 cannot honor is present (`--simple`, `--parallel`, `--sandbox`, ...). Check with `loki route start <args
|
|
7
|
+
From 11.4.0, `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine whatever the flag order or input form. The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, for a provider with no Loki 10 invoker, for a PRD over 100000 bytes, or for one call when a flag Loki 10 cannot honor is present (`--simple`, `--parallel`, `--sandbox`, ...). Check with `loki route start <args> --json`: it reports `engine`, `adapter`, `reason` and `capability_differences`. The full table is in the Routing contract section of `docs/exit-codes.md`.
|
|
8
|
+
|
|
9
|
+
## Exit codes
|
|
10
|
+
|
|
11
|
+
The exit code of every command is listed in `docs/exit-codes.md`. Two things are kept as they are for compatibility:
|
|
12
|
+
|
|
13
|
+
- `loki run` (deprecated alias of `loki start <issue-ref>`) exits 1 on an unknown option, not 64.
|
|
14
|
+
- `loki start` without `LOKI_DURABLE_STATE=1` has no finer run-outcome signal than 0 versus nonzero.
|
|
15
|
+
|
|
16
|
+
An empty diff has a distinct exit code only for `loki verify` (3). `loki quick`, `loki start` and `loki run` do not define one.
|