loki-mode 8.5.2 → 8.6.1
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/README.md +16 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +226 -6
- package/autonomy/run.sh +117 -9
- package/autonomy/telemetry.sh +45 -0
- package/autonomy/verify.sh +85 -19
- package/completions/_loki +1 -0
- package/completions/loki.bash +1 -1
- package/dashboard/__init__.py +1 -1
- package/docs/EVALUATING.md +28 -0
- package/docs/PRIVACY.md +25 -2
- package/docs/STRATEGY-2026-2028.md +124 -0
- package/docs/adoption-baseline-2026-07-31.md +84 -0
- package/docs/air-gapped.md +91 -0
- package/docs/brownfield.md +120 -0
- package/docs/cost-controls.md +88 -0
- package/docs/environment-variables.md +102 -0
- package/docs/exit-codes.md +130 -0
- package/docs/image-provenance.md +124 -0
- package/loki-ts/dist/loki.js +381 -372
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -121,6 +121,22 @@ usually load-bearing.
|
|
|
121
121
|
Every coding agent tells you it finished. Loki hands you something you can
|
|
122
122
|
check yourself.
|
|
123
123
|
|
|
124
|
+
**We are not the only tool that checks its own work, and you should be
|
|
125
|
+
suspicious of anyone who claims to be.** Lovable runs a security scan on every
|
|
126
|
+
publish and can block the publish outright. Claude Code's review has a step that
|
|
127
|
+
checks findings against actual code behavior. Replit says its agent tests its
|
|
128
|
+
own work.
|
|
129
|
+
|
|
130
|
+
The difference is what you are left holding. Their output lives in their
|
|
131
|
+
dashboard: a findings count in a dialog, a check run that by design never blocks
|
|
132
|
+
a merge. Ours is a **file**. It is bound to a specific diff by `diff_sha256`, it
|
|
133
|
+
records what was NOT proven as prominently as what was, and someone who has
|
|
134
|
+
never installed Loki can re-verify it from the repository alone. Commit it,
|
|
135
|
+
attach it to the PR, hand it to an auditor.
|
|
136
|
+
|
|
137
|
+
Portable, diff-bound, and honest about its gaps -- that is the claim, and it is
|
|
138
|
+
the one worth checking.
|
|
139
|
+
|
|
124
140
|
Each run writes a receipt to `.loki/proofs/<run_id>/` that separates
|
|
125
141
|
**deterministic FACTS** (the git diff with base and head SHAs plus a
|
|
126
142
|
`diff_sha256`, the test command and its exit code, the build command and its
|
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 v8.
|
|
6
|
+
# Loki Mode v8.6.1
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -469,4 +469,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
469
469
|
|
|
470
470
|
---
|
|
471
471
|
|
|
472
|
-
**v8.
|
|
472
|
+
**v8.6.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
8.
|
|
1
|
+
8.6.1
|
package/autonomy/loki
CHANGED
|
@@ -985,8 +985,44 @@ show_help() {
|
|
|
985
985
|
echo " loki cockpit # Live multi-repo cockpit (terminal image)"
|
|
986
986
|
echo " loki remote # Remote session (phone/browser)"
|
|
987
987
|
echo ""
|
|
988
|
+
# COMPLETE COMMAND INDEX. The curated sections above stay curated -- they
|
|
989
|
+
# are the onboarding path and stuffing 112 commands into them would make
|
|
990
|
+
# the first screen useless. But a command that appears NOWHERE in help does
|
|
991
|
+
# not exist to a user: measured, 43 of 112 were unreachable from here,
|
|
992
|
+
# including `loki proof` -- the Evidence Receipt, which is the whole trust
|
|
993
|
+
# argument. Discoverability is an adoption lever, not tidiness.
|
|
994
|
+
#
|
|
995
|
+
# tests/test-help-discoverability.sh fails the build when a real command is
|
|
996
|
+
# missing from this list, so a new command cannot ship invisible.
|
|
997
|
+
echo ""
|
|
998
|
+
echo "All commands:"
|
|
999
|
+
echo " agent analyze api assets audit bench checkpoint (cp) ci cleanup"
|
|
1000
|
+
echo " cluster cockpit code compliance completions compound config context (ctx)"
|
|
1001
|
+
echo " cost council crash dashboard demo deploy docker docs doctor dogfood"
|
|
1002
|
+
echo " enterprise explain export failover github grill heal help import init"
|
|
1003
|
+
echo " issue kpis logs magic mcp memory metrics migrate modernize monitor"
|
|
1004
|
+
echo " next notify onboard open optimize otel own (handoff) pause plan preview"
|
|
1005
|
+
echo " projects proof (receipt) provider quick quickstart rc remote report reset"
|
|
1006
|
+
echo " resume review rollback run sandbox secrets secure self-update sentrux"
|
|
1007
|
+
echo " serve setup-skill share ship spec start state stats status steer stop"
|
|
1008
|
+
echo " syslog telemetry template test tour trigger trust trust-metrics"
|
|
1009
|
+
echo " ultracode update verify version voice watch watchdog web welcome why"
|
|
1010
|
+
echo " wiki worktree (wt)"
|
|
1011
|
+
echo ""
|
|
1012
|
+
echo "Any command: loki <command> --help"
|
|
1013
|
+
echo ""
|
|
988
1014
|
echo "Phase A-J features (v7.5.18 - v7.5.28) are default-on. See CHANGELOG."
|
|
989
1015
|
echo ""
|
|
1016
|
+
echo "Environment variables an operator would set: docs/environment-variables.md"
|
|
1017
|
+
echo "Cost controls (budget, iteration and wall-clock caps): docs/cost-controls.md"
|
|
1018
|
+
echo "Already have a codebase? Start read-only: docs/brownfield.md"
|
|
1019
|
+
echo "No egress allowed? What runs disconnected: docs/air-gapped.md"
|
|
1020
|
+
echo "Where this is going: docs/STRATEGY-2026-2028.md"
|
|
1021
|
+
echo ""
|
|
1022
|
+
echo "Scripting Loki? Exit codes for every command are documented in"
|
|
1023
|
+
echo "docs/exit-codes.md. Severity rises with the code, and 'loki start'"
|
|
1024
|
+
echo "has a richer contract under LOKI_DURABLE_STATE=1 for k8s and ECS."
|
|
1025
|
+
echo ""
|
|
990
1026
|
echo "Environment Variables:"
|
|
991
1027
|
echo " Opt-outs: LOKI_HOOK_EVENTS=off, LOKI_DYNAMIC_PROMPT_SECTIONS=keep,"
|
|
992
1028
|
echo " LOKI_MEMORY_BASE_PATH (shared memory dir for app graph)"
|
|
@@ -1400,8 +1436,6 @@ cmd_start() {
|
|
|
1400
1436
|
local positional_arg="" # raw positional arg for mode detection
|
|
1401
1437
|
local issue_ref_explicit="" # --issue value (when explicit_mode=issue)
|
|
1402
1438
|
local issue_mode_args=() # captured issue-mode flags (--dry-run, --pr, etc.)
|
|
1403
|
-
local issue_dry_run=false
|
|
1404
|
-
local issue_no_start=false
|
|
1405
1439
|
local issue_output_file=""
|
|
1406
1440
|
local issue_use_worktree=false
|
|
1407
1441
|
local issue_create_pr=false
|
|
@@ -1462,6 +1496,9 @@ cmd_start() {
|
|
|
1462
1496
|
echo " --no-plan Skip auto-shown PRD analysis at startup"
|
|
1463
1497
|
echo " --brief \"TEXT\" Zero-config fast first run from a one-line brief"
|
|
1464
1498
|
echo " --yes, -y Skip confirmation prompts (auto-confirm)"
|
|
1499
|
+
echo " --quiet Suppress [INFO]/[STEP]; warnings and errors still print"
|
|
1500
|
+
echo " --log-level LEVEL debug | info | warn | error (default: info)"
|
|
1501
|
+
echo " --max-duration DUR Wall-clock cap: seconds, or 90m / 2h (default: none)"
|
|
1465
1502
|
echo ""
|
|
1466
1503
|
echo "Issue-mode options (only used when input is an ISSUE-REF):"
|
|
1467
1504
|
echo " --dry-run Preview generated PRD without starting"
|
|
@@ -1472,6 +1509,16 @@ cmd_start() {
|
|
|
1472
1509
|
echo " --ship Worktree + PR + auto-merge"
|
|
1473
1510
|
echo " --detach, -d Run in background"
|
|
1474
1511
|
echo ""
|
|
1512
|
+
echo "Preview without spending (any input mode):"
|
|
1513
|
+
echo " loki plan <PRD> Analyze a spec WITHOUT executing: complexity,"
|
|
1514
|
+
echo " estimated iterations, tokens, and cost. Add --json"
|
|
1515
|
+
echo " for machine-readable output. This is the dry run"
|
|
1516
|
+
echo " for PRD and brief mode; --dry-run above is"
|
|
1517
|
+
echo " issue-mode only."
|
|
1518
|
+
echo " LOKI_CONFIG_DUMP=1 loki start"
|
|
1519
|
+
echo " Print the resolved LOKI_* configuration and exit"
|
|
1520
|
+
echo " without starting a run or spending anything."
|
|
1521
|
+
echo ""
|
|
1475
1522
|
echo "Environment Variables:"
|
|
1476
1523
|
echo " LOKI_PRD_FILE Path to PRD file (alternative to positional arg)"
|
|
1477
1524
|
echo " LOKI_AUTO_CONFIRM Set to 'true'/'false' to control prompts (takes precedence over CI)"
|
|
@@ -1661,6 +1708,52 @@ cmd_start() {
|
|
|
1661
1708
|
export LOKI_AUTO_CONFIRM=true
|
|
1662
1709
|
shift
|
|
1663
1710
|
;;
|
|
1711
|
+
# Wall-clock cap. Accepts plain seconds or a suffix (30m, 2h) --
|
|
1712
|
+
# an operator setting a build timeout thinks in minutes and hours,
|
|
1713
|
+
# and forcing them to compute 7200 invites the off-by-3600 error.
|
|
1714
|
+
--max-duration)
|
|
1715
|
+
if [[ -n "${2:-}" ]]; then
|
|
1716
|
+
local _md="$2" _mdn _mdmul=1
|
|
1717
|
+
case "$_md" in
|
|
1718
|
+
*s) _mdn="${_md%s}" ;;
|
|
1719
|
+
*m) _mdn="${_md%m}"; _mdmul=60 ;;
|
|
1720
|
+
*h) _mdn="${_md%h}"; _mdmul=3600 ;;
|
|
1721
|
+
*) _mdn="$_md" ;;
|
|
1722
|
+
esac
|
|
1723
|
+
if ! [[ "$_mdn" =~ ^[0-9]+$ ]] || [ "$_mdn" -eq 0 ]; then
|
|
1724
|
+
echo "Error: --max-duration must be a positive duration (e.g. 5400, 90m, 2h); got '$2'" >&2
|
|
1725
|
+
return 1
|
|
1726
|
+
fi
|
|
1727
|
+
export LOKI_MAX_DURATION=$(( _mdn * _mdmul ))
|
|
1728
|
+
shift 2
|
|
1729
|
+
else
|
|
1730
|
+
echo "Error: --max-duration requires a value (e.g. 90m)" >&2
|
|
1731
|
+
return 1
|
|
1732
|
+
fi
|
|
1733
|
+
;;
|
|
1734
|
+
# Verbosity. --quiet suppresses [INFO]/[STEP]; warnings and errors
|
|
1735
|
+
# always print, so a quiet run can still say why it failed.
|
|
1736
|
+
--quiet)
|
|
1737
|
+
export LOKI_LOG_LEVEL=warn
|
|
1738
|
+
shift
|
|
1739
|
+
;;
|
|
1740
|
+
--log-level)
|
|
1741
|
+
if [[ -n "${2:-}" ]]; then
|
|
1742
|
+
case "$2" in
|
|
1743
|
+
debug|info|warn|error)
|
|
1744
|
+
export LOKI_LOG_LEVEL="$2"
|
|
1745
|
+
;;
|
|
1746
|
+
*)
|
|
1747
|
+
echo "Error: --log-level must be debug, info, warn, or error (got '$2')" >&2
|
|
1748
|
+
return 1
|
|
1749
|
+
;;
|
|
1750
|
+
esac
|
|
1751
|
+
shift 2
|
|
1752
|
+
else
|
|
1753
|
+
echo "Error: --log-level requires a value (debug|info|warn|error)" >&2
|
|
1754
|
+
return 1
|
|
1755
|
+
fi
|
|
1756
|
+
;;
|
|
1664
1757
|
--bmad-project)
|
|
1665
1758
|
if [[ -n "${2:-}" ]]; then
|
|
1666
1759
|
bmad_project_path="$2"
|
|
@@ -1836,12 +1929,10 @@ cmd_start() {
|
|
|
1836
1929
|
;;
|
|
1837
1930
|
# v6.84.0: issue-mode flags (only meaningful when input is an issue)
|
|
1838
1931
|
--dry-run)
|
|
1839
|
-
issue_dry_run=true
|
|
1840
1932
|
issue_mode_args+=("--dry-run")
|
|
1841
1933
|
shift
|
|
1842
1934
|
;;
|
|
1843
1935
|
--no-start)
|
|
1844
|
-
issue_no_start=true
|
|
1845
1936
|
issue_mode_args+=("--no-start")
|
|
1846
1937
|
shift
|
|
1847
1938
|
;;
|
|
@@ -9879,6 +9970,7 @@ cmd_config() {
|
|
|
9879
9970
|
echo " issue.provider Default issue provider: github, gitlab, jira, azure_devops"
|
|
9880
9971
|
echo " blind_validation Blind validation mode: true, false (default: true)"
|
|
9881
9972
|
echo " adversarial_testing Adversarial testing: true, false (default: true)"
|
|
9973
|
+
echo " log_level Output verbosity: debug, info, warn, error (default: info)"
|
|
9882
9974
|
echo " spawn_timeout [DEPRECATED] No effect since WAVE9 (no consumer); accepted for back-compat"
|
|
9883
9975
|
echo " spawn_retries [DEPRECATED] No effect since WAVE9 (no consumer); accepted for back-compat"
|
|
9884
9976
|
echo " notify.slack Slack webhook URL"
|
|
@@ -9954,6 +10046,12 @@ cmd_config_set() {
|
|
|
9954
10046
|
*) echo -e "${RED}Invalid $key: $value (expected: true, false)${NC}"; return 1 ;;
|
|
9955
10047
|
esac
|
|
9956
10048
|
;;
|
|
10049
|
+
log_level)
|
|
10050
|
+
case "$value" in
|
|
10051
|
+
debug|info|warn|error) ;;
|
|
10052
|
+
*) echo -e "${RED}Invalid log_level: $value (expected: debug, info, warn, error)${NC}"; return 1 ;;
|
|
10053
|
+
esac
|
|
10054
|
+
;;
|
|
9957
10055
|
spawn_timeout|spawn_retries)
|
|
9958
10056
|
if ! echo "$value" | grep -qE '^[0-9]+$'; then
|
|
9959
10057
|
echo -e "${RED}Invalid $key: $value (expected: integer)${NC}"; return 1
|
|
@@ -9985,7 +10083,7 @@ cmd_config_set() {
|
|
|
9985
10083
|
echo -e "${RED}Unknown configuration key: '$key'${NC}"
|
|
9986
10084
|
echo ""
|
|
9987
10085
|
echo "Valid keys: maxTier, provider, issue.provider, blind_validation,"
|
|
9988
|
-
echo " adversarial_testing, spawn_timeout, spawn_retries, budget,"
|
|
10086
|
+
echo " adversarial_testing, log_level, spawn_timeout, spawn_retries, budget,"
|
|
9989
10087
|
echo " model.planning, model.development, model.fast,"
|
|
9990
10088
|
echo " notify.slack, notify.discord"
|
|
9991
10089
|
echo ""
|
|
@@ -11308,6 +11406,25 @@ except Exception:
|
|
|
11308
11406
|
echo ""
|
|
11309
11407
|
echo "Then re-run: loki doctor"
|
|
11310
11408
|
echo "Meanwhile 'loki tour' works right now -- no provider, no key, no spend."
|
|
11409
|
+
# Adoption signal: which CLASS of dependency stopped this first run.
|
|
11410
|
+
# Mapped to an enum here rather than forwarding the blocker TEXT, so
|
|
11411
|
+
# the payload can never carry a path or a version. Best-effort and
|
|
11412
|
+
# backgrounded: telemetry must never delay or fail a doctor run, and
|
|
11413
|
+
# every existing opt-out still gates it downstream.
|
|
11414
|
+
if declare -f loki_emit_first_run_blocked >/dev/null 2>&1; then
|
|
11415
|
+
local _blk_key="other"
|
|
11416
|
+
case "$_doctor_blockers" in
|
|
11417
|
+
*"No AI provider CLI"*) _blk_key="no_provider" ;;
|
|
11418
|
+
*"Node.js is not installed"*|*"Node.js must be"*) _blk_key="node" ;;
|
|
11419
|
+
*"Python 3 is not installed"*|*"Python 3 must be"*) _blk_key="python3" ;;
|
|
11420
|
+
*"jq is not installed"*) _blk_key="jq" ;;
|
|
11421
|
+
*"git is not installed"*) _blk_key="git" ;;
|
|
11422
|
+
*"curl is not installed"*) _blk_key="curl" ;;
|
|
11423
|
+
*"Free up disk"*) _blk_key="disk" ;;
|
|
11424
|
+
*"broken symlink"*) _blk_key="skill_symlink" ;;
|
|
11425
|
+
esac
|
|
11426
|
+
( loki_emit_first_run_blocked "$_blk_key" >/dev/null 2>&1 </dev/null & ) 2>/dev/null || true
|
|
11427
|
+
fi
|
|
11311
11428
|
return 1
|
|
11312
11429
|
elif [ "$warn_count" -gt 0 ]; then
|
|
11313
11430
|
echo -e "${YELLOW}All required checks passed with some warnings.${NC}"
|
|
@@ -11477,6 +11594,35 @@ if disk_status == 'pass': pass_count += 1
|
|
|
11477
11594
|
elif disk_status == 'fail': fail_count += 1
|
|
11478
11595
|
elif disk_status == 'warn': warn_count += 1
|
|
11479
11596
|
|
|
11597
|
+
# AGGREGATE PROVIDER CHECK. Each provider CLI is individually optional --
|
|
11598
|
+
# you need Claude OR Codex OR Cline OR Aider, not all four -- so no single
|
|
11599
|
+
# one can be marked required. But having NONE is a blocker, and the text
|
|
11600
|
+
# path reports it as one (the No-AI-provider branch near autonomy/loki:11000).
|
|
11601
|
+
#
|
|
11602
|
+
# --json omitted this entirely, so on a host with no provider the two
|
|
11603
|
+
# outputs gave OPPOSITE verdicts: the text path exited 1 while --json
|
|
11604
|
+
# reported zero failures and ok true. An operator gating a pipeline on the
|
|
11605
|
+
# JSON got a pass on a machine that cannot run a build -- the fake-green
|
|
11606
|
+
# this project exists to refuse.
|
|
11607
|
+
#
|
|
11608
|
+
# CAUTION: this block lives inside python3 -c with a DOUBLE-QUOTED program,
|
|
11609
|
+
# so an apostrophe or a double quote in a comment terminates the shell
|
|
11610
|
+
# string and mangles the program. First attempt did exactly that: doctor
|
|
11611
|
+
# --json printed nothing and exited 0. Keep this comment quote-free.
|
|
11612
|
+
_provider_cmds = ('claude', 'codex', 'cline', 'aider')
|
|
11613
|
+
_any_provider = any(shutil.which(_p) is not None for _p in _provider_cmds)
|
|
11614
|
+
ai_provider = {
|
|
11615
|
+
'found': _any_provider,
|
|
11616
|
+
'status': 'pass' if _any_provider else 'fail',
|
|
11617
|
+
'required': 'required',
|
|
11618
|
+
'detail': None if _any_provider
|
|
11619
|
+
else 'No AI provider CLI. Fix: npm install -g @anthropic-ai/claude-code',
|
|
11620
|
+
}
|
|
11621
|
+
if _any_provider:
|
|
11622
|
+
pass_count += 1
|
|
11623
|
+
else:
|
|
11624
|
+
fail_count += 1
|
|
11625
|
+
|
|
11480
11626
|
result = {
|
|
11481
11627
|
'loki_mode_version': os.environ.get('LOKI_VERSION', 'unknown'),
|
|
11482
11628
|
'checks': checks,
|
|
@@ -11484,6 +11630,7 @@ result = {
|
|
|
11484
11630
|
'available_gb': disk_gb,
|
|
11485
11631
|
'status': disk_status
|
|
11486
11632
|
},
|
|
11633
|
+
'ai_provider': ai_provider,
|
|
11487
11634
|
'sentrux': sentrux,
|
|
11488
11635
|
'receipt_signing': receipt_signing,
|
|
11489
11636
|
'memory': memory,
|
|
@@ -15589,6 +15736,35 @@ for fn in sorted(os.listdir(root)) if os.path.isdir(root) else []:
|
|
|
15589
15736
|
# dependency staleness is NOT computed offline -> reported as 'unknown', never guessed.
|
|
15590
15737
|
dep_lock_present = len(found_lockfiles) > 0 if found_manifests else None
|
|
15591
15738
|
|
|
15739
|
+
# RUNTIME + TOP DEPENDENCIES, read from the manifest itself. Offline-safe: this
|
|
15740
|
+
# reports what the file DECLARES, never what is current upstream (that stays
|
|
15741
|
+
# 'unknown' -- see dependency_staleness above).
|
|
15742
|
+
#
|
|
15743
|
+
# Why it matters: after 'how far behind am I', the next question a legacy
|
|
15744
|
+
# modernization buyer asks is 'what is this even built on'. Answering
|
|
15745
|
+
# 'package.json exists' and nothing more leaves them to open the file
|
|
15746
|
+
# themselves, which is the work they were trying to delegate.
|
|
15747
|
+
runtime_info = {'declared_runtime': None, 'declared_dependencies': None, 'framework_hints': []}
|
|
15748
|
+
try:
|
|
15749
|
+
if 'package.json' in found_manifests:
|
|
15750
|
+
with open(os.path.join(root, 'package.json')) as _f:
|
|
15751
|
+
_pkg = json.load(_f)
|
|
15752
|
+
_eng = (_pkg.get('engines') or {}).get('node')
|
|
15753
|
+
runtime_info['declared_runtime'] = ('node ' + _eng) if _eng else 'node (engine unspecified)'
|
|
15754
|
+
_deps = dict(_pkg.get('dependencies') or {})
|
|
15755
|
+
runtime_info['declared_dependencies'] = len(_deps)
|
|
15756
|
+
# Name only frameworks actually present. A hint is a FACT from the file,
|
|
15757
|
+
# not an inference about what the project "probably" is.
|
|
15758
|
+
for _fw in ('express', 'react', 'next', 'vue', 'angular', 'fastify', 'koa', 'nest'):
|
|
15759
|
+
if _fw in _deps:
|
|
15760
|
+
runtime_info['framework_hints'].append(_fw + ' ' + str(_deps[_fw]))
|
|
15761
|
+
elif 'pyproject.toml' in found_manifests or 'requirements.txt' in found_manifests:
|
|
15762
|
+
runtime_info['declared_runtime'] = 'python (version unspecified in manifest)'
|
|
15763
|
+
except (OSError, ValueError):
|
|
15764
|
+
# A malformed manifest is not a crash and not a guess: leave the fields None
|
|
15765
|
+
# so the report says nothing rather than something wrong.
|
|
15766
|
+
pass
|
|
15767
|
+
|
|
15592
15768
|
todo_density = round((todo_count / total_loc) * 1000, 2) if total_loc else 0.0
|
|
15593
15769
|
|
|
15594
15770
|
# --- 4-level maturity model (Ad hoc -> Planned -> Systematic -> Optimized) ---
|
|
@@ -15683,6 +15859,7 @@ report = {
|
|
|
15683
15859
|
'lockfiles': found_lockfiles,
|
|
15684
15860
|
'dependency_lock_present': dep_lock_present,
|
|
15685
15861
|
'dependency_staleness': 'unknown', # not computable offline; never guessed
|
|
15862
|
+
'runtime': runtime_info,
|
|
15686
15863
|
},
|
|
15687
15864
|
'maturity': {
|
|
15688
15865
|
'level': maturity_level,
|
|
@@ -29711,6 +29888,7 @@ cmd_ci() {
|
|
|
29711
29888
|
echo " --github-comment Post review results as PR comment (needs GITHUB_TOKEN)"
|
|
29712
29889
|
echo " --fail-on <levels> Set exit code 1 on severity: critical,high,medium,low"
|
|
29713
29890
|
echo " --format <fmt> Output format: json, markdown, github (default: markdown)"
|
|
29891
|
+
echo " --json Alias for --format json"
|
|
29714
29892
|
echo " --help, -h Show this help"
|
|
29715
29893
|
echo ""
|
|
29716
29894
|
echo "Exit codes:"
|
|
@@ -29753,6 +29931,15 @@ cmd_ci() {
|
|
|
29753
29931
|
ci_fail_on="$(echo "$ci_fail_on" | tr '[:upper:]' '[:lower:]')"
|
|
29754
29932
|
shift
|
|
29755
29933
|
;;
|
|
29934
|
+
# --json is an alias for --format json. `loki ci` could already
|
|
29935
|
+
# emit JSON, but only under a flag name no other command uses, so a
|
|
29936
|
+
# script author reaching for the repo-wide --json convention found
|
|
29937
|
+
# nothing and concluded the capability was missing. Same output,
|
|
29938
|
+
# one spelling everywhere.
|
|
29939
|
+
--json)
|
|
29940
|
+
ci_format="json"
|
|
29941
|
+
shift
|
|
29942
|
+
;;
|
|
29756
29943
|
--format)
|
|
29757
29944
|
shift
|
|
29758
29945
|
ci_format="${1:-markdown}"
|
|
@@ -32523,7 +32710,8 @@ cmd_proof() {
|
|
|
32523
32710
|
echo " show <id> Pretty-print .loki/proofs/<id>/proof.json"
|
|
32524
32711
|
echo " verify <id> Re-check a receipt against the repo (tamper + drift);"
|
|
32525
32712
|
echo " exit 0 clean, 1 tamper/drift. Verify it yourself."
|
|
32526
|
-
echo "
|
|
32713
|
+
echo " md <id> Paste-able Markdown for a PR comment or Slack
|
|
32714
|
+
open <id> Open .loki/proofs/<id>/index.html in a browser"
|
|
32527
32715
|
echo " share <id> Publish the proof page as a GitHub Gist (opt-in)"
|
|
32528
32716
|
echo ""
|
|
32529
32717
|
echo "Options for 'share':"
|
|
@@ -32608,6 +32796,38 @@ PYEOF
|
|
|
32608
32796
|
fi
|
|
32609
32797
|
exit 0
|
|
32610
32798
|
;;
|
|
32799
|
+
md)
|
|
32800
|
+
# Paste-able Markdown for a PR comment, a Slack message, or a
|
|
32801
|
+
# ticket. The renderer already existed -- render_evidence_receipt_md
|
|
32802
|
+
# in lib/proof-pr.sh -- but was reachable ONLY when Loki opened a PR
|
|
32803
|
+
# itself. So the one form of the receipt built for humans to forward
|
|
32804
|
+
# could not be produced on demand, which is the built-and-unreachable
|
|
32805
|
+
# pattern this project keeps finding.
|
|
32806
|
+
#
|
|
32807
|
+
# Reuses that renderer rather than adding a second one: two writers
|
|
32808
|
+
# of one artifact drift, and the drift surfaces as a PR body and a
|
|
32809
|
+
# pasted summary disagreeing about the same run.
|
|
32810
|
+
local id="${1:-}"
|
|
32811
|
+
if [ -z "$id" ]; then
|
|
32812
|
+
echo -e "${RED}Missing proof id.${NC} Use 'loki proof list'." >&2
|
|
32813
|
+
exit 2
|
|
32814
|
+
fi
|
|
32815
|
+
local pj="${proofs_dir}/${id}/proof.json"
|
|
32816
|
+
if [ ! -f "$pj" ]; then
|
|
32817
|
+
echo -e "${RED}Proof not found: ${id}${NC}" >&2
|
|
32818
|
+
echo "Use 'loki proof list' to see available proofs." >&2
|
|
32819
|
+
exit 1
|
|
32820
|
+
fi
|
|
32821
|
+
local _pr_lib="$_LOKI_SCRIPT_DIR/lib/proof-pr.sh"
|
|
32822
|
+
if [ ! -f "$_pr_lib" ]; then
|
|
32823
|
+
echo -e "${RED}Renderer not found: ${_pr_lib}${NC}" >&2
|
|
32824
|
+
exit 3
|
|
32825
|
+
fi
|
|
32826
|
+
# shellcheck source=/dev/null
|
|
32827
|
+
source "$_pr_lib"
|
|
32828
|
+
render_evidence_receipt_md "$pj"
|
|
32829
|
+
exit 0
|
|
32830
|
+
;;
|
|
32611
32831
|
verify)
|
|
32612
32832
|
# Deterministic re-check of a receipt against the repo: re-hashes the
|
|
32613
32833
|
# canonical proof (tamper check) and re-derives the diff from the
|
package/autonomy/run.sh
CHANGED
|
@@ -1508,11 +1508,39 @@ log_header() {
|
|
|
1508
1508
|
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
|
1509
1509
|
}
|
|
1510
1510
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1511
|
+
# VERBOSITY. LOKI_LOG_LEVEL is one of debug|info|warn|error (default info);
|
|
1512
|
+
# LOKI_QUIET=1 is shorthand for warn.
|
|
1513
|
+
#
|
|
1514
|
+
# Implemented at these five functions rather than at 527 call sites, which is
|
|
1515
|
+
# both the smaller diff and the only version that cannot drift -- a new
|
|
1516
|
+
# log_info added next year is covered without anyone remembering to gate it.
|
|
1517
|
+
#
|
|
1518
|
+
# WHY THIS IS NEEDED DESPITE GOOD TTY DETECTION. The heavy decoration (HUD,
|
|
1519
|
+
# completion card, start headline) is already `[ -t 1 ]`-guarded and vanishes
|
|
1520
|
+
# off a TTY, so CI output was never the wall of banners it might have been.
|
|
1521
|
+
# But 527 log_info/log_step calls are unguarded and print regardless, and a
|
|
1522
|
+
# pipeline that wants only warnings had no way to ask. log_debug already
|
|
1523
|
+
# honored LOKI_DEBUG; the other levels honored nothing.
|
|
1524
|
+
#
|
|
1525
|
+
# ERRORS ARE NEVER SUPPRESSED. `error` is the floor: the quietest setting still
|
|
1526
|
+
# prints failures. A verbosity flag that can hide the reason a build failed is
|
|
1527
|
+
# a footgun, not a feature.
|
|
1528
|
+
_loki_log_threshold() {
|
|
1529
|
+
case "${LOKI_LOG_LEVEL:-$([ "${LOKI_QUIET:-0}" = "1" ] && echo warn || echo info)}" in
|
|
1530
|
+
debug) echo 0 ;;
|
|
1531
|
+
info) echo 1 ;;
|
|
1532
|
+
warn) echo 2 ;;
|
|
1533
|
+
error) echo 3 ;;
|
|
1534
|
+
*) echo 1 ;; # unrecognized value behaves as the default, never silences
|
|
1535
|
+
esac
|
|
1536
|
+
}
|
|
1537
|
+
_loki_log_enabled() { [ "$1" -ge "$(_loki_log_threshold)" ]; }
|
|
1538
|
+
|
|
1539
|
+
log_info() { _loki_log_enabled 1 && echo -e "${GREEN}[INFO]${NC} $*" || true; }
|
|
1540
|
+
log_warn() { _loki_log_enabled 2 && echo -e "${YELLOW}[WARN]${NC} $*" || true; }
|
|
1513
1541
|
log_warning() { log_warn "$@"; } # Alias for backwards compatibility
|
|
1514
1542
|
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
1515
|
-
log_step() { echo -e "${CYAN}[STEP]${NC} $*"; }
|
|
1543
|
+
log_step() { _loki_log_enabled 1 && echo -e "${CYAN}[STEP]${NC} $*" || true; }
|
|
1516
1544
|
log_debug() { [[ "${LOKI_DEBUG:-}" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $*" >&2 || true; }
|
|
1517
1545
|
|
|
1518
1546
|
#===============================================================================
|
|
@@ -13293,6 +13321,17 @@ ${dependency_context}"
|
|
|
13293
13321
|
"diff_bytes=$_review_diff_bytes" \
|
|
13294
13322
|
"limit_bytes=$_review_max_bytes" \
|
|
13295
13323
|
"iteration=${ITERATION_COUNT:-0}" 2>/dev/null || true
|
|
13324
|
+
# Classify this as INFRASTRUCTURE, not a finding. The discriminator is
|
|
13325
|
+
# otherwise set at :14912, which this early return never reaches, so the
|
|
13326
|
+
# variable stayed "" from the reset at :12959 and the consumer at :22126
|
|
13327
|
+
# read a file-size condition as "Critical/High findings" -- escalating to
|
|
13328
|
+
# PAUSE over a diff that was merely too large to send.
|
|
13329
|
+
#
|
|
13330
|
+
# Still fail-closed: the review did NOT pass, and nothing here converts a
|
|
13331
|
+
# skipped review into a green one. It only records WHY it could not run,
|
|
13332
|
+
# which is the difference between "your code is bad" and "we could not
|
|
13333
|
+
# look at it".
|
|
13334
|
+
_LOKI_REVIEW_FAILURE_KIND="infrastructure_inconclusive"
|
|
13296
13335
|
return 1
|
|
13297
13336
|
fi
|
|
13298
13337
|
|
|
@@ -16470,6 +16509,46 @@ check_max_iterations() {
|
|
|
16470
16509
|
return 1
|
|
16471
16510
|
}
|
|
16472
16511
|
|
|
16512
|
+
# WALL-CLOCK CAP. Returns 0 (stop) when the run has exceeded LOKI_MAX_DURATION
|
|
16513
|
+
# seconds. Unset or 0 = no cap, which is the default and exactly today's
|
|
16514
|
+
# behavior.
|
|
16515
|
+
#
|
|
16516
|
+
# WHY A THIRD BOUND. Spend was already capped (LOKI_BUDGET_LIMIT) and so were
|
|
16517
|
+
# iterations (LOKI_MAX_ITERATIONS), but a run that STALLS is bounded by neither:
|
|
16518
|
+
# a hung provider call or a wedged subprocess burns hours while spending almost
|
|
16519
|
+
# nothing and completing no iteration, so neither existing breaker ever trips.
|
|
16520
|
+
# It runs until something external kills it -- and an external kill leaves no
|
|
16521
|
+
# terminal status, so the receipt cannot say what happened.
|
|
16522
|
+
#
|
|
16523
|
+
# Kubernetes operators already have activeDeadlineSeconds in the Job spec, but
|
|
16524
|
+
# that SIGKILLs the pod: no status is written, no receipt, and the platform
|
|
16525
|
+
# sees a crash rather than a deliberate stop. This cap stops the loop cleanly
|
|
16526
|
+
# at the next iteration boundary so the run still explains itself.
|
|
16527
|
+
#
|
|
16528
|
+
# It is checked at the boundary, not mid-iteration: interrupting an agent
|
|
16529
|
+
# mid-write is how you get a half-applied change. So the effective stop time is
|
|
16530
|
+
# the cap plus the remainder of the current iteration, which the log states
|
|
16531
|
+
# rather than pretending to be exact.
|
|
16532
|
+
check_max_duration() {
|
|
16533
|
+
local cap="${LOKI_MAX_DURATION:-0}"
|
|
16534
|
+
case "$cap" in
|
|
16535
|
+
''|*[!0-9]*) return 1 ;; # unset or non-numeric: no cap, never stop
|
|
16536
|
+
esac
|
|
16537
|
+
[ "$cap" -eq 0 ] && return 1
|
|
16538
|
+
|
|
16539
|
+
local start="${_LOKI_RUN_START_EPOCH:-0}"
|
|
16540
|
+
[ "$start" -eq 0 ] 2>/dev/null && return 1
|
|
16541
|
+
|
|
16542
|
+
local now elapsed
|
|
16543
|
+
now=$(date +%s)
|
|
16544
|
+
elapsed=$((now - start))
|
|
16545
|
+
if [ "$elapsed" -ge "$cap" ]; then
|
|
16546
|
+
log_warn "Wall-clock cap reached (${elapsed}s elapsed, LOKI_MAX_DURATION=${cap}s). Stopping at the iteration boundary."
|
|
16547
|
+
return 0
|
|
16548
|
+
fi
|
|
16549
|
+
return 1
|
|
16550
|
+
}
|
|
16551
|
+
|
|
16473
16552
|
# Load latest ledger content for context injection
|
|
16474
16553
|
load_ledger_context() {
|
|
16475
16554
|
local ledger_content=""
|
|
@@ -20577,6 +20656,18 @@ except Exception as exc:
|
|
|
20577
20656
|
return 20
|
|
20578
20657
|
fi
|
|
20579
20658
|
|
|
20659
|
+
# Wall-clock cap. Checked alongside the iteration cap and treated the
|
|
20660
|
+
# same way: a distinct terminal status so the receipt and `loki why`
|
|
20661
|
+
# can say the run ran out of TIME rather than iterations or money, and
|
|
20662
|
+
# exit 20 because re-running the same spec under the same cap will hit
|
|
20663
|
+
# the same wall. The operator raises LOKI_MAX_DURATION (or narrows the
|
|
20664
|
+
# spec) and submits again.
|
|
20665
|
+
if check_max_duration; then
|
|
20666
|
+
save_state "$retry" "max_duration_reached" 20
|
|
20667
|
+
emit_completion_summary max_duration
|
|
20668
|
+
return 20
|
|
20669
|
+
fi
|
|
20670
|
+
|
|
20580
20671
|
# Increment after all pre-attempt stop checks pass.
|
|
20581
20672
|
((ITERATION_COUNT++))
|
|
20582
20673
|
|
|
@@ -24266,11 +24357,12 @@ except Exception:
|
|
|
24266
24357
|
#
|
|
24267
24358
|
# Contract (LOKI_DURABLE_STATE=1 only; local/CI exit codes are unchanged):
|
|
24268
24359
|
# 0 = success / human-controlled clean stop (council approved, completion
|
|
24269
|
-
# promise, force-stop, or paused/interrupted/
|
|
24270
|
-
#
|
|
24360
|
+
# promise, force-stop, or paused/interrupted/stopped where a HUMAN
|
|
24361
|
+
# chose to stop and will resume). Job -> Complete, no retry.
|
|
24271
24362
|
# 20 = deterministic terminal failure (failed, max_iterations_reached,
|
|
24272
|
-
# max_retries_exceeded,
|
|
24273
|
-
#
|
|
24363
|
+
# max_retries_exceeded, budget_exceeded, max_duration_reached,
|
|
24364
|
+
# policy_blocked). Re-running on
|
|
24365
|
+
# the same inputs fails the same way -> Job must NOT retry. The Helm Job pairs
|
|
24274
24366
|
# this with restartPolicy: Never + a podFailurePolicy rule that maps
|
|
24275
24367
|
# exit 20 to FailJob (no retry), so a deterministic failure does not
|
|
24276
24368
|
# burn the backoffLimit (the Job records the failure; an operator
|
|
@@ -24283,9 +24375,25 @@ except Exception:
|
|
|
24283
24375
|
_final_state_file="$(_loki_state_file)"
|
|
24284
24376
|
_final_status=$(LOKI_STATE_FILE="$_final_state_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_STATE_FILE'])).get('status','unknown'))" 2>/dev/null || echo "unknown")
|
|
24285
24377
|
case "$_final_status" in
|
|
24286
|
-
council_approved|council_force_approved|deterministic_gates_passed|completion_promise_fulfilled|force_stopped|paused|interrupted|
|
|
24378
|
+
council_approved|council_force_approved|deterministic_gates_passed|completion_promise_fulfilled|force_stopped|paused|interrupted|stopped)
|
|
24287
24379
|
result=0 ;;
|
|
24288
|
-
|
|
24380
|
+
# budget_exceeded belongs HERE, not with the human-controlled stops.
|
|
24381
|
+
# It sat in the result=0 arm on the rationale that "a human will
|
|
24382
|
+
# resume", which is true of `paused` (a human pressed pause) and
|
|
24383
|
+
# false of a cost breaker firing inside a k8s Job or a CI pipeline,
|
|
24384
|
+
# where there is no human. A build killed mid-work then reported
|
|
24385
|
+
# SUCCESS: the Job went Complete, the pipeline went green, and an
|
|
24386
|
+
# incomplete build looked finished. That is a false green produced
|
|
24387
|
+
# by our own gate, which is precisely what the Evidence Receipt
|
|
24388
|
+
# exists to prevent.
|
|
24389
|
+
#
|
|
24390
|
+
# It is deterministic rather than retryable: re-running the same
|
|
24391
|
+
# inputs against the same cap exhausts the same budget and fails
|
|
24392
|
+
# identically, so retrying only burns money to reach the same place.
|
|
24393
|
+
# The operator raises the cap (or narrows the spec) and submits a
|
|
24394
|
+
# NEW Job -- the same remedy as max_iterations_reached, which is why
|
|
24395
|
+
# it shares that code.
|
|
24396
|
+
failed|max_iterations_reached|max_retries_exceeded|budget_exceeded|max_duration_reached|policy_blocked|inconclusive_spec_contradiction)
|
|
24289
24397
|
result=20 ;;
|
|
24290
24398
|
*)
|
|
24291
24399
|
# Unknown/running/exited terminal: leave $result as-is (nonzero on a
|
package/autonomy/telemetry.sh
CHANGED
|
@@ -187,6 +187,20 @@ _loki_known_command() {
|
|
|
187
187
|
esac
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
# Clamp a doctor blocker to a FIXED ENUM. Same discipline as
|
|
191
|
+
# _loki_known_command: an unrecognized value becomes "other" rather than being
|
|
192
|
+
# forwarded, so a new blocker string added later cannot silently start leaking
|
|
193
|
+
# text. Deliberately coarse -- we need to know WHICH CLASS of dependency stops
|
|
194
|
+
# a first run, never the user's paths, versions, or hostnames.
|
|
195
|
+
_loki_known_blocker() {
|
|
196
|
+
case "${1:-}" in
|
|
197
|
+
no_provider|node|python3|jq|git|curl|disk|skill_symlink)
|
|
198
|
+
printf '%s' "$1" ;;
|
|
199
|
+
*)
|
|
200
|
+
printf 'other' ;;
|
|
201
|
+
esac
|
|
202
|
+
}
|
|
203
|
+
|
|
190
204
|
loki_telemetry() {
|
|
191
205
|
_loki_telemetry_enabled || return 0
|
|
192
206
|
local event="$1"; shift
|
|
@@ -297,6 +311,37 @@ loki_emit_funnel_once() {
|
|
|
297
311
|
# fields are excluded on purpose: headline is a bounded enum (VERIFIED / VERIFIED
|
|
298
312
|
# WITH GAPS / NOT VERIFIED), never spec/project text; files_changed is a COUNT,
|
|
299
313
|
# never paths.
|
|
314
|
+
# loki_emit_first_run_blocked <blocker-key>: fire ONE first_run_blocked event
|
|
315
|
+
# naming the CLASS of dependency that stopped a first run.
|
|
316
|
+
#
|
|
317
|
+
# WHY THIS EXISTS. `first_start_attempted` already fires, so we know a first run
|
|
318
|
+
# was ATTEMPTED and nothing about whether it succeeded. That is the wrong half:
|
|
319
|
+
# an attempt that dies at `doctor` looks identical to one that built something.
|
|
320
|
+
# Without this, the question that decides an adoption strategy -- does a trial
|
|
321
|
+
# fail on capability, discoverability, or trust -- has no data behind it.
|
|
322
|
+
#
|
|
323
|
+
# The measured motivation is concrete: on a host with no provider CLI the Bun
|
|
324
|
+
# route used to print "Some required prerequisites are missing" and stop, while
|
|
325
|
+
# bash pointed at `loki tour`. That was a dead end for a first-time evaluator
|
|
326
|
+
# and nobody could see it happening.
|
|
327
|
+
#
|
|
328
|
+
# WHAT IT SENDS, and deliberately no more: one enum from _loki_known_blocker.
|
|
329
|
+
# Never a path, a version, a hostname, a spec, or a command line. Coarse on
|
|
330
|
+
# purpose -- "node" is actionable, "/Users/x/.nvm/versions/node/v18" is a leak.
|
|
331
|
+
# An adoption tool that exfiltrates a user's environment would cost exactly the
|
|
332
|
+
# trust this product sells.
|
|
333
|
+
#
|
|
334
|
+
# Once per install (the funnel marker), under the strict analytics gate, and
|
|
335
|
+
# every existing opt-out still wins because it routes through the same
|
|
336
|
+
# _loki_analytics_enabled check as everything else.
|
|
337
|
+
loki_emit_first_run_blocked() {
|
|
338
|
+
local _blocker
|
|
339
|
+
_blocker="$(_loki_known_blocker "${1:-}")"
|
|
340
|
+
loki_emit_funnel_once "first-run-blocked" "first_run_blocked" \
|
|
341
|
+
"blocker=${_blocker}" 2>/dev/null || true
|
|
342
|
+
return 0
|
|
343
|
+
}
|
|
344
|
+
|
|
300
345
|
loki_emit_build_verified() {
|
|
301
346
|
_loki_analytics_enabled || return 0
|
|
302
347
|
local _proof="$1"
|