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 +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +79 -15
- package/autonomy/lib/browser-open.sh +51 -0
- package/autonomy/lib/deadline.py +46 -5
- package/autonomy/lib/proof-check.sh +17 -2
- package/autonomy/loki +50 -73
- package/autonomy/run.sh +662 -51
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/dev/release-checklist.md +11 -32
- package/docs/v10/BOARD.md +46 -32
- package/docs/v10/DECISIONS.md +33 -0
- package/docs/v10/GUARDS.md +435 -0
- package/docs/v10/METRICS.md +33 -0
- package/docs/v10/PROGRESS.md +20 -0
- package/docs/v10/SWARM.md +24 -0
- package/learning/suggest.sh +1 -1
- package/loki-ts/dist/loki.js +307 -307
- package/loki-ts/package.json +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
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.
|
|
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.
|
|
473
|
+
**v9.63.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
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
|
-
|
|
3488
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
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
|
|
3736
|
-
#
|
|
3737
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
|
4013
|
-
|
|
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"
|
|
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"
|
|
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
|
+
}
|
package/autonomy/lib/deadline.py
CHANGED
|
@@ -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
|
-
|
|
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[
|
|
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
|
-
[
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
3617
|
-
|
|
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
|
|
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 "
|
|
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
|
|
7778
|
-
|
|
7779
|
-
|
|
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 "${
|
|
7791
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16054
|
-
|
|
16055
|
-
if
|
|
16056
|
-
|
|
16057
|
-
|
|
16058
|
-
|
|
16059
|
-
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33949
|
-
|
|
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
|
-
|
|
34093
|
-
|
|
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
|
|
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 "
|
|
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
|