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.
- package/README.md +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/git-pr-advisory.sh +112 -0
- package/autonomy/loki +404 -1
- package/autonomy/run.sh +416 -28
- package/autonomy/verify.sh +7 -1
- package/dashboard/__init__.py +1 -1
- package/docs/BRANCH-LIFECYCLE-PLAN.md +354 -0
- package/docs/DEPLOY-PLAN.md +302 -0
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
|
@@ -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)
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.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.
|
|
398
|
+
asklokesh/loki-mode:7.73.0 start ./my-spec.md
|
|
399
399
|
```
|
|
400
400
|
|
|
401
401
|
##### docker compose + .env (no host install)
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -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.
|
|
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=
|
|
796
|
+
//# debugId=8A68E6EB2762797464756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.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.
|
|
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",
|