loki-mode 7.119.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 +2 -2
- package/VERSION +1 -1
- package/autonomy/checklist-verify.py +147 -26
- package/autonomy/run.sh +49 -0
- package/autonomy/verify.sh +31 -2
- 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/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
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.
|
|
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.
|
|
411
|
+
**v7.121.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
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
|
-
|
|
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
|
-
#
|
|
88
|
-
|
|
89
|
-
|
|
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"
|
|
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
|
-
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
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
|
-
"
|
|
110
|
-
or "no
|
|
111
|
-
or "no tests
|
|
112
|
-
or
|
|
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
|
-
|
|
119
|
-
"
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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"
|
package/autonomy/run.sh
CHANGED
|
@@ -9317,6 +9317,21 @@ _loki_zero_tests_executed() {
|
|
|
9317
9317
|
fi
|
|
9318
9318
|
return 1
|
|
9319
9319
|
;;
|
|
9320
|
+
unittest)
|
|
9321
|
+
# #139: `python3 -m unittest discover` on ZERO discovered tests prints
|
|
9322
|
+
# "Ran 0 tests" + "NO TESTS RAN". Depending on the Python version it
|
|
9323
|
+
# exits 5 (newer, already not-pass) OR 0 (older = a silent fake-green,
|
|
9324
|
+
# the #82/#89 trap). POSITIVE detection so it is inconclusive on BOTH:
|
|
9325
|
+
# a "Ran 0 tests" line (authoritative), or the "NO TESTS RAN" banner,
|
|
9326
|
+
# AND no ran-count >= 1. A real run prints "Ran N test(s)" with N>=1
|
|
9327
|
+
# and "OK"/"FAILED", so it is never downgraded. Verified live: zero ->
|
|
9328
|
+
# "Ran 0 tests"/"NO TESTS RAN"; one -> "Ran 1 test"/"OK".
|
|
9329
|
+
if printf '%s' "$_zt_out" | grep -qE '^Ran 0 tests' 2>/dev/null \
|
|
9330
|
+
|| printf '%s' "$_zt_out" | grep -qE '^NO TESTS RAN$' 2>/dev/null; then
|
|
9331
|
+
return 0
|
|
9332
|
+
fi
|
|
9333
|
+
return 1
|
|
9334
|
+
;;
|
|
9320
9335
|
*)
|
|
9321
9336
|
# Unknown/unparseable runner -> never claim zero-tests (safe default).
|
|
9322
9337
|
return 1
|
|
@@ -9483,6 +9498,17 @@ sys.stdout.write(t.strip())
|
|
|
9483
9498
|
-print -quit 2>/dev/null | grep -q .; then
|
|
9484
9499
|
has_python_project=true
|
|
9485
9500
|
fi
|
|
9501
|
+
elif find "${TARGET_DIR:-.}" -maxdepth 2 -type f \
|
|
9502
|
+
\( -name 'test_*.py' -o -name '*_test.py' \) \
|
|
9503
|
+
-not -path '*/.loki/*' -not -path '*/.git/*' -not -path '*/node_modules/*' \
|
|
9504
|
+
-not -path '*/.venv/*' -not -path '*/venv/*' \
|
|
9505
|
+
-print -quit 2>/dev/null | grep -q .; then
|
|
9506
|
+
# #139: a ROOT-LEVEL (or shallow) test_*.py / *_test.py with NO tests/
|
|
9507
|
+
# dir and NO config file. The founder's invoice CLI had exactly this
|
|
9508
|
+
# shape (test_invoice_cli.py at the root) -> was missed -> tests read
|
|
9509
|
+
# "not run" on a genuinely-passing 16-test unittest suite. This is the
|
|
9510
|
+
# same false-negative class as #79 (validated work reads as unvalidated).
|
|
9511
|
+
has_python_project=true
|
|
9486
9512
|
fi
|
|
9487
9513
|
if [ "$has_python_project" = "true" ] && command -v pytest &>/dev/null; then
|
|
9488
9514
|
test_runner="pytest"
|
|
@@ -9499,6 +9525,29 @@ sys.stdout.write(t.strip())
|
|
|
9499
9525
|
[ "$pytest_exit" -ne 0 ] && test_passed=false
|
|
9500
9526
|
details="pytest: $(echo "$output" | tail -5 | tr '\n' ' ')"
|
|
9501
9527
|
fi
|
|
9528
|
+
elif [ "$has_python_project" = "true" ] && command -v python3 &>/dev/null; then
|
|
9529
|
+
# #139: pytest ABSENT but a Python test suite exists. stdlib unittest
|
|
9530
|
+
# discovery runs test_*.py with ZERO third-party deps -- so a legit
|
|
9531
|
+
# unittest suite (the invoice CLI's 16 tests) is verified instead of
|
|
9532
|
+
# silently read as "not run". The zero-test guard (unittest case) makes
|
|
9533
|
+
# a zero-discovery run inconclusive, NOT a fake-green pass (unittest
|
|
9534
|
+
# prints "NO TESTS RAN" and exits 0 -- the #82/#89 trap, guarded).
|
|
9535
|
+
test_runner="unittest"
|
|
9536
|
+
local output unittest_exit _ut_to
|
|
9537
|
+
_ut_to="${LOKI_PYTEST_TIMEOUT:-${LOKI_GATE_TIMEOUT:-300}}"
|
|
9538
|
+
local _ut_cmd=(timeout "${_ut_to}s")
|
|
9539
|
+
command -v gtimeout &>/dev/null && _ut_cmd=(gtimeout "${_ut_to}s")
|
|
9540
|
+
command -v timeout &>/dev/null || command -v gtimeout &>/dev/null || _ut_cmd=()
|
|
9541
|
+
output=$(cd "${TARGET_DIR:-.}" && "${_ut_cmd[@]}" python3 -m unittest discover -p 'test_*.py' 2>&1)
|
|
9542
|
+
unittest_exit=$?
|
|
9543
|
+
if [ "$unittest_exit" -eq 124 ]; then
|
|
9544
|
+
test_passed=false
|
|
9545
|
+
log_warn "unittest gate timed out after ${_ut_to}s (exit 124)"
|
|
9546
|
+
details="unittest: TIMED OUT after ${_ut_to}s -- $(echo "$output" | tail -3 | tr '\n' ' ')"
|
|
9547
|
+
else
|
|
9548
|
+
[ "$unittest_exit" -ne 0 ] && test_passed=false
|
|
9549
|
+
details="unittest: $(echo "$output" | tail -5 | tr '\n' ' ')"
|
|
9550
|
+
fi
|
|
9502
9551
|
fi
|
|
9503
9552
|
fi
|
|
9504
9553
|
|
package/autonomy/verify.sh
CHANGED
|
@@ -350,6 +350,18 @@ _verify_zero_tests_executed() {
|
|
|
350
350
|
fi
|
|
351
351
|
return 1
|
|
352
352
|
;;
|
|
353
|
+
unittest)
|
|
354
|
+
# #139: `python3 -m unittest discover` on ZERO discovered tests prints
|
|
355
|
+
# "Ran 0 tests" + "NO TESTS RAN". On older Python it EXITS 0 (a silent
|
|
356
|
+
# fake-green), on newer it exits 5 (already not-pass). POSITIVE detection
|
|
357
|
+
# so it is inconclusive on BOTH. A real run prints "Ran N test(s)" (N>=1)
|
|
358
|
+
# + "OK"/"FAILED" -> never downgraded. Mirrors run.sh's guard exactly.
|
|
359
|
+
if printf '%s' "$_zt_out" | grep -qE '^Ran 0 tests' 2>/dev/null \
|
|
360
|
+
|| printf '%s' "$_zt_out" | grep -qE '^NO TESTS RAN$' 2>/dev/null; then
|
|
361
|
+
return 0
|
|
362
|
+
fi
|
|
363
|
+
return 1
|
|
364
|
+
;;
|
|
353
365
|
*)
|
|
354
366
|
return 1
|
|
355
367
|
;;
|
|
@@ -411,8 +423,15 @@ verify_gate_tests() {
|
|
|
411
423
|
# them so the gate runs (or goes inconclusive -> CONCERNS when no
|
|
412
424
|
# runner is installed), never silently skipped.
|
|
413
425
|
if [ "$has_python" = "false" ]; then
|
|
414
|
-
|
|
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 \
|
|
415
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/*' \
|
|
416
435
|
-print -quit 2>/dev/null | grep -q .; then
|
|
417
436
|
has_python=true
|
|
418
437
|
fi
|
|
@@ -421,9 +440,19 @@ verify_gate_tests() {
|
|
|
421
440
|
if command -v pytest >/dev/null 2>&1; then
|
|
422
441
|
runner="pytest"
|
|
423
442
|
out="$(cd "$tree" && timeout "$timeout_s" pytest --tb=short 2>&1)" || rc=$?
|
|
443
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
444
|
+
# #139 parity: pytest ABSENT but a Python suite exists -> run it with
|
|
445
|
+
# stdlib unittest (zero third-party deps) instead of going blindly
|
|
446
|
+
# inconclusive. A real passing test_*.py suite is now VERIFIED here
|
|
447
|
+
# too, matching run.sh's enforce_test_coverage. Zero-discovery is
|
|
448
|
+
# handled below (unittest exits 5 on newer Python = fail-not-pass, or
|
|
449
|
+
# a "Ran 0 tests" line detected by the zero-test guard on the run.sh
|
|
450
|
+
# mirror) so this never green-washes an empty suite.
|
|
451
|
+
runner="unittest"
|
|
452
|
+
out="$(cd "$tree" && timeout "$timeout_s" python3 -m unittest discover -p 'test_*.py' 2>&1)" || rc=$?
|
|
424
453
|
else
|
|
425
454
|
# Applicable but cannot run -> inconclusive (Entanglement 2).
|
|
426
|
-
_verify_add_gate "tests" "inconclusive" "pytest" "python project detected but pytest
|
|
455
|
+
_verify_add_gate "tests" "inconclusive" "pytest" "python project detected but no pytest/python3 on PATH" "true"
|
|
427
456
|
return 0
|
|
428
457
|
fi
|
|
429
458
|
fi
|
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.121.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.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=
|
|
817
|
+
//# debugId=3745511B1A35D37D64756E2164756E21
|
package/mcp/__init__.py
CHANGED
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.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.
|
|
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",
|