loki-mode 11.76.0 → 11.78.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 (103) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/check_author.py +146 -0
  4. package/autonomy/lib/claude-flags.sh +5 -1
  5. package/autonomy/lib/mcp-config.sh +41 -1
  6. package/autonomy/lib/proof-analytics-props.py +115 -6
  7. package/autonomy/lib/proof-verify.py +22 -0
  8. package/autonomy/lib/telemetry-schema.json +52 -0
  9. package/autonomy/loki +398 -7
  10. package/autonomy/telemetry.sh +70 -31
  11. package/completions/_loki +19 -0
  12. package/completions/loki.bash +17 -1
  13. package/dashboard/__init__.py +1 -1
  14. package/dashboard/api_start.py +2 -10
  15. package/dashboard/secure_tmp.py +92 -0
  16. package/dashboard/telemetry.py +81 -15
  17. package/docs/ARCHITECTURE.md +6 -4
  18. package/docs/{v10/CLI-MODERN.md → CLI-INVENTORY.md} +6 -6
  19. package/docs/CLI-REFERENCE.md +3 -2
  20. package/docs/COMPARISON.md +6 -8
  21. package/docs/COMPONENTS.md +1 -1
  22. package/docs/INSTALLATION.md +1 -1
  23. package/docs/PRIVACY.md +47 -11
  24. package/docs/V10-RESEARCH.md +1 -1
  25. package/docs/architecture/ADR-001-runtime-migration.md +7 -7
  26. package/docs/cursor-comparison.md +1 -1
  27. package/docs/dev/architecture-reference.md +7 -2
  28. package/docs/dev/project-structure.md +2 -1
  29. package/docs/image-provenance.md +32 -7
  30. package/docs/slack.md +2 -0
  31. package/docs/v11/COST-HALF.md +2 -1
  32. package/loki-ts/dist/loki.js +1744 -1604
  33. package/loki-ts/package.json +2 -2
  34. package/mcp/__init__.py +1 -1
  35. package/mcp/requirements.txt +1 -1
  36. package/package.json +3 -3
  37. package/packages/control-plane/dist/server.js +20045 -19119
  38. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  39. package/providers/aider.sh +7 -0
  40. package/providers/claude.sh +8 -1
  41. package/providers/cline.sh +3 -0
  42. package/providers/loader.sh +5 -0
  43. package/providers/model_catalog.json +3 -0
  44. package/providers/models.sh +59 -0
  45. package/providers/opencode.sh +9 -3
  46. package/skills/providers.md +1 -0
  47. package/web-app/dist/assets/{AdminPage-DoGPqZIa.js → AdminPage-BPF2Xehf.js} +1 -1
  48. package/web-app/dist/assets/{Avatar-Dyf7xJsz.js → Avatar-CSW9BhBU.js} +1 -1
  49. package/web-app/dist/assets/{Badge-Cm4dADew.js → Badge-wgm79wim.js} +1 -1
  50. package/web-app/dist/assets/{Button-LGxjldY5.js → Button-BcIAcPKr.js} +1 -1
  51. package/web-app/dist/assets/{CockpitPage-DuC9g48J.js → CockpitPage-BFi-0Kwj.js} +1 -1
  52. package/web-app/dist/assets/{ComparePage-DuTrmedK.js → ComparePage-B7Wibrfn.js} +1 -1
  53. package/web-app/dist/assets/{ErrorBoundary-B0f7NEiT.js → ErrorBoundary-BYyOVh2c.js} +1 -1
  54. package/web-app/dist/assets/{EvidenceReceiptPanel-DsE8B9Hw.js → EvidenceReceiptPanel-C9QcrjNl.js} +1 -1
  55. package/web-app/dist/assets/{GitHubIssuesPanel-aYflOqd4.js → GitHubIssuesPanel-CWbuBxVZ.js} +1 -1
  56. package/web-app/dist/assets/{GitHubPRsPanel-CCJGck6c.js → GitHubPRsPanel-qcBHK615.js} +1 -1
  57. package/web-app/dist/assets/{HomePage-BJU3PVkJ.js → HomePage-B1mYFU4t.js} +2 -2
  58. package/web-app/dist/assets/{LoginPage-UzYoF5CJ.js → LoginPage-CmIO1xL7.js} +1 -1
  59. package/web-app/dist/assets/{MagicPage-5X3hQ7lt.js → MagicPage-L7UP4qKP.js} +1 -1
  60. package/web-app/dist/assets/{MetricsPage-CFsTNyOu.js → MetricsPage-DVPCjN1U.js} +1 -1
  61. package/web-app/dist/assets/{NotFoundPage-Ba0nMlqG.js → NotFoundPage-D3XZmCH5.js} +1 -1
  62. package/web-app/dist/assets/{ProjectPage-fvU-EYZb.js → ProjectPage-BlaMzlVi.js} +3 -3
  63. package/web-app/dist/assets/{ProjectsPage-CVEKCorG.js → ProjectsPage-DN7W28Zq.js} +1 -1
  64. package/web-app/dist/assets/{SettingsPage-JGbsNmqD.js → SettingsPage-2u9ODYB5.js} +1 -1
  65. package/web-app/dist/assets/{ShowcasePage-Dr2c_V-y.js → ShowcasePage-CoKuTfab.js} +1 -1
  66. package/web-app/dist/assets/{SystemSettingsPage-D9dCc4Ir.js → SystemSettingsPage-Crhj2XAU.js} +1 -1
  67. package/web-app/dist/assets/{TeamsPage-B56u-bod.js → TeamsPage-DehMgnKh.js} +1 -1
  68. package/web-app/dist/assets/{TemplatesPage-CUwqj6ge.js → TemplatesPage-DGtwfZgU.js} +1 -1
  69. package/web-app/dist/assets/{TerminalOutput-Cm920SDi.js → TerminalOutput-7HZRtXoC.js} +1 -1
  70. package/web-app/dist/assets/{activity-DDF6juD-.js → activity-DAp5wswo.js} +1 -1
  71. package/web-app/dist/assets/{bell-7MW_oAcb.js → bell-Ncvnrz7d.js} +1 -1
  72. package/web-app/dist/assets/{bot-b63uz2Ov.js → bot-BWob3K3E.js} +1 -1
  73. package/web-app/dist/assets/{check-2oBMAALa.js → check-DReWA2vm.js} +1 -1
  74. package/web-app/dist/assets/{chevron-left-UuhI6pbU.js → chevron-left-CBJpzVU1.js} +1 -1
  75. package/web-app/dist/assets/{circle-alert-BikRzQA7.js → circle-alert-2c_vs5PW.js} +1 -1
  76. package/web-app/dist/assets/{clock-BdkTaAgO.js → clock-BrS_ZKKi.js} +1 -1
  77. package/web-app/dist/assets/{cloud-oKg24kTO.js → cloud-Czvuo51R.js} +1 -1
  78. package/web-app/dist/assets/{code-xml-CpDmdnYQ.js → code-xml-BWEqJ2I8.js} +1 -1
  79. package/web-app/dist/assets/{database-BT133u2w.js → database-CJ6M-oK-.js} +1 -1
  80. package/web-app/dist/assets/{dollar-sign-khUEgmXm.js → dollar-sign-DSObKmVv.js} +1 -1
  81. package/web-app/dist/assets/{file-code-corner-_TlPqN5g.js → file-code-corner-CVEW-gF5.js} +1 -1
  82. package/web-app/dist/assets/{file-plus-B5UT0SZU.js → file-plus-CAK0I0tE.js} +1 -1
  83. package/web-app/dist/assets/{globe-Chhrgg2K.js → globe-DeUgcsSZ.js} +1 -1
  84. package/web-app/dist/assets/{hammer-DJ6Z4tiA.js → hammer-B74PyjS0.js} +1 -1
  85. package/web-app/dist/assets/{index-BprNVRFx.js → index-DW3_HRqp.js} +3 -3
  86. package/web-app/dist/assets/{layers-CtYWuenz.js → layers-Dw7kaGyJ.js} +1 -1
  87. package/web-app/dist/assets/{loader-circle-BRxnh_Va.js → loader-circle-B6xzlAjw.js} +1 -1
  88. package/web-app/dist/assets/{lock-btaZm2bN.js → lock-5ncCPzIG.js} +1 -1
  89. package/web-app/dist/assets/{package-BFU4d6gL.js → package-x59_dTx0.js} +1 -1
  90. package/web-app/dist/assets/{plus-BogXrZQi.js → plus-BcVBzjpS.js} +1 -1
  91. package/web-app/dist/assets/{refresh-cw-CsdQkR5n.js → refresh-cw-CNrJhks6.js} +1 -1
  92. package/web-app/dist/assets/{rotate-ccw-BeLxChlE.js → rotate-ccw-BpmS_ezI.js} +1 -1
  93. package/web-app/dist/assets/{scroll-text-DAXSJc0i.js → scroll-text-CwBYNyoj.js} +1 -1
  94. package/web-app/dist/assets/{server-Cf_yJUqU.js → server-C4hnxkk6.js} +1 -1
  95. package/web-app/dist/assets/{shield-alert-B2gh1hM3.js → shield-alert-CSVUyWCM.js} +1 -1
  96. package/web-app/dist/assets/{thumbs-up-DxYjJ1v8.js → thumbs-up-DEU9fmDX.js} +1 -1
  97. package/web-app/dist/assets/{trash-2-BaAsSOYk.js → trash-2-DJKMevAm.js} +1 -1
  98. package/web-app/dist/assets/{trending-down-Kc-Tady7.js → trending-down-Dq3wAM59.js} +1 -1
  99. package/web-app/dist/assets/{trending-up-BDsns895.js → trending-up-DribptN4.js} +1 -1
  100. package/web-app/dist/assets/{upload-CCJzLwO9.js → upload-DUlZxsH1.js} +1 -1
  101. package/web-app/dist/assets/{usePolling-4aqc_A7F.js → usePolling-PecqVZ0k.js} +1 -1
  102. package/web-app/dist/assets/{user-BzoHXLDe.js → user-BcLYLDVU.js} +1 -1
  103. package/web-app/dist/index.html +1 -1
@@ -52,6 +52,12 @@ _loki_telemetry_auto_off() {
52
52
  return 1
53
53
  }
54
54
 
55
+ # BASH_SOURCE is empty when sourced under zsh: skip the lookup rather than
56
+ # resolve dirname "" to the current directory.
57
+ _LOKI_TELEMETRY_SELF_DIR=""
58
+ if [ -n "${BASH_SOURCE[0]:-}" ]; then
59
+ _LOKI_TELEMETRY_SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd -P)" || _LOKI_TELEMETRY_SELF_DIR=""
60
+ fi
55
61
  LOKI_POSTHOG_HOST="${LOKI_TELEMETRY_ENDPOINT:-https://us.i.posthog.com}"
56
62
  LOKI_POSTHOG_KEY="phc_ya0vGBru41AJWtGNfZZ8H9W4yjoZy4KON0nnayS7s87"
57
63
 
@@ -208,48 +214,81 @@ _loki_known_blocker() {
208
214
  esac
209
215
  }
210
216
 
217
+ # _loki_os_family: raw `uname -s` collapsed to a fixed enum (MINGW64_NT-10.0-19045
218
+ # and friends are one family, not one value per Windows build).
219
+ _loki_os_family() {
220
+ case "${1:-}" in
221
+ Linux*) printf 'linux' ;;
222
+ Darwin*) printf 'darwin' ;;
223
+ MINGW*|MSYS*|CYGWIN*|Windows*) printf 'windows' ;;
224
+ *) printf 'other' ;;
225
+ esac
226
+ }
227
+
228
+ _loki_arch_family() {
229
+ case "${1:-}" in
230
+ x86_64|amd64|x64) printf 'x64' ;;
231
+ arm64|aarch64) printf 'arm64' ;;
232
+ *) printf 'other' ;;
233
+ esac
234
+ }
235
+
236
+ # _loki_traffic: who is driving this process, as the enum user | ci | internal,
237
+ # so analytics can filter bots and our own seats. internal wins, then ci (CI env
238
+ # vars or a non-interactive session), else user. Mirrors _traffic in
239
+ # dashboard/telemetry.py.
240
+ _loki_traffic() {
241
+ if [ "${LOKI_INTERNAL:-}" = "1" ] || [ "${LOKI_SEAT_TICK:-}" = "1" ] || [ -n "${LOKI_SEAT:-}" ] || [ -n "${LOKI_COMPANY_SEAT:-}" ]; then
242
+ printf 'internal'; return
243
+ fi
244
+ if [ -n "${CI:-}" ] || [ -n "${GITHUB_ACTIONS:-}" ] || [ -n "${GITLAB_CI:-}" ] \
245
+ || [ -n "${BUILDKITE:-}" ] || [ -n "${JENKINS_URL:-}" ] \
246
+ || [ -n "${TEAMCITY_VERSION:-}" ] || [ -n "${CONTINUOUS_INTEGRATION:-}" ]; then
247
+ printf 'ci'; return
248
+ fi
249
+ if [ -n "${LOKI_TTY_INTERACTIVE:-}" ]; then
250
+ [ "${LOKI_TTY_INTERACTIVE}" = "1" ] && printf 'user' || printf 'ci'
251
+ return
252
+ fi
253
+ if [ ! -t 1 ] && [ ! -t 0 ]; then printf 'ci'; return; fi
254
+ printf 'user'
255
+ }
256
+
257
+ # loki_telemetry <event> [key=value...]: the ONE send path of the bash senders.
258
+ # Order is load-bearing: the opt-out gate runs before anything else, so with
259
+ # LOKI_TELEMETRY=off or DO_NOT_TRACK=1 no python, no curl and no network code is
260
+ # reached. Then every key and value is filtered through
261
+ # autonomy/lib/telemetry-schema.json (unlisted event: nothing is sent; unknown
262
+ # key or out-of-enum value: dropped). Callers' pairs are untrusted by design;
263
+ # the trusted base properties are appended last so a caller cannot override them.
211
264
  loki_telemetry() {
212
265
  _loki_telemetry_enabled || return 0
213
- local event="$1"; shift
266
+ local event="${1:-}"; shift
267
+ [ -n "$event" ] || return 0
268
+ # The schema sits next to this file; prefer that over SCRIPT_DIR, which some
269
+ # callers leave unset when they only source this file.
270
+ local _lib="${_LOKI_TELEMETRY_SELF_DIR:-}/lib"
271
+ [ -f "$_lib/telemetry-schema.json" ] || _lib="${SCRIPT_DIR:-${SKILL_DIR:-}}/lib"
272
+ [ -f "$_lib/telemetry-schema.json" ] && [ -f "$_lib/proof-analytics-props.py" ] || return 0
214
273
  local distinct_id
215
274
  distinct_id=$(_loki_telemetry_id 2>/dev/null) || return 0
216
275
  local version
217
- version=$(cat "${SCRIPT_DIR:-${SKILL_DIR:-}}/VERSION" 2>/dev/null || cat "${SCRIPT_DIR:-${SKILL_DIR:-}}/../VERSION" 2>/dev/null || echo "unknown")
276
+ version=$(cat "${SCRIPT_DIR:-${SKILL_DIR:-}}/VERSION" 2>/dev/null || cat "${SCRIPT_DIR:-${SKILL_DIR:-}}/../VERSION" 2>/dev/null || cat "${_LOKI_TELEMETRY_SELF_DIR:-}/../VERSION" 2>/dev/null || echo "unknown")
218
277
  version=$(echo "$version" | tr -d '[:space:]')
219
278
  local channel
220
279
  channel=$(_loki_detect_channel 2>/dev/null || echo "unknown")
221
- local os_name arch
222
- os_name=$(uname -s 2>/dev/null || echo "unknown")
223
- arch=$(uname -m 2>/dev/null || echo "unknown")
280
+ local os_family arch
281
+ os_family=$(_loki_os_family "$(uname -s 2>/dev/null)")
282
+ arch=$(_loki_arch_family "$(uname -m 2>/dev/null)")
224
283
 
225
- # Build JSON payload safely using Python to prevent injection.
226
284
  # The extra key=value pairs are forwarded as "$@" -- one argv slot per pair,
227
- # QUOTED. They used to be joined into a single space-delimited string and
228
- # then re-split by an unquoted expansion, which broke two ways: a value
229
- # containing a space was truncated (`prd=my file.md` -> `prd=my`, rest
230
- # dropped) or, if a fragment itself contained `=`, forged an extra PostHog
231
- # property KEY; and the unquoted expansion also globbed, so `spec=*.md`
232
- # expanded against the cwd and injected file names into the payload. Callers
233
- # already pass each pair as its own quoted word, so "$@" is the array and no
234
- # caller signature changes.
285
+ # QUOTED, so a value with spaces or glob characters is neither split nor
286
+ # expanded. The python side splits on the first "=" only.
235
287
  local payload
236
- payload=$(python3 -c "
237
- import json, sys
238
- props = {'os': sys.argv[1], 'arch': sys.argv[2], 'version': sys.argv[3], 'channel': sys.argv[4]}
239
- for arg in sys.argv[5:]:
240
- if '=' in arg:
241
- k, v = arg.split('=', 1)
242
- props[k] = v
243
- print(json.dumps({'api_key': '$LOKI_POSTHOG_KEY', 'event': sys.argv[5] if len(sys.argv) > 5 else '', 'distinct_id': '$distinct_id', 'properties': props}))
244
- " "$os_name" "$arch" "$version" "$channel" "$@" 2>/dev/null) || return 0
245
- # Re-inject event and distinct_id properly
246
- payload=$(python3 -c "
247
- import json, sys
248
- d = json.loads(sys.argv[1])
249
- d['event'] = sys.argv[2]
250
- d['distinct_id'] = sys.argv[3]
251
- print(json.dumps(d))
252
- " "$payload" "$event" "$distinct_id" 2>/dev/null) || return 0
288
+ payload=$(python3 "$_lib/proof-analytics-props.py" --payload "$event" "$distinct_id" "$LOKI_POSTHOG_KEY" \
289
+ "$@" "os_family=$os_family" "arch=$arch" "version=$version" "channel=$channel" \
290
+ "traffic=$(_loki_traffic)" 2>/dev/null) || return 0
291
+ [ -n "$payload" ] || return 0
253
292
 
254
293
  (curl -sS --max-time 3 -X POST "${LOKI_POSTHOG_HOST}/capture/" \
255
294
  -H "Content-Type: application/json" \
package/completions/_loki CHANGED
@@ -74,6 +74,15 @@ function _loki {
74
74
  agent)
75
75
  _loki_agent
76
76
  ;;
77
+ cost)
78
+ _arguments \
79
+ '--json[Output machine-readable JSON]' \
80
+ '--last[Show only the last N runs]:count:' \
81
+ '--month[Only runs generated in that month]:month (YYYY-MM):' \
82
+ '--csv[One CSV row per run]' \
83
+ '--detail[Per-iteration cache and trend breakdown]' \
84
+ '--help[Show help]'
85
+ ;;
77
86
  estimate)
78
87
  _arguments \
79
88
  '--json[Output machine-readable JSON]' \
@@ -86,6 +95,7 @@ function _loki {
86
95
  '--json[Output machine-readable JSON]' \
87
96
  '--badge[Print verified deploy badge]' \
88
97
  '--probe[Re-probe last deploy health URL]' \
98
+ '--follow[Print one line per run event until the run ends]' \
89
99
  '--help[Show help]'
90
100
  ;;
91
101
  doctor)
@@ -203,6 +213,15 @@ function _loki {
203
213
  code)
204
214
  _loki_code
205
215
  ;;
216
+ github)
217
+ _arguments \
218
+ '1:subcommand:(status sync export pr publish protect)' \
219
+ '--dry-run[protect: show the branch-protection read, change nothing]' \
220
+ '--off[protect: restore the saved branch protection]' \
221
+ '--public[publish: create a public repository]' \
222
+ '--private[publish: create a private repository]' \
223
+ '--dir[publish: project directory]:directory:_directories'
224
+ ;;
206
225
  deploy)
207
226
  _arguments \
208
227
  '--dir[Project directory to scan]:directory:_directories' \
@@ -125,6 +125,13 @@ _loki_completion() {
125
125
  COMPREPLY=( $(compgen -W "${syslog_cmds}" -- "$cur") )
126
126
  ;;
127
127
 
128
+ cost)
129
+ if [[ "$cur" == -* ]]; then
130
+ COMPREPLY=( $(compgen -W "--json --last --month --csv --detail --help" -- "$cur") )
131
+ return 0
132
+ fi
133
+ ;;
134
+
128
135
  estimate)
129
136
  if [[ "$cur" == -* ]]; then
130
137
  COMPREPLY=( $(compgen -W "--json --model --provider --help" -- "$cur") )
@@ -134,7 +141,7 @@ _loki_completion() {
134
141
 
135
142
  status)
136
143
  if [[ "$cur" == -* ]]; then
137
- COMPREPLY=( $(compgen -W "--json --badge --probe --help" -- "$cur") )
144
+ COMPREPLY=( $(compgen -W "--json --badge --probe --follow --help" -- "$cur") )
138
145
  return 0
139
146
  fi
140
147
  ;;
@@ -152,6 +159,15 @@ _loki_completion() {
152
159
  fi
153
160
  ;;
154
161
 
162
+ github)
163
+ if [[ "$cur" == -* ]]; then
164
+ COMPREPLY=( $(compgen -W "--dry-run --off --public --private --dir --help" -- "$cur") )
165
+ return 0
166
+ fi
167
+ COMPREPLY=( $(compgen -W "status sync export pr publish protect" -- "$cur") )
168
+ return 0
169
+ ;;
170
+
155
171
  deploy)
156
172
  if [[ "$cur" == -* ]]; then
157
173
  COMPREPLY=( $(compgen -W "--execute --destination --canary --env-file --domain --health-url --health-match --auto-rollback --dir --no-clip --allow-unanchored --help" -- "$cur") )
@@ -7,6 +7,6 @@ Modules:
7
7
  control: Session control helpers (status, atomic writes, origin check)
8
8
  """
9
9
 
10
- __version__ = "11.76.0"
10
+ __version__ = "11.78.0"
11
11
 
12
12
  __all__ = ["__version__"]
@@ -32,7 +32,7 @@ from typing import Optional
32
32
  from fastapi import APIRouter, Depends, HTTPException
33
33
  from pydantic import BaseModel
34
34
 
35
- from . import auth
35
+ from . import auth, secure_tmp
36
36
 
37
37
  logger = logging.getLogger(__name__)
38
38
  router = APIRouter(tags=["start"])
@@ -85,15 +85,7 @@ def _creds_dir() -> Path:
85
85
 
86
86
 
87
87
  def _write_secret(name: str, value: str) -> None:
88
- dest = _secret_path(name)
89
- tmp = dest.with_name(".%s.%d.tmp" % (dest.name, os.getpid()))
90
- fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
91
- try:
92
- os.fchmod(fd, 0o600)
93
- os.write(fd, (value + "\n").encode())
94
- finally:
95
- os.close(fd)
96
- os.replace(tmp, dest)
88
+ secure_tmp.write_secret_atomic(_secret_path(name), (value + "\n").encode())
97
89
 
98
90
 
99
91
  def _read_secret(name: str) -> Optional[str]:
@@ -0,0 +1,92 @@
1
+ """Shared write-temp-then-rename for secrets, plus the stale-temp sweep.
2
+
3
+ One-line justification for a new file: no existing dashboard module owns
4
+ secret-temp creation and cleanup (build_supervisor._atomic_write is private to
5
+ execution state and does not sweep). A writer killed between the temp write and
6
+ the rename would otherwise leave the secret on disk forever.
7
+
8
+ Mirrors sweepStaleTmp / writeExclusiveTmp in loki-ts/src/util/atomic.ts and
9
+ _loki_mcp_sweep_stale_tmp in autonomy/lib/mcp-config.sh.
10
+ """
11
+ import os
12
+ import re
13
+ import stat
14
+ import time
15
+ from pathlib import Path
16
+
17
+ STALE_SECONDS = 60 * 60
18
+
19
+
20
+ def _pid_alive(pid: int) -> bool:
21
+ try:
22
+ os.kill(pid, 0)
23
+ except ProcessLookupError:
24
+ return False
25
+ except PermissionError:
26
+ return True # EPERM: the process exists, owned by someone else
27
+ except (OSError, OverflowError, ValueError):
28
+ return False
29
+ return True
30
+
31
+
32
+ def sweep_stale_tmp(directory, pattern: str) -> None:
33
+ """Remove temps left by a dead writer.
34
+
35
+ `pattern` is fullmatched against each name in `directory` (never recursive)
36
+ and its first group is the writer pid. An entry is removed only when it is a
37
+ regular file (not a symlink), owned by this uid, not ours, and its pid is
38
+ dead or its mtime is over 60 minutes (pid reuse). A live writer's temp stays.
39
+ """
40
+ rx = re.compile(pattern, re.DOTALL)
41
+ uid = os.getuid()
42
+ try:
43
+ entries = list(os.scandir(directory))
44
+ except OSError:
45
+ return
46
+ for entry in entries:
47
+ m = rx.fullmatch(entry.name)
48
+ if not m:
49
+ continue
50
+ pid = int(m.group(1))
51
+ if pid == os.getpid():
52
+ continue
53
+ if pid > 2**31 - 1:
54
+ continue # not a pid we could own; never signal or remove it
55
+ try:
56
+ st = os.lstat(entry.path)
57
+ if not stat.S_ISREG(st.st_mode) or st.st_uid != uid:
58
+ continue
59
+ if _pid_alive(pid) and time.time() - st.st_mtime <= STALE_SECONDS:
60
+ continue
61
+ os.unlink(entry.path)
62
+ except OSError:
63
+ continue
64
+
65
+
66
+ def write_secret_atomic(dest, data: bytes, mode: int = 0o600) -> None:
67
+ """Write `data` to `dest` via `.<name>.<pid>.tmp` then rename.
68
+
69
+ The temp is created O_CREAT|O_EXCL|O_NOFOLLOW so a planted symlink or file
70
+ is never written through. Stale temps of dead writers are swept first.
71
+ """
72
+ dest = Path(dest)
73
+ sweep_stale_tmp(dest.parent, r"\.%s\.(\d+)\.tmp" % re.escape(dest.name))
74
+ tmp = dest.with_name(".%s.%d.tmp" % (dest.name, os.getpid()))
75
+ try:
76
+ os.unlink(tmp) # our own pid left over from a reused pid
77
+ except FileNotFoundError:
78
+ pass
79
+ fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, mode)
80
+ try:
81
+ try:
82
+ os.fchmod(fd, mode)
83
+ os.write(fd, data)
84
+ finally:
85
+ os.close(fd)
86
+ os.replace(tmp, dest)
87
+ except BaseException:
88
+ try:
89
+ os.unlink(tmp)
90
+ except OSError:
91
+ pass
92
+ raise
@@ -13,6 +13,7 @@ Force-on: LOKI_TELEMETRY=on OR ~/.loki/config: TELEMETRY_ENABLED=true
13
13
  All calls are fire-and-forget, silent on failure, non-blocking.
14
14
  """
15
15
 
16
+ import importlib.util
16
17
  import json
17
18
  import os
18
19
  import platform
@@ -153,27 +154,92 @@ def _get_version():
153
154
  return "unknown"
154
155
 
155
156
 
157
+ _LIB = Path(__file__).resolve().parent.parent / "autonomy" / "lib"
158
+
159
+
160
+ def _load_schema_module():
161
+ """The one filter and schema live in autonomy/lib (shared with the bash
162
+ sender). Missing files mean nothing is sent: fail closed."""
163
+ spec = importlib.util.spec_from_file_location(
164
+ "loki_telemetry_schema", str(_LIB / "proof-analytics-props.py"))
165
+ mod = importlib.util.module_from_spec(spec)
166
+ spec.loader.exec_module(mod)
167
+ return mod
168
+
169
+
170
+ def _os_family():
171
+ name = platform.system().lower()
172
+ if name in ("linux", "darwin"):
173
+ return name
174
+ if name.startswith(("windows", "mingw", "msys", "cygwin")):
175
+ return "windows"
176
+ return "other"
177
+
178
+
179
+ def _arch_family():
180
+ m = platform.machine().lower()
181
+ if m in ("x86_64", "amd64", "x64"):
182
+ return "x64"
183
+ if m in ("arm64", "aarch64"):
184
+ return "arm64"
185
+ return "other"
186
+
187
+
188
+ def _traffic():
189
+ """user | ci | internal. Mirrors _loki_traffic in autonomy/telemetry.sh."""
190
+ env = os.environ
191
+ if env.get("LOKI_INTERNAL") == "1" or env.get("LOKI_SEAT_TICK") == "1" or env.get("LOKI_SEAT") or env.get("LOKI_COMPANY_SEAT"):
192
+ return "internal"
193
+ for var in ("CI", "GITHUB_ACTIONS", "GITLAB_CI", "BUILDKITE", "JENKINS_URL",
194
+ "TEAMCITY_VERSION", "CONTINUOUS_INTEGRATION"):
195
+ if env.get(var):
196
+ return "ci"
197
+ sig = env.get("LOKI_TTY_INTERACTIVE")
198
+ if sig:
199
+ return "user" if sig == "1" else "ci"
200
+ try:
201
+ if not sys.stdout.isatty() and not sys.stdin.isatty():
202
+ return "ci"
203
+ except Exception:
204
+ pass
205
+ return "user"
206
+
207
+
208
+ def build_payload(event, properties=None):
209
+ """Return the schema-filtered capture body, or None when the event is not
210
+ listed. Caller properties are untrusted; trusted base properties win."""
211
+ mod = _load_schema_module()
212
+ raw = dict(properties or {})
213
+ raw.update({
214
+ "os_family": _os_family(),
215
+ "arch": _arch_family(),
216
+ "version": _get_version(),
217
+ "channel": _detect_channel(),
218
+ "traffic": _traffic(),
219
+ })
220
+ props = mod.sanitize(mod.load_schema(), event, raw)
221
+ if props is None:
222
+ return None
223
+ return {
224
+ "api_key": _POSTHOG_KEY,
225
+ "event": event,
226
+ "distinct_id": _get_distinct_id(),
227
+ "properties": props,
228
+ }
229
+
230
+
156
231
  def send_telemetry(event, properties=None):
157
- """Send anonymous telemetry event. Non-blocking, silent on failure."""
232
+ """Send anonymous telemetry event. Non-blocking, silent on failure.
233
+ The opt-out gate runs first, before any schema or network code."""
158
234
  if not _is_enabled():
159
235
  return
160
236
 
161
237
  def _send():
162
238
  try:
163
- props = {
164
- "os": platform.system(),
165
- "arch": platform.machine(),
166
- "version": _get_version(),
167
- "channel": _detect_channel(),
168
- }
169
- if properties:
170
- props.update(properties)
171
- payload = json.dumps({
172
- "api_key": _POSTHOG_KEY,
173
- "event": event,
174
- "distinct_id": _get_distinct_id(),
175
- "properties": props,
176
- }).encode()
239
+ body = build_payload(event, properties)
240
+ if body is None:
241
+ return
242
+ payload = json.dumps(body).encode()
177
243
  req = Request(
178
244
  f"{_POSTHOG_HOST}/capture/",
179
245
  data=payload,
@@ -244,8 +244,9 @@ templates/ 21 PRD templates (saas, cli, discord-bot, ...)
244
244
  agents/ Agent registry: types.json, hub install, managed registry
245
245
  benchmarks/ SWE-bench and HumanEval harnesses
246
246
  plugins/ Claude Code plugin packaging
247
- .github/workflows/ CI: tests, release, sbom,
248
- security-audit, post-release-smoke, ...
247
+ .github/workflows/ release.yml, converge-dist-tags.yml,
248
+ sdk-freshness.yml (the pre-push gate is
249
+ scripts/local-ci.sh, run locally)
249
250
  SKILL.md Slim core skill (progressive disclosure entry)
250
251
  CLAUDE.md Project + agent operating instructions
251
252
  VERSION / package.json Single source of version truth
@@ -324,8 +325,9 @@ docker/ Container distribution (Dockerfiles, compose)
324
325
  TS providers module.
325
326
  - **Docker / Docker Compose** for distribution and for the multi-service,
326
327
  12-factor stacks Loki generates (web + database + cache with healthchecks).
327
- - **GitHub Actions** for CI/CD: test suites, SBOM and security audits,
328
- multi-channel release (npm, Docker, Homebrew), and post-release smoke tests.
328
+ - **GitHub Actions** (`.github/workflows/`): `release.yml` (multi-channel
329
+ release), `converge-dist-tags.yml` and `sdk-freshness.yml`. The test suites
330
+ run locally through `scripts/local-ci.sh`, not in a workflow.
329
331
 
330
332
  ---
331
333
 
@@ -11,7 +11,7 @@ Classes: KEEP-MODERN (v10-native or engine-neutral), UPDATE (live but needs mode
11
11
  - DROP-LEGACY: 31
12
12
  - DELETE: 14
13
13
  - Total rows: 144
14
- - Registry paths (commands and nested subcommands): 212
14
+ - Registry paths (commands and nested subcommands): 214
15
15
 
16
16
  ## Commands
17
17
 
@@ -169,19 +169,19 @@ Classes: KEEP-MODERN (v10-native or engine-neutral), UPDATE (live but needs mode
169
169
  - compliance: 1
170
170
  - config: 4
171
171
  - context: 5
172
- - cost: 6
172
+ - cost: 7
173
173
  - deploy: 1
174
174
  - docs: 4
175
175
  - enterprise: 2
176
176
  - explain: 5
177
- - export: 6
178
- - github: 6
177
+ - export: 7
178
+ - github: 7
179
179
  - import: 1
180
180
  - init: 21
181
181
  - issue: 2
182
182
  - magic: 12
183
183
  - memory: 7
184
- - metrics: 6
184
+ - metrics: 7
185
185
  - modernize: 3
186
186
  - next: 1
187
187
  - notify: 6
@@ -198,7 +198,7 @@ Classes: KEEP-MODERN (v10-native or engine-neutral), UPDATE (live but needs mode
198
198
  - secrets: 3
199
199
  - secure: 3
200
200
  - setup-skill: 1
201
- - share: 6
201
+ - share: 7
202
202
  - ship: 1
203
203
  - syslog: 3
204
204
  - telemetry: 1
@@ -1,7 +1,7 @@
1
1
  # CLI reference
2
2
 
3
- Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.76.0. Do not edit by hand.
4
- Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for the full inventory.
3
+ Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.78.0. Do not edit by hand.
4
+ Hidden and legacy-only commands are omitted; see `docs/CLI-INVENTORY.md` for the full inventory.
5
5
 
6
6
  | Command | Description |
7
7
  | --- | --- |
@@ -144,6 +144,7 @@ Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for th
144
144
  | `loki assets export` | Export shareable assets |
145
145
  | `loki export` | Export session data |
146
146
  | `loki notify` | Notifications |
147
+ | `loki notify desktop` | Send a desktop notification (macOS or Linux notify-send) |
147
148
  | `loki tour` | See a sample result |
148
149
  | `loki wow` | Tiny real task, or the offline sample |
149
150
  | `loki factory` | loki start with the autonomy policy printed |
@@ -31,7 +31,7 @@
31
31
 
32
32
  | Feature | **Loki Mode** | **Devin** | **Codex** | **Cursor** | **Kiro** | **Antigravity** | **Amazon Q** | **OpenCode** |
33
33
  |---------|--------------|-----------|-----------|------------|----------|-----------------|--------------|--------------|
34
- | **Multi-Agent** | 41 prompt-defined agent roles in 8 domains adopted per phase; parallel review council + optional worktree streams (Claude), sequential elsewhere | Single | Single | Up to 8 parallel | Background | Manager Surface | Multiple types | 4 built-in |
34
+ | **Multi-Agent** | Role inventory: 41 prompt-defined roles in 8 domains (`agents/types.json`), adopted per phase; parallel review council + optional worktree streams (Claude), sequential elsewhere | Single | Single | Up to 8 parallel | Background | Manager Surface | Multiple types | 4 built-in |
35
35
  | **Orchestration** | Full orchestrator | N/A | N/A | Git worktree | Hooks | Manager view | Workflow | Subagents |
36
36
  | **Parallel Exec** | 10+ Haiku, 4 impl (worktree) | No | No | 8 max | Yes | Yes | Yes | Yes |
37
37
  | **Agent Domains** | Eng, Ops, Business, Data, Product, Growth, Review | N/A | N/A | N/A | N/A | N/A | 3 types | N/A |
@@ -145,7 +145,7 @@
145
145
  | **Multi-Agent Verification** | Model diversity (Claude vs OpenAI, 54% improvement) | 3 blind reviewers + devil's advocate | Different approach (N/A for Claude Code - only Claude models) |
146
146
  | **Quality Gates** | Built-in verification loops | 8 explicit gates + anti-sycophancy | **Loki Mode** |
147
147
  | **Memory System** | Not documented | 3-tier episodic/semantic/procedural | **Loki Mode** |
148
- | **Agent Specialization** | Custom Zen Agents | 41 pre-defined specialized agent roles | **Loki Mode** |
148
+ | **Agent Specialization** | Custom Zen Agents | Role inventory: 41 prompt-defined roles in `agents/types.json` (a count of definitions, not a measured advantage) | Different approach |
149
149
  | **CI Failure Analysis** | Explicit pattern with auto-fix | DevOps agent only | **ADOPTED from Zencoder** |
150
150
  | **Review Comment Resolution** | Auto-apply simple changes | Manual review | **ADOPTED from Zencoder** |
151
151
  | **Dependency Management** | Scheduled PRs, one group at a time | Mentioned only | **ADOPTED from Zencoder** |
@@ -174,10 +174,9 @@
174
174
 
175
175
  1. **Quality Control**: 8 explicit gates + blind review + devil's advocate vs built-in loops
176
176
  2. **Memory System**: 3-tier (episodic/semantic/procedural) with cross-project learning
177
- 3. **Agent Specialization**: 41 pre-defined specialized agent roles across 8 domains
178
- 4. **Anti-Sycophancy**: CONSENSAGENT patterns prevent reviewer groupthink
179
- 5. **Autonomy Design**: Minimal human intervention from PRD to production
180
- 6. **Research Foundation**: 10+ academic papers integrated vs proprietary
177
+ 3. **Anti-Sycophancy**: CONSENSAGENT patterns prevent reviewer groupthink
178
+ 4. **Autonomy Design**: Minimal human intervention from PRD to production
179
+ 5. **Research Foundation**: 10+ academic papers integrated vs proprietary
181
180
 
182
181
  ### Where Zencoder EXCEEDS Loki Mode
183
182
 
@@ -230,7 +229,6 @@ These are patterns from competing projects that are **practically and scientific
230
229
  | **Constitutional AI Integration** | Principles-based self-critique from Anthropic research | None have this |
231
230
  | **Anti-Sycophancy (CONSENSAGENT)** | Blind review + devil's advocate prevents groupthink | None have this |
232
231
  | **Provider Abstraction Layer** | Clean degradation from full-featured to sequential-only | Claude-only projects can't degrade |
233
- | **41 Specialized Agent Roles** | Purpose-built role definitions in 8 domains vs generic; Loki gates every role's output through blind review + council | agents (108) has more but less organized |
234
232
  | **Research Foundation** | 10+ academic papers integrated with citations | Most have no research backing |
235
233
 
236
234
  ### Superpowers Deep-Dive (35K+ Stars)
@@ -352,7 +350,7 @@ Tiered agent architecture with explicit escalation:
352
350
  | Dimension | Loki Mode Advantage |
353
351
  |-----------|-------------------|
354
352
  | **Autonomy** | Designed for high autonomy with minimal human intervention |
355
- | **Multi-Agent** | 41 prompt-defined agent roles in 8 domains adopted per phase (parallel review council + optional worktree streams on Claude, sequential elsewhere) vs 1-8 in competitors, with all output gated by blind review + council |
353
+ | **Multi-Agent** | Role inventory: 41 prompt-defined roles in 8 domains (`agents/types.json`), adopted per phase; parallel review council + optional worktree streams on Claude, sequential elsewhere. Output is gated by blind review + council |
356
354
  | **Quality** | 8 gates + blind review + devil's advocate + property-based testing |
357
355
  | **Research** | 10+ academic papers integrated vs proprietary/undisclosed |
358
356
  | **Anti-Sycophancy** | Only agent with CONSENSAGENT-based blind review |
@@ -324,7 +324,7 @@ The agent type registry and installation tooling.
324
324
  phase.
325
325
 
326
326
  **Key files:**
327
- - `types.json` - Agent type definitions (41 agent types across 8 domains).
327
+ - `agents/types.json` - Agent type definitions (41 agent types across 8 domains).
328
328
  - `hub_install.py` - Agent hub install.
329
329
  - `managed_registry.py` - Managed agent registry.
330
330
 
@@ -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.76.0
5
+ **Version:** v11.78.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