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 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.0
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.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
411
+ **v7.91.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.91.0
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 script on exit (only when running from temp copy)
231
- if [[ "${LOKI_RUNNING_FROM_TEMP:-}" == "1" ]]; then
232
- trap 'rm -f "${BASH_SOURCE[0]}" 2>/dev/null' EXIT
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
- local _review_pathspec=(-- . ':(exclude).loki/' ':(exclude).git/' ':(exclude)**/.loki/**')
9677
- local diff_content
9678
- diff_content=$(git -C "${TARGET_DIR:-.}" diff HEAD~1 "${_review_pathspec[@]}" 2>/dev/null || git -C "${TARGET_DIR:-.}" diff --cached "${_review_pathspec[@]}" 2>/dev/null || echo "")
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
- log_info "Code review: No diff to review, skipping"
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
- : # BLOCK lifted; continue without escalation
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
 
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.91.0"
10
+ __version__ = "7.91.1"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try: