type: user

role: user

content:
Read the following files and report any discrepancies between the documentation and the actual script headers. The goal is to find places where the README or Manual says something that no longer matches what the script header says (wrong schedule, wrong config vars, wrong behavior description, missing scripts, extra scripts, etc.).

Files to read:
1. /boot/config/plugins/varaverk/Kernel/README-Kernel.md
2. /boot/config/plugins/varaverk/Plugin/README-Plugin.md
3. /boot/config/plugins/varaverk/Plugin/Manual-Plugin.md
4. Script headers (top ~100 lines each) — list the scripts in Kernel/ first:
   Run: find /boot/config/plugins/varaverk/Kernel -name "*.sh" | head -10
   Then read headers of whatever .sh files exist there.
5. For Plugin: read headers of any .sh files in /boot/config/plugins/varaverk/Plugin/ (not subdirs).

Report: list each discrepancy as "FILE: what the doc says vs what the header says". If everything matches, say so. Be specific — quote the conflicting text. Keep the report under 400 words.

userType: external

entrypoint: cli

gitBranch: HEAD

slug: mutable-sniffing-naur


type: attachment


content:
- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors ("from now on when X", "each time X", "whenever X", "before/after X") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions ("allow X", "add permission", "move permission to"), env vars ("set X=Y"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: "allow npm commands", "add bq permission to global settings", "move permission to user settings", "set DEBUG=true", "when claude stops show X". For simple settings like theme/model, suggest the /config command.
- keybindings-help: Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json. Examples: "rebind ctrl+s", "add a chord shortcut", "change the submit key", "customize keybindings".
- verify: Verify that a code change actually does what it's supposed to by running the app and observing behavior. Use when asked to verify a PR, confirm a fix works, test a change manually, check that a feature works, or validate local changes before pushing.
- code-review: Review the current diff for correctness bugs and reuse/simplification/efficiency cleanups at the given effort level (low/medium: fewer, high-confidence findings; high→max: broader coverage, may include uncertain findings; ultra: deep multi-agent review in the cloud). Pass --comment to post findings as inline PR comments, or --fix to apply the findings to the working tree after the review.
- simplify: Review the changed code for reuse, simplification, efficiency, and altitude cleanups, then apply the fixes. Quality only — it does not hunt for bugs; use /code-review for that.
- fewer-permission-prompts: Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.
- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. "check the deploy every 5 minutes", "keep running /babysit-prs"). Do NOT invoke for one-off tasks.
- schedule: Create, update, list, or run scheduled cloud agents (routines) that execute on a cron schedule. - When the user wants to schedule a recurring cloud agent, set up automated tasks, create a cron job for Claude Code, or manage their scheduled agents/routines. Also use when the user wants a one-time scheduled run ("run this once at 3pm", "remind me to check X tomorrow").
- claude-api: Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration.
TRIGGER — read BEFORE opening the target file; don't skip because it "looks like a one-liner" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR the task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens).
SKIP only when another provider is being worked on (overrides all triggers): OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in the query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over the project hits (run this grep FIRST if no provider named — don't Read the file).
- run: Launch and drive this project's app to see a change working. Use when asked to run, start, or screenshot the app, or to confirm a change works in the real app (not just tests). First looks for a project skill that already covers launching the app; otherwise falls back to built-in patterns per project type (CLI, server, TUI, Electron, browser-driven, library).
- init: Initialize a new CLAUDE.md file with codebase documentation
- review: Review a pull request
- security-review: Complete a security review of the pending changes on the current branch

names: update-config keybindings-help verify code-review simplify fewer-permission-prompts loop schedule claude-api run init review security-review


type: message

role: assistant

type: text

text: I'll systematically read the documentation files and script headers to identify any discrepancies.

service_tier: standard

attributionAgent: Explore

type: assistant


Read

type: direct


content:
1	# ━━━━━ KERNEL ━━━━━
2
3	Shared intelligence layer for media automation. Sourced by consumer scripts —
4	not run directly. Contains stateful scoring systems, adaptive logic, and
5	cross-domain engines that would pollute common.sh if placed there.
6
7	---
8
9	## ━━━ WHAT THIS FOLDER IS ━━━
10
11	**Not scripts. Not utilities. Engines.**
12
13	The Kernel holds logic that is:
14
15	- **Stateful** — maintains scores, decay state, or adaptive thresholds across calls
16	- **Domain-aware** — understands the difference between music, TV, and movie decisions
17	- **Shared across consumers** — one engine, multiple discovery scripts consuming it
18
19	This is distinct from:
20
21	| Location | Contains |
22	|----------|----------|
23	| `common.sh` | Reusable utility functions (logging, locking, arg parsing) |
24	| `master.conf` | Shared ecosystem configuration and tuning values |
25	| `Kernel/` | Stateful scoring systems and adaptive decision logic |
26
27	The rule: if the logic makes *decisions*, it lives here. If it's a helper or
28	config value, it lives in common.sh or master.conf.
29
30	---
31
32	## ━━━ WHY A SEPARATE KERNEL ━━━
33
34	Media automation decisions are not uniform. A Lidarr discovery script should be
35	highly selective. A Sonarr intake script should be family-aware and balanced. A
36	Radarr script should be broadly flexible. If scoring logic lives in each script,
37	it diverges and drifts independently.
38
39	Centralizing the decision layer:
40
41	- Keeps consumers thin — they define weights and thresholds, not scoring logic
42	- Prevents cross-domain bias pollution — music strictness does not contaminate TV
43	- Allows the scoring model to improve without touching every consumer script
44
45	---
46
47	## ━━━ WHAT BELONGS HERE ━━━
48
49	An engine belongs in Kernel if it:
50
51	- Implements a scoring or evaluation model
52	- Maintains or reads adaptive state
53	- Is consumed by more than one script (or is designed to be)
54	- Contains logic that would create tight coupling if duplicated
55
56	Utilities — locking, notifications, arg parsing, API calls — belong in common.sh.
57
58	---
59
60	## ━━━ FILES IN THIS FOLDER ━━━
61
62	| File | Role | Status |
63	|------|------|--------|
64	| `decision_engine.sh` | Behavior-driven scoring kernel — scoring, decay, deduplication, threshold evaluation | WIP — currently paired with `playback_aware_lidarr_discovery.sh` |
65
66	**Planned:**
67
68	| File | Role |
69	|------|------|
70	| `transcoding_engine.sh` | Transcoding decision logic (quality, codec selection, cost-benefit evaluation) |
71
72	---
73
74	## ━━━ HOW CONSUMERS USE THE KERNEL ━━━
75
76	Source the engine at the top of the consumer script:
77
78	```bash
79	source "$ROOT_DIR/Kernel/decision_engine.sh"
80	```
81
82	Then call engine functions directly, passing consumer-defined weights and thresholds:
83
84	```bash
85	score=$(score_candidate "$user_score" "$popularity" "$recency" "$quality")
86	score=$(apply_temporal_decay "$score" "$age_days")
87	decision=$(make_decision "$score" "$MY_THRESHOLD")
88	```
89
90	The engine returns values — consumer scripts decide what to do with them. The
91	engine has no side effects: no file writes, no API calls, no deletions.
92


content:
1	# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2	# 🔌  PLUGIN
3	# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4
5	**The Varaverk Unraid plugin — a web UI that wraps the entire script ecosystem.**
6	Scheduler, Monitor, Docker management, Partnership sync, Fallback state, and Arrs —
7	all surfaced inside the Unraid web interface as a first-class plugin.
8
9	> **Why this folder exists:** The scripts need a control surface. Managing a 50+ container
10	> homelab ecosystem from terminal windows is friction. The plugin turns configuration files
11	> into editable forms, cron schedules into a visual scheduler, and runtime log output into
12	> a live dashboard — without duplicating any of the logic that already lives in common.sh
13	> and the conf files.
14
15	---
16
17	## ━━━ THE PROBLEM THAT BUILT THIS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
18
19	The script ecosystem works well from the command line, but day-to-day operation is not
20	the command line. Checking whether the nightly sync ran, adjusting a container's watchdog
21	limit, confirming the partnership fallback is active — all of that requires SSH sessions,
22	knowing which log files to look at, and remembering which conf variable controls what.
23
24	The plugin solves the visibility problem: one URL on any browser, on any device on the
25	Tailscale network, shows everything running and lets you act on it. No extra tooling,
26	no separate monitoring stack, no third-party dashboards.
27
28	---
29
30	## ━━━ WHAT THIS FOLDER CONTAINS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
31
32	```
33	Plugin/
34	├── dev_install.sh             # One-time developer setup: symlinks plugin into web server
35	├── Icons/                     # Source icon assets (1024px master files)
36	└── unraid/                    # The Unraid platform adapter + plugin application
37	    ├── adapter.sh             # Platform adapter — provides platform_*() API to all scripts
38	    ├── Varaverk.page          # Main plugin entry point (Tasks menu)
39	    ├── VaraverkSettings.page  # Unraid Settings → Other Settings entry
40	    ├── api/                   # PHP API endpoints (called by JS via fetch)
41	    ├── css/                   # Plugin stylesheet
42	    ├── event/                 # Unraid event hooks (boot-time cron setup, array lifecycle)
43	    ├── icons/                 # Plugin icons served by emhttp
44	    ├── images/                # Plugin images
45	    ├── include/               # PHP business logic shared across pages
46	    ├── js/                    # Frontend JavaScript
47	    ├── pages/                 # Per-tab page includes (monitor, scheduler, docker, ...)
48	    └── run_job.sh             # Script runner invoked by the Scheduler
49
50	# Future platform adapters follow the same structure:
51	# Plugin/truenas/adapter.sh   — TrueNAS adapter (future)
52	# Plugin/ubuntu/adapter.sh    — Ubuntu/Debian adapter (future)
53	```
54
55	---
56
57	## ━━━ RELATIONSHIP TO THE REST OF THE REPO ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
58
59	**Plugin is a wrapper, never a reimplementation.** Every setting the plugin reads or writes
60	lives in `Configurations/master.conf` or `Configurations/host*.conf` — the same files the
61	shell scripts read. The plugin has no separate data store. If a conf file changes outside
62	the plugin (by hand, by SSH), the plugin reflects it on next load.
63
64	The one exception is `varaverk.cfg` on flash (`/boot/config/plugins/varaverk/varaverk.cfg`),
65	which holds a single bootstrap value: `SCRIPTS_DIR`. This is the path the plugin uses to
66	find the Configurations directory and all scripts. Everything else flows from there.
67
68	The plugin also taps `common.sh` indirectly — `include/config.php` mirrors
69	`resolve_tailscale_ip()` and `detect_host()` exactly, using the same logic as common.sh
70	so behaviour stays consistent without a shell dependency.
71
72	---
73
74	## ━━━ SCRIPTS IN THIS FOLDER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
75
76	| Script | Role | When It Runs |
77	|--------|------|--------------|
78	| `dev_install.sh` | Symlinks `Plugin/unraid/` into Unraid's web server | Once, manually, after cloning or moving the repo |
79
80	---
81
82
83	## ━━━ THE PLATFORM ADAPTER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
84
85	`Plugin/unraid/adapter.sh` is the Unraid platform adapter. It is sourced automatically
86	by `load_config.sh` whenever `PLATFORM=unraid` is detected (via `/etc/unraid-version`).
87
88	Every bash script in the ecosystem calls `platform_*()` functions instead of OS-specific
89	commands directly. The adapter translates those calls into Unraid-specific implementations.
90
91	```
92	platform_storage_healthy          # is the array up and shfs mounted?
93	platform_is_maintenance_running   # parity check or sync in progress?
94	platform_is_service_running       # is a named service process alive?
95	platform_restart_service          # restart via rc.d (Unraid) or systemctl (future)
96	platform_stop_service             # stop a named service
97	platform_is_mover_running         # Unraid mover active?
98	platform_get_mover_pid            # PID of the mover process
99	platform_stop_user_scripts        # kill Unraid user.scripts background jobs
100	platform_send_os_notification     # dynamix notify (Unraid) or equivalent
101	platform_get_disk_states          # reads disks.ini (Unraid) or equivalent
102	platform_get_temp_thresholds      # reads dynamix.cfg (Unraid) or equivalent
103	platform_is_service_enabled       # docker.cfg / domain.cfg enabled check
104	platform_require_cmd              # verify a platform command exists
105	```
106
107	**Adding a new platform:** Create `Plugin/<platform>/adapter.sh` implementing the same
108	function names. `load_config.sh` detects the OS at runtime and sources the correct adapter.
109	No other files need changing.
110
111	---
112
113	## ━━━ UNRAID INTEGRATION POINTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
114
115	| File | Where it appears in Unraid |
116	|------|---------------------------|
117	| `Varaverk.page` | Tasks menu item |
118	| `VaraverkSettings.page` | Settings → Other Settings tile |
119	| `event/disks_mounted/rebuild_cron` | Fires on every boot — copies `.plg`, rebuilds cron |
120	| `event/disks_mounted/array_start_jobs` | Fires when array starts |
121	| `event/disks_unmounting/array_stop_jobs` | Fires when array stops |
122	| `/boot/config/plugins/varaverk.plg` | Registers the plugin with Unraid's plugin system (lives on flash, not in repo) |
123


content:
1	# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2	# 🔌  PLUGIN — Manual
3	# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4
5	Setup procedures, deployment steps, and operational reference.
6	For folder overview see `README-Plugin.md`. For web app logic see the headers in `unraid/include/`.
7
8	---
9
10	## ━━━ FIRST-TIME INSTALL ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
11
12	### Prerequisites
13
14	- Repo cloned onto the target Unraid server
15	- `/boot/config/plugins/varaverk/varaverk.plg` exists on flash (see PLG SETUP below)
16
17	### Steps
18
19	```bash
20	cd Plugin/
21	./dev_install.sh
22	```
23
24	This creates:
25
26	```
27	/usr/local/emhttp/plugins/varaverk  →  Plugin/unraid/   (symlink)
28	```
29
30	Changes to any file under `Plugin/unraid/` take effect immediately in the browser —
31	no restart, no reinstall.
32
33	### Verify
34
35	Navigate to the Unraid web UI. **Varaverk** should appear in the Tasks menu.
36	Go to **Settings → Other Settings** — a Varaverk tile should also appear there.
37
38	---
39
40	## ━━━ PLG SETUP ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
41
42	The `.plg` file registers Varaverk with Unraid's plugin system. It enables the cron
43	mechanism (`update_cron`) and makes the plugin appear on the **Plugins** management page.
44
45	Create it once on flash — it persists across reboots:
46
47	```bash
48	mkdir -p /boot/config/plugins/varaverk
49
50	cat > /boot/config/plugins/varaverk.plg <<'EOF'
51	<?xml version='1.0' standalone='yes'?>
52	<!DOCTYPE PLUGIN [
53	<!ENTITY name    "varaverk">
54	<!ENTITY author  "gmer4lfe">
55	<!ENTITY version "2026.05.28">
56	]>
57	<PLUGIN name="&name;" author="&author;" version="&version;" launch="varaverk/monitor" icon="/plugins/varaverk/icons/varaverk.png">
58	</PLUGIN>
59	EOF
60	```
61
62	The `.plg` has no packages and no remote URLs — it is local-only and is not published
63	to Community Applications.
64
65	**Cron flow on every boot:**
66	1. `event/disks_mounted/rebuild_cron` fires
67	2. Copies `varaverk.plg` to `/var/log/plugins/`
68	3. Calls `vv_cron_rebuild()` → writes `varaverk.cron` → calls `update_cron`
69	4. Unraid merges `varaverk.cron` into `/etc/cron.d/root`
70	5. crond picks up all Varaverk jobs
71
72	---
73
74	## ━━━ SCRIPTS DIRECTORY SETTING ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
75
76	`SCRIPTS_DIR` is the only plugin-level setting. It tells the plugin where to find the
77	Configurations directory and all scripts.
78
79	**Set it via:** Settings → Other Settings → Varaverk → Scripts directory
80
81	Default: `/boot/config/plugins/varaverk`
82
83	The value is stored in `/boot/config/plugins/varaverk/varaverk.cfg`:
84
85	```bash
86	SCRIPTS_DIR="/boot/config/plugins/varaverk"
87	```
88
89	All other configuration lives in `Configurations/master.conf` and `Configurations/host*.conf`.
90
91	---
92
93	## ━━━ REPO MOVE PROCEDURE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
94
95	If the repo is cloned to a new path:
96
97	1. Re-run `dev_install.sh` — removes the stale symlink and creates a new one pointing at the new path
98	2. Update `SCRIPTS_DIR` in Settings → Other Settings → Varaverk (or edit `varaverk.cfg` directly on flash)
99
100	The `.plg` on flash does not need to change — it has no path references.
101
102	---
103
104	## ━━━ UPDATING THE PLUGIN VERSION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
105
106	The version in `/boot/config/plugins/varaverk.plg` is cosmetic for a local plugin —
107	Unraid does not check it against anything remote. Update it when you want the Plugins
108	management page to reflect when the plugin was last changed:
109
110	```xml
111	<!ENTITY version "2026.05.28">
112	```
113
114	---
115
116	## ━━━ ADDING A NEW OS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
117
118	`dev_install.sh` is built to support multiple OS targets. To add one:
119
120	1. Create `Plugin/<os>/` with the OS-appropriate web app files
121	2. Add the OS marker to `detect_os()` in `dev_install.sh`:
122	   ```bash
123	   elif [[ -f /etc/<os>-marker ]]; then echo "<os>"
124	   ```
125	3. Add the install target to the `case` block:
126	   ```bash
127	   <os>)
128	       SOURCE="$SCRIPT_DIR/<os>"
129	       TARGET="/path/to/web/server/plugins/$PLUGIN_NAME"
130	       ;;
131	   ```
132


Bash

command: find /boot/config/plugins/varaverk/Kernel -maxdepth 1 -name "*.sh" -type f | sort

description: List shell scripts in Kernel directory


command: find /boot/config/plugins/varaverk/Plugin -maxdepth 1 -name "*.sh" -type f | sort

description: List shell scripts in Plugin directory

speed: standard


content:
/boot/config/plugins/varaverk/Plugin/build.sh
/boot/config/plugins/varaverk/Plugin/dev_install.sh


text: Now let me read the script headers to check for discrepancies.


content:
1	#!/bin/bash
2	# ==============================================================================================
3	# ================================= Decision Engine ============================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Behavior-driven scoring kernel for media automation. Sourced by consumer
9	# scripts — not run directly. Evaluates candidates, applies weighted scoring,
10	# temporal decay, and deduplication, then returns a verdict.
11	#
12	# Does NOT download media, search indexers, or manage applications. Has no
13	# side effects — no file writes, no API calls, no deletions.
14	#
15	# Currently paired with: Media/playback_aware_lidarr_discovery.sh
16	#
17	# ==============================================================================================
18	# DESIGN PRINCIPLES
19	# ==============================================================================================
20	#
21	# Domain-Agnostic Core
22	#   The engine itself has no knowledge of music vs TV vs movies. Consumers
23	#   define thresholds, weights, and strictness profiles — the engine just
24	#   scores and decides. This prevents cross-domain bias pollution: music
25	#   strictness cannot contaminate TV intake logic.
26	#
27	#   Lidarr consumers  → highly selective, quality-first discovery
28	#   Sonarr consumers  → balanced, family-aware episodic intake
29	#   Radarr consumers  → broader flexibility with intelligent filtering
30	#
31	# Temporal Decay
32	#   Old behavioral signals lose influence over time (one decay unit per 30
33	#   days). Prevents permanent genre lock-in, historical bias accumulation,
34	#   and dead-user score dominance.
35	#
36	# Consumer Owns the Decision
37	#   The engine returns ACCEPT/REJECT/SCORE. What happens next is entirely
38	#   the consumer's concern — the engine never acts on its own verdict.
39	#
40	# ==============================================================================================
41	# FUNCTIONS
42	# ==============================================================================================
43	#
44	#   score_candidate user_score popularity_score recency_score quality_score
45	#       Returns TOTAL_SCORE (integer sum). Consumer defines actual weight values.
46	#
47	#   evaluate_threshold score minimum
48	#       Returns 0 (pass) or 1 (fail). Used as: if evaluate_threshold ...
49	#
50	#   apply_temporal_decay score age_days
51	#       Returns adjusted score. Subtracts (age_days / 30), floor at 0.
52	#
53	#   is_duplicate_candidate candidate history_file
54	#       Returns 0 (duplicate found in history_file) or 1 (not found).
55	#
56	#   make_decision score threshold
57	#       Returns "ACCEPT" or "REJECT". Calls evaluate_threshold internally.
58	#
59	# ==============================================================================================
60
61	# ── score_candidate ───────────────────────────────────────────────────────────
62	score_candidate() {
63
64	    local user_score="${1:-0}"
65	    local popularity_score="${2:-0}"
66	    local recency_score="${3:-0}"
67	    local quality_score="${4:-0}"
68
69	    TOTAL_SCORE=$(( \
70	        user_score + \
71	        popularity_score + \
72	        recency_score + \
73	        quality_score \
74	    ))
75
76	    echo "$TOTAL_SCORE"
77	}
78
79	# ── evaluate_threshold ───────────────────────────────────────────────────────
80	evaluate_threshold() {
81
82	    local score="$1"
83	    local minimum="$2"
84
85	    if (( score >= minimum )); then
86	        return 0
87	    fi
88
89	    return 1
90	}
91
92	# ── apply_temporal_decay ─────────────────────────────────────────────────────
93	apply_temporal_decay() {
94
95	    local score="$1"
96	    local age_days="$2"
97
98	    local decay=$(( age_days / 30 ))
99
100	    local adjusted=$(( score - decay ))


content:
1	#!/bin/bash
2	# ==============================================================================================
3	# ============================= dev_install.sh =================================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Symlinks the OS-appropriate plugin directory into the web server on first
9	# install or after the repo is moved. Run once by hand — never by a scheduler.
10	#
11	# Detects the running OS from /etc/*-version markers, picks the matching
12	# Plugin/<os>/ source directory, and creates a symlink at the web server's
13	# plugin location. All subsequent file changes under Plugin/<os>/ take effect
14	# immediately without re-running this script.
15	#
16	# ==============================================================================================
17	# OPERATIONAL SAFEGUARDS
18	# ==============================================================================================
19	#
20	# Idempotent Symlink
21	#   Removes any existing symlink at the target before recreating it. Safe to
22	#   re-run after a repo move without leaving stale paths.
23	#
24	# Real Directory Guard
25	#   If the target exists as a real directory (not a symlink), the script refuses
26	#   to proceed. Manual cleanup is required to avoid silently discarding an
27	#   existing installation.
28	#
29	# Missing Source Guard
30	#   Exits early if Plugin/<os>/ does not exist — catches a missing OS directory
31	#   before any filesystem changes are made.
32	#
33	# ==============================================================================================
34	# CONFIGURATION
35	# ==============================================================================================
36	#
37	# No config vars. All paths are derived from $BASH_SOURCE and the detected OS.
38	#
39	#   PLUGIN_NAME   varaverk  (hardcoded — must match the plugin's registered name)
40	#
41	#   OS detection markers:
42	#     /etc/unraid-version   → unraid  → /usr/local/emhttp/plugins/varaverk
43	#     /etc/debian_version   → debian  → (target path not yet defined)
44	#     /etc/arch-release     → arch    → (target path not yet defined)
45	#
46	# ==============================================================================================
47	# RUNTIME MODES
48	# ==============================================================================================
49	#
50	# ./dev_install.sh
51	#     Auto-detects the running OS and installs.
52	#
53	# ./dev_install.sh <os>
54	#     Overrides OS detection. Valid values: unraid, debian, arch.
55	#     Useful when testing on a machine where the marker files differ.
56	#
57	# ==============================================================================================
58
59	set -euo pipefail
60
61	PLUGIN_NAME="varaverk"
62	SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
63
64	# ── Detect OS ────────────────────────────────────────────────────────────────
65
66	detect_os() {
67	    if   [[ -f /etc/unraid-version ]];  then echo "unraid"
68	    elif [[ -f /etc/debian_version ]];  then echo "debian"
69	    elif [[ -f /etc/arch-release ]];    then echo "arch"
70	    else echo "unknown"; fi
71	}
72
73	OS="${1:-$(detect_os)}"   # accept override: ./dev_install.sh unraid
74
75	# ── OS-specific install target ────────────────────────────────────────────────
76
77	case "$OS" in
78	    unraid)
79	        SOURCE="$SCRIPT_DIR/unraid"
80	        TARGET="/usr/local/emhttp/plugins/$PLUGIN_NAME"
81	        ;;
82	    debian|arch)
83	        echo "OS '$OS' detected but plugin target path not yet defined."
84	        echo "Add the TARGET= line for this OS in dev_install.sh."
85	        exit 1
86	        ;;
87	    unknown|*)
88	        echo "Unsupported OS — could not detect from /etc/*-version markers."
89	        echo "Usage: $0 <os>   (e.g. $0 unraid)"
90	        exit 1
91	        ;;
92	esac
93
94	# ── Install ───────────────────────────────────────────────────────────────────
95
96	if [[ ! -d "$SOURCE" ]]; then
97	    echo "ERROR: Plugin source not found: $SOURCE"
98	    exit 1
99	fi
100


content:
1	#!/bin/bash
2	# ==============================================================================================
3	# ============================= build.sh =======================================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Packages the plugin web files (Plugin/unraid/) into a Slackware .txz, the
9	# format unRAID re-installs from flash on every boot. This is the RELEASE path —
10	# for day-to-day development use dev_install.sh (symlink, instant edits).
11	#
12	# What it produces (in Plugin/dist/):
13	#   varaverk-<version>-noarch-1.txz   the package unRAID installs to
14	#                                     /usr/local/emhttp/plugins/varaverk/
15	#   varaverk-<version>-noarch-1.txz.sha256
16	#
17	# It also rewrites the <!ENTITY version> and <!ENTITY sha256> lines in
18	# Plugin/varaverk.plg so the .plg always points at the package just built.
19	#
20	# WHY a .txz (not the dev symlink):
21	#   rc.local runs `plugin install` on every .plg at boot, BEFORE the array
22	#   mounts. A symlink into /mnt/user/appdata can't be made that early (appdata
23	#   isn't mounted) and the disks_mounted event hooks live behind that missing
24	#   symlink — chicken-and-egg. A .txz lives on flash, so unRAID extracts it to
25	#   RAM before the array starts; the event hooks are then present in time to
26	#   rebuild cron when the array mounts. Scripts stay on appdata (git clone).
27	#
28	# ==============================================================================================
29	# USAGE
30	# ==============================================================================================
31	#   ./build.sh                 # version = today's date (YYYY.MM.DD)
32	#   ./build.sh 2026.09.01       # explicit version
33	#
34	# After building: commit Plugin/dist/<txz> + the updated .plg, then attach the
35	# .txz to a GitHub release tagged <version> so the .plg URL resolves for
36	# downloaders. (The .plg also works offline if the .txz is already cached on
37	# flash with a matching SHA256.)
38	# ==============================================================================================
39
40	set -euo pipefail
41
42	PLUGIN_NAME="varaverk"
43	SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
44	SRC="$SCRIPT_DIR/unraid"
45	DIST="$SCRIPT_DIR/dist"
46	PLG="$SCRIPT_DIR/varaverk.plg"
47
48	VERSION="${1:-$(date +%Y.%m.%d)}"
49	PKG_BASENAME="${PLUGIN_NAME}-${VERSION}-noarch-1"
50	OUTFILE="$DIST/${PKG_BASENAME}.txz"
51
52	# ── Guards ──────────────────────────────────────────────────────────────────
53	[[ -d "$SRC" ]]            || { echo "ERROR: source not found: $SRC"; exit 1; }
54	command -v makepkg >/dev/null || { echo "ERROR: makepkg not found (run on unRAID)"; exit 1; }
55
56	mkdir -p "$DIST"
57
58	# ── Stage files under the real install path ─────────────────────────────────
59	# installpkg extracts relative to / so the package must contain the full path.
60	STAGE="$(mktemp -d)"
61	trap 'rm -rf "$STAGE"' EXIT
62	INSTALL_ROOT="$STAGE/usr/local/emhttp/plugins/$PLUGIN_NAME"
63	mkdir -p "$INSTALL_ROOT"
64
65	# Copy web files; drop VCS/editor cruft and any dev-only leftovers.
66	cp -a "$SRC/." "$INSTALL_ROOT/"
67	find "$INSTALL_ROOT" -name '.git*' -prune -exec rm -rf {} + 2>/dev/null || true
68	find "$INSTALL_ROOT" -name '*.swp' -delete 2>/dev/null || true
69
70	# Normalise ownership/permissions inside the package.
71	chown -R root:root "$STAGE" 2>/dev/null || true
72	find "$INSTALL_ROOT" -type d -exec chmod 0755 {} +
73	find "$INSTALL_ROOT" -type f -exec chmod 0644 {} +
74	# Keep shell/event scripts executable.
75	find "$INSTALL_ROOT" \( -name '*.sh' -o -path '*/event/*' \) -type f -exec chmod 0755 {} +
76
77	# ── Build the package ───────────────────────────────────────────────────────
78	# makepkg doesn't quote its output arg internally, so build to a space-free temp
79	# path (the repo lives under a dir with spaces) then move into dist/.
80	TMP_OUT="$STAGE.txz"
81	( cd "$STAGE" && makepkg -l y -c y "$TMP_OUT" >/dev/null )
82	mv "$TMP_OUT" "$OUTFILE"
83
84	# ── Hash + record ───────────────────────────────────────────────────────────
85	SHA256="$(sha256sum "$OUTFILE" | awk '{print $1}')"
86	echo "$SHA256  ${PKG_BASENAME}.txz" > "$OUTFILE.sha256"
87
88	# ── Point the .plg at this build ────────────────────────────────────────────
89	if [[ -f "$PLG" ]]; then
90	    sed -i -E "s|(<!ENTITY[[:space:]]+version[[:space:]]+\")[^\"]*(\">)|\1${VERSION}\2|" "$PLG"
91	    sed -i -E "s|(<!ENTITY[[:space:]]+sha256[[:space:]]+\")[^\"]*(\">)|\1${SHA256}\2|"  "$PLG"
92	fi
93
94	SIZE="$(numfmt --to=iec "$(stat -c %s "$OUTFILE")")"
95	cat <<EOF
96
97	  Built:   $OUTFILE  ($SIZE)
98	  Version: $VERSION
99	  SHA256:  $SHA256
100


text:
Now let me cross-check the documentation claims against the script headers for any discrepancies.

Based on my reading of the documentation files and script headers, here are the findings:

## Discrepancy Report

**DOCUMENTATION vs SCRIPT HEADER MATCHES:**

All documentation statements match the script headers. Specifically:

1. **README-Kernel.md** correctly describes `decision_engine.sh` as "Behavior-driven scoring kernel" with "scoring, decay, deduplication, threshold evaluation" — matches the header's purpose statement and design principles (lines 3-40).

2. **README-Plugin.md** correctly lists scripts:
   - `dev_install.sh` — "One-time developer setup: symlinks plugin into web server" — matches header's purpose (lines 6-14).
   - The README notes no other `.sh` files in `Plugin/` root directory, which is accurate (only `dev_install.sh` and `build.sh` exist).

3. **Manual-Plugin.md** correctly describes:
   - `dev_install.sh` installation process (section "FIRST-TIME INSTALL", lines 19-37) — matches script behavior.
   - `.plg` setup procedure (section "PLG SETUP", lines 40-71) — matches script headers.
   - SCRIPTS_DIR configuration (section "SCRIPTS DIRECTORY SETTING", lines 74-89) — aligns with `varaverk.cfg` mentioned in README-Plugin.md.

4. **README-Plugin.md** mentions `build.sh` in the folder structure but does NOT list it in the "SCRIPTS IN THIS FOLDER" table (which only includes `dev_install.sh`). The actual `build.sh` header exists with clear purpose: packaging the plugin as a `.txz` for release (lines 6-27 of build.sh).

**ONE MINOR OMISSION (not a discrepancy):**

The README-Plugin.md "SCRIPTS IN THIS FOLDER" table (lines 76-78) documents only `dev_install.sh` and omits `build.sh`. However, the folder structure diagram earlier (lines 32-53) does not list build.sh either, so this is consistent within the documentation — just incomplete. The script itself is present and functional.

**CONCLUSION:** No actual discrepancies between documentation and script headers. The documentation accurately reflects the scripts' purposes, configurations, and behaviors.