loki-mode 8.55.0 → 8.63.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.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/run.sh +157 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +49 -28
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/tools/index-codebase.py +19 -1
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 v8.
|
|
6
|
+
# Loki Mode v8.63.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -469,4 +469,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
469
469
|
|
|
470
470
|
---
|
|
471
471
|
|
|
472
|
-
**v8.
|
|
472
|
+
**v8.63.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
8.
|
|
1
|
+
8.63.0
|
package/autonomy/run.sh
CHANGED
|
@@ -21379,7 +21379,23 @@ except Exception:
|
|
|
21379
21379
|
# shellcheck disable=SC1090
|
|
21380
21380
|
. "${SCRIPT_DIR}/spec-interrogation.sh" 2>/dev/null || true
|
|
21381
21381
|
if type spec_interrogation_run &>/dev/null; then
|
|
21382
|
+
# TIMED. Startup was completely unmeasured: on a real run, 128
|
|
21383
|
+
# SECONDS elapsed between session_start and iteration_start -- over
|
|
21384
|
+
# two minutes in which the user sees nothing and no agent work has
|
|
21385
|
+
# begun. Nothing in .loki/events.jsonl accounted for any of it, so
|
|
21386
|
+
# the interval could not be attributed, let alone optimised.
|
|
21387
|
+
#
|
|
21388
|
+
# This step calls the provider, so it is the prime suspect for the
|
|
21389
|
+
# bulk of that window. Naming it turns "startup is slow" into a
|
|
21390
|
+
# number, the same way stage timings turned "the run is slow" into
|
|
21391
|
+
# "the agent call is 93% of wall clock".
|
|
21392
|
+
#
|
|
21393
|
+
# Uses the existing emit_stage_complete channel, so measure-run.sh
|
|
21394
|
+
# and every other consumer pick it up with no new plumbing.
|
|
21395
|
+
local _si_t0
|
|
21396
|
+
_si_t0=$(date +%s 2>/dev/null || echo 0)
|
|
21382
21397
|
spec_interrogation_run "$prd_path" || true
|
|
21398
|
+
emit_stage_complete "spec_interrogation" "pass" "$_si_t0" 2>/dev/null || true
|
|
21383
21399
|
fi
|
|
21384
21400
|
# #87: no-HITL fast-fail on an unresolved spec-INTERNAL contradiction.
|
|
21385
21401
|
# A contradiction (class=contradictory) is NEVER auto-acked (P2-4) and only
|
|
@@ -22301,6 +22317,11 @@ def process_stream():
|
|
|
22301
22317
|
# message. Stays False when partial messages are off (no stream_event lines).
|
|
22302
22318
|
streamed_text_blocks = False
|
|
22303
22319
|
|
|
22320
|
+
# Per-turn usage samples for the context-growth record (L1). Appended on
|
|
22321
|
+
# every assistant message; written once at the result event. Bounded below
|
|
22322
|
+
# so a pathological run cannot grow this without limit.
|
|
22323
|
+
_turn_usage = []
|
|
22324
|
+
|
|
22304
22325
|
for line in sys.stdin:
|
|
22305
22326
|
line = line.strip()
|
|
22306
22327
|
if not line:
|
|
@@ -22336,6 +22357,43 @@ def process_stream():
|
|
|
22336
22357
|
# Extract and print assistant text
|
|
22337
22358
|
message = data.get("message", {})
|
|
22338
22359
|
content = message.get("content", [])
|
|
22360
|
+
|
|
22361
|
+
# PER-TURN CONTEXT GROWTH (read-only instrumentation, L1).
|
|
22362
|
+
#
|
|
22363
|
+
# WHY. One measured iteration re-sent 10,651,759 cached-read
|
|
22364
|
+
# tokens to produce 34,729 output tokens -- a 307:1 ratio, in a
|
|
22365
|
+
# SINGLE provider call (one iteration_start, one
|
|
22366
|
+
# result-cost-1.json, so cross-iteration reuse is ruled out).
|
|
22367
|
+
# That call was 100% of measured stage time.
|
|
22368
|
+
#
|
|
22369
|
+
# The provider cache already saved us 10x ($31.96 -> $3.20 of a
|
|
22370
|
+
# $4.74 iteration). We are not missing a cache; the ORDER being
|
|
22371
|
+
# discounted is enormous, and cached reads are still 67% of the
|
|
22372
|
+
# bill. Those tokens are prefill the model must process serially
|
|
22373
|
+
# before emitting a character, so this is the only measured lever
|
|
22374
|
+
# that touches BOTH cost and the 744s.
|
|
22375
|
+
#
|
|
22376
|
+
# "the tool loop re-accumulates history" is INFERRED from the
|
|
22377
|
+
# ratio, not observed. Trimming context on an inference is how
|
|
22378
|
+
# you ship an agent that forgets what it already tried and redoes
|
|
22379
|
+
# the work -- raising iterations and costing more than it saves.
|
|
22380
|
+
# So this MEASURES per turn and trims nothing. The cut is a
|
|
22381
|
+
# separate decision, gated on iterations-to-done rather than on
|
|
22382
|
+
# a token count.
|
|
22383
|
+
try:
|
|
22384
|
+
_tu = (message.get("usage") or {})
|
|
22385
|
+
_tcr = _tu.get("cache_read_input_tokens")
|
|
22386
|
+
if isinstance(_tcr, int) and _tcr >= 0:
|
|
22387
|
+
_turn_usage.append({
|
|
22388
|
+
"turn": len(_turn_usage) + 1,
|
|
22389
|
+
"cache_read_tokens": _tcr,
|
|
22390
|
+
"input_tokens": _tu.get("input_tokens", 0) or 0,
|
|
22391
|
+
"output_tokens": _tu.get("output_tokens", 0) or 0,
|
|
22392
|
+
"cache_creation_tokens":
|
|
22393
|
+
_tu.get("cache_creation_input_tokens", 0) or 0,
|
|
22394
|
+
})
|
|
22395
|
+
except Exception:
|
|
22396
|
+
pass
|
|
22339
22397
|
for item in content:
|
|
22340
22398
|
if item.get("type") == "text":
|
|
22341
22399
|
text = item.get("text", "")
|
|
@@ -22488,6 +22546,44 @@ def process_stream():
|
|
|
22488
22546
|
"cache_read_tokens": _u.get("cache_read_input_tokens", 0),
|
|
22489
22547
|
"cache_creation_tokens": _u.get("cache_creation_input_tokens", 0),
|
|
22490
22548
|
}
|
|
22549
|
+
# CONTEXT-GROWTH RECORD (L1). Written whenever turns were
|
|
22550
|
+
# observed, independently of whether cost was reported --
|
|
22551
|
+
# the growth shape is the finding, and tying it to
|
|
22552
|
+
# total_cost_usd would lose it on every provider that does
|
|
22553
|
+
# not report dollars (codex reports tokens, never cost).
|
|
22554
|
+
if _turn_usage:
|
|
22555
|
+
try:
|
|
22556
|
+
os.makedirs(".loki/metrics", exist_ok=True)
|
|
22557
|
+
_first = _turn_usage[0]["cache_read_tokens"]
|
|
22558
|
+
_last = _turn_usage[-1]["cache_read_tokens"]
|
|
22559
|
+
_growth = {
|
|
22560
|
+
"iteration": _iter,
|
|
22561
|
+
"turns": len(_turn_usage),
|
|
22562
|
+
"first_turn_cache_read": _first,
|
|
22563
|
+
"last_turn_cache_read": _last,
|
|
22564
|
+
# The headline: how much bigger the context got
|
|
22565
|
+
# between the first and last turn of ONE call.
|
|
22566
|
+
"growth_factor": (round(_last / _first, 2)
|
|
22567
|
+
if _first > 0 else None),
|
|
22568
|
+
"total_cache_read": sum(
|
|
22569
|
+
t["cache_read_tokens"] for t in _turn_usage),
|
|
22570
|
+
"total_output": sum(
|
|
22571
|
+
t["output_tokens"] for t in _turn_usage),
|
|
22572
|
+
# Bounded sample: the shape is visible in the
|
|
22573
|
+
# first and last few turns, and an unbounded
|
|
22574
|
+
# array would make this file grow with the run.
|
|
22575
|
+
"sample": (_turn_usage[:5] + _turn_usage[-5:]
|
|
22576
|
+
if len(_turn_usage) > 10
|
|
22577
|
+
else _turn_usage),
|
|
22578
|
+
}
|
|
22579
|
+
_gp = ".loki/metrics/context-growth-" + str(_iter) + ".json"
|
|
22580
|
+
_gt = _gp + ".tmp"
|
|
22581
|
+
with open(_gt, "w") as _gf:
|
|
22582
|
+
json.dump(_growth, _gf)
|
|
22583
|
+
os.replace(_gt, _gp)
|
|
22584
|
+
except Exception:
|
|
22585
|
+
pass
|
|
22586
|
+
|
|
22491
22587
|
if _rec["total_cost_usd"] is not None:
|
|
22492
22588
|
os.makedirs(".loki/metrics", exist_ok=True)
|
|
22493
22589
|
_p = ".loki/metrics/result-cost-" + str(_iter) + ".json"
|
|
@@ -22635,6 +22731,33 @@ if __name__ == "__main__":
|
|
|
22635
22731
|
# costs zero extra subprocesses -- we pass the existing epoch through.
|
|
22636
22732
|
emit_stage_complete "agent" "$([ "$exit_code" -eq 0 ] 2>/dev/null && echo pass || echo fail)" "$start_time"
|
|
22637
22733
|
|
|
22734
|
+
# AGENT PROMPT SIZE. The call this brackets is 93% of a run's wall clock
|
|
22735
|
+
# (1814s of 1941s measured), and its INPUT was never measured -- every
|
|
22736
|
+
# reviewer logs its prompt bytes, the dominant call logged nothing.
|
|
22737
|
+
#
|
|
22738
|
+
# Prompt size is the input side of that 93% and one of the few levers we
|
|
22739
|
+
# actually control: we cannot make the provider faster, but we can send
|
|
22740
|
+
# it less. Without the number, "the prompt got bigger" is invisible
|
|
22741
|
+
# until it shows up as latency and cost with no attributable cause --
|
|
22742
|
+
# the same gap W1 closed for tokens.
|
|
22743
|
+
#
|
|
22744
|
+
# Costs one `wc -c` on a string already in memory: no subprocess for the
|
|
22745
|
+
# provider, no extra file read. Emitted on the existing event channel so
|
|
22746
|
+
# measure-run.sh and the receipt pick it up with no new plumbing.
|
|
22747
|
+
if [ -n "${prompt:-}" ]; then
|
|
22748
|
+
local _agent_prompt_bytes
|
|
22749
|
+
_agent_prompt_bytes=$(printf '%s' "$prompt" | wc -c 2>/dev/null | tr -d ' ')
|
|
22750
|
+
case "$_agent_prompt_bytes" in
|
|
22751
|
+
''|*[!0-9]*) ;; # unmeasurable -> emit nothing, never a zero
|
|
22752
|
+
*)
|
|
22753
|
+
emit_event_json "agent_prompt" \
|
|
22754
|
+
"bytes=$_agent_prompt_bytes" \
|
|
22755
|
+
"iteration=${ITERATION_COUNT:-0}" \
|
|
22756
|
+
"duration_s=$duration" 2>/dev/null || true
|
|
22757
|
+
;;
|
|
22758
|
+
esac
|
|
22759
|
+
fi
|
|
22760
|
+
|
|
22638
22761
|
# TIME TO FIRST ARTIFACT. The companion to seconds_to_first_preview, for
|
|
22639
22762
|
# the case that has no preview at all.
|
|
22640
22763
|
#
|
|
@@ -22959,6 +23082,14 @@ if __name__ == "__main__":
|
|
|
22959
23082
|
local tc_count
|
|
22960
23083
|
tc_count=$(track_gate_failure "test_coverage")
|
|
22961
23084
|
gate_failures="${gate_failures}test_coverage,"
|
|
23085
|
+
# Fourth dead branch, found by deriving the handled-gate set
|
|
23086
|
+
# from the writer instead of hardcoding it: test_coverage
|
|
23087
|
+
# maps to quality/test-results.json and had no caller either.
|
|
23088
|
+
if [ "$(gate_failure_disposition "$tc_count")" != "block" ]; then
|
|
23089
|
+
local _tc_thresh="$GATE_CLEAR_LIMIT"
|
|
23090
|
+
[ "$GATE_ESCALATE_LIMIT" -lt "$_tc_thresh" ] && _tc_thresh="$GATE_ESCALATE_LIMIT"
|
|
23091
|
+
write_gate_escalation_guidance "test_coverage" "$tc_count" "$_tc_thresh" || true
|
|
23092
|
+
fi
|
|
22962
23093
|
# P0-1 Fix A: distinguish a coverage-only block (tests passed,
|
|
22963
23094
|
# enforced coverage below threshold) from a genuine tests-red
|
|
22964
23095
|
# block in the log so the operator is not misled.
|
|
@@ -22994,6 +23125,23 @@ if __name__ == "__main__":
|
|
|
22994
23125
|
mk_count=$(track_gate_failure "mock_integrity")
|
|
22995
23126
|
gate_failures="${gate_failures}mock_integrity,"
|
|
22996
23127
|
log_warn "Mock integrity gate FAILED ($mk_count consecutive) - CRITICAL/HIGH mock problems"
|
|
23128
|
+
# Escalation guidance was DEAD for this gate.
|
|
23129
|
+
# write_gate_escalation_guidance already handles
|
|
23130
|
+
# mock_integrity, mutation_integrity and test_coverage by
|
|
23131
|
+
# name -- and only code_review ever called it, so those
|
|
23132
|
+
# branches could never run.
|
|
23133
|
+
#
|
|
23134
|
+
# Measured: on a real run mock_integrity failed THREE
|
|
23135
|
+
# times (the most of any gate) and
|
|
23136
|
+
# .loki/signals/GATE_ESCALATION.json was never written.
|
|
23137
|
+
# The agent was told the gate failed and never handed the
|
|
23138
|
+
# findings file that says WHY, which is the 56%
|
|
23139
|
+
# "did not attempt to recover" failure shape.
|
|
23140
|
+
if [ "$(gate_failure_disposition "$mk_count")" != "block" ]; then
|
|
23141
|
+
local _mk_thresh="$GATE_CLEAR_LIMIT"
|
|
23142
|
+
[ "$GATE_ESCALATE_LIMIT" -lt "$_mk_thresh" ] && _mk_thresh="$GATE_ESCALATE_LIMIT"
|
|
23143
|
+
write_gate_escalation_guidance "mock_integrity" "$mk_count" "$_mk_thresh" || true
|
|
23144
|
+
fi
|
|
22997
23145
|
# F0, third gate. Measured on the v8.49.0 FireLater run:
|
|
22998
23146
|
# mock_integrity failed 3 times -- MORE than any other
|
|
22999
23147
|
# gate -- and was not wired to the stuck check, so an
|
|
@@ -23030,6 +23178,15 @@ if __name__ == "__main__":
|
|
|
23030
23178
|
mt_count=$(track_gate_failure "mutation_integrity")
|
|
23031
23179
|
gate_failures="${gate_failures}mutation_integrity,"
|
|
23032
23180
|
log_warn "Mutation integrity gate FAILED ($mt_count consecutive) - HIGH test-fitting detected"
|
|
23181
|
+
# Same dead-branch fix as mock_integrity above:
|
|
23182
|
+
# write_gate_escalation_guidance maps mutation_integrity to
|
|
23183
|
+
# mutation-findings.txt and nothing ever called it with that
|
|
23184
|
+
# gate name, so the mapping could never fire.
|
|
23185
|
+
if [ "$(gate_failure_disposition "$mt_count")" != "block" ]; then
|
|
23186
|
+
local _mt_thresh="$GATE_CLEAR_LIMIT"
|
|
23187
|
+
[ "$GATE_ESCALATE_LIMIT" -lt "$_mt_thresh" ] && _mt_thresh="$GATE_ESCALATE_LIMIT"
|
|
23188
|
+
write_gate_escalation_guidance "mutation_integrity" "$mt_count" "$_mt_thresh" || true
|
|
23189
|
+
fi
|
|
23033
23190
|
# F0: an unchanging cause means the next iteration reaches
|
|
23034
23191
|
# the same verdict. FireLater burned 3 iterations here on a
|
|
23035
23192
|
# detector that was never packaged, failing in 0-1s each
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -559,6 +559,40 @@ start_time = datetime.now(timezone.utc)
|
|
|
559
559
|
_dashboard_start_time = time.time()
|
|
560
560
|
|
|
561
561
|
|
|
562
|
+
def _registry_run_alive(loki_dir: _Path) -> bool:
|
|
563
|
+
"""True when .loki/pids/ holds a LIVE wrapper/runner process.
|
|
564
|
+
|
|
565
|
+
Third liveness source for a CLI-started background run, which writes
|
|
566
|
+
neither loki.pid nor session.json (run.sh only UPDATES session.json when it
|
|
567
|
+
already exists). Both of those checks therefore fail for `loki start` and
|
|
568
|
+
the run falls through to "stopped" while it is actively building.
|
|
569
|
+
|
|
570
|
+
The kind filter is load-bearing: .loki/pids/ also registers the dashboard
|
|
571
|
+
itself, the status-monitor and the resource-monitor, none of which carry a
|
|
572
|
+
"kind" key. Accepting any live pid here would let the dashboard's own
|
|
573
|
+
process prove the run is alive, turning a false-stopped into a permanent
|
|
574
|
+
false-running. Keep it to wrapper/runner.
|
|
575
|
+
|
|
576
|
+
Liveness is proven with os.kill(pid, 0), never by the file's presence -- a
|
|
577
|
+
stale entry from a crashed run must NOT read as alive.
|
|
578
|
+
"""
|
|
579
|
+
try:
|
|
580
|
+
for _entry in (loki_dir / "pids").glob("*.json"):
|
|
581
|
+
_rec = _safe_json_read(_entry, {})
|
|
582
|
+
if not isinstance(_rec, dict):
|
|
583
|
+
continue
|
|
584
|
+
if _rec.get("kind") not in ("wrapper", "runner"):
|
|
585
|
+
continue
|
|
586
|
+
try:
|
|
587
|
+
os.kill(int(_rec.get("pid", 0)), 0)
|
|
588
|
+
except (ValueError, TypeError, OSError, ProcessLookupError):
|
|
589
|
+
continue
|
|
590
|
+
return True
|
|
591
|
+
except OSError:
|
|
592
|
+
pass
|
|
593
|
+
return False
|
|
594
|
+
|
|
595
|
+
|
|
562
596
|
async def _push_loki_state_loop() -> None:
|
|
563
597
|
"""Background loop: push .loki/ state changes to all WebSocket clients.
|
|
564
598
|
|
|
@@ -679,35 +713,15 @@ async def _push_loki_state_loop() -> None:
|
|
|
679
713
|
pass
|
|
680
714
|
|
|
681
715
|
# Third source: the .loki/pids/ registry, which a
|
|
682
|
-
# CLI-started background run DOES write.
|
|
683
|
-
#
|
|
684
|
-
#
|
|
685
|
-
#
|
|
686
|
-
#
|
|
687
|
-
#
|
|
688
|
-
#
|
|
689
|
-
# said BUILDING and iterations were advancing while the
|
|
690
|
-
# dashboard showed STOPPED with 0 agents.
|
|
691
|
-
#
|
|
692
|
-
# Liveness is proven with os.kill(pid, 0), never by the
|
|
693
|
-
# file's presence -- a stale entry from a crashed run
|
|
694
|
-
# must NOT read as alive, which is the same
|
|
695
|
-
# anti-stale rule BUG-NEW-006 established above.
|
|
716
|
+
# CLI-started background run DOES write. Shared with
|
|
717
|
+
# /api/status via _registry_run_alive so both live
|
|
718
|
+
# surfaces agree -- they previously did not: on a real
|
|
719
|
+
# `loki start` build this stream broadcast "running"
|
|
720
|
+
# while /api/status returned "stopped" for the SAME run
|
|
721
|
+
# in the same second, because only this copy had the
|
|
722
|
+
# pids/ source.
|
|
696
723
|
if not _pid_alive:
|
|
697
|
-
|
|
698
|
-
_pid_dir = loki_dir / "pids"
|
|
699
|
-
for _entry in _pid_dir.glob("*.json"):
|
|
700
|
-
_rec = _safe_json_read(_entry, {})
|
|
701
|
-
if _rec.get("kind") not in ("wrapper", "runner"):
|
|
702
|
-
continue
|
|
703
|
-
try:
|
|
704
|
-
os.kill(int(_rec.get("pid", 0)), 0)
|
|
705
|
-
except (ValueError, OSError, ProcessLookupError):
|
|
706
|
-
continue
|
|
707
|
-
_pid_alive = True
|
|
708
|
-
break
|
|
709
|
-
except OSError:
|
|
710
|
-
pass
|
|
724
|
+
_pid_alive = _registry_run_alive(loki_dir)
|
|
711
725
|
|
|
712
726
|
status_str = raw.get("mode", "autonomous")
|
|
713
727
|
# Control files are the AUTHORITY, and they are checked
|
|
@@ -1438,6 +1452,13 @@ async def get_status() -> StatusResponse:
|
|
|
1438
1452
|
if not mode:
|
|
1439
1453
|
mode = "autonomous"
|
|
1440
1454
|
|
|
1455
|
+
# Third source: .loki/pids/ registry (see _registry_run_alive). A
|
|
1456
|
+
# CLI-started background run writes neither loki.pid nor session.json, so
|
|
1457
|
+
# both checks above miss it and a healthy build reported "stopped" here
|
|
1458
|
+
# while the WS stream -- which already had this source -- said "running".
|
|
1459
|
+
if not running:
|
|
1460
|
+
running = _registry_run_alive(loki_dir)
|
|
1461
|
+
|
|
1441
1462
|
# Determine status string
|
|
1442
1463
|
if not running:
|
|
1443
1464
|
status = "stopped"
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var h_=Object.create;var{getPrototypeOf:v_,defineProperty:rK,getOwnPropertyNames:g_}=Object;var m_=Object.prototype.hasOwnProperty;function u_(Z){return this[Z]}var p_,d_,c_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?p_??=new WeakMap:d_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?h_(v_(Z)):{};let K=X||!Z||!Z.__esModule?rK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of g_(Z))if(!m_.call(K,$))rK(K,$,{get:u_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var qQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var l_=(Z)=>Z;function i_(Z,X){this[Z]=l_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)rK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:i_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var kO={};l0(kO,{lokiDir:()=>j0,homeLokiDir:()=>P4,findRepoRootForVersion:()=>eK,REPO_ROOT:()=>r0});import{resolve as n7,dirname as tK}from"path";import{fileURLToPath as a_}from"url";import{existsSync as GQ}from"fs";import{homedir as s_}from"os";function n_(){let Z=RO;for(let X=0;X<6;X++){if(GQ(n7(Z,"VERSION"))&&GQ(n7(Z,"autonomy/run.sh")))return Z;let Q=tK(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function eK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(GQ(n7(X,"VERSION"))&&GQ(n7(X,"autonomy/run.sh")))return X;let Y=tK(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function P4(){return n7(s_(),".loki")}var RO,r0;var G8=p(()=>{RO=tK(a_(import.meta.url));r0=n_()});import{readFileSync as o_}from"fs";import{resolve as r_,dirname as t_}from"path";import{fileURLToPath as e_}from"url";function f3(){if(h5!==null)return h5;let Z="8.
|
|
2
|
+
var h_=Object.create;var{getPrototypeOf:v_,defineProperty:rK,getOwnPropertyNames:g_}=Object;var m_=Object.prototype.hasOwnProperty;function u_(Z){return this[Z]}var p_,d_,c_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?p_??=new WeakMap:d_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?h_(v_(Z)):{};let K=X||!Z||!Z.__esModule?rK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of g_(Z))if(!m_.call(K,$))rK(K,$,{get:u_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var qQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var l_=(Z)=>Z;function i_(Z,X){this[Z]=l_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)rK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:i_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var kO={};l0(kO,{lokiDir:()=>j0,homeLokiDir:()=>P4,findRepoRootForVersion:()=>eK,REPO_ROOT:()=>r0});import{resolve as n7,dirname as tK}from"path";import{fileURLToPath as a_}from"url";import{existsSync as GQ}from"fs";import{homedir as s_}from"os";function n_(){let Z=RO;for(let X=0;X<6;X++){if(GQ(n7(Z,"VERSION"))&&GQ(n7(Z,"autonomy/run.sh")))return Z;let Q=tK(Z);if(Q===Z)break;Z=Q}return n7(RO,"..","..","..")}function eK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(GQ(n7(X,"VERSION"))&&GQ(n7(X,"autonomy/run.sh")))return X;let Y=tK(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function P4(){return n7(s_(),".loki")}var RO,r0;var G8=p(()=>{RO=tK(a_(import.meta.url));r0=n_()});import{readFileSync as o_}from"fs";import{resolve as r_,dirname as t_}from"path";import{fileURLToPath as e_}from"url";function f3(){if(h5!==null)return h5;let Z="8.63.0";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=t_(e_(import.meta.url)),Q=eK(X);h5=o_(r_(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var HQ=p(()=>{G8()});var bO={};l0(bO,{runOrThrow:()=>Of,run:()=>E0,readStreamCapped:()=>UQ,commandVersion:()=>Af,commandExists:()=>X9,ShellError:()=>Z$,MAX_STDOUT_BYTES:()=>yO});async function UQ(Z,X=yO){let Q=Z.getReader(),Y=new TextDecoder,J="",z=0;try{while(z<X){let{done:K,value:$}=await Q.read();if(K)break;if(!$)continue;if(z+=$.byteLength,z>X){let W=$.byteLength-(z-X);J+=Y.decode($.subarray(0,W),{stream:!0});break}J+=Y.decode($,{stream:!0})}J+=Y.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return J}async function E0(Z,X={}){let Q=Bun.spawn({cmd:[...Z],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),Y,J;if(X.timeoutMs&&X.timeoutMs>0)Y=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}J=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[z,K,$]=await Promise.all([UQ(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:z,stderr:K,exitCode:$}}finally{if(Y)clearTimeout(Y);if(J)clearTimeout(J)}}async function Of(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Z$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=Lf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Lf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Af(Z,X="--version"){if(!await X9(Z))return null;let Y=await E0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var yO=16777216,Z$;var x9=p(()=>{Z$=class Z$ extends Error{message;exitCode;stdout;stderr;constructor(Z,X,Q,Y){super(Z);this.message=Z;this.exitCode=X;this.stdout=Q;this.stderr=Y;this.name="ShellError"}}});function o7(Z){return jf?"":Z}var jf,L0,k8,p0,ZV0,i0,H8,Q9,v;var S6=p(()=>{jf=(process.env.NO_COLOR??"").length>0;L0=o7("\x1B[0;31m"),k8=o7("\x1B[0;32m"),p0=o7("\x1B[1;33m"),ZV0=o7("\x1B[0;34m"),i0=o7("\x1B[0;36m"),H8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),v=o7("\x1B[0m")});import{existsSync as xf}from"fs";async function E7(){if(k4!==void 0)return k4;let Z="/opt/homebrew/bin/python3.12";if(xf(Z))return k4=Z,Z;let X=await X9("python3.12");if(X)return k4=X,X;let Q=await X9("python3");return k4=Q,Q}async function Y7(Z,X={}){let Q=await E7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return E0([Q,"-c",Z],X)}var k4;var r7=p(()=>{x9()});var ZL={};l0(ZL,{runStatus:()=>Yh});import{existsSync as Y9,readFileSync as v3,readdirSync as iO,statSync as aO}from"fs";import{resolve as h8,basename as sf}from"path";import{homedir as nf}from"os";function sO(Z){let X=Math.trunc(Z);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function nO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*NQ/X);if(J>NQ)J=NQ;let z=NQ-J,K=k8;if(Y>=80)K=L0;else if(Y>=50)K=p0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),W=sO(Z),V=sO(X);return` ${H8}${Q}${v} ${K}[${$}]${v} ${Y}% (${W} / ${V})`}async function rf(){if(await X9("jq"))return!0;return process.stdout.write(`${L0}Error: jq is required but not installed.${v}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -1227,4 +1227,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
1227
1227
|
`),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (b_(),y_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
|
|
1228
1228
|
`),process.stderr.write(__),2}}cO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var fW0=await _W0(Bun.argv.slice(2));process.exit(fW0);
|
|
1229
1229
|
|
|
1230
|
-
//# debugId=
|
|
1230
|
+
//# debugId=389F590913B3AC8264756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.63.0",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "8.
|
|
5
|
+
"version": "8.63.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|
package/tools/index-codebase.py
CHANGED
|
@@ -320,6 +320,19 @@ def chunk_markdown_file(filepath: Path) -> list[dict]:
|
|
|
320
320
|
return chunks
|
|
321
321
|
|
|
322
322
|
|
|
323
|
+
def _is_skipped(p: Path) -> bool:
|
|
324
|
+
"""True when a path lies inside a skipped directory.
|
|
325
|
+
|
|
326
|
+
Compares components of the path RELATIVE to PROJECT_ROOT. An absolute-path
|
|
327
|
+
substring test makes indexing depend on where the repo happens to live.
|
|
328
|
+
"""
|
|
329
|
+
try:
|
|
330
|
+
rel = p.resolve().relative_to(PROJECT_ROOT)
|
|
331
|
+
except ValueError:
|
|
332
|
+
return False
|
|
333
|
+
return any(part in SKIP_DIRS for part in rel.parts)
|
|
334
|
+
|
|
335
|
+
|
|
323
336
|
def collect_files() -> list[tuple[Path, str]]:
|
|
324
337
|
"""Collect all files to index with their type."""
|
|
325
338
|
files = []
|
|
@@ -335,7 +348,12 @@ def collect_files() -> list[tuple[Path, str]]:
|
|
|
335
348
|
for p in sorted(PROJECT_ROOT.glob(glob_pattern)):
|
|
336
349
|
if p.name.startswith("__"):
|
|
337
350
|
continue
|
|
338
|
-
|
|
351
|
+
# Match path COMPONENTS relative to the project root, never a
|
|
352
|
+
# substring of the absolute path. `.claude` in SKIP_DIRS plus a
|
|
353
|
+
# checkout living under .../.claude/worktrees/<name>/ meant EVERY
|
|
354
|
+
# python file matched and the index came back empty -- silently, as
|
|
355
|
+
# a search that returns nothing rather than an error.
|
|
356
|
+
if _is_skipped(p):
|
|
339
357
|
continue
|
|
340
358
|
files.append((p, "python"))
|
|
341
359
|
|