loki-mode 10.6.8 → 10.6.9

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 (101) hide show
  1. package/README.md +14 -13
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/lib/backlog.py +167 -36
  5. package/autonomy/lib/workspace.py +133 -39
  6. package/autonomy/lib/workspace_comment.py +91 -0
  7. package/autonomy/loki +85 -17
  8. package/bin/loki +3 -3
  9. package/completions/_loki +1 -0
  10. package/completions/loki.bash +1 -1
  11. package/dashboard/__init__.py +1 -1
  12. package/dashboard/api_start.py +26 -1
  13. package/docs/INSTALLATION.md +1 -1
  14. package/docs/WORKSPACES.md +11 -4
  15. package/docs/control-plane-container.md +8 -3
  16. package/docs/dashboard-guide.md +4 -0
  17. package/docs/exit-codes.md +1 -0
  18. package/docs/slack.md +12 -4
  19. package/docs/trackers.md +27 -0
  20. package/docs/v10/BOARD.md +64 -29
  21. package/docs/v10/CONTROL-PLANE.md +14 -7
  22. package/docs/v10/D51-PHASE-B.md +3 -1
  23. package/docs/v10/D61-SPEED.md +4 -0
  24. package/docs/v10/D63-CARDS.md +354 -0
  25. package/docs/v10/DECISIONS.md +39 -0
  26. package/docs/v10/PROGRESS.md +50 -0
  27. package/loki-ts/dist/loki.js +583 -564
  28. package/mcp/__init__.py +1 -1
  29. package/mcp/tests/test_v10_tools.py +56 -0
  30. package/mcp/v10_tools.py +13 -3
  31. package/package.json +1 -1
  32. package/packages/control-plane/dist/server.js +76 -6
  33. package/packages/control-plane/drizzle/0001_group_id.sql +3 -0
  34. package/packages/control-plane/drizzle/meta/_journal.json +8 -1
  35. package/packages/control-plane/src/db/schema.ts +3 -0
  36. package/packages/control-plane/src/server/answer.ts +1 -1
  37. package/packages/control-plane/src/server/app.ts +6 -2
  38. package/packages/control-plane/src/server/auth.ts +51 -0
  39. package/packages/control-plane/src/server/runs.ts +4 -3
  40. package/packages/control-plane/src/server/serve.ts +11 -1
  41. package/packages/control-plane/src/shipper/discover.ts +29 -0
  42. package/packages/control-plane/ui/dist/assets/{index-53E32yrO.js → index-Jt31nrK-.js} +24 -24
  43. package/packages/control-plane/ui/dist/index.html +1 -1
  44. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  45. package/web-app/dist/assets/{AdminPage-DN_pe8vo.js → AdminPage-CZKZLuWU.js} +1 -1
  46. package/web-app/dist/assets/{Avatar-BnQsr8z-.js → Avatar-Dcy9kO2E.js} +1 -1
  47. package/web-app/dist/assets/{Badge-DzERiZn2.js → Badge-DUotUSyx.js} +1 -1
  48. package/web-app/dist/assets/{Button-CaaoxQAt.js → Button-CFz_zfMZ.js} +1 -1
  49. package/web-app/dist/assets/{CockpitPage-SXhnysJA.js → CockpitPage-BhRw_oiM.js} +1 -1
  50. package/web-app/dist/assets/{ComparePage-krxTCFJY.js → ComparePage-DfnsRTTC.js} +1 -1
  51. package/web-app/dist/assets/{ErrorBoundary-BL96ESMc.js → ErrorBoundary-Dn0gUu3D.js} +1 -1
  52. package/web-app/dist/assets/{EvidenceReceiptPanel-PUiGSpkB.js → EvidenceReceiptPanel-Co-xi282.js} +1 -1
  53. package/web-app/dist/assets/{GitHubIssuesPanel-C0hPs-P2.js → GitHubIssuesPanel-CoCpfexq.js} +1 -1
  54. package/web-app/dist/assets/{GitHubPRsPanel-CLvqNUlu.js → GitHubPRsPanel-Dg0Eg6F_.js} +1 -1
  55. package/web-app/dist/assets/{HomePage-X-sq_Rc9.js → HomePage-BysxL4mV.js} +2 -2
  56. package/web-app/dist/assets/{LoginPage-DhQuwdua.js → LoginPage-CQ366p_d.js} +1 -1
  57. package/web-app/dist/assets/{MagicPage-CP2ODjfn.js → MagicPage-DD7YcbNq.js} +1 -1
  58. package/web-app/dist/assets/{MetricsPage-C6SYiQES.js → MetricsPage-BYz49xQj.js} +1 -1
  59. package/web-app/dist/assets/{NotFoundPage-CCDxxYc-.js → NotFoundPage-CKs09MeO.js} +1 -1
  60. package/web-app/dist/assets/{ProjectPage-yDF--8FQ.js → ProjectPage-Ci2BIA_D.js} +3 -3
  61. package/web-app/dist/assets/{ProjectsPage-BBLspdLn.js → ProjectsPage-ClwMH0eu.js} +1 -1
  62. package/web-app/dist/assets/{SettingsPage-C_sWbQRw.js → SettingsPage-Cm0rCX-y.js} +1 -1
  63. package/web-app/dist/assets/{ShowcasePage-xCMuL0N8.js → ShowcasePage-CxvESt8X.js} +1 -1
  64. package/web-app/dist/assets/{SystemSettingsPage-DnJKGCx7.js → SystemSettingsPage-W_CGYPOY.js} +1 -1
  65. package/web-app/dist/assets/{TeamsPage-BBXdhrSs.js → TeamsPage-feVvRvys.js} +1 -1
  66. package/web-app/dist/assets/{TemplatesPage-AZ1Lr1rn.js → TemplatesPage-C-pUWMmF.js} +1 -1
  67. package/web-app/dist/assets/{TerminalOutput-CtIm9tXc.js → TerminalOutput-j5ibjKY3.js} +1 -1
  68. package/web-app/dist/assets/{activity-Dk1FK25Q.js → activity-TTdVYMa_.js} +1 -1
  69. package/web-app/dist/assets/{bell-dhqXyzP4.js → bell-DeoTFwpv.js} +1 -1
  70. package/web-app/dist/assets/{bot-CExHd4Kf.js → bot-B5SghxqU.js} +1 -1
  71. package/web-app/dist/assets/{check-D51zaA1a.js → check-k8SzsdbE.js} +1 -1
  72. package/web-app/dist/assets/{chevron-left-DEwnQzH1.js → chevron-left-DHnEllFz.js} +1 -1
  73. package/web-app/dist/assets/{circle-alert-C6U90Msk.js → circle-alert-DW_SLdcB.js} +1 -1
  74. package/web-app/dist/assets/{clock-6mFbMzzH.js → clock-Bmoaxy09.js} +1 -1
  75. package/web-app/dist/assets/{cloud-BrZZtE7F.js → cloud-eg3tzXTh.js} +1 -1
  76. package/web-app/dist/assets/{code-xml-DTFzeepP.js → code-xml-VAfXihhD.js} +1 -1
  77. package/web-app/dist/assets/{database-B5rEiTre.js → database-CY3_-5aK.js} +1 -1
  78. package/web-app/dist/assets/{dollar-sign-bQs4rlS_.js → dollar-sign-DYC0OLlg.js} +1 -1
  79. package/web-app/dist/assets/{file-code-corner-Mo6e6qbf.js → file-code-corner-0X3SveKz.js} +1 -1
  80. package/web-app/dist/assets/{file-plus-DzHZ0jpP.js → file-plus-B_oh_ukl.js} +1 -1
  81. package/web-app/dist/assets/{globe-DYN9UhTa.js → globe-F0tLA8WJ.js} +1 -1
  82. package/web-app/dist/assets/{hammer-bB0hSRcJ.js → hammer-DrlvBmfW.js} +1 -1
  83. package/web-app/dist/assets/{index-CIH5CqZB.js → index-YihVaZAj.js} +3 -3
  84. package/web-app/dist/assets/{layers-qhFkY8Nu.js → layers-CbJfn5UC.js} +1 -1
  85. package/web-app/dist/assets/{loader-circle-BGBkaYH7.js → loader-circle-CpmOggou.js} +1 -1
  86. package/web-app/dist/assets/{lock-DIcsNFum.js → lock-BGJ6QWrI.js} +1 -1
  87. package/web-app/dist/assets/{package-BdIIbZhS.js → package-BTmH4Xto.js} +1 -1
  88. package/web-app/dist/assets/{plus-Co1Bv8zT.js → plus-mKQ7hz-8.js} +1 -1
  89. package/web-app/dist/assets/{refresh-cw-CGF4ABWH.js → refresh-cw-vPetpLsm.js} +1 -1
  90. package/web-app/dist/assets/{rotate-ccw-BX10Q79h.js → rotate-ccw-DiJ8GFhj.js} +1 -1
  91. package/web-app/dist/assets/{scroll-text-CbXDlpGp.js → scroll-text-DpPQIlQR.js} +1 -1
  92. package/web-app/dist/assets/{server-CIhi6yUO.js → server-CiaV4drx.js} +1 -1
  93. package/web-app/dist/assets/{shield-alert-DJ5fB6Dg.js → shield-alert-BaaQqyco.js} +1 -1
  94. package/web-app/dist/assets/{thumbs-up-BEft2T94.js → thumbs-up-2zNCKp0Q.js} +1 -1
  95. package/web-app/dist/assets/{trash-2-CWGUPn7Y.js → trash-2-CiVDx5Co.js} +1 -1
  96. package/web-app/dist/assets/{trending-down-CjqstvxQ.js → trending-down-tcxvw5BH.js} +1 -1
  97. package/web-app/dist/assets/{trending-up-CTyLjMzE.js → trending-up-C2bv2_v_.js} +1 -1
  98. package/web-app/dist/assets/{upload-BmRx-uv4.js → upload-DS4VPUAp.js} +1 -1
  99. package/web-app/dist/assets/{usePolling-ChHkSJye.js → usePolling-JggBcVln.js} +1 -1
  100. package/web-app/dist/assets/{user-ZXh_f35z.js → user-CvavvOxM.js} +1 -1
  101. package/web-app/dist/index.html +1 -1
@@ -0,0 +1,91 @@
1
+ """Post the workspace integration result as one PR comment per repo PR (D51-B11).
2
+
3
+ No LLM. Uses the `gh` CLI with the caller's own environment, so GH_TOKEN or
4
+ GITHUB_TOKEN is read by gh itself and never placed in argv, the comment body
5
+ or any log line. Commenting is best effort: it never raises and never changes
6
+ the run's exit code. Opt out with LOKI_WORKSPACE_COMMENT=0.
7
+ """
8
+ import os
9
+ import re
10
+ import subprocess
11
+ import tempfile
12
+
13
+ GH_TIMEOUT_S = 60
14
+
15
+
16
+ def verdict(ev):
17
+ """Map integration evidence to a label. Only a clean exit 0 reads as PASSED."""
18
+ status = ev.get("status")
19
+ if "timeout" in str(ev.get("note") or "").lower():
20
+ return "TIMEOUT"
21
+ if status == "passed" and ev.get("exit_code") == 0:
22
+ return "PASSED"
23
+ if status == "not_configured":
24
+ return "NOT CONFIGURED"
25
+ return "FAILED"
26
+
27
+
28
+ def render(ws_name, run_id, repo, ev):
29
+ heads = ev.get("heads") or {}
30
+ lines = ["Loki workspace `%s` run `%s`" % (ws_name, run_id), "",
31
+ "Integration: **%s** (exit code %s)" % (verdict(ev), ev.get("exit_code")), "",
32
+ "| Repo | Outcome | Head SHA |", "| --- | --- | --- |"]
33
+ outcomes = ev.get("outcomes") or {}
34
+ for r in sorted(heads.keys() | outcomes.keys()):
35
+ out = outcomes.get(r, "unknown")
36
+ out = "ok" if out == "ok" else str(out).replace("|", "/")
37
+ mark = " (this PR)" if r == repo else ""
38
+ lines.append("| %s%s | %s | %s |" % (r, mark, out, heads.get(r) or "none"))
39
+ if ev.get("log_sha256"):
40
+ lines += ["", "Integration log sha256: `%s`" % ev["log_sha256"]]
41
+ lines += ["", "Integration evidence is not part of the Seal."]
42
+ return "\n".join(lines) + "\n"
43
+
44
+
45
+ def _gh(args, cwd):
46
+ return subprocess.run(["gh"] + args, cwd=cwd, stdin=subprocess.DEVNULL, capture_output=True,
47
+ text=True, timeout=GH_TIMEOUT_S)
48
+
49
+
50
+ def _branch(wt):
51
+ r = subprocess.run(["git", "-C", wt, "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True,
52
+ timeout=GH_TIMEOUT_S)
53
+ b = r.stdout.strip()
54
+ return b if r.returncode == 0 and b and b != "HEAD" else None
55
+
56
+
57
+ def post_comments(ws_name, run_id, run_dir, worktrees, ev, say):
58
+ """One comment per repo that has an open PR on its run branch. Never raises."""
59
+ if os.environ.get("LOKI_WORKSPACE_COMMENT") == "0":
60
+ return 0
61
+ posted = 0
62
+ try:
63
+ for repo, wt in sorted(worktrees.items()):
64
+ try:
65
+ branch = _branch(wt)
66
+ if not branch:
67
+ continue
68
+ r = _gh(["pr", "list", "--repo", repo, "--head", branch, "--state", "open",
69
+ "--json", "number", "--jq", ".[0].number"], wt)
70
+ num = r.stdout.strip()
71
+ if r.returncode != 0 or not re.fullmatch(r"\d+", num):
72
+ continue
73
+ fd, body = tempfile.mkstemp(prefix="comment-", suffix=".md", dir=run_dir) # mode 0600
74
+ try:
75
+ with os.fdopen(fd, "w") as f:
76
+ f.write(render(ws_name, run_id, repo, ev))
77
+ c = _gh(["pr", "comment", num, "--repo", repo, "--body-file", body], wt)
78
+ finally:
79
+ try:
80
+ os.unlink(body)
81
+ except OSError:
82
+ pass
83
+ if c.returncode == 0:
84
+ posted += 1
85
+ else:
86
+ say("workspace: comment on %s#%s failed (gh exit %d)" % (repo, num, c.returncode))
87
+ except (OSError, subprocess.SubprocessError, ValueError) as e:
88
+ say("workspace: comment for %s skipped (%s)" % (repo, type(e).__name__))
89
+ except Exception as e: # never fail the run because commenting failed
90
+ say("workspace: commenting skipped (%s)" % type(e).__name__)
91
+ return posted
package/autonomy/loki CHANGED
@@ -1630,7 +1630,7 @@ show_help() {
1630
1630
  echo " start [SPEC] Start a build (PRD file, GitHub/GitLab/Jira/Azure issue, or no arg)"
1631
1631
  echo " plan <PRD-file> Dry-run analysis: complexity, cost, execution plan"
1632
1632
  echo " backlog <owner/repo> --all|--label X|--issues N,N Run every matching open issue, N in parallel"
1633
- echo " workspace list | run <name> <ref> Multi-repo runs from loki.yaml workspaces (LOKI_WORKSPACES=1)"
1633
+ echo " workspace list | run <name> <ref> Multi-repo runs from loki.yaml workspaces (LOKI_WORKSPACES=0 disables)"
1634
1634
  echo " quickstart [idea] Guided first build: setup, idea, template, plan, go"
1635
1635
  echo ""
1636
1636
  echo "Session:"
@@ -1783,7 +1783,7 @@ show_help() {
1783
1783
  echo " syslog telemetry template test tour trigger trust trust-metrics"
1784
1784
  echo " ultracode update verify version voice watch watchdog web welcome why"
1785
1785
  echo " wiki worktree (wt)"
1786
- echo " control (preview, needs LOKI_CONTROL=1)"
1786
+ echo " control"
1787
1787
  echo ""
1788
1788
  echo "Any command: loki <command> --help"
1789
1789
  echo ""
@@ -1897,7 +1897,74 @@ _loki_dashboard_port_is_ours() {
1897
1897
  return 1
1898
1898
  }
1899
1899
 
1900
+ # C3: the Control Plane is the default UI. Prints a live loopback URL from
1901
+ # ~/.loki/control/instance.json (pid alive, http loopback only, /health says
1902
+ # service=loki-control), or starts `loki control serve` detached and waits for
1903
+ # it to publish one. Returns 1 when the caller should fall back to the old
1904
+ # dashboard (LOKI_CONTROL_DEFAULT=0, no bun, no python3, server did not start).
1905
+ _loki_control_live_url() {
1906
+ local inst="${HOME}/.loki/control/instance.json" fields pid url health
1907
+ [ -f "$inst" ] || return 1
1908
+ fields=$(python3 - "$inst" 2>/dev/null <<'PY'
1909
+ import json, sys, urllib.parse
1910
+ d = json.load(open(sys.argv[1]))
1911
+ pid, url = d.get("pid"), d.get("url")
1912
+ if not isinstance(pid, int) or isinstance(pid, bool) or not isinstance(url, str):
1913
+ sys.exit(1)
1914
+ u = urllib.parse.urlparse(url)
1915
+ if u.scheme != "http" or u.hostname not in ("127.0.0.1", "localhost", "::1"):
1916
+ sys.exit(1)
1917
+ if u.path not in ("", "/") or u.query or u.fragment or u.params or "@" in u.netloc or "?" in url or "#" in url:
1918
+ sys.exit(1)
1919
+ print(pid, url.rstrip("/"))
1920
+ PY
1921
+ ) || return 1
1922
+ pid="${fields%% *}"; url="${fields#* }"
1923
+ kill -0 "$pid" 2>/dev/null || return 1
1924
+ health=$(curl -g -s --max-time 2 "${url}/health" 2>/dev/null) || return 1
1925
+ case "$health" in *'"service"'*'"loki-control"'*) ;; *) return 1 ;; esac
1926
+ printf '%s\n' "$url"
1927
+ }
1928
+
1929
+ _loki_control_ui() {
1930
+ [ "${LOKI_CONTROL_DEFAULT:-1}" = "0" ] && return 1
1931
+ local url="" open_it=1 arg i
1932
+ for arg in "$@"; do [ "$arg" = "--no-open" ] && open_it=0; done
1933
+ if [ "${LOKI_HEADLESS:-0}" = "1" ] || [ "${LOKI_NO_BROWSER:-0}" = "1" ]; then open_it=0; fi
1934
+ command -v python3 >/dev/null 2>&1 || return 1
1935
+ url=$(_loki_control_live_url) || url=""
1936
+ if [ -z "$url" ]; then
1937
+ if ! command -v bun >/dev/null 2>&1; then
1938
+ echo "loki: the Control Plane needs bun (https://bun.sh); using the classic dashboard." >&2
1939
+ return 1
1940
+ fi
1941
+ local cli="$SKILL_DIR/loki-ts/dist/loki.js" cdir="${HOME}/.loki/control" spid
1942
+ [ -f "$cli" ] || cli="$SKILL_DIR/loki-ts/src/cli.ts"
1943
+ mkdir -p "$cdir" && chmod 700 "$cdir" 2>/dev/null || true
1944
+ LOKI_CONTROL=1 LOKI_NO_BROWSER=1 nohup bun "$cli" control serve >"$cdir/serve.log" 2>&1 </dev/null &
1945
+ spid=$!
1946
+ echo "$spid" > "$cdir/serve.pid" 2>/dev/null || true
1947
+ for i in $(seq 1 100); do
1948
+ url=$(_loki_control_live_url) && break
1949
+ url=""
1950
+ kill -0 "$spid" 2>/dev/null || break
1951
+ sleep 0.1
1952
+ done
1953
+ if [ -z "$url" ]; then
1954
+ echo "loki: the Control Plane did not start (see $cdir/serve.log); using the classic dashboard." >&2
1955
+ return 1
1956
+ fi
1957
+ fi
1958
+ if [ "$open_it" = "1" ]; then
1959
+ loki_open_url "$url" || echo "$url"
1960
+ else
1961
+ echo "$url"
1962
+ fi
1963
+ return 0
1964
+ }
1965
+
1900
1966
  cmd_ui() {
1967
+ _loki_control_ui "$@" && return 0
1901
1968
  local port="" pid=""
1902
1969
  if [ -f "$DASHBOARD_PID_FILE" ]; then
1903
1970
  pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)
@@ -7325,16 +7392,15 @@ _loki_dashboard_bind_is_safe() {
7325
7392
  cmd_dashboard() {
7326
7393
  local subcommand="${1:-}"
7327
7394
 
7328
- # LOKI_CONTROL_DEFAULT=1 (off by default): open the Control Plane instead of the old dashboard.
7329
- # The old dashboard stays available when the flag is unset, and via `loki dashboard stop|status|url`.
7330
- if [ "${LOKI_CONTROL_DEFAULT:-0}" = "1" ]; then
7331
- case "$subcommand" in
7332
- ""|start|open)
7333
- echo "LOKI_CONTROL_DEFAULT=1: starting the Control Plane (unset it to use the old dashboard)"
7334
- LOKI_CONTROL=1 exec "$0" control serve
7335
- ;;
7336
- esac
7337
- fi
7395
+ # The Control Plane is the default UI; LOKI_CONTROL_DEFAULT=0 restores the classic dashboard.
7396
+ # Only the bare forms are taken over; `start --port ...` and friends keep the classic server.
7397
+ case "$subcommand" in
7398
+ ""|start|open)
7399
+ if [ $# -le 1 ] || { [ $# -eq 2 ] && [ "$2" = "--no-open" ]; }; then
7400
+ _loki_control_ui "${@:2}" && return 0
7401
+ fi
7402
+ ;;
7403
+ esac
7338
7404
 
7339
7405
  # If no subcommand provided, show help (backward compatible with old behavior)
7340
7406
  if [ -z "$subcommand" ]; then
@@ -12921,10 +12987,10 @@ cmd_config_schema() {
12921
12987
  loki_config_generate_schema
12922
12988
  }
12923
12989
 
12924
- # D65: multi-repo workspaces (D51 Phase B). Gated by LOKI_WORKSPACES=1.
12990
+ # D65: multi-repo workspaces (D51 Phase B). On by default; LOKI_WORKSPACES=0 disables.
12925
12991
  cmd_workspace() {
12926
- if [ "${LOKI_WORKSPACES:-}" != "1" ]; then
12927
- echo -e "${RED}loki workspace is disabled.${NC} Set LOKI_WORKSPACES=1 to use it." >&2
12992
+ if [ "${LOKI_WORKSPACES:-}" = "0" ]; then
12993
+ echo -e "${RED}loki workspace is disabled.${NC} Unset LOKI_WORKSPACES (it is set to 0)." >&2
12928
12994
  return 2
12929
12995
  fi
12930
12996
  command -v python3 >/dev/null 2>&1 || { echo -e "${RED}loki workspace: python3 is required${NC}" >&2; return 2; }
@@ -18714,10 +18780,12 @@ with open(manifest_path, 'w') as f:
18714
18780
  # usable.
18715
18781
  # ---------------------------------------------------------------------------
18716
18782
  cmd_verify() {
18717
- # D48: --pubkey verifies a receipt against a given public key (v10 engine, no ~/.loki needed).
18783
+ # D48: --pubkey FILE or --pubkey=FILE (any position) verifies a receipt against a given public key (v10 engine, no ~/.loki needed).
18718
18784
  local _pk_arg
18719
18785
  for _pk_arg in "$@"; do
18720
- [ "$_pk_arg" = "--pubkey" ] && LOKI_ENGINE=v10 exec "$_LOKI_SCRIPT_DIR/../bin/loki" verify "$@"
18786
+ case "$_pk_arg" in
18787
+ --pubkey|--pubkey=*) LOKI_ENGINE=v10 exec "$_LOKI_SCRIPT_DIR/../bin/loki" verify "$@" ;;
18788
+ esac
18721
18789
  done
18722
18790
  # --fast: deterministic checks only, in milliseconds. Measured on this repo
18723
18791
  # (1,932 tracked files): 298 ms cold, 87 ms warm, 19 ms diff-scoped, against
package/bin/loki CHANGED
@@ -354,12 +354,12 @@ case "${LOKI_ENGINE:-$_loki_engine_default}" in
354
354
  # `loki <ref>`; any other start call (PRD file, flags first, one word) stays legacy.
355
355
  start)
356
356
  case "${2:-}" in
357
- */*\#[0-9]*|http*://*/issues/*|http*://*/browse/*|*" "*) _e10=1; _e10_args=("${@:2}") ;;
357
+ */*\#[0-9]*|http*://*/issues/*|http*://*/browse/*|jira:*|linear:*|https://linear.app/*/issue/*|*" "*) _e10=1; _e10_args=("${@:2}") ;;
358
358
  *) _e10=0 ;;
359
359
  esac
360
360
  ;;
361
361
  # http*://*/issues/* also covers GitLab's /-/issues/ URLs.
362
- */*\#[0-9]*|http*://*/issues/*|http*://*/browse/*) _e10=1 ;;
362
+ */*\#[0-9]*|http*://*/issues/*|http*://*/browse/*|jira:*|linear:*|https://linear.app/*/issue/*) _e10=1 ;;
363
363
  *" "*) _e10=1 ;;
364
364
  # ponytail: a one-word argument stays on the legacy CLI. The telemetry
365
365
  # allowlist is not a command registry (estimate, intent, outcomes are
@@ -567,7 +567,7 @@ fi
567
567
  # Two-token routes (provider show/list, memory list/index) match on the first
568
568
  # token only; the Bun dispatcher handles subcommand routing internally.
569
569
  case "${1:-}" in
570
- version|--version|-v|status|stats|doctor|provider|memory|rollback|internal|kpis|trust|proof|receipt|wiki|crash|control|contract)
570
+ version|--version|-v|status|stats|doctor|provider|memory|rollback|internal|kpis|trust|proof|receipt|wiki|crash|control|contract|slack|answer)
571
571
  # v7.5.2: rollback added (wires loki-ts/src/commands/rollback.ts).
572
572
  # v7.5.3: internal added for autonomy/run.sh phase1-hooks calls.
573
573
  # v7.5.28: kpis added (Phase K MVP: read-only KPI snapshot).
package/completions/_loki CHANGED
@@ -206,6 +206,7 @@ function _loki_commands {
206
206
  'crash:Crash report management'
207
207
  'otel:OpenTelemetry management'
208
208
  'wiki:Wiki commands'
209
+ 'answer:Resume a BLOCKED run with an answer'
209
210
  'control:Control plane (preview, LOKI_CONTROL=1)'
210
211
  'workspace:Multi-project workspaces (preview, LOKI_WORKSPACES=1)'
211
212
  'backlog:Run every matching open issue, N in parallel'
@@ -5,7 +5,7 @@ _loki_completion() {
5
5
  _init_completion || return
6
6
 
7
7
  # Main subcommands (must match autonomy/loki main case statement)
8
- local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash control workspace open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help keys"
8
+ local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash control workspace open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help keys answer"
9
9
 
10
10
  # 1. If we are on the first argument (subcommand)
11
11
  if [[ $cword -eq 1 ]]; then
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "10.6.8"
10
+ __version__ = "10.6.9"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -21,6 +21,7 @@ import os
21
21
  import re
22
22
  import shutil
23
23
  import subprocess
24
+ import sys
24
25
  import threading
25
26
  import time
26
27
  import urllib.error
@@ -156,6 +157,25 @@ def _project_dir() -> str:
156
157
  return os.environ.get("LOKI_PROJECT_DIR") or os.getcwd()
157
158
 
158
159
 
160
+ def _autonomy_lib():
161
+ lib = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "autonomy", "lib")
162
+ if lib not in sys.path:
163
+ sys.path.insert(0, lib)
164
+
165
+
166
+ def _workspaces_enabled() -> bool:
167
+ """Same rule as `loki workspace`: on by default, LOKI_WORKSPACES=0 disables."""
168
+ _autonomy_lib()
169
+ import workspace
170
+ return workspace.enabled()
171
+
172
+
173
+ def _worktree_prep():
174
+ _autonomy_lib()
175
+ import worktree_prep
176
+ return worktree_prep
177
+
178
+
159
179
  def _prepare_workdir(repo: str, number: int, token: str) -> str:
160
180
  """One git worktree per issue, off the repo's default branch."""
161
181
  env = dict(os.environ, GH_TOKEN=token, GIT_TERMINAL_PROMPT="0")
@@ -180,7 +200,12 @@ def _prepare_workdir(repo: str, number: int, token: str) -> str:
180
200
  stamp = time.strftime("%Y%m%d%H%M%S")
181
201
  wt = _home() / "worktrees" / slug / ("issue-%d-%s" % (number, stamp))
182
202
  wt.parent.mkdir(parents=True, exist_ok=True)
183
- _git("worktree", "add", "-b", "loki/backlog-%d-%s" % (number, stamp), str(wt), start, cwd=base)
203
+ branch = "loki/backlog-%d-%s" % (number, stamp)
204
+ if _workspaces_enabled():
205
+ # Shared worktree prep (D51-B06): same per-base lock and dep copy as backlog and workspace runs.
206
+ _worktree_prep().prepare_worktree(base, str(wt), branch, base=start)
207
+ return str(wt)
208
+ _git("worktree", "add", "-b", branch, str(wt), start, cwd=base)
184
209
  return str(wt)
185
210
 
186
211
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v10.6.8
5
+ **Version:** v10.6.9
6
6
 
7
7
  **Engine note:** `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine. `loki start` still routes to the legacy engine, which is being removed (planned work resumes 2026-10-07); prefer `loki owner/repo#N`. See [docs/v10/GUIDE.md](v10/GUIDE.md).
8
8
 
@@ -1,6 +1,6 @@
1
1
  # Workspaces: one issue across several repos
2
2
 
3
- Experimental, off by default. Set `LOKI_WORKSPACES=1` to enable. Design: `docs/v10/D51-PHASE-B.md`.
3
+ On by default. Set `LOKI_WORKSPACES=0` to disable. Design: `docs/v10/D51-PHASE-B.md`.
4
4
 
5
5
  Define a group of repos once in `loki.yaml`:
6
6
 
@@ -13,17 +13,24 @@ workspaces:
13
13
  integration: {command: "make e2e", timeout_s: 900}
14
14
  ```
15
15
 
16
+ Repos with no pending `after` run in parallel, at most `concurrency` at a time (default 2; set `LOKI_WORKSPACE_CONCURRENCY` to change it).
17
+
16
18
  Then:
17
19
 
18
20
  ```bash
19
- LOKI_WORKSPACES=1 loki workspace list
20
- LOKI_WORKSPACES=1 loki workspace run shop acme/api#12
21
+ loki workspace list
22
+ loki workspace show shop
23
+ loki workspace run shop acme/api#12
24
+ loki workspace status # every recorded run, one row per repo
25
+ loki workspace status <run-id>
21
26
  ```
22
27
 
23
28
  - Each repo gets its own git worktree on a `loki/ws-<run-id>-N` branch and its own engine run. Your checkouts are not touched.
24
29
  - `after` orders repos: a repo starts once its predecessors succeed. If a predecessor fails, its dependents are SKIPPED.
25
- - A failing repo does not stop the others (continue and report). The command exits 0 only when every repo succeeded and the integration step did not fail; otherwise 1.
30
+ - A failing repo does not stop the others (continue and report). The command exits 0 only when every repo succeeded and the integration step did not fail. It exits 3 when the only non-ok outcomes are budget stops (a child exited 3, or a dependent was skipped behind one), and 1 for any other failure. Ctrl-C (SIGINT) stops the recorded child process groups like SIGTERM.
26
31
  - The repo that owns the issue ref gets the ref; other repos get a task text naming the ref and the sibling worktree paths as read-only context.
27
32
  - After all repos finish, the `integration` command runs once from the run directory with `LOKI_WS_DIR_<OWNER>_<REPO>` pointing at each worktree. A timeout counts as failed. With no integration configured the status is `not_configured`.
28
33
  - Evidence is written to `.loki/workspaces/<name>/<run-id>/integration.json`: head SHA per repo, exit code, log sha256. It is not part of the Seal.
29
34
  - Without `path`, a repo is cloned to `~/.loki/repos/owner__name`.
35
+ - After the integration step, a PR comment is posted on each repo PR that exists for its run branch, showing the integration status (PASSED, FAILED, TIMEOUT or NOT CONFIGURED) and every head SHA. It uses your own `gh` login or GH_TOKEN, skips silently when there is no PR, never fails the run, and is disabled with `LOKI_WORKSPACE_COMMENT=0`.
36
+ - Backlog (`loki backlog`) and dashboard starts now share the workspace worktree prep: a per-repo lock, a clean start point and copied dependencies. `LOKI_WORKSPACES=0` restores the previous worktree creation (D51-B05, D51-B06).
@@ -4,14 +4,17 @@ The Control Plane (run ingest API plus UI) ships as its own small image built
4
4
  from `Dockerfile.control-plane`. It serves on port 47821, stores its SQLite
5
5
  database under `/data`, runs as a non-root user and contains no secrets.
6
6
 
7
- The server has no authentication of its own. Keep it on a private network or
8
- behind an authenticating proxy; do not expose it publicly.
7
+ The image binds 0.0.0.0, so `LOKI_CONTROL_TOKEN` is required at run time. Without
8
+ it the server exits 2 before listening. Every `/v1/*` call then needs
9
+ `Authorization: Bearer <token>`. The token is never baked into the image. Set
10
+ `LOKI_CONTROL_ALLOW_INSECURE_BIND=1` only on a trusted private network; the
11
+ server prints a warning when you do.
9
12
 
10
13
  ## Docker
11
14
 
12
15
  ```bash
13
16
  docker build -f Dockerfile.control-plane -t loki-control-plane .
14
- docker run -d -p 47821:47821 -v loki-control:/data loki-control-plane
17
+ docker run -d -p 47821:47821 -e LOKI_CONTROL_TOKEN="$(openssl rand -hex 32)" -v loki-control:/data loki-control-plane
15
18
  LOKI_CONTROL_URL=http://localhost:47821 loki control status
16
19
  ```
17
20
 
@@ -42,4 +45,6 @@ EFS volume, secrets from Secrets Manager ARNs).
42
45
  | --- | --- | --- |
43
46
  | `PORT` | 47821 | listen port |
44
47
  | `LOKI_CONTROL_HOST` | 0.0.0.0 | bind address (outside the image: 127.0.0.1) |
48
+ | `LOKI_CONTROL_TOKEN` | none (required) | bearer token for `/v1/*`; required on a non-loopback bind |
49
+ | `LOKI_CONTROL_ALLOW_INSECURE_BIND` | unset | `1` accepts a tokenless non-loopback bind (not recommended) |
45
50
  | `LOKI_CONTROL_DB` | /data/control.db | SQLite path |
@@ -482,3 +482,7 @@ Useful for:
482
482
  - [Agent Types](../references/agent-types.md) - Agent role definitions
483
483
  - [Quality Control](../references/quality-control.md) - Quality gates system
484
484
  - [Memory System](../references/memory-system.md) - Memory architecture
485
+
486
+ ## Control Plane by default
487
+
488
+ Bare `loki` and `loki dashboard` (also `start` or `open` with no other flags) open the Control Plane. If `~/.loki/control/instance.json` names a live loopback server whose `/health` reports `service=loki-control`, that URL is reused; otherwise `loki control serve` is started detached and its PID is recorded in `~/.loki/control/serve.pid`. With `LOKI_HEADLESS=1`, `LOKI_NO_BROWSER=1` or `--no-open` the URL is printed and no browser is opened. Without bun, Loki prints one line naming bun and uses the classic dashboard. Set `LOKI_CONTROL_DEFAULT=0` to always use the classic dashboard. Multi-repo `loki workspace` is on by default; `LOKI_WORKSPACES=0` disables it.
@@ -104,6 +104,7 @@ never the local JWKS. Exits: 0 VERIFIED, 1 TAMPERED, 2 UNCHECKED (kid does not
104
104
  match the key, or the key file is unusable), 3 UNSIGNED (a receipt with no
105
105
  signature is refused even with `--allow-unsigned`, because the caller asked for a
106
106
  signature check), 4 run outcome not verified (checked before UNSIGNED).
107
+ `--pubkey` may come before or after the run-id and may be written `--pubkey=FILE`; `--pubkey` with no file or an empty `--pubkey=`, a repeated `--pubkey`, an unknown option, or more than one run-id exits 2.
107
108
 
108
109
  An early draft spec listed `1=BLOCKED, 2=CONCERNS`. That ordering was rejected:
109
110
  it is not used anywhere, it has no consumers, and it inverts the
package/docs/slack.md CHANGED
@@ -1,9 +1,10 @@
1
- # Two-way Slack (preview)
1
+ # Two-way Slack
2
2
 
3
- Off by default. Set `LOKI_SLACK_INBOUND=1` to enable the inbound handler.
3
+ On by default. Set `LOKI_SLACK_INBOUND=0` to disable the inbound handler (`loki slack serve` then exits 2).
4
+
5
+ Without `SLACK_BOT_TOKEN` or `SLACK_SIGNING_SECRET`, `loki slack serve` prints one line naming both and exits 2 without binding a port.
4
6
 
5
7
  ```bash
6
- export LOKI_SLACK_INBOUND=1
7
8
  export SLACK_BOT_TOKEN=... # from env only, never stored by Loki
8
9
  export SLACK_SIGNING_SECRET=...
9
10
  loki slack serve --port 3000 # binds 127.0.0.1 unless --host is given
@@ -13,10 +14,17 @@ Point the Slack Events API request URL at this server through a tunnel or revers
13
14
 
14
15
  ## What it does
15
16
 
16
- - `@loki <issue ref or task text>` in a channel starts a Loki 10 run (the same entry as `loki "<task>"`) and replies in the thread with the run id.
17
+ - `@loki <issue ref or task text>` in a channel starts a Loki 10 run (it launches `bin/loki "<task>"`; the text must be at least two words and must not start with `-`, otherwise the bot replies asking you to describe the task, so a lone word like `reset` can never reach a legacy subcommand) and replies in the thread with the run id.
17
18
  - When the run ends BLOCKED, the question is posted in the same thread. A reply in that thread starts a follow-up run with your answer added to the original task (v10 has no in-place resume, BLOCKED is terminal).
19
+ - Runs are launched through the repo `bin/loki` with the two Slack secrets removed from the child environment and `LOKI_NO_BROWSER=1` set. A failed Slack post logs one redacted line to stderr.
18
20
  - Every request is verified with the Slack signature (HMAC sha256 over `v0:timestamp:body`, constant-time compare, timestamps older than 5 minutes are rejected). Retried events are deduplicated.
19
21
 
22
+ ## Who can start a run
23
+
24
+ Set `LOKI_SLACK_ALLOWED_USERS` to a comma-separated list of Slack user IDs (for example `U012ABC,U034DEF`). A mention or a BLOCKED-thread answer from any other user is rejected with a short thread reply and nothing is spawned. When it is unset, anyone who can mention the bot can start a run, and `loki slack serve` prints one startup line saying so.
25
+
26
+ Limits: task text over 64 KB is rejected with "task too long" and no run starts. If a run cannot be launched, the thread gets a generic message and the detail (which may include local paths) is logged to stderr only. `--port` must be a whole number (an empty value exits 2).
27
+
20
28
  ## App manifest scopes
21
29
 
22
30
  Bot token scopes: `app_mentions:read`, `chat:write`, `channels:history` (and `groups:history` for private channels, `im:history` for DMs).
@@ -0,0 +1,27 @@
1
+ # Jira and Linear intake
2
+
3
+ Loki 10 can start a run from a Jira or Linear issue. This is intake only: the issue is fetched once, before any model runs, and normalized into the same `issue.json` GitHub intake writes (with a `source` field of `jira` or `linear`). Loki does not write back to the tracker or sync status.
4
+
5
+ ```bash
6
+ loki jira:PROJ-123
7
+ loki linear:ENG-42
8
+ ```
9
+
10
+ ## Credentials (environment only)
11
+
12
+ - Jira: `JIRA_EMAIL`, `JIRA_API_TOKEN` and `JIRA_BASE_URL` (for example `https://acme.atlassian.net`). `JIRA_BASE_URL` may be omitted when you pass an `atlassian.net` browse URL.
13
+ - Linear: `LINEAR_API_KEY`.
14
+
15
+ A missing variable stops the run with an error that names it. A Linear HTTP 401, or a GraphQL response with errors and no data, also names `LINEAR_API_KEY`.
16
+
17
+ ## Self-hosted Jira
18
+
19
+ `<JIRA_BASE_URL>/browse/KEY` is accepted as a ref only when its origin equals the origin of `JIRA_BASE_URL`. A browse URL on any other host is not treated as a tracker ref.
20
+
21
+ ## Kill switch
22
+
23
+ `LOKI_TRACKER_INTAKE=0` disables Jira and Linear refs entirely; they are then treated as ordinary text.
24
+
25
+ ## Safety
26
+
27
+ Issue text is untrusted input. It is passed to the run as data and never grants tool authority.