loki-mode 6.83.1 → 7.0.2
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 +62 -11
- package/VERSION +1 -1
- package/agents/managed_registry.py +246 -0
- package/agents/types.json +330 -0
- package/autonomy/completion-council.sh +226 -0
- package/autonomy/loki +346 -15
- package/autonomy/run.sh +408 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +235 -0
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/mcp/managed_tools.py +245 -0
- package/mcp/server.py +22 -0
- package/memory/managed_memory/__init__.py +9 -0
- package/memory/managed_memory/retrieve.py +237 -1
- package/package.json +4 -2
- package/providers/managed.py +789 -0
- package/skills/00-index.md +1 -0
- package/skills/memory.md +187 -0
package/autonomy/run.sh
CHANGED
|
@@ -649,7 +649,23 @@ export LOKI_SESSION_MODEL LOKI_LEGACY_TIER_SWITCHING
|
|
|
649
649
|
# Both off (default) => zero behavior change from v6.82.0.
|
|
650
650
|
LOKI_MANAGED_AGENTS="${LOKI_MANAGED_AGENTS:-false}"
|
|
651
651
|
LOKI_MANAGED_MEMORY="${LOKI_MANAGED_MEMORY:-false}"
|
|
652
|
-
|
|
652
|
+
# v7.0.0 Phase 2: remote->local hydrate on session boot (grandchild of MEMORY).
|
|
653
|
+
# Pulls semantic patterns + procedural skills once at init_loki_dir time.
|
|
654
|
+
LOKI_MANAGED_MEMORY_HYDRATE="${LOKI_MANAGED_MEMORY_HYDRATE:-false}"
|
|
655
|
+
# v7.0.0 Phase 3+4 foundation: umbrella flag for the multiagent-session path.
|
|
656
|
+
# Gates every providers/managed.py entry point (run_council,
|
|
657
|
+
# run_completion_council). Off by default because the Managed Agents
|
|
658
|
+
# multiagent surface is a research preview.
|
|
659
|
+
LOKI_EXPERIMENTAL_MANAGED_AGENTS="${LOKI_EXPERIMENTAL_MANAGED_AGENTS:-false}"
|
|
660
|
+
# v7.0.0 Phase 3 (T5): managed code-review council. Routes run_code_review
|
|
661
|
+
# through providers/managed.py::run_council when true. Requires parent +
|
|
662
|
+
# umbrella.
|
|
663
|
+
LOKI_EXPERIMENTAL_MANAGED_REVIEW="${LOKI_EXPERIMENTAL_MANAGED_REVIEW:-false}"
|
|
664
|
+
# v7.0.0 Phase 4 (T6): managed completion council. Routes council_should_stop
|
|
665
|
+
# through providers/managed.py::run_completion_council when true. Requires
|
|
666
|
+
# parent + umbrella.
|
|
667
|
+
LOKI_EXPERIMENTAL_MANAGED_COUNCIL="${LOKI_EXPERIMENTAL_MANAGED_COUNCIL:-false}"
|
|
668
|
+
export LOKI_MANAGED_AGENTS LOKI_MANAGED_MEMORY LOKI_MANAGED_MEMORY_HYDRATE LOKI_EXPERIMENTAL_MANAGED_AGENTS LOKI_EXPERIMENTAL_MANAGED_REVIEW LOKI_EXPERIMENTAL_MANAGED_COUNCIL
|
|
653
669
|
|
|
654
670
|
# Fail-fast: child on with parent off is a misconfiguration.
|
|
655
671
|
if [ "$LOKI_MANAGED_MEMORY" = "true" ] && [ "$LOKI_MANAGED_AGENTS" != "true" ]; then
|
|
@@ -657,6 +673,47 @@ if [ "$LOKI_MANAGED_MEMORY" = "true" ] && [ "$LOKI_MANAGED_AGENTS" != "true" ];
|
|
|
657
673
|
exit 2
|
|
658
674
|
fi
|
|
659
675
|
|
|
676
|
+
# Phase 2 fail-fast: HYDRATE is a grandchild of MEMORY.
|
|
677
|
+
if [ "$LOKI_MANAGED_MEMORY_HYDRATE" = "true" ] && [ "$LOKI_MANAGED_MEMORY" != "true" ]; then
|
|
678
|
+
echo "ERROR: LOKI_MANAGED_MEMORY_HYDRATE=true requires LOKI_MANAGED_MEMORY=true" >&2
|
|
679
|
+
exit 2
|
|
680
|
+
fi
|
|
681
|
+
|
|
682
|
+
# Same fail-fast for the experimental multiagent session path.
|
|
683
|
+
if [ "$LOKI_EXPERIMENTAL_MANAGED_AGENTS" = "true" ] && [ "$LOKI_MANAGED_AGENTS" != "true" ]; then
|
|
684
|
+
echo "ERROR: LOKI_EXPERIMENTAL_MANAGED_AGENTS=true requires LOKI_MANAGED_AGENTS=true" >&2
|
|
685
|
+
exit 2
|
|
686
|
+
fi
|
|
687
|
+
|
|
688
|
+
# Phase 3 fail-fast: REVIEW requires parent AND umbrella.
|
|
689
|
+
if [ "$LOKI_EXPERIMENTAL_MANAGED_REVIEW" = "true" ]; then
|
|
690
|
+
if [ "$LOKI_MANAGED_AGENTS" != "true" ]; then
|
|
691
|
+
echo "ERROR: LOKI_EXPERIMENTAL_MANAGED_REVIEW=true requires LOKI_MANAGED_AGENTS=true" >&2
|
|
692
|
+
exit 2
|
|
693
|
+
fi
|
|
694
|
+
if [ "$LOKI_EXPERIMENTAL_MANAGED_AGENTS" != "true" ]; then
|
|
695
|
+
echo "ERROR: LOKI_EXPERIMENTAL_MANAGED_REVIEW=true requires LOKI_EXPERIMENTAL_MANAGED_AGENTS=true" >&2
|
|
696
|
+
exit 2
|
|
697
|
+
fi
|
|
698
|
+
fi
|
|
699
|
+
|
|
700
|
+
# Phase 4 fail-fast: COUNCIL requires parent AND umbrella.
|
|
701
|
+
if [ "$LOKI_EXPERIMENTAL_MANAGED_COUNCIL" = "true" ]; then
|
|
702
|
+
if [ "$LOKI_MANAGED_AGENTS" != "true" ]; then
|
|
703
|
+
echo "ERROR: LOKI_EXPERIMENTAL_MANAGED_COUNCIL=true requires LOKI_MANAGED_AGENTS=true" >&2
|
|
704
|
+
exit 2
|
|
705
|
+
fi
|
|
706
|
+
if [ "$LOKI_EXPERIMENTAL_MANAGED_AGENTS" != "true" ]; then
|
|
707
|
+
echo "ERROR: LOKI_EXPERIMENTAL_MANAGED_COUNCIL=true requires LOKI_EXPERIMENTAL_MANAGED_AGENTS=true" >&2
|
|
708
|
+
exit 2
|
|
709
|
+
fi
|
|
710
|
+
fi
|
|
711
|
+
|
|
712
|
+
# Research-preview warning banner.
|
|
713
|
+
if [ "$LOKI_EXPERIMENTAL_MANAGED_AGENTS" = "true" ]; then
|
|
714
|
+
echo "WARN: LOKI_EXPERIMENTAL_MANAGED_AGENTS uses Managed Agents research preview; expect beta churn." >&2
|
|
715
|
+
fi
|
|
716
|
+
|
|
660
717
|
# Parallel Workflows (Git Worktrees)
|
|
661
718
|
PARALLEL_MODE=${LOKI_PARALLEL_MODE:-false}
|
|
662
719
|
MAX_WORKTREES=${LOKI_MAX_WORKTREES:-5}
|
|
@@ -983,6 +1040,41 @@ emit_event_json() {
|
|
|
983
1040
|
log_debug "Event: $event_type - $json_data"
|
|
984
1041
|
}
|
|
985
1042
|
|
|
1043
|
+
# v7.0.2: Bash helper to emit a managed-agents event to the dashboard's
|
|
1044
|
+
# managed event log (.loki/managed/events.ndjson). Mirrors the Python
|
|
1045
|
+
# emit_managed_event helper so bash callers can land events in the same
|
|
1046
|
+
# stream the dashboard reads. Schema: {ts, type, payload}.
|
|
1047
|
+
emit_managed_event_bash() {
|
|
1048
|
+
local event_type="$1"
|
|
1049
|
+
shift
|
|
1050
|
+
local target_dir="${TARGET_DIR:-.}"
|
|
1051
|
+
local events_file="$target_dir/.loki/managed/events.ndjson"
|
|
1052
|
+
mkdir -p "$target_dir/.loki/managed"
|
|
1053
|
+
|
|
1054
|
+
local timestamp
|
|
1055
|
+
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1056
|
+
|
|
1057
|
+
# Build payload JSON from key=value args (same convention as emit_event_json)
|
|
1058
|
+
local payload="{"
|
|
1059
|
+
local first=true
|
|
1060
|
+
while [ $# -gt 0 ]; do
|
|
1061
|
+
local key="${1%%=*}"
|
|
1062
|
+
local value="${1#*=}"
|
|
1063
|
+
if [ "$first" = true ]; then first=false; else payload+=","; fi
|
|
1064
|
+
if [[ "$value" =~ ^[0-9]+\.?[0-9]*$ ]] || [[ "$value" =~ ^(true|false|null)$ ]]; then
|
|
1065
|
+
payload+="\"$key\":$value"
|
|
1066
|
+
else
|
|
1067
|
+
value=$(printf '%s' "$value" | sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g')
|
|
1068
|
+
payload+="\"$key\":\"$value\""
|
|
1069
|
+
fi
|
|
1070
|
+
shift
|
|
1071
|
+
done
|
|
1072
|
+
payload+="}"
|
|
1073
|
+
|
|
1074
|
+
local json_event="{\"ts\":\"$timestamp\",\"type\":\"$event_type\",\"payload\":$payload}"
|
|
1075
|
+
echo "$json_event" >> "$events_file"
|
|
1076
|
+
}
|
|
1077
|
+
|
|
986
1078
|
# Emit event to .loki/events/pending/ directory (for event bus subscribers)
|
|
987
1079
|
# Used by OTEL bridge and other enterprise services that watch the pending dir.
|
|
988
1080
|
# Usage: emit_event_pending <type> [key=value ...]
|
|
@@ -3021,6 +3113,30 @@ BUDGET_EOF
|
|
|
3021
3113
|
log_info "Budget limit set: \$$BUDGET_LIMIT"
|
|
3022
3114
|
fi
|
|
3023
3115
|
|
|
3116
|
+
# v7.0.0 Phase 2: remote->local hydrate. Runs ONCE at session boot (not
|
|
3117
|
+
# per iteration) to pull semantic patterns + procedural skills from the
|
|
3118
|
+
# managed store into .loki/memory/semantic/patterns.json and
|
|
3119
|
+
# .loki/memory/skills/*.json. Gated on parent + MEMORY + HYDRATE; all
|
|
3120
|
+
# three must be "true". 10s hard timeout so a slow remote never blocks
|
|
3121
|
+
# startup. Idempotent (sentinel: .loki/managed/hydrate.lock).
|
|
3122
|
+
if [ "$LOKI_MANAGED_AGENTS" = "true" ] \
|
|
3123
|
+
&& [ "$LOKI_MANAGED_MEMORY" = "true" ] \
|
|
3124
|
+
&& [ "$LOKI_MANAGED_MEMORY_HYDRATE" = "true" ]; then
|
|
3125
|
+
local _hydrate_target="${TARGET_DIR:-$(pwd)}"
|
|
3126
|
+
local _hydrate_out
|
|
3127
|
+
_hydrate_out=$(
|
|
3128
|
+
cd "$PROJECT_DIR" 2>/dev/null && \
|
|
3129
|
+
LOKI_TARGET_DIR="$_hydrate_target" \
|
|
3130
|
+
timeout 10 python3 -m memory.managed_memory.retrieve --hydrate 2>/dev/null || true
|
|
3131
|
+
)
|
|
3132
|
+
if [ -n "$_hydrate_out" ]; then
|
|
3133
|
+
log_info "Managed hydrate: $_hydrate_out"
|
|
3134
|
+
else
|
|
3135
|
+
LOKI_TARGET_DIR="$_hydrate_target" \
|
|
3136
|
+
python3 -c "from memory.managed_memory.events import emit_managed_event; emit_managed_event('managed_memory_hydrate_timeout', {'phase': 'init'})" 2>/dev/null || true
|
|
3137
|
+
fi
|
|
3138
|
+
fi
|
|
3139
|
+
|
|
3024
3140
|
log_info "Loki directory initialized: .loki/"
|
|
3025
3141
|
}
|
|
3026
3142
|
|
|
@@ -5869,6 +5985,252 @@ run_magic_debate_gate() {
|
|
|
5869
5985
|
# architecture-strategist always included, 2 more selected by keyword scoring
|
|
5870
5986
|
# ============================================================================
|
|
5871
5987
|
|
|
5988
|
+
# Write managed-council verdicts into the legacy per-reviewer .txt layout so
|
|
5989
|
+
# the dashboard quality panel (which only reads .loki/quality/reviews/$id/*.txt)
|
|
5990
|
+
# stays functional. Called from the managed branch of run_code_review().
|
|
5991
|
+
# Single-writer invariant: either this helper writes the files, or the legacy
|
|
5992
|
+
# CLI fan-out does -- never both for the same review_id.
|
|
5993
|
+
council_verdicts_to_txt_files() {
|
|
5994
|
+
local review_id="$1"
|
|
5995
|
+
local verdicts_json="$2"
|
|
5996
|
+
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
5997
|
+
local review_dir="$loki_dir/quality/reviews/$review_id"
|
|
5998
|
+
mkdir -p "$review_dir"
|
|
5999
|
+
|
|
6000
|
+
# Use python3 to fan the JSON verdict list out to individual .txt files
|
|
6001
|
+
# in the same VERDICT/FINDINGS format the legacy parser expects.
|
|
6002
|
+
local out_dir_env="$review_dir"
|
|
6003
|
+
export LOKI_COUNCIL_OUT_DIR="$out_dir_env"
|
|
6004
|
+
export LOKI_COUNCIL_VERDICTS_JSON="$verdicts_json"
|
|
6005
|
+
python3 << 'COUNCIL_WRITE'
|
|
6006
|
+
import json
|
|
6007
|
+
import os
|
|
6008
|
+
import re
|
|
6009
|
+
|
|
6010
|
+
out_dir = os.environ["LOKI_COUNCIL_OUT_DIR"]
|
|
6011
|
+
raw = os.environ.get("LOKI_COUNCIL_VERDICTS_JSON", "").strip()
|
|
6012
|
+
if not raw:
|
|
6013
|
+
raise SystemExit(0)
|
|
6014
|
+
|
|
6015
|
+
try:
|
|
6016
|
+
payload = json.loads(raw)
|
|
6017
|
+
except json.JSONDecodeError:
|
|
6018
|
+
raise SystemExit("council_verdicts_to_txt_files: invalid JSON")
|
|
6019
|
+
|
|
6020
|
+
if isinstance(payload, dict):
|
|
6021
|
+
verdicts = payload.get("verdicts") or []
|
|
6022
|
+
else:
|
|
6023
|
+
verdicts = payload or []
|
|
6024
|
+
|
|
6025
|
+
SAFE_NAME = re.compile(r"[^A-Za-z0-9._-]+")
|
|
6026
|
+
DOT_RUN = re.compile(r"\.{2,}")
|
|
6027
|
+
|
|
6028
|
+
def _pool_name(v):
|
|
6029
|
+
name = v.get("pool_name") or v.get("name") or v.get("agent_id") or "reviewer"
|
|
6030
|
+
cleaned = SAFE_NAME.sub("-", str(name))
|
|
6031
|
+
# Defend against path-traversal via ".." in pool names.
|
|
6032
|
+
cleaned = DOT_RUN.sub("-", cleaned).strip("-.")
|
|
6033
|
+
return cleaned[:80] or "reviewer"
|
|
6034
|
+
|
|
6035
|
+
def _verdict_token(v):
|
|
6036
|
+
token = str(v.get("verdict") or "").strip().upper()
|
|
6037
|
+
if token in ("APPROVE", "PASS"):
|
|
6038
|
+
return "PASS"
|
|
6039
|
+
if token in ("REQUEST_CHANGES", "REJECT", "FAIL"):
|
|
6040
|
+
return "FAIL"
|
|
6041
|
+
return "PASS" # ABSTAIN => PASS per legacy behavior
|
|
6042
|
+
|
|
6043
|
+
def _findings(v):
|
|
6044
|
+
rationale = (v.get("rationale") or "").strip()
|
|
6045
|
+
sev = v.get("severity")
|
|
6046
|
+
if not rationale:
|
|
6047
|
+
return "- None"
|
|
6048
|
+
lines = []
|
|
6049
|
+
for line in rationale.splitlines():
|
|
6050
|
+
line = line.strip()
|
|
6051
|
+
if not line:
|
|
6052
|
+
continue
|
|
6053
|
+
if line.lstrip().startswith("- ["):
|
|
6054
|
+
lines.append(line)
|
|
6055
|
+
else:
|
|
6056
|
+
tag = f"[{sev.capitalize()}]" if sev else "[Medium]"
|
|
6057
|
+
lines.append(f"- {tag} {line}")
|
|
6058
|
+
return "\n".join(lines) if lines else "- None"
|
|
6059
|
+
|
|
6060
|
+
for v in verdicts:
|
|
6061
|
+
if not isinstance(v, dict):
|
|
6062
|
+
continue
|
|
6063
|
+
name = _pool_name(v)
|
|
6064
|
+
path = os.path.join(out_dir, f"{name}.txt")
|
|
6065
|
+
body = f"VERDICT: {_verdict_token(v)}\nFINDINGS:\n{_findings(v)}\n"
|
|
6066
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
6067
|
+
f.write(body)
|
|
6068
|
+
COUNCIL_WRITE
|
|
6069
|
+
local rc=$?
|
|
6070
|
+
unset LOKI_COUNCIL_OUT_DIR LOKI_COUNCIL_VERDICTS_JSON
|
|
6071
|
+
return $rc
|
|
6072
|
+
}
|
|
6073
|
+
|
|
6074
|
+
# Execute the managed-agents multiagent council path. Writes legacy .txt
|
|
6075
|
+
# files via council_verdicts_to_txt_files() on success so the existing
|
|
6076
|
+
# aggregation loop below can read them exactly like the CLI path.
|
|
6077
|
+
# Returns 0 on success, 1 on ManagedUnavailable (caller should fall back).
|
|
6078
|
+
_run_managed_review_council() {
|
|
6079
|
+
local review_id="$1"
|
|
6080
|
+
local diff_file="$2"
|
|
6081
|
+
local files_file="$3"
|
|
6082
|
+
local review_dir="${TARGET_DIR:-.}/.loki/quality/reviews/$review_id"
|
|
6083
|
+
mkdir -p "$review_dir"
|
|
6084
|
+
|
|
6085
|
+
export LOKI_MANAGED_REVIEW_ID="$review_id"
|
|
6086
|
+
export LOKI_MANAGED_REVIEW_DIFF_FILE="$diff_file"
|
|
6087
|
+
export LOKI_MANAGED_REVIEW_FILES_FILE="$files_file"
|
|
6088
|
+
export LOKI_MANAGED_REVIEW_OUT_JSON="$review_dir/managed_result.json"
|
|
6089
|
+
local project_dir_env="${PROJECT_DIR:-.}"
|
|
6090
|
+
export LOKI_MANAGED_REVIEW_PROJECT_DIR="$project_dir_env"
|
|
6091
|
+
|
|
6092
|
+
local result_json
|
|
6093
|
+
result_json=$(python3 << 'MANAGED_REVIEW' 2>&1
|
|
6094
|
+
import json
|
|
6095
|
+
import os
|
|
6096
|
+
import sys
|
|
6097
|
+
|
|
6098
|
+
project_dir = os.environ.get("LOKI_MANAGED_REVIEW_PROJECT_DIR", ".")
|
|
6099
|
+
if project_dir and project_dir not in sys.path:
|
|
6100
|
+
sys.path.insert(0, project_dir)
|
|
6101
|
+
|
|
6102
|
+
try:
|
|
6103
|
+
from providers import managed as managed_mod
|
|
6104
|
+
except Exception as e:
|
|
6105
|
+
print(json.dumps({"status": "unavailable", "reason": f"import_failed: {e}"}))
|
|
6106
|
+
sys.exit(0)
|
|
6107
|
+
|
|
6108
|
+
# Test hook: allow tests to inject a fake run_council by setting
|
|
6109
|
+
# LOKI_MANAGED_REVIEW_FAKE_MODULE to a dotted path exposing run_council.
|
|
6110
|
+
fake_mod = os.environ.get("LOKI_MANAGED_REVIEW_FAKE_MODULE", "").strip()
|
|
6111
|
+
if fake_mod:
|
|
6112
|
+
try:
|
|
6113
|
+
import importlib
|
|
6114
|
+
fm = importlib.import_module(fake_mod)
|
|
6115
|
+
if hasattr(fm, "install"):
|
|
6116
|
+
fm.install(managed_mod)
|
|
6117
|
+
except Exception as e:
|
|
6118
|
+
print(json.dumps({"status": "unavailable", "reason": f"fake_install_failed: {e}"}))
|
|
6119
|
+
sys.exit(0)
|
|
6120
|
+
|
|
6121
|
+
if not managed_mod.is_enabled():
|
|
6122
|
+
print(json.dumps({"status": "unavailable", "reason": "is_enabled_false"}))
|
|
6123
|
+
sys.exit(0)
|
|
6124
|
+
|
|
6125
|
+
diff_path = os.environ.get("LOKI_MANAGED_REVIEW_DIFF_FILE", "")
|
|
6126
|
+
files_path = os.environ.get("LOKI_MANAGED_REVIEW_FILES_FILE", "")
|
|
6127
|
+
diff_text = ""
|
|
6128
|
+
files_text = ""
|
|
6129
|
+
if diff_path and os.path.exists(diff_path):
|
|
6130
|
+
with open(diff_path, "r", encoding="utf-8", errors="replace") as f:
|
|
6131
|
+
diff_text = f.read()
|
|
6132
|
+
if files_path and os.path.exists(files_path):
|
|
6133
|
+
with open(files_path, "r", encoding="utf-8", errors="replace") as f:
|
|
6134
|
+
files_text = f.read()
|
|
6135
|
+
|
|
6136
|
+
target_paths = [p.strip() for p in files_text.splitlines() if p.strip()]
|
|
6137
|
+
|
|
6138
|
+
pool = ["security-sentinel", "test-coverage-auditor", "performance-oracle"]
|
|
6139
|
+
context = {
|
|
6140
|
+
"diff": diff_text,
|
|
6141
|
+
"files": target_paths,
|
|
6142
|
+
"target_paths": target_paths,
|
|
6143
|
+
}
|
|
6144
|
+
|
|
6145
|
+
try:
|
|
6146
|
+
result = managed_mod.run_council(pool, context, timeout_s=300)
|
|
6147
|
+
except managed_mod.ManagedUnavailable as e:
|
|
6148
|
+
print(json.dumps({"status": "unavailable", "reason": str(e)}))
|
|
6149
|
+
sys.exit(0)
|
|
6150
|
+
except Exception as e:
|
|
6151
|
+
# Anything else is unexpected; bubble up as unavailable so the caller
|
|
6152
|
+
# falls back rather than aborting the iteration.
|
|
6153
|
+
print(json.dumps({"status": "unavailable", "reason": f"unexpected: {e}"}))
|
|
6154
|
+
sys.exit(0)
|
|
6155
|
+
|
|
6156
|
+
verdicts_out = []
|
|
6157
|
+
for v in (result.verdicts or []):
|
|
6158
|
+
verdicts_out.append({
|
|
6159
|
+
"agent_id": getattr(v, "agent_id", ""),
|
|
6160
|
+
"pool_name": getattr(v, "pool_name", ""),
|
|
6161
|
+
"verdict": getattr(v, "verdict", ""),
|
|
6162
|
+
"rationale": getattr(v, "rationale", ""),
|
|
6163
|
+
"severity": getattr(v, "severity", None),
|
|
6164
|
+
})
|
|
6165
|
+
out = {
|
|
6166
|
+
"status": "ok",
|
|
6167
|
+
"verdicts": verdicts_out,
|
|
6168
|
+
"session_id": getattr(result, "session_id", None),
|
|
6169
|
+
"elapsed_ms": getattr(result, "elapsed_ms", 0),
|
|
6170
|
+
"partial": getattr(result, "partial", False),
|
|
6171
|
+
}
|
|
6172
|
+
print(json.dumps(out))
|
|
6173
|
+
MANAGED_REVIEW
|
|
6174
|
+
)
|
|
6175
|
+
local py_rc=$?
|
|
6176
|
+
unset LOKI_MANAGED_REVIEW_ID LOKI_MANAGED_REVIEW_DIFF_FILE LOKI_MANAGED_REVIEW_FILES_FILE
|
|
6177
|
+
unset LOKI_MANAGED_REVIEW_OUT_JSON LOKI_MANAGED_REVIEW_PROJECT_DIR
|
|
6178
|
+
|
|
6179
|
+
if [ $py_rc -ne 0 ] || [ -z "$result_json" ]; then
|
|
6180
|
+
emit_event_json "managed_agents_fallback" \
|
|
6181
|
+
"op=run_code_review" \
|
|
6182
|
+
"reason=subprocess_failed" \
|
|
6183
|
+
"review_id=$review_id"
|
|
6184
|
+
emit_managed_event_bash "managed_agents_fallback" \
|
|
6185
|
+
"op=run_code_review" \
|
|
6186
|
+
"reason=subprocess_failed" \
|
|
6187
|
+
"review_id=$review_id"
|
|
6188
|
+
return 1
|
|
6189
|
+
fi
|
|
6190
|
+
|
|
6191
|
+
local status
|
|
6192
|
+
status=$(printf '%s' "$result_json" | python3 -c "import json,sys; d=json.loads(sys.stdin.read() or '{}'); print(d.get('status',''))" 2>/dev/null || echo "")
|
|
6193
|
+
|
|
6194
|
+
if [ "$status" != "ok" ]; then
|
|
6195
|
+
local reason
|
|
6196
|
+
reason=$(printf '%s' "$result_json" | python3 -c "import json,sys; d=json.loads(sys.stdin.read() or '{}'); print(d.get('reason',''))" 2>/dev/null || echo "")
|
|
6197
|
+
emit_event_json "managed_agents_fallback" \
|
|
6198
|
+
"op=run_code_review" \
|
|
6199
|
+
"reason=managed_unavailable" \
|
|
6200
|
+
"detail=${reason//\"/}" \
|
|
6201
|
+
"review_id=$review_id"
|
|
6202
|
+
emit_managed_event_bash "managed_agents_fallback" \
|
|
6203
|
+
"op=run_code_review" \
|
|
6204
|
+
"reason=managed_unavailable" \
|
|
6205
|
+
"detail=${reason//\"/}" \
|
|
6206
|
+
"review_id=$review_id"
|
|
6207
|
+
return 1
|
|
6208
|
+
fi
|
|
6209
|
+
|
|
6210
|
+
# Persist the raw managed result for observability and write legacy .txt
|
|
6211
|
+
# files for the dashboard panel / aggregation loop.
|
|
6212
|
+
printf '%s\n' "$result_json" > "$review_dir/managed_result.json"
|
|
6213
|
+
if ! council_verdicts_to_txt_files "$review_id" "$result_json"; then
|
|
6214
|
+
emit_event_json "managed_agents_fallback" \
|
|
6215
|
+
"op=run_code_review" \
|
|
6216
|
+
"reason=verdict_write_failed" \
|
|
6217
|
+
"review_id=$review_id"
|
|
6218
|
+
emit_managed_event_bash "managed_agents_fallback" \
|
|
6219
|
+
"op=run_code_review" \
|
|
6220
|
+
"reason=verdict_write_failed" \
|
|
6221
|
+
"review_id=$review_id"
|
|
6222
|
+
return 1
|
|
6223
|
+
fi
|
|
6224
|
+
|
|
6225
|
+
emit_event_json "managed_review_council_ok" \
|
|
6226
|
+
"review_id=$review_id" \
|
|
6227
|
+
"iteration=${ITERATION_COUNT:-0}"
|
|
6228
|
+
emit_managed_event_bash "managed_review_council_ok" \
|
|
6229
|
+
"review_id=$review_id" \
|
|
6230
|
+
"iteration=${ITERATION_COUNT:-0}"
|
|
6231
|
+
return 0
|
|
6232
|
+
}
|
|
6233
|
+
|
|
5872
6234
|
run_code_review() {
|
|
5873
6235
|
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
5874
6236
|
local review_dir="$loki_dir/quality/reviews"
|
|
@@ -5888,6 +6250,51 @@ run_code_review() {
|
|
|
5888
6250
|
changed_files=$(git -C "${TARGET_DIR:-.}" diff --name-only HEAD~1 2>/dev/null || git -C "${TARGET_DIR:-.}" diff --name-only --cached 2>/dev/null || echo "")
|
|
5889
6251
|
|
|
5890
6252
|
log_header "CODE REVIEW: $review_id"
|
|
6253
|
+
|
|
6254
|
+
# Phase 3 (v7.0.0): managed code-review council. When the flag is on,
|
|
6255
|
+
# route to providers/managed.py::run_council. On ManagedUnavailable,
|
|
6256
|
+
# emit a fallback event and drop through to the legacy CLI fan-out
|
|
6257
|
+
# below -- the existing v6.83.1 behavior is preserved.
|
|
6258
|
+
if [ "${LOKI_EXPERIMENTAL_MANAGED_REVIEW:-false}" = "true" ]; then
|
|
6259
|
+
local managed_diff_file="$review_dir/$review_id/diff.txt"
|
|
6260
|
+
local managed_files_file="$review_dir/$review_id/files.txt"
|
|
6261
|
+
printf '%s\n' "$diff_content" > "$managed_diff_file"
|
|
6262
|
+
printf '%s\n' "$changed_files" > "$managed_files_file"
|
|
6263
|
+
log_info "Managed review council: attempting multiagent session (Phase 3)"
|
|
6264
|
+
if _run_managed_review_council "$review_id" "$managed_diff_file" "$managed_files_file"; then
|
|
6265
|
+
log_info "Managed review council: verdicts written, skipping CLI fan-out"
|
|
6266
|
+
# Managed path wrote legacy .txt files; skip CLI fan-out but let
|
|
6267
|
+
# the aggregation step run by setting a minimal selection.json
|
|
6268
|
+
# the downstream loop can read.
|
|
6269
|
+
emit_event_json "code_review_complete" \
|
|
6270
|
+
"review_id=$review_id" \
|
|
6271
|
+
"source=managed" \
|
|
6272
|
+
"iteration=${ITERATION_COUNT:-0}"
|
|
6273
|
+
# Build a selection.json so any downstream consumer can find the
|
|
6274
|
+
# reviewer list. Mirrors the shape the CLI path writes below.
|
|
6275
|
+
python3 - "$review_dir/$review_id/selection.json" << 'MANAGED_SELECTION'
|
|
6276
|
+
import json
|
|
6277
|
+
import sys
|
|
6278
|
+
|
|
6279
|
+
path = sys.argv[1]
|
|
6280
|
+
selection = {
|
|
6281
|
+
"reviewers": [
|
|
6282
|
+
{"name": "security-sentinel", "focus": "managed", "checks": "managed council"},
|
|
6283
|
+
{"name": "test-coverage-auditor", "focus": "managed", "checks": "managed council"},
|
|
6284
|
+
{"name": "performance-oracle", "focus": "managed", "checks": "managed council"},
|
|
6285
|
+
],
|
|
6286
|
+
"scores": {},
|
|
6287
|
+
"pool_size": 3,
|
|
6288
|
+
"source": "managed",
|
|
6289
|
+
}
|
|
6290
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
6291
|
+
json.dump(selection, f)
|
|
6292
|
+
MANAGED_SELECTION
|
|
6293
|
+
return 0
|
|
6294
|
+
fi
|
|
6295
|
+
log_warn "Managed review council unavailable; falling back to CLI fan-out"
|
|
6296
|
+
fi
|
|
6297
|
+
|
|
5891
6298
|
log_info "Selecting 3 specialist reviewers from pool..."
|
|
5892
6299
|
|
|
5893
6300
|
# Write diff/files to temp files for python to read (avoid env var size limits)
|