loki-mode 8.4.0 → 8.5.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 +1 -1
- package/VERSION +1 -1
- package/autonomy/lib/iteration_attribution.py +181 -0
- package/autonomy/lib/proof-generator.py +31 -0
- package/autonomy/lib/silence_report.py +163 -0
- package/dashboard/__init__.py +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ _The free, source-available autonomous coding agent by [Autonomi](https://www.au
|
|
|
15
15
|
|
|
16
16
|
[Website](https://www.autonomi.dev/) | [Documentation](wiki/Home.md) | [Installation](docs/INSTALLATION.md) | [Changelog](CHANGELOG.md) | [Purple Lab -- deprecated v7.44.0](#purple-lab)
|
|
17
17
|
|
|
18
|
-
**Current release: v8.
|
|
18
|
+
**Current release: v8.5.1**
|
|
19
19
|
|
|
20
20
|
</div>
|
|
21
21
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
8.
|
|
1
|
+
8.5.1
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Explain WHY a run took the iterations it took.
|
|
3
|
+
|
|
4
|
+
WHY THIS EXISTS. Iterations are the direct multiplier on both wall clock and
|
|
5
|
+
cost, and the Evidence Receipt reports only `{count, succeeded, failed}`. A user
|
|
6
|
+
who sees "6 iterations" cannot tell real work from a gate false-positive that
|
|
7
|
+
forced five redos. Neither can we, which is worse: it means we cannot tell
|
|
8
|
+
whether an expensive run was the model being slow or the harness being wrong.
|
|
9
|
+
|
|
10
|
+
That distinction has already bitten this project. A measured run had the agent
|
|
11
|
+
claim done on EVERY iteration while a mock-integrity false positive blocked all
|
|
12
|
+
six, and a start-sha bug made the council see a permanently empty diff so it
|
|
13
|
+
could never vote done. Both were HARNESS defects billed to the user as model
|
|
14
|
+
cost. Removing a false positive raises accuracy AND cuts iterations, which is
|
|
15
|
+
the rare change that improves both axes at once -- but only if you can see it.
|
|
16
|
+
|
|
17
|
+
WHAT THIS DOES NOT DO. It does not guess. Every field is derived from records
|
|
18
|
+
the engine already writes, and anything not recorded is reported as unknown
|
|
19
|
+
rather than inferred. An attribution that invents a reason would be worse than
|
|
20
|
+
no attribution, because it would send someone optimising the wrong thing.
|
|
21
|
+
|
|
22
|
+
Sources, all already written by the engine:
|
|
23
|
+
.loki/metrics/efficiency/iteration-N.json status, duration_ms, cost_usd, model
|
|
24
|
+
.loki/events.jsonl iteration_complete events
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
python3 autonomy/lib/iteration_attribution.py [--loki-dir .loki] [--json]
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import argparse
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
import sys
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _read_json(path, default=None):
|
|
39
|
+
try:
|
|
40
|
+
with open(path, encoding="utf-8") as fh:
|
|
41
|
+
return json.load(fh)
|
|
42
|
+
except Exception:
|
|
43
|
+
return default
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _iteration_records(loki_dir):
|
|
47
|
+
"""Return per-iteration records sorted by iteration number.
|
|
48
|
+
|
|
49
|
+
Reads only what the engine already wrote. A malformed or partial record is
|
|
50
|
+
SKIPPED rather than defaulted, because a zero-cost placeholder would silently
|
|
51
|
+
understate the total and make the attribution wrong in the direction that
|
|
52
|
+
flatters us.
|
|
53
|
+
"""
|
|
54
|
+
eff_dir = os.path.join(loki_dir, "metrics", "efficiency")
|
|
55
|
+
out = []
|
|
56
|
+
try:
|
|
57
|
+
names = sorted(os.listdir(eff_dir))
|
|
58
|
+
except OSError:
|
|
59
|
+
return out
|
|
60
|
+
for name in names:
|
|
61
|
+
if not (name.startswith("iteration-") and name.endswith(".json")):
|
|
62
|
+
continue
|
|
63
|
+
rec = _read_json(os.path.join(eff_dir, name))
|
|
64
|
+
if not isinstance(rec, dict) or "iteration" not in rec:
|
|
65
|
+
continue
|
|
66
|
+
out.append(rec)
|
|
67
|
+
out.sort(key=lambda r: r.get("iteration", 0))
|
|
68
|
+
return out
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def attribute(loki_dir):
|
|
72
|
+
"""Split iteration cost and time into PROGRESS versus REWORK.
|
|
73
|
+
|
|
74
|
+
The definition is deliberately conservative and stated plainly, because a
|
|
75
|
+
generous definition of "progress" is how a tool flatters itself:
|
|
76
|
+
|
|
77
|
+
progress an iteration that COMPLETED (exit 0)
|
|
78
|
+
rework an iteration that FAILED and therefore had to be repeated
|
|
79
|
+
unknown an iteration whose status was never recorded
|
|
80
|
+
|
|
81
|
+
This is a floor on rework, not a ceiling. An iteration that "completed" but
|
|
82
|
+
was forced to run again by a false-positive gate is counted as progress here,
|
|
83
|
+
because the engine does not currently record the blocking gate per iteration.
|
|
84
|
+
Saying so is the point: the number is honest about what it cannot see.
|
|
85
|
+
"""
|
|
86
|
+
recs = _iteration_records(loki_dir)
|
|
87
|
+
|
|
88
|
+
summary = {
|
|
89
|
+
"iterations": len(recs),
|
|
90
|
+
"progress": {"count": 0, "cost_usd": 0.0, "duration_ms": 0},
|
|
91
|
+
"rework": {"count": 0, "cost_usd": 0.0, "duration_ms": 0},
|
|
92
|
+
"unknown": {"count": 0, "cost_usd": 0.0, "duration_ms": 0},
|
|
93
|
+
"cost_recorded": False,
|
|
94
|
+
"notes": [],
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for r in recs:
|
|
98
|
+
status = str(r.get("status", "")).strip().lower()
|
|
99
|
+
if status == "completed":
|
|
100
|
+
bucket = "progress"
|
|
101
|
+
elif status == "failed":
|
|
102
|
+
bucket = "rework"
|
|
103
|
+
else:
|
|
104
|
+
bucket = "unknown"
|
|
105
|
+
summary[bucket]["count"] += 1
|
|
106
|
+
cost = r.get("cost_usd")
|
|
107
|
+
if isinstance(cost, (int, float)):
|
|
108
|
+
summary[bucket]["cost_usd"] += float(cost)
|
|
109
|
+
if cost > 0:
|
|
110
|
+
summary["cost_recorded"] = True
|
|
111
|
+
dur = r.get("duration_ms")
|
|
112
|
+
if isinstance(dur, (int, float)) and dur >= 0:
|
|
113
|
+
summary[bucket]["duration_ms"] += int(dur)
|
|
114
|
+
|
|
115
|
+
for b in ("progress", "rework", "unknown"):
|
|
116
|
+
summary[b]["cost_usd"] = round(summary[b]["cost_usd"], 4)
|
|
117
|
+
|
|
118
|
+
total_cost = sum(summary[b]["cost_usd"] for b in ("progress", "rework", "unknown"))
|
|
119
|
+
summary["total_cost_usd"] = round(total_cost, 4) if summary["cost_recorded"] else None
|
|
120
|
+
|
|
121
|
+
if not recs:
|
|
122
|
+
summary["notes"].append(
|
|
123
|
+
"no efficiency records: this run predates the recorder, or no iteration completed"
|
|
124
|
+
)
|
|
125
|
+
if not summary["cost_recorded"]:
|
|
126
|
+
# Distinguishing "not recorded" from a genuine $0.00 is the same honesty
|
|
127
|
+
# property the Evidence Receipt depends on. A skeptic reading $0.00
|
|
128
|
+
# concludes the artifact is fake.
|
|
129
|
+
summary["notes"].append(
|
|
130
|
+
"cost not recorded for any iteration (reported as null, not as $0.00)"
|
|
131
|
+
)
|
|
132
|
+
if summary["rework"]["count"] and summary["cost_recorded"]:
|
|
133
|
+
share = summary["rework"]["cost_usd"] / total_cost if total_cost else 0.0
|
|
134
|
+
summary["rework_cost_share"] = round(share, 4)
|
|
135
|
+
summary["notes"].append(
|
|
136
|
+
f"{summary['rework']['count']} of {len(recs)} iterations failed and were "
|
|
137
|
+
f"repeated, costing {share:.0%} of the run"
|
|
138
|
+
)
|
|
139
|
+
summary["notes"].append(
|
|
140
|
+
"rework is a FLOOR: an iteration that completed but was forced to repeat by a "
|
|
141
|
+
"gate is counted as progress, because the blocking gate is not recorded per "
|
|
142
|
+
"iteration"
|
|
143
|
+
)
|
|
144
|
+
return summary
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _render(s):
|
|
148
|
+
lines = ["Iteration attribution", "====================", ""]
|
|
149
|
+
lines.append(f"Iterations: {s['iterations']}")
|
|
150
|
+
lines.append(f" progress: {s['progress']['count']}")
|
|
151
|
+
lines.append(f" rework: {s['rework']['count']}")
|
|
152
|
+
if s["unknown"]["count"]:
|
|
153
|
+
lines.append(f" unknown: {s['unknown']['count']}")
|
|
154
|
+
if s["total_cost_usd"] is None:
|
|
155
|
+
lines.append("Cost: not recorded")
|
|
156
|
+
else:
|
|
157
|
+
lines.append(f"Cost: ${s['total_cost_usd']}")
|
|
158
|
+
lines.append(f" progress: ${s['progress']['cost_usd']}")
|
|
159
|
+
lines.append(f" rework: ${s['rework']['cost_usd']}")
|
|
160
|
+
lines.append("")
|
|
161
|
+
for n in s["notes"]:
|
|
162
|
+
lines.append(f"note: {n}")
|
|
163
|
+
return "\n".join(lines)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main():
|
|
167
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
168
|
+
ap.add_argument("--loki-dir", default=".loki")
|
|
169
|
+
ap.add_argument("--json", action="store_true")
|
|
170
|
+
args = ap.parse_args()
|
|
171
|
+
|
|
172
|
+
s = attribute(args.loki_dir)
|
|
173
|
+
if args.json:
|
|
174
|
+
print(json.dumps(s, indent=2))
|
|
175
|
+
else:
|
|
176
|
+
print(_render(s))
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__":
|
|
181
|
+
sys.exit(main())
|
|
@@ -1081,6 +1081,37 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
|
|
|
1081
1081
|
|
|
1082
1082
|
files_changed, diffs, diff_base_sha = _git_diffstat(target_dir, args.include_diffs)
|
|
1083
1083
|
iterations = _collect_iterations(loki_dir)
|
|
1084
|
+
# Attribute iterations to PROGRESS vs REWORK. The receipt previously reported
|
|
1085
|
+
# only {count, succeeded, failed}, so a user seeing "6 iterations" could not
|
|
1086
|
+
# tell real work from a gate false-positive that forced five redos -- and
|
|
1087
|
+
# neither could we, which is worse, because it hides whether an expensive run
|
|
1088
|
+
# was a slow model or our own harness being wrong. Measured here: an agent
|
|
1089
|
+
# once claimed done on EVERY iteration while a mock-integrity false positive
|
|
1090
|
+
# blocked all six.
|
|
1091
|
+
#
|
|
1092
|
+
# Deterministic and derived only from records the engine already writes, so
|
|
1093
|
+
# this belongs with the FACTS, not the AI assessments. Failure to import or
|
|
1094
|
+
# attribute leaves iterations untouched rather than guessing: a fabricated
|
|
1095
|
+
# attribution would send someone optimising the wrong thing.
|
|
1096
|
+
try:
|
|
1097
|
+
from iteration_attribution import attribute as _attribute_iterations
|
|
1098
|
+
_attr = _attribute_iterations(loki_dir)
|
|
1099
|
+
if _attr.get("iterations"):
|
|
1100
|
+
iterations["attribution"] = {
|
|
1101
|
+
"progress": _attr["progress"],
|
|
1102
|
+
"rework": _attr["rework"],
|
|
1103
|
+
"rework_cost_share": _attr.get("rework_cost_share"),
|
|
1104
|
+
# Stated in the artifact, not just the tool: rework is a FLOOR.
|
|
1105
|
+
# An iteration that completed but was forced to repeat by a gate
|
|
1106
|
+
# counts as progress, because the blocking gate is not recorded
|
|
1107
|
+
# per iteration. Overstating certainty here would be worse than
|
|
1108
|
+
# omitting the split.
|
|
1109
|
+
"basis": "rework counts FAILED iterations only; a completed "
|
|
1110
|
+
"iteration forced to repeat by a gate is counted as "
|
|
1111
|
+
"progress, so rework is a floor",
|
|
1112
|
+
}
|
|
1113
|
+
except Exception:
|
|
1114
|
+
pass
|
|
1084
1115
|
spec = _collect_spec(loki_dir, target_dir)
|
|
1085
1116
|
council = _collect_council(loki_dir)
|
|
1086
1117
|
quality_gates = _collect_quality_gates(loki_dir)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Find the longest stretches where a build told the user nothing.
|
|
3
|
+
|
|
4
|
+
WHY THIS EXISTS. The loudest complaints about agents in this category are not
|
|
5
|
+
"it was wrong", they are "it went quiet and I could not tell if it was working":
|
|
6
|
+
opencode#11112 "always stuck at Preparing write" (76 comments), continue#7143
|
|
7
|
+
"not making changes to code", continue#5696 "agent does not execute functions".
|
|
8
|
+
In each case the agent may well be working. The user cannot tell, so they kill
|
|
9
|
+
it or leave.
|
|
10
|
+
|
|
11
|
+
Wall-clock total is the wrong metric for that experience. A ten-minute build
|
|
12
|
+
that reports something every twenty seconds feels fast; a four-minute build that
|
|
13
|
+
goes silent for three of them feels broken. What matters is the LONGEST GAP.
|
|
14
|
+
|
|
15
|
+
This reads the events the engine already writes and reports the worst silences,
|
|
16
|
+
so the number can be driven down instead of guessed at.
|
|
17
|
+
|
|
18
|
+
HOW TO MEASURE IT HONESTLY, learned the hard way here:
|
|
19
|
+
- Read wall clock from the engine's own event stream, never from `ps etime` on
|
|
20
|
+
a supervising process. A hung harness once reported 39 minutes for a 20
|
|
21
|
+
minute run.
|
|
22
|
+
- Separate IDLE time between sessions from SILENCE during a build. A gap
|
|
23
|
+
between two `session_resume` events is a human having lunch, not a defect,
|
|
24
|
+
and counting it makes the report useless.
|
|
25
|
+
- Report the event on each side of a gap. "Silent for 214s" is not actionable;
|
|
26
|
+
"silent for 214s after code_review_start" names the step to fix.
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
python3 autonomy/lib/silence_report.py [--events .loki/events.jsonl]
|
|
30
|
+
[--threshold 30] [--json]
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import argparse
|
|
36
|
+
import json
|
|
37
|
+
import sys
|
|
38
|
+
from datetime import datetime
|
|
39
|
+
|
|
40
|
+
# Events that mark a session boundary rather than build progress. A gap
|
|
41
|
+
# alongside one of these is a human being away, not the engine being silent.
|
|
42
|
+
_BOUNDARY = {
|
|
43
|
+
"session_resume",
|
|
44
|
+
"session_start",
|
|
45
|
+
"claude_hook_sessionstart",
|
|
46
|
+
"cli_command_deprecated",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse(path):
|
|
51
|
+
"""Yield (datetime, event_type). Malformed lines are skipped, not defaulted.
|
|
52
|
+
|
|
53
|
+
A line we cannot parse must not become a synthetic timestamp: that would
|
|
54
|
+
invent a gap or hide one, and either way the report stops being evidence.
|
|
55
|
+
"""
|
|
56
|
+
out = []
|
|
57
|
+
try:
|
|
58
|
+
fh = open(path, encoding="utf-8", errors="replace")
|
|
59
|
+
except OSError:
|
|
60
|
+
return out
|
|
61
|
+
with fh:
|
|
62
|
+
for line in fh:
|
|
63
|
+
line = line.strip()
|
|
64
|
+
if not line:
|
|
65
|
+
continue
|
|
66
|
+
try:
|
|
67
|
+
d = json.loads(line)
|
|
68
|
+
except Exception:
|
|
69
|
+
continue
|
|
70
|
+
t = d.get("timestamp") or d.get("ts") or d.get("at")
|
|
71
|
+
if not isinstance(t, str) or not t:
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
dt = datetime.fromisoformat(t.replace("Z", "+00:00"))
|
|
75
|
+
except Exception:
|
|
76
|
+
continue
|
|
77
|
+
kind = d.get("type") or d.get("event") or "unknown"
|
|
78
|
+
out.append((dt, str(kind)))
|
|
79
|
+
out.sort(key=lambda r: r[0])
|
|
80
|
+
return out
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def analyse(events_path, threshold_s=30.0, session_gap_s=1800.0):
|
|
84
|
+
evs = _parse(events_path)
|
|
85
|
+
if len(evs) < 2:
|
|
86
|
+
return {
|
|
87
|
+
"events": len(evs),
|
|
88
|
+
"gaps_over_threshold": 0,
|
|
89
|
+
"worst": [],
|
|
90
|
+
"threshold_s": threshold_s,
|
|
91
|
+
"notes": ["fewer than two timestamped events: nothing to measure"],
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
gaps = []
|
|
95
|
+
skipped_boundary = 0
|
|
96
|
+
for i in range(1, len(evs)):
|
|
97
|
+
prev_t, prev_k = evs[i - 1]
|
|
98
|
+
cur_t, cur_k = evs[i]
|
|
99
|
+
secs = (cur_t - prev_t).total_seconds()
|
|
100
|
+
if secs <= 0:
|
|
101
|
+
continue
|
|
102
|
+
# A gap longer than the session window, or one touching a session
|
|
103
|
+
# boundary event, is idle time rather than in-build silence.
|
|
104
|
+
if secs >= session_gap_s or prev_k in _BOUNDARY or cur_k in _BOUNDARY:
|
|
105
|
+
skipped_boundary += 1
|
|
106
|
+
continue
|
|
107
|
+
gaps.append({"seconds": round(secs, 1), "after": prev_k, "before": cur_k})
|
|
108
|
+
|
|
109
|
+
gaps.sort(key=lambda g: g["seconds"], reverse=True)
|
|
110
|
+
over = [g for g in gaps if g["seconds"] > threshold_s]
|
|
111
|
+
|
|
112
|
+
notes = []
|
|
113
|
+
if not gaps:
|
|
114
|
+
notes.append(
|
|
115
|
+
"no in-build gaps found: this event stream is session/CLI telemetry, "
|
|
116
|
+
"not a build. Point --events at a real build's .loki/events.jsonl"
|
|
117
|
+
)
|
|
118
|
+
if skipped_boundary:
|
|
119
|
+
notes.append(
|
|
120
|
+
f"{skipped_boundary} gap(s) ignored as idle time between sessions, "
|
|
121
|
+
"not in-build silence"
|
|
122
|
+
)
|
|
123
|
+
return {
|
|
124
|
+
"events": len(evs),
|
|
125
|
+
"in_build_gaps": len(gaps),
|
|
126
|
+
"gaps_over_threshold": len(over),
|
|
127
|
+
"threshold_s": threshold_s,
|
|
128
|
+
"worst": gaps[:10],
|
|
129
|
+
"notes": notes,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _render(r):
|
|
134
|
+
lines = ["Silence report", "==============", ""]
|
|
135
|
+
lines.append(f"Events: {r['events']}")
|
|
136
|
+
lines.append(f"In-build gaps: {r.get('in_build_gaps', 0)}")
|
|
137
|
+
lines.append(f"Over {int(r['threshold_s'])}s: {r['gaps_over_threshold']}")
|
|
138
|
+
if r["worst"]:
|
|
139
|
+
lines.append("")
|
|
140
|
+
lines.append("Longest silences:")
|
|
141
|
+
for g in r["worst"][:5]:
|
|
142
|
+
lines.append(
|
|
143
|
+
f" {g['seconds']:>8.1f}s after '{g['after']}' before '{g['before']}'"
|
|
144
|
+
)
|
|
145
|
+
for n in r.get("notes", []):
|
|
146
|
+
lines.append("")
|
|
147
|
+
lines.append(f"note: {n}")
|
|
148
|
+
return "\n".join(lines)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main():
|
|
152
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
153
|
+
ap.add_argument("--events", default=".loki/events.jsonl")
|
|
154
|
+
ap.add_argument("--threshold", type=float, default=30.0)
|
|
155
|
+
ap.add_argument("--json", action="store_true")
|
|
156
|
+
args = ap.parse_args()
|
|
157
|
+
r = analyse(args.events, args.threshold)
|
|
158
|
+
print(json.dumps(r, indent=2) if args.json else _render(r))
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
sys.exit(main())
|
package/dashboard/__init__.py
CHANGED
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var S_=Object.create;var{getPrototypeOf:y_,defineProperty:oK,getOwnPropertyNames:b_}=Object;var __=Object.prototype.hasOwnProperty;function f_(Z){return this[Z]}var h_,v_,g_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?h_??=new WeakMap:v_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?S_(y_(Z)):{};let K=X||!Z||!Z.__esModule?oK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of b_(Z))if(!__.call(K,$))oK(K,$,{get:f_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var VQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var m_=(Z)=>Z;function u_(Z,X){this[Z]=m_.bind(null,X)}var c0=(Z,X)=>{for(var Q in X)oK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:u_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var t0=import.meta.require;var IO={};c0(IO,{lokiDir:()=>j0,homeLokiDir:()=>I4,findRepoRootForVersion:()=>tK,REPO_ROOT:()=>Q8});import{resolve as s7,dirname as rK}from"path";import{fileURLToPath as p_}from"url";import{existsSync as qQ}from"fs";import{homedir as d_}from"os";function c_(){let Z=EO;for(let X=0;X<6;X++){if(qQ(s7(Z,"VERSION"))&&qQ(s7(Z,"autonomy/run.sh")))return Z;let Q=rK(Z);if(Q===Z)break;Z=Q}return s7(EO,"..","..","..")}function tK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(qQ(s7(X,"VERSION"))&&qQ(s7(X,"autonomy/run.sh")))return X;let Y=rK(X);if(Y===X)break;X=Y}return s7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??s7(process.cwd(),".loki")}function I4(){return s7(d_(),".loki")}var EO,Q8;var G8=p(()=>{EO=rK(p_(import.meta.url));Q8=c_()});import{readFileSync as l_}from"fs";import{resolve as i_,dirname as a_}from"path";import{fileURLToPath as s_}from"url";function _3(){if(f5!==null)return f5;let Z="8.
|
|
2
|
+
var S_=Object.create;var{getPrototypeOf:y_,defineProperty:oK,getOwnPropertyNames:b_}=Object;var __=Object.prototype.hasOwnProperty;function f_(Z){return this[Z]}var h_,v_,g_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?h_??=new WeakMap:v_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?S_(y_(Z)):{};let K=X||!Z||!Z.__esModule?oK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of b_(Z))if(!__.call(K,$))oK(K,$,{get:f_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var VQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var m_=(Z)=>Z;function u_(Z,X){this[Z]=m_.bind(null,X)}var c0=(Z,X)=>{for(var Q in X)oK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:u_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var t0=import.meta.require;var IO={};c0(IO,{lokiDir:()=>j0,homeLokiDir:()=>I4,findRepoRootForVersion:()=>tK,REPO_ROOT:()=>Q8});import{resolve as s7,dirname as rK}from"path";import{fileURLToPath as p_}from"url";import{existsSync as qQ}from"fs";import{homedir as d_}from"os";function c_(){let Z=EO;for(let X=0;X<6;X++){if(qQ(s7(Z,"VERSION"))&&qQ(s7(Z,"autonomy/run.sh")))return Z;let Q=rK(Z);if(Q===Z)break;Z=Q}return s7(EO,"..","..","..")}function tK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(qQ(s7(X,"VERSION"))&&qQ(s7(X,"autonomy/run.sh")))return X;let Y=rK(X);if(Y===X)break;X=Y}return s7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??s7(process.cwd(),".loki")}function I4(){return s7(d_(),".loki")}var EO,Q8;var G8=p(()=>{EO=rK(p_(import.meta.url));Q8=c_()});import{readFileSync as l_}from"fs";import{resolve as i_,dirname as a_}from"path";import{fileURLToPath as s_}from"url";function _3(){if(f5!==null)return f5;let Z="8.5.1";if(typeof Z==="string"&&Z.length>0)return f5=Z,f5;try{let X=a_(s_(import.meta.url)),Q=tK(X);f5=l_(i_(Q,"VERSION"),"utf-8").trim()}catch{f5="unknown"}return f5}var f5=null;var GQ=p(()=>{G8()});var xO={};c0(xO,{runOrThrow:()=>qf,run:()=>E0,readStreamCapped:()=>HQ,commandVersion:()=>Hf,commandExists:()=>X9,ShellError:()=>eK,MAX_STDOUT_BYTES:()=>kO});async function HQ(Z,X=kO){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([HQ(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 qf(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new eK(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=Gf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Gf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Hf(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 kO=16777216,eK;var k9=p(()=>{eK=class eK 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 n7(Z){return Uf?"":Z}var Uf,M0,k8,l0,dW0,a0,H8,Q9,g;var S6=p(()=>{Uf=(process.env.NO_COLOR??"").length>0;M0=n7("\x1B[0;31m"),k8=n7("\x1B[0;32m"),l0=n7("\x1B[1;33m"),dW0=n7("\x1B[0;34m"),a0=n7("\x1B[0;36m"),H8=n7("\x1B[1m"),Q9=n7("\x1B[2m"),g=n7("\x1B[0m")});import{existsSync as Ff}from"fs";async function F7(){if(R4!==void 0)return R4;let Z="/opt/homebrew/bin/python3.12";if(Ff(Z))return R4=Z,Z;let X=await X9("python3.12");if(X)return R4=X,X;let Q=await X9("python3");return R4=Q,Q}async function D7(Z,X={}){let Q=await F7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return E0([Q,"-c",Z],X)}var R4;var o7=p(()=>{k9()});var rO={};c0(rO,{runStatus:()=>tf});import{existsSync as Y9,readFileSync as h3,readdirSync as dO,statSync as cO}from"fs";import{resolve as h8,basename as pf}from"path";import{homedir as df}from"os";function lO(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 iO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*BQ/X);if(J>BQ)J=BQ;let z=BQ-J,K=k8;if(Y>=80)K=M0;else if(Y>=50)K=l0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),W=lO(Z),V=lO(X);return` ${H8}${Q}${g} ${K}[${$}]${g} ${Y}% (${W} / ${V})`}async function lf(){if(await X9("jq"))return!0;return process.stdout.write(`${M0}Error: jq is required but not installed.${g}
|
|
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)
|
|
@@ -1206,4 +1206,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
1206
1206
|
`),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (R_(),P_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
|
|
1207
1207
|
`),process.stderr.write(k_),2}}uO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var DW0=await FW0(Bun.argv.slice(2));process.exit(DW0);
|
|
1208
1208
|
|
|
1209
|
-
//# debugId=
|
|
1209
|
+
//# debugId=F70DD9091466CEA964756E2164756E21
|
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.5.1",
|
|
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.5.1",
|
|
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",
|