loki-mode 6.36.2 → 6.36.4
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/loki +56 -8
- package/autonomy/run.sh +21 -8
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/web-app/server.py +28 -4
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.36.
|
|
6
|
+
# Loki Mode v6.36.4
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -267,4 +267,4 @@ The following features are documented in skill modules but not yet fully automat
|
|
|
267
267
|
| Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
|
|
268
268
|
| Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
|
|
269
269
|
|
|
270
|
-
**v6.36.
|
|
270
|
+
**v6.36.4 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.36.
|
|
1
|
+
6.36.4
|
package/autonomy/loki
CHANGED
|
@@ -219,7 +219,7 @@ require_jq() {
|
|
|
219
219
|
echo " brew install jq (macOS)"
|
|
220
220
|
echo " apt install jq (Debian/Ubuntu)"
|
|
221
221
|
echo " yum install jq (RHEL/CentOS)"
|
|
222
|
-
|
|
222
|
+
return 1
|
|
223
223
|
fi
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -551,6 +551,10 @@ cmd_start() {
|
|
|
551
551
|
;;
|
|
552
552
|
--provider)
|
|
553
553
|
if [[ -n "${2:-}" ]]; then
|
|
554
|
+
if [[ "$2" == --* ]]; then
|
|
555
|
+
echo -e "${RED}Missing value for --provider flag${NC}"
|
|
556
|
+
exit 1
|
|
557
|
+
fi
|
|
554
558
|
provider="$2"
|
|
555
559
|
args+=("--provider" "$provider")
|
|
556
560
|
shift 2
|
|
@@ -1450,7 +1454,7 @@ cmd_status() {
|
|
|
1450
1454
|
esac
|
|
1451
1455
|
done
|
|
1452
1456
|
|
|
1453
|
-
require_jq
|
|
1457
|
+
require_jq || return 1
|
|
1454
1458
|
|
|
1455
1459
|
if [ ! -d "$LOKI_DIR" ]; then
|
|
1456
1460
|
echo -e "${BOLD}Loki Mode Status${NC}"
|
|
@@ -3451,7 +3455,7 @@ cmd_issue_parse() {
|
|
|
3451
3455
|
|
|
3452
3456
|
# View parsed GitHub issue details
|
|
3453
3457
|
cmd_issue_view() {
|
|
3454
|
-
require_jq
|
|
3458
|
+
require_jq || return 1
|
|
3455
3459
|
|
|
3456
3460
|
local issue_ref="${1:-}"
|
|
3457
3461
|
|
|
@@ -3566,7 +3570,7 @@ cmd_issue_view() {
|
|
|
3566
3570
|
#===============================================================================
|
|
3567
3571
|
|
|
3568
3572
|
cmd_run() {
|
|
3569
|
-
require_jq
|
|
3573
|
+
require_jq || return 1
|
|
3570
3574
|
|
|
3571
3575
|
local issue_ref=""
|
|
3572
3576
|
local dry_run=false
|
|
@@ -3731,6 +3735,22 @@ cmd_run() {
|
|
|
3731
3735
|
esac
|
|
3732
3736
|
done
|
|
3733
3737
|
|
|
3738
|
+
# Validate git prerequisites for --ship and --pr flags
|
|
3739
|
+
if $create_pr || $auto_merge; then
|
|
3740
|
+
if ! command -v git &>/dev/null; then
|
|
3741
|
+
echo -e "${RED}Error: --pr/--ship requires git but it is not installed.${NC}"
|
|
3742
|
+
return 1
|
|
3743
|
+
fi
|
|
3744
|
+
if ! git rev-parse --git-dir &>/dev/null 2>&1; then
|
|
3745
|
+
echo -e "${RED}Error: --pr/--ship requires a git repository but the current directory is not one.${NC}"
|
|
3746
|
+
return 1
|
|
3747
|
+
fi
|
|
3748
|
+
if [[ -z "$(git remote 2>/dev/null)" ]]; then
|
|
3749
|
+
echo -e "${RED}Error: --pr/--ship requires a git remote but none is configured.${NC}"
|
|
3750
|
+
return 1
|
|
3751
|
+
fi
|
|
3752
|
+
fi
|
|
3753
|
+
|
|
3734
3754
|
# Add --parallel once if worktree mode is enabled (not per-flag)
|
|
3735
3755
|
if $use_worktree; then
|
|
3736
3756
|
start_args+=("--parallel")
|
|
@@ -3994,7 +4014,7 @@ cmd_issue() {
|
|
|
3994
4014
|
echo -e "${YELLOW} 'loki run' supports GitHub, GitLab, Jira, and Azure DevOps issues.${NC}" >&2
|
|
3995
4015
|
echo "" >&2
|
|
3996
4016
|
|
|
3997
|
-
require_jq
|
|
4017
|
+
require_jq || return 1
|
|
3998
4018
|
|
|
3999
4019
|
local issue_ref=""
|
|
4000
4020
|
local repo=""
|
|
@@ -6595,6 +6615,19 @@ QPRDEOF
|
|
|
6595
6615
|
|
|
6596
6616
|
# Project scaffolding (v6.28.0)
|
|
6597
6617
|
cmd_init() {
|
|
6618
|
+
# Guard: check if .loki/ already exists to avoid overwriting active session
|
|
6619
|
+
if [[ -d ".loki" ]]; then
|
|
6620
|
+
if [[ -f ".loki/loki.pid" ]]; then
|
|
6621
|
+
local pid
|
|
6622
|
+
pid=$(cat ".loki/loki.pid" 2>/dev/null)
|
|
6623
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
6624
|
+
echo -e "${RED}Cannot initialize: active session running. Stop it first with 'loki stop'.${NC}"
|
|
6625
|
+
return 1
|
|
6626
|
+
fi
|
|
6627
|
+
fi
|
|
6628
|
+
echo -e "${YELLOW}Reinitializing existing .loki/ directory${NC}"
|
|
6629
|
+
fi
|
|
6630
|
+
|
|
6598
6631
|
local version=$(get_version)
|
|
6599
6632
|
local project_name=""
|
|
6600
6633
|
local template_name=""
|
|
@@ -10269,10 +10302,25 @@ cmd_worktree() {
|
|
|
10269
10302
|
return 1
|
|
10270
10303
|
fi
|
|
10271
10304
|
|
|
10305
|
+
# Validate JSON is parseable and contains required fields
|
|
10306
|
+
if ! python3 -c "
|
|
10307
|
+
import json, sys
|
|
10308
|
+
try:
|
|
10309
|
+
data = json.load(open('$signal_file'))
|
|
10310
|
+
assert data.get('branch'), 'missing branch'
|
|
10311
|
+
assert data.get('worktree'), 'missing worktree'
|
|
10312
|
+
except Exception as e:
|
|
10313
|
+
print(str(e), file=sys.stderr)
|
|
10314
|
+
sys.exit(1)
|
|
10315
|
+
" 2>/dev/null; then
|
|
10316
|
+
echo -e "${RED}Invalid or corrupted merge signal file${NC}"
|
|
10317
|
+
exit 1
|
|
10318
|
+
fi
|
|
10319
|
+
|
|
10272
10320
|
local branch
|
|
10273
|
-
branch=$(python3 -c "import json; print(json.load(open('$signal_file'))['branch'])"
|
|
10321
|
+
branch=$(python3 -c "import json; print(json.load(open('$signal_file'))['branch'])")
|
|
10274
10322
|
local worktree_path
|
|
10275
|
-
worktree_path=$(python3 -c "import json; print(json.load(open('$signal_file'))['worktree'])"
|
|
10323
|
+
worktree_path=$(python3 -c "import json; print(json.load(open('$signal_file'))['worktree'])")
|
|
10276
10324
|
|
|
10277
10325
|
if git merge --no-ff "$branch" -m "merge($name): auto-merge from parallel worktree"; then
|
|
10278
10326
|
echo -e "${GREEN}Merge successful${NC}"
|
|
@@ -10785,7 +10833,7 @@ print()
|
|
|
10785
10833
|
|
|
10786
10834
|
# Reset session state
|
|
10787
10835
|
cmd_reset() {
|
|
10788
|
-
require_jq
|
|
10836
|
+
require_jq || return 1
|
|
10789
10837
|
|
|
10790
10838
|
local subcommand="${1:-all}"
|
|
10791
10839
|
|
package/autonomy/run.sh
CHANGED
|
@@ -1212,13 +1212,17 @@ detect_complexity() {
|
|
|
1212
1212
|
fi
|
|
1213
1213
|
|
|
1214
1214
|
# Count files in project (excluding common non-source dirs)
|
|
1215
|
-
local file_count
|
|
1215
|
+
local file_count=0
|
|
1216
|
+
file_count=$(find "$target_dir" -type f \
|
|
1216
1217
|
\( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \
|
|
1217
1218
|
-o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.java" \
|
|
1218
1219
|
-o -name "*.rb" -o -name "*.php" -o -name "*.swift" -o -name "*.kt" \) \
|
|
1219
1220
|
! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/vendor/*" \
|
|
1220
1221
|
! -path "*/dist/*" ! -path "*/build/*" ! -path "*/__pycache__/*" \
|
|
1221
1222
|
2>/dev/null | wc -l | tr -d ' ')
|
|
1223
|
+
# Validate file_count is numeric (guard against empty/malformed pipeline output)
|
|
1224
|
+
file_count="${file_count:-0}"
|
|
1225
|
+
file_count="${file_count//[^0-9]/}"
|
|
1222
1226
|
|
|
1223
1227
|
# Check for external integrations
|
|
1224
1228
|
local has_external=false
|
|
@@ -7901,8 +7905,10 @@ build_prompt() {
|
|
|
7901
7905
|
# Load existing context if resuming
|
|
7902
7906
|
local context_injection=""
|
|
7903
7907
|
if [ $retry -gt 0 ]; then
|
|
7904
|
-
local ledger
|
|
7905
|
-
|
|
7908
|
+
local ledger=""
|
|
7909
|
+
type -t load_ledger_context &>/dev/null && ledger=$(load_ledger_context)
|
|
7910
|
+
local handoff=""
|
|
7911
|
+
type -t load_handoff_context &>/dev/null && handoff=$(load_handoff_context)
|
|
7906
7912
|
|
|
7907
7913
|
if [ -n "$ledger" ]; then
|
|
7908
7914
|
context_injection="PREVIOUS_LEDGER_STATE: $ledger"
|
|
@@ -8432,11 +8438,15 @@ run_autonomous() {
|
|
|
8432
8438
|
if [ -n "$found_prd" ]; then
|
|
8433
8439
|
log_info "Found existing PRD: $found_prd"
|
|
8434
8440
|
prd_path="$found_prd"
|
|
8441
|
+
# Warn if a generated PRD also exists (user file takes precedence)
|
|
8442
|
+
if [ -f ".loki/generated-prd.md" ] || [ -f ".loki/generated-prd.json" ]; then
|
|
8443
|
+
log_warn "Using user PRD ($found_prd) instead of generated PRD (.loki/generated-prd.md). Remove generated PRD if no longer needed."
|
|
8444
|
+
fi
|
|
8435
8445
|
elif [ -f ".loki/generated-prd.md" ]; then
|
|
8436
|
-
log_info "Using previously generated PRD: .loki/generated-prd.md"
|
|
8446
|
+
log_info "No user PRD found. Using previously generated PRD: .loki/generated-prd.md"
|
|
8437
8447
|
prd_path=".loki/generated-prd.md"
|
|
8438
8448
|
elif [ -f ".loki/generated-prd.json" ]; then
|
|
8439
|
-
log_info "Using previously generated PRD: .loki/generated-prd.json"
|
|
8449
|
+
log_info "No user PRD found. Using previously generated PRD: .loki/generated-prd.json"
|
|
8440
8450
|
prd_path=".loki/generated-prd.json"
|
|
8441
8451
|
else
|
|
8442
8452
|
log_info "No PRD found - will analyze codebase and generate one"
|
|
@@ -9194,8 +9204,9 @@ check_human_intervention() {
|
|
|
9194
9204
|
log_warn "PAUSE file created by budget limit - NOT auto-clearing in perpetual mode"
|
|
9195
9205
|
log_warn "Budget limit reached. Remove .loki/signals/BUDGET_EXCEEDED and .loki/PAUSE to continue."
|
|
9196
9206
|
notify_intervention_needed "Budget limit reached - execution paused" 2>/dev/null || true
|
|
9207
|
+
local pause_result
|
|
9197
9208
|
handle_pause
|
|
9198
|
-
|
|
9209
|
+
pause_result=$?
|
|
9199
9210
|
rm -f "$loki_dir/PAUSE"
|
|
9200
9211
|
if [ "$pause_result" -eq 1 ]; then
|
|
9201
9212
|
return 2
|
|
@@ -9211,8 +9222,9 @@ check_human_intervention() {
|
|
|
9211
9222
|
fi
|
|
9212
9223
|
log_warn "PAUSE file detected - pausing execution"
|
|
9213
9224
|
notify_intervention_needed "Execution paused via PAUSE file"
|
|
9225
|
+
local pause_result
|
|
9214
9226
|
handle_pause
|
|
9215
|
-
|
|
9227
|
+
pause_result=$?
|
|
9216
9228
|
rm -f "$loki_dir/PAUSE"
|
|
9217
9229
|
if [ "$pause_result" -eq 1 ]; then
|
|
9218
9230
|
# STOP was requested during pause
|
|
@@ -9228,8 +9240,9 @@ check_human_intervention() {
|
|
|
9228
9240
|
rm -f "$loki_dir/PAUSE_AT_CHECKPOINT"
|
|
9229
9241
|
notify_intervention_needed "Execution paused at checkpoint"
|
|
9230
9242
|
touch "$loki_dir/PAUSE"
|
|
9243
|
+
local pause_result
|
|
9231
9244
|
handle_pause
|
|
9232
|
-
|
|
9245
|
+
pause_result=$?
|
|
9233
9246
|
rm -f "$loki_dir/PAUSE"
|
|
9234
9247
|
if [ "$pause_result" -eq 1 ]; then
|
|
9235
9248
|
return 2
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
package/web-app/server.py
CHANGED
|
@@ -44,9 +44,20 @@ DIST_DIR = SCRIPT_DIR / "dist"
|
|
|
44
44
|
|
|
45
45
|
app = FastAPI(title="Purple Lab", docs_url=None, redoc_url=None)
|
|
46
46
|
|
|
47
|
+
_default_cors_origins = [
|
|
48
|
+
f"http://127.0.0.1:{PORT}",
|
|
49
|
+
f"http://localhost:{PORT}",
|
|
50
|
+
]
|
|
51
|
+
_cors_env = os.environ.get("PURPLE_LAB_CORS_ORIGINS", "")
|
|
52
|
+
_cors_origins = (
|
|
53
|
+
[o.strip() for o in _cors_env.split(",") if o.strip()]
|
|
54
|
+
if _cors_env
|
|
55
|
+
else _default_cors_origins
|
|
56
|
+
)
|
|
57
|
+
|
|
47
58
|
app.add_middleware(
|
|
48
59
|
CORSMiddleware,
|
|
49
|
-
allow_origins=
|
|
60
|
+
allow_origins=_cors_origins,
|
|
50
61
|
allow_methods=["*"],
|
|
51
62
|
allow_headers=["*"],
|
|
52
63
|
)
|
|
@@ -149,12 +160,25 @@ def _loki_dir() -> Path:
|
|
|
149
160
|
|
|
150
161
|
|
|
151
162
|
def _safe_resolve(base: Path, requested: str) -> Optional[Path]:
|
|
152
|
-
"""Resolve a path ensuring it stays within base (path traversal protection).
|
|
163
|
+
"""Resolve a path ensuring it stays within base (path traversal protection).
|
|
164
|
+
|
|
165
|
+
Uses os.path.commonpath to avoid the startswith prefix collision where
|
|
166
|
+
/tmp/proj would incorrectly pass a check against /tmp/projother.
|
|
167
|
+
Also rejects symlinks that escape the base directory.
|
|
168
|
+
"""
|
|
153
169
|
try:
|
|
154
170
|
resolved = (base / requested).resolve()
|
|
155
171
|
base_resolved = base.resolve()
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
# Ensure resolved is strictly inside base_resolved
|
|
173
|
+
resolved.relative_to(base_resolved)
|
|
174
|
+
# Reject if any component is a symlink pointing outside base
|
|
175
|
+
check = base_resolved
|
|
176
|
+
for part in resolved.relative_to(base_resolved).parts:
|
|
177
|
+
check = check / part
|
|
178
|
+
if check.is_symlink():
|
|
179
|
+
link_target = check.resolve()
|
|
180
|
+
link_target.relative_to(base_resolved) # raises ValueError if outside
|
|
181
|
+
return resolved
|
|
158
182
|
except (ValueError, OSError):
|
|
159
183
|
pass
|
|
160
184
|
return None
|