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 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.17.0
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.17.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
386
+ **v7.18.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.17.0
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
- if os.path.isfile(gen):
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
- root = Path(args.root).resolve()
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
- local gist_url
25759
- gist_url=$(gh gist create "$file" --desc "$desc" $visibility 2>&1)
25760
- local gist_exit=$?
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 "$gist_url"
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"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.17.0"
10
+ __version__ = "7.18.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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
- raise HTTPException(status_code=404, detail="wiki not generated")
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: #0f1115; --panel: #171a21; --panel-2: #1d2129; --border: #2a2f3a;
21
- --text: #e7e9ee; --muted: #9aa1ad; --faint: #6b7280; --accent: #6f7bf7;
22
- --green: #34d399; --red: #f87171; --amber: #fbbf24;
23
- --mono: ui-monospace, "SF Mono", "Menlo", "Consolas", monospace;
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: 24px; font-weight: 650; letter-spacing: -0.3px; margin: 0; }
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; }