loki-mode 7.78.0 → 7.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/checkpoint_sync.py +189 -0
- package/autonomy/lib/config-map.sh +955 -0
- package/autonomy/loki +143 -8
- package/autonomy/run.sh +227 -195
- package/autonomy/sandbox.sh +98 -0
- package/autonomy/trigger-server.py +419 -43
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +63 -7
- package/docs/CONFIG-FILE-PLAN.md +459 -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
package/dashboard/server.py
CHANGED
|
@@ -4008,7 +4008,7 @@ async def get_memory_summary():
|
|
|
4008
4008
|
return summary
|
|
4009
4009
|
|
|
4010
4010
|
|
|
4011
|
-
@app.get("/api/memory/episodes")
|
|
4011
|
+
@app.get("/api/memory/episodes", dependencies=[Depends(auth.require_scope("read"))])
|
|
4012
4012
|
async def list_episodes(limit: int = Query(default=50, ge=1, le=1000)):
|
|
4013
4013
|
"""List episodic memory entries."""
|
|
4014
4014
|
# Both backends below are blocking (SQLite queries / a glob+read loop over
|
|
@@ -4079,7 +4079,7 @@ async def get_episode(episode_id: str):
|
|
|
4079
4079
|
raise HTTPException(status_code=404, detail="Episode not found")
|
|
4080
4080
|
|
|
4081
4081
|
|
|
4082
|
-
@app.get("/api/memory/patterns")
|
|
4082
|
+
@app.get("/api/memory/patterns", dependencies=[Depends(auth.require_scope("read"))])
|
|
4083
4083
|
async def list_patterns():
|
|
4084
4084
|
"""List semantic patterns."""
|
|
4085
4085
|
# Try SQLite first
|
|
@@ -4118,7 +4118,7 @@ async def get_pattern(pattern_id: str):
|
|
|
4118
4118
|
raise HTTPException(status_code=404, detail="Pattern not found")
|
|
4119
4119
|
|
|
4120
4120
|
|
|
4121
|
-
@app.get("/api/memory/skills")
|
|
4121
|
+
@app.get("/api/memory/skills", dependencies=[Depends(auth.require_scope("read"))])
|
|
4122
4122
|
async def list_skills():
|
|
4123
4123
|
"""List procedural skills."""
|
|
4124
4124
|
# Blocking SQLite query / glob+read loop; offload the whole read so the
|
|
@@ -4343,7 +4343,7 @@ async def retrieve_memory(query: dict = None):
|
|
|
4343
4343
|
raise HTTPException(status_code=503, detail=f"Retrieval unavailable: {e}")
|
|
4344
4344
|
|
|
4345
4345
|
|
|
4346
|
-
@app.get("/api/memory/index")
|
|
4346
|
+
@app.get("/api/memory/index", dependencies=[Depends(auth.require_scope("read"))])
|
|
4347
4347
|
async def get_memory_index():
|
|
4348
4348
|
"""Get memory index (Layer 1 - lightweight discovery)."""
|
|
4349
4349
|
index_file = _get_loki_dir() / "memory" / "index.json"
|
|
@@ -4355,7 +4355,7 @@ async def get_memory_index():
|
|
|
4355
4355
|
return {"topics": [], "lastUpdated": None}
|
|
4356
4356
|
|
|
4357
4357
|
|
|
4358
|
-
@app.get("/api/memory/timeline")
|
|
4358
|
+
@app.get("/api/memory/timeline", dependencies=[Depends(auth.require_scope("read"))])
|
|
4359
4359
|
async def get_memory_timeline():
|
|
4360
4360
|
"""Get memory timeline (Layer 2 - progressive disclosure)."""
|
|
4361
4361
|
timeline_file = _get_loki_dir() / "memory" / "timeline.json"
|
|
@@ -4550,7 +4550,7 @@ def _get_memory_storage():
|
|
|
4550
4550
|
return None
|
|
4551
4551
|
|
|
4552
4552
|
|
|
4553
|
-
@app.get("/api/memory/search")
|
|
4553
|
+
@app.get("/api/memory/search", dependencies=[Depends(auth.require_scope("read"))])
|
|
4554
4554
|
async def search_memory(
|
|
4555
4555
|
q: str = Query(..., min_length=1, max_length=500, description="Search query"),
|
|
4556
4556
|
collection: str = Query(default="all", pattern="^(episodes|patterns|skills|all)$"),
|
|
@@ -4588,7 +4588,7 @@ async def search_memory(
|
|
|
4588
4588
|
raise HTTPException(status_code=500, detail=f"Search failed: {e}")
|
|
4589
4589
|
|
|
4590
4590
|
|
|
4591
|
-
@app.get("/api/memory/stats")
|
|
4591
|
+
@app.get("/api/memory/stats", dependencies=[Depends(auth.require_scope("read"))])
|
|
4592
4592
|
async def get_memory_stats():
|
|
4593
4593
|
"""Get memory system statistics (counts, size, backend info)."""
|
|
4594
4594
|
# SQLite stats query or a directory-walk over many JSON files; both block,
|
|
@@ -6860,6 +6860,62 @@ except ImportError as e:
|
|
|
6860
6860
|
logger.debug(f"Collaboration module not available: {e}")
|
|
6861
6861
|
|
|
6862
6862
|
|
|
6863
|
+
class _CollabWsAuthMiddleware:
|
|
6864
|
+
"""ASGI middleware that auth-gates the native /ws/collab WebSocket.
|
|
6865
|
+
|
|
6866
|
+
The collaboration module registers @app.websocket("/ws/collab") inside
|
|
6867
|
+
create_collab_routes() and performs NO token validation: it accepts any
|
|
6868
|
+
connection and trusts a client-supplied ?user_id=. With enterprise auth or
|
|
6869
|
+
OIDC enabled this native WS is therefore reachable UNAUTHENTICATED, exposing
|
|
6870
|
+
user presence, shared state, and operation sync to any client. The dashboard
|
|
6871
|
+
cannot rely on route dependencies for WebSockets (FastAPI Depends() is not
|
|
6872
|
+
supported on @app.websocket routes), so this middleware validates the token
|
|
6873
|
+
on the /ws/collab handshake before the route runs, mirroring the native /ws
|
|
6874
|
+
gate and the _MountAuthGuard WS logic.
|
|
6875
|
+
|
|
6876
|
+
Scope: the collab WS handle_message path applies state operations (writes)
|
|
6877
|
+
via ws_manager.handle_message -> sync.apply_operation, so a valid but
|
|
6878
|
+
read-only token must not be admitted. This requires the "control" scope to
|
|
6879
|
+
match the _MountAuthGuard WS scope-check pattern (a valid token alone is not
|
|
6880
|
+
enough), so a read-only token is closed 1008 even though it authenticates.
|
|
6881
|
+
|
|
6882
|
+
When enterprise auth and OIDC are both OFF this is a pass-through, so local
|
|
6883
|
+
default-mode behavior is unchanged. Non-websocket scopes and other websocket
|
|
6884
|
+
paths (the native /ws self-guards in-route) are passed through untouched.
|
|
6885
|
+
Added via app.add_middleware(), so app stays a FastAPI instance and all
|
|
6886
|
+
later route registrations are unaffected.
|
|
6887
|
+
"""
|
|
6888
|
+
|
|
6889
|
+
def __init__(self, app) -> None:
|
|
6890
|
+
self._app = app
|
|
6891
|
+
|
|
6892
|
+
async def __call__(self, scope, receive, send) -> None:
|
|
6893
|
+
if scope.get("type") != "websocket" or scope.get("path") != "/ws/collab":
|
|
6894
|
+
await self._app(scope, receive, send)
|
|
6895
|
+
return
|
|
6896
|
+
if not auth.is_enterprise_mode() and not auth.is_oidc_mode():
|
|
6897
|
+
await self._app(scope, receive, send)
|
|
6898
|
+
return
|
|
6899
|
+
token_str = _MountAuthGuard._ws_token_from_scope(scope)
|
|
6900
|
+
token_info = _MountAuthGuard._validate_ws_token(token_str)
|
|
6901
|
+
if token_info is None or not auth.has_scope(token_info, "control"):
|
|
6902
|
+
# Accept-then-close is the portable ASGI way to surface a policy
|
|
6903
|
+
# violation to the client before any route code runs. 1008 = policy
|
|
6904
|
+
# violation, matching the native /ws and _MountAuthGuard behavior.
|
|
6905
|
+
# "control" (not just a valid token) is required because the collab
|
|
6906
|
+
# WS path performs state writes; a read-only token is rejected here.
|
|
6907
|
+
await send({"type": "websocket.accept"})
|
|
6908
|
+
await send({"type": "websocket.close", "code": 1008})
|
|
6909
|
+
return
|
|
6910
|
+
await self._app(scope, receive, send)
|
|
6911
|
+
|
|
6912
|
+
|
|
6913
|
+
# Gate the /ws/collab handshake before the unauthenticated collab route runs.
|
|
6914
|
+
# Registered as middleware so app remains a FastAPI instance (route decorators
|
|
6915
|
+
# below keep working).
|
|
6916
|
+
app.add_middleware(_CollabWsAuthMiddleware)
|
|
6917
|
+
|
|
6918
|
+
|
|
6863
6919
|
# =============================================================================
|
|
6864
6920
|
# Secrets / Credential Status
|
|
6865
6921
|
# =============================================================================
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# Unified Config-File Plan (FEAT-CONFIG, task #691)
|
|
2
|
+
|
|
3
|
+
Status: design for implementation. Build target: POST-v7.73.0 main (the v7.73.0
|
|
4
|
+
branch-default + `loki deploy` + secret-scan work is present in the working tree;
|
|
5
|
+
all line anchors below are verified against that state). NO version bump, NO
|
|
6
|
+
commit, NO implementation code in this doc. Devil's-advocate corrections to the
|
|
7
|
+
approved plan (`~/.claude/plans/polished-waddling-stardust.md`) are folded in and
|
|
8
|
+
marked CORRECTION where the approved text was factually wrong against source.
|
|
9
|
+
|
|
10
|
+
Goal: `loki start --config <path>` (aliases `--vars`, `--env-file`) loads a single
|
|
11
|
+
`.env` / YAML / JSON file so Docker / compose / k8s / Vault operators inject one
|
|
12
|
+
mounted file instead of a wall of `LOKI_*` env vars or CLI flags. v1 makes ALL
|
|
13
|
+
~250 flags configurable via flat `.env`; nested friendly YAML/JSON full-coverage
|
|
14
|
+
is v2. Secrets are never inlined -- referenced via `${VAR}`.
|
|
15
|
+
|
|
16
|
+
--------------------------------------------------------------------------------
|
|
17
|
+
## 0. Locked precedence ladder (CONTRACT -- cannot be phased)
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
CLI flags (explicit this run) highest
|
|
21
|
+
> --config file (explicit this run) <- NEW layer
|
|
22
|
+
> ambient env (ConfigMap / Secret / .env_file / exported LOKI_*)
|
|
23
|
+
> auto .loki/config.yaml + ~/.config/loki-mode/config.yaml (ambient project file)
|
|
24
|
+
> settings.json (.loki/config/settings.json)
|
|
25
|
+
> built-in defaults lowest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`--config` MUST beat ambient env: the Helm chart injects every non-secret `LOKI_*`
|
|
29
|
+
via `envFrom: configMapRef` and docker-compose auto-loads `.env`, so ambient
|
|
30
|
+
`LOKI_*` is ALWAYS present in the exact deployments this feature targets. If env
|
|
31
|
+
beat `--config`, a mounted file would override nothing. Auto-discovered
|
|
32
|
+
`.loki/config.yaml` stays env-LOSES (unchanged contract).
|
|
33
|
+
|
|
34
|
+
--------------------------------------------------------------------------------
|
|
35
|
+
## 1. Pre-pass placement in main() (autonomy/loki)
|
|
36
|
+
|
|
37
|
+
Verified anchors: `main()` at loki:15285; `command="$1"; shift` at 15301-15302;
|
|
38
|
+
`loki_telemetry` at 15307; `case "$command"` dispatch at 15309 (`start)` ->
|
|
39
|
+
cmd_start at 15314, `run)`/`quick)` adjacent). cmd_start at loki:1012, its arg
|
|
40
|
+
loop at 1042. Exec handoff `_loki_new_session_exec "$RUN_SH" ...` at loki:2097.
|
|
41
|
+
CLI flags export inline in the cmd_start loop (e.g. `export LOKI_COMPLEXITY=simple`
|
|
42
|
+
under `--simple`; `export LOKI_GITHUB_IMPORT=true` under `--github`), confirmed in
|
|
43
|
+
the 1042-1560 range.
|
|
44
|
+
|
|
45
|
+
### 1a. Where the pre-pass runs
|
|
46
|
+
Insert the pre-pass AFTER `command="$1"; shift` (15302) and AFTER the
|
|
47
|
+
`loki_telemetry` call (15307), and BEFORE the `case "$command"` dispatch (15309).
|
|
48
|
+
Gate it to SESSION commands only so `loki status` / `config` / `stop` etc. never
|
|
49
|
+
trigger a config load:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
case "$command" in
|
|
53
|
+
start|run|quick)
|
|
54
|
+
loki_maybe_apply_config_file "$@" # NEW pre-pass; scans, does NOT consume
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
# ... existing case "$command" dispatch unchanged ...
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`loki_maybe_apply_config_file` is a NEW helper sourced from the new lib (SS2). It:
|
|
61
|
+
1. Honors `LOKI_CONFIG_FILE` env var first (for ENTRYPOINT / in-container direct
|
|
62
|
+
`loki start` where flags are awkward). An explicit flag overrides the env var.
|
|
63
|
+
2. Pre-SCANS `"$@"` for `--config[=]`, `--vars[=]`, `--env-file[=]` to extract the
|
|
64
|
+
path, WITHOUT shifting/consuming (the per-command loops still see every arg).
|
|
65
|
+
Scan reads both `--config <path>` and `--config=<path>` forms.
|
|
66
|
+
3. If a path is found, calls `loki_apply_config_file "$path"` (SS2), which detects
|
|
67
|
+
format, expands `${VAR}` refs, validates, and `export LOKI_*` for each key.
|
|
68
|
+
|
|
69
|
+
### 1b. Why pre-pass (not a run.sh flag)
|
|
70
|
+
By the time run.sh runs, a config-set `LOKI_COMPLEXITY=simple` is byte-identical to
|
|
71
|
+
an ambient env var; run.sh cannot tell explicit-config from ambient env. Only a
|
|
72
|
+
pre-pass running BEFORE the cmd_start arg loop can:
|
|
73
|
+
- (a) be OVERWRITTEN by the subsequent CLI loop -> CLI > config, no extra logic
|
|
74
|
+
(the loop's own `export LOKI_*=...` arms run after and win); and
|
|
75
|
+
- (b) have its exports SURVIVE `exec run.sh` (loki:2097), where auto-config /
|
|
76
|
+
settings.json / defaults all env-LOSE to what is already set.
|
|
77
|
+
|
|
78
|
+
The pre-pass export OVERRIDES ambient env -- the ONE intentional difference from
|
|
79
|
+
the existing env-wins loaders, correct per the Helm/compose reasoning. DO NOT add
|
|
80
|
+
a "force-override from explicit config" path to run.sh's env-wins guard
|
|
81
|
+
(run.sh:395/490): it would clobber CLI flags and break CLI > config.
|
|
82
|
+
|
|
83
|
+
### 1c. REQUIRED cmd_start edit (do not let this hide in "scan without consume")
|
|
84
|
+
Because the pre-pass does NOT consume args, cmd_start's loop (1042+) still SEES
|
|
85
|
+
`--config <path>`. With no case arm it would be misread as the PRD positional or
|
|
86
|
+
forwarded to run.sh. ADD consume-and-ignore arms in the cmd_start arg loop (and in
|
|
87
|
+
cmd_run / cmd_quick loops if they accept these) for all three aliases, both `=`
|
|
88
|
+
and space forms:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
--config|--vars|--env-file) shift 2; continue ;; # consumed by pre-pass
|
|
92
|
+
--config=*|--vars=*|--env-file=*) shift; continue ;;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
These arms intentionally do nothing else: the pre-pass already applied the file.
|
|
96
|
+
|
|
97
|
+
--------------------------------------------------------------------------------
|
|
98
|
+
## 2. NEW autonomy/lib/config-map.sh (single-source mapping; fixes the 3-way drift)
|
|
99
|
+
|
|
100
|
+
This lib is the canonical mapping array PLUS the config-file loader helpers, and
|
|
101
|
+
it is the SINGLE HOME of the parse-and-export logic. It is sourced by BOTH the loki
|
|
102
|
+
pre-pass and run.sh's parsers, collapsing 3 tables to 1.
|
|
103
|
+
|
|
104
|
+
DESIGN CONSTRAINT (for SDET, SS7): the lib MUST be SIDE-EFFECT-FREE ON SOURCE --
|
|
105
|
+
sourcing it only defines the array + functions, exports nothing, runs nothing. The
|
|
106
|
+
loki pre-pass and run.sh each call the functions explicitly. This lets unit tests
|
|
107
|
+
source the lib and call individual functions (parser, expander) in isolation.
|
|
108
|
+
|
|
109
|
+
### 2a-0. The override-mode loader (the load-bearing precedence mechanism)
|
|
110
|
+
The keystone (config beats ambient env) lives HERE, as an `override` PARAMETER on
|
|
111
|
+
the shared per-key export -- NOT in run.sh, NOT duplicated per format. All three
|
|
112
|
+
format parsers (.env / YAML / JSON) call ONE export helper:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
loki_config_export_key <env_var> <value> <override>
|
|
116
|
+
# if [ "$override" != 1 ] && [ -n "${!env_var:-}" ]; then return 0; fi # env-wins guard
|
|
117
|
+
# value -> ${VAR}-expand -> validate_yaml_value -> export
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- loki pre-pass (SS1) calls every key with override=1 -> config BEATS ambient env,
|
|
121
|
+
UNIFORMLY across .env / YAML / JSON (this is what makes case 3 AND case 7 pass).
|
|
122
|
+
- run.sh's two parsers (SS3) delegate with override=0 -> env-wins guard PRESERVED,
|
|
123
|
+
the existing auto-discovery contract is byte-unchanged.
|
|
124
|
+
|
|
125
|
+
CORRECTION (defect in the first draft): do NOT have the pre-pass "reuse run.sh
|
|
126
|
+
engines" by sourcing run.sh -- run.sh fires on-source side effects
|
|
127
|
+
(load_config_file at run.sh:507, _load_json_settings at 567, all the `:-` defaults).
|
|
128
|
+
The pre-pass sources config-map.sh ONLY and calls its loader; run.sh ALSO sources
|
|
129
|
+
config-map.sh and calls the same loader with override=0. Without the override
|
|
130
|
+
parameter, a reused env-wins parser would SKIP a config key whenever ambient env is
|
|
131
|
+
present -- exactly the Helm/compose case -- so the keystone would silently fail for
|
|
132
|
+
YAML/JSON while .env (no guard) overrode correctly, diverging the formats.
|
|
133
|
+
|
|
134
|
+
### 2a. Canonical array
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
LOKI_CONFIG_MAP=(
|
|
138
|
+
"nested.path:LOKI_ENV_VAR"
|
|
139
|
+
...
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 2b. VERIFIED drift enumeration (CORRECTIONS to the approved plan)
|
|
144
|
+
|
|
145
|
+
Three tables exist today:
|
|
146
|
+
- T1 `parse_simple_yaml` (run.sh:262-350) -- 64 `set_from_yaml` calls.
|
|
147
|
+
- T2 `parse_yaml_with_yq` (run.sh:419-504) -- 61 `path:LOKI_*` entries.
|
|
148
|
+
- T3 `config.example.yaml` (docs the user-facing keys).
|
|
149
|
+
|
|
150
|
+
Verified deltas (read against source, not the approved plan's claims):
|
|
151
|
+
|
|
152
|
+
1. `model.planning`, `model.development`, `model.fast` -> in T1 (run.sh:320-322)
|
|
153
|
+
ONLY; ABSENT from T2 and from T3. (Approved plan correct.) Target env vars
|
|
154
|
+
LOKI_MODEL_PLANNING / _DEVELOPMENT / _FAST are CONFIRMED via settings.json
|
|
155
|
+
mapping (run.sh:546-548). T1 minus T2 = exactly these 3 (64 vs 61).
|
|
156
|
+
|
|
157
|
+
2. `model.compaction_interval` -> in T3 (config.example.yaml:120) ONLY; NOT in T1,
|
|
158
|
+
NOT in T2. CORRECTION/IMPORTANT: it has ZERO runtime consumer anywhere
|
|
159
|
+
(`grep compaction_interval|LOKI_COMPACTION` -> nothing). It is a DEAD documented
|
|
160
|
+
key. Do NOT invent a LOKI_* var for it in v1. Either (a) wire a real consumer
|
|
161
|
+
first, or (b) drop it from the generated example. Recommended v1: leave it OUT
|
|
162
|
+
of LOKI_CONFIG_MAP and remove it from the generated example (note in CHANGELOG),
|
|
163
|
+
defer a real consumer to v2.
|
|
164
|
+
|
|
165
|
+
3. `model.autonomy_mode` -> CORRECTION: the approved plan says "neither table
|
|
166
|
+
carries" it. FALSE. It is in T1 (run.sh:319), T2 (run.sh:463) AND T3
|
|
167
|
+
(config.example.yaml:118). It is fully consistent; do not treat it as drift.
|
|
168
|
+
|
|
169
|
+
4. `completion.council.*` (6 keys: enabled/size/threshold/check_interval/
|
|
170
|
+
min_iterations/stagnation_limit) and `completion.uncertainty.*` (4 keys:
|
|
171
|
+
escalation/rounds/nochange_min/split_rounds) -> in BOTH T1 and T2, but ABSENT
|
|
172
|
+
as live keys from T3 (council not present; uncertainty only as commented prose
|
|
173
|
+
at example.yaml:92-107). So T3 (config.example.yaml) is the MOST out-of-sync of
|
|
174
|
+
the three. The generated `config example` (SS6) closes this by emitting these
|
|
175
|
+
from LOKI_CONFIG_MAP so example can never lag the parsers again.
|
|
176
|
+
|
|
177
|
+
### 2c. Reconciled canonical key set (LOCK)
|
|
178
|
+
|
|
179
|
+
LOKI_CONFIG_MAP = the 64 keys of T1 (the superset; it equals T2's 61 PLUS the 3
|
|
180
|
+
model.* keys). `model.compaction_interval` is EXCLUDED (no consumer). Final v1
|
|
181
|
+
count: 64 mappings. They are (grouped):
|
|
182
|
+
|
|
183
|
+
- core: max_retries, base_wait, max_wait, skip_prereqs
|
|
184
|
+
- dashboard: enabled, port
|
|
185
|
+
- resources: check_interval, cpu_threshold, mem_threshold
|
|
186
|
+
- security: staged_autonomy, audit_log, max_parallel_agents, sandbox_mode,
|
|
187
|
+
allowed_paths, blocked_commands
|
|
188
|
+
- phases: unit_tests, api_tests, e2e_tests, security, integration, code_review,
|
|
189
|
+
web_research, performance, accessibility, regression, uat
|
|
190
|
+
- completion: promise, max_iterations, perpetual_mode
|
|
191
|
+
- completion.council: enabled, size, threshold, check_interval, min_iterations,
|
|
192
|
+
stagnation_limit
|
|
193
|
+
- completion.uncertainty: escalation, rounds, nochange_min, split_rounds
|
|
194
|
+
- model: prompt_repetition, confidence_routing, autonomy_mode, planning,
|
|
195
|
+
development, fast
|
|
196
|
+
- parallel: enabled, max_worktrees, max_sessions, testing, docs, blog, auto_merge
|
|
197
|
+
- complexity: tier
|
|
198
|
+
- github: import, pr, sync, repo, labels, milestone, assignee, limit, pr_label
|
|
199
|
+
- notifications: enabled, sound
|
|
200
|
+
|
|
201
|
+
The exact `nested.path:LOKI_ENV_VAR` pairs are copied verbatim from T1's
|
|
202
|
+
`set_from_yaml` calls (run.sh:266-349) so env-var names are guaranteed correct.
|
|
203
|
+
|
|
204
|
+
### 2d. settings.json mapping stays SEPARATE (do not merge)
|
|
205
|
+
`_load_json_settings` (run.sh:544-558) carries a DIFFERENT schema -- maxTier,
|
|
206
|
+
provider, issue.provider, notify.slack/discord, blind_validation,
|
|
207
|
+
adversarial_testing, spawn_timeout, spawn_retries, budget -- none of which any YAML
|
|
208
|
+
parser maps. Only model.planning/development/fast overlap. LOKI_CONFIG_MAP is the
|
|
209
|
+
YAML/config-file surface ONLY; settings.json keeps its own map. Merging them would
|
|
210
|
+
corrupt both surfaces. (v2 may optionally unify, additively.)
|
|
211
|
+
|
|
212
|
+
--------------------------------------------------------------------------------
|
|
213
|
+
## 3. Refactor run.sh's two parsers to iterate the shared array
|
|
214
|
+
|
|
215
|
+
After config-map.sh exists, both parsers source it and iterate LOKI_CONFIG_MAP,
|
|
216
|
+
delegating the per-key export to the shared loader with override=0 (env-wins
|
|
217
|
+
PRESERVED -- the auto-discovery contract is unchanged):
|
|
218
|
+
|
|
219
|
+
- `parse_yaml_with_yq` (run.sh:419-504): delete the inline `mappings=(...)` array
|
|
220
|
+
(421-483); source config-map.sh; loop over `LOKI_CONFIG_MAP`; for each key read
|
|
221
|
+
the value via `yq eval ".$path"` (as today, 496) and pass it to
|
|
222
|
+
`loki_config_export_key "$env_var" "$value" 0`. The shared helper now owns the
|
|
223
|
+
env-wins guard, validate, and export (formerly run.sh:490-501).
|
|
224
|
+
- `parse_simple_yaml` (run.sh:262-350): replace the 64 hand-written
|
|
225
|
+
`set_from_yaml` calls with a loop over LOKI_CONFIG_MAP. `set_from_yaml`
|
|
226
|
+
(run.sh:389) is refactored to extract via grep/sed (410) then delegate to
|
|
227
|
+
`loki_config_export_key "$env" "$value" 0` (so its guard/validate/export at
|
|
228
|
+
395/413/414 also route through the single shared helper).
|
|
229
|
+
|
|
230
|
+
`load_config_file` (run.sh:228-259) and its yq-present/absent routing are
|
|
231
|
+
unchanged. Net effect: 3 tables -> 1 AND one export helper -> env-wins (override=0)
|
|
232
|
+
and config-override (override=1) share identical parse/validate logic, so .env /
|
|
233
|
+
YAML / JSON can never diverge. run.sh keeps override=0 everywhere, so its shipped
|
|
234
|
+
behavior is byte-identical.
|
|
235
|
+
|
|
236
|
+
--------------------------------------------------------------------------------
|
|
237
|
+
## 4. Format detection + parsing (reuse existing engines)
|
|
238
|
+
|
|
239
|
+
`loki_apply_config_file <path>` (in config-map.sh):
|
|
240
|
+
|
|
241
|
+
1. Validate path: file exists, readable, not a symlink for project-local paths
|
|
242
|
+
(mirror load_config_file's symlink guard at run.sh:234). Missing/unreadable ->
|
|
243
|
+
honest non-zero exit + message; NO silent default fallback.
|
|
244
|
+
2. Detect format:
|
|
245
|
+
- extension `.env` / no-ext-named-".env" -> ENV
|
|
246
|
+
- `.yaml` / `.yml` -> YAML
|
|
247
|
+
- `.json` -> JSON
|
|
248
|
+
- unknown/no extension -> content sniff: first non-blank, non-`#` line
|
|
249
|
+
starts with `{` -> JSON; matches `^[A-Z_][A-Z0-9_]*=` -> ENV; matches
|
|
250
|
+
`^[a-zA-Z0-9_.-]+:` -> YAML.
|
|
251
|
+
3. Route (ALL three call `loki_config_export_key ... 1` -- override=1, in
|
|
252
|
+
config-map.sh; the pre-pass sources config-map.sh ONLY, never run.sh):
|
|
253
|
+
- ENV -> flat parser (SS4a).
|
|
254
|
+
- YAML -> yq if `command -v yq` else the simple grep/sed fallback, iterating
|
|
255
|
+
LOKI_CONFIG_MAP over the arbitrary path. Same logic as run.sh's parsers but
|
|
256
|
+
invoked with override=1.
|
|
257
|
+
- JSON -> yq reads JSON natively; else the audited python3 path modeled on
|
|
258
|
+
`_load_json_settings` (run.sh:525-565): json.load, isinstance(str) guard,
|
|
259
|
+
shlex.quote, fixed export template. Reuse that exact safe pattern, then feed
|
|
260
|
+
each resolved value to `loki_config_export_key ... 1`.
|
|
261
|
+
|
|
262
|
+
### 4a. The flat `.env` parser (FULL ~250-flag coverage, day one)
|
|
263
|
+
|
|
264
|
+
For each line: skip blank and `#`-comment lines; split on the FIRST `=` only
|
|
265
|
+
(`key="${line%%=*}"`, `val="${line#*=}"`); strip surrounding quotes; trim. Key
|
|
266
|
+
allowlist: accept `^LOKI_[A-Z0-9_]+$`. Also accept a SHORT documented allowlist of
|
|
267
|
+
non-LOKI build vars actually consumed (verify each against source before locking;
|
|
268
|
+
candidates seen in run.sh defaults: e.g. provider/budget are already LOKI_*).
|
|
269
|
+
Default: reject any key not matching the allowlist with a visible warning (never a
|
|
270
|
+
silent skip). Each accepted value goes through `${VAR}` expansion (SS5) THEN
|
|
271
|
+
`validate_yaml_value` (run.sh:353) THEN `export`. This is what makes "all flags
|
|
272
|
+
configurable" honest on day one with near-zero code -- `.env` is the flat
|
|
273
|
+
full-surface form; nested friendly YAML/JSON full-coverage is v2.
|
|
274
|
+
|
|
275
|
+
Every value (ENV/YAML/JSON) passes `validate_yaml_value` (run.sh:353) before
|
|
276
|
+
export: rejects shell metachars `[$\`|;&><(){}[]\\]`, newlines, over-length
|
|
277
|
+
(>1000). NOTE (document this): because validate runs AFTER expansion, a resolved
|
|
278
|
+
secret whose VALUE contains a shell metachar would be rejected. This is
|
|
279
|
+
conservative and acceptable -- such values must be delivered via the env directly,
|
|
280
|
+
not through the validated config path.
|
|
281
|
+
|
|
282
|
+
--------------------------------------------------------------------------------
|
|
283
|
+
## 5. ${VAR} env-ref expansion + raw-secret warning
|
|
284
|
+
|
|
285
|
+
### 5a. Expansion (NEVER eval)
|
|
286
|
+
- Match a full-value ref `^\$\{[A-Za-z_][A-Za-z0-9_]*\}$` and embedded refs.
|
|
287
|
+
- Resolve via bash indirect expansion: `name="${ref:2:-1}"; value="${!name}"`.
|
|
288
|
+
NEVER `eval`.
|
|
289
|
+
- Order is EXPAND-THEN-VALIDATE: validate_yaml_value rejects `$`, so an
|
|
290
|
+
unexpanded `${VAR}` would always fail. Expansion is precisely what makes a ref
|
|
291
|
+
usable while every other literal `$` stays rejected.
|
|
292
|
+
- Unset ref -> SKIP that key + emit a warning (do not export empty, do not abort
|
|
293
|
+
the whole load). `config validate` reports unresolved refs (SS6).
|
|
294
|
+
|
|
295
|
+
### 5b. Raw-secret warning (reuse the shipped scanner patterns)
|
|
296
|
+
Reuse the verified v7.73.0 commit-time scanner ideas:
|
|
297
|
+
`autonomy/run.sh:_commit_scan_secret_file` (6331-6382) and
|
|
298
|
+
`_commit_path_looks_secret` (6384+), which mirror `autonomy/verify.sh`'s
|
|
299
|
+
`verify_secret_scan_file`. Apply its TIER-1 format patterns to a config-file
|
|
300
|
+
VALUE that is a literal (not a `${VAR}` ref):
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
AKIA[0-9A-Z]{16} | ASIA[0-9A-Z]{16}
|
|
304
|
+
-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----
|
|
305
|
+
gh[pousr]_[A-Za-z0-9]{36,} | github_pat_[A-Za-z0-9_]{60,}
|
|
306
|
+
xox[baprs]-[A-Za-z0-9-]{10,}
|
|
307
|
+
sk-[A-Za-z0-9]{20,} (covers sk-ant-)
|
|
308
|
+
AIza[0-9A-Za-z_-]{35} | glpat-[A-Za-z0-9_-]{20,}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Plus TIER-2 generic-assignment + bearer + URI-embedded-credential
|
|
312
|
+
(`scheme://user:pass@host`) patterns, run through the existing deny filter so
|
|
313
|
+
`${VAR}`-ref values are correctly IGNORED (the deny regex already excludes
|
|
314
|
+
`\$\{`/`\$[A-Za-z_]`/`process.env`/placeholders). On load: WARN ("use ${VAR} +
|
|
315
|
+
env/Vault"). In `config validate`: ERROR (non-zero exit).
|
|
316
|
+
|
|
317
|
+
--------------------------------------------------------------------------------
|
|
318
|
+
## 6. `loki config example|schema|validate` (reuse loki:8177-8244 validators)
|
|
319
|
+
|
|
320
|
+
### 6a. CRITICAL prerequisite: extract the validators
|
|
321
|
+
The per-key validators at loki:8177-8244 are INLINE in `cmd_config_set` (8141),
|
|
322
|
+
not a reusable function. CORRECTION to "reuse loki:8177-8244 validators": they
|
|
323
|
+
cannot be reused as-is. EXTRACT them into a new `validate_config_key <key>
|
|
324
|
+
<value>` helper (returns non-zero + message on invalid), and have BOTH
|
|
325
|
+
cmd_config_set AND `config validate` call it. Otherwise we duplicate the
|
|
326
|
+
validator logic -- reintroducing the exact drift this feature exists to fix.
|
|
327
|
+
|
|
328
|
+
### 6b. New subcommands (add arms to cmd_config dispatch, loki:8093-8137)
|
|
329
|
+
- `config example` -> emit the full annotated nested YAML GENERATED from
|
|
330
|
+
LOKI_CONFIG_MAP (so it can never drift from the parsers), carrying
|
|
331
|
+
config.example.yaml's prose as comments. Drop the dead `compaction_interval`.
|
|
332
|
+
- `config schema` -> machine-readable `key -> LOKI_ENV_VAR -> type` table,
|
|
333
|
+
generated from LOKI_CONFIG_MAP.
|
|
334
|
+
- `config validate <file>` -> detect format (SS4), dry-expand refs and report
|
|
335
|
+
unresolved (SS5a), raw-secret check as ERROR (SS5b), run `validate_config_key`
|
|
336
|
+
per key where a validator exists, run `validate_yaml_value` on every value.
|
|
337
|
+
Non-zero exit on ANY failure. Update the cmd_config usage block (8113-8136) and
|
|
338
|
+
the `*)` default help to list the three new subcommands.
|
|
339
|
+
|
|
340
|
+
--------------------------------------------------------------------------------
|
|
341
|
+
## 7. SDET test plan (mutation-proof; the 12 cases)
|
|
342
|
+
|
|
343
|
+
OBSERVABILITY DESIGN (this is the hard part -- named, not hand-waved):
|
|
344
|
+
- config-map.sh is side-effect-free on source (SS2), so the .env parser, the
|
|
345
|
+
`${VAR}` expander, format-detect, and the raw-secret matcher are UNIT-tested by
|
|
346
|
+
sourcing the lib and calling each function directly with crafted input.
|
|
347
|
+
- For the integration cases (esp. the keystone) the test drives the REAL binary
|
|
348
|
+
as a subprocess (same rationale as test-deploy.sh's header: autonomy/loki runs
|
|
349
|
+
main() when sourced, so extract+source is unsafe). The test needs an OBSERVATION
|
|
350
|
+
HOOK to read the RESOLVED `LOKI_*` without a full build. Mechanism: stub the
|
|
351
|
+
exec target -- put a fake `run.sh` (or a fake provider CLI) earlier on PATH /
|
|
352
|
+
via a test-only `RUN_SH` override that DUMPS the environment (`env | grep ^LOKI_`)
|
|
353
|
+
to a sentinel file and exits 0. The test asserts on the dumped values. (A small
|
|
354
|
+
documented `LOKI_CONFIG_DUMP=1` dry mode that prints resolved LOKI_* and exits
|
|
355
|
+
is an acceptable alternative observation hook; pick one and document it.)
|
|
356
|
+
- The test MUST NOT set `LOKI_AUTO_FIX=true`: run.sh:625-627 would clobber
|
|
357
|
+
MAX_ITERATIONS to 5 and corrupt the MAX_ITERATIONS assertions.
|
|
358
|
+
- Non-vacuity: every "value reaches runtime == X" assertion is paired with a flip
|
|
359
|
+
to a second value to prove it is not a default coincidence.
|
|
360
|
+
|
|
361
|
+
Cases:
|
|
362
|
+
1. Config-only key reaches runtime: `LOKI_MAX_ITERATIONS=4242` via config file
|
|
363
|
+
only -> dumped == 4242; flip to 1337 to prove non-default.
|
|
364
|
+
2. CLI overrides config: config `complexity.tier: complex` + `--simple` -> simple.
|
|
365
|
+
3. KEYSTONE -- `--config` overrides ambient env: `export LOKI_MAX_ITERATIONS=10`,
|
|
366
|
+
config sets 4242 -> dumped == 4242. Proves the whole point.
|
|
367
|
+
4. auto `.loki/config.yaml` LOSES to `--config`; AND ambient env still BEATS auto
|
|
368
|
+
`.loki/config.yaml` (unchanged-contract regression).
|
|
369
|
+
5. `${VAR}` expands from env; `${UNSET}` -> key skipped + warning emitted.
|
|
370
|
+
6. Raw-secret literal -> warning on load; non-zero on `config validate`; a
|
|
371
|
+
`${VAR}`-ref value -> no warning (deny-filter path).
|
|
372
|
+
7. Format parity (WITH ambient env present -- mandatory): export a conflicting
|
|
373
|
+
ambient `LOKI_*`, then load the same logical config as `.env`, `.yaml`, AND
|
|
374
|
+
`.json` -> all three produce IDENTICAL dumped exports == the config value (NOT
|
|
375
|
+
the ambient value). Setting ambient env is required: the override defect only
|
|
376
|
+
surfaces when env is present (with env absent all three pass vacuously), so a
|
|
377
|
+
parity test without ambient env would not catch a YAML/JSON env-wins regression.
|
|
378
|
+
8. Injection: value with `$(...)` / backticks / `;` rejected by
|
|
379
|
+
validate_yaml_value, never executed (sentinel-absent + value-not-exported).
|
|
380
|
+
9. Bad/missing/symlink file -> honest non-zero exit, no silent default fallback.
|
|
381
|
+
10. Drift test: every var in LOKI_CONFIG_MAP is consumed in run.sh (grep each
|
|
382
|
+
LOKI_* target has a `${LOKI_...:-` reader); report any unmapped LOKI_* so
|
|
383
|
+
coverage growth is measurable. Asserts T1==T2 (both now iterate the array).
|
|
384
|
+
11. cmd_start no-op-arm test: `loki start --config f.env ./prd.md` -> prd_file is
|
|
385
|
+
./prd.md (the path is NOT misread as the PRD positional), and `--config` is
|
|
386
|
+
NOT forwarded to the exec target.
|
|
387
|
+
12. Bash/Bun parity harness (task #630) green; `bash scripts/local-ci.sh` green;
|
|
388
|
+
`bash tests/run-shellcheck.sh` clean on the new lib. Register a new
|
|
389
|
+
`test-config-file.sh` in tests/run-all-tests.sh (alongside the
|
|
390
|
+
`test-deploy.sh` registration at run-all-tests.sh:218).
|
|
391
|
+
|
|
392
|
+
Full SDLC fleet (Architect -> PO -> dev -> SDET -> 3/3 council) -> ship as own
|
|
393
|
+
MINOR release.
|
|
394
|
+
|
|
395
|
+
--------------------------------------------------------------------------------
|
|
396
|
+
## 8. Docs to update + version bump
|
|
397
|
+
|
|
398
|
+
Docs (content updates, NOT version-gated):
|
|
399
|
+
- README.md, docs/INSTALLATION.md, DOCKER_README.md, wiki: `loki start --config
|
|
400
|
+
<path>` (+ `--vars` / `--env-file`), the precedence ladder, `${VAR}` syntax, the
|
|
401
|
+
secret rule, `config example|schema|validate`.
|
|
402
|
+
- docker-compose.yml: show mounting a config file + that config > env; secrets stay
|
|
403
|
+
in `.env` / mounted OAuth. `.env.example`: note `.env` is the flat full-surface
|
|
404
|
+
form.
|
|
405
|
+
- deploy/helm/autonomi/values.yaml + deployment-controlplane.yaml +
|
|
406
|
+
deployment-worker.yaml: document mounting config as a ConfigMap volume +
|
|
407
|
+
`--config /etc/loki/config.yaml`; secrets stay in the existing
|
|
408
|
+
Secret/`existingSecret` (Vault-ready) path, referenced via `${VAR}`.
|
|
409
|
+
- autonomy/config.example.yaml: regenerate (or note it is now generated by
|
|
410
|
+
`config example`); drop the dead `compaction_interval`.
|
|
411
|
+
|
|
412
|
+
Version bump -- INTEGRATOR ONLY, single release commit (per CONTRIBUTING.md:120,
|
|
413
|
+
devs must NOT bump versions; merge-conflict avoidance). The canonical 14 locations
|
|
414
|
+
(references/deployment.md:610): VERSION, package.json, SKILL.md (header + footer),
|
|
415
|
+
Dockerfile, Dockerfile.sandbox, vscode-extension/package.json, CLAUDE.md,
|
|
416
|
+
dashboard/__init__.py, mcp/__init__.py, CHANGELOG.md, docs/INSTALLATION.md,
|
|
417
|
+
wiki/Home.md, wiki/_Sidebar.md, wiki/API-Reference.md. Plus 4-channel validation.
|
|
418
|
+
NOTE for the integrator: the documented 14-list and the OBSERVED v7.73.0 bump set
|
|
419
|
+
diverge -- v7.73.0 also touched plugins/loki-mode/.claude-plugin/plugin.json and
|
|
420
|
+
loki-ts/dist/loki.js (not in the documented 14), while the documented list includes
|
|
421
|
+
vscode-extension/package.json and wiki/API-Reference.md (which v7.73.0 did not
|
|
422
|
+
touch). Reconcile against the actual repo at release time rather than trusting
|
|
423
|
+
either list blindly.
|
|
424
|
+
|
|
425
|
+
--------------------------------------------------------------------------------
|
|
426
|
+
## 9. Phasing
|
|
427
|
+
|
|
428
|
+
- v1 (this plan, one release): `--config`/`--vars`/`--env-file` pre-pass; `.env` +
|
|
429
|
+
YAML + JSON; locked precedence; shared config-map.sh (pays down drift debt: 3
|
|
430
|
+
tables -> 1); `${VAR}` expansion + raw-secret warning; extracted
|
|
431
|
+
`validate_config_key`; `config example|schema|validate`; drift + mutation tests.
|
|
432
|
+
Satisfies "all flags configurable" via `.env` immediately.
|
|
433
|
+
- v2 (next release, ADDITIVE only -- never re-touch precedence): grow nested
|
|
434
|
+
friendly-key schema to the full ~250-flag surface; wire a real consumer for
|
|
435
|
+
`model.compaction_interval`; optionally unify settings.json map with
|
|
436
|
+
LOKI_CONFIG_MAP; optionally unify init/edit(YAML) vs set/get(settings.json).
|
|
437
|
+
|
|
438
|
+
--------------------------------------------------------------------------------
|
|
439
|
+
## 10. Critical files (verified line anchors, post-v7.73.0 working tree)
|
|
440
|
+
|
|
441
|
+
- autonomy/loki -- main() pre-pass insert after 15302/before 15309; session-gate
|
|
442
|
+
on the start/run/quick arms (15314+); cmd_start arg loop 1042 (add no-op
|
|
443
|
+
--config/--vars/--env-file arms); exec handoff 2097; cmd_config dispatch
|
|
444
|
+
8089-8137 (+3 subcommands); EXTRACT validators 8177-8244 -> validate_config_key.
|
|
445
|
+
- autonomy/run.sh -- load_config_file 228-259 (unchanged routing);
|
|
446
|
+
parse_simple_yaml 262-350 and parse_yaml_with_yq 419-504 (refactor to iterate
|
|
447
|
+
LOKI_CONFIG_MAP); set_from_yaml 389-416; validate_yaml_value 353-379;
|
|
448
|
+
_load_json_settings 522-566 (JSON safe-pattern reuse); secret patterns
|
|
449
|
+
_commit_scan_secret_file 6331-6382 (reuse for raw-secret check); AUTO_FIX clobber
|
|
450
|
+
625-627 (test caveat).
|
|
451
|
+
- autonomy/lib/config-map.sh -- NEW: canonical LOKI_CONFIG_MAP (64 keys) +
|
|
452
|
+
loki_maybe_apply_config_file + loki_apply_config_file + .env parser + ${VAR}
|
|
453
|
+
expander + format detect; side-effect-free on source.
|
|
454
|
+
- autonomy/config.example.yaml -- basis for generated `config example`; drop dead
|
|
455
|
+
compaction_interval.
|
|
456
|
+
- deploy/helm/autonomi/values.yaml + deployment-controlplane.yaml +
|
|
457
|
+
deployment-worker.yaml -- the envFrom injection that makes --config > env
|
|
458
|
+
necessary; doc/mount updates.
|
|
459
|
+
- tests/test-config-file.sh (NEW) + tests/run-all-tests.sh (register near :218).
|
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.79.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.79.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 QQ=Object.defineProperty;var ZQ=($)=>$;function zQ($,Q){this[$]=ZQ.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)QQ($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:zQ.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var h1={};b(h1,{lokiDir:()=>P,homeLokiDir:()=>i$,findRepoRootForVersion:()=>t$,REPO_ROOT:()=>g});import{resolve as a,dirname as r$}from"path";import{fileURLToPath as XQ}from"url";import{existsSync as R$}from"fs";import{homedir as KQ}from"os";function qQ(){let $=b1;for(let Q=0;Q<6;Q++){if(R$(a($,"VERSION"))&&R$(a($,"autonomy/run.sh")))return $;let Z=r$($);if(Z===$)break;$=Z}return a(b1,"..","..","..")}function t$($){let Q=$;for(let Z=0;Z<6;Z++){if(R$(a(Q,"VERSION"))&&R$(a(Q,"autonomy/run.sh")))return Q;let z=r$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function P(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function i$(){return a(KQ(),".loki")}var b1,g;var C=L(()=>{b1=r$(XQ(import.meta.url));g=qQ()});import{readFileSync as VQ}from"fs";import{resolve as JQ,dirname as UQ}from"path";import{fileURLToPath as WQ}from"url";function E$(){if(Q$!==null)return Q$;let $="7.
|
|
2
|
+
var QQ=Object.defineProperty;var ZQ=($)=>$;function zQ($,Q){this[$]=ZQ.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)QQ($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:zQ.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var h1={};b(h1,{lokiDir:()=>P,homeLokiDir:()=>i$,findRepoRootForVersion:()=>t$,REPO_ROOT:()=>g});import{resolve as a,dirname as r$}from"path";import{fileURLToPath as XQ}from"url";import{existsSync as R$}from"fs";import{homedir as KQ}from"os";function qQ(){let $=b1;for(let Q=0;Q<6;Q++){if(R$(a($,"VERSION"))&&R$(a($,"autonomy/run.sh")))return $;let Z=r$($);if(Z===$)break;$=Z}return a(b1,"..","..","..")}function t$($){let Q=$;for(let Z=0;Z<6;Z++){if(R$(a(Q,"VERSION"))&&R$(a(Q,"autonomy/run.sh")))return Q;let z=r$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function P(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function i$(){return a(KQ(),".loki")}var b1,g;var C=L(()=>{b1=r$(XQ(import.meta.url));g=qQ()});import{readFileSync as VQ}from"fs";import{resolve as JQ,dirname as UQ}from"path";import{fileURLToPath as WQ}from"url";function E$(){if(Q$!==null)return Q$;let $="7.79.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=UQ(WQ(import.meta.url)),Z=t$(Q);Q$=VQ(JQ(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var e$=L(()=>{C()});var g1={};b(g1,{runOrThrow:()=>HQ,run:()=>F,readStreamCapped:()=>m1,commandVersion:()=>BQ,commandExists:()=>f,ShellError:()=>$1,MAX_STDOUT_BYTES:()=>v1});async function m1($,Q=v1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:U}=await Z.read();if(K)break;if(!U)continue;if(q+=U.byteLength,q>Q){let J=U.byteLength-(q-Q);X+=z.decode(U.subarray(0,J),{stream:!0});break}X+=z.decode(U,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,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,U]=await Promise.all([m1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function HQ($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new $1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=GQ($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function GQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function BQ($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var v1=16777216,$1;var d=L(()=>{$1=class $1 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 YQ?"":$}var YQ,O,S,_,_Z,I,k,h,V;var c=L(()=>{YQ=(process.env.NO_COLOR??"").length>0;O=s("\x1B[0;31m"),S=s("\x1B[0;32m"),_=s("\x1B[1;33m"),_Z=s("\x1B[0;34m"),I=s("\x1B[0;36m"),k=s("\x1B[1m"),h=s("\x1B[2m"),V=s("\x1B[0m")});import{existsSync as jQ}from"fs";async function Z$(){if(Y$!==void 0)return Y$;let $="/opt/homebrew/bin/python3.12";if(jQ($))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 F([Z,"-c",$],Q)}var Y$;var V$=L(()=>{d()});var X0={};b(X0,{runStatus:()=>oQ});import{existsSync as y,readFileSync as U$,readdirSync as r1,statSync as t1}from"fs";import{resolve as D,basename as vQ}from"path";import{homedir as mQ}from"os";function i1($){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 e1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*N$/Q);if(X>N$)X=N$;let q=N$-X,K=S;if(z>=80)K=O;else if(z>=50)K=_;let U="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=i1($),W=i1(Q);return` ${k}${Z}${V} ${K}[${U}]${V} ${z}% (${J} / ${W})`}async function fQ(){if(await f("jq"))return!0;return process.stdout.write(`${O}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($Q),2}}s1();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=79AD90D8C0F4D86964756E2164756E21
|
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.79.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",
|