loki-mode 6.9.0 → 6.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -252,7 +252,7 @@ class PrdAnalyzer:
252
252
  count = 0
253
253
  in_feature_section = False
254
254
  for line in self.lines:
255
- if re.search(r"(?i)#+\s.*(?:feature|requirement|scope|functional)", line):
255
+ if re.search(r"(?i)#+\s.*(?:feature|requirement|scope|functional|module|component|service|endpoint|api|milestone|deliverable|workstream|epic|story|task|phase|capability|objective)", line):
256
256
  in_feature_section = True
257
257
  continue
258
258
  if in_feature_section and re.match(r"^\s*#+\s", line):
@@ -262,12 +262,25 @@ class PrdAnalyzer:
262
262
  if re.match(r"^\s*[-*]\s+\S", line) or re.match(r"^\s*\d+\.\s+\S", line):
263
263
  count += 1
264
264
 
265
+ # Fallback: count ## headings as feature indicators when bullet items are few
266
+ if count == 0:
267
+ for line in self.lines:
268
+ if re.match(r"^##\s+\S", line):
269
+ count += 1
270
+
265
271
  self.feature_count = count
266
272
  for threshold, label in SCOPE_THRESHOLDS:
267
273
  if count <= threshold:
268
274
  self.scope = label
269
275
  break
270
276
 
277
+ # Word-count fallback: large PRDs should never be classified as small
278
+ word_count = len(self.content.split())
279
+ if word_count > 2000 and self.scope in ("small", "medium"):
280
+ self.scope = "large"
281
+ elif word_count > 500 and self.scope == "small":
282
+ self.scope = "medium"
283
+
271
284
  def generate_observations(self):
272
285
  """Generate the observations markdown content."""
273
286
  now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
package/autonomy/run.sh CHANGED
@@ -622,6 +622,11 @@ MAX_WORKTREES=${LOKI_MAX_WORKTREES:-5}
622
622
  MAX_PARALLEL_SESSIONS=${LOKI_MAX_PARALLEL_SESSIONS:-3}
623
623
  PARALLEL_TESTING=${LOKI_PARALLEL_TESTING:-true}
624
624
  PARALLEL_DOCS=${LOKI_PARALLEL_DOCS:-true}
625
+
626
+ # Gate Escalation Ladder (v6.10.0)
627
+ GATE_CLEAR_LIMIT=${LOKI_GATE_CLEAR_LIMIT:-3}
628
+ GATE_ESCALATE_LIMIT=${LOKI_GATE_ESCALATE_LIMIT:-5}
629
+ GATE_PAUSE_LIMIT=${LOKI_GATE_PAUSE_LIMIT:-10}
625
630
  TARGET_DIR="${LOKI_TARGET_DIR:-$(pwd)}"
626
631
  PARALLEL_BLOG=${LOKI_PARALLEL_BLOG:-false}
627
632
  AUTO_MERGE=${LOKI_AUTO_MERGE:-true}
@@ -5250,6 +5255,69 @@ SAFEOF
5250
5255
  fi
5251
5256
  }
5252
5257
 
5258
+ #===============================================================================
5259
+ # Gate Failure Tracking (v6.10.0)
5260
+ #===============================================================================
5261
+
5262
+ track_gate_failure() {
5263
+ local gate_name="$1"
5264
+ local gate_file="${TARGET_DIR:-.}/.loki/quality/gate-failure-count.json"
5265
+ mkdir -p "$(dirname "$gate_file")"
5266
+
5267
+ _GATE_FILE="$gate_file" _GATE_NAME="$gate_name" python3 -c "
5268
+ import json, os
5269
+ gate_file = os.environ['_GATE_FILE']
5270
+ gate_name = os.environ['_GATE_NAME']
5271
+ try:
5272
+ with open(gate_file) as f:
5273
+ counts = json.load(f)
5274
+ except (json.JSONDecodeError, FileNotFoundError, OSError):
5275
+ counts = {}
5276
+ counts[gate_name] = counts.get(gate_name, 0) + 1
5277
+ with open(gate_file, 'w') as f:
5278
+ json.dump(counts, f, indent=2)
5279
+ print(counts[gate_name])
5280
+ " 2>/dev/null || echo "1"
5281
+ }
5282
+
5283
+ clear_gate_failure() {
5284
+ local gate_name="$1"
5285
+ local gate_file="${TARGET_DIR:-.}/.loki/quality/gate-failure-count.json"
5286
+ [ -f "$gate_file" ] || return 0
5287
+
5288
+ _GATE_FILE="$gate_file" _GATE_NAME="$gate_name" python3 -c "
5289
+ import json, os
5290
+ gate_file = os.environ['_GATE_FILE']
5291
+ gate_name = os.environ['_GATE_NAME']
5292
+ try:
5293
+ with open(gate_file) as f:
5294
+ counts = json.load(f)
5295
+ except (json.JSONDecodeError, FileNotFoundError, OSError):
5296
+ counts = {}
5297
+ counts[gate_name] = 0
5298
+ with open(gate_file, 'w') as f:
5299
+ json.dump(counts, f, indent=2)
5300
+ " 2>/dev/null || true
5301
+ }
5302
+
5303
+ get_gate_failure_count() {
5304
+ local gate_name="$1"
5305
+ local gate_file="${TARGET_DIR:-.}/.loki/quality/gate-failure-count.json"
5306
+ [ -f "$gate_file" ] || { echo "0"; return; }
5307
+
5308
+ _GATE_FILE="$gate_file" _GATE_NAME="$gate_name" python3 -c "
5309
+ import json, os
5310
+ gate_file = os.environ['_GATE_FILE']
5311
+ gate_name = os.environ['_GATE_NAME']
5312
+ try:
5313
+ with open(gate_file) as f:
5314
+ counts = json.load(f)
5315
+ print(counts.get(gate_name, 0))
5316
+ except (json.JSONDecodeError, FileNotFoundError, OSError):
5317
+ print(0)
5318
+ " 2>/dev/null || echo "0"
5319
+ }
5320
+
5253
5321
  # ============================================================================
5254
5322
  # Hard Quality Gate: Test Coverage (v6.7.0)
5255
5323
  # Detects test runner and runs tests with coverage reporting
@@ -5287,6 +5355,58 @@ enforce_test_coverage() {
5287
5355
  fi
5288
5356
  fi
5289
5357
 
5358
+ # Monorepo: scan workspace packages for test runners (v6.10.0)
5359
+ if [ "$test_runner" = "none" ] && [ -f "${TARGET_DIR:-.}/package.json" ]; then
5360
+ local is_monorepo=false
5361
+ # Detect monorepo indicators
5362
+ if [ -f "${TARGET_DIR:-.}/pnpm-workspace.yaml" ] || \
5363
+ [ -f "${TARGET_DIR:-.}/turbo.json" ] || \
5364
+ [ -f "${TARGET_DIR:-.}/lerna.json" ] || \
5365
+ grep -q '"workspaces"' "${TARGET_DIR:-.}/package.json" 2>/dev/null; then
5366
+ is_monorepo=true
5367
+ fi
5368
+
5369
+ if [ "$is_monorepo" = "true" ]; then
5370
+ # Allow env override
5371
+ if [ -n "${LOKI_MONOREPO_TEST_CMD:-}" ]; then
5372
+ test_runner="monorepo-custom"
5373
+ local output
5374
+ output=$(cd "${TARGET_DIR:-.}" && eval "$LOKI_MONOREPO_TEST_CMD" 2>&1) || test_passed=false
5375
+ details="monorepo-custom: $(echo "$output" | tail -3 | tr '\n' ' ')"
5376
+ else
5377
+ # Scan workspace packages for test runners
5378
+ local workspace_runner=""
5379
+ for pkg_json in "${TARGET_DIR:-.}"/packages/*/package.json \
5380
+ "${TARGET_DIR:-.}"/apps/*/package.json \
5381
+ "${TARGET_DIR:-.}"/services/*/package.json; do
5382
+ [ -f "$pkg_json" ] || continue
5383
+ if grep -q '"vitest"' "$pkg_json" 2>/dev/null; then
5384
+ workspace_runner="vitest"
5385
+ break
5386
+ elif grep -q '"jest"' "$pkg_json" 2>/dev/null; then
5387
+ workspace_runner="jest"
5388
+ break
5389
+ fi
5390
+ done
5391
+
5392
+ if [ -n "$workspace_runner" ]; then
5393
+ test_runner="monorepo-$workspace_runner"
5394
+ local output
5395
+ if [ -f "${TARGET_DIR:-.}/turbo.json" ] && command -v turbo &>/dev/null; then
5396
+ output=$(cd "${TARGET_DIR:-.}" && npx turbo test 2>&1) || test_passed=false
5397
+ details="turbo test ($workspace_runner): $(echo "$output" | tail -3 | tr '\n' ' ')"
5398
+ elif [ -f "${TARGET_DIR:-.}/pnpm-workspace.yaml" ] && command -v pnpm &>/dev/null; then
5399
+ output=$(cd "${TARGET_DIR:-.}" && pnpm test --recursive 2>&1) || test_passed=false
5400
+ details="pnpm test --recursive ($workspace_runner): $(echo "$output" | tail -3 | tr '\n' ' ')"
5401
+ else
5402
+ output=$(cd "${TARGET_DIR:-.}" && npm test 2>&1) || test_passed=false
5403
+ details="npm test ($workspace_runner): $(echo "$output" | tail -3 | tr '\n' ' ')"
5404
+ fi
5405
+ fi
5406
+ fi
5407
+ fi
5408
+ fi
5409
+
5290
5410
  # Python
5291
5411
  if [ "$test_runner" = "none" ]; then
5292
5412
  if [ -f "${TARGET_DIR:-.}/setup.py" ] || [ -f "${TARGET_DIR:-.}/pyproject.toml" ] || \
@@ -7398,11 +7518,16 @@ build_prompt() {
7398
7518
  if [ -n "$COMPLETION_PROMISE" ]; then
7399
7519
  completion_instruction="COMPLETION_PROMISE: [$COMPLETION_PROMISE]. ONLY output 'COMPLETION PROMISE FULFILLED: $COMPLETION_PROMISE' when this EXACT condition is met."
7400
7520
  else
7401
- completion_instruction="NO COMPLETION PROMISE SET. Run forever finding improvements. Iteration $iteration of max $MAX_ITERATIONS."
7521
+ completion_instruction="NO COMPLETION PROMISE SET. Continue finding improvements. The Completion Council will evaluate your progress periodically. Iteration $iteration of max $MAX_ITERATIONS."
7402
7522
  fi
7403
7523
 
7404
7524
  # Core autonomous instructions - NO questions, NO waiting, NEVER say done
7405
- local autonomous_suffix="CRITICAL AUTONOMY RULES: 1) NEVER ask questions - just decide. 2) NEVER wait for confirmation - just act. 3) NEVER say 'done' or 'complete' - there's always more to improve. 4) NEVER stop voluntarily - if out of tasks, create new ones (add tests, optimize, refactor, add features). 5) Work continues PERPETUALLY. Even if PRD is implemented, find bugs, add tests, improve UX, optimize performance."
7525
+ local autonomous_suffix=""
7526
+ if [ "$AUTONOMY_MODE" = "perpetual" ] || [ "$PERPETUAL_MODE" = "true" ]; then
7527
+ autonomous_suffix="CRITICAL AUTONOMY RULES: 1) NEVER ask questions - just decide. 2) NEVER wait for confirmation - just act. 3) NEVER say 'done' or 'complete' - there's always more to improve. 4) NEVER stop voluntarily - if out of tasks, create new ones (add tests, optimize, refactor, add features). 5) Work continues PERPETUALLY. Even if PRD is implemented, find bugs, add tests, improve UX, optimize performance."
7528
+ else
7529
+ autonomous_suffix="CRITICAL AUTONOMY RULES: 1) NEVER ask questions - just decide. 2) NEVER wait for confirmation - just act. 3) When all PRD requirements are implemented and tests pass, output the completion promise text EXACTLY: '$COMPLETION_PROMISE'. 4) If out of tasks but PRD is not fully implemented, continue working on remaining requirements. 5) Focus on completing PRD scope, not endless improvements."
7530
+ fi
7406
7531
 
7407
7532
  # Skill files are always copied to .loki/skills/ for all providers
7408
7533
  local sdlc_instruction="SDLC_PHASES_ENABLED: [$phases]. Execute ALL enabled phases. Log results to .loki/logs/. See .loki/SKILL.md for phase details. Skill modules at .loki/skills/."
@@ -7585,6 +7710,29 @@ except: pass
7585
7710
  fi
7586
7711
  fi
7587
7712
 
7713
+ # OpenSpec delta context injection (if available)
7714
+ local openspec_context=""
7715
+ if [[ -f ".loki/openspec/delta-context.json" ]]; then
7716
+ openspec_context=$(_DELTA_FILE=".loki/openspec/delta-context.json" python3 -c "
7717
+ import json, os
7718
+ try:
7719
+ with open(os.environ['_DELTA_FILE']) as f:
7720
+ data = json.load(f)
7721
+ parts = ['OPENSPEC DELTA CONTEXT:']
7722
+ for domain, deltas in data.get('deltas', {}).items():
7723
+ for req in deltas.get('added', []):
7724
+ parts.append(f' ADDED [{domain}]: {req[\"name\"]} - Create new code following existing patterns')
7725
+ for req in deltas.get('modified', []):
7726
+ parts.append(f' MODIFIED [{domain}]: {req[\"name\"]} - Find and update existing code, do NOT create new files. Previously: {req.get(\"previously\", \"N/A\")}')
7727
+ for req in deltas.get('removed', []):
7728
+ parts.append(f' REMOVED [{domain}]: {req[\"name\"]} - Deprecate or remove. Reason: {req.get(\"reason\", \"N/A\")}')
7729
+ parts.append(f'Complexity: {data.get(\"complexity\", \"unknown\")}')
7730
+ print(' '.join(parts))
7731
+ except Exception:
7732
+ pass
7733
+ " 2>/dev/null || true)
7734
+ fi
7735
+
7588
7736
  # Degraded providers with small models need simplified prompts
7589
7737
  # Full RARV/SDLC instructions overwhelm models < 30B parameters
7590
7738
  if [ "${PROVIDER_DEGRADED:-false}" = "true" ]; then
@@ -7609,15 +7757,15 @@ except: pass
7609
7757
  else
7610
7758
  if [ $retry -eq 0 ]; then
7611
7759
  if [ -n "$prd" ]; then
7612
- echo "Loki Mode with PRD at $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
7760
+ echo "Loki Mode with PRD at $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
7613
7761
  else
7614
- echo "Loki Mode. $human_directive $gate_failure_context $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
7762
+ echo "Loki Mode. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
7615
7763
  fi
7616
7764
  else
7617
7765
  if [ -n "$prd" ]; then
7618
- echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
7766
+ echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
7619
7767
  else
7620
- echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $gate_failure_context $queue_tasks $bmad_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
7768
+ echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $gate_failure_context $queue_tasks $bmad_context $openspec_context $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
7621
7769
  fi
7622
7770
  fi
7623
7771
  fi
@@ -7742,6 +7890,91 @@ BMAD_QUEUE_EOF
7742
7890
  log_info "BMAD queue population complete"
7743
7891
  }
7744
7892
 
7893
+ #===============================================================================
7894
+ # OpenSpec Task Queue Population
7895
+ #===============================================================================
7896
+
7897
+ # Populate the task queue from OpenSpec task artifacts
7898
+ # Only runs once -- skips if queue was already populated from OpenSpec
7899
+ populate_openspec_queue() {
7900
+ # Skip if no OpenSpec tasks file
7901
+ if [[ ! -f ".loki/openspec-tasks.json" ]]; then
7902
+ return 0
7903
+ fi
7904
+
7905
+ # Skip if already populated (marker file)
7906
+ if [[ -f ".loki/queue/.openspec-populated" ]]; then
7907
+ log_info "OpenSpec queue already populated, skipping"
7908
+ return 0
7909
+ fi
7910
+
7911
+ log_step "Populating task queue from OpenSpec tasks..."
7912
+
7913
+ # Ensure queue directory exists
7914
+ mkdir -p ".loki/queue"
7915
+
7916
+ # Read OpenSpec tasks and create queue entries
7917
+ python3 << 'OPENSPEC_QUEUE_EOF'
7918
+ import json
7919
+ import sys
7920
+
7921
+ openspec_tasks_path = ".loki/openspec-tasks.json"
7922
+ pending_path = ".loki/queue/pending.json"
7923
+
7924
+ try:
7925
+ with open(openspec_tasks_path, "r") as f:
7926
+ openspec_tasks = json.load(f)
7927
+ except (json.JSONDecodeError, FileNotFoundError) as e:
7928
+ print(f"Warning: Could not read OpenSpec tasks: {e}", file=sys.stderr)
7929
+ sys.exit(0)
7930
+
7931
+ # Load existing queue
7932
+ existing = []
7933
+ try:
7934
+ with open(pending_path, "r") as f:
7935
+ existing = json.load(f)
7936
+ except (json.JSONDecodeError, FileNotFoundError):
7937
+ pass
7938
+
7939
+ # Convert OpenSpec tasks to queue format (skip completed tasks)
7940
+ for task in openspec_tasks:
7941
+ if task.get("status") == "completed":
7942
+ continue
7943
+ queue_entry = {
7944
+ "id": task.get("id", "openspec-unknown"),
7945
+ "title": task.get("title", "Untitled"),
7946
+ "description": f"[OpenSpec] {task.get('group', 'General')}: {task.get('title', '')}",
7947
+ "priority": task.get("priority", "medium"),
7948
+ "status": "pending",
7949
+ "source": "openspec",
7950
+ "metadata": {
7951
+ "openspec_source": task.get("source", "tasks.md"),
7952
+ "openspec_group": task.get("group", ""),
7953
+ }
7954
+ }
7955
+ existing.append(queue_entry)
7956
+
7957
+ with open(pending_path, "w") as f:
7958
+ json.dump(existing, f, indent=2)
7959
+
7960
+ pending_count = sum(1 for t in openspec_tasks if t.get('status') != 'completed')
7961
+ if pending_count == 0:
7962
+ print("WARNING: All OpenSpec tasks are already marked as completed. No tasks added to queue.", file=sys.stderr)
7963
+ print("Check your tasks.md file -- all checkboxes are checked.", file=sys.stderr)
7964
+ else:
7965
+ print(f"Added {pending_count} OpenSpec tasks to queue")
7966
+ OPENSPEC_QUEUE_EOF
7967
+
7968
+ if [[ $? -ne 0 ]]; then
7969
+ log_warn "Failed to populate OpenSpec queue (python3 error)"
7970
+ return 0
7971
+ fi
7972
+
7973
+ # Mark as populated so we don't re-add on restart
7974
+ touch ".loki/queue/.openspec-populated"
7975
+ log_info "OpenSpec queue population complete"
7976
+ }
7977
+
7745
7978
  #===============================================================================
7746
7979
  # Main Autonomous Loop
7747
7980
  #===============================================================================
@@ -7823,9 +8056,27 @@ run_autonomous() {
7823
8056
  fi
7824
8057
  fi
7825
8058
 
8059
+ # Auto-derive completion promise from PRD (v6.10.0)
8060
+ # When PRD exists but no explicit promise, auto-derive one and switch to checkpoint mode
8061
+ if [ -n "$prd_path" ] && [ -f "$prd_path" ] && [ -z "$COMPLETION_PROMISE" ]; then
8062
+ if [ "${LOKI_AUTO_COMPLETION_PROMISE:-true}" = "true" ]; then
8063
+ COMPLETION_PROMISE="All PRD requirements implemented and tests passing"
8064
+ log_info "Auto-derived completion promise: $COMPLETION_PROMISE"
8065
+ # PRD-driven work is finite; switch from perpetual to checkpoint
8066
+ if [ "${LOKI_FORCE_PERPETUAL:-false}" != "true" ] && [ "$AUTONOMY_MODE" = "perpetual" ]; then
8067
+ AUTONOMY_MODE="checkpoint"
8068
+ PERPETUAL_MODE="false"
8069
+ log_info "Switched autonomy mode: perpetual -> checkpoint (PRD-driven work is finite)"
8070
+ fi
8071
+ fi
8072
+ fi
8073
+
7826
8074
  # Populate task queue from BMAD artifacts (if present, runs once)
7827
8075
  populate_bmad_queue
7828
8076
 
8077
+ # Populate task queue from OpenSpec artifacts (if present, runs once)
8078
+ populate_openspec_queue
8079
+
7829
8080
  # Check max iterations before starting
7830
8081
  if check_max_iterations; then
7831
8082
  log_error "Max iterations already reached. Reset with: rm .loki/autonomy-state.json"
@@ -8302,29 +8553,54 @@ if __name__ == "__main__":
8302
8553
  # Checkpoint after each iteration (v5.57.0)
8303
8554
  create_checkpoint "iteration-${ITERATION_COUNT} complete" "iteration-${ITERATION_COUNT}"
8304
8555
 
8305
- # Quality gates (v6.7.0 - hard enforcement)
8556
+ # Quality gates (v6.10.0 - escalation ladder)
8306
8557
  local gate_failures=""
8307
8558
  if [ "${LOKI_HARD_GATES:-true}" = "true" ]; then
8308
8559
  # Static analysis gate
8309
8560
  if [ "${PHASE_STATIC_ANALYSIS:-true}" = "true" ]; then
8310
- enforce_static_analysis || {
8561
+ if enforce_static_analysis; then
8562
+ clear_gate_failure "static_analysis"
8563
+ else
8564
+ local sa_count
8565
+ sa_count=$(track_gate_failure "static_analysis")
8311
8566
  gate_failures="${gate_failures}static_analysis,"
8312
- log_warn "Static analysis FAILED - findings injected into next iteration"
8313
- }
8567
+ log_warn "Static analysis FAILED ($sa_count consecutive) - findings injected into next iteration"
8568
+ fi
8314
8569
  fi
8315
8570
  # Test coverage gate
8316
8571
  if [ "${PHASE_UNIT_TESTS:-true}" = "true" ]; then
8317
- enforce_test_coverage || {
8572
+ if enforce_test_coverage; then
8573
+ clear_gate_failure "test_coverage"
8574
+ else
8575
+ local tc_count
8576
+ tc_count=$(track_gate_failure "test_coverage")
8318
8577
  gate_failures="${gate_failures}test_coverage,"
8319
- log_warn "Test coverage gate FAILED - must pass next iteration"
8320
- }
8578
+ log_warn "Test coverage gate FAILED ($tc_count consecutive) - must pass next iteration"
8579
+ fi
8321
8580
  fi
8322
- # Code review gate (upgraded from advisory)
8581
+ # Code review gate (upgraded from advisory, with escalation)
8323
8582
  if [ "$PHASE_CODE_REVIEW" = "true" ] && [ "$ITERATION_COUNT" -gt 0 ]; then
8324
- run_code_review || {
8325
- gate_failures="${gate_failures}code_review,"
8326
- log_warn "Code review BLOCKED - Critical/High findings"
8327
- }
8583
+ if run_code_review; then
8584
+ clear_gate_failure "code_review"
8585
+ else
8586
+ local cr_count
8587
+ cr_count=$(track_gate_failure "code_review")
8588
+ if [ "$cr_count" -ge "$GATE_PAUSE_LIMIT" ]; then
8589
+ log_error "Gate escalation: code_review failed $cr_count times (>= $GATE_PAUSE_LIMIT) - forcing PAUSE for human intervention"
8590
+ echo "PAUSE" > "${TARGET_DIR:-.}/.loki/signals/GATE_ESCALATION"
8591
+ echo "code_review gate failed $cr_count consecutive times" >> "${TARGET_DIR:-.}/.loki/signals/GATE_ESCALATION"
8592
+ touch "${TARGET_DIR:-.}/.loki/signals/PAUSE"
8593
+ elif [ "$cr_count" -ge "$GATE_ESCALATE_LIMIT" ]; then
8594
+ log_warn "Gate escalation: code_review failed $cr_count times (>= $GATE_ESCALATE_LIMIT) - escalating"
8595
+ echo "ESCALATE" > "${TARGET_DIR:-.}/.loki/signals/GATE_ESCALATION"
8596
+ gate_failures="${gate_failures}code_review_ESCALATED,"
8597
+ elif [ "$cr_count" -ge "$GATE_CLEAR_LIMIT" ]; then
8598
+ log_warn "Gate cleared: code_review failed $cr_count times (>= $GATE_CLEAR_LIMIT) - passing gate this iteration, counter continues"
8599
+ else
8600
+ gate_failures="${gate_failures}code_review,"
8601
+ log_warn "Code review BLOCKED ($cr_count consecutive) - Critical/High findings"
8602
+ fi
8603
+ fi
8328
8604
  fi
8329
8605
  # Store gate failures for prompt injection
8330
8606
  if [ -n "$gate_failures" ]; then
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.9.0"
10
+ __version__ = "6.11.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v6.9.0
5
+ **Version:** v6.11.0
6
6
 
7
7
  ---
8
8
 
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '6.9.0'
60
+ __version__ = '6.11.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.9.0",
3
+ "version": "6.11.0",
4
4
  "description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "agent",
@@ -27,6 +27,7 @@
27
27
  | Scale patterns (50+ agents) | `parallel-workflows.md` + `references/cursor-learnings.md` |
28
28
  | GitHub issues, PRs, syncing | `github-integration.md` |
29
29
  | Multi-provider (Codex, Gemini) | `providers.md` |
30
+ | OpenSpec delta context, brownfield modifications | `openspec-integration.md` |
30
31
  | Plan deepening, knowledge extraction | `compound-learning.md` |
31
32
 
32
33
  ## Module Descriptions
@@ -109,6 +110,14 @@
109
110
  - Filter by labels, milestone, assignee
110
111
  - Requires `gh` CLI authenticated
111
112
 
113
+ ### openspec-integration.md
114
+ **When:** Working with OpenSpec delta context, `--openspec` flag, brownfield modifications
115
+ - Delta-aware development rules (ADDED/MODIFIED/REMOVED)
116
+ - Task execution by group order
117
+ - Scenario-to-test mapping (GIVEN/WHEN/THEN)
118
+ - Source mapping and verification tracking
119
+ - Complexity-based agent strategy
120
+
112
121
  ### compound-learning.md (v5.30.0)
113
122
  **When:** After architecture phase (deepen plan), after verification (extract learnings)
114
123
  - Deepen-plan: 4 parallel research agents enhance plans before implementation
@@ -0,0 +1,147 @@
1
+ # OpenSpec Integration
2
+
3
+ > **Reference:** OpenSpec delta specs use ADDED/MODIFIED/REMOVED sections to describe changes to existing system behavior. See `.loki/openspec/delta-context.json` for the parsed delta context injected into your prompt.
4
+
5
+ ---
6
+
7
+ ## When This Module Applies
8
+
9
+ - Your prompt contains an `OPENSPEC DELTA CONTEXT` section
10
+ - The project has `.loki/openspec/delta-context.json`
11
+ - The session was started with `--openspec` flag
12
+ - Tasks in `.loki/queue/pending.json` have `openspec_group` metadata
13
+
14
+ **If none of the above are true, do not load this module.**
15
+
16
+ ---
17
+
18
+ ## Delta-Aware Development Rules
19
+
20
+ ### ADDED Requirements
21
+
22
+ New behavior that does not exist in the codebase yet.
23
+
24
+ 1. Create NEW files and functions following existing codebase patterns
25
+ 2. Do NOT modify existing code unless the new feature integrates with it
26
+ 3. Write tests for every scenario (GIVEN/WHEN/THEN from the delta spec)
27
+ 4. Reference: `delta-context.json` entries with `"type": "added"`
28
+
29
+ ```
30
+ # Mental model for ADDED
31
+ Read scenario -> Write test -> Implement -> Verify test passes
32
+ ```
33
+
34
+ ### MODIFIED Requirements
35
+
36
+ Existing behavior that is changing. This is the most common delta type in brownfield work.
37
+
38
+ 1. Find the EXISTING code that implements this requirement
39
+ 2. Modify IN PLACE -- do NOT create new files for modified behavior
40
+ 3. Check the `(Previously: ...)` annotation to understand what changed
41
+ 4. Update existing tests to match the new behavior
42
+ 5. Reference: `delta-context.json` entries with `"type": "modified"`
43
+
44
+ ```
45
+ # Mental model for MODIFIED
46
+ Read "(Previously: ...)" -> Find existing code -> Update code -> Update tests -> Verify
47
+ ```
48
+
49
+ **Common mistake:** Treating MODIFIED as ADDED and creating new files. Always search the codebase first for the existing implementation.
50
+
51
+ ### REMOVED Requirements
52
+
53
+ Behavior that is being deprecated or deleted.
54
+
55
+ 1. Find and remove or deprecate the code implementing this requirement
56
+ 2. Check the `(Deprecated: ...)` annotation for the reason
57
+ 3. Remove associated tests
58
+ 4. Ensure no orphaned imports or dead code remains
59
+ 5. Reference: `delta-context.json` entries with `"type": "removed"`
60
+
61
+ ```
62
+ # Mental model for REMOVED
63
+ Read "(Deprecated: ...)" -> Find existing code -> Remove code -> Remove tests -> Verify no dead refs
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Task Execution
69
+
70
+ Tasks are generated from OpenSpec `tasks.md` and loaded into `.loki/queue/pending.json`.
71
+
72
+ - Each task has `openspec_group` metadata indicating its task group number
73
+ - Execute tasks in group order (group 1 before group 2, etc.)
74
+ - Within a group, tasks can run in parallel if they touch different files
75
+ - Mark tasks complete in the queue when done
76
+
77
+ ```json
78
+ {
79
+ "id": "task-3",
80
+ "title": "Implement session timeout change",
81
+ "openspec_group": 1,
82
+ "delta_type": "modified",
83
+ "spec_ref": "auth/spec.md#session-expiration"
84
+ }
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Scenario Verification
90
+
91
+ After implementing a requirement, verify its scenarios.
92
+
93
+ 1. Each scenario has GIVEN (precondition), WHEN (action), THEN (expected outcome)
94
+ 2. Write test cases that map 1:1 to scenarios
95
+ 3. Use the scenario name as the test name for traceability
96
+ 4. Verification results are tracked in `.loki/openspec/verification-results.json`
97
+
98
+ ```python
99
+ # Scenario: "Idle timeout" -> test name matches scenario
100
+ def test_idle_timeout():
101
+ # GIVEN an authenticated session
102
+ session = create_authenticated_session()
103
+ # WHEN 15 minutes pass without activity
104
+ advance_time(minutes=15)
105
+ # THEN the session is invalidated
106
+ assert session.is_expired()
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Source Mapping
112
+
113
+ `.loki/openspec/source-map.json` maps each task ID to its origin in the spec files.
114
+
115
+ | Field | Purpose |
116
+ |-------|---------|
117
+ | `task_id` | Queue task identifier |
118
+ | `spec_file` | Source spec file path |
119
+ | `requirement` | Requirement name |
120
+ | `scenario` | Scenario name (if applicable) |
121
+ | `line` | Line number in spec file |
122
+
123
+ Use this to trace implementation decisions back to the specification.
124
+
125
+ ---
126
+
127
+ ## Complexity Levels
128
+
129
+ | Level | Tasks | Spec Files | Design | Agent Strategy |
130
+ |-------|-------|------------|--------|----------------|
131
+ | simple | 1-3 | 1 | none | Single agent, sequential |
132
+ | standard | 4-10 | 2-5 | present | Parallel where possible |
133
+ | complex | 11-20 | 5-10 | present | Task tool parallelization |
134
+ | enterprise | 20+ | 10+ | present | Full agent team |
135
+
136
+ ---
137
+
138
+ ## Common Mistakes
139
+
140
+ | Mistake | Correction |
141
+ |---------|------------|
142
+ | Creating new files for MODIFIED requirements | Search codebase first, update existing code in place |
143
+ | Ignoring `(Previously: ...)` annotations | These tell you exactly what changed -- read them |
144
+ | Not writing tests for GIVEN/WHEN/THEN scenarios | Every scenario must have a corresponding test |
145
+ | Treating all deltas as ADDED | Most brownfield work is MODIFIED -- check the delta type |
146
+ | Skipping REMOVED cleanup | Dead code and orphaned imports cause maintenance burden |
147
+ | Implementing groups out of order | Group 1 must complete before group 2 starts |