loki-mode 7.32.2 → 7.33.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, 11 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.32.2
6
+ # Loki Mode v7.33.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -398,4 +398,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
398
398
 
399
399
  ---
400
400
 
401
- **v7.32.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.33.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.32.2
1
+ 7.33.0
@@ -1759,7 +1759,23 @@ ISSUES: CRITICAL:description (optional, one per line per issue)"
1759
1759
  claude)
1760
1760
  if command -v claude &>/dev/null; then
1761
1761
  local council_model="${PROVIDER_MODEL_FAST:-haiku}"
1762
- verdict=$(echo "$prompt" | claude --model "$council_model" -p 2>/dev/null | tail -20)
1762
+ # EMBED 2 + 3 (v7.33.0). Council member completion vote. The
1763
+ # $prompt is fully self-contained (evidence + instructions +
1764
+ # strict VOTE/REASON/ISSUES output format, piped via stdin) and
1765
+ # the verdict is captured. So --bare (cheap, no hooks/LSP/CLAUDE.
1766
+ # md/MCP) and --disallowedTools (a voting reviewer must never
1767
+ # mutate the tree) both apply. Gated + opt-out
1768
+ # LOKI_BARE_SUBCALLS=0 / LOKI_REVIEW_TOOL_GUARD=0. Helpers may be
1769
+ # out of scope when this file is sourced standalone, so each is
1770
+ # type-guarded (degrades to the prior bare invocation).
1771
+ local _cm_argv=("--model" "$council_model")
1772
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
1773
+ _cm_argv+=("--bare")
1774
+ fi
1775
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
1776
+ _cm_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
1777
+ fi
1778
+ verdict=$(echo "$prompt" | claude "${_cm_argv[@]}" -p 2>/dev/null | tail -20)
1763
1779
  fi
1764
1780
  ;;
1765
1781
  codex)
@@ -1842,7 +1858,19 @@ REASON: your reasoning"
1842
1858
  claude)
1843
1859
  if command -v claude &>/dev/null; then
1844
1860
  local council_model="${PROVIDER_MODEL_FAST:-haiku}"
1845
- verdict=$(echo "$prompt" | claude --model "$council_model" -p 2>/dev/null | tail -20)
1861
+ # EMBED 2 + 3 (v7.33.0). Contrarian (devil's-advocate) vote --
1862
+ # an adversarial reviewer. Self-contained $prompt via stdin,
1863
+ # verdict captured. --bare + --disallowedTools both apply (a
1864
+ # reviewer must never mutate the tree). Gated + opt-out
1865
+ # LOKI_BARE_SUBCALLS=0 / LOKI_REVIEW_TOOL_GUARD=0; type-guarded.
1866
+ local _co_argv=("--model" "$council_model")
1867
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
1868
+ _co_argv+=("--bare")
1869
+ fi
1870
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
1871
+ _co_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
1872
+ fi
1873
+ verdict=$(echo "$prompt" | claude "${_co_argv[@]}" -p 2>/dev/null | tail -20)
1846
1874
  fi
1847
1875
  ;;
1848
1876
  codex)
@@ -263,7 +263,20 @@ Respond ONLY with a valid JSON object. No markdown fencing."
263
263
  case "${PROVIDER_NAME:-claude}" in
264
264
  claude)
265
265
  if command -v claude &>/dev/null; then
266
- result=$(echo "$full_prompt" | claude --model haiku -p 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
266
+ # EMBED 2 + 3 (v7.33.0). Council-v2 reviewer verdict. $full_prompt
267
+ # is self-contained (evidence + PRD + strict JSON output, via
268
+ # stdin) and the JSON result is captured. --bare + --disallowedTools
269
+ # both apply (a reviewer must never mutate the tree). Gated +
270
+ # opt-out LOKI_BARE_SUBCALLS=0 / LOKI_REVIEW_TOOL_GUARD=0;
271
+ # type-guarded for standalone sourcing.
272
+ local _c2_argv=("--model" "haiku")
273
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
274
+ _c2_argv+=("--bare")
275
+ fi
276
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
277
+ _c2_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
278
+ fi
279
+ result=$(echo "$full_prompt" | claude "${_c2_argv[@]}" -p 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
267
280
  else
268
281
  result='{"verdict":"REJECT","reasoning":"reviewer CLI unavailable","issues":[]}'
269
282
  fi
package/autonomy/grill.sh CHANGED
@@ -38,6 +38,16 @@ GRILL_EXIT_ERROR=3
38
38
  GRILL_DIR_DEFAULT=".loki/grill"
39
39
  GRILL_REPORT_NAME="report.md"
40
40
 
41
+ # Source the claude-flags helper (idempotent, guarded) so the v7.33.0 embeds
42
+ # (--bare / --disallowedTools) are available in grill's standalone invocation
43
+ # path. Best-effort: if the file is missing the type-guards at the call site
44
+ # degrade to the prior bare invocation.
45
+ _grill_flags_helper="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/claude-flags.sh"
46
+ if [ -f "$_grill_flags_helper" ]; then
47
+ # shellcheck disable=SC1090
48
+ . "$_grill_flags_helper"
49
+ fi
50
+
41
51
  _grill_log() { printf '[grill] %s\n' "$*" >&2; }
42
52
  _grill_err() { printf '[grill][error] %s\n' "$*" >&2; }
43
53
 
@@ -178,8 +188,24 @@ grill_invoke_provider() {
178
188
  local out
179
189
  # Single-shot, non-interactive. Same pattern as the in-loop
180
190
  # adversarial reviewer (run.sh:7807) and USAGE regen (run.sh:9832).
191
+ # EMBED 2 + 3 (v7.33.0). The devil's-advocate grill is a cheap
192
+ # self-contained adversarial subcall (the entire spec + interrogation
193
+ # instructions are in $prompt, piped via -p -; output captured). So:
194
+ # EMBED 2 (--bare): no hooks/LSP/CLAUDE.md/MCP needed; cheaper.
195
+ # Opt out LOKI_BARE_SUBCALLS=0.
196
+ # EMBED 3 (--disallowedTools): a grill agent interrogates a spec and
197
+ # must never mutate the tree (defense-in-depth). Deny Edit/Write/
198
+ # NotebookEdit + git mutations. Opt out LOKI_REVIEW_TOOL_GUARD=0.
199
+ # Type-guarded so an environment without the helper degrades cleanly.
200
+ local _gr_argv=("--dangerously-skip-permissions" "--model" "${LOKI_GRILL_MODEL:-sonnet}")
201
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
202
+ _gr_argv+=("--bare")
203
+ fi
204
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
205
+ _gr_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
206
+ fi
181
207
  out="$(printf '%s' "$prompt" \
182
- | _grill_with_timeout "${LOKI_GRILL_TIMEOUT:-180}" claude --dangerously-skip-permissions --model "${LOKI_GRILL_MODEL:-sonnet}" -p - 2>/dev/null)"
208
+ | _grill_with_timeout "${LOKI_GRILL_TIMEOUT:-180}" claude "${_gr_argv[@]}" -p - 2>/dev/null)"
183
209
  if [ -z "$out" ]; then
184
210
  _grill_err "provider returned no output (timeout or invocation error)"
185
211
  return $GRILL_EXIT_ERROR
@@ -130,3 +130,99 @@ loki_claude_flag_supported() {
130
130
  *) return 1 ;;
131
131
  esac
132
132
  }
133
+
134
+ # ---------- v7.33.0 cheap-subcall + reviewer-guard flag emitters ----------
135
+ # These centralize three Claude Code 2.1.170 embeds so every cheap NON-MAIN
136
+ # subcall site emits the same gated flags. Each emitter prints its flags to
137
+ # stdout (space-free per element via one-per-line is not needed; callers read
138
+ # them into an array with command substitution + word-splitting on the single
139
+ # token they emit, OR append the function output as additional argv elements).
140
+ # All are default-ON, opt-out via env, and gated on loki_claude_flag_supported
141
+ # so an older CLI degrades gracefully (emits nothing).
142
+
143
+ # EMBED 2 -- --bare (cheap NON-MAIN subcalls only). Minimal mode. Per
144
+ # `claude --help` it SKIPS hooks, LSP, plugin sync, attribution, auto-memory,
145
+ # background prefetches, keychain reads, and CLAUDE.md AUTO-discovery. It does
146
+ # NOT nullify explicit --mcp-config/--settings/--agents (the help lists those as
147
+ # the way to "explicitly provide context" UNDER --bare); what it drops is the
148
+ # IMPLICIT/auto-discovered context. Therefore --bare is ONLY safe on subcalls
149
+ # whose prompt is fully self-contained (the entire instruction set + context is
150
+ # passed via -p), and NEVER on the main RARV loop or on any call that relies on
151
+ # auto-discovered CLAUDE.md / hooks / auto-memory.
152
+ #
153
+ # AUTH GATE (critical): --bare sets CLAUDE_CODE_SIMPLE=1 and per `claude --help`
154
+ # reads Anthropic auth STRICTLY from ANTHROPIC_API_KEY or an apiKeyHelper via
155
+ # --settings -- "OAuth and keychain are never read". A subscription/OAuth user
156
+ # (no ANTHROPIC_API_KEY) gets "Not logged in" on every --bare subcall, which
157
+ # exits 0 with the error on stdout, so a council vote parses as the default
158
+ # REJECT and the loop silently corrupts. So --bare is enabled ONLY when an
159
+ # API-key auth path exists (ANTHROPIC_API_KEY set, or an apiKeyHelper configured
160
+ # in settings); otherwise it emits nothing and the subcall runs full-auth, the
161
+ # same as before this embed. Subscription users are thus unaffected by default.
162
+ # Default-ON (when auth-safe); opt out entirely with LOKI_BARE_SUBCALLS=0.
163
+ # Predicate so call sites can append "--bare" to their argv array uniformly:
164
+ # loki_subcall_bare_enabled && argv+=("--bare")
165
+ loki_subcall_bare_enabled() {
166
+ [ "${LOKI_BARE_SUBCALLS:-1}" = "0" ] && return 1
167
+ # API-key auth required (see AUTH GATE above). ANTHROPIC_API_KEY is the
168
+ # common case; apiKeyHelper covers the settings-configured helper. Anything
169
+ # else (OAuth/keychain subscription) must NOT use --bare. Trim the key so a
170
+ # whitespace-only value does not count as set (it would fail --bare auth).
171
+ local _key
172
+ _key="$(printf '%s' "${ANTHROPIC_API_KEY:-}" | tr -d '[:space:]')"
173
+ if [ -z "$_key" ] && ! _loki_apikey_helper_configured; then
174
+ return 1
175
+ fi
176
+ loki_claude_flag_supported "--bare"
177
+ }
178
+
179
+ # True when an apiKeyHelper is configured in any Claude settings source, which
180
+ # (unlike OAuth/keychain) IS honored under --bare. Best-effort, read-only.
181
+ _loki_apikey_helper_configured() {
182
+ local f
183
+ for f in "$HOME/.claude/settings.json" "$HOME/.config/claude/settings.json" \
184
+ "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json" \
185
+ "$PWD/.claude/settings.json" "$PWD/.claude/settings.local.json"; do
186
+ [ -f "$f" ] || continue
187
+ # Require "apiKeyHelper" : "<non-empty value>" (a real key:value pair),
188
+ # not the bare token in a comment/prose. Tolerates whitespace around the
189
+ # colon. Not a full JSON parse, but rejects the obvious false-positives.
190
+ grep -Eq '"apiKeyHelper"[[:space:]]*:[[:space:]]*"[^"]+"' "$f" 2>/dev/null && return 0
191
+ done
192
+ return 1
193
+ }
194
+
195
+ # EMBED 3 -- --disallowedTools (reviewer / adversarial subcalls). Motivation: a
196
+ # parallel agent once ran `git reset --hard` and wiped uncommitted work, so a
197
+ # reviewer/voter subcall should not casually mutate the tree. Per `claude --help`
198
+ # the value is a comma-or-space-separated list of tool names (e.g. "Bash(git *)
199
+ # Edit"); Bash(...) matching is command-PREFIX based (the `cmd:*` form).
200
+ #
201
+ # SCOPE / LIMITS (honest -- this is a denylist, NOT a sandbox):
202
+ # - Denies the direct file-mutation tools (Edit, Write, NotebookEdit).
203
+ # - Denies the common git MUTATION forms: the bare subcommand (`git reset:*`)
204
+ # AND the global-flag-prefixed evasions (`git -C:*`, `git --git-dir:*`,
205
+ # `git -c:*`) that slip a flag before the subcommand so the bare prefix
206
+ # does not match. Read-only git (diff/log/show/status) stays allowed.
207
+ # - It does NOT and cannot block every mutation path: a determined agent can
208
+ # still write via `Bash(echo > f)`, `sed -i`, `cp/mv/tee`, `python -c`, etc.
209
+ # This is a guardrail that raises the cost of the casual/common destructive
210
+ # command, not a guarantee the tree is immutable. The real safety net is
211
+ # that the integrator commits before any agent wave (see CLAUDE.md).
212
+ # The flag is variadic (<tools...>), so we emit ONE comma-separated token to
213
+ # avoid swallowing the following -p prompt as additional tool names.
214
+ # Default-ON; opt out with LOKI_REVIEW_TOOL_GUARD=0.
215
+ # Predicate + denylist so call sites append uniformly:
216
+ # if loki_review_guard_enabled; then
217
+ # argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
218
+ # fi
219
+ loki_review_guard_enabled() {
220
+ [ "${LOKI_REVIEW_TOOL_GUARD:-1}" = "0" ] && return 1
221
+ loki_claude_flag_supported "--disallowedTools"
222
+ }
223
+ loki_review_guard_denylist() {
224
+ # Comma-separated single token. Bash(cmd:*) is command-prefix matching.
225
+ # The `git -C:*` / `git --git-dir:*` / `git -c:*` entries close the
226
+ # global-flag-before-subcommand evasion of the bare git-mutation rules.
227
+ printf '%s' "Edit,Write,NotebookEdit,Bash(git commit:*),Bash(git reset:*),Bash(git push:*),Bash(git checkout:*),Bash(git clean:*),Bash(git rm:*),Bash(git stash:*),Bash(git -C:*),Bash(git --git-dir:*),Bash(git -c:*)"
228
+ }
package/autonomy/run.sh CHANGED
@@ -3248,7 +3248,18 @@ Output ONLY the resolved file content with no conflict markers. No explanations.
3248
3248
 
3249
3249
  case "${PROVIDER_NAME:-claude}" in
3250
3250
  claude)
3251
- resolution=$(claude --dangerously-skip-permissions -p "$conflict_prompt" --output-format text 2>/dev/null)
3251
+ # EMBED 2 (v7.33.0): --bare on this cheap NON-MAIN subcall.
3252
+ # Reasoning: $conflict_prompt is fully self-contained -- it
3253
+ # carries the complete instruction set AND the entire conflicted
3254
+ # file content inline, and the agent's output is captured to a
3255
+ # variable (the shell, not the agent, writes the resolved file).
3256
+ # It needs no hooks, LSP, CLAUDE.md auto-discovery, or MCP, so
3257
+ # --bare is safe and cheaper. Gated + opt-out LOKI_BARE_SUBCALLS=0.
3258
+ local _cr_argv=("--dangerously-skip-permissions")
3259
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
3260
+ _cr_argv+=("--bare")
3261
+ fi
3262
+ resolution=$(claude "${_cr_argv[@]}" -p "$conflict_prompt" --output-format text 2>/dev/null)
3252
3263
  ;;
3253
3264
  codex)
3254
3265
  resolution=$(codex exec --full-auto --skip-git-repo-check "$conflict_prompt" 2>/dev/null)
@@ -4543,7 +4554,14 @@ _loki_hash_stdin() {
4543
4554
  # Compute a cheap, clone-stable signature of the codebase so we can tell whether
4544
4555
  # it changed since the generated PRD was last written. Git repos: HEAD sha +
4545
4556
  # dirty flag (.loki/.git churn filtered out). Non-git: a hash of sorted
4546
- # path+size pairs (size, not mtime, so it is clone-stable). Echoes the signature.
4557
+ # path+size pairs PLUS file content (v7.32.3, #569: path+size alone was blind to
4558
+ # a same-size content edit, so a stale PRD could be silently reused with a
4559
+ # false "codebase unchanged" disclosure). Content hashing is clone-stable
4560
+ # (mtime is not, which is why mtime was never used). Trees larger than
4561
+ # LOKI_PRD_SIG_CONTENT_BUDGET bytes (default 50MB) skip the content pass and
4562
+ # emit a "files-shallow:" signature so startup stays fast; the safe failure
4563
+ # mode there is unchanged-from-before (size-blind), never a false "changed".
4564
+ # Echoes the signature.
4547
4565
  compute_codebase_signature() {
4548
4566
  local dir="${1:-.}"
4549
4567
  ( cd "$dir" 2>/dev/null || exit 0
@@ -4558,7 +4576,7 @@ compute_codebase_signature() {
4558
4576
  fi
4559
4577
  echo "git:${head}:${dirty}"
4560
4578
  else
4561
- local listing count
4579
+ local listing count total_sz budget
4562
4580
  listing=$(find . \
4563
4581
  -type d \( -name .loki -o -name .git -o -name node_modules -o -name dist \
4564
4582
  -o -name build -o -name .next -o -name target -o -name vendor \
@@ -4569,8 +4587,26 @@ compute_codebase_signature() {
4569
4587
  sz=$(stat -f%z "$f" 2>/dev/null || stat -c%s "$f" 2>/dev/null || echo 0)
4570
4588
  printf '%s\t%s\n' "$f" "$sz"
4571
4589
  done | LC_ALL=C sort)
4572
- count=$(printf '%s\n' "$listing" | grep -c . || echo 0)
4573
- echo "files:$(printf '%s' "$listing" | _loki_hash_stdin):${count}"
4590
+ # grep -c prints 0 itself on no match (exit 1); '|| true' avoids the
4591
+ # old '|| echo 0' double-zero that embedded a newline on empty trees
4592
+ count=$(printf '%s\n' "$listing" | grep -c . || true)
4593
+ total_sz=$(printf '%s\n' "$listing" | awk -F'\t' '{s+=$2} END {printf "%d", s}')
4594
+ budget="${LOKI_PRD_SIG_CONTENT_BUDGET:-52428800}"
4595
+ if [ "${total_sz:-0}" -le "$budget" ] 2>/dev/null; then
4596
+ # Content pass: stream all file contents through one hash in the
4597
+ # same sorted order as the listing. Detects same-size edits.
4598
+ # xargs -0 batches the reads into a handful of cat invocations,
4599
+ # so cost scales with BYTES (which the budget above bounds), not
4600
+ # file count: a fork-per-file loop here measured ~38s of added
4601
+ # startup on a 30k-small-file tree. Renames and content swaps
4602
+ # are still caught by the listing hash (paths+sizes) below.
4603
+ local content_hash
4604
+ content_hash=$(printf '%s\n' "$listing" | cut -f1 | tr '\n' '\0' \
4605
+ | xargs -0 cat 2>/dev/null | _loki_hash_stdin)
4606
+ echo "files:$(printf '%s' "$listing" | _loki_hash_stdin):${count}:${content_hash}"
4607
+ else
4608
+ echo "files-shallow:$(printf '%s' "$listing" | _loki_hash_stdin):${count}"
4609
+ fi
4574
4610
  fi
4575
4611
  )
4576
4612
  }
@@ -4644,6 +4680,25 @@ except Exception:
4644
4680
  if [ "$stored" = "$current" ]; then
4645
4681
  echo "reuse"
4646
4682
  else
4683
+ # v7.32.3 format transition (#569): a stored pre-content-hash signature
4684
+ # ("files:<listing>:<count>", 3 fields) compared against the new 4-field
4685
+ # format would falsely claim "codebase changed" on the first post-upgrade
4686
+ # run. When the new signature extends the stored one (same listing
4687
+ # fields), the tree is unchanged at the old format's trust level: reuse,
4688
+ # honestly. The next persist upgrades the stored format. A same-size edit
4689
+ # made BEFORE the upgrade stays invisible for this one run, exactly as it
4690
+ # was on the old version (no regression, no false disclosure).
4691
+ case "$stored" in
4692
+ files:*:*)
4693
+ # Require the legitimate old 3-field format (files:HASH:COUNT,
4694
+ # exactly 2 colons), not a truncated/corrupted 2-field value
4695
+ # (council hardening: corruption must fall to update, as before).
4696
+ if [ "$(printf '%s' "$stored" | tr -dc ':' | wc -c | tr -d ' ')" = "2" ] \
4697
+ && [ "${current#"${stored}":}" != "$current" ]; then
4698
+ echo "reuse"; return 0
4699
+ fi
4700
+ ;;
4701
+ esac
4647
4702
  echo "update"
4648
4703
  fi
4649
4704
  }
@@ -4692,7 +4747,16 @@ try:
4692
4747
  except Exception:
4693
4748
  prev = {}
4694
4749
  prev_at = prev.get('generated_at') if isinstance(prev, dict) else None
4695
- if prev_at and prev.get('signature') == sig:
4750
+ prev_sig = prev.get('signature') if isinstance(prev, dict) else None
4751
+ # Unchanged, OR the v7.32.3 files-signature format upgrade (#569): the new
4752
+ # 4-field signature extends an old 3-field one whose listing fields match.
4753
+ # Preserve the date in both cases; the PRD content did not change.
4754
+ _legacy_upgrade = (
4755
+ isinstance(prev_sig, str) and prev_sig.startswith('files:')
4756
+ and prev_sig.count(':') == 2
4757
+ and sig.startswith(prev_sig + ':')
4758
+ )
4759
+ if prev_at and (prev_sig == sig or _legacy_upgrade):
4696
4760
  generated_at = prev_at
4697
4761
  else:
4698
4762
  generated_at = datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00','Z')
@@ -7750,7 +7814,31 @@ BUILD_PROMPT
7750
7814
  # Mythos 5 (Project Glasswing), not Fable. If a future change
7751
7815
  # adds --model here, the security-sentinel reviewer must be
7752
7816
  # pinned to opus, never fable.
7753
- claude --dangerously-skip-permissions -p "$prompt_text" \
7817
+ # EMBED 2 + 3 (v7.33.0). This is a 3-reviewer council
7818
+ # subcall. $prompt_text is fully self-contained (built above
7819
+ # into $review_prompt_file with the diff, changed files,
7820
+ # checks, and strict VERDICT/FINDINGS output format), output
7821
+ # is captured to $review_output, and it deliberately does NOT
7822
+ # pass --model or go through buildAutoFlags. So:
7823
+ # EMBED 2 (--bare): the prompt needs no hooks/LSP/CLAUDE.md/
7824
+ # MCP discovery, so --bare is safe and cheaper. Opt out
7825
+ # LOKI_BARE_SUBCALLS=0.
7826
+ # EMBED 3 (--disallowedTools): raise the cost of a reviewer
7827
+ # casually mutating the tree (a parallel agent once ran
7828
+ # `git reset --hard` and wiped uncommitted work). Deny
7829
+ # Edit/Write/NotebookEdit + git mutation forms (incl. the
7830
+ # git -C / --git-dir evasions); read-only git stays allowed.
7831
+ # Guardrail, not a sandbox -- echo>/sed -i/etc. remain; the
7832
+ # real net is commit-before-agent-wave. Opt out
7833
+ # LOKI_REVIEW_TOOL_GUARD=0. See loki_review_guard_denylist.
7834
+ local _rv_argv=("--dangerously-skip-permissions")
7835
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
7836
+ _rv_argv+=("--bare")
7837
+ fi
7838
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
7839
+ _rv_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
7840
+ fi
7841
+ claude "${_rv_argv[@]}" -p "$prompt_text" \
7754
7842
  --output-format text > "$review_output" 2>/dev/null
7755
7843
  ;;
7756
7844
  codex)
@@ -7963,7 +8051,25 @@ ADVERSARIAL_EOF
7963
8051
  case "${PROVIDER_NAME:-claude}" in
7964
8052
  claude)
7965
8053
  if command -v claude &>/dev/null; then
7966
- claude --dangerously-skip-permissions -p "$adversarial_prompt" \
8054
+ # EMBED 2 + 3 (v7.33.0). Adversarial probe subcall.
8055
+ # $adversarial_prompt is fully self-contained (instructions +
8056
+ # changed files + diff inlined via the heredoc above) and output
8057
+ # is captured to $result_file. So:
8058
+ # EMBED 2 (--bare): no hooks/LSP/CLAUDE.md/MCP needed; cheaper.
8059
+ # Opt out LOKI_BARE_SUBCALLS=0.
8060
+ # EMBED 3 (--disallowedTools): keep an adversarial agent from
8061
+ # casually mutating the tree. Deny Edit/Write/NotebookEdit +
8062
+ # git mutation forms (incl. git -C / --git-dir evasions);
8063
+ # read-only git stays allowed. Guardrail, not a sandbox.
8064
+ # Opt out LOKI_REVIEW_TOOL_GUARD=0.
8065
+ local _adv_argv=("--dangerously-skip-permissions")
8066
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
8067
+ _adv_argv+=("--bare")
8068
+ fi
8069
+ if type loki_review_guard_enabled >/dev/null 2>&1 && loki_review_guard_enabled; then
8070
+ _adv_argv+=("--disallowedTools" "$(loki_review_guard_denylist)")
8071
+ fi
8072
+ claude "${_adv_argv[@]}" -p "$adversarial_prompt" \
7967
8073
  --output-format text > "$result_file" 2>/dev/null || true
7968
8074
  fi
7969
8075
  ;;
@@ -9988,9 +10094,21 @@ ${_commits}"
9988
10094
 
9989
10095
  # Use haiku for cheap, fast generation. --dangerously-skip-permissions
9990
10096
  # because this is a one-shot non-interactive call.
10097
+ # EMBED 2 (v7.33.0): --bare on this cheap NON-MAIN haiku subcall. The
10098
+ # USAGE.md-regen prompt ($_ic_prompt, piped via -p -) is fully self-contained
10099
+ # (project tree + manifests + entrypoint contents + commits inlined) and the
10100
+ # output is captured, not written by the agent. No hooks/LSP/CLAUDE.md/MCP
10101
+ # needed, so --bare is safe and cheaper. Opt out LOKI_BARE_SUBCALLS=0.
10102
+ # Always at least --dangerously-skip-permissions, so the array is never
10103
+ # empty (empty "${arr[@]}" under set -u errors on bash 3.2, stock macOS).
10104
+ local _ic_argv=("--dangerously-skip-permissions")
10105
+ if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
10106
+ _ic_argv+=("--bare")
10107
+ fi
10108
+ _ic_argv+=("--model" "haiku")
9991
10109
  local _ic_out
9992
10110
  _ic_out=$(printf '%s' "$_ic_prompt" \
9993
- | timeout 60 claude --dangerously-skip-permissions --model haiku -p - 2>/dev/null \
10111
+ | timeout 60 claude "${_ic_argv[@]}" -p - 2>/dev/null \
9994
10112
  | head -200)
9995
10113
  # Sanity check: response must look like Markdown (starts with # or ##).
9996
10114
  if [ -z "$_ic_out" ] || ! printf '%s' "$_ic_out" | head -1 | grep -qE '^#'; then
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.32.2"
10
+ __version__ = "7.33.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.32.2
5
+ **Version:** v7.33.0
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.32.2";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});async function j($,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[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a$;var d=L(()=>{a$=class a$ 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 a($){return WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
2
+ var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.33.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});async function j($,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[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a$;var d=L(()=>{a$=class a$ 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 a($){return WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
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)
@@ -789,4 +789,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
789
789
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
790
790
  `),process.stderr.write(o6),2}}p1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var ZZ=await QZ(Bun.argv.slice(2));process.exit(ZZ);
791
791
 
792
- //# debugId=F7A4FD0C7A94555A64756E2164756E21
792
+ //# debugId=27D659E047A7DB5564756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.32.2'
60
+ __version__ = '7.33.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.32.2",
4
+ "version": "7.33.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 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
6
6
  "keywords": [
7
7
  "agent",
@@ -190,6 +190,20 @@ _loki_build_claude_auto_flags() {
190
190
  for _mcp_path in $_mcp_argv; do
191
191
  _LOKI_CLAUDE_AUTO_FLAGS+=("$_mcp_path")
192
192
  done
193
+ # EMBED 1 (v7.33.0): --strict-mcp-config. ONLY emitted alongside an
194
+ # actual --mcp-config bundle (never bare). Per `claude --help` it
195
+ # makes the agent load servers ONLY from --mcp-config, ignoring ALL
196
+ # other MCP sources (auto-discovered project .mcp.json AND any
197
+ # settings-injected configs). Note the bundle already includes the
198
+ # user's ~/.claude/mcp.json overlay explicitly, so the common
199
+ # user-MCP case is preserved; what is dropped is any MCP config not
200
+ # in the explicit bundle, making the run reproducible.
201
+ # Default-ON; opt out with LOKI_STRICT_MCP=0. Gated on CLI support so
202
+ # an older claude degrades gracefully.
203
+ if [ "${LOKI_STRICT_MCP:-1}" != "0" ] \
204
+ && loki_claude_flag_supported "--strict-mcp-config"; then
205
+ _LOKI_CLAUDE_AUTO_FLAGS+=("--strict-mcp-config")
206
+ fi
193
207
  fi
194
208
  fi
195
209