loki-mode 7.100.1 → 7.101.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-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 v7.100.1
6
+ # Loki Mode v7.101.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
408
408
 
409
409
  ---
410
410
 
411
- **v7.100.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
411
+ **v7.101.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.100.1
1
+ 7.101.0
@@ -843,12 +843,17 @@ def _compute_headline(facts, degraded):
843
843
  if tests_verified and not degraded and diff_nonempty:
844
844
  return "VERIFIED"
845
845
  # Any fact verified at all (tests/build verified, or a passed gate)?
846
+ # A non-empty diff is a PREREQUISITE for VERIFIED (checked above), NOT a
847
+ # positive fact of passage: code was written, but nothing was shown to pass.
848
+ # Including diff_nonempty here let a build that ran ZERO tests/gates but
849
+ # produced code emit "VERIFIED WITH GAPS" - a fake-green at the receipt. Only
850
+ # a fact that actually ran and passed (tests/build verified, or a passed gate)
851
+ # may qualify; otherwise the honest headline is NOT VERIFIED.
846
852
  any_verified = (
847
853
  tests.get("status") == "verified"
848
854
  or build.get("status") == "verified"
849
855
  or any(g.get("status") == "passed"
850
856
  for g in (facts.get("quality_gates") or []))
851
- or diff_nonempty
852
857
  )
853
858
  if any_verified and degraded:
854
859
  return "VERIFIED WITH GAPS"
package/autonomy/loki CHANGED
@@ -922,6 +922,7 @@ show_help() {
922
922
  echo " loki start --bg ./prd.md # Start in background"
923
923
  echo " loki start --parallel ./prd.md # Parallel mode (git worktrees)"
924
924
  echo " loki quick \"add dark mode\" # Single-task mode (3 iters max)"
925
+ echo " loki tour # See a real sample result (no provider, no key, no spend)"
925
926
  echo " loki demo # Build a sample todo app end to end"
926
927
  echo " loki init -t saas-starter # Scaffold from template"
927
928
  echo " loki template install <src> # Install a community PRD template"
package/autonomy/run.sh CHANGED
@@ -2023,6 +2023,35 @@ _loki_check_git_present() {
2023
2023
  return 0
2024
2024
  }
2025
2025
 
2026
+ # _loki_check_workspace_writable: the build writes state, proofs, and source into
2027
+ # the working directory and its .loki/ subtree on every iteration. A read-only
2028
+ # volume, a permission-denied directory, or a full disk would otherwise fail
2029
+ # silently deep inside the loop (every state write swallows errors with
2030
+ # `2>/dev/null || true`), leaving the user with a confusing half-built run. Catch
2031
+ # it once, up front, with one actionable message instead. HARD requirement.
2032
+ _loki_check_workspace_writable() {
2033
+ local dir="${TARGET_DIR:-$(pwd)}"
2034
+ local probe="${dir}/.loki/.write-probe.$$"
2035
+ # Ensure the .loki subtree can be created and written to.
2036
+ if ! mkdir -p "${dir}/.loki" 2>/dev/null; then
2037
+ if [ ! -w "$dir" ]; then
2038
+ log_error "Cannot create ${dir}/.loki -- the working directory is not writable. Fix permissions (e.g. chmod u+w '${dir}') or run from a directory you own, then retry."
2039
+ else
2040
+ log_error "Cannot create ${dir}/.loki. If the disk is full, free space (df -h '${dir}'); if it is a read-only mount, run from a writable location, then retry."
2041
+ fi
2042
+ return 1
2043
+ fi
2044
+ # Confirm we can actually write a byte (catches full disk / read-only mount
2045
+ # where the directory exists but writes fail).
2046
+ if ! (: > "$probe") 2>/dev/null; then
2047
+ rm -f "$probe" 2>/dev/null || true
2048
+ log_error "Cannot write to ${dir}/.loki -- check free disk space (df -h '${dir}') and that the volume is not read-only, then retry."
2049
+ return 1
2050
+ fi
2051
+ rm -f "$probe" 2>/dev/null || true
2052
+ return 0
2053
+ }
2054
+
2026
2055
  # _loki_check_network_reachable: ADVISORY only. A fast (3s) reachability probe to
2027
2056
  # the active provider endpoint that WARNS and continues if it cannot reach it -- a
2028
2057
  # curl failure does not prove the provider CLI cannot connect (3s timeout under
@@ -2070,6 +2099,9 @@ validate_api_keys() {
2070
2099
  if ! _loki_check_git_present; then
2071
2100
  return 1
2072
2101
  fi
2102
+ if ! _loki_check_workspace_writable; then
2103
+ return 1
2104
+ fi
2073
2105
  if ! _loki_check_network_reachable "$provider"; then
2074
2106
  return 1
2075
2107
  fi
@@ -3229,6 +3261,22 @@ except Exception:
3229
3261
  else
3230
3262
  echo "Diff stat: (no changes detected vs run start, or git unavailable)"
3231
3263
  fi
3264
+ # Empty-diff remedy (mirrors emit_completion_summary's on-screen line) so
3265
+ # a --bg or dashboard user reading COMPLETION.txt gets the same honest,
3266
+ # actionable guidance as a foreground user. Never implies success.
3267
+ case "$files_changed" in
3268
+ ''|0)
3269
+ case "$outcome" in
3270
+ complete|max_iterations)
3271
+ echo ""
3272
+ echo "No file changes were produced this run. Likely the spec was too vague"
3273
+ echo "or already satisfied. Next: re-run with a more concrete spec, e.g."
3274
+ echo " loki start \"<one concrete, testable change you want>\""
3275
+ echo "or inspect what happened: loki why"
3276
+ ;;
3277
+ esac
3278
+ ;;
3279
+ esac
3232
3280
  } > "$loki_dir/COMPLETION.txt" 2>/dev/null || true
3233
3281
 
3234
3282
  # ---- Durable machine-readable file: .loki/state/completion.json -----------
@@ -3515,6 +3563,24 @@ except Exception:
3515
3563
  fi
3516
3564
  echo -e "${GREEN}|${NC} Branch: ${BOLD}${_branch}${NC}"
3517
3565
  echo -e "${GREEN}|${NC} Files: ${BOLD}${_files}${NC} changed ${GREEN}+${_ins}${NC} / ${RED}-${_del}${NC}"
3566
+ # Empty-diff guard (honest, not fake-green): a run that ended without
3567
+ # producing ANY file change is the classic confusing new-user outcome
3568
+ # ("it said done, but nothing happened"). Never imply success on a 0-file
3569
+ # run -- surface it plainly with one actionable next step. Only for the
3570
+ # non-failure outcomes (a "failed"/"stopped" box already explains itself).
3571
+ case "$_files" in
3572
+ ''|0)
3573
+ case "$_outcome" in
3574
+ complete|max_iterations)
3575
+ echo -e "${GREEN}|${NC}"
3576
+ echo -e "${GREEN}|${NC} ${YELLOW}No file changes were produced this run.${NC} Likely the spec was too vague"
3577
+ echo -e "${GREEN}|${NC} or already satisfied. Next: re-run with a more concrete spec, e.g."
3578
+ echo -e "${GREEN}|${NC} ${BOLD}loki start \"<one concrete, testable change you want>\"${NC}"
3579
+ echo -e "${GREEN}|${NC} or inspect what happened: ${BOLD}loki why${NC}"
3580
+ ;;
3581
+ esac
3582
+ ;;
3583
+ esac
3518
3584
  case "$_atotal" in ''|0) : ;; *)
3519
3585
  echo -e "${GREEN}|${NC} Spec assumptions recorded: ${BOLD}${_atotal}${NC} (${_ahigh} high) ${DIM}see .loki/assumptions/ledger.md${NC}"
3520
3586
  ;;
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.100.1"
10
+ __version__ = "7.101.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -851,6 +851,116 @@
851
851
 
852
852
  /* Overview handled by <loki-overview> shadow DOM */
853
853
 
854
+ /* First-run hero: shown only to a brand-new user (no completed runs,
855
+ no active session, no current spec). Gated in JS on /api/spec/history
856
+ being empty so a returning-but-idle user never sees it. Hidden by
857
+ default; JS adds .first-run-hero--visible after the signal confirms. */
858
+ .first-run-hero {
859
+ display: none;
860
+ }
861
+ .first-run-hero--visible {
862
+ display: block;
863
+ margin-bottom: 28px;
864
+ background: var(--loki-bg-card);
865
+ border: 1px solid var(--loki-border);
866
+ border-radius: 8px;
867
+ padding: 28px 28px 24px;
868
+ }
869
+ .first-run-hero__top {
870
+ display: flex;
871
+ align-items: center;
872
+ gap: 12px;
873
+ margin-bottom: 6px;
874
+ }
875
+ .first-run-hero__icon {
876
+ width: 40px;
877
+ height: 40px;
878
+ flex-shrink: 0;
879
+ display: flex;
880
+ align-items: center;
881
+ justify-content: center;
882
+ border-radius: 9999px;
883
+ background: var(--loki-accent-glow);
884
+ color: var(--loki-accent);
885
+ }
886
+ .first-run-hero__icon svg { width: 22px; height: 22px; }
887
+ .first-run-hero__title {
888
+ font-family: 'DM Serif Display', Georgia, serif;
889
+ font-size: 1.5rem;
890
+ font-weight: 400;
891
+ color: var(--loki-text-primary);
892
+ letter-spacing: -0.01em;
893
+ }
894
+ .first-run-hero__desc {
895
+ font-size: 14px;
896
+ line-height: 1.6;
897
+ color: var(--loki-text-secondary);
898
+ max-width: 620px;
899
+ margin: 0 0 18px;
900
+ }
901
+ .first-run-hero__cmd-label {
902
+ font-size: 11px;
903
+ font-weight: 600;
904
+ text-transform: uppercase;
905
+ letter-spacing: 0.05em;
906
+ color: var(--loki-text-muted);
907
+ margin-bottom: 6px;
908
+ }
909
+ .first-run-hero__cmd {
910
+ display: flex;
911
+ align-items: center;
912
+ gap: 10px;
913
+ background: var(--loki-bg-tertiary);
914
+ border: 1px solid var(--loki-border);
915
+ border-radius: 6px;
916
+ padding: 10px 12px;
917
+ max-width: 420px;
918
+ margin-bottom: 14px;
919
+ }
920
+ .first-run-hero__cmd code {
921
+ flex: 1;
922
+ font-family: var(--loki-font-mono, 'JetBrains Mono', monospace);
923
+ font-size: 13px;
924
+ color: var(--loki-text-primary);
925
+ white-space: nowrap;
926
+ overflow-x: auto;
927
+ }
928
+ .first-run-hero__copy {
929
+ flex-shrink: 0;
930
+ display: inline-flex;
931
+ align-items: center;
932
+ gap: 5px;
933
+ background: var(--loki-accent);
934
+ color: #ffffff;
935
+ border: none;
936
+ border-radius: 5px;
937
+ padding: 6px 12px;
938
+ font-size: 12px;
939
+ font-weight: 600;
940
+ font-family: inherit;
941
+ cursor: pointer;
942
+ transition: background 0.15s ease;
943
+ }
944
+ .first-run-hero__copy:hover { background: var(--loki-accent-hover); }
945
+ .first-run-hero__copy:focus-visible {
946
+ outline: 2px solid var(--loki-accent);
947
+ outline-offset: 2px;
948
+ }
949
+ .first-run-hero__copy svg { width: 13px; height: 13px; }
950
+ .first-run-hero__secondary {
951
+ font-size: 13px;
952
+ color: var(--loki-text-muted);
953
+ line-height: 1.6;
954
+ }
955
+ .first-run-hero__secondary code {
956
+ font-family: var(--loki-font-mono, 'JetBrains Mono', monospace);
957
+ font-size: 12px;
958
+ background: var(--loki-bg-tertiary);
959
+ color: var(--loki-text-secondary);
960
+ padding: 1px 6px;
961
+ border-radius: 3px;
962
+ }
963
+
854
964
  /* Offline Banner */
855
965
  .offline-banner {
856
966
  position: fixed;
@@ -1135,6 +1245,35 @@
1135
1245
  <main class="main-content" id="main-content">
1136
1246
  <!-- Overview + Tasks (combined) -->
1137
1247
  <div class="section-page active" id="page-overview">
1248
+ <!-- First-run hero: a brand-new user (zero completed runs) lands here
1249
+ with nothing to show. Instead of three stacked passive empty
1250
+ states, give them one branded next action. Hidden by default;
1251
+ initFirstRunHero() reveals it only when /api/spec/history is
1252
+ empty AND no session is active AND no spec is loaded. A returning
1253
+ user who has shipped a run never sees it. -->
1254
+ <section class="first-run-hero" id="first-run-hero" role="region" aria-label="Get started with Loki" hidden>
1255
+ <div class="first-run-hero__top">
1256
+ <span class="first-run-hero__icon" aria-hidden="true">
1257
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
1258
+ </span>
1259
+ <h2 class="first-run-hero__title">Start your first build</h2>
1260
+ </div>
1261
+ <p class="first-run-hero__desc">
1262
+ Loki turns a spec (a PRD, a GitHub issue, or a one-line idea) into a verified, working product.
1263
+ No build has run in this project yet. Run the guided quickstart in your terminal to kick off your first one, then watch it here live.
1264
+ </p>
1265
+ <div class="first-run-hero__cmd-label">Run in your terminal</div>
1266
+ <div class="first-run-hero__cmd">
1267
+ <code id="first-run-cmd">loki quickstart</code>
1268
+ <button type="button" class="first-run-hero__copy" id="first-run-copy" aria-label="Copy command: loki quickstart" data-copy="loki quickstart">
1269
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
1270
+ <span id="first-run-copy-label">Copy</span>
1271
+ </button>
1272
+ </div>
1273
+ <p class="first-run-hero__secondary">
1274
+ Just looking? Try a zero-key, zero-spend sample receipt: <code>loki tour</code>
1275
+ </p>
1276
+ </section>
1138
1277
  <!-- Active spec: what Loki is building from + history of past specs -->
1139
1278
  <div style="margin-bottom: 28px;">
1140
1279
  <div class="section-page-header">
@@ -15819,7 +15958,82 @@ document.addEventListener('DOMContentLoaded', function() {
15819
15958
 
15820
15959
  // Overview cards are now handled by the <loki-overview> component
15821
15960
  // which polls /api/status reactively via the unified API client.
15961
+
15962
+ // First-run hero: reveal only for a genuinely brand-new user. The honest
15963
+ // "has this project ever produced a build?" signal is /api/spec/history,
15964
+ // which is derived from real Evidence Receipts (never faked). We additionally
15965
+ // require no active session and no current spec so the hero never overlaps a
15966
+ // live or just-finished run. A returning-but-idle user (history > 0) never
15967
+ // sees it. Any fetch error -> leave the hero hidden (fail safe, no false
15968
+ // "you've never built" claim).
15969
+ (function initFirstRunHero() {
15970
+ var hero = document.getElementById('first-run-hero');
15971
+ if (!hero) return;
15972
+
15973
+ var copyBtn = document.getElementById('first-run-copy');
15974
+ if (copyBtn) {
15975
+ copyBtn.addEventListener('click', function() {
15976
+ var cmd = copyBtn.getAttribute('data-copy') || 'loki quickstart';
15977
+ var label = document.getElementById('first-run-copy-label');
15978
+ var done = function() {
15979
+ if (!label) return;
15980
+ var prev = label.textContent;
15981
+ label.textContent = 'Copied';
15982
+ setTimeout(function() { label.textContent = prev; }, 1600);
15983
+ };
15984
+ if (navigator.clipboard && navigator.clipboard.writeText) {
15985
+ navigator.clipboard.writeText(cmd).then(done).catch(function() {});
15986
+ } else {
15987
+ // Older/insecure-context fallback: select + execCommand.
15988
+ var ta = document.createElement('textarea');
15989
+ ta.value = cmd;
15990
+ ta.setAttribute('readonly', '');
15991
+ ta.style.position = 'absolute';
15992
+ ta.style.left = '-9999px';
15993
+ document.body.appendChild(ta);
15994
+ ta.select();
15995
+ try { document.execCommand('copy'); done(); } catch (e) { /* ignore */ }
15996
+ document.body.removeChild(ta);
15997
+ }
15998
+ });
15999
+ }
16000
+
16001
+ function showHero(show) {
16002
+ if (show) {
16003
+ hero.hidden = false;
16004
+ hero.classList.add('first-run-hero--visible');
16005
+ } else {
16006
+ hero.classList.remove('first-run-hero--visible');
16007
+ hero.hidden = true;
16008
+ }
16009
+ }
16010
+
16011
+ function evaluate() {
16012
+ Promise.all([
16013
+ fetch(detectedUrl + '/api/spec/history').then(function(r) { return r.ok ? r.json() : null; }).catch(function() { return null; }),
16014
+ fetch(detectedUrl + '/api/status').then(function(r) { return r.ok ? r.json() : null; }).catch(function() { return null; }),
16015
+ fetch(detectedUrl + '/api/spec').then(function(r) { return r.ok ? r.json() : null; }).catch(function() { return null; })
16016
+ ]).then(function(res) {
16017
+ var history = res[0];
16018
+ var status = res[1];
16019
+ var spec = res[2];
16020
+ // Fail safe: if we cannot read the history signal, do not claim
16021
+ // "you've never built" -- keep the hero hidden.
16022
+ if (!history || !Array.isArray(history.history)) { showHero(false); return; }
16023
+ var hasEverBuilt = history.history.length > 0;
16024
+ var st = status && status.status ? String(status.status) : 'offline';
16025
+ var sessionActive = (st === 'running' || st === 'autonomous' || st === 'paused');
16026
+ var hasSpec = !!(spec && spec.type && spec.type !== 'none');
16027
+ showHero(!hasEverBuilt && !sessionActive && !hasSpec);
16028
+ }).catch(function() { showHero(false); });
16029
+ }
16030
+
16031
+ evaluate();
16032
+ // Re-check after a session starts/stops so the hero disappears once the
16033
+ // first build begins and never reappears once a run has shipped.
16034
+ setInterval(evaluate, 15000);
16035
+ })();
15822
16036
  });
15823
- </script>
16037
+ </script>
15824
16038
  </body>
15825
16039
  </html>
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.100.1
5
+ **Version:** v7.101.0
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var z8=Object.defineProperty;var X8=($)=>$;function K8($,Q){this[$]=X8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)z8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:K8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var y1={};b(y1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as t,dirname as i$}from"path";import{fileURLToPath as q8}from"url";import{existsSync as E$}from"fs";import{homedir as J8}from"os";function V8(){let $=h1;for(let Q=0;Q<6;Q++){if(E$(t($,"VERSION"))&&E$(t($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return t(h1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(E$(t(Q,"VERSION"))&&E$(t(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function P(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(J8(),".loki")}var h1,h;var C=L(()=>{h1=i$(q8(import.meta.url));h=V8()});import{readFileSync as W8}from"fs";import{resolve as U8,dirname as H8}from"path";import{fileURLToPath as G8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.100.1";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=H8(G8(import.meta.url)),Z=e$(Q);Q$=W8(U8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var $1=L(()=>{C()});var u1={};b(u1,{runOrThrow:()=>x8,run:()=>F,readStreamCapped:()=>f1,commandVersion:()=>N8,commandExists:()=>f,ShellError:()=>Q1,MAX_STDOUT_BYTES:()=>m1});async function f1($,Q=m1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([f1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function x8($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new Q1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=E8($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function E8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function N8($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var m1=16777216,Q1;var d=L(()=>{Q1=class Q1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function r($){return S8?"":$}var S8,T,S,_,lZ,I,k,y,J;var c=L(()=>{S8=(process.env.NO_COLOR??"").length>0;T=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),lZ=r("\x1B[0;34m"),I=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as u8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(u8($))return A$=$,$;let Q=await f("python3.12");if(Q)return A$=Q,Q;let Z=await f("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var A$;var V$=L(()=>{d()});var q0={};b(q0,{runStatus:()=>W3});import{existsSync as v,readFileSync as U$,readdirSync as i1,statSync as e1}from"fs";import{resolve as D,basename as $3}from"path";import{homedir as Q3}from"os";function $0($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function Q0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*D$/Q);if(X>D$)X=D$;let q=D$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=$0($),U=$0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${U})`}async function z3(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
2
+ var z8=Object.defineProperty;var X8=($)=>$;function K8($,Q){this[$]=X8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)z8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:K8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var y1={};b(y1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as t,dirname as i$}from"path";import{fileURLToPath as q8}from"url";import{existsSync as E$}from"fs";import{homedir as J8}from"os";function V8(){let $=h1;for(let Q=0;Q<6;Q++){if(E$(t($,"VERSION"))&&E$(t($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return t(h1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(E$(t(Q,"VERSION"))&&E$(t(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function P(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(J8(),".loki")}var h1,h;var C=L(()=>{h1=i$(q8(import.meta.url));h=V8()});import{readFileSync as W8}from"fs";import{resolve as U8,dirname as H8}from"path";import{fileURLToPath as G8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.101.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=H8(G8(import.meta.url)),Z=e$(Q);Q$=W8(U8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var $1=L(()=>{C()});var u1={};b(u1,{runOrThrow:()=>x8,run:()=>F,readStreamCapped:()=>f1,commandVersion:()=>N8,commandExists:()=>f,ShellError:()=>Q1,MAX_STDOUT_BYTES:()=>m1});async function f1($,Q=m1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([f1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function x8($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new Q1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=E8($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function E8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function N8($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var m1=16777216,Q1;var d=L(()=>{Q1=class Q1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function r($){return S8?"":$}var S8,T,S,_,lZ,I,k,y,J;var c=L(()=>{S8=(process.env.NO_COLOR??"").length>0;T=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),lZ=r("\x1B[0;34m"),I=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as u8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(u8($))return A$=$,$;let Q=await f("python3.12");if(Q)return A$=Q,Q;let Z=await f("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var A$;var V$=L(()=>{d()});var q0={};b(q0,{runStatus:()=>W3});import{existsSync as v,readFileSync as U$,readdirSync as i1,statSync as e1}from"fs";import{resolve as D,basename as $3}from"path";import{homedir as Q3}from"os";function $0($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function Q0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*D$/Q);if(X>D$)X=D$;let q=D$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=$0($),U=$0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${U})`}async function z3(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
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)
@@ -802,4 +802,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
802
802
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
803
803
  `),process.stderr.write(Z8),2}}r1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var FZ=await jZ(Bun.argv.slice(2));process.exit(FZ);
804
804
 
805
- //# debugId=83F4F01D21A9206164756E2164756E21
805
+ //# debugId=01C60773ABB86B6464756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.100.1'
60
+ __version__ = '7.101.0'
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": "7.100.1",
4
+ "version": "7.101.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": "7.100.1",
5
+ "version": "7.101.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",
@@ -1,244 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Loki Mode postinstall script
4
- * Sets up the skill symlink for Claude Code, Codex CLI, and Gemini CLI
5
- */
6
-
7
- const fs = require('fs');
8
- const path = require('path');
9
- const os = require('os');
10
-
11
- const homeDir = os.homedir();
12
- const packageDir = path.join(__dirname, '..');
13
-
14
- const version = (() => {
15
- try { return fs.readFileSync(path.join(packageDir, 'VERSION'), 'utf8').trim(); }
16
- catch { return require(path.join(packageDir, 'package.json')).version; }
17
- })();
18
-
19
- console.log('');
20
- console.log(`Loki Mode v${version} installed!`);
21
- console.log('');
22
-
23
- // Multi-provider skill targets
24
- const skillTargets = [
25
- { dir: path.join(homeDir, '.claude', 'skills', 'loki-mode'), name: 'Claude Code' },
26
- { dir: path.join(homeDir, '.codex', 'skills', 'loki-mode'), name: 'Codex CLI' },
27
- { dir: path.join(homeDir, '.gemini', 'skills', 'loki-mode'), name: 'Gemini CLI' },
28
- ];
29
-
30
- const results = [];
31
-
32
- for (const target of skillTargets) {
33
- try {
34
- const skillParent = path.dirname(target.dir);
35
-
36
- if (!fs.existsSync(skillParent)) {
37
- fs.mkdirSync(skillParent, { recursive: true });
38
- }
39
-
40
- // Remove existing symlink/directory
41
- if (fs.existsSync(target.dir)) {
42
- const stats = fs.lstatSync(target.dir);
43
- if (stats.isSymbolicLink()) {
44
- fs.unlinkSync(target.dir);
45
- } else {
46
- // Existing real directory (not a symlink) - back it up and replace
47
- const backupDir = target.dir + '.backup.' + Date.now();
48
- console.log(`[WARNING] Existing non-symlink installation found at ${target.dir}`);
49
- console.log(` Backing up to: ${backupDir}`);
50
- try {
51
- fs.renameSync(target.dir, backupDir);
52
- } catch (backupErr) {
53
- console.log(` Could not back up: ${backupErr.message}`);
54
- results.push({ name: target.name, path: target.dir, ok: false });
55
- continue;
56
- }
57
- }
58
- }
59
-
60
- // Create symlink
61
- if (!fs.existsSync(target.dir)) {
62
- fs.symlinkSync(packageDir, target.dir);
63
- }
64
- results.push({ name: target.name, path: target.dir, ok: true });
65
- } catch (err) {
66
- results.push({ name: target.name, path: target.dir, ok: false, error: err.message });
67
- }
68
- }
69
-
70
- // Print summary
71
- console.log('Skills installed:');
72
- for (const r of results) {
73
- const icon = r.ok ? 'OK' : 'SKIP';
74
- const shortPath = r.path.replace(homeDir, '~');
75
- if (r.ok) {
76
- console.log(` [${icon}] ${r.name.padEnd(12)} (${shortPath})`);
77
- } else {
78
- console.log(` [${icon}] ${r.name.padEnd(12)} (${shortPath}) - ${r.error || 'backup failed'}`);
79
- }
80
- }
81
-
82
- if (results.some(r => !r.ok)) {
83
- console.log('');
84
- console.log('To fix missing symlinks:');
85
- console.log(` loki setup-skill`);
86
- }
87
-
88
- // PATH check: warn if npm global bin is not in PATH, and auto-fix on macOS Homebrew
89
- try {
90
- const { execSync } = require('child_process');
91
- const npmBin = execSync('npm bin -g 2>/dev/null || npm prefix -g', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
92
- const npmBinDir = npmBin.endsWith('/bin') ? npmBin : npmBin + '/bin';
93
- const pathDirs = (process.env.PATH || '').split(':');
94
- const lokiBinInPath = pathDirs.some(d => d === npmBinDir || d === npmBin);
95
-
96
- // On macOS with Homebrew Node, npm global bin path changes on every Node version upgrade
97
- // (e.g., /opt/homebrew/Cellar/node/22.x/bin -> /opt/homebrew/Cellar/node/23.x/bin).
98
- // Use the npm prefix bin directory which is stable across upgrades.
99
- if (os.platform() === 'darwin') {
100
- let stableDir;
101
- try {
102
- stableDir = execSync('npm prefix -g', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim() + '/bin';
103
- } catch {
104
- stableDir = '/opt/homebrew/bin';
105
- }
106
- if (fs.existsSync(stableDir) && stableDir !== npmBinDir) {
107
- for (const bin of ['loki', 'loki-mode']) {
108
- const src = path.join(npmBinDir, bin);
109
- const dest = path.join(stableDir, bin);
110
- try {
111
- if (fs.existsSync(src)) {
112
- // Remove stale symlink or existing file
113
- try { fs.unlinkSync(dest); } catch {}
114
- fs.symlinkSync(src, dest);
115
- }
116
- } catch {
117
- // Silently skip if no permission (non-root)
118
- }
119
- }
120
- }
121
- }
122
-
123
- if (!lokiBinInPath) {
124
- console.log('');
125
- console.log('[IMPORTANT] The `loki` command may not be in your PATH.');
126
- console.log('');
127
- console.log('Add the npm global bin directory to your PATH:');
128
- console.log(` export PATH="${npmBinDir}:$PATH"`);
129
- console.log('');
130
- console.log('To make this permanent, add it to your shell config (~/.zshrc or ~/.bashrc):');
131
- console.log(` echo 'export PATH="${npmBinDir}:$PATH"' >> ~/.zshrc && source ~/.zshrc`);
132
- console.log('');
133
- console.log('Or use the Homebrew tap (sets PATH automatically):');
134
- console.log(' brew tap asklokesh/tap && brew install loki-mode');
135
- }
136
- } catch {
137
- // If npm bin check fails, skip PATH warning silently
138
- }
139
-
140
- // Install Python dependencies for Purple Lab (pexpect, watchdog, httpx)
141
- try {
142
- const { execSync } = require('child_process');
143
- const pyDeps = ['pexpect', 'watchdog', 'httpx'];
144
- // Check if deps are already installed
145
- const missing = pyDeps.filter(dep => {
146
- try {
147
- execSync(`python3 -c "import ${dep}"`, { stdio: 'pipe' });
148
- return false;
149
- } catch { return true; }
150
- });
151
- if (missing.length > 0) {
152
- console.log(`Installing Python dependencies: ${missing.join(', ')}...`);
153
- try {
154
- execSync(`python3 -m pip install --break-system-packages ${missing.join(' ')}`, { stdio: 'pipe', timeout: 60000 });
155
- console.log(' [OK] Python dependencies installed');
156
- } catch {
157
- try {
158
- execSync(`python3 -m pip install ${missing.join(' ')}`, { stdio: 'pipe', timeout: 60000 });
159
- console.log(' [OK] Python dependencies installed');
160
- } catch {
161
- console.log(` [WARN] Could not install Python deps. Run: pip install ${missing.join(' ')}`);
162
- }
163
- }
164
- }
165
- } catch {
166
- // Python not available, skip silently
167
- }
168
-
169
- console.log('');
170
- console.log('CLI commands:');
171
- console.log(' loki start ./prd.md Start with Claude (default)');
172
- console.log(' loki start --provider codex Start with OpenAI Codex');
173
- console.log(' loki start --provider gemini Start with Google Gemini');
174
- console.log(' loki status Check status');
175
- console.log(' loki doctor Verify installation');
176
- console.log(' loki welcome Quick tour, docs, and setup');
177
- console.log(' loki --help Show all commands');
178
- console.log('');
179
- console.log('New here? Run `loki welcome` for a 30-second tour.');
180
- console.log('');
181
-
182
- // Anonymous install telemetry (fire-and-forget, silent).
183
- // Collection is OPT-IN and OFF by default: a default `npm install` (including
184
- // air-gapped, GDPR, and FedRAMP environments) sends NOTHING. This precedence
185
- // mirrors loki_collection_enabled in autonomy/crash.sh, _is_enabled in
186
- // dashboard/telemetry.py, and _loki_telemetry_enabled in autonomy/telemetry.sh.
187
- // 1. Any opt-out flag present -> false (hard kill, always wins)
188
- // 2. Else any opt-in flag present -> true
189
- // 3. Else (default) -> false (no egress)
190
- function _lokiCollectionEnabled() {
191
- const telem = (process.env.LOKI_TELEMETRY || '').toLowerCase();
192
- // 1. Opt-out always wins.
193
- if (telem === 'off') return false;
194
- if (process.env.LOKI_TELEMETRY_DISABLED === 'true') return false;
195
- if (process.env.DO_NOT_TRACK === '1') return false;
196
- let configEnabled = false;
197
- try {
198
- const cfg = path.join(homeDir, '.loki', 'config');
199
- const lines = fs.readFileSync(cfg, 'utf8').split('\n');
200
- if (lines.some((l) => l.startsWith('TELEMETRY_DISABLED=true'))) return false;
201
- if (lines.some((l) => l.startsWith('TELEMETRY_ENABLED=true'))) configEnabled = true;
202
- } catch {}
203
- // 2. Opt-in required.
204
- if (telem === 'on') return true;
205
- if (configEnabled) return true;
206
- // 3. Default: OFF.
207
- return false;
208
- }
209
- try {
210
- if (_lokiCollectionEnabled()) {
211
- const https = require('https');
212
- const crypto = require('crypto');
213
- const idFile = path.join(homeDir, '.loki-telemetry-id');
214
- let distinctId;
215
- try {
216
- distinctId = fs.readFileSync(idFile, 'utf8').trim();
217
- } catch {
218
- distinctId = crypto.randomUUID();
219
- try { fs.writeFileSync(idFile, distinctId + '\n'); } catch {}
220
- }
221
- const payload = JSON.stringify({
222
- api_key: 'phc_ya0vGBru41AJWtGNfZZ8H9W4yjoZy4KON0nnayS7s87',
223
- event: 'install',
224
- distinct_id: distinctId,
225
- properties: {
226
- os: os.platform(),
227
- arch: os.arch(),
228
- version: version,
229
- channel: 'npm',
230
- node_version: process.version,
231
- providers_installed: results.filter(r => r.ok).map(r => r.name).join(','),
232
- },
233
- });
234
- const req = https.request({
235
- hostname: 'us.i.posthog.com',
236
- path: '/capture/',
237
- method: 'POST',
238
- headers: { 'Content-Type': 'application/json', 'Content-Length': payload.length },
239
- timeout: 3000,
240
- });
241
- req.on('error', () => {});
242
- req.end(payload);
243
- }
244
- } catch {}