loki-mode 10.5.16 → 10.5.19

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 (133) hide show
  1. package/.claude-plugin/marketplace.json +12 -0
  2. package/README.md +878 -29
  3. package/SKILL.md +3 -3
  4. package/VERSION +1 -1
  5. package/autonomy/lib/backlog.py +280 -0
  6. package/autonomy/lib/loki_yaml.py +225 -0
  7. package/autonomy/lib/proof-generator.py +251 -0
  8. package/autonomy/lib/proof-verify.py +1 -0
  9. package/autonomy/loki +117 -12
  10. package/autonomy/run.sh +27 -2
  11. package/autonomy/verify.sh +11 -0
  12. package/completions/_loki +1 -0
  13. package/completions/loki.bash +2 -2
  14. package/dashboard/__init__.py +1 -1
  15. package/dashboard/api_start.py +463 -0
  16. package/dashboard/server.py +31 -0
  17. package/dashboard/static/start.html +100 -0
  18. package/docs/COMPARISON.md +6 -19
  19. package/docs/COMPETITIVE-ANALYSIS.md +6 -41
  20. package/docs/CRASH-REPORTING-PLAN.md +1 -1
  21. package/docs/ENTERPRISE-IDENTITY-ROADMAP.md +1 -1
  22. package/docs/INSTALLATION.md +1 -1
  23. package/docs/PRIVACY.md +1 -1
  24. package/docs/R8-SHAREABLE-TEAM-ASSETS-PLAN.md +1 -1
  25. package/docs/WANG-PRINCIPLES-PLAN.md +1 -1
  26. package/docs/architecture/STATE-MACHINES.md +18 -27
  27. package/docs/architecture/bmad-integration-validation.md +2 -2
  28. package/docs/audit-logging.md +12 -86
  29. package/docs/authentication.md +4 -4
  30. package/docs/authorization.md +15 -47
  31. package/docs/auto-claude-comparison.md +1 -33
  32. package/docs/certification/01-core-concepts/lab.md +3 -3
  33. package/docs/certification/01-core-concepts/lesson.md +11 -22
  34. package/docs/certification/01-core-concepts/quiz.md +7 -7
  35. package/docs/certification/02-enterprise-features/lab.md +0 -2
  36. package/docs/certification/02-enterprise-features/lesson.md +0 -10
  37. package/docs/certification/04-production-deployment/lab.md +1 -1
  38. package/docs/certification/05-troubleshooting/lab.md +2 -2
  39. package/docs/certification/README.md +1 -1
  40. package/docs/certification/answer-key.md +37 -20
  41. package/docs/certification/certification-exam.md +7 -7
  42. package/docs/competitive/emergence-others-analysis.md +3 -3
  43. package/docs/competitive/replit-lovable-analysis.md +2 -2
  44. package/docs/cursor-comparison.md +2 -2
  45. package/docs/dashboard-guide.md +14 -2
  46. package/docs/dev/architecture-reference.md +2 -2
  47. package/docs/dev/project-structure.md +1 -1
  48. package/docs/enterprise/architecture.md +11 -8
  49. package/docs/enterprise/integration-cookbook.md +23 -14
  50. package/docs/enterprise/migration.md +5 -9
  51. package/docs/enterprise/performance.md +3 -6
  52. package/docs/enterprise/sdk-guide.md +1 -1
  53. package/docs/enterprise/security.md +8 -17
  54. package/docs/exit-codes.md +29 -0
  55. package/docs/git-workflow.md +4 -62
  56. package/docs/network-security.md +3 -13
  57. package/docs/plans/ULTRAPLAN-FORGE-BAAS.md +1 -1
  58. package/docs/prd-purple-lab-platform-v2.md +1 -1
  59. package/docs/prd-purple-lab-platform.md +1 -3
  60. package/docs/research-2026-07/GAP-ANALYSIS-BACKLOG.md +1 -1
  61. package/docs/show-hn-post.md +3 -4
  62. package/docs/siem-integration.md +8 -10
  63. package/docs/test-scenarios/edge-cases.md +2 -2
  64. package/docs/test-scenarios/enterprise-scenarios.md +4 -4
  65. package/docs/v10/BOARD.md +21 -11
  66. package/docs/v10/DECISIONS.md +36 -0
  67. package/docs/v10/FOUNDER-QUEUE.md +1 -0
  68. package/docs/v10/GUIDE.md +5 -3
  69. package/docs/v10/METRICS.md +36 -0
  70. package/docs/v10/PROGRESS.md +76 -0
  71. package/docs/walkthrough/README.md +1 -7
  72. package/loki-ts/dist/loki.js +415 -407
  73. package/mcp/__init__.py +1 -1
  74. package/package.json +1 -1
  75. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  76. package/web-app/dist/assets/{AdminPage-B1zdOJaF.js → AdminPage-BH_7fpoz.js} +1 -1
  77. package/web-app/dist/assets/{Avatar-DKjAJ_w7.js → Avatar-BzyhrX0s.js} +1 -1
  78. package/web-app/dist/assets/{Badge-DP-UrPLv.js → Badge-CL05xwgC.js} +1 -1
  79. package/web-app/dist/assets/{Button-c8qBEA2K.js → Button-BVHrGLGH.js} +1 -1
  80. package/web-app/dist/assets/{CockpitPage-CFljzGbi.js → CockpitPage-B9BAEqD-.js} +1 -1
  81. package/web-app/dist/assets/{ComparePage-BNKqdNRu.js → ComparePage-D4grckhY.js} +1 -1
  82. package/web-app/dist/assets/{ErrorBoundary-DNkpXGt5.js → ErrorBoundary-Bg52u7eg.js} +1 -1
  83. package/web-app/dist/assets/{EvidenceReceiptPanel-CQd49V4Z.js → EvidenceReceiptPanel-B15kRZbJ.js} +1 -1
  84. package/web-app/dist/assets/{GitHubIssuesPanel-B9wji_yi.js → GitHubIssuesPanel-DP_BhJSC.js} +1 -1
  85. package/web-app/dist/assets/{GitHubPRsPanel-BkfCV4ag.js → GitHubPRsPanel-DJBu83u9.js} +1 -1
  86. package/web-app/dist/assets/{HomePage-BVV3Cowy.js → HomePage-6mw4uvLs.js} +2 -2
  87. package/web-app/dist/assets/{LoginPage-CWiOenLJ.js → LoginPage-BWWzFHif.js} +1 -1
  88. package/web-app/dist/assets/{MagicPage-DckrjTVd.js → MagicPage-CSyo7k5t.js} +1 -1
  89. package/web-app/dist/assets/{MetricsPage-DNBnH68C.js → MetricsPage-Buq03cch.js} +1 -1
  90. package/web-app/dist/assets/{NotFoundPage-Ca8za8mL.js → NotFoundPage-BG8vuV4g.js} +1 -1
  91. package/web-app/dist/assets/{ProjectPage-C92sm9xi.js → ProjectPage-D7eJn--6.js} +3 -3
  92. package/web-app/dist/assets/{ProjectsPage-BxsCyJ95.js → ProjectsPage-DXPycvjA.js} +1 -1
  93. package/web-app/dist/assets/{SettingsPage-BKg-6aHI.js → SettingsPage-Blvav87U.js} +1 -1
  94. package/web-app/dist/assets/{ShowcasePage-Bw_fI5rG.js → ShowcasePage-ZCAKRsNN.js} +1 -1
  95. package/web-app/dist/assets/{SystemSettingsPage-Bc-2OWUp.js → SystemSettingsPage-ClzjKfI4.js} +1 -1
  96. package/web-app/dist/assets/{TeamsPage-GsYRD1b9.js → TeamsPage-CKp1-WGX.js} +1 -1
  97. package/web-app/dist/assets/{TemplatesPage-BN7ZlULA.js → TemplatesPage-C0yAp2Yl.js} +1 -1
  98. package/web-app/dist/assets/{TerminalOutput-d7__ysTd.js → TerminalOutput-C3KbV8E7.js} +1 -1
  99. package/web-app/dist/assets/{activity-ycOMpCGz.js → activity-CG5PtpVI.js} +1 -1
  100. package/web-app/dist/assets/{bell-B93o4lsL.js → bell-CyLp--NR.js} +1 -1
  101. package/web-app/dist/assets/{bot-CB0kmriG.js → bot-Dp0zY9t7.js} +1 -1
  102. package/web-app/dist/assets/{check-BAYNAOEO.js → check-ipranz8L.js} +1 -1
  103. package/web-app/dist/assets/{chevron-left-Bgu8UgyR.js → chevron-left-DhYs9D3t.js} +1 -1
  104. package/web-app/dist/assets/{circle-alert-n_FciWUP.js → circle-alert-BLAxBNgz.js} +1 -1
  105. package/web-app/dist/assets/{clock-CNceeS66.js → clock-DzocnapF.js} +1 -1
  106. package/web-app/dist/assets/{cloud-CJBEdb7m.js → cloud-B3YsIUwq.js} +1 -1
  107. package/web-app/dist/assets/{code-xml-BHnKVqrH.js → code-xml-BPcy_Eqm.js} +1 -1
  108. package/web-app/dist/assets/{database-DSVoL7Pl.js → database-DOuuclZf.js} +1 -1
  109. package/web-app/dist/assets/{dollar-sign-48dvOe4H.js → dollar-sign-o77cj-xB.js} +1 -1
  110. package/web-app/dist/assets/{file-code-corner-8yzS5DeZ.js → file-code-corner-BZ2VMnYA.js} +1 -1
  111. package/web-app/dist/assets/{file-plus-BcuVrrey.js → file-plus-DeHfGncj.js} +1 -1
  112. package/web-app/dist/assets/{globe-Bb-O58oN.js → globe-DmIW-phf.js} +1 -1
  113. package/web-app/dist/assets/{hammer-DeMMY6xW.js → hammer-CoW8R8V5.js} +1 -1
  114. package/web-app/dist/assets/{index-9hue9wt2.js → index-5rCTAnsF.js} +3 -3
  115. package/web-app/dist/assets/{layers-DmCoMrQC.js → layers-DGEk-2RQ.js} +1 -1
  116. package/web-app/dist/assets/{loader-circle-CJvjM7JW.js → loader-circle-DXn-F-H5.js} +1 -1
  117. package/web-app/dist/assets/{lock-Dpje5cWO.js → lock-D5nAjeUu.js} +1 -1
  118. package/web-app/dist/assets/{package-mA_hCaf8.js → package-D8Drpzi8.js} +1 -1
  119. package/web-app/dist/assets/{plus-Db0JnAoC.js → plus-C-0T9dAu.js} +1 -1
  120. package/web-app/dist/assets/{refresh-cw-DbCL1y7h.js → refresh-cw-AyYQQPZG.js} +1 -1
  121. package/web-app/dist/assets/{rotate-ccw-Bt6pp50d.js → rotate-ccw-DYbslLfT.js} +1 -1
  122. package/web-app/dist/assets/{scroll-text-7j0hXxm7.js → scroll-text-CQFQhF_6.js} +1 -1
  123. package/web-app/dist/assets/{server-BMJYtpVZ.js → server-DxNytQEJ.js} +1 -1
  124. package/web-app/dist/assets/{shield-alert-Bu8Pbdy-.js → shield-alert-Xj5wGUSK.js} +1 -1
  125. package/web-app/dist/assets/{thumbs-up-8erzRJ8k.js → thumbs-up-DW0j9Yxz.js} +1 -1
  126. package/web-app/dist/assets/{trash-2-DBC_D_Hv.js → trash-2-VOdpiJ1e.js} +1 -1
  127. package/web-app/dist/assets/{trending-down-DMNq5hSm.js → trending-down-eTizH4_b.js} +1 -1
  128. package/web-app/dist/assets/{trending-up-9b1IqBqR.js → trending-up-agYVl7bU.js} +1 -1
  129. package/web-app/dist/assets/{upload-SoA0iiLG.js → upload-DQ3NfXA_.js} +1 -1
  130. package/web-app/dist/assets/{usePolling-DLP2FX9M.js → usePolling-BKETnkDT.js} +1 -1
  131. package/web-app/dist/assets/{user-D7Pb9wW3.js → user-Cv6BNmOT.js} +1 -1
  132. package/web-app/dist/index.html +1 -1
  133. package/docs/README-FULL.md +0 -977
@@ -1115,6 +1115,230 @@ def _empty_tree_sha(repo_dir):
1115
1115
  return out.stdout.strip() if out.returncode == 0 else ""
1116
1116
 
1117
1117
 
1118
+ # A-118 / D47: tests_integrity. Did the run WEAKEN the tests that vouch for it?
1119
+ # CFG_ALWAYS is copied from loki-ts/src/engine10/stages/verify.ts (A-115); keep in sync.
1120
+ # The comparison is base vs the final WORKING TREE plus untracked files (like
1121
+ # collect_workspace_diff), so uncommitted work is covered.
1122
+ _TI_CFG_ALWAYS = re.compile(r"(^|/)(conftest\.py|\.?pytest\.(?:ini|toml)|tox\.ini|jest\.config\.[\w.]+|vitest\.config\.[\w.]+|\.mocharc[\w.]*)$")
1123
+ _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
+ _TI_SKIP_JS = re.compile(
1127
+ # options object only as the 2nd argument: title (string or identifier), then `, {`
1128
+ 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"
1129
+ r"|\b(it|test|describe|suite|context|specify)\s*\.\s*(skip|only|todo)\b"
1130
+ r"|\bx(it|test|describe)\s*\(|\bf(it|describe)\s*\("
1131
+ r"|\b(t|ctx)\s*\.\s*(skip|todo)\s*\(")
1132
+ _TI_SKIP_PY = re.compile(
1133
+ r"\b(?:pytest\.)?mark\.(skip|skipif|xfail)\b|\bpytest\.(skip|xfail|importorskip)\s*\("
1134
+ r"|@(?:unittest\.)?(skip|skipIf|skipUnless|expectedFailure)\b|\bSkipTest\b|\bskipTest\s*\(")
1135
+ _TI_SKIP_GO = re.compile(r"\b[tb]\.Skip(f|Now)?\s*\(")
1136
+ _TI_SKIP_RS = re.compile(r"#\[ignore")
1137
+ _TI_SKIP_BY_EXT = (
1138
+ (("js", "jsx", "ts", "tsx", "mjs", "cjs"), _TI_SKIP_JS),
1139
+ (("py",), _TI_SKIP_PY), (("go",), _TI_SKIP_GO), (("rs",), _TI_SKIP_RS))
1140
+ _TI_ASSERT = re.compile(r"\bassert|\bexpect\(|\.should\b|\.to[A-Z]\w*\(|\bt\.(Error|Fatal)")
1141
+ _TI_PKG_KEYS = ("jest", "mocha", "vitest", "ava")
1142
+
1143
+
1144
+ def _ti_skip_re(path):
1145
+ ext = path.rsplit(".", 1)[-1].lower() if "." in path else ""
1146
+ for exts, rx in _TI_SKIP_BY_EXT:
1147
+ if ext in exts:
1148
+ return rx
1149
+ return None
1150
+
1151
+
1152
+ def _ti_run(repo, *args):
1153
+ """Run a read-only git command; returns text decoded from bytes (so a lone
1154
+ CR is never a line break; callers split on \\n only), or None on failure."""
1155
+ try:
1156
+ r = subprocess.run(["git", "-c", "core.quotePath=false", *args], cwd=repo,
1157
+ capture_output=True, timeout=30)
1158
+ except Exception:
1159
+ return None
1160
+ if r.returncode != 0:
1161
+ return None
1162
+ return r.stdout.decode("utf-8", "replace")
1163
+
1164
+
1165
+ def _ti_pkg_view(text, _path=""):
1166
+ """Test-relevant slice of a package.json, or None when unparseable."""
1167
+ try:
1168
+ d = json.loads(text) if text.strip() else {}
1169
+ except ValueError:
1170
+ return None
1171
+ if not isinstance(d, dict):
1172
+ return None
1173
+ scripts = d.get("scripts") if isinstance(d.get("scripts"), dict) else {}
1174
+ view = {k: v for k, v in scripts.items() if re.match(r"^(pre|post)?test(:.*)?$", k)}
1175
+ return {"scripts": view, **{k: d[k] for k in _TI_PKG_KEYS if k in d}}
1176
+
1177
+
1178
+ _TI_PYTEST_KEYS = ("addopts", "testpaths", "python_files", "python_functions",
1179
+ "python_classes", "norecursedirs")
1180
+
1181
+
1182
+ def _ti_pytest_view(text, path):
1183
+ """The test-selection keys of a pytest config, or None when unparseable.
1184
+ Parsed (tomllib / configparser), so a multi-line addopts edit is seen.
1185
+ ponytail: without tomllib (python < 3.11) try tomli, else compare only the
1186
+ whitespace-normalized [tool.pytest.ini_options] section text."""
1187
+ if not text.strip():
1188
+ return {}
1189
+ if path.endswith(".toml"):
1190
+ try:
1191
+ try:
1192
+ import tomllib
1193
+ except ImportError:
1194
+ import tomli as tomllib
1195
+ except ImportError:
1196
+ m = re.search(r"^\[tool\.pytest\.ini_options\][ \t]*(?:#.*)?$(.*?)(?=^\[[^\[\]\n]+\][ \t]*(?:#.*)?$|\Z)",
1197
+ text, re.M | re.S)
1198
+ return {"ini_options": " ".join(m.group(1).split())} if m else {}
1199
+ try:
1200
+ d = tomllib.loads(text)
1201
+ except Exception:
1202
+ return None
1203
+ t = d.get("tool", {}).get("pytest", {})
1204
+ t = t.get("ini_options", t) if isinstance(t, dict) else {}
1205
+ return {k: t[k] for k in _TI_PYTEST_KEYS if k in t}
1206
+ import configparser
1207
+ cp = configparser.ConfigParser(interpolation=None, strict=False)
1208
+ try:
1209
+ cp.read_string(text)
1210
+ except Exception:
1211
+ return None
1212
+ out = {}
1213
+ for sec in ("pytest", "tool:pytest"):
1214
+ if cp.has_section(sec):
1215
+ for k in _TI_PYTEST_KEYS:
1216
+ if cp.has_option(sec, k):
1217
+ out[sec + "." + k] = cp.get(sec, k)
1218
+ return out
1219
+
1220
+
1221
+ _TI_SELECT_PY = re.compile(r"\bcollect_ignore(?:_glob)?\b|\bpytest_collection_modifyitems\b")
1222
+ _TI_PYTEST_NAMED = re.compile(r"(^|/)(conftest\.py|\.?pytest\.ini|tox\.ini)$")
1223
+
1224
+
1225
+ def _ti_base_has_pytest_config(target_dir, base):
1226
+ names = _ti_run(target_dir, "ls-tree", "-r", "--name-only", "-z", base) or ""
1227
+ for n in names.split("\0"):
1228
+ if _TI_PYTEST_NAMED.search(n):
1229
+ return True
1230
+ if n.endswith(("pyproject.toml", "setup.cfg")) and \
1231
+ _ti_pytest_view(_ti_run(target_dir, "show", "%s:./%s" % (base, n)) or "", n):
1232
+ return True
1233
+ return False
1234
+
1235
+
1236
+ def _ti_added_config_selects(path, text):
1237
+ """True when a newly added pytest config changes test selection (unparseable counts)."""
1238
+ if path.endswith(".py"):
1239
+ return bool(_TI_SELECT_PY.search(text))
1240
+ if path.endswith((".ini", ".toml", ".cfg")):
1241
+ v = _ti_pytest_view(text, path)
1242
+ return v is None or bool(v)
1243
+ return False
1244
+
1245
+
1246
+ def _collect_tests_integrity(target_dir, base):
1247
+ """Return {"weakened": [...], "assertions_edited": [...], "disclosed": [...]} or None.
1248
+
1249
+ Derived from base vs the final working tree (committed, staged, unstaged and
1250
+ untracked). None when there is no usable diff (no repo, no base)."""
1251
+ if not base:
1252
+ return None
1253
+ names = _ti_run(target_dir, "diff", "--relative", "--name-status", "-M", "-z", base, "--")
1254
+ if names is None:
1255
+ return None
1256
+ weakened, edited, disclosed = [], [], []
1257
+ greenfield = base == _empty_tree_sha(target_dir)
1258
+ toks = names.split("\0")
1259
+ entries, i = [], 0 # (status, old_path, new_path)
1260
+ while i < len(toks) and toks[i]:
1261
+ st = toks[i][:1]
1262
+ if st in "RC" and i + 2 < len(toks):
1263
+ entries.append((st, toks[i + 1], toks[i + 2]))
1264
+ i += 3
1265
+ else:
1266
+ entries.append((st, toks[i + 1] if i + 1 < len(toks) else "", toks[i + 1] if i + 1 < len(toks) else ""))
1267
+ i += 2
1268
+ untracked = _ti_run(target_dir, "ls-files", "--others", "--exclude-standard", "-z") or ""
1269
+ for u in untracked.split("\0"):
1270
+ if u and not u.startswith(".loki/"):
1271
+ entries.append(("A", u, u))
1272
+ for st, old, new in entries:
1273
+ if st in ("D", "R") and _TI_TEST_FILE.search(old):
1274
+ weakened.append("test file %s: %s" % ("deleted" if st == "D" else "renamed", old))
1275
+ if not greenfield:
1276
+ for st, old, new in entries:
1277
+ path = new if st != "D" else old
1278
+ is_always = bool(_TI_CFG_ALWAYS.search(path))
1279
+ if not (is_always or _TI_CFG_SHARED.search(path)):
1280
+ continue
1281
+ if st == "A": # no base version: disclose, unless it re-selects tests over a base pytest config
1282
+ try:
1283
+ with open(os.path.join(target_dir, new), encoding="utf-8", errors="replace") as h:
1284
+ text = h.read()
1285
+ except OSError:
1286
+ text = ""
1287
+ if _ti_added_config_selects(path, text) and _ti_base_has_pytest_config(target_dir, base):
1288
+ weakened.append("test runner config changed: " + path)
1289
+ else:
1290
+ disclosed.append("new test runner config: " + path)
1291
+ elif is_always:
1292
+ weakened.append("test runner config changed: " + path)
1293
+ else:
1294
+ before = _ti_run(target_dir, "show", "%s:./%s" % (base, old)) or ""
1295
+ try:
1296
+ with open(os.path.join(target_dir, new), encoding="utf-8", errors="replace") as h:
1297
+ after = h.read()
1298
+ except OSError:
1299
+ after = ""
1300
+ view = _ti_pkg_view if path.endswith("package.json") else _ti_pytest_view
1301
+ b, a = view(before, path), view(after, path)
1302
+ if b is None or a is None or b != a:
1303
+ weakened.append("test runner config changed: " + path)
1304
+ # Skip markers: added lines of tracked test files, and the content of untracked ones.
1305
+ patch = _ti_run(target_dir, "diff", "--relative", "-U0", "-M", base, "--") or ""
1306
+ cur, rx, removed_assert = None, None, False
1307
+ for line in patch.split("\n") + ["diff --git a/ b/"]:
1308
+ if line.startswith("diff --git "):
1309
+ if cur and removed_assert and cur not in edited:
1310
+ edited.append(cur)
1311
+ cur, rx, removed_assert = None, None, False
1312
+ elif line.startswith("+++ b/"):
1313
+ cur = line[6:] if _TI_TEST_FILE.search(line[6:]) else None
1314
+ rx = _ti_skip_re(cur) if cur else None
1315
+ elif cur and line.startswith("+") and rx and rx.search(line):
1316
+ weakened.append("skip added in %s" % cur)
1317
+ elif cur and line.startswith("-") and not line.startswith("---") \
1318
+ and _TI_ASSERT.search(line):
1319
+ removed_assert = True
1320
+ for u in untracked.split("\0"):
1321
+ if not u or u.startswith(".loki/") or not _TI_TEST_FILE.search(u):
1322
+ continue
1323
+ rx = _ti_skip_re(u)
1324
+ if not rx:
1325
+ continue
1326
+ try:
1327
+ with open(os.path.join(target_dir, u), "rb") as h:
1328
+ text = h.read().decode("utf-8", "replace")
1329
+ except OSError:
1330
+ continue
1331
+ if any(rx.search(ln) for ln in text.split("\n")):
1332
+ weakened.append("skip added in %s" % u)
1333
+ uniq = []
1334
+ for w in weakened:
1335
+ if w not in uniq:
1336
+ uniq.append(w)
1337
+ if not uniq and not edited and not disclosed:
1338
+ return None
1339
+ return {"weakened": uniq, "assertions_edited": edited, "disclosed": disclosed}
1340
+
1341
+
1118
1342
  def _collect_iterations(loki_dir):
1119
1343
  completed = _read_json(os.path.join(loki_dir, "queue", "completed.json"), default=[])
1120
1344
  failed = _read_json(os.path.join(loki_dir, "queue", "failed.json"), default=[])
@@ -1455,6 +1679,10 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
1455
1679
  # only, never read by _compute_headline / _compute_degraded.
1456
1680
  if journey:
1457
1681
  facts["journey"] = journey
1682
+ # A-118: additive, present only when the diff touched test integrity.
1683
+ _ti = _collect_tests_integrity(target_dir, diff_base_sha)
1684
+ if _ti:
1685
+ facts["tests_integrity"] = _ti
1458
1686
  ablation = _collect_ablation(loki_dir)
1459
1687
  if ablation is not None:
1460
1688
  facts["ablation"] = ablation
@@ -1517,6 +1745,24 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
1517
1745
  "post_headline": True,
1518
1746
  })
1519
1747
 
1748
+ # D47: edited assertion lines are disclosed but never move the headline or rc.
1749
+ _ae = (facts.get("tests_integrity") or {}).get("assertions_edited")
1750
+ if _ae:
1751
+ degraded.append({
1752
+ "item": "tests_integrity:assertions_edited",
1753
+ "status": "inconclusive",
1754
+ "reason": "assertion lines edited in: " + ", ".join(_ae),
1755
+ "post_headline": True,
1756
+ })
1757
+ _di = (facts.get("tests_integrity") or {}).get("disclosed")
1758
+ if _di:
1759
+ degraded.append({
1760
+ "item": "tests_integrity:config_added",
1761
+ "status": "inconclusive",
1762
+ "reason": "; ".join(_di),
1763
+ "post_headline": True,
1764
+ })
1765
+
1520
1766
  honesty = {
1521
1767
  "headline": headline,
1522
1768
  "degraded": degraded,
@@ -1653,6 +1899,10 @@ def _compute_degraded(facts):
1653
1899
  out.append({"item": "security", "status": "findings",
1654
1900
  "reason": "%s un-waived HIGH security finding(s)"
1655
1901
  % sec.get("high_active")})
1902
+ ti = facts.get("tests_integrity") or {}
1903
+ if ti.get("weakened"):
1904
+ out.append({"item": "tests_integrity", "status": "failed",
1905
+ "reason": "tests weakened: " + "; ".join(ti["weakened"])})
1656
1906
  git = facts.get("git") or {}
1657
1907
  if not (git.get("diff") or {}).get("count"):
1658
1908
  out.append({"item": "git.diff", "status": "not_run",
@@ -1748,6 +1998,7 @@ def _compute_headline(facts, degraded):
1748
1998
  if _is_exogenous(g))
1749
1999
  or sec_high
1750
2000
  or fn_failed
2001
+ or bool((facts.get("tests_integrity") or {}).get("weakened"))
1751
2002
  )
1752
2003
  if any_failed:
1753
2004
  return "NOT VERIFIED"
@@ -331,6 +331,7 @@ def _compute_headline(facts, degraded):
331
331
  if _is_exogenous(g))
332
332
  or sec_high
333
333
  or fn_failed
334
+ or bool((facts.get("tests_integrity") or {}).get("weakened"))
334
335
  )
335
336
  if any_failed:
336
337
  return "NOT VERIFIED"
package/autonomy/loki CHANGED
@@ -1627,6 +1627,7 @@ show_help() {
1627
1627
  echo "Build:"
1628
1628
  echo " start [SPEC] Start a build (PRD file, GitHub/GitLab/Jira/Azure issue, or no arg)"
1629
1629
  echo " plan <PRD-file> Dry-run analysis: complexity, cost, execution plan"
1630
+ echo " backlog <owner/repo> --all|--label X|--issues N,N Run every matching open issue, N in parallel"
1630
1631
  echo " quickstart [idea] Guided first build: setup, idea, template, plan, go"
1631
1632
  echo ""
1632
1633
  echo "Session:"
@@ -1765,7 +1766,7 @@ show_help() {
1765
1766
  # missing from this list, so a new command cannot ship invisible.
1766
1767
  echo ""
1767
1768
  echo "All commands:"
1768
- echo " agent analyze api assets audit bench checkpoint (cp) ci cleanup"
1769
+ echo " agent analyze api assets audit backlog bench checkpoint (cp) ci cleanup"
1769
1770
  echo " cluster cockpit code compliance completions compound config context (ctx)"
1770
1771
  echo " cost council crash dashboard demo deploy docker docs doctor dogfood"
1771
1772
  echo " enterprise estimate explain export failover github grill heal help"
@@ -1849,6 +1850,48 @@ show_help_aliases() {
1849
1850
  echo "See internal/CLI-CONSOLIDATION-DESIGN.md for the full migration plan."
1850
1851
  }
1851
1852
 
1853
+ # D51: bare `loki`. Reuse a running dashboard, else start one on a free
1854
+ # loopback port, then open /start (or just print the URL when headless).
1855
+ loki_ui_free_port() {
1856
+ python3 - "$1" <<'PY'
1857
+ import socket, sys
1858
+ for p in range(int(sys.argv[1]), int(sys.argv[1]) + 100):
1859
+ s = socket.socket()
1860
+ try:
1861
+ s.bind(("127.0.0.1", p))
1862
+ except OSError:
1863
+ continue
1864
+ finally:
1865
+ s.close()
1866
+ print(p)
1867
+ break
1868
+ PY
1869
+ }
1870
+
1871
+ cmd_ui() {
1872
+ local port="" pid=""
1873
+ if [ -f "$DASHBOARD_PID_FILE" ]; then
1874
+ pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)
1875
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
1876
+ port=$(cat "${DASHBOARD_PID_DIR}/port" 2>/dev/null)
1877
+ fi
1878
+ fi
1879
+ if [ -z "$port" ]; then
1880
+ port=$(loki_ui_free_port "${LOKI_DASHBOARD_PORT:-$DASHBOARD_DEFAULT_PORT}")
1881
+ [ -n "$port" ] || { echo "loki: no free local port found" >&2; return 1; }
1882
+ # The WS-origin allowlist must name the chosen port on both loopback names.
1883
+ LOKI_PROJECT_DIR="$PWD" \
1884
+ LOKI_DASHBOARD_CORS="http://localhost:$port,http://127.0.0.1:$port" \
1885
+ cmd_dashboard_start --host 127.0.0.1 --port "$port" >&2 || return 1
1886
+ fi
1887
+ local url="http://127.0.0.1:${port}/start"
1888
+ if [ "${LOKI_HEADLESS:-0}" = "1" ] || [ "${1:-}" = "--no-open" ]; then
1889
+ echo "$url"
1890
+ else
1891
+ loki_open_url "$url" || echo "$url"
1892
+ fi
1893
+ }
1894
+
1852
1895
  # Tight newcomer landing for the bare `loki` invocation (no args).
1853
1896
  # v7.28.x UX: the full 70-command table lives in `loki help`; a no-args run
1854
1897
  # now shows only a calm ~12-line orientation so the "what do I do first"
@@ -3396,7 +3439,7 @@ cmd_start() {
3396
3439
  ${LOKI_MIROFISH_ROUNDS:+--max-rounds "$LOKI_MIROFISH_ROUNDS"} \
3397
3440
  ${LOKI_MIROFISH_TIMEOUT:+--timeout "$LOKI_MIROFISH_TIMEOUT"}
3398
3441
  # PID is tracked in .loki/mirofish/pipeline-state.json by the adapter
3399
- echo -e "${CYAN}MiroFish pipeline started in background. Check status: loki mirofish status${NC}"
3442
+ echo -e "${CYAN}MiroFish pipeline started in background. Status: $LOKI_DIR/mirofish/pipeline-state.json${NC}"
3400
3443
  else
3401
3444
  echo -e "${YELLOW}Warning: PRD validation for MiroFish failed. Continuing without.${NC}"
3402
3445
  unset MIROFISH_URL
@@ -9306,6 +9349,32 @@ _deploy_receipt_verdict() {
9306
9349
  # evaluated, so no key set is written and the verdict is UNCHECKED, never
9307
9350
  # TAMPERED: the hash is fine, we just cannot vouch for who signed it. A
9308
9351
  # local key only proves "a holder of this key", not an independent build.
9352
+ # Token SHAPE before any key lookup (mirrors engine10 checkAttestation): a present
9353
+ # attestation that is not a string, not a 3-part JWS, or whose header/payload are not
9354
+ # JSON with alg EdDSA is junk, so TAMPERED, never UNCHECKED, with or without a key.
9355
+ if ! python3 -E - "$pj" >/dev/null 2>&1 <<'PYSHAPE'
9356
+ import sys
9357
+ sys.path[:] = [p for p in sys.path if p not in ("", ".")]
9358
+ import json, base64
9359
+ def dec(x):
9360
+ return json.loads(base64.urlsafe_b64decode(x + "=" * (-len(x) % 4)))
9361
+ v = json.load(open(sys.argv[1])).get("verification") or {}
9362
+ t = v.get("attestation")
9363
+ if t is None or t == "":
9364
+ sys.exit(0)
9365
+ if not isinstance(t, str):
9366
+ sys.exit(1)
9367
+ parts = t.split(".")
9368
+ if len(parts) != 3 or not all(parts):
9369
+ sys.exit(1)
9370
+ try:
9371
+ h = dec(parts[0]); dec(parts[1])
9372
+ except Exception:
9373
+ sys.exit(1)
9374
+ sys.exit(0 if isinstance(h, dict) and h.get("alg") == "EdDSA" else 1)
9375
+ PYSHAPE
9376
+ then printf '%s' "TAMPERED"; return 0; fi
9377
+
9309
9378
  local jwks att
9310
9379
  jwks="$(mktemp "${TMPDIR:-/tmp}/loki-jwks.XXXXXX")" || { printf '%s' "UNCHECKED"; return 0; }
9311
9380
  python3 -E - "${_LOKI_SCRIPT_DIR}" "$pj" >"$jwks" 2>/dev/null <<'PY' || true
@@ -12593,7 +12662,7 @@ cmd_config() {
12593
12662
  echo " get KEY Get a configuration value"
12594
12663
  echo " example Print an annotated config template (for --config)"
12595
12664
  echo " schema Print the key -> env-var mapping table"
12596
- echo " validate FILE Validate a config file (--config) without applying it"
12665
+ echo " validate [FILE] Validate loki.yaml (no FILE: repo root, then ~/.loki) or a --config file"
12597
12666
  echo ""
12598
12667
  echo "Settable keys (v6.0.0):"
12599
12668
  echo " maxTier Cost ceiling: opus, sonnet, haiku (default: opus)"
@@ -12797,14 +12866,43 @@ cmd_config_schema() {
12797
12866
  loki_config_generate_schema
12798
12867
  }
12799
12868
 
12869
+ # D51: headless backlog runner. One v10 issue-mode run per open issue, each in
12870
+ # its own worktree and branch, at most N in parallel (loki.yaml concurrency).
12871
+ cmd_backlog() {
12872
+ case "${1:-}" in
12873
+ ""|--help|-h|help)
12874
+ echo -e "${BOLD}loki backlog${NC} - run a v10 issue-mode run for every matching open issue"
12875
+ echo ""
12876
+ echo "Usage: loki backlog owner/repo (--all | --label X | --issues 1,2,3) [--concurrency N] [--dry-run]"
12877
+ echo ""
12878
+ echo " --all Every open issue"
12879
+ echo " --label X Open issues with label X"
12880
+ echo " --issues 1,2,3 Exactly these issue numbers"
12881
+ echo " --concurrency N Max runs in parallel (default: loki.yaml concurrency, else 2)"
12882
+ echo " --dry-run List what would run; launch nothing"
12883
+ echo ""
12884
+ echo "Run from a checkout of owner/repo. Each issue runs in its own worktree on loki/backlog-N."
12885
+ echo "Config: loki.yaml (repo root, then ~/.loki/loki.yaml); see docs/loki.yaml.example."
12886
+ echo "Exit: 0 every issue ended VERIFIED or with a PR; 3 only budget stops; 1 otherwise; 2 usage."
12887
+ return 0
12888
+ ;;
12889
+ esac
12890
+ command -v python3 >/dev/null 2>&1 || { echo -e "${RED}loki backlog: python3 is required${NC}" >&2; return 2; }
12891
+ python3 "$_LOKI_SCRIPT_DIR/lib/backlog.py" "$@"
12892
+ }
12893
+
12800
12894
  # #691: validate a config file (--config) WITHOUT applying it. Reports
12801
12895
  # unresolved ${VAR} refs, raw-secret literals (error), and per-value validation
12802
12896
  # failures. Non-zero exit on any failure.
12803
12897
  cmd_config_validate() {
12804
- local file="${1:-}"
12805
- if [ -z "$file" ]; then
12806
- echo -e "${RED}Usage: loki config validate <file>${NC}" >&2
12807
- return 2
12898
+ local file="${1:-}" _loki_yaml
12899
+ # D51: loki.yaml (UI + headless config) has its own schema; no argument
12900
+ # validates the discovered loki.yaml (repo root, then ~/.loki/loki.yaml).
12901
+ case "$(basename -- "$file")" in "" | loki.yaml | loki.yml | loki.yaml.example) _loki_yaml=1 ;; *) _loki_yaml=0 ;; esac
12902
+ if [ "$_loki_yaml" = 1 ]; then
12903
+ command -v python3 >/dev/null 2>&1 || { echo -e "${RED}config validate: python3 is required${NC}" >&2; return 2; }
12904
+ python3 "$_LOKI_SCRIPT_DIR/lib/loki_yaml.py" validate ${file:+"$file"}
12905
+ return $?
12808
12906
  fi
12809
12907
  if ! declare -f loki_config_validate_file >/dev/null 2>&1; then
12810
12908
  echo -e "${RED}config validate: config-map.sh helper not loaded${NC}" >&2
@@ -21530,11 +21628,15 @@ main() {
21530
21628
  exit 1
21531
21629
  fi
21532
21630
 
21533
- if [ $# -eq 0 ]; then
21534
- # v7.28.x UX: bare `loki` shows a tight newcomer landing, not the full
21535
- # command wall. `loki help`/`--help` still print show_help unchanged.
21536
- show_landing
21537
- exit 0
21631
+ if [ $# -eq 0 ] || { [ $# -eq 1 ] && [ "$1" = "--no-open" ]; }; then
21632
+ # D51: bare `loki` starts the local UI. LOKI_LANDING=1 keeps the v7.28
21633
+ # newcomer landing (tests and scripts that parse it set it).
21634
+ if [ "${LOKI_LANDING:-0}" = "1" ]; then
21635
+ show_landing
21636
+ exit 0
21637
+ fi
21638
+ cmd_ui "$@"
21639
+ exit $?
21538
21640
  fi
21539
21641
 
21540
21642
  local command="$1"
@@ -21767,6 +21869,9 @@ main() {
21767
21869
  assets)
21768
21870
  cmd_assets "$@"
21769
21871
  ;;
21872
+ backlog)
21873
+ cmd_backlog "$@"
21874
+ ;;
21770
21875
  config)
21771
21876
  cmd_config "$@"
21772
21877
  ;;
package/autonomy/run.sh CHANGED
@@ -11252,6 +11252,27 @@ _loki_proof_json_for_pr() {
11252
11252
  }
11253
11253
 
11254
11254
  # A-134: compact quiet-mode receipt for the outer shell (see top of file) to print.
11255
+ # A-118 / D47: a quick run that weakened its own tests exits 3. Prints the rc to use:
11256
+ # only ever RAISES a 0 (never lowers a non-zero rc); NOT VERIFIED from unproven gates alone keeps 0.
11257
+ _loki_quick_integrity_rc() {
11258
+ local rc="${1:-0}" ld="${TARGET_DIR:-.}/.loki" rid pj
11259
+ [ "$rc" = "0" ] || { echo "$rc"; return 0; }
11260
+ rid="$(cat "$ld/state/last-proof-id.txt" 2>/dev/null || true)"
11261
+ case "$rid" in '' | *[!A-Za-z0-9._-]*) echo 0; return 0 ;; esac
11262
+ pj="$ld/proofs/$rid/proof.json"
11263
+ if [ -f "$pj" ] && python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
11264
+ import json
11265
+ d = json.load(open(sys.argv[1]))
11266
+ g = (d.get('facts') or {}).get('git') or {}
11267
+ if sys.argv[2] and g.get('head_sha') != sys.argv[2]:
11268
+ sys.exit(1)
11269
+ sys.exit(0 if any(isinstance(x, dict) and x.get('item') == 'tests_integrity' and x.get('status') == 'failed' for x in ((d.get('honesty') or {}).get('degraded') or [])) else 1)" "$pj" "$(git -C "${TARGET_DIR:-.}" rev-parse HEAD 2>/dev/null || true)" 2>/dev/null; then
11270
+ echo 3
11271
+ else
11272
+ echo 0
11273
+ fi
11274
+ }
11275
+
11255
11276
  _loki_quick_receipt_write() {
11256
11277
  local ld="${TARGET_DIR:-.}/.loki" rid pj
11257
11278
  rid="$(cat "$ld/state/last-proof-id.txt" 2>/dev/null || true)"
@@ -11266,8 +11287,10 @@ v = d.get('verification') or {}
11266
11287
  if sys.argv[3] and g.get('head_sha') != sys.argv[3]:
11267
11288
  print('Evidence Receipt: unavailable (final proof generation failed; newest proof predates the session commit)')
11268
11289
  sys.exit(0)
11269
- deg = [str(x.get('item') or '').split(':')[-1] for x in ((d.get('honesty') or {}).get('degraded') or []) if isinstance(x, dict)]
11270
- notes = ([] if (v.get('gpg_signature') or v.get('attestation')) else ['unsigned']) + (['%d not proven: %s' % (len(deg), ', '.join(deg))] if deg else [])
11290
+ dg = [x for x in ((d.get('honesty') or {}).get('degraded') or []) if isinstance(x, dict)]
11291
+ tw = [str(x.get('reason') or 'tests weakened') for x in dg if x.get('item') == 'tests_integrity' and x.get('status') == 'failed']
11292
+ deg = [str(x.get('item') or '').split(':')[-1] for x in dg if x.get('item') != 'tests_integrity']
11293
+ notes = tw[:1] + ([] if (v.get('gpg_signature') or v.get('attestation')) else ['unsigned']) + (['%d not proven: %s' % (len(deg), ', '.join(deg))] if deg else [])
11271
11294
  print('Evidence Receipt: ' + str((d.get('honesty') or {}).get('headline') or 'unavailable') + (' (' + '; '.join(notes) + ')' if notes else ''))
11272
11295
  print('receipt_sha256: ' + str(v.get('hash') or ''))
11273
11296
  print('Head sha: ' + str(g.get('head_sha') or '') + ' Diff sha256: ' + str(g.get('diff_sha256') or ''))
@@ -29402,6 +29425,8 @@ except Exception:
29402
29425
  if [ "${LOKI_PROOF:-1}" != "0" ]; then
29403
29426
  generate_proof_of_run "$result" || true
29404
29427
  fi
29428
+ # A-118: a quick run that weakened its tests exits 3 (quiet and verbose alike).
29429
+ ! _loki_is_quick_prd "${PRD_PATH:-}" || result="$(_loki_quick_integrity_rc "$result")"
29405
29430
  [ -z "${LOKI_QUICK_INNER:-}" ] || _loki_quick_receipt_write
29406
29431
  # Trusted post-session step. NOT wrapped in _loki_with_github_tokens as a
29407
29432
  # whole (round 5): its push (_loki_trusted_push) and gh calls each re-grant
@@ -3074,6 +3074,15 @@ sys.exit(0 if ok else 3)" "$lib" "$pj" 2>/dev/null || rc=$?
3074
3074
  declare -f _deploy_receipt_verdict >/dev/null 2>&1 || return 0
3075
3075
  local att
3076
3076
  att="$(_deploy_receipt_verdict "$pj")"
3077
+ # D47: UNSIGNED is never a pass; --allow-unsigned / LOKI_VERIFY_ALLOW_UNSIGNED=1 accepts it, said aloud.
3078
+ if [ "$att" = "UNSIGNED" ]; then
3079
+ if [ "${VERIFY_ALLOW_UNSIGNED:-0}" = "1" ] || [ "${LOKI_VERIFY_ALLOW_UNSIGNED:-}" = "1" ]; then
3080
+ printf 'attestation: UNSIGNED (accepted by --allow-unsigned; integrity not attested)\n'
3081
+ return 0
3082
+ fi
3083
+ printf 'attestation: UNSIGNED, integrity not attested; refusing (pass --allow-unsigned to accept)\n'
3084
+ return 1
3085
+ fi
3077
3086
  printf 'attestation: %s\n' "$att"
3078
3087
  [ "$att" != "TAMPERED" ]
3079
3088
  }
@@ -3093,6 +3102,7 @@ verify_main() {
3093
3102
  VERIFY_CHECK_FRESH=0
3094
3103
  # Opt-in machine-readable stdout (--json). Default 0 = exactly today.
3095
3104
  VERIFY_JSON=0
3105
+ VERIFY_ALLOW_UNSIGNED=0
3096
3106
 
3097
3107
  # Fail-closed defaults. These globals are read at the end of this function
3098
3108
  # (the VERDICT banner and the function return code). verify_compute_verdict()
@@ -3158,6 +3168,7 @@ verify_main() {
3158
3168
  # verdict above stays authoritative for the exit code. Unset =
3159
3169
  # exactly today's behavior. See verify_hosted_enrich().
3160
3170
  VERIFY_HOSTED=1; shift ;;
3171
+ --allow-unsigned) VERIFY_ALLOW_UNSIGNED=1; shift ;;
3161
3172
  --) shift; break ;;
3162
3173
  -*)
3163
3174
  _verify_err "unknown option: $1"; verify_help; return $VERIFY_EXIT_ERROR ;;
package/completions/_loki CHANGED
@@ -205,6 +205,7 @@ function _loki_commands {
205
205
  'crash:Crash report management'
206
206
  'otel:OpenTelemetry management'
207
207
  'wiki:Wiki commands'
208
+ 'backlog:Run every matching open issue, N in parallel'
208
209
  'explain:Explain Loki concepts'
209
210
  'optimize:Optimization commands'
210
211
  'review:Code review commands'
@@ -5,8 +5,8 @@ _loki_completion() {
5
5
  _init_completion || return
6
6
 
7
7
  # Main subcommands (must match autonomy/loki main case statement)
8
- local main_commands="start quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
9
- local main_commands="start quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
8
+ local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
9
+ local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
10
10
 
11
11
  # 1. If we are on the first argument (subcommand)
12
12
  if [[ $cword -eq 1 ]]; then
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "10.5.16"
10
+ __version__ = "10.5.19"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try: