loki-mode 6.74.5 → 6.75.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/README.md +1 -53
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +990 -1
- package/autonomy/run.sh +106 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +12 -7
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +177 -0
- package/package.json +1 -1
- package/references/mcp-integration.md +59 -0
- package/skills/00-index.md +9 -0
- package/skills/documentation.md +123 -0
- package/skills/quality-gates.md +25 -1
- package/web-app/dist/assets/{AdminPage-Cwqm_kDg.js → AdminPage-D4QSV6Zi.js} +1 -1
- package/web-app/dist/assets/{Avatar-BgcFY2E5.js → Avatar-88MlpLO5.js} +1 -1
- package/web-app/dist/assets/{Badge-DeFGfZLB.js → Badge-DbGjLr4i.js} +1 -1
- package/web-app/dist/assets/{Button-Dg1EkPtN.js → Button-sp_FVGZj.js} +1 -1
- package/web-app/dist/assets/{ComparePage-D-wvMVP2.js → ComparePage-p2ENnfa7.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-B_Jm7CmJ.js → GitHubIssuesPanel-DBbBTG9w.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-B5i8Q99N.js → GitHubPRsPanel-Bi_yrcAE.js} +1 -1
- package/web-app/dist/assets/{HomePage-CUDTdntY.js → HomePage-BB83YPiX.js} +1 -1
- package/web-app/dist/assets/{LoginPage-BobwVXx5.js → LoginPage-BXUudCJ9.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-DmM--20B.js → MetricsPage-CX0Ahy-_.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-6_gjeogD.js → NotFoundPage-C4JqatEk.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-C3R2wbFt.js → ProjectPage-t5J2XAJT.js} +46 -46
- package/web-app/dist/assets/{ProjectsPage-DQr06iBk.js → ProjectsPage-Bzpz1clk.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-eROlGKB9.js → SettingsPage-y_yl8FvH.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-DEA5tT_R.js → ShowcasePage-B7d6pzMq.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-C_rIbgZg.js → SystemSettingsPage-C4tR33KU.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-DZGoYZnD.js → TeamsPage-DIOCfZIP.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-DVblWpbO.js → TemplatesPage-DlKyapXX.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-DnESY9zk.js → TerminalOutput-Czg-ZC2k.js} +1 -1
- package/web-app/dist/assets/{activity-COLsZyo1.js → activity-h1wU9a0L.js} +1 -1
- package/web-app/dist/assets/{bell-BjLe9xXk.js → bell-Bu8lsWOp.js} +1 -1
- package/web-app/dist/assets/{bot-Dz62aBIi.js → bot-rWO7KjkQ.js} +1 -1
- package/web-app/dist/assets/{check-BQPQjkH4.js → check-BWp8L5Cy.js} +1 -1
- package/web-app/dist/assets/{chevron-left-BVvOVUQ8.js → chevron-left-Bw4I1yGm.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DqoLW238.js → circle-alert-C37PKXiC.js} +1 -1
- package/web-app/dist/assets/{clock-Cn9fFUva.js → clock-DDScLol4.js} +1 -1
- package/web-app/dist/assets/{cloud-COJxbgUu.js → cloud-DaYKPLaM.js} +1 -1
- package/web-app/dist/assets/{copy-DOn0hVgy.js → copy-DKIRv0VK.js} +1 -1
- package/web-app/dist/assets/{database-Bj3Llvnk.js → database-CYZBHz51.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-BcmncygQ.js → dollar-sign-CydJu0kl.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BysxoSyu.js → file-code-corner-DqZ9gpdv.js} +1 -1
- package/web-app/dist/assets/{file-plus-Dwi1MqSS.js → file-plus-CzeFJWp3.js} +1 -1
- package/web-app/dist/assets/{folder-open-WzXNCq2x.js → folder-open-4YWk08dP.js} +1 -1
- package/web-app/dist/assets/{git-commit-horizontal-D85UUfNF.js → git-commit-horizontal-wbqFPNID.js} +1 -1
- package/web-app/dist/assets/{globe-CegT0VaL.js → globe-Cby-g5Yb.js} +1 -1
- package/web-app/dist/assets/{hammer-ZGKvu_xy.js → hammer-BNScgGdp.js} +1 -1
- package/web-app/dist/assets/{index-_2iPl2nX.js → index-6Z4B0I6r.js} +74 -74
- package/web-app/dist/assets/{layers-B40lki5j.js → layers-XfssQc5V.js} +1 -1
- package/web-app/dist/assets/{lightbulb-CFSoqUsV.js → lightbulb-EhnzRw7M.js} +1 -1
- package/web-app/dist/assets/{loader-circle-womi7Brk.js → loader-circle-BA0QIVGA.js} +1 -1
- package/web-app/dist/assets/{lock-CEWBb_SL.js → lock-BABtHe6K.js} +1 -1
- package/web-app/dist/assets/{mail-DimGrYf8.js → mail-Dokiey5S.js} +1 -1
- package/web-app/dist/assets/{package-DysIuuIJ.js → package-DbJyS1Ft.js} +1 -1
- package/web-app/dist/assets/{plus-DG1hW27_.js → plus-BcAN8Kaj.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-X31ig0S6.js → refresh-cw-B3dG1-Sb.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CBXooICo.js → rotate-ccw-Cs1Phctm.js} +1 -1
- package/web-app/dist/assets/{save-DQVrWTjZ.js → save-DsrNCZrP.js} +1 -1
- package/web-app/dist/assets/{server-BYAMALfa.js → server-CpN2GX4G.js} +1 -1
- package/web-app/dist/assets/{shield-alert-B3G7vLiW.js → shield-alert-CKJ1pzCz.js} +1 -1
- package/web-app/dist/assets/{trash-2-ChVunC-C.js → trash-2-C9vZqTqw.js} +1 -1
- package/web-app/dist/assets/{trending-down-DgsuOE2t.js → trending-down-BNLTrF5P.js} +1 -1
- package/web-app/dist/assets/{trending-up-CZHZsGeN.js → trending-up-DmFIdVOc.js} +1 -1
- package/web-app/dist/assets/{usePolling-ns_dFCVn.js → usePolling-vUlY-o6P.js} +1 -1
- package/web-app/dist/assets/{user-FQUrWHhF.js → user-Dh00W8De.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/web-app/server.py +213 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as e}from"./index-
|
|
1
|
+
import{r as e}from"./index-6Z4B0I6r.js";function m(s,u=2e3,a=!0){const[c,l]=e.useState(null),[f,o]=e.useState(null),[i,d]=e.useState(!0),r=e.useRef(!0),n=e.useCallback(async()=>{try{const t=await s();r.current&&(l(t),o(null))}catch(t){r.current&&o(t instanceof Error?t.message:"Unknown error")}finally{r.current&&d(!1)}},[s]);return e.useEffect(()=>{if(r.current=!0,!a)return;n();const t=setInterval(n,u);return()=>{r.current=!1,clearInterval(t)}},[n,u,a]),{data:c,error:f,loading:i,refresh:n}}export{m as u};
|
package/web-app/dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-6Z4B0I6r.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-CVM4A1Fw.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="bg-background text-ink font-sans antialiased">
|
package/web-app/server.py
CHANGED
|
@@ -2729,6 +2729,23 @@ async def start_session(req: StartRequest) -> JSONResponse:
|
|
|
2729
2729
|
"pid": proc.pid,
|
|
2730
2730
|
}})
|
|
2731
2731
|
|
|
2732
|
+
# Notify the Loki Dashboard (port 57374) about the active project so it
|
|
2733
|
+
# reads state files from the correct .loki/ directory (BUG-FOCUS-001).
|
|
2734
|
+
try:
|
|
2735
|
+
os.makedirs(os.path.join(project_dir, ".loki"), exist_ok=True)
|
|
2736
|
+
import urllib.request
|
|
2737
|
+
focus_data = json.dumps({"project_dir": project_dir}).encode()
|
|
2738
|
+
focus_req = urllib.request.Request(
|
|
2739
|
+
"http://127.0.0.1:57374/api/focus",
|
|
2740
|
+
data=focus_data,
|
|
2741
|
+
headers={"Content-Type": "application/json"},
|
|
2742
|
+
method="POST",
|
|
2743
|
+
)
|
|
2744
|
+
urllib.request.urlopen(focus_req, timeout=2)
|
|
2745
|
+
logger.info("Notified dashboard of project focus: %s", project_dir)
|
|
2746
|
+
except Exception:
|
|
2747
|
+
logger.debug("Could not notify dashboard (may not be running)")
|
|
2748
|
+
|
|
2732
2749
|
return JSONResponse(content={
|
|
2733
2750
|
"started": True,
|
|
2734
2751
|
"pid": proc.pid,
|
|
@@ -7023,6 +7040,14 @@ async def github_import_repo(session_id: str, req: GitHubImportRequest) -> JSONR
|
|
|
7023
7040
|
)
|
|
7024
7041
|
default_branch = branch_result.stdout.strip() if branch_result.returncode == 0 else req.branch
|
|
7025
7042
|
|
|
7043
|
+
# After successful clone, trigger doc generation in background
|
|
7044
|
+
try:
|
|
7045
|
+
loki_cli = _find_loki_cli()
|
|
7046
|
+
if loki_cli:
|
|
7047
|
+
asyncio.create_task(_run_doc_generation(str(target), loki_cli))
|
|
7048
|
+
except Exception:
|
|
7049
|
+
logger.debug("Auto-doc generation skipped")
|
|
7050
|
+
|
|
7026
7051
|
return JSONResponse(content={
|
|
7027
7052
|
"success": True,
|
|
7028
7053
|
"files_count": files_count,
|
|
@@ -8305,5 +8330,193 @@ async def github_actions_cancel_run(session_id: str, run_id: int) -> JSONRespons
|
|
|
8305
8330
|
return JSONResponse(status_code=500, content={"error": f"Failed to cancel run: {exc}"})
|
|
8306
8331
|
|
|
8307
8332
|
|
|
8333
|
+
# ---------------------------------------------------------------------------
|
|
8334
|
+
# Documentation generation endpoints
|
|
8335
|
+
# ---------------------------------------------------------------------------
|
|
8336
|
+
|
|
8337
|
+
|
|
8338
|
+
async def _run_doc_generation(project_dir: str, loki_cli: str) -> None:
|
|
8339
|
+
"""Run loki docs generate in background after repo import."""
|
|
8340
|
+
loop = asyncio.get_running_loop()
|
|
8341
|
+
try:
|
|
8342
|
+
await loop.run_in_executor(None, lambda: subprocess.run(
|
|
8343
|
+
[loki_cli, "docs", "generate"],
|
|
8344
|
+
cwd=project_dir,
|
|
8345
|
+
capture_output=True,
|
|
8346
|
+
timeout=300,
|
|
8347
|
+
))
|
|
8348
|
+
logger.info("Auto-generated documentation for %s", project_dir)
|
|
8349
|
+
except Exception as e:
|
|
8350
|
+
logger.warning("Doc generation failed: %s", e)
|
|
8351
|
+
|
|
8352
|
+
|
|
8353
|
+
@app.post("/api/sessions/{session_id}/docs/generate")
|
|
8354
|
+
async def docs_generate(session_id: str) -> JSONResponse:
|
|
8355
|
+
"""Trigger loki docs generate in the session's project directory."""
|
|
8356
|
+
target, err = _validate_session_and_find_dir(session_id)
|
|
8357
|
+
if err:
|
|
8358
|
+
return err
|
|
8359
|
+
|
|
8360
|
+
_cleanup_chat_tasks()
|
|
8361
|
+
task = ChatTask()
|
|
8362
|
+
_chat_tasks[task.id] = task
|
|
8363
|
+
|
|
8364
|
+
async def run_docs() -> None:
|
|
8365
|
+
loki = _find_loki_cli()
|
|
8366
|
+
if loki is None:
|
|
8367
|
+
task.output_lines = ["loki CLI not found"]
|
|
8368
|
+
task.returncode = 1
|
|
8369
|
+
task.complete = True
|
|
8370
|
+
return
|
|
8371
|
+
proc: Optional[subprocess.Popen] = None
|
|
8372
|
+
try:
|
|
8373
|
+
proc = subprocess.Popen(
|
|
8374
|
+
[loki, "docs", "generate"],
|
|
8375
|
+
stdout=subprocess.PIPE,
|
|
8376
|
+
stderr=subprocess.STDOUT,
|
|
8377
|
+
stdin=subprocess.DEVNULL,
|
|
8378
|
+
text=True,
|
|
8379
|
+
cwd=str(target),
|
|
8380
|
+
start_new_session=True,
|
|
8381
|
+
)
|
|
8382
|
+
task.process = proc
|
|
8383
|
+
_track_child_pid(proc.pid)
|
|
8384
|
+
loop = asyncio.get_running_loop()
|
|
8385
|
+
|
|
8386
|
+
def _read() -> None:
|
|
8387
|
+
assert proc.stdout is not None
|
|
8388
|
+
for raw_line in proc.stdout:
|
|
8389
|
+
if task.cancelled:
|
|
8390
|
+
break
|
|
8391
|
+
clean = re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', raw_line.rstrip("\n"))
|
|
8392
|
+
stripped = clean.strip()
|
|
8393
|
+
if not stripped:
|
|
8394
|
+
continue
|
|
8395
|
+
task.output_lines.append(stripped)
|
|
8396
|
+
proc.wait()
|
|
8397
|
+
|
|
8398
|
+
await loop.run_in_executor(None, _read)
|
|
8399
|
+
task.returncode = proc.returncode or 0
|
|
8400
|
+
except Exception as exc:
|
|
8401
|
+
task.output_lines.append(f"Doc generation failed: {exc}")
|
|
8402
|
+
task.returncode = 1
|
|
8403
|
+
finally:
|
|
8404
|
+
task.complete = True
|
|
8405
|
+
if proc and proc.pid:
|
|
8406
|
+
_untrack_child_pid(proc.pid)
|
|
8407
|
+
|
|
8408
|
+
asyncio.create_task(run_docs())
|
|
8409
|
+
return JSONResponse(content={"task_id": task.id, "message": "Documentation generation started"})
|
|
8410
|
+
|
|
8411
|
+
|
|
8412
|
+
@app.get("/api/sessions/{session_id}/docs/status")
|
|
8413
|
+
async def docs_status(session_id: str) -> JSONResponse:
|
|
8414
|
+
"""Read .loki/docs/docs-manifest.json from the project directory."""
|
|
8415
|
+
target, err = _validate_session_and_find_dir(session_id)
|
|
8416
|
+
if err:
|
|
8417
|
+
return err
|
|
8418
|
+
|
|
8419
|
+
docs_dir = target / ".loki" / "docs"
|
|
8420
|
+
manifest_path = docs_dir / "docs-manifest.json"
|
|
8421
|
+
|
|
8422
|
+
if not manifest_path.exists():
|
|
8423
|
+
return JSONResponse(content={
|
|
8424
|
+
"has_docs": False,
|
|
8425
|
+
"coverage_pct": 0,
|
|
8426
|
+
"doc_files": [],
|
|
8427
|
+
"last_generated": None,
|
|
8428
|
+
"commits_behind": 0,
|
|
8429
|
+
})
|
|
8430
|
+
|
|
8431
|
+
try:
|
|
8432
|
+
manifest = json.loads(manifest_path.read_text())
|
|
8433
|
+
except (json.JSONDecodeError, OSError):
|
|
8434
|
+
return JSONResponse(content={
|
|
8435
|
+
"has_docs": False,
|
|
8436
|
+
"coverage_pct": 0,
|
|
8437
|
+
"doc_files": [],
|
|
8438
|
+
"last_generated": None,
|
|
8439
|
+
"commits_behind": 0,
|
|
8440
|
+
})
|
|
8441
|
+
|
|
8442
|
+
# List doc files
|
|
8443
|
+
doc_files = []
|
|
8444
|
+
if docs_dir.exists():
|
|
8445
|
+
doc_files = [
|
|
8446
|
+
f.name for f in docs_dir.iterdir()
|
|
8447
|
+
if f.is_file() and f.suffix in (".md", ".txt", ".html") and f.name != "docs-manifest.json"
|
|
8448
|
+
]
|
|
8449
|
+
|
|
8450
|
+
# Count commits behind (compare HEAD to last_generated commit if available)
|
|
8451
|
+
commits_behind = 0
|
|
8452
|
+
last_commit = manifest.get("last_commit")
|
|
8453
|
+
if last_commit:
|
|
8454
|
+
try:
|
|
8455
|
+
result = subprocess.run(
|
|
8456
|
+
["git", "rev-list", "--count", f"{last_commit}..HEAD"],
|
|
8457
|
+
cwd=str(target),
|
|
8458
|
+
capture_output=True, text=True, timeout=10,
|
|
8459
|
+
)
|
|
8460
|
+
if result.returncode == 0:
|
|
8461
|
+
commits_behind = int(result.stdout.strip())
|
|
8462
|
+
except (subprocess.TimeoutExpired, ValueError, OSError):
|
|
8463
|
+
pass
|
|
8464
|
+
|
|
8465
|
+
return JSONResponse(content={
|
|
8466
|
+
"has_docs": True,
|
|
8467
|
+
"coverage_pct": manifest.get("coverage_pct", 0),
|
|
8468
|
+
"doc_files": doc_files,
|
|
8469
|
+
"last_generated": manifest.get("last_generated"),
|
|
8470
|
+
"commits_behind": commits_behind,
|
|
8471
|
+
})
|
|
8472
|
+
|
|
8473
|
+
|
|
8474
|
+
@app.get("/api/sessions/{session_id}/docs/files")
|
|
8475
|
+
async def docs_list_files(session_id: str) -> JSONResponse:
|
|
8476
|
+
"""List all generated doc files in .loki/docs/."""
|
|
8477
|
+
target, err = _validate_session_and_find_dir(session_id)
|
|
8478
|
+
if err:
|
|
8479
|
+
return err
|
|
8480
|
+
|
|
8481
|
+
docs_dir = target / ".loki" / "docs"
|
|
8482
|
+
if not docs_dir.exists():
|
|
8483
|
+
return JSONResponse(content={"files": []})
|
|
8484
|
+
|
|
8485
|
+
files = []
|
|
8486
|
+
for f in sorted(docs_dir.iterdir()):
|
|
8487
|
+
if f.is_file() and f.name != "docs-manifest.json":
|
|
8488
|
+
stat = f.stat()
|
|
8489
|
+
files.append({
|
|
8490
|
+
"name": f.name,
|
|
8491
|
+
"size": stat.st_size,
|
|
8492
|
+
"generated_at": datetime.fromtimestamp(stat.st_mtime).isoformat(),
|
|
8493
|
+
})
|
|
8494
|
+
|
|
8495
|
+
return JSONResponse(content={"files": files})
|
|
8496
|
+
|
|
8497
|
+
|
|
8498
|
+
@app.get("/api/sessions/{session_id}/docs/files/{filename}")
|
|
8499
|
+
async def docs_get_file(session_id: str, filename: str) -> Response:
|
|
8500
|
+
"""Return the content of a specific generated doc file."""
|
|
8501
|
+
target, err = _validate_session_and_find_dir(session_id)
|
|
8502
|
+
if err:
|
|
8503
|
+
return err
|
|
8504
|
+
|
|
8505
|
+
# Sanitize filename to prevent directory traversal
|
|
8506
|
+
if "/" in filename or "\\" in filename or ".." in filename:
|
|
8507
|
+
return JSONResponse(status_code=400, content={"error": "Invalid filename"})
|
|
8508
|
+
|
|
8509
|
+
doc_path = target / ".loki" / "docs" / filename
|
|
8510
|
+
if not doc_path.exists() or not doc_path.is_file():
|
|
8511
|
+
return JSONResponse(status_code=404, content={"error": "Doc file not found"})
|
|
8512
|
+
|
|
8513
|
+
try:
|
|
8514
|
+
content = doc_path.read_text()
|
|
8515
|
+
except OSError:
|
|
8516
|
+
return JSONResponse(status_code=500, content={"error": "Cannot read doc file"})
|
|
8517
|
+
|
|
8518
|
+
return Response(content=content, media_type="text/markdown")
|
|
8519
|
+
|
|
8520
|
+
|
|
8308
8521
|
if __name__ == "__main__":
|
|
8309
8522
|
main()
|