loki-mode 9.22.8 → 9.22.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/autonomy/run.sh CHANGED
@@ -1709,12 +1709,15 @@ fi
1709
1709
  if [ "$BASH_VERSION_MAJOR" -ge 4 ] 2>/dev/null; then
1710
1710
  declare -A WORKTREE_PIDS=()
1711
1711
  declare -A WORKTREE_PATHS=()
1712
+ declare -A WORKTREE_BASE_SHAS=()
1712
1713
  else
1713
1714
  # Fallback: parallel mode will check and warn
1714
1715
  # shellcheck disable=SC2178
1715
1716
  WORKTREE_PIDS=""
1716
1717
  # shellcheck disable=SC2178
1717
1718
  WORKTREE_PATHS=""
1719
+ # shellcheck disable=SC2178
1720
+ WORKTREE_BASE_SHAS=""
1718
1721
  fi
1719
1722
  # Track background install PIDs for cleanup (indexed array, works on all bash versions)
1720
1723
  WORKTREE_INSTALL_PIDS=()
@@ -5184,6 +5187,81 @@ ${_del_receipt}"
5184
5187
  # Parallel Workflow Functions (Git Worktrees)
5185
5188
  #===============================================================================
5186
5189
 
5190
+ # Production bridge for the Bun execution-manifest intelligence. The feature is
5191
+ # explicitly opt-in; with LOKI_EXEC_MANIFEST unset this performs no I/O and the
5192
+ # legacy parallel workflow remains byte-for-byte on its old path.
5193
+ _loki_exec_manifest() {
5194
+ [ "${LOKI_EXEC_MANIFEST:-0}" = "1" ] || return 0
5195
+ bun "${SCRIPT_DIR}/../loki-ts/dist/loki.js" internal exec-manifest "$@"
5196
+ }
5197
+
5198
+ init_exec_manifest() {
5199
+ [ "${LOKI_EXEC_MANIFEST:-0}" = "1" ] || return 0
5200
+ local base_sha plan_file
5201
+ # create_worktree's built-in streams branch from main (with HEAD only as its
5202
+ # final fallback), so the manifest must pin that exact production base.
5203
+ base_sha=$(git -C "$TARGET_DIR" rev-parse main 2>/dev/null) || \
5204
+ base_sha=$(git -C "$TARGET_DIR" rev-parse HEAD 2>/dev/null) || return 1
5205
+ mkdir -p "${TARGET_DIR}/.loki"
5206
+ plan_file=$(mktemp "${TARGET_DIR}/.loki/.exec-manifest-plan.XXXXXX") || return 1
5207
+ LOKI_PLAN_FILE="$plan_file" LOKI_BASE_SHA="$base_sha" \
5208
+ LOKI_PARALLEL_TESTING_VALUE="$PARALLEL_TESTING" \
5209
+ LOKI_PARALLEL_DOCS_VALUE="$PARALLEL_DOCS" \
5210
+ LOKI_PARALLEL_BLOG_VALUE="$PARALLEL_BLOG" python3 <<'PY'
5211
+ import json, os
5212
+ streams = []
5213
+ if os.environ["LOKI_PARALLEL_TESTING_VALUE"] == "true":
5214
+ streams.append({"name": "testing", "paths": ["tests", "loki-ts/tests"],
5215
+ "acceptance": "test stream exits successfully"})
5216
+ if os.environ["LOKI_PARALLEL_DOCS_VALUE"] == "true":
5217
+ streams.append({"name": "docs", "paths": ["docs", "README.md", "CHANGELOG.md"],
5218
+ "acceptance": "documentation stream exits successfully"})
5219
+ if os.environ["LOKI_PARALLEL_BLOG_VALUE"] == "true":
5220
+ streams.append({"name": "blog", "paths": ["blog"],
5221
+ "acceptance": "blog stream exits successfully"})
5222
+ with open(os.environ["LOKI_PLAN_FILE"], "w") as f:
5223
+ json.dump({"baseSha": os.environ["LOKI_BASE_SHA"],
5224
+ "integrationOwner": "parallel-orchestrator",
5225
+ "streams": streams,
5226
+ "env": {"LOKI_EXEC_MANIFEST": "1"}}, f)
5227
+ PY
5228
+ _loki_exec_manifest plan "$plan_file" "${TARGET_DIR}/.loki" >/dev/null
5229
+ local rc=$?
5230
+ rm -f "$plan_file"
5231
+ return "$rc"
5232
+ }
5233
+
5234
+ validate_exec_manifest_result() {
5235
+ local stream_name="$1" branch="$2"
5236
+ [ "${LOKI_EXEC_MANIFEST:-0}" = "1" ] || return 0
5237
+ local base_sha result_file
5238
+ base_sha="${WORKTREE_BASE_SHAS[$stream_name]:-}"
5239
+ [ -n "$base_sha" ] || base_sha=$(git -C "$TARGET_DIR" merge-base "$branch" HEAD 2>/dev/null) || return 1
5240
+ result_file=$(mktemp "${TARGET_DIR}/.loki/.exec-manifest-result.XXXXXX") || return 1
5241
+ LOKI_RESULT_FILE="$result_file" LOKI_RESULT_STREAM="$stream_name" \
5242
+ LOKI_RESULT_BASE="$base_sha" LOKI_RESULT_BRANCH="$branch" \
5243
+ LOKI_RESULT_REPO="$TARGET_DIR" python3 <<'PY'
5244
+ import json, os, subprocess
5245
+ paths = subprocess.check_output(
5246
+ ["git", "-C", os.environ["LOKI_RESULT_REPO"], "diff", "--name-only",
5247
+ f'{os.environ["LOKI_RESULT_BASE"]}..{os.environ["LOKI_RESULT_BRANCH"]}'],
5248
+ text=True).splitlines()
5249
+ with open(os.environ["LOKI_RESULT_FILE"], "w") as f:
5250
+ json.dump({"name": os.environ["LOKI_RESULT_STREAM"],
5251
+ "baseSha": os.environ["LOKI_RESULT_BASE"],
5252
+ "changedPaths": paths, "acceptanceMet": True}, f)
5253
+ PY
5254
+ local outcome rc=0
5255
+ outcome=$(_loki_exec_manifest validate "$result_file" "${TARGET_DIR}/.loki" 2>&1) || rc=$?
5256
+ rm -f "$result_file"
5257
+ if [ "$rc" -ne 0 ]; then
5258
+ log_error "Execution manifest rejected $stream_name: $outcome"
5259
+ return 1
5260
+ fi
5261
+ log_info "Execution manifest accepted: $stream_name"
5262
+ return 0
5263
+ }
5264
+
5187
5265
  # Check if parallel mode is supported (bash 4+ required for associative arrays)
5188
5266
  check_parallel_support() {
5189
5267
  if [ "$BASH_VERSION_MAJOR" -lt 4 ] 2>/dev/null; then
@@ -5211,6 +5289,9 @@ create_worktree() {
5211
5289
  if [ -d "$worktree_path" ]; then
5212
5290
  log_info "Worktree already exists: $stream_name"
5213
5291
  WORKTREE_PATHS[$stream_name]="$worktree_path"
5292
+ if [ "${LOKI_EXEC_MANIFEST:-0}" = "1" ]; then
5293
+ WORKTREE_BASE_SHAS[$stream_name]="$(python3 -c "import json; print(json.load(open('${TARGET_DIR}/.loki/manifest/exec-manifest.json'))['base_sha'])" 2>/dev/null)"
5294
+ fi
5214
5295
  return 0
5215
5296
  fi
5216
5297
 
@@ -5231,6 +5312,7 @@ create_worktree() {
5231
5312
 
5232
5313
  if [ $wt_exit -eq 0 ]; then
5233
5314
  WORKTREE_PATHS[$stream_name]="$worktree_path"
5315
+ WORKTREE_BASE_SHAS[$stream_name]="$(git -C "$worktree_path" rev-parse HEAD 2>/dev/null)"
5234
5316
 
5235
5317
  # Copy .loki state to worktree
5236
5318
  if [ -d "$TARGET_DIR/.loki" ]; then
@@ -5304,6 +5386,7 @@ remove_worktree() {
5304
5386
 
5305
5387
  unset "WORKTREE_PATHS[$stream_name]"
5306
5388
  unset "WORKTREE_PIDS[$stream_name]"
5389
+ unset "WORKTREE_BASE_SHAS[$stream_name]"
5307
5390
 
5308
5391
  log_info "Removed worktree: $stream_name"
5309
5392
  }
@@ -5609,6 +5692,12 @@ merge_worktree() {
5609
5692
 
5610
5693
  log_step "Merging worktree: $stream_name (branch: $branch)"
5611
5694
 
5695
+ # Validate the actual branch result at the integration seam, immediately
5696
+ # before any checkout or merge can mutate the integration tree.
5697
+ if ! validate_exec_manifest_result "$stream_name" "$branch"; then
5698
+ return 1
5699
+ fi
5700
+
5612
5701
  # BUG-PAR-009: Verify git checkout main before merge
5613
5702
  local current_branch
5614
5703
  current_branch=$(git -C "${TARGET_DIR:-.}" branch --show-current 2>/dev/null)
@@ -5797,8 +5886,25 @@ merge_feature() {
5797
5886
  local clean_feature="${feature#feature-}"
5798
5887
  local branch="feature/$clean_feature"
5799
5888
 
5889
+ # The session signal is authoritative for worktree branches (for example
5890
+ # parallel-testing). The historical feature/<name> convention remains the
5891
+ # fallback for legacy feature streams.
5892
+ local _mf_signal="$TARGET_DIR/.loki/signals/MERGE_REQUESTED_$feature"
5893
+ if [ -f "$_mf_signal" ]; then
5894
+ local _mf_branch=""
5895
+ _mf_branch=$(LOKI_SIGNAL_FILE="$_mf_signal" python3 -c \
5896
+ "import json,os; print(json.load(open(os.environ['LOKI_SIGNAL_FILE'])).get('branch',''))" 2>/dev/null || true)
5897
+ [ -n "$_mf_branch" ] && branch="$_mf_branch"
5898
+ fi
5899
+
5800
5900
  log_step "Merging feature: $clean_feature"
5801
5901
 
5902
+ # This is the autonomous orchestrator's real integration seam. Reject a
5903
+ # stale, out-of-scope, unknown, or unsuccessful stream before checkout/merge.
5904
+ if ! validate_exec_manifest_result "$feature" "$branch"; then
5905
+ return 1
5906
+ fi
5907
+
5802
5908
  # BUG-PAR-011: Ensure we're on main using git -C (no subshell)
5803
5909
  git -C "$TARGET_DIR" checkout main 2>/dev/null
5804
5910
 
@@ -5847,6 +5953,11 @@ init_parallel_streams() {
5847
5953
 
5848
5954
  log_header "Initializing Parallel Workflows"
5849
5955
 
5956
+ if ! init_exec_manifest; then
5957
+ log_error "Failed to initialize execution manifest"
5958
+ return 1
5959
+ fi
5960
+
5850
5961
  local active_streams=0
5851
5962
 
5852
5963
  # Create testing worktree (always tracks main)
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.22.8"
10
+ __version__ = "9.22.10"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -8361,6 +8361,61 @@ async def get_trust_trajectory():
8361
8361
  return traj
8362
8362
 
8363
8363
 
8364
+ # Gate policy is derived in one deterministic module so the dashboard never
8365
+ # invents whether a gate blocks or whether an absent measurement means zero.
8366
+ _GATE_POLICY_MODULE = None
8367
+
8368
+
8369
+ def _load_gate_policy_module():
8370
+ global _GATE_POLICY_MODULE
8371
+ if _GATE_POLICY_MODULE is not None:
8372
+ return _GATE_POLICY_MODULE
8373
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8374
+ module_path = os.path.join(repo_root, "autonomy", "lib", "gate_policy.py")
8375
+ if not os.path.isfile(module_path):
8376
+ return None
8377
+ try:
8378
+ import importlib.util as importlib_util
8379
+
8380
+ spec = importlib_util.spec_from_file_location("gate_policy", module_path)
8381
+ if spec is None or spec.loader is None:
8382
+ return None
8383
+ module = importlib_util.module_from_spec(spec)
8384
+ spec.loader.exec_module(module)
8385
+ _GATE_POLICY_MODULE = module
8386
+ return module
8387
+ except Exception:
8388
+ return None
8389
+
8390
+
8391
+ @app.get("/api/gate-policy", dependencies=[Depends(auth.require_scope("read"))])
8392
+ async def get_gate_policy():
8393
+ """Report blocking/advisory policy; never promote or mutate a gate."""
8394
+ module = _load_gate_policy_module()
8395
+ if module is None:
8396
+ return {
8397
+ "schema_version": 1,
8398
+ "available": False,
8399
+ "status": "unavailable",
8400
+ "ledger": "absent",
8401
+ "gates": [],
8402
+ "error": "gate_policy module not found",
8403
+ }
8404
+ try:
8405
+ result = module.assess(str(_get_loki_dir()))
8406
+ except Exception as error:
8407
+ return {
8408
+ "schema_version": 1,
8409
+ "available": False,
8410
+ "status": "unavailable",
8411
+ "ledger": "absent",
8412
+ "gates": [],
8413
+ "error": f"gate policy assessment failed: {error}",
8414
+ }
8415
+ result["available"] = True
8416
+ return result
8417
+
8418
+
8364
8419
  # =============================================================================
8365
8420
  # Pricing API
8366
8421
  # =============================================================================