loki-mode 7.91.0 → 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/run.sh +301 -11
- package/dashboard/__init__.py +1 -1
- 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.91.
|
|
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.91.
|
|
411
|
+
**v7.91.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.91.
|
|
1
|
+
7.91.1
|
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
|
#===============================================================================
|
|
@@ -1384,6 +1394,34 @@ emit_event_json() {
|
|
|
1384
1394
|
log_debug "Event: $event_type - $json_data"
|
|
1385
1395
|
}
|
|
1386
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
|
+
|
|
1387
1425
|
# Trust-layer metrics event writer (benchmark program section 3). Appends one
|
|
1388
1426
|
# durable record per trust event to .loki/metrics/trust-events.jsonl via the
|
|
1389
1427
|
# Python writer (single source of truth for the JSONL schema). This is ADDITIVE
|
|
@@ -6189,6 +6227,152 @@ audit_log() {
|
|
|
6189
6227
|
echo "$log_entry" >> "$audit_file"
|
|
6190
6228
|
}
|
|
6191
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
|
+
|
|
6192
6376
|
#===============================================================================
|
|
6193
6377
|
# Branch Protection for Agent Changes
|
|
6194
6378
|
#===============================================================================
|
|
@@ -9673,17 +9857,89 @@ run_code_review() {
|
|
|
9673
9857
|
# common case) because git ignores the exclude pathspec for paths it does not
|
|
9674
9858
|
# track. ':(exclude).git/' is harmless (git never diffs .git/) and is kept
|
|
9675
9859
|
# only for parity with the evidence-gate exclusion list.
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
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
|
+
|
|
9679
9922
|
if [ -z "$diff_content" ]; then
|
|
9680
|
-
|
|
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)"
|
|
9681
9940
|
return 0
|
|
9682
9941
|
fi
|
|
9683
9942
|
|
|
9684
|
-
local changed_files
|
|
9685
|
-
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 "")
|
|
9686
|
-
|
|
9687
9943
|
log_header "CODE REVIEW: $review_id"
|
|
9688
9944
|
|
|
9689
9945
|
# Phase 3 (v7.0.0): managed code-review council. When the flag is on,
|
|
@@ -16029,27 +16285,33 @@ if __name__ == "__main__":
|
|
|
16029
16285
|
# Static analysis gate
|
|
16030
16286
|
if [ "${PHASE_STATIC_ANALYSIS:-true}" = "true" ]; then
|
|
16031
16287
|
log_info "Quality gate: static analysis..."
|
|
16288
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16032
16289
|
if enforce_static_analysis; then
|
|
16033
16290
|
clear_gate_failure "static_analysis"
|
|
16034
16291
|
else
|
|
16292
|
+
_stg_ok=fail
|
|
16035
16293
|
local sa_count
|
|
16036
16294
|
sa_count=$(track_gate_failure "static_analysis")
|
|
16037
16295
|
gate_failures="${gate_failures}static_analysis,"
|
|
16038
16296
|
log_warn "Static analysis FAILED ($sa_count consecutive) - findings injected into next iteration"
|
|
16039
16297
|
fi
|
|
16298
|
+
emit_stage_complete "static_analysis" "$_stg_ok" "$_stg_t0"
|
|
16040
16299
|
fi
|
|
16041
16300
|
# Secure-by-default scan (v7.87.0). Advisory by default (never
|
|
16042
16301
|
# blocks); records .loki/quality/security-findings.json each
|
|
16043
16302
|
# iteration. Blocks only on un-waived HIGH when LOKI_SECURE_GATE=block.
|
|
16044
16303
|
log_info "Quality gate: security scan (advisory)..."
|
|
16304
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16045
16305
|
if run_secure_scan; then
|
|
16046
16306
|
clear_gate_failure "security_scan"
|
|
16047
16307
|
else
|
|
16308
|
+
_stg_ok=fail
|
|
16048
16309
|
local sec_count
|
|
16049
16310
|
sec_count=$(track_gate_failure "security_scan")
|
|
16050
16311
|
gate_failures="${gate_failures}security_scan,"
|
|
16051
16312
|
log_warn "Security gate BLOCKED ($sec_count consecutive) - un-waived HIGH findings (LOKI_SECURE_GATE=block)"
|
|
16052
16313
|
fi
|
|
16314
|
+
emit_stage_complete "security_scan" "$_stg_ok" "$_stg_t0"
|
|
16053
16315
|
# BUG-ST-002: Check pause signal between quality gates
|
|
16054
16316
|
if [ -f "${TARGET_DIR:-.}/.loki/PAUSE" ] || [ -f "${TARGET_DIR:-.}/.loki/STOP" ]; then
|
|
16055
16317
|
log_warn "Pause/stop signal detected between quality gates - deferring remaining gates"
|
|
@@ -16063,11 +16325,13 @@ if __name__ == "__main__":
|
|
|
16063
16325
|
# Test coverage gate
|
|
16064
16326
|
if [ "${PHASE_UNIT_TESTS:-true}" = "true" ]; then
|
|
16065
16327
|
log_info "Quality gate: test suite (pass/fail)..."
|
|
16328
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16066
16329
|
# F49: isolate HOME so the project's suite cannot pollute the
|
|
16067
16330
|
# user's real home when it execs the generated app.
|
|
16068
16331
|
if _loki_with_app_sandbox enforce_test_coverage; then
|
|
16069
16332
|
clear_gate_failure "test_coverage"
|
|
16070
16333
|
else
|
|
16334
|
+
_stg_ok=fail
|
|
16071
16335
|
local tc_count
|
|
16072
16336
|
tc_count=$(track_gate_failure "test_coverage")
|
|
16073
16337
|
gate_failures="${gate_failures}test_coverage,"
|
|
@@ -16081,6 +16345,7 @@ if __name__ == "__main__":
|
|
|
16081
16345
|
log_warn "Test suite gate FAILED ($tc_count consecutive) - must pass next iteration"
|
|
16082
16346
|
fi
|
|
16083
16347
|
fi
|
|
16348
|
+
emit_stage_complete "test_suite" "$_stg_ok" "$_stg_t0"
|
|
16084
16349
|
fi
|
|
16085
16350
|
# BUG-ST-002: Check pause signal between quality gates (after test coverage)
|
|
16086
16351
|
if [ -f "${TARGET_DIR:-.}/.loki/PAUSE" ] || [ -f "${TARGET_DIR:-.}/.loki/STOP" ]; then
|
|
@@ -16093,26 +16358,32 @@ if __name__ == "__main__":
|
|
|
16093
16358
|
# Mock integrity gate (P0-3): block on CRITICAL/HIGH mock problems.
|
|
16094
16359
|
if [ "${LOKI_GATE_MOCK:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16095
16360
|
log_info "Quality gate: mock integrity..."
|
|
16361
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16096
16362
|
if enforce_mock_integrity; then
|
|
16097
16363
|
clear_gate_failure "mock_integrity"
|
|
16098
16364
|
else
|
|
16365
|
+
_stg_ok=fail
|
|
16099
16366
|
local mk_count
|
|
16100
16367
|
mk_count=$(track_gate_failure "mock_integrity")
|
|
16101
16368
|
gate_failures="${gate_failures}mock_integrity,"
|
|
16102
16369
|
log_warn "Mock integrity gate FAILED ($mk_count consecutive) - CRITICAL/HIGH mock problems"
|
|
16103
16370
|
fi
|
|
16371
|
+
emit_stage_complete "mock_integrity" "$_stg_ok" "$_stg_t0"
|
|
16104
16372
|
fi
|
|
16105
16373
|
# Test mutation integrity gate (P0-3): block on HIGH test-fitting.
|
|
16106
16374
|
if [ "${LOKI_GATE_MUTATION:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16107
16375
|
log_info "Quality gate: test mutation integrity..."
|
|
16376
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16108
16377
|
if enforce_mutation_integrity; then
|
|
16109
16378
|
clear_gate_failure "mutation_integrity"
|
|
16110
16379
|
else
|
|
16380
|
+
_stg_ok=fail
|
|
16111
16381
|
local mt_count
|
|
16112
16382
|
mt_count=$(track_gate_failure "mutation_integrity")
|
|
16113
16383
|
gate_failures="${gate_failures}mutation_integrity,"
|
|
16114
16384
|
log_warn "Mutation integrity gate FAILED ($mt_count consecutive) - HIGH test-fitting detected"
|
|
16115
16385
|
fi
|
|
16386
|
+
emit_stage_complete "mutation_integrity" "$_stg_ok" "$_stg_t0"
|
|
16116
16387
|
fi
|
|
16117
16388
|
# LSP diagnostics gate (P1-5 bash-route parity, v7.51.0; default-on
|
|
16118
16389
|
# advisory-surfacing as of v7.57.0). Closes the parity gap: the Bun
|
|
@@ -16148,6 +16419,7 @@ if __name__ == "__main__":
|
|
|
16148
16419
|
# runLSPDiagnosticsWriter: cwd=REPO_ROOT, --root=ctx.cwd).
|
|
16149
16420
|
if { [ "${LOKI_GATE_LSP_DIAGNOSTICS:-true}" = "true" ] || [ "${LOKI_GATE_LSP_DIAGNOSTICS:-true}" = "1" ]; } && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16150
16421
|
log_info "Quality gate: LSP diagnostics..."
|
|
16422
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16151
16423
|
# WRITER: route-neutral Python, same program as the Bun route.
|
|
16152
16424
|
if [ "${LOKI_GATE_LSP_WRITER:-1}" != "0" ]; then
|
|
16153
16425
|
( cd "$PROJECT_DIR" && LOKI_DIR="${TARGET_DIR:-.}/.loki" python3 -m mcp.lsp_proxy --write-diagnostics --root "${TARGET_DIR:-.}" ) >/dev/null 2>&1 || true
|
|
@@ -16181,6 +16453,7 @@ else:
|
|
|
16181
16453
|
fi
|
|
16182
16454
|
case "$_lsp_verdict" in
|
|
16183
16455
|
block*)
|
|
16456
|
+
_stg_ok=fail
|
|
16184
16457
|
local _lsp_e _lsp_w
|
|
16185
16458
|
_lsp_e=$(printf '%s' "$_lsp_verdict" | awk '{print $2}')
|
|
16186
16459
|
_lsp_w=$(printf '%s' "$_lsp_verdict" | awk '{print $3}')
|
|
@@ -16207,6 +16480,7 @@ else:
|
|
|
16207
16480
|
log_info "LSP diagnostics: no lsp-diagnostics.json artifact (lsp not available) -- gate did not run"
|
|
16208
16481
|
;;
|
|
16209
16482
|
esac
|
|
16483
|
+
emit_stage_complete "lsp_diagnostics" "$_stg_ok" "$_stg_t0"
|
|
16210
16484
|
fi
|
|
16211
16485
|
# Semantic test-authenticity gate -- mid-iteration ADVISORY arm
|
|
16212
16486
|
# (v7.57.0 default-on surfacing). Clones the mock arm (~15126)
|
|
@@ -16259,9 +16533,11 @@ else:
|
|
|
16259
16533
|
# Code review gate (upgraded from advisory, with escalation)
|
|
16260
16534
|
if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16261
16535
|
log_info "Quality gate: code review..."
|
|
16536
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16262
16537
|
if run_code_review; then
|
|
16263
16538
|
clear_gate_failure "code_review"
|
|
16264
16539
|
else
|
|
16540
|
+
_stg_ok=fail
|
|
16265
16541
|
local cr_count
|
|
16266
16542
|
cr_count=$(track_gate_failure "code_review")
|
|
16267
16543
|
# BUG-QG-007: Always append to gate_failures regardless of escalation tier
|
|
@@ -16286,7 +16562,7 @@ else:
|
|
|
16286
16562
|
esac
|
|
16287
16563
|
fi
|
|
16288
16564
|
if [ "$_phase1_overrode" = "true" ]; then
|
|
16289
|
-
|
|
16565
|
+
_stg_ok=pass # BLOCK lifted; continue without escalation
|
|
16290
16566
|
elif [ "$cr_count" -ge "$GATE_PAUSE_LIMIT" ]; then
|
|
16291
16567
|
log_error "Gate escalation: code_review failed $cr_count times (>= $GATE_PAUSE_LIMIT) - forcing PAUSE for human intervention"
|
|
16292
16568
|
echo "PAUSE" > "${TARGET_DIR:-.}/.loki/signals/GATE_ESCALATION"
|
|
@@ -16316,6 +16592,7 @@ else:
|
|
|
16316
16592
|
bun "${SCRIPT_DIR}/../loki-ts/dist/loki.js" internal phase1-hooks reflect "$ITERATION_COUNT" 2>/dev/null || true
|
|
16317
16593
|
fi
|
|
16318
16594
|
fi
|
|
16595
|
+
emit_stage_complete "code_review" "$_stg_ok" "$_stg_t0"
|
|
16319
16596
|
fi
|
|
16320
16597
|
# Auto-generate docs (default-on) BEFORE the staleness check and the
|
|
16321
16598
|
# gate, so neither nags the user to run 'loki docs generate' by hand.
|
|
@@ -16330,26 +16607,32 @@ else:
|
|
|
16330
16607
|
# Documentation quality gate - Gate 7 (Documentation Coverage)
|
|
16331
16608
|
if [ "${LOKI_GATE_DOC_COVERAGE:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16332
16609
|
log_info "Quality gate: documentation coverage..."
|
|
16610
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16333
16611
|
if run_doc_quality_gate; then
|
|
16334
16612
|
clear_gate_failure "doc_coverage"
|
|
16335
16613
|
else
|
|
16614
|
+
_stg_ok=fail
|
|
16336
16615
|
local dc_count
|
|
16337
16616
|
dc_count=$(track_gate_failure "doc_coverage")
|
|
16338
16617
|
gate_failures="${gate_failures}doc_coverage,"
|
|
16339
16618
|
log_warn "Documentation coverage gate: Score below threshold ($dc_count consecutive)"
|
|
16340
16619
|
fi
|
|
16620
|
+
emit_stage_complete "doc_coverage" "$_stg_ok" "$_stg_t0"
|
|
16341
16621
|
fi
|
|
16342
16622
|
# Magic Modules debate gate - Gate 12 (v6.77.0)
|
|
16343
16623
|
if [ "${LOKI_GATE_MAGIC_DEBATE:-true}" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
|
|
16344
16624
|
log_info "Quality gate: magic modules debate..."
|
|
16625
|
+
local _stg_t0=$(date +%s 2>/dev/null); local _stg_ok=pass
|
|
16345
16626
|
if run_magic_debate_gate; then
|
|
16346
16627
|
clear_gate_failure "magic_debate"
|
|
16347
16628
|
else
|
|
16629
|
+
_stg_ok=fail
|
|
16348
16630
|
local md_count
|
|
16349
16631
|
md_count=$(track_gate_failure "magic_debate")
|
|
16350
16632
|
gate_failures="${gate_failures}magic_debate,"
|
|
16351
16633
|
log_warn "Magic Modules debate gate: BLOCK severity detected ($md_count consecutive)"
|
|
16352
16634
|
fi
|
|
16635
|
+
emit_stage_complete "magic_debate" "$_stg_ok" "$_stg_t0"
|
|
16353
16636
|
fi
|
|
16354
16637
|
# Store gate failures for prompt injection
|
|
16355
16638
|
if [ -n "$gate_failures" ]; then
|
|
@@ -17843,6 +18126,13 @@ main() {
|
|
|
17843
18126
|
# resume/rollback sees the restored state. Zero behavior change for local.
|
|
17844
18127
|
_loki_object_store_hydrate_checkpoints || true
|
|
17845
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
|
+
|
|
17846
18136
|
# Setup agent branch protection (isolates agent changes to a feature branch)
|
|
17847
18137
|
setup_agent_branch
|
|
17848
18138
|
|