loki-mode 7.72.0 → 7.73.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.
@@ -0,0 +1,302 @@
1
+ # DEPLOY-PLAN.md -- `loki deploy` (ADVISORY / PRINT-ONLY)
2
+
3
+ **Feature:** `loki deploy` -- detect project type + the user's installed cloud CLI,
4
+ PRINT (and best-effort copy) the canonical deploy command(s), and **never run them**.
5
+
6
+ **Status:** Architecture plan. No implementation code here. Pattern family: the just-shipped
7
+ `loki preview --public` (`_preview_public` at `autonomy/loki:5306`, helpers from `_read_app_state`
8
+ at `:5216`, arg parsing in `cmd_preview` at `:5546`-`:5609`, dispatch arm `preview)` at `:14966`).
9
+
10
+ **Why this exists / why it is safe.** README states (line 447) *"Does not deploy -- human runs
11
+ deploy commands"* and (line 452) *"It does NOT access your cloud accounts ... Human oversight is
12
+ expected for deployment."* This feature keeps that literally true: it is an **advisory printer**.
13
+ It runs `command -v <cli>` ONLY (never the CLI itself, not even `--version`), prints the exact
14
+ command, and the **human runs it**. Fully reversible; touches no cloud account.
15
+
16
+ ---
17
+
18
+ ## Product Owner scope locks (RECOMMENDED -- integrator to confirm)
19
+
20
+ These are the decisions the Product Owner / integrator should lock before build. Each has a
21
+ clear recommended default.
22
+
23
+ ### LOCK 1 -- Command surface: NEW top-level command `loki deploy` (not a flag)
24
+ - Recommendation: **new top-level `deploy)` dispatch arm + `cmd_deploy()`**, NOT `loki preview --deploy`.
25
+ - Justification: `preview` is "show me the local app I already built/started" and is gated on a
26
+ **running app** (`state.json` status=running, live port). `deploy` is conceptually distinct:
27
+ it is a **static, filesystem-only advisory about project type** that must work with nothing
28
+ running and no build started. Folding it into `preview` would force the running-app
29
+ preconditions onto a feature that must not have them (see LOCK 6). Per the CLI-consolidation
30
+ mandate (`autonomy/loki:675`, lean ~17-entry front page), `deploy` does **not** go on the
31
+ front-page command list; it lives in the "More commands" footer (`:728`) and is fully
32
+ documented via `loki deploy --help`. It earns a command (verb users already expect) but not
33
+ front-page real estate.
34
+
35
+ ### LOCK 2 -- Disambiguation: print ALL viable options, most-idiomatic first
36
+ - Recommendation: when several `(project-type x installed-CLI)` pairs are viable, **print every
37
+ viable option, clearly labeled, idiomatic one first.** Never silently pick one.
38
+ - Precedence (the "idiomatic first" order), recommended default:
39
+ | Detected project type | Idiomatic 1st | Then (if that CLI also installed) |
40
+ |---|---|---|
41
+ | Next.js | Vercel | Netlify, Fly (if Dockerfile present) |
42
+ | Static / SPA (dist/ or build/ + index.html; Vite/CRA) | Netlify | Cloudflare Pages, Vercel |
43
+ | Dockerfile / containerized server | Fly | Cloudflare (wrangler), (Render: doc link only) |
44
+ | Generic Node server (package.json, no static build) | Fly | Vercel |
45
+ | Python (requirements.txt / pyproject.toml) | Fly | (doc links: Render/Railway) |
46
+ - Within each row, only print options whose **CLI is actually installed** (`command -v`).
47
+ - "Idiomatic first" = ordering only; it never suppresses a viable installed alternative.
48
+
49
+ ### LOCK 3 -- Per-provider canonical commands (DOCUMENTED, not invented)
50
+ Confidence column: "task" = exactly the form specified in the approved task brief (authoritative
51
+ for this build); placeholders are `<...>` the user fills. No flag is invented.
52
+
53
+ | Provider | CLI binary (`command -v`) | Command printed | Notes / placeholders | Confidence |
54
+ |---|---|---|---|---|
55
+ | Vercel | `vercel` | `vercel --prod` | none | task / high |
56
+ | Netlify | `netlify` | `netlify deploy --prod` | for **static**, append `--dir=<build-output>` (e.g. `dist` or `build`); print the placeholder, do not guess | task / high |
57
+ | Fly.io | `flyctl` | `fly deploy` | **Detect `flyctl`, print `fly deploy`** -- the binary is `flyctl` but the canonical subcommand verb is `fly deploy`; this is intentional, not a typo. If no `fly.toml`, append a one-line note: "run `fly launch` once first to create fly.toml" (note text only, never executed) | task / high |
58
+ | Cloudflare Pages (static) | `wrangler` | `wrangler pages deploy <build-output>` | placeholder dir | task / high |
59
+ | Cloudflare Workers/containers | `wrangler` | `wrangler deploy` | none | task / high |
60
+
61
+ - Where a flag depends on the project (build dir), print a `<placeholder>` and reference official
62
+ docs rather than inventing. Each printed block ends with the provider's official docs URL.
63
+ - **A wrong flag is worse than no feature** -- conservatism over completeness.
64
+
65
+ ### LOCK 4 -- Clipboard: best-effort, cross-platform, never fatal, copy the idiomatic one
66
+ - Recommendation: **always copy the idiomatic (first) command**, and print a note:
67
+ `copied to clipboard: <cmd> (other options shown above)`. Cleaner than "copy only when exactly one".
68
+ - TTY gate: skip clipboard entirely when `[ ! -t 1 ]` (meaningless over SSH / pipes / non-TTY).
69
+ - Tools, in order, guarded by `command -v`: `pbcopy` (macOS); `wl-copy`, `xclip -selection clipboard`,
70
+ `xsel --clipboard --input` (Linux); `clip.exe`/`clip` (Windows/WSL).
71
+ - **Never fatal:** `printf '%s' "$cmd" | <tool> 2>/dev/null || true`. If no tool exists, print is the
72
+ primary output and the command still exits 0. Clipboard failure never changes the exit code.
73
+ - These clipboard tools ARE allowed to run. Only the FOUR cloud CLIs are forbidden (LOCK 5).
74
+
75
+ ### LOCK 5 -- NON-EXECUTION is a tested invariant
76
+ - The command must **NEVER** invoke vercel / netlify / flyctl / wrangler -- not even `--version`.
77
+ - Detection is **`command -v <cli>` ONLY**. No `$cli ...` call anywhere in the code path.
78
+ - Proven by SDET test (see Section 8, headline test): PATH stubs for all four that write a sentinel
79
+ IF invoked; after `loki deploy` runs, assert all four sentinels ABSENT **and** the expected
80
+ command string was printed (both halves -- sentinel-absent alone passes vacuously).
81
+
82
+ ### LOCK 6 -- Filesystem-only; NOT gated on a running app
83
+ - `loki deploy` is a static advisory. It must work with **nothing running, no build started.**
84
+ - It does **NOT** read `app-runner/state.json` and has **no** status=running / live-port
85
+ precondition (this is the key divergence from `_preview_public`). Detection is from project
86
+ files in the target dir, using the same `${TARGET_DIR:-.}` resolution app-runner uses.
87
+ - Only two real preconditions: (a) a project type is detected, else honest non-zero exit;
88
+ (b) at least one matching CLI is installed, else honest install hint + non-zero exit.
89
+
90
+ ### LOCK 7 -- Bun parity: bash-only is acceptable
91
+ - bash-only, no Bun runner change. Precedent: HUD (`FEAT-HUD`) and preview (`FEAT-PREVIEW-LINK`)
92
+ are bash-only; the Bun runner is dormant for the live/advisory path. State explicitly in CHANGELOG.
93
+
94
+ ---
95
+
96
+ ## 1. Project-type detection (filesystem signals)
97
+
98
+ Reuse the signals already proven in `autonomy/app-runner.sh`. All checks are read-only file/dir
99
+ existence + `grep` on `package.json`. Resolve the directory as `local dir="${TARGET_DIR:-.}"`
100
+ (same idiom as `app_runner_init` at `app-runner.sh:739` and `_detect_nextjs_standalone` at `:594`).
101
+
102
+ Detection cascade (first match wins for the *primary* type label; multiple provider options can
103
+ still be offered per LOCK 2):
104
+
105
+ | # | Type | Signal (exact checks) | Source in repo |
106
+ |---|---|---|---|
107
+ | 1 | Next.js | `grep -q '"next"' "$dir/package.json"` OR `[ -f "$dir/next.config.js" ]` OR `next.config.mjs`/`next.config.ts` present | mirrors next detection near `app-runner.sh:800-813` |
108
+ | 2 | Static / SPA | (`[ -d "$dir/dist" ]` OR `[ -d "$dir/build" ]`) AND a built `index.html` in that dir; OR `grep -q '"vite"' package.json` / CRA (`react-scripts`) | vite signal at `app-runner.sh:697` |
109
+ | 3 | Dockerfile / container | `[ -f "$dir/Dockerfile" ]` (and/or `docker-compose.yml`/`compose.yml`) | `app-runner.sh:759`, `:774` |
110
+ | 4 | Generic Node server | `[ -f "$dir/package.json" ]` with a `"start"`/`"dev"` script and no static build dir | `app-runner.sh:798`, `:814`, `:821` |
111
+ | 5 | Python | `[ -f "$dir/requirements.txt" ]` OR `[ -f "$dir/pyproject.toml" ]` | `app-runner.sh:953` |
112
+
113
+ - Do **not** call `_detect_nextjs_standalone`'s artifact-only path as the sole Next signal; for an
114
+ advisory we want the *source* signal (`"next"` in package.json / `next.config.*`) since the build
115
+ may not have run yet. (The artifact path keys on `.next/standalone/server.js` -- too narrow here.)
116
+ - Type -> candidate providers per the LOCK 2 precedence table.
117
+ - If **no** type matches: honest "no deployable project detected here" message naming the signals
118
+ it looked for (package.json / Dockerfile / dist|build / requirements.txt|pyproject.toml), non-zero exit.
119
+
120
+ ---
121
+
122
+ ## 2. Command surface + dispatch wiring
123
+
124
+ ### 2.1 Dispatch arm (new, contiguous block placement)
125
+ Add a `deploy)` arm to the dispatch `case` next to `preview)` (`autonomy/loki:14966`):
126
+ ```
127
+ deploy)
128
+ cmd_deploy "$@"
129
+ ;;
130
+ ```
131
+ No deprecated alias (brand-new verb; nothing to alias).
132
+
133
+ ### 2.2 Function placement -- CONTIGUOUS, for test extractability
134
+ Place ALL deploy code as **one contiguous block** so the SDET test can extract it by name anchor
135
+ the same way `test-preview-public.sh` extracts the preview block (awk from the first helper def to
136
+ the closing `}` of `cmd_deploy`). Recommended location: immediately AFTER `cmd_preview()` ends
137
+ (after `autonomy/loki:5649`-area close), so the two advisory features sit together. The block:
138
+ - `_deploy_detect_type()` -- echoes the primary type label (Section 1); always returns 0.
139
+ - `_deploy_options_for_type()`-- given type, echoes ordered `provider|cli|command` rows (pure, testable).
140
+ - `_deploy_copy_clipboard()` -- best-effort copy (LOCK 4); always returns 0; TTY/`command -v` guarded.
141
+ - `cmd_deploy()` -- arg parse + orchestration + printing.
142
+
143
+ Keep helpers pure where possible (take dir / type as args, echo to stdout, `return 0`) so unit
144
+ tests can call them directly without a process -- mirrors the pure extractors `_extract_tunnel_url_*`.
145
+
146
+ ### 2.3 `cmd_deploy()` flow
147
+ 1. Parse args in a `while [ $# -gt 0 ]` loop (mirror `cmd_preview` `:5555`):
148
+ - `--help|-h|help` -> print help block (Section 2.4), `return 0`.
149
+ - `--dir <path>` -> override the project dir to scan (default `${TARGET_DIR:-.}` then `.`).
150
+ - `--no-clip` -> disable clipboard copy.
151
+ - `--json` -> machine-readable output (optional; see 2.5). Lenient on unknown args (no hard
152
+ error -> no behavior drift), matching `cmd_preview`. Guard value-consuming shifts exactly as
153
+ `--provider` does at `:5595`-`:5599` (avoid set -e underflow when a flag is the last arg).
154
+ 2. `type=$(_deploy_detect_type "$dir")`.
155
+ 3. If empty -> no-project path: honest message + `return 1`.
156
+ 4. `options=$(_deploy_options_for_type "$type")`; filter to rows whose CLI is installed
157
+ (`command -v "$cli" >/dev/null 2>&1`).
158
+ 5. If zero installed CLIs -> no-CLI path: honest install hints for each candidate provider
159
+ (brew + official URL), `return 1` (Section 3).
160
+ 6. Else: print a header (detected type), then each installed option block (label, command,
161
+ docs URL), idiomatic first. Best-effort copy the first command (LOCK 4). `return 0`.
162
+ 7. **Never** call any cloud CLI. Print only.
163
+
164
+ ### 2.4 `--help`
165
+ Mirror the `cmd_preview` help block (`:5557`-`:5585`). Must state: advisory/print-only; it does NOT
166
+ deploy and does NOT run any cloud CLI; it detects project type + your installed CLI and prints the
167
+ command for YOU to run; clipboard is best-effort. List options `--dir`, `--no-clip`, `--json`, `--help`.
168
+
169
+ ### 2.5 `--json` (optional, recommended)
170
+ Emit `{"type": "...", "options": [{"provider","cli","command","docs"}], "copied": "<cmd|>"}`.
171
+ Honest: empty `options` when no CLI installed. Suppress clipboard note under `--json`.
172
+
173
+ ---
174
+
175
+ ## 3. No-CLI-installed path (honest install hints)
176
+
177
+ Mirror the gh-missing / tunnel-missing block at `autonomy/loki:5413`-`:5433`. For each candidate
178
+ provider for the detected type, print to **stderr**, then `return 1`:
179
+ ```
180
+ No deploy CLI found for this <type> project.
181
+ Loki never accesses your cloud account or runs deploy for you -- you run the printed command.
182
+ Install one of the following, then re-run 'loki deploy':
183
+
184
+ Vercel: brew install vercel | https://vercel.com/docs/cli
185
+ Netlify: brew install netlify-cli | https://docs.netlify.com/cli/get-started/
186
+ Fly.io: brew install flyctl | https://fly.io/docs/flyctl/install/
187
+ Cloudflare: npm i -g wrangler | https://developers.cloudflare.com/workers/wrangler/install-and-update/
188
+ ```
189
+ - NEVER fabricate success; NEVER download a binary. Only print candidates relevant to the type.
190
+ - Non-zero exit so scripts/CI see the failure.
191
+
192
+ ## 3b. No-detected-project path
193
+ Honest message to stderr naming the signals checked (Section 1), `return 1`.
194
+
195
+ ---
196
+
197
+ ## 4. set -e / shellcheck safety (file runs under `set -euo pipefail`)
198
+
199
+ - Increment counters as `i=$((i + 1))` (never `((i++))`, which returns 1 at zero and aborts).
200
+ - All `grep`/`command -v` that may "fail" guarded: `grep -q ... || true` or `if command -v ...`.
201
+ - Quote every path: `"$dir"`, `"$dir/package.json"`. Pass paths as argv to any python helper
202
+ (never interpolate into a heredoc) -- mirror `_read_app_state` (`:5219`-`:5227`).
203
+ - If a python heredoc is used (e.g. for `--json`), escape `$` that bash must not expand
204
+ (`\$`); follow `tests/check-heredoc-dollar-digit.sh` (the repo gate for `$1`/`$2` in heredocs).
205
+ - Clipboard pipeline always `|| true`; never let a missing tool or non-TTY abort.
206
+ - Value-consuming flag shifts guarded (`[ $# -ge 2 ] && shift`) -- see `:5595`.
207
+ - Capture sub-function exit into a local then `return $rc` (don't bare-return a non-zero call line
208
+ under set -e) -- mirror `:5614`-`:5617`.
209
+ - Must pass `tests/run-shellcheck.sh` clean.
210
+
211
+ ---
212
+
213
+ ## 5. SDET test plan -- `tests/test-deploy.sh` (NEW)
214
+
215
+ Structure mirrors `tests/test-preview-public.sh`: extract the contiguous deploy block by name anchor
216
+ into a temp lib, source it, drive `cmd_deploy` and the pure helpers. `pass/fail` counters, `mktemp -d`
217
+ WORKROOT, `trap cleanup EXIT INT TERM`. Color globals exported empty. Every un-runnable case emits a
218
+ visible FAIL/SKIP -- never a silent pass. Fixtures live under temp dirs (fake package.json,
219
+ Dockerfile, dist/index.html, requirements.txt).
220
+
221
+ Extraction sanity (non-vacuity gate, like preview lines 113-126): assert all 4 deploy function
222
+ defs are present in the extracted lib, else fail loudly and abort.
223
+
224
+ ### Test 1 (HEADLINE) -- NON-EXECUTION invariant
225
+ - Build a `fake-bin` with stubs `vercel`, `netlify`, `flyctl`, `wrangler`, each:
226
+ `#!/usr/bin/env bash` -> `echo ran > "$SENTINEL_<name>"` -> `exit 0`.
227
+ - Prepend `fake-bin` to PATH so `command -v` RESOLVES all four (CLIs DETECTED + printed).
228
+ - Fixture: a Next.js project dir (package.json with `"next"`).
229
+ - Run `cmd_deploy --dir <fixture>`; capture stdout.
230
+ - Assert BOTH halves:
231
+ 1. **NONE** of the four sentinels exists (no cloud CLI was invoked) -- the core invariant.
232
+ 2. The expected command string (`vercel --prod`) **was printed** (proves non-vacuity -- a
233
+ deploy that prints nothing would pass half 1 falsely).
234
+ - This is the highest-stakes test (echoes the real tunnel-CLI-launched-during-dev incident).
235
+
236
+ ### Test 2 -- Project-type detection (pure helper, per type)
237
+ For each fixture (Next.js / static dist+index.html / Dockerfile / generic Node / Python), assert
238
+ `_deploy_detect_type "$dir"` returns the expected label. Include a "none" fixture (empty dir) ->
239
+ empty label.
240
+
241
+ ### Test 3 -- Option ordering / idiomatic-first (pure helper)
242
+ With all four stubs on PATH, assert `_deploy_options_for_type next` lists Vercel before Netlify
243
+ before Fly; static lists Netlify/Cloudflare before Vercel; Dockerfile lists Fly first. Assert the
244
+ printed command strings are EXACTLY the LOCK 3 canonical forms (mutation-proof: a wrong flag fails).
245
+
246
+ ### Test 4 -- No-CLI-installed path
247
+ Curated clean PATH with NONE of the four CLIs (build_clean_bin idiom, preview lines 191-201;
248
+ symlink only coreutils). Next.js fixture. Assert: honest install hint printed (contains
249
+ "Install one of the following" and at least the Vercel brew + URL) AND non-zero exit AND no sentinel.
250
+
251
+ ### Test 5 -- No-detected-project path
252
+ Empty dir fixture. Assert honest "no deployable project" message naming signals + non-zero exit.
253
+
254
+ ### Test 6 -- Clipboard best-effort, never fatal
255
+ - 6a: PATH with NO clipboard tool (clean bin) + Next.js fixture + on a non-TTY (`</dev/null` /
256
+ piped stdout). Assert exit 0 and the command still PRINTED (clipboard absence is non-fatal).
257
+ - 6b (if a clipboard tool exists on host or via a fake `pbcopy` stub that writes a file): assert the
258
+ idiomatic command was copied AND the "copied to clipboard" note printed. Fake pbcopy is allowed to
259
+ run (it is not a cloud CLI). SKIP-safe if no TTY can be simulated.
260
+
261
+ ### Test 7 -- `--json` honesty (if implemented)
262
+ Assert valid JSON; `options` empty when no CLI installed; non-empty with correct commands when stubs
263
+ present. Suppresses the clipboard note.
264
+
265
+ ### Test 8 -- set -e safety / no-abort
266
+ Run a representative `cmd_deploy` invocation inside a `set -e -o pipefail` subshell and assert it
267
+ reaches a sentinel "ALIVE" echo after the call (proves no spurious abort), mirroring preview 1b
268
+ (lines 304-314).
269
+
270
+ ### Wiring into `tests/run-all-tests.sh`
271
+ Add next to the preview registration (`tests/run-all-tests.sh:204`):
272
+ ```
273
+ run_test "Deploy Advisory (print-only, non-execution invariant)" "$SCRIPT_DIR/test-deploy.sh"
274
+ ```
275
+ Also covered transitively by `tests/run-shellcheck.sh` and the mutation/mock detectors (gates #8/#9).
276
+
277
+ ---
278
+
279
+ ## 6. Docs to update on release
280
+
281
+ - `README.md:447` -- change the Deploy "What Works" cell to note advisory-print: e.g.
282
+ "Generates configs/Dockerfiles/CI-CD; `loki deploy` prints the exact deploy command (advisory)".
283
+ Keep "What Doesn't (Yet)": "Does not run deploy -- human runs the printed command." Line 452's
284
+ "Human oversight is expected for deployment" stays TRUE and unchanged.
285
+ - `CHANGELOG.md` -- new feature entry (FEAT-DEPLOY): advisory print-only `loki deploy`; non-execution
286
+ invariant; filesystem-only detection; best-effort clipboard; bash-only (Bun parity note).
287
+ - `docs/INSTALLATION.md` -- optional "Deploying your build" note pointing at `loki deploy`.
288
+ - `wiki/CLI-Reference.md` -- add the `deploy` command entry + options.
289
+ - `skills/production.md` (and `skills/00-index.md` if it indexes commands) -- document the advisory
290
+ deploy step in the production workflow.
291
+ - Create this plan's sibling precedent set is `docs/PREVIEW-LINK-PLAN.md` / `docs/BUILD-HUD-PLAN.md`
292
+ -- this file (`docs/DEPLOY-PLAN.md`) is the matching plan artifact.
293
+
294
+ ---
295
+
296
+ ## 7. Critical files for implementation
297
+
298
+ - /Users/lokesh/git/loki-mode/autonomy/loki (cmd_deploy + helpers as a contiguous block after cmd_preview ~:5649; dispatch arm next to preview ~:14966; "More commands" footer ~:728)
299
+ - /Users/lokesh/git/loki-mode/autonomy/app-runner.sh (detection signal source: next ~:800-813, vite ~:697, Dockerfile ~:759/:774, python ~:953, ${TARGET_DIR:-.} idiom ~:739)
300
+ - /Users/lokesh/git/loki-mode/tests/test-deploy.sh (NEW -- mirror tests/test-preview-public.sh)
301
+ - /Users/lokesh/git/loki-mode/tests/run-all-tests.sh (register the new test near :204)
302
+ - /Users/lokesh/git/loki-mode/README.md (line 447 Deploy row -> advisory-print)
@@ -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.72.0
5
+ **Version:** v7.73.0
6
6
 
7
7
  ---
8
8
 
@@ -395,7 +395,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
395
395
  # Run Loki Mode in Docker (Claude provider, API-key auth)
396
396
  docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
397
397
  -v $(pwd):/workspace -w /workspace \
398
- asklokesh/loki-mode:7.72.0 start ./my-spec.md
398
+ asklokesh/loki-mode:7.73.0 start ./my-spec.md
399
399
  ```
400
400
 
401
401
  ##### docker compose + .env (no host install)
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var t6=Object.defineProperty;var i6=($)=>$;function e6($,Q){this[$]=i6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)t6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:e6.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var D1={};h(D1,{lokiDir:()=>j,homeLokiDir:()=>r$,findRepoRootForVersion:()=>s$,REPO_ROOT:()=>g});import{resolve as a,dirname as a$}from"path";import{fileURLToPath as $Q}from"url";import{existsSync as F$}from"fs";import{homedir as QQ}from"os";function ZQ(){let $=S1;for(let Q=0;Q<6;Q++){if(F$(a($,"VERSION"))&&F$(a($,"autonomy/run.sh")))return $;let Z=a$($);if(Z===$)break;$=Z}return a(S1,"..","..","..")}function s$($){let Q=$;for(let Z=0;Z<6;Z++){if(F$(a(Q,"VERSION"))&&F$(a(Q,"autonomy/run.sh")))return Q;let z=a$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function j(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function r$(){return a(QQ(),".loki")}var S1,g;var b=P(()=>{S1=a$($Q(import.meta.url));g=ZQ()});import{readFileSync as zQ}from"fs";import{resolve as XQ,dirname as KQ}from"path";import{fileURLToPath as qQ}from"url";function R$(){if(Q$!==null)return Q$;let $="7.72.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=KQ(qQ(import.meta.url)),Z=s$(Q);Q$=zQ(XQ(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var t$=P(()=>{b()});var b1={};h(b1,{runOrThrow:()=>VQ,run:()=>k,commandVersion:()=>WQ,commandExists:()=>f,ShellError:()=>i$});async function k($,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([new Response(Z.stdout).text(),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 VQ($,Q={}){let Z=await k($,Q);if(Z.exitCode!==0)throw new i$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=JQ($),Z=await k(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function JQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function WQ($,Q="--version"){if(!await f($))return null;let z=await k([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var i$;var d=P(()=>{i$=class i$ 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 s($){return UQ?"":$}var UQ,T,S,_,_Z,I,x,y,V;var c=P(()=>{UQ=(process.env.NO_COLOR??"").length>0;T=s("\x1B[0;31m"),S=s("\x1B[0;32m"),_=s("\x1B[1;33m"),_Z=s("\x1B[0;34m"),I=s("\x1B[0;36m"),x=s("\x1B[1m"),y=s("\x1B[2m"),V=s("\x1B[0m")});import{existsSync as _Q}from"fs";async function Z$(){if(Y$!==void 0)return Y$;let $="/opt/homebrew/bin/python3.12";if(_Q($))return Y$=$,$;let Q=await f("python3.12");if(Q)return Y$=Q,Q;let Z=await f("python3");return Y$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return k([Z,"-c",$],Q)}var Y$;var V$=P(()=>{d()});var e1={};h(e1,{runStatus:()=>cQ});import{existsSync as v,readFileSync as W$,readdirSync as d1,statSync as o1}from"fs";import{resolve as D,basename as CQ}from"path";import{homedir as bQ}from"os";function n1($){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 a1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*x$/Q);if(X>x$)X=x$;let q=x$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=n1($),U=n1(Q);return` ${x}${Z}${V} ${K}[${W}]${V} ${z}% (${J} / ${U})`}async function yQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${V}
2
+ var t6=Object.defineProperty;var i6=($)=>$;function e6($,Q){this[$]=i6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)t6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:e6.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var D1={};h(D1,{lokiDir:()=>j,homeLokiDir:()=>r$,findRepoRootForVersion:()=>s$,REPO_ROOT:()=>g});import{resolve as a,dirname as a$}from"path";import{fileURLToPath as $Q}from"url";import{existsSync as F$}from"fs";import{homedir as QQ}from"os";function ZQ(){let $=S1;for(let Q=0;Q<6;Q++){if(F$(a($,"VERSION"))&&F$(a($,"autonomy/run.sh")))return $;let Z=a$($);if(Z===$)break;$=Z}return a(S1,"..","..","..")}function s$($){let Q=$;for(let Z=0;Z<6;Z++){if(F$(a(Q,"VERSION"))&&F$(a(Q,"autonomy/run.sh")))return Q;let z=a$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function j(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function r$(){return a(QQ(),".loki")}var S1,g;var b=P(()=>{S1=a$($Q(import.meta.url));g=ZQ()});import{readFileSync as zQ}from"fs";import{resolve as XQ,dirname as KQ}from"path";import{fileURLToPath as qQ}from"url";function R$(){if(Q$!==null)return Q$;let $="7.73.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=KQ(qQ(import.meta.url)),Z=s$(Q);Q$=zQ(XQ(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var t$=P(()=>{b()});var b1={};h(b1,{runOrThrow:()=>VQ,run:()=>k,commandVersion:()=>WQ,commandExists:()=>f,ShellError:()=>i$});async function k($,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([new Response(Z.stdout).text(),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 VQ($,Q={}){let Z=await k($,Q);if(Z.exitCode!==0)throw new i$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=JQ($),Z=await k(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function JQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function WQ($,Q="--version"){if(!await f($))return null;let z=await k([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var i$;var d=P(()=>{i$=class i$ 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 s($){return UQ?"":$}var UQ,T,S,_,_Z,I,x,y,V;var c=P(()=>{UQ=(process.env.NO_COLOR??"").length>0;T=s("\x1B[0;31m"),S=s("\x1B[0;32m"),_=s("\x1B[1;33m"),_Z=s("\x1B[0;34m"),I=s("\x1B[0;36m"),x=s("\x1B[1m"),y=s("\x1B[2m"),V=s("\x1B[0m")});import{existsSync as _Q}from"fs";async function Z$(){if(Y$!==void 0)return Y$;let $="/opt/homebrew/bin/python3.12";if(_Q($))return Y$=$,$;let Q=await f("python3.12");if(Q)return Y$=Q,Q;let Z=await f("python3");return Y$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return k([Z,"-c",$],Q)}var Y$;var V$=P(()=>{d()});var e1={};h(e1,{runStatus:()=>cQ});import{existsSync as v,readFileSync as W$,readdirSync as d1,statSync as o1}from"fs";import{resolve as D,basename as CQ}from"path";import{homedir as bQ}from"os";function n1($){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 a1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*x$/Q);if(X>x$)X=x$;let q=x$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=n1($),U=n1(Q);return` ${x}${Z}${V} ${K}[${W}]${V} ${z}% (${J} / ${U})`}async function yQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${V}
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)
@@ -793,4 +793,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
793
793
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
794
794
  `),process.stderr.write(r6),2}}l1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var qZ=await KZ(Bun.argv.slice(2));process.exit(qZ);
795
795
 
796
- //# debugId=7A89607799C5087964756E2164756E21
796
+ //# debugId=8A68E6EB2762797464756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.72.0'
60
+ __version__ = '7.73.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.72.0",
4
+ "version": "7.73.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.72.0",
5
+ "version": "7.73.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",