loki-mode 7.113.0 → 7.115.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +131 -5
- package/autonomy/lib/effort_estimator.py +397 -0
- package/autonomy/lib/proof-generator.py +22 -0
- package/autonomy/prd-checklist.sh +329 -21
- package/autonomy/run.sh +106 -6
- package/autonomy/verify.sh +203 -0
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/mcp/lsp_proxy.py +41 -0
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/autonomy/verify.sh
CHANGED
|
@@ -170,6 +170,71 @@ verify_diff_base() {
|
|
|
170
170
|
return 0
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# Code-scope / locality record (rank 10, ADVISORY-FIRST).
|
|
175
|
+
#
|
|
176
|
+
# Turns the already-computed VERIFY_DIFF_FILES / VERIFY_DIFF_INS / VERIFY_DIFF_DEL
|
|
177
|
+
# into a structured scope record: {files_changed, net_lines, verdict, thresholds}.
|
|
178
|
+
# net_lines is line GROWTH (insertions - deletions), matching "net line growth" --
|
|
179
|
+
# it goes negative on deletion-heavy diffs (only growth trips a threshold).
|
|
180
|
+
#
|
|
181
|
+
# ADVISORY BY DEFAULT. The verdict is a LABEL in the record only; this helper does
|
|
182
|
+
# NOT emit a gate row and does NOT feed verify_compute_verdict, so it can never
|
|
183
|
+
# change the exit code or block a build (the friction-regression guard, and the
|
|
184
|
+
# greenfield byte-identity guarantee -- loki start never reaches verify_main).
|
|
185
|
+
#
|
|
186
|
+
# Thresholds:
|
|
187
|
+
# - Soft thresholds (LOKI_SCOPE_SOFT_FILES / LOKI_SCOPE_SOFT_NET_LINES) have sane
|
|
188
|
+
# defaults; exceeding a soft threshold with NO hard cap set -> verdict "warn".
|
|
189
|
+
# - Hard caps (LOKI_SCOPE_MAX_FILES / LOKI_SCOPE_MAX_NET_LINES) are OPT-IN: only
|
|
190
|
+
# when such an env var is EXPLICITLY SET and exceeded does the verdict become
|
|
191
|
+
# "block". Even then this record does not by itself block the build; it is the
|
|
192
|
+
# opt-in signal a consumer/enforcer can act on.
|
|
193
|
+
# - Under all applicable thresholds -> "ok".
|
|
194
|
+
#
|
|
195
|
+
# Sets globals: VERIFY_SCOPE_FILES VERIFY_SCOPE_NET_LINES VERIFY_SCOPE_VERDICT
|
|
196
|
+
# VERIFY_SCOPE_SOFT_FILES VERIFY_SCOPE_SOFT_NET_LINES
|
|
197
|
+
# VERIFY_SCOPE_MAX_FILES VERIFY_SCOPE_MAX_NET_LINES (empty when unset)
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
verify_scope_record() {
|
|
200
|
+
local files="${VERIFY_DIFF_FILES:-0}"
|
|
201
|
+
local ins="${VERIFY_DIFF_INS:-0}"
|
|
202
|
+
local del="${VERIFY_DIFF_DEL:-0}"
|
|
203
|
+
local net=$(( ins - del ))
|
|
204
|
+
|
|
205
|
+
# Soft advisory thresholds (defaults; overridable).
|
|
206
|
+
local soft_files="${LOKI_SCOPE_SOFT_FILES:-25}"
|
|
207
|
+
local soft_net="${LOKI_SCOPE_SOFT_NET_LINES:-500}"
|
|
208
|
+
# Hard caps are opt-in: unset means "never block".
|
|
209
|
+
local max_files="${LOKI_SCOPE_MAX_FILES:-}"
|
|
210
|
+
local max_net="${LOKI_SCOPE_MAX_NET_LINES:-}"
|
|
211
|
+
|
|
212
|
+
local verdict="ok"
|
|
213
|
+
|
|
214
|
+
# Hard-cap check first (only when explicitly set) -> "block".
|
|
215
|
+
if [ -n "$max_files" ] && [ "$files" -gt "$max_files" ] 2>/dev/null; then
|
|
216
|
+
verdict="block"
|
|
217
|
+
elif [ -n "$max_net" ] && [ "$net" -gt "$max_net" ] 2>/dev/null; then
|
|
218
|
+
verdict="block"
|
|
219
|
+
# Soft threshold (advisory, no hard cap tripped) -> "warn".
|
|
220
|
+
elif [ "$files" -gt "$soft_files" ] 2>/dev/null; then
|
|
221
|
+
verdict="warn"
|
|
222
|
+
elif [ "$net" -gt "$soft_net" ] 2>/dev/null; then
|
|
223
|
+
verdict="warn"
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
VERIFY_SCOPE_FILES="$files"
|
|
227
|
+
VERIFY_SCOPE_NET_LINES="$net"
|
|
228
|
+
VERIFY_SCOPE_VERDICT="$verdict"
|
|
229
|
+
VERIFY_SCOPE_SOFT_FILES="$soft_files"
|
|
230
|
+
VERIFY_SCOPE_SOFT_NET_LINES="$soft_net"
|
|
231
|
+
VERIFY_SCOPE_MAX_FILES="$max_files"
|
|
232
|
+
VERIFY_SCOPE_MAX_NET_LINES="$max_net"
|
|
233
|
+
|
|
234
|
+
_verify_log "scope: $files files, net $net lines -> $verdict (soft $soft_files/$soft_net, hard ${max_files:-unset}/${max_net:-unset})"
|
|
235
|
+
return 0
|
|
236
|
+
}
|
|
237
|
+
|
|
173
238
|
# ---------------------------------------------------------------------------
|
|
174
239
|
# Gate: build (faithful extension of run.sh language detection).
|
|
175
240
|
#
|
|
@@ -916,6 +981,100 @@ PYEOF
|
|
|
916
981
|
return 0
|
|
917
982
|
}
|
|
918
983
|
|
|
984
|
+
# ---------------------------------------------------------------------------
|
|
985
|
+
# Setup-recipe WRITER (rank 7). Writes .loki/setup-recipe.json after a VERIFIED
|
|
986
|
+
# runtime boot, capturing the deterministic steps to reach a runnable state so
|
|
987
|
+
# later verify/e2e runs replay the recipe instead of re-detecting heuristically
|
|
988
|
+
# (the CONSUMER already exists in _verify_runtime_detect: it reads "start"/"port").
|
|
989
|
+
#
|
|
990
|
+
# Keys: {install, seed, env_keys, start, health_path, port}.
|
|
991
|
+
# - env_keys are env var NAMES ONLY, harvested from .env.example / .env in the
|
|
992
|
+
# tree (left side of `NAME=value`). SECRET VALUES ARE NEVER PERSISTED.
|
|
993
|
+
# - install/seed are best-effort deterministic detections (npm ci / pip install
|
|
994
|
+
# / go mod download; a seed script when present). Empty string when unknown.
|
|
995
|
+
#
|
|
996
|
+
# Best effort and fail-open: any failure leaves no recipe and never changes the
|
|
997
|
+
# gate outcome. Called ONLY from the boot-pass branch, so a failed boot writes
|
|
998
|
+
# nothing.
|
|
999
|
+
#
|
|
1000
|
+
# Args: $1 tree $2 start_command $3 port $4 health_path
|
|
1001
|
+
# ---------------------------------------------------------------------------
|
|
1002
|
+
_verify_write_setup_recipe() {
|
|
1003
|
+
local tree="$1" start="$2" port="$3" health_path="$4"
|
|
1004
|
+
command -v python3 >/dev/null 2>&1 || return 0
|
|
1005
|
+
[ -d "$tree" ] || return 0
|
|
1006
|
+
|
|
1007
|
+
# Detect a deterministic install command from the tree (best effort).
|
|
1008
|
+
local install=""
|
|
1009
|
+
if [ -f "$tree/package-lock.json" ]; then
|
|
1010
|
+
install="npm ci"
|
|
1011
|
+
elif [ -f "$tree/package.json" ]; then
|
|
1012
|
+
install="npm install"
|
|
1013
|
+
elif [ -f "$tree/requirements.txt" ]; then
|
|
1014
|
+
install="pip install -r requirements.txt"
|
|
1015
|
+
elif [ -f "$tree/go.mod" ]; then
|
|
1016
|
+
install="go mod download"
|
|
1017
|
+
elif [ -f "$tree/Cargo.toml" ]; then
|
|
1018
|
+
install="cargo fetch"
|
|
1019
|
+
fi
|
|
1020
|
+
|
|
1021
|
+
# Detect a seed step (best effort): package.json "seed" script, else empty.
|
|
1022
|
+
local seed=""
|
|
1023
|
+
if [ -f "$tree/package.json" ] && grep -q '"seed"[[:space:]]*:' "$tree/package.json" 2>/dev/null; then
|
|
1024
|
+
seed="npm run seed"
|
|
1025
|
+
fi
|
|
1026
|
+
|
|
1027
|
+
# Harvest env var NAMES only from .env.example / .env (never the values).
|
|
1028
|
+
# The python writer parses these; we hand it the file paths that exist.
|
|
1029
|
+
local env_src=""
|
|
1030
|
+
[ -f "$tree/.env.example" ] && env_src="$tree/.env.example"
|
|
1031
|
+
[ -z "$env_src" ] && [ -f "$tree/.env" ] && env_src="$tree/.env"
|
|
1032
|
+
|
|
1033
|
+
_SR_DIR="$tree/.loki" _SR_INSTALL="$install" _SR_SEED="$seed" \
|
|
1034
|
+
_SR_START="$start" _SR_HEALTH="$health_path" _SR_PORT="$port" \
|
|
1035
|
+
_SR_ENVSRC="$env_src" \
|
|
1036
|
+
python3 - <<'PYEOF' 2>/dev/null || return 0
|
|
1037
|
+
import json, os, re
|
|
1038
|
+
|
|
1039
|
+
out_dir = os.environ["_SR_DIR"]
|
|
1040
|
+
|
|
1041
|
+
# env_keys: NAMES ONLY. Parse KEY=VALUE / export KEY=VALUE lines, keep the KEY.
|
|
1042
|
+
# The value side is never read into the recipe (security AC).
|
|
1043
|
+
env_keys = []
|
|
1044
|
+
src = os.environ.get("_SR_ENVSRC") or ""
|
|
1045
|
+
if src and os.path.exists(src):
|
|
1046
|
+
seen = set()
|
|
1047
|
+
try:
|
|
1048
|
+
with open(src) as f:
|
|
1049
|
+
for line in f:
|
|
1050
|
+
line = line.strip()
|
|
1051
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
1052
|
+
continue
|
|
1053
|
+
name = line.split("=", 1)[0].strip()
|
|
1054
|
+
name = re.sub(r"^export\s+", "", name)
|
|
1055
|
+
if re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", name) and name not in seen:
|
|
1056
|
+
seen.add(name)
|
|
1057
|
+
env_keys.append(name)
|
|
1058
|
+
except Exception:
|
|
1059
|
+
env_keys = []
|
|
1060
|
+
|
|
1061
|
+
recipe = {
|
|
1062
|
+
"install": os.environ.get("_SR_INSTALL", ""),
|
|
1063
|
+
"seed": os.environ.get("_SR_SEED", ""),
|
|
1064
|
+
"env_keys": env_keys,
|
|
1065
|
+
"start": os.environ.get("_SR_START", ""),
|
|
1066
|
+
"health_path": os.environ.get("_SR_HEALTH", "") or "/",
|
|
1067
|
+
"port": os.environ.get("_SR_PORT", ""),
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
1071
|
+
with open(os.path.join(out_dir, "setup-recipe.json"), "w") as f:
|
|
1072
|
+
json.dump(recipe, f, indent=2)
|
|
1073
|
+
f.write("\n")
|
|
1074
|
+
PYEOF
|
|
1075
|
+
return 0
|
|
1076
|
+
}
|
|
1077
|
+
|
|
919
1078
|
verify_gate_runtime() {
|
|
920
1079
|
local tree="$1"
|
|
921
1080
|
# Opt-out (default on). Disabled -> emit no row -> byte-identical.
|
|
@@ -1055,6 +1214,11 @@ verify_gate_runtime() {
|
|
|
1055
1214
|
"Runtime boot smoke failed: '$method' booted but returned HTTP $http_status on $url (server error)."
|
|
1056
1215
|
else
|
|
1057
1216
|
_verify_add_gate "runtime" "pass" "boot" "$summary" "true"
|
|
1217
|
+
# Setup-recipe WRITER (rank 7). Only on a VERIFIED boot (2xx/3xx/4xx):
|
|
1218
|
+
# persist a deterministic .loki/setup-recipe.json that the runtime
|
|
1219
|
+
# detector (_verify_runtime_detect) consumes first on later runs. Env var
|
|
1220
|
+
# NAMES only, never secret VALUES. Best effort; never changes the verdict.
|
|
1221
|
+
_verify_write_setup_recipe "$tree" "$method" "$probe_port" "$health_path" || true
|
|
1058
1222
|
fi
|
|
1059
1223
|
|
|
1060
1224
|
# Record a structured runtime artifact alongside the gate row so the boot is
|
|
@@ -1339,6 +1503,13 @@ verify_emit_evidence() {
|
|
|
1339
1503
|
_V_BLOCKON="$block_on" \
|
|
1340
1504
|
_V_LEDGER_SHA="${_VERIFY_EXPECT_LEDGER_SHA:-}" \
|
|
1341
1505
|
_V_LEDGER_HASHOK="${_VERIFY_EXPECT_LEDGER_HASH_OK:-}" \
|
|
1506
|
+
_V_SCOPE_FILES="${VERIFY_SCOPE_FILES:-}" \
|
|
1507
|
+
_V_SCOPE_NET="${VERIFY_SCOPE_NET_LINES:-}" \
|
|
1508
|
+
_V_SCOPE_VERDICT="${VERIFY_SCOPE_VERDICT:-}" \
|
|
1509
|
+
_V_SCOPE_SOFT_FILES="${VERIFY_SCOPE_SOFT_FILES:-}" \
|
|
1510
|
+
_V_SCOPE_SOFT_NET="${VERIFY_SCOPE_SOFT_NET_LINES:-}" \
|
|
1511
|
+
_V_SCOPE_MAX_FILES="${VERIFY_SCOPE_MAX_FILES:-}" \
|
|
1512
|
+
_V_SCOPE_MAX_NET="${VERIFY_SCOPE_MAX_NET_LINES:-}" \
|
|
1342
1513
|
python3 - <<'PYEOF'
|
|
1343
1514
|
import json, os, hashlib
|
|
1344
1515
|
|
|
@@ -1432,6 +1603,33 @@ if _ledger_sha:
|
|
|
1432
1603
|
"hash_ok": (os.environ.get("_V_LEDGER_HASHOK") == "true"),
|
|
1433
1604
|
}
|
|
1434
1605
|
|
|
1606
|
+
# Code-scope / locality record (rank 10, advisory-first). Additive top-level
|
|
1607
|
+
# "scope" object; the verdict here is a LABEL only and never affects the
|
|
1608
|
+
# document's authoritative verdict/exit_code. Emitted only when the scope helper
|
|
1609
|
+
# ran (env set), so evidence.json stays byte-identical on paths that skip it.
|
|
1610
|
+
_scope_verdict = os.environ.get("_V_SCOPE_VERDICT") or ""
|
|
1611
|
+
if _scope_verdict:
|
|
1612
|
+
def _to_int(name, default=0):
|
|
1613
|
+
v = os.environ.get(name) or ""
|
|
1614
|
+
try:
|
|
1615
|
+
return int(v)
|
|
1616
|
+
except (TypeError, ValueError):
|
|
1617
|
+
return default
|
|
1618
|
+
_thresholds = {
|
|
1619
|
+
"soft_files": _to_int("_V_SCOPE_SOFT_FILES"),
|
|
1620
|
+
"soft_net_lines": _to_int("_V_SCOPE_SOFT_NET"),
|
|
1621
|
+
# Hard caps are opt-in; None when not explicitly set.
|
|
1622
|
+
"max_files": (_to_int("_V_SCOPE_MAX_FILES") if (os.environ.get("_V_SCOPE_MAX_FILES") or "") != "" else None),
|
|
1623
|
+
"max_net_lines": (_to_int("_V_SCOPE_MAX_NET") if (os.environ.get("_V_SCOPE_MAX_NET") or "") != "" else None),
|
|
1624
|
+
}
|
|
1625
|
+
doc["scope"] = {
|
|
1626
|
+
"files_changed": _to_int("_V_SCOPE_FILES"),
|
|
1627
|
+
"net_lines": _to_int("_V_SCOPE_NET"),
|
|
1628
|
+
"verdict": _scope_verdict,
|
|
1629
|
+
"advisory": True,
|
|
1630
|
+
"thresholds": _thresholds,
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1435
1633
|
os.makedirs(out_dir, exist_ok=True)
|
|
1436
1634
|
ev_path = os.path.join(out_dir, "evidence.json")
|
|
1437
1635
|
with open(ev_path, "w") as f:
|
|
@@ -2069,6 +2267,11 @@ verify_main() {
|
|
|
2069
2267
|
|
|
2070
2268
|
verify_diff_base "$base_ref"
|
|
2071
2269
|
|
|
2270
|
+
# Code-scope / locality record (rank 10, advisory-first). Reads the diff
|
|
2271
|
+
# stats verify_diff_base just computed; never emits a gate row or feeds the
|
|
2272
|
+
# verdict, so it can never block or change the exit code.
|
|
2273
|
+
verify_scope_record
|
|
2274
|
+
|
|
2072
2275
|
# Short-circuit on an empty or unresolvable change set. A verifier verifies
|
|
2073
2276
|
# a CHANGE; with nothing to verify there is no basis for a VERIFIED verdict.
|
|
2074
2277
|
# Recording the gates as skipped (rather than running them against the whole
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -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.
|
|
5
|
+
**Version:** v7.115.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -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.
|
|
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.115.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=
|
|
817
|
+
//# debugId=7679240CF905E39B64756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/mcp/lsp_proxy.py
CHANGED
|
@@ -1595,8 +1595,49 @@ def main() -> None:
|
|
|
1595
1595
|
'install dir -- the install dir is only the import path for '
|
|
1596
1596
|
'`-m mcp.lsp_proxy`.',
|
|
1597
1597
|
)
|
|
1598
|
+
parser.add_argument(
|
|
1599
|
+
'--check-symbols', action='store_true',
|
|
1600
|
+
help='One-shot: read a newline-delimited list of symbol names on stdin '
|
|
1601
|
+
'and, for each, run lsp_check_exists against the workspace at '
|
|
1602
|
+
'--root. Emits JSON {"available": bool, "results": {sym: '
|
|
1603
|
+
'true|false|null}} on stdout. available=false / null verdicts mean '
|
|
1604
|
+
'no language server was reachable (honest inconclusive, never a '
|
|
1605
|
+
'fabricated exists). Same cwd/--root contract as --write-diagnostics: '
|
|
1606
|
+
'cwd is the install dir so `-m mcp.lsp_proxy` imports; --root is the '
|
|
1607
|
+
'TARGET project the symbols must exist in.',
|
|
1608
|
+
)
|
|
1598
1609
|
args = parser.parse_args()
|
|
1599
1610
|
|
|
1611
|
+
if args.check_symbols:
|
|
1612
|
+
# One-shot symbol-existence probe for the acceptance-oracle (source-
|
|
1613
|
+
# grounded checklist). Chdir into --root so the in-process LSP client's
|
|
1614
|
+
# _resolve_workspace_root()/_pick_language_for_workspace() see the TARGET
|
|
1615
|
+
# project (the process cwd is the install dir for the import). Always
|
|
1616
|
+
# cleans up spawned language-server subprocesses via the finally arm.
|
|
1617
|
+
target = os.path.abspath(args.root or os.getcwd())
|
|
1618
|
+
symbols = [ln.strip() for ln in sys.stdin.read().splitlines() if ln.strip()]
|
|
1619
|
+
results: Dict[str, Any] = {}
|
|
1620
|
+
available = False
|
|
1621
|
+
try:
|
|
1622
|
+
if os.path.isdir(target):
|
|
1623
|
+
os.chdir(target)
|
|
1624
|
+
for sym in symbols:
|
|
1625
|
+
try:
|
|
1626
|
+
raw = _lsp_check_exists_blocking(sym)
|
|
1627
|
+
parsed = json.loads(raw)
|
|
1628
|
+
except (OSError, ValueError):
|
|
1629
|
+
results[sym] = None
|
|
1630
|
+
continue
|
|
1631
|
+
exists = parsed.get('exists', None)
|
|
1632
|
+
results[sym] = exists
|
|
1633
|
+
# A non-null verdict on ANY symbol means the server answered.
|
|
1634
|
+
if exists is not None:
|
|
1635
|
+
available = True
|
|
1636
|
+
finally:
|
|
1637
|
+
_cleanup_all_clients()
|
|
1638
|
+
print(json.dumps({'available': available, 'results': results}))
|
|
1639
|
+
return
|
|
1640
|
+
|
|
1600
1641
|
if args.write_diagnostics:
|
|
1601
1642
|
# One-shot writer mode -- no MCP server, no event loop. Always cleans
|
|
1602
1643
|
# up any spawned LSP clients before exit. `--root` is the TARGET
|
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.
|
|
4
|
+
"version": "7.115.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.
|
|
5
|
+
"version": "7.115.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",
|