loki-mode 6.6.0 → 6.7.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/README.md +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +5 -1
- package/autonomy/loki +918 -37
- package/autonomy/run.sh +456 -29
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +2 -2
- package/docs/architecture/STATE-MACHINES.md +1767 -0
- package/mcp/__init__.py +1 -1
- package/package.json +7 -1
- package/providers/aider.sh +6 -6
- package/src/observability/otel.js +62 -23
package/autonomy/run.sh
CHANGED
|
@@ -1410,7 +1410,7 @@ get_provider_tier_param() {
|
|
|
1410
1410
|
;;
|
|
1411
1411
|
aider)
|
|
1412
1412
|
# Aider uses single externally-configured model
|
|
1413
|
-
echo "${LOKI_AIDER_MODEL:-claude-
|
|
1413
|
+
echo "${LOKI_AIDER_MODEL:-claude-sonnet-4-5-20250929}"
|
|
1414
1414
|
;;
|
|
1415
1415
|
*)
|
|
1416
1416
|
echo "development"
|
|
@@ -2181,38 +2181,53 @@ spawn_worktree_session() {
|
|
|
2181
2181
|
|
|
2182
2182
|
(
|
|
2183
2183
|
cd "$worktree_path" || exit 1
|
|
2184
|
+
_wt_exit=0
|
|
2184
2185
|
# Provider-specific invocation for parallel sessions
|
|
2185
2186
|
case "${PROVIDER_NAME:-claude}" in
|
|
2186
2187
|
claude)
|
|
2187
2188
|
claude --dangerously-skip-permissions \
|
|
2188
2189
|
-p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
2189
|
-
>> "$log_file" 2>&1
|
|
2190
|
+
>> "$log_file" 2>&1 || _wt_exit=$?
|
|
2190
2191
|
;;
|
|
2191
2192
|
codex)
|
|
2192
2193
|
codex exec --full-auto \
|
|
2193
2194
|
"Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
2194
|
-
>> "$log_file" 2>&1
|
|
2195
|
+
>> "$log_file" 2>&1 || _wt_exit=$?
|
|
2195
2196
|
;;
|
|
2196
2197
|
gemini)
|
|
2197
|
-
# Note: -p flag is DEPRECATED per gemini --help. Using positional prompt.
|
|
2198
|
-
# Uses invoke_gemini helper for rate limit fallback to flash model
|
|
2199
2198
|
invoke_gemini "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
2200
|
-
>> "$log_file" 2>&1
|
|
2199
|
+
>> "$log_file" 2>&1 || _wt_exit=$?
|
|
2201
2200
|
;;
|
|
2202
2201
|
cline)
|
|
2203
|
-
# Cline supports parallel instances
|
|
2204
2202
|
invoke_cline "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
2205
|
-
>> "$log_file" 2>&1
|
|
2203
|
+
>> "$log_file" 2>&1 || _wt_exit=$?
|
|
2206
2204
|
;;
|
|
2207
2205
|
aider)
|
|
2208
|
-
# Aider has known issues with parallel - skip
|
|
2209
2206
|
log_warn "Aider does not support parallel sessions, skipping"
|
|
2207
|
+
_wt_exit=1
|
|
2210
2208
|
;;
|
|
2211
2209
|
*)
|
|
2212
2210
|
log_error "Unknown provider: ${PROVIDER_NAME}"
|
|
2213
|
-
|
|
2211
|
+
_wt_exit=1
|
|
2214
2212
|
;;
|
|
2215
2213
|
esac
|
|
2214
|
+
|
|
2215
|
+
# Completion signaling (v6.7.0)
|
|
2216
|
+
if [ $_wt_exit -eq 0 ]; then
|
|
2217
|
+
# Commit any uncommitted work
|
|
2218
|
+
git -C "$worktree_path" add -A 2>/dev/null
|
|
2219
|
+
git -C "$worktree_path" commit -m "feat($stream_name): worktree work complete" 2>/dev/null || true
|
|
2220
|
+
# Signal merge readiness to main orchestrator
|
|
2221
|
+
mkdir -p "${TARGET_DIR:-.}/.loki/signals"
|
|
2222
|
+
cat > "${TARGET_DIR:-.}/.loki/signals/MERGE_REQUESTED_${stream_name}" <<EOSIG
|
|
2223
|
+
{"stream":"$stream_name","branch":"$(git -C "$worktree_path" branch --show-current 2>/dev/null)","worktree":"$worktree_path","timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","exit_code":$_wt_exit}
|
|
2224
|
+
EOSIG
|
|
2225
|
+
echo "WORKTREE_COMPLETE: $stream_name" >> "$log_file"
|
|
2226
|
+
else
|
|
2227
|
+
mkdir -p "${TARGET_DIR:-.}/.loki/signals"
|
|
2228
|
+
echo "{\"stream\":\"$stream_name\",\"status\":\"failed\",\"exit_code\":$_wt_exit,\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" \
|
|
2229
|
+
> "${TARGET_DIR:-.}/.loki/signals/WORKTREE_FAILED_${stream_name}"
|
|
2230
|
+
fi
|
|
2216
2231
|
) &
|
|
2217
2232
|
|
|
2218
2233
|
local pid=$!
|
|
@@ -2223,6 +2238,70 @@ spawn_worktree_session() {
|
|
|
2223
2238
|
return 0
|
|
2224
2239
|
}
|
|
2225
2240
|
|
|
2241
|
+
# Merge a completed worktree back to main branch (v6.7.0)
|
|
2242
|
+
# Usage: merge_worktree <stream_name>
|
|
2243
|
+
merge_worktree() {
|
|
2244
|
+
local stream_name="$1"
|
|
2245
|
+
local signal_file="${TARGET_DIR:-.}/.loki/signals/MERGE_REQUESTED_${stream_name}"
|
|
2246
|
+
|
|
2247
|
+
if [ ! -f "$signal_file" ]; then
|
|
2248
|
+
log_error "No merge signal found for: $stream_name"
|
|
2249
|
+
return 1
|
|
2250
|
+
fi
|
|
2251
|
+
|
|
2252
|
+
local branch worktree_path
|
|
2253
|
+
branch=$(python3 -c "import json; print(json.load(open('$signal_file'))['branch'])" 2>/dev/null)
|
|
2254
|
+
worktree_path=$(python3 -c "import json; print(json.load(open('$signal_file'))['worktree'])" 2>/dev/null)
|
|
2255
|
+
|
|
2256
|
+
if [ -z "$branch" ]; then
|
|
2257
|
+
log_error "Could not determine branch for: $stream_name"
|
|
2258
|
+
return 1
|
|
2259
|
+
fi
|
|
2260
|
+
|
|
2261
|
+
log_step "Merging worktree: $stream_name (branch: $branch)"
|
|
2262
|
+
|
|
2263
|
+
# Merge into current branch
|
|
2264
|
+
local current_branch
|
|
2265
|
+
current_branch=$(git -C "${TARGET_DIR:-.}" branch --show-current 2>/dev/null)
|
|
2266
|
+
|
|
2267
|
+
if git -C "${TARGET_DIR:-.}" merge --no-ff "$branch" -m "merge($stream_name): auto-merge from parallel worktree" 2>&1; then
|
|
2268
|
+
log_info "Merge successful: $stream_name"
|
|
2269
|
+
# Clean up signal and worktree
|
|
2270
|
+
rm -f "$signal_file"
|
|
2271
|
+
if [ -n "$worktree_path" ] && [ -d "$worktree_path" ]; then
|
|
2272
|
+
git -C "${TARGET_DIR:-.}" worktree remove "$worktree_path" --force 2>/dev/null || true
|
|
2273
|
+
git -C "${TARGET_DIR:-.}" branch -d "$branch" 2>/dev/null || true
|
|
2274
|
+
fi
|
|
2275
|
+
return 0
|
|
2276
|
+
else
|
|
2277
|
+
log_error "Merge conflict for $stream_name - manual resolution needed"
|
|
2278
|
+
git -C "${TARGET_DIR:-.}" merge --abort 2>/dev/null || true
|
|
2279
|
+
return 1
|
|
2280
|
+
fi
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
# Check and process all pending merge signals (v6.7.0)
|
|
2284
|
+
process_pending_merges() {
|
|
2285
|
+
local signals_dir="${TARGET_DIR:-.}/.loki/signals"
|
|
2286
|
+
local merged=0
|
|
2287
|
+
local failed=0
|
|
2288
|
+
|
|
2289
|
+
for signal_file in "$signals_dir"/MERGE_REQUESTED_*; do
|
|
2290
|
+
[ -f "$signal_file" ] || continue
|
|
2291
|
+
local stream_name
|
|
2292
|
+
stream_name=$(basename "$signal_file" | sed 's/MERGE_REQUESTED_//')
|
|
2293
|
+
if merge_worktree "$stream_name"; then
|
|
2294
|
+
((merged++))
|
|
2295
|
+
else
|
|
2296
|
+
((failed++))
|
|
2297
|
+
fi
|
|
2298
|
+
done
|
|
2299
|
+
|
|
2300
|
+
if [ $merged -gt 0 ] || [ $failed -gt 0 ]; then
|
|
2301
|
+
log_info "Merge results: $merged successful, $failed failed"
|
|
2302
|
+
fi
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2226
2305
|
# List all active worktrees
|
|
2227
2306
|
list_worktrees() {
|
|
2228
2307
|
log_header "Active Worktrees"
|
|
@@ -2908,11 +2987,12 @@ invoke_cline_capture() {
|
|
|
2908
2987
|
invoke_aider() {
|
|
2909
2988
|
local prompt="$1"
|
|
2910
2989
|
shift
|
|
2911
|
-
local model="${LOKI_AIDER_MODEL:-claude-
|
|
2990
|
+
local model="${LOKI_AIDER_MODEL:-claude-sonnet-4-5-20250929}"
|
|
2912
2991
|
local extra_flags="${LOKI_AIDER_FLAGS:-}"
|
|
2913
2992
|
# shellcheck disable=SC2086
|
|
2993
|
+
# < /dev/null prevents aider from blocking on stdin in non-interactive mode
|
|
2914
2994
|
aider --message "$prompt" --yes-always --no-auto-commits \
|
|
2915
|
-
--model "$model" $extra_flags "$@" 2>&1
|
|
2995
|
+
--model "$model" $extra_flags "$@" < /dev/null 2>&1
|
|
2916
2996
|
}
|
|
2917
2997
|
|
|
2918
2998
|
# Invoke Aider and capture output (for variable assignment)
|
|
@@ -2920,11 +3000,11 @@ invoke_aider() {
|
|
|
2920
3000
|
invoke_aider_capture() {
|
|
2921
3001
|
local prompt="$1"
|
|
2922
3002
|
shift
|
|
2923
|
-
local model="${LOKI_AIDER_MODEL:-claude-
|
|
3003
|
+
local model="${LOKI_AIDER_MODEL:-claude-sonnet-4-5-20250929}"
|
|
2924
3004
|
local extra_flags="${LOKI_AIDER_FLAGS:-}"
|
|
2925
3005
|
# shellcheck disable=SC2086
|
|
2926
3006
|
aider --message "$prompt" --yes-always --no-auto-commits \
|
|
2927
|
-
--model "$model" $extra_flags "$@" 2>&1
|
|
3007
|
+
--model "$model" $extra_flags "$@" < /dev/null 2>&1
|
|
2928
3008
|
}
|
|
2929
3009
|
|
|
2930
3010
|
#===============================================================================
|
|
@@ -3445,7 +3525,7 @@ track_iteration_complete() {
|
|
|
3445
3525
|
elif [ "${PROVIDER_NAME:-claude}" = "cline" ]; then
|
|
3446
3526
|
model_tier="${LOKI_CLINE_MODEL:-sonnet}"
|
|
3447
3527
|
elif [ "${PROVIDER_NAME:-claude}" = "aider" ]; then
|
|
3448
|
-
model_tier="${LOKI_AIDER_MODEL:-claude-
|
|
3528
|
+
model_tier="${LOKI_AIDER_MODEL:-claude-sonnet-4-5-20250929}"
|
|
3449
3529
|
fi
|
|
3450
3530
|
local phase="${LAST_KNOWN_PHASE:-}"
|
|
3451
3531
|
[ -z "$phase" ] && phase=$(python3 -c "import json; print(json.load(open('.loki/state/orchestrator.json')).get('currentPhase', 'unknown'))" 2>/dev/null || echo "unknown")
|
|
@@ -5029,6 +5109,245 @@ else:
|
|
|
5029
5109
|
COMPOUND_SCRIPT
|
|
5030
5110
|
}
|
|
5031
5111
|
|
|
5112
|
+
|
|
5113
|
+
# ============================================================================
|
|
5114
|
+
# Hard Quality Gate: Static Analysis (v6.7.0)
|
|
5115
|
+
# Detects project type and runs appropriate linter on changed files
|
|
5116
|
+
# Results stored in .loki/quality/static-analysis.json
|
|
5117
|
+
# ============================================================================
|
|
5118
|
+
|
|
5119
|
+
enforce_static_analysis() {
|
|
5120
|
+
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
5121
|
+
local quality_dir="$loki_dir/quality"
|
|
5122
|
+
mkdir -p "$quality_dir"
|
|
5123
|
+
|
|
5124
|
+
local changed_files
|
|
5125
|
+
changed_files=$(git -C "${TARGET_DIR:-.}" diff --name-only HEAD~1 2>/dev/null || \
|
|
5126
|
+
git -C "${TARGET_DIR:-.}" diff --name-only --cached 2>/dev/null || echo "")
|
|
5127
|
+
if [ -z "$changed_files" ]; then
|
|
5128
|
+
log_info "Static analysis: no changed files to check"
|
|
5129
|
+
touch "$quality_dir/static-analysis.pass"
|
|
5130
|
+
return 0
|
|
5131
|
+
fi
|
|
5132
|
+
|
|
5133
|
+
local findings=0
|
|
5134
|
+
local total_checked=0
|
|
5135
|
+
local details=""
|
|
5136
|
+
|
|
5137
|
+
# JavaScript/TypeScript
|
|
5138
|
+
local js_files
|
|
5139
|
+
js_files=$(echo "$changed_files" | grep -E '\.(js|ts|jsx|tsx)$' || true)
|
|
5140
|
+
if [ -n "$js_files" ]; then
|
|
5141
|
+
local abs_files=""
|
|
5142
|
+
for f in $js_files; do
|
|
5143
|
+
[ -f "${TARGET_DIR:-.}/$f" ] && abs_files="$abs_files ${TARGET_DIR:-.}/$f"
|
|
5144
|
+
done
|
|
5145
|
+
if [ -n "$abs_files" ]; then
|
|
5146
|
+
total_checked=$((total_checked + $(echo "$abs_files" | wc -w)))
|
|
5147
|
+
if [ -f "${TARGET_DIR:-.}/.eslintrc.js" ] || [ -f "${TARGET_DIR:-.}/.eslintrc.json" ] || \
|
|
5148
|
+
[ -f "${TARGET_DIR:-.}/eslint.config.js" ] || [ -f "${TARGET_DIR:-.}/eslint.config.mjs" ]; then
|
|
5149
|
+
local eslint_out
|
|
5150
|
+
# shellcheck disable=SC2086
|
|
5151
|
+
eslint_out=$(cd "${TARGET_DIR:-.}" && npx eslint $js_files 2>&1) || {
|
|
5152
|
+
findings=$((findings + 1))
|
|
5153
|
+
details="${details}ESLint: $(echo "$eslint_out" | tail -3 | tr '\n' ' '). "
|
|
5154
|
+
}
|
|
5155
|
+
else
|
|
5156
|
+
for f in $abs_files; do
|
|
5157
|
+
node --check "$f" 2>&1 || {
|
|
5158
|
+
findings=$((findings + 1))
|
|
5159
|
+
details="${details}Syntax error: $f. "
|
|
5160
|
+
}
|
|
5161
|
+
done
|
|
5162
|
+
fi
|
|
5163
|
+
fi
|
|
5164
|
+
fi
|
|
5165
|
+
|
|
5166
|
+
# Python
|
|
5167
|
+
local py_files
|
|
5168
|
+
py_files=$(echo "$changed_files" | grep -E '\.py$' || true)
|
|
5169
|
+
if [ -n "$py_files" ]; then
|
|
5170
|
+
for f in $py_files; do
|
|
5171
|
+
[ -f "${TARGET_DIR:-.}/$f" ] || continue
|
|
5172
|
+
total_checked=$((total_checked + 1))
|
|
5173
|
+
python3 -m py_compile "${TARGET_DIR:-.}/$f" 2>&1 || {
|
|
5174
|
+
findings=$((findings + 1))
|
|
5175
|
+
details="${details}py_compile failed: $f. "
|
|
5176
|
+
}
|
|
5177
|
+
done
|
|
5178
|
+
if command -v ruff &>/dev/null; then
|
|
5179
|
+
local ruff_files=""
|
|
5180
|
+
for f in $py_files; do
|
|
5181
|
+
[ -f "${TARGET_DIR:-.}/$f" ] && ruff_files="$ruff_files ${TARGET_DIR:-.}/$f"
|
|
5182
|
+
done
|
|
5183
|
+
if [ -n "$ruff_files" ]; then
|
|
5184
|
+
# shellcheck disable=SC2086
|
|
5185
|
+
ruff check $ruff_files 2>&1 || {
|
|
5186
|
+
findings=$((findings + 1))
|
|
5187
|
+
details="${details}Ruff check found issues. "
|
|
5188
|
+
}
|
|
5189
|
+
fi
|
|
5190
|
+
fi
|
|
5191
|
+
fi
|
|
5192
|
+
|
|
5193
|
+
# Shell scripts
|
|
5194
|
+
local sh_files
|
|
5195
|
+
sh_files=$(echo "$changed_files" | grep -E '\.sh$' || true)
|
|
5196
|
+
if [ -n "$sh_files" ]; then
|
|
5197
|
+
for f in $sh_files; do
|
|
5198
|
+
[ -f "${TARGET_DIR:-.}/$f" ] || continue
|
|
5199
|
+
total_checked=$((total_checked + 1))
|
|
5200
|
+
bash -n "${TARGET_DIR:-.}/$f" 2>&1 || {
|
|
5201
|
+
findings=$((findings + 1))
|
|
5202
|
+
details="${details}Syntax error: $f. "
|
|
5203
|
+
}
|
|
5204
|
+
done
|
|
5205
|
+
if command -v shellcheck &>/dev/null; then
|
|
5206
|
+
for f in $sh_files; do
|
|
5207
|
+
[ -f "${TARGET_DIR:-.}/$f" ] || continue
|
|
5208
|
+
shellcheck "${TARGET_DIR:-.}/$f" 2>&1 || {
|
|
5209
|
+
findings=$((findings + 1))
|
|
5210
|
+
details="${details}shellcheck: $f. "
|
|
5211
|
+
}
|
|
5212
|
+
done
|
|
5213
|
+
fi
|
|
5214
|
+
fi
|
|
5215
|
+
|
|
5216
|
+
# Go
|
|
5217
|
+
if [ -f "${TARGET_DIR:-.}/go.mod" ]; then
|
|
5218
|
+
local go_files
|
|
5219
|
+
go_files=$(echo "$changed_files" | grep -E '\.go$' || true)
|
|
5220
|
+
if [ -n "$go_files" ] && command -v go &>/dev/null; then
|
|
5221
|
+
total_checked=$((total_checked + $(echo "$go_files" | wc -w)))
|
|
5222
|
+
(cd "${TARGET_DIR:-.}" && go vet ./... 2>&1) || {
|
|
5223
|
+
findings=$((findings + 1))
|
|
5224
|
+
details="${details}go vet found issues. "
|
|
5225
|
+
}
|
|
5226
|
+
fi
|
|
5227
|
+
fi
|
|
5228
|
+
|
|
5229
|
+
# Rust
|
|
5230
|
+
if [ -f "${TARGET_DIR:-.}/Cargo.toml" ] && command -v cargo &>/dev/null; then
|
|
5231
|
+
total_checked=$((total_checked + 1))
|
|
5232
|
+
(cd "${TARGET_DIR:-.}" && cargo check 2>&1) || {
|
|
5233
|
+
findings=$((findings + 1))
|
|
5234
|
+
details="${details}cargo check failed. "
|
|
5235
|
+
}
|
|
5236
|
+
fi
|
|
5237
|
+
|
|
5238
|
+
# Write results
|
|
5239
|
+
cat > "$quality_dir/static-analysis.json" << SAFEOF
|
|
5240
|
+
{"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","files_checked":$total_checked,"findings":$findings,"summary":"$details","pass":$([ $findings -eq 0 ] && echo "true" || echo "false")}
|
|
5241
|
+
SAFEOF
|
|
5242
|
+
|
|
5243
|
+
if [ "$findings" -gt 0 ]; then
|
|
5244
|
+
rm -f "$quality_dir/static-analysis.pass"
|
|
5245
|
+
echo "static_analysis" > "$loki_dir/signals/STATIC_ANALYSIS_FAILED" 2>/dev/null || true
|
|
5246
|
+
log_warn "Static analysis: $findings issue(s) in $total_checked files"
|
|
5247
|
+
return 1
|
|
5248
|
+
else
|
|
5249
|
+
touch "$quality_dir/static-analysis.pass"
|
|
5250
|
+
rm -f "$loki_dir/signals/STATIC_ANALYSIS_FAILED" 2>/dev/null || true
|
|
5251
|
+
log_info "Static analysis: $total_checked files checked, all clean"
|
|
5252
|
+
return 0
|
|
5253
|
+
fi
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5256
|
+
# ============================================================================
|
|
5257
|
+
# Hard Quality Gate: Test Coverage (v6.7.0)
|
|
5258
|
+
# Detects test runner and runs tests with coverage reporting
|
|
5259
|
+
# Results stored in .loki/quality/test-results.json
|
|
5260
|
+
# ============================================================================
|
|
5261
|
+
|
|
5262
|
+
enforce_test_coverage() {
|
|
5263
|
+
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
5264
|
+
local quality_dir="$loki_dir/quality"
|
|
5265
|
+
mkdir -p "$quality_dir"
|
|
5266
|
+
|
|
5267
|
+
local min_coverage="${LOKI_MIN_COVERAGE:-80}"
|
|
5268
|
+
local test_passed=true
|
|
5269
|
+
local coverage_pct=0
|
|
5270
|
+
local test_runner="none"
|
|
5271
|
+
local details=""
|
|
5272
|
+
|
|
5273
|
+
# JavaScript/TypeScript
|
|
5274
|
+
if [ -f "${TARGET_DIR:-.}/package.json" ]; then
|
|
5275
|
+
if grep -q '"vitest"' "${TARGET_DIR:-.}/package.json" 2>/dev/null; then
|
|
5276
|
+
test_runner="vitest"
|
|
5277
|
+
local output
|
|
5278
|
+
output=$(cd "${TARGET_DIR:-.}" && npx vitest run --reporter=json 2>&1) || test_passed=false
|
|
5279
|
+
details="vitest: $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
5280
|
+
elif grep -q '"jest"' "${TARGET_DIR:-.}/package.json" 2>/dev/null; then
|
|
5281
|
+
test_runner="jest"
|
|
5282
|
+
local output
|
|
5283
|
+
output=$(cd "${TARGET_DIR:-.}" && npx jest --passWithNoTests --forceExit 2>&1) || test_passed=false
|
|
5284
|
+
details="jest: $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
5285
|
+
elif grep -q '"mocha"' "${TARGET_DIR:-.}/package.json" 2>/dev/null; then
|
|
5286
|
+
test_runner="mocha"
|
|
5287
|
+
local output
|
|
5288
|
+
output=$(cd "${TARGET_DIR:-.}" && npx mocha 2>&1) || test_passed=false
|
|
5289
|
+
details="mocha: $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
5290
|
+
fi
|
|
5291
|
+
fi
|
|
5292
|
+
|
|
5293
|
+
# Python
|
|
5294
|
+
if [ "$test_runner" = "none" ]; then
|
|
5295
|
+
if [ -f "${TARGET_DIR:-.}/setup.py" ] || [ -f "${TARGET_DIR:-.}/pyproject.toml" ] || \
|
|
5296
|
+
[ -d "${TARGET_DIR:-.}/tests" ]; then
|
|
5297
|
+
if command -v pytest &>/dev/null; then
|
|
5298
|
+
test_runner="pytest"
|
|
5299
|
+
local output
|
|
5300
|
+
output=$(cd "${TARGET_DIR:-.}" && pytest --tb=short 2>&1) || test_passed=false
|
|
5301
|
+
details="pytest: $(echo "$output" | tail -5 | tr '\n' ' ')"
|
|
5302
|
+
fi
|
|
5303
|
+
fi
|
|
5304
|
+
fi
|
|
5305
|
+
|
|
5306
|
+
# Go
|
|
5307
|
+
if [ "$test_runner" = "none" ] && [ -f "${TARGET_DIR:-.}/go.mod" ] && command -v go &>/dev/null; then
|
|
5308
|
+
test_runner="go-test"
|
|
5309
|
+
local output
|
|
5310
|
+
output=$(cd "${TARGET_DIR:-.}" && go test ./... 2>&1) || test_passed=false
|
|
5311
|
+
details="go test: $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
5312
|
+
fi
|
|
5313
|
+
|
|
5314
|
+
# Rust
|
|
5315
|
+
if [ "$test_runner" = "none" ] && [ -f "${TARGET_DIR:-.}/Cargo.toml" ] && command -v cargo &>/dev/null; then
|
|
5316
|
+
test_runner="cargo-test"
|
|
5317
|
+
local output
|
|
5318
|
+
output=$(cd "${TARGET_DIR:-.}" && cargo test 2>&1) || test_passed=false
|
|
5319
|
+
details="cargo test: $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
5320
|
+
fi
|
|
5321
|
+
|
|
5322
|
+
if [ "$test_runner" = "none" ]; then
|
|
5323
|
+
log_info "Test coverage: no test runner detected, skipping"
|
|
5324
|
+
touch "$quality_dir/unit-tests.pass"
|
|
5325
|
+
cat > "$quality_dir/test-results.json" << TREOF
|
|
5326
|
+
{"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","runner":"none","pass":true,"summary":"No test runner detected"}
|
|
5327
|
+
TREOF
|
|
5328
|
+
return 0
|
|
5329
|
+
fi
|
|
5330
|
+
|
|
5331
|
+
# Sanitize details for JSON
|
|
5332
|
+
details=$(echo "$details" | tr '"' "'" | tr '\n' ' ' | head -c 500)
|
|
5333
|
+
|
|
5334
|
+
cat > "$quality_dir/test-results.json" << TREOF
|
|
5335
|
+
{"timestamp":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","runner":"$test_runner","pass":$test_passed,"min_coverage":$min_coverage,"summary":"$details"}
|
|
5336
|
+
TREOF
|
|
5337
|
+
|
|
5338
|
+
if [ "$test_passed" = "true" ]; then
|
|
5339
|
+
touch "$quality_dir/unit-tests.pass"
|
|
5340
|
+
rm -f "$loki_dir/signals/TESTS_FAILED" 2>/dev/null || true
|
|
5341
|
+
log_info "Test coverage gate: $test_runner passed"
|
|
5342
|
+
return 0
|
|
5343
|
+
else
|
|
5344
|
+
rm -f "$quality_dir/unit-tests.pass"
|
|
5345
|
+
echo "tests_failed" > "$loki_dir/signals/TESTS_FAILED" 2>/dev/null || true
|
|
5346
|
+
log_warn "Test coverage gate: $test_runner FAILED"
|
|
5347
|
+
return 1
|
|
5348
|
+
fi
|
|
5349
|
+
}
|
|
5350
|
+
|
|
5032
5351
|
# ============================================================================
|
|
5033
5352
|
# 3-Reviewer Parallel Code Review (v5.35.0)
|
|
5034
5353
|
# Specialist pool from skills/quality-gates.md with blind review
|
|
@@ -5063,13 +5382,16 @@ run_code_review() {
|
|
|
5063
5382
|
echo "$changed_files" > "$files_file"
|
|
5064
5383
|
|
|
5065
5384
|
# Select specialists via keyword scoring (python3 reads files, not env vars)
|
|
5385
|
+
# Loads from agents/types.json when available, falls back to hardcoded pool (v6.7.0)
|
|
5066
5386
|
export LOKI_REVIEW_DIFF_FILE="$diff_file"
|
|
5067
5387
|
export LOKI_REVIEW_FILES_FILE="$files_file"
|
|
5388
|
+
export LOKI_AGENTS_TYPES_FILE="${PROJECT_DIR}/agents/types.json"
|
|
5068
5389
|
local selected_specialists
|
|
5069
5390
|
selected_specialists=$(python3 << 'SPECIALIST_SELECT'
|
|
5070
5391
|
import os
|
|
5071
5392
|
import json
|
|
5072
5393
|
|
|
5394
|
+
# Hardcoded specialists (always available as fallback)
|
|
5073
5395
|
SPECIALISTS = {
|
|
5074
5396
|
"security-sentinel": {
|
|
5075
5397
|
"keywords": ["auth", "login", "password", "token", "api", "sql", "query", "cookie", "cors", "csrf"],
|
|
@@ -5097,6 +5419,37 @@ SPECIALISTS = {
|
|
|
5097
5419
|
}
|
|
5098
5420
|
}
|
|
5099
5421
|
|
|
5422
|
+
# Load additional specialists from agents/types.json (v6.7.0)
|
|
5423
|
+
types_file = os.environ.get("LOKI_AGENTS_TYPES_FILE", "")
|
|
5424
|
+
if types_file and os.path.exists(types_file):
|
|
5425
|
+
try:
|
|
5426
|
+
with open(types_file) as f:
|
|
5427
|
+
agent_types = json.load(f)
|
|
5428
|
+
FOCUS_KEYWORDS = {
|
|
5429
|
+
"ops-security": ["auth", "security", "vuln", "cve", "injection", "xss", "csrf", "encrypt", "secret", "permission"],
|
|
5430
|
+
"eng-qa": ["test", "spec", "coverage", "assert", "mock", "fixture", "expect", "describe", "e2e", "unit"],
|
|
5431
|
+
"eng-perf": ["perf", "cache", "query", "slow", "memory", "leak", "optimize", "bundle", "load", "latency"],
|
|
5432
|
+
"eng-database": ["database", "sql", "query", "migration", "index", "join", "schema", "postgres", "mongo"],
|
|
5433
|
+
"eng-frontend": ["react", "vue", "css", "html", "component", "render", "dom", "accessibility", "responsive"],
|
|
5434
|
+
"eng-backend": ["api", "endpoint", "middleware", "route", "controller", "service", "auth", "validation"],
|
|
5435
|
+
"eng-infra": ["docker", "k8s", "kubernetes", "deploy", "ci", "cd", "pipeline", "terraform", "helm"],
|
|
5436
|
+
"review-code": ["refactor", "pattern", "solid", "coupling", "abstraction", "class", "function", "module"],
|
|
5437
|
+
"review-security": ["auth", "login", "password", "token", "secret", "inject", "xss", "cors", "permission", "encrypt"],
|
|
5438
|
+
"review-business": ["logic", "workflow", "business", "rule", "validation", "price", "payment", "order"],
|
|
5439
|
+
}
|
|
5440
|
+
for agent in agent_types:
|
|
5441
|
+
agent_type = agent.get("type", "")
|
|
5442
|
+
if agent_type in FOCUS_KEYWORDS and agent_type not in SPECIALISTS:
|
|
5443
|
+
SPECIALISTS[agent_type] = {
|
|
5444
|
+
"keywords": FOCUS_KEYWORDS[agent_type],
|
|
5445
|
+
"focus": agent.get("capabilities", ""),
|
|
5446
|
+
"checks": "Review from " + agent.get("name", agent_type) + " perspective: " + ", ".join(agent.get("focus", [])),
|
|
5447
|
+
"priority": len(SPECIALISTS),
|
|
5448
|
+
"persona": agent.get("persona", "")
|
|
5449
|
+
}
|
|
5450
|
+
except Exception:
|
|
5451
|
+
pass # Fall back to hardcoded specialists
|
|
5452
|
+
|
|
5100
5453
|
diff_path = os.environ.get("LOKI_REVIEW_DIFF_FILE", "")
|
|
5101
5454
|
files_path = os.environ.get("LOKI_REVIEW_FILES_FILE", "")
|
|
5102
5455
|
|
|
@@ -5142,12 +5495,13 @@ result = {
|
|
|
5142
5495
|
}
|
|
5143
5496
|
for name in selected
|
|
5144
5497
|
],
|
|
5145
|
-
"scores": {n: scores[n] for n in scores}
|
|
5498
|
+
"scores": {n: scores[n] for n in scores},
|
|
5499
|
+
"pool_size": len(SPECIALISTS)
|
|
5146
5500
|
}
|
|
5147
5501
|
print(json.dumps(result))
|
|
5148
5502
|
SPECIALIST_SELECT
|
|
5149
5503
|
)
|
|
5150
|
-
unset LOKI_REVIEW_DIFF_FILE LOKI_REVIEW_FILES_FILE
|
|
5504
|
+
unset LOKI_REVIEW_DIFF_FILE LOKI_REVIEW_FILES_FILE LOKI_AGENTS_TYPES_FILE
|
|
5151
5505
|
|
|
5152
5506
|
if [ -z "$selected_specialists" ]; then
|
|
5153
5507
|
log_error "Code review: Specialist selection failed"
|
|
@@ -7108,6 +7462,25 @@ build_prompt() {
|
|
|
7108
7462
|
context_injection="$context_injection $memory_context"
|
|
7109
7463
|
fi
|
|
7110
7464
|
|
|
7465
|
+
# Gate failure injection (v6.7.0) - tells LLM what to fix
|
|
7466
|
+
local gate_failure_context=""
|
|
7467
|
+
if [ -f "${TARGET_DIR:-.}/.loki/quality/gate-failures.txt" ]; then
|
|
7468
|
+
local failures
|
|
7469
|
+
failures=$(cat "${TARGET_DIR:-.}/.loki/quality/gate-failures.txt")
|
|
7470
|
+
gate_failure_context="QUALITY GATE FAILURES FROM PREVIOUS ITERATION: [$failures]. "
|
|
7471
|
+
if [ -f "${TARGET_DIR:-.}/.loki/quality/static-analysis.json" ]; then
|
|
7472
|
+
local sa_summary
|
|
7473
|
+
sa_summary=$(python3 -c "import json; d=json.load(open('${TARGET_DIR:-.}/.loki/quality/static-analysis.json')); print(d.get('summary',''))" 2>/dev/null || echo "")
|
|
7474
|
+
[ -n "$sa_summary" ] && gate_failure_context="${gate_failure_context}Static analysis: ${sa_summary}. "
|
|
7475
|
+
fi
|
|
7476
|
+
if [ -f "${TARGET_DIR:-.}/.loki/quality/test-results.json" ]; then
|
|
7477
|
+
local test_summary
|
|
7478
|
+
test_summary=$(python3 -c "import json; d=json.load(open('${TARGET_DIR:-.}/.loki/quality/test-results.json')); print(d.get('summary',''))" 2>/dev/null || echo "")
|
|
7479
|
+
[ -n "$test_summary" ] && gate_failure_context="${gate_failure_context}Tests: ${test_summary}. "
|
|
7480
|
+
fi
|
|
7481
|
+
gate_failure_context="${gate_failure_context}FIX THESE ISSUES BEFORE PROCEEDING WITH NEW WORK."
|
|
7482
|
+
fi
|
|
7483
|
+
|
|
7111
7484
|
# Human directive injection (from HUMAN_INPUT.md)
|
|
7112
7485
|
# NOTE: Do NOT unset LOKI_HUMAN_INPUT here - build_prompt runs in a subshell
|
|
7113
7486
|
# (command substitution) so unset would not affect the parent shell.
|
|
@@ -7215,17 +7588,40 @@ except: pass
|
|
|
7215
7588
|
fi
|
|
7216
7589
|
fi
|
|
7217
7590
|
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7591
|
+
# Degraded providers with small models need simplified prompts
|
|
7592
|
+
# Full RARV/SDLC instructions overwhelm models < 30B parameters
|
|
7593
|
+
if [ "${PROVIDER_DEGRADED:-false}" = "true" ]; then
|
|
7594
|
+
local prd_content=""
|
|
7595
|
+
if [ -n "$prd" ] && [ -f "$prd" ]; then
|
|
7596
|
+
prd_content=$(head -c 4000 "$prd")
|
|
7597
|
+
fi
|
|
7598
|
+
|
|
7599
|
+
if [ $retry -eq 0 ]; then
|
|
7600
|
+
if [ -n "$prd" ]; then
|
|
7601
|
+
echo "You are a coding assistant. Read and implement the requirements from the PRD below. Write working code, run tests if possible, and commit changes. ${human_directive:+Priority: $human_directive} ${queue_tasks:+Tasks: $queue_tasks} PRD contents: $prd_content"
|
|
7602
|
+
else
|
|
7603
|
+
echo "You are a coding assistant. Analyze this codebase and suggest improvements. Write working code and commit changes. ${human_directive:+Priority: $human_directive} ${queue_tasks:+Tasks: $queue_tasks}"
|
|
7604
|
+
fi
|
|
7221
7605
|
else
|
|
7222
|
-
|
|
7606
|
+
if [ -n "$prd" ]; then
|
|
7607
|
+
echo "You are a coding assistant. Continue working on iteration $iteration. Review what exists, implement remaining PRD requirements, fix any issues, add tests. ${human_directive:+Priority: $human_directive} ${queue_tasks:+Tasks: $queue_tasks} PRD contents: $prd_content"
|
|
7608
|
+
else
|
|
7609
|
+
echo "You are a coding assistant. Continue working on iteration $iteration. Review what exists, improve code, fix bugs, add tests. ${human_directive:+Priority: $human_directive} ${queue_tasks:+Tasks: $queue_tasks}"
|
|
7610
|
+
fi
|
|
7223
7611
|
fi
|
|
7224
7612
|
else
|
|
7225
|
-
if [ -
|
|
7226
|
-
|
|
7613
|
+
if [ $retry -eq 0 ]; then
|
|
7614
|
+
if [ -n "$prd" ]; then
|
|
7615
|
+
echo "Loki Mode with PRD at $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7616
|
+
else
|
|
7617
|
+
echo "Loki Mode. $human_directive $gate_failure_context $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7618
|
+
fi
|
|
7227
7619
|
else
|
|
7228
|
-
|
|
7620
|
+
if [ -n "$prd" ]; then
|
|
7621
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7622
|
+
else
|
|
7623
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $gate_failure_context $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
7624
|
+
fi
|
|
7229
7625
|
fi
|
|
7230
7626
|
fi
|
|
7231
7627
|
}
|
|
@@ -7817,8 +8213,8 @@ if __name__ == "__main__":
|
|
|
7817
8213
|
;;
|
|
7818
8214
|
aider)
|
|
7819
8215
|
# Aider: Tier 3 - degraded mode, 18+ providers
|
|
7820
|
-
echo "[loki] Aider model: ${LOKI_AIDER_MODEL:-claude-
|
|
7821
|
-
echo "[loki] Aider model: ${LOKI_AIDER_MODEL:-claude-
|
|
8216
|
+
echo "[loki] Aider model: ${LOKI_AIDER_MODEL:-claude-sonnet-4-5-20250929}, tier: $tier_param" >> "$log_file"
|
|
8217
|
+
echo "[loki] Aider model: ${LOKI_AIDER_MODEL:-claude-sonnet-4-5-20250929}, tier: $tier_param" >> "$agent_log"
|
|
7822
8218
|
{ invoke_aider "$prompt" 2>&1 | tee -a "$log_file" "$agent_log"; \
|
|
7823
8219
|
} && exit_code=0 || exit_code=$?
|
|
7824
8220
|
;;
|
|
@@ -7909,9 +8305,40 @@ if __name__ == "__main__":
|
|
|
7909
8305
|
# Checkpoint after each iteration (v5.57.0)
|
|
7910
8306
|
create_checkpoint "iteration-${ITERATION_COUNT} complete" "iteration-${ITERATION_COUNT}"
|
|
7911
8307
|
|
|
7912
|
-
#
|
|
7913
|
-
|
|
7914
|
-
|
|
8308
|
+
# Quality gates (v6.7.0 - hard enforcement)
|
|
8309
|
+
local gate_failures=""
|
|
8310
|
+
if [ "${LOKI_HARD_GATES:-true}" = "true" ]; then
|
|
8311
|
+
# Static analysis gate
|
|
8312
|
+
if [ "${PHASE_STATIC_ANALYSIS:-true}" = "true" ]; then
|
|
8313
|
+
enforce_static_analysis || {
|
|
8314
|
+
gate_failures="${gate_failures}static_analysis,"
|
|
8315
|
+
log_warn "Static analysis FAILED - findings injected into next iteration"
|
|
8316
|
+
}
|
|
8317
|
+
fi
|
|
8318
|
+
# Test coverage gate
|
|
8319
|
+
if [ "${PHASE_UNIT_TESTS:-true}" = "true" ]; then
|
|
8320
|
+
enforce_test_coverage || {
|
|
8321
|
+
gate_failures="${gate_failures}test_coverage,"
|
|
8322
|
+
log_warn "Test coverage gate FAILED - must pass next iteration"
|
|
8323
|
+
}
|
|
8324
|
+
fi
|
|
8325
|
+
# Code review gate (upgraded from advisory)
|
|
8326
|
+
if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
8327
|
+
run_code_review || {
|
|
8328
|
+
gate_failures="${gate_failures}code_review,"
|
|
8329
|
+
log_warn "Code review BLOCKED - Critical/High findings"
|
|
8330
|
+
}
|
|
8331
|
+
fi
|
|
8332
|
+
# Store gate failures for prompt injection
|
|
8333
|
+
if [ -n "$gate_failures" ]; then
|
|
8334
|
+
echo "$gate_failures" > "${TARGET_DIR:-.}/.loki/quality/gate-failures.txt"
|
|
8335
|
+
else
|
|
8336
|
+
rm -f "${TARGET_DIR:-.}/.loki/quality/gate-failures.txt"
|
|
8337
|
+
fi
|
|
8338
|
+
else
|
|
8339
|
+
if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
8340
|
+
run_code_review || log_warn "Code review found issues - check .loki/quality/reviews/"
|
|
8341
|
+
fi
|
|
7915
8342
|
fi
|
|
7916
8343
|
|
|
7917
8344
|
# Check for success - ONLY stop on explicit completion promise
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v6.
|
|
5
|
+
**Version:** v6.7.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## What's New in v6.
|
|
9
|
+
## What's New in v6.7.0
|
|
10
10
|
|
|
11
11
|
### Dual-Mode Architecture (v6.0.0)
|
|
12
12
|
- `loki run` command for direct autonomous execution
|