loki-mode 6.2.0 → 6.2.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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![Agent Types](https://img.shields.io/badge/Agent%20Types-41-blue)]()
10
10
  [![Autonomi](https://img.shields.io/badge/Autonomi-autonomi.dev-5B4EEA)](https://www.autonomi.dev/)
11
11
 
12
- **Current Version: v5.52.4**
12
+ **Current Version: v6.2.1**
13
13
 
14
14
  ---
15
15
 
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v6.2.0
6
+ # Loki Mode v6.2.1
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -263,4 +263,4 @@ The following features are documented in skill modules but not yet fully automat
263
263
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
264
264
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
265
265
 
266
- **v6.2.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
266
+ **v6.2.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 6.2.0
1
+ 6.2.1
@@ -80,7 +80,7 @@ def parse_frontmatter(text: str) -> Tuple[Dict[str, Any], str]:
80
80
  """Extract YAML frontmatter from a markdown document.
81
81
 
82
82
  Returns (metadata_dict, body_without_frontmatter).
83
- Handles simple YAML: scalars, lists (flow and block), quoted strings.
83
+ Handles simple YAML: scalars, lists (flow-style only; block-style not supported), quoted strings.
84
84
  Does NOT require PyYAML -- uses regex-based extraction.
85
85
  """
86
86
  stripped = text.lstrip()
@@ -514,9 +514,10 @@ def validate_chain(
514
514
  "message": "PRD frontmatter has no inputDocuments -- cannot verify product-brief linkage.",
515
515
  })
516
516
  else:
517
+ docs = input_docs if isinstance(input_docs, list) else [input_docs] if input_docs else []
517
518
  findings.append({
518
519
  "level": "info",
519
- "message": f"PRD references input documents: {', '.join(input_docs)}",
520
+ "message": f"PRD references input documents: {', '.join(docs)}",
520
521
  })
521
522
 
522
523
  # 2. Missing artifacts
package/autonomy/loki CHANGED
@@ -646,8 +646,9 @@ cmd_start() {
646
646
  if [[ -n "$bmad_project_path" ]]; then
647
647
  # Resolve to absolute path
648
648
  if [[ ! "$bmad_project_path" = /* ]]; then
649
+ local original_bmad_path="$bmad_project_path"
649
650
  bmad_project_path="$(cd "$bmad_project_path" 2>/dev/null && pwd)" || {
650
- echo -e "${RED}Error: BMAD project path does not exist: $bmad_project_path${NC}"
651
+ echo -e "${RED}Error: BMAD project path does not exist: $original_bmad_path${NC}"
651
652
  exit 1
652
653
  }
653
654
  fi
@@ -2754,10 +2755,12 @@ cmd_run() {
2754
2755
 
2755
2756
  # Write PRD first so background process can use it
2756
2757
  mkdir -p "$LOKI_DIR"
2757
- local prd_content_detach
2758
- prd_content_detach=$(echo "$issue_json" | generate_prd_from_issue)
2759
2758
  local detach_prd="$LOKI_DIR/prd-issue-${number}.md"
2760
- echo "$prd_content_detach" > "$detach_prd"
2759
+ echo "$prd_content" > "$detach_prd"
2760
+
2761
+ if [[ -z "${branch_name:-}" ]]; then
2762
+ branch_name="issue/detach-$(date +%s)"
2763
+ fi
2761
2764
 
2762
2765
  nohup bash -c "
2763
2766
  cd $(pwd)
@@ -2765,6 +2768,24 @@ cmd_run() {
2765
2768
  export LOKI_PARALLEL_MODE=true
2766
2769
  export LOKI_WORKTREE_BRANCH=\"$branch_name\"
2767
2770
  $(command -v loki || echo "$0") start \"$detach_prd\" --parallel ${start_args[*]+"${start_args[*]}"}
2771
+
2772
+ # Post-completion: create PR if requested
2773
+ if [[ \"$create_pr\" == \"true\" ]]; then
2774
+ branch_current=\$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\")
2775
+ if [[ -n \"\$branch_current\" && \"\$branch_current\" != \"main\" && \"\$branch_current\" != \"master\" ]]; then
2776
+ git push origin \"\$branch_current\" 2>/dev/null || true
2777
+ gh pr create --title \"${title:-Implementation for issue ${issue_ref}}\" --body \"Implemented by Loki Mode\" --head \"\$branch_current\" 2>/dev/null || true
2778
+ fi
2779
+ fi
2780
+ # Post-completion: auto-merge if requested
2781
+ if [[ \"$auto_merge\" == \"true\" ]]; then
2782
+ branch_current=\$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\")
2783
+ if gh pr merge \"\$branch_current\" --squash --delete-branch 2>/dev/null; then
2784
+ if [[ -n \"${number:-}\" ]]; then
2785
+ gh issue close \"$number\" --comment \"Resolved by Loki Mode\" 2>/dev/null || true
2786
+ fi
2787
+ fi
2788
+ fi
2768
2789
  " > "$log_file" 2>&1 &
2769
2790
 
2770
2791
  local bg_pid=$!
@@ -2862,11 +2883,13 @@ $(git log --oneline "main..HEAD" 2>/dev/null || echo "See diff")"
2862
2883
  github)
2863
2884
  local branch_current
2864
2885
  branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
2865
- gh pr merge "$branch_current" --squash --delete-branch 2>/dev/null && \
2866
- echo -e "${GREEN}PR merged and branch deleted${NC}" || \
2886
+ if gh pr merge "$branch_current" --squash --delete-branch 2>/dev/null; then
2887
+ echo -e "${GREEN}PR merged and branch deleted${NC}"
2888
+ if [[ -n "${number:-}" ]]; then
2889
+ gh issue close "$number" --comment "Resolved by Loki Mode" 2>/dev/null || true
2890
+ fi
2891
+ else
2867
2892
  echo -e "${YELLOW}Auto-merge failed. PR remains open.${NC}"
2868
- if [[ -n "${number:-}" ]]; then
2869
- gh issue close "$number" --comment "Resolved by Loki Mode" 2>/dev/null || true
2870
2893
  fi
2871
2894
  ;;
2872
2895
  gitlab)
@@ -6964,7 +6987,6 @@ except: print('?')
6964
6987
  local errors
6965
6988
  errors=$(python3 -c "
6966
6989
  import json, sys
6967
- sys.path.insert(0, '$(dirname "$SKILL_DIR/swarm/")')
6968
6990
  sys.path.insert(0, '$SKILL_DIR')
6969
6991
  from swarm.patterns import TopologyValidator
6970
6992
  errors = TopologyValidator.validate_file(sys.argv[1])
@@ -7026,17 +7048,20 @@ for a in d.get('agents', []):
7026
7048
  return 1
7027
7049
  fi
7028
7050
  # Validate first
7029
- local errors
7030
- errors=$(python3 -c "
7051
+ local result
7052
+ result=$(python3 -c "
7031
7053
  import json, sys
7032
7054
  sys.path.insert(0, '$SKILL_DIR')
7033
7055
  from swarm.patterns import TopologyValidator
7034
7056
  errors = TopologyValidator.validate_file(sys.argv[1])
7035
- for e in errors: print(e)
7057
+ if errors:
7058
+ for e in errors: print(f'ERROR: {e}')
7059
+ else:
7060
+ print('VALID')
7036
7061
  " "$template_file" 2>&1)
7037
- if [[ -n "$errors" ]]; then
7062
+ if ! echo "$result" | grep -q "^VALID$"; then
7038
7063
  echo -e "${RED}Template validation failed:${NC}"
7039
- echo "$errors"
7064
+ echo "$result"
7040
7065
  return 1
7041
7066
  fi
7042
7067
  echo -e "${GREEN}Cluster template: $template_name${NC}"
@@ -114,7 +114,6 @@ DIMENSIONS = {
114
114
  "weight": 0.75,
115
115
  "heading_patterns": [
116
116
  r"(?i)#+\s.*(?:deploy|hosting|infra|ci.?cd|environment)",
117
- r"(?i)^##\s+Non-Functional\s+Requirements",
118
117
  ],
119
118
  "content_patterns": [
120
119
  r"(?i)\b(?:deploy|hosting|ci.?cd|pipeline|staging|production)\b",
@@ -259,8 +258,9 @@ class PrdAnalyzer:
259
258
  if in_feature_section and re.match(r"^\s*#+\s", line):
260
259
  in_feature_section = False
261
260
  continue
262
- if re.match(r"^\s*[-*]\s+\S", line) or re.match(r"^\s*\d+\.\s+\S", line):
263
- count += 1
261
+ if in_feature_section:
262
+ if re.match(r"^\s*[-*]\s+\S", line) or re.match(r"^\s*\d+\.\s+\S", line):
263
+ count += 1
264
264
 
265
265
  self.feature_count = count
266
266
  for threshold, label in SCOPE_THRESHOLDS:
package/autonomy/run.sh CHANGED
@@ -7043,7 +7043,19 @@ except: pass
7043
7043
  fi
7044
7044
  local bmad_tasks=""
7045
7045
  if [[ -f ".loki/bmad-tasks.json" ]]; then
7046
- bmad_tasks=$(head -c 32000 ".loki/bmad-tasks.json")
7046
+ bmad_tasks=$(python3 -c "
7047
+ import json, sys
7048
+ try:
7049
+ with open('.loki/bmad-tasks.json') as f:
7050
+ data = json.load(f)
7051
+ out = json.dumps(data, indent=None)
7052
+ if len(out) > 32000 and isinstance(data, list):
7053
+ while len(json.dumps(data, indent=None)) > 32000 and data:
7054
+ data.pop()
7055
+ out = json.dumps(data, indent=None)
7056
+ print(out[:32000])
7057
+ except: pass
7058
+ " 2>/dev/null)
7047
7059
  fi
7048
7060
  local bmad_validation=""
7049
7061
  if [[ -f ".loki/bmad-validation.md" ]]; then
@@ -7100,7 +7112,7 @@ populate_bmad_queue() {
7100
7112
  mkdir -p ".loki/queue"
7101
7113
 
7102
7114
  # Read BMAD tasks and create queue entries
7103
- python3 << 'BMAD_QUEUE_EOF' 2>/dev/null
7115
+ python3 << 'BMAD_QUEUE_EOF'
7104
7116
  import json
7105
7117
  import os
7106
7118
  import sys
@@ -7155,12 +7167,16 @@ if os.path.exists(pending_path):
7155
7167
  except (json.JSONDecodeError, FileNotFoundError):
7156
7168
  existing = []
7157
7169
 
7158
- # Convert BMAD stories to queue task format
7170
+ # Convert BMAD stories to queue task format (with deduplication)
7171
+ existing_ids = {t.get("id") for t in existing if isinstance(t, dict)}
7159
7172
  for i, story in enumerate(stories):
7160
7173
  if not isinstance(story, dict):
7161
7174
  continue
7175
+ task_id = f"bmad-{i+1}"
7176
+ if task_id in existing_ids:
7177
+ continue
7162
7178
  task = {
7163
- "id": f"bmad-{i+1}",
7179
+ "id": task_id,
7164
7180
  "title": story.get("title", story.get("name", f"BMAD Story {i+1}")),
7165
7181
  "description": story.get("description", story.get("action", "")),
7166
7182
  "priority": story.get("priority", "medium"),
@@ -70,9 +70,10 @@ PROMPT_INJECTION_ENABLED="${LOKI_PROMPT_INJECTION:-false}"
70
70
  # Container runs as user 'loki' (UID 1000), so mount to /home/loki/
71
71
  declare -A DOCKER_MOUNT_PRESETS
72
72
  DOCKER_MOUNT_PRESETS=(
73
+ # Note: gh and git are also hardcoded in start_sandbox() defaults
73
74
  [gh]="$HOME/.config/gh:/home/loki/.config/gh:ro"
74
75
  [git]="$HOME/.gitconfig:/home/loki/.gitconfig:ro"
75
- [ssh]="$HOME/.ssh:/home/loki/.ssh:ro"
76
+ [ssh]="$HOME/.ssh/known_hosts:/home/loki/.ssh/known_hosts:ro"
76
77
  [aws]="$HOME/.aws:/home/loki/.aws:ro"
77
78
  [azure]="$HOME/.azure:/home/loki/.azure:ro"
78
79
  [kube]="$HOME/.kube:/home/loki/.kube:ro"
@@ -799,7 +800,7 @@ docker_desktop_sandbox_run() {
799
800
  # Reads from: .loki/config/settings.json dockerMounts, LOKI_DOCKER_MOUNTS env var
800
801
  # Returns: string of -v and -e flags for docker run
801
802
  resolve_docker_mounts() {
802
- local mount_args=""
803
+ RESOLVED_MOUNTS=()
803
804
 
804
805
  # Read configured presets (JSON array of strings)
805
806
  local presets_json=""
@@ -807,10 +808,10 @@ resolve_docker_mounts() {
807
808
  presets_json=$(python3 -c "
808
809
  import json, sys
809
810
  try:
810
- with open('${PROJECT_DIR}/.loki/config/settings.json') as f:
811
+ with open(sys.argv[1] + '/.loki/config/settings.json') as f:
811
812
  print(json.dumps(json.load(f).get('dockerMounts', [])))
812
813
  except: print('[]')
813
- " 2>/dev/null || echo "[]")
814
+ " "${PROJECT_DIR}" 2>/dev/null || echo "[]")
814
815
  fi
815
816
 
816
817
  # Override with env var if set
@@ -846,12 +847,16 @@ except: pass
846
847
  local container_path="${parts[1]}"
847
848
  local mode="${parts[2]:-ro}"
848
849
 
849
- # Expand ~ and $HOME
850
- host_path=$(eval echo "$host_path" 2>/dev/null || echo "$host_path")
850
+ # Safe tilde expansion (no eval)
851
+ if [[ "$host_path" == "~/"* ]]; then
852
+ host_path="$HOME/${host_path#\~/}"
853
+ elif [[ "$host_path" == "~" ]]; then
854
+ host_path="$HOME"
855
+ fi
851
856
 
852
857
  # Only mount if host path exists
853
858
  if [[ -e "$host_path" ]]; then
854
- mount_args="$mount_args -v ${host_path}:${container_path}:${mode}"
859
+ RESOLVED_MOUNTS+=("-v" "${host_path}:${container_path}:${mode}")
855
860
  log_info " Mount preset [$name]: $host_path -> $container_path ($mode)"
856
861
  fi
857
862
 
@@ -865,18 +870,18 @@ except: pass
865
870
  # Wildcard: pass all matching env vars
866
871
  local prefix="${env_name%\*}"
867
872
  while IFS='=' read -r key val; do
868
- [[ "$key" == "$prefix"* ]] && [[ -n "$val" ]] && \
869
- mount_args="$mount_args -e $key"
873
+ [[ "$key" == "$prefix"* ]] && [[ -n "$val" ]] && {
874
+ RESOLVED_MOUNTS+=("-e" "$key")
875
+ log_info " Env wildcard [$name]: passing $key"
876
+ }
870
877
  done < <(env)
871
878
  elif [[ -n "${!env_name:-}" ]]; then
872
- mount_args="$mount_args -e $env_name"
879
+ RESOLVED_MOUNTS+=("-e" "$env_name")
873
880
  fi
874
881
  done
875
882
  fi
876
883
  done <<< "$preset_names"
877
884
  fi
878
-
879
- echo "$mount_args"
880
885
  }
881
886
 
882
887
  start_sandbox() {
@@ -1037,11 +1042,9 @@ start_sandbox() {
1037
1042
 
1038
1043
  # Apply Docker credential mount presets (additive on top of defaults above)
1039
1044
  if [[ "$no_mounts" != "true" ]] && [[ "${LOKI_NO_DOCKER_MOUNTS:-}" != "true" ]]; then
1040
- local preset_mounts
1041
- preset_mounts=$(resolve_docker_mounts)
1042
- if [[ -n "$preset_mounts" ]]; then
1043
- # shellcheck disable=SC2206
1044
- docker_args+=($preset_mounts)
1045
+ resolve_docker_mounts
1046
+ if [[ ${#RESOLVED_MOUNTS[@]} -gt 0 ]]; then
1047
+ docker_args+=("${RESOLVED_MOUNTS[@]}")
1045
1048
  fi
1046
1049
  fi
1047
1050
 
@@ -1054,7 +1057,12 @@ start_sandbox() {
1054
1057
  local c_container="${mount_parts[1]:-}"
1055
1058
  local c_mode="${mount_parts[2]:-ro}"
1056
1059
  if [[ -n "$c_host" ]] && [[ -n "$c_container" ]]; then
1057
- c_host=$(eval echo "$c_host" 2>/dev/null || echo "$c_host")
1060
+ # Safe tilde expansion (no eval)
1061
+ if [[ "$c_host" == "~/"* ]]; then
1062
+ c_host="$HOME/${c_host#\~/}"
1063
+ elif [[ "$c_host" == "~" ]]; then
1064
+ c_host="$HOME"
1065
+ fi
1058
1066
  if [[ -e "$c_host" ]]; then
1059
1067
  docker_args+=("--volume" "${c_host}:${c_container}:${c_mode}")
1060
1068
  log_info " Custom mount: $c_host -> $c_container ($c_mode)"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "6.2.0"
10
+ __version__ = "6.2.1"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -2,33 +2,28 @@
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.2.0
5
+ **Version:** v6.2.1
6
6
 
7
7
  ---
8
8
 
9
- ## What's New in v5.49.1
10
-
11
- ### Enterprise Security (v5.36.0-v5.37.1)
12
- - TLS/HTTPS support for dashboard connections
13
- - OIDC/SSO authentication (Google, Azure AD, Okta)
14
- - RBAC roles (admin, operator, viewer, auditor)
15
- - WebSocket authentication for real-time connections
16
- - Syslog forwarding for SIEM integration
17
- - Non-root Docker with SETUID/SETGID removed
18
- - Salted token hashing and rate limiting
19
-
20
- ### Monitoring & Observability (v5.38.0)
21
- - Prometheus/OpenMetrics `/metrics` endpoint with 9 metrics
22
- - `loki metrics` CLI command
23
- - Agent action audit trail at `.loki/logs/agent-audit.jsonl`
24
- - `loki audit` CLI with log/count subcommands
25
- - SHA-256 chain-hashed tamper-evident audit entries
26
-
27
- ### Workflow Protection (v5.38.0)
28
- - Branch protection: agent sessions auto-create feature branches
29
- - PR creation via `gh` on session completion
30
- - OpenClaw bridge foundation for external integrations
31
- - Network security documentation (Docker/Kubernetes)
9
+ ## What's New in v6.2.1
10
+
11
+ ### Dual-Mode Architecture (v6.0.0)
12
+ - `loki run` command for direct autonomous execution
13
+ - Dual-mode: skill mode (inside Claude Code) and standalone mode
14
+ - Dynamic model resolution across all providers
15
+ - Multi-provider issue fixes and stability improvements
16
+
17
+ ### ChromaDB Semantic Code Search (v6.1.0)
18
+ - Semantic code search via ChromaDB vector database
19
+ - MCP integration with `loki_code_search` and `loki_code_search_stats` tools
20
+ - Automatic codebase indexing with `tools/index-codebase.py`
21
+
22
+ ### Memory System (v5.15.0+)
23
+ - Episodic, semantic, and procedural memory layers
24
+ - Progressive disclosure with 3-layer loading
25
+ - Token economics tracking for discovery vs read tokens
26
+ - Optional vector search with sentence-transformers
32
27
 
33
28
  ---
34
29
 
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '6.2.0'
60
+ __version__ = '6.2.1'
package/mcp/server.py CHANGED
@@ -1231,10 +1231,15 @@ def _get_chroma_collection():
1231
1231
  """Get or create ChromaDB collection (lazy connection)."""
1232
1232
  global _chroma_client, _chroma_collection
1233
1233
  if _chroma_collection is not None:
1234
- return _chroma_collection
1234
+ try:
1235
+ _chroma_client.heartbeat()
1236
+ return _chroma_collection
1237
+ except Exception:
1238
+ _chroma_client = None
1239
+ _chroma_collection = None
1235
1240
  try:
1236
1241
  import chromadb
1237
- _chroma_client = chromadb.HttpClient(host=CHROMA_HOST, port=int(CHROMA_PORT))
1242
+ _chroma_client = chromadb.HttpClient(host=CHROMA_HOST, port=CHROMA_PORT)
1238
1243
  _chroma_collection = _chroma_client.get_collection(name=CHROMA_COLLECTION)
1239
1244
  return _chroma_collection
1240
1245
  except Exception as e:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.2.0",
3
+ "version": "6.2.1",
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",