loki-mode 7.128.2 → 7.129.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.128.2
6
+ # Loki Mode v7.129.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.128.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
411
+ **v7.129.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.128.2
1
+ 7.129.0
@@ -296,7 +296,47 @@ Respond ONLY with a valid JSON object. No markdown fencing."
296
296
  # inline prefix here is belt-and-suspenders so the carve-out is
297
297
  # self-documenting and robust to sourcing order. No-op when caveman
298
298
  # is absent.
299
- result=$(echo "$full_prompt" | CAVEMAN_DEFAULT_MODE=off claude "${_c2_argv[@]}" -p 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
299
+ #
300
+ # STRUCTURED VERDICT (v8.x): when the CLI supports --json-schema,
301
+ # force valid JSON so the sed-carve below never has to rescue
302
+ # prose-wrapped output. --json-schema takes INLINE content, not a
303
+ # path (CLI 2.1.207 rejects a path). On any failure fall through to
304
+ # the plain -p call + sed-carve (unchanged, fail-closed to REJECT).
305
+ # Opt out LOKI_REVIEW_JSON_SCHEMA=off. claude branch only.
306
+ local _c2_schema_dir _c2_schema
307
+ _c2_schema_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
308
+ _c2_schema="${_c2_schema_dir}/loki-ts/data/council-v2-schema.json"
309
+ result=""
310
+ if [ "${LOKI_REVIEW_JSON_SCHEMA:-on}" != "off" ] && [ -f "$_c2_schema" ] \
311
+ && type loki_claude_flag_supported >/dev/null 2>&1 \
312
+ && loki_claude_flag_supported "--json-schema"; then
313
+ local _c2_schema_content _c2_json
314
+ _c2_schema_content="$(cat "$_c2_schema" 2>/dev/null)" || _c2_schema_content=""
315
+ if [ -n "$_c2_schema_content" ]; then
316
+ _c2_json="$(echo "$full_prompt" | CAVEMAN_DEFAULT_MODE=off claude "${_c2_argv[@]}" -p \
317
+ --json-schema "$_c2_schema_content" --output-format json 2>/dev/null)" || _c2_json=""
318
+ if [ -n "$_c2_json" ]; then
319
+ # Pull the schema-validated object out of the envelope.
320
+ result="$(printf '%s' "$_c2_json" | python3 -c 'import sys,json
321
+ try:
322
+ e=json.load(sys.stdin)
323
+ p=e.get("structured_output")
324
+ if not isinstance(p,dict):
325
+ r=e.get("result")
326
+ p=json.loads(r) if isinstance(r,str) else None
327
+ if isinstance(p,dict) and str(p.get("verdict","")).upper() in ("APPROVE","REJECT"):
328
+ sys.stdout.write(json.dumps(p))
329
+ else:
330
+ sys.exit(1)
331
+ except Exception:
332
+ sys.exit(1)' 2>/dev/null)" || result=""
333
+ fi
334
+ fi
335
+ fi
336
+ # Fall through to the plain text call (+ sed-carve) on any miss.
337
+ if [ -z "$result" ]; then
338
+ result=$(echo "$full_prompt" | CAVEMAN_DEFAULT_MODE=off claude "${_c2_argv[@]}" -p 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
339
+ fi
300
340
  else
301
341
  result='{"verdict":"REJECT","reasoning":"reviewer CLI unavailable","issues":[]}'
302
342
  fi
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+ """Re-materialize a `claude --json-schema --output-format json` code-review
3
+ envelope into the LEGACY 'VERDICT: X\\nFINDINGS:\\n- [severity] description'
4
+ text, so the downstream text parsers (_classify_verdict, _severity_is_blocking,
5
+ _count_nonblocking_findings in autonomy/run.sh, and the TS mirror) stay
6
+ byte-identical. This is the adapter that lets the code-review verdict path adopt
7
+ structured output WITHOUT rewriting any consumer.
8
+
9
+ Contract:
10
+ - Reads the raw CLI envelope JSON from env _LOKI_CR_JSON (or stdin if unset).
11
+ - Writes legacy text to the path in env _LOKI_CR_OUT (or stdout if unset).
12
+ - FAIL-CLOSED: exits non-zero on ANY structural miss so the bash caller falls
13
+ through to the text path. NEVER emits 'VERDICT: PASS' on a miss.
14
+ - T1 SAFETY (cross-field enforcement JSON Schema cannot express): if ANY finding
15
+ is Critical or High, the verdict is FORCED to FAIL regardless of the model's
16
+ emitted verdict token. A self-contradictory PASS+Critical can never be waved
17
+ through as non-blocking.
18
+
19
+ Live-verified envelope shape (claude 2.1.207): top-level 'structured_output'
20
+ object is the payload; 'result' is the same JSON as a string (fallback);
21
+ 'stop_reason' is 'tool_use' for a valid structured turn.
22
+
23
+ Exit codes (all non-zero == fail-closed, caller falls through to text):
24
+ 0 success 2 no input/output target 3 envelope not JSON / not a dict
25
+ 4 wrong stop_reason 5 no usable payload 6 missing/invalid verdict
26
+ 7 write error
27
+ """
28
+ import json
29
+ import os
30
+ import sys
31
+
32
+ _BLOCKING = {"CRITICAL", "HIGH"}
33
+
34
+
35
+ def rematerialize(raw):
36
+ """Return legacy VERDICT/FINDINGS text, or raise ValueError(exit_code)."""
37
+ try:
38
+ env = json.loads(raw)
39
+ except Exception:
40
+ raise ValueError(3)
41
+ if not isinstance(env, dict):
42
+ raise ValueError(3)
43
+
44
+ # A valid structured turn ends with tool_use. A refusal / max_tokens / other
45
+ # stop_reason means the schema was not honored -> fail-closed.
46
+ if env.get("stop_reason") not in (None, "tool_use"):
47
+ raise ValueError(4)
48
+
49
+ payload = env.get("structured_output")
50
+ if not isinstance(payload, dict):
51
+ res = env.get("result")
52
+ if isinstance(res, str):
53
+ try:
54
+ payload = json.loads(res)
55
+ except Exception:
56
+ payload = None
57
+ if not isinstance(payload, dict):
58
+ raise ValueError(5)
59
+
60
+ verdict = str(payload.get("verdict", "")).strip().upper()
61
+ if verdict not in ("PASS", "FAIL"):
62
+ raise ValueError(6)
63
+
64
+ findings = payload.get("findings")
65
+ if not isinstance(findings, list):
66
+ findings = []
67
+
68
+ body = []
69
+ has_blocking = False
70
+ for f in findings:
71
+ if not isinstance(f, dict):
72
+ continue
73
+ sev = str(f.get("severity", "")).strip()
74
+ desc = str(f.get("description", "")).strip()
75
+ if not sev or not desc:
76
+ continue
77
+ if sev.upper() in _BLOCKING:
78
+ has_blocking = True
79
+ # Bracketed severity form matches _severity_is_blocking /
80
+ # _count_nonblocking_findings regexes exactly.
81
+ body.append("- [" + sev + "] " + desc)
82
+
83
+ # T1: a Critical/High finding forces FAIL even if the model said PASS.
84
+ if has_blocking:
85
+ verdict = "FAIL"
86
+
87
+ lines = ["VERDICT: " + verdict, "FINDINGS:"]
88
+ if body:
89
+ lines.extend(body)
90
+ else:
91
+ lines.append("- None")
92
+ return "\n".join(lines) + "\n"
93
+
94
+
95
+ def main():
96
+ raw = os.environ.get("_LOKI_CR_JSON")
97
+ if raw is None:
98
+ raw = sys.stdin.read()
99
+ out_path = os.environ.get("_LOKI_CR_OUT", "")
100
+ if not raw:
101
+ return 2
102
+
103
+ try:
104
+ text = rematerialize(raw)
105
+ except ValueError as e:
106
+ return int(e.args[0])
107
+
108
+ if out_path:
109
+ try:
110
+ with open(out_path, "w") as fp:
111
+ fp.write(text)
112
+ except OSError:
113
+ return 7
114
+ else:
115
+ sys.stdout.write(text)
116
+ return 0
117
+
118
+
119
+ if __name__ == "__main__":
120
+ sys.exit(main())
@@ -51,6 +51,54 @@
51
51
  _loki_done_recog_invoke() {
52
52
  local prompt="$1"
53
53
  command -v claude >/dev/null 2>&1 || return 1
54
+
55
+ # STRUCTURED VERDICT (v8.x): when the CLI supports --json-schema, force valid
56
+ # JSON so parse_object never has to brace-slice prose. --json-schema takes
57
+ # INLINE content, not a path (CLI 2.1.207 rejects a path). On any miss (flag
58
+ # unsupported, empty, no structured_output) fall through to the plain -p call
59
+ # below; parse_object then handles it and defaults to inconclusive (safe).
60
+ # Opt out LOKI_REVIEW_JSON_SCHEMA=off.
61
+ local _dr_schema_dir _dr_schema
62
+ _dr_schema_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
63
+ _dr_schema="${_dr_schema_dir}/loki-ts/data/done-recognition-schema.json"
64
+ if [ "${LOKI_REVIEW_JSON_SCHEMA:-on}" != "off" ] && [ -f "$_dr_schema" ] \
65
+ && type loki_claude_flag_supported >/dev/null 2>&1 \
66
+ && loki_claude_flag_supported "--json-schema"; then
67
+ local _dr_schema_content _dr_json _dr_obj
68
+ _dr_schema_content="$(cat "$_dr_schema" 2>/dev/null)" || _dr_schema_content=""
69
+ if [ -n "$_dr_schema_content" ]; then
70
+ if command -v timeout >/dev/null 2>&1; then
71
+ _dr_json=$(CAVEMAN_DEFAULT_MODE=off timeout "${LOKI_DONE_RECOG_TIMEOUT}" \
72
+ claude --dangerously-skip-permissions -p "$prompt" \
73
+ --json-schema "$_dr_schema_content" --output-format json 2>/dev/null) || _dr_json=""
74
+ else
75
+ _dr_json=$(CAVEMAN_DEFAULT_MODE=off \
76
+ claude --dangerously-skip-permissions -p "$prompt" \
77
+ --json-schema "$_dr_schema_content" --output-format json 2>/dev/null) || _dr_json=""
78
+ fi
79
+ if [ -n "$_dr_json" ]; then
80
+ _dr_obj=$(printf '%s' "$_dr_json" | python3 -c 'import sys,json
81
+ try:
82
+ e=json.load(sys.stdin)
83
+ p=e.get("structured_output")
84
+ if not isinstance(p,dict):
85
+ r=e.get("result")
86
+ p=json.loads(r) if isinstance(r,str) else None
87
+ if isinstance(p,dict) and "requirements" in p:
88
+ sys.stdout.write(json.dumps(p))
89
+ else:
90
+ sys.exit(1)
91
+ except Exception:
92
+ sys.exit(1)' 2>/dev/null) || _dr_obj=""
93
+ if [ -n "$_dr_obj" ]; then
94
+ printf '%s' "$_dr_obj"
95
+ return 0
96
+ fi
97
+ fi
98
+ fi
99
+ # fall through to the plain text call (parse_object handles it, inconclusive-safe)
100
+ fi
101
+
54
102
  local rc=0
55
103
  local out=""
56
104
  if command -v timeout >/dev/null 2>&1; then
@@ -2,7 +2,7 @@
2
2
  # autonomy/lib/voter-agents.sh -- Phase C (v7.5.20) bash side.
3
3
  #
4
4
  # Builds the JSON declaration of the council voter agents and orchestrates a
5
- # single `claude --agents <json> --json-schema <path>` dispatch per iteration.
5
+ # single `claude --agents <json> --json-schema <inline-content>` dispatch per iteration.
6
6
  # Replaces the ad-hoc per-voter heuristic loop in council_aggregate_votes when
7
7
  # the locally installed Claude CLI supports both flags.
8
8
  #
@@ -176,7 +176,7 @@ loki_finding_schema_path() {
176
176
  # --json-schema in `claude --help`. Either missing -> return 1 (fallback).
177
177
  # 2. Build the agents JSON via loki_voter_agents_json.
178
178
  # 3. Invoke `claude --dangerously-skip-permissions -p <prompt>
179
- # --agents <json> --json-schema <path>` and capture stdout.
179
+ # --agents <json> --json-schema <inline-content>` and capture stdout.
180
180
  # Any non-zero exit or empty stdout -> return 1 (fallback).
181
181
  # 4. Parse the response via python3 (no jq dependency): extract
182
182
  # findings[].{role, vote, reason, confidence}. On parse failure -> 1.
@@ -236,6 +236,16 @@ loki_council_dispatch_agents() {
236
236
  local schema_path
237
237
  schema_path=$(loki_finding_schema_path) || return 1
238
238
 
239
+ # v8.x FIX: `claude --json-schema` takes the schema JSON as INLINE CONTENT,
240
+ # not a file path. Live-verified on CLI 2.1.207: passing a path errors with
241
+ # "--json-schema is not valid JSON: Unrecognized token '/'" -> rc!=0 ->
242
+ # fail-closed to the heuristic path, so this structured dispatch was SILENTLY
243
+ # DEAD (never actually schema-constrained the council). Read the file and pass
244
+ # its content. Fail-closed if unreadable/empty (return 1 -> heuristic path).
245
+ local schema_content
246
+ schema_content=$(cat "$schema_path" 2>/dev/null) || return 1
247
+ [ -n "$schema_content" ] || return 1
248
+
239
249
  # 3. Invoke claude. Guard against absent binary or non-zero exit.
240
250
  command -v claude >/dev/null 2>&1 || return 1
241
251
 
@@ -274,7 +284,7 @@ loki_council_dispatch_agents() {
274
284
  env CAVEMAN_DEFAULT_MODE=off claude --dangerously-skip-permissions \
275
285
  -p "$prompt" \
276
286
  --agents "$agents_json" \
277
- --json-schema "$schema_path" 2>"$stderr_log") || rc=$?
287
+ --json-schema "$schema_content" 2>"$stderr_log") || rc=$?
278
288
  if [ "$rc" -ne 0 ] || [ -z "$response" ]; then
279
289
  return 1
280
290
  fi
package/autonomy/loki CHANGED
@@ -826,7 +826,7 @@ show_help() {
826
826
  echo "New here? Try these in order:"
827
827
  echo " loki doctor Check your setup is ready (a few seconds)"
828
828
  echo " loki quickstart Guided first build from your idea (no PRD needed)"
829
- echo " loki start ./prd.md Build from a spec (PRD file, GitHub issue, or no arg)"
829
+ echo " loki start ./prd.md Build from a spec (PRD file, GitHub/GitLab/Jira/Azure issue, or no arg)"
830
830
  echo " quickstart = guided first build; quick = one small task (3 iters max)."
831
831
  echo " Docs: https://github.com/asklokesh/loki-mode | Report a problem: loki crash"
832
832
  echo ""
@@ -846,7 +846,7 @@ show_help() {
846
846
  echo "Commands:"
847
847
  echo ""
848
848
  echo "Build:"
849
- echo " start [SPEC] Start a build (PRD file, GitHub issue, or no arg)"
849
+ echo " start [SPEC] Start a build (PRD file, GitHub/GitLab/Jira/Azure issue, or no arg)"
850
850
  echo " plan <PRD-file> Dry-run analysis: complexity, cost, execution plan"
851
851
  echo " quickstart [idea] Guided first build: setup, idea, template, plan, go"
852
852
  echo ""
@@ -1043,7 +1043,7 @@ show_landing() {
1043
1043
  echo -e " ${CYAN}loki tour${NC} See a real result now (no provider, no key, no spend)"
1044
1044
  echo -e " ${CYAN}loki doctor${NC} Check your setup is ready (a few seconds)"
1045
1045
  echo -e " ${CYAN}loki quickstart${NC} Guided first build from your idea (no PRD needed)"
1046
- echo -e " ${CYAN}loki start ./prd.md${NC} Build from a spec (PRD file, GitHub issue, or no arg)"
1046
+ echo -e " ${CYAN}loki start ./prd.md${NC} Build from a spec (PRD file, GitHub/GitLab/Jira/Azure issue, or no arg)"
1047
1047
  echo " quickstart = guided first build; quick = one small task (3 iters max)."
1048
1048
  echo ""
1049
1049
  echo -e "Need help? ${CYAN}loki help${NC} lists every command."
package/autonomy/run.sh CHANGED
@@ -5911,8 +5911,15 @@ compute_codebase_signature() {
5911
5911
  # truncating the output and misaligning the path<->hash pairing. Drop
5912
5912
  # them; their removal from the list is itself the detected change.
5913
5913
  gitc_deleted=$(git ls-files --deleted -z 2>/dev/null | tr '\0' '\n')
5914
+ # Exclude .loki/ AND Loki's own session-end artifacts (HANDOFF.md,
5915
+ # USAGE.md) written+committed to the repo ROOT by the completion path
5916
+ # (LOKI_HANDOFF / commit_session_changes, both default-on). Those are
5917
+ # runtime OUTPUT of the session, not user codebase; counting them would
5918
+ # make the next PRD-reuse check see a spurious "codebase changed" and
5919
+ # flip reuse->update (the signature is persisted per-iteration BEFORE
5920
+ # the after-loop HANDOFF write, so it can never account for them).
5914
5921
  gitc_paths=$( { git ls-files -z 2>/dev/null; git ls-files --others --exclude-standard -z 2>/dev/null; } \
5915
- | tr '\0' '\n' | grep -vE '(^|/)\.loki(/|$)' | LC_ALL=C sort -u )
5922
+ | tr '\0' '\n' | grep -vE '(^|/)\.loki(/|$)|^HANDOFF\.md$|^USAGE\.md$' | LC_ALL=C sort -u )
5916
5923
  if [ -n "$gitc_deleted" ]; then
5917
5924
  gitc_paths=$(printf '%s\n' "$gitc_paths" | grep -vxF -f <(printf '%s\n' "$gitc_deleted") || true)
5918
5925
  fi
@@ -5933,6 +5940,7 @@ compute_codebase_signature() {
5933
5940
  -o -name build -o -name .next -o -name target -o -name vendor \
5934
5941
  -o -name __pycache__ -o -name .venv -o -name venv \) -prune -o \
5935
5942
  -type f -print 2>/dev/null \
5943
+ | grep -vE '^\./(HANDOFF|USAGE)\.md$' \
5936
5944
  | while IFS= read -r f; do
5937
5945
  local sz
5938
5946
  sz=$(stat -f%z "$f" 2>/dev/null || stat -c%s "$f" 2>/dev/null || echo 0)
@@ -9658,8 +9666,16 @@ sys.stdout.write(t.strip())
9658
9666
  local output
9659
9667
  # Pass matched files explicitly (node --test globbing is
9660
9668
  # Node-version-sensitive); quote each so paths with spaces survive.
9669
+ # Force --test-reporter=tap: Node 20+ made the human "spec" reporter
9670
+ # the default for a TTY and Node 26 emits it even under capture
9671
+ # ("i pass N" / check+cross marks) instead of the TAP "# pass N" /
9672
+ # "ok N - " lines that BOTH the count parser below AND
9673
+ # _loki_zero_tests_executed rely on. TAP has been stable since Node 18,
9674
+ # so forcing it restores a deterministic, version-independent format
9675
+ # without touching any parser. Older node ignores nothing here (tap is
9676
+ # a valid built-in reporter on every supported version).
9661
9677
  output=$(cd "${TARGET_DIR:-.}" && timeout "${LOKI_GATE_TIMEOUT:-300}" \
9662
- node --test "${_nt_files[@]}" 2>&1) || test_passed=false
9678
+ node --test --test-reporter=tap "${_nt_files[@]}" 2>&1) || test_passed=false
9663
9679
  # tail -14 so node's TAP summary block (# tests / # pass N / # fail N,
9664
9680
  # ~10 lines) survives truncation for the best-effort count parse below.
9665
9681
  details="node --test: $(echo "$output" | tail -14 | tr '\n' ' ')"
@@ -10994,6 +11010,39 @@ _dispatch_reviewer() {
10994
11010
  # then deletes its flag and emits nothing). Set inline, not via
10995
11011
  # the helper, so the carve-out holds even when the helper is
10996
11012
  # out of scope. No-op when caveman is absent.
11013
+ # STRUCTURED VERDICT (v8.x): when the CLI supports --json-schema,
11014
+ # force valid JSON and re-materialize the LEGACY VERDICT/FINDINGS text
11015
+ # so every downstream consumer (_classify_verdict,
11016
+ # _severity_is_blocking, _count_nonblocking_findings, mergeability,
11017
+ # DA arm, aggregate.json) stays byte-identical. Fail-closed: any
11018
+ # failure falls through to the text path below (which itself may leave
11019
+ # an empty file -> NO_VERDICT -> inconclusive block). NEVER a PASS on a
11020
+ # miss. --json-schema takes INLINE content, not a path (CLI 2.1.207
11021
+ # rejects a path). Opt out with LOKI_REVIEW_JSON_SCHEMA=off.
11022
+ local _cr_root _cr_here _cr_schema _cr_remat
11023
+ _cr_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
11024
+ _cr_here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11025
+ _cr_schema="${_cr_root}/loki-ts/data/code-review-schema.json"
11026
+ _cr_remat="${_cr_here}/lib/cr-rematerialize.py"
11027
+ if [ "${LOKI_REVIEW_JSON_SCHEMA:-on}" != "off" ] \
11028
+ && [ -f "$_cr_schema" ] && [ -f "$_cr_remat" ] \
11029
+ && type loki_claude_flag_supported >/dev/null 2>&1 \
11030
+ && loki_claude_flag_supported "--json-schema"; then
11031
+ local _cr_schema_content _cr_json _cr_rc=0
11032
+ _cr_schema_content="$(cat "$_cr_schema" 2>/dev/null)" || _cr_schema_content=""
11033
+ if [ -n "$_cr_schema_content" ]; then
11034
+ _cr_json="$(CAVEMAN_DEFAULT_MODE=off \
11035
+ claude "${_rv_argv[@]}" -p "$prompt_text" \
11036
+ --json-schema "$_cr_schema_content" \
11037
+ --output-format json 2>/dev/null)" || _cr_rc=$?
11038
+ if [ "$_cr_rc" -eq 0 ] && [ -n "$_cr_json" ] \
11039
+ && _LOKI_CR_JSON="$_cr_json" _LOKI_CR_OUT="$review_output" \
11040
+ python3 "$_cr_remat"; then
11041
+ return 0
11042
+ fi
11043
+ fi
11044
+ # fall through to the text path below (fail-closed)
11045
+ fi
10997
11046
  CAVEMAN_DEFAULT_MODE=off \
10998
11047
  claude "${_rv_argv[@]}" -p "$prompt_text" \
10999
11048
  --output-format text > "$review_output" 2>/dev/null
@@ -427,6 +427,14 @@ _verify_zero_tests_executed() {
427
427
  verify_gate_tests() {
428
428
  local tree="$1"
429
429
  local timeout_s="${LOKI_GATE_TIMEOUT:-300}"
430
+ # Bounded-exec prefix: "timeout <secs>" / "gtimeout <secs>", or EMPTY when
431
+ # neither is on PATH (bare macOS, or a shadowed PATH). A bare `timeout ...`
432
+ # would exit 127 (command not found) and be misread as a test FAILURE ->
433
+ # spurious BLOCKED verdict. Degrade to running without a wall-clock cap
434
+ # instead. Mirrors run.sh's with_timeout discipline.
435
+ local _vt_bin _vt
436
+ _vt_bin="$(_verify_runtime_timeout_bin)"
437
+ if [ -n "$_vt_bin" ]; then _vt="$_vt_bin $timeout_s"; else _vt=""; fi
430
438
  local runner="none"
431
439
  local rc=0
432
440
  local out=""
@@ -434,13 +442,13 @@ verify_gate_tests() {
434
442
  if [ -f "$tree/package.json" ]; then
435
443
  if grep -q '"vitest"' "$tree/package.json" 2>/dev/null; then
436
444
  runner="vitest"
437
- out="$(cd "$tree" && timeout "$timeout_s" npx vitest run 2>&1)" || rc=$?
445
+ out="$(cd "$tree" && $_vt npx vitest run 2>&1)" || rc=$?
438
446
  elif grep -q '"jest"' "$tree/package.json" 2>/dev/null; then
439
447
  runner="jest"
440
- out="$(cd "$tree" && timeout "$timeout_s" npx jest --passWithNoTests --forceExit 2>&1)" || rc=$?
448
+ out="$(cd "$tree" && $_vt npx jest --passWithNoTests --forceExit 2>&1)" || rc=$?
441
449
  elif grep -q '"mocha"' "$tree/package.json" 2>/dev/null; then
442
450
  runner="mocha"
443
- out="$(cd "$tree" && timeout "$timeout_s" npx mocha 2>&1)" || rc=$?
451
+ out="$(cd "$tree" && $_vt npx mocha 2>&1)" || rc=$?
444
452
  fi
445
453
  fi
446
454
 
@@ -480,7 +488,7 @@ verify_gate_tests() {
480
488
  if [ "$has_python" = "true" ]; then
481
489
  if command -v pytest >/dev/null 2>&1; then
482
490
  runner="pytest"
483
- out="$(cd "$tree" && timeout "$timeout_s" pytest --tb=short 2>&1)" || rc=$?
491
+ out="$(cd "$tree" && $_vt pytest --tb=short 2>&1)" || rc=$?
484
492
  elif command -v python3 >/dev/null 2>&1; then
485
493
  # #139 parity: pytest ABSENT but a Python suite exists -> run it with
486
494
  # stdlib unittest (zero third-party deps) instead of going blindly
@@ -490,7 +498,7 @@ verify_gate_tests() {
490
498
  # a "Ran 0 tests" line detected by the zero-test guard on the run.sh
491
499
  # mirror) so this never green-washes an empty suite.
492
500
  runner="unittest"
493
- out="$(cd "$tree" && timeout "$timeout_s" python3 -m unittest discover -p 'test_*.py' 2>&1)" || rc=$?
501
+ out="$(cd "$tree" && $_vt python3 -m unittest discover -p 'test_*.py' 2>&1)" || rc=$?
494
502
  else
495
503
  # Applicable but cannot run -> inconclusive (Entanglement 2).
496
504
  _verify_add_gate "tests" "inconclusive" "pytest" "python project detected but no pytest/python3 on PATH" "true"
@@ -502,7 +510,7 @@ verify_gate_tests() {
502
510
  if [ "$runner" = "none" ] && [ -f "$tree/go.mod" ]; then
503
511
  if command -v go >/dev/null 2>&1; then
504
512
  runner="go-test"
505
- out="$(cd "$tree" && timeout "$timeout_s" go test ./... 2>&1)" || rc=$?
513
+ out="$(cd "$tree" && $_vt go test ./... 2>&1)" || rc=$?
506
514
  else
507
515
  _verify_add_gate "tests" "inconclusive" "go-test" "go project detected but go not on PATH" "true"
508
516
  return 0
@@ -512,7 +520,7 @@ verify_gate_tests() {
512
520
  if [ "$runner" = "none" ] && [ -f "$tree/Cargo.toml" ]; then
513
521
  if command -v cargo >/dev/null 2>&1; then
514
522
  runner="cargo-test"
515
- out="$(cd "$tree" && timeout "$timeout_s" cargo test 2>&1)" || rc=$?
523
+ out="$(cd "$tree" && $_vt cargo test 2>&1)" || rc=$?
516
524
  else
517
525
  _verify_add_gate "tests" "inconclusive" "cargo-test" "rust project detected but cargo not on PATH" "true"
518
526
  return 0
@@ -550,7 +558,13 @@ verify_gate_tests() {
550
558
  runner="node-test"
551
559
  # Pass matched files explicitly (node --test globbing is
552
560
  # Node-version-sensitive); quote each so paths with spaces survive.
553
- out="$(cd "$tree" && timeout "$timeout_s" node --test "${_nt_files[@]}" 2>&1)" || rc=$?
561
+ # Force --test-reporter=tap: Node 20+ defaults to the human "spec"
562
+ # reporter (Node 26 emits it even under capture), whose "i pass N" /
563
+ # check-mark lines the count parser + zero-tests detector below do NOT
564
+ # match; they expect TAP "# pass N" / "ok N - ". TAP is stable since
565
+ # Node 18, so forcing it restores a version-independent format with no
566
+ # parser change. Parity with autonomy/run.sh:9662.
567
+ out="$(cd "$tree" && $_vt node --test --test-reporter=tap "${_nt_files[@]}" 2>&1)" || rc=$?
554
568
  fi
555
569
  fi
556
570
 
@@ -564,6 +578,22 @@ verify_gate_tests() {
564
578
  return 0
565
579
  fi
566
580
 
581
+ # #82 (Python 3.12+): `python3 -m unittest` exits 5 (NOT 0) when it discovers
582
+ # ZERO tests -- e.g. a suite of pytest-style bare `def test_*` functions that
583
+ # unittest (TestCase-only) cannot collect. That non-zero exit would otherwise
584
+ # fall into the fail branch below and record tests=fail -> BLOCKED, when the
585
+ # honest classification is "applicable but ran no real tests" = inconclusive
586
+ # -> CONCERNS. Route the unittest zero-discovery case through the same guard
587
+ # the rc==0 path uses (its unittest arm matches "Ran 0 tests"/"NO TESTS RAN"
588
+ # but was unreachable behind rc==0). Scoped to unittest so pytest/go/cargo/node
589
+ # non-zero exits still correctly record fail.
590
+ if [ "$rc" -eq 5 ] && [ "$runner" = "unittest" ] \
591
+ && _verify_zero_tests_executed "$runner" "$out" "${_nt_files[@]:-}"; then
592
+ _verify_add_gate "tests" "inconclusive" "$runner" \
593
+ "runner ran but discovered zero tests (source_without_runnable_tests)" "true"
594
+ return 0
595
+ fi
596
+
567
597
  if [ "$rc" -eq 0 ]; then
568
598
  # #82: a green run that executed ZERO real tests is a mini fake-green.
569
599
  # Record it as HONEST inconclusive (forces at-least-CONCERNS), NOT pass
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.128.2"
10
+ __version__ = "7.129.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.128.2
5
+ **Version:** v7.129.0
6
6
 
7
7
  ---
8
8
 
@@ -396,7 +396,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
396
396
  # Run Loki Mode in Docker (Claude provider, API-key auth)
397
397
  docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
398
398
  -v $(pwd):/workspace -w /workspace \
399
- asklokesh/loki-mode:7.128.2 start ./my-spec.md
399
+ asklokesh/loki-mode:7.129.0 start ./my-spec.md
400
400
  ```
401
401
 
402
402
  ##### docker compose + .env (no host install)
@@ -0,0 +1,278 @@
1
+ # RARV-C 100x Native-Primitives Upgrade - Plan (v8.0.0 arc)
2
+
3
+ ## Context
4
+
5
+ Founder mandate (memory `project-rarvc-cookbook-upgrade`): we can't out-build
6
+ Replit/Emergent/Cognition on capability. Loki's moat is loop RIGOR (never-fake-green
7
+ RARV-C, 3-reviewer council, evidence gate), which is ON-THESIS with Anthropic's own
8
+ long-running-agent guidance. The gap isn't rigor; it's efficiency: the loop does its
9
+ mechanics via home-grown bash (parsing model output as TEXT, polling for budget,
10
+ inferring cost from token math) where the installed `claude` CLI now ships NATIVE
11
+ primitives that do the same job cheaper, faster, and without fragile parsing.
12
+
13
+ This plan adopts those primitives where they MEASURABLY beat the bash equivalents, one
14
+ per commit, bench-proven, quality never regresses. MAJOR arc (v8.0.0) on branch
15
+ `feature/rarv-c-100x-primitives` (not yet created).
16
+
17
+ **The honest headline: much of this is already shipped.** Two prior plan docs exist
18
+ (`docs/RARV-C-LOOP-EFFICIENCY-PLAN.md`, `docs/RARV-C-CHANGE-MAP.md`, grounded to v7.121.5),
19
+ AND the engine has since adopted several native flags. So this plan does NOT regenerate
20
+ them. It: (1) reconciles drift to v7.128.2, (2) records what's ALREADY adopted so we don't
21
+ rebuild it, (3) scopes the genuinely net-new work (extend structured-outputs to 4 remaining
22
+ text-parse sites), (4) adds the missing PARITY and RELEASE sections, and consolidates into
23
+ `docs/RARV-C-100X-PLAN.md`.
24
+
25
+ ---
26
+
27
+ ## Primary-source verification (done live this session, CLI v2.1.207)
28
+
29
+ Per `reference-claude-session-flags` ("live-probe, don't trust stale training or --help
30
+ alone"), every flag was verified by RUNNING it:
31
+
32
+ | Primitive | Flag | Verified behavior (2026-07-13) |
33
+ |---|---|---|
34
+ | Structured output | `--json-schema <schema>` | Forces valid JSON. Envelope carries top-level `structured_output` key + `result` valid-JSON + `stop_reason:"tool_use"`. Model CANNOT emit malformed output. |
35
+ | Cost breaker | `--max-budget-usd <amt>` | On exceed: `subtype:"error_max_budget_usd"`, `is_error:true`, exit 0 (graceful, parseable). |
36
+ | Effort dial | `--effort low\|medium\|high\|xhigh\|max` | 5 levels; maps onto `get_rarv_tier()`. |
37
+ | Rate-limit resilience | `--fallback-model <model>` | Present. |
38
+ | Caching visibility | JSON envelope | `usage.cache_creation.ephemeral_1h_input_tokens` vs `ephemeral_5m` per call -> caching measurable. |
39
+ | Cost/iter readout | JSON envelope | `total_cost_usd`, per-model `costUSD`, `cache_read_input_tokens`, `iterations[]`. |
40
+ | Continuity | `--resume`/`--session-id`/`--fork-session` | iter0 `--session-id U0`, iterN `--resume U0`; `--session-id` reuse ERRORS. |
41
+
42
+ ---
43
+
44
+ ## What is ALREADY adopted (do NOT rebuild - ponytail rung 2)
45
+
46
+ Verified in current source. The prior CHANGE-MAP predates these:
47
+
48
+ - **`--effort` + `--max-budget-usd` are ALREADY plumbed** in `_loki_build_claude_auto_flags`
49
+ (`providers/claude.sh:143-158`), gated on `loki_claude_flag_supported` (degrades on old CLI),
50
+ mirrored in `loki-ts/src/providers/claude_flags.ts` (`effortForTier()`:23, `buildAutoFlags()`:150).
51
+ So "adopt native budget/effort breakers" is LARGELY DONE. `check_budget_limit()` (`run.sh:13035`)
52
+ and `is_rate_limited()` (`run.sh:12835`) still exist as the belt to the native suspenders.
53
+ - **`--json-schema` is ALREADY adopted for the council-vote path**: `voter-agents.sh:277`
54
+ (`claude --agents <json> --json-schema <schema>`), wired as PREFERRED council dispatch at
55
+ `completion-council.sh:3137-3144` with graceful text-parse fallback (3146-3154). Schema:
56
+ `loki-ts/data/finding-schema.json`. TS mirror: `loki-ts/src/council/voter_agents.ts:299-327`.
57
+ This pair is the TEMPLATE to extend, not a greenfield build.
58
+ - **Completion detection is already structured** via `.loki/signals/*` + `loki_complete_task`
59
+ MCP tool (`run.sh:13351-13369`); text-match is legacy behind `LOKI_LEGACY_COMPLETION_MATCH=true`.
60
+
61
+ **Net-new structured-output work = extend the voter-agents+schema template to the 4 remaining
62
+ text-parse sites. Everything else is tuning + bench + pointing side-calls at flags that exist.**
63
+
64
+ ---
65
+
66
+ ## Reconciled anchors (v7.128.2; lines drift, re-grep before editing)
67
+
68
+ **completion-council.sh:** `_council_effective_min_iter()`:118 | `COUNCIL_CHECK_INTERVAL`:84 |
69
+ `COUNCIL_STAGNATION_LIMIT`:132 | `council_evidence_gate()`:1704 | explicit-claim fast path:3640
70
+ (block 3625-3648) | modulo gate now in helper `_council_should_check_now()`:3521-3560 (gate:3548),
71
+ called from `council_should_stop`:3644 | `council_should_stop()`:3562
72
+
73
+ **run.sh:** `detect_complexity()`:2491 | `get_rarv_tier()`:2616 | `run_code_review()`:11095 |
74
+ `is_rate_limited()`:12835 | `check_budget_limit()`:13035 | `store_episode_trace()`:13729 |
75
+ `save_state()`:14395 | `build_prompt()`:14633 | `run_autonomous()`:16299 | `LOKI_SESSION_MODEL`
76
+ default:742, session-pin read:16959, case block:16963-16967 | `[CACHE_BREAKPOINT]` INERT (doc anchor
77
+ only, no cache wiring):15143/15231/15278 | APP_CRASHED injection:14969 (python3 -c inline)
78
+
79
+ **Main-loop invocation:** `run.sh:17316-17320` -> `claude "${_loki_claude_argv[@]}" -p "$prompt"
80
+ --output-format stream-json --verbose` (argv ~17226+). Uses stream-json, not --json-schema.
81
+ Model catalog: opus->claude-opus-4-8, sonnet->claude-sonnet-5, haiku->claude-haiku-4-5; defaults
82
+ planning/development/fast ALL=sonnet (v7.104.0).
83
+
84
+ ### The 4 remaining text-parse sites (the genuine structured-output targets)
85
+ 1. **Code-review verdict** `run.sh:11044-11063` `_classify_verdict` (regex `grep -iE "VERDICT:"`
86
+ -> case FAIL/PASS/AMBIGUOUS, no retry) + severity greps :11075/:11090; reviewer call :10998
87
+ uses `--output-format text`. Parity-locked to `loki-ts/src/runner/quality_gates.ts`.
88
+ 2. **Council VOTE fallback** `completion-council.sh:645-659` (`grep -oE VOTE:`) + `:1005` (Python
89
+ `re.search`) - the fallback under the already-structured path; mangled VOTE silently->REJECT.
90
+ 3. **Council-v2 JSON** `council-v2.sh:337-346` (`sed -n '/^{/,/^}/p'` carves JSON from prose, else
91
+ hardcodes REJECT); invocation :299 (no schema).
92
+ 4. **Done-recognition** `done-recognition.sh:282-303` (`json.loads` + brace-substring fallback,
93
+ else `inconclusive`); call :57/:171 (no schema).
94
+
95
+ ---
96
+
97
+ ## Deliverable 1: Bench harness (BUILD/LOCK THIS FIRST - nothing ships before it)
98
+
99
+ Reuse `benchmarks/bench/` (~95% built per existing plan S2a). The work is lock + reconcile +
100
+ baseline, not a build:
101
+ - **Lock a discriminator-derived corpus** (existing plan 2e-HARDENING #3): simple + HARD (tier-guard)
102
+ + MULTI-FAILURE (parallel-fix) + TOKEN-HEAVY (distillation), each with machine-checkable held-out
103
+ acceptance. Frozen once baselined.
104
+ - **Reconcile iterations source** to `events.jsonl` `iteration_complete` count (canonical) with
105
+ session.json fallback, so `adapters/loki.py` and `speed-benchmark.sh` agree. PREREQUISITE, blocking.
106
+ - **NEW (from live probe):** read cost/cache/iterations straight from the `--json-schema`/`--output-format
107
+ json` envelope (`total_cost_usd`, `cache_read_input_tokens`, `iterations[]`) instead of token-math x
108
+ price-table where available - more accurate, self-consistent with the CLI.
109
+ - **Noise floor** (blocking): baseline reports SPREAD (std/min-max across trials); a change ships only
110
+ if its metric moves BEYOND the noise band. Effect < trial variance -> "no measurable effect", don't ship.
111
+ - **2-spec smoke check** (the one runnable guard): one MUST-pass, one underspecified MUST-fail; assert
112
+ `verified_pass == [True, False]`. If the grader can't tell them apart it measures nothing.
113
+ - **Commit the v7.128.2 baseline** (`benchmarks/results/<baseline>.json`, N real OFF-WORKTREE builds,
114
+ isolated source copy, cwd + LOKI_TARGET_DIR pinned to scratch) BEFORE any change.
115
+ - **Grader stays deterministic** `success = exit==0` on held-out acceptance. NEVER council/completion-claim.
116
+ `validate_adapter_output()` already rejects self-judged keys - keep that invariant.
117
+
118
+ **Gate rule (binding, every change):** ships iff it improves >=1 of {iterations, $/build, wall-clock}
119
+ beyond the noise band AND does not regress verified-pass-rate on the frozen corpus vs baseline.
120
+
121
+ ---
122
+
123
+ ## Deliverable 2: Primitive-by-primitive adoption table (ranked, one per commit)
124
+
125
+ Bucket key: **(a)** orchestration/flags, changeable now | **(b)** founder-gated (build_prompt
126
+ byte-lock) | **(c)** needs off-worktree real-build validation.
127
+
128
+ | # | Primitive / change | Current impl (file:line) | Native replacement | Expected win | Bucket | Parity | Rollback |
129
+ |---|---|---|---|---|---|---|---|
130
+ | 1 | **Structured code-review verdict** | `run.sh:11044` regex `_classify_verdict` + severity greps :11075/:11090; reviewer :10998 `--output-format text` | reviewer emits `--json-schema` (extend `voter-agents.sh`+`finding-schema.json` template); read `structured_output.verdict/severity` | Kills fragile parse + AMBIGUOUS-token bugs; zero retry. Quality-safety, not speed | (a) | safe (invocation flag, NOT build_prompt); MUST mirror in `quality_gates.ts` | keep text fallback path (like council does 3146-3154) |
131
+ | 2 | **Structured council VOTE** (retire text fallback) | `completion-council.sh:645/1005` regex/re.search fallback | make the already-wired `--json-schema` path (3137) the only path; delete text fallback once proven | Removes silent VOTE->REJECT corruption (:2337) | (a) | safe; TS mirror `voter_agents.ts` | revert to fallback |
132
+ | 3 | **Structured council-v2** | `council-v2.sh:337-346` sed-carved JSON | add `--json-schema` + `--output-format json` to :299 | Removes "prose around JSON"->hardcoded REJECT | (a) | safe | hardcoded-REJECT fallback stays |
133
+ | 4 | **Structured done-recognition** | `done-recognition.sh:282-303` json.loads+brace-slice | add `--json-schema` to :57/:171 | Removes unparsable->inconclusive | (a) | safe | inconclusive fallback stays |
134
+ | 5 | **Council interval sweep** (existing CHANGE-MAP #1) | `completion-council.sh:84` interval=5 | tune 2-3 for simple tier | fewer idle iters -> $/wall-clock | (a) | n/a | env default |
135
+ | 6 | **Complexity->tier routing** (existing #3) | `run.sh:742` flat sonnet; case :16963 | `auto` arm: simple->haiku-enable, complex->opus-pin | $/build on simple majority | (c) | 3 readers + hard-guard | revert to `sonnet` |
136
+ | 7 | **Native budget/effort tighten** | ALREADY plumbed `claude.sh:143-158`; side-calls (:10998, council-v2:299, done-rec:57) still bare | point side-calls at `--max-budget-usd`/`--effort` too | consistent native breaker everywhere | (a) | mirror bash<->TS flags | drop flag |
137
+ | 8 | **Prompt caching** (existing #7, CONTINGENT) | `[CACHE_BREAKPOINT]` INERT `run.sh:15143` | wire real `cache_control` on static prefix IF CLI folds it across per-iter processes | $/build on multi-iter (cache reads visible in envelope) | (b)-adjacent | verify CLI first; may be no-op | remove marker wiring |
138
+ | 9 | **Self-heal from logs** (existing #4) | APP_CRASHED count only `run.sh:14969`; LAST_ERROR write-only | route `tail+grep` error signature into prompt | pass-rate on crashing specs | (c) | build_prompt-adjacent -> check byte-lock | honest-degrade to today |
139
+
140
+ Ranked order to execute: **1 -> 2 -> 3 -> 4** (structured-outputs cluster, all bucket-(a),
141
+ parity-safe, mostly quality/robustness - do first, low risk) **-> 5** (interval, zero code)
142
+ **-> 7** (side-call flag tighten) **-> 6** (tier routing, (c)) **-> 9** (self-heal, (c)) **-> 8**
143
+ (caching, contingent, verify CLI first). Each: change, re-run corpus, apply gate rule. No bench
144
+ move the right way = don't ship (ponytail: no no-ops).
145
+
146
+ **Do NOT** touch `build_prompt()` output text (items that would: 8, 9 partially) without founder
147
+ auth + 60-fixture regen. Structured-outputs items 1-4 do NOT touch build_prompt - confirmed
148
+ parity-safe below.
149
+
150
+ ---
151
+
152
+ ## Deliverable 3: Parity plan
153
+
154
+ **The load-bearing fact:** the 60-fixture SHA-256 lock (`loki-ts/tests/parity/build_prompt.test.ts`,
155
+ KNOWN_FAILING empty = hard 60/60) hashes ONLY `buildPrompt()` return TEXT. CLI flags come from a
156
+ separate surface (`claude_flags.ts` / `voter_agents.ts` / `providers.ts:244`). **So adding
157
+ `--json-schema`/`--effort`/`--max-budget-usd` to a claude call is parity-SAFE w.r.t. the lock**, and
158
+ also doesn't touch the 10-command CLI matrix (which never invokes a model).
159
+
160
+ **The parity TRAP this arc must own** (agent-verified): a native flag added to ONE route only
161
+ (bash `autonomy/lib/claude-flags.sh` vs TS `loki-ts/src/providers/claude_flags.ts`) would **pass all
162
+ four parity gates** (bun-parity.yml, parity-drift.yml, local-ci matrix, build_prompt lock) while
163
+ diverging at RUNTIME - because the matrix doesn't spawn the model. Therefore:
164
+ - **Every native-flag change lands in BOTH `claude-flags.sh` AND `claude_flags.ts`, byte-mirrored**,
165
+ gated on `claudeFlagSupported()`/`loki_claude_flag_supported` for old-CLI degrade.
166
+ - **Proven by the flag-parity unit tests** (`loki-ts/tests/runner/providers.test.ts`,
167
+ `tests/test-bash-bun-parity.sh` run at `local-ci.sh:390`), NOT the CLI matrix. Add a case per new flag.
168
+ - Structured-output items 1-4: mirror the bash change (voter-agents/council-v2/done-rec/quality-gates)
169
+ in the corresponding TS (`council/voter_agents.ts`, `runner/quality_gates.ts`).
170
+ - **build_prompt-touching items (8, 9)**: founder-gated. Regenerate all 60 fixtures via `run-bash.sh`
171
+ (`for i in $(seq 1 60); do bash run-bash.sh fixture-$i; done`), regenerate `index.json`, keep bash<->TS
172
+ 60/60 in the SAME PR. Watch the fixtures 27/45 alphabetical-readdir determinism guard.
173
+ - **Doctor normalizers are TRIPLICATED** (`bun-parity.yml:127-184`, `parity-drift.yml:83-135`,
174
+ `local-ci.sh:812-860`) with subtle diffs (JSON floor-vs-delete, counts kept-vs-blanked). If any
175
+ primitive changes doctor output, edit ALL THREE in sync.
176
+
177
+ Every commit proven on BOTH routes (`bin/loki` + `LOKI_LEGACY_BASH=1 bin/loki`).
178
+
179
+ ---
180
+
181
+ ## Deliverable 4: Gate plan
182
+
183
+ - **Trust-core = 3-reviewer council** (2 Opus + 1 Sonnet, unanimous APPROVE, reviewers RUN tests).
184
+ Any CONCERN/REJECT -> read source, fix, RE-RUN whole council. Loop to 3/3. Never "2-of-3".
185
+ - **`bash scripts/local-ci.sh` green before EVERY push** (Step 0). It mirrors every workflow:
186
+ bash -n + shellcheck, pytest, `bun typecheck` + `bun test` (the 60-fixture lock runs here),
187
+ CLI dual-route (`test-cli-commands.sh` Bun + LEGACY_BASH), `test-bash-bun-parity.sh`, the local
188
+ bun-parity matrix, npm-pack contents (>=6 load-bearing files), SBOM, no-emoji, no-`git add -A`.
189
+ - **Extraction-test harness for bash fn changes** (`feedback-function-extraction-test-harness`):
190
+ extract the changed fn AND every helper it calls (grep `sed -n '/^<fn>() {/'` tests); `set +u`
191
+ inside the eval subshell; prove runtime-vs-test before "fixing" a red test (fix the TEST if runtime
192
+ is right, never patch correct code).
193
+ - **RARV-C changes proven with hermetic Bun integration tests** (stub judge, `LOKI_OVERRIDE_REAL_JUDGE=0`,
194
+ mkdtemp scratch, no token cost) - pattern in `loki-ts/tests/integration/override_on_block.test.ts`.
195
+ - **Never run an engine build from the worktree** (`feedback-never-run-engine-build`): all real builds
196
+ from isolated source copy, cwd + LOKI_TARGET_DIR pinned to scratch.
197
+
198
+ ---
199
+
200
+ ## Deliverable 5: Release plan (v8.0.0, MAJOR)
201
+
202
+ - **14 version locations** (all verified at 7.128.2, zero drift): VERSION:1, package.json:4,
203
+ SKILL.md:6+411, Dockerfile:9/14, Dockerfile.sandbox:102/103, plugins/.../plugin.json:5, CLAUDE.md:306,
204
+ dashboard/__init__.py:10, mcp/__init__.py:60, CHANGELOG.md:8, docs/INSTALLATION.md:5(+399 docker tag),
205
+ wiki/Home.md:106, wiki/_Sidebar.md:46, wiki/API-Reference.md:69.
206
+ - **MAJOR bump also touches docker-tag group** (README.md ~81/~380, docs/INSTALLATION.md 7+ tags,
207
+ docker-compose.yml:1).
208
+ - **Do NOT touch** (independently versioned, would break if "fixed"): `sdk/python/pyproject.toml`
209
+ (auto-synced by release.yml:352 at publish), `sdk/typescript/package.json` (5.55.0),
210
+ `loki-ts/package.json` (0.1.0-alpha.1), `vscode-extension/package.json` (deprecated).
211
+ - **FIX in v8 - release-notes bug** (`release.yml:179/231`): awk expects bracketed `## [VERSION]` but
212
+ CHANGELOG uses unbracketed `## vVERSION`, so release notes have silently been generic. Fix the awk or
213
+ the header format (one PR, verify the extracted body is non-empty).
214
+ - **Dashboard frontend rebuilt** (`cd dashboard-ui && npm ci && npm run build:all`) - writes both
215
+ `dashboard-ui/dist/` and `dashboard/static/`.
216
+ - **Pre-publish validation** (CLAUDE.md 3a): `npm pack --dry-run` contains web-app/dist + dashboard/static;
217
+ fresh global install serves web app + API.
218
+ - **Per-job release verification**: `gh run view <id> --json jobs` per channel (npm/Docker/Homebrew/Release),
219
+ not just "workflow green".
220
+ - **Post-release smoke from SHIPPED artifacts on BOTH routes**: `npm pack loki-mode@8.0.0` + `bun run bin/loki
221
+ version` and `LOKI_LEGACY_BASH=1 ... version`; `docker run --rm asklokesh/loki-mode:8.0.0 doctor --json`;
222
+ WebFetch brew formula version+sha.
223
+ - **Cleanup after every local-ci/validation**: `lsof -ti:57374 | xargs kill -9; rm -rf /tmp/loki-* /tmp/test-* /tmp/package /tmp/*.tgz`.
224
+
225
+ ---
226
+
227
+ ## Do-NOT list (binding)
228
+
229
+ 1. Do NOT replace the 3-reviewer council with a single-LLM outcome-grader. Structured-outputs changes
230
+ the OUTPUT FORMAT (text->JSON), NOT the 3-judge architecture. That's the moat.
231
+ 2. Do NOT add an LLM judge to the bench grader (deterministic exit==0 only; `validate_adapter_output`
232
+ rejects self-judged keys).
233
+ 3. Do NOT trust council APPROVE / completion-claim as the bench pass signal (fake-green failure mode).
234
+ 4. Do NOT touch `build_prompt()` output text without founder auth + 60-fixture regen + 60/60 bash<->TS.
235
+ 5. Do NOT change corpus/grader/price-table across a before/after pair.
236
+ 6. Do NOT run a real engine build from the worktree.
237
+ 7. Do NOT add a native flag to one route only (silent runtime divergence past all parity gates).
238
+ 8. Do NOT ship a change that doesn't move the bench the right way.
239
+ 9. Do NOT rebuild what's already adopted (--effort/--max-budget-usd plumbing, council --json-schema path).
240
+
241
+ ---
242
+
243
+ ## FOUNDER SCOPE DECISIONS (2026-07-13, locked)
244
+
245
+ - **SCOPE = FULL ARC** including founder-gated items 8 (caching) and 9 (self-heal-from-logs).
246
+ This constitutes explicit authorization to unlock `build_prompt()`'s 60-fixture byte-lock WHEN
247
+ those items come up - executed per Deliverable 3 (regenerate all 60 fixtures via `run-bash.sh`,
248
+ keep bash<->TS 60/60 in the same PR, full council + local-ci). Founder-gated items still run LAST,
249
+ after all bucket-(a) structured-output wins are banked and bench-measured. Caching (8) still
250
+ requires the CLI-mechanics verification (does the CLI fold cache_control across per-iteration
251
+ processes?) BEFORE committing - if it's a no-op, report that and drop it, don't ship a no-op.
252
+ - **BASELINE = 3-SPEC SMOKE FIRST.** Prove the full pipeline (baseline -> one change -> measure ->
253
+ gate rule) on a tiny 3-spec corpus to validate the harness machinery cheaply, THEN build the real
254
+ discriminator-derived corpus (simple+hard+multi-failure+token-heavy) and run the full v7.128.2
255
+ baseline. Catches harness bugs before the big real-dollar run.
256
+ - **Honesty pre-commit stands:** most iteration wins are already shipped; expect single-digit-% to
257
+ maybe-2x from tuning, NOT 100x (the real 100x is the already-scoped k8s INFRA work, not this arc).
258
+ Report the true number even if unimpressive.
259
+
260
+ ## Consolidation + sequencing
261
+
262
+ 1. Create branch `feature/rarv-c-100x-primitives`. Build harness, run 3-SPEC SMOKE to validate the
263
+ pipeline, then commit the full v7.128.2 baseline (Deliverable 1) before any change.
264
+ 2. Write `docs/RARV-C-100X-PLAN.md` = this plan (supersedes/links the two prior v7.121.5 docs; keep
265
+ them as history, note they're superseded).
266
+ 3. Execute the adoption table in ranked order, one primitive per commit, bench + full gate after each.
267
+ 4. Founder-gated items (8 caching, 9 self-heal build_prompt half) LAST, only after (a)-items banked and
268
+ explicit unlock.
269
+ 5. Release v8.0.0 per Deliverable 5 once the bench shows the aggregate win (or honestly report the real
270
+ delta if single-digit - the plan pre-commits to reporting the true number, not dressing it up).
271
+
272
+ ## Verification (how to test end-to-end)
273
+ - Bench: `bash benchmarks/bench/run.sh run <spec> --trials 3` from isolated scratch; read
274
+ iterations/cost/duration from result JSON; grader `exit==0`. 2-spec smoke asserts [pass, fail].
275
+ - Structured-outputs: hermetic Bun integration test with stub judge asserts JSON verdict parsed,
276
+ no text-regex path hit; + extraction test for the bash side.
277
+ - Parity: `bash scripts/local-ci.sh` green (runs 60-fixture lock + dual-route CLI + flag-parity tests).
278
+ - Per commit: full 3-reviewer council + local-ci, both routes.
@@ -1,5 +1,10 @@
1
1
  # RARV-C Efficiency Change Map (apply-and-measure, from lever investigation)
2
2
 
3
+ > SUPERSEDED by `docs/RARV-C-100X-PLAN.md` (v8.0.0 arc, 2026-07-13). Kept as history.
4
+ > Anchors here are grounded to v7.121.5 and have DRIFTED; use the 100X plan for current
5
+ > file:line. Note: budget/effort (#7) and council --json-schema were adopted AFTER this doc.
6
+
7
+
3
8
  Both checks resolve cleanly:
4
9
 
5
10
  **Check 1 (env propagation): CONFIRMED SAFE.** `_base.py:113` does `full_env = dict(os.environ)` then passes it as `env=full_env` to subprocess. The loki adapter (`loki.py:120`) uses `run_env`. So env-prefixing the bench invocation propagates the knob into the loki subprocess, where completion-council.sh:84 reads it at source time. Every `KNOB=x bench run` command in the plan is a real measurement, not a no-op.
@@ -1,5 +1,10 @@
1
1
  # RARV-C Loop-Efficiency Upgrade Plan (ultracode phase)
2
2
 
3
+ > SUPERSEDED by `docs/RARV-C-100X-PLAN.md` (v8.0.0 arc, 2026-07-13). Kept as history.
4
+ > Anchors here are grounded to v7.121.5 and have DRIFTED; use the 100X plan for current
5
+ > file:line and for the native-primitives-already-adopted findings.
6
+
7
+
3
8
  Architect: synthesis of the six cookbook pattern sets against the current
4
9
  Loki RARV-C loop. This is a PLAN, not code. Every proposed change carries a
5
10
  constraint bucket and a bench-gate. No benchmark numbers are asserted here --
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://loki-mode.dev/schemas/code-review.json",
4
+ "title": "Loki Code-Review Verdict",
5
+ "description": "Structured output contract for the 8-gate code-review reviewer (main + Devils-Advocate). Claude Code emits this when invoked with --json-schema (INLINE content, not a path) --output-format json. A python3/TS adapter re-materializes it into the legacy 'VERDICT: X\\nFINDINGS:\\n- [severity] description' text so _classify_verdict, _severity_is_blocking, _count_nonblocking_findings, the mergeability score, the DA arm, and aggregate.json stay byte-identical. verdict is the sole gating token; findings[].severity feeds blocking (Critical/High) and mergeability (Medium/Low) weighting. NOTE: JSON Schema cannot express the cross-field rule 'a Critical/High finding requires verdict FAIL'; the re-materializer enforces it (force-FAIL on any Critical/High finding) so a self-contradictory PASS+Critical can never be waved through as non-blocking.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["verdict", "findings"],
9
+ "properties": {
10
+ "verdict": {
11
+ "type": "string",
12
+ "enum": ["PASS", "FAIL"],
13
+ "description": "PASS if no blocking issue; FAIL if a real issue was found. A FAIL with a Critical or High finding blocks the merge; a FAIL with only Medium/Low is a non-blocking dissent. If any finding is Critical or High you MUST set verdict to FAIL."
14
+ },
15
+ "findings": {
16
+ "type": "array",
17
+ "minItems": 0,
18
+ "description": "Zero or more issues. Empty array means clean (equivalent to the legacy '- None' bullet).",
19
+ "items": {
20
+ "type": "object",
21
+ "additionalProperties": false,
22
+ "required": ["severity", "description"],
23
+ "properties": {
24
+ "severity": {
25
+ "type": "string",
26
+ "enum": ["Critical", "High", "Medium", "Low"],
27
+ "description": "Critical/High are blocking; Medium/Low are advisory and feed the mergeability score."
28
+ },
29
+ "description": {
30
+ "type": "string",
31
+ "minLength": 1,
32
+ "maxLength": 2000,
33
+ "description": "One-line issue description, ideally ending with (file:line)."
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://loki-mode.dev/schemas/council-v2.json",
4
+ "title": "Loki Council-v2 Reviewer Verdict",
5
+ "description": "Structured output contract for a council-v2 reviewer (requirements/test/quality/devils-advocate/general). Claude Code emits this when invoked with --json-schema (INLINE content, not a path) --output-format json. The council-v2 dispatch reads structured_output directly; a sed-carve of prose JSON remains the fail-closed fallback. verdict is the gating token; any parse miss defaults to REJECT (the safe direction).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["verdict", "reasoning", "issues"],
9
+ "properties": {
10
+ "verdict": {
11
+ "type": "string",
12
+ "enum": ["APPROVE", "REJECT"],
13
+ "description": "APPROVE if the evidence satisfies the requirements for this reviewer's lens; REJECT otherwise."
14
+ },
15
+ "reasoning": {
16
+ "type": "string",
17
+ "minLength": 1,
18
+ "maxLength": 4000,
19
+ "description": "Short justification for the verdict."
20
+ },
21
+ "issues": {
22
+ "type": "array",
23
+ "minItems": 0,
24
+ "description": "Zero or more issues found. Empty array is allowed on APPROVE.",
25
+ "items": {
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": ["severity", "description"],
29
+ "properties": {
30
+ "severity": {
31
+ "type": "string",
32
+ "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"],
33
+ "description": "Issue severity."
34
+ },
35
+ "description": {
36
+ "type": "string",
37
+ "minLength": 1,
38
+ "maxLength": 2000,
39
+ "description": "One-line issue description."
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://loki-mode.dev/schemas/done-recognition.json",
4
+ "title": "Loki Done-Recognition Verdict",
5
+ "description": "Structured output contract for the done-recognition gate (is a reused PRD already satisfied by the current codebase?). Claude Code emits this when invoked with --json-schema (INLINE content, not a path) --output-format json. The gate DEFENSIVELY re-derives the verdict from requirements[].status, never trusting the top-line verdict, so a valid requirements array is the load-bearing part. Any parse miss falls back to the existing json.loads/brace-slice path, which defaults to inconclusive (the safe direction).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["verdict", "summary", "requirements"],
9
+ "properties": {
10
+ "verdict": {
11
+ "type": "string",
12
+ "enum": ["done", "incomplete", "inconclusive"],
13
+ "description": "Top-line claim. done ONLY when ALL requirements are met AND fresh tests are green; incomplete when any requirement is unmet; inconclusive when ground truth cannot be established. The gate re-derives this from requirements[].status regardless."
14
+ },
15
+ "summary": {
16
+ "type": "string",
17
+ "maxLength": 2000,
18
+ "description": "One plain sentence for the user."
19
+ },
20
+ "tests": {
21
+ "type": "object",
22
+ "additionalProperties": false,
23
+ "required": ["passed", "total", "green"],
24
+ "properties": {
25
+ "passed": { "type": "integer", "minimum": 0 },
26
+ "total": { "type": "integer", "minimum": 0 },
27
+ "green": { "type": "boolean" }
28
+ }
29
+ },
30
+ "requirements": {
31
+ "type": "array",
32
+ "minItems": 0,
33
+ "description": "One entry per PRD requirement, with its satisfaction status. The gate re-derives the verdict from these statuses.",
34
+ "items": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["title", "status"],
38
+ "properties": {
39
+ "id": { "type": "string", "maxLength": 256 },
40
+ "title": { "type": "string", "minLength": 1, "maxLength": 512 },
41
+ "status": {
42
+ "type": "string",
43
+ "enum": ["met", "unmet", "uncertain"],
44
+ "description": "met = proven satisfied; unmet = not implemented; uncertain = cannot establish."
45
+ },
46
+ "evidence": { "type": "string", "maxLength": 1024 }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
@@ -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.128.2";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.129.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=3642C2645E1F762E64756E2164756E21
817
+ //# debugId=4C57B2F2D01C30E264756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.128.2'
60
+ __version__ = '7.129.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.128.2",
4
+ "version": "7.129.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.128.2",
5
+ "version": "7.129.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",