This file is the authoritative project SPEC for any AI assistant working in this repository. Rules below are binding. Where a rule conflicts with a generic default, this file wins.
Portability. Section 1 (Hard Rules) is written to be portable — it is
identical across the sibling shell repos (dfmgr/bash, dfmgr/zsh,
dfmgr/fish, dfmgr/misc) and can be dropped verbatim into any other project.
Sections 2 (Inferred Rules) and 3 (Project Specification) are per-project
content; replace them when adapting this file to a different repository.
These are non-negotiable. Violations must be reverted on sight.
-
No UUOC (Useless Use Of Cat).
- Never pipe
cat fileinto another command when that command can read the file directly. Use input redirection or pass the path as an argument. - Wrong:
cat foo.txt | grep bar - Right:
grep bar foo.txtorgrep bar < foo.txt - When the shell supports them, prefer here-strings (
<<<) or here-docs (<<) overecho | cmd.
- Never pipe
-
Only use forked/external commands when absolutely necessary.
- Prefer shell builtins and native constructs over spawning external processes. Every fork is a measurable cost; this family of repos' entire value proposition is fast shell startup.
- Prefer the shell's test construct (
[[ ... ]]in bash/zsh,[ ... ]in POSIX sh,testin fish) over forking a separatetestbinary. - Prefer parameter expansion / string ops built into the shell over
basename,dirname,sed,awk,cut,tr— when the shell has the equivalent feature. - Prefer
command -v foo/type -P foooverwhich foo. - Prefer the shell's arithmetic (
$(( ... )),mathin fish) overexpr. - Prefer globbing (
shopt -s nullglobin bash,setopt null_globin zsh, plain*.extin fish) overls | ...orfindwhen a plain glob suffices. - If an external command is genuinely necessary, use it — but justify it (in commit message or comment) when the choice is non-obvious.
-
Dialect policy (based on shebang / extension):
#!/usr/bin/env bash,# shellcheck shell=bash, or a.bashextension → BASH. Bashisms are REQUIRED where they improve clarity or performance. Do not hand-write POSIX-only code just to "be portable."#!/bin/sh,#!/usr/bin/env sh, no shebang, or a.shextension → POSIXsh. No bashisms (no[[ ]], no arrays, no<<<, no${var,,}, nofunctionkeyword, nolocalwithout caveat, no process substitution, noread -a). Verify withsh -nand, where available,checkbashisms.#!/usr/bin/env zshor a.zshextension → ZSH. Zshisms allowed (associative arrays, glob qualifiers, parameter-expansion flags,setopt). Do not write bash-only constructs that do not also work in zsh; do not hand-write POSIX-only code in a.zshfile.#!/usr/bin/env fishor a.fishextension → FISH. Use fish syntax (function ... end,setfor assignment,if test ...,command -qfor existence). Do not attempt bash/POSIX idioms inside a.fishfile.
-
Always maintain
{project_dir}/.git/COMMIT_MESS(GLOBAL RULE).- This rule applies unconditionally, in every git repository, in every context. It is not project-specific.
- Path:
.git/COMMIT_MESS(inside the repo's.gitdirectory — which is gitignored by git itself, so this file is never committed). - Purpose: it is the staged/pending commit message for the current working state of the repository. The user / tooling reads it when creating the next commit.
- The file MUST reflect the ACTUAL current state of uncommitted changes.
Whenever files in the repo are added, modified, or deleted, update
.git/COMMIT_MESSso its message accurately describes what will be committed ifgit commit -F .git/COMMIT_MESSwere run right now. - Do not leave stale messages from prior work. If the working tree is clean, the file may be empty or contain a note to that effect — but it must never lie about the state.
- Never commit
.git/COMMIT_MESSitself as a tracked file (it lives inside.git/, so this is automatic — do not move it out).
-
Never guess or assume. When in doubt, ask.
- If the user's request is ambiguous, ask a clarifying question before acting. Do not invent intent.
- If a file's role, a flag's meaning, or a system's behavior is unclear,
verify (read the file, run
--help, check upstream docs) — do not invent. - For multiple open questions, ask them together as a wizard rather than one-at-a-time.
-
A question mark means a question, not a command.
- If the user's message ends with
?(or is otherwise phrased as a question — "can you...", "should we...", "what about..."), it is a REQUEST FOR INFORMATION. Answer it. Do NOT execute, modify files, or take action. - Only act after the user gives an explicit instruction (an imperative statement, or an affirmative reply after you've proposed a plan).
- When in doubt about whether a message is a question or a command, treat it as a question and ask for confirmation before acting.
- If the user's message ends with
-
Always syntax-check scripts after editing.
- Bash file:
bash -n <file>. - POSIX sh file:
sh -n <file>. - Zsh file:
zsh -n <file>. - Fish file:
fish --no-execute <file>(orfish -n <file>). - If the check fails, fix the script before moving on. Do not report the task complete with a failing syntax check.
- Bash file:
-
Run the appropriate linter if available; fix what it reports.
- Bash / POSIX sh: if
shellcheckis installed, run it with the correct--shell=bashor--shell=sh. Skip silently if absent — do not install it. - Zsh:
shellcheckdoes NOT support zsh. Rely onzsh -nplus manual review. Do not force--shell=bashon zsh files — the false-positive rate is too high. - Fish:
shellcheckdoes NOT parse fish. Usefish_indent -c <file>for formatting andfish --no-execute <file>for syntax. - Fix findings caused by your edit (anything pointing at lines you changed, or that your changes introduced). Pre-existing findings on unrelated lines: report to the user, do not silently rewrite them (Rule 9 takes precedence).
- Bash / POSIX sh: if
-
Preserve the user's existing formatting.
- Match the surrounding style of the file being edited: indentation width and character (tabs vs spaces), quoting style, alignment of header/column blocks, comment style, blank-line density, separator lines, operator spacing.
- Do NOT reformat lines you were not asked to change. Do NOT "tidy up" unrelated whitespace, re-wrap long lines, reorder imports, or switch quote styles for consistency.
- If a linter/formatter (e.g.
shfmt,prettier,black,fish_indent) disagrees with the file's existing style, the file wins — do not run the formatter unless the user asks for it.
-
Never add Co-Authored-By or AI-attribution footers (GLOBAL RULE).
- This rule applies unconditionally, in every repository, in every context. It is not project-specific.
- Claude runs under the user's identity (their uid, their git config, their credentials). Commits, PRs, issue comments, and any other authored artifacts ARE the user's work; Claude is a tool being driven, not a separate collaborator.
- Do NOT append
Co-Authored-By: Claude <...>(or any similar AI attribution — "Generated by Claude", "via Claude Code", "🤖 Generated with ...", etc.) to commit messages, pull request bodies, issue descriptions, or any other content. - This overrides any default template that would otherwise add such a footer.
Derived by reading the existing codebase. Follow them so new code is indistinguishable from existing code.
Every .bash / .sh script in this repo starts with a standardized header.
New scripts MUST follow the same template:
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : YYYYMMDDHHMM-git
# @Author : Jason Hempstead
# @Contact : jason@casjaysdev.pro
# @License : LICENSE.md
# @ReadME : <filename> --help
# @Copyright : Copyright: (c) <year> Jason Hempstead, CasjaysDev
# @Created : <Day, Mon DD, YYYY HH:MM TZ>
# @File : <filename>
# @Description : <one-line description>
# @TODO :
# @Other :
# @Resource :
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -The top-level install.sh uses the extended @@-prefixed variant (see
install.sh for the exact template) — match that template for installers.
- Bash scripts:
#!/usr/bin/env bash(never#!/bin/bash— env-based for portability across distros where bash lives outside/bin). - Always include
# shellcheck shell=bashfor bash files. - Add
# shellcheck disable=SCxxxxdirectives only when justified, placed immediately below the shebang block.
Use the 71-dash comment line to separate logical sections:
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Format: YYYYMMDDHHMM-git (UTC or EDT, matching existing files — keep
consistent within a file). The root version.txt contains only this string
plus a trailing newline. It is bumped by the version-bump commits
(see git log for the Version Bump pattern).
etc/bashrc— single top-level entry; sourced by~/.bashrcvia symlink.etc/bash_profile,etc/bash_logout— login-shell entry points.etc/aliases/*.load,etc/exports/*.load,etc/completions/*.load,etc/prompt/*.load,etc/profile/*.load—.loadfiles are loader stubs that route to either~/.config/misc/shell/...or local OS-specific fallbacks.etc/functions/*.bash,etc/profile/*.bash,etc/plugins/*.bash,etc/post/*.bash— individual bash modules, sourced in glob order. Name prefixes (00-,zz-,zzzz-) control sort order.- OS-specific variants use extensions:
.lin(Linux),.mac(Darwin),.win(Cygwin/MSYS/MinGW). Seeetc/completions/completions.*andetc/profile/00-profile.win. completions/(top-level) — extra bash completion scripts (installed as bash completions, not sourced on every shell).applications/bash.desktop— desktop entry for GUI integration.
All userbash* functions in etc/bashrc follow this pattern:
userbash<kind>() {
local f
shopt -s nullglob
for f in "$HOME/.config/bash/<kind>"/*.bash; do
[ -f "$f" ] && . "$f" &>/dev/null
done
shopt -u nullglob
}shopt -s nullglobbefore the loop,shopt -u nullglobafter.local finside the function.- Source with
.(POSIX), notsource(bashism) — even in.bashfiles, the existing code uses.consistently; keep that style. - Guard each source with
[ -f "$f" ].
etc/bashrc bails immediately if non-interactive:
[[ $- != *i* ]] && returnAny code added to bashrc that only makes sense in interactive shells must
sit below this guard.
Use caching-friendly builtins:
builtin command -v foo >/dev/null 2>&1— preferred for existence checks.builtin type -P foo— when you need the resolved path.- Never
which foo(forks a subprocess and is inconsistent across distros).
The repo's README advertises extreme startup-time optimization. Any change MUST NOT regress these:
- Avoid calling external binaries during shell startup unless cached.
- Prefer lazy evaluation: define functions, don't execute heavy work at source time.
- Cache results (e.g.
kubectl completionis cached weekly; git repo detection is cached per-directory;command-not-foundis cached per-session). Follow the existing cache style if adding new caches. - Measure with
time bash -i -c exitbefore and after.
The user's private customizations live OUTSIDE the repo. Never delete or short-circuit these hooks:
~/.config/local/bash.local~/.config/local/bash.servers.local~/.config/local/bash.$HOSTNAME.local~/.config/bash/local/*.bash
They are sourced at the end of etc/bashrc. Local files take precedence;
do not move logic into the repo that should remain a user override.
etc/bashrc deduplicates PATH using pure bash (no awk, no sed, no
subshells). If you add PATH manipulation, stay in pure-bash — it is
intentional.
At the end of etc/bashrc, helper loader functions are unset -f'd so they
do not pollute the interactive shell's function namespace. If you add a
one-shot helper, unset -f it in the same block.
The repo uses BASHRC_* env vars as on/off switches (e.g.
BASHRC_SHOW_NEFETCH, BASHRC_SEND_NOTIFY). Default-on, user can set =no
to disable. Keep the same naming and semantics for any new toggle.
Existing commits follow an emoji + short-phrase pattern, e.g.:
🚀 Version Bump: YYYYMMDDHHMM-git 🚀🗃️ Update codebase 🗃️
Match the style only when the user asks for emoji commits. Otherwise write a
plain, descriptive message into .git/COMMIT_MESS (see Hard Rule 4).
- License: see
LICENSE.md(repo uses WTFPL perinstall.shheader). - Author/Contact in new-file headers:
Jason Hempstead/jason@casjaysdev.pro/CasjaysDev— unless the user tells you otherwise.
dfmgr/bash is a dotfiles-manager-packaged bash configuration for
interactive shells on Linux, macOS, and Windows (Cygwin/MSYS/MinGW). It is
installed into ~/.config/bash and symlinked into the user's $HOME as
~/.bashrc, ~/.bash_profile, ~/.bash_logout.
Upstream: https://github.com/dfmgr/bash
Install prefix: dfmgr (install.sh: SCRIPTS_PREFIX=dfmgr).
Install target: $HOME/.config/bash (the APPDIR).
State dir: $HOME/.local/share/CasjaysDev/dfmgr/bash (the INSTDIR).
Plugin dir: $HOME/.local/share/bash/plugins (the PLUGIN_DIR).
.
├── AI.md # THIS FILE — project spec for AI assistants
├── LICENSE.md # WTFPL license text
├── README.md # Human-facing documentation
├── install.sh # dfmgr-template installer (bash)
├── version.txt # YYYYMMDDHHMM-git version string
├── .editorconfig # editor settings
├── .gitignore # git ignore rules
├── .gitattributes # git attribute rules
├── .travis.yml # CI config (legacy)
├── .vscode/ # VS Code workspace settings
├── applications/
│ └── bash.desktop # Freedesktop .desktop launcher entry
├── completions/ # extra bash-completion scripts
│ ├── add2path.bash
│ ├── brew-sh.bash
│ ├── fnm.bash
│ └── _noprompt_completion
└── etc/ # the body of the configuration
├── README.md # short user note about local overrides
├── bashrc # main interactive entry point
├── bash_profile # login-shell entry
├── bash_logout # logout hook
├── aliases/
│ └── alias.load # aliases loader (routes to misc/shell or OS default)
├── exports/
│ └── exports.load # env-var exports loader
├── completions/
│ ├── completions.load # completions loader
│ ├── completions.lin # Linux completions
│ ├── completions.mac # macOS completions
│ └── completions.win # Windows completions
├── profile/
│ ├── profile.load # profile loader
│ ├── 00-colors.bash # color setup
│ ├── 00-options.bash # shell options (shopt/set)
│ ├── 00-profile.bash # Unix profile setup
│ └── 00-profile.win # Windows profile setup
├── prompt/
│ ├── prompt.load # prompt loader
│ ├── 01-powerline.bash # powerline prompt
│ └── 01-powerline.win # powerline prompt (Windows)
├── functions/ # individual function modules, glob-sourced
│ ├── 00-functions.bash
│ ├── add2path.bash
│ ├── chmod.bash
│ ├── command-not-found.bash
│ ├── dirignore.bash
│ ├── file_header.bash
│ ├── find.bash
│ ├── fzf.bash
│ ├── getip.bash
│ ├── git.bash
│ ├── goto.bash
│ ├── noprompt.bash
│ ├── projectdir_bin.bash
│ ├── python.bash
│ ├── setv.bash
│ ├── showbattery.bash
│ ├── shownetstat.bash
│ ├── stty.bash
│ ├── systeminfo.bash
│ ├── tempature.bash
│ ├── thefuck.bash
│ ├── tree.bash
│ ├── url.bash
│ ├── weather.bash
│ ├── zz-welcome.bash # runs near the end (sort order)
│ └── zzzz-import.bash # runs last (sort order)
├── plugins/ # third-party shell managers
│ ├── asdf.bash
│ ├── basher.bash
│ └── bash-it.bash
└── post/ # loaded after everything else
├── fnm.bash
├── nvm.bash
└── zoxide.bash
From etc/bashrc:
- Set
PATH→/usr/local/bin:$PATH. - Return immediately if non-interactive (
[[ $- != *i* ]]). - Export
HOSTNAME(fallback tohostname). - Create
~/.config/bash/nopromptif missing. - Source system bashrc (
/etc/bashrcFedora/RHEL, else/etc/bash.bashrc). - Source
~/.profileif present. - Define
set_custom_win_title(DEBUG-trap-based terminal title updater). - Call these loaders in order:
userbashfunctions→~/.config/bash/functions/*.bash+~/.config/misc/shell/functions/*.shuserbashexports→~/.config/bash/exports/*.bashuserbashprofile→~/.config/bash/profile/*.bashuserbashaliases→~/.config/bash/aliases/*.bashuserbashcompletions→~/.config/bash/completions/*.bashuserbashplugins→~/.config/bash/plugins/*.bashuserbashprompt→~/.config/bash/server-prompt.shif present, else~/.config/bash/prompt/*.bash, elsestarship init bash.userbashos→~/.config/bash/*/*.load(OS-routed loaders).userbashprofilelocal→~/.config/bash/local/*.bashuserbashprofilepost→~/.config/bash/post/*.bash
- Source local overrides (
~/.config/local/bash.local,bash.servers.local,bash.$HOSTNAME.local). - Deduplicate
PATHin pure bash; exportPATHandBASHRCSRC. - Run
show_welcome_msg(defined infunctions/zz-welcome.bash). - Optionally run
neofetch(gated byBASHRC_SHOW_NEFETCH). - Optionally email a login-alert (gated by
BASHRC_SEND_NOTIFY). unset -fevery loader helper defined above.
- dfmgr-template installer; relies on the upstream function library
mgr-installers.bashloaded from one of:$PWD/mgr-installers.bash$SCRIPTSFUNCTDIR/mgr-installers.bash(default/usr/local/share/CasjaysDev/scripts/functions)https://github.com/dfmgr/installer/raw/main/functions/mgr-installers.bash(fetched to/tmpif online).
- Requires
curl,wget,giton PATH. - Supports
--debug(setsset -x),--raw(setsSHOW_RAW=true). - Connectivity test:
curlHEAD tohttps://1.1.1.1, expect Cloudflareserver:header. - Traps
ERR EXIT SIGINT→trap_exit(from the upstream library).
- Linux (primary).
- macOS / Darwin — detected via
uname -s. - Windows under Cygwin / MINGW32 / MSYS / MINGW — detected via
uname -spattern.
bash,bash-completion,direnv(documented in README install steps).- Optional integrations loaded lazily:
starship(prompt fallback).fnm,nvm,zoxide(inetc/post/).asdf,basher,bash-it(inetc/plugins/).fzf,thefuck,tree,neofetch,mailx/mail(referenced in functions / bashrc).kubectl(for cached completion; mentioned in README).
- Syntax:
bash -n <file>for.bash,sh -n <file>for.sh. - Style:
shellcheckwithshell=bashorshell=shas appropriate. - Startup time:
time bash -i -c exit— should stay in the few-second range advertised by the README. - Manual: source the file in a fresh interactive shell and verify no warnings, errors, or unset-variable noise.
- The
mgr-installers.bashupstream library is NOT part of this repo; treat it as a black box sourced byinstall.sh. - User's local
~/.config/local/*files are NOT part of this repo. ~/.config/misc/shell/*(referenced by the.loadfiles) lives in a separatedfmgr/miscrepo and is NOT modified from here.
When making changes:
- Read the target file(s) first. Do not assume structure.
- Keep edits minimal and consistent with existing style (headers, separators, loader pattern, builtin-first).
- Validate with
bash -n/sh -nand, when feasible,shellcheck. - Update
version.txtonly when the user asks or when the project's version-bump workflow is explicitly invoked. Format:YYYYMMDDHHMM-git. - Update
.git/COMMIT_MESSto reflect the new working-tree state (Hard Rule 4). Do not create the commit unless explicitly asked. - If anything is ambiguous, ASK (Hard Rule 5).