loki-mode 7.120.0 → 7.121.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/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.1
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.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.120.0
1
+ 7.121.1
@@ -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,74 @@ 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 POSITIVE PROOF that at
146
+ # least one test actually ran and passed. rc==0 alone is NOT
147
+ # proof: a no-op test script (`echo done`, `exit 0`, `true`, `:`)
148
+ # exits 0 having run ZERO tests -> passing on rc==0 would be a
149
+ # FAKE-GREEN (a required verification green with nothing tested).
150
+ # So we require a runner's "N passed" signal. Absence of that
151
+ # signal on rc==0 is INCONCLUSIVE (None -> pending), never True
152
+ # (fake-green) and never False (that would fake-RED an exotic
153
+ # passing runner). rc!=0 with real failures -> honest False. This
154
+ # is the same inconclusive-never-false moat as grep_codebase.
108
155
  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
156
+ "no tests found" in _low # jest
157
+ or "no test files found" in _low # vitest
158
+ or "no tests ran" in _low # pytest/jest phrasing
159
+ or "no tests to run" in _low
160
+ or proc.returncode == 5 # pytest: none collected
161
+ )
162
+ # Positive "tests ran and passed" signals across common runners.
163
+ # jest: "Tests: 3 passed" vitest: "Tests 26 passed (26)"
164
+ # pytest:"3 passed in 0.05s" mocha: "3 passing"
165
+ # node:test: "# pass 3" tap: "pass 3"
166
+ # The count MUST be >=1 (`[1-9]\d*`, never a literal 0): a
167
+ # runner can print "0 passed" / "0 passing" (mocha over an
168
+ # empty describe, all-.skip, node:test "# pass 0") on rc=0 with
169
+ # ZERO tests run -- matching a bare `\d+` would re-open the
170
+ # fake-green. A zero count falls through to the inconclusive
171
+ # else -> None (pending), correctly never green.
172
+ _ran_and_passed = bool(
173
+ re.search(r'tests?:\s*[1-9]\d*\s+passed', _low) # jest
174
+ or re.search(r'tests?\s+[1-9]\d*\s+passed', _low) # vitest
175
+ or re.search(r'\b[1-9]\d*\s+passed\b', _low) # pytest/vitest
176
+ or re.search(r'\b[1-9]\d*\s+passing\b', _low) # mocha
177
+ or re.search(r'#\s*pass\s+[1-9]\d*', _low) # node:test
178
+ or re.search(r'\bpass\s+[1-9]\d*\b', _low) # tap
113
179
  )
114
180
  if no_tests:
115
181
  result["passed"] = False
116
182
  result["output"] = (
117
183
  "No tests discovered for required check "
118
- f"(pattern={pattern!r}); a tests_pass check must run "
119
- "at least one test. Output: "
184
+ "(a tests_pass check must run at least one test). "
185
+ "Output: "
120
186
  ) + combined[:400]
121
- else:
122
- result["passed"] = proc.returncode == 0
187
+ elif proc.returncode != 0:
188
+ # Ran and FAILED -> honest False (blocks). A non-zero exit
189
+ # from a real runner is a genuine negative, not inconclusive.
190
+ result["passed"] = False
123
191
  result["output"] = combined[:500]
192
+ elif _ran_and_passed:
193
+ # rc==0 WITH positive "N passed" proof -> True.
194
+ result["passed"] = True
195
+ result["output"] = combined[:500]
196
+ else:
197
+ # rc==0 but NO positive "tests ran" signal (a no-op script,
198
+ # or a runner whose output we do not recognise). We cannot
199
+ # prove tests ran -> INCONCLUSIVE (None -> pending), never a
200
+ # fake-green True and never a fake-RED False.
201
+ result["passed"] = None
202
+ result["output"] = (
203
+ "Test command exited 0 but produced no recognisable "
204
+ "'N passed' signal -- cannot confirm any test ran "
205
+ "(inconclusive, not a pass). Output: "
206
+ ) + combined[:350]
124
207
  except subprocess.TimeoutExpired:
125
- result["passed"] = None # timeout = pending
208
+ result["passed"] = None # timeout = pending (couldn't run)
126
209
  result["output"] = f"Timed out after {timeout}s"
127
210
  except FileNotFoundError:
128
- result["passed"] = None
211
+ result["passed"] = None # runner not found = couldn't run
129
212
  result["output"] = "Test runner not found"
130
213
  else:
131
214
  result["passed"] = None
@@ -169,9 +252,17 @@ def run_check(check: dict, project_dir: str, timeout: int) -> dict:
169
252
  elif pattern:
170
253
  try:
171
254
  # grep with --exclude-dir for safety (no .git, node_modules)
172
- # Use '--' to prevent pattern being interpreted as flags
255
+ # Use '--' to prevent pattern being interpreted as flags.
256
+ # Use -E (ERE) NOT the default BRE: LLM-emitted patterns are
257
+ # ERE/PCRE-flavoured (e.g. app\.get\('/api/tasks'). In BRE an
258
+ # escaped `\(` is a GROUP-OPEN, so an unmatched one makes grep
259
+ # error 'parentheses not balanced' (rc=2) -- a fake-RED that
260
+ # marked 3 present endpoints failing -> a 64-min non-converging
261
+ # build (#142/#124). Under -E, `\(` is a LITERAL paren (no
262
+ # error) AND quantifiers `.` `*` `+` keep their regex meaning,
263
+ # so a present-as-regex endpoint matches instead of erroring.
173
264
  proc = subprocess.run(
174
- ["grep", "-r", "-l",
265
+ ["grep", "-r", "-l", "-E",
175
266
  "--exclude-dir=.git", "--exclude-dir=node_modules",
176
267
  "--exclude-dir=.loki", "--exclude-dir=__pycache__",
177
268
  "--", pattern, "."],
@@ -180,9 +271,74 @@ def run_check(check: dict, project_dir: str, timeout: int) -> dict:
180
271
  text=True,
181
272
  timeout=timeout,
182
273
  )
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)"
274
+ # grep exit codes: 0=match found, 1=no match, >=2=ERROR (bad
275
+ # regex, unreadable file, or a grep variant -- e.g. ugrep on
276
+ # macOS -- that rejects a pattern GNU grep accepts). A grep that
277
+ # ERRORED has NOT proven the pattern is absent, so it must be
278
+ # INCONCLUSIVE (passed=None), NOT a hard False. Collapsing an
279
+ # error to False is a fake-RED: it marks a genuinely-present
280
+ # endpoint 'failing' -> the council hard-gate blocks a correct
281
+ # build forever (observed: an LLM-emitted pattern app\.get\('...'
282
+ # made ugrep error position-26 mismatched-paren -> returncode 2
283
+ # -> 3 working endpoints read failing -> a 64-min non-converging
284
+ # build). Only a clean 1 (real no-match) is an honest False.
285
+ if proc.returncode == 0:
286
+ result["passed"] = True
287
+ files_found = proc.stdout.strip().split("\n") if proc.stdout.strip() else []
288
+ result["output"] = f"Found in {len(files_found)} file(s)"
289
+ elif proc.returncode == 1:
290
+ result["passed"] = False
291
+ result["output"] = "Found in 0 file(s)"
292
+ else:
293
+ # rc>=2 = grep ERROR even under -E (e.g. a genuinely
294
+ # malformed pattern, or an unreadable file). Do NOT collapse
295
+ # to False (fake-RED) and do NOT punt straight to
296
+ # inconclusive (that opens a fake-green: an ABSENT endpoint
297
+ # whose pattern also errors would read pending, not failing).
298
+ # RECOVER real signal by retrying as a FIXED string (grep -F,
299
+ # escapes stripped to the intended literal), which cannot
300
+ # error on metacharacters. A fixed-string retry is a strict
301
+ # subset match: rc=0 (literal present) is a sound True; a
302
+ # literal-absent rc=1 keeps the honest-False moat for the
303
+ # common case. Only if -F ALSO errors is the check truly
304
+ # unrecoverable -> inconclusive (None), never a hard fail.
305
+ # NB: the -E primary already resolves the whole LLM
306
+ # escaped-paren class, so this branch is now rarely reached.
307
+ literal = re.sub(r'\\(.)', r'\1', pattern)
308
+ try:
309
+ proc2 = subprocess.run(
310
+ ["grep", "-r", "-l", "-F",
311
+ "--exclude-dir=.git", "--exclude-dir=node_modules",
312
+ "--exclude-dir=.loki", "--exclude-dir=__pycache__",
313
+ "--", literal, "."],
314
+ cwd=project_dir, capture_output=True,
315
+ text=True, timeout=timeout,
316
+ )
317
+ except subprocess.TimeoutExpired:
318
+ proc2 = None
319
+ if proc2 is not None and proc2.returncode == 0:
320
+ files_found = proc2.stdout.strip().split("\n") if proc2.stdout.strip() else []
321
+ result["passed"] = True
322
+ result["output"] = f"Found in {len(files_found)} file(s) (fixed-string retry after regex error)"
323
+ else:
324
+ # -F rc=1 (literal absent) or rc>=2 (also errored) ->
325
+ # INCONCLUSIVE (None), never False. Once -E has failed to
326
+ # parse the pattern, the escape-stripped literal cannot
327
+ # distinguish "present only via a regex quantifier" from
328
+ # "genuinely absent" -- so a literal-absent here is NOT
329
+ # proof of absence. Mapping it to False would re-open the
330
+ # narrowed fake-RED. None -> item 'pending' (never blocks
331
+ # a correct build); a genuinely-absent endpoint is caught
332
+ # by the -E primary's honest rc=1 False above, not here.
333
+ result["passed"] = None
334
+ _err = (proc.stderr or "").strip().splitlines()
335
+ _why = ("literal-absent, cannot prove absence"
336
+ if (proc2 is not None and proc2.returncode == 1)
337
+ else (_err[0] if _err else f"exit {proc.returncode}"))
338
+ result["output"] = (
339
+ "grep regex error, unrecoverable (inconclusive, not a failure): "
340
+ + _why
341
+ )[:200]
186
342
  except subprocess.TimeoutExpired:
187
343
  result["passed"] = None
188
344
  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.1"
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.1
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.1";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=18E173D8ADB8057464756E2164756E21
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.1'
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.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": "7.120.0",
5
+ "version": "7.121.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",