loki-mode 6.9.0 → 6.10.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/completion-council.sh +19 -2
- package/autonomy/prd-analyzer.py +14 -1
- package/autonomy/run.sh +180 -14
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v6.
|
|
6
|
+
# Loki Mode v6.10.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -267,4 +267,4 @@ The following features are documented in skill modules but not yet fully automat
|
|
|
267
267
|
| Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
|
|
268
268
|
| Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
|
|
269
269
|
|
|
270
|
-
**v6.
|
|
270
|
+
**v6.10.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.
|
|
1
|
+
6.10.0
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
# LOKI_COUNCIL_MIN_ITERATIONS - Minimum iterations before council runs (default: 3)
|
|
28
28
|
# LOKI_COUNCIL_CONVERGENCE_WINDOW - Iterations to track for convergence (default: 3)
|
|
29
29
|
# LOKI_COUNCIL_STAGNATION_LIMIT - Max iterations with no git changes (default: 5)
|
|
30
|
+
# LOKI_COUNCIL_DONE_SIGNAL_LIMIT - Max total done signals before force stop (default: 10)
|
|
30
31
|
#
|
|
31
32
|
# Usage:
|
|
32
33
|
# source autonomy/completion-council.sh
|
|
@@ -48,6 +49,7 @@ fi
|
|
|
48
49
|
COUNCIL_MIN_ITERATIONS=${LOKI_COUNCIL_MIN_ITERATIONS:-3}
|
|
49
50
|
COUNCIL_CONVERGENCE_WINDOW=${LOKI_COUNCIL_CONVERGENCE_WINDOW:-3}
|
|
50
51
|
COUNCIL_STAGNATION_LIMIT=${LOKI_COUNCIL_STAGNATION_LIMIT:-5}
|
|
52
|
+
COUNCIL_DONE_SIGNAL_LIMIT=${LOKI_COUNCIL_DONE_SIGNAL_LIMIT:-10}
|
|
51
53
|
|
|
52
54
|
# Error budget: severity-aware completion (v5.49.0)
|
|
53
55
|
# SEVERITY_THRESHOLD: minimum severity that blocks completion (critical, high, medium, low)
|
|
@@ -62,6 +64,7 @@ COUNCIL_STATE_DIR=""
|
|
|
62
64
|
COUNCIL_PRD_PATH=""
|
|
63
65
|
COUNCIL_CONSECUTIVE_NO_CHANGE=0
|
|
64
66
|
COUNCIL_DONE_SIGNALS=0
|
|
67
|
+
COUNCIL_TOTAL_DONE_SIGNALS=0
|
|
65
68
|
COUNCIL_LAST_DIFF_HASH=""
|
|
66
69
|
|
|
67
70
|
#===============================================================================
|
|
@@ -149,6 +152,7 @@ council_track_iteration() {
|
|
|
149
152
|
|
|
150
153
|
if [ "$done_indicators" -gt 0 ]; then
|
|
151
154
|
((COUNCIL_DONE_SIGNALS++))
|
|
155
|
+
((COUNCIL_TOTAL_DONE_SIGNALS++))
|
|
152
156
|
else
|
|
153
157
|
# Reset if agent stopped claiming done
|
|
154
158
|
COUNCIL_DONE_SIGNALS=0
|
|
@@ -172,6 +176,7 @@ council_track_iteration() {
|
|
|
172
176
|
_COUNCIL_STATE_FILE="$COUNCIL_STATE_DIR/state.json" \
|
|
173
177
|
_COUNCIL_NO_CHANGE="$COUNCIL_CONSECUTIVE_NO_CHANGE" \
|
|
174
178
|
_COUNCIL_DONE_SIGNALS="$COUNCIL_DONE_SIGNALS" \
|
|
179
|
+
_COUNCIL_TOTAL_DONE_SIGNALS="$COUNCIL_TOTAL_DONE_SIGNALS" \
|
|
175
180
|
_COUNCIL_ITERATION="${ITERATION_COUNT:-0}" \
|
|
176
181
|
_COUNCIL_FILES_CHANGED="$files_changed" \
|
|
177
182
|
python3 -c "
|
|
@@ -184,6 +189,7 @@ except (json.JSONDecodeError, FileNotFoundError, OSError):
|
|
|
184
189
|
state = {}
|
|
185
190
|
state['consecutive_no_change'] = int(os.environ['_COUNCIL_NO_CHANGE'])
|
|
186
191
|
state['done_signals'] = int(os.environ['_COUNCIL_DONE_SIGNALS'])
|
|
192
|
+
state['total_done_signals'] = int(os.environ['_COUNCIL_TOTAL_DONE_SIGNALS'])
|
|
187
193
|
state['last_track_iteration'] = int(os.environ['_COUNCIL_ITERATION'])
|
|
188
194
|
state['files_changed'] = int(os.environ['_COUNCIL_FILES_CHANGED'])
|
|
189
195
|
with open(state_file, 'w') as f:
|
|
@@ -459,7 +465,8 @@ EVIDENCE_SECTION
|
|
|
459
465
|
|
|
460
466
|
## Convergence Data
|
|
461
467
|
- Consecutive iterations with no code changes: $COUNCIL_CONSECUTIVE_NO_CHANGE
|
|
462
|
-
- Done signals from agent: $COUNCIL_DONE_SIGNALS
|
|
468
|
+
- Done signals from agent (consecutive): $COUNCIL_DONE_SIGNALS
|
|
469
|
+
- Total done signals from agent: $COUNCIL_TOTAL_DONE_SIGNALS
|
|
463
470
|
- Current iteration: $ITERATION_COUNT
|
|
464
471
|
|
|
465
472
|
## Queue Status
|
|
@@ -990,6 +997,8 @@ council_evaluate_member() {
|
|
|
990
997
|
if [ -f "$log_file" ]; then
|
|
991
998
|
local errs
|
|
992
999
|
errs=$(tail -50 "$log_file" 2>/dev/null | grep -ciE "(uncaught|unhandled|panic|fatal|segfault|traceback)" 2>/dev/null || echo "0")
|
|
1000
|
+
errs=$(echo "$errs" | tr -dc '0-9')
|
|
1001
|
+
errs="${errs:-0}"
|
|
993
1002
|
error_count=$((error_count + errs))
|
|
994
1003
|
fi
|
|
995
1004
|
done
|
|
@@ -1365,6 +1374,13 @@ council_should_stop() {
|
|
|
1365
1374
|
fi
|
|
1366
1375
|
fi
|
|
1367
1376
|
|
|
1377
|
+
# Safety valve 2: Total done signals exceed limit (agent keeps saying done)
|
|
1378
|
+
if [ "$COUNCIL_TOTAL_DONE_SIGNALS" -ge "$COUNCIL_DONE_SIGNAL_LIMIT" ]; then
|
|
1379
|
+
log_error "Safety valve: Agent signaled 'done' $COUNCIL_TOTAL_DONE_SIGNALS times (limit: $COUNCIL_DONE_SIGNAL_LIMIT)"
|
|
1380
|
+
log_error "Forcing stop - agent believes work is complete"
|
|
1381
|
+
return 0 # FORCE STOP
|
|
1382
|
+
fi
|
|
1383
|
+
|
|
1368
1384
|
return 1 # CONTINUE
|
|
1369
1385
|
}
|
|
1370
1386
|
|
|
@@ -1385,7 +1401,8 @@ council_write_report() {
|
|
|
1385
1401
|
## Convergence Data
|
|
1386
1402
|
- Total iterations: $ITERATION_COUNT
|
|
1387
1403
|
- Final consecutive no-change count: $COUNCIL_CONSECUTIVE_NO_CHANGE
|
|
1388
|
-
- Done signals from agent: $COUNCIL_DONE_SIGNALS
|
|
1404
|
+
- Done signals from agent (consecutive): $COUNCIL_DONE_SIGNALS
|
|
1405
|
+
- Total done signals from agent: $COUNCIL_TOTAL_DONE_SIGNALS
|
|
1389
1406
|
|
|
1390
1407
|
## Council Configuration
|
|
1391
1408
|
- Council size: $COUNCIL_SIZE
|
package/autonomy/prd-analyzer.py
CHANGED
|
@@ -252,7 +252,7 @@ class PrdAnalyzer:
|
|
|
252
252
|
count = 0
|
|
253
253
|
in_feature_section = False
|
|
254
254
|
for line in self.lines:
|
|
255
|
-
if re.search(r"(?i)#+\s.*(?:feature|requirement|scope|functional)", line):
|
|
255
|
+
if re.search(r"(?i)#+\s.*(?:feature|requirement|scope|functional|module|component|service|endpoint|api|milestone|deliverable|workstream|epic|story|task|phase|capability|objective)", line):
|
|
256
256
|
in_feature_section = True
|
|
257
257
|
continue
|
|
258
258
|
if in_feature_section and re.match(r"^\s*#+\s", line):
|
|
@@ -262,12 +262,25 @@ class PrdAnalyzer:
|
|
|
262
262
|
if re.match(r"^\s*[-*]\s+\S", line) or re.match(r"^\s*\d+\.\s+\S", line):
|
|
263
263
|
count += 1
|
|
264
264
|
|
|
265
|
+
# Fallback: count ## headings as feature indicators when bullet items are few
|
|
266
|
+
if count == 0:
|
|
267
|
+
for line in self.lines:
|
|
268
|
+
if re.match(r"^##\s+\S", line):
|
|
269
|
+
count += 1
|
|
270
|
+
|
|
265
271
|
self.feature_count = count
|
|
266
272
|
for threshold, label in SCOPE_THRESHOLDS:
|
|
267
273
|
if count <= threshold:
|
|
268
274
|
self.scope = label
|
|
269
275
|
break
|
|
270
276
|
|
|
277
|
+
# Word-count fallback: large PRDs should never be classified as small
|
|
278
|
+
word_count = len(self.content.split())
|
|
279
|
+
if word_count > 2000 and self.scope in ("small", "medium"):
|
|
280
|
+
self.scope = "large"
|
|
281
|
+
elif word_count > 500 and self.scope == "small":
|
|
282
|
+
self.scope = "medium"
|
|
283
|
+
|
|
271
284
|
def generate_observations(self):
|
|
272
285
|
"""Generate the observations markdown content."""
|
|
273
286
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
package/autonomy/run.sh
CHANGED
|
@@ -622,6 +622,11 @@ MAX_WORKTREES=${LOKI_MAX_WORKTREES:-5}
|
|
|
622
622
|
MAX_PARALLEL_SESSIONS=${LOKI_MAX_PARALLEL_SESSIONS:-3}
|
|
623
623
|
PARALLEL_TESTING=${LOKI_PARALLEL_TESTING:-true}
|
|
624
624
|
PARALLEL_DOCS=${LOKI_PARALLEL_DOCS:-true}
|
|
625
|
+
|
|
626
|
+
# Gate Escalation Ladder (v6.10.0)
|
|
627
|
+
GATE_CLEAR_LIMIT=${LOKI_GATE_CLEAR_LIMIT:-3}
|
|
628
|
+
GATE_ESCALATE_LIMIT=${LOKI_GATE_ESCALATE_LIMIT:-5}
|
|
629
|
+
GATE_PAUSE_LIMIT=${LOKI_GATE_PAUSE_LIMIT:-10}
|
|
625
630
|
TARGET_DIR="${LOKI_TARGET_DIR:-$(pwd)}"
|
|
626
631
|
PARALLEL_BLOG=${LOKI_PARALLEL_BLOG:-false}
|
|
627
632
|
AUTO_MERGE=${LOKI_AUTO_MERGE:-true}
|
|
@@ -5250,6 +5255,69 @@ SAFEOF
|
|
|
5250
5255
|
fi
|
|
5251
5256
|
}
|
|
5252
5257
|
|
|
5258
|
+
#===============================================================================
|
|
5259
|
+
# Gate Failure Tracking (v6.10.0)
|
|
5260
|
+
#===============================================================================
|
|
5261
|
+
|
|
5262
|
+
track_gate_failure() {
|
|
5263
|
+
local gate_name="$1"
|
|
5264
|
+
local gate_file="${TARGET_DIR:-.}/.loki/quality/gate-failure-count.json"
|
|
5265
|
+
mkdir -p "$(dirname "$gate_file")"
|
|
5266
|
+
|
|
5267
|
+
_GATE_FILE="$gate_file" _GATE_NAME="$gate_name" python3 -c "
|
|
5268
|
+
import json, os
|
|
5269
|
+
gate_file = os.environ['_GATE_FILE']
|
|
5270
|
+
gate_name = os.environ['_GATE_NAME']
|
|
5271
|
+
try:
|
|
5272
|
+
with open(gate_file) as f:
|
|
5273
|
+
counts = json.load(f)
|
|
5274
|
+
except (json.JSONDecodeError, FileNotFoundError, OSError):
|
|
5275
|
+
counts = {}
|
|
5276
|
+
counts[gate_name] = counts.get(gate_name, 0) + 1
|
|
5277
|
+
with open(gate_file, 'w') as f:
|
|
5278
|
+
json.dump(counts, f, indent=2)
|
|
5279
|
+
print(counts[gate_name])
|
|
5280
|
+
" 2>/dev/null || echo "1"
|
|
5281
|
+
}
|
|
5282
|
+
|
|
5283
|
+
clear_gate_failure() {
|
|
5284
|
+
local gate_name="$1"
|
|
5285
|
+
local gate_file="${TARGET_DIR:-.}/.loki/quality/gate-failure-count.json"
|
|
5286
|
+
[ -f "$gate_file" ] || return 0
|
|
5287
|
+
|
|
5288
|
+
_GATE_FILE="$gate_file" _GATE_NAME="$gate_name" python3 -c "
|
|
5289
|
+
import json, os
|
|
5290
|
+
gate_file = os.environ['_GATE_FILE']
|
|
5291
|
+
gate_name = os.environ['_GATE_NAME']
|
|
5292
|
+
try:
|
|
5293
|
+
with open(gate_file) as f:
|
|
5294
|
+
counts = json.load(f)
|
|
5295
|
+
except (json.JSONDecodeError, FileNotFoundError, OSError):
|
|
5296
|
+
counts = {}
|
|
5297
|
+
counts[gate_name] = 0
|
|
5298
|
+
with open(gate_file, 'w') as f:
|
|
5299
|
+
json.dump(counts, f, indent=2)
|
|
5300
|
+
" 2>/dev/null || true
|
|
5301
|
+
}
|
|
5302
|
+
|
|
5303
|
+
get_gate_failure_count() {
|
|
5304
|
+
local gate_name="$1"
|
|
5305
|
+
local gate_file="${TARGET_DIR:-.}/.loki/quality/gate-failure-count.json"
|
|
5306
|
+
[ -f "$gate_file" ] || { echo "0"; return; }
|
|
5307
|
+
|
|
5308
|
+
_GATE_FILE="$gate_file" _GATE_NAME="$gate_name" python3 -c "
|
|
5309
|
+
import json, os
|
|
5310
|
+
gate_file = os.environ['_GATE_FILE']
|
|
5311
|
+
gate_name = os.environ['_GATE_NAME']
|
|
5312
|
+
try:
|
|
5313
|
+
with open(gate_file) as f:
|
|
5314
|
+
counts = json.load(f)
|
|
5315
|
+
print(counts.get(gate_name, 0))
|
|
5316
|
+
except (json.JSONDecodeError, FileNotFoundError, OSError):
|
|
5317
|
+
print(0)
|
|
5318
|
+
" 2>/dev/null || echo "0"
|
|
5319
|
+
}
|
|
5320
|
+
|
|
5253
5321
|
# ============================================================================
|
|
5254
5322
|
# Hard Quality Gate: Test Coverage (v6.7.0)
|
|
5255
5323
|
# Detects test runner and runs tests with coverage reporting
|
|
@@ -5287,6 +5355,58 @@ enforce_test_coverage() {
|
|
|
5287
5355
|
fi
|
|
5288
5356
|
fi
|
|
5289
5357
|
|
|
5358
|
+
# Monorepo: scan workspace packages for test runners (v6.10.0)
|
|
5359
|
+
if [ "$test_runner" = "none" ] && [ -f "${TARGET_DIR:-.}/package.json" ]; then
|
|
5360
|
+
local is_monorepo=false
|
|
5361
|
+
# Detect monorepo indicators
|
|
5362
|
+
if [ -f "${TARGET_DIR:-.}/pnpm-workspace.yaml" ] || \
|
|
5363
|
+
[ -f "${TARGET_DIR:-.}/turbo.json" ] || \
|
|
5364
|
+
[ -f "${TARGET_DIR:-.}/lerna.json" ] || \
|
|
5365
|
+
grep -q '"workspaces"' "${TARGET_DIR:-.}/package.json" 2>/dev/null; then
|
|
5366
|
+
is_monorepo=true
|
|
5367
|
+
fi
|
|
5368
|
+
|
|
5369
|
+
if [ "$is_monorepo" = "true" ]; then
|
|
5370
|
+
# Allow env override
|
|
5371
|
+
if [ -n "${LOKI_MONOREPO_TEST_CMD:-}" ]; then
|
|
5372
|
+
test_runner="monorepo-custom"
|
|
5373
|
+
local output
|
|
5374
|
+
output=$(cd "${TARGET_DIR:-.}" && eval "$LOKI_MONOREPO_TEST_CMD" 2>&1) || test_passed=false
|
|
5375
|
+
details="monorepo-custom: $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
5376
|
+
else
|
|
5377
|
+
# Scan workspace packages for test runners
|
|
5378
|
+
local workspace_runner=""
|
|
5379
|
+
for pkg_json in "${TARGET_DIR:-.}"/packages/*/package.json \
|
|
5380
|
+
"${TARGET_DIR:-.}"/apps/*/package.json \
|
|
5381
|
+
"${TARGET_DIR:-.}"/services/*/package.json; do
|
|
5382
|
+
[ -f "$pkg_json" ] || continue
|
|
5383
|
+
if grep -q '"vitest"' "$pkg_json" 2>/dev/null; then
|
|
5384
|
+
workspace_runner="vitest"
|
|
5385
|
+
break
|
|
5386
|
+
elif grep -q '"jest"' "$pkg_json" 2>/dev/null; then
|
|
5387
|
+
workspace_runner="jest"
|
|
5388
|
+
break
|
|
5389
|
+
fi
|
|
5390
|
+
done
|
|
5391
|
+
|
|
5392
|
+
if [ -n "$workspace_runner" ]; then
|
|
5393
|
+
test_runner="monorepo-$workspace_runner"
|
|
5394
|
+
local output
|
|
5395
|
+
if [ -f "${TARGET_DIR:-.}/turbo.json" ] && command -v turbo &>/dev/null; then
|
|
5396
|
+
output=$(cd "${TARGET_DIR:-.}" && npx turbo test 2>&1) || test_passed=false
|
|
5397
|
+
details="turbo test ($workspace_runner): $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
5398
|
+
elif [ -f "${TARGET_DIR:-.}/pnpm-workspace.yaml" ] && command -v pnpm &>/dev/null; then
|
|
5399
|
+
output=$(cd "${TARGET_DIR:-.}" && pnpm test --recursive 2>&1) || test_passed=false
|
|
5400
|
+
details="pnpm test --recursive ($workspace_runner): $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
5401
|
+
else
|
|
5402
|
+
output=$(cd "${TARGET_DIR:-.}" && npm test 2>&1) || test_passed=false
|
|
5403
|
+
details="npm test ($workspace_runner): $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
5404
|
+
fi
|
|
5405
|
+
fi
|
|
5406
|
+
fi
|
|
5407
|
+
fi
|
|
5408
|
+
fi
|
|
5409
|
+
|
|
5290
5410
|
# Python
|
|
5291
5411
|
if [ "$test_runner" = "none" ]; then
|
|
5292
5412
|
if [ -f "${TARGET_DIR:-.}/setup.py" ] || [ -f "${TARGET_DIR:-.}/pyproject.toml" ] || \
|
|
@@ -7398,11 +7518,16 @@ build_prompt() {
|
|
|
7398
7518
|
if [ -n "$COMPLETION_PROMISE" ]; then
|
|
7399
7519
|
completion_instruction="COMPLETION_PROMISE: [$COMPLETION_PROMISE]. ONLY output 'COMPLETION PROMISE FULFILLED: $COMPLETION_PROMISE' when this EXACT condition is met."
|
|
7400
7520
|
else
|
|
7401
|
-
completion_instruction="NO COMPLETION PROMISE SET.
|
|
7521
|
+
completion_instruction="NO COMPLETION PROMISE SET. Continue finding improvements. The Completion Council will evaluate your progress periodically. Iteration $iteration of max $MAX_ITERATIONS."
|
|
7402
7522
|
fi
|
|
7403
7523
|
|
|
7404
7524
|
# Core autonomous instructions - NO questions, NO waiting, NEVER say done
|
|
7405
|
-
local autonomous_suffix="
|
|
7525
|
+
local autonomous_suffix=""
|
|
7526
|
+
if [ "$AUTONOMY_MODE" = "perpetual" ] || [ "$PERPETUAL_MODE" = "true" ]; then
|
|
7527
|
+
autonomous_suffix="CRITICAL AUTONOMY RULES: 1) NEVER ask questions - just decide. 2) NEVER wait for confirmation - just act. 3) NEVER say 'done' or 'complete' - there's always more to improve. 4) NEVER stop voluntarily - if out of tasks, create new ones (add tests, optimize, refactor, add features). 5) Work continues PERPETUALLY. Even if PRD is implemented, find bugs, add tests, improve UX, optimize performance."
|
|
7528
|
+
else
|
|
7529
|
+
autonomous_suffix="CRITICAL AUTONOMY RULES: 1) NEVER ask questions - just decide. 2) NEVER wait for confirmation - just act. 3) When all PRD requirements are implemented and tests pass, output the completion promise text EXACTLY: '$COMPLETION_PROMISE'. 4) If out of tasks but PRD is not fully implemented, continue working on remaining requirements. 5) Focus on completing PRD scope, not endless improvements."
|
|
7530
|
+
fi
|
|
7406
7531
|
|
|
7407
7532
|
# Skill files are always copied to .loki/skills/ for all providers
|
|
7408
7533
|
local sdlc_instruction="SDLC_PHASES_ENABLED: [$phases]. Execute ALL enabled phases. Log results to .loki/logs/. See .loki/SKILL.md for phase details. Skill modules at .loki/skills/."
|
|
@@ -7823,6 +7948,21 @@ run_autonomous() {
|
|
|
7823
7948
|
fi
|
|
7824
7949
|
fi
|
|
7825
7950
|
|
|
7951
|
+
# Auto-derive completion promise from PRD (v6.10.0)
|
|
7952
|
+
# When PRD exists but no explicit promise, auto-derive one and switch to checkpoint mode
|
|
7953
|
+
if [ -n "$prd_path" ] && [ -f "$prd_path" ] && [ -z "$COMPLETION_PROMISE" ]; then
|
|
7954
|
+
if [ "${LOKI_AUTO_COMPLETION_PROMISE:-true}" = "true" ]; then
|
|
7955
|
+
COMPLETION_PROMISE="All PRD requirements implemented and tests passing"
|
|
7956
|
+
log_info "Auto-derived completion promise: $COMPLETION_PROMISE"
|
|
7957
|
+
# PRD-driven work is finite; switch from perpetual to checkpoint
|
|
7958
|
+
if [ "${LOKI_FORCE_PERPETUAL:-false}" != "true" ] && [ "$AUTONOMY_MODE" = "perpetual" ]; then
|
|
7959
|
+
AUTONOMY_MODE="checkpoint"
|
|
7960
|
+
PERPETUAL_MODE="false"
|
|
7961
|
+
log_info "Switched autonomy mode: perpetual -> checkpoint (PRD-driven work is finite)"
|
|
7962
|
+
fi
|
|
7963
|
+
fi
|
|
7964
|
+
fi
|
|
7965
|
+
|
|
7826
7966
|
# Populate task queue from BMAD artifacts (if present, runs once)
|
|
7827
7967
|
populate_bmad_queue
|
|
7828
7968
|
|
|
@@ -8302,29 +8442,55 @@ if __name__ == "__main__":
|
|
|
8302
8442
|
# Checkpoint after each iteration (v5.57.0)
|
|
8303
8443
|
create_checkpoint "iteration-${ITERATION_COUNT} complete" "iteration-${ITERATION_COUNT}"
|
|
8304
8444
|
|
|
8305
|
-
# Quality gates (v6.
|
|
8445
|
+
# Quality gates (v6.10.0 - escalation ladder)
|
|
8306
8446
|
local gate_failures=""
|
|
8307
8447
|
if [ "${LOKI_HARD_GATES:-true}" = "true" ]; then
|
|
8308
8448
|
# Static analysis gate
|
|
8309
8449
|
if [ "${PHASE_STATIC_ANALYSIS:-true}" = "true" ]; then
|
|
8310
|
-
enforce_static_analysis
|
|
8450
|
+
if enforce_static_analysis; then
|
|
8451
|
+
clear_gate_failure "static_analysis"
|
|
8452
|
+
else
|
|
8453
|
+
local sa_count
|
|
8454
|
+
sa_count=$(track_gate_failure "static_analysis")
|
|
8311
8455
|
gate_failures="${gate_failures}static_analysis,"
|
|
8312
|
-
log_warn "Static analysis FAILED - findings injected into next iteration"
|
|
8313
|
-
|
|
8456
|
+
log_warn "Static analysis FAILED ($sa_count consecutive) - findings injected into next iteration"
|
|
8457
|
+
fi
|
|
8314
8458
|
fi
|
|
8315
8459
|
# Test coverage gate
|
|
8316
8460
|
if [ "${PHASE_UNIT_TESTS:-true}" = "true" ]; then
|
|
8317
|
-
enforce_test_coverage
|
|
8461
|
+
if enforce_test_coverage; then
|
|
8462
|
+
clear_gate_failure "test_coverage"
|
|
8463
|
+
else
|
|
8464
|
+
local tc_count
|
|
8465
|
+
tc_count=$(track_gate_failure "test_coverage")
|
|
8318
8466
|
gate_failures="${gate_failures}test_coverage,"
|
|
8319
|
-
log_warn "Test coverage gate FAILED - must pass next iteration"
|
|
8320
|
-
|
|
8467
|
+
log_warn "Test coverage gate FAILED ($tc_count consecutive) - must pass next iteration"
|
|
8468
|
+
fi
|
|
8321
8469
|
fi
|
|
8322
|
-
# Code review gate (upgraded from advisory)
|
|
8470
|
+
# Code review gate (upgraded from advisory, with escalation)
|
|
8323
8471
|
if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
8324
|
-
run_code_review
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8472
|
+
if run_code_review; then
|
|
8473
|
+
clear_gate_failure "code_review"
|
|
8474
|
+
else
|
|
8475
|
+
local cr_count
|
|
8476
|
+
cr_count=$(track_gate_failure "code_review")
|
|
8477
|
+
if [ "$cr_count" -ge "$GATE_PAUSE_LIMIT" ]; then
|
|
8478
|
+
log_error "Gate escalation: code_review failed $cr_count times (>= $GATE_PAUSE_LIMIT) - forcing PAUSE for human intervention"
|
|
8479
|
+
echo "PAUSE" > "${TARGET_DIR:-.}/.loki/signals/GATE_ESCALATION"
|
|
8480
|
+
echo "code_review gate failed $cr_count consecutive times" >> "${TARGET_DIR:-.}/.loki/signals/GATE_ESCALATION"
|
|
8481
|
+
touch "${TARGET_DIR:-.}/.loki/signals/PAUSE"
|
|
8482
|
+
elif [ "$cr_count" -ge "$GATE_ESCALATE_LIMIT" ]; then
|
|
8483
|
+
log_warn "Gate escalation: code_review failed $cr_count times (>= $GATE_ESCALATE_LIMIT) - escalating"
|
|
8484
|
+
echo "ESCALATE" > "${TARGET_DIR:-.}/.loki/signals/GATE_ESCALATION"
|
|
8485
|
+
gate_failures="${gate_failures}code_review_ESCALATED,"
|
|
8486
|
+
elif [ "$cr_count" -ge "$GATE_CLEAR_LIMIT" ]; then
|
|
8487
|
+
log_warn "Gate cleared: code_review failed $cr_count times (>= $GATE_CLEAR_LIMIT) - clearing gate, agent tried"
|
|
8488
|
+
clear_gate_failure "code_review"
|
|
8489
|
+
else
|
|
8490
|
+
gate_failures="${gate_failures}code_review,"
|
|
8491
|
+
log_warn "Code review BLOCKED ($cr_count consecutive) - Critical/High findings"
|
|
8492
|
+
fi
|
|
8493
|
+
fi
|
|
8328
8494
|
fi
|
|
8329
8495
|
# Store gate failures for prompt injection
|
|
8330
8496
|
if [ -n "$gate_failures" ]; then
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
package/mcp/__init__.py
CHANGED