loki-mode 7.90.2 → 7.91.1
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/loki +19 -1
- package/autonomy/run.sh +373 -12
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +176 -4
- package/dashboard/static/index.html +162 -134
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.
|
|
6
|
+
# Loki Mode v7.91.1
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
411
|
-
**v7.
|
|
411
|
+
**v7.91.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.91.1
|
package/autonomy/loki
CHANGED
|
@@ -9834,7 +9834,25 @@ cmd_doctor() {
|
|
|
9834
9834
|
echo -e " ${GREEN}PASS${NC} ANTHROPIC_API_KEY is set"
|
|
9835
9835
|
pass_count=$((pass_count + 1))
|
|
9836
9836
|
elif command -v claude &>/dev/null; then
|
|
9837
|
-
|
|
9837
|
+
# Zero-network check: warn if the Claude OAuth login has expired. An
|
|
9838
|
+
# expired token would otherwise pass and then 401 mid-build (the build
|
|
9839
|
+
# stalls at BOOTSTRAP). Mirror run.sh's fail-fast preflight here so a
|
|
9840
|
+
# user catches it with `loki doctor` BEFORE starting a build.
|
|
9841
|
+
_claude_creds="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.credentials.json"
|
|
9842
|
+
if [ -s "$_claude_creds" ] && [ "$(python3 -c "
|
|
9843
|
+
import json, sys, time
|
|
9844
|
+
try:
|
|
9845
|
+
d = json.load(open(sys.argv[1]))
|
|
9846
|
+
exp = d.get('claudeAiOauth', {}).get('expiresAt')
|
|
9847
|
+
print('expired' if isinstance(exp,(int,float)) and exp>0 and (exp/1000.0)<=(time.time()+60) else 'ok')
|
|
9848
|
+
except Exception:
|
|
9849
|
+
print('ok')
|
|
9850
|
+
" "$_claude_creds" 2>/dev/null || echo ok)" = "expired" ]; then
|
|
9851
|
+
echo -e " ${YELLOW}WARN${NC} Claude login has EXPIRED -- run 'claude login' before a build (it would otherwise stall)"
|
|
9852
|
+
warn_count=$((warn_count + 1))
|
|
9853
|
+
else
|
|
9854
|
+
echo -e " ${DIM} -- ${NC} ANTHROPIC_API_KEY not set (Claude CLI uses its own login)"
|
|
9855
|
+
fi
|
|
9838
9856
|
fi
|
|
9839
9857
|
if [ -n "${OPENAI_API_KEY:-}" ]; then
|
|
9840
9858
|
echo -e " ${GREEN}PASS${NC} OPENAI_API_KEY is set"
|
package/autonomy/run.sh
CHANGED
|
@@ -218,6 +218,11 @@ if [[ -z "${LOKI_RUNNING_FROM_TEMP:-}" ]] && [[ "${BASH_SOURCE[0]}" == "${0}" ]]
|
|
|
218
218
|
# BUG-XC-011: Set trap BEFORE exec so the temp file gets cleaned up
|
|
219
219
|
trap 'rm -f "$TEMP_SCRIPT"' EXIT
|
|
220
220
|
export LOKI_RUNNING_FROM_TEMP=1
|
|
221
|
+
# Record the EXACT temp-copy path so the post-exec cleanup trap deletes THIS
|
|
222
|
+
# temp file and NEVER the canonical source (root cause of the recurring
|
|
223
|
+
# "run.sh self-deleted on build spawn" bug: an inherited LOKI_RUNNING_FROM_TEMP
|
|
224
|
+
# skips the self-copy block, leaving BASH_SOURCE[0]=the real run.sh).
|
|
225
|
+
export LOKI_TEMP_SCRIPT_PATH="$TEMP_SCRIPT"
|
|
221
226
|
export LOKI_ORIGINAL_SCRIPT_DIR="$SCRIPT_DIR"
|
|
222
227
|
export LOKI_ORIGINAL_PROJECT_DIR="$PROJECT_DIR"
|
|
223
228
|
exec "$TEMP_SCRIPT" "$@"
|
|
@@ -227,9 +232,14 @@ fi
|
|
|
227
232
|
SCRIPT_DIR="${LOKI_ORIGINAL_SCRIPT_DIR:-$SCRIPT_DIR}"
|
|
228
233
|
PROJECT_DIR="${LOKI_ORIGINAL_PROJECT_DIR:-$PROJECT_DIR}"
|
|
229
234
|
|
|
230
|
-
# Clean up temp
|
|
231
|
-
|
|
232
|
-
|
|
235
|
+
# Clean up ONLY the recorded temp copy, and ONLY if it is a real temp file.
|
|
236
|
+
# Deleting BASH_SOURCE[0] here was the bug: an inherited LOKI_RUNNING_FROM_TEMP
|
|
237
|
+
# made BASH_SOURCE[0] the canonical source, so run.sh deleted itself on spawned
|
|
238
|
+
# builds. Guard on the recorded path being a real /tmp/loki-run-* file.
|
|
239
|
+
if [[ "${LOKI_RUNNING_FROM_TEMP:-}" == "1" ]] \
|
|
240
|
+
&& [[ -n "${LOKI_TEMP_SCRIPT_PATH:-}" ]] \
|
|
241
|
+
&& [[ "${LOKI_TEMP_SCRIPT_PATH}" == /tmp/loki-run-* || "${LOKI_TEMP_SCRIPT_PATH}" == "${TMPDIR:-/tmp}"loki-run-* ]]; then
|
|
242
|
+
trap 'rm -f "${LOKI_TEMP_SCRIPT_PATH}" 2>/dev/null' EXIT
|
|
233
243
|
fi
|
|
234
244
|
|
|
235
245
|
#===============================================================================
|
|
@@ -829,6 +839,12 @@ CONCURRENCY_CRITICAL_THRESHOLD=${LOKI_CONCURRENCY_CRITICAL_THRESHOLD:-95}
|
|
|
829
839
|
GATE_CLEAR_LIMIT=${LOKI_GATE_CLEAR_LIMIT:-3}
|
|
830
840
|
GATE_ESCALATE_LIMIT=${LOKI_GATE_ESCALATE_LIMIT:-5}
|
|
831
841
|
GATE_PAUSE_LIMIT=${LOKI_GATE_PAUSE_LIMIT:-10}
|
|
842
|
+
# Workspace resolution: the build runs against TARGET_DIR, defaulting to the
|
|
843
|
+
# launch cwd. A caller can pin a per-build workspace by exporting
|
|
844
|
+
# LOKI_TARGET_DIR (and the matching LOKI_DIR=<dir>/.loki); the dashboard
|
|
845
|
+
# /api/control/start `workspace` param (Track-1 S1) does exactly this so a
|
|
846
|
+
# hosted build runs in its own dir instead of the engine's repo. Every
|
|
847
|
+
# $TARGET_DIR/.loki reference below then resolves under that workspace.
|
|
832
848
|
TARGET_DIR="${LOKI_TARGET_DIR:-$(pwd)}"
|
|
833
849
|
PARALLEL_BLOG=${LOKI_PARALLEL_BLOG:-false}
|
|
834
850
|
AUTO_MERGE=${LOKI_AUTO_MERGE:-true}
|
|
@@ -1378,6 +1394,34 @@ emit_event_json() {
|
|
|
1378
1394
|
log_debug "Event: $event_type - $json_data"
|
|
1379
1395
|
}
|
|
1380
1396
|
|
|
1397
|
+
# Per-stage timeline event (v7.91.x). Emits one stage_complete record per
|
|
1398
|
+
# quality-gate / build stage so the SaaS timeline can show where build time
|
|
1399
|
+
# goes within an iteration (the provider call itself is already bracketed by
|
|
1400
|
+
# iteration_start/iteration_complete). This is purely ADDITIVE: it appends one
|
|
1401
|
+
# event line via emit_event_json and never changes a gate verdict, gate exit
|
|
1402
|
+
# code, or control flow. Duration is computed by the caller (whole-second
|
|
1403
|
+
# resolution, sufficient for multi-second gates) and carried on the event so
|
|
1404
|
+
# the SaaS does not have to infer it from coarse ISO timestamps.
|
|
1405
|
+
# emit_stage_complete <stage_name> <status: pass|fail> <start_epoch_seconds>
|
|
1406
|
+
# Event shape:
|
|
1407
|
+
# {type:"stage_complete", timestamp, data:{stage, status, duration_s, iteration}}
|
|
1408
|
+
# Best-effort: any failure is swallowed so it can never block the build.
|
|
1409
|
+
emit_stage_complete() {
|
|
1410
|
+
local stage="$1"
|
|
1411
|
+
local status="$2"
|
|
1412
|
+
local t0="$3"
|
|
1413
|
+
local now dur
|
|
1414
|
+
now=$(date +%s 2>/dev/null) || return 0
|
|
1415
|
+
[ -n "$t0" ] || return 0
|
|
1416
|
+
dur=$(( now - t0 ))
|
|
1417
|
+
[ "$dur" -ge 0 ] 2>/dev/null || dur=0
|
|
1418
|
+
emit_event_json "stage_complete" \
|
|
1419
|
+
"stage=$stage" \
|
|
1420
|
+
"status=$status" \
|
|
1421
|
+
"duration_s=$dur" \
|
|
1422
|
+
"iteration=${ITERATION_COUNT:-0}" 2>/dev/null || true
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1381
1425
|
# Trust-layer metrics event writer (benchmark program section 3). Appends one
|
|
1382
1426
|
# durable record per trust event to .loki/metrics/trust-events.jsonl via the
|
|
1383
1427
|
# Python writer (single source of truth for the JSONL schema). This is ADDITIVE
|
|
@@ -1428,6 +1472,30 @@ _loki_trust_run_id() {
|
|
|
1428
1472
|
printf '%s' ""
|
|
1429
1473
|
}
|
|
1430
1474
|
|
|
1475
|
+
# _advance_current_phase: atomically advance currentPhase in orchestrator.json.
|
|
1476
|
+
# This is the single source of truth for the build dashboard and the BFF
|
|
1477
|
+
# reconciliation gate (isTerminalPhase). The engine initialises it to "BOOTSTRAP"
|
|
1478
|
+
# at session start; call this to advance through the SDLC lifecycle phases.
|
|
1479
|
+
# Args: $1 = the new phase value (REASONING, BUILDING, VERIFYING, COMPLETED, etc.)
|
|
1480
|
+
_advance_current_phase() {
|
|
1481
|
+
local new_phase="${1:?phase required}"
|
|
1482
|
+
local loki_dir="${LOKI_DIR:-${TARGET_DIR:-.}}/.loki"
|
|
1483
|
+
local orch="$loki_dir/state/orchestrator.json"
|
|
1484
|
+
[ -f "$orch" ] || return 0
|
|
1485
|
+
# Values are passed via argv (not interpolated into the source) so a phase or
|
|
1486
|
+
# path containing quotes can never break or inject into the python.
|
|
1487
|
+
python3 -c "
|
|
1488
|
+
import json, sys
|
|
1489
|
+
f, phase = sys.argv[1], sys.argv[2]
|
|
1490
|
+
try:
|
|
1491
|
+
d = json.load(open(f))
|
|
1492
|
+
d['currentPhase'] = phase
|
|
1493
|
+
json.dump(d, open(f, 'w'))
|
|
1494
|
+
except (json.JSONDecodeError, OSError):
|
|
1495
|
+
pass
|
|
1496
|
+
" "$orch" "$new_phase" 2>/dev/null || true
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1431
1499
|
# Usage: record_trust_event_bash <event_type> [key=value ...]
|
|
1432
1500
|
# Pass LOKI_TRUST_RUN_ID in the environment to override the resolved id (the
|
|
1433
1501
|
# run_start site sets it to the freshly minted id so the first event matches).
|
|
@@ -1808,6 +1876,37 @@ validate_api_keys() {
|
|
|
1808
1876
|
local masked="${key_value:0:8}...${key_value: -4}"
|
|
1809
1877
|
log_info "API key $key_var: $masked (${#key_value} chars)"
|
|
1810
1878
|
|
|
1879
|
+
# Fail-fast auth preflight (v7.91): for Claude OAuth logins, a present-but-
|
|
1880
|
+
# EXPIRED token passes the presence check above but then 401s on the first
|
|
1881
|
+
# provider call, leaving the build stalled at BOOTSTRAP with no clear cause.
|
|
1882
|
+
# Catch that here with a zero-network, zero-token local expiry check so the
|
|
1883
|
+
# user gets an instant, copy-pasteable fix instead of a silent stall.
|
|
1884
|
+
# Opt out with LOKI_SKIP_AUTH_PREFLIGHT=1 (offline/odd setups).
|
|
1885
|
+
if [[ "$provider" == "claude" && "${LOKI_SKIP_AUTH_PREFLIGHT:-}" != "1" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
1886
|
+
local _creds="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.credentials.json"
|
|
1887
|
+
if [[ -s "$_creds" ]]; then
|
|
1888
|
+
local _expired
|
|
1889
|
+
_expired=$(python3 -c "
|
|
1890
|
+
import json, sys, time
|
|
1891
|
+
try:
|
|
1892
|
+
d = json.load(open(sys.argv[1]))
|
|
1893
|
+
exp = d.get('claudeAiOauth', {}).get('expiresAt')
|
|
1894
|
+
# expiresAt is epoch milliseconds; compare with a 60s safety margin.
|
|
1895
|
+
if isinstance(exp, (int, float)) and exp > 0 and (exp / 1000.0) <= (time.time() + 60):
|
|
1896
|
+
print('expired')
|
|
1897
|
+
except Exception:
|
|
1898
|
+
pass # unreadable/unknown schema -> do not block (fail open, let the call decide)
|
|
1899
|
+
" "$_creds" 2>/dev/null || true)
|
|
1900
|
+
if [[ "$_expired" == "expired" ]]; then
|
|
1901
|
+
log_error "Your Claude Code login has expired -- the build would stall instead of running."
|
|
1902
|
+
log_error "Fix it in one step, then retry:"
|
|
1903
|
+
log_error " claude login"
|
|
1904
|
+
log_error "(or set ANTHROPIC_API_KEY, or LOKI_SKIP_AUTH_PREFLIGHT=1 to bypass this check)"
|
|
1905
|
+
return 1
|
|
1906
|
+
fi
|
|
1907
|
+
fi
|
|
1908
|
+
fi
|
|
1909
|
+
|
|
1811
1910
|
return 0
|
|
1812
1911
|
}
|
|
1813
1912
|
|
|
@@ -6128,6 +6227,152 @@ audit_log() {
|
|
|
6128
6227
|
echo "$log_entry" >> "$audit_file"
|
|
6129
6228
|
}
|
|
6130
6229
|
|
|
6230
|
+
#===============================================================================
|
|
6231
|
+
# Engine-owned workspace git-init (Plan #16, Option A-1)
|
|
6232
|
+
#===============================================================================
|
|
6233
|
+
|
|
6234
|
+
# Resolve a path to its physical absolute form (symlinks + .. collapsed) using
|
|
6235
|
+
# whatever is available; falls back to the input unchanged. Portable across the
|
|
6236
|
+
# BSD (macOS) and GNU userlands the engine runs on.
|
|
6237
|
+
_loki_resolve_path() {
|
|
6238
|
+
local p="${1:-}"
|
|
6239
|
+
[ -n "$p" ] || { printf '%s' ""; return 0; }
|
|
6240
|
+
if command -v realpath >/dev/null 2>&1; then
|
|
6241
|
+
realpath "$p" 2>/dev/null && return 0
|
|
6242
|
+
fi
|
|
6243
|
+
# python3 is a hard engine dependency; use it as the portable fallback.
|
|
6244
|
+
python3 - "$p" <<'PYRESOLVE' 2>/dev/null && return 0
|
|
6245
|
+
import os, sys
|
|
6246
|
+
print(os.path.realpath(sys.argv[1]))
|
|
6247
|
+
PYRESOLVE
|
|
6248
|
+
printf '%s' "$p"
|
|
6249
|
+
}
|
|
6250
|
+
|
|
6251
|
+
# True (0) when TARGET_DIR is an engine-owned, freshly-minted build workspace
|
|
6252
|
+
# that Loki may auto-git-init without surprising a user. Two honest signals:
|
|
6253
|
+
# 1. LOKI_AUTO_GIT_INIT=1 -- explicit opt-in (non-SaaS automation).
|
|
6254
|
+
# 2. LOKI_TARGET_DIR is set AND realpath-contained under one of the
|
|
6255
|
+
# colon-separated LOKI_WORKSPACE_ROOTS dirs (the v7.91.0 SaaS route: the
|
|
6256
|
+
# BFF mints <root>/<buildId> and the server pins LOKI_TARGET_DIR to it).
|
|
6257
|
+
# A user's own folder (`loki start ./prd.md`, no workspace, roots unset) is
|
|
6258
|
+
# NEVER engine-owned -- it must not get a silent .git. Realpath containment (not
|
|
6259
|
+
# a prefix string match) so /root/build-other does not match /root/build.
|
|
6260
|
+
_loki_workspace_is_engine_owned() {
|
|
6261
|
+
[ "${LOKI_AUTO_GIT_INIT:-0}" = "1" ] && return 0
|
|
6262
|
+
|
|
6263
|
+
local roots_raw="${LOKI_WORKSPACE_ROOTS:-}"
|
|
6264
|
+
[ -n "${LOKI_TARGET_DIR:-}" ] || return 1
|
|
6265
|
+
[ -n "$roots_raw" ] || return 1
|
|
6266
|
+
|
|
6267
|
+
local ws_real root_real
|
|
6268
|
+
ws_real="$(_loki_resolve_path "${TARGET_DIR:-.}")"
|
|
6269
|
+
[ -n "$ws_real" ] || return 1
|
|
6270
|
+
|
|
6271
|
+
local IFS=':'
|
|
6272
|
+
local root
|
|
6273
|
+
for root in $roots_raw; do
|
|
6274
|
+
[ -n "$root" ] || continue
|
|
6275
|
+
root_real="$(_loki_resolve_path "$root")"
|
|
6276
|
+
[ -n "$root_real" ] || continue
|
|
6277
|
+
# Exact match or contained: ws == root, or ws starts with root + "/".
|
|
6278
|
+
if [ "$ws_real" = "$root_real" ] || case "$ws_real" in "$root_real"/*) true ;; *) false ;; esac; then
|
|
6279
|
+
return 0
|
|
6280
|
+
fi
|
|
6281
|
+
done
|
|
6282
|
+
return 1
|
|
6283
|
+
}
|
|
6284
|
+
|
|
6285
|
+
# Plan #16 Option A-1: establish git in an engine-owned build workspace so the
|
|
6286
|
+
# review/verify gate (run_code_review) and branch-isolation chain can actually
|
|
6287
|
+
# run. Without git history, run_code_review's diff resolves empty and the gate
|
|
6288
|
+
# SKIPS (silently reporting PASS) -- so a build that was never reviewed could
|
|
6289
|
+
# earn a VERIFIED receipt. This makes the gate RUN; it does NOT force a green
|
|
6290
|
+
# (a build whose review/tests fail still gets an honest verdict).
|
|
6291
|
+
#
|
|
6292
|
+
# Constraints honored:
|
|
6293
|
+
# - Engine-owned workspaces ONLY (see _loki_workspace_is_engine_owned). A
|
|
6294
|
+
# user's own folder is never silently git-init'd.
|
|
6295
|
+
# - Already a git repo (user's repo OR the engine source tree) -> NO-OP. Only
|
|
6296
|
+
# a non-git workspace is initialized.
|
|
6297
|
+
# - One INITIAL commit (not a bare init): a zero-commit unborn HEAD breaks the
|
|
6298
|
+
# start-SHA capture (git rev-parse HEAD) and re-trips the HEAD~1 skip. The
|
|
6299
|
+
# commit uses --allow-empty because a greenfield workspace at build start
|
|
6300
|
+
# holds only .loki/ (the spec lands in .loki/specs/), which .loki/.gitignore
|
|
6301
|
+
# excludes -> nothing to stage -> a bare commit would fail and leave an
|
|
6302
|
+
# unborn HEAD. --allow-empty guarantees a real HEAD either way.
|
|
6303
|
+
# - Neutral repo-local identity (loki-build) so the initial commit AND the
|
|
6304
|
+
# later commit_session_changes commit never inherit a developer's global git
|
|
6305
|
+
# identity. Repo-local sticks on a freshly-init'd workspace (no revert hook).
|
|
6306
|
+
# - Secrets never committed: brownfield files are staged through the same
|
|
6307
|
+
# secret-scan guard (_commit_path_looks_secret / _commit_scan_secret_file)
|
|
6308
|
+
# used by commit_session_changes; any offender unstages the whole set and
|
|
6309
|
+
# the initial commit falls back to --allow-empty (HEAD still established).
|
|
6310
|
+
maybe_git_init_engine_workspace() {
|
|
6311
|
+
command -v git >/dev/null 2>&1 || return 0
|
|
6312
|
+
_loki_workspace_is_engine_owned || return 0
|
|
6313
|
+
|
|
6314
|
+
local ws="${TARGET_DIR:-.}"
|
|
6315
|
+
[ -d "$ws" ] || return 0
|
|
6316
|
+
|
|
6317
|
+
# Already a git repo (user repo or engine source tree): do nothing.
|
|
6318
|
+
if git -C "$ws" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
6319
|
+
return 0
|
|
6320
|
+
fi
|
|
6321
|
+
|
|
6322
|
+
log_info "Engine-owned workspace is not a git repo; initializing for review/verify gates"
|
|
6323
|
+
|
|
6324
|
+
if ! git -C "$ws" init -q >/dev/null 2>&1; then
|
|
6325
|
+
log_warn "git init failed in engine workspace; review gate will skip (non-fatal)"
|
|
6326
|
+
return 0
|
|
6327
|
+
fi
|
|
6328
|
+
|
|
6329
|
+
# Neutral repo-local identity for the initial AND session-end commits.
|
|
6330
|
+
git -C "$ws" config user.name "loki-build" >/dev/null 2>&1 || true
|
|
6331
|
+
git -C "$ws" config user.email "loki-build@autonomi.dev" >/dev/null 2>&1 || true
|
|
6332
|
+
|
|
6333
|
+
# Self-ignore .loki/ runtime state so no commit ever stages it (mirrors
|
|
6334
|
+
# setup_agent_branch). Idempotent.
|
|
6335
|
+
mkdir -p "$ws/.loki" 2>/dev/null || true
|
|
6336
|
+
[ -f "$ws/.loki/.gitignore" ] || printf '*\n' > "$ws/.loki/.gitignore" 2>/dev/null || true
|
|
6337
|
+
|
|
6338
|
+
# Stage everything except .loki/ and an obvious-secret-path denylist (same
|
|
6339
|
+
# first-cut excludes commit_session_changes uses). The scan loop below is the
|
|
6340
|
+
# real guarantee for nested/weak secrets.
|
|
6341
|
+
git -C "$ws" add -A \
|
|
6342
|
+
':!.loki' ':!.loki/' \
|
|
6343
|
+
':!.env' ':!.env.*' ':!*.env' \
|
|
6344
|
+
':!*.key' ':!*.pem' ':!*.p12' ':!*.keystore' \
|
|
6345
|
+
':!id_rsa*' ':!*.token' ':!credentials*' >/dev/null 2>&1 || true
|
|
6346
|
+
|
|
6347
|
+
# Secret-scan staged files. ANY offender -> unstage all (safe default: never
|
|
6348
|
+
# commit a possible secret). The --allow-empty commit below still runs so a
|
|
6349
|
+
# real HEAD is established regardless.
|
|
6350
|
+
local _offenders=""
|
|
6351
|
+
local _f
|
|
6352
|
+
while IFS= read -r -d '' _f; do
|
|
6353
|
+
[ -f "$ws/$_f" ] || continue
|
|
6354
|
+
if _commit_path_looks_secret "$ws/$_f" || _commit_scan_secret_file "$ws/$_f"; then
|
|
6355
|
+
_offenders="${_offenders}${_offenders:+, }${_f}"
|
|
6356
|
+
fi
|
|
6357
|
+
done < <(git -C "$ws" diff --cached --name-only -z 2>/dev/null)
|
|
6358
|
+
|
|
6359
|
+
if [ -n "$_offenders" ]; then
|
|
6360
|
+
git -C "$ws" reset >/dev/null 2>&1 || true
|
|
6361
|
+
log_warn "Initial commit: possible secret in ${_offenders}; left unstaged (baseline commit will be empty)"
|
|
6362
|
+
fi
|
|
6363
|
+
|
|
6364
|
+
# ONE initial commit. --allow-empty: a greenfield workspace stages nothing
|
|
6365
|
+
# (only .loki/, which is ignored), so a bare commit would fail and leave an
|
|
6366
|
+
# unborn HEAD -- the exact failure mode this whole block exists to avoid.
|
|
6367
|
+
if git -C "$ws" commit --allow-empty -q -m "loki: initial build workspace baseline" >/dev/null 2>&1; then
|
|
6368
|
+
log_info "Initialized git in engine workspace (initial baseline commit created)"
|
|
6369
|
+
audit_log "WORKSPACE_GIT_INIT" "workspace=$ws"
|
|
6370
|
+
else
|
|
6371
|
+
log_warn "Initial baseline commit failed; review gate may skip (non-fatal)"
|
|
6372
|
+
fi
|
|
6373
|
+
return 0
|
|
6374
|
+
}
|
|
6375
|
+
|
|
6131
6376
|
#===============================================================================
|
|
6132
6377
|
# Branch Protection for Agent Changes
|
|
6133
6378
|
#===============================================================================
|
|
@@ -9612,17 +9857,89 @@ run_code_review() {
|
|
|
9612
9857
|
# common case) because git ignores the exclude pathspec for paths it does not
|
|
9613
9858
|
# track. ':(exclude).git/' is harmless (git never diffs .git/) and is kept
|
|
9614
9859
|
# only for parity with the evidence-gate exclusion list.
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9860
|
+
# Finding #596 + Plan #16: exclude .loki/, .git/, AND the standard dependency
|
|
9861
|
+
# / build noise dirs. The A-2 temp-index `git add -A` below stages EVERY
|
|
9862
|
+
# untracked file, and a fresh greenfield workspace has no root .gitignore yet
|
|
9863
|
+
# (only .loki/.gitignore, scoped to .loki/). Without these excludes a build
|
|
9864
|
+
# that ran `npm install` / `pip install` before writing a .gitignore would
|
|
9865
|
+
# stage node_modules/ (or .venv/, dist/, build/) into the temp index, bloat
|
|
9866
|
+
# the review diff to multi-MB, overflow the reviewer prompt, force NO_OUTPUT,
|
|
9867
|
+
# and defeat the gate -- exactly the Finding #596 class. The diff pathspec
|
|
9868
|
+
# filters the OUTPUT regardless of what the temp index staged, so this one
|
|
9869
|
+
# list fixes both diff_content and changed_files. Mirrors the metrics-path
|
|
9870
|
+
# noise set (run.sh:12592-12598) plus __pycache__ and vendor.
|
|
9871
|
+
local _review_pathspec=(-- . \
|
|
9872
|
+
':(exclude).loki/' ':(exclude).git/' ':(exclude)**/.loki/**' \
|
|
9873
|
+
':(exclude)node_modules/' ':(exclude)**/node_modules/**' \
|
|
9874
|
+
':(exclude)venv/' ':(exclude).venv/' ':(exclude)**/venv/**' ':(exclude)**/.venv/**' \
|
|
9875
|
+
':(exclude)dist/' ':(exclude)build/' ':(exclude)**/dist/**' ':(exclude)**/build/**' \
|
|
9876
|
+
':(exclude)__pycache__/' ':(exclude)**/__pycache__/**' \
|
|
9877
|
+
':(exclude)vendor/' ':(exclude)**/vendor/**')
|
|
9878
|
+
|
|
9879
|
+
# Plan #16 (A-2): make the review diff base robust to shallow/fresh history,
|
|
9880
|
+
# and surface NEW (untracked) files -- the whole greenfield build is new
|
|
9881
|
+
# files, which `git diff <base>` (tracked-only) never shows.
|
|
9882
|
+
#
|
|
9883
|
+
# Base preference, mirroring the proven metrics-path fallback chain:
|
|
9884
|
+
# 1. ${_LOKI_RUN_START_SHA} when it resolves to a commit -- the correct
|
|
9885
|
+
# per-run baseline (captured at run start; also what the summary/"Review
|
|
9886
|
+
# the work" command uses). After A-1, a fresh workspace has exactly one
|
|
9887
|
+
# commit at iteration 1, so HEAD~1 does NOT resolve -- the start-SHA is
|
|
9888
|
+
# what makes the gate run on iteration 1.
|
|
9889
|
+
# 2. HEAD~1 when it resolves (the live engine-source case, deep history).
|
|
9890
|
+
# 3. the git empty-tree object (computed, never a hardcoded SHA-1 constant,
|
|
9891
|
+
# so it survives a SHA-256 repo) so a single-commit repo still yields a
|
|
9892
|
+
# real diff against an empty baseline instead of an empty string.
|
|
9893
|
+
local _review_base=""
|
|
9894
|
+
if [ -n "${_LOKI_RUN_START_SHA:-}" ] && \
|
|
9895
|
+
git -C "${TARGET_DIR:-.}" rev-parse --verify --quiet "${_LOKI_RUN_START_SHA}^{commit}" >/dev/null 2>&1; then
|
|
9896
|
+
_review_base="${_LOKI_RUN_START_SHA}"
|
|
9897
|
+
elif git -C "${TARGET_DIR:-.}" rev-parse --verify --quiet 'HEAD~1^{commit}' >/dev/null 2>&1; then
|
|
9898
|
+
_review_base="HEAD~1"
|
|
9899
|
+
else
|
|
9900
|
+
_review_base="$(git -C "${TARGET_DIR:-.}" hash-object -t tree /dev/null 2>/dev/null || echo "")"
|
|
9901
|
+
fi
|
|
9902
|
+
|
|
9903
|
+
# Build the review diff against a THROWAWAY index (GIT_INDEX_FILE points at a
|
|
9904
|
+
# fresh, nonexistent path) so a `git add -A` captures the full working tree --
|
|
9905
|
+
# new + modified files alike -- WITHOUT touching the real index. This avoids
|
|
9906
|
+
# disturbing any other porcelain consumer (the evidence hard gate's status
|
|
9907
|
+
# read, create_checkpoint's `git stash create`, commit_session_changes). The
|
|
9908
|
+
# `.loki/.gitignore` (`*`) keeps runtime state out; the pathspec is a belt-
|
|
9909
|
+
# and-suspenders exclude. `diff --cached <base>` then compares that staged
|
|
9910
|
+
# snapshot to the baseline -- a real unified diff the reviewer can read.
|
|
9911
|
+
local diff_content=""
|
|
9912
|
+
local changed_files=""
|
|
9913
|
+
if [ -n "$_review_base" ]; then
|
|
9914
|
+
local _rev_idx
|
|
9915
|
+
_rev_idx="$(mktemp -u "${TMPDIR:-/tmp}/loki-revidx.XXXXXX")"
|
|
9916
|
+
GIT_INDEX_FILE="$_rev_idx" git -C "${TARGET_DIR:-.}" add -A 2>/dev/null || true
|
|
9917
|
+
diff_content=$(GIT_INDEX_FILE="$_rev_idx" git -C "${TARGET_DIR:-.}" diff --cached "$_review_base" "${_review_pathspec[@]}" 2>/dev/null || echo "")
|
|
9918
|
+
changed_files=$(GIT_INDEX_FILE="$_rev_idx" git -C "${TARGET_DIR:-.}" diff --cached --name-only "$_review_base" "${_review_pathspec[@]}" 2>/dev/null || echo "")
|
|
9919
|
+
rm -f "$_rev_idx" 2>/dev/null || true
|
|
9920
|
+
fi
|
|
9921
|
+
|
|
9618
9922
|
if [ -z "$diff_content" ]; then
|
|
9619
|
-
|
|
9923
|
+
# Honesty (Finding #596 class): a silent PASS on an empty diff is a trust
|
|
9924
|
+
# gap ONLY when the run actually produced changes we failed to diff.
|
|
9925
|
+
# Distinguish a genuine no-op iteration (nothing changed -> legitimate
|
|
9926
|
+
# PASS) from "changes exist but we could not compute a diff" (must not
|
|
9927
|
+
# pass silently). Use the same .loki/.git-excluding porcelain the
|
|
9928
|
+
# evidence gate uses to detect real changes independent of the diff base.
|
|
9929
|
+
local _dirty
|
|
9930
|
+
_dirty=$(git -C "${TARGET_DIR:-.}" status --porcelain "${_review_pathspec[@]}" 2>/dev/null | head -1 || echo "")
|
|
9931
|
+
if [ -n "$_dirty" ] || [ -n "$changed_files" ]; then
|
|
9932
|
+
# Return non-zero so the gate dispatcher records a failure (it calls
|
|
9933
|
+
# track_gate_failure on the else branch). Do NOT call track_gate_failure
|
|
9934
|
+
# here: it echoes its count to stdout (callers capture it) and the
|
|
9935
|
+
# dispatcher would double-count.
|
|
9936
|
+
log_warn "Code review: workspace has changes but the review diff is empty (could not compute a diff base); NOT passing the gate silently"
|
|
9937
|
+
return 1
|
|
9938
|
+
fi
|
|
9939
|
+
log_info "Code review: no changes this iteration, skipping (genuine no-op)"
|
|
9620
9940
|
return 0
|
|
9621
9941
|
fi
|
|
9622
9942
|
|
|
9623
|
-
local changed_files
|
|
9624
|
-
changed_files=$(git -C "${TARGET_DIR:-.}" diff --name-only HEAD~1 "${_review_pathspec[@]}" 2>/dev/null || git -C "${TARGET_DIR:-.}" diff --name-only --cached "${_review_pathspec[@]}" 2>/dev/null || echo "")
|
|
9625
|
-
|
|
9626
9943
|
log_header "CODE REVIEW: $review_id"
|
|
9627
9944
|
|
|
9628
9945
|
# Phase 3 (v7.0.0): managed code-review council. When the flag is on,
|
|
@@ -15968,27 +16285,33 @@ if __name__ == "__main__":
|
|
|
15968
16285
|
# Static analysis gate
|
|
15969
16286
|
if [ "${PHASE_STATIC_ANALYSIS:-true}" = "true" ]; then
|
|
15970
16287
|
log_info "Quality gate: static analysis..."
|
|
16288
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
15971
16289
|
if enforce_static_analysis; then
|
|
15972
16290
|
clear_gate_failure "static_analysis"
|
|
15973
16291
|
else
|
|
16292
|
+
_stg_ok=fail
|
|
15974
16293
|
local sa_count
|
|
15975
16294
|
sa_count=$(track_gate_failure "static_analysis")
|
|
15976
16295
|
gate_failures="${gate_failures}static_analysis,"
|
|
15977
16296
|
log_warn "Static analysis FAILED ($sa_count consecutive) - findings injected into next iteration"
|
|
15978
16297
|
fi
|
|
16298
|
+
emit_stage_complete "static_analysis" "$_stg_ok" "$_stg_t0"
|
|
15979
16299
|
fi
|
|
15980
16300
|
# Secure-by-default scan (v7.87.0). Advisory by default (never
|
|
15981
16301
|
# blocks); records .loki/quality/security-findings.json each
|
|
15982
16302
|
# iteration. Blocks only on un-waived HIGH when LOKI_SECURE_GATE=block.
|
|
15983
16303
|
log_info "Quality gate: security scan (advisory)..."
|
|
16304
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
15984
16305
|
if run_secure_scan; then
|
|
15985
16306
|
clear_gate_failure "security_scan"
|
|
15986
16307
|
else
|
|
16308
|
+
_stg_ok=fail
|
|
15987
16309
|
local sec_count
|
|
15988
16310
|
sec_count=$(track_gate_failure "security_scan")
|
|
15989
16311
|
gate_failures="${gate_failures}security_scan,"
|
|
15990
16312
|
log_warn "Security gate BLOCKED ($sec_count consecutive) - un-waived HIGH findings (LOKI_SECURE_GATE=block)"
|
|
15991
16313
|
fi
|
|
16314
|
+
emit_stage_complete "security_scan" "$_stg_ok" "$_stg_t0"
|
|
15992
16315
|
# BUG-ST-002: Check pause signal between quality gates
|
|
15993
16316
|
if [ -f "${TARGET_DIR:-.}/.loki/PAUSE" ] || [ -f "${TARGET_DIR:-.}/.loki/STOP" ]; then
|
|
15994
16317
|
log_warn "Pause/stop signal detected between quality gates - deferring remaining gates"
|
|
@@ -16002,11 +16325,13 @@ if __name__ == "__main__":
|
|
|
16002
16325
|
# Test coverage gate
|
|
16003
16326
|
if [ "${PHASE_UNIT_TESTS:-true}" = "true" ]; then
|
|
16004
16327
|
log_info "Quality gate: test suite (pass/fail)..."
|
|
16328
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16005
16329
|
# F49: isolate HOME so the project's suite cannot pollute the
|
|
16006
16330
|
# user's real home when it execs the generated app.
|
|
16007
16331
|
if _loki_with_app_sandbox enforce_test_coverage; then
|
|
16008
16332
|
clear_gate_failure "test_coverage"
|
|
16009
16333
|
else
|
|
16334
|
+
_stg_ok=fail
|
|
16010
16335
|
local tc_count
|
|
16011
16336
|
tc_count=$(track_gate_failure "test_coverage")
|
|
16012
16337
|
gate_failures="${gate_failures}test_coverage,"
|
|
@@ -16020,6 +16345,7 @@ if __name__ == "__main__":
|
|
|
16020
16345
|
log_warn "Test suite gate FAILED ($tc_count consecutive) - must pass next iteration"
|
|
16021
16346
|
fi
|
|
16022
16347
|
fi
|
|
16348
|
+
emit_stage_complete "test_suite" "$_stg_ok" "$_stg_t0"
|
|
16023
16349
|
fi
|
|
16024
16350
|
# BUG-ST-002: Check pause signal between quality gates (after test coverage)
|
|
16025
16351
|
if [ -f "${TARGET_DIR:-.}/.loki/PAUSE" ] || [ -f "${TARGET_DIR:-.}/.loki/STOP" ]; then
|
|
@@ -16032,26 +16358,32 @@ if __name__ == "__main__":
|
|
|
16032
16358
|
# Mock integrity gate (P0-3): block on CRITICAL/HIGH mock problems.
|
|
16033
16359
|
if [ "${LOKI_GATE_MOCK:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16034
16360
|
log_info "Quality gate: mock integrity..."
|
|
16361
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16035
16362
|
if enforce_mock_integrity; then
|
|
16036
16363
|
clear_gate_failure "mock_integrity"
|
|
16037
16364
|
else
|
|
16365
|
+
_stg_ok=fail
|
|
16038
16366
|
local mk_count
|
|
16039
16367
|
mk_count=$(track_gate_failure "mock_integrity")
|
|
16040
16368
|
gate_failures="${gate_failures}mock_integrity,"
|
|
16041
16369
|
log_warn "Mock integrity gate FAILED ($mk_count consecutive) - CRITICAL/HIGH mock problems"
|
|
16042
16370
|
fi
|
|
16371
|
+
emit_stage_complete "mock_integrity" "$_stg_ok" "$_stg_t0"
|
|
16043
16372
|
fi
|
|
16044
16373
|
# Test mutation integrity gate (P0-3): block on HIGH test-fitting.
|
|
16045
16374
|
if [ "${LOKI_GATE_MUTATION:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16046
16375
|
log_info "Quality gate: test mutation integrity..."
|
|
16376
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16047
16377
|
if enforce_mutation_integrity; then
|
|
16048
16378
|
clear_gate_failure "mutation_integrity"
|
|
16049
16379
|
else
|
|
16380
|
+
_stg_ok=fail
|
|
16050
16381
|
local mt_count
|
|
16051
16382
|
mt_count=$(track_gate_failure "mutation_integrity")
|
|
16052
16383
|
gate_failures="${gate_failures}mutation_integrity,"
|
|
16053
16384
|
log_warn "Mutation integrity gate FAILED ($mt_count consecutive) - HIGH test-fitting detected"
|
|
16054
16385
|
fi
|
|
16386
|
+
emit_stage_complete "mutation_integrity" "$_stg_ok" "$_stg_t0"
|
|
16055
16387
|
fi
|
|
16056
16388
|
# LSP diagnostics gate (P1-5 bash-route parity, v7.51.0; default-on
|
|
16057
16389
|
# advisory-surfacing as of v7.57.0). Closes the parity gap: the Bun
|
|
@@ -16087,6 +16419,7 @@ if __name__ == "__main__":
|
|
|
16087
16419
|
# runLSPDiagnosticsWriter: cwd=REPO_ROOT, --root=ctx.cwd).
|
|
16088
16420
|
if { [ "${LOKI_GATE_LSP_DIAGNOSTICS:-true}" = "true" ] || [ "${LOKI_GATE_LSP_DIAGNOSTICS:-true}" = "1" ]; } && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16089
16421
|
log_info "Quality gate: LSP diagnostics..."
|
|
16422
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16090
16423
|
# WRITER: route-neutral Python, same program as the Bun route.
|
|
16091
16424
|
if [ "${LOKI_GATE_LSP_WRITER:-1}" != "0" ]; then
|
|
16092
16425
|
( cd "$PROJECT_DIR" && LOKI_DIR="${TARGET_DIR:-.}/.loki" python3 -m mcp.lsp_proxy --write-diagnostics --root "${TARGET_DIR:-.}" ) >/dev/null 2>&1 || true
|
|
@@ -16120,6 +16453,7 @@ else:
|
|
|
16120
16453
|
fi
|
|
16121
16454
|
case "$_lsp_verdict" in
|
|
16122
16455
|
block*)
|
|
16456
|
+
_stg_ok=fail
|
|
16123
16457
|
local _lsp_e _lsp_w
|
|
16124
16458
|
_lsp_e=$(printf '%s' "$_lsp_verdict" | awk '{print $2}')
|
|
16125
16459
|
_lsp_w=$(printf '%s' "$_lsp_verdict" | awk '{print $3}')
|
|
@@ -16146,6 +16480,7 @@ else:
|
|
|
16146
16480
|
log_info "LSP diagnostics: no lsp-diagnostics.json artifact (lsp not available) -- gate did not run"
|
|
16147
16481
|
;;
|
|
16148
16482
|
esac
|
|
16483
|
+
emit_stage_complete "lsp_diagnostics" "$_stg_ok" "$_stg_t0"
|
|
16149
16484
|
fi
|
|
16150
16485
|
# Semantic test-authenticity gate -- mid-iteration ADVISORY arm
|
|
16151
16486
|
# (v7.57.0 default-on surfacing). Clones the mock arm (~15126)
|
|
@@ -16198,9 +16533,11 @@ else:
|
|
|
16198
16533
|
# Code review gate (upgraded from advisory, with escalation)
|
|
16199
16534
|
if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16200
16535
|
log_info "Quality gate: code review..."
|
|
16536
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16201
16537
|
if run_code_review; then
|
|
16202
16538
|
clear_gate_failure "code_review"
|
|
16203
16539
|
else
|
|
16540
|
+
_stg_ok=fail
|
|
16204
16541
|
local cr_count
|
|
16205
16542
|
cr_count=$(track_gate_failure "code_review")
|
|
16206
16543
|
# BUG-QG-007: Always append to gate_failures regardless of escalation tier
|
|
@@ -16225,7 +16562,7 @@ else:
|
|
|
16225
16562
|
esac
|
|
16226
16563
|
fi
|
|
16227
16564
|
if [ "$_phase1_overrode" = "true" ]; then
|
|
16228
|
-
|
|
16565
|
+
_stg_ok=pass # BLOCK lifted; continue without escalation
|
|
16229
16566
|
elif [ "$cr_count" -ge "$GATE_PAUSE_LIMIT" ]; then
|
|
16230
16567
|
log_error "Gate escalation: code_review failed $cr_count times (>= $GATE_PAUSE_LIMIT) - forcing PAUSE for human intervention"
|
|
16231
16568
|
echo "PAUSE" > "${TARGET_DIR:-.}/.loki/signals/GATE_ESCALATION"
|
|
@@ -16255,6 +16592,7 @@ else:
|
|
|
16255
16592
|
bun "${SCRIPT_DIR}/../loki-ts/dist/loki.js" internal phase1-hooks reflect "$ITERATION_COUNT" 2>/dev/null || true
|
|
16256
16593
|
fi
|
|
16257
16594
|
fi
|
|
16595
|
+
emit_stage_complete "code_review" "$_stg_ok" "$_stg_t0"
|
|
16258
16596
|
fi
|
|
16259
16597
|
# Auto-generate docs (default-on) BEFORE the staleness check and the
|
|
16260
16598
|
# gate, so neither nags the user to run 'loki docs generate' by hand.
|
|
@@ -16269,26 +16607,32 @@ else:
|
|
|
16269
16607
|
# Documentation quality gate - Gate 7 (Documentation Coverage)
|
|
16270
16608
|
if [ "${LOKI_GATE_DOC_COVERAGE:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16271
16609
|
log_info "Quality gate: documentation coverage..."
|
|
16610
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16272
16611
|
if run_doc_quality_gate; then
|
|
16273
16612
|
clear_gate_failure "doc_coverage"
|
|
16274
16613
|
else
|
|
16614
|
+
_stg_ok=fail
|
|
16275
16615
|
local dc_count
|
|
16276
16616
|
dc_count=$(track_gate_failure "doc_coverage")
|
|
16277
16617
|
gate_failures="${gate_failures}doc_coverage,"
|
|
16278
16618
|
log_warn "Documentation coverage gate: Score below threshold ($dc_count consecutive)"
|
|
16279
16619
|
fi
|
|
16620
|
+
emit_stage_complete "doc_coverage" "$_stg_ok" "$_stg_t0"
|
|
16280
16621
|
fi
|
|
16281
16622
|
# Magic Modules debate gate - Gate 12 (v6.77.0)
|
|
16282
16623
|
if [ "${LOKI_GATE_MAGIC_DEBATE:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16283
16624
|
log_info "Quality gate: magic modules debate..."
|
|
16625
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16284
16626
|
if run_magic_debate_gate; then
|
|
16285
16627
|
clear_gate_failure "magic_debate"
|
|
16286
16628
|
else
|
|
16629
|
+
_stg_ok=fail
|
|
16287
16630
|
local md_count
|
|
16288
16631
|
md_count=$(track_gate_failure "magic_debate")
|
|
16289
16632
|
gate_failures="${gate_failures}magic_debate,"
|
|
16290
16633
|
log_warn "Magic Modules debate gate: BLOCK severity detected ($md_count consecutive)"
|
|
16291
16634
|
fi
|
|
16635
|
+
emit_stage_complete "magic_debate" "$_stg_ok" "$_stg_t0"
|
|
16292
16636
|
fi
|
|
16293
16637
|
# Store gate failures for prompt injection
|
|
16294
16638
|
if [ -n "$gate_failures" ]; then
|
|
@@ -17782,6 +18126,13 @@ main() {
|
|
|
17782
18126
|
# resume/rollback sees the restored state. Zero behavior change for local.
|
|
17783
18127
|
_loki_object_store_hydrate_checkpoints || true
|
|
17784
18128
|
|
|
18129
|
+
# Plan #16 (A-1): establish git in an engine-owned, non-git build workspace
|
|
18130
|
+
# BEFORE branch setup + start-SHA capture (both need a resolvable HEAD), so
|
|
18131
|
+
# the per-iteration review/verify gate actually runs instead of skipping on
|
|
18132
|
+
# an empty diff. No-op for the engine source tree, a user's own repo, or a
|
|
18133
|
+
# non-engine-owned folder. See maybe_git_init_engine_workspace.
|
|
18134
|
+
maybe_git_init_engine_workspace
|
|
18135
|
+
|
|
17785
18136
|
# Setup agent branch protection (isolates agent changes to a feature branch)
|
|
17786
18137
|
setup_agent_branch
|
|
17787
18138
|
|
|
@@ -17852,7 +18203,10 @@ main() {
|
|
|
17852
18203
|
# Cleanup parallel streams
|
|
17853
18204
|
cleanup_parallel_streams
|
|
17854
18205
|
else
|
|
17855
|
-
# Standard mode: single session
|
|
18206
|
+
# Standard mode: single session. Advance phase from BOOTSTRAP to BUILDING
|
|
18207
|
+
# before the first iteration so the dashboard shows real progress, not
|
|
18208
|
+
# a stuck "Planning" state.
|
|
18209
|
+
_advance_current_phase "BUILDING"
|
|
17856
18210
|
run_autonomous "$PRD_PATH" || result=$?
|
|
17857
18211
|
fi
|
|
17858
18212
|
|
|
@@ -17939,6 +18293,13 @@ main() {
|
|
|
17939
18293
|
generate_proof_of_run "$result" || true
|
|
17940
18294
|
fi
|
|
17941
18295
|
|
|
18296
|
+
# Advance currentPhase to COMPLETED so the BFF reconciliation gate and the
|
|
18297
|
+
# engine's own is_completed() recognise this session as terminal. Write the
|
|
18298
|
+
# file-system COMPLETED marker (checked by is_completed() in the main loop).
|
|
18299
|
+
_advance_current_phase "COMPLETED"
|
|
18300
|
+
local loki_dir="${LOKI_DIR:-${TARGET_DIR:-.}}/.loki"
|
|
18301
|
+
echo "Session completed at $(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$loki_dir/COMPLETED" 2>/dev/null || true
|
|
18302
|
+
|
|
17942
18303
|
# Finish-and-own (v7.88.0): write a plain-English ownership handoff
|
|
17943
18304
|
# (HANDOFF.md) for a non-technical owner. Runs AFTER the proof so the
|
|
17944
18305
|
# "is it working?" verdict reads the receipt's honest headline. Default-on,
|