loki-mode 11.4.2 → 11.6.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/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/chat.sh +6 -3
  4. package/autonomy/lib/engine10-push.sh +19 -1
  5. package/autonomy/lib/help-core.sh +1 -1
  6. package/autonomy/lib/proof-generator.py +23 -5
  7. package/autonomy/lib/proof-verify.py +607 -5
  8. package/autonomy/lib/run-summary.sh +17 -1
  9. package/autonomy/lib/setup-requirements.json +35 -0
  10. package/autonomy/loki +244 -237
  11. package/autonomy/notification-checker.py +1 -1
  12. package/autonomy/provider-offer.sh +95 -47
  13. package/autonomy/route.sh +38 -6
  14. package/autonomy/run.sh +42 -14
  15. package/autonomy/setup-wizard.sh +19 -10
  16. package/autonomy/verify.sh +40 -1
  17. package/completions/_loki +3 -1
  18. package/completions/loki.bash +9 -3
  19. package/dashboard/__init__.py +3 -10
  20. package/dashboard/control.py +5 -435
  21. package/docs/ARCHITECTURE.md +1 -2
  22. package/docs/CLI-CORE-VERBS.md +2 -6
  23. package/docs/CLI-REFERENCE.md +3 -1
  24. package/docs/COMPONENTS.md +0 -2
  25. package/docs/EVALUATING.md +27 -0
  26. package/docs/HARNESS-INTELLIGENCE-PLAN.md +20 -0
  27. package/docs/INSTALLATION.md +1 -1
  28. package/docs/KNOWN-ISSUES.md +10 -1
  29. package/docs/PROOF-PASSPORT.md +114 -0
  30. package/docs/alternative-installations.md +103 -0
  31. package/docs/authentication.md +19 -1
  32. package/docs/cost-controls.md +139 -0
  33. package/docs/dev/release-checklist.md +9 -8
  34. package/docs/enterprise/security.md +3 -234
  35. package/docs/environment-variables.md +36 -0
  36. package/docs/exit-codes.md +102 -2
  37. package/docs/spec-first-intent.md +29 -0
  38. package/docs/v10/ARCHITECT-CUTS.md +304 -0
  39. package/docs/v10/CLI-MODERN.md +2 -2
  40. package/docs/v10/DECISIONS.md +5 -5
  41. package/docs/v10/ENGINE.md +735 -267
  42. package/docs/v10/GUARDS.md +16 -14
  43. package/docs/v10/GUIDE.md +5 -7
  44. package/docs/v10/PROGRESS.md +12 -1
  45. package/docs/v11/MARKET-SLICES.md +1 -5
  46. package/integrations/openclaw/README.md +20 -0
  47. package/integrations/vibe-kanban.md +12 -50
  48. package/loki-ts/data/model-pricing.json +1 -0
  49. package/loki-ts/dist/loki.js +1156 -1103
  50. package/mcp/__init__.py +1 -1
  51. package/mcp/_sdk_loader.py +1 -1
  52. package/package.json +2 -2
  53. package/packages/control-plane/dist/server.js +2063 -1032
  54. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  55. package/plugins/loki-mode/README.md +41 -1
  56. package/plugins/loki-mode/commands/handoff.md +12 -0
  57. package/plugins/loki-mode/commands/loki-verify.md +9 -2
  58. package/plugins/loki-mode/commands/start.md +20 -0
  59. package/plugins/loki-mode/hooks/hooks.json +11 -0
  60. package/plugins/loki-mode/scripts/loki-stop-verify.sh +84 -0
  61. package/references/quality-control.md +1 -116
  62. package/skills/00-index.md +2 -2
  63. package/skills/artifacts.md +0 -22
  64. package/skills/production.md +2 -38
  65. package/web-app/dist/assets/{AdminPage-CYJ2BWRC.js → AdminPage-DhXdJNQk.js} +1 -1
  66. package/web-app/dist/assets/{Avatar-DdK4wynI.js → Avatar-ixVj76fM.js} +1 -1
  67. package/web-app/dist/assets/{Badge-CrX3MI7J.js → Badge-BGMSp22s.js} +1 -1
  68. package/web-app/dist/assets/{Button-709-wwGM.js → Button-pYJc32tG.js} +1 -1
  69. package/web-app/dist/assets/{CockpitPage-BapdTInO.js → CockpitPage-C1JBBouA.js} +1 -1
  70. package/web-app/dist/assets/{ComparePage-DT99pGvA.js → ComparePage-qD0NV4RK.js} +1 -1
  71. package/web-app/dist/assets/{ErrorBoundary-B0uJSys8.js → ErrorBoundary-C-yxO58j.js} +1 -1
  72. package/web-app/dist/assets/{EvidenceReceiptPanel-BtMFgElP.js → EvidenceReceiptPanel-ByPzuE1S.js} +1 -1
  73. package/web-app/dist/assets/{GitHubIssuesPanel-DjJuU941.js → GitHubIssuesPanel-BNv_2BEJ.js} +1 -1
  74. package/web-app/dist/assets/{GitHubPRsPanel-HXfEX1BC.js → GitHubPRsPanel-CuLZQ9CS.js} +1 -1
  75. package/web-app/dist/assets/{HomePage-Cqyves-S.js → HomePage-CS_FW8Je.js} +2 -2
  76. package/web-app/dist/assets/{LoginPage-jUbtC7_Y.js → LoginPage-DwiNf1Wm.js} +1 -1
  77. package/web-app/dist/assets/{MagicPage-DHRsqjgR.js → MagicPage-CmQybLPZ.js} +1 -1
  78. package/web-app/dist/assets/{MetricsPage-B-pED05R.js → MetricsPage-D9ERdpv8.js} +1 -1
  79. package/web-app/dist/assets/{NotFoundPage-CsbUs21R.js → NotFoundPage-D9ccgdbF.js} +1 -1
  80. package/web-app/dist/assets/{ProjectPage-BtlLEQwB.js → ProjectPage-B9Jslmgv.js} +3 -3
  81. package/web-app/dist/assets/{ProjectsPage-DaRbRjSp.js → ProjectsPage-DnU99Go2.js} +1 -1
  82. package/web-app/dist/assets/{SettingsPage-DALrtP3F.js → SettingsPage-DcTyx6wo.js} +1 -1
  83. package/web-app/dist/assets/{ShowcasePage-CoPHSNP0.js → ShowcasePage-jEY9ghSr.js} +1 -1
  84. package/web-app/dist/assets/{SystemSettingsPage-kOuSN50O.js → SystemSettingsPage-CSGltW-8.js} +1 -1
  85. package/web-app/dist/assets/{TeamsPage-DGWK0LMp.js → TeamsPage-gUuJXb9l.js} +1 -1
  86. package/web-app/dist/assets/{TemplatesPage-C-_10CwA.js → TemplatesPage-37Zhe--v.js} +1 -1
  87. package/web-app/dist/assets/{TerminalOutput-Cz_vPbXL.js → TerminalOutput-B09hRwaR.js} +1 -1
  88. package/web-app/dist/assets/{activity-icIsXJVa.js → activity-q_4MY9J0.js} +1 -1
  89. package/web-app/dist/assets/{bell-DbPR2_4r.js → bell-X8OC_PFP.js} +1 -1
  90. package/web-app/dist/assets/{bot-DvjP3Yh5.js → bot-C-N1PUOD.js} +1 -1
  91. package/web-app/dist/assets/{check-D1OBxHeU.js → check-B2c76Jmp.js} +1 -1
  92. package/web-app/dist/assets/{chevron-left-D09jDZ29.js → chevron-left-BF76YLNw.js} +1 -1
  93. package/web-app/dist/assets/{circle-alert-DyZMLOcC.js → circle-alert-AeFKBD8h.js} +1 -1
  94. package/web-app/dist/assets/{clock-CdpdJTId.js → clock-CiXRf-FH.js} +1 -1
  95. package/web-app/dist/assets/{cloud-CoLTlmoj.js → cloud-DKQ8SRJ5.js} +1 -1
  96. package/web-app/dist/assets/{code-xml-DrkE2ZZY.js → code-xml-DEb6jXWP.js} +1 -1
  97. package/web-app/dist/assets/{database-CMQQuM2R.js → database-6W7e-E9c.js} +1 -1
  98. package/web-app/dist/assets/{dollar-sign-CuioDaOG.js → dollar-sign-Ds2GOuyW.js} +1 -1
  99. package/web-app/dist/assets/{file-code-corner-Cx9j9K1S.js → file-code-corner-CZU0jg52.js} +1 -1
  100. package/web-app/dist/assets/{file-plus-CYD2zaaI.js → file-plus-DTVYnXCN.js} +1 -1
  101. package/web-app/dist/assets/{globe-6w-hnQkL.js → globe-0mUzKYDe.js} +1 -1
  102. package/web-app/dist/assets/{hammer-BpqtautL.js → hammer-BMHZg3YX.js} +1 -1
  103. package/web-app/dist/assets/{index-Dzs-9EgJ.js → index-BvOTvjax.js} +3 -3
  104. package/web-app/dist/assets/{layers-D5hd7jb7.js → layers-Df7TdcWB.js} +1 -1
  105. package/web-app/dist/assets/{loader-circle-C6Y9x_7H.js → loader-circle-kPsVfrSY.js} +1 -1
  106. package/web-app/dist/assets/{lock-D51L7rYh.js → lock-CXYcbEc5.js} +1 -1
  107. package/web-app/dist/assets/{package-BJF8hkP8.js → package-Cqhua1ID.js} +1 -1
  108. package/web-app/dist/assets/{plus-D5kUQ79n.js → plus-lseXhsn-.js} +1 -1
  109. package/web-app/dist/assets/{refresh-cw-BKdlCa7F.js → refresh-cw-OZriI6gD.js} +1 -1
  110. package/web-app/dist/assets/{rotate-ccw-_bd_YI1e.js → rotate-ccw-CKCfjPg8.js} +1 -1
  111. package/web-app/dist/assets/{scroll-text-DbkegIxF.js → scroll-text-DoVyyfKb.js} +1 -1
  112. package/web-app/dist/assets/{server-DAwqFIsq.js → server-DGy7TflW.js} +1 -1
  113. package/web-app/dist/assets/{shield-alert-BuR1tPHu.js → shield-alert-BCzRcMXi.js} +1 -1
  114. package/web-app/dist/assets/{thumbs-up-BF6p3mRd.js → thumbs-up-C_znJd5g.js} +1 -1
  115. package/web-app/dist/assets/{trash-2-BVpnkqqz.js → trash-2-BosCbeeA.js} +1 -1
  116. package/web-app/dist/assets/{trending-down-DpsDvRwW.js → trending-down-CkR5zDkV.js} +1 -1
  117. package/web-app/dist/assets/{trending-up-mFogTjt3.js → trending-up-DpUIfOH4.js} +1 -1
  118. package/web-app/dist/assets/{upload-4T8zpBsi.js → upload-CH9aKc8L.js} +1 -1
  119. package/web-app/dist/assets/{usePolling-DPXQv8WO.js → usePolling-pzxrHJeg.js} +1 -1
  120. package/web-app/dist/assets/{user-BYGIFaTm.js → user-B7OHa-VQ.js} +1 -1
  121. package/web-app/dist/index.html +1 -1
  122. package/dashboard/run.py +0 -63
  123. package/docs/openclaw-integration.md +0 -52
  124. package/docs/v10/HANDOFF-NEXT-WEEK.md +0 -40
  125. package/docs/v10/MORNING-BRIEF-1003.md +0 -25
  126. package/docs/v10/MORNING-BRIEF.md +0 -52
  127. package/docs/v10/TODO.md +0 -35
  128. package/mcp/resources.py +0 -351
  129. package/mcp/tools.py +0 -200
@@ -265,7 +265,7 @@ def check_quality_gate(trigger, loki_dir, iteration, notifications):
265
265
  defect as the budget key, in the same file.
266
266
 
267
267
  gate-failures.txt is the canonical failure source used by
268
- proof-generator.py:413 and loki-ts build_prompt_helpers.ts:250, so this
268
+ proof-generator.py:413 and loki-ts build_prompt.ts, so this
269
269
  reuses a proven contract rather than introducing a second one.
270
270
  """
271
271
  failures_file = Path(loki_dir) / "quality" / "gate-failures.txt"
@@ -80,6 +80,60 @@ detect_any_provider() {
80
80
  return 1
81
81
  }
82
82
 
83
+ # sdk_binary_present: true (0) when an executable Claude Agent SDK platform binary exists
84
+ # in an installed node_modules. Shared by detect_bundled_sdk_provider and
85
+ # loki_requirements_json.
86
+ sdk_binary_present() {
87
+ # Search the node_modules trees Loki is actually installed into. The root is
88
+ # derived from THIS FILE's location (self-containment contract, see header):
89
+ # SKILL_DIR belongs to autonomy/loki and is NOT exported, so it would be
90
+ # empty when doctor.ts spawns this script standalone -- which would silently
91
+ # answer "no SDK" on the Bun route while the bash route said yes, exactly the
92
+ # kind of route split the parity gate exists to prevent.
93
+ # LOKI_SDK_NODE_MODULES is a TEST-ONLY seam (never set in production) so the
94
+ # shell tests can point at a fixture tree without mutating real node_modules.
95
+ local _po_root
96
+ _po_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" || _po_root=""
97
+ # Array, not a word-split string: install roots can contain spaces.
98
+ local _po_search=()
99
+ if [ -n "${LOKI_SDK_NODE_MODULES:-}" ]; then
100
+ # When the test seam is set it is AUTHORITATIVE (not merely first): the
101
+ # real tree must not be consulted as a fallback, or a fixture asserting
102
+ # "no usable SDK" would silently pass by finding the repo's own binary.
103
+ _po_search=("$LOKI_SDK_NODE_MODULES")
104
+ elif [ -n "$_po_root" ]; then
105
+ # Two layouts, both real:
106
+ # repo clone -> <root>/node_modules, <root>/loki-ts/node_modules
107
+ # npm install -> npm HOISTS the SDK out of node_modules/loki-mode/
108
+ # up to the installing tree's top-level
109
+ # node_modules/ (verified by packing this repo and
110
+ # installing the tarball). Without the hoisted
111
+ # candidates the predicate silently returns 1 for
112
+ # every npm-installed user, i.e. the feature would
113
+ # be dead for exactly the audience it targets.
114
+ # <root> is .../node_modules/loki-mode, so ../ is the @scope-less
115
+ # top-level node_modules and ../../ covers a scoped install.
116
+ _po_search=(
117
+ "$_po_root/node_modules"
118
+ "$_po_root/loki-ts/node_modules"
119
+ "$_po_root/.."
120
+ "$_po_root/../.."
121
+ )
122
+ fi
123
+ local _nm
124
+ for _nm in ${_po_search+"${_po_search[@]}"}; do
125
+ [ -n "$_nm" ] || continue
126
+ set -- "$_nm"/@anthropic-ai/claude-agent-sdk-*/claude
127
+ # Unmatched globs stay literal in bash, so test the expansion for real.
128
+ while [ "$#" -gt 0 ]; do
129
+ [ -x "$1" ] && return 0
130
+ shift
131
+ done
132
+ done
133
+ return 1
134
+ }
135
+
136
+
83
137
  # detect_bundled_sdk_provider: true (0) only when the bundled Claude Agent SDK
84
138
  # can ACTUALLY run this machine's main loop with no separate CLI install.
85
139
  #
@@ -130,53 +184,7 @@ detect_bundled_sdk_provider() {
130
184
  return 1
131
185
  fi
132
186
 
133
- # Search the node_modules trees Loki is actually installed into. The root is
134
- # derived from THIS FILE's location (self-containment contract, see header):
135
- # SKILL_DIR belongs to autonomy/loki and is NOT exported, so it would be
136
- # empty when doctor.ts spawns this script standalone -- which would silently
137
- # answer "no SDK" on the Bun route while the bash route said yes, exactly the
138
- # kind of route split the parity gate exists to prevent.
139
- # LOKI_SDK_NODE_MODULES is a TEST-ONLY seam (never set in production) so the
140
- # shell tests can point at a fixture tree without mutating real node_modules.
141
- local _po_root
142
- _po_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" || _po_root=""
143
- # Array, not a word-split string: install roots can contain spaces.
144
- local _po_search=()
145
- if [ -n "${LOKI_SDK_NODE_MODULES:-}" ]; then
146
- # When the test seam is set it is AUTHORITATIVE (not merely first): the
147
- # real tree must not be consulted as a fallback, or a fixture asserting
148
- # "no usable SDK" would silently pass by finding the repo's own binary.
149
- _po_search=("$LOKI_SDK_NODE_MODULES")
150
- elif [ -n "$_po_root" ]; then
151
- # Two layouts, both real:
152
- # repo clone -> <root>/node_modules, <root>/loki-ts/node_modules
153
- # npm install -> npm HOISTS the SDK out of node_modules/loki-mode/
154
- # up to the installing tree's top-level
155
- # node_modules/ (verified by packing this repo and
156
- # installing the tarball). Without the hoisted
157
- # candidates the predicate silently returns 1 for
158
- # every npm-installed user, i.e. the feature would
159
- # be dead for exactly the audience it targets.
160
- # <root> is .../node_modules/loki-mode, so ../ is the @scope-less
161
- # top-level node_modules and ../../ covers a scoped install.
162
- _po_search=(
163
- "$_po_root/node_modules"
164
- "$_po_root/loki-ts/node_modules"
165
- "$_po_root/.."
166
- "$_po_root/../.."
167
- )
168
- fi
169
- local _nm
170
- for _nm in ${_po_search+"${_po_search[@]}"}; do
171
- [ -n "$_nm" ] || continue
172
- set -- "$_nm"/@anthropic-ai/claude-agent-sdk-*/claude
173
- # Unmatched globs stay literal in bash, so test the expansion for real.
174
- while [ "$#" -gt 0 ]; do
175
- [ -x "$1" ] && return 0
176
- shift
177
- done
178
- done
179
- return 1
187
+ sdk_binary_present
180
188
  }
181
189
 
182
190
  # _po_assume_yes: true when the user has opted into unattended confirmation.
@@ -545,6 +553,46 @@ print(json.dumps({"selected": selected, "providers": rows}, indent=2))
545
553
  ) || return 1
546
554
  }
547
555
 
556
+ # loki_requirements_json: print the setup requirements report (shared table
557
+ # autonomy/lib/setup-requirements.json) as one JSON object, matching the "requirements"
558
+ # key of the Bun doctor (loki-ts/src/commands/doctor.ts). Used by the minimal
559
+ # `loki doctor --json` fallback when the Bun doctor cannot run. Presence only: unlike the
560
+ # Bun evaluator it does not compare versions.
561
+ loki_requirements_json() {
562
+ local table statuses="" id kind target values st v
563
+ table="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/lib/setup-requirements.json"
564
+ if [ ! -f "$table" ] || ! command -v jq >/dev/null 2>&1; then
565
+ printf 'null\n'
566
+ return 0
567
+ fi
568
+ while IFS=$'\t' read -r id kind target values; do
569
+ st=missing
570
+ case "$kind" in
571
+ command) command -v "$target" >/dev/null 2>&1 && st=present ;;
572
+ env_any)
573
+ for v in ${target//,/ }; do [ -n "${!v:-}" ] && st=present; done ;;
574
+ env_value)
575
+ for v in ${target//,/ }; do
576
+ case ",${values}," in *",${!v:-},"*) [ -n "${!v:-}" ] && st=present ;; esac
577
+ done ;;
578
+ sdk_binary) sdk_binary_present && st=present ;;
579
+ esac
580
+ statuses="${statuses}${id} ${st}"$'\n'
581
+ done < <(jq -r '.requirements[] | [.id, .kind, (.target | if type == "array" then join(",") else . end), ((.values // []) | join(","))] | @tsv' "$table")
582
+
583
+ jq -n --rawfile st <(printf '%s' "$statuses") --slurpfile t "$table" '
584
+ ($st | split("\n") | map(select(length > 0) | split(" ") | {(.[0]): .[1]}) | add) as $s
585
+ | $t[0] as $tab
586
+ | {paths: [ $tab.paths[] as $p
587
+ | ([ $tab.requirements[] | select(.path == $p.id)
588
+ | {id, label, level, status: $s[.id], detail: (if $s[.id] == "present" then null else "not found by presence check" end), fix} ]) as $items
589
+ | ([ $items[] | select(.level == "required") ]) as $req
590
+ | {id: $p.id, title: $p.title,
591
+ ready: (if ($req | length) > 0 then ($req | all(.status == "present")) else ($items | any(.status == "present")) end),
592
+ items: $items} ]}
593
+ | . + {first_task_ready: ((.paths[] | select(.id == "common") | .ready) and ([.paths[] | select(.id != "common") | .ready] | any))}'
594
+ }
595
+
548
596
  # Executed directly (doctor.ts child_process bridge, or manual): run the offer.
549
597
  # When sourced by autonomy/loki, this block does not run.
550
598
  if [ "${BASH_SOURCE[0]}" = "$0" ]; then
package/autonomy/route.sh CHANGED
@@ -45,7 +45,7 @@ loki_route_resolve() {
45
45
  local verb="$1"; shift
46
46
  R_DECIDED=0; R_E10=0; R_HARD=0; R_ENGINE=""; R_REASON=""; R_INPUT_TYPE=""; R_INPUT=""
47
47
  R_PROVIDER=""; R_MODEL=""; R_NOTICE=""; R_ERR=""; R_EXPLAIN=0; R_JSON=0; R_SESSION_MODEL=""; R_VERB=""
48
- R_E10_ARGS=(); R_CLEAN=()
48
+ R_E10_ARGS=(); R_CLEAN=(); R_ADAPTER=""; R_CAPDIFF=()
49
49
 
50
50
  local a engine_req="" help=0 legacy_flag="" session_model="" provider_flag=""
51
51
  local -a positionals=() passthru=()
@@ -208,21 +208,53 @@ loki_route_resolve() {
208
208
  R_REASON="default: $R_INPUT_TYPE input runs on engine10 regardless of flag order"
209
209
  fi
210
210
  fi
211
+ _route_adapter_and_diffs "$legacy_flag"
211
212
  return 0
212
213
  }
213
214
 
215
+ # Sets R_ADAPTER and R_CAPDIFF[] from R_ENGINE / R_PROVIDER. Empty R_CAPDIFF means the
216
+ # full Claude Code path on the Loki 10 engine. Statements mirror providers/*.sh
217
+ # (PROVIDER_HAS_TASK_TOOL=false for every non-claude provider).
218
+ _route_adapter_and_diffs() {
219
+ local legacy_flag="$1"
220
+ R_CAPDIFF=()
221
+ if [ "$R_ENGINE" = "legacy" ]; then
222
+ R_ADAPTER="legacy-bash:$R_PROVIDER"
223
+ R_CAPDIFF+=("runs the previous bash loop (autonomy/run.sh) instead of the Loki 10 supervisor")
224
+ if [ -n "$legacy_flag" ]; then
225
+ R_CAPDIFF+=("honors $legacy_flag, which engine10 does not support")
226
+ fi
227
+ else
228
+ R_ADAPTER="engine10:$R_PROVIDER"
229
+ fi
230
+ case "$R_PROVIDER" in
231
+ claude) ;;
232
+ codex|cline|aider|opencode)
233
+ R_CAPDIFF+=("provider $R_PROVIDER has no Claude Code Task-tool subagents (providers/$R_PROVIDER.sh)") ;;
234
+ *)
235
+ R_CAPDIFF+=("provider $R_PROVIDER has no engine10 invoker") ;;
236
+ esac
237
+ }
238
+
214
239
  # Print the route (human or --json). Returns 0.
215
240
  loki_route_print() {
216
241
  local shown="$R_INPUT"
217
242
  if [ "${#shown}" -gt 72 ]; then shown="$(printf '%s' "$shown" | cut -c1-69)..."; fi
218
243
  if [ "$R_JSON" = "1" ]; then
219
- printf '{"engine":"%s","provider":"%s","model":"%s","input_type":"%s","input":"%s","reason":"%s"}\n' \
220
- "$(_route_json_str "$R_ENGINE")" "$(_route_json_str "$R_PROVIDER")" "$(_route_json_str "$R_MODEL")" \
221
- "$(_route_json_str "$R_INPUT_TYPE")" "$(_route_json_str "$shown")" "$(_route_json_str "$R_REASON")"
244
+ local diffs="" d
245
+ for d in ${R_CAPDIFF[@]+"${R_CAPDIFF[@]}"}; do
246
+ diffs="${diffs:+$diffs,}\"$(_route_json_str "$d")\""
247
+ done
248
+ printf '{"engine":"%s","adapter":"%s","provider":"%s","model":"%s","input_type":"%s","input":"%s","reason":"%s","capability_differences":[%s]}\n' \
249
+ "$(_route_json_str "$R_ENGINE")" "$(_route_json_str "$R_ADAPTER")" "$(_route_json_str "$R_PROVIDER")" "$(_route_json_str "$R_MODEL")" \
250
+ "$(_route_json_str "$R_INPUT_TYPE")" "$(_route_json_str "$shown")" "$(_route_json_str "$R_REASON")" "$diffs"
222
251
  else
223
- printf 'engine: %s\nprovider: %s\nmodel: %s\ninput: %s (%s)\nreason: %s\n' \
224
- "$R_ENGINE" "$R_PROVIDER" "$R_MODEL" "$R_INPUT_TYPE" "$shown" "$R_REASON"
252
+ printf 'engine: %s\nadapter: %s\nprovider: %s\nmodel: %s\ninput: %s (%s)\nreason: %s\n' \
253
+ "$R_ENGINE" "$R_ADAPTER" "$R_PROVIDER" "$R_MODEL" "$R_INPUT_TYPE" "$shown" "$R_REASON"
254
+ local d
255
+ for d in ${R_CAPDIFF[@]+"${R_CAPDIFF[@]}"}; do printf 'differs: %s\n' "$d"; done
225
256
  fi
257
+ return 0
226
258
  }
227
259
 
228
260
  # Entry used by bin/loki. Args: the full original argv. Sets R_* and returns:
package/autonomy/run.sh CHANGED
@@ -31,7 +31,7 @@
31
31
  # LOKI_RESOURCE_MEM_THRESHOLD - Memory % threshold to warn (default: 80)
32
32
  #
33
33
  # Budget / Cost Limits (opt-in):
34
- # LOKI_BUDGET_LIMIT - Max USD spend before auto-pause (default: empty = unlimited)
34
+ # LOKI_BUDGET_LIMIT - Max USD spend before auto-pause (default: loki-ts/data/model-pricing.json default_max_cost_usd, 100 USD; empty = unlimited)
35
35
  # Example: "50.00" pauses session when estimated cost >= $50
36
36
  #
37
37
  # Security & Autonomy Controls (Enterprise):
@@ -532,7 +532,7 @@ for key, env_var in mapping.items():
532
532
  val = get_nested(data, key) or data.get(key) or data.get(key.replace('.', '_'))
533
533
  if val and isinstance(val, str):
534
534
  safe_val = shlex.quote(val)
535
- print(f'[ -z \"\${{{env_var}:-}}\" ] && export {env_var}={safe_val}')
535
+ print(f'[ -z \"\${{{env_var}:-}}\" ] && export {env_var}={safe_val}' + (\" && export LOKI_BUDGET_SOURCE=config\" if env_var == 'LOKI_BUDGET_LIMIT' else ''))
536
536
  " 2>/dev/null)" 2>/dev/null || true
537
537
  }
538
538
  _LOKI_SETTINGS_FILE="${TARGET_DIR:-.}/.loki/config/settings.json" _load_json_settings
@@ -565,7 +565,23 @@ RESOURCE_MEM_THRESHOLD=${LOKI_RESOURCE_MEM_THRESHOLD:-80} # Memory % thresho
565
565
  # it never kills work or discards a deliverable. The user removes .loki/PAUSE
566
566
  # to resume, or raises LOKI_BUDGET_LIMIT. Set LOKI_BUDGET_LIMIT="" to restore
567
567
  # the old unlimited behavior explicitly.
568
- BUDGET_LIMIT=${LOKI_BUDGET_LIMIT-"100.00"} # USD. Note `-` not `:-`: an
568
+ # LM-002: the default value is read from default_max_cost_usd in loki-ts/data/model-pricing.json, the same
569
+ # file the Loki 10 engine reads (docs/cost-controls.md). "100.00" is only the
570
+ # fallback when that file is absent, and budget_cap.test.ts pins it to the file.
571
+ _loki_budget_default="$(sed -n 's/.*"default_max_cost_usd"[[:space:]]*:[[:space:]]*\([0-9][0-9.]*\).*/\1/p' \
572
+ "${SCRIPT_DIR}/../loki-ts/data/model-pricing.json" 2>/dev/null | head -n 1)"
573
+ case "$_loki_budget_default" in
574
+ '' | *[!0-9.]*) _loki_budget_default="100.00" ;;
575
+ *) _loki_budget_default="$(awk -v v="$_loki_budget_default" 'BEGIN{printf "%.2f", v}')" ;;
576
+ esac
577
+ # Where the active cap came from: flag (loki start --budget), config (settings.json),
578
+ # env (LOKI_BUDGET_LIMIT set by the caller), or default. Printed once at run start.
579
+ if [ "${LOKI_BUDGET_LIMIT+set}" = "set" ]; then
580
+ BUDGET_SOURCE="${LOKI_BUDGET_SOURCE:-env}"
581
+ else
582
+ BUDGET_SOURCE="default"
583
+ fi
584
+ BUDGET_LIMIT=${LOKI_BUDGET_LIMIT-"$_loki_budget_default"} # USD. Note `-` not `:-`: an
569
585
  # explicit empty value means the
570
586
  # operator chose unlimited, and is
571
587
  # honored.
@@ -7536,7 +7552,7 @@ EOF
7536
7552
  "created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
7537
7553
  }
7538
7554
  BUDGET_EOF
7539
- log_info "Budget limit set: \$$BUDGET_LIMIT"
7555
+ log_info "Budget limit set: \$$BUDGET_LIMIT (source: ${BUDGET_SOURCE:-default})"
7540
7556
  fi
7541
7557
 
7542
7558
  # v7.0.0 Phase 2: remote->local hydrate. Runs ONCE at session boot (not
@@ -9041,21 +9057,25 @@ _read_iteration_cost() {
9041
9057
  if [ -f "$result_cost_file" ]; then
9042
9058
  python3 -c "
9043
9059
  import json
9060
+ def _c(v):
9061
+ return v if isinstance(v, (int, float)) and not isinstance(v, bool) else 'unmeasured'
9044
9062
  try:
9045
9063
  d = json.load(open('$result_cost_file'))
9046
9064
  print(
9047
9065
  d.get('input_tokens', 0) or 0,
9048
9066
  d.get('output_tokens', 0) or 0,
9049
- d.get('total_cost_usd', 0) or 0,
9067
+ _c(d.get('total_cost_usd')),
9050
9068
  d.get('cache_read_tokens', 0) or 0,
9051
9069
  d.get('cache_creation_tokens', 0) or 0,
9052
9070
  )
9053
9071
  except Exception:
9054
- print(0, 0, 0, 0, 0)
9055
- " 2>/dev/null || echo "0 0 0 0 0"
9072
+ print(0, 0, 'unmeasured', 0, 0)
9073
+ " 2>/dev/null || echo "0 0 unmeasured 0 0"
9056
9074
  elif [ -f ".loki/context/tracking.json" ]; then
9057
9075
  python3 -c "
9058
9076
  import json
9077
+ def _c(v):
9078
+ return v if isinstance(v, (int, float)) and not isinstance(v, bool) else 'unmeasured'
9059
9079
  try:
9060
9080
  t = json.load(open('.loki/context/tracking.json'))
9061
9081
  iters = t.get('per_iteration', [])
@@ -9065,17 +9085,17 @@ try:
9065
9085
  print(
9066
9086
  m.get('input_tokens', 0),
9067
9087
  m.get('output_tokens', 0),
9068
- m.get('cost_usd', 0),
9088
+ _c(m.get('cost_usd')),
9069
9089
  m.get('cache_read_tokens', 0),
9070
9090
  m.get('cache_creation_tokens', 0),
9071
9091
  )
9072
9092
  else:
9073
- print(0, 0, 0, 0, 0)
9093
+ print(0, 0, 'unmeasured', 0, 0)
9074
9094
  except Exception:
9075
- print(0, 0, 0, 0, 0)
9076
- " 2>/dev/null || echo "0 0 0 0 0"
9095
+ print(0, 0, 'unmeasured', 0, 0)
9096
+ " 2>/dev/null || echo "0 0 unmeasured 0 0"
9077
9097
  else
9078
- echo "0 0 0 0 0"
9098
+ echo "0 0 unmeasured 0 0"
9079
9099
  fi
9080
9100
  }
9081
9101
 
@@ -9192,6 +9212,15 @@ track_iteration_complete() {
9192
9212
  $iter_cost_line
9193
9213
  ITER_COST_EOF
9194
9214
 
9215
+ # Unknown cost is omitted with a reason, never written as 0 (readers treat absence as unmeasured).
9216
+ local iter_cost_json
9217
+ case "${iter_cost:-unmeasured}" in
9218
+ unmeasured) iter_cost_json=' "cost_unmeasured_reason": "no dollar figure recorded for this iteration",
9219
+ ' ;;
9220
+ *) iter_cost_json=" \"cost_usd\": ${iter_cost},
9221
+ " ;;
9222
+ esac
9223
+
9195
9224
  cat > ".loki/metrics/efficiency/iteration-${iteration}.json" << EFF_EOF
9196
9225
  {
9197
9226
  "iteration": $iteration,
@@ -9204,8 +9233,7 @@ ITER_COST_EOF
9204
9233
  "output_tokens": ${iter_output:-0},
9205
9234
  "cache_read_tokens": ${iter_cache_read:-0},
9206
9235
  "cache_creation_tokens": ${iter_cache_creation:-0},
9207
- "cost_usd": ${iter_cost:-0},
9208
- "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
9236
+ ${iter_cost_json} "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
9209
9237
  }
9210
9238
  EFF_EOF
9211
9239
 
@@ -4,7 +4,7 @@
4
4
  # file. `loki welcome` (intro), `loki quickstart` (first build) and
5
5
  # `loki doctor` (health) remain separate entry points.
6
6
  #
7
- # REQUIRED (doctor fails without them): node >= 18, python3 >= 3.8, jq, git, curl
7
+ # REQUIRED (doctor fails without them): node >= 20, python3 >= 3.8, jq, git, curl
8
8
  # AT LEAST ONE of: a provider CLI (claude, codex, cline, aider, opencode) or the
9
9
  # bundled Claude Agent SDK with credentials. Claude Code is the default.
10
10
  # OPTIONAL: bun (bundled by npm), bash >= 4, codex/cline/aider/opencode, ollama.
@@ -31,6 +31,14 @@ _lsw_confirm() {
31
31
  case "$ans" in y|Y|yes|YES) return 0 ;; *) return 1 ;; esac
32
32
  }
33
33
 
34
+ _lsw_fix() {
35
+ # _lsw_fix ID FALLBACK -> the one-line fix from the shared requirements table
36
+ # (autonomy/lib/setup-requirements.json), the same text loki doctor prints.
37
+ local out=""
38
+ out="$(jq -r --arg id "$1" '.requirements[] | select(.id == $id) | .fix' "${SKILL_DIR}/autonomy/lib/setup-requirements.json" 2>/dev/null | head -n 1)" || out=""
39
+ printf '%s' "${out:-$2}"
40
+ }
41
+
34
42
  _lsw_tool() {
35
43
  # _lsw_tool NAME LABEL INSTALL_HINT
36
44
  if command -v "$1" >/dev/null 2>&1; then
@@ -79,12 +87,12 @@ cmd_setup() {
79
87
  echo "Loki Mode setup"
80
88
  echo ""
81
89
  echo "1. Required tools"
82
- _lsw_tool node REQUIRED "https://nodejs.org (Node 20+ recommended, 18 minimum)" || true
83
- _lsw_tool python3 REQUIRED "brew install python3, or your package manager" || true
84
- _lsw_tool jq REQUIRED "brew install jq, or your package manager" || true
85
- _lsw_tool git REQUIRED "brew install git, or your package manager" || true
86
- _lsw_tool curl REQUIRED "brew install curl, or your package manager" || true
87
- _lsw_tool bun OPTIONAL "bundled by npm; else see https://bun.sh" || true
90
+ _lsw_tool node REQUIRED "$(_lsw_fix node "install Node 20+ from https://nodejs.org")" || true
91
+ _lsw_tool python3 REQUIRED "$(_lsw_fix python3 "install python3")" || true
92
+ _lsw_tool jq REQUIRED "$(_lsw_fix jq "install jq")" || true
93
+ _lsw_tool git REQUIRED "$(_lsw_fix git "install git")" || true
94
+ _lsw_tool curl REQUIRED "$(_lsw_fix curl "install curl")" || true
95
+ _lsw_tool bun OPTIONAL "$(_lsw_fix bun "bundled by npm; else see https://bun.sh")" || true
88
96
  if [ "$_LSW_MISSING" -gt 0 ]; then
89
97
  echo ""
90
98
  echo "Install the missing REQUIRED tools above with your package manager (setup never installs system packages), then rerun: loki setup"
@@ -95,7 +103,7 @@ cmd_setup() {
95
103
  echo "2. Provider CLI (Claude Code is the default and the only Tier-1 provider)"
96
104
  local p
97
105
  for p in codex cline aider opencode; do
98
- _lsw_tool "$p" OPTIONAL "see 'loki doctor' for the command" || true
106
+ _lsw_tool "$p" OPTIONAL "$(_lsw_fix "$p" "see 'loki doctor' for the command")" || true
99
107
  done
100
108
  if command -v claude >/dev/null 2>&1; then
101
109
  printf ' [ OK ] %-10s DEFAULT\n' claude
@@ -108,7 +116,7 @@ cmd_setup() {
108
116
  fi
109
117
  fi
110
118
  else
111
- echo " [MISS] claude DEFAULT. Install: npm install -g @anthropic-ai/claude-code (then: claude auth login)"
119
+ echo " [MISS] claude DEFAULT. Install: $(_lsw_fix claude "npm install -g @anthropic-ai/claude-code") (then: claude auth login)"
112
120
  if [ "$check_only" = 0 ] && [ -f "${SKILL_DIR}/autonomy/provider-offer.sh" ]; then
113
121
  if _lsw_confirm "Run that install command now?"; then
114
122
  # shellcheck disable=SC1091
@@ -118,7 +126,8 @@ cmd_setup() {
118
126
  fi
119
127
  if ! command -v claude >/dev/null 2>&1; then
120
128
  echo " Claude Code is not mandatory if another provider above is installed,"
121
- echo " or if the bundled Claude Agent SDK has credentials."
129
+ echo " or if you use the Anthropic API-key path (LOKI_SDK_MODE=full with ANTHROPIC_API_KEY)."
130
+ echo " Requirements per path: loki doctor --requirements"
122
131
  fi
123
132
  fi
124
133
 
@@ -2268,6 +2268,7 @@ verify_emit_evidence() {
2268
2268
  _V_SCOPE_SOFT_NET="${VERIFY_SCOPE_SOFT_NET_LINES:-}" \
2269
2269
  _V_SCOPE_MAX_FILES="${VERIFY_SCOPE_MAX_FILES:-}" \
2270
2270
  _V_SCOPE_MAX_NET="${VERIFY_SCOPE_MAX_NET_LINES:-}" \
2271
+ _V_LIBDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib" \
2271
2272
  _verify_py - <<'PYEOF'
2272
2273
  import json, os, hashlib, sys
2273
2274
 
@@ -2401,6 +2402,27 @@ if _scope_verdict:
2401
2402
  "thresholds": _thresholds,
2402
2403
  }
2403
2404
 
2405
+ # Verification record (docs/PROOF-PASSPORT.md): integrity, execution
2406
+ # validity (revision + environment) and requirement coverage, kept separate, plus
2407
+ # per-check status and a scoped label. Additive; verdict/exit_code are untouched.
2408
+ # A failure to build it is recorded, never swallowed into a clean-looking doc.
2409
+ try:
2410
+ sys.path.insert(0, os.environ["_V_LIBDIR"])
2411
+ import importlib.util
2412
+ _spec = importlib.util.spec_from_file_location(
2413
+ "loki_proof_verify", os.path.join(os.environ["_V_LIBDIR"], "proof-verify.py"))
2414
+ _pv_mod = importlib.util.module_from_spec(_spec)
2415
+ _spec.loader.exec_module(_pv_mod)
2416
+ doc["verification"] = _pv_mod.build_for_evidence(
2417
+ doc, repo_dir=".", loki_dir=os.environ.get("LOKI_DIR") or ".loki",
2418
+ record_path="", fresh_capture=True)
2419
+ # base_verdict duplicates doc["verdict"]; record_path would pin the output
2420
+ # directory into a document that must be byte-identical across directories.
2421
+ doc["verification"].pop("base_verdict", None)
2422
+ except Exception as _vr_exc:
2423
+ doc["verification"] = {"schema_version": "1.0", "error": "verification record unavailable: %s" % _vr_exc,
2424
+ "label": doc["verdict"] + " (verification record unavailable)"}
2425
+
2404
2426
  os.makedirs(out_dir, exist_ok=True)
2405
2427
  ev_path = os.path.join(out_dir, "evidence.json")
2406
2428
  with open(ev_path, "w") as f:
@@ -2437,6 +2459,10 @@ lines.append("# Autonomi Verify report")
2437
2459
  lines.append("")
2438
2460
  lines.append("Verdict: **%s** (exit %d)" % (doc["verdict"], doc["exit_code"]))
2439
2461
  lines.append("")
2462
+ _vr_label = (doc.get("verification") or {}).get("label")
2463
+ if _vr_label:
2464
+ lines.append("Verification: %s" % _vr_label)
2465
+ lines.append("")
2440
2466
  _llm = doc.get("llm_review") or {}
2441
2467
  _llm_status = _llm.get("status", "unavailable")
2442
2468
  if _llm_status == "reviewed":
@@ -3419,7 +3445,20 @@ verify_main() {
3419
3445
  # Under --json stdout carries the evidence document alone, so the human
3420
3446
  # banner is redirected to stderr rather than dropped: an operator watching a
3421
3447
  # terminal still sees the verdict, and `| jq` still parses.
3422
- printf 'VERDICT: %s\n' "$VERIFY_VERDICT" >&$_v_banner_fd
3448
+ # Scoped label + compact reviewer summary from the verification record just
3449
+ # written to evidence.json. The exit code is unchanged; only the wording is
3450
+ # stricter: a skipped or not-run check never leaves a bare "VERIFIED".
3451
+ local _v_lib _v_label _v_summary
3452
+ _v_lib="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib"
3453
+ _v_label="$VERIFY_VERDICT"
3454
+ if [ -f "$_v_lib/proof-verify.py" ]; then
3455
+ _v_summary="$(_verify_py "$_v_lib/proof-verify.py" summary "$out_dir/evidence.json" --embedded 2>/dev/null)" || _v_summary=""
3456
+ if [ -n "$_v_summary" ]; then
3457
+ _v_label="${_v_summary%%$'\n'*}"
3458
+ printf '%s\n' "${_v_summary#*$'\n'}" >&$_v_banner_fd
3459
+ fi
3460
+ fi
3461
+ printf 'VERDICT: %s\n' "$_v_label" >&$_v_banner_fd
3423
3462
  printf 'Evidence: %s/evidence.json\n' "$out_dir" >&$_v_banner_fd
3424
3463
  printf 'Report: %s/report.md\n' "$out_dir" >&$_v_banner_fd
3425
3464
  # --hosted only: surface the enrichment in human output so the extra signal
package/completions/_loki CHANGED
@@ -378,7 +378,8 @@ function _loki_provider {
378
378
  'show:Display current provider'
379
379
  'set:Set default provider'
380
380
  'list:List available providers'
381
- 'info:Get provider information'
381
+ 'info:Get provider information (--json: capability contract)'
382
+ 'matrix:Show the capability matrix'
382
383
  'help:Provider help'
383
384
  )
384
385
  _describe -t cmds 'provider subcommand' cmds
@@ -389,6 +390,7 @@ function _loki_plan {
389
390
  '--json[Machine-readable JSON output]' \
390
391
  '--verbose[Detailed per-iteration breakdown]' \
391
392
  '--spec[Write editable acceptance criteria to .loki/specs/<slug>.md]' \
393
+ '--requirements[Print the last run requirement-to-check map (read-only)]' \
392
394
  '*:prd file:_files'
393
395
  }
394
396
 
@@ -67,7 +67,7 @@ _loki_completion() {
67
67
  ;;
68
68
 
69
69
  provider)
70
- local provider_cmds="show set list info help"
70
+ local provider_cmds="show set list info matrix help"
71
71
  COMPREPLY=( $(compgen -W "${provider_cmds}" -- "$cur") )
72
72
  ;;
73
73
 
@@ -123,9 +123,15 @@ _loki_completion() {
123
123
  fi
124
124
  ;;
125
125
 
126
+ setup-skill)
127
+ if [[ "$cur" == -* ]]; then
128
+ COMPREPLY=( $(compgen -W "--claude-code --print --project --stop-hook --settings --help" -- "$cur") )
129
+ return 0
130
+ fi
131
+ ;;
126
132
  doctor)
127
133
  if [[ "$cur" == -* ]]; then
128
- COMPREPLY=( $(compgen -W "--json --fix --help" -- "$cur") )
134
+ COMPREPLY=( $(compgen -W "--json --fix --repo --requirements --help" -- "$cur") )
129
135
  return 0
130
136
  fi
131
137
  ;;
@@ -197,7 +203,7 @@ _loki_completion() {
197
203
 
198
204
  plan)
199
205
  if [[ "$cur" == -* ]]; then
200
- COMPREPLY=( $(compgen -W "--json --verbose --spec --help" -- "$cur") )
206
+ COMPREPLY=( $(compgen -W "--json --verbose --spec --requirements --help" -- "$cur") )
201
207
  return 0
202
208
  fi
203
209
  ;;
@@ -4,16 +4,9 @@ Loki Mode Dashboard Backend
4
4
  FastAPI-based dashboard for managing Loki Mode projects, tasks, and agents.
5
5
 
6
6
  Modules:
7
- control: Session control API (start/stop/pause/resume)
7
+ control: Session control helpers (status, atomic writes, origin check)
8
8
  """
9
9
 
10
- __version__ = "11.4.2"
10
+ __version__ = "11.6.0"
11
11
 
12
- # Expose the control app for easy import
13
- try:
14
- from .control import app as control_app
15
- except ImportError:
16
- # FastAPI not installed, control module not available
17
- control_app = None
18
-
19
- __all__ = ["control_app", "__version__"]
12
+ __all__ = ["__version__"]