Compare commits

2 Commits

Author SHA1 Message Date
alexei.dolgolyov b7006c5f41 chore: add vex code-search config 2026-06-21 21:04:41 +03:00
alexei.dolgolyov 3355f0dda8 fix: harden Washing Machine remaining-time parsing for unavailable states
Treat 'unavailable'/'none'/'-'/'' remaining-time values as not-running and
parse with int(0) defaults so a partially invalid sensor value degrades to 0
instead of raising. Bump manifest to 2.14.1.
2026-06-21 21:04:41 +03:00
3 changed files with 73 additions and 13 deletions
+57
View File
@@ -0,0 +1,57 @@
# vex configuration — https://github.com/tenatarika/vex
#
# Place this file in your project root as .vex.toml
# Glob patterns to exclude from indexing (gitignore syntax, on top of .gitignore)
# exclude = [
# "vendor/**",
# "node_modules/**",
# "*.generated.go",
# "dist/**",
# ]
# Default output format: "text", "json", or "compact"
# format = "text"
# Enable semantic embeddings by default (slower indexing, enables meaning-based search)
semantic = true
# Automatically run `vex update` before search if the index is stale
auto_update = true
# Embedder used for semantic indexing. Known IDs: minilm-l6-v2 (default).
# Changing the embedder requires a full reindex.
# embedder = "minilm-l6-v2"
# Cache directory override. Defaults to the platform cache location.
# macOS: ~/Library/Caches/vex
# Linux: $XDG_CACHE_HOME/vex (fallback: ~/.cache/vex)
# Windows: %LOCALAPPDATA%\vex (fallback: %USERPROFILE%\AppData\Local\vex)
# Accepts absolute paths, "~/..." or paths relative to this file (e.g. "./.vex/cache").
# Can also be overridden per-invocation with --cache-dir or $VEX_CACHE_DIR.
# cache_dir = "./.vex/cache"
# Store the index inside the project as `<project>/.vex_cache/`. Useful when
# the cache should travel with the project (e.g. on a moved or renamed
# directory). vex writes a `.gitignore` inside it so contents are not
# committed. Overridden by `cache_dir`, `--cache-dir`, or $VEX_CACHE_DIR.
# local_cache = false
# Thread count for parallel indexing (index/update/watch).
# * unset — 80% of available cores, rounded up (default, leaves headroom)
# * 0 — use all cores (explicit opt-in to max throughput)
# * N — exactly N workers
# Overridable per-invocation with `-j/--jobs` or $VEX_JOBS.
# jobs = 4
# Build the persistent call-graph section. Disabling falls back to live-scan
# for `vex callers`/`vex callees` (slower per-query, but saves indexing
# time on large monorepos). The opt-out is persisted in the manifest so
# `vex update` does not silently re-add the section.
# Per-invocation override: `vex index --no-call-graph`.
# call_graph = true
# Build the BM25 channel. Disabling drops the third RRF channel and keeps
# only structural (+ semantic). Same persistence rules as `call_graph`.
# Per-invocation override: `vex index --no-bm25`.
# bm25 = true
+15 -12
View File
@@ -553,15 +553,17 @@ variables:
and run_state not in non_running_state_ids and run_state not in non_running_state_ids
and run_state != preparation_state_id and run_state != preparation_state_id
and run_state != run_state_completion_id and run_state != run_state_completion_id
and remaining not in ['unknown', 'unavailable'] }} and (remaining | string | trim) not in ['unknown', 'unavailable', 'none', '-', ''] }}
# Parse remaining time string (hh:mm:ss) to total minutes # Parse remaining time string (hh:mm:ss) to total minutes.
# Use int(0) defaults so a partially invalid sensor value (e.g.
# 'unavailable:00:00') degrades to 0 instead of raising.
remaining_time_in_minutes: > remaining_time_in_minutes: >
{% if remaining not in ['unknown', 'unavailable', '-'] %} {% set r = remaining | string | trim %}
{% set parts = remaining.split(':') %} {% if r not in ['unknown', 'unavailable', 'none', '-', ''] %}
{% set parts = r.split(':') %}
{% if parts | length >= 2 %} {% if parts | length >= 2 %}
{% set total = parts[0]|int * 60 + parts[1]|int %} {{ parts[0] | int(0) * 60 + parts[1] | int(0) }}
{{ total }}
{% else %} {% else %}
{{ 0 }} {{ 0 }}
{% endif %} {% endif %}
@@ -571,11 +573,12 @@ variables:
# Calculate estimated end time from remaining time # Calculate estimated end time from remaining time
estimated_end_time: > estimated_end_time: >
{% if remaining not in ['unknown', 'unavailable', '-'] %} {% set r = remaining | string | trim %}
{% set parts = remaining.split(':') %} {% if r not in ['unknown', 'unavailable', 'none', '-', ''] %}
{% set parts = r.split(':') %}
{% if parts | length >= 2 %} {% if parts | length >= 2 %}
{% set hours = parts[0] | int %} {% set hours = parts[0] | int(0) %}
{% set minutes = parts[1] | int %} {% set minutes = parts[1] | int(0) %}
{% set end_time = now() + timedelta(hours=hours, minutes=minutes) %} {% set end_time = now() + timedelta(hours=hours, minutes=minutes) %}
{{ end_time.strftime('%H:%M') }} {{ end_time.strftime('%H:%M') }}
{% else %} {% else %}
@@ -739,7 +742,7 @@ action:
value_template: > value_template: >
{{ not is_running {{ not is_running
and automation_state.get(state_notification_about_start_sent, false) and automation_state.get(state_notification_about_start_sent, false)
and (states(run_state_sensor) in [run_state_completion_id, 'unknown', '-'] or states(remaining_time_sensor) in ['unknown', '-']) }} and (states(run_state_sensor) in [run_state_completion_id, 'unknown', 'unavailable', '-'] or states(remaining_time_sensor) in ['unknown', 'unavailable', '-']) }}
sequence: sequence:
- variables: - variables:
# Calculate energy consumption # Calculate energy consumption
@@ -891,7 +894,7 @@ action:
- variables: - variables:
# Calculate minutes for the template # Calculate minutes for the template
minutes: "{{ remaining.split(':')[1] | int }}" minutes: "{{ remaining.split(':')[1] | int(0) if ':' in (remaining | string) else 0 }}"
# Render the message template with available variables # Render the message template with available variables
message: > message: >
{% set tpl = message_almost_done_template %} {% set tpl = message_almost_done_template %}
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"version": "2.14.0" "version": "2.14.1"
} }