loki-mode 11.3.9 → 11.5.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.
Files changed (119) hide show
  1. package/README.md +34 -18
  2. package/SKILL.md +3 -3
  3. package/VERSION +1 -1
  4. package/agents/custom_agents.py +280 -0
  5. package/autonomy/council-v2.sh +22 -1
  6. package/autonomy/edit.sh +242 -0
  7. package/autonomy/lib/chat.sh +315 -0
  8. package/autonomy/lib/engine10-push.sh +19 -1
  9. package/autonomy/lib/help-core.sh +164 -0
  10. package/autonomy/lib/json-output.sh +38 -0
  11. package/autonomy/lib/proof-generator.py +23 -5
  12. package/autonomy/lib/proof-verify.py +607 -5
  13. package/autonomy/lib/run-summary.sh +112 -0
  14. package/autonomy/lib/setup-requirements.json +35 -0
  15. package/autonomy/loki +337 -46
  16. package/autonomy/provider-offer.sh +95 -47
  17. package/autonomy/route.sh +302 -0
  18. package/autonomy/run.sh +48 -14
  19. package/autonomy/setup-wizard.sh +170 -0
  20. package/autonomy/verify.sh +62 -3
  21. package/bin/loki +73 -18
  22. package/completions/_loki +58 -1
  23. package/completions/loki.bash +57 -5
  24. package/dashboard/__init__.py +1 -1
  25. package/docs/CLI-CORE-VERBS.md +112 -0
  26. package/docs/CLI-REFERENCE.md +9 -1
  27. package/docs/COMPARE-OPENCODE.md +94 -0
  28. package/docs/COMPONENTS.md +1 -0
  29. package/docs/EVALUATING.md +27 -0
  30. package/docs/HARNESS-INTELLIGENCE-PLAN.md +20 -0
  31. package/docs/INSTALLATION.md +56 -12
  32. package/docs/KNOWN-ISSUES.md +8 -9
  33. package/docs/PROOF-PASSPORT.md +114 -0
  34. package/docs/QUICKSTART.md +108 -0
  35. package/docs/alternative-installations.md +103 -0
  36. package/docs/cost-controls.md +139 -0
  37. package/docs/environment-variables.md +45 -2
  38. package/docs/exit-codes.md +129 -12
  39. package/docs/spec-first-intent.md +29 -0
  40. package/docs/v10/CLI-MODERN.md +11 -5
  41. package/docs/v10/ENGINE.md +32 -0
  42. package/docs/v10/GUIDE.md +270 -44
  43. package/docs/v10/OPENCODE-PARITY-PLAN.md +84 -0
  44. package/loki-ts/data/model-pricing.json +1 -0
  45. package/loki-ts/dist/loki.js +1065 -978
  46. package/mcp/__init__.py +1 -1
  47. package/package.json +2 -2
  48. package/packages/control-plane/dist/server.js +1829 -715
  49. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  50. package/plugins/loki-mode/README.md +41 -1
  51. package/plugins/loki-mode/commands/handoff.md +12 -0
  52. package/plugins/loki-mode/commands/loki-verify.md +9 -2
  53. package/plugins/loki-mode/commands/start.md +20 -0
  54. package/plugins/loki-mode/hooks/hooks.json +11 -0
  55. package/plugins/loki-mode/scripts/loki-stop-verify.sh +84 -0
  56. package/providers/claude.sh +16 -2
  57. package/providers/model_route_exec.sh +22 -0
  58. package/providers/model_routes.sh +188 -0
  59. package/skills/providers.md +13 -0
  60. package/templates/agents/planner.md +10 -0
  61. package/templates/agents/reviewer.md +10 -0
  62. package/templates/agents/tester.md +10 -0
  63. package/web-app/dist/assets/{AdminPage-QK-i_x-O.js → AdminPage-Cbu3pXL8.js} +1 -1
  64. package/web-app/dist/assets/{Avatar-DblkLNjB.js → Avatar-DGqGGcGx.js} +1 -1
  65. package/web-app/dist/assets/{Badge-DIV2ny5H.js → Badge-Bhlipt6v.js} +1 -1
  66. package/web-app/dist/assets/{Button--rePJOlQ.js → Button-CpN2sO61.js} +1 -1
  67. package/web-app/dist/assets/{CockpitPage-BZlKxpGy.js → CockpitPage-BqeDJwdL.js} +1 -1
  68. package/web-app/dist/assets/{ComparePage-3_INSVux.js → ComparePage-DUieA2ba.js} +1 -1
  69. package/web-app/dist/assets/{ErrorBoundary-DHG63CNr.js → ErrorBoundary-DTWsYrOm.js} +1 -1
  70. package/web-app/dist/assets/{EvidenceReceiptPanel-DFsBnLlO.js → EvidenceReceiptPanel-BU_B9X19.js} +1 -1
  71. package/web-app/dist/assets/{GitHubIssuesPanel-phPGah98.js → GitHubIssuesPanel-DEZoLOsP.js} +1 -1
  72. package/web-app/dist/assets/{GitHubPRsPanel-T7KQqJmx.js → GitHubPRsPanel-BEsnrq7Y.js} +1 -1
  73. package/web-app/dist/assets/{HomePage-DFjGUF5I.js → HomePage-BoUVuq7h.js} +2 -2
  74. package/web-app/dist/assets/{LoginPage-CorUid_5.js → LoginPage-CmOED7vb.js} +1 -1
  75. package/web-app/dist/assets/{MagicPage-DofbSxKD.js → MagicPage-DATZitrZ.js} +1 -1
  76. package/web-app/dist/assets/{MetricsPage-qzrhhfLC.js → MetricsPage-DGL1v0I-.js} +1 -1
  77. package/web-app/dist/assets/{NotFoundPage-BAZR0mMb.js → NotFoundPage-C2WgOjZd.js} +1 -1
  78. package/web-app/dist/assets/{ProjectPage-BOSUzG9J.js → ProjectPage-BMi1tepj.js} +3 -3
  79. package/web-app/dist/assets/{ProjectsPage-Ba0YLtzD.js → ProjectsPage-vc3y5YPh.js} +1 -1
  80. package/web-app/dist/assets/{SettingsPage-Cr10Ue2K.js → SettingsPage-kbq0Ftvi.js} +1 -1
  81. package/web-app/dist/assets/{ShowcasePage-Czn655fe.js → ShowcasePage-Bn6Q8Kbg.js} +1 -1
  82. package/web-app/dist/assets/{SystemSettingsPage-Dwjnw6Ob.js → SystemSettingsPage-Basmhs_b.js} +1 -1
  83. package/web-app/dist/assets/{TeamsPage-D8nc69Be.js → TeamsPage-BTqOc4TR.js} +1 -1
  84. package/web-app/dist/assets/{TemplatesPage-DHsaCDGv.js → TemplatesPage-2P9002VL.js} +1 -1
  85. package/web-app/dist/assets/{TerminalOutput-DBejni2a.js → TerminalOutput-CAoSPBrg.js} +1 -1
  86. package/web-app/dist/assets/{activity-B2VsBl1C.js → activity-BcWVWCqU.js} +1 -1
  87. package/web-app/dist/assets/{bell-CT77Fq2H.js → bell-CYRqqgZ6.js} +1 -1
  88. package/web-app/dist/assets/{bot-DfaWgF4H.js → bot-CU7gdn3g.js} +1 -1
  89. package/web-app/dist/assets/{check-D1yp7Ndv.js → check-DLlCpz9V.js} +1 -1
  90. package/web-app/dist/assets/{chevron-left-B6Pa8IOX.js → chevron-left-CE9Cb30C.js} +1 -1
  91. package/web-app/dist/assets/{circle-alert-D5jrL_Y7.js → circle-alert-uou4bjua.js} +1 -1
  92. package/web-app/dist/assets/{clock-DIsY_fa_.js → clock-C_dSJcDX.js} +1 -1
  93. package/web-app/dist/assets/{cloud-eGwLh1As.js → cloud-DZ22ITF6.js} +1 -1
  94. package/web-app/dist/assets/{code-xml-CGDIcZ3m.js → code-xml-BrJS0hhS.js} +1 -1
  95. package/web-app/dist/assets/{database-CWpdaykI.js → database-D2HOmxtl.js} +1 -1
  96. package/web-app/dist/assets/{dollar-sign-DZEuTCs6.js → dollar-sign-CQ1vK_6q.js} +1 -1
  97. package/web-app/dist/assets/{file-code-corner-BHBW6m9v.js → file-code-corner-w2vpGm5d.js} +1 -1
  98. package/web-app/dist/assets/{file-plus-D4h1jZSR.js → file-plus-DlF6wjkD.js} +1 -1
  99. package/web-app/dist/assets/{globe-CXdqKgwd.js → globe-Dy3lBJg1.js} +1 -1
  100. package/web-app/dist/assets/{hammer-B3YuuYI_.js → hammer-C3xIiRXN.js} +1 -1
  101. package/web-app/dist/assets/{index-BLEnboSv.js → index-4t-_IW2y.js} +3 -3
  102. package/web-app/dist/assets/{layers-Dvp5nFtw.js → layers-Y8Fs8qXI.js} +1 -1
  103. package/web-app/dist/assets/{loader-circle-DodDmIZ3.js → loader-circle-CxgN1v4m.js} +1 -1
  104. package/web-app/dist/assets/{lock-EiuaH1Ep.js → lock-DFFNbXDs.js} +1 -1
  105. package/web-app/dist/assets/{package-Cx4m2hFg.js → package-5e6qCTUg.js} +1 -1
  106. package/web-app/dist/assets/{plus-D-Sd5rpH.js → plus-C0Go_ytj.js} +1 -1
  107. package/web-app/dist/assets/{refresh-cw-C0w1cdmF.js → refresh-cw-D4i-NW3o.js} +1 -1
  108. package/web-app/dist/assets/{rotate-ccw-C_qk6MTT.js → rotate-ccw-YlKkiUW6.js} +1 -1
  109. package/web-app/dist/assets/{scroll-text-DSxiFtkc.js → scroll-text-4qXOHTap.js} +1 -1
  110. package/web-app/dist/assets/{server-NTJt7I8M.js → server-BB6zXNp9.js} +1 -1
  111. package/web-app/dist/assets/{shield-alert-HBxMQOvH.js → shield-alert-D4tzjB1H.js} +1 -1
  112. package/web-app/dist/assets/{thumbs-up-DEgsHzSD.js → thumbs-up-DZiRwdFj.js} +1 -1
  113. package/web-app/dist/assets/{trash-2-px46FnTD.js → trash-2-Dv_LkRIK.js} +1 -1
  114. package/web-app/dist/assets/{trending-down-CBosVGKa.js → trending-down-BJYp9PQe.js} +1 -1
  115. package/web-app/dist/assets/{trending-up-Dbd7zrQj.js → trending-up-BTcXLklK.js} +1 -1
  116. package/web-app/dist/assets/{upload-nhdFKAs1.js → upload-fcHqwOxr.js} +1 -1
  117. package/web-app/dist/assets/{usePolling-BgaGdinh.js → usePolling-BeJYw_4L.js} +1 -1
  118. package/web-app/dist/assets/{user-CzIihUy9.js → user-BcDwUCsO.js} +1 -1
  119. package/web-app/dist/index.html +1 -1
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bash
2
+ # Core help screen and help topics for `loki help`. Source this file; it
3
+ # defines functions only. The full command table stays in show_help_all
4
+ # (autonomy/loki) and is reached with `loki help all`.
5
+
6
+ # Topics reachable as `loki help <topic>`. Keep in sync with help_topic_text.
7
+ LOKI_HELP_TOPICS="providers exit-codes"
8
+
9
+ help_core_screen() {
10
+ local version="$1"
11
+ echo "Loki Mode v${version}"
12
+ echo ""
13
+ echo "Usage: loki <command> [options]"
14
+ echo ""
15
+ echo "Start:"
16
+ echo " start [SPEC] PRIMARY: build from a PRD file, brief, or issue (owner/repo#N, PROJ-1) (alias: run)"
17
+ echo " chat [--attach] Interactive alternative; --attach joins the current 'loki start' run"
18
+ echo " edit \"<change>\" Make one targeted change to the current repo"
19
+ echo " quick \"<task>\" One small task, no PR"
20
+ echo ""
21
+ echo "Check:"
22
+ echo " status [--json] What is running and where it stands"
23
+ echo " verify [--json] Deterministic verification (CI-gate exit codes)"
24
+ echo " receipt Inspect the proof-of-run receipt (alias of: proof)"
25
+ echo " diff / undo See what a run changed; undo <run> to plan or apply a rollback"
26
+ echo ""
27
+ echo "Configure:"
28
+ echo " setup First-run setup (provider, keys, defaults)"
29
+ echo " config Show or change settings (config list --json)"
30
+ echo " models Model tiers and routes (models --local for local models)"
31
+ echo " agents Agent roles and routing (route shows where a task would go)"
32
+ echo " doctor [--json] Check your setup is ready"
33
+ echo ""
34
+ echo "Options for 'start': --provider NAME, --model ID, --budget USD (default cap $(loki_budget_default_usd 2>/dev/null)), --isolation LVL (isolation dial: none | worktree | docker)"
35
+ echo "Also: cost / estimate (spend before you build), resume (continue a paused or interrupted run)"
36
+ echo "Exit codes: docs/exit-codes.md"
37
+ echo ""
38
+ echo "Examples:"
39
+ echo " loki start ./prd.md Build from a spec file"
40
+ echo " loki start owner/repo#123 Build from a GitHub issue"
41
+ echo " loki start ./prd.md --budget 20 Cap spend at 20 USD"
42
+ echo " loki plan ./prd.md Dry run: complexity, cost, plan"
43
+ echo " loki verify --json Machine-readable verification for CI"
44
+ echo " loki resume Continue a paused or interrupted run"
45
+ echo " loki status --json What is running, as JSON"
46
+ echo " loki cost ./prd.md Estimate spend before you build"
47
+ echo " loki doctor --fix Check setup and repair what it can"
48
+ echo ""
49
+ echo "More:"
50
+ echo " loki help all Every command"
51
+ echo " loki help <topic> Topics: ${LOKI_HELP_TOPICS// /, }"
52
+ echo " loki <command> --help Help for one command"
53
+ echo ""
54
+ echo "New here? Run: loki doctor, then loki quickstart"
55
+ }
56
+
57
+ help_topic_text() {
58
+ case "$1" in
59
+ start)
60
+ cat <<'T'
61
+ Starting work
62
+
63
+ loki start ./prd.md Build from a spec file
64
+ loki start owner/repo#123 Build from a GitHub issue
65
+ loki "<task>" Same as quick
66
+ loki quick "add dark mode" One small task, no PR
67
+ loki edit "rename foo to bar" One targeted change
68
+ loki chat Interactive alternative to start (/run inside chat runs loki start)
69
+ loki chat --attach Chat about the current or latest start run (/status /steer /diff /stop)
70
+ loki plan ./prd.md Dry run: complexity, cost, plan
71
+
72
+ Common flags: --provider NAME, --model ID (tier or route, e.g. ollama/qwen3-coder), --budget USD, --simple, --complex,
73
+ --no-dashboard, --spec FILE, --config PATH. Full list: loki help all.
74
+ T
75
+ ;;
76
+ models)
77
+ cat <<'T'
78
+ Models
79
+
80
+ Loki routes work by tier: planning (opus), development (sonnet), fast (haiku).
81
+ loki models Show the model for each tier
82
+ loki models --local Show local models (Ollama, LM Studio, llama.cpp)
83
+ loki config set maxTier sonnet Cap model cost
84
+ loki config set model.development <id> Override one tier
85
+ LOKI_MODEL_OVERRIDE=<id> Force one model id (Anthropic-protocol gateways)
86
+ T
87
+ ;;
88
+ providers)
89
+ cat <<'T'
90
+ Providers
91
+
92
+ Claude Code is the default and the only Tier 1 provider.
93
+ loki provider list Providers and install status
94
+ loki provider set codex Switch provider (claude, codex, cline, aider, opencode)
95
+ loki start ./prd.md --provider cline
96
+
97
+ Tier 2: cline. Tier 3 (sequential, degraded): codex, aider. opencode is model-agnostic.
98
+ T
99
+ ;;
100
+ agents)
101
+ cat <<'T'
102
+ Agents
103
+
104
+ loki agents list|new|show Your own agents (.loki/agents/<name>.md); council reviewers use them
105
+ loki route "<task>" Show which engine, provider and model a task would use
106
+ loki agent --help Lower-level agent commands
107
+
108
+ Roles use Opus for planning, Sonnet for development, Haiku for fast work.
109
+ T
110
+ ;;
111
+ verify)
112
+ cat <<'T'
113
+ Verification
114
+
115
+ loki verify Deterministic PR verification
116
+ loki verify --fast Exogenous checks only, milliseconds, no model call
117
+ loki verify --json One JSON object: command, exit_code, ok, output
118
+ loki verify --pubkey FILE Check a receipt against a public key
119
+ loki receipt Inspect the proof-of-run receipt
120
+ loki undo <run> [--plan] Plan or apply an undo of a run
121
+
122
+ Exit codes: see loki help exit-codes.
123
+ T
124
+ ;;
125
+ exit-codes)
126
+ cat <<'T'
127
+ Exit codes
128
+
129
+ 0 success
130
+ 1 failure
131
+ 2 usage error (unknown command, bad flag)
132
+ 3 missing component
133
+
134
+ Severity rises with the code: a bigger number is never better news.
135
+ Per-command contracts (verify, proof, start under LOKI_DURABLE_STATE=1):
136
+ docs/exit-codes.md
137
+ T
138
+ ;;
139
+ config)
140
+ cat <<'T'
141
+ Configuration
142
+
143
+ loki config Show current configuration
144
+ loki config list --json Machine-readable configuration
145
+ loki config set KEY VALUE Set a value (maxTier, model.planning, ...)
146
+ loki config get KEY Read a value
147
+ loki config validate [FILE] Validate loki.yaml
148
+ loki setup First-run setup
149
+ loki start --config FILE Load settings from a file
150
+
151
+ Precedence: CLI flag > env var > --config file > defaults.
152
+ Environment variables: docs/environment-variables.md
153
+ T
154
+ ;;
155
+ *)
156
+ return 1
157
+ ;;
158
+ esac
159
+ }
160
+
161
+ # Print the topic, or return 1 when $1 is not a topic.
162
+ help_topic() {
163
+ help_topic_text "$1"
164
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # Shared --json helpers. Source this file; it defines functions only.
3
+ #
4
+ # loki_json_requested ARGS... exit 0 when --json is among ARGS
5
+ # loki_json_strip ARGS... print ARGS (one per line) without --json
6
+ # loki_json_wrap NAME CMD ARGS.. run CMD, print one JSON object:
7
+ # {"command":NAME,"exit_code":N,"ok":bool,"output":"..."}
8
+ # and return CMD's exit code. Use ONLY for
9
+ # commands with no native --json output.
10
+
11
+ loki_json_requested() {
12
+ local a
13
+ for a in "$@"; do
14
+ [ "$a" = "--json" ] && return 0
15
+ done
16
+ return 1
17
+ }
18
+
19
+ loki_json_strip() {
20
+ local a
21
+ for a in "$@"; do
22
+ [ "$a" = "--json" ] || printf '%s\n' "$a"
23
+ done
24
+ }
25
+
26
+ loki_json_wrap() {
27
+ local name="$1" out rc
28
+ shift
29
+ out="$("$@" 2>&1)"
30
+ rc=$?
31
+ LOKI_JW_NAME="$name" LOKI_JW_RC="$rc" LOKI_JW_OUT="$out" python3 -c '
32
+ import json, os, re
33
+ rc = int(os.environ["LOKI_JW_RC"])
34
+ out = re.sub(r"\x1b\[[0-9;]*m", "", os.environ["LOKI_JW_OUT"])
35
+ print(json.dumps({"command": os.environ["LOKI_JW_NAME"], "exit_code": rc, "ok": rc == 0, "output": out}))
36
+ '
37
+ return "$rc"
38
+ }
@@ -40,6 +40,21 @@ from efficiency_cost import collect_efficiency as _collect_efficiency # noqa: E
40
40
  import effort_estimator # noqa: E402
41
41
  from tree_digest import MANIFEST_VERSION, compute_tree_digest # noqa: E402
42
42
  from workspace_diff import collect_workspace_diff # noqa: E402
43
+ import importlib.util as _ilu # noqa: E402
44
+
45
+
46
+ def _load_proof_verify_mod():
47
+ # proof-verify.py is hyphenated (not importable); it owns the verification record.
48
+ spec = _ilu.spec_from_file_location(
49
+ "loki_proof_verify", os.path.join(os.path.dirname(os.path.abspath(__file__)), "proof-verify.py"))
50
+ mod = _ilu.module_from_spec(spec)
51
+ spec.loader.exec_module(mod)
52
+ return mod
53
+
54
+
55
+ _PV = _load_proof_verify_mod()
56
+ _vr_capture_environment = _PV.capture_environment
57
+ _vr_git_dirty = _PV.git_dirty
43
58
 
44
59
 
45
60
  # ---------------------------------------------------------------------------
@@ -1121,8 +1136,6 @@ def _empty_tree_sha(repo_dir):
1121
1136
  # collect_workspace_diff), so uncommitted work is covered.
1122
1137
  _TI_CFG_ALWAYS = re.compile(r"(^|/)(conftest\.py|\.?pytest\.(?:ini|toml)|tox\.ini|jest\.config\.[\w.]+|vitest\.config\.[\w.]+|\.mocharc[\w.]*)$")
1123
1138
  _TI_CFG_SHARED = re.compile(r"(^|/)(setup\.cfg|pyproject\.toml|package\.json)$")
1124
- _TI_TEST_FILE = re.compile(
1125
- r"(^|/)(tests?|__tests__|spec|specs)/|\.(test|spec)\.[\w]+$|(^|/)test_[^/]*\.py$|_test\.(py|go|rs)$|(^|/)conftest\.py$")
1126
1139
  _TI_SKIP_JS = re.compile(
1127
1140
  # options object only as the 2nd argument: title (string or identifier), then `, {`
1128
1141
  r"\b(test|it|describe|suite|context|specify)\s*\(\s*(?:'[^'\n]*'|\"[^\"\n]*\"|`[^`\n]*`|[\w.$]+)\s*,\s*\{[^}]*\b(skip|todo)\s*:\s*(?!(?:false|0|null|undefined)\b)\S"
@@ -1270,7 +1283,7 @@ def _collect_tests_integrity(target_dir, base):
1270
1283
  if u and not u.startswith(".loki/"):
1271
1284
  entries.append(("A", u, u))
1272
1285
  for st, old, new in entries:
1273
- if st in ("D", "R") and _TI_TEST_FILE.search(old):
1286
+ if st in ("D", "R") and _PV._TEST_FILE.search(old):
1274
1287
  weakened.append("test file %s: %s" % ("deleted" if st == "D" else "renamed", old))
1275
1288
  if not greenfield:
1276
1289
  for st, old, new in entries:
@@ -1310,7 +1323,7 @@ def _collect_tests_integrity(target_dir, base):
1310
1323
  edited.append(cur)
1311
1324
  cur, rx, removed_assert = None, None, False
1312
1325
  elif line.startswith("+++ b/"):
1313
- cur = line[6:] if _TI_TEST_FILE.search(line[6:]) else None
1326
+ cur = line[6:] if _PV._TEST_FILE.search(line[6:]) else None
1314
1327
  rx = _ti_skip_re(cur) if cur else None
1315
1328
  elif cur and line.startswith("+") and rx and rx.search(line):
1316
1329
  weakened.append("skip added in %s" % cur)
@@ -1318,7 +1331,7 @@ def _collect_tests_integrity(target_dir, base):
1318
1331
  and _TI_ASSERT.search(line):
1319
1332
  removed_assert = True
1320
1333
  for u in untracked.split("\0"):
1321
- if not u or u.startswith(".loki/") or not _TI_TEST_FILE.search(u):
1334
+ if not u or u.startswith(".loki/") or not _PV._TEST_FILE.search(u):
1322
1335
  continue
1323
1336
  rx = _ti_skip_re(u)
1324
1337
  if not rx:
@@ -1625,6 +1638,9 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
1625
1638
  # recompute it after runner exit to reject proofs followed by code edits.
1626
1639
  "tree_sha256": compute_tree_digest(target_dir),
1627
1640
  "tree_manifest_version": MANIFEST_VERSION,
1641
+ # Execution validity (docs/PROOF-PASSPORT.md): whether the tree
1642
+ # was dirty when the checks ran. None when git could not answer.
1643
+ "dirty": _vr_git_dirty(target_dir),
1628
1644
  }
1629
1645
  facts = {
1630
1646
  "git": git_facts,
@@ -1663,6 +1679,8 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
1663
1679
  # Descriptive; NOT read by _compute_headline (gating is founder-gated).
1664
1680
  "healthcheck": healthcheck,
1665
1681
  "cost": cost,
1682
+ # Environment the checks ran in (measured at generation time).
1683
+ "environment": _vr_capture_environment(),
1666
1684
  "meta": {
1667
1685
  "run_id": run_id,
1668
1686
  "loki_version": loki_version,