From 94a4fbe45fe19e150f6a62ec2a9f150162161508 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 20:15:29 -0400 Subject: [PATCH 01/30] feat(config): add sub-category cascade evaluator --- functions/__fish_config_op_cascade.fish | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 functions/__fish_config_op_cascade.fish diff --git a/functions/__fish_config_op_cascade.fish b/functions/__fish_config_op_cascade.fish new file mode 100644 index 0000000..cf05228 --- /dev/null +++ b/functions/__fish_config_op_cascade.fish @@ -0,0 +1,63 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __fish_config_op_cascade [] +# +# DESCRIPTION +# Evaluates the opt-out cascade for one C1-C6 classification: an +# explicit truthy/falsy sub-category variable wins outright; otherwise +# falls back to the category variable; otherwise falls back to the +# master switch __fish_config_opinionated. A category in the opt-in +# list (currently just __fish_config_op_logging, C5) defaults to +# disabled when nothing in the chain is explicit, and the master switch +# cannot enable it -- this is data-driven here instead of a hardcoded +# string comparison, so any sub-category nested under an opt-in +# category inherits "off unless explicit" for free through the cascade, +# with no per-sub-category special-casing. +# +# ARGUMENTS +# category_variable Name (without $) of a C1-C6 category variable +# subcategory_variable Optional name (without $) of a sub-category +# variable nested under that category +# +# EXIT STATUS +# 0 Enabled +# 1 Disabled +# +# EXAMPLE +# __fish_config_op_cascade __fish_config_op_aliases +# __fish_config_op_cascade __fish_config_op_aliases __fish_config_op_aliases_filesystem +function __fish_config_op_cascade --description 'Evaluate the sub-category -> category -> master opt-out cascade' + set -l opt_in_categories __fish_config_op_logging + + set -l chain $argv[1] + if test (count $argv) -ge 2 -a -n "$argv[2]" + set chain $argv[2] $argv[1] + end + + for var_name in $chain + __fish_variable_check $var_name + set -l s $status + if test $s -eq 0 + return 0 + end + if test $s -eq 1 + return 1 + end + end + + # Every variable in the chain was unset/unrecognized. chain[-1] is + # always the category variable (present whether or not a + # sub-category was given) -- opt-in categories default to disabled + # and the master switch cannot override that. + if contains -- $chain[-1] $opt_in_categories + return 1 + end + + __fish_variable_check __fish_config_opinionated + if test $status -eq 1 + return 1 + end + return 0 +end From 7fa5ca25684430e10a4c85b54599a8c7a48cc5bc Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 20:19:42 -0400 Subject: [PATCH 02/30] feat(config): add component registry lookup helper --- .../__fish_config_op_registry_lookup.fish | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 functions/__fish_config_op_registry_lookup.fish diff --git a/functions/__fish_config_op_registry_lookup.fish b/functions/__fish_config_op_registry_lookup.fish new file mode 100644 index 0000000..ddee75a --- /dev/null +++ b/functions/__fish_config_op_registry_lookup.fish @@ -0,0 +1,38 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __fish_config_op_registry_lookup +# +# DESCRIPTION +# Looks up the ":" key in the generated component +# registry ($__fish_config_op_registry_keys / +# $__fish_config_op_registry_values, sourced from +# conf.d/__fish_config_op_registry.fish at shell startup) and prints its +# tags, one per line. is empty string for the default/unnamed +# site. +# +# ARGUMENTS +# identity Function name (status current-function) or file basename +# with any trailing .fish stripped (status basename) +# site Site slug, or empty string for the default site +# +# EXIT STATUS +# 0 Found: tags printed to stdout +# 1 Not found: nothing printed +# +# RETURNS +# Matching tags, one per line, printed to stdout +# +# EXAMPLE +# set -l tags (__fish_config_op_registry_lookup rm "") +# or return 0 # unclassified: caller treats this as always-on +function __fish_config_op_registry_lookup --description 'Look up the COMPONENT tags for an identity:site pair' + set -l key "$argv[1]:$argv[2]" + set -l idx (contains -i -- $key $__fish_config_op_registry_keys) + if test -z "$idx" + return 1 + end + string split ' ' -- $__fish_config_op_registry_values[$idx] + return 0 +end From 9642cd69db93995d76b3542b57cd8d14ea30dab1 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 20:23:49 -0400 Subject: [PATCH 03/30] feat(config): rewrite guard as self-identifying with C0 and site support --- functions/__fish_config_op_enabled.fish | 81 +++++++++++++------------ 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/functions/__fish_config_op_enabled.fish b/functions/__fish_config_op_enabled.fish index aa68f56..18f143c 100644 --- a/functions/__fish_config_op_enabled.fish +++ b/functions/__fish_config_op_enabled.fish @@ -2,63 +2,68 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # SYNOPSIS -# __fish_config_op_enabled +# __fish_config_op_enabled [] # # DESCRIPTION -# Guard predicate for opinionated components (AGENTS.md Task #3). -# The category variable is evaluated first via __fish_variable_check: -# an explicit truthy value (1/true/yes/on/y) enables the component -# regardless of the master switch; an explicit falsy value -# (0/false/no/off/n) disables it regardless of the master switch. -# Only when the category variable is unset or unrecognized (status 2 -# or 3) does the master switch __fish_config_opinionated apply: a -# falsy master disables every unset-category component at once. -# Unset master with unset category → enabled (active by default). +# Guard predicate for an opinionated component. is computed +# by the caller, never hand-typed as a category name: (status +# current-function) inside a function body, (status basename) at +# top-level conf.d/*.fish or config.fish code (fish has no API for a +# callee to introspect its own caller, so the caller must compute and +# pass its own identity -- see the spec's "A note on self-identifying"). +# A trailing .fish is stripped so a status-basename identity and a +# status-current-function identity land in the same key space. # -# One exception: __fish_config_op_logging (C5) is opt-in, because it -# writes terminal output to disk. Unset or unrecognized means disabled, -# and the master switch cannot enable it — only an explicit truthy -# value turns logging on. +# Looks up ":" (site defaults to the empty/unnamed site) +# in the generated component registry. No registry entry (unclassified, +# or a doc header with no # COMPONENT section) resolves to enabled -- +# the same fail-open default as an explicit `always/on` tag, so +# user-authored and third-party functions that never call this guard in +# the first place are unaffected, and one that somehow does is never +# silently broken by a missing header. A found `always/off` tag +# disables unconditionally; a found `always/on` tag enables +# unconditionally, short-circuiting before any other tagged +# sub-category is evaluated. Otherwise every tagged sub-category must +# pass the cascade (AND semantics). # # ARGUMENTS -# category_variable Name (without $) of the category opt-out variable: -# __fish_config_op_aliases, __fish_config_op_autoexec, -# __fish_config_op_overrides, -# __fish_config_op_integrations, -# __fish_config_op_logging, or -# __fish_config_op_greeting +# identity (status current-function) or (status basename) +# site Optional site slug (see # COMPONENT header grammar); +# omitted for the default/unnamed site # # EXIT STATUS -# 0 Component enabled (category explicitly truthy; or category unset and master not falsy — except C5 logging, which requires an explicit truthy value) -# 1 Component disabled (category explicitly falsy; or category unset and master falsy; or C5 logging unset; or no argument with falsy master) +# 0 Component enabled +# 1 Component disabled # # EXAMPLE -# if __fish_config_op_enabled __fish_config_op_aliases +# if not __fish_config_op_enabled (status current-function) # alias grep='grep --color=auto' # end -function __fish_config_op_enabled --description 'Check whether an opinionated component category is enabled' - __fish_variable_check $argv[1] - set -l cat_status $status +# if not __fish_config_op_enabled (status current-function) exit-plain +# builtin exit +# end +function __fish_config_op_enabled --description 'Guard for an opinionated component, identified by its own caller' + set -l identity (string replace -r '\.fish$' '' -- $argv[1]) + set -l site $argv[2] - if test $cat_status -eq 0 + set -l tags (__fish_config_op_registry_lookup $identity $site) + if test $status -ne 0 return 0 end - if test $cat_status -eq 1 + if contains -- always/off $tags return 1 end - - # C5 logging is opt-in: it writes terminal output to disk, so an unset or - # unrecognized value means off — the master switch cannot enable it. - if test "$argv[1]" = __fish_config_op_logging - return 1 + if contains -- always/on $tags + return 0 end - # Status 3 (garbage) defers to master — an unrecognized value is not an opt-out. - __fish_variable_check __fish_config_opinionated - if test $status -eq 1 - return 1 + for tag in $tags + set -l parts (string split -m 1 -- / $tag) + set -l category_var "__fish_config_op_$parts[1]" + set -l subcat_var "__fish_config_op_$parts[1]_$parts[2]" + __fish_config_op_cascade $category_var $subcat_var + or return 1 end - return 0 end From 31b47ddfc5ff818e6b04886faa83a10506c9e033 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 20:29:02 -0400 Subject: [PATCH 04/30] feat(docs): parse # COMPONENT headers in manualtools --- docs/manualtools.py | 77 ++++++++++++++++++++++++++++++++++++++++--- docs/verify-manual.py | 73 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 5 deletions(-) diff --git a/docs/manualtools.py b/docs/manualtools.py index 7103653..70054a2 100644 --- a/docs/manualtools.py +++ b/docs/manualtools.py @@ -62,6 +62,7 @@ HEADER_LABEL = re.compile(r"^#\s+([A-Z][A-Z ]*[A-Z])\s*$") FUNC_DEF = re.compile(r"^\s*function\s+(\S+)") SECTIONS = ( "CATEGORY", + "COMPONENT", "DEPENDENCIES", "SYNOPSIS", "DESCRIPTION", @@ -109,6 +110,19 @@ def _trailing_blanks(lines: list[str]) -> int: return n +def _block_identity(path: Path, lines: list[str], end: int, blocks_count: int) -> str: + """Resolve a header block's associated name. + + A file carrying exactly one header is associated with its own stem, so + a `function` nested inside a `type -q` guard still resolves. A file + with several headers walks forward to the next `function` definition. + """ + if blocks_count == 1: + return path.stem + after = (m.group(1) for ln in lines[end:] if (m := FUNC_DEF.match(ln))) + return next(after, path.stem) + + def parse_functions(root: Path) -> dict[str, dict[str, list[str]]]: """Parse the comment header above every documented public function. @@ -128,11 +142,7 @@ def parse_functions(root: Path) -> dict[str, dict[str, list[str]]]: lines = path.read_text(encoding="utf-8").split("\n") blocks = _header_blocks(lines) for end, sections in blocks: - if len(blocks) == 1: - name = path.stem - else: - after = (m.group(1) for ln in lines[end:] if (m := FUNC_DEF.match(ln))) - name = next(after, path.stem) + name = _block_identity(path, lines, end, len(blocks)) if name.startswith("_") or "CATEGORY" not in sections: continue out[name] = { @@ -185,6 +195,63 @@ def parse_abbreviations(root: Path) -> dict[str, list[dict]]: return out +SITE_LINE_RE = re.compile(r"^site\s+(\S+):\s*(\S+)$") + + +def parse_component_lines(lines: list[str]) -> list[tuple[str, str]]: + """Parse raw `# COMPONENT` body lines into (site, tag) pairs. + + A line of the form `site : ` scopes to that site; a + bare `` line belongs to the default (unnamed) site, keyed "". + Blank lines are skipped. + """ + out: list[tuple[str, str]] = [] + for line in lines: + line = line.strip() + if not line: + continue + m = SITE_LINE_RE.match(line) + if m: + out.append((m.group(1), m.group(2))) + else: + out.append(("", line)) + return out + + +def _parse_component_blocks(path: Path) -> dict[str, list[str]]: + """Parse every `# COMPONENT` header block in one file. + + Unlike parse_functions, there is no `# CATEGORY` gate and no + underscore exclusion: component classification applies to every + function/script, public or private, documented in the manual or not + -- the registry needs to see every guarded identity, not just the + ones that appear in the public function reference. + """ + lines = path.read_text(encoding="utf-8").split("\n") + blocks = _header_blocks(lines) + out: dict[str, list[str]] = {} + for end, sections in blocks: + if "COMPONENT" not in sections: + continue + name = _block_identity(path, lines, end, len(blocks)) + body = sections["COMPONENT"] + out[name] = body[: len(body) - _trailing_blanks(body)] + return out + + +def parse_component_file(path: Path) -> dict[str, list[str]]: + """Parse `# COMPONENT` header block(s) in one specific file (e.g. config.fish).""" + return _parse_component_blocks(path) + + +def parse_components(root: Path) -> dict[str, list[str]]: + """Parse `# COMPONENT` header blocks across every `*.fish` file under root.""" + out: dict[str, list[str]] = {} + for path in sorted(root.glob("*.fish")): + out.update(_parse_component_blocks(path)) + return out + + def _sort_key(entry: Path) -> tuple: """Order by sidebar.order when present, else by filename. Stable.""" target = entry / "index.md" if entry.is_dir() else entry diff --git a/docs/verify-manual.py b/docs/verify-manual.py index d242012..ae5810b 100644 --- a/docs/verify-manual.py +++ b/docs/verify-manual.py @@ -827,6 +827,79 @@ def test_site_avoids_reserved_dir(): ) +def test_parse_component_lines_default_and_named_sites(): + lines = [ + "aliases/filesystem", + "site exit-plain: overrides/key-bindings", + "site logging-guard: logging/terminal-capture", + "", + " ", + ] + got = mt.parse_component_lines(lines) + assert got == [ + ("", "aliases/filesystem"), + ("exit-plain", "overrides/key-bindings"), + ("logging-guard", "logging/terminal-capture"), + ], f"unexpected parse: {got}" + + +def test_parse_components_includes_underscore_prefixed_and_uncategorised(): + """Unlike parse_functions, parse_components has no # CATEGORY gate and + no underscore exclusion -- every guarded identity must be visible.""" + with tempfile.TemporaryDirectory() as d: + root = Path(d) + (root / "__private_helper.fish").write_text( + "# COMPONENT\n" + "# logging/terminal-capture\n" + "function __private_helper\n" + "end\n" + ) + (root / "no_category.fish").write_text( + "# COMPONENT\n" + "# aliases/filesystem\n" + "#\n" + "# SYNOPSIS\n" + "# no_category\n" + "function no_category\n" + "end\n" + ) + got = mt.parse_components(root) + assert got["__private_helper"] == ["logging/terminal-capture"] + assert got["no_category"] == ["aliases/filesystem"] + + +def test_parse_components_resolves_multi_header_file_to_function_name(): + with tempfile.TemporaryDirectory() as d: + root = Path(d) + (root / "multi.fish").write_text( + "# COMPONENT\n" + "# aliases/filesystem\n" + "function first_fn\n" + "end\n" + "\n" + "# COMPONENT\n" + "# aliases/network\n" + "function second_fn\n" + "end\n" + ) + got = mt.parse_components(root) + assert got == { + "first_fn": ["aliases/filesystem"], + "second_fn": ["aliases/network"], + }, f"unexpected resolution: {got}" + + +def test_parse_component_file_single_file(): + with tempfile.TemporaryDirectory() as d: + path = Path(d) / "config.fish" + path.write_text( + "# COMPONENT\n" + "# site greeting-block: greeting/greeting-message\n" + ) + got = mt.parse_component_file(path) + assert got == {"config": ["site greeting-block: greeting/greeting-message"]} + + TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")] From c0628dbd4cab07e1ca5b7320e6958d6bc544dabe Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 20:34:48 -0400 Subject: [PATCH 05/30] feat(docs): add component registry generator --- docs/generate_component_registry.py | 101 ++++++++++++++++++++++++++++ docs/verify-manual.py | 69 +++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 docs/generate_component_registry.py diff --git a/docs/generate_component_registry.py b/docs/generate_component_registry.py new file mode 100644 index 0000000..712290b --- /dev/null +++ b/docs/generate_component_registry.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Generate the committed opinionated-component registry. + +Walks every `# COMPONENT` header in functions/*.fish, conf.d/*.fish, and +config.fish and writes conf.d/__fish_config_op_registry.fish, the fish +data file __fish_config_op_registry_lookup reads at shell startup. + +Run manually (via __fish_config_op_registry_rebuild) after editing a +# COMPONENT header, and automatically as a pre-step in build-manual.py +before the manual is built. +""" + +import sys +from pathlib import Path + +import manualtools as mt + +DOCS = Path(__file__).parent +REPO = DOCS.parent +OUTPUT = REPO / "conf.d" / "__fish_config_op_registry.fish" + + +def collect_components() -> dict[str, list[str]]: + """Gather every `# COMPONENT` header across the whole repo.""" + out = mt.parse_components(REPO / "functions") + out.update(mt.parse_components(REPO / "conf.d")) + out.update(mt.parse_component_file(REPO / "config.fish")) + return out + + +def build_registry(components: dict[str, list[str]]) -> tuple[dict[str, list[str]], list[str]]: + """Turn {identity: [raw COMPONENT lines]} into ({"identity:site": [tags]}, warnings). + + A site with both always/on and always/off tagged is a contradiction: + both are stripped and a warning is emitted, but generation continues + -- any other real tag on that same site survives. A site whose + effective tag set is empty after stripping produces no registry entry + at all, which __fish_config_op_enabled already treats as always/on + (fail-open) at guard time -- see spec §4.5. + """ + registry: dict[str, list[str]] = {} + warnings: list[str] = [] + for identity, raw_lines in components.items(): + by_site: dict[str, list[str]] = {} + for site, tag in mt.parse_component_lines(raw_lines): + by_site.setdefault(site, []).append(tag) + for site, tags in by_site.items(): + if "always/on" in tags and "always/off" in tags: + label = identity if not site else f"{identity}:{site}" + warnings.append( + f"{label}: both always/on and always/off tagged; ignoring both" + ) + tags = [t for t in tags if t not in ("always/on", "always/off")] + if tags: + registry[f"{identity}:{site}"] = tags + return registry, warnings + + +def render(registry: dict[str, list[str]]) -> str: + keys = sorted(registry) + lines = [ + "# Copyright (C) 2026 Rootiest", + "# SPDX-License-Identifier: AGPL-3.0-or-later", + "#", + "# GENERATED FILE --- do not edit by hand.", + "# Regenerate with __fish_config_op_registry_rebuild after editing a", + "# # COMPONENT header, or automatically via docs/build-manual.py.", + "# Source: docs/generate_component_registry.py", + "", + ] + if not keys: + lines.append("set -g __fish_config_op_registry_keys") + lines.append("set -g __fish_config_op_registry_values") + return "\n".join(lines) + "\n" + + lines.append("set -g __fish_config_op_registry_keys \\") + lines += [f" {k} \\" for k in keys[:-1]] + [f" {keys[-1]}"] + lines.append("") + + values = ['"' + " ".join(registry[k]) + '"' for k in keys] + lines.append("set -g __fish_config_op_registry_values \\") + lines += [f" {v} \\" for v in values[:-1]] + [f" {values[-1]}"] + lines.append("") + return "\n".join(lines) + "\n" + + +def main() -> int: + components = collect_components() + registry, warnings = build_registry(components) + for w in warnings: + print(f" WARN {w}", file=sys.stderr) + OUTPUT.write_text(render(registry)) + print(f"wrote {OUTPUT} ({len(registry)} entries)") + return 0 + + +if __name__ == "__main__": + sys.path.insert(0, str(Path(__file__).parent)) + raise SystemExit(main()) diff --git a/docs/verify-manual.py b/docs/verify-manual.py index ae5810b..c4067f3 100644 --- a/docs/verify-manual.py +++ b/docs/verify-manual.py @@ -900,6 +900,75 @@ def test_parse_component_file_single_file(): assert got == {"config": ["site greeting-block: greeting/greeting-message"]} +def test_build_registry_strips_on_off_contradiction_with_warning(): + import generate_component_registry as gcr + + components = {"contradictory_fn": ["always/on", "always/off", "aliases/filesystem"]} + registry, warnings = gcr.build_registry(components) + assert registry["contradictory_fn:"] == ["aliases/filesystem"], ( + f"the non-contradictory tag should survive: {registry}" + ) + assert len(warnings) == 1 and "contradictory_fn" in warnings[0] + + +def test_build_registry_drops_empty_effective_tag_sets(): + import generate_component_registry as gcr + + components = {"only_contradictory": ["always/on", "always/off"]} + registry, warnings = gcr.build_registry(components) + assert "only_contradictory:" not in registry, ( + "a site stripped down to nothing must produce no registry entry " + "(fail-open: absence of an entry already means always/on at guard time)" + ) + assert len(warnings) == 1 + + +def test_build_registry_keeps_sites_independent(): + import generate_component_registry as gcr + + components = { + "smart_exit": [ + "site exit-plain: overrides/key-bindings", + "site logging-guard: logging/terminal-capture", + ] + } + registry, warnings = gcr.build_registry(components) + assert registry["smart_exit:exit-plain"] == ["overrides/key-bindings"] + assert registry["smart_exit:logging-guard"] == ["logging/terminal-capture"] + assert not warnings + + +def test_render_registry_is_valid_fish_and_round_trips(): + """Sourcing render()'s output must leave the two arrays in the exact + shape __fish_config_op_registry_lookup expects -- checked via the real + lookup helper (functions/__fish_config_op_registry_lookup.fish, Task + 2) rather than re-parsing the generated text by hand.""" + import subprocess + + import generate_component_registry as gcr + + registry = { + "rm:": ["aliases/filesystem"], + "smart_exit:exit-plain": ["overrides/key-bindings"], + } + text = gcr.render(registry) + + repo = Path(__file__).parent.parent + proc = subprocess.run( + [ + "fish", "-c", + f"source {repo}/functions/__fish_config_op_registry_lookup.fish; " + "source /dev/stdin; " + "__fish_config_op_registry_lookup rm ''; echo status=$status", + ], + input=text, + capture_output=True, + text=True, + ) + assert "aliases/filesystem" in proc.stdout, f"unexpected output: {proc.stdout!r} {proc.stderr!r}" + assert "status=0" in proc.stdout, f"lookup did not report found: {proc.stdout!r}" + + TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")] From 9d6923f2258b01a0646364f617f1ea37bd2d2f29 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 20:42:04 -0400 Subject: [PATCH 06/30] fix(docs): merge, not overwrite, COMPONENT lines on identity collision collect_components() used dict.update(), which let conf.d silently overwrite functions/ (or vice versa) when the same bare identity appears in both, e.g. functions/auto-pull.fish and conf.d/auto-pull.fish. The runtime guard can only ever look up the bare status current-function/basename string, so both call sites genuinely share one identity and their raw COMPONENT lines must be concatenated, not replaced. --- docs/generate_component_registry.py | 21 +++++++++++++---- docs/verify-manual.py | 36 +++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/docs/generate_component_registry.py b/docs/generate_component_registry.py index 712290b..2c0b572 100644 --- a/docs/generate_component_registry.py +++ b/docs/generate_component_registry.py @@ -23,10 +23,23 @@ OUTPUT = REPO / "conf.d" / "__fish_config_op_registry.fish" def collect_components() -> dict[str, list[str]]: - """Gather every `# COMPONENT` header across the whole repo.""" - out = mt.parse_components(REPO / "functions") - out.update(mt.parse_components(REPO / "conf.d")) - out.update(mt.parse_component_file(REPO / "config.fish")) + """Gather every `# COMPONENT` header across the whole repo. + + Concatenates raw component lines when the same identity appears in + more than one source (e.g. functions/auto-pull.fish and + conf.d/auto-pull.fish both self-identify as "auto-pull" at runtime, + since the guard can only ever look up the bare status + current-function/basename string) rather than letting one silently + overwrite the other. + """ + out: dict[str, list[str]] = {} + for source in ( + mt.parse_components(REPO / "functions"), + mt.parse_components(REPO / "conf.d"), + mt.parse_component_file(REPO / "config.fish"), + ): + for identity, lines in source.items(): + out.setdefault(identity, []).extend(lines) return out diff --git a/docs/verify-manual.py b/docs/verify-manual.py index c4067f3..5d6d449 100644 --- a/docs/verify-manual.py +++ b/docs/verify-manual.py @@ -938,6 +938,42 @@ def test_build_registry_keeps_sites_independent(): assert not warnings +def test_collect_components_merges_identity_collisions_across_sources(): + """functions/auto-pull.fish and conf.d/auto-pull.fish both self-identify + as "auto-pull" at runtime -- the guard only ever has the bare + status current-function/basename string to look up with -- so + collect_components must concatenate their raw COMPONENT lines + rather than letting conf.d's entry silently overwrite functions'.""" + import generate_component_registry as gcr + + with tempfile.TemporaryDirectory() as d: + root = Path(d) + (root / "functions").mkdir() + (root / "conf.d").mkdir() + (root / "functions" / "auto-pull.fish").write_text( + "# COMPONENT\n" + "# autoexec/sync\n" + "function auto-pull\n" + "end\n" + ) + (root / "conf.d" / "auto-pull.fish").write_text( + "# COMPONENT\n" + "# autoexec/sync\n" + ) + (root / "config.fish").write_text("") + + orig_repo = gcr.REPO + try: + gcr.REPO = root + got = gcr.collect_components() + finally: + gcr.REPO = orig_repo + + assert got["auto-pull"] == ["autoexec/sync", "autoexec/sync"], ( + f"both sources' tags should survive the merge, not overwrite: {got}" + ) + + def test_render_registry_is_valid_fish_and_round_trips(): """Sourcing render()'s output must leave the two arrays in the exact shape __fish_config_op_registry_lookup expects -- checked via the real From d2e07effc1fb04ff3f4f6cfb0aa4f4491c98a256 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 20:46:55 -0400 Subject: [PATCH 07/30] feat(config): add manual registry-rebuild command --- .../__fish_config_op_registry_rebuild.fish | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 functions/__fish_config_op_registry_rebuild.fish diff --git a/functions/__fish_config_op_registry_rebuild.fish b/functions/__fish_config_op_registry_rebuild.fish new file mode 100644 index 0000000..d9c7f9f --- /dev/null +++ b/functions/__fish_config_op_registry_rebuild.fish @@ -0,0 +1,30 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __fish_config_op_registry_rebuild +# +# DESCRIPTION +# Regenerates conf.d/__fish_config_op_registry.fish from every +# # COMPONENT header in functions/*.fish, conf.d/*.fish, and +# config.fish, then re-sources it into the current session so the +# change takes effect immediately. Run manually after editing a +# # COMPONENT header; never run automatically at shell startup -- +# parsing every header on every new shell would be wasted work on +# every session that isn't actively editing a header. +# +# EXIT STATUS +# 0 Registry regenerated +# 1 python3 is not available, or generation failed +# +# EXAMPLE +# __fish_config_op_registry_rebuild +function __fish_config_op_registry_rebuild --description 'Regenerate the opinionated-component registry from # COMPONENT headers' + if not type -q python3 + echo "__fish_config_op_registry_rebuild: python3 not found" >&2 + return 1 + end + python3 "$__fish_config_dir/docs/generate_component_registry.py" + or return 1 + source "$__fish_config_dir/conf.d/__fish_config_op_registry.fish" +end From d748289c115d5a819786a4b833dde9811664779d Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 20:49:42 -0400 Subject: [PATCH 08/30] feat(docs): regenerate component registry before building the manual --- docs/build-manual.py | 3 +++ docs/verify-manual.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/build-manual.py b/docs/build-manual.py index 7b3070d..5f1227d 100644 --- a/docs/build-manual.py +++ b/docs/build-manual.py @@ -15,6 +15,7 @@ import sys from pathlib import Path import manualtools as mt +import generate_component_registry DOCS = Path(__file__).parent MANUAL = DOCS / "manual" @@ -840,6 +841,8 @@ def main() -> int: if not (args.concat or args.site): ap.error("nothing to do: pass --concat and/or --site") + generate_component_registry.main() + if args.site: src = DOCS / "site" / "src" out = src / "content" / "docs" diff --git a/docs/verify-manual.py b/docs/verify-manual.py index 5d6d449..8626168 100644 --- a/docs/verify-manual.py +++ b/docs/verify-manual.py @@ -1005,6 +1005,16 @@ def test_render_registry_is_valid_fish_and_round_trips(): assert "status=0" in proc.stdout, f"lookup did not report found: {proc.stdout!r}" +def test_build_manual_regenerates_registry_before_building(): + """docs/build-manual.py must regenerate the registry as a pre-step.""" + import build_manual + + assert hasattr(build_manual, "generate_component_registry"), ( + "build-manual.py must import generate_component_registry so its " + "main() can be called as a pre-step before --site/--concat run" + ) + + TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")] From 2cf61b0590d06786c867798c67ade4181edfb939 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 20:55:36 -0400 Subject: [PATCH 09/30] docs: author the sub-category taxonomy and C0 explanation --- .../01-c1-command-shadows.md | 32 +++++++++++++++++++ .../02-c2-startup-side-effects.md | 25 +++++++++++++++ .../03-c3-key-and-environment-overrides.md | 20 ++++++++++++ .../04-c4-terminal-and-tool-integration.md | 25 +++++++++++++++ .../05-c5-logging-and-capture.md | 19 +++++++++++ .../06-c6-greeting-and-first-run-ui.md | 13 ++++++++ docs/manual/08-components-reference/index.md | 11 +++++++ 7 files changed, 145 insertions(+) diff --git a/docs/manual/08-components-reference/01-c1-command-shadows.md b/docs/manual/08-components-reference/01-c1-command-shadows.md index c316630..eb3b5fb 100644 --- a/docs/manual/08-components-reference/01-c1-command-shadows.md +++ b/docs/manual/08-components-reference/01-c1-command-shadows.md @@ -31,3 +31,35 @@ all of these commands. When C1 is disabled, `rm` uses bare `command rm` with no wrapper — files are permanently deleted, not trashed. There is no intermediate safety net. +## Sub-categories + +`__fish_config_op_aliases` sub-divides into six sub-categories, each with +its own `__fish_config_op_aliases_` toggle: + +## filesystem + +`ls`, `cat`, `cd`, `du`, `mkdir`, `rm`, `mv`, and `cd`/zoxide navigation -- +the everyday filesystem-inspection and -modification shadows. + +## search + +`rg`, with its Kitty hyperlink formatting. + +## network + +`ping`, `ssh`, and `yt-dlp` -- shadows that talk to the network. + +## monitor + +`top` -> `btop`. + +## shell-tools + +`bash` (XDG bashrc + `$SHELL` reset), `less` (`$PAGER` fallback chain), +and the `help config` interception. + +## dev-tools + +`claude` (AGENTS.md/CLAUDE.md auto-linking) and `edit` (multi-editor +launcher), plus `agy`. + diff --git a/docs/manual/08-components-reference/02-c2-startup-side-effects.md b/docs/manual/08-components-reference/02-c2-startup-side-effects.md index e2f0eb5..e9c8b49 100644 --- a/docs/manual/08-components-reference/02-c2-startup-side-effects.md +++ b/docs/manual/08-components-reference/02-c2-startup-side-effects.md @@ -43,3 +43,28 @@ branches, or repos without a remote. The handler fires once per repo entry (not on every sub-directory `cd`). The registry is machine-local at `$__fish_user_dots_path/auto-pull.list` (defaults to `~/.config/.user-dots/fish/auto-pull.list`) and is never committed. +## Sub-categories + +`__fish_config_op_autoexec` sub-divides into five sub-categories, each +with its own `__fish_config_op_autoexec_` toggle: + +## plugin-management + +Fisher bootstrap on first run. + +## pkg-wrappers + +`paru`/`yay` wrapper generation. + +## venv + +Automatic Python virtualenv activation. + +## telemetry + +The WakaTime hook's startup bootstrap. + +## sync + +Auto-pull background fast-forward, and the user-dots convenience symlink. + diff --git a/docs/manual/08-components-reference/03-c3-key-and-environment-overrides.md b/docs/manual/08-components-reference/03-c3-key-and-environment-overrides.md index 79addf0..9e7102f 100644 --- a/docs/manual/08-components-reference/03-c3-key-and-environment-overrides.md +++ b/docs/manual/08-components-reference/03-c3-key-and-environment-overrides.md @@ -33,3 +33,23 @@ When C3 is disabled, `exit` falls back to `builtin exit` with no scrollback capture, no Kitty IPC, and no file I/O on exit. The scrollback capture block is independently controlled by C5 (see below). +## Sub-categories + +`__fish_config_op_overrides` sub-divides into three sub-categories, each +with its own `__fish_config_op_overrides_` toggle: + +## key-bindings + +Vi mode, autopair, puffer key intercepts, bang-bang history expansion, +and `smart_exit`'s plain-exit path. + +## environment + +`$PATH`, `$PAGER`/`$EDITOR`/`$GPG_TTY`, and `$CDPATH`. + +## prompt + +Starship, the right prompt, Catppuccin syntax/prompt colors, and FZF +theming (`$FZF_DEFAULT_OPTS`) -- all driven by the same guard as a single +unit, not independently toggleable from each other. + diff --git a/docs/manual/08-components-reference/04-c4-terminal-and-tool-integration.md b/docs/manual/08-components-reference/04-c4-terminal-and-tool-integration.md index 0219051..952df73 100644 --- a/docs/manual/08-components-reference/04-c4-terminal-and-tool-integration.md +++ b/docs/manual/08-components-reference/04-c4-terminal-and-tool-integration.md @@ -22,3 +22,28 @@ Disabled integration commands (`spwin`, `tab`, `split`, `hist`, `logs`, `upgrade a colored error to stderr naming the variable that disabled them rather than silently failing. +## Sub-categories + +`__fish_config_op_integrations` sub-divides into five sub-categories, +each with its own `__fish_config_op_integrations_` toggle: + +## terminal-abbrs + +The Kitty/WezTerm abbreviation set. + +## window-mgmt + +`spwin`, `tab`, `split`. + +## notifications + +`done`'s completion notifications, and the WakaTime activity hook. + +## history-logs + +`hist`, `logs`. + +## pkg-upgrade + +`upgrade`. + diff --git a/docs/manual/08-components-reference/05-c5-logging-and-capture.md b/docs/manual/08-components-reference/05-c5-logging-and-capture.md index a9f52e4..4867c1e 100644 --- a/docs/manual/08-components-reference/05-c5-logging-and-capture.md +++ b/docs/manual/08-components-reference/05-c5-logging-and-capture.md @@ -112,3 +112,22 @@ Note: C3 and C5 compose independently. C3 controls whether the smart_exit wrapper is active at all; C5 controls only the scrollback-capture block inside it. With C3 disabled, exit is plain builtin exit regardless of C5. +## Sub-categories + +`__fish_config_op_logging` sub-divides into three sub-categories, each +with its own `__fish_config_op_logging_` toggle (all still opt-in +by default, inherited from C5's own opt-in behavior -- see §3 of the +design spec): + +## terminal-capture + +Kitty watcher scrollback capture, and `smart_exit`'s logging-guard path. + +## multiplexer-capture + +tmux `pipe-pane` and zellij `dump-screen` capture. + +## pkg-logs + +`paru`/`yay` AUR log wrappers. + diff --git a/docs/manual/08-components-reference/06-c6-greeting-and-first-run-ui.md b/docs/manual/08-components-reference/06-c6-greeting-and-first-run-ui.md index 699e721..39b33f3 100644 --- a/docs/manual/08-components-reference/06-c6-greeting-and-first-run-ui.md +++ b/docs/manual/08-components-reference/06-c6-greeting-and-first-run-ui.md @@ -13,3 +13,16 @@ When C6 is disabled, no greeting is printed by this config. Any greeting set by the distro or other configs runs normally — this config simply does not override it. +## Sub-categories + +`__fish_config_op_greeting` sub-divides into two sub-categories, each +with its own `__fish_config_op_greeting_` toggle: + +## first-run + +The first-run welcome banner. + +## greeting-message + +The per-session `fish_greeting` override. + diff --git a/docs/manual/08-components-reference/index.md b/docs/manual/08-components-reference/index.md index 8e8fc9f..44969b1 100644 --- a/docs/manual/08-components-reference/index.md +++ b/docs/manual/08-components-reference/index.md @@ -18,3 +18,14 @@ category variable. C4 [Integrations](/08-components-reference/04-c4-terminal-and-tool-integration/) — Kitty/Wezterm integrations, starship hooks, fzf theme C5 [Logging and Capture](/08-components-reference/05-c5-logging-and-capture/) — Session logs, command duration C6 [Greeting & First-Run UI](/08-components-reference/06-c6-greeting-and-first-run-ui/) — Custom startup banner + +## Per-function overrides: `C0`/`always` + +Every guarded function or file can also carry a reserved `always/on` or +`always/off` tag in its `# COMPONENT` header, independent of every C1-C6 +category and sub-category toggle and invisible to `config-settings`. An +`always/off` tag disables that function unconditionally; an `always/on` +tag enables it unconditionally, ignoring the state of every other tagged +sub-category. This is a per-function escape hatch for cases too granular +or too idiosyncratic to justify a taxonomy entry -- edit the header +directly and run `__fish_config_op_registry_rebuild` to apply the change. From fbb6b6e7402bb4ddd58220e8f95e751e62427c4b Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 21:02:11 -0400 Subject: [PATCH 10/30] feat(docs): validate # COMPONENT tags against the sub-category taxonomy --- docs/verify-manual.py | 110 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/docs/verify-manual.py b/docs/verify-manual.py index 8626168..34fb924 100644 --- a/docs/verify-manual.py +++ b/docs/verify-manual.py @@ -130,6 +130,40 @@ def _parsed_functions() -> dict[str, dict[str, list[str]]]: return mt.parse_functions(Path(__file__).parent.parent / "functions") +def _parsed_components() -> dict[str, list[str]]: + repo = Path(__file__).parent.parent + out = mt.parse_components(repo / "functions") + out.update(mt.parse_components(repo / "conf.d")) + out.update(mt.parse_component_file(repo / "config.fish")) + return out + + +_C0_TAGS = {"always/on", "always/off"} +_TAXONOMY_FILES = { + "aliases": "01-c1-command-shadows.md", + "autoexec": "02-c2-startup-side-effects.md", + "overrides": "03-c3-key-and-environment-overrides.md", + "integrations": "04-c4-terminal-and-tool-integration.md", + "logging": "05-c5-logging-and-capture.md", + "greeting": "06-c6-greeting-and-first-run-ui.md", +} + + +def _load_taxonomy() -> dict[str, set[str]]: + """{category: {sub-category slugs}}, parsed from `## ` headings + in each category's docs/manual/08-components-reference/ file.""" + ref_root = Path(__file__).parent / "manual" / "08-components-reference" + taxonomy: dict[str, set[str]] = {} + for category, filename in _TAXONOMY_FILES.items(): + _, body = mt.parse(ref_root / filename) + taxonomy[category] = { + m.group(1) + for ln in body.split("\n") + if (m := re.match(r"^## (\S+)$", ln)) + } + return taxonomy + + def test_every_categorised_function_produces_one_entry(): import build_manual @@ -184,6 +218,37 @@ def test_dependencies_resolve(): assert not dangling, "unresolvable # DEPENDENCIES:\n " + "\n ".join(dangling) +def test_every_component_resolves_to_a_taxonomy_entry(): + """Every non-C0 # COMPONENT tag must resolve to a documented sub-category.""" + taxonomy = _load_taxonomy() + unknown = [] + for identity, raw_lines in _parsed_components().items(): + for site, tag in mt.parse_component_lines(raw_lines): + if tag in _C0_TAGS: + continue + if "/" not in tag: + unknown.append(f"{identity}: malformed tag {tag!r}") + continue + category, subcat = tag.split("/", 1) + if category not in taxonomy or subcat not in taxonomy[category]: + unknown.append(f"{identity}: {tag}") + assert not unknown, "# COMPONENT tags with no taxonomy entry:\n " + "\n ".join(unknown) + + +def test_c0_tags_never_combine_with_contradiction_unwarned(): + """Every always/on + always/off contradiction must be one this repo's + own generator would warn about -- this is a direct repo-content check, + independent of running the generator, so CI catches it even if + someone forgets to regenerate.""" + from generate_component_registry import build_registry + + _, warnings = build_registry(_parsed_components()) + # No assertion failure here by design: warnings are non-fatal (spec + # §4.5). This test exists to print them prominently in CI output. + for w in warnings: + print(f" WARN {w}") + + def warn_public_functions_without_category(): """Warn — never fail — on a public function carrying no `# CATEGORY`. @@ -205,6 +270,49 @@ def warn_public_functions_without_category(): print(" " + ", ".join(orphans)) +def warn_functions_without_component(): + """Warn -- never fail -- on a documented function calling the + opinionated guard but carrying no `# COMPONENT` section. + + Mirrors warn_public_functions_without_category: a function that never + opted into the header convention at all (no # SYNOPSIS) is silently + out of scope, matching spec §4.5's fail-open tiering. + """ + repo = Path(__file__).parent.parent + components = _parsed_components() + orphans = [] + for p in list((repo / "functions").glob("*.fish")) + list((repo / "conf.d").glob("*.fish")): + text = p.read_text(encoding="utf-8") + if "__fish_config_op_enabled" not in text or "# SYNOPSIS" not in text: + continue + if p.stem not in components: + orphans.append(str(p.relative_to(repo))) + if orphans: + print(f" WARN {len(orphans)} function(s) call the opinionated guard but lack # COMPONENT:") + print(" " + ", ".join(sorted(orphans))) + + +def warn_unused_taxonomy_entries(): + """Warn -- never fail -- on a documented sub-category with zero tagged functions.""" + taxonomy = _load_taxonomy() + used: dict[str, set[str]] = {c: set() for c in taxonomy} + for raw_lines in _parsed_components().values(): + for _site, tag in mt.parse_component_lines(raw_lines): + if tag in _C0_TAGS or "/" not in tag: + continue + category, subcat = tag.split("/", 1) + if category in used: + used[category].add(subcat) + unused = [ + f"{category}/{subcat}" + for category, subcats in taxonomy.items() + for subcat in sorted(subcats - used[category]) + ] + if unused: + print(f" WARN {len(unused)} taxonomy entr{'y has' if len(unused) == 1 else 'ies have'} zero tagged functions:") + print(" " + ", ".join(unused)) + + def _without_section_5(text: str) -> str: """Drop `# 5. FUNCTIONS REFERENCE` through the start of section 6. @@ -1028,6 +1136,8 @@ def main() -> int: print(f" FAIL {t.__name__}: {e}", file=sys.stderr) failed += 1 warn_public_functions_without_category() + warn_functions_without_component() + warn_unused_taxonomy_entries() print(f"\n{len(TESTS) - failed}/{len(TESTS)} passed") return 1 if failed else 0 From 0554a3dddcf8bb24cd278c7dabbcb7499c150fea Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 21:04:06 -0400 Subject: [PATCH 11/30] fix(docs): tighten taxonomy slug regex to exclude the Sub-categories heading --- docs/verify-manual.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/verify-manual.py b/docs/verify-manual.py index 34fb924..2963de4 100644 --- a/docs/verify-manual.py +++ b/docs/verify-manual.py @@ -159,7 +159,7 @@ def _load_taxonomy() -> dict[str, set[str]]: taxonomy[category] = { m.group(1) for ln in body.split("\n") - if (m := re.match(r"^## (\S+)$", ln)) + if (m := re.match(r"^## ([a-z][a-z0-9-]*)$", ln)) } return taxonomy From 77c68558b26b23e933f837e3a547a730e74aeb2d Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 21:07:25 -0400 Subject: [PATCH 12/30] docs(readme): mention sub-category toggles --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 0cd1165..11e074c 100644 --- a/README.md +++ b/README.md @@ -320,6 +320,14 @@ set -Ue __fish_config_op_greeting Command shadows react immediately; bindings, prompt, and abbreviations take effect in new shells. With aliases disabled, `rm` deletes permanently again instead of trashing. See `help config opinionated` for the full component list. +Each category further sub-divides into two to six sub-categories with +their own `__fish_config_op__` toggles (e.g. +`__fish_config_op_aliases_filesystem`), following the exact same +truthy/falsy/unset cascade one level deeper. Run `config-settings` and +press Enter on a category row to browse and toggle its sub-categories, or +see the [Components Reference](https://fish.rootiest.fyi/08-components-reference/) +for the full sub-category list per category. + --- ## Attribution From 04fd83297429144d7273f8aae0a12ca99a715513 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 21:12:56 -0400 Subject: [PATCH 13/30] refactor(config): migrate C1 aliases call sites to self-identifying guard --- conf.d/__fish_config_op_registry.fish | 48 +++++++++++++++++++++++++++ conf.d/help.fish | 5 ++- conf.d/zoxide.fish | 5 ++- functions/agy.fish | 5 ++- functions/bash.fish | 5 ++- functions/cat.fish | 5 ++- functions/claude.fish | 5 ++- functions/du.fish | 5 ++- functions/edit.fish | 5 ++- functions/less.fish | 5 ++- functions/ls.fish | 5 ++- functions/mkdir.fish | 5 ++- functions/mv.fish | 5 ++- functions/ping.fish | 5 ++- functions/rg.fish | 5 ++- functions/rm.fish | 5 ++- functions/ssh.fish | 5 ++- functions/top.fish | 5 ++- functions/yt-dlp.fish | 5 ++- 19 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 conf.d/__fish_config_op_registry.fish diff --git a/conf.d/__fish_config_op_registry.fish b/conf.d/__fish_config_op_registry.fish new file mode 100644 index 0000000..df89810 --- /dev/null +++ b/conf.d/__fish_config_op_registry.fish @@ -0,0 +1,48 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# GENERATED FILE --- do not edit by hand. +# Regenerate with __fish_config_op_registry_rebuild after editing a +# # COMPONENT header, or automatically via docs/build-manual.py. +# Source: docs/generate_component_registry.py + +set -g __fish_config_op_registry_keys \ + agy: \ + bash: \ + cat: \ + claude: \ + du: \ + edit: \ + help: \ + less: \ + ls: \ + mkdir: \ + mv: \ + ping: \ + rg: \ + rm: \ + ssh: \ + top: \ + yt-dlp: \ + zoxide: + +set -g __fish_config_op_registry_values \ + "aliases/dev-tools" \ + "aliases/shell-tools" \ + "aliases/filesystem" \ + "aliases/dev-tools" \ + "aliases/filesystem" \ + "aliases/dev-tools" \ + "aliases/shell-tools" \ + "aliases/shell-tools" \ + "aliases/filesystem" \ + "aliases/filesystem" \ + "aliases/filesystem" \ + "aliases/network" \ + "aliases/search" \ + "aliases/filesystem" \ + "aliases/network" \ + "aliases/monitor" \ + "aliases/network" \ + "aliases/filesystem" + diff --git a/conf.d/help.fish b/conf.d/help.fish index ecca847..0e7e3e5 100644 --- a/conf.d/help.fish +++ b/conf.d/help.fish @@ -5,6 +5,9 @@ # │ help config wrapper │ # ╰──────────────────────────────────────────────────────────╯ # +# COMPONENT +# aliases/shell-tools +# # SYNOPSIS # help [topic] [sub-topic...] # help config [section] [-w|--html] [-m|--man] [-h|--help] @@ -50,7 +53,7 @@ end # --- Wrapper Definition --- function help --wraps help --description "Custom wrapper to intercept 'help config'" # Opinionated guard (C1): fall back to the native fish help when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) __original_help $argv return $status end diff --git a/conf.d/zoxide.fish b/conf.d/zoxide.fish index 57a6efe..5d2319c 100644 --- a/conf.d/zoxide.fish +++ b/conf.d/zoxide.fish @@ -3,6 +3,9 @@ # Adapted from icezyclon/zoxide.fish (MIT) # Heavily customized for Fish 4.x compatibility and performance +# COMPONENT +# aliases/filesystem + if status is-interactive if type -q zoxide @@ -65,7 +68,7 @@ if status is-interactive # Shadowing cd with zoxide is opinionated (C1 aliasing); z and zi # remain available either way. - if __fish_config_op_enabled __fish_config_op_aliases + if __fish_config_op_enabled (status basename) alias cd=z end diff --git a/functions/agy.fish b/functions/agy.fish index 21aed74..cfc751d 100644 --- a/functions/agy.fish +++ b/functions/agy.fish @@ -4,6 +4,9 @@ # CATEGORY # 12-ai-and-developer-tools # +# COMPONENT +# aliases/dev-tools +# # DEPENDENCIES # agents-init # @@ -35,7 +38,7 @@ # agy -i "initial prompt" # agy models function agy --wraps=agy --description 'agy wrapper: auto-initializes AGENTS/ sub-repo before launch' - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command agy $argv return $status end diff --git a/functions/bash.fish b/functions/bash.fish index 39cbd93..bfb721e 100644 --- a/functions/bash.fish +++ b/functions/bash.fish @@ -4,6 +4,9 @@ # CATEGORY # 14-miscellaneous # +# COMPONENT +# aliases/shell-tools +# # SYNOPSIS # bash [args...] # @@ -18,7 +21,7 @@ # bash function bash --wraps='bash' --description 'bash switches to bash shell' # Opinionated guard (C1): fall back to bare command bash when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command bash $argv return $status end diff --git a/functions/cat.fish b/functions/cat.fish index 76049a6..e6394b8 100644 --- a/functions/cat.fish +++ b/functions/cat.fish @@ -4,6 +4,9 @@ # CATEGORY # 01-file-and-directory # +# COMPONENT +# aliases/filesystem +# # SYNOPSIS # cat [args...] # @@ -21,7 +24,7 @@ # cat ~/projects/myapp function cat --wraps='bat' --description 'Use bat for files, ls for directories, and raw cat for ANSI logs' # Opinionated guard (C1): fall back to bare command cat when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command cat $argv return $status end diff --git a/functions/claude.fish b/functions/claude.fish index 3b981d7..ff3d278 100644 --- a/functions/claude.fish +++ b/functions/claude.fish @@ -4,6 +4,9 @@ # CATEGORY # 12-ai-and-developer-tools # +# COMPONENT +# aliases/dev-tools +# # DEPENDENCIES # agents-init # @@ -33,7 +36,7 @@ # claude --resume # claude "Explain the recent changes" function claude --wraps=claude --description 'claude wrapper: auto-links AGENTS.md as CLAUDE.md' - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command claude $argv return $status end diff --git a/functions/du.fish b/functions/du.fish index 6fd4f93..ad03503 100644 --- a/functions/du.fish +++ b/functions/du.fish @@ -4,6 +4,9 @@ # CATEGORY # 01-file-and-directory # +# COMPONENT +# aliases/filesystem +# # SYNOPSIS # du [--disk|--dir|--dua] [args...] # @@ -23,7 +26,7 @@ # du --disk function du --description 'Execute du' # Opinionated guard (C1): fall back to bare command du when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command du $argv return $status end diff --git a/functions/edit.fish b/functions/edit.fish index b02e3ab..ed178b4 100644 --- a/functions/edit.fish +++ b/functions/edit.fish @@ -4,6 +4,9 @@ # CATEGORY # 03-editors-and-viewers # +# COMPONENT +# aliases/dev-tools +# # SYNOPSIS # edit [-V|-t] [-e EDITOR] [-c] [-x TEXT] [-n] [-v|-s] [FILE...] # @@ -50,7 +53,7 @@ function edit --description 'Open files in a terminal or GUI editor with fallbac set -l c_reset (set_color normal) # Opinionated guard (C1): fall back to the legacy bare-editor behavior. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) if set -q EDITOR; and test -n "$EDITOR" $EDITOR $argv else if type -q nvim diff --git a/functions/less.fish b/functions/less.fish index f60bdc5..11c034e 100644 --- a/functions/less.fish +++ b/functions/less.fish @@ -4,6 +4,9 @@ # CATEGORY # 03-editors-and-viewers # +# COMPONENT +# aliases/shell-tools +# # SYNOPSIS # less [args...] # @@ -18,7 +21,7 @@ # less /var/log/syslog function less --wraps='ov' --description 'Pager wrapper: $PAGER → ov → less → more → cat' # Opinionated guard (C1): fall back to bare command less when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command less $argv return $status end diff --git a/functions/ls.fish b/functions/ls.fish index a094d4d..da11956 100644 --- a/functions/ls.fish +++ b/functions/ls.fish @@ -4,6 +4,9 @@ # CATEGORY # 01-file-and-directory # +# COMPONENT +# aliases/filesystem +# # SYNOPSIS # ls [args...] # @@ -20,7 +23,7 @@ # ls -a ~/projects function ls --description 'List all files' # Opinionated guard (C1): fall back to bare command ls when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command ls $argv return $status end diff --git a/functions/mkdir.fish b/functions/mkdir.fish index bb45658..cf013d6 100644 --- a/functions/mkdir.fish +++ b/functions/mkdir.fish @@ -4,6 +4,9 @@ # CATEGORY # 01-file-and-directory # +# COMPONENT +# aliases/filesystem +# # SYNOPSIS # mkdir [args...] # @@ -20,7 +23,7 @@ # mkdir ~/projects/myapp/src function mkdir --description 'Execute mkdir' # Opinionated guard (C1): fall back to bare command mkdir when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command mkdir $argv return $status end diff --git a/functions/mv.fish b/functions/mv.fish index d8ea113..e8e4b8b 100644 --- a/functions/mv.fish +++ b/functions/mv.fish @@ -4,6 +4,9 @@ # CATEGORY # 01-file-and-directory # +# COMPONENT +# aliases/filesystem +# # SYNOPSIS # mv [args...] # @@ -27,7 +30,7 @@ # mv ~/.config/btop/themes/themes ~/.config/btop/themes function mv --wraps='mv' --description 'Move files with auto-collapse for nested directories' # Opinionated guard (C1): fall back to bare command mv when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command mv $argv return $status end diff --git a/functions/ping.fish b/functions/ping.fish index ca9fd62..947d597 100644 --- a/functions/ping.fish +++ b/functions/ping.fish @@ -4,6 +4,9 @@ # CATEGORY # 10-network # +# COMPONENT +# aliases/network +# # SYNOPSIS # ping [args...] # @@ -21,7 +24,7 @@ # ping --legend google.com function ping --description 'prettyping with default nolegend' # Opinionated guard (C1): fall back to bare command ping when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command ping $argv return $status end diff --git a/functions/rg.fish b/functions/rg.fish index 346f652..0c164cb 100644 --- a/functions/rg.fish +++ b/functions/rg.fish @@ -4,6 +4,9 @@ # CATEGORY # 01-file-and-directory # +# COMPONENT +# aliases/search +# # SYNOPSIS # rg [args...] # @@ -21,7 +24,7 @@ # rg -l "TODO" ~/projects/myapp function rg --description 'alias rg=rg --hyperlink-format=kitty' # Opinionated guard (C1): fall back to bare command rg when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command rg $argv return $status end diff --git a/functions/rm.fish b/functions/rm.fish index 965e0e8..bbcd46c 100644 --- a/functions/rm.fish +++ b/functions/rm.fish @@ -4,6 +4,9 @@ # CATEGORY # 01-file-and-directory # +# COMPONENT +# aliases/filesystem +# # SYNOPSIS # rm [-e [options] | -S | args...] # @@ -38,7 +41,7 @@ # Falls back to /usr/bin/rm when trash is unavailable. function rm --description 'Ultimate rm: trash, list, empty, and secure-erase' # Opinionated guard (C1): fall back to bare command rm when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command rm $argv return $status end diff --git a/functions/ssh.fish b/functions/ssh.fish index 4814277..162432f 100644 --- a/functions/ssh.fish +++ b/functions/ssh.fish @@ -4,6 +4,9 @@ # CATEGORY # 08-terminal-management # +# COMPONENT +# aliases/network +# # SYNOPSIS # ssh [args...] # @@ -20,7 +23,7 @@ # ssh user@host function ssh --description 'Alias ssh to kitten ssh when using Kitty terminal' # Opinionated guard (C1): fall back to bare command ssh when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command ssh $argv return $status end diff --git a/functions/top.fish b/functions/top.fish index b765586..73c77df 100644 --- a/functions/top.fish +++ b/functions/top.fish @@ -4,6 +4,9 @@ # CATEGORY # 07-system-and-monitoring # +# COMPONENT +# aliases/monitor +# # SYNOPSIS # top [args...] # @@ -18,7 +21,7 @@ # top function top --wraps='btop' --description 'Use btop as a modern replacement for top' # Opinionated guard (C1): fall back to bare command top when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command top $argv return $status end diff --git a/functions/yt-dlp.fish b/functions/yt-dlp.fish index 3ce96fb..da8c317 100644 --- a/functions/yt-dlp.fish +++ b/functions/yt-dlp.fish @@ -4,6 +4,9 @@ # CATEGORY # 13-media-and-utilities # +# COMPONENT +# aliases/network +# # SYNOPSIS # yt-dlp [args...] URL [URL...] # @@ -29,7 +32,7 @@ # yt-dlp --no-embed-thumbnail dQw4w9WgXcQ # drops our thumbnail default function yt-dlp --description 'yt-dlp with embedding + SponsorBlock defaults' # Opinionated guard (C1): fall back to bare command yt-dlp when disabled. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status current-function) command yt-dlp $argv return $status end From 760b8e68fd2e7e36a4f3f9ead6a66404b4b9507c Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 21:23:12 -0400 Subject: [PATCH 14/30] refactor(config): migrate C2 autoexec call sites to self-identifying guard --- conf.d/__fish_config_op_registry.fish | 6 ++++++ conf.d/auto-pull.fish | 5 ++++- functions/__auto_source_fallback_venv.fish | 5 ++++- functions/__fish_user_dots_link.fish | 5 ++++- functions/auto-pull.fish | 5 ++++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/conf.d/__fish_config_op_registry.fish b/conf.d/__fish_config_op_registry.fish index df89810..07201ef 100644 --- a/conf.d/__fish_config_op_registry.fish +++ b/conf.d/__fish_config_op_registry.fish @@ -7,7 +7,10 @@ # Source: docs/generate_component_registry.py set -g __fish_config_op_registry_keys \ + __auto_source_fallback_venv: \ + __fish_user_dots_link: \ agy: \ + auto-pull: \ bash: \ cat: \ claude: \ @@ -27,7 +30,10 @@ set -g __fish_config_op_registry_keys \ zoxide: set -g __fish_config_op_registry_values \ + "autoexec/venv" \ + "autoexec/sync" \ "aliases/dev-tools" \ + "autoexec/sync autoexec/sync" \ "aliases/shell-tools" \ "aliases/filesystem" \ "aliases/dev-tools" \ diff --git a/conf.d/auto-pull.fish b/conf.d/auto-pull.fish index 41fa0a7..852559a 100644 --- a/conf.d/auto-pull.fish +++ b/conf.d/auto-pull.fish @@ -14,8 +14,11 @@ # Manage the registry with: auto-pull add / remove / list / status # C2 guard: when auto-execution is disabled, do not register the handler. -__fish_config_op_enabled __fish_config_op_autoexec; or exit +__fish_config_op_enabled (status basename); or exit +# COMPONENT +# autoexec/sync +# # SYNOPSIS # __auto_pull_on_pwd (event handler, --on-variable PWD) # diff --git a/functions/__auto_source_fallback_venv.fish b/functions/__auto_source_fallback_venv.fish index ab17f05..6ca0e83 100644 --- a/functions/__auto_source_fallback_venv.fish +++ b/functions/__auto_source_fallback_venv.fish @@ -1,6 +1,9 @@ # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later +# COMPONENT +# autoexec/venv +# # SYNOPSIS # __auto_source_fallback_venv # @@ -16,7 +19,7 @@ function __auto_source_fallback_venv --on-variable PWD status --is-command-substitution; and return # Opinionated guard (C2): no automatic venv activation when disabled. - __fish_config_op_enabled __fish_config_op_autoexec; or return + __fish_config_op_enabled (status current-function); or return # 1. Skip if direnv is already managing this directory if set -q DIRENV_DIR; or test -e ".envrc" diff --git a/functions/__fish_user_dots_link.fish b/functions/__fish_user_dots_link.fish index e61bb2f..f02e583 100644 --- a/functions/__fish_user_dots_link.fish +++ b/functions/__fish_user_dots_link.fish @@ -1,6 +1,9 @@ # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later +# COMPONENT +# autoexec/sync +# # SYNOPSIS # __fish_user_dots_link # @@ -41,7 +44,7 @@ function __fish_user_dots_link --description 'Manage the user-dots convenience s end # Enabled: creation is a C2 startup side-effect. - __fish_config_op_enabled __fish_config_op_autoexec; or return 0 + __fish_config_op_enabled (status current-function); or return 0 test -d "$__fish_user_dots_path"; or return 0 if test -L "$link" diff --git a/functions/auto-pull.fish b/functions/auto-pull.fish index 6527fb6..1472aaf 100644 --- a/functions/auto-pull.fish +++ b/functions/auto-pull.fish @@ -4,6 +4,9 @@ # CATEGORY # 04-git-and-version-control # +# COMPONENT +# autoexec/sync +# # SYNOPSIS # auto-pull [list] # auto-pull add [PATH] @@ -136,7 +139,7 @@ function auto-pull --description 'Manage the auto-pull repository registry' return 0 case status - if __fish_config_op_enabled __fish_config_op_autoexec + if __fish_config_op_enabled (status current-function) echo "$c_ok""auto-pull: ENABLED$c_reset $c_dim(C2 auto-execution on)$c_reset" else echo "$c_warn""auto-pull: DISABLED$c_reset $c_dim(via __fish_config_op_autoexec)$c_reset" From a981fa7f0a5f3130d18fa0f363087ea227f6553b Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 21:37:35 -0400 Subject: [PATCH 15/30] refactor(config): migrate C3 overrides call sites to self-identifying guard --- conf.d/__fish_config_op_registry.fish | 14 ++++++++++++++ conf.d/autopair.fish | 5 ++++- conf.d/bash_expands.fish | 15 +++++++++------ conf.d/key_bindings.fish | 9 ++++++++- conf.d/puffer.fish | 5 ++++- conf.d/starship.fish | 6 +++++- conf.d/theme.fish | 6 +++++- functions/fish_right_prompt.fish | 5 ++++- 8 files changed, 53 insertions(+), 12 deletions(-) diff --git a/conf.d/__fish_config_op_registry.fish b/conf.d/__fish_config_op_registry.fish index 07201ef..d2b3f98 100644 --- a/conf.d/__fish_config_op_registry.fish +++ b/conf.d/__fish_config_op_registry.fish @@ -11,20 +11,27 @@ set -g __fish_config_op_registry_keys \ __fish_user_dots_link: \ agy: \ auto-pull: \ + autopair: \ bash: \ + bash_expands: \ cat: \ claude: \ du: \ edit: \ + fish_right_prompt: \ help: \ + key_bindings: \ less: \ ls: \ mkdir: \ mv: \ ping: \ + puffer: \ rg: \ rm: \ ssh: \ + starship: \ + theme: \ top: \ yt-dlp: \ zoxide: @@ -34,20 +41,27 @@ set -g __fish_config_op_registry_values \ "autoexec/sync" \ "aliases/dev-tools" \ "autoexec/sync autoexec/sync" \ + "overrides/key-bindings" \ "aliases/shell-tools" \ + "overrides/key-bindings" \ "aliases/filesystem" \ "aliases/dev-tools" \ "aliases/filesystem" \ "aliases/dev-tools" \ + "overrides/prompt" \ "aliases/shell-tools" \ + "overrides/key-bindings" \ "aliases/shell-tools" \ "aliases/filesystem" \ "aliases/filesystem" \ "aliases/filesystem" \ "aliases/network" \ + "overrides/key-bindings" \ "aliases/search" \ "aliases/filesystem" \ "aliases/network" \ + "overrides/prompt" \ + "overrides/prompt" \ "aliases/monitor" \ "aliases/network" \ "aliases/filesystem" diff --git a/conf.d/autopair.fish b/conf.d/autopair.fish index 571457f..66741d3 100644 --- a/conf.d/autopair.fish +++ b/conf.d/autopair.fish @@ -1,8 +1,11 @@ status is-interactive || exit +# COMPONENT +# overrides/key-bindings + # Local modification: opinionated guard (AGENTS.md Task #3). Bracket # auto-pairing intercepts single-character input, classified as C3 overrides. -__fish_config_op_enabled __fish_config_op_overrides || exit +__fish_config_op_enabled (status basename) || exit set --global autopair_left "(" "[" "{" '"' "'" set --global autopair_right ")" "]" "}" '"' "'" diff --git a/conf.d/bash_expands.fish b/conf.d/bash_expands.fish index 5c6a9d3..d81dc91 100644 --- a/conf.d/bash_expands.fish +++ b/conf.d/bash_expands.fish @@ -1,13 +1,16 @@ # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later +# COMPONENT +# overrides/key-bindings + # Provides bash-style history expansion functions for abbreviations. # These functions are gated by the C3 overrides switch. # Execute expand_bang_all function expand_bang_all --description 'Execute expand_bang_all' # Opinionated guard (C3): no expansion when overrides are disabled. - __fish_config_op_enabled __fish_config_op_overrides; or return 1 + __fish_config_op_enabled (status basename); or return 1 set -l token $argv[1] if test -z "$token"; set token (commandline -t); end @@ -23,7 +26,7 @@ end # Execute expand_bang_caret function expand_bang_caret --description 'Execute expand_bang_caret' # Opinionated guard (C3): no expansion when overrides are disabled. - __fish_config_op_enabled __fish_config_op_overrides; or return 1 + __fish_config_op_enabled (status basename); or return 1 # Split the last history item into a list set -l tokens (string split -n ' ' -- $history[1]) @@ -36,7 +39,7 @@ end # Execute expand_bang_minus_n function expand_bang_minus_n --description 'Execute expand_bang_minus_n' # Opinionated guard (C3): no expansion when overrides are disabled. - __fish_config_op_enabled __fish_config_op_overrides; or return 1 + __fish_config_op_enabled (status basename); or return 1 set -l token $argv[1] if test -z "$token"; set token (commandline -t); end @@ -58,7 +61,7 @@ end # Execute expand_bang_search function expand_bang_search --description 'Execute expand_bang_search' # Opinionated guard (C3): no expansion when overrides are disabled. - __fish_config_op_enabled __fish_config_op_overrides; or return 1 + __fish_config_op_enabled (status basename); or return 1 set -l token $argv[1] if test -z "$token" @@ -84,7 +87,7 @@ end # Execute expand_bang_string function expand_bang_string --description 'Execute expand_bang_string' # Opinionated guard (C3): no expansion when overrides are disabled. - __fish_config_op_enabled __fish_config_op_overrides; or return 1 + __fish_config_op_enabled (status basename); or return 1 # Fish 4.x passes the matched token as argv[1] set -l token $argv[1] @@ -112,7 +115,7 @@ end # Execute expand_typo_sub function expand_typo_sub --description 'Execute expand_typo_sub' # Opinionated guard (C3): no expansion when overrides are disabled. - __fish_config_op_enabled __fish_config_op_overrides; or return 1 + __fish_config_op_enabled (status basename); or return 1 # In newer Fish, the matched token is often passed as $argv[1] # if the abbr is set up correctly. We'll fallback to commandline just in case. diff --git a/conf.d/key_bindings.fish b/conf.d/key_bindings.fish index 21a2855..1f9793f 100644 --- a/conf.d/key_bindings.fish +++ b/conf.d/key_bindings.fish @@ -8,6 +8,9 @@ # This file defines custom key bindings for the Fish shell. # It is sourced by Fish on startup. +# COMPONENT +# overrides/key-bindings + # ────────────────── Bind Prewious Path Head to Ctrl+G ───────────────── # Bindings to insert the previous path head into the command line # Behaves like `!$:h` does in bash @@ -48,7 +51,11 @@ function fish_user_key_bindings # Custom key chords are opinionated (C3 overrides); skip them entirely # when overrides are disabled so stock bindings remain untouched. - __fish_config_op_enabled __fish_config_op_overrides; or return + # NOTE: (status basename), not (status current-function) -- this guard + # lives inside fish's own reserved fish_user_key_bindings function, whose + # name is not this file's identity; the registry key is this file's + # bare basename, key_bindings. + __fish_config_op_enabled (status basename); or return # ───────────────────────────── Set Bindings ───────────────────────────── # diff --git a/conf.d/puffer.fish b/conf.d/puffer.fish index 35ac61c..fbe7ad0 100644 --- a/conf.d/puffer.fish +++ b/conf.d/puffer.fish @@ -1,9 +1,12 @@ status is-interactive || exit +# COMPONENT +# overrides/key-bindings + # Local modification: opinionated guard (AGENTS.md Task #3). Puffer's key # intercepts are part of the bang-bang system, gated atomically under C3 # overrides with conf.d/tricks.fish, conf.d/abbr.fish, and expand_*.fish. -__fish_config_op_enabled __fish_config_op_overrides || exit +__fish_config_op_enabled (status basename) || exit function _puffer_fish_key_bindings --on-variable fish_key_bindings set -l modes diff --git a/conf.d/starship.fish b/conf.d/starship.fish index a7a429a..fc9673e 100644 --- a/conf.d/starship.fish +++ b/conf.d/starship.fish @@ -1,12 +1,16 @@ # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later + +# COMPONENT +# overrides/prompt + # # Defines fish_prompt only when starship is installed. # Without starship, fish's built-in prompt already emits OSC 133;A # on the prompt line itself, so no wrapper is needed. # Replacing the prompt is opinionated (C3 overrides) -__fish_config_op_enabled __fish_config_op_overrides; or return +__fish_config_op_enabled (status basename); or return type -q starship; or return diff --git a/conf.d/theme.fish b/conf.d/theme.fish index 1cd1f8a..5944728 100644 --- a/conf.d/theme.fish +++ b/conf.d/theme.fish @@ -1,5 +1,9 @@ # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later + +# COMPONENT +# overrides/prompt + # # ╭──────────────────────────────────────────────────────────╮ # │ Fish Theme │ @@ -9,7 +13,7 @@ # Forcing theme colors and $FZF_DEFAULT_OPTS is opinionated (C3 overrides). # The FZF variable is universal, so clean up our Catppuccin value if it # lingers from a session where overrides were still enabled. -if not __fish_config_op_enabled __fish_config_op_overrides +if not __fish_config_op_enabled (status basename) if set -q FZF_DEFAULT_OPTS; and string match -q '*#1E1E2E*' -- "$FZF_DEFAULT_OPTS" set --erase FZF_DEFAULT_OPTS end diff --git a/functions/fish_right_prompt.fish b/functions/fish_right_prompt.fish index f658e71..36a1c81 100644 --- a/functions/fish_right_prompt.fish +++ b/functions/fish_right_prompt.fish @@ -4,6 +4,9 @@ # CATEGORY # 08-terminal-management # +# COMPONENT +# overrides/prompt +# # SYNOPSIS # fish_right_prompt # @@ -31,7 +34,7 @@ function fish_right_prompt # Docker context — only relevant alongside the starship prompt, and only # when docker is actually installed (guarded like every other optional # integration in this config). - if type -q docker; and type -q starship; and __fish_config_op_enabled __fish_config_op_overrides + if type -q docker; and type -q starship; and __fish_config_op_enabled (status current-function) set -l docker_ctx (docker context show 2>/dev/null) if test -n "$docker_ctx"; and test "$docker_ctx" != default set_color blue From 516b2ba26c25f2bcf5f5059e5b6f29fd490cb0d4 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 21:49:45 -0400 Subject: [PATCH 16/30] refactor(config): migrate C4 integrations call sites to self-identifying guard --- conf.d/__fish_config_op_registry.fish | 14 ++++++++++++++ conf.d/done.fish | 5 ++++- functions/hist.fish | 5 ++++- functions/logs.fish | 5 ++++- functions/split.fish | 5 ++++- functions/spwin.fish | 5 ++++- functions/tab.fish | 5 ++++- functions/upgrade.fish | 5 ++++- 8 files changed, 42 insertions(+), 7 deletions(-) diff --git a/conf.d/__fish_config_op_registry.fish b/conf.d/__fish_config_op_registry.fish index d2b3f98..b9cd5d7 100644 --- a/conf.d/__fish_config_op_registry.fish +++ b/conf.d/__fish_config_op_registry.fish @@ -16,12 +16,15 @@ set -g __fish_config_op_registry_keys \ bash_expands: \ cat: \ claude: \ + done: \ du: \ edit: \ fish_right_prompt: \ help: \ + hist: \ key_bindings: \ less: \ + logs: \ ls: \ mkdir: \ mv: \ @@ -29,10 +32,14 @@ set -g __fish_config_op_registry_keys \ puffer: \ rg: \ rm: \ + split: \ + spwin: \ ssh: \ starship: \ + tab: \ theme: \ top: \ + upgrade: \ yt-dlp: \ zoxide: @@ -46,12 +53,15 @@ set -g __fish_config_op_registry_values \ "overrides/key-bindings" \ "aliases/filesystem" \ "aliases/dev-tools" \ + "integrations/notifications" \ "aliases/filesystem" \ "aliases/dev-tools" \ "overrides/prompt" \ "aliases/shell-tools" \ + "integrations/history-logs" \ "overrides/key-bindings" \ "aliases/shell-tools" \ + "integrations/history-logs" \ "aliases/filesystem" \ "aliases/filesystem" \ "aliases/filesystem" \ @@ -59,10 +69,14 @@ set -g __fish_config_op_registry_values \ "overrides/key-bindings" \ "aliases/search" \ "aliases/filesystem" \ + "integrations/window-mgmt" \ + "integrations/window-mgmt" \ "aliases/network" \ "overrides/prompt" \ + "integrations/window-mgmt" \ "overrides/prompt" \ "aliases/monitor" \ + "integrations/pkg-upgrade" \ "aliases/network" \ "aliases/filesystem" diff --git a/conf.d/done.fish b/conf.d/done.fish index bb108d0..95a0e3b 100644 --- a/conf.d/done.fish +++ b/conf.d/done.fish @@ -19,6 +19,9 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +# +# COMPONENT +# integrations/notifications if not status is-interactive exit @@ -26,7 +29,7 @@ end # Local modification: opinionated guard (AGENTS.md Task #3). Desktop # notifications assume a graphical session, classified as C4 integrations. -__fish_config_op_enabled __fish_config_op_integrations; or exit +__fish_config_op_enabled (status basename); or exit set -g __done_version 1.19.1 diff --git a/functions/hist.fish b/functions/hist.fish index 9fd2b06..b21c97c 100644 --- a/functions/hist.fish +++ b/functions/hist.fish @@ -4,6 +4,9 @@ # CATEGORY # 04-git-and-version-control # +# COMPONENT +# integrations/history-logs +# # SYNOPSIS # hist # @@ -15,7 +18,7 @@ # hist function hist --description 'Search fish history and put it in the prompt' # Opinionated guard (C4): integrations disabled - if not __fish_config_op_enabled __fish_config_op_integrations + if not __fish_config_op_enabled (status current-function) set -l c_err (set_color red) set -l c_reset (set_color normal) echo "$c_err"'hist: disabled by __fish_config_op_integrations'"$c_reset" >&2 diff --git a/functions/logs.fish b/functions/logs.fish index 3474ed1..fe64d17 100644 --- a/functions/logs.fish +++ b/functions/logs.fish @@ -4,6 +4,9 @@ # CATEGORY # 11-pager-and-logging # +# COMPONENT +# integrations/history-logs +# # SYNOPSIS # logs [-h] [-c ] # @@ -35,7 +38,7 @@ # logs -c scrollback function logs --description 'Browse terminal log files interactively with fzf' # Opinionated guard (C4): integrations disabled - if not __fish_config_op_enabled __fish_config_op_integrations + if not __fish_config_op_enabled (status current-function) set -l c_err (set_color red) set -l c_reset (set_color normal) echo "$c_err"'logs: disabled by __fish_config_op_integrations'"$c_reset" >&2 diff --git a/functions/split.fish b/functions/split.fish index 9317d8a..618b42a 100644 --- a/functions/split.fish +++ b/functions/split.fish @@ -4,6 +4,9 @@ # CATEGORY # 08-terminal-management # +# COMPONENT +# integrations/window-mgmt +# # SYNOPSIS # split [-h | -v] [command...] # @@ -27,7 +30,7 @@ # split -v nvim README.md function split --description 'Run a command in a new terminal split' # Opinionated guard (C4): integrations disabled - if not __fish_config_op_enabled __fish_config_op_integrations + if not __fish_config_op_enabled (status current-function) set -l c_err (set_color red) set -l c_reset (set_color normal) echo "$c_err"'split: disabled by __fish_config_op_integrations'"$c_reset" >&2 diff --git a/functions/spwin.fish b/functions/spwin.fish index 54bbd04..18fb036 100644 --- a/functions/spwin.fish +++ b/functions/spwin.fish @@ -4,6 +4,9 @@ # CATEGORY # 08-terminal-management # +# COMPONENT +# integrations/window-mgmt +# # SYNOPSIS # spwin [args...] # @@ -22,7 +25,7 @@ # spwin function spwin --wraps='~/.config/kitty/spawn-window.sh' --description 'spawn window in kitty or wezterm' # Opinionated guard (C4): integrations disabled - if not __fish_config_op_enabled __fish_config_op_integrations + if not __fish_config_op_enabled (status current-function) set -l c_err (set_color red) set -l c_reset (set_color normal) echo "$c_err"'spwin: disabled by __fish_config_op_integrations'"$c_reset" >&2 diff --git a/functions/tab.fish b/functions/tab.fish index 9b77440..6cb9c21 100644 --- a/functions/tab.fish +++ b/functions/tab.fish @@ -4,6 +4,9 @@ # CATEGORY # 08-terminal-management # +# COMPONENT +# integrations/window-mgmt +# # SYNOPSIS # tab [args...] # @@ -23,7 +26,7 @@ # tab function tab --description 'Spawn a new tab in the current terminal' # Opinionated guard (C4): integrations disabled - if not __fish_config_op_enabled __fish_config_op_integrations + if not __fish_config_op_enabled (status current-function) set -l c_err (set_color red) set -l c_reset (set_color normal) echo "$c_err"'tab: disabled by __fish_config_op_integrations'"$c_reset" >&2 diff --git a/functions/upgrade.fish b/functions/upgrade.fish index 296f56d..68702bb 100644 --- a/functions/upgrade.fish +++ b/functions/upgrade.fish @@ -4,6 +4,9 @@ # CATEGORY # 05-package-management # +# COMPONENT +# integrations/pkg-upgrade +# # SYNOPSIS # upgrade # @@ -19,7 +22,7 @@ # upgrade function upgrade --description 'Full system upgrade via paru or yay' # Opinionated guard (C4): integrations disabled - if not __fish_config_op_enabled __fish_config_op_integrations + if not __fish_config_op_enabled (status current-function) set -l c_err (set_color red) set -l c_reset (set_color normal) echo "$c_err"'upgrade: disabled by __fish_config_op_integrations'"$c_reset" >&2 From e02919de2156bcbf6d25cc3e5dbb851708f5d980 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 17 Aug 2026 21:53:48 -0400 Subject: [PATCH 17/30] fix(config): translate hyphens to underscores in sub-category guard variable names __fish_config_op_enabled built the sub-category override variable name by concatenating the tag's slug verbatim, e.g. __fish_config_op_integrations_window-mgmt. Fish variable names cannot contain hyphens, so any hyphenated sub-category slug (14 of the 24 in the taxonomy) silently could never be overridden -- an unset such variable safely fell through via set -q, but attempting to set it always errored with "invalid variable name", masking the defect since no prior smoke test exercised an explicit sub-category-level toggle. The registry tag itself (as authored in # COMPONENT headers and the taxonomy docs) stays hyphenated; only the derived fish variable name is translated. --- functions/__fish_config_op_enabled.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/__fish_config_op_enabled.fish b/functions/__fish_config_op_enabled.fish index 18f143c..ec877e2 100644 --- a/functions/__fish_config_op_enabled.fish +++ b/functions/__fish_config_op_enabled.fish @@ -61,7 +61,7 @@ function __fish_config_op_enabled --description 'Guard for an opinionated compon for tag in $tags set -l parts (string split -m 1 -- / $tag) set -l category_var "__fish_config_op_$parts[1]" - set -l subcat_var "__fish_config_op_$parts[1]_$parts[2]" + set -l subcat_var "__fish_config_op_$parts[1]_"(string replace -a -- '-' '_' $parts[2]) __fish_config_op_cascade $category_var $subcat_var or return 1 end From c3a2e3546570b4cb7756ec2ca2199576027e3b6d Mon Sep 17 00:00:00 2001 From: Rootiest Date: Tue, 18 Aug 2026 02:22:31 -0400 Subject: [PATCH 18/30] refactor(config): migrate C5 logging call sites to self-identifying guard --- conf.d/__fish_config_op_registry.fish | 10 ++++++++++ conf.d/kitty-watcher-reminder.fish | 6 +++++- conf.d/tmux-logging.fish | 6 +++++- functions/__fish_config_sync_logging.fish | 5 ++++- functions/_zellij_dump_log.fish | 5 ++++- functions/kitty-logging.fish | 7 +++++-- 6 files changed, 33 insertions(+), 6 deletions(-) diff --git a/conf.d/__fish_config_op_registry.fish b/conf.d/__fish_config_op_registry.fish index b9cd5d7..96bda28 100644 --- a/conf.d/__fish_config_op_registry.fish +++ b/conf.d/__fish_config_op_registry.fish @@ -8,7 +8,9 @@ set -g __fish_config_op_registry_keys \ __auto_source_fallback_venv: \ + __fish_config_sync_logging: \ __fish_user_dots_link: \ + _zellij_dump_log: \ agy: \ auto-pull: \ autopair: \ @@ -23,6 +25,8 @@ set -g __fish_config_op_registry_keys \ help: \ hist: \ key_bindings: \ + kitty-logging: \ + kitty-watcher-reminder: \ less: \ logs: \ ls: \ @@ -38,6 +42,7 @@ set -g __fish_config_op_registry_keys \ starship: \ tab: \ theme: \ + tmux-logging: \ top: \ upgrade: \ yt-dlp: \ @@ -45,7 +50,9 @@ set -g __fish_config_op_registry_keys \ set -g __fish_config_op_registry_values \ "autoexec/venv" \ + "logging/terminal-capture" \ "autoexec/sync" \ + "logging/multiplexer-capture" \ "aliases/dev-tools" \ "autoexec/sync autoexec/sync" \ "overrides/key-bindings" \ @@ -60,6 +67,8 @@ set -g __fish_config_op_registry_values \ "aliases/shell-tools" \ "integrations/history-logs" \ "overrides/key-bindings" \ + "logging/terminal-capture" \ + "logging/terminal-capture" \ "aliases/shell-tools" \ "integrations/history-logs" \ "aliases/filesystem" \ @@ -75,6 +84,7 @@ set -g __fish_config_op_registry_values \ "overrides/prompt" \ "integrations/window-mgmt" \ "overrides/prompt" \ + "logging/multiplexer-capture" \ "aliases/monitor" \ "integrations/pkg-upgrade" \ "aliases/network" \ diff --git a/conf.d/kitty-watcher-reminder.fish b/conf.d/kitty-watcher-reminder.fish index ad8e37c..d8bcb71 100644 --- a/conf.d/kitty-watcher-reminder.fish +++ b/conf.d/kitty-watcher-reminder.fish @@ -1,5 +1,9 @@ # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later + +# COMPONENT +# logging/terminal-capture + # # C5 — Logging & Capture: a non-blocking, per-session reminder shown inside Kitty # when the fish-config scrollback watcher is not yet set up. It never blocks the @@ -10,7 +14,7 @@ status is-interactive; or exit type -q kitty; or exit set -q KITTY_WINDOW_ID; or exit -__fish_config_op_enabled __fish_config_op_logging; or exit +__fish_config_op_enabled (status basename); or exit __fish_variable_check __fish_config_kitty_watcher_dismissed; and exit __kitty_logging_has_watcher; and exit diff --git a/conf.d/tmux-logging.fish b/conf.d/tmux-logging.fish index cc416b4..c1857fc 100644 --- a/conf.d/tmux-logging.fish +++ b/conf.d/tmux-logging.fish @@ -1,12 +1,16 @@ # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later + +# COMPONENT +# logging/multiplexer-capture + # # C5 — Logging & Capture: starts a pipe-pane log for the current tmux pane # when fish launches inside a tmux session. Each fish shell gets its own # timestamped log file in SCROLLBACK_HISTORY_DIR (default: ~/.terminal_history). # Naming: tmux_-w-p_YYYY-MM-DD_HH-MM-SS.log -__fish_config_op_enabled __fish_config_op_logging; or exit +__fish_config_op_enabled (status basename); or exit status is-interactive; or exit type -q tmux; or exit set -q TMUX; or exit diff --git a/functions/__fish_config_sync_logging.fish b/functions/__fish_config_sync_logging.fish index 431a793..2d61e86 100644 --- a/functions/__fish_config_sync_logging.fish +++ b/functions/__fish_config_sync_logging.fish @@ -1,6 +1,9 @@ # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later +# COMPONENT +# logging/terminal-capture +# # SYNOPSIS # __fish_config_sync_logging # @@ -29,7 +32,7 @@ function __fish_config_sync_logging --description 'Sync C5 logging state: sentin set -l yay_wrapper "$HOME/.local/bin/yay" set -l wrapper_version 1 - if __fish_config_op_enabled __fish_config_op_logging + if __fish_config_op_enabled (status current-function) # Logging enabled: remove sentinel and regenerate wrappers if binaries exist rm -f $sentinel diff --git a/functions/_zellij_dump_log.fish b/functions/_zellij_dump_log.fish index ab036c7..46ab9bb 100644 --- a/functions/_zellij_dump_log.fish +++ b/functions/_zellij_dump_log.fish @@ -1,6 +1,9 @@ # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later +# COMPONENT +# logging/multiplexer-capture +# # SYNOPSIS # _zellij_dump_log # @@ -23,7 +26,7 @@ # EXAMPLE # _zellij_dump_log function _zellij_dump_log --description 'Dump the current Zellij pane scrollback to a log file, with pruning' - __fish_config_op_enabled __fish_config_op_logging; or return 0 + __fish_config_op_enabled (status current-function); or return 0 set -q ZELLIJ; or return 0 type -q zellij; or return 0 diff --git a/functions/kitty-logging.fish b/functions/kitty-logging.fish index 3c56241..c9c1151 100644 --- a/functions/kitty-logging.fish +++ b/functions/kitty-logging.fish @@ -4,6 +4,9 @@ # CATEGORY # 14-miscellaneous # +# COMPONENT +# logging/terminal-capture +# # SYNOPSIS # kitty-logging [install | uninstall | status | dismiss] [-h] # @@ -97,7 +100,7 @@ function kitty-logging --description 'Install/manage the fish-config Kitty scrol else echo " Watcher file: $c_warn""not installed$c_reset" end - if __fish_config_op_enabled __fish_config_op_logging + if __fish_config_op_enabled (status current-function) echo " C5 logging: $c_ok""enabled$c_reset" else echo " C5 logging: $c_warn""disabled$c_reset (capture is currently inert)" @@ -143,7 +146,7 @@ function kitty-logging --description 'Install/manage the fish-config Kitty scrol end echo "$c_ok""→ Installed.$c_reset Watcher wired into $conf" echo " $c_dim""Restart Kitty (new windows) for it to take effect.$c_reset" - if not __fish_config_op_enabled __fish_config_op_logging + if not __fish_config_op_enabled (status current-function) echo " $c_warn""Note: C5 logging is disabled, so capture is currently inert.$c_reset" end return 0 From 6b2d3bf491a8d06d642ac093b38a848ae4b4bd19 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Tue, 18 Aug 2026 02:39:47 -0400 Subject: [PATCH 19/30] refactor(config): migrate multi-category files and config.fish to named sites Adds multi-site `# COMPONENT` headers and converts every guard call site in conf.d/tricks.fish, conf.d/abbr.fish, functions/smart_exit.fish, conf.d/yay-wrapper.fish, conf.d/paru-wrapper.fish, conf.d/wakatime.fish, conf.d/first_run.fish, and config.fish (9 sites across 3 categories) to the self-identifying `(status current-function|basename) ` calling convention. Regenerates conf.d/__fish_config_op_registry.fish. --- conf.d/__fish_config_op_registry.fish | 46 +++++++++++++++++++++++++++ conf.d/abbr.fish | 10 ++++-- conf.d/first_run.fish | 8 +++-- conf.d/paru-wrapper.fish | 8 +++-- conf.d/tricks.fish | 12 ++++--- conf.d/wakatime.fish | 8 +++-- conf.d/yay-wrapper.fish | 8 +++-- config.fish | 29 +++++++++++------ functions/smart_exit.fish | 8 +++-- 9 files changed, 111 insertions(+), 26 deletions(-) diff --git a/conf.d/__fish_config_op_registry.fish b/conf.d/__fish_config_op_registry.fish index 96bda28..3691228 100644 --- a/conf.d/__fish_config_op_registry.fish +++ b/conf.d/__fish_config_op_registry.fish @@ -11,6 +11,8 @@ set -g __fish_config_op_registry_keys \ __fish_config_sync_logging: \ __fish_user_dots_link: \ _zellij_dump_log: \ + abbr:abbr-integrations \ + abbr:abbr-overrides \ agy: \ auto-pull: \ autopair: \ @@ -18,9 +20,20 @@ set -g __fish_config_op_registry_keys \ bash_expands: \ cat: \ claude: \ + config:cachyos-strip-aliases \ + config:cachyos-strip-overrides \ + config:cachyos-tricks \ + config:cdpath \ + config:exit-wiring \ + config:greeting-stamp \ + config:pager-editor-gpg \ + config:path-setup \ + config:vi-mode \ done: \ du: \ edit: \ + first_run:first-run-bootstrap \ + first_run:first-run-greeting \ fish_right_prompt: \ help: \ hist: \ @@ -32,10 +45,14 @@ set -g __fish_config_op_registry_keys \ ls: \ mkdir: \ mv: \ + paru-wrapper:paru-autoexec \ + paru-wrapper:paru-logging \ ping: \ puffer: \ rg: \ rm: \ + smart_exit:exit-plain \ + smart_exit:logging-guard \ split: \ spwin: \ ssh: \ @@ -44,7 +61,13 @@ set -g __fish_config_op_registry_keys \ theme: \ tmux-logging: \ top: \ + tricks:aliases-tricks \ + tricks:overrides-tricks \ upgrade: \ + wakatime:wakatime-autoexec \ + wakatime:wakatime-hook \ + yay-wrapper:yay-autoexec \ + yay-wrapper:yay-logging \ yt-dlp: \ zoxide: @@ -53,6 +76,8 @@ set -g __fish_config_op_registry_values \ "logging/terminal-capture" \ "autoexec/sync" \ "logging/multiplexer-capture" \ + "integrations/terminal-abbrs" \ + "overrides/key-bindings" \ "aliases/dev-tools" \ "autoexec/sync autoexec/sync" \ "overrides/key-bindings" \ @@ -60,9 +85,20 @@ set -g __fish_config_op_registry_values \ "overrides/key-bindings" \ "aliases/filesystem" \ "aliases/dev-tools" \ + "aliases/filesystem" \ + "overrides/key-bindings" \ + "aliases/filesystem" \ + "overrides/environment" \ + "overrides/key-bindings" \ + "greeting/greeting-message" \ + "overrides/environment" \ + "overrides/environment" \ + "overrides/key-bindings" \ "integrations/notifications" \ "aliases/filesystem" \ "aliases/dev-tools" \ + "autoexec/plugin-management" \ + "greeting/first-run" \ "overrides/prompt" \ "aliases/shell-tools" \ "integrations/history-logs" \ @@ -74,10 +110,14 @@ set -g __fish_config_op_registry_values \ "aliases/filesystem" \ "aliases/filesystem" \ "aliases/filesystem" \ + "autoexec/pkg-wrappers" \ + "logging/pkg-logs" \ "aliases/network" \ "overrides/key-bindings" \ "aliases/search" \ "aliases/filesystem" \ + "overrides/key-bindings" \ + "logging/terminal-capture" \ "integrations/window-mgmt" \ "integrations/window-mgmt" \ "aliases/network" \ @@ -86,7 +126,13 @@ set -g __fish_config_op_registry_values \ "overrides/prompt" \ "logging/multiplexer-capture" \ "aliases/monitor" \ + "aliases/filesystem" \ + "overrides/environment" \ "integrations/pkg-upgrade" \ + "autoexec/telemetry" \ + "integrations/notifications" \ + "autoexec/pkg-wrappers" \ + "logging/pkg-logs" \ "aliases/network" \ "aliases/filesystem" diff --git a/conf.d/abbr.fish b/conf.d/abbr.fish index f7ef839..5771e32 100644 --- a/conf.d/abbr.fish +++ b/conf.d/abbr.fish @@ -8,6 +8,10 @@ # This file contains all the abbreviations for the terminal. # It is sourced by Fish on startup. +# COMPONENT +# site abbr-integrations: integrations/terminal-abbrs +# site abbr-overrides: overrides/key-bindings + # Neovim # @category Editors # @desc nvim @@ -155,7 +159,7 @@ abbr -a ag. agy . # @desc exit abbr -a /exit exit # Window-management abbreviations are opinionated (C4 integrations) -if __fish_config_op_enabled __fish_config_op_integrations +if __fish_config_op_enabled (status basename) abbr-integrations if test "$TERM" = xterm-kitty # @category Terminal Windows, Tabs, and Panes # @desc Close current pane/window @@ -208,7 +212,7 @@ abbr -a speedtest-fast fast-cli # Kitty/WezTerm window-management abbreviations are opinionated (C4 # integrations): they assume an active Kitty or WezTerm session. -if __fish_config_op_enabled __fish_config_op_integrations +if __fish_config_op_enabled (status basename) abbr-integrations # Window Creation (OS Windows) if test "$TERM" = xterm-kitty # @category Terminal Windows, Tabs, and Panes @@ -666,7 +670,7 @@ abbr -a url-open open-url ### History Expansions and Substitutions ### # Bash-style history expansion is opinionated (C3 overrides), gated atomically # with conf.d/tricks.fish, conf.d/puffer.fish, and functions/expand_*.fish. -if __fish_config_op_enabled __fish_config_op_overrides +if __fish_config_op_enabled (status basename) abbr-overrides # @category History Expansion # @name !^ # @desc Expand to the first argument of the previous command diff --git a/conf.d/first_run.fish b/conf.d/first_run.fish index d098456..0939edf 100644 --- a/conf.d/first_run.fish +++ b/conf.d/first_run.fish @@ -7,6 +7,10 @@ # # Runs exactly once on the first interactive fish session after install. # To reset for testing, run: set -Ue __fish_config_first_run_complete +# +# COMPONENT +# site first-run-greeting: greeting/first-run +# site first-run-bootstrap: autoexec/plugin-management # Exit early in non-interactive shells (scripts, completions, subshells) if not status is-interactive @@ -36,7 +40,7 @@ end # Printing a first-run welcome banner is opinionated (C6 greeting). The # first-run state variable is already set unconditionally above, so # disabling the greeting never re-triggers this file. -if __fish_config_op_enabled __fish_config_op_greeting +if __fish_config_op_enabled (status basename) first-run-greeting echo "" echo " Welcome to your fish shell configuration!" echo " Run 'help config' for offline documentation." @@ -48,7 +52,7 @@ end # Startup side-effects below (Fisher curl, fisher update, theme apply) are # opinionated (C2 auto-execution). The first-run state variable is already # set above either way, so disabling auto-exec never re-triggers this file. -if not __fish_config_op_enabled __fish_config_op_autoexec +if not __fish_config_op_enabled (status basename) first-run-bootstrap return end diff --git a/conf.d/paru-wrapper.fish b/conf.d/paru-wrapper.fish index 13e3159..bef1e54 100644 --- a/conf.d/paru-wrapper.fish +++ b/conf.d/paru-wrapper.fish @@ -6,12 +6,16 @@ # bars are preserved, renders the captured animation to a clean static log # (via scripts/clean_progress_log.py), and prunes old logs. +# COMPONENT +# site paru-autoexec: autoexec/pkg-wrappers +# site paru-logging: logging/pkg-logs + # Auto-generating a wrapper in ~/.local/bin is opinionated (C2 auto-exec). # Wrapper generation is also gated by C5 (Logging & Capture). -__fish_config_op_enabled __fish_config_op_autoexec; or return +__fish_config_op_enabled (status basename) paru-autoexec; or return # C5 — Logging & Capture: remove generated wrapper and skip when logging is off -if not __fish_config_op_enabled __fish_config_op_logging +if not __fish_config_op_enabled (status basename) paru-logging if test -f "$HOME/.local/bin/paru" and grep -q "# paru-wrapper-version:" "$HOME/.local/bin/paru" 2>/dev/null rm -f "$HOME/.local/bin/paru" diff --git a/conf.d/tricks.fish b/conf.d/tricks.fish index 4662a49..8c1a5e5 100644 --- a/conf.d/tricks.fish +++ b/conf.d/tricks.fish @@ -7,6 +7,10 @@ # │ system aliases, and history/backup utilities │ # ╰──────────────────────────────────────────────────────────╯ +# COMPONENT +# site aliases-tricks: aliases/filesystem +# site overrides-tricks: overrides/environment + ## Environment setup # Apply .profile: use this to put fish compatible .profile stuff in if test -f ~/.fish_profile @@ -24,7 +28,7 @@ end # Format man pages using bat (only if bat is installed) # Overriding $MANPAGER is opinionated (C3 overrides) -if type -q bat; and __fish_config_op_enabled __fish_config_op_overrides +if type -q bat; and __fish_config_op_enabled (status basename) overrides-tricks set -gx MANROFFOPT -c set -gx MANPAGER "sh -c 'col -bx | bat -l man -p'" end @@ -37,7 +41,7 @@ set -gx __done_notification_urgency_level low # Functions needed for !! and !$ https://github.com/oh-my-fish/plugin-bang-bang # The bang-bang system is opinionated (C3 overrides) and is gated atomically # here, in conf.d/abbr.fish, conf.d/puffer.fish, and functions/expand_*.fish. -if __fish_config_op_enabled __fish_config_op_overrides +if __fish_config_op_enabled (status basename) overrides-tricks function __history_previous_command switch (commandline -t) case "!" @@ -83,7 +87,7 @@ end # Fish command history override to show timestamps # Shadowing the history command is opinionated (C1 aliasing); when disabled, # the function is never defined and fish's stock history behavior applies. -if __fish_config_op_enabled __fish_config_op_aliases +if __fish_config_op_enabled (status basename) aliases-tricks function history builtin history --show-time='%F %T ' end @@ -123,7 +127,7 @@ alias .....='cd ../../../..' alias ......='cd ../../../../..' # Silent flag injection into POSIX tools is opinionated (C1 aliasing) -if __fish_config_op_enabled __fish_config_op_aliases +if __fish_config_op_enabled (status basename) aliases-tricks # Tools & Core command color overrides # @category Shell Aliases # @desc dir --color=auto diff --git a/conf.d/wakatime.fish b/conf.d/wakatime.fish index 91c88f4..db97a43 100644 --- a/conf.d/wakatime.fish +++ b/conf.d/wakatime.fish @@ -5,11 +5,15 @@ # see: https://github.com/ik11235/wakatime.fish ### +# COMPONENT +# site wakatime-autoexec: autoexec/telemetry +# site wakatime-hook: integrations/notifications + # Local modification: opinionated guard (AGENTS.md Task #3). WakaTime # reporting is classified under both C2 auto-execution and C4 integrations; # disabling either category skips registering the hook. -__fish_config_op_enabled __fish_config_op_autoexec; or exit -__fish_config_op_enabled __fish_config_op_integrations; or exit +__fish_config_op_enabled (status basename) wakatime-autoexec; or exit +__fish_config_op_enabled (status basename) wakatime-hook; or exit function __register_wakatime_fish_before_exec -e fish_postexec if set -q FISH_WAKATIME_DISABLED diff --git a/conf.d/yay-wrapper.fish b/conf.d/yay-wrapper.fish index 203f306..53da589 100644 --- a/conf.d/yay-wrapper.fish +++ b/conf.d/yay-wrapper.fish @@ -6,12 +6,16 @@ # bars are preserved, renders the captured animation to a clean static log # (via scripts/clean_progress_log.py), and prunes old logs. +# COMPONENT +# site yay-autoexec: autoexec/pkg-wrappers +# site yay-logging: logging/pkg-logs + # Auto-generating a wrapper in ~/.local/bin is opinionated (C2 auto-exec). # Wrapper generation is also gated by C5 (Logging & Capture). -__fish_config_op_enabled __fish_config_op_autoexec; or return +__fish_config_op_enabled (status basename) yay-autoexec; or return # C5 — Logging & Capture: remove generated wrapper and skip when logging is off -if not __fish_config_op_enabled __fish_config_op_logging +if not __fish_config_op_enabled (status basename) yay-logging if test -f "$HOME/.local/bin/yay" and grep -q "# yay-wrapper-version:" "$HOME/.local/bin/yay" 2>/dev/null rm -f "$HOME/.local/bin/yay" diff --git a/config.fish b/config.fish index deef5fb..fe1a45b 100644 --- a/config.fish +++ b/config.fish @@ -21,12 +21,23 @@ # C5 is the one exception: it defaults to disabled and needs an explicit # truthy value — set -U __fish_config_op_logging on +# COMPONENT +# site cachyos-tricks: aliases/filesystem +# site cachyos-strip-aliases: aliases/filesystem +# site cachyos-strip-overrides: overrides/key-bindings +# site pager-editor-gpg: overrides/environment +# site exit-wiring: overrides/key-bindings +# site path-setup: overrides/environment +# site cdpath: overrides/environment +# site vi-mode: overrides/key-bindings +# site greeting-stamp: greeting/greeting-message + # ──────────────────────── Source CachyOS configs ──────────────────────── if test -f /usr/share/cachyos-fish-config/cachyos-config.fish source /usr/share/cachyos-fish-config/cachyos-config.fish # Surgically overriding the distro config is opinionated (C3 overrides): # skip it entirely when overrides are disabled, keeping CachyOS defaults. - if __fish_config_op_enabled __fish_config_op_overrides + if __fish_config_op_enabled (status basename) cachyos-tricks # Source our tricks over the cachyOS config test -f "$__fish_config_dir/conf.d/tricks.fish" and source "$__fish_config_dir/conf.d/tricks.fish" @@ -41,7 +52,7 @@ if test -f /usr/share/cachyos-fish-config/cachyos-config.fish # The distro config ships opinionated pieces of its own (it is the origin # of tricks.fish); strip them when the matching category is disabled so # the guards hold on CachyOS systems too. - if not __fish_config_op_enabled __fish_config_op_aliases + if not __fish_config_op_enabled (status basename) cachyos-strip-aliases for _fname in grep fgrep egrep dir vdir wget functions -q $_fname; and functions --erase $_fname end @@ -53,7 +64,7 @@ if test -f /usr/share/cachyos-fish-config/cachyos-config.fish and source $__fish_data_dir/functions/$_fname.fish end end - if not __fish_config_op_enabled __fish_config_op_overrides + if not __fish_config_op_enabled (status basename) cachyos-strip-overrides for _fname in __history_previous_command __history_previous_command_arguments functions -q $_fname; and functions --erase $_fname end @@ -95,7 +106,7 @@ set -q WORDLIST; or set -gx WORDLIST "$XDG_CONFIG_HOME/hunspell_en_US" # ─────────────────────────── Pager variables ──────────────────────────── # Overriding $PAGER, $EDITOR, and $GPG_TTY is opinionated (C3 overrides) -if __fish_config_op_enabled __fish_config_op_overrides +if __fish_config_op_enabled (status basename) pager-editor-gpg if type -q ov set -gx PAGER ov else if type -q less @@ -136,7 +147,7 @@ and set -gx SCROLLBACK_HISTORY_MAX_FILES $__fish_scrollback_history_max_files # Wire up a clean exit function that won't fire on background subshells # Replacing the exit builtin is opinionated (C3 overrides); smart_exit also # guards itself so a live toggle takes effect without restarting the shell. -if status is-interactive; and __fish_config_op_enabled __fish_config_op_overrides +if status is-interactive; and __fish_config_op_enabled (status basename) exit-wiring function exit --description 'Safe interactive exit' # If the smart_exit file exists in our function path, invoke it explicitly if functions -q smart_exit @@ -153,7 +164,7 @@ end # the cargo bin directory is moved to the end of the PATH, which can help avoid conflicts # with system-installed Rust tools while still allowing user-installed cargo binaries to be found. # PATH setup is opinionated (C3 overrides) -if __fish_config_op_enabled __fish_config_op_overrides +if __fish_config_op_enabled (status basename) path-setup fish_add_path $HOME/.local/bin # Standard user-local executables (XDG spec) fish_add_path $HOME/.local/share/../bin # Alternative/legacy path for local user binaries fish_add_path $HOME/Applications # User-installed applications and standalone apps @@ -176,7 +187,7 @@ end # so if you have a directory named 'myproject' in the current directory, # running 'cd myproject' will take you there instead of $HOME/projects/myproject. # CDPATH injection is opinionated (C3 overrides) -if __fish_config_op_enabled __fish_config_op_overrides +if __fish_config_op_enabled (status basename) cdpath set -gx CDPATH . $HOME/projects $HOME end @@ -191,7 +202,7 @@ if status is-interactive # This is optional but can improve the user experience for those who prefer Vi-style key bindings. # Global Vi mode is opinionated (C3 overrides); without it fish keeps its # default Emacs-style bindings. - if __fish_config_op_enabled __fish_config_op_overrides + if __fish_config_op_enabled (status basename) vi-mode set -g fish_key_bindings fish_vi_key_bindings end @@ -251,7 +262,7 @@ if status is-interactive # function that distro configs set (e.g., CachyOS defines it as fastfetch). # This runs last inside the interactive block so our empty definition wins # over whatever cachyos-config.fish or vendor conf.d installed. - if not __fish_config_op_enabled __fish_config_op_greeting + if not __fish_config_op_enabled (status basename) greeting-stamp function fish_greeting end end diff --git a/functions/smart_exit.fish b/functions/smart_exit.fish index 732103b..6b509e7 100644 --- a/functions/smart_exit.fish +++ b/functions/smart_exit.fish @@ -4,6 +4,10 @@ # CATEGORY # 11-pager-and-logging # +# COMPONENT +# site exit-plain: overrides/key-bindings +# site logging-guard: logging/terminal-capture +# # SYNOPSIS # smart_exit [-h] [-n] # @@ -32,7 +36,7 @@ function smart_exit --description 'Capture colorized scrollback before exiting, # Opinionated guard (C3): exit plainly when overrides are disabled. # This composes with Task #4's __fish_config_enable_logging, which will # gate only the scrollback capture while leaving the exit wrapper active. - if not __fish_config_op_enabled __fish_config_op_overrides + if not __fish_config_op_enabled (status current-function) exit-plain builtin exit $argv end @@ -60,7 +64,7 @@ function smart_exit --description 'Capture colorized scrollback before exiting, # C5 — Logging & Capture: skip all capture when logging is disabled. # When disabled, tell Kitty the window is handled so its watcher doesn't # capture either — belt-and-suspenders alongside the sentinel file. - if not __fish_config_op_enabled __fish_config_op_logging + if not __fish_config_op_enabled (status current-function) logging-guard if test -n "$KITTY_WINDOW_ID" kitty @ set-user-vars "logged_by_shell=true" 2>/dev/null end From 207540c093291641aa00782b7b22a498f52c843a Mon Sep 17 00:00:00 2001 From: Rootiest Date: Tue, 18 Aug 2026 02:45:32 -0400 Subject: [PATCH 20/30] fix(config): restore cachyos-tricks site to its documented overrides category The site was mistagged aliases/filesystem in the prior commit. The original code's own comment above the guarded block ("Surgically overriding the distro config is opinionated (C3 overrides): skip it entirely when overrides are disabled") documents this as an overrides decision, and it gated __fish_config_op_overrides pre-migration. Retag to overrides/environment (matching tricks.fish's own sibling site for the equivalent action) so aliases=on/overrides=off keeps skipping tricks.fish/ls/lt/cleanup/copy, as originally documented -- no silent user-facing behavior change. --- conf.d/__fish_config_op_registry.fish | 2 +- config.fish | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conf.d/__fish_config_op_registry.fish b/conf.d/__fish_config_op_registry.fish index 3691228..70490ca 100644 --- a/conf.d/__fish_config_op_registry.fish +++ b/conf.d/__fish_config_op_registry.fish @@ -87,7 +87,7 @@ set -g __fish_config_op_registry_values \ "aliases/dev-tools" \ "aliases/filesystem" \ "overrides/key-bindings" \ - "aliases/filesystem" \ + "overrides/environment" \ "overrides/environment" \ "overrides/key-bindings" \ "greeting/greeting-message" \ diff --git a/config.fish b/config.fish index fe1a45b..82a5318 100644 --- a/config.fish +++ b/config.fish @@ -22,7 +22,7 @@ # truthy value — set -U __fish_config_op_logging on # COMPONENT -# site cachyos-tricks: aliases/filesystem +# site cachyos-tricks: overrides/environment # site cachyos-strip-aliases: aliases/filesystem # site cachyos-strip-overrides: overrides/key-bindings # site pager-editor-gpg: overrides/environment From db21efee0cb8ef2941d1598b3429aa28a5431983 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Tue, 18 Aug 2026 02:56:35 -0400 Subject: [PATCH 21/30] fix(config): split tricks.fish overrides-tricks into manpager/bang sites overrides-tricks conflated two distinct C3 concerns under one shared site (overrides/environment), so __fish_config_op_overrides_key_bindings off left tricks.fish's bang-bang bindings active while abbr.fish/puffer.fish/ config.fish's equivalent sites correctly disabled -- a half-dismantled bang-bang system per docs/manual's own atomic-gating claim. Split into tricks-manpager (overrides/environment, matches PAGER/EDITOR/CDPATH) and tricks-bang (overrides/key-bindings, matches abbr.fish/puffer.fish/ config.fish's bang-related sites, per docs/manual/08-components-reference/03-c3-key-and-environment-overrides.md). Also fixes stale prose in config.fish's top-of-file comment: the guard signature is now [], not . --- conf.d/__fish_config_op_registry.fish | 4 +++- conf.d/tricks.fish | 7 ++++--- config.fish | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/conf.d/__fish_config_op_registry.fish b/conf.d/__fish_config_op_registry.fish index 70490ca..23471be 100644 --- a/conf.d/__fish_config_op_registry.fish +++ b/conf.d/__fish_config_op_registry.fish @@ -62,7 +62,8 @@ set -g __fish_config_op_registry_keys \ tmux-logging: \ top: \ tricks:aliases-tricks \ - tricks:overrides-tricks \ + tricks:tricks-bang \ + tricks:tricks-manpager \ upgrade: \ wakatime:wakatime-autoexec \ wakatime:wakatime-hook \ @@ -127,6 +128,7 @@ set -g __fish_config_op_registry_values \ "logging/multiplexer-capture" \ "aliases/monitor" \ "aliases/filesystem" \ + "overrides/key-bindings" \ "overrides/environment" \ "integrations/pkg-upgrade" \ "autoexec/telemetry" \ diff --git a/conf.d/tricks.fish b/conf.d/tricks.fish index 8c1a5e5..a3a5474 100644 --- a/conf.d/tricks.fish +++ b/conf.d/tricks.fish @@ -9,7 +9,8 @@ # COMPONENT # site aliases-tricks: aliases/filesystem -# site overrides-tricks: overrides/environment +# site tricks-manpager: overrides/environment +# site tricks-bang: overrides/key-bindings ## Environment setup # Apply .profile: use this to put fish compatible .profile stuff in @@ -28,7 +29,7 @@ end # Format man pages using bat (only if bat is installed) # Overriding $MANPAGER is opinionated (C3 overrides) -if type -q bat; and __fish_config_op_enabled (status basename) overrides-tricks +if type -q bat; and __fish_config_op_enabled (status basename) tricks-manpager set -gx MANROFFOPT -c set -gx MANPAGER "sh -c 'col -bx | bat -l man -p'" end @@ -41,7 +42,7 @@ set -gx __done_notification_urgency_level low # Functions needed for !! and !$ https://github.com/oh-my-fish/plugin-bang-bang # The bang-bang system is opinionated (C3 overrides) and is gated atomically # here, in conf.d/abbr.fish, conf.d/puffer.fish, and functions/expand_*.fish. -if __fish_config_op_enabled (status basename) overrides-tricks +if __fish_config_op_enabled (status basename) tricks-bang function __history_previous_command switch (commandline -t) case "!" diff --git a/config.fish b/config.fish index 82a5318..acae248 100644 --- a/config.fish +++ b/config.fish @@ -7,7 +7,7 @@ # ───────────────────── Opinionated component guards ───────────────────── # Opinionated components (AGENTS.md Task #3) are wrapped in -# __fish_config_op_enabled guards throughout this file and conf.d/. +# __fish_config_op_enabled [] guards throughout this file and conf.d/. # The helper always evaluates the master switch __fish_config_opinionated # first (falsy disables everything), then the per-category opt-out variable: # __fish_config_op_aliases C1 — command shadows / flag injection From 7ad3b90503d9c42c78eedfe3dea0dd1e5a003e00 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Tue, 18 Aug 2026 03:01:22 -0400 Subject: [PATCH 22/30] feat(config-settings): add sub-category drill-down navigation state --- functions/__config_settings_subcats.fish | 65 ++++++++++++++++++++++++ functions/config-settings.fish | 53 +++++++++++++++---- 2 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 functions/__config_settings_subcats.fish diff --git a/functions/__config_settings_subcats.fish b/functions/__config_settings_subcats.fish new file mode 100644 index 0000000..52a2ad8 --- /dev/null +++ b/functions/__config_settings_subcats.fish @@ -0,0 +1,65 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# __config_settings_subcats +# +# DESCRIPTION +# Prints the sub-category rows for one C1-C6 category, one per line as +# "\t