loki-mode 7.120.0 → 7.121.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.120.0
6
+ # Loki Mode v7.121.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.120.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
411
+ **v7.121.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.120.0
1
+ 7.121.0
@@ -53,12 +53,19 @@ def run_check(check: dict, project_dir: str, timeout: int) -> dict:
53
53
 
54
54
  try:
55
55
  if check_type == "file_exists":
56
- path = check.get("path", "")
56
+ # The checklist is LLM-emitted (generated_from a PRD), so the path
57
+ # field name is not pinnable: real checks in the wild carry the path
58
+ # under "path" OR "target" OR "file". Reading only "path" made a valid
59
+ # file_exists on a present file (target="package.json") raise
60
+ # "Invalid path characters: ''" -> None -> the item stuck 'pending' ->
61
+ # a correct build never reached a verified card. Alias the variants
62
+ # (same fake-inconclusive class as the grep/tests_pass runner bugs).
63
+ path = check.get("path") or check.get("target") or check.get("file") or ""
57
64
  full_path = _validate_path(path, project_dir)
58
65
  result["passed"] = os.path.exists(full_path)
59
66
 
60
67
  elif check_type == "file_contains":
61
- path = check.get("path", "")
68
+ path = check.get("path") or check.get("target") or check.get("file") or ""
62
69
  pattern = check.get("pattern", "")
63
70
  if pattern and not _SAFE_PATTERN_RE.match(pattern):
64
71
  result["passed"] = None
@@ -84,11 +91,47 @@ def run_check(check: dict, project_dir: str, timeout: int) -> dict:
84
91
  result["passed"] = None
85
92
  result["output"] = f"Unsafe pattern rejected: {pattern!r}"
86
93
  elif pattern:
87
- # Use list form (shell=False) to prevent injection
88
- if os.path.isfile(os.path.join(project_dir, "package.json")):
89
- cmd = ["npx", "jest", "--testPathPattern", pattern, "--passWithNoTests"]
94
+ # Runner-agnostic: use the PROJECT'S OWN declared test command
95
+ # rather than hardcoding a runner. A project's package.json
96
+ # "scripts.test" may be vitest / jest / mocha / node:test / ava /
97
+ # etc. Hardcoding `npx jest` was a fake-RED: on a vitest project
98
+ # (cdbbb5af, "test":"vitest run", jest not installed) `npx jest`
99
+ # either errored on the drifted --testPathPattern flag or npx tried
100
+ # to FETCH jest and timed out -> a genuinely-passing 26/26 suite read
101
+ # False/None ("tests not run") -> the item never reaches 'verified'
102
+ # -> a correct build never shows a verified card. Running the
103
+ # declared command runs the runner the build actually chose, so it
104
+ # is model-/complexity-agnostic. NB: we run the WHOLE declared suite
105
+ # (the per-item `pattern` is not a runner filter -- a mismatched
106
+ # vitest/jest filter can exit 0 with zero tests run = a fake-green).
107
+ pkg_path = os.path.join(project_dir, "package.json")
108
+ test_script = None
109
+ if os.path.isfile(pkg_path):
110
+ try:
111
+ with open(pkg_path) as _pf:
112
+ _pkg = json.load(_pf)
113
+ test_script = (_pkg.get("scripts") or {}).get("test")
114
+ except (json.JSONDecodeError, OSError):
115
+ test_script = None
116
+ if os.path.isfile(pkg_path):
117
+ if test_script and test_script.strip():
118
+ # `npm test` runs scripts.test with the project's local
119
+ # runner on PATH; list-form + shell=False + cwd = same
120
+ # posture as before (the engine already ran this exact
121
+ # command to build the workspace -- not a new surface).
122
+ cmd = ["npm", "test", "--silent"]
123
+ else:
124
+ # package.json but no test script -> nothing declared to
125
+ # run. Do NOT invent a runner (that reintroduces the
126
+ # hardcoded-runner fake-RED). Inconclusive, not a failure.
127
+ result["passed"] = None
128
+ result["output"] = (
129
+ "No 'scripts.test' declared in package.json; cannot run "
130
+ "the project's tests (inconclusive, not a failure)."
131
+ )
132
+ return result
90
133
  else:
91
- cmd = ["python3", "-m", "pytest", "-q", pattern]
134
+ cmd = ["python3", "-m", "pytest", "-q"]
92
135
  try:
93
136
  proc = subprocess.run(
94
137
  cmd,
@@ -98,34 +141,39 @@ def run_check(check: dict, project_dir: str, timeout: int) -> dict:
98
141
  timeout=timeout,
99
142
  )
100
143
  combined = proc.stdout + proc.stderr
101
- # Trust gate: a tests_pass check REQUIRES that at least one
102
- # test was actually discovered and run. jest is invoked with
103
- # --passWithNoTests, so a zero-match pattern exits 0 ("No
104
- # tests found ...") -- that would be a fake-green (a required
105
- # verification passing with nothing run). pytest exits 5 when
106
- # it collects no tests. Detect either no-test signal and fail
107
- # the check rather than report success on an empty run.
144
+ _low = combined.lower()
145
+ # Trust gate: a tests_pass check REQUIRES that at least one test
146
+ # actually ran. A runner can exit 0 with nothing executed
147
+ # (jest --passWithNoTests, a mismatched vitest filter, a no-op
148
+ # `echo` test script) -> that would be a FAKE-GREEN (a required
149
+ # verification passing with zero tests). Detect the no-test
150
+ # signals across runners and refuse to pass on an empty run.
108
151
  no_tests = (
109
- "No tests found" in combined
110
- or "no tests ran" in combined.lower()
111
- or "no tests to run" in combined.lower()
112
- or proc.returncode == 5 # pytest: no tests collected
152
+ "no tests found" in _low # jest
153
+ or "no test files found" in _low # vitest
154
+ or "no tests ran" in _low # pytest/jest phrasing
155
+ or "no tests to run" in _low
156
+ or proc.returncode == 5 # pytest: none collected
113
157
  )
114
158
  if no_tests:
115
159
  result["passed"] = False
116
160
  result["output"] = (
117
161
  "No tests discovered for required check "
118
- f"(pattern={pattern!r}); a tests_pass check must run "
119
- "at least one test. Output: "
162
+ "(a tests_pass check must run at least one test). "
163
+ "Output: "
120
164
  ) + combined[:400]
121
165
  else:
166
+ # rc==0 with tests run -> True; ran and FAILED -> False
167
+ # (blocks). "Ran and failed" is an honest False; only a
168
+ # runner that could not RUN (timeout / not found, below)
169
+ # is inconclusive None -- same moat as grep_codebase.
122
170
  result["passed"] = proc.returncode == 0
123
171
  result["output"] = combined[:500]
124
172
  except subprocess.TimeoutExpired:
125
- result["passed"] = None # timeout = pending
173
+ result["passed"] = None # timeout = pending (couldn't run)
126
174
  result["output"] = f"Timed out after {timeout}s"
127
175
  except FileNotFoundError:
128
- result["passed"] = None
176
+ result["passed"] = None # runner not found = couldn't run
129
177
  result["output"] = "Test runner not found"
130
178
  else:
131
179
  result["passed"] = None
@@ -169,9 +217,17 @@ def run_check(check: dict, project_dir: str, timeout: int) -> dict:
169
217
  elif pattern:
170
218
  try:
171
219
  # grep with --exclude-dir for safety (no .git, node_modules)
172
- # Use '--' to prevent pattern being interpreted as flags
220
+ # Use '--' to prevent pattern being interpreted as flags.
221
+ # Use -E (ERE) NOT the default BRE: LLM-emitted patterns are
222
+ # ERE/PCRE-flavoured (e.g. app\.get\('/api/tasks'). In BRE an
223
+ # escaped `\(` is a GROUP-OPEN, so an unmatched one makes grep
224
+ # error 'parentheses not balanced' (rc=2) -- a fake-RED that
225
+ # marked 3 present endpoints failing -> a 64-min non-converging
226
+ # build (#142/#124). Under -E, `\(` is a LITERAL paren (no
227
+ # error) AND quantifiers `.` `*` `+` keep their regex meaning,
228
+ # so a present-as-regex endpoint matches instead of erroring.
173
229
  proc = subprocess.run(
174
- ["grep", "-r", "-l",
230
+ ["grep", "-r", "-l", "-E",
175
231
  "--exclude-dir=.git", "--exclude-dir=node_modules",
176
232
  "--exclude-dir=.loki", "--exclude-dir=__pycache__",
177
233
  "--", pattern, "."],
@@ -180,9 +236,74 @@ def run_check(check: dict, project_dir: str, timeout: int) -> dict:
180
236
  text=True,
181
237
  timeout=timeout,
182
238
  )
183
- result["passed"] = proc.returncode == 0
184
- files_found = proc.stdout.strip().split("\n") if proc.stdout.strip() else []
185
- result["output"] = f"Found in {len(files_found)} file(s)"
239
+ # grep exit codes: 0=match found, 1=no match, >=2=ERROR (bad
240
+ # regex, unreadable file, or a grep variant -- e.g. ugrep on
241
+ # macOS -- that rejects a pattern GNU grep accepts). A grep that
242
+ # ERRORED has NOT proven the pattern is absent, so it must be
243
+ # INCONCLUSIVE (passed=None), NOT a hard False. Collapsing an
244
+ # error to False is a fake-RED: it marks a genuinely-present
245
+ # endpoint 'failing' -> the council hard-gate blocks a correct
246
+ # build forever (observed: an LLM-emitted pattern app\.get\('...'
247
+ # made ugrep error position-26 mismatched-paren -> returncode 2
248
+ # -> 3 working endpoints read failing -> a 64-min non-converging
249
+ # build). Only a clean 1 (real no-match) is an honest False.
250
+ if proc.returncode == 0:
251
+ result["passed"] = True
252
+ files_found = proc.stdout.strip().split("\n") if proc.stdout.strip() else []
253
+ result["output"] = f"Found in {len(files_found)} file(s)"
254
+ elif proc.returncode == 1:
255
+ result["passed"] = False
256
+ result["output"] = "Found in 0 file(s)"
257
+ else:
258
+ # rc>=2 = grep ERROR even under -E (e.g. a genuinely
259
+ # malformed pattern, or an unreadable file). Do NOT collapse
260
+ # to False (fake-RED) and do NOT punt straight to
261
+ # inconclusive (that opens a fake-green: an ABSENT endpoint
262
+ # whose pattern also errors would read pending, not failing).
263
+ # RECOVER real signal by retrying as a FIXED string (grep -F,
264
+ # escapes stripped to the intended literal), which cannot
265
+ # error on metacharacters. A fixed-string retry is a strict
266
+ # subset match: rc=0 (literal present) is a sound True; a
267
+ # literal-absent rc=1 keeps the honest-False moat for the
268
+ # common case. Only if -F ALSO errors is the check truly
269
+ # unrecoverable -> inconclusive (None), never a hard fail.
270
+ # NB: the -E primary already resolves the whole LLM
271
+ # escaped-paren class, so this branch is now rarely reached.
272
+ literal = re.sub(r'\\(.)', r'\1', pattern)
273
+ try:
274
+ proc2 = subprocess.run(
275
+ ["grep", "-r", "-l", "-F",
276
+ "--exclude-dir=.git", "--exclude-dir=node_modules",
277
+ "--exclude-dir=.loki", "--exclude-dir=__pycache__",
278
+ "--", literal, "."],
279
+ cwd=project_dir, capture_output=True,
280
+ text=True, timeout=timeout,
281
+ )
282
+ except subprocess.TimeoutExpired:
283
+ proc2 = None
284
+ if proc2 is not None and proc2.returncode == 0:
285
+ files_found = proc2.stdout.strip().split("\n") if proc2.stdout.strip() else []
286
+ result["passed"] = True
287
+ result["output"] = f"Found in {len(files_found)} file(s) (fixed-string retry after regex error)"
288
+ else:
289
+ # -F rc=1 (literal absent) or rc>=2 (also errored) ->
290
+ # INCONCLUSIVE (None), never False. Once -E has failed to
291
+ # parse the pattern, the escape-stripped literal cannot
292
+ # distinguish "present only via a regex quantifier" from
293
+ # "genuinely absent" -- so a literal-absent here is NOT
294
+ # proof of absence. Mapping it to False would re-open the
295
+ # narrowed fake-RED. None -> item 'pending' (never blocks
296
+ # a correct build); a genuinely-absent endpoint is caught
297
+ # by the -E primary's honest rc=1 False above, not here.
298
+ result["passed"] = None
299
+ _err = (proc.stderr or "").strip().splitlines()
300
+ _why = ("literal-absent, cannot prove absence"
301
+ if (proc2 is not None and proc2.returncode == 1)
302
+ else (_err[0] if _err else f"exit {proc.returncode}"))
303
+ result["output"] = (
304
+ "grep regex error, unrecoverable (inconclusive, not a failure): "
305
+ + _why
306
+ )[:200]
186
307
  except subprocess.TimeoutExpired:
187
308
  result["passed"] = None
188
309
  result["output"] = f"Timed out after {timeout}s"
@@ -423,8 +423,15 @@ verify_gate_tests() {
423
423
  # them so the gate runs (or goes inconclusive -> CONCERNS when no
424
424
  # runner is installed), never silently skipped.
425
425
  if [ "$has_python" = "false" ]; then
426
- if find "$tree" -maxdepth 1 -type f \
426
+ # maxdepth 2 (was 1) + exclusions to MIRROR run.sh's enforce_test_coverage
427
+ # detection exactly (#139 parity): a shallow-but-not-root test_*.py (e.g.
428
+ # a src/-adjacent test) is now seen by BOTH gates, not just run.sh. Still
429
+ # the safe direction (under-detect only, never a fake-green): worst case a
430
+ # deeper test dir is inconclusive, never falsely VERIFIED.
431
+ if find "$tree" -maxdepth 2 -type f \
427
432
  \( -name 'test_*.py' -o -name '*_test.py' \) \
433
+ -not -path '*/.loki/*' -not -path '*/.git/*' -not -path '*/node_modules/*' \
434
+ -not -path '*/.venv/*' -not -path '*/venv/*' \
428
435
  -print -quit 2>/dev/null | grep -q .; then
429
436
  has_python=true
430
437
  fi
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.120.0"
10
+ __version__ = "7.121.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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.120.0
5
+ **Version:** v7.121.0
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.120.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=B8(M8(import.meta.url)),Z=$1(Q);Q$=U8(Y8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var Q1=P(()=>{C()});var p1={};b(p1,{runOrThrow:()=>S8,run:()=>R,readStreamCapped:()=>c1,commandVersion:()=>C8,commandExists:()=>u,ShellError:()=>Z1,MAX_STDOUT_BYTES:()=>u1});async function c1($,Q=u1){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 R($,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([c1(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 S8($,Q={}){let Z=await R($,Q);if(Z.exitCode!==0)throw new Z1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function u($){let Q=D8($),Z=await R(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function D8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function C8($,Q="--version"){if(!await u($))return null;let z=await R([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var u1=16777216,Z1;var o=P(()=>{Z1=class Z1 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 b8?"":$}var b8,M,S,_,tZ,L,k,y,J;var p=P(()=>{b8=(process.env.NO_COLOR??"").length>0;M=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),tZ=r("\x1B[0;34m"),L=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as l8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(l8($))return A$=$,$;let Q=await u("python3.12");if(Q)return A$=Q,Q;let Z=await u("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return R([Z,"-c",$],Q)}var A$;var V$=P(()=>{o()});var V0={};b(V0,{runStatus:()=>U3});import{existsSync as v,readFileSync as H$,readdirSync as $0,statSync as Q0}from"fs";import{resolve as D,basename as z3}from"path";import{homedir as X3}from"os";function Z0($){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 z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*C$/Q);if(X>C$)X=C$;let q=C$-X,K=S;if(z>=80)K=M;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Z0($),H=Z0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${H})`}async function q3(){if(await u("jq"))return!0;return process.stdout.write(`${M}Error: jq is required but not installed.${J}
2
+ var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.121.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=B8(M8(import.meta.url)),Z=$1(Q);Q$=U8(Y8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var Q1=P(()=>{C()});var p1={};b(p1,{runOrThrow:()=>S8,run:()=>R,readStreamCapped:()=>c1,commandVersion:()=>C8,commandExists:()=>u,ShellError:()=>Z1,MAX_STDOUT_BYTES:()=>u1});async function c1($,Q=u1){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 R($,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([c1(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 S8($,Q={}){let Z=await R($,Q);if(Z.exitCode!==0)throw new Z1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function u($){let Q=D8($),Z=await R(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function D8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function C8($,Q="--version"){if(!await u($))return null;let z=await R([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var u1=16777216,Z1;var o=P(()=>{Z1=class Z1 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 b8?"":$}var b8,M,S,_,tZ,L,k,y,J;var p=P(()=>{b8=(process.env.NO_COLOR??"").length>0;M=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),tZ=r("\x1B[0;34m"),L=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as l8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(l8($))return A$=$,$;let Q=await u("python3.12");if(Q)return A$=Q,Q;let Z=await u("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return R([Z,"-c",$],Q)}var A$;var V$=P(()=>{o()});var V0={};b(V0,{runStatus:()=>U3});import{existsSync as v,readFileSync as H$,readdirSync as $0,statSync as Q0}from"fs";import{resolve as D,basename as z3}from"path";import{homedir as X3}from"os";function Z0($){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 z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*C$/Q);if(X>C$)X=C$;let q=C$-X,K=S;if(z>=80)K=M;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Z0($),H=Z0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${H})`}async function q3(){if(await u("jq"))return!0;return process.stdout.write(`${M}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)
@@ -814,4 +814,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
814
814
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
815
815
  `),process.stderr.write(K8),2}}e1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var SZ=await NZ(Bun.argv.slice(2));process.exit(SZ);
816
816
 
817
- //# debugId=4C6F828B3BD8743B64756E2164756E21
817
+ //# debugId=3745511B1A35D37D64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.120.0'
60
+ __version__ = '7.121.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.120.0",
4
+ "version": "7.121.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.120.0",
5
+ "version": "7.121.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",