loki-mode 9.48.2 → 9.49.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/loki-issue-to-pr.yml +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/claude-flags.sh +18 -2
- package/autonomy/loki +137 -12
- package/autonomy/notification-checker.py +45 -17
- package/autonomy/provider-offer.sh +47 -0
- package/autonomy/quickstart.sh +306 -9
- package/autonomy/run.sh +43 -4
- package/dashboard/__init__.py +1 -1
- package/dashboard/static/index.html +7 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/KNOWN-ISSUES.md +42 -0
- package/docs/cost-controls.md +1 -1
- package/docs/environment-variables.md +1 -1
- package/loki-ts/dist/loki.js +317 -317
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-PyyVXg4b.js → AdminPage-BA6etdwL.js} +1 -1
- package/web-app/dist/assets/{Avatar-CTUlr_Xf.js → Avatar-CAtbcD23.js} +1 -1
- package/web-app/dist/assets/{Badge-BQeXvmg8.js → Badge-sb_JbKl0.js} +1 -1
- package/web-app/dist/assets/{Button-Dc6-P6pz.js → Button-BamXbIWa.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BspOJ3Fv.js → CockpitPage-CUYizBtG.js} +4 -4
- package/web-app/dist/assets/{ComparePage-CFXIM--Z.js → ComparePage-BnmNTggM.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-C7vQYDW9.js → ErrorBoundary-BjX4_KOW.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CqjsjTOs.js → EvidenceReceiptPanel-Bq6pmNUw.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-DyuNDMgD.js → GitHubIssuesPanel-DD8c6RVj.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BtAq20xF.js → GitHubPRsPanel-4NawTXRl.js} +2 -2
- package/web-app/dist/assets/{HomePage-BuVFOrO8.js → HomePage-DTREi8OE.js} +1 -1
- package/web-app/dist/assets/{LoginPage-CuhhrxGb.js → LoginPage-DavfeQxC.js} +1 -1
- package/web-app/dist/assets/{MagicPage-D2KZPX3F.js → MagicPage-BIbTFlwh.js} +1 -1
- package/web-app/dist/assets/MetricsPage-Cn3UPKpU.js +6 -0
- package/web-app/dist/assets/{NotFoundPage-CTnnaWlL.js → NotFoundPage-BVs_nuBv.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-uJFaoM8-.js → ProjectPage-CLplvOHe.js} +25 -25
- package/web-app/dist/assets/{ProjectsPage-8lKAOBIW.js → ProjectsPage-BBFzBBG_.js} +2 -2
- package/web-app/dist/assets/{SettingsPage-D1BBd5qy.js → SettingsPage-WPsJ_K9r.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-CmQ0n8iO.js → ShowcasePage-ZaLaJl5J.js} +1 -1
- package/web-app/dist/assets/SystemSettingsPage-CCORP-uG.js +1 -0
- package/web-app/dist/assets/{TeamsPage-BS0TtVUa.js → TeamsPage-B6IGT_K2.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-C2zPnnf2.js → TemplatesPage-RrYYC3CM.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-Dle1klqq.js → TerminalOutput-CeTvKnkl.js} +1 -1
- package/web-app/dist/assets/{activity-DQp5U9f9.js → activity-CySdOdDP.js} +1 -1
- package/web-app/dist/assets/{bell-BqkTmgL7.js → bell-BHdr5yat.js} +1 -1
- package/web-app/dist/assets/{bot-DILhaIGH.js → bot-BaB4q_4m.js} +1 -1
- package/web-app/dist/assets/{check-BC9x7oPm.js → check-BAgbzVrK.js} +1 -1
- package/web-app/dist/assets/{chevron-left--yqvAfjx.js → chevron-left-CrVUY29C.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DOH6PcgZ.js → circle-alert-YrWgH49w.js} +1 -1
- package/web-app/dist/assets/{clock-DKSDFsE6.js → clock-DN8LAN-0.js} +1 -1
- package/web-app/dist/assets/{cloud-DRBU3JYs.js → cloud-D6C4b1KY.js} +1 -1
- package/web-app/dist/assets/{code-xml-D2LqszR9.js → code-xml-xBWyKfux.js} +1 -1
- package/web-app/dist/assets/{database-D7ceedlh.js → database-BYqcS6kn.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-D0A47egY.js → dollar-sign--WOh4k_m.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CH_Qbtjo.js → file-code-corner-B0rLPjW1.js} +1 -1
- package/web-app/dist/assets/{file-plus-CUNMjHk9.js → file-plus-BNhUQ1TQ.js} +1 -1
- package/web-app/dist/assets/folder-open-DliiOgFl.js +11 -0
- package/web-app/dist/assets/{globe-gZnaGg8N.js → globe-BV-hx0u_.js} +1 -1
- package/web-app/dist/assets/{hammer-BhJC5DMK.js → hammer-DhmP4GLw.js} +1 -1
- package/web-app/dist/assets/index-D98GzaW8.css +1 -0
- package/web-app/dist/assets/{index-BMmYYws0.js → index-x0TESFYr.js} +3 -3
- package/web-app/dist/assets/{layers-glXeoUbJ.js → layers-DXWDMSYj.js} +1 -1
- package/web-app/dist/assets/{lightbulb-gXn8H0Lf.js → lightbulb-PBw6hhLS.js} +1 -1
- package/web-app/dist/assets/{loader-circle-Do2ke8xm.js → loader-circle-B7KiGAVU.js} +1 -1
- package/web-app/dist/assets/{lock-Bgplb1_n.js → lock-BUTRYdDQ.js} +1 -1
- package/web-app/dist/assets/{mail-j4Lo8hfB.js → mail-DaLiNrPb.js} +1 -1
- package/web-app/dist/assets/{minus-dhrzKSK5.js → minus-DB_hlGEk.js} +1 -1
- package/web-app/dist/assets/{package-CInWQQ97.js → package-CymmXoiQ.js} +1 -1
- package/web-app/dist/assets/{plus-BpYvoLlZ.js → plus-Cp4AO4I2.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-F6koqEgF.js → refresh-cw-ygYTmZTE.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CQ2W5S8f.js → rotate-ccw-Ba3QxM9K.js} +1 -1
- package/web-app/dist/assets/{save-DMCYGm06.js → save-DPm96BnR.js} +1 -1
- package/web-app/dist/assets/{server-Bj8EX4Z1.js → server-BapOz_vf.js} +1 -1
- package/web-app/dist/assets/{shield-alert-CiYuulCU.js → shield-alert-DYzfVqWS.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-BqEh1puP.js → thumbs-up-DxOTplej.js} +11 -6
- package/web-app/dist/assets/{trash-2-CCwouZJJ.js → trash-2-Br8dh_6B.js} +1 -1
- package/web-app/dist/assets/{trending-down-Cx1vhE4o.js → trending-down-CxSP6MU3.js} +1 -1
- package/web-app/dist/assets/{trending-up-BUhVYJ2T.js → trending-up-CXx0ywZb.js} +1 -1
- package/web-app/dist/assets/{upload-D5IsZeFM.js → upload-CpEO3qNc.js} +1 -1
- package/web-app/dist/assets/{usePolling-CJbfhFcH.js → usePolling-BuzXwjmC.js} +1 -1
- package/web-app/dist/assets/{user-BRLhezsD.js → user-DUUYT47B.js} +1 -1
- package/web-app/dist/index.html +2 -2
- package/web-app/server.py +121 -1
- package/web-app/dist/assets/MetricsPage-DFEYJl9z.js +0 -6
- package/web-app/dist/assets/SystemSettingsPage-CSHaVyuD.js +0 -6
- package/web-app/dist/assets/copy-BKW4U4Qr.js +0 -6
- package/web-app/dist/assets/folder-open-CQ_SVq94.js +0 -6
- package/web-app/dist/assets/git-commit-horizontal-DQUBe0oD.js +0 -6
- package/web-app/dist/assets/index-84JUN_aA.css +0 -1
|
@@ -61,7 +61,7 @@ jobs:
|
|
|
61
61
|
# Pinned to a TAG, not @main. A moving ref would hand every user
|
|
62
62
|
# whatever main holds at the moment their issue is labeled, including a
|
|
63
63
|
# half-landed release. Bump this line deliberately.
|
|
64
|
-
uses: asklokesh/loki-mode/.github/actions/issue-to-pr@v9.
|
|
64
|
+
uses: asklokesh/loki-mode/.github/actions/issue-to-pr@v9.49.3
|
|
65
65
|
env:
|
|
66
66
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
67
67
|
with:
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v9.
|
|
6
|
+
# Loki Mode v9.49.3
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
470
470
|
|
|
471
471
|
---
|
|
472
472
|
|
|
473
|
-
**v9.
|
|
473
|
+
**v9.49.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.49.3
|
|
@@ -51,7 +51,21 @@ loki_effort_for_tier() {
|
|
|
51
51
|
|
|
52
52
|
# ---------- Remaining budget ----------
|
|
53
53
|
# Compute remaining budget = LOKI_BUDGET_LIMIT - cumulative spend so far.
|
|
54
|
-
# Spend lives in .loki/metrics/budget.json under "
|
|
54
|
+
# Spend lives in .loki/metrics/budget.json under "budget_used".
|
|
55
|
+
#
|
|
56
|
+
# It was read as "current_spend" until v9.50.0, a key NO production writer has
|
|
57
|
+
# ever emitted. All six writers of that file (run.sh:6581,17851,17869,
|
|
58
|
+
# loki-ts/src/runner/budget.ts:267, dashboard/server.py:7953, autonomy/loki)
|
|
59
|
+
# write "budget_used". The old citation pointed at run.sh:8167+, which is
|
|
60
|
+
# per-iteration token parsing and writes no budget key at all.
|
|
61
|
+
#
|
|
62
|
+
# Effect of the bug: spend always read 0, so --max-budget-usd was handed the
|
|
63
|
+
# FULL cap on every call instead of the remainder, and the per-call backstop
|
|
64
|
+
# never tightened as spend accumulated. Only reachable for users who set
|
|
65
|
+
# LOKI_BUDGET_LIMIT explicitly (run.sh sets BUDGET_LIMIT, not LOKI_*), i.e.
|
|
66
|
+
# exactly the cost-conscious users who asked for the cap.
|
|
67
|
+
# "current_spend" is still accepted as a fallback so a budget.json written by
|
|
68
|
+
# an in-flight older run still parses.
|
|
55
69
|
# Emit empty string when budget is unlimited (LOKI_BUDGET_LIMIT unset or 0).
|
|
56
70
|
# Emit empty when remaining <= 0 (caller decides what to do; we never emit 0).
|
|
57
71
|
loki_remaining_budget() {
|
|
@@ -71,7 +85,9 @@ import json, os, sys
|
|
|
71
85
|
try:
|
|
72
86
|
with open(os.environ["_LOKI_BUDGET_FILE"]) as f:
|
|
73
87
|
d = json.load(f)
|
|
74
|
-
v = d.get("
|
|
88
|
+
v = d.get("budget_used")
|
|
89
|
+
if v is None:
|
|
90
|
+
v = d.get("current_spend", 0)
|
|
75
91
|
print(float(v))
|
|
76
92
|
except Exception:
|
|
77
93
|
print(0)
|
package/autonomy/loki
CHANGED
|
@@ -13538,10 +13538,34 @@ except Exception:
|
|
|
13538
13538
|
"$HOME/.cline/skills/loki-mode:Cline CLI"
|
|
13539
13539
|
"$HOME/.aider/skills/loki-mode:Aider CLI"
|
|
13540
13540
|
)
|
|
13541
|
+
# SEVERITY DEPENDS ON WHOSE SKILL IT IS (v9.48.x). A dangling skill symlink
|
|
13542
|
+
# used to block unconditionally, so a stale link left by an old checkout
|
|
13543
|
+
# path exited 1 on a HEALTHY machine -- while the Provider Availability
|
|
13544
|
+
# section three lines above said that same provider was "not installed".
|
|
13545
|
+
# Doctor contradicted itself on one screen and told the user to fix a tool
|
|
13546
|
+
# they do not have.
|
|
13547
|
+
#
|
|
13548
|
+
# The link only blocks when it belongs to the provider a build would
|
|
13549
|
+
# actually launch. Anything else is inert state: still reported, with the
|
|
13550
|
+
# dangling target and the fix, but not a reason to refuse to start.
|
|
13551
|
+
#
|
|
13552
|
+
# `|| true` is mandatory under `set -euo pipefail`: the accessor returns
|
|
13553
|
+
# non-zero when no provider resolves (a bare call would abort doctor), and
|
|
13554
|
+
# an empty id then makes every entry non-blocking -- the same direction the
|
|
13555
|
+
# No-AI-provider blocker above already covers.
|
|
13556
|
+
local _selected_provider=""
|
|
13557
|
+
if declare -f effective_provider_id >/dev/null 2>&1; then
|
|
13558
|
+
_selected_provider="$(effective_provider_id 2>/dev/null || true)"
|
|
13559
|
+
fi
|
|
13541
13560
|
for entry in "${skill_dirs[@]}"; do
|
|
13542
13561
|
local sdir="${entry%%:*}"
|
|
13543
13562
|
local sname="${entry#*:}"
|
|
13544
13563
|
local short_path="${sdir/$HOME/~}"
|
|
13564
|
+
# Provider id from the directory, never a parallel list: `.claude` ->
|
|
13565
|
+
# `claude`. A fifth hand-maintained list is how the lists in this repo
|
|
13566
|
+
# have drifted from auto_detect_provider before (v8.76.0, v8.82.0).
|
|
13567
|
+
local _sid="${sdir#"$HOME"/.}"
|
|
13568
|
+
_sid="${_sid%%/*}"
|
|
13545
13569
|
if [ -f "$sdir/SKILL.md" ]; then
|
|
13546
13570
|
echo -e " ${GREEN}PASS${NC} $sname ${DIM}$short_path${NC}"
|
|
13547
13571
|
pass_count=$((pass_count + 1))
|
|
@@ -13560,10 +13584,22 @@ except Exception:
|
|
|
13560
13584
|
_target=$(ls -ld "$sdir" 2>/dev/null | sed -e 's/^[^>]*-> //')
|
|
13561
13585
|
[ -n "$_target" ] || _target="unknown"
|
|
13562
13586
|
fi
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13587
|
+
# Blocking ONLY for the provider a build would actually use. The
|
|
13588
|
+
# non-blocking arm is a WARN rather than a non-counted FAIL on
|
|
13589
|
+
# purpose: a red FAIL badge that never reaches `N failed` or the
|
|
13590
|
+
# `Blocking (N)` list makes the summary disagree with the screen.
|
|
13591
|
+
# Either way the dangling target and the fix are still printed --
|
|
13592
|
+
# the stale link is reported, never silently swallowed.
|
|
13593
|
+
if [ -n "$_selected_provider" ] && [ "$_sid" = "$_selected_provider" ]; then
|
|
13594
|
+
echo -e " ${RED}FAIL${NC} $sname ${DIM}(broken symlink -> $_target)${NC}"
|
|
13595
|
+
echo -e " ${YELLOW}Fix: loki setup-skill${NC}"
|
|
13596
|
+
_doctor_block "$sname is a broken symlink. Fix: loki setup-skill"
|
|
13597
|
+
fail_count=$((fail_count + 1))
|
|
13598
|
+
else
|
|
13599
|
+
echo -e " ${YELLOW}WARN${NC} $sname ${DIM}(broken symlink -> $_target)${NC}"
|
|
13600
|
+
echo -e " ${YELLOW}Fix: loki setup-skill${NC}"
|
|
13601
|
+
warn_count=$((warn_count + 1))
|
|
13602
|
+
fi
|
|
13567
13603
|
else
|
|
13568
13604
|
echo -e " ${YELLOW}WARN${NC} $sname ${DIM}(not found - run 'loki setup-skill')${NC}"
|
|
13569
13605
|
warn_count=$((warn_count + 1))
|
|
@@ -14183,14 +14219,26 @@ memory = {
|
|
|
14183
14219
|
#
|
|
14184
14220
|
# CAUTION: this block lives inside a DOUBLE-QUOTED python3 -c program, so no
|
|
14185
14221
|
# apostrophes and no double quotes anywhere here, comments included.
|
|
14222
|
+
#
|
|
14223
|
+
# SEVERITY MIRRORS THE TEXT PATH: a dangling link is a failure only for the
|
|
14224
|
+
# provider a build would actually use. Read from the SAME payload the text
|
|
14225
|
+
# route uses (LOKI_PROVIDER_AVAILABILITY, set by cmd_doctor_json), parsed here
|
|
14226
|
+
# rather than via a second env var so the two cannot disagree. LOKI_PROVIDER
|
|
14227
|
+
# wins for the same reason effective_provider_id honors it.
|
|
14186
14228
|
_SKILL_ENTRIES = (
|
|
14187
|
-
('Claude Code', '.claude/skills/loki-mode'),
|
|
14188
|
-
('Codex CLI', '.codex/skills/loki-mode'),
|
|
14189
|
-
('Cline CLI', '.cline/skills/loki-mode'),
|
|
14190
|
-
('Aider CLI', '.aider/skills/loki-mode'),
|
|
14229
|
+
('Claude Code', '.claude/skills/loki-mode', 'claude'),
|
|
14230
|
+
('Codex CLI', '.codex/skills/loki-mode', 'codex'),
|
|
14231
|
+
('Cline CLI', '.cline/skills/loki-mode', 'cline'),
|
|
14232
|
+
('Aider CLI', '.aider/skills/loki-mode', 'aider'),
|
|
14191
14233
|
)
|
|
14234
|
+
try:
|
|
14235
|
+
_sk_pa = os.environ.get('LOKI_PROVIDER_AVAILABILITY', '').strip()
|
|
14236
|
+
_sk_selected = (json.loads(_sk_pa).get('selected') or '') if _sk_pa else ''
|
|
14237
|
+
except Exception:
|
|
14238
|
+
_sk_selected = ''
|
|
14239
|
+
_sk_selected = os.environ.get('LOKI_PROVIDER', '') or _sk_selected
|
|
14192
14240
|
skills = []
|
|
14193
|
-
for _sk_name, _sk_rel in _SKILL_ENTRIES:
|
|
14241
|
+
for _sk_name, _sk_rel, _sk_id in _SKILL_ENTRIES:
|
|
14194
14242
|
_sk_dir = os.path.join(os.path.expanduser('~'), _sk_rel)
|
|
14195
14243
|
# JSON is commonly archived or forwarded to automation. Keep the useful
|
|
14196
14244
|
# user-relative location without exposing the machine-specific home path.
|
|
@@ -14200,8 +14248,14 @@ for _sk_name, _sk_rel in _SKILL_ENTRIES:
|
|
|
14200
14248
|
'detail': None, 'required': 'required'})
|
|
14201
14249
|
elif os.path.islink(_sk_dir):
|
|
14202
14250
|
# islink is true for a DANGLING link (lstat-based), which is exactly
|
|
14203
|
-
# the broken case the text path reports
|
|
14204
|
-
|
|
14251
|
+
# the broken case the text path reports. fail only for the effective
|
|
14252
|
+
# provider; otherwise warn, matching the text path badge for badge.
|
|
14253
|
+
# `required` stays 'required' in both arms: an absent link has always
|
|
14254
|
+
# been warn+required, so the warn arm introduces no new shape, and a
|
|
14255
|
+
# consumer keys on `status`.
|
|
14256
|
+
_sk_blocking = bool(_sk_selected) and _sk_id == _sk_selected
|
|
14257
|
+
skills.append({'name': _sk_name, 'path': _sk_json_path,
|
|
14258
|
+
'status': 'fail' if _sk_blocking else 'warn',
|
|
14205
14259
|
'detail': 'broken symlink. Fix: loki setup-skill',
|
|
14206
14260
|
'required': 'required'})
|
|
14207
14261
|
else:
|
|
@@ -17358,7 +17412,78 @@ if manifests:
|
|
|
17358
17412
|
echo -e "${RED}Error: No repositories found matching: ${multi_repo}${NC}"
|
|
17359
17413
|
return 1
|
|
17360
17414
|
fi
|
|
17361
|
-
#
|
|
17415
|
+
# ORCHESTRATION (v9.49.0): --multi-repo now MIGRATES each discovered
|
|
17416
|
+
# repository, one at a time, instead of only listing them.
|
|
17417
|
+
#
|
|
17418
|
+
# WHY A WRAPPER AND NOT SURGERY INSIDE THIS FUNCTION: cmd_migrate_start
|
|
17419
|
+
# is self-contained per codebase_path (manifest, secret scan, phases)
|
|
17420
|
+
# and was measured to have 20 `return`s and ZERO `exit`s, so it is
|
|
17421
|
+
# safe to call in a loop -- a failing repo returns a code instead of
|
|
17422
|
+
# killing the run. Recursing once per repo reuses every existing gate
|
|
17423
|
+
# rather than reimplementing the pipeline.
|
|
17424
|
+
#
|
|
17425
|
+
# PARTIAL-FAILURE SEMANTICS: continue-and-report, decided on the
|
|
17426
|
+
# codebase's own precedent, not preference:
|
|
17427
|
+
# - the task schema already has `blocked` as a first-class state
|
|
17428
|
+
# alongside `completed` (mcp/server.py:888), so "some units did not
|
|
17429
|
+
# finish" is already expressible
|
|
17430
|
+
# - run.sh carries 562 best-effort sites: the house style is degrade
|
|
17431
|
+
# honestly, do not abort
|
|
17432
|
+
# - fail-closed is reserved for DEPENDENCY violations (a migrate phase
|
|
17433
|
+
# refusing to run without its predecessor's artifacts, :17575), and
|
|
17434
|
+
# independent repositories have no such dependency on each other
|
|
17435
|
+
# One repo failing therefore must not deny the other four their result.
|
|
17436
|
+
# The per-repo status table plus a non-zero aggregate exit code is what
|
|
17437
|
+
# makes that honest rather than silent.
|
|
17438
|
+
#
|
|
17439
|
+
# LOKI_MULTI_REPO_SEQUENTIAL=0 is NOT offered: these share a migrations
|
|
17440
|
+
# dir and a dashboard port, and parallel runs would interleave output
|
|
17441
|
+
# into an unreadable report.
|
|
17442
|
+
if [ "$repo_count" -gt 1 ] && [ "${_LOKI_MR_CHILD:-0}" != "1" ]; then
|
|
17443
|
+
echo -e "${BOLD}Multi-repo migration${NC} ($repo_count repositories, sequential)"
|
|
17444
|
+
echo "---"
|
|
17445
|
+
for repo in "${repos[@]}"; do echo " - $repo"; done
|
|
17446
|
+
echo ""
|
|
17447
|
+
|
|
17448
|
+
local _mr_ok=0 _mr_bad=0
|
|
17449
|
+
local _mr_rows=""
|
|
17450
|
+
local _mr_repo _mr_rc
|
|
17451
|
+
for _mr_repo in "${repos[@]}"; do
|
|
17452
|
+
echo ""
|
|
17453
|
+
echo -e "${CYAN}=== [$((_mr_ok + _mr_bad + 1))/$repo_count] $_mr_repo ===${NC}"
|
|
17454
|
+
_mr_rc=0
|
|
17455
|
+
# _LOKI_MR_CHILD guards the recursion: the child sees the same
|
|
17456
|
+
# --multi-repo argument and must NOT re-enter this block.
|
|
17457
|
+
_LOKI_MR_CHILD=1 cmd_migrate_start \
|
|
17458
|
+
"$_mr_repo" "$target" "$plan_only" "$phase" "$parallel" \
|
|
17459
|
+
"$compliance" "$dry_run" "$resume" "" "$export_report" \
|
|
17460
|
+
"$no_dashboard" "$no_docs" || _mr_rc=$?
|
|
17461
|
+
if [ "$_mr_rc" -eq 0 ]; then
|
|
17462
|
+
_mr_ok=$((_mr_ok + 1))
|
|
17463
|
+
_mr_rows="${_mr_rows} OK ${_mr_repo}"$'\n'
|
|
17464
|
+
else
|
|
17465
|
+
_mr_bad=$((_mr_bad + 1))
|
|
17466
|
+
_mr_rows="${_mr_rows} BLOCKED ${_mr_repo} (exit ${_mr_rc})"$'\n'
|
|
17467
|
+
fi
|
|
17468
|
+
done
|
|
17469
|
+
|
|
17470
|
+
echo ""
|
|
17471
|
+
echo -e "${BOLD}Multi-repo result${NC}"
|
|
17472
|
+
echo "---"
|
|
17473
|
+
printf '%s' "$_mr_rows"
|
|
17474
|
+
echo ""
|
|
17475
|
+
echo " ${_mr_ok} of ${repo_count} migrated, ${_mr_bad} blocked."
|
|
17476
|
+
if [ "$_mr_bad" -gt 0 ]; then
|
|
17477
|
+
# A non-zero exit is what keeps "continue" from becoming
|
|
17478
|
+
# "silently tolerate": CI and `loki next` both branch on it.
|
|
17479
|
+
echo -e "${YELLOW}Some repositories did not complete. Re-run just those, or inspect them with 'loki why'.${NC}"
|
|
17480
|
+
return 1
|
|
17481
|
+
fi
|
|
17482
|
+
return 0
|
|
17483
|
+
fi
|
|
17484
|
+
|
|
17485
|
+
# HONESTY (single-repo, or a --multi-repo glob that matched exactly one):
|
|
17486
|
+
# this flag DISCOVERS repositories; it does not migrate them.
|
|
17362
17487
|
# `repos[]` is populated here and never referenced again -- everything
|
|
17363
17488
|
# below operates on the single "$codebase_path". The old message said
|
|
17364
17489
|
# "Multi-repo migration (3 repositories)" and listed all three, then
|
|
@@ -165,7 +165,15 @@ def check_budget_threshold(trigger, loki_dir, iteration, notifications):
|
|
|
165
165
|
state = json.loads(state_file.read_text())
|
|
166
166
|
budget = state.get("budget", {})
|
|
167
167
|
limit = budget.get("limit", 0)
|
|
168
|
-
|
|
168
|
+
# run.sh:7084 nests .loki/metrics/budget.json verbatim under "budget",
|
|
169
|
+
# and that file's spend key is "budget_used". Reading only "used" meant
|
|
170
|
+
# this trigger never fired in production: verified against the exact
|
|
171
|
+
# state run.sh writes, 0 notifications were produced at 85% of cap.
|
|
172
|
+
# "used" stays as a fallback for the /api/cost-shaped payload
|
|
173
|
+
# (autonomy/loki:28122), which does write that key.
|
|
174
|
+
used = budget.get("budget_used")
|
|
175
|
+
if used is None:
|
|
176
|
+
used = budget.get("used", 0)
|
|
169
177
|
if limit <= 0:
|
|
170
178
|
return None
|
|
171
179
|
pct = (used / limit) * 100
|
|
@@ -241,24 +249,44 @@ def check_file_access(trigger, loki_dir, iteration, notifications):
|
|
|
241
249
|
|
|
242
250
|
|
|
243
251
|
def check_quality_gate(trigger, loki_dir, iteration, notifications):
|
|
244
|
-
"""Check for quality gate failures. Reports ALL failed gates, not just first.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
252
|
+
"""Check for quality gate failures. Reports ALL failed gates, not just first.
|
|
253
|
+
|
|
254
|
+
Reads .loki/quality/gate-failures.txt, which is what run.sh actually writes
|
|
255
|
+
(:24100, :24142, :24553) as a comma-terminated list of failing gate names,
|
|
256
|
+
e.g. "mock_integrity,".
|
|
257
|
+
|
|
258
|
+
This previously read state["qualityGates"] out of dashboard-state.json,
|
|
259
|
+
sourced from .loki/state/quality-gates.json (run.sh:6972). NOTHING writes
|
|
260
|
+
that file: all seven repo references are readers, autonomy/hooks/
|
|
261
|
+
quality-gate.sh:12 guards on `[ ! -f ]`, and proof-generator.py:345 records
|
|
262
|
+
the same finding as issue #125 and already works around it by reading these
|
|
263
|
+
per-gate artifacts. The aggregate was therefore always absent, qualityGates
|
|
264
|
+
was always null, and this trigger could never fire -- the same dead-reader
|
|
265
|
+
defect as the budget key, in the same file.
|
|
266
|
+
|
|
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
|
|
269
|
+
reuses a proven contract rather than introducing a second one.
|
|
270
|
+
"""
|
|
271
|
+
failures_file = Path(loki_dir) / "quality" / "gate-failures.txt"
|
|
249
272
|
results = []
|
|
250
273
|
try:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
274
|
+
if failures_file.exists():
|
|
275
|
+
# Cap the read: a pathological file must not be loaded whole
|
|
276
|
+
# (same reason as build_prompt.ts:707).
|
|
277
|
+
raw = failures_file.read_text(errors="replace")[:8000]
|
|
278
|
+
seen = set()
|
|
279
|
+
for name in raw.replace("\n", ",").split(","):
|
|
280
|
+
gate_name = name.strip()
|
|
281
|
+
if not gate_name or gate_name in seen:
|
|
282
|
+
continue
|
|
283
|
+
seen.add(gate_name)
|
|
284
|
+
msg = trigger["message"].format(gate=gate_name)
|
|
285
|
+
results.append(make_notification(
|
|
286
|
+
trigger["id"], trigger["severity"], msg, iteration,
|
|
287
|
+
{"gate": gate_name},
|
|
288
|
+
))
|
|
289
|
+
except (OSError, KeyError):
|
|
262
290
|
pass
|
|
263
291
|
return results if results else None
|
|
264
292
|
|
|
@@ -464,6 +464,49 @@ render_provider_availability() {
|
|
|
464
464
|
) || return 1
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
+
# effective_provider_id: print the provider id a build would ACTUALLY use, or
|
|
468
|
+
# nothing when none is resolvable. Exit status is 0 only when an id was printed.
|
|
469
|
+
#
|
|
470
|
+
# WHY IT EXISTS. Doctor grades a dangling skill symlink, and the severity of
|
|
471
|
+
# that depends on WHOSE skill it is. A stale link for a provider the user does
|
|
472
|
+
# not use is inert state; the same link for the provider a build will launch is
|
|
473
|
+
# a real blocker. Both the text route and the Bun route need that one id, and
|
|
474
|
+
# both previously re-derived it inside their own subshells (the two callers
|
|
475
|
+
# below). Deriving it a third and fourth time is exactly the drift this file's
|
|
476
|
+
# header exists to prevent, so it is extracted here once.
|
|
477
|
+
#
|
|
478
|
+
# LOKI_PROVIDER WINS. render_provider_availability prints "override with
|
|
479
|
+
# LOKI_PROVIDER" two lines above, so honoring auto-detection alone would grade
|
|
480
|
+
# a skill link against a provider the very same screen says is not in use.
|
|
481
|
+
# The override is NOT validated against installed state on purpose: an explicit
|
|
482
|
+
# LOKI_PROVIDER is the user's stated intent, and a build would carry it through.
|
|
483
|
+
#
|
|
484
|
+
# DEGRADES SILENTLY, like its two siblings: a missing or unsourceable loader
|
|
485
|
+
# prints nothing and returns non-zero, and the caller falls back to treating
|
|
486
|
+
# every entry as non-blocking rather than erroring.
|
|
487
|
+
effective_provider_id() {
|
|
488
|
+
if [ -n "${LOKI_PROVIDER:-}" ]; then
|
|
489
|
+
printf '%s\n' "$LOKI_PROVIDER"
|
|
490
|
+
return 0
|
|
491
|
+
fi
|
|
492
|
+
|
|
493
|
+
local _po_here
|
|
494
|
+
_po_here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" || return 1
|
|
495
|
+
local _loader="$_po_here/providers/loader.sh"
|
|
496
|
+
[ -f "$_loader" ] || return 1
|
|
497
|
+
|
|
498
|
+
local _sel
|
|
499
|
+
_sel="$(
|
|
500
|
+
# shellcheck source=/dev/null
|
|
501
|
+
source "$_loader" >/dev/null 2>&1 || exit 1
|
|
502
|
+
declare -f auto_detect_provider >/dev/null 2>&1 || exit 1
|
|
503
|
+
auto_detect_provider 2>/dev/null
|
|
504
|
+
)" || return 1
|
|
505
|
+
|
|
506
|
+
[ -n "$_sel" ] || return 1
|
|
507
|
+
printf '%s\n' "$_sel"
|
|
508
|
+
}
|
|
509
|
+
|
|
467
510
|
# provider_availability_json: the same data as one JSON object, for doctor --json.
|
|
468
511
|
# Separate from the renderer so neither format has to parse the other. Same
|
|
469
512
|
# degrade rule: no loader means no output and a non-zero status.
|
|
@@ -517,6 +560,10 @@ if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
|
517
560
|
# routes call these so the two renderings cannot drift apart.
|
|
518
561
|
providers) render_provider_availability ;;
|
|
519
562
|
providers-json) provider_availability_json ;;
|
|
563
|
+
# The provider id a build would actually use, for the Bun doctor's
|
|
564
|
+
# skill-severity decision. Same one-implementation rule as the two
|
|
565
|
+
# above: doctor.ts reads this instead of re-deriving the priority list.
|
|
566
|
+
effective-provider) effective_provider_id ;;
|
|
520
567
|
*) offer_provider_install report ;;
|
|
521
568
|
esac
|
|
522
569
|
fi
|