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/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
- export LOKI_MANAGED_AGENTS LOKI_MANAGED_MEMORY
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)
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.83.1"
10
+ __version__ = "7.0.2"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try: