loki-mode 9.53.0 → 9.54.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.
Files changed (119) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/completion-council.sh +28 -5
  4. package/autonomy/council-v2.sh +13 -7
  5. package/autonomy/lib/done-recognition.sh +24 -16
  6. package/autonomy/lib/proof-check.sh +11 -3
  7. package/autonomy/lib/proof-generator.py +36 -5
  8. package/autonomy/lib/voter-agents.sh +8 -3
  9. package/autonomy/lib/workspace_diff.py +106 -12
  10. package/autonomy/prd-checklist.sh +19 -11
  11. package/autonomy/run.sh +503 -52
  12. package/dashboard/__init__.py +1 -1
  13. package/dashboard/api_v2.py +4 -0
  14. package/dashboard/audit.py +9 -1
  15. package/dashboard/auth.py +16 -4
  16. package/dashboard/registry.py +34 -12
  17. package/dashboard/rigour_integration.py +25 -17
  18. package/dashboard/server.py +252 -104
  19. package/dashboard/static/cost.html +55 -26
  20. package/dashboard/static/index.html +475 -1544
  21. package/dashboard/static/proofs.html +1 -1
  22. package/docs/INSTALLATION.md +1 -1
  23. package/docs/air-gapped.md +7 -2
  24. package/docs/image-provenance.md +4 -2
  25. package/docs/v10/BACKLOG.md +82 -2
  26. package/docs/v10/BOARD.md +105 -0
  27. package/docs/v10/DECISIONS.md +71 -0
  28. package/docs/v10/METRICS.md +2 -1
  29. package/docs/v10/PROGRESS.md +60 -0
  30. package/docs/v10/SWARM-PROMPT.md +98 -0
  31. package/docs/v10/SWARM.md +237 -0
  32. package/loki-ts/dist/loki.js +323 -322
  33. package/mcp/__init__.py +1 -1
  34. package/package.json +1 -1
  35. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  36. package/providers/aider.sh +8 -0
  37. package/providers/cline.sh +8 -0
  38. package/providers/codex.sh +9 -0
  39. package/providers/opencode.sh +9 -0
  40. package/web-app/dist/assets/AdminPage-CZrBkzRF.js +32 -0
  41. package/web-app/dist/assets/Avatar-gQIp6FBI.js +11 -0
  42. package/web-app/dist/assets/{Badge-BF3mhT57.js → Badge-D2_fBTFY.js} +1 -1
  43. package/web-app/dist/assets/{Button-BDp9aJ9q.js → Button-D5qDd2ef.js} +1 -1
  44. package/web-app/dist/assets/CockpitPage-DVMk2RUK.js +47 -0
  45. package/web-app/dist/assets/ComparePage-NMLyE1xL.js +1 -0
  46. package/web-app/dist/assets/{ErrorBoundary-ut3wrUmu.js → ErrorBoundary-BRABI-BT.js} +1 -1
  47. package/web-app/dist/assets/EvidenceReceiptPanel-D2LNKqUx.js +1 -0
  48. package/web-app/dist/assets/GitHubIssuesPanel-BC5GzKUN.js +7 -0
  49. package/web-app/dist/assets/{GitHubPRsPanel-DurZgSaj.js → GitHubPRsPanel-BSlmoVTS.js} +3 -3
  50. package/web-app/dist/assets/HomePage-D_CDMoen.js +12 -0
  51. package/web-app/dist/assets/{LoginPage-HeRGJeZy.js → LoginPage-BBNA_N9t.js} +1 -1
  52. package/web-app/dist/assets/{MagicPage-CUKrqsZs.js → MagicPage-CnFRZCMx.js} +1 -1
  53. package/web-app/dist/assets/MetricsPage-CPfLMRlh.js +6 -0
  54. package/web-app/dist/assets/{NotFoundPage-BZDZ-B77.js → NotFoundPage-lA0XSGTz.js} +1 -1
  55. package/web-app/dist/assets/ProjectPage-CNAPbr9z.js +256 -0
  56. package/web-app/dist/assets/ProjectsPage-CMtm8goN.js +6 -0
  57. package/web-app/dist/assets/SettingsPage-BhIjVMYE.js +10 -0
  58. package/web-app/dist/assets/{ShowcasePage-BGS2jTGH.js → ShowcasePage-BndtGeYo.js} +1 -1
  59. package/web-app/dist/assets/SystemSettingsPage-euXI-KXE.js +1 -0
  60. package/web-app/dist/assets/TeamsPage-ClUKecun.js +11 -0
  61. package/web-app/dist/assets/TemplatesPage-BWHWVk0L.js +29 -0
  62. package/web-app/dist/assets/{TerminalOutput-togzv9Zl.js → TerminalOutput-Cv3N3Szf.js} +1 -1
  63. package/web-app/dist/assets/{activity-Dreyb_Kr.js → activity-dEhz-bXR.js} +1 -1
  64. package/web-app/dist/assets/{bell-DWH0drES.js → bell-B3XUMq1t.js} +1 -1
  65. package/web-app/dist/assets/{bot-78MoCuDl.js → bot-X-ssNlwR.js} +1 -1
  66. package/web-app/dist/assets/{check-ikfylPst.js → check-C_9Nx7kb.js} +1 -1
  67. package/web-app/dist/assets/{chevron-left-DXUqkd8J.js → chevron-left-tvNswo3A.js} +1 -1
  68. package/web-app/dist/assets/{circle-alert-Da74VO8P.js → circle-alert-CSB88nEs.js} +1 -1
  69. package/web-app/dist/assets/{clock-mlGCFq8l.js → clock-C8p2e4NI.js} +1 -1
  70. package/web-app/dist/assets/{cloud-B_Y5ZrIO.js → cloud-CKp2ZvhG.js} +1 -1
  71. package/web-app/dist/assets/{code-xml-Bbom7YWV.js → code-xml-dY70LCHD.js} +1 -1
  72. package/web-app/dist/assets/{database-CLcofiVp.js → database-DObn5fmn.js} +1 -1
  73. package/web-app/dist/assets/{dollar-sign-C5mkjXIM.js → dollar-sign-DSu6n3K3.js} +1 -1
  74. package/web-app/dist/assets/{file-code-corner-CVrkaOQP.js → file-code-corner-DpCnS73z.js} +1 -1
  75. package/web-app/dist/assets/{file-plus-BV9240gX.js → file-plus-D3IPKCf1.js} +1 -1
  76. package/web-app/dist/assets/{globe-aZ0Zso4I.js → globe-C9QXkgH_.js} +1 -1
  77. package/web-app/dist/assets/{hammer-EgOQNukG.js → hammer-D_FtWe1y.js} +1 -1
  78. package/web-app/dist/assets/index-CI54rara.css +1 -0
  79. package/web-app/dist/assets/{index-DvZ0Kk2x.js → index-Cz7q1bvC.js} +2 -2
  80. package/web-app/dist/assets/{layers-BOVKmepE.js → layers-DsW7jnJX.js} +1 -1
  81. package/web-app/dist/assets/{loader-circle-0K8yLgHl.js → loader-circle-B3HspjKR.js} +1 -1
  82. package/web-app/dist/assets/{lock-XzlDMy8M.js → lock-DSI29pct.js} +1 -1
  83. package/web-app/dist/assets/{package-BtXTKusI.js → package-CLNxtA2x.js} +1 -1
  84. package/web-app/dist/assets/{plus-PxeiD8ju.js → plus-DyI4WC4_.js} +1 -1
  85. package/web-app/dist/assets/{refresh-cw-CjqzYczt.js → refresh-cw-D2dv0xA0.js} +1 -1
  86. package/web-app/dist/assets/{rotate-ccw-CY0os4DH.js → rotate-ccw-CBvihA-a.js} +1 -1
  87. package/web-app/dist/assets/scroll-text-DvoOvAl_.js +6 -0
  88. package/web-app/dist/assets/{server-pIIs9vNp.js → server-DenWvSUt.js} +1 -1
  89. package/web-app/dist/assets/{shield-alert-CsDqKXwA.js → shield-alert-40cX_FvG.js} +1 -1
  90. package/web-app/dist/assets/{thumbs-up-CAh3HVzH.js → thumbs-up-BrjZ6QuV.js} +12 -7
  91. package/web-app/dist/assets/trash-2-C6evwkEH.js +16 -0
  92. package/web-app/dist/assets/{trending-down-DrtBd_C9.js → trending-down-CfbZis3O.js} +1 -1
  93. package/web-app/dist/assets/{trending-up-D-DbCHXm.js → trending-up-DWuEK3WC.js} +1 -1
  94. package/web-app/dist/assets/{upload-D4l6DCOt.js → upload-B9DFBcPG.js} +1 -1
  95. package/web-app/dist/assets/{usePolling-D0aoTX80.js → usePolling-BryVC-T1.js} +1 -1
  96. package/web-app/dist/assets/{user-BQf_XJO_.js → user-DMRXwZiw.js} +1 -1
  97. package/web-app/dist/index.html +2 -2
  98. package/web-app/server.py +122 -47
  99. package/web-app/dist/assets/AdminPage-y6_b2Xo4.js +0 -32
  100. package/web-app/dist/assets/Avatar-BFPkXhIJ.js +0 -6
  101. package/web-app/dist/assets/CockpitPage-5TKICCoA.js +0 -47
  102. package/web-app/dist/assets/ComparePage-BSuNsUHy.js +0 -1
  103. package/web-app/dist/assets/EvidenceReceiptPanel-CcaBwEI-.js +0 -1
  104. package/web-app/dist/assets/GitHubIssuesPanel-DJeYgzAa.js +0 -12
  105. package/web-app/dist/assets/HomePage-Bs451LAy.js +0 -28
  106. package/web-app/dist/assets/MetricsPage-DvY1csgz.js +0 -6
  107. package/web-app/dist/assets/ProjectPage-CqHkEZkl.js +0 -257
  108. package/web-app/dist/assets/ProjectsPage-CLvCK1Y8.js +0 -6
  109. package/web-app/dist/assets/SettingsPage-DP7OmdwP.js +0 -10
  110. package/web-app/dist/assets/SystemSettingsPage-P_rlLh0x.js +0 -1
  111. package/web-app/dist/assets/TeamsPage-DxtBDcBC.js +0 -11
  112. package/web-app/dist/assets/TemplatesPage-5e-faH3W.js +0 -29
  113. package/web-app/dist/assets/folder-open-D_6yV78G.js +0 -11
  114. package/web-app/dist/assets/index-BSgmPwC0.css +0 -1
  115. package/web-app/dist/assets/lightbulb-DdASgqOG.js +0 -6
  116. package/web-app/dist/assets/mail-COu670_W.js +0 -6
  117. package/web-app/dist/assets/minus-bQpUt9ff.js +0 -6
  118. package/web-app/dist/assets/save-v8qep5cY.js +0 -6
  119. package/web-app/dist/assets/trash-2-Dt6urTyY.js +0 -6
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.53.0"
10
+ __version__ = "9.54.2"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -894,6 +894,10 @@ async def verify_audit_integrity():
894
894
 
895
895
  return {
896
896
  "valid": aggregate["valid"],
897
+ # verified/status: whether anything was checked (valid stays True for
898
+ # an empty chain); see audit.verify_all_logs_in_dir.
899
+ "verified": aggregate["verified"],
900
+ "status": aggregate["status"],
897
901
  "files_checked": aggregate["files_checked"],
898
902
  "files_skipped": aggregate.get("files_skipped", 0),
899
903
  "entries_checked": aggregate.get("entries_checked", 0),
@@ -657,7 +657,8 @@ def verify_all_logs_in_dir(audit_dir) -> dict:
657
657
  if not audit_dir.exists():
658
658
  return {"valid": True, "files_checked": 0, "files_skipped": 0,
659
659
  "entries_checked": 0, "first_tampered_file": None,
660
- "first_tampered_line": None, "genesis_file": None}
660
+ "first_tampered_line": None, "genesis_file": None,
661
+ "verified": False, "status": "nothing_checked"}
661
662
  # v7.7.15 council fix (Opus 2): rotated files have name shape
662
663
  # `audit-YYYY-MM-DD.HHMMSS.jsonl` (from `_rotate_logs_if_needed` at
663
664
  # line 167). Lexicographic sort puts `audit-2026-05-04.123456.jsonl`
@@ -689,8 +690,13 @@ def verify_all_logs_in_dir(audit_dir) -> dict:
689
690
  "first_tampered_file": str(log_file),
690
691
  "first_tampered_line": result.get("first_tampered_line"),
691
692
  "genesis_file": genesis_file,
693
+ "verified": False,
694
+ "status": "tampered",
692
695
  }
693
696
  prev_hash = result.get("last_hash", prev_hash)
697
+ # `valid` means "no break found" and stays True for an empty chain (the
698
+ # cross-chain verifier reads it). `verified` and `status` say whether
699
+ # anything was actually checked: zero files checked is not a verified chain.
694
700
  return {
695
701
  "valid": True,
696
702
  "files_checked": files_checked,
@@ -699,6 +705,8 @@ def verify_all_logs_in_dir(audit_dir) -> dict:
699
705
  "first_tampered_file": None,
700
706
  "first_tampered_line": None,
701
707
  "genesis_file": genesis_file,
708
+ "verified": files_checked > 0,
709
+ "status": "verified" if files_checked > 0 else "nothing_checked",
702
710
  }
703
711
 
704
712
 
package/dashboard/auth.py CHANGED
@@ -343,6 +343,18 @@ def list_tokens(include_revoked: bool = False) -> list[dict]:
343
343
  return result
344
344
 
345
345
 
346
+ def _deadline_passed(value) -> bool:
347
+ """True when an ISO-8601 deadline is in the past, or cannot be read.
348
+
349
+ Fails closed: an unparseable value, or a naive timestamp that cannot be
350
+ ordered against an aware "now", is treated as passed.
351
+ """
352
+ try:
353
+ return datetime.now(timezone.utc) > datetime.fromisoformat(str(value))
354
+ except (TypeError, ValueError):
355
+ return True
356
+
357
+
346
358
  def validate_token(raw_token: str) -> Optional[dict]:
347
359
  """
348
360
  Validate a raw token.
@@ -372,10 +384,10 @@ def validate_token(raw_token: str) -> Optional[dict]:
372
384
  if matched_token.get("revoked"):
373
385
  return None
374
386
 
375
- # Check expiration
376
- if matched_token.get("expires_at"):
377
- expires = datetime.fromisoformat(matched_token["expires_at"])
378
- if datetime.now(timezone.utc) > expires:
387
+ # Check expiration, and the grace period of a rotated-out key
388
+ # (api_keys.rotate_key sets rotation_expires_at on the old key).
389
+ for field in ("expires_at", "rotation_expires_at"):
390
+ if matched_token.get(field) and _deadline_passed(matched_token[field]):
379
391
  return None
380
392
 
381
393
  # Update last used
@@ -16,6 +16,8 @@ from pathlib import Path
16
16
  from typing import Iterator, Optional
17
17
  import hashlib
18
18
 
19
+ from .api_runs import _efficiency_module
20
+
19
21
 
20
22
  # Registry file location
21
23
  REGISTRY_DIR = Path.home() / ".loki" / "dashboard"
@@ -686,13 +688,14 @@ def _read_project_run_snapshot(path: str) -> dict:
686
688
  .loki/context/tracking.json fallback (totals.total_cost_usd)
687
689
 
688
690
  Returns a dict with phase, iteration, cost_usd, started_at, and ended_at
689
- (best-effort; missing values default to None/0). Never raises: any file
690
- problem degrades the affected field to its default.
691
+ (best-effort; missing values default to None). iteration and cost_usd are
692
+ None, never 0 / 0.0, when nothing recorded them. Never raises: any file problem degrades the
693
+ affected field to its default.
691
694
  """
692
695
  snap = {
693
696
  "phase": "",
694
- "iteration": 0,
695
- "cost_usd": 0.0,
697
+ "iteration": None,
698
+ "cost_usd": None,
696
699
  "started_at": None,
697
700
  "ended_at": None,
698
701
  }
@@ -708,8 +711,8 @@ def _read_project_run_snapshot(path: str) -> dict:
708
711
  if isinstance(state, dict):
709
712
  _p = state.get("phase", "")
710
713
  snap["phase"] = _p if isinstance(_p, str) else ""
711
- _i = state.get("iteration", 0)
712
- snap["iteration"] = _i if isinstance(_i, int) else 0
714
+ _i = state.get("iteration")
715
+ snap["iteration"] = _i if isinstance(_i, int) and not isinstance(_i, bool) else None
713
716
  except (json.JSONDecodeError, OSError, ValueError):
714
717
  pass
715
718
 
@@ -727,6 +730,15 @@ def _read_project_run_snapshot(path: str) -> dict:
727
730
  pass
728
731
 
729
732
  # Cost: sum per-iteration efficiency files; fall back to context tracking.
733
+ # A present record is not a measurement: only a record that carried an
734
+ # observed value (the canonical record_is_measured, the same predicate as
735
+ # dashboard/server.py _record_is_measured) and a numeric cost counts, so an
736
+ # all-zero or field-less record, or the 0.0 a fresh tracking.json is seeded
737
+ # with, reads as unknown instead of $0.00. A measured $0.00 stays 0.0.
738
+ # ponytail: a measured record with tokens but no cost_usd adds nothing (the
739
+ # fleet does not price tokens); price it here if unpriced records appear.
740
+ eff_mod = _efficiency_module()
741
+ measured = eff_mod.record_is_measured if eff_mod is not None else (lambda _rec: False)
730
742
  cost = 0.0
731
743
  found_cost = False
732
744
  eff_dir = loki_dir / "metrics" / "efficiency"
@@ -738,7 +750,7 @@ def _read_project_run_snapshot(path: str) -> dict:
738
750
  if not isinstance(data, dict):
739
751
  continue
740
752
  c = data.get("cost_usd")
741
- if isinstance(c, (int, float)):
753
+ if isinstance(c, (int, float)) and not isinstance(c, bool) and measured(data):
742
754
  cost += float(c)
743
755
  found_cost = True
744
756
  except (json.JSONDecodeError, OSError, ValueError):
@@ -754,11 +766,15 @@ def _read_project_run_snapshot(path: str) -> dict:
754
766
  totals = ctx.get("totals", {})
755
767
  if isinstance(totals, dict):
756
768
  tc = totals.get("total_cost_usd")
757
- if isinstance(tc, (int, float)):
769
+ if isinstance(tc, (int, float)) and not isinstance(tc, bool) and measured({
770
+ "cost_usd": tc,
771
+ "input_tokens": totals.get("total_input"),
772
+ "output_tokens": totals.get("total_output")}):
758
773
  cost = float(tc)
774
+ found_cost = True
759
775
  except (json.JSONDecodeError, OSError, ValueError):
760
776
  pass
761
- snap["cost_usd"] = round(cost, 6)
777
+ snap["cost_usd"] = round(cost, 6) if found_cost else None
762
778
  return snap
763
779
 
764
780
 
@@ -836,7 +852,7 @@ def get_fleet_runs(include_inactive: bool = True) -> list[dict]:
836
852
  "running": running,
837
853
  "phase": snap.get("phase", ""),
838
854
  "iteration": snap.get("iteration", 0),
839
- "cost_usd": snap.get("cost_usd", 0.0),
855
+ "cost_usd": snap.get("cost_usd"),
840
856
  "started_at": started_at,
841
857
  "duration_seconds": duration_seconds,
842
858
  "port": p.get("port"),
@@ -862,16 +878,22 @@ def get_fleet_summary(include_inactive: bool = True) -> dict:
862
878
  Returns counts (total / running / stopped) and a summed cost across all
863
879
  registered projects. v1 polls the shared metadata store (see
864
880
  get_fleet_runs); not a controller. Never raises.
881
+
882
+ total_cost_usd sums only runs that recorded a cost and is None when none
883
+ did; total_cost_partial is True when some runs recorded no cost, so the
884
+ total is a lower bound, not the whole bill.
865
885
  """
866
886
  runs = get_fleet_runs(include_inactive=include_inactive)
867
887
  total = len(runs)
868
888
  running = sum(1 for r in runs if r.get("running"))
869
- total_cost = round(sum(float(r.get("cost_usd") or 0.0) for r in runs), 6)
889
+ costs = [float(r["cost_usd"]) for r in runs
890
+ if isinstance(r.get("cost_usd"), (int, float)) and not isinstance(r.get("cost_usd"), bool)]
870
891
  return {
871
892
  "total_runs": total,
872
893
  "running_runs": running,
873
894
  "stopped_runs": total - running,
874
- "total_cost_usd": total_cost,
895
+ "total_cost_usd": round(sum(costs), 6) if costs else None,
896
+ "total_cost_partial": 0 < len(costs) < total,
875
897
  }
876
898
 
877
899
 
@@ -245,32 +245,36 @@ class RigourIntegration:
245
245
  for sev in findings:
246
246
  findings[sev] = int(_extract_num(raw_findings, sev, 0))
247
247
 
248
- # Extract category scores
248
+ # Extract category scores. A category Rigour did not report is None
249
+ # (the panel shows "--"), never a scored 0.
249
250
  raw_categories = raw.get("categories", raw.get("scores", {}))
251
+ if not isinstance(raw_categories, dict):
252
+ raw_categories = {}
250
253
  categories = {
251
- "security": _extract_num(raw_categories, "security", 0),
252
- "code_quality": _extract_num(raw_categories, "code_quality", _extract_num(raw_categories, "codeQuality", 0)),
253
- "compliance": _extract_num(raw_categories, "compliance", 0),
254
- "best_practices": _extract_num(raw_categories, "best_practices", _extract_num(raw_categories, "bestPractices", 0)),
254
+ "security": _extract_num(raw_categories, "security", None),
255
+ "code_quality": _extract_num(raw_categories, "code_quality", _extract_num(raw_categories, "codeQuality", None)),
256
+ "compliance": _extract_num(raw_categories, "compliance", None),
257
+ "best_practices": _extract_num(raw_categories, "best_practices", _extract_num(raw_categories, "bestPractices", None)),
255
258
  }
256
259
 
257
- # Compute overall score
260
+ # Compute overall score: the reported one, else the average of the
261
+ # categories that were reported, else None (no score, no grade).
258
262
  raw_score = raw.get("score", raw.get("overall_score"))
263
+ score = None
259
264
  if raw_score is not None:
260
265
  try:
261
266
  score = float(raw_score)
262
267
  except (ValueError, TypeError):
263
- score = 0.0
268
+ score = None
264
269
  else:
265
- # Average of non-zero categories
266
- non_zero = [v for v in categories.values() if v > 0]
267
- score = round(sum(non_zero) / len(non_zero), 1) if non_zero else 0.0
270
+ reported = [v for v in categories.values() if v is not None]
271
+ score = round(sum(reported) / len(reported), 1) if reported else None
268
272
 
269
273
  result: dict[str, Any] = {
270
274
  "available": True,
271
- "score": round(score, 1),
275
+ "score": round(score, 1) if score is not None else None,
272
276
  "max_score": 100,
273
- "grade": _score_to_grade(score),
277
+ "grade": _score_to_grade(score) if score is not None else None,
274
278
  "timestamp": datetime.now(timezone.utc).isoformat(),
275
279
  "preset": preset,
276
280
  "findings": findings,
@@ -301,8 +305,9 @@ class RigourIntegration:
301
305
  "grade": None,
302
306
  "timestamp": datetime.now(timezone.utc).isoformat(),
303
307
  "preset": preset,
304
- "findings": {"critical": 0, "major": 0, "minor": 0, "info": 0},
305
- "categories": {"security": 0, "code_quality": 0, "compliance": 0, "best_practices": 0},
308
+ # Nothing was scanned: no counts and no category scores.
309
+ "findings": {"critical": None, "major": None, "minor": None, "info": None},
310
+ "categories": {"security": None, "code_quality": None, "compliance": None, "best_practices": None},
306
311
  "reason": reason,
307
312
  }
308
313
 
@@ -325,9 +330,12 @@ class RigourIntegration:
325
330
  }
326
331
 
327
332
 
328
- def _extract_num(d: dict, key: str, default: float = 0) -> float:
329
- """Safely extract a numeric value from a dict."""
333
+ def _extract_num(d: dict, key: str, default: Optional[float] = 0) -> Optional[float]:
334
+ """Safely extract a numeric value from a dict; `default` when absent or not numeric."""
335
+ v = d.get(key)
336
+ if v is None or isinstance(v, bool):
337
+ return default
330
338
  try:
331
- return float(d.get(key, default))
339
+ return float(v)
332
340
  except (ValueError, TypeError):
333
341
  return default