loki-mode 9.57.0 → 9.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v9.57.0
6
+ # Loki Mode v9.63.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
470
470
 
471
471
  ---
472
472
 
473
- **v9.57.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.63.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.57.0
1
+ 9.63.0
@@ -72,6 +72,38 @@ for _cc_cand in "$_LOKI_CC_DIR/lib/secret-scan.sh" "${SCRIPT_DIR:-}/lib/secret-s
72
72
  done
73
73
  unset _LOKI_CC_DIR _cc_cand
74
74
 
75
+ # BACKLOG 134: the three test-result readers (council_heuristic_review,
76
+ # council_evaluate_member, council_devils_advocate_review) resolve their
77
+ # interpreter through run.sh's _loki_snapshot_py_tool and run it -I -S: -E alone
78
+ # still loads user site-packages, whose .pth "import" lines run before any
79
+ # sys.path scrub and can forge json.load (see that function's comment in
80
+ # run.sh for the full rationale and accepted gaps). run.sh sources this file
81
+ # BEFORE it defines _loki_snapshot_py_tool, so in production run.sh's own
82
+ # definition replaces this one; the copy below only serves callers that source
83
+ # this file on its own (tests). run.sh is the source of truth: keep this copy
84
+ # byte-identical to it.
85
+ declare -F _loki_snapshot_py_tool >/dev/null 2>&1 || \
86
+ _loki_snapshot_py_tool() {
87
+ local c
88
+ for c in /usr/bin/python3 /bin/python3; do
89
+ [ -x "$c" ] && [ ! -d "$c" ] && "$c" -I -S -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return 0; }
90
+ done
91
+ local dir
92
+ local IFS=:
93
+ for dir in $PATH; do
94
+ case "$dir" in
95
+ /*) ;;
96
+ *) continue ;;
97
+ esac
98
+ if [ -x "$dir/python3" ] && [ ! -d "$dir/python3" ] \
99
+ && "$dir/python3" -I -S -c '' >/dev/null 2>&1; then
100
+ printf '%s\n' "$dir/python3"
101
+ return 0
102
+ fi
103
+ done
104
+ return 1
105
+ }
106
+
75
107
  # Council configuration
76
108
  COUNCIL_ENABLED=${LOKI_COUNCIL_ENABLED:-true}
77
109
  COUNCIL_SIZE=${LOKI_COUNCIL_SIZE:-3}
@@ -3484,8 +3516,10 @@ council_heuristic_review() {
3484
3516
  # evidence -> REJECT (keep iterating), never a heuristic APPROVE.
3485
3517
  local _tr_file=".loki/quality/test-results.json"
3486
3518
  local _tests_ok=0
3487
- if [ -f "$_tr_file" ] && command -v python3 >/dev/null 2>&1; then
3488
- _tests_ok=$(_TR="$_tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3519
+ local _tr_py=""
3520
+ [ -f "$_tr_file" ] && _tr_py="$(_loki_snapshot_py_tool)" || _tr_py=""
3521
+ if [ -n "$_tr_py" ]; then
3522
+ _tests_ok=$(_TR="$_tr_file" "$_tr_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3489
3523
  import json, os
3490
3524
  try:
3491
3525
  with open(os.environ['_TR']) as f:
@@ -3668,9 +3702,11 @@ council_evaluate_member() {
3668
3702
  local tr_file="$loki_dir/quality/test-results.json"
3669
3703
  local test_evidence="absent" # absent | pass | fail | inconclusive
3670
3704
  local test_runner_seen="none"
3671
- if [ -f "$tr_file" ]; then
3705
+ local _tr_py=""
3706
+ [ -f "$tr_file" ] && _tr_py="$(_loki_snapshot_py_tool)" || _tr_py=""
3707
+ if [ -n "$_tr_py" ]; then
3672
3708
  local _tr_status
3673
- _tr_status=$(_TR_FILE="$tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3709
+ _tr_status=$(_TR_FILE="$tr_file" "$_tr_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3674
3710
  import json, os, sys
3675
3711
  try:
3676
3712
  with open(os.environ['_TR_FILE']) as f:
@@ -3727,14 +3763,25 @@ else:
3727
3763
  # Cases 6 and 7, with Case 8 as the positive control proving this
3728
3764
  # can still reach COMPLETE when every queue really is empty.
3729
3765
  local unfinished=0
3730
- local _q _qcount
3766
+ local _q _qcount _q_py=""
3731
3767
  for _q in pending in-progress blocked; do
3732
3768
  [ -f "$loki_dir/queue/${_q}.json" ] || continue
3733
- _qcount=$(_QUEUE_FILE="$loki_dir/queue/${_q}.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os
3769
+ # Same -I -S interpreter as the test-result readers: a user-site
3770
+ # .pth must not be able to make this count read 0.
3771
+ [ -n "$_q_py" ] || _q_py="$(_loki_snapshot_py_tool)" || _q_py=""
3772
+ _qcount=""
3773
+ [ -n "$_q_py" ] && _qcount=$(_QUEUE_FILE="$loki_dir/queue/${_q}.json" "$_q_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os
3734
3774
  d = json.load(open(os.environ['_QUEUE_FILE']))
3735
- print(len(d.get('tasks', d) if isinstance(d, dict) else d))" 2>/dev/null || echo "0")
3736
- # Guard against a non-numeric read (malformed file, python absent).
3737
- case "$_qcount" in ''|*[!0-9]*) _qcount=0 ;; esac
3775
+ print(len(d.get('tasks', d) if isinstance(d, dict) else d))" 2>/dev/null)
3776
+ # Fail closed: a present queue file whose count cannot be read
3777
+ # (malformed file, no interpreter) is not evidence it is empty.
3778
+ case "$_qcount" in
3779
+ ''|*[!0-9]*)
3780
+ blocked="true"
3781
+ reasons="${reasons}${_q} queue count unreadable; "
3782
+ continue
3783
+ ;;
3784
+ esac
3738
3785
  if [ "$_qcount" -gt 0 ]; then
3739
3786
  unfinished=$((unfinished + _qcount))
3740
3787
  blocked="true"
@@ -3969,9 +4016,11 @@ council_devils_advocate_review() {
3969
4016
  # (nothing writes .loki/logs/test-*.log, so an empty glob is the normal case
3970
4017
  # and must never veto a unanimous COMPLETE on its own).
3971
4018
  local tr_file="$loki_dir/quality/test-results.json"
3972
- if [ -f "$tr_file" ]; then
4019
+ local _tr_py=""
4020
+ [ -f "$tr_file" ] && _tr_py="$(_loki_snapshot_py_tool)" || _tr_py=""
4021
+ if [ -n "$_tr_py" ]; then
3973
4022
  local _tr_status
3974
- _tr_status=$(_TR_FILE="$tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4023
+ _tr_status=$(_TR_FILE="$tr_file" "$_tr_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
3975
4024
  import json, os, sys
3976
4025
  try:
3977
4026
  with open(os.environ['_TR_FILE']) as f:
@@ -3994,6 +4043,8 @@ else:
3994
4043
  ((issues_found++))
3995
4044
  issue_details="${issue_details}structured test results red (pass==false); "
3996
4045
  fi
4046
+ elif [ -f "$tr_file" ]; then
4047
+ ((issues_found++)); issue_details="${issue_details}test results unreadable (no isolated python3); "
3997
4048
  fi
3998
4049
  # Additional source: any legacy test log that shows no pass indicator is a red
3999
4050
  # signal. Missing logs are NOT counted (this path is not written by the runner).
@@ -4009,8 +4060,18 @@ else:
4009
4060
  # Skeptical check 2: Are there still failing tasks in the queue?
4010
4061
  if [ -f "$loki_dir/queue/failed.json" ]; then
4011
4062
  local failed_count
4012
- failed_count=$(_QUEUE_FILE="$loki_dir/queue/failed.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "0")
4013
- if [ "$failed_count" -gt 0 ]; then
4063
+ local failed_count="" _fq_py=""
4064
+ _fq_py="$(_loki_snapshot_py_tool)" || _fq_py=""
4065
+ [ -n "$_fq_py" ] && failed_count=$(_QUEUE_FILE="$loki_dir/queue/failed.json" "$_fq_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null)
4066
+ # Fail closed: an unreadable failed-queue count (malformed, no
4067
+ # interpreter) vetoes instead of reading as zero.
4068
+ case "$failed_count" in
4069
+ ''|*[!0-9]*)
4070
+ ((issues_found++))
4071
+ issue_details="${issue_details}failed queue count unreadable; "
4072
+ ;;
4073
+ esac
4074
+ if [ "${failed_count:-0}" -gt 0 ] 2>/dev/null; then
4014
4075
  ((issues_found++))
4015
4076
  issue_details="${issue_details}$failed_count tasks in failed queue; "
4016
4077
  fi
@@ -4492,8 +4553,11 @@ _council_convergence_evidence_green() {
4492
4553
  # Affirmative test-green is REQUIRED: a real runner that passed. A missing
4493
4554
  # file or runner=="none" (no suite) is NOT affirmative evidence -> not green.
4494
4555
  [ -f "$tr_file" ] || return 1
4556
+ # -I -S interpreter (see _loki_snapshot_py_tool); none resolvable -> not green.
4557
+ local _cv_py
4558
+ _cv_py="$(_loki_snapshot_py_tool)" || return 1
4495
4559
  local tr_state
4496
- tr_state=$(_TR_FILE="$tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4560
+ tr_state=$(_TR_FILE="$tr_file" "$_cv_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4497
4561
  import json, os, sys
4498
4562
  try:
4499
4563
  with open(os.environ['_TR_FILE']) as f:
@@ -4516,7 +4580,7 @@ print('yes' if (runner != 'none' and passed is True
4516
4580
  local results_file="${TARGET_DIR:-.}/.loki/checklist/verification-results.json"
4517
4581
  if [ -f "$results_file" ]; then
4518
4582
  local cl_state
4519
- cl_state=$(_RESULTS_FILE="$results_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4583
+ cl_state=$(_RESULTS_FILE="$results_file" "$_cv_py" -I -S -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4520
4584
  import json, os, sys
4521
4585
  try:
4522
4586
  with open(os.environ['_RESULTS_FILE']) as f:
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # Loki Mode -- the single guarded browser opener (S-103).
3
+ #
4
+ # Every place Loki opens a browser MUST go through loki_open_url. Tests and
5
+ # agent sessions must never pop a browser tab on a developer's machine (a
6
+ # test run once opened dozens of "Loki Mode" tabs at an offline dashboard).
7
+ # tests/test-browser-open-guard.sh fails if a raw opener call appears anywhere
8
+ # else.
9
+ #
10
+ # Public API:
11
+ # loki_browser_allowed -> 0 when a browser may be opened, 1 otherwise
12
+ # loki_open_url <target> -> 0 when an opener ran, 1 when suppressed or no
13
+ # opener exists (caller prints the URL instead)
14
+ #
15
+ # Suppressed when any of these hold:
16
+ # LOKI_NO_BROWSER=1 or LOKI_NO_AUTO_OPEN=1 (explicit opt-out)
17
+ # CI is set
18
+ # LOKI_TEST, BATS_VERSION, BATS_TEST_FILENAME or PYTEST_CURRENT_TEST is set
19
+ # stdout is not a TTY
20
+ #
21
+ # Call loki_open_url WITHOUT redirecting its stdout: the TTY check reads fd 1.
22
+ # The helper silences the opener itself.
23
+
24
+ if [ "${__LOKI_BROWSER_OPEN_SH_LOADED:-0}" = "1" ]; then
25
+ return 0 2>/dev/null || true
26
+ fi
27
+ __LOKI_BROWSER_OPEN_SH_LOADED=1
28
+
29
+ loki_browser_allowed() {
30
+ [ "${LOKI_NO_BROWSER:-0}" = "1" ] && return 1
31
+ [ "${LOKI_NO_AUTO_OPEN:-0}" = "1" ] && return 1
32
+ [ -n "${CI:-}" ] && return 1
33
+ [ -n "${LOKI_TEST:-}${BATS_VERSION:-}${BATS_TEST_FILENAME:-}${PYTEST_CURRENT_TEST:-}" ] && return 1
34
+ [ -t 1 ] || return 1
35
+ return 0
36
+ }
37
+
38
+ loki_open_url() {
39
+ local target="$1"
40
+ loki_browser_allowed || return 1
41
+ if command -v open >/dev/null 2>&1; then
42
+ open "$target" >/dev/null 2>&1
43
+ elif command -v xdg-open >/dev/null 2>&1; then
44
+ xdg-open "$target" >/dev/null 2>&1
45
+ elif command -v cmd.exe >/dev/null 2>&1; then
46
+ # Windows (Git Bash/WSL): `start` is a cmd builtin; "" is its title arg.
47
+ cmd.exe /c start "" "$target" >/dev/null 2>&1
48
+ else
49
+ return 1
50
+ fi
51
+ }
@@ -29,17 +29,29 @@ _LINEAGE_DISCOVERY_WINDOW_SECONDS = 0.20
29
29
  _RECONCILE_SCHEDULING_MARGIN_SECONDS = 0.30
30
30
  _LINEAGE_ENV_NAME = "LOKI_DEADLINE_LINEAGE"
31
31
  _CONTROL_ENV_NAME = "LOKI_DEADLINE_CONTROL_FILE"
32
+ # How long spawn_tracked waits for the bootstrap to report that it is running.
33
+ # Exceeding it fails the launch closed; it never skips the lineage check.
34
+ _LAUNCH_READY_TIMEOUT_SECONDS = 30.0
35
+
36
+ # The ready byte is written by interpreted code, so execve has fully returned
37
+ # to userspace and the kernel has published the new environment block. Popen
38
+ # returning is NOT that point: its exec-error pipe closes at close-on-exec time,
39
+ # before the ELF loader sets mm->env_end, and /proc/<pid>/environ reads as empty
40
+ # for a live child in that window.
32
41
  _EXEC_BOOTSTRAP = """
33
42
  import os
34
43
  import sys
35
44
 
36
- gate = int(sys.argv[1])
45
+ ready = int(sys.argv[1])
46
+ os.write(ready, b"1")
47
+ os.close(ready)
48
+ gate = int(sys.argv[2])
37
49
  released = os.read(gate, 1)
38
50
  os.close(gate)
39
51
  if released != b"1":
40
52
  raise SystemExit(125)
41
53
  try:
42
- os.execvpe(sys.argv[2], sys.argv[2:], os.environ)
54
+ os.execvpe(sys.argv[3], sys.argv[3:], os.environ)
43
55
  except FileNotFoundError:
44
56
  raise SystemExit(127)
45
57
  except PermissionError:
@@ -585,6 +597,7 @@ def spawn_tracked(
585
597
  process: subprocess.Popen[bytes] | None = None
586
598
  gate_read = gate_write = -1
587
599
  lineage_read = lineage_write = -1
600
+ ready_read = ready_write = -1
588
601
  try:
589
602
  subreaper = _configure_child_subreaper()
590
603
  token = secrets.token_hex(16)
@@ -594,6 +607,7 @@ def spawn_tracked(
594
607
  inherited = tuple(popen_kwargs.pop("pass_fds", ()))
595
608
  gate_read, gate_write = os.pipe()
596
609
  lineage_read, lineage_write = os.pipe()
610
+ ready_read, ready_write = os.pipe()
597
611
  handles = (
598
612
  _darwin_pipe_handles(os.getpid(), lineage_read)
599
613
  if sys.platform == "darwin"
@@ -602,14 +616,34 @@ def spawn_tracked(
602
616
  if sys.platform == "darwin" and not handles:
603
617
  raise RuntimeError("attempt lineage pipe identity is unavailable")
604
618
  process = subprocess.Popen(
605
- [sys.executable, "-c", _EXEC_BOOTSTRAP, str(gate_read), *command],
619
+ [
620
+ sys.executable,
621
+ "-c",
622
+ _EXEC_BOOTSTRAP,
623
+ str(ready_write),
624
+ str(gate_read),
625
+ *command,
626
+ ],
606
627
  start_new_session=True,
607
628
  env=child_env,
608
- pass_fds=(*inherited, gate_read, lineage_write),
629
+ pass_fds=(*inherited, ready_write, gate_read, lineage_write),
609
630
  **popen_kwargs,
610
631
  )
611
632
  os.close(gate_read)
612
633
  gate_read = -1
634
+ os.close(ready_write)
635
+ ready_write = -1
636
+ # Wait until the bootstrap is running before reading its marker. EOF
637
+ # without a byte means it died first; the tracker's reaped-child path
638
+ # (poll) owns that case, so fall through to it.
639
+ with selectors.DefaultSelector() as ready_wait:
640
+ ready_wait.register(ready_read, selectors.EVENT_READ)
641
+ readable = ready_wait.select(_LAUNCH_READY_TIMEOUT_SECONDS)
642
+ if not readable:
643
+ raise RuntimeError("provider attempt did not report ready")
644
+ os.read(ready_read, 1)
645
+ os.close(ready_read)
646
+ ready_read = -1
613
647
  tracker = _LineageTracker(process, subreaper, token, handles)
614
648
  tracker._lineage_read_fd = lineage_read
615
649
  lineage_read = -1
@@ -620,7 +654,14 @@ def spawn_tracked(
620
654
  lineage_write = -1
621
655
  return process, tracker
622
656
  except BaseException:
623
- for descriptor in (gate_read, gate_write, lineage_read, lineage_write):
657
+ for descriptor in (
658
+ gate_read,
659
+ gate_write,
660
+ lineage_read,
661
+ lineage_write,
662
+ ready_read,
663
+ ready_write,
664
+ ):
624
665
  if descriptor >= 0:
625
666
  try:
626
667
  os.close(descriptor)
@@ -42,7 +42,14 @@ _PROOF_CHECK_SH=1
42
42
  _proof_check_net() {
43
43
  # run.sh withholds GitHub tokens from agent sessions (Rule of Two); this is
44
44
  # one of Loki's own trusted calls, so it takes them back for the command.
45
- if declare -f _loki_with_github_tokens >/dev/null 2>&1; then
45
+ if declare -f _loki_run_neutral >/dev/null 2>&1; then
46
+ # BACKLOG 149 round 5: run from / with GH_REPO read as data first, so
47
+ # gh's own git calls never load the agent's repo config while holding
48
+ # the real credentials.
49
+ local _pc_repo
50
+ _pc_repo="$(_loki_trusted_repo)"
51
+ _loki_with_github_tokens _loki_run_neutral "$_pc_repo" _proof_check_net_timed "$@"
52
+ elif declare -f _loki_with_github_tokens >/dev/null 2>&1; then
46
53
  _loki_with_github_tokens _proof_check_net_timed "$@"
47
54
  else
48
55
  _proof_check_net_timed "$@"
@@ -197,8 +204,16 @@ post_verified_completion_check() {
197
204
 
198
205
  # --- Resolve owner/repo (nameWithOwner). ----------------------------------
199
206
  # Prefer the current repo context (Loki's model is same-repo branch PRs).
207
+ # Under run.sh, gh runs from / (BACKLOG 149 round 5) and `gh repo view`
208
+ # with no argument IGNORES GH_REPO, so it could never resolve the repo
209
+ # there. Use the origin repo run.sh already reads as data and validates.
210
+ # Standalone (no run.sh helpers), gh runs in the cwd repo as before.
200
211
  local repo=""
201
- repo="$(_proof_check_net gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || true)"
212
+ if declare -f _loki_trusted_repo >/dev/null 2>&1; then
213
+ repo="$(_loki_trusted_repo)"
214
+ else
215
+ repo="$(_proof_check_net gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || true)"
216
+ fi
202
217
 
203
218
  # --- Resolve head sha: PR head if a pr_url is given, else proof fallback. --
204
219
  local head_sha=""
package/autonomy/loki CHANGED
@@ -71,6 +71,13 @@ if [ -f "$_LOKI_SCRIPT_DIR/lib/lock.sh" ]; then
71
71
  source "$_LOKI_SCRIPT_DIR/lib/lock.sh"
72
72
  fi
73
73
 
74
+ # Guarded browser opener (loki_open_url). Every browser open goes through it
75
+ # so tests, CI and non-TTY runs never pop a tab. If missing, nothing opens.
76
+ if [ -f "$_LOKI_SCRIPT_DIR/lib/browser-open.sh" ]; then
77
+ # shellcheck source=lib/browser-open.sh
78
+ source "$_LOKI_SCRIPT_DIR/lib/browser-open.sh"
79
+ fi
80
+
74
81
  # Crash-reporting helpers (provides loki_collection_enabled, used by
75
82
  # cmd_telemetry status and cmd_crash). Self-guarded against double-source.
76
83
  if [ -f "$_LOKI_SCRIPT_DIR/crash.sh" ]; then
@@ -3613,8 +3620,8 @@ cmd_start() {
3613
3620
  # cockpit hint is printed by _loki_start_handoff for the Both path.
3614
3621
  args+=("--bg")
3615
3622
  local _du="http://127.0.0.1:${LOKI_DASHBOARD_PORT:-57374}"
3616
- if command -v open >/dev/null 2>&1; then open "$_du" >/dev/null 2>&1 &
3617
- elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$_du" >/dev/null 2>&1 & fi
3623
+ # stdout must stay unredirected: loki_open_url checks the TTY.
3624
+ loki_open_url "$_du" 2>/dev/null &
3618
3625
  ;;
3619
3626
  launch-bg-watch)
3620
3627
  # Watch (cockpit only) detaches without opening the browser.
@@ -7693,16 +7700,10 @@ cmd_dashboard_open() {
7693
7700
 
7694
7701
  echo -e "${GREEN}Opening dashboard: $url${NC}"
7695
7702
 
7696
- # Open in browser (cross-platform)
7697
- if command -v open &> /dev/null; then
7698
- open "$url"
7699
- elif command -v xdg-open &> /dev/null; then
7700
- xdg-open "$url"
7701
- elif command -v start &> /dev/null; then
7702
- start "$url"
7703
- else
7703
+ # Open in browser (cross-platform, guarded: no-op in tests/CI/non-TTY)
7704
+ if ! loki_open_url "$url" 2>/dev/null; then
7704
7705
  echo ""
7705
- echo "Could not detect browser opener."
7706
+ echo "Browser not opened (headless, test, or no opener)."
7706
7707
  echo "Please open in browser: $url"
7707
7708
  fi
7708
7709
  }
@@ -7753,6 +7754,12 @@ cmd_welcome_terminal() {
7753
7754
  echo -e " ${DIM}Anonymous diagnostics on (os/arch/version/error type only -- never${NC}"
7754
7755
  echo -e " ${DIM}code, prompts, or paths). Off: loki telemetry off - docs/PRIVACY.md${NC}"
7755
7756
  echo ""
7757
+ elif [ -n "${LOKI_TELEMETRY_DISABLED:-}" ] || [ -n "${DO_NOT_TRACK:-}" ]; then
7758
+ # Off-by-default means silence otherwise (nothing to disclose), but an
7759
+ # EXPLICIT opt-out via env var deserves a confirmation that it took
7760
+ # effect, distinct from the default off state nobody asked about.
7761
+ echo -e " ${DIM}Analytics are off (LOKI_TELEMETRY_DISABLED/DO_NOT_TRACK).${NC}"
7762
+ echo ""
7756
7763
  fi
7757
7764
  }
7758
7765
 
@@ -7774,21 +7781,14 @@ cmd_welcome() {
7774
7781
  q="${q}&telemetry=off"
7775
7782
  fi
7776
7783
 
7777
- # Headless / Docker / CI / no-browser: print the terminal welcome only.
7778
- local has_browser=""
7779
- if command -v open >/dev/null 2>&1; then has_browser="open"
7780
- elif command -v xdg-open >/dev/null 2>&1; then has_browser="xdg-open"
7781
- elif command -v start >/dev/null 2>&1; then has_browser="start"
7782
- fi
7783
-
7784
- if [ -z "$has_browser" ] || [ ! -f "$welcome_file" ] || [ -n "${CI:-}" ] || [ ! -t 1 ]; then
7785
- cmd_welcome_terminal "$ver"
7786
- [ -f "$welcome_file" ] && echo -e " ${DIM}Full welcome page: ${welcome_file}${NC}" && echo ""
7784
+ # Headless / Docker / CI / tests / no-browser: print the terminal welcome.
7785
+ if [ -f "$welcome_file" ] && loki_open_url "file://${welcome_file}?${q}" 2>/dev/null; then
7786
+ echo -e "${GREEN}Opened the Loki Mode welcome page in your browser.${NC}"
7787
7787
  return 0
7788
7788
  fi
7789
-
7790
- echo -e "${GREEN}Opening the Loki Mode welcome page in your browser...${NC}"
7791
- "$has_browser" "file://${welcome_file}?${q}" >/dev/null 2>&1 || cmd_welcome_terminal "$ver"
7789
+ cmd_welcome_terminal "$ver"
7790
+ [ -f "$welcome_file" ] && echo -e " ${DIM}Full welcome page: ${welcome_file}${NC}" && echo ""
7791
+ return 0
7792
7792
  }
7793
7793
 
7794
7794
  # First-run hook: open the welcome once, then never again. Browser only when
@@ -7802,7 +7802,7 @@ cmd_welcome_maybe_firstrun() {
7802
7802
  # re-checks for a browser and falls back to the terminal welcome, so we
7803
7803
  # gate purely on interactivity + CI here (grouped to avoid &&/|| precedence
7804
7804
  # ambiguity). Backgrounded so it never delays the start.
7805
- if [ -t 1 ] && [ -z "${CI:-}" ]; then
7805
+ if declare -F loki_browser_allowed >/dev/null 2>&1 && loki_browser_allowed; then
7806
7806
  (cmd_welcome >/dev/null 2>&1 &) || true
7807
7807
  fi
7808
7808
  }
@@ -8132,11 +8132,7 @@ cmd_web_start() {
8132
8132
  local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"
8133
8133
  echo -e "Open: ${CYAN}$url${NC}"
8134
8134
  if [ "$open_browser" = true ]; then
8135
- if command -v open &> /dev/null; then
8136
- open "$url"
8137
- elif command -v xdg-open &> /dev/null; then
8138
- xdg-open "$url"
8139
- fi
8135
+ loki_open_url "$url" 2>/dev/null || true
8140
8136
  fi
8141
8137
  return 0
8142
8138
  fi
@@ -8231,15 +8227,7 @@ cmd_web_start() {
8231
8227
  if [ "$open_browser" = true ]; then
8232
8228
  if [ "$server_ready" = true ]; then
8233
8229
  echo ""
8234
- if command -v open &> /dev/null; then
8235
- open "$url"
8236
- elif command -v xdg-open &> /dev/null; then
8237
- xdg-open "$url"
8238
- elif command -v start &> /dev/null; then
8239
- start "$url"
8240
- else
8241
- echo "Please open in browser: $url"
8242
- fi
8230
+ loki_open_url "$url" 2>/dev/null || echo "Please open in browser: $url"
8243
8231
  else
8244
8232
  echo ""
8245
8233
  echo -e "${YELLOW}Browser not opened because server is not ready yet.${NC}"
@@ -8885,15 +8873,7 @@ cmd_preview() {
8885
8873
  echo "Served from localhost on this machine."
8886
8874
 
8887
8875
  if [ "$open_browser" = true ]; then
8888
- if command -v open &> /dev/null; then
8889
- open "$url"
8890
- elif command -v xdg-open &> /dev/null; then
8891
- xdg-open "$url"
8892
- elif command -v start &> /dev/null; then
8893
- start "$url"
8894
- else
8895
- echo "Please open in browser: $url"
8896
- fi
8876
+ loki_open_url "$url" 2>/dev/null || echo "Please open in browser: $url"
8897
8877
  fi
8898
8878
  }
8899
8879
 
@@ -16050,16 +16030,16 @@ cmd_demo() {
16050
16030
  if [ "$has_web" = true ] && [ -n "$open_target" ]; then
16051
16031
  echo -e "${CYAN}A web frontend was generated.${NC}"
16052
16032
  echo ""
16053
- echo -n "Open in browser? [Y/n] "
16054
- read -r answer </dev/tty 2>/dev/null || answer="n"
16055
- if [[ -z "$answer" || "$answer" =~ ^[Yy] ]]; then
16056
- if command -v open &>/dev/null; then
16057
- open "$open_target"
16058
- elif command -v xdg-open &>/dev/null; then
16059
- xdg-open "$open_target"
16060
- else
16061
- echo -e "Open this file in your browser: ${BLUE}$open_target${NC}"
16062
- fi
16033
+ local answer="n"
16034
+ # Ask only when a browser may open, so a test never blocks on /dev/tty.
16035
+ if loki_browser_allowed 2>/dev/null; then
16036
+ echo -n "Open in browser? [Y/n] "
16037
+ read -r answer </dev/tty 2>/dev/null || answer="n"
16038
+ fi
16039
+ if [[ -z "$answer" || "$answer" =~ ^[Yy] ]] && loki_open_url "$open_target" 2>/dev/null; then
16040
+ :
16041
+ else
16042
+ echo -e "Open this file in your browser: ${BLUE}$open_target${NC}"
16063
16043
  fi
16064
16044
  elif [ "$has_start_script" = true ]; then
16065
16045
  echo -e "${CYAN}A start script was found in package.json.${NC}"
@@ -17958,9 +17938,7 @@ print(json.dumps({'id': manifest.id, 'dir': str(pipeline.migration_dir)}))
17958
17938
  sleep 2
17959
17939
  if kill -0 "$migrate_dashboard_pid" 2>/dev/null; then
17960
17940
  echo -e " Dashboard: ${CYAN}${url_scheme}://127.0.0.1:${dashboard_port}/${NC}"
17961
- if [[ "$OSTYPE" == "darwin"* ]]; then
17962
- open "${url_scheme}://127.0.0.1:${dashboard_port}/" 2>/dev/null || true
17963
- fi
17941
+ loki_open_url "${url_scheme}://127.0.0.1:${dashboard_port}/" 2>/dev/null || true
17964
17942
  echo ""
17965
17943
  else
17966
17944
  echo -e " ${YELLOW}Dashboard failed to start (check logs)${NC}"
@@ -33945,8 +33923,10 @@ except Exception:
33945
33923
  # Only scan added lines (starting with "+") to avoid flagging deleted code
33946
33924
  while IFS= read -r match; do
33947
33925
  [ -z "$match" ] && continue
33948
- local ml
33949
- ml=$(echo "$match" | cut -d: -f1)
33926
+ # grep -n emits "N:content"; take the number before the first
33927
+ # colon without forking echo+cut per line (matters at finding
33928
+ # counts in the thousands+).
33929
+ local ml="${match%%:*}"
33950
33930
  _ci_add_finding "diff" "$ml" "$sev" "$cat" "$finding" "$suggestion"
33951
33931
  done < <(printf '%s\n' "$diff_content" \
33952
33932
  | grep -n '^+' \
@@ -34089,8 +34069,11 @@ TEST_SUGGEST_PY
34089
34069
  # --- Tally findings ---
34090
34070
  local count_critical=0 count_high=0 count_medium=0 count_low=0 count_info=0
34091
34071
  for f in "${findings[@]+"${findings[@]}"}"; do
34092
- local sev
34093
- sev=$(echo "$f" | cut -d'|' -f3)
34072
+ # "file|line|sev|cat|finding|suggestion" (see _ci_add_finding above).
34073
+ # No echo+cut fork per finding -- this loop runs once per finding and
34074
+ # dominated the runtime at the fixture's old ARG_MAX-scale sizes.
34075
+ local sev _tally_file _tally_line _tally_cat _tally_finding _tally_suggestion
34076
+ IFS='|' read -r _tally_file _tally_line sev _tally_cat _tally_finding _tally_suggestion <<< "$f"
34094
34077
  case "$sev" in
34095
34078
  CRITICAL) count_critical=$((count_critical + 1)) ;;
34096
34079
  HIGH) count_high=$((count_high + 1)) ;;
@@ -37169,15 +37152,9 @@ print(json.dumps(doc, indent=2))
37169
37152
  exit 1
37170
37153
  fi
37171
37154
  echo -e "${GREEN}Opening proof: $html${NC}"
37172
- if command -v open &>/dev/null; then
37173
- open "$html"
37174
- elif command -v xdg-open &>/dev/null; then
37175
- xdg-open "$html"
37176
- elif command -v start &>/dev/null; then
37177
- start "$html"
37178
- else
37155
+ if ! loki_open_url "$html" 2>/dev/null; then
37179
37156
  echo ""
37180
- echo "Could not detect browser opener."
37157
+ echo "Browser not opened (headless, test, or no opener)."
37181
37158
  echo "Please open in browser: $html"
37182
37159
  fi
37183
37160
  exit 0