loki-mode 9.50.2 → 9.50.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,277 @@
1
+ # One-Run Completion Audit
2
+
3
+ Empirical audit of whether ONE RUN completes the job today. Read against
4
+ committed source at `bb73addb` (branch `main`). No files were changed; this
5
+ document is the only artifact.
6
+
7
+ The demand under audit: a user hands over a PR, an issue, or a spec, and the
8
+ run completes all development, testing, and release work and hands back
9
+ completion proof, with least cost and highest task value per dollar, without
10
+ back-and-forth or failing loops.
11
+
12
+ Verdict: a default run does reach working code, a session branch, quality
13
+ gates, and an Evidence Receipt without human input. It breaks the one-run
14
+ promise in five specific places, ranked below. Two of the five are hard stops
15
+ that wait on a person. Two are missing measurements, not defects. One is the
16
+ last mile of the deliverable.
17
+
18
+ ## Method notes
19
+
20
+ Every zero reported here carries a positive control, because an unproven zero
21
+ is an absent measurement rather than a finding. Controls are printed inline
22
+ with each finding. Claims are read against committed source, and each cited
23
+ function was read in full before it was judged.
24
+
25
+ Two mid-audit hypotheses were refuted by the source and are recorded in
26
+ "Refuted during this audit" so they are not re-raised.
27
+
28
+ ## 1. A blocked gate escalates to a PAUSE that waits forever, with no timeout and no tty guard
29
+
30
+ Highest user pain. This is the failing loop the demand names, and in
31
+ background mode nobody is watching it.
32
+
33
+ The escalation ladder defaults are `GATE_CLEAR_LIMIT=3`,
34
+ `GATE_ESCALATE_LIMIT=5`, `GATE_PAUSE_LIMIT=10` (`autonomy/run.sh:1513-1515`).
35
+ `gate_failure_disposition` (`autonomy/run.sh:11293-11302`) returns `pause`
36
+ once a gate's consecutive-failure count reaches 10.
37
+
38
+ For code review, that disposition does this (`autonomy/run.sh:24473-24477`):
39
+
40
+ ```
41
+ log_error "Gate escalation: code_review failed $cr_count times (>= $GATE_PAUSE_LIMIT) - forcing PAUSE for human intervention"
42
+ echo "PAUSE" > "${TARGET_DIR:-.}/.loki/signals/GATE_ESCALATION"
43
+ touch "${TARGET_DIR:-.}/.loki/PAUSE"
44
+ ```
45
+
46
+ `check_human_intervention` (`autonomy/run.sh:25460`) sees that file and calls
47
+ `handle_pause` (`autonomy/run.sh:25690`). The wait loop
48
+ (`autonomy/run.sh:25793-25818`) is:
49
+
50
+ ```
51
+ while [ "$PAUSED" = "true" ]; do
52
+ if [ -f "$loki_dir/STOP" ]; then ... return 1; fi
53
+ if [ ! -f "$loki_dir/PAUSE" ]; then PAUSED=false; break; fi
54
+ if read -t 1 -n 1 2>/dev/null; then rm -f "$loki_dir/PAUSE"; PAUSED=false; break; fi
55
+ sleep 1
56
+ done
57
+ ```
58
+
59
+ There is no timeout and no maximum wait. The loop exits only on a person
60
+ removing `.loki/PAUSE`, creating `.loki/STOP`, or pressing a key. With no TTY
61
+ attached (`--bg`, a container, a CI job) the `read` can never fire, so the run
62
+ spins on `sleep 1` indefinitely.
63
+
64
+ There is no non-interactive guard on this path. Positive control: `grep -c
65
+ "-t 0" autonomy/run.sh` returns `1`, so the grep does find tty checks in this
66
+ file; the single hit is `autonomy/run.sh:22411`, inside the unrelated
67
+ spec-contradiction fast-fail. `handle_pause` has none.
68
+
69
+ Scope it honestly: perpetual mode auto-clears PAUSE and continues
70
+ (`autonomy/run.sh:25470-25500`), except when the pause came from budget
71
+ enforcement. Default mode does not auto-clear.
72
+
73
+ ## 2. Three gates can terminate the run at exit 20, and that path never opens a PR
74
+
75
+ `_loki_gate_stuck` (`autonomy/run.sh:11098`, threshold
76
+ `LOKI_GATE_STUCK_THRESHOLD:-3`) compares a stable cause line across
77
+ consecutive failures. When the same gate fails for the same reason three
78
+ times, the run stops rather than grinding. It fires for three gates:
79
+
80
+ - static analysis, `autonomy/run.sh:24094-24101`
81
+ - mock integrity, `autonomy/run.sh:24220-24227`
82
+ - mutation integrity, `autonomy/run.sh:24266-24275`
83
+
84
+ Each does `save_state ... 20` and `return 20` out of `run_autonomous`.
85
+
86
+ Stopping a non-converging loop is correct behavior and better than grinding.
87
+ The one-run break is what the user is left holding: `on_run_complete`, the
88
+ function that opens the PR, is called only from the success exits
89
+ (`autonomy/run.sh:24803`, `:25025`, `:25661`). A `return 20` leaves
90
+ `run_autonomous` before reaching any of them, so no PR is opened. The
91
+ deliverable stays on the session branch and the user must discover and finish
92
+ it by hand.
93
+
94
+ This mirrors a deliberate decision elsewhere: the council force-stop path at
95
+ `autonomy/run.sh:24770` carries the comment "No on_run_complete: a force-stop
96
+ must never open a 'done' PR." The gate-stuck path inherits that outcome
97
+ without stating it.
98
+
99
+ ## 3. There is no cost-per-completed-task anywhere; only cost per iteration
100
+
101
+ This is a missing measurement, reported plainly rather than invented.
102
+
103
+ The per-iteration writer is `autonomy/run.sh:8303-8318`, emitting
104
+ `.loki/metrics/efficiency/iteration-N.json` with `cost_usd` at
105
+ `autonomy/run.sh:8315`.
106
+
107
+ The only aggregation that divides cost by anything is
108
+ `autonomy/loki:6447`:
109
+
110
+ ```
111
+ 'avg_cost_per_iteration': round(total_cost / iteration_count, 2) if iteration_count > 0 else 0,
112
+ ```
113
+
114
+ plus the same division at `autonomy/loki:6518`. Both denominators are
115
+ iterations, not completed tasks.
116
+
117
+ The sharpest evidence that the metric was never built: in one script,
118
+ `total_cost` is computed at `autonomy/loki:28461` and `tasks_completed` is
119
+ emitted at `autonomy/loki:28514` and printed at `autonomy/loki:28557`, about
120
+ fifty lines apart, with no division between them. Both numbers are in hand at
121
+ the same moment and are never combined.
122
+
123
+ Searched two ways. Positive control first: `grep -c "cost_usd" autonomy/loki`
124
+ returns `19`, so the file and the pattern style both resolve.
125
+
126
+ - Division by any completed or task count, across `autonomy/loki`,
127
+ `autonomy/run.sh`, `dashboard/*.py`: only the two
128
+ `total_cost / iteration_count` hits above.
129
+ - Identifier search for `per_task`, `per-task`, `cost_per`, `per_completed`
130
+ across `autonomy/`, `dashboard/*.py`, `loki-ts/src/`: only
131
+ `avg_cost_per_iteration` at `autonomy/loki:6447`.
132
+
133
+ An iteration is not a unit of delivered value. A run that solves the task in
134
+ two iterations and one that thrashes for twenty both report a healthy average
135
+ cost per iteration.
136
+
137
+ ## 4. There is no task-value-per-dollar metric
138
+
139
+ Zero hits, with a positive control, searched two ways.
140
+
141
+ Positive control: `grep -rln "cost_usd" autonomy/` resolves to
142
+ `autonomy/loki`, `autonomy/run.sh`, `autonomy/context-tracker.py`, so the
143
+ recursive search reaches these trees.
144
+
145
+ - Case-insensitive `value.per.dollar`, `value_per_dollar`, `valuePerDollar`
146
+ across `autonomy/`, `dashboard/`, `memory/`, `loki-ts/src/`: 0 hits.
147
+ - Identifier search for `task_value`, `value_score`, `roi` across
148
+ `autonomy/`, `dashboard/`: no metric definition. Hits were unrelated
149
+ substrings (a template line in `autonomy/quickstart.sh:136`, bundled
150
+ `mermaid.min.js`).
151
+
152
+ Nothing computes value per dollar, and nothing defines task value as a
153
+ quantity. The closest artifact is the productivity report at
154
+ `autonomy/loki:28535-28545`, which estimates "time saved" as
155
+ `total_iterations x 15 minutes`. That is a fixed multiplier applied to
156
+ iteration count, not a measure of delivered value, and it rises with a run
157
+ that iterates more.
158
+
159
+ ## 5. On the headline issue use case, the PR rests entirely on one default-on guard chain, and the teardown then tells the user to open a PR that already exists
160
+
161
+ The back-and-forth is not conversational. The agent almost never stops to ask
162
+ a question mid-run.
163
+
164
+ Searched for blocking interactive prompts two ways. Only two exist in the CLI,
165
+ both in `cmd_config_init` (`autonomy/loki:12652`): `read -p "Choice [1]: "` at
166
+ `autonomy/loki:12669`, and `autonomy/loki:12719`, which already auto-confirms
167
+ under `LOKI_AUTO_CONFIRM`, `CI`, or a non-TTY stdin. Neither is on the
168
+ `loki start` build path. The `LOKI_PROMPT_INJECTION` handling in
169
+ `check_human_intervention` is default-off and consumes input rather than
170
+ requesting it (`autonomy/run.sh:25557-25565`).
171
+
172
+ The real issue is on the founder's named use case, `loki start
173
+ owner/repo#123`. With no flags, `create_pr` and `use_worktree` both stay false:
174
+ they are set only by `--pr`, `--ship`, `--prepare-pr`, `--worktree`, or
175
+ `--detach` (`autonomy/loki:2602`, `:2616`, `:10330`, `:10383`). So the issue
176
+ path's own PR block at `autonomy/loki:10762` (`if $create_pr;`) never executes
177
+ on a bare no-flag run.
178
+
179
+ The PR therefore rests entirely on one other path: `on_run_complete`
180
+ (`autonomy/run.sh:5246`), which is default ON (`LOKI_DELEGATE_PR:-1`,
181
+ `autonomy/run.sh:5259`) and is called from the success exits
182
+ (`autonomy/run.sh:24803`, `:25025`, `:25661`). Its guard chain does hold on a
183
+ normal run: `GITHUB_PR:-false` does not trigger the early return at
184
+ `autonomy/run.sh:5263`; branch protection is on by default
185
+ (`autonomy/run.sh:8922`) and `setup_agent_branch` is called unconditionally in
186
+ `main()` (`autonomy/run.sh:26681`) before `run_autonomous` at
187
+ `autonomy/run.sh:26783`, minting `loki/session-<ts>-<pid>`
188
+ (`autonomy/run.sh:8984-8999`), so the non-default-branch guard at
189
+ `autonomy/run.sh:5296-5298` passes.
190
+
191
+ But every remaining link is a silent no-op. Missing `gh`
192
+ (`autonomy/run.sh:5279`), failing `gh auth status` (`autonomy/run.sh:5282`),
193
+ or a non-GitHub remote (`autonomy/run.sh:5287-5290`) each `return 0` with no
194
+ log line at all. On the headline use case the deliverable is the PR, and three
195
+ environment conditions can remove it without saying so.
196
+
197
+ Then the teardown contradicts itself. `create_session_pr` runs later, from
198
+ `main()` at `autonomy/run.sh:26956`, after the in-loop `on_run_complete` has
199
+ already opened the PR. It reaches `autonomy/run.sh:9175`:
200
+
201
+ ```
202
+ if [ "${LOKI_AUTO_PR:-0}" != "1" ]; then
203
+ print_pr_advice "$base" "$branch_name"
204
+ ...
205
+ return 0
206
+ fi
207
+ ```
208
+
209
+ `print_pr_advice` (`autonomy/lib/git-pr-advisory.sh:69-111`) takes only base,
210
+ head, and dir. It consults nothing about an existing PR and unconditionally
211
+ prints "To open a pull request: git push -u origin ...". Note that
212
+ `_loki_persist_pr_url` writes the opened PR url to `.loki/state/pr-url.txt`
213
+ (`autonomy/run.sh:5242`), and repo-wide the only other references are in
214
+ `tests/test-issue-to-pr-action.sh`: nothing in the advice path reads it.
215
+
216
+ So a successful run opens the PR, folds its url into the completion summary
217
+ via `build_completion_summary` (`autonomy/run.sh:4644`, reached through
218
+ `emit_completion_summary` at `autonomy/run.sh:5064`, called at `:24804` and
219
+ `:25026`), and then prints instructions to create the PR it just created. That
220
+ is a contradictory instruction at the moment the user is deciding whether the
221
+ job is done.
222
+
223
+ The same hand-back shape appears on the pause path: the guidance written into
224
+ `PAUSED.md` (`autonomy/run.sh:25778`) tells the user to read the findings file,
225
+ fix what it names, and resume.
226
+
227
+ ## What works, and should not be disturbed
228
+
229
+ Stated so the defect list is not mistaken for a verdict on the whole system.
230
+
231
+ - The Evidence Receipt is automatic, not a second command. `loki proof` is an
232
+ inspection surface; generation happens in-run via `generate_proof_of_run`
233
+ (`autonomy/run.sh:7926`), default on (`LOKI_PROOF:-1`), called from
234
+ `main()` at `autonomy/run.sh:26811`, `:26895`, `:26980` and from `cleanup()`
235
+ at `autonomy/run.sh:26016`.
236
+ - That receipt survives a gate-stuck exit 20. `autonomy/run.sh:26783` is
237
+ `run_autonomous "$PRD_PATH" || result=$?`, which catches the 20, and the
238
+ zombie-receipt guard at `autonomy/run.sh:26802-26812` generates the proof
239
+ immediately after the loop returns.
240
+ - PRs that are opened carry the receipt inline: `autonomy/run.sh:5331` and
241
+ `:9257`, plus the issue path at `autonomy/loki:10779-10793`.
242
+ - The receipt is independently re-checkable, which is the substantive answer to
243
+ "does the user get proof they can verify." `cmd_verify` (`autonomy/loki:18131`)
244
+ has a `--fast` path through `lib/fast_verify.py` that runs only exogenous,
245
+ deterministic checks with no model call and no network, so any third party
246
+ with the same commit re-derives the same verdict; the deeper route is
247
+ `autonomy/verify.sh`. `loki proof verify <id>` (`autonomy/loki:36196`)
248
+ re-checks a receipt for tamper and drift, with `--jwks` for attestation
249
+ against a published key set.
250
+ - The stuck-gate valve itself is sound. `_loki_gate_stuck` skips the static
251
+ banner and compares the first real cause line
252
+ (`autonomy/run.sh:11132-11145`), so a run making genuine progress through
253
+ different findings is not misread as stuck.
254
+
255
+ ## Refuted during this audit
256
+
257
+ Recorded so neither is raised again.
258
+
259
+ - "`enforce_mock_integrity || true` at `autonomy/run.sh:24187` discards the
260
+ BLOCK." False. The verdict travels via the global
261
+ `_LOKI_MOCK_INTEGRITY_STATUS`, read on the next line
262
+ (`autonomy/run.sh:24188`); the `fail` arm runs `track_gate_failure`,
263
+ escalation guidance, and the `_loki_gate_stuck` abort
264
+ (`autonomy/run.sh:24190-24228`). The `|| true` only prevents `set -e` from
265
+ killing the script.
266
+ - "The receipt is lost on the gate-stuck path." False, refuted by
267
+ `autonomy/run.sh:26783` and `:26810-26812` as described above.
268
+
269
+ ## Ranked summary
270
+
271
+ | # | Break | Evidence | Pain |
272
+ |---|---|---|---|
273
+ | 1 | Gate escalation forces a PAUSE that waits forever, no timeout, no tty guard | `run.sh:24473-24477`, `run.sh:25793-25818`, defaults `run.sh:1513-1515` | Run stalls silently in `--bg`; the failing loop the demand names |
274
+ | 2 | Gate-stuck exit 20 ends the run without opening a PR | `run.sh:11098`, `:24101`, `:24227`, `:24275`; PR only at `:24803`, `:25025`, `:25661` | Work exists on a branch the user must find and finish |
275
+ | 3 | No cost-per-completed-task; only per-iteration | `loki:6447`, `:6518`; cost `loki:28461` and tasks `loki:28514` never divided | "Least cost per task" is unmeasurable today |
276
+ | 4 | No task-value-per-dollar metric | 0 hits with positive control; `loki:28535-28545` is a fixed 15-min multiplier | The demand's headline metric does not exist |
277
+ | 5 | No-flag issue run never uses its own PR block; PR depends on a guard chain with three silent no-ops, then teardown prints advice for the PR it already opened | `loki:10762` unreached (`loki:2602`, `:10330`); `run.sh:5279`, `:5282`, `:5287-5290`; `run.sh:9175` + `git-pr-advisory.sh:69-111` | Deliverable can vanish silently on the headline use case; contradictory closing instruction |
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var ht=Object.create;var{getPrototypeOf:gt,defineProperty:jG,getOwnPropertyNames:mt}=Object;var ut=Object.prototype.hasOwnProperty;function dt($){return this[$]}var pt,ct,lt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?pt??=new WeakMap:ct??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?ht(gt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of mt($))if(!ut.call(J,q))jG(J,q,{get:dt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var it=($)=>$;function at($,X){this[$]=it.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:at.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var HR={};B1(HR,{lokiDir:()=>h0,homeLokiDir:()=>GQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as ot}from"url";import{existsSync as Zq}from"fs";import{homedir as st}from"os";function nt(){let $=UR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(UR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function GQ(){return h2(st(),".loki")}var UR,L1;var k1=s(()=>{UR=LG(ot(import.meta.url));L1=nt()});import{readFileSync as rt}from"fs";import{resolve as tt,dirname as et}from"path";import{fileURLToPath as $e}from"url";function j9(){if(n3!==null)return n3;let $="9.50.2";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=et($e(import.meta.url)),Q=AG(X);n3=rt(tt(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var jR={};B1(jR,{runOrThrow:()=>Me,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>je,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>OR});async function Jq($,X=OR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function Me($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Oe($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Oe($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function je($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var OR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Le?"":$}var Le,p0,$5,q1,F61,A1,f1,m5,r;var t7=s(()=>{Le=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),q1=g2("\x1B[1;33m"),F61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),r=g2("\x1B[0m")});import{existsSync as Ee}from"fs";async function Z2(){if(BQ!==void 0)return BQ;let $="/opt/homebrew/bin/python3.12";if(Ee($))return BQ=$,$;let X=await g5("python3.12");if(X)return BQ=X,X;let Q=await g5("python3");return BQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var BQ;var m2=s(()=>{y8()});var hR={};B1(hR,{runStatus:()=>$00});import{existsSync as u5,readFileSync as C9,readdirSync as xR,statSync as kR}from"fs";import{resolve as A5,basename as le}from"path";import{homedir as ie}from"os";function SR($){let X=Math.trunc($);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function yR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=q1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=SR($),Y=SR(X);return` ${f1}${Q}${r} ${J}[${q}]${r} ${z}% (${V} / ${Y})`}async function oe(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${r}
2
+ var ht=Object.create;var{getPrototypeOf:gt,defineProperty:jG,getOwnPropertyNames:mt}=Object;var ut=Object.prototype.hasOwnProperty;function dt($){return this[$]}var pt,ct,lt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?pt??=new WeakMap:ct??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?ht(gt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of mt($))if(!ut.call(J,q))jG(J,q,{get:dt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var it=($)=>$;function at($,X){this[$]=it.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:at.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var HR={};B1(HR,{lokiDir:()=>h0,homeLokiDir:()=>GQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as ot}from"url";import{existsSync as Zq}from"fs";import{homedir as st}from"os";function nt(){let $=UR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(UR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function GQ(){return h2(st(),".loki")}var UR,L1;var k1=s(()=>{UR=LG(ot(import.meta.url));L1=nt()});import{readFileSync as rt}from"fs";import{resolve as tt,dirname as et}from"path";import{fileURLToPath as $e}from"url";function j9(){if(n3!==null)return n3;let $="9.50.3";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=et($e(import.meta.url)),Q=AG(X);n3=rt(tt(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var jR={};B1(jR,{runOrThrow:()=>Me,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>je,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>OR});async function Jq($,X=OR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function Me($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Oe($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Oe($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function je($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var OR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Le?"":$}var Le,p0,$5,q1,F61,A1,f1,m5,r;var t7=s(()=>{Le=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),q1=g2("\x1B[1;33m"),F61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),r=g2("\x1B[0m")});import{existsSync as Ee}from"fs";async function Z2(){if(BQ!==void 0)return BQ;let $="/opt/homebrew/bin/python3.12";if(Ee($))return BQ=$,$;let X=await g5("python3.12");if(X)return BQ=X,X;let Q=await g5("python3");return BQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var BQ;var m2=s(()=>{y8()});var hR={};B1(hR,{runStatus:()=>$00});import{existsSync as u5,readFileSync as C9,readdirSync as xR,statSync as kR}from"fs";import{resolve as A5,basename as le}from"path";import{homedir as ie}from"os";function SR($){let X=Math.trunc($);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function yR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=q1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=SR($),Y=SR(X);return` ${f1}${Q}${r} ${J}[${q}]${r} ${z}% (${V} / ${Y})`}async function oe(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${r}
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)
@@ -1337,4 +1337,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1337
1337
  `),2}case"start":{let{runStart:z}=await Promise.resolve().then(() => (ft(),bt));return z(Q)}default:return process.stderr.write(`Unknown command: ${X}
1338
1338
  `),process.stderr.write(_t),2}}PR();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var J61=await K61(Bun.argv.slice(2));process.exit(J61);
1339
1339
 
1340
- //# debugId=DCDDCB6951FA867C7A41715C9E3207BA
1340
+ //# debugId=6847A0F1DFFA16BD15E556FA99A4D261
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '9.50.2'
78
+ __version__ = '9.50.3'
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": "9.50.2",
4
+ "version": "9.50.3",
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, opencode).",
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": "9.50.2",
5
+ "version": "9.50.3",
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",
@@ -36,6 +36,8 @@
36
36
  | Plan deepening, knowledge extraction | `compound-learning.md` |
37
37
  | Managed Agents memory, multiagent council, flag hierarchy | `memory.md` |
38
38
  | Non-trivial change (>3 files, agent runtime, MINOR/MAJOR release) | `sdlc-fleet.md` |
39
+ | Several agent teams running at once, file ownership, WIP limits | `factory-operations.md` |
40
+ | Cutting a release, version bump, publish verification | `release-cadence.md` |
39
41
  | Adding your own gate/reviewer/agent to the loop | `extending.md` |
40
42
 
41
43
  ## Module Descriptions
@@ -169,6 +171,19 @@ parity change). Binding per CLAUDE.md, so this index must be able to route to it
169
171
  - Unanimous 3-of-3 APPROVE required from the review council
170
172
  - Skip rules: typo fixes, docs-only edits, reverts, emergency hotfixes
171
173
 
174
+ ### factory-operations.md
175
+ **When:** More than one agent team is working the repo at the same time
176
+ - Roles and the DECIDES vs ADVISES split (CoS, PM, Dev Fleet, SDET, Council, Release Captain)
177
+ - Per-file ownership: the lead never edits a file an agent owns
178
+ - WIP limits, and what a blocked team does instead of waiting
179
+ - The spine: a measurement that cannot see what it claims to measure is worse than none
180
+
181
+ ### release-cadence.md
182
+ **When:** Taking a change from merged to published
183
+ - The fast tier is the release gate; the full tier is not a blocker
184
+ - VERSION must be the push HEAD or the release workflow never fires
185
+ - Verify the artifact a user installs, never a green job or an exit code
186
+
172
187
  ### extending.md
173
188
  **When:** Adding a custom reviewer/agent to the loop without editing engine files
174
189
  - The live seam: `loki agent install` -> `.loki/agents/installed.json`