loki-mode 7.17.0 → 7.18.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/lib/proof-generator.py +10 -1
- package/autonomy/lib/wiki-generator.py +5 -1
- package/autonomy/loki +39 -4
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +8 -1
- package/dashboard/static/cost.html +41 -5
- package/dashboard/static/index.html +97 -54
- package/dashboard/static/proofs.html +41 -5
- package/dashboard/static/trust.html +44 -5
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-to-product system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product via the RARV-C closure loop, with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.
|
|
6
|
+
# Loki Mode v7.18.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -383,4 +383,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
383
383
|
|
|
384
384
|
---
|
|
385
385
|
|
|
386
|
-
**v7.
|
|
386
|
+
**v7.18.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.18.0
|
|
@@ -307,7 +307,16 @@ def _collect_spec(loki_dir, target_dir):
|
|
|
307
307
|
brief = _read_text(prd_path)
|
|
308
308
|
else:
|
|
309
309
|
gen = os.path.join(loki_dir, "generated-prd.md")
|
|
310
|
-
|
|
310
|
+
# Raw one-liner from `loki start "<brief>"` (zero-config first run). The
|
|
311
|
+
# brief path writes the typed brief here; showing it verbatim is a
|
|
312
|
+
# stronger, more honest proof artifact than the synthesized PRD or a
|
|
313
|
+
# "No brief recorded" fallback. Checked before generated-prd.md because a
|
|
314
|
+
# brief run never produces generated-prd.md (it writes brief-prd-$$.md).
|
|
315
|
+
raw_brief = os.path.join(loki_dir, "state", "brief.txt")
|
|
316
|
+
if os.path.isfile(raw_brief):
|
|
317
|
+
source = "brief"
|
|
318
|
+
brief = _read_text(raw_brief)
|
|
319
|
+
elif os.path.isfile(gen):
|
|
311
320
|
source = gen
|
|
312
321
|
brief = _read_text(gen)
|
|
313
322
|
else:
|
|
@@ -213,12 +213,16 @@ def _render_md(section):
|
|
|
213
213
|
|
|
214
214
|
def main(argv=None):
|
|
215
215
|
ap = argparse.ArgumentParser(description="Generate a cited project wiki.")
|
|
216
|
+
ap.add_argument("path", nargs="?", default=None,
|
|
217
|
+
help="project root (positional; same as --root)")
|
|
216
218
|
ap.add_argument("--root", default=".", help="project root")
|
|
217
219
|
ap.add_argument("--force", action="store_true", help="regenerate even if unchanged")
|
|
218
220
|
ap.add_argument("--quiet", action="store_true")
|
|
219
221
|
args = ap.parse_args(argv)
|
|
220
222
|
|
|
221
|
-
|
|
223
|
+
# A positional path (documented as `loki wiki generate [path]`) takes
|
|
224
|
+
# precedence over --root when both are given; otherwise fall back to --root.
|
|
225
|
+
root = Path(args.path if args.path is not None else args.root).resolve()
|
|
222
226
|
if not root.is_dir():
|
|
223
227
|
print("error: not a directory: %s" % root, file=sys.stderr)
|
|
224
228
|
return 2
|
package/autonomy/loki
CHANGED
|
@@ -1220,6 +1220,13 @@ cmd_start() {
|
|
|
1220
1220
|
esac
|
|
1221
1221
|
done
|
|
1222
1222
|
|
|
1223
|
+
# Clear any stale raw-brief marker from a PRIOR run before we decide the
|
|
1224
|
+
# mode of THIS run. Only the brief branch (below) re-writes it. Without this,
|
|
1225
|
+
# a brief run leaves .loki/state/brief.txt behind and a later non-brief run
|
|
1226
|
+
# (PRD or codebase-analysis) would have its proof mislabel source="brief"
|
|
1227
|
+
# with the old one-liner, since proof-generator reads brief.txt first.
|
|
1228
|
+
rm -f "$LOKI_DIR/state/brief.txt" 2>/dev/null || true
|
|
1229
|
+
|
|
1223
1230
|
# v6.84.0: Unified dispatch based on explicit flags or auto-detection
|
|
1224
1231
|
# Precedence: --brief > --issue > --prd > positional auto-detect >
|
|
1225
1232
|
# LOKI_PRD_FILE env
|
|
@@ -1292,6 +1299,15 @@ cmd_start() {
|
|
|
1292
1299
|
synthesize_brief_prd "$brief_prd" "$brief_text"
|
|
1293
1300
|
prd_file="$brief_prd"
|
|
1294
1301
|
|
|
1302
|
+
# Persist the raw one-liner so the proof-of-run can show the actual brief
|
|
1303
|
+
# the user typed ("Built and verified from: <brief>") instead of falling
|
|
1304
|
+
# back to "codebase-analysis / No brief recorded". The synthesized PRD is
|
|
1305
|
+
# kept distinct (brief-prd-$$.md); the raw brief is the stronger, more
|
|
1306
|
+
# honest shareable artifact. proof-generator.py::_collect_spec reads this
|
|
1307
|
+
# as a fallback when no PRD_PATH / generated-prd.md is present.
|
|
1308
|
+
mkdir -p "$LOKI_DIR/state" 2>/dev/null || true
|
|
1309
|
+
printf '%s' "$brief_text" > "$LOKI_DIR/state/brief.txt" 2>/dev/null || true
|
|
1310
|
+
|
|
1295
1311
|
# Apply the shared lightweight profile and flag the TTFV first-run path.
|
|
1296
1312
|
# The signal value ("brief") drives the end-of-run wording in run.sh so
|
|
1297
1313
|
# the message matches what actually ran (lightweight, council off).
|
|
@@ -25755,19 +25771,38 @@ _loki_gist_upload() {
|
|
|
25755
25771
|
local desc="$2"
|
|
25756
25772
|
local visibility="$3"
|
|
25757
25773
|
|
|
25758
|
-
|
|
25759
|
-
|
|
25760
|
-
|
|
25774
|
+
# gh prints progress ("- Creating gist...", "Created public gist...") on
|
|
25775
|
+
# STDERR and the final gist URL on STDOUT. Capture them separately so the
|
|
25776
|
+
# URL we expose and print is clean: folding stderr into stdout pollutes the
|
|
25777
|
+
# "Shared:" line and the ready-to-post hook with multi-line gh chatter.
|
|
25778
|
+
local gist_out gist_err gist_exit
|
|
25779
|
+
local errfile
|
|
25780
|
+
# Fall back to /dev/null if mktemp fails (disk full / unwritable /tmp) so the
|
|
25781
|
+
# redirect target is always a real path -- never an empty string (2>"" is a
|
|
25782
|
+
# bash error). We lose the captured stderr text in that rare case, not the
|
|
25783
|
+
# clean URL parse.
|
|
25784
|
+
errfile=$(mktemp "/tmp/loki-gist-err-XXXXXX" 2>/dev/null) || errfile=/dev/null
|
|
25785
|
+
gist_out=$(gh gist create "$file" --desc "$desc" $visibility 2>"$errfile")
|
|
25786
|
+
gist_exit=$?
|
|
25787
|
+
gist_err=$(cat "$errfile" 2>/dev/null)
|
|
25788
|
+
[ "$errfile" != "/dev/null" ] && rm -f "$errfile"
|
|
25761
25789
|
|
|
25762
25790
|
# Cleanup temp file
|
|
25763
25791
|
rm -f "$file"
|
|
25764
25792
|
|
|
25765
25793
|
if [ $gist_exit -ne 0 ]; then
|
|
25766
25794
|
echo -e "${RED}Failed to create gist${NC}"
|
|
25767
|
-
echo "$
|
|
25795
|
+
[ -n "$gist_err" ] && echo "$gist_err"
|
|
25796
|
+
[ -n "$gist_out" ] && echo "$gist_out"
|
|
25768
25797
|
exit 1
|
|
25769
25798
|
fi
|
|
25770
25799
|
|
|
25800
|
+
# The URL is the gist permalink on stdout. Extract just the URL line in
|
|
25801
|
+
# case any non-URL text slips into stdout on some gh versions.
|
|
25802
|
+
local gist_url
|
|
25803
|
+
gist_url=$(printf '%s\n' "$gist_out" | grep -Eo 'https://gist\.github\.com/[^[:space:]]+' | head -1)
|
|
25804
|
+
[ -z "$gist_url" ] && gist_url="$gist_out"
|
|
25805
|
+
|
|
25771
25806
|
# Expose the URL to callers (e.g. cmd_proof prints a ready-to-post hook
|
|
25772
25807
|
# after this returns). The shared "Shared:" line stays unchanged.
|
|
25773
25808
|
LOKI_LAST_GIST_URL="$gist_url"
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -7772,7 +7772,14 @@ async def get_wiki_section(section: str):
|
|
|
7772
7772
|
raise HTTPException(status_code=400, detail=f"unknown section: {section}")
|
|
7773
7773
|
wiki_json = _wiki_dir() / "wiki.json"
|
|
7774
7774
|
if not wiki_json.is_file():
|
|
7775
|
-
|
|
7775
|
+
# Soft empty state for dashboard consumers on a fresh repo. A hard 404
|
|
7776
|
+
# here floods the browser console (the panel and the SPA both poll this)
|
|
7777
|
+
# even though "no wiki yet" is an expected, benign state. Mirrors the
|
|
7778
|
+
# generated:false contract of GET /api/wiki.
|
|
7779
|
+
return JSONResponse(content={
|
|
7780
|
+
"generated": False, "id": section, "title": "",
|
|
7781
|
+
"body": "", "citations": [],
|
|
7782
|
+
})
|
|
7776
7783
|
data = _safe_json_read(wiki_json, default=None)
|
|
7777
7784
|
if not isinstance(data, dict):
|
|
7778
7785
|
raise HTTPException(status_code=500, detail="wiki.json unreadable")
|
|
@@ -14,14 +14,50 @@
|
|
|
14
14
|
<head>
|
|
15
15
|
<meta charset="utf-8">
|
|
16
16
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
17
|
+
<script>
|
|
18
|
+
// Match the embedding SPA's theme via the ?theme=dark|light param it passes
|
|
19
|
+
// when loading this page in its iframe. Set before paint to avoid a flash;
|
|
20
|
+
// with no/invalid param, OS prefers-color-scheme applies.
|
|
21
|
+
(function () {
|
|
22
|
+
try {
|
|
23
|
+
var t = new URLSearchParams(location.search).get('theme');
|
|
24
|
+
if (t === 'dark' || t === 'light') {
|
|
25
|
+
document.documentElement.setAttribute('data-loki-theme', t);
|
|
26
|
+
}
|
|
27
|
+
} catch (e) { /* no-op: fall back to OS preference */ }
|
|
28
|
+
})();
|
|
29
|
+
</script>
|
|
17
30
|
<title>Loki Mode - Cost and Observability</title>
|
|
31
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
32
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
33
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
18
34
|
<style>
|
|
35
|
+
/* Match the dashboard SPA design language (cream/light + serif headings),
|
|
36
|
+
so this page reads as one product when embedded in the SPA iframe. The
|
|
37
|
+
local --bg/--panel/--text/--mono/--sans names are kept (the rest of the
|
|
38
|
+
CSS references them) and remapped to the SPA --loki-* palette, with a
|
|
39
|
+
prefers-color-scheme dark variant mirroring the SPA. */
|
|
19
40
|
:root {
|
|
20
|
-
--bg: #
|
|
21
|
-
--text: #
|
|
22
|
-
--green: #
|
|
23
|
-
--mono: ui-monospace, "SF Mono", "Menlo",
|
|
41
|
+
--bg: #FAFAF7; --panel: rgba(255,255,255,0.72); --panel-2: #E8E5DE; --border: rgba(0,0,0,0.08);
|
|
42
|
+
--text: #1A1614; --muted: #4A4640; --faint: #8A857C; --accent: #553DE9;
|
|
43
|
+
--green: #1AAF95; --red: #C04848; --amber: #C4922E;
|
|
44
|
+
--mono: 'JetBrains Mono', ui-monospace, "SF Mono", "Menlo", monospace;
|
|
24
45
|
--sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
46
|
+
--serif: 'DM Serif Display', Georgia, serif;
|
|
47
|
+
}
|
|
48
|
+
/* Dark palette. data-loki-theme (set from the SPA's ?theme= param) wins over
|
|
49
|
+
the OS preference so the iframe follows the SPA's manual Dark toggle. */
|
|
50
|
+
html[data-loki-theme="dark"] {
|
|
51
|
+
--bg: #0F0B1A; --panel: rgba(30,21,51,0.72); --panel-2: #1E1533; --border: rgba(255,255,255,0.08);
|
|
52
|
+
--text: #F0ECF8; --muted: #B8B0C8; --faint: #7B6FA0; --accent: #7B6BF0;
|
|
53
|
+
--green: #2ED8B6; --red: #E07070; --amber: #E8B84A;
|
|
54
|
+
}
|
|
55
|
+
@media (prefers-color-scheme: dark) {
|
|
56
|
+
html:not([data-loki-theme]) {
|
|
57
|
+
--bg: #0F0B1A; --panel: rgba(30,21,51,0.72); --panel-2: #1E1533; --border: rgba(255,255,255,0.08);
|
|
58
|
+
--text: #F0ECF8; --muted: #B8B0C8; --faint: #7B6FA0; --accent: #7B6BF0;
|
|
59
|
+
--green: #2ED8B6; --red: #E07070; --amber: #E8B84A;
|
|
60
|
+
}
|
|
25
61
|
}
|
|
26
62
|
* { box-sizing: border-box; }
|
|
27
63
|
body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--sans); line-height: 1.5; }
|
|
@@ -29,7 +65,7 @@
|
|
|
29
65
|
a:hover { text-decoration: underline; }
|
|
30
66
|
.wrap { max-width: 960px; margin: 0 auto; padding: 40px 20px 80px; }
|
|
31
67
|
.head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; }
|
|
32
|
-
h1 { font-size:
|
|
68
|
+
h1 { font-family: var(--serif); font-size: 28px; font-weight: 400; letter-spacing: -0.3px; margin: 0; }
|
|
33
69
|
h2 { font-size: 15px; font-weight: 600; color: var(--muted); margin: 30px 0 12px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
34
70
|
.head a { font-size: 13px; }
|
|
35
71
|
.sub { color: var(--muted); font-size: 14px; margin: 0 0 26px; }
|