Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions cstm_cmt_msg_rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Custom Commit Message Rules

## Format
- Subject line: imperative verb + what changed (50 chars max)
- Body: explain WHY, not what (the diff shows what)
- Use present tense: "Add feature" not "Added feature"

## Structure
First line: <type>: <subject>

Body (if needed):
- Context: Why was this change necessary?
- Impact: What does this enable or fix?
- Notes: Any caveats, side effects, or follow-up needed

## Type Prefixes
Use one of these tags:
- feat: New feature or capability
- fix: Bug fix or correction
- refactor: Code restructuring without behavior change
- perf: Performance improvement
- docs: Documentation only
- test: Test additions or updates
- chore: Build, tooling, dependencies
- style: Formatting, whitespace, naming

## Content Guidelines
- Be specific: "Fix null pointer in user login" not "Fix bug"
- Mention affected area: "auth:", "ui:", "api:", etc. when relevant
- Avoid obvious statements: don't say "update file X" if that's clear from diff
- Include ticket/issue reference if applicable: "Fixes #123"

## Examples
Good:
- feat(auth): Add OAuth2 token refresh logic
- fix(api): Prevent race condition in cache writes
- refactor(db): Extract query builder to separate module

Bad:
- Updated files
- Changes
- Fixed stuff
- WIP

## Output Format
Plain text only. No markdown formatting (**bold**, `code`, etc.)
123 changes: 104 additions & 19 deletions resources/diffsense.sh
Original file line number Diff line number Diff line change
Expand Up @@ -149,36 +149,40 @@ set_max_chars_for_model() {
DIFFSENSE_MAX_CHARS=1194000
;;
*)
DIFFSENSE_MAX_CHARS=10000
DIFFSENSE_MAX_CHARS=8000
;;
esac
}

# ---------- help ----------
print_help() {
cat <<'EOF'
Usage: diffsense [MESSAGE STYLE] [AI MODEL] [OPTIONS]
Usage: diffsense [--byo=<path>] [STYLE] [MODEL] [OPTIONS]

BYO (Bring Your Own Prompt):
--byo=<path> Custom prompt file (max 400 chars), relative to current dir.

STYLE:
default Default style. Works when nothing is specified
verbose Detailed multi-line commit message
minimal Single-line, 72-char max subject
--default Default style (if nothing specified)
--verbose Detailed multi-line commit message
--minimal Single-line, 50-char max subject

MODEL:
afm On-device (LOCAL) model. [DEFAULT MODEL]
pcc Perplexity (PRIVATE) model
gpt ChatGPT / OpenAI model
--afm On-device (LOCAL) model [DEFAULT]
--pcc Perplexity (PRIVATE) model
--gpt ChatGPT / OpenAI model

OPTIONS:
--nopopup Disable popup editor in the Shortcut
-h, --help Show this help message and exit
-h, --help Show this help message

Examples:
diffsense
diffsense --verbose
diffsense --verbose --gpt
diffsense --byo=.diffsense-prompt --verbose --gpt
diffsense --byo=prompts/rules.txt --minimal
diffsense --nopopup
diffsense --minimal --nopopup
EOF
}

Expand All @@ -187,8 +191,15 @@ parse_args() {
local message_style="default"
local ai_model="afm"
local nopopup_suffix=""
local byo_path=""

for raw_arg in "$@"; do
# Handle --byo=path separately (before stripping --)
if [[ "$raw_arg" == --byo=* ]]; then
byo_path="${raw_arg#--byo=}"
continue
fi

local arg="${raw_arg#--}"

case "$arg" in
Expand All @@ -215,9 +226,11 @@ parse_args() {
gpt) ai_model_internal="CHATGPT" ;;
esac

echo "$ai_model_internal $message_style $nopopup_suffix"
# Use | as delimiter to handle empty fields
echo "${ai_model_internal}|${message_style}|${nopopup_suffix}|${byo_path}"
}


# ---------- platform ----------
check_platform_and_arch() {
local arch os_major
Expand Down Expand Up @@ -268,7 +281,6 @@ check_git_state() {
fi
}

# ---------- build prompt ----------
# ---------- build prompt ----------
build_prompt() {
case "$1" in
Expand Down Expand Up @@ -317,6 +329,67 @@ Constraints:
esac
}

# ---------- load BYO (bring your own) prompt ----------
load_byo_prompt() {
local path="$1"
local max_chars=400

# No path provided = no BYO
if [[ -z "$path" ]]; then
return 0
fi

# Trim whitespace/newlines
path="${path#"${path%%[![:space:]]*}"}"
path="${path%"${path##*[![:space:]]}"}"

# Resolve path relative to current directory
local resolved_path
if [[ "$path" == /* ]]; then
resolved_path="$path"
elif [[ "$path" == ~* ]]; then
resolved_path="${path/#\~/$HOME}"
else
resolved_path="$(pwd)/$path"
fi

# Check file exists
if [[ ! -f "$resolved_path" ]]; then
echo "⚠️ BYO file not found: $path → Falling back to base prompt." >&2
return 0
fi

# Check file is readable
if [[ ! -r "$resolved_path" ]]; then
echo "⚠️ BYO file unreadable: $path → Falling back to base prompt." >&2
return 0
fi

# Read content using cat
local content
content=$(cat "$resolved_path")
if [[ $? -ne 0 ]]; then
echo "⚠️ BYO file parse error: $path → Falling back to base prompt." >&2
return 0
fi

# Check if empty
if [[ -z "$content" ]]; then
echo "⚠️ BYO file is empty: $path → Falling back to base prompt." >&2
return 0
fi

# Check length
if (( ${#content} > max_chars )); then
echo "⚠️ BYO exceeds ${max_chars} chars (${#content}) → Falling back to base prompt." >&2
return 0
fi

# Success
printf '%s' "$content"
}



# ---------- build exclude args ----------
build_diff_excludes() {
Expand Down Expand Up @@ -557,7 +630,6 @@ build_allocated_diff() {
echo "${preamble}${files_header}${file_summary}${diff_header}${final_diff}"
}


# ---------- prepare diff ----------
prepare_diff() {
local header="$1"
Expand Down Expand Up @@ -605,8 +677,8 @@ commit_changes() {

# ---------- main ----------
diffsense() {
local parsed ai_model message_style nopopup_suffix
local diff header prompt payload commit_msg
local parsed ai_model message_style nopopup_suffix byo_path
local diff header prompt byo_prompt payload commit_msg

if [[ "$#" -gt 0 ]]; then
case "$1" in
Expand All @@ -621,25 +693,36 @@ diffsense() {
exit 1
fi

ai_model=$(awk '{print $1}' <<< "$parsed")
message_style=$(awk '{print $2}' <<< "$parsed")
nopopup_suffix=$(awk '{print $3}' <<< "$parsed")
# Parse with | delimiter
IFS='|' read -r ai_model message_style nopopup_suffix byo_path <<< "$parsed"


# DEBUG
set_max_chars_for_model "$ai_model"

check_platform_and_arch || exit 1
check_is_git_repo
check_git_state

# Build base prompt
prompt=$(build_prompt "$message_style")

# Load and append BYO prompt if provided
if [[ -n "$byo_path" ]]; then
byo_prompt=$(load_byo_prompt "$byo_path")
if [[ -n "$byo_prompt" ]]; then
prompt="${prompt}"$'\n\n'"Additional Instructions:"$'\n'"${byo_prompt}"
fi
fi

header="@@DIFFSENSE_META=${ai_model}${nopopup_suffix}"

# Main diff strategy
diff=$(prepare_diff "$header" "$prompt") || exit 1

payload="${header}"$'\n'"${prompt}"$'\n\n'"${diff}"

# SAFETY: Final truncation to guarantee we never exceed the limit
# SAFETY: Final truncation to guarantee we never exceed the limit
if (( ${#payload} > DIFFSENSE_MAX_CHARS )); then
payload="${payload:0:DIFFSENSE_MAX_CHARS}"
fi
Expand All @@ -648,4 +731,6 @@ diffsense() {
commit_changes "$commit_msg"
}

diffsense "$@"

diffsense "$@"