loki-mode 7.66.1 → 7.68.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/app-runner.sh +128 -7
- package/autonomy/loki +202 -87
- package/autonomy/run.sh +161 -87
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +25 -4
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/loki.js +112 -110
- package/mcp/__init__.py +1 -1
- package/memory/consolidation.py +86 -12
- package/memory/retrieval.py +18 -1
- package/memory/storage.py +161 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/autonomy/run.sh
CHANGED
|
@@ -1725,12 +1725,25 @@ detect_complexity() {
|
|
|
1725
1725
|
# Markdown PRD: count headers and checkboxes
|
|
1726
1726
|
feature_count=$(grep -c "^##\|^- \[" "$prd_path" 2>/dev/null || echo "0")
|
|
1727
1727
|
fi
|
|
1728
|
+
# WAVE8 FIX run.sh-provider-F1 (HIGH): grep -c prints "0" AND exits 1 on
|
|
1729
|
+
# zero matches; with the '|| echo "0"' fallback that yields "0\n0", which
|
|
1730
|
+
# crashes the integer tests below ([: 0\n0: integer expression expected)
|
|
1731
|
+
# and silently drops complexity from simple->standard. Strip to digits
|
|
1732
|
+
# after every assignment path (jq, both greps), mirroring file_count:1688.
|
|
1733
|
+
# "0\n0" -> "00" -> arithmetically 0.
|
|
1734
|
+
feature_count="${feature_count:-0}"
|
|
1735
|
+
feature_count="${feature_count//[^0-9]/}"
|
|
1736
|
+
feature_count="${feature_count:-0}"
|
|
1728
1737
|
|
|
1729
1738
|
# Count distinct sections (h2/h3 headers) for structural complexity (#74)
|
|
1730
1739
|
local section_count=0
|
|
1731
1740
|
if [[ "$prd_path" != *.json ]]; then
|
|
1732
1741
|
section_count=$(grep -c "^##\|^###" "$prd_path" 2>/dev/null || echo "0")
|
|
1733
1742
|
fi
|
|
1743
|
+
# WAVE8 FIX run.sh-provider-F1: same grep -c double-output guard.
|
|
1744
|
+
section_count="${section_count:-0}"
|
|
1745
|
+
section_count="${section_count//[^0-9]/}"
|
|
1746
|
+
section_count="${section_count:-0}"
|
|
1734
1747
|
|
|
1735
1748
|
# PRD complexity uses content length, feature count, AND structural depth (#74)
|
|
1736
1749
|
# A PRD with multiple sections or substantial content is not "simple" even with few project files
|
|
@@ -1919,61 +1932,32 @@ get_provider_tier_param() {
|
|
|
1919
1932
|
}
|
|
1920
1933
|
|
|
1921
1934
|
#===============================================================================
|
|
1922
|
-
# Provider Spawn Timeout (
|
|
1923
|
-
#
|
|
1924
|
-
#
|
|
1935
|
+
# Provider Spawn Timeout (removed WAVE9 / provider-F2)
|
|
1936
|
+
#
|
|
1937
|
+
# A former invoke_with_timeout() helper (v6.0.0) wrapped a command in
|
|
1938
|
+
# `timeout <s> "$@"` with a retry loop. It was never wired to the main
|
|
1939
|
+
# provider invocation and is intentionally not revived, for two reasons:
|
|
1940
|
+
#
|
|
1941
|
+
# 1. No safe generous default. The main provider call is a long-running
|
|
1942
|
+
# autonomous coding agent. Any fixed timeout short enough to catch a
|
|
1943
|
+
# hang would also kill legitimate multi-minute iterations, and there is
|
|
1944
|
+
# no "generous enough" value that is both safe and useful by default.
|
|
1945
|
+
# 2. Wrong retry semantics. The helper re-ran the same command on timeout.
|
|
1946
|
+
# Re-running a coding agent mid-work (it may have already edited files)
|
|
1947
|
+
# is actively harmful, not protective.
|
|
1948
|
+
#
|
|
1949
|
+
# The main invocation is also a pipeline (`claude | tee | python3`), which a
|
|
1950
|
+
# positional-arg `timeout "$@"` wrapper cannot wrap at all. Interrupting a
|
|
1951
|
+
# hung provider is handled by the SIGINT trap (kill_provider_child) instead.
|
|
1952
|
+
#
|
|
1953
|
+
# The `loki config spawn_timeout` / `spawn_retries` knobs (autonomy/loki) and
|
|
1954
|
+
# the config->env mapping in this file (`'spawn_timeout':'LOKI_SPAWN_TIMEOUT'`
|
|
1955
|
+
# in the config loader) still export LOKI_SPAWN_TIMEOUT / LOKI_SPAWN_RETRIES,
|
|
1956
|
+
# but nothing consumes them now. The mapping line is intentionally left in place
|
|
1957
|
+
# (a config-schema test may enumerate it); the full inert-knob removal spans
|
|
1958
|
+
# autonomy/loki too and is a separate cross-file follow-up.
|
|
1925
1959
|
#===============================================================================
|
|
1926
1960
|
|
|
1927
|
-
PROVIDER_SPAWN_TIMEOUT=${LOKI_SPAWN_TIMEOUT:-120}
|
|
1928
|
-
PROVIDER_SPAWN_RETRIES=${LOKI_SPAWN_RETRIES:-2}
|
|
1929
|
-
|
|
1930
|
-
# Invoke a command with timeout and retry logic
|
|
1931
|
-
# Usage: invoke_with_timeout <timeout_seconds> <retries> <command...>
|
|
1932
|
-
invoke_with_timeout() {
|
|
1933
|
-
local timeout="$1"
|
|
1934
|
-
local max_retries="$2"
|
|
1935
|
-
shift 2
|
|
1936
|
-
|
|
1937
|
-
local attempt=0
|
|
1938
|
-
while [ $attempt -le $max_retries ]; do
|
|
1939
|
-
if [ $attempt -gt 0 ]; then
|
|
1940
|
-
log_warn "Provider spawn retry $attempt/$max_retries..."
|
|
1941
|
-
fi
|
|
1942
|
-
|
|
1943
|
-
local exit_code=0
|
|
1944
|
-
# Use timeout command if available (GNU coreutils or macOS)
|
|
1945
|
-
if command -v timeout &>/dev/null; then
|
|
1946
|
-
timeout "$timeout" "$@"
|
|
1947
|
-
exit_code=$?
|
|
1948
|
-
elif command -v gtimeout &>/dev/null; then
|
|
1949
|
-
gtimeout "$timeout" "$@"
|
|
1950
|
-
exit_code=$?
|
|
1951
|
-
else
|
|
1952
|
-
# Fallback: no timeout wrapper, run directly
|
|
1953
|
-
log_warn "timeout/gtimeout not available - running without timeout enforcement"
|
|
1954
|
-
"$@"
|
|
1955
|
-
exit_code=$?
|
|
1956
|
-
fi
|
|
1957
|
-
|
|
1958
|
-
# Exit code 124 = timeout
|
|
1959
|
-
if [ $exit_code -eq 124 ]; then
|
|
1960
|
-
log_warn "Provider spawn timed out after ${timeout}s (attempt $((attempt+1))/$((max_retries+1)))"
|
|
1961
|
-
((attempt++))
|
|
1962
|
-
continue
|
|
1963
|
-
fi
|
|
1964
|
-
|
|
1965
|
-
return $exit_code
|
|
1966
|
-
done
|
|
1967
|
-
|
|
1968
|
-
log_error "Provider spawn failed after $((max_retries+1)) attempts (timeout=${timeout}s)"
|
|
1969
|
-
# Crash friction (retry_loop): provider spawn exhausted all retries -- a
|
|
1970
|
-
# clear threshold (not a single retry). Best-effort, never blocks.
|
|
1971
|
-
if type loki_crash_friction &>/dev/null; then
|
|
1972
|
-
loki_crash_friction "retry_loop" "provider spawn failed after $((max_retries+1)) attempts" >/dev/null 2>&1 || true
|
|
1973
|
-
fi
|
|
1974
|
-
return 124
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
1961
|
#===============================================================================
|
|
1978
1962
|
# GitHub Integration Functions (v4.1.0)
|
|
1979
1963
|
#===============================================================================
|
|
@@ -8927,6 +8911,63 @@ _dispatch_reviewer() {
|
|
|
8927
8911
|
esac
|
|
8928
8912
|
}
|
|
8929
8913
|
|
|
8914
|
+
# WAVE8 FIX run.sh-F1/F3 (CRITICAL/HIGH): SAFE-DEFAULT verdict classification.
|
|
8915
|
+
# Given a reviewer file, extract the VERDICT: line (tolerant of leading
|
|
8916
|
+
# markdown like '**VERDICT:**' or '# VERDICT:' so fewer reviewers fall to
|
|
8917
|
+
# NO_VERDICT) and classify it as one of: FAIL, PASS, AMBIGUOUS, NONE.
|
|
8918
|
+
# FAIL -> verdict text contains FAIL/REJECT/BLOCK (verbose suffixes like
|
|
8919
|
+
# "FAIL - [Critical] SQLi", "FAIL.", "FAIL (3 criticals)" all match)
|
|
8920
|
+
# PASS -> verdict text contains PASS/APPROVE (and NOT a fail token); this
|
|
8921
|
+
# preserves the deliberate "PASS with concerns" = pass semantics.
|
|
8922
|
+
# AMBIGUOUS -> a VERDICT: line exists but matches neither (unparseable token).
|
|
8923
|
+
# Callers MUST treat this as non-passing (safe direction), never pass.
|
|
8924
|
+
# NONE -> no parseable VERDICT: line at all (empty / missing).
|
|
8925
|
+
# FAIL-first ordering means a verdict naming both (rare) blocks -- the safe way.
|
|
8926
|
+
# Mirrors the council's _council_parse_vote: parse-miss defaults to the safe
|
|
8927
|
+
# (blocking) direction, never to pass.
|
|
8928
|
+
_classify_verdict() {
|
|
8929
|
+
local file="$1"
|
|
8930
|
+
[ -f "$file" ] && [ -s "$file" ] || { echo "NONE"; return 0; }
|
|
8931
|
+
local verdict
|
|
8932
|
+
# Tolerant anchor: optional leading whitespace, then optional markdown
|
|
8933
|
+
# markers (* # >), then optional whitespace, then VERDICT:. This rescues
|
|
8934
|
+
# '**VERDICT:** FAIL', '# VERDICT: PASS', '> VERDICT: FAIL' that the strict
|
|
8935
|
+
# '^VERDICT:' anchor missed (those previously became NO_VERDICT and dropped
|
|
8936
|
+
# the reviewer's dissent).
|
|
8937
|
+
verdict=$(grep -iE "^[[:space:]]*[*#>]*[[:space:]]*VERDICT:" "$file" \
|
|
8938
|
+
| head -1 \
|
|
8939
|
+
| sed -E 's/^[[:space:]]*[*#>]*[[:space:]]*[Vv][Ee][Rr][Dd][Ii][Cc][Tt]:[*[:space:]]*//' \
|
|
8940
|
+
| tr '[:lower:]' '[:upper:]')
|
|
8941
|
+
# Classify on the FIRST verdict TOKEN only, not a substring scan of the whole
|
|
8942
|
+
# despaced line. A whole-line scan is asymmetric and wrong: "PASS, no failures
|
|
8943
|
+
# found" or "PASS - no blocking issues" contain FAIL/BLOCK as substrings and
|
|
8944
|
+
# would misclassify a valid PASS as FAIL (a false-block, and worse, it breaks
|
|
8945
|
+
# the unanimous-PASS Devil's-Advocate trigger -> indirect false-PASS). Take
|
|
8946
|
+
# the leading alphabetic run as the verdict word: "FAIL - [Critical] x" ->
|
|
8947
|
+
# FAIL, "PASS, no failures" -> PASS. Strip leading markdown emphasis first.
|
|
8948
|
+
verdict=$(printf '%s' "$verdict" | sed -E 's/^[*_`[:space:]]+//')
|
|
8949
|
+
local _vtok
|
|
8950
|
+
_vtok=$(printf '%s' "$verdict" | sed -E 's/[^A-Z].*$//')
|
|
8951
|
+
if [ -z "$_vtok" ]; then echo "NONE"; return 0; fi
|
|
8952
|
+
case "$_vtok" in
|
|
8953
|
+
FAIL|FAILED|FAILURE|REJECT|REJECTED|BLOCK|BLOCKED) echo "FAIL" ;;
|
|
8954
|
+
PASS|PASSED|APPROVE|APPROVED|OK) echo "PASS" ;;
|
|
8955
|
+
*) echo "AMBIGUOUS" ;;
|
|
8956
|
+
esac
|
|
8957
|
+
}
|
|
8958
|
+
|
|
8959
|
+
# WAVE8 FIX run.sh-F2 (HIGH): SAFE-DEFAULT severity detection. Returns 0
|
|
8960
|
+
# (blocking) if the reviewer file names a Critical or High severity finding in
|
|
8961
|
+
# any realistic emitted form: bracketed '[Critical]', bold '**Critical**',
|
|
8962
|
+
# 'Severity: High', or a bullet line '- Critical' / '* High'. The strict
|
|
8963
|
+
# bracket-only match previously missed unbracketed forms, so a FAIL naming an
|
|
8964
|
+
# unbracketed Critical was treated as non-blocking. BSD/GNU portable (no \b).
|
|
8965
|
+
_severity_is_blocking() {
|
|
8966
|
+
local file="$1"
|
|
8967
|
+
[ -f "$file" ] || return 1
|
|
8968
|
+
grep -qiE '(\[(critical|high)\])|(\*\*[[:space:]]*(critical|high)[[:space:]]*\*\*)|(severity:?[[:space:]]*(critical|high))|(^[[:space:]]*[-*][[:space:]]+(critical|high)([[:space:]:.,*]|$))' "$file"
|
|
8969
|
+
}
|
|
8970
|
+
|
|
8930
8971
|
run_code_review() {
|
|
8931
8972
|
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
8932
8973
|
local review_dir="$loki_dir/quality/reviews"
|
|
@@ -9284,21 +9325,27 @@ BUILD_PROMPT
|
|
|
9284
9325
|
continue
|
|
9285
9326
|
fi
|
|
9286
9327
|
|
|
9287
|
-
# Extract verdict
|
|
9328
|
+
# Extract + classify verdict (WAVE8 FIX run.sh-F1/F3). _classify_verdict
|
|
9329
|
+
# uses a markdown-tolerant anchor (rescues '**VERDICT:** FAIL') and a
|
|
9330
|
+
# SAFE-DEFAULT contract: FAIL=any FAIL/REJECT/BLOCK token (so verbose
|
|
9331
|
+
# "FAIL - [Critical] SQLi" / "FAIL." / "FAIL (3 criticals)" all count as
|
|
9332
|
+
# FAIL, previously mis-counted as PASS); PASS=PASS/APPROVE; AMBIGUOUS=a
|
|
9333
|
+
# verdict line that parses to neither; NONE=no parseable verdict line.
|
|
9288
9334
|
local verdict
|
|
9289
|
-
verdict=$(
|
|
9290
|
-
|
|
9291
|
-
# FIX A2: a "real verdict" is
|
|
9292
|
-
#
|
|
9293
|
-
#
|
|
9294
|
-
#
|
|
9295
|
-
#
|
|
9296
|
-
#
|
|
9297
|
-
#
|
|
9298
|
-
#
|
|
9299
|
-
#
|
|
9300
|
-
|
|
9301
|
-
|
|
9335
|
+
verdict=$(_classify_verdict "$review_output")
|
|
9336
|
+
|
|
9337
|
+
# FIX A2 + WAVE8 FIX run.sh-F1/F3: a "real verdict" is a parseable
|
|
9338
|
+
# VERDICT line that classifies cleanly to PASS or FAIL. NONE (no usable
|
|
9339
|
+
# verdict line) AND AMBIGUOUS (a verdict line whose token is neither PASS
|
|
9340
|
+
# nor FAIL, e.g. "VERDICT: UNCLEAR") are BOTH routed to the NO_VERDICT
|
|
9341
|
+
# path. This is the SAFE-DEFAULT contract: an unparseable token must NOT
|
|
9342
|
+
# silently pass. It cannot count toward pass_count, and merely bumping
|
|
9343
|
+
# fail_count would be inert (only has_blocking / review_inconclusive gate
|
|
9344
|
+
# the return). So we treat it as a non-real verdict; the
|
|
9345
|
+
# real_verdict_count < reviewer_count check below then makes the review
|
|
9346
|
+
# inconclusive -> bounded retry -> block (FIX 3 machinery).
|
|
9347
|
+
if [ "$verdict" = "NONE" ] || [ "$verdict" = "AMBIGUOUS" ]; then
|
|
9348
|
+
log_warn "Reviewer $reviewer_name returned no usable verdict (empty, unparseable, or ambiguous token)"
|
|
9302
9349
|
verdicts_summary="${verdicts_summary}${reviewer_name}:NO_VERDICT "
|
|
9303
9350
|
((no_output_count++))
|
|
9304
9351
|
continue
|
|
@@ -9306,8 +9353,9 @@ BUILD_PROMPT
|
|
|
9306
9353
|
((real_verdict_count++))
|
|
9307
9354
|
if [ "$verdict" = "FAIL" ]; then
|
|
9308
9355
|
((fail_count++))
|
|
9309
|
-
# Check for Critical/High severity findings
|
|
9310
|
-
|
|
9356
|
+
# Check for Critical/High severity findings (bracketed OR unbracketed
|
|
9357
|
+
# OR bold OR 'Severity:' OR bullet form -- WAVE8 FIX run.sh-F2).
|
|
9358
|
+
if _severity_is_blocking "$review_output"; then
|
|
9311
9359
|
has_blocking=true
|
|
9312
9360
|
log_error "BLOCKING: $reviewer_name found Critical/High severity issues"
|
|
9313
9361
|
else
|
|
@@ -9320,16 +9368,25 @@ BUILD_PROMPT
|
|
|
9320
9368
|
verdicts_summary="${verdicts_summary}${reviewer_name}:${verdict:-UNKNOWN} "
|
|
9321
9369
|
done
|
|
9322
9370
|
|
|
9323
|
-
# Finding #596 FIX A2
|
|
9324
|
-
#
|
|
9325
|
-
#
|
|
9326
|
-
#
|
|
9327
|
-
#
|
|
9371
|
+
# Finding #596 FIX A2 + WAVE8 FIX run.sh-F3: a review is INCONCLUSIVE (=>
|
|
9372
|
+
# blocking) whenever FEWER reviewers returned a usable verdict than were
|
|
9373
|
+
# dispatched. The original gate only fired on real_verdict_count==0 (ALL
|
|
9374
|
+
# reviewers empty); a MIXED review (e.g. 1 of 3 NO_VERDICT, 2 PASS) silently
|
|
9375
|
+
# passed on the surviving majority and dropped the malformed reviewer's
|
|
9376
|
+
# potential dissent (Devil's Advocate never fired). Now ANY NO_VERDICT
|
|
9377
|
+
# reviewer makes the review inconclusive: a dropped reviewer is a dropped
|
|
9378
|
+
# vote, and the safe direction is to refuse to pass on a partial council.
|
|
9379
|
+
# The markdown-tolerant anchor in _classify_verdict already rescues most
|
|
9380
|
+
# real-but-wrapped verdicts, so this fires only on genuinely unusable output.
|
|
9381
|
+
# Optional bounded retry first (LOKI_REVIEW_RETRY=1, default on) so a
|
|
9382
|
+
# transient empty-output blip does not hard-block; the retry re-runs the
|
|
9383
|
+
# whole review with the (now .loki-excluded) diff. Opt out of the block
|
|
9384
|
+
# entirely with LOKI_REVIEW_INCONCLUSIVE_BLOCK=0 (records, never blocks).
|
|
9328
9385
|
local review_inconclusive=false
|
|
9329
|
-
if [ "$reviewer_count" -gt 0 ] && [ "$real_verdict_count" -
|
|
9386
|
+
if [ "$reviewer_count" -gt 0 ] && [ "$real_verdict_count" -lt "$reviewer_count" ]; then
|
|
9330
9387
|
review_inconclusive=true
|
|
9331
|
-
log_error "CODE REVIEW INCONCLUSIVE:
|
|
9332
|
-
log_error "
|
|
9388
|
+
log_error "CODE REVIEW INCONCLUSIVE: only $real_verdict_count of $reviewer_count reviewers returned a usable verdict (no_output=$no_output_count)"
|
|
9389
|
+
log_error " A partial review drops dissent; refusing to pass the gate without every reviewer's verdict."
|
|
9333
9390
|
if [ "${LOKI_REVIEW_RETRY:-1}" = "1" ] && [ "${_LOKI_REVIEW_RETRYING:-0}" != "1" ]; then
|
|
9334
9391
|
log_warn " Retrying code review once (LOKI_REVIEW_RETRY=1)..."
|
|
9335
9392
|
_LOKI_REVIEW_RETRYING=1 run_code_review
|
|
@@ -9437,9 +9494,12 @@ BUILD_DA_PROMPT
|
|
|
9437
9494
|
_dispatch_reviewer "$da_prompt_text" "$da_output" || true
|
|
9438
9495
|
|
|
9439
9496
|
if [ -f "$da_output" ] && [ -s "$da_output" ]; then
|
|
9497
|
+
# WAVE8 FIX run.sh-F1/F2: classify with the shared SAFE-DEFAULT
|
|
9498
|
+
# helpers so a verbose DA "VERDICT: FAIL - [Critical] ..." (and
|
|
9499
|
+
# AMBIGUOUS tokens) and an unbracketed Critical/High both block.
|
|
9440
9500
|
local da_verdict
|
|
9441
|
-
da_verdict=$(
|
|
9442
|
-
if [ "$da_verdict" = "FAIL" ]
|
|
9501
|
+
da_verdict=$(_classify_verdict "$da_output")
|
|
9502
|
+
if { [ "$da_verdict" = "FAIL" ] || [ "$da_verdict" = "AMBIGUOUS" ]; } && _severity_is_blocking "$da_output"; then
|
|
9443
9503
|
has_blocking=true
|
|
9444
9504
|
# Audit accuracy: aggregate.json was written above (line ~8429)
|
|
9445
9505
|
# with has_blocking=false (entering this block requires a
|
|
@@ -9465,7 +9525,7 @@ DA_AGG_PATCH
|
|
|
9465
9525
|
log_error "DEVIL'S ADVOCATE: found Critical/High issue the unanimous council missed -- BLOCK"
|
|
9466
9526
|
{
|
|
9467
9527
|
echo "DEVILS_ADVOCATE_BLOCK: Critical/High found after unanimous PASS"
|
|
9468
|
-
grep -iE
|
|
9528
|
+
grep -iE '(\[(critical|high)\])|(\*\*[[:space:]]*(critical|high)[[:space:]]*\*\*)|(severity:?[[:space:]]*(critical|high))|(^[[:space:]]*[-*][[:space:]]+(critical|high)([[:space:]:.,*]|$))' "$da_output" || true
|
|
9469
9529
|
} >> "$review_dir/$review_id/anti-sycophancy.txt"
|
|
9470
9530
|
else
|
|
9471
9531
|
log_info "Devil's Advocate: no additional Critical/High issues found"
|
|
@@ -9487,16 +9547,16 @@ DA_AGG_PATCH
|
|
|
9487
9547
|
return 1
|
|
9488
9548
|
fi
|
|
9489
9549
|
|
|
9490
|
-
# Finding #596 FIX A2: an inconclusive review (
|
|
9491
|
-
# already exhausted or disabled) blocks
|
|
9492
|
-
# the 'verified before done' promise: a
|
|
9493
|
-
# cannot stand in for a
|
|
9550
|
+
# Finding #596 FIX A2 + WAVE8 FIX run.sh-F3: an inconclusive review (fewer
|
|
9551
|
+
# usable verdicts than reviewers, retry already exhausted or disabled) blocks
|
|
9552
|
+
# unless explicitly opted out. This is the 'verified before done' promise: a
|
|
9553
|
+
# review missing any reviewer's verdict cannot stand in for a full review.
|
|
9494
9554
|
if [ "$review_inconclusive" = "true" ]; then
|
|
9495
9555
|
if [ "${LOKI_REVIEW_INCONCLUSIVE_BLOCK:-1}" = "0" ]; then
|
|
9496
|
-
log_warn "Code review inconclusive (
|
|
9556
|
+
log_warn "Code review inconclusive ($real_verdict_count/$reviewer_count real verdicts) but LOKI_REVIEW_INCONCLUSIVE_BLOCK=0 - not blocking"
|
|
9497
9557
|
return 0
|
|
9498
9558
|
fi
|
|
9499
|
-
log_error "CODE REVIEW BLOCKED: inconclusive (
|
|
9559
|
+
log_error "CODE REVIEW BLOCKED: inconclusive ($real_verdict_count/$reviewer_count reviewers returned a usable verdict)"
|
|
9500
9560
|
log_error " Review details: $review_dir/$review_id/ ; opt out with LOKI_REVIEW_INCONCLUSIVE_BLOCK=0"
|
|
9501
9561
|
return 1
|
|
9502
9562
|
fi
|
|
@@ -9889,6 +9949,16 @@ CPEOF
|
|
|
9889
9949
|
find "$checkpoint_dir" -maxdepth 1 -type d -name "cp-*" 2>/dev/null \
|
|
9890
9950
|
| while read -r p; do basename "$p"; done | sort -t'-' -k3 -n \
|
|
9891
9951
|
| head -n "$to_remove" | while read -r old_cp; do
|
|
9952
|
+
# WAVE9 (checkpoint leak): also delete the anchored worktree-snapshot
|
|
9953
|
+
# ref so its stash commit becomes eligible for `git gc`. Without this,
|
|
9954
|
+
# refs/loki/cp/<id> (and its commit) leaked forever even after the
|
|
9955
|
+
# checkpoint directory was pruned. Targeted deletion of exactly the
|
|
9956
|
+
# ids being pruned ONLY -- never a blanket refs/loki/cp/* sweep, since
|
|
9957
|
+
# git refs are shared across worktrees of one repo while checkpoint
|
|
9958
|
+
# dirs are per-TARGET_DIR; a parallel worktree may still need a ref we
|
|
9959
|
+
# are not pruning here. `|| true` because not every checkpoint has a
|
|
9960
|
+
# ref (only those where `git stash create` returned a non-empty sha).
|
|
9961
|
+
git update-ref -d "refs/loki/cp/${old_cp}" 2>/dev/null || true
|
|
9892
9962
|
old_cp="${checkpoint_dir}/${old_cp}"
|
|
9893
9963
|
rm -rf "$old_cp" 2>/dev/null || true
|
|
9894
9964
|
done
|
|
@@ -11403,7 +11473,11 @@ try:
|
|
|
11403
11473
|
storage = MemoryStorage(f'{target_dir}/.loki/memory')
|
|
11404
11474
|
retriever = MemoryRetrieval(storage)
|
|
11405
11475
|
context = {'goal': goal, 'phase': phase}
|
|
11406
|
-
|
|
11476
|
+
# The autonomous RARV loop opts into persist_boost so retrieved memories are
|
|
11477
|
+
# reinforced on disk ("use it or lose it"). Manual surfaces (loki memory CLI,
|
|
11478
|
+
# dashboard, MCP) keep the default persist_boost=False so a human browsing
|
|
11479
|
+
# memories does not silently inflate their importance.
|
|
11480
|
+
results = retriever.retrieve_task_aware(context, top_k=3, persist_boost=True)
|
|
11407
11481
|
if results:
|
|
11408
11482
|
print('RELEVANT MEMORIES:')
|
|
11409
11483
|
for r in results[:3]:
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -8675,6 +8675,17 @@ def _get_migration_imports():
|
|
|
8675
8675
|
return _migration_imports
|
|
8676
8676
|
|
|
8677
8677
|
|
|
8678
|
+
def _get_migration_terminal_phase():
|
|
8679
|
+
"""Return the last phase in the migration PHASE_ORDER (the terminal phase),
|
|
8680
|
+
or None if the migration engine is unavailable. Used to let the terminal
|
|
8681
|
+
phase be advanced/completed without a successor to_phase (WAVE9 F1)."""
|
|
8682
|
+
try:
|
|
8683
|
+
from dashboard.migration_engine import PHASE_ORDER
|
|
8684
|
+
return PHASE_ORDER[-1] if PHASE_ORDER else None
|
|
8685
|
+
except (ImportError, IndexError):
|
|
8686
|
+
return None
|
|
8687
|
+
|
|
8688
|
+
|
|
8678
8689
|
@app.get("/api/migration/list", dependencies=[Depends(auth.require_scope("read"))])
|
|
8679
8690
|
def list_migrations_endpoint():
|
|
8680
8691
|
"""List all migrations."""
|
|
@@ -8825,7 +8836,16 @@ def advance_migration(migration_id: str, request_body: dict):
|
|
|
8825
8836
|
MigrationPipeline, list_migrations = imports
|
|
8826
8837
|
from_phase = request_body.get("from_phase")
|
|
8827
8838
|
to_phase = request_body.get("to_phase")
|
|
8828
|
-
|
|
8839
|
+
# The terminal phase (the last in PHASE_ORDER, e.g. "verify") has no
|
|
8840
|
+
# successor, so check_phase_gate can never pass for it and to_phase is
|
|
8841
|
+
# meaningless. Without this carve-out the terminal phase could never be
|
|
8842
|
+
# completed via the API, so overall_status could never reach "completed"
|
|
8843
|
+
# (WAVE9 migration-F1). For the terminal phase we require only from_phase
|
|
8844
|
+
# and skip the gate; advance_phase still validates the phase and the
|
|
8845
|
+
# (ValueError, RuntimeError) -> 409 handler below preserves idempotency.
|
|
8846
|
+
terminal_phase = _get_migration_terminal_phase()
|
|
8847
|
+
is_terminal = terminal_phase is not None and from_phase == terminal_phase
|
|
8848
|
+
if not from_phase or (not to_phase and not is_terminal):
|
|
8829
8849
|
raise HTTPException(status_code=400, detail="from_phase and to_phase are required")
|
|
8830
8850
|
# Load pipeline and check phase gate before the try/except to let
|
|
8831
8851
|
# HTTPException and FileNotFoundError propagate naturally.
|
|
@@ -8833,9 +8853,10 @@ def advance_migration(migration_id: str, request_body: dict):
|
|
|
8833
8853
|
pipeline = MigrationPipeline.load(migration_id)
|
|
8834
8854
|
except FileNotFoundError:
|
|
8835
8855
|
raise HTTPException(status_code=404, detail=f"Migration not found: {migration_id}")
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8856
|
+
if not is_terminal:
|
|
8857
|
+
passed, reason = pipeline.check_phase_gate(from_phase, to_phase)
|
|
8858
|
+
if not passed:
|
|
8859
|
+
raise HTTPException(status_code=409, detail=reason)
|
|
8839
8860
|
try:
|
|
8840
8861
|
result = pipeline.advance_phase(from_phase)
|
|
8841
8862
|
return asdict(result) if hasattr(result, '__dataclass_fields__') else result
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.68.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -395,7 +395,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
|
|
|
395
395
|
# Run Loki Mode in Docker (Claude provider, API-key auth)
|
|
396
396
|
docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
|
397
397
|
-v $(pwd):/workspace -w /workspace \
|
|
398
|
-
asklokesh/loki-mode:7.
|
|
398
|
+
asklokesh/loki-mode:7.68.0 start ./my-spec.md
|
|
399
399
|
```
|
|
400
400
|
|
|
401
401
|
##### docker compose + .env (no host install)
|