loki-mode 11.17.34 → 11.17.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/proof-generator.py +53 -0
- package/autonomy/loki +171 -14
- package/completions/_loki +1 -0
- package/completions/loki.bash +1 -1
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/BOARD.md +10 -10
- package/docs/v10/DECISIONS.md +8 -0
- package/docs/v10/PROGRESS.md +6 -0
- package/loki-ts/dist/loki.js +974 -974
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +461 -419
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-o3gtaCnU.js → AdminPage-B-I1NRtL.js} +1 -1
- package/web-app/dist/assets/{Avatar-C02t-Irl.js → Avatar-DT_8Mdba.js} +1 -1
- package/web-app/dist/assets/{Badge-BuN80MKq.js → Badge-C09l4W-0.js} +1 -1
- package/web-app/dist/assets/{Button-lPXsKVcs.js → Button-aC5gMeUz.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-8CNxkZSh.js → CockpitPage-CpH1eekK.js} +1 -1
- package/web-app/dist/assets/{ComparePage-j-XhFiwH.js → ComparePage-hM6QwfSW.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-D8nyc_y_.js → ErrorBoundary-6c3dc7IQ.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-DtTSMxZm.js → EvidenceReceiptPanel-DKE4l5Tk.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-Bzmm3gBY.js → GitHubIssuesPanel-abdxEU9p.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-Dh6vSHLE.js → GitHubPRsPanel-pHo-s2C4.js} +1 -1
- package/web-app/dist/assets/{HomePage-CKKPOMOg.js → HomePage-_-344_LT.js} +2 -2
- package/web-app/dist/assets/{LoginPage-49FrYkL9.js → LoginPage-frrXakkY.js} +1 -1
- package/web-app/dist/assets/{MagicPage-D5psAu69.js → MagicPage-BhafCYFh.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-oQXV5q4J.js → MetricsPage-C5zf292O.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-C2hj6k1f.js → NotFoundPage-D6M43Y9l.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-Bo8ocjwn.js → ProjectPage-D4kvygK3.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-QUhbz991.js → ProjectsPage-Cu7lmyKY.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-6XW8I3LO.js → SettingsPage-8itZwlRh.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-9CHGu9uW.js → ShowcasePage-CYlATNwJ.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-gZZK7T45.js → SystemSettingsPage-DBDfGnoQ.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-DSlKvZhA.js → TeamsPage-unFdeJaG.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-BVDR5mF2.js → TemplatesPage-c6QupvoO.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-DnOFdmTG.js → TerminalOutput-DzuhSLY5.js} +1 -1
- package/web-app/dist/assets/{activity-D22UFJWA.js → activity-C3i3sjXj.js} +1 -1
- package/web-app/dist/assets/{bell-B2Mfgp26.js → bell-BXgcpNiL.js} +1 -1
- package/web-app/dist/assets/{bot-CTXo6aac.js → bot-gYi5RFNf.js} +1 -1
- package/web-app/dist/assets/{check-D9kJod1H.js → check--JEYw3iQ.js} +1 -1
- package/web-app/dist/assets/{chevron-left-XI4pnrlY.js → chevron-left-CJAjz61W.js} +1 -1
- package/web-app/dist/assets/{circle-alert-CvS3mi0t.js → circle-alert-1GfPVQQS.js} +1 -1
- package/web-app/dist/assets/{clock-CCgwQ80p.js → clock-DjpWVOcn.js} +1 -1
- package/web-app/dist/assets/{cloud-S-SuEsEp.js → cloud-Cgitkgr5.js} +1 -1
- package/web-app/dist/assets/{code-xml-PRnhUKWP.js → code-xml-CxZaU6C9.js} +1 -1
- package/web-app/dist/assets/{database-C_a7H_3N.js → database-DOrPow1f.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-FDCzdLrB.js → dollar-sign-DqAZbHwb.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-28-hDPSO.js → file-code-corner-whhcIwS0.js} +1 -1
- package/web-app/dist/assets/{file-plus-c0yygSC8.js → file-plus-Dp5DUu4j.js} +1 -1
- package/web-app/dist/assets/{globe-DOA9Y_1n.js → globe-Dq6fTDw8.js} +1 -1
- package/web-app/dist/assets/{hammer-Omzy4vB3.js → hammer--bO0oBWq.js} +1 -1
- package/web-app/dist/assets/{index-Dp6emuTo.js → index-M9OMp27I.js} +3 -3
- package/web-app/dist/assets/{layers-D7ZjWXJQ.js → layers-CDURqqkV.js} +1 -1
- package/web-app/dist/assets/{loader-circle-CJ2SKm0h.js → loader-circle-C1bCbfqC.js} +1 -1
- package/web-app/dist/assets/{lock-HKmv1nIN.js → lock-DVMr4ren.js} +1 -1
- package/web-app/dist/assets/{package-FQl71HNO.js → package-CpLzC77t.js} +1 -1
- package/web-app/dist/assets/{plus-DY8mlU6I.js → plus-CXi3LhCN.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-CJ_pkRQQ.js → refresh-cw-26KajS9u.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-rGU_udZ1.js → rotate-ccw-DAXQ_5XQ.js} +1 -1
- package/web-app/dist/assets/{scroll-text-CeVAfOXM.js → scroll-text-ipwVF4bE.js} +1 -1
- package/web-app/dist/assets/{server-D-N_u9OT.js → server-B6Yla6UI.js} +1 -1
- package/web-app/dist/assets/{shield-alert-CJfx3HCX.js → shield-alert-BunS6EV9.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-BrC-E9mS.js → thumbs-up-X1xmkCyf.js} +1 -1
- package/web-app/dist/assets/{trash-2-Bb3mW1oq.js → trash-2-DrY1jsTr.js} +1 -1
- package/web-app/dist/assets/{trending-down-CrgUJZLA.js → trending-down-D5A0-8g2.js} +1 -1
- package/web-app/dist/assets/{trending-up-Dk_GS57K.js → trending-up-BUVdVPXq.js} +1 -1
- package/web-app/dist/assets/{upload-BAxHyFqH.js → upload-BMHttdxa.js} +1 -1
- package/web-app/dist/assets/{usePolling-CkTdc2YU.js → usePolling-C108x_AT.js} +1 -1
- package/web-app/dist/assets/{user-Dr6XCReg.js → user-C0rsicvA.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/web-app/server.py +164 -132
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: An autonomous software factory that knows what it is supposed to deliver, and proves it did. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v11.17.
|
|
6
|
+
# Loki Mode v11.17.36
|
|
7
7
|
|
|
8
8
|
**Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the previous bash loop. PRD-file starts (`loki start ./prd.md`) and the opencode provider still run on it until FC38-SWEEP ports them; the legacy-engine command and the LOKI_ENGINE switch remain removed. Prefer `loki owner/repo#N` for issues.
|
|
9
9
|
|
|
@@ -483,4 +483,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
483
483
|
|
|
484
484
|
---
|
|
485
485
|
|
|
486
|
-
**v11.17.
|
|
486
|
+
**v11.17.36 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
11.17.
|
|
1
|
+
11.17.36
|
|
@@ -782,6 +782,8 @@ def _collect_security(loki_dir):
|
|
|
782
782
|
make here. Stated explicitly because those two say so in their own
|
|
783
783
|
docstrings and this one did not, leaving the behaviour to be inferred from
|
|
784
784
|
silence -- which is the exact failure the honesty ledger exists to prevent.
|
|
785
|
+
A scan that ran and came back clean IS read by _exogenous_corroboration
|
|
786
|
+
(TEN-GATES-10): it can only stand beside a green suite, never cause a gap.
|
|
785
787
|
"""
|
|
786
788
|
out = {
|
|
787
789
|
"ran": False, "total": 0, "active": 0, "waived": 0,
|
|
@@ -853,6 +855,8 @@ def _collect_functional(loki_dir):
|
|
|
853
855
|
NOT change what "Verified" means. Making functional-satisfaction gate the green
|
|
854
856
|
headline is a trust-semantics product decision (council + founder), the second
|
|
855
857
|
half of FV-2. Recording it first lets the signal be seen and validated safely.
|
|
858
|
+
Exception (TEN-GATES-10): functional_status verified is read by
|
|
859
|
+
_exogenous_corroboration as agent-independent evidence beside a green suite.
|
|
856
860
|
"""
|
|
857
861
|
out = {"ran": False, "functional_status": "not_run",
|
|
858
862
|
"passed": 0, "failed": 0, "inconclusive": 0}
|
|
@@ -892,6 +896,8 @@ def _collect_healthcheck(loki_dir):
|
|
|
892
896
|
NOT read by _compute_headline / _compute_degraded, so it does not change what
|
|
893
897
|
"Verified" means. Gating on it is the founder-gated trust decision (mirrors the
|
|
894
898
|
FV-2 opt-in gate). ponytail: reuses the health.json app-runner already writes.
|
|
899
|
+
Exception (TEN-GATES-10): ok:true is read by _exogenous_corroboration as
|
|
900
|
+
agent-independent evidence beside a green suite.
|
|
895
901
|
"""
|
|
896
902
|
out = {"ran": False, "ok": False, "status": "not_run", "checked_at": ""}
|
|
897
903
|
raw = _read_json(
|
|
@@ -1960,14 +1966,61 @@ def _compute_degraded(facts):
|
|
|
1960
1966
|
if rec.get("state") == "gap":
|
|
1961
1967
|
out.append({"item": "functionality:%s" % axis, "status": "failed",
|
|
1962
1968
|
"reason": rec.get("reason") or "axis disproven"})
|
|
1969
|
+
# TEN-GATES-10 (D105): the agent writes both the code and the suite, so a
|
|
1970
|
+
# green suite alone is the agent grading its own work. When the suite is the
|
|
1971
|
+
# ONLY passing fact, record the gap here (pre-headline, so proof-verify.py
|
|
1972
|
+
# re-derives the same amber headline from the recorded ledger) and VERIFIED
|
|
1973
|
+
# becomes VERIFIED WITH GAPS. LOKI_PROOF_REQUIRE_EXOGENOUS=0 opts out.
|
|
1974
|
+
tests_verified = (
|
|
1975
|
+
tests.get("status") == "verified"
|
|
1976
|
+
and bool(tests.get("command"))
|
|
1977
|
+
and tests.get("exit_code") == 0
|
|
1978
|
+
)
|
|
1979
|
+
if (tests_verified
|
|
1980
|
+
and os.environ.get("LOKI_PROOF_REQUIRE_EXOGENOUS", "1").strip() != "0"
|
|
1981
|
+
and not _exogenous_corroboration(facts)):
|
|
1982
|
+
out.append({"item": "exogenous_evidence", "status": "not_run",
|
|
1983
|
+
"reason": "no agent-independent evidence (only the agent's "
|
|
1984
|
+
"own test suite passed)"})
|
|
1963
1985
|
return out
|
|
1964
1986
|
|
|
1965
1987
|
|
|
1988
|
+
def _exogenous_corroboration(facts):
|
|
1989
|
+
"""Names of agent-independent facts that ran and passed (TEN-GATES-10).
|
|
1990
|
+
|
|
1991
|
+
Counts: build verified, healthcheck ok, functional verified, a security scan
|
|
1992
|
+
that actually ran and came back clean, or an EXOGENOUS quality gate that
|
|
1993
|
+
passed. An advisory (model-authored) gate pass never counts: it is the same
|
|
1994
|
+
plausibility signal the agent's own suite already is. Absent facts count as
|
|
1995
|
+
nothing (fail-closed).
|
|
1996
|
+
"""
|
|
1997
|
+
names = []
|
|
1998
|
+
if (facts.get("build") or {}).get("status") == "verified":
|
|
1999
|
+
names.append("build")
|
|
2000
|
+
hc = facts.get("healthcheck") or {}
|
|
2001
|
+
if hc.get("ran") and hc.get("ok") is True:
|
|
2002
|
+
names.append("healthcheck")
|
|
2003
|
+
fn = facts.get("functional") or {}
|
|
2004
|
+
if fn.get("ran") and str(fn.get("functional_status") or "").lower() in (
|
|
2005
|
+
"verified", "passed"):
|
|
2006
|
+
names.append("functional")
|
|
2007
|
+
sec = facts.get("security") or {}
|
|
2008
|
+
if sec.get("ran") and sec.get("status") == "clean":
|
|
2009
|
+
names.append("security")
|
|
2010
|
+
for g in facts.get("quality_gates") or []:
|
|
2011
|
+
if g.get("status") == "passed" and _is_exogenous(g):
|
|
2012
|
+
names.append("quality_gate:%s" % g.get("name", ""))
|
|
2013
|
+
return names
|
|
2014
|
+
|
|
2015
|
+
|
|
1966
2016
|
def _compute_headline(facts, degraded):
|
|
1967
2017
|
"""Deterministic headline. NEVER green from an LLM opinion or a bare
|
|
1968
2018
|
pass:true. Rules:
|
|
1969
2019
|
- VERIFIED only when tests.status == verified AND there are no degraded
|
|
1970
2020
|
items AND the diff is non-empty AND tests recorded a real command.
|
|
2021
|
+
(TEN-GATES-10: _compute_degraded adds an exogenous_evidence gap when
|
|
2022
|
+
the suite is the only passing fact, so VERIFIED also needs one
|
|
2023
|
+
agent-independent fact; see _exogenous_corroboration.)
|
|
1971
2024
|
- VERIFIED WITH GAPS when some facts verified but degraded is non-empty.
|
|
1972
2025
|
- NOT VERIFIED otherwise.
|
|
1973
2026
|
"""
|
package/autonomy/loki
CHANGED
|
@@ -10009,7 +10009,7 @@ _deploy_latest_receipt() {
|
|
|
10009
10009
|
return 0
|
|
10010
10010
|
}
|
|
10011
10011
|
|
|
10012
|
-
# _deploy_record <dir> <command> <exit_code> <run_id> <anchor_sha> [state] [health_status] [health_url] [rb_attempted] [rb_exit] [receipt_sha256] [started_at] [finished_at] [target] [canary] [post_rollback_health] [health_http_code] [health_match] [tree_sha] [destination] [deployed_url] [health_url_source]
|
|
10012
|
+
# _deploy_record <dir> <command> <exit_code> <run_id> <anchor_sha> [state] [health_status] [health_url] [rb_attempted] [rb_exit] [receipt_sha256] [started_at] [finished_at] [target] [canary] [post_rollback_health] [health_http_code] [health_match] [tree_sha] [destination] [deployed_url] [health_url_source] [env_keys_csv] [env_source]
|
|
10013
10013
|
# Writes the executed deploy's own record, then signs it (loki.deploy/1, Ed25519 via the
|
|
10014
10014
|
# receipt signer in loki-ts/src/util/receipt_signer.ts) so it is bound to the receipt that
|
|
10015
10015
|
# authorized it; `loki verify <record>` re-checks it offline. An unsigned record is reported
|
|
@@ -10034,6 +10034,9 @@ _deploy_record() {
|
|
|
10034
10034
|
local receipt_sha="${11:-}" started_at="${12:-}" finished_at="${13:-}" target="${14:-}" canary="${15:-false}"
|
|
10035
10035
|
local post_rb="${16:-unchecked}" hcode_rec="${17:-}" hmatch="${18:-}" tree_sha="${19:-}" dest_rec="${20:-}"
|
|
10036
10036
|
local deployed_url="${21:-}" hsrc="${22:-}"
|
|
10037
|
+
# --env-file: key NAMES only (comma-joined, validated identifiers) and the file
|
|
10038
|
+
# path. A value is never passed here, so the signed record cannot carry one.
|
|
10039
|
+
local env_keys_csv="${23:-}" env_source="${24:-}"
|
|
10037
10040
|
local ddir="$dir/.loki/deploys"
|
|
10038
10041
|
[ -d "${LOKI_DIR:-}" ] && ddir="${LOKI_DIR}/deploys"
|
|
10039
10042
|
mkdir -p "$ddir" 2>/dev/null || return 0
|
|
@@ -10042,6 +10045,7 @@ _deploy_record() {
|
|
|
10042
10045
|
DR_STATE="$anchor_state" DR_HS="$health_status" DR_HU="$health_url" \
|
|
10043
10046
|
DR_RBA="$rb_attempted" DR_RBX="$rb_exit" DR_RSHA="$receipt_sha" DR_T0="$started_at" \
|
|
10044
10047
|
DR_T1="$finished_at" DR_TGT="$target" DR_CAN="$canary" DR_PRB="$post_rb" DR_HC="$hcode_rec" DR_HM="$hmatch" DR_TREE="$tree_sha" DR_DEST="$dest_rec" DR_DURL="$deployed_url" DR_HSRC="$hsrc" \
|
|
10048
|
+
DR_ENVK="$env_keys_csv" DR_ENVS="$env_source" \
|
|
10045
10049
|
python3 -c '
|
|
10046
10050
|
import json, os, datetime
|
|
10047
10051
|
state = os.environ.get("DR_STATE") or "anchored"
|
|
@@ -10073,6 +10077,8 @@ json.dump({
|
|
|
10073
10077
|
"destination": os.environ.get("DR_DEST", ""),
|
|
10074
10078
|
"deployed_url": os.environ.get("DR_DURL", ""),
|
|
10075
10079
|
"health_url_source": os.environ.get("DR_HSRC", ""),
|
|
10080
|
+
"env_keys": sorted(k for k in os.environ.get("DR_ENVK", "").split(",") if k),
|
|
10081
|
+
"env_source": os.environ.get("DR_ENVS", ""),
|
|
10076
10082
|
"health_http_code": int(os.environ["DR_HC"]) if os.environ.get("DR_HC", "").isdigit() else None,
|
|
10077
10083
|
}, open(os.environ["DR_OUT"], "w"), indent=2)
|
|
10078
10084
|
' 2>/dev/null || return 0
|
|
@@ -10241,6 +10247,59 @@ sys.exit(0 if ok else 1)' "$f" "$sha" 2>/dev/null || continue
|
|
|
10241
10247
|
return 1
|
|
10242
10248
|
}
|
|
10243
10249
|
|
|
10250
|
+
# _deploy_env_load <env_file> <example_file>
|
|
10251
|
+
# Parses KEY=VALUE lines with NO shell evaluation (python, never source/eval) and
|
|
10252
|
+
# writes NUL-terminated records to stdout: one "KEY=VALUE" per key (sorted by
|
|
10253
|
+
# key), then "__LOKI_ENV_OK__". On any problem it writes a single
|
|
10254
|
+
# "__LOKI_ENV_ERR__:<reason>" record instead. A reason names keys and line
|
|
10255
|
+
# numbers only, never a value. When <example_file> exists, every key it lists
|
|
10256
|
+
# must be present in <env_file>, else the reason names the missing keys.
|
|
10257
|
+
_deploy_env_load() {
|
|
10258
|
+
python3 - "$1" "$2" <<'PYEOF'
|
|
10259
|
+
import os, re, sys
|
|
10260
|
+
KEY = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\Z")
|
|
10261
|
+
def out(s):
|
|
10262
|
+
sys.stdout.buffer.write(s.encode("utf-8", "surrogateescape") + b"\0")
|
|
10263
|
+
def parse(path, label):
|
|
10264
|
+
found = {}
|
|
10265
|
+
with open(path, encoding="utf-8", errors="surrogateescape") as fh:
|
|
10266
|
+
for n, raw in enumerate(fh, 1):
|
|
10267
|
+
line = raw.strip()
|
|
10268
|
+
if not line or line.startswith("#"):
|
|
10269
|
+
continue
|
|
10270
|
+
if line.startswith("export "):
|
|
10271
|
+
line = line[7:].lstrip()
|
|
10272
|
+
if "=" not in line:
|
|
10273
|
+
raise ValueError("%s line %d is not KEY=VALUE" % (label, n))
|
|
10274
|
+
k, v = line.split("=", 1)
|
|
10275
|
+
k, v = k.strip(), v.strip()
|
|
10276
|
+
if not KEY.match(k):
|
|
10277
|
+
raise ValueError("%s line %d has an invalid key name" % (label, n))
|
|
10278
|
+
if len(v) >= 2 and v[0] == v[-1] and v[0] in "\"'":
|
|
10279
|
+
v = v[1:-1]
|
|
10280
|
+
elif " #" in v:
|
|
10281
|
+
v = v.split(" #", 1)[0].rstrip()
|
|
10282
|
+
if "\0" in v:
|
|
10283
|
+
raise ValueError("%s key %s has a NUL byte in its value" % (label, k))
|
|
10284
|
+
found[k] = v
|
|
10285
|
+
return found
|
|
10286
|
+
try:
|
|
10287
|
+
env = parse(sys.argv[1], "env file")
|
|
10288
|
+
if not env:
|
|
10289
|
+
raise ValueError("env file has no KEY=VALUE entries")
|
|
10290
|
+
if os.path.isfile(sys.argv[2]):
|
|
10291
|
+
missing = sorted(set(parse(sys.argv[2], ".env.example")) - set(env))
|
|
10292
|
+
if missing:
|
|
10293
|
+
raise ValueError("env file is missing key(s) listed in .env.example: " + ", ".join(missing))
|
|
10294
|
+
except (OSError, ValueError) as e:
|
|
10295
|
+
out("__LOKI_ENV_ERR__:" + (str(e) if isinstance(e, ValueError) else "cannot read the env file"))
|
|
10296
|
+
sys.exit(0)
|
|
10297
|
+
for k in sorted(env):
|
|
10298
|
+
out(k + "=" + env[k])
|
|
10299
|
+
out("__LOKI_ENV_OK__")
|
|
10300
|
+
PYEOF
|
|
10301
|
+
}
|
|
10302
|
+
|
|
10244
10303
|
# _deploy_try_execute <dir> <type>
|
|
10245
10304
|
# The ONLY place a deploy command is ever run. Evaluates the gate, and either
|
|
10246
10305
|
# executes + records, or REFUSES naming every failed check. Returns the deploy
|
|
@@ -10290,6 +10349,52 @@ _deploy_try_execute() {
|
|
|
10290
10349
|
esac
|
|
10291
10350
|
fi
|
|
10292
10351
|
|
|
10352
|
+
# --env-file: values live ONLY in env_pairs and the provider argv. Stdout, the
|
|
10353
|
+
# 'Running:' line and the record carry names (K=***), never values.
|
|
10354
|
+
# Documented verbs only: Vercel '-e K=V' per key on the deploy itself; Fly
|
|
10355
|
+
# 'fly secrets set --stage K=V ...' before 'fly deploy'; Netlify
|
|
10356
|
+
# 'netlify env:import FILE' and Cloudflare 'wrangler secret bulk FILE' before
|
|
10357
|
+
# the deploy (the provider reads FILE, so no value enters argv).
|
|
10358
|
+
local env_file="${_LOKI_DEPLOY_ENV_FILE:-}" env_kind="" env_keys="" env_disp="" env_pre_disp="" _env_rec _env_ok=false _env_err=""
|
|
10359
|
+
local -a env_pairs=() env_pre_argv=()
|
|
10360
|
+
if [ -n "$env_file" ]; then
|
|
10361
|
+
case "$command" in
|
|
10362
|
+
"vercel --prod") env_kind="vercel" ;;
|
|
10363
|
+
"fly deploy") env_kind="fly" ;;
|
|
10364
|
+
"netlify deploy --prod") env_kind="netlify" ;;
|
|
10365
|
+
"wrangler deploy") env_kind="cloudflare" ;;
|
|
10366
|
+
*)
|
|
10367
|
+
echo -e "${RED}REFUSED: --env-file is not supported for: ${command}${NC}" >&2
|
|
10368
|
+
echo " No documented env verb is wired for this provider. Nothing was run." >&2
|
|
10369
|
+
return 1
|
|
10370
|
+
;;
|
|
10371
|
+
esac
|
|
10372
|
+
while IFS= read -r -d '' _env_rec; do
|
|
10373
|
+
case "$_env_rec" in
|
|
10374
|
+
__LOKI_ENV_OK__) _env_ok=true ;;
|
|
10375
|
+
__LOKI_ENV_ERR__:*) _env_err="${_env_rec#__LOKI_ENV_ERR__:}" ;;
|
|
10376
|
+
*) env_pairs+=("$_env_rec") ;;
|
|
10377
|
+
esac
|
|
10378
|
+
done < <(_deploy_env_load "$env_file" "$dir/.env.example")
|
|
10379
|
+
if [ "$_env_ok" != true ]; then
|
|
10380
|
+
echo -e "${RED}REFUSED: --env-file: ${_env_err:-the env file could not be parsed}${NC}" >&2
|
|
10381
|
+
echo " Nothing was run." >&2
|
|
10382
|
+
return 1
|
|
10383
|
+
fi
|
|
10384
|
+
case "$env_kind" in
|
|
10385
|
+
fly) env_pre_argv=(fly secrets set --stage); env_pre_disp="fly secrets set --stage" ;;
|
|
10386
|
+
netlify) env_pre_argv=(netlify env:import "$env_file"); env_pre_disp="netlify env:import ${env_file}" ;;
|
|
10387
|
+
cloudflare) env_pre_argv=(wrangler secret bulk "$env_file"); env_pre_disp="wrangler secret bulk ${env_file}" ;;
|
|
10388
|
+
esac
|
|
10389
|
+
for _env_rec in "${env_pairs[@]}"; do
|
|
10390
|
+
env_keys="${env_keys:+${env_keys},}${_env_rec%%=*}"
|
|
10391
|
+
case "$env_kind" in
|
|
10392
|
+
vercel) env_disp="${env_disp} -e ${_env_rec%%=*}=***" ;;
|
|
10393
|
+
fly) env_pre_argv+=("$_env_rec"); env_pre_disp="${env_pre_disp} ${_env_rec%%=*}=***" ;;
|
|
10394
|
+
esac
|
|
10395
|
+
done
|
|
10396
|
+
fi
|
|
10397
|
+
|
|
10293
10398
|
local reasons
|
|
10294
10399
|
reasons="$(_deploy_gate "$dir" "$command")"
|
|
10295
10400
|
|
|
@@ -10346,17 +10451,18 @@ _deploy_try_execute() {
|
|
|
10346
10451
|
else
|
|
10347
10452
|
echo -e "${GREEN}Evidence gate PASSED.${NC} Receipt ${run_id:-unknown} VERIFIED this tree (${anchor:0:12})."
|
|
10348
10453
|
fi
|
|
10349
|
-
|
|
10454
|
+
[ -n "$env_pre_disp" ] && echo "Running (env): ${env_pre_disp}"
|
|
10455
|
+
if [ "$canary" = true ]; then echo "Running (canary): ${exec_cmd}${env_disp}"; else echo "Running: ${command}${env_disp}"; fi
|
|
10350
10456
|
echo ""
|
|
10351
10457
|
|
|
10352
10458
|
# Run in the project dir. No credential env var is read, set, or forwarded:
|
|
10353
10459
|
# the command inherits the user's already-authenticated CLI session as-is.
|
|
10354
10460
|
#
|
|
10355
|
-
#
|
|
10356
|
-
# ("vercel --prod"
|
|
10357
|
-
#
|
|
10358
|
-
#
|
|
10359
|
-
#
|
|
10461
|
+
# The deploy runs as an argv array, never through eval: the canonical
|
|
10462
|
+
# command ("vercel --prod") is word-split from the hardcoded
|
|
10463
|
+
# _deploy_options_for_type table (already past the destructive-operation
|
|
10464
|
+
# check in _deploy_gate), and --env-file values are appended as single
|
|
10465
|
+
# array elements, so a value such as $(cmd) is never evaluated.
|
|
10360
10466
|
# Resolve the rollback target BEFORE deploying: after it, the previous Fly
|
|
10361
10467
|
# release is no longer the newest one and cannot be recovered.
|
|
10362
10468
|
local rb_cmd="" rb_note=""
|
|
@@ -10377,6 +10483,24 @@ _deploy_try_execute() {
|
|
|
10377
10483
|
fi
|
|
10378
10484
|
|
|
10379
10485
|
local rc=0 started_at finished_at purl="" canary_out=""
|
|
10486
|
+
local -a deploy_argv=()
|
|
10487
|
+
read -r -a deploy_argv <<<"$exec_cmd"
|
|
10488
|
+
if [ "$env_kind" = vercel ]; then
|
|
10489
|
+
for _env_rec in "${env_pairs[@]}"; do deploy_argv+=(-e "$_env_rec"); done
|
|
10490
|
+
fi
|
|
10491
|
+
# What the record and messages name: the command with values masked as K=***.
|
|
10492
|
+
local exec_disp="${env_pre_disp:+${env_pre_disp}; }${exec_cmd}${env_disp}"
|
|
10493
|
+
if [ -n "$env_pre_disp" ]; then
|
|
10494
|
+
# The env step runs after the gate and before the deploy; if it fails the
|
|
10495
|
+
# deploy is NOT run (a deploy missing its env is not the one asked for).
|
|
10496
|
+
local env_rc=0
|
|
10497
|
+
( cd "$dir" && "${env_pre_argv[@]}" ) || env_rc=$?
|
|
10498
|
+
if [ "$env_rc" -ne 0 ]; then
|
|
10499
|
+
echo -e "${RED}REFUSED: the env step exited ${env_rc}: ${env_pre_disp}${NC}" >&2
|
|
10500
|
+
echo " The deploy was NOT run." >&2
|
|
10501
|
+
return "$env_rc"
|
|
10502
|
+
fi
|
|
10503
|
+
fi
|
|
10380
10504
|
started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
10381
10505
|
local deployed_url="" health_src=""
|
|
10382
10506
|
[ -n "$health_url" ] && health_src="flag"
|
|
@@ -10385,8 +10509,7 @@ _deploy_try_execute() {
|
|
|
10385
10509
|
# Staged: a preview deploy (no --prod), probe the preview URL, promote only if healthy.
|
|
10386
10510
|
# Nothing reaches production before the probe, so there is nothing to roll back.
|
|
10387
10511
|
rb_cmd=""; auto_rollback=false
|
|
10388
|
-
|
|
10389
|
-
canary_out="$( cd "$dir" && eval "$exec_cmd" )" || rc=$?
|
|
10512
|
+
canary_out="$( cd "$dir" && "${deploy_argv[@]}" )" || rc=$?
|
|
10390
10513
|
printf '%s\n' "$canary_out"
|
|
10391
10514
|
purl="$(grep -Eo 'https?://[^[:space:]]+' <<<"$canary_out" | tail -n 1)"
|
|
10392
10515
|
if [ "$rc" -eq 0 ] && [ -n "$purl" ]; then
|
|
@@ -10395,7 +10518,7 @@ _deploy_try_execute() {
|
|
|
10395
10518
|
if hcode="$(_deploy_probe_health "$health_url" "$health_match")"; then
|
|
10396
10519
|
health_status="healthy"
|
|
10397
10520
|
echo "Promoting: vercel promote ${purl}"
|
|
10398
|
-
|
|
10521
|
+
exec_disp="${exec_disp}; vercel promote ${purl}"
|
|
10399
10522
|
( cd "$dir" && vercel promote "$purl" ) || rc=$?
|
|
10400
10523
|
else
|
|
10401
10524
|
health_status="FAILED_HEALTH"
|
|
@@ -10418,7 +10541,7 @@ _deploy_try_execute() {
|
|
|
10418
10541
|
fi
|
|
10419
10542
|
case $- in *e*) dep_errexit=1 ;; esac
|
|
10420
10543
|
set +e
|
|
10421
|
-
( cd "$dir" &&
|
|
10544
|
+
( cd "$dir" && "${deploy_argv[@]}" ) 2>&1 | tee "$dep_tmp"
|
|
10422
10545
|
rc=${PIPESTATUS[0]}
|
|
10423
10546
|
[ "$dep_errexit" -eq 1 ] && set -e
|
|
10424
10547
|
if [ "$rc" -eq 0 ] && [ "$dep_tmp" != /dev/null ]; then
|
|
@@ -10472,9 +10595,9 @@ _deploy_try_execute() {
|
|
|
10472
10595
|
local rec rsha
|
|
10473
10596
|
finished_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
10474
10597
|
rsha="$(python3 -c 'import json,sys;print((json.load(open(sys.argv[1])).get("verification") or {}).get("hash") or "")' "$pj" 2>/dev/null || printf '')"
|
|
10475
|
-
rec="$(_deploy_record "$dir" "$
|
|
10598
|
+
rec="$(_deploy_record "$dir" "$exec_disp" "$rc" "${run_id:-unknown}" "${anchor:-unknown}" \
|
|
10476
10599
|
"$anchor_state" "$health_status" "$health_url" "$rb_attempted" "$rb_exit" \
|
|
10477
|
-
"$rsha" "$started_at" "$finished_at" "${command%% *}" "$canary" "$post_rb" "$hcode" "$health_match" "$(git -C "$dir" rev-parse 'HEAD^{tree}' 2>/dev/null || true)" "$dest" "$deployed_url" "$health_src")"
|
|
10600
|
+
"$rsha" "$started_at" "$finished_at" "${command%% *}" "$canary" "$post_rb" "$hcode" "$health_match" "$(git -C "$dir" rev-parse 'HEAD^{tree}' 2>/dev/null || true)" "$dest" "$deployed_url" "$health_src" "$env_keys" "$env_file")"
|
|
10478
10601
|
echo ""
|
|
10479
10602
|
if [ "$rc" -eq 0 ]; then
|
|
10480
10603
|
echo -e "${GREEN}Deploy command exited 0.${NC}"
|
|
@@ -10623,6 +10746,7 @@ cmd_deploy() {
|
|
|
10623
10746
|
local LOKI_DEPLOY_ALLOW_UNANCHORED=false
|
|
10624
10747
|
# Per-invocation like --execute; local so an ambient env var cannot set them.
|
|
10625
10748
|
local _LOKI_DEPLOY_HEALTH_URL="" _LOKI_DEPLOY_HEALTH_MATCH="" _LOKI_DEPLOY_AUTO_ROLLBACK=false _LOKI_DEPLOY_CANARY=false _LOKI_DEPLOY_DESTINATION=""
|
|
10749
|
+
local _LOKI_DEPLOY_ENV_FILE=""
|
|
10626
10750
|
|
|
10627
10751
|
# Arg parse (mirror cmd_preview: lenient on unknown args -> no behavior drift).
|
|
10628
10752
|
while [ $# -gt 0 ]; do
|
|
@@ -10630,7 +10754,7 @@ cmd_deploy() {
|
|
|
10630
10754
|
--help|-h|help)
|
|
10631
10755
|
echo -e "${BOLD}Loki Mode -- deploy command advisory (print-only by default)${NC}"
|
|
10632
10756
|
echo ""
|
|
10633
|
-
echo "Usage: loki deploy [--dir <path>] [--no-clip] [--execute [--canary]] [--destination preview|staging|production] [--health-url URL [--health-match STR] [--auto-rollback]]"
|
|
10757
|
+
echo "Usage: loki deploy [--dir <path>] [--no-clip] [--execute [--canary] [--env-file FILE]] [--destination preview|staging|production] [--health-url URL [--health-match STR] [--auto-rollback]]"
|
|
10634
10758
|
echo ""
|
|
10635
10759
|
echo "Detects your project type and your installed cloud CLI (and any"
|
|
10636
10760
|
echo "CI/CD pipeline), then PRINTS the exact deploy command for YOU to run."
|
|
@@ -10720,6 +10844,16 @@ cmd_deploy() {
|
|
|
10720
10844
|
echo " With --execute: preview/staging run a non-production deploy"
|
|
10721
10845
|
echo " (Vercel only); production is refused unless a VERIFIED staging"
|
|
10722
10846
|
echo " deploy record exists for the same SHA. Unset keeps today's behavior."
|
|
10847
|
+
echo " --env-file FILE"
|
|
10848
|
+
echo " With --execute: push KEY=VALUE lines from FILE to the provider"
|
|
10849
|
+
echo " (parsed, never shell-evaluated). Refuses when a key listed in"
|
|
10850
|
+
echo " .env.example is missing from FILE. Vercel: 'vercel --prod -e K=V'"
|
|
10851
|
+
echo " per key; Fly: 'fly secrets set --stage K=V ...' then 'fly deploy';"
|
|
10852
|
+
echo " Netlify: 'netlify env:import FILE'; Cloudflare: 'wrangler secret"
|
|
10853
|
+
echo " bulk FILE'. Any other provider refuses with nothing run. If the env"
|
|
10854
|
+
echo " step fails the deploy is not run. Values are masked as K=*** in"
|
|
10855
|
+
echo " output; the deploy record stores env_keys (names) and env_source,"
|
|
10856
|
+
echo " never values."
|
|
10723
10857
|
echo " --allow-unanchored"
|
|
10724
10858
|
echo " Proceed when the receipt records NO baseline (nothing"
|
|
10725
10859
|
echo " about this tree is proven). Never bypasses a recorded"
|
|
@@ -10770,10 +10904,33 @@ cmd_deploy() {
|
|
|
10770
10904
|
--no-clip)
|
|
10771
10905
|
do_clip=false
|
|
10772
10906
|
;;
|
|
10907
|
+
--env-file)
|
|
10908
|
+
case "${2:-}" in
|
|
10909
|
+
-*|"") echo "loki deploy --env-file requires a file path" >&2; return 1 ;;
|
|
10910
|
+
*) _LOKI_DEPLOY_ENV_FILE="$2"; shift ;;
|
|
10911
|
+
esac
|
|
10912
|
+
;;
|
|
10913
|
+
--env-file=*)
|
|
10914
|
+
_LOKI_DEPLOY_ENV_FILE="${1#--env-file=}"
|
|
10915
|
+
[ -n "$_LOKI_DEPLOY_ENV_FILE" ] || { echo "loki deploy --env-file requires a file path" >&2; return 1; }
|
|
10916
|
+
;;
|
|
10773
10917
|
esac
|
|
10774
10918
|
shift
|
|
10775
10919
|
done
|
|
10776
10920
|
|
|
10921
|
+
if [ -n "$_LOKI_DEPLOY_ENV_FILE" ]; then
|
|
10922
|
+
if [ "$do_execute" != true ]; then
|
|
10923
|
+
echo "loki deploy --env-file requires --execute (the advisory run pushes nothing)" >&2
|
|
10924
|
+
return 1
|
|
10925
|
+
fi
|
|
10926
|
+
if [ ! -f "$_LOKI_DEPLOY_ENV_FILE" ] || [ ! -r "$_LOKI_DEPLOY_ENV_FILE" ]; then
|
|
10927
|
+
echo "loki deploy --env-file: not a readable file: $_LOKI_DEPLOY_ENV_FILE" >&2
|
|
10928
|
+
return 1
|
|
10929
|
+
fi
|
|
10930
|
+
# Absolute, so a provider verb run from the project dir finds the same file.
|
|
10931
|
+
_LOKI_DEPLOY_ENV_FILE="$(cd "$(dirname -- "$_LOKI_DEPLOY_ENV_FILE")" && pwd -P)/$(basename -- "$_LOKI_DEPLOY_ENV_FILE")"
|
|
10932
|
+
fi
|
|
10933
|
+
|
|
10777
10934
|
if [ "$_LOKI_DEPLOY_AUTO_ROLLBACK" = true ] && [ -z "$_LOKI_DEPLOY_HEALTH_URL" ]; then
|
|
10778
10935
|
echo "loki deploy --auto-rollback requires --health-url (no health signal, no rollback)" >&2
|
|
10779
10936
|
return 1
|
package/completions/_loki
CHANGED
|
@@ -178,6 +178,7 @@ function _loki {
|
|
|
178
178
|
'--no-clip[Do not copy the command to the clipboard]' \
|
|
179
179
|
'--execute[Run the deploy command if the receipt gate passes]' \
|
|
180
180
|
'--canary[With --execute: staged rollout (Vercel promote, Fly canary)]' \
|
|
181
|
+
'--env-file[With --execute: push KEY=VALUE env via Vercel, Fly, Netlify or Cloudflare (names-only record)]:env file:_files' \
|
|
181
182
|
'--allow-unanchored[Proceed when the receipt records no baseline]' \
|
|
182
183
|
'--health-url[Health probe URL]:url:' \
|
|
183
184
|
'--auto-rollback[Roll back on a failed health probe]' \
|
package/completions/loki.bash
CHANGED
|
@@ -150,7 +150,7 @@ _loki_completion() {
|
|
|
150
150
|
|
|
151
151
|
deploy)
|
|
152
152
|
if [[ "$cur" == -* ]]; then
|
|
153
|
-
COMPREPLY=( $(compgen -W "--execute --destination --canary --health-url --health-match --auto-rollback --dir --no-clip --allow-unanchored --help" -- "$cur") )
|
|
153
|
+
COMPREPLY=( $(compgen -W "--execute --destination --canary --env-file --health-url --health-match --auto-rollback --dir --no-clip --allow-unanchored --help" -- "$cur") )
|
|
154
154
|
return 0
|
|
155
155
|
fi
|
|
156
156
|
;;
|
package/dashboard/__init__.py
CHANGED
package/docs/CLI-REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CLI reference
|
|
2
2
|
|
|
3
|
-
Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.17.
|
|
3
|
+
Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.17.36. Do not edit by hand.
|
|
4
4
|
Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for the full inventory.
|
|
5
5
|
|
|
6
6
|
| Command | Description |
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Loki Mode, by [Autonomi](https://www.autonomi.dev/), turns a spec or an issue into a verified pull request with a receipt you can check. This page covers installing it, a first run, and the options around it.
|
|
4
4
|
|
|
5
|
-
**Version:** v11.17.
|
|
5
|
+
**Version:** v11.17.36
|
|
6
6
|
|
|
7
7
|
**Engine note:** `loki start` is the primary command. `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` all run the Loki 10 engine, whatever the flag order or input form (PRD file, issue ref, task or one-word brief). The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, or for one call when a flag Loki 10 cannot honor is present. See the [user guide](v10/GUIDE.md) and the [5-minute quickstart](QUICKSTART.md).
|
|
8
8
|
|