loki-mode 11.78.0 → 11.79.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.
Files changed (83) hide show
  1. package/SKILL.md +3 -3
  2. package/VERSION +1 -1
  3. package/autonomy/lib/cockpit-render.sh +1 -1
  4. package/autonomy/lib/proof-generator.py +48 -0
  5. package/autonomy/lib/proof-pr.sh +13 -0
  6. package/autonomy/lib/telemetry-schema.json +4 -0
  7. package/autonomy/loki +259 -11
  8. package/autonomy/run.sh +126 -8
  9. package/autonomy/telemetry.sh +13 -0
  10. package/autonomy/verify.sh +2 -2
  11. package/bin/loki +17 -12
  12. package/completions/_loki +15 -0
  13. package/completions/loki.bash +15 -1
  14. package/dashboard/__init__.py +1 -1
  15. package/docs/CLI-INVENTORY.md +4 -3
  16. package/docs/CLI-REFERENCE.md +2 -1
  17. package/docs/INSTALLATION.md +1 -1
  18. package/docs/PRIVACY.md +1 -0
  19. package/docs/dev/architecture-reference.md +1 -0
  20. package/loki-ts/data/child-env.json +1 -0
  21. package/loki-ts/dist/loki.js +1334 -1324
  22. package/mcp/__init__.py +1 -1
  23. package/package.json +2 -2
  24. package/packages/control-plane/dist/server.js +1453 -1267
  25. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  26. package/skills/quality-gates.md +6 -0
  27. package/web-app/dist/assets/{AdminPage-BPF2Xehf.js → AdminPage-Bph4VGNm.js} +1 -1
  28. package/web-app/dist/assets/{Avatar-CSW9BhBU.js → Avatar-Btrppoor.js} +1 -1
  29. package/web-app/dist/assets/{Badge-wgm79wim.js → Badge-fJFe3CEH.js} +1 -1
  30. package/web-app/dist/assets/{Button-BcIAcPKr.js → Button-B_cYY4Me.js} +1 -1
  31. package/web-app/dist/assets/{CockpitPage-BFi-0Kwj.js → CockpitPage-26oBcuYy.js} +1 -1
  32. package/web-app/dist/assets/{ComparePage-B7Wibrfn.js → ComparePage-CSuQZUm2.js} +1 -1
  33. package/web-app/dist/assets/{ErrorBoundary-BYyOVh2c.js → ErrorBoundary-Cf7DQMz0.js} +1 -1
  34. package/web-app/dist/assets/{EvidenceReceiptPanel-C9QcrjNl.js → EvidenceReceiptPanel-BMZszYBV.js} +1 -1
  35. package/web-app/dist/assets/{GitHubIssuesPanel-CWbuBxVZ.js → GitHubIssuesPanel-Bt5dkJwW.js} +1 -1
  36. package/web-app/dist/assets/{GitHubPRsPanel-qcBHK615.js → GitHubPRsPanel-CL4NbWJ3.js} +1 -1
  37. package/web-app/dist/assets/{HomePage-B1mYFU4t.js → HomePage-Bqugxf45.js} +2 -2
  38. package/web-app/dist/assets/{LoginPage-CmIO1xL7.js → LoginPage-ENM9QyG8.js} +1 -1
  39. package/web-app/dist/assets/{MagicPage-L7UP4qKP.js → MagicPage-D-7g-mew.js} +1 -1
  40. package/web-app/dist/assets/{MetricsPage-DVPCjN1U.js → MetricsPage-CelqK13G.js} +1 -1
  41. package/web-app/dist/assets/{NotFoundPage-D3XZmCH5.js → NotFoundPage-EQ3Aq4Xg.js} +1 -1
  42. package/web-app/dist/assets/{ProjectPage-BlaMzlVi.js → ProjectPage-D3Jls_Oh.js} +3 -3
  43. package/web-app/dist/assets/{ProjectsPage-DN7W28Zq.js → ProjectsPage-CgGRkJ3X.js} +1 -1
  44. package/web-app/dist/assets/{SettingsPage-2u9ODYB5.js → SettingsPage-B79K8xg2.js} +1 -1
  45. package/web-app/dist/assets/{ShowcasePage-CoKuTfab.js → ShowcasePage-DxSsaGGx.js} +1 -1
  46. package/web-app/dist/assets/{SystemSettingsPage-Crhj2XAU.js → SystemSettingsPage-CYNKB-5I.js} +1 -1
  47. package/web-app/dist/assets/{TeamsPage-DehMgnKh.js → TeamsPage-Bit01iEC.js} +1 -1
  48. package/web-app/dist/assets/{TemplatesPage-DGtwfZgU.js → TemplatesPage-DQ3fPi7x.js} +1 -1
  49. package/web-app/dist/assets/{TerminalOutput-7HZRtXoC.js → TerminalOutput-By137hB-.js} +1 -1
  50. package/web-app/dist/assets/{activity-DAp5wswo.js → activity-rbRWrIjS.js} +1 -1
  51. package/web-app/dist/assets/{bell-Ncvnrz7d.js → bell-DIiSalJU.js} +1 -1
  52. package/web-app/dist/assets/{bot-BWob3K3E.js → bot-BQyrbJaf.js} +1 -1
  53. package/web-app/dist/assets/{check-DReWA2vm.js → check-BJ_PqlU2.js} +1 -1
  54. package/web-app/dist/assets/{chevron-left-CBJpzVU1.js → chevron-left-_QsMbFBT.js} +1 -1
  55. package/web-app/dist/assets/{circle-alert-2c_vs5PW.js → circle-alert-DEovFrER.js} +1 -1
  56. package/web-app/dist/assets/{clock-BrS_ZKKi.js → clock-Dmrl0gvN.js} +1 -1
  57. package/web-app/dist/assets/{cloud-Czvuo51R.js → cloud-Bs2a7FCR.js} +1 -1
  58. package/web-app/dist/assets/{code-xml-BWEqJ2I8.js → code-xml-D2WdclgQ.js} +1 -1
  59. package/web-app/dist/assets/{database-CJ6M-oK-.js → database-kbYbPMS5.js} +1 -1
  60. package/web-app/dist/assets/{dollar-sign-DSObKmVv.js → dollar-sign-BoOAvw6B.js} +1 -1
  61. package/web-app/dist/assets/{file-code-corner-CVEW-gF5.js → file-code-corner-ew5NSFtR.js} +1 -1
  62. package/web-app/dist/assets/{file-plus-CAK0I0tE.js → file-plus-CNV2nXNx.js} +1 -1
  63. package/web-app/dist/assets/{globe-DeUgcsSZ.js → globe-CZo7LjYX.js} +1 -1
  64. package/web-app/dist/assets/{hammer-B74PyjS0.js → hammer-BedES-IA.js} +1 -1
  65. package/web-app/dist/assets/{index-DW3_HRqp.js → index-C_uRanCX.js} +3 -3
  66. package/web-app/dist/assets/{layers-Dw7kaGyJ.js → layers-B-OfB6O0.js} +1 -1
  67. package/web-app/dist/assets/{loader-circle-B6xzlAjw.js → loader-circle-C8hx3EE8.js} +1 -1
  68. package/web-app/dist/assets/{lock-5ncCPzIG.js → lock-Cyi_Tc3f.js} +1 -1
  69. package/web-app/dist/assets/{package-x59_dTx0.js → package-Cfps-Gdr.js} +1 -1
  70. package/web-app/dist/assets/{plus-BcVBzjpS.js → plus-CjSNRaN2.js} +1 -1
  71. package/web-app/dist/assets/{refresh-cw-CNrJhks6.js → refresh-cw-pEhkaiQh.js} +1 -1
  72. package/web-app/dist/assets/{rotate-ccw-BpmS_ezI.js → rotate-ccw-twl6HuT5.js} +1 -1
  73. package/web-app/dist/assets/{scroll-text-CwBYNyoj.js → scroll-text-B9XWtVcd.js} +1 -1
  74. package/web-app/dist/assets/{server-C4hnxkk6.js → server-DeBbwC8T.js} +1 -1
  75. package/web-app/dist/assets/{shield-alert-CSVUyWCM.js → shield-alert-BGQQ_YfF.js} +1 -1
  76. package/web-app/dist/assets/{thumbs-up-DEU9fmDX.js → thumbs-up-Dy9nEa04.js} +1 -1
  77. package/web-app/dist/assets/{trash-2-DJKMevAm.js → trash-2-DIftKB8Y.js} +1 -1
  78. package/web-app/dist/assets/{trending-down-Dq3wAM59.js → trending-down-Cjh8EsVU.js} +1 -1
  79. package/web-app/dist/assets/{trending-up-DribptN4.js → trending-up-xWyMrayW.js} +1 -1
  80. package/web-app/dist/assets/{upload-DUlZxsH1.js → upload-Bh3moncl.js} +1 -1
  81. package/web-app/dist/assets/{usePolling-PecqVZ0k.js → usePolling-C2sF90ff.js} +1 -1
  82. package/web-app/dist/assets/{user-BcLYLDVU.js → user-CtBrnwfC.js} +1 -1
  83. package/web-app/dist/index.html +1 -1
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Runs an autonomous software factory that knows what it is supposed to deliver and proves it did, with a signed receipt. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
4
4
  ---
5
5
 
6
- # Loki Mode v11.78.0
6
+ # Loki Mode v11.79.0
7
7
 
8
8
  **Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the previous bash loop. PRD-file starts (`loki start ./prd.md`) and the opencode provider still run on it until FC38-SWEEP ports them; the legacy-engine command and the LOKI_ENGINE switch remain removed. Prefer `loki owner/repo#N` for issues.
9
9
 
@@ -227,7 +227,7 @@ Pick modules from this table, one hop from here. Each module over 100 lines open
227
227
  <!-- generated:commands -->
228
228
  Commands (generated from `loki-ts/src/cli/registry.ts`; full reference in `docs/CLI-REFERENCE.md`):
229
229
 
230
- `version`, `status`, `doctor`, `provider`, `memory`, `rollback`, `backup`, `restore`, `proof`, `wiki`, `control`, `runs`, `intent`, `kpis`, `report`, `trust`, `crash`, `contract`, `start`, `slack`, `queue`, `issues`, `undo`, `verify-pr`, `answer`, `engine10`, `help`, `chat`, `edit`, `diff`, `models`, `setup`, `quick`, `quickstart`, `init`, `template`, `verify`, `keys`, `review`, `dashboard`, `config`, `mcp`, `acp`, `stop`, `pause`, `resume`, `cancel`, `why`, `next`, `logs`, `ship`, `deploy`, `import`, `github`, `issue`, `ci`, `modernize`, `share`, `assets`, `export`, `notify`, `tour`, `wow`, `factory`, `welcome`, `onboard`, `setup-skill`, `self-update`, `remote`, `cockpit`, `code`, `context`, `secrets`, `api`, `sandbox`, `docker`, `web`, `preview`, `telemetry`, `syslog`, `explain`, `docs`, `test`, `bench`, `voice`, `own`, `secure`, `compliance`, `enterprise`, `projects`, `audit`, `cost`, `metrics`, `sentrux`, `magic`, `steer`
230
+ `version`, `status`, `doctor`, `provider`, `memory`, `rollback`, `backup`, `restore`, `proof`, `wiki`, `control`, `runs`, `intent`, `kpis`, `report`, `trust`, `crash`, `contract`, `start`, `slack`, `queue`, `issues`, `undo`, `verify-pr`, `answer`, `engine10`, `help`, `chat`, `edit`, `diff`, `models`, `setup`, `quick`, `quickstart`, `init`, `template`, `verify`, `keys`, `review`, `dashboard`, `config`, `mcp`, `acp`, `stop`, `pause`, `resume`, `cancel`, `why`, `next`, `logs`, `ship`, `deploy`, `import`, `github`, `issue`, `ci`, `modernize`, `share`, `assets`, `export`, `notify`, `tour`, `wow`, `try`, `factory`, `welcome`, `onboard`, `setup-skill`, `self-update`, `remote`, `cockpit`, `code`, `context`, `secrets`, `api`, `sandbox`, `docker`, `web`, `preview`, `telemetry`, `syslog`, `explain`, `docs`, `test`, `bench`, `voice`, `own`, `secure`, `compliance`, `enterprise`, `projects`, `audit`, `cost`, `metrics`, `sentrux`, `magic`, `steer`
231
231
  <!-- /generated -->
232
232
 
233
233
  ---
@@ -475,4 +475,4 @@ Implemented, planned and deprecated items, with the version each landed in: `ski
475
475
 
476
476
  ---
477
477
 
478
- **v11.78.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
478
+ **v11.79.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 11.78.0
1
+ 11.79.0
@@ -235,7 +235,7 @@ cockpit_render() {
235
235
  [ "$no_image" = "1" ] && args+=(--no-image)
236
236
 
237
237
  # Gather -> render. Preserve the TS exit code (0 image / 3 fallback).
238
- cockpit_gather_state "$skill_dir" "$focus_repo" | bun "$entry" "${args[@]}"
238
+ cockpit_gather_state "$skill_dir" "$focus_repo" | bun --no-env-file --config=/dev/null "$entry" "${args[@]}"
239
239
  }
240
240
 
241
241
  #===============================================================================
@@ -361,6 +361,51 @@ def _test_results_fresh(quality_dir):
361
361
  return False
362
362
 
363
363
 
364
+ def _collect_advisory_review(loki_dir):
365
+ """Advisory reviewers' Critical/High counts from the latest code review.
366
+
367
+ architecture-strategist, maintainer-mergeability and test-coverage-auditor
368
+ run but hold no veto, so their findings must not vanish: they are recorded
369
+ here (receipt) and rendered in the PR report. DISPLAY ONLY: this never feeds
370
+ the headline. Returns None when no aggregate carries the advisory fields
371
+ (older run, or no review), so existing receipts are byte-identical.
372
+ """
373
+ reviews_dir = os.path.join(loki_dir, "quality", "reviews")
374
+ try:
375
+ ids = [d for d in os.listdir(reviews_dir)
376
+ if os.path.isfile(os.path.join(reviews_dir, d, "aggregate.json"))]
377
+ except OSError:
378
+ return None
379
+ if not ids:
380
+ return None
381
+ latest = max(ids, key=lambda d: (
382
+ os.path.getmtime(os.path.join(reviews_dir, d, "aggregate.json")), d))
383
+ agg = _read_json(os.path.join(reviews_dir, latest, "aggregate.json"), default=None)
384
+ if not isinstance(agg, dict) or "advisory_critical" not in agg:
385
+ return None
386
+ rows = agg.get("reviewers")
387
+ rows = rows if isinstance(rows, list) else []
388
+ reviewers = [
389
+ {"name": str(r.get("name") or ""),
390
+ "critical": _as_int(r.get("critical")), "high": _as_int(r.get("high"))}
391
+ for r in rows if isinstance(r, dict) and r.get("advisory") is True
392
+ ]
393
+ return {
394
+ "review_id": str(agg.get("review_id") or latest),
395
+ "critical": _as_int(agg.get("advisory_critical")),
396
+ "high": _as_int(agg.get("advisory_high")),
397
+ "reviewers": reviewers,
398
+ "veto": False,
399
+ }
400
+
401
+
402
+ def _as_int(v):
403
+ try:
404
+ return int(v)
405
+ except Exception:
406
+ return 0
407
+
408
+
364
409
  def _collect_quality_gates(loki_dir):
365
410
  gates_raw = _read_json(
366
411
  os.path.join(loki_dir, "state", "quality-gates.json"), default=None
@@ -523,7 +568,10 @@ def _collect_quality_gates(loki_dir):
523
568
  if name.startswith("LOKI_PHASE_")
524
569
  and str(value).strip().lower() in ("false", "0", "no", "off")
525
570
  )
571
+ _adv_review = _collect_advisory_review(loki_dir)
572
+ _adv_extra = {"advisory_review": _adv_review} if _adv_review else {}
526
573
  return {
574
+ **_adv_extra,
527
575
  "passed": passed,
528
576
  "total": total,
529
577
  "gates": gates,
@@ -235,6 +235,19 @@ def main():
235
235
  _off = [str(x) for x in _off] if isinstance(_off, list) else []
236
236
  if _off:
237
237
  _line("| Gates disabled | " + ", ".join(sorted(_off)) + " |")
238
+ # Advisory reviewers run without a veto; their Critical/High findings are
239
+ # shown here so a removed veto is never a hidden one. Display only: this row
240
+ # never feeds the headline. Emitted only when something was recorded.
241
+ _adv = _qg.get("advisory_review")
242
+ if isinstance(_adv, dict):
243
+ try:
244
+ _ac = int(_adv.get("critical") or 0)
245
+ _ah = int(_adv.get("high") or 0)
246
+ except Exception:
247
+ _ac = _ah = 0
248
+ if _ac + _ah > 0:
249
+ _line("| Advisory review findings | Critical " + str(_ac) + ", High "
250
+ + str(_ah) + " (recorded, no veto; see the review aggregate) |")
238
251
  _line("| Base sha | `" + (base_sha or "(none)") + "` |")
239
252
  _line("| Head sha | `" + (head_sha or "(none)") + "` |")
240
253
 
@@ -36,6 +36,10 @@
36
36
  "first_run_blocked": {
37
37
  "blocker": {"type": "enum", "values": ["no_provider", "not_logged_in", "not_a_repo", "git_identity", "node", "python3", "jq", "git", "curl", "disk", "skill_symlink", "other"]}
38
38
  },
39
+ "seal_hook_decision": {
40
+ "decision": {"type": "enum", "values": ["allow", "block"]},
41
+ "reason": {"type": "enum", "values": ["none", "tests_failed", "tests_weakened", "no_tests", "contract_unverified", "released", "error"]}
42
+ },
39
43
  "build_verified": {
40
44
  "headline": {"type": "enum", "values": ["VERIFIED", "VERIFIED WITH GAPS", "NOT VERIFIED"]},
41
45
  "route": {"type": "enum", "values": ["engine10"]},
package/autonomy/loki CHANGED
@@ -1864,6 +1864,7 @@ show_help_all() {
1864
1864
  echo " workspace list | infer <name> | run <name> <ref> Multi-repo runs from loki.yaml workspaces (LOKI_WORKSPACES=0 disables)"
1865
1865
  echo " start --workspace <name> \"<brief>\" Run a workspace; start --deploy [--health-url U] deploys after a VERIFIED run"
1866
1866
  echo " quickstart [idea] Guided first build: setup, idea, template, plan, go"
1867
+ echo " try <repo-url> Try Loki on a public repo in a temp clone (--dry-run previews); only the receipt is kept"
1867
1868
  echo ""
1868
1869
  echo "Session:"
1869
1870
  echo " status [--json|--badge|--probe] Show current status (--json machine-readable, --badge deploy badge, --probe re-check deploy health)"
@@ -2023,7 +2024,7 @@ show_help_all() {
2023
2024
  echo " projects proof (receipt) provider quick quickstart rc remote report reset"
2024
2025
  echo " merge restore resume review rollback run runs sandbox secrets secure self-update sentrux"
2025
2026
  echo " serve setup setup-skill share ship spec start state stats status steer stop"
2026
- echo " syslog telemetry template test tour trigger trust trust-metrics"
2027
+ echo " syslog telemetry template test tour trigger trust trust-metrics try"
2027
2028
  echo " ultracode undo update verify version voice watch watchdog web welcome why"
2028
2029
  echo " wiki workspace worktree (wt) wow"
2029
2030
  echo " control"
@@ -2186,7 +2187,7 @@ _loki_control_ui() {
2186
2187
  local cli="$SKILL_DIR/loki-ts/dist/loki.js" cdir="${HOME}/.loki/control" spid
2187
2188
  [ -f "$cli" ] || cli="$SKILL_DIR/loki-ts/src/cli.ts"
2188
2189
  mkdir -p "$cdir" && chmod 700 "$cdir" 2>/dev/null || true
2189
- LOKI_CONTROL=1 LOKI_NO_BROWSER=1 nohup bun "$cli" control serve >"$cdir/serve.log" 2>&1 </dev/null &
2190
+ LOKI_CONTROL=1 LOKI_NO_BROWSER=1 nohup bun --no-env-file --config=/dev/null "$cli" control serve >"$cdir/serve.log" 2>&1 </dev/null &
2190
2191
  spid=$!
2191
2192
  echo "$spid" > "$cdir/serve.pid" 2>/dev/null || true
2192
2193
  for i in $(seq 1 100); do
@@ -15941,9 +15942,9 @@ _loki_bun_delegate() {
15941
15942
 
15942
15943
  t="$(command -v timeout 2>/dev/null || command -v gtimeout 2>/dev/null || true)"
15943
15944
  if [ -n "$t" ]; then
15944
- "$t" -k 1 5 "$bun_bin" "$cli" version >/dev/null 2>&1
15945
+ "$t" -k 1 5 "$bun_bin" --no-env-file --config=/dev/null "$cli" version >/dev/null 2>&1
15945
15946
  else
15946
- "$bun_bin" "$cli" version >/dev/null 2>&1
15947
+ "$bun_bin" --no-env-file --config=/dev/null "$cli" version >/dev/null 2>&1
15947
15948
  fi
15948
15949
  rc=$?
15949
15950
  if [ "$rc" -ne 0 ]; then
@@ -15952,7 +15953,7 @@ _loki_bun_delegate() {
15952
15953
  return 1
15953
15954
  fi
15954
15955
 
15955
- exec "$bun_bin" "$cli" "$cmd" "$@"
15956
+ exec "$bun_bin" --no-env-file --config=/dev/null "$cli" "$cmd" "$@"
15956
15957
  }
15957
15958
 
15958
15959
  # One-line fix for a requirement id (a provider name is an id), read from the shared
@@ -17185,13 +17186,20 @@ PY
17185
17186
  return 0
17186
17187
  }
17187
17188
 
17188
- # Removes only a directory this command created: a direct child of the temp root named loki-wow.*.
17189
+ # Removes only a directory the caller created: a direct child of the temp root named
17190
+ # <prefix>.* (default loki-wow), not a symlink. With a marker file name as $3, the directory
17191
+ # must also hold that file (not a symlink) whose first line equals the directory's own path.
17189
17192
  wow_cleanup_dir() {
17190
- local d="${1:-}" root
17193
+ local d="${1:-}" prefix="${2:-loki-wow}" marker="${3:-}" root marker_value=""
17191
17194
  [ -n "$d" ] && [ -d "$d" ] && [ ! -L "$d" ] || return 0
17192
17195
  root="$(cd "${TMPDIR:-/tmp}" 2>/dev/null && pwd -P)" || return 0
17193
- case "$d" in "$root"/loki-wow.*) ;; *) return 0 ;; esac
17196
+ case "$d" in "$root/$prefix".*) ;; *) return 0 ;; esac
17194
17197
  [ "$(dirname -- "$d")" = "$root" ] || return 0
17198
+ if [ -n "$marker" ]; then
17199
+ [ -f "$d/$marker" ] && [ ! -L "$d/$marker" ] || return 0
17200
+ IFS= read -r marker_value <"$d/$marker" || true
17201
+ [ "$marker_value" = "$d" ] || return 0
17202
+ fi
17195
17203
  rm -rf -- "$d"
17196
17204
  }
17197
17205
 
@@ -23024,6 +23032,243 @@ for r in reqs:
23024
23032
  "
23025
23033
  }
23026
23034
 
23035
+ # loki try: clone a public repo into a run-owned temp dir, pick a starter task, run it
23036
+ # there, keep only the receipt. The caller's checkout is never touched. The temp helpers
23037
+ # below follow the loki_run_tmp_create/cleanup contract (the repo's own copies live under
23038
+ # eval/, which the npm package does not ship).
23039
+ try_tmp_create() {
23040
+ local temp_root d
23041
+ temp_root="$(cd "${TMPDIR:-/tmp}" 2>/dev/null && pwd -P)" || return 1
23042
+ d="$(mktemp -d "${temp_root}/loki-run.XXXXXXXX")" || return 1
23043
+ chmod 700 "$d" || { rmdir "$d" 2>/dev/null; return 1; }
23044
+ if ! printf '%s\n' "$d" >"$d/.loki-run-owned"; then rm -rf -- "$d"; return 1; fi
23045
+ printf '%s\n' "$d"
23046
+ }
23047
+
23048
+ # Removes only a directory try_tmp_create made (loki-run.* child of the temp root whose
23049
+ # .loki-run-owned marker holds its own path); the checks live in wow_cleanup_dir.
23050
+ try_tmp_cleanup() {
23051
+ wow_cleanup_dir "${1:-}" loki-run .loki-run-owned
23052
+ }
23053
+
23054
+ # Prints the starter task for the clone at $1 (source URL $2): the first open GitHub
23055
+ # issue title when gh can read it, else the first TODO line. Prints nothing when neither exists.
23056
+ try_pick_task() {
23057
+ local dest="$1" url="$2" slug title first p l t todo_file
23058
+ case "$url" in
23059
+ https://github.com/*)
23060
+ slug="${url#https://github.com/}"
23061
+ slug="${slug%.git}"
23062
+ slug="${slug%/}"
23063
+ if command -v gh >/dev/null 2>&1; then
23064
+ title="$(gh issue list --repo "$slug" --state open --limit 1 --json title --jq '.[0].title // empty' 2>/dev/null)" || title=""
23065
+ title="$(printf '%s' "$title" | LC_ALL=C tr -d '\000-\011\013-\037\177' | LC_ALL=C tr '\n' ' ')"
23066
+ if [ -n "$title" ]; then
23067
+ printf '%s\n' "Resolve the open issue: ${title:0:200}"
23068
+ return 0
23069
+ fi
23070
+ fi
23071
+ ;;
23072
+ esac
23073
+ todo_file="$(dirname -- "$dest")/todo.txt"
23074
+ git -C "$dest" grep -I -n -e TODO >"$todo_file" 2>/dev/null || true
23075
+ first="$(sed -n 1p "$todo_file" 2>/dev/null)"
23076
+ [ -n "$first" ] || return 0
23077
+ p="${first%%:*}"
23078
+ first="${first#*:}"
23079
+ l="${first%%:*}"
23080
+ t="${first#*:}"
23081
+ t="$(printf '%s' "$t" | LC_ALL=C tr -d '\000-\037\177' | sed -e 's/^[[:space:]]*//' | cut -c1-160)"
23082
+ p="$(printf '%s' "$p" | LC_ALL=C tr -d '\000-\037\177')"
23083
+ printf '%s\n' "Resolve the TODO in ${p} (line ${l}): ${t}"
23084
+ }
23085
+
23086
+ # Copies the newest receipt.json under $1/.loki/runs/<run>/ to a kept location and prints its
23087
+ # path. The clone is untrusted: every level (.loki, runs, the run dir, the file) is opened
23088
+ # without following symlinks, and only a receipt modified after the stamp file $2 (touched
23089
+ # right before loki start) is accepted, so a receipt committed in the repo is never reported.
23090
+ try_keep_receipt() {
23091
+ local dest="$1" stamp="$2" out_root
23092
+ out_root="${LOKI_TRY_RECEIPT_DIR:-${HOME}/.loki/try-receipts}"
23093
+ python3 -I - "$dest" "$stamp" "$out_root" <<'PY'
23094
+ import os, re, stat, sys
23095
+ dest, stamp, out_root = sys.argv[1:4]
23096
+ D = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
23097
+ try:
23098
+ start = os.stat(stamp).st_mtime_ns
23099
+ root = os.open(dest, D)
23100
+ loki = os.open(".loki", D, dir_fd=root)
23101
+ runs = os.open("runs", D, dir_fd=loki)
23102
+ names = os.listdir(runs)
23103
+ except OSError:
23104
+ sys.exit(1)
23105
+ best = None
23106
+ for name in names:
23107
+ if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", name):
23108
+ continue
23109
+ try:
23110
+ rd = os.open(name, D, dir_fd=runs)
23111
+ except OSError:
23112
+ continue
23113
+ try:
23114
+ fd = os.open("receipt.json", os.O_RDONLY | os.O_NOFOLLOW, dir_fd=rd)
23115
+ except OSError:
23116
+ os.close(rd)
23117
+ continue
23118
+ try:
23119
+ st = os.fstat(fd)
23120
+ if stat.S_ISREG(st.st_mode) and st.st_size <= 16 * 1024 * 1024 and st.st_mtime_ns > start:
23121
+ if best is None or st.st_mtime_ns > best[0]:
23122
+ with os.fdopen(os.dup(fd), "rb") as f:
23123
+ best = (st.st_mtime_ns, name, f.read())
23124
+ finally:
23125
+ os.close(fd)
23126
+ os.close(rd)
23127
+ if best is None:
23128
+ sys.exit(1)
23129
+ out = os.path.join(out_root, best[1])
23130
+ os.makedirs(out, exist_ok=True)
23131
+ target = os.path.join(out, "receipt.json")
23132
+ with open(target, "wb") as f:
23133
+ f.write(best[2])
23134
+ print(target)
23135
+ PY
23136
+ }
23137
+
23138
+ _cmd_try_body() {
23139
+ local tmp="$1" url="$2" dry_run="$3" provider="$4" max_cost="$5"
23140
+ local dest="$tmp/repo" task
23141
+ if ! (
23142
+ unset GIT_DIR GIT_WORK_TREE GIT_COMMON_DIR GIT_INDEX_FILE GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_PREFIX
23143
+ GIT_TERMINAL_PROMPT=0 git -c protocol.ext.allow=never -c core.hooksPath=/dev/null \
23144
+ clone --depth 1 --quiet -- "$url" "$dest"
23145
+ ); then
23146
+ echo "loki try: could not clone $url" >&2
23147
+ return 1
23148
+ fi
23149
+ task="$(try_pick_task "$dest" "$url")"
23150
+ if [ -z "$task" ]; then
23151
+ echo "loki try: no starter task found (no open issue readable, no TODO line) in $url" >&2
23152
+ return 1
23153
+ fi
23154
+ echo "Clone path: $dest"
23155
+ echo "Task: $task"
23156
+ echo "Estimate: one task on the Loki 10 engine, provider $provider, cost capped at \$$max_cost (--max-cost); the receipt records the actual spend."
23157
+ if [ "$dry_run" = true ]; then
23158
+ echo "[dry-run] No model call was made. A real run would execute:"
23159
+ echo " loki start \"<task>\" --no-pr --provider $provider --max-cost $max_cost"
23160
+ return 0
23161
+ fi
23162
+ local rc=0 receipt stamp="$tmp/started.stamp"
23163
+ : >"$stamp"
23164
+ (
23165
+ cd "$dest" && LOKI_ASSUME_YES=1 "$SKILL_DIR/bin/loki" start "$task" --no-pr --provider "$provider" --max-cost "$max_cost"
23166
+ ) || rc=$?
23167
+ if receipt="$(try_keep_receipt "$dest" "$stamp")"; then
23168
+ echo ""
23169
+ echo "Receipt: $receipt"
23170
+ echo "Check it: loki verify $receipt"
23171
+ else
23172
+ echo ""
23173
+ echo "No receipt was written (run exited $rc)."
23174
+ [ "$rc" != "0" ] || rc=1
23175
+ fi
23176
+ return "$rc"
23177
+ }
23178
+
23179
+ cmd_try() {
23180
+ local url="" dry_run=false provider="" max_cost="2"
23181
+ while [[ $# -gt 0 ]]; do
23182
+ case "$1" in
23183
+ --help|-h)
23184
+ echo -e "${BOLD}loki try${NC} - Try Loki on a public repo without touching your checkout"
23185
+ echo ""
23186
+ echo "Usage: loki try <public-repo-url> [--dry-run] [--provider P] [--max-cost N]"
23187
+ echo ""
23188
+ echo "Clones the repo (depth 1) into a run-owned temp directory, picks a starter"
23189
+ echo "task (the first open GitHub issue title, else the first TODO line), runs it"
23190
+ echo "on the Loki 10 engine with no PR, keeps only the receipt, and always removes"
23191
+ echo "the temp clone. Your current checkout is never read or changed."
23192
+ echo ""
23193
+ echo "Options:"
23194
+ echo " --dry-run Clone, then print the clone path, the task and an estimate. No model call, no spend"
23195
+ echo " --provider P claude (default) or opencode, codex, cline, aider"
23196
+ echo " --max-cost N Hard per-run cap in USD, a positive number (default 2)"
23197
+ echo " --help, -h Show this help"
23198
+ echo ""
23199
+ echo "URL schemes: https://, http://, git://, file://"
23200
+ echo "The receipt is kept under \${LOKI_TRY_RECEIPT_DIR:-~/.loki/try-receipts}/<run>/receipt.json;"
23201
+ echo "re-check it with: loki verify <receipt path>"
23202
+ echo ""
23203
+ echo "Examples:"
23204
+ echo " loki try https://github.com/owner/repo --dry-run"
23205
+ echo " loki try https://github.com/owner/repo"
23206
+ return 0
23207
+ ;;
23208
+ --dry-run) dry_run=true; shift ;;
23209
+ --provider)
23210
+ provider="${2:-}"
23211
+ if [ -z "$provider" ]; then echo "loki try: --provider requires a value" >&2; return 2; fi
23212
+ shift 2
23213
+ ;;
23214
+ --provider=*) provider="${1#*=}"; shift ;;
23215
+ --max-cost)
23216
+ max_cost="${2:-}"
23217
+ if [ -z "$max_cost" ]; then echo "loki try: --max-cost requires a value" >&2; return 2; fi
23218
+ shift 2
23219
+ ;;
23220
+ --max-cost=*) max_cost="${1#*=}"; shift ;;
23221
+ -*)
23222
+ echo "loki try: unknown option: $1" >&2
23223
+ echo "Run 'loki try --help'." >&2
23224
+ return 2
23225
+ ;;
23226
+ *)
23227
+ if [ -n "$url" ]; then
23228
+ echo "loki try: only one repo URL is accepted" >&2
23229
+ return 2
23230
+ fi
23231
+ url="$1"
23232
+ shift
23233
+ ;;
23234
+ esac
23235
+ done
23236
+ if [ -z "$url" ]; then
23237
+ echo "Usage: loki try <public-repo-url> [--dry-run]" >&2
23238
+ return 2
23239
+ fi
23240
+ case "$url" in
23241
+ https://*|http://*|git://*|file://*) ;;
23242
+ *) echo "loki try: unsupported URL '$url' (use https://, http://, git:// or file://)" >&2; return 2 ;;
23243
+ esac
23244
+ case "$url" in *[[:space:]]*) echo "loki try: the URL must not contain whitespace" >&2; return 2 ;; esac
23245
+ if ! [[ "$max_cost" =~ ^[0-9]+(\.[0-9]+)?$ ]] || ! awk -v c="$max_cost" 'BEGIN { exit !(c > 0) }'; then
23246
+ echo "loki try: --max-cost must be a positive number of USD, got: $max_cost" >&2
23247
+ return 2
23248
+ fi
23249
+ local use_provider="${provider:-${LOKI_PROVIDER:-claude}}"
23250
+ case "$use_provider" in
23251
+ claude|opencode|codex|cline|aider) ;;
23252
+ *) echo "loki try: unknown provider '$use_provider' (use claude, opencode, codex, cline or aider)" >&2; return 2 ;;
23253
+ esac
23254
+ if [ "$dry_run" != true ] && ! wow_provider_ready "$use_provider"; then
23255
+ echo "loki try: no provider ready for '$use_provider'. Run 'loki doctor', or preview with --dry-run." >&2
23256
+ return 1
23257
+ fi
23258
+
23259
+ local tmp rc=0
23260
+ tmp="$(try_tmp_create)" || { echo "loki try: cannot create a temp directory" >&2; return 1; }
23261
+ # shellcheck disable=SC2064 # expand tmp now; it is local to this call
23262
+ trap "try_tmp_cleanup '$tmp'; trap - INT TERM HUP; exit 130" INT
23263
+ trap "try_tmp_cleanup '$tmp'; trap - INT TERM HUP; exit 143" TERM
23264
+ trap "try_tmp_cleanup '$tmp'; trap - INT TERM HUP; exit 129" HUP
23265
+ _cmd_try_body "$tmp" "$url" "$dry_run" "$use_provider" "$max_cost" || rc=$?
23266
+ trap - INT TERM HUP
23267
+ try_tmp_cleanup "$tmp"
23268
+ if [ ! -e "$tmp" ]; then echo "Removed the temp clone."; fi
23269
+ return "$rc"
23270
+ }
23271
+
23027
23272
  cmd_plan() {
23028
23273
  local prd_file=""
23029
23274
  local show_requirements=false
@@ -23149,7 +23394,7 @@ _suggest_command() {
23149
23394
  ultracode council demo dogfood heal modernize import issue github init template \
23150
23395
  status stop pause resume monitor watch watchdog dashboard web open preview share \
23151
23396
  memory context ctx checkpoint state report kpis cost metrics stats logs otel \
23152
- syslog telemetry crash config provider doctor setup setup-skill onboard quickstart \
23397
+ syslog telemetry crash config provider doctor setup setup-skill onboard quickstart try \
23153
23398
  welcome update self-update rollback merge failover cluster enterprise remote deploy docker \
23154
23399
  sandbox api mcp magic compound assets export wiki control docs explain why audit compliance \
23155
23400
  welcome update self-update rollback failover cluster enterprise remote deploy docker \
@@ -23722,6 +23967,9 @@ main() {
23722
23967
  plan)
23723
23968
  cmd_plan "$@"
23724
23969
  ;;
23970
+ try)
23971
+ cmd_try "$@"
23972
+ ;;
23725
23973
  failover)
23726
23974
  cmd_failover "$@"
23727
23975
  ;;
@@ -30496,7 +30744,7 @@ cmd_estimate() {
30496
30744
  echo "Install Bun (https://bun.sh) or remove LOKI_LEGACY_BASH=1." >&2
30497
30745
  return 1
30498
30746
  fi
30499
- bun "$entry" estimate "$@"
30747
+ bun --no-env-file --config=/dev/null "$entry" estimate "$@"
30500
30748
  }
30501
30749
 
30502
30750
  # Transparent cost view (R3): per-run + per-project spend, model routing, and
@@ -36071,7 +36319,7 @@ cmd_control() {
36071
36319
  return 1
36072
36320
  fi
36073
36321
  [ -f "$cli" ] || cli="$SKILL_DIR/loki-ts/src/cli.ts"
36074
- exec bun "$cli" control "$@"
36322
+ exec bun --no-env-file --config=/dev/null "$cli" control "$@"
36075
36323
  }
36076
36324
 
36077
36325
  cmd_wiki() {