loki-mode 9.54.0 → 9.55.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.
Files changed (129) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/completion-council.sh +120 -35
  5. package/autonomy/crash.sh +6 -6
  6. package/autonomy/lib/done-recognition.sh +7 -0
  7. package/autonomy/lib/prd-enrich.sh +7 -0
  8. package/autonomy/lib/proof-generator.py +36 -5
  9. package/autonomy/lib/proof-verify.py +93 -21
  10. package/autonomy/lib/voter-agents.sh +7 -0
  11. package/autonomy/loki +404 -119
  12. package/autonomy/quickstart.sh +7 -0
  13. package/autonomy/run.sh +836 -58
  14. package/autonomy/verify.sh +114 -8
  15. package/bin/loki +12 -0
  16. package/dashboard/__init__.py +1 -1
  17. package/dashboard/api_v2.py +4 -0
  18. package/dashboard/audit.py +9 -1
  19. package/dashboard/auth.py +16 -4
  20. package/dashboard/registry.py +34 -12
  21. package/dashboard/rigour_integration.py +25 -17
  22. package/dashboard/server.py +252 -104
  23. package/dashboard/static/cost.html +55 -26
  24. package/dashboard/static/index.html +475 -1544
  25. package/dashboard/static/proofs.html +1 -1
  26. package/docs/INSTALLATION.md +7 -6
  27. package/docs/air-gapped.md +38 -11
  28. package/docs/enterprise/security.md +10 -13
  29. package/docs/environment-variables.md +1 -0
  30. package/docs/exit-codes.md +76 -2
  31. package/docs/image-provenance.md +4 -2
  32. package/docs/v10/BACKLOG.md +64 -2
  33. package/docs/v10/BOARD.md +134 -0
  34. package/docs/v10/CONTROL.md +36 -0
  35. package/docs/v10/DECISIONS.md +102 -0
  36. package/docs/v10/METRICS.md +78 -1
  37. package/docs/v10/PROGRESS.md +240 -0
  38. package/docs/v10/SWARM-PROMPT.md +98 -0
  39. package/docs/v10/SWARM.md +237 -0
  40. package/loki-ts/dist/loki.js +7 -4
  41. package/mcp/__init__.py +1 -1
  42. package/package.json +1 -1
  43. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  44. package/providers/aider.sh +8 -0
  45. package/providers/cline.sh +8 -0
  46. package/providers/codex.sh +9 -0
  47. package/providers/loader.sh +10 -0
  48. package/providers/opencode.sh +9 -0
  49. package/tools/receipt-attest.py +1 -1
  50. package/web-app/dist/assets/AdminPage-CZrBkzRF.js +32 -0
  51. package/web-app/dist/assets/Avatar-gQIp6FBI.js +11 -0
  52. package/web-app/dist/assets/{Badge-BF3mhT57.js → Badge-D2_fBTFY.js} +1 -1
  53. package/web-app/dist/assets/{Button-BDp9aJ9q.js → Button-D5qDd2ef.js} +1 -1
  54. package/web-app/dist/assets/CockpitPage-DVMk2RUK.js +47 -0
  55. package/web-app/dist/assets/ComparePage-NMLyE1xL.js +1 -0
  56. package/web-app/dist/assets/{ErrorBoundary-ut3wrUmu.js → ErrorBoundary-BRABI-BT.js} +1 -1
  57. package/web-app/dist/assets/EvidenceReceiptPanel-D2LNKqUx.js +1 -0
  58. package/web-app/dist/assets/GitHubIssuesPanel-BC5GzKUN.js +7 -0
  59. package/web-app/dist/assets/{GitHubPRsPanel-DurZgSaj.js → GitHubPRsPanel-BSlmoVTS.js} +3 -3
  60. package/web-app/dist/assets/HomePage-D_CDMoen.js +12 -0
  61. package/web-app/dist/assets/{LoginPage-HeRGJeZy.js → LoginPage-BBNA_N9t.js} +1 -1
  62. package/web-app/dist/assets/{MagicPage-CUKrqsZs.js → MagicPage-CnFRZCMx.js} +1 -1
  63. package/web-app/dist/assets/MetricsPage-CPfLMRlh.js +6 -0
  64. package/web-app/dist/assets/{NotFoundPage-BZDZ-B77.js → NotFoundPage-lA0XSGTz.js} +1 -1
  65. package/web-app/dist/assets/ProjectPage-CNAPbr9z.js +256 -0
  66. package/web-app/dist/assets/ProjectsPage-CMtm8goN.js +6 -0
  67. package/web-app/dist/assets/SettingsPage-BhIjVMYE.js +10 -0
  68. package/web-app/dist/assets/{ShowcasePage-BGS2jTGH.js → ShowcasePage-BndtGeYo.js} +1 -1
  69. package/web-app/dist/assets/SystemSettingsPage-euXI-KXE.js +1 -0
  70. package/web-app/dist/assets/TeamsPage-ClUKecun.js +11 -0
  71. package/web-app/dist/assets/TemplatesPage-BWHWVk0L.js +29 -0
  72. package/web-app/dist/assets/{TerminalOutput-togzv9Zl.js → TerminalOutput-Cv3N3Szf.js} +1 -1
  73. package/web-app/dist/assets/{activity-Dreyb_Kr.js → activity-dEhz-bXR.js} +1 -1
  74. package/web-app/dist/assets/{bell-DWH0drES.js → bell-B3XUMq1t.js} +1 -1
  75. package/web-app/dist/assets/{bot-78MoCuDl.js → bot-X-ssNlwR.js} +1 -1
  76. package/web-app/dist/assets/{check-ikfylPst.js → check-C_9Nx7kb.js} +1 -1
  77. package/web-app/dist/assets/{chevron-left-DXUqkd8J.js → chevron-left-tvNswo3A.js} +1 -1
  78. package/web-app/dist/assets/{circle-alert-Da74VO8P.js → circle-alert-CSB88nEs.js} +1 -1
  79. package/web-app/dist/assets/{clock-mlGCFq8l.js → clock-C8p2e4NI.js} +1 -1
  80. package/web-app/dist/assets/{cloud-B_Y5ZrIO.js → cloud-CKp2ZvhG.js} +1 -1
  81. package/web-app/dist/assets/{code-xml-Bbom7YWV.js → code-xml-dY70LCHD.js} +1 -1
  82. package/web-app/dist/assets/{database-CLcofiVp.js → database-DObn5fmn.js} +1 -1
  83. package/web-app/dist/assets/{dollar-sign-C5mkjXIM.js → dollar-sign-DSu6n3K3.js} +1 -1
  84. package/web-app/dist/assets/{file-code-corner-CVrkaOQP.js → file-code-corner-DpCnS73z.js} +1 -1
  85. package/web-app/dist/assets/{file-plus-BV9240gX.js → file-plus-D3IPKCf1.js} +1 -1
  86. package/web-app/dist/assets/{globe-aZ0Zso4I.js → globe-C9QXkgH_.js} +1 -1
  87. package/web-app/dist/assets/{hammer-EgOQNukG.js → hammer-D_FtWe1y.js} +1 -1
  88. package/web-app/dist/assets/index-CI54rara.css +1 -0
  89. package/web-app/dist/assets/{index-DvZ0Kk2x.js → index-Cz7q1bvC.js} +2 -2
  90. package/web-app/dist/assets/{layers-BOVKmepE.js → layers-DsW7jnJX.js} +1 -1
  91. package/web-app/dist/assets/{loader-circle-0K8yLgHl.js → loader-circle-B3HspjKR.js} +1 -1
  92. package/web-app/dist/assets/{lock-XzlDMy8M.js → lock-DSI29pct.js} +1 -1
  93. package/web-app/dist/assets/{package-BtXTKusI.js → package-CLNxtA2x.js} +1 -1
  94. package/web-app/dist/assets/{plus-PxeiD8ju.js → plus-DyI4WC4_.js} +1 -1
  95. package/web-app/dist/assets/{refresh-cw-CjqzYczt.js → refresh-cw-D2dv0xA0.js} +1 -1
  96. package/web-app/dist/assets/{rotate-ccw-CY0os4DH.js → rotate-ccw-CBvihA-a.js} +1 -1
  97. package/web-app/dist/assets/scroll-text-DvoOvAl_.js +6 -0
  98. package/web-app/dist/assets/{server-pIIs9vNp.js → server-DenWvSUt.js} +1 -1
  99. package/web-app/dist/assets/{shield-alert-CsDqKXwA.js → shield-alert-40cX_FvG.js} +1 -1
  100. package/web-app/dist/assets/{thumbs-up-CAh3HVzH.js → thumbs-up-BrjZ6QuV.js} +12 -7
  101. package/web-app/dist/assets/trash-2-C6evwkEH.js +16 -0
  102. package/web-app/dist/assets/{trending-down-DrtBd_C9.js → trending-down-CfbZis3O.js} +1 -1
  103. package/web-app/dist/assets/{trending-up-D-DbCHXm.js → trending-up-DWuEK3WC.js} +1 -1
  104. package/web-app/dist/assets/{upload-D4l6DCOt.js → upload-B9DFBcPG.js} +1 -1
  105. package/web-app/dist/assets/{usePolling-D0aoTX80.js → usePolling-BryVC-T1.js} +1 -1
  106. package/web-app/dist/assets/{user-BQf_XJO_.js → user-DMRXwZiw.js} +1 -1
  107. package/web-app/dist/index.html +2 -2
  108. package/web-app/server.py +122 -47
  109. package/web-app/dist/assets/AdminPage-y6_b2Xo4.js +0 -32
  110. package/web-app/dist/assets/Avatar-BFPkXhIJ.js +0 -6
  111. package/web-app/dist/assets/CockpitPage-5TKICCoA.js +0 -47
  112. package/web-app/dist/assets/ComparePage-BSuNsUHy.js +0 -1
  113. package/web-app/dist/assets/EvidenceReceiptPanel-CcaBwEI-.js +0 -1
  114. package/web-app/dist/assets/GitHubIssuesPanel-DJeYgzAa.js +0 -12
  115. package/web-app/dist/assets/HomePage-Bs451LAy.js +0 -28
  116. package/web-app/dist/assets/MetricsPage-DvY1csgz.js +0 -6
  117. package/web-app/dist/assets/ProjectPage-CqHkEZkl.js +0 -257
  118. package/web-app/dist/assets/ProjectsPage-CLvCK1Y8.js +0 -6
  119. package/web-app/dist/assets/SettingsPage-DP7OmdwP.js +0 -10
  120. package/web-app/dist/assets/SystemSettingsPage-P_rlLh0x.js +0 -1
  121. package/web-app/dist/assets/TeamsPage-DxtBDcBC.js +0 -11
  122. package/web-app/dist/assets/TemplatesPage-5e-faH3W.js +0 -29
  123. package/web-app/dist/assets/folder-open-D_6yV78G.js +0 -11
  124. package/web-app/dist/assets/index-BSgmPwC0.css +0 -1
  125. package/web-app/dist/assets/lightbulb-DdASgqOG.js +0 -6
  126. package/web-app/dist/assets/mail-COu670_W.js +0 -6
  127. package/web-app/dist/assets/minus-bQpUt9ff.js +0 -6
  128. package/web-app/dist/assets/save-v8qep5cY.js +0 -6
  129. package/web-app/dist/assets/trash-2-Dt6urTyY.js +0 -6
@@ -0,0 +1,237 @@
1
+ # The swarm
2
+
3
+ Adopted 2026-09-26, superseding the one-cycle-at-a-time loop in `docs/v10/PROGRESS.md`
4
+ for everything after cycle 4. Goal: 10-20x more releases per day than cycles
5
+ 1-4 (about 3 releases in 18 hours), without weakening anything in
6
+ `docs/LOKI-10-BUILD-PROMPT.md` sections 2, 5b or 5c, or any global "never do"
7
+ rule. Full text of the founder's prompt: `docs/v10/SWARM-PROMPT.md`.
8
+
9
+ ## Why cycles were slow
10
+
11
+ Each cycle batched 3-6 slices into one release, reviewed the whole batch in
12
+ serial council rounds (cycle 1 took 9 rounds, cycle 3 took 6, cycle 4's P7
13
+ property was found falsely PROVEN in three consecutive rounds before a fourth
14
+ caught it), then verified every channel before the next cycle started. Every
15
+ release waited on the slowest slice and on every round.
16
+
17
+ ## Changes from the swarm prompt, and why (see DECISIONS.md D12, D13)
18
+
19
+ - **D12: unanimous APPROVE, not "3 of 4".** The founder's prompt scored
20
+ HIGH-tier review as "3 of 4 approve". CLAUDE.md's SDLC fleet pattern
21
+ requires unanimous APPROVE and a full re-run on any CONCERN or REJECT.
22
+ Cycle-4 round 3 was APPROVE, APPROVE, CONCERN, and the concern was a real
23
+ false PROVEN; a vote-count rule would have shipped it. A HIGH-tier round may
24
+ add a fourth adversarial reviewer for extra recall, but any reviewer's
25
+ blocking finding with a working reproduction blocks the slice regardless of
26
+ the count, and the whole council re-runs after the fix.
27
+ - **D13: every review or council agent pins its model explicitly.** Round 4
28
+ of the cycle-4 council was launched with two of three lenses left to
29
+ inherit the session model; the session model changed between launch and
30
+ the lenses resolving, so a round meant to run 2 Opus + 1 Sonnet in fact ran
31
+ 3 Sonnet-5 lenses. It was killed and relaunched with every lens pinned.
32
+ Every council, review or HIGH-tier script from here on sets `model`
33
+ explicitly; composition is enforced by the script, never assumed from the
34
+ session.
35
+ - **Section 5c.5 (single writer) still governs.** One agent, the Release
36
+ Captain, writes `main`, `VERSION`, tags and publishes. Builders are each a
37
+ single writer on their own declared file set, never on `main`. Parallel
38
+ reviewers are read-only. This is what the swarm's role split already is;
39
+ it is restated here because the swarm looks superficially like
40
+ peer-to-peer and it is not.
41
+ - **`scripts/release.sh` is stale**, covering only `VERSION`,
42
+ `package.json`, `vscode-extension/package.json`. The Captain does not run
43
+ it as-is; the Captain follows CLAUDE.md's 14-file release checklist
44
+ directly (see Captain brief below), and fixing `scripts/release.sh` to
45
+ match is a queued LOW slice (BOARD).
46
+ - **Budget:** the founder was asked whether to size wave 1 to a conservative
47
+ estimate or launch at planned scale without a live cost feed, given
48
+ cycle-5 alone had already run about 3.3M subagent output tokens before the
49
+ swarm started. Founder chose full scale. METRICS.md still records a
50
+ labelled *estimate* per wave (subagent token counts from workflow
51
+ journals, not exact billing) so spend stays visible even though it does
52
+ not gate wave size. If a wave's estimate would cross a plausible reading of
53
+ founder-queue item 3, the swarm drops to 3 builders, keeps shipping LOW
54
+ slices, and adds a founder-queue line asking for a clearer budget signal.
55
+
56
+ ## Roles
57
+
58
+ All roles are agents spawned through `Workflow`, run in git worktrees for
59
+ isolation except the Product Owner and Release Captain, which touch shared
60
+ state files and must serialize.
61
+
62
+ ### 1. Product Owner (PO)
63
+
64
+ One agent per wave planning step (re-spawned fresh each time it is needed;
65
+ it writes `BACKLOG.md` and `BOARD.md`, never code). Ranks the backlog per
66
+ build-prompt section 3 (moat and red-main first, then metric-moved / effort).
67
+ Cuts work into release-sized slices:
68
+
69
+ - under about 300 changed lines;
70
+ - one owner;
71
+ - a declared file set that overlaps no other in-flight slice (hot files and
72
+ generated bundles are never in a builder's file set: see "Hot files"
73
+ below);
74
+ - acceptance checks written before building (the Wall, section 5c.1);
75
+ - a risk tier (HIGH / MEDIUM / LOW, see Reviewers).
76
+
77
+ Keeps roughly twice the builder count of ready slices queued.
78
+
79
+ ### 2. Builders
80
+
81
+ 6-10 in parallel, `isolation: "worktree"`, one slice each, single writer on
82
+ that slice. Each builder:
83
+
84
+ - runs `git merge --ff-only <captain's current main sha>` first (worktrees
85
+ cut from origin/main, not local main: see memory
86
+ `feedback-worktree-agents-base-branch`);
87
+ - implements with red-then-green tests;
88
+ - runs diff-scoped tests plus `bash tests/moat/run.sh` in its own worktree;
89
+ - never touches `main`, `VERSION`, or any hot file or bundle listed below;
90
+ - returns BLOCKED (never guesses past its file set) if the slice needs a
91
+ file outside its declared set, so the PO can re-slice.
92
+
93
+ ### 3. Reviewers
94
+
95
+ Read-only, parallel, diff-scoped, tiered by risk. Every finding needs a
96
+ reproduction (file:line, a concrete failing scenario, the fix). Models are
97
+ always pinned explicitly (D13):
98
+
99
+ - **HIGH** (verifier, Seal, gates, council, exit codes, auth, secrets,
100
+ `tests/moat`, anything touching the trust core): 3 reviewers with
101
+ different lenses (correctness, security/bypass, fail-closed) plus 1
102
+ adversarial reviewer told to find a false pass. All 4 pinned to `opus`
103
+ except the adversarial slot, which may run `sonnet` for a genuinely
104
+ different read. **Unanimous APPROVE required (D12).** Any blocking finding
105
+ with a reproduction blocks regardless of the other votes; after a fix,
106
+ the whole HIGH council re-runs from round 1.
107
+ - **MEDIUM** (runtime, CLI behavior, dashboard logic without a trust-core
108
+ path): 2 reviewers, pinned `opus`. Unanimous APPROVE required; one
109
+ CONCERN re-runs both after the fix.
110
+ - **LOW** (docs, copy, tests-only, UI with no data path, deleting
111
+ unreachable code): 1 reviewer, may be pinned `sonnet`. APPROVE required;
112
+ a CONCERN re-runs it after the fix.
113
+
114
+ After 3 rounds at any tier without unanimous APPROVE, the slice goes back to
115
+ the PO to be cut smaller. It never blocks the line: other slices keep moving
116
+ through the pipeline.
117
+
118
+ ### 4. Release Captain
119
+
120
+ Exactly one, never parallelized, serialized by a promise chain within one
121
+ Workflow run (see "Cross-wave exclusion"). The only agent allowed to write
122
+ `main`, bump `VERSION`, tag, or publish. For each approved slice, in order:
123
+
124
+ 1. Rebase the slice onto current `origin/main`.
125
+ 2. Apply any hot-file additions the slice requested (below), mechanically.
126
+ 3. Rebuild every bundle the slice's file set could affect (below).
127
+ 4. Run `bash scripts/local-ci.sh` (fast tier) and `bash tests/moat/run.sh`.
128
+ The ratchet (`pending.txt` shrink-only, `cases.txt` grow-only, D2) must
129
+ not regress; a regression refuses the merge and returns the slice to its
130
+ builder.
131
+ 5. Stage by name (never `git add -A`), commit with the repo-local asklokesh
132
+ identity, push.
133
+ 6. Bump `VERSION` and the other 13 files per CLAUDE.md's release checklist
134
+ (`scripts/release.sh` is stale; do this directly, not through the
135
+ script), rebuild dashboard and dist, run the full pre-publish validation,
136
+ commit as the push head, push with `PRE_PUSH_SKIP=1` (D5).
137
+ 7. Wait for the previous release's required-ci (Tests, Bun Parity, Security
138
+ Audit) to go green before bumping `VERSION` again, so releases never
139
+ cancel each other's CI (the v9.7-v9.9 failure, and
140
+ `feedback-release-push-cancels-tests`). If several slices are ready
141
+ while waiting, batch them into the next release rather than idle.
142
+ 8. On a code conflict, send the slice back to its builder with the new base.
143
+ The Captain never resolves a code conflict itself.
144
+ 9. After a required-ci failure, rerun the failed job once; if the second
145
+ run also fails, do a full `gh run rerun` (never `--failed`) on the whole
146
+ Release run, and wait out the roughly 4-minute npm propagation window
147
+ before treating a publish as real
148
+ (`feedback-release-rerun-must-be-full-not-failed`,
149
+ `feedback-npm-propagation-window-4min`).
150
+
151
+ **Hot files** (never in any builder's declared file set; the Captain applies
152
+ requested additions mechanically at merge time):
153
+ `tests/moat/cases.txt`, `tests/moat/pending.txt` (merge with
154
+ `scratchpad/union_cases.py`'s approach: union for cases, intersection-aware
155
+ shrink for pending), `CHANGELOG.md`, `docs/v10/BACKLOG.md`,
156
+ `docs/v10/BOARD.md`, `docs/v10/PROGRESS.md`, `docs/v10/METRICS.md`,
157
+ `scripts/local-ci.sh` (`_FAST_KEEP` entries and `run_check` call sites),
158
+ `tests/run-all-tests.sh`.
159
+
160
+ **Generated bundles** (never edited by hand by a builder; the Captain
161
+ rebuilds all three after every merge that could touch their sources):
162
+ `loki-ts/dist/loki.js` (from `loki-ts/src`), `dashboard/static/index.html`
163
+ and `dashboard-ui/dist` (from `dashboard-ui/scripts/build-standalone.js` and
164
+ components), `web-app/dist` (from `web-app/src`).
165
+
166
+ **Cross-wave exclusion:** a promise chain only serializes within one
167
+ `Workflow` run. The swarm runs the PO-refill and Captain-queue loop inside a
168
+ single long-running `Workflow` invocation rather than launching a fresh one
169
+ per wave, so there is exactly one Captain at any time. If a script must be
170
+ edited and re-invoked, the previous run is stopped first (`TaskStop`) and its
171
+ in-flight slices are recovered from `BOARD.md` before the new run starts.
172
+
173
+ ### 5. Channel Verifier
174
+
175
+ One, async, read-only, spawned after each release. Checks npm `latest` and
176
+ `gitHead`, the tag, the GitHub release, Docker amd64/arm64, the Homebrew
177
+ sha256, and a smoke test from a fresh PATH. Never blocks the next merge. A
178
+ failure becomes a HIGH-priority fix-forward slice through the normal PO
179
+ queue. Never unpublishes or deletes a version.
180
+
181
+ ## Orchestration
182
+
183
+ - Repeated `Workflow` waves inside one long run: the PO produces up to 10
184
+ ready slices, then `pipeline(slices, build -> verify -> review/fix loop ->
185
+ captain queue)`. `pipeline`, not `parallel`, so a LOW slice ships while a
186
+ HIGH slice is still mid-review.
187
+ - The Captain step in the pipeline is itself a promise chain
188
+ (`captainQueue = captainQueue.then(() => mergeAndRelease(slice))`), so
189
+ exactly one merge or release runs at a time even though builders and
190
+ reviewers for other slices run concurrently.
191
+ - BOARD.md (slice id, owner, file set, tier, status, blockers) is the
192
+ coordination surface between agents; only the PO and the Captain write it.
193
+ - The next wave's PO step starts as soon as ready slices run low; the
194
+ script never waits on Channel Verifier output before starting more work.
195
+
196
+ ## Speed levers, in priority order
197
+
198
+ 1. One slice, one release.
199
+ 2. Risk-tiered review; most slices are LOW or MEDIUM.
200
+ 3. Parallel reviewers per slice instead of serial whole-batch rounds.
201
+ 4. Asynchronous channel verification.
202
+ 5. Diff-scoped tests while building; the full fast tier plus moat run only
203
+ at merge.
204
+ 6. Flaky tests: rerun once, record it in `BACKLOG.md`, open a slice. Never
205
+ pass a flake silently (BACKLOG 93).
206
+
207
+ ## Guardrails (restated; speed pressure erodes them first)
208
+
209
+ - Priority when goals conflict: moat > Seal accuracy > delivered accuracy >
210
+ cost > speed (build prompt 5b).
211
+ - The moat ratchet never regresses on `main`.
212
+ - If two releases in a row fail required-ci, stop releasing, fix `main`,
213
+ resume only when green.
214
+ - No force push. Never delete versions or tags. Never disable a test or
215
+ guard to get green.
216
+ - Every "never do" item in build-prompt section 1 still applies without
217
+ exception, swarm or not.
218
+ - Section 5c.5 (single writer): one agent writes a given change; parallel
219
+ agents are read-only; parallelize only across independent items under one
220
+ central coordinator, never peer-to-peer.
221
+ - Section 5c.11 (injection-safe intake): P9's split jobs and token
222
+ withholding (cycle 5) are the concrete form of this rule; the swarm's own
223
+ agent-authored slices never combine untrusted input, an agent, and push
224
+ rights either.
225
+
226
+ ## Metrics (appended to METRICS.md after every wave)
227
+
228
+ - releases in the last 24h
229
+ - median slice lead time (ready to npm)
230
+ - review rounds per slice, by tier
231
+ - moat proven count
232
+ - required-ci failures
233
+ - spend estimate (subagent output tokens from workflow journals x published
234
+ per-token price, labelled ESTIMATE, not exact billing)
235
+
236
+ Target: at least 30 releases per 24h at steady state, moat proven count never
237
+ decreasing.
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var dt=Object.create;var{getPrototypeOf:pt,defineProperty:jG,getOwnPropertyNames:ct}=Object;var lt=Object.prototype.hasOwnProperty;function it($){return this[$]}var at,ot,st=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?at??=new WeakMap:ot??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?dt(pt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of ct($))if(!lt.call(J,q))jG(J,q,{get:it.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var Zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var nt=($)=>$;function rt($,X){this[$]=nt.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:rt.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var WR={};B1(WR,{lokiDir:()=>h0,homeLokiDir:()=>GQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>O1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as tt}from"url";import{existsSync as Kq}from"fs";import{homedir as et}from"os";function $e(){let $=HR;for(let X=0;X<6;X++){if(Kq(h2($,"VERSION"))&&Kq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(HR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Kq(h2(X,"VERSION"))&&Kq(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(et(),".loki")}var HR,O1;var k1=s(()=>{HR=LG(tt(import.meta.url));O1=$e()});import{readFileSync as Xe}from"fs";import{resolve as Qe,dirname as ze}from"path";import{fileURLToPath as Ze}from"url";function L9(){if(t3!==null)return t3;let $="9.54.0";if(typeof $==="string"&&$.length>0)return t3=$,t3;try{let X=ze(Ze(import.meta.url)),Q=AG(X);t3=Xe(Qe(Q,"VERSION"),"utf-8").trim()}catch{t3="unknown"}return t3}var t3=null;var Jq=s(()=>{k1()});var LR={};B1(LR,{runOrThrow:()=>Ae,run:()=>X1,readStreamCapped:()=>qq,commandVersion:()=>Te,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>jR});async function qq($,X=jR){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 X1($,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([qq(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 Ae($,X={}){let Q=await X1($,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=Ce($),Q=await X1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Ce($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function Te($,X="--version"){if(!await g5($))return null;let z=await X1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var jR=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 De?"":$}var De,p0,$5,Z1,E61,A1,f1,m5,n;var e7=s(()=>{De=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),Z1=g2("\x1B[1;33m"),E61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),n=g2("\x1B[0m")});import{existsSync as ye}from"fs";async function Z2(){if(BQ!==void 0)return BQ;let $="/opt/homebrew/bin/python3.12";if(ye($))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 X1([Q,"-c",$],X)}var BQ;var m2=s(()=>{y8()});var gR={};B1(gR,{runStatus:()=>Z00});import{existsSync as u5,readFileSync as T9,readdirSync as kR,statSync as SR}from"fs";import{resolve as A5,basename as se}from"path";import{homedir as ne}from"os";function yR($){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 bR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Yq/X);if(Z>Yq)Z=Yq;let K=Yq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=Z1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=yR($),Y=yR(X);return` ${f1}${Q}${n} ${J}[${q}]${n} ${z}% (${V} / ${Y})`}async function te(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${n}
2
+ var dt=Object.create;var{getPrototypeOf:pt,defineProperty:jG,getOwnPropertyNames:ct}=Object;var lt=Object.prototype.hasOwnProperty;function it($){return this[$]}var at,ot,st=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?at??=new WeakMap:ot??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?dt(pt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of ct($))if(!lt.call(J,q))jG(J,q,{get:it.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var Zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var nt=($)=>$;function rt($,X){this[$]=nt.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:rt.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var WR={};B1(WR,{lokiDir:()=>h0,homeLokiDir:()=>GQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>O1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as tt}from"url";import{existsSync as Kq}from"fs";import{homedir as et}from"os";function $e(){let $=HR;for(let X=0;X<6;X++){if(Kq(h2($,"VERSION"))&&Kq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(HR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Kq(h2(X,"VERSION"))&&Kq(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(et(),".loki")}var HR,O1;var k1=s(()=>{HR=LG(tt(import.meta.url));O1=$e()});import{readFileSync as Xe}from"fs";import{resolve as Qe,dirname as ze}from"path";import{fileURLToPath as Ze}from"url";function L9(){if(t3!==null)return t3;let $="9.55.0";if(typeof $==="string"&&$.length>0)return t3=$,t3;try{let X=ze(Ze(import.meta.url)),Q=AG(X);t3=Xe(Qe(Q,"VERSION"),"utf-8").trim()}catch{t3="unknown"}return t3}var t3=null;var Jq=s(()=>{k1()});var LR={};B1(LR,{runOrThrow:()=>Ae,run:()=>X1,readStreamCapped:()=>qq,commandVersion:()=>Te,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>jR});async function qq($,X=jR){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 X1($,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([qq(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 Ae($,X={}){let Q=await X1($,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=Ce($),Q=await X1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Ce($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function Te($,X="--version"){if(!await g5($))return null;let z=await X1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var jR=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 De?"":$}var De,p0,$5,Z1,E61,A1,f1,m5,n;var e7=s(()=>{De=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),Z1=g2("\x1B[1;33m"),E61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),n=g2("\x1B[0m")});import{existsSync as ye}from"fs";async function Z2(){if(BQ!==void 0)return BQ;let $="/opt/homebrew/bin/python3.12";if(ye($))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 X1([Q,"-c",$],X)}var BQ;var m2=s(()=>{y8()});var gR={};B1(gR,{runStatus:()=>Z00});import{existsSync as u5,readFileSync as T9,readdirSync as kR,statSync as SR}from"fs";import{resolve as A5,basename as se}from"path";import{homedir as ne}from"os";function yR($){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 bR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Yq/X);if(Z>Yq)Z=Yq;let K=Yq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=Z1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=yR($),Y=yR(X);return` ${f1}${Q}${n} ${J}[${q}]${n} ${z}% (${V} / ${Y})`}async function te(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${n}
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)
@@ -335,10 +335,13 @@ print(json.dumps(result, indent=2))
335
335
  Start a session with: loki start <prd>`}}let Z=Y00(z);return{exitCode:0,stdout:X?U00(Z,Q):H00(Z,Q)}}async function W00($){let{emitDeprecatedAlias:X}=await Promise.resolve().then(() => (IG(),dR));X("stats","report session",$);let Q=lR($);return console.log(Q.stdout),Q.exitCode}var aR=s(()=>{k1();e7()});var ZI={};B1(ZI,{skillsForJson:()=>eR,runDoctor:()=>E00,renderProviderAvailability:()=>QI,readProviderAvailability:()=>XI,readEffectiveProvider:()=>tR,pythonImportOk:()=>SG,httpReachable:()=>xG,describeCatalogFreshness:()=>fG,checkTool:()=>rR,checkSkills:()=>yG,checkDisk:()=>kG,buildDoctorJson:()=>zI,_setPythonImportOkForTest:()=>j00});import{existsSync as MQ,lstatSync as G00,readFileSync as oR,readlinkSync as B00,statfsSync as N00}from"fs";import{spawnSync as I9}from"child_process";import{homedir as OQ}from"os";import{resolve as e3}from"path";function O00($){let X=$.match(M00);return X?X[1]:null}async function sR($){try{let X=await X1([$,"--version"],{timeoutMs:5000}),Q=(X.stdout||X.stderr||"").trim();return O00(Q)}catch{return null}}function nR($,X){let Q=$.split(".").map((Z)=>parseInt(Z,10)),z=X.split(".").map((Z)=>parseInt(Z,10));while(Q.length<2)Q.push(0);while(z.length<2)z.push(0);for(let Z=0;Z<2;Z++){let K=Q[Z]??0,J=z[Z]??0;if(Number.isNaN(K)||Number.isNaN(J))return 0;if(K!==J)return K-J}return 0}async function rR($,X,Q,z=null){let Z=await g5(X),K=Z!==null,J=K?await sR(X):null,q="pass";if(!K)q=Q==="required"?"fail":"warn";else if(z&&J){if(nR(J,z)<0)q=Q==="required"?"fail":"warn"}return{name:$,command:X,found:K,version:J,required:Q,min_version:z,status:q,path:Z}}function kG(){let $=null;try{let Q=N00(OQ()),z=Number(Q.bavail)*Number(Q.bsize);$=Math.round(z/1073741824*10)/10}catch{$=null}let X="pass";if($!==null){if($<1)X="fail";else if($<5)X="warn"}return{available_gb:$,status:X}}async function xG($,X=2000){try{return(await fetch($,{signal:AbortSignal.timeout(X)})).ok}catch{return!1}}async function SG($,X=!1){let Q=`import ${$}`,z=X?30000:5000;if(!X)return(await I4(Q,{timeoutMs:z})).exitCode===0;let Z=await Z2();if(!Z)return!1;return(await X1([Z,"-c",Q],{timeoutMs:z})).exitCode===0}function j00($){Bq.fn=$??SG}function tR(){let $=bG();if(!$)return null;try{let X=I9("bash",[$,"effective-provider"],{encoding:"utf8"});if(X.status!==0||!X.stdout)return null;let Q=X.stdout.trim();return Q===""?null:Q}catch{return null}}function yG($){let X=$===void 0?tR():$,Q=OQ();return L00.map(({name:z,dir:Z,id:K})=>{let J=e3(Q,Z),q=J,V=e3(J,"SKILL.md");if(MQ(V))return{name:z,path:q,status:"pass",detail:"",dangling:!1};try{if(G00(J).isSymbolicLink()){let U="unknown";try{U=B00(J)}catch{}return{name:z,path:q,status:"fail",detail:`(broken symlink -> ${U})`,dangling:!0}}}catch{}return{name:z,path:q,status:"warn",detail:"(not found - run 'loki setup-skill')",dangling:!1}})}function eR(){return yG().map(($)=>({name:$.name,path:$.path.startsWith(`${OQ()}/`)?`~/${$.path.slice(OQ().length+1)}`:$.path,status:$.status,detail:$.status==="pass"?null:$.status==="fail"?"broken symlink. Fix: loki setup-skill":"not found - run loki setup-skill",required:"required"}))}async function $I(){return Promise.all(A00.map(async($)=>{return{...await rR($.jsonName,$.cmd,$.required,$.min??null),displayName:$.displayName}}))}function bG(){let $=e3(O1,"autonomy/provider-offer.sh");return MQ($)?$:null}function XI(){let $=bG();if(!$)return null;try{let X=I9("bash",[$,"providers-json"],{encoding:"utf8"});if(X.status!==0||!X.stdout)return null;let Q=JSON.parse(X.stdout);if(!Q||!Array.isArray(Q.providers))return null;return Q}catch{return null}}function QI(){let $=bG();if(!$)return"";try{let X=I9("bash",[$,"providers"],{encoding:"utf8"});if(X.status!==0||!X.stdout)return"";return X.stdout}catch{return""}}async function C00(){let X=await g5("sentrux")!==null,Q=X?await sR("sentrux"):null;return{found:X,version:Q,status:X?"pass":"warn",required:"optional"}}async function T00(){let $=(process.env.LOKI_PROOF_GPG_KEY??"").trim()!=="",X=await g5("gpg")!==null,Q=$&&X;return{enabled:Q,key_configured:$,gpg_available:X,status:Q?"pass":"warn",required:"optional"}}async function D00(){let{openSync:$,statSync:X,readSync:Q,closeSync:z,existsSync:Z}=await import("fs"),{join:K}=await import("path"),J=65536,q=process.env.LOKI_DIR??".loki",V=K(q,"memory",".errors.log"),Y=[],U=!1;try{if(Z(V)){U=!0;let H=X(V).size,W=Math.max(0,H-65536),G=H-W,N=Buffer.alloc(G),M=$(V,"r");try{Q(M,N,0,G,W)}finally{z(M)}let j=N.toString("utf-8").split(`
336
336
  `);if(W>0&&j.length>0)j=j.slice(1);j=j.map((B)=>B.trim()).filter((B)=>B.length>0),Y=j.slice(-5)}}catch{Y=[]}return{errors_log_path:U?V:null,recent_errors:Y,recent_error_count:Y.length,status:Y.length===0?"pass":"warn"}}function fG(){let $=process.env.LOKI_MODEL_CATALOG||e3(O1,"providers/model_catalog.json"),X,Q;try{X=JSON.parse(oR($,"utf-8")).updated;let z=Date.parse(`${X}T00:00:00Z`),Z=Date.parse(`${new Date().toISOString().slice(0,10)}T00:00:00Z`);if(Number.isNaN(z))throw Error("bad date");Q=Math.round((Z-z)/86400000)}catch{return{status:"warn",updated:null,age_days:null,detail:'Catalog unreadable or missing an ISO "updated" date -- cannot determine age'}}return Q>F00?{status:"warn",updated:X,age_days:Q,detail:`Last updated ${X} (${Q} days ago) -- may be missing newer models`}:{status:"pass",updated:X,age_days:Q,detail:`Last updated ${X} (${Q} days ago)`}}async function zI(){let X=(await $I()).map(({displayName:W,...G})=>G),Q=kG(),z=await C00(),Z=await T00(),K=await D00(),J=0,q=0,V=0;for(let W of X)if(W.status==="pass")J++;else if(W.status==="fail")q++;else V++;if(Q.status==="pass")J++;else if(Q.status==="fail")q++;else V++;let Y=["claude","codex","cline","aider","opencode"].some((W)=>X.find((G)=>G.command===W)?.found===!0),U={found:Y,status:Y?"pass":"fail",required:"required",detail:Y?null:"No AI provider CLI. Fix: npm install -g @anthropic-ai/claude-code"};if(Y)J++;else q++;let H=eR();for(let W of H)if(W.status==="pass")J++;else if(W.status==="fail")q++;else V++;return{loki_mode_version:L9(),checks:X,provider_availability:XI(),disk:Q,ai_provider:U,skills:H,sentrux:z,receipt_signing:Z,memory:K,model_catalog:fG(),summary:{passed:J,failed:q,warnings:V,ok:q===0}}}function R00(){try{let $=process.env.CLAUDE_CONFIG_DIR||`${OQ()}/.claude`,X=oR(`${$}/.credentials.json`,"utf8");if(!X)return!1;let Q=JSON.parse(X)?.claudeAiOauth?.expiresAt;return typeof Q==="number"&&Q>0&&Q/1000<=Date.now()/1000+60}catch{return!1}}function I00(){try{let X=(I9("claude",["auth","status"],{encoding:"utf8",timeout:5000}).stdout||"").trim();if(!X)return"";let Q=JSON.parse(X);if(Q?.loggedIn===!0)return"yes";if(Q?.loggedIn===!1)return"no";return""}catch{return""}}function a0($){switch($){case"pass":return`${$5}PASS${n}`;case"fail":return`${p0}FAIL${n}`;case"warn":return`${Z1}WARN${n}`}}function Wq($){let X=$.version?` (v${$.version})`:"",Q=$.displayName;if(!$.found){let z=$.required==="required"?"not found":$.required==="recommended"?"not found (recommended)":"not found (optional)";return` ${a0($.status)} ${Q} - ${z}`}if($.min_version&&$.version&&nR($.version,$.min_version)<0){let z=$.required==="required"?"requires":"recommended";return` ${a0($.status)} ${Q}${X} - ${z} >= ${$.min_version}`}return` ${a0($.status)} ${Q}${X}`}function Gq($,X){if(X==="pass")$.pass++;else if(X==="fail")$.fail++;else $.warn++}function w00(){process.stdout.write(`${f1}loki doctor${n} - Check system prerequisites
337
337
 
338
- `),process.stdout.write(`Usage: loki doctor [--json]
338
+ `),process.stdout.write(`Usage: loki doctor [--json] [--airgap]
339
339
 
340
340
  `),process.stdout.write(`Options:
341
341
  `),process.stdout.write(` --json Output machine-readable JSON
342
+ `),process.stdout.write(` --airgap Audit network egress: model inference, telemetry and the
343
+ `),process.stdout.write(` update check, REQUIRED or optional, and how to disable
344
+ `),process.stdout.write(` each. Exits non-zero while a required egress remains.
342
345
 
343
346
  `),process.stdout.write(`Checks: node, python3, jq, git, curl, bash version,
344
347
  `),process.stdout.write(` claude/codex CLIs, and disk space.
@@ -441,7 +444,7 @@ Start a session with: loki start <prd>`}}let Z=Y00(z);return{exitCode:0,stdout:X
441
444
  `),process.stdout.write(`Next: loki quickstart (guided first build from your idea, no PRD needed)
442
445
  `),process.stdout.write(` or loki demo (builds a sample todo app end to end) or loki start ./prd.md
443
446
  `),0}async function E00($){let X=!1;for(let Q of $)if(Q==="--json")X=!0;else if(Q==="--help"||Q==="-h")return w00(),0;else return process.stderr.write(`${p0}Unknown option: ${Q}${n}
444
- `),process.stderr.write(`Usage: loki doctor [--json]
447
+ `),process.stderr.write(`Usage: loki doctor [--json] [--airgap]
445
448
  `),1;if(X){let Q=await zI();return process.stdout.write(JSON.stringify(Q,null,2)+`
446
449
  `),Q.summary.ok?0:1}return P00()}var M00,Bq,L00,A00,F00=90;var KI=s(()=>{k1();y8();m2();e7();Jq();M00=/(\d+\.\d+(?:\.\d+)*)/;Bq={fn:SG};L00=[{name:"Claude Code",dir:".claude/skills/loki-mode",id:"claude"},{name:"Codex CLI",dir:".codex/skills/loki-mode",id:"codex"},{name:"Cline CLI",dir:".cline/skills/loki-mode",id:"cline"},{name:"Aider CLI",dir:".aider/skills/loki-mode",id:"aider"}];A00=[{displayName:"Node.js (>= 18)",jsonName:"Node.js",cmd:"node",required:"required",min:"18.0"},{displayName:"Python 3 (>= 3.8)",jsonName:"Python 3",cmd:"python3",required:"required",min:"3.8"},{displayName:"jq",jsonName:"jq",cmd:"jq",required:"required"},{displayName:"git",jsonName:"git",cmd:"git",required:"required"},{displayName:"curl",jsonName:"curl",cmd:"curl",required:"required"},{displayName:"bash (>= 4.0)",jsonName:"bash",cmd:"bash",required:"recommended",min:"4.0"},{displayName:"Bun (>= 1.3)",jsonName:"Bun",cmd:"bun",required:"recommended",min:"1.3"},{displayName:"Claude CLI",jsonName:"Claude CLI",cmd:"claude",required:"optional"},{displayName:"Codex CLI",jsonName:"Codex CLI",cmd:"codex",required:"optional"},{displayName:"Cline CLI",jsonName:"Cline CLI",cmd:"cline",required:"optional"},{displayName:"Aider CLI",jsonName:"Aider CLI",cmd:"aider",required:"optional"},{displayName:"opencode CLI",jsonName:"opencode CLI",cmd:"opencode",required:"optional"}]});var UI={};B1(UI,{writeBudgetState:()=>mG,readEfficiencyDir:()=>Mq,readBudgetState:()=>g00,parseRetryAfter:()=>c00,isRateLimited:()=>p00,checkBudgetLimitForRunner:()=>i00,checkBudgetLimit:()=>YI,calculateRateLimitBackoff:()=>l00,calculateCostFromRecords:()=>Nq,PRICING:()=>gG});import{existsSync as uG,mkdirSync as vG,readdirSync as x00,readFileSync as dG,renameSync as k00,writeFileSync as hG}from"fs";import{dirname as pG,join as p2,resolve as S00}from"path";import{fileURLToPath as y00}from"url";function b00(){try{let $=pG(y00(import.meta.url)),X=S00($,"..","..","data","model-pricing.json");if(!uG(X))return jQ;let z=JSON.parse(dG(X,"utf8")).pricing;if(!z||typeof z!=="object")return jQ;let Z={};for(let[K,J]of Object.entries(z))if(J!==null&&typeof J==="object"&&typeof J.input==="number"&&typeof J.output==="number"){let q=J;Z[K]={input:q.input,output:q.output,...typeof q.cache_read==="number"?{cache_read:q.cache_read}:{},...typeof q.cache_write==="number"?{cache_write:q.cache_write}:{}}}for(let K of Object.keys(jQ))if(!(K in Z))return jQ;return Z}catch{return jQ}}function _00($){return Math.round(($+Number.EPSILON)*1e4)/1e4}function v00($){let X=($??JI).toLowerCase();return gG[X]??gG[JI]}function Nq($){let X=0;for(let Q of $){if(typeof Q.cost_usd==="number"&&Number.isFinite(Q.cost_usd)){X+=Q.cost_usd;continue}let z=v00(Q.model),Z=typeof Q.input_tokens==="number"?Q.input_tokens:0,K=typeof Q.output_tokens==="number"?Q.output_tokens:0,J=typeof Q.cache_read_tokens==="number"?Q.cache_read_tokens:0,q=typeof Q.cache_creation_tokens==="number"?Q.cache_creation_tokens:0,V=z.cache_read??z.input,Y=z.cache_write??z.input;X+=Z/1e6*z.input+K/1e6*z.output+J/1e6*V+q/1e6*Y}return _00(X)}function Mq($){if(!uG($))return[];let X=[],Q;try{Q=x00($)}catch{return[]}for(let z of Q){if(!z.endsWith(".json"))continue;let Z=p2($,z);try{let K=dG(Z,"utf8"),J=JSON.parse(K);if(J&&typeof J==="object")X.push(J)}catch{}}return X}function h00($){if($===null||typeof $!=="object"||Array.isArray($))return!1;let X=$;if(typeof X.limit!=="number"||!Number.isFinite(X.limit))return!1;if(typeof X.budget_limit!=="number"||!Number.isFinite(X.budget_limit))return!1;if(typeof X.budget_used!=="number"||!Number.isFinite(X.budget_used))return!1;if(typeof X.exceeded!=="boolean")return!1;if(X.exceeded_at!==void 0&&typeof X.exceeded_at!=="string")return!1;if(X.created_at!==void 0&&typeof X.created_at!=="string")return!1;return!0}function g00($){let X=$??p2(h0(),"metrics","budget.json");if(!uG(X))return null;try{let Q=dG(X,"utf8"),z=JSON.parse(Q);if(!h00(z))return console.warn(`[budget] discarding malformed budget state at ${X}`),null;return z}catch{return null}}function mG($,X){let Q=X??p2(h0(),"metrics","budget.json");vG(pG(Q),{recursive:!0});let z=[];if(z.push("{"),z.push(` "limit": ${_G($.limit)},`),z.push(` "budget_limit": ${_G($.budget_limit)},`),z.push(` "budget_used": ${_G($.budget_used)},`),$.exceeded){z.push(' "exceeded": true,');let J=$.exceeded_at??VI();z.push(` "exceeded_at": "${J}"`)}else z.push(' "exceeded": false');z.push("}");let Z=z.join(`
447
450
  `)+`
@@ -1345,4 +1348,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1345
1348
  `),2}case"start":{let{runStart:z}=await Promise.resolve().then(() => (gt(),ht));return z(Q)}default:return process.stderr.write(`Unknown command: ${X}
1346
1349
  `),process.stderr.write(mt),2}}ER();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var H61=await U61(Bun.argv.slice(2));process.exit(H61);
1347
1350
 
1348
- //# debugId=F9072E566EE545D0EBA53BA934122C29
1351
+ //# debugId=581D4F54CF5D0BFAC2AEC3492BD2352A
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '9.54.0'
78
+ __version__ = '9.55.0'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "loki-mode",
3
3
  "mcpName": "io.github.asklokesh/loki-mode",
4
- "version": "9.54.0",
4
+ "version": "9.55.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, 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.54.0",
5
+ "version": "9.55.0",
6
6
  "description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
7
7
  "author": {
8
8
  "name": "Autonomi",
@@ -109,6 +109,12 @@ provider_version() {
109
109
  aider --version 2>/dev/null | head -1
110
110
  }
111
111
 
112
+ # Commit hygiene, the sentence providers/claude.sh (and the Bun
113
+ # AUTONOMY_OVERRIDE_TEXT) puts in Claude's system prompt. Aider takes no
114
+ # system prompt here, so it leads every prompt the functions below send
115
+ # (BACKLOG 74: an agent's own `git add -A` commits the user's files).
116
+ PROVIDER_COMMIT_HYGIENE='Commit hygiene still applies: git checkpoints are LOCAL only. Never push or force-push. Stage files by explicit path, never `git add -A` or `git add .`, and never commit secrets, credentials, .env files, or untracked files you did not author this session.'
117
+
112
118
  # Invocation function
113
119
  # --message: single instruction mode (process and exit)
114
120
  # --yes-always: auto-approve all prompts
@@ -116,6 +122,7 @@ provider_version() {
116
122
  provider_invoke() {
117
123
  local prompt="$1"
118
124
  shift
125
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
119
126
  local model="$AIDER_DEFAULT_MODEL"
120
127
  local extra_flags="${LOKI_AIDER_FLAGS:-}"
121
128
  # shellcheck disable=SC2086
@@ -150,6 +157,7 @@ resolve_model_for_tier() {
150
157
  provider_invoke_argv() {
151
158
  local tier="${1:-development}"
152
159
  local prompt="${2:-}"
160
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
153
161
  local model
154
162
  model="$(provider_get_tier_param "$tier" 2>/dev/null || printf '%s' "")"
155
163
  _LOKI_INVOKE_ARGV=(aider --yes-always --no-auto-commits)
@@ -107,6 +107,12 @@ provider_version() {
107
107
  cline --version 2>/dev/null | head -1
108
108
  }
109
109
 
110
+ # Commit hygiene, the sentence providers/claude.sh (and the Bun
111
+ # AUTONOMY_OVERRIDE_TEXT) puts in Claude's system prompt. Cline takes no
112
+ # system prompt here, so it leads every prompt the functions below send
113
+ # (BACKLOG 74: an agent's own `git add -A` commits the user's files).
114
+ PROVIDER_COMMIT_HYGIENE='Commit hygiene still applies: git checkpoints are LOCAL only. Never push or force-push. Stage files by explicit path, never `git add -A` or `git add .`, and never commit secrets, credentials, .env files, or untracked files you did not author this session.'
115
+
110
116
  # Invocation function
111
117
  # Uses -y (YOLO) for autonomous mode, positional prompt
112
118
  # BUG-PROV-009 fix: build model flag as array to prevent word-splitting on model
@@ -114,6 +120,7 @@ provider_version() {
114
120
  provider_invoke() {
115
121
  local prompt="$1"
116
122
  shift
123
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
117
124
  local model="${LOKI_CLINE_MODEL:-}"
118
125
  local model_args=()
119
126
  [[ -n "$model" ]] && model_args=("-m" "$model")
@@ -148,6 +155,7 @@ resolve_model_for_tier() {
148
155
  provider_invoke_argv() {
149
156
  local tier="${1:-development}"
150
157
  local prompt="${2:-}"
158
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
151
159
  local model
152
160
  model="$(provider_get_tier_param "$tier" 2>/dev/null || printf '%s' "")"
153
161
  _LOKI_INVOKE_ARGV=(cline -y)
@@ -209,9 +209,16 @@ _codex_model_flag() {
209
209
  return 0
210
210
  }
211
211
 
212
+ # Commit hygiene, the sentence providers/claude.sh (and the Bun
213
+ # AUTONOMY_OVERRIDE_TEXT) puts in Claude's system prompt. Codex takes no
214
+ # system prompt here, so it leads every prompt the functions below send
215
+ # (BACKLOG 74: an agent's own `git add -A` commits the user's files).
216
+ PROVIDER_COMMIT_HYGIENE='Commit hygiene still applies: git checkpoints are LOCAL only. Never push or force-push. Stage files by explicit path, never `git add -A` or `git add .`, and never commit secrets, credentials, .env files, or untracked files you did not author this session.'
217
+
212
218
  provider_invoke() {
213
219
  local prompt="$1"
214
220
  shift
221
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
215
222
  local model_flag=()
216
223
  while IFS= read -r _mf; do
217
224
  [ -n "$_mf" ] && model_flag+=("$_mf")
@@ -302,6 +309,7 @@ provider_invoke_with_tier() {
302
309
  local tier="$1"
303
310
  local prompt="$2"
304
311
  shift 2
312
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
305
313
  local effort
306
314
  effort=$(resolve_model_for_tier "$tier")
307
315
 
@@ -360,6 +368,7 @@ provider_invoke_with_tier() {
360
368
  provider_invoke_argv() {
361
369
  local tier="${1:-development}"
362
370
  local prompt="${2:-}"
371
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
363
372
 
364
373
  # DO NOT pass provider_get_tier_param to --model. For Codex that function
365
374
  # returns an EFFORT LEVEL (xhigh/high/low), not a model name -- the note at
@@ -197,3 +197,13 @@ auto_detect_provider() {
197
197
  echo ""
198
198
  return 1
199
199
  }
200
+
201
+ # loki_claude_sidecall_allowed [provider] -- 0 when an engine side-call (PRD
202
+ # enrichment, done recognition, council voters, USAGE.md refresh, quickstart
203
+ # intent) may prompt the claude CLI. Only the provider the operator chose sees
204
+ # prompts (V10 P5): allowed when that provider is claude, or on the explicit
205
+ # opt-in LOKI_ALLOW_CLAUDE_SIDECALLS=1 (exact value), which
206
+ # `loki doctor --airgap` reports as required egress to Anthropic.
207
+ loki_claude_sidecall_allowed() {
208
+ [ "${1:-${LOKI_PROVIDER:-claude}}" = "claude" ] || [ "${LOKI_ALLOW_CLAUDE_SIDECALLS:-}" = "1" ]
209
+ }
@@ -117,6 +117,12 @@ provider_get_tier_param() {
117
117
  esac
118
118
  }
119
119
 
120
+ # Commit hygiene, the sentence providers/claude.sh (and the Bun
121
+ # AUTONOMY_OVERRIDE_TEXT) puts in Claude's system prompt. opencode takes no
122
+ # system prompt here, so it leads every prompt the functions below send
123
+ # (BACKLOG 74: an agent's own `git add -A` commits the user's files).
124
+ PROVIDER_COMMIT_HYGIENE='Commit hygiene still applies: git checkpoints are LOCAL only. Never push or force-push. Stage files by explicit path, never `git add -A` or `git add .`, and never commit secrets, credentials, .env files, or untracked files you did not author this session.'
125
+
120
126
  # provider_invoke <prompt> -- run one non-interactive turn.
121
127
  provider_invoke() {
122
128
  # CONTRACT: prompt is $1, and EXTRA ARGS MUST PASS THROUGH. The first cut of
@@ -130,6 +136,7 @@ provider_invoke() {
130
136
  local prompt="$1"
131
137
  shift
132
138
  [ -n "$prompt" ] || return 1
139
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
133
140
  command -v opencode >/dev/null 2>&1 || return 127
134
141
  opencode run --auto --model "$PROVIDER_MODEL_DEVELOPMENT" "$prompt" "$@"
135
142
  }
@@ -143,6 +150,7 @@ provider_invoke_with_tier() {
143
150
  shift 2
144
151
  [ -n "$tier" ] || tier="development"
145
152
  [ -n "$prompt" ] || return 1
153
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
146
154
  command -v opencode >/dev/null 2>&1 || return 127
147
155
  local model
148
156
  model="$(provider_get_tier_param "$tier")"
@@ -153,6 +161,7 @@ provider_invoke_with_tier() {
153
161
  provider_invoke_argv() {
154
162
  local tier="${1:-development}"
155
163
  local prompt="${2:-}"
164
+ prompt="$PROVIDER_COMMIT_HYGIENE"$'\n\n'"$prompt"
156
165
  local model
157
166
  model="$(provider_get_tier_param "$tier")"
158
167
  _LOKI_INVOKE_ARGV=(opencode run --auto --model "$model" "$prompt")
@@ -197,7 +197,7 @@ def attest(proof_path, repo_dir="."):
197
197
  axes["drift"] = _tri(
198
198
  result.get("diff_drift") is False if result.get("diff_drift") is not None
199
199
  else None,
200
- _pick(reasons, "drift unverifiable") or
200
+ _pick(reasons, "could not check drift", "drift unverifiable") or
201
201
  "the recorded diff could not be re-derived here",
202
202
  )
203
203
  if result.get("diff_drift") is True: