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/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-3.7-sonnet}"
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
- return 1
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-3.7-sonnet}"
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-3.7-sonnet}"
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-3.7-sonnet}"
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
- if [ $retry -eq 0 ]; then
7219
- if [ -n "$prd" ]; then
7220
- echo "Loki Mode with PRD at $prd. $human_directive $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"
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
- echo "Loki Mode. $human_directive $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"
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 [ -n "$prd" ]; then
7226
- echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $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"
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
- echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $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"
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-3.7-sonnet}, tier: $tier_param" >> "$log_file"
7821
- echo "[loki] Aider model: ${LOKI_AIDER_MODEL:-claude-3.7-sonnet}, tier: $tier_param" >> "$agent_log"
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
- # Code review gate (v5.35.0)
7913
- if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
7914
- run_code_review || log_warn "Code review found issues - check .loki/quality/reviews/"
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
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.6.0"
10
+ __version__ = "6.7.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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.6.0
5
+ **Version:** v6.7.0
6
6
 
7
7
  ---
8
8
 
9
- ## What's New in v6.6.0
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