loki-mode 9.17.0 → 9.18.2

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.
@@ -421,6 +421,30 @@ _verify_zero_tests_executed() {
421
421
  }
422
422
 
423
423
  # ---------------------------------------------------------------------------
424
+ # Reads scripts.test out of a package.json, or prints nothing.
425
+ #
426
+ # Parsed as JSON, never grepped: a substring search over the whole file is the
427
+ # exact bug this helper exists to remove, and re-introducing it one level down
428
+ # would be invisible. A malformed package.json prints nothing, which routes to
429
+ # runner=none -> INCONCLUSIVE, never to a guessed runner.
430
+ _verify_pkg_test_script() {
431
+ local tree="$1"
432
+ [ -f "$tree/package.json" ] || return 0
433
+ python3 -c '
434
+ import json,sys
435
+ try:
436
+ with open(sys.argv[1]) as fh:
437
+ d = json.load(fh)
438
+ except Exception:
439
+ sys.exit(0)
440
+ if not isinstance(d, dict):
441
+ sys.exit(0)
442
+ s = d.get("scripts")
443
+ if isinstance(s, dict) and isinstance(s.get("test"), str):
444
+ sys.stdout.write(s["test"])
445
+ ' "$tree/package.json" 2>/dev/null || true
446
+ }
447
+
424
448
  # Gate: tests (faithful port of enforce_test_coverage detection, run.sh:6624).
425
449
  #
426
450
  # Detection order mirrors the source: vitest -> jest -> mocha (package.json),
@@ -451,16 +475,52 @@ verify_gate_tests() {
451
475
  local out=""
452
476
 
453
477
  if [ -f "$tree/package.json" ]; then
454
- if grep -q '"vitest"' "$tree/package.json" 2>/dev/null; then
455
- runner="vitest"
456
- out="$(cd "$tree" && $_vt npx vitest run 2>&1)" || rc=$?
457
- elif grep -q '"jest"' "$tree/package.json" 2>/dev/null; then
458
- runner="jest"
459
- out="$(cd "$tree" && $_vt npx jest --passWithNoTests --forceExit 2>&1)" || rc=$?
460
- elif grep -q '"mocha"' "$tree/package.json" 2>/dev/null; then
461
- runner="mocha"
462
- out="$(cd "$tree" && $_vt npx mocha 2>&1)" || rc=$?
463
- fi
478
+ # WHICH runner, decided by what `npm test` ACTUALLY INVOKES -- not by
479
+ # what happens to be installed.
480
+ #
481
+ # The old check was `grep '"jest"' package.json`, which matches a
482
+ # DEVDEPENDENCY entry. On this very repo that is exactly what happened:
483
+ # jest is a devDependency with NO jest config, while scripts.test runs
484
+ # bash -n plus `node --test`. So verify hijacked the runner, jest globbed
485
+ # 895 files that are not jest tests, every one reported "Your test suite
486
+ # must contain at least one test", and `loki verify` returned BLOCKED on
487
+ # a clean tree -- permanently, for a defect that does not exist.
488
+ #
489
+ # A false BLOCK on the flagship verification command is worse than a
490
+ # missed one: it trains users to ignore the verdict.
491
+ #
492
+ # scripts.test is the project's own declaration of its runner, so it is
493
+ # the signal with authority here. A declared script that names none of
494
+ # the three is still RUN, via `npm test` -- mirroring run.sh's npm-test
495
+ # fallback. Falling through instead would be its own defect: this repo
496
+ # declares `bash -n ... && node --test ...`, and without the fallback
497
+ # verify skipped it and ran PYTEST over a bash/node project.
498
+ local _test_script=""
499
+ _test_script="$(_verify_pkg_test_script "$tree")"
500
+ case "$_test_script" in
501
+ *vitest*)
502
+ runner="vitest"
503
+ out="$(cd "$tree" && $_vt npx vitest run 2>&1)" || rc=$? ;;
504
+ *jest*)
505
+ runner="jest"
506
+ out="$(cd "$tree" && $_vt npx jest --passWithNoTests --forceExit 2>&1)" || rc=$? ;;
507
+ *mocha*)
508
+ runner="mocha"
509
+ out="$(cd "$tree" && $_vt npx mocha 2>&1)" || rc=$? ;;
510
+ "")
511
+ : ;; # nothing declared: leave runner=none for the paths below
512
+ *"no test specified"*)
513
+ : ;; # npm's placeholder is not a test script
514
+ *)
515
+ # Labelled by what it invokes, so the evidence names the real
516
+ # runner rather than a generic "npm".
517
+ case "$_test_script" in
518
+ *"node --test"*|*"node:test"*) runner="node-test" ;;
519
+ *pytest*) runner="pytest" ;;
520
+ *) runner="npm-test" ;;
521
+ esac
522
+ out="$(cd "$tree" && $_vt npm test 2>&1)" || rc=$? ;;
523
+ esac
464
524
  fi
465
525
 
466
526
  if [ "$runner" = "none" ]; then
@@ -1060,10 +1120,38 @@ print("%d %d %d %d" % (crit, high, mod, low))
1060
1120
  ' 2>/dev/null || echo "")"
1061
1121
  if [ -n "$sev" ]; then
1062
1122
  read -r _c _h _m _l <<<"$sev"
1123
+ # SHIPPED vs dev. The audit above covers ALL dependencies,
1124
+ # which is right for a gate -- a compromised build tool is a
1125
+ # real risk -- but the FINDING must say which of the two it
1126
+ # is. This repo reports 4 high CVEs while `npm audit
1127
+ # --omit=dev` reports ZERO: nothing a user installs is
1128
+ # vulnerable. A receipt that says "4 high severity
1129
+ # vulnerabilities" with no such qualifier reads as "the
1130
+ # shipped product is vulnerable", which is a materially
1131
+ # different and false claim.
1132
+ #
1133
+ # Measured separately rather than subtracted: the two runs
1134
+ # count different dependency graphs, so arithmetic on the
1135
+ # totals would not be sound.
1136
+ local _prod_hc=""
1137
+ _prod_hc="$(cd "$tree" && npm audit --omit=dev --json 2>/dev/null | python3 -c '
1138
+ import sys, json
1139
+ try:
1140
+ v = json.load(sys.stdin).get("metadata", {}).get("vulnerabilities", {})
1141
+ except Exception:
1142
+ sys.exit(0)
1143
+ print(v.get("critical", 0) + v.get("high", 0))
1144
+ ' 2>/dev/null || echo "")"
1145
+ local _scope_note=""
1146
+ if [ "$_prod_hc" = "0" ]; then
1147
+ _scope_note=" All are in devDependencies; \`npm audit --omit=dev\` reports 0 high/critical, so nothing in the shipped dependency tree is affected."
1148
+ elif [ -n "$_prod_hc" ]; then
1149
+ _scope_note=" $_prod_hc of these are in the SHIPPED (non-dev) dependency tree."
1150
+ fi
1063
1151
  if [ "$_c" -gt 0 ] || [ "$_h" -gt 0 ]; then
1064
- _verify_add_gate "dependency_audit" "fail" "npm-audit" "$_c critical, $_h high CVEs" "true"
1152
+ _verify_add_gate "dependency_audit" "fail" "npm-audit" "$_c critical, $_h high CVEs (shipped high/critical: ${_prod_hc:-unmeasured})" "true"
1065
1153
  _verify_add_finding "High" "dependencies" "deterministic:npm-audit" "package-lock.json" "null" \
1066
- "npm audit found $_c critical and $_h high severity vulnerabilities."
1154
+ "npm audit found $_c critical and $_h high severity vulnerabilities.${_scope_note}"
1067
1155
  elif [ "$_m" -gt 0 ]; then
1068
1156
  _verify_add_gate "dependency_audit" "fail" "npm-audit" "$_m moderate CVEs" "true"
1069
1157
  _verify_add_finding "Medium" "dependencies" "deterministic:npm-audit" "package-lock.json" "null" \
package/bin/loki CHANGED
@@ -216,8 +216,14 @@ fi
216
216
  # we never disclose for an egress that will not happen. This mirrors the
217
217
  # cli_command sites below. No arguments are read, so nothing user-authored can
218
218
  # reach the payload.
219
+ #
220
+ # `quickstart` and `demo` are here because they are the two commands `loki
221
+ # doctor` actually RECOMMENDS as a first build. Without them the funnel missed
222
+ # every user who followed the product's own advice, which is precisely the
223
+ # population whose drop-off this measures. Both are already in the
224
+ # _loki_known_command allowlist, so `entry=` stays a bounded enum.
219
225
  case "${1:-}" in
220
- start|run|quick)
226
+ start|run|quick|quickstart|demo)
221
227
  if command -v curl &>/dev/null && [ -f "$REPO_ROOT/autonomy/telemetry.sh" ]; then
222
228
  if ( SCRIPT_DIR="$REPO_ROOT/autonomy"; source "$SCRIPT_DIR/telemetry.sh" 2>/dev/null \
223
229
  && declare -f _loki_analytics_enabled >/dev/null 2>&1 && _loki_analytics_enabled \
package/completions/_loki CHANGED
@@ -120,7 +120,6 @@ function _loki {
120
120
  function _loki_commands {
121
121
  local -a commands
122
122
  commands=(
123
- 'gates:What blocks here vs only advises, and the promotion knob'
124
123
  'start:Start Loki Mode'
125
124
  'quick:Quick single-task mode'
126
125
  'quickstart:Guided first build from your idea'
@@ -173,8 +172,6 @@ function _loki_commands {
173
172
  'share:Share session report as GitHub Gist'
174
173
  'proof:Inspect/share proof-of-run artifacts'
175
174
  'outcomes:What happened to the work AFTER the receipt (reverted/reworked/survived)'
176
- 'verdict:The five measured trust signals in one readable block'
177
- 'readiness:Can an agent verify its own work in this repo? (measured, not LLM-scored)'
178
175
  'preview:Preview the locally-running app'
179
176
  'deploy:Deploy the built product (CI/CD-aware)'
180
177
  'context:Context window management'
@@ -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 verdict readiness 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 gates 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 gates 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 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"
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__ = "9.17.0"
10
+ __version__ = "9.18.2"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -8354,93 +8354,6 @@ async def get_trust_trajectory():
8354
8354
  return traj
8355
8355
 
8356
8356
 
8357
- # =============================================================================
8358
- # Gate policy API: which gates block here, and which only advise?
8359
- # =============================================================================
8360
-
8361
- _GATE_POLICY_MODULE = None # cached import of autonomy/lib/gate_policy.py
8362
-
8363
-
8364
- def _load_gate_policy_module():
8365
- """Import the shared gate-policy reporter (single source of truth).
8366
-
8367
- Same shape as _load_trust_module above: the derivation lives in
8368
- autonomy/lib/gate_policy.py so this endpoint, the bash `loki gates`, and the
8369
- test suite all agree, and it loads via importlib because autonomy/lib is not
8370
- an importable package. Cached after first load, None when unavailable.
8371
- """
8372
- global _GATE_POLICY_MODULE
8373
- if _GATE_POLICY_MODULE is not None:
8374
- return _GATE_POLICY_MODULE
8375
- repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8376
- mod_path = os.path.join(repo_root, "autonomy", "lib", "gate_policy.py")
8377
- if not os.path.isfile(mod_path):
8378
- return None
8379
- try:
8380
- import importlib.util as _ilu
8381
- spec = _ilu.spec_from_file_location("gate_policy", mod_path)
8382
- if spec is None or spec.loader is None:
8383
- return None
8384
- mod = _ilu.module_from_spec(spec)
8385
- spec.loader.exec_module(mod)
8386
- _GATE_POLICY_MODULE = mod
8387
- return mod
8388
- except Exception:
8389
- return None
8390
-
8391
-
8392
- @app.get("/api/gate-policy", dependencies=[Depends(auth.require_scope("read"))])
8393
- async def get_gate_policy():
8394
- """Which quality gates block on this repo, and which only advise.
8395
-
8396
- Read-only by construction. There is deliberately no POST counterpart:
8397
- promoting an advisory gate to blocking stays an explicit operator act via
8398
- the environment variable named in each record's `promote_with`, because a
8399
- surface that can silently start blocking is the thing operators most
8400
- reasonably fear. This endpoint reports; it never promotes.
8401
-
8402
- Honest-data rule, and the reason `audit_hits` is nullable: an unmeasured
8403
- gate reports null, NEVER 0. Zero is the positive claim that the gate ran and
8404
- never fired; an absent ledger is not evidence of that. See the shape
8405
- contract in the gate_policy module docstring.
8406
-
8407
- Fails open like /api/trust/trajectory: a missing module returns a renderable
8408
- available=False payload rather than a 500. Unlike that sibling the assess()
8409
- call is also wrapped, because it reads a ledger file that an unrelated
8410
- writer can leave malformed, and a broken ledger must not take down a
8411
- read-only report.
8412
-
8413
- Response keys are the module shape contract plus `available` (bool). On the
8414
- fail-open path `available` is False, `status` is "unavailable" (a value the
8415
- module contract does not itself define, since assess() never returned), an
8416
- `error` string explains why, and `gates` is empty. A UI should branch on
8417
- `available`, not on a non-empty `gates`.
8418
- """
8419
- mod = _load_gate_policy_module()
8420
- if mod is None:
8421
- return {
8422
- "schema_version": 1,
8423
- "available": False,
8424
- "error": "gate_policy module not found",
8425
- "status": "unavailable",
8426
- "ledger": "absent",
8427
- "gates": [],
8428
- }
8429
- try:
8430
- res = mod.assess(str(_get_loki_dir()))
8431
- except Exception as e:
8432
- return {
8433
- "schema_version": 1,
8434
- "available": False,
8435
- "error": f"gate policy assessment failed: {e}",
8436
- "status": "unavailable",
8437
- "ledger": "absent",
8438
- "gates": [],
8439
- }
8440
- res["available"] = True
8441
- return res
8442
-
8443
-
8444
8357
  # =============================================================================
8445
8358
  # Pricing API
8446
8359
  # =============================================================================
@@ -10270,71 +10183,6 @@ async def remove_checklist_waiver(item_id: str):
10270
10183
  # Council Hard Gate Endpoint (Phase 4)
10271
10184
  # =============================================================================
10272
10185
 
10273
- # Receipts name gates in snake_case; the UI rows are display names. Mapping is
10274
- # explicit rather than derived (a lower()/replace() heuristic would silently
10275
- # mis-map "Test Suite" <-> "test_coverage" and "Test Mutation" <->
10276
- # "mutation_integrity", which are different gates).
10277
- _RECEIPT_GATE_NAMES = {
10278
- "static_analysis": "Static Analysis",
10279
- "test_coverage": "Test Suite",
10280
- "code_review": "Blind Code Review",
10281
- "anti_sycophancy": "Anti-Sycophancy",
10282
- "mock_integrity": "Mock Integrity",
10283
- "mutation_integrity": "Test Mutation",
10284
- "doc_coverage": "Documentation Coverage",
10285
- "magic_debate": "Magic Modules Debate",
10286
- }
10287
-
10288
-
10289
- # Receipts say "passed"/"failed"; the UI switches on "pass"/"fail"
10290
- # (dashboard-ui/components/loki-quality-gates.js:51). Passing the receipt's word
10291
- # through unchanged matched nothing, so every gate fell to the "pending" default
10292
- # and the page still read "0 Pass, 0 Fail, 8 Pending" while showing real
10293
- # timestamps -- half-fixed, and only visible by loading the page.
10294
- #
10295
- # An UNRECOGNISED status maps to nothing and the gate stays pending. Guessing
10296
- # (e.g. treating any unknown word as a pass) is how a future status string would
10297
- # silently become a green badge.
10298
- _RECEIPT_GATE_STATUS = {
10299
- "passed": "pass", "pass": "pass", "ok": "pass",
10300
- "failed": "fail", "fail": "fail", "blocked": "fail",
10301
- }
10302
-
10303
-
10304
- def _receipt_backed_gates():
10305
- """_DEFAULT_QUALITY_GATES, with results filled in from the newest receipt.
10306
-
10307
- Fail-open by design: any error returns the plain defaults, so a malformed
10308
- receipt degrades to "pending" rather than breaking the Quality page.
10309
-
10310
- Gates absent from the receipt keep status "pending" and get NO last_checked.
10311
- An absent measurement is never rendered as a result.
10312
- """
10313
- gates = [dict(g) for g in _DEFAULT_QUALITY_GATES]
10314
- try:
10315
- proofs = sorted((_get_loki_dir() / "proofs").glob("*/proof.json"))
10316
- if not proofs:
10317
- return gates
10318
- receipt = json.loads(proofs[-1].read_text())
10319
- checked_at = receipt.get("generated_at") or ""
10320
- by_display = {}
10321
- for entry in receipt.get("quality_gates", {}).get("gates", []) or []:
10322
- display = _RECEIPT_GATE_NAMES.get(entry.get("name", ""))
10323
- status = _RECEIPT_GATE_STATUS.get(str(entry.get("status", "")).lower())
10324
- if display and status:
10325
- by_display[display] = status
10326
- for gate in gates:
10327
- status = by_display.get(gate.get("name"))
10328
- if status:
10329
- gate["status"] = status
10330
- if checked_at:
10331
- gate["last_checked"] = checked_at
10332
- gate["source"] = "receipt"
10333
- except (OSError, ValueError, KeyError, TypeError, AttributeError):
10334
- return [dict(g) for g in _DEFAULT_QUALITY_GATES]
10335
- return gates
10336
-
10337
-
10338
10186
  _DEFAULT_QUALITY_GATES = [
10339
10187
  {"name": "Static Analysis", "description": "CodeQL, ESLint/Pylint, type-checker findings on the diff", "status": "pending"},
10340
10188
  {"name": "Test Suite", "description": "Project test runner pass/fail (red blocks)", "status": "pending"},
@@ -10373,22 +10221,7 @@ async def get_council_gate():
10373
10221
  except (json.JSONDecodeError, IOError):
10374
10222
  data = {"blocked": False, "gates": _DEFAULT_QUALITY_GATES, "error": "Failed to read gate file"}
10375
10223
  else:
10376
- # No live gate file. Fall back to the most recent RECEIPT, which already
10377
- # records per-gate results -- rather than serving eight rows that all
10378
- # say "Last checked: Never" while .loki/proofs/ holds the real answers.
10379
- #
10380
- # That "Never" was not a display bug: /api/council/gate served
10381
- # _DEFAULT_QUALITY_GATES verbatim, and those entries carry no
10382
- # last_checked, so the UI honestly rendered "Never" for every gate on a
10383
- # repo with 9 receipts.
10384
- #
10385
- # ONLY gates the receipt actually names are filled in. Measured across
10386
- # all 9 receipts here, that is 3 of 8 (static_analysis, code_review,
10387
- # doc_coverage); the other five stay "pending" with no timestamp,
10388
- # because a gate that never ran must not inherit a neighbour's result.
10389
- # Filling all eight from a 3-gate receipt is the exact false-green this
10390
- # codebase exists to prevent.
10391
- data = {"blocked": False, "gates": _receipt_backed_gates()}
10224
+ data = {"blocked": False, "gates": _DEFAULT_QUALITY_GATES}
10392
10225
 
10393
10226
  # Verified-completion evidence gate (additive).
10394
10227
  if evidence_file.exists():